feat(marketing): phase 4 — the marketing pages
All checks were successful
PR checks / checks (pull_request) Successful in 9m9s

PLAN.md §13 phase 4: /features/, /architecture/, /modules/, /integrations/, and
/community/ — plus the two scope items the phase table never assigned to anyone.

Six decisions taken by the org lead before coding, recorded in PLAN.md §10 as
D20-D25:

- D20 /features/ is the homepage's list with a `detail` line, not a second list.
  One data file, two renderings, so they cannot disagree about what exists.
- D21 /architecture/ draws reasons, not reference: three new inline SVGs, one per
  boundary. No endpoint tables, no config keys — those are phase 8's and stay
  canonical in docs/.
- D22 The deliberate absences of §2 become one tagged data file, rendered on the
  three pages that promise them.
- D23 Phase 4 absorbs /community/ (specified in §10 and §14 N3, linked from the
  header since phase 1, built by no phase) and checkLinks.mjs.
- D24 `needsModule`: writing the Teams detail exposed a false claim phase 3
  shipped. Teams are module-sourced only — teams.module_id is NOT NULL, there is
  no create route, sync is gated on providerModuleId() — so the Community group
  no longer says a bare core does all of it.
- D25 The per-capability demo affordance brand.json had promised since phase 2 is
  a deep link, filled at boot from data-demo-path.

checkLinks.mjs reads the built HTML rather than src/, because half these links
are assembled from data files and template literals. Its PLANNED_ROUTES list is
checked in both directions, so it cannot rot into a permanent exemption.

applyBrand.mjs gained a pass that recomputes deep links from their immutable
path, making it idempotent and reversible; checkBrand.mjs lifts that pattern out
and runs it against the stock markup so the two cannot drift. Both proved
against a real mount, in both directions.

Fixes a cascade bug the checks could not see: [data-demo-url=''] and a scoped
component class are both specificity 0,1,0, so .demo-link's `display` beat the
hide rule and twelve links to a nonexistent demo rendered, each resolving to the
current page. The rule is now !important.

The four diagrams' shared SVG vocabulary moved to src/styles/diagram.css.

Verified from a clean checkout: npm ci, all five checks, astro check (0 errors),
production build, and a live browser pass at desktop and 390px.

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2026-08-24 01:53:11 -05:00
parent d9d7a8d47f
commit 2d19ee4220
21 changed files with 3332 additions and 119 deletions

View File

@@ -8,6 +8,12 @@
@import '@fontsource-variable/cinzel';
@import '@fontsource-variable/inter';
/* The SVG diagram vocabulary and the figure-beside-prose layout, shared by the
homepage's data path and `/architecture/`'s three. Its own file because it is
a self-contained language rather than part of the shell — see its header for
the two rules every diagram on this site follows. */
@import './diagram.css';
*,
*::before,
*::after {
@@ -437,8 +443,25 @@ svg {
Written here, before phase 3 writes that markup, because the rule and the
rewrite have to agree and they live in different files. */
/* `!important`, and it is earning its keep rather than papering over something.
This selector is specificity 0,1,0. So is a class — including the scoped class an
Astro component puts on the very same element — and a component's styles are emitted
AFTER this file, so any component that gives one of these elements a `display` wins on
source order alone. Phase 4 did exactly that: `/features/`'s `.demo-link` set
`display: inline-flex` for its arrow, and twelve links to a demo that does not exist
appeared on the page, each one pointing at `href=""` — which a browser resolves to the
page it is already on.
Nothing caught it. checkBrand.mjs verifies the ATTRIBUTES, and they were perfect; the
defect was three files away in the cascade. It was found by looking at the rendered
page, which is not a mechanism.
So the rule is stated as one: while there is no demo, these elements do not render, and
no component style may overrule that by accident. A component that genuinely needs to
lay one of these out sets every property except `display`. */
[data-demo-url=''] {
display: none;
display: none !important;
}
/* Phase 3 writes that markup as `class="btn demo-cta"`, so the slot is a button