diff --git a/blocks/columns/columns.css b/blocks/columns/columns.css index 8907f029..c4ecdd4f 100644 --- a/blocks/columns/columns.css +++ b/blocks/columns/columns.css @@ -1,25 +1,33 @@ .columns > div { - display: flex; - flex-direction: column; + display: flex; + flex-direction: column; } .columns img { - width: 100%; + width: 100%; } -@media (min-width: 900px) { - .columns > div { - display: flex; - align-items: center; - flex-direction: unset; - } +.columns > div > div { + order: 1; +} + +.columns > div > .columns-img-col { + order: 0; +} + +.columns > div > .columns-img-col img { + display: block; +} - .columns > div > div { - flex: 1; - margin-left: 32px; - } +@media (min-width: 600px) { + .columns > div { + align-items: center; + flex-direction: unset; + gap: 32px; + } - .columns > div > div:first-of-type { - margin-left: unset; - } + .columns > div > div { + flex: 1; + order: unset; + } } \ No newline at end of file diff --git a/blocks/columns/columns.js b/blocks/columns/columns.js index a545fc6c..9b78c812 100644 --- a/blocks/columns/columns.js +++ b/blocks/columns/columns.js @@ -1,4 +1,18 @@ export default function decorate(block) { const cols = [...block.firstElementChild.children]; block.classList.add(`columns-${cols.length}-cols`); + + // setup image columns + [...block.children].forEach((row) => { + [...row.children].forEach((col) => { + const pic = col.querySelector('picture'); + if (pic) { + const picWrapper = pic.closest('div'); + if (picWrapper && picWrapper.children.length === 1) { + // picture is only content in column + picWrapper.classList.add('columns-img-col'); + } + } + }); + }); } diff --git a/blocks/header/header.css b/blocks/header/header.css index 7a7ebf17..3094e370 100644 --- a/blocks/header/header.css +++ b/blocks/header/header.css @@ -314,3 +314,189 @@ header .header-markets { header .header-topbar .icon svg { height: 100%; } + +/* APP HEADER */ +header.app-header nav { + display: grid; + grid-template: + 'hamburger brand tools' var(--app-nav-height) + 'sections sections sections' 0 / 28px 1fr auto; + gap: 8px; + top: 0; + width: 100%; + height: var(--app-nav-height); + z-index: 2; +} + +header.app-header nav[aria-expanded='true'] { + grid-template: + 'brand hamburger' var(--app-nav-height) + 'sections sections' 1fr + 'tools tools' auto / 1fr 28px; + gap: 0 16px; + overflow-y: auto; + width: 60vw; + min-height: 100vh; + z-index: 3; +} + +@media (min-width: 900px) { + header.app-header nav, + header.app-header nav[aria-expanded='true'] { + display: flex; + justify-content: space-between; + height: 100px; + padding: 20px; + background-color: transparent; + } +} + +/* hamburger */ +header.app-header nav .nav-hamburger { + display: flex; + align-items: center; + justify-content: center; + width: 28px; + height: 28px; + padding: 0; +} + +header.app-header nav[aria-expanded='false'] .nav-hamburger-icon, +header.app-header nav[aria-expanded='false'] .nav-hamburger-icon::after, +header.app-header nav[aria-expanded='false'] .nav-hamburger-icon::before { + top: 0; + width: 22px; + height: 3.5px; + border-radius: 1px; +} + +header.app-header nav[aria-expanded='false'] .nav-hamburger-icon::after, +header.app-header nav[aria-expanded='false'] .nav-hamburger-icon::before { + top: -8px; +} + +header.app-header nav[aria-expanded='false'] .nav-hamburger-icon::after { + top: 8px; +} + +header.app-header nav[aria-expanded='true'] .nav-hamburger-icon { + transform: translate(-3px, 7px); +} + +header.app-header nav[aria-expanded='true'] .nav-hamburger-icon::after, +header.app-header nav[aria-expanded='true'] .nav-hamburger-icon::before { + width: 24px; + height: 3.5px; + border-radius: 1px; + top: 0; + left: 0; +} + +@media (min-width: 900px) { + header.app-header nav .nav-hamburger { + display: none; + visibility: hidden; + } +} + +/* brand */ +header.app-header nav[aria-expanded='true'] .nav-brand { + display: relative; +} + +header.app-header nav[aria-expanded='true'] .nav-brand::after { + content: ''; + display: block; + position: absolute; + width: 45%; + height: 2px; + margin-top: 16px; + background-color: #d6d6d6; +} + +header.app-header nav .nav-brand p { + margin: 0; + line-height: 0; +} + +header.app-header nav .nav-brand .icon-wknd-logo-dk { + width: unset; + margin-top: 0; + margin-bottom: 0; +} + +header.app-header nav .nav-brand svg { + height: 22px; + width: auto; +} + +@media (min-width: 900px) { + header.app-header nav .nav-brand svg { + height: 36px; + } +} + +/* sections */ +header.app-header nav[aria-expanded='true'] .nav-sections { + padding: 60px 0; +} + +header.app-header nav[aria-expanded='true'] .nav-sections ul { + display: flex; + flex-direction: column; + gap: 30px; + margin: 0; +} + +header.app-header nav .nav-sections li { + font-size: 16px; + font-weight: 600; + letter-spacing: 1px; + text-transform: uppercase; +} + +@media (min-width: 900px) { + header.app-header nav .nav-sections { + display: unset; + } + + header.app-header nav .nav-sections ul { + display: flex; + gap: 20px; + justify-content: center; + } + + header.app-header nav .nav-sections li { + padding: 0; + font-size: 14px; + letter-spacing: .28px; + } +} + +/* tools */ +header.app-header nav .nav-tools { + height: unset; +} + +header.app-header nav[aria-expanded='true'] .nav-tools { + margin-bottom: 15%; +} + +header.app-header nav .nav-tools a:hover { + text-decoration: none; +} + +header.app-header nav .nav-tools p { + margin: 0; + line-height: 1; + font-size: 16px; + font-weight: 600; + text-transform: uppercase; +} + +@media (min-width: 900px) { + header.app-header nav .nav-tools p { + font-size: 14px; + letter-spacing: .28px; + } +} diff --git a/blocks/header/header.js b/blocks/header/header.js index 95bffabb..02685b56 100644 --- a/blocks/header/header.js +++ b/blocks/header/header.js @@ -56,10 +56,18 @@ export default async function decorate(block) { document.body.style.overflowY = expanded ? '' : 'hidden'; nav.setAttribute('aria-expanded', expanded ? 'false' : 'true'); }); - const topBar = document.createElement('div'); - topBar.classList.add('header-topbar'); - block.prepend(topBar); - topBar.innerHTML = '
Sign In
EN-US
'; + + if (navPath === '/nav') { + block.parentElement.classList.add('has-topbar'); + const topBar = document.createElement('div'); + topBar.classList.add('header-topbar'); + block.prepend(topBar); + topBar.innerHTML = `
Sign In
+
+ EN-US +
`; + } + nav.prepend(hamburger); nav.setAttribute('aria-expanded', 'false'); decorateIcons(block); diff --git a/blocks/menu/menu.css b/blocks/menu/menu.css new file mode 100644 index 00000000..2bb233db --- /dev/null +++ b/blocks/menu/menu.css @@ -0,0 +1,160 @@ +@media (min-width: 900px) { + .menu { + width: max-content; + padding-right: 20px; + } +} + +/* button */ +.menu button { + position: relative; + width: 100%; + margin: 0; + border: 0; + padding: 12px; + font-weight: 800; + text-align: left; +} + +.menu button::after { + content: ''; + position: absolute; + top: 50%; + right: 12px; + transform: translateY(-50%) rotate(45deg); + width: 6px; + height: 6px; + border-radius: 0 1px 2px; + border-right: 2px solid black; + border-bottom: 2px solid black; + transition: transform .3s ease-in-out; +} + +.menu button[aria-expanded=true]::after { + transform: translateY(-50%) rotate(225deg); +} + +@media (min-width: 900px) { + .menu button { + visibility: hidden; + display: none; + } +} + +/* menu */ +.menu menu, +.menu menu ul { + margin: 0; + padding: 0; +} + +.menu menu { + padding: 20px 8px; + background-color: #202020; + color: white; + font-size: 16px; + opacity: 1; + transition: opacity .3s ease-in-out; +} + +.menu menu ul { + list-style: none; + display: flex; + flex-direction: column; + gap: 25px; + border-left: 2px solid #4a4a4a; + padding: 8px 0; +} + +.menu menu li { + margin-left: -2px; + border-left: 2px solid #4a4a4a; +} + +.menu menu li:hover, +.menu menu li:focus { + border-left: 2px solid var(--color-yellow); +} + +.menu menu a { + display: block; + padding-left: 12px; + color: white; +} + +.menu menu a:hover { + color: var(--color-yellow); + text-decoration: none; +} + +.menu button[aria-expanded=false] + menu { + opacity: 0; + visibility: hidden; + height: 0; +} + +@media (min-width: 900px) { + .menu menu { + padding: 0; + background-color: transparent; + color: var(--text-color); + font-size: 14px; + letter-spacing: .28px; + text-align: right; + text-transform: uppercase; + } + + .menu menu, + .menu button[aria-expanded=false] + menu { + opacity: 1; + visibility: visible; + height: initial; + } + + .menu menu.dark { + color: white; + } + + .menu menu ul { + list-style: none; + display: flex; + flex-direction: column; + gap: 0; + border-left: 0; + border-right: 2px solid; + padding: 0; + } + + .menu menu li { + margin-right: -2px; + border-left: 0; + border-right: 2px solid; + padding: 4px 0; + transition: all .3s ease-in-out; + } + + .menu menu li.active, + .menu menu li:hover, + .menu menu li:focus { + margin-right: -3px; + border-left: 0; + border-right: 4px solid; + padding: 2px 0; + font-size: 18px; + font-weight: bold; + } + + .menu menu a { + padding-right: 6px; + color: var(--text-color); + } + + .menu menu a:hover { + color: var(--text-color); + } + + .menu menu.dark a, + .menu menu.dark a:hover { + color: white; + } +} diff --git a/blocks/menu/menu.js b/blocks/menu/menu.js new file mode 100644 index 00000000..08b7028e --- /dev/null +++ b/blocks/menu/menu.js @@ -0,0 +1,70 @@ +import { toClassName } from '../../scripts/lib-franklin.js'; + +export default function decorate(block) { + // build menu item button (mobile) + const button = document.createElement('button'); + button.setAttribute('aria-controls', 'menu'); + button.setAttribute('aria-expanded', false); + button.id = 'menu-button'; + + // decorate menu item list + const menuItems = []; + const menu = document.createElement('menu'); + menu.setAttribute('aria-labelledby', 'menu-button'); + menu.id = 'menu'; + const ul = document.createElement('ul'); + menu.append(ul); + [...block.firstElementChild.children].forEach((item, i) => { + const text = item.textContent; + const li = document.createElement('li'); + menuItems.push(li); + if (!i) { + button.textContent = text; + li.classList.add('active'); + } + li.setAttribute('data-menu-id', toClassName(text)); + li.innerHTML = `${text}`; + li.querySelector('a').addEventListener('click', (e) => { + e.preventDefault(); + document.getElementById(toClassName(text)).scrollIntoView({ + behavior: 'smooth', + }); + button.setAttribute('aria-expanded', false); + }); + ul.append(li); + }); + block.innerHTML = ''; + block.append(button, menu); + + button.addEventListener('click', () => { + const expanded = button.getAttribute('aria-expanded') === 'true'; + button.setAttribute('aria-expanded', !expanded); + }); + + // setup scroll behavior + const sections = document.querySelectorAll('main .section'); + sections.forEach((section, i) => { + const observer = new IntersectionObserver(async (entries) => { + const observed = entries.find((entry) => entry.isIntersecting); + if (observed) { + let observedSection = section; + if (!section.id) observedSection = sections[i - 1]; + if ([...observedSection.classList].includes('dark')) { + menu.classList.add('dark'); + } else { + menu.classList.remove('dark'); + } + button.textContent = observedSection.dataset.menuItem; + menuItems.forEach((item) => { + if (item.getAttribute('data-menu-id') === observedSection.id) { + item.classList.add('active'); + } else { + item.classList.remove('active'); + } + }); + } + }, { threshold: 0.5 }); + + observer.observe(section); + }); +} diff --git a/blocks/parallax/parallax.css b/blocks/parallax/parallax.css new file mode 100644 index 00000000..ee125643 --- /dev/null +++ b/blocks/parallax/parallax.css @@ -0,0 +1,150 @@ +.parallax.dark { + color: white; +} + +.parallax > div { + position: relative; + min-height: 100vh; + overflow: hidden; +} + +.parallax img { + display: block; +} + +/* background */ +.parallax .parallax-background picture { + box-sizing: border-box; + position: absolute; + top: 0; + left: 0; + bottom: 0; + right: 0; + z-index: -1; +} + +.parallax .parallax-background img { + width: 100%; + height: 100%; + object-fit: cover; +} + +/* text */ +.parallax .parallax-text { + box-sizing: border-box; + display: flex; + flex-direction: column; + justify-content: flex-start; + position: absolute; + top: 0; + left: 0; + bottom: 0; + right: 0; + opacity: 0; + margin-top: 100%; + z-index: 0; + padding: 32px; + text-align: center; + text-shadow: 0 0 32px white; +} + +.parallax.dark .parallax-text { + text-shadow: 0 0 32px black; +} + +.parallax .parallax-text.active { + opacity: 1; + margin-top: 0; + transition: opacity .9s ease-in-out, margin .6s ease-in; +} + +@media (min-width: 900px) { + .parallax .parallax-text { + justify-content: center; + } + + .parallax .parallax-text h2 { + margin-top: 16px; + font-size: 60px; + } +} + +/* layer */ +.parallax .parallax-layer { + overflow: hidden; +} + +.parallax .parallax-layer img { + opacity: .5; + position: absolute; + box-sizing: border-box; + width: auto; + height: auto; + min-width: 100vw; + object-fit: contain; + /* stylelint-disable declaration-block-no-redundant-longhand-properties */ + transition-delay: .2s; + transition-property: top, left, bottom, right, opacity; + transition-timing-function: ease-in-out; + transition-duration: .8s; + z-index: -1; +} + +.parallax .parallax-layer[class*='to'] img { + opacity: 1; +} + +.parallax .parallax-layer.parallax-focal img { + width: 100%; + max-width: 100vw; + height: 100%; + max-height: 50vh; + margin: auto; + padding: 20px; +} + +/* FROM */ +.parallax .parallax-layer.from-bottom img { + bottom: -20%; + left: 0; + right: 0; +} + +.parallax .parallax-layer.from-bottom-left img { + bottom: -20%; + left: -20%; +} + +.parallax .parallax-layer.from-bottom-right img { + bottom: -20%; + right: -20%; +} + +/* TO */ +.parallax .parallax-layer.to-bottom img { + bottom: 0; + left: 0; + right: 0; +} + +.parallax .parallax-layer.to-bottom-left img { + bottom: 0; + left: 0; +} + +.parallax .parallax-layer.to-bottom-right img { + bottom: 0; + right: 0; +} + +.parallax .parallax-layer.to-center img { + top: 50%; + left: 50%; + transform: translate(-50%, -50%); +} + +@media (min-width: 900px) { + .parallax .parallax-layer.parallax-focal img { + height: calc(100vh - 100px); + } +} diff --git a/blocks/parallax/parallax.js b/blocks/parallax/parallax.js new file mode 100644 index 00000000..0c533e03 --- /dev/null +++ b/blocks/parallax/parallax.js @@ -0,0 +1,84 @@ +import { toClassName } from '../../scripts/lib-franklin.js'; + +function formatKey(el) { + const strong = el.querySelector('strong'); + let isFocal = false; + if (strong) { + isFocal = true; + strong.remove(); + } + const text = el.textContent.trim().toLowerCase(); + // basic element + if (text === 'background' || text === 'text') return { type: text }; + // styled text + if (text.startsWith('text')) { + const paren = text.match(/\(([^)]+)\)/g)[0].replace(/\W/g, ''); + return { type: 'text', style: paren }; + } + // layer element with direction + const split = text.split(',').map((t) => t.trim()); + if (split && split.length > 1) { + return { + type: 'layer', + from: split[0], + to: split[1], + focal: isFocal, + }; + } + return { + type: 'layer', + from: split[0], + to: split[0], + focal: isFocal, + }; +} + +export default function decorate(block) { + const wrapper = document.createElement('div'); + const layers = []; + [...block.children].forEach((row) => { + const [key, content] = [...row.children]; + const config = formatKey(key); + if (config.type === 'background') { + // create background image + content.className = 'parallax-background'; + content.querySelector('img').setAttribute('loading', 'eager'); + wrapper.prepend(content); + } else if (config.type === 'text') { + // create text layer + content.className = 'parallax-text'; + if (config.style) content.classList.add(`parallax-text-${config.style}`); + wrapper.append(content); + } else if (config.type === 'layer') { + // create parallax layer + content.classList.add('parallax-layer', `from-${toClassName(config.from)}`); + content.setAttribute('data-from', toClassName(config.from)); + content.setAttribute('data-to', toClassName(config.to)); + if (config.focal) content.classList.add('parallax-focal'); + layers.push(content); + wrapper.append(content); + } + }); + block.innerHTML = ''; + + const observer = new IntersectionObserver(async (entries) => { + const observed = entries.find((entry) => entry.isIntersecting); + layers.forEach((layer) => { + const to = layer.getAttribute('data-to'); + if (observed && observed.isIntersecting) { + layer.classList.add(`to-${to}`); + } else { + layer.classList.remove(`to-${to}`); + } + }); + const text = block.querySelector('.parallax-text'); + if (observed && observed.isIntersecting) { + text.classList.add('active'); + } else { + text.classList.remove('active'); + } + }, { threshold: 0.5 }); + observer.observe(block); + + block.append(wrapper); +} diff --git a/blocks/pointers/pointers.css b/blocks/pointers/pointers.css new file mode 100644 index 00000000..fbf935aa --- /dev/null +++ b/blocks/pointers/pointers.css @@ -0,0 +1,157 @@ +.pointers { + position: relative; + width: 100%; + max-width: 500px; + height: min(calc(100vw + 64px), 500px); + margin: auto; +} + +@media (min-width: 900px) { + .pointers { + width: 500px; + } +} + +.pointers picture { + position: absolute; + top: 0; + left: 0; + bottom: 0; + right: 0; + box-sizing: border-box; +} + +.pointers img { + object-fit: contain; + width: 100%; + height: 100%; +} + +.pointers .pointers-pointer { + box-sizing: border-box; + position: absolute; + opacity: 0; + z-index: 1; + width: min(150px, calc(50vw - 16px)); + margin-top: 50%; + border: 2px solid #0e67cf; + border-radius: 4px; + padding: 4px 12px; + background-color: #0e67cf; + color: white; + cursor: pointer; +} + +.pointers.active .pointers-pointer { + opacity: 1; + margin-top: 0; + transition: opacity .9s ease-in-out, margin .6s ease-in; +} + +.pointers .pointers-pointer .pointer-text { + transition: opacity .2s; +} + +.pointers .pointers-pointer:hover .pointer-text { + opacity: 0; +} + +.pointers .pointers-pointer::before, +.pointers .pointers-pointer::after { + content: ''; + position: absolute; + left: 50%; + transform: translateX(-50%); +} + +.pointers .pointers-pointer::before { + bottom: -22px; /* height + border */ + border: 10px solid transparent; + border-top: 10px solid #0e67cf; +} + +.pointers .pointers-pointer::after { + box-sizing: border-box; + bottom: -35px; + width: 15px; + height: 15px; + background-color: white; + border: 2px solid var(--text-color); + border-radius: 50%; +} + +.pointers .pointers-pointer.pointer-left { + top: 50%; + left: -12px; + transform: translateY(-50%); +} + +.pointers .pointers-pointer.pointer-right { + top: 50%; + right: -12px; + transform: translateY(-50%); +} + +.pointers .pointers-pointer.pointer-top { + top: 0; + left: 50%; + transform: translateX(-50%); +} + +.pointers .pointers-pointer.pointer-top-left { + top: 0; + left: 0; +} + +.pointers .pointers-pointer.pointer-top-right { + top: 0; + right: 0; +} + +@media (min-width: 900px) { + .pointers .pointers-pointer.pointer-left { + left: 32px; + } + + .pointers .pointers-pointer.pointer-right { + right: 32px; + } + + .pointers .pointers-pointer.pointer-top-right { + right: 64px; + } +} + +.pointers .pointers-pointer p { + margin: 0; + font-size: 16px; + line-height: 1.4; + text-align: left; +} + +.pointers .pointers-pointer .pointer-button { + box-sizing: border-box; + position: absolute; + top: calc(50% + 2px); + left: 50%; + transform: translate(-50%, -50%); + max-width: calc(150px - 12px); + opacity: 0; + transition: opacity .2s; +} + +.pointers .pointers-pointer:hover .pointer-button { + opacity: 1; +} + +.pointers .pointers-pointer .pointer-button .button { + max-width: 100%; + margin: 0; + border: 2px solid; + border-radius: 50px; + padding: 4px 12px; + background-color: transparent; + font-size: 16px; + font-weight: 800; + text-transform: initial; +} diff --git a/blocks/pointers/pointers.js b/blocks/pointers/pointers.js new file mode 100644 index 00000000..98471599 --- /dev/null +++ b/blocks/pointers/pointers.js @@ -0,0 +1,40 @@ +import { toClassName } from '../../scripts/lib-franklin.js'; + +function buildPointer(position, content) { + const pointer = document.createElement('div'); + pointer.className = `pointers-pointer pointer-${position}`; + const contentTypes = ['text', 'button']; + content.forEach((c, i) => { + if (contentTypes[i]) c.classList.add(`pointer-${contentTypes[i]}`); + pointer.append(c); + }); + return pointer; +} + +export default function decorate(block) { + const wrapper = document.createElement('div'); + [...block.children].forEach((row) => { + const [key, content] = [...row.children]; + row.remove(); + const type = toClassName(key.textContent); + if (type === 'image') { + const picture = content.querySelector('picture'); + wrapper.prepend(picture); + } else { + const pointer = buildPointer(type, [...content.children]); + wrapper.append(pointer); + } + }); + block.innerHTML = wrapper.innerHTML; + + const observer = new IntersectionObserver(async (entries) => { + const observed = entries.find((entry) => entry.isIntersecting); + if (observed && observed.isIntersecting) { + block.classList.add('active'); + } else { + block.classList.remove('active'); + } + }, { threshold: 0 }); + + observer.observe(block); +} diff --git a/package-lock.json b/package-lock.json index 315e469d..c54ad5f3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3960,9 +3960,9 @@ "dev": true }, "node_modules/json5": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz", - "integrity": "sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==", + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", "dev": true, "bin": { "json5": "lib/cli.js" @@ -5215,12 +5215,18 @@ } }, "node_modules/qs": { - "version": "6.7.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", - "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==", + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", + "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", "dev": true, + "dependencies": { + "side-channel": "^1.0.4" + }, "engines": { "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, "node_modules/queue-microtask": { @@ -6317,9 +6323,9 @@ } }, "node_modules/tsconfig-paths/node_modules/json5": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", - "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", + "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", "dev": true, "dependencies": { "minimist": "^1.2.0" @@ -9639,9 +9645,9 @@ "dev": true }, "json5": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz", - "integrity": "sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==", + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", "dev": true }, "just-extend": { @@ -10613,10 +10619,13 @@ } }, "qs": { - "version": "6.7.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", - "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==", - "dev": true + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", + "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", + "dev": true, + "requires": { + "side-channel": "^1.0.4" + } }, "queue-microtask": { "version": "1.2.3", @@ -11440,9 +11449,9 @@ }, "dependencies": { "json5": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", - "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", + "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", "dev": true, "requires": { "minimist": "^1.2.0" diff --git a/scripts/lib-franklin.js b/scripts/lib-franklin.js index a899cbbc..086d133b 100644 --- a/scripts/lib-franklin.js +++ b/scripts/lib-franklin.js @@ -527,8 +527,8 @@ export async function waitForLCP(lcpBlocks) { /** * loads a block named 'header' into header */ -export function loadHeader(header) { - const headerBlock = buildBlock('header', ''); +export function loadHeader(header, config) { + const headerBlock = buildBlock('header', config ? [['nav', config]] : ''); header.append(headerBlock); decorateBlock(headerBlock); return loadBlock(headerBlock); diff --git a/scripts/scripts.js b/scripts/scripts.js index b2de1e0d..fc00bd7e 100644 --- a/scripts/scripts.js +++ b/scripts/scripts.js @@ -3,6 +3,7 @@ import { buildBlock, loadHeader, loadFooter, + createOptimizedPicture, decorateButtons, decorateIcons, decorateSections, @@ -11,9 +12,11 @@ import { waitForLCP, loadBlocks, loadCSS, + getMetadata, + toClassName, } from './lib-franklin.js'; -const LCP_BLOCKS = []; // add your LCP blocks to the list +const LCP_BLOCKS = ['parallax']; // add your LCP blocks to the list window.hlx.RUM_GENERATION = 'project-1'; // add your RUM generation information here function buildHeroBlock(main) { @@ -34,6 +37,20 @@ function buildHeroBlock(main) { function buildAutoBlocks(main) { try { buildHeroBlock(main); + const isApp = toClassName(getMetadata('template')) === 'app'; + if (isApp) { + // setup badges + const badges = main.querySelectorAll('a[href$="#menu-item"]'); + badges.forEach((b) => { + const container = b.closest('.button-container') || b.parentElement; + container.classList.remove('button-container'); + container.classList.add('badge-container'); + const badge = document.createElement('span'); + badge.className = 'badge'; + badge.innerHTML = b.innerHTML; + b.replaceWith(badge); + }); + } } catch (error) { // eslint-disable-next-line no-console console.error('Auto Blocking failed', error); @@ -45,6 +62,7 @@ async function loadDemoConfig() { const pathSegments = window.location.pathname.split('/'); if (window.location.pathname.startsWith('/drafts/') && pathSegments.length > 4) { const demoBase = pathSegments.slice(0, 4).join('/'); + demoConfig.demoBase = demoBase; const resp = await fetch(`${demoBase}/theme.json`); if (resp.status === 200) { const json = await resp.json(); @@ -55,7 +73,6 @@ async function loadDemoConfig() { demoConfig[e.token] = e.value; }); demoConfig.tokens = tokens; - demoConfig.demoBase = demoBase; } } window.wknd = window.wknd || {}; @@ -74,6 +91,50 @@ export function decorateMain(main) { buildAutoBlocks(main); decorateSections(main); + + const isApp = toClassName(getMetadata('template')) === 'app'; + if (isApp) { + const header = document.querySelector('header'); + header.classList.add('app-header'); + } + const sections = [...main.querySelectorAll('.section')]; + const menuItems = []; + sections.forEach((section) => { + const { menuItem, background, video } = section.dataset; + if (menuItem) { + section.id = toClassName(menuItem); + menuItems.push(menuItem); + } + if (background) { + const picture = createOptimizedPicture(background); + picture.classList.add('section-background'); + section.prepend(picture); + } + if (video) { + const wrapper = document.createElement('div'); + wrapper.className = 'section-video'; + wrapper.innerHTML = ``; + section.prepend(wrapper); + + const videoObserver = new IntersectionObserver(async (entries) => { + const observed = entries.find((entry) => entry.isIntersecting); + if (observed) { + const source = wrapper.querySelector('source'); + source.src = source.dataset.src; + wrapper.querySelector('video').load(); + videoObserver.disconnect(); + } + }, { threshold: 0 }); + videoObserver.observe(wrapper); + } + }); + const menuWrapper = document.createElement('div'); + const menu = buildBlock('menu', [menuItems]); + menuWrapper.append(menu); + sections[0].prepend(menuWrapper); + decorateBlocks(main); } @@ -123,7 +184,8 @@ async function loadLazy(doc) { if (hash && element) element.scrollIntoView(); loadHeader(doc.querySelector('header')); - loadFooter(doc.querySelector('footer')); + const isApp = toClassName(getMetadata('template')) === 'app'; + if (!isApp) loadFooter(doc.querySelector('footer')); if (window.wknd.demoConfig.fonts) { const fonts = window.wknd.demoConfig.fonts.split('\n'); diff --git a/styles/lazy-styles.css b/styles/lazy-styles.css index f5de0796..e75e3e5a 100644 --- a/styles/lazy-styles.css +++ b/styles/lazy-styles.css @@ -1,3 +1,5 @@ /* below the fold CSS goes here */ -@import url('https://fonts.googleapis.com/css2?family=Asar&family=Source+Sans+Pro&display=swap'); \ No newline at end of file +@import url('https://fonts.googleapis.com/css2?family=Asar&display=swap'); + +@import url('https://fonts.googleapis.com/css2?family=Source+Sans+Pro:wght@400;600;800&display=swap'); \ No newline at end of file diff --git a/styles/styles.css b/styles/styles.css index bd59bb0a..bc632874 100644 --- a/styles/styles.css +++ b/styles/styles.css @@ -41,7 +41,10 @@ /* nav height */ --nav-height: 115px; - + --app-nav-height: 50px; + --topbar-height: 25px; + --topmenu-height: 42px; + /* buttons */ --button-background-color: var(--color-yellow); --button-border-radius: 0; @@ -49,6 +52,14 @@ --button-padding: 1em 2.5em; } +@media (min-width: 900px) { + :root { + /* stylelint-disable-next-line length-zero-no-unit */ + --app-nav-height: 0px; + --topmenu-height: 0; + } +} + body { font-size: var(--body-font-size-m); margin: 0; @@ -67,6 +78,22 @@ header { height: var(--nav-height); } +header.app-header { + height: calc(var(--app-nav-height) + var(--topmenu-height)); +} + +header.app-header > div { + visibility: hidden; +} + +header.app-header > div[data-block-status='loaded'] { + visibility: visible; +} + +header.has-topbar { + height: calc(var(--nav-height) + var(--topbar-height)); +} + .hidden { display: none; } @@ -101,7 +128,7 @@ body.article h2::after { width: 84px; padding-top: 8px; content: ""; - border-bottom: 2px solid #ffea00; + border-bottom: 2px solid var(--color-yellow); } h2 { font-size: var(--heading-font-size-xl) } @@ -115,7 +142,7 @@ p, dl, ol, ul, pre, blockquote { margin-bottom: 1em; } -body p { +p { margin: 0 0 13.5px; font-size: 18px; line-height: 2.5; @@ -144,29 +171,43 @@ pre { overflow: scroll; } +.badge-container { + text-align: inherit; +} + +.badge { + padding: 6px; + background-color: var(--color-yellow); + color: var(--text-color); + font-size: 14px; + font-weight: 800; + letter-spacing: .4px; + text-transform: uppercase; +} + /* buttons */ a.button:any-link, button { - color: var(--button-text-color); + box-sizing: border-box; + display: inline-block; + margin: 16px 0; + border: 2px solid transparent; + border-radius: var(--button-border-radius); + padding: var(--button-padding); background-color: var(--button-background-color); - text-transform: uppercase; + color: var(--button-text-color); + font-family: var(--body-font-family); font-size: 14px; - padding: var(--button-padding); + font-style: normal; font-weight: 600; - font-family: var(--body-font-family); - display: inline-block; - box-sizing: border-box; - text-decoration: none; - border: 2px solid transparent; + line-height: 1.5; text-align: center; - font-style: normal; - cursor: pointer; - margin: 16px 0; - white-space: nowrap; - overflow: hidden; + text-decoration: none; + text-transform: uppercase; text-overflow: ellipsis; - border-radius: var(--button-border-radius); - line-height: 1.5; + overflow: hidden; + white-space: nowrap; + cursor: pointer; } a.button:hover, a.button:focus, button:hover, button:focus { @@ -298,4 +339,189 @@ main .section.highlight { main .section[data-section-status='loading'], main .section[data-section-status='initialized'] { display: none; -} \ No newline at end of file +} + +main .section[data-background], +main .section[data-video] { + position: relative; + color: var(--background-color); + text-shadow: 0 0 32px black; +} + +main .section[data-background] picture.section-background, +main .section[data-video] .section-video { + position: absolute; + top: 0; + left: 0; + bottom: 0; + right: 0; + z-index: -1; + overflow: hidden; +} + +main .section[data-video] .section-video { + display: none; + visibility: hidden; +} + +@media (min-width: 900px) { + main .section[data-background] picture.section-background { + display: none; + visibility: hidden; + } + + main .section[data-video] .section-video { + display: block; + visibility: visible; + } +} + +main .section[data-background] picture.section-background img, +main .section[data-video] .section-video video { + width: 100%; + height: 100%; + object-fit: cover; +} + +/* themes */ +/* stylelint-disable-next-line no-descending-specificity */ +.app.appear main { + display: flex; + flex-direction: column; + height: calc(100vh - var(--app-nav-height) - var(--topmenu-height)); + overflow-y: scroll; + scroll-snap-type: y mandatory; +} + +@media (min-width: 900px) { + .app.appear main { + height: 100vh; + } +} + +.app main .section { + scroll-snap-align: start; +} + +.app main h2, +.app main p { + text-align: center; + line-height: 1.2; +} + +.app main h2 + p { + text-transform: uppercase; +} + +.app main .button-container { + margin: 0; +} + +.app main .button { + margin: 0; + padding: 16px 40px; + background-color: var(--link-color); + color: white; + line-height: 1; +} + +.app main > .section { + box-sizing: border-box; + overflow: hidden; + min-height: calc(100vh - var(--app-nav-height) - var(--topmenu-height)); + background-color: var(--color-yellow); +} + +@media (min-width: 600px) { + .app main > .section { + padding: 20px; + } +} + +@media (min-width: 900px) { + .app main > .section { + min-height: 100vh; + padding-top: 100px; + } + + .app main > .section > div { + max-width: unset; + } +} + +.app main > .section[data-section-status='loaded'] { + display: flex; + flex-direction: column; + justify-content: center; +} + +.app main > .section[data-background][data-section-status='loaded'] { + background-color: transparent; + display: flex; + align-items: center; + justify-content: center; +} + +@media (min-width: 900px) { + .app main > .section[data-background] .default-content-wrapper { + width: 50%; + margin-left: 0; + text-align: left; + } + + .app main > .section[data-background] .default-content-wrapper > * { + text-align: left; + } +} + +/* stylelint-disable-next-line no-descending-specificity */ +.app main > .section .columns img { + max-height: 50vw; + width: auto; + margin: auto; +} + +@media (min-width: 900px) { + .app main > .section .columns { + width: 66%; + } +} + +.app main > .section[data-section-status='loaded'].parallax-container { + display: unset; + padding: 0; + background-color: transparent; +} + +.app main .parallax > div { + min-height: calc(100vh - var(--app-nav-height) - var(--app-nav-height) + 8px); +} + +@media (min-width: 900px) { + .app main .parallax .parallax-text { + width: 50%; + text-align: left; + } + + .app main .parallax .parallax-text > * { + text-align: left; + } +} + +.app main .menu-wrapper { + position: fixed; + top: var(--app-nav-height); + width: 100%; + z-index: 2; +} + +@media (min-width: 900px) { + .app main .menu-wrapper { + box-sizing: border-box; + top: 50%; + right: 0; + transform: translateY(-50%); + width: auto; + padding: 0 20px; + } +}