@streamscloud/kit 0.0.1-1771006476137 → 0.0.1-1771075495185

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 (51) hide show
  1. package/dist/ui/player/buttons/cmp.mobile-player-buttons.svelte +37 -0
  2. package/dist/ui/player/buttons/cmp.mobile-player-buttons.svelte.d.ts +7 -0
  3. package/dist/ui/player/buttons/cmp.player-buttons.svelte +127 -0
  4. package/dist/ui/player/buttons/cmp.player-buttons.svelte.d.ts +9 -0
  5. package/dist/ui/player/buttons/cmp.responsive-player-buttons.svelte +32 -0
  6. package/dist/ui/player/buttons/cmp.responsive-player-buttons.svelte.d.ts +8 -0
  7. package/dist/ui/player/buttons/index.d.ts +4 -0
  8. package/dist/ui/player/buttons/index.js +3 -0
  9. package/dist/ui/player/buttons/types.d.ts +7 -0
  10. package/dist/ui/player/buttons/types.js +1 -0
  11. package/dist/ui/player/carousel/carousel-localization.d.ts +3 -0
  12. package/dist/ui/player/carousel/carousel-localization.js +12 -0
  13. package/dist/ui/player/carousel/cmp.carousel.svelte +418 -0
  14. package/dist/ui/player/carousel/cmp.carousel.svelte.d.ts +38 -0
  15. package/dist/ui/player/carousel/index.d.ts +2 -0
  16. package/dist/ui/player/carousel/index.js +1 -0
  17. package/dist/ui/player/carousel/types.d.ts +1 -0
  18. package/dist/ui/player/carousel/types.js +1 -0
  19. package/dist/ui/player/feed-slider/cmp.feed-slider.svelte +206 -0
  20. package/dist/ui/player/feed-slider/cmp.feed-slider.svelte.d.ts +43 -0
  21. package/dist/ui/player/feed-slider/index.d.ts +3 -0
  22. package/dist/ui/player/feed-slider/index.js +2 -0
  23. package/dist/ui/player/feed-slider/prevent-feed-scroll.d.ts +5 -0
  24. package/dist/ui/player/feed-slider/prevent-feed-scroll.js +11 -0
  25. package/dist/ui/player/feed-slider/types.d.ts +16 -0
  26. package/dist/ui/player/feed-slider/types.js +1 -0
  27. package/dist/ui/player/feed-slider/wheel-gestures-adapter.d.ts +17 -0
  28. package/dist/ui/player/feed-slider/wheel-gestures-adapter.js +79 -0
  29. package/dist/ui/player/providers/chunks-player-buffer/index.d.ts +2 -0
  30. package/dist/ui/player/providers/chunks-player-buffer/index.js +2 -0
  31. package/dist/ui/player/providers/chunks-player-buffer/player-chunk-item.svelte.d.ts +9 -0
  32. package/dist/ui/player/providers/chunks-player-buffer/player-chunk-item.svelte.js +9 -0
  33. package/dist/ui/player/providers/chunks-player-buffer/player-chunk.svelte.d.ts +30 -0
  34. package/dist/ui/player/providers/chunks-player-buffer/player-chunk.svelte.js +72 -0
  35. package/dist/ui/player/providers/chunks-player-buffer/player-chunks-manager.svelte.d.ts +23 -0
  36. package/dist/ui/player/providers/chunks-player-buffer/player-chunks-manager.svelte.js +220 -0
  37. package/dist/ui/player/providers/default-chunks-player-buffer.svelte.d.ts +23 -0
  38. package/dist/ui/player/providers/default-chunks-player-buffer.svelte.js +71 -0
  39. package/dist/ui/player/providers/default-feed-player-buffer.svelte.d.ts +29 -0
  40. package/dist/ui/player/providers/default-feed-player-buffer.svelte.js +121 -0
  41. package/dist/ui/player/providers/index.d.ts +4 -0
  42. package/dist/ui/player/providers/index.js +3 -0
  43. package/dist/ui/player/providers/service.d.ts +2 -0
  44. package/dist/ui/player/providers/service.js +13 -0
  45. package/dist/ui/player/providers/types.d.ts +54 -0
  46. package/dist/ui/player/providers/types.js +1 -0
  47. package/dist/ui/player/utils/index.d.ts +1 -0
  48. package/dist/ui/player/utils/index.js +1 -0
  49. package/dist/ui/player/utils/touch-synchronizer.d.ts +7 -0
  50. package/dist/ui/player/utils/touch-synchronizer.js +21 -0
  51. package/package.json +23 -5
