diff --git a/CHANGELOG.md b/CHANGELOG.md index b707591..90b3461 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,67 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 --- +## [Unreleased] — Refine Phase Where To Go Next section to match prototype (LS-4179) + +### Changed + +- `patterns/sections/phase-where-to-go-next.php`: narrowed the cards row into a `900px` centred container (previously full `align:wide`), tightened the gap between cards (`spacing|20` → `10`), increased the gap between the eyebrow and the cards (`spacing|30` → `40`), increased section bottom padding (`spacing|90` → `100`), reduced the card title to `fontSize:300` and the "Read more" CTA to the standard `fontSize:200` body token, and removed the `verticalAlignment:"top"` on the columns/column blocks so `core/columns`' default equal-height column stretch applies. +- `src/scss/structural/phase-where-to-go-next.scss`: added a scoped override (`.ls-phase-where-to-go-next .is-style-card-link-row`) so each card fills its now-equal-height column (`height:100%`), which combined with the card's existing flex-column layout and the "Read more" link's existing `margin-top:auto` bottom-aligns both CTAs regardless of description length — no fixed heights, no CSS Grid rebuild needed. Also increased card padding (`spacing|20` → `30`), reduced the border radius (`border-radius|400` → `300`), and softened the border colour, scoped to this pattern only so the Work archive and Services page's use of the same shared Card - Link Row style is unaffected. The existing Discover phase accent colour is unchanged. + +([#64](https://github.com/lightspeedwp/ls-theme/pull/64)) + +--- + +## [Unreleased] — Fix FAQ accordion closed-state gap (LS-4179) + +### Fixed + +- `src/scss/structural/faq.scss`: the shared `schema-faq` accordion's closed-row collapse used `grid-template-rows: 0fr` → `1fr`, which does not actually resolve to zero on this element — it is both the grid container and, via its own anonymous content box, the only grid item, so the browser's intrinsic-sizing pass for an auto-height container falls back to the answer text's own content height regardless of the `0fr` value (confirmed via computed styles; `min-height: 0` did not change it either). This left a large empty gap below every closed question. Replaced with a `max-height: 0` → `1000px` collapse, a more reliable technique for this exact shape. Root-caused and fixed in the shared component (not scoped to `.ls-phase-faq`) since it affects every consumer of `section-faq.php`'s accordion, not just this pattern. + +([#64](https://github.com/lightspeedwp/ls-theme/pull/64)) + +--- + +## [Unreleased] — Refine Phase FAQ section to match prototype (LS-4179) + +### Changed + +- `patterns/sections/phase-faq.php`: increased section top padding (`spacing|90` → `100`) for more breathing room before the heading; widened the heading/intro/FAQ containers from `620px`/`880px` to a unified `780px` so the heading stays on one line at desktop without reducing its font size; changed the intro paragraph from centred to left-aligned and reduced its size from `fontSize:300` to the theme's standard `fontSize:200` body-copy token. +- `src/scss/structural/phase-faq.scss`: added a scoped visual redesign of the shared `schema-faq` accordion for this pattern only (`.ls-phase-faq`) — larger corner radius (`border-radius|400`), a subtler rest-state border (colour-mixed down), tighter gap between rows (`spacing|10`), taller/vertically-centred rows (`spacing|50` vertical padding), and a circular plus/minus control (sized via `spacing|50`, tinted with the current phase accent) replacing the shared component's small rotated-chevron icon. Implemented as a scoped override so the homepage FAQ and any other consumer of `schema-faq` are unaffected. +- Fixed a CSS specificity bug in the same file where the shared component's open-state chevron rotation was still winning over the new plus/minus icon's transform, distorting it when a question was expanded. + +([#64](https://github.com/lightspeedwp/ls-theme/pull/64)) + +--- + +## [Unreleased] — Add Phase FAQ and Phase Where To Go Next sections (LS-4179) + +### Added + +- `patterns/sections/phase-faq.php`: shared "Frequently asked questions" section for all six lifecycle phase pages, currently authored with Discover's own five questions. Reuses the existing Yoast FAQ block and its `schema-faq` accordion component (`patterns/section-faq.php`, `assets/css/faq.css`, `assets/js/faq-accordion.js`) as-is — no new accordion, JS, or markup pattern introduced. +- `patterns/sections/phase-where-to-go-next.php`: shared "Where to go next" section for all six lifecycle phase pages, currently authored with Discover's own two next-step cards. Reuses the existing Card - Link Row section style and Link Arrow Accent paragraph style (the same "Explore service"/"Read more" convention already used in `phase-services-in-phase.php`) in a real 2-column row, rather than the source Figma frame's broken 4-column grid. +- `src/scss/structural/phase-faq.scss` → `assets/css/phase-faq.css`: phase-scoped override (via the `page-slug-{phase}` body class, one rule per phase) so the FAQ accordion's open-state border and toggle-icon colour match the current phase's accent instead of the sitewide generic link-accent token, without affecting any other page that uses the shared `schema-faq` component. +- `src/scss/structural/phase-where-to-go-next.scss` → `assets/css/phase-where-to-go-next.css`: same phase-scoped override technique for the Card - Link Row hover border/background/icon colours and the "Read more" link's colour, without affecting the Work archive or Services page, which also use these shared styles. +- Wired both new bundles into `package.json` (`build:css`/`build:css:dev`/`watch:css`), `inc/animations.php` (conditional on the six phase-page slugs, same pattern as the other phase bundles), and `functions.php` (editor styles). + +([#64](https://github.com/lightspeedwp/ls-theme/pull/64)) + +--- + +## [Unreleased] — Refine Phase Support Focus section layout and typography (LS-4179) + +### Changed + +- `patterns/sections/phase-support-focus.php`: widened the eyebrow/heading wrapper from a fixed `620px` content width to the theme's existing wide alignment (`align:"wide"`, no custom `contentSize`) so "AI Readiness Assessment" stays on one line at desktop without changing the global H2 style, heading level, or inventing a new width token; added top margin above the eyebrow/heading group for more breathing room from the previous section. +- `patterns/sections/phase-support-focus.php`: widened the column gutter between the body copy and focus-area card (`spacing|60` → `spacing|70`) so the section reads more balanced, matching the reference design. +- `patterns/sections/phase-support-focus.php`: changed each focus-area row's vertical alignment from `top` to `center` so the hollow-circle bullet centres against its single-line list text instead of sitting high above it. +- `patterns/sections/phase-support-focus.php`: reduced left-column body copy and focus-card heading/list typography from `fontSize:300` to `fontSize:200`, tightened line-height and paragraph spacing, and reduced the CTA's top margin — all using existing typography/spacing tokens. +- `patterns/sections/phase-support-focus.php`: replaced the filled/tinted `lightspeed/dot` icon bullet with a simple hollow outlined circle (1px border, no fill, no icon) in the approved Discover accent colour, matching the reference's bullet style. + +([#64](https://github.com/lightspeedwp/ls-theme/pull/64)) + +--- + ## [Unreleased] — Phase page review fixes ### Fixed diff --git a/assets/css/faq.css b/assets/css/faq.css index 07ee6fa..3e15450 100644 --- a/assets/css/faq.css +++ b/assets/css/faq.css @@ -1 +1 @@ -.schema-faq{display:flex;flex-direction:column;gap:var(--wp--preset--spacing--20)}.schema-faq.is-accordion .schema-faq-section{--ls-faq-border-rest: var(--wp--custom--color--border--card);--ls-faq-border-active: var(--wp--custom--color--link--accent);position:relative;overflow:hidden;border:1px solid var(--ls-faq-border-rest);border-radius:var(--wp--preset--border-radius--200);background:var(--wp--custom--color--surface--card);box-shadow:none;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)}.schema-faq.is-accordion .schema-faq-section:hover{border-color:var(--ls-faq-border-active)}.schema-faq.is-accordion .schema-faq-section.is-open{background:var(--wp--custom--color--surface--card-raised);border-color:var(--ls-faq-border-active)}.schema-faq.is-accordion .schema-faq-section:focus-within{border-color:var(--ls-faq-border-active);box-shadow:0 0 0 2px var(--wp--custom--color--focus--ring)}.schema-faq.is-accordion .schema-faq-question{display:flex;align-items:center;gap:var(--wp--preset--spacing--20);margin:0;padding:var(--wp--preset--spacing--20) var(--wp--preset--spacing--30);font-weight:var(--wp--custom--typography--font-weight--bold);cursor:pointer;transition:inherit}.schema-faq.is-accordion .schema-faq-question::after{content:"";display:block;flex:none;width:.625rem;height:.625rem;margin-inline-start:auto;border-right:2px solid var(--wp--custom--color--link--accent);border-bottom:2px solid var(--wp--custom--color--link--accent);transform:translateY(-1px) rotate(45deg);transform-origin:center;transition:transform var(--wp--custom--animation--duration--base) var(--wp--custom--animation--easing--emphasised),border-color var(--wp--custom--animation--duration--base) var(--wp--custom--animation--easing--standard)}.schema-faq.is-accordion .schema-faq-question:focus-visible{outline:2px solid var(--wp--custom--color--focus--ring);outline-offset:-2px}.schema-faq.is-accordion .schema-faq-section.is-open>.schema-faq-question::after{transform:translateY(1px) rotate(-135deg)}.schema-faq.is-accordion .schema-faq-answer{display:grid;grid-template-rows:0fr;margin:0;padding:0 var(--wp--preset--spacing--30);overflow:hidden;opacity:0;transition:grid-template-rows var(--wp--custom--animation--duration--base) var(--wp--custom--animation--easing--standard),opacity var(--wp--custom--animation--duration--base) var(--wp--custom--animation--easing--standard),padding var(--wp--custom--animation--duration--base) var(--wp--custom--animation--easing--standard)}.schema-faq.is-accordion .schema-faq-section.is-open>.schema-faq-answer{grid-template-rows:1fr;padding:0 var(--wp--preset--spacing--30) var(--wp--preset--spacing--20);opacity:1}@media(prefers-reduced-motion: reduce){.schema-faq.is-accordion .schema-faq-section,.schema-faq.is-accordion .schema-faq-question::after,.schema-faq.is-accordion .schema-faq-answer{transition:none}} +.schema-faq{display:flex;flex-direction:column;gap:var(--wp--preset--spacing--20)}.schema-faq.is-accordion .schema-faq-section{--ls-faq-border-rest: var(--wp--custom--color--border--card);--ls-faq-border-active: var(--wp--custom--color--link--accent);position:relative;overflow:hidden;border:1px solid var(--ls-faq-border-rest);border-radius:var(--wp--preset--border-radius--200);background:var(--wp--custom--color--surface--card);box-shadow:none;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)}.schema-faq.is-accordion .schema-faq-section:hover{border-color:var(--ls-faq-border-active)}.schema-faq.is-accordion .schema-faq-section.is-open{background:var(--wp--custom--color--surface--card-raised);border-color:var(--ls-faq-border-active)}.schema-faq.is-accordion .schema-faq-section:focus-within{border-color:var(--ls-faq-border-active);box-shadow:0 0 0 2px var(--wp--custom--color--focus--ring)}.schema-faq.is-accordion .schema-faq-question{display:flex;align-items:center;gap:var(--wp--preset--spacing--20);margin:0;padding:var(--wp--preset--spacing--20) var(--wp--preset--spacing--30);font-weight:var(--wp--custom--typography--font-weight--bold);cursor:pointer;transition:inherit}.schema-faq.is-accordion .schema-faq-question::after{content:"";display:block;flex:none;width:.625rem;height:.625rem;margin-inline-start:auto;border-right:2px solid var(--wp--custom--color--link--accent);border-bottom:2px solid var(--wp--custom--color--link--accent);transform:translateY(-1px) rotate(45deg);transform-origin:center;transition:transform var(--wp--custom--animation--duration--base) var(--wp--custom--animation--easing--emphasised),border-color var(--wp--custom--animation--duration--base) var(--wp--custom--animation--easing--standard)}.schema-faq.is-accordion .schema-faq-question:focus-visible{outline:2px solid var(--wp--custom--color--focus--ring);outline-offset:-2px}.schema-faq.is-accordion .schema-faq-section.is-open>.schema-faq-question::after{transform:translateY(1px) rotate(-135deg)}.schema-faq.is-accordion .schema-faq-answer{max-height:0;margin:0;padding:0 var(--wp--preset--spacing--30);overflow:hidden;opacity:0;transition:max-height var(--wp--custom--animation--duration--base) var(--wp--custom--animation--easing--standard),opacity var(--wp--custom--animation--duration--base) var(--wp--custom--animation--easing--standard),padding var(--wp--custom--animation--duration--base) var(--wp--custom--animation--easing--standard)}.schema-faq.is-accordion .schema-faq-section.is-open>.schema-faq-answer{max-height:1000px;padding:0 var(--wp--preset--spacing--30) var(--wp--preset--spacing--20);opacity:1}@media(prefers-reduced-motion: reduce){.schema-faq.is-accordion .schema-faq-section,.schema-faq.is-accordion .schema-faq-question::after,.schema-faq.is-accordion .schema-faq-answer{transition:none}} diff --git a/assets/css/phase-common-services.css b/assets/css/phase-common-services.css new file mode 100644 index 0000000..20d0f6a --- /dev/null +++ b/assets/css/phase-common-services.css @@ -0,0 +1 @@ +@media(max-width: 781.98px){.ls-phase-common-services__pills{flex-direction:column !important;align-items:stretch !important}.ls-phase-common-services__pills>.wp-block-group.has-border-color{width:100%;max-width:none;box-sizing:border-box;align-items:flex-start !important;border-radius:var(--wp--preset--border-radius--300) !important}.ls-phase-common-services__pills>.wp-block-group.has-border-color>.wp-block-icon{margin-top:calc((var(--wp--preset--font-size--200)*var(--wp--custom--line-height--paragraph) - 6px)/2)}} diff --git a/assets/css/phase-cta.css b/assets/css/phase-cta.css new file mode 100644 index 0000000..c68f929 --- /dev/null +++ b/assets/css/phase-cta.css @@ -0,0 +1 @@ +.ls-phase-cta{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),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;background-repeat:repeat,repeat,no-repeat;background-size:32px 32px,32px 32px,auto} diff --git a/assets/css/phase-delivery-numbers.css b/assets/css/phase-delivery-numbers.css new file mode 100644 index 0000000..80237a4 --- /dev/null +++ b/assets/css/phase-delivery-numbers.css @@ -0,0 +1 @@ +@media(max-width: 781.98px){.ls-phase-stats-row .is-style-stat-segment{border-inline-end:0;border-block-end:1px solid var(--wp--custom--color--border--card);padding-block-end:var(--wp--preset--spacing--30);margin-block-end:var(--wp--preset--spacing--30)}.ls-phase-stats-row>.wp-block-column:last-child .is-style-stat-segment{border-block-end:0;padding-block-end:0;margin-block-end:0}} diff --git a/assets/css/phase-faq.css b/assets/css/phase-faq.css new file mode 100644 index 0000000..31b1214 --- /dev/null +++ b/assets/css/phase-faq.css @@ -0,0 +1 @@ +.ls-phase-faq .schema-faq{gap:var(--wp--preset--spacing--10)}.ls-phase-faq .schema-faq.is-accordion .schema-faq-section{--ls-faq-border-rest: color-mix(in srgb, var(--wp--custom--color--border--card) 55%, transparent);border-radius:var(--wp--preset--border-radius--400)}.ls-phase-faq .schema-faq.is-accordion .schema-faq-question{position:relative;padding-top:var(--wp--preset--spacing--50);padding-bottom:var(--wp--preset--spacing--50);padding-right:calc(var(--wp--preset--spacing--30) + var(--wp--preset--spacing--50) + var(--wp--preset--spacing--10))}.ls-phase-faq .schema-faq.is-accordion .schema-faq-question::before{content:"";position:absolute;top:50%;right:var(--wp--preset--spacing--30);width:var(--wp--preset--spacing--50);height:var(--wp--preset--spacing--50);transform:translateY(-50%);border-radius:var(--wp--preset--border-radius--500);background:color-mix(in srgb, var(--ls-faq-border-active) 15%, transparent);transition:background-color var(--wp--custom--animation--duration--base) var(--wp--custom--animation--easing--standard)}.ls-phase-faq .schema-faq.is-accordion .schema-faq-question::after{content:"";display:block;position:absolute;top:50%;right:var(--wp--preset--spacing--30);width:var(--wp--preset--spacing--50);height:var(--wp--preset--spacing--50);margin-inline-start:0;border:none;transform:translateY(-50%);transform-origin:center;background-image:linear-gradient(currentColor, currentColor),linear-gradient(currentColor, currentColor);background-position:center;background-size:40% 2px,2px 40%;background-repeat:no-repeat;color:var(--ls-faq-border-active);transition:background-size var(--wp--custom--animation--duration--base) var(--wp--custom--animation--easing--emphasised)}.ls-phase-faq .schema-faq.is-accordion .schema-faq-section.is-open>.schema-faq-question::after{background-size:40% 2px,0 2px;transform:translateY(-50%)}@media(prefers-reduced-motion: reduce){.ls-phase-faq .schema-faq.is-accordion .schema-faq-question::before,.ls-phase-faq .schema-faq.is-accordion .schema-faq-question::after{transition:none}}body.page-slug-discover .ls-phase-faq .schema-faq.is-accordion .schema-faq-section{--ls-faq-border-active: var(--wp--custom--color--phase--discover)}body.page-slug-create .ls-phase-faq .schema-faq.is-accordion .schema-faq-section{--ls-faq-border-active: var(--wp--custom--color--phase--create)}body.page-slug-build .ls-phase-faq .schema-faq.is-accordion .schema-faq-section{--ls-faq-border-active: var(--wp--custom--color--phase--build)}body.page-slug-launch .ls-phase-faq .schema-faq.is-accordion .schema-faq-section{--ls-faq-border-active: var(--wp--custom--color--phase--launch)}body.page-slug-grow .ls-phase-faq .schema-faq.is-accordion .schema-faq-section{--ls-faq-border-active: var(--wp--custom--color--phase--grow)}body.page-slug-evolve .ls-phase-faq .schema-faq.is-accordion .schema-faq-section{--ls-faq-border-active: var(--wp--custom--color--phase--evolve)} diff --git a/assets/css/phase-journey-nav.css b/assets/css/phase-journey-nav.css index ce40130..b9db564 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__step:not(.is-active) .ls-phase-journey-nav__link a{color:var(--wp--custom--color--text--on-dark-muted)}.ls-phase-journey-nav__step:not(.is-active) .ls-phase-journey-nav__link a:hover,.ls-phase-journey-nav__step:not(.is-active) .ls-phase-journey-nav__link a:focus-visible{color:var(--wp--custom--color--text--on-dark)}.ls-phase-journey-nav__step.is-active .ls-phase-journey-nav__link a{color:var(--ls-phase-item-accent)}.ls-phase-journey-nav__step.is-active .ls-phase-journey-nav__link a:hover,.ls-phase-journey-nav__step.is-active .ls-phase-journey-nav__link a:focus-visible{color:var(--wp--custom--color--text--on-dark)}.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-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/assets/css/phase-services-in-phase.css b/assets/css/phase-services-in-phase.css new file mode 100644 index 0000000..94da63a --- /dev/null +++ b/assets/css/phase-services-in-phase.css @@ -0,0 +1 @@ +body.page-slug-discover .ls-phase-services-in-phase .is-style-link-arrow-accent{--ls-link-arrow-colour: var(--wp--custom--color--phase--discover);--ls-link-arrow-hover-colour: var(--wp--custom--color--phase--discover)}body.page-slug-discover .ls-phase-services-in-phase .is-style-card-service-tile{--ls-card-service-tile-border-active: color-mix( in srgb, var(--wp--custom--color--phase--discover) 60%, var(--wp--custom--color--border--card) )}body.page-slug-create .ls-phase-services-in-phase .is-style-link-arrow-accent{--ls-link-arrow-colour: var(--wp--custom--color--phase--create);--ls-link-arrow-hover-colour: var(--wp--custom--color--phase--create)}body.page-slug-create .ls-phase-services-in-phase .is-style-card-service-tile{--ls-card-service-tile-border-active: color-mix( in srgb, var(--wp--custom--color--phase--create) 60%, var(--wp--custom--color--border--card) )}body.page-slug-build .ls-phase-services-in-phase .is-style-link-arrow-accent{--ls-link-arrow-colour: var(--wp--custom--color--phase--build);--ls-link-arrow-hover-colour: var(--wp--custom--color--phase--build)}body.page-slug-build .ls-phase-services-in-phase .is-style-card-service-tile{--ls-card-service-tile-border-active: color-mix( in srgb, var(--wp--custom--color--phase--build) 60%, var(--wp--custom--color--border--card) )}body.page-slug-launch .ls-phase-services-in-phase .is-style-link-arrow-accent{--ls-link-arrow-colour: var(--wp--custom--color--phase--launch);--ls-link-arrow-hover-colour: var(--wp--custom--color--phase--launch)}body.page-slug-launch .ls-phase-services-in-phase .is-style-card-service-tile{--ls-card-service-tile-border-active: color-mix( in srgb, var(--wp--custom--color--phase--launch) 60%, var(--wp--custom--color--border--card) )}body.page-slug-grow .ls-phase-services-in-phase .is-style-link-arrow-accent{--ls-link-arrow-colour: var(--wp--custom--color--phase--grow);--ls-link-arrow-hover-colour: var(--wp--custom--color--phase--grow)}body.page-slug-grow .ls-phase-services-in-phase .is-style-card-service-tile{--ls-card-service-tile-border-active: color-mix( in srgb, var(--wp--custom--color--phase--grow) 60%, var(--wp--custom--color--border--card) )}body.page-slug-evolve .ls-phase-services-in-phase .is-style-link-arrow-accent{--ls-link-arrow-colour: var(--wp--custom--color--phase--evolve);--ls-link-arrow-hover-colour: var(--wp--custom--color--phase--evolve)}body.page-slug-evolve .ls-phase-services-in-phase .is-style-card-service-tile{--ls-card-service-tile-border-active: color-mix( in srgb, var(--wp--custom--color--phase--evolve) 60%, var(--wp--custom--color--border--card) )} diff --git a/assets/css/phase-support-focus.css b/assets/css/phase-support-focus.css new file mode 100644 index 0000000..8520062 --- /dev/null +++ b/assets/css/phase-support-focus.css @@ -0,0 +1 @@ +@media(max-width: 781.98px){.ls-phase-focus-areas>.wp-block-group{align-items:flex-start !important}.ls-phase-focus-areas>.wp-block-group>.wp-block-group.has-border-color{margin-top:calc((var(--wp--preset--font-size--200)*var(--wp--custom--line-height--paragraph) - (2px + var(--wp--preset--spacing--5)*2))/2)}} diff --git a/assets/css/phase-where-to-go-next.css b/assets/css/phase-where-to-go-next.css new file mode 100644 index 0000000..3995bc8 --- /dev/null +++ b/assets/css/phase-where-to-go-next.css @@ -0,0 +1 @@ +.ls-phase-where-to-go-next .is-style-card-link-row{height:100%;padding:var(--wp--preset--spacing--30);border-color:color-mix(in srgb, var(--wp--custom--color--border--card) 55%, transparent);border-radius:var(--wp--preset--border-radius--300)}body.page-slug-discover .ls-phase-where-to-go-next .is-style-card-link-row{--ls-card-link-row-border-active: var(--wp--custom--color--phase--discover);--ls-card-link-row-background-active: color-mix( in srgb, var(--wp--custom--color--phase--discover) 10%, transparent );--ls-card-link-row-icon-active: var(--wp--custom--color--phase--discover)}body.page-slug-discover .ls-phase-where-to-go-next .is-style-link-arrow-accent{--ls-link-arrow-colour: var(--wp--custom--color--phase--discover);--ls-link-arrow-hover-colour: var(--wp--custom--color--phase--discover)}body.page-slug-create .ls-phase-where-to-go-next .is-style-card-link-row{--ls-card-link-row-border-active: var(--wp--custom--color--phase--create);--ls-card-link-row-background-active: color-mix( in srgb, var(--wp--custom--color--phase--create) 10%, transparent );--ls-card-link-row-icon-active: var(--wp--custom--color--phase--create)}body.page-slug-create .ls-phase-where-to-go-next .is-style-link-arrow-accent{--ls-link-arrow-colour: var(--wp--custom--color--phase--create);--ls-link-arrow-hover-colour: var(--wp--custom--color--phase--create)}body.page-slug-build .ls-phase-where-to-go-next .is-style-card-link-row{--ls-card-link-row-border-active: var(--wp--custom--color--phase--build);--ls-card-link-row-background-active: color-mix( in srgb, var(--wp--custom--color--phase--build) 10%, transparent );--ls-card-link-row-icon-active: var(--wp--custom--color--phase--build)}body.page-slug-build .ls-phase-where-to-go-next .is-style-link-arrow-accent{--ls-link-arrow-colour: var(--wp--custom--color--phase--build);--ls-link-arrow-hover-colour: var(--wp--custom--color--phase--build)}body.page-slug-launch .ls-phase-where-to-go-next .is-style-card-link-row{--ls-card-link-row-border-active: var(--wp--custom--color--phase--launch);--ls-card-link-row-background-active: color-mix( in srgb, var(--wp--custom--color--phase--launch) 10%, transparent );--ls-card-link-row-icon-active: var(--wp--custom--color--phase--launch)}body.page-slug-launch .ls-phase-where-to-go-next .is-style-link-arrow-accent{--ls-link-arrow-colour: var(--wp--custom--color--phase--launch);--ls-link-arrow-hover-colour: var(--wp--custom--color--phase--launch)}body.page-slug-grow .ls-phase-where-to-go-next .is-style-card-link-row{--ls-card-link-row-border-active: var(--wp--custom--color--phase--grow);--ls-card-link-row-background-active: color-mix( in srgb, var(--wp--custom--color--phase--grow) 10%, transparent );--ls-card-link-row-icon-active: var(--wp--custom--color--phase--grow)}body.page-slug-grow .ls-phase-where-to-go-next .is-style-link-arrow-accent{--ls-link-arrow-colour: var(--wp--custom--color--phase--grow);--ls-link-arrow-hover-colour: var(--wp--custom--color--phase--grow)}body.page-slug-evolve .ls-phase-where-to-go-next .is-style-card-link-row{--ls-card-link-row-border-active: var(--wp--custom--color--phase--evolve);--ls-card-link-row-background-active: color-mix( in srgb, var(--wp--custom--color--phase--evolve) 10%, transparent );--ls-card-link-row-icon-active: var(--wp--custom--color--phase--evolve)}body.page-slug-evolve .ls-phase-where-to-go-next .is-style-link-arrow-accent{--ls-link-arrow-colour: var(--wp--custom--color--phase--evolve);--ls-link-arrow-hover-colour: var(--wp--custom--color--phase--evolve)} diff --git a/functions.php b/functions.php index e43ccbf..6a1e715 100644 --- a/functions.php +++ b/functions.php @@ -42,6 +42,9 @@ /** Loads bare phase-slug (e.g. /discover/) redirects to their real nested URL */ require get_template_directory() . '/inc/phase-page-redirects.php'; +/** Loads the phase-page body class used to drive the Journey Phases nav's active state */ +require get_template_directory() . '/inc/phase-page-body-class.php'; + /** * Sets up theme supports. */ @@ -91,6 +94,13 @@ function ls_theme_setup() { add_editor_style( 'assets/css/button-secondary.css' ); add_editor_style( 'assets/css/button-phase.css' ); add_editor_style( 'assets/css/phase-journey-nav.css' ); + add_editor_style( 'assets/css/phase-services-in-phase.css' ); + add_editor_style( 'assets/css/phase-cta.css' ); + add_editor_style( 'assets/css/phase-delivery-numbers.css' ); + add_editor_style( 'assets/css/phase-common-services.css' ); + add_editor_style( 'assets/css/phase-support-focus.css' ); + add_editor_style( 'assets/css/phase-faq.css' ); + add_editor_style( 'assets/css/phase-where-to-go-next.css' ); add_editor_style( 'assets/css/featured-work.css' ); add_editor_style( 'assets/css/where-to-fit.css' ); add_editor_style( 'assets/css/homepage-cta.css' ); diff --git a/inc/animations.php b/inc/animations.php index 15480e8..fbe2d22 100644 --- a/inc/animations.php +++ b/inc/animations.php @@ -437,11 +437,84 @@ function ls_theme_get_effect_styles( $context = 'front' ) { 'handle' => 'ls-theme-phase-journey-nav', 'path' => 'assets/css/phase-journey-nav.css', 'contexts' => array( 'front', 'editor' ), - // Same reasoning as button-phase above: fast path for Discover today, plus the - // render_block fallback registered in ls_theme_get_bundle_render_markers() so it keeps - // working once this same pattern is reused on the other five phase pages. + // Shared across all six phase pages (this pattern is now reused on all of them, not + // just Discover), so scoped directly to all six known slugs — matches + // phase-services-in-phase below — rather than relying on the render_block/footer + // fallback, which was printing this after first paint on Create/Build/Launch/Grow/ + // Evolve (LS-4179, CodeRabbit PR #64 comment #2). 'condition' => static function () { - return is_page( 'discover' ); + return is_page( array( 'discover', 'create', 'build', 'launch', 'grow', 'evolve' ) ); + }, + ), + 'phase-services-in-phase' => array( + 'handle' => 'ls-theme-phase-services-in-phase', + 'path' => 'assets/css/phase-services-in-phase.css', + 'contexts' => array( 'front', 'editor' ), + // Shared across all six phase pages from the start (unlike phase-journey-nav/ + // button-phase above, which started Discover-only), so scoped directly to all six + // known slugs rather than needing a render_block fallback. + 'condition' => static function () { + return is_page( array( 'discover', 'create', 'build', 'launch', 'grow', 'evolve' ) ); + }, + ), + 'phase-cta' => array( + 'handle' => 'ls-theme-phase-cta', + 'path' => 'assets/css/phase-cta.css', + 'contexts' => array( 'front', 'editor' ), + // Same reasoning as phase-services-in-phase above: shared from the start across all + // six phase pages. + 'condition' => static function () { + return is_page( array( 'discover', 'create', 'build', 'launch', 'grow', 'evolve' ) ); + }, + ), + 'phase-delivery-numbers' => array( + 'handle' => 'ls-theme-phase-delivery-numbers', + 'path' => 'assets/css/phase-delivery-numbers.css', + 'contexts' => array( 'front', 'editor' ), + // Same reasoning as phase-services-in-phase/phase-cta above: shared from the start + // across all six phase pages. + 'condition' => static function () { + return is_page( array( 'discover', 'create', 'build', 'launch', 'grow', 'evolve' ) ); + }, + ), + 'phase-common-services' => array( + 'handle' => 'ls-theme-phase-common-services', + 'path' => 'assets/css/phase-common-services.css', + 'contexts' => array( 'front', 'editor' ), + // Same reasoning as phase-delivery-numbers/phase-services-in-phase/phase-cta above: + // shared from the start across all six phase pages. + 'condition' => static function () { + return is_page( array( 'discover', 'create', 'build', 'launch', 'grow', 'evolve' ) ); + }, + ), + 'phase-support-focus' => array( + 'handle' => 'ls-theme-phase-support-focus', + 'path' => 'assets/css/phase-support-focus.css', + 'contexts' => array( 'front', 'editor' ), + // Same reasoning as phase-common-services/phase-delivery-numbers above: shared from + // the start across all six phase pages. + 'condition' => static function () { + return is_page( array( 'discover', 'create', 'build', 'launch', 'grow', 'evolve' ) ); + }, + ), + 'phase-faq' => array( + 'handle' => 'ls-theme-phase-faq', + 'path' => 'assets/css/phase-faq.css', + 'contexts' => array( 'front', 'editor' ), + // Same reasoning as phase-support-focus/phase-common-services above: shared from the + // start across all six phase pages. + 'condition' => static function () { + return is_page( array( 'discover', 'create', 'build', 'launch', 'grow', 'evolve' ) ); + }, + ), + 'phase-where-to-go-next' => array( + 'handle' => 'ls-theme-phase-where-to-go-next', + 'path' => 'assets/css/phase-where-to-go-next.css', + 'contexts' => array( 'front', 'editor' ), + // Same reasoning as phase-faq/phase-support-focus above: shared from the start across + // all six phase pages. + 'condition' => static function () { + return is_page( array( 'discover', 'create', 'build', 'launch', 'grow', 'evolve' ) ); }, ), 'featured-work' => array( diff --git a/inc/phase-page-body-class.php b/inc/phase-page-body-class.php new file mode 100644 index 0000000..89baae2 --- /dev/null +++ b/inc/phase-page-body-class.php @@ -0,0 +1,37 @@ + + +
+ + +
+ +

