Personal online resume — alejandroiglesias.github.io/cv
| Layer | Tool |
|---|---|
| Framework | React 19 + TypeScript (strict) |
| Build | Vite |
| Styles | Tailwind CSS v4 + semantic CSS tokens |
| Components | shadcn/ui (Radix primitives, copy-in owned) |
| Motion | Framer Motion (entrance fades, reduced-motion aware) |
| Fonts | Inter + Fraunces (self-hosted via @fontsource) |
| Icons | Inline SVGs (zero runtime) |
| Tests | Vitest + React Testing Library |
| Deploy | GitHub Actions → GitHub Pages |
npm install
npm run dev # http://localhost:5173/cv/| Command | Description |
|---|---|
npm run dev |
Dev server with HMR at /cv/ |
npm run build |
Type-check + Vite build + automatic PDF generation → dist/ |
npm run build:web |
Type-check + Vite production build without regenerating PDFs |
npm run pdf:generate |
Regenerate both PDFs from an existing production build |
npm run preview |
Preview production build locally |
npm test |
Run Vitest test suite |
npm run lint |
ESLint |
src/
├── data/resume.ts # Main CV and shared factual work history
├── data/resumes.ts # URL-to-variant registry
├── data/technical-project-ai-systems-resume.ts
├── types/resume.ts # TypeScript types for resume data
├── components/
│ ├── ui/ # shadcn-style primitives (Button, Badge, Collapsible)
│ ├── Hero.tsx
│ ├── About.tsx
│ ├── Skills.tsx
│ ├── Experience.tsx
│ ├── Role.tsx
│ ├── OlderRoles.tsx # collapsible "Show more" for historical roles
│ ├── ContactList.tsx
│ ├── ObfuscatedEmail.tsx # anti-scraper email reveal
│ ├── ThemeToggle.tsx
│ └── Footer.tsx
├── hooks/
│ ├── useTheme.ts # light/dark/system, persisted to localStorage
│ └── useReducedMotion.ts
├── lib/
│ ├── analytics.ts # GA4, lazy-loaded after idle, DNT-aware
│ └/utils.ts # cn() helper (clsx + tailwind-merge)
└── __tests__/
├── smoke.test.tsx
├── Experience.test.tsx
└── ObfuscatedEmail.test.tsx
Shared factual content is centralized in src/data/resume.ts. Role-specific variants reuse those facts and apply their own positioning, ordering, and supported wording.
- Add a new role: append to the
rolesarray. Setfeatured: trueto show it by default,falseto put it behind "Show more". - Update skills: edit the
skillsarray. - Update contacts: edit the
contactsarray. - Update the Technical Project / AI Systems variant: edit
src/data/technical-project-ai-systems-resume.ts. - Add a new web variant: register its data in
src/data/resumes.tsand add a static HTML entry to Vite so direct GitHub Pages URLs resolve correctly.
Semantic CSS tokens in src/index.css. Both light and dark sets are defined — the active class is toggled on <html> by useTheme. No Tailwind config file needed (Tailwind v4 reads from @theme inline in the CSS).
Pushing to main triggers the GitHub Actions workflow at .github/workflows/deploy.yml, which builds the site and deploys dist/ to GitHub Pages via the Actions + Pages integration.
One-time GitHub setup (already done): set Pages source to "GitHub Actions" in the repo Settings → Pages.
Both resume pages use index, follow, self-referencing canonical URLs, and are listed in public/sitemap.xml. Because this project is hosted below /cv/, an effective robots.txt would need to be published by the root alejandroiglesias.github.io site rather than this repository.
Both PDFs are generated automatically during npm run build and committed under public/:
- Main CV:
alejandro-garcia-iglesias-cv.pdf - Technical Project / AI Systems:
alejandro-garcia-iglesias-technical-project-ai-systems-cv.pdf
The generator builds the site, serves dist/ locally, and uses Chrome's native headless print-to-PDF support. It detects Google Chrome on macOS and common Chrome/Chromium locations on Linux; set CHROME_PATH when the executable lives elsewhere. Generated files are written to both public/ and dist/ so the committed assets and deployment artifact stay in sync.