@codeforamerica/marcomms-design-system 1.14.0 → 1.15.0

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/dist/index.js CHANGED
@@ -2075,7 +2075,7 @@
2075
2075
 
2076
2076
  .slide {
2077
2077
  background: var(--background);
2078
- box-shadow: var(--shadow-small);
2078
+ filter: drop-shadow(0 2px 4px rgba(0, 0, 0, 0.1));
2079
2079
  color: inherit;
2080
2080
  display: flex;
2081
2081
  flex-direction: column;
@@ -2084,13 +2084,13 @@
2084
2084
  margin-inline: auto;
2085
2085
  max-width: var(--width);
2086
2086
  text-decoration: none;
2087
- transition: box-shadow 0.2s ease-in-out;
2087
+ transition: filter 0.2s ease-in-out;
2088
2088
  width: 100%;
2089
2089
  }
2090
2090
 
2091
2091
  .slide:hover,
2092
2092
  .slide:focus {
2093
- box-shadow: var(--shadow-medium);
2093
+ filter: drop-shadow(0 4px 8px rgba(0, 0, 0, 0.15));
2094
2094
  }
2095
2095
 
2096
2096
  .image {
@@ -2518,7 +2518,7 @@
2518
2518
  min-width: calc(var(--carousel-dot-size) * 0.8);
2519
2519
  }
2520
2520
  }
