srcdev-nuxt-components 9.1.38 → 9.1.40

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 (29) hide show
  1. package/.claude/settings.json +4 -1
  2. package/.claude/settings.local.json +6 -1
  3. package/.claude/skills/components/carousel-flip.md +188 -0
  4. package/.claude/skills/index.md +3 -1
  5. package/.claude/skills/qa-panel.md +231 -0
  6. package/.claude/skills/testing-add-unit-test.md +32 -0
  7. package/app/components/01.atoms/{grid-stack → grids/grid-stack}/stories/GridStack.stories.ts +1 -1
  8. package/app/components/02.molecules/samaritan-prompt/SamaritanPrompt.vue +230 -0
  9. package/app/components/02.molecules/samaritan-prompt/SamaritanPromptMixed.vue +234 -0
  10. package/app/components/02.molecules/samaritan-prompt/tests/SamaritanPrompt.spec.ts +368 -0
  11. package/app/components/02.molecules/samaritan-prompt/tests/SamaritanPromptMixed.spec.ts +306 -0
  12. package/app/components/carousels/CarouselFlip.vue +211 -153
  13. package/app/components/carousels/stories/CarouselFlip.stories.ts +35 -0
  14. package/app/components/carousels/tests/CarouselFlip.spec.ts +38 -0
  15. package/app/composables/tests/useCancellableTimer.spec.ts +135 -0
  16. package/app/composables/useCancellableTimer.ts +42 -0
  17. package/app/pages/samaritan.vue +332 -0
  18. package/app/pages/ui/carousel-flip.vue +222 -17
  19. package/package.json +1 -1
  20. /package/app/components/01.atoms/{scroll-reveal-frame → animations/scroll-reveal-frame}/ScrollRevealFrame.vue +0 -0
  21. /package/app/components/01.atoms/{scroll-reveal-frame → animations/scroll-reveal-frame}/stories/ScrollRevealFrame.stories.ts +0 -0
  22. /package/app/components/01.atoms/{scroll-reveal-frame → animations/scroll-reveal-frame}/tests/ScrollRevealFrame.spec.ts +0 -0
  23. /package/app/components/01.atoms/{scroll-reveal-frame → animations/scroll-reveal-frame}/tests/__snapshots__/ScrollRevealFrame.spec.ts.snap +0 -0
  24. /package/app/components/01.atoms/{scroll-reveal-image → animations/scroll-reveal-image}/ScrollRevealImage.vue +0 -0
  25. /package/app/components/01.atoms/{scroll-reveal-image → animations/scroll-reveal-image}/stories/ScrollRevealImage.stories.ts +0 -0
  26. /package/app/components/01.atoms/{scroll-reveal-image → animations/scroll-reveal-image}/tests/ScrollRevealImage.spec.ts +0 -0
  27. /package/app/components/01.atoms/{scroll-reveal-image → animations/scroll-reveal-image}/tests/__snapshots__/ScrollRevealImage.spec.ts.snap +0 -0
  28. /package/app/components/01.atoms/{grid-stack → grids/grid-stack}/GridStack.vue +0 -0
  29. /package/app/components/01.atoms/{grid-stack → grids/grid-stack}/tests/GridStack.spec.ts +0 -0
