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
';
+
+ 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
+ `;
+ }
+
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;
+ }
+}