noph-ui 0.47.0 → 0.48.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.
package/README.md CHANGED
@@ -95,6 +95,7 @@ Live demos and usage examples for every component are available on [noph.dev](ht
95
95
 
96
96
  **Containment**
97
97
  [Cards](https://noph.dev/components/card) ·
98
+ [Carousel](https://noph.dev/components/carousel) ·
98
99
  [Divider](https://noph.dev/components/divider) ·
99
100
  [Sheets](https://noph.dev/components/sheet) ·
100
101
  [Lists](https://noph.dev/components/list)
@@ -0,0 +1,410 @@
1
+ <script lang="ts">
2
+ import { arrowKeyNav } from '../keyboard-nav.js'
3
+ import { reducedMotion } from '../media.js'
4
+ import type { Attachment } from 'svelte/attachments'
5
+ import { carouselMorph, scrollTimelines } from './carouselMorph.js'
6
+ import { setCarouselContext } from './context.js'
7
+ import type { CarouselContext, CarouselItemElement, CarouselProps } from './types.js'
8
+
9
+ let {
10
+ variant = 'multi-browse',
11
+ alignment = 'start',
12
+ orientation,
13
+ snap,
14
+ label,
15
+ itemLabel = (text, position, total) => `${text}, ${position} of ${total}`,
16
+ children,
17
+ scroller = $bindable(),
18
+ element = $bindable(),
19
+ onkeydown: userKeydown,
20
+ ...attributes
21
+ }: CarouselProps = $props()
22
+
23
+ const uid = $props.id()
24
+ let track = $state<HTMLDivElement>()
25
+ let metrics = $state<HTMLDivElement>()
26
+ let snapPoints = $state<HTMLDivElement>()
27
+ let items = $state.raw<CarouselItemElement[]>([])
28
+
29
+ const axis = $derived(orientation ?? (variant === 'full-screen' ? 'vertical' : 'horizontal'))
30
+ const vertical = $derived(axis === 'vertical')
31
+ const snapping = $derived(snap ?? variant !== 'uncontained')
32
+
33
+ const keylined = $derived(variant === 'multi-browse' || variant === 'hero' ? variant : undefined)
34
+ const morphing = $derived(!!keylined && scrollTimelines && !reducedMotion.current)
35
+
36
+ const cropping = $derived(variant === 'uncontained' && !reducedMotion.current)
37
+
38
+ const collectItems: Attachment<HTMLElement> = (node) => {
39
+ const read = () => {
40
+ const found = [...node.querySelectorAll<CarouselItemElement>('.np-carousel-item')]
41
+ const same =
42
+ found.length === items.length && found.every((item, index) => item === items[index])
43
+ if (!same) items = found
44
+ }
45
+ read()
46
+ let queued = false
47
+ const observer = new MutationObserver(() => {
48
+ if (queued) return
49
+ queued = true
50
+ queueMicrotask(() => {
51
+ queued = false
52
+ read()
53
+ })
54
+ })
55
+ observer.observe(node, { childList: true, subtree: true })
56
+ return () => observer.disconnect()
57
+ }
58
+
59
+ const morph = $derived(
60
+ morphing && keylined
61
+ ? carouselMorph({ variant: keylined, alignment, vertical, items, track, metrics, uid })
62
+ : undefined,
63
+ )
64
+
65
+ const arrowHandler = $derived(arrowKeyNav('.np-carousel-item', axis, { wrap: false }))
66
+
67
+ const handleKeydown = (
68
+ event: KeyboardEvent & { currentTarget: EventTarget & HTMLDivElement },
69
+ ) => {
70
+ userKeydown?.(event)
71
+ if (!event.defaultPrevented) arrowHandler(event)
72
+ }
73
+
74
+ const places = $derived(new Map(items.map((item, index) => [item, index + 1])))
75
+
76
+ const handleFocusIn = (event: FocusEvent) => {
77
+ const item = (event.target as HTMLElement | null)?.closest<HTMLElement>('.np-carousel-item')
78
+ if (!item) return
79
+ const behavior = reducedMotion.current ? 'auto' : 'smooth'
80
+ const place = places.get(item as CarouselItemElement) ?? 0
81
+ const notch =
82
+ place > 0 ? (snapPoints?.children[place - 1] as HTMLElement | undefined) : undefined
83
+ if (notch) notch.scrollIntoView({ block: 'start', inline: 'start', behavior })
84
+ else item.scrollIntoView({ block: 'nearest', inline: 'nearest', behavior })
85
+ }
86
+
87
+ const carouselContext: CarouselContext = {
88
+ get items() {
89
+ return items
90
+ },
91
+ set items(next) {
92
+ items = next
93
+ },
94
+ position: (item) => places.get(item) ?? 0,
95
+ get itemLabel() {
96
+ return itemLabel
97
+ },
98
+ set itemLabel(next) {
99
+ itemLabel = next
100
+ },
101
+ }
102
+ setCarouselContext(carouselContext)
103
+ </script>
104
+
105
+ <div
106
+ {...attributes}
107
+ bind:this={element}
108
+ role={attributes.role ?? 'group'}
109
+ aria-roledescription={attributes['aria-roledescription'] ?? 'carousel'}
110
+ aria-label={label ?? attributes['aria-label']}
111
+ class={[
112
+ 'np-carousel',
113
+ `np-carousel-${variant}`,
114
+ `np-carousel-${axis}`,
115
+ snapping && 'np-carousel-snap',
116
+ cropping && 'np-carousel-cropping',
117
+ attributes.class,
118
+ ]}
119
+ onkeydown={handleKeydown}
120
+ onfocusin={handleFocusIn}
121
+ >
122
+ {#if keylined}
123
+ <div bind:this={metrics} class="np-carousel-metrics" aria-hidden="true">
124
+ <span class="np-carousel-metric-item"></span>
125
+ <span class="np-carousel-metric-small"></span>
126
+ </div>
127
+ {/if}
128
+ <div bind:this={scroller} class="np-carousel-scroller" {@attach morph}>
129
+ {#if morphing}
130
+ <div bind:this={snapPoints} class="np-carousel-snap-points" aria-hidden="true">
131
+ {#each items as item (item)}
132
+ <span></span>
133
+ {/each}
134
+ </div>
135
+ {/if}
136
+ <div bind:this={track} class="np-carousel-track" {@attach collectItems}>
137
+ {@render children?.()}
138
+ </div>
139
+ </div>
140
+ </div>
141
+
142
+ <style>
143
+ .np-carousel {
144
+ position: relative;
145
+ container-type: inline-size;
146
+ --_pad: 0px;
147
+ --_gap: var(--np-carousel-item-spacing, 0.5rem);
148
+ --_cross: var(--np-carousel-cross-padding, 0.5rem);
149
+ }
150
+
151
+ @media (prefers-reduced-motion: no-preference) {
152
+ .np-carousel {
153
+ --_pad: var(--np-carousel-padding, 1rem);
154
+ }
155
+ }
156
+
157
+ .np-carousel-scroller {
158
+ display: flex;
159
+ position: relative;
160
+ overflow-anchor: none;
161
+ scrollbar-width: var(--np-carousel-scrollbar-width, none);
162
+ }
163
+
164
+ .np-carousel-horizontal .np-carousel-scroller {
165
+ overflow-x: auto;
166
+ overflow-y: clip;
167
+ touch-action: pan-x pinch-zoom;
168
+ overscroll-behavior-inline: contain;
169
+ scroll-padding-inline-start: var(--_pad);
170
+ }
171
+
172
+ .np-carousel-vertical .np-carousel-scroller {
173
+ flex-direction: column;
174
+ overflow-x: clip;
175
+ overflow-y: auto;
176
+ touch-action: pan-y pinch-zoom;
177
+ block-size: var(--np-carousel-length, 100%);
178
+ scroll-padding-block-start: var(--_pad);
179
+ }
180
+
181
+ .np-carousel-snap.np-carousel-horizontal .np-carousel-scroller {
182
+ scroll-snap-type: x var(--np-carousel-snap-strictness, mandatory);
183
+ }
184
+
185
+ .np-carousel-snap.np-carousel-vertical .np-carousel-scroller {
186
+ scroll-snap-type: y var(--np-carousel-snap-strictness, mandatory);
187
+ }
188
+
189
+ .np-carousel-track {
190
+ display: flex;
191
+ flex: none;
192
+ align-items: center;
193
+ gap: var(--_gap);
194
+ padding-inline: var(--_pad);
195
+ padding-block: var(--_cross);
196
+ }
197
+
198
+ .np-carousel-vertical .np-carousel-track {
199
+ flex-direction: column;
200
+ padding-block: var(--_pad);
201
+ padding-inline: var(--_cross);
202
+ }
203
+
204
+ .np-carousel-vertical .np-carousel-track :global(.np-carousel-item-aspect) {
205
+ inline-size: var(--np-carousel-item-width, 12.5rem);
206
+ block-size: auto;
207
+ }
208
+
209
+ .np-carousel-full-screen {
210
+ --np-carousel-padding: 0px;
211
+ --_gap: 0px;
212
+ --_cross: 0px;
213
+ --np-carousel-item-container-shape: 0px;
214
+ --np-carousel-snap-stop: always;
215
+ }
216
+
217
+ .np-carousel-full-screen .np-carousel-track {
218
+ block-size: 100%;
219
+ }
220
+
221
+ .np-carousel-full-screen .np-carousel-track :global(.np-carousel-item) {
222
+ inline-size: 100%;
223
+ block-size: 100%;
224
+ flex: none;
225
+ }
226
+
227
+ .np-carousel-full-screen.np-carousel-vertical .np-carousel-scroller {
228
+ overscroll-behavior-block: contain;
229
+ }
230
+
231
+ :global(.np-carousel-morphing) .np-carousel-track {
232
+ overflow: clip;
233
+ overflow-clip-margin: var(--_cross);
234
+ }
235
+
236
+ :global(.np-carousel-morphing-inline) .np-carousel-track {
237
+ inline-size: var(--_track);
238
+ }
239
+
240
+ :global(.np-carousel-morphing-block) .np-carousel-track {
241
+ block-size: var(--_track);
242
+ }
243
+
244
+ :global(.np-carousel-morphing-inline) .np-carousel-track :global(.np-carousel-item) {
245
+ flex: 0 0 var(--_item, auto);
246
+ inline-size: var(--_item, auto);
247
+ }
248
+
249
+ :global(.np-carousel-morphing-block) .np-carousel-track :global(.np-carousel-item) {
250
+ flex: 0 0 var(--_item, auto);
251
+ block-size: var(--_item, auto);
252
+ }
253
+ @property --np-carousel-clip {
254
+ syntax: '<length>';
255
+ inherits: true;
256
+ initial-value: 0px;
257
+ }
258
+ @property --_out {
259
+ syntax: '<number>';
260
+ inherits: false;
261
+ initial-value: 0;
262
+ }
263
+
264
+ @property --_in {
265
+ syntax: '<number>';
266
+ inherits: false;
267
+ initial-value: 0;
268
+ }
269
+
270
+ @keyframes np-carousel-edge-out {
271
+ from {
272
+ --_out: 0;
273
+ }
274
+ to {
275
+ --_out: 1;
276
+ }
277
+ }
278
+
279
+ @keyframes np-carousel-edge-in {
280
+ from {
281
+ --_in: 1;
282
+ }
283
+ to {
284
+ --_in: 0;
285
+ }
286
+ }
287
+
288
+ @supports (animation-timeline: view()) {
289
+ .np-carousel-cropping .np-carousel-track :global(.np-carousel-item) {
290
+ animation:
291
+ np-carousel-edge-out linear both,
292
+ np-carousel-edge-in linear both;
293
+ animation-range: exit, entry;
294
+ --_crop-start: calc((1 - var(--_visible, 0.3333)) * var(--_out) * 100%);
295
+ --_crop-end: calc((1 - var(--_visible, 0.3333)) * var(--_in) * 100%);
296
+ --_shift: calc(var(--_visible, 0.3333) * (var(--_out) - var(--_in)) * 100%);
297
+ }
298
+
299
+ .np-carousel-cropping.np-carousel-horizontal .np-carousel-track :global(.np-carousel-item) {
300
+ animation-timeline: view(inline), view(inline);
301
+ clip-path: inset(
302
+ 0 var(--_crop-end) 0 var(--_crop-start) round
303
+ var(--np-carousel-item-container-shape, var(--np-shape-corner-extra-large))
304
+ );
305
+ --np-carousel-parallax: var(--_shift) 0;
306
+ }
307
+
308
+ .np-carousel-cropping.np-carousel-horizontal
309
+ .np-carousel-track:dir(rtl)
310
+ :global(.np-carousel-item) {
311
+ clip-path: inset(
312
+ 0 var(--_crop-start) 0 var(--_crop-end) round
313
+ var(--np-carousel-item-container-shape, var(--np-shape-corner-extra-large))
314
+ );
315
+ --_shift: calc(var(--_visible, 0.3333) * (var(--_in) - var(--_out)) * 100%);
316
+ }
317
+
318
+ .np-carousel-cropping.np-carousel-vertical .np-carousel-track :global(.np-carousel-item) {
319
+ animation-timeline: view(block), view(block);
320
+ clip-path: inset(
321
+ var(--_crop-start) 0 var(--_crop-end) 0 round
322
+ var(--np-carousel-item-container-shape, var(--np-shape-corner-extra-large))
323
+ );
324
+ --np-carousel-parallax: 0 var(--_shift);
325
+ }
326
+
327
+ .np-carousel-cropping .np-carousel-track :global(.np-carousel-item-label) {
328
+ inset-inline-start: var(--_crop-start);
329
+ inset-inline-end: var(--_crop-end);
330
+ }
331
+ }
332
+
333
+ @supports (animation-timeline: scroll(nearest inline)) {
334
+ :global(.np-carousel-morphing) .np-carousel-track :global(.np-carousel-item) {
335
+ animation: var(--_morph, none) linear both;
336
+ }
337
+
338
+ :global(.np-carousel-morphing-inline) .np-carousel-track :global(.np-carousel-item) {
339
+ animation-timeline: scroll(nearest inline);
340
+ }
341
+
342
+ :global(.np-carousel-morphing-block) .np-carousel-track :global(.np-carousel-item) {
343
+ animation-timeline: scroll(nearest block);
344
+ }
345
+ }
346
+
347
+ .np-carousel-snap-points > span {
348
+ position: absolute;
349
+ inset-block: 0;
350
+ inline-size: 1px;
351
+ pointer-events: none;
352
+ visibility: hidden;
353
+ }
354
+
355
+ .np-carousel-horizontal .np-carousel-snap-points > span {
356
+ inset-inline-start: calc(var(--_pad) + var(--_at, 0px));
357
+ }
358
+
359
+ .np-carousel-vertical .np-carousel-snap-points > span {
360
+ inset-block-start: calc(var(--_pad) + var(--_at, 0px));
361
+ inset-inline: 0;
362
+ block-size: 1px;
363
+ }
364
+
365
+ .np-carousel-snap .np-carousel-snap-points > span {
366
+ scroll-snap-align: start;
367
+ }
368
+
369
+ .np-carousel-snap .np-carousel-track :global(.np-carousel-item) {
370
+ scroll-snap-align: start;
371
+ }
372
+
373
+ :global(.np-carousel-morphing) .np-carousel-track :global(.np-carousel-item) {
374
+ scroll-snap-align: none;
375
+ }
376
+
377
+ .np-carousel-metrics {
378
+ position: absolute;
379
+ inset-block-start: 0;
380
+ inset-inline-start: 0;
381
+ block-size: 0;
382
+ visibility: hidden;
383
+ pointer-events: none;
384
+ }
385
+
386
+ .np-carousel-metrics > * {
387
+ display: block;
388
+ block-size: 0;
389
+ }
390
+
391
+ .np-carousel-metric-item {
392
+ inline-size: var(--np-carousel-item-width, 12.5rem);
393
+ }
394
+
395
+ .np-carousel-vertical .np-carousel-metric-item {
396
+ inline-size: auto;
397
+ block-size: var(--np-carousel-item-height, 12.5rem);
398
+ }
399
+
400
+ .np-carousel-metric-small {
401
+ inline-size: 0;
402
+ min-inline-size: var(--np-carousel-small-item-min-width, 2.5rem);
403
+ max-inline-size: var(--np-carousel-small-item-max-width, 3.5rem);
404
+ }
405
+
406
+ .np-carousel-vertical .np-carousel-metric-small {
407
+ min-block-size: var(--np-carousel-small-item-min-width, 2.5rem);
408
+ max-block-size: var(--np-carousel-small-item-max-width, 3.5rem);
409
+ }
410
+ </style>
@@ -0,0 +1,4 @@
1
+ import type { CarouselProps } from './types.ts';
2
+ declare const Carousel: import("svelte").Component<CarouselProps, {}, "scroller" | "element">;
3
+ type Carousel = ReturnType<typeof Carousel>;
4
+ export default Carousel;
@@ -0,0 +1,300 @@
1
+ <script lang="ts">
2
+ import '../internal/focus-ring.css'
3
+ import { pressMorph } from '../press.svelte.js'
4
+ import Ripple from '../ripple/Ripple.svelte'
5
+ import { minVisibleFraction } from './carouselStrategy.js'
6
+ import { getCarouselContext } from './context.js'
7
+ import type { CarouselItemElement, CarouselItemProps } from './types.js'
8
+
9
+ let {
10
+ type,
11
+ label,
12
+ image,
13
+ aspectRatio,
14
+ disabled = false,
15
+ children,
16
+ element = $bindable(),
17
+ onpointerdown,
18
+ style,
19
+ ...attributes
20
+ }: CarouselItemProps = $props()
21
+
22
+ const carouselContext = getCarouselContext()
23
+ const press = pressMorph()
24
+
25
+ const total = $derived(carouselContext.items.length)
26
+ const position = $derived(element ? carouselContext.position(element) : 0)
27
+
28
+ const itemStyle = $derived(
29
+ [
30
+ `--_visible:${minVisibleFraction(aspectRatio)}`,
31
+ aspectRatio != null && `--_aspect:${aspectRatio}`,
32
+ style,
33
+ ]
34
+ .filter(Boolean)
35
+ .join(';'),
36
+ )
37
+
38
+ const accessibleName = $derived(
39
+ attributes['aria-label'] ??
40
+ (type !== 'text' && label && position > 0
41
+ ? carouselContext.itemLabel(label, position, total)
42
+ : undefined),
43
+ )
44
+
45
+ const labelHidden = $derived(accessibleName !== undefined)
46
+
47
+ const handlePointerdown = (
48
+ event: PointerEvent & { currentTarget: EventTarget & CarouselItemElement },
49
+ ) => {
50
+ onpointerdown?.(event)
51
+ if (!disabled && type !== 'text') press.press()
52
+ }
53
+ </script>
54
+
55
+ {#snippet content()}
56
+ {#if image}
57
+ <div class="np-carousel-item-image" style="background-image: url({image})"></div>
58
+ {/if}
59
+ {#if children}
60
+ {@render children()}
61
+ {/if}
62
+ {#if label}
63
+ <span class="np-carousel-item-label" aria-hidden={labelHidden || undefined}>{label}</span>
64
+ {/if}
65
+ {#if !disabled && type !== 'text'}
66
+ <Ripple />
67
+ {/if}
68
+ {/snippet}
69
+
70
+ {#if type === 'text'}
71
+ <div
72
+ {...attributes}
73
+ bind:this={element}
74
+ aria-disabled={disabled}
75
+ style={itemStyle}
76
+ class={[
77
+ 'np-carousel-item',
78
+ 'np-carousel-item-plain',
79
+ aspectRatio != null && 'np-carousel-item-aspect',
80
+ disabled && 'np-carousel-item-disabled',
81
+ attributes.class,
82
+ ]}
83
+ >
84
+ {@render content()}
85
+ </div>
86
+ {:else if type === 'button'}
87
+ <button
88
+ {...attributes}
89
+ bind:this={element}
90
+ type="button"
91
+ {disabled}
92
+ aria-disabled={disabled}
93
+ aria-label={accessibleName}
94
+ onpointerdown={handlePointerdown}
95
+ style={itemStyle}
96
+ class={[
97
+ 'np-carousel-item',
98
+ press.pressed && 'np-carousel-item-pressed',
99
+ aspectRatio != null && 'np-carousel-item-aspect',
100
+ disabled && 'np-carousel-item-disabled',
101
+ attributes.class,
102
+ ]}
103
+ >
104
+ {@render content()}
105
+ </button>
106
+ {:else if type === 'link'}
107
+ <a
108
+ {...attributes}
109
+ bind:this={element}
110
+ role="link"
111
+ aria-disabled={disabled}
112
+ aria-label={accessibleName}
113
+ tabindex={disabled ? -1 : undefined}
114
+ onpointerdown={handlePointerdown}
115
+ style={itemStyle}
116
+ class={[
117
+ 'np-carousel-item',
118
+ press.pressed && 'np-carousel-item-pressed',
119
+ aspectRatio != null && 'np-carousel-item-aspect',
120
+ disabled && 'np-carousel-item-disabled',
121
+ attributes.class,
122
+ ]}
123
+ >
124
+ {@render content()}
125
+ </a>
126
+ {/if}
127
+
128
+ <style>
129
+ .np-carousel-item {
130
+ position: relative;
131
+ box-sizing: border-box;
132
+ flex: none;
133
+ display: block;
134
+ margin: 0;
135
+ padding: 0;
136
+ border: none;
137
+ font: inherit;
138
+ color: var(--np-color-on-surface);
139
+ text-align: start;
140
+ text-decoration: none;
141
+ overflow: clip;
142
+ cursor: pointer;
143
+ scroll-snap-stop: var(--np-carousel-snap-stop, normal);
144
+ -webkit-tap-highlight-color: transparent;
145
+ inline-size: var(--np-carousel-item-width, 12.5rem);
146
+ block-size: var(--np-carousel-item-height, auto);
147
+ align-self: stretch;
148
+ border-radius: var(--np-carousel-item-container-shape, var(--np-shape-corner-extra-large));
149
+ box-shadow: var(--np-carousel-item-elevation, none);
150
+ background-color: var(--np-carousel-item-container-color, transparent);
151
+ --np-ripple-hover-color: var(--np-carousel-item-state-layer-color, var(--np-color-on-surface));
152
+ --np-ripple-pressed-color: var(
153
+ --np-carousel-item-state-layer-color,
154
+ var(--np-color-on-surface)
155
+ );
156
+ }
157
+
158
+ .np-carousel-item-aspect {
159
+ aspect-ratio: var(--_aspect);
160
+ inline-size: auto;
161
+ block-size: var(--np-carousel-item-height, 12.5rem);
162
+ align-self: center;
163
+ flex: none;
164
+ }
165
+
166
+ .np-carousel-item-plain {
167
+ cursor: unset;
168
+ }
169
+
170
+ .np-carousel-item::after {
171
+ content: '';
172
+ position: absolute;
173
+ inset: 0;
174
+ border-radius: inherit;
175
+ pointer-events: none;
176
+ outline-style: solid;
177
+ outline-width: var(--np-carousel-item-outline-width, 0);
178
+ outline-color: var(--np-carousel-item-outline-color, var(--np-color-outline));
179
+ outline-offset: calc(-1 * var(--np-carousel-item-outline-width, 0px));
180
+ }
181
+
182
+ .np-carousel-item-image {
183
+ position: absolute;
184
+ inset: 0;
185
+ background-position: center;
186
+ background-size: cover;
187
+ background-repeat: no-repeat;
188
+ }
189
+
190
+ .np-carousel-item-image,
191
+ .np-carousel-item > :global(img),
192
+ .np-carousel-item > :global(picture),
193
+ .np-carousel-item > :global(video) {
194
+ translate: var(--np-carousel-parallax, 0);
195
+ }
196
+
197
+ .np-carousel-item > :global(img),
198
+ .np-carousel-item > :global(picture) {
199
+ display: block;
200
+ inline-size: 100%;
201
+ block-size: 100%;
202
+ object-fit: cover;
203
+ }
204
+
205
+ .np-carousel-item-label {
206
+ position: absolute;
207
+ inset-inline: var(--np-carousel-clip, 0px);
208
+ inset-block-end: 0;
209
+ display: block;
210
+ padding: 1rem;
211
+ font-size: 0.875rem;
212
+ line-height: 1.25rem;
213
+ font-weight: 500;
214
+ overflow: hidden;
215
+ text-overflow: ellipsis;
216
+ white-space: nowrap;
217
+ pointer-events: none;
218
+ color: var(
219
+ --np-carousel-item-label-text-color,
220
+ light-dark(var(--np-color-surface), var(--np-color-on-surface))
221
+ );
222
+ background-image: linear-gradient(
223
+ to top,
224
+ var(
225
+ --np-carousel-item-label-scrim-color,
226
+ color-mix(in srgb, var(--np-color-scrim) 60%, transparent)
227
+ ),
228
+ transparent
229
+ );
230
+ }
231
+
232
+ @media (hover: hover) {
233
+ .np-carousel-item:is(button, a):not(.np-carousel-item-disabled):hover {
234
+ box-shadow: var(--np-carousel-item-hover-elevation, var(--np-elevation-1));
235
+ }
236
+ }
237
+
238
+ .np-carousel-item:focus-visible {
239
+ outline: none;
240
+ box-shadow: var(--np-carousel-item-elevation, none);
241
+ }
242
+
243
+ .np-carousel-item:focus-visible::after {
244
+ outline-width: 3px;
245
+ outline-color: var(--np-color-secondary);
246
+ outline-offset: -3px;
247
+ }
248
+
249
+ @media (prefers-reduced-motion: no-preference) {
250
+ .np-carousel-item:focus-visible::after {
251
+ animation: focusAnimationInset var(--np-motion-expressive-slow-effects) forwards;
252
+ }
253
+ }
254
+
255
+ .np-carousel-item:focus-visible :global(.np-ripple-surface)::before {
256
+ opacity: var(--np-ripple-focus-opacity, 0.1);
257
+ }
258
+
259
+ .np-carousel-item:is(:active, .np-carousel-item-pressed) {
260
+ box-shadow: var(--np-carousel-item-elevation, none);
261
+ }
262
+
263
+ @media (prefers-reduced-motion: no-preference) {
264
+ .np-carousel-item {
265
+ transition: border-radius var(--np-motion-expressive-fast-effects);
266
+ }
267
+
268
+ .np-carousel-item:is(button, a):is(:active, .np-carousel-item-pressed) {
269
+ border-radius: var(--np-carousel-item-pressed-container-shape, var(--np-shape-corner-medium));
270
+ }
271
+ }
272
+
273
+ .np-carousel-item-disabled {
274
+ opacity: 0.38;
275
+ cursor: unset;
276
+ pointer-events: none;
277
+ box-shadow: none;
278
+ }
279
+
280
+ .np-carousel-item-disabled::after {
281
+ outline-color: color-mix(in srgb, var(--np-color-outline) 12%, transparent);
282
+ }
283
+
284
+ @media (forced-colors: active) {
285
+ .np-carousel-item::after {
286
+ outline-width: 1px;
287
+ outline-color: CanvasText;
288
+ }
289
+
290
+ .np-carousel-item-disabled::after {
291
+ outline-color: GrayText;
292
+ }
293
+
294
+ .np-carousel-item-label {
295
+ background-image: none;
296
+ background-color: Canvas;
297
+ color: CanvasText;
298
+ }
299
+ }
300
+ </style>