2521
- `])();constructor(){super(),this.currentPage=0,this.totalSlides=0,this.progressPercentage=0,this.isPaused=!0,this.prefersReducedMotion=window.matchMedia("(prefers-reduced-motion: reduce)").matches,this.itemsPerView=Ft.ITEMS_PER_VIEW_DESKTOP,this.autoPlay=!0,this.autoPlayDuration=Math.max(100,Ft.AUTOPLAY_DURATION),this.autoPlayTimer=null,this.autoPlayTimeout=null,this.touchStartX=null,this.touchEndX=null,this._initialized=!1,this._resizeTimerId=null,this._mouseEnterListener=null,this._mouseLeaveListener=null}connectedCallback(){super.connectedCallback(),this.style.setProperty("--items-per-view",this.itemsPerView),this.addEventListener("keydown",t=>this._handleKeydown(t)),this.addEventListener("touchstart",t=>this._handleTouchStart(t),!1),this.addEventListener("touchend",t=>this._handleTouchEnd(t),!1),this._resizeListener=()=>{this._resizeTimerId&&clearTimeout(this._resizeTimerId),this._resizeTimerId=setTimeout(()=>{this.updateItemsPerView(),this.updateCarouselHeight()},150)},window.addEventListener("resize",this._resizeListener)}disconnectedCallback(){super.disconnectedCallback(),this.stopAutoPlay(),this._resizeListener&&window.removeEventListener("resize",this._resizeListener),this._resizeTimerId&&clearTimeout(this._resizeTimerId);const t=this.shadowRoot?.querySelector(".carousel-slides");t&&this._mouseEnterListener&&this._mouseLeaveListener&&(t.removeEventListener("mouseenter",this._mouseEnterListener),t.removeEventListener("mouseleave",this._mouseLeaveListener))}updated(t){t.has("itemsPerView")&&this.style.setProperty("--items-per-view",this.itemsPerView),t.has("currentPage")&&this.updateItemVisibility()}firstUpdated(){const t=this.shadowRoot.querySelector("slot"),e=this.shadowRoot.querySelector(".carousel-slides");if(t&&!this._initialized){this._initialized=!0;const e=t.assignedElements();this.totalSlides=e.length,this.updateItemsPerView(),this.updateItemVisibility(),this.startAutoPlay(),this._waitForImages(e,5e3).then(()=>{this.updateCarouselHeight()})}e&&!this._mouseEnterListener&&(this._mouseEnterListener=()=>{this.stopAutoPlay()},this._mouseLeaveListener=()=>{this.isPaused||this._scheduleNextPage()},e.addEventListener("mouseenter",this._mouseEnterListener),e.addEventListener("mouseleave",this._mouseLeaveListener))}_waitForImages(t){let e=arguments.length>1&&void 0!==arguments[1]?arguments[1]:3e3;return Promise.race([Promise.all(Array.from(t).map(t=>Promise.all(Array.from(t.querySelectorAll("img")).map(t=>t.complete?Promise.resolve():new Promise(e=>{t.addEventListener("load",e,{once:!0}),t.addEventListener("error",e,{once:!0})}))))),new Promise((t,i)=>setTimeout(()=>i(new Error("Image load timeout")),e))]).catch(()=>Promise.resolve())}get totalPages(){return Math.ceil(this.totalSlides/this.itemsPerView)}updateItemsPerView(){const t=window.innerWidth>=Ft.DESKTOP_BREAKPOINT?Ft.ITEMS_PER_VIEW_DESKTOP:Ft.ITEMS_PER_VIEW_MOBILE;t!==this.itemsPerView&&(this.itemsPerView=t,this.style.setProperty("--items-per-view",this.itemsPerView),this.currentPage>=this.totalPages&&(this.currentPage=0),this.requestUpdate())}updateItemVisibility(){const t=this.shadowRoot.querySelector("slot");if(!t)return;const e=t.assignedElements();if(0===e.length)return;const i=this.currentPage*this.itemsPerView,o=i+this.itemsPerView;e.forEach((t,e)=>{e>=i&&e<o?t.setAttribute("data-slide-active",""):t.removeAttribute("data-slide-active")}),this.updateCarouselHeight()}updateCarouselHeight(){const t=this.shadowRoot.querySelector("slot"),e=this.shadowRoot.querySelector(".carousel-slides");if(!t||!e)return;const i=t.assignedElements();if(0===i.length)return;const o=this.currentPage*this.itemsPerView,a=o+this.itemsPerView;let s=0;for(let t=o;t<a&&t<i.length;t++){const e=i[t];s=Math.max(s,e.offsetHeight)}requestAnimationFrame(()=>{s>0&&(e.style.height=s+Ft.SHADOW_SPACE+"px")})}startAutoPlay(){this.autoPlay&&!this.prefersReducedMotion&&0!==this.totalSlides?(this.isPaused=!1,this.progressPercentage=0,this._scheduleNextPage()):this.isPaused=!0}play(){this.prefersReducedMotion||this.startAutoPlay()}pause(){this.isPaused=!0,this.stopAutoPlay()}stopAutoPlay(){this.autoPlayTimer&&(clearInterval(this.autoPlayTimer),this.autoPlayTimer=null),this.autoPlayTimeout&&(clearTimeout(this.autoPlayTimeout),this.autoPlayTimeout=null)}_scheduleNextPage(){this.stopAutoPlay();const t=100/(this.autoPlayDuration/Ft.PROGRESS_UPDATE_INTERVAL);this.autoPlayTimer=setInterval(()=>{this.progressPercentage=Math.min(100,this.progressPercentage+t)},Ft.PROGRESS_UPDATE_INTERVAL),this.autoPlayTimeout=setTimeout(()=>{this.isPaused||(this.nextPage(),this.startAutoPlay())},this.autoPlayDuration)}nextPage(){this.currentPage=(this.currentPage+1)%this.totalPages,this.progressPercentage=0,this.stopAutoPlay(),this.requestUpdate(),this.play()}previousPage(){this.currentPage=(this.currentPage-1+this.totalPages)%this.totalPages,this.progressPercentage=0,this.stopAutoPlay(),this.requestUpdate(),this.play()}goToPage(t){t>=0&&t<this.totalPages&&t!==this.currentPage&&(this.currentPage=t,this.progressPercentage=0,this.stopAutoPlay(),this.requestUpdate(),this.play())}_handleKeydown(t){"ArrowRight"===t.key?(t.preventDefault(),this.nextPage(),this.pause()):"ArrowLeft"===t.key&&(t.preventDefault(),this.previousPage(),this.pause())}_handleTouchStart(t){t.changedTouches&&t.changedTouches.length>0&&(this.touchStartX=t.changedTouches[0].screenX)}_handleTouchEnd(t){t.changedTouches&&t.changedTouches.length>0&&(this.touchEndX=t.changedTouches[0].screenX,this._handleSwipe())}_handleSwipe(){if(null===this.touchStartX||null===this.touchEndX)return;const t=this.touchStartX-this.touchEndX;Math.abs(t)>Ft.SWIPE_THRESHOLD&&(t>0?(this.nextPage(),this.pause()):(this.previousPage(),this.pause()))}render(){return D`
2521
+ `])();constructor(){super(),this.currentPage=0,this.totalSlides=0,this.progressPercentage=0,this.isPaused=!0,this.prefersReducedMotion=window.matchMedia("(prefers-reduced-motion: reduce)").matches,this.itemsPerView=Ft.ITEMS_PER_VIEW_DESKTOP,this.autoPlay=!0,this.autoPlayDuration=Math.max(100,Ft.AUTOPLAY_DURATION),this.autoPlayTimer=null,this.autoPlayTimeout=null,this.touchStartX=null,this.touchEndX=null,this._initialized=!1,this._resizeTimerId=null,this._mouseEnterListener=null,this._mouseLeaveListener=null}connectedCallback(){super.connectedCallback();const t=this.getAttribute("items-per-view");if(null!==t){const e=parseInt(t,10);isNaN(e)||(this.itemsPerView=e)}this.style.setProperty("--items-per-view",this.itemsPerView),this.addEventListener("keydown",t=>this._handleKeydown(t)),this.addEventListener("touchstart",t=>this._handleTouchStart(t),!1),this.addEventListener("touchend",t=>this._handleTouchEnd(t),!1),this._resizeListener=()=>{this._resizeTimerId&&clearTimeout(this._resizeTimerId),this._resizeTimerId=setTimeout(()=>{this.updateItemsPerView(),this.updateCarouselHeight()},150)},window.addEventListener("resize",this._resizeListener)}disconnectedCallback(){super.disconnectedCallback(),this.stopAutoPlay(),this._resizeListener&&window.removeEventListener("resize",this._resizeListener),this._resizeTimerId&&clearTimeout(this._resizeTimerId);const t=this.shadowRoot?.querySelector(".carousel-slides");t&&this._mouseEnterListener&&this._mouseLeaveListener&&(t.removeEventListener("mouseenter",this._mouseEnterListener),t.removeEventListener("mouseleave",this._mouseLeaveListener))}updated(t){t.has("itemsPerView")&&this.style.setProperty("--items-per-view",this.itemsPerView),t.has("currentPage")&&this.updateItemVisibility()}firstUpdated(){const t=this.shadowRoot.querySelector("slot"),e=this.shadowRoot.querySelector(".carousel-slides");t&&!this._slotChangeSetup&&(this._slotChangeSetup=!0,t.addEventListener("slotchange",()=>{this._slotChangeTimeout&&clearTimeout(this._slotChangeTimeout),this._slotChangeTimeout=setTimeout(()=>{this._initialized||this._initializeCarousel()},50)})),e&&!this._mouseEnterListener&&(this._mouseEnterListener=()=>{this.stopAutoPlay()},this._mouseLeaveListener=()=>{this.isPaused||this._scheduleNextPage()},e.addEventListener("mouseenter",this._mouseEnterListener),e.addEventListener("mouseleave",this._mouseLeaveListener))}_initializeCarousel(){if(this._initialized)return;const t=this.shadowRoot.querySelector("slot");if(!t)return;const e=t.assignedElements();0!==e.length&&(this._initialized=!0,this.totalSlides=e.length,this.updateItemsPerView(),this.updateItemVisibility(),this.startAutoPlay(),this._waitForImages(e,5e3).then(()=>{this.updateCarouselHeight()}))}_waitForImages(t){let e=arguments.length>1&&void 0!==arguments[1]?arguments[1]:3e3;return Promise.race([Promise.all(Array.from(t).map(t=>Promise.all(Array.from(t.querySelectorAll("img")).map(t=>t.complete?Promise.resolve():new Promise(e=>{t.addEventListener("load",e,{once:!0}),t.addEventListener("error",e,{once:!0})}))))),new Promise((t,i)=>setTimeout(()=>i(new Error("Image load timeout")),e))]).catch(()=>Promise.resolve())}get totalPages(){return Math.ceil(this.totalSlides/this.itemsPerView)}updateItemsPerView(){if(this.hasAttribute("items-per-view"))return;const t=window.innerWidth>=Ft.DESKTOP_BREAKPOINT?Ft.ITEMS_PER_VIEW_DESKTOP:Ft.ITEMS_PER_VIEW_MOBILE;t!==this.itemsPerView&&(this.itemsPerView=t,this.style.setProperty("--items-per-view",this.itemsPerView),this.currentPage>=this.totalPages&&(this.currentPage=0),this.requestUpdate())}updateItemVisibility(){const t=this.shadowRoot.querySelector("slot");if(!t)return;const e=t.assignedElements();if(0===e.length)return;const i=this.currentPage*this.itemsPerView,o=i+this.itemsPerView;e.forEach((t,e)=>{e>=i&&e<o?t.setAttribute("data-slide-active",""):t.removeAttribute("data-slide-active")}),this.updateCarouselHeight()}updateCarouselHeight(){const t=this.shadowRoot.querySelector("slot"),e=this.shadowRoot.querySelector(".carousel-slides");if(!t||!e)return;const i=t.assignedElements();if(0===i.length)return;const o=this.currentPage*this.itemsPerView,a=o+this.itemsPerView;requestAnimationFrame(()=>{requestAnimationFrame(()=>{setTimeout(()=>{let t=0;for(let e=o;e<a&&e<i.length;e++){const o=i[e].offsetHeight;t=Math.max(t,o)}const s=Math.max(t,Ft.SHADOW_SPACE);e.style.height=s+"px"},0)})})}startAutoPlay(){this.autoPlay&&!this.prefersReducedMotion&&0!==this.totalSlides?(this.isPaused=!1,this.progressPercentage=0,this._scheduleNextPage()):this.isPaused=!0}play(){this.prefersReducedMotion||this.startAutoPlay()}pause(){this.isPaused=!0,this.stopAutoPlay()}stopAutoPlay(){this.autoPlayTimer&&(clearInterval(this.autoPlayTimer),this.autoPlayTimer=null),this.autoPlayTimeout&&(clearTimeout(this.autoPlayTimeout),this.autoPlayTimeout=null)}_scheduleNextPage(){this.stopAutoPlay();const t=100/(this.autoPlayDuration/Ft.PROGRESS_UPDATE_INTERVAL);this.autoPlayTimer=setInterval(()=>{this.progressPercentage=Math.min(100,this.progressPercentage+t)},Ft.PROGRESS_UPDATE_INTERVAL),this.autoPlayTimeout=setTimeout(()=>{this.isPaused||(this.nextPage(),this.startAutoPlay())},this.autoPlayDuration)}nextPage(){this.currentPage=(this.currentPage+1)%this.totalPages,this.progressPercentage=0,this.stopAutoPlay(),this.requestUpdate(),this.play()}previousPage(){this.currentPage=(this.currentPage-1+this.totalPages)%this.totalPages,this.progressPercentage=0,this.stopAutoPlay(),this.requestUpdate(),this.play()}goToPage(t){t>=0&&t<this.totalPages&&t!==this.currentPage&&(this.currentPage=t,this.progressPercentage=0,this.stopAutoPlay(),this.requestUpdate(),this.play())}_handleKeydown(t){"ArrowRight"===t.key?(t.preventDefault(),this.nextPage(),this.pause()):"ArrowLeft"===t.key&&(t.preventDefault(),this.previousPage(),this.pause())}_handleTouchStart(t){t.changedTouches&&t.changedTouches.length>0&&(this.touchStartX=t.changedTouches[0].screenX)}_handleTouchEnd(t){t.changedTouches&&t.changedTouches.length>0&&(this.touchEndX=t.changedTouches[0].screenX,this._handleSwipe())}_handleSwipe(){if(null===this.touchStartX||null===this.touchEndX)return;const t=this.touchStartX-this.touchEndX;Math.abs(t)>Ft.SWIPE_THRESHOLD&&(t>0?(this.nextPage(),this.pause()):(this.previousPage(),this.pause()))}render(){return D`
2522
2522
  <div
