From 072db80d405a1802b48613994d4f6ade0deb5f54 Mon Sep 17 00:00:00 2001 From: taimurCognizant <150666850+taimurCognizant@users.noreply.github.com> Date: Wed, 29 Nov 2023 17:49:11 +0100 Subject: [PATCH] Countdown in hero #66 (#264) * add countdown timer in hero block * refactor gaps, UI improvements for all variants * fix button padding for tablet * better text wrapping * fix hero container takes at least the height of the viewport, and in case of overflow content, the height expands * update hero gradient * add variant to right align background in hero * refactor better class names for positioning * fix hero background * add play icon --------- Co-authored-by: Syb Wartna --- blocks/v2-hero/v2-hero.css | 98 +++++++++++++++++++++++++++++++------- blocks/v2-hero/v2-hero.js | 81 ++++++++++++++++++++++++++++--- icons/play.svg | 3 ++ placeholder.json | 16 +++++++ 4 files changed, 174 insertions(+), 24 deletions(-) create mode 100644 icons/play.svg diff --git a/blocks/v2-hero/v2-hero.css b/blocks/v2-hero/v2-hero.css index a9dc5d94..7f6f5162 100644 --- a/blocks/v2-hero/v2-hero.css +++ b/blocks/v2-hero/v2-hero.css @@ -26,24 +26,27 @@ --scroll-dot-width: 9px; --v2-hero-padding: 32px; - height: calc(100vh - var(--nav-height)); + min-height: calc(100vh - var(--nav-height)); overflow: hidden; position: relative; + background-color: var(--c-grey-1); } /* Reduce the Hero height by the inpage-navigation height */ .v2-inpage-navigation-wrapper + .v2-hero-container .v2-hero { - height: calc(100vh - (var(--nav-height) + var(--inpage-navigation-height) - 2px)); + min-height: calc(100vh - (var(--nav-height) + var(--inpage-navigation-height) - 2px)); } @supports (height: 1svh) { .v2-hero { - height: calc(100svh - var(--nav-height)); + min-height: calc(100svh - var(--nav-height)); } - .v2-inpage-navigation-wrapper + .v2-hero-container .v2-hero { - height: calc(100svh - (var(--nav-height) + var(--inpage-navigation-height) - 2px)); + .v2-inpage-navigation-wrapper + .v2-hero-container .v2-hero, + .v2-inpage-navigation-wrapper + .v2-hero-container .v2-hero__content-wrapper { + min-height: calc(100svh - (var(--nav-height) + var(--inpage-navigation-height) - 2px)); } + } .section.v2-hero-container { @@ -58,15 +61,19 @@ width: 100%; } +.v2-hero--background-right .v2-hero__image, .v2-hero--background-right .v2-hero__video { + object-position: center right; +} + .v2-hero__content-wrapper { color: var(--c-main-black); position: relative; display: flex; flex-direction: column; - height: 100%; + min-height: calc(100svh - var(--nav-height)); padding: var(--v2-hero-padding); padding-bottom: calc((var(--v2-hero-padding) * 2) + var(--scroll-icon-height)); - background: linear-gradient(180deg, rgb(255 255 255 / 50%) 0%, rgb(255 255 255 / 0%) 100%); + background: linear-gradient(180deg, rgb(255 255 255 / 40%) 0%, rgb(255 255 255 / 0%) 100%); } .v2-hero--centered .v2-hero__content-wrapper { @@ -75,18 +82,22 @@ text-align: center; } -.v2-hero--left .v2-hero__content-wrapper { +.v2-hero--left-center .v2-hero__content-wrapper { justify-content: center; } -.v2-hero--bottom .v2-hero__content-wrapper { +.v2-hero--center-bottom .v2-hero__content-wrapper { align-items: center; justify-content: flex-end; text-align: center; } +.v2-hero--dark { + background-color: var(--c-grey-4); +} + .v2-hero--dark .v2-hero__content-wrapper { - background: linear-gradient(180deg, rgb(0 0 0 / 50%) 0%, rgb(0 0 0 / 0%) 100%); + background: linear-gradient(180deg, rgb(0 0 0 / 40%) 0%, rgb(0 0 0 / 0%) 100%); color: var(--c-white); } @@ -95,11 +106,11 @@ } .v2-hero__content > * { - max-width: 600px; + max-width: var(--text-block-max-width); } .v2-hero--centered .v2-hero__content > *, -.v2-hero--bottom .v2-hero__content > * { +.v2-hero--center-bottom .v2-hero__content > * { margin-left: auto; margin-right: auto; } @@ -109,18 +120,27 @@ font-size: 70px; line-height: 85%; letter-spacing: 5px; + margin-bottom: 0; + text-wrap: balance; +} + +.v2-hero__title + p { + margin-top: 8px; } -.v2-hero__title, .v2-hero__content p { - margin: 10px 0 0; + margin: 0; } .v2-hero__buttons-wrapper { display: inline-flex; flex-flow: column wrap; - gap: 8px; - margin-top: 24px; + gap: 24px; + padding: 24px 0 0; +} + +.v2-hero__buttons-wrapper .button { + width: max-content; } /* Scroll icon */ @@ -146,6 +166,48 @@ width: var(--scroll-dot-width); } +/* START - Countdown */ + +.v2-hero__countdown { + display: flex; + gap: 32px; + padding: 24px 0; +} + +.v2-hero--centered .v2-hero__countdown, .v2-hero--center-bottom .v2-hero__countdown { + justify-content: center; +} + +.v2-hero__countdown-segment { + position: relative; + flex-basis: 50px; + text-align: center; +} + +.v2-hero__countdown-segment + .v2-hero__countdown-segment::before { + content: ":"; + position: absolute; + font: 400 var(--f-heading-3-5-font-size) / var(--f-heading-3-5-line-height) var(--ff-volvo-novum-medium); + top: 0; + left: -16px; + transform: translate(-50%, 0); +} + +.v2-hero__countdown-number { + font: 400 var(--f-heading-3-5-font-size) / var(--f-heading-3-5-line-height) var(--ff-volvo-novum-medium); + letter-spacing: 0.25px; + font-feature-settings: 'tnum'; +} + +.v2-hero__countdown-label { + font: 400 var(--f-caption-font-size) / var(--f-caption-line-height) var(--ff-volvo-novum); + letter-spacing: var(--f-caption-letter-spacing); + text-transform: capitalize; +} + +/* END - Countdown */ + + @media (min-width: 744px) { .v2-hero { --v2-hero-padding: 50px; @@ -158,7 +220,7 @@ .v2-hero__title { font-size: 92px; line-height: 100%; - letter-spacing: -1.15px; + letter-spacing: 6.5px; } .v2-hero__buttons-wrapper { @@ -175,6 +237,6 @@ .v2-hero__title { font-size: 100px; line-height: 85%; - letter-spacing: 2px; + letter-spacing: 7px; } } diff --git a/blocks/v2-hero/v2-hero.js b/blocks/v2-hero/v2-hero.js index cf168b4f..d58b7ea0 100644 --- a/blocks/v2-hero/v2-hero.js +++ b/blocks/v2-hero/v2-hero.js @@ -3,16 +3,49 @@ import { createVideo, } from '../../scripts/video-helper.js'; import { - createElement, - removeEmptyTags, - variantsClassesToBEM, + createElement, getTextLabel, removeEmptyTags, variantsClassesToBEM, } from '../../scripts/common.js'; -const variantClasses = ['centered', 'left', 'bottom', 'dark']; +const variantClasses = ['centered', 'left-center', 'center-bottom', 'dark', 'background-right']; +let intervalId = null; +const blockName = 'v2-hero'; -export default async function decorate(block) { - const blockName = 'v2-hero'; +function updateCountdownElement(block, elementId, value, label) { + const element = block.querySelector(`#${elementId}`); + element.textContent = value.toString().padStart(2, '0'); + element.nextElementSibling.textContent = label; +} + +function updateCountdown(eventTime, block) { + const now = new Date(); + const diff = eventTime - now; + + // Check if the event time has passed + if (diff <= 0) { + block.querySelector(`.${blockName}__countdown-wrapper`)?.remove(); + clearInterval(intervalId); + return; + } + + // Calculate time left + const days = Math.floor(diff / (1000 * 60 * 60 * 24)); + const hours = Math.floor((diff % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60)); + const minutes = Math.floor((diff % (1000 * 60 * 60)) / (1000 * 60)); + const seconds = Math.floor((diff % (1000 * 60)) / 1000); + + // Format labels + const dayLabel = days === 1 ? getTextLabel('day') : `${getTextLabel('day')}s`; + const hourLabel = hours === 1 ? getTextLabel('hour') : `${getTextLabel('hour')}s`; + const minuteLabel = minutes === 1 ? getTextLabel('minute') : `${getTextLabel('minute')}s`; + const secondLabel = seconds === 1 ? getTextLabel('second') : `${getTextLabel('second')}s`; + updateCountdownElement(block, 'days', days, dayLabel); + updateCountdownElement(block, 'hours', hours, hourLabel); + updateCountdownElement(block, 'minutes', minutes, minuteLabel); + updateCountdownElement(block, 'seconds', seconds, secondLabel); +} + +export default async function decorate(block) { // add Hero variant classnames variantsClassesToBEM(block.classList, variantClasses, blockName); @@ -45,6 +78,38 @@ export default async function decorate(block) { const content = block.querySelector(':scope > div > div'); content.classList.add(`${blockName}__content`); + // Countdown timer + const blockSection = block.parentElement?.parentElement; + if (blockSection && blockSection.dataset?.countdownDate) { + const countDownWrapper = createElement('div', { classes: `${blockName}__countdown-wrapper` }); + countDownWrapper.innerHTML = `
+
+
00
+
Days
+
+
+
00
+
Hours
+
+
+
00
+
Minutes
+
+
+
00
+
Seconds
+
+
`; + content.prepend(countDownWrapper); + + const eventTimeIso = blockSection.dataset.countdownDate; + const eventTime = new Date(eventTimeIso); + updateCountdown(eventTime, block); + intervalId = setInterval(() => { + updateCountdown(eventTime, block); + }, 1000); + } + // convert all headings to h1 const headings = [...content.querySelectorAll('h1, h2, h3, h4, h5, h6')]; headings.forEach((heading) => { @@ -58,6 +123,10 @@ export default async function decorate(block) { } }); + // render all paragraph as H6 with the class + const paragraphs = [...content.querySelectorAll('p')]; + paragraphs.forEach((paragraph) => paragraph.classList.add('h6')); + const buttonsWrapper = createElement('div', { classes: `${blockName}__buttons-wrapper` }); const ctaButtons = content.querySelectorAll('.button-container > a'); [...ctaButtons].forEach((b, i) => { diff --git a/icons/play.svg b/icons/play.svg new file mode 100644 index 00000000..8ede56fa --- /dev/null +++ b/icons/play.svg @@ -0,0 +1,3 @@ + + + diff --git a/placeholder.json b/placeholder.json index 55fdc4a7..90b1a8ae 100644 --- a/placeholder.json +++ b/placeholder.json @@ -143,6 +143,22 @@ "Key": "vinformat-length", "Text": "Please fill out this field" }, + { + "Key": "day", + "Text": "day" + }, + { + "Key": "hour", + "Text": "hour" + }, + { + "Key": "minute", + "Text": "minute" + }, + { + "Key": "second", + "Text": "second" + }, { "Key": "Close", "Text": "Close"