Skip to content
Merged

Dev #40

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file removed web/public/images/Doma.png
Binary file not shown.
248 changes: 185 additions & 63 deletions web/src/app/(root)/HeroSection.tsx
Original file line number Diff line number Diff line change
@@ -1,85 +1,207 @@
'use client';

import { useRef } from 'react';
import { gsap } from '@/lib/gsap';
import { useGSAP } from '@gsap/react';
import { SplitText } from 'gsap/all';
import { useRef } from 'react';
import Link from 'next/link';

export default function HeroSection() {
const titleRef = useRef<null | HTMLDivElement>(null);
const shuffleTextRef = useRef<null | HTMLDivElement>(null);
useGSAP(() => {
const title = SplitText.create(titleRef.current, {
type: 'chars',
});
const shuffleText = SplitText.create(shuffleTextRef.current, {
type: 'chars',
});
const chars = shuffleText.chars as HTMLElement[];

chars.forEach((el) => {
el.dataset.final = el.textContent || '';
});

const randomChar = () => {
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
return chars[Math.floor(Math.random() * chars.length)];
};

const tl = gsap.timeline({ delay: 1 });

chars.forEach((el, i) => {
const containerRef = useRef<HTMLDivElement>(null);
const titleRef = useRef<HTMLHeadingElement>(null);

useGSAP(
() => {
const handleMouseMove = (e: MouseEvent) => {
const { clientX, clientY } = e;
const x = (clientX / window.innerWidth - 0.5) * 2;
const y = (clientY / window.innerHeight - 0.5) * 2;

gsap.to('.parallax-item-1', {
x: x * 60,
y: y * 60,
duration: 1.5,
ease: 'power2.out',
});

gsap.to('.parallax-item-2', {
x: x * -80,
y: y * -80,
duration: 1.5,
ease: 'power2.out',
});

gsap.to('.parallax-item-3', {
x: x * 40,
y: y * -40,
duration: 1.5,
ease: 'power2.out',
});

gsap.to('.parallax-item-4', {
x: x * -50,
y: y * 50,
duration: 1.5,
ease: 'power2.out',
});
};

window.addEventListener('mousemove', handleMouseMove);

const titleSplit = SplitText.create(titleRef.current, {
type: 'chars, words',
});

const tl = gsap.timeline({ delay: 0.3 });

tl.fromTo(
el,
titleSplit.chars,
{
opacity: 0,
y: 100,
rotateZ: 15,
scale: 0.5,
},
{
opacity: 1,
duration: 0.03,
repeat: 8,
onUpdate: () => {
el.textContent = randomChar();
},
onComplete: () => {
el.textContent = el.dataset.final || '';
},
y: 0,
rotateZ: 0,
scale: 1,
stagger: 0.03,
ease: 'back.out(1.5)',
duration: 0.8,
}
);

tl.fromTo(
'.fade-up',
{ opacity: 0, y: 40 },
{ opacity: 1, y: 0, duration: 0.8, stagger: 0.15, ease: 'power3.out' },
'-=0.4'
);

tl.fromTo(
'.floating-badge',
{ opacity: 0, scale: 0, rotate: -20 },
{
opacity: 1,
scale: 1,
rotate: 0,
duration: 0.8,
stagger: 0.1,
ease: 'back.out(1.5)',
},
i * 0.09
'-=0.6'
);
});
tl.fromTo(
title.chars,
{
y: 100,
opacity: 0,
},
{
y: 0,
opacity: 1,
ease: 'power1.out',
stagger: 0.02,
}
);
});

gsap.to('.marquee-content', {
xPercent: -50,
ease: 'none',
duration: 25,
repeat: -1,
});

return () => {
window.removeEventListener('mousemove', handleMouseMove);
};
},
{ scope: containerRef }
);

return (
<header className="mt-20 mb-16 flex flex-col gap-6 md:mt-0">
<div className="flex flex-col justify-between gap-3 md:flex-row md:items-center">
<div className="flex items-center gap-2">
<div className="bg-foreground/20 h-px w-12" />
<section
ref={containerRef}
className="bg-background relative flex h-full w-full flex-col items-center justify-center overflow-hidden px-6"
>
<div className="pointer-events-none absolute inset-0 z-0 overflow-hidden">
<div className="floating-badge parallax-item-1 border-border/50 bg-foreground/5 absolute top-[20%] left-[10%] rotate-[-8deg] rounded-full border px-6 py-3 backdrop-blur-xl md:left-[15%]">
<span className="text-foreground text-sm font-black tracking-widest uppercase">
Frontend Architecture
</span>
</div>

<div className="floating-badge parallax-item-2 border-border/50 bg-foreground/5 absolute top-[30%] right-[10%] rotate-[6deg] rounded-full border px-6 py-3 backdrop-blur-xl md:right-[15%]">
<span className="text-foreground text-sm font-black tracking-widest uppercase">
System Design
</span>
</div>

<div className="floating-badge parallax-item-3 border-border/50 bg-foreground/5 absolute bottom-[35%] left-[5%] rotate-[12deg] rounded-full border px-6 py-3 backdrop-blur-xl md:left-[12%]">
<span className="text-foreground text-sm font-black tracking-widest uppercase">
Microservices
</span>
</div>

<div className="floating-badge parallax-item-4 border-border/50 bg-foreground/5 absolute right-[5%] bottom-[25%] rotate-[-10deg] rounded-full border px-6 py-3 backdrop-blur-xl md:right-[20%]">
<span className="text-foreground text-sm font-black tracking-widest uppercase">
Performance Tuning
</span>
</div>
</div>

<div className="relative z-10 flex w-full max-w-5xl flex-col items-center text-center">
<div className="fade-up border-border/50 bg-foreground/5 mb-6 inline-flex items-center gap-3 rounded-full border px-5 py-2 backdrop-blur-md">
<span className="relative flex h-2.5 w-2.5">
<span className="bg-foreground absolute inline-flex h-full w-full animate-ping rounded-full opacity-75" />
<span className="bg-foreground relative inline-flex h-2.5 w-2.5 rounded-full" />
</span>
<span className="text-muted-foreground text-xs font-bold tracking-[0.2em] uppercase">
Kirito Blogs v1.0
</span>
</div>

<h1
ref={titleRef}
className="font-ops mb-6 text-6xl font-black tracking-tighter uppercase md:text-8xl lg:text-9xl"
style={{ perspective: '1000px' }}
>
<span className="text-foreground">CRAFTING</span> <br />
<span
ref={shuffleTextRef}
className="text-muted-foreground text-xs font-bold tracking-[0.2em] uppercase"
className="text-transparent"
style={{ WebkitTextStroke: '2px var(--foreground)' }}
>
Welcome to <span className="text-white">Kirito</span> Blog
THE WEB
</span>
</h1>

<p className="fade-up text-muted-foreground mb-10 max-w-2xl text-base leading-relaxed font-medium md:text-xl">
Where clean code meets scalable architecture. I build digital
experiences that are fast, accessible, and visually stunning.
</p>

<div className="fade-up flex flex-wrap items-center justify-center gap-6">
<Link
href="/blogs"
className="group bg-foreground text-background hover:shadow-foreground/20 relative flex items-center gap-3 overflow-hidden rounded-full px-8 py-4 font-bold transition-transform duration-300 hover:scale-105 hover:shadow-2xl"
>
<span className="relative z-10 tracking-wider">EXPLORE BLOGS</span>
<svg
className="relative z-10 h-5 w-5 transition-transform duration-300 group-hover:translate-x-1"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M17 8l4 4m0 0l-4 4m4-4H3"
/>
</svg>
<div className="bg-background/20 absolute inset-0 -translate-x-full transition-transform duration-300 group-hover:translate-x-0" />
</Link>
</div>
<div ref={titleRef}>
<h1 className="font-ops text-center text-5xl font-semibold tracking-tight md:text-7xl lg:text-8xl">
A personal space for{' '}
<span className="text-red-500">development</span> <br />
and <span className="text-blue-500">system design</span>
</h1>
</div>

<div className="pointer-events-none absolute bottom-5 left-0 z-0 flex w-full overflow-hidden opacity-50 select-none">
<div
className="marquee-content flex gap-8 text-[150px] leading-none font-black tracking-tighter whitespace-nowrap text-transparent uppercase"
style={{ WebkitTextStroke: '2px var(--foreground)' }}
>
<span>INNOVATE • BUILD • SCALE • OPTIMIZE •</span>
<span>INNOVATE • BUILD • SCALE • OPTIMIZE •</span>
</div>
</div>
</header>
</section>
);
}
41 changes: 41 additions & 0 deletions web/src/app/(root)/components/ScrollIndicator.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
'use client';

import { useEffect, useRef } from 'react';
import { ChevronDown } from 'lucide-react';

export function ScrollIndicator({
progressRef,
}: {
progressRef?: React.MutableRefObject<number>;
}) {
const ref = useRef<HTMLDivElement>(null);

useEffect(() => {
if (!progressRef) return;

let raf: number;
const tick = () => {
if (ref.current) {
const opacity = Math.max(0, 1 - progressRef.current / 0.15);
ref.current.style.opacity = String(opacity);
ref.current.style.pointerEvents = opacity <= 0 ? 'none' : 'auto';
}
raf = requestAnimationFrame(tick);
};
tick();

return () => cancelAnimationFrame(raf);
}, [progressRef]);

return (
<div
ref={ref}
className="text-foreground pointer-events-none absolute bottom-8 left-1/2 z-20 flex -translate-x-1/2 flex-col items-center gap-1 transition-opacity duration-300"
>
<span className="text-xs tracking-widest uppercase opacity-70">
Scroll
</span>
<ChevronDown className="h-5 w-5 animate-bounce" />
</div>
);
}
Loading
Loading