@getvitops/generator 7.0.0 → 8.0.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 +127 -22
- package/assets/css/global.css +28 -0
- package/assets/css/index.css +2 -0
- package/assets/css/patterns/gallery.css +8 -4
- package/assets/css/patterns/lightbox.css +84 -66
- package/assets/css/patterns/review.css +91 -0
- package/assets/js/elements.js +7 -7
- package/config.schema.json +69 -0
- package/dist/index.d.mts +75 -2
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +126 -15
- package/dist/index.mjs.map +1 -1
- package/package.json +15 -4
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,118 @@
|
|
|
1
1
|
# @getvitops/generator
|
|
2
2
|
|
|
3
|
+
## 8.0.0
|
|
4
|
+
|
|
5
|
+
### Major Changes
|
|
6
|
+
|
|
7
|
+
- Rebuild the lightbox on one dialog + the existing carousel, fixing the flicker on
|
|
8
|
+
prev/next and the stutter on open.
|
|
9
|
+
|
|
10
|
+
**Breaking (`css`/`bricks` and `tailwind` formats — `.lightbox-dialog`'s markup
|
|
11
|
+
contract changed):**
|
|
12
|
+
|
|
13
|
+
- `<Gallery />` now emits ONE `<dialog class="lightbox-dialog">` per gallery,
|
|
14
|
+
wrapping a `<Carousel />`, instead of one dialog per image. A `.gallery__trigger`
|
|
15
|
+
is now a plain `<a href="#slide-id">`, not a `command="show-modal"` button.
|
|
16
|
+
- `.lightbox-dialog__content` / `__image` / `__nav` / `__nav-button` /
|
|
17
|
+
`__caption` are gone. The dialog's child is `.lightbox-dialog__track` (a
|
|
18
|
+
`.carousel`) — prev/next, dots, arrow keys and swipe are the carousel's,
|
|
19
|
+
reused rather than reimplemented. `.lightbox-dialog__close` is unchanged.
|
|
20
|
+
- `<wc-gallery>` no longer builds nav buttons or steps between images — the
|
|
21
|
+
nested `<wc-carousel>` does. It fires `gallery-open` (unchanged shape) and
|
|
22
|
+
re-emits the carousel's own stepping as `gallery-change`.
|
|
23
|
+
- `packages/core/src/web-components/utils/gallery.ts` dropped `keyToAction` and
|
|
24
|
+
`swipeAction` (the carousel's own key/swipe handling covers both now).
|
|
25
|
+
`stepIndex`/`preloadTargets` are unchanged.
|
|
26
|
+
|
|
27
|
+
**Why:** stepping used to close one `<dialog>` and open another, which made
|
|
28
|
+
`animation.css`'s top-layer driver play a full exit-then-entry transition on
|
|
29
|
+
every step — visible as a flicker on prev/next, by construction, not a bug to
|
|
30
|
+
tune away. Opening also awaited the full image's `decode()` before starting the
|
|
31
|
+
view-transition morph, so the dialog visibly stalled on a slow network. One
|
|
32
|
+
dialog containing one carousel makes the first failure mode unrepresentable
|
|
33
|
+
(stepping is a scroll, not a close/open pair) and removes the wait for the
|
|
34
|
+
second (the morph starts immediately, painting the thumbnail as a CSS
|
|
35
|
+
background placeholder until the full image arrives).
|
|
36
|
+
|
|
37
|
+
**Also improved, not just fixed:** the no-JS fallback is no longer a _stripped_
|
|
38
|
+
lightbox — it's a genuine one. Each trigger is ordinary fragment navigation
|
|
39
|
+
(`:has(:target)` in `lightbox.css` reveals the dialog), which needs no Invoker
|
|
40
|
+
Commands polyfill in any engine, and it now has working prev/next with no
|
|
41
|
+
JavaScript at all — the previous per-dialog fallback had no way to express that.
|
|
42
|
+
|
|
43
|
+
**Migration:** a consumer using `<Gallery />` needs no changes — the component's
|
|
44
|
+
props are unchanged. Anyone who hand-authored `.lightbox-dialog` markup (per
|
|
45
|
+
`docs/concepts/patterns.md` / `docs/css/classes.md`) must move to the new single-
|
|
46
|
+
dialog-wrapping-a-carousel shape; see `patterns.astro`'s "Gallery, hand-authored"
|
|
47
|
+
demo for the reference markup. `<wc-gallery>` consumers listening for
|
|
48
|
+
`gallery-change` keep the same event shape (`{ index, total }`).
|
|
49
|
+
|
|
50
|
+
### Minor Changes
|
|
51
|
+
|
|
52
|
+
- Add Google Business Profile reviews: fetch, cache, render.
|
|
53
|
+
|
|
54
|
+
- **`vitops reviews`** (new CLI command) fetches a client's GBP reviews (legacy v4 API — the only
|
|
55
|
+
one that has them) and writes them to `.vitops/reviews.json`, meant to be committed so a build
|
|
56
|
+
never needs a live Google credential. `--dry` prints the plan; `--check` diffs against the cache
|
|
57
|
+
without writing. Requires the same `VITOPS_GOOGLE_CLIENT_ID`/`_CLIENT_SECRET`/`_REFRESH_TOKEN` (or
|
|
58
|
+
ADC login) `search setup` uses, scoped to `business.manage` — **and the calling GCP project must
|
|
59
|
+
be allowlisted by Google first**, a separate one-time approval from every other Google API here.
|
|
60
|
+
- **`site.reviews`** (config): which `organization.locations` to fetch, and per-location
|
|
61
|
+
`featured`/`minRating`/`sort`/`limit` selection. `schema` (JSON-LD) and `avatars` both default to
|
|
62
|
+
**off** — see the migration notes below.
|
|
63
|
+
- **`@getvitops/utils/reviews`** (new subpath): the pure plan/select/merge functions plus the GBP
|
|
64
|
+
fetcher and cache reader/writer, mirroring `./indexing`'s pure/IO split.
|
|
65
|
+
- **`<ReviewCard>`** (new Astro component, `@getvitops/astro`): one review card from a cache entry.
|
|
66
|
+
No web component — nothing here needs progressive enhancement. Composes the existing
|
|
67
|
+
`schemas/Review.astro` for JSON-LD only when both `schema` and `itemReviewed` are passed.
|
|
68
|
+
- **`<Marquee>`** (new Astro component): the tier-3 wrapper `wc-marquee`/`marquee.css` was missing.
|
|
69
|
+
- **`.review` pattern** (`css`/`bricks` and `tailwind` formats): a new config-generated
|
|
70
|
+
`patterns.items.review` entry plus `packages/core/css/patterns/review.css` for the structural
|
|
71
|
+
parts (rating, identity, body, reply, source link). Two new semantic icon names, `star` and
|
|
72
|
+
`star-half`, across every icon set.
|
|
73
|
+
|
|
74
|
+
**Migration:** no breaking change. A site adding `site.reviews` should read the "Google reviews"
|
|
75
|
+
section in AGENTS.md before enabling `schema` — Google's structured-data policy treats a business
|
|
76
|
+
marking up reviews of itself as ineligible for review rich results and risks a manual action, which
|
|
77
|
+
is why it defaults off.
|
|
78
|
+
|
|
79
|
+
- Repo-wide simplification pass: de-boilerplate the CLI, dedupe shared cache/plan
|
|
80
|
+
primitives in `@getvitops/utils`, drop dead code, and tighten dependency hygiene.
|
|
81
|
+
|
|
82
|
+
No breaking changes. (`vitops icons`'s `--site` flag was briefly going to be renamed to
|
|
83
|
+
`--input` for consistency with the other `Config`-anchored commands — deferred rather than
|
|
84
|
+
spending a major bump on a rename with no other breaking change riding along; it stays
|
|
85
|
+
`--site` for now.)
|
|
86
|
+
|
|
87
|
+
**Fixed:**
|
|
88
|
+
|
|
89
|
+
- `@getvitops/core`'s 12 runtime deps (`lit`, both `@oddbird` polyfills, both
|
|
90
|
+
`@webcomponents` packages, `colorjs.io`, etc.) were listed as `dependencies` despite
|
|
91
|
+
being bundled into `dist/elements.js`/`dist/polyfills.js`/`dist/deferred.js` — a
|
|
92
|
+
consumer installed them twice for nothing. Moved to `devDependencies`; no change to
|
|
93
|
+
what ships.
|
|
94
|
+
- Deleted `old-css-lib/` (59 files), superseded by `packages/core/css/` since the
|
|
95
|
+
in-memory bundling change — no consumer-facing effect, it was not part of any
|
|
96
|
+
published output.
|
|
97
|
+
- Deleted a dead Astro module (`packages/astro/src/scripts/deferred.ts`) that had
|
|
98
|
+
silently diverged from `packages/core/src/js/deferred.ts` and had zero importers.
|
|
99
|
+
|
|
100
|
+
**No consumer-facing change in `@getvitops/vite`** beyond the fixed-group version bump —
|
|
101
|
+
task-graph and caching fixes internal to this repo's own build.
|
|
102
|
+
|
|
103
|
+
Everything else (the `cli.ts` file split into `commands/{search,ads}.ts`, shared
|
|
104
|
+
`json-store.ts`/`plan-format.ts` helpers in `@getvitops/utils`, a `.github/actions/setup`
|
|
105
|
+
composite action, restatement-comment cleanup) is internal refactoring with no change to
|
|
106
|
+
any published API, CLI flag, or generated output.
|
|
107
|
+
|
|
108
|
+
### Patch Changes
|
|
109
|
+
|
|
110
|
+
- Updated dependencies
|
|
111
|
+
- Updated dependencies
|
|
112
|
+
- Updated dependencies
|
|
113
|
+
- Updated dependencies [a84839b]
|
|
114
|
+
- @getvitops/utils@8.0.0
|
|
115
|
+
|
|
3
116
|
## 7.0.0
|
|
4
117
|
|
|
5
118
|
### Major Changes
|
|
@@ -1012,28 +1125,20 @@ hidden` would pull it out of the accessible-name computation along with the visu
|
|
|
1012
1125
|
{
|
|
1013
1126
|
"designSystem": {
|
|
1014
1127
|
"themes": {
|
|
1015
|
-
"default": {
|
|
1016
|
-
/* … */
|
|
1017
|
-
},
|
|
1128
|
+
"default": {/* … */},
|
|
1018
1129
|
},
|
|
1019
1130
|
},
|
|
1020
1131
|
"organization": {
|
|
1021
1132
|
"name": "Acme",
|
|
1022
1133
|
"contact": "hq",
|
|
1023
1134
|
"locations": {
|
|
1024
|
-
"hq": {
|
|
1025
|
-
/* … */
|
|
1026
|
-
},
|
|
1135
|
+
"hq": {/* … */},
|
|
1027
1136
|
},
|
|
1028
1137
|
},
|
|
1029
1138
|
"site": {
|
|
1030
1139
|
"defaultLocale": "en",
|
|
1031
|
-
"domains": {
|
|
1032
|
-
|
|
1033
|
-
},
|
|
1034
|
-
"analytics": {
|
|
1035
|
-
/* … */
|
|
1036
|
-
},
|
|
1140
|
+
"domains": {/* … */},
|
|
1141
|
+
"analytics": {/* … */},
|
|
1037
1142
|
},
|
|
1038
1143
|
}
|
|
1039
1144
|
```
|
|
@@ -1198,7 +1303,7 @@ open-fade-in"` composes slide and fade. Applied at zero specificity with identit
|
|
|
1198
1303
|
Also new, and worth wiring at the same time — **`gitLastmod()` for real sitemap dates**:
|
|
1199
1304
|
|
|
1200
1305
|
```js
|
|
1201
|
-
import vitops, { gitLastmod } from
|
|
1306
|
+
import vitops, { gitLastmod } from "@getvitops/astro";
|
|
1202
1307
|
vitops({ sitemap: { serialize: await gitLastmod() } });
|
|
1203
1308
|
```
|
|
1204
1309
|
|
|
@@ -1383,8 +1488,8 @@ open-fade-in"` composes slide and fade. Applied at zero specificity with identit
|
|
|
1383
1488
|
|
|
1384
1489
|
```js
|
|
1385
1490
|
vitops({
|
|
1386
|
-
analytics: { googleAnalytics:
|
|
1387
|
-
consent: { policyUrl:
|
|
1491
|
+
analytics: { googleAnalytics: "G-XXXXXXXXXX", plausible: "acme.com" },
|
|
1492
|
+
consent: { policyUrl: "/legal/cookies" },
|
|
1388
1493
|
});
|
|
1389
1494
|
```
|
|
1390
1495
|
|
|
@@ -1524,7 +1629,7 @@ inset(0 100% 0 0)`, and `clip-path` clips **hit-testing** as well as painting
|
|
|
1524
1629
|
|
|
1525
1630
|
```js
|
|
1526
1631
|
// astro.config.mjs
|
|
1527
|
-
getvitops({ css: { input:
|
|
1632
|
+
getvitops({ css: { input: "company.json", format: "css" } });
|
|
1528
1633
|
```
|
|
1529
1634
|
|
|
1530
1635
|
```
|
|
@@ -1575,11 +1680,11 @@ inset(0 100% 0 0)`, and `clip-path` clips **hit-testing** as well as painting
|
|
|
1575
1680
|
getvitops({
|
|
1576
1681
|
fonts: [
|
|
1577
1682
|
{
|
|
1578
|
-
name:
|
|
1579
|
-
provider:
|
|
1580
|
-
cssVariable:
|
|
1581
|
-
weights: [
|
|
1582
|
-
subsets: [
|
|
1683
|
+
name: "League Spartan",
|
|
1684
|
+
provider: "fontsource",
|
|
1685
|
+
cssVariable: "--font-league-spartan",
|
|
1686
|
+
weights: ["100 900"],
|
|
1687
|
+
subsets: ["latin"],
|
|
1583
1688
|
preload: true,
|
|
1584
1689
|
},
|
|
1585
1690
|
],
|
|
@@ -2450,7 +2555,7 @@ inset(0 100% 0 0)`, and `clip-path` clips **hit-testing** as well as painting
|
|
|
2450
2555
|
```js
|
|
2451
2556
|
// astro.config.mjs
|
|
2452
2557
|
getvitops({
|
|
2453
|
-
css: { input:
|
|
2558
|
+
css: { input: "design-system.json", format: "css", out: "src/styles" },
|
|
2454
2559
|
editor: true,
|
|
2455
2560
|
});
|
|
2456
2561
|
```
|
package/assets/css/global.css
CHANGED
|
@@ -75,3 +75,31 @@ body {
|
|
|
75
75
|
scroll-padding-block-start: var(--top-offset, 0);
|
|
76
76
|
}
|
|
77
77
|
}
|
|
78
|
+
|
|
79
|
+
/* The focus ring must survive Windows High Contrast Mode, and as generated it
|
|
80
|
+
does not. Every pattern's `ring: true` state (`generate.ts`) emits `outline:
|
|
81
|
+
none; box-shadow: 0 0 0 3px <color>;` — a deliberate choice the rest of the
|
|
82
|
+
time, since a box-shadow ring can follow a border-radius where a native
|
|
83
|
+
outline cannot. But `forced-colors: active` forces `box-shadow` to `none`
|
|
84
|
+
unconditionally (CSS Color Adjust Module), and this framework already asked
|
|
85
|
+
for no native outline either — so a keyboard user in forced-colors mode gets
|
|
86
|
+
NO focus indicator at all, on every `:focus-visible` element in every
|
|
87
|
+
format. This is the single most severe instance of the class of bug
|
|
88
|
+
`forced-colors`/`prefers-contrast` coverage exists to catch (AGENTS.md's
|
|
89
|
+
design-system-gaps notes on `--surface-glass`/`--overlay`/role solids are
|
|
90
|
+
the same class, decorative rather than safety-critical).
|
|
91
|
+
|
|
92
|
+
`!important`, deliberately, for the same reason `:root`'s `font-size` above
|
|
93
|
+
gets it: this must win regardless of which pattern's `vitops.components`
|
|
94
|
+
rule set `outline: none`, and `!important` outranks a normal-priority
|
|
95
|
+
declaration regardless of cascade layer. The colour is the token every
|
|
96
|
+
other ring already uses (`--color-border-focus`, from `ui-primary`,
|
|
97
|
+
contrast-checked as a non-text boundary) — forced-colors mode remaps
|
|
98
|
+
`outline-color` to a system colour automatically, so this is really only
|
|
99
|
+
asserting THAT an outline renders, not which colour it renders in. */
|
|
100
|
+
@media (forced-colors: active) {
|
|
101
|
+
:focus-visible {
|
|
102
|
+
outline: 2px solid var(--color-border-focus) !important;
|
|
103
|
+
outline-offset: 2px !important;
|
|
104
|
+
}
|
|
105
|
+
}
|
package/assets/css/index.css
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
@import './generated/type-tokens.css';
|
|
7
7
|
@import './generated/tokens.css';
|
|
8
8
|
@import './generated/typography.css';
|
|
9
|
+
@import './generated/typography-headings.css';
|
|
9
10
|
@import './layout.css';
|
|
10
11
|
/* Must precede utilities.css: both are `vitops.utilities`, and utilities.css
|
|
11
12
|
re-emits `.<bp>-flex` AFTER its own `.hidden` so `class="hidden md-flex"`
|
|
@@ -53,6 +54,7 @@
|
|
|
53
54
|
@import './patterns/table.css';
|
|
54
55
|
@import './patterns/details.css';
|
|
55
56
|
@import './patterns/comment.css';
|
|
57
|
+
@import './patterns/review.css';
|
|
56
58
|
@import './patterns/banner.css';
|
|
57
59
|
@import './patterns/consent.css';
|
|
58
60
|
@import './patterns/list.css';
|
|
@@ -6,10 +6,13 @@
|
|
|
6
6
|
* concerns — "a grid of images" and "view one full-screen" — and only the dialog
|
|
7
7
|
* half needs the `lightbox` name; the grid never was one.
|
|
8
8
|
*
|
|
9
|
-
* `<wc-gallery>` wraps this and
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
9
|
+
* `<wc-gallery>` wraps this and the single `.lightbox-dialog` it opens, running the
|
|
10
|
+
* thumbnail → full-image view-transition morph; stepping between images is a
|
|
11
|
+
* `<wc-carousel>` scroll inside that dialog, not a second dialog. With no JS each
|
|
12
|
+
* `.gallery__trigger` is a real `<a href="#slide-id">` into the carousel's own
|
|
13
|
+
* `<li id="slide-id">`, and `:has(:target)` in lightbox.css is what reveals the
|
|
14
|
+
* dialog — so the grid + dialog already form a working modal lightbox, with
|
|
15
|
+
* prev/next, before any script runs.
|
|
13
16
|
*/
|
|
14
17
|
|
|
15
18
|
wc-gallery {
|
|
@@ -40,6 +43,7 @@ wc-gallery {
|
|
|
40
43
|
padding: 0;
|
|
41
44
|
background: none;
|
|
42
45
|
border: none;
|
|
46
|
+
text-decoration: none;
|
|
43
47
|
cursor: pointer;
|
|
44
48
|
|
|
45
49
|
&:focus-visible {
|
|
@@ -2,10 +2,17 @@
|
|
|
2
2
|
* Lightbox — the full-screen `<dialog>` a gallery trigger opens.
|
|
3
3
|
* Consumes the generated token cascade (src/css/generated/tokens.css).
|
|
4
4
|
*
|
|
5
|
-
* The thumbnail grid that drives this
|
|
5
|
+
* The thumbnail grid that drives this lives in `patterns/gallery.css`
|
|
6
6
|
* (`.gallery` / `.gallery__item` / `.gallery__trigger` / `.gallery__thumb`) — this
|
|
7
7
|
* partial owns only the dialog a trigger opens. `<Gallery />` emits both; this
|
|
8
8
|
* file has no opinion on what triggers it.
|
|
9
|
+
*
|
|
10
|
+
* **One dialog, one carousel** — not one dialog per image. Stepping between
|
|
11
|
+
* images is a scroll inside an already-open dialog (`.carousel`, patterns/
|
|
12
|
+
* carousel.css), so there is no close-one/open-another sequence for the top-layer
|
|
13
|
+
* driver in animation.css to double-animate. `<wc-gallery>` only opens/closes;
|
|
14
|
+
* `<wc-carousel>` (nested inside, `<Gallery />` emits both tags) owns prev/next,
|
|
15
|
+
* dots, arrow keys and swipe — reused rather than re-implemented.
|
|
9
16
|
*/
|
|
10
17
|
|
|
11
18
|
/* ── Lightbox dialog — full-screen, w/ view transitions ─────────────────────── */
|
|
@@ -22,7 +29,7 @@ dialog.lightbox-dialog {
|
|
|
22
29
|
background: var(--lightbox-dialog-bg, oklch(0 0 0 / 0.92));
|
|
23
30
|
color: #fff;
|
|
24
31
|
display: grid;
|
|
25
|
-
place-items:
|
|
32
|
+
place-items: stretch;
|
|
26
33
|
|
|
27
34
|
/* No scrim. Stated through popover.css's token rather than overriding its
|
|
28
35
|
::backdrop rule, so it keeps the shared fade timing. */
|
|
@@ -31,7 +38,8 @@ dialog.lightbox-dialog {
|
|
|
31
38
|
/* The entry, as effect vars rather than a keyframe — same approach as
|
|
32
39
|
dialog.css, and for the same reason: the top-layer driver in animation.css
|
|
33
40
|
then animates this OUT too, which an `animation:` on `[open]` never does
|
|
34
|
-
(it only ever plays once, on open).
|
|
41
|
+
(it only ever plays once, on open). Runs on real open/close ONLY — stepping
|
|
42
|
+
between slides never opens or closes this dialog, so it can never fire here. */
|
|
35
43
|
--opacity-from: 0;
|
|
36
44
|
--scale-x-from: 0.95;
|
|
37
45
|
--scale-y-from: 0.95;
|
|
@@ -40,26 +48,79 @@ dialog.lightbox-dialog {
|
|
|
40
48
|
display: grid;
|
|
41
49
|
}
|
|
42
50
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
51
|
+
/* ── No-JS fallback: `:target` + `:has()` ──────────────────────────────────
|
|
52
|
+
A `.gallery__trigger` is a real `<a href="#slide-id">`. Following it makes
|
|
53
|
+
that slide's `<li>` match `:target`, which this rule alone is what reveals
|
|
54
|
+
the dialog — no Invoker Commands, no polyfill, in every engine that has
|
|
55
|
+
`:has()` (Safari 15.4+, Chromium 105+, Firefox 121+). The browser's own
|
|
56
|
+
fragment-navigation scroll then lands the carousel on the right slide,
|
|
57
|
+
because the target lives inside `.carousel__track`.
|
|
58
|
+
|
|
59
|
+
`:not([open])` is load-bearing: once `<wc-gallery>` opens this for real via
|
|
60
|
+
`showModal()`, `[open]` is present and this rule must yield to it — a stale
|
|
61
|
+
`:target` from a previous no-JS visit must not fight the real modal, and
|
|
62
|
+
must not keep the dialog "open" by this rule alone once JS is stepping
|
|
63
|
+
between slides (which never changes the URL fragment). */
|
|
64
|
+
&:not([open]):has(:target) {
|
|
65
|
+
display: grid;
|
|
66
|
+
position: fixed;
|
|
67
|
+
inset: 0;
|
|
68
|
+
z-index: var(--lightbox-z, 100);
|
|
50
69
|
}
|
|
51
70
|
|
|
52
|
-
.lightbox-
|
|
71
|
+
.lightbox-dialog__track {
|
|
53
72
|
max-inline-size: 100%;
|
|
54
|
-
max-block-size:
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
`
|
|
61
|
-
|
|
62
|
-
|
|
73
|
+
max-block-size: 100%;
|
|
74
|
+
|
|
75
|
+
/* `.carousel__media`'s own rule reads this for its `object-fit` (default
|
|
76
|
+
`cover`, right for a thumbnail strip, wrong for a full image — nothing
|
|
77
|
+
here should be cropped). Nesting under `dialog.lightbox-dialog` gives
|
|
78
|
+
this whole block higher specificity than carousel.css's bare
|
|
79
|
+
`.carousel__media` rule regardless of import order, so it always wins. */
|
|
80
|
+
--carousel-media-fit: contain;
|
|
81
|
+
|
|
82
|
+
.carousel__track {
|
|
83
|
+
max-block-size: 100%;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/* Breathing room against the edge-to-edge dialog, on the SLIDE rather than
|
|
87
|
+
the media box — a slide is the grid item `align-items: stretch` fills to
|
|
88
|
+
the track's full height, so padding here is what keeps the image off the
|
|
89
|
+
glass instead of the media box's aspect-ratio doing it. */
|
|
90
|
+
.carousel__slide {
|
|
91
|
+
box-sizing: border-box;
|
|
92
|
+
padding: 5vh 5vw;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
.carousel__media {
|
|
96
|
+
/* Full images vary wildly in ratio; let each fill its slide instead of
|
|
97
|
+
carousel.css's fixed 16/9 box (right for a thumbnail strip, wrong
|
|
98
|
+
here). */
|
|
99
|
+
aspect-ratio: unset;
|
|
100
|
+
block-size: 100%;
|
|
101
|
+
|
|
102
|
+
/* Painted the instant a slide is visible, whether or not the full image
|
|
103
|
+
has loaded yet — the morph target is never an empty box. Set per-slide
|
|
104
|
+
by `<Gallery />` from the thumbnail URL; declared as a var rather than
|
|
105
|
+
a bare property so a slide that omits a thumb (no placeholder) simply
|
|
106
|
+
paints nothing extra. */
|
|
107
|
+
background-image: var(--lightbox-placeholder, none);
|
|
108
|
+
background-size: contain;
|
|
109
|
+
background-position: center;
|
|
110
|
+
background-repeat: no-repeat;
|
|
111
|
+
|
|
112
|
+
/* `<wc-gallery>` assigns `view-transition-name: lightbox-active` to the
|
|
113
|
+
slide's `<img>` and the originating thumbnail at the moment it opens,
|
|
114
|
+
then clears it on close — that shared, runtime-assigned name is what
|
|
115
|
+
morphs one into the other. `match-element` (the auto-generated
|
|
116
|
+
per-element name) does NOT do this: it names each element uniquely,
|
|
117
|
+
so it can never morph a thumbnail into a different `<img>`. Do not put
|
|
118
|
+
a `view-transition-name` on the image here statically. */
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
.carousel__caption {
|
|
122
|
+
color: inherit;
|
|
123
|
+
}
|
|
63
124
|
}
|
|
64
125
|
|
|
65
126
|
.lightbox-dialog__close {
|
|
@@ -77,6 +138,9 @@ dialog.lightbox-dialog {
|
|
|
77
138
|
display: flex;
|
|
78
139
|
align-items: center;
|
|
79
140
|
justify-content: center;
|
|
141
|
+
text-decoration: none;
|
|
142
|
+
font-size: 1.5rem;
|
|
143
|
+
line-height: 1;
|
|
80
144
|
transition: background-color 0.15s ease;
|
|
81
145
|
z-index: 10;
|
|
82
146
|
|
|
@@ -88,50 +152,4 @@ dialog.lightbox-dialog {
|
|
|
88
152
|
outline-offset: 2px;
|
|
89
153
|
}
|
|
90
154
|
}
|
|
91
|
-
|
|
92
|
-
.lightbox-dialog__nav {
|
|
93
|
-
position: absolute;
|
|
94
|
-
inset-block: 0;
|
|
95
|
-
display: flex;
|
|
96
|
-
align-items: center;
|
|
97
|
-
padding: 1rem;
|
|
98
|
-
|
|
99
|
-
&[data-direction='prev'] {
|
|
100
|
-
inset-inline-start: 0;
|
|
101
|
-
}
|
|
102
|
-
&[data-direction='next'] {
|
|
103
|
-
inset-inline-end: 0;
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
.lightbox-dialog__nav-button {
|
|
108
|
-
inline-size: 3rem;
|
|
109
|
-
block-size: 3rem;
|
|
110
|
-
padding: 0;
|
|
111
|
-
background: var(--lightbox-nav-bg, transparent);
|
|
112
|
-
border: none;
|
|
113
|
-
border-radius: 50%;
|
|
114
|
-
color: inherit;
|
|
115
|
-
cursor: pointer;
|
|
116
|
-
display: flex;
|
|
117
|
-
align-items: center;
|
|
118
|
-
justify-content: center;
|
|
119
|
-
transition: background-color 0.15s ease;
|
|
120
|
-
|
|
121
|
-
&:hover {
|
|
122
|
-
background: var(--lightbox-nav-hover-bg, oklch(1 0 0 / 0.15));
|
|
123
|
-
}
|
|
124
|
-
&:disabled {
|
|
125
|
-
opacity: 0.3;
|
|
126
|
-
cursor: not-allowed;
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
.lightbox-dialog__caption {
|
|
131
|
-
position: absolute;
|
|
132
|
-
inset-block-end: 0;
|
|
133
|
-
inset-inline: 0;
|
|
134
|
-
padding: 1rem 2rem;
|
|
135
|
-
text-align: center;
|
|
136
|
-
}
|
|
137
155
|
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Review — the `.review` card's structure only.
|
|
3
|
+
*
|
|
4
|
+
* Box chrome (border/padding/background/shadow) is CONFIG-GENERATED
|
|
5
|
+
* (`patterns.items.review` in design-system.json), the same split `card.css`
|
|
6
|
+
* documents. This partial holds what the framework owns: the internal layout —
|
|
7
|
+
* rating, identity, body, reply, source link.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
.review {
|
|
11
|
+
.review__header {
|
|
12
|
+
display: flex;
|
|
13
|
+
flex-wrap: wrap;
|
|
14
|
+
align-items: center;
|
|
15
|
+
gap: 0.75rem;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
.review__avatar {
|
|
19
|
+
inline-size: 2.5rem;
|
|
20
|
+
block-size: 2.5rem;
|
|
21
|
+
border-radius: 50%;
|
|
22
|
+
object-fit: cover;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/* Shown instead of `.review__avatar` when `avatars` is off (the default) — see
|
|
26
|
+
the config-generated pattern's rationale in AGENTS.md's Reviews section. */
|
|
27
|
+
.review__initials {
|
|
28
|
+
display: inline-flex;
|
|
29
|
+
align-items: center;
|
|
30
|
+
justify-content: center;
|
|
31
|
+
inline-size: 2.5rem;
|
|
32
|
+
block-size: 2.5rem;
|
|
33
|
+
border-radius: 50%;
|
|
34
|
+
background: var(--color-bg-neutral-muted);
|
|
35
|
+
color: var(--color-text-neutral);
|
|
36
|
+
font-weight: 600;
|
|
37
|
+
font-size: 0.875em;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
.review__identity {
|
|
41
|
+
display: flex;
|
|
42
|
+
flex-direction: column;
|
|
43
|
+
gap: 0.125rem;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
.review__author {
|
|
47
|
+
font-weight: 600;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
.review__date {
|
|
51
|
+
font-size: 0.875em;
|
|
52
|
+
opacity: 0.7;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
.review__stars {
|
|
56
|
+
display: inline-flex;
|
|
57
|
+
align-items: center;
|
|
58
|
+
gap: 0.125rem;
|
|
59
|
+
margin-inline-start: auto;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
.review__star--empty {
|
|
63
|
+
opacity: 0.35;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
.review__body {
|
|
67
|
+
margin: 0;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
.review__reply {
|
|
71
|
+
margin-inline-start: 1.5rem;
|
|
72
|
+
padding-inline-start: 1rem;
|
|
73
|
+
border-inline-start: 2px solid var(--color-bg-surface-muted);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
.review__reply-author {
|
|
77
|
+
font-weight: 600;
|
|
78
|
+
font-size: 0.875em;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
.review__source {
|
|
82
|
+
font-size: 0.875em;
|
|
83
|
+
align-self: start;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
.review-list {
|
|
88
|
+
display: flex;
|
|
89
|
+
flex-direction: column;
|
|
90
|
+
gap: 1.5rem;
|
|
91
|
+
}
|