diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 986998e6..f14ef506 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -41,17 +41,27 @@ jobs: - name: Install dependencies run: pnpm install --frozen-lockfile + - name: Install Playwright browsers + run: pnpm exec playwright install --with-deps chromium + - name: Build site run: pnpm build + - name: Generate og-image + run: pnpm generate:og && cp public/og-image.png dist/og-image.png + - name: Generate resume DOCX with pandoc run: pandoc dist/resume/index.html -f html -t docx -o dist/Jon_Bogaty_Resume.docx - - name: Verify generated resume files + - name: Generate resume PDF via Playwright + run: pnpm exec tsx scripts/generate-resume-pdf-only.ts + + - name: Verify generated artifacts run: | - ls -la dist/Jon_Bogaty_Resume.pdf dist/Jon_Bogaty_Resume.docx + ls -la dist/Jon_Bogaty_Resume.pdf dist/Jon_Bogaty_Resume.docx dist/og-image.png test -s dist/Jon_Bogaty_Resume.pdf || { echo "PDF is empty"; exit 1; } test -s dist/Jon_Bogaty_Resume.docx || { echo "DOCX is empty"; exit 1; } + test -s dist/og-image.png || { echo "og-image is empty"; exit 1; } - name: Configure Pages uses: actions/configure-pages@983d7736d9b0ae728b81ab479565c72886d7745b # v5.0.0 diff --git a/astro.config.mjs b/astro.config.mjs index b9ca6fea..cb5f5ad8 100644 --- a/astro.config.mjs +++ b/astro.config.mjs @@ -1,14 +1,23 @@ +import { readFileSync } from 'node:fs' import { resolve } from 'node:path' import react from '@astrojs/react' +import sitemap from '@astrojs/sitemap' import tailwindcss from '@tailwindcss/vite' import { defineConfig } from 'astro/config' +const pkg = JSON.parse(readFileSync(new URL('./package.json', import.meta.url), 'utf-8')) +const buildDate = new Date().toISOString().slice(0, 10) + export default defineConfig({ - site: 'https://jbcom.github.io', + site: 'https://www.jonbogaty.com', output: 'static', - integrations: [react()], + integrations: [react(), sitemap()], vite: { plugins: [tailwindcss()], + define: { + __APP_VERSION__: JSON.stringify(pkg.version), + __BUILD_DATE__: JSON.stringify(buildDate), + }, resolve: { alias: { '@': resolve(import.meta.dirname, './src'), diff --git a/package.json b/package.json index 5858a131..819c8b1d 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,7 @@ "build": "astro build", "preview": "astro preview", "generate:resume": "tsx scripts/generate-resume.ts", + "generate:og": "tsx scripts/generate-og-image.ts", "test": "vitest run", "test:watch": "vitest", "test:e2e": "playwright test", @@ -21,6 +22,10 @@ }, "dependencies": { "@astrojs/react": "^4.4.2", + "@astrojs/sitemap": "^3.7.2", + "@fontsource/instrument-serif": "^5.2.8", + "@fontsource/inter": "^5.2.8", + "@fontsource/jetbrains-mono": "^5.2.8", "@paper-design/shaders-react": "^0.0.71", "astro": "^5.18.0", "class-variance-authority": "^0.7.1", @@ -33,6 +38,7 @@ "tailwind-merge": "^3.5.0" }, "devDependencies": { + "@astrojs/check": "^0.9.8", "@biomejs/biome": "^2.4.5", "@playwright/test": "^1.58.2", "@tailwindcss/vite": "^4.2.1", @@ -47,7 +53,7 @@ "typescript": "^5.9.3", "vitest": "^4.0.18" }, - "homepage": "https://jbcom.github.io", + "homepage": "https://www.jonbogaty.com", "repository": { "type": "git", "url": "git+https://github.com/jbcom/jbcom.github.io.git" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d5902459..45b488cc 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -11,6 +11,18 @@ importers: '@astrojs/react': specifier: ^4.4.2 version: 4.4.2(@types/node@25.0.3)(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(jiti@2.6.1)(lightningcss@1.31.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(tsx@4.21.0)(yaml@2.8.2) + '@astrojs/sitemap': + specifier: ^3.7.2 + version: 3.7.2 + '@fontsource/instrument-serif': + specifier: ^5.2.8 + version: 5.2.8 + '@fontsource/inter': + specifier: ^5.2.8 + version: 5.2.8 + '@fontsource/jetbrains-mono': + specifier: ^5.2.8 + version: 5.2.8 '@paper-design/shaders-react': specifier: ^0.0.71 version: 0.0.71(@types/react@19.2.14)(react@19.2.4) @@ -42,6 +54,9 @@ importers: specifier: ^3.5.0 version: 3.5.0 devDependencies: + '@astrojs/check': + specifier: ^0.9.8 + version: 0.9.8(prettier@3.8.3)(typescript@5.9.3) '@biomejs/biome': specifier: ^2.4.5 version: 2.4.5 @@ -88,12 +103,30 @@ packages: resolution: {integrity: sha512-9q/yCljni37pkMr4sPrI3G4jqdIk074+iukc5aFJl7kmDCCsiJrbZ6zKxnES1Gwg+i9RcDZwvktl23puGslmvA==} hasBin: true + '@astrojs/check@0.9.8': + resolution: {integrity: sha512-LDng8446QLS5ToKjRHd3bgUdirvemVVExV7nRyJfW2wV36xuv7vDxwy5NWN9zqeSEDgg0Tv84sP+T3yEq+Zlkw==} + hasBin: true + peerDependencies: + typescript: ^5.0.0 + '@astrojs/compiler@2.13.1': resolution: {integrity: sha512-f3FN83d2G/v32ipNClRKgYv30onQlMZX1vCeZMjPsMMPl1mDpmbl0+N5BYo4S/ofzqJyS5hvwacEo0CCVDn/Qg==} '@astrojs/internal-helpers@0.7.5': resolution: {integrity: sha512-vreGnYSSKhAjFJCWAwe/CNhONvoc5lokxtRoZims+0wa3KbHBdPHSSthJsKxPd8d/aic6lWKpRTYGY/hsgK6EA==} + '@astrojs/language-server@2.16.6': + resolution: {integrity: sha512-N990lu+HSFiG57owR0XBkr02BYMgiLCshLf+4QG4v6jjSWkBeQGnzqi+E1L08xFPPJ7eEeXnxPXGLaVv5pa4Ug==} + hasBin: true + peerDependencies: + prettier: ^3.0.0 + prettier-plugin-astro: '>=0.11.0' + peerDependenciesMeta: + prettier: + optional: true + prettier-plugin-astro: + optional: true + '@astrojs/markdown-remark@6.3.10': resolution: {integrity: sha512-kk4HeYR6AcnzC4QV8iSlOfh+N8TZ3MEStxPyenyCtemqn8IpEATBFMTJcfrNW32dgpt6MY3oCkMM/Tv3/I4G3A==} @@ -110,10 +143,16 @@ packages: react: ^17.0.2 || ^18.0.0 || ^19.0.0 react-dom: ^17.0.2 || ^18.0.0 || ^19.0.0 + '@astrojs/sitemap@3.7.2': + resolution: {integrity: sha512-PqkzkcZTb5ICiyIR8VoKbIAP/laNRXi5tw616N1Ckk+40oNB8Can1AzVV56lrbC5GKSZFCyJYUVYqVivMisvpA==} + '@astrojs/telemetry@3.3.0': resolution: {integrity: sha512-UFBgfeldP06qu6khs/yY+q1cDAaArM2/7AEIqQ9Cuvf7B1hNLq0xDrZkct+QoIGyjq56y8IaE2I3CTvG99mlhQ==} engines: {node: 18.20.8 || ^20.3.0 || >=22.0.0} + '@astrojs/yaml2ts@0.2.3': + resolution: {integrity: sha512-PJzRmgQzUxI2uwpdX2lXSHtP4G8ocp24/t+bZyf5Fy0SZLSF9f9KXZoMlFM/XCGue+B0nH/2IZ7FpBYQATBsCg==} + '@babel/code-frame@7.29.0': resolution: {integrity: sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==} engines: {node: '>=6.9.0'} @@ -322,6 +361,27 @@ packages: peerDependencies: '@noble/ciphers': ^1.0.0 + '@emmetio/abbreviation@2.3.3': + resolution: {integrity: sha512-mgv58UrU3rh4YgbE/TzgLQwJ3pFsHHhCLqY20aJq+9comytTXUDNGG/SMtSeMJdkpxgXSXunBGLD8Boka3JyVA==} + + '@emmetio/css-abbreviation@2.1.8': + resolution: {integrity: sha512-s9yjhJ6saOO/uk1V74eifykk2CBYi01STTK3WlXWGOepyKa23ymJ053+DNQjpFcy1ingpaO7AxCcwLvHFY9tuw==} + + '@emmetio/css-parser@0.4.1': + resolution: {integrity: sha512-2bC6m0MV/voF4CTZiAbG5MWKbq5EBmDPKu9Sb7s7nVcEzNQlrZP6mFFFlIaISM8X6514H9shWMme1fCm8cWAfQ==} + + '@emmetio/html-matcher@1.3.0': + resolution: {integrity: sha512-NTbsvppE5eVyBMuyGfVu2CRrLvo7J4YHb6t9sBFLyY03WYhXET37qA4zOYUjBWFCRHO7pS1B9khERtY0f5JXPQ==} + + '@emmetio/scanner@1.0.4': + resolution: {integrity: sha512-IqRuJtQff7YHHBk4G8YZ45uB9BaAGcwQeVzgj/zj8/UdOhtQpEIupUhSk8dys6spFIWVZVeK20CzGEnqR5SbqA==} + + '@emmetio/stream-reader-utils@0.1.0': + resolution: {integrity: sha512-ZsZ2I9Vzso3Ho/pjZFsmmZ++FWeEd/txqybHTm4OgaZzdS8V9V/YYWQwg5TC38Z7uLWUV1vavpLLbjJtKubR1A==} + + '@emmetio/stream-reader@2.2.0': + resolution: {integrity: sha512-fXVXEyFA5Yv3M3n8sUGT7+fvecGrZP4k6FnWWMSZVQf69kAq0LLpaBQLGcPR30m3zMmKYhECP4k/ZkzvhEW5kw==} + '@emnapi/runtime@1.8.1': resolution: {integrity: sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg==} @@ -652,6 +712,15 @@ packages: '@floating-ui/utils@0.2.10': resolution: {integrity: sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==} + '@fontsource/instrument-serif@5.2.8': + resolution: {integrity: sha512-s+bkz+syj2rO00Rmq9g0P+PwuLig33DR1xDR8pTWmovH1pUjwnncrFk++q9mmOex8fUQ7oW80gPpPDaw7V1MMw==} + + '@fontsource/inter@5.2.8': + resolution: {integrity: sha512-P6r5WnJoKiNVV+zvW2xM13gNdFhAEpQ9dQJHt3naLvfg+LkF2ldgSLiF4T41lf1SQCM9QmkqPTn4TH568IRagg==} + + '@fontsource/jetbrains-mono@5.2.8': + resolution: {integrity: sha512-6w8/SG4kqvIMu7xd7wt6x3idn1Qux3p9N62s6G3rfldOUYHpWcc2FKrqf+Vo44jRvqWj2oAtTHrZXEP23oSKwQ==} + '@hono/node-server@1.19.9': resolution: {integrity: sha512-vHL6w3ecZsky+8P5MD+eFfaGTyCeOHUIFYMGpQGbrBTSmNNoxv0if69rEZ5giu36weC5saFuznL411gRX7bJDw==} engines: {node: '>=18.14.1'} @@ -1885,6 +1954,9 @@ packages: '@types/nlcst@2.0.3': resolution: {integrity: sha512-vSYNSDe6Ix3q+6Z7ri9lyWqgGhJTmzRjZRqyq15N0Z/1/UnVsno9G/N40NBijoYx2seFDIl0+B2mgAb9mezUCA==} + '@types/node@24.12.2': + resolution: {integrity: sha512-A1sre26ke7HDIuY/M23nd9gfB+nrmhtYyMINbjI1zHJxYteKR6qSMX56FsmjMcDb3SMcjJg5BiRRgOCC/yBD0g==} + '@types/node@25.0.3': resolution: {integrity: sha512-W609buLVRVmeW693xKfzHeIV6nJGGz98uCPfeXI1ELMLXVeKYZ9m15fAMSaUPBHYLGFsVRcMmSCksQOrZV9BYA==} @@ -1896,6 +1968,9 @@ packages: '@types/react@19.2.14': resolution: {integrity: sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==} + '@types/sax@1.2.7': + resolution: {integrity: sha512-rO73L89PJxeYM3s3pPPjiPgVVcymqU490g0YO5n5By0k2Erzj6tay/4lr1CHAAU4JyOWd1rpQ8bCf6cZfHU96A==} + '@types/statuses@2.0.6': resolution: {integrity: sha512-xMAgYwceFhRA2zY+XbEA7mxYbA093wdiW8Vu6gZPGWy9cmOyU9XesH1tNcEWsKFd5Vzrqx5T3D38PWx1FIIXkA==} @@ -1943,6 +2018,32 @@ packages: '@vitest/utils@4.0.18': resolution: {integrity: sha512-msMRKLMVLWygpK3u2Hybgi4MNjcYJvwTb0Ru09+fOyCXIgT5raYP041DRRdiJiI3k/2U6SEbAETB3YtBrUkCFA==} + '@volar/kit@2.4.28': + resolution: {integrity: sha512-cKX4vK9dtZvDRaAzeoUdaAJEew6IdxHNCRrdp5Kvcl6zZOqb6jTOfk3kXkIkG3T7oTFXguEMt5+9ptyqYR84Pg==} + peerDependencies: + typescript: '*' + + '@volar/language-core@2.4.28': + resolution: {integrity: sha512-w4qhIJ8ZSitgLAkVay6AbcnC7gP3glYM3fYwKV3srj8m494E3xtrCv6E+bWviiK/8hs6e6t1ij1s2Endql7vzQ==} + + '@volar/language-server@2.4.28': + resolution: {integrity: sha512-NqcLnE5gERKuS4PUFwlhMxf6vqYo7hXtbMFbViXcbVkbZ905AIVWhnSo0ZNBC2V127H1/2zP7RvVOVnyITFfBw==} + + '@volar/language-service@2.4.28': + resolution: {integrity: sha512-Rh/wYCZJrI5vCwMk9xyw/Z+MsWxlJY1rmMZPsxUoJKfzIRjS/NF1NmnuEcrMbEVGja00aVpCsInJfixQTMdvLw==} + + '@volar/source-map@2.4.28': + resolution: {integrity: sha512-yX2BDBqJkRXfKw8my8VarTyjv48QwxdJtvRgUpNE5erCsgEUdI2DsLbpa+rOQVAJYshY99szEcRDmyHbF10ggQ==} + + '@volar/typescript@2.4.28': + resolution: {integrity: sha512-Ja6yvWrbis2QtN4ClAKreeUZPVYMARDYZl9LMEv1iQ1QdepB6wn0jTRxA9MftYmYa4DQ4k/DaSZpFPUfxl8giw==} + + '@vscode/emmet-helper@2.11.0': + resolution: {integrity: sha512-QLxjQR3imPZPQltfbWRnHU6JecWTF1QSWhx3GAKQpslx7y3Dp6sIIXhKjiUJ/BR9FX8PVthjr9PD6pNwOJfAzw==} + + '@vscode/l10n@0.0.18': + resolution: {integrity: sha512-KYSIHVmslkaCDyw013pphY+d7x1qV8IZupYfeIfzNA+nsaWHbn5uPuQRvdRFsa9zFzGeudPuoGoZ1Op4jrJXIQ==} + accepts@2.0.0: resolution: {integrity: sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==} engines: {node: '>= 0.6'} @@ -1956,6 +2057,14 @@ packages: resolution: {integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==} engines: {node: '>= 14'} + ajv-draft-04@1.0.0: + resolution: {integrity: sha512-mv00Te6nmYbRp5DCwclxtt7yV/joXJPGS7nM+97GdxvuttCOfgI3K4U25zboyeX0O+myI8ERluxQe5wljMmVIw==} + peerDependencies: + ajv: ^8.5.0 + peerDependenciesMeta: + ajv: + optional: true + ajv-formats@3.0.1: resolution: {integrity: sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==} peerDependencies: @@ -1994,6 +2103,9 @@ packages: resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} engines: {node: '>= 8'} + arg@5.0.2: + resolution: {integrity: sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==} + argparse@2.0.1: resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} @@ -2111,6 +2223,10 @@ packages: character-entities@2.0.2: resolution: {integrity: sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==} + chokidar@4.0.3: + resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==} + engines: {node: '>= 14.16.0'} + chokidar@5.0.0: resolution: {integrity: sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==} engines: {node: '>= 20.19.0'} @@ -2356,6 +2472,9 @@ packages: electron-to-chromium@1.5.302: resolution: {integrity: sha512-sM6HAN2LyK82IyPBpznDRqlTQAtuSaO+ShzFiWTvoMJLHyZ+Y39r8VMfHzwbU8MVBzQ4Wdn85+wlZl2TLGIlwg==} + emmet@2.4.11: + resolution: {integrity: sha512-23QPJB3moh/U9sT4rQzGgeyyGIrcM+GH5uVYg2C6wZIxAIJq7Ng3QLT79tl8FUwDXhyq9SusfknOrofAKqvgyQ==} + emoji-regex@10.6.0: resolution: {integrity: sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==} @@ -2844,6 +2963,12 @@ packages: engines: {node: '>=6'} hasBin: true + jsonc-parser@2.3.1: + resolution: {integrity: sha512-H8jvkz1O50L3dMZCsLqiuB2tA7muqbSg1AtGEkN0leAqGjsUzDJir3Zwr02BhqdcITPg3ei3mZ+HjMocAknhhg==} + + jsonc-parser@3.3.1: + resolution: {integrity: sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==} + jsonfile@6.2.0: resolution: {integrity: sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==} @@ -3168,6 +3293,9 @@ packages: typescript: optional: true + muggle-string@0.4.1: + resolution: {integrity: sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==} + mute-stream@2.0.0: resolution: {integrity: sha512-WWdIxpyjEn+FhQJQQv9aQAYlHoNVdzIzUySNV1gHUPDSdZJ3yZn7pAAbQcV7B56Mvu881q9FZV+0Vx2xC44VWA==} engines: {node: ^18.17.0 || >=20.5.0} @@ -3371,6 +3499,11 @@ packages: resolution: {integrity: sha512-dM0jVuXJPsDN6DvRpea484tCUaMiXWjuCn++HGTqUWzGDjv5tZkEZldAJ/UMlqRYGFrD/etByo4/xOuC/snX2A==} engines: {node: '>=20'} + prettier@3.8.3: + resolution: {integrity: sha512-7igPTM53cGHMW8xWuVTydi2KO233VFiTNyF5hLJqpilHfmn8C8gPf+PS7dUT64YcXFbiMGZxS9pCSxL/Dxm/Jw==} + engines: {node: '>=14'} + hasBin: true + pretty-ms@9.3.0: resolution: {integrity: sha512-gjVS5hOP+M3wMm5nmNOucbIrqudzs9v/57bWRHQWLYklXqoXKrVfYW2W9+glfGsqtPgpiz5WwyEEB+ksXIx3gQ==} engines: {node: '>=18'} @@ -3464,6 +3597,10 @@ packages: resolution: {integrity: sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==} engines: {node: '>=0.10.0'} + readdirp@4.1.2: + resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==} + engines: {node: '>= 14.18.0'} + readdirp@5.0.0: resolution: {integrity: sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ==} engines: {node: '>= 20.19.0'} @@ -3509,6 +3646,12 @@ packages: remark-stringify@11.0.0: resolution: {integrity: sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw==} + request-light@0.5.8: + resolution: {integrity: sha512-3Zjgh+8b5fhRJBQZoy+zbVKpAQGLyka0MPgW3zruTF4dFFJ8Fqcfu9YsAvi/rvdcaTeWG3MkbZv4WKxAn/84Lg==} + + request-light@0.7.0: + resolution: {integrity: sha512-lMbBMrDoxgsyO+yB3sDcrDuX85yYt7sS8BfQd11jtbW/z5ZWgLZRcEGLsLoYw7I0WSUGQBs8CC8ScIxkTX1+6Q==} + require-directory@2.1.1: resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} engines: {node: '>=0.10.0'} @@ -3641,6 +3784,11 @@ packages: sisteransi@1.0.5: resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==} + sitemap@9.0.1: + resolution: {integrity: sha512-S6hzjGJSG3d6if0YoF5kTyeRJvia6FSTBroE5fQ0bu1QNxyJqhhinfUsXi9fH3MgtXODWvwo2BDyQSnhPQ88uQ==} + engines: {node: '>=20.19.5', npm: '>=10.8.2'} + hasBin: true + smol-toml@1.6.0: resolution: {integrity: sha512-4zemZi0HvTnYwLfrpk/CF9LOd9Lt87kAt50GnqhMpyF9U3poDAP2+iukq2bZsO/ufegbYehBkqINbsWxj4l4cw==} engines: {node: '>= 18'} @@ -3670,6 +3818,9 @@ packages: resolution: {integrity: sha512-UhDfHmA92YAlNnCfhmq0VeNL5bDbiZGg7sZ2IvPsXubGkiNa9EC+tUTsjBRsYUAz87btI6/1wf4XoVvQ3uRnmQ==} engines: {node: '>=18'} + stream-replace-string@2.0.0: + resolution: {integrity: sha512-TlnjJ1C0QrmxRNrON00JvaFFlNh5TTG00APw23j74ET7gkQpTASi6/L2fuiav8pzK715HXtUeClpBTw2NPSn6w==} + strict-event-emitter@0.5.1: resolution: {integrity: sha512-vMgjE/GGEPEFnhFub6pa4FmJBRBVOLpIII2hvCZ8Kzb7K0hlHo7mQv6xYrBvCL2LtAIBwFUK8wvuJgTVSQ5MFQ==} @@ -3813,6 +3964,12 @@ packages: resolution: {integrity: sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==} engines: {node: '>= 0.6'} + typesafe-path@0.2.2: + resolution: {integrity: sha512-OJabfkAg1WLZSqJAJ0Z6Sdt3utnbzr/jh+NAHoyWHJe8CMSy79Gm085094M9nvTPy22KzTVn5Zq5mbapCI/hPA==} + + typescript-auto-import-cache@0.3.6: + resolution: {integrity: sha512-RpuHXrknHdVdK7wv/8ug3Fr0WNsNi5l5aB8MYYuXhq2UH5lnEB1htJ1smhtD5VeCsGr2p8mUDtd83LCQDFVgjQ==} + typescript@5.9.3: resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} engines: {node: '>=14.17'} @@ -4113,6 +4270,98 @@ packages: jsdom: optional: true + volar-service-css@0.0.70: + resolution: {integrity: sha512-K1qyOvBpE3rzdAv3e4/6Rv5yizrYPy5R/ne3IWCAzLBuMO4qBMV3kSqWzj6KUVe6S0AnN6wxF7cRkiaKfYMYJw==} + peerDependencies: + '@volar/language-service': ~2.4.0 + peerDependenciesMeta: + '@volar/language-service': + optional: true + + volar-service-emmet@0.0.70: + resolution: {integrity: sha512-xi5bC4m/VyE3zy/n2CXspKeDZs3qA41tHLTw275/7dNWM/RqE2z3BnDICQybHIVp/6G1iOQj5c1qXMgQC08TNg==} + peerDependencies: + '@volar/language-service': ~2.4.0 + peerDependenciesMeta: + '@volar/language-service': + optional: true + + volar-service-html@0.0.70: + resolution: {integrity: sha512-eR6vCgMdmYAo4n+gcT7DSyBQbwB8S3HZZvSagTf0sxNaD4WppMCFfpqWnkrlGStPKMZvMiejRRVmqsX9dYcTvQ==} + peerDependencies: + '@volar/language-service': ~2.4.0 + peerDependenciesMeta: + '@volar/language-service': + optional: true + + volar-service-prettier@0.0.70: + resolution: {integrity: sha512-Z6BCFSpGVCd8BPAsZ785Kce1BGlWd5ODqmqZGVuB14MJvrR4+CYz6cDy4F+igmE1gMifqfvMhdgT8Aud4M5ngg==} + peerDependencies: + '@volar/language-service': ~2.4.0 + prettier: ^2.2 || ^3.0 + peerDependenciesMeta: + '@volar/language-service': + optional: true + prettier: + optional: true + + volar-service-typescript-twoslash-queries@0.0.70: + resolution: {integrity: sha512-IdD13Z9N2Bu8EM6CM0fDV1E69olEYGHDU25X51YXmq8Y0CmJ2LNj6gOiBJgpS5JGUqFzECVhMNBW7R0sPdRTMQ==} + peerDependencies: + '@volar/language-service': ~2.4.0 + peerDependenciesMeta: + '@volar/language-service': + optional: true + + volar-service-typescript@0.0.70: + resolution: {integrity: sha512-l46Bx4cokkUedTd74ojO5H/zqHZJ8SUuyZ0IB8JN4jfRqUM3bQFBHoOwlZCyZmOeO0A3RQNkMnFclxO4c++gsg==} + peerDependencies: + '@volar/language-service': ~2.4.0 + peerDependenciesMeta: + '@volar/language-service': + optional: true + + volar-service-yaml@0.0.70: + resolution: {integrity: sha512-0c8bXDBeoATF9F6iPIlOuYTuZAC4c+yi0siQo920u7eiBJk8oQmUmg9cDUbR4+Gl++bvGP4plj3fErbJuPqdcQ==} + peerDependencies: + '@volar/language-service': ~2.4.0 + peerDependenciesMeta: + '@volar/language-service': + optional: true + + vscode-css-languageservice@6.3.10: + resolution: {integrity: sha512-eq5N9Er3fC4vA9zd9EFhyBG90wtCCuXgRSpAndaOgXMh1Wgep5lBgRIeDgjZBW9pa+332yC9+49cZMW8jcL3MA==} + + vscode-html-languageservice@5.6.2: + resolution: {integrity: sha512-ulCrSnFnfQ16YzvwnYUgEbUEl/ZG7u2eV27YhvLObSHKkb8fw1Z9cgsnUwjTEeDIdJDoTDTDpxuhQwoenoLNMg==} + + vscode-json-languageservice@4.1.8: + resolution: {integrity: sha512-0vSpg6Xd9hfV+eZAaYN63xVVMOTmJ4GgHxXnkLCh+9RsQBkWKIghzLhW2B9ebfG+LQQg8uLtsQ2aUKjTgE+QOg==} + engines: {npm: '>=7.0.0'} + + vscode-jsonrpc@8.2.0: + resolution: {integrity: sha512-C+r0eKJUIfiDIfwJhria30+TYWPtuHJXHtI7J0YlOmKAo7ogxP20T0zxB7HZQIFhIyvoBPwWskjxrvAtfjyZfA==} + engines: {node: '>=14.0.0'} + + vscode-languageserver-protocol@3.17.5: + resolution: {integrity: sha512-mb1bvRJN8SVznADSGWM9u/b07H7Ecg0I3OgXDuLdn307rl/J3A9YD6/eYOssqhecL27hK1IPZAsaqh00i/Jljg==} + + vscode-languageserver-textdocument@1.0.12: + resolution: {integrity: sha512-cxWNPesCnQCcMPeenjKKsOCKQZ/L6Tv19DTRIGuLWe32lyzWhihGVJ/rcckZXJxfdKCFvRLS3fpBIsV/ZGX4zA==} + + vscode-languageserver-types@3.17.5: + resolution: {integrity: sha512-Ld1VelNuX9pdF39h2Hgaeb5hEZM2Z3jUrrMgWQAu82jMtZp7p3vJT3BzToKtZI7NgQssZje5o0zryOrhQvzQAg==} + + vscode-languageserver@9.0.1: + resolution: {integrity: sha512-woByF3PDpkHFUreUa7Hos7+pUWdeWMXRd26+ZX2A8cFx6v/JPTtd4/uN0/jB6XQHYaOlHbio03NTHCqrgG5n7g==} + hasBin: true + + vscode-nls@5.2.0: + resolution: {integrity: sha512-RAaHx7B14ZU04EU31pT+rKz2/zSl7xMsfIZuo8pd+KZO6PXtQmpevpq3vxvWNcrGbdmhM/rr5Uw5Mz+NBfhVng==} + + vscode-uri@3.1.0: + resolution: {integrity: sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ==} + web-namespaces@2.0.1: resolution: {integrity: sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ==} @@ -4172,6 +4421,15 @@ packages: yallist@3.1.1: resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} + yaml-language-server@1.20.0: + resolution: {integrity: sha512-qhjK/bzSRZ6HtTvgeFvjNPJGWdZ0+x5NREV/9XZWFjIGezew2b4r5JPy66IfOhd5OA7KeFwk1JfmEbnTvev0cA==} + hasBin: true + + yaml@2.7.1: + resolution: {integrity: sha512-10ULxpnOCQXxJvBgxsn9ptjq6uviG/htZKk9veJGhlqn3w/DxQ631zFF+nlQXLwmImeS5amR2dl2U8sg6U9jsQ==} + engines: {node: '>= 14'} + hasBin: true + yaml@2.8.2: resolution: {integrity: sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A==} engines: {node: '>= 14.6'} @@ -4215,6 +4473,9 @@ packages: zod@3.25.76: resolution: {integrity: sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==} + zod@4.3.6: + resolution: {integrity: sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==} + zwitch@2.0.4: resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==} @@ -4227,10 +4488,46 @@ snapshots: package-manager-detector: 1.6.0 tinyexec: 1.0.2 + '@astrojs/check@0.9.8(prettier@3.8.3)(typescript@5.9.3)': + dependencies: + '@astrojs/language-server': 2.16.6(prettier@3.8.3)(typescript@5.9.3) + chokidar: 4.0.3 + kleur: 4.1.5 + typescript: 5.9.3 + yargs: 17.7.2 + transitivePeerDependencies: + - prettier + - prettier-plugin-astro + '@astrojs/compiler@2.13.1': {} '@astrojs/internal-helpers@0.7.5': {} + '@astrojs/language-server@2.16.6(prettier@3.8.3)(typescript@5.9.3)': + dependencies: + '@astrojs/compiler': 2.13.1 + '@astrojs/yaml2ts': 0.2.3 + '@jridgewell/sourcemap-codec': 1.5.5 + '@volar/kit': 2.4.28(typescript@5.9.3) + '@volar/language-core': 2.4.28 + '@volar/language-server': 2.4.28 + '@volar/language-service': 2.4.28 + muggle-string: 0.4.1 + tinyglobby: 0.2.15 + volar-service-css: 0.0.70(@volar/language-service@2.4.28) + volar-service-emmet: 0.0.70(@volar/language-service@2.4.28) + volar-service-html: 0.0.70(@volar/language-service@2.4.28) + volar-service-prettier: 0.0.70(@volar/language-service@2.4.28)(prettier@3.8.3) + volar-service-typescript: 0.0.70(@volar/language-service@2.4.28) + volar-service-typescript-twoslash-queries: 0.0.70(@volar/language-service@2.4.28) + volar-service-yaml: 0.0.70(@volar/language-service@2.4.28) + vscode-html-languageservice: 5.6.2 + vscode-uri: 3.1.0 + optionalDependencies: + prettier: 3.8.3 + transitivePeerDependencies: + - typescript + '@astrojs/markdown-remark@6.3.10': dependencies: '@astrojs/internal-helpers': 0.7.5 @@ -4284,6 +4581,12 @@ snapshots: - tsx - yaml + '@astrojs/sitemap@3.7.2': + dependencies: + sitemap: 9.0.1 + stream-replace-string: 2.0.0 + zod: 4.3.6 + '@astrojs/telemetry@3.3.0': dependencies: ci-info: 4.4.0 @@ -4296,6 +4599,10 @@ snapshots: transitivePeerDependencies: - supports-color + '@astrojs/yaml2ts@0.2.3': + dependencies: + yaml: 2.8.2 + '@babel/code-frame@7.29.0': dependencies: '@babel/helper-validator-identifier': 7.28.5 @@ -4547,6 +4854,29 @@ snapshots: dependencies: '@noble/ciphers': 1.3.0 + '@emmetio/abbreviation@2.3.3': + dependencies: + '@emmetio/scanner': 1.0.4 + + '@emmetio/css-abbreviation@2.1.8': + dependencies: + '@emmetio/scanner': 1.0.4 + + '@emmetio/css-parser@0.4.1': + dependencies: + '@emmetio/stream-reader': 2.2.0 + '@emmetio/stream-reader-utils': 0.1.0 + + '@emmetio/html-matcher@1.3.0': + dependencies: + '@emmetio/scanner': 1.0.4 + + '@emmetio/scanner@1.0.4': {} + + '@emmetio/stream-reader-utils@0.1.0': {} + + '@emmetio/stream-reader@2.2.0': {} + '@emnapi/runtime@1.8.1': dependencies: tslib: 2.8.1 @@ -4725,6 +5055,12 @@ snapshots: '@floating-ui/utils@0.2.10': {} + '@fontsource/instrument-serif@5.2.8': {} + + '@fontsource/inter@5.2.8': {} + + '@fontsource/jetbrains-mono@5.2.8': {} + '@hono/node-server@1.19.9(hono@4.12.2)': dependencies: hono: 4.12.2 @@ -5932,6 +6268,10 @@ snapshots: dependencies: '@types/unist': 3.0.3 + '@types/node@24.12.2': + dependencies: + undici-types: 7.16.0 + '@types/node@25.0.3': dependencies: undici-types: 7.16.0 @@ -5944,6 +6284,10 @@ snapshots: dependencies: csstype: 3.2.3 + '@types/sax@1.2.7': + dependencies: + '@types/node': 25.0.3 + '@types/statuses@2.0.6': {} '@types/unist@3.0.3': {} @@ -6004,6 +6348,56 @@ snapshots: '@vitest/pretty-format': 4.0.18 tinyrainbow: 3.0.3 + '@volar/kit@2.4.28(typescript@5.9.3)': + dependencies: + '@volar/language-service': 2.4.28 + '@volar/typescript': 2.4.28 + typesafe-path: 0.2.2 + typescript: 5.9.3 + vscode-languageserver-textdocument: 1.0.12 + vscode-uri: 3.1.0 + + '@volar/language-core@2.4.28': + dependencies: + '@volar/source-map': 2.4.28 + + '@volar/language-server@2.4.28': + dependencies: + '@volar/language-core': 2.4.28 + '@volar/language-service': 2.4.28 + '@volar/typescript': 2.4.28 + path-browserify: 1.0.1 + request-light: 0.7.0 + vscode-languageserver: 9.0.1 + vscode-languageserver-protocol: 3.17.5 + vscode-languageserver-textdocument: 1.0.12 + vscode-uri: 3.1.0 + + '@volar/language-service@2.4.28': + dependencies: + '@volar/language-core': 2.4.28 + vscode-languageserver-protocol: 3.17.5 + vscode-languageserver-textdocument: 1.0.12 + vscode-uri: 3.1.0 + + '@volar/source-map@2.4.28': {} + + '@volar/typescript@2.4.28': + dependencies: + '@volar/language-core': 2.4.28 + path-browserify: 1.0.1 + vscode-uri: 3.1.0 + + '@vscode/emmet-helper@2.11.0': + dependencies: + emmet: 2.4.11 + jsonc-parser: 2.3.1 + vscode-languageserver-textdocument: 1.0.12 + vscode-languageserver-types: 3.17.5 + vscode-uri: 3.1.0 + + '@vscode/l10n@0.0.18': {} + accepts@2.0.0: dependencies: mime-types: 3.0.2 @@ -6013,6 +6407,10 @@ snapshots: agent-base@7.1.4: {} + ajv-draft-04@1.0.0(ajv@8.18.0): + optionalDependencies: + ajv: 8.18.0 + ajv-formats@3.0.1(ajv@8.18.0): optionalDependencies: ajv: 8.18.0 @@ -6045,6 +6443,8 @@ snapshots: normalize-path: 3.0.0 picomatch: 2.3.1 + arg@5.0.2: {} + argparse@2.0.1: {} aria-hidden@1.2.6: @@ -6250,6 +6650,10 @@ snapshots: character-entities@2.0.2: {} + chokidar@4.0.3: + dependencies: + readdirp: 4.1.2 + chokidar@5.0.0: dependencies: readdirp: 5.0.0 @@ -6448,6 +6852,11 @@ snapshots: electron-to-chromium@1.5.302: {} + emmet@2.4.11: + dependencies: + '@emmetio/abbreviation': 2.3.3 + '@emmetio/css-abbreviation': 2.1.8 + emoji-regex@10.6.0: {} emoji-regex@8.0.0: {} @@ -6993,6 +7402,10 @@ snapshots: json5@2.2.3: {} + jsonc-parser@2.3.1: {} + + jsonc-parser@3.3.1: {} + jsonfile@6.2.0: dependencies: universalify: 2.0.1 @@ -7472,6 +7885,8 @@ snapshots: transitivePeerDependencies: - '@types/node' + muggle-string@0.4.1: {} + mute-stream@2.0.0: {} nanoid@3.3.11: {} @@ -7660,6 +8075,8 @@ snapshots: powershell-utils@0.1.0: {} + prettier@3.8.3: {} + pretty-ms@9.3.0: dependencies: parse-ms: 4.0.0 @@ -7794,6 +8211,8 @@ snapshots: react@19.2.4: {} + readdirp@4.1.2: {} + readdirp@5.0.0: {} recast@0.23.11: @@ -7880,6 +8299,10 @@ snapshots: mdast-util-to-markdown: 2.1.2 unified: 11.0.5 + request-light@0.5.8: {} + + request-light@0.7.0: {} + require-directory@2.1.1: {} require-from-string@2.0.2: {} @@ -8132,6 +8555,13 @@ snapshots: sisteransi@1.0.5: {} + sitemap@9.0.1: + dependencies: + '@types/node': 24.12.2 + '@types/sax': 1.2.7 + arg: 5.0.2 + sax: 1.5.0 + smol-toml@1.6.0: {} source-map-js@1.2.1: {} @@ -8148,6 +8578,8 @@ snapshots: stdin-discarder@0.2.2: {} + stream-replace-string@2.0.0: {} + strict-event-emitter@0.5.1: {} string-width@4.2.3: @@ -8278,6 +8710,12 @@ snapshots: media-typer: 1.1.0 mime-types: 3.0.2 + typesafe-path@0.2.2: {} + + typescript-auto-import-cache@0.3.6: + dependencies: + semver: 7.7.4 + typescript@5.9.3: {} ufo@1.6.3: {} @@ -8484,6 +8922,103 @@ snapshots: - tsx - yaml + volar-service-css@0.0.70(@volar/language-service@2.4.28): + dependencies: + vscode-css-languageservice: 6.3.10 + vscode-languageserver-textdocument: 1.0.12 + vscode-uri: 3.1.0 + optionalDependencies: + '@volar/language-service': 2.4.28 + + volar-service-emmet@0.0.70(@volar/language-service@2.4.28): + dependencies: + '@emmetio/css-parser': 0.4.1 + '@emmetio/html-matcher': 1.3.0 + '@vscode/emmet-helper': 2.11.0 + vscode-uri: 3.1.0 + optionalDependencies: + '@volar/language-service': 2.4.28 + + volar-service-html@0.0.70(@volar/language-service@2.4.28): + dependencies: + vscode-html-languageservice: 5.6.2 + vscode-languageserver-textdocument: 1.0.12 + vscode-uri: 3.1.0 + optionalDependencies: + '@volar/language-service': 2.4.28 + + volar-service-prettier@0.0.70(@volar/language-service@2.4.28)(prettier@3.8.3): + dependencies: + vscode-uri: 3.1.0 + optionalDependencies: + '@volar/language-service': 2.4.28 + prettier: 3.8.3 + + volar-service-typescript-twoslash-queries@0.0.70(@volar/language-service@2.4.28): + dependencies: + vscode-uri: 3.1.0 + optionalDependencies: + '@volar/language-service': 2.4.28 + + volar-service-typescript@0.0.70(@volar/language-service@2.4.28): + dependencies: + path-browserify: 1.0.1 + semver: 7.7.4 + typescript-auto-import-cache: 0.3.6 + vscode-languageserver-textdocument: 1.0.12 + vscode-nls: 5.2.0 + vscode-uri: 3.1.0 + optionalDependencies: + '@volar/language-service': 2.4.28 + + volar-service-yaml@0.0.70(@volar/language-service@2.4.28): + dependencies: + vscode-uri: 3.1.0 + yaml-language-server: 1.20.0 + optionalDependencies: + '@volar/language-service': 2.4.28 + + vscode-css-languageservice@6.3.10: + dependencies: + '@vscode/l10n': 0.0.18 + vscode-languageserver-textdocument: 1.0.12 + vscode-languageserver-types: 3.17.5 + vscode-uri: 3.1.0 + + vscode-html-languageservice@5.6.2: + dependencies: + '@vscode/l10n': 0.0.18 + vscode-languageserver-textdocument: 1.0.12 + vscode-languageserver-types: 3.17.5 + vscode-uri: 3.1.0 + + vscode-json-languageservice@4.1.8: + dependencies: + jsonc-parser: 3.3.1 + vscode-languageserver-textdocument: 1.0.12 + vscode-languageserver-types: 3.17.5 + vscode-nls: 5.2.0 + vscode-uri: 3.1.0 + + vscode-jsonrpc@8.2.0: {} + + vscode-languageserver-protocol@3.17.5: + dependencies: + vscode-jsonrpc: 8.2.0 + vscode-languageserver-types: 3.17.5 + + vscode-languageserver-textdocument@1.0.12: {} + + vscode-languageserver-types@3.17.5: {} + + vscode-languageserver@9.0.1: + dependencies: + vscode-languageserver-protocol: 3.17.5 + + vscode-nls@5.2.0: {} + + vscode-uri@3.1.0: {} + web-namespaces@2.0.1: {} web-streams-polyfill@3.3.3: {} @@ -8538,8 +9073,23 @@ snapshots: yallist@3.1.1: {} - yaml@2.8.2: - optional: true + yaml-language-server@1.20.0: + dependencies: + '@vscode/l10n': 0.0.18 + ajv: 8.18.0 + ajv-draft-04: 1.0.0(ajv@8.18.0) + prettier: 3.8.3 + request-light: 0.5.8 + vscode-json-languageservice: 4.1.8 + vscode-languageserver: 9.0.1 + vscode-languageserver-textdocument: 1.0.12 + vscode-languageserver-types: 3.17.5 + vscode-uri: 3.1.0 + yaml: 2.7.1 + + yaml@2.7.1: {} + + yaml@2.8.2: {} yargs-parser@21.1.1: {} @@ -8574,4 +9124,6 @@ snapshots: zod@3.25.76: {} + zod@4.3.6: {} + zwitch@2.0.4: {} diff --git a/public/Jon_Bogaty_Resume.docx b/public/Jon_Bogaty_Resume.docx index db07a8df..c4c7f6c8 100644 Binary files a/public/Jon_Bogaty_Resume.docx and b/public/Jon_Bogaty_Resume.docx differ diff --git a/public/Jon_Bogaty_Resume.pdf b/public/Jon_Bogaty_Resume.pdf index f62f8e60..0ce0b645 100644 Binary files a/public/Jon_Bogaty_Resume.pdf and b/public/Jon_Bogaty_Resume.pdf differ diff --git a/public/og-image.png b/public/og-image.png new file mode 100644 index 00000000..2933758b Binary files /dev/null and b/public/og-image.png differ diff --git a/scripts/generate-og-image.ts b/scripts/generate-og-image.ts new file mode 100644 index 00000000..64c72a2e --- /dev/null +++ b/scripts/generate-og-image.ts @@ -0,0 +1,102 @@ +/** + * Generate og-image.png (1200x630) — social share card. + * Output: public/og-image.png + */ +import { writeFileSync } from 'node:fs' +import { resolve } from 'node:path' +import { chromium } from 'playwright' +import resume from '../src/content/resume.json' with { type: 'json' } + +const root = resolve(import.meta.dirname!, '..') +const out = resolve(root, 'public/og-image.png') + +const html = ` + + + + + + +
${resume.about.status?.label ?? 'Available'}
+