@@ -0,0 +1,42 @@
1
+ export function useCancellableTimer() {
2
+ let _isActive = false;
3
+ let timeoutId: ReturnType<typeof setTimeout> | null = null;
4
+ let rejectCurrent: (() => void) | null = null;
5
+
6
+ const wait = (ms: number): Promise<void> =>
7
+ new Promise((resolve, reject) => {
8
+ if (!_isActive) {
9
+ reject();
10
+ return;
11
+ }
12
+ rejectCurrent = reject;
13
+ timeoutId = setTimeout(() => {
14
+ rejectCurrent = null;
15
+ if (_isActive) resolve();
16
+ else reject();
17
+ }, ms);
18
+ });
19
+
20
+ const schedule = (fn: () => void, ms: number) => {
21
+ if (!_isActive) return;
22
+ timeoutId = setTimeout(() => {
23
+ if (_isActive) fn();
24
+ }, ms);
25
+ };
26
+
27
+ const stop = () => {
28
+ _isActive = false;
29
+ if (timeoutId !== null) {
30
+ clearTimeout(timeoutId);
31
+ timeoutId = null;
32
+ }
33
+ rejectCurrent?.();
34
+ rejectCurrent = null;
35
+ };
36
+
37
+ const start = () => {
38
+ _isActive = true;
39
+ };
40
+
41
+ return { wait, schedule, stop, start };
42
+ }
@@ -0,0 +1,332 @@
1
+ <template>
2
+ <div>
3
+ <NuxtLayout name="default">
4
+ <template #layout-content>
5
+ <div v-if="isDev" class="qa-panel">
6
+ <details class="qa-panel__details">
7
+ <summary class="qa-panel__summary">
8
+ <span class="qa-panel__title">QA — SamaritanPrompt</span>
9
+ <code v-if="qaEffect === 'typewriter'" class="qa-panel__status">
10
+ effect:typewriter · type:{{ qaTypeSpeed }}ms · hold:{{ qaHoldDuration }}ms · del:{{ qaDeleteSpeed }}ms ·
11
+ pause:{{ qaPauseDuration }}ms
12
+ </code>
13
+ <code v-else class="qa-panel__status">
14
+ effect:word-pulse · fade:{{ qaFadeDuration }}ms · visible:{{ qaWordDuration }}ms · pause:{{
15
+ qaPauseDuration
16
+ }}ms
17
+ </code>
18
+ </summary>
19
+ <div class="qa-panel__body">
20
+ <div class="qa-panel__group">
21
+ <span class="qa-panel__label">Effect</span>
22
+ <div class="qa-panel__chips">
23
+ <button
24
+ v-for="opt in effectOptions"
25
+ :key="opt"
26
+ class="qa-panel__chip"
27
+ :class="{ 'is-active': qaEffect === opt }"
28
+ @click="qaEffect = opt"
29
+ >
30
+ {{ opt }}
31
+ </button>
32
+ </div>
33
+ </div>
34
+
35
+ <!-- Typewriter controls -->
36
+ <template v-if="qaEffect === 'typewriter'">
37
+ <div class="qa-panel__group">
38
+ <span class="qa-panel__label">Type speed (ms/char)</span>
39
+ <div class="qa-panel__chips">
40
+ <button
41
+ v-for="n in [40, 80, 120, 200]"
42
+ :key="n"
43
+ class="qa-panel__chip"
44
+ :class="{ 'is-active': qaTypeSpeed === n }"
45
+ @click="qaTypeSpeed = n"
46
+ >
47
+ {{ n }}
48
+ </button>
49
+ </div>
50
+ </div>
51
+
52
+ <div class="qa-panel__group">
53
+ <span class="qa-panel__label">Hold duration (ms)</span>
54
+ <div class="qa-panel__chips">
55
+ <button
56
+ v-for="n in [500, 1000, 2000, 4000]"
57
+ :key="n"
58
+ class="qa-panel__chip"
59
+ :class="{ 'is-active': qaHoldDuration === n }"
60
+ @click="qaHoldDuration = n"
61
+ >
62
+ {{ n }}
63
+ </button>
64
+ </div>
65
+ </div>
66
+
67
+ <div class="qa-panel__group">
68
+ <span class="qa-panel__label">Delete speed (ms/char)</span>
69
+ <div class="qa-panel__chips">
70
+ <button
71
+ v-for="n in [20, 40, 80]"
72
+ :key="n"
73
+ class="qa-panel__chip"
74
+ :class="{ 'is-active': qaDeleteSpeed === n }"
75
+ @click="qaDeleteSpeed = n"
76
+ >
77
+ {{ n }}
78
+ </button>
79
+ </div>
80
+ </div>
81
+ </template>
82
+
83
+ <!-- Word-pulse controls -->
84
+ <template v-else>
85
+ <div class="qa-panel__group">
86
+ <span class="qa-panel__label">Fade duration (ms)</span>
87
+ <div class="qa-panel__chips">
88
+ <button
89
+ v-for="n in [200, 400, 600, 1000]"
90
+ :key="n"
91
+ class="qa-panel__chip"
92
+ :class="{ 'is-active': qaFadeDuration === n }"
93
+ @click="qaFadeDuration = n"
94
+ >
95
+ {{ n }}
96
+ </button>
97
+ </div>
98
+ </div>
99
+
100
+ <div class="qa-panel__group">
101
+ <span class="qa-panel__label">Visible duration (ms)</span>
102
+ <div class="qa-panel__chips">
103
+ <button
104
+ v-for="n in [800, 1200, 2000, 3000]"
105
+ :key="n"
106
+ class="qa-panel__chip"
107
+ :class="{ 'is-active': qaWordDuration === n }"
108
+ @click="qaWordDuration = n"
109
+ >
110
+ {{ n }}
111
+ </button>
112
+ </div>
113
+ </div>
114
+ </template>
115
+
116
+ <!-- Shared: pause before restart -->
117
+ <div class="qa-panel__group">
118
+ <span class="qa-panel__label">Pause before restart (ms)</span>
119
+ <div class="qa-panel__chips">
120
+ <button
121
+ v-for="n in [300, 500, 1000, 2000, 5000, 10000]"
122
+ :key="n"
123
+ class="qa-panel__chip"
124
+ :class="{ 'is-active': qaPauseDuration === n }"
125
+ @click="qaPauseDuration = n"
126
+ >
127
+ {{ n }}
128
+ </button>
129
+ </div>
130
+ </div>
131
+
132
+ <!-- Shared: hide cursor in cycle -->
133
+ <div class="qa-panel__group">
134
+ <span class="qa-panel__label">Hide cursor in cycle</span>
135
+ <div class="qa-panel__chips">
136
+ <button
137
+ v-for="opt in [true, false]"
138
+ :key="String(opt)"
139
+ class="qa-panel__chip"
140
+ :class="{ 'is-active': qaHideCursorInCycle === opt }"
141
+ @click="qaHideCursorInCycle = opt"
142
+ >
143
+ {{ opt ? "on" : "off" }}
144
+ </button>
145
+ </div>
146
+ </div>
147
+ </div>
148
+ </details>
149
+ </div>
150
+
151
+ <LayoutRow tag="div" variant="full-width">
152
+ <div class="samaritan-stage samaritan-stage--mixed">
153
+ <SamaritanPromptMixed :message-configs="mixedMessages" :hide-cursor-in-cycle="false" />
154
+ </div>
155
+ </LayoutRow>
156
+
157
+ <LayoutRow tag="div" variant="full-width">
158
+ <div class="samaritan-stage">
159
+ <SamaritanPrompt
160
+ :messages="messages"
161
+ :effect="qaEffect"
162
+ :type-speed="qaTypeSpeed"
163
+ :delete-speed="qaDeleteSpeed"
164
+ :hold-duration="qaHoldDuration"
165
+ :pause-duration="qaPauseDuration"
166
+ :word-duration="qaWordDuration"
167
+ :fade-duration="qaFadeDuration"
168
+ :hide-cursor-in-cycle="qaHideCursorInCycle"
169
+ />
170
+ </div>
171
+ </LayoutRow>
172
+ </template>
173
+ </NuxtLayout>
174
+ </div>
175
+ </template>
176
+
177
+ <script setup lang="ts">
178
+ definePageMeta({
179
+ layout: false,
180
+ });
181
+
182
+ useHead({
183
+ title: "Samaritan",
184
+ meta: [{ name: "description", content: "Samaritan prompt effect" }],
185
+ bodyAttrs: { class: "samaritan-page" },
186
+ });
187
+
188
+ const messages = [
189
+ "What do I do?",
190
+ "Bring your imaginaton to life",
191
+ "Craft beautiful interfaces",
192
+ "Keep it simple",
193
+ "My work is?",
194
+ "Performance tested",
195
+ "Penetration tested",
196
+ "Accessible",
197
+ ];
198
+
199
+ const mixedMessages = [
200
+ { text: "What do I do?", effect: "typewriter" as const, holdDuration: 7000, pauseDuration: 3000 },
201
+ { text: "Bring your imaginaton to life", effect: "word-pulse" as const, wordDuration: 7000, pauseDuration: 3000 },
202
+ { text: "Craft beautiful interfaces", effect: "word-pulse" as const, wordDuration: 7000, pauseDuration: 3000 },
203
+ { text: "Keep it simple", effect: "word-pulse" as const, wordDuration: 7000, pauseDuration: 3000 },
204
+ { text: "My work is?", effect: "typewriter" as const, holdDuration: 7000, pauseDuration: 3000 },
205
+ { text: "Performance tested", effect: "word-pulse" as const, wordDuration: 7000, pauseDuration: 3000 },
206
+ { text: "Penetration tested", effect: "word-pulse" as const, wordDuration: 7000, pauseDuration: 3000 },
207
+ { text: "Accessible", effect: "word-pulse" as const, wordDuration: 7000, pauseDuration: 3000 },
208
+ ];
209
+
210
+ // ── QA controls (dev only) ────────────────────────────────────────
211
+ const isDev = import.meta.dev;
212
+
213
+ const effectOptions = ["typewriter", "word-pulse"] as const;
214
+ const qaEffect = ref<"typewriter" | "word-pulse">("typewriter");
215
+
216
+ const qaTypeSpeed = ref(80);
217
+ const qaDeleteSpeed = ref(40);
218
+ const qaHoldDuration = ref(2000);
219
+ const qaPauseDuration = ref(5000);
220
+ const qaWordDuration = ref(1200);
221
+ const qaFadeDuration = ref(400);
222
+ const qaHideCursorInCycle = ref(true);
223
+ </script>
224
+
225
+ <style lang="css">
226
+ .samaritan-page {
227
+ background: #0a0a0a;
228
+
229
+ --samaritan-font-size: clamp(1.4rem, 2.5vw, 2.8rem);
230
+ --samaritan-letter-spacing: 0.14em;
231
+
232
+ .samaritan-stage {
233
+ display: flex;
234
+ align-items: center;
235
+ justify-content: center;
236
+ min-block-size: 100dvh;
237
+
238
+ &--mixed {
239
+ border-top: 1px solid oklch(100% 0 0 / 0.08);
240
+ }
241
+ }
242
+
243
+ /* ── QA Panel ──────────────────────────────────────────────────── */
244
+
245
+ .qa-panel {
246
+ background: oklch(15% 0 0);
247
+ color: white;
248
+ font-size: 1.3rem;
249
+ }
250
+
251
+ .qa-panel__details {
252
+ padding: 1rem 2rem;
253
+ }
254
+
255
+ .qa-panel__summary {
256
+ cursor: pointer;
257
+ display: flex;
258
+ align-items: center;
259
+ gap: 1.6rem;
260
+ list-style: none;
261
+ user-select: none;
262
+
263
+ &::-webkit-details-marker {
264
+ display: none;
265
+ }
266
+ }
267
+
268
+ .qa-panel__title {
269
+ font-weight: 600;
270
+ font-size: 1.1rem;
271
+ text-transform: uppercase;
272
+ letter-spacing: 0.08em;
273
+ }
274
+
275
+ .qa-panel__status {
276
+ font-family: monospace;
277
+ font-size: 1.2rem;
278
+ background: oklch(0% 0 0 / 0.3);
279
+ padding: 0.2rem 0.8rem;
280
+ border-radius: 0.4rem;
281
+ user-select: text;
282
+ cursor: text;
283
+ }
284
+
285
+ .qa-panel__body {
286
+ display: flex;
287
+ flex-wrap: wrap;
288
+ gap: 2.4rem;
289
+ padding-block: 1.2rem 0.4rem;
290
+ }
291
+
292
+ .qa-panel__group {
293
+ display: flex;
294
+ flex-direction: column;
295
+ gap: 0.6rem;
296
+ }
297
+
298
+ .qa-panel__label {
299
+ font-size: 1.1rem;
300
+ text-transform: uppercase;
301
+ letter-spacing: 0.08em;
302
+ opacity: 0.55;
303
+ }
304
+
305
+ .qa-panel__chips {
306
+ display: flex;
307
+ flex-wrap: wrap;
308
+ gap: 0.4rem;
309
+ }
310
+
311
+ .qa-panel__chip {
312
+ font-family: monospace;
313
+ font-size: 1.2rem;
314
+ color: white;
315
+ background: oklch(0% 0 0 / 0.25);
316
+ border: 1px solid oklch(100% 0 0 / 0.18);
317
+ padding: 0.3rem 1rem;
318
+ border-radius: 0.4rem;
319
+ cursor: pointer;
320
+ transition: background 0.15s;
321
+
322
+ &:hover {
323
+ background: oklch(0% 0 0 / 0.4);
324
+ }
325
+
326
+ &.is-active {
327
+ background: oklch(55% 0.18 240);
328
+ border-color: oklch(55% 0.18 240);
329
+ }
330
+ }
331
+ }
332
+ </style>
@@ -6,24 +6,127 @@
6
6
  <h1 class="page-heading-2">Carousel</h1>
