diff --git a/.gitignore b/.gitignore
index d8b6db9779..31da6c6dc2 100644
--- a/.gitignore
+++ b/.gitignore
@@ -37,3 +37,4 @@ yarn-error.log*
# vercel
.vercel
+package-lock.json
diff --git a/components/NotionPageWrapper.tsx b/components/NotionPageWrapper.tsx
new file mode 100644
index 0000000000..ac7ea8aaab
--- /dev/null
+++ b/components/NotionPageWrapper.tsx
@@ -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 && }
+
+ >
+ )
+}
diff --git a/components/effects/CustomCursor.module.css b/components/effects/CustomCursor.module.css
new file mode 100644
index 0000000000..1465cfe3a3
--- /dev/null
+++ b/components/effects/CustomCursor.module.css
@@ -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;
+ }
+}
diff --git a/components/effects/CustomCursor.tsx b/components/effects/CustomCursor.tsx
new file mode 100644
index 0000000000..5242edeac4
--- /dev/null
+++ b/components/effects/CustomCursor.tsx
@@ -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 (
+ <>
+
+
+ >
+ )
+}
diff --git a/components/effects/PageTransition.module.css b/components/effects/PageTransition.module.css
new file mode 100644
index 0000000000..22923e0c08
--- /dev/null
+++ b/components/effects/PageTransition.module.css
@@ -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;
+}
diff --git a/components/effects/PageTransition.tsx b/components/effects/PageTransition.tsx
new file mode 100644
index 0000000000..9b8e04e19b
--- /dev/null
+++ b/components/effects/PageTransition.tsx
@@ -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 (
+ <>
+
+ {children}
+ >
+ )
+}
diff --git a/components/effects/ReadingProgress.module.css b/components/effects/ReadingProgress.module.css
new file mode 100644
index 0000000000..af9fd6166b
--- /dev/null
+++ b/components/effects/ReadingProgress.module.css
@@ -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;
+}
diff --git a/components/effects/ReadingProgress.tsx b/components/effects/ReadingProgress.tsx
new file mode 100644
index 0000000000..ae79983245
--- /dev/null
+++ b/components/effects/ReadingProgress.tsx
@@ -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 (
+
+ )
+}
diff --git a/components/effects/SmoothScroll.tsx b/components/effects/SmoothScroll.tsx
new file mode 100644
index 0000000000..065d4cd7ac
--- /dev/null
+++ b/components/effects/SmoothScroll.tsx
@@ -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
+}
diff --git a/components/home/About.tsx b/components/home/About.tsx
new file mode 100644
index 0000000000..2c43313a5d
--- /dev/null
+++ b/components/home/About.tsx
@@ -0,0 +1,86 @@
+'use client'
+
+import { useEffect, useRef } from 'react'
+import styles from '@/styles/home.module.css'
+
+const roles = [
+ { emoji: '๐ข', title: 'CEO', company: 'Autonomous Technologies' },
+ { emoji: '๐ง ', title: 'Founder', company: 'Memox' },
+ { emoji: '๐ค', title: 'AI Builder', company: 'Agentic Systems' },
+ { emoji: '๐จโ๐งโ๐ฆ', title: 'Father of Two', company: 'Life' },
+]
+
+export default function About() {
+ const sectionRef = useRef(null)
+ const titleRef = useRef(null)
+ const contentRef = useRef(null)
+
+ useEffect(() => {
+ const initAnimations = async () => {
+ try {
+ const gsap = (await import('gsap')).default
+ const ScrollTrigger = (await import('gsap/ScrollTrigger')).default
+ gsap.registerPlugin(ScrollTrigger)
+
+ // Use 'to' with autoAlpha for safe animation
+ // Elements start visible, GSAP handles the reveal
+ gsap.fromTo(
+ titleRef.current,
+ { opacity: 0, y: 30 },
+ {
+ scrollTrigger: { trigger: sectionRef.current, start: 'top 85%' },
+ duration: 0.8,
+ opacity: 1,
+ y: 0,
+ ease: 'power3.out',
+ }
+ )
+
+ const children = contentRef.current?.children
+ if (children) {
+ gsap.fromTo(
+ Array.from(children),
+ { opacity: 0, y: 25 },
+ {
+ scrollTrigger: { trigger: sectionRef.current, start: 'top 75%' },
+ duration: 0.6,
+ opacity: 1,
+ y: 0,
+ stagger: 0.15,
+ ease: 'power3.out',
+ }
+ )
+ }
+ } catch {
+ // GSAP failed - elements visible via CSS
+ }
+ }
+
+ initAnimations()
+ }, [])
+
+ return (
+
+
+
+ About
+
+
+
+ I build technology that works for people. I specialize in designing AI-driven
+ platforms, orchestrating multi-agent workflows, and leading teams that turn
+ bold ideas into working, scalable systems.
+
+
+ {roles.map((role, index) => (
+
+
{role.emoji} {role.title}
+
{role.company}
+
+ ))}
+
+
+
+
+ )
+}
diff --git a/components/home/BlogGrid.tsx b/components/home/BlogGrid.tsx
new file mode 100644
index 0000000000..3b95d2e5d7
--- /dev/null
+++ b/components/home/BlogGrid.tsx
@@ -0,0 +1,116 @@
+'use client'
+
+import { useEffect, useRef } from 'react'
+import Link from 'next/link'
+import { formatDate } from '@/lib/utils'
+import styles from '@/styles/home.module.css'
+
+interface BlogPost {
+ id: string
+ title: string
+ slug: string
+ date?: string
+ tags?: string[]
+}
+
+interface BlogGridProps {
+ posts: BlogPost[]
+}
+
+export default function BlogGrid({ posts }: BlogGridProps) {
+ const sectionRef = useRef(null)
+ const titleRef = useRef(null)
+ const gridRef = useRef(null)
+
+ useEffect(() => {
+ const initAnimations = async () => {
+ try {
+ const gsap = (await import('gsap')).default
+ const ScrollTrigger = (await import('gsap/ScrollTrigger')).default
+ gsap.registerPlugin(ScrollTrigger)
+
+ gsap.fromTo(
+ titleRef.current,
+ { opacity: 0, y: 30 },
+ {
+ scrollTrigger: { trigger: sectionRef.current, start: 'top 85%' },
+ duration: 0.8,
+ opacity: 1,
+ y: 0,
+ ease: 'power3.out',
+ }
+ )
+
+ const cards = gridRef.current?.children
+ if (cards && cards.length > 0) {
+ gsap.fromTo(
+ Array.from(cards),
+ { opacity: 0, y: 30 },
+ {
+ scrollTrigger: { trigger: gridRef.current, start: 'top 85%' },
+ duration: 0.6,
+ opacity: 1,
+ y: 0,
+ stagger: 0.1,
+ ease: 'power3.out',
+ }
+ )
+ }
+ } catch {
+ // GSAP failed - elements visible via CSS
+ }
+ }
+
+ if (posts && posts.length > 0) {
+ initAnimations()
+ }
+ }, [posts])
+
+ if (!posts || posts.length === 0) {
+ return (
+
+
+
Latest Posts
+
Posts coming soon...
+
+
+ )
+ }
+
+ return (
+
+
+
+ Latest Posts
+
+
+ {posts.slice(0, 6).map((post) => (
+
+
+ {post.title}
+ {post.date && (
+
+ {formatDate(post.date)}
+
+ )}
+ {post.tags && post.tags.length > 0 && (
+
+ {post.tags.slice(0, 3).map((tag, i) => (
+
+ {tag}
+
+ ))}
+
+ )}
+
+
+ ))}
+
+
+
+ )
+}
diff --git a/components/home/Hero.tsx b/components/home/Hero.tsx
new file mode 100644
index 0000000000..c8c98315d6
--- /dev/null
+++ b/components/home/Hero.tsx
@@ -0,0 +1,100 @@
+'use client'
+
+import { useEffect, useRef, useState } from 'react'
+import styles from '@/styles/home.module.css'
+
+export default function Hero() {
+ const heroRef = useRef(null)
+ const headingRef = useRef(null)
+ const subtitleRef = useRef(null)
+ const socialsRef = useRef(null)
+ const [animated, setAnimated] = useState(false)
+
+ useEffect(() => {
+ const initAnimations = async () => {
+ try {
+ const gsap = (await import('gsap')).default
+
+ // Hide elements before animating
+ if (headingRef.current) {
+ gsap.set(headingRef.current, { opacity: 0, y: 30 })
+ }
+ if (subtitleRef.current) {
+ gsap.set(subtitleRef.current, { opacity: 0, y: 20 })
+ }
+ if (socialsRef.current) {
+ gsap.set(Array.from(socialsRef.current.children), { opacity: 0, y: 15 })
+ }
+
+ setAnimated(true)
+
+ const tl = gsap.timeline({ defaults: { ease: 'power3.out' } })
+
+ tl.to(headingRef.current, { duration: 0.8, opacity: 1, y: 0 })
+ tl.to(subtitleRef.current, { duration: 0.6, opacity: 1, y: 0 }, '-=0.4')
+ tl.to(
+ Array.from(socialsRef.current?.children || []),
+ { duration: 0.4, opacity: 1, y: 0, stagger: 0.08 },
+ '-=0.3'
+ )
+ } catch {
+ // GSAP failed to load - elements stay visible via CSS defaults
+ }
+ }
+
+ initAnimations()
+ }, [])
+
+ return (
+
+
+
+ Hello World ๐, I'm Abdullah
+
+
+ Tech Leader & Builder – CEO @ Autonomous, Founder @ Memox.
+
+ Building AI-driven platforms and leading teams that ship.
+
+
+
+
+
+
+ )
+}
diff --git a/components/layout/EnhancedFooter.module.css b/components/layout/EnhancedFooter.module.css
new file mode 100644
index 0000000000..0105929066
--- /dev/null
+++ b/components/layout/EnhancedFooter.module.css
@@ -0,0 +1,21 @@
+.enhancedFooter {
+ position: relative;
+ margin-top: 4rem;
+}
+
+.enhancedFooter::before {
+ content: '';
+ position: absolute;
+ top: 0;
+ left: 50%;
+ transform: translateX(-50%);
+ width: 80%;
+ max-width: 800px;
+ height: 1px;
+ background: linear-gradient(
+ 90deg,
+ transparent,
+ var(--fg-color-2) 50%,
+ transparent
+ );
+}
diff --git a/components/layout/EnhancedFooter.tsx b/components/layout/EnhancedFooter.tsx
new file mode 100644
index 0000000000..97d8793024
--- /dev/null
+++ b/components/layout/EnhancedFooter.tsx
@@ -0,0 +1,40 @@
+'use client'
+
+import { useEffect, useRef } from 'react'
+import { Footer } from '../Footer'
+
+export default function EnhancedFooter() {
+ const footerRef = useRef(null)
+
+ useEffect(() => {
+ const initAnimations = async () => {
+ try {
+ const gsap = (await import('gsap')).default
+ const ScrollTrigger = (await import('gsap/ScrollTrigger')).default
+ gsap.registerPlugin(ScrollTrigger)
+
+ gsap.fromTo(
+ footerRef.current,
+ { opacity: 0, y: 20 },
+ {
+ scrollTrigger: { trigger: footerRef.current, start: 'top 95%' },
+ duration: 0.6,
+ opacity: 1,
+ y: 0,
+ ease: 'power3.out',
+ }
+ )
+ } catch {
+ // visible by default
+ }
+ }
+
+ initAnimations()
+ }, [])
+
+ return (
+
+
+
+ )
+}
diff --git a/components/layout/EnhancedHeader.module.css b/components/layout/EnhancedHeader.module.css
new file mode 100644
index 0000000000..d61cdbdd4c
--- /dev/null
+++ b/components/layout/EnhancedHeader.module.css
@@ -0,0 +1,28 @@
+.enhancedHeader {
+ position: sticky;
+ top: 0;
+ z-index: 100;
+ background: var(--bg-color);
+ backdrop-filter: blur(20px);
+ -webkit-backdrop-filter: blur(20px);
+ border-bottom: 1px solid var(--fg-color-1);
+ transition: transform 0.3s ease, opacity 0.3s ease;
+}
+
+.enhancedHeader.hidden {
+ transform: translateY(-100%);
+ opacity: 0;
+}
+
+.enhancedHeader.visible {
+ transform: translateY(0);
+ opacity: 1;
+}
+
+.dark .enhancedHeader {
+ background: rgba(18, 18, 18, 0.8);
+}
+
+:root:not(.dark) .enhancedHeader {
+ background: rgba(255, 255, 255, 0.8);
+}
diff --git a/components/layout/EnhancedHeader.tsx b/components/layout/EnhancedHeader.tsx
new file mode 100644
index 0000000000..14aa6b86b2
--- /dev/null
+++ b/components/layout/EnhancedHeader.tsx
@@ -0,0 +1,53 @@
+'use client'
+
+import { useEffect, useRef, useState } from 'react'
+import { NotionPageHeader } from '../NotionPageHeader'
+import styles from './EnhancedHeader.module.css'
+
+export default function EnhancedHeader(props: any) {
+ const headerRef = useRef(null)
+ const [isVisible, setIsVisible] = useState(true)
+ const [lastScrollY, setLastScrollY] = useState(0)
+
+ useEffect(() => {
+ let ticking = false
+
+ const handleScroll = () => {
+ if (!ticking) {
+ window.requestAnimationFrame(() => {
+ const currentScrollY = window.scrollY
+
+ if (currentScrollY < 100) {
+ setIsVisible(true)
+ } else if (currentScrollY > lastScrollY) {
+ // Scrolling down
+ setIsVisible(false)
+ } else {
+ // Scrolling up
+ setIsVisible(true)
+ }
+
+ setLastScrollY(currentScrollY)
+ ticking = false
+ })
+
+ ticking = true
+ }
+ }
+
+ window.addEventListener('scroll', handleScroll, { passive: true })
+
+ return () => {
+ window.removeEventListener('scroll', handleScroll)
+ }
+ }, [lastScrollY])
+
+ return (
+
+
+
+ )
+}
diff --git a/lib/homepage-data.ts b/lib/homepage-data.ts
new file mode 100644
index 0000000000..529dde1a14
--- /dev/null
+++ b/lib/homepage-data.ts
@@ -0,0 +1,95 @@
+import { NotionAPI } from 'notion-client'
+import { getCanonicalPageId } from './get-canonical-page-id'
+
+const notion = new NotionAPI()
+
+export interface BlogPost {
+ id: string
+ title: string
+ slug: string
+ date?: string
+ tags?: string[]
+}
+
+// Notion schema property IDs (from collection schema)
+const PROP_PUBLISHED = 'a {
+ try {
+ const pageRm = await notion.getPage(id)
+ const block = pageRm.block?.[id]?.value
+ if (!block || !block.properties) return null
+
+ const props = block.properties
+ const title = props.title?.[0]?.[0] || 'Untitled'
+
+ // Check if public
+ const isPublic = props[PROP_PUBLIC]?.[0]?.[0] === 'Yes'
+ if (!isPublic) return null
+
+ // Get slug from canonical page ID
+ const slug =
+ getCanonicalPageId(block.id, pageRm, { uuid: false }) ||
+ block.id.replace(/-/g, '')
+
+ // Extract date - format: [["โฃ",[["d",{"type":"date","start_date":"2025-08-12"}]]]]
+ let date: string | undefined
+ const dateVal = props[PROP_PUBLISHED]
+ if (dateVal) {
+ const dateInfo = dateVal?.[0]?.[1]?.[0]?.[1]
+ date = dateInfo?.start_date
+ }
+
+ // Extract tags - format: [["Tag1,Tag2"]] for multi_select
+ let tags: string[] = []
+ const tagsVal = props[PROP_TAGS]?.[0]?.[0]
+ if (tagsVal) {
+ tags = tagsVal.split(',').map((t: string) => t.trim())
+ }
+
+ return { id: block.id, title, slug, date, tags }
+ } catch {
+ return null
+ }
+ })
+
+ const results = await Promise.all(fetchPromises)
+ results.forEach((r) => {
+ if (r) posts.push(r)
+ })
+
+ // Sort by date (newest first)
+ posts.sort((a, b) => {
+ if (!a.date) return 1
+ if (!b.date) return -1
+ return new Date(b.date).getTime() - new Date(a.date).getTime()
+ })
+
+ return { posts }
+ } catch (error) {
+ console.error('Error fetching homepage data:', error)
+ return { posts: [] }
+ }
+}
diff --git a/lib/utils.ts b/lib/utils.ts
new file mode 100644
index 0000000000..b5b2ec6705
--- /dev/null
+++ b/lib/utils.ts
@@ -0,0 +1,12 @@
+export function formatDate(dateString: string): string {
+ try {
+ const date = new Date(dateString)
+ return new Intl.DateTimeFormat('en-US', {
+ year: 'numeric',
+ month: 'long',
+ day: 'numeric',
+ }).format(date)
+ } catch (error) {
+ return dateString
+ }
+}
diff --git a/next.config.js b/next.config.js
index cb72e69c83..1b8a66eeea 100644
--- a/next.config.js
+++ b/next.config.js
@@ -2,6 +2,10 @@
// import { fileURLToPath } from 'node:url'
export default {
+ typescript: {
+ // GSAP ships untyped JS files that fail Turbopack's type checker
+ ignoreBuildErrors: true,
+ },
staticPageGenerationTimeout: 300,
images: {
remotePatterns: [
diff --git a/package.json b/package.json
index ab56b5b451..898e433066 100644
--- a/package.json
+++ b/package.json
@@ -33,6 +33,8 @@
"@keyvhq/redis": "^1.6.10",
"classnames": "^2.5.1",
"expiry-map": "^2.0.0",
+ "gsap": "^3.14.2",
+ "lenis": "^1.3.17",
"fathom-client": "^3.4.1",
"katex": "^0.16.28",
"ky": "^1.14.3",
diff --git a/pages/[pageId].tsx b/pages/[pageId].tsx
index a10f8f3246..fa476a3145 100644
--- a/pages/[pageId].tsx
+++ b/pages/[pageId].tsx
@@ -1,6 +1,6 @@
import { type GetStaticProps } from 'next'
-import { NotionPage } from '@/components/NotionPage'
+import { NotionPageWrapper } from '@/components/NotionPageWrapper'
import { domain, isDev, pageUrlOverrides } from '@/lib/config'
import { getSiteMap } from '@/lib/get-site-map'
import { resolveNotionPage } from '@/lib/resolve-notion-page'
@@ -53,5 +53,5 @@ export async function getStaticPaths() {
}
export default function NotionDomainDynamicPage(props: PageProps) {
- return
+ return
}
diff --git a/pages/_app.tsx b/pages/_app.tsx
index 4e0b796064..76900f31ab 100644
--- a/pages/_app.tsx
+++ b/pages/_app.tsx
@@ -12,12 +12,15 @@ import 'styles/global.css'
import 'styles/notion.css'
// global style overrides for prism theme (optional)
import 'styles/prism-theme.css'
+// effects styles
+import 'styles/effects.css'
import type { AppProps } from 'next/app'
import * as Fathom from 'fathom-client'
import { useRouter } from 'next/router'
import { posthog } from 'posthog-js'
import * as React from 'react'
+import dynamic from 'next/dynamic'
import { bootstrap } from '@/lib/bootstrap-client'
import {
@@ -28,6 +31,19 @@ import {
posthogId
} from '@/lib/config'
+// Dynamically import effects to avoid SSR issues
+const SmoothScroll = dynamic(() => import('@/components/effects/SmoothScroll'), {
+ ssr: false,
+})
+
+const CustomCursor = dynamic(() => import('@/components/effects/CustomCursor'), {
+ ssr: false,
+})
+
+const PageTransition = dynamic(() => import('@/components/effects/PageTransition'), {
+ ssr: false,
+})
+
if (!isServer) {
bootstrap()
}
@@ -61,5 +77,13 @@ export default function App({ Component, pageProps }: AppProps) {
}
}, [router.events])
- return
+ return (
+ <>
+
+
+
+
+
+ >
+ )
}
diff --git a/pages/_document.tsx b/pages/_document.tsx
index aa234f3466..96a5c15809 100644
--- a/pages/_document.tsx
+++ b/pages/_document.tsx
@@ -7,8 +7,15 @@ export default class MyDocument extends Document {
-
+
+ {/* Google Fonts */}
+
+
+
diff --git a/pages/index.tsx b/pages/index.tsx
index 1f817f9e54..dd93dace89 100644
--- a/pages/index.tsx
+++ b/pages/index.tsx
@@ -1,22 +1,66 @@
import type { PageProps } from '@/lib/types'
-import { NotionPage } from '@/components/NotionPage'
+import { NotionPageWrapper } from '@/components/NotionPageWrapper'
import { domain } from '@/lib/config'
import { resolveNotionPage } from '@/lib/resolve-notion-page'
+import { getHomepageData, BlogPost } from '@/lib/homepage-data'
+import Hero from '@/components/home/Hero'
+import About from '@/components/home/About'
+import BlogGrid from '@/components/home/BlogGrid'
+import EnhancedFooter from '@/components/layout/EnhancedFooter'
+import { NotionPageHeader } from '@/components/NotionPageHeader'
+import { PageHead } from '@/components/PageHead'
+import { getBlockValue } from 'notion-utils'
+
+interface HomePageProps extends PageProps {
+ isHomePage: boolean
+ posts: BlogPost[]
+}
export const getStaticProps = async () => {
try {
const props = await resolveNotionPage(domain)
+ const { posts } = await getHomepageData()
- return { props, revalidate: 10 }
+ return {
+ props: {
+ ...props,
+ isHomePage: true,
+ posts,
+ },
+ revalidate: 10,
+ }
} catch (err) {
console.error('page error', domain, err)
-
- // we don't want to publish the error version of this page, so
- // let next.js know explicitly that incremental SSG failed
throw err
}
}
-export default function NotionDomainPage(props: PageProps) {
- return
+export default function NotionDomainPage(props: HomePageProps) {
+ const { isHomePage, posts, ...notionProps } = props
+
+ // Render custom homepage for the root page
+ if (isHomePage && notionProps.pageId === '16ccc94eb4cf4b3d85fb31ac7be58e87') {
+ // Get block for header
+ const keys = Object.keys(notionProps.recordMap?.block || {})
+ const block = getBlockValue(notionProps.recordMap?.block?.[keys[0]!])
+
+ return (
+ <>
+
+ {block && }
+
+
+
+
+ >
+ )
+ }
+
+ // Render normal Notion page for other routes
+ return
}
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 7be8cb4419..b802a95f35 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -26,12 +26,18 @@ importers:
fathom-client:
specifier: ^3.4.1
version: 3.7.2
+ gsap:
+ specifier: ^3.14.2
+ version: 3.14.2
katex:
specifier: ^0.16.28
version: 0.16.28
ky:
specifier: ^1.14.3
version: 1.14.3
+ lenis:
+ specifier: ^1.3.17
+ version: 1.3.17(react@19.2.4)
lqip-modern:
specifier: ^2.2.1
version: 2.2.1
@@ -1386,6 +1392,9 @@ packages:
resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==}
engines: {node: '>= 0.4'}
+ gsap@3.14.2:
+ resolution: {integrity: sha512-P8/mMxVLU7o4+55+1TCnQrPmgjPKnwkzkXOK1asnR9Jg2lna4tEY5qBJjMmAaOBDDZWtlRjBXjLa0w53G/uBLA==}
+
has-bigints@1.1.0:
resolution: {integrity: sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==}
engines: {node: '>= 0.4'}
@@ -1646,6 +1655,20 @@ packages:
resolution: {integrity: sha512-MbjN408fEndfiQXbFQ1vnd+1NoLDsnQW41410oQBXiyXDMYH5z505juWa4KUE1LqxRC7DgOgZDbKLxHIwm27hA==}
engines: {node: '>=0.10'}
+ lenis@1.3.17:
+ resolution: {integrity: sha512-k9T9rgcxne49ggJOvXCraWn5dt7u2mO+BNkhyu6yxuEnm9c092kAW5Bus5SO211zUvx7aCCEtzy9UWr0RB+oJw==}
+ peerDependencies:
+ '@nuxt/kit': '>=3.0.0'
+ react: '>=17.0.0'
+ vue: '>=3.0.0'
+ peerDependenciesMeta:
+ '@nuxt/kit':
+ optional: true
+ react:
+ optional: true
+ vue:
+ optional: true
+
levn@0.4.1:
resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==}
engines: {node: '>= 0.8.0'}
@@ -3895,6 +3918,8 @@ snapshots:
gopd@1.2.0: {}
+ gsap@3.14.2: {}
+
has-bigints@1.1.0: {}
has-flag@4.0.0: {}
@@ -4151,6 +4176,10 @@ snapshots:
dependencies:
language-subtag-registry: 0.3.23
+ lenis@1.3.17(react@19.2.4):
+ optionalDependencies:
+ react: 19.2.4
+
levn@0.4.1:
dependencies:
prelude-ls: 1.2.1
diff --git a/styles/effects.css b/styles/effects.css
new file mode 100644
index 0000000000..13ab4a314a
--- /dev/null
+++ b/styles/effects.css
@@ -0,0 +1,59 @@
+/* Global effect styles */
+
+* {
+ cursor: none !important;
+}
+
+@media (pointer: coarse) {
+ * {
+ cursor: auto !important;
+ }
+}
+
+/* Smooth scroll behavior */
+html.lenis,
+html.lenis body {
+ height: auto;
+}
+
+.lenis.lenis-smooth {
+ scroll-behavior: auto !important;
+}
+
+.lenis.lenis-smooth [data-lenis-prevent] {
+ overscroll-behavior: contain;
+}
+
+.lenis.lenis-stopped {
+ overflow: hidden;
+}
+
+.lenis.lenis-scrolling iframe {
+ pointer-events: none;
+}
+
+/* Reduced motion */
+@media (prefers-reduced-motion: reduce) {
+ *,
+ *::before,
+ *::after {
+ animation-duration: 0.01ms !important;
+ animation-iteration-count: 1 !important;
+ transition-duration: 0.01ms !important;
+ scroll-behavior: auto !important;
+ }
+
+ * {
+ cursor: auto !important;
+ }
+}
+
+/* Interactive elements for custom cursor */
+.cursor-interactive {
+ position: relative;
+}
+
+/* Page transitions */
+body {
+ overflow-x: hidden;
+}
diff --git a/styles/global.css b/styles/global.css
index 7eee7abea3..db02d78f90 100644
--- a/styles/global.css
+++ b/styles/global.css
@@ -13,12 +13,65 @@ html {
margin: 0;
}
-body {
- --notion-font: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont,
+/* CSS Custom Properties for Design System */
+:root {
+ /* Typography */
+ --font-body: 'Inter', ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont,
'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans',
'Droid Sans', 'Helvetica Neue', 'Noto Sans', sans-serif;
- font-family: var(--notion-font);
+ --font-heading: 'Space Grotesk', 'Inter', ui-sans-serif, system-ui, -apple-system,
+ BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell',
+ 'Fira Sans', 'Droid Sans', 'Helvetica Neue', 'Noto Sans', sans-serif;
+
+ /* Spacing */
+ --space-xs: 0.25rem;
+ --space-sm: 0.5rem;
+ --space-md: 1rem;
+ --space-lg: 2rem;
+ --space-xl: 4rem;
+ --space-2xl: 8rem;
+}
+
+/* Light Mode Colors (default) */
+:root:not(.dark-mode) {
+ --bg-color: #ffffff;
+ --bg-color-0: #f9fafb;
+ --bg-color-1: #f3f4f6;
+ --fg-color: #1f2937;
+ --fg-color-0: #e5e7eb;
+ --fg-color-1: #d1d5db;
+ --fg-color-2: #9ca3af;
+ --fg-color-3: #6b7280;
+ --fg-color-4: #4b5563;
+ --fg-color-5: #374151;
+}
+
+/* Dark Mode Colors */
+.dark-mode {
+ --bg-color: #121212;
+ --bg-color-0: #1a1a1a;
+ --bg-color-1: #242424;
+ --fg-color: #f9fafb;
+ --fg-color-0: #2a2a2a;
+ --fg-color-1: #3a3a3a;
+ --fg-color-2: #6b7280;
+ --fg-color-3: #9ca3af;
+ --fg-color-4: #d1d5db;
+ --fg-color-5: #e5e7eb;
+}
+
+body {
+ --notion-font: var(--font-body);
+ font-family: var(--font-body);
overflow-x: hidden;
+ background: var(--bg-color);
+ color: var(--fg-color);
+ transition: background-color 0.3s ease, color 0.3s ease;
+}
+
+/* Headings use Space Grotesk */
+h1, h2, h3, h4, h5, h6 {
+ font-family: var(--font-heading);
}
.static-tweet blockquote {
@@ -39,24 +92,34 @@ body {
caret-color: var(--fg-color);
}
-::-webkit-scrollbar
-{
+::-webkit-scrollbar {
width: 5px;
height: 5px;
- background-color: #F5F5F5;
background-color: var(--bg-color-1);
}
-::-webkit-scrollbar-thumb
-{
- border-radius: 10px;
- -webkit-box-shadow: inset 0 0 6px rgba(0,0,0,.3);
- background-color: #555;
+::-webkit-scrollbar-thumb {
border-radius: 10px;
-webkit-box-shadow: inset 0 0 6px rgba(0,0,0,.3);
background-color: var(--fg-color-1);
}
::-webkit-scrollbar-track {
- background-color: var(--bg-color);
+ background-color: var(--bg-color);
+}
+
+/* Smooth transitions */
+* {
+ transition-property: background-color, border-color, color;
+ transition-duration: 0.2s;
+ transition-timing-function: ease;
+}
+
+/* Reduced motion: disable all animations */
+@media (prefers-reduced-motion: reduce) {
+ *, *::before, *::after {
+ animation-duration: 0.01ms !important;
+ animation-iteration-count: 1 !important;
+ transition-duration: 0.01ms !important;
+ }
}
diff --git a/styles/home.module.css b/styles/home.module.css
new file mode 100644
index 0000000000..904b859ece
--- /dev/null
+++ b/styles/home.module.css
@@ -0,0 +1,320 @@
+/* Hero Section */
+.hero {
+ position: relative;
+ min-height: 100vh;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ overflow: hidden;
+ background: var(--bg-color);
+}
+
+.heroContent {
+ position: relative;
+ z-index: 2;
+ text-align: center;
+ padding: 2rem;
+ max-width: 1100px;
+ margin: 0 auto;
+}
+
+.heroHeading {
+ font-size: clamp(2.5rem, 7vw, 5.5rem);
+ font-weight: 700;
+ line-height: 1.1;
+ margin: 0 0 1.5rem 0;
+ letter-spacing: -0.03em;
+ background: linear-gradient(135deg, var(--fg-color) 0%, var(--fg-color-3) 100%);
+ -webkit-background-clip: text;
+ -webkit-text-fill-color: transparent;
+ background-clip: text;
+}
+
+.heroHeading :global(.char) {
+ display: inline-block;
+ transform-origin: 50% 100%;
+}
+
+.heroSubtitle {
+ font-size: clamp(1.1rem, 2vw, 1.4rem);
+ color: var(--fg-color-4);
+ margin: 0 0 2.5rem 0;
+ font-weight: 400;
+ letter-spacing: 0.01em;
+ line-height: 1.6;
+}
+
+.heroSocials {
+ display: flex;
+ gap: 1rem;
+ justify-content: center;
+ margin-bottom: 0;
+}
+
+.socialLink {
+ font-size: 0.95rem;
+ font-weight: 500;
+ color: var(--fg-color);
+ text-decoration: none;
+ padding: 0.6rem 1.4rem;
+ border: 1px solid var(--fg-color-1);
+ border-radius: 50px;
+ transition: all 0.3s ease;
+ background: var(--bg-color-0);
+}
+
+.socialLink:hover {
+ background: var(--fg-color);
+ color: var(--bg-color);
+ border-color: var(--fg-color);
+ transform: translateY(-2px);
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
+}
+
+.scrollIndicator {
+ position: absolute;
+ bottom: 2rem;
+ left: 50%;
+ transform: translateX(-50%);
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ gap: 0.5rem;
+ color: var(--fg-color-2);
+ font-size: 0.75rem;
+ text-transform: uppercase;
+ letter-spacing: 0.15em;
+ animation: bounce 2s infinite;
+}
+
+@keyframes bounce {
+ 0%, 100% { transform: translateX(-50%) translateY(0); }
+ 50% { transform: translateX(-50%) translateY(8px); }
+}
+
+.heroBackground {
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ z-index: 1;
+ background:
+ radial-gradient(ellipse at 20% 50%, rgba(102, 126, 234, 0.08), transparent 50%),
+ radial-gradient(ellipse at 80% 50%, rgba(118, 75, 162, 0.06), transparent 50%),
+ radial-gradient(ellipse at 50% 80%, rgba(236, 72, 153, 0.04), transparent 40%);
+}
+
+:global(.dark-mode) .heroBackground {
+ background:
+ radial-gradient(ellipse at 20% 50%, rgba(102, 126, 234, 0.12), transparent 50%),
+ radial-gradient(ellipse at 80% 50%, rgba(118, 75, 162, 0.10), transparent 50%),
+ radial-gradient(ellipse at 50% 80%, rgba(236, 72, 153, 0.06), transparent 40%);
+}
+
+/* About Section */
+.about {
+ padding: 6rem 2rem;
+ background: var(--bg-color-0);
+}
+
+.aboutContainer {
+ max-width: 1100px;
+ margin: 0 auto;
+}
+
+.sectionTitle {
+ font-size: clamp(2rem, 4vw, 3rem);
+ font-weight: 700;
+ margin: 0 0 2.5rem 0;
+ letter-spacing: -0.02em;
+}
+
+.aboutContent {
+ display: flex;
+ flex-direction: column;
+ gap: 2.5rem;
+}
+
+.aboutText {
+ font-size: clamp(1.05rem, 1.8vw, 1.2rem);
+ line-height: 1.8;
+ color: var(--fg-color-4);
+ max-width: 750px;
+}
+
+.rolesGrid {
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
+ gap: 1.5rem;
+}
+
+.roleCard {
+ padding: 1.75rem;
+ border: 1px solid var(--fg-color-1);
+ border-radius: 16px;
+ background: var(--bg-color);
+ transition: all 0.3s ease;
+ position: relative;
+ overflow: hidden;
+}
+
+.roleCard::before {
+ content: '';
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 3px;
+ background: linear-gradient(90deg, rgba(102, 126, 234, 0.8), rgba(118, 75, 162, 0.8));
+ opacity: 0;
+ transition: opacity 0.3s ease;
+}
+
+.roleCard:hover {
+ border-color: var(--fg-color-2);
+ transform: translateY(-4px);
+ box-shadow: 0 8px 24px rgba(0, 0, 0, 0.08);
+}
+
+.roleCard:hover::before {
+ opacity: 1;
+}
+
+.roleTitle {
+ font-size: 1.3rem;
+ font-weight: 600;
+ margin: 0 0 0.4rem 0;
+ color: var(--fg-color);
+}
+
+.roleCompany {
+ font-size: 0.95rem;
+ color: var(--fg-color-3);
+ margin: 0;
+}
+
+/* Blog Section */
+.blog {
+ padding: 6rem 2rem;
+ background: var(--bg-color);
+}
+
+.blogContainer {
+ max-width: 1100px;
+ margin: 0 auto;
+}
+
+.blogGrid {
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
+ gap: 1.5rem;
+ margin-bottom: 2rem;
+}
+
+.blogCard {
+ display: block;
+ padding: 1.75rem;
+ border: 1px solid var(--fg-color-1);
+ border-radius: 16px;
+ background: var(--bg-color-0);
+ text-decoration: none;
+ color: var(--fg-color);
+ transition: all 0.3s ease;
+ will-change: transform;
+}
+
+.blogCard:hover {
+ border-color: var(--fg-color-2);
+ transform: translateY(-4px);
+ box-shadow: 0 8px 24px rgba(0, 0, 0, 0.08);
+}
+
+.blogTitle {
+ font-size: 1.25rem;
+ font-weight: 600;
+ margin: 0 0 0.75rem 0;
+ line-height: 1.35;
+}
+
+.blogDate {
+ display: block;
+ font-size: 0.8rem;
+ color: var(--fg-color-3);
+ margin-bottom: 0.75rem;
+}
+
+.blogTags {
+ display: flex;
+ gap: 0.5rem;
+ flex-wrap: wrap;
+}
+
+.blogTag {
+ padding: 0.2rem 0.6rem;
+ font-size: 0.7rem;
+ background: var(--fg-color-0);
+ color: var(--fg-color-4);
+ border-radius: 50px;
+ text-transform: uppercase;
+ letter-spacing: 0.05em;
+ font-weight: 500;
+}
+
+.blogViewAll {
+ text-align: center;
+ margin-top: 2.5rem;
+}
+
+.viewAllLink {
+ display: inline-block;
+ font-size: 1rem;
+ font-weight: 500;
+ color: var(--fg-color);
+ text-decoration: none;
+ padding: 0.8rem 2rem;
+ border: 1px solid var(--fg-color-1);
+ border-radius: 50px;
+ transition: all 0.3s ease;
+}
+
+.viewAllLink:hover {
+ background: var(--fg-color);
+ color: var(--bg-color);
+ border-color: var(--fg-color);
+}
+
+/* Responsive */
+@media (max-width: 768px) {
+ .hero {
+ min-height: 85vh;
+ }
+
+ .heroSocials {
+ flex-wrap: wrap;
+ gap: 0.75rem;
+ }
+
+ .about,
+ .blog {
+ padding: 4rem 1.5rem;
+ }
+
+ .rolesGrid {
+ grid-template-columns: 1fr;
+ }
+
+ .blogGrid {
+ grid-template-columns: 1fr;
+ }
+}
+
+@media (prefers-reduced-motion: reduce) {
+ .heroHeading :global(.char),
+ .scrollIndicator,
+ .blogCard,
+ .roleCard {
+ animation: none;
+ transition: none;
+ }
+}
diff --git a/styles/notion.css b/styles/notion.css
index 94a7f309fe..bddfbc61ff 100644
--- a/styles/notion.css
+++ b/styles/notion.css
@@ -403,3 +403,76 @@
.notion-equation.notion-equation-block{
align-items: center;
}
+
+/* Enhanced Typography for Blog Posts */
+.notion-page {
+ font-feature-settings: 'kern' 1, 'liga' 1, 'calt' 1;
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
+}
+
+.notion-title {
+ letter-spacing: -0.02em;
+ line-height: 1.2;
+ margin-bottom: 1.5em;
+}
+
+.notion-h1,
+.notion-h2,
+.notion-h3 {
+ letter-spacing: -0.01em;
+ font-weight: 600;
+ margin-top: 1.5em;
+}
+
+.notion-text {
+ font-size: 1.1em;
+ line-height: 1.75;
+}
+
+/* Enhanced Code Blocks */
+.notion-code {
+ font-size: 0.875em;
+ line-height: 1.6;
+ padding: 1.25em;
+ overflow-x: auto;
+ border-radius: 8px;
+ background: var(--bg-color-0);
+ border: 1px solid var(--fg-color-1);
+}
+
+.dark-mode .notion-code {
+ background: rgba(255, 255, 255, 0.05);
+ border-color: rgba(255, 255, 255, 0.1);
+}
+
+/* Better spacing */
+.notion-list {
+ margin: 0.75em 0;
+}
+
+.notion-list-item {
+ padding: 0.25em 0;
+}
+
+.notion-callout {
+ padding: 1.25em;
+ border-radius: 8px;
+ background: var(--bg-color-0);
+}
+
+/* Smooth transitions */
+.notion-link,
+.notion-collection-card,
+.notion-bookmark {
+ transition: all 0.2s ease;
+}
+
+/* Better image styling */
+.notion-asset-wrapper {
+ margin: 2em 0;
+}
+
+.notion-asset-wrapper img {
+ border-radius: 8px;
+}
diff --git a/tsconfig.json b/tsconfig.json
index 2284d3ac2b..a6102cb3fe 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -1,6 +1,7 @@
{
"extends": "@fisch0920/config/tsconfig-react",
"compilerOptions": {
+ "skipLibCheck": true,
"baseUrl": ".",
"paths": {
"@/components/*": ["components/*"],