srcdev-nuxt-components 9.4.7 → 9.4.8

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 (49) hide show
  1. package/.claude/component-ledger/audit.json +1 -1
  2. package/.claude/component-ledger/output.html +1 -1
  3. package/.claude/skills/component-dynamic-slots.md +1 -1
  4. package/.claude/skills/components/header-block.md +92 -0
  5. package/.claude/skills/components/rotating-carousel-image.md +101 -0
  6. package/.claude/skills/components/skip-links.md +82 -0
  7. package/.claude/skills/components/tabs-core.md +187 -0
  8. package/.claude/skills/index.md +4 -0
  9. package/.vscode/srcdev-component-header-block.code-snippets +41 -0
  10. package/.vscode/srcdev-component-rotating-carousel-image.code-snippets +74 -0
  11. package/.vscode/srcdev-component-skip-links.code-snippets +55 -0
  12. package/.vscode/srcdev-component-tabs-core.code-snippets +78 -0
  13. package/app/assets/styles/setup/05.typography/02.utility-classes/_font-classes-page-heading.css +2 -1
  14. package/app/components/01.atoms/animations/rotating-carousel-image/CONSUMER-STYLING.md +54 -0
  15. package/app/components/01.atoms/animations/rotating-carousel-image/RotatingCarouselImage.vue +315 -0
  16. package/app/components/01.atoms/animations/rotating-carousel-image/stories/RotatingCarouselImage.stories.ts +156 -0
  17. package/app/components/01.atoms/animations/rotating-carousel-image/tests/RotatingCarouselImage.spec.ts +230 -0
  18. package/app/components/01.atoms/animations/rotating-carousel-image/tests/__snapshots__/RotatingCarouselImage.spec.ts.snap +19 -0
  19. package/app/components/01.atoms/navigation/skip-links/CONSUMER-STYLING.md +31 -0
  20. package/app/components/01.atoms/navigation/skip-links/SkipLinks.vue +92 -0
  21. package/app/components/01.atoms/navigation/skip-links/stories/SkipLinks.stories.ts +113 -0
  22. package/app/components/01.atoms/navigation/skip-links/tests/SkipLinks.spec.ts +71 -0
  23. package/app/components/01.atoms/navigation/skip-links/tests/__snapshots__/SkipLinks.spec.ts.snap +15 -0
  24. package/app/components/01.atoms/navigation/tabs/CONSUMER-STYLING.md +99 -0
  25. package/app/components/01.atoms/navigation/tabs/TabsCore.vue +272 -0
  26. package/app/components/01.atoms/navigation/tabs/stories/TabsCore.stories.ts +199 -0
  27. package/app/components/01.atoms/navigation/tabs/tests/TabsCore.spec.ts +274 -0
  28. package/app/components/01.atoms/text-blocks/header-block/CONSUMER-STYLING.md +43 -0
  29. package/app/components/01.atoms/text-blocks/header-block/HeaderBlock.vue +50 -0
  30. package/app/components/01.atoms/text-blocks/header-block/stories/HeaderBlock.stories.ts +111 -0
  31. package/app/components/01.atoms/text-blocks/header-block/tests/HeaderBlock.spec.ts +119 -0
  32. package/app/components/01.atoms/text-blocks/header-block/tests/__snapshots__/HeaderBlock.spec.ts.snap +5 -0
  33. package/app/components/03.organisms/site-header/SiteHeader.vue +1 -1
  34. package/app/components/03.organisms/site-header/tests/SiteHeader.spec.ts +1 -1
  35. package/app/components/03.organisms/site-header/tests/__snapshots__/SiteHeader.spec.ts.snap +3 -3
  36. package/app/composables/useTabs.ts +225 -207
  37. package/app/types/components/index.ts +2 -0
  38. package/app/types/components/rotating-carousel-image.d.ts +4 -0
  39. package/app/types/components/skip-links.d.ts +4 -0
  40. package/package.json +1 -1
  41. package/app/components/01.atoms/grids/data-grid/tests/__snapshots__/DataGrid.spec.ts.snap +0 -11
  42. package/app/components/rotating-carousel/RotatingCarouselImage.vue +0 -216
  43. package/app/components/skip-links/SkipLinks.vue +0 -60
  44. package/app/components/tabs/TabsCore.vue +0 -306
  45. package/app/components/typography/HeaderBlock.vue +0 -35
  46. /package/app/components/01.atoms/grids/{data-grid → auto-grid}/AutoGrid.vue +0 -0
  47. /package/app/components/01.atoms/grids/{data-grid → auto-grid}/stories/AutoGrid.stories.ts +0 -0
  48. /package/app/components/01.atoms/grids/{data-grid → auto-grid}/tests/AutoGrid.spec.ts +0 -0
  49. /package/app/components/01.atoms/grids/{data-grid → auto-grid}/tests/__snapshots__/AutoGrid.spec.ts.snap +0 -0