7
7
  </LayoutRow>
8
8
 
9
- <LayoutRow tag="div" variant="popout" :style-class-passthrough="['mbe-20']">
10
- <CarouselFlip
11
- v-if="carouselStatus === 'success'"
12
- :carousel-data-ids
13
- :allow-carousel-overflow="true"
14
- :transition-speed="1000"
15
- :use-flip-animation="true"
16
- :use-spring-effect="false"
17
- :style-class-passthrough="['carousel-flip-demo', 'mbe-20']"
18
- >
19
- <template v-for="(item, index) in carouselData?.items" :key="index" #[item.id]>
20
- <div class="custom-carousel-item">
21
- <h3>{{ index + 1 }}</h3>
22
- <p>{{ item.alt }}</p>
9
+ <!-- ── QA Panel (dev only) ───────────────────────────────── -->
10
+ <div v-if="isDev" class="qa-panel">
11
+ <details class="qa-panel__details">
12
+ <summary class="qa-panel__summary">
13
+ <span class="qa-panel__title">QA — CarouselFlip</span>
14
+ <code class="qa-panel__status">
15
+ overflow:{{ qaAllowOverflow ? "on" : "off" }} · speed:{{ qaTransitionSpeed }}ms · flip:{{
16
+ qaUseFlipAnimation ? "on" : "off"
17
+ }}
18
+ · spring:{{ qaUseSpringEffect ? "on" : "off" }} · {{ qaButtonLayout }} · controls:{{
19
+ qaShowControls ? "on" : "off"
20
+ }}
21
+ </code>
22
+ </summary>
23
+ <div class="qa-panel__body">
24
+ <div class="qa-panel__group">
25
+ <span class="qa-panel__label">Allow Overflow</span>
26
+ <div class="qa-panel__chips">
27
+ <button
28
+ v-for="opt in [true, false]"
29
+ :key="String(opt)"
30
+ class="qa-panel__chip"
31
+ :class="{ 'is-active': qaAllowOverflow === opt }"
32
+ @click="qaAllowOverflow = opt"
33
+ >
34
+ {{ opt ? "on" : "off" }}
35
+ </button>
36
+ </div>
23
37
  </div>
