srcdev-nuxt-components 9.6.19 → 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.
Files changed (135) hide show
  1. package/.claude/rules/consumer/ids-from-useid.md +3 -2
  2. package/.claude/skills/component-compliance-checklist.md +5 -0
  3. package/.claude/skills/components/auto-grid.md +3 -1
  4. package/.claude/skills/components/capture-qr-code.md +9 -5
  5. package/.claude/skills/components/carousel-flip.md +41 -30
  6. package/.claude/skills/components/column-flow-grid.md +2 -0
  7. package/.claude/skills/components/dashboard-quad-grid.md +7 -2
  8. package/.claude/skills/components/dashboard-stats-grid.md +4 -0
  9. package/.claude/skills/components/decode-qr-code.md +7 -2
  10. package/.claude/skills/components/deep-expanding-menu-classic.md +31 -30
  11. package/.claude/skills/components/deep-expanding-menu.md +22 -21
  12. package/.claude/skills/components/grid-stack.md +2 -0
  13. package/.claude/skills/components/layout-grid-by-cols.md +2 -0
  14. package/.claude/skills/components/masonry-grid.md +9 -1
  15. package/.claude/skills/components/page-row.md +3 -0
  16. package/.claude/skills/components/samaritan-prompt.md +24 -8
  17. package/.claude/skills/components/site-header.md +16 -2
  18. package/.claude/skills/index.md +12 -12
  19. package/.claude/skills/storybook-add-story.md +25 -0
  20. package/.claude/skills/testing-add-unit-test.md +26 -0
  21. package/.vscode/srcdev-component-capture-decode-qr-code.code-snippets +11 -2
  22. package/.vscode/srcdev-component-carousel-flip.code-snippets +22 -4
  23. package/.vscode/srcdev-component-deep-expanding-menu-classic.code-snippets +32 -2
  24. package/.vscode/srcdev-component-deep-expanding-menu.code-snippets +31 -1
  25. package/.vscode/srcdev-component-samaritan-prompt.code-snippets +5 -4
  26. package/.vscode/srcdev-component-site-header.code-snippets +13 -1
  27. package/app/components/01.atoms/content-wrappers/layout-grid/dashboard-quad-grid/CONSUMER-STYLING.md +12 -2
  28. package/app/components/01.atoms/content-wrappers/layout-grid/dashboard-quad-grid/DashboardQuadGrid.vue +19 -17
  29. package/app/components/01.atoms/content-wrappers/layout-grid/dashboard-quad-grid/stories/DashboardQuadGrid.stories.ts +42 -0
  30. package/app/components/01.atoms/content-wrappers/layout-grid/dashboard-quad-grid/tests/DashboardQuadGrid.spec.ts +31 -44
  31. package/app/components/01.atoms/content-wrappers/layout-grid/dashboard-stats-grid/CONSUMER-STYLING.md +13 -1
  32. package/app/components/01.atoms/content-wrappers/layout-grid/dashboard-stats-grid/DashboardStatsGrid.vue +40 -31
  33. package/app/components/01.atoms/content-wrappers/layout-grid/dashboard-stats-grid/stories/DashboardStatsGrid.stories.ts +47 -0
  34. package/app/components/01.atoms/content-wrappers/layout-grid/dashboard-stats-grid/tests/DashboardStatsGrid.spec.ts +51 -53
  35. package/app/components/01.atoms/content-wrappers/layout-grid/layout-grid-by-cols/CONSUMER-STYLING.md +3 -0
  36. package/app/components/01.atoms/content-wrappers/layout-grid/layout-grid-by-cols/LayoutGridByCols.vue +4 -0
  37. package/app/components/01.atoms/content-wrappers/layout-grid/layout-grid-by-cols/stories/LayoutGridByCols.stories.ts +47 -2
  38. package/app/components/01.atoms/content-wrappers/layout-grid/layout-grid-by-cols/tests/LayoutGridByCols.spec.ts +36 -23
  39. package/app/components/01.atoms/grids/auto-grid/AutoGrid.vue +7 -3
  40. package/app/components/01.atoms/grids/auto-grid/CONSUMER-STYLING.md +1 -1
  41. package/app/components/01.atoms/grids/auto-grid/stories/AutoGrid.stories.ts +46 -1
  42. package/app/components/01.atoms/grids/auto-grid/tests/AutoGrid.spec.ts +40 -27
  43. package/app/components/01.atoms/grids/auto-grid/tests/__snapshots__/AutoGrid.spec.ts.snap +1 -1
  44. package/app/components/01.atoms/grids/column-flow-grid/CONSUMER-STYLING.md +9 -0
  45. package/app/components/01.atoms/grids/column-flow-grid/ColumnFlowGrid.vue +3 -2
  46. package/app/components/01.atoms/grids/column-flow-grid/stories/ColumnFlowGrid.stories.ts +46 -0
  47. package/app/components/01.atoms/grids/column-flow-grid/tests/ColumnFlowGrid.spec.ts +24 -11
  48. package/app/components/01.atoms/grids/grid-stack/CONSUMER-STYLING.md +3 -0
  49. package/app/components/01.atoms/grids/grid-stack/GridStack.vue +2 -0
  50. package/app/components/01.atoms/grids/grid-stack/stories/GridStack.stories.ts +41 -0
  51. package/app/components/01.atoms/grids/grid-stack/tests/GridStack.spec.ts +26 -13
  52. package/app/components/01.atoms/grids/masonry-grid/CONSUMER-STYLING.md +15 -0
  53. package/app/components/01.atoms/grids/masonry-grid/MasonryGrid.vue +51 -31
  54. package/app/components/01.atoms/grids/masonry-grid/stories/MasonryGrid.stories.ts +48 -0
  55. package/app/components/01.atoms/grids/masonry-grid/tests/MasonryGrid.spec.ts +84 -29
  56. package/app/components/01.atoms/page-row/CONSUMER-STYLING.md +6 -1
  57. package/app/components/01.atoms/page-row/PageRow.vue +16 -15
  58. package/app/components/01.atoms/page-row/stories/PageRow.stories.ts +23 -0
  59. package/app/components/01.atoms/page-row/tests/PageRow.spec.ts +6 -5
  60. package/app/components/02.molecules/navigation/deep-expanding-menu/CONSUMER-STYLING.md +63 -44
  61. package/app/components/02.molecules/navigation/deep-expanding-menu/DeepExpandingMenu.vue +74 -37
  62. package/app/components/02.molecules/navigation/deep-expanding-menu/stories/DeepExpandingMenu.stories.ts +139 -19
  63. package/app/components/02.molecules/navigation/deep-expanding-menu/tests/DeepExpandingMenu.spec.ts +90 -27
  64. package/app/components/02.molecules/navigation/deep-expanding-menu/tests/__snapshots__/DeepExpandingMenu.spec.ts.snap +8 -8
  65. package/app/components/02.molecules/navigation/deep-expanding-menu-classic/CONSUMER-STYLING.md +58 -26
  66. package/app/components/02.molecules/navigation/deep-expanding-menu-classic/DeepExpandingMenuClassic.vue +113 -57
  67. package/app/components/02.molecules/navigation/deep-expanding-menu-classic/stories/DeepExpandingMenuClassic.stories.ts +139 -19
  68. package/app/components/02.molecules/navigation/deep-expanding-menu-classic/tests/DeepExpandingMenuClassic.spec.ts +140 -22
  69. package/app/components/02.molecules/navigation/deep-expanding-menu-classic/tests/__snapshots__/DeepExpandingMenuClassic.spec.ts.snap +9 -9
  70. package/app/components/02.molecules/qr-code/CONSUMER-STYLING.md +22 -7
  71. package/app/components/02.molecules/qr-code/CaptureQrCode.vue +63 -71
  72. package/app/components/02.molecules/qr-code/DecodeQrCode.vue +47 -10
  73. package/app/components/02.molecules/qr-code/stories/CaptureQrCode.stories.ts +114 -26
  74. package/app/components/02.molecules/qr-code/stories/DecodeQrCode.stories.ts +115 -24
  75. package/app/components/02.molecules/qr-code/tests/CaptureQrCode.spec.ts +124 -51
  76. package/app/components/02.molecules/qr-code/tests/DecodeQrCode.spec.ts +74 -25
  77. package/app/components/02.molecules/samaritan-prompt/CONSUMER-STYLING.md +22 -10
  78. package/app/components/02.molecules/samaritan-prompt/SamaritanPrompt.vue +30 -12
  79. package/app/components/02.molecules/samaritan-prompt/SamaritanPromptMixed.vue +34 -10
  80. package/app/components/02.molecules/samaritan-prompt/stories/SamaritanPrompt.stories.ts +55 -4
  81. package/app/components/02.molecules/samaritan-prompt/stories/SamaritanPromptMixed.stories.ts +67 -7
  82. package/app/components/02.molecules/samaritan-prompt/tests/SamaritanPrompt.spec.ts +113 -29
  83. package/app/components/02.molecules/samaritan-prompt/tests/SamaritanPromptMixed.spec.ts +92 -45
  84. package/app/components/03.organisms/image-galleries/carousel-flip/CONSUMER-STYLING.md +82 -29
  85. package/app/components/03.organisms/image-galleries/carousel-flip/CarouselFlip.vue +176 -113
  86. package/app/components/03.organisms/image-galleries/carousel-flip/stories/CarouselFlip.stories.ts +153 -23
  87. package/app/components/03.organisms/image-galleries/carousel-flip/tests/CarouselFlip.spec.ts +119 -63
  88. package/app/components/03.organisms/services/service-detail/tests/ServiceDetail.spec.ts +8 -8
  89. package/app/components/03.organisms/services/service-detail/tests/__snapshots__/ServiceDetail.spec.ts.snap +4 -4
  90. package/app/components/03.organisms/site-header/CONSUMER-STYLING.md +41 -23
  91. package/app/components/03.organisms/site-header/SiteHeader.vue +16 -21
  92. package/app/components/03.organisms/site-header/stories/SiteHeader.stories.ts +138 -0
  93. package/app/components/03.organisms/site-header/tests/SiteHeader.spec.ts +53 -26
  94. package/app/components/03.organisms/site-header/tests/__snapshots__/SiteHeader.spec.ts.snap +2 -2
  95. package/app/types/components/qr-code.d.ts +12 -0
  96. package/app/utils/graphemes.ts +8 -0
  97. package/package.json +1 -1
  98. package/app/components/01.atoms/animations/entry/.nuxt/app.config.mjs +0 -21
  99. package/app/components/01.atoms/animations/entry/.nuxt/components.d.ts +0 -66
  100. package/app/components/01.atoms/animations/entry/.nuxt/imports.d.ts +0 -30
  101. package/app/components/01.atoms/animations/entry/.nuxt/manifest/meta/test.json +0 -1
  102. package/app/components/01.atoms/animations/entry/.nuxt/types/app.config.d.ts +0 -35
  103. package/app/components/01.atoms/animations/entry/.nuxt/types/build.d.ts +0 -23
  104. package/app/components/01.atoms/animations/entry/.nuxt/types/builder-env.d.ts +0 -1
  105. package/app/components/01.atoms/animations/entry/.nuxt/types/components.d.ts +0 -71
  106. package/app/components/01.atoms/animations/entry/.nuxt/types/imports.d.ts +0 -386
  107. package/app/components/01.atoms/animations/entry/.nuxt/types/middleware.d.ts +0 -11
  108. package/app/components/01.atoms/animations/entry/.nuxt/types/modules.d.ts +0 -79
  109. package/app/components/01.atoms/animations/entry/.nuxt/types/nitro-layouts.d.ts +0 -17
  110. package/app/components/01.atoms/animations/entry/.nuxt/types/nitro-nuxt.d.ts +0 -64
  111. package/app/components/01.atoms/animations/entry/.nuxt/types/plugins.d.ts +0 -29
  112. package/app/components/01.atoms/animations/entry/.nuxt/types/runtime-config.d.ts +0 -32
  113. package/app/components/01.atoms/animations/entry/.nuxt/types/shared-imports.d.ts +0 -10
  114. package/app/components/01.atoms/animations/entry/.nuxt/types/vue-shim.d.ts +0 -0
  115. package/app/components/01.atoms/animations/entry/.nuxtrc +0 -1
  116. package/app/components/01.atoms/animations/entry/node_modules/.cache/nuxt/chrome-workspace.json +0 -1
  117. package/app/components/01.atoms/animations/entry/node_modules/.cache/vite/client/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +0 -1
  118. package/app/components/03.organisms/responsive-header/tests/.nuxt/app.config.mjs +0 -21
  119. package/app/components/03.organisms/responsive-header/tests/.nuxt/components.d.ts +0 -66
  120. package/app/components/03.organisms/responsive-header/tests/.nuxt/imports.d.ts +0 -30
  121. package/app/components/03.organisms/responsive-header/tests/.nuxt/manifest/meta/test.json +0 -1
  122. package/app/components/03.organisms/responsive-header/tests/.nuxt/types/app.config.d.ts +0 -35
  123. package/app/components/03.organisms/responsive-header/tests/.nuxt/types/build.d.ts +0 -23
  124. package/app/components/03.organisms/responsive-header/tests/.nuxt/types/builder-env.d.ts +0 -1
  125. package/app/components/03.organisms/responsive-header/tests/.nuxt/types/components.d.ts +0 -71
  126. package/app/components/03.organisms/responsive-header/tests/.nuxt/types/imports.d.ts +0 -386
  127. package/app/components/03.organisms/responsive-header/tests/.nuxt/types/middleware.d.ts +0 -11
  128. package/app/components/03.organisms/responsive-header/tests/.nuxt/types/modules.d.ts +0 -79
  129. package/app/components/03.organisms/responsive-header/tests/.nuxt/types/nitro-layouts.d.ts +0 -17
  130. package/app/components/03.organisms/responsive-header/tests/.nuxt/types/nitro-nuxt.d.ts +0 -64
  131. package/app/components/03.organisms/responsive-header/tests/.nuxt/types/plugins.d.ts +0 -29
  132. package/app/components/03.organisms/responsive-header/tests/.nuxt/types/runtime-config.d.ts +0 -32
  133. package/app/components/03.organisms/responsive-header/tests/.nuxt/types/shared-imports.d.ts +0 -10
  134. package/app/components/03.organisms/responsive-header/tests/.nuxt/types/vue-shim.d.ts +0 -0
  135. package/app/components/03.organisms/responsive-header/tests/node_modules/.cache/nuxt/chrome-workspace.json +0 -1