@@ -0,0 +1,82 @@
1
+ ---
2
+ name: SkipLinks
3
+ description: SkipLinks accessibility skip-navigation utility — focus-revealed jump links, props, CSS tokens, consumer styling
4
+ type: reference
5
+ ---
6
+
7
+ # SkipLinks
8
+
9
+ ## Overview
10
+
11
+ `SkipLinks` renders a set of visually-hidden links that become visible on keyboard focus, letting
12
+ keyboard and screen-reader users jump past repeated navigation straight to main content, the
13
+ footer, or any other in-page target. Place it first in the page layout, before the header/nav, so
14
+ it's the first focusable element on the page.
15
+
16
+ ## Props
17
+
18
+ | Prop | Type | Default | Required |
19
+ |------|------|---------|----------|
20
+ | `links` | `SkipLink[]` (`{ href: string; label: string }[]`) | `[{ href: "#main-content", label: "Skip to main content" }, { href: "#footer-content", label: "Skip to footer" }]` | no |
21
+ | `ariaLabel` | `string` | `"Skip navigation"` | no — override for localisation |
22
+ | `styleClassPassthrough` | `string \| string[]` | `[]` | no |
23
+
24
+ `SkipLink` is exported from `~/types/components/skip-links` (and re-exported from
25
+ `~/types/components`):
26
+
27
+ ```typescript
28
+ interface SkipLink {
29
+ href: string;
30
+ label: string;
31
+ }
32
+ ```
33
+
34
+ ## Slots
35
+
36
+ `homeLink` — optional content (typically a logo/home link) rendered before the skip-nav, wrapped
37
+ in `.skip-links__home`. Only rendered when the slot is actually used.
38
+
39
+ ```vue
40
+ <SkipLinks>
41
+ <template #homeLink>
42
+ <NuxtLink to="/" class="logo">Acme</NuxtLink>
43
+ </template>
44
+ </SkipLinks>
45
+ ```
46
+
47
+ ## Usage
48
+
49
+ ```vue
50
+ <SkipLinks
51
+ :links="[
52
+ { href: '#site-navigation', label: 'Skip to navigation' },
53
+ { href: '#main-content', label: 'Skip to main content' },
54
+ { href: '#footer-content', label: 'Skip to footer' },
55
+ ]"
56
+ />
57
+ ```
58
+
59
+ Each `href` must match the `id` of a real element on the page (e.g. `<main id="main-content">`) —
60
+ the component does not create those anchors itself.
61
+
62
+ ## Accessibility behaviour
63
+
64
+ - Links are hidden via `opacity`/`transform`, not `display`/`visibility` — they stay in the tab
65
+ order and keyboard-focusable even while visually hidden.
66
+ - The panel reveals on `:focus-within`, so tabbing onto any link shows the whole group.
67
+ - Wrapped in a `<nav :aria-label="ariaLabel">` landmark, defaulting to `"Skip navigation"`.
68
+ - The reveal transition is disabled under `prefers-reduced-motion: reduce`.
69
+ - `ariaLabel` and every link's `label` are plain string props/fields — pass translated strings
70
+ from the consumer's own i18n solution.
71
+
72
+ ## CSS custom properties
73
+
74
+ See `CONSUMER-STYLING.md` in this component's folder for the full `--skip-links-*` token API
75
+ (panel background/border, link text colour, spacing, transition duration, focus outline).
76
+
77
+ ## Notes
78
+
79
+ - Component is auto-imported in Nuxt — no import needed.
80
+ - Migrated 2026-09-15 from the unplaced `skip-links/` folder: the link set was previously
81
+ hardcoded (two fixed `<a>` tags with hardcoded English copy) — now data-driven via `links` so
82
+ consumers can add/remove targets and localise the copy without forking the component.
@@ -0,0 +1,187 @@
1
+ ---
2
+ name: TabsCore
3
+ description: TabsCore tab list/panel widget — indexed dynamic slots, arrow-key navigation, moving hover/active/underline indicators, CSS tokens
4
+ type: reference
5
+ ---
6
+
7
+ # TabsCore
8
+
9
+ ## Overview
10
+
11
+ `TabsCore` renders a WAI-ARIA tablist from indexed slots. The number of tabs is set via
12
+ `itemCount`; each tab's trigger and content come from `tab-{n}-trigger`/`tab-{n}-content` slots.
13
+ Indexed slots (rather than named dynamic slots) are used here deliberately — `itemCount` is needed
14
+ for ARIA linking across the two parallel loops (triggers and panels), not just the slot loop
15
+ itself.
16
+
17
+ Three independent moving-highlight decorators track pointer/keyboard state: a hover highlight, an
18
+ active-tab highlight, and an underline/sideline indicator — each can be turned off individually.
19
+
20
+ ## Props
21
+
22
+ | Prop | Type | Default | Required |
23
+ |------|------|---------|----------|
24
+ | `itemCount` | `number` | — | **yes** — number of tabs; drives both slot loops |
25
+ | `axis` | `"x" \| "y"` | `"x"` | no — horizontal row or vertical column layout |
26
+ | `transitionDuration` | `number` | `200` | no — ms duration of the moving indicators |
27
+ | `trackHover` | `boolean` | `true` | no — shows the moving hover highlight |
28
+ | `trackActive` | `boolean` | `true` | no — shows the moving active-tab highlight |
29
+ | `trackIndicator` | `boolean` | `true` | no — shows the moving underline/sideline indicator |
30
+ | `ariaLabel` | `string` | `"Tabs"` | no — override for localisation |
31
+ | `styleClassPassthrough` | `string \| string[]` | `[]` | no |
32
+
33
+ ## Slots
34
+
35
+ For each index `0..itemCount-1`:
36
+
37
+ - `tab-{n}-trigger` — content of the tab button.
38
+ - `tab-{n}-content` — content of that tab's panel.
39
+
40
+ ```vue
41
+ <TabsCore :item-count="2">
42
+ <template #tab-0-trigger>Overview</template>
43
+ <template #tab-0-content>Overview content…</template>
44
+ <template #tab-1-trigger>Specs</template>
45
+ <template #tab-1-content>Specs content…</template>
46
+ </TabsCore>
47
+ ```
48
+
49
+ ## Accessibility behaviour
50
+
51
+ - Renders `role="tablist"` (with `aria-label`), `role="tab"` triggers (`type="button"`), and
52
+ `role="tabpanel"` panels (`aria-labelledby` pointing at their trigger, `tabindex="0"` so keyboard
53
+ users can scroll into panel content per the WAI-ARIA Tabs Pattern).
54
+ Fixed 2026-09-15 — panels previously used `role="region"` and were hardcoded
55
+ `aria-hidden="true"` permanently, even the visible one, hiding all tab content from assistive
56
+ tech at all times.
57
+ - Roving tabindex: only the active trigger has `tabindex="0"`; the rest are `-1`.
58
+ - Arrow keys move focus and activate the target tab (automatic activation): `ArrowLeft`/`ArrowRight`
59
+ on `axis="x"`, `ArrowUp`/`ArrowDown` on `axis="y"`, wrapping at the ends; `Home`/`End` jump to the
60
+ first/last tab. Fixed 2026-09-15 — previously there was no keyboard navigation at all beyond
61
+ native Tab-key focus and click/Enter/Space activation on individual buttons.
62
+ - Focus is visible via `:focus-visible` on both triggers and panels.
63
+
64
+ ## CSS custom properties
65
+
66
+ See `CONSUMER-STYLING.md` in this component's folder for the full `--tabs-*` token API (indicator
67
+ colours, list-item spacing/typography, content-panel border/background, axis-y gap). Whether each
68
+ indicator renders at all is controlled by `trackHover`/`trackActive`/`trackIndicator`, not tokens.
69
+
70
+ ## Migration note (2026-09-15)
71
+
72
+ Previously lived at `tabs/TabsCore.vue` with options-style `defineProps`, no tier folder, and
73
+ several defects fixed in this pass:
74
+
75
+ - `querySelectorAll("[data-nav-item")` was missing its closing `]` — an invalid selector that
76
+ some browsers tolerate but that throws in strict DOM implementations (including this repo's test
77
+ environment). Fixed to `"[data-nav-item]"`.
78
+ - `aria-labelledby="channel-name"` was hardcoded on the tablist, pointing at an id that doesn't
79
+ exist anywhere in this library. Replaced with the new `ariaLabel` prop.
80
+ - `border-bottom`/`border-left` on the tab list referenced `--_tabs-border-bottom`, a private CSS
81
+ variable that was never set anywhere (dead reference — the border never rendered). Promoted to
82
+ the public `--tabs-nav-border` token with a real default.
83
+ - `tag`, `trackHover`, `trackActive`, and `trackIndicator` were all declared props with no effect
84
+ anywhere in the component or its composable (`useTabs`). `tag` had no coherent purpose (triggers
85
+ were always hardcoded `<button>`s) and was removed; the three `track*` flags were wired up to
86
+ actually gate their respective decorator elements, since removing them would have been a bigger
87
+ behavioural change than making them work as their names already implied.
88
+ - Almost every colour/spacing/typography value in the `<style>` block was hardcoded with no
89
+ override hook at all (not even a private token) — promoted to public `--tabs-*` tokens.
90
+
91
+ ## Performance notes (2026-09-15)
92
+
93
+ `useTabs` had a few real perf issues, fixed alongside the migration:
94
+
95
+ - **Layout thrashing**: `moveActiveIndicator`/`moveHoveredIndicator`/`setFinalActivePositions`/
96
+ `setFinalHoveredPositions` used to interleave `offsetLeft`/`offsetWidth`/etc. reads with
97
+ `style.setProperty` writes, forcing a synchronous reflow between each pair. All layout reads are
98
+ now batched into local variables before any writes happen.
99
+ - **`@mouseover` → `@mouseenter`**: the hover trigger listener used `mouseover`, which bubbles and
100
+ re-fires as the pointer crosses any child element inside the button. `mouseenter` fires exactly
101
+ once per tab entered.
102
+ - **Debounced settle timers**: `moveActiveIndicator`/`moveHoveredIndicator` schedule a `setTimeout`
103
+ to snap the indicator to its exact final position once the CSS transition ends. Previously each
104
+ call scheduled a new timeout without clearing the last one, so moving the pointer quickly across
105
+ several tabs stacked up redundant pending timeouts. Now the previous timeout is cleared before
106
+ scheduling the next, and both are cleared in `onUnmounted`.
107
+ - `handleTransitioningClass` called `.indexOf()` on the same two tabs repeatedly inside its loop
108
+ condition and body — now computed once per call.
109
+ - `previousActiveTab` used `useState("previousActiveTab", ...)` with a fixed, unscoped key — every
110
+ `TabsCore` instance on a page would have shared that one piece of state. Not actually a
111
+ performance issue, but a correctness bug found while auditing this file; fixed by switching to a
112
+ plain per-instance `ref`, since this state doesn't need SSR/hydration sharing.
113
+
114
+ ## Fixed: hover/active label text could become unreadable against the indicator (2026-09-15)
115
+
116
+ `--tabs-hover-indicator-text-colour` and `--tabs-active-indicator-text-colour` were set as `color`
117
+ on `.nav__hovered`/`.nav__active` — decorative, empty, absolutely-positioned divs with no text
118
+ content, so that `color` was dead CSS with no visible effect. The actual tab label
119
+ (`.tabs-list-item`) used one shared `--tabs-list-item-colour-selected` token for hover, active,
120
+ *and* transitioning alike, regardless of which of the two (potentially differently-coloured)
121
+ indicator backgrounds was actually behind it at that moment. A consumer overriding only the hover
122
+ indicator's background (or the active one) had no working lever to keep the label readable against
123
+ it, and could end up with text the same colour as the background sliding behind it — this was the
124
+ original defect noticed during this component's initial authoring, reported as "the button text
125
+ became the same colour as the track item background and became invisible."
126
+
127
+ Fixed by removing the dead `color` declarations from the two decorator divs and splitting
128
+ `.tabs-list-item`'s combined `:hover, [aria-selected="true"], .transitioning` rule so hover uses
129
+ `--tabs-hover-indicator-text-colour` and active uses `--tabs-list-item-colour-selected` falling
130
+ back to `--tabs-active-indicator-text-colour` — each state's text colour now tracks the background
131
+ actually behind it. See `CONSUMER-STYLING.md`'s "Tab-label text colour tracks the indicator behind
132
+ it" section: overriding an indicator background without its matching text-colour token can still
133
+ produce poor contrast (same tradeoff as any two independently-set colour tokens) — the fix makes
134
+ the tokens functional, it doesn't auto-compute contrast.
135
+
136
+ ## Fixed: intermediate tab labels could go invisible during a multi-tab jump (2026-09-15)
137
+
138
+ The first fix above wasn't the whole story. The exact repro that surfaced the rest of it: with tab
139
+ 1 active, hover then click tab 5 — the tabs in between briefly lost all visible text. Going the
140
+ other direction (tab 5 active, hover then click tab 1) looked fine.
141
+
142
+ Cause: `useTabs` had a `handleTransitioningClass` helper that added a `.transitioning` class to
143
+ every tab spanned by a jump (previously shared by the `[aria-selected="true"]` colour rule fixed
144
+ above), on the assumption the sliding indicator visually covers every spanned tab for the whole
145
+ transition — so it force-applied the active text colour to all of them, for the full
146
+ `transitionDuration`. It doesn't cover them the whole time: the indicator's "grow from anchor" CSS
147
+ transition (see `moveActiveIndicator`/`moveHoveredIndicator`) keeps the box's leading edge pinned
148
+ at the *old* tab's position on a forward move and only grows its width, so it only gradually sweeps
149
+ across later tabs — a tab several positions away isn't actually covered until *late* in the
150
+ transition. Recolouring it to the active text colour from the very start meant its label matched
151
+ neither its own background nor the not-yet-arrived indicator, going invisible. A backward move
152
+ animates position and width toward their final values together, so the box's rendered coverage is
153
+ more consistent throughout — which is why that direction looked fine and made the bug easy to miss
154
+ without a specific repro.
155
+
156
+ Fixed by removing `handleTransitioningClass` and the `.transitioning` class entirely — it had no
157
+ other purpose. Tabs spanned by a jump now keep their normal (inactive) styling throughout; only the
158
+ newly-active tab (`[aria-selected="true"]`) and the currently-hovered tab (`:hover`) get their
159
+ special colours. See `CONSUMER-STYLING.md`'s "Tabs spanned by a jump keep their normal styling".
160
+
161
+ ## Fixed: passed-over tab text still invisible under the active indicator specifically (2026-09-15)
162
+
163
+ Removing `.transitioning` (previous fix) uncovered a second, pre-existing defect that
164
+ `.transitioning`'s forced recolour had been accidentally masking the whole time: a non-active tab's
165
+ normal text colour (`--tabs-list-item-colour`) was **exactly the same value** as the active
166
+ indicator's background (`--tabs-active-indicator-colour`, same default) — this collision existed in
167
+ the very first authored version of this component, not something introduced by any migration
168
+ change. So whenever the active pill visually passes under (or briefly sits under) any tab that
169
+ isn't itself marked active — which happens on every transition, single-step or distant, in either
170
+ direction — that tab's text became literally the same colour as the background sliding beneath it.
171
+ `--tabs-hover-indicator-colour`'s default never had this problem since it doesn't coincide with the
172
+ inactive text default.
173
+
174
+ Fixed by changing `--tabs-active-indicator-colour`'s default to one step in from the extreme,
175
+ still clearly the boldest of the three indicator colours, but no longer numerically identical to
176
+ `--tabs-list-item-colour`'s default. `--tabs-active-indicator-text-colour`/
177
+ `--tabs-list-item-colour-selected` (used by the truly active tab) still contrast fine against the
178
+ new value. See `CONSUMER-STYLING.md` for the note on keeping these two tokens from colliding again
179
+ if you override either one.
180
+
181
+ ## Colour token defaults are flat values, not light-dark() (2026-09-15)
182
+
183
+ Every colour token's default was originally a `light-dark(lightValue, darkValue)` pair (as most of
184
+ this library's other components use). Deliberately changed to a single flat `--slate-*` value per
185
+ token for this component — it doesn't bake in automatic light/dark scheme-switching for its own
186
+ defaults; a consumer who wants a token to adapt to `color-scheme` wraps their own override value in
187
+ `light-dark()`. See `CONSUMER-STYLING.md`'s "Colour tokens are flat values, not light-dark() pairs".
@@ -70,6 +70,7 @@ Each skill is a single markdown file named `<area>-<task>.md`.
70
70
  ├── layout-grid-by-cols.md — LayoutGridByCols dynamic slots (item-{n}), props, column/gap/breakpoint decisions
