Live URL: [your-deploy-url.vercel.app]
GitHub: [your-repo-url]
Built with SvelteKit for the Frontend Wizards Stage 5b challenge.
# Clone the repo
git clone <your-repo-url>
cd portfolio
# Install dependencies
npm install
# Run dev server
npm run dev
# Build for production
npm run build
# Preview production build
npm run preview
Requires Node.js 18+. Deploys to Vercel with zero config.
src/
├── app.css # Global CSS, design tokens, light/dark themes
├── routes/
│ ├── +layout.svelte # Root layout — Nav, CommandPalette, theme init
│ ├── +page.svelte # Home page
│ ├── projects/+page.svelte # All projects with category filter tabs
│ ├── about/+page.svelte # Bio, experience timeline, skills
│ └── contact/+page.svelte # Dedicated contact page
└── lib/
├── components/
│ ├── Nav.svelte # Fixed nav with mobile menu
│ ├── CommandPalette.svelte # Command palette — the creative feature
│ ├── Hero.svelte # Hero with typewriter animation
│ ├── ProjectCard.svelte # Expandable project card
│ ├── Skills.svelte # Skills grid with scroll reveal
│ ├── Contact.svelte # Contact form with validation
│ └── Footer.svelte
├── data/portfolio.js # All content in one place
└── stores/index.js # Theme store + commandOpen state
All personalisation lives in src/lib/data/portfolio.js. To update your name, projects, or skills, edit that file only.
Triggered from anywhere on the site with Ctrl+K or Cmd+K.
Capabilities:
Why this: Command palettes are a hallmark of serious developer tooling — VS Code, Linear, Vercel, Raycast all use them. Building one from scratch in a single Svelte component demonstrates keyboard accessibility, focus management, reactive filtering, and interaction design simultaneously.
Implementation notes:
$state, $derived, $effectsvelte:window onkeydowntick() used to focus the input after the DOM updates| Animation | Technique | Reason |
|---|---|---|
| Hero entrance | CSS transitions + staggered animation-delay |
No JS overhead |
| Typewriter tagline | setInterval in onMount |
Simple, performant |
| Scroll reveals | IntersectionObserver per component |
No scroll listeners |
| Project card hover | CSS transform + box-shadow |
GPU composited, 60fps |
| Card accent underline | CSS width transition on ::after |
Pure CSS |
| Command palette open | CSS @keyframes slideDown |
Declarative |
| Hero background orbs | CSS @keyframes float with filter: blur |
GPU offloaded |
All animations respect prefers-reduced-motion via a global media query.
transform and opacity are GPU compositedheader, nav, main, section, article, footer)aria-label; decorative SVGs have aria-hidden="true"aria-current="page" on active nav linksrole="dialog", aria-modal, role="listbox", role="option", aria-selectedfor/id; errors linked via aria-describedby; role="alert" on error messages:focus-visible outlines on all interactive elementsprefers-reduced-motion media query disables all animations globallyNo GSAP: Native CSS transitions and IntersectionObserver cover the animation needs without adding ~30kb to the bundle. GSAP would be worth adding for scroll-scrubbed storytelling sequences.
Contact form is frontend-only: Validates and shows a success state but doesn't send real emails. To wire it up: add a SvelteKit +page.server.js form action with Resend or Formspree.
Static data (no CMS): All content is imported from portfolio.js. For a real long-term portfolio, a headless CMS with SvelteKit's load functions would make content updates easier without touching code.
npm i -g vercel
vercel
SvelteKit is auto-detected. No configuration needed. Connect your GitHub repo to Vercel for automatic deploys on every push.