Skip to content

Repository files navigation

me

Personal portfolio — Next.js 16 (App Router) + React 19 + Tailwind CSS v4, TypeScript, deployable to Vercel.

Run it

npm install
npm run dev      # http://localhost:3000
npm run build    # production build

Editing content

All copy, links, projects, services, stack and stats live in src/content/site.ts. Anything marked // TODO is a placeholder — swap it before shipping. No other file needs touching for a content change.

Structure

src/app/layout.tsx      fonts + metadata (title, OG, Twitter)
src/app/globals.css     theme tokens, marquee/reveal/caret animations
src/app/page.tsx        section order
src/components/         Nav, Hero, Marquee, About, Featured, Services,
                        Stack, Writing, Contact, Footer, Cursor, Reveal

Checking it visually

node scripts/screenshot.mjs http://localhost:3000 home

Scrolls the page in steps, screenshots each one into screenshots/ (gitignored) and prints any console errors — the scroll-driven animations are hard to verify any other way. Needs npx playwright install chromium once.

Theming

Dark is the default and the design's home; light is an explicit opt-in, not a system guess. Two independent axes, both stored on <html>:

  • data-modedark | light (surfaces, borders, effect strengths)
  • data-themeblue | ember | matrix | violet | amber | rose (accent trio)

Any accent works in either mode. The picker lives in the nav; the choice is saved to localStorage and replayed by an inline script in <head> before first paint, so there is no flash of the default theme. <html data-*> is the single source of truth — components read it through useSyncExternalStore, never a mirrored state.

Add a palette by appending to THEMES in src/lib/theme.ts and adding a matching [data-theme="…"] block in globals.css.

Motion

Ported from the reference's own bundles (I read its minified components), using the same libraries it does — motion for spring/scroll work, lenis for smooth scrolling. The numbers below are the ones it actually runs.

Effect Where Detail
Section-outline particle field fx/EdgeField 6000 particles trace the perimeter of the section you're on and morph to the next, staggered per particle; ring for the hero (sized to enclose the name). A section can nominate an inner element with data-field-frame and the outline wraps that instead — the contact card uses it — or pin it with data-field-hold (the services deck does both, so the outline holds still until every card has passed), then breaks into two side clusters over the footer. The box follows the section as you scroll — that tracking is most of the effect. Pointer repulsion with swirl, click shockwaves, scroll-speed energy driving size/opacity/jitter. Canvas 2D, additive
Intro preloader, 0→100 fx/Preloader 900ms easeOutQuad, once per session, freezes scroll via is-loading
Cursor ring + dot fx/CursorRing exponential smoothing (dot k=30, ring k=12, scale k=16); ring ×1.8 over links, dot ×0.5 on press
Pointer glow + grid reveal fx/CursorFX --mx/--my on <html>, rAF-throttled
Hero pointer parallax Hero whole block on ±10/8px springs (60/18)
Smooth scrolling fx/SmoothScroll Lenis, held stopped until the intro clears
Scroll progress hairline fx/ScrollProgress spring 120/30/0.3, 3px with glow
Page tint drifting per section fx/SectionTint colours in tints (site.ts)
Word-by-word masked headings fx/AnimatedHeading y 110%→0, 0.06s stagger, accent words in gradient
Glyph scramble fx/ScrambleText 700ms; terminal lines cascade at 140ms each
Auto-firing glitch fx/Glitch hover, plus a 280ms burst every 4–8s on its own
Velocity marquee Marquee base 3.33%/s, boosted/reversed by scroll velocity, skewX ±4°
Scroll-driven 3D card deck Services 260vh sticky stage, 1400px perspective, progress dots
Card spotlight border + fill fx/Spotlight --gx/--gy per card
Directional scroll reveals fx/Reveal 0.7s, cubic-bezier(.22,1,.36,1)
3D tilt card fx/TiltCard About photo: pointer-tracked rotateX/rotateY on 150/18 springs, children layered on the Z axis (bloom −60px, outline +45px, photo +20px, label +70px), glare tracking the cursor
Magnetic buttons fx/Magnetic
Stat count-up fx/CountUp easeOutExpo on first view
Role typewriter + block caret fx/Typewriter
Copy-email confirmation, animated mobile drawer, social lift-and-glow, floating orbs, scan lines, pulse dot, link wipes Contact, Nav, SocialLinks, globals.css

All of it collapses to static under prefers-reduced-motion: reduce — the card deck falls back to a plain grid — and the pointer-driven layers are dropped on coarse pointers and small screens.

Section order

Mirrors the reference exactly:

hero → marquee → about (01) → services (02) → stack (03)
     → work (04) → writing (unnumbered) → contact (05)

Nav links follow the same order, with Blog last. Stats live at the foot of About, not in the hero — the hero carries the badge, name, one paragraph, two buttons and the social row, and nothing else.

Pages

/ and /blog. Both wrap in SiteChrome, so the ambient layers, cursor, smooth scroll, nav and footer are identical across routes; nav anchors rewrite to /#section when you are off the home page.

Deploy

Push to GitHub and import on Vercel — zero config.

About

personal portfolio

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages