Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ yarn-error.log*
lerna-debug.log*
.pnpm-debug.log*

# macOS
.DS_Store

# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json

Expand Down
117 changes: 56 additions & 61 deletions website/modules/asset/ui/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,15 @@ import { initCaseStudiesFilterHandler } from './initCaseStudiesFilterHandler';
import { initFormValidation } from './js/formValidation';
import { initPhoneFormatting } from './js/phoneFormat';
import { initSmoothCounters } from './smoothCounters';
import lozad from 'lozad';
import { initFontChanger } from './initFontChanger';
import { initImageLozad } from './initImageLozad';
import { setupTagSearchForInput } from './searchInputHandler';
import { FilterModal } from './filterModal';
import { initClientSideFiltering } from './clientSideFiltering';
import {
saveScrollPosition,
getSavedScrollPosition,
clearSavedScrollPosition,
} from './scrollMemory';

function revealLoaded() {
document
Expand Down Expand Up @@ -42,38 +47,6 @@ function initConfiguration() {
}
}

function initImageLozad() {
const observer = lozad();
observer.observe();
}

function initFontChanger() {
const heroContent = document.querySelector('.sf-hero-content strong');
if (!heroContent) return;

const fonts = [
'Poppins',
'Philosopher',
'Pinyon Script',
'Racing Sans One',
'Poiret One',
'Redacted Script',
'Redressed',
'Rock 3D',
'Rubik Glitch Pop',
'Yesteryear',
'Roboto Mono',
'Pixelify Sans',
];
let currentFontIndex = 0;

setInterval(() => {
currentFontIndex = (currentFontIndex + 1) % fonts.length;
const currentFont = fonts.at(currentFontIndex);
heroContent.style.fontFamily = currentFont;
}, 500);
}

function initCaseStudiesTagFilter({
inputSelector = '.tag-search',
containerSelector = '.filter-section',
Expand Down Expand Up @@ -110,7 +83,14 @@ function initBarbaPageTransitions() {

const originalEnterCallback = function (data, hasFilterAnchor) {
if (!hasFilterAnchor) {
window.scrollTo(0, 0);
const nextUrl = data.next.url.href;
const savedScroll = getSavedScrollPosition(nextUrl);
if (savedScroll === null) {
window.scrollTo(0, 0);
} else {
// Restore the scroll position when returning to the cases listing.
window.scrollTo(0, savedScroll);
}
}

const menuButton = document.getElementById('nav-icon');
Expand Down Expand Up @@ -147,8 +127,20 @@ function initBarbaPageTransitions() {
};

// Initialize Apostrophe forms (already inside apos.util.onReady)
if (!initializeApostropheForm(data.next.container)) {
const willReload = !initializeApostropheForm(data.next.container);

if (willReload) {
/*
* A full reload is about to happen. Do NOT clear the saved scroll
* position here; the load-time restore relies on it surviving the reload.
*/
window.location.reload();
} else {
/*
* SPA transition: the saved scroll position (if any) has been applied
* above, so it is safe to clear now.
*/
clearSavedScrollPosition();
}

// Remove the previous page container to avoid blinking
Expand Down Expand Up @@ -178,6 +170,11 @@ function initBarbaPageTransitions() {
],
});

barba.hooks.beforeLeave((data) => {
// Remember scroll position of the cases listing before navigating away.
saveScrollPosition(data.current.url.href);
});

barba.hooks.after(() => {
// Update menu active state
const currentPath = window.location.pathname;
Expand All @@ -192,9 +189,6 @@ function initBarbaPageTransitions() {
});

revealLoaded();
if (!window.caseStudiesFilterModal) {
initFilterModal();
}
});
});
}
Expand Down Expand Up @@ -240,28 +234,6 @@ function initMenuToggle() {
});
}

function initFilterModal() {
if (!document.querySelector('.cs_list')) {
return;
}

window.caseStudiesFilterModal = new FilterModal({
modalSelector: '#filter-modal',
openBtnSelector: '.filters-cta',
closeBtnSelector: '.filter-modal__button',
backdropSelector: '.filter-modal__backdrop',
clearAllSelector: '.clear-all',
selectedTagsSelector: '.selected-tags',
tagsFilterSelector: '.tags-filter',
});
}

if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initFilterModal);
} else {
initFilterModal();
}

