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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions assets/scss/_styles_project.scss
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ body {
-webkit-font-smoothing: antialiased;
font-family: $font-family-base;
padding-top: 90px; // navbar height
overflow-x: hidden; // Prevent horizontal scroll

}

.logo-img {
Expand Down
95 changes: 57 additions & 38 deletions static/scripts/main.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
// Register Plugin
gsap.registerPlugin(ScrollTrigger);

// Detect small-device viewport once on load (used by scroll-animation helpers)
const isMobile = window.matchMedia('(max-width: 768px)').matches;

// 1. Header Timeline
let tl = gsap.timeline({
defaults: { ease: "power4.out" }
Expand Down Expand Up @@ -68,13 +71,12 @@ const initMarquee = () => {
// Helper: per-element scrub entrance
const scrubEach = (elements, props, triggerEl, startBase, endBase, offsetPer) => {
elements.forEach((el, i) => {
const startVal = startBase - i * offsetPer;
const endVal = endBase - i * offsetPer;
gsap.from(el, Object.assign({}, props, {
scrollTrigger: {
trigger: triggerEl,
start: 'top ' + (startBase - i * offsetPer) + '%',
end: 'top ' + (endBase - i * offsetPer) + '%',
scrub: 1,
}
scrollTrigger: isMobile
? { trigger: triggerEl, start: 'top ' + Math.min(startVal + 5, 100) + '%', toggleActions: "play none none none" }
: { trigger: triggerEl, start: 'top ' + startVal + '%', end: 'top ' + endVal + '%', scrub: 1 },
}));
});
};
Expand Down Expand Up @@ -356,6 +358,16 @@ const initScrollPieces = () => {
}
};

// Mobile-aware ScrollTrigger config helper.
// On small devices: fires ~5% earlier and plays once (no scrub-reversal when scrolling back up).
const stConfig = (trigger, start, end, scrubVal = 1) => {
if (isMobile) {
const mStart = start.replace(/(\d+)%/, (_, n) => Math.min(+n + 5, 100) + '%');
return { trigger, start: mStart, toggleActions: "play none none none" };
}
return { trigger, start, end, scrub: scrubVal };
};

const initScrollAnimations = () => {
if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) return;

Expand Down Expand Up @@ -395,23 +407,25 @@ const initScrollAnimations = () => {
hero.parentNode.insertBefore(wrapper, hero);
wrapper.appendChild(hero);

gsap.to(wrapper, {
scale: 0.92, opacity: 1, y: -40,
scrollTrigger: {
trigger: wrapper,
start: 'bottom 30%', // recession begins when bottom of hero reaches 60% viewport
end: 'bottom top',
scrub: 1,
}
});
if (!isMobile) {
gsap.to(wrapper, {
scale: 0.92, opacity: 1, y: -40,
scrollTrigger: {
trigger: wrapper,
start: 'bottom 30%',
end: 'bottom top',
scrub: 1,
}
});
}
}

// ── Customers Section ──
const customersSection = document.querySelector('.customers-section');
if (customersSection) {
gsap.from('.customers-title', {
y: 30, opacity: 0,
scrollTrigger: { trigger: customersSection, start: 'top 90%', end: 'top 55%', scrub: 1 }
scrollTrigger: stConfig(customersSection, 'top 90%', 'top 55%'),
});
}

Expand All @@ -420,19 +434,19 @@ const initScrollAnimations = () => {
if (heroSection) {
gsap.from('.hero-badge', {
y: 40, opacity: 0,
scrollTrigger: { trigger: heroSection, start: 'top 90%', end: 'top 50%', scrub: 1 }
scrollTrigger: stConfig(heroSection, 'top 90%', 'top 50%'),
});
gsap.from('.hero-title', {
y: 80, opacity: 0,
scrollTrigger: { trigger: heroSection, start: 'top 88%', end: 'top 40%', scrub: 1 }
scrollTrigger: stConfig(heroSection, 'top 88%', 'top 40%'),
});
gsap.from('.hero-subtitle', {
y: 100, opacity: 0,
scrollTrigger: { trigger: heroSection, start: 'top 85%', end: 'top 35%', scrub: 1 }
scrollTrigger: stConfig(heroSection, 'top 85%', 'top 35%'),
});
gsap.from('.hero-actions', {
y: 60, opacity: 0,
scrollTrigger: { trigger: heroSection, start: 'top 80%', end: 'top 35%', scrub: 1 }
scrollTrigger: stConfig(heroSection, 'top 80%', 'top 35%'),
});

const statItems = heroSection.querySelectorAll('.stat-item');
Expand All @@ -442,31 +456,33 @@ const initScrollAnimations = () => {

ScrollTrigger.create({
trigger: heroSection,
start: 'top 70%',
once: true,
start: isMobile ? 'top 75%' : 'top 70%',
toggleActions: "play none none none",
onEnter: animateCounters,
});

// Recession — only after scrolling well past
gsap.to(heroSection, {
opacity: 0.4, y: -30,
scrollTrigger: { trigger: heroSection, start: 'bottom 10%', end: 'bottom top', scrub: 1 }
});
// Recession — desktop only (no reverse scrub on mobile)
if (!isMobile) {
gsap.to(heroSection, {
opacity: 0.4, y: -30,
scrollTrigger: { trigger: heroSection, start: 'bottom 10%', end: 'bottom top', scrub: 1 }
});
}
}

// ── Demo Section ──
const demoSection = document.querySelector('.demo-section');
if (demoSection) {
gsap.from('.demo-header', {
y: 50, opacity: 0,
scrollTrigger: { trigger: demoSection, start: 'top 88%', end: 'top 50%', scrub: 1 }
scrollTrigger: stConfig(demoSection, 'top 88%', 'top 50%'),
});

const demoContainer = document.querySelector('.demo-container');
if (demoContainer) {
gsap.from(demoContainer, {
scale: 0.88, opacity: 0, y: 60,
scrollTrigger: { trigger: demoContainer, start: 'top 90%', end: 'top 35%', scrub: 1 }
scrollTrigger: stConfig(demoContainer, 'top 90%', 'top 35%'),
});
}

Expand All @@ -475,10 +491,13 @@ const initScrollAnimations = () => {
scrubEach(personaCards, { y: 60, opacity: 0, scale: 0.94 }, '.demo-personas', 90, 50, 5);
}

gsap.to(demoSection, {
opacity: 0.5, y: -20,
scrollTrigger: { trigger: demoSection, start: 'bottom 40%', end: 'bottom top', scrub: 1 }
});
// Recession — desktop only (no reverse scrub on mobile)
if (!isMobile) {
gsap.to(demoSection, {
opacity: 0.5, y: -20,
scrollTrigger: { trigger: demoSection, start: 'bottom 40%', end: 'bottom top', scrub: 1 }
});
}
}

// ── Capabilities Section ──
Expand All @@ -500,7 +519,7 @@ const initScrollAnimations = () => {
if (ctaBox) {
gsap.from(ctaBox, {
y: 40, opacity: 0, scale: 0.96,
scrollTrigger: { trigger: ctaBox, start: 'top 90%', end: 'top 55%', scrub: 1 }
scrollTrigger: stConfig(ctaBox, 'top 90%', 'top 55%'),
});
}
}
Expand All @@ -527,15 +546,15 @@ const initScrollAnimations = () => {
if (browserShell) {
gsap.from(browserShell, {
scale: 0.82, opacity: 0, y: 80,
scrollTrigger: { trigger: '.browser', start: 'top 90%', end: 'top 30%', scrub: 1 }
scrollTrigger: stConfig('.browser', 'top 90%', 'top 30%'),
});
}

const browserStand = document.querySelector('.browser-stand');
if (browserStand) {
gsap.from(browserStand, {
scaleX: 0.5, opacity: 0,
scrollTrigger: { trigger: browserStand, start: 'top 95%', end: 'top 65%', scrub: 1 }
scrollTrigger: stConfig(browserStand, 'top 95%', 'top 65%'),
});
}

Expand All @@ -553,7 +572,7 @@ const initScrollAnimations = () => {

gsap.from(divider, {
opacity: 0, scaleX: 0.3,
scrollTrigger: { trigger: divider, start: 'top 90%', end: 'top 70%', scrub: 1 }
scrollTrigger: stConfig(divider, 'top 90%', 'top 70%'),
});
});
};
Expand Down Expand Up @@ -588,4 +607,4 @@ document.addEventListener("DOMContentLoaded", () => {
onLeaveBack: () => header.classList.remove("scrolled"),
});
}
});
});
Loading