diff --git a/blocks/v2-truck-features/v2-truck-features.css b/blocks/v2-truck-features/v2-truck-features.css new file mode 100644 index 000000000..b3803d315 --- /dev/null +++ b/blocks/v2-truck-features/v2-truck-features.css @@ -0,0 +1,240 @@ +.section.v2-truck-features-container > .v2-truck-features-wrapper { + padding: 0; +} + +.v2-truck-features { + padding: 0; + color: var(--text-color); +} + +.v2-truck-features__content { + width: 100%; + height: calc(100vh - var(--nav-height) - var(--inpage-navigation-height)); + position: sticky; + top: calc(var(--nav-height) + var(--inpage-navigation-height)); + display: flex; + flex-direction: column; + align-items: flex-start; + justify-content: space-between; +} + +@supports (height: 1svh) { + .v2-truck-features__content { + height: calc(100svh - var(--nav-height) - var(--inpage-navigation-height)); + } +} + +.v2-truck-features__heading { + font-family: var(--ff-subheadings-medium); + font-size: 32px; + line-height: var(--headline-3-line-height); + margin: 0; + padding: 40px 16px; +} + +.v2-truck-features__slides { + margin: 0; + list-style: none; + padding: 0; + flex-grow: 1; +} + +.v2-truck-features ul ul:first-of-type { + display: none; +} + +.v2-truck-features .v2-truck-features__images-list { + margin: 0; + padding: 0; +} + +.v2-truck-features .v2-truck-features__images-list li { + display: none; + width: auto; +} + +.v2-truck-features .v2-truck-features__slide { + display: none; + flex-direction: column; + gap: 8px; + height: 100%; + justify-content: space-evenly; +} + +.v2-truck-features .v2-truck-features__slide.v2-truck-features__slide--active { + display: flex; + flex-direction: column-reverse; +} + +.v2-truck-features .v2-truck-features__slide h4 { + font-family: var(--ff-subheadings-medium); + line-height: var(--headline-5-line-height); + font-size: 18px; + letter-spacing: 0.36px; + margin: 0; +} + +.v2-truck-features .v2-truck-features__slide h4::before { + width: 2px; + height: 27px; + content: ''; + display: block; + position: absolute; + background: var(--c-primary-white); + left: 16px; +} + +.v2-truck-features .v2-truck-features__slide p { + margin: 0; +} + +.v2-truck-features .v2-truck-features__slide--active h4::before { + background: var(--c-accent-red); +} + +.v2-truck-features + .v2-truck-features__images-list + li.v2-truck-features__slide-image--active { + display: flex; + justify-content: center; + position: relative; +} + +.v2-truck-features__slide-image--active::after { + content: ''; + display: block; + height: 100%; + position: absolute; + background: radial-gradient( + 52.65% 52.65% at 50% 56%, + #fff0 31.16%, + #dfdfdf 89.49% + ); + width: 298px; + max-width: 800px; +} + +.v2-truck-features__slide-image--active picture, +.v2-truck-features__slide-image--active img { + display: flex; + height: 275px; + aspect-ratio: 800/740; +} + +.v2-truck-features__text-wrapper { + position: relative; + align-self: flex-start; + padding: 24px 16px 24px 40px; +} + +.v2-truck-features__text-wrapper a:any-link { + color: currentcolor; + text-decoration: underline; + text-decoration-color: var(--link-color); +} + +@media (min-width: 744px) { + .v2-truck-features .v2-truck-features__heading { + width: 368px; + padding: 100px 0 0 32px; + } + + .v2-truck-features .v2-truck-features__slides { + margin: 22px 0; + width: 368px; + flex-grow: 0; + } + + .v2-truck-features .v2-truck-features__slide { + padding: 12px 0 12px 48px; + display: flex; + height: auto; + } + + .v2-truck-features .v2-truck-features__slide h4::before { + height: calc(100% + 24px); + left: -16px; + top: -12px; + } + + .v2-truck-features .v2-truck-features__text-wrapper { + padding: 0; + } + + .v2-truck-features .v2-truck-features__text-wrapper p { + display: none; + } + + .v2-truck-features .v2-truck-features__slide--active p { + display: block; + } + + .v2-truck-features .v2-truck-features__images-list { + position: absolute; + right: 0; + top: 50%; + transform: translateY(-50%); + } + + .v2-truck-features__slide-image--active::after { + width: 51vw; + } + + .v2-truck-features__slide-image--active picture, + .v2-truck-features__slide-image--active img { + width: 50vw; + max-width: 800px; + height: auto; + } + + .v2-truck-features__content { + justify-content: flex-start; + } + + .v2-truck-features.v2-truck-features--image-left .v2-truck-features__content { + align-items: flex-end; + } + + .v2-truck-features.v2-truck-features--image-left + .v2-truck-features__images-list { + left: 0; + right: unset; + } +} + +@media (min-width: 1200px) { + .v2-truck-features { + --image-margin: calc((min(1440px, 100vw) - 1040px) / -2); + } + + .v2-truck-features .v2-truck-features__slide { + padding-left: 36px; + } + + .v2-truck-features__slide-image--active::after { + margin-right: var(--image-margin); + } + + .v2-truck-features .v2-truck-features__slide-image--active picture { + margin-right: var(--image-margin); + } + + .v2-truck-features.v2-truck-features--image-left picture { + margin-left: var(--image-margin); + margin-right: unset; + } + + .v2-truck-features--image-left + .v2-truck-features__slide-image--active::after { + margin-left: var(--image-margin); + margin-right: unset; + } +} + +@media (min-width: 1440px) { + .v2-truck-features__slide-image--active picture, + .v2-truck-features__slide-image--active img, + .v2-truck-features__slide-image--active::after { + min-width: 800px; + } +} diff --git a/blocks/v2-truck-features/v2-truck-features.js b/blocks/v2-truck-features/v2-truck-features.js new file mode 100644 index 000000000..d01836924 --- /dev/null +++ b/blocks/v2-truck-features/v2-truck-features.js @@ -0,0 +1,184 @@ +import { + createElement, unwrapDivs, variantsClassesToBEM, +} from '../../scripts/common.js'; +import { getAllElWithChildren } from '../../scripts/scripts.js'; + +const blockName = 'v2-truck-features'; +const desktopMQ = window.matchMedia('(min-width: 1200px)'); +const DESKTOP_SCROLL_PADDING = 200; +const MOBILE_SCROLL_PADDING = 400; +let slideScrollPaddingInPx = desktopMQ.matches ? DESKTOP_SCROLL_PADDING : MOBILE_SCROLL_PADDING; + +desktopMQ.addEventListener('change', (event) => { + slideScrollPaddingInPx = event.matches ? DESKTOP_SCROLL_PADDING : MOBILE_SCROLL_PADDING; +}); + +const selectImagesList = (slide) => { + const imagesLists = [...getAllElWithChildren(slide.querySelectorAll('ul'), ':scope > li > picture')]; + + if (imagesLists === 0) { + return; + } + + imagesLists.forEach((el) => { + el.style.display = 'none'; + }); + + const selectedImagesListIndex = desktopMQ.matches ? '-1' : '0'; + + imagesLists.at(selectedImagesListIndex).style.display = 'block'; + imagesLists.at(selectedImagesListIndex).classList.add(`${blockName}__images-list`); +}; + +const setContentWrapperHeight = (wrapper, slidesCount) => { + const navHeight = getComputedStyle(document.documentElement).getPropertyValue('--nav-height'); + const navHeightInPx = Number.parseInt(navHeight, 10); // assuming that the --nav-height is in px + const inPageNav = getComputedStyle(document.documentElement).getPropertyValue('--inpage-navigation-height'); + // assuming that the --inpage-navigation-height is in px + const inPageNavInPx = Number.parseInt(inPageNav, 10); + const windowHeightInPx = window.innerHeight; + const availableViewportInPx = windowHeightInPx - navHeightInPx - inPageNavInPx; + // wrapper height is the viewport height without navigations + // (to make sure that the slide will fit inside the block) + scroll padding for every slide + const wrapperHeight = slideScrollPaddingInPx * slidesCount + availableViewportInPx; + wrapper.style.height = `${wrapperHeight}px`; +}; + +export default async function decorate(block) { + const activeSlideClass = `${blockName}__slide--active`; + const activeSlideImageClass = `${blockName}__slide-image--active`; + const variantClasses = ['image-left']; + variantsClassesToBEM(block.classList, variantClasses, blockName); + + [...block.querySelectorAll(':scope > div')].forEach(unwrapDivs); + + const heading = block.querySelector(':scope > div > :is(h1, h2, h3, h4, h5, h6)'); + const rows = [...block.querySelectorAll(':scope > div')].slice(1); + const list = createElement('ul', { classes: `${blockName}__slides` }); + const contentEl = createElement('div', { classes: `${blockName}__content` }); + + heading.parentElement.replaceWith(heading); + heading.classList.add(`${blockName}__heading`); + + contentEl.append(heading, list); + block.append(contentEl); + + // moving the rows to list + rows.forEach((el) => { + const newEl = createElement('li', { classes: `${blockName}__slide` }); + const textWrapper = createElement('div', { classes: `${blockName}__text-wrapper` }); + + newEl.innerHTML = el.innerHTML; + + const descriptionHeading = newEl.querySelector(':scope > :is(h1, h2, h3, h4, h5, h6)'); + const description = newEl.querySelector(':scope > p'); + + descriptionHeading.replaceWith(textWrapper); + textWrapper.append(descriptionHeading, description); + + el.remove(); + list.append(newEl); + selectImagesList(newEl); + }); + + const slidesCount = list.querySelectorAll(`.${blockName}__images-list picture`).length; + setContentWrapperHeight(block, slidesCount); + + // setting the first slide as active + let activeSlide = list.children[0]; + activeSlide.classList.add(activeSlideClass); + // setting the first image in the first slide active + let activePicListItem = activeSlide.querySelector(`.${blockName}__images-list li`); + activePicListItem.classList.add(activeSlideImageClass); + + const showNextSlide = () => { + const nextImageInSlide = block.querySelector(`.${activeSlideImageClass} + li`); + let hasNextSlide = true; + + // if there is a next image in the same slide just switch image + if (nextImageInSlide) { + activePicListItem.classList.remove(activeSlideImageClass); + nextImageInSlide.classList.add(activeSlideImageClass); + activePicListItem = nextImageInSlide; + } else { + // if no next image in slide switch to next slide + const nextSlide = block.querySelector(`.${activeSlideClass} + li`); + + if (nextSlide) { + activeSlide.classList.remove(activeSlideClass); + nextSlide.classList.add(activeSlideClass); + activeSlide = nextSlide; + + activePicListItem.classList.remove(activeSlideImageClass); + activePicListItem = nextSlide.querySelector(`.${blockName}__images-list li`); + activePicListItem.classList.add(activeSlideImageClass); + } else { + hasNextSlide = false; + } + } + + return hasNextSlide; + }; + + const showPrevSlide = () => { + const prevImageInSlide = block.querySelector(`.${activeSlideImageClass}`).previousElementSibling; + let hasPrevSlide = true; + + // if there is a prev image in the same slide just switch image + if (prevImageInSlide) { + activePicListItem.classList.remove(activeSlideImageClass); + prevImageInSlide.classList.add(activeSlideImageClass); + activePicListItem = prevImageInSlide; + } else { + // if no prev image in slide switch to prev slide + const prevSlide = block.querySelector(`.${activeSlideClass}`).previousElementSibling; + + if (prevSlide) { + activeSlide.classList.remove(activeSlideClass); + prevSlide.classList.add(activeSlideClass); + activeSlide = prevSlide; + + activePicListItem.classList.remove(activeSlideImageClass); + activePicListItem = prevSlide.querySelector(`.${blockName}__images-list li:last-of-type`); + activePicListItem.classList.add(activeSlideImageClass); + } else { + hasPrevSlide = false; + } + } + + return hasPrevSlide; + }; + + let slideIndex = 0; + + window.addEventListener('scroll', () => { + const navHeight = getComputedStyle(document.documentElement).getPropertyValue('--nav-height'); + const navHeightInPx = Number.parseInt(navHeight, 10); // assuming that the --nav-height is in px + const inPageNav = getComputedStyle(document.documentElement).getPropertyValue('--inpage-navigation-height'); + // assuming that the --inpage-navigation-height is in px + const inPageNavInPx = Number.parseInt(inPageNav, 10); + const { top: blockTopPosition, bottom: blockBottomPosition } = block.getBoundingClientRect(); + + if ( + blockTopPosition < navHeightInPx + inPageNavInPx + && blockBottomPosition > navHeightInPx + inPageNavInPx + ) { + const blockScrollInPx = Math.abs(blockTopPosition - navHeightInPx - inPageNavInPx); + const newSlideIndex = Math.floor(blockScrollInPx / slideScrollPaddingInPx); + + if (newSlideIndex > slidesCount) { + return; + } + + if (newSlideIndex > slideIndex) { + showNextSlide(); + } + + if (newSlideIndex < slideIndex) { + showPrevSlide(); + } + + slideIndex = newSlideIndex; + } + }); +} diff --git a/styles/styles.css b/styles/styles.css index e6da46389..8246e710e 100644 --- a/styles/styles.css +++ b/styles/styles.css @@ -866,7 +866,7 @@ main .section.responsive-title h1 { --easing-standard: cubic-bezier(0.2, 0, 0.1, 1); /* In page navigation */ - --inpage-navigation-height: 0; + --inpage-navigation-height: 0px; } .redesign-v2 body {