+ +
+ + + +
+ +

+ +
+ + + +
+ +

+ +
+ + + +
+ +
+ + +
+ + + +

+ +
+ + +
+ +
+ + + +
+ +
+ +

+ +
+ +
+ +
+ diff --git a/patterns/sections/phase-cta.php b/patterns/sections/phase-cta.php new file mode 100644 index 0000000..7da6954 --- /dev/null +++ b/patterns/sections/phase-cta.php @@ -0,0 +1,98 @@ + + +
+ + +
+ + +
+ +

+ + + +
+ +

+ +
+ + + +
+ +
+ + + +
+ +
+ +
+ + + +
+ +
+ + +
+ +
+ +
+ + + +

+ +
+ + +
+ +
+ +
+ +
+ diff --git a/patterns/sections/phase-deliverables-and-role.php b/patterns/sections/phase-deliverables-and-role.php new file mode 100644 index 0000000..1d03d82 --- /dev/null +++ b/patterns/sections/phase-deliverables-and-role.php @@ -0,0 +1,154 @@ + + +
+ + + +

+ +
+ + + +
+ + +
+ +

+ + + +

+ +
+ + + +
+ +
+ + +
+ +
+ +
+ + + +

+ +
+ + + +

+ + + +
+ +
+ +
+ +
+ + + +
+ +
+ +
+ + + +

