A single-page, scroll-driven portfolio built with Next.js 14, TypeScript, Tailwind CSS, Framer Motion and GSAP ScrollTrigger. Statically exported and deployed to GitHub Pages via GitHub Actions.
π Live Site: https://bcastelino.github.io
- Single-page story scroll - every section pins, and the next one flips in over it via GSAP
ScrollTrigger(rotation pivot at the bottom-left). Honorsprefers-reduced-motionand is auto-disabled on touch/small screens to avoid pin jank. - Fully responsive - fluid
clamp()typography for the hero, breakpoint-aware grids in every section, explicitviewportmeta, and a globaloverflow-x: hiddensafety net for mobile / tablet / desktop. - Always-home navigation - refresh disables
history.scrollRestorationand clears any stray hash, so the page always lands at the Hero. TheBrsignature andHOMEmenu item both force a smooth scroll to the top, even when already at#home. - Theme-aware lime accent - bright
#C3E41Din dark mode, darker olive#5C7C12in light mode for accessible contrast on white. Driven by CSS variables (--accent/--accent-fg). - Hero with blur-in animation + mouse parallax -
BRIAN/CASTELINOrendered in Fira Code with a per-letter blur-reveal (BlurText), a circular profile overlay, and a subtle cursor-driven parallax (text drifts opposite the mouse, centered on the viewport). - Interactive hover buttons - reusable
InteractiveHoverButtonwith an expanding-dot fill, slide-in action label, and an idle β loading β success lifecycle. Used for the contact form submit (driven by Formspree state) and the rΓ©sumΓ© download CTA. - Auto GitHub social previews - each project card pulls its preview straight from
opengraph.githubassets.combased on theowner/repoit points to. Falls back to a placeholder on error. - Real certifications - badge PNGs in
public/badges/and PDF artifacts inpublic/certificates/, mapped through a single data array. - Formspree-backed contact form with success/error animations.
- Static export for GitHub Pages, with a Pages workflow that skips deploys when only docs/tooling files change.
| Concern | Choice |
|---|---|
| Framework | Next.js 14 (App Router) |
| Language | TypeScript |
| Styling | Tailwind CSS + CSS vars |
| Reveal animations | Framer Motion |
| Scroll choreography | GSAP + ScrollTrigger + @gsap/react |
| Icons | lucide-react |
| Contact form | @formspree/react |
| Fonts | next/font/google - Inter + Fira Code |
| Hosting | GitHub Pages (static export) |
| CI/CD | GitHub Actions |
bcastelino.github.io/
βββ .github/workflows/
β βββ main.yml # Pages deploy; ignores docs/tooling changes
βββ app/
β βββ layout.tsx # Root layout, fonts, inline theme bootstrap
β βββ page.tsx # Single page β composes Header + FlowArt + Footer
β βββ globals.css # CSS vars, theme tokens, scrollbar, animations
β βββ lib/
β β βββ data.ts # All content: personal, projects, experience,
β β # education, certifications, skills, nav
β βββ components/
β βββ Header.tsx # Fixed nav: menu (HOME = scroll-to-top), signature, theme toggle
β βββ Hero.tsx # BRIAN / CASTELINO hero + mouse parallax (FlowSection)
β βββ BlurText.tsx # Per-letter / per-word blur-reveal animation
β βββ FlowArt.tsx # GSAP story-scroll: pin + rotate-in (off on mobile)
β βββ Section.tsx # Shared section shell (FlowSection wrapper)
β βββ InteractiveHoverButton.tsx # Dot-fill CTA with idle/loading/success states
β βββ Footer.tsx
β βββ sections/
β βββ About.tsx
β βββ Projects.tsx # GitHub OG previews + onError fallback
β βββ Experience.tsx # Timeline
β βββ Education.tsx
β βββ Certifications.tsx# Badge PNG β PDF link
β βββ Contact.tsx # Formspree form + rΓ©sumΓ© button
βββ public/
β βββ personal/ # Profile photo
β βββ projects/ # Optional project screenshots / placeholder
β βββ badges/ # Certification badge PNGs
β βββ certificates/ # Issued PDF certificates
β βββ logos/ # Company / school logos
βββ next.config.js # output: 'export', trailingSlash, unoptimized images
βββ tailwind.config.js # accent color, font variables, class dark mode
βββ postcss.config.js
βββ tsconfig.json
βββ package.json
βββ README.md
- Theme + scroll position are set before paint - an inline script in
app/layout.tsxreadslocalStorage.theme(or defaults to dark), toggles.darkon<html>to avoid a flash, setshistory.scrollRestoration = 'manual', and clears any non-#homehash so a refresh always lands on the Hero. - Hero appears first -
BlurTextletters fade and de-blur in sequence over Fira Code; the circular profile image overlays the name; a tagline below uses the same effect at the word level; on pointer devices the name follows the cursor with a subtle inverse parallax. - Story scroll begins - as you scroll past the hero on desktop/tablet (β₯ 768 px), the About section pins at its bottom and the Projects section flips in over it from
rotation: 30degto0deg(origin: bottom-left). Each subsequent section does the same. On mobile the choreography is disabled and sections simply stack. - Each section's content uses Framer Motion
whileInViewfor its internal cards/timeline items so the reveals stay snappy. - Contact form posts to Formspree; the
InteractiveHoverButtonsubmit reflectsidle β loading β successdirectly from Formspree state.
- Node.js 18 or later
- npm
git clone https://github.com/bcastelino/bcastelino.github.io.git
cd bcastelino.github.io
npm install
npm run devOpen http://localhost:3000 - hot reload is enabled.
npm run build # outputs static export to ./outDon't run
npm run buildwhilenpm run devis active - both write to.next/and will conflict. Stop the dev server first, or use a separate clone.
| Command | Purpose |
|---|---|
npm run dev |
Start dev server with HMR on http://localhost:3000 |
npm run build |
Static export β ./out for GitHub Pages |
npm run start |
Serve the production build locally |
npm run lint |
Run next lint |
All content lives in app/lib/data.ts - there is no CMS. Edit the arrays and objects and the site updates automatically.
| What you want to change | Where to edit |
|---|---|
| Name, tagline, socials, email | personal object in app/lib/data.ts |
| About copy + interests | aboutIntro string + interests array |
| Skills grouped by category | skillGroups array |
| Projects | projects array - set repo: "owner/name" for auto OG |
| Experience timeline | experience array |
| Education cards | education array |
| Certifications | certifications array - badge PNG + pdf path |
| Nav menu labels / order | navItems array |
| Theme colors | :root and .dark CSS variables in app/globals.css |
| Hero size / font | NAME_SIZE_CLASSES (clamp()) in app/components/Hero.tsx |
| Hero parallax intensity | max constant in the mousemove handler in Hero.tsx |
| Education summary + coursework | summary (paragraph) + description (pills) on each EducationItem |
| CTA button look + states | app/components/InteractiveHoverButton.tsx |
| Section animations | Framer Motion props inside each section component |
| Scroll choreography | Tweens / triggers in app/components/FlowArt.tsx |
| Folder | Drop in |
|---|---|
public/personal/ |
Profile photo (profile.jpg) |
public/badges/ |
Square certification badge PNGs |
public/certificates/ |
Issued certificate PDFs |
public/logos/ |
Company / school logos |
public/projects/ |
Manual project screenshots (used when repo is unset, or as the fallback if the OG fetch fails) |
The form posts to Formspree. Update the form ID in app/components/sections/Contact.tsx:
const [state, handleSubmit] = useForm("YOUR_FORMSPREE_ID");Set personal.resumePath in app/lib/data.ts - can be a local PDF under public/ or any external URL (the current site uses a Google Drive link).
FlowArt(app/components/FlowArt.tsx) - finds every[data-flow-section]descendant and, for each, pins it atbottom bottom β bottom topand tweens the next one's inner fromrotation: 30degto0degbetweentop bottomandtop 25%. The hero is also a FlowSection so the first content section flips in over it.prefers-reduced-motionor a viewport β€ 767 px disables the GSAP setup entirely.BlurText(app/components/BlurText.tsx) - IntersectionObserver-driven; once the element enters the viewport each letter/word transitions fromblur(10px) translateY(-20px) opacity:0toblur(0) translateY(0) opacity:1with a configurable per-segment delay.- Hero parallax (
app/components/Hero.tsx) - amousemovehandler (rAF-throttled) normalises the cursor position to Β±1 around the viewport center and appliestranslate3d(-nx*max%, -ny*max%, 0)to the name wrapper. Negated, so the text drifts opposite the cursor; capped at Β±4% so it stays subtle. Skipped on coarse pointers and whenprefers-reduced-motionis set. InteractiveHoverButton(app/components/InteractiveHoverButton.tsx) - supports bothas="button"(with optional externalstatusoverride) andas="a". The resting dot usesscale-[120]on hover so the accent fill comfortably covers full-width buttons; the action label slides in from the left withtranslate-x+opacity. Noclsx/tailwind-mergedependency.- Always-home navigation - the boot script in
<head>setshistory.scrollRestoration = 'manual'and strips non-#homehashes on load. TheBrsignature and theHOMEmenu item bothpreventDefaultand callwindow.scrollTo({ top: 0, behavior: 'smooth' }), which works even when already at#home(where browsers normally no-op). - Project previews -
githubSocialImage("owner/repo")returnshttps://opengraph.githubassets.com/1/owner/repo. The image lives inside a small client component (ProjectPreview) so itsonErrorhandler can swap to/projects/project-placeholder.jpgif the OG endpoint 4xx's (e.g. private repos). - Theme toggle - a small inline script in
<head>applies the saved theme before paint, then theHeaderbutton flips the.darkclass on<html>and persists tolocalStorage.
next.config.js-output: 'export',trailingSlash: true,images.unoptimized: true(required for static hosting on GitHub Pages).tailwind.config.js-darkMode: 'class',accentcolor, Inter / Fira Code font variable mappings.postcss.config.js- Tailwind + autoprefixer.tsconfig.json- Next.js defaults,strict: falseto allow incremental adoption..github/workflows/main.yml- GitHub Actions Pages deployment withpaths-ignorefor docs/tooling.public/.nojekyll- disables Jekyll on GitHub Pages so files starting with_are served.
- Status - see the Actions tab
- Live - https://bcastelino.github.io
- Build badge -
Open source under the MIT License.
|
AI Data Engineer Β· Data Analytics Β· GenAI I turn vague ideas into clean, working systems - because someone's got to π€ GitHub Β· LinkedIn Β· Twitter / X Β· Email |
Built with β€οΈ using Next.js, TypeScript, Tailwind CSS, Framer Motion, and GSAP.
