srcdev-nuxt-components 9.5.7 → 9.6.1

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 (159) hide show
  1. package/.claude/skills/component-compliance-checklist.md +8 -2
  2. package/.claude/skills/component-dynamic-slots.md +3 -3
  3. package/.claude/skills/component-export-types.md +10 -16
  4. package/.claude/skills/component-naming.md +1 -1
  5. package/.claude/skills/components/alert-content.md +9 -1
  6. package/.claude/skills/components/breadcrumb.md +5 -0
  7. package/.claude/skills/components/container-glow.md +21 -8
  8. package/.claude/skills/components/display-avatar.md +13 -7
  9. package/.claude/skills/components/display-chip.md +37 -24
  10. package/.claude/skills/components/display-pill.md +58 -8
  11. package/.claude/skills/components/display-prompt.md +8 -5
  12. package/.claude/skills/components/display-toast.md +21 -4
  13. package/.claude/skills/components/glowing-border.md +20 -2
  14. package/.claude/skills/components/input-button.md +3 -0
  15. package/.claude/skills/components/responsive-header.md +59 -0
  16. package/.claude/skills/components/select-menu.md +54 -5
  17. package/.claude/skills/components/service-detail.md +32 -0
  18. package/.claude/skills/components/service-summary.md +8 -0
  19. package/.claude/skills/components/services-card-grid.md +5 -3
  20. package/.claude/skills/components/services-card.md +22 -12
  21. package/.claude/skills/components/site-header.md +2 -2
  22. package/.claude/skills/components/slider-gallery.md +10 -4
  23. package/.claude/skills/components/tabbed-content.md +300 -0
  24. package/.claude/skills/composable-anchor-scroll.md +16 -15
  25. package/.claude/skills/index.md +4 -5
  26. package/.claude/skills/storybook-add-story.md +53 -0
  27. package/.claude/skills/theming-status-tokens.md +60 -0
  28. package/.vscode/srcdev-component-display-avatar.code-snippets +2 -1
  29. package/.vscode/srcdev-component-display-chip.code-snippets +3 -2
  30. package/.vscode/srcdev-component-display-pill.code-snippets +10 -0
  31. package/.vscode/srcdev-component-responsive-header.code-snippets +16 -0
  32. package/.vscode/srcdev-component-select-menu.code-snippets +28 -0
  33. package/.vscode/srcdev-component-service-detail.code-snippets +1 -0
  34. package/.vscode/srcdev-component-service-summary.code-snippets +3 -1
  35. package/.vscode/srcdev-component-services-card.code-snippets +38 -36
  36. package/.vscode/srcdev-component-services-grids.code-snippets +2 -1
  37. package/.vscode/{srcdev-component-tabs-core.code-snippets → srcdev-component-tabbed-content.code-snippets} +35 -16
  38. package/app/assets/styles/setup/03.theming/_status.css +24 -0
  39. package/app/assets/styles/setup/03.theming/index.css +1 -0
  40. package/app/components/01.atoms/animations/container-glow/CONSUMER-STYLING.md +39 -35
  41. package/app/components/01.atoms/animations/container-glow/ContainerGlow.vue +25 -15
  42. package/app/components/01.atoms/animations/container-glow/stories/ContainerGlow.stories.ts +130 -24
  43. package/app/components/01.atoms/animations/container-glow/tests/ContainerGlow.spec.ts +45 -0
  44. package/app/components/01.atoms/animations/container-glow/tests/__snapshots__/ContainerGlow.spec.ts.snap +2 -2
  45. package/app/components/01.atoms/animations/glowing-border/CONSUMER-STYLING.md +76 -19
  46. package/app/components/01.atoms/animations/glowing-border/GlowingBorder.vue +12 -10
  47. package/app/components/01.atoms/animations/glowing-border/stories/GlowingBorder.stories.ts +201 -42
  48. package/app/components/01.atoms/animations/glowing-border/tests/GlowingBorder.spec.ts +15 -4
  49. package/app/components/01.atoms/animations/glowing-border/tests/__snapshots__/GlowingBorder.spec.ts.snap +1 -1
  50. package/app/components/01.atoms/display-pill/CONSUMER-STYLING.md +78 -9
  51. package/app/components/01.atoms/display-pill/DisplayPill.vue +59 -23
  52. package/app/components/01.atoms/display-pill/stories/DisplayPill.stories.ts +240 -16
  53. package/app/components/01.atoms/display-pill/tests/DisplayPill.spec.ts +43 -14
  54. package/app/components/01.atoms/display-pill/tests/__snapshots__/DisplayPill.spec.ts.snap +3 -3
  55. package/app/components/01.atoms/navigation/breadcrumb/Breadcrumb.vue +16 -4
  56. package/app/components/01.atoms/navigation/breadcrumb/CONSUMER-STYLING.md +28 -5
  57. package/app/components/01.atoms/navigation/breadcrumb/stories/Breadcrumb.stories.ts +94 -0
  58. package/app/components/01.atoms/navigation/breadcrumb/tests/Breadcrumb.spec.ts +27 -0
  59. package/app/components/01.atoms/tabbed-content/CONSUMER-STYLING.md +201 -0
  60. package/app/components/01.atoms/tabbed-content/TabbedContent.vue +750 -0
  61. package/app/components/01.atoms/tabbed-content/stories/TabbedContent.stories.ts +279 -0
  62. package/app/components/01.atoms/tabbed-content/tests/TabbedContent.spec.ts +816 -0
  63. package/app/components/02.molecules/alert-content/AlertContent.vue +11 -2
  64. package/app/components/02.molecules/alert-content/AlertContentInner.vue +19 -0
  65. package/app/components/02.molecules/alert-content/CONSUMER-STYLING.md +17 -4
  66. package/app/components/02.molecules/alert-content/stories/AlertContent.stories.ts +200 -17
  67. package/app/components/02.molecules/alert-content/tests/AlertContent.spec.ts +33 -0
  68. package/app/components/02.molecules/display-avatar/CONSUMER-STYLING.md +7 -5
  69. package/app/components/02.molecules/display-avatar/DisplayAvatar.vue +6 -2
  70. package/app/components/02.molecules/display-avatar/stories/DisplayAvatar.stories.ts +24 -3
  71. package/app/components/02.molecules/display-avatar/tests/DisplayAvatar.spec.ts +19 -0
  72. package/app/components/02.molecules/display-avatar/tests/__snapshots__/DisplayAvatar.spec.ts.snap +2 -1
  73. package/app/components/02.molecules/display-chip/CONSUMER-STYLING.md +52 -19
  74. package/app/components/02.molecules/display-chip/DisplayChip.vue +72 -54
  75. package/app/components/02.molecules/display-chip/stories/DisplayChip.stories.ts +103 -6
  76. package/app/components/02.molecules/display-chip/tests/DisplayChip.spec.ts +98 -29
  77. package/app/components/02.molecules/display-chip/tests/__snapshots__/DisplayChip.spec.ts.snap +8 -4
  78. package/app/components/02.molecules/prompt/CONSUMER-STYLING.md +9 -1
  79. package/app/components/02.molecules/prompt/DisplayPrompt.vue +2 -2
  80. package/app/components/02.molecules/prompt/stories/DisplayPrompt.stories.ts +208 -71
  81. package/app/components/02.molecules/prompt/tests/DisplayPrompt.spec.ts +19 -6
  82. package/app/components/02.molecules/select-menu/CONSUMER-STYLING.md +66 -8
  83. package/app/components/02.molecules/select-menu/SelectMenu.vue +141 -18
  84. package/app/components/02.molecules/select-menu/stories/SelectMenu.stories.ts +232 -2
  85. package/app/components/02.molecules/select-menu/tests/SelectMenu.spec.ts +151 -0
  86. package/app/components/02.molecules/select-menu/tests/__snapshots__/SelectMenu.spec.ts.snap +6 -2
  87. package/app/components/02.molecules/toast/CONSUMER-STYLING.md +91 -27
  88. package/app/components/02.molecules/toast/DisplayToast.vue +109 -50
  89. package/app/components/02.molecules/toast/DisplayToastProvider.vue +109 -45
  90. package/app/components/02.molecules/toast/stories/DisplayToast.stories.ts +23 -0
  91. package/app/components/02.molecules/toast/stories/DisplayToastProvider.stories.ts +73 -0
  92. package/app/components/02.molecules/toast/tests/DisplayToast.spec.ts +132 -18
  93. package/app/components/02.molecules/toast/tests/DisplayToastProvider.spec.ts +105 -39
  94. package/app/components/03.organisms/image-galleries/slider-gallery/CONSUMER-STYLING.md +19 -1
  95. package/app/components/03.organisms/image-galleries/slider-gallery/SliderGallery.vue +65 -7
  96. package/app/components/03.organisms/image-galleries/slider-gallery/stories/SliderGallery.stories.ts +201 -63
  97. package/app/components/03.organisms/image-galleries/slider-gallery/tests/SliderGallery.spec.ts +41 -2
  98. package/app/components/03.organisms/image-galleries/slider-gallery/tests/__snapshots__/SliderGallery.spec.ts.snap +6 -6
  99. package/app/components/03.organisms/responsive-header/CONSUMER-STYLING.md +42 -11
  100. package/app/components/03.organisms/responsive-header/NavigationItems.vue +60 -16
  101. package/app/components/03.organisms/responsive-header/ResponsiveHeader.vue +86 -24
  102. package/app/components/03.organisms/responsive-header/stories/ResponsiveHeader.stories.ts +52 -0
  103. package/app/components/03.organisms/responsive-header/tests/NavigationItems.spec.ts +20 -0
  104. package/app/components/03.organisms/responsive-header/tests/ResponsiveHeader.spec.ts +40 -0
  105. package/app/components/03.organisms/services/service-detail/CONSUMER-STYLING.md +92 -41
  106. package/app/components/03.organisms/services/service-detail/ServiceDetail.vue +198 -73
  107. package/app/components/03.organisms/services/service-detail/stories/ServiceDetail.stories.ts +157 -9
  108. package/app/components/03.organisms/services/service-detail/tests/ServiceDetail.spec.ts +58 -0
  109. package/app/components/03.organisms/services/service-detail/tests/__snapshots__/ServiceDetail.spec.ts.snap +5 -4
  110. package/app/components/03.organisms/services/service-summary/CONSUMER-STYLING.md +30 -0
  111. package/app/components/03.organisms/services/service-summary/ServiceSummary.vue +32 -8
  112. package/app/components/03.organisms/services/service-summary/stories/ServiceSummary.stories.ts +147 -0
  113. package/app/components/03.organisms/services/service-summary/tests/ServiceSummary.spec.ts +26 -0
  114. package/app/components/03.organisms/services/service-summary/tests/__snapshots__/ServiceSummary.spec.ts.snap +2 -2
  115. package/app/components/03.organisms/services/services-card/CONSUMER-STYLING.md +104 -63
  116. package/app/components/03.organisms/services/services-card/ServicesCard.vue +92 -66
  117. package/app/components/03.organisms/services/services-card/stories/ServicesCard.stories.ts +194 -27
  118. package/app/components/03.organisms/services/services-card/tests/ServicesCard.spec.ts +34 -8
  119. package/app/components/03.organisms/services/services-card/tests/__snapshots__/ServicesCard.spec.ts.snap +8 -8
  120. package/app/components/03.organisms/services/services-grids/CONSUMER-STYLING.md +9 -0
  121. package/app/components/03.organisms/services/services-grids/ServicesCardGrid.vue +2 -1
  122. package/app/components/03.organisms/services/services-grids/stories/ServiceSummaryGrid.stories.ts +46 -6
  123. package/app/components/03.organisms/services/services-grids/stories/ServicesCardGrid.stories.ts +89 -11
  124. package/app/components/03.organisms/services/services-grids/tests/ServicesCardGrid.spec.ts +8 -0
  125. package/app/components/03.organisms/services/services-grids/tests/__snapshots__/ServiceSummaryGrid.spec.ts.snap +6 -6
  126. package/app/components/03.organisms/services/services-grids/tests/__snapshots__/ServicesCardGrid.spec.ts.snap +24 -24
  127. package/app/components/05.forms/input-button/CONSUMER-STYLING.md +8 -0
  128. package/app/components/05.forms/input-button/InputButton.vue +3 -1
  129. package/app/composables/tests/useAnchorScroll.spec.ts +8 -0
  130. package/app/composables/useAnchorScroll.ts +1 -1
  131. package/app/composables/useTabs.ts +148 -206
  132. package/app/types/components/display-chip.d.ts +8 -4
  133. package/app/types/components/display-toast.d.ts +2 -0
  134. package/app/types/components/index.ts +0 -1
  135. package/app/types/components/select-menu.d.ts +2 -0
  136. package/app/utils/pausableTimeout.ts +41 -0
  137. package/nuxt.config.ts +1 -1
  138. package/package.json +1 -1
  139. package/.claude/skills/components/site-navigation.md +0 -145
  140. package/.claude/skills/components/tab-navigation.md +0 -272
  141. package/.claude/skills/components/tabs-core.md +0 -187
  142. package/.vscode/srcdev-component-site-navigation.code-snippets +0 -38
  143. package/.vscode/srcdev-component-tab-navigation.code-snippets +0 -30
  144. package/app/components/01.atoms/navigation/tabs/CONSUMER-STYLING.md +0 -120
  145. package/app/components/01.atoms/navigation/tabs/TabsCore.vue +0 -272
  146. package/app/components/01.atoms/navigation/tabs/stories/TabsCore.stories.ts +0 -199
  147. package/app/components/01.atoms/navigation/tabs/tests/TabsCore.spec.ts +0 -274
  148. package/app/components/02.molecules/navigation/site-navigation/CONSUMER-STYLING.md +0 -109
  149. package/app/components/02.molecules/navigation/site-navigation/SiteNavigation.vue +0 -751
  150. package/app/components/02.molecules/navigation/site-navigation/stories/SiteNavigation.stories.ts +0 -356
  151. package/app/components/02.molecules/navigation/site-navigation/tests/SiteNavigation.spec.ts +0 -367
  152. package/app/components/02.molecules/navigation/site-navigation/tests/__snapshots__/SiteNavigation.spec.ts.snap +0 -30
  153. package/app/components/02.molecules/navigation/tab-navigation/CONSUMER-STYLING.md +0 -150
  154. package/app/components/02.molecules/navigation/tab-navigation/TabNavigation.vue +0 -518
  155. package/app/components/02.molecules/navigation/tab-navigation/stories/TabNavigation.stories.ts +0 -340
  156. package/app/components/02.molecules/navigation/tab-navigation/tests/TabNavigation.spec.ts +0 -339
  157. package/app/components/02.molecules/navigation/tab-navigation/tests/__snapshots__/TabNavigation.spec.ts.snap +0 -33
  158. package/app/composables/useNavCollapse.ts +0 -86
  159. package/app/types/components/nav-item.d.ts +0 -11
