diff --git a/blocks/carousel/carousel.css b/blocks/carousel/carousel.css new file mode 100644 index 0000000..af5faed --- /dev/null +++ b/blocks/carousel/carousel.css @@ -0,0 +1,227 @@ +.section.carousel-container { + padding: 0; +} + +.carousel .carousel-slides-container { + position: relative; +} + +.carousel .carousel-slides, +.carousel .carousel-slide-indicators { + list-style: none; + margin: 0; + padding: 0; +} + +.carousel .carousel-slides { + display: flex; + scroll-behavior: smooth; + scroll-snap-type: x mandatory; + overflow: scroll clip; +} + +.carousel .carousel-slides::-webkit-scrollbar { + display: none; +} + +.carousel .carousel-slide { + flex: 0 0 100%; + scroll-snap-align: start; + display: flex; + flex-direction: column; + align-items: flex-start; + justify-content: center; + position: relative; + width: 100%; + min-height: 1015px; +} + +.carousel .carousel-slide:has(.carousel-slide-content[data-align="center"]) { + align-items: center; +} + +.carousel .carousel-slide:has(.carousel-slide-content[data-align="right"]) { + align-items: flex-end; +} + +.carousel .carousel-slide .carousel-slide-image picture { + position: absolute; + inset: 0; +} + +.carousel .carousel-slide .carousel-slide-image picture > img { + height: 100%; + width: 100%; + object-fit: cover; +} + +.carousel .carousel-slide .carousel-slide-content { + z-index: 1; + padding: 1rem; + margin: 1.5rem 3rem; + color: white; + background-color: none; + position: relative; + width: var(--slide-content-width, auto); +} + +.carousel .carousel-slide .carousel-slide-content .button { + display: flex; + max-width: 260px; + align-items: center; + max-height: 60px; + position: relative; + border: 0; + height: 6rem; + width: 100%; + font-family: Calibre, Helvetica, Tahoma, Arial, sans-serif; + cursor: pointer; + appearance: none; + border-radius: .4rem; + transition: all .2s ease-in-out; + background: #80ba27; + color: #fff; + letter-spacing: -.02rem; + box-shadow: 0 .2rem .4rem 0 rgba(0 0 0 / 5%); + font-size: 18px; + font-weight: normal; +} + +.carousel .carousel-slide-image::after { + width: 100vw; + height: 100vh; + display: block; + position: absolute; + top:0; + left: 0; + background-color: rgba(0 0 0 /30%); + content: " "; +} + +.carousel .carousel-slide-indicators { + display: flex; + justify-content: center; + gap: 0.5rem; +} + +.carousel .carousel-slide-indicator button { + width: 1rem; + height: 1rem; + padding: 0; + border-radius: 1rem; + background-color: rgba(0 0 0 / 25%); +} + +.carousel .carousel-slide-indicator button:disabled, +.carousel .carousel-slide-indicator button:hover, +.carousel .carousel-slide-indicator button:focus-visible { + background-color: rgba(0 0 0 / 80%); +} + +.carousel .carousel-slide-indicator span, +.carousel .carousel-navigation-buttons span { + border: 0; + clip: rect(0 0 0 0); + clip-path: inset(50%); + height: 1px; + margin: -1px; + overflow: hidden; + padding: 0; + position: absolute; + width: 1px; + white-space: nowrap; +} + +.carousel .carousel-navigation-buttons { + position: absolute; + top: 50%; + transform: translateY(-50%); + left: 0.5rem; + right: 0.5rem; + display: flex; + align-items: center; + justify-content: flex-end; + gap: 20px; + margin-right: 5rem; + z-index: 1; +} + +/* stylelint-disable-next-line no-descending-specificity */ +.carousel .carousel-navigation-buttons button { + border-radius: 8px; + margin: 0; + padding: 0; + width: 2rem; + height: 2rem; + position: relative; + background-color: transparent; +} + +.carousel .carousel-navigation-buttons button:hover, +.carousel .carousel-navigation-buttons button:focus-visible { + background-color: transparent; +} + +.carousel .carousel-navigation-buttons button::after { + display: block; + content: ""; + border: 3px white solid; + border-bottom: 0; + border-left: 0; + height: 0.75rem; + width: 0.75rem; + position: absolute; + top: 50%; + left: calc(50% + 3px); + transform: translate(-50%, -50%) rotate(-135deg); +} + +.carousel .carousel-navigation-buttons button.slide-next::after { + transform: translate(-50%, -50%) rotate(45deg); + left: calc(50% - 3px); +} + +@media (width >= 600px) { + .carousel .carousel-navigation-buttons { + left: 1rem; + right: 1rem; + } + + .carousel .carousel-navigation-buttons button { + width: 3rem; + height: 3rem; + } + + .carousel .carousel-navigation-buttons button::after { + width: 1rem; + height: 1rem; + } + + .carousel .carousel-slide .carousel-slide-content { + --slide-content-width: 50%; + + margin: 2.5rem 5rem; + } + + .carousel .carousel-slide .carousel-slide-content h3 { + color: #fff; + } + + .carousel .carousel-slide .carousel-slide-content[data-align="justify"] { + --slide-content-width: auto; + } +} + + +.carousel-container .carousel-wrapper { + max-width: 100%; + height: calc(100vh - 64px); + min-height: calc(100vh - 64px); +} + +@media (width >= 900px) { + .carousel-container .carousel-wrapper { + max-width: 100%; + min-height: 1015px; + } +} diff --git a/blocks/carousel/carousel.js b/blocks/carousel/carousel.js new file mode 100644 index 0000000..2b1ae16 --- /dev/null +++ b/blocks/carousel/carousel.js @@ -0,0 +1,150 @@ +import { fetchPlaceholders } from '../../scripts/aem.js'; + +function updateActiveSlide(slide) { + const block = slide.closest('.carousel'); + const slideIndex = parseInt(slide.dataset.slideIndex, 10); + block.dataset.activeSlide = slideIndex; + + const slides = block.querySelectorAll('.carousel-slide'); + + slides.forEach((aSlide, idx) => { + aSlide.setAttribute('aria-hidden', idx !== slideIndex); + aSlide.querySelectorAll('a').forEach((link) => { + if (idx !== slideIndex) { + link.setAttribute('tabindex', '-1'); + } else { + link.removeAttribute('tabindex'); + } + }); + }); + + const indicators = block.querySelectorAll('.carousel-slide-indicator'); + indicators.forEach((indicator, idx) => { + if (idx !== slideIndex) { + indicator.querySelector('button').removeAttribute('disabled'); + } else { + indicator.querySelector('button').setAttribute('disabled', 'true'); + } + }); +} + +function showSlide(block, slideIndex = 0) { + const slides = block.querySelectorAll('.carousel-slide'); + let realSlideIndex = slideIndex < 0 ? slides.length - 1 : slideIndex; + if (slideIndex >= slides.length) realSlideIndex = 0; + const activeSlide = slides[realSlideIndex]; + + activeSlide.querySelectorAll('a').forEach((link) => link.removeAttribute('tabindex')); + block.querySelector('.carousel-slides').scrollTo({ + top: 0, + left: activeSlide.offsetLeft, + behavior: 'smooth', + }); +} + +function bindEvents(block) { + const slideIndicators = block.querySelector('.carousel-slide-indicators'); + if (!slideIndicators) return; + + slideIndicators.querySelectorAll('button').forEach((button) => { + button.addEventListener('click', (e) => { + const slideIndicator = e.currentTarget.parentElement; + showSlide(block, parseInt(slideIndicator.dataset.targetSlide, 10)); + }); + }); + + block.querySelector('.slide-prev').addEventListener('click', () => { + showSlide(block, parseInt(block.dataset.activeSlide, 10) - 1); + }); + block.querySelector('.slide-next').addEventListener('click', () => { + showSlide(block, parseInt(block.dataset.activeSlide, 10) + 1); + }); + + const slideObserver = new IntersectionObserver((entries) => { + entries.forEach((entry) => { + if (entry.isIntersecting) updateActiveSlide(entry.target); + }); + }, { threshold: 0.5 }); + block.querySelectorAll('.carousel-slide').forEach((slide) => { + slideObserver.observe(slide); + }); +} + +function createSlide(row, slideIndex, carouselId) { + const slide = document.createElement('li'); + slide.dataset.slideIndex = slideIndex; + slide.setAttribute('id', `carousel-${carouselId}-slide-${slideIndex}`); + slide.classList.add('carousel-slide'); + + row.querySelectorAll(':scope > div').forEach((column, colIdx) => { + column.classList.add(`carousel-slide-${colIdx === 0 ? 'image' : 'content'}`); + slide.append(column); + }); + + const labeledBy = slide.querySelector('h1, h2, h3, h4, h5, h6'); + if (labeledBy) { + slide.setAttribute('aria-labelledby', labeledBy.getAttribute('id')); + } + + return slide; +} + +let carouselId = 0; +export default async function decorate(block) { + carouselId += 1; + block.setAttribute('id', `carousel-${carouselId}`); + const rows = block.querySelectorAll(':scope > div'); + const isSingleSlide = rows.length < 2; + + const placeholders = await fetchPlaceholders(); + + block.setAttribute('role', 'region'); + block.setAttribute('aria-roledescription', placeholders.carousel || 'Carousel'); + + const container = document.createElement('div'); + container.classList.add('carousel-slides-container'); + + const slidesWrapper = document.createElement('ul'); + slidesWrapper.classList.add('carousel-slides'); + block.prepend(slidesWrapper); + + let slideIndicators; + if (!isSingleSlide) { + const slideIndicatorsNav = document.createElement('nav'); + slideIndicatorsNav.setAttribute('aria-label', placeholders.carouselSlideControls || 'Carousel Slide Controls'); + slideIndicators = document.createElement('ol'); + slideIndicators.classList.add('carousel-slide-indicators'); + slideIndicatorsNav.append(slideIndicators); + block.append(slideIndicatorsNav); + + const slideNavButtons = document.createElement('div'); + slideNavButtons.classList.add('carousel-navigation-buttons'); + slideNavButtons.innerHTML = ` + + + `; + + container.append(slideNavButtons); + } + + rows.forEach((row, idx) => { + const slide = createSlide(row, idx, carouselId); + slidesWrapper.append(slide); + + if (slideIndicators) { + const indicator = document.createElement('li'); + indicator.classList.add('carousel-slide-indicator'); + indicator.dataset.targetSlide = idx; + indicator.innerHTML = ``; + slideIndicators.append(indicator); + } + row.remove(); + }); + + container.append(slidesWrapper); + block.prepend(container); + + if (!isSingleSlide) { + bindEvents(block); + } +} diff --git a/blocks/logo/logo.css b/blocks/logo/logo.css new file mode 100644 index 0000000..a363948 --- /dev/null +++ b/blocks/logo/logo.css @@ -0,0 +1,3 @@ +.logo { + display: block; +} \ No newline at end of file diff --git a/favicon.ico b/favicon.ico index 96ab42c..2ef03c7 100644 Binary files a/favicon.ico and b/favicon.ico differ diff --git a/icons/arrow-right.svg b/icons/arrow-right.svg new file mode 100644 index 0000000..970c1c7 --- /dev/null +++ b/icons/arrow-right.svg @@ -0,0 +1,13 @@ + + + ic / Arrow-medium 2 + + + + + + + + + + \ No newline at end of file diff --git a/styles/styles.css b/styles/styles.css index b4f62ee..509d4dd 100644 --- a/styles/styles.css +++ b/styles/styles.css @@ -13,7 +13,7 @@ :root { /* colors */ --link-color: #80ba27; - --link-hover-color: #80ba27; + --link-hover-color: #000; --background-color: white; --light-color: #eee; --dark-color: #ccc; @@ -167,7 +167,7 @@ a.button:hover, a.button:focus, button:hover, button:focus { - background-color: var(--link-hover-color); + background-color: var(--link-color); cursor: pointer; }