Typography
Progress and possibility
Progress and possibility
Progress and possibility
Progress and possibility
Progress and possibility
Progress and possibility
Progress and possibility
Progress and possibility
Progress and possibility
Progress and possibility
Progress and possibility
Progress and possibility
Progress and possibility
Progress and possibility
Progress and possibility
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse varius enim in eros elementum tristique. Duis cursus, mi quis viverra ornare, eros dolor interdum nulla, ut commodo diam libero vitae erat. Aenean faucibus nibh et justo cursus id rutrum lorem imperdiet. Nunc ut sem vitae risus tristique posuere.
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse varius enim in eros elementum tristique. Duis cursus, mi quis viverra ornare, eros dolor interdum nulla, ut commodo diam libero vitae erat. Aenean faucibus nibh et justo cursus id rutrum lorem imperdiet. Nunc ut sem vitae risus tristique posuere.
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse varius enim in eros elementum tristique. Duis cursus, mi quis viverra ornare, eros dolor interdum nulla, ut commodo diam libero vitae erat. Aenean faucibus nibh et justo cursus id rutrum lorem imperdiet. Nunc ut sem vitae risus tristique posuere.
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse varius enim in eros elementum tristique. Duis cursus, mi quis viverra ornare, eros dolor interdum nulla, ut commodo diam libero vitae erat. Aenean faucibus nibh et justo cursus id rutrum lorem imperdiet. Nunc ut sem vitae risus tristique posuere.
Heading 1
Heading 2
Heading 3
Heading 4
Heading 5
Heading 6
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.
Block quote
Ordered list
- Item 1
- Item 2
- Item 3
Unordered list
- Item A
- Item B
- Item C
Bold text
Emphasis
Superscript
Subscript
Block Quote
- This is some text inside of a div block.
- This is some text inside of a div block.
- This is some text inside of a div block.
Color
Elements
Progress and possibility
Brand Sprint
A rapid, high-impact sprint to craft your brand’s foundation—strategy, identity, and messaging—so it’s instantly recognizable and built for growth.
Your brand is your story our process brings it to life.
A high-impact sprint to craft your brand’s foundation—strategy, identity, and messaging—so you launch with clarity, confidence, and a brand that stands out. Designed for speed and impact, our sprint gets you from zero to a market-ready brand in weeks, not months.
Components
From bold ideas to brandsproductsbrandsthat resonate.
We help ambitious founders and entrepreneurs transform vision into reality—combining deep AI expertise with world-class design to build faster, smarter, and with more impact.