@@ -0,0 +1,37 @@
1
+ <script lang="ts">import { Icon } from '../../icon';
2
+ let { actions } = $props();
3
+ </script>
4
+
5
+ <div class="mobile-player-buttons">
6
+ {#each actions as action (action.icon)}
7
+ <button type="button" class="mobile-player-buttons__action" disabled={action.disabled} onclick={action.callback}>
8
+ <span class="mobile-player-buttons__action-icon">
9
+ <Icon src={action.icon} color={action.iconColor} />
10
+ </span>
11
+ </button>
12
+ {/each}
13
+ </div>
14
+
15
+ <style>.mobile-player-buttons {
16
+ cursor: pointer;
17
+ display: flex;
18
+ flex-direction: column;
19
+ justify-content: center;
20
+ pointer-events: auto;
21
+ }
22
+ .mobile-player-buttons__action {
23
+ display: flex;
24
+ justify-content: center;
25
+ align-items: center;
26
+ padding: 1rem;
27
+ --sc-kit--icon--color: #ffffff;
28
+ --sc-kit--icon--size: 2rem;
29
+ --sc-kit--icon--filter: drop-shadow(1px 1px 0 rgba(0, 0, 0, 0.2));
30
+ }
31
+ .mobile-player-buttons__action:disabled {
32
+ opacity: 0.5;
33
+ cursor: default;
34
+ }
35
+ .mobile-player-buttons__action-icon {
36
+ display: block;
37
+ }</style>
@@ -0,0 +1,7 @@
1
+ import type { PlayerButtonDef } from './types';
2
+ type Props = {
3
+ actions: PlayerButtonDef[];
4
+ };
5
+ declare const Cmp: import("svelte").Component<Props, {}, "">;
6
+ type Cmp = ReturnType<typeof Cmp>;
7
+ export default Cmp;
@@ -0,0 +1,127 @@
1
+ <script lang="ts">import { Icon } from '../../icon';
2
+ let { actions, scaleEffect = false, zoom = 1 } = $props();
3
+ </script>
4
+
5
+ {#if actions.length === 1}
6
+ {@const action = actions[0]}
7
+ <button
8
+ type="button"
9
+ class="player-button"
10
+ style:zoom={zoom}
11
+ class:player-button--scale-effect={scaleEffect}
12
+ disabled={action.disabled}
13
+ onclick={action.callback}>
14
+ <span class="player-button__icon">
15
+ <Icon src={action.icon} color={action.iconColor} />
16
+ </span>
17
+ </button>
18
+ {:else if actions.length > 1}
19
+ <div class="player-buttons" style:zoom={zoom}>
20
+ {#each actions as action (action.icon)}
21
+ <button
22
+ type="button"
23
+ class="player-buttons__action"
24
+ class:player-buttons__action--scale-effect={scaleEffect}
25
+ disabled={action.disabled}
26
+ onclick={action.callback}>
27
+ <span class="player-buttons__action-icon">
28
+ <Icon src={action.icon} color={action.iconColor} />
29
+ </span>
30
+ </button>
31
+ {/each}
32
+ </div>
33
+ {/if}
34
+
35
+ <style>.player-button {
36
+ --_player-button--color: var(--sc-kit--player-button--color, rgb(from light-dark(#272727, #000000) r g b / 95%));
37
+ --_player-button--color--inactive: var(--sc-kit--player-button--color--inactive, rgb(from var(--_player-button--color) r g b / 60%));
38
+ --_player-button--icon-scale: 1;
39
+ pointer-events: auto;
40
+ padding: 0.625rem;
41
+ display: flex;
42
+ justify-content: center;
43
+ align-items: center;
44
+ background-color: var(--_player-button--color--inactive);
45
+ transition: background-color 0.5s;
46
+ border-radius: 50%;
47
+ color: #ffffff;
48
+ --sc-kit--icon--color: #ffffff;
49
+ --sc-kit--icon--size: 1.75rem;
50
+ }
51
+ .player-button:hover:not(:disabled) {
52
+ background-color: var(--_player-button--color);
53
+ }
54
+ .player-button:disabled {
55
+ opacity: 0.5;
56
+ cursor: default;
57
+ }
58
+ .player-button--scale-effect:hover:not(:disabled) {
59
+ --_player-button--icon-scale: 1.2;
60
+ }
61
+ .player-button__icon {
62
+ display: block;
63
+ transform: scale(var(--_player-button--icon-scale));
64
+ transition: 0.3s;
65
+ }
66
+ .player-button {
67
+ /* Set 'container-type: inline-size;' to reference container*/
68
+ }
69
+ @container (width < 576px) {
70
+ .player-button {
71
+ padding: 0.5rem;
72
+ --sc-kit--icon--size: 1.5rem;
73
+ }
74
+ }
75
+
76
+ .player-buttons {
77
+ --_player-button--color: var(--sc-kit--player-button--color, rgb(from light-dark(#272727, #000000) r g b / 95%));
78
+ --_player-button--color--inactive: var(--sc-kit--player-button--color--inactive, rgb(from var(--_player-button--color) r g b / 60%));
79
+ cursor: pointer;
80
+ display: flex;
81
+ flex-direction: column;
82
+ justify-content: center;
83
+ border-radius: 1.25rem;
84
+ background: var(--_player-button--color--inactive);
85
+ padding: 0.4375rem 0.0625rem;
86
+ pointer-events: auto;
87
+ }
88
+ .player-buttons__action {
89
+ --_player-button--icon-scale: 1;
90
+ display: flex;
91
+ justify-content: center;
92
+ align-items: center;
93
+ padding: 0.5625rem;
94
+ border-radius: 1.25rem;
95
+ color: #ffffff;
96
+ --sc-kit--icon--color: #ffffff;
97
+ --sc-kit--icon--size: 1.75rem;
98
+ transition: background-color 0.5s;
99
+ }
100
+ .player-buttons__action:hover:not(:disabled) {
101
+ background-color: var(--_player-button--color);
102
+ }
103
+ .player-buttons__action:disabled {
104
+ opacity: 0.5;
105
+ cursor: default;
106
+ }
107
+ .player-buttons__action--scale-effect:hover:not(:disabled) {
108
+ --_player-button--icon-scale: 1.2;
109
+ background-color: transparent;
110
+ }
111
+ .player-buttons__action-icon {
112
+ display: block;
113
+ transform: scale(var(--_player-button--icon-scale));
114
+ transition: 0.3s;
115
+ }
116
+ .player-buttons {
117
+ /* Set 'container-type: inline-size;' to reference container*/
118
+ }
119
+ @container (width < 576px) {
120
+ .player-buttons {
121
+ border-radius: 0.9375rem;
122
+ padding: 0.25rem 0.0625rem;
123
+ }
124
+ .player-buttons__action {
125
+ --sc-kit--icon--size: 1.5rem;
126
+ }
127
+ }</style>
@@ -0,0 +1,9 @@
1
+ import type { PlayerButtonDef } from './types';
2
+ type Props = {
3
+ actions: PlayerButtonDef[];
4
+ scaleEffect?: boolean;
5
+ zoom?: number;
6
+ };
7
+ declare const Cmp: import("svelte").Component<Props, {}, "">;
8
+ type Cmp = ReturnType<typeof Cmp>;
9
+ export default Cmp;
@@ -0,0 +1,32 @@
1
+ <script lang="ts">import { default as MobilePlayerButtons } from './cmp.mobile-player-buttons.svelte';
2
+ import { default as PlayerButtons } from './cmp.player-buttons.svelte';
3
+ let { scaleEffect = false, actions } = $props();
4
+ </script>
5
+
6
+ <div class="desktop-controls">
7
+ <PlayerButtons actions={actions} scaleEffect={scaleEffect} />
8
+ </div>
9
+
10
+ <div class="mobile-controls">
11
+ <MobilePlayerButtons actions={actions} />
12
+ </div>
13
+
14
+ <style>.desktop-controls {
15
+ display: contents;
16
+ /* Set 'container-type: inline-size;' to reference container*/
17
+ }
18
+ @container (width < 576px) {
19
+ .desktop-controls {
20
+ display: none;
21
+ }
22
+ }
23
+
24
+ .mobile-controls {
25
+ display: none;
26
+ /* Set 'container-type: inline-size;' to reference container*/
27
+ }
28
+ @container (width < 576px) {
29
+ .mobile-controls {
30
+ display: contents;
31
+ }
32
+ }</style>
@@ -0,0 +1,8 @@
1
+ import type { PlayerButtonDef } from './types';
2
+ type Props = {
3
+ actions: PlayerButtonDef[];
4
+ scaleEffect?: boolean;
5
+ };
6
+ declare const Cmp: import("svelte").Component<Props, {}, "">;
7
+ type Cmp = ReturnType<typeof Cmp>;
8
+ export default Cmp;
@@ -0,0 +1,4 @@
1
+ export { default as MobilePlayerButtons } from './cmp.mobile-player-buttons.svelte';
2
+ export { default as PlayerButtons } from './cmp.player-buttons.svelte';
3
+ export { default as ResponsivePlayerButtons } from './cmp.responsive-player-buttons.svelte';
4
+ export type { PlayerButtonDef } from './types';
@@ -0,0 +1,3 @@
1
+ export { default as MobilePlayerButtons } from './cmp.mobile-player-buttons.svelte';
2
+ export { default as PlayerButtons } from './cmp.player-buttons.svelte';
3
+ export { default as ResponsivePlayerButtons } from './cmp.responsive-player-buttons.svelte';
@@ -0,0 +1,7 @@
1
+ import type { IconColor } from '../../icon';
2
+ export type PlayerButtonDef = {
3
+ icon: string;
4
+ iconColor?: IconColor;
5
+ callback: () => void;
6
+ disabled?: boolean;
7
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,3 @@
1
+ export declare class CarouselLocalization {
2
+ get nOfM(): (n: number, m: number) => string;
3
+ }
@@ -0,0 +1,12 @@
1
+ import { getLocale } from '../../../locale';
2
+ export class CarouselLocalization {
3
+ get nOfM() {
4
+ return loc.nOfM[getLocale()];
5
+ }
6
+ }
7
+ const loc = {
8
+ nOfM: {
9
+ en: (n, m) => `${n} of ${m}`,
10
+ no: (n, m) => `${n} av ${m}`
11
+ }
12
+ };
@@ -0,0 +1,418 @@
1
+ <script lang="ts" generics="T">import { Utils, isBrowser } from '../../../core/utils';
2
+ import { Icon } from '../../icon';
3
+ import { TouchSynchronizer } from '../utils';
4
+ import { CarouselLocalization } from './carousel-localization';
5
+ import IconChevronLeft from '@fluentui/svg-icons/icons/chevron_left_20_regular.svg?raw';
6
+ import IconChevronRight from '@fluentui/svg-icons/icons/chevron_right_20_regular.svg?raw';
7
+ import { onDestroy, onMount, untrack } from 'svelte';
8
+ let { items, mode = 'arrows-with-counts', initialIndex, autoSlideMs = 0, on, dot, children } = $props();
9
+ const localization = new CarouselLocalization();
10
+ const itemIndices = $derived(items.map((_, index) => index));
11
+ const animationDuration = 300;
12
+ let previousItems = $state.raw(undefined);
13
+ let selectedIndex = $state(untrack(() => initialIndex));
14
+ let animationIndex = $state(null);
15
+ let interval;
16
+ let slidesRef;
17
+ let sliderWidth = $state(0);
18
+ let swipeTransition = $state(0);
19
+ let resizeObserver;
20
+ onMount(() => {
21
+ notifyIndexChanged();
22
+ setTimeout(() => {
23
+ slidesRef?.classList.toggle('animate', false);
24
+ });
25
+ let touchStartX = 0;
26
+ let touchStartY = 0;
27
+ let touchMoveX = 0;
28
+ let touchStartTime = 0;
29
+ let swipeState = 'not-initialized';
30
+ slidesRef.addEventListener('touchstart', (e) => {
31
+ TouchSynchronizer.touchStarted(slidesRef);
32
+ slidesRef.classList.toggle('animate', false);
33
+ touchStartX = e.changedTouches[0].screenX;
34
+ touchStartY = e.changedTouches[0].screenY;
35
+ touchStartTime = Date.now();
36
+ swipeState = 'not-initialized';
37
+ }, { passive: true });
38
+ slidesRef.addEventListener('touchmove', (e) => {
39
+ if (swipeState === 'horizontal') {
40
+ e.preventDefault();
41
+ e.stopPropagation();
42
+ TouchSynchronizer.touchMovePropagationBlocked(slidesRef);
43
+ }
44
+ else if (swipeState === 'vertical') {
45
+ return;
46
+ }
47
+ const touchCurrentX = e.changedTouches[0].screenX;
48
+ const touchCurrentY = e.changedTouches[0].screenY;
49
+ const diffX = Math.abs(touchCurrentX - touchStartX);
50
+ const diffY = Math.abs(touchCurrentY - touchStartY);
51
+ // Determine direction on first significant movement
52
+ if (diffX > 10 || diffY > 10) {
53
+ if (diffX > diffY) {
54
+ // Horizontal swipe - block vertical scroll
55
+ swipeState = 'horizontal';
56
+ e.preventDefault();
57
+ e.stopPropagation();
58
+ TouchSynchronizer.touchMovePropagationBlocked(slidesRef);
59
+ touchMoveX = touchCurrentX - touchStartX;
60
+ const basePosition = -sliderWidth * (animationIndex === null ? selectedIndex + 1 : animationIndex);
61
+ swipeTransition = basePosition + touchMoveX;
62
+ }
63
+ else {
64
+ // Vertical swipe - allow parent to handle
65
+ swipeState = 'vertical';
66
+ }
67
+ }
68
+ }, { passive: false });
69
+ slidesRef.addEventListener('touchend', (e) => {
70
+ const reset = () => {
71
+ swipeTransition = 0;
72
+ touchMoveX = 0;
73
+ };
74
+ if (swipeState !== 'horizontal') {
75
+ reset();
76
+ return;
77
+ }
78
+ // This is a horizontal swipe - block propagation
79
+ e.stopPropagation();
80
+ e.preventDefault();
81
+ slidesRef.classList.toggle('animate', true);
82
+ TouchSynchronizer.touchEndPropatationBlocked(slidesRef);
83
+ TouchSynchronizer.touchEnded(slidesRef);
84
+ const isFastSwipe = Date.now() - touchStartTime < 300;
85
+ if (!isFastSwipe && Math.abs(touchMoveX) < sliderWidth / 6) {
86
+ reset();
87
+ return;
88
+ }
89
+ if (touchMoveX < 0) {
90
+ loadNext();
91
+ }
92
+ else {
93
+ loadPrevious();
94
+ }
95
+ reset();
96
+ }, { passive: false });
97
+ slidesRef.addEventListener('transitionend', (e) => {
98
+ e.stopPropagation();
99
+ if (animationIndex !== null && animationIndex > items.length - 1) {
100
+ slidesRef.classList.toggle('animate', false);
101
+ animationIndex = null;
102
+ }
103
+ if (animationIndex !== null && animationIndex === 0) {
104
+ slidesRef.classList.toggle('animate', false);
105
+ animationIndex = null;
106
+ }
107
+ setTimeout(() => {
108
+ slidesRef?.classList.toggle('animate', true);
109
+ });
110
+ });
111
+ if (autoSlideMs) {
112
+ interval = window.setInterval(() => {
113
+ if (items?.length <= 1) {
114
+ return;
115
+ }
116
+ loadNext();
117
+ }, autoSlideMs);
118
+ }
119
+ sliderWidth = slidesRef.getBoundingClientRect().width;
120
+ let resizeTimeout = 0;
121
+ resizeObserver = new ResizeObserver(() => {
122
+ if (isBrowser() && resizeTimeout) {
123
+ window.clearTimeout(resizeTimeout);
124
+ }
125
+ slidesRef.classList.toggle('animate', false);
126
+ sliderWidth = slidesRef.getBoundingClientRect().width;
127
+ resizeTimeout = window.setTimeout(() => {
128
+ slidesRef?.classList.toggle('animate', true);
129
+ });
130
+ });
131
+ resizeObserver.observe(slidesRef);
132
+ if (isBrowser()) {
133
+ window.addEventListener(`keydown`, onKeyPress);
134
+ }
135
+ });
136
+ onDestroy(() => {
137
+ if (resizeObserver) {
138
+ resizeObserver.disconnect();
139
+ }
140
+ if (isBrowser()) {
141
+ window.removeEventListener('keydown', onKeyPress);
142
+ if (interval) {
143
+ window.clearInterval(interval);
144
+ }
145
+ }
146
+ });
147
+ const onKeyPress = Utils.throttle((e) => {
148
+ if (e.key === 'ArrowLeft') {
149
+ loadPrevious();
150
+ }
151
+ if (e.key === 'ArrowRight') {
152
+ loadNext();
153
+ }
154
+ }, animationDuration);
155
+ const notifyIndexChanged = () => {
156
+ on?.indexChanged?.(selectedIndex);
157
+ };
158
+ notifyIndexChanged();
159
+ const setIndex = (index) => {
160
+ if (index < 0) {
161
+ animationIndex = 0;
162
+ selectedIndex = items.length - 1;
163
+ }
164
+ else if (index > items.length - 1) {
165
+ animationIndex = index + 1;
166
+ selectedIndex = 0;
167
+ }
168
+ else {
169
+ animationIndex = null;
170
+ selectedIndex = index;
171
+ }
172
+ setTimeout(() => {
173
+ notifyIndexChanged();
174
+ }, 600);
175
+ };
176
+ const loadPrevious = Utils.throttle(() => {
177
+ setIndex(--selectedIndex);
178
+ }, animationDuration);
179
+ const loadNext = Utils.throttle(() => {
180
+ setIndex(++selectedIndex);
181
+ }, animationDuration);
182
+ $effect(() => {
183
+ if (previousItems && items !== previousItems) {
184
+ setIndex(0);
185
+ }
186
+ previousItems = items;
187
+ });
188
+ const slidesTranslateX = $derived(swipeTransition || (items.length > 1 ? -sliderWidth * (animationIndex === null ? selectedIndex + 1 : animationIndex) : 0));
189
+ </script>
190
+
191
+ <div
192
+ class="carousel"
193
+ class:carousel--arrows-and-dots={mode === 'arrows-and-dots'}
194
+ class:carousel--arrows-only={mode === 'arrows-only'}
195
+ class:carousel--dots-only={mode === 'dots-only'}
196
+ class:carousel--dots-only-below={mode === 'dots-only-below'}
197
+ class:carousel--arrows-with-counts={mode === 'arrows-with-counts'}
198
+ style:--carousel--animation="{animationDuration}ms ease-out transform">
199
+ <div class="carousel__slides" bind:this={slidesRef} style:transform="translateX({slidesTranslateX}px)">
200
+ {#if items.length > 1}
201
+ <div class="carousel__slide">
202
+ {#if children}
203
+ {@render children(items[items.length - 1])}
204
+ {/if}
205
+ </div>
206
+ {/if}
207
+ {#each items as item (item)}
208
+ <div class="carousel__slide">
209
+ {#if children}
210
+ {@render children(item)}
211
+ {/if}
212
+ </div>
213
+ {/each}
214
+ {#if items.length > 1}
215
+ <div class="carousel__slide" data-after>
216
+ {#if children}
217
+ {@render children(items[0])}
218
+ {/if}
219
+ </div>
220
+ {/if}
221
+ </div>
222
+
223
+ {#if items.length > 1}
224
+ {#if mode === 'arrows-with-counts'}
225
+ <div class="carousel__arrows-with-counts">
226
+ <button type="button" class="carousel__counts-navigation-button" onclick={loadPrevious} aria-label="none">
227
+ <Icon src={IconChevronLeft} />
228
+ </button>
229
+ <div class="carousel__counts">{localization.nOfM(selectedIndex + 1, items.length)}</div>
230
+ <button type="button" class="carousel__counts-navigation-button" onclick={loadNext} aria-label="none">
231
+ <Icon src={IconChevronRight} />
232
+ </button>
233
+ </div>
234
+ {:else}
235
+ <button type="button" class="carousel__navigation-button carousel__navigation-button--prev" onclick={loadPrevious} aria-label="none">
236
+ <Icon src={IconChevronLeft} />
237
+ </button>
238
+ <button type="button" class="carousel__navigation-button carousel__navigation-button--next" onclick={loadNext} aria-label="none">
239
+ <Icon src={IconChevronRight} />
240
+ </button>
241
+ <div class="carousel__dots">
242
+ {#each itemIndices as index (index)}
243
+ <button type="button" class="carousel__dot" onclick={() => setIndex(index)}>
244
+ {#if dot}
245
+ {@render dot({ active: index === selectedIndex })}
246
+ {:else}
247
+ <span class="carousel__dot-indicator" class:carousel__dot-indicator--active={index === selectedIndex}></span>
248
+ {/if}
249
+ </button>
250
+ {/each}
251
+ </div>
252
+ {/if}
253
+ {/if}
254
+ </div>
255
+
256
+ <style>@charset "UTF-8";
257
+ .carousel {
258
+ /* Public API */
259
+ --_carousel--button-color: var(--sc-kit--carousel--button-color, #9ca3af);
260
+ --_carousel--dot-color: var(--sc-kit--carousel--dot-color, #ffffff);
261
+ --_carousel--dot-size: var(--sc-kit--carousel--dot-size, 0.5rem);
262
+ --_carousel--text-color: var(--sc-kit--carousel--text-color, #ffffff);
263
+ --_carousel--animation: var(--carousel--animation);
264
+ /* Mode control — overridden by modifiers */
265
+ --_carousel--nav-display: block;
266
+ --_carousel--nav-opacity: 0;
267
+ --_carousel--nav-visibility: hidden;
268
+ --_carousel--dots-display: flex;
269
+ --_carousel--dots-position: absolute;
270
+ --_carousel--dots-margin-top: 0;
271
+ display: flex;
272
+ flex-direction: column;
273
+ flex: 1;
274
+ position: relative;
275
+ width: 100%;
276
+ height: 100%;
277
+ overflow: hidden;
278
+ }
279
+ .carousel:hover {
280
+ --_carousel--nav-opacity: 1;
281
+ --_carousel--nav-visibility: visible;
282
+ }
283
+ .carousel--arrows-only {
284
+ --_carousel--dots-display: none;
285
+ }
286
+ .carousel--dots-only {
287
+ --_carousel--nav-display: none;
288
+ }
289
+ .carousel--dots-only-below {
290
+ --_carousel--nav-display: none;
291
+ --_carousel--dots-position: static;
292
+ --_carousel--dots-margin-top: 1rem;
293
+ }
294
+ .carousel__slides {
295
+ display: flex;
296
+ height: 100%;
297
+ }
298
+ .carousel__slides:global(.animate) {
299
+ transition: var(--_carousel--animation);
300
+ }
301
+ .carousel__slide {
302
+ display: flex;
303
+ justify-content: center;
304
+ align-items: center;
305
+ position: relative;
306
+ height: 100%;
307
+ width: 100%;
308
+ min-width: 100%;
309
+ max-width: 100%;
310
+ }
311
+ .carousel__dots {
312
+ margin: 0;
313
+ display: var(--_carousel--dots-display);
314
+ justify-content: center;
315
+ z-index: 1;
316
+ gap: 0.5rem;
317
+ flex-wrap: wrap;
318
+ position: var(--_carousel--dots-position);
319
+ margin-top: var(--_carousel--dots-margin-top);
320
+ left: 0;
321
+ right: 0;
322
+ bottom: 0.625rem;
323
+ }
324
+ .carousel__dot {
325
+ cursor: pointer;
326
+ padding: 0;
327
+ background: transparent;
328
+ border: none;
329
+ }
330
+ .carousel__dot-indicator {
331
+ display: block;
332
+ width: var(--_carousel--dot-size);
333
+ height: var(--_carousel--dot-size);
334
+ border-radius: 50%;
335
+ border: 1px solid var(--_carousel--dot-color);
336
+ background: transparent;
337
+ transition: background-color 0.2s;
338
+ }
339
+ .carousel__dot-indicator--active {
340
+ background: var(--_carousel--dot-color);
341
+ }
342
+ .carousel__navigation-button {
343
+ display: var(--_carousel--nav-display);
344
+ font-size: 0.75rem;
345
+ width: 1.875rem;
346
+ height: 1.875rem;
347
+ background-color: var(--_carousel--button-color);
348
+ border-radius: 50%;
349
+ text-align: center;
350
+ color: var(--_carousel--text-color);
351
+ position: absolute;
352
+ top: 50%;
353
+ transform: translateY(-50%);
354
+ opacity: var(--_carousel--nav-opacity);
355
+ visibility: var(--_carousel--nav-visibility);
356
+ transition: opacity 0.2s, visibility 0.2s;
357
+ /* Set 'container-type: inline-size;' to reference container*/
358
+ }
359
+ @container (width < 576px) {
360
+ .carousel__navigation-button {
361
+ opacity: 1;
362
+ visibility: visible;
363
+ }
364
+ }
365
+ .carousel__navigation-button:disabled {
366
+ opacity: 0.5;
367
+ cursor: default;
368
+ }
369
+ .carousel__navigation-button--prev {
370
+ left: 0.9375rem;
371
+ }
372
+ .carousel__navigation-button--next {
373
+ right: 0.9375rem;
374
+ }
375
+ .carousel__arrows-with-counts {
376
+ position: absolute;
377
+ bottom: 0.75rem;
378
+ left: 50%;
379
+ transform: translateX(-50%);
380
+ display: flex;
381
+ justify-content: center;
382
+ align-items: center;
383
+ height: 1.625rem;
384
+ z-index: 1;
385
+ }
386
+ .carousel__counts {
387
+ font-size: 0.9375rem;
388
+ font-weight: 300;
389
+ width: 5.625rem;
390
+ text-align: center;
391
+ color: var(--_carousel--text-color);
392
+ text-shadow: 0 1px 0 rgba(0, 0, 0, 0.1), 0 1px 3px rgba(0, 0, 0, 0.1), 0 1px 6px rgba(0, 0, 0, 0.05);
393
+ }
394
+ .carousel__counts-navigation-button {
395
+ font-size: 0.75rem;
396
+ width: 1.625rem;
397
+ height: 1.625rem;
398
+ background-color: var(--_carousel--button-color);
399
+ border-radius: 50%;
400
+ text-align: center;
401
+ color: var(--_carousel--text-color);
402
+ opacity: var(--_carousel--nav-opacity);
403
+ visibility: var(--_carousel--nav-visibility);
404
+ transition: opacity 0.2s, visibility 0.2s;
405
+ }
406
+ .carousel__counts-navigation-button:disabled {
407
+ opacity: 0.5;
408
+ cursor: default;
409
+ }
410
+ .carousel__counts-navigation-button {
411
+ /* Set 'container-type: inline-size;' to reference container*/
412
+ }
413
+ @container (width < 576px) {
414
+ .carousel__counts-navigation-button {
415
+ opacity: 1;
416
+ visibility: visible;
417
+ }
418
+ }</style>