diff --git a/app/layout.tsx b/app/layout.tsx index b854079..9f157ef 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -6,11 +6,13 @@ import { ThemeProvider } from "@/components/theme-provider" const inter = Inter({ variable: "--font-inter", subsets: ["latin"], + display: "swap", }); const outfit = Outfit({ variable: "--font-outfit", subsets: ["latin"], + display: "swap", }); export const metadata: Metadata = { diff --git a/components/sections/contact.tsx b/components/sections/contact.tsx index d50743f..05bf11c 100644 --- a/components/sections/contact.tsx +++ b/components/sections/contact.tsx @@ -19,7 +19,7 @@ export async function ContactSection() {

{contact.location}

-

{contact.timezone}

+

{contact.timezone}

diff --git a/components/sections/more-about.tsx b/components/sections/more-about.tsx index 8293bcb..4fe2197 100644 --- a/components/sections/more-about.tsx +++ b/components/sections/more-about.tsx @@ -42,7 +42,7 @@ export async function MoreAboutSection() { {/* Image Column */} -
+
Andrés Sánchez
diff --git a/package-lock.json b/package-lock.json index 0617d0c..960b6f1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,6 @@ "version": "0.1.0", "dependencies": { "@tailwindcss/typography": "^0.5.19", - "framer-motion": "^12.38.0", "lucide-react": "^0.563.0", "next": "16.1.4", "next-themes": "^0.4.6", @@ -3786,33 +3785,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/framer-motion": { - "version": "12.38.0", - "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-12.38.0.tgz", - "integrity": "sha512-rFYkY/pigbcswl1XQSb7q424kSTQ8q6eAC+YUsSKooHQYuLdzdHjrt6uxUC+PRAO++q5IS7+TamgIw1AphxR+g==", - "license": "MIT", - "dependencies": { - "motion-dom": "^12.38.0", - "motion-utils": "^12.36.0", - "tslib": "^2.4.0" - }, - "peerDependencies": { - "@emotion/is-prop-valid": "*", - "react": "^18.0.0 || ^19.0.0", - "react-dom": "^18.0.0 || ^19.0.0" - }, - "peerDependenciesMeta": { - "@emotion/is-prop-valid": { - "optional": true - }, - "react": { - "optional": true - }, - "react-dom": { - "optional": true - } - } - }, "node_modules/function-bind": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", @@ -6114,21 +6086,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/motion-dom": { - "version": "12.38.0", - "resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-12.38.0.tgz", - "integrity": "sha512-pdkHLD8QYRp8VfiNLb8xIBJis1byQ9gPT3Jnh2jqfFtAsWUA3dEepDlsWe/xMpO8McV+VdpKVcp+E+TGJEtOoA==", - "license": "MIT", - "dependencies": { - "motion-utils": "^12.36.0" - } - }, - "node_modules/motion-utils": { - "version": "12.36.0", - "resolved": "https://registry.npmjs.org/motion-utils/-/motion-utils-12.36.0.tgz", - "integrity": "sha512-eHWisygbiwVvf6PZ1vhaHCLamvkSbPIeAYxWUuL3a2PD/TROgE7FvfHWTIH4vMl798QLfMw15nRqIaRDXTlYRg==", - "license": "MIT" - }, "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", diff --git a/package.json b/package.json index 5929831..f11e592 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,6 @@ }, "dependencies": { "@tailwindcss/typography": "^0.5.19", - "framer-motion": "^12.38.0", "lucide-react": "^0.563.0", "next": "16.1.4", "next-themes": "^0.4.6", diff --git a/public/about-me.avif b/public/about-me.avif new file mode 100644 index 0000000..2e12a82 Binary files /dev/null and b/public/about-me.avif differ diff --git a/public/about-me.webp b/public/about-me.webp index 19426ad..437378d 100644 Binary files a/public/about-me.webp and b/public/about-me.webp differ diff --git a/public/profile-cutout.avif b/public/profile-cutout.avif new file mode 100644 index 0000000..6be29b9 Binary files /dev/null and b/public/profile-cutout.avif differ diff --git a/public/profile-cutout.webp b/public/profile-cutout.webp index 4549b31..3124a88 100644 Binary files a/public/profile-cutout.webp and b/public/profile-cutout.webp differ diff --git a/public/swift-logo.avif b/public/swift-logo.avif new file mode 100644 index 0000000..765f0f8 Binary files /dev/null and b/public/swift-logo.avif differ diff --git a/public/tri.avif b/public/tri.avif new file mode 100644 index 0000000..d0931a6 Binary files /dev/null and b/public/tri.avif differ diff --git a/scripts/generate-avif.mjs b/scripts/generate-avif.mjs new file mode 100644 index 0000000..2974448 --- /dev/null +++ b/scripts/generate-avif.mjs @@ -0,0 +1,36 @@ +import sharp from 'sharp'; +import path from 'path'; +import { fileURLToPath } from 'url'; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); +const publicDir = path.join(__dirname, '../public'); + +const images = [ + 'profile-cutout.webp', + 'about-me.webp', + 'swift-logo.webp', + 'tri.webp', +]; + +async function generateAvif() { + for (const img of images) { + const inputPath = path.join(publicDir, img); + const outputPath = path.join(publicDir, img.replace('.webp', '.avif')); + + try { + await sharp(inputPath) + .avif({ quality: 80 }) + .toFile(outputPath); + + const inputSize = (await import('fs')).statSync(inputPath).size; + const outputSize = (await import('fs')).statSync(outputPath).size; + const savings = Math.round((1 - outputSize / inputSize) * 100); + + console.log(`✓ ${img.padEnd(25)} → ${img.replace('.webp', '.avif').padEnd(25)} (${savings}% smaller)`); + } catch (err) { + console.error(`✗ Failed to generate AVIF for ${img}:`, err.message); + } + } +} + +generateAvif(); diff --git a/scripts/resize-images.mjs b/scripts/resize-images.mjs new file mode 100644 index 0000000..dcfd3d2 --- /dev/null +++ b/scripts/resize-images.mjs @@ -0,0 +1,31 @@ +import sharp from 'sharp'; +import path from 'path'; +import { fileURLToPath } from 'url'; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); +const publicDir = path.join(__dirname, '../public'); + +const images = [ + { file: 'profile-cutout.webp', width: 600, height: 695 }, + { file: 'about-me.webp', width: 650, height: 888 }, +]; + +async function resizeImages() { + for (const img of images) { + const inputPath = path.join(publicDir, img.file); + const outputPath = path.join(publicDir, img.file); + + try { + const result = await sharp(inputPath) + .resize(img.width, img.height, { fit: 'cover' }) + .toFile(outputPath + '.tmp'); + + await sharp(outputPath + '.tmp').toFile(outputPath); + console.log(`✓ Resized ${img.file} to ${img.width}x${img.height}`); + } catch (err) { + console.error(`✗ Failed to resize ${img.file}:`, err.message); + } + } +} + +resizeImages(); diff --git a/todo.md b/todo.md index 9445754..384630f 100644 --- a/todo.md +++ b/todo.md @@ -1,5 +1,31 @@ # Todo +## Deploy to Vercel + +Migrate from GitHub Pages to Vercel's free tier for better performance and caching. + +**Benefits:** +- HTTP/2 Server Push (fixes critical request chains, saves ~109ms on LCP) +- Custom cache headers (fixes 10-min GitHub Pages TTL → 1yr for hashed assets) +- Free default domain: `andsang-github-io.vercel.app` +- Optional: add custom domain later ($10-15/year) +- Automatic deployments on git push + +**Steps:** +- [ ] Push current branch (`feat/liquidglass`) to GitHub +- [ ] Connect repo to Vercel (or use `npm i -g vercel && vercel`) +- [ ] Vercel auto-detects Next.js + `output: 'export'` +- [ ] Test site on Vercel domain +- [ ] Update any hardcoded links from `andsang.github.io` to new Vercel domain (if any) +- [ ] Merge to `master` and deploy to production + +**What this unlocks (GitHub Pages cannot fix these):** + +| Issue | Current (GitHub Pages) | With Vercel | Impact | +|-------|-------|---------|--------| +| **Cache lifetimes** | 10 min TTL | 1 year for hashed assets | 365 KiB savings per repeat visit | +| **Critical request chains** | CSS blocks rendering at 348→456ms | HTTP/2 Server Push | ~109ms LCP improvement | + ## Render-blocking CSS Next.js App Router injects CSS via ``. Because of the `data-precedence` attribute (React's internal hydration ordering), `beasties` inlines critical CSS but leaves the blocking `` intact — Lighthouse still flags it. Work was reverted. diff --git a/tsconfig.json b/tsconfig.json index 3a13f90..15c7b97 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,6 +1,6 @@ { "compilerOptions": { - "target": "ES2017", + "target": "ES2020", "lib": ["dom", "dom.iterable", "esnext"], "allowJs": true, "skipLibCheck": true,