@@ -86,7 +86,7 @@ Each skill is a single markdown file named `<area>-<task>.md`.
86
86
  ├── eyebrow-text.md — EyebrowText props, usage patterns, styling
87
87
  ├── hero-text.md — HeroText props, usage patterns, styling
88
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
89
- ├── 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
90
90
  ├── link-text.md — LinkText props, slots, usage patterns, styling
91
91
  ├── header-block.md — HeaderBlock: tagLevel/classLevel semantic-vs-visual heading decoupling, page-heading-N utility classes
92
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
@@ -120,9 +120,9 @@ Each skill is a single markdown file named `<area>-<task>.md`.
120
120
  ├── rotating-carousel-image.md — RotatingCarouselImage: 3D rotating image carousel, scroll-parallax tilt, focus/keyboard/hover pause, reduced-motion, CSS tokens
121
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
122
122
  ├── display-qr-code.md — DisplayQrCode: QR code SVG from a string value, colour/size/variant/radius props, currentColor default
123
- ├── capture-qr-code.md — CaptureQrCode: live camera scanner, error state, visibility/route/KeepAlive lifecycle, media stream cleanup
124
- ├── decode-qr-code.md — DecodeQrCode: file picker + drag-and-drop image decoder, shared results list, 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, 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
126
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
127
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
128
128
  ├── display-theme-switch.md — DisplayThemeSwitch: system/light/dark picker wrapping SegmentedControl, wired to useSettingsStore, labels/icons props, small sizing variant
