srcdev-nuxt-components 9.6.18 → 9.6.20
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/.claude/rules/consumer/css-relative-colour-calc.md +15 -0
- package/.claude/rules/consumer/ids-from-useid.md +3 -2
- package/.claude/rules/consumer/vue-dynamic-arg-no-spaces.md +16 -0
- package/.claude/skills/component-compliance-checklist.md +11 -1
- package/.claude/skills/component-dynamic-slots.md +2 -2
- package/.claude/skills/component-naming.md +1 -1
- package/.claude/skills/components/accordion-chat.md +99 -0
- package/.claude/skills/components/accordion-group.md +98 -0
- package/.claude/skills/components/action-menu.md +11 -5
- package/.claude/skills/components/auto-grid.md +3 -1
- package/.claude/skills/components/capture-qr-code.md +9 -5
- package/.claude/skills/components/carousel-flip.md +41 -30
- package/.claude/skills/components/column-flow-grid.md +2 -0
- package/.claude/skills/components/dashboard-quad-grid.md +7 -2
- package/.claude/skills/components/dashboard-stats-grid.md +4 -0
- package/.claude/skills/components/decode-qr-code.md +7 -2
- package/.claude/skills/components/deep-expanding-menu-classic.md +31 -30
- package/.claude/skills/components/deep-expanding-menu.md +22 -21
- package/.claude/skills/components/expanding-panel-classic.md +9 -7
- package/.claude/skills/components/expanding-panel.md +9 -8
- package/.claude/skills/components/grid-stack.md +2 -0
- package/.claude/skills/components/layout-grid-by-cols.md +2 -0
- package/.claude/skills/components/masonry-grid.md +9 -1
- package/.claude/skills/components/page-row.md +3 -0
- package/.claude/skills/components/samaritan-prompt.md +24 -8
- package/.claude/skills/components/site-header.md +16 -2
- package/.claude/skills/components/sparkle-effect.md +79 -0
- package/.claude/skills/index.md +16 -14
- package/.claude/skills/storybook-add-story.md +33 -0
- package/.claude/skills/testing-add-unit-test.md +26 -0
- package/.claude/skills/using-component-skills.md +1 -1
- package/.vscode/srcdev-component-accordion-chat.code-snippets +42 -0
- package/.vscode/srcdev-component-accordion-group.code-snippets +38 -0
- package/.vscode/srcdev-component-action-menu.code-snippets +3 -3
- package/.vscode/srcdev-component-capture-decode-qr-code.code-snippets +11 -2
- package/.vscode/srcdev-component-carousel-flip.code-snippets +22 -4
- package/.vscode/srcdev-component-deep-expanding-menu-classic.code-snippets +32 -2
- package/.vscode/srcdev-component-deep-expanding-menu.code-snippets +31 -1
- package/.vscode/srcdev-component-samaritan-prompt.code-snippets +5 -4
- package/.vscode/srcdev-component-site-header.code-snippets +13 -1
- package/.vscode/srcdev-component-sparkle-effect.code-snippets +33 -0
- package/app/components/01.atoms/animations/sparkle-effect/CONSUMER-STYLING.md +75 -0
- package/app/components/01.atoms/animations/sparkle-effect/SparkleEffect.vue +213 -0
- package/app/components/01.atoms/animations/sparkle-effect/stories/SparkleEffect.stories.ts +303 -0
- package/app/components/01.atoms/animations/sparkle-effect/tests/SparkleEffect.spec.ts +150 -0
- package/app/components/01.atoms/content-wrappers/docs-pages/ContentDocs.vue +1 -1
- package/app/components/01.atoms/content-wrappers/layout-grid/dashboard-quad-grid/CONSUMER-STYLING.md +12 -2
- package/app/components/01.atoms/content-wrappers/layout-grid/dashboard-quad-grid/DashboardQuadGrid.vue +19 -17
- package/app/components/01.atoms/content-wrappers/layout-grid/dashboard-quad-grid/stories/DashboardQuadGrid.stories.ts +42 -0
- package/app/components/01.atoms/content-wrappers/layout-grid/dashboard-quad-grid/tests/DashboardQuadGrid.spec.ts +31 -44
- package/app/components/01.atoms/content-wrappers/layout-grid/dashboard-stats-grid/CONSUMER-STYLING.md +13 -1
- package/app/components/01.atoms/content-wrappers/layout-grid/dashboard-stats-grid/DashboardStatsGrid.vue +40 -31
- package/app/components/01.atoms/content-wrappers/layout-grid/dashboard-stats-grid/stories/DashboardStatsGrid.stories.ts +47 -0
- package/app/components/01.atoms/content-wrappers/layout-grid/dashboard-stats-grid/tests/DashboardStatsGrid.spec.ts +51 -53
- package/app/components/01.atoms/content-wrappers/layout-grid/layout-grid-by-cols/CONSUMER-STYLING.md +3 -0
- package/app/components/01.atoms/content-wrappers/layout-grid/layout-grid-by-cols/LayoutGridByCols.vue +4 -0
- package/app/components/01.atoms/content-wrappers/layout-grid/layout-grid-by-cols/stories/LayoutGridByCols.stories.ts +47 -2
- package/app/components/01.atoms/content-wrappers/layout-grid/layout-grid-by-cols/tests/LayoutGridByCols.spec.ts +36 -23
- package/app/components/01.atoms/grids/auto-grid/AutoGrid.vue +7 -3
- package/app/components/01.atoms/grids/auto-grid/CONSUMER-STYLING.md +1 -1
- package/app/components/01.atoms/grids/auto-grid/stories/AutoGrid.stories.ts +46 -1
- package/app/components/01.atoms/grids/auto-grid/tests/AutoGrid.spec.ts +40 -27
- package/app/components/01.atoms/grids/auto-grid/tests/__snapshots__/AutoGrid.spec.ts.snap +1 -1
- package/app/components/01.atoms/grids/column-flow-grid/CONSUMER-STYLING.md +9 -0
- package/app/components/01.atoms/grids/column-flow-grid/ColumnFlowGrid.vue +3 -2
- package/app/components/01.atoms/grids/column-flow-grid/stories/ColumnFlowGrid.stories.ts +46 -0
- package/app/components/01.atoms/grids/column-flow-grid/tests/ColumnFlowGrid.spec.ts +24 -11
- package/app/components/01.atoms/grids/grid-stack/CONSUMER-STYLING.md +3 -0
- package/app/components/01.atoms/grids/grid-stack/GridStack.vue +2 -0
- package/app/components/01.atoms/grids/grid-stack/stories/GridStack.stories.ts +41 -0
- package/app/components/01.atoms/grids/grid-stack/tests/GridStack.spec.ts +26 -13
- package/app/components/01.atoms/grids/masonry-grid/CONSUMER-STYLING.md +15 -0
- package/app/components/01.atoms/grids/masonry-grid/MasonryGrid.vue +51 -31
- package/app/components/01.atoms/grids/masonry-grid/stories/MasonryGrid.stories.ts +48 -0
- package/app/components/01.atoms/grids/masonry-grid/tests/MasonryGrid.spec.ts +84 -29
- package/app/components/01.atoms/page-row/CONSUMER-STYLING.md +6 -1
- package/app/components/01.atoms/page-row/PageRow.vue +16 -15
- package/app/components/01.atoms/page-row/stories/PageRow.stories.ts +23 -0
- package/app/components/01.atoms/page-row/tests/PageRow.spec.ts +6 -5
- package/app/components/02.molecules/action-menu/ActionMenu.vue +6 -2
- package/app/components/02.molecules/action-menu/ActionMenuItem.vue +6 -3
- package/app/components/02.molecules/action-menu/CONSUMER-STYLING.md +11 -4
- package/app/components/02.molecules/action-menu/stories/ActionMenu.stories.ts +130 -18
- package/app/components/02.molecules/expandable/accordion-chat/AccordionChat.vue +227 -0
- package/app/components/02.molecules/expandable/accordion-chat/CONSUMER-STYLING.md +150 -0
- package/app/components/02.molecules/expandable/accordion-chat/stories/AccordionChat.stories.ts +347 -0
- package/app/components/02.molecules/expandable/accordion-chat/tests/AccordionChat.spec.ts +205 -0
- package/app/components/02.molecules/expandable/accordion-chat/tests/__snapshots__/AccordionChat.spec.ts.snap +40 -0
- package/app/components/02.molecules/expandable/{accordian/AccordianCore.vue → accordion-group/AccordionGroup.vue} +32 -20
- package/app/components/02.molecules/expandable/accordion-group/CONSUMER-STYLING.md +66 -0
- package/app/components/02.molecules/expandable/accordion-group/stories/AccordionGroup.stories.ts +238 -0
- package/app/components/02.molecules/expandable/accordion-group/tests/AccordionGroup.spec.ts +172 -0
- package/app/components/02.molecules/expandable/accordion-group/tests/__snapshots__/AccordionGroup.spec.ts.snap +45 -0
- package/app/components/02.molecules/expandable/expanding-panel/CONSUMER-STYLING.md +1 -0
- package/app/components/02.molecules/expandable/expanding-panel/ExpandingPanel.vue +11 -6
- package/app/components/02.molecules/expandable/expanding-panel/stories/ExpandingPanel.stories.ts +82 -1
- package/app/components/02.molecules/expandable/expanding-panel/tests/ExpandingPanel.spec.ts +24 -30
- package/app/components/02.molecules/expandable/expanding-panel/tests/__snapshots__/ExpandingPanel.spec.ts.snap +8 -8
- package/app/components/02.molecules/expandable/expanding-panel-classic/CONSUMER-STYLING.md +1 -0
- package/app/components/02.molecules/expandable/expanding-panel-classic/ExpandingPanelClassic.vue +10 -5
- package/app/components/02.molecules/expandable/expanding-panel-classic/stories/ExpandingPanelClassic.stories.ts +83 -1
- package/app/components/02.molecules/expandable/expanding-panel-classic/tests/ExpandingPanelClassic.spec.ts +24 -30
- package/app/components/02.molecules/expandable/expanding-panel-classic/tests/__snapshots__/ExpandingPanelClassic.spec.ts.snap +8 -8
- package/app/components/02.molecules/navigation/deep-expanding-menu/CONSUMER-STYLING.md +63 -44
- package/app/components/02.molecules/navigation/deep-expanding-menu/DeepExpandingMenu.vue +74 -37
- package/app/components/02.molecules/navigation/deep-expanding-menu/stories/DeepExpandingMenu.stories.ts +139 -19
- package/app/components/02.molecules/navigation/deep-expanding-menu/tests/DeepExpandingMenu.spec.ts +90 -27
- package/app/components/02.molecules/navigation/deep-expanding-menu/tests/__snapshots__/DeepExpandingMenu.spec.ts.snap +8 -8
- package/app/components/02.molecules/navigation/deep-expanding-menu-classic/CONSUMER-STYLING.md +58 -26
- package/app/components/02.molecules/navigation/deep-expanding-menu-classic/DeepExpandingMenuClassic.vue +113 -57
- package/app/components/02.molecules/navigation/deep-expanding-menu-classic/stories/DeepExpandingMenuClassic.stories.ts +139 -19
- package/app/components/02.molecules/navigation/deep-expanding-menu-classic/tests/DeepExpandingMenuClassic.spec.ts +140 -22
- package/app/components/02.molecules/navigation/deep-expanding-menu-classic/tests/__snapshots__/DeepExpandingMenuClassic.spec.ts.snap +9 -9
- package/app/components/02.molecules/qr-code/CONSUMER-STYLING.md +22 -7
- package/app/components/02.molecules/qr-code/CaptureQrCode.vue +63 -71
- package/app/components/02.molecules/qr-code/DecodeQrCode.vue +47 -10
- package/app/components/02.molecules/qr-code/stories/CaptureQrCode.stories.ts +114 -26
- package/app/components/02.molecules/qr-code/stories/DecodeQrCode.stories.ts +115 -24
- package/app/components/02.molecules/qr-code/tests/CaptureQrCode.spec.ts +124 -51
- package/app/components/02.molecules/qr-code/tests/DecodeQrCode.spec.ts +74 -25
- package/app/components/02.molecules/samaritan-prompt/CONSUMER-STYLING.md +22 -10
- package/app/components/02.molecules/samaritan-prompt/SamaritanPrompt.vue +30 -12
- package/app/components/02.molecules/samaritan-prompt/SamaritanPromptMixed.vue +34 -10
- package/app/components/02.molecules/samaritan-prompt/stories/SamaritanPrompt.stories.ts +55 -4
- package/app/components/02.molecules/samaritan-prompt/stories/SamaritanPromptMixed.stories.ts +67 -7
- package/app/components/02.molecules/samaritan-prompt/tests/SamaritanPrompt.spec.ts +113 -29
- package/app/components/02.molecules/samaritan-prompt/tests/SamaritanPromptMixed.spec.ts +92 -45
- package/app/components/03.organisms/image-galleries/carousel-flip/CONSUMER-STYLING.md +82 -29
- package/app/components/03.organisms/image-galleries/carousel-flip/CarouselFlip.vue +176 -113
- package/app/components/03.organisms/image-galleries/carousel-flip/stories/CarouselFlip.stories.ts +153 -23
- package/app/components/03.organisms/image-galleries/carousel-flip/tests/CarouselFlip.spec.ts +119 -63
- package/app/components/03.organisms/responsive-header/tests/__snapshots__/NavigationItems.spec.ts.snap +2 -2
- package/app/components/03.organisms/responsive-header/tests/__snapshots__/ResponsiveHeader.spec.ts.snap +2 -2
- package/app/components/03.organisms/services/service-detail/tests/ServiceDetail.spec.ts +8 -8
- package/app/components/03.organisms/services/service-detail/tests/__snapshots__/ServiceDetail.spec.ts.snap +4 -4
- package/app/components/03.organisms/site-header/CONSUMER-STYLING.md +41 -23
- package/app/components/03.organisms/site-header/SiteHeader.vue +16 -21
- package/app/components/03.organisms/site-header/stories/SiteHeader.stories.ts +138 -0
- package/app/components/03.organisms/site-header/tests/SiteHeader.spec.ts +53 -26
- package/app/components/03.organisms/site-header/tests/__snapshots__/SiteHeader.spec.ts.snap +2 -2
- package/app/types/components/qr-code.d.ts +12 -0
- package/app/utils/graphemes.ts +8 -0
- package/package.json +1 -1
- package/.claude/skills/components/accordian-core.md +0 -180
- package/.vscode/srcdev-component-accordian-core.code-snippets +0 -74
- package/app/components/01.atoms/animations/entry/.nuxt/app.config.mjs +0 -21
- package/app/components/01.atoms/animations/entry/.nuxt/components.d.ts +0 -66
- package/app/components/01.atoms/animations/entry/.nuxt/imports.d.ts +0 -30
- package/app/components/01.atoms/animations/entry/.nuxt/manifest/meta/test.json +0 -1
- package/app/components/01.atoms/animations/entry/.nuxt/types/app.config.d.ts +0 -35
- package/app/components/01.atoms/animations/entry/.nuxt/types/build.d.ts +0 -23
- package/app/components/01.atoms/animations/entry/.nuxt/types/builder-env.d.ts +0 -1
- package/app/components/01.atoms/animations/entry/.nuxt/types/components.d.ts +0 -71
- package/app/components/01.atoms/animations/entry/.nuxt/types/imports.d.ts +0 -386
- package/app/components/01.atoms/animations/entry/.nuxt/types/middleware.d.ts +0 -11
- package/app/components/01.atoms/animations/entry/.nuxt/types/modules.d.ts +0 -79
- package/app/components/01.atoms/animations/entry/.nuxt/types/nitro-layouts.d.ts +0 -17
- package/app/components/01.atoms/animations/entry/.nuxt/types/nitro-nuxt.d.ts +0 -64
- package/app/components/01.atoms/animations/entry/.nuxt/types/plugins.d.ts +0 -29
- package/app/components/01.atoms/animations/entry/.nuxt/types/runtime-config.d.ts +0 -32
- package/app/components/01.atoms/animations/entry/.nuxt/types/shared-imports.d.ts +0 -10
- package/app/components/01.atoms/animations/entry/.nuxt/types/vue-shim.d.ts +0 -0
- package/app/components/01.atoms/animations/entry/.nuxtrc +0 -1
- package/app/components/01.atoms/animations/entry/node_modules/.cache/nuxt/chrome-workspace.json +0 -1
- package/app/components/01.atoms/animations/entry/node_modules/.cache/vite/client/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +0 -1
- package/app/components/02.molecules/expandable/accordian/CONSUMER-STYLING.md +0 -100
- package/app/components/02.molecules/expandable/accordian/stories/AccordianCore.stories.ts +0 -272
- package/app/components/02.molecules/expandable/accordian/tests/AccordianCore.spec.ts +0 -257
- package/app/components/02.molecules/expandable/accordian/tests/__snapshots__/AccordianCore.spec.ts.snap +0 -95
- package/app/components/03.organisms/responsive-header/tests/.nuxt/app.config.mjs +0 -21
- package/app/components/03.organisms/responsive-header/tests/.nuxt/components.d.ts +0 -66
- package/app/components/03.organisms/responsive-header/tests/.nuxt/imports.d.ts +0 -30
- package/app/components/03.organisms/responsive-header/tests/.nuxt/manifest/meta/test.json +0 -1
- package/app/components/03.organisms/responsive-header/tests/.nuxt/types/app.config.d.ts +0 -35
- package/app/components/03.organisms/responsive-header/tests/.nuxt/types/build.d.ts +0 -23
- package/app/components/03.organisms/responsive-header/tests/.nuxt/types/builder-env.d.ts +0 -1
- package/app/components/03.organisms/responsive-header/tests/.nuxt/types/components.d.ts +0 -71
- package/app/components/03.organisms/responsive-header/tests/.nuxt/types/imports.d.ts +0 -386
- package/app/components/03.organisms/responsive-header/tests/.nuxt/types/middleware.d.ts +0 -11
- package/app/components/03.organisms/responsive-header/tests/.nuxt/types/modules.d.ts +0 -79
- package/app/components/03.organisms/responsive-header/tests/.nuxt/types/nitro-layouts.d.ts +0 -17
- package/app/components/03.organisms/responsive-header/tests/.nuxt/types/nitro-nuxt.d.ts +0 -64
- package/app/components/03.organisms/responsive-header/tests/.nuxt/types/plugins.d.ts +0 -29
- package/app/components/03.organisms/responsive-header/tests/.nuxt/types/runtime-config.d.ts +0 -32
- package/app/components/03.organisms/responsive-header/tests/.nuxt/types/shared-imports.d.ts +0 -10
- package/app/components/03.organisms/responsive-header/tests/.nuxt/types/vue-shim.d.ts +0 -0
- package/app/components/03.organisms/responsive-header/tests/node_modules/.cache/nuxt/chrome-workspace.json +0 -1
|
@@ -11,8 +11,10 @@ type: reference
|
|
|
11
11
|
`<details>`/`<summary>`-based fallback for [`DeepExpandingMenu`](deep-expanding-menu.md) — same
|
|
12
12
|
props, same `ResponsiveHeaderNavItem` data shape, same visual result, but built without CSS
|
|
13
13
|
anchor-positioning or the Popover API. Each nav item with `childLinks` renders as a native
|
|
14
|
-
`<details>` element
|
|
15
|
-
`onClickOutside
|
|
14
|
+
`<details>` element. Groups in one menu share a per-instance `name` (from `useId()`), so opening
|
|
15
|
+
one closes the others. A click outside the menu (`@vueuse/core` `onClickOutside`), a click
|
|
16
|
+
elsewhere inside it, Escape (focus returns to the group's `<summary>`) or following a child link
|
|
17
|
+
closes the open group.
|
|
16
18
|
|
|
17
19
|
Prefer `DeepExpandingMenu` unless the modern implementation's browser support isn't acceptable for
|
|
18
20
|
your target audience — see its own docs for the caveat.
|
|
@@ -21,7 +23,8 @@ your target audience — see its own docs for the caveat.
|
|
|
21
23
|
|
|
22
24
|
| Prop | Type | Default | Description |
|
|
23
25
|
|------|------|---------|-------------|
|
|
24
|
-
| `tag` | `"div" \| "section" \| "nav"
|
|
26
|
+
| `tag` | `"div" \| "section" \| "nav"` | `"nav"` | Root element tag. `ul`/`ol` were dropped 2026-10-21: the root wraps a `<div>`, so they rendered invalid HTML. |
|
|
27
|
+
| `ariaLabel` | `string` | `undefined` | Accessible name on the root. Set it when the page has more than one `<nav>`. |
|
|
25
28
|
| `navLinks` | `ResponsiveHeaderNavItem[]` | `[]` | Top-level nav items. An item with `path` renders as a direct link; an item with `childLinks` renders as a `<details>` group. |
|
|
26
29
|
| `styleClassPassthrough` | `string \| string[]` | `[]` | Extra classes applied to the root element. |
|
|
27
30
|
|
|
@@ -53,37 +56,35 @@ const navLinks: ResponsiveHeaderNavItem[] = [
|
|
|
53
56
|
|
|
54
57
|
## CSS custom properties
|
|
55
58
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
| `--deep-expanding-menu-classic-panel-shadow` | `0 0 1rem rgba(0, 0, 0, 0.1)` | Panel box-shadow |
|
|
73
|
-
| `--deep-expanding-menu-classic-panel-padding` | `1.2rem` | Panel padding |
|
|
74
|
-
| `--deep-expanding-menu-classic-panel-heading-colour` | `var(--slate-10)` | `childLinksTitle` heading colour |
|
|
75
|
-
| `--deep-expanding-menu-classic-panel-list-gap` | `1.2rem` | Gap between child link grid items |
|
|
76
|
-
| `--deep-expanding-menu-classic-group-link-colour` | `var(--slate-10)` | Child link text colour |
|
|
77
|
-
| `--deep-expanding-menu-classic-group-link-border-colour-hover` | `var(--slate-10)` | Child link bottom border colour on hover/focus |
|
|
78
|
-
|
|
79
|
-
See `CONSUMER-STYLING.md` for the full token API.
|
|
59
|
+
Public tokens are all `--deep-expanding-menu-classic-*`: item gap, link border/padding/hover
|
|
60
|
+
colour, focus ring, icon size, and the panel's offset, three container-width tiers, colours,
|
|
61
|
+
border, radius, shadow, padding, heading line clamp, list gap and child link colours. Colours
|
|
62
|
+
default from `--theme-text`, `--theme-border`, `--theme-surface-subtle` and `--theme-ring`. The
|
|
63
|
+
full table with defaults, the prefixed class names and the sizing model are in this component's
|
|
64
|
+
`CONSUMER-STYLING.md`.
|
|
65
|
+
|
|
66
|
+
## Data handling
|
|
67
|
+
|
|
68
|
+
- An item with `path` is a link; anything else is a `<details>` group. An empty `path` is a group.
|
|
69
|
+
- A group's heading renders only when `childLinksTitle` is non-empty, and its list only when
|
|
70
|
+
`childLinks` has items.
|
|
71
|
+
- Child links are keyed on index + name, so duplicate names are safe.
|
|
72
|
+
- Long or unbroken labels wrap, the top-level row wraps onto more lines, and each panel is capped
|
|
73
|
+
at the menu's width. A panel opens from its toggle's left edge and doesn't flip, so near the
|
|
74
|
+
right edge it can overrun the menu. The `StressTest` story covers all of these.
|
|
80
75
|
|
|
81
76
|
## Notes
|
|
82
77
|
|
|
83
78
|
- Auto-imported in Nuxt — no manual import needed.
|
|
84
|
-
-
|
|
85
|
-
|
|
86
|
-
|
|
79
|
+
- Until 2026-10-21 every instance used the fixed `name="navigation-group"`, so all menus on a page
|
|
80
|
+
formed one exclusive group (opening a panel in one menu closed another menu's), and the doc
|
|
81
|
+
wrongly said siblings didn't auto-close. Click-outside listeners were also bound per group in
|
|
82
|
+
`onMounted`, so groups added later never closed.
|
|
83
|
+
- 2026-10-21 migration: prefixed every structural class, moved colours to `--theme-*`, panel
|
|
84
|
+
offset to `calc(100% + 1rem)`, panel widths from viewport `@media` to `@container`, removed
|
|
85
|
+
the no-op `--deep-expanding-menu-classic-gap` and a `display: flex !important`, added
|
|
86
|
+
`ariaLabel`, Escape and link-follow closing, a `:focus-visible` ring, `role="list"`, an
|
|
87
|
+
`aria-hidden` caret, the heading line-clamp token and the `StressTest` story.
|
|
87
88
|
- 2026-09-07 migration: moved from an unplaced top-level folder (was `DeepExpandingMenuOld.vue`)
|
|
88
89
|
into `02.molecules/navigation/deep-expanding-menu-classic/`; converted options-style
|
|
89
90
|
`defineProps` to `interface Props` + `withDefaults`; switched its locally-declared
|
|
@@ -24,7 +24,8 @@ built on `<details>`/`<summary>`: same API and visual result, with no popover la
|
|
|
24
24
|
|
|
25
25
|
| Prop | Type | Default | Description |
|
|
26
26
|
|------|------|---------|-------------|
|
|
27
|
-
| `tag` | `"div" \| "section" \| "nav"
|
|
27
|
+
| `tag` | `"div" \| "section" \| "nav"` | `"nav"` | Root element tag. `ul`/`ol` were dropped 2026-10-21: the root wraps a `<div>`, so they rendered invalid HTML. |
|
|
28
|
+
| `ariaLabel` | `string` | `undefined` | Accessible name on the root. Set it when the page has more than one `<nav>`. |
|
|
28
29
|
| `navLinks` | `ResponsiveHeaderNavItem[]` | `[]` | Top-level nav items. An item with `path` renders as a direct link; an item with `childLinks` renders as a toggle + popover panel. |
|
|
29
30
|
| `styleClassPassthrough` | `string \| string[]` | `[]` | Extra classes applied to the root element. |
|
|
30
31
|
|
|
@@ -56,26 +57,22 @@ const navLinks: ResponsiveHeaderNavItem[] = [
|
|
|
56
57
|
|
|
57
58
|
## CSS custom properties
|
|
58
59
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
| `--deep-expanding-menu-group-link-colour` | `var(--slate-10)` | Child link text colour |
|
|
76
|
-
| `--deep-expanding-menu-group-link-border-colour-hover` | `var(--slate-10)` | Child link bottom border colour on hover/focus |
|
|
77
|
-
|
|
78
|
-
See `CONSUMER-STYLING.md` for the full token API and what's not tokenised.
|
|
60
|
+
Public tokens are all `--deep-expanding-menu-*`: row gap, link border/padding/hover colour, focus
|
|
61
|
+
ring, icon size, and the panel's width, max height, colours, border, radius, shadow, padding,
|
|
62
|
+
heading line clamp, list gap and child link colours. Colours default from `--theme-text`,
|
|
63
|
+
`--theme-border`, `--theme-surface-subtle` and `--theme-ring`. The full table with defaults, the
|
|
64
|
+
prefixed class names and the state hooks are in this component's `CONSUMER-STYLING.md`.
|
|
65
|
+
|
|
66
|
+
## Data handling
|
|
67
|
+
|
|
68
|
+
- An item with `path` is a link; anything else is a group (toggle + panel). An empty `path` is a group.
|
|
69
|
+
- A group's heading renders only when `childLinksTitle` is non-empty, and its list only when
|
|
70
|
+
`childLinks` has items, so an empty group opens a panel with just its heading.
|
|
71
|
+
- Child links are keyed on index + name, so duplicate names are safe.
|
|
72
|
+
- Long or unbroken labels wrap (`overflow-wrap: anywhere`), the top-level row wraps onto more
|
|
73
|
+
lines, panel lists longer than `--deep-expanding-menu-panel-max-height` scroll, and a panel near
|
|
74
|
+
the right edge flips to align with its toggle's right edge where anchor positioning is supported.
|
|
75
|
+
The `StressTest` story covers all of these.
|
|
79
76
|
|
|
80
77
|
## Notes
|
|
81
78
|
|
|
@@ -92,6 +89,10 @@ See `CONSUMER-STYLING.md` for the full token API and what's not tokenised.
|
|
|
92
89
|
`aria-expanded` revealing ordinary links), not an ARIA menu. `role="menu"` was removed 2026-10-05
|
|
93
90
|
because it requires `menuitem` children and arrow-key handling the panels don't have, so screen
|
|
94
91
|
readers announced a menu that didn't behave like one. Don't add it back.
|
|
92
|
+
- 2026-10-21 migration: prefixed every structural class (`.inner`, `.navigation-*`, `.icon` became
|
|
93
|
+
`.deep-expanding-menu-*`), moved colour defaults to `--theme-*` tokens, added `ariaLabel`, a
|
|
94
|
+
`:focus-visible` ring, `role="list"` on the child list, an `aria-hidden` caret, the heading
|
|
95
|
+
line-clamp and max-height tokens, `position-try-fallbacks: flip-inline`, and the `StressTest` story.
|
|
95
96
|
- 2026-10-05: removed two never-referenced `@position-try` blocks (`--anchor-left`, and an invalid
|
|
96
97
|
`@position-try-fallbacks --anchor-right` at-rule).
|
|
97
98
|
- 2026-09-07 migration: moved from an unplaced top-level folder into
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
**Prefer [`ExpandingPanel`](expanding-panel.md) unless you have a concrete reason not to.** `ExpandingPanelClassic` is the pre-`::details-content` implementation, kept only as a fallback for consumers who need the open/close animation to work identically in every browser today (its `grid-template-rows: 0fr → 1fr` sibling trick has no Baseline-2025 feature dependency). `ExpandingPanel` looks and behaves identically everywhere too — the only difference is that its animation degrades to an instant (unanimated) open/close in browsers without `interpolate-size` support (Chromium only, as of 2026), where this component still animates.
|
|
6
6
|
|
|
7
|
-
`ExpandingPanelClassic` is a single expand/collapse panel built on the native `<details>`/`<summary>` element. It animates open/close via a CSS grid-template-rows trick, supports `v-model` for controlled state, and can be locked open with `forceOpened`. Multiple panels can be grouped into a native accordion by sharing the same `name` prop (see `
|
|
7
|
+
`ExpandingPanelClassic` is a single expand/collapse panel built on the native `<details>`/`<summary>` element. It animates open/close via a CSS grid-template-rows trick, supports `v-model` for controlled state, and can be locked open with `forceOpened`. Multiple panels can be grouped into a native accordion by sharing the same `name` prop (see `AccordionGroup`).
|
|
8
8
|
|
|
9
9
|
---
|
|
10
10
|
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
|
|
13
13
|
| Prop | Type | Default | Notes |
|
|
14
14
|
|------|------|---------|-------|
|
|
15
|
-
| `name` | `string` | `useId()` |
|
|
15
|
+
| `name` | `string` | `useId()` | The `<details>` `name`; panels sharing it form an exclusive-open group. Not used for element ids (each panel gets its own `useId()`). If omitted, a unique name is generated automatically. |
|
|
16
16
|
| `animationDuration` | `number` | `400` | Expand/collapse transition duration in milliseconds. Pass `0` to disable animation. |
|
|
17
17
|
| `forceOpened` | `boolean` | `false` | When `true`, the panel is always open. The toggle icon is hidden and clicks do not close the panel. |
|
|
18
18
|
| `contentIsOnTop` | `boolean` | `false` | When `true`, the content region is taken out of flow and absolutely positioned directly below the summary, raised above surrounding page content via `z-index` — instead of pushing layout down when it opens. Applies a `content-is-on-top` class to the root `.expanding-panel-classic` element (not the content div). Also enables click-outside-to-close (see below) — not applied when `forceOpened` is `true`. |
|
|
@@ -132,11 +132,11 @@ The component wires ARIA automatically from the `name` prop:
|
|
|
132
132
|
|
|
133
133
|
| Element | Attribute | Value |
|
|
134
134
|
|---------|-----------|-------|
|
|
135
|
-
| `<summary>` | `id` | `
|
|
136
|
-
| `<summary>` | `aria-controls` | `
|
|
135
|
+
| `<summary>` | `id` | `{useId()}-trigger` |
|
|
136
|
+
| `<summary>` | `aria-controls` | `{useId()}-content` |
|
|
137
137
|
| `<summary>` | `aria-expanded` | `true` / `false` |
|
|
138
|
-
| content div | `id` | `
|
|
139
|
-
| content div | `aria-labelledby` | `
|
|
138
|
+
| content div | `id` | `{useId()}-content` |
|
|
139
|
+
| content div | `aria-labelledby` | `{useId()}-trigger` |
|
|
140
140
|
| content div | `role` | `region` |
|
|
141
141
|
|
|
142
142
|
Always supply a meaningful `name` prop when using multiple panels on the same page to avoid duplicate IDs.
|
|
@@ -213,9 +213,11 @@ See [component-local-style-override.md](../component-local-style-override.md) fo
|
|
|
213
213
|
|
|
214
214
|
## Notes
|
|
215
215
|
|
|
216
|
+
- 2026-10-20: element ids come from a per-panel `useId()` instead of `name`. Panels sharing a `name` (an exclusive group, ResponsiveHeader's overflow groups) all rendered the same `id-{name}-trigger`/`-content`, so every `aria-controls` pointed at the first panel. The same pass made the summary track `minmax(0, 1fr) auto` and added `overflow-wrap: anywhere` on the root, so a long unbroken summary wraps instead of pushing the caret out of the clipped summary. The `StressTest` story covers long, RTL, emoji and empty-slot cases.
|
|
216
217
|
- The open/close animation uses `grid-template-rows: 0fr → 1fr` — no JS height measurement needed.
|
|
217
218
|
- `content-is-on-top` is applied to the root `.expanding-panel-classic` element, not `.expanding-panel-classic-content` — style overrides must scope through it, e.g. `.expanding-panel-classic.my-panel .expanding-panel-classic-content .inner { ... }`.
|
|
218
219
|
- When `forceOpened` is `true`, `open` stays `true` regardless of `v-model`, but `v-model` still updates internally on clicks (useful if you later set `forceOpened` back to `false`).
|
|
219
|
-
- Group panels into a native accordion (only one open at a time) by passing the same `name` to multiple panels or use `
|
|
220
|
+
- Group panels into a native accordion (only one open at a time) by passing the same `name` to multiple panels or use `AccordionGroup` which handles this automatically.
|
|
221
|
+
- **Used by:** `AccordionGroup` (`variant="classic"`, the default), `AccordionChat` (restyles the summary as a question bubble and wraps the content in an answer bubble; never touches `.inner`), `ContentDocs`, `ResponsiveHeader` and `NavigationItems` (`panelVariant="classic"`).
|
|
220
222
|
- Auto-imported in Nuxt — no manual import needed.
|
|
221
223
|
- File: `app/components/02.molecules/expandable/expanding-panel-classic/ExpandingPanelClassic.vue`
|
|
@@ -2,11 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
## Overview
|
|
4
4
|
|
|
5
|
-
`ExpandingPanel` is a single expand/collapse panel built on the native `<details>`/`<summary>` element. It animates open/close by styling the `::details-content` pseudo-element (the browser's own anonymous box wrapping everything after `<summary>`) where supported, supports `v-model` for controlled state, and can be locked open with `forceOpened`. Multiple panels can be grouped into a native accordion by sharing the same `name` prop (see `
|
|
5
|
+
`ExpandingPanel` is a single expand/collapse panel built on the native `<details>`/`<summary>` element. It animates open/close by styling the `::details-content` pseudo-element (the browser's own anonymous box wrapping everything after `<summary>`) where supported, supports `v-model` for controlled state, and can be locked open with `forceOpened`. Multiple panels can be grouped into a native accordion by sharing the same `name` prop (see `AccordionGroup`).
|
|
6
6
|
|
|
7
7
|
**WebKit (Safari/iOS) does not support `::details-content`** as of 2026-08, confirmed via real-device testing — Chrome's device-emulation mode cannot catch this, since it's still the Blink engine underneath regardless of which browser UI it's simulating. WebKit silently drops the whole rule block: no animation and (more seriously) `content-is-on-top`'s `position: absolute` never applying, so an overlay panel renders in normal document flow and can break the surrounding layout. An `@supports not selector(::details-content)` CSS-only fallback was tried (2026-08-22) but didn't reliably kick in on real devices either and was reverted the same day — **do not reintroduce that approach**.
|
|
8
8
|
|
|
9
|
-
**The actual fix is component-level, not CSS-level**: every consumer that gave `ExpandingPanel` a choice of implementation now defaults to `ExpandingPanelClassic` instead — see `
|
|
9
|
+
**The actual fix is component-level, not CSS-level**: every consumer that gave `ExpandingPanel` a choice of implementation now defaults to `ExpandingPanelClassic` instead — see `AccordionGroup`'s `variant` prop (default `"classic"`), `ContentDocs`'s `panelVariant` prop (default `"classic"`), and `ResponsiveHeader`/`NavigationItems`'s `panelVariant` prop (default `"classic"`). `ExpandingPanel` itself is unchanged (still `::details-content`-only, no fallback) and logs a `console.warn` in dev mode on every mount warning about this. Reach for `ExpandingPanel` directly, or pass `variant`/`panelVariant: "modern"` to one of the above, only once you've confirmed it's acceptable for your actual audience — it is **not** the safe default anywhere in this library anymore. See pitfall #19 in the repo's `CLAUDE.md` for the full history.
|
|
10
10
|
|
|
11
11
|
---
|
|
12
12
|
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
|
|
15
15
|
| Prop | Type | Default | Notes |
|
|
16
16
|
|------|------|---------|-------|
|
|
17
|
-
| `name` | `string` | `useId()` |
|
|
17
|
+
| `name` | `string` | `useId()` | The `<details>` `name`; panels sharing it form an exclusive-open group. Not used for element ids (each panel gets its own `useId()`). If omitted, a unique name is generated automatically. |
|
|
18
18
|
| `animationDuration` | `number` | `400` | Expand/collapse transition duration in milliseconds. Pass `0` to disable animation. |
|
|
19
19
|
| `forceOpened` | `boolean` | `false` | When `true`, the panel is always open. The toggle icon is hidden and clicks do not close the panel. |
|
|
20
20
|
| `contentIsOnTop` | `boolean` | `false` | When `true`, the content region is taken out of flow and absolutely positioned directly below the summary, raised above surrounding page content via `z-index` — instead of pushing layout down when it opens. Applies a `content-is-on-top` class to the root `.expanding-panel` element (not the content div). Also enables click-outside-to-close (see below) — not applied when `forceOpened` is `true`. |
|
|
@@ -132,11 +132,11 @@ The component wires ARIA automatically from the `name` prop:
|
|
|
132
132
|
|
|
133
133
|
| Element | Attribute | Value |
|
|
134
134
|
|---------|-----------|-------|
|
|
135
|
-
| `<summary>` | `id` | `
|
|
136
|
-
| `<summary>` | `aria-controls` | `
|
|
135
|
+
| `<summary>` | `id` | `{useId()}-trigger` |
|
|
136
|
+
| `<summary>` | `aria-controls` | `{useId()}-content` |
|
|
137
137
|
| `<summary>` | `aria-expanded` | `true` / `false` |
|
|
138
|
-
| content div | `id` | `
|
|
139
|
-
| content div | `aria-labelledby` | `
|
|
138
|
+
| content div | `id` | `{useId()}-content` |
|
|
139
|
+
| content div | `aria-labelledby` | `{useId()}-trigger` |
|
|
140
140
|
| content div | `role` | `region` |
|
|
141
141
|
|
|
142
142
|
Always supply a meaningful `name` prop when using multiple panels on the same page to avoid duplicate IDs.
|
|
@@ -213,9 +213,10 @@ See [component-local-style-override.md](../component-local-style-override.md) fo
|
|
|
213
213
|
|
|
214
214
|
## Notes
|
|
215
215
|
|
|
216
|
+
- 2026-10-20: element ids come from a per-panel `useId()` instead of `name`. Panels sharing a `name` (an exclusive group, ResponsiveHeader's overflow groups) all rendered the same `id-{name}-trigger`/`-content`, so every `aria-controls` pointed at the first panel. The same pass made the summary track `minmax(0, 1fr) auto` and added `overflow-wrap: anywhere` on the root, so a long unbroken summary wraps instead of pushing the caret out of the clipped summary. The `StressTest` story covers long, RTL, emoji and empty-slot cases.
|
|
216
217
|
- The open/close animation targets `::details-content` (`height: 0 → auto`, `overflow: clip`) — no JS height measurement needed. Requires `interpolate-size: allow-keywords` for the height transition itself (Chromium only as of 2026; other browsers with `::details-content` support still toggle correctly, just instantly). WebKit has neither, and there is no automatic fallback — use `ExpandingPanelClassic` (see the Overview section above) for anything that needs to work correctly there.
|
|
217
218
|
- `content-is-on-top` is applied to the root `.expanding-panel` element, not `.expanding-panel-content` — style overrides must scope through it, e.g. `.expanding-panel.my-panel .expanding-panel-content { ... }`.
|
|
218
219
|
- When `forceOpened` is `true`, `open` stays `true` regardless of `v-model`, but `v-model` still updates internally on clicks (useful if you later set `forceOpened` back to `false`).
|
|
219
|
-
- Group panels into a native accordion (only one open at a time) by passing the same `name` to multiple panels or use `
|
|
220
|
+
- Group panels into a native accordion (only one open at a time) by passing the same `name` to multiple panels or use `AccordionGroup` which handles this automatically.
|
|
220
221
|
- Auto-imported in Nuxt — no manual import needed.
|
|
221
222
|
- File: `app/components/02.molecules/expandable/expanding-panel/ExpandingPanel.vue`
|
|
@@ -134,6 +134,8 @@ consumer's `<style scoped>` file, `.grid-stack__layer` rules need `:deep()`. Ful
|
|
|
134
134
|
|
|
135
135
|
## Notes
|
|
136
136
|
|
|
137
|
+
- The `StressTest` story stacks an overlay of unbroken, long and right-to-left text over an image layer.
|
|
138
|
+
|
|
137
139
|
- `pointer-events: none` on overlay layers (and `pointer-events: auto` on interactive children within them) is the standard pattern for overlays that shouldn't block interaction with layers behind them.
|
|
138
140
|
- There is no built-in `z-index` — stacking is handled purely by DOM order. If a consumer applies `z-index` on a layer for other reasons, be aware it creates a new stacking context.
|
|
139
141
|
- The slot name is used as the Vue `:key` on the layer wrapper, so slot names must be unique.
|
|
@@ -130,6 +130,8 @@ Tokens: `--layout-grid-by-cols-column-count`, `-single-col-below`, `-gap`, `-row
|
|
|
130
130
|
|
|
131
131
|
## Notes
|
|
132
132
|
|
|
133
|
+
- The `StressTest` story feeds every cell oversized content (an unbroken string and URL, long German, a 1600px image, emoji, right-to-left, an empty cell).
|
|
134
|
+
|
|
133
135
|
- Auto-imported in Nuxt — no manual import needed.
|
|
134
136
|
- Slot names can be anything — semantic or indexed. Document order determines render order.
|
|
135
137
|
- 2026-09-27 migration: `singleColBelow` never worked before (the breakpoint was a hardcoded `768px` container query); it does now. `gap`/`columnCount` moved from `v-bind()` to public tokens, and the layout props lost their defaults (defaults now live in CSS). A `section` without a `label` used to read the placeholder "If tag='section' then a label is required" to screen readers; it now renders nothing and relies on the composable's dev warning. `gap` must be a single length (compound values were claimed before but break the column calculation). Inner class `.layout-grid-inner` → `.layout-grid-by-cols-inner`; root no longer a `layoutGrid` size container. No consumer app used the component at the time.
|
|
@@ -98,9 +98,17 @@ See `CONSUMER-STYLING.md` in the component folder.
|
|
|
98
98
|
|
|
99
99
|
## Notes
|
|
100
100
|
|
|
101
|
+
- 2026-10-21: tiles re-pack when any tile changes height (a `ResizeObserver` per tile, throttled to
|
|
102
|
+
one `requestAnimationFrame`), not only when the container width changes, so a late-loading image
|
|
103
|
+
no longer overlaps the tile below. Slot names are read per render (they were a `computed` over
|
|
104
|
+
`useSlots()`, which missed slots added after mount), and tiles are read in DOM order from the
|
|
105
|
+
wrapper instead of a `v-for` ref array, which Vue doesn't keep in DOM order. The multi-column state
|
|
106
|
+
is `data-multiple-cols` and the private tokens are prefixed `--_masonry-grid-*`.
|
|
107
|
+
- The `StressTest` story mixes unbroken, long, image, emoji, right-to-left, empty and one-character tiles.
|
|
108
|
+
|
|
101
109
|
- **How positioning works**: on mount and on every measured resize, each item is read (`item.
|
|
102
110
|
offsetHeight`) in DOM order and placed at the top of whichever column currently has the least
|
|
103
|
-
accumulated height, via inline `--
|
|
111
|
+
accumulated height, via inline `--_masonry-grid-item-top`/`--_masonry-grid-item-left`/`--_masonry-grid-item-width` custom
|
|
104
112
|
properties the component sets directly (not part of the public token API — these are internal
|
|
105
113
|
positioning state, not a styling surface). Column steps are computed in real pixels
|
|
106
114
|
(`itemWidth + gap`) rather than equal percentage slots of the container — a percentage slot only
|
|
@@ -24,6 +24,9 @@ no extra wrapper divs needed.
|
|
|
24
24
|
|
|
25
25
|
---
|
|
26
26
|
|
|
27
|
+
The `variant` prop renders as `data-variant` on the root (a bare class like `.content` until
|
|
28
|
+
2026-10-21). The `StressTest` story nests every variant with oversized content.
|
|
29
|
+
|
|
27
30
|
## Track widths
|
|
28
31
|
|
|
29
32
|
| Variant | Max width | Typical use |
|
|
@@ -17,7 +17,9 @@ effects, styled as a monospace line over an underline with a pulsing cursor glyp
|
|
|
17
17
|
|
|
18
18
|
Effects:
|
|
19
19
|
|
|
20
|
-
- `typewriter` — characters typed one by one, held, deleted one by one.
|
|
20
|
+
- `typewriter` — characters typed one by one, held, deleted one by one. "Characters" are graphemes
|
|
21
|
+
(`splitGraphemes()` in `app/utils/graphemes.ts`, `Intl.Segmenter`), so an emoji with a skin tone
|
|
22
|
+
or a family emoji never shows half a glyph.
|
|
21
23
|
- `word-pulse` — the whole message fades in, holds, fades out.
|
|
22
24
|
|
|
23
25
|
Both use `useCancellableTimer` for all timing, so the loop exits cleanly on unmount.
|
|
@@ -31,8 +33,9 @@ Both use `useCancellableTimer` for all timing, so the loop exits cleanly on unmo
|
|
|
31
33
|
deletes or fades out, so a repeated message announces again.
|
|
32
34
|
- **Pause on hover** (WCAG 2.2.2): pointer over the root pauses the sequence where it is and the
|
|
33
35
|
cursor pulse; leaving resumes. The root gets `data-paused` while paused.
|
|
34
|
-
- `prefers-reduced-motion: reduce` removes the cursor pulse and the word-pulse fade transition
|
|
35
|
-
|
|
36
|
+
- `prefers-reduced-motion: reduce` removes the cursor pulse and the word-pulse fade transition, and
|
|
37
|
+
the typewriter shows each message whole instead of typing and deleting it (since 2026-10-21). The
|
|
38
|
+
message sequence keeps running (it's the content).
|
|
36
39
|
|
|
37
40
|
> **Changed 2026-09-27**: `SamaritanPrompt` previously had no live region (the visible text was read
|
|
38
41
|
> character by character, if at all) and no reduced-motion handling; both now match
|
|
@@ -123,9 +126,13 @@ props on the component tag are hyphenated.
|
|
|
123
126
|
|
|
124
127
|
Full token list and class hooks: `app/components/02.molecules/samaritan-prompt/CONSUMER-STYLING.md`.
|
|
125
128
|
Tokens are `--samaritan-prompt-*` (text/underline/cursor colours, font, sizes, gaps, pulse
|
|
126
|
-
duration
|
|
127
|
-
|
|
128
|
-
|
|
129
|
+
duration, white-space, line clamp). Colours default to `--theme-text` (text, underline) and
|
|
130
|
+
`--theme-accent` (cursor); set the three colour tokens to `#fff`, `#fff`, `#cc0000` for the classic
|
|
131
|
+
white-on-black look. Text wraps by default (`--samaritan-prompt-text-white-space: nowrap` restores
|
|
132
|
+
one line).
|
|
133
|
+
|
|
134
|
+
> **Changed 2026-10-21**: colour defaults were `#ffffff` text/underline and a `#cc0000` cursor on the
|
|
135
|
+
> assumption of a dark page, and text was always `nowrap`, so a long message overflowed.
|
|
129
136
|
|
|
130
137
|
The component brings no page layout; centre it from its parent.
|
|
131
138
|
|
|
@@ -134,7 +141,16 @@ The component brings no page layout; centre it from its parent.
|
|
|
134
141
|
|
|
135
142
|
## Notes
|
|
136
143
|
|
|
144
|
+
- Both stories files have a `StressTest` story (long German, unbroken URL, empty message, emoji,
|
|
145
|
+
HTML-like text, right-to-left, single character) with the `CanvasSwitcher` decorator.
|
|
146
|
+
|
|
137
147
|
- `SamaritanPromptMixed` reads `messageConfigs` at the start of each loop, so prop changes land on
|
|
138
|
-
the next loop.
|
|
148
|
+
the next loop. With an empty list it polls every 500 ms (or `introDelay`, if longer) and starts
|
|
149
|
+
once messages arrive; until 2026-10-21 an empty list with `introDelay: 0` spun forever and froze
|
|
150
|
+
the tab.
|
|
151
|
+
- `SamaritanPrompt` restarts when `effect` or the `messages` values change (compared by value, so
|
|
152
|
+
an inline array literal re-created on every parent render doesn't restart it). Until 2026-10-21 it
|
|
153
|
+
only restarted on `effect`, and an empty string in `messages` (or a list that shrank mid-cycle)
|
|
154
|
+
stopped the loop for good.
|
|
139
155
|
- The `Mono MMM 5` font is declared with `@font-face` in each component's unscoped style from
|
|
140
|
-
`/fonts/monoMMM_5.ttf` (`font-display: swap` in
|
|
156
|
+
`/fonts/monoMMM_5.ttf` (`font-display: swap` in both since 2026-10-21; `SamaritanPromptMixed` used `optional`).
|
|
@@ -23,7 +23,7 @@ Branding (logo/wordmark) is entirely consumer-authored via the `#branding` slot
|
|
|
23
23
|
| `pageRowVariant` | `"full" \| "popout" \| "content" \| "inset-content"` | `"content"` | Forwarded to the root `PageRow`'s `variant` prop. |
|
|
24
24
|
| `styleClassPassthrough` | `string \| string[]` | `[]` | Extra CSS classes on the root `PageRow` — targets `--site-header-*` tokens (see `CONSUMER-STYLING.md`). |
|
|
25
25
|
| `navStyleClassPassthrough` | `string \| string[]` | `[]` | Extra CSS classes on the nested `ResponsiveHeader` — targets its own `--responsive-header-*`/`--overflow-nav-*` tokens. |
|
|
26
|
-
| `homeNavAriaLabel` | `string` | `"Home
|
|
26
|
+
| `homeNavAriaLabel` | `string` | `"Home navigation"` | aria-label on the home/branding nav landmark — override for localisation. |
|
|
27
27
|
| `mainNavAriaLabel` | `string` | `"Main navigation"` | Forwarded to `ResponsiveHeader`'s `mainNavAriaLabel` — override for localisation. |
|
|
28
28
|
| `secondaryNavAriaLabel` | `string` | `"Secondary navigation"` | Forwarded to `ResponsiveHeader`'s `secondaryNavAriaLabel` — override for localisation. |
|
|
29
29
|
| `overflowMenuAriaLabel` | `string` | `"Overflow navigation menu"` | Forwarded to `ResponsiveHeader`'s `overflowMenuAriaLabel` — override for localisation. |
|
|
@@ -97,8 +97,22 @@ const responsiveNavLinks = {
|
|
|
97
97
|
|
|
98
98
|
---
|
|
99
99
|
|
|
100
|
+
## Styling and layout
|
|
101
|
+
|
|
102
|
+
Tokens, class names and the sticky recipe are in this component's `CONSUMER-STYLING.md`. Since
|
|
103
|
+
2026-10-21:
|
|
104
|
+
|
|
105
|
+
- `--site-header-position: sticky` (with `--site-header-sticky-offset`, `--site-header-z-index`)
|
|
106
|
+
applies to the outer `.site-header-row` (the `PageRow`). Before that it sat on the inner
|
|
107
|
+
`<header>`, whose parent is exactly its height, so the header never actually stuck.
|
|
108
|
+
- The branding column is `fit-content(var(--site-header-branding-max-width, 50%))` and the nav
|
|
109
|
+
column `minmax(0, 1fr)`, so a long brand wraps instead of squeezing the nav, and the nav can
|
|
110
|
+
shrink and collapse instead of overflowing. The `StressTest` story covers it.
|
|
111
|
+
- `.home-navigation` became `.site-header-home-nav`, and `homeNavAriaLabel` defaults to
|
|
112
|
+
`"Home navigation"` (was `"Home Navigation"`).
|
|
113
|
+
|
|
100
114
|
## Migration notes
|
|
101
115
|
|
|
102
|
-
- Consuming layouts previously assembling `PageRow` + `SkipLinks` + `ResponsiveHeader` (or the removed `TabNavigation`) by hand can switch to `SiteHeader` directly; the two `styleClassPassthrough` props map 1:1 onto the two independent passthrough hooks that pattern used.
|
|
116
|
+
- Consuming layouts previously assembling `PageRow` + `SkipLinks` + `ResponsiveHeader` (or the removed `TabNavigation`) by hand can switch to `SiteHeader` directly; the two `styleClassPassthrough` props map 1:1 onto the two independent passthrough hooks that pattern used. Their hand-built markup often uses a `.home-navigation` class of its own; SiteHeader's nav is `.site-header-home-nav`, so move any styles across.
|
|
103
117
|
- `TabNavigation` (removed 2026-10-07) took `NavItemData` (`{ main: [{ text, href, iconName, cssName }] }`, also removed), which is **not** the same shape as `ResponsiveHeaderProp` (`{ groupKey: [{ name, path, childLinksTitle, childLinks }] }`): migrating means reshaping the nav data, not just swapping the component. See "Migrating from TabNavigation" in `responsive-header.md`. Its `navAlign` prop has no `SiteHeader` equivalent since `ResponsiveHeader`'s layout is fixed (branding column + nav column).
|
|
104
118
|
- Requires the consuming layout's main/footer regions to carry `id="main-content"` / `id="footer-content"` — same requirement as using `SkipLinks` directly.
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: SparkleEffect
|
|
3
|
+
description: SparkleEffect decorative wrapper that sends sparkles along its rounded border and fades in a glow on hover or keyboard focus
|
|
4
|
+
type: reference
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# SparkleEffect
|
|
8
|
+
|
|
9
|
+
## Overview
|
|
10
|
+
|
|
11
|
+
`SparkleEffect` wraps one element (a button, link or card) and, on hover or keyboard focus inside
|
|
12
|
+
it, sends small blurred sparkles travelling along the wrapper's border box while a soft glow fades in
|
|
13
|
+
behind the slot. Adapted from a Kevin Powell CodePen ("whimsical" button). It's a wrapper rather than
|
|
14
|
+
a utility class because the effect needs its own decorative elements.
|
|
15
|
+
|
|
16
|
+
## Props
|
|
17
|
+
|
|
18
|
+
| Prop | Type | Default | Description |
|
|
19
|
+
|------|------|---------|-------------|
|
|
20
|
+
| `count` | `number` | `5` | Number of sparkles. Floored and clamped to 1 to 12; a non-finite value falls back to 5. |
|
|
21
|
+
| `radius` | `string` | `undefined` | Written inline as `--sparkle-effect-radius`. Match the slotted element's corner radius. |
|
|
22
|
+
| `continuous` | `boolean` | `false` | Loop the sparkles for as long as the hover or focus lasts, instead of one burst per hover. Rendered as `data-continuous`. |
|
|
23
|
+
| `tag` | `"span" \| "div"` | `"span"` | `span` (inline-grid) for buttons and inline content, `div` (grid) for block content like cards. |
|
|
24
|
+
| `styleClassPassthrough` | `string \| string[]` | `[]` | Extra classes on the root. |
|
|
25
|
+
|
|
26
|
+
## Slots
|
|
27
|
+
|
|
28
|
+
| Slot | Description |
|
|
29
|
+
|---|---|
|
|
30
|
+
| `default` | The element that gets the effect. One element; the wrapper shrinks to it. |
|
|
31
|
+
|
|
32
|
+
## Basic usage
|
|
33
|
+
|
|
34
|
+
```vue
|
|
35
|
+
<SparkleEffect>
|
|
36
|
+
<InputButton button-text="Whimsy" is-pill />
|
|
37
|
+
</SparkleEffect>
|
|
38
|
+
|
|
39
|
+
<SparkleEffect continuous>
|
|
40
|
+
<InputButton button-text="Keep hovering" is-pill />
|
|
41
|
+
</SparkleEffect>
|
|
42
|
+
|
|
43
|
+
<SparkleEffect tag="div" radius="1.2rem" :count="7">
|
|
44
|
+
<NuxtLink to="/workshop" class="workshop-card">...</NuxtLink>
|
|
45
|
+
</SparkleEffect>
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Behaviour
|
|
49
|
+
|
|
50
|
+
- Renders the slotted content unchanged; the sparkles and glow are added around it.
|
|
51
|
+
- Renders `count` sparkles, floored and clamped to 1 to 12, with 5 for NaN or Infinity.
|
|
52
|
+
- The first five sparkles use tuned presets (lightness, hue shift, scale, drift, end point, speed)
|
|
53
|
+
from the original pen. Sparkles 6 to 12 reuse them with a further hue shift (35° per cycle), an end
|
|
54
|
+
point 13% further round the path and mirrored drift. Script writes them inline per sparkle, so there
|
|
55
|
+
is no `:nth-child` and slotted siblings can't shift them.
|
|
56
|
+
- Hover, or `:focus-visible` on anything inside, runs the effect. By default it's one burst per hover; with
|
|
57
|
+
`continuous` each sparkle loops at its own speed until the hover or focus ends. The slotted control keeps its own
|
|
58
|
+
focus ring.
|
|
59
|
+
- The glow sits behind the slot (`isolation: isolate` on the root, glow at `z-index: -1`), so it
|
|
60
|
+
works whatever stacking context the slotted element creates.
|
|
61
|
+
- Sparkles follow `--sparkle-effect-radius`: the wrapper can't read the child's radius.
|
|
62
|
+
- Sparkles are in an `aria-hidden`, `pointer-events: none` layer with nothing focusable.
|
|
63
|
+
- `prefers-reduced-motion: reduce` gives the glow only.
|
|
64
|
+
- Without `offset-path: border-box` (Safari before 17.2) no sparkles render; the glow still works.
|
|
65
|
+
Without relative colour syntax (Safari before 18) sparkles use the base colour unshifted.
|
|
66
|
+
- No effect while the slotted element is `:disabled` or `aria-disabled="true"` (InputButton
|
|
67
|
+
`readonly`).
|
|
68
|
+
- Hostile counts, long unbroken button labels, full-width block children, round icon buttons, nested
|
|
69
|
+
wrappers and an empty wrapper don't break layout (`StressTest` story).
|
|
70
|
+
|
|
71
|
+
## Styling
|
|
72
|
+
|
|
73
|
+
Colour (defaults to `--theme-border`), sparkle size, sparkle blur (`1px`), duration, radius, glow blur and glow opacity
|
|
74
|
+
are public tokens; see `CONSUMER-STYLING.md`.
|
|
75
|
+
|
|
76
|
+
## Stories
|
|
77
|
+
|
|
78
|
+
**Atoms/Effects/SparkleEffect**: Default (the original pen's lavender), ThemeColour, ManySparks,
|
|
79
|
+
Continuous, OnCard, Disabled, StressTest.
|
package/.claude/skills/index.md
CHANGED
|
@@ -81,11 +81,12 @@ Each skill is a single markdown file named `<area>-<task>.md`.
|
|
|
81
81
|
├── alert-mask.md — AlertMask (renamed from AlertMaskCore): SVG border/fill sized to slotted content via ResizeObserver; config-prop geometry, --alert-mask-* colour tokens
|
|
82
82
|
├── alert-masked-content.md — AlertMaskedContent: AlertContentInner inside AlertMask's SVG mask (accent cut-out border + translucent fill); masked variant for DisplayToast/DisplayPrompt; --alert-masked-content-* colour tokens
|
|
83
83
|
├── animated-svg-text.md — AnimatedSvgText: inline SVG stroke-draw-then-fill animation, text slot, CSS token API
|
|
84
|
-
├──
|
|
84
|
+
├── accordion-group.md — AccordionGroup (renamed from AccordianCore): panels inferred from item-{n}-summary/content/icon slots, exclusive-open via shared name, unique per-panel ids, classic/modern variant, max-width token
|
|
85
|
+
├── accordion-chat.md — AccordionChat: accordion as a chat (question bubbles right, rich answer bubbles left), avatar precedence item-{n}-avatar > #avatar > avatarLabel > none, built on ExpandingPanelClassic, full --accordion-chat-* token API (dark theme via tokens only)
|
|
85
86
|
├── eyebrow-text.md — EyebrowText props, usage patterns, styling
|
|
86
87
|
├── hero-text.md — HeroText props, usage patterns, styling
|
|
87
88
|
├── layout-grid-by-cols.md — LayoutGridByCols dynamic slots (item-{n}), column/gap/collapse props that write public --layout-grid-by-cols-* tokens (CSS-settable), section label
|
|
88
|
-
├── page-row.md — PageRow layout primitive: CSS grid named lines, nesting pattern, align prop, aria-labelledby, CSS token API
|
|
89
|
+
├── page-row.md — PageRow layout primitive: CSS grid named lines, nesting pattern, align prop, data-variant hook, aria-labelledby, CSS token API
|
|
89
90
|
├── link-text.md — LinkText props, slots, usage patterns, styling
|
|
90
91
|
├── header-block.md — HeaderBlock: tagLevel/classLevel semantic-vs-visual heading decoupling, page-heading-N utility classes
|
|
91
92
|
├── text-block.md — TextBlock: vertical-rhythm text wrapper, --text-block-padding-block-start/-end tokens, heading-id slot prop for section/article aria-labelledby
|
|
@@ -119,30 +120,30 @@ Each skill is a single markdown file named `<area>-<task>.md`.
|
|
|
119
120
|
├── rotating-carousel-image.md — RotatingCarouselImage: 3D rotating image carousel, scroll-parallax tilt, focus/keyboard/hover pause, reduced-motion, CSS tokens
|
|
120
121
|
├── social-icons-list.md — SocialIconsList: data-driven social icon links, ISocialIcon type, logos: icon names (dark ones vanish on dark backgrounds; mono alternatives), showLabel + scoped icon/label slots, per-item brandColour (set brand-colour tokens on .social-icons-list-item), tile/circle/label tokens, four recipes (mono tiles, dark dock, pills, overlapping stack), unsafe hrefs skipped
|
|
121
122
|
├── display-qr-code.md — DisplayQrCode: QR code SVG from a string value, colour/size/variant/radius props, currentColor default
|
|
122
|
-
├── capture-qr-code.md — CaptureQrCode: live camera scanner, error
|
|
123
|
-
├── decode-qr-code.md — DecodeQrCode: file picker + drag-and-drop image decoder, shared results list, CSS override points
|
|
124
|
-
├── auto-grid.md — AutoGrid: auto-fit responsive grid, $slots iteration, --auto-grid-gap/min-col-size-{small,default,large} tokens, is-responsive, semantic tag + aria
|
|
123
|
+
├── capture-qr-code.md — CaptureQrCode: live camera scanner, localisable errorMessages, error slot, restarts when the tab is shown again, own-root media stream cleanup, wrapping results
|
|
124
|
+
├── decode-qr-code.md — DecodeQrCode: file picker + drag-and-drop image decoder, shared results list, no-result/read-error messages, data-dropping state hook, CSS override points
|
|
125
|
+
├── auto-grid.md — AutoGrid: auto-fit responsive grid, $slots iteration, --auto-grid-gap/min-col-size-{small,default,large} tokens, is-responsive (data-responsive hook), semantic tag + aria
|
|
125
126
|
├── display-avatar.md — DisplayAvatar (02.molecules): circular avatar with image/initials fallback (alt announced), broken-image fallback, data-size scale tokens, image-size, --display-avatar-* colours, chip badge, icon slot
|
|
126
127
|
├── profile-section.md — ProfileSection (02.molecules): picture + numbered profile-info-N slots, eyebrow/hero header (above, or over the picture top/bottom on a scrim via header-placement), profileLinks, container-query two-column layout, .location/.services highlight tokens
|
|
127
128
|
├── display-theme-switch.md — DisplayThemeSwitch: system/light/dark picker wrapping SegmentedControl, wired to useSettingsStore, labels/icons props, small sizing variant
|
|
128
129
|
├── card-core.md — CardCore: generic card container, dynamic named slots as rows, 4 variants, blurred backdrop layer, full CSS token API
|
|
129
|
-
├── action-menu.md — ActionMenu + ActionMenuItem: ellipsis trigger + anchored popover menu, indexed item-{n} slots, link/button items, full CSS token API
|
|
130
|
+
├── action-menu.md — ActionMenu + ActionMenuItem: ellipsis trigger + anchored popover menu, indexed item-{n} slots, link/button items, max-width cap with wrapping labels + label line-clamp token, inline flip near the left edge, full CSS token API
|
|
130
131
|
├── select-menu.md — SelectMenu: v-model single-select listbox popover (ActionMenu's popover mechanics, InputSelect's selected-option semantics), icon/text/chevron trigger toggles, checkmark on selected option, options-array driven, full CSS token API
|
|
131
132
|
├── display-dialog.md — DisplayDialog: native <dialog> overlay, 5 variants (dialog/modal/confirm/alert/fullscreen), useDialogControls integration, InputButton close button, viewport-capped panel with wrapping title/content/footer, aria-label fallthrough when untitled, CSS token API
|
|
132
133
|
├── display-chip.md — DisplayChip: status indicator chip overlay, CSS trig positioning, circle/square shapes, status colours, icon/label content
|
|
133
134
|
├── display-pill.md — DisplayPill: pill/badge label with icon slot, 6 variants, 3 sizes, reversible order, --display-pill-* tokens (variants fall back to base colour/border/ring tokens); data-size/data-variant/data-reversed hooks (2026-10-08, were bare classes), label line-clamp token, -02 ring and outlined-monochrome recipes, StressTest story
|
|
134
|
-
├── carousel-flip.md — CarouselFlip: FLIP-animated carousel, carouselDataIds slot API, buttonLayout variants (sides/controls-flanking/controls-grouped-right/overlay), CSS tokens
|
|
135
|
-
├── samaritan-prompt.md — SamaritanPrompt + SamaritanPromptMixed: animated text prompts, typewriter/word-pulse effects, MessageConfig API, pause on hover, aria-live, CSS tokens
|
|
135
|
+
├── carousel-flip.md — CarouselFlip: FLIP-animated carousel, carouselDataIds slot API, buttonLayout variants (sides/controls-flanking/controls-grouped-right/overlay/track-edges), button align/offset/edge-inset tokens, InputButton prev/next, prefixed classes + data-* state hooks, empty/single-item handling, CSS tokens
|
|
136
|
+
├── samaritan-prompt.md — SamaritanPrompt + SamaritanPromptMixed: animated text prompts, typewriter/word-pulse effects, MessageConfig API, grapheme-safe typing, instant typing under reduced motion, pause on hover, aria-live, theme-default colours, wrapping + line-clamp, CSS tokens
|
|
136
137
|
├── display-toast.md — DisplayToast (standalone v-model) + DisplayToastProvider + useToastQueue (app-wide queue): stacking, FLIP dismiss, maxVisible, SemanticTheme × 4, masked SVG glass variant
|
|
137
138
|
├── display-prompt.md — DisplayPrompt: inline notification banner, SemanticTheme × 4, local vs parent-controlled dismiss, outlined modifier, CSS token override
|
|
138
139
|
├── expanding-panel-classic.md — ExpandingPanelClassic: grid-template-rows animation (no Baseline-2025 dependency), same API as ExpandingPanel, cross-browser animation parity trade-off
|
|
139
|
-
├── site-header.md — SiteHeader: PageRow + SkipLinks + ResponsiveHeader composition, #branding/#secondaryNavigation slots, dual styleClassPassthrough hooks
|
|
140
|
+
├── site-header.md — SiteHeader: PageRow + SkipLinks + ResponsiveHeader composition, #branding/#secondaryNavigation slots, dual styleClassPassthrough hooks, sticky on the outer row, capped branding column
|
|
140
141
|
├── responsive-header.md — ResponsiveHeader: overflow-collapsing adaptive nav, measurement-pipeline gotchas (unsized icons, vw font-size drift), full CSS token API
|
|
141
142
|
├── navigation-items.md — NavigationItems: internal overflow-panel renderer for ResponsiveHeader, complement-visibility logic, not used standalone
|
|
142
143
|
├── cookie-consent-banner.md — CookieConsentBanner: fixed non-modal Accept/Reject banner built on AlertContent, driven by useCookieConsent; title/message slots, acceptLabel/rejectLabel/icon/showIcon/ariaLabel props, InputButton (inline) actions, position/button tokens (panel via --alert-content-*), teleported so tokens go on html/passthrough
|
|
143
144
|
├── display-banner.md — DisplayBanner: canvas/content stacked overlay banner, conditional slot wrappers, CSS token API
|
|
144
|
-
├── deep-expanding-menu.md — DeepExpandingMenu: anchor-positioned popover nav panels, browser support caveat, CSS token API
|
|
145
|
-
├── deep-expanding-menu-classic.md — DeepExpandingMenuClassic: <details>-based fallback nav, click-
|
|
145
|
+
├── deep-expanding-menu.md — DeepExpandingMenu: anchor-positioned popover nav panels, browser support caveat, ariaLabel, prefixed classes, wrapping/flip/scroll stress handling, CSS token API
|
|
146
|
+
├── deep-expanding-menu-classic.md — DeepExpandingMenuClassic: <details>-based fallback nav, per-instance exclusive groups, outside-click/Escape/link-follow close, ariaLabel, prefixed classes, container-width panels, CSS token API
|
|
146
147
|
├── display-tooltip.md — DisplayTooltip: anchor-positioned popover trigger, browser support caveat, triggerIcon prop, type="button" trigger, hideTrigger removes it from tab order (data-hidden), CSS token API
|
|
147
148
|
├── display-tooltip-rich.md — DisplayTooltipRich (Material 3 rich tooltip, renamed from DisplayTooltipDefined 2026-10-18): structured title/body/action content with InputButton close button, per-field line-clamp tokens, composes DisplayTooltip
|
|
148
149
|
├── pop-over.md — PopOver: generic anchor-positioned disclosure panel, consumer-supplied trigger/content slots, placement prop, CSS token API
|
|
@@ -168,12 +169,13 @@ Each skill is a single markdown file named `<area>-<task>.md`.
|
|
|
168
169
|
├── sliding-toggle.md — SlidingToggle: single-choice segmented radiogroup with text labels of any width, measured sliding + hover indicators (useSlidingIndicator), roving tabindex/arrow keys, corner-shape token, CSS token API
|
|
169
170
|
├── entry-animation.md — EntryAnimation: scroll-driven entry animation wrapper (slide-in/zoom-reveal/exit-blur utility classes), skipAnimation for above-the-fold loop items, reduced-motion handled at the CSS layer
|
|
170
171
|
├── column-flow-grid.md — ColumnFlowGrid (renamed from MasonryGrid): CSS multi-column text-flow layout, named dynamic slots (no count/data prop), itemMinWidth/gap/unit sizing, CSS token API; not a true masonry, see "which one do I want?"
|
|
171
|
-
├── masonry-grid.md — MasonryGrid: real measured-height masonry (greedy shortest-column packing, animated resize) absorbed from the now-retired MasonryGridOrdered; named dynamic slots, fixedWidth/justify, CSS token API
|
|
172
|
+
├── masonry-grid.md — MasonryGrid: real measured-height masonry (greedy shortest-column packing, animated resize, re-packs on any tile resize) absorbed from the now-retired MasonryGridOrdered; named dynamic slots, fixedWidth/justify, CSS token API
|
|
172
173
|
├── glowing-border.md — GlowingBorder: animated conic-gradient glow border, 5 colour variants, tag prop, full CSS token API for width/radius/surface/duration/per-variant colour stops, reduced-motion guard
|
|
174
|
+
├── sparkle-effect.md — SparkleEffect: slot wrapper sending sparkles along its rounded border (offset-path) plus a glow on hover/focus-visible, count prop (1 to 12, preset table), continuous prop (loop while hovered/focused), radius prop/token, blur token, disabled suppression, reduced-motion and old-Safari glow-only fallback, CSS token API
|
|
173
175
|
├── section-parallax.md — SectionParallax: CSS fixed-background parallax section, iOS Safari limitation, when to use vs ScrollRevealImage, CSS token API, reduced-motion guard
|
|
174
176
|
├── container-glow.md — ContainerGlow (renamed from ContainerGlowCore): pointer-proximity glow-border cards, named dynamic slots (one card per slot), config-prop-driven layout/interaction, full CSS token API for static visuals, reduced-motion guard
|
|
175
|
-
├── dashboard-quad-grid.md — DashboardQuadGrid (renamed from LayoutGridA): fixed 4-slot dashboard grid, container-query breakpoints, CSS token API
|
|
176
|
-
└── dashboard-stats-grid.md — DashboardStatsGrid (renamed from LayoutGridB): top-row panel cluster + bottom-row panel strip dashboard grid, topRowSlot1ItemCount/bottomRowItemCount, CSS token API
|
|
177
|
+
├── dashboard-quad-grid.md — DashboardQuadGrid (renamed from LayoutGridA): fixed 4-slot dashboard grid, prefixed cell classes, container-query breakpoints, CSS token API
|
|
178
|
+
└── dashboard-stats-grid.md — DashboardStatsGrid (renamed from LayoutGridB): top-row panel cluster + bottom-row panel strip dashboard grid, topRowSlot1ItemCount/bottomRowItemCount (floored, clamped at 0), prefixed classes, CSS token API
|
|
177
179
|
```
|
|
178
180
|
|
|
179
181
|
## Skill file template
|