+ +
+ + + +

+ + + +
+ +
+ +
+ +
+ +
+ +
+ +
+ diff --git a/patterns/sections/phase-delivery-numbers.php b/patterns/sections/phase-delivery-numbers.php index 38783d2..6efaa8f 100644 --- a/patterns/sections/phase-delivery-numbers.php +++ b/patterns/sections/phase-delivery-numbers.php @@ -49,7 +49,7 @@ $ls_phase_stats_count = count( $ls_phase_stats ); ?> -
+
@@ -67,8 +67,8 @@
- -

+ +

diff --git a/patterns/sections/phase-faq.php b/patterns/sections/phase-faq.php new file mode 100644 index 0000000..c048c4c --- /dev/null +++ b/patterns/sections/phase-faq.php @@ -0,0 +1,84 @@ + __( 'What exactly happens during the Discover stage?', 'ls-theme' ), + 'answer' => __( 'Discover brings together research, audits, technical review and strategic clarification before design or development begins. We work with your team to understand the platform, the business pressure and the real shape of the opportunity.', 'ls-theme' ), + ), + array( + 'question' => __( 'How long does the Discover phase typically take?', 'ls-theme' ), + 'answer' => __( 'Most engagements run between four and eight weeks. The scope depends on the size of the existing platform, the number of stakeholders and how much migration or integration analysis is needed.', 'ls-theme' ), + ), + array( + 'question' => __( 'Is this only for large projects?', 'ls-theme' ), + 'answer' => __( 'No. Discover is useful any time the route forward is unclear or the current platform carries enough complexity that guessing would create risk later.', 'ls-theme' ), + ), + array( + 'question' => __( 'How is AI used in this stage?', 'ls-theme' ), + 'answer' => __( "AI helps us audit content, find structural patterns and surface issues faster. Human review, judgement and accountability remain central \xe2\x80\x94 AI accelerates the work, it doesn't replace the thinking.", 'ls-theme' ), + ), + array( + 'question' => __( 'What are the main deliverables?', 'ls-theme' ), + 'answer' => __( 'A clearer brief, a stronger sense of delivery scope, earlier visibility on risk and complexity, and a realistic basis for moving into Create or Build.', 'ls-theme' ), + ), +); + +$ls_faq_block_questions = array(); +foreach ( $ls_phase_faqs as $ls_faq_index => $ls_faq ) { + $ls_faq_block_questions[] = array( + 'id' => 'faq-question-' . ( $ls_faq_index + 1 ), + 'question' => $ls_faq['question'], + 'answer' => $ls_faq['answer'], + 'images' => array(), + ); +} +?> + +
+ + +
+ +