71
71
  ├── page-row.md — PageRow layout primitive: CSS grid named lines, nesting pattern, align prop, aria-labelledby, CSS token API
72
72
  ├── link-text.md — LinkText props, slots, usage patterns, styling
73
+ ├── header-block.md — HeaderBlock: tagLevel/classLevel semantic-vs-visual heading decoupling, page-heading-N utility classes
73
74
  ├── page-hero-highlights.md — PageHeroHighlights template: hero + highlights strip grid, CSS custom property theming
74
75
  ├── services-card.md — ServicesCard props (incl. eyebrowConfig/heroConfig), actions slot, CSS tokens, page boilerplate
75
76
  ├── services-card-grid.md — ServicesCardGrid props, config pass-through, CSS tokens, full page boilerplate
@@ -77,6 +78,8 @@ Each skill is a single markdown file named `<area>-<task>.md`.
77
78
  ├── service-summary.md — ServiceSummary props, summary-link slot, DisplayPill duration/price (renamed/stripped from ServicesSection — full mode is now ServiceDetail)
78
79
  ├── service-detail.md — ServiceDetail: full service detail page (hero banner, sticky sidebar booking card + related services, closing CTA), headerTag vs subheadingTag split, book-cta/sidebar-note/related-service/final-cta slots
79
80
  ├── breadcrumb.md — Breadcrumb: items (BreadcrumbItem[]) trail, link vs current-page text, CSS token API
