subtitle flipper

This commit is contained in:
Mike Karl 2026-04-22 20:27:52 +02:00
parent a9c5059ad0
commit 0a14de0269
2 changed files with 72 additions and 4 deletions

View file

@ -27,7 +27,12 @@ export const footerContent = {
}; };
export const heroContent = { export const heroContent = {
subtitle: "Systementwickler", subtitle: [
"Systementwickler",
"Creative Technologist",
"Low-Level Enthusiast",
"Graphics Programmer",
],
nameFirst: "MIKE", nameFirst: "MIKE",
nameLast: "KARL", nameLast: "KARL",
}; };

View file

@ -6,6 +6,8 @@ import { ScrollTrigger } from "gsap/ScrollTrigger";
import ParticleGrid from "../components/ParticleGrid"; import ParticleGrid from "../components/ParticleGrid";
import { heroContent, site } from "../data/site"; import { heroContent, site } from "../data/site";
const subtitles = heroContent.subtitle;
gsap.registerPlugin(ScrollTrigger); gsap.registerPlugin(ScrollTrigger);
export default function HeroSection() { export default function HeroSection() {
@ -80,9 +82,9 @@ export default function HeroSection() {
ref={contentRef} ref={contentRef}
className="relative z-10 text-center px-6" className="relative z-10 text-center px-6"
> >
<p className="font-mono text-amber text-sm md:text-base tracking-[0.1em] uppercase mb-6"> <div className="font-mono text-amber text-sm md:text-base tracking-[0.1em] uppercase mb-6 overflow-hidden">
{heroContent.subtitle} <SubtitleFlipper />
</p> </div>
<h1 <h1
className="font-display font-bold text-text-primary leading-[0.9] tracking-tight" className="font-display font-bold text-text-primary leading-[0.9] tracking-tight"
style={{ fontSize: "clamp(4rem, 12vw, 10rem)" }} style={{ fontSize: "clamp(4rem, 12vw, 10rem)" }}
@ -101,3 +103,64 @@ export default function HeroSection() {
</section> </section>
); );
} }
function SubtitleFlipper() {
const containerRef = useRef<HTMLDivElement>(null);
useEffect(() => {
const container = containerRef.current;
if (!container) return;
const items = container.querySelectorAll<HTMLSpanElement>(".subtitle-item");
if (items.length <= 1) return;
gsap.set(items, { yPercent: 100, opacity: 0 });
gsap.set(items[0], { yPercent: 0, opacity: 1 });
let current = 0;
let timeoutId: ReturnType<typeof setTimeout>;
const duration = 0.5;
const hold = 2500;
const showNext = () => {
const next = (current + 1) % items.length;
gsap.to(items[current], {
yPercent: -100,
opacity: 0,
duration,
ease: "power2.inOut",
});
gsap.fromTo(
items[next],
{ yPercent: 100, opacity: 0 },
{ yPercent: 0, opacity: 1, duration, ease: "power2.inOut" }
);
current = next;
timeoutId = setTimeout(showNext, hold);
};
timeoutId = setTimeout(showNext, hold);
return () => {
clearTimeout(timeoutId);
gsap.killTweensOf(items);
};
}, []);
return (
<div ref={containerRef} className="grid place-items-center">
{subtitles.map((text, i) => (
<span
key={text}
className="subtitle-item col-start-1 row-start-1 whitespace-nowrap"
aria-hidden={i !== 0}
>
{text}
</span>
))}
</div>
);
}