Skip to content
Open
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -37,3 +37,4 @@ yarn-error.log*

# vercel
.vercel
package-lock.json
20 changes: 20 additions & 0 deletions components/NotionPageWrapper.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { NotionPage as OriginalNotionPage } from './NotionPage'
import dynamic from 'next/dynamic'
import type { PageProps } from '@/lib/types'
import { useRouter } from 'next/router'

const ReadingProgress = dynamic(() => import('./effects/ReadingProgress'), {
ssr: false,
})

export function NotionPageWrapper(props: PageProps) {
const router = useRouter()
const isBlogPost = router.pathname === '/[pageId]' && props.pageId !== '16ccc94eb4cf4b3d85fb31ac7be58e87'

return (
<>
{isBlogPost && <ReadingProgress />}
<OriginalNotionPage {...props} />
</>
)
}
42 changes: 42 additions & 0 deletions components/effects/CustomCursor.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
.cursor,
.cursorFollower {
position: fixed;
pointer-events: none;
z-index: 9999;
mix-blend-mode: difference;
transition: transform 0.15s ease-out;
}

.cursor {
width: 8px;
height: 8px;
background: white;
border-radius: 50%;
transform: translate(-50%, -50%);
}

.cursorFollower {
width: 32px;
height: 32px;
border: 1px solid rgba(255, 255, 255, 0.5);
border-radius: 50%;
transform: translate(-50%, -50%);
transition: transform 0.3s ease-out, width 0.3s ease, height 0.3s ease;
}

.cursor.hovering {
transform: translate(-50%, -50%) scale(1.5);
}

.cursorFollower.hovering {
width: 50px;
height: 50px;
border-color: white;
}

@media (pointer: coarse) {
.cursor,
.cursorFollower {
display: none;
}
}
84 changes: 84 additions & 0 deletions components/effects/CustomCursor.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
'use client'

import { useEffect, useState } from 'react'
import styles from './CustomCursor.module.css'

export default function CustomCursor() {
const [position, setPosition] = useState({ x: 0, y: 0 })
const [isHovering, setIsHovering] = useState(false)
const [isMobile, setIsMobile] = useState(false)

useEffect(() => {
// Check if device is mobile/touch
const checkMobile = () => {
setIsMobile(window.matchMedia('(pointer: coarse)').matches)
}

checkMobile()
window.addEventListener('resize', checkMobile)

if (isMobile) return

const updatePosition = (e: MouseEvent) => {
setPosition({ x: e.clientX, y: e.clientY })
}

const handleMouseEnter = (e: Event) => {
const target = e.target as HTMLElement
if (
target.tagName === 'A' ||
target.tagName === 'BUTTON' ||
target.closest('a') ||
target.closest('button') ||
target.classList.contains('cursor-interactive')
) {
setIsHovering(true)
}
}

const handleMouseLeave = (e: Event) => {
const target = e.target as HTMLElement
if (
target.tagName === 'A' ||
target.tagName === 'BUTTON' ||
target.closest('a') ||
target.closest('button') ||
target.classList.contains('cursor-interactive')
) {
setIsHovering(false)
}
}

window.addEventListener('mousemove', updatePosition)
document.addEventListener('mouseenter', handleMouseEnter, true)
document.addEventListener('mouseleave', handleMouseLeave, true)

return () => {
window.removeEventListener('mousemove', updatePosition)
document.removeEventListener('mouseenter', handleMouseEnter, true)
document.removeEventListener('mouseleave', handleMouseLeave, true)
window.removeEventListener('resize', checkMobile)
}
}, [isMobile])

if (isMobile) return null

return (
<>
<div
className={`${styles.cursor} ${isHovering ? styles.hovering : ''}`}
style={{
left: `${position.x}px`,
top: `${position.y}px`,
}}
/>
<div
className={`${styles.cursorFollower} ${isHovering ? styles.hovering : ''}`}
style={{
left: `${position.x}px`,
top: `${position.y}px`,
}}
/>
</>
)
}
11 changes: 11 additions & 0 deletions components/effects/PageTransition.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
.overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: var(--bg-color);
opacity: 0;
pointer-events: none;
z-index: 9998;
}
54 changes: 54 additions & 0 deletions components/effects/PageTransition.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
'use client'