81
+ ├── skip-links.md — SkipLinks: focus-revealed accessibility skip-nav, links (SkipLink[]) data-driven, homeLink slot, CSS token API
82
+ ├── tabs-core.md — TabsCore: tablist from indexed slots (itemCount), arrow-key nav, moving hover/active/underline indicators, CSS token API
80
83
  ├── contact-section.md — ContactSection props (stepperIndicatorSize pass-through), 3-item info+form layout, slot API
81
84
  ├── stepper-list.md — StepperList dynamic slots (item-{n}/indicator-{n}), props, connector behaviour
82
85
  ├── expanding-panel.md — ExpandingPanel v-model, forceOpened, contentIsOnTop overlay mode, slots (summary/icon/content), ARIA wiring, CSS token API
@@ -90,6 +93,7 @@ Each skill is a single markdown file named `<area>-<task>.md`.
90
93
  ├── scroll-reveal-frame.md — ScrollRevealFrame: generic parallax clipping frame, slot API, image grid pattern, CSS tokens, browser support
91
94
  ├── scroll-reveal-image.md — ScrollRevealImage: single-image parallax reveal, focalX, imgWidth/imgHeight, responsive frame height
92
95
  ├── marquee-scroller.md — MarqueeScroller: infinite logo/badge scroller, per-item dynamic slots (marqueeData id), hover/focus/keyboard pause, reduced-motion, CSS tokens