/*-----------------------------------------------------------*/
/* Gallery Masthead */
/*-----------------------------------------------------------*/
function initMarqueeScrollDirection() {
document.querySelectorAll('[data-marquee-scroll-direction-target]').forEach((marquee) => {
// Query marquee elements
const marqueeContent = marquee.querySelector('[data-marquee-collection-target]');
const marqueeScroll = marquee.querySelector('[data-marquee-scroll-target]');
if (!marqueeContent || !marqueeScroll) return;
// Get data attributes
let { marqueeSpeed: speed, marqueeDirection: direction, marqueeDuplicate: duplicate, marqueeScrollSpeed: scrollSpeed } = marquee.dataset;
// Normalize direction (treat "left" as "up" and "right" as "down")
if (direction === "left") direction = "up";
if (direction === "right") direction = "down";
// Convert data attributes to usable types
const marqueeSpeedAttr = parseFloat(speed);
const marqueeDirectionAttr = direction === 'down' ? 1 : -1; // 1 for down, -1 for up
const duplicateAmount = parseInt(duplicate || 0);
const scrollSpeedAttr = parseFloat(scrollSpeed);
const speedMultiplier = window.innerWidth < 479 ? 0.25 : window.innerWidth < 991 ? 0.5 : 1;
let marqueeSpeed = marqueeSpeedAttr * (marqueeContent.offsetHeight / window.innerHeight) * speedMultiplier;
// Precompute styles for the scroll container
marqueeScroll.style.marginTop = `${scrollSpeedAttr * -1}%`;
marqueeScroll.style.height = `${(scrollSpeedAttr * 2) + 100}%`;
// Duplicate marquee content for seamless looping
if (duplicateAmount > 0) {
const fragment = document.createDocumentFragment();
for (let i = 0; i < duplicateAmount; i++) {
fragment.appendChild(marqueeContent.cloneNode(true));
}
marqueeScroll.appendChild(fragment);
}
// GSAP animation for vertical marquee movement
const marqueeItems = marquee.querySelectorAll('[data-marquee-collection-target]');
const animation = gsap.to(marqueeItems, {
yPercent: -100, // Move completely out of view vertically
repeat: -1,
duration: marqueeSpeed,
ease: 'linear'
}).totalProgress(0.5);
// Initialize marquee in the correct direction
gsap.set(marqueeItems, { yPercent: marqueeDirectionAttr === 1 ? 100 : -100 });
animation.timeScale(marqueeDirectionAttr); // Set correct direction
animation.play(); // Start animation immediately
// Set initial marquee status
marquee.setAttribute('data-marquee-status', 'normal');
// ScrollTrigger logic for direction inversion
ScrollTrigger.create({
trigger: marquee,
start: 'top bottom',
end: 'bottom top',
onUpdate: (self) => {
const isInverted = self.direction === 1; // Scrolling down
const currentDirection = isInverted ? -marqueeDirectionAttr : marqueeDirectionAttr;
// Update animation direction and marquee status
animation.timeScale(currentDirection);
marquee.setAttribute('data-marquee-status', isInverted ? 'normal' : 'inverted');
}
});
// Extra speed effect on scroll
const tl = gsap.timeline({
scrollTrigger: {
trigger: marquee,
start: '0% 100%',
end: '100% 0%',
scrub: 0
}
});
const scrollStart = marqueeDirectionAttr === -1 ? scrollSpeedAttr : -scrollSpeedAttr;
const scrollEnd = -scrollStart;
tl.fromTo(marqueeScroll, { y: `${scrollStart}vh` }, { y: `${scrollEnd}vh`, ease: 'none' });
});
}
initMarqueeScrollDirection();
function cycleMarqueeWords() {
const track = document.querySelector(".marquee_track");
const words = ["brands", "products"];
let index = 0;
if (!track) return;
function animateWordChange() {
const currentWord = track.querySelector(".marquee_item");
// Clone the current word but don't add it yet
const newWord = currentWord.cloneNode(true);
newWord.textContent = words[(index + 1) % words.length]; // Set next word
gsap.set(newWord, { opacity: 0, x: 50, position: "absolute" });
// Animate the old word moving out first
gsap.to(currentWord, {
x: -50,
opacity: 0,
duration: 0.8,
ease: "power2.inOut",
onComplete: () => {
// Once the old word has moved out, add the new word to the DOM
track.appendChild(newWord);
// Animate the new word into place
gsap.to(newWord, {
x: 0,
opacity: 1,
duration: 0.8,
ease: "power2.inOut",
onComplete: () => {
// Remove the old word **only after the new word has animated in**
track.removeChild(currentWord);
newWord.style.position = "relative";
// Update index for the next cycle
index = (index + 1) % words.length;
},
});
},
});
}
// Run animation every 3 seconds
setInterval(animateWordChange, 3000);
}
setTimeout(cycleMarqueeWords, 3000);
Your brand is your story our process brings it to life.
A high-impact sprint to craft your brand’s foundation—strategy, identity, and messaging—so you launch with clarity, confidence, and a brand that stands out. Designed for speed and impact, our sprint gets you from zero to a market-ready brand in weeks, not months.