import { useEffect } from 'react'
import { useRouter } from 'next/router'
import styles from './PageTransition.module.css'

export default function PageTransition({ children }: { children: React.ReactNode }) {
const router = useRouter()

useEffect(() => {
const initGSAP = async () => {
const gsap = (await import('gsap')).default

const handleRouteChangeStart = () => {
gsap.to('.page-transition-overlay', {
duration: 0.3,
opacity: 1,
ease: 'power2.inOut',
})
}

const handleRouteChangeComplete = () => {
gsap.to('.page-transition-overlay', {
duration: 0.3,
opacity: 0,
ease: 'power2.inOut',
delay: 0.1,
})

// Scroll to top on route change
window.scrollTo(0, 0)
}

router.events.on('routeChangeStart', handleRouteChangeStart)
router.events.on('routeChangeComplete', handleRouteChangeComplete)
router.events.on('routeChangeError', handleRouteChangeComplete)

return () => {
router.events.off('routeChangeStart', handleRouteChangeStart)
router.events.off('routeChangeComplete', handleRouteChangeComplete)
router.events.off('routeChangeError', handleRouteChangeComplete)
}
}

initGSAP()
}, [router])

return (
<>
<div className={`page-transition-overlay ${styles.overlay}`} />
{children}
</>
)
}
15 changes: 15 additions & 0 deletions components/effects/ReadingProgress.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
.progressBar {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 3px;
background: var(--fg-color-0);
z-index: 9999;
}

.progressFill {
height: 100%;
background: linear-gradient(90deg, #667eea 0%, #764ba2 100%);
transition: width 0.1s ease-out;
}
34 changes: 34 additions & 0 deletions components/effects/ReadingProgress.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
'use client'

import { useEffect, useState } from 'react'
import styles from './ReadingProgress.module.css'

export default function ReadingProgress() {
const [progress, setProgress] = useState(0)

useEffect(() => {
const updateProgress = () => {
const scrollTop = window.scrollY
const docHeight = document.documentElement.scrollHeight - window.innerHeight
const scrollPercent = (scrollTop / docHeight) * 100

setProgress(Math.min(scrollPercent, 100))
}

window.addEventListener('scroll', updateProgress, { passive: true })
updateProgress() // Initial call

return () => {
window.removeEventListener('scroll', updateProgress)
}
}, [])

return (
<div className={styles.progressBar}>
<div
className={styles.progressFill}
style={{ width: `${progress}%` }}
/>
</div>
)
}
58 changes: 58 additions & 0 deletions components/effects/SmoothScroll.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
'use client'

import { useEffect } from 'react'

export default function SmoothScroll() {
useEffect(() => {
let lenis: any = null

const initLenis = async () => {
const Lenis = (await import('lenis')).default

lenis = new Lenis({
duration: 1.2,
easing: (t) => Math.min(1, 1.001 - Math.pow(2, -10 * t)),
orientation: 'vertical',
gestureOrientation: 'vertical',
smoothWheel: true,
wheelMultiplier: 1,
touchMultiplier: 2,
})

function raf(time: number) {
lenis.raf(time)
requestAnimationFrame(raf)
}

requestAnimationFrame(raf)

// Integrate with GSAP ScrollTrigger if available
if (typeof window !== 'undefined') {
const gsapModule = await import('gsap')
const ScrollTriggerModule = await import('gsap/ScrollTrigger')
const gsap = gsapModule.default
const ScrollTrigger = ScrollTriggerModule.default

gsap.registerPlugin(ScrollTrigger)

lenis.on('scroll', ScrollTrigger.update)

gsap.ticker.add((time) => {
lenis.raf(time * 1000)
})

gsap.ticker.lagSmoothing(0)
}
}

initLenis()

return () => {
if (lenis) {
lenis.destroy()
}
}
}, [])

return null
}
Loading
Loading