let mm = gsap.matchMedia();
CustomEase.create(
"loaderEase",
"M0,0,C0,0,0.10,0.34,0.238,0.442,0.305,0.506,0.322,0.514,0.396,0.54,0.478,0.568,0.468,0.56,0.522,0.584,0.572,0.606,0.61,0.719,0.714,0.826,0.798,0.912,1,1,1,1"
);
CustomEase.create("primary-ease", "M0,0 C0.15,0 0.15,1 1,1");
CustomEase.create(
"slideEase",
"M0,0 C0.071,0.505 0.192,0.726 0.318,0.852 0.45,0.984 0.504,1 1,1"
);
// Lenis things
let lenis;
Konva.pixelRatio = 1;
let preloaderLottie;
let pageLottie;
let contactLottie;
const lottieWrap = document.querySelector(".preloader-lottie");
const footerLottieWrap = document.querySelector(".lottie-holder");
const contactLottieWrap = document.querySelector(".contact-lottie");
const loadUpElements = document.querySelectorAll("[load-up]");
const loadFadeElements = document.querySelectorAll("[load-fade]");
const loaderProgress = document.querySelector(".loader_progress");
const loader = document.querySelector(".preloader");
pageLottie = lottie.loadAnimation({
container: footerLottieWrap,
renderer: "svg",
loop: false,
autoplay: false,
path: "https://cdn.prod.website-files.com/6762c3ab555175366e3368ed/67f50430b68a3ba8c2366b0d_Robot_FooterWordmark_Red.json",
});
if (loader && getComputedStyle(loader).display === "flex") {
let animationCompleted = false;
preloaderLottie = lottie.loadAnimation({
container: lottieWrap,
renderer: "svg",
loop: false,
autoplay: false,
path: "https://cdn.prod.website-files.com/674ee1defa0316684b52d0f1/67599393ca97704fb7b75d2e_Robot_PreLoader_V3_Red.json",
});
contactLottie = lottie.loadAnimation({
container: contactLottieWrap,
renderer: "svg",
loop: false,
autoplay: false,
path: "https://cdn.prod.website-files.com/674ee1defa0316684b52d0f1/67599393ca97704fb7b75d2e_Robot_PreLoader_V3_Red.json",
});
// Set Lottie playback speed (optional)
preloaderLottie.setSpeed(2);
preloaderLottie.addEventListener("DOMLoaded", () => {
const lottieDuration = 2;
preloaderLottie.play();
let counter = {
value: 0,
};
function updateLoaderText() {
let progress = Math.round(counter.value);
$(".preloader-progress").text(progress);
}
gsap.to(counter, {
value: 99,
onUpdate: updateLoaderText,
duration: lottieDuration,
ease: "loaderEase",
});
preloaderLottie.addEventListener("complete", () => {
animationCompleted = true;
// Complete the counter to 100 and then fire loaderComplete immediately
gsap.to(counter, {
value: 100,
onUpdate: updateLoaderText,
duration: 0.2,
ease: "power2.out",
onComplete: loaderComplete,
});
});
});
function loaderComplete() {
preloaderLottie.pause();
const preloaderTl = gsap.timeline();
preloaderTl.to(loader, {
clipPath: "inset(0% 0% 100% 0%)",
duration: 0.8,
ease: "primary-ease",
});
preloaderTl.fromTo(
".home-hero_bg",
{
scale: 2,
// clipPath: "polygon(0% 0%, 100% 0%, 100% 0%, 0% 0%)",
},
{
scale: 1,
// clipPath: "polygon(0% 0%, 100% 0%, 100% 100%, 0% 100%)",
duration: 2,
ease: "expo.out",
},
"<"
);
preloaderTl.fromTo(
".home-hero_vid",
{
yPercent: 20,
clipPath: "polygon(0% 0%, 100% 0%, 100% 0%, 0% 0%)",
},
{
yPercent: 0,
clipPath: "polygon(0% 0%, 100% 0%, 100% 100%, 0% 100%)",
duration: 1.4,
ease: "expo.out",
},
"<15%"
);
}
} else {
if (loader) loader.style.display = "none";
}
$("[pixel-container]").each(function () {
const container = $(this);
const img = container.find("img");
if (!img.length) return;
const imgSrc = img.attr("src");
const width = container.width();
const height = container.height();
const initialPixelSize = parseInt(container.attr("pixel-container")) || 80;
img.remove();
const konvaDiv = $("
")
.css({ width, height })
.appendTo(container);
Konva.Image.fromURL(imgSrc, function (konvaImage) {
const stage = new Konva.Stage({
container: konvaDiv[0],
width,
height,
});
const layer = new Konva.Layer();
konvaImage.setAttrs({
x: 0,
y: 0,
width,
height,
});
konvaImage.cache();
konvaImage.filters([Konva.Filters.Pixelate]);
konvaImage.pixelSize(initialPixelSize);
layer.add(konvaImage);
stage.add(layer);
let pixelateTween;
const galleryThumb = container.closest("[gallery-thumb]")[0];
if (galleryThumb) {
const observer = new MutationObserver(() => {
const isActive = $(galleryThumb).hasClass("is-active");
if (pixelateTween) pixelateTween.destroy();
pixelateTween = new Konva.Tween({
node: konvaImage,
duration: 0.4,
pixelSize: isActive ? 1 : initialPixelSize,
onUpdate: () => layer.batchDraw(),
});
pixelateTween.play();
});
observer.observe(galleryThumb, {
attributes: true,
attributeFilter: ["class"],
});
}
});
});
function lenisHorizontal() {
lenis = new Lenis({
orientation: "horizontal",
infinite: true,
// syncTouch: true,
// syncTouchLerp: 0.1,
});
lenis.on("scroll", ScrollTrigger.update);
gsap.ticker.add((time) => {
lenis.raf(time * 1000);
});
gsap.ticker.lagSmoothing(0);
lenis.scrollTo(0, {
immediate: true,
});
}
function lenisVert() {
lenis = new Lenis();
lenis.on("scroll", ScrollTrigger.update);
gsap.ticker.add((time) => {
lenis.raf(time * 1000);
});
gsap.ticker.lagSmoothing(0);
}
// Decide which one to run based on the URL
window.addEventListener("DOMContentLoaded", () => {
if (window.location.href.includes("contact")) {
lenisHorizontal();
contactInfinite();
} else {
lenisVert();
}
if (document.querySelector(".threejs-img")) {
robotThree();
}
});
// Lenis things
function nextPrev() {
const currentArticleNumber = parseInt($(".current-project").text());
const $nextProjectItems = $(".next-project_item"); // Cache the jQuery collection
// Find the next item first
let $nextItem = null;
$nextProjectItems.each(function () {
const nextArticleNumber = parseInt(
$(this).find(".next-project_name").text()
);
if (nextArticleNumber === currentArticleNumber + 1) {
$nextItem = $(this);
return false; // Break the loop once found
}
});
// If no next item found, look for item with number 1 (reset case)
if (!$nextItem) {
$nextProjectItems.each(function () {
const nextArticleNumber = parseInt(
$(this).find(".next-project_name").text()
);
if (nextArticleNumber === 1) {
$nextItem = $(this);
return false; // Break the loop once found
}
});
}
// Remove all items except the next one
$nextProjectItems.each(function () {
if (this !== $nextItem[0]) {
// Compare DOM elements
$(this).remove(); // Remove from DOM entirely
} else {
$(this).addClass("current-next"); // Keep and mark as current
}
});
}
nextPrev();
function introTransition() {
let transitionTrigger = $(".transition-holder");
let transitionPanels = $(".transition-el");
let transitionTl = gsap.timeline({
scrollTrigger: {
trigger: transitionTrigger,
start: "top bottom",
end: "top top",
endTrigger: ".home-content_section",
scrub: true,
},
defaults: {
ease: "primary-ease",
},
});
transitionTl.fromTo(
transitionPanels,
{
top: "100%",
},
{
top: "0%",
stagger: {
each: 0.1,
from: "random",
ease: "primary-ease",
},
}
);
}
// introTransition();
$("[img-hover_wrap]").each(function () {
let hoverImgs = $(this).find(".hover-img");
let interval;
$(this).hover(
function () {
let index = 0;
$(".hover-img").removeClass("show-img");
$(hoverImgs[index]).addClass("show-img");
index++;
interval = setInterval(() => {
$(".hover-img").removeClass("show-img");
$(hoverImgs[index]).addClass("show-img");
index = (index + 1) % hoverImgs.length;
}, 400);
},
function () {
clearInterval(interval);
$(".hover-img").removeClass("show-img");
}
);
gsap.set(".img-list", { xPercent: -50, yPercent: -50 });
let xTo = gsap.quickTo(".img-list", "x", { duration: 0.6, ease: "power3" });
let yTo = gsap.quickTo(".img-list", "y", { duration: 0.6, ease: "power3" });
window.addEventListener("mousemove", (e) => {
xTo(e.clientX);
yTo(e.clientY);
});
});
mm.add("(min-width: 992px) and (pointer: fine)", () => {
let scrambleLinks = $("[data-cursor]");
scrambleLinks.each(function () {
let text = $(this).text();
$(this).on("mouseenter", function () {
gsap.to($(this), {
scrambleText: {
text: text,
chars: "$&*6RBT0",
speed: 1,
// tweenLength: false,
},
duration: 0.4,
});
});
});
});
$("[video-player]").each(function (index) {
let playVidBtn = $(this).find("[video-trigger]");
let videoPopupEl = $(this).find(".video-popup");
let closeVidBtn = $(this).find("[video-close]");
let videoItem = $(this).find(".plyr_component");
let videoBg = $(this).find(".video-popup_bg");
let thisComponent = $(this);
const controls = `
`;
let player = new Plyr(thisComponent.find(".plyr_video")[0], {
controls,
resetOnEnd: true,
});
let videoPopupTl = gsap.timeline({
paused: true,
defaults: {
ease: "primary-ease",
duration: 0.5,
},
onStart: () => {
lenis.stop();
},
onReverseComplete: () => {
lenis.start();
},
});
videoPopupTl.set(videoPopupEl, {
display: "flex",
});
videoPopupTl.to(videoBg, {
opacity: 1,
});
videoPopupTl.fromTo(
videoItem,
{
opacity: 0,
},
{
opacity: 1,
},
"<"
);
videoPopupTl.from(
closeVidBtn,
{
opacity: 0,
duration: 0.6,
},
"<"
);
videoPopupTl.set(videoBg, {
pointerEvents: "auto",
});
playVidBtn.on("click", function () {
videoPopupTl.timeScale(1).restart();
player.play();
});
closeVidBtn.on("click", function () {
videoPopupTl.timeScale(2).reverse();
player.pause();
});
player.on("ended", (event) => {
videoPopupTl.timeScale(2).reverse();
});
mm.add("(min-width: 992px) and (pointer: fine)", () => {
player.on("pause", () => {
$("[h-cursor_text]").text("Play");
});
player.on("play", () => {
$("[h-cursor_text]").text("Pause");
});
});
});
// $("[data-scroll]").each(function (index) {
// let floatingText = gsap.timeline({
// scrollTrigger: {
// trigger: ".home-content_section",
// start: "top bottom",
// end: "bottom top",
// // markers: true,
// scrub: true,
// },
// });
// mm.add("(min-width: 992px)", () => {
// floatingText.to($(this), {
// top: "130%",
// ease: "linear",
// });
// });
// });
$("[data-scroll]").each(function (index) {
let floatingEl = gsap.timeline({
scrollTrigger: {
trigger: "[data-scroll-trigger]",
start: "clamp(top bottom)",
end: "clamp(bottom top)",
// markers: true,
scrub: true,
},
});
mm.add("(min-width: 992px)", () => {
floatingEl.to($(this), {
y: "-10rem",
ease: "linear",
});
});
});
$("[push-down-wrap]").each(function (index) {
let pushEl = $(this).find("[push-down]");
let floatingEl = gsap.timeline({
scrollTrigger: {
trigger: $(this),
start: "clamp(top top)",
end: "clamp(bottom top)",
pin: pushEl,
pinSpacing: false,
scrub: true,
},
});
// mm.add("(min-width: 992px)", () => {
floatingEl.to(pushEl, {
yPercent: -100,
ease: "linear",
});
// });
});
//
$("[smooth-pin]").each(function (index) {
let pinEl = $(this).find("[pin-el]");
let pinnedTl = gsap.timeline({
scrollTrigger: {
trigger: $(this),
start: "clamp(top bottom)",
end: "clamp(bottom bottom)",
// markers: true,
// pinSpacing: false,
scrub: true,
},
});
mm.add("(min-width: 992px)", () => {
pinnedTl.to(pinEl, {
y: "200vh",
ease: "sine.inOut",
});
});
});
$("[smooth-slow]").each(function (index) {
let pinElSmooth = $(this);
let pinnedSmoothTl = gsap.timeline({
scrollTrigger: {
trigger: $(this),
start: "clamp(top top)",
end: "clamp(bottom bottom)",
endTrigger: "[smooth-pin]",
pin: pinElSmooth,
pinSpacing: false,
scrub: true,
},
});
mm.add("(min-width: 992px)", () => {
pinnedSmoothTl.to(pinElSmooth, {
yPercent: -20,
ease: "none",
});
});
});
//contact-panel
document.addEventListener("DOMContentLoaded", () => {
let footerItems = $(".footer-component");
let footerLottie = gsap.timeline({
scrollTrigger: {
trigger: "[footer-trigger]",
start: "bottom bottom",
// markers: true,
onEnter: () => {
footerLottie.restart();
pageLottie.setSpeed(1.3);
pageLottie.goToAndPlay(0, true);
},
},
});
footerLottie.fromTo(
footerItems.find("[data-shift]"),
{
clipPath: "polygon(0% 0%, 100% 0%, 100% 0%, 0% 0%)",
yPercent: -100,
},
{
clipPath: "polygon(0% 0%, 100% 0%, 100% 100%, 0% 100%)",
yPercent: 0,
stagger: {
each: 0.065,
},
ease: "primary-ease",
duration: 0.5,
}
);
});
function init() {
function menuTransition() {
let menuOpenTrigger = $("[menu-open]");
let menuCloseTrigger = $(".menu-close");
let menuBg = $(".menu-bg");
let menuEl = $(".menu-el");
let menuOverlay = $(".menu-overlay");
let menuContent = $(".menu-layout");
let menuOpenTl = gsap.timeline({
paused: true,
defaults: {
ease: "circ.inOut",
duration: 1,
},
});
menuOpenTl.set(menuEl, {
display: "flex",
// clipPath: "polygon(0% 0%, 100% 0%, 100% 100%, 0% 100%)",
});
menuOpenTl.fromTo(
menuOverlay,
{
opacity: 0,
},
{
opacity: 0.6,
ease: "sine.inOut",
}
);
menuOpenTl.fromTo(
menuBg,
{
top: "-100%",
},
{
top: "0%",
},
"<"
);
menuOpenTl.fromTo(
menuContent.find("[data-shift"),
{
yPercent: -100,
clipPath: "polygon(0% 100%, 100% 100%, 100% 100%, 0% 100%)",
},
{
yPercent: 0,
clipPath: "polygon(0% 0%, 100% 0%, 100% 100%, 0% 100%)",
stagger: 0.025,
ease: "expo.out",
duration: 1,
},
"<50%"
);
menuOpenTl.fromTo(
menuEl.find("[menu-fade"),
{
opacity: 0,
},
{
opacity: 1,
ease: "expo.out",
duration: 1,
},
"<"
);
menuOpenTrigger.on("click", function () {
menuOpenTl.timeScale(1).restart();
lenis.stop();
});
menuCloseTrigger.on("click", function () {
menuOpenTl.timeScale(2).reverse();
lenis.start();
});
}
menuTransition();
const date = new Date();
const options = {
hour: "2-digit",
minute: "2-digit",
};
// Manually append GMT+2
const timeString = date.toLocaleTimeString("en-GB", options) + " (GMT+2)";
$("[display-time]").text(timeString);
mm.add("(min-width: 992px) and (pointer: fine)", () => {
gsap.set(".cursor", { xPercent: 0, yPercent: 0 });
let xTo = gsap.quickTo(".cursor", "x", { duration: 0.6, ease: "power3" });
let yTo = gsap.quickTo(".cursor", "y", { duration: 0.6, ease: "power3" });
window.addEventListener("mousemove", (e) => {
xTo(e.clientX + 20);
yTo(e.clientY + 20);
});
let links = document.querySelectorAll("[move-cursor]");
links.forEach((link) => {
let text = link.getAttribute("move-cursor");
let target = $(this);
link.addEventListener("mouseenter", () => {
$("[h-cursor_text]").text(text);
gsap.set(".cursor [h-cursor_text]", {
scrambleText: {
text: text,
chars: "$&*6RBT0",
speed: 0.2,
},
duration: 1,
});
});
link.addEventListener("mousemove", () => {
$("body").addClass("c-vis");
});
link.addEventListener("mouseleave", () => {
$("body").removeClass("c-vis");
});
});
});
$("[count-items=wrap]").each(function () {
const countWrap = $(this);
const itemCounts = {}; // Object to store counts per category
// Find all countable items inside this wrap
countWrap.find("[count-items]").each(function () {
const countType = $(this).attr("count-items");
// Ignore "wrap" and "total" elements
if (countType !== "wrap" && countType !== "total") {
itemCounts[countType] = (itemCounts[countType] || 0) + 1;
}
});
// Update total elements based on their specific category
countWrap.find("[count-items^=total]").each(function () {
const totalType = $(this).attr("count-items").replace("total-", "");
const countValue = itemCounts[totalType] || 0;
$(this).text(countValue.toString().padStart(2, "0"));
});
});
// $("[data-pixelate]").each(function () {
// const container = $(this);
// const originalImage = container.find("img");
// const originalVideo = container.find("video");
// const canvas = $('')[0];
// container.append(canvas);
// const ctx = canvas.getContext("2d");
// function setCanvasDimensions() {
// const rect = container[0].getBoundingClientRect();
// const dpr = window.devicePixelRatio || 1;
// canvas.style.width = rect.width + "px";
// canvas.style.height = rect.height + "px";
// canvas.width = rect.width * dpr;
// canvas.height = rect.height * dpr;
// ctx.scale(dpr, dpr);
// return { width: rect.width, height: rect.height, dpr: dpr };
// }
// function createPixelation(pixelSize) {
// let mediaElement = originalImage.length
// ? originalImage[0]
// : originalVideo[0];
// if (!mediaElement) return;
// if (mediaElement.tagName === "IMG" && !mediaElement.complete) {
// mediaElement.onload = function () {
// createPixelation(pixelSize);
// };
// return;
// }
// const rect = container[0].getBoundingClientRect();
// const { width, height } = rect;
// canvas.width = width;
// canvas.height = height;
// const mediaWidth = mediaElement.videoWidth || mediaElement.naturalWidth;
// const mediaHeight =
// mediaElement.videoHeight || mediaElement.naturalHeight;
// const mediaAspectRatio = mediaWidth / mediaHeight;
// const canvasAspectRatio = width / height;
// let drawWidth, drawHeight, offsetX, offsetY;
// if (mediaAspectRatio > canvasAspectRatio) {
// drawHeight = height;
// drawWidth = mediaWidth * (height / mediaHeight);
// offsetX = (width - drawWidth) / 2;
// offsetY = 0;
// } else {
// drawWidth = width;
// drawHeight = mediaHeight * (width / mediaWidth);
// offsetX = 0;
// offsetY = (height - drawHeight) / 2;
// }
// const tempCanvas = document.createElement("canvas");
// const tempCtx = tempCanvas.getContext("2d");
// tempCanvas.width = Math.ceil(drawWidth / pixelSize);
// tempCanvas.height = Math.ceil(drawHeight / pixelSize);
// tempCtx.drawImage(
// mediaElement,
// 0,
// 0,
// tempCanvas.width,
// tempCanvas.height
// );
// ctx.imageSmoothingEnabled = false;
// ctx.clearRect(0, 0, width, height);
// ctx.drawImage(
// tempCanvas,
// 0,
// 0,
// tempCanvas.width,
// tempCanvas.height,
// offsetX,
// offsetY,
// drawWidth,
// drawHeight
// );
// }
// createPixelation(10);
// if (originalVideo.length) {
// originalVideo.on("play", function () {
// setInterval(() => createPixelation(10), 10);
// });
// }
// $(window).on("resize", function () {
// createPixelation(12);
// });
// });
// USE THIS TO RELOAD PAGE GEOFF
// document.addEventListener("visibilitychange", function () {
// if (document.hidden) {
// // User has left - start the timer
// awayTime = Date.now();
// } else {
// // User has returned - check how long they were away
// const returnTime = Date.now();
// const timeAway = (returnTime - awayTime) / 1000; // convert to seconds
// if (timeAway > 50) {
// alert("You were away for more than 50 seconds!");
// }
// console.log(`User was away for ${timeAway.toFixed(1)} seconds.`);
// }
// });
$(".featured-work").each(function () {
let featuredItems = $(this).find(".featured-work_item");
let projName = $(this).find(".project-slide_name");
let projIndex = $(this).find(".current-project_number");
let slideNext = $(this).find(".slide-next");
let slidePrev = $(this).find(".slide-prev");
let currentIndex = 0;
let interval;
let isHovered = false;
gsap.set(featuredItems, {
flex: "auto",
});
function cycleHover() {
if (!isHovered) {
currentIndex = (currentIndex + 1) % featuredItems.length;
triggerHover(featuredItems.eq(currentIndex));
}
}
function triggerHover($item) {
let $this = $item;
let $siblings = featuredItems.not($item);
let $name = $item.find(".hidden-name").text();
let $currentNumber = $item.index() + 1;
featuredItems.removeClass("is-active");
$item.addClass("is-active");
let workTl = gsap.timeline();
gsap.to($this, {
width: "80%",
ease: "primary-ease",
duration: 0.5,
onComplete: function () {
// alert("test");
},
});
gsap.to($this.find("canvas"), {
ease: "primary-ease",
duration: 0.5,
});
gsap.set($this.find("canvas"), {
// width: "400%",
opacity: 0,
});
gsap.to($siblings, {
width: `${20 / $siblings.length}%`,
ease: "primary-ease",
duration: 0.5,
});
gsap.set($siblings.find("canvas"), {
opacity: 1,
});
let currentText = projName.text();
// Check if the text is already the same as $name
if (currentText !== $name) {
gsap.to(projName, {
scrambleText: {
text: $name,
chars: "$&*~%^#6RBT0",
speed: 1,
},
duration: 0.4,
});
}
projIndex.text(
$currentNumber < 10 ? "0" + $currentNumber : $currentNumber
);
currentIndex = featuredItems.index($this);
}
function startAutoCycle() {
interval = setInterval(cycleHover, 3000);
}
function stopAutoCycle() {
clearInterval(interval);
}
// Navigation functions for next/prev buttons
function goToNext(e) {
if (e) e.preventDefault();
stopAutoCycle();
currentIndex = (currentIndex + 1) % featuredItems.length;
triggerHover(featuredItems.eq(currentIndex));
// Only restart auto cycle on desktop if not hovering
if (
window.matchMedia("(min-width: 992px) and (pointer: fine)").matches &&
!isHovered
) {
startAutoCycle();
}
}
function goToPrev(e) {
if (e) e.preventDefault();
stopAutoCycle();
currentIndex =
(currentIndex - 1 + featuredItems.length) % featuredItems.length;
triggerHover(featuredItems.eq(currentIndex));
// Only restart auto cycle on desktop if not hovering
if (
window.matchMedia("(min-width: 992px) and (pointer: fine)").matches &&
!isHovered
) {
startAutoCycle();
}
}
// Add click handlers for next/prev buttons
slideNext.on("click", goToNext);
slidePrev.on("click", goToPrev);
mm.add("(min-width: 992px) and (pointer: fine)", () => {
featuredItems.on("mouseenter", function () {
isHovered = true;
stopAutoCycle();
triggerHover($(this));
});
featuredItems.on("mouseleave", function () {
isHovered = false;
startAutoCycle();
});
});
mm.add("(max-width: 991px)", () => {
featuredItems.on("click", function () {
isHovered = true;
stopAutoCycle();
triggerHover($(this));
});
});
// Initialize the first item and start auto cycle
triggerHover(featuredItems.eq(0));
// featuredItems.eq(0).mouseenter();
// featuredItems.eq(0).hover();
startAutoCycle();
});
$(".categories-section").each(function () {
let categoryTitles = $(this).find(".category-title_item");
let categoryTriggers = $(this).find(".categories-trigger_item");
let catIndex = $(this).find("[current-slide]");
let catEyebrow = $(this).find(".categories-eyebrow_item");
let catReveal = $(this).find("[cat-reveal]");
let catTl = gsap.timeline({
paused: true,
});
catTl.fromTo(
".nav-eyebrow",
{
clipPath: "polygon(0% 0%, 100% 0%, 100% 100%, 0% 100%)",
yPercent: 0,
opacity: 1,
},
{
clipPath: "polygon(0% 100%, 100% 100%, 100% 100%, 0% 100%)",
yPercent: 100,
opacity: 0,
ease: "expo.out",
duration: 0.3,
}
);
catTl.fromTo(
catReveal,
{
clipPath: "polygon(0% 0%, 100% 0%, 100% 0%, 0% 0%)",
yPercent: -100,
opacity: 0,
},
{
clipPath: "polygon(0% 0%, 100% 0%, 100% 100%, 0% 100%)",
yPercent: 0,
opacity: 1,
stagger: {
each: 0.05,
},
ease: "expo.out",
duration: 0.3,
},
"<"
);
ScrollTrigger.create({
trigger: $(this),
start: "top top",
end: "bottom top",
endTrigger: ".categories-trigger_wrap",
onEnter: () => {
catTl.restart();
},
onEnterBack: () => {
catTl.restart();
},
onLeaveBack: () => {
catTl.reverse();
},
onLeave: () => {
catTl.reverse();
},
});
categoryTriggers.each(function (index) {
ScrollTrigger.create({
trigger: $(this),
start: "top center",
end: "bottom center",
onEnter: () => {
let slideNumber = String(index + 1).padStart(2, "0");
catIndex.text(slideNumber);
},
onEnterBack: () => {
let slideNumber = String(index + 1).padStart(2, "0");
catIndex.text(slideNumber);
},
});
ScrollTrigger.create({
trigger: $(this),
start: "top center",
end: "bottom center",
onEnter: () => {
catEyebrow.removeClass("is-active");
let activeEyebrow = catEyebrow.eq(index);
activeEyebrow.addClass("is-active");
},
onEnterBack: () => {
catEyebrow.removeClass("is-active");
let activeEyebrow = catEyebrow.eq(index);
activeEyebrow.addClass("is-active");
},
});
// Animation logic (skip first)
if (index >= 1) {
let transitionTl = gsap.timeline({
scrollTrigger: {
trigger: $(this),
start: "top bottom",
end: "top top",
scrub: 0.4,
},
defaults: {
ease: "expo.inOut",
},
});
transitionTl.to(categoryTitles, {
yPercent: "-=100",
});
}
});
});
$("[gallery-section]").each(function () {
let featuredPress = $(this).find("[gallery-thumb]");
let pressName = $(this).find("[gallery-title]");
let pressIndex = $(this).find("[gallery-indicator]");
let pressWebEl = $(this).find("[press-web-target]");
let currentIndex = 0;
let interval;
let isHovered = false;
function cycleHover() {
if (!isHovered) {
currentIndex = (currentIndex + 1) % featuredPress.length;
triggerHover(featuredPress.eq(currentIndex));
}
}
function triggerHover($item) {
let $this = $item;
let index = featuredPress.index($this);
let $siblings = featuredPress.not($item);
let $name = $item.attr("press-name-trigger");
let $web = $item.attr("press-web-trigger");
let $currentNumber = index + 1;
let currentText = pressName.text();
featuredPress.removeClass("is-active");
$item.addClass("is-active");
if (currentText !== $name) {
gsap.to(pressName, {
scrambleText: {
text: $name,
chars: "$&*~%^#60",
speed: 1,
tweenLength: false,
},
duration: 0.4,
});
pressWebEl.text($web);
}
pressIndex.text(
$currentNumber < 10 ? "0" + $currentNumber : $currentNumber
);
currentIndex = index;
// Handle preview-wrap classes
let $allWraps = $(".gallery-items_active");
let $targetWrap = $allWraps.eq(index).find("[gallery-wrap]");
$allWraps.find("[gallery-wrap]").removeClass("hovering");
$targetWrap.addClass("hovering");
}
function startAutoCycle() {
interval = setInterval(cycleHover, 5000);
}
function stopAutoCycle() {
clearInterval(interval);
}
featuredPress.on("mouseenter", function () {
isHovered = true;
stopAutoCycle();
triggerHover($(this));
});
featuredPress.on("mouseleave", function () {
isHovered = false;
startAutoCycle();
let index = featuredPress.index($(this));
});
// Start from the first item
triggerHover(featuredPress.eq(0));
startAutoCycle();
});
$(".bts-item").each(function (index) {
const image = this;
const imgEl = $(this).find(".bts-img");
ScrollTrigger.create({
trigger: image,
start: "top bottom",
end: "bottom top",
scrub: true,
onUpdate({ getVelocity }) {
gsap.fromTo(
image,
{
skewY: `${getVelocity() / 800}deg`,
},
{
skewY: 0,
ease: "none",
}
);
gsap.fromTo(
imgEl,
{
skewY: `-${getVelocity() / 800}deg`,
},
{
skewY: 0,
ease: "none",
},
"<"
);
},
});
});
}
init();
mm.add("(min-width: 992px)", () => {
$("[mouse-imgs_wrap]").each(function () {
const $root = $(this);
const images = [];
$root.find("[mouse-imgs_list] [mouse-imgs_item] img").each(function () {
images.push($(this).attr("src"));
});
let incr = 0;
let oldIncrX = null;
let oldIncrY = null;
const resetDist = $(window).width() / 8;
let indexImg = 0;
let lastMouseX = 0;
let lastMouseY = 0;
let lastScrollTop = $(window).scrollTop();
// Track mouse position globally
$(document).on("mousemove mouseover", function (e) {
lastMouseX = e.clientX;
lastMouseY = e.clientY;
});
// Handle scroll events
$(window).on("scroll", function () {
const currentScrollTop = $(window).scrollTop();
const scrollDelta = currentScrollTop - lastScrollTop;
lastScrollTop = currentScrollTop;
// Check if mouse is over the element
const offset = $root.offset();
const width = $root.outerWidth();
const height = $root.outerHeight();
// Convert client coordinates to page coordinates
const pageX = lastMouseX + window.pageXOffset;
const pageY = lastMouseY + window.pageYOffset;
if (
pageX >= offset.left &&
pageX <= offset.left + width &&
pageY >= offset.top &&
pageY <= offset.top + height
) {
// Mouse is inside the element
const valX = pageX - offset.left;
const valY = pageY - offset.top;
if (oldIncrX === null || oldIncrY === null) {
oldIncrX = valX;
oldIncrY = valY;
return;
}
// Add scroll delta to simulate mouse movement
incr += Math.abs(scrollDelta);
if (incr > resetDist) {
incr = 0;
createMedia(valX, valY, 0, -scrollDelta);
}
oldIncrX = valX;
oldIncrY = valY;
}
});
$root.on("mouseenter", function () {
// Reset on enter to avoid shared state
incr = 0;
oldIncrX = null;
oldIncrY = null;
});
$root.on("mousemove", function (e) {
const offset = $root.offset();
const valX = e.pageX - offset.left;
const valY = e.pageY - offset.top;
if (oldIncrX === null || oldIncrY === null) {
oldIncrX = valX;
oldIncrY = valY;
return;
}
incr += Math.abs(valX - oldIncrX) + Math.abs(valY - oldIncrY);
if (incr > resetDist) {
incr = 0;
createMedia(valX, valY, valX - oldIncrX, valY - oldIncrY);
}
oldIncrX = valX;
oldIncrY = valY;
});
function createMedia(x, y, deltaX, deltaY) {
const image = $("
").attr("src", images[indexImg]).css({
position: "absolute",
left: 0,
top: 0,
pointerEvents: "none",
});
$root.append(image);
const tl = gsap.timeline({
onComplete: function () {
image.remove();
tl.kill();
},
});
tl.fromTo(
image,
{
xPercent: -50 + (Math.random() - 0.5) * 80,
yPercent: -50 + (Math.random() - 0.5) * 10,
scaleX: 1,
scaleY: 1,
},
{
scaleX: 1,
scaleY: 1,
ease: "elastic.out(2, 0.6)",
duration: 0.6,
}
);
tl.fromTo(
image,
{
x: x,
y: y,
rotation: (Math.random() - 0.5) * 20,
},
{
x: x + deltaX * 4,
y: y + deltaY * 4,
rotation: (Math.random() - 0.5) * 20,
ease: "power4.out",
duration: 1.5,
},
"<"
);
tl.to(image, {
duration: 0.3,
scale: 0.5,
delay: 0.1,
ease: "back.in(1.5)",
});
indexImg = (indexImg + 1) % images.length;
}
});
});
// $(window).on("resize", function () {
// instances.forEach(({ $container, renderer, camera, uniforms, text }) => {
// const rect = $container[0].getBoundingClientRect();
// const aspectRatio = rect.width / rect.height;
// camera.left = -1;
// camera.right = 1;
// camera.top = 1 / aspectRatio;
// camera.bottom = -1 / aspectRatio;
// camera.updateProjectionMatrix();
// renderer.setSize(rect.width, rect.height);
// uniforms.u_texture.value = createTextTexture($container, text);
// });
// });
$("[preview-trigger]").on("mouseenter", function () {
const $wrap = $(this).find("[preview-wrap]");
$wrap.removeClass("closing").addClass("hovering");
});
$("[preview-trigger]").on("mouseleave", function () {
const $wrap = $(this).find("[preview-wrap]");
$wrap.removeClass("hovering").addClass("closing");
});
$("[preview-wrap]").on("animationend", function () {
$(this).removeClass("closing");
});
$(".sticky-intro").each(function () {
let vidGrowTrigger = $(this).find(".director-intro_spacer");
let vidGrowEl = $(this).find(".sticky-director_vid");
let vidGrowWrap = $(this).find(".directors-vid_wrap");
mm.add("(min-width: 992px) and (pointer: fine)", () => {
let vidGrowTl = gsap.timeline({
scrollTrigger: {
trigger: vidGrowTrigger,
start: "top bottom",
end: "top top",
scrub: true,
},
defaults: {
ease: "linear",
},
});
vidGrowTl.to(vidGrowEl, {
width: "100%",
});
vidGrowTl.to(
vidGrowWrap,
{
scale: 1,
},
"<"
);
});
});
$(".section-divider").each(function (index) {
let dividerPanels = $(this).find(".divider-path");
gsap.set(dividerPanels, {
yPercent: "random(15, 75, 15)",
});
let dividerTl = gsap.timeline({
scrollTrigger: {
trigger: this,
start: "top bottom",
end: "bottom top",
scrub: true,
},
});
dividerTl.to(dividerPanels, {
yPercent: 0,
stagger: 0.1,
ease: "linear",
});
});
$(".hero-title_visual").on("mousemove", function () {
$(".robot-pixel").addClass("interactive");
});
$(".hero-title_visual").on("mouseleave", function () {
$(".robot-pixel").removeClass("interactive");
});
$(".work-section").each(function (index) {
let $clickedItem = null;
gsap.registerPlugin(CustomEase);
CustomEase.create(
"hop",
"M0,0 C0.053,0.604 0.157,0.72 0.293,0.837 0.435,0.959 0.633,1 1,1"
);
const getItemWidth = () => {
const temp = document.createElement("div");
temp.style.width = "var(--size--13rem)";
temp.style.position = "absolute";
document.body.appendChild(temp);
const width = window.getComputedStyle(temp).width;
document.body.removeChild(temp);
return width;
};
const formatCount = (count) => {
return count < 10 ? `0${count}` : count.toString();
};
const updateCountDisplay = (count) => {
$('[count-items="total-work"]').text(formatCount(count));
};
const updateCurrentPressNumber = (number) => {
$(".current-press_number").text(number);
};
const updateProjectName = (projectName, force = false) => {
const currentText = $(".work-slide_name").text();
if (force || currentText !== projectName) {
gsap.to(".work-slide_name", {
scrambleText: {
text: projectName,
chars: "$&*~%^#6RBT0",
speed: 1,
},
duration: 0.4,
});
}
};
const updateActiveCategories = ($item) => {
const $categoriesDiv = $(".active-categories_div");
$categoriesDiv.empty();
$item.find(".filter-text_hidden").each(function () {
const categoryText = $(this).text();
// Only add the category if it's not "All"
if (categoryText.toLowerCase() !== "all") {
$("")
.addClass("u-text-style-small")
.text(categoryText)
.appendTo($categoriesDiv);
}
});
};
const activateGalleryItem = ($thumbItem, force = false) => {
const originalIndex = $thumbItem.index();
const projectName = $thumbItem.attr("project-name");
const projectDirector = $thumbItem.attr("director");
const projectProducer = $thumbItem.attr("producer");
const directorTarget = $("[director-target]");
const producerTarget = $("[producer-target]");
const $targetGalleryItem = $galleryItems.eq(originalIndex);
if (force || !$targetGalleryItem.hasClass("active-project")) {
$galleryItems.removeClass("active-project");
$targetGalleryItem.addClass("active-project");
$galleryItems.removeClass("hovering");
$targetGalleryItem.addClass("hovering");
updateCurrentPressNumber($thumbItem.attr("data-category-number"));
updateProjectName(projectName);
directorTarget.text(projectDirector);
producerTarget.text(projectProducer);
}
updateActiveCategories($thumbItem);
};
const $items = $(".work-thumbs_list");
const $filterButtons = $(".filters-item");
const $galleryItems = $(".gallery-items_active");
let itemsWidth = $items[0].scrollWidth;
let containerWidth = $(window).width();
let currentX = 0;
let targetX = 0;
const lerpFactor = 0.1;
function handleHoverState() {
$(".work-thumbs_item").on("mouseenter", function () {
$(".work-thumbs_item").removeClass("is-active"); // remove from all
$(this).addClass("is-active"); // add to hovered item
activateGalleryItem($(this)); // activate the item
});
$(".work-thumbs_item").on("mouseleave", function () {
$(".work-thumbs_item").removeClass("is-active");
});
}
$filterButtons.on("click", function () {
const $this = $(this);
$filterButtons.removeClass("active active-category");
$this.addClass("active active-category");
const filterText = $this
.find(".filter-text_hidden")
.text()
.toLowerCase()
.trim();
filterItems(filterText);
// $items
});
function filterItems(filter) {
const $allItems = $(".work-thumbs_item");
const finalWidth = getItemWidth();
let visibleItemCount = 0;
let $firstVisibleItem = null;
const visibleItems = $allItems.filter(function () {
const itemText = $(this).text().toLowerCase();
return (
filter === "all" || filter === "featured" || itemText.includes(filter)
);
});
$allItems.each(function () {
const $item = $(this);
const itemText = $item.text().toLowerCase();
const isCurrentlyHidden = $item.is(":hidden");
if (
filter === "all" ||
filter === "featured" ||
itemText.includes(filter)
) {
visibleItemCount++;
$item.attr("data-category-number", formatCount(visibleItemCount));
if (!$firstVisibleItem) {
$firstVisibleItem = $item;
}
if (isCurrentlyHidden) {
gsap.set($item, { display: "flex", width: "25px" });
gsap.to($item, {
width: finalWidth,
ease: "hop",
duration: 1,
onComplete: updateItemsWidth,
});
}
} else {
gsap.set($item, { display: "none", width: "0px" });
$item.removeAttr("data-category-number");
}
});
updateCountDisplay(visibleItemCount);
if ($firstVisibleItem) {
activateGalleryItem($firstVisibleItem, true);
$clickedItem = $firstVisibleItem;
$(".work-thumbs_item").removeClass("is-active");
$clickedItem.addClass("is-active");
}
resetPosition();
setTimeout(() => {
mm.add("(min-width: 992px) and (pointer: fine)", () => {
updateItemsWidth();
applyMouseMoveEffect();
});
}, 1000);
// handleItemClick();
}
function updateItemsWidth() {
itemsWidth = $items[0].scrollWidth;
}
function resetPosition() {
gsap.to($items, { x: 0, ease: "power2.out", duration: 0.5 });
currentX = 0;
targetX = 0;
mm.add("(max-width: 991px)", () => {
$(".work-thumbs_wrap.w-dyn-list").animate({ scrollLeft: 0 }, 200);
});
}
function applyMouseMoveEffect() {
$items.off("mousemove");
if (itemsWidth > containerWidth) {
$items.on("mousemove", handleMouseMove);
}
}
function handleMouseMove(e) {
const mouseX = e.clientX;
const maxScroll = itemsWidth - containerWidth;
const percentage = mouseX / containerWidth;
targetX = -maxScroll * percentage;
}
function animate() {
currentX += (targetX - currentX) * lerpFactor;
gsap.set($items, { x: currentX });
requestAnimationFrame(animate);
}
function initialize() {
const $firstFilter = $filterButtons.first();
$firstFilter.addClass("active active-category");
const initialFilterText = $firstFilter
.find(".filter-text_hidden")
.text()
.toLowerCase()
.trim();
animate();
filterItems(initialFilterText);
// Delay the click until after everything is set up
setTimeout(() => {
$(".work-thumbs_item:visible").first().trigger("click");
$(".filters-item").first().trigger("click");
}, 200);
}
initialize();
handleHoverState();
$(window).on("resize", function () {
containerWidth = $(window).width();
updateItemsWidth();
mm.add("(min-width: 992px) and (pointer: fine)", () => {
applyMouseMoveEffect();
});
});
mm.add("(min-width: 992px) and (pointer: fine)", () => {
let catReveal = $(this).find("[cat-reveal]");
let catTl = gsap.timeline({
paused: true,
});
catTl.fromTo(
".nav-eyebrow",
{
clipPath: "polygon(0% 0%, 100% 0%, 100% 100%, 0% 100%)",
yPercent: 0,
opacity: 1,
},
{
clipPath: "polygon(0% 100%, 100% 100%, 100% 100%, 0% 100%)",
yPercent: 100,
opacity: 0,
ease: "expo.out",
duration: 0.3,
}
);
catTl.fromTo(
catReveal,
{
clipPath: "polygon(0% 0%, 100% 0%, 100% 0%, 0% 0%)",
yPercent: -100,
opacity: 0,
},
{
clipPath: "polygon(0% 0%, 100% 0%, 100% 100%, 0% 100%)",
yPercent: 0,
opacity: 1,
stagger: {
each: 0.05,
},
ease: "expo.out",
duration: 0.3,
},
"<"
);
ScrollTrigger.create({
trigger: $(this),
start: "top +10px",
end: "bottom -10px",
endTrigger: ".work-section",
// markers: true,
onEnter: () => {
catTl.restart();
},
onEnterBack: () => {
catTl.restart();
},
onLeaveBack: () => {
catTl.reverse();
},
onLeave: () => {
catTl.reverse();
},
});
});
});
// function galleryFilter() {
function lostSkulls() {
class LostSkull {
constructor() {
this.x = Math.random() * (lostWidth - lostSkullSize.width);
this.y = Math.random() * (lostHeight - lostSkullSize.height);
this.vx = (Math.random() - 0.5) * 4; // Random x velocity
this.vy = (Math.random() - 0.5) * 4; // Random y velocity
}
update() {
this.x += this.vx;
this.y += this.vy;
// Slight random motion over time
this.vx += (Math.random() - 0.5) * 0.1;
this.vy += (Math.random() - 0.5) * 0.1;
// Limit maximum speed to avoid chaos
let speedLimit = 4;
this.vx = Math.max(-speedLimit, Math.min(this.vx, speedLimit));
this.vy = Math.max(-speedLimit, Math.min(this.vy, speedLimit));
// Wall collision
if (this.x <= 0 || this.x + lostSkullSize.width >= lostWidth) {
this.vx *= -1;
this.x = Math.max(0, Math.min(this.x, lostWidth - lostSkullSize.width));
}
if (this.y <= 0 || this.y + lostSkullSize.height >= lostHeight) {
this.vy *= -1;
this.y = Math.max(
0,
Math.min(this.y, lostHeight - lostSkullSize.height)
);
}
}
draw() {
lostCtx.drawImage(
lostSkullImage,
this.x,
this.y,
lostSkullSize.width,
lostSkullSize.height
);
}
}
let lostCanvas;
let lostCtx;
let lostWidth, lostHeight;
let lostSkullImage;
let lostTick = 0;
let lostSkulls = [];
let lostSkullSize = { width: 0, height: 0 };
function lostSetup() {
lostCanvas = document.querySelector(".lost-canvas");
lostCtx = lostCanvas.getContext("2d");
lostReset();
lostSkullImage = new Image();
lostSkullImage.onload = () => {
lostSkullSize = {
width: lostSkullImage.naturalWidth,
height: lostSkullImage.naturalHeight,
};
lostInitSkulls();
lostDraw(); // Start animation loop only after image is loaded
};
lostSkullImage.src =
"https://cdn.prod.website-files.com/6762c3ab555175366e3368ed/67ff78d855f46aa1c8926467_robot-404.svg";
window.addEventListener("resize", () => {
lostReset();
});
}
function lostInitSkulls() {
lostSkulls = [];
for (let i = 0; i < 10; i++) {
lostSkulls.push(new LostSkull());
}
}
function lostReset() {
lostWidth = lostCanvas.width = window.innerWidth;
lostHeight = lostCanvas.height = window.innerHeight;
}
function lostDraw() {
requestAnimationFrame(lostDraw);
lostCtx.fillStyle = "rgba(41, 15, 240, 1)";
lostCtx.fillRect(0, 0, lostWidth, lostHeight);
// Update and draw
lostSkulls.forEach((skull, index) => {
skull.update();
for (let j = index + 1; j < lostSkulls.length; j++) {
let other = lostSkulls[j];
if (
skull.x < other.x + lostSkullSize.width &&
skull.x + lostSkullSize.width > other.x &&
skull.y < other.y + lostSkullSize.height &&
skull.y + lostSkullSize.height > other.y
) {
// They overlap
// Calculate centres
let centreSkullX = skull.x + lostSkullSize.width / 2;
let centreSkullY = skull.y + lostSkullSize.height / 2;
let centreOtherX = other.x + lostSkullSize.width / 2;
let centreOtherY = other.y + lostSkullSize.height / 2;
// Differences
let dx = centreSkullX - centreOtherX;
let dy = centreSkullY - centreOtherY;
// Calculate distance
let distance = Math.sqrt(dx * dx + dy * dy) || 1; // avoid division by zero
// Normalise
let nx = dx / distance;
let ny = dy / distance;
// Calculate relative velocity
let vxRel = skull.vx - other.vx;
let vyRel = skull.vy - other.vy;
// Dot product (how much they are moving toward each other)
let dot = vxRel * nx + vyRel * ny;
if (dot < 0) {
// Only bounce if they are moving toward each other
// Exchange momentum
skull.vx -= dot * nx;
skull.vy -= dot * ny;
other.vx += dot * nx;
other.vy += dot * ny;
}
// Push them apart slightly
let pushAmount = 1;
skull.x += nx * pushAmount;
skull.y += ny * pushAmount;
other.x -= nx * pushAmount;
other.y -= ny * pushAmount;
}
}
skull.draw();
});
}
lostSetup();
}
$(".directors-all").each(function () {
// Store references to elements within this specific .directors-all section
var $thisSection = $(this);
var $directorsItem = $thisSection.find(".directors-item");
var $directorsWrap = $thisSection.find(".directors-wrap");
mm.add("(min-width: 992px) and (pointer: fine)", () => {
if ($directorsItem.length && $directorsWrap.length) {
var updateDirectorsWrapPadding = function () {
var itemHeight = $directorsItem.outerHeight();
var paddingBottom = itemHeight * 3;
$directorsWrap.css("padding-bottom", paddingBottom + "px");
};
updateDirectorsWrapPadding();
$(window).on("resize", updateDirectorsWrapPadding);
}
});
});
function robotThree() {
var GUI = lil.GUI;
function clamp(number, min, max) {
return Math.max(min, Math.min(number, max));
}
class Sketch {
constructor(options) {
this.scene = new THREE.Scene();
this.container = options.dom;
this.img = this.container.querySelector("img");
this.width = this.container.offsetWidth;
this.height = this.container.offsetHeight;
this.renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });
this.renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
this.renderer.setSize(this.width, this.height);
this.renderer.setClearColor(0xeeeeee, 0);
this.renderer.physicallyCorrectLights = true;
this.renderer.outputEncoding = THREE.sRGBEncoding;
this.container.appendChild(this.renderer.domElement);
this.camera = new THREE.PerspectiveCamera(
70,
window.innerWidth / window.innerHeight,
0.1,
100
);
var frustumSize = 1;
var aspect = this.width / this.height;
this.camera = new THREE.OrthographicCamera(
frustumSize / -2,
frustumSize / 2,
frustumSize / 2,
frustumSize / -2,
-1000,
1000
);
this.camera.position.set(0, 0, 2);
this.time = 0;
this.mouse = {
x: 0,
y: 0,
prevX: 0,
prevY: 0,
vX: 0,
vY: 0,
};
this.isPlaying = true;
this.settings();
this.addObjects();
this.resize();
this.render();
this.setupResize();
this.mouseEvents();
}
getValue(val) {
return parseFloat(this.container.getAttribute("data-" + val));
}
mouseEvents() {
// Mouse event handler
this.container.addEventListener("mousemove", (e) => {
this.handlePointerMove(e.clientX, e.clientY);
});
// Touch event handlers
this.container.addEventListener("touchstart", (e) => {
if (e.touches.length > 0) {
// Prevent scrolling when interacting with the element
e.preventDefault();
const touch = e.touches[0];
this.handlePointerMove(touch.clientX, touch.clientY);
}
});
this.container.addEventListener("touchmove", (e) => {
if (e.touches.length > 0) {
// Prevent scrolling when interacting with the element
e.preventDefault();
const touch = e.touches[0];
this.handlePointerMove(touch.clientX, touch.clientY);
}
});
}
// Unified handler for both mouse and touch events
handlePointerMove(clientX, clientY) {
const rect = this.container.getBoundingClientRect();
this.mouse.x = (clientX - rect.left) / rect.width;
this.mouse.y = (clientY - rect.top) / rect.height;
this.mouse.vX = this.mouse.x - this.mouse.prevX;
this.mouse.vY = this.mouse.y - this.mouse.prevY;
this.mouse.prevX = this.mouse.x;
this.mouse.prevY = this.mouse.y;
}
settings() {
let that = this;
this.settings = {
grid: this.getValue("grid") || 34,
mouse: this.getValue("mouse") || 0.25,
strength: this.getValue("strength") || 1,
relaxation: this.getValue("relaxation") || 0.9,
};
this.gui = new GUI();
this.gui.add(this.settings, "grid", 2, 1000, 1).onFinishChange(() => {
this.regenerateGrid();
});
this.gui.add(this.settings, "mouse", 0, 1, 0.01);
this.gui.add(this.settings, "strength", 0, 1, 0.01);
this.gui.add(this.settings, "relaxation", 0, 1, 0.01);
}
setupResize() {
mm.add("(min-width: 992px) and (pointer: fine)", () => {
window.addEventListener("resize", this.resize.bind(this));
});
}
resize() {
this.width = this.container.offsetWidth;
this.height = this.container.offsetHeight;
this.renderer.setSize(this.width, this.height);
this.camera.aspect = this.width / this.height;
// image cover
this.imageAspect = 1.5085 / 1;
let a1;
let a2;
if (this.height / this.width > this.imageAspect) {
a1 = (this.width / this.height) * this.imageAspect;
a2 = 1;
} else {
a1 = 1;
a2 = this.height / this.width / this.imageAspect;
}
this.material.uniforms.resolution.value.x = this.width;
this.material.uniforms.resolution.value.y = this.height;
this.material.uniforms.resolution.value.z = a1;
this.material.uniforms.resolution.value.w = a2;
this.camera.updateProjectionMatrix();
this.regenerateGrid();
}
regenerateGrid() {
this.size = this.settings.grid;
const width = this.size;
const height = this.size;
const size = width * height;
const data = new Float32Array(3 * size);
const color = new THREE.Color(0xffffff);
const r = Math.floor(color.r * 255);
const g = Math.floor(color.g * 255);
const b = Math.floor(color.b * 255);
for (let i = 0; i < size; i++) {
let r = Math.random() * 255 - 125;
let r1 = Math.random() * 255 - 125;
const stride = i * 3;
data[stride] = r;
data[stride + 1] = r1;
data[stride + 2] = r;
}
// used the buffer to create a DataTexture
this.texture = new THREE.DataTexture(
data,
width,
height,
THREE.RGBFormat,
THREE.FloatType
);
this.texture.magFilter = this.texture.minFilter = THREE.NearestFilter;
if (this.material) {
this.material.uniforms.uDataTexture.value = this.texture;
this.material.uniforms.uDataTexture.value.needsUpdate = true;
}
}
addObjects() {
this.regenerateGrid();
let texture = new THREE.TextureLoader().load(this.img.src);
texture.needsUpdate = true;
this.material = new THREE.ShaderMaterial({
extensions: {
derivatives: "#extension GL_OES_standard_derivatives : enable",
},
side: THREE.DoubleSide,
uniforms: {
time: {
value: 0,
},
resolution: {
value: new THREE.Vector4(),
},
uTexture: {
value: texture,
},
uDataTexture: {
value: this.texture,
},
},
vertexShader: `uniform float time;
varying vec2 vUv;
varying vec3 vPosition;
uniform vec2 pixels;
float PI = 3.141592653589793238;
void main() {
vUv = uv;
gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
}`,
fragmentShader: `uniform float time;
uniform float progress;
uniform sampler2D uDataTexture;
uniform sampler2D uTexture;
uniform vec4 resolution;
varying vec2 vUv;
varying vec3 vPosition;
float PI = 3.141592653589793238;
void main() {
vec2 newUV = (vUv - vec2(0.5))*resolution.zw + vec2(0.5);
vec4 color = texture2D(uTexture,newUV);
vec4 offset = texture2D(uDataTexture,vUv);
gl_FragColor = vec4(vUv,0.0,1.);
gl_FragColor = vec4(offset.r,0.,0.,1.);
gl_FragColor = color;
gl_FragColor = texture2D(uTexture,newUV - 0.02*offset.rg);
// gl_FragColor = offset;
}`,
});
this.geometry = new THREE.PlaneGeometry(1, 1, 1, 1);
this.plane = new THREE.Mesh(this.geometry, this.material);
this.scene.add(this.plane);
}
updateDataTexture() {
let data = this.texture.image.data;
for (let i = 0; i < data.length; i += 3) {
data[i] *= this.settings.relaxation;
data[i + 1] *= this.settings.relaxation;
}
let gridMouseX = this.size * this.mouse.x;
let gridMouseY = this.size * (1 - this.mouse.y);
let maxDist = this.size * this.settings.mouse;
let aspect = this.height / this.width;
for (let i = 0; i < this.size; i++) {
for (let j = 0; j < this.size; j++) {
let distance = (gridMouseX - i) ** 2 / aspect + (gridMouseY - j) ** 2;
let maxDistSq = maxDist ** 2;
if (distance < maxDistSq) {
let index = 3 * (i + this.size * j);
let power = maxDist / Math.sqrt(distance);
power = clamp(power, 0, 10);
// if(distance {
// Image appearance
gsap.fromTo(
el.querySelector("img"),
{
transformOrigin: "0% 50%", // Starting from the left edge
},
{
transformOrigin: "0% 50%",
scaleX: 1, // Returning to initial width
ease: "none", // Linear movement
scrollTrigger: {
horizontal: true,
trigger: el, // Listen to the image position
start: "left 100%", // Starts when the left edge of the image is on the right side of the viewport
end: "left 80%", // Ends when the left edge of the image is at 80% of the viewport
scrub: true, // Progresses with the scroll
},
}
);
// Disappearance of the image
gsap.fromTo(
el.querySelector("img"),
{
transformOrigin: "100% 50%",
},
{
transformOrigin: "100% 50%",
scaleX: 0, // Squashing the image
ease: "none", // Linear movement
immediateRender: false, // To avoid tween conflicts
scrollTrigger: {
horizontal: true,
trigger: el, // Listen to the image position
start: "right 20%", // Starts when the right edge of the image is at 20% of the viewport
end: "right 0%", // Ends when the right edge of the image is at 0% of the viewport
scrub: true, // Progresses with the scroll
},
}
);
});
}
$("[data-email-address]").on("click", function () {
const $el = $(this);
const email = $el.attr("data-email-address");
const original = $el.text();
function showCopied() {
$el.text("[ COPIED TO CLIPBOARD ]");
setTimeout(() => $el.text(original), 1000);
}
if (navigator.clipboard && navigator.clipboard.writeText) {
navigator.clipboard.writeText(email).then(showCopied).catch(showCopied);
} else {
// Fallback for old browsers
const temp = $("").val(email).appendTo("body").select();
try {
document.execCommand("copy");
} catch (err) {
console.error("Fallback copy failed", err);
}
temp.remove();
showCopied();
}
});