/*-----------------------------------------------------------*/
/* AI Upskill Masthead */
/*-----------------------------------------------------------*/
function initAiMasthead() {
const canvas = document.querySelector('[data-sequence-canvas]');
if (!canvas) return;
const ctx = canvas.getContext('2d');
const baseUrl = canvas.getAttribute('data-sequence-url');
const totalFrames = parseInt(canvas.getAttribute('data-sequence-frames')) || 278;
const fps = 30;
const images = [];
let frame = 0;
let loaded = 0;
const cacheBuster = `v=${Date.now()}`;
// Setup DPR scaling
const dpr = window.devicePixelRatio || 1;
const cssWidth = canvas.clientWidth;
const cssHeight = canvas.clientHeight;
canvas.width = cssWidth * dpr;
canvas.height = cssHeight * dpr;
ctx.scale(dpr, dpr);
// 🔥 Load first frame FAST
const firstFrame = new Image();
firstFrame.src = `${baseUrl}00000.webp?${cacheBuster}`;
firstFrame.onload = () => {
drawCoverFrame(firstFrame);
};
function drawCoverFrame(img) {
const canvasRatio = cssWidth / cssHeight;
const imgRatio = img.naturalWidth / img.naturalHeight;
let drawWidth, drawHeight, offsetX, offsetY;
if (canvasRatio > imgRatio) {
drawWidth = cssWidth;
drawHeight = cssWidth / imgRatio;
offsetX = 0;
offsetY = -(drawHeight - cssHeight) / 2;
} else {
drawWidth = cssHeight * imgRatio;
drawHeight = cssHeight;
offsetX = -(drawWidth - cssWidth) / 2;
offsetY = 0;
}
ctx.clearRect(0, 0, cssWidth, cssHeight);
ctx.drawImage(img, offsetX, offsetY, drawWidth, drawHeight);
}
// Preload ALL frames (including first one again for consistency)
for (let i = 0; i <= totalFrames; i++) {
const frameNumber = String(i).padStart(5, '0');
const img = new Image();
img.src = `${baseUrl}${frameNumber}.webp?${cacheBuster}`;
img.onload = () => {
loaded++;
if (loaded === totalFrames + 1) {
loop();
}
};
images.push(img);
}
function loop() {
ctx.clearRect(0, 0, cssWidth, cssHeight);
const img = images[frame];
const canvasRatio = cssWidth / cssHeight;
const imgRatio = img.naturalWidth / img.naturalHeight;
let drawWidth, drawHeight, offsetX, offsetY;
if (canvasRatio > imgRatio) {
drawWidth = cssWidth;
drawHeight = cssWidth / imgRatio;
offsetX = 0;
offsetY = -(drawHeight - cssHeight) / 2;
} else {
drawWidth = cssHeight * imgRatio;
drawHeight = cssHeight;
offsetX = -(drawWidth - cssWidth) / 2;
offsetY = 0;
}
ctx.drawImage(img, offsetX, offsetY, drawWidth, drawHeight);
frame = (frame + 1) % (totalFrames + 1);
setTimeout(loop, 1000 / fps);
}
// Hover .ai-upskill_gen-img
const cards = document.querySelectorAll('.ai-upskill_masthead-wrapper .ai-upskill_gen-img');
cards.forEach((card) => {
card.addEventListener('mousemove', (e) => {
const rect = card.getBoundingClientRect();
const x = e.clientX - rect.left;
const y = e.clientY - rect.top;
const centerX = rect.width / 2;
const centerY = rect.height / 2;
const deltaX = (x - centerX) / centerX;
const deltaY = (y - centerY) / centerY;
gsap.to(card, {
duration: 0.4,
rotateX: deltaY * -10, // adjust intensity here
rotateY: deltaX * 10,
transformPerspective: 800,
ease: "power2.out"
});
});
card.addEventListener('mouseleave', () => {
gsap.to(card, {
duration: 0.6,
rotateX: 0,
rotateY: 0,
ease: "power3.out"
});
});
});
}
initAiMasthead();
Brand Sprint
A rapid, high-impact sprint to craft your brand’s foundation—strategy, identity, and messaging—so it’s instantly recognizable and built for growth.

Product Sprint
From concept to reality, we help you define, prototype, and validate your product—ensuring perfect market fit before you build.

AI Upskilling
Most teams waste months figuring out AI. Yours won't. Upskilling sprints give your people practical AI skills they'll use every day. No fluff, no theory — just valuable, practical tools.
human x ai
•
•
Research
•
•
Ideate
•
•
Generate
•
•
human x ai
•
•
Research
•
•
Ideate
•
•
Generate
•
•

