@getvitops/core 8.2.0-alpha.0 → 8.3.0-alpha.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +194 -20
- package/css/index.css +7 -0
- package/css/patterns/banner.css +20 -0
- package/css/patterns/callout.css +39 -0
- package/css/patterns/copy.css +5 -0
- package/css/patterns/embed.css +39 -0
- package/css/patterns/forms.css +19 -0
- package/css/patterns/logos.css +45 -0
- package/css/patterns/marquee.css +16 -0
- package/css/patterns/section.css +53 -0
- package/css/patterns/sitenav.css +262 -0
- package/css/patterns/splitter.css +11 -0
- package/css/patterns/stats.css +61 -0
- package/css/patterns/steps.css +98 -0
- package/css/patterns/table.css +10 -0
- package/css/patterns/tone.css +38 -0
- package/dist/consent.js +1 -1
- package/dist/elements.js +5 -5
- package/dist/polyfills/adoptedStyleSheets.js +1 -1
- package/dist/polyfills/css-anchor-positioning-fn.js +2 -2
- package/dist/polyfills/popover.js +2 -2
- package/package.json +3 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,12 +1,194 @@
|
|
|
1
1
|
# @getvitops/core
|
|
2
2
|
|
|
3
|
+
## 8.3.0-alpha.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 93d2067: New framework patterns and an `Entries` component for the EmDash block library.
|
|
8
|
+
|
|
9
|
+
**CSS, all formats** (css, bricks and inlined into tailwind's `components` layer; no existing class changes): `tone` (a shared tint/solid colour convention driven by `--tone-*` custom properties, plus `.accent` for a run inside a heading), `section` (head and heading-left `--split` layout), `steps` (numbered `<ol>` with a connector line), `stats` (figures and `--tiles`), `callout` (a call-to-action band), `logos` (a still partner grid) and `embed` (a consent-gated iframe frame). All are structural partials, classified `vitops.components`.
|
|
10
|
+
|
|
11
|
+
**`@getvitops/astro`:** new `Entries.astro` (`@getvitops/astro/components/Entries.astro`) emits `<wc-entries>` itself with the semantic heading + `<dl>` fallback.
|
|
12
|
+
|
|
13
|
+
**`@getvitops/generator`:** `TierEntry` gains `emdashExempt` (a reason a shipped web component has no EmDash block). `tiers.test.ts` now fails if a shipped `wc-*` has neither `emdash` nor `emdashExempt`, and the components doc and the docs site list the exemptions.
|
|
14
|
+
|
|
15
|
+
No other consumer-facing change in `@getvitops/core`, `@getvitops/utils`, `@getvitops/cli` or `@getvitops/vite` beyond the version bump.
|
|
16
|
+
|
|
17
|
+
- 161c7f3: A contact form that works end to end: `vitops setup turnstile`, `<wc-turnstile>`, the `vitops.form` EmDash block, and the route the emdash template ships to receive it.
|
|
18
|
+
|
|
19
|
+
**CSS, all formats** (no existing class changes): `forms.css` gains `.form-notice--target:not(:target)` (a message revealed only when the URL fragment names it, which is how a prerendered page shows a no-JS form's result) and `:where(wc-turnstile) { display: block }`.
|
|
20
|
+
|
|
21
|
+
**`@getvitops/core`:** new `<wc-turnstile sitekey="…">` in `elements.js`. It loads Cloudflare Turnstile on the first interaction with its form (no request to `challenges.cloudflare.com` for a visitor who only reads the page), holds an early submit until the token arrives, and leaves its slotted no-JS explanation in place until JS takes over. Turnstile is category `necessary`, so it is not behind the consent banner.
|
|
22
|
+
|
|
23
|
+
**`@getvitops/utils` / `@getvitops/cli`:** new `@getvitops/utils/turnstile` and `vitops setup turnstile [--dry] [--check] [--no-write] [--no-secret]`. It plans from the site's hostnames, creates a managed widget unless one already covers them (an existing widget is adopted, never edited, rotated or deleted), writes the public key to `site.security.turnstile.siteKey`, and pipes the one-time secret to `wrangler secret put VITOPS_TURNSTILE_SECRET` without writing it to any file. Needs `CLOUDFLARE_API_TOKEN` with **Account > Turnstile > Edit** and `CLOUDFLARE_ACCOUNT_ID`.
|
|
24
|
+
|
|
25
|
+
**`@getvitops/astro`:** `virtual:getvitops/head` gains `turnstileSiteKey` (read from `site.security.turnstile.siteKey`) so blocks can render the widget. No other change.
|
|
26
|
+
|
|
27
|
+
**`@getvitops/generator`:** `TIERS` gains a `turnstile` entry (wc-turnstile, claimed by `vitops.form`); `wc-multi-field`'s `emdashExempt` reason is reworded now the form block exists.
|
|
28
|
+
|
|
29
|
+
**`@getvitops/emdash`:** new `vitops.form` block: a field repeater (text, email, phone, long text, choose one, tick box), required flags, help text, success/error messages, optional Turnstile. It renders a semantic `<form method="post">` that works with no JavaScript, with a honeypot named per `createConversionRoute`'s defaults. A field named `phone` submits as `tel`, because the route reserves `phone` and `event`.
|
|
30
|
+
|
|
31
|
+
**`@getvitops/create`:** the emdash template ships `src/pages/api/track.ts` (`prerender = false`, `createConversionRoute()`), a seeded contact form, and `challenges.cloudflare.com` in both templates' Report-Only CSP (`script-src`, `frame-src`). The route refuses a post with nowhere to deliver and fails closed when a site key exists without `VITOPS_TURNSTILE_SECRET`. SETUP.html gains the Turnstile step. Existing sites: copy the route and the CSP change, then run `vitops setup turnstile`.
|
|
32
|
+
|
|
33
|
+
- 175d16e: **Added: `vitops.reviews` and `vitops.nav` EmDash blocks.** Non-breaking.
|
|
34
|
+
|
|
35
|
+
- **`vitops.reviews`** renders Google reviews from the committed `.vitops/reviews.json` cache (`vitops reviews`) as a grid, carousel or marquee of `<ReviewCard>`. Fields: location key, layout, columns, limit, lowest rating, order, featured ids, reviewer photos (off by default), tone/colour. Which reviews show is `selectReviews()`, the one shared decision. Review JSON-LD is not offered (Google treats self-marked-up reviews as ineligible for rich results). A new review reaches the page after `vitops reviews` is re-run, committed and the site rebuilt, not when an editor saves; with no cache file the block renders nothing (a note in `astro dev`).
|
|
36
|
+
- **`vitops.nav`** renders a named EmDash menu inside page content (site map, section links, footer-style columns), never the header. Fields: menu (picked from the site's menus via a new `menus/list` plugin route, with `footer-sitemap`/`footer-legal`/`primary` as the static fallback), accessible name, visible heading and level, layout `list | inline | columns | sitenav`, depth 1-3, tone/colour. It is a `<nav>` with a name that is never `Primary`, `Site map` or `Legal` (the layout's landmarks), `aria-current="page"`, no runtime JS; a missing or empty menu renders nothing.
|
|
37
|
+
- `@getvitops/astro`: **`virtual:getvitops/reviews`**, served by `vitops()`, is `.vitops/reviews.json` as a build-time import (an empty cache when absent or unreadable), because a Worker cannot read the file. `<ReviewCard>` now renders the review's source link (`.review__source`, `rel="nofollow noopener noreferrer"`), which the pattern already styled. **Fix:** `src/json-ld.ts` was missing from the published `files`, so any consumer importing `<ReviewCard>` (which composes `schemas/Review.astro`) failed to build with an unresolved import. `@getvitops/emdash` now needs this version of `@getvitops/astro` for the `vitops.reviews` block.
|
|
38
|
+
- `@getvitops/utils`: new Worker-safe subpath **`@getvitops/utils/reviews/select`** (`selectReviews` and the review types, no `node:fs`). `selectReviews` now takes only the display fields (`featured`, `minRating`, `limit`, `sort`), so a caller with no configured `locationId` can use it; existing calls are unchanged.
|
|
39
|
+
- `@getvitops/core`: new `.navlist` pattern in `patterns/sitenav.css` (`.navlist`, `__item`, `__link`, `__label`, `__sub`, `--inline`, `--columns`). **All formats** (css, bricks, tailwind). No existing class changes.
|
|
40
|
+
- `@getvitops/generator`: `TIERS.review` lists `vitops.reviews` and `TIERS.sitenav` lists `vitops.nav` (and `.navlist`), so `vitops docs components` and the docsite show them.
|
|
41
|
+
|
|
42
|
+
- 55061e2: **Added: `<SiteNav>` and `<SiteFooter>` for Astro/EmDash sites.** Both are tier-1 markup over the existing `sitenav` pattern (no runtime JS: `<details>` accordions + Popover API drawer). Non-breaking.
|
|
43
|
+
|
|
44
|
+
- `<SiteNav {...await loadNavMenus(getMenu)} />` renders EmDash menu `primary` (the default: depth 3, dropdowns from the `md` container width, drawer below). If a `primary-mobile` menu exists it replaces the nav below `md` (depth 1); if `primary-mega` exists it replaces it from `xl` (mega panels). Adding the menu in the admin is the whole change. Only one tier is in the accessibility tree at a time. An item's `titleAttr` is its description (EmDash has no description field yet).
|
|
45
|
+
- `<SiteFooter sitemap legal name>` renders `footer-sitemap` + `footer-legal` + a copyright line.
|
|
46
|
+
- `@getvitops/astro/components/sitenav.ts` exports the pure helpers (`loadNavMenus`, `selectTiers`, ...).
|
|
47
|
+
- CSS (`patterns/sitenav.css`, **all formats**): tier visibility classes (`.sitenav-tier--below-*`/`--from-*`), `.sitenav--mega`, `.sitenav__desc`, `.sitefooter*`, `aria-current` styling, reduced-motion and forced-colors handling. Existing `.sitenav` rules are unchanged, so the Bricks element is unaffected.
|
|
48
|
+
- `@getvitops/create` (emdash template): the layout now renders header and footer through these components, with the colour-scheme toggle in the header. It reads menus `primary`, `footer-sitemap` and `footer-legal`; a template whose seed still has the old `footer` menu renders no footer links until the seed is updated.
|
|
49
|
+
- `@getvitops/generator`: `TIERS.sitenav` now lists the two Astro components.
|
|
50
|
+
|
|
51
|
+
## 8.2.0
|
|
52
|
+
|
|
53
|
+
### Minor Changes
|
|
54
|
+
|
|
55
|
+
- c3c1bcb: **New: the design-system editor, mounted inside the EmDash admin.** `vitopsEmdash({
|
|
56
|
+
designSystemEditor: { config } })` (`config` is your `site.json`, e.g. `import config from
|
|
57
|
+
'./site.json' with { type: 'json' }`) adds a `/design-system` admin page mounting
|
|
58
|
+
`<wc-theme-editor>` — the same live token editor `@getvitops/vite`'s dev server has always
|
|
59
|
+
offered, now usable by the client directly, no dev server required. An edit is visible
|
|
60
|
+
site-wide immediately (a runtime `:root` override, served via a new plugin API route and
|
|
61
|
+
`page:fragments`) and — once `site.deployment.repo`/`branch` and a `VITOPS_GITHUB_TOKEN`
|
|
62
|
+
secret are set (see the scaffold's `SETUP.html`) — auto-commits to your `dev` branch, so it
|
|
63
|
+
reaches the git-tracked `site.json` through your repo's normal review path rather than
|
|
64
|
+
becoming a second, unreviewed source of truth. Until that's configured, the live half still
|
|
65
|
+
works; only the auto-commit half is skipped (and says so in the admin page). The save route
|
|
66
|
+
runs the exact same accessibility (APCA contrast) gate `generate()` enforces at build time,
|
|
67
|
+
synchronously, before persisting — nothing non-compliant ever goes live even momentarily.
|
|
68
|
+
|
|
69
|
+
Requires `@astrojs/react` + `react` + `react-dom` (already scaffold dependencies, for
|
|
70
|
+
EmDash's own admin) and `@getvitops/core` (added as a new scaffold dependency this release).
|
|
71
|
+
Declares two new plugin capabilities when enabled: `network:request` (for the GitHub
|
|
72
|
+
auto-commit) and `hooks.page-fragments:register` (for the live override) — reviewed once at
|
|
73
|
+
plugin-install time.
|
|
74
|
+
|
|
75
|
+
**Breaking for `@getvitops/emdash`**: `vitopsEmdash()`'s `hooks.page-fragments:register`
|
|
76
|
+
capability is now also granted whenever `designSystemEditor` is set, independent of the
|
|
77
|
+
existing `scripts: 'fragments'` option (which governs something unrelated — web-component
|
|
78
|
+
script delivery). No change if you don't use `designSystemEditor`.
|
|
79
|
+
|
|
80
|
+
Also new, supporting the above:
|
|
81
|
+
|
|
82
|
+
- `@getvitops/generator`: `validateColorContract()` and `buildReverseIndex()` (both
|
|
83
|
+
`/tokens` subpath, Worker-safe) — the monotonicity/contrast gate and the
|
|
84
|
+
`design-manifest.json` reverse-index, each extracted from `generate()`'s `build()` into
|
|
85
|
+
standalone, non-throwing functions. `patchToOverrideCss()` turns a design-system patch
|
|
86
|
+
into the `:root` override `<style>` block. `designSystemPath`/`getAt`/`setAt`/
|
|
87
|
+
`mergePatch`/`formatConfig` moved from `@getvitops/vite` into `@getvitops/generator/config`
|
|
88
|
+
(re-exported from `@getvitops/vite` unchanged) — one "patch a config file's design-system
|
|
89
|
+
subtree" implementation shared by the dev endpoint and the new plugin route. New
|
|
90
|
+
`site.deployment.repo`/`branch` config fields.
|
|
91
|
+
- `@getvitops/utils`: new `/git` subpath — `commitFile()`, a Worker-safe (fetch-only)
|
|
92
|
+
wrapper over GitHub's Contents API.
|
|
93
|
+
- `@getvitops/core`: `<wc-theme-editor>` gains a `save-endpoint` attribute (default
|
|
94
|
+
unchanged — `/__vitops/design-system`) so the same element can target the new plugin
|
|
95
|
+
route instead of the dev-only Vite endpoint.
|
|
96
|
+
|
|
97
|
+
- 88b465a: **Accessibility and layout fixes for the web components.**
|
|
98
|
+
|
|
99
|
+
- **`<wc-entries>`: the column selector is no longer `aria-hidden`.** Its buttons are real and
|
|
100
|
+
focusable, so hiding the `<nav>` left a keyboard user on a control that assistive tech never
|
|
101
|
+
announced. It is now exposed as a `Column selector` navigation. The generated table stays
|
|
102
|
+
`aria-hidden` (it duplicates the `<dl>`, which is what assistive tech reads) and contains nothing
|
|
103
|
+
focusable.
|
|
104
|
+
- **`prefers-reduced-motion` is honoured by programmatic scrolling.** `<wc-carousel>` (prev/next and
|
|
105
|
+
marker clicks) and `<wc-tree>` (deep-link scroll) always passed `behavior: 'smooth'` to
|
|
106
|
+
`scrollIntoView`, which overrides the stylesheet's `scroll-behavior` and so ignored the setting.
|
|
107
|
+
They now scroll instantly when it is set, reading the preference at call time.
|
|
108
|
+
- **Pre-upgrade `display` for six more elements** — `wc-entries`, `wc-image-compare`,
|
|
109
|
+
`wc-split-panel`, `wc-multi-field`, `wc-dismissable` (all `block`), `wc-copy` (`inline-flex`) and
|
|
110
|
+
`wc-marquee` (`block`, as a fallback under the `.marquee` class). Each is declared in its pattern's
|
|
111
|
+
partial inside `:where()` (zero specificity, so any consumer rule wins) and matches the element's
|
|
112
|
+
upgraded `:host`, so registering no longer shifts layout. `wc-split-panel`, which declared no
|
|
113
|
+
display at all, is now a block after upgrade too — a visible change if a layout relied on it being
|
|
114
|
+
inline. `wc-counter` is deliberately left inline.
|
|
115
|
+
- **A dismiss button no longer shows before it can work.**
|
|
116
|
+
`wc-dismissable:not(:defined)` hides `.banner__close`, `.notification__close` and `[data-dismiss]`
|
|
117
|
+
buttons, so with no JS a banner has no dead ✕. (`:not(:defined)` is also true until `elements.js`
|
|
118
|
+
has run, so the button appears on upgrade.)
|
|
119
|
+
|
|
120
|
+
Formats reached for the CSS changes: all formats (the partials are components-layer patterns, inlined
|
|
121
|
+
by the tailwind format as well). Other packages in the `fixed` group: No consumer-facing change in this
|
|
122
|
+
package.
|
|
123
|
+
|
|
124
|
+
### Patch Changes
|
|
125
|
+
|
|
126
|
+
- 734ab60: EmDash gallery and carousel blocks now derive their element id from the block's `_key`, so two blocks with the same label on one page no longer open each other's dialog. `<wc-entries>` column navigation now honours `prefers-reduced-motion`.
|
|
127
|
+
- 0574388: Bump `emdash`/`@emdash-cms/cloudflare` peers to `>=1.0.0 <2` and `vite-plus` to `^1.0.0`, following
|
|
128
|
+
both projects' GA releases.
|
|
129
|
+
|
|
130
|
+
**Breaking for `@getvitops/emdash` consumers on EmDash 0.x**: the peer range no longer admits
|
|
131
|
+
`emdash@0.31.x`/`@emdash-cms/cloudflare@0.31.x`. Upgrade `emdash` and `@emdash-cms/cloudflare` to
|
|
132
|
+
`^1.0.1` first — nothing in `@getvitops/emdash`'s own plugin surface (`admin.portableTextBlocks`,
|
|
133
|
+
the `hooks.page-fragments:register` capability, `definePlugin()`) changed against EmDash 1.0, so no
|
|
134
|
+
code here needed to change, only the declared range.
|
|
135
|
+
|
|
136
|
+
The `emdash` scaffold template (`vp create @getvitops:emdash`) picks up several fixes alongside
|
|
137
|
+
the version bump:
|
|
138
|
+
|
|
139
|
+
- `tailwindcss`/`@tailwindcss/vite` are now real dependencies — the template selected
|
|
140
|
+
`css.format: 'tailwind'` without installing either, which threw on build.
|
|
141
|
+
- `site.json` now sets `site.icons.sprite: true` plus the full `semantic` list the
|
|
142
|
+
`vitops.actionLink` block's icon picker offers — previously its sprite icons pointed at a file
|
|
143
|
+
the build never generated.
|
|
144
|
+
- `astro.config.mjs` sets `migrations: { runtime: 'check', dev: 'auto' }`, and
|
|
145
|
+
`site-deploy-emdash.yml` runs EmDash 1.0's documented `build → status → apply → deploy → check`
|
|
146
|
+
migration sequence explicitly, rather than relying solely on first-request auto-migration. This
|
|
147
|
+
needs a new GitHub Environment variable, `EMDASH_TARGET_FINGERPRINT` (one per environment),
|
|
148
|
+
which only a human can set — run `emdash migrate --status` against the real deployed database,
|
|
149
|
+
review the printed identity, then `gh variable set EMDASH_TARGET_FINGERPRINT --env <env> --body
|
|
150
|
+
<value>`. Deploys fail clearly on the migration-apply step until it's set.
|
|
151
|
+
- `lint.options.typeAware`/`typeCheck` in the template's own `vite.config.ts` are commented out,
|
|
152
|
+
not deleted: vite-plus 1.0 only honours that option in a project's actual root config, and hard
|
|
153
|
+
errors on it in a nested one (which this file is, sitting inside the `design-system` monorepo).
|
|
154
|
+
Uncomment it once scaffolded into its own repo.
|
|
155
|
+
|
|
156
|
+
No config schema changes. `@getvitops/generator` also switches from the `zod/mini` subpath import
|
|
157
|
+
to the standalone `@zod/mini` package (zod 4.5+; same API) and fixes `validateConfig()` silently
|
|
158
|
+
losing the offending key name on an `unrecognized_keys` error (e.g. a typo'd `site.ads` platform
|
|
159
|
+
now names the bad key instead of reporting a bare "Invalid input").
|
|
160
|
+
|
|
161
|
+
Also fixed: `vp check`'s `tsconfig-error` for `packages/create/templates/emdash/tsconfig.json`
|
|
162
|
+
(`extends: "astro/tsconfigs/strict"`, unresolvable from inside this monorepo since the template
|
|
163
|
+
has no `node_modules` of its own — a pre-existing, structural gap, newly surfaced as a hard error
|
|
164
|
+
by oxlint 1.85's deeper config discovery, and blocking on every commit since this repo's pre-commit
|
|
165
|
+
hook runs `vp check --fix`). Fixed with a new `scripts/link-template-deps.mjs`, wired as the root
|
|
166
|
+
`postinstall` script: symlinks `astro`/`emdash`/`@emdash-cms/cloudflare` into the template's own
|
|
167
|
+
`node_modules/` (resolved fresh from sibling packages that already depend on them for real, not a
|
|
168
|
+
hardcoded `.pnpm` hash) and writes a minimal `astro:content` ambient shim at `.astro/types.d.ts`
|
|
169
|
+
(the same path `astro sync` would generate for real). Both are gitignored and only ever exist
|
|
170
|
+
inside this monorepo's copy of the template — a real scaffold installs its own real copies instead.
|
|
171
|
+
Considered and rejected first: inlining astro's strict preset (drift risk against astro's own
|
|
172
|
+
evolving default) and making the template a real pnpm workspace member (would change how its
|
|
173
|
+
`@getvitops/*: "latest"` deps resolve locally, which `templates.test.ts` exists to protect).
|
|
174
|
+
|
|
175
|
+
**Known gap, not fixed here**: `packages/astro/src/components/container.test.ts` (the
|
|
176
|
+
`AstroContainer`-based render tests) fails to even load under vite-plus 1.0's Vite 8 — direct
|
|
177
|
+
`.astro` imports in a Vitest file need the Astro Vite plugin registered, which nothing in this
|
|
178
|
+
repo's test config actually does (Astro's own docs call for `getViteConfig()`, which doesn't
|
|
179
|
+
compose with vite-plus's own `defineConfig`). A type-level shim
|
|
180
|
+
(`packages/astro/src/components/astro-modules.d.ts`) fixes `vp check`'s type-aware pass, but the
|
|
181
|
+
runtime `vp test` failure for this one file is still open.
|
|
182
|
+
|
|
183
|
+
- fdc3f7e: `<wc-marquee>` adds a "Pause animation" button beside the track (WCAG 2.2.2), and the marquee also pauses while focus is inside it (`data-paused` and `:focus-within`). Pattern CSS: all formats. `<wc-consent>` moves focus into the banner when a visitor opens it from a settings control and returns it on close; an unprompted banner still doesn't take focus. `<wc-image-compare>`'s before/after panes are `role="group"` so their labels are announced. The conversion route format-checks `phone`/`email` beacon payloads and imports the Worker-safe `@getvitops/utils/content` instead of the utils root barrel.
|
|
184
|
+
|
|
3
185
|
## 8.2.0-alpha.0
|
|
4
186
|
|
|
5
187
|
### Minor Changes
|
|
6
188
|
|
|
7
189
|
- c3c1bcb: **New: the design-system editor, mounted inside the EmDash admin.** `vitopsEmdash({
|
|
8
|
-
designSystemEditor: { config } })` (`config` is your `site.json`, e.g. `import config from
|
|
9
|
-
'./site.json' with { type: 'json' }`) adds a `/design-system` admin page mounting
|
|
190
|
+
designSystemEditor: { config } })` (`config` is your `site.json`, e.g. `import config from
|
|
191
|
+
'./site.json' with { type: 'json' }`) adds a `/design-system` admin page mounting
|
|
10
192
|
`<wc-theme-editor>` — the same live token editor `@getvitops/vite`'s dev server has always
|
|
11
193
|
offered, now usable by the client directly, no dev server required. An edit is visible
|
|
12
194
|
site-wide immediately (a runtime `:root` override, served via a new plugin API route and
|
|
@@ -69,7 +251,7 @@ designSystemEditor: { config } })` (`config` is your `site.json`, e.g. `import c
|
|
|
69
251
|
needs a new GitHub Environment variable, `EMDASH_TARGET_FINGERPRINT` (one per environment),
|
|
70
252
|
which only a human can set — run `emdash migrate --status` against the real deployed database,
|
|
71
253
|
review the printed identity, then `gh variable set EMDASH_TARGET_FINGERPRINT --env <env> --body
|
|
72
|
-
<value>`. Deploys fail clearly on the migration-apply step until it's set.
|
|
254
|
+
<value>`. Deploys fail clearly on the migration-apply step until it's set.
|
|
73
255
|
- `lint.options.typeAware`/`typeCheck` in the template's own `vite.config.ts` are commented out,
|
|
74
256
|
not deleted: vite-plus 1.0 only honours that option in a project's actual root config, and hard
|
|
75
257
|
errors on it in a nested one (which this file is, sitting inside the `design-system` monorepo).
|
|
@@ -431,7 +613,7 @@ designSystemEditor: { config } })` (`config` is your `site.json`, e.g. `import c
|
|
|
431
613
|
created, with `patterns/counter.css`'s own reduced-motion query as a second belt.
|
|
432
614
|
- The accessible name stays the final value for the whole animation: `.counter__value` is toggled
|
|
433
615
|
to the framework's `.sr-only` utility while running, never hidden — `display: none`/`visibility:
|
|
434
|
-
hidden` would pull it out of the accessible-name computation along with the visual hiding, and a
|
|
616
|
+
hidden` would pull it out of the accessible-name computation along with the visual hiding, and a
|
|
435
617
|
screen reader must never be read ~60 intermediate numbers. The animated text lives in a separate
|
|
436
618
|
`aria-hidden` presentation span it never reaches.
|
|
437
619
|
- `data-from` (default `0`), `data-duration` (ms, default `1200`), `data-easing` (`linear` |
|
|
@@ -978,28 +1160,20 @@ hidden` would pull it out of the accessible-name computation along with the visu
|
|
|
978
1160
|
{
|
|
979
1161
|
"designSystem": {
|
|
980
1162
|
"themes": {
|
|
981
|
-
"default": {
|
|
982
|
-
/* … */
|
|
983
|
-
},
|
|
1163
|
+
"default": {/* … */},
|
|
984
1164
|
},
|
|
985
1165
|
},
|
|
986
1166
|
"organization": {
|
|
987
1167
|
"name": "Acme",
|
|
988
1168
|
"contact": "hq",
|
|
989
1169
|
"locations": {
|
|
990
|
-
"hq": {
|
|
991
|
-
/* … */
|
|
992
|
-
},
|
|
1170
|
+
"hq": {/* … */},
|
|
993
1171
|
},
|
|
994
1172
|
},
|
|
995
1173
|
"site": {
|
|
996
1174
|
"defaultLocale": "en",
|
|
997
|
-
"domains": {
|
|
998
|
-
|
|
999
|
-
},
|
|
1000
|
-
"analytics": {
|
|
1001
|
-
/* … */
|
|
1002
|
-
},
|
|
1175
|
+
"domains": {/* … */},
|
|
1176
|
+
"analytics": {/* … */},
|
|
1003
1177
|
},
|
|
1004
1178
|
}
|
|
1005
1179
|
```
|
|
@@ -1093,7 +1267,7 @@ hidden` would pull it out of the accessible-name computation along with the visu
|
|
|
1093
1267
|
`animate-view` / `animate-scroll` / `animate-trigger` / `transition`, and every effect
|
|
1094
1268
|
gained an `open-<fx>` state variant. Overlays now state _where they start_
|
|
1095
1269
|
(`--translate-x-from`) instead of owning a keyframe: `class="drawer drawer--right
|
|
1096
|
-
open-fade-in"` composes slide and fade. Applied at zero specificity with identity
|
|
1270
|
+
open-fade-in"` composes slide and fade. Applied at zero specificity with identity
|
|
1097
1271
|
defaults, so a popover that sets no effect vars is unchanged.
|
|
1098
1272
|
- **One scrim.** `--scrim` / `--scrim-filter` tokens and `.no-scrim` in `popover.css`,
|
|
1099
1273
|
replacing 18 `::backdrop` blocks across seven partials. `.drawer--modeless` is aliased.
|
|
@@ -1296,8 +1470,8 @@ open-fade-in"` composes slide and fade. Applied at zero specificity with identit
|
|
|
1296
1470
|
replace it with `--anim-range`. `--stagger-range-step` is now a viewport length (`5vh`) to match.
|
|
1297
1471
|
|
|
1298
1472
|
**Fixed: every journey ran on a truncated range.** The generator emitted `animation-range: entry
|
|
1299
|
-
exit`; lightningcss parsed the end of that shorthand as `exit 0%` rather than the spec's `exit
|
|
1300
|
-
100%`, so the bundle shipped `entry exit 0%`. Journeys reach their `100%` keyframe — the hidden
|
|
1473
|
+
exit`; lightningcss parsed the end of that shorthand as `exit 0%` rather than the spec's `exit
|
|
1474
|
+
100%`, so the bundle shipped `entry exit 0%`. Journeys reach their `100%` keyframe — the hidden
|
|
1301
1475
|
`from` state — as the element hits the top of the viewport, which is to say they faded out while
|
|
1302
1476
|
still fully on screen. Journeys now run `entry calc(50% + 10vh) → exit 100%`: they start on the same
|
|
1303
1477
|
midpoint pivot as everything else and keep the full entry → hold → exit arc, so the hold sits in the
|
|
@@ -1310,7 +1484,7 @@ exit`; lightningcss parsed the end of that shorthand as `exit 0%` rather than th
|
|
|
1310
1484
|
so one knob tunes both. If your own config has an empty `slide` base, add the same line.
|
|
1311
1485
|
|
|
1312
1486
|
**Fixed: `reveal-*` on hover was unreachable.** A `hover-reveal-left` element rests at `clip-path:
|
|
1313
|
-
inset(0 100% 0 0)`, and `clip-path` clips **hit-testing** as well as painting — so it had zero
|
|
1487
|
+
inset(0 100% 0 0)`, and `clip-path` clips **hit-testing** as well as painting — so it had zero
|
|
1314
1488
|
hittable area and could never receive the hover that would reveal it. The state variants now match
|
|
1315
1489
|
the element **or its direct parent**, mirroring what `animation.css` already did for the trigger
|
|
1316
1490
|
driver (`:is(.is-active, [data-active]) > .animate-trigger`):
|
package/css/index.css
CHANGED
|
@@ -72,6 +72,13 @@
|
|
|
72
72
|
@import './patterns/text-effects.css';
|
|
73
73
|
@import './patterns/svg.css';
|
|
74
74
|
@import './patterns/marquee.css';
|
|
75
|
+
@import './patterns/tone.css';
|
|
76
|
+
@import './patterns/section.css';
|
|
77
|
+
@import './patterns/steps.css';
|
|
78
|
+
@import './patterns/stats.css';
|
|
79
|
+
@import './patterns/callout.css';
|
|
80
|
+
@import './patterns/logos.css';
|
|
81
|
+
@import './patterns/embed.css';
|
|
75
82
|
@import './patterns/counter.css';
|
|
76
83
|
@import './patterns/nav.css';
|
|
77
84
|
@import './patterns/carousel.css';
|
package/css/patterns/banner.css
CHANGED
|
@@ -69,3 +69,23 @@
|
|
|
69
69
|
.banner[hidden] {
|
|
70
70
|
display: none;
|
|
71
71
|
}
|
|
72
|
+
|
|
73
|
+
/*
|
|
74
|
+
* `<wc-dismissable>` wraps a banner or notification — block content — and animates its own
|
|
75
|
+
* opacity/scale on dismiss, so it needs a box of its own: `display: contents` would drop
|
|
76
|
+
* that animation. Pre-upgrade it would otherwise be an inline box around a block child.
|
|
77
|
+
*/
|
|
78
|
+
:where(wc-dismissable) {
|
|
79
|
+
display: block;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/*
|
|
83
|
+
* A dismiss button does nothing until `<wc-dismissable>` upgrades — an inert control that
|
|
84
|
+
* looks live is worse than none. `:not(:defined)` is true with no JS (and until elements.js
|
|
85
|
+
* has run), so the close affordance only appears once something can act on it. The banner
|
|
86
|
+
* itself stays fully readable. Markup that puts a `[data-dismiss]` button inside the element
|
|
87
|
+
* gets the same treatment, whatever its class.
|
|
88
|
+
*/
|
|
89
|
+
wc-dismissable:not(:defined) :is(.banner__close, .notification__close, [data-dismiss]) {
|
|
90
|
+
display: none;
|
|
91
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Callout — a call-to-action band: a short message and the action it points at, set
|
|
3
|
+
* apart from the page. Not a banner: `banner` is a dismissible announcement; this is
|
|
4
|
+
* content that stays.
|
|
5
|
+
*
|
|
6
|
+
* Message left and actions right when there is room, stacked when there is not. Colour
|
|
7
|
+
* is `.tone` (tone.css); `--center` stacks and centres at every width.
|
|
8
|
+
*/
|
|
9
|
+
.callout {
|
|
10
|
+
display: grid;
|
|
11
|
+
gap: var(--callout-gap, 1rem 2rem);
|
|
12
|
+
align-items: center;
|
|
13
|
+
padding: var(--callout-padding, clamp(1.25rem, 4cqi, 2.5rem));
|
|
14
|
+
border: var(--b-card, var(--b-card-group, var(--b-default)));
|
|
15
|
+
border-radius: var(--br-card, var(--br-card-group, var(--br-default)));
|
|
16
|
+
|
|
17
|
+
.callout__body {
|
|
18
|
+
display: grid;
|
|
19
|
+
gap: 0.5rem;
|
|
20
|
+
min-inline-size: 0;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
.callout__title,
|
|
24
|
+
.callout__text,
|
|
25
|
+
.callout__actions {
|
|
26
|
+
margin: 0;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
&.callout--center {
|
|
30
|
+
text-align: center;
|
|
31
|
+
justify-items: center;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
@container (min-width: 48rem) {
|
|
36
|
+
.callout:not(.callout--center) {
|
|
37
|
+
grid-template-columns: minmax(0, 1fr) auto;
|
|
38
|
+
}
|
|
39
|
+
}
|
package/css/patterns/copy.css
CHANGED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Embed — a third-party iframe (map, video) held at a fixed aspect ratio, behind consent.
|
|
3
|
+
*
|
|
4
|
+
* The iframe ships with NO `src`: the address rides on `data-consent-src`, and the consent
|
|
5
|
+
* runtime assigns it only once the visitor has granted the category. Until then the
|
|
6
|
+
* `.embed__placeholder` — a real link to the same content, so it is useful with no JS — is
|
|
7
|
+
* what the page shows; `[data-vitops-activated]` on the iframe (set by the runtime) hides
|
|
8
|
+
* it. No third-party request is made before consent.
|
|
9
|
+
*/
|
|
10
|
+
.embed {
|
|
11
|
+
position: relative;
|
|
12
|
+
aspect-ratio: var(--embed-ratio, 16 / 9);
|
|
13
|
+
overflow: hidden;
|
|
14
|
+
border: var(--b-card, var(--b-card-group, var(--b-default)));
|
|
15
|
+
border-radius: var(--br-card, var(--br-card-group, var(--br-default)));
|
|
16
|
+
|
|
17
|
+
.embed__frame {
|
|
18
|
+
position: absolute;
|
|
19
|
+
inset: 0;
|
|
20
|
+
inline-size: 100%;
|
|
21
|
+
block-size: 100%;
|
|
22
|
+
border: 0;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
.embed__placeholder {
|
|
26
|
+
position: absolute;
|
|
27
|
+
inset: 0;
|
|
28
|
+
display: grid;
|
|
29
|
+
place-content: center;
|
|
30
|
+
gap: 0.5rem;
|
|
31
|
+
padding: 1rem;
|
|
32
|
+
text-align: center;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/* Once the runtime has loaded the frame, the placeholder has nothing left to say. */
|
|
36
|
+
.embed__frame[data-vitops-activated] ~ .embed__placeholder {
|
|
37
|
+
display: none;
|
|
38
|
+
}
|
|
39
|
+
}
|
package/css/patterns/forms.css
CHANGED
|
@@ -364,3 +364,22 @@ select.custom-select {
|
|
|
364
364
|
outline-offset: 2px;
|
|
365
365
|
}
|
|
366
366
|
}
|
|
367
|
+
|
|
368
|
+
/* `<wc-multi-field>` is a group of controls, a block. Its shadow `:host` declares no display. */
|
|
369
|
+
:where(wc-multi-field) {
|
|
370
|
+
display: block;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
/* `<wc-turnstile>` wraps a fallback paragraph and the widget box, both blocks (light DOM, no `:host`). */
|
|
374
|
+
:where(wc-turnstile) {
|
|
375
|
+
display: block;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
/*
|
|
379
|
+
* A message shown only when the URL fragment names it. A prerendered page cannot know whether a
|
|
380
|
+
* no-JS post succeeded, so a form's success and error notices are always in the markup and the
|
|
381
|
+
* endpoint's redirect (`#form-sent` / `#form-error`) reveals one.
|
|
382
|
+
*/
|
|
383
|
+
.form-notice--target:not(:target) {
|
|
384
|
+
display: none;
|
|
385
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Logos — a static grid of partner/client marks. `marquee` is the scrolling version;
|
|
3
|
+
* this one holds still, which is what most "key partners" rows should do (nothing to
|
|
4
|
+
* pause, nothing to track, no motion to opt out of).
|
|
5
|
+
*
|
|
6
|
+
* Each item is a plain image, or a link around one. Marks are normalised to a common
|
|
7
|
+
* height rather than a common width, because logos vary far more in aspect ratio than
|
|
8
|
+
* in how tall they should read. `--grayscale` quiets them until hover or focus.
|
|
9
|
+
*/
|
|
10
|
+
.logos {
|
|
11
|
+
display: flex;
|
|
12
|
+
flex-wrap: wrap;
|
|
13
|
+
align-items: center;
|
|
14
|
+
justify-content: var(--logos-justify, center);
|
|
15
|
+
gap: var(--logos-gap, 1.5rem 2.5rem);
|
|
16
|
+
margin: 0;
|
|
17
|
+
padding: 0;
|
|
18
|
+
list-style: none;
|
|
19
|
+
|
|
20
|
+
.logos__item {
|
|
21
|
+
display: grid;
|
|
22
|
+
place-items: center;
|
|
23
|
+
margin: 0;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
.logos__item img {
|
|
27
|
+
block-size: var(--logos-height, 2.5rem);
|
|
28
|
+
inline-size: auto;
|
|
29
|
+
max-inline-size: 100%;
|
|
30
|
+
object-fit: contain;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
&.logos--grayscale .logos__item img {
|
|
34
|
+
filter: grayscale(1);
|
|
35
|
+
opacity: 0.7;
|
|
36
|
+
transition:
|
|
37
|
+
filter 0.2s,
|
|
38
|
+
opacity 0.2s;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
&.logos--grayscale .logos__item:is(:hover, :focus-within) img {
|
|
42
|
+
filter: none;
|
|
43
|
+
opacity: 1;
|
|
44
|
+
}
|
|
45
|
+
}
|
package/css/patterns/marquee.css
CHANGED
|
@@ -56,6 +56,13 @@
|
|
|
56
56
|
animation-play-state: paused;
|
|
57
57
|
}
|
|
58
58
|
|
|
59
|
+
/* Keyboard users can't hover; focusing anything inside (a link in an item) also pauses, and
|
|
60
|
+
`<wc-marquee>`'s pause button sets `data-paused` (WCAG 2.2.2). */
|
|
61
|
+
&:focus-within .marquee__content,
|
|
62
|
+
&[data-paused] .marquee__content {
|
|
63
|
+
animation-play-state: paused;
|
|
64
|
+
}
|
|
65
|
+
|
|
59
66
|
&.marquee--reverse {
|
|
60
67
|
--_marquee-direction: reverse;
|
|
61
68
|
}
|
|
@@ -100,3 +107,12 @@
|
|
|
100
107
|
translate: 0 calc(-1 * var(--_marquee-distance, calc(100% + var(--marquee-gap, 1rem))));
|
|
101
108
|
}
|
|
102
109
|
}
|
|
110
|
+
|
|
111
|
+
/*
|
|
112
|
+
* The `.marquee` class carries `display: flex` when present (the Astro component always sets
|
|
113
|
+
* it). This is the fallback for a hand-written `<wc-marquee>` without the class, and it is
|
|
114
|
+
* zero specificity so `.marquee` still wins.
|
|
115
|
+
*/
|
|
116
|
+
:where(wc-marquee) {
|
|
117
|
+
display: block;
|
|
118
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Section — the editorial wrapper most pages are made of: an optional kicker, a heading,
|
|
3
|
+
* a lead-in line, then a body of other blocks.
|
|
4
|
+
*
|
|
5
|
+
* Composed from the layout primitives rather than reinventing them: the outer element
|
|
6
|
+
* is `.region` (vertical rhythm) and the content sits on `.centered`'s tracks, so this
|
|
7
|
+
* partial only adds what neither has — the head's own rhythm and the `--split` layout,
|
|
8
|
+
* which puts the head beside the body (heading-left) above the container breakpoint.
|
|
9
|
+
* Backdrop and colour come from `.tone` (tone.css), never from here.
|
|
10
|
+
*/
|
|
11
|
+
.section {
|
|
12
|
+
.section__head {
|
|
13
|
+
display: grid;
|
|
14
|
+
gap: var(--section-head-gap, 0.75rem);
|
|
15
|
+
align-content: start;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
.section__kicker,
|
|
19
|
+
.section__title {
|
|
20
|
+
margin: 0;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
.section__sub {
|
|
24
|
+
margin: 0;
|
|
25
|
+
max-inline-size: var(--width-measure, 65ch);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
.section__body {
|
|
29
|
+
display: grid;
|
|
30
|
+
gap: var(--section-body-gap, 1.5rem);
|
|
31
|
+
min-inline-size: 0;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/* Head and body stack until there is room for two columns (below). */
|
|
35
|
+
&.section--split .section__inner {
|
|
36
|
+
display: grid;
|
|
37
|
+
gap: var(--section-split-gap, clamp(1.5rem, 4cqi, 3rem));
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
&.section--center .section__head {
|
|
41
|
+
text-align: center;
|
|
42
|
+
justify-items: center;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
@container (min-width: 48rem) {
|
|
47
|
+
.section.section--split .section__inner {
|
|
48
|
+
grid-template-columns:
|
|
49
|
+
minmax(0, var(--section-split-head, 1fr))
|
|
50
|
+
minmax(0, var(--section-split-body, 2fr));
|
|
51
|
+
align-items: start;
|
|
52
|
+
}
|
|
53
|
+
}
|