24
- </template>
25
- </CarouselFlip>
26
- </LayoutRow>
38
+ <div class="qa-panel__group">
39
+ <span class="qa-panel__label">Transition Speed</span>
40
+ <div class="qa-panel__chips">
41
+ <button
42
+ v-for="preset in transitionSpeedPresets"
43
+ :key="preset"
44
+ class="qa-panel__chip"
45
+ :class="{ 'is-active': qaTransitionSpeed === preset }"
46
+ @click="qaTransitionSpeed = preset"
47
+ >
48
+ {{ preset }}ms
49
+ </button>
50
+ </div>
51
+ </div>
52
+ <div class="qa-panel__group">
53
+ <span class="qa-panel__label">Flip Animation</span>
54
+ <div class="qa-panel__chips">
55
+ <button
56
+ v-for="opt in [true, false]"
57
+ :key="String(opt)"
58
+ class="qa-panel__chip"
59
+ :class="{ 'is-active': qaUseFlipAnimation === opt }"
60
+ @click="qaUseFlipAnimation = opt"
61
+ >
62
+ {{ opt ? "on" : "off" }}
63
+ </button>
64
+ </div>
65
+ </div>
66
+ <div class="qa-panel__group">
67
+ <span class="qa-panel__label">Spring Effect</span>
68
+ <div class="qa-panel__chips">
69
+ <button
70
+ v-for="opt in [true, false]"
71
+ :key="String(opt)"
72
+ class="qa-panel__chip"
73
+ :class="{ 'is-active': qaUseSpringEffect === opt }"
74
+ @click="qaUseSpringEffect = opt"
75
+ >
76
+ {{ opt ? "on" : "off" }}
77
+ </button>
78
+ </div>
79
+ </div>
80
+ <div class="qa-panel__group">
81
+ <span class="qa-panel__label">Button Layout</span>
82
+ <div class="qa-panel__chips">
83
+ <button
84
+ v-for="layout in buttonLayoutPresets"
85
+ :key="layout"
86
+ class="qa-panel__chip"
87
+ :class="{ 'is-active': qaButtonLayout === layout }"
88
+ @click="qaButtonLayout = layout"
89
+ >
90
+ {{ layout }}
91
+ </button>
92
+ </div>
93
+ </div>
94
+ <div class="qa-panel__group">
95
+ <span class="qa-panel__label">Show Controls</span>
96
+ <div class="qa-panel__chips">
97
+ <button
98
+ v-for="opt in [true, false]"
99
+ :key="String(opt)"
100
+ class="qa-panel__chip"
101
+ :class="{ 'is-active': qaShowControls === opt }"
102
+ @click="qaShowControls = opt"
103
+ >
104
+ {{ opt ? "on" : "off" }}
105
+ </button>
106
+ </div>
107
+ </div>
108
+ </div>
109
+ </details>
110
+ </div>
111
+
112
+ <CarouselFlip
113
+ v-if="carouselStatus === 'success'"
114
+ :carousel-data-ids
115
+ :allow-carousel-overflow="qaAllowOverflow"
116
+ :transition-speed="qaTransitionSpeed"
117
+ :use-flip-animation="qaUseFlipAnimation"
118
+ :use-spring-effect="qaUseSpringEffect"
119
+ :button-layout="qaButtonLayout"
120
+ :show-controls="qaShowControls"
121
+ :style-class-passthrough="['carousel-flip-demo', 'mbe-20']"
122
+ >
123
+ <template v-for="(item, index) in carouselData?.items" :key="index" #[item.id]>
124
+ <div class="custom-carousel-item">
125
+ <h3>{{ index + 1 }}</h3>
126
+ <p>{{ item.alt }}</p>
127
+ </div>
128
+ </template>
129
+ </CarouselFlip>
27
130
  </template>
