|
1 | 1 | # diegosaid |
2 | 2 |
|
3 | | -Personal portfolio site — editorial layout, scroll-driven animation, deployed on Cloudflare Pages. |
| 3 | +Personal portfolio site — editorial layout, scroll-driven animation, shipped on Cloudflare Pages. |
4 | 4 |
|
5 | 5 | **Live:** https://diegosaid.com |
6 | 6 |
|
| 7 | +## What this repo demonstrates |
| 8 | + |
| 9 | +A production single-page app written from scratch with an emphasis on three things: motion that respects user preferences, a payload small enough to load fast on mobile, and an editorial visual language that holds up under scrutiny. |
| 10 | + |
| 11 | +- **Scroll choreography** — GSAP timelines synced to a Lenis smooth-scroll context, with `prefers-reduced-motion` honored at every entry point ([`src/lib/lenis-context.tsx`](src/lib/lenis-context.tsx), [`src/hooks/use-reduced-motion.ts`](src/hooks/use-reduced-motion.ts)). |
| 12 | +- **Performance budget** — routes are code-split with `React.lazy`; vendor chunks for React, GSAP and motion libs are pinned in [`vite.config.ts`](vite.config.ts) to keep the initial bundle predictable. Hero and project clips ship as compressed WebM with MP4 fallback and a poster frame. |
| 13 | +- **Lazy media** — `useLazyVideo` defers `<video>` decode until the element enters the viewport ([`src/hooks/use-lazy-video.ts`](src/hooks/use-lazy-video.ts)). |
| 14 | +- **Resilience** — top-level `ErrorBoundary` catches render errors per route so a broken section never blanks the page. |
| 15 | +- **A11y baseline** — visible focus rings, skip link, semantic landmarks, reduced-motion fallbacks, alt text on every illustrative asset. |
| 16 | +- **SEO / PWA** — sitemap, robots, OpenGraph image, web manifest, Apple touch icon, and `_headers` with CSP, HSTS, and referrer policy live in [`public/`](public/). |
| 17 | + |
7 | 18 | ## Stack |
8 | 19 |
|
9 | | -- React 19 + TypeScript |
10 | | -- Vite 7 (dev/build) — requires Node 20.19+ |
11 | | -- Tailwind CSS 3 + shadcn/ui (Radix primitives) |
12 | | -- GSAP + Lenis for scroll choreography; Framer Motion for component-level motion |
13 | | -- React Router 7 |
14 | | -- Cloudflare Pages (Wrangler) |
| 20 | +| Concern | Choice | |
| 21 | +| -------------- | -------------------------------------------------------------- | |
| 22 | +| Framework | React 19 + TypeScript 5.9 | |
| 23 | +| Build | Vite 7 (Node 20.19+) | |
| 24 | +| Styling | Tailwind CSS 3, shadcn/ui on top of Radix primitives | |
| 25 | +| Motion | GSAP 3 + Lenis for scroll; Framer Motion for component motion | |
| 26 | +| Routing | React Router 7 with lazy route boundaries | |
| 27 | +| Forms | React Hook Form + Zod | |
| 28 | +| Hosting | Cloudflare Pages via Wrangler | |
15 | 29 |
|
16 | 30 | ## Getting started |
17 | 31 |
|
18 | 32 | ```bash |
19 | 33 | npm install |
20 | | -npm run dev # http://localhost:5173 |
| 34 | +npm run dev # http://localhost:3000 |
21 | 35 | ``` |
22 | 36 |
|
23 | 37 | ## Scripts |
24 | 38 |
|
25 | | -| Script | Purpose | |
26 | | -| ----------------------- | ------------------------------------------------------------ | |
27 | | -| `npm run dev` | Vite dev server with HMR. | |
28 | | -| `npm run build` | Type-check (`tsc -b`) then build to `dist/`. | |
29 | | -| `npm run lint` | ESLint over the codebase. | |
30 | | -| `npm run preview` | Serve the production build locally. | |
31 | | -| `npm run deploy` | Build and upload `dist/` to Cloudflare Pages via Wrangler. | |
32 | | -| `npm run deploy:prod` | `git push origin main`, then build and deploy. | |
| 39 | +| Script | Purpose | |
| 40 | +| --------------------- | ---------------------------------------------------------- | |
| 41 | +| `npm run dev` | Vite dev server with HMR. | |
| 42 | +| `npm run build` | Type-check (`tsc -b`) and build to `dist/`. | |
| 43 | +| `npm run lint` | ESLint over the codebase. | |
| 44 | +| `npm run preview` | Serve the production build locally. | |
| 45 | +| `npm run deploy` | Build and upload `dist/` to Cloudflare Pages via Wrangler. | |
| 46 | +| `npm run deploy:prod` | Push `main`, then build and deploy. | |
33 | 47 |
|
34 | 48 | ## Routes |
35 | 49 |
|
36 | | -- `/` — Home (hero, manifesto, selected projects, profile, timeline, skills, awards, contact, social, blog preview) |
37 | | -- `/projects` — Project index and case studies |
38 | | -- `/blog` — Writing index |
39 | | -- `/blog/:slug` — Individual posts |
| 50 | +- `/` — Home: hero, manifesto, selected projects, profile, timeline, skills, awards, contact, social, blog preview. |
| 51 | +- `/projects` — Project index and case studies. |
| 52 | +- `/blog` — Writing index. |
| 53 | +- `/blog/:slug` — Individual posts. |
40 | 54 |
|
41 | 55 | ## Project layout |
42 | 56 |
|
43 | 57 | ``` |
44 | 58 | src/ |
45 | | - components/ Layout chrome (Navbar, Footer, ErrorBoundary) + reusable atoms |
| 59 | + components/ Layout chrome (Navbar, Footer, ErrorBoundary, SkipLink) + reusable atoms |
46 | 60 | ui/ shadcn/ui primitives |
47 | 61 | hooks/ matchMedia, lazy video, reduced-motion |
48 | 62 | lib/ Utilities and the Lenis scroll context |
49 | | - pages/ Route entry points |
| 63 | + pages/ Route entry points (lazy-loaded) |
50 | 64 | sections/ Home sections in render order (Section01..Section11) |
51 | 65 | index.css Global styles, focus rings, texture utilities |
52 | 66 | main.tsx Application entry |
53 | | -public/ Static assets (videos, SVGs, _redirects) |
| 67 | +public/ Static assets, _headers, _redirects, sitemap, manifest |
54 | 68 | ``` |
55 | 69 |
|
| 70 | +Sections are intentionally numbered to make the home-page render order grep-able and reorderable without hunting through imports. |
| 71 | + |
56 | 72 | ## Deployment |
57 | 73 |
|
58 | 74 | ```bash |
59 | | -npm run deploy:prod # push to GitHub, then build and deploy |
| 75 | +npm run deploy:prod # push to GitHub, build, deploy |
60 | 76 | npm run deploy # build and deploy without pushing |
61 | 77 | ``` |
62 | 78 |
|
63 | | -Wrangler reads the Cloudflare Pages project name from the script (`--project-name=diegosaid`); auth is handled by `wrangler login` on first use. |
| 79 | +Wrangler reads the Cloudflare Pages project name from the script flag (`--project-name=diegosaid`). Auth is handled once with `wrangler login`. |
| 80 | + |
| 81 | +Security headers (CSP, HSTS, X-Content-Type-Options, Referrer-Policy, Permissions-Policy) and SPA rewrites are served from [`public/_headers`](public/_headers) and [`public/_redirects`](public/_redirects). |
64 | 82 |
|
65 | 83 | ## License |
66 | 84 |
|
67 | | -All rights reserved. Code is published for portfolio review; reuse of brand |
68 | | -assets, copy, and videos requires permission. |
| 85 | +All rights reserved. Code is published for portfolio review; reuse of brand assets, copy, and videos requires permission. |
0 commit comments