96
+ ├── rotating-carousel-image.md — RotatingCarouselImage: 3D rotating image carousel, scroll-parallax tilt, focus/keyboard/hover pause, reduced-motion, CSS tokens
93
97
  ├── site-navigation.md — SiteNavigation: responsive nav with auto-collapse, burger menu, decorator indicators, CSS token API
94
98
  ├── tab-navigation.md — TabNavigation: horizontal tab nav with CSS anchor-positioning indicators, anchor scroll, burger collapse, full CSS token API
95
99
  ├── social-icons-list.md — SocialIconsList: data-driven social icon links, ISocialIcon type, logos: icon names, CSS tokens
@@ -0,0 +1,41 @@
1
+ {
2
+ "SRCDEV HeaderBlock Basic": {
3
+ "description": "HeaderBlock — semantic heading tag decoupled from visual page-heading-N size",
4
+ "scope": "vue,html",
5
+ "body": [
6
+ "<HeaderBlock :tag-level=\"${1:1}\" :class-level=\"${2:1}\">",
7
+ " $3",
8
+ "</HeaderBlock>"
9
+ ]
10
+ },
11
+ "SRCDEV HeaderBlock Decoupled Levels": {
12
+ "description": "HeaderBlock with a semantic tag lower than its visual size (e.g. hero heading below the page's real h1)",
13
+ "scope": "vue,html",
14
+ "body": [
15
+ "<HeaderBlock :tag-level=\"${1:2}\" :class-level=\"${2:1}\">",
16
+ " $3",
17
+ "</HeaderBlock>"
18
+ ]
19
+ },
20
+ "SRCDEV HeaderBlock With Passthrough Class": {
21
+ "description": "HeaderBlock with a custom class for one-off styling overrides",
22
+ "scope": "vue,html",
23
+ "body": [
24
+ "<HeaderBlock :tag-level=\"${1:1}\" :class-level=\"${2:1}\" style-class-passthrough=\"${3:hero-title}\">",
25
+ " $4",
26
+ "</HeaderBlock>"
27
+ ]
28
+ },
29
+ "SRCDEV HeaderBlock Paired With Section aria-labelledby": {
30
+ "description": "HeaderBlock with id bound to a wrapping section's aria-labelledby target",
31
+ "scope": "vue,html",
32
+ "body": [
33
+ "<section aria-labelledby=\"${1:section-heading}\">",
34
+ " <HeaderBlock id=\"${1:section-heading}\" :tag-level=\"${2:2}\" :class-level=\"${3:1}\">",
35
+ " $4",
36
+ " </HeaderBlock>",
37
+ " $5",
38
+ "</section>"
39
+ ]
40
+ }
41
+ }
@@ -0,0 +1,74 @@
1
+ {
2
+ "SRCDEV RotatingCarouselImage Basic": {
3
+ "description": "RotatingCarouselImage — 3D rotating image carousel with scroll-parallax tilt",
4
+ "scope": "vue,html",
5
+ "body": [
6
+ "<RotatingCarouselImage",
7
+ " :data=\"[",
8
+ " { src: '${1:/carousel/1.jpg}', alt: '${2:Image 1}' },",
9
+ " { src: '${3:/carousel/2.jpg}', alt: '${4:Image 2}' },",
10
+ " ]\"",
11
+ ">$5</RotatingCarouselImage>"
12
+ ]
13
+ },
14
+ "SRCDEV RotatingCarouselImage With Controls": {
15
+ "description": "RotatingCarouselImage with the visible pause/play control button (WCAG 2.2.2)",
16
+ "scope": "vue,html",
17
+ "body": [
18
+ "<RotatingCarouselImage",
19
+ " :data=\"$1images\"",
20
+ " show-controls",
21
+ " aria-label=\"$2Rotating product photos\"",
22
+ "/>"
23
+ ]
24
+ },
25
+ "SRCDEV RotatingCarouselImage Static Tilt": {
26
+ "description": "RotatingCarouselImage with scroll-parallax disabled, using a fixed rotateX instead",
27
+ "scope": "vue,html",
28
+ "body": [
29
+ "<RotatingCarouselImage",
30
+ " :data=\"$1images\"",
31
+ " :use-parallax-effect=\"false\"",
32
+ " :rotate-x=\"${2:10}\"",
33
+ " show-controls",
34
+ "/>"
35
+ ]
36
+ },
37
+ "SRCDEV RotatingCarouselImage Custom Toggle Icon Slot": {
38
+ "description": "RotatingCarouselImage with the toggle-icon slot fully replacing the control button's icon",
39
+ "scope": "vue,html",
40
+ "body": [
41
+ "<RotatingCarouselImage :data=\"$1images\" show-controls>",
42
+ " <template #toggle-icon=\"{ isPaused }\">",
43
+ " $2",
44
+ " </template>",
45
+ "</RotatingCarouselImage>"
46
+ ]
47
+ },
48
+ "SRCDEV RotatingCarouselImage CSS Override": {
49
+ "description": "CSS override scaffold for RotatingCarouselImage — scope to a page or section class",
50
+ "scope": "css",
51
+ "body": [
52
+ ".$1my-page {",
53
+ " .rotating-carousel {",
54
+ " --rotating-carousel-height: ;",
55
+ " --rotating-carousel-item-width: ;",
56
+ " --rotating-carousel-item-height: ;",
57
+ " --rotating-carousel-offset-bottom: ;",
58
+ " --rotating-carousel-rotation-duration: ;",
59
+ " --rotating-carousel-z-index: ;",
60
+ " --rotating-carousel-focus-outline-width: ;",
61
+ " --rotating-carousel-focus-outline-offset: ;",
62
+ " --rotating-carousel-control-offset: ;",
63
+ " --rotating-carousel-control-background-colour: ;",
64
+ " --rotating-carousel-control-background-colour-hover: ;",
65
+ " --rotating-carousel-control-text-colour: ;",
66
+ " --rotating-carousel-control-border-radius: ;",
67
+ " --rotating-carousel-control-border-width: ;",
68
+ " --rotating-carousel-control-padding: ;",
69
+ " --rotating-carousel-control-font-size: ;",
70
+ " }",
71
+ "}"
72
+ ]
73
+ }
74
+ }
@@ -0,0 +1,55 @@
1
+ {
2
+ "SRCDEV SkipLinks Basic": {
3
+ "description": "SkipLinks — accessibility skip-navigation utility, default main-content/footer links",
4
+ "scope": "vue,html",
5
+ "body": [
6
+ "<SkipLinks />"
7
+ ]
8
+ },
9
+ "SRCDEV SkipLinks Custom Links": {
10
+ "description": "SkipLinks with a custom list of skip targets",
11
+ "scope": "vue,html",
12
+ "body": [
13
+ "<SkipLinks",
14
+ " :links=\"[",
15
+ " { href: '${1:#site-navigation}', label: '${2:Skip to navigation}' },",
16
+ " { href: '${3:#main-content}', label: '${4:Skip to main content}' },",
17
+ " { href: '${5:#footer-content}', label: '${6:Skip to footer}' },",
18
+ " ]\"",
19
+ "/>"
20
+ ]
21
+ },
22
+ "SRCDEV SkipLinks With Home Link Slot": {
23
+ "description": "SkipLinks with the homeLink slot for a logo/home link before the skip-nav",
24
+ "scope": "vue,html",
25
+ "body": [
26
+ "<SkipLinks>",
27
+ " <template #homeLink>",
28
+ " $1",
29
+ " </template>",
30
+ "</SkipLinks>"
31
+ ]
32
+ },
33
+ "SRCDEV SkipLinks CSS Override": {
34
+ "description": "CSS override scaffold for SkipLinks — scope to a page or section class",
35
+ "scope": "css",
36
+ "body": [
37
+ ".$1my-page {",
38
+ " .skip-links {",
39
+ " --skip-links-z-index: ;",
40
+ " --skip-links-gap: ;",
41
+ " --skip-links-background-colour: ;",
42
+ " --skip-links-border-colour: ;",
43
+ " --skip-links-border-width: ;",
44
+ " --skip-links-padding: ;",
45
+ " --skip-links-text-colour: ;",
46
+ " --skip-links-link-padding-block: ;",
47
+ " --skip-links-link-padding-inline: ;",
48
+ " --skip-links-transition-duration: ;",
49
+ " --skip-links-focus-outline-width: ;",
50
+ " --skip-links-focus-outline-colour: ;",
51
+ " }",
52
+ "}"
53
+ ]
54
+ }
55
+ }
@@ -0,0 +1,78 @@
1
+ {
2
+ "SRCDEV TabsCore Basic": {
3
+ "description": "TabsCore — horizontal tablist with indexed trigger/content slots",
4
+ "scope": "vue,html",
5
+ "body": [
6
+ "<TabsCore :item-count=\"${1:3}\">",
7
+ " <template #tab-0-trigger>${2:Overview}</template>",
8
+ " <template #tab-0-content>",
9
+ " $3",
10
+ " </template>",
11
+ " <template #tab-1-trigger>${4:Specs}</template>",
12
+ " <template #tab-1-content>",
13
+ " $5",
14
+ " </template>",
15
+ " <template #tab-2-trigger>${6:Reviews}</template>",
16
+ " <template #tab-2-content>",
17
+ " $7",
18
+ " </template>",
19
+ "</TabsCore>"
20
+ ]
21
+ },
22
+ "SRCDEV TabsCore Vertical": {
23
+ "description": "TabsCore with axis=\"y\" for a vertical tab column",
24
+ "scope": "vue,html",
25
+ "body": [
26
+ "<TabsCore axis=\"y\" :item-count=\"${1:3}\" aria-label=\"${2:Settings}\">",
27
+ " <template #tab-0-trigger>$3</template>",
28
+ " <template #tab-0-content>$4</template>",
29
+ "</TabsCore>"
30
+ ]
31
+ },
32
+ "SRCDEV TabsCore Indicators Off": {
33
+ "description": "TabsCore with the moving hover/active/underline decorators all disabled",
34
+ "scope": "vue,html",
35
+ "body": [
36
+ "<TabsCore",
37
+ " :item-count=\"${1:3}\"",
38
+ " :track-hover=\"false\"",
39
+ " :track-active=\"false\"",
40
+ " :track-indicator=\"false\"",
41
+ ">",
42
+ " $2",
43
+ "</TabsCore>"
44
+ ]
45
+ },
46
+ "SRCDEV TabsCore CSS Override": {
47
+ "description": "CSS override scaffold for TabsCore — scope to a page or section class",
48
+ "scope": "css",
49
+ "body": [
50
+ ".$1my-page {",
51
+ " .tabs-core {",
52
+ " --tabs-nav-border: ;",
53
+ " --tabs-hover-indicator-colour: ;",
54
+ " --tabs-hover-indicator-text-colour: ;",
55
+ " --tabs-active-indicator-colour: ;",
56
+ " --tabs-active-indicator-text-colour: ;",
57
+ " --tabs-underline-indicator-colour: ;",
58
+ " --tabs-underline-indicator-height: ;",
59
+ " --tabs-list-item-opacity: ;",
60
+ " --tabs-list-item-colour: ;",
61
+ " --tabs-list-item-colour-selected: ;",
62
+ " --tabs-list-item-text-transform: ;",
63
+ " --tabs-list-item-font-weight: ;",
64
+ " --tabs-list-item-padding-block: ;",
65
+ " --tabs-list-item-padding-inline: ;",
66
+ " --tabs-list-item-colour-transition-duration: ;",
67
+ " --tabs-list-item-focus-outline-width: ;",
68
+ " --tabs-list-item-focus-outline-offset: ;",
69
+ " --tabs-content-background-colour: ;",
70
+ " --tabs-content-border-width: ;",
71
+ " --tabs-content-border-colour: ;",
72
+ " --tabs-content-border-radius: ;",
73
+ " --tabs-axis-y-gap: ;",
74
+ " }",
75
+ "}"
76
+ ]
77
+ }
78
+ }
@@ -31,5 +31,6 @@
31
31
  font-weight: 600;
