diff --git a/web/public/images/Doma.png b/web/public/images/Doma.png deleted file mode 100644 index eedc8b6..0000000 Binary files a/web/public/images/Doma.png and /dev/null differ diff --git a/web/src/app/(root)/HeroSection.tsx b/web/src/app/(root)/HeroSection.tsx index b0bf51a..980cc0b 100644 --- a/web/src/app/(root)/HeroSection.tsx +++ b/web/src/app/(root)/HeroSection.tsx @@ -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); - const shuffleTextRef = useRef(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(null); + const titleRef = useRef(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 ( -
-
-
-
+
+
+
+ + Frontend Architecture + +
+ +
+ + System Design + +
+ +
+ + Microservices + +
+ +
+ + Performance Tuning + +
+
+ +
+
+ + + + + + Kirito Blogs v1.0 + +
+ +

+ CRAFTING
- Welcome to Kirito Blog + THE WEB +

+ +

+ Where clean code meets scalable architecture. I build digital + experiences that are fast, accessible, and visually stunning. +

+ +
+ + EXPLORE BLOGS + + + +
+
-
-

- A personal space for{' '} - development
- and system design -

+
+ +
+
+ INNOVATE • BUILD • SCALE • OPTIMIZE • + INNOVATE • BUILD • SCALE • OPTIMIZE •
-
+ ); } diff --git a/web/src/app/(root)/components/ScrollIndicator.tsx b/web/src/app/(root)/components/ScrollIndicator.tsx new file mode 100644 index 0000000..5f0c52c --- /dev/null +++ b/web/src/app/(root)/components/ScrollIndicator.tsx @@ -0,0 +1,41 @@ +'use client'; + +import { useEffect, useRef } from 'react'; +import { ChevronDown } from 'lucide-react'; + +export function ScrollIndicator({ + progressRef, +}: { + progressRef?: React.MutableRefObject; +}) { + const ref = useRef(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 ( +
+ + Scroll + + +
+ ); +} diff --git a/web/src/app/(root)/page.tsx b/web/src/app/(root)/page.tsx index 5d8c4d3..f7396f7 100644 --- a/web/src/app/(root)/page.tsx +++ b/web/src/app/(root)/page.tsx @@ -1,10 +1,8 @@ 'use client'; -import { useEffect, useMemo, useState } from 'react'; +import { useEffect, useMemo, useRef, useState } from 'react'; -import Link from 'next/link'; - -import { ScrollSmoother } from '@/lib/gsap'; +import { gsap, ScrollSmoother } from '@/lib/gsap'; import { DisplacementSphere } from '@/components/model/displacement-sphere'; @@ -19,6 +17,8 @@ import { useSession } from 'next-auth/react'; import Logo from '@/components/Logo'; import { TextIntro } from '@/components/Animations'; import { TransitionLink } from '@/components/Navigation'; +import HeroSection from './HeroSection'; +import { ScrollIndicator } from './components/ScrollIndicator'; // import { seedData } from '@/data/seed'; let introAlreadyPlayed = false; @@ -81,206 +81,224 @@ export default function Home() { }); }, [loading]); + const mainLayerRef = useRef(null); + const pinWrapRef = useRef(null); + const scrollProgressRef = useRef(0); + + useEffect(() => { + if (loading) return; + + const smoother = ScrollSmoother.create({ smooth: 3, effects: true }); + + const ctx = gsap.context(() => { + gsap.set(mainLayerRef.current, { clipPath: 'inset(0% 0% 0% 0%)' }); + + gsap.to(mainLayerRef.current, { + clipPath: 'inset(0% 0% 100% 0%)', + ease: 'none', + scrollTrigger: { + trigger: pinWrapRef.current, + start: 'top top', + end: '+=100%', + scrub: true, + pin: true, + onUpdate: (self) => { + scrollProgressRef.current = self.progress; + }, + }, + }); + }); + + return () => { + ctx.revert(); + smoother.kill(); + }; + }, [loading]); + return ( <> - {/* - - - - - KIRITO - - - - - */}
-
+
+
+ +
- { - setSphereReady(true); - }} - /> -
- - {!loading && ( -
-
-
setHomePageHover('logo')} - onMouseLeave={() => setHomePageHover(null)} - className={`${getClass('logo')} ${ - homePageHover === 'logo' ? 'scale-110' : 'scale-100' - }`} - > - -
+
+
+ { + setSphereReady(true); + }} + /> +
-
-
setHomePageHover('blogs')} - onMouseLeave={() => setHomePageHover(null)} - className={`${getClass('blogs')} ${ - homePageHover === 'blogs' ? 'scale-110' : 'scale-100' - }`} - > - - - Blogs - - -
+ {!loading && ( +
+
+
setHomePageHover('logo')} + onMouseLeave={() => setHomePageHover(null)} + className={`${getClass('logo')} ${ + homePageHover === 'logo' ? 'scale-110' : 'scale-100' + }`} + > + +
-
setHomePageHover('about')} - onMouseLeave={() => setHomePageHover(null)} - className={`${getClass('about')} ${ - homePageHover === 'about' ? 'scale-110' : 'scale-100' - }`} - > - - +
setHomePageHover('blogs')} + onMouseLeave={() => setHomePageHover(null)} + className={`${getClass('blogs')} ${ + homePageHover === 'blogs' + ? 'scale-110' + : 'scale-100' + }`} > - About - - -
- -
setHomePageHover('theme')} - onMouseLeave={() => setHomePageHover(null)} - className={`${getClass('theme')} ${ - homePageHover === 'theme' ? 'scale-110' : 'scale-100' - }`} - > - - - + + + Blogs + + +
+ +
setHomePageHover('theme')} + onMouseLeave={() => setHomePageHover(null)} + className={`${getClass('theme')} ${ + homePageHover === 'theme' + ? 'scale-110' + : 'scale-100' + }`} + > + + + +
+ +
{ + setHomePageHover('signin'); + setThreeDModelBlur(true); + }} + onMouseLeave={() => { + setHomePageHover(null); + setThreeDModelBlur(false); + }} + className={`${getClass('signin')} ${ + homePageHover === 'signin' + ? 'scale-110' + : 'scale-100' + }`} + > + + + +
+
+
+ {session && ( +
setHomePageHover('signin')} + onMouseLeave={() => setHomePageHover(null)} + className={`${getClass('signin')} ${ + homePageHover === 'signin' + ? 'scale-110' + : 'scale-100' + }`} + > +

+ Logged in as {session.user.name} +

+
+ )} +
setHomePageHover('admin')} + onMouseLeave={() => setHomePageHover(null)} + className={`${getClass('admin')} ${ + homePageHover === 'admin' + ? 'scale-110' + : 'scale-100' + }`} + > + {session?.user.role === 'admin' && ( + + + Admin + + + )} +
+
-
{ - setHomePageHover('signin'); - setThreeDModelBlur(true); - }} - onMouseLeave={() => { - setHomePageHover(null); - setThreeDModelBlur(false); - }} - className={`${getClass('signin')} ${ - homePageHover === 'signin' ? 'scale-110' : 'scale-100' - }`} - > - - - -
-
-
- {session && ( +

setHomePageHover('signin')} + onMouseEnter={() => setHomePageHover('tech')} onMouseLeave={() => setHomePageHover(null)} - className={`${getClass('signin')} ${ - homePageHover === 'signin' ? 'scale-110' : 'scale-100' + className={`${getClass('tech')} ${ + homePageHover === 'tech' ? 'scale-110' : 'scale-100' }`} > -

- Logged in as {session.user.name} -

+ +
+ tech blogs 2026 +
+
- )} -
setHomePageHover('admin')} - onMouseLeave={() => setHomePageHover(null)} - className={`${getClass('admin')} ${ - homePageHover === 'admin' ? 'scale-110' : 'scale-100' - }`} - > - {session?.user.role === 'admin' && ( - - - Admin - - - )} -
-

-
-

-
setHomePageHover('tech')} - onMouseLeave={() => setHomePageHover(null)} - className={`${getClass('tech')} ${ - homePageHover === 'tech' ? 'scale-110' : 'scale-100' - }`} - > - -
- tech blogs 2026 +
setHomePageHover('coding')} + onMouseLeave={() => setHomePageHover(null)} + className={`${getClass('coding')} ${ + homePageHover === 'coding' ? 'scale-110' : 'scale-100' + }`} + > + +
+ Coding +
+
- -
-
setHomePageHover('coding')} - onMouseLeave={() => setHomePageHover(null)} - className={`${getClass('coding')} ${ - homePageHover === 'coding' ? 'scale-110' : 'scale-100' - }`} - > - -
- Coding +
setHomePageHover('logs')} + onMouseLeave={() => setHomePageHover(null)} + className={`${getClass('logs')} ${ + homePageHover === 'logs' ? 'scale-110' : 'scale-100' + }`} + > + +
+ Logs +
+
- +

+ )} -
setHomePageHover('logs')} - onMouseLeave={() => setHomePageHover(null)} - className={`${getClass('logs')} ${ - homePageHover === 'logs' ? 'scale-110' : 'scale-100' - }`} - > - -
- Logs -
-
-
- -
- )} -
+ + +
+
+
diff --git a/web/src/app/about/components/Footer.tsx b/web/src/app/about/components/Footer.tsx deleted file mode 100644 index 615eb09..0000000 --- a/web/src/app/about/components/Footer.tsx +++ /dev/null @@ -1,104 +0,0 @@ -'use client'; - -import { useEffect, useRef } from 'react'; -import gsap from 'gsap'; -import ScrollTrigger from 'gsap/ScrollTrigger'; - -export function Footer() { - const containerRef = useRef(null); - const textRef = useRef(null); - - useEffect(() => { - gsap.registerPlugin(ScrollTrigger); - - const ctx = gsap.context(() => { - // The text starts pushed down by 150% of its own height - // and slides up to 0% exactly as the user scrolls to the bottom of the page - gsap.fromTo( - textRef.current, - { y: '150%' }, - { - y: '0%', - ease: 'none', - scrollTrigger: { - trigger: containerRef.current, - start: 'top bottom', // when the top of the footer enters the bottom of the screen - end: 'bottom bottom', // when the footer is fully in view - scrub: true, // ties the animation to the scrollbar - }, - } - ); - }, containerRef); - - return () => ctx.revert(); - }, []); - - return ( - - ); -} diff --git a/web/src/app/about/components/HeroSection.tsx b/web/src/app/about/components/HeroSection.tsx deleted file mode 100644 index a81929a..0000000 --- a/web/src/app/about/components/HeroSection.tsx +++ /dev/null @@ -1,137 +0,0 @@ -'use client'; -import { gsap } from '@/lib/gsap'; -import Image from 'next/image'; -import { useEffect, useRef } from 'react'; -import { MagneticButton } from './MagneticButton'; - -export default function HeroSection() { - const heroImageRef = useRef(null); - const heroBgRef = useRef(null); - const heroTextRef = useRef(null); - - useEffect(() => { - const ctx = gsap.context(() => { - // 1. Initial Load Reveal (Hero) - gsap.from(heroTextRef.current, { - y: 100, - opacity: 0, - duration: 1.5, - ease: 'power4.out', - delay: 0.2, - }); - - gsap.from(heroImageRef.current, { - scale: 1.1, - opacity: 0, - duration: 2, - ease: 'power3.out', - delay: 0.4, - }); - - // 2. Mouse Parallax (Hero) - const handleMouseMove = (e: MouseEvent) => { - const { innerWidth, innerHeight } = window; - const xPos = (e.clientX / innerWidth - 0.5) * 2; - const yPos = (e.clientY / innerHeight - 0.5) * 2; - - gsap.to(heroBgRef.current, { - x: xPos * 40, - y: yPos * 40, - duration: 1, - ease: 'power2.out', - }); - - gsap.to(heroImageRef.current, { - x: xPos * -20, - y: yPos * -20, - duration: 1, - ease: 'power2.out', - }); - }; - window.addEventListener('mousemove', handleMouseMove); - - return () => { - window.removeEventListener('mousemove', handleMouseMove); - }; - }); - - return () => ctx.revert(); - }, []); - - return ( -
- {/* Background Grid Pattern & Giant Typography */} -
- -
-

- ABOUT ME -

-
- - {/* Central Content Container */} -
- {/* Left: Text & Info */} -
-
- - - Introduction - -
- -

- Creative
- - Developer - -

- -

- Merging cutting-edge engineering with bold, experimental aesthetics - to build unforgettable digital experiences. -

- -
- - Explore Work - -
-
- - {/* Right: Character Image with Geometric Backdrop */} -
- {/* Geometric accents */} -
-
- - {/* The Image */} -
- Kirito -
-
-
- - {/* Decorative Corner Elements */} -
-
-
-
-
- ); -} diff --git a/web/src/app/about/components/JourneyTimeline.tsx b/web/src/app/about/components/JourneyTimeline.tsx deleted file mode 100644 index 9204edd..0000000 --- a/web/src/app/about/components/JourneyTimeline.tsx +++ /dev/null @@ -1,132 +0,0 @@ -'use client'; - -import { gsap } from '@/lib/gsap'; -import { useEffect, useRef } from 'react'; - -export function JourneyTimeline() { - const timelineNodesRef = useRef<(HTMLDivElement | null)[]>([]); - - useEffect(() => { - const ctx = gsap.context(() => { - gsap.from(timelineNodesRef.current, { - x: -50, - opacity: 0, - duration: 1, - stagger: 0.25, - ease: 'power3.out', - scrollTrigger: { - trigger: timelineNodesRef.current[0], - start: 'top 85%', - }, - }); - - timelineNodesRef.current.forEach((node) => { - const dot = node?.querySelector('.timeline-dot'); - if (dot) { - gsap.to(dot, { - scale: 1.3, - boxShadow: '0 0 20px rgba(255,255,255,0.8)', - backgroundColor: 'var(--foreground)', - duration: 0.4, - scrollTrigger: { - trigger: node, - start: 'top 60%', - end: 'bottom 40%', - toggleActions: 'play reverse play reverse', - }, - }); - } - }); - }); - - return () => ctx.revert(); - }, []); - return ( -
-
-

- The{' '} - - Journey - -

-

- A chronological timeline of my professional experience, continuous - learning, and major milestones. -

-
- -
-
{ - timelineNodesRef.current[0] = el; - }} - className="relative" - > -
-
- - 2024 — PRESENT - - - Lorem Ipsum Title -
-

- Lorem Company Name -

-

- Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do - eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim - ad minim veniam. -

-
- -
{ - timelineNodesRef.current[1] = el; - }} - className="relative" - > -
-
- - 2022 — 2024 - - - Lorem Ipsum Position -
-

Ipsum Studio

-

- Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do - eiusmod tempor incididunt ut labore et dolore magna aliqua. Duis - aute irure dolor in reprehenderit. -

-
- -
{ - timelineNodesRef.current[2] = el; - }} - className="relative" - > -
-
- - 2020 — 2022 - - - Dolor Sit Amet -
-

- Placeholder Organization -

-

- Lorem ipsum dolor sit amet, consectetur adipiscing elit. Excepteur - sint occaecat cupidatat non proident, sunt in culpa qui officia - deserunt. -

-
-
-
- ); -} diff --git a/web/src/app/about/components/MagneticButton.tsx b/web/src/app/about/components/MagneticButton.tsx deleted file mode 100644 index 485447b..0000000 --- a/web/src/app/about/components/MagneticButton.tsx +++ /dev/null @@ -1,61 +0,0 @@ -'use client'; - -import { ReactNode, useRef } from 'react'; -import gsap from 'gsap'; -import Link from 'next/link'; - -interface MagneticButtonProps { - children: ReactNode; - className?: string; - href?: string; - onClick?: () => void; -} - -export function MagneticButton({ - children, - className = '', - href, - onClick, -}: MagneticButtonProps) { - const buttonRef = useRef(null); - - const handleMove = (e: React.MouseEvent) => { - if (!buttonRef.current) return; - - const rect = buttonRef.current.getBoundingClientRect(); - - const x = e.clientX - rect.left - rect.width / 2; - const y = e.clientY - rect.top - rect.height / 2; - - gsap.to(buttonRef.current, { - x: x * 0.4, - y: y * 0.4, - duration: 0.5, - ease: 'power3.out', - }); - }; - - const handleLeave = () => { - if (!buttonRef.current) return; - - gsap.to(buttonRef.current, { - x: 0, - y: 0, - duration: 0.7, - ease: 'elastic.out(1,0.3)', - }); - }; - - return ( - - {children} - - ); -} diff --git a/web/src/app/about/components/PremiumBentoGrid.tsx b/web/src/app/about/components/PremiumBentoGrid.tsx deleted file mode 100644 index 7d4969e..0000000 --- a/web/src/app/about/components/PremiumBentoGrid.tsx +++ /dev/null @@ -1,138 +0,0 @@ -'use client'; - -import { gsap, ScrollTrigger } from '@/lib/gsap'; -import { useEffect, useRef } from 'react'; -import { MagneticButton } from './MagneticButton'; - -export function PremiumBentoGrid() { - const bentoCardsRef = useRef<(HTMLDivElement | null)[]>([]); - - useEffect(() => { - const ctx = gsap.context(() => { - const cards = bentoCardsRef.current.filter( - (card): card is HTMLDivElement => card !== null - ); - - if (!cards.length) return; - - // Ensure cards are visible initially - gsap.set(cards, { - opacity: 1, - y: 0, - }); - - // Simple animation - cards.forEach((card) => { - gsap.from(card, { - opacity: 0, - y: 50, - duration: 0.8, - ease: 'power2.out', - scrollTrigger: { - trigger: card, - start: 'top 90%', - toggleActions: 'play none none none', - }, - }); - }); - - ScrollTrigger.refresh(); - }); - - return () => ctx.revert(); - }, []); - return ( -
- {/* Background Grid */} -
- -
-

- Crafting{' '} - - digital - {' '} - realms. -

- -
-

- I bridge the gap between exceptional design and robust engineering. -

- -

- Specializing in immersive, interactive and highly performant - applications. -

-
-
- - {/* BENTO GRID */} -
- {/* CARD 1 */} -
{ - bentoCardsRef.current[0] = el; - }} - className="from-border/40 to-border/10 border-border/50 group hover:border-foreground/30 relative flex flex-col justify-between overflow-hidden rounded-3xl border bg-gradient-to-br p-10 transition-all duration-500 md:col-span-2" - > -
- -

- Philosophy -

- -

- Code is poetry. Design is the emotion it evokes. -

-
- - {/* CARD 2 */} -
{ - bentoCardsRef.current[1] = el; - }} - className="bg-border/20 border-border/50 group hover:border-foreground/30 flex flex-col items-center justify-center rounded-3xl border p-8 text-center" - > -

Tech

- -

React, Next.js, GSAP, Tailwind

-
- - {/* CARD 3 */} -
{ - bentoCardsRef.current[2] = el; - }} - className="bg-border/20 border-border/50 group hover:border-foreground/30 flex flex-col items-center justify-center rounded-3xl border p-8 text-center" - > -

Design

- -

Figma, Motion, UI/UX

-
- - {/* CARD 4 */} -
{ - bentoCardsRef.current[3] = el; - }} - className="bg-foreground text-background relative flex flex-col items-center justify-between rounded-3xl p-10 md:col-span-2 md:flex-row" - > -
-

- Let's Connect -

- -

- Open for freelance opportunities. -

-
- - - Get in touch - -
-
-
- ); -} diff --git a/web/src/app/about/components/ScrollMarquee.tsx b/web/src/app/about/components/ScrollMarquee.tsx deleted file mode 100644 index 6078be3..0000000 --- a/web/src/app/about/components/ScrollMarquee.tsx +++ /dev/null @@ -1,105 +0,0 @@ -'use client'; - -import { useEffect, useRef } from 'react'; - -interface ScrollMarqueeProps { - text: string; - direction?: -1 | 1; - baseSpeed?: number; - scrollSpeed?: number; - className?: string; - textClassName?: string; -} - -export function ScrollMarquee({ - text, - direction = -1, - baseSpeed = 1, - scrollSpeed = 0.5, - className = '', - textClassName = '', -}: ScrollMarqueeProps) { - const containerRef = useRef(null); - const textRef = useRef(null); - - useEffect(() => { - let animationFrameId: number; - let lastScrollY = window.scrollY; - let currentX = 0; - let currentDirection: -1 | 1 = direction; - - const initId = setTimeout(() => { - let singleBlockWidth = 0; - - if (textRef.current && textRef.current.children.length > 0) { - singleBlockWidth = (textRef.current.children[0] as HTMLElement) - .offsetWidth; - } - - const loop = () => { - if (!textRef.current) return; - - const currentScrollY = window.scrollY; - const scrollDelta = currentScrollY - lastScrollY; - - if (scrollDelta > 0) { - currentDirection = direction; - } else if (scrollDelta < 0) { - currentDirection = -direction as -1 | 1; - } - - lastScrollY = currentScrollY; - - currentX += - (baseSpeed + Math.abs(scrollDelta) * scrollSpeed) * currentDirection; - - if (singleBlockWidth > 0) { - if (currentX <= -singleBlockWidth) { - currentX += singleBlockWidth; - } else if (currentX >= 0) { - currentX -= singleBlockWidth; - } - } - - textRef.current.style.transform = `translate3d(${currentX}px,0,0)`; - animationFrameId = requestAnimationFrame(loop); - }; - - loop(); - }, 100); - - return () => { - clearTimeout(initId); - - if (animationFrameId) { - cancelAnimationFrame(animationFrameId); - } - }; - }, [direction, baseSpeed, scrollSpeed]); - - const repeatedText = Array(15).fill(text); - - return ( -
-
-
- {repeatedText.map((t, i) => ( - {t} - ))} -
- -
- {repeatedText.map((t, i) => ( - {t} - ))} -
-
-
- ); -} diff --git a/web/src/app/about/components/SkillsConstellation.tsx b/web/src/app/about/components/SkillsConstellation.tsx deleted file mode 100644 index 17b3a82..0000000 --- a/web/src/app/about/components/SkillsConstellation.tsx +++ /dev/null @@ -1,40 +0,0 @@ -export function SkillsConstellation() { - return ( -
-
-
-

- The{' '} - - Arsenal - -

-

- My toolkit consists of industry-leading frameworks and libraries, - allowing me to build robust, scalable, and visually stunning - applications. -

-
- {[ - 'TypeScript', - 'React', - 'Next.js', - 'Tailwind', - 'Three.js', - 'GSAP', - 'Node.js', - 'Figma', - ].map((tech) => ( - - {tech} - - ))} -
-
-
-
- ); -} diff --git a/web/src/app/about/components/SpanBox.tsx b/web/src/app/about/components/SpanBox.tsx deleted file mode 100644 index 07615a1..0000000 --- a/web/src/app/about/components/SpanBox.tsx +++ /dev/null @@ -1,50 +0,0 @@ -'use client'; -import { ReactNode, useRef, useState } from 'react'; - -export const SpanBox = ({ - children, - className, -}: { - children: ReactNode; - className?: string; -}) => { - const boxRef = useRef(null); - - const [pos, setPos] = useState({ x: 0, y: 0 }); - - const handleMove = (e: React.MouseEvent) => { - if (!boxRef.current) return; - - const rect = boxRef.current.getBoundingClientRect(); - - const x = e.clientX - rect.left - rect.width / 2; - const y = e.clientY - rect.top - rect.height / 2; - - setPos({ - x: x * 0.5, - y: y * 0.5, - }); - }; - - const handleLeave = () => { - setPos({ x: 0, y: 0 }); - }; - - return ( - - - {children} - - - ); -}; diff --git a/web/src/app/about/page.tsx b/web/src/app/about/page.tsx deleted file mode 100644 index 629cfad..0000000 --- a/web/src/app/about/page.tsx +++ /dev/null @@ -1,33 +0,0 @@ -import { ScrollMarquee } from './components/ScrollMarquee'; -import { Footer } from './components/Footer'; -import HeroSection from './components/HeroSection'; -import { JourneyTimeline } from './components/JourneyTimeline'; -import { SkillsConstellation } from './components/SkillsConstellation'; -import { PremiumBentoGrid } from './components/PremiumBentoGrid'; - -export default function About() { - return ( -
- {/* HERO */} - - - {/* MARQUEE */} -
- -
- - {/* ABOUT */} - - - - - - -
-
- ); -} diff --git a/web/src/app/blogs/BlogsClient.tsx b/web/src/app/blogs/BlogsClient.tsx index 6d52831..8b42cd0 100644 --- a/web/src/app/blogs/BlogsClient.tsx +++ b/web/src/app/blogs/BlogsClient.tsx @@ -9,8 +9,6 @@ import { useGSAP } from '@gsap/react'; import type { Blog } from '@/models/blog.schema'; import Logo from '@/components/Logo'; -const CATEGORIES = ['All', 'Frontend', 'Backend', 'DevOps']; - export default function BlogsClient({ initialBlogs, }: { @@ -40,11 +38,11 @@ export default function BlogsClient({ tl.from( gridRef.current?.children || [], { - scale: 0.9, + y: 40, opacity: 0, - duration: 0.5, + duration: 0.6, stagger: 0.1, - ease: 'back.out(1.7)', + ease: 'power3.out', }, '-=0.4' ); @@ -63,6 +61,16 @@ export default function BlogsClient({ } }, [activeCategory]); + const availableCategories = useMemo(() => { + const cats = new Set(); + blogs.forEach((blog) => { + if (blog.category) { + cats.add(blog.category); + } + }); + return ['All', ...Array.from(cats)]; + }, [blogs]); + const filteredBlogs = useMemo(() => { return activeCategory === 'All' ? blogs @@ -72,19 +80,20 @@ export default function BlogsClient({ return (
+ {/* Hero Section */}

THE{' '} - BL<G> + BL<G>

Deep dives into modern web technologies, architectural patterns, and @@ -93,14 +102,19 @@ export default function BlogsClient({

{/* Filter Section */} - +
+ +
{/* Blogs Grid */} -
+
{filteredBlogs.map((blog) => ( ))} {filteredBlogs.length === 0 && ( -
+
No articles found in this category. Stay tuned!
)} diff --git a/web/src/app/blogs/components/BlogCard.tsx b/web/src/app/blogs/components/BlogCard.tsx index 9e3b4a6..541ea06 100644 --- a/web/src/app/blogs/components/BlogCard.tsx +++ b/web/src/app/blogs/components/BlogCard.tsx @@ -1,8 +1,7 @@ 'use client'; -import React, { useRef, useEffect } from 'react'; +import React from 'react'; import Link from 'next/link'; -import gsap from 'gsap'; interface BlogCardProps { title: string; @@ -10,6 +9,12 @@ interface BlogCardProps { category: string; excerpt: string; tags: string[]; + readingTime?: number; + createdAt?: string; + coverImage?: { + url: string; + alt: string; + }; } export const BlogCard = ({ @@ -18,62 +23,121 @@ export const BlogCard = ({ category, excerpt, tags, + readingTime = 5, + createdAt, + coverImage, }: BlogCardProps) => { + const formattedDate = createdAt + ? new Intl.DateTimeFormat('en-US', { + month: 'short', + day: 'numeric', + year: 'numeric', + }).format(new Date(createdAt)) + : 'Recently'; + return ( - -
-
- - {category} - + +
+ {/* Image Header */} +
+ {coverImage ? ( + {coverImage.alt} + ) : ( +
+ + + +
+ )} + +
- - 5 min read - +
+ + {category} + + + + + + {readingTime} min + +
-

- {title} -

+ {/* Content */} +
+

+ {title} +

-

- {excerpt} -

+

+ {excerpt} +

-
- {tags.map((tag) => ( - - #{tag} - - ))} -
+ {/* Tags */} +
+ {tags.slice(0, 3).map((tag) => ( + + #{tag} + + ))} + {tags.length > 3 && ( + + +{tags.length - 3} more + + )} +
-
- READ MORE - - - + {/* Footer */} +
+ + {formattedDate} + +
+ READ MORE + + + +
+
diff --git a/web/src/app/blogs/components/CategoryFilter.tsx b/web/src/app/blogs/components/CategoryFilter.tsx index ad6d51e..04ab6dd 100644 --- a/web/src/app/blogs/components/CategoryFilter.tsx +++ b/web/src/app/blogs/components/CategoryFilter.tsx @@ -20,24 +20,27 @@ export const CategoryFilter = ({ onCategoryChange, }: CategoryFilterProps) => { return ( -
- {categories.map((category) => ( - - ))} +
+ {categories.map((category) => { + const isActive = activeCategory === category; + return ( + + ); + })}
); };