// SCRIPT V 1.014
gsap.registerPlugin(ScrollTrigger, SplitText);
// Global variable to hold SplitText instances and split state
const splitTextData = {
instances: [], // An array for storing SplitText instances
elements: new Map() // Map to keep track of which elements are already broken
};
// SWUP.JS
// Global object to track loaded scripts
const loadedScripts = {};
// Function to dynamically load a script if it hasn't been loaded yet
function loadScript(src, position = 'head', async = false, scriptId = null) {
// If scriptId is provided and the script is already loaded, skip loading
if (scriptId && loadedScripts[scriptId]) {
return;
}
// Check if the script already exists in the DOM
if (scriptId && document.querySelector(`script[data-script-id="${scriptId}"]`)) {
loadedScripts[scriptId] = true;
return;
}
const script = document.createElement('script');
script.src = src;
if (async) script.async = true;
if (scriptId) script.setAttribute('data-script-id', scriptId);
if (position === 'head') {
document.head.appendChild(script);
} else if (position === 'after-body') {
document.body.appendChild(script);
}
// Mark the script as loaded
if (scriptId) {
loadedScripts[scriptId] = true;
}
}
// Function to load scripts based on the current page URL
function loadScriptsForPage(url) {
// Normalize URL to remove query parameters or hash
const path = url.split('?')[0].split('#')[0];
// Scripts for Home (/) and /portfolio (load after
)
/*if (path === '/' || path === '/portfolio') {
loadScript('https://www.youtube.com/iframe_api', 'after-body', false, 'youtube-iframe-api');
loadScript('https://player.vimeo.com/api/player.js', 'after-body', false, 'vimeo-player');
}*/
// Scripts for /portfolio and /blog (load in
)
if (path === '/portfolio' || path === '/blog') {
loadScript('https://cdn.jsdelivr.net/npm/@finsweet/attributes-cmsfilter@1/cmsfilter.js', 'head',
true, 'finsweet-cmsfilter');
}
// Script for /contact (load in
)
if (path === '/contact') {
loadScript('https://assets.calendly.com/assets/external/widget.js', 'head', true,
'calendly-widget');
}
}
// Function to initialize scripts after page load or transition
function initScriptsForPage(url) {
// Normalize URL to remove query parameters or hash
const path = url.split('?')[0].split('#')[0];
// Initialize YouTube and Vimeo players for Home (/) and /portfolio
if (path === '/' || path === '/portfolio') {
// YouTube IFrame API
if (window.YT && window.YT.Player) {
// Find all YouTube iframes and initialize players
document.querySelectorAll('iframe[src*="youtube.com"]').forEach(iframe => {
new YT.Player(iframe, {
events: {
onReady: (event) => {
// Optional: Add logic for YouTube player readiness
}
}
});
});
}
// Vimeo Player API
if (window.Vimeo && window.Vimeo.Player) {
// Find all Vimeo iframes and initialize players
document.querySelectorAll('iframe[src*="vimeo.com"]').forEach(iframe => {
new Vimeo.Player(iframe);
});
}
}
// Initialize Finsweet CMS Filter for /portfolio and /blog
if (path === '/portfolio' || path === '/blog') {
if (window.fsAttributes && window.fsAttributes.cmsfilter) {
window.fsAttributes.cmsfilter.init();
}
}
// Initialize Calendly for /contact
if (path === '/contact') {
// Wait for Calendly script to load before initializing
const initCalendlyWithRetry = (attempts = 10, delay = 500) => {
if (attempts <= 0) {
console.error('Failed to initialize Calendly: Script did not load in time.');
return;
}
if (window.Calendly) {
// Check for Calendly inline widgets in the DOM
const calendlyWidgets = document.querySelectorAll('[data-calendly]');
if (calendlyWidgets.length) {
// If initInlineWidgets is available, use it; otherwise, assume auto-initialization
if (typeof window.Calendly.initInlineWidgets === 'function') {
window.Calendly.initInlineWidgets();
} else {
console.log(
'Calendly auto-initializes widgets. Ensure data-calendly attributes are set correctly.'
);
}
}
} else {
// Retry after delay if Calendly is not yet loaded
setTimeout(() => initCalendlyWithRetry(attempts - 1, delay), delay);
}
};
// Start the retry process
initCalendlyWithRetry();
}
}
function SwupJS() {
const swup = new Swup({
containers: ['.page-container'],
animationSelector: false
});
let nextPageUrl = null;
swup.hooks.on('visit:start', (visit) => {
if (visit.to && visit.to.url) {
nextPageUrl = visit.to.url;
}
if (lenis) {
lenis.stop();
}
/*// Show loading screen during page transition
const loadingScreen = document.querySelector('.loading-screen-block');
if (loadingScreen) {
gsap.set(loadingScreen, { display: 'block' }); // Show the loading screen
}*/
});
swup.hooks.on('animation:out:start', (visit, args, defaultHandler) => {
return new Promise((resolve) => {
gsap.to('.page-container', {
opacity: 0,
duration: 0.5,
ease: 'power2.out',
onComplete: () => resolve()
});
});
});
swup.hooks.on('content:replace', () => {
const newPageContainer = document.querySelector('.page-container');
gsap.set(newPageContainer, { opacity: 0 }); // Initially set container to invisible
if (nextPageUrl) {
webflowReinit(nextPageUrl);
}
});
function webflowReinit(url) {
if (!url) return;
fetch(url)
.then(response => response.text())
.then(htmlContent => {
let parser = new DOMParser();
let dom = parser.parseFromString(htmlContent, 'text/html');
let webflowPageId = dom.querySelector('html').getAttribute('data-wf-page');
if (webflowPageId) {
document.documentElement.setAttribute('data-wf-page', webflowPageId);
}
setTimeout(() => {
if (window.Webflow) {
window.Webflow.destroy();
window.Webflow.ready();
if (window.Webflow.require && window.Webflow.require('ix2')) {
window.Webflow.require('ix2').init();
}
}
if (window.ScrollTrigger) {
ScrollTrigger.refresh();
}
if (lenis) {
lenis.destroy();
}
Smooth();
loadScriptsForPage(url);
initScriptsForPage(url);
setTimeout(() => {
const newPageContainer = document.querySelector('.page-container');
gsap.set(newPageContainer, { opacity: 1 }); // Make container visible
if (lenis) {
lenis.start();
}
// Hide loading screen and start animations after everything is ready
hideLoadingScreen();
}, 100);
}, 100);
});
}
// Load and initialize scripts for the initial page load
loadScriptsForPage(window.location.pathname);
initScriptsForPage(window.location.pathname);
} // SWUP.JS
// SMOOTH
// Declare lenis globally
let lenis;
function Smooth() {
// Destroy previous Lenis instance if it exists
if (lenis) {
lenis.destroy();
}
// Initialize Lenis with your original settings
lenis = new Lenis({
duration: 0.5,
smoothTouch: false
});
// Attach ScrollTrigger update
lenis.on('scroll', ScrollTrigger.update);
// Attach GSAP ticker for Lenis RAF
gsap.ticker.add((time) => {
lenis.raf(time * 1000);
});
// Ensure lag smoothing is disabled
gsap.ticker.lagSmoothing(0);
// Remove previous keydown event listeners to prevent duplication
$(document).off('keydown.lenis');
// Handle keyboard scrolling with Arrow Up and Arrow Down
$(document).on('keydown.lenis', function (event) {
// Define the scroll step (in pixels)
const scrollStep = 300; // You can adjust this value
const currentScroll = lenis.actualScroll; // Current scroll position
// Arrow Down
if (event.key === 'ArrowDown') {
event.preventDefault(); // Prevent default browser scrolling
lenis.scrollTo(currentScroll + scrollStep, {
duration: 1, // Smooth scroll duration
easing: (t) => 1 - Math.pow(1 - t, 2.5) // Custom easing (cubic ease-out)
});
}
// Arrow Up
if (event.key === 'ArrowUp') {
event.preventDefault(); // Prevent default browser scrolling
lenis.scrollTo(currentScroll - scrollStep, {
duration: 1, // Smooth scroll duration
easing: (t) => 1 - Math.pow(1 - t, 2.5) // Custom easing (cubic ease-out)
});
}
});
} // SMOOTH
// Arrays to store timelines for each animation type
let headingIntoViewTimelines = [];
let textIntoViewTimelines = [];
let buttonIntoViewTimelines = [];
let dropdownArrowTimelines = [];
let separatorLineTimelines = [];
let footerTimelines = [];
let lightboxTimelines = [];
let mediaFormTimelines = [];
let socialIconsTimelines = [];
function IntoView() {
// Media query handling with GSAP matchMedia
const mm = gsap.matchMedia();
// HEADING INTO VIEW
let headingIntoView = $('.text-block .heading').not(
'.section.hero .heading, .section.footer .heading, .cta-block .heading, .menu-overlay-block .heading'
);
if (headingIntoView.length) {
gsap.utils.toArray(headingIntoView).forEach(function (elem) {
const innerSplit = new SplitText(elem, {
type: 'lines',
linesClass: 'text-line'
});
const outerSplit = new SplitText(elem, {
type: 'lines',
linesClass: 'text-mask'
});
// Set initial state (hide lines)
gsap.set(innerSplit.lines, {
y: '150%',
willChange: 'transform'
});
gsap.set(outerSplit.lines, {
overflow: 'hidden',
display: 'block'
});
// Create paused timeline without auto-triggering
const splitTimeline = gsap.timeline({
paused: true,
onComplete: () => {
outerSplit.revert();
innerSplit.revert();
}
});
splitTimeline.to(innerSplit.lines, {
duration: 0.8,
y: 0,
ease: 'Power4.easeOut',
stagger: 0.12
});
// Create ScrollTrigger to track visibility
const scrollTrigger = ScrollTrigger.create({
trigger: elem,
scrub: false,
start: 'top 90%',
end: 'bottom 0%',
});
headingIntoViewTimelines.push({
timeline: splitTimeline,
scrollTrigger: scrollTrigger,
element: elem
});
});
}
// TEXT INTO VIEW
let textIntoView = $('.text-block .text, .text.w-richtext p').not(
'.text.w-richtext, .section.hero .text, .section.hero .text.w-richtext p, .section.footer .text, .cta-block .text, .button-block .text, .form-wrapper .text, .menu-overlay-block .text'
);
if (textIntoView.length) {
gsap.utils.toArray(textIntoView).forEach(function (elem) {
// Fixing empty rows manually
elem.innerHTML = elem.innerHTML.replace(/
/g,
"
");
const innerSplit = new SplitText(elem, {
type: 'lines',
linesClass: 'text-line'
});
const outerSplit = new SplitText(elem, {
type: 'lines',
linesClass: 'text-mask'
});
// Set initial state (hide lines)
gsap.set(innerSplit.lines, {
y: '150%',
willChange: 'transform'
});
gsap.set(outerSplit.lines, {
overflow: 'hidden',
display: 'block'
});
// Create paused timeline
const splitTimeline = gsap.timeline({
paused: true,
onComplete: () => {
outerSplit.revert();
innerSplit.revert();
// Deleting empty lines
elem.querySelectorAll('.empty-line').forEach(line => line.remove());
}
});
splitTimeline.to(innerSplit.lines, {
duration: 0.8,
y: 0,
ease: 'Power4.easeOut',
stagger: 0.12
});
// Create ScrollTrigger to track visibility
const scrollTrigger = ScrollTrigger.create({
trigger: elem,
scrub: false,
start: 'top 90%',
end: 'bottom 0%',
});
textIntoViewTimelines.push({
timeline: splitTimeline,
scrollTrigger: scrollTrigger,
element: elem
});
});
}
// BUTTON INTO VIEW
let buttonIntoView = $('.button-block').not(
'.section.hero .button-block, .navbar-block .button-block, .cta-block .button-block, .section.footer .button-block, .back-to-top-wrapper .button-block'
);
if (buttonIntoView.length) {
gsap.utils.toArray(buttonIntoView).forEach((elem) => {
const buttonText = elem.querySelector('.text');
const buttonLineContainer = elem.querySelector('.button-line-container');
const arrowMask = elem.querySelector('.arrow-mask');
let textLine = null; // Variable for text-line
// Set initial state for elements
if (buttonText) {
// Create text-mask wrapper
const textMask = document.createElement('div');
textMask.className = 'text-mask';
// Create text-line wrapper
textLine = document.createElement('div');
textLine.className = 'text-line';
// Move buttonText content into textLine
while (buttonText.firstChild) {
textLine.appendChild(buttonText.firstChild);
}
// Add textLine to textMask, and textMask to buttonText
textMask.appendChild(textLine);
buttonText.appendChild(textMask);
// Set initial state for textLine
gsap.set(textLine, {
y: '150%',
willChange: 'transform'
});
}
if (buttonLineContainer) {
gsap.set(buttonLineContainer, { scaleX: 0, transformOrigin: 'left' });
}
if (arrowMask) {
gsap.set(arrowMask, { scale: 0, transformOrigin: '0% 100%' });
}
// Create paused timeline
const tl = gsap.timeline({
paused: true
});
if (textLine) {
tl.to(textLine, {
duration: 0.8,
y: 0,
ease: 'Power4.easeOut',
stagger: 0.12,
}, 0);
}
if (buttonLineContainer) {
tl.to(buttonLineContainer, {
duration: 0.7,
scaleX: 1,
ease: 'Power3.easeOut',
}, 0.15);
}
if (arrowMask) {
tl.to(arrowMask, {
duration: 0.7,
scale: 1,
ease: 'Power4.easeOut',
}, 0.15);
}
// Create ScrollTrigger to track visibility
const scrollTrigger = ScrollTrigger.create({
trigger: elem,
scrub: false,
start: 'top 90%',
end: 'bottom 0%',
});
buttonIntoViewTimelines.push({
timeline: tl,
scrollTrigger: scrollTrigger,
element: elem
});
});
}
// ARROWS.02
gsap.set('.vector.arrow._02', { display: 'flex', width: '0%', height: '0%' });
// FOOTER ANIMATION ON SCROLL
let footerSection = $('.section.footer');
if (footerSection.length) {
gsap.utils.toArray(footerSection).forEach(function (elem) {
// Select all text elements to animate
let footerText = $(elem).find('.text, .button-text .text, .text._14px');
let footerSocialIcons = $(elem).find('.vector.social');
let footerButtonBlocks = $(elem).find('.button-block');
// Split text into lines using SplitText
let footerTextSpitted = false;
function splitFooterText() {
gsap.utils.toArray(footerText).forEach(function (textElem) {
textElem.innerHTML = textElem.innerHTML.replace(/
/g,
"
");
const innerSplit = new SplitText(textElem, {
type: 'lines',
linesClass: 'text-line'
});
const outerSplit = new SplitText(textElem, {
type: 'lines',
linesClass: 'text-mask'
});
gsap.set(innerSplit.lines, {
y: '150%',
willChange: 'transform'
});
gsap.set(outerSplit.lines, {
overflow: 'hidden',
display: 'block'
});
});
footerTextSpitted = true;
}
// Initial setup for social icons and button lines
gsap.set(footerSocialIcons, { opacity: 0, scale: 0.5 });
gsap.set(footerButtonBlocks.find('.button-line-container'), {
scaleX: 0,
transformOrigin: 'left'
});
// Split text if not already done
if (!footerTextSpitted) {
splitFooterText();
}
// Create paused timeline
const tl = gsap.timeline({
paused: true
});
tl.to($(elem).find('.text-line'), {
duration: 0.8,
y: 0,
ease: 'Power4.easeOut',
stagger: 0.1
}, 0)
.to(footerButtonBlocks.find('.button-line-container'), {
duration: 0.5,
scaleX: 1,
ease: 'Power3.easeOut',
stagger: 0.1
}, 0.2)
.to(footerSocialIcons, {
duration: 0.5,
opacity: 1,
scale: 1,
ease: 'Power2.easeOut',
stagger: 0.05
}, 0.3);
// Create ScrollTrigger to track visibility
const scrollTrigger = ScrollTrigger.create({
trigger: elem,
start: 'top 90%'
});
footerTimelines.push({ timeline: tl, scrollTrigger: scrollTrigger, element: elem });
});
}
/* DROPDOWN ARROWS INTO VIEW */
const dropdownArrowBlock = $('.dropdown-arrow-block');
gsap.utils.toArray(dropdownArrowBlock).forEach((elem) => {
const dropdownArrowMask = $('.arrow-mask', elem);
// Set initial state
gsap.set(dropdownArrowMask, { scale: 0, transformOrigin: '100% 100%' });
// Create paused timeline
const tl = gsap.timeline({
paused: true
});
tl.to(dropdownArrowMask, {
duration: 0.8,
scale: 1,
ease: 'Power4.easeOut',
}, 0);
// Create ScrollTrigger to track visibility
const scrollTrigger = ScrollTrigger.create({
trigger: elem,
scrub: false,
start: 'top 90%',
end: 'bottom 0%'
});
dropdownArrowTimelines.push({ timeline: tl, scrollTrigger: scrollTrigger, element: elem });
});
// SEPARATOR LINES INTO VIEW
let separatorLine = $('.separator-line').not(
'.section.hero .separator-line, .navbar-block .separator-line');
if (separatorLine.length) {
gsap.utils.toArray(separatorLine).forEach(function (elem) {
// Set initial state
gsap.set(elem, { width: '0%' });
// Create paused timeline
const tl = gsap.timeline({
paused: true
});
tl.to(elem, {
duration: 1.2,
width: '100%',
ease: 'power4'
});
// Create ScrollTrigger to track visibility
const scrollTrigger = ScrollTrigger.create({
trigger: elem,
scrub: false,
start: 'top 90%',
end: 'bottom 0%',
});
separatorLineTimelines.push({
timeline: tl,
scrollTrigger: scrollTrigger,
element: elem
});
});
}
// FOOTER INTO VIEW
if (footerSection.length) {
gsap.utils.toArray(footerSection).forEach(function (elem) {
// Select all text elements to animate
let footerText = $(elem).find('.text, .button-text .text, .text._14px');
let footerSocialIcons = $(elem).find('.vector.social');
let footerButtonBlocks = $(elem).find('.button-block');
// Split text into lines using SplitText
let footerTextSpitted = false;
function splitFooterText() {
gsap.utils.toArray(footerText).forEach(function (textElem) {
textElem.innerHTML = textElem.innerHTML.replace(/
/g,
"
");
const innerSplit = new SplitText(textElem, {
type: 'lines',
linesClass: 'text-line'
});
const outerSplit = new SplitText(textElem, {
type: 'lines',
linesClass: 'text-mask'
});
gsap.set(innerSplit.lines, {
y: '150%',
willChange: 'transform'
});
gsap.set(outerSplit.lines, {
overflow: 'hidden',
display: 'block'
});
});
footerTextSpitted = true;
}
// Initial setup for social icons and button lines
gsap.set(footerSocialIcons, { opacity: 0, scale: 0.5 });
gsap.set(footerButtonBlocks.find('.button-line-container'), {
scaleX: 0,
transformOrigin: 'left'
});
// Split text if not already done
if (!footerTextSpitted) {
splitFooterText();
}
// Create paused timeline
const tl = gsap.timeline({
paused: true
});
// Animate non-button text (address and "Social links:")
let nonButtonTextLines = $(elem).find('.text-line').not(footerButtonBlocks.find(
'.text-line'));
tl.to(nonButtonTextLines, {
duration: 0.8,
y: 0,
ease: 'Power4.easeOut',
stagger: 0.1
}, 0);
// Animate button text and lines together
footerButtonBlocks.each(function (index) {
const buttonTextLine = $(this).find('.text-line');
const buttonLineContainer = $(this).find('.button-line-container');
tl.to(buttonTextLine, {
duration: 0.8,
y: 0,
ease: 'Power4.easeOut'
}, 0.3 + index * 0.1)
.to(buttonLineContainer, {
duration: 0.5,
scaleX: 1,
ease: 'Power3.easeOut'
}, 0.3 + index * 0.1 + 0.2);
});
// Animate social icons
tl.to(footerSocialIcons, {
duration: 0.5,
opacity: 1,
scale: 1,
ease: 'Power2.easeOut',
stagger: 0.05
}, 1.0);
// Create ScrollTrigger to track visibility
const scrollTrigger = ScrollTrigger.create({
trigger: elem,
start: 'top 90%'
});
footerTimelines.push({ timeline: tl, scrollTrigger: scrollTrigger, element: elem });
});
}
// LIGHTBOX INTO VIEW
const lightboxWrapper = $('.showreel-lightbox-wrapper').not(
'.menu-overlay-block .showreel-lightbox-wrapper'
);;
if (lightboxWrapper.length) {
gsap.utils.toArray(lightboxWrapper).forEach(function (elem) {
const lightboxBlock = $(elem).find('.showreel-lightbox-block');
const playButton = $(elem).find('.play-button');
const lightboxText = $(elem).find('.text');
const innerLightboxText = $(elem).find('.showreel-lightbox-block .text');
let lightboxTextSpitted = false;
function splitLightboxText() {
gsap.utils.toArray(lightboxText).forEach(function (textElem) {
textElem.innerHTML = textElem.innerHTML.replace(/
/g,
"
");
const innerSplit = new SplitText(textElem, {
type: 'lines',
linesClass: 'text-line'
});
const outerSplit = new SplitText(textElem, {
type: 'lines',
linesClass: 'text-mask'
});
gsap.set(innerSplit.lines, {
y: '150%',
willChange: 'transform'
});
gsap.set(outerSplit.lines, {
overflow: 'hidden',
display: 'block'
});
});
gsap.utils.toArray(innerLightboxText).forEach(function (textElem) {
textElem.innerHTML = textElem.innerHTML.replace(/
/g,
"
div>
");
const innerSplit = new SplitText(textElem, {
type: 'lines',
linesClass: 'text-line'
});
const outerSplit = new SplitText(textElem, {
type: 'lines',
linesClass: 'text-mask'
});
gsap.set(innerSplit.lines, {
y: '150%',
willChange: 'transform'
});
gsap.set(outerSplit.lines, {
overflow: 'hidden',
display: 'block'
});
});
lightboxTextSpitted = true;
}
gsap.set(lightboxBlock, { opacity: 0, y: '10%' });
gsap.set(playButton, { scale: 0.5 });
if (!lightboxTextSpitted) {
splitLightboxText();
}
const tl = gsap.timeline({
paused: true,
onComplete: () => {
lightboxText.each(function () {
$(this).find('.empty-line').remove();
});
innerLightboxText.each(function () {
$(this).find('.empty-line').remove();
});
}
});
tl.to(lightboxBlock, { duration: 0.8, opacity: 1, y: 0, ease: 'Power2.easeOut' }, 0)
.to(playButton, { duration: 0.8, scale: 1, ease: 'Power2.easeOut' }, 0)
.to($(elem).find('.text-line'), {
duration: 0.8,
y: 0,
ease: 'Power4.easeOut',
stagger: 0.12
}, 0.2);
const scrollTrigger = ScrollTrigger.create({
trigger: elem,
scrub: false,
start: 'top 90%',
end: 'bottom 0%'
});
lightboxTimelines.push({ timeline: tl, scrollTrigger: scrollTrigger, element: elem });
});
}
// MEDIA AND FORM BLOCKS INTO VIEW
// Select only .image-block .image and .video-block elements for animation
/*const mediaBlocks = $('.image-block .image, .video-block');
// Check if there are any elements to animate
if (mediaBlocks.length) {
// Array to store timelines for delayed playback
const mediaFormTimelines = [];
// Loop through each element to set up the animation
gsap.utils.toArray(mediaBlocks).forEach(function (elem) {
// Cache the jQuery element for reuse
const $elem = $(elem);
// Get the dimensions of the element for SVG sizing
const width = $elem.width();
const height = $elem.height();
// Define SVG namespace for creating SVG elements
const svgNS = "http://www.w3.org/2000/svg";
// Create an SVG wrapper for the mask
const svg = document.createElementNS(svgNS, "svg");
svg.setAttribute("width", width);
svg.setAttribute("height", height);
svg.style.position = "absolute";
svg.style.top = "0";
svg.style.left = "0";
// Create a mask element for the SVG
const mask = document.createElementNS(svgNS, "mask");
// Generate a unique ID for the mask to avoid conflicts
mask.setAttribute("id", "reveal-mask-" + Math.random().toString(36).substr(2, 9));
const rect = document.createElementNS(svgNS, "rect");
rect.setAttribute("width", width);
rect.setAttribute("height", height);
rect.setAttribute("fill", "white");
// Create a path for the animation (a rectangle that follows the element's perimeter)
const path = document.createElementNS(svgNS, "path");
// Define the path as a rectangle that matches the element's dimensions
const pathD = `M0,0 H${width} V${height} H0 V0 Z`;
path.setAttribute("d", pathD);
path.setAttribute("fill", "none");
path.setAttribute("stroke", "black");
path.setAttribute("stroke-width", "2");
// Append the rectangle and path to the mask
mask.appendChild(rect);
mask.appendChild(path);
// Append the mask to the SVG
svg.appendChild(mask);
// Create a wrapper div to position the SVG and element
const wrapper = document.createElement("div");
wrapper.style.position = "relative";
wrapper.style.width = width + "px";
wrapper.style.height = height + "px";
// Wrap the element with the wrapper div
$elem.wrap(wrapper);
// Insert the SVG after the element
$elem.after(svg);
// Apply the mask to the element using CSS
$elem.css({
"-webkit-mask": `url(#${mask.id})`,
"mask": `url(#${mask.id})`,
"opacity": 1 // Ensure the element is initially visible
});
// Calculate the total length of the path for animation
const pathLength = path.getTotalLength();
// Set the stroke dash array to the path length for the drawing effect
path.setAttribute("stroke-dasharray", pathLength);
// Initially offset the stroke to hide the path
path.setAttribute("stroke-dashoffset", pathLength);
// Create a GSAP timeline for the animation, initially paused
const tl = gsap.timeline({ paused: true });
// Animate the strokeDashoffset to reveal the element
tl.to(path, {
strokeDashoffset: 0,
duration: 1.5,
ease: "power2.inOut"
});
// Create ScrollTrigger to track visibility (but not to trigger animation directly)
const scrollTrigger = ScrollTrigger.create({
trigger: $elem.parent()[0], // Use the wrapper as the trigger
scrub: false,
start: "top 90%",
end: "bottom 0%"
});
// Push the timeline and scrollTrigger to the array
mediaFormTimelines.push({
timeline: tl,
scrollTrigger: scrollTrigger,
element: elem
});
});
};*/
// SOCIAL ICONS INTO VIEW
const socialLinkLists = $('.social-link-list').not(
'.menu-overlay-block .social-link-list, .section.footer .social-link-list');
if (socialLinkLists.length) {
gsap.utils.toArray(socialLinkLists).forEach(function (list) {
const socialIconsInList = $(list).find('.vector.social');
if (socialIconsInList.length) {
gsap.set(socialIconsInList, { opacity: 0, scale: 0.5 });
const tl = gsap.timeline({
paused: true
});
tl.to(socialIconsInList, {
duration: 0.8,
opacity: 1,
scale: 1,
ease: 'Power2.easeOut',
stagger: 0.08
});
const scrollTrigger = ScrollTrigger.create({
trigger: list, // Тригером є весь блок .social-link-list
scrub: false,
start: 'top 90%',
end: 'bottom 0%'
});
socialIconsTimelines.push({
timeline: tl,
scrollTrigger: scrollTrigger,
element: list
});
}
});
}
} // INTO VIEW
// INTO VIEW ANIMATIONS START
function IntoViewAnimationsStart() {
// Heading Into View Start
if (headingIntoViewTimelines.length) {
headingIntoViewTimelines.forEach(({ timeline, element }) => {
const rect = element.getBoundingClientRect();
const windowHeight = window.innerHeight || document.documentElement.clientHeight;
const isInViewport = rect.top <= windowHeight * 0.9;
if (isInViewport) {
timeline.play();
} else {
const onScroll = () => {
const updatedRect = element.getBoundingClientRect();
if (updatedRect.top <= windowHeight * 0.9) {
timeline.play();
window.removeEventListener('scroll', onScroll);
}
};
window.addEventListener('scroll', onScroll);
}
});
}
// Text Into View Start
if (textIntoViewTimelines.length) {
textIntoViewTimelines.forEach(({ timeline, element }) => {
const rect = element.getBoundingClientRect();
const windowHeight = window.innerHeight || document.documentElement.clientHeight;
const isInViewport = rect.top <= windowHeight * 0.9;
if (isInViewport) {
timeline.play();
} else {
const onScroll = () => {
const updatedRect = element.getBoundingClientRect();
if (updatedRect.top <= windowHeight * 0.9) {
timeline.play();
window.removeEventListener('scroll', onScroll);
}
};
window.addEventListener('scroll', onScroll);
}
});
}
// Button Into View Start
if (buttonIntoViewTimelines.length) {
buttonIntoViewTimelines.forEach(({ timeline, element }) => {
const rect = element.getBoundingClientRect();
const windowHeight = window.innerHeight || document.documentElement.clientHeight;
const isInViewport = rect.top <= windowHeight * 0.9;
if (isInViewport) {
timeline.play();
} else {
const onScroll = () => {
const updatedRect = element.getBoundingClientRect();
if (updatedRect.top <= windowHeight * 0.9) {
timeline.play();
window.removeEventListener('scroll', onScroll);
}
};
window.addEventListener('scroll', onScroll);
}
});
}
// Dropdown Arrow Into View Start
if (dropdownArrowTimelines.length) {
dropdownArrowTimelines.forEach(({ timeline, element }) => {
const rect = element.getBoundingClientRect();
const windowHeight = window.innerHeight || document.documentElement.clientHeight;
const isInViewport = rect.top <= windowHeight * 0.9;
if (isInViewport) {
timeline.play();
} else {
const onScroll = () => {
const updatedRect = element.getBoundingClientRect();
if (updatedRect.top <= windowHeight * 0.9) {
timeline.play();
window.removeEventListener('scroll', onScroll);
}
};
window.addEventListener('scroll', onScroll);
}
});
}
// Separator Line Into View Start
if (separatorLineTimelines.length) {
separatorLineTimelines.forEach(({ timeline, element }) => {
const rect = element.getBoundingClientRect();
const windowHeight = window.innerHeight || document.documentElement.clientHeight;
const isInViewport = rect.top <= windowHeight * 0.9;
if (isInViewport) {
timeline.play();
} else {
const onScroll = () => {
const updatedRect = element.getBoundingClientRect();
if (updatedRect.top <= windowHeight * 0.9) {
timeline.play();
window.removeEventListener('scroll', onScroll);
}
};
window.addEventListener('scroll', onScroll);
}
});
}
// Footer Into View Start
if (footerTimelines.length) {
footerTimelines.forEach(({ timeline, element }) => {
const rect = element.getBoundingClientRect();
const windowHeight = window.innerHeight || document.documentElement.clientHeight;
const isInViewport = rect.top <= windowHeight * 0.9;
if (isInViewport) {
timeline.play();
} else {
const onScroll = () => {
const updatedRect = element.getBoundingClientRect();
if (updatedRect.top <= windowHeight * 0.9) {
timeline.play();
window.removeEventListener('scroll', onScroll);
}
};
window.addEventListener('scroll', onScroll);
}
});
}
// Lightbox Into View Start
if (lightboxTimelines.length) {
lightboxTimelines.forEach(({ timeline, element }) => {
const rect = element.getBoundingClientRect();
const windowHeight = window.innerHeight || document.documentElement.clientHeight;
const isInViewport = rect.top <= windowHeight * 0.9;
if (isInViewport) {
timeline.play();
} else {
const onScroll = () => {
const updatedRect = element.getBoundingClientRect();
if (updatedRect.top <= windowHeight * 0.9) {
timeline.play();
window.removeEventListener('scroll', onScroll);
}
};
window.addEventListener('scroll', onScroll);
}
});
}
// Media and Forms Into View Start
if (mediaFormTimelines.length) {
mediaFormTimelines.forEach(({ timeline, element }) => {
const rect = element.getBoundingClientRect();
const windowHeight = window.innerHeight || document.documentElement.clientHeight;
const isInViewport = rect.top <= windowHeight * 0.9;
if (isInViewport) {
timeline.play();
} else {
const onScroll = () => {
const updatedRect = element.getBoundingClientRect();
if (updatedRect.top <= windowHeight * 0.9) {
timeline.play();
window.removeEventListener('scroll', onScroll);
}
};
window.addEventListener('scroll', onScroll);
}
});
}
// Social Icons Into View Start
if (socialIconsTimelines.length) {
socialIconsTimelines.forEach(({ timeline, element }) => {
const rect = element.getBoundingClientRect();
const windowHeight = window.innerHeight || document.documentElement.clientHeight;
const isInViewport = rect.top <= windowHeight * 0.9;
if (isInViewport) {
timeline.play();
} else {
const onScroll = () => {
const updatedRect = element.getBoundingClientRect();
if (updatedRect.top <= windowHeight * 0.9) {
timeline.play();
window.removeEventListener('scroll', onScroll);
}
};
window.addEventListener('scroll', onScroll);
}
});
}
} // INTO VIEW ANIMATIONS START
// INTERACTIONS
function Interactions() {
// Media query handling with GSAP matchMedia
const mm = gsap.matchMedia();
// MODE CLASSES CLEAN
let darkModeClasses = $('.dark-mode');
let lightModeClasses = $('.light-mode');
darkModeClasses.removeClass('dark-mode');
lightModeClasses.removeClass('light-mode');
// COLOR THEME
$("[data-animate-theme-to]").each(function () {
let theme = $(this).attr("data-animate-theme-to");
ScrollTrigger.create({
trigger: $(this),
start: "top center",
end: "bottom center",
//markers: true,
onToggle: ({ self, isActive }) => {
if (isActive) gsap.to("body", { ...colorThemes.getTheme(theme) });
}
});
});
// NAVBAR BG AND LOGO ANIMATION
const navbarBGwrapper = $('.navbar-bg-wrapper');
const navbarBlur = navbarBGwrapper.find('.navbar-blur');
// Select the logo text elements in reverse order (3rd to 1st) for scroll animation
const logoTextElementsForScroll = [
document.querySelector('.logo_text._03'),
document.querySelector('.logo_text._02'),
document.querySelector('.logo_text._01')
];
// Select the logo text elements in normal order (1st to 3rd) for menu open animation
const logoTextElementsForMenuOpen = [
document.querySelector('.logo_text._01'),
document.querySelector('.logo_text._02'),
document.querySelector('.logo_text._03')
];
// Run animation only if all elements exist
if (navbarBGwrapper.length && navbarBlur.length && logoTextElementsForScroll.every(el => el)) {
// --- Navbar Blur Animation ---
// Create a timeline for the blur effect with ScrollTrigger (scrubbed)
let blurTl = gsap.timeline({
scrollTrigger: {
trigger: '.main',
start: 'top top',
end: '400px top',
scrub: true, // Blur animates gradually from 0 to 400px
},
});
// Add navbar blur animation
blurTl.fromTo(navbarBlur, { backdropFilter: 'blur(0px)' }, {
backdropFilter: 'blur(10px)',
duration: 0.5
}, 0);
// --- Logo Animation ---
// Initial setup for logo letters (start at their normal position)
gsap.set(logoTextElementsForScroll, { x: '0%' });
// Create a separate animation for the logo (triggered at 400px, no scrub)
gsap.to(logoTextElementsForScroll, {
x: '-120%',
duration: 0.5, // Total duration of 0.5 seconds
ease: 'Power3.easeInOut',
stagger: 0.08, // Stagger of 0.1 seconds between each letter
scrollTrigger: {
trigger: '.main',
start: '400px top', // Start exactly at 400px
toggleActions: 'play none none reverse', // Play on enter, reverse on leave back
},
});
}
// HANDLE ENTER KEY PRESS TO SIMULATE CLICK
$(document).on('keydown', function (event) {
if (event.keyCode === 13) { // 13 is the keycode for Enter
const activeElement = document.activeElement;
if (activeElement) {
$(activeElement).trigger('click'); // Use jQuery to trigger the click
}
}
});
// MENU OVERLAY
let menuOpened = false;
const menuOverlayBlock = $('.menu-overlay-block');
const menuOverlayContainer = $('.menu-overlay-container', menuOverlayBlock);
const menuOverlayBG = $('.menu-overlay-bg', menuOverlayBlock);
const menuButton = $('.nav-menu-button');
const menuButtonLine01BG = $('.menu-button-line._01 .menu-button-line-bg', menuButton);
const menuButtonLine02BG = $('.menu-button-line._02 .menu-button-line-bg', menuButton);
const menuButtonLine03BG = $('.menu-button-line._03 .menu-button-line-bg', menuButton);
const menuButtonLine04BG = $('.menu-button-line._04 .menu-button-line-bg', menuButton);
const menuSocialIcons = menuOverlayBlock.find('.vector.social');
const menuShowreelBlock = menuOverlayBlock.find('.showreel-lightbox-block');
const menuPlayButton = menuShowreelBlock.find('.play-button');
const menuText = menuOverlayBlock.find('.text, .menu-overlay-links .button-block .text');
let menuTimeline = null;
// Main content container (including footer)
const mainContainer = $('.main, footer');
// Focusable elements in the main container and footer
const mainFocusableElements = $('.main, footer').find(
'a, button, input, textarea, select, [tabindex="0"]').not('.menu-overlay-block *');
// Focusable elements inside the menu
const insideMenuFocusableElements = menuOverlayBlock.find(
'a, .social-link-block, .play-button');
// Initial setup for elements
gsap.set(menuOverlayBlock, { display: 'none' }, 0);
gsap.set(menuOverlayContainer, { pointerEvents: 'none' }, 0);
gsap.set(menuOverlayBG, { opacity: 0 }, 0);
gsap.set(menuSocialIcons, { opacity: 0, scale: 0.5 }, 0);
gsap.set(menuShowreelBlock, { opacity: 0, y: '10%' }, 0);
gsap.set(menuPlayButton, { scale: 0.5 }, 0);
// Split text into lines for animation
function menuSplitText() {
gsap.utils.toArray(menuText).forEach(function (elem) {
// Check if the element has already been split
if (splitTextData.elements.has(elem)) {
return; // Skip if element is already split
}
// Mark the element as split
splitTextData.elements.set(elem, true);
// Replace double
with an empty line div for spacing
elem.innerHTML = elem.innerHTML.replace(/
/g,
"
");
// Split the text only once
const split = new SplitText(elem, {
type: 'lines',
linesClass: 'text-line'
});
// Add mask wrapper manually via jQuery
$(split.lines).wrap('
');
// Store the SplitText instance
splitTextData.instances.push(split);
gsap.set(split.lines, {
y: '150%',
willChange: 'transform'
});
gsap.set('.text-mask', {
overflow: 'hidden',
display: 'block'
});
});
}
// Perform text splitting immediately upon initialization
menuSplitText();
// Function to disable focus and interactions on the main content and footer
function disableMainContent() {
mainContainer.css('pointer-events', 'none');
mainFocusableElements.each(function () {
const $el = $(this);
$el.data('original-tabindex', $el.attr('tabindex') || 0);
$el.attr('tabindex', '-1').attr('aria-hidden', 'true');
});
insideMenuFocusableElements.each(function () {
$(this).attr('tabindex', '0').removeAttr('aria-hidden');
});
}
// Function to restore focus and interactions on the main content and footer
function enableMainContent() {
mainContainer.css('pointer-events', 'auto');
mainFocusableElements.each(function () {
const $el = $(this);
const originalTabindex = $el.data('original-tabindex') || 0;
$el.attr('tabindex', originalTabindex).removeAttr('aria-hidden');
});
insideMenuFocusableElements.each(function () {
$(this).attr('tabindex', '-1').attr('aria-hidden', 'true');
});
}
// Animation for opening the menu
function menuOpens() {
if (menuTimeline) {
menuTimeline.kill();
}
menuTimeline = gsap.timeline({
onComplete: () => {
menuText.each(function () {
$(this).find('.empty-line').remove();
});
}
});
gsap.set(menuOverlayBlock, { display: 'flex' }, 0);
// Reset element states before animation
gsap.set(menuOverlayBlock.find('.text-line'), { y: '150%' });
gsap.set(menuSocialIcons, { opacity: 0, scale: 0.5 });
gsap.set(menuShowreelBlock, { opacity: 0, y: '10%' });
gsap.set(menuPlayButton, { scale: 0.5 });
menuTimeline
.to(menuOverlayBG, { duration: 0.5, opacity: 1, ease: 'Power1.easeInOut' }, 0)
.to(menuButtonLine01BG, { duration: 0.3, width: '0%', ease: 'Power2.easeInOut' }, 0)
.to(menuButtonLine02BG, { duration: 0.3, width: '0%', ease: 'Power2.easeInOut' }, 0.1)
.to(menuButtonLine03BG, { duration: 0.3, width: '100%', ease: 'Power2.easeInOut' }, 0.4)
.to(menuButtonLine04BG, { duration: 0.3, width: '100%', ease: 'Power2.easeInOut' }, 0.5)
.to(menuOverlayBlock.find('.text-line'), {
duration: 0.8,
y: 0,
ease: 'Power4.easeOut',
stagger: 0.1
}, 0.5)
.to(menuSocialIcons, {
duration: 0.8,
opacity: 1,
scale: 1,
ease: 'Power2.easeOut',
stagger: 0.08
}, 1.3)
.to(menuShowreelBlock, { duration: 0.8, opacity: 1, y: 0, ease: 'Power2.easeOut' }, 1.4)
.to(menuPlayButton, { duration: 0.8, scale: 1, ease: 'Power2.easeOut' }, 1.4)
.set(menuOverlayContainer, { pointerEvents: 'auto' }, 1.5)
.add(() => {
disableMainContent();
}, 0);
if (logoTextElementsForMenuOpen.every(el => el)) {
menuTimeline.to(logoTextElementsForMenuOpen, {
x: '0%',
duration: 0.5,
ease: 'Power3.easeInOut',
stagger: 0.08
}, 0);
}
menuOpened = !menuOpened;
lenis.stop();
}
// Animation for closing the menu
function menuCloses() {
if (menuTimeline) {
menuTimeline.kill();
}
menuTimeline = gsap.timeline({
onComplete: () => {
menuText.each(function () {
$(this).find('.empty-line').remove();
});
enableMainContent();
}
});
menuTimeline
.to(menuOverlayBlock.find('.text-line'), {
duration: 0.5,
y: '150%',
ease: 'Power4.easeIn'
},
0)
.to(menuSocialIcons, { duration: 0.5, opacity: 0, scale: 0.5, ease: 'Power4.easeIn' }, 0)
.to(menuShowreelBlock, { duration: 0.5, opacity: 0, y: '10%', ease: 'Power4.easeIn' }, 0)
.to(menuPlayButton, { duration: 0.5, scale: 0.5, ease: 'Power4.easeIn' }, 0)
.to(menuOverlayBG, { duration: 0.5, opacity: 0, ease: 'Power1.easeInOut' }, 0.5)
.to(menuButtonLine03BG, { duration: 0.3, width: '0%', ease: 'Power2.easeInOut' }, 0)
.to(menuButtonLine04BG, { duration: 0.3, width: '0%', ease: 'Power2.easeInOut' }, 0.1)
.to(menuButtonLine01BG, { duration: 0.3, width: '100%', ease: 'Power2.easeInOut' }, 0.4)
.to(menuButtonLine02BG, { duration: 0.3, width: '100%', ease: 'Power2.easeInOut' }, 0.5)
.set(menuOverlayBlock, { display: 'none' }, 1)
.set(menuOverlayContainer, { pointerEvents: 'none' }, 0);
if (logoTextElementsForScroll.every(el => el)) {
const scrollPosition = window.scrollY || window.pageYOffset;
if (scrollPosition >= 400) {
menuTimeline.to(logoTextElementsForScroll, {
x: '-120%',
duration: 0.5,
ease: 'Power3.easeInOut',
stagger: 0.1
}, 0);
}
}
menuOpened = !menuOpened;
lenis.start();
}
// Handle menu button click to toggle open/close
$(menuButton).click(function () {
if (!menuOpened) {
menuOpens();
} else {
menuCloses();
}
});
// Handle ESC key press to toggle menu
$(document).on('keydown', function (event) {
if (event.keyCode === 27) {
if (!isVideoOverlayOpen) {
if (!menuOpened) {
menuOpens();
} else {
menuCloses();
}
}
}
});
// Handle click on menu buttons to close the menu
const menuButtons = menuOverlayBlock.find('.menu-overlay-links .button-block');
$(menuButtons).click(function () {
if (menuOpened) {
menuCloses();
}
});
// Handle click on logo link to close the menu if open
const logoLink = $('.brand.top'); // Find the logo link by updated class selector
$(logoLink).click(function (e) {
if (menuOpened) {
menuCloses();
}
// If menu is not open, default navigation will occur (handled by Swup)
});
// CTA SHOWREEL REPLACE
let ctaSpanReplace = $('.cta-showreel-span-peplace');
if (ctaSpanReplace.length) {
let showreelLightboxBlock = $('.showreel-lightbox-block.cta');
if (showreelLightboxBlock.length) {
ctaSpanReplace.replaceWith(
showreelLightboxBlock);
}
}
// CTA BLOCK WHILE SCROLLING
let ctaBlock = $('.cta-block');
if (ctaBlock.length) {
gsap.utils.toArray(ctaBlock).forEach(function (elem) {
let ctaShapeImage = $(elem).find(
'.image.cta-shape'); // Corrected selector to find the image
const tl = gsap.timeline({
scrollTrigger: {
trigger: elem,
scrub: true,
start: 'top 100%',
end: 'bottom 0%'
}
});
tl.fromTo(ctaShapeImage, {
scale: 0.75,
ease: 'linear'
}, {
scale: 1.1
});
});
}
// FEATURED PROJECTS OVERLAPPING
mm.add("(min-width: 1281px)", () => {
const featuredWrapper = $('.featured-projects-wrapper');
if (featuredWrapper.length) {
const title = featuredWrapper.find('.text-block.featured-projects');
const featuredProjectBlocks = featuredWrapper.find('.featured-project-block');
const lastFeaturedProjectBlock = featuredProjectBlocks.last();
let calculateEnd = () => {
let lastFeaturedBlockCenterOffset = lastFeaturedProjectBlock.offset().top - title
.offset().top +
lastFeaturedProjectBlock.outerHeight() / 2 - title.outerHeight() / 2;
return `+=${lastFeaturedBlockCenterOffset}`;
};
let tl = gsap.timeline({
scrollTrigger: {
id: 'featured-projects-pin',
trigger: title,
start: 'center center',
end: calculateEnd,
pin: true
}
});
featuredProjectBlocks.each((index, block) => {
const $block = $(block);
const $videoBlock = $block.find('.video-block');
const $scaleVideoFilter = $block.find('.video-scale-filter');
const $playButtonArea = $block.find('.play-button-area');
let calculateBlockEnd = () => {
let lastFeaturedBlockCenterOffset = lastFeaturedProjectBlock.offset().top -
$block.offset().top +
lastFeaturedProjectBlock.outerHeight() / 2 - $block.outerHeight() / 2;
return `+=${lastFeaturedBlockCenterOffset}`;
};
ScrollTrigger.create({
trigger: $block,
start: 'center center',
end: calculateBlockEnd,
pin: true,
pinSpacing: false
});
if (index < featuredProjectBlocks.length - 1) {
let nextBlock = featuredProjectBlocks.eq(index + 1);
let calculateScaleEnd = () => {
let offset = nextBlock.offset().top - $block.offset().top;
return `+=${offset}`;
};
let scaleTl = gsap.timeline({
scrollTrigger: {
trigger: $block,
start: 'center center',
end: calculateScaleEnd,
scrub: true
}
}, 0)
.to($videoBlock, {
scale: 0.8,
ease: 'linear'
}, 0)
.to($scaleVideoFilter, {
opacity: 0.75,
ease: 'linear'
}, 0)
.to($playButtonArea, {
scale: 1.22,
ease: 'linear'
}, 0);
}
});
// Add resize handler with a check for ScrollTrigger existence
window.addEventListener('resize', () => {
const trigger = ScrollTrigger.getById('featured-projects-pin');
if (trigger) { // Only update if the ScrollTrigger exists
trigger.end = calculateEnd();
}
});
}
});
// DROPDOWN EXPANDING
const dropdowns = document.querySelectorAll('.dropdown-block');
if (dropdowns.length) {
dropdowns.forEach((dropdown) => {
// Find child elements
const dropdownList = dropdown.querySelector('.dropdown-list');
const arrow01 = dropdown.querySelector('.vector.arrow._01');
const arrow02 = dropdown.querySelector('.vector.arrow._02');
// Set initial state: dropdown closed
gsap.set(dropdownList, {
height: 0,
overflow: 'hidden',
onComplete: () => {
ScrollTrigger.refresh();
if (window.lenis) lenis.resize();
},
});
// Create a single timeline for opening/closing animation
const tl = gsap.timeline({ paused: true });
// Add animations to the timeline
tl.to(dropdownList, {
height: 'auto',
duration: 0.5,
ease: 'power2.inOut',
})
.to(arrow01, {
width: '0%',
height: '0%',
duration: 0.5,
ease: 'power2.inOut',
}, 0)
.to(arrow02, {
width: '100%',
height: '100%',
duration: 0.5,
ease: 'power2.inOut',
}, 0.05);
// Add click event listener to toggle the dropdown
dropdown.addEventListener('click', () => {
const isOpen = dropdown.classList.contains('is-open');
if (isOpen) {
tl.reverse();
} else {
tl.play();
}
// Toggle the is-open class after animation completes
tl.eventCallback('onComplete', () => {
dropdown.classList.toggle('is-open');
ScrollTrigger.refresh();
if (window.lenis) lenis.resize();
});
tl.eventCallback('onReverseComplete', () => {
dropdown.classList.toggle('is-open');
ScrollTrigger.refresh();
if (window.lenis) lenis.resize();
});
});
});
}
// PROJECT LIST - SCROLLTRIGGER UPDATE AFTER FILTERING
const filterList = document.querySelector('[fs-cmsfilter-element="list"]');
const filterControls = document.querySelector('[fs-cmsfilter-element="filters"]');
if (filterList && filterControls) {
window.fsAttributes = window.fsAttributes || [];
window.fsAttributes.push([
'cmsfilter',
(filterInstances) => {
const [filterInstance] = filterInstances;
filterInstance.listInstance.on('renderitems', (renderedItems) => {
setTimeout(() => {
ScrollTrigger.refresh();
if (lenis) {
lenis.resize();
}
}, 500);
});
},
]);
}
// BLOG FILTERS STICKY
/*mm.add("(min-width: 992px)", () => {
const blogFiltersWrapper = document.querySelector('.blog-filters-wrapper');
const parentContainer = document.querySelector('.grid-col.span-4.end--1.relative');
const searchInput = document.querySelector('.text-field.search.w-input');
const checkboxes = document.querySelectorAll(
'.filter-checkbox-block input[type="checkbox"]');
if (blogFiltersWrapper && parentContainer && searchInput && checkboxes.length) {
const updateWidth = () => {
const parentRect = parentContainer.getBoundingClientRect();
gsap.set(blogFiltersWrapper, { width: parentRect.width });
};
updateWidth();
window.addEventListener('resize', updateWidth);
const scrollTriggerInstance = gsap.to(blogFiltersWrapper, {
scrollTrigger: {
id: 'blogFiltersTrigger',
trigger: blogFiltersWrapper,
start: 'bottom bottom',
end: () => `${window.scrollY + parentContainer.offsetHeight} bottom`,
onEnter: () => {
blogFiltersWrapper.classList.add('fixed');
},
onLeaveBack: () => {
blogFiltersWrapper.classList.remove('fixed');
},
onLeave: () => {
blogFiltersWrapper.classList.remove('fixed');
gsap.set(blogFiltersWrapper, { position: 'absolute', bottom: 0, left: 0 });
},
onEnterBack: () => {
blogFiltersWrapper.classList.add('fixed');
gsap.set(blogFiltersWrapper, { position: '', bottom: '', left: '' });
},
},
});
}
});*/
// VIDEO PLAYER OVERLAY
// Variable to store the current player (YouTube or Vimeo)
let player = null;
let isVideoOverlayOpen = false;
// Focusable elements outside the video overlay (excluding menu overlay elements)
const outsideFocusableElements = $('body').find(
'a, button, input, textarea, select, [tabindex="0"]'
).not('.video-overlay-block *, .menu-overlay-block *');
// Function to extract YouTube video ID from URL
function getYouTubeVideoId(url) {
const regex =
/(?:youtube\.com\/(?:[^\/]+\/.+\/|(?:v|e(?:mbed)?)\/|.*[?&]v=)|youtu\.be\/)([^"&?\/\s]{11})/;
const match = url.match(regex);
const videoId = match ? match[1] : null;
return videoId;
}
// Function to extract Vimeo video ID from URL
function getVimeoVideoId(url) {
const regex = /(?:vimeo\.com\/)([^"&?\/\s]+)/;
const match = url.match(regex);
const videoId = match ? match[1] : null;
return videoId;
}
// Function to disable focus on elements outside the video overlay
function disableOutsideFocus() {
outsideFocusableElements.each(function () {
const $el = $(this);
$el.data('original-tabindex', $el.attr('tabindex') || 0); // Save original tabindex
$el.attr('tabindex', '-1').attr('aria-hidden', 'true');
});
}
// Function to restore focus on elements outside the video overlay
function enableOutsideFocus() {
outsideFocusableElements.each(function () {
const $el = $(this);
const originalTabindex = $el.data('original-tabindex') || 0;
$el.attr('tabindex', originalTabindex).removeAttr('aria-hidden');
});
}
// Function to initialize the video overlay logic
function initializeVideoOverlay() {
// Select all video-click-block elements
const videoClickBlocks = document.querySelectorAll('.video-click-block');
// If no video-click-block elements exist, exit the function
if (!videoClickBlocks.length) {
return;
}
const overlayBlock = document.querySelector('.video-overlay-block');
const overlayBg = document.querySelector('.video-overlay-bg');
const playerContainer = document.querySelector('#player');
const closeButton = document.querySelector('.video-overlay-close-button');
const closeButtonLine01BG = document.querySelector('.close-button-line._01 .button-line-bg');
const closeButtonLine02BG = document.querySelector('.close-button-line._02 .button-line-bg');
if (!overlayBlock || !overlayBg || !playerContainer || !closeButton || !closeButtonLine01BG ||
!
closeButtonLine02BG) {
return;
}
// Animation timeline for the popup
const tl = gsap.timeline({
paused: true
});
// Initial setup for close button lines
gsap.set(closeButtonLine01BG, { width: '0%' }, 0);
gsap.set(closeButtonLine02BG, { width: '0%' }, 0);
// Animation for opening the video overlay
tl.to(overlayBlock, {
display: 'flex', // Change display to flex when opening
duration: 0
}, 0)
.fromTo(overlayBlock, {
opacity: 0,
duration: 0.3,
ease: 'Power1.easeOut'
}, {
opacity: 1
}, 0)
.to(overlayBg, {
opacity: 0.95, // Updated opacity to 0.95
duration: 0.3,
ease: 'Power1.easeOut'
}, 0)
.to(closeButtonLine01BG, {
duration: 0.3,
width: '100%',
ease: 'Power2.easeInOut'
}, 0.4)
.to(closeButtonLine02BG, {
duration: 0.3,
width: '100%',
ease: 'Power2.easeInOut'
}, 0.5);
// Animation for closing the video overlay (will be played in reverse)
tl.to(closeButtonLine01BG, {
duration: 0.3,
width: '0%',
ease: 'Power2.easeInOut'
}, 0);
tl.to(closeButtonLine02BG, {
duration: 0.3,
width: '0%',
ease: 'Power2.easeInOut'
}, 0.1);
// Function to close the video overlay
const closeVideoOverlay = () => {
tl.reverse();
lenis.start();
if (player) {
if (videoType === 'youtube' && player.pauseVideo) {
player.pauseVideo(); // Pause YouTube video
} else if (videoType === 'vimeo' && player.pause) {
player.pause(); // Pause Vimeo video
}
}
isVideoOverlayOpen = false;
enableOutsideFocus();
};
// Variable to store the video type (needed for closing)
let videoType = null;
// Function to handle video seeking with arrow keys and ESC key
function handleVideoControls(event) {
if (!isVideoOverlayOpen) return; // Only handle if video overlay is open
const seekStep = 5; // Number of seconds to seek forward/backward
let currentTime;
if (event.key === 'Escape') { // Handle ESC key
event.preventDefault();
closeVideoOverlay();
} else if (player) {
if (event.key === 'ArrowLeft') {
event.preventDefault(); // Prevent default scrolling behavior
if (player.getCurrentTime) { // YouTube
currentTime = player.getCurrentTime();
player.seekTo(Math.max(0, currentTime - seekStep), true);
} else if (player.getCurrentTime) { // Vimeo
player.getCurrentTime().then((time) => {
player.setCurrentTime(Math.max(0, time - seekStep));
});
}
} else if (event.key === 'ArrowRight') {
event.preventDefault(); // Prevent default scrolling behavior
if (player.getCurrentTime) { // YouTube
currentTime = player.getCurrentTime();
player.getDuration().then((duration) => {
player.seekTo(Math.min(duration, currentTime + seekStep), true);
});
} else if (player.getCurrentTime) { // Vimeo
player.getCurrentTime().then((time) => {
player.getDuration().then((duration) => {
player.setCurrentTime(Math.min(duration, time + seekStep));
});
});
}
}
}
}
// Add keydown event listener for seeking and ESC
document.addEventListener('keydown', handleVideoControls);
// Add click event listener to each video-click-block
videoClickBlocks.forEach((block) => {
block.addEventListener('click', (e) => {
e.preventDefault(); // Prevent default link behavior
// Open the popup immediately
tl.play();
lenis.stop();
isVideoOverlayOpen = true;
disableOutsideFocus(); // Disable focus on elements outside the video overlay
// Get the video link and type
const videoLinkElement = block.querySelector('a[data-video-link]');
const videoLink = videoLinkElement ? videoLinkElement.getAttribute('href') : null;
videoType = block.getAttribute('data-overlay-type');
// Destroy the existing player if it exists
if (player) {
if (videoType === 'youtube' && player.destroy) {
player.destroy();
} else if (videoType === 'vimeo' && player.destroy) {
player.destroy();
}
player = null;
}
// Clear the player container
playerContainer.innerHTML = '';
if (videoType === 'youtube') {
// Extract YouTube video ID
const videoId = getYouTubeVideoId(videoLink);
if (!videoId) {
return;
}
// Initialize YouTube player
player = new YT.Player('player', {
height: '390',
width: '640',
videoId: videoId,
playerVars: {
'playsinline': 1
},
events: {
'onReady': (event) => {
// Fade in the player when ready
gsap.fromTo(playerContainer, {
opacity: 0,
duration: 0.3,
ease: 'Power1.easeOut'
}, {
opacity: 1
});
// Start playing the video
event.target.playVideo();
}
}
});
} else if (videoType === 'vimeo') {
// Extract Vimeo video ID
const videoId = getVimeoVideoId(videoLink);
if (!videoId) {
return;
}
// Create a new div for Vimeo player
const vimeoPlayerDiv = document.createElement('div');
vimeoPlayerDiv.id = 'player';
playerContainer.appendChild(vimeoPlayerDiv);
// Initialize Vimeo player
player = new Vimeo.Player('player', {
id: videoId,
width: 640,
height: 390,
playsinline: true
});
// Fade in the player when ready
player.on('loaded', () => {
gsap.fromTo(playerContainer, {
opacity: 0,
duration: 0.3,
ease: 'Power1.easeOut'
}, {
opacity: 1
});
// Start playing the video
player.play();
});
}
});
});
// Close the popup when clicking on the background
overlayBg.addEventListener('click', closeVideoOverlay);
// Close the popup when clicking on the close button
closeButton.addEventListener('click', closeVideoOverlay);
// Close the popup when pressing Enter or Space on the close button
closeButton.addEventListener('keydown', (event) => {
if (event.key === 'Enter' || event.key === ' ') {
event.preventDefault(); // Prevent default behavior (e.g., scrolling on Space)
closeVideoOverlay();
}
});
// Ensure display is set to none and clear player container when the animation reverses (closes)
tl.eventCallback('onReverseComplete', () => {
gsap.set(overlayBlock, { display: 'none' });
// Clear the player container and destroy the player after animation completes
if (player) {
if (videoType === 'youtube' && player.destroy) {
player.destroy(); // Destroy YouTube player
} else if (videoType === 'vimeo' && player.destroy) {
player.destroy(); // Destroy Vimeo player
}
player = null;
}
playerContainer.innerHTML = ''; // Clear the player container
});
}
// Define onYouTubeIframeAPIReady as a global function
window.onYouTubeIframeAPIReady = function () {
initializeVideoOverlay();
console.log('youtube ready');
};
// Fallback: If the API is already loaded, call the function manually
if (window.YT && window.YT.Player) {
window.onYouTubeIframeAPIReady();
}
// PROJECTS FILTER CHECKBOX ALL
let checkboxAll = $('#chackbox-all'); // Select the #chackbox-all element
let projectFilterList = $('.project-filter-list'); // Select the .project-filter-list container
// Check if both elements exist, then move #chackbox-all to the beginning of .project-filter-list
if (checkboxAll.length && projectFilterList.length) {
projectFilterList.prepend(checkboxAll);
}
}; // INTERACTIONS
// HOVERS
function Hovers() {
// BUTTON HOVER
const buttonBlock = document.querySelectorAll(
'.button-block'
);
// Check if there are any buttons to animate
if (buttonBlock.length) {
gsap.utils.toArray(buttonBlock).forEach((elem) => {
// Find child elements within the current button
const buttonLine = elem.querySelector('.button-line');
const buttonLineContainer = elem.querySelector('.button-line-container');
const arrowMask = elem.querySelector('.arrow-mask');
const arrow01 = elem.querySelector('.vector.arrow._01');
const arrow02 = elem.querySelector('.vector.arrow._02');
// Create a GSAP timeline for scroll-triggered animation (if needed)
const scrollTl = gsap.timeline({
scrollTrigger: {
trigger: elem,
scrub: false,
start: 'top 90%',
end: 'bottom 0%',
},
});
// Create a GSAP timeline for hover animation
const hoverTimeline = gsap.timeline({ paused: true });
// Add animations for hover effect if elements exist
if (arrowMask) {
hoverTimeline.to(arrow01, {
duration: 0.5,
width: '0%',
height: '0%',
ease: 'power2.inOut',
}, 0)
.to(
arrow02, {
duration: 0.5,
width: '100%',
height: '100%',
ease: 'power2.inOut',
}, 0.05);
}
// Add animation for buttonLine if it exists
if (buttonLineContainer) {
const buttonLine01 = elem.querySelector('.button-line._01');
const buttonLine02 = elem.querySelector('.button-line._02');
gsap.set(buttonLine02, { width: '0%' });
hoverTimeline.to(buttonLine01, {
duration: 0.4,
width: '0%',
ease: 'Power3.easeOut',
}, 0)
.to(buttonLine02, {
duration: 0.3,
width: '100%',
ease: 'Power3.easeOut',
}, 0.2);
}
// Play the timeline when the mouse enters the element
elem.addEventListener('mouseenter', () => {
hoverTimeline.play();
});
// Reverse the timeline when the mouse leaves the element
elem.addEventListener('mouseleave', () => {
hoverTimeline.reverse();
});
});
}
// FEATURED VIDEO BLOCK HOVER
let currentClientX = 0;
let currentClientY = 0;
// Select all video blocks
const videoBlocks = document.querySelectorAll(
'.video-block.featured, .video-block.portfolio-showreel');
//const videoBlocksHover = document.querySelectorAll('.video-block.featured, .video-block.portfolio-showreel');
// Check if there are any video blocks
if (videoBlocks.length) {
// Update global cursor position on mousemove
document.addEventListener('mousemove', (e) => {
currentClientX = e.clientX;
currentClientY = e.clientY;
});
videoBlocks.forEach((videoBlock) => {
// Find the play button wrapper
const playButtonWrapper = videoBlock.querySelector('.play-button-wrapper');
// Variables to store the state
let isMouseOver = false;
// Function to update the play button position
const updateButtonPosition = () => {
// Get the current bounding rectangle (reflects scaling and position)
const rect = videoBlock.getBoundingClientRect();
// Check if the cursor is within the current block
const isCursorOver = currentClientX >= rect.left && currentClientX <= rect.right &&
currentClientY >= rect.top && currentClientY <= rect.bottom;
// Only update if the mouse is over the block
if (!isMouseOver || !isCursorOver) {
return;
}
// Calculate the center of the video block (relative to the viewport)
const centerX = rect.left + rect.width / 2;
const centerY = rect.top + rect.height / 2;
// Calculate the offset from the center based on current dimensions
const offsetX = currentClientX - centerX;
const offsetY = currentClientY - centerY;
// Constrain the button position within the current video block
const maxX = rect.width / 2;
const maxY = rect.height / 2;
const constrainedX = Math.max(-maxX, Math.min(offsetX, maxX));
const constrainedY = Math.max(-maxY, Math.min(offsetY, maxY));
// Animate the play button position relative to the center
gsap.to(playButtonWrapper, {
x: constrainedX,
y: constrainedY,
duration: 0,
ease: 'power2.out',
});
};
// On mouse enter: animate the play button size and set flag
videoBlock.addEventListener('mouseenter', () => {
isMouseOver = true;
gsap.to(playButtonWrapper, {
opacity: 1,
scale: 1,
duration: 0.5,
ease: 'power2.out',
});
updateButtonPosition(); // Initialize position on enter
});
// On mouse leave: reset the play button size and clear flag
videoBlock.addEventListener('mouseleave', () => {
isMouseOver = false;
gsap.to(playButtonWrapper, {
opacity: 0,
scale: 0.3,
duration: 0.2,
ease: 'power2.in',
});
});
// On mouse move: update the button position
videoBlock.addEventListener('mousemove', () => {
updateButtonPosition();
});
// On scroll: update the button position for the current block
window.addEventListener('scroll', () => {
updateButtonPosition();
});
});
}
}; // HOVERS
// CASE STUDIES THREE DOTS
/*function CaseStudiesThreeDots() {
const caseBlocksCaseStudies = document.querySelectorAll('.bento-cell.case.studies-page');
if (caseBlocksCaseStudies.length > 0) {
caseBlocksCaseStudies.forEach(caseBlockCaseStudy => {
let caseHeadings = caseBlockCaseStudy.querySelectorAll('.text-block .heading');
let caseHeadingsresult = caseBlockCaseStudy.querySelectorAll(
'.text-block .heading.case-result');
caseHeadingsresult.forEach(heading => {
// Check if the element has already been processed
if (!heading.classList.contains('processed')) {
let text = heading.textContent.trim();
if (text.length > 0) {
// Append three dots to the text
heading.textContent = text + ' ...';
}
// Add a class to mark the element as processed
heading.classList.add('processed');
}
});
});
};
};*/
// Animations
function Animations() {
let mm = gsap.matchMedia();
mm.add('(min-width: 768px)', () => {
AnimationsDesktop();
let mm = gsap.matchMedia();
mm.add('(min-width: 1281px)', () => {
Hovers();
});
});
mm.add('(max-width: 767px)', () => {
AnimationsMobile();
});
}
function AnimationsDesktop() {
Interactions();
IntoView();
}
function AnimationsMobile() {
Interactions();
IntoView();
}
// Function to hide the loading screen and start animations
function hideLoadingScreen() {
const loadingScreen = document.querySelector('.loading-screen-block');
if (loadingScreen) {
gsap.set(loadingScreen, { display: 'none' }); // Hide the loading screen
}
// Start animations immediately after hiding the loading screen
Animations();
setTimeout(() => {
IntoViewAnimationsStart();
}, 10);
}
// Initial page load
window.addEventListener('load', function () {
window.history.scrollRestoration = 'manual';
window.scrollTo(0, 0);
SwupJS();
Smooth();
// Hide loading screen and start animations
hideLoadingScreen();
let resizeTimeout;
window.addEventListener('resize', () => {
clearTimeout(resizeTimeout);
resizeTimeout = setTimeout(() => {
ScrollTrigger.refresh();
if (lenis) {
lenis.resize();
}
}, 200);
});
});