UNPKG

@tuel/scroll

Version:

Advanced scroll-triggered animations and effects for React.

1,377 lines (1,371 loc) 88.3 kB
'use strict'; var gsap7 = require('gsap'); var ScrollTrigger = require('gsap/ScrollTrigger'); var react = require('lenis/react'); var react$1 = require('react'); var THREE = require('three'); var jsxRuntime = require('react/jsx-runtime'); var utils = require('@tuel/utils'); var performance = require('@tuel/performance'); var framerMotion = require('framer-motion'); var SplitText = require('gsap/SplitText'); var GLTFLoader_js = require('three/examples/jsm/loaders/GLTFLoader.js'); function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; } function _interopNamespace(e) { if (e && e.__esModule) return e; var n = Object.create(null); if (e) { Object.keys(e).forEach(function (k) { if (k !== 'default') { var d = Object.getOwnPropertyDescriptor(e, k); Object.defineProperty(n, k, d.get ? d : { enumerable: true, get: function () { return e[k]; } }); } }); } n.default = e; return Object.freeze(n); } var gsap7__default = /*#__PURE__*/_interopDefault(gsap7); var THREE__namespace = /*#__PURE__*/_interopNamespace(THREE); var defaultImages = [ "/assets/img1.jpg", "/assets/img2.jpg", "/assets/img3.jpg", "/assets/img4.jpg", "/assets/img5.jpg", "/assets/img6.jpg", "/assets/img7.jpg" ]; gsap7.gsap.registerPlugin(ScrollTrigger.ScrollTrigger); function WodniackWorkScroll({ letters = ["W", "O", "R", "K"], images = defaultImages, introText = "( Intro )", outroText = "( Outro )", primaryColor = "#f40c3f", backgroundColor = "#000000", dotSize = 0.75, gridSpacing = 20, letterInstanceCount = 15, lineSpeedMultipliers = [0.8, 1, 0.7, 0.9], lerpFactor = 0.07, enableSmoothScroll = true, className, lenisOptions = { lerp: 0.1, smoothWheel: true, duration: 1.2 } }) { return /* @__PURE__ */ jsxRuntime.jsx("div", { className: `wodniack-work-scroll ${className || ""}`, children: enableSmoothScroll ? /* @__PURE__ */ jsxRuntime.jsx(react.ReactLenis, { root: true, options: lenisOptions, children: /* @__PURE__ */ jsxRuntime.jsx( WodniackWorkScrollContent, { letters, images, introText, outroText, primaryColor, backgroundColor, dotSize, gridSpacing, letterInstanceCount, lineSpeedMultipliers, lerpFactor, className } ) }) : /* @__PURE__ */ jsxRuntime.jsx( WodniackWorkScrollContent, { letters, images, introText, outroText, primaryColor, backgroundColor, dotSize, gridSpacing, letterInstanceCount, lineSpeedMultipliers, lerpFactor, className } ) }); } function WodniackWorkScrollContent({ letters, images, introText, outroText, primaryColor, backgroundColor, dotSize, gridSpacing, letterInstanceCount, lineSpeedMultipliers, lerpFactor, className }) { const containerRef = react$1.useRef(null); const workRef = react$1.useRef(null); const textContainerRef = react$1.useRef(null); const gridCanvasRef = react$1.useRef(null); const lettersCanvasRef = react$1.useRef(null); const cardsCanvasRef = react$1.useRef(null); const lettersSceneRef = react$1.useRef(null); const cardsSceneRef = react$1.useRef(null); const lettersCameraRef = react$1.useRef(null); const cardsCameraRef = react$1.useRef(null); const lettersRendererRef = react$1.useRef(null); const cardsRendererRef = react$1.useRef(null); const pathsRef = react$1.useRef([]); const cardsTextureRef = react$1.useRef(null); const textureCanvasRef = react$1.useRef(null); const lenisRef = react$1.useRef(null); const letterPositionsRef = react$1.useRef( /* @__PURE__ */ new Map() ); const animationIdRef = react$1.useRef(null); const scrollTriggerRef = react$1.useRef(null); const [isReady, setIsReady] = react$1.useState(false); const [loadedTextures, setLoadedTextures] = react$1.useState([]); const lerp = react$1.useCallback((start, end, t) => { return start + (end - start) * t; }, []); const initGridCanvas = react$1.useCallback(() => { const canvas = gridCanvasRef.current; if (!canvas || !workRef.current) return; const ctx = canvas.getContext("2d"); const resizeCanvas = () => { const dpr = window.devicePixelRatio || 1; canvas.width = window.innerWidth * dpr; canvas.height = window.innerHeight * dpr; canvas.style.width = `${window.innerWidth}px`; canvas.style.height = `${window.innerHeight}px`; ctx.scale(dpr, dpr); }; resizeCanvas(); window.addEventListener("resize", resizeCanvas); return () => window.removeEventListener("resize", resizeCanvas); }, []); const drawGrid = react$1.useCallback( (scrollProgress = 0) => { const canvas = gridCanvasRef.current; if (!canvas) return; const ctx = canvas.getContext("2d"); ctx.fillStyle = backgroundColor; ctx.fillRect(0, 0, canvas.width, canvas.height); ctx.fillStyle = primaryColor; const rows = Math.ceil(canvas.height / gridSpacing); const cols = Math.ceil(canvas.width / gridSpacing) + 15; const offset = scrollProgress * gridSpacing * 10 % gridSpacing; for (let y = 0; y < rows; y++) { for (let x = 0; x < cols; x++) { ctx.beginPath(); ctx.arc( x * gridSpacing - offset, y * gridSpacing, dotSize, 0, Math.PI * 2 ); ctx.fill(); } } }, [backgroundColor, primaryColor, gridSpacing, dotSize] ); const createTextAnimationPath = react$1.useCallback( (yPos, amplitude) => { const points = []; for (let i = 0; i <= 20; i++) { const t = i / 20; points.push( new THREE__namespace.Vector3( -25 + 50 * t, yPos + Math.sin(t * Math.PI) * -amplitude, (1 - Math.pow(Math.abs(t - 0.5) * 2, 2)) * -5 ) ); } const curve = new THREE__namespace.CatmullRomCurve3(points); const line = new THREE__namespace.Line( new THREE__namespace.BufferGeometry().setFromPoints(curve.getPoints(100)), new THREE__namespace.LineBasicMaterial({ color: 0, linewidth: 1 }) ); line.curve = curve; line.letterElements = []; return line; }, [] ); const loadImages = react$1.useCallback(async () => { const textureLoader = new THREE__namespace.TextureLoader(); const loadPromises = images.map( (imageSrc) => new Promise((resolve) => { textureLoader.load(imageSrc, (texture) => { texture.generateMipmaps = true; texture.minFilter = THREE__namespace.LinearMipmapLinearFilter; texture.magFilter = THREE__namespace.LinearFilter; resolve(texture); }); }) ); const textures = await Promise.all(loadPromises); setLoadedTextures(textures); return textures; }, [images]); const initThreeJS = react$1.useCallback(() => { if (!workRef.current) return; const lettersScene = new THREE__namespace.Scene(); const cardsScene = new THREE__namespace.Scene(); lettersSceneRef.current = lettersScene; cardsSceneRef.current = cardsScene; const lettersCamera = new THREE__namespace.PerspectiveCamera( 50, window.innerWidth / window.innerHeight, 0.1, 1e3 ); const cardsCamera = new THREE__namespace.PerspectiveCamera( 50, window.innerWidth / window.innerHeight, 0.1, 1e3 ); lettersCamera.position.setZ(20); cardsCamera.position.setZ(20); lettersCameraRef.current = lettersCamera; cardsCameraRef.current = cardsCamera; const lettersRenderer = new THREE__namespace.WebGLRenderer({ antialias: true, alpha: true }); lettersRenderer.setSize(window.innerWidth, window.innerHeight); lettersRenderer.setClearColor(0, 0); lettersRenderer.setPixelRatio(window.devicePixelRatio); const cardsRenderer = new THREE__namespace.WebGLRenderer({ antialias: true, alpha: true }); cardsRenderer.setPixelRatio(Math.min(window.devicePixelRatio, 2)); cardsRenderer.setSize(window.innerWidth, window.innerHeight); cardsRenderer.setClearColor(0, 0); lettersRendererRef.current = lettersRenderer; cardsRendererRef.current = cardsRenderer; const paths = [ createTextAnimationPath(10, 2), createTextAnimationPath(3.5, 1), createTextAnimationPath(-3.5, -1), createTextAnimationPath(-10, -2) ]; paths.forEach((line) => lettersScene.add(line)); pathsRef.current = paths; if (lettersCanvasRef.current) { lettersCanvasRef.current.remove(); } if (cardsCanvasRef.current) { cardsCanvasRef.current.remove(); } lettersRenderer.domElement.style.position = "absolute"; lettersRenderer.domElement.style.top = "0"; lettersRenderer.domElement.style.left = "0"; lettersRenderer.domElement.style.pointerEvents = "none"; cardsRenderer.domElement.style.position = "absolute"; cardsRenderer.domElement.style.top = "0"; cardsRenderer.domElement.style.left = "0"; cardsRenderer.domElement.style.pointerEvents = "none"; workRef.current.appendChild(lettersRenderer.domElement); workRef.current.appendChild(cardsRenderer.domElement); lettersCanvasRef.current = lettersRenderer.domElement; cardsCanvasRef.current = cardsRenderer.domElement; return () => { lettersRenderer.dispose(); cardsRenderer.dispose(); }; }, [createTextAnimationPath]); const createLetterElements = react$1.useCallback(() => { if (!textContainerRef.current || !pathsRef.current.length) return; textContainerRef.current.innerHTML = ""; letterPositionsRef.current.clear(); pathsRef.current.forEach((line, pathIndex) => { line.letterElements = Array.from({ length: letterInstanceCount }, () => { const element = document.createElement("div"); element.className = "wodniack-letter"; element.textContent = letters[pathIndex] || "X"; element.style.position = "absolute"; element.style.transform = "translate(-50%, -50%)"; element.style.color = primaryColor; element.style.fontSize = "4rem"; element.style.fontWeight = "bold"; element.style.pointerEvents = "none"; element.style.zIndex = "10"; textContainerRef.current.appendChild(element); letterPositionsRef.current.set(element, { current: { x: 0, y: 0 }, target: { x: 0, y: 0 } }); return element; }); }); }, [letters, letterInstanceCount, primaryColor]); const initCardsTexture = react$1.useCallback(async () => { if (!loadedTextures.length || !cardsSceneRef.current || !cardsRendererRef.current) return; const textureCanvas = document.createElement("canvas"); textureCanvas.width = 4096; textureCanvas.height = 2048; textureCanvasRef.current = textureCanvas; const cardsTexture = new THREE__namespace.CanvasTexture(textureCanvas); cardsTexture.generateMipmaps = true; cardsTexture.minFilter = THREE__namespace.LinearMipmapLinearFilter; cardsTexture.magFilter = THREE__namespace.LinearFilter; cardsTexture.anisotropy = cardsRendererRef.current.capabilities.getMaxAnisotropy(); cardsTexture.wrapS = THREE__namespace.RepeatWrapping; cardsTexture.wrapT = THREE__namespace.RepeatWrapping; cardsTextureRef.current = cardsTexture; const cardsPlane = new THREE__namespace.Mesh( new THREE__namespace.PlaneGeometry(30, 15, 50, 1), new THREE__namespace.MeshBasicMaterial({ map: cardsTexture, side: THREE__namespace.DoubleSide, transparent: true, opacity: 1, depthTest: false, depthWrite: false }) ); const positions = cardsPlane.geometry.attributes.position; for (let i = 0; i < positions.count; i++) { positions.setZ(i, Math.pow(positions.getX(i) / 15, 2) * 5); } positions.needsUpdate = true; cardsSceneRef.current.add(cardsPlane); }, [loadedTextures]); const drawCardsOnCanvas = react$1.useCallback( (offset = 0) => { const canvas = textureCanvasRef.current; if (!canvas || !loadedTextures.length) return; const ctx = canvas.getContext("2d"); ctx.clearRect(0, 0, canvas.width, canvas.height); const cardWidth = canvas.width / 3; const cardHeight = canvas.height / 2; const spacing = canvas.width / 2.5; loadedTextures.forEach((texture, i) => { if (texture.image) { ctx.drawImage( texture.image, i * spacing + (0.35 - offset) * canvas.width * 5 - cardWidth, (canvas.height - cardHeight) / 2, cardWidth, cardHeight ); } }); if (cardsTextureRef.current) { cardsTextureRef.current.needsUpdate = true; } }, [loadedTextures] ); const updateTargetPositions = react$1.useCallback( (scrollProgress = 0) => { if (!pathsRef.current.length || !lettersCameraRef.current) return; pathsRef.current.forEach((line, lineIndex) => { const speedMultiplier = lineSpeedMultipliers[lineIndex] || 1; line.letterElements.forEach((element, i) => { const point = line.curve.getPoint( (i / (letterInstanceCount - 1) + scrollProgress * speedMultiplier) % 1 ); const vector = point.clone().project(lettersCameraRef.current); const positions = letterPositionsRef.current.get(element); if (positions) { positions.target = { x: (-vector.x * 0.5 + 0.5) * window.innerWidth, y: (-vector.y * 0.5 + 0.5) * window.innerHeight }; } }); }); }, [letterInstanceCount, lineSpeedMultipliers] ); const updateLetterPositions = react$1.useCallback(() => { letterPositionsRef.current.forEach((positions, element) => { const distX = positions.target.x - positions.current.x; if (Math.abs(distX) > window.innerWidth * 0.7) { positions.current.x = positions.target.x; positions.current.y = positions.target.y; } else { positions.current.x = lerp( positions.current.x, positions.target.x, lerpFactor ); positions.current.y = lerp( positions.current.y, positions.target.y, lerpFactor ); } element.style.transform = `translate(-50%, -50%) translate3d(${positions.current.x}px, ${positions.current.y}px, 0px)`; }); }, [lerp, lerpFactor]); const animate = react$1.useCallback(() => { updateLetterPositions(); if (lettersSceneRef.current && lettersCameraRef.current && lettersRendererRef.current) { lettersRendererRef.current.render( lettersSceneRef.current, lettersCameraRef.current ); } if (cardsSceneRef.current && cardsCameraRef.current && cardsRendererRef.current) { cardsRendererRef.current.render( cardsSceneRef.current, cardsCameraRef.current ); } animationIdRef.current = requestAnimationFrame(animate); }, [updateLetterPositions]); const initScrollTrigger = react$1.useCallback(() => { if (!workRef.current) return; const scrollTrigger = ScrollTrigger.ScrollTrigger.create({ trigger: workRef.current, start: "top top", end: "+=700%", pin: true, pinSpacing: true, scrub: 1, onUpdate: (self) => { updateTargetPositions(self.progress); drawCardsOnCanvas(self.progress); drawGrid(self.progress); } }); scrollTriggerRef.current = scrollTrigger; }, [updateTargetPositions, drawCardsOnCanvas, drawGrid]); const handleResize = react$1.useCallback(() => { if (!lettersCameraRef.current || !cardsCameraRef.current || !lettersRendererRef.current || !cardsRendererRef.current) return; lettersCameraRef.current.aspect = window.innerWidth / window.innerHeight; lettersCameraRef.current.updateProjectionMatrix(); cardsCameraRef.current.aspect = window.innerWidth / window.innerHeight; cardsCameraRef.current.updateProjectionMatrix(); lettersRendererRef.current.setSize(window.innerWidth, window.innerHeight); cardsRendererRef.current.setSize(window.innerWidth, window.innerHeight); cardsRendererRef.current.setPixelRatio( Math.min(window.devicePixelRatio, 2) ); const canvas = gridCanvasRef.current; if (canvas) { const ctx = canvas.getContext("2d"); const dpr = window.devicePixelRatio || 1; canvas.width = window.innerWidth * dpr; canvas.height = window.innerHeight * dpr; canvas.style.width = `${window.innerWidth}px`; canvas.style.height = `${window.innerHeight}px`; ctx.scale(dpr, dpr); } const progress = scrollTriggerRef.current?.progress || 0; drawGrid(progress); updateTargetPositions(progress); }, [drawGrid, updateTargetPositions]); react$1.useEffect(() => { const init = async () => { try { initGridCanvas(); await loadImages(); initThreeJS(); createLetterElements(); await initCardsTexture(); drawGrid(0); updateTargetPositions(0); animate(); initScrollTrigger(); window.addEventListener("resize", handleResize); setIsReady(true); } catch (error) { console.error("Failed to initialize WodniackWorkScroll:", error); } }; init(); return () => { if (animationIdRef.current) { cancelAnimationFrame(animationIdRef.current); } if (scrollTriggerRef.current) { scrollTriggerRef.current.kill(); } if (lenisRef.current) { lenisRef.current.destroy(); } window.removeEventListener("resize", handleResize); if (lettersRendererRef.current) { lettersRendererRef.current.dispose(); } if (cardsRendererRef.current) { cardsRendererRef.current.dispose(); } }; }, [ initGridCanvas, loadImages, initThreeJS, createLetterElements, initCardsTexture, drawGrid, updateTargetPositions, animate, initScrollTrigger, handleResize ]); return /* @__PURE__ */ jsxRuntime.jsxs( "div", { className: `wodniack-work-scroll-content ${className || ""}`, ref: containerRef, children: [ /* @__PURE__ */ jsxRuntime.jsx("section", { className: "wodniack-intro", children: /* @__PURE__ */ jsxRuntime.jsx("h1", { children: introText }) }), /* @__PURE__ */ jsxRuntime.jsxs("section", { className: "wodniack-work", ref: workRef, children: [ /* @__PURE__ */ jsxRuntime.jsx("canvas", { ref: gridCanvasRef, className: "wodniack-grid-canvas" }), /* @__PURE__ */ jsxRuntime.jsx("div", { ref: textContainerRef, className: "wodniack-text-container" }) ] }), /* @__PURE__ */ jsxRuntime.jsx("section", { className: "wodniack-outro", children: /* @__PURE__ */ jsxRuntime.jsx("h1", { children: outroText }) }), !isReady && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "wodniack-loading", children: /* @__PURE__ */ jsxRuntime.jsx("p", { children: "Loading..." }) }) ] } ); } // src/hooks/useSSR.ts function canUseDOM() { return !!(typeof window !== "undefined" && window.document && window.document.createElement); } var gsap2; var ScrollTrigger2; if (canUseDOM()) { import('gsap').then((mod) => { gsap2 = mod.gsap; import('gsap/ScrollTrigger').then((st) => { ScrollTrigger2 = st.ScrollTrigger; gsap2.registerPlugin(ScrollTrigger2); }); }); } function HorizontalScroll({ children, className, containerClassName, speed = 1, scrub = 1, pin = true, snap = false, direction = "left", triggerElement, start = "top top", end = "bottom top", onUpdate, onComplete }) { const containerRef = react$1.useRef(null); const scrollRef = react$1.useRef(null); const prefersReducedMotion = performance.useReducedMotion(); react$1.useEffect(() => { if (!canUseDOM() || prefersReducedMotion || !gsap2 || !ScrollTrigger2) { return; } const container = containerRef.current; const scrollElement = scrollRef.current; if (!container || !scrollElement) return; const scrollWidth = scrollElement.scrollWidth; const containerWidth = container.offsetWidth; const scrollDistance = scrollWidth - containerWidth; const scrollDirection = direction === "left" ? -scrollDistance : scrollDistance; const animation = gsap2.to(scrollElement, { x: scrollDirection * speed, ease: "none", scrollTrigger: { trigger: triggerElement || container, start, end: end === "auto" ? `+=${scrollDistance}` : end, scrub, pin: pin ? container : false, snap: typeof snap === "boolean" ? void 0 : snap, invalidateOnRefresh: true, anticipatePin: 1, onUpdate: (self) => { if (onUpdate) { onUpdate(self.progress); } } } }); return () => { animation.kill(); }; }, [ speed, scrub, pin, snap, direction, triggerElement, start, end, onUpdate, onComplete, prefersReducedMotion ]); return /* @__PURE__ */ jsxRuntime.jsx( "div", { ref: containerRef, className: utils.cn("overflow-hidden", containerClassName), children: /* @__PURE__ */ jsxRuntime.jsx("div", { ref: scrollRef, className: utils.cn("flex w-fit", className), children }) } ); } function HorizontalScrollItem({ children, className, width = "auto" }) { return /* @__PURE__ */ jsxRuntime.jsx("div", { className: utils.cn("flex-shrink-0", className), "data-width": width, children }); } if (typeof window !== "undefined") { gsap7.gsap.registerPlugin(ScrollTrigger.ScrollTrigger); } var cubesData = { "cube-1": { initial: { top: -55, left: 37.5, rotateX: 360, rotateY: -360, rotateZ: -48, z: -3e4 }, final: { top: 50, left: 15, rotateX: 0, rotateY: 3, rotateZ: 0, z: 0 } }, "cube-2": { initial: { top: -35, left: 12.5, rotateX: -360, rotateY: 360, rotateZ: 135, z: -3e4 }, final: { top: 25, left: 25, rotateX: 1, rotateY: 2, rotateZ: 0, z: 0 } }, "cube-3": { initial: { top: -75, left: 87.5, rotateX: -360, rotateY: -360, rotateZ: 180, z: -3e4 }, final: { top: 75, left: 15, rotateX: 0, rotateY: -1, rotateZ: 0, z: 0 } }, "cube-4": { initial: { top: -75, left: 12.5, rotateX: 360, rotateY: 360, rotateZ: 90, z: -3e4 }, final: { top: 75, left: 55, rotateX: 0, rotateY: 0, rotateZ: 0, z: 0 } }, "cube-5": { initial: { top: -55, left: 62.5, rotateX: 360, rotateY: 360, rotateZ: -135, z: -3e4 }, final: { top: 25, left: 75, rotateX: -1, rotateY: -2, rotateZ: 0, z: 0 } }, "cube-6": { initial: { top: -35, left: 67.5, rotateX: -180, rotateY: -360, rotateZ: -180, z: -3e4 }, final: { top: 50, left: 85, rotateX: 0, rotateY: -3, rotateZ: 0, z: 0 } } }; function Cube({ cubeKey, images, className = "" }) { const data = cubesData[cubeKey]; return /* @__PURE__ */ jsxRuntime.jsxs( "div", { className: `cube absolute z-10 w-20 h-20 md:w-24 md:h-24 preserve-3d ${className}`, style: { transformStyle: "preserve-3d", perspective: "1000px", top: `${data.initial.top}%`, left: `${data.initial.left}%`, transform: ` translate3d(-50%, -50%, ${data.initial.z}px) rotateX(${data.initial.rotateX}deg) rotateY(${data.initial.rotateY}deg) rotateZ(${data.initial.rotateZ}deg) ` }, children: [ /* @__PURE__ */ jsxRuntime.jsx( "div", { className: "cube-face absolute w-full h-full", style: { transform: "rotateY(0deg) translateZ(50px)" }, children: /* @__PURE__ */ jsxRuntime.jsx( "img", { src: images[0] || "/api/placeholder/300/300", alt: "cube-face", className: "w-full h-full object-cover" } ) } ), /* @__PURE__ */ jsxRuntime.jsx( "div", { className: "cube-face absolute w-full h-full", style: { transform: "rotateY(180deg) translateZ(50px)" }, children: /* @__PURE__ */ jsxRuntime.jsx( "img", { src: images[1] || "/api/placeholder/300/300", alt: "cube-face", className: "w-full h-full object-cover" } ) } ), /* @__PURE__ */ jsxRuntime.jsx( "div", { className: "cube-face absolute w-full h-full", style: { transform: "rotateY(90deg) translateZ(50px)" }, children: /* @__PURE__ */ jsxRuntime.jsx( "img", { src: images[2] || "/api/placeholder/300/300", alt: "cube-face", className: "w-full h-full object-cover" } ) } ), /* @__PURE__ */ jsxRuntime.jsx( "div", { className: "cube-face absolute w-full h-full", style: { transform: "rotateY(-90deg) translateZ(50px)" }, children: /* @__PURE__ */ jsxRuntime.jsx( "img", { src: images[3] || "/api/placeholder/300/300", alt: "cube-face", className: "w-full h-full object-cover" } ) } ), /* @__PURE__ */ jsxRuntime.jsx( "div", { className: "cube-face absolute w-full h-full", style: { transform: "rotateX(90deg) translateZ(50px)" }, children: /* @__PURE__ */ jsxRuntime.jsx( "img", { src: images[4] || "/api/placeholder/300/300", alt: "cube-face", className: "w-full h-full object-cover" } ) } ), /* @__PURE__ */ jsxRuntime.jsx( "div", { className: "cube-face absolute w-full h-full", style: { transform: "rotateX(-90deg) translateZ(50px)" }, children: /* @__PURE__ */ jsxRuntime.jsx( "img", { src: images[5] || "/api/placeholder/300/300", alt: "cube-face", className: "w-full h-full object-cover" } ) } ) ] } ); } function OrchestraCubes({ className = "", header1 = "The first media company crafted for the digital first generation.", header2 = "Where innovation meets precision.", subtitle = "Symphonia unites visionary thinkers, creative architects, and analytical experts, collaborating seamlessly to transform challenges into opportunities. Together, we deliver tailored solutions that drive impact and inspire growth.", images = Array.from( { length: 36 }, (_, i) => `/api/placeholder/300/300?seed=${i + 1}` ), stickyHeight = 4, backgroundColor = "#331707", textColor = "#ffe9d9" }) { const stickyRef = react$1.useRef(null); const logoRef = react$1.useRef(null); const cubesRef = react$1.useRef(null); const header1Ref = react$1.useRef(null); const header2Ref = react$1.useRef(null); const [cubeRefs] = react$1.useState(() => { const refs = {}; Object.keys(cubesData).forEach((key) => { refs[key] = { current: null }; }); return refs; }); const interpolate = (start, end, progress) => { return start + (end - start) * progress; }; react$1.useEffect(() => { if (!stickyRef.current) return; Object.entries(cubesData).forEach(([cubeKey, data]) => { const cubeRef = cubeRefs[cubeKey]; if (!cubeRef.current) return; cubeRef.current.style.top = `${data.initial.top}%`; cubeRef.current.style.left = `${data.initial.left}%`; cubeRef.current.style.transform = ` translate3d(-50%, -50%, ${data.initial.z}px) rotateX(${data.initial.rotateX}deg) rotateY(${data.initial.rotateY}deg) rotateZ(${data.initial.rotateZ}deg) `; }); const scrollTrigger = ScrollTrigger.ScrollTrigger.create({ trigger: stickyRef.current, start: "top top", end: `+=${stickyHeight * 100}vh`, pin: true, scrub: 1, onUpdate: (self) => { if (!logoRef.current || !cubesRef.current || !header1Ref.current || !header2Ref.current) { return; } const logo = logoRef.current; const cubes = cubesRef.current; const header12 = header1Ref.current; const header22 = header2Ref.current; if (self.progress <= 0.2) { const logoProgress = self.progress * 5; const logoScale = interpolate(1, 0, logoProgress); const logoOpacity = interpolate(1, 0, logoProgress); logo.style.transform = `translate(-50%, -50%) scale(${logoScale})`; logo.style.opacity = String(logoOpacity); cubes.style.opacity = String(logoProgress); } else { logo.style.opacity = "0"; cubes.style.opacity = "1"; } if (self.progress >= 0.2 && self.progress <= 0.4) { const header1StartProgress = (self.progress - 0.2) * 5; const header1Progress = Math.max( 0, Math.min(header1StartProgress, 1) ); const header1Scale = interpolate(0.75, 1, header1Progress); const header1Blur = interpolate(10, 0, header1Progress); header12.style.transform = `translate(-50%, -50%) scale(${header1Scale})`; header12.style.filter = `blur(${header1Blur}px)`; header12.style.opacity = String(header1Progress); } else if (self.progress > 0.4) { header12.style.opacity = "0"; } if (self.progress >= 0.4 && self.progress <= 0.6) { const header2StartProgress = (self.progress - 0.4) * 10; const header2Progress = Math.max( 0, Math.min(header2StartProgress, 1) ); const header2Scale = interpolate(0.75, 1, header2Progress); const header2Blur = interpolate(10, 0, header2Progress); header22.style.transform = `translate(-50%, -50%) scale(${header2Scale})`; header22.style.filter = `blur(${header2Blur}px)`; header22.style.opacity = String(header2Progress); } const firstPhaseProgress = Math.min(self.progress * 2, 1); const secondPhaseProgress = self.progress >= 0.5 ? (self.progress - 0.5) * 2 : 0; Object.entries(cubesData).forEach(([cubeKey, data]) => { const cubeRef = cubeRefs[cubeKey]; if (!cubeRef.current) return; const { initial, final } = data; const currentTop = interpolate( initial.top, final.top, firstPhaseProgress ); const currentLeft = interpolate( initial.left, final.left, firstPhaseProgress ); const currentRotateX = interpolate( initial.rotateX, final.rotateX, firstPhaseProgress ); const currentRotateY = interpolate( initial.rotateY, final.rotateY, firstPhaseProgress ); const currentRotateZ = interpolate( initial.rotateZ, final.rotateZ, firstPhaseProgress ); const currentZ = interpolate(initial.z, final.z, firstPhaseProgress); let additionalRotation = 0; if (cubeKey === "cube-2") { additionalRotation = interpolate(0, 180, secondPhaseProgress); } else if (cubeKey === "cube-4") { additionalRotation = interpolate(0, -180, secondPhaseProgress); } cubeRef.current.style.top = `${currentTop}%`; cubeRef.current.style.left = `${currentLeft}%`; cubeRef.current.style.transform = ` translate3d(-50%, -50%, ${currentZ}px) rotateX(${currentRotateX}deg) rotateY(${currentRotateY + additionalRotation}deg) rotateZ(${currentRotateZ}deg) `; }); } }); return () => { scrollTrigger.kill(); }; }, [stickyHeight, cubeRefs]); const cubeImages = Object.keys(cubesData).map( (_, index) => images.slice(index * 6, (index + 1) * 6) ); return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [ /* @__PURE__ */ jsxRuntime.jsxs( "div", { ref: stickyRef, className: `orchestra-cubes relative w-full h-screen overflow-hidden ${className}`, "data-bg": backgroundColor, "data-color": textColor, children: [ /* @__PURE__ */ jsxRuntime.jsxs( "div", { ref: logoRef, className: "absolute top-[25%] left-1/2 transform -translate-x-1/2 -translate-y-1/2 flex gap-6 z-20", children: [ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-2", children: [ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-4 h-4 bg-[#ffe9d9]" }), /* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-4 h-4 bg-[#ffe9d9]" }) ] }), /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-2", children: [ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-4 h-4 bg-[#ffe9d9]" }), /* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-4 h-4 bg-[#ffe9d9]" }) ] }), /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-2", children: [ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-4 h-4 bg-[#ffe9d9]" }), /* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-4 h-4 bg-[#ffe9d9]" }) ] }) ] } ), /* @__PURE__ */ jsxRuntime.jsx("div", { ref: cubesRef, className: "cubes-container opacity-0", children: Object.keys(cubesData).map((cubeKey, index) => /* @__PURE__ */ jsxRuntime.jsx("div", { ref: cubeRefs[cubeKey], children: /* @__PURE__ */ jsxRuntime.jsx(Cube, { cubeKey, images: cubeImages[index] }) }, cubeKey)) }), /* @__PURE__ */ jsxRuntime.jsx( "div", { ref: header1Ref, className: "absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 text-center z-10 max-w-4xl px-8", children: /* @__PURE__ */ jsxRuntime.jsx("h1", { className: "text-4xl md:text-6xl font-bold leading-tight text-[#ffe9d9]", children: header1 }) } ), /* @__PURE__ */ jsxRuntime.jsxs( "div", { ref: header2Ref, className: "header-2 absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 text-center z-10 max-w-2xl px-8 opacity-0", children: [ /* @__PURE__ */ jsxRuntime.jsx("h2", { className: "text-3xl md:text-5xl font-bold mb-6 text-[#ffe9d9]", children: header2 }), /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-lg md:text-xl leading-relaxed text-[#ffe9d9]", children: subtitle }) ] } ) ] } ), /* @__PURE__ */ jsxRuntime.jsx("section", { className: "relative w-full h-screen flex justify-center items-center text-center bg-[#cdb9ab] text-[#331707]", children: /* @__PURE__ */ jsxRuntime.jsx("h2", { className: "text-3xl md:text-5xl font-bold", children: "Your next section goes here" }) }) ] }); } if (typeof window !== "undefined") { gsap7.gsap.registerPlugin(ScrollTrigger.ScrollTrigger); } var defaultCards = [ { id: 1, title: "Silent Veil", productCode: "PROD8372", image: "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNDAwIiBoZWlnaHQ9IjYwMCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48ZGVmcz48bGluZWFyR3JhZGllbnQgaWQ9ImciIHgxPSIwJSIgeTE9IjAlIiB4Mj0iMTAwJSIgeTI9IjEwMCUiPjxzdG9wIG9mZnNldD0iMCUiIHN0b3AtY29sb3I9IiM2NjY2NjYiLz48c3RvcCBvZmZzZXQ9IjEwMCUiIHN0b3AtY29sb3I9IiMzMzMzMzMiLz48L2xpbmVhckdyYWRpZW50PjwvZGVmcz48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSJ1cmwoI2cpIi8+PC9zdmc+" }, { id: 2, title: "Crimson Echoes", productCode: "PROD4921", image: "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNDAwIiBoZWlnaHQ9IjYwMCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48ZGVmcz48bGluZWFyR3JhZGllbnQgaWQ9ImciIHgxPSIwJSIgeTE9IjAlIiB4Mj0iMTAwJSIgeTI9IjEwMCUiPjxzdG9wIG9mZnNldD0iMCUiIHN0b3AtY29sb3I9IiNkYzI2MjYiLz48c3RvcCBvZmZzZXQ9IjEwMCUiIHN0b3AtY29sb3I9IiM5OTE5MTkiLz48L2xpbmVhckdyYWRpZW50PjwvZGVmcz48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSJ1cmwoI2cpIi8+PC9zdmc+" }, { id: 3, title: "Zenith Arc", productCode: "PROD7586", image: "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNDAwIiBoZWlnaHQ9IjYwMCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48ZGVmcz48bGluZWFyR3JhZGllbnQgaWQ9ImciIHgxPSIwJSIgeTE9IjAlIiB4Mj0iMTAwJSIgeTI9IjEwMCUiPjxzdG9wIG9mZnNldD0iMCUiIHN0b3AtY29sb3I9IiMyNTYzZWIiLz48c3RvcCBvZmZzZXQ9IjEwMCUiIHN0b3AtY29sb3I9IiMxZTQwYWYiLz48L2xpbmVhckdyYWRpZW50PjwvZGVmcz48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSJ1cmwoI2cpIi8+PC9zdmc+" }, { id: 4, title: "Moonspire Dream", productCode: "PROD3410", image: "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNDAwIiBoZWlnaHQ9IjYwMCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48ZGVmcz48bGluZWFyR3JhZGllbnQgaWQ9ImciIHgxPSIwJSIgeTE9IjAlIiB4Mj0iMTAwJSIgeTI9IjEwMCUiPjxzdG9wIG9mZnNldD0iMCUiIHN0b3AtY29sb3I9IiM2MzY2ZjEiLz48c3RvcCBvZmZzZXQ9IjEwMCUiIHN0b3AtY29sb3I9IiM0MzM4Y2EiLz48L2xpbmVhckdyYWRpZW50PjwvZGVmcz48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSJ1cmwoI2cpIi8+PC9zdmc+" }, { id: 5, title: "Ember Flux", productCode: "PROD9053", image: "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNDAwIiBoZWlnaHQ9IjYwMCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48ZGVmcz48bGluZWFyR3JhZGllbnQgaWQ9ImciIHgxPSIwJSIgeTE9IjAlIiB4Mj0iMTAwJSIgeTI9IjEwMCUiPjxzdG9wIG9mZnNldD0iMCUiIHN0b3AtY29sb3I9IiNmOTc4MTYiLz48c3RvcCBvZmZzZXQ9IjEwMCUiIHN0b3AtY29sb3I9IiNlYTU4MDYiLz48L2xpbmVhckdyYWRpZW50PjwvZGVmcz48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSJ1cmwoI2cpIi8+PC9zdmc+" } ]; function OrkenWorldScroll({ className = "" }) { const stickyRef = react$1.useRef(null); const outlineCanvasRef = react$1.useRef(null); const fillCanvasRef = react$1.useRef(null); const cardsRef = react$1.useRef(null); const triangleStatesRef = react$1.useRef(/* @__PURE__ */ new Map()); const animationFrameIdRef = react$1.useRef(null); const canvasXPositionRef = react$1.useRef(0); const TRIANGLE_SIZE = 15; const TRIANGLE_ROWS = 100; const TRIANGLE_COLS = 200; const SCALE_THRESHOLD = 0.1; const setCanvasSize = (canvas, ctx) => { const rect = canvas.getBoundingClientRect(); const pixelRatio = window.devicePixelRatio || 1; canvas.width = rect.width * pixelRatio; canvas.height = rect.height * pixelRatio; ctx.scale(pixelRatio, pixelRatio); canvas.style.width = rect.width + "px"; canvas.style.height = rect.height + "px"; }; const drawTriangle = (ctx, x, y, size, flipped, outlineScale, fillScale, lineWidth) => { const halfSize = size * 0.5; if (outlineScale >= SCALE_THRESHOLD) { ctx.save(); ctx.translate(x, y); ctx.scale(outlineScale, outlineScale); ctx.translate(-x, -y); ctx.beginPath(); if (!flipped) { ctx.moveTo(x, y - halfSize); ctx.lineTo(x + halfSize, y + halfSize); ctx.lineTo(x - halfSize, y + halfSize); } else { ctx.moveTo(x, y + halfSize); ctx.lineTo(x + halfSize, y - halfSize); ctx.lineTo(x - halfSize, y - halfSize); } ctx.closePath(); ctx.strokeStyle = "rgba(255, 255, 255, 0.075)"; ctx.lineWidth = lineWidth; ctx.stroke(); } if (fillScale >= SCALE_THRESHOLD) { ctx.save(); ctx.translate(x, y); ctx.scale(fillScale, fillScale); ctx.translate(-x, -y); ctx.beginPath(); if (!flipped) { ctx.moveTo(x, y - halfSize); ctx.lineTo(x + halfSize, y + halfSize); ctx.lineTo(x - halfSize, y + halfSize); } else { ctx.moveTo(x, y + halfSize); ctx.lineTo(x + halfSize, y - halfSize); ctx.lineTo(x - halfSize, y - halfSize); } ctx.closePath(); ctx.fillStyle = "#ff6b00"; ctx.strokeStyle = "#ff6b00"; ctx.lineWidth = lineWidth; ctx.stroke(); ctx.fill(); ctx.restore(); } }; const drawGrid = (scrollProgress = 0) => { const outlineCanvas = outlineCanvasRef.current; const fillCanvas = fillCanvasRef.current; if (!outlineCanvas || !fillCanvas) return; const outlineCtx = outlineCanvas.getContext("2d"); const fillCtx = fillCanvas.getContext("2d"); if (!outlineCtx || !fillCtx) return; if (animationFrameIdRef.current) { cancelAnimationFrame(animationFrameIdRef.current); } outlineCtx.clearRect(0, 0, outlineCanvas.width, outlineCanvas.height); fillCtx.clearRect(0, 0, fillCanvas.width, fillCanvas.height); const animationProgress = scrollProgress <= 0.65 ? 0 : (scrollProgress - 0.65) / 0.35; let needsUpdate = false; const canvasWidth = outlineCanvas.style.width ? parseInt(outlineCanvas.style.width.replace("px", "")) : outlineCanvas.width; outlineCanvas.style.height ? parseInt(outlineCanvas.style.height.replace("px", "")) : outlineCanvas.height; for (let row = 0; row < TRIANGLE_ROWS; row++) { for (let col = 0; col < TRIANGLE_COLS; col++) { const isFlipped = (row + col) % 2 === 1; const key = `${row}-${col}`; const triangle = triangleStatesRef.current.get(key); if (!triangle) continue; const targetScale = triangle.order <= animationProgress ? 1 : 0; const scaleSpeed = 0.08; if (triangle.scale !== targetScale) { triangle.scale += (targetScale - triangle.scale) * scaleSpeed; needsUpdate = true; } const x = col * (TRIANGLE_SIZE * 0.75) + canvasXPositionRef.current; const y = row * (TRIANGLE_SIZE * 0.85) + (isFlipped ? TRIANGLE_SIZE * 0.3 : 0); if (x + TRIANGLE_SIZE < 0 || x - TRIANGLE_SIZE > canvasWidth) continue; const outlineScale = Math.min(triangle.scale + 0.3, 1); const fillScale = Math.max(triangle.scale - 0.2, 0); drawTriangle( outlineCtx, x, y, TRIANGLE_SIZE, isFlipped, outlineScale, 0, 1 ); drawTriangle(fillCtx, x, y, TRIANGLE_SIZE, isFlipped, 0, fillScale, 1); } } if (needsUpdate) { animationFrameIdRef.current = requestAnimationFrame( () => drawGrid(scrollProgress) ); } }; const initializeTriangles = () => { const positions = []; for (let row = 0; row < TRIANGLE_ROWS; row++) { for (let col = 0; col < TRIANGLE_COLS; col++) { positions.push({ key: `${row}-${col}`, row, col }); } } const totalTriangles = positions.length; for (let i = positions.length - 1; i > 0; i--) { const j = Math.floor(Math.random() * (i + 1)); [positions[i], positions[j]] = [positions[j], positions[i]]; } triangleStatesRef.current.clear(); positions.forEach((pos, index) => { triangleStatesRef.current.set(pos.key, { order: index / totalTriangles, scale: 0, row: pos.row, col: pos.col }); }); }; react$1.useEffect(() => { const outlineCanvas = outlineCanvasRef.current; const fillCanvas = fillCanvasRef.current; const stickySection = stickyRef.current; const cards = cardsRef.current; if (!outlineCanvas || !fillCanvas || !stickySection || !cards) return; const outlineCtx = outlineCanvas.getContext("2d"); const fillCtx = fillCanvas.getContext("2d"); if (!outlineCtx || !fillCtx) return; setCanvasSize(outlineCanvas, outlineCtx); setCanvasSize(fillCanvas, fillCtx); initializeTriangles(); drawGrid(); const stickyHeight = window.innerHeight * 5; const scrollTrigger = ScrollTrigger.ScrollTrigger.create({ trigger: stickySection, start: "top top", end: `+=${stickyHeight}px`, pin: true, onUpdate: (self) => { canvasXPositionRef.current = -self.progress * 200; drawGrid(self.progress); const progress = Math.min(self.progress / 0.654, 1); const translateX = -progress * 200; cards.style.transform = `translateX(${translateX}%)`; } }); const handleResize = () => { if (outlineCtx && fillCtx) { setCanvasSize(outlineCanvas, outlineCtx); setCanvasSize(fillCanvas, fillCtx); drawGrid(); } }; window.addEventListener("resize", handleResize); return () => { if (animationFrameIdRef.current) { cancelAnimationFrame(animationFrameIdRef.current); } scrollTrigger.kill(); window.removeEventListener("resize", handleResize); }; }, []); return /* @__PURE__ */ jsxRuntime.jsxs( "div", { className: `min-h-screen ${className}`, style: { height: "800vh", fontFamily: '"PP Neue Montreal", sans-serif' }, children: [ /* @__PURE__ */ jsxRuntime.jsx("section", { className: "relative w-screen h-screen overflow-hidden flex justify-center items-center bg-black text-white", children: /* @__PURE__ */ jsxRuntime.jsxs("h1", { className: "text-center text-4xl md:text-6xl lg:text-[80px] uppercase font-light leading-none", children: [ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-orange-500", children: "Enter a Universe" }), " Powered by Imagination" ] }) }), /* @__PURE__ */ jsxRuntime.jsxs( "section", { ref: stickyRef, className: "relative w-screen h-screen overflow-hidden", children: [ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute inset-0 w-full h-full bg-gradient-to-br from-slate-900 via-purple-900 to-slate-900" }), /* @__PURE__ */ jsxRuntime.jsx( "canvas", { ref: outlineCanvasRef, className: "absolute top-0 left-0 z-[1]", style: { width: "150%", height: "150%" } } ), /* @__PURE__ */ jsxRuntime.jsx( "div", { ref: cardsRef, className: "absolute top-0 left-0 w-[300%] h-screen flex justify-around items-center z-[2]", style: { willChange: "transform" }, children: defaultCards.map((card) => /* @__PURE__ */ jsxRuntime.jsxs( "div", { className: "relative w-[10%] h-[75%] bg-black rounded-xl overflow-hidden flex flex-col", children: [ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-1 w-full", children: /* @__PURE__ */ jsxRuntime.jsx( "img", { src: card.image, alt: card.title, className: "w-full h-full object-cover" } ) }), /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "absolute bottom-5 left-5 text-white", children: [ /* @__PURE__ */ jsxRuntime.jsx("h1", { className: "text-2xl mb-1 font-light uppercase", children: card.title }), /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm opacity-70 font-medium", children: card.productCode }) ] }) ] }, card.id )) } ), /* @__PURE__ */ jsxRuntime.jsx( "canvas", { ref: fillCanvasRef, className: "absolute top-0 left-0 z-[3]", style: { width: "150%", height: "150%" } } ) ] } ), /* @__PURE__ */ jsxRuntime.jsx("section", { className: "relative w-screen h-screen overflow-hidden flex justify-center items-center bg-black text-white", children: /* @__PURE__ */ jsxRuntime.jsxs("h1", { className: "text-center text-4xl md:text-6xl lg:text-[80px] uppercase font-light leading-none", children: [ "Chase the ", /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-orange-500", children: "shadows" }), " to embrace the light" ] }) }) ] } ); } function ParallaxScroll({ children, className, speed = 0.5, offset = ["start end", "end start"], direction = "vertical", fadeIn = false, scaleOnScroll = false, rotateOnScroll = false }) { const ref = react$1.useRef(null); const { scrollYProgress } = framerMotion.useScroll({