@@ -132,18 +132,18 @@ Each skill is a single markdown file named `<area>-<task>.md`.
132
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
133
133
  ├── display-chip.md — DisplayChip: status indicator chip overlay, CSS trig positioning, circle/square shapes, status colours, icon/label content
134
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
135
- ├── carousel-flip.md — CarouselFlip: FLIP-animated carousel, carouselDataIds slot API, buttonLayout variants (sides/controls-flanking/controls-grouped-right/overlay), CSS tokens
136
- ├── 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
137
137
  ├── display-toast.md — DisplayToast (standalone v-model) + DisplayToastProvider + useToastQueue (app-wide queue): stacking, FLIP dismiss, maxVisible, SemanticTheme × 4, masked SVG glass variant
138
138
  ├── display-prompt.md — DisplayPrompt: inline notification banner, SemanticTheme × 4, local vs parent-controlled dismiss, outlined modifier, CSS token override
139
139
  ├── expanding-panel-classic.md — ExpandingPanelClassic: grid-template-rows animation (no Baseline-2025 dependency), same API as ExpandingPanel, cross-browser animation parity trade-off
140
- ├── 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
141
141
  ├── responsive-header.md — ResponsiveHeader: overflow-collapsing adaptive nav, measurement-pipeline gotchas (unsized icons, vw font-size drift), full CSS token API