28
131
  </NuxtLayout>
29
132
  </div>
@@ -32,6 +135,17 @@
32
135
  <script setup lang="ts">
33
136
  import type { ICarouselBasic } from "~/types/components";
34
137
 
138
+ // ── QA controls (dev only) ────────────────────────────────────────
139
+ const isDev = import.meta.dev;
140
+ const qaAllowOverflow = ref(true);
141
+ const qaTransitionSpeed = ref(1000);
142
+ const qaUseFlipAnimation = ref(true);
143
+ const qaUseSpringEffect = ref(false);
144
+ const qaButtonLayout = ref<"sides" | "controls-flanking" | "controls-grouped-right" | "overlay">("sides");
145
+ const qaShowControls = ref(true);
146
+ const transitionSpeedPresets = [100, 200, 400, 600, 1000, 2000];
147
+ const buttonLayoutPresets = ["sides", "controls-flanking", "controls-grouped-right", "overlay"] as const;
148
+
35
149
  definePageMeta({
36
150
  layout: false,
37
151
  });
@@ -54,6 +168,97 @@ const carouselDataIds = computed(() => {
54
168
  </script>
55
169
 
56
170
  <style lang="css">
171
+ .carousel-flip-page {
172
+ /* ── QA Panel ──────────────────────────────────────────────────── */
173
+
174
+ .qa-panel {
175
+ background: oklch(15% 0 0);
176
+ color: white;
177
+ font-size: 1.3rem;
178
+ }
179
+
180
+ .qa-panel__details {
181
+ padding: 1rem 2rem;
182
+ }
183
+
184
+ .qa-panel__summary {
185
+ cursor: pointer;
186
+ display: flex;
187
+ align-items: center;
188
+ gap: 1.6rem;
189
+ list-style: none;
190
+ user-select: none;
191
+
192
+ &::-webkit-details-marker {
193
+ display: none;
194
+ }
195
+ }
196
+
197
+ .qa-panel__title {
198
+ font-weight: 600;
199
+ font-size: 1.1rem;
200
+ text-transform: uppercase;
201
+ letter-spacing: 0.08em;
202
+ }
203
+
204
+ .qa-panel__status {
205
+ font-family: monospace;
206
+ font-size: 1.2rem;
207
+ background: oklch(0% 0 0 / 0.3);
208
+ padding: 0.2rem 0.8rem;
209
+ border-radius: 0.4rem;
210
+ user-select: text;
211
+ cursor: text;
212
+ }
213
+
214
+ .qa-panel__body {
215
+ display: flex;
216
+ flex-wrap: wrap;
217
+ gap: 2.4rem;
218
+ padding-block: 1.2rem 0.4rem;
219
+ }
220
+
221
+ .qa-panel__group {
222
+ display: flex;
223
+ flex-direction: column;
224
+ gap: 0.6rem;
225
+ }
226
+
227
+ .qa-panel__label {
228
+ font-size: 1.1rem;
229
+ text-transform: uppercase;
230
+ letter-spacing: 0.08em;
231
+ opacity: 0.55;
232
+ }
233
+
234
+ .qa-panel__chips {
235
+ display: flex;
236
+ flex-wrap: wrap;
237
+ gap: 0.4rem;
238
+ }
239
+
240
+ .qa-panel__chip {
241
+ font-family: monospace;
242
+ font-size: 1.2rem;
243
+ color: white;
244
+ background: oklch(0% 0 0 / 0.25);
245
+ border: 1px solid oklch(100% 0 0 / 0.18);
246
+ padding: 0.3rem 1rem;
247
+ border-radius: 0.4rem;
248
+ cursor: pointer;
249
+ transition: background 0.15s;
250
+
251
+ &:hover {
252
+ background: oklch(0% 0 0 / 0.4);
253
+ }
254
+
255
+ &.is-active {
256
+ background: oklch(55% 0.18 240);
257
+ border-color: oklch(55% 0.18 240);
258
+ }
259
+ }
260
+ }
261
+
57
262
  @property --glow-deg {
58
263
  syntax: "<angle>";
59
264
  inherits: true;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "srcdev-nuxt-components",
3
3
  "type": "module",
4
- "version": "9.1.38",
4
+ "version": "9.1.40",
5
5
  "main": "nuxt.config.ts",
6
6
  "types": "types.d.ts",
7
7
  "license": "MIT",