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.
Files changed (187) hide show
  1. package/.claude/rules/consumer/css-relative-colour-calc.md +15 -0
  2. package/.claude/rules/consumer/ids-from-useid.md +3 -2
  3. package/.claude/rules/consumer/vue-dynamic-arg-no-spaces.md +16 -0
  4. package/.claude/skills/component-compliance-checklist.md +11 -1
  5. package/.claude/skills/component-dynamic-slots.md +2 -2
  6. package/.claude/skills/component-naming.md +1 -1
  7. package/.claude/skills/components/accordion-chat.md +99 -0
  8. package/.claude/skills/components/accordion-group.md +98 -0
  9. package/.claude/skills/components/action-menu.md +11 -5
  10. package/.claude/skills/components/auto-grid.md +3 -1
  11. package/.claude/skills/components/capture-qr-code.md +9 -5
  12. package/.claude/skills/components/carousel-flip.md +41 -30
  13. package/.claude/skills/components/column-flow-grid.md +2 -0
  14. package/.claude/skills/components/dashboard-quad-grid.md +7 -2
  15. package/.claude/skills/components/dashboard-stats-grid.md +4 -0
  16. package/.claude/skills/components/decode-qr-code.md +7 -2
  17. package/.claude/skills/components/deep-expanding-menu-classic.md +31 -30
  18. package/.claude/skills/components/deep-expanding-menu.md +22 -21
  19. package/.claude/skills/components/expanding-panel-classic.md +9 -7
  20. package/.claude/skills/components/expanding-panel.md +9 -8
  21. package/.claude/skills/components/grid-stack.md +2 -0
  22. package/.claude/skills/components/layout-grid-by-cols.md +2 -0
  23. package/.claude/skills/components/masonry-grid.md +9 -1
  24. package/.claude/skills/components/page-row.md +3 -0
  25. package/.claude/skills/components/samaritan-prompt.md +24 -8
  26. package/.claude/skills/components/site-header.md +16 -2
  27. package/.claude/skills/components/sparkle-effect.md +79 -0
  28. package/.claude/skills/index.md +16 -14
  29. package/.claude/skills/storybook-add-story.md +33 -0
  30. package/.claude/skills/testing-add-unit-test.md +26 -0
  31. package/.claude/skills/using-component-skills.md +1 -1
  32. package/.vscode/srcdev-component-accordion-chat.code-snippets +42 -0
  33. package/.vscode/srcdev-component-accordion-group.code-snippets +38 -0
  34. package/.vscode/srcdev-component-action-menu.code-snippets +3 -3
  35. package/.vscode/srcdev-component-capture-decode-qr-code.code-snippets +11 -2
  36. package/.vscode/srcdev-component-carousel-flip.code-snippets +22 -4
  37. package/.vscode/srcdev-component-deep-expanding-menu-classic.code-snippets +32 -2
  38. package/.vscode/srcdev-component-deep-expanding-menu.code-snippets +31 -1
  39. package/.vscode/srcdev-component-samaritan-prompt.code-snippets +5 -4
  40. package/.vscode/srcdev-component-site-header.code-snippets +13 -1
  41. package/.vscode/srcdev-component-sparkle-effect.code-snippets +33 -0
  42. package/app/components/01.atoms/animations/sparkle-effect/CONSUMER-STYLING.md +75 -0
  43. package/app/components/01.atoms/animations/sparkle-effect/SparkleEffect.vue +213 -0
  44. package/app/components/01.atoms/animations/sparkle-effect/stories/SparkleEffect.stories.ts +303 -0
  45. package/app/components/01.atoms/animations/sparkle-effect/tests/SparkleEffect.spec.ts +150 -0
  46. package/app/components/01.atoms/content-wrappers/docs-pages/ContentDocs.vue +1 -1
  47. package/app/components/01.atoms/content-wrappers/layout-grid/dashboard-quad-grid/CONSUMER-STYLING.md +12 -2
  48. package/app/components/01.atoms/content-wrappers/layout-grid/dashboard-quad-grid/DashboardQuadGrid.vue +19 -17
  49. package/app/components/01.atoms/content-wrappers/layout-grid/dashboard-quad-grid/stories/DashboardQuadGrid.stories.ts +42 -0
  50. package/app/components/01.atoms/content-wrappers/layout-grid/dashboard-quad-grid/tests/DashboardQuadGrid.spec.ts +31 -44
  51. package/app/components/01.atoms/content-wrappers/layout-grid/dashboard-stats-grid/CONSUMER-STYLING.md +13 -1
  52. package/app/components/01.atoms/content-wrappers/layout-grid/dashboard-stats-grid/DashboardStatsGrid.vue +40 -31
  53. package/app/components/01.atoms/content-wrappers/layout-grid/dashboard-stats-grid/stories/DashboardStatsGrid.stories.ts +47 -0
  54. package/app/components/01.atoms/content-wrappers/layout-grid/dashboard-stats-grid/tests/DashboardStatsGrid.spec.ts +51 -53
  55. package/app/components/01.atoms/content-wrappers/layout-grid/layout-grid-by-cols/CONSUMER-STYLING.md +3 -0
  56. package/app/components/01.atoms/content-wrappers/layout-grid/layout-grid-by-cols/LayoutGridByCols.vue +4 -0
  57. package/app/components/01.atoms/content-wrappers/layout-grid/layout-grid-by-cols/stories/LayoutGridByCols.stories.ts +47 -2
  58. package/app/components/01.atoms/content-wrappers/layout-grid/layout-grid-by-cols/tests/LayoutGridByCols.spec.ts +36 -23
  59. package/app/components/01.atoms/grids/auto-grid/AutoGrid.vue +7 -3
  60. package/app/components/01.atoms/grids/auto-grid/CONSUMER-STYLING.md +1 -1
  61. package/app/components/01.atoms/grids/auto-grid/stories/AutoGrid.stories.ts +46 -1
  62. package/app/components/01.atoms/grids/auto-grid/tests/AutoGrid.spec.ts +40 -27
  63. package/app/components/01.atoms/grids/auto-grid/tests/__snapshots__/AutoGrid.spec.ts.snap +1 -1
  64. package/app/components/01.atoms/grids/column-flow-grid/CONSUMER-STYLING.md +9 -0
  65. package/app/components/01.atoms/grids/column-flow-grid/ColumnFlowGrid.vue +3 -2
  66. package/app/components/01.atoms/grids/column-flow-grid/stories/ColumnFlowGrid.stories.ts +46 -0
  67. package/app/components/01.atoms/grids/column-flow-grid/tests/ColumnFlowGrid.spec.ts +24 -11
  68. package/app/components/01.atoms/grids/grid-stack/CONSUMER-STYLING.md +3 -0
  69. package/app/components/01.atoms/grids/grid-stack/GridStack.vue +2 -0
  70. package/app/components/01.atoms/grids/grid-stack/stories/GridStack.stories.ts +41 -0
  71. package/app/components/01.atoms/grids/grid-stack/tests/GridStack.spec.ts +26 -13
  72. package/app/components/01.atoms/grids/masonry-grid/CONSUMER-STYLING.md +15 -0
  73. package/app/components/01.atoms/grids/masonry-grid/MasonryGrid.vue +51 -31
  74. package/app/components/01.atoms/grids/masonry-grid/stories/MasonryGrid.stories.ts +48 -0
  75. package/app/components/01.atoms/grids/masonry-grid/tests/MasonryGrid.spec.ts +84 -29
  76. package/app/components/01.atoms/page-row/CONSUMER-STYLING.md +6 -1
  77. package/app/components/01.atoms/page-row/PageRow.vue +16 -15
  78. package/app/components/01.atoms/page-row/stories/PageRow.stories.ts +23 -0
  79. package/app/components/01.atoms/page-row/tests/PageRow.spec.ts +6 -5
  80. package/app/components/02.molecules/action-menu/ActionMenu.vue +6 -2
  81. package/app/components/02.molecules/action-menu/ActionMenuItem.vue +6 -3
  82. package/app/components/02.molecules/action-menu/CONSUMER-STYLING.md +11 -4
  83. package/app/components/02.molecules/action-menu/stories/ActionMenu.stories.ts +130 -18
  84. package/app/components/02.molecules/expandable/accordion-chat/AccordionChat.vue +227 -0
  85. package/app/components/02.molecules/expandable/accordion-chat/CONSUMER-STYLING.md +150 -0
  86. package/app/components/02.molecules/expandable/accordion-chat/stories/AccordionChat.stories.ts +347 -0
  87. package/app/components/02.molecules/expandable/accordion-chat/tests/AccordionChat.spec.ts +205 -0
  88. package/app/components/02.molecules/expandable/accordion-chat/tests/__snapshots__/AccordionChat.spec.ts.snap +40 -0
  89. package/app/components/02.molecules/expandable/{accordian/AccordianCore.vue → accordion-group/AccordionGroup.vue} +32 -20
  90. package/app/components/02.molecules/expandable/accordion-group/CONSUMER-STYLING.md +66 -0
  91. package/app/components/02.molecules/expandable/accordion-group/stories/AccordionGroup.stories.ts +238 -0
  92. package/app/components/02.molecules/expandable/accordion-group/tests/AccordionGroup.spec.ts +172 -0
  93. package/app/components/02.molecules/expandable/accordion-group/tests/__snapshots__/AccordionGroup.spec.ts.snap +45 -0
  94. package/app/components/02.molecules/expandable/expanding-panel/CONSUMER-STYLING.md +1 -0
  95. package/app/components/02.molecules/expandable/expanding-panel/ExpandingPanel.vue +11 -6
  96. package/app/components/02.molecules/expandable/expanding-panel/stories/ExpandingPanel.stories.ts +82 -1
  97. package/app/components/02.molecules/expandable/expanding-panel/tests/ExpandingPanel.spec.ts +24 -30
  98. package/app/components/02.molecules/expandable/expanding-panel/tests/__snapshots__/ExpandingPanel.spec.ts.snap +8 -8
  99. package/app/components/02.molecules/expandable/expanding-panel-classic/CONSUMER-STYLING.md +1 -0
  100. package/app/components/02.molecules/expandable/expanding-panel-classic/ExpandingPanelClassic.vue +10 -5
  101. package/app/components/02.molecules/expandable/expanding-panel-classic/stories/ExpandingPanelClassic.stories.ts +83 -1
  102. package/app/components/02.molecules/expandable/expanding-panel-classic/tests/ExpandingPanelClassic.spec.ts +24 -30
  103. package/app/components/02.molecules/expandable/expanding-panel-classic/tests/__snapshots__/ExpandingPanelClassic.spec.ts.snap +8 -8
  104. package/app/components/02.molecules/navigation/deep-expanding-menu/CONSUMER-STYLING.md +63 -44
  105. package/app/components/02.molecules/navigation/deep-expanding-menu/DeepExpandingMenu.vue +74 -37
  106. package/app/components/02.molecules/navigation/deep-expanding-menu/stories/DeepExpandingMenu.stories.ts +139 -19
  107. package/app/components/02.molecules/navigation/deep-expanding-menu/tests/DeepExpandingMenu.spec.ts +90 -27
  108. package/app/components/02.molecules/navigation/deep-expanding-menu/tests/__snapshots__/DeepExpandingMenu.spec.ts.snap +8 -8
  109. package/app/components/02.molecules/navigation/deep-expanding-menu-classic/CONSUMER-STYLING.md +58 -26
  110. package/app/components/02.molecules/navigation/deep-expanding-menu-classic/DeepExpandingMenuClassic.vue +113 -57
  111. package/app/components/02.molecules/navigation/deep-expanding-menu-classic/stories/DeepExpandingMenuClassic.stories.ts +139 -19
  112. package/app/components/02.molecules/navigation/deep-expanding-menu-classic/tests/DeepExpandingMenuClassic.spec.ts +140 -22
  113. package/app/components/02.molecules/navigation/deep-expanding-menu-classic/tests/__snapshots__/DeepExpandingMenuClassic.spec.ts.snap +9 -9
  114. package/app/components/02.molecules/qr-code/CONSUMER-STYLING.md +22 -7
  115. package/app/components/02.molecules/qr-code/CaptureQrCode.vue +63 -71
  116. package/app/components/02.molecules/qr-code/DecodeQrCode.vue +47 -10
  117. package/app/components/02.molecules/qr-code/stories/CaptureQrCode.stories.ts +114 -26
  118. package/app/components/02.molecules/qr-code/stories/DecodeQrCode.stories.ts +115 -24
  119. package/app/components/02.molecules/qr-code/tests/CaptureQrCode.spec.ts +124 -51
  120. package/app/components/02.molecules/qr-code/tests/DecodeQrCode.spec.ts +74 -25
  121. package/app/components/02.molecules/samaritan-prompt/CONSUMER-STYLING.md +22 -10
  122. package/app/components/02.molecules/samaritan-prompt/SamaritanPrompt.vue +30 -12
  123. package/app/components/02.molecules/samaritan-prompt/SamaritanPromptMixed.vue +34 -10
  124. package/app/components/02.molecules/samaritan-prompt/stories/SamaritanPrompt.stories.ts +55 -4
  125. package/app/components/02.molecules/samaritan-prompt/stories/SamaritanPromptMixed.stories.ts +67 -7
  126. package/app/components/02.molecules/samaritan-prompt/tests/SamaritanPrompt.spec.ts +113 -29
  127. package/app/components/02.molecules/samaritan-prompt/tests/SamaritanPromptMixed.spec.ts +92 -45
  128. package/app/components/03.organisms/image-galleries/carousel-flip/CONSUMER-STYLING.md +82 -29
  129. package/app/components/03.organisms/image-galleries/carousel-flip/CarouselFlip.vue +176 -113
  130. package/app/components/03.organisms/image-galleries/carousel-flip/stories/CarouselFlip.stories.ts +153 -23
  131. package/app/components/03.organisms/image-galleries/carousel-flip/tests/CarouselFlip.spec.ts +119 -63
  132. package/app/components/03.organisms/responsive-header/tests/__snapshots__/NavigationItems.spec.ts.snap +2 -2
  133. package/app/components/03.organisms/responsive-header/tests/__snapshots__/ResponsiveHeader.spec.ts.snap +2 -2
  134. package/app/components/03.organisms/services/service-detail/tests/ServiceDetail.spec.ts +8 -8
  135. package/app/components/03.organisms/services/service-detail/tests/__snapshots__/ServiceDetail.spec.ts.snap +4 -4
  136. package/app/components/03.organisms/site-header/CONSUMER-STYLING.md +41 -23
  137. package/app/components/03.organisms/site-header/SiteHeader.vue +16 -21
  138. package/app/components/03.organisms/site-header/stories/SiteHeader.stories.ts +138 -0
  139. package/app/components/03.organisms/site-header/tests/SiteHeader.spec.ts +53 -26
  140. package/app/components/03.organisms/site-header/tests/__snapshots__/SiteHeader.spec.ts.snap +2 -2
  141. package/app/types/components/qr-code.d.ts +12 -0
  142. package/app/utils/graphemes.ts +8 -0
  143. package/package.json +1 -1
  144. package/.claude/skills/components/accordian-core.md +0 -180
  145. package/.vscode/srcdev-component-accordian-core.code-snippets +0 -74
  146. package/app/components/01.atoms/animations/entry/.nuxt/app.config.mjs +0 -21
  147. package/app/components/01.atoms/animations/entry/.nuxt/components.d.ts +0 -66
  148. package/app/components/01.atoms/animations/entry/.nuxt/imports.d.ts +0 -30
  149. package/app/components/01.atoms/animations/entry/.nuxt/manifest/meta/test.json +0 -1
  150. package/app/components/01.atoms/animations/entry/.nuxt/types/app.config.d.ts +0 -35
  151. package/app/components/01.atoms/animations/entry/.nuxt/types/build.d.ts +0 -23
  152. package/app/components/01.atoms/animations/entry/.nuxt/types/builder-env.d.ts +0 -1
  153. package/app/components/01.atoms/animations/entry/.nuxt/types/components.d.ts +0 -71
  154. package/app/components/01.atoms/animations/entry/.nuxt/types/imports.d.ts +0 -386
  155. package/app/components/01.atoms/animations/entry/.nuxt/types/middleware.d.ts +0 -11
  156. package/app/components/01.atoms/animations/entry/.nuxt/types/modules.d.ts +0 -79
  157. package/app/components/01.atoms/animations/entry/.nuxt/types/nitro-layouts.d.ts +0 -17
  158. package/app/components/01.atoms/animations/entry/.nuxt/types/nitro-nuxt.d.ts +0 -64
  159. package/app/components/01.atoms/animations/entry/.nuxt/types/plugins.d.ts +0 -29
  160. package/app/components/01.atoms/animations/entry/.nuxt/types/runtime-config.d.ts +0 -32
  161. package/app/components/01.atoms/animations/entry/.nuxt/types/shared-imports.d.ts +0 -10
  162. package/app/components/01.atoms/animations/entry/.nuxt/types/vue-shim.d.ts +0 -0
  163. package/app/components/01.atoms/animations/entry/.nuxtrc +0 -1
  164. package/app/components/01.atoms/animations/entry/node_modules/.cache/nuxt/chrome-workspace.json +0 -1
  165. package/app/components/01.atoms/animations/entry/node_modules/.cache/vite/client/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +0 -1
  166. package/app/components/02.molecules/expandable/accordian/CONSUMER-STYLING.md +0 -100
  167. package/app/components/02.molecules/expandable/accordian/stories/AccordianCore.stories.ts +0 -272
  168. package/app/components/02.molecules/expandable/accordian/tests/AccordianCore.spec.ts +0 -257
  169. package/app/components/02.molecules/expandable/accordian/tests/__snapshots__/AccordianCore.spec.ts.snap +0 -95
  170. package/app/components/03.organisms/responsive-header/tests/.nuxt/app.config.mjs +0 -21
  171. package/app/components/03.organisms/responsive-header/tests/.nuxt/components.d.ts +0 -66
  172. package/app/components/03.organisms/responsive-header/tests/.nuxt/imports.d.ts +0 -30
  173. package/app/components/03.organisms/responsive-header/tests/.nuxt/manifest/meta/test.json +0 -1
  174. package/app/components/03.organisms/responsive-header/tests/.nuxt/types/app.config.d.ts +0 -35
  175. package/app/components/03.organisms/responsive-header/tests/.nuxt/types/build.d.ts +0 -23
  176. package/app/components/03.organisms/responsive-header/tests/.nuxt/types/builder-env.d.ts +0 -1
  177. package/app/components/03.organisms/responsive-header/tests/.nuxt/types/components.d.ts +0 -71
  178. package/app/components/03.organisms/responsive-header/tests/.nuxt/types/imports.d.ts +0 -386
  179. package/app/components/03.organisms/responsive-header/tests/.nuxt/types/middleware.d.ts +0 -11
  180. package/app/components/03.organisms/responsive-header/tests/.nuxt/types/modules.d.ts +0 -79
  181. package/app/components/03.organisms/responsive-header/tests/.nuxt/types/nitro-layouts.d.ts +0 -17
  182. package/app/components/03.organisms/responsive-header/tests/.nuxt/types/nitro-nuxt.d.ts +0 -64
  183. package/app/components/03.organisms/responsive-header/tests/.nuxt/types/plugins.d.ts +0 -29
  184. package/app/components/03.organisms/responsive-header/tests/.nuxt/types/runtime-config.d.ts +0 -32
  185. package/app/components/03.organisms/responsive-header/tests/.nuxt/types/shared-imports.d.ts +0 -10
  186. package/app/components/03.organisms/responsive-header/tests/.nuxt/types/vue-shim.d.ts +0 -0
  187. package/app/components/03.organisms/responsive-header/tests/node_modules/.cache/nuxt/chrome-workspace.json +0 -1