Learn more
/*-----------------------------------------------------------*/
/* Testimonail Slider */
/*-----------------------------------------------------------*/
$(".tst-slider_component").each(function (index) {
const swiper = new Swiper($(this).find(".swiper")[0], {
// Default parameters
slidesPerView: 1.12,
spaceBetween: 18,
speed: 800,
allowTouchMove: true,
followFinger: true,
freeMode: false,
loop: true,
rewind: false,
keyboard: {
enabled: true,
onlyInViewport: true
},
navigation: {
nextEl: $(this).find(".swiper-next")[0],
prevEl: $(this).find(".swiper-prev")[0],
disabledClass: "is-disabled"
},
scrollbar: {
el: $(this).find(".swiper-drag-wrapper")[0],
draggable: true,
dragClass: "swiper-drag",
snapOnRelease: true
},
// Breakpoints
breakpoints: {
480: {
slidesPerView: 1.12,
spaceBetween: 26
},
// when window width is >= 768px
768: {
slidesPerView: 1.12,
spaceBetween: 26
},
// when window width is >= 992px
992: {
slidesPerView: 2.12,
spaceBetween: 26
}
}
});
});
let cursorItem = document.querySelector(".cursor");
let cursorParagraph = cursorItem.querySelector("p");
let targets = document.querySelectorAll("[data-cursor]");
let xOffset = 6;
let yOffset = 140;
let cursorIsOnRight = false;
let currentTarget = null;
let lastText = '';
// Position cursor relative to actual cursor position on page load
gsap.set(cursorItem, { xPercent: xOffset, yPercent: yOffset });
// Use GSAP quick.to for a more performative tween on the cursor
let xTo = gsap.quickTo(cursorItem, "x", { ease: "power3" });
let yTo = gsap.quickTo(cursorItem, "y", { ease: "power3" });
// Function to get the width of the cursor element including a buffer
const getCursorEdgeThreshold = () => {
return cursorItem.offsetWidth + 16; // Cursor width + 16px margin
};
// On mousemove, call the quickTo functions to the actual cursor position
window.addEventListener("mousemove", e => {
let windowWidth = window.innerWidth;
let windowHeight = window.innerHeight;
let scrollY = window.scrollY;
let cursorX = e.clientX;
let cursorY = e.clientY + scrollY; // Adjust cursorY to account for scroll
// Default offsets
let xPercent = xOffset;
let yPercent = yOffset;
// Adjust X offset dynamically based on cursor width
let cursorEdgeThreshold = getCursorEdgeThreshold();
if (cursorX > windowWidth - cursorEdgeThreshold) {
cursorIsOnRight = true;
xPercent = -100;
} else {
cursorIsOnRight = false;
}
// Adjust Y offset if in the bottom 10% of the current viewport
if (cursorY > scrollY + windowHeight * 0.9) {
yPercent = -120;
}
if (currentTarget) {
let newText = currentTarget.getAttribute("data-cursor");
if (newText !== lastText) { // Only update if the text is different
cursorParagraph.innerHTML = newText;
lastText = newText;
// Recalculate edge awareness whenever the text changes
cursorEdgeThreshold = getCursorEdgeThreshold();
}
}
gsap.to(cursorItem, { xPercent: xPercent, yPercent: yPercent, duration: 0.9, ease: "power3" });
xTo(cursorX);
yTo(cursorY - scrollY);
});
// Add a mouse enter listener for each link that has a data-cursor attribute
targets.forEach(target => {
target.addEventListener("mouseenter", () => {
currentTarget = target; // Set the current target
let newText = target.getAttribute("data-cursor");
// Update only if the text changes
if (newText !== lastText) {
cursorParagraph.innerHTML = newText;
lastText = newText;
// Recalculate edge awareness whenever the text changes
let cursorEdgeThreshold = getCursorEdgeThreshold();
}
});
});
Organizations that tap into generative AI and workflow automation are seeing a 4x return on every dollar spent
/*-----------------------------------------------------------*/
/* Scramble Text */
/*-----------------------------------------------------------*/
gsap.registerPlugin(ScrambleTextPlugin)
gsap.set('[animate-txt="scramble"]', { visibility: "visible" });
function initScrambleOnScroll(){
let targets = document.querySelectorAll('[animate-txt="scramble"]')
targets.forEach((target) => {
// Used this attribute to showcase a different character scramble, can be replaced with many scenarios
let isAlternative = target.hasAttribute("data-scramble-alt")
let split = new SplitText(target, {
type: "words, chars",
wordsClass: "word",
charsClass: "char"
});
gsap.to(split.words, {
duration: 1.4,
stagger: 0.015,
scrambleText: {
text: "{original}",
chars: '←©→↓↑®↖℗↗↘ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789@#',
speed: 0.45,
},
scrollTrigger: {
trigger: target,
start: "top 80%",
once: true
},
// Once animation is done, revert the split to reduce DOM size
onComplete: () => split.revert()
})
})
}
initScrambleOnScroll();
Find your resonance.
We deliver in weeks, not months. You work directly with experts, not layers of junior staff. We blend world-class design with AI expertise to build brands and products that don’t just exist—they resonate.










