@hyvor/design 2.1.15 → 2.1.17
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.
|
@@ -9,7 +9,8 @@
|
|
|
9
9
|
|
|
10
10
|
let { label, content, children }: Props = $props();
|
|
11
11
|
|
|
12
|
-
const
|
|
12
|
+
const uid = $props.id();
|
|
13
|
+
const labelId = `detail-card-${uid}`;
|
|
13
14
|
</script>
|
|
14
15
|
|
|
15
16
|
<div class="detail-card" role="region" aria-labelledby={labelId}>
|
|
@@ -126,11 +126,7 @@
|
|
|
126
126
|
</Modal>
|
|
127
127
|
</div>
|
|
128
128
|
|
|
129
|
-
<style>.image-uploader :global(.
|
|
130
|
-
z-index: 1000 !important;
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
.image-uploader :global(.inner) {
|
|
129
|
+
<style>.image-uploader :global(.inner) {
|
|
134
130
|
height: 100%;
|
|
135
131
|
width: 1100px !important;
|
|
136
132
|
display: flex;
|
|
@@ -38,36 +38,14 @@
|
|
|
38
38
|
handwrittenNames = true
|
|
39
39
|
}: Props = $props();
|
|
40
40
|
|
|
41
|
-
function identicon(seed: string) {
|
|
42
|
-
let hash = 0;
|
|
43
|
-
for (let i = 0; i < seed.length; i++) {
|
|
44
|
-
hash = (hash * 31 + seed.charCodeAt(i)) | 0;
|
|
45
|
-
}
|
|
46
|
-
const hue = Math.abs(hash) % 360;
|
|
47
|
-
const cells: { x: number; y: number }[] = [];
|
|
48
|
-
for (let col = 0; col < 3; col++) {
|
|
49
|
-
for (let row = 0; row < 5; row++) {
|
|
50
|
-
const bit = (hash >> (col * 5 + row)) & 1;
|
|
51
|
-
if (!bit) continue;
|
|
52
|
-
cells.push({ x: col, y: row });
|
|
53
|
-
if (col < 2) cells.push({ x: 4 - col, y: row });
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
return {
|
|
57
|
-
bg: `hsl(${hue} 45% 92%)`,
|
|
58
|
-
fg: `hsl(${hue} 50% 40%)`,
|
|
59
|
-
cells
|
|
60
|
-
};
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
let scrollEl: HTMLDivElement | undefined = $state();
|
|
64
|
-
let dragging = $state(false);
|
|
65
|
-
let dragStartX = 0;
|
|
66
|
-
let dragStartScroll = 0;
|
|
67
|
-
|
|
68
41
|
let started: boolean[] = $state(reviews.map(() => false));
|
|
69
42
|
let playing: boolean[] = $state(reviews.map(() => false));
|
|
70
43
|
let videoEls: (HTMLVideoElement | undefined)[] = $state([]);
|
|
44
|
+
let currentTime: number[] = $state(reviews.map(() => 0));
|
|
45
|
+
let duration: number[] = $state(reviews.map(() => 0));
|
|
46
|
+
|
|
47
|
+
let rowEl: HTMLDivElement | undefined = $state();
|
|
48
|
+
let activeIndex = $state(0);
|
|
71
49
|
|
|
72
50
|
function play(i: number) {
|
|
73
51
|
started[i] = true;
|
|
@@ -83,23 +61,74 @@
|
|
|
83
61
|
playing[i] = false;
|
|
84
62
|
}
|
|
85
63
|
|
|
86
|
-
function
|
|
87
|
-
if (
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
64
|
+
function onCardClick(e: MouseEvent, i: number) {
|
|
65
|
+
if ((e.target as HTMLElement).closest('a')) return;
|
|
66
|
+
if (!started[i]) {
|
|
67
|
+
play(i);
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
const video = videoEls[i];
|
|
71
|
+
if (!video) return;
|
|
72
|
+
if (playing[i]) {
|
|
73
|
+
video.pause();
|
|
74
|
+
} else {
|
|
75
|
+
video.play().catch(() => {});
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function onCardKeydown(e: KeyboardEvent, i: number) {
|
|
80
|
+
if (e.key !== 'Enter' && e.key !== ' ') return;
|
|
81
|
+
e.preventDefault();
|
|
82
|
+
onCardClick(e as unknown as MouseEvent, i);
|
|
94
83
|
}
|
|
95
84
|
|
|
96
|
-
function
|
|
97
|
-
|
|
98
|
-
|
|
85
|
+
function seekFromEvent(e: MouseEvent, i: number) {
|
|
86
|
+
const video = videoEls[i];
|
|
87
|
+
if (!video || !duration[i]) return;
|
|
88
|
+
const track = e.currentTarget as HTMLElement;
|
|
89
|
+
const rect = track.getBoundingClientRect();
|
|
90
|
+
const ratio = Math.min(1, Math.max(0, (e.clientX - rect.left) / rect.width));
|
|
91
|
+
video.currentTime = ratio * duration[i];
|
|
99
92
|
}
|
|
100
93
|
|
|
101
|
-
function
|
|
102
|
-
|
|
94
|
+
function onSeekClick(e: MouseEvent, i: number) {
|
|
95
|
+
e.stopPropagation();
|
|
96
|
+
seekFromEvent(e, i);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function onSeekKeydown(e: KeyboardEvent, i: number) {
|
|
100
|
+
const video = videoEls[i];
|
|
101
|
+
if (!video) return;
|
|
102
|
+
if (e.key === 'ArrowRight') {
|
|
103
|
+
e.preventDefault();
|
|
104
|
+
video.currentTime = Math.min(duration[i], video.currentTime + 5);
|
|
105
|
+
} else if (e.key === 'ArrowLeft') {
|
|
106
|
+
e.preventDefault();
|
|
107
|
+
video.currentTime = Math.max(0, video.currentTime - 5);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function goTo(i: number) {
|
|
112
|
+
if (!rowEl) return;
|
|
113
|
+
const card = rowEl.children[i] as HTMLElement | undefined;
|
|
114
|
+
card?.scrollIntoView({ behavior: 'smooth', inline: 'center', block: 'nearest' });
|
|
115
|
+
activeIndex = i;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function onRowScroll() {
|
|
119
|
+
if (!rowEl) return;
|
|
120
|
+
const rowCenter = rowEl.getBoundingClientRect().left + rowEl.clientWidth / 2;
|
|
121
|
+
let closest = 0;
|
|
122
|
+
let closestDist = Infinity;
|
|
123
|
+
Array.from(rowEl.children).forEach((child, i) => {
|
|
124
|
+
const rect = (child as HTMLElement).getBoundingClientRect();
|
|
125
|
+
const dist = Math.abs(rect.left + rect.width / 2 - rowCenter);
|
|
126
|
+
if (dist < closestDist) {
|
|
127
|
+
closestDist = dist;
|
|
128
|
+
closest = i;
|
|
129
|
+
}
|
|
130
|
+
});
|
|
131
|
+
activeIndex = closest;
|
|
103
132
|
}
|
|
104
133
|
</script>
|
|
105
134
|
|
|
@@ -133,110 +162,140 @@
|
|
|
133
162
|
<h2>{@html title}</h2>
|
|
134
163
|
</div>
|
|
135
164
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
{:else}
|
|
155
|
-
<svg
|
|
156
|
-
class="avatar card-avatar"
|
|
157
|
-
viewBox="0 0 5 5"
|
|
158
|
-
style="background: {av.bg}"
|
|
159
|
-
aria-hidden="true"
|
|
160
|
-
>
|
|
161
|
-
{#each av.cells as cell}
|
|
162
|
-
<rect x={cell.x} y={cell.y} width="1" height="1" fill={av.fg} />
|
|
163
|
-
{/each}
|
|
165
|
+
<div class="hds-container-max cards-wrap">
|
|
166
|
+
<div
|
|
167
|
+
class="cards-row"
|
|
168
|
+
bind:this={rowEl}
|
|
169
|
+
onscroll={onRowScroll}
|
|
170
|
+
role="region"
|
|
171
|
+
aria-label={label}
|
|
172
|
+
>
|
|
173
|
+
{#each reviews as review, i}
|
|
174
|
+
{#if review.type === 'text'}
|
|
175
|
+
<figure class="card text-card hds-box">
|
|
176
|
+
{#if review.imageUrl}
|
|
177
|
+
<img class="avatar photo card-avatar" src={review.imageUrl} alt={review.name} />
|
|
178
|
+
{/if}
|
|
179
|
+
<svg class="quote-mark" viewBox="0 0 24 24" aria-hidden="true">
|
|
180
|
+
<path
|
|
181
|
+
d="M10 7c-3.3 0-6 2.7-6 6v4h6v-6H7c0-1.7 1.3-3 3-3V7zm10 0c-3.3 0-6 2.7-6 6v4h6v-6h-3c0-1.7 1.3-3 3-3V7z"
|
|
182
|
+
/>
|
|
164
183
|
</svg>
|
|
165
|
-
{/if}
|
|
166
|
-
<svg class="quote-mark" viewBox="0 0 24 24" aria-hidden="true">
|
|
167
|
-
<path
|
|
168
|
-
d="M10 7c-3.3 0-6 2.7-6 6v4h6v-6H7c0-1.7 1.3-3 3-3V7zm10 0c-3.3 0-6 2.7-6 6v4h6v-6h-3c0-1.7 1.3-3 3-3V7z"
|
|
169
|
-
/>
|
|
170
|
-
</svg>
|
|
171
|
-
{#if review.summary}
|
|
172
|
-
<p class="text-summary">{review.summary}</p>
|
|
173
|
-
{/if}
|
|
174
|
-
<blockquote>“{review.quote}”</blockquote>
|
|
175
|
-
<figcaption>
|
|
176
|
-
<span class="caption-text">
|
|
177
|
-
<span class="name">{review.name}</span>
|
|
178
|
-
{@render meta(review)}
|
|
179
|
-
</span>
|
|
180
|
-
</figcaption>
|
|
181
|
-
</figure>
|
|
182
|
-
{:else}
|
|
183
|
-
<figure class="card video-card hds-box" class:playing={playing[i]}>
|
|
184
|
-
{#if started[i]}
|
|
185
|
-
<!-- svelte-ignore a11y_media_has_caption -->
|
|
186
|
-
<video
|
|
187
|
-
class="video"
|
|
188
|
-
src={review.videoUrl}
|
|
189
|
-
poster={review.posterUrl}
|
|
190
|
-
controls={playing[i]}
|
|
191
|
-
playsinline
|
|
192
|
-
bind:this={videoEls[i]}
|
|
193
|
-
use:autoplayVideo
|
|
194
|
-
onplay={() => (playing[i] = true)}
|
|
195
|
-
onpause={(e) => onVideoPause(e, i)}
|
|
196
|
-
onended={() => (playing[i] = false)}
|
|
197
|
-
></video>
|
|
198
|
-
{:else}
|
|
199
|
-
<div class="poster">
|
|
200
|
-
<img src={review.posterUrl} alt="" />
|
|
201
|
-
</div>
|
|
202
|
-
{/if}
|
|
203
|
-
|
|
204
|
-
{#if !playing[i]}
|
|
205
|
-
<div class="poster-scrim"></div>
|
|
206
|
-
|
|
207
184
|
{#if review.summary}
|
|
208
|
-
<p class="
|
|
185
|
+
<p class="text-summary">{review.summary}</p>
|
|
209
186
|
{/if}
|
|
210
|
-
|
|
211
|
-
<button
|
|
212
|
-
class="play-btn"
|
|
213
|
-
aria-label={started[i] ? 'Resume video testimonial' : 'Play video testimonial'}
|
|
214
|
-
onclick={() => play(i)}
|
|
215
|
-
>
|
|
216
|
-
<svg
|
|
217
|
-
width="20"
|
|
218
|
-
height="20"
|
|
219
|
-
viewBox="0 0 16 16"
|
|
220
|
-
fill="currentColor"
|
|
221
|
-
aria-hidden="true"
|
|
222
|
-
>
|
|
223
|
-
<path d="M5 3.5v9l8-4.5-8-4.5z" />
|
|
224
|
-
</svg>
|
|
225
|
-
</button>
|
|
226
|
-
|
|
187
|
+
<blockquote>“{review.quote}”</blockquote>
|
|
227
188
|
<figcaption>
|
|
228
|
-
{#if review.imageUrl}
|
|
229
|
-
<img class="avatar photo" src={review.imageUrl} alt={review.name} />
|
|
230
|
-
{/if}
|
|
231
189
|
<span class="caption-text">
|
|
232
190
|
<span class="name">{review.name}</span>
|
|
233
191
|
{@render meta(review)}
|
|
234
192
|
</span>
|
|
235
193
|
</figcaption>
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
194
|
+
</figure>
|
|
195
|
+
{:else}
|
|
196
|
+
<!-- svelte-ignore a11y_no_noninteractive_element_to_interactive_role -->
|
|
197
|
+
<figure
|
|
198
|
+
class="card video-card hds-box"
|
|
199
|
+
class:playing={playing[i]}
|
|
200
|
+
role="button"
|
|
201
|
+
tabindex="0"
|
|
202
|
+
aria-label={playing[i]
|
|
203
|
+
? 'Pause video testimonial'
|
|
204
|
+
: started[i]
|
|
205
|
+
? 'Resume video testimonial'
|
|
206
|
+
: 'Play video testimonial'}
|
|
207
|
+
onclick={(e) => onCardClick(e, i)}
|
|
208
|
+
onkeydown={(e) => onCardKeydown(e, i)}
|
|
209
|
+
>
|
|
210
|
+
{#if started[i]}
|
|
211
|
+
<!-- svelte-ignore a11y_media_has_caption -->
|
|
212
|
+
<video
|
|
213
|
+
class="video"
|
|
214
|
+
src={review.videoUrl}
|
|
215
|
+
poster={review.posterUrl}
|
|
216
|
+
playsinline
|
|
217
|
+
bind:this={videoEls[i]}
|
|
218
|
+
bind:currentTime={currentTime[i]}
|
|
219
|
+
bind:duration={duration[i]}
|
|
220
|
+
use:autoplayVideo
|
|
221
|
+
onplay={() => (playing[i] = true)}
|
|
222
|
+
onpause={(e) => onVideoPause(e, i)}
|
|
223
|
+
onended={() => (playing[i] = false)}
|
|
224
|
+
></video>
|
|
225
|
+
{:else}
|
|
226
|
+
<div class="poster">
|
|
227
|
+
<img src={review.posterUrl} alt="" />
|
|
228
|
+
</div>
|
|
229
|
+
{/if}
|
|
230
|
+
|
|
231
|
+
{#if started[i]}
|
|
232
|
+
<!-- svelte-ignore a11y_no_noninteractive_element_interactions -->
|
|
233
|
+
<div
|
|
234
|
+
class="progress-track"
|
|
235
|
+
role="slider"
|
|
236
|
+
aria-label="Seek video"
|
|
237
|
+
aria-valuemin={0}
|
|
238
|
+
aria-valuemax={100}
|
|
239
|
+
aria-valuenow={duration[i] ? Math.round((currentTime[i] / duration[i]) * 100) : 0}
|
|
240
|
+
tabindex="0"
|
|
241
|
+
onclick={(e) => onSeekClick(e, i)}
|
|
242
|
+
onkeydown={(e) => onSeekKeydown(e, i)}
|
|
243
|
+
>
|
|
244
|
+
<div
|
|
245
|
+
class="progress-fill"
|
|
246
|
+
style="width: {duration[i] ? (currentTime[i] / duration[i]) * 100 : 0}%"
|
|
247
|
+
></div>
|
|
248
|
+
</div>
|
|
249
|
+
{/if}
|
|
250
|
+
|
|
251
|
+
{#if !playing[i]}
|
|
252
|
+
<div class="poster-scrim"></div>
|
|
253
|
+
|
|
254
|
+
{#if review.summary}
|
|
255
|
+
<p class="video-summary">“{review.summary}”</p>
|
|
256
|
+
{/if}
|
|
257
|
+
|
|
258
|
+
<div class="play-btn" aria-hidden="true">
|
|
259
|
+
<svg
|
|
260
|
+
width="20"
|
|
261
|
+
height="20"
|
|
262
|
+
viewBox="0 0 16 16"
|
|
263
|
+
fill="currentColor"
|
|
264
|
+
aria-hidden="true"
|
|
265
|
+
>
|
|
266
|
+
<path d="M5 3.5v9l8-4.5-8-4.5z" />
|
|
267
|
+
</svg>
|
|
268
|
+
</div>
|
|
269
|
+
|
|
270
|
+
<figcaption>
|
|
271
|
+
{#if review.imageUrl}
|
|
272
|
+
<img class="avatar photo" src={review.imageUrl} alt={review.name} />
|
|
273
|
+
{/if}
|
|
274
|
+
<span class="caption-text">
|
|
275
|
+
<span class="name">{review.name}</span>
|
|
276
|
+
{@render meta(review)}
|
|
277
|
+
</span>
|
|
278
|
+
</figcaption>
|
|
279
|
+
{/if}
|
|
280
|
+
</figure>
|
|
281
|
+
{/if}
|
|
282
|
+
{/each}
|
|
283
|
+
</div>
|
|
284
|
+
|
|
285
|
+
{#if reviews.length > 1}
|
|
286
|
+
<div class="dots" role="tablist" aria-label="Testimonials navigation">
|
|
287
|
+
{#each reviews as _, i}
|
|
288
|
+
<button
|
|
289
|
+
class="dot"
|
|
290
|
+
class:active={i === activeIndex}
|
|
291
|
+
role="tab"
|
|
292
|
+
aria-selected={i === activeIndex}
|
|
293
|
+
aria-label={`Go to testimonial ${i + 1}`}
|
|
294
|
+
onclick={() => goTo(i)}
|
|
295
|
+
></button>
|
|
296
|
+
{/each}
|
|
297
|
+
</div>
|
|
298
|
+
{/if}
|
|
240
299
|
</div>
|
|
241
300
|
</section>
|
|
242
301
|
|
|
@@ -245,6 +304,7 @@
|
|
|
245
304
|
background: var(--accent-lightest);
|
|
246
305
|
padding: 100px 0 96px;
|
|
247
306
|
overflow: hidden;
|
|
307
|
+
scroll-margin-top: calc(var(--header-height, 55px) + 20px);
|
|
248
308
|
}
|
|
249
309
|
|
|
250
310
|
.head {
|
|
@@ -270,31 +330,17 @@
|
|
|
270
330
|
font-family: var(--font-serif);
|
|
271
331
|
}
|
|
272
332
|
|
|
273
|
-
.
|
|
274
|
-
|
|
333
|
+
.cards-wrap {
|
|
334
|
+
padding: 32px 15px 4px;
|
|
335
|
+
box-sizing: border-box;
|
|
336
|
+
}
|
|
275
337
|
|
|
338
|
+
.cards-row {
|
|
276
339
|
display: flex;
|
|
340
|
+
flex-wrap: wrap;
|
|
277
341
|
align-items: stretch;
|
|
342
|
+
justify-content: center;
|
|
278
343
|
gap: 20px;
|
|
279
|
-
overflow-x: auto;
|
|
280
|
-
overflow-y: visible;
|
|
281
|
-
scroll-behavior: smooth;
|
|
282
|
-
cursor: grab;
|
|
283
|
-
padding: 32px var(--side-padding) 36px;
|
|
284
|
-
scrollbar-width: none;
|
|
285
|
-
-webkit-overflow-scrolling: touch;
|
|
286
|
-
touch-action: pan-y;
|
|
287
|
-
user-select: none;
|
|
288
|
-
-webkit-user-select: none;
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
.scroll-row.dragging {
|
|
292
|
-
cursor: grabbing;
|
|
293
|
-
scroll-behavior: auto;
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
.scroll-row::-webkit-scrollbar {
|
|
297
|
-
display: none;
|
|
298
344
|
}
|
|
299
345
|
|
|
300
346
|
.card {
|
|
@@ -310,6 +356,8 @@
|
|
|
310
356
|
}
|
|
311
357
|
|
|
312
358
|
.text-card {
|
|
359
|
+
height: auto;
|
|
360
|
+
min-height: 440px;
|
|
313
361
|
padding: 28px;
|
|
314
362
|
display: flex;
|
|
315
363
|
flex-direction: column;
|
|
@@ -417,15 +465,27 @@
|
|
|
417
465
|
}
|
|
418
466
|
|
|
419
467
|
.video-card {
|
|
420
|
-
width: 280px;
|
|
421
468
|
overflow: hidden;
|
|
422
469
|
padding: 0;
|
|
470
|
+
cursor: pointer;
|
|
471
|
+
transition:
|
|
472
|
+
width 0.35s cubic-bezier(0.16, 1, 0.3, 1),
|
|
473
|
+
height 0.35s cubic-bezier(0.16, 1, 0.3, 1),
|
|
474
|
+
transform 0.4s cubic-bezier(0.16, 1, 0.3, 1),
|
|
475
|
+
box-shadow 0.4s cubic-bezier(0.16, 1, 0.3, 1);
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
.video-card:focus-visible {
|
|
479
|
+
outline: 2px solid var(--accent);
|
|
480
|
+
outline-offset: 2px;
|
|
423
481
|
}
|
|
424
482
|
|
|
425
483
|
.video-card.playing {
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
484
|
+
z-index: 5;
|
|
485
|
+
transform: scale(1.08);
|
|
486
|
+
box-shadow:
|
|
487
|
+
0 30px 60px -15px rgba(20, 14, 12, 0.4),
|
|
488
|
+
0 12px 24px -12px rgba(20, 14, 12, 0.3);
|
|
429
489
|
}
|
|
430
490
|
|
|
431
491
|
.poster {
|
|
@@ -500,6 +560,32 @@
|
|
|
500
560
|
background: #000;
|
|
501
561
|
}
|
|
502
562
|
|
|
563
|
+
.progress-track {
|
|
564
|
+
position: absolute;
|
|
565
|
+
left: 0;
|
|
566
|
+
right: 0;
|
|
567
|
+
bottom: 0;
|
|
568
|
+
z-index: 4;
|
|
569
|
+
height: 4px;
|
|
570
|
+
background: rgba(255, 255, 255, 0.25);
|
|
571
|
+
cursor: pointer;
|
|
572
|
+
transition: height 0.15s;
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
.progress-track:hover,
|
|
576
|
+
.progress-track:focus-visible {
|
|
577
|
+
height: 7px;
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
.progress-track:focus-visible {
|
|
581
|
+
outline: none;
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
.progress-fill {
|
|
585
|
+
height: 100%;
|
|
586
|
+
background: #fff;
|
|
587
|
+
}
|
|
588
|
+
|
|
503
589
|
.video-card figcaption {
|
|
504
590
|
position: absolute;
|
|
505
591
|
left: 0;
|
|
@@ -528,11 +614,26 @@
|
|
|
528
614
|
color: rgba(255, 255, 255, 0.9);
|
|
529
615
|
}
|
|
530
616
|
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
617
|
+
.dots {
|
|
618
|
+
display: none;
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
.dot {
|
|
622
|
+
width: 8px;
|
|
623
|
+
height: 8px;
|
|
624
|
+
padding: 0;
|
|
625
|
+
border: none;
|
|
626
|
+
border-radius: 50%;
|
|
627
|
+
background: var(--border);
|
|
628
|
+
cursor: pointer;
|
|
629
|
+
transition:
|
|
630
|
+
background 0.2s,
|
|
631
|
+
transform 0.2s;
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
.dot.active {
|
|
635
|
+
background: var(--accent);
|
|
636
|
+
transform: scale(1.3);
|
|
536
637
|
}
|
|
537
638
|
|
|
538
639
|
@media (max-width: 768px) {
|
|
@@ -546,13 +647,44 @@
|
|
|
546
647
|
height: 400px;
|
|
547
648
|
}
|
|
548
649
|
|
|
549
|
-
.
|
|
550
|
-
|
|
650
|
+
.text-card {
|
|
651
|
+
height: auto;
|
|
652
|
+
min-height: 400px;
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
@media (max-width: 640px) {
|
|
657
|
+
.cards-wrap {
|
|
658
|
+
padding: 24px 0 4px;
|
|
551
659
|
}
|
|
552
660
|
|
|
553
|
-
.
|
|
554
|
-
|
|
555
|
-
|
|
661
|
+
.cards-row {
|
|
662
|
+
flex-wrap: nowrap;
|
|
663
|
+
justify-content: flex-start;
|
|
664
|
+
overflow-x: auto;
|
|
665
|
+
scroll-snap-type: x mandatory;
|
|
666
|
+
-webkit-overflow-scrolling: touch;
|
|
667
|
+
scrollbar-width: none;
|
|
668
|
+
padding: 0 24px;
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
.cards-row::-webkit-scrollbar {
|
|
672
|
+
display: none;
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
.card {
|
|
676
|
+
flex: 0 0 calc(100% - 32px);
|
|
677
|
+
width: calc(100% - 32px);
|
|
678
|
+
max-width: 360px;
|
|
679
|
+
scroll-snap-align: center;
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
.dots {
|
|
683
|
+
display: flex;
|
|
684
|
+
justify-content: center;
|
|
685
|
+
align-items: center;
|
|
686
|
+
gap: 8px;
|
|
687
|
+
margin-top: 20px;
|
|
556
688
|
}
|
|
557
689
|
}
|
|
558
690
|
</style>
|