@@ -1,518 +0,0 @@
1
- <template>
2
- <nav
3
- ref="navRef"
4
- class="tab-navigation"
5
- :class="[
6
- elementClasses,
7
- `tab-navigation--${navAlign}`,
8
- { 'is-collapsed': isCollapsed, 'is-loaded': isLoaded, 'menu-open': isMenuOpen, 'is-animated': isAnimated },
9
- ]"
10
- :aria-label="ariaLabel"
11
- >
12
- <ul v-if="!isCollapsed || !isLoaded" ref="navListRef" class="tab-nav-list" @mouseleave="hoveredItemHref = null">
13
- <li
14
- v-for="item in navItemData.main"
15
- :key="item.href"
16
- :data-href="item.href"
17
- :class="[
18
- item.cssName,
19
- {
20
- 'is-active': item.href?.startsWith('#') ? item.href === activeHash : isActiveItem(item.href),
21
- 'is-hovered': hoveredItemHref === item.href,
22
- },
23
- ]"
24
- @mouseenter="hoveredItemHref = item.href ?? null"
25
- >
26
- <!-- Plain <a> for hash links — keeps Vue Router out of the smooth-scroll path -->
27
- <a
28
- v-if="item.href?.startsWith('#')"
29
- :href="item.href"
30
- class="tab-nav-link"
31
- data-nav-item
32
- @click="(e) => item.href && handleNavClick(e, item.href)"
33
- >
34
- <Icon v-if="item.iconName" :name="item.iconName" aria-hidden="true" />
35
- {{ item.text }}
36
- </a>
37
- <NuxtLink
38
- v-else
39
- :href="item.href"
40
- :external="item.isExternal || undefined"
41
- class="tab-nav-link"
42
- data-nav-item
43
- @click="(e) => item.href && handleNavClick(e, item.href)"
44
- >
45
- <Icon v-if="item.iconName" :name="item.iconName" aria-hidden="true" />
46
- {{ item.text }}
47
- </NuxtLink>
48
- </li>
49
- <li aria-hidden="true" role="none" class="nav-indicator-li"><div class="nav__hovered"></div></li>
50
- <li aria-hidden="true" role="none" class="nav-indicator-li"><div class="nav__active-indicator"></div></li>
51
- </ul>
52
-
53
- <InputButton
54
- v-if="showCollapsed"
55
- class="tab-nav-burger"
56
- :class="{ 'is-open': isMenuOpen }"
57
- variant="tertiary"
58
- :button-text="isMenuOpen ? 'Close navigation menu' : 'Open navigation menu'"
59
- :aria-expanded="String(isMenuOpen)"
60
- aria-controls="tab-nav-panel"
61
- @click="toggleMenu"
62
- >
63
- <template #iconOnly>
64
- <span class="burger-bar" aria-hidden="true"></span>
65
- <span class="burger-bar" aria-hidden="true"></span>
66
- <span class="burger-bar" aria-hidden="true"></span>
67
- </template>
68
- </InputButton>
69
-
70
- <Teleport to="body">
71
- <div
72
- v-if="showCollapsed"
73
- class="tab-nav-backdrop"
74
- :class="{ 'is-open': isMenuOpen }"
75
- aria-hidden="true"
76
- @click="closeMenu"
77
- ></div>
78
- </Teleport>
79
-
80
- <div
81
- v-if="showCollapsed"
82
- id="tab-nav-panel"
83
- class="tab-nav-panel"
84
- :class="{ 'is-open': isMenuOpen }"
85
- :inert="!isMenuOpen ? true : undefined"
86
- >
87
- <div class="tab-nav-panel-inner">
88
- <ul class="tab-nav-panel-list">
89
- <li v-for="item in navItemData.main" :key="item.href" :class="item.cssName">
90
- <a
91
- v-if="item.href?.startsWith('#')"
92
- :href="item.href"
93
- class="tab-nav-panel-link"
94
- @click="
95
- (e) => {
96
- item.href && handleNavClick(e, item.href);
97
- closeMenu();
98
- }
99
- "
100
- >
101
- <Icon v-if="item.iconName" :name="item.iconName" aria-hidden="true" />
102
- {{ item.text }}
103
- </a>
104
- <NuxtLink
105
- v-else
106
- :href="item.href"
107
- :external="item.isExternal || undefined"
108
- class="tab-nav-panel-link"
109
- @click="
110
- (e) => {
111
- item.href && handleNavClick(e, item.href);
112
- closeMenu();
113
- }
114
- "
115
- >
116
- <Icon v-if="item.iconName" :name="item.iconName" aria-hidden="true" />
117
- {{ item.text }}
118
- </NuxtLink>
119
- </li>
120
- </ul>
121
- </div>
122
- </div>
123
- </nav>
124
- </template>
125
-
126
- <script setup lang="ts">
127
- import type { NavItemData } from "~/types/components";
128
-
129
- interface Props {
130
- navItemData: NavItemData;
131
- navAlign?: "left" | "center" | "right";
132
- styleClassPassthrough?: string | string[];
133
- anchorScrollOffset?: number | (() => number);
134
- /** aria-label on the nav landmark — override for localisation. */
135
- ariaLabel?: string;
136
- }
137
-
138
- const props = withDefaults(defineProps<Props>(), {
139
- navAlign: "left",
140
- styleClassPassthrough: () => [],
141
- anchorScrollOffset: undefined,
142
- ariaLabel: "Site navigation",
143
- });
144
-
145
- const { navRef, navListRef, isCollapsed, isLoaded, isMenuOpen, isActiveItem, toggleMenu, closeMenu } =
146
- useNavCollapse("tab-nav-loaded");
147
-
148
- const { handleNavClick, activeHash } = useAnchorScroll({ offset: props.anchorScrollOffset });
149
-
150
- onMounted(() => {
151
- if (!activeHash.value) {
152
- const firstHashItem = props.navItemData.main?.find((item) => item.href?.startsWith("#"));
153
- if (firstHashItem?.href) activeHash.value = firstHashItem.href;
154
- }
155
- });
156
-
157
- // ─── Animation gate — disables indicator transitions during route changes ────
158
- // Starts true: CSS anchor positioning resolves before first paint so there is
159
- // no previous position to animate from on initial render.
160
- // Uses flush:"pre" so isAnimated = false lands in the same DOM update as the
161
- // is-active class moving — the browser never sees the anchor shift with
162
- // transitions active.
163
- const isAnimated = ref(true);
164
- const route = useRoute();
165
-
166
- watch(
167
- () => route.path,
168
- () => {
169
- isAnimated.value = false;
170
- requestAnimationFrame(() => {
171
- requestAnimationFrame(() => {
172
- isAnimated.value = true;
173
- });
174
- });
175
- },
176
- { flush: "pre" }
177
- );
178
-
179
- const hoveredItemHref = ref<string | null>(null);
180
- const showCollapsed = computed(() => isCollapsed.value && isLoaded.value);
181
-
182
- const { elementClasses, resetElementClasses } = useStyleClassPassthrough(props.styleClassPassthrough);
183
-
184
- watch(
185
- () => props.styleClassPassthrough,
186
- () => resetElementClasses(props.styleClassPassthrough)
187
- );
188
- </script>
189
-
190
- <style lang="css">
191
- @layer components {
192
- .tab-nav-backdrop {
193
- position: fixed;
194
- inset: 0;
195
- z-index: 10;
196
- background: var(--tab-nav-backdrop-bg, oklch(0% 0 0 / 55%));
197
- backdrop-filter: blur(var(--tab-nav-backdrop-blur, 3px));
198
- opacity: 0;
199
- pointer-events: none;
200
- transition: opacity var(--tab-nav-backdrop-duration, 350ms) ease;
201
-
202
- &.is-open {
203
- opacity: 1;
204
- pointer-events: auto;
205
- }
206
- }
207
-
208
- .tab-navigation {
209
- /* ─── Public token API ────────────────────────────────────────────── */
210
-
211
- /* Horizontal nav */
212
- --_link-color: var(--tab-nav-link-color, var(--slate-01, currentColor));
213
- --_link-size: var(--tab-nav-link-size, 1.6rem);
214
- --_link-tracking: var(--tab-nav-link-tracking, 0.06em);
215
- --_link-weight: var(--tab-nav-link-weight, 400);
216
- --_nav-transition: var(--tab-nav-transition, 250ms ease);
217
-
218
- /* Panel */
219
- --_panel-border-color: var(
220
- --tab-nav-panel-border-color,
221
- color-mix(in oklch, var(--slate-01, #c0847a) 35%, transparent)
222
- );
223
- --_panel-link-color: var(--tab-nav-panel-link-color, var(--slate-01, currentColor));
224
- --_panel-slide-duration: var(--tab-nav-panel-slide-duration, 350ms);
225
- --_panel-slide-easing: var(--tab-nav-panel-slide-easing, cubic-bezier(0.4, 0, 0.2, 1));
226
-
227
- /* Burger */
228
- --_burger-bar-height: var(--tab-nav-burger-height, 1.5px);
229
- --_burger-bar-gap: var(--tab-nav-burger-gap, 5px);
230
- --_burger-color: var(--tab-nav-burger-color, var(--slate-01, currentColor));
231
- --_burger-transition: var(--tab-nav-burger-transition, 300ms ease);
232
-
233
- /* ─────────────────────────────────────────────────────────────────── */
234
-
235
- display: flex;
236
- align-items: center;
237
- min-width: 0;
238
-
239
- /* Hide everything until first measurement to prevent wrong-state flash */
240
- &:not(.is-loaded) {
241
- opacity: 0;
242
- }
243
-
244
- /* ─── Horizontal list ───────────────────────────────────────────── */
245
-
246
- .tab-nav-list {
247
- list-style: none;
248
- margin: 0;
249
- padding: 0;
250
- display: flex;
251
- gap: var(--tab-nav-gap, 2.2rem);
252
- align-items: center;
253
- position: relative;
254
-
255
- .nav-indicator-li {
256
- /* display: contents removes the li's box entirely — children become
257
- direct participants in the flex container, so position: absolute
258
- on .nav__hovered / .nav__active-indicator resolves against
259
- .tab-nav-list (same containing block as the anchor li elements).
260
- Without this, .nav-indicator-li is the containing block, which is
261
- a different scope and Chrome's anchor positioning rejects it. */
262
- display: contents;
263
- }
264
-
265
- /* Indicators hidden by default — shown only with anchor positioning support */
266
- .nav__hovered,
267
- .nav__active-indicator {
268
- display: none;
269
- pointer-events: none;
270
- }
271
-
272
- .tab-nav-link {
273
- display: flex;
274
- align-items: center;
275
- gap: 0.4em;
276
- color: var(--_link-color);
277
- font-size: var(--_link-size);
278
- font-weight: var(--_link-weight);
279
- letter-spacing: var(--_link-tracking);
280
- text-decoration: none;
281
- white-space: nowrap;
282
- padding-block: 0.8rem;
283
- padding-inline: 0.4rem;
284
- position: relative;
285
- z-index: 4;
286
- transition: color var(--_nav-transition);
287
-
288
- &:hover,
289
- &:focus-visible {
290
- color: var(--tab-nav-link-hover-color, var(--slate-04, var(--_link-color)));
291
- outline: none;
292
- }
293
-
294
- &.router-link-exact-active {
295
- color: var(--tab-nav-link-active-color, var(--slate-01, var(--_link-color)));
296
- }
297
- }
298
- }
299
-
300
- /* ─── Alignment variants ────────────────────────────────────────── */
301
-
302
- &.tab-navigation--center .tab-nav-list {
303
- margin-inline: auto;
304
- }
305
-
306
- &.tab-navigation--right .tab-nav-list {
307
- margin-inline-start: auto;
308
- }
309
-
310
- &.is-collapsed {
311
- justify-content: end;
312
- }
313
-
314
- /* ─── Burger button (InputButton) ──────────────────────────── */
315
-
316
- .tab-nav-burger.input-button.icon-only {
317
- margin-inline-start: auto;
318
- color: var(--_burger-color);
319
-
320
- /* Strip all InputButton visual styling */
321
- background: none;
322
- border: none;
323
- outline: none;
324
- text-decoration: none;
325
- padding: 8px;
326
- border-radius: 4px;
327
- transition: outline-color var(--_nav-transition);
328
-
329
- &.icon-only {
330
- aspect-ratio: unset;
331
- border-radius: 4px;
332
-
333
- .btn-icon {
334
- margin: 0;
335
- display: flex;
336
- flex-direction: column;
337
- gap: var(--_burger-bar-gap);
338
- }
339
- }
340
-
341
- &:focus-visible {
342
- outline: 2px solid var(--_burger-color);
343
- outline-offset: 4px;
344
- }
345
- }
346
-
347
- .burger-bar {
348
- display: block;
349
- width: var(--tab-nav-burger-width, 22px);
350
- height: var(--_burger-bar-height);
351
- background: currentColor;
352
- border-radius: 1px;
353
- transform-origin: center;
354
- transition:
355
- transform var(--_burger-transition),
356
- opacity var(--_burger-transition);
357
- }
358
-
359
- .tab-nav-burger.is-open {
360
- .burger-bar:nth-child(1) {
361
- transform: translateY(calc(var(--_burger-bar-height) + var(--_burger-bar-gap))) rotate(45deg);
362
- }
363
-
364
- .burger-bar:nth-child(2) {
365
- opacity: 0;
366
- transform: scaleX(0);
367
- }
368
-
369
- .burger-bar:nth-child(3) {
370
- transform: translateY(calc(-1 * (var(--_burger-bar-height) + var(--_burger-bar-gap)))) rotate(-45deg);
371
- }
372
- }
373
-
374
- /* ─── Mobile drop panel ─────────────────────────────────────────── */
375
-
376
- .tab-nav-panel {
377
- position: absolute;
378
- left: 0;
379
- right: 0;
380
- top: 100%;
381
-
382
- display: grid;
383
- grid-template-rows: 0fr;
384
- border-block-start: 1px solid transparent;
385
- transition:
386
- grid-template-rows var(--_panel-slide-duration) var(--_panel-slide-easing),
387
- border-color var(--_panel-slide-duration) var(--_panel-slide-easing);
388
-
389
- z-index: 1;
390
-
391
- &.is-open {
392
- grid-template-rows: 1fr;
393
- border-block-start-color: var(--_panel-border-color);
394
- }
395
-
396
- .tab-nav-panel-inner {
397
- overflow: hidden;
398
- background-color: var(--tab-nav-panel-bg, var(--page-bg, #1a1614));
399
- }
400
-
401
- .tab-nav-panel-list {
402
- list-style: none;
403
- margin: 0;
404
- padding: 0;
405
-
406
- li {
407
- border-block-end: 1px solid var(--tab-nav-panel-item-border, color-mix(in oklch, var(--slate-01, white) 8%, transparent));
408
-
409
- &:last-child {
410
- border-block-end: none;
411
- }
412
-
413
- &:hover {
414
- background-color: color-mix(in oklch, var(--slate-01, white) 5%, transparent);
415
- }
416
- }
417
-
418
- .tab-nav-panel-link {
419
- display: flex;
420
- align-items: center;
421
- gap: 0.5em;
422
- color: var(--_panel-link-color);
423
- font-size: var(--_link-size);
424
- font-weight: var(--_link-weight);
425
- letter-spacing: var(--_link-tracking);
426
- text-decoration: none;
427
- padding-block: var(--tab-nav-panel-padding-block, 1.4rem);
428
- padding-inline: var(--tab-nav-panel-padding-inline, 1.5rem);
429
- position: relative;
430
- z-index: 1;
431
- transition: color var(--_nav-transition);
432
-
433
- &:hover,
434
- &:focus-visible {
435
- color: var(--tab-nav-panel-link-hover-color, var(--slate-04, var(--_panel-link-color)));
436
- outline: none;
437
- }
438
-
439
- &.router-link-exact-active {
440
- color: var(--tab-nav-panel-link-active-color, var(--slate-01, var(--_panel-link-color)));
441
- }
442
- }
443
- }
444
- }
445
- }
446
-
447
- /* ─── Anchor positioning for nav indicators ──────────────────────────────
448
- anchor-name is declared unconditionally so the @oddbird polyfill can
449
- read it on browsers without native support (the polyfill checks for
450
- anchor-name in raw CSS text, but skips rules inside a failing @supports).
451
- The display guard stays inside @supports so indicators are hidden on
452
- truly old browsers where neither native CSS nor the polyfill will work.
453
- Requires Chrome 125+, Edge 125+, Firefox 131+, or the polyfill.
454
- ──────────────────────────────────────────────────────────────────────── */
455
-
456
- /* Single anchor --tab-nav-indicator tracks the hovered item, or falls back
457
- to the active item when nothing is hovered. No dual anchor-names needed. */
458
-
459
- /* Nothing hovered: anchor sits on the active item */
460
- .tab-navigation .tab-nav-list:not(:has(.is-hovered)) li.is-active {
461
- anchor-name: --tab-nav-indicator;
462
- }
463
-
464
- /* Something hovered: anchor sits on the hovered item */
465
- .tab-navigation .tab-nav-list li.is-hovered {
466
- anchor-name: --tab-nav-indicator;
467
- }
468
-
469
- /* @supports (anchor-name: --x) { */
470
- /* Hover highlight: background pill that follows the pointer */
471
- .tab-navigation .tab-nav-list .nav__hovered {
472
- display: block;
473
- position: absolute;
474
- position-anchor: --tab-nav-indicator;
475
- left: anchor(left);
476
- right: anchor(right);
477
- top: 0;
478
- bottom: 0;
479
- opacity: 0;
480
- pointer-events: none;
481
- background: var(--tab-nav-decorator-hovered-bg, transparent);
482
- border-radius: 4px;
483
- z-index: 1;
484
- }
485
-
486
- .tab-navigation.is-animated .tab-nav-list .nav__hovered {
487
- transition:
488
- left 200ms ease,
489
- right 200ms ease,
490
- opacity 150ms ease;
491
- }
492
-
493
- .tab-navigation .tab-nav-list:has(.is-hovered) .nav__hovered {
494
- opacity: 1;
495
- }
496
-
497
- /* Active indicator bar: always follows --tab-nav-indicator */
498
- .tab-navigation .tab-nav-list .nav__active-indicator {
499
- display: block;
500
- position: absolute;
501
- position-anchor: --tab-nav-indicator;
502
- left: anchor(left);
503
- right: anchor(right);
504
- bottom: 0;
505
- height: 2px;
506
- pointer-events: none;
507
- background-color: var(--tab-nav-decorator-indicator-color, var(--slate-01, currentColor));
508
- z-index: 3;
509
- }
510
-
511
- .tab-navigation.is-animated .tab-nav-list .nav__active-indicator {
512
- transition:
513
- left 200ms ease,
514
- right 200ms ease;
515
- }
516
- /* } */
517
- }
518
- </style>