142
142
  ├── navigation-items.md — NavigationItems: internal overflow-panel renderer for ResponsiveHeader, complement-visibility logic, not used standalone
143
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
144
144
  ├── display-banner.md — DisplayBanner: canvas/content stacked overlay banner, conditional slot wrappers, CSS token API
145
- ├── deep-expanding-menu.md — DeepExpandingMenu: anchor-positioned popover nav panels, browser support caveat, CSS token API
146
- ├── deep-expanding-menu-classic.md — DeepExpandingMenuClassic: <details>-based fallback nav, click-outside close, CSS token API
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
147
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
148
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
149
149
  ├── pop-over.md — PopOver: generic anchor-positioned disclosure panel, consumer-supplied trigger/content slots, placement prop, CSS token API
@@ -169,13 +169,13 @@ Each skill is a single markdown file named `<area>-<task>.md`.
169
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
170
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
171
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?"
172
- ├── 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
173
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
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
175
175
  ├── section-parallax.md — SectionParallax: CSS fixed-background parallax section, iOS Safari limitation, when to use vs ScrollRevealImage, CSS token API, reduced-motion guard
176
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
177
- ├── dashboard-quad-grid.md — DashboardQuadGrid (renamed from LayoutGridA): fixed 4-slot dashboard grid, 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, 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
179
179
  ```
180
180
 
181
181
  ## Skill file template
@@ -313,6 +313,31 @@ prop and try to pass it. Make it unmistakable:
313
313
 
314
314
  `PriceList.stories.ts` and `GoogleReviews.stories.ts` (line-clamp controls) follow this.
315
315
 
316
+ ## Where shared story helpers can live
317
+
318
+ Nowhere shared, so far. Stories ship in the package (`app/` is in `package.json` `files`) but
319
+ `.storybook/` doesn't, so a story importing from `.storybook/` breaks in a consumer's type-check;
320
+ and any `.ts` under `app/components/` is registered as a component (`pathPrefix: false` scan).
321
+ Repeat small fixtures per story instead (the grid `StressTest` stories, 2026-10-21, each carry their
322
+ own oversized-content block).
323
+
324
+ ## Simulating hardware or library events in a stress story
325
+
326
+ When a component's data only arrives from hardware or a third-party child (a camera, a file
327
+ decoder), a story can't feed it worst-case data through props. Add story-only buttons that call the
328
+ component's own handlers through its internal instance, and say so in the story text:
329
+
330
+ ```ts
331
+ const cmpRef = ref<ComponentPublicInstance | null>(null);
332
+ const internals = () =>
333
+ (cmpRef.value?.$ as unknown as { setupState: { onDetect: (codes: { rawValue: string }[]) => void } } | undefined)
334
+ ?.setupState;
335
+ ```
336
+
337
+ `setupState` is internal (not on the public type, hence the cast) but present in every build.
338
+ Don't `defineExpose` handlers just for a story. `CaptureQrCode` and `DecodeQrCode` (2026-10-21)
339
+ follow this.
340
+
316
341
  ## Dynamic slot names in template strings
317
342
 
318
343
  A dynamic slot argument can't contain spaces: `` #[`item-${i + 6}-summary`] `` is cut at the first space
@@ -346,6 +346,32 @@ be resolved from the ends and never reaches it. Mount with two items sharing the
346
346
  then `setProps` with the first and last items swapped, and assert the rendered labels in order
347
347
  (`SkipLinks.spec.ts`). With an `href`-only key that test rendered five links instead of four.
348
348
 
349
+ ## Stub third-party children that hold shared state
350
+
351
+ When a component renders a library child whose behaviour the spec doesn't need (the spec drives
352
+ the parent's own handlers), stub the child in the mount helper. Unmounting doesn't isolate a
353
+ library that keeps module-level state: `QrcodeStream` queues every camera start/stop on one shared
354
+ promise chain, so a start left from an earlier test failed mid-test and emitted `error` into the
355
+ next `CaptureQrCode`, but only under full-suite load and only with `SRCDEV_STANDALONE=true`
356
+ (2026-10-21).
357
+
358
+ ```typescript
359
+ const qrcodeStreamStub = { name: "QrcodeStream", emits: ["detect", "error"], template: `<div class="qrcode-stream-stub"></div>` };
360
+
361
+ const mountCapture = async (options: CaptureMountOptions = {}) => {
362
+ const wrapper = await mountSuspended(CaptureQrCode, {
363
+ ...options,
364
+ global: { ...options?.global, stubs: { QrcodeStream: qrcodeStreamStub, ...options?.global?.stubs } },
365
+ });
366
+ mounted.push(wrapper);
367
+ return wrapper;
368
+ };
369
+ ```
370
+
371
+ Assert against the stub's class, not the component's tag name (see
372
+ `.claude/rules/test-no-vacuous-assertions.md`). Run the spec with `npm run test:run` so
373
+ standalone-only modules load.
374
+
349
375
  ## Mocking browser APIs
350
376
 
351
377
  ### Global constructors (ResizeObserver, IntersectionObserver, etc.)
@@ -3,7 +3,11 @@
3
3
  "description": "CaptureQrCode live camera scanner with localisable copy",
4
4
  "scope": "vue,html",
5
5
  "body": [
6
- "<CaptureQrCode camera-stopped-label=\"${1:Camera stopped}\" reset-camera-label=\"${2:Reset camera}\" />"
6
+ "<CaptureQrCode",
7
+ " camera-stopped-label=\"${1:Camera stopped}\"",
8
+ " reset-camera-label=\"${2:Reset camera}\"",
9
+ " :error-messages=\"{ NotAllowedError: '${3:Camera access was blocked.}', default: '${4:The camera could not be started.}' }\"",
10
+ "/>"
7
11
  ]
8
12
  },
9
13
  "SRCDEV CaptureQrCode Custom Error": {
@@ -22,7 +26,12 @@
22
26
  "description": "DecodeQrCode image upload / drop decoder",
23
27
  "scope": "vue,html",
24
28
  "body": [
25
- "<DecodeQrCode upload-label=\"${1:Upload a QR code image}\" drop-label=\"${2:Or drop a QR code image here}\" />"
29
+ "<DecodeQrCode",
30
+ " upload-label=\"${1:Upload a QR code image}\"",
31
+ " drop-label=\"${2:Or drop a QR code image here}\"",
32
+ " no-result-label=\"${3:No QR code was found in that image.}\"",
33
+ " read-error-label=\"${4:That file couldn't be read as an image.}\"",
34
+ "/>"
26
35
  ]
27
36
  }
28
37
  }
@@ -3,7 +3,7 @@
3
3
  "description": "CarouselFlip with looped item slots",
4
4
  "scope": "vue,html",
5
5
  "body": [
6
- "<CarouselFlip :carousel-data-ids=\"$1items.map((item) => item.id)\">",
6
+ "<CarouselFlip :carousel-data-ids=\"$1items.map((item) => item.id)\" aria-label=\"${4:Image carousel}\">",
7
7
  " <template v-for=\"$2item in $1items\" :key=\"$2item.id\" #[$2item.id]>",
8
8
  " $3",
9
9
  " </template>",
@@ -25,15 +25,33 @@
25
25
  "</CarouselFlip>"
26
26
  ]
27
27
  },
28
+ "SRCDEV CarouselFlip Track Edges": {
29
+ "description": "CarouselFlip with prev/next overlaid on the ends of the visible track",
30
+ "scope": "vue,html",
31
+ "body": [
32
+ "<CarouselFlip",
33
+ " :carousel-data-ids=\"$1items.map((item) => item.id)\"",
34
+ " button-layout=\"track-edges\"",
35
+ " :allow-carousel-overflow=\"true\"",
36
+ ">",
37
+ " <template v-for=\"$2item in $1items\" :key=\"$2item.id\" #[$2item.id]>",
38
+ " $3",
39
+ " </template>",
40
+ "</CarouselFlip>"
41
+ ]
42
+ },
28
43
  "SRCDEV CarouselFlip CSS Override": {
29
44
  "description": "CSS override scaffold for CarouselFlip tokens",
30
45
  "scope": "css",
31
46
  "body": [
32
47
  ".$1my-page {",
33
48
  " .carousel-flip {",
34
- " --carousel-flip-gap: $2;",
35
- " --carousel-flip-marker-active-colour: $3;",
36
- " --carousel-flip-button-background-colour: $4;",
49
+ " --carousel-flip-gap: ${2:1rem};",
50
+ " --carousel-flip-marker-active-colour: ${3:var(--theme-surface)};",
51
+ " --carousel-flip-button-background-colour: ${4:var(--theme-surface-subtle)};",
52
+ " --carousel-flip-button-icon-colour: ${5:var(--theme-text)};",
53
+ " --carousel-flip-button-align: ${6:center};",
54
+ " --carousel-flip-button-inline-offset: ${7:0px};",
37
55
  " }",
38
56
  "}"
39
57
  ]
@@ -1,9 +1,39 @@
1
1
  {
2
2
  "SRCDEV DeepExpandingMenuClassic Basic": {
3
- "description": "DeepExpandingMenuClassic — <details>-based fallback nav",
3
+ "description": "DeepExpandingMenuClassic — <details>-based nav",
4
4
  "scope": "vue,html",
5
+ "prefix": "srcdev-deep-expanding-menu-classic",
5
6
  "body": [
6
- "<DeepExpandingMenuClassic :nav-links=\"$1navLinks\" />"
7
+ "<DeepExpandingMenuClassic :nav-links=\"${1:navLinks}\" aria-label=\"${2:Main}\" />"
8
+ ]
9
+ },
10
+ "SRCDEV DeepExpandingMenuClassic Nav Links": {
11
+ "description": "DeepExpandingMenuClassic — navLinks data (ResponsiveHeaderNavItem[])",
12
+ "scope": "typescript,vue",
13
+ "prefix": "srcdev-deep-expanding-menu-classic-links",
14
+ "body": [
15
+ "const ${1:navLinks}: ResponsiveHeaderNavItem[] = [",
16
+ " { name: \"${2:Home}\", path: \"${3:/}\" },",
17
+ " {",
18
+ " name: \"${4:Services}\",",
19
+ " childLinksTitle: \"${5:Our services}\",",
20
+ " childLinks: [",
21
+ " { name: \"${6:Haircuts}\", path: \"${7:/services/haircuts}\" },",
22
+ " ],",
23
+ " },",
24
+ "];"
25
+ ]
26
+ },
27
+ "SRCDEV DeepExpandingMenuClassic Style Override": {
28
+ "description": "DeepExpandingMenuClassic — unlayered token override scaffold",
29
+ "scope": "css,vue",
30
+ "prefix": "srcdev-deep-expanding-menu-classic-css",
31
+ "body": [
32
+ ".${1:site-nav} {",
33
+ " --deep-expanding-menu-classic-item-gap: ${2:2.4rem};",
34
+ " --deep-expanding-menu-classic-panel-background-colour: ${3:var(--theme-surface-subtle)};",
35
+ " --deep-expanding-menu-classic-panel-heading-line-clamp: ${4:none};",
36
+ "}"
7
37
  ]
8
38
  }
9
39
  }
@@ -2,8 +2,38 @@
2
2
  "SRCDEV DeepExpandingMenu Basic": {
3
3
  "description": "DeepExpandingMenu — anchor-positioned popover nav panels",
4
4
  "scope": "vue,html",
5
+ "prefix": "srcdev-deep-expanding-menu",
5
6
  "body": [
6
- "<DeepExpandingMenu :nav-links=\"$1navLinks\" />"
7
+ "<DeepExpandingMenu :nav-links=\"${1:navLinks}\" aria-label=\"${2:Main}\" />"
8
+ ]
9
+ },
10
+ "SRCDEV DeepExpandingMenu Nav Links": {
11
+ "description": "DeepExpandingMenu — navLinks data (ResponsiveHeaderNavItem[])",
12
+ "scope": "typescript,vue",
13
+ "prefix": "srcdev-deep-expanding-menu-links",
14
+ "body": [
15
+ "const ${1:navLinks}: ResponsiveHeaderNavItem[] = [",
16
+ " { name: \"${2:Home}\", path: \"${3:/}\" },",
17
+ " {",
18
+ " name: \"${4:Services}\",",
19
+ " childLinksTitle: \"${5:Our services}\",",
20
+ " childLinks: [",
21
+ " { name: \"${6:Haircuts}\", path: \"${7:/services/haircuts}\" },",
22
+ " ],",
23
+ " },",
24
+ "];"
25
+ ]
26
+ },
27
+ "SRCDEV DeepExpandingMenu Style Override": {
28
+ "description": "DeepExpandingMenu — unlayered token override scaffold",
29
+ "scope": "css,vue",
30
+ "prefix": "srcdev-deep-expanding-menu-css",
31
+ "body": [
32
+ ".${1:site-nav} {",
33
+ " --deep-expanding-menu-gap: ${2:2.4rem};",
34
+ " --deep-expanding-menu-panel-background-colour: ${3:var(--theme-surface-subtle)};",
35
+ " --deep-expanding-menu-panel-heading-line-clamp: ${4:none};",
36
+ "}"
7
37
  ]
8
38
  }
9
39
  }
@@ -48,13 +48,14 @@
48
48
  ]
49
49
  },
50
50
  "SRCDEV SamaritanPrompt Tokens": {
51
- "description": "SamaritanPrompt / SamaritanPromptMixed CSS token overrides",
51
+ "description": "SamaritanPrompt / SamaritanPromptMixed CSS token overrides (classic white-on-black look)",
52
52
  "scope": "css,vue",
53
53
  "body": [
54
54
  "--samaritan-prompt-font-size: ${1:2rem};",
55
- "--samaritan-prompt-text-colour: ${2:var(--theme-text)};",
56
- "--samaritan-prompt-underline-colour: ${3:var(--theme-text)};",
57
- "--samaritan-prompt-cursor-colour: ${4:#cc0000};"
55
+ "--samaritan-prompt-text-colour: ${2:#fff};",
56
+ "--samaritan-prompt-underline-colour: ${3:#fff};",
57
+ "--samaritan-prompt-cursor-colour: ${4:#cc0000};",
58
+ "--samaritan-prompt-text-line-clamp: ${5:none};"
58
59
  ]
59
60
  }
60
61
  }
@@ -14,6 +14,18 @@
14
14
  "</SiteHeader>"
15
15
  ]
16
16
  },
17
+ "SRCDEV SiteHeader Sticky Tokens": {
18
+ "description": "SiteHeader sticky + layout token overrides (set on the styleClassPassthrough class)",
19
+ "scope": "css",
20
+ "body": [
21
+ ".${1:header} {",
22
+ " --site-header-position: ${2:sticky};",
23
+ " --site-header-sticky-offset: ${3:0px};",
24
+ " --site-header-bg: ${4:var(--theme-surface-subtle)};",
25
+ " --site-header-branding-max-width: ${5:50%};",
26
+ "}"
27
+ ]
28
+ },
17
29
  "SRCDEV SiteHeader With Secondary Navigation": {
18
30
  "description": "SiteHeader with the secondaryNavigation slot (e.g. a settings icon link)",
19
31
  "scope": "vue,html",
@@ -36,7 +48,7 @@
36
48
  "body": [
37
49
  "<SiteHeader",
38
50
  " :responsive-nav-links=\"$1responsiveNavLinks\"",
39
- " home-nav-aria-label=\"${2:Home Navigation}\"",
51
+ " home-nav-aria-label=\"${2:Home navigation}\"",
40
52
  " main-nav-aria-label=\"${3:Main navigation}\"",
41
53
  " secondary-nav-aria-label=\"${4:Secondary navigation}\"",
42
54
  " overflow-menu-aria-label=\"${5:Overflow navigation menu}\"",
@@ -7,10 +7,10 @@
7
7
  | `--dashboard-quad-grid-gap` | `2rem` | Gap between the four slot panels |
8
8
  | `--dashboard-quad-grid-padding` | `2rem` | Padding inside each slot panel |
9
9
  | `--dashboard-quad-grid-outline-width` | `0.1rem` | Slot panel outline width |
10
- | `--dashboard-quad-grid-outline-colour` | `black` | Slot panel outline colour |
10
+ | `--dashboard-quad-grid-outline-colour` | `var(--theme-border)` | Slot panel outline colour |
11
11
  | `--dashboard-quad-grid-outline-offset` | `0` | Slot panel outline offset |
12
12
  | `--dashboard-quad-grid-border-width` | `0` | Slot panel border width (off by default — the panel edge is drawn by the outline) |
13
- | `--dashboard-quad-grid-border-colour` | `black` | Slot panel border colour, only visible once `--dashboard-quad-grid-border-width` is set |
13
+ | `--dashboard-quad-grid-border-colour` | `var(--theme-border)` | Slot panel border colour, only visible once `--dashboard-quad-grid-border-width` is set |
14
14
  | `--dashboard-quad-grid-border-radius` | `0.5rem` | Slot panel corner radius |
15
15
  | `--dashboard-quad-grid-rule-width` | `0.1rem` | Width of the grid gap rule line (CSS Gap Decorations — see Notes) |
16
16
  | `--dashboard-quad-grid-rule-colour` | `transparent` | Colour of the grid gap rule line, invisible by default |
@@ -31,6 +31,16 @@ Or scope to a single instance via `styleClassPassthrough`:
31
31
 
32
32
  ---
33
33
 
34
+ ## State hooks
35
+
36
+ No state attributes. Element classes: `.dashboard-quad-grid-wrapper` (root, the container),
37
+ `.dashboard-quad-grid` (the grid), `.dashboard-quad-grid-cell` (each of the four cells) with
38
+ `.dashboard-quad-grid-slot-1` to `-slot-4`. Cells wrap their content and the columns are
39
+ `minmax(0, auto)`, so an unbroken string can't widen the grid.
40
+
41
+ > Changed 2026-10-21: the cells were `.slot1` to `.slot4` (bare classes a consumer's own CSS could
42
+ > match), styled through `& > div`; the outline and border colours defaulted to `black`.
43
+
34
44
  ## Local overrides
35
45
 
36
46
  Set the tokens above on an element you own (a page or section class, or a class added with
@@ -1,16 +1,16 @@
1
1
  <template>
2
2
  <div class="dashboard-quad-grid-wrapper" :class="[elementClasses]">
3
3
  <div class="dashboard-quad-grid">
4
- <div class="slot1">
4
+ <div class="dashboard-quad-grid-cell dashboard-quad-grid-slot-1">
5
5
  <slot name="slot1"></slot>
6
6
  </div>
7
- <div class="slot2">
7
+ <div class="dashboard-quad-grid-cell dashboard-quad-grid-slot-2">
8
8
  <slot name="slot2"></slot>
9
9
  </div>
10
- <div class="slot3">
10
+ <div class="dashboard-quad-grid-cell dashboard-quad-grid-slot-3">
11
11
  <slot name="slot3"></slot>
12
12
  </div>
13
- <div class="slot4">
13
+ <div class="dashboard-quad-grid-cell dashboard-quad-grid-slot-4">
14
14
  <slot name="slot4"></slot>
15
15
  </div>
16
16
  </div>
@@ -50,61 +50,63 @@ watch(
50
50
  rule-break: intersection;
51
51
  rule-inset: var(--dashboard-quad-grid-rule-inset, 0);
52
52
 
53
- & > div {
53
+ .dashboard-quad-grid-cell {
54
+ min-inline-size: 0;
55
+ overflow-wrap: anywhere;
54
56
  padding: var(--dashboard-quad-grid-padding, 2rem);
55
57
  outline: var(--dashboard-quad-grid-outline-width, 0.1rem) solid
56
- var(--dashboard-quad-grid-outline-colour, black);
58
+ var(--dashboard-quad-grid-outline-colour, var(--theme-border));
57
59
  outline-offset: var(--dashboard-quad-grid-outline-offset, 0);
58
60
  border: var(--dashboard-quad-grid-border-width, 0) solid
59
- var(--dashboard-quad-grid-border-colour, black);
61
+ var(--dashboard-quad-grid-border-colour, var(--theme-border));
60
62
  border-radius: var(--dashboard-quad-grid-border-radius, 0.5rem);
61
63
  }
62
64
 
63
65
  @container (min-width: 768px) {
64
- grid-template-columns: repeat(2, auto);
66
+ grid-template-columns: repeat(2, minmax(0, auto));
65
67
  grid-template-rows: repeat(3, auto);
66
68
 
67
- .slot1 {
69
+ .dashboard-quad-grid-slot-1 {
68
70
  grid-column: 1 / span 2;
69
71
  grid-row: 1;
70
72
  }
71
73
 
72
- .slot2 {
74
+ .dashboard-quad-grid-slot-2 {
73
75
  grid-column: 1 / span 2;
74
76
  grid-row: 2;
75
77
  }
76
78
 
77
- .slot3 {
79
+ .dashboard-quad-grid-slot-3 {
78
80
  grid-column: 1;
79
81
  grid-row: 3;
80
82
  }
81
83
 
82
- .slot4 {
84
+ .dashboard-quad-grid-slot-4 {
83
85
  grid-column: 2;
84
86
  grid-row: 3;
85
87
  }
86
88
  }
87
89
 
88
90
  @container (min-width: 1060px) {
89
- grid-template-columns: repeat(3, auto);
91
+ grid-template-columns: repeat(3, minmax(0, auto));
90
92
  grid-template-rows: repeat(2, auto);
91
93
 
92
- .slot1 {
94
+ .dashboard-quad-grid-slot-1 {
93
95
  grid-column: 1;
94
96
  grid-row: 1 / span 2;
95
97
  }
96
98
 
97
- .slot2 {
99
+ .dashboard-quad-grid-slot-2 {
98
100
  grid-column: 2 / span 2;
99
101
  grid-row: 1;
100
102
  }
101
103
 
102
- .slot3 {
104
+ .dashboard-quad-grid-slot-3 {
103
105
  grid-column: 2;
104
106
  grid-row: 2;
105
107
  }
106
108
 
107
- .slot4 {
109
+ .dashboard-quad-grid-slot-4 {
108
110
  grid-column: 3;
109
111
  grid-row: 2;
110
112
  }
@@ -1,4 +1,6 @@
1
+ import { ref } from "vue";
1
2
  import type { Meta, StoryFn } from "@nuxtjs/storybook";
3
+ import CanvasSwitcher from "../../../../canvas-switcher/CanvasSwitcher.vue";
2
4
  import DashboardQuadGridComponent from "../DashboardQuadGrid.vue";
3
5
  import type { MediaCanvas } from "~/types/components";
4
6
 
@@ -530,3 +532,43 @@ ResponsiveComparison.parameters = {
530
532
  },
531
533
  },
532
534
  };
535
+
536
+ export const StressTest: StoryFn = () => ({
537
+ components: { DashboardQuadGridComponent },
538
+ template: `
539
+ <DashboardQuadGridComponent>
540
+ <template #slot1><div style="padding: 1.2rem; background: var(--theme-surface-subtle); outline: 1px dashed currentColor;"><h3 style="margin: 0;">Supercalifragilisticexpialidociousandthensomemoretextwithoutanyspaces</h3><p style="margin: 0;">https://example.com/a/very/long/unbroken/path/that/never/wraps/on/its/own</p></div></template>
541
+ <template #slot2><div style="padding: 1.2rem; background: var(--theme-surface-subtle); outline: 1px dashed currentColor;"><p style="margin: 0;">Ein sehr langer Beschreibungstext aus Northtown, der auf schmalen Bildschirmen mehrfach umbrechen muss, ohne das Raster zu sprengen oder die Nachbarspalten zusammenzudrücken.</p></div></template>
542
+ <template #slot3><div style="padding: 1.2rem; background: var(--theme-surface-subtle); outline: 1px dashed currentColor;"><img src="/images/banners/banner-1.webp" width="1600" height="400" alt="" style="display: block; max-inline-size: 100%; block-size: auto;"></div></template>
543
+ <template #slot4><div style="padding: 1.2rem; background: var(--theme-surface-subtle); outline: 1px dashed currentColor;"><p style="margin: 0;">🙂 👍🏽 👨‍👩‍👧 &lt;b&gt;Not bold&lt;/b&gt;</p></div></template>
544
+ </DashboardQuadGridComponent>
545
+ `,
546
+ });
547
+ StressTest.storyName = "Stress Test (Worst-Case Data)";
548
+ StressTest.decorators = [
549
+ (story, context) => ({
550
+ components: { story, CanvasSwitcher },
551
+ setup() {
552
+ const canvasName = ref<MediaCanvas>(context.parameters.initialCanvas ?? "mobileCanvas");
553
+ return { canvasName };
554
+ },
555
+ template: `
556
+ <div style="padding: 1.2rem 1.6rem; border-block-end: 1px solid currentColor;">
557
+ <CanvasSwitcher v-model:canvas-name="canvasName" />
558
+ </div>
559
+ <div :class="canvasName" style="margin-inline: auto; padding: 2rem; outline: 1px dashed currentColor;">
560
+ <story />
561
+ </div>
562
+ `,
563
+ }),
564
+ ];
565
+ StressTest.parameters = {
566
+ layout: "fullscreen",
567
+ docs: {
568
+ story: { inline: false, height: "720px" },
569
+ description: {
570
+ story:
571
+ "Oversized and hostile slot content at every `CanvasSwitcher` width. Check that: the unbroken heading and URL wrap inside their cell instead of widening it; the German paragraph wraps; the 1600px image scales down to its cell; emoji, `<b>` and right-to-left text render as text; the empty and one-character cells keep their place; no neighbouring cell is squeezed to nothing and nothing scrolls sideways. At tablet and wider, check the two- and three-column arrangements too.",
572
+ },
573
+ },
574
+ };