/*-----------------------------------------------------------*/
/* Main CTA */
/*-----------------------------------------------------------*/
function initImageTrail(config = {}) {
// config + defaults
const options = {
minWidth: config.minWidth ?? 992,
moveDistance: config.moveDistance ?? 15,
stopDuration: config.stopDuration ?? 300,
trailLength: config.trailLength ?? 5
};
const wrapper = document.querySelector('[data-trail="wrapper"]');
if (!wrapper || window.innerWidth < options.minWidth) {
return;
}
// State management
const state = {
trailInterval: null,
globalIndex: 0,
last: { x: 0, y: 0 },
trailImageTimestamps: new Map(),
trailImages: Array.from(document.querySelectorAll('[data-trail="item"]')),
isActive: false
};
// Utility functions
const MathUtils = {
lerp: (a, b, n) => (1 - n) * a + n * b,
distance: (x1, y1, x2, y2) => Math.hypot(x2 - x1, y2 - y1)
};
function getRelativeCoordinates(e, rect) {
return {
x: e.clientX - rect.left,
y: e.clientY - rect.top
};
}
function activate(trailImage, x, y) {
if (!trailImage) return;
const rect = trailImage.getBoundingClientRect();
const styles = {
left: `${x - rect.width / 2}px`,
top: `${y - rect.height / 2}px`,
zIndex: state.globalIndex,
display: 'block'
};
Object.assign(trailImage.style, styles);
state.trailImageTimestamps.set(trailImage, Date.now());
// Here, animate how the images will appear!
gsap.fromTo(
trailImage,
{ autoAlpha: 0, scale: 0.8 },
{
scale: 1,
autoAlpha: 1,
duration: 0.2,
overwrite: true
}
);
state.last = { x, y };
}
function fadeOutTrailImage(trailImage) {
if (!trailImage) return;
// Here, animate how the images will disappear!
gsap.to(trailImage, {
opacity: 0,
scale: 0.2,
duration: 0.8,
ease: "expo.out",
onComplete: () => {
gsap.set(trailImage, { autoAlpha: 0 });
}
});
}
function handleOnMove(e) {
if (!state.isActive) return;
const rectWrapper = wrapper.getBoundingClientRect();
const { x: relativeX, y: relativeY } = getRelativeCoordinates(e, rectWrapper);
const distanceFromLast = MathUtils.distance(
relativeX,
relativeY,
state.last.x,
state.last.y
);
if (distanceFromLast > window.innerWidth / options.moveDistance) {
const lead = state.trailImages[state.globalIndex % state.trailImages.length];
const tail = state.trailImages[(state.globalIndex - options.trailLength) % state.trailImages.length];
activate(lead, relativeX, relativeY);
fadeOutTrailImage(tail);
state.globalIndex++;
}
}
function cleanupTrailImages() {
const currentTime = Date.now();
for (const [trailImage, timestamp] of state.trailImageTimestamps.entries()) {
if (currentTime - timestamp > options.stopDuration) {
fadeOutTrailImage(trailImage);
state.trailImageTimestamps.delete(trailImage);
}
}
}
function startTrail() {
if (state.isActive) return;
state.isActive = true;
wrapper.addEventListener("mousemove", handleOnMove);
state.trailInterval = setInterval(cleanupTrailImages, 100);
}
function stopTrail() {
if (!state.isActive) return;
state.isActive = false;
wrapper.removeEventListener("mousemove", handleOnMove);
clearInterval(state.trailInterval);
state.trailInterval = null;
// Clean up remaining trail images
state.trailImages.forEach(fadeOutTrailImage);
state.trailImageTimestamps.clear();
}
// Initialize ScrollTrigger
ScrollTrigger.create({
trigger: wrapper,
start: "top bottom",
end: "bottom top",
onEnter: startTrail,
onEnterBack: startTrail,
onLeave: stopTrail,
onLeaveBack: stopTrail
});
// Clean up on window resize
const handleResize = () => {
if (window.innerWidth < options.minWidth && state.isActive) {
stopTrail();
} else if (window.innerWidth >= options.minWidth && !state.isActive) {
startTrail();
}
};
window.addEventListener('resize', handleResize);
return () => {
stopTrail();
window.removeEventListener('resize', handleResize);
};
}
const imageTrail = initImageTrail({
minWidth: 992,
moveDistance: 15,
stopDuration: 350,
trailLength: 8
});
/*-----------------------------------------------------------*/
/* FW Video */
/*-----------------------------------------------------------*/
// Initialize Full Width Video Function
function initFullWidthVideo() {
document.querySelectorAll(".fw-video_wrapper").forEach(wrapper => {
const playButton = wrapper.querySelector("[data-video-play]");
const video = wrapper.querySelector(".fw-video");
if (!playButton || !video) return;
playButton.addEventListener("click", () => {
if (playButton.classList.contains("is-playing")) {
playButton.classList.remove("is-playing");
video.muted = true;
video.currentTime = 0;
video.play();
} else {
playButton.classList.add("is-playing");
video.muted = false;
video.currentTime = 0;
video.play();
}
});
});
}
// Call the function to initialize the functionality
initFullWidthVideo();
/*-----------------------------------------------------------*/
/* Grid Grow */
/*-----------------------------------------------------------*/
function initGridGrow() {
const grids = document.querySelectorAll('.bg-grid');
grids.forEach((grid) => {
const growItems = grid.querySelectorAll('[data-grid-grow]');
growItems.forEach((item) => {
const growValue = parseFloat(item.getAttribute('data-grid-grow')) || 1;
// Initial height to 0
gsap.set(item, { height: '0rem' });
// Create ScrollTrigger
gsap.to(item, {
height: `${growValue}rem`,
ease: 'power2.out',
scrollTrigger: {
trigger: grid,
start: 'top 80%',
end: 'bottom 70%',
scrub: true
}
});
});
});
}
initGridGrow();
/*-----------------------------------------------------------*/
/* Logo Wall */
/*-----------------------------------------------------------*/
function initLogoWall() {
document.querySelectorAll('[data-marquee-scroll-direction-target]').forEach((marquee) => {
// Query marquee elements
const marqueeContent = marquee.querySelector('[data-marquee-collection-target]');
const marqueeScroll = marquee.querySelector('[data-marquee-scroll-target]');
if (!marqueeContent || !marqueeScroll) return;
// Get data attributes
const { marqueeSpeed: speed, marqueeDirection: direction, marqueeDuplicate: duplicate, marqueeScrollSpeed: scrollSpeed } = marquee.dataset;
// Convert data attributes to usable types
const marqueeSpeedAttr = parseFloat(speed);
const marqueeDirectionAttr = direction === 'right' ? 1 : -1; // 1 for right, -1 for left
const duplicateAmount = parseInt(duplicate || 0);
const scrollSpeedAttr = parseFloat(scrollSpeed);
const speedMultiplier = window.innerWidth < 479 ? 0.25 : window.innerWidth < 991 ? 0.5 : 1;
let marqueeSpeed = marqueeSpeedAttr * (marqueeContent.offsetWidth / window.innerWidth) * speedMultiplier;
// Precompute styles for the scroll container
marqueeScroll.style.marginLeft = `${scrollSpeedAttr * -1}%`;
marqueeScroll.style.width = `${(scrollSpeedAttr * 2) + 100}%`;
// Duplicate marquee content
if (duplicateAmount > 0) {
const fragment = document.createDocumentFragment();
for (let i = 0; i < duplicateAmount; i++) {
fragment.appendChild(marqueeContent.cloneNode(true));
}
marqueeScroll.appendChild(fragment);
}
// GSAP animation for marquee content
const marqueeItems = marquee.querySelectorAll('[data-marquee-collection-target]');
const animation = gsap.to(marqueeItems, {
xPercent: -100, // Move completely out of view
repeat: -1,
duration: marqueeSpeed,
ease: 'linear'
}).totalProgress(0.5);
// Initialize marquee in the correct direction
gsap.set(marqueeItems, { xPercent: marqueeDirectionAttr === 1 ? 100 : -100 });
animation.timeScale(marqueeDirectionAttr); // Set correct direction
animation.play(); // Start animation immediately
// Set initial marquee status
marquee.setAttribute('data-marquee-status', 'normal');
// ScrollTrigger logic for direction inversion
ScrollTrigger.create({
trigger: marquee,
start: 'top bottom',
end: 'bottom top',
onUpdate: (self) => {
const isInverted = self.direction === 1; // Scrolling down
const currentDirection = isInverted ? -marqueeDirectionAttr : marqueeDirectionAttr;
// Update animation direction and marquee status
animation.timeScale(currentDirection);
marquee.setAttribute('data-marquee-status', isInverted ? 'normal' : 'inverted');
}
});
// Extra speed effect on scroll
const tl = gsap.timeline({
scrollTrigger: {
trigger: marquee,
start: '0% 100%',
end: '100% 0%',
scrub: 0
}
});
const scrollStart = marqueeDirectionAttr === -1 ? scrollSpeedAttr : -scrollSpeedAttr;
const scrollEnd = -scrollStart;
tl.fromTo(marqueeScroll, { x: `${scrollStart}vw` }, { x: `${scrollEnd}vw`, ease: 'none' });
});
}
initLogoWall();
/*-----------------------------------------------------------*/
/* Footer */
/*-----------------------------------------------------------*/
// Footer Logo
gsap.from('.footer-logo path', {
y: 100,
duration: 1,
stagger: {
amount: 0.5,
from: "start"
},
ease: "main",
scrollTrigger: {
trigger: ".footer_container",
start: "bottom+=-20% bottom",
toggleActions: "play none none reverse"
}
});
//Footer Current Year
var spanYear = document.querySelector('[data-get-year]');
if (spanYear) {
var currentYear = new Date().getFullYear();
spanYear.textContent = currentYear;
};
/*-----------------------------------------------------------*/
/* Navigation (Legacy) */
/*-----------------------------------------------------------*/
function initializeNavMenu() {
var nav = document.querySelector('.nav');
if (!nav) return; // Stop execution if nav is not found
// Get the button
var menuButton = document.querySelector('.nav-menu_btn');
if (menuButton) {
menuButton.addEventListener('click', function () {
var navMenu = menuButton.parentElement.querySelector('.nav-menu');
if (!navMenu) return;
navMenu.classList.toggle('is-open');
var menuLines = menuButton.querySelectorAll('.menu-line');
menuLines.forEach(function (line) {
line.classList.toggle('is-open');
});
});
}
// Dropdown button
let dropdowns = document.querySelectorAll('.nav-dropdown');
// Loop through each .nav-dropdown element
dropdowns.forEach(function (dropdown) {
let wrapper = dropdown.querySelector('.nav-dropdown_wrapper');
if (!wrapper) return;
dropdown.addEventListener('mouseover', function () {
dropdown.classList.add('is-open');
wrapper.classList.add('is-open');
});
dropdown.addEventListener('mouseout', function () {
dropdown.classList.remove('is-open');
wrapper.classList.remove('is-open');
});
});
// Get navigation height
var navHeight = nav.offsetHeight;
document.documentElement.style.setProperty('--nav-height', navHeight + 'px');
}
// Run the function to initialize the navigation menu
initializeNavMenu();
/*-----------------------------------------------------------*/
/* Navigation (Legacy) */
/*-----------------------------------------------------------*/
function initializeNavMenu() {
var nav = document.querySelector('.nav');
if (!nav) return; // Stop execution if nav is not found
// Get the button
var menuButton = document.querySelector('.nav-menu_btn');
if (menuButton) {
menuButton.addEventListener('click', function () {
var navMenu = menuButton.parentElement.querySelector('.nav-menu');
if (!navMenu) return;
navMenu.classList.toggle('is-open');
var menuLines = menuButton.querySelectorAll('.menu-line');
menuLines.forEach(function (line) {
line.classList.toggle('is-open');
});
});
}
// Dropdown button
let dropdowns = document.querySelectorAll('.nav-dropdown');
// Loop through each .nav-dropdown element
dropdowns.forEach(function (dropdown) {
let wrapper = dropdown.querySelector('.nav-dropdown_wrapper');
if (!wrapper) return;
dropdown.addEventListener('mouseover', function () {
dropdown.classList.add('is-open');
wrapper.classList.add('is-open');
});
dropdown.addEventListener('mouseout', function () {
dropdown.classList.remove('is-open');
wrapper.classList.remove('is-open');
});
});
// Get navigation height
var navHeight = nav.offsetHeight;
document.documentElement.style.setProperty('--nav-height', navHeight + 'px');
}
// Run the function to initialize the navigation menu
initializeNavMenu();