${resume.about.name}

+
${resume.about.label.split(' | ')[0]}
+
${resume.about.tagline}
+
jonbogaty.com
+ +` + +const browser = await chromium.launch() +try { + const page = await browser.newPage({ viewport: { width: 1200, height: 630 } }) + await page.setContent(html, { waitUntil: 'networkidle', timeout: 20000 }) + const buf = await page.screenshot({ type: 'png', fullPage: false }) + writeFileSync(out, buf) + console.log(`og-image generated: ${out} (${(buf.length / 1024).toFixed(1)} KB)`) +} finally { + await browser.close() +} diff --git a/scripts/generate-resume-pdf-only.ts b/scripts/generate-resume-pdf-only.ts new file mode 100644 index 00000000..d876bcad --- /dev/null +++ b/scripts/generate-resume-pdf-only.ts @@ -0,0 +1,32 @@ +/** + * Generate resume PDF from pre-built dist/resume/index.html. + * Used by CI after pnpm build. Output: dist/Jon_Bogaty_Resume.pdf + */ +import { existsSync, readFileSync, writeFileSync } from 'node:fs' +import { resolve } from 'node:path' +import { chromium } from 'playwright' + +const root = resolve(import.meta.dirname!, '..') +const htmlPath = resolve(root, 'dist/resume/index.html') +const outPath = resolve(root, 'dist/Jon_Bogaty_Resume.pdf') + +if (!existsSync(htmlPath)) { + console.error(`Missing ${htmlPath} — run pnpm build first`) + process.exit(1) +} + +const html = readFileSync(htmlPath, 'utf-8') +const browser = await chromium.launch() +try { + const page = await browser.newPage() + await page.setContent(html, { waitUntil: 'load', timeout: 15000 }) + const pdf = await page.pdf({ + format: 'Letter', + margin: { top: '0.5in', right: '0.5in', bottom: '0.5in', left: '0.5in' }, + printBackground: true, + }) + writeFileSync(outPath, pdf) + console.log(`PDF generated: ${outPath} (${(pdf.length / 1024).toFixed(1)} KB)`) +} finally { + await browser.close() +} diff --git a/src/App.tsx b/src/App.tsx index 2fa406fb..88d5a0f6 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,394 +1,87 @@ -import { ExternalLink, Globe, Package } from 'lucide-react' -import { useState } from 'react' +import { useRef } from 'react' import { HeroSection } from '@/components/HeroSection' +import { SectionTabs } from '@/components/SectionTabs' import { SiteFooter } from '@/components/SiteFooter' -import { Badge } from '@/components/ui/badge' -import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card' -import { Separator } from '@/components/ui/separator' -import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs' +import { AboutSection } from '@/components/sections/AboutSection' +import { EarlierCareer } from '@/components/sections/EarlierCareer' +import { EducationList } from '@/components/sections/EducationList' +import { JobList } from '@/components/sections/JobList' +import { ProjectGrid } from '@/components/sections/ProjectGrid' +import { SkillGrid } from '@/components/sections/SkillGrid' +import { Tabs, TabsContent } from '@/components/ui/tabs' import resume from '@/content/resume.json' -import { formatDateRange } from '@/lib/dates' -import { cn } from '@/lib/utils' -// Every top-level key in resume.json is a tab. "about" goes first. -const sectionKeys = Object.keys(resume).sort((a, b) => (a === 'about' ? -1 : b === 'about' ? 1 : 0)) +// Explicit, reader-value ordering (not alphabetical, not JSON order) +const TABS = [ + { key: 'about', label: 'About' }, + { key: 'projects', label: 'Projects' }, + { key: 'work', label: 'Work' }, + { key: 'skills', label: 'Skills' }, + { key: 'earlierCareer', label: 'Earlier Career' }, + { key: 'education', label: 'Education' }, +] as const -// camelCase → Title Case -function labelFromKey(key: string): string { - return key - .replace(/([A-Z])/g, ' $1') - .replace(/^./, (s) => s.toUpperCase()) - .trim() -} - -const PROJECT_ACCENTS = ['#E8A849', '#6B8BAD', '#4ADE80'] - -// Generic renderer — inspects the shape of each resume.json section and renders it -function SectionRenderer({ data }: { data: unknown }) { - // Plain string → paragraph (e.g. basics.about) - if (typeof data === 'string') { - return

{data}

- } - - // Array of strings → bullet grid - if (Array.isArray(data) && data.length > 0 && typeof data[0] === 'string') { - return ( -
- {(data as string[]).map((item) => ( -
- - {item} -
- ))} -
- ) - } - - // Array of objects — inspect the first item's shape to decide rendering - if (Array.isArray(data) && data.length > 0 && typeof data[0] === 'object') { - const sample = data[0] as Record - - // Work experience: has position + startDate + highlights - if ('position' in sample && 'startDate' in sample && 'highlights' in sample) { - return - } - - // Skills: has name + keywords - if ('name' in sample && 'keywords' in sample) { - return - } - - // Projects: has name + description + url + tech - if ('url' in sample && 'tech' in sample) { - return - } - - // Education: has institution + studyType - if ('institution' in sample && 'studyType' in sample) { - return - } - - // Fallback for unknown object arrays: render as JSON-like cards - return ( -
- {(data as Record[]).map((item) => ( - - - {Object.entries(item).map(([k, v]) => ( -

- {labelFromKey(k)}:{' '} - {String(v)} -

- ))} -
-
- ))} -
- ) - } +export default function App() { + const heroSentinelRef = useRef(null) - // About object: has name, summary, email, location, profiles - if ( - typeof data === 'object' && - data !== null && - 'summary' in (data as Record) && - 'email' in (data as Record) && - 'profiles' in (data as Record) - ) { - const info = data as typeof resume.about - return ( -
- {(Array.isArray(info.summary) ? info.summary : [info.summary]).map((p) => ( -

- {p} -

- ))} -
- - {info.location.city}, {info.location.region} - - · - - {info.email} - -
-
- {info.profiles.map((p) => ( - - {p.network} - - ))} -
-
- ) - } + return ( + +
- // Object with summary + positions (earlierCareer shape) - if ( - typeof data === 'object' && - data !== null && - 'summary' in (data as Record) && - 'positions' in (data as Record) - ) { - const ec = data as typeof resume.earlierCareer - return ( -
-

{ec.summary}

-
- {ec.positions.map((pos) => ( - - {pos.position} - - @ {pos.name} ({pos.year}) - - - ))} -
-
- ) - } + - // Fallback: stringify - return
{JSON.stringify(data, null, 2)}
-} +
-// --- Sub-renderers for specific data shapes --- + -function JobList({ jobs }: { jobs: typeof resume.work }) { - const [selected, setSelected] = useState(0) - const active = jobs[selected] - return ( -
- - - -
-

{active.name}

-

- {active.position} -

-

- {formatDateRange(active.startDate, active.endDate)} -

+
+ +
+

About

+
- {active.summary && ( -

{active.summary}

- )} - {(active.highlights ?? []).length > 0 && ( -
    - {(active.highlights ?? []).map((h) => ( -
  • - - {h} -
  • - ))} -
- )} - - -
- ) -} + -function SkillGrid({ categories }: { categories: typeof resume.skills }) { - return ( -
- {categories.map((cat) => ( - - -

- {cat.name} -

-
- {cat.keywords.map((kw) => ( - - {kw} - - ))} -
-
-
- ))} -
- ) -} - -function ProjectGrid({ items }: { items: typeof resume.projects }) { - return ( -
- {items.map((project, i) => { - const accent = PROJECT_ACCENTS[i % PROJECT_ACCENTS.length] - return ( - -
- -
- {project.domain && ( - - - {project.domain} - - )} - - - -
- - {project.name} - - {project.tagline && ( - - {project.tagline} - - )} -
- -

{project.description}

- {project.tech && ( -
- {project.tech.map((t) => ( - - {t} - - ))} -
- )} - {project.packages && ( - <> - -
-

- - Packages -

-
- {project.packages.map((pkg) => ( -
- - {pkg.name} - - - {pkg.description} - -
- ))} -
-
- - )} -
- - ) - })} -
- ) -} - -function EducationList({ items }: { items: typeof resume.education }) { - return ( -
- {items.map((edu) => ( -
-

- {edu.studyType} — {edu.area} -

-

- {edu.institution} | {edu.startDate}–{edu.endDate} -

- {edu.honors && ( -

{edu.honors.join(' · ')}

- )} -
- ))} -
- ) -} + +
+

Projects

+ +
+
-// --- App: hero from basics, tabs auto-generated from every other key --- + +
+

Work

+ +
+
-export default function App() { - return ( - -
-
- - {sectionKeys.map((key) => ( - - {labelFromKey(key)} - - ))} - -
-
+ +
+

Skills

+ +
+
- + +
+

Earlier Career

+ +
+
-
- {sectionKeys.map((key) => ( - -
-

{labelFromKey(key)}

- )[key]} /> -
-
- ))} + +
+

Education

+ +
+
diff --git a/src/components/HeroSection.tsx b/src/components/HeroSection.tsx index 4e7e0d14..6ea830ba 100644 --- a/src/components/HeroSection.tsx +++ b/src/components/HeroSection.tsx @@ -1,19 +1,26 @@ -import { Download, Github, Linkedin, MessageCircle } from 'lucide-react' +import { Download, FileText, Github, Linkedin, MessageCircle } from 'lucide-react' import { motion } from 'motion/react' import { useEffect, useState } from 'react' import { Button } from '@/components/ui/button' +interface Stat { + value: string + label: string +} + +interface Status { + label: string + pulse?: boolean +} + interface HeroProps { name: string label: string - summary: string + tagline: string + status?: Status + stats?: Stat[] } -/** - * Shader background with graceful degradation. - * Dynamically imports @paper-design/shaders-react to avoid SSR/build issues. - * Falls back to a pure CSS animated gradient if WebGL is unavailable. - */ function ShaderBg() { const [ShaderComponent, setShaderComponent] = useState | null>(null) useEffect(() => { - // Check for WebGL support before loading the shader const canvas = document.createElement('canvas') const gl = canvas.getContext('webgl2') || canvas.getContext('webgl') - if (!gl) return // Fall back to CSS gradient + if (!gl) return import('@paper-design/shaders-react') .then((mod) => setShaderComponent(() => mod.MeshGradient)) - .catch(() => { - // Shader load failed — CSS fallback stays - }) + .catch(() => {}) }, []) if (ShaderComponent) { @@ -47,7 +51,6 @@ function ShaderBg() { ) } - // CSS fallback — animated radial gradients return (
- {/* Shader / gradient background */} - {/* Dot grid overlay for texture */}
- {/* Content */}
- {/* Name with gradient */} -

+ {status && ( +
+ + {status.pulse && ( + + )} + {status.label} + +
+ )} + +

{name}

- {/* Label / role */} - {/* Tagline */} {tagline} - {/* CTAs */} + {stats && stats.length > 0 && ( + + {stats.map((stat) => ( +
+
+ {stat.value} +
+
+ {stat.label} +
+
+ ))} +
+ )} + - - - - +
+ + + +
- {/* Bottom fade into content */}
) diff --git a/src/components/SectionTabs.tsx b/src/components/SectionTabs.tsx new file mode 100644 index 00000000..830f0fbc --- /dev/null +++ b/src/components/SectionTabs.tsx @@ -0,0 +1,79 @@ +import { Download } from 'lucide-react' +import { useEffect, useRef, useState } from 'react' +import { Button } from '@/components/ui/button' +import { TabsList, TabsTrigger } from '@/components/ui/tabs' +import { cn } from '@/lib/utils' + +interface SectionTabsProps { + tabs: { key: string; label: string }[] + name: string + heroSentinelRef: React.RefObject +} + +export function SectionTabs({ tabs, name, heroSentinelRef }: SectionTabsProps) { + const [collapsed, setCollapsed] = useState(false) + const headerRef = useRef(null) + + useEffect(() => { + const sentinel = heroSentinelRef.current + if (!sentinel) return + const observer = new IntersectionObserver( + (entries) => setCollapsed(!entries[0].isIntersecting), + { threshold: 0 } + ) + observer.observe(sentinel) + return () => observer.disconnect() + }, [heroSentinelRef]) + + return ( +
+
+ {collapsed && ( + + {name} + + )} + + + {tabs.map((tab) => ( + + {tab.label} + + ))} + + + {collapsed && ( + + )} +
+
+ ) +} diff --git a/src/components/SiteFooter.tsx b/src/components/SiteFooter.tsx index f57e17f1..f6ecc494 100644 --- a/src/components/SiteFooter.tsx +++ b/src/components/SiteFooter.tsx @@ -1,4 +1,4 @@ -import { Download, Github, Linkedin, Mail, MapPin, MessageCircle } from 'lucide-react' +import { Download, FileText, Github, Linkedin, Mail, MapPin, MessageCircle } from 'lucide-react' import { Button } from '@/components/ui/button' import { Separator } from '@/components/ui/separator' @@ -16,7 +16,6 @@ export function SiteFooter() {