From 90ef52e6973de02e511db5d9b57d29fe0cbba3d4 Mon Sep 17 00:00:00 2001 From: Paras Shah Date: Sun, 10 Mar 2024 11:51:17 +0530 Subject: [PATCH 1/7] Turn off eslint import sorting Fix import sorting conflict between eslint and prettier --- .eslintrc.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.eslintrc.json b/.eslintrc.json index 8798e28..54ce2d6 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -1,6 +1,7 @@ { "extends": ["@finsweet"], "rules": { - "no-console": "off" + "no-console": "off", + "simple-import-sort/imports": "off" } } From 1a644218f35c5fc6b311ec5ed78f981891fe497a Mon Sep 17 00:00:00 2001 From: Paras Shah Date: Sun, 10 Mar 2024 11:51:36 +0530 Subject: [PATCH 2/7] Add Project Gallery implementation with lightbox --- home-projects-lightbox.ts | 1 - package.json | 1 + src/components/home-projects-gallery.ts | 107 ++++++++++++++++++++++++ src/components/home-projects-load.ts | 11 --- src/entry.ts | 3 - src/pages/home.ts | 1 + 6 files changed, 109 insertions(+), 15 deletions(-) delete mode 100644 home-projects-lightbox.ts create mode 100644 src/components/home-projects-gallery.ts delete mode 100644 src/components/home-projects-load.ts diff --git a/home-projects-lightbox.ts b/home-projects-lightbox.ts deleted file mode 100644 index bc8619a..0000000 --- a/home-projects-lightbox.ts +++ /dev/null @@ -1 +0,0 @@ -window.Webflow?.push(() => {}); diff --git a/package.json b/package.json index 02da93c..3314e64 100644 --- a/package.json +++ b/package.json @@ -28,6 +28,7 @@ }, "dependencies": { "@finsweet/ts-utils": "^0.40.0", + "fslightbox": "^3.4.1", "gsap": "^3.12.5", "htmx.org": "^1.9.10", "swiper": "^11.0.7" diff --git a/src/components/home-projects-gallery.ts b/src/components/home-projects-gallery.ts new file mode 100644 index 0000000..3d3b553 --- /dev/null +++ b/src/components/home-projects-gallery.ts @@ -0,0 +1,107 @@ +import 'fslightbox'; +import { SCRIPTS_LOADED_EVENT } from 'src/entry'; + +const PROJECT_ITEM_SELECTOR = '.projects_item-link-wrapper'; +const LIGHTBOX_WRAPPER_SELECTOR = '[data-lightbox-wrapper]'; +const LIGHTBOX_CONTENT_WRAPPER_SELECTOR = '[data-lightbox-content-wrapper]'; +const LIGHTBOX_CLOSE_BTN_ATTRIBUTE = 'data-lightbox-close'; + +let lightboxWrapperEl: HTMLElement | null; +let projectItemsList: NodeListOf; + +window.addEventListener(SCRIPTS_LOADED_EVENT, () => { + lightboxWrapperEl = document.querySelector(LIGHTBOX_WRAPPER_SELECTOR); + projectItemsList = document.querySelectorAll(PROJECT_ITEM_SELECTOR); + + if (!lightboxWrapperEl || !projectItemsList.length) { + window.DEBUG( + 'No lightbox wrapper found or no project items found.', + { lightboxWrapperEl }, + { projectItemsList } + ); + return; + } + + initLightbox(); + onProjectsItemLoad(); + refreshLightbox(); +}); + +/** + * On loading new projects via CMS Load + */ +window.fsAttributes = window.fsAttributes || []; +window.fsAttributes.push([ + 'cmsload', + (listInstances) => { + const [listInstance] = listInstances; + + listInstance.on('renderitems', (renderedItems: NodeListOf) => { + projectItemsList = renderedItems; + + onProjectsItemLoad(); + }); + }, +]); + +function onProjectsItemLoad() { + document.querySelectorAll(`${PROJECT_ITEM_SELECTOR}[data-slug]`).forEach((item) => { + initHTMX(item); + + item.addEventListener('click', (clickEv) => { + window.gsap.set(lightboxWrapperEl, { display: 'block' }); + window.gsap.set('body', { overflow: 'hidden' }); + window.gsap.to(lightboxWrapperEl, { opacity: 1, duration: 0.3 }); + }); + }); +} + +/** + * Sets `hx-get` attribute on the project list items, dynamically joining the slug + */ +function initHTMX(item: HTMLElement) { + const slug = item.getAttribute('data-slug'); + const currentGetPath = item.getAttribute('hx-get') || '/projects/'; + item.setAttribute('hx-get', currentGetPath + slug); + + item.removeAttribute('data-slug'); + + htmx.process(item); +} + +function initLightbox() { + window.gsap.set(lightboxWrapperEl, { opacity: 0 }); + + const lightboxContentWrapperEl = lightboxWrapperEl?.querySelector( + LIGHTBOX_CONTENT_WRAPPER_SELECTOR + ); + + lightboxWrapperEl?.addEventListener('click', (clickEv) => { + const target = clickEv.target as HTMLElement; + if (!target.closest(`[${LIGHTBOX_CLOSE_BTN_ATTRIBUTE}]`)) { + return; + } + + window.gsap.to(lightboxWrapperEl, { + opacity: 0, + duration: 0.3, + onComplete: () => { + window.gsap.set(lightboxWrapperEl, { display: 'none' }); + window.gsap.set('body', { overflow: 'auto' }); + + if (lightboxContentWrapperEl) { + lightboxContentWrapperEl.innerHTML = ''; + } + }, + }); + }); +} + +function refreshLightbox() { + htmx.onLoad(function (content) { + window.DEBUG('htmx loaded', content); + refreshFsLightbox(); + }); +} + +export {}; diff --git a/src/components/home-projects-load.ts b/src/components/home-projects-load.ts deleted file mode 100644 index 452f8cc..0000000 --- a/src/components/home-projects-load.ts +++ /dev/null @@ -1,11 +0,0 @@ -window.fsAttributes = window.fsAttributes || []; -window.fsAttributes.push([ - 'cmsload', - (listInstances) => { - const [listInstance] = listInstances; - - listInstance.on('renderitems', (renderedItems) => { - console.log(renderedItems); - }); - }, -]); diff --git a/src/entry.ts b/src/entry.ts index d5e6435..7798f9d 100644 --- a/src/entry.ts +++ b/src/entry.ts @@ -81,6 +81,3 @@ function fetchLocalScripts() { appendScripts(); }); } - -// window.Webflow = window.Webflow || []; -// window.Webflow.push(() => {}); diff --git a/src/pages/home.ts b/src/pages/home.ts index 68f5184..cdff487 100644 --- a/src/pages/home.ts +++ b/src/pages/home.ts @@ -1,4 +1,5 @@ import { initProcessSlider } from 'src/components/home-process-slider'; +import 'src/components/home-projects-gallery'; window.Webflow?.push(() => { initProcessSlider(); From 1358a410aa140810593e6bb93acb8b9059a2181a Mon Sep 17 00:00:00 2001 From: Paras Shah Date: Sun, 10 Mar 2024 11:59:08 +0530 Subject: [PATCH 3/7] Move script loaded event variable to a separate file fixed including the entire file's content in import --- src/components/home-projects-gallery.ts | 2 +- src/constants.ts | 1 + src/entry.ts | 3 +-- 3 files changed, 3 insertions(+), 3 deletions(-) create mode 100644 src/constants.ts diff --git a/src/components/home-projects-gallery.ts b/src/components/home-projects-gallery.ts index 3d3b553..3473db5 100644 --- a/src/components/home-projects-gallery.ts +++ b/src/components/home-projects-gallery.ts @@ -1,5 +1,5 @@ import 'fslightbox'; -import { SCRIPTS_LOADED_EVENT } from 'src/entry'; +import { SCRIPTS_LOADED_EVENT } from 'src/constants'; const PROJECT_ITEM_SELECTOR = '.projects_item-link-wrapper'; const LIGHTBOX_WRAPPER_SELECTOR = '[data-lightbox-wrapper]'; diff --git a/src/constants.ts b/src/constants.ts new file mode 100644 index 0000000..641827b --- /dev/null +++ b/src/constants.ts @@ -0,0 +1 @@ +export const SCRIPTS_LOADED_EVENT = 'scriptsLoaded'; diff --git a/src/entry.ts b/src/entry.ts index 7798f9d..97ca2ec 100644 --- a/src/entry.ts +++ b/src/entry.ts @@ -3,6 +3,7 @@ * Fetches scripts from localhost or production site depending on the setup * Polls `localhost` on page load, else falls back to deriving code from production URL */ +import { SCRIPTS_LOADED_EVENT } from './constants'; import './dev/debug'; import './dev/env'; @@ -12,8 +13,6 @@ const PRODUCTION_BASE = window.JS_SCRIPTS = new Set(); -export const SCRIPTS_LOADED_EVENT = 'scriptsLoaded'; - const SCRIPT_LOAD_PROMISES: Array> = []; // init adding scripts to the page From bad9364306b71c1defce143d2e36a46e24feb483 Mon Sep 17 00:00:00 2001 From: Paras Shah Date: Sun, 10 Mar 2024 12:07:25 +0530 Subject: [PATCH 4/7] Update readme Add info about executing code after all scripts are loaded --- README.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/README.md b/README.md index 04388d9..d0cb429 100644 --- a/README.md +++ b/README.md @@ -47,6 +47,21 @@ Note: The setup won't automatically clean up deleted files that already exist in 5. As changes are made to the code locally and saved, the [localhost:3000](http://localhost:3000) will then serve those files +#### Code execution + +To execute code after all the scripts have loaded, the script loader emits an event listener on the `window` object. This can come in handy when you want to ensure a certain imported library from another script file has loaded before executing it. + +You can use that as following: + + ```html + + ``` + #### Debugging There is an opt-in debugging setup that turns on logs in the console. The preference can be toggled via browser console, and is stored in browser localStorage. From 9ca309b5abe75a3bc83e348d1f8ec8e0c35f20c3 Mon Sep 17 00:00:00 2001 From: Paras Shah Date: Sun, 10 Mar 2024 12:18:45 +0530 Subject: [PATCH 5/7] Optimize gallery lightbox event listener code --- src/components/home-projects-gallery.ts | 33 ++++++++++++++++--------- 1 file changed, 22 insertions(+), 11 deletions(-) diff --git a/src/components/home-projects-gallery.ts b/src/components/home-projects-gallery.ts index 3473db5..255fff9 100644 --- a/src/components/home-projects-gallery.ts +++ b/src/components/home-projects-gallery.ts @@ -1,23 +1,27 @@ import 'fslightbox'; import { SCRIPTS_LOADED_EVENT } from 'src/constants'; +const PROJECTS_LIST_SELECTOR = '[data-projects-list]'; const PROJECT_ITEM_SELECTOR = '.projects_item-link-wrapper'; const LIGHTBOX_WRAPPER_SELECTOR = '[data-lightbox-wrapper]'; const LIGHTBOX_CONTENT_WRAPPER_SELECTOR = '[data-lightbox-content-wrapper]'; const LIGHTBOX_CLOSE_BTN_ATTRIBUTE = 'data-lightbox-close'; let lightboxWrapperEl: HTMLElement | null; +let projectsListEl: HTMLElement | null; let projectItemsList: NodeListOf; window.addEventListener(SCRIPTS_LOADED_EVENT, () => { lightboxWrapperEl = document.querySelector(LIGHTBOX_WRAPPER_SELECTOR); + projectsListEl = document.querySelector(PROJECTS_LIST_SELECTOR); projectItemsList = document.querySelectorAll(PROJECT_ITEM_SELECTOR); - if (!lightboxWrapperEl || !projectItemsList.length) { + if (!lightboxWrapperEl || !projectsListEl || !projectItemsList.length) { window.DEBUG( - 'No lightbox wrapper found or no project items found.', - { lightboxWrapperEl }, - { projectItemsList } + 'One of these elements not found on page - Lightbox wrapper, Projects List, or any Project items.', + 'Looking for', + { LIGHTBOX_WRAPPER_SELECTOR, PROJECTS_LIST_SELECTOR, PROJECT_ITEM_SELECTOR }, + { lightboxWrapperEl, projectsListEl, projectItemsList } ); return; } @@ -47,12 +51,6 @@ window.fsAttributes.push([ function onProjectsItemLoad() { document.querySelectorAll(`${PROJECT_ITEM_SELECTOR}[data-slug]`).forEach((item) => { initHTMX(item); - - item.addEventListener('click', (clickEv) => { - window.gsap.set(lightboxWrapperEl, { display: 'block' }); - window.gsap.set('body', { overflow: 'hidden' }); - window.gsap.to(lightboxWrapperEl, { opacity: 1, duration: 0.3 }); - }); }); } @@ -76,6 +74,19 @@ function initLightbox() { LIGHTBOX_CONTENT_WRAPPER_SELECTOR ); + // open + projectsListEl?.addEventListener('click', (clickEv) => { + const target = clickEv.target as HTMLElement; + if (!target.closest(PROJECT_ITEM_SELECTOR)) { + return; + } + + window.gsap.set(lightboxWrapperEl, { display: 'block' }); + window.gsap.set('body', { overflow: 'hidden' }); + window.gsap.to(lightboxWrapperEl, { opacity: 1, duration: 0.3 }); + }); + + // close lightboxWrapperEl?.addEventListener('click', (clickEv) => { const target = clickEv.target as HTMLElement; if (!target.closest(`[${LIGHTBOX_CLOSE_BTN_ATTRIBUTE}]`)) { @@ -99,7 +110,7 @@ function initLightbox() { function refreshLightbox() { htmx.onLoad(function (content) { - window.DEBUG('htmx loaded', content); + window.DEBUG('htmx content loaded', content); refreshFsLightbox(); }); } From 49198d8d2d1e93b58df96604d3f537457b7304ac Mon Sep 17 00:00:00 2001 From: Paras Shah Date: Sun, 10 Mar 2024 13:59:18 +0530 Subject: [PATCH 6/7] Update hero reveal animation --- src/components/home-hero-image-reveal.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/components/home-hero-image-reveal.ts b/src/components/home-hero-image-reveal.ts index dcd5ca6..ee48399 100644 --- a/src/components/home-hero-image-reveal.ts +++ b/src/components/home-hero-image-reveal.ts @@ -1,16 +1,17 @@ export function homeHeroImageReveal() { const COMPONENT_SELECTOR = '[data-image-reveal-parent]'; + const BLOCKS_SELECTOR = `${COMPONENT_SELECTOR} > div`; const boxes = 10 * 5; - const DURATION_IN_SEC = 3; + const DURATION_IN_SEC = 1; window.gsap.fromTo( - `${COMPONENT_SELECTOR} > div`, + BLOCKS_SELECTOR, { opacity: 1, }, { opacity: 0, - duration: DURATION_IN_SEC / boxes, + duration: 0.3, stagger: { each: DURATION_IN_SEC / boxes, from: 'random', From 1418145964130054a3de5ef93eebe8b2be3075b2 Mon Sep 17 00:00:00 2001 From: Paras Shah Date: Sun, 10 Mar 2024 13:59:32 +0530 Subject: [PATCH 7/7] Add custom cursor on the site --- src/components/cursor.ts | 102 +++++++++++++++++++++++++++++++++++++++ src/global.ts | 2 + src/pages/home.ts | 5 +- 3 files changed, 108 insertions(+), 1 deletion(-) create mode 100644 src/components/cursor.ts diff --git a/src/components/cursor.ts b/src/components/cursor.ts new file mode 100644 index 0000000..8367d5d --- /dev/null +++ b/src/components/cursor.ts @@ -0,0 +1,102 @@ +const CURSOR_OUTER_SELECTOR = '.page-cursor.is-outer'; +const CURSOR_INNER_SELECTOR = '.page-cursor.is-inner'; + +let scaleAnim: gsap.core.Timeline; +let outerX: gsap.QuickToFunc; +let outerY: gsap.QuickToFunc; +let InnerX: gsap.QuickToFunc; +let innerY: gsap.QuickToFunc; + +export function cursorInit() { + if (window.innerWidth < 992) { + return; + } + + window.gsap.set(CURSOR_INNER_SELECTOR, { scale: 0.3 }); + window.gsap.set(`${CURSOR_OUTER_SELECTOR}, ${CURSOR_INNER_SELECTOR}`, { opacity: 1 }); + + document.addEventListener('mousemove', cursorMove); + + outerX = window.gsap.quickTo(CURSOR_OUTER_SELECTOR, 'left', { + duration: 0.2, + ease: 'power3', + }); + outerY = window.gsap.quickTo(CURSOR_OUTER_SELECTOR, 'top', { + duration: 0.2, + ease: 'power3', + }); + + InnerX = window.gsap.quickTo(CURSOR_INNER_SELECTOR, 'left', { + duration: 0.6, + ease: 'power3', + }); + innerY = window.gsap.quickTo(CURSOR_INNER_SELECTOR, 'top', { + duration: 0.6, + ease: 'power3', + }); + + setScaleAnimations(); + setLinksInteraction(); +} + +function setScaleAnimations() { + scaleAnim = window.gsap.timeline({ paused: true }); + + scaleAnim.to( + CURSOR_OUTER_SELECTOR, + { + scale: 0.4, + duration: 0.35, + }, + 0 + ); + scaleAnim.to( + CURSOR_INNER_SELECTOR, + { + opacity: 0, + duration: 0.35, + }, + 0 + ); +} + +function cursorMove(mouseEv: MouseEvent) { + const cursorPosition = { + left: mouseEv.clientX, + top: mouseEv.clientY, + }; + + outerX(cursorPosition.left); + outerY(cursorPosition.top); + InnerX(cursorPosition.left); + innerY(cursorPosition.top); +} + +function setLinksInteraction() { + const TARGET_SELECTORS_LIST = + 'a, [data-cursor-link], .button.is-form-submit, .process_slider_pagination-bullet'; + + document.addEventListener( + 'mouseenter', + (e: MouseEvent) => { + const target = e.target as HTMLElement; + const isTargetMatch = target.matches(TARGET_SELECTORS_LIST); + if (!isTargetMatch) { + return; + } + + window.DEBUG('link hover - cursor scale play'); + scaleAnim.play(); + + target.addEventListener( + 'mouseleave', + (e) => { + window.DEBUG('link hover - cursor scale reverse'); + scaleAnim.reverse(); + }, + { once: true } + ); + }, + true + ); +} diff --git a/src/global.ts b/src/global.ts index 067be9d..84e9ca5 100644 --- a/src/global.ts +++ b/src/global.ts @@ -2,10 +2,12 @@ import { gsap } from 'gsap'; import { ScrollTrigger } from 'gsap/ScrollTrigger'; import { animatedDetailsAccordions } from './components/accordions'; +import { cursorInit } from './components/cursor'; window.gsap = gsap; window.gsap.registerPlugin(ScrollTrigger); window.Webflow?.push(() => { animatedDetailsAccordions(); + cursorInit(); }); diff --git a/src/pages/home.ts b/src/pages/home.ts index cdff487..fcbb317 100644 --- a/src/pages/home.ts +++ b/src/pages/home.ts @@ -1,6 +1,9 @@ +import { homeHeroImageReveal } from 'src/components/home-hero-image-reveal'; import { initProcessSlider } from 'src/components/home-process-slider'; import 'src/components/home-projects-gallery'; +import { SCRIPTS_LOADED_EVENT } from 'src/constants'; -window.Webflow?.push(() => { +window.addEventListener(SCRIPTS_LOADED_EVENT, () => { + homeHeroImageReveal(); initProcessSlider(); });