All notable changes to this project will be documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
Unreleased — Fix editor validation errors and Icon block attributes on Services patterns (LS-4207)
patterns/sections/services-linked-decisions.php,patterns/sections/services-service-clusters.php: added the missinghas-border-colorclass to thels-process-pillandls-cluster-tagwrapper groups. Both blocks set an inline border colour via a custom JSON style attribute, but WordPress's border block support always expects ahas-border-colorclass on the wrapper whenever a border colour is set. The hand-authored static HTML never included it, so the editor's re-derived expected markup never matched the stored HTML, permanently flagging these blocks as "Block contains unexpected or invalid content" with an "Attempt Recovery" prompt in the Site Editor.patterns/cards/section-card-services.php,patterns/sections/services-service-tiles.php: moved the Icon block's width from an unsupported top-level"width"JSON attribute to the correct"style":{"dimensions":{"width":...}}path, matchingcore/icon's actual schema. The old attribute was silently dropped, leaving the icon unsized.patterns/cards/section-card-services.php: fixed icon sluglightspeed/rocket-launchtolightspeed/rocket—rocket-launch.svgdoes not exist in thels-pluginicon library, so the icon failed to render entirely.
(#60)
Unreleased — Fix mobile menu: restore dropdown links, remove Systems, refine spacing (LS-3222)
parts/mobile-menu.html,styles/blocks/details/mobile-menu-accordion.json: each top-level accordion label (Work, Solutions, Services, Pricing, Insights, About) is now a real link to its overview page (/work/,/solutions/,/services/,/pricing/,/blog/,/about/). Previously the label was plain text inside<summary>, so there was no way to reach e.g. "All Work" without opening the dropdown. Clicking the label now navigates directly; clicking elsewhere in the row still toggles the dropdown, using native<summary>click behavior with no JS needed. Added the label link's own colour/underline reset and:focus-visibleoutline.parts/mobile-menu.html: removed the non-functional "Systems" link row.
parts/mobile-menu.html,src/scss/structural/_mega-menu.scss,styles/blocks/groups/mega-menu-item-service.json: converted the Work/Solutions/Pricing/Insights/About and Services mobile dropdown page-lists from 2-column to single-column layouts; decoupled each row's visual height from its tap-target size (rows read tighter while the actual tap area stays at the accessible ~44px minimum via an expanded stretched-link hit area); tightened spacing between links, between each heading and its first link, and between the last link and the "See all…" CTA, while keeping stronger separation between sections/phases; increased the "See all…" CTA font size to match page-link text and removed a redundant divider above it; reduced indentation under each Services phase heading.
(#58)
Unreleased — Build Services page: Entry Points and Delivery by the Numbers (LS-1598)
- Added
patterns/sections/services-entry-points.php: "You don't have to buy everything at once" — eyebrow/heading/description with a 2x2 grid of 4 entry-point link cards, reusing the existing Card - Link Row style. - Added
patterns/sections/services-delivery-numbers.php: "Twelve years of WordPress engineering" — a centered eyebrow/heading/description intro followed by 3 centered delivery-scale stats, reusing the existing Stat Segment style.
- Fixed Entry Points' card grid forcing a 2-column squeeze (and multi-line text wrap) on mobile/tablet — switched from a fixed
columnCounttominimumColumnWidth, which collapses to 1 column natively. - Fixed Delivery by the Numbers' stat divider staying a vertical line on the right of a stacked mobile/tablet card instead of a horizontal line between stacked cards — added a scoped SCSS override that flips the divider orientation below 782px, without touching the shared Stat Segment style used elsewhere.
Unreleased — Build Services page: closing CTA section (LS-1598)
- Added
patterns/sections/services-cta.php(renamed from the empty, unusedpatterns/section-cta.phpstub): "Let's scope it properly" — eyebrow, heading, description, and a single button reusing the exact button already established inservices-hero.php. - Added
src/scss/structural/corner-glow.scss: a shared, multi-consumer two-corner radial-gradient glow background, with its colours as CSS custom properties so future consumers can override them per instance instead of duplicating the file.
- Corrected a Figma SVG gradient matrix decode error that made the corner-glow's blobs roughly 10x too small (a missing
r="10"base-circle multiplier), and set the CTA panel to a real 800px max width.
Unreleased — Build Services page: service tiles section (LS-1598)
- Added
patterns/sections/services-service-tiles.php: the "Fourteen services. One delivery model." section — a 14-card bento grid (Discovery through AI), each card a single stretched link to its individual service page, with an icon well, index number, kicker line and description. - Added
styles/sections/cards/card-service-tile.json, a new shared card shell modelled on Card - Category's shell but flatter, with an index-number slot Card - Category doesn't have.
- Fixed each row's
blockGaponly setting its horizontal component, which meant stacked mobile/tablet spacing fell back to WordPress's default instead of the intended value, producing inconsistent gaps between cards.
Unreleased — Expand CodeRabbit config for full auto-review and finishing touches (LS-4124)
.coderabbit.yml: correctedauto_review.base_branchesto use thefeature/.*andfix/.*regular expressions, so stacked PRs (based on a prior feature or fix branch rather thanmain/develop) trigger an automatic review. Enabledauto_incremental_review,assess_linked_issues,related_issues,related_prs,path_filters,finishing_touches(autofix/docstrings/unit_tests),tools(eslint/markdownlint/gitleaks/trufflehog), andknowledge_base.code_guidelines(pointed atAGENTS.md). Setprofile: assertiveand enabledrequest_changes_workflow. (#57)
Unreleased — Build Search template (LS-2594)
- Added
patterns/hero/search-hero.php: the Search template's hero — breadcrumb trail, eyebrow, a static "Search LightSpeed" heading (intentionally not bound to the query term), supporting copy, and a no-button pill search field (is-style-search-pill, reused from the existing Blog All Articles search box). The breadcrumb is embedded here rather than the sharedls-theme/breadcrumbstemplate part, so it shares this hero's own padding instead of stacking a second set on top, matching theblog-hero/work-heroconvention. - Added
patterns/sections/search-useful-destinations.php: the "Useful destinations" section — eyebrow, heading, and a 4-card grid (FAQ, Pricing, Website packages, Contact), reusing the existingis-style-card-category/ls-icon-well-brand/is-style-link-arrow-accentstyles and the same Phosphor icon markup already used by404-best-next-routes.php. Kept as a separate, self-contained file per this repo's one-pattern-per-file convention rather than a shared cross-pattern reference. Always shown below the results list regardless of result count. - Added
src/scss/structural/search-hero.scss: the hero's decorative corner-glow background (two radial gradients, layered —color.gradientblock support only accepts one), reusing the existingeffect.hero.brand/effect.hero.cyantokens; and.ls-optical-trim, a font-leading correction (see Fixed, below). - Added
src/scss/structural/search-results.scssadditions: whole-row clickable results with a rightward-shift hover, using the same stretched-link technique as Card - Post (font-size:0on a realcore/read-morelink, not the corescreen-reader-textutility — that setsposition:absoluteon the anchor itself, which breaks the stretched-link's::before{inset:0}sizing).
- Rebuilt
patterns/template-search.php: hero → results query loop → useful destinations. Each result shows a category eyebrow (core/post-terms) above anH2title (core/post-title,level:2— the previouslevel:3skipped a heading level with noH2before it) and excerpt, plus a hairline divider (border.card) between items. Removed the query loop's unusedpostType/search/excludeargs, whichinherit:truewas already ignoring in favor of the main search query — pages now correctly appear in results alongside posts, matching core behavior. styles/blocks/search/search-pill.json: added explicittext.default/text.subtlecolours for the input's typed text and placeholder. Neither this file norcore-search.jsonpreviously set acoloron the input, so it fell back to the browser's black UA default — invisible against this pill's dark-modesurface.cardbackground (only coincidentally legible in light mode). Fixes both consumers of this shared style (the Search hero and Blog All Articles' header search, which had the same latent bug).
- Hover-state title colour on search results used
link.accent(dark mode:cta-500, the same saturated cyan as the CTA buttons — too loud for a hover-only state on heading-sized text). Changed tolink.accent-hover(dark mode:cta-300, same family, softer), the token that exists specifically for this role. - Added
.ls-optical-trim(text-box-trim/text-box-edge, plus atransform: translateY(calc((1cap - 1lh) / 2))fallback for broader browser support) to the hero eyebrow/heading and the Useful destinations eyebrow/heading.line-heightalone can't fully cancel a font's own built-in leading (baked into the font file's vertical metrics), which differs between the body font (Manrope) and the display font (Lexend) — that residual, font-specific leading was making the gap above each heading read larger than the gap below it, even with identical margin values.cap/lhare real CSS units resolved per-font by the browser, not a guessed pixel offset.
- No new colour tokens or font-size presets were needed — every value maps to an existing semantic token or preset, and every reused block style already carries dark-mode parity.
- The results-list "eyebrow" shows the post's category term rather than a post-type label, since WordPress core has no block for rendering a post-type name and this site has no custom taxonomy resembling the Figma mock's placeholder labels (e.g. "Trust", "Commercial").
- Verified against the dev site (
ls-agency.lightspeedwp.dev, the theme's active environment there): thecategorytaxonomy exists onpostand matches local; all four destination links (/faq/,/pricing/,/website-packages/,/contact/) are published there. All links useesc_url( home_url( '/slug/' ) )— no hardcoded domains.
Unreleased — Migrate Services patterns to the Core Icon block (LS-3229)
- Replaced
outermost/icon-block(third-party "Icon Block" plugin) with WordPress 7.1's native Core Icon block (core/icon) across the 3 Services page patterns —patterns/hero/services-hero.php,patterns/sections/services-linked-decisions.php,patterns/sections/services-service-clusters.php— referencing icons from thelightspeedcollection registered inls-plugin. Final batch of the Core Icon block migration (LS-3229). - Converted the PHP icon arrays in these files (per-service, per-cluster, per-step) from raw inline SVG to bare
lightspeed/{name}slugs, and rewrote their loop templates to emitcore/iconblock markup dynamically per iteration, removing the now-unused raw-SVG helper variables.
See PR #50.
Unreleased — Fix legacy outermost/icon-block CSS selectors broken by Core Icon migration (LS-3720)
- Fixed mega-menu and Work-archive card icon hover/focus colour treatments no longer applying after the Core Icon block migration (LS-3229):
.wp-block-outermost-icon-blockselectors insrc/scss/animations/_menu-motion.scssandsrc/scss/structural/work-archive-sections.scssonly matched the legacyoutermost/icon-blockmarkup, so any icon already converted to WordPress 7.1's Corecore/iconblock (.wp-block-icon) lost its hover styling. Both selector sets now target.wp-block-outermost-icon-blockand.wp-block-iconside by side, so neither markup shape loses the treatment while any remaining legacy instances are migrated. - Removed a redundant, footer-specific
.icon-container svg { fill: currentColor; }rule fromsrc/scss/animations/_footer-motion.scss. - Removed the stale compiled artifact
assets/css/animations.min.css.
- Moved the
.is-style-mega-menu-item-defaulticon hover/focuscolorrule fromsrc/scss/animations/_menu-motion.scsstosrc/scss/structural/_mega-menu.scss, per code review: motion partials may only containtransition/transform/animation-family properties (AGENTS.md), so the non-motion colour swap now lives in the structural partial alongside the rest of that style's rules, while the animations file retains only thetransitionand itsprefers-reduced-motionoverride.
Unreleased — Build Services page: hero, linked decisions, and service clusters (LS-1598)
- Added
patterns/hero/services-hero.php: breadcrumb, eyebrow, heading, description, CTAs, and a lifecycle-phase-coloured row of all 14 service tags linking to their/services/pages. - Added
patterns/sections/services-linked-decisions.php: a six-step process pill row (Discover/Create/Build/Launch/Grow/Evolve) linking to new/services/phase pages, with a fixed whole-pill click target. - Added
patterns/sections/services-service-clusters.php: five cluster cards in an asymmetric 3+2 layout grouping all 14 services, each linking out to its individual service page. - Added
styles/sections/cards/card-cluster.json, a new shared card shell for the service-clusters section.
- Fixed the second service-clusters card row's
blockGapbeing nested outsidespacinginstead of inside it, which silently fell back to WordPress's default gap instead of matching row 1.
Unreleased — Fix button.fill color contrast (LS-2937)
- Fixed a WCAG AA color-contrast failure on the theme's secondary filled button style (
is-style-button-secondary, used by the 404 page's "Back to homepage" button, the Work archive's "Ready to discuss a project?" CTA, and the mobile menu's "Start a project" button).button.fill.background/borderwasbrand-500(#1E6AFF) againstbutton.fill.text(base,#FAFAFA) — 4.41:1, below the 4.5:1 minimum. Changed tobrand-600(#1C5EE4), which is already the token used everywhere else this brand blue needs to be accessible (text.brand,link.accent,card.platform.wordpress) — 5.34:1, passes with real margin. - Fixed a related, previously-undetected hover-state contrast failure on the same button:
text-hoverwascontrast(#080808, near-black) againstbackground-hover(the same blue), which was already failing at 4.35:1 before this change and would have failed worse (3.6:1) after the background swap alone. Changedtext-hovertobase(white), matching the resting state's text color. Resting and hover now share the samebrand-600/basecolor pair, so both are 5.34:1. Automated axe-core scans don't trigger:hoverstates, so this wasn't caught by the original BugHerd report. - Fixed the same underlying failure on the theme's secondary outline button style (
is-style-button-secondary-outline):button.outline.text/border/background-hoverwere alsobrand-500, failing at 4.41:1 in both resting (text on a transparent/light background) and hover (white text on abrand-500background) states. Changed all three tobrand-600— 5.34:1 in both states, matching the filled button fix above.
- Dark mode's equivalent button tokens (
cta-500/cta-400againstcontrast) were checked for both the filled and outline styles and already pass comfortably (15.57:1 resting, 13.1:1 hover) — no dark-mode change needed. - The header's "Start a project →" gradient CTA (
ls-button-cta-gradientinpatterns/header.php) is unaffected — it uses its own custom gradient background, notbutton.fill.background.
Unreleased — 404 template rebuild (LS-2596)
- Added
patterns/sections/404-best-next-routes.php, the "Five useful destinations" section: eyebrow, heading, and a 5-card grid (Homepage, Pricing, Website packages, FAQ, Contact) reusing the existing Card - Category style and Phosphor icons. Wired intotemplates/404.htmlalongside the existing 404 pattern. - Added the
1000("Display") font-size preset tostyles/presets/typography.json(fluid4.5rem–10rem), used by the 404 numeral — the previous largest preset (900,4.5rem) was too small for the design's display-scale treatment. - Added the
effect.watermark.brandsemantic colour token totheme.jsonandstyles/dark.json, backed by two new palette presets (brand-450,cta-150), for the 404 numeral's colour.
- Rebuilt
patterns/template-404.phpin place: large 404 numeral, not-found heading, supporting copy, and Homepage/Search CTAs, replacing the previous minimal search-field version.
- Fixed the routes section rendering with no gap before the footer by applying the existing
is-style-content-bandsection style (padding, not margin — WordPress core zeroesmargin-topon template-part wrappers, so rootblockGapalone can't create space against the footer). - Fixed missing card icons:
outermost/icon-blockis a static block that reads its SVG from serialized HTML content, not theiconNameattribute alone — embedded real Phosphor "regular" SVG markup for each card icon. - Fixed a Gutenberg editor crash on the routes section caused by an invalid
core/grouplayout type ("flow"is not a real layout type; corrected to"default").
Unreleased — Work Single (Portfolio) page rebuild (LS-2277)
- Added
templates/single-project.html, a dedicated single template for theprojectcustom post type. Previouslyprojectposts fell back to the generictemplates/single.htmlused for blog posts, which had no eyebrow, CTA buttons, or meta row. - Added
patterns/template-work-single.php, the Work Single main-content pattern: the new hero followed bywp:post-contentrendered exactly as authored, so every existing case study's body (headings, columns, image/gallery blocks) keeps rendering unchanged. - Added
patterns/hero/work-single-hero.php, the redesigned hero: breadcrumb trail, "Case Study" eyebrow, post title/excerpt, CTA buttons ("Talk to us about a similar project" + a "View site" button sourced from thels_plugin_portfolio_websitepost meta), a three-column meta row (Software / Project Type / Services, from the existingproject-group/project-type/project-tagtaxonomies), and the post's featured image, bordered so it reads clearly against the page background regardless of the image's own colours. - Added
inc/work-single-hero.php, arender_blockfilter that resolves the "View site" button's real URL (or removes the button) at actual render time. A pattern file's top-level PHP runs once at registration (init, no post context), soget_post_meta( get_the_ID(), ... )called directly in the pattern always returned empty — the button never appeared regardless of whether the post meta was set. - Added
src/scss/structural/work-single-hero.scss(compiled toassets/css/work-single-hero.css, loaded only whenis_singular('project')via the same conditional-bundle system aswork-hero/blog-hero), for the meta row's mobile divider swap — see Changed below. No JSON block-supports equivalent exists for a divider that must be a vertical border on desktop and a horizontal one once columns stack.
- Refined the metadata row's spacing and grouping, design-only — no taxonomy values, links, block bindings, or dynamic data changed: tightened the row's vertical padding and reduced the label-to-value gap to
spacing|10(within the requested 12–16px range); added a 1px vertical divider between columns (reusing the existingborder.cardtoken) with consistent horizontal padding per column, while the first and last columns keep 0 padding on their outer edge so their content still aligns with the hero content above; gave every meta value (wp:post-terms) explicit, consistent typography (regular weight,fontSize:200) without touching their existing link colour; moved the featured image closer to the metadata row (margin-topspacing|60→spacing|40, matching the same reduction applied to the meta row's own top margin). On mobile (below WordPress core's own 782pxwp-block-columnsstacking breakpoint), the vertical dividers switch to horizontal dividers between the stacked blocks instead of an oddly full-height left border.
- Fixed the hero's title/excerpt/buttons/meta-row rendering narrower and centred instead of left-aligned with the breadcrumb: the inner wrapper group used
layout:"constrained", which restricts un-aligned children to content-width, while the breadcrumb block forced itself toalignwide. Removed the group'slayoutoverride entirely so all children fill the same wide width via the block's own default. - Increased the gap between the hero content and the featured-image panel from
spacing|60tospacing|80for clearer visual separation. - Fixed the Site Editor throwing "This block has encountered an error and cannot be previewed" on the
single-projecttemplate, caused by aTypeError: Cannot read properties of undefined (reading 'getAlignments'). Root cause: an intermediate fix for the alignment bug above usedlayout:{"type":"flow"}—"flow"is not a registered Gutenberg layout type (valid values aredefault,constrained,flex,grid), so resolving it returnedundefinedand calling.getAlignments()on that threw. Frontend rendering was never affected (block themes just ignore an unrecognised layout type there), but the pattern is unusable in the Site Editor until fixed. Removed the invalidlayoutattribute. - Fixed the breadcrumb, meta row, and CTA column using a 850px-fixed / flex-wrap layout combination untested elsewhere in this theme; reverted to the same
wp:columns/wp:columnshape (percentage widths) already proven throughout the codebase. - Fixed the "View site" button's meta key: initially wired to
lsx_project_url, sourced from a different remote environment's plugin version. Confirmed via WP-CLI (wp post meta list) that this site's actual key isls_plugin_portfolio_website. - Fixed the metadata row's two divided columns rendering a bold black box around all four sides instead of a subtle single-side divider. Their
styleonly declaredborder-left-*; WordPress core's block-library CSS appliesborder-style: solidto every side once any border colour is set on a block (via.has-border-color), so the other three sides fell back to the browser's default border-width (medium, ~3px) andcurrentColor(black) — a real border, not a rendering glitch. Fixed by explicitly zeroing out the top/right/bottom sides (width:0; style:none) alongside the intendedborder.card-coloured 1px left divider, so nothing is left to a browser default. - Fixed the "View site" button's hover animation rendering the arrow icon on top of the button text instead of masking it. Two compounding causes: (1) an earlier attempt to lighten the icon well set
--ls-button-outline-accent-background: transparent— that background is what visually covers the text as the icon slides from the button's right edge to its left edge on hover, not a decorative fill; (2) both hero buttons had custom, smallerspacing.paddingoverrides (an earlier "reduce button dominance" pass), but the outline button's hover slide uses fixed padding/positioning constants insrc/scss/animations/_button-motion.scsscalibrated for the theme's default button padding — shrinking the rest-state padding left the slide with less room than it needs, so the icon overlapped the text regardless of the well's background colour. Fixed by removing both hero buttons' padding overrides entirely, matching every other hero/CTA button in the theme (none of which override button padding) and letting the built-in hover animation work as designed. - Fixed case-study body content (
wp:post-contentinpatterns/template-work-single.php) rendering capped at the theme's 800pxcontentSizeinstead of the 1370pxwideSize, even though every section band inside a case study's own content already carries analignwideclass. Root cause: thewp:post-contentblock itself had noalignattribute, so its own box was an unaligned child of<main>and rendered at 800px regardless of what alignment its children requested — a child can't be wider than its own parent's box. Added"align":"wide"to the block, matching how every other wide-content pattern in this theme is wired. No post content or per-post structure changes needed.
- Only the hero is new design. The meta row deliberately reuses the three taxonomies that already exist on LIVE (
project-group,project-type,project-tag) rather than introducing new fields like "Client Type" or "Platform" seen in the Figma reference — adding new taxonomies/meta would leave existing LIVE posts with blank data once this template ships. - The hero's featured-image panel renders the plain featured image with no caption/browser-chrome treatment — the Figma frame's caption chip is a design annotation, not real post content.
- Drive Botswana's own post content (not a theme file — edited directly via WP-CLI/
wp_update_post, same as the other post-content fixes above) needed several further passes: its mini-heading labels (e.g. "About Drive Botswana") were authored as plain paragraphs withis-style-subheading-2/-3classes that were never registered in this theme, so they rendered as unstyled text. Converted every instance to realcore/headingblocks (h3/h4), which already get correct weight and size from the theme's existing heading styles — no new theme code needed. Also standardized every section's label-column width (40%→30%), removed each section's per-instancewp:columnsblockGapoverride (falling back to the theme's own default,spacing|50), and gave each section equal top/bottom padding (spacing|20) for consistent vertical rhythm. Separately, several inner content groups had"blockGap":"0", which WordPress renders as a hardmargin-block: 0pxreset on every child regardless of that child's own margin — changed to a real token (spacing|10) so WordPress's own flow-gap mechanism (margin-top on each subsequent sibling) produces a real gap between each label and the copy below it.
Unreleased — Build Single Blog template (LS-2932)
- Added
ls-theme/blog-single-hero(patterns/hero/blog-single-hero.php): breadcrumb, dot-icon category eyebrow,post-title,post-excerpt, and a bordered author/date/read-time meta strip, all left-aligned and wide, then a full-wide featured image capped to a21/9aspect ratio. Adapted from the Work Single hero's structure, using existing semantic tokens only. - Added
inc/blog-single-related-query.php: scopes the new "Related Reading" Query Loop to the current post's own category and excludes the post itself, resolved at render time viaquery_loop_block_query_vars(a pattern's ownwp:queryattributes can't express "the current post").
- Rewired
template-single.php(the Single Blog main-content pattern): hero →post-content(unchanged, default 800px width) → share row → wide "Related Reading" grid (reusing the existingblog-post-cardpattern) → the existingblog-writing-ctapattern as the closing CTA. - Removed the shared
parts/breadcrumbs.htmltemplate-part reference fromtemplates/single.html— the hero pattern now renders its own breadcrumb, matching the Blog Archive hero's convention, avoiding a duplicate breadcrumb.
- Added a "Core WordPress blocks first" rule to
AGENTS.md, documenting the existing practice of preferring semantic core blocks (post-title,post-excerpt,post-terms,query/post-template, etc.) over genericgroup/columnsmarkup.
Unreleased — Fix broken placeholder links and heading hierarchy skips (LS-2936, LS-2938)
- Fixed 25
href="#"placeholder links acrosspatterns/footer.php(Services, Solutions, Systems, Company, Studio, and legal-bar columns),patterns/header.php("Start a project →" CTA), andpatterns/sections/work-related-routes.php(6 card links) — all now point to their real destination pages, verified against the live page list on ls-agency.lightspeedwp.dev. - Fixed a heading hierarchy skip (h1 straight to h3, no intermediate h2) caused by reusable patterns whose lead heading is hard-coded at
level: 3, designed to sit under an existing h2 section heading but used standalone in several places. Promotedpatterns/cta/section-cta-consultation-inline.phpandsection-cta-consultation-strip.phpfrom h3 to h2.patterns/thank-you-consultation.phphas its own embedded h1 and jumped straight to h3 card headings; converted its "While you wait" intro from a plain paragraph to a proper h2.
section-card-feature.php,section-card-services.php, andsection-card-solutions.phpwere deliberately left at h3 — they're grid-card patterns correctly nested under an existing h2 section heading elsewhere (e.g.work-related-routes.php's "Where to go next" h2), so changing them would have broken correctly structured pages.- A related
/contact-us/404 was also fixed as part of this work, but not in this repo: the link didn't exist anywhere in theme source — it was hardcoded directly in the content of two blog posts ("Tour Operator Websites: Promoting Experiences" and "Marketing your Tour Operator Business Online"), corrected on the live site to point to/contact/instead.
Unreleased — Fix footer overflow, logo switching, and uneven stat card heights (LS-2939)
- Fixed the footer's newsletter heading and paragraph overflowing horizontally at 320-380px viewports.
.ls-footer-notes-panelis a vertical flex container with no explicit alignment set, so WordPress's defaultalign-items: flex-startlet the heading/paragraph size to their own unwrapped content width instead of the panel's actual available width. Added"layout":{"selfStretch":"fill"}(and the resultingwidth:100%) to both blocks inpatterns/footer.phpso they stretch to fit and wrap normally. The "LightSpeed notes" badge pill above them was left untouched since it's meant to hug its own content. - Fixed the footer logo not responding to light/dark mode. It used WordPress's core
wp:site-logoblock (a single static logo from Site Identity), unlike the header'sls-theme/site-logo-switcherpattern (a light/dark image pair toggled via existing global CSS in_header.scss). Swapped the footer to reference the same shared pattern already used by the header and mobile menu — no new CSS needed since the switching selectors aren't scoped to the header. - Fixed the footer's 3 stat cards (
.ls-footer-proof-card) having visibly uneven heights when their text wrapped to a different number of lines. The columns they sit in already stretch to equal height by default, but each card's own background/border box only wrapped its own content. Addedheight: 100%to.ls-footer-proof-cardinsrc/scss/structural/_footer.scss— a genuine JSON limitation, sincecore/group's dimensions support only offersminHeight, not a "stretch to fill parent" key.
Unreleased — PageSpeed: Fix mobile performance on Homepage (LS-2922)
- Fixed the homepage loading every structural CSS bundle in the theme unconditionally (~20 files), regardless of whether the page actually used them — the root cause behind PageSpeed's "render-blocking requests" and "unused CSS" findings on mobile.
inc/animations.phpnow gates most bundles behind aconditionreflecting their real, verified usage (e.g.is_front_page(),is_post_type_archive('project'),is_page_template('page-blog-archive')) instead of loading everywhere. - Fixed several of those bundles being gated too narrowly at first:
work-project-card,work-archive-sections, andtaxonomy-filterare also used by patterns outside their assumed "home" template (e.g. homepage sections, the Blog archive's filter), not just the Work archive. - Fixed a
render_block-based safety net for every pattern that's individually insertable via the block inserter (Inserter: true), since a template-only condition can't detect a pattern placed somewhere the condition doesn't anticipate. This uncovered and fixed a live bug: the mobile menu's two CTA buttons (parts/mobile-menu.html) were unstyled on every page except the homepage/Work archive/404, since template-part markup never appears in a page's ownpost_content. - Fixed missing
font-display: swapon all 17fontFaceentries instyles/presets/typography.json, so text renders in a fallback font instead of staying invisible while a custom font loads. - Fixed the Featured Work card grid overlay being invisible in light mode:
work-project-card.scsswas tinting the grid lines from--wp--custom--color--surface--highlight, a token pinned to the same near-white value in boththeme.jsonandstyles/dark.json(unlike its siblingcanvas, which correctly flips per mode). Switched to--wp--custom--color--text--default, which already adapts correctly, instead of changing the shared token — that token also drives the glass-card/glass-button sheen effect elsewhere and needs to stay light-colored in both modes.
- Added a
build:css:dev/build:csssplit inpackage.json:build:cssnow outputs the compressed CSS that's actually committed and shipped; the old expanded output is preserved asbuild:css:devfor local debugging.
card-shells,cta-buttons, andfaqare loaded unconditionally rather than through therender_blocksafety net — they have no head-time condition at all, so every use would otherwise flash unstyled (not just a rare off-template case). At ~10 KB/~5 KB/~3 KB compressed combined, that's a better trade than the FOUC/CLS risk.homepage-why-lightspeedis a known, deliberate gap in therender_blocksafety net: its only defined CSS selector isn't present anywhere in the pattern's current markup (pre-existing, unrelated to this fix), so there's no reliable marker to detect yet.- Header search expand animation, JS minification, and the GSAP "legacy JavaScript" PageSpeed flag were investigated and deliberately left out of scope — see LS-2922 for reasoning.
- Forced-reflow/long-task profiling and an LCP font preload hint were flagged by the original PageSpeed report but not implemented in this pass.
Unreleased — Sync mobile menu links with desktop mega menus (LS-2801)
- Fixed
parts/mobile-menu.htmlstill using the original placeholder titles andhref="#"links across all 6 dropdowns (Work, Solutions, Services, Pricing, Insights, About) — these were never updated when the desktop mega menus were wired to real pages/posts. Every item now matches its desktop equivalent's label and real URL exactly. - Fixed the mobile menu's two footer action buttons ("Book a consultation" and "Start a project") both linking to
#and pointing at the same destination as each other. "Start a project" is now the primary button, linking to/free-consultation/to match the desktop header's CTA; the secondary button now reads "Explore case studies" and links to/work/, reusing the same pairing already established on the homepage hero.
- Confirmed mobile and desktop are otherwise in full parity — every link/label pair cross-checked programmatically. Desktop's secondary per-dropdown CTAs (e.g. "Book a free consultation" on Work, "Tell us about your project" on Pricing) were deliberately not added to mobile, since they'd duplicate the persistent "Start a project" button already visible at all times in the mobile menu.
Unreleased — Audit link cursor behaviour across theme patterns and templates (LS-2804)
- Fixed clickable cards across the theme where only a small inner link (e.g. "See the work →") had the pointer cursor / clickable hit area, not the whole card, despite hover styling implying the entire card was interactive. Applied the existing stretched-link technique (
::before/::afterwithinset:0over aposition:relativecard, already used byis-style-card-link-row) to:is-style-card-category(Homepage "Where to start", "What we build", Work archive categories),is-style-card-feature,is-style-card-solutions,is-style-card-package(Homepage "Where to fit" packages),is-style-card-case-study(Work archive project cards, Homepage Featured Work),is-style-card-post(Blog All Articles), andis-style-card-highlight-dark(Blog Hero featured tile). Added a:focus-visibleoutline to each so keyboard users see where focus lands. - Fixed the same underlying issue on the mega-menu item rows (
is-style-mega-menu-item-default,is-style-mega-menu-item-service) by adding a:focus-visibleoutline — the stretched-link hit area itself already existed viasrc/scss/structural/_mega-menu.scssfrom LS-2801, which this branch's own JSON-based stretched-link addition was found to duplicate during code review and was removed in favour of the existing SCSS implementation. - Fixed 10 icon SVGs in
patterns/footer.phpmissingfill="currentColor", plus a staleiconColor/has-icon-colorclass conflict on the availability badge icon — both caused "Block contains unexpected or invalid content" errors in the Site Editor. - Fixed a block validation error on the Work archive's "Ready to discuss a project?" CTA (
patterns/sections/work-discuss-project.php): the columns wrapper had a hand-authored inline--wp--style--block-gapstyle thatcore/columns' actual save output never produces.
- Every card audited has either exactly one link, or two links to the same destination (post title + "read more"/"view project"), so a single stretched link is safe everywhere it was applied — no card required per-link disambiguation.
card-package.jsonandcard-post.jsonhad nocssfield at all before this change;card-case-study.jsonhad noposition: relative. Added both from scratch where missing.
Unreleased — Build Homepage Where to Fit and Homepage CTA (LS-1616)
- Added
patterns/sections/homepage-where-to-fit.php: eyebrow, heading, copy, and a 3-card package row (Foundation/Growth/Enterprise). The middle "Growth" card is pre-styled with its border/shadow as a permanent rest-state (it's the featured package, not literally hovered); the other two cards gain that border/shadow only on hover. All 3 lift slightly on hover and share an equal height regardless of content length. - Added
patterns/sections/homepage-cta.php: the homepage's closing CTA, mirroringblog-writing-cta.php's structure/max-width-cap convention — eyebrow, two-line heading, copy, two buttons ("Book a free consultation" reusing the existingis-style-button-primary-on-dark; "Send a brief first" a plain-bordered on-dark outline with no arrow), and a "What you'll leave with" definition-list panel on the right. - Added
src/scss/structural/where-to-fit.scss(equal card height, hover border/shadow/lift motion) andsrc/scss/structural/homepage-cta.scss(max-width cap, decorative glow, panel label column width, arrow-reveal on the primary button, and neutralisingis-style-outline's built-in arrow-well for the one button that shouldn't have it) — all genuinetheme.jsonlimitations, documented inline. - Wired both into
templates/front-page.html: Where to Fit after Featured Work, Homepage CTA as the final section before the footer.
- No new colour tokens needed. Where to Fit flips normally with the style variation (
surface.card/border.card/text.brand, same as other homepage card sections); Homepage CTA is permanently dark, reusing the exactsurface.band-start/band-endgradient andon-darktoken family already established byblog-writing-cta.php— confirmed this matches the "light mode still looks dark, dark mode is a lighter shade" behavior requested, since those tokens already resolve that way. - "Send a brief first" needed its pill shape from
is-style-outlinebut not that style's built-in arrow icon — rather than edit the sharedis-style-outline/_button-motion.scss(global, used site-wide, and touching it would have compiled intoanimations.css), the arrow/hover-reveal is neutralised via a scoped override inhomepage-cta.scsstargeting only this button's class.animations.csswas not touched. - All CTA buttons across Where to Fit reuse the existing pill button styles (
is-style-button-secondary/-outline) and thels-has-arrow-revealmarker class already established for the other homepage sections, and flip with the style variation as requested.
- Fixed editor crashes on the Why LightSpeed, Featured Work, What We Build, and Where to Start sections (and the mobile menu part):
"layout":{"type":"flow"}is not a registered Gutenberg layout type (the valid value is"default") — the editor threwCannot read properties of undefined (reading 'getOrientation')and the block failed to render. - Fixed a block validation "unexpected or invalid content" error on Where to Fit's featured "Growth" card — its shadow style pointed at a nonexistent theme.json preset (
var:preset|shadow|400) instead of the real custom token (var:custom|shadow|elevation|400) already used in the rendered markup. - Fixed a block validation error on the homepage CTA's "Send a brief first" button — missing
has-custom-font-sizeclass caused by combining a namedfontSizewith a custom inline style on acore/button.
- Rebuilt Featured Work's card row as a real
wp:queryQuery Loop (projectpost type, 3 per page) filtered to the existing "Featured"project-tagterm, replacing the 3 hand-written static cards. Editors control which case studies appear by tagging/untagging posts as "Featured" in the post editor — no code changes needed. - Tagged 3 real project posts ("Modernising African Safari Consultants' Website", "Novus Media", "Drive Botswana") with the "Featured" term so the section has real content on load.
- Card markup is inlined directly inside the Post Template rather than referenced via
wp:pattern {"slug":...}— awp:patternreference does not forward the Post Template's block context to nested dynamic blocks (post-title/post-excerpt/post-terms), so referencing it by slug rendered every card empty. Confirmed via live HTML inspection. - Superseded the custom horizontal "list view" card entirely (from the entry below) in favour of reusing the Work archive's existing
is-style-card-case-studycard in a 3-column grid (wp:post-template {"layout":{"type":"grid","columnCount":3}}), matchingwork-selected-projects.php's convention — the custom card had no real per-post data for its stat row and looked sparse once the fabricated numbers were removed.src/scss/structural/featured-work.scssnow only contains the equal-card-height fix (.ls-featured-work-grid, scoped so the Work archive's own grid is unaffected) — the old thumbnail-texture and flex-grow rules were removed as dead code. - Excerpt/title/tag-pill content is now genuinely dynamic per post (via
post-terms/post-title/post-excerpt), not fabricated placeholder copy.
Unreleased — Build Homepage What We Build, Why LightSpeed, Featured Work (LS-1616)
- Added
patterns/sections/homepage-what-we-build.php: eyebrow, heading, a 4-card row (WordPress platforms/WooCommerce/Design systems/Migrations) reusing the sharedis-style-card-categoryshell, and an "All services"core/button is-style-outlineCTA. - Added
patterns/sections/homepage-why-lightspeed.php: two-column section with positioning copy + twocore/buttonCTAs (filled + outline) on the left, a 5-item checklist card on the right. - Added
patterns/sections/homepage-featured-work.php: a new horizontal "list view" case-study card (thumbnail, heading/description, 3-figure stat row, trailing arrow) — genuinely new, not reused from the existing verticalis-style-card-case-studyQuery Loop card. - Added
src/scss/structural/featured-work.scssfor the case-study card's flex-grow content column and the thumbnail's decorative diagonal-line texture, both genuinetheme.jsonlimitations (documented inline). - Wired all 3 new patterns into
templates/front-page.html, after Where to Start. - Registered
featured-work.cssinpackage.json'sbuild:css/watch:cssscripts,inc/animations.php's effect-styles list, andfunctions.php's editor styles, matching the existing per-pattern CSS convention.
- No new colour tokens were needed — all colours map to existing
surface.card/border.card/text.brand/text.mutedtokens (already flip correctly between light/dark) or the establishedcolor-mix()convention for tinted badge backgrounds. - No new
is-stylevariants were registered;is-style-card-categoryandis-style-link-arrow-accentare reused as already-established multi-use styles, per LS-2341. - All pill-shaped CTAs use the native
core/buttonblock (default fill +is-style-outline), both already registered/token-driven — no new button styling needed. assets/css/animations.csswas not touched.
Unreleased — Build Homepage Hero, Stats Bar, Where to Start (LS-1616)
- Rebuilt the Homepage Hero (
patterns/home-hero.php) to match Figma: AI-planner intro, decorative prompt-input row, project-type suggestion pills, and a consultation link. Kept the existing GSAP network background system (.ls-home-hero-section) untouched and layered new content on top. - Added
patterns/section-stats-bar.php, a 4-figure stat strip with vertical dividers, placed directly beneath the Hero. - Added
patterns/sections/homepage-where-to-start.php("Three honest routes into LightSpeed"), reusing the Work archive'sis-style-card-category/.ls-icon-well-brand/is-style-link-arrow-accentstyling so both pages now share one improved card treatment. - Wired all 3 new patterns into
templates/front-page.html. - Added 4 new mode-invariant custom colour tokens for the Hero's translucent "glass" badges/borders, following the existing
on-darkfamily convention (defined intheme.json, intentionally not overridden instyles/dark.json):surface.glass,surface.glass-lighter,surface.glass-subtle,border.glass. - Added scoped
ls-hero-badge-neon/ls-hero-prompt-row/ls-hero-pillrules tosrc/scss/structural/home-hero.scssfor the glass-panel styling with aJSON limitationcomment (comma-separated background+border+box-shadow combination and hover-state background swap have no theme.json equivalent).
- No new
is-stylevariants were registered, per the LS-2341 cleanup (PR #24) — single-use Hero treatments use scopedls-*classes; the Where to Start cards reuse the already multi-useis-style-card-category. - Hero prompt input is decorative only (no functional wiring) per current scope.
assets/css/animations.csswas not touched.
PR #26 — Build Blog Archive page (Hero, All Articles, Engagement, Writing CTA) - 2026-08-19
- Built the Blog Archive template's 4 sections to match Figma (LS-1616):
patterns/hero/blog-hero.php,patterns/sections/blog-all-articles.php,patterns/sections/blog-engagement.php,patterns/sections/blog-writing-cta.php, plus supporting card patterns (blog-featured-article.php,blog-latest-item.php,blog-post-card.php,blog-code-snippet.php). - Added a 9-slug
category.*semantic colour token family (theme.json/dark.json), mapped to the existingphase-*token family by post-volume rank, replacing 5 placeholder categories that didn't match the real site taxonomy. - Added
text.on-dark-accent,surface.on-dark-accent-tint,icon.on-dark, andborder.on-darktokens for the permanently-dark Hero and Writing CTA sections. - Added the
core/post-time-to-readblock to the Blog card patterns for per-post reading time in Query Loop cards. - Added a
Button - Primary On Darkblock style and aCard - Postsection style for the archive's post grid. - Added a
search-pill.jsonno-button search variant for the All Articles filter row. - Added horizontal-scroll behaviour for the taxonomy filter pills below 790px (
src/scss/structural/taxonomy-filter.scss), now shared by both the Work and Blog archives.
- Fixed category badge and post-title text rendering black instead of the correct on-dark/on-light colour — caused by
elements.linkstyles winning the cascade over inherited heading colour onisLinkblocks (post-title, post-terms). - Fixed the category badge colour not swapping per post in the Query Loop —
elements.linkrenders as one deduped, shared rule, not per-instance — replaced with a plain class +color: inherit. - Fixed long category names overflowing the post card edge.
- Fixed the Writing CTA card's max-width and taxonomy-filter's mobile flex-wrap both silently no-op'ing against WordPress core's own
.alignwide/.wp-block-buttonscascade. - Fixed the
ls-plugin/taxonomy-filterblock rendering an editor "invalid content" error due to a self-closing pattern comment. - Removed the Blog Archive template's top padding so the Hero sits flush against the header; kept bottom padding for consistent spacing above the footer.
- Added 4 Work archive component patterns (LS-1616), each an individually insertable pattern built entirely from existing adaptive semantic tokens — no new
theme.json/dark.jsontokens were needed:patterns/cards/work-project-card.php— a case-study card bound to real Portfolio post data via WordPress block bindings (LS-1617):core/post-title,core/post-excerpt,core/post-terms(badge bound toproject-group, tag pills bound toproject-tag), andcore/read-morefor the permalink. Intended as the Post Template content inside a Query Loop scoped to theprojectpost type.patterns/cards/work-discuss-project-list.php— a static checklist card reusing the existingtick-accentlist style.patterns/cards/work-engagement-stat.php— a stat segment with a split-colour big number (accent-coloured suffix) and monospace uppercase labels.patterns/cards/work-next-steps-card.php— a compact related-route button.- Matching section/block styles for rendering dynamically-bound taxonomy terms as a badge bar and individual tag pills, and for the card/divider/checklist shells (all since folded into their consumer patterns or converted to plain classes by LS-2341 — see Removed below).
- Colour, computed values (background tints, borders, custom-property recipes) are kept out of pattern-level block attributes and defined only in external style/structural files — WordPress's block validator can't reliably round-trip a raw
color-mix()value placed directly in a block's ownstyleattribute. Static values with a real block-supports equivalent (border style/width/radius, padding) were later moved into pattern block attributes directly by LS-2341, per the theme's theme-first rule. - Added hover-lift behaviour for the two interactive cards (
is-style-card-case-study— still the registered style on the card root; only its descendant elements picked up new.ls-card-case-study__*BEM classes under LS-2341 — andis-style-card-link-row, still a registered multi-consumer style) toassets/css/animations.css, reusing the existingcard.hovershadow contract, and extended theis-style-link-arrow-accentstyle/CSS to also supportcore/read-more(previouslycore/paragraphonly).
- Added a
Menu Item Cardpattern (patterns/menu/menu-item-card.php) as the single reusable source of the mega-menu list item structure (icon well, title/description, hover-reveal trailing arrow), used across the Work, Solutions, Pricing, Insights, and About mega menus — Services is excluded, it uses its own per-phase item style. Deliberately a plain registered pattern, not a Synced Pattern, so each insertion stays independently editable per menu item (LS-1618). - Added 6 real mega menu template parts for Ollie Menu Designer's Dropdown Menu block, each with final content, no shared placeholder scaffold:
parts/work-mega-menu.html,parts/solutions-mega-menu.html,parts/pricing-mega-menu.html,parts/insights-mega-menu.html,parts/about-mega-menu.html(Default item styling), andparts/services-mega-menu.html(Service item styling, 6 lifecycle-phase columns). All registered intheme.jsontemplateParts(LS-1618). - Added
Mega Menu Item - DefaultandMega Menu Item - Serviceblock styles (styles/blocks/groups/mega-menu-item-default.json,mega-menu-item-service.json) as the single reusable, registered source of a row's structural styling (padding, radius) — the icon well itself carries no separate style, just plain inline block attributes, so it has no independent interactive behaviour of its own. - Added
.is-style-mega-menu-item-defaulthover/focus behaviour (row background highlight + trailing arrow icon reveal) insrc/scss/animations/_menu-motion.scss, compiled toassets/css/animations.css. This couldn't be expressed in the JSON style itself — WordPress's global-styles engine only generates:hover/:focusCSS for the built-inelementsallowlist (link/button/etc.), not for arbitrary block style variations — so it's authored here instead, scoped tightly to this one selector. - Added a
Mega Menu Panelsection style (styles/sections/menu/mega-menu-panel.json) plus ashadow.popovercustom shadow token for the dropdown panel shell, and moved its border colour onto native JSONborder.colorinstead of a CSS override. - Added a
phase.discover|create|build|launch|grow|evolvesemantic colour token family (six newphase-*/phase-*-strongpalette presets) for the Services mega menu's lifecycle-phase colour coding, with light-mode values darkened to meet WCAG AA 4.5:1 against the light card surface. - Added a
text.subtlesemantic colour token (neutral-600light /neutral-500dark) for a tertiary text tier betweentext.mutedand full-contrast text. - Added the Search Results template (
search.html+template-search.php), a Page (No Title) custom template (page-no-title.html+template-page-no-title.php, registered intheme.jsoncustomTemplates), and a shared Taxonomy template (taxonomy.html+template-taxonomy.php) covering all four Portfolio taxonomies (LS-1226). - Extracted
front-page.html,index.html,page.html,single.html, andarchive.htmlinto dedicated pattern files (hero+front-page-latest-posts,template-index,template-page,template-single,template-archive) so every template body is a single pattern injection, per the new template/pattern naming convention (LS-1226). - Built out
archive.html's main content (query title, term description, paginated post query loop), which previously rendered no content at all (LS-1226). - Added
settings.layout.contentSize(800px) andwideSize(1370px) totheme.jsonto match the design system's Figma values (LS-1226). - Wired Yoast SEO's native
yoast-seo/breadcrumbsblock into theBreadcrumbspattern and added thebreadcrumbstemplate part to thepage,single, andarchivetemplates (LS-1228 Part 2). The pattern degrades gracefully (renders nothing) if Yoast SEO is inactive. - Added reusable
Glass ButtonandGlass Cardblock style variations that apply the shared Sass frosted-glass surface treatment to buttons and Group-based card shells (Glass Buttonsince folded into its one consumer pattern by LS-2341 — see Removed below;Glass Cardremains a real reusable style). - Added a
Card - Servicespattern with a reusable accent-border gradient contract, a matching services card section style (since folded into the pattern by LS-2341), and a blue tick list block style. - Added a
Solutions Cardpattern with a reusableIcon Frame Glowgroup style (since folded into the pattern by LS-2341) and a compact shared-arrow card CTA button style. - Added individual merged block preset files under
styles/presets/blocks/so live block runtime defaults can be maintained one block per file. - Added a theme-local
themejson-completionskill,ThemeJSON Completeragent, andcomplete-theme-jsonprompt for approval-first Global Styles completion work. - Registered the theme font-family presets as
body,heading, andmonospace, and applied themonospacepreset to code elements intheme.json. - Added a Sass source layer under
src/scss/with reusable mixin families for breakpoints, motion, surfaces, and glass effects. - Added a GSAP asset bootstrap in
inc/gsap.phpfor class-based interactive effects. - Added a GSAP-driven
is-style-card-spotlightgroup effect using theme surface and accent colours. - Added usage documentation for the spotlight card class.
- Added a registered
Card SpotlightGroup block style for easy editor application. - Added a CSS-only sliding icon treatment for the core outline button variation.
- Added a GSAP-powered front-page hero with rotating proof points, interactive network background, and primary / outline CTAs.
- Added a dedicated
front-page.htmltemplate that renders the marketing hero before editable page content. - Added a repo-scoped semantic design-token instruction for theme colour work.
- Added a portable
theme-color-token-enforcerskill to audit or fix semantic colour token usage. - Added a theme-local
pattern-extractorskill and matchingextract-patternprompt wrapper for Figma-to-pattern workflows that honour semantic tokens and CSS-versus-GSAP motion routing. - Added a CSS-only
Card - Featuregroup treatment, a matching inline CTA paragraph style, and an insertable single-card pattern atpatterns/cards/card-feature.php. - Added a Playwright end-to-end testing setup (LS-2335), matching the official
create-playwrightscaffold:@playwright/test,dotenv, and@types/nodeas dev dependencies, andplaywright.config.tsconfigured for all 3 browser projects (Chromium, Firefox, WebKit) withbaseURLread from a local, gitignored.env(BASE_URL=) so each developer points tests at their own environment — no CI wiring, run manually vianpx playwright test. Added a real spec (tests/specs/work-archive.spec.ts) exercising all 6 generic assertion helpers fromtests/helpers/assertions.ts(LS-2244) against the live Work Archive template — section order, category-card count/parts, hero link href, related-routes grid reflow at mobile, and stats-grid divider styling. - Added a standing, content-agnostic Playwright regression suite (LS-2335) under
tests/specs/standing/, run manually against the staging site rather than any single template:site-health,runtime-errors,network-errors,accessibility(via@axe-core/playwright),internal-links,responsive-overflow,media-integrity, andspecial-routes(search + 404). Backed by a sitemap/REST/crawl URL-discovery fixture (tests/helpers/site-urls.ts) with a fetch timeout, crawl-path exclusions (wp-admin,wp-login.php, feeds), and a hard crawl-budget guardrail. All standing assertions useexpect.soft()so every discovered URL is checked in full instead of stopping at the first failure. - Added a BugHerd reporter (
tests/reporters/bugherd-reporter.ts) that auto-logs standing-suite failures as BugHerd tasks, scoped only totests/specs/standing/— other specs never create tasks. Deduplicates by a stableexternal_idderived from the spec and a normalized failure signature (tests/helpers/failure-signature.ts), so the same underlying bug across many pages becomes one task instead of one per page, while genuinely distinct bugs (e.g. two different broken links) stay separate. ReadsBUGHERD_API_KEY/BUGHERD_PROJECT_IDfrom.env.
- Moved the majority of
assets/css/animations.css's structural (non-motion) CSS intotheme.json/styles/**JSON block-style partials across buttons, cards, footer, header, mega menu, mobile menu, details/accordion, and links — leaving only real motion (transitions, transforms,@keyframes) and a small set of confirmed WordPress/JSON limitations (no:hover/:focusgeneration for arbitrary style variations,@media/@supportsstripped from thestyles/**.jsoncssfield, content referencing Sass variables) in Sass.assets/css/animations.cssdropped from 2,003 to 1,444 lines (LS-2340). - Eliminated 3 of 5 files under
src/scss/abstracts/mixins/(_mq.scss,_breakpoints-theme.scss,_glass.scss) by inlining every remaining call-site as literal CSS;_motion.scssand_surface.scssremain only because the deadgsap/_card-spotlight.scss(tracked for removal on LS-2341) still references them. Removed the now-unneededsync:breakpointsnpm script and itstheme-utils.mjsimplementation. - Consolidated 3 groups of byte-identical (or near-identical) hover/motion rules that had been duplicated once per consuming file — a shared circular icon-button hover contract (header search button, mobile-menu close button, footer proof-card, footer social-icon), a shared mega-menu-item row hover contract, and a shared frosted-glass
@supportsblur block — into one new partial,src/scss/animations/_shared-hover.scss. - Fixed a render-order bug in
inc/portfolio-card-colors.php: the WooCommerce card-colour swap now runs onrender_block(after WordPress has already generated the block's own markup and decided which style variation applies) instead ofrender_block_data, whose pre-render$block['attrs']proved unreliable for readingcore/grouppost context inside a Post Template Query Loop — confirmed empirically (LS-2341) — so WooCommerce-tagged Portfolio cards no longer silently lose their intended styling. - Renamed section-level patterns to lead with
section-(section-cta,section-cta-consultation-band/inline/reassurance/strip,section-card-feature/services/solutions,section-stats-grid) to distinguish freely-insertable section patterns from full-page template patterns and page-scoped content patterns (LS-1226). - Refactored
src/scss/animations.scssandsrc/scss/gsap-animations.scssinto loader entrypoints backed by smaller contextual partials, normalised live preset JSON onto WordPress shorthand for non-colour tokens, and removed the deadsettings.custom.button-paddingbranch. - Made
theme.jsoncustom layout breakpoints the build-time source for the Sassmq()map, and tightened the CSS build to compile only explicit Sass entry files so tracked source-directory CSS artefacts can no longer override live assets. - Styled the merged
core/detailspreset as the site accordion contract, including token-driven default, hover, focus, and open states, tuned chevron alignment, custom typography font-weight tokens for the question and answer text, and moved the selector-driven accordion layer into Sass for maintainability. - Moved the longer heading, link, list, card, glass, and button style contracts out of inline style JSON and into
src/scss/animations.scss, added maintenance notes to the migrated style files, and standardised the shared gradient contract on--ls-accent-gradient-*. - Moved authored effect CSS maintenance to Sass entry files that compile back to
assets/css/animations.cssandassets/css/gsap-animations.css, while keeping the WordPress runtime asset paths unchanged. - Rewired shared card icon shells onto the existing accent token path so dark-mode accent remaps stay in sync across icon shells, list markers, and related card accents, and removed the services card icon rotation from its hover state.
- Normalised the GSAP hero mobile breakpoint onto the canonical
theme.jsonbreakpoint naming set by routing the authored media query through the newmq()mixin. - Tightened the theme.json workflow and instruction files so all live block configuration must be authored in individual
styles/presets/blocks/*.jsonfiles rather than directly intheme.json. - Tightened the theme-local pattern extractor guidance so patterns are grouped into subfolders, card icons use a nested Group plus Icon Block structure, radius values always resolve through presets, whole-card hover states are preferred for interactive cards, and reusable muted-text and hover-shadow tokens are favoured over card-specific hardcoding.
- Enabled root-padding-aware alignments, added theme typography writing-mode support, and applied a default spacing-20 horizontal page gutter in
theme.json. - Moved block-style visual tokens into
theme.jsonandstyles/blocks/**/*.json, leavingassets/css/animations.cssas the interaction layer for the heading, link, and button treatments. - Started the semantic colour token migration by adding a minimal
settings.custom.colorlayer totheme.jsonandstyles/dark.json, then rewiring the button, heading, and paragraph style JSON hotspots onto semantic tokens. - Corrected the first dark-mode semantic token mappings so surface and foreground roles invert properly while interactive accents shift contextually within the existing palette.
- Clarified that
styles/dark.jsonkeeps the same preset palette values astheme.json, with only the semantic token preset assignments changing between modes. - Expanded the token migration across shared preset JSON, animation fallbacks, spotlight card surfaces, and dark-aware custom shadow tokens.
- Added shared animation and z-index custom tokens to
theme.json, documented how agents should use them, and rewired common motion and stacking literals onto those shared tokens. - Rewired the GSAP runtime to read shared theme animation tokens for common durations and easings, and to resolve the hero effect colours from the semantic custom token layer.
- Corrected the shared button spacing token references and narrowed the outline button icon well so the fill and outline buttons keep equal height without the outline label or icon frame breaking.
- Reduced the outline button's top and bottom padding by the border thickness so it stays level with the fill button while preserving the narrower square icon frame.
- Tightened the token guidance so same-family semantic tokens must be reused before new ones are created, and re-mapped the light surface tokens so only distinct surface roles keep distinct preset assignments.
- Re-aligned the home hero GSAP wiring to the reusable
Home Hero Sectionblock style and dropped the abandoned cycling heading path. - Moved theme and heading line heights in
theme.jsononto shared custom tokens, with H1 using a tighter value and H2 through H6 sharing a common heading rhythm. - Refined the GSAP spotlight card glow alignment and switched the card shell to a lighter theme palette.
- Refined the GSAP front-page hero so the network background sits behind the headline, responds more directly to pointer movement, and swaps words with a right-to-left character stagger.
- Reduced the default H1 weight in
theme.jsonand rebuilt the hero word loop so longer words no longer clip, overlap, or reset with a blank frame. - Fixed the homepage template so sites using
show_on_front = postsrender the posts query beneath the hero instead of an empty static-pagepost-contentblock. - Tightened the hero word-stage measurement, removed the empty transition frame, and added smaller-screen layout rules so the hero scales more cleanly on mobile.
- Softened the hero word transition so the letters stagger vertically from right to left without the word shifting too far sideways during the swap.
- Replaced the hero's stacked-word repeat timeline with a two-layer word controller so the active word sits slightly higher and the loop no longer starts with an empty frame.
- Added a subtle character gap to the animated hero word so the rotating terms read a little more cleanly.
- Simplified the spotlight effect configuration so live tuning happens directly in
assets/css/gsap-animations.css. - Moved GSAP-specific spotlight styling from
assets/css/animations.csstoassets/css/gsap-animations.css. - Removed glow hover styling from default and outline buttons so only the dedicated glow button style blooms.
- Renamed the default fill button interaction tokens to a dedicated fill-button namespace and kept the configured fill styling intact.
- Matched the core outline button sizing and typography to the configured fill button proportions.
- Removed the outline border shimmer and kept the core outline button as a solid border with a sliding icon well.
- Added small-screen responsive sizing for the default fill and outline buttons so long labels can wrap cleanly while preserving the icon slide.
- Centred the default fill button arrow inside a fixed icon well so it stays aligned across desktop and small-screen sizes.
- Switched local theme asset enqueue versions to file modification times so CSS and JS edits invalidate browser cache during development.
- Replaced all placeholder tokens with final
ls-theme/LightSpeedvalues across all files. - Updated
README.md,AGENTS.md, and.github/copilot-instructions.mdto reflect the real theme repo rather than a starter template. - Updated theme prompts and instructions to require semantic custom colour tokens, matching
styles/dark.jsontoken paths, and WCAG AA contrast checks for new colour work.
- Removed ~30 single-use "fake"
is-styleblock-style variants that cluttered the block editor's style picker with options that were never a real second editorial choice (LS-2341): folded each into its one consumer pattern as real inline block attributes, or as scoped plain-classname CSS under the newsrc/scss/structural/directory where JSON block-supports genuinely can't express the rule (nested selectors, custom-property recipes, layered gradients). Convertedbadge-brand/badge-woocommerceandcard-banner-tint/card-banner-tint-woocommercefrom registered is-styles into plain classes swapped programmatically byinc/portfolio-card-colors.php, since they were a taxonomy-driven state flag, not an editorial choice. Deleted 6 fully dead, zero-consumer style files found during the audit, plus the single-use button (button-cta-gradient,glass-button,button-glow-accent), heading (gradient-accent,shadow-heading-base,shadow-heading-contrast), and paragraph/link (footer-nav-link,link-underline-accent) variants found in those same families. Left only the genuinely multi-consumer variants within icon-well, buttons, headings, and paragraph/link (e.g.link-arrow-accent,button-arrow-compact,button-secondary) registered as-is. - Removed stale compiled CSS and source-map artefacts from
src/scss/soassets/css/remains the only runtime stylesheet output tree. - Removed the experimental vanilla JavaScript spotlight card implementation pending GSAP evaluation.
- Removed the section style contract loader for
styles/sections/*.jsonruntime CSS generation. - Removed the temporary experimental button variation and its temporary selector path.
- Removed the temporary CSS/JS light-dark toggle built for the header (
inc/theme-toggle.php,assets/js/theme-toggle.js,assets/css/theme-toggle.css, thegenerate-theme-togglecommand intheme-utils.mjs, and the toggle button markup inpatterns/header.php). This was a workaround built before discovering the site already has a proper, native light/dark switcher — thels-plugin/style-switcherblock registered by the LightSpeed Site Plugin, which readsstyles/*.jsonvariations directly and needs no theme-side duplicate. The header currently has no light/dark toggle until that block is wired in as follow-up work; fully recoverable from git history if needed sooner (LS-1618). - Removed
tests/specs/work-archive.spec.ts,tests/specs/work-single.spec.ts, andtests/specs/archive-pagination.spec.ts(LS-2335) — these tested one specific page/template's identity rather than a reusable, generic component or behavior, which doesn't fit this suite's intended shape. Fully recoverable from git history if a page-specific spec is wanted again later.tests/helpers/assertions.tsis kept, sincemedia-integrity.spec.ts(standing suite) still usesexpectElementCountfrom it.
- Fixed several pre-existing bugs surfaced while auditing the is-style layer (LS-2341): a
wp:listblock with a duplicated base classname breaking editor validation on the Work Discuss Project CTA; hand-authored inline spacing styles oncore/query/core/columnsthat neither block nor the theme's settings actually support; thels-plugin/taxonomy-filterblock being inserted as a self-closing void block when itssave()returns a real wrapper div;assets/js/gsap-effects.jsquerying a stale classname after a fold-in rename, breaking the homepage hero's GSAP initialization; the WooCommerce card-colour swap not reliably reading post context forcore/groupblocks onrender_block_datainside a Post Template loop (moved torender_block); and WordPress core's default.wp-block-group.has-backgroundpadding going un-overridden on the Work Project Card, inset-ing its banner grid. - Fixed an invalid nested
wp:site-titleblock insidepatterns/footer.php's paragraph markup that broke block parsing on every template (LS-1226). - Fixed
patterns/breadcrumbs.phprendering full-bleed instead of content width by removing an unnecessaryalign:full(LS-1226). - Fixed the header search field rendering incorrectly in the Site Editor (input shown expanded by default) by adding
assets/css/animations.cssviaadd_editor_style()infunctions.php, so it reliably reaches the Site Editor's iframed canvas rather than only the outer wp-admin document (LS-1618). - Fixed inconsistent column widths in the footer nav link grid (
patterns/footer.php): the second row (Company/Studio) used a fixed226pxcolumn against an auto column, splitting differently than the first row's three auto-equal columns above it, so nothing lined up as a grid. Both rows now use the same auto-equal column pattern, with an empty spacer column completing the second row to three tracks (LS-1618). - Fixed mega-menu item icon alignment across the Work, Solutions, Pricing, Insights, and About menus (31 items total): the icon, text block, and trailing arrow were direct siblings all vertically centred together, so aligning the icon to the top would have also incorrectly pushed the arrow to the top. Wrapped the text block and arrow together in their own centred group so the icon can align to the top independently while the arrow stays vertically centred against the text (LS-1618).
- Fixed the footer's phase-colour nav dots and social icons rendering black in both light and dark mode: none of their
<svg>markup declaredfill="currentColor", so the browser's default SVG fill (solid black) was used instead of the semantic colour token set via their inlinecolorstyle. Added.site-footer .icon-container svg { fill: currentColor; }insrc/scss/animations/_footer-motion.scss, the same fix already used foris-style-icon-frame-glowand the Services card icon shell (LS-1618). - Fixed several elements missing the
has-text-colorclass despite setting an inlinestyle.color.textattribute, causing the block editor to flag them as invalid content on re-insertion, across 9 pattern files (LS-2436). - Fixed non-existent
x-small/small/mediumfontSizeslugs and a non-existentx-smallspacing slug referenced in several patterns/templates — none of these slugs were ever registered instyles/presets/typography.jsonorspacing.json— replaced with real registered slugs (LS-2436). - Fixed
settings.custom.color.surface.band-start/band-end/on-dark-cardintheme.json/styles/dark.jsonbeing byte-identical between light and dark mode (hardcoded rather than tokenised); gave dark mode its own distinct, contrast-checked values. Added atext.brand-on-darktoken so on-dark text/icon colour meets WCAG AA 4.5:1 in both modes (LS-2436).
0.1.0 - YYYY-MM-DD
- Initial LightSpeed Theme repository setup.
- Root theme files:
style.css,theme.json,functions.php,readme.txt. - Block template:
templates/index.html. - Block template parts:
parts/header.html,parts/footer.html. - Style variations:
styles/light.json,styles/dark.json. - Asset folders:
assets/fonts/,assets/icons/,assets/logos/,assets/images/,assets/css/,assets/js/. - Validation and utility script:
theme-utils.mjs. - Node-based tooling:
package.json,.lintstagedrc.json,.nvmrc. - PHP tooling:
composer.json. - GitHub config:
.editorconfig,.gitignore,.gitattributes,.coderabbit.yml,CODEOWNERS. - AI guidance:
AGENTS.md,CLAUDE.md. - GitHub Copilot instructions:
.github/copilot-instructions.md,.github/instructions/. - GitHub prompts:
.github/prompts/. - GitHub reports folder:
.github/reports/. - GitHub tasks folder:
.github/tasks/. - GitHub Actions workflows:
ci.yml,code-quality.yml,release.yml. - Portable agent assets:
.agents/skills/,.agents/agents/. - End-user documentation folder:
docs/.