diff --git a/.specify/memory/constitution.md b/.specify/memory/constitution.md index a4670ff..55844a3 100644 --- a/.specify/memory/constitution.md +++ b/.specify/memory/constitution.md @@ -1,50 +1,266 @@ -# [PROJECT_NAME] Constitution - + + +# LightSpeed Theme (ls-theme) Constitution ## Core Principles -### [PRINCIPLE_1_NAME] - -[PRINCIPLE_1_DESCRIPTION] - +### I. Theme-First Styling + +`theme.json` and `styles/**/*.json` are the single source of truth for color, typography, +spacing, layout, borders, shadows, and block-level structural properties. Sass/CSS in +`src/scss/**/*.scss` is permitted **only** for what JSON genuinely cannot express: +`:hover`/`:focus-within`/`:focus-visible` states with no `elements.*` pseudo-state key, +`content:""` pseudo-elements, comma-separated selectors, SVG `fill`, and parent-triggered +child-selector motion. Every such rule MUST carry a comment directly above it naming the +specific limitation, in the form: +`// JSON limitation: — see AGENTS.md Theme-First Approach`. + +Motion/animation files (`src/scss/animations/**`, `src/scss/gsap/**`) MUST contain only +`@keyframes`, `transition`, `transform`, `animation`, and `will-change` rules, plus their +`prefers-reduced-motion` companions — nothing else; any other property in those files is a +defect. `assets/css/animations.css` (and the Sass it compiles from) MUST contain **only** +genuinely global styling — content that loads sitewide via the header/footer template parts, +including anything nested in them (mobile menu, mega menus). Styling tied to a specific page +or pattern does not belong there no matter how small, and MUST live in its own dedicated file +instead. GSAP is permitted only for JS-driven interaction that CSS transition/animation +structurally cannot achieve (e.g. scroll-triggered sequencing, cursor-tracked effects) — it +MUST NOT be the default choice just because a pattern happens to have motion. + +**Rationale**: A block theme's editor-facing consistency and dark-mode parity depend on +styling living in the token/JSON layer, where the Site Editor and `theme.json` can reason +about it. Undocumented hand-authored CSS silently drifts from that system and is the +single biggest source of untraceable styling bugs in this repo's history. The +`animations.css` global-only rule exists specifically because a page-scoped rule leaking into +a sitewide-loaded file is both a performance cost (loaded on every page) and a cascade risk +(the header/footer are inescapable, so a leaked rule can't be scoped away later). + +### II. Reuse Before Create + +Before authoring a new pattern, card style, or token, check existing patterns, styles, and +tokens for something that already fits. Name new patterns and card styles by shape, not by +page (e.g. `card-link-row`, not `services-page-link-card`), so they remain available for +reuse elsewhere. A single-consumer card style under `styles/sections/cards/` is acceptable, +established precedent in this repo, provided it defines something a JSON-vs-inline-attribute +approach genuinely cannot express otherwise (child selectors, pseudo-elements). It is **not** +acceptable to invent a new one-off `is-style` variant for a single-use treatment with no +second consumer ever anticipated — style that inline on the pattern's block attributes +instead. `styles/blocks/**` and `styles/sections/**` files are auto-discovered by WordPress +6.6+ as real, user-visible style-picker entries — every file added there is a live editor +option, not an implementation detail, so this rule matters beyond code cleanliness. + +Building a new pattern from a Figma design MUST use the repo's own `.agents/skills/` +toolchain, not ad hoc Figma-to-markup translation: `pattern-extractor` for the Figma-to-pattern +conversion itself (it already encodes this reuse-or-create workflow, dark-token parity, and +Phosphor/Icon-Block mapping), which in turn mandatorily loads `theme-color-token-enforcer` for +any authored pattern/style/CSS file it creates or touches. `wp-block-style-audit` is the +authoritative JSON-vs-CSS decision procedure for any `styles/**/*.json` file — consult it +before writing a `css` field or a new Sass exception. For any WordPress-development task more +generally (not just pattern building), check the "Available Skills for Planning" section +below and use whichever available skill actually matches the task — do not default to a +single familiar skill when a more specific one is available. + +**Rationale**: Page-scoped naming and speculative one-off styles fragment the design system +and make later reuse (or auditing) effectively impossible; shape-based naming is what makes +"is there already something for this?" answerable in the first place. + +### III. Token Parity + +Any new custom color, spacing, or typography token MUST have a real, resolved value in both +`theme.json` and `styles/dark.json`. Assigning the same literal value to both light and dark +modes is hardcoding, not tokenizing, and MUST NOT be done. Before reusing an existing +token or class, verify what it actually resolves to (color/size) rather than inferring from +its name. When mapping a design's heading levels (H1-H6) onto theme typography tokens, verify +the actual resolved px value first — do not assume a design's H2 maps onto the theme's `heading-2` +token just because the names match. + +**Rationale**: Dark-mode parity breaks silently and often invisibly to the author working in +light mode; verifying resolved values (not names) is the only reliable defense against both +missing dark-mode coverage and confidently reusing the wrong token or the wrong type scale. + +### IV. Core Blocks First -### [PRINCIPLE_2_NAME] - -[PRINCIPLE_2_DESCRIPTION] - +Prefer the most semantic core WordPress block available (`core/post-title`, +`core/post-excerpt`, `core/buttons`, `core/navigation`, etc.) before falling back to a +generic `core/group`/`core/columns` substitute. Any decision to fall back to a generic block +MUST be noted in the pattern's description. Before using an attribute or attribute value on a +core WordPress block, verify it is actually supported by that block (check its registered +attributes/supports, or an existing working usage in this repo) rather than guessing. -### [PRINCIPLE_3_NAME] - -[PRINCIPLE_3_DESCRIPTION] - +**Rationale**: Semantic core blocks carry accessibility, editor-UX, and future-core-feature +benefits that a hand-assembled group/columns substitute does not, and silently defaulting to +generic blocks erodes those benefits theme-wide over time. Guessing at unsupported attributes +produces markup that silently fails to apply, which is harder to diagnose than a missing +feature. -### [PRINCIPLE_4_NAME] - -[PRINCIPLE_4_DESCRIPTION] - +### V. Accessibility and Security Non-Negotiables -### [PRINCIPLE_5_NAME] - -[PRINCIPLE_5_DESCRIPTION] - +Heading hierarchy MUST be correct (no skipped levels), images MUST have descriptive alt +text, interactive elements MUST be keyboard-accessible, and focus states MUST NOT be +removed — WCAG 2.1 AA is the baseline. Use ARIA attributes only where genuinely needed; do +not over-ARIA. All PHP output MUST be escaped (`esc_html()`, `esc_attr()`, `esc_url()`, +`wp_kses_post()`), all input MUST be sanitized (`sanitize_text_field()`, `absint()`, etc.) and +validated before use, and translation functions MUST be used correctly +(`__()`, `esc_html__()`, `esc_attr__()`). Never use `eval()`; avoid direct database queries and +use `$wpdb->prepare()` when one is genuinely necessary. A stretched-link/whole-card-click +pattern MUST keep the real anchor at `position: static` — only its `::before` overlay gets +`position: absolute`, sized against an ancestor with `position: relative`. Setting +`position: relative` directly on the anchor breaks the click target by making the anchor its +own containing block; this is a real, recurring bug in this codebase and has been fixed +multiple times. -## [SECTION_2_NAME] - +**Rationale**: These are non-negotiable because they are either legally/ethically required +(accessibility, security) or a specific, repeatedly-reintroduced structural bug in this exact +codebase (the stretched-link anchor mistake) that is cheaper to prevent by rule than to keep +re-diagnosing. -[SECTION_2_CONTENT] - +### VI. Validation Before Done -## [SECTION_3_NAME] - +Every new or changed PHP pattern MUST pass `php -l`, `npm run patterns:escape`, and +`npm run security:scan`; every new/changed JSON file MUST pass `npm run schema:validate`; +theme-wide consistency (slugs, required files) MUST pass `npm run theme:validate`; and all +changed PHP MUST pass `phpcs --standard=WordPress`, before being considered complete. The +`validate_blocks` tool MUST NEVER be used — it has corrupted patterns before and is banned +outright. Verify block correctness via source/JSON inspection or a manual Site Editor check +instead. -[SECTION_3_CONTENT] - +**Rationale**: These checks are cheap, fast, and catch the classes of error (escaping gaps, +security issues, malformed JSON, coding-standard drift) that are expensive to find later in +review or production; the `validate_blocks` ban exists because of direct prior damage it +caused to real patterns in this repo. + +### VII. PHP Minimalism & Engineering Discipline + +Keep `functions.php` as short as sensibly possible — only register block supports, enqueue +assets, or add editor styles there; use `inc/` only for genuine PHP logic that doesn't belong +in `functions.php`, and never invent PHP architecture that `theme.json` can handle. Do not add +a plugin-like architecture to the theme, and do not add features that belong in a plugin +instead. Prefer WordPress core hooks and filters over custom implementations. Prefer small, +targeted diffs — do not rewrite a file that doesn't need rewriting. Do not add npm or Composer +dependencies without clear justification, and do not invent a build pipeline (Webpack, Vite, +etc.) — this repo does not use one unless explicitly added later. + +**Rationale**: This is a theme, not a plugin — architecture creep here is a maintenance and +upgrade-path risk. Small diffs and dependency discipline keep the codebase reviewable and keep +the actual cost of a change proportionate to its stated scope. + +## Available Skills for Planning + +Every plan should check this list and use whichever skill actually matches the task at hand — +not just the one most recently used. Repo-local skills are always available and are +purpose-built for this theme's exact conventions; where a repo-local and a global skill share +similar territory, the repo-local one takes precedence for this repository. Global/session +skill availability can vary by session — confirm a global skill is actually listed as +available before relying on it, and don't treat its absence in a given session as a reason to +skip the workflow it would otherwise cover. + +**Repo-local (`.agents/skills/`, always available in this repo):** + +- `pattern-extractor` — Figma → `ls-theme` pattern conversion (reuse-or-create, token mapping, + dark parity, Icon Block mapping, CSS-vs-GSAP routing); mandatorily chains + `theme-color-token-enforcer` +- `theme-color-token-enforcer` — audit/fix semantic color token usage, dark-mode parity, WCAG + AA contrast +- `wp-block-style-audit` — the JSON-vs-CSS decision procedure for `styles/**/*.json` files +- `block-theme-audit` — broader block-theme conformance audit +- `theme-orphaned-refs` — find orphaned/dead references across the theme +- `themejson-completion` — fill out `theme.json` gaps without overwriting existing config +- `themejson-extractor-orchestrator` / `extractor-skills` — coordinate multi-part + `theme.json` extraction work +- `figma-themejson-palette`, `figma-themejson-radius`, `figma-themejson-shadow`, + `figma-themejson-spacing`, `figma-themejson-style-variations`, `figma-themejson-typography` + — extract specific token families from a Figma variables table into `theme.json` +- `breakdown-plan` — issue/project planning breakdown + +**Global/session WordPress skills (availability varies by session — verify before relying on one):** + +- `wordpress-router` / `wordpress-block-theme-router` — route an ambiguous WP task to the + correct specialist skill +- `wp-block-development`, `wp-block-themes`, `wp-patterns` — block and block-theme development + fundamentals +- `wp-interactivity-api` — Interactivity API (`data-wp-*` directives, stores) +- `wp-abilities-api`, `wp-abilities-audit`, `wp-abilities-verify` — WordPress Abilities API + registration and verification +- `wp-rest-api` — REST route/controller development +- `wp-performance` — profiling, query/cache/autoload optimization +- `wp-phpstan` — static analysis setup and fixes +- `wp-playground` — WordPress Playground blueprints and local instances +- `wp-plugin-development`, `wp-plugin-directory-guidelines` — plugin architecture and + WordPress.org guideline compliance (not generally applicable to this theme repo, but + relevant if a companion plugin is touched) +- `wp-project-triage` — deterministic repo inspection/classification +- `wp-wpcli-and-ops` — WP-CLI usage and operational scripting +- `wpds` — WordPress Design System component/token usage +- `wordpress-pattern-generator`, `wordpress-block-style-generator`, + `wordpress-section-style-generator`, `wordpress-template-generator`, + `wordpress-template-part-generator`, `wordpress-custom-template-generator`, + `wordpress-asset-parameter-generator`, `wordpress-block-asset-validator` — generator/ + validator skills for specific WP block-theme asset types; prefer the repo-local + `pattern-extractor` for actual pattern builds in this repo, but these remain useful for + template/template-part/custom-template work `pattern-extractor` doesn't cover +- `wordpress-plugin-packaging-review` — plugin packaging/release review (not generally + applicable to this theme repo) + +## Workflow & Process + +- `CHANGELOG.md` gets one dated entry per PR (Keep a Changelog format), written when the PR + is opened/updated — not batched per-commit. +- Commit messages use a heading + bullet structure, never prose paragraphs, grouped under + short section headings (e.g. "Bug fix", "Cleanup", "Context"). +- PR test-plan checkboxes are only checked when actually run/verified in that session — leave + manual-QA-only items unchecked/pending; never mark an item complete to make the list look + more thorough than the work actually was. +- Never branch directly from a remote-tracking ref (`git checkout -b x origin/develop` + silently tracks it as upstream); verify branch tracking with `git branch -vv`. +- A structural/enqueue change (e.g. a new CSS bundle's front-end loading condition) should use + a real, known WordPress conditional tag (`is_page()`, `is_front_page()`, + `is_post_type_archive()`, etc.) as soon as the condition is actually knowable. Don't defer + it indefinitely once the answer is known, and don't invent a fragile guess before it is. +- Never reuse a `wp:pattern` slug reference for text that must vary between call sites — it + always renders identical static content at every instance; use a PHP pattern with per-call + data instead when content needs to vary. +- File locations are not optional conventions: developer/AI-generated reports go in + `.github/reports/` (never the repo root or `docs/`), task lists in `.github/tasks/`, + reusable prompt files in `.github/prompts/`, portable skills in `.agents/skills/`, and agent + persona definitions in `.agents/agents/`. `docs/` is reserved for end-user documentation + only. Do not modify `.github/workflows/` without understanding the CI impact of the change. ## Governance - -[GOVERNANCE_RULES] - +This constitution is derived from, and subordinate to, `AGENTS.md` — the repo's actual +binding contributor guidance. `AGENTS.md` remains authoritative if the two ever diverge; this +file exists so Spec Kit's `/speckit-plan`, `/speckit-specify`, and `/speckit-tasks` generation +inherit these rules automatically, instead of requiring them to be manually re-explained each +session. When `AGENTS.md` changes in a way that affects a principle here, this file MUST be +amended to match in the same change or a prompt follow-up. This file is intentionally *not* a +full copy of `AGENTS.md` — only the rules that materially affect planning/implementation +decisions are curated here, to avoid the two documents drifting out of sync from duplicated +content. + +Amendments follow semantic versioning: MAJOR for a backward-incompatible principle removal or +redefinition, MINOR for a new principle or materially expanded guidance, PATCH for wording/ +clarification fixes. Every PR that touches `patterns/`, `styles/`, `src/scss/`, or +`theme.json` is expected to comply with the principles above; a reviewer citing this document +supersedes an unstated personal preference, but never supersedes `AGENTS.md` itself. -**Version**: [CONSTITUTION_VERSION] | **Ratified**: [RATIFICATION_DATE] | **Last Amended**: [LAST_AMENDED_DATE] - +**Version**: 1.2.0 | **Ratified**: 2026-09-11 | **Last Amended**: 2026-09-11 diff --git a/CHANGELOG.md b/CHANGELOG.md index f4c2ebd..7cacab6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,21 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 --- +## [Unreleased] — Build Services page: service tiles section (LS-1598) + +### Added + +- 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 + +- Fixed each row's `blockGap` only 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. + +[PR #54](https://github.com/lightspeedwp/ls-theme/pull/54) + +--- + ## [Unreleased] — Expand CodeRabbit config for full auto-review and finishing touches (LS-4124) ### Changed diff --git a/assets/css/services-service-tiles.css b/assets/css/services-service-tiles.css new file mode 100644 index 0000000..6a67678 --- /dev/null +++ b/assets/css/services-service-tiles.css @@ -0,0 +1 @@ +.is-style-card-service-tile{transition:border-color var(--wp--custom--animation--duration--base) var(--wp--custom--animation--easing--standard)}.is-style-card-service-tile:hover,.is-style-card-service-tile:focus-within{border-color:var(--ls-card-service-tile-border-active)}@media(prefers-reduced-motion: reduce){.is-style-card-service-tile{transition:none}} diff --git a/functions.php b/functions.php index 76c0786..034f025 100644 --- a/functions.php +++ b/functions.php @@ -73,6 +73,7 @@ function ls_theme_setup() { add_editor_style( 'assets/css/services-hero.css' ); add_editor_style( 'assets/css/services-linked-decisions.css' ); add_editor_style( 'assets/css/services-service-clusters.css' ); + add_editor_style( 'assets/css/services-service-tiles.css' ); add_editor_style( 'assets/css/work-hero.css' ); add_editor_style( 'assets/css/work-single-hero.css' ); add_editor_style( 'assets/css/blog-hero.css' ); diff --git a/inc/animations.php b/inc/animations.php index f398e82..20b16e4 100644 --- a/inc/animations.php +++ b/inc/animations.php @@ -74,6 +74,7 @@ function ls_theme_get_bundle_render_markers() { 'services-hero' => array( 'classes' => array( 'ls-service-pill' ) ), 'services-linked-decisions' => array( 'classes' => array( 'ls-process-pill' ) ), 'services-service-clusters' => array( 'classes' => array( 'ls-cluster-tag' ) ), + 'services-service-tiles' => array( 'classes' => array( 'is-style-card-service-tile' ) ), 'work-hero' => array( 'classes' => array( 'ls-work-hero' ) ), 'work-single-hero' => array( 'classes' => array( 'ls-work-single-meta' ) ), 'blog-hero' => array( 'classes' => array( 'ls-blog-hero' ) ), @@ -241,11 +242,12 @@ function ls_theme_get_effect_styles( $context = 'front' ) { // Icon-well classes are also used by 3 homepage sections (what-we-build, // where-to-start, where-to-fit) in addition to the Work archive, by the // Search template's "Useful destinations" section, and by the Services page's - // "Service clusters" section (is-style-card-category, ls-icon-well-brand) — - // without a check for each of these here, that reuse only gets caught by the - // render_block fallback below, which prints in the footer and visibly restyles - // the cards after first paint. The Services page has no dedicated template yet - // (LS-1598 in progress), so this checks its slug directly rather than a template. + // "Service clusters" and "Service tiles" sections (is-style-card-category, + // ls-icon-well-brand) — without a check for each of these here, that reuse only + // gets caught by the render_block fallback below, which prints in the footer and + // visibly restyles the cards after first paint. The Services page has no dedicated + // template yet (LS-1598 in progress), so this checks its slug directly rather than + // a template. 'condition' => static function () { return is_front_page() || is_post_type_archive( 'project' ) || is_search() || is_page( 'services' ); }, @@ -291,6 +293,17 @@ function ls_theme_get_effect_styles( $context = 'front' ) { 'contexts' => array( 'front', 'editor' ), // Same reasoning as services-hero above. ), + 'services-service-tiles' => array( + 'handle' => 'ls-theme-services-service-tiles', + 'path' => 'assets/css/services-service-tiles.css', + 'contexts' => array( 'front', 'editor' ), + // Unlike services-hero/services-linked-decisions/services-service-clusters above, + // this bundle is scoped to a real, known page slug rather than deferred — no reason + // to wait for a dedicated template when the condition is this cheap to add now. + 'condition' => static function () { + return is_page( 'services' ); + }, + ), 'work-hero' => array( 'handle' => 'ls-theme-work-hero', 'path' => 'assets/css/work-hero.css', diff --git a/package.json b/package.json index b888b5a..35ce368 100644 --- a/package.json +++ b/package.json @@ -4,15 +4,15 @@ "description": "LightSpeed Theme is a custom WordPress block theme built by LightSpeed for fast, accessible, maintainable websites using the WordPress Site Editor and block editor.", "type": "module", "scripts": { - "build:css": "sass --no-source-map --no-charset --style=compressed src/scss/animations.scss:assets/css/animations.css src/scss/gsap-animations.scss:assets/css/gsap-animations.css src/scss/structural/taxonomy-filter.scss:assets/css/taxonomy-filter.css src/scss/structural/work-project-card.scss:assets/css/work-project-card.css src/scss/structural/work-archive-sections.scss:assets/css/work-archive-sections.css src/scss/structural/card-shells.scss:assets/css/card-shells.css src/scss/structural/cta-buttons.scss:assets/css/cta-buttons.css src/scss/structural/home-hero.scss:assets/css/home-hero.css src/scss/structural/work-hero.scss:assets/css/work-hero.css src/scss/structural/work-single-hero.scss:assets/css/work-single-hero.css src/scss/structural/blog-hero.scss:assets/css/blog-hero.css src/scss/structural/blog-all-articles.scss:assets/css/blog-all-articles.css src/scss/structural/blog-writing-cta.scss:assets/css/blog-writing-cta.css src/scss/structural/faq.scss:assets/css/faq.css src/scss/structural/links.scss:assets/css/links.css src/scss/structural/button-secondary.scss:assets/css/button-secondary.css src/scss/structural/featured-work.scss:assets/css/featured-work.css src/scss/structural/where-to-fit.scss:assets/css/where-to-fit.css src/scss/structural/homepage-cta.scss:assets/css/homepage-cta.css src/scss/structural/stats-bar.scss:assets/css/stats-bar.css src/scss/structural/homepage-card-rows.scss:assets/css/homepage-card-rows.css src/scss/structural/homepage-why-lightspeed.scss:assets/css/homepage-why-lightspeed.css src/scss/structural/search-results.scss:assets/css/search-results.css src/scss/structural/search-hero.scss:assets/css/search-hero.css src/scss/structural/services-hero.scss:assets/css/services-hero.css src/scss/structural/services-linked-decisions.scss:assets/css/services-linked-decisions.css src/scss/structural/services-service-clusters.scss:assets/css/services-service-clusters.css", - "build:css:dev": "sass --no-source-map --no-charset --style=expanded src/scss/animations.scss:assets/css/animations.css src/scss/gsap-animations.scss:assets/css/gsap-animations.css src/scss/structural/taxonomy-filter.scss:assets/css/taxonomy-filter.css src/scss/structural/work-project-card.scss:assets/css/work-project-card.css src/scss/structural/work-archive-sections.scss:assets/css/work-archive-sections.css src/scss/structural/card-shells.scss:assets/css/card-shells.css src/scss/structural/cta-buttons.scss:assets/css/cta-buttons.css src/scss/structural/home-hero.scss:assets/css/home-hero.css src/scss/structural/work-hero.scss:assets/css/work-hero.css src/scss/structural/work-single-hero.scss:assets/css/work-single-hero.css src/scss/structural/blog-hero.scss:assets/css/blog-hero.css src/scss/structural/blog-all-articles.scss:assets/css/blog-all-articles.css src/scss/structural/blog-writing-cta.scss:assets/css/blog-writing-cta.css src/scss/structural/faq.scss:assets/css/faq.css src/scss/structural/links.scss:assets/css/links.css src/scss/structural/button-secondary.scss:assets/css/button-secondary.css src/scss/structural/featured-work.scss:assets/css/featured-work.css src/scss/structural/where-to-fit.scss:assets/css/where-to-fit.css src/scss/structural/homepage-cta.scss:assets/css/homepage-cta.css src/scss/structural/stats-bar.scss:assets/css/stats-bar.css src/scss/structural/homepage-card-rows.scss:assets/css/homepage-card-rows.css src/scss/structural/homepage-why-lightspeed.scss:assets/css/homepage-why-lightspeed.css src/scss/structural/search-results.scss:assets/css/search-results.css src/scss/structural/search-hero.scss:assets/css/search-hero.css src/scss/structural/services-hero.scss:assets/css/services-hero.css src/scss/structural/services-linked-decisions.scss:assets/css/services-linked-decisions.css src/scss/structural/services-service-clusters.scss:assets/css/services-service-clusters.css", + "build:css": "sass --no-source-map --no-charset --style=compressed src/scss/animations.scss:assets/css/animations.css src/scss/gsap-animations.scss:assets/css/gsap-animations.css src/scss/structural/taxonomy-filter.scss:assets/css/taxonomy-filter.css src/scss/structural/work-project-card.scss:assets/css/work-project-card.css src/scss/structural/work-archive-sections.scss:assets/css/work-archive-sections.css src/scss/structural/card-shells.scss:assets/css/card-shells.css src/scss/structural/cta-buttons.scss:assets/css/cta-buttons.css src/scss/structural/home-hero.scss:assets/css/home-hero.css src/scss/structural/work-hero.scss:assets/css/work-hero.css src/scss/structural/work-single-hero.scss:assets/css/work-single-hero.css src/scss/structural/blog-hero.scss:assets/css/blog-hero.css src/scss/structural/blog-all-articles.scss:assets/css/blog-all-articles.css src/scss/structural/blog-writing-cta.scss:assets/css/blog-writing-cta.css src/scss/structural/faq.scss:assets/css/faq.css src/scss/structural/links.scss:assets/css/links.css src/scss/structural/button-secondary.scss:assets/css/button-secondary.css src/scss/structural/featured-work.scss:assets/css/featured-work.css src/scss/structural/where-to-fit.scss:assets/css/where-to-fit.css src/scss/structural/homepage-cta.scss:assets/css/homepage-cta.css src/scss/structural/stats-bar.scss:assets/css/stats-bar.css src/scss/structural/homepage-card-rows.scss:assets/css/homepage-card-rows.css src/scss/structural/homepage-why-lightspeed.scss:assets/css/homepage-why-lightspeed.css src/scss/structural/search-results.scss:assets/css/search-results.css src/scss/structural/search-hero.scss:assets/css/search-hero.css src/scss/structural/services-hero.scss:assets/css/services-hero.css src/scss/structural/services-linked-decisions.scss:assets/css/services-linked-decisions.css src/scss/structural/services-service-clusters.scss:assets/css/services-service-clusters.css src/scss/structural/services-service-tiles.scss:assets/css/services-service-tiles.css", + "build:css:dev": "sass --no-source-map --no-charset --style=expanded src/scss/animations.scss:assets/css/animations.css src/scss/gsap-animations.scss:assets/css/gsap-animations.css src/scss/structural/taxonomy-filter.scss:assets/css/taxonomy-filter.css src/scss/structural/work-project-card.scss:assets/css/work-project-card.css src/scss/structural/work-archive-sections.scss:assets/css/work-archive-sections.css src/scss/structural/card-shells.scss:assets/css/card-shells.css src/scss/structural/cta-buttons.scss:assets/css/cta-buttons.css src/scss/structural/home-hero.scss:assets/css/home-hero.css src/scss/structural/work-hero.scss:assets/css/work-hero.css src/scss/structural/work-single-hero.scss:assets/css/work-single-hero.css src/scss/structural/blog-hero.scss:assets/css/blog-hero.css src/scss/structural/blog-all-articles.scss:assets/css/blog-all-articles.css src/scss/structural/blog-writing-cta.scss:assets/css/blog-writing-cta.css src/scss/structural/faq.scss:assets/css/faq.css src/scss/structural/links.scss:assets/css/links.css src/scss/structural/button-secondary.scss:assets/css/button-secondary.css src/scss/structural/featured-work.scss:assets/css/featured-work.css src/scss/structural/where-to-fit.scss:assets/css/where-to-fit.css src/scss/structural/homepage-cta.scss:assets/css/homepage-cta.css src/scss/structural/stats-bar.scss:assets/css/stats-bar.css src/scss/structural/homepage-card-rows.scss:assets/css/homepage-card-rows.css src/scss/structural/homepage-why-lightspeed.scss:assets/css/homepage-why-lightspeed.css src/scss/structural/search-results.scss:assets/css/search-results.css src/scss/structural/search-hero.scss:assets/css/search-hero.css src/scss/structural/services-hero.scss:assets/css/services-hero.css src/scss/structural/services-linked-decisions.scss:assets/css/services-linked-decisions.css src/scss/structural/services-service-clusters.scss:assets/css/services-service-clusters.css src/scss/structural/services-service-tiles.scss:assets/css/services-service-tiles.css", "schema:validate": "node theme-utils.mjs validate-schema", "theme:validate": "node theme-utils.mjs validate-theme", "patterns:escape": "node theme-utils.mjs escape-patterns", "security:scan": "node theme-utils.mjs security-scan", "lint": "npm run lint:json", "lint:json": "node --input-type=module --eval \"import { readFileSync } from 'fs'; import { glob } from 'glob'; const files = await glob(['theme.json', 'styles/**/*.json']); let ok = true; for (const f of files) { try { JSON.parse(readFileSync(f, 'utf8')); } catch (e) { console.error('Invalid JSON:', f, e.message); ok = false; } } if (ok) console.log('All JSON files are valid.'); else process.exit(1);\"", - "watch:css": "sass --watch --no-source-map --no-charset --style=expanded src/scss/animations.scss:assets/css/animations.css src/scss/gsap-animations.scss:assets/css/gsap-animations.css src/scss/structural/taxonomy-filter.scss:assets/css/taxonomy-filter.css src/scss/structural/work-project-card.scss:assets/css/work-project-card.css src/scss/structural/work-archive-sections.scss:assets/css/work-archive-sections.css src/scss/structural/card-shells.scss:assets/css/card-shells.css src/scss/structural/cta-buttons.scss:assets/css/cta-buttons.css src/scss/structural/home-hero.scss:assets/css/home-hero.css src/scss/structural/work-hero.scss:assets/css/work-hero.css src/scss/structural/work-single-hero.scss:assets/css/work-single-hero.css src/scss/structural/blog-hero.scss:assets/css/blog-hero.css src/scss/structural/blog-all-articles.scss:assets/css/blog-all-articles.css src/scss/structural/blog-writing-cta.scss:assets/css/blog-writing-cta.css src/scss/structural/faq.scss:assets/css/faq.css src/scss/structural/links.scss:assets/css/links.css src/scss/structural/button-secondary.scss:assets/css/button-secondary.css src/scss/structural/featured-work.scss:assets/css/featured-work.css src/scss/structural/where-to-fit.scss:assets/css/where-to-fit.css src/scss/structural/homepage-cta.scss:assets/css/homepage-cta.css src/scss/structural/stats-bar.scss:assets/css/stats-bar.css src/scss/structural/homepage-card-rows.scss:assets/css/homepage-card-rows.css src/scss/structural/homepage-why-lightspeed.scss:assets/css/homepage-why-lightspeed.css src/scss/structural/search-results.scss:assets/css/search-results.css src/scss/structural/search-hero.scss:assets/css/search-hero.css src/scss/structural/services-hero.scss:assets/css/services-hero.css src/scss/structural/services-linked-decisions.scss:assets/css/services-linked-decisions.css src/scss/structural/services-service-clusters.scss:assets/css/services-service-clusters.css" + "watch:css": "sass --watch --no-source-map --no-charset --style=expanded src/scss/animations.scss:assets/css/animations.css src/scss/gsap-animations.scss:assets/css/gsap-animations.css src/scss/structural/taxonomy-filter.scss:assets/css/taxonomy-filter.css src/scss/structural/work-project-card.scss:assets/css/work-project-card.css src/scss/structural/work-archive-sections.scss:assets/css/work-archive-sections.css src/scss/structural/card-shells.scss:assets/css/card-shells.css src/scss/structural/cta-buttons.scss:assets/css/cta-buttons.css src/scss/structural/home-hero.scss:assets/css/home-hero.css src/scss/structural/work-hero.scss:assets/css/work-hero.css src/scss/structural/work-single-hero.scss:assets/css/work-single-hero.css src/scss/structural/blog-hero.scss:assets/css/blog-hero.css src/scss/structural/blog-all-articles.scss:assets/css/blog-all-articles.css src/scss/structural/blog-writing-cta.scss:assets/css/blog-writing-cta.css src/scss/structural/faq.scss:assets/css/faq.css src/scss/structural/links.scss:assets/css/links.css src/scss/structural/button-secondary.scss:assets/css/button-secondary.css src/scss/structural/featured-work.scss:assets/css/featured-work.css src/scss/structural/where-to-fit.scss:assets/css/where-to-fit.css src/scss/structural/homepage-cta.scss:assets/css/homepage-cta.css src/scss/structural/stats-bar.scss:assets/css/stats-bar.css src/scss/structural/homepage-card-rows.scss:assets/css/homepage-card-rows.css src/scss/structural/homepage-why-lightspeed.scss:assets/css/homepage-why-lightspeed.css src/scss/structural/search-results.scss:assets/css/search-results.css src/scss/structural/search-hero.scss:assets/css/search-hero.css src/scss/structural/services-hero.scss:assets/css/services-hero.css src/scss/structural/services-linked-decisions.scss:assets/css/services-linked-decisions.css src/scss/structural/services-service-clusters.scss:assets/css/services-service-clusters.css src/scss/structural/services-service-tiles.scss:assets/css/services-service-tiles.css" }, "devDependencies": { "@axe-core/playwright": "^4.13.0", diff --git a/patterns/sections/services-service-tiles.php b/patterns/sections/services-service-tiles.php new file mode 100644 index 0000000..37a1ed5 --- /dev/null +++ b/patterns/sections/services-service-tiles.php @@ -0,0 +1,265 @@ + __( 'Discovery', 'ls-theme' ), + 'kicker' => __( 'Strategy, research and technical clarity', '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', + ), + array( + 'label' => __( 'Content', 'ls-theme' ), + 'kicker' => __( 'Structure, taxonomy, governance', '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' ), + 'kicker' => __( 'Systems, patterns, accessibility', 'ls-theme' ), + 'description' => __( 'Design systems, accessible patterns and Figma→WordPress parity. Already a deeper page.', 'ls-theme' ), + 'url' => '/services/design/', + 'icon' => 'paint-brush', + ), + array( + 'label' => __( 'Development', 'ls-theme' ), + 'kicker' => __( 'Maintainable WordPress engineering', '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' ), + 'kicker' => __( 'Move platforms with control', 'ls-theme' ), + 'description' => __( 'Audits, mapping and redirects that take legacy platforms apart without losing traction.', 'ls-theme' ), + 'url' => '/services/migrations/', + 'icon' => 'arrows-left-right', + ), + array( + 'label' => __( 'Hosting', 'ls-theme' ), + 'kicker' => __( 'Aligned environments', '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' ), + 'kicker' => __( 'Speed, Vitals, stability', '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' ), + 'kicker' => __( 'Hardening, monitoring, recovery', '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' ), + 'kicker' => __( 'Confidence and adoption', 'ls-theme' ), + 'description' => __( 'Role-specific training and reference materials that move teams from dependency to confidence.', 'ls-theme' ), + 'url' => '/services/training/', + 'icon' => 'graduation-cap', + ), + array( + 'label' => __( 'Support', 'ls-theme' ), + 'kicker' => __( 'Maintenance and continuity', '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' ), + 'kicker' => __( 'Structural, technical, content', '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' ), + 'kicker' => __( 'Usable for more people, by design', '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' ), + 'kicker' => __( 'Subscriber journeys, aligned', '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', + ), + array( + 'label' => __( 'AI', 'ls-theme' ), + 'kicker' => __( 'Readiness, governance, workflow', 'ls-theme' ), + 'description' => __( 'AI-readiness reviews, governance and workflow planning — practical use without messy adoption.', 'ls-theme' ), + 'url' => '/services/ai/', + 'icon' => 'special-interests', + ), +); + +/** + * Renders one service tile card. A local closure (not a top-level function) since this file + * can be included more than once per request via pattern registration/re-registration. + * + * @param array $ls_tile Service tile data from $ls_service_tiles. + * @param int $ls_index Human-facing 1-based index shown in the card corner. + */ +$ls_render_service_tile = function ( $ls_tile, $ls_index ) { + // Acronym labels (AI, SEO) must stay upper-case in the CTA text — only lower-case the rest. + $ls_acronym_labels = array( 'AI', 'SEO' ); + $ls_read_link_service = in_array( $ls_tile['label'], $ls_acronym_labels, true ) + ? $ls_tile['label'] + : strtolower( $ls_tile['label'] ); + $ls_read_link_text = sprintf( + /* translators: %s: service name, lowercase except acronyms (AI, SEO). */ + __( 'Read about %s', 'ls-theme' ), + $ls_read_link_service + ); + ?> + + +
+ +
+ +
+ + + +