32
32
  font-variation-settings: "wght" 600;
33
33
  line-height: 1;
34
- margin: 0;
34
+ margin-block: 0rem;
35
+ padding-block: 0rem;
35
36
  }
@@ -0,0 +1,54 @@
1
+ # RotatingCarouselImage — Consumer Styling Guide
2
+
3
+ ## Public token API
4
+
5
+ | Token | Default | Controls |
6
+ |---|---|---|
7
+ | `--rotating-carousel-height` | `70svh` | Height of the carousel's root element |
8
+ | `--rotating-carousel-item-width` | `200px` | Width of each rotating item (and the slider box itself) |
9
+ | `--rotating-carousel-item-height` | `250px` | Height of each rotating item (and the slider box itself) |
10
+ | `--rotating-carousel-offset-bottom` | `35%` | Vertical position of the slider box within the root element |
11
+ | `--rotating-carousel-rotation-duration` | `30s` | Duration of one full 360° rotation |
12
+ | `--rotating-carousel-z-index` | `2` | Stacking context of the slider box |
13
+ | `--rotating-carousel-focus-outline-width` | `2px` | Outline width on the root region and the control button when keyboard-focused (colour is `--theme-ring`) |
14
+ | `--rotating-carousel-focus-outline-offset` | `2px` | Outline offset on the same two focus states |
15
+ | `--rotating-carousel-control-offset` | `8px` | Distance of the pause/play button from the top and end edges |
16
+ | `--rotating-carousel-control-background-colour` | `rgba(0, 0, 0, 0.7)` | Pause/play button background |
17
+ | `--rotating-carousel-control-background-colour-hover` | `rgba(0, 0, 0, 0.9)` | Pause/play button background on hover |
18
+ | `--rotating-carousel-control-text-colour` | `white` | Pause/play button icon colour |
19
+ | `--rotating-carousel-control-border-radius` | `4px` | Pause/play button border radius |
20
+ | `--rotating-carousel-control-padding` | `8px` | Pause/play button padding |
21
+ | `--rotating-carousel-control-font-size` | `14px` | Pause/play button icon font size |
22
+ | `--rotating-carousel-control-transition-duration` | `0.2s` | Pause/play button background-colour transition duration |
23
+ | `--rotating-carousel-control-border-width` | `1px` | Pause/play button border width in forced-colors/high-contrast mode (`prefers-contrast: high`) |
24
+
25
+ ```css
26
+ .my-page {
27
+ --rotating-carousel-item-width: 260px;
28
+ --rotating-carousel-item-height: 320px;
29
+ --rotating-carousel-rotation-duration: 45s;
30
+ }
31
+ ```
32
+
33
+ ## High-contrast mode colours are not tokenised
34
+
35
+ Under `prefers-contrast: high`, the control button switches to the system colour keywords
36
+ `ButtonFace`/`ButtonText` rather than the component's own background/text tokens, so it tracks the
37
+ user's OS-level high-contrast theme. Only the border *width* in that mode is a token
38
+ (`--rotating-carousel-control-border-width`).
39
+
40
+ ## Rotation geometry is prop-driven, not tokens
41
+
42
+ `rotateX`, `perspective`, and `translateZ` control the 3D tilt and radius of the rotation and are
43
+ set via props (not CSS custom properties), since they also drive the component's own scroll-based
44
+ parallax math:
45
+
46
+ ```vue
47
+ <RotatingCarouselImage :data="images" :perspective="1200" :translate-z="1200" />
48
+ ```
49
+
50
+ ## Control button icon and copy
51
+
52
+ The pause/play button's icon and its `aria-label` copy are overridable via `playIcon`/`pauseIcon`/
53
+ `playLabel`/`pauseLabel` props, or the `toggle-icon` slot for a fully custom icon (scoped with
54
+ `isPaused`) — same pattern as `MarqueeScroller`.