diff --git a/parfum-shop/public/atmos-discovery-set-thumbnail.png b/parfum-shop/public/atmos-discovery-set-thumbnail.png new file mode 100755 index 0000000..59bb364 Binary files /dev/null and b/parfum-shop/public/atmos-discovery-set-thumbnail.png differ diff --git a/parfum-shop/public/blasse-seide-product-sample-image.png b/parfum-shop/public/blasse-seide-product-sample-image.png new file mode 100755 index 0000000..f85610f Binary files /dev/null and b/parfum-shop/public/blasse-seide-product-sample-image.png differ diff --git a/parfum-shop/public/kalter-beton-product-sample-image.png b/parfum-shop/public/kalter-beton-product-sample-image.png new file mode 100755 index 0000000..b2ed715 Binary files /dev/null and b/parfum-shop/public/kalter-beton-product-sample-image.png differ diff --git a/parfum-shop/public/nasser-marmor-product-sample-image.png b/parfum-shop/public/nasser-marmor-product-sample-image.png new file mode 100755 index 0000000..dd59ab7 Binary files /dev/null and b/parfum-shop/public/nasser-marmor-product-sample-image.png differ diff --git a/parfum-shop/public/schwarzes-benzin-product-sample-image.png b/parfum-shop/public/schwarzes-benzin-product-sample-image.png new file mode 100755 index 0000000..cb0f6c6 Binary files /dev/null and b/parfum-shop/public/schwarzes-benzin-product-sample-image.png differ diff --git a/parfum-shop/public/verbrannteschrom-product-sample-image.png b/parfum-shop/public/verbrannteschrom-product-sample-image.png new file mode 100755 index 0000000..aeeedc0 Binary files /dev/null and b/parfum-shop/public/verbrannteschrom-product-sample-image.png differ diff --git a/parfum-shop/public/weisse-asche-product-sample-image.png b/parfum-shop/public/weisse-asche-product-sample-image.png new file mode 100755 index 0000000..4210ffc Binary files /dev/null and b/parfum-shop/public/weisse-asche-product-sample-image.png differ diff --git a/parfum-shop/src/hooks/useScrollTextReveal.js b/parfum-shop/src/hooks/useScrollTextReveal.js new file mode 100644 index 0000000..3f6c9a8 --- /dev/null +++ b/parfum-shop/src/hooks/useScrollTextReveal.js @@ -0,0 +1,168 @@ +import { useLayoutEffect } from "react"; +import { gsap } from "gsap"; +import { ScrollTrigger } from "gsap/ScrollTrigger"; + +let pluginsRegistered = false; + +const registerGsap = () => { + if (!pluginsRegistered) { + gsap.registerPlugin(ScrollTrigger); + pluginsRegistered = true; + } +}; + +const createRevealLines = (element) => { + if (!element) { + return []; + } + + if (element.dataset.revealPrepared === "true") { + return Array.from(element.querySelectorAll(".reveal-line")); + } + + const originalHtml = element.innerHTML; + const segments = originalHtml + .split(//i) + .map((segment) => segment.trim()) + .filter(Boolean); + + if (segments.length === 0) { + return []; + } + + element.dataset.revealPrepared = "true"; + element.dataset.revealOriginalHtml = originalHtml; + element.innerHTML = segments + .map( + (segment) => + `${segment}` + ) + .join(""); + + return Array.from(element.querySelectorAll(".reveal-line")); +}; + +const restoreRevealLines = (element) => { + if (!element || element.dataset.revealPrepared !== "true") { + return; + } + + const originalHtml = element.dataset.revealOriginalHtml; + + if (originalHtml) { + element.innerHTML = originalHtml; + } + + delete element.dataset.revealPrepared; + delete element.dataset.revealOriginalHtml; +}; + +function useScrollTextReveal(scopeRef, deps = []) { + useLayoutEffect(() => { + const scope = scopeRef.current; + + if (!scope || typeof window === "undefined") { + return undefined; + } + + if (window.matchMedia("(prefers-reduced-motion: reduce)").matches) { + return undefined; + } + + registerGsap(); + + const preparedElements = []; + const ctx = gsap.context(() => { + const groups = gsap.utils.toArray("[data-reveal-group]"); + + groups.forEach((group) => { + const items = Array.from(group.querySelectorAll("[data-reveal]")).filter( + (item) => item.closest("[data-reveal-group]") === group + ); + + if (items.length === 0) { + return; + } + + items.forEach((item) => { + if (item.dataset.reveal === "lines") { + gsap.set(item, { autoAlpha: 0 }); + return; + } + + gsap.set(item, { + y: 36, + autoAlpha: 0, + force3D: true, + }); + }); + + ScrollTrigger.create({ + trigger: group, + start: group.dataset.revealStart || "top 86%", + once: true, + onEnter: () => { + const timeline = gsap.timeline(); + + items.forEach((item, index) => { + const position = index === 0 ? 0 : "<0.16"; + + if (item.dataset.reveal === "lines") { + const lines = createRevealLines(item); + + if (lines.length === 0) { + return; + } + + preparedElements.push(item); + gsap.set(item, { autoAlpha: 1 }); + gsap.set(lines, { + yPercent: 115, + rotate: 2.2, + transformOrigin: "0% 100%", + force3D: true, + }); + + timeline.to( + lines, + { + yPercent: 0, + rotate: 0, + duration: 1.18, + stagger: 0.1, + ease: "power4.out", + clearProps: "transform", + }, + position + ); + + return; + } + + timeline.to( + item, + { + y: 0, + autoAlpha: 1, + duration: 1.02, + ease: "power4.out", + clearProps: "transform,opacity,visibility", + }, + position + ); + }); + }, + }); + }); + + ScrollTrigger.refresh(); + }, scope); + + return () => { + ctx.revert(); + preparedElements.forEach((element) => restoreRevealLines(element)); + }; + }, [scopeRef, ...deps]); +} + +export default useScrollTextReveal; diff --git a/parfum-shop/src/style/textReveal.css b/parfum-shop/src/style/textReveal.css new file mode 100644 index 0000000..a288d61 --- /dev/null +++ b/parfum-shop/src/style/textReveal.css @@ -0,0 +1,16 @@ +.reveal-line-mask { + display: block; + overflow: hidden; + padding-bottom: 0.08em; + margin-bottom: -0.08em; +} + +.reveal-line { + display: block; + will-change: transform; + backface-visibility: hidden; +} + +[data-reveal="fade"] { + will-change: transform, opacity; +}