feat(marketing): phase 4 — the marketing pages
All checks were successful
PR checks / checks (pull_request) Successful in 9m9s
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:
194
src/styles/diagram.css
Normal file
194
src/styles/diagram.css
Normal file
@@ -0,0 +1,194 @@
|
||||
/* ============================================================================
|
||||
The diagram vocabulary
|
||||
============================================================================
|
||||
§11 makes hand-drawn SVG the site's motif, "used where it explains something".
|
||||
Phase 3 drew the first one on the homepage; phase 4 drew three more on
|
||||
`/architecture/`, at which point the same fifteen rules existed in four files.
|
||||
|
||||
Two things live here and nothing else does:
|
||||
|
||||
1. The SVG vocabulary — what a node, a spine, an arrow and the boundary look
|
||||
like. Shared by name, so a diagram is markup and the drawing is one
|
||||
decision. `DataPath.astro` reads these too; it keeps its own layout,
|
||||
because its right-hand column is a numbered walk rather than notes.
|
||||
|
||||
2. The `.diagram` layout — figure beside prose on a wide screen, figure
|
||||
above prose on a narrow one.
|
||||
|
||||
Every colour is a class rather than a presentation attribute, and that is not
|
||||
a style preference: `var()` is only substituted in style declarations, so
|
||||
`fill="var(--line)"` on an element parses and draws nothing at all. It is also
|
||||
what keeps `checkTokens.mjs` green, since no literal ever reaches the markup.
|
||||
|
||||
The two rules every diagram here follows, learned in phase 3:
|
||||
|
||||
- An inline SVG cannot reflow. A tall, ~380px-wide viewBox with only node
|
||||
titles inside it is legible on a phone AND useful at 1440px; a wide
|
||||
horizontal diagram is neither.
|
||||
- The picture is `aria-hidden` because the prose beside it says the same
|
||||
thing better. The consequence is a rule: a diagram must never carry a fact
|
||||
the prose does not.
|
||||
-------------------------------------------------------------------------- */
|
||||
|
||||
/* ---- Layout ------------------------------------------------------------- */
|
||||
|
||||
.diagram__head h2 {
|
||||
margin: 0 0 0.75rem;
|
||||
font-size: clamp(1.6rem, 3.2vw, 2.1rem);
|
||||
}
|
||||
|
||||
.diagram__head .prose {
|
||||
margin: 0;
|
||||
color: var(--muted);
|
||||
}
|
||||
|
||||
.diagram__body {
|
||||
display: grid;
|
||||
gap: clamp(1.75rem, 4vw, 3rem);
|
||||
margin-top: 2.5rem;
|
||||
grid-template-columns: minmax(0, 380px) minmax(0, 1fr);
|
||||
align-items: start;
|
||||
}
|
||||
|
||||
.diagram__figure {
|
||||
position: sticky;
|
||||
top: calc(var(--header-h) + 1.5rem);
|
||||
}
|
||||
|
||||
.diagram__caption {
|
||||
margin: 1rem 0 0;
|
||||
max-width: 380px;
|
||||
color: var(--dim);
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
|
||||
.diagram__notes section + section {
|
||||
margin-top: 1.5rem;
|
||||
}
|
||||
|
||||
.diagram__notes h3 {
|
||||
margin: 0 0 0.4rem;
|
||||
color: var(--gold);
|
||||
font-size: 1.06rem;
|
||||
}
|
||||
|
||||
.diagram__notes p {
|
||||
margin: 0;
|
||||
max-width: var(--measure);
|
||||
color: var(--muted);
|
||||
}
|
||||
|
||||
@media (max-width: 900px) {
|
||||
.diagram__body {
|
||||
grid-template-columns: minmax(0, 1fr);
|
||||
}
|
||||
|
||||
/* Sticky is a wide-screen affordance only. Once the figure sits above the
|
||||
prose rather than beside it, pinning it would cover the thing it explains. */
|
||||
.diagram__figure {
|
||||
position: static;
|
||||
justify-self: center;
|
||||
}
|
||||
}
|
||||
|
||||
/* ---- The drawing -------------------------------------------------------- */
|
||||
|
||||
.flow {
|
||||
display: block;
|
||||
width: 100%;
|
||||
max-width: 380px;
|
||||
}
|
||||
|
||||
/* An outer grouping: a machine, a process boundary, a side of a contract. Sits
|
||||
under the nodes it contains, so it reads as the thing they are inside. */
|
||||
.host {
|
||||
fill: var(--panel-flat);
|
||||
stroke: var(--line-soft);
|
||||
stroke-width: 1;
|
||||
}
|
||||
|
||||
.host-title {
|
||||
fill: var(--head);
|
||||
font-family: var(--sans);
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.host-sub {
|
||||
fill: var(--dim);
|
||||
font-family: var(--sans);
|
||||
font-size: 11.5px;
|
||||
}
|
||||
|
||||
.node {
|
||||
fill: var(--panel-b);
|
||||
stroke: var(--line);
|
||||
stroke-width: 1;
|
||||
}
|
||||
|
||||
/* The one node that is the reader's own site. Gold edge, because gold is
|
||||
emphasis everywhere else on the site too. */
|
||||
.node--self {
|
||||
fill: var(--panel-a);
|
||||
stroke: var(--gold-deep);
|
||||
}
|
||||
|
||||
.node-title {
|
||||
fill: var(--head);
|
||||
font-family: var(--sans);
|
||||
font-size: 15px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.node-sub {
|
||||
fill: var(--dim);
|
||||
font-family: var(--sans);
|
||||
font-size: 11.5px;
|
||||
}
|
||||
|
||||
.spine {
|
||||
fill: none;
|
||||
stroke: var(--gold-deep);
|
||||
stroke-width: 2;
|
||||
}
|
||||
|
||||
/* Cyan is the live signal everywhere on this site — the same colour the portal
|
||||
in the emblem is, and the same one the homepage draws the event feed in. A
|
||||
spine in this colour means data actually moving, not a relationship. */
|
||||
.spine--live {
|
||||
stroke: var(--portal);
|
||||
filter: drop-shadow(0 0 6px var(--portal-deep));
|
||||
}
|
||||
|
||||
.arrow {
|
||||
fill: var(--gold-deep);
|
||||
stroke: none;
|
||||
}
|
||||
|
||||
.arrow--live {
|
||||
fill: var(--portal);
|
||||
}
|
||||
|
||||
.boundary {
|
||||
fill: none;
|
||||
stroke: var(--line);
|
||||
stroke-width: 1;
|
||||
stroke-dasharray: 4 5;
|
||||
}
|
||||
|
||||
.boundary-label {
|
||||
fill: var(--dim);
|
||||
font-family: var(--sans);
|
||||
font-size: 11px;
|
||||
letter-spacing: 0.09em;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
/* The emblem's concentric rings, used as a ground behind the one place a
|
||||
diagram's argument actually happens. */
|
||||
.rings {
|
||||
fill: none;
|
||||
stroke: var(--gold-deep);
|
||||
stroke-width: 1;
|
||||
opacity: 0.16;
|
||||
}
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user