+ + + +
+ +

+ + + +

+ + + +

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

+ +
+ + + +

+ +
+ + + +
+ +
+ +

+ +
+ +
+ +
+ + + +
+ $ls_tile ) : ?> + +
+ +
+ + +
+ + + +
+ $ls_tile ) : ?> + +
+ +
+ + +
+ + + +
+ $ls_tile ) : ?> + +
+ +
+ + +
+ + + +
+ $ls_tile ) : ?> + +
+ +
+ + +
+ +
+ +
+ diff --git a/specs/001-services-page/checklists/content-ux.md b/specs/001-services-page/checklists/content-ux.md new file mode 100644 index 0000000..79b43c9 --- /dev/null +++ b/specs/001-services-page/checklists/content-ux.md @@ -0,0 +1,87 @@ +# Content/UX Checklist: Services Page (Remaining Sections & QA) + +**Purpose**: Validate that spec.md's requirements for the Entry Points, Delivery by the +Numbers, and closing CTA sections — plus the process constraints from `.specify/memory/constitution.md` +v1.2.0 — are complete, clear, consistent, and reviewable by a PR reviewer before +implementation begins +**Created**: 2026-09-11 +**Feature**: [spec.md](../spec.md) + +**Note**: This custom checklist is generated by the `/speckit-checklist` command based on +feature context and requirements. This regenerates the previous version of this file, which +was written against a stale, superseded scope (generic lifecycle-stage sections) that +spec.md's own Clarifications session already overturned — the old content was replaced rather +than appended to, since its FR references no longer exist and mixing them with current items +would be actively misleading rather than merely obsolete. +**Review Ownership**: This checklist is a reviewer-owned requirements-quality review artifact. +Mark an item `[x]` only when the reviewer determines the requirements-quality criterion is +satisfied. +**Marker Semantics**: `[x]` means the criterion has been reviewed and satisfied for +requirements quality. It does not mean implementation work is complete. + +## Requirement Completeness + +- [ ] CHK001 Are content requirements defined for the exact number of Entry Point options, or only "the distinct ways to begin engaging"? [Completeness, Spec §FR-001] +- [ ] CHK002 Are content requirements defined for the exact number and set of Delivery Metrics, or only "metrics/figures"? [Completeness, Spec §FR-002] +- [ ] CHK003 Are requirements defined for what the closing CTA's action actually links to (a specific page/form), or only "a clear, actionable next step"? [Completeness, Spec §FR-003] +- [ ] CHK004 Are requirements defined for the exact reuse mechanism for Hero/Linked decisions/Service clusters/Service tiles (e.g., "no duplication") beyond the general FR-004 statement? [Completeness, Spec §FR-004] +- [ ] CHK005 Are content requirements defined for what happens if an Entry Point option has no working link yet at build time? [Completeness, Spec Edge Cases] + +## Requirement Clarity + +- [ ] CHK006 Is "the distinct ways to begin engaging" in FR-001 clarified with a concrete list, or left to the Figma frame alone with no textual fallback if Figma is unavailable? [Clarity, Spec §FR-001] +- [ ] CHK007 Is "delivery-scale metrics/figures" in FR-002 clarified with expected value types (e.g., counts, percentages, years), or left fully to Figma? [Clarity, Spec §FR-002] +- [ ] CHK008 Is "normal implementation tolerance" in User Story 4's acceptance scenario quantified, or left as a subjective judgment call for design QA sign-off? [Ambiguity, Spec §User Story 4] +- [ ] CHK009 Is "significantly longer" in the Delivery Metric edge case (more digits than siblings) quantified with a specific threshold, or left subjective? [Ambiguity, Spec Edge Cases] + +## Requirement Consistency + +- [ ] CHK010 Are the content requirements for the 3 new sections consistent with how the already-merged Hero/Linked decisions/Service clusters/Service tiles sections were specified (e.g., same level of copy detail expected)? [Consistency] +- [ ] CHK011 Does FR-005 ("reusable ls-theme pattern consistent with existing Services page patterns") align with constitution Principle II's shape-based naming rule, or could a reviewer read FR-005 as permitting page-scoped naming since it says "consistent with existing Services page patterns"? [Consistency, Spec §FR-005] + +## Acceptance Criteria Quality + +- [ ] CHK012 Can SC-001 ("all three remaining sections render... matching their respective Figma frames") be objectively verified, or does "matching" rely on a subjective visual judgment with no defined tolerance? [Measurability, Spec §SC-001] +- [ ] CHK013 Is a review/testing method specified for confirming SC-002 ("zero unresolved visual discrepancies"), beyond "compare each section side-by-side"? [Gap, Spec §SC-002] + +## Scenario Coverage + +- [ ] CHK014 Are content requirements defined for a visitor who arrives directly at the closing CTA (e.g., via anchor link) without reading the earlier sections first? [Coverage, Gap] +- [ ] CHK015 Are requirements defined for how Entry Points or Delivery by the Numbers should render if the Figma-specified icons/imagery are not yet available in the `lightspeed` icon collection? [Coverage, Gap] + +## Edge Case Coverage + +- [ ] CHK016 Is the "viewport between defined breakpoints" edge case (spec Edge Cases) given a concrete requirement for these 3 new sections specifically, or only a general intention inherited from the page-wide statement? [Clarity, Spec Edge Cases] +- [ ] CHK017 Are requirements defined for an Entry Point whose target page or Delivery Metric value is later removed or changed? [Gap, Exception Flow] + +## Non-Functional Requirements + +- [ ] CHK018 Are accessibility requirements (heading structure, link context, color contrast) specified for the 3 new sections specifically, beyond the general assumption of reusing existing tokens? [Gap, Non-Functional] +- [ ] CHK019 Are SEO metadata requirements (FR-006) specific enough to be reviewed objectively (e.g., character-length guidance for title/description), or left fully to implementer judgment? [Ambiguity, Spec §FR-006] + +## Process & Tooling Requirements + +- [ ] CHK020 Does plan.md require each of the 3 new sections to be built via the `pattern-extractor` skill's approval-gated reuse-or-create workflow, rather than ad hoc Figma-to-markup translation, per constitution Principle II? [Completeness, Plan §Constitution Check] +- [ ] CHK021 Does plan.md/tasks.md require `theme-color-token-enforcer` to run for any pattern/style/CSS file created or touched by the 3 new sections, not just a general "reuse tokens" statement? [Completeness, Constitution Principle II] +- [ ] CHK022 Does tasks.md require consulting `wp-block-style-audit` before any new `css` field is written in a `styles/**/*.json` file for these sections, rather than leaving the JSON-vs-CSS decision to implementer discretion? [Completeness, Constitution Principle II] +- [ ] CHK023 Is it specified which validation commands (per constitution Principle VI: `php -l`, `patterns:escape`, `security:scan`, `schema:validate`, `phpcs`) are required for each new pattern individually, or only as one general end-of-feature pass? [Clarity, Tasks Phase 9] + +## Dependencies & Assumptions + +- [ ] CHK024 Is the assumption that "the Figma design already reflects the final approved layout" (spec.md Assumptions) validated before this checklist's review, or does it remain an open dependency at spec-approval time? [Assumption, Spec Assumptions] +- [ ] CHK025 Is the dependency on `card-link-row.json`/`stat-segment.json` being suitable reuse candidates (research.md) confirmed against the actual Figma frames, or still an untested hypothesis at spec-approval time? [Dependency, Gap] + +## Ambiguities & Conflicts + +- [ ] CHK026 Is there a single, unambiguous definition of "the Services page's remaining content order" (Entry Points → Delivery by the Numbers → CTA) used consistently across spec.md, data-model.md, and tasks.md? [Consistency] +- [ ] CHK027 Is a requirement/acceptance-criteria ID scheme (FR-###, SC-###) applied consistently enough for a PR reviewer to trace each checklist finding back to a single spec line without ambiguity? [Traceability] + +## Notes + +- Mark items `[x]` only after review confirms the requirement-quality criterion is satisfied +- Leave items unchecked when they still require clarification, correction, or reviewer evaluation +- `/speckit-implement` reads checklist checkbox state as a gate and must not modify markers +- `checklists/requirements.md` has a separate built-in lifecycle maintained by `/speckit-specify` and `/speckit-clarify` +- Add comments or findings inline +- Link to relevant resources or documentation +- Items are numbered sequentially for easy reference diff --git a/specs/001-services-page/checklists/requirements.md b/specs/001-services-page/checklists/requirements.md new file mode 100644 index 0000000..454bea8 --- /dev/null +++ b/specs/001-services-page/checklists/requirements.md @@ -0,0 +1,34 @@ +# Specification Quality Checklist: Services Page (Lifecycle Sections & QA) + +**Purpose**: Validate specification completeness and quality before proceeding to planning +**Created**: 2026-09-11 +**Feature**: [spec.md](../spec.md) + +## Content Quality + +- [x] No implementation details (languages, frameworks, APIs) +- [x] Focused on user value and business needs +- [x] Written for non-technical stakeholders +- [x] All mandatory sections completed + +## Requirement Completeness + +- [x] No [NEEDS CLARIFICATION] markers remain +- [x] Requirements are testable and unambiguous +- [x] Success criteria are measurable +- [x] Success criteria are technology-agnostic (no implementation details) +- [x] All acceptance scenarios are defined +- [x] Edge cases are identified +- [x] Scope is clearly bounded +- [x] Dependencies and assumptions identified + +## Feature Readiness + +- [x] All functional requirements have clear acceptance criteria +- [x] User scenarios cover primary flows +- [x] Feature meets measurable outcomes defined in Success Criteria +- [x] No implementation details leak into specification + +## Notes + +- All items pass on first validation pass. Ready for `/speckit-plan`. diff --git a/specs/001-services-page/data-model.md b/specs/001-services-page/data-model.md new file mode 100644 index 0000000..c729f51 --- /dev/null +++ b/specs/001-services-page/data-model.md @@ -0,0 +1,70 @@ +# Phase 1 Data Model: Services Page (Remaining Sections & QA) + +This feature has no application data storage — it is static block-theme content. "Entities" +below describe content structure, not database models. + +## Entry Point + +Represents one distinct way a visitor can begin engaging with LightSpeed. + +- **Label**: Short name for the entry point +- **Description**: Explanatory copy for what this path involves +- **Link/Action**: URL or CTA the visitor follows to act on it + +**Validation rules**: +- Every entry point has a label, description, and a working link/action — no placeholder-only + content. +- The section layout must accommodate at least the Figma-specified count (node 8044-164205) + without visual imbalance if the count changes later. + +## Delivery Metric + +A single stat/figure in the "Delivery by the Numbers" section. + +- **Value**: The figure itself (e.g. a number, often with a unit/suffix) +- **Label**: What the figure measures + +**Validation rules**: +- Layout must not break or visually unbalance the row when value lengths vary significantly + (e.g. more digits than sibling values). + +## CTA (Call to Action) + +The page's closing action element. + +- **Heading/Description**: Closing copy summarizing the page's call to action +- **Primary action link**: The single main link/button the visitor is directed to + +**Validation rules**: +- Must render a clear, actionable, working link matching Figma (node 8044-164294). + +## Service Cluster / Linked Decision / Service Tile (existing, reused) + +Already-implemented navigational elements from the merged Hero, "Linked decisions", "Service +clusters", and "Service tiles" sections (PR #51, #54). Not modified by this feature unless +Figma QA finds a defect in one of them. + +## Page Metadata + +- **Title**: Unique SEO title for the Services page +- **Description**: Meta description summarizing the service-model hub purpose + +**Validation rule**: Title and description must be distinct from every other page's metadata +on the site (FR-006 / SC-004). + +## State / Relationships + +```text +Services Page + ├─ Hero (built, merged PR #51) + ├─ Linked Decisions (built, merged PR #51) + ├─ Service Clusters (built, merged PR #51) + ├─ Service Tiles (built, merged PR #54) + ├─ Entry Points [to build — Figma node 8044-164205] + ├─ Delivery by the Numbers [to build — Figma node 8044-164253] + ├─ Closing CTA [to build — Figma node 8044-164294] + └─ Page Metadata (title, description) [to set] +``` + +No section has a hard render-time dependency on another; they are visually sequential per +FR-001–FR-003 but each is independently testable (per spec User Stories 1–3). diff --git a/specs/001-services-page/plan.md b/specs/001-services-page/plan.md new file mode 100644 index 0000000..4973ec6 --- /dev/null +++ b/specs/001-services-page/plan.md @@ -0,0 +1,147 @@ +# Implementation Plan: Services Page (Remaining Sections & QA) + +**Branch**: `feature/ls-1598-services-page-batch-2` | **Date**: 2026-09-11 | **Spec**: [spec.md](./spec.md) + +**Input**: Feature specification from `/specs/001-services-page/spec.md` + +## Summary + +Complete the LightSpeed Services page (LS-1598). Hero, "Linked decisions", "Service clusters", +and "Service tiles" sections are already built and merged (PR #51, #54) and together already +cover all six lifecycle stages (Discover → Create → Build → Launch → Grow → Evolve) per +spec.md's Clarifications session — no further lifecycle-stage sections are needed. The +remaining scope is exactly three new `ls-theme` block patterns confirmed against Figma: + +1. **Entry Points** ([Figma node 8044-164205](https://www.figma.com/design/OTqchq3sRBzUy6TICruzc3/LightSpeedWP-Design-System?node-id=8044-164205)) +2. **Delivery by the Numbers** ([Figma node 8044-164253](https://www.figma.com/design/OTqchq3sRBzUy6TICruzc3/LightSpeedWP-Design-System?node-id=8044-164253)) +3. **Closing CTA** ([Figma node 8044-164294](https://www.figma.com/design/OTqchq3sRBzUy6TICruzc3/LightSpeedWP-Design-System?node-id=8044-164294)) + +...followed by SEO metadata, design QA against Figma, and a responsive check — all as +WordPress block-theme patterns/templates, no custom PHP architecture or backend services. + +## Technical Context + +**Language/Version**: PHP (block theme, no plugin logic), Sass/SCSS compiled to CSS, block +markup (HTML comments) for patterns + +**Primary Dependencies**: WordPress block theme conventions (`theme.json`, Site Editor +patterns/templates), existing `ls-theme` build tooling (`theme-utils.mjs`, Sass build), the +`lightspeed` icon collection (`core/icon` + `lightspeed/{slug}`, registered in `ls-plugin` via +the WordPress 7.1 icon API) per the theme's current icon-block migration (LS-3229) — **not** +the legacy `outermost/icon-block` plugin, GSAP only if a section genuinely needs JS-driven +motion CSS cannot express. This repo's own `.agents/skills/` toolchain is the primary +implementation dependency for each new pattern: `pattern-extractor` (Figma → pattern +conversion, reuse-or-create workflow, Phosphor/Icon-Block mapping), which mandatorily loads +`theme-color-token-enforcer` (semantic color token audit/fix, dark-mode parity, contrast) for +any file it creates or touches, and `wp-block-style-audit` (the JSON-vs-CSS decision authority +for any `styles/**/*.json` file) — per constitution Principle II + +**Storage**: N/A (static theme patterns/content; no custom data storage) + +**Testing**: Manual Site Editor verification, `theme-utils.mjs` validation/lint commands +(`npm run schema:validate`, `npm run patterns:escape`, `npm run security:scan`, +`composer run phpcs`), visual QA against Figma, WCAG AA contrast checks — the `validate_blocks` +tool is banned by the project constitution and MUST NOT be used + +**Target Platform**: WordPress Site Editor / front-end, responsive desktop/tablet/mobile + +**Project Type**: WordPress block theme (single project — no frontend/backend split) + +**Performance Goals**: Standard front-end page-load expectations for a marketing/hub page; any +new structural CSS bundle's front-end enqueue condition should use a real WordPress +conditional tag (e.g. `is_page( 'services' )`) rather than loading unconditionally, once that +condition is actually knowable — it already is for this page + +**Constraints**: Theme-first styling (`theme.json`/`styles/**` JSON before any hand-authored +CSS, each Sass exception commented with the specific JSON limitation it addresses); reuse +existing semantic color/typography/spacing tokens with light/dark parity, or add new ones with +real resolved values in both `theme.json` and `styles/dark.json`; new patterns and any new card +style named by shape, not by page, and only created after confirming no existing +pattern/style/token already fits; prefer semantic core blocks before a generic +group/columns fallback; SCSS-only for hand-authored styling, properly partial-scoped + +**Scale/Scope**: Single page (`Services`), exactly 3 new section patterns (Entry Points, +Delivery by the Numbers, closing CTA), plus SEO metadata and a responsive QA pass + +## Constitution Check + +*GATE: Must pass before Phase 0 research. Re-check after Phase 1 design.* + +Checked against `.specify/memory/constitution.md` v1.2.0 (ratified 2026-09-11): + +- **I. Theme-First Styling**: New sections style via `theme.json`/`styles/**` JSON first; any + Sass exception must carry a `// JSON limitation: ...` comment. No violation anticipated. +- **II. Reuse Before Create**: Before building each of the 3 new patterns, check + `styles/sections/cards/**` and existing Services page patterns for a fitting shape first + (e.g. `card-link-row.json` is a plausible fit for Entry Points' link-style cards; + `stat-segment.json` is a plausible fit for Delivery by the Numbers). Any new card style must + be named by shape and justified by a genuine JSON-vs-inline gap, not page-scoped naming. + Build each pattern via the `pattern-extractor` skill rather than ad hoc Figma translation — + it already encodes this reuse-or-create workflow and mandatorily invokes + `theme-color-token-enforcer`; consult `wp-block-style-audit` for any JSON-vs-CSS decision. +- **III. Token Parity**: No new tokens are expected (existing Services page sections already + established a sufficient token set); if Figma QA reveals a genuine gap, any new token gets + real resolved values in both `theme.json` and `styles/dark.json`. +- **IV. Core Blocks First**: CTA section uses `core/buttons`/`core/button`, not a hand-rolled + link; Entry Points/Delivery by the Numbers use `core/heading`/`core/paragraph` over generic + substitutes where a semantic block fits. +- **V. Accessibility and Security**: Heading hierarchy continues correctly from the existing + page sections (no skipped levels); any whole-card-click pattern keeps the anchor + `position: static` with the `::before` overlay on a `position: relative` ancestor; all PHP + output escaped, translation functions used correctly. +- **VI. Validation Before Done**: Each new pattern passes `php -l`, `patterns:escape`, + `security:scan`, `schema:validate` (for new JSON), and `phpcs --standard=WordPress` before + being considered done. `validate_blocks` is never used. +- **VII. PHP Minimalism & Engineering Discipline**: No new PHP architecture is introduced — + the 3 sections are patterns under `patterns/sections/`, with any enqueue wiring going into + the existing `inc/animations.php`/`functions.php` structure, not a new file/class hierarchy. + No new npm/Composer dependencies or build-pipeline changes are anticipated. + +No violations anticipated — this feature is additive content/pattern work within existing +theme conventions. **Gate: PASS.** + +## Project Structure + +### Documentation (this feature) + +```text +specs/001-services-page/ +├── plan.md # This file (/speckit-plan command output) +├── research.md # Phase 0 output (/speckit-plan command) +├── data-model.md # Phase 1 output (/speckit-plan command) +├── quickstart.md # Phase 1 output (/speckit-plan command) +├── contracts/ # Phase 1 output (/speckit-plan command) — skipped, no external interface +└── tasks.md # Phase 2 output (/speckit-tasks command - NOT created by /speckit-plan) +``` + +### Source Code (repository root) + +```text +patterns/sections/ +├── services-entry-points.php # New — Entry Points section +├── services-delivery-numbers.php # New — Delivery by the Numbers section +└── services-cta.php # New — closing CTA section + +styles/sections/cards/ +└── [only if Reuse Before Create confirms no existing card style fits — name by shape] + +theme.json +styles/ +├── sections/ # section-style JSON partials, reused/extended for the 3 new sections +└── dark.json # required dark-mode parity for any new tokens (not expected) + +src/scss/structural/ +└── [only if a genuine JSON-gap styling need is identified — one file per new section, + matching the existing services-*.scss naming convention, each rule commented with its + specific JSON limitation] +``` + +**Structure Decision**: Single WordPress block-theme project (no frontend/backend split, no +contracts/API surface). The 3 remaining sections are delivered as `ls-theme` block patterns +under `patterns/sections/`, styled via `theme.json`/`styles/**` JSON first, assembled onto the +existing Services page (post ID varies by environment; slug `services`). No new PHP +architecture, storage, or services are introduced. + +## Complexity Tracking + +*No constitution violations identified — this section is not applicable.* diff --git a/specs/001-services-page/quickstart.md b/specs/001-services-page/quickstart.md new file mode 100644 index 0000000..312c95e --- /dev/null +++ b/specs/001-services-page/quickstart.md @@ -0,0 +1,78 @@ +# Quickstart: Validate the Services Page (LS-1598) + +## Prerequisites + +- Local WordPress environment running `ls-theme` (e.g. via WordPress Studio) with the local + test page already resynced to the current pattern source (see project convention: fetch + registered pattern content via `WP_Block_Patterns_Registry`, write to a temp file, + `wp post update`, `wp_get_theme()->delete_pattern_cache()`, `cache flush`) +- Access to the Figma file referenced in LS-1598 for design QA +- `npm install` run at repo root if patterns require Sass compilation + +## Setup + +```bash +npm install +npm run build:css +``` + +Open the Services page (slug `services`) on the local site. + +## Validate: Entry Points section present (FR-001, SC-001) + +1. Load the Services page front-end view. +2. Scroll to the Entry Points section. +3. Expected: distinct, labeled entry-point options matching Figma (node 8044-164205), each + with a working link/CTA. + +## Validate: Delivery by the Numbers section present (FR-002, SC-001) + +1. Scroll to the Delivery by the Numbers section. +2. Expected: metric values/labels render as specified in Figma (node 8044-164253), with no + layout imbalance regardless of value length. + +## Validate: Closing CTA present (FR-003, SC-001) + +1. Scroll to the end of the page. +2. Expected: a clear, actionable CTA matching Figma (node 8044-164294) with a working link. + +## Validate: Existing sections reused, not duplicated (FR-004) + +1. Confirm Hero, Linked decisions, Service clusters, and Service tiles each appear exactly + once. +2. Expected: no duplicate patterns, no content forked from the merged PR #51/#54 versions. + +## Validate: Design QA against Figma (FR-008, SC-002) + +1. Open the Figma file for the Services page. +2. Compare each new section side-by-side with the live page (spacing, typography, color + tokens, copy). +3. Expected: zero unresolved discrepancies; any found are fixed or logged as an accepted + deviation. + +## Validate: Responsive check (FR-007, SC-003) + +```bash +# Use the Browser pane's resize_window tool (or browser dev tools) to emulate breakpoints +``` + +1. View the page at desktop, tablet, and mobile widths. +2. Expected: no overlapping, clipped, or unreadable content in any section, including the 3 + new ones, at any breakpoint. + +## Validate: SEO metadata (FR-006, SC-004) + +1. Inspect the page's `` and meta description (view source or SEO plugin panel). +2. Expected: unique title and description reflecting the service-model hub purpose, not + shared with any other page. + +## Validate: Constitution compliance (theme-first, reuse, tokens, icons, validation) + +1. Confirm each new section reused an existing card shell where one genuinely fit + (`card-link-row.json`, `stat-segment.json`, or the existing `section-cta.php` stub) before + any new `styles/sections/cards/**` file was created, and that any new one is named by shape. +2. Confirm icons use `core/icon` + `lightspeed/{slug}`, not `outermost/icon-block`. +3. Confirm any new structural CSS bundle's front-end condition uses `is_page( 'services' )` + (or a more specific real conditional), not an unconditional load deferred "until later." +4. Run `npm run schema:validate`, `npm run patterns:escape`, `npm run security:scan`, and + `composer run phpcs` — expect no new failures. **Never** use the `validate_blocks` tool. diff --git a/specs/001-services-page/research.md b/specs/001-services-page/research.md new file mode 100644 index 0000000..45e45bc --- /dev/null +++ b/specs/001-services-page/research.md @@ -0,0 +1,79 @@ +# Phase 0 Research: Services Page (Remaining Sections & QA) + +No `[NEEDS CLARIFICATION]` markers remain in the spec or Technical Context — spec.md's +Clarifications session (2026-09-11) already settled the exact scope: three remaining patterns +(Entry Points, Delivery by the Numbers, closing CTA), not generic lifecycle-stage sections. +This document records the key decisions taken from existing repo conventions. + +## Decision: Deliver the 3 remaining sections as reusable block patterns + +- **Rationale**: `ls-theme` is a block theme; every prior Services page section (Hero, Linked + decisions, Service clusters, Service tiles) was built as a pattern in `patterns/sections/` or + `patterns/hero/`. Consistency with established structure, and reviewable against Figma the + same way PR #51/#54 were. +- **Alternatives considered**: A single monolithic template for the whole page — rejected, + breaks the established reuse-first pattern convention (constitution Principle II) and + duplicates work already proven out. + +## Decision: Check for a reusable card shell before creating a new one, per section + +- **Rationale**: Constitution Principle II requires confirming no existing + `styles/sections/cards/**` style already fits before creating a new one, and naming any new + one by shape. Plausible existing-shape candidates to check first: + - **Entry Points**: `card-link-row.json` (compact, bordered, trailing-arrow link card — + already used by `homepage-where-to-start.php`/`work-related-routes.php`) is a strong + candidate if Figma shows a similar simple link-card treatment. + - **Delivery by the Numbers**: `stat-segment.json` (already used by + `patterns/section-stats-grid.php`) is a strong candidate for a metrics row. + - **Closing CTA**: `section-cta.php` already exists as a stub pattern reusing existing + button/heading conventions — check whether it should be fleshed out directly rather than + creating a second CTA pattern. + Each candidate must be confirmed against the actual Figma frame before reuse is assumed. +- **Alternatives considered**: Assuming a new card style is needed for each section without + checking — rejected, violates Principle II and risks duplicating an existing shape. + +## Decision: Style exclusively via theme.json / styles JSON, Sass only for genuine gaps + +- **Rationale**: Constitution Principle I mandates theme-first styling; Sass is permitted only + for what JSON cannot express, with a `// JSON limitation: ...` comment on each such rule. +- **Alternatives considered**: Hand-authored CSS for new sections — rejected, violates the + constitution and prior explicit team feedback. + +## Decision: Reuse existing semantic tokens; only add new tokens with resolved light+dark values if a genuine gap is found during Figma QA + +- **Rationale**: Constitution Principle III bans hardcoded/identical light-dark token values; + existing Services page sections already established a token set likely sufficient for these + 3 sections. +- **Alternatives considered**: Pre-emptively creating new tokens before QA — rejected, adds + risk of duplicate/unused tokens; confirm the gap first. + +## Decision: Icons use `core/icon` with `lightspeed/{slug}`, not `outermost/icon-block` + +- **Rationale**: This branch is now based on PR #50 (LS-3229, Core Icon block migration), which + already converted every sibling Services section (`services-hero.php`, + `services-linked-decisions.php`, `services-service-clusters.php`) — and, per a prior review + finding, `services-service-tiles.php` — to `core/icon` referencing the `lightspeed` icon + collection. Any new section using icons MUST match this, not reintroduce the legacy plugin + block. +- **Alternatives considered**: Continuing to use `outermost/icon-block` + inline SVG (the + original convention before LS-3229) — rejected, now inconsistent with every sibling section + on this exact page. + +## Decision: New structural CSS bundles get a real `is_page( 'services' )` enqueue condition immediately + +- **Rationale**: Earlier Services sections deferred a head-time condition because the page had + no stable template yet ("no page template yet (LS-1598 in progress)"). That excuse no longer + holds — the page's slug (`services`) is already confirmed and already used as a real + condition for the `work-archive-sections` and `services-service-tiles` bundles (fixed in + PR #51/#54 review follow-up). New bundles for these 3 sections should use the same condition + from the start rather than repeating the deferral. +- **Alternatives considered**: Deferring the condition again "until the template exists" — + rejected; the condition is already knowable, per constitution's Workflow & Process guidance + on not deferring indefinitely once an answer is known. + +## Decision: SEO metadata and responsive QA use existing site-wide mechanisms + +- **Rationale**: No custom SEO plugin/infrastructure work is in scope per LS-1598; the site + already has a metadata mechanism (theme/plugin-level) to reuse. +- **Alternatives considered**: Building custom meta-tag handling in the theme — rejected, out + of scope and unnecessary. diff --git a/specs/001-services-page/spec.md b/specs/001-services-page/spec.md new file mode 100644 index 0000000..3261129 --- /dev/null +++ b/specs/001-services-page/spec.md @@ -0,0 +1,146 @@ +# Feature Specification: Services Page (Remaining Sections & QA) + +**Feature Branch**: `feature/ls-1598-services-page-batch-2` + +**Created**: 2026-09-11 + +**Status**: Draft + +**Input**: User description: "Build the Services page (LS-1598) — a hub page explaining the LightSpeed service model, following the confirmed Discover → Create → Build → Launch → Grow → Evolve lifecycle model (LS-1204). Hero, "Linked decisions", and "Service clusters" sections are already built and merged (PR #51); the "Service tiles" section is also already built and merged (PR #54). Remaining scope: build any remaining lifecycle-stage sections/patterns in ls-theme to complete the page content, then design QA against Figma, SEO metadata (title/description), and a responsive check (desktop/tablet/mobile). Reference: https://linear.app/lightspeedwp/issue/LS-1598/design-services-page-build-services-page" + +## Clarifications + +### Session 2026-09-11 + +- Q: On the Services page itself, which of the six lifecycle stages still need their own dedicated section built (beyond the already-merged Hero, "Linked decisions," "Service clusters," and "Service tiles" sections)? → A: None — all six lifecycle stages are already fully represented via the merged "Linked decisions" six-step pill row. No lifecycle-stage sections remain to be built. +- Q: What are the two remaining section patterns and the CTA pattern needed to complete the Services page? → A: Section pattern 4 "Entry Points" ([Figma](https://www.figma.com/design/OTqchq3sRBzUy6TICruzc3/LightSpeedWP-Design-System?node-id=8044-164205)), Section pattern 5 "Delivery by the Numbers" ([Figma](https://www.figma.com/design/OTqchq3sRBzUy6TICruzc3/LightSpeedWP-Design-System?node-id=8044-164253)), and a closing CTA pattern ([Figma](https://www.figma.com/design/OTqchq3sRBzUy6TICruzc3/LightSpeedWP-Design-System?node-id=8044-164294)). + +## User Scenarios & Testing *(mandatory)* + +### User Story 1 - Understand entry points into working with LightSpeed (Priority: P1) + +A prospective client who already understands the service lifecycle (via Hero/Linked decisions/Service clusters/Service tiles) needs to know the concrete ways they can actually start engaging — the "Entry Points" section. + +**Why this priority**: This is the next unbuilt piece of the page and the primary remaining content gap — without it, a convinced visitor has no clear next step. + +**Independent Test**: Load the Services page, scroll to the Entry Points section, and confirm a visitor can identify the distinct ways to begin working with LightSpeed using only on-page content, matching the Figma design. + +**Acceptance Scenarios**: + +1. **Given** a visitor who has read the earlier Services page sections, **When** they reach the Entry Points section, **Then** they see clearly labeled entry-point options matching the Figma design (node 8044-164205). +2. **Given** an entry-point option is presented, **When** the visitor wants to act on it, **Then** an appropriate link/CTA is provided consistent with the site's existing link/CTA conventions. + +--- + +### User Story 2 - See proof of delivery scale (Priority: P1) + +A prospective client wants quick, credible evidence of LightSpeed's track record — the "Delivery by the Numbers" section. + +**Why this priority**: This is the second unbuilt content section and reinforces trust before the closing CTA; equally load-bearing as Entry Points for page completeness. + +**Independent Test**: Load the Services page, scroll to the Delivery by the Numbers section, and confirm the stated metrics/figures render correctly and match the Figma design. + +**Acceptance Scenarios**: + +1. **Given** a visitor scrolling the Services page, **When** they reach the Delivery by the Numbers section, **Then** they see the metrics/figures laid out as specified in Figma (node 8044-164253). + +--- + +### User Story 3 - Take action after reading the page (Priority: P1) + +A prospective client who has read the full page is ready to act — the closing CTA pattern gives them an unambiguous next step. + +**Why this priority**: Without a closing call to action, the page's persuasive work goes nowhere; this is the final piece completing the page. + +**Independent Test**: Load the Services page, scroll to the end, and confirm a clear, actionable CTA renders matching the Figma design. + +**Acceptance Scenarios**: + +1. **Given** a visitor who reaches the bottom of the Services page, **When** they view the closing section, **Then** they see a CTA pattern matching Figma (node 8044-164294) with a working link/action. + +--- + +### User Story 4 - Confirm the page matches the approved design (Priority: P2) + +A design reviewer or stakeholder checks the built page against the Figma design to confirm visual and content fidelity before sign-off. + +**Why this priority**: Design QA is an explicit acceptance criterion in LS-1598 and gates stakeholder approval. + +**Independent Test**: Compare each section of the live page side-by-side with its Figma frame and confirm no unresolved visual discrepancies. + +**Acceptance Scenarios**: + +1. **Given** the completed Services page, **When** compared section-by-section against Figma, **Then** spacing, typography, color tokens, and content match the design within normal implementation tolerance. +2. **Given** a discrepancy is found during QA, **Then** it is either fixed or explicitly logged as an accepted deviation before the page is considered done. + +--- + +### User Story 5 - View the page correctly on any device (Priority: P2) + +A visitor on a phone or tablet views the Services page and all sections, including the new Entry Points, Delivery by the Numbers, and CTA sections, remain legible and usable. + +**Why this priority**: Responsive check is an explicit task in LS-1598; a hub page that breaks on mobile fails its core purpose. + +**Independent Test**: Load the page at desktop, tablet, and mobile breakpoints and confirm no overlapping, clipped, or unreadable content in any section. + +**Acceptance Scenarios**: + +1. **Given** the Services page on a mobile viewport, **When** a visitor scrolls through all sections, **Then** all text, images, and interactive elements remain legible and reachable. +2. **Given** the Services page on a tablet viewport, **When** compared to desktop, **Then** layout adapts appropriately without broken grids or overflow. + +--- + +### User Story 6 - Find the page via search with meaningful context (Priority: P3) + +A search engine indexes the Services page and a searcher sees a meaningful title and description in results. + +**Why this priority**: SEO metadata is an explicit task in LS-1598 but is lower-impact than the on-page content and QA. + +**Independent Test**: Inspect the page's title tag and meta description and confirm they accurately summarize the Services page content. + +**Acceptance Scenarios**: + +1. **Given** the published Services page, **When** its metadata is inspected, **Then** it has a distinct, descriptive title and meta description reflecting the service model hub purpose. + +--- + +### Edge Cases + +- What happens if the Entry Points section has more or fewer options than the Figma design currently shows (e.g. a future entry point is added)? The section layout must accommodate at least the Figma-specified count without visual imbalance. +- How does the Delivery by the Numbers section handle a metric value that is significantly longer (e.g. more digits) than the others? Layout must not break or visually unbalance the row when value lengths vary. +- What happens if a visitor's viewport is between defined breakpoints (e.g. a small laptop)? Content must degrade gracefully rather than jump abruptly. + +## Requirements *(mandatory)* + +### Functional Requirements + +- **FR-001**: The Services page MUST include an "Entry Points" section matching the Figma design (node 8044-164205), presenting the distinct ways a visitor can begin working with LightSpeed. +- **FR-002**: The Services page MUST include a "Delivery by the Numbers" section matching the Figma design (node 8044-164253), presenting delivery-scale metrics/figures. +- **FR-003**: The Services page MUST include a closing CTA section matching the Figma design (node 8044-164294), giving the visitor a clear, actionable next step. +- **FR-004**: The Services page MUST reuse the already-merged Hero, "Linked decisions", "Service clusters", and "Service tiles" sections without duplicating their content or patterns. +- **FR-005**: Each new section (Entry Points, Delivery by the Numbers, CTA) MUST be implemented as a reusable `ls-theme` pattern consistent with existing Services page patterns (naming, semantic color tokens, spacing tokens). +- **FR-006**: The Services page MUST have a unique, descriptive SEO title and meta description reflecting its role as the LightSpeed service-model hub. +- **FR-007**: The Services page MUST render correctly (no overlapping, clipped, or unreadable content) at desktop, tablet, and mobile breakpoints, including the three new sections. +- **FR-008**: The completed page MUST be reviewed against the Figma design for the Services page, with discrepancies resolved or explicitly accepted before sign-off. + +### Key Entities + +- **Entry Point**: A distinct way a visitor can begin engaging with LightSpeed; has a label, description, and an associated link/action. +- **Delivery Metric**: A single stat/figure in the "Delivery by the Numbers" section; has a value and a label describing what it measures. +- **CTA (Call to Action)**: The page's closing action element; has a heading/description and a primary action link. +- **Service Cluster / Linked Decision / Service Tile** (existing, unchanged): Already-implemented navigational elements from the merged sections; not modified by this feature unless Figma QA finds a defect. + +## Success Criteria *(mandatory)* + +### Measurable Outcomes + +- **SC-001**: All three remaining sections (Entry Points, Delivery by the Numbers, CTA) render on the Services page matching their respective Figma frames. +- **SC-002**: The page shows zero unresolved visual discrepancies against Figma at design QA sign-off. +- **SC-003**: The page renders with no layout defects (overlap, clipping, overflow) across desktop, tablet, and mobile breakpoints. +- **SC-004**: The page has a unique title and meta description distinct from every other page on the site. + +## Assumptions + +- The Figma design referenced in LS-1598 (nodes 8044-164205, 8044-164253, 8044-164294) already reflects the final approved layout and content for the three remaining sections; no new design decisions are being made in this spec. +- Existing `ls-theme` semantic color/typography/spacing tokens are sufficient for the three new sections; no new design tokens are required unless Figma QA reveals a gap. +- The site's existing SEO metadata mechanism (theme/plugin-level) is reused; no new SEO infrastructure is introduced. diff --git a/specs/001-services-page/tasks.md b/specs/001-services-page/tasks.md new file mode 100644 index 0000000..d7d33d7 --- /dev/null +++ b/specs/001-services-page/tasks.md @@ -0,0 +1,327 @@ +--- +description: "Task list for Services Page (Remaining Sections & QA) — LS-1598" +--- + +# Tasks: Services Page (Remaining Sections & QA) + +**Input**: Design documents from `/specs/001-services-page/` + +**Prerequisites**: plan.md, spec.md, research.md, data-model.md, quickstart.md, +`.specify/memory/constitution.md` + +**Tests**: Not requested for this feature (manual QA per quickstart.md is the verification +method — no automated test suite exists in this theme repo for pattern/content work). + +**Organization**: Tasks are grouped by user story from spec.md, in priority order +(P1 → P1 → P1 → P2 → P2 → P3). + +## Format: `[ID] [P?] [Story] Description` + +- **[P]**: Can run in parallel (different files, no dependencies) +- **[Story]**: US1 = Entry Points, US2 = Delivery by the Numbers, US3 = closing CTA, + US4 = Figma design QA, US5 = responsive check, US6 = SEO metadata + +## Path Conventions + +Single WordPress block-theme project. Existing Services page patterns live in +`patterns/sections/services-*.php` (and `patterns/hero/services-hero.php`), styled via +`styles/sections/**` JSON, with `theme.json`/`styles/dark.json` for any new tokens. No +`src/`/`backend/`/`frontend/` split. + +--- + +## Phase 1: Setup + +**Purpose**: Confirm exact Figma content for the 3 remaining sections before building anything. + +- [ ] T001 Pull design context (code, tokens, screenshot) for Entry Points via the Figma MCP + tools, node `8044-164205`, from the file + `https://www.figma.com/design/OTqchq3sRBzUy6TICruzc3/LightSpeedWP-Design-System` +- [ ] T002 [P] Pull design context for Delivery by the Numbers, node `8044-164253`, same Figma + file +- [ ] T003 [P] Pull design context for the closing CTA, node `8044-164294`, same Figma file + +**Checkpoint**: Exact copy, layout, and card/metric counts for all 3 sections are confirmed +from Figma — no guessing content during implementation. + +--- + +## Phase 2: Foundational (Blocking Prerequisites) + +**Purpose**: Reuse-before-create and token/icon groundwork per constitution Principle II, +using this repo's own `.agents/skills/` toolchain — not ad hoc manual comparison — before any +new pattern file is written. + +- [ ] T004 Invoke the `pattern-extractor` skill's analysis phase for Entry Points (Figma + context from T001): propose what to reuse (starting from + `styles/sections/cards/card-link-row.json`, already used by + `patterns/sections/homepage-where-to-start.php` and + `patterns/sections/work-related-routes.php`) vs. create, per its approval-gated + reuse-or-create workflow — get explicit sign-off on the proposal before any file is + written +- [ ] T005 [P] Invoke `pattern-extractor`'s analysis phase for Delivery by the Numbers (Figma + context from T002): propose reuse of `styles/sections/cards/stat-segment.json` (already + used by `patterns/section-stats-grid.php`) vs. create, same approval-gated workflow +- [ ] T006 [P] Invoke `pattern-extractor`'s analysis phase for the closing CTA (Figma context + from T003), first reading the existing `patterns/section-cta.php` stub: propose fleshing + it out vs. creating a new pattern, per research.md's decision +- [ ] T007 Confirm every color/spacing/typography value in all 3 Figma frames resolves to an + existing `settings.custom.color`/typography/spacing token in `theme.json` — for any value + that doesn't, add the token to both `theme.json` and `styles/dark.json` with real + resolved values in each (never an identical light/dark duplicate); `pattern-extractor`'s + mandatory `theme-color-token-enforcer` load covers color specifically, so this task only + needs to separately confirm spacing/typography tokens +- [ ] T008 [P] Confirm the icon slugs needed for all 3 sections exist in + `wp-content/plugins/ls-plugin/assets/icons/lightspeed/` — every icon must be referenced + via `core/icon` (`{"icon":"lightspeed/{slug}", ...}`), never `outermost/icon-block`, per + research.md's icon decision (`pattern-extractor`'s Phosphor/Icon-Block mapping step + should already produce this, but confirm explicitly since this theme uses the newer + Core Icon block form, not the legacy Icon Block plugin `pattern-extractor` may default to) + +**Checkpoint**: Card-shell reuse decisions, token gaps, and icon slugs are confirmed and +approved — Phase 3+ +can proceed without inventing anything speculatively. + +--- + +## Phase 3: User Story 1 - Understand entry points into working with LightSpeed (Priority: P1) 🎯 MVP + +**Goal**: A convinced visitor sees clearly labeled entry-point options with working +links/CTAs, matching Figma node `8044-164205`. + +**Independent Test**: Load the Services page, scroll to the Entry Points section, and confirm +a visitor can identify the distinct ways to begin working with LightSpeed using only on-page +content, per quickstart.md's "Entry Points section present" validation. + +### Implementation for User Story 1 + +- [ ] T009 [US1] Execute `pattern-extractor`'s build phase for Entry Points, using T004's + approved reuse-or-create proposal — this creates + `patterns/sections/services-entry-points.php` with the standard pattern header + (Title/Slug/Categories/Block Types/Description/Keywords/Viewport Width/Inserter, + matching the format in `patterns/sections/services-service-tiles.php`) and, if new JSON + styling is written, `theme-color-token-enforcer` runs automatically per + `pattern-extractor`'s own mandatory chain — do not skip it +- [ ] T010 [US1] Write the Entry Point array (per data-model.md: label, description, + link/action for each) and render loop, reusing `card-link-row.json` if T004 confirmed + it fits, or the new shape-named style if T004 identified a genuine gap (consult + `wp-block-style-audit` before writing any new `css` field in that style's JSON) +- [ ] T011 [US1] Render each entry point's icon via `core/icon` with the `lightspeed/{slug}` + confirmed in T008 +- [ ] T012 [US1] Assemble the Entry Points section onto the Services page content, positioned + per the Figma page flow (after Service Tiles, before Delivery by the Numbers) +- [ ] T013 [US1] Register a `services-entry-points` bundle in `inc/animations.php` + (`ls_theme_get_effect_styles()` and, if it needs a render marker, + `ls_theme_get_bundle_render_markers()`) with a `condition` of `is_page( 'services' )` set + from the start — do not defer this per research.md's enqueue-condition decision +- [ ] T014 [US1] Add `add_editor_style( 'assets/css/services-entry-points.css' )` to + `functions.php` and wire the new SCSS file (if T004/T007 required one) into + `package.json`'s `build:css`/`build:css:dev`/`watch:css` scripts, matching the existing + `services-*` entries +- [ ] T015 [US1] Run `php -l`, `npm run patterns:escape`, `npm run security:scan`, + `npm run schema:validate` (if new JSON was added), and `composer run phpcs` against every + file touched in T009-T014 — fix any failure before proceeding; never use `validate_blocks` + +**Checkpoint**: Entry Points section renders on the Services page, independently testable via +quickstart.md. + +--- + +## Phase 4: User Story 2 - See proof of delivery scale (Priority: P1) + +**Goal**: A visitor sees credible delivery-scale metrics, matching Figma node `8044-164253`. + +**Independent Test**: Load the Services page, scroll to Delivery by the Numbers, and confirm +the stated metrics render correctly and match Figma, per quickstart.md. + +### Implementation for User Story 2 + +- [ ] T016 [US2] Execute `pattern-extractor`'s build phase for Delivery by the Numbers, using + T005's approved proposal — creates `patterns/sections/services-delivery-numbers.php` with + the standard pattern header; `theme-color-token-enforcer` runs automatically if new JSON + styling is written +- [ ] T017 [US2] Write the Delivery Metric array (per data-model.md: value, label for each) and + render loop, reusing `stat-segment.json` if T005 confirmed it fits, or the new + shape-named style if T005 identified a genuine gap (consult `wp-block-style-audit` + before writing any new `css` field) +- [ ] T018 [US2] Confirm the row layout does not visually unbalance when a value has + significantly more digits than its siblings (data-model.md validation rule) — test with + the actual longest value from the Figma frame, not a placeholder +- [ ] T019 [US2] Assemble the Delivery by the Numbers section onto the Services page content, + after Entry Points and before the closing CTA +- [ ] T020 [US2] Register a `services-delivery-numbers` bundle in `inc/animations.php` with a + `condition` of `is_page( 'services' )`, same as T013 +- [ ] T021 [US2] Add the editor style and build-script wiring, same pattern as T014 +- [ ] T022 [US2] Run the full validation suite from T015 against every file touched in + T016-T021 + +**Checkpoint**: Delivery by the Numbers section renders correctly, independently testable. + +--- + +## Phase 5: User Story 3 - Take action after reading the page (Priority: P1) + +**Goal**: A visitor who reaches the bottom of the page sees an unambiguous, working CTA, +matching Figma node `8044-164294`. + +**Independent Test**: Load the Services page, scroll to the end, and confirm a clear CTA +renders matching Figma, per quickstart.md. + +### Implementation for User Story 3 + +- [ ] T023 [US3] Execute `pattern-extractor`'s build phase using T006's approved finding: + either flesh out the existing `patterns/section-cta.php` stub with this section's + heading/description/primary link, or (only if T006 found it genuinely doesn't fit) + create a new, shape-named CTA pattern — reusing existing `core/buttons`/`core/button` + conventions per constitution Principle IV, not a hand-rolled link; + `theme-color-token-enforcer` runs automatically if new JSON styling is written +- [ ] T024 [US3] Render any CTA icon via `core/icon` with the `lightspeed/{slug}` confirmed in + T008, if the Figma frame includes one +- [ ] T025 [US3] Assemble the closing CTA section as the final section on the Services page + content +- [ ] T026 [US3] If T023 required new/changed styling, register/update the bundle's + `is_page( 'services' )` condition and editor-style/build-script wiring, same pattern as + T013-T014 +- [ ] T027 [US3] Run the full validation suite from T015 against every file touched in + T023-T026 + +**Checkpoint**: Closing CTA renders correctly — all 3 remaining sections are now built. This +completes the MVP (all P1 user stories). + +--- + +## Phase 6: User Story 4 - Confirm the page matches the approved design (Priority: P2) + +**Goal**: The completed page has zero unresolved visual discrepancies against Figma. + +**Independent Test**: Compare each section of the live page against its Figma frame per +quickstart.md's "Design QA against Figma" validation. + +### Implementation for User Story 4 + +- [ ] T028 [US4] Resync the local test page from the current pattern registry (per the + project's established WP-CLI resync workflow) and compare Entry Points, Delivery by the + Numbers, and the closing CTA against their Figma frames: spacing, typography, color + tokens, copy. Also explicitly confirm Hero, Linked Decisions, Service Clusters, and + Service Tiles each still appear exactly once on the assembled page (FR-004) — not just + visually similar to before, but not duplicated or forked by the new sections' assembly +- [ ] T029 [US4] Fix any discrepancy found in T028 directly in the relevant pattern/style file, + or explicitly log it as an accepted deviation in `specs/001-services-page/quickstart.md` + under a new "Accepted deviations" heading +- [ ] T030 [US4] Verify correctness via source/JSON inspection or a manual Site Editor check — + never the `validate_blocks` tool, per constitution Principle VI + +**Checkpoint**: Design QA sign-off achieved. + +--- + +## Phase 7: User Story 5 - View the page correctly on any device (Priority: P2) + +**Goal**: All 3 new sections remain legible and usable at desktop, tablet, and mobile +breakpoints. + +**Independent Test**: Load the page at desktop, tablet, and mobile widths and confirm no +overlapping, clipped, or unreadable content, per quickstart.md's "Responsive check" validation. + +### Implementation for User Story 5 + +- [ ] T031 [US5] Check Entry Points, Delivery by the Numbers, and the closing CTA at desktop, + tablet, and mobile breakpoints using the Browser pane's `resize_window` tool — pay + specific attention to vertical `blockGap` consistency when any multi-column row stacks + (the exact bug class already found twice in `services-service-clusters.php` and + `services-service-tiles.php`: a `blockGap` set only as `{"left": ...}` silently drops the + vertical gap to WordPress's default on stack — use a scalar `blockGap` value instead) +- [ ] T032 [US5] Fix any layout defect found in T031 by adjusting layout/spacing JSON in + `styles/sections/**` (theme-first) rather than adding hand-authored CSS, unless a genuine + JSON gap exists + +**Checkpoint**: Page confirmed responsive across all three breakpoints with no layout defects. + +--- + +## Phase 8: User Story 6 - Find the page via search with meaningful context (Priority: P3) + +**Goal**: The Services page has a unique, descriptive SEO title and meta description. + +**Independent Test**: Inspect the page's title tag and meta description per quickstart.md's +"SEO metadata" validation. + +### Implementation for User Story 6 + +- [ ] T033 [US6] Set a unique SEO title and meta description for the Services page using the + site's existing metadata mechanism, summarizing its role as the LightSpeed service-model + hub +- [ ] T034 [US6] Confirm the title/description from T033 are distinct from every other page's + metadata on the site + +**Checkpoint**: Page metadata complete and unique. + +--- + +## Phase 9: Polish & Cross-Cutting Concerns + +**Purpose**: Final housekeeping across all stories. + +- [ ] T035 [P] Add a dated `CHANGELOG.md` entry (Keep a Changelog format, one entry for this + PR) describing the 3 completed sections, per constitution Workflow & Process +- [ ] T036 Run `npm run lint:json` and `npm run build:css` (full, not just the 3 new files) to + confirm no unintended drift in other compiled output +- [ ] T037 Run through `specs/001-services-page/quickstart.md` end-to-end as a final full + validation pass before opening/updating the PR +- [ ] T038 Only check off PR test-plan items that were actually run and verified this session; + leave manual-QA-only items (e.g. live Figma comparison, physical device check) unchecked/ + pending if not literally performed, per constitution Workflow & Process +- [ ] T039 Write the commit message using heading + bullet structure (never prose paragraphs), + grouped under short section headings, per constitution Workflow & Process + +--- + +## Dependencies & Execution Order + +### Phase Dependencies + +- **Setup (Phase 1)**: No dependencies — start immediately +- **Foundational (Phase 2)**: Depends on Phase 1 (needs the Figma design context to compare + against existing card shells/tokens/icons) +- **User Story 1 (Phase 3)**: Depends on Phase 2 — this is part of the MVP +- **User Story 2 (Phase 4)**: Depends on Phase 2; independent of Phase 3's files — can run in + parallel with Phase 3 +- **User Story 3 (Phase 5)**: Depends on Phase 2; independent of Phases 3-4's files — can run + in parallel with them +- **User Story 4 (Phase 6)**: Depends on Phases 3-5 being complete (nothing to QA until + sections exist) +- **User Story 5 (Phase 7)**: Depends on Phases 3-5 being complete; can run in parallel with + Phase 6 (different concern — visual fidelity vs. responsive layout) +- **User Story 6 (Phase 8)**: Independent of Phases 3-7 content-wise; can start any time after + Phase 1, sequenced last here since it's P3 and lowest impact +- **Polish (Phase 9)**: Depends on all prior phases being complete + +### Parallel Opportunities + +- T002, T003 can run in parallel with T001 (independent Figma pulls) +- T005, T006, T008 can run in parallel with T004 (different files/checks) +- Phase 3 (US1), Phase 4 (US2), and Phase 5 (US3) can be worked in parallel once Phase 2 is + complete — three different pattern files, no shared files +- Phase 6 (US4) and Phase 7 (US5) can be worked in parallel once Phases 3-5 are complete +- T035 (changelog) can be written in parallel with T036-T039 (verification tasks) + +--- + +## Implementation Strategy + +### MVP First (User Stories 1-3) + +1. Complete Phase 1: Setup (pull Figma context for all 3 sections) +2. Complete Phase 2: Foundational (confirm card-shell reuse, tokens, icons) +3. Complete Phases 3-5: Build Entry Points, Delivery by the Numbers, and the closing CTA + (parallelizable) +4. **STOP and VALIDATE**: Run quickstart.md's section-presence checks independently +5. This is a demoable MVP — the page's remaining content is complete + +### Incremental Delivery + +1. Setup + Foundational → confirmed Figma content and reuse/token/icon decisions +2. User Stories 1-3 (parallel) → all 3 remaining sections built (MVP) +3. User Story 4 + User Story 5 (parallel) → design QA and responsive check pass +4. User Story 6 → SEO metadata finalized +5. Polish → changelog, lint, full quickstart pass, honest PR test-plan, commit message diff --git a/src/scss/structural/services-service-tiles.scss b/src/scss/structural/services-service-tiles.scss new file mode 100644 index 0000000..15aa4b6 --- /dev/null +++ b/src/scss/structural/services-service-tiles.scss @@ -0,0 +1,27 @@ +/********** Services - Service Tiles (Services page only) **********/ + +/* + * Services Service Tiles (LS-1598, single consumer + * patterns/sections/services-service-tiles.php). Rest-state look and hover custom-property + * recipe live in styles/sections/cards/card-service-tile.json. The hover border-color swap + * itself stays here, same split as Card - Category/work-archive-sections.scss. + */ +.is-style-card-service-tile { + // JSON limitation: block-level :hover/:focus-within has no theme.json pseudo-state key for + // an arbitrary is-style variant — see AGENTS.md Theme-First Approach. + transition: border-color var(--wp--custom--animation--duration--base) var(--wp--custom--animation--easing--standard); + + &:hover, + &:focus-within { + // JSON limitation: the border-color custom property is only swapped, not declared, at + // rest, so no inline style attribute outranks this — !important isn't needed here (unlike + // Card - Category, which does need it for its box-shadow/transform swaps). + border-color: var(--ls-card-service-tile-border-active); + } +} + +@media (prefers-reduced-motion: reduce) { + .is-style-card-service-tile { + transition: none; + } +} diff --git a/styles/sections/cards/card-service-tile.json b/styles/sections/cards/card-service-tile.json new file mode 100644 index 0000000..cde3d3f --- /dev/null +++ b/styles/sections/cards/card-service-tile.json @@ -0,0 +1,32 @@ +{ + "$schema": "https://schemas.wp.org/wp/6.9/theme.json", + "version": 3, + "title": "Card - Service Tile", + "slug": "card-service-tile", + "blockTypes": [ + "core/group" + ], + "description": "Services page bento-grid tile: icon well + index number header, H3 heading, an accent-coloured kicker line, supporting copy, and a bottom-anchored arrow link — the whole card is a single stretched link, unlike Card - Cluster's per-tag links. Modelled on Card - Category's shell (position/overflow/content-flex/stretched-link/focus-visible + the hover border-color swap recipe) but flatter: no default shadow, tighter padding, and an index-number slot Card - Category doesn't have. The hover border-color/box-shadow swap itself stays in src/scss/structural/services-service-tiles.scss, same split as Card - Category/work-archive-sections.scss — a bare &:hover in this css field is silently dropped, same class of bug as @media/@supports stripping.", + "styles": { + "color": { + "background": "var:custom|color|surface|card", + "text": "var:custom|color|text|default" + }, + "border": { + "color": "var:custom|color|border|card", + "radius": "var:preset|border-radius|300", + "style": "solid", + "width": "1px" + }, + "spacing": { + "blockGap": "var:preset|spacing|20", + "padding": { + "top": "var:preset|spacing|30", + "right": "var:preset|spacing|30", + "bottom": "var:preset|spacing|30", + "left": "var:preset|spacing|30" + } + }, + "css": "position: relative;\noverflow: hidden;\n--ls-card-service-tile-border-active: var(--wp--custom--color--link--accent);\n\n& .ls-card-service-tile__content {\n\tflex: 1 1 auto;\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: var(--wp--preset--spacing--10);\n}\n\n& .ls-card-service-tile__index {\n\tposition: absolute;\n\ttop: var(--wp--preset--spacing--20);\n\tright: var(--wp--preset--spacing--20);\n}\n\n& .ls-card-service-tile__link::before {\n\tcontent: \"\";\n\tposition: absolute;\n\tinset: 0;\n}\n\n& .ls-card-service-tile__link:focus-visible {\n\toutline: 2px solid var(--wp--custom--color--focus--ring);\n\toutline-offset: 2px;\n}" + } +}