@@ -2,6 +2,12 @@
2
2
 
3
3
  ## Public token API
4
4
 
5
+ > Changed 2026-10-21: colour defaults come from `--theme-*` tokens (were `white`,
6
+ > `hsl(0, 29%, 3%)` and `--slate-10`), `--carousel-flip-button-icon-colour` is new, and the
7
+ > `--carousel-flip-focus-outline-*` tokens now style the prev/next buttons as well as the markers.
8
+
9
+ ### Layout
10
+
5
11
  | Token | Default | Controls |
6
12
  |---|---|---|
7
13
  | `--carousel-flip-gap` | `1rem` | Gap between items inside the item track |
@@ -9,34 +15,80 @@
9
15
  | `--carousel-flip-display-max-width` | `80rem` | Max width of the item track and the controls bar |
10
16
  | `--carousel-flip-item-max-width` | `80rem` | Max width of a single item, before subtracting the edge-preview width |
11
17
  | `--carousel-flip-edge-preview-width` | `4rem` | How much of the neighbouring items peek in at each edge |
12
- | `--carousel-flip-marker-gap` | `1rem` | Gap between marker (dot) buttons |
13
- | `--carousel-flip-marker-active-colour` | `var(--slate-10)` | Active marker background colour |
14
- | `--carousel-flip-button-background-colour` | `white` | Prev/next button background colour |
15
- | `--carousel-flip-button-border-width` | `0.1rem` | Prev/next button border width |
16
- | `--carousel-flip-button-border-colour` | `hsl(0, 29%, 3%)` | Prev/next button border colour |
17
- | `--carousel-flip-button-border-radius` | `100vw` | Prev/next button corner radius |
18
- | `--carousel-flip-button-padding` | `0.8rem` | Prev/next button padding |
19
- | `--carousel-flip-button-icon-size` | `2.4rem` | Prev/next button icon glyph size |
20
- | `--carousel-flip-buttons-gap` | `1rem` | Gap between prev/next buttons in the `controls-grouped-right` layout |
21
18
  | `--carousel-flip-overlay-controls-offset` | `1rem` | Distance of the controls bar from the bottom edge in the `overlay` layout |