export default () => {
initConfiguration();

Expand All @@ -281,7 +253,30 @@ export default () => {
initAnchorNavigation();
initMenuToggle();

/*
* Restore scroll position when returning to the /cases listing after a full
* page reload (Barba reloads pages without an Apostrophe form).
*/
const restoredScroll = getSavedScrollPosition(window.location.href);
if (restoredScroll !== null) {
clearSavedScrollPosition();
/*
* Reassert the position over a short window to counter layout shifts from
* lazily loaded images/content.
*/
const reapply = (attempts) => {
window.scrollTo(0, restoredScroll);
if (attempts > 0) {
setTimeout(() => reapply(attempts - 1), 100);
}
};
reapply(5);
}

setTimeout(() => {
// Do not hijack scroll if we are restoring a saved listing position.
if (restoredScroll !== null) return;

const { pathname, search, hash } = window.location;
if (
pathname.includes('/cases') &&
Expand Down
25 changes: 25 additions & 0 deletions website/modules/asset/ui/src/initFilterModal.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { FilterModal } from './filterModal';

const initFilterModal = function () {
if (!document.querySelector('.cs_list')) {
return;
}

window.caseStudiesFilterModal = new FilterModal({
modalSelector: '#filter-modal',
openBtnSelector: '.filters-cta',
closeBtnSelector: '.filter-modal__button',
backdropSelector: '.filter-modal__backdrop',
clearAllSelector: '.clear-all',
selectedTagsSelector: '.selected-tags',
tagsFilterSelector: '.tags-filter',
});
};

if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initFilterModal);
} else {
initFilterModal();
}

export { initFilterModal };
28 changes: 28 additions & 0 deletions website/modules/asset/ui/src/initFontChanger.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
const initFontChanger = function () {
const heroContent = document.querySelector('.sf-hero-content strong');
if (!heroContent) return;

const fonts = [
'Poppins',
'Philosopher',
'Pinyon Script',
'Racing Sans One',
'Poiret One',
'Redacted Script',
'Redressed',
'Rock 3D',
'Rubik Glitch Pop',
'Yesteryear',
'Roboto Mono',
'Pixelify Sans',
];
let currentFontIndex = 0;

setInterval(() => {
currentFontIndex = (currentFontIndex + 1) % fonts.length;
const currentFont = fonts.at(currentFontIndex);
heroContent.style.fontFamily = currentFont;
}, 500);
};

export { initFontChanger };
8 changes: 8 additions & 0 deletions website/modules/asset/ui/src/initImageLozad.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import lozad from 'lozad';

const initImageLozad = function () {
const observer = lozad();
observer.observe();
};

export { initImageLozad };
75 changes: 75 additions & 0 deletions website/modules/asset/ui/src/scrollMemory.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
/*
* Scroll memory for the case studies index page.
* Preserves the user's scroll position when navigating from a case study
* show page back to the /cases listing.
*/

const CASES_PATH = '/cases';
/*
* Use sessionStorage so the position survives full page reloads. The Barba
* enter callback triggers window.location.reload() for pages without an
* Apostrophe form (which includes the /cases listing), so an in-memory store
* would be wiped before we could restore.
*/
const STORAGE_KEY = 'casesScrollPosition';

const isCasesListing = function (pathname) {
return pathname === CASES_PATH;
};

// Save current scroll position for a given URL if it is the cases listing.
const saveScrollPosition = function (url) {
try {
const { pathname } = new URL(url, window.location.origin);
if (isCasesListing(pathname)) {
const position = window.scrollY || window.pageYOffset || 0;
window.sessionStorage.setItem(STORAGE_KEY, String(position));
}
} catch (error) {
// Ignore malformed URLs / unavailable storage
// eslint-disable-next-line no-console
console.error('Failed to save scroll position:', error);
}
};

// Returns the stored scroll position for a URL, or null if none exists.
const getSavedScrollPosition = function (url) {
try {
const { pathname } = new URL(url, window.location.origin);
if (!isCasesListing(pathname)) {
return null;
}
const stored = window.sessionStorage.getItem(STORAGE_KEY);
if (stored === null) {
return null;
}
const parsed = parseInt(stored, 10);
if (isNaN(parsed)) {
return null;
}
return parsed;
} catch (error) {
// Ignore malformed URLs / unavailable storage
// eslint-disable-next-line no-console
console.error('Failed to get saved scroll position:', error);
return null;
}
};

// Clears the stored scroll position.
const clearSavedScrollPosition = function () {
try {
window.sessionStorage.removeItem(STORAGE_KEY);
} catch (error) {
// Ignore unavailable storage
// eslint-disable-next-line no-console
console.error('Failed to clear saved scroll position:', error);
}
};

export {
saveScrollPosition,
getSavedScrollPosition,
clearSavedScrollPosition,
isCasesListing,
};
8 changes: 4 additions & 4 deletions website/modules/asset/ui/src/scss/_cases.scss
Original file line number Diff line number Diff line change
Expand Up @@ -953,7 +953,7 @@

@include breakpoint-medium {
margin: 40px 0 120px;
height: 600px;
height: auto;
}

&-card {
Expand All @@ -973,6 +973,7 @@
max-height: 396px;
max-width: none;
position: absolute;
top: 0;
width: 100%;
z-index: 0;

Expand All @@ -997,9 +998,9 @@
@include breakpoint-medium {
background: $white;
flex-direction: row;
margin-top: 0;
margin-top: 40px;
margin-left: 20.76%;
height: 520px;
height: auto;
width: 79.24%;
}

Expand Down Expand Up @@ -1166,7 +1167,6 @@
border-top: none;
padding: 16px 20px;
margin: 0;
overflow-y: auto;
}

@include breakpoint-large {
Expand Down
Loading
Loading