+ + + +

+ +
+ + + +
+ +
+ $ls_faq ) : ?> +
+ +

+
+ +
+ +
+ +
+ diff --git a/patterns/sections/phase-introduction.php b/patterns/sections/phase-introduction.php index f7915f9..5c0197a 100644 --- a/patterns/sections/phase-introduction.php +++ b/patterns/sections/phase-introduction.php @@ -27,8 +27,8 @@
- -

+ +

diff --git a/patterns/sections/phase-journey-nav.php b/patterns/sections/phase-journey-nav.php index c0606f4..f5a8223 100644 --- a/patterns/sections/phase-journey-nav.php +++ b/patterns/sections/phase-journey-nav.php @@ -23,16 +23,19 @@ * passed down as a CSS custom property instead of a literal `color`, leaving the actual `color` * property free for that external stylesheet to own per state. * - * IMPORTANT — insert this as a live reference, not a flattened copy: the "auto-detect active - * phase" logic below only re-evaluates on every real front-end request if this pattern stays a - * live `` reference block. WordPress's - * normal "click to insert from the pattern library" flow instead flattens a file-registered - * pattern into a static copy of whatever get_queried_object() resolved to at insertion time (which - * is frequently no page at all, e.g. inside the REST endpoint that serves the inserter panel) — - * baking in a permanently-wrong or permanently-blank active state that can never update again, - * confirmed empirically while building this page. Use the pattern/reference block, or the Insert - * Pattern flow if a future WP version changes this default, but verify the rendered HTML actually - * contains "is-active" before treating a phase page as done. + * Active-state detection does NOT depend on this pattern staying a live reference. An earlier + * version computed the active step purely in PHP via get_queried_object(), which only re-evaluated + * on real front-end requests while the pattern stayed a live `wp:pattern` reference — WordPress + * routinely flattens a pattern into a frozen static copy the moment a page is opened in the + * editor, permanently baking in whatever active state existed at that moment (often wrong or + * blank), and re-attaching the live reference doesn't prevent it from being reflattened again on + * the next edit. The PHP below still computes `$ls_is_active` and adds an `is-active` class as a + * best-effort default, but the actual visual active/inactive treatment (link colour, dot + * visibility) is driven entirely by CSS keyed off a `page-slug-{slug}` body class — see + * ls_theme_add_phase_page_body_class() in inc/phase-page-body-class.php and the body-class rules + * in phase-journey-nav.scss. That body class is recomputed by WordPress core on every single + * request regardless of how this pattern's blocks are stored, so the correct step stays + * highlighted even if this instance is (or becomes) a flattened copy. * Keywords: phase, journey, lifecycle, navigation, section * Viewport Width: 1280 * Inserter: true @@ -114,9 +117,7 @@ - -
diff --git a/patterns/sections/phase-services-in-phase.php b/patterns/sections/phase-services-in-phase.php new file mode 100644 index 0000000..9d6ff5f --- /dev/null +++ b/patterns/sections/phase-services-in-phase.php @@ -0,0 +1,216 @@ +` reference block. + * Keywords: phase, discover, create, build, launch, grow, evolve, services, cards, section + * Viewport Width: 1280 + * Inserter: true + * + * @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/patterns/sections/phase-support-focus.php b/patterns/sections/phase-support-focus.php new file mode 100644 index 0000000..cfd0bab --- /dev/null +++ b/patterns/sections/phase-support-focus.php @@ -0,0 +1,109 @@ + + +
+ + +
+ +

+ + + +

+ +
+ + + +
+ +
+ + +
+ +

+ + + +

+ + + +

+ +
+ + + +
+ +
+ +

+ + + +
+ + +
+ +
+ + + +

+ +
+ + +
+ +
+ +
+ +
+ +
+ + + +
+ +
+ +
+ +
+ diff --git a/patterns/sections/phase-where-to-go-next.php b/patterns/sections/phase-where-to-go-next.php new file mode 100644 index 0000000..2c4d634 --- /dev/null +++ b/patterns/sections/phase-where-to-go-next.php @@ -0,0 +1,81 @@ + __( 'Create', 'ls-theme' ), + 'description' => __( 'Once the evidence is in place, Create turns that clarity into usable design and content structure.', 'ls-theme' ), + 'url' => '/services/create/', + ), + array( + 'title' => __( 'Our Process', 'ls-theme' ), + 'description' => __( 'See where Discover sits inside the full lifecycle.', 'ls-theme' ), + 'url' => '/about/process/', + ), +); + +$ls_phase_accent = 'var(--wp--custom--color--phase--discover)'; +?> + +
+ + +
+ +

+ +
+ + + +
+ +
+ + + +
+ + + +
+ + + +
+ +
+ +
+ diff --git a/src/scss/structural/faq.scss b/src/scss/structural/faq.scss index d777de1..4ee75a5 100644 --- a/src/scss/structural/faq.scss +++ b/src/scss/structural/faq.scss @@ -76,14 +76,20 @@ } .schema-faq-answer { - display: grid; - grid-template-rows: 0fr; + // grid-template-rows: 0fr → 1fr (the usual CSS-only collapse technique) does not + // actually resolve to zero here: this element is both the grid container and, via its + // own anonymous content box, the only grid item, so the intrinsic-sizing pass for an + // auto-height container falls back to the content's own height regardless of the 0fr + // track value (verified: min-height:0 does not change this). max-height is a more + // reliable cross-browser collapse for this exact shape — the fixed cap below is simply + // generous enough that no realistic FAQ answer gets clipped when open. + max-height: 0; margin: 0; padding: 0 var(--wp--preset--spacing--30); overflow: hidden; opacity: 0; transition: - grid-template-rows var(--wp--custom--animation--duration--base) + max-height var(--wp--custom--animation--duration--base) var(--wp--custom--animation--easing--standard), opacity var(--wp--custom--animation--duration--base) var(--wp--custom--animation--easing--standard), @@ -92,7 +98,7 @@ } .schema-faq-section.is-open > .schema-faq-answer { - grid-template-rows: 1fr; + max-height: 1000px; padding: 0 var(--wp--preset--spacing--30) var(--wp--preset--spacing--20); opacity: 1; } diff --git a/src/scss/structural/phase-common-services.scss b/src/scss/structural/phase-common-services.scss new file mode 100644 index 0000000..61ae923 --- /dev/null +++ b/src/scss/structural/phase-common-services.scss @@ -0,0 +1,56 @@ +/********** Phase - Common Services (all six lifecycle phase pages) **********/ + +/* + * Phase Common Services (LS-4179, single consumer + * patterns/sections/phase-common-services.php). Desktop rest-state is a centred, wrapped "pill + * cloud" of accent-dot + short-label chips, each fully rounded (border-radius|500, a true + * stadium shape) — fine while every label stays on one line, since the chips are only ever as + * tall as one line of text and the centred dot sits level with it. + * + * Below the 782px columns-stacking breakpoint used sitewide (see e.g. + * services-delivery-numbers.scss), several of these labels are long enough to wrap onto two + * lines. Left unchanged, that breaks the pill in three ways: the stadium shape stretches into a + * lopsided capsule around two lines instead of one, the dot (vertically centred against the + * chip) floats at the vertical midpoint of two lines instead of sitting with the first line, and + * the chips' own variable widths under `justifyContent:center` produce a jagged, off-alignment + * stack rather than a clean list. This switches the wrap container to a single-column, full-width + * stack, drops each chip to a smaller rounded-rectangle radius (matching the card radius used + * elsewhere in this same pattern, e.g. its footnote panel), and re-anchors the dot to the top of + * the (now potentially two-line) text — the same top-alignment fix already used for the Focus + * Areas bullet list in phase-support-focus.php, so the two sections share one mobile list + * language. + */ + +// JSON limitation: switching a flex row's direction/alignment and a chip's border-radius based on +// the current viewport has no theme.json/block-attribute key for "below this breakpoint" — see +// AGENTS.md Theme-First Approach. 781.98px = 782px, WP core's own .wp-block-columns stacking +// breakpoint, reused here for consistency even though this row is a `core/group` flex layout, +// not `core/columns`, so it inherits no automatic stacking of its own. +@media (max-width: 781.98px) { + // `!important` needed: WP's own generated unique-class rule for this flex layout + // (`is-layout-flex`) otherwise wins the cascade over a plain class override — same reasoning + // documented in stats-bar.scss. + .ls-phase-common-services__pills { + flex-direction: column !important; + align-items: stretch !important; + } + + .ls-phase-common-services__pills > .wp-block-group.has-border-color { + width: 100%; + max-width: none; + box-sizing: border-box; + align-items: flex-start !important; + // `!important` needed: the stadium radius comes from the chip's own inline `style` attribute + // (its block JSON style attribute), which always wins over a plain class selector. + border-radius: var(--wp--preset--border-radius--300) !important; + } + + // The dot (a fixed 6px icon) sits flush with the row's top edge once align-items switches to + // flex-start above — correct for the *row*, but visually high compared with the text, which + // renders with its own line-height (font-size|200 × the paragraph line-height ratio) above and + // below the glyph. Nudge the dot down by half the gap between one text line's full height and + // the dot's own height, so it centres on the first line specifically rather than the row. + .ls-phase-common-services__pills > .wp-block-group.has-border-color > .wp-block-icon { + margin-top: calc((var(--wp--preset--font-size--200) * var(--wp--custom--line-height--paragraph) - 6px) / 2); + } +} diff --git a/src/scss/structural/phase-cta.scss b/src/scss/structural/phase-cta.scss new file mode 100644 index 0000000..f15edaf --- /dev/null +++ b/src/scss/structural/phase-cta.scss @@ -0,0 +1,28 @@ +/********** Section - Phase CTA (closing CTA for the six phase pages) **********/ + +/* + * Background: the same faint 32px crossed-gradient grid over the permanently-dark + * band-start/band-end diagonal base already used by phase-hero.php's own background (see + * src/scss/structural/phase-hero.scss) — this section normally closes out the same page the hero + * opens, so the two ends of the page share one atmosphere. Deliberately scoped to its own + * .ls-phase-cta class (not reusing .ls-phase-hero) so this stays fully independent of the hero + * file. No radial phase-colour glow here — that's specific to the hero's own upper-content + * composition, not needed on this section. + * + * Left as external background-image (not the block's own inline color.gradient attribute) for the + * same reason documented in phase-hero.scss/blog-hero.scss: an inline `style="background:..."` + * always wins the cascade over an external stylesheet's background-image, so the two can't + * coexist on this block — the pattern's own color.background attribute is left unset so this can + * take over. + */ +.ls-phase-cta { + 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), + 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; + background-repeat: repeat, repeat, no-repeat; + background-size: 32px 32px, 32px 32px, auto; +} diff --git a/src/scss/structural/phase-delivery-numbers.scss b/src/scss/structural/phase-delivery-numbers.scss new file mode 100644 index 0000000..6ae347c --- /dev/null +++ b/src/scss/structural/phase-delivery-numbers.scss @@ -0,0 +1,36 @@ +/********** Phase - Delivery Numbers (all six lifecycle phase pages) **********/ + +/* + * Phase Delivery Numbers (LS-4179, single consumer patterns/sections/phase-delivery-numbers.php). + * Rest-state look for the 3 stat segments comes entirely from the shared + * styles/sections/cards/stat-segment.json (its border-inline-end divider, reused as-is from the + * Work archive's engagement row) — this file only overrides that divider's orientation once the + * row stacks to 1 column on mobile/tablet. Identical fix to + * src/scss/structural/services-delivery-numbers.scss, applied to this pattern's own row class + * (`ls-phase-stats-row` instead of `ls-delivery-stats-row`) since the two patterns render on + * different page sets and are kept independently overridable. + */ + +// JSON limitation: a divider that must be a vertical border-inline-end in a horizontal row but +// a horizontal border-block-end once that same row stacks to a single column has no +// theme.json/block-attribute key for "current layout orientation" — see AGENTS.md Theme-First +// Approach. 781.98px = 782px, matching WP core's own .wp-block-columns stacking breakpoint +// used sitewide (e.g. services-delivery-numbers.scss, work-archive-sections.scss) — the point at +// which this row's 3 columns actually become a vertical stack. +@media (max-width: 781.98px) { + .ls-phase-stats-row .is-style-stat-segment { + border-inline-end: 0; + border-block-end: 1px solid var(--wp--custom--color--border--card); + padding-block-end: var(--wp--preset--spacing--30); + margin-block-end: var(--wp--preset--spacing--30); + } + + // Each stat is the only child of its own .wp-block-column, so a bare + // .is-style-stat-segment:last-child would always match (trivially the last child of its own + // parent) — target the last *column* instead, to only strip the trailing divider once. + .ls-phase-stats-row > .wp-block-column:last-child .is-style-stat-segment { + border-block-end: 0; + padding-block-end: 0; + margin-block-end: 0; + } +} diff --git a/src/scss/structural/phase-faq.scss b/src/scss/structural/phase-faq.scss new file mode 100644 index 0000000..ad76743 --- /dev/null +++ b/src/scss/structural/phase-faq.scss @@ -0,0 +1,114 @@ +/********** Section - Phase FAQ (visual redesign + per-phase open-state accent) **********/ + +/* + * The shared schema-faq accordion (patterns/section-faq.php + src/scss/structural/faq.scss) is + * reused as-is for its markup, JS-driven expand/collapse, and accessibility (role="button", + * aria-expanded, focus-visible), but this pattern's own reference calls for a visibly different + * rest-state treatment: a much larger corner radius, a subtler border, tighter row gaps, and a + * circular plus/minus control in place of the shared component's small rotated-chevron icon. + * Rather than changing those shared defaults (which would also affect the homepage FAQ and any + * other page that inserts section-faq.php), every rule below is scoped to .ls-phase-faq so only + * this pattern's rendering changes. + * + * JSON limitation: none of this has a theme.json/block-attribute equivalent — the accordion's + * question/answer markup is plain hard-coded HTML from the Yoast FAQ block (not built from + * block-supports-aware sub-blocks), so there is no style JSON path to reach it at all; see + * AGENTS.md Theme-First Approach. + */ +.ls-phase-faq { + .schema-faq { + // Tighter rhythm between rows than the shared component's default spacing|20 gap. + gap: var(--wp--preset--spacing--10); + } + + .schema-faq.is-accordion .schema-faq-section { + // Subtler rest-state border than the shared component's default border.card at full + // strength — color-mix'd down so the outline is present but doesn't compete with the card. + --ls-faq-border-rest: color-mix(in srgb, var(--wp--custom--color--border--card) 55%, transparent); + border-radius: var(--wp--preset--border-radius--400); + } + + .schema-faq.is-accordion .schema-faq-question { + position: relative; + padding-top: var(--wp--preset--spacing--50); + padding-bottom: var(--wp--preset--spacing--50); + // Extra inset on the right so the question text never runs under the circular control. + padding-right: calc( + var(--wp--preset--spacing--30) + var(--wp--preset--spacing--50) + var(--wp--preset--spacing--10) + ); + } + + // The circular control's tinted background. A separate pseudo-element from the plus/minus + // glyph below so the glyph can sit on top of it in natural paint order without extra markup. + .schema-faq.is-accordion .schema-faq-question::before { + content: ""; + position: absolute; + top: 50%; + right: var(--wp--preset--spacing--30); + width: var(--wp--preset--spacing--50); + height: var(--wp--preset--spacing--50); + transform: translateY(-50%); + border-radius: var(--wp--preset--border-radius--500); + background: color-mix(in srgb, var(--ls-faq-border-active) 15%, transparent); + transition: background-color var(--wp--custom--animation--duration--base) + var(--wp--custom--animation--easing--standard); + } + + // The plus/minus glyph itself: two crossed bars drawn with a pair of linear-gradient + // backgrounds (a common CSS-only plus/minus icon technique) rather than the shared + // component's single rotated-corner chevron, since the reference calls for a true plus that + // resolves to a minus when open, not a chevron. No new icon asset needed. + .schema-faq.is-accordion .schema-faq-question::after { + content: ""; + display: block; + position: absolute; + top: 50%; + right: var(--wp--preset--spacing--30); + width: var(--wp--preset--spacing--50); + height: var(--wp--preset--spacing--50); + margin-inline-start: 0; + border: none; + transform: translateY(-50%); + transform-origin: center; + background-image: linear-gradient(currentColor, currentColor), linear-gradient(currentColor, currentColor); + background-position: center; + background-size: 40% 2px, 2px 40%; + background-repeat: no-repeat; + color: var(--ls-faq-border-active); + transition: background-size var(--wp--custom--animation--duration--base) + var(--wp--custom--animation--easing--emphasised); + } + + // Open state: collapse the vertical bar only, leaving a minus — same technique used + // elsewhere in the theme for CSS-only state icons, no JS change needed. Also re-asserts + // transform: the shared faq.scss's own open-state rule (rotate(-135deg), for its chevron) has + // higher selector specificity than the rest-state override above, so it would otherwise still + // win and visibly distort this plus/minus glyph. + .schema-faq.is-accordion .schema-faq-section.is-open > .schema-faq-question::after { + background-size: 40% 2px, 0 2px; + transform: translateY(-50%); + } + + @media (prefers-reduced-motion: reduce) { + .schema-faq.is-accordion .schema-faq-question::before, + .schema-faq.is-accordion .schema-faq-question::after { + transition: none; + } + } +} + +/* + * The open-state border colour and the new circular control both read from the + * --ls-faq-border-active custom property, which the shared faq.scss falls back to a sitewide + * generic link-accent token (unrelated to any phase). Overridden here to the current phase's + * accent so both match the rest of the phase page's accent language (eyebrow, bullets, buttons), + * scoped per phase via the page-slug-{phase} body class from inc/phase-page-body-class.php, same + * mechanism as phase-services-in-phase.scss. + */ +@each $ls-phase in discover, create, build, launch, grow, evolve { + body.page-slug-#{$ls-phase} .ls-phase-faq { + .schema-faq.is-accordion .schema-faq-section { + --ls-faq-border-active: var(--wp--custom--color--phase--#{$ls-phase}); + } + } +} diff --git a/src/scss/structural/phase-journey-nav.scss b/src/scss/structural/phase-journey-nav.scss index 59cab9c..a8535bc 100644 --- a/src/scss/structural/phase-journey-nav.scss +++ b/src/scss/structural/phase-journey-nav.scss @@ -51,31 +51,53 @@ var(--wp--custom--animation--easing--standard); } -// JSON limitation: selecting inactive steps via :not(.is-active) has no theme.json equivalent. -.ls-phase-journey-nav__step:not(.is-active) .ls-phase-journey-nav__link a { - color: var(--wp--custom--color--text--on-dark-muted); +/* + * Inactive (rest, default for every item): noticeably darker than the hover state below, so hover + * actually reads as a state change instead of every item already looking hovered — but still built + * from the same on-dark-muted token (dimmed via color-mix against the bar's own dark background) + * rather than a separate hardcoded grey, so it stays part of the same token family as everything + * else here. 70% (rather than the original 55%) is the minimum mix that keeps this text at/above + * WCAG AA's 4.5:1 contrast ratio against the bar's surface.band-end background — 55% measured + * 3.58:1 via axe. The dot is hidden by default; it's always rendered in the markup (never + * PHP-conditional) so the body-class rules below can show/hide it with pure CSS. + */ +.ls-phase-journey-nav__link a { + color: color-mix(in srgb, var(--wp--custom--color--text--on-dark-muted) 70%, transparent); } -/* Inactive hover/focus: brighter and clearly interactive — never the active phase colour, never the dot. */ -.ls-phase-journey-nav__step:not(.is-active) .ls-phase-journey-nav__link a:hover, -.ls-phase-journey-nav__step:not(.is-active) .ls-phase-journey-nav__link a:focus-visible { +.ls-phase-journey-nav__link a:hover, +.ls-phase-journey-nav__link a:focus-visible { color: var(--wp--custom--color--text--on-dark); } -/* Active (rest): the current phase's own accent colour, passed down per item as a custom property. */ -.ls-phase-journey-nav__step.is-active .ls-phase-journey-nav__link a { - color: var(--ls-phase-item-accent); +.ls-phase-journey-nav__dot { + display: none; } /* - * Active hover/focus: text lightens to the same "on-dark" white used for inactive hover, but the - * dot (a separate icon element, styled independently) is untouched by this rule and keeps reading - * in the phase colour — preserving "this is still the current page" rather than looking like a - * plain hovered-inactive item. + * Active override, keyed off the `page-slug-{phase}` body class from + * inc/phase-page-body-class.php rather than the `is-active` class PHP adds to this step: that body + * class is recomputed by WordPress on every request regardless of whether this pattern is a live + * reference or a flattened copy, so the correct step stays highlighted even if this instance's own + * `is-active` class is stale or was never set. Written per phase (not a generic + * `.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. */ -.ls-phase-journey-nav__step.is-active .ls-phase-journey-nav__link a:hover, -.ls-phase-journey-nav__step.is-active .ls-phase-journey-nav__link a:focus-visible { - color: var(--wp--custom--color--text--on-dark); +@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 { + color: var(--ls-phase-item-accent); + } + + .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: inline-flex; + } + } } /* diff --git a/src/scss/structural/phase-services-in-phase.scss b/src/scss/structural/phase-services-in-phase.scss new file mode 100644 index 0000000..f96efe3 --- /dev/null +++ b/src/scss/structural/phase-services-in-phase.scss @@ -0,0 +1,41 @@ +/********** Section - Phase Services In Phase (per-phase CTA + hover accents) **********/ + +/* + * The shared is-style-link-arrow-accent style (styles/blocks/paragraphs/link-arrow-accent.json + + * src/scss/structural/links.scss) reads its rest and hover colours from + * --ls-link-arrow-colour/--ls-link-arrow-hover-colour custom properties, both falling back to + * sitewide generic link-accent tokens (a cyan-blue unrelated to any phase). Those properties have + * no equivalent in core/paragraph's declared style attributes, so setting them inline on the block + * itself would always fail block validation the moment the block is re-parsed (the same class of + * bug already fixed on phase-journey-nav.php's step accent) — JSON limitation: a raw custom + * property can't be expressed in a style JSON attribute, see AGENTS.md Theme-First Approach. + * + * The shared is-style-card-service-tile style (styles/sections/cards/card-service-tile.json + + * src/scss/structural/services-service-tiles.scss) swaps its border to + * --ls-card-service-tile-border-active on hover/focus-within, same generic cyan-blue default — + * also not editable here, since that style is reused by ~16 other patterns sitewide. Overridden + * to a muted phase-accent tone (colour-mix'd down, not full-strength) rather than a plain swap, + * per the request to make the hover feel less dominant. + * + * All three scoped per phase via the page-slug-{phase} body class from + * inc/phase-page-body-class.php (same mechanism as phase-journey-nav.scss) rather than + * get_queried_object() in the pattern's own PHP, so the correct phase accent survives this + * section being flattened, and to the .ls-phase-services-in-phase section class so no other + * consumer of these shared styles is affected. + */ +@each $ls-phase in discover, create, build, launch, grow, evolve { + body.page-slug-#{$ls-phase} .ls-phase-services-in-phase { + .is-style-link-arrow-accent { + --ls-link-arrow-colour: var(--wp--custom--color--phase--#{$ls-phase}); + --ls-link-arrow-hover-colour: var(--wp--custom--color--phase--#{$ls-phase}); + } + + .is-style-card-service-tile { + --ls-card-service-tile-border-active: color-mix( + in srgb, + var(--wp--custom--color--phase--#{$ls-phase}) 60%, + var(--wp--custom--color--border--card) + ); + } + } +} diff --git a/src/scss/structural/phase-support-focus.scss b/src/scss/structural/phase-support-focus.scss new file mode 100644 index 0000000..36e3031 --- /dev/null +++ b/src/scss/structural/phase-support-focus.scss @@ -0,0 +1,46 @@ +/********** Phase - Support Focus (all six lifecycle phase pages) **********/ + +/* + * Phase Support Focus (LS-4179, single consumer + * patterns/sections/phase-support-focus.php). Same underlying issue as + * phase-common-services.scss's mobile fix: each focus-area row centres its accent bullet + * (verticalAlignment: center) against the row's full height, which is correct while the row's + * label stays on one line — the layout this pattern was originally built and reviewed at. Once + * the right-hand column narrows enough (either at this pattern's own two-column mobile stack, or + * simply a narrower viewport) for a label to wrap onto two or three lines, centring against the + * whole row leaves the bullet floating well above the visual middle of the text block instead of + * levelled with its first line. + * + * Unlike the dot icon in phase-common-services (a fixed 6px SVG), this bullet is a bordered, + * padding-sized circle (border-radius|500, 1px border, spacing|5 padding on all sides), so its + * total rendered height is itself token-driven rather than a literal pixel — the margin-top below + * computes half the gap between one text line's full height and the circle's own height entirely + * from custom properties, so it stays correct whatever the spacing/typography tokens resolve to + * at a given viewport width. + */ + +// JSON limitation: overriding a flex row's cross-axis alignment based on the current viewport has +// no theme.json/block-attribute key for "below this breakpoint" — see AGENTS.md Theme-First +// Approach. 781.98px = 782px, WP core's own .wp-block-columns stacking breakpoint, reused here +// for consistency (see phase-common-services.scss, services-delivery-numbers.scss). +@media (max-width: 781.98px) { + // `!important` needed: WP's own generated unique-class rule for this flex layout + // (`is-layout-flex`) otherwise wins the cascade over a plain class override — same reasoning + // documented in stats-bar.scss and phase-common-services.scss. + .ls-phase-focus-areas > .wp-block-group { + align-items: flex-start !important; + } + + // Nudge the bullet down by half the gap between one text line's full height (font-size|200 × + // the paragraph line-height ratio) and the bullet's own height (its 1px border plus + // spacing|5 padding on both the top and bottom), so it centres on the first line specifically + // instead of the row. + .ls-phase-focus-areas > .wp-block-group > .wp-block-group.has-border-color { + margin-top: calc( + ( + var(--wp--preset--font-size--200) * var(--wp--custom--line-height--paragraph) - + (2px + var(--wp--preset--spacing--5) * 2) + ) / 2 + ); + } +} diff --git a/src/scss/structural/phase-where-to-go-next.scss b/src/scss/structural/phase-where-to-go-next.scss new file mode 100644 index 0000000..8c17c3d --- /dev/null +++ b/src/scss/structural/phase-where-to-go-next.scss @@ -0,0 +1,66 @@ +/********** Section - Phase Where To Go Next (visual redesign + per-phase hover accents) **********/ + +/* + * Equal-height cards + bottom-aligned CTA: core/columns already stretches its columns to equal + * height by default (the block's own CSS only overrides that to align-items:flex-start when a + * verticalAlignment is set, which the pattern no longer sets), so each .wp-block-column is + * already the same height as its tallest sibling. What's missing is telling the card itself to + * fill that column height — a plain block box only ever sizes to its own content — so the + * article's own height is set to 100% here. Once the card is genuinely as tall as the column, + * its own flex-column layout (already declared in the pattern's block attributes) plus the + * "Read more" paragraph's own margin-top:auto (already in the pattern) push the CTA to the + * bottom, matching the reference regardless of how much either card's description wraps. No + * fixed/hardcoded height anywhere, and nothing here needs a CSS Grid rebuild of the row: JSON + * limitation is only "fill 100% of the parent's flex-stretched height", which has no + * block-attribute equivalent — see AGENTS.md Theme-First Approach. + * + * Padding/border/radius overrides use the same colour-mix'd-down, scoped-override technique + * already established in phase-faq.scss for the same reason: these are the shared + * Card - Link Row style's rest-state values, also used by the Work archive and Services page, so + * they're overridden here rather than in styles/sections/cards/card-link-row.json to avoid + * changing that shared look anywhere else it's used. + */ +.ls-phase-where-to-go-next { + .is-style-card-link-row { + height: 100%; + padding: var(--wp--preset--spacing--30); + border-color: color-mix(in srgb, var(--wp--custom--color--border--card) 55%, transparent); + border-radius: var(--wp--preset--border-radius--300); + } +} + +/* + * The shared is-style-card-link-row style (styles/sections/cards/card-link-row.json + + * src/scss/structural/work-archive-sections.scss) reads its hover border/background/icon colours + * from --ls-card-link-row-border-active/--ls-card-link-row-background-active/ + * --ls-card-link-row-icon-active custom properties, all falling back to sitewide generic + * link-accent tokens (a cyan-blue unrelated to any phase) since that style is also reused by the + * Work archive and Services page. The shared is-style-link-arrow-accent "Read more" link reads + * --ls-link-arrow-colour/--ls-link-arrow-hover-colour the same way (see + * phase-services-in-phase.scss for the identical reasoning — a raw custom property can't be + * expressed in a style JSON attribute, so it can't be set inline on the block itself; JSON + * limitation, see AGENTS.md Theme-First Approach). + * + * Both scoped per phase via the page-slug-{phase} body class from + * inc/phase-page-body-class.php, same mechanism as phase-services-in-phase.scss, and to the + * .ls-phase-where-to-go-next section class so no other consumer of these shared styles is + * affected. + */ +@each $ls-phase in discover, create, build, launch, grow, evolve { + body.page-slug-#{$ls-phase} .ls-phase-where-to-go-next { + .is-style-card-link-row { + --ls-card-link-row-border-active: var(--wp--custom--color--phase--#{$ls-phase}); + --ls-card-link-row-background-active: color-mix( + in srgb, + var(--wp--custom--color--phase--#{$ls-phase}) 10%, + transparent + ); + --ls-card-link-row-icon-active: var(--wp--custom--color--phase--#{$ls-phase}); + } + + .is-style-link-arrow-accent { + --ls-link-arrow-colour: var(--wp--custom--color--phase--#{$ls-phase}); + --ls-link-arrow-hover-colour: var(--wp--custom--color--phase--#{$ls-phase}); + } + } +}