22
- | `--carousel-flip-focus-outline-width` | `0.2rem` | Marker button focus-visible outline width |
23
- | `--carousel-flip-focus-outline-colour` | `var(--theme-ring)` | Marker button focus-visible outline colour |
24
- | `--carousel-flip-focus-outline-offset` | `0.2rem` | Marker button focus-visible outline offset |
25
19
 
26
- ```css
27
- .my-page {
28
- --carousel-flip-button-background-colour: var(--theme-surface);
29
- --carousel-flip-marker-active-colour: var(--brand-primary);
30
- }
31
- ```
20
+ ### Markers
21
+
22
+ | Token | Default | Controls |
23
+ |---|---|---|
24
+ | `--carousel-flip-marker-gap` | `1rem` | Gap between marker (dot) buttons, in both directions (the markers wrap) |
25
+ | `--carousel-flip-marker-active-colour` | `var(--theme-surface)` | Active marker background colour |
26
+
27
+ The markers otherwise keep the browser's button styling: size and shape them on
28
+ `.carousel-flip-marker` (see the story's `carousel-flip-demo` styles for an example).
29
+
30
+ ### Prev/next buttons
31
+
32
+ | Token | Default | Controls |
33
+ |---|---|---|
34
+ | `--carousel-flip-button-icon-colour` | `var(--theme-text)` | Icon colour |
35
+ | `--carousel-flip-button-background-colour` | `var(--theme-surface-subtle)` | Background colour |
36
+ | `--carousel-flip-button-border-width` | `0.1rem` | Border width |
37
+ | `--carousel-flip-button-border-colour` | `var(--theme-border)` | Border colour |
38
+ | `--carousel-flip-button-border-radius` | `100vw` | Corner radius |
39
+ | `--carousel-flip-button-padding` | `0.8rem` | Padding |
40
+ | `--carousel-flip-button-icon-size` | `2.4rem` | Icon glyph size |
41
+ | `--carousel-flip-buttons-gap` | `1rem` | Gap between the buttons in the `controls-grouped-right` layout |
42
+ | `--carousel-flip-button-align` | `center` | Vertical alignment of each button in its row: `start`, `center` or `end` (every layout except `controls-grouped-right`, where the buttons sit in the controls row) |
43
+ | `--carousel-flip-button-inline-offset` | `0px` | `sides` and `controls-flanking` only: pushes each button outward from its default spot (negative pulls it inward over the track). Mirrors in right-to-left |
44
+ | `--carousel-flip-button-edge-inset` | `calc(var(--carousel-flip-edge-preview-width, 4rem) + 0.8rem)` | `track-edges` only: distance of each button from the visible track's end, so the default clears the edge preview |
45
+
46
+ The buttons are `InputButton` with `variant="inline"`, which adds no colours of its own; these
47
+ tokens style them.
32
48
 
33
- Or scope to a single instance via `styleClassPassthrough`:
49
+ **Placement.** In `sides` the buttons sit at the outer edges of the component's own box, so with
50
+ `allowCarouselOverflow` on they can land over the neighbouring items that peek out past it. Use
51
+ `buttonLayout="track-edges"` to keep them on the active frame, or the offset token to move them.
52
+ To reach the viewport edges from a centred carousel of known width, set
53
+ `--carousel-flip-button-inline-offset: calc((100vw - <that width>) / 2 - 1.6rem)`. A percentage
54
+ doesn't work there (on `translate` it resolves against the button), `vw` includes the scrollbar,
55
+ and an offset past the viewport can cause horizontal scrolling.
34
56
 
35
- ```vue
36
- <CarouselFlip style-class-passthrough="hero-carousel">...</CarouselFlip>
37
- ```
57
+ > Added 2026-10-21: the three placement tokens and the `track-edges` layout.
38
58
 
39
- ---
59
+ ### Focus
60
+
61
+ | Token | Default | Controls |
62
+ |---|---|---|
63
+ | `--carousel-flip-focus-outline-width` | `0.2rem` | `:focus-visible` outline width on markers and prev/next buttons |
64
+ | `--carousel-flip-focus-outline-colour` | `var(--theme-ring)` | `:focus-visible` outline colour |
65
+ | `--carousel-flip-focus-outline-offset` | `0.2rem` | `:focus-visible` outline offset |
66
+
67
+ Private (not public API): `--_carousel-item-track-gap`, read from `--carousel-flip-gap`, and
68
+ `--_carousel-flip-button-direction` (`1`, or `-1` under `:dir(rtl)`).
69
+
70
+ ## State hooks
71
+
72
+ | Hook | Meaning |
73
+ |---|---|
74
+ | `.carousel-flip[data-button-layout="…"]` | The `buttonLayout` prop |
75
+ | `.carousel-flip[data-mounted]` | Setup finished; the root fades from `opacity: 0` to `1` |
76
+ | `.carousel-flip-track[data-allow-overflow]` | `allowCarouselOverflow` is on |
77
+ | `.carousel-flip-item[aria-current="true"]` | The active item |
78
+ | `.carousel-flip-item[data-loaded]` | The user has navigated at least once; items get their CSS transition |
79
+ | `.carousel-flip-marker[data-active]` | The active marker (also `aria-current="true"`) |
80
+
81
+ Inner elements: `.carousel-flip-track`, `.carousel-flip-item`, `.carousel-flip-controls`,
82
+ `.carousel-flip-markers` (the `<ul>`), `.carousel-flip-markers-item`, `.carousel-flip-marker`,
83
+ `.carousel-flip-buttons`, `.carousel-flip-button` (with `.carousel-flip-button-prev` /
84
+ `.carousel-flip-button-next`), `.carousel-flip-button-icon`.
85
+
86
+ > Changed 2026-10-21: these were `.item-container`, `.item`, `.controls-container`,
87
+ > `.markers-container`/`.markers-list`, `.markers-item`, `.btn-marker`, `.buttons-container`,
88
+ > `.btn-action`/`.btn-prev`/`.btn-next` and `.arrows-icon`, with state classes `.mounted`,
89
+ > `.allow-overflow`, `.loaded` and `.active`. The markers lost their extra wrapper div.
90
+
91
+ The controls bar and the prev/next buttons only render when there are at least two items.
40
92
 
41
93
  ## Local overrides
42
94
 
@@ -46,14 +98,15 @@ Set the tokens above on an element you own (a page or section class, or a class
46
98
  `<style scoped>`, tokens set on your element still work, but selectors that reach inside the component need
47
99
  `:deep()`. Patterns and examples: `.claude/skills/component-local-style-override.md`.
48
100
 
49
- ---
101
+ ## Class passthrough
102
+
103
+ `style-class-passthrough` adds classes to the root `<section>`. Reactive: changing the prop
104
+ replaces the previous classes (until 2026-10-21 later changes were ignored).
50
105
 
51
106
  ## Notes
52
107
 
53
108
  - `--carousel-flip-display-max-width`/`--carousel-flip-item-max-width` previously had no declared
54
- value anywhere in the component (referenced via `var()` with no fallback) — this meant the
55
- edge-preview offset and max-width caps silently never applied in production. Fixed 2026-09-21;
56
- see the component's skill doc for detail.
109
+ value anywhere in the component (referenced via `var()` with no fallback), so the edge-preview
110
+ offset and max-width caps never applied. Fixed 2026-09-21; see the component's skill doc.
57
111
  - `useFlipAnimation`/`useSpringEffect`/`transitionSpeed` control animation behaviour via props,
58
- not tokens — use the props rather than CSS overrides for those.
59
-
112
+ not tokens.
@@ -4,65 +4,79 @@
4
4
  class="carousel-flip"
5
5
  :class="[elementClasses]"
6
6
  :data-button-layout="buttonLayout"
7
- role="region"
7
+ :data-mounted="isMounted ? '' : undefined"
8
8
  :aria-label="ariaLabel"
9
9
  >
10
10
  <div aria-live="polite" aria-atomic="true" class="sr-only">
11
- {{ props.itemPositionAnnouncement(currentActiveIndex + 1, itemCount) }}
11
+ {{ itemCount > 0 ? props.itemPositionAnnouncement(currentActiveIndex + 1, itemCount) : "" }}
12
12
  </div>
13
13
 
14
14
  <div
15
15
  ref="carouselContainerRef"
16
16
  tabindex="0"
17
- class="item-container"
18
- :class="{ 'allow-overflow': allowCarouselOverflow }"
17
+ class="carousel-flip-track"
18
+ :data-allow-overflow="allowCarouselOverflow ? '' : undefined"
19
19
  role="group"
20
20
  :aria-label="itemsAriaLabel"
21
21
  >
22
22
  <div
23
23
  v-for="(item, index) in carouselDataIds"
24
- :key="index"
24
+ :key="`${index}-${item}`"
25
25
  ref="carouselItems"
26
- class="item"
27
- :class="{ loaded: carouselInitComplete && userHasInteracted }"
26
+ class="carousel-flip-item"
27
+ :data-loaded="carouselInitComplete && userHasInteracted ? '' : undefined"
28
28
  :data-id="item"
29
- :aria-current="currentActiveIndex === index ? 'true' : 'false'"
29
+ :aria-current="currentActiveIndex === index ? 'true' : undefined"
30
30
  >
31
31
  <slot :name="item"></slot>
32
32
  </div>
33
33
  </div>
34
34
 
35
- <div v-if="showControls" ref="controlsContainerRef" tabindex="0" class="controls-container">
36
- <div class="markers-container">
37
- <ul class="markers-list">
38
- <li v-for="index in itemCount" :key="index" class="markers-item">
39
- <button
40
- type="button"
41
- class="btn-marker"
42
- :class="[{ active: displayActiveIndex === index - 1 }]"
43
- :aria-label="`${jumpToItemLabel} ${index}`"
44
- @click.prevent="jumpToFrame(index - 1)"
45
- ></button>
46
- </li>
47
- </ul>
48
- </div>
35
+ <div v-if="showControls && itemCount > 1" ref="controlsContainerRef" class="carousel-flip-controls">
36
+ <ul class="carousel-flip-markers" role="list">
37
+ <li v-for="index in itemCount" :key="index" class="carousel-flip-markers-item">
38
+ <button
39
+ type="button"
40
+ class="carousel-flip-marker"
41
+ :data-active="displayActiveIndex === index - 1 ? '' : undefined"
42
+ :aria-current="displayActiveIndex === index - 1 ? 'true' : undefined"
43
+ :aria-label="`${jumpToItemLabel} ${index}`"
44
+ @click.prevent="jumpToFrame(index - 1)"
45
+ ></button>
46
+ </li>
47
+ </ul>
49
48
  </div>
50
49
 
51
- <div class="buttons-container">
52
- <button type="button" class="btn-action btn-prev" :aria-label="previousLabel" @click.prevent="actionPrevious()">
53
- <slot name="prev-icon">
54
- <Icon :name="prevIcon" class="arrows-icon" />
55
- </slot>
56
- </button>
57
- <button type="button" class="btn-action btn-next" :aria-label="nextLabel" @click.prevent="actionNext()">
58
- <slot name="next-icon">
59
- <Icon :name="nextIcon" class="arrows-icon" />
60
- </slot>
61
- </button>
50
+ <div v-if="itemCount > 1" class="carousel-flip-buttons">
51
+ <InputButton
52
+ variant="inline"
53
+ class="carousel-flip-button carousel-flip-button-prev"
54
+ :button-text="previousLabel"
55
+ @click.prevent="actionPrevious()"
56
+ >
57
+ <template #iconOnly>
58
+ <slot name="prev-icon">
59
+ <Icon :name="prevIcon" class="carousel-flip-button-icon" />
60
+ </slot>
61
+ </template>
62
+ </InputButton>
63
+ <InputButton
64
+ variant="inline"
65
+ class="carousel-flip-button carousel-flip-button-next"
66
+ :button-text="nextLabel"
67
+ @click.prevent="actionNext()"
68
+ >
69
+ <template #iconOnly>
70
+ <slot name="next-icon">
71
+ <Icon :name="nextIcon" class="carousel-flip-button-icon" />
72
+ </slot>
73
+ </template>
74
+ </InputButton>
62
75
  </div>
63
76
  </section>
64
77
  </template>
65
78
 
79
+
66
80
  <script setup lang="ts">
67
81
  import { useEventListener, useResizeObserver, useSwipe } from "@vueuse/core";
68
82
 
@@ -73,7 +87,7 @@ interface Props {
73
87
  allowCarouselOverflow?: boolean;
74
88
  useFlipAnimation?: boolean;
75
89
  useSpringEffect?: boolean;
76
- buttonLayout?: "sides" | "controls-flanking" | "controls-grouped-right" | "overlay";
90
+ buttonLayout?: "sides" | "controls-flanking" | "controls-grouped-right" | "overlay" | "track-edges";
77
91
  showControls?: boolean;
78
92
  ariaLabel?: string;
79
93
  itemsAriaLabel?: string;
@@ -104,8 +118,12 @@ const props = withDefaults(defineProps<Props>(), {
104
118
  nextIcon: "ic:outline-keyboard-arrow-right",
105
119
  });
106
120
 
121
+ const { elementClasses, resetElementClasses } = useStyleClassPassthrough(props.styleClassPassthrough);
107
122
 
108
- const { elementClasses } = useStyleClassPassthrough(props.styleClassPassthrough);
123
+ watch(
124
+ () => props.styleClassPassthrough,
125
+ () => resetElementClasses(props.styleClassPassthrough)
126
+ );
109
127
 
110
128
  const carouselWrapperRef = ref<HTMLDivElement | null>(null);
111
129
  const carouselContainerRef = ref<HTMLDivElement | null>(null);
@@ -113,14 +131,15 @@ const carouselItemsRef = useTemplateRef<HTMLDivElement[]>("carouselItems");
113
131
  const controlsContainerRef = ref<HTMLDivElement | null>(null);
114
132
  const carouselInitComplete = ref(false);
115
133
  const userHasInteracted = ref(false);
134
+ const isMounted = ref(false);
116
135
 
117
136
  const initialItemOffset = computed(() => {
118
137
  return props.useFlipAnimation ? 1 : 2;
119
138
  });
120
139
 
121
140
  const currentIndex = ref(0);
122
- const itemCount = ref(props.carouselDataIds.length);
123
- const transitionSpeedStr = props.transitionSpeed + "ms";
141
+ const itemCount = computed(() => props.carouselDataIds.length);
142
+ const transitionSpeedStr = computed(() => `${props.transitionSpeed}ms`);
124
143
 
125
144
  const itemWidth = ref(0);
126
145
  const itemWidthOffsetStr = computed(() => {
@@ -176,7 +195,7 @@ function analyzeOffsets(offsets: number[]) {
176
195
  }
177
196
 
178
197
  const reorderItems = (skipAnimation: boolean = false) => {
179
- if (!carouselItemsRef?.value) return;
198
+ if (!carouselItemsRef?.value || itemCount.value < 1) return;
180
199
 
181
200
  // Capture positions before reordering (only if we're going to animate)
182
201
  const beforeRects = skipAnimation ? [] : carouselItemsRef.value.map((item) => item.getBoundingClientRect());
@@ -274,18 +293,18 @@ const reorderItems = (skipAnimation: boolean = false) => {
274
293
  if (shouldTransition) {
275
294
  if (props.allowCarouselOverflow) {
276
295
  if (props.useFlipAnimation) {
277
- transitionProperties = `transform ${transitionSpeedStr} ${timingFunction}`;
296
+ transitionProperties = `transform ${transitionSpeedStr.value} ${timingFunction}`;
278
297
  } else {
279
298
  if (leftValues.minorityIndex !== index) {
280
- transitionProperties = `transform ${transitionSpeedStr} ${timingFunction}`;
299
+ transitionProperties = `transform ${transitionSpeedStr.value} ${timingFunction}`;
281
300
  }
282
301
  }
283
302
  } else {
284
303
  if (props.useFlipAnimation) {
285
- transitionProperties = `transform ${transitionSpeedStr} ${timingFunction}`;
304
+ transitionProperties = `transform ${transitionSpeedStr.value} ${timingFunction}`;
286
305
  } else {
287
306
  if (leftValues.minorityIndex !== index) {
288
- transitionProperties = `transform ${transitionSpeedStr} ${timingFunction}`;
307
+ transitionProperties = `transform ${transitionSpeedStr.value} ${timingFunction}`;
289
308
  }
290
309
  }
291
310
  }
@@ -315,7 +334,7 @@ const reorderItems = (skipAnimation: boolean = false) => {
315
334
  };
316
335
 
317
336
  const actionPrevious = () => {
318
- if (!carouselInitComplete.value || !carouselItemsRef?.value) return;
337
+ if (!carouselInitComplete.value || !carouselItemsRef?.value || itemCount.value < 2) return;
319
338
 
320
339
  userHasInteracted.value = true;
321
340
 
@@ -330,7 +349,7 @@ const actionPrevious = () => {
330
349
  };
331
350
 
332
351
  const actionNext = () => {
333
- if (!carouselInitComplete.value || !carouselItemsRef?.value) return;
352
+ if (!carouselInitComplete.value || !carouselItemsRef?.value || itemCount.value < 2) return;
334
353
 
335
354
  userHasInteracted.value = true;
336
355
 
@@ -381,11 +400,8 @@ const initialSetup = () => {
381
400
  carouselInitComplete.value = true;
382
401
  checkAndMoveLastItem();
383
402
 
384
- // Add mounted class to trigger opacity transition after setup is complete
385
403
  nextTick(() => {
386
- if (carouselWrapperRef.value) {
387
- carouselWrapperRef.value.classList.add("mounted");
388
- }
404
+ isMounted.value = true;
389
405
  });
390
406
  };
391
407
 
@@ -416,6 +432,14 @@ useEventListener(controlsContainerRef, "keydown", (event: KeyboardEvent) => {
416
432
  }
417
433
  });
418
434
 
435
+ watch(
436
+ () => props.carouselDataIds.join("\u0000"),
437
+ () => {
438
+ if (currentActiveIndex.value >= itemCount.value) currentActiveIndex.value = 0;
439
+ nextTick(initialSetup);
440
+ }
441
+ );
442
+
419
443
  useResizeObserver(carouselWrapperRef, async () => {
420
444
  initialSetup();
421
445
  });
@@ -429,9 +453,10 @@ onMounted(() => {
429
453
  @layer components {
430
454
  .carousel-flip {
431
455
  --_carousel-item-track-gap: var(--carousel-flip-gap, 1rem);
456
+ --_carousel-flip-button-direction: 1;
432
457
 
433
458
  display: grid;
434
- grid-template-columns: auto 1fr auto;
459
+ grid-template-columns: auto minmax(0, 1fr) auto;
435
460
  grid-template-rows: 1fr auto;
436
461
  grid-template-areas:
437
462
  "prev carousel next"
@@ -439,41 +464,36 @@ onMounted(() => {
439
464
  gap: var(--carousel-flip-layout-gap, 1rem);
440
465
  opacity: 0;
441
466
 
442
- &.mounted {
467
+ &[data-mounted] {
443
468
  opacity: 1;
444
469
  }
445
470
 
446
- .sr-only {
447
- position: absolute;
448
- width: 1px;
449
- height: 1px;
450
- padding: 0;
451
- margin: -1px;
452
- overflow: hidden;
453
- clip: rect(0, 0, 0, 0);
454
- white-space: nowrap;
455
- border: 0;
471
+ &:dir(rtl) {
472
+ --_carousel-flip-button-direction: -1;
456
473
  }
457
474
 
458
- .item-container {
475
+ .carousel-flip-track {
459
476
  grid-area: carousel;
460
477
  display: flex;
461
478
  gap: var(--_carousel-item-track-gap);
462
479
  overflow-x: hidden;
463
480
  position: relative;
464
481
  isolation: isolate;
482
+ min-inline-size: 0;
465
483
 
466
484
  max-inline-size: var(--carousel-flip-display-max-width, 80rem);
467
485
  margin-inline: auto;
468
486
 
469
- &.allow-overflow {
487
+ &[data-allow-overflow] {
470
488
  overflow-x: initial;
471
489
  }
472
490
 
473
- .item {
491
+ .carousel-flip-item {
474
492
  display: flex;
475
493
  flex: 0 0 100%;
476
494
  position: relative;
495
+ min-inline-size: 0;
496
+ overflow-wrap: anywhere;
477
497
 
478
498
  margin-inline: auto;
479
499
 
@@ -488,7 +508,7 @@ onMounted(() => {
488
508
  )
489
509
  0;
490
510
 
491
- &.loaded {
511
+ &[data-loaded] {
492
512
  @media (prefers-reduced-motion: no-preference) {
493
513
  transition: transform v-bind(transitionSpeedStr) ease;
494
514
  }
@@ -496,90 +516,96 @@ onMounted(() => {
496
516
  }
497
517
  }
498
518
 
499
- .controls-container {
519
+ .carousel-flip-controls {
500
520
  grid-area: controls;
501
521
  display: flex;
502
522
  align-items: center;
503
523
  justify-content: center;
524
+ min-inline-size: 0;
504
525
  max-inline-size: var(--carousel-flip-display-max-width, 80rem);
505
526
  margin-inline: auto;
506
527
 
507
- .markers-container {
508
- .markers-list {
509
- display: flex;
510
- flex-direction: row;
511
- gap: var(--carousel-flip-marker-gap, 1rem);
512
- list-style-type: none;
513
- margin: unset;
514
- padding: unset;
515
-
516
- .markers-item {
517
- .btn-marker {
518
- border: 1px solid transparent;
519
- outline: 1px solid transparent;
520
- box-shadow: none;
521
- cursor: pointer;
522
- transition: background-color v-bind(transitionSpeedStr) linear;
523
-
524
- &.active {
525
- background-color: var(--carousel-flip-marker-active-colour, var(--slate-10));
526
- }
528
+ .carousel-flip-markers {
529
+ display: flex;
530
+ flex-wrap: wrap;
531
+ justify-content: center;
532
+ gap: var(--carousel-flip-marker-gap, 1rem);
533
+ list-style-type: none;
534
+ margin: unset;
535
+ padding: unset;
536
+
537
+ .carousel-flip-marker {
538
+ border: 1px solid transparent;
539
+ outline: 1px solid transparent;
540
+ box-shadow: none;
541
+ cursor: pointer;
542
+ transition: background-color v-bind(transitionSpeedStr) linear;
543
+
544
+ &[data-active] {
545
+ background-color: var(--carousel-flip-marker-active-colour, var(--theme-surface));
546
+ }
527
547
 
528
- &:focus-visible {
529
- outline: var(--carousel-flip-focus-outline-width, 0.2rem) solid
530
- var(--carousel-flip-focus-outline-colour, var(--theme-ring));
531
- outline-offset: var(--carousel-flip-focus-outline-offset, 0.2rem);
532
- }
533
- }
548
+ &:focus-visible {
549
+ outline: var(--carousel-flip-focus-outline-width, 0.2rem) solid
550
+ var(--carousel-flip-focus-outline-colour, var(--theme-ring));
551
+ outline-offset: var(--carousel-flip-focus-outline-offset, 0.2rem);
534
552
  }
535
553
  }
536
554
  }
537
555
  }
538
556
 
539
- .buttons-container {
557
+ .carousel-flip-buttons {
540
558
  display: contents;
541
559
  }
542
560
 
543
- .btn-prev {
544
- grid-area: prev;
545
- align-self: center;
546
- z-index: 1;
547
- }
548
-
549
- .btn-next {
550
- grid-area: next;
551
- align-self: center;
552
- z-index: 1;
553
- }
554
-
555
- .btn-action {
561
+ .carousel-flip-button {
556
562
  display: flex;
557
563
  align-items: center;
558
564
  justify-content: center;
565
+ align-self: var(--carousel-flip-button-align, center);
566
+ z-index: 1;
559
567
  cursor: pointer;
560
568
  height: fit-content;
569
+ min-height: 0;
561
570
 
562
- background-color: var(--carousel-flip-button-background-colour, white);
571
+ color: var(--carousel-flip-button-icon-colour, var(--theme-text));
572
+ background-color: var(--carousel-flip-button-background-colour, var(--theme-surface-subtle));
563
573
  border: var(--carousel-flip-button-border-width, 0.1rem) solid
564
- var(--carousel-flip-button-border-colour, hsl(0, 29%, 3%));
574
+ var(--carousel-flip-button-border-colour, var(--theme-border));
565
575
  border-radius: var(--carousel-flip-button-border-radius, 100vw);
566
576
  padding: var(--carousel-flip-button-padding, 0.8rem);
567
577
 
568
- .arrows-icon {
578
+ &:focus-visible {
579
+ outline: var(--carousel-flip-focus-outline-width, 0.2rem) solid
580
+ var(--carousel-flip-focus-outline-colour, var(--theme-ring));
581
+ outline-offset: var(--carousel-flip-focus-outline-offset, 0.2rem);
582
+ }
583
+
584
+ .carousel-flip-button-icon {
569
585
  width: var(--carousel-flip-button-icon-size, 2.4rem);
570
586
  height: var(--carousel-flip-button-icon-size, 2.4rem);
571
587
  }
572
588
  }
573
589
 
590
+ .carousel-flip-button-prev {
591
+ grid-area: prev;
592
+ translate: calc(-1 * var(--_carousel-flip-button-direction) * var(--carousel-flip-button-inline-offset, 0px)) 0;
593
+ }
594
+
595
+ .carousel-flip-button-next {
596
+ grid-area: next;
597
+ translate: calc(var(--_carousel-flip-button-direction) * var(--carousel-flip-button-inline-offset, 0px)) 0;
598
+ }
599
+
574
600
  /* ── controls-flanking: buttons flank the controls row ─────── */
575
601
  &[data-button-layout="controls-flanking"] {
576
- .btn-prev {
602
+ .carousel-flip-button-prev {
577
603
  grid-area: unset;
578
604
  grid-column: 1;
579
605
  grid-row: 2;
580
606
  }
581
607
 
582
- .btn-next {
608
+ .carousel-flip-button-next {
583
609
  grid-area: unset;
584
610
  grid-column: 3;
585
611
  grid-row: 2;
@@ -588,23 +614,59 @@ onMounted(() => {
588
614
 
589
615
  /* ── controls-grouped-right: markers centred, buttons end ────── */
590
616
  &[data-button-layout="controls-grouped-right"] {
591
- grid-template-columns: 1fr;
617
+ grid-template-columns: minmax(0, 1fr);
592
618
  grid-template-areas:
593
619
  "carousel"
594
620
  "controls";
595
621
 
596
- .buttons-container {
622
+ .carousel-flip-buttons {
597
623
  display: flex;
598
624
  align-items: center;
599
625
  gap: var(--carousel-flip-buttons-gap, 1rem);
600
626
  grid-area: controls;
601
627
  justify-self: end;
602
628
  }
629
+
630
+ .carousel-flip-button-prev,
631
+ .carousel-flip-button-next {
632
+ translate: none;
633
+ }
634
+ }
635
+
636
+ /* ── track-edges: buttons overlay the ends of the visible track ── */
637
+ &[data-button-layout="track-edges"] {
638
+ grid-template-columns: minmax(0, 1fr);
639
+ grid-template-areas:
640
+ "carousel"
641
+ "controls";
642
+
643
+ .carousel-flip-buttons {
644
+ grid-area: carousel;
645
+ display: flex;
646
+ justify-content: space-between;
647
+ inline-size: 100%;
648
+ max-inline-size: var(--carousel-flip-display-max-width, 80rem);
649
+ margin-inline: auto;
650
+ padding-inline: var(
651
+ --carousel-flip-button-edge-inset,
652
+ calc(var(--carousel-flip-edge-preview-width, 4rem) + 0.8rem)
653
+ );
654
+ box-sizing: border-box;
655
+ z-index: 1;
656
+ pointer-events: none;
657
+ }
658
+
659
+ .carousel-flip-button-prev,
660
+ .carousel-flip-button-next {
661
+ grid-area: unset;
662
+ translate: none;
663
+ pointer-events: auto;
664
+ }
603
665
  }
604
666
 
605
667
  /* ── overlay: controls float at the bottom edge of the frame ── */
606
668
  &[data-button-layout="overlay"] {
607
- .controls-container {
669
+ .carousel-flip-controls {
608
670
  grid-area: carousel;
609
671
  align-self: end;
610
672
  z-index: 1;
@@ -614,3 +676,4 @@ onMounted(() => {
614
676
  }
615
677
  }
616
678
  </style>
679
+