2523
2523
  class="carousel-container"
2524
2524
  role="region"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@codeforamerica/marcomms-design-system",
3
- "version": "1.14.0",
3
+ "version": "1.15.0",
4
4
  "main": "src/index.js",
5
5
  "dependencies": {
6
6
  "lit": "^3.2.1",
@@ -2,6 +2,25 @@ import { LitElement, html, css } from "lit";
2
2
  import { commonStyles } from "../shared/common";
3
3
  import "./icon";
4
4
 
5
+ /**
6
+ * Carousel Component - Pagination-based content carousel with fade transitions
7
+ *
8
+ * Supports HTML attributes (kebab-case):
9
+ * - items-per-view: Number of items to show per page (default: 3 on desktop, 1 on mobile)
10
+ * - auto-play: Enable/disable auto-play (default: true)
11
+ * - auto-play-duration: Duration in milliseconds for auto-play (default: 10000)
12
+ *
13
+ * Example usage:
14
+ * <cfa-carousel items-per-view="3" auto-play="true" auto-play-duration="5000">
15
+ * <cfa-slide>...</cfa-slide>
16
+ * <cfa-slide>...</cfa-slide>
17
+ * </cfa-carousel>
18
+ *
19
+ * CSS Custom Properties (for styling):
20
+ * - --carousel-fade-duration: Fade transition duration (default: 300ms)
21
+ * - --carousel-dot-color: Pagination dot color (default: var(--black-20))
22
+ * - --carousel-dot-active-color: Active dot progress bar color (default: var(--purple-60))
23
+ */
5
24
  class Carousel extends LitElement {
6
25
  // Configuration constants
7
26
  static SWIPE_THRESHOLD = 50;
@@ -206,6 +225,15 @@ class Carousel extends LitElement {
206
225
  connectedCallback() {
207
226
  super.connectedCallback();
208
227
 
228
+ // Parse items-per-view attribute before setting CSS property
229
+ const attrValue = this.getAttribute('items-per-view');
230
+ if (attrValue !== null) {
231
+ const parsedValue = parseInt(attrValue, 10);
232
+ if (!isNaN(parsedValue)) {
233
+ this.itemsPerView = parsedValue;
234
+ }
235
+ }
236
+
209
237
  this.style.setProperty("--items-per-view", this.itemsPerView);
210
238
 
211
239
  this.addEventListener("keydown", (e) => this._handleKeydown(e));
@@ -255,17 +283,19 @@ class Carousel extends LitElement {
255
283
  const slot = this.shadowRoot.querySelector("slot");
256
284
  const slidesContainer = this.shadowRoot.querySelector(".carousel-slides");
257
285
 
258
- if (slot && !this._initialized) {
259
- this._initialized = true;
260
- const items = slot.assignedElements();
261
- this.totalSlides = items.length;
262
- this.updateItemsPerView();
263
- this.updateItemVisibility();
264
- this.startAutoPlay();
265
-
266
- // Recalculate height after images load with timeout guard
267
- this._waitForImages(items, 5000).then(() => {
268
- this.updateCarouselHeight();
286
+ // Set up the slotchange listener which will actually initialize when elements are assigned
287
+ if (slot && !this._slotChangeSetup) {
288
+ this._slotChangeSetup = true;
289
+ slot.addEventListener('slotchange', () => {
290
+ // Debounce initialization - wait for DOM to stabilize
291
+ if (this._slotChangeTimeout) {
292
+ clearTimeout(this._slotChangeTimeout);
293
+ }
294
+ this._slotChangeTimeout = setTimeout(() => {
295
+ if (!this._initialized) {
296
+ this._initializeCarousel();
297
+ }
298
+ }, 50);
269
299
  });
270
300
  }
271
301
 
@@ -285,6 +315,34 @@ class Carousel extends LitElement {
285
315
  }
286
316
  }
287
317
 
318
+ _initializeCarousel() {
319
+ if (this._initialized) {
320
+ return;
321
+ }
322
+
323
+ const slot = this.shadowRoot.querySelector("slot");
324
+ if (!slot) {
325
+ return;
326
+ }
327
+
328
+ const items = slot.assignedElements();
329
+
330
+ if (items.length === 0) {
331
+ return;
332
+ }
333
+
334
+ this._initialized = true;
335
+ this.totalSlides = items.length;
336
+ this.updateItemsPerView();
337
+ this.updateItemVisibility();
338
+ this.startAutoPlay();
339
+
340
+ // Recalculate height after images load with timeout guard
341
+ this._waitForImages(items, 5000).then(() => {
342
+ this.updateCarouselHeight();
343
+ });
344
+ }
345
+
288
346
  _waitForImages(items, timeout = 3000) {
289
347
  return Promise.race([
290
348
  Promise.all(
@@ -315,6 +373,11 @@ class Carousel extends LitElement {
315
373
  }
316
374
 
317
375
  updateItemsPerView() {
376
+ // If itemsPerView was explicitly set via attribute, don't override it with responsive defaults
377
+ if (this.hasAttribute('items-per-view')) {
378
+ return;
379
+ }
380
+
318
381
  const isDesktop = window.innerWidth >= Carousel.DESKTOP_BREAKPOINT;
319
382
  const newItemsPerView = isDesktop
320
383
  ? Carousel.ITEMS_PER_VIEW_DESKTOP
@@ -333,14 +396,19 @@ class Carousel extends LitElement {
333
396
 
334
397
  updateItemVisibility() {
335
398
  const slot = this.shadowRoot.querySelector("slot");
336
- if (!slot) return;
399
+ if (!slot) {
400
+ return;
401
+ }
337
402
 
338
403
  const items = slot.assignedElements();
339
- if (items.length === 0) return;
404
+
405
+ if (items.length === 0) {
406
+ return;
407
+ }
340
408
 
341
409
  const startIdx = this.currentPage * this.itemsPerView;
342
410
  const endIdx = startIdx + this.itemsPerView;
343
-
411
+
344
412
  items.forEach((item, idx) => {
345
413
  if (idx >= startIdx && idx < endIdx) {
346
414
  item.setAttribute("data-slide-active", "");
@@ -357,27 +425,37 @@ class Carousel extends LitElement {
357
425
  const slot = this.shadowRoot.querySelector("slot");
358
426
  const slidesContainer = this.shadowRoot.querySelector(".carousel-slides");
359
427
 
360
- if (!slot || !slidesContainer) return;
428
+ if (!slot || !slidesContainer) {
429
+ return;
430
+ }
361
431
 
362
432
  const items = slot.assignedElements();
363
- if (items.length === 0) return;
433
+ if (items.length === 0) {
434
+ return;
435
+ }
364
436
 
365
437
  const startIdx = this.currentPage * this.itemsPerView;
366
438
  const endIdx = startIdx + this.itemsPerView;
367
439
 
368
- // Get max height of current page items
369
- let maxHeight = 0;
370
- for (let i = startIdx; i < endIdx && i < items.length; i++) {
371
- const item = items[i];
372
- maxHeight = Math.max(maxHeight, item.offsetHeight);
373
- }
374
-
375
- // Use requestAnimationFrame to ensure layout is ready
376
- // Add extra space for box-shadows to display without clipping
440
+ // Use multiple animation frames and a small delay to ensure layout is complete
441
+ // after CSS changes have been applied to show/hide slides
377
442
  requestAnimationFrame(() => {
378
- if (maxHeight > 0) {
379
- slidesContainer.style.height = (maxHeight + Carousel.SHADOW_SPACE) + "px";
380
- }
443
+ requestAnimationFrame(() => {
444
+ // Small timeout to ensure layout has been computed
445
+ setTimeout(() => {
446
+ let maxHeight = 0;
447
+
448
+ for (let i = startIdx; i < endIdx && i < items.length; i++) {
449
+ const item = items[i];
450
+ const height = item.offsetHeight;
451
+ maxHeight = Math.max(maxHeight, height);
452
+ }
453
+
454
+ // Set height (minimum SHADOW_SPACE to accommodate buttons/controls)
455
+ const totalHeight = Math.max(maxHeight, Carousel.SHADOW_SPACE);
456
+ slidesContainer.style.height = totalHeight + "px";
457
+ }, 0);
458
+ });
381
459
  });
382
460
  }
383
461
 
@@ -18,7 +18,7 @@ class Slide extends LitElement {
18
18
 
19
19
  .slide {
20
20
  background: var(--background);
21
- box-shadow: var(--shadow-small);
21
+ filter: drop-shadow(0 2px 4px rgba(0, 0, 0, 0.1));
22
22
  color: inherit;
23
23
  display: flex;
24
24
  flex-direction: column;
@@ -27,13 +27,13 @@ class Slide extends LitElement {
27
27
  margin-inline: auto;
28
28
  max-width: var(--width);
29
29
  text-decoration: none;
30
- transition: box-shadow 0.2s ease-in-out;
30
+ transition: filter 0.2s ease-in-out;
31
31
  width: 100%;
32
32
  }
33
33
 
34
34
  .slide:hover,
35
35
  .slide:focus {
36
- box-shadow: var(--shadow-medium);
36
+ filter: drop-shadow(0 4px 8px rgba(0, 0, 0, 0.15));
37
37
  }
38
38
 
39
39
  .image {