From 19081ec6827e14b026fbe13bdb6c3ad79f99e970 Mon Sep 17 00:00:00 2001 From: Marc Mintel Date: Tue, 13 Jan 2026 00:00:22 +0100 Subject: [PATCH] wip --- context/about.md | 6 +- package-lock.json | 1797 +++++++++++++++++++++++++- package.json | 18 +- postcss.config.js | 6 + scripts/smoke-test.ts | 576 +++++++++ scripts/test-links.ts | 178 +++ src/components/ArticleBlockquote.tsx | 90 +- src/components/BlogPostCard.tsx | 48 - src/components/Container.tsx | 23 - src/components/Footer.tsx | 36 + src/components/Hero.tsx | 54 + src/components/MediumCard.astro | 130 ++ src/components/SearchBar.tsx | 141 ++ src/components/Tag.astro | 125 ++ src/layouts/BaseLayout.astro | 209 ++- src/layouts/BlogLayout.astro | 133 -- src/pages/blog/[slug].astro | 716 ++++++++++ src/pages/blog/debugging-tips.astro | 79 -- src/pages/blog/first-note.astro | 69 - src/pages/index.astro | 421 +++++- src/pages/tags/[tag].astro | 42 + src/styles/global.css | 579 +++++++++ tailwind.config.js | 68 + 23 files changed, 5023 insertions(+), 521 deletions(-) create mode 100644 postcss.config.js create mode 100644 scripts/smoke-test.ts create mode 100644 scripts/test-links.ts delete mode 100644 src/components/BlogPostCard.tsx delete mode 100644 src/components/Container.tsx create mode 100644 src/components/Footer.tsx create mode 100644 src/components/Hero.tsx create mode 100644 src/components/MediumCard.astro create mode 100644 src/components/SearchBar.tsx create mode 100644 src/components/Tag.astro delete mode 100644 src/layouts/BlogLayout.astro create mode 100644 src/pages/blog/[slug].astro delete mode 100644 src/pages/blog/debugging-tips.astro delete mode 100644 src/pages/blog/first-note.astro create mode 100644 src/pages/tags/[tag].astro create mode 100644 src/styles/global.css create mode 100644 tailwind.config.js diff --git a/context/about.md b/context/about.md index 7c7b30e..a974021 100644 --- a/context/about.md +++ b/context/about.md @@ -1,15 +1,15 @@ -# Marc — technical problem solver +# Marc — digital problem solver ## Identity - Name: Marc Mintel - Mail: marc@mintel.me - Location: Vulkaneifel, Germany -- Role: Independent technical problem solver +- Role: Independent digital problem solver - Mode: Solo - Focus: Understanding problems and building practical solutions ## What I do -I work on technical problems and build tools, scripts, and systems to solve them. +I work on digital problems and build tools, scripts, and systems to solve them. Sometimes that means code, sometimes automation, sometimes AI, sometimes something else. The tool is secondary. The problem comes first. diff --git a/package-lock.json b/package-lock.json index bb979af..d6c233e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,20 +1,43 @@ { - "name": "molecular-main", - "version": "0.0.1", + "name": "mintel-blog", + "version": "0.1.0", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "molecular-main", - "version": "0.0.1", + "name": "mintel-blog", + "version": "0.1.0", "dependencies": { "@astrojs/mdx": "^4.3.13", "@astrojs/react": "^4.4.2", + "@astrojs/tailwind": "^6.0.2", "@types/react": "^19.2.8", "@types/react-dom": "^19.2.3", "astro": "^5.16.8", + "lucide-react": "^0.468.0", "react": "^19.2.3", - "react-dom": "^19.2.3" + "react-dom": "^19.2.3", + "shiki": "^1.24.2", + "tailwindcss": "^3.4.0" + }, + "devDependencies": { + "@tailwindcss/typography": "^0.5.15", + "@types/node": "^25.0.6", + "autoprefixer": "^10.4.20", + "postcss": "^8.4.49", + "tsx": "^4.21.0" + } + }, + "node_modules/@alloc/quick-lru": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", + "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/@astrojs/compiler": { @@ -58,6 +81,112 @@ "vfile": "^6.0.3" } }, + "node_modules/@astrojs/markdown-remark/node_modules/@shikijs/core": { + "version": "3.21.0", + "resolved": "https://registry.npmjs.org/@shikijs/core/-/core-3.21.0.tgz", + "integrity": "sha512-AXSQu/2n1UIQekY8euBJlvFYZIw0PHY63jUzGbrOma4wPxzznJXTXkri+QcHeBNaFxiiOljKxxJkVSoB3PjbyA==", + "license": "MIT", + "dependencies": { + "@shikijs/types": "3.21.0", + "@shikijs/vscode-textmate": "^10.0.2", + "@types/hast": "^3.0.4", + "hast-util-to-html": "^9.0.5" + } + }, + "node_modules/@astrojs/markdown-remark/node_modules/@shikijs/engine-javascript": { + "version": "3.21.0", + "resolved": "https://registry.npmjs.org/@shikijs/engine-javascript/-/engine-javascript-3.21.0.tgz", + "integrity": "sha512-ATwv86xlbmfD9n9gKRiwuPpWgPENAWCLwYCGz9ugTJlsO2kOzhOkvoyV/UD+tJ0uT7YRyD530x6ugNSffmvIiQ==", + "license": "MIT", + "dependencies": { + "@shikijs/types": "3.21.0", + "@shikijs/vscode-textmate": "^10.0.2", + "oniguruma-to-es": "^4.3.4" + } + }, + "node_modules/@astrojs/markdown-remark/node_modules/@shikijs/engine-oniguruma": { + "version": "3.21.0", + "resolved": "https://registry.npmjs.org/@shikijs/engine-oniguruma/-/engine-oniguruma-3.21.0.tgz", + "integrity": "sha512-OYknTCct6qiwpQDqDdf3iedRdzj6hFlOPv5hMvI+hkWfCKs5mlJ4TXziBG9nyabLwGulrUjHiCq3xCspSzErYQ==", + "license": "MIT", + "dependencies": { + "@shikijs/types": "3.21.0", + "@shikijs/vscode-textmate": "^10.0.2" + } + }, + "node_modules/@astrojs/markdown-remark/node_modules/@shikijs/langs": { + "version": "3.21.0", + "resolved": "https://registry.npmjs.org/@shikijs/langs/-/langs-3.21.0.tgz", + "integrity": "sha512-g6mn5m+Y6GBJ4wxmBYqalK9Sp0CFkUqfNzUy2pJglUginz6ZpWbaWjDB4fbQ/8SHzFjYbtU6Ddlp1pc+PPNDVA==", + "license": "MIT", + "dependencies": { + "@shikijs/types": "3.21.0" + } + }, + "node_modules/@astrojs/markdown-remark/node_modules/@shikijs/themes": { + "version": "3.21.0", + "resolved": "https://registry.npmjs.org/@shikijs/themes/-/themes-3.21.0.tgz", + "integrity": "sha512-BAE4cr9EDiZyYzwIHEk7JTBJ9CzlPuM4PchfcA5ao1dWXb25nv6hYsoDiBq2aZK9E3dlt3WB78uI96UESD+8Mw==", + "license": "MIT", + "dependencies": { + "@shikijs/types": "3.21.0" + } + }, + "node_modules/@astrojs/markdown-remark/node_modules/@shikijs/types": { + "version": "3.21.0", + "resolved": "https://registry.npmjs.org/@shikijs/types/-/types-3.21.0.tgz", + "integrity": "sha512-zGrWOxZ0/+0ovPY7PvBU2gIS9tmhSUUt30jAcNV0Bq0gb2S98gwfjIs1vxlmH5zM7/4YxLamT6ChlqqAJmPPjA==", + "license": "MIT", + "dependencies": { + "@shikijs/vscode-textmate": "^10.0.2", + "@types/hast": "^3.0.4" + } + }, + "node_modules/@astrojs/markdown-remark/node_modules/oniguruma-to-es": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/oniguruma-to-es/-/oniguruma-to-es-4.3.4.tgz", + "integrity": "sha512-3VhUGN3w2eYxnTzHn+ikMI+fp/96KoRSVK9/kMTcFqj1NRDh2IhQCKvYxDnWePKRXY/AqH+Fuiyb7VHSzBjHfA==", + "license": "MIT", + "dependencies": { + "oniguruma-parser": "^0.12.1", + "regex": "^6.0.1", + "regex-recursion": "^6.0.2" + } + }, + "node_modules/@astrojs/markdown-remark/node_modules/regex": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/regex/-/regex-6.1.0.tgz", + "integrity": "sha512-6VwtthbV4o/7+OaAF9I5L5V3llLEsoPyq9P1JVXkedTP33c7MfCG0/5NOPcSJn0TzXcG9YUrR0gQSWioew3LDg==", + "license": "MIT", + "dependencies": { + "regex-utilities": "^2.3.0" + } + }, + "node_modules/@astrojs/markdown-remark/node_modules/regex-recursion": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/regex-recursion/-/regex-recursion-6.0.2.tgz", + "integrity": "sha512-0YCaSCq2VRIebiaUviZNs0cBz1kg5kVS2UKUfNIx8YVs1cN3AV7NTctO5FOKBA+UT2BPJIWZauYHPqJODG50cg==", + "license": "MIT", + "dependencies": { + "regex-utilities": "^2.3.0" + } + }, + "node_modules/@astrojs/markdown-remark/node_modules/shiki": { + "version": "3.21.0", + "resolved": "https://registry.npmjs.org/shiki/-/shiki-3.21.0.tgz", + "integrity": "sha512-N65B/3bqL/TI2crrXr+4UivctrAGEjmsib5rPMMPpFp1xAx/w03v8WZ9RDDFYteXoEgY7qZ4HGgl5KBIu1153w==", + "license": "MIT", + "dependencies": { + "@shikijs/core": "3.21.0", + "@shikijs/engine-javascript": "3.21.0", + "@shikijs/engine-oniguruma": "3.21.0", + "@shikijs/langs": "3.21.0", + "@shikijs/themes": "3.21.0", + "@shikijs/types": "3.21.0", + "@shikijs/vscode-textmate": "^10.0.2", + "@types/hast": "^3.0.4" + } + }, "node_modules/@astrojs/mdx": { "version": "4.3.13", "resolved": "https://registry.npmjs.org/@astrojs/mdx/-/mdx-4.3.13.tgz", @@ -117,6 +246,56 @@ "react-dom": "^17.0.2 || ^18.0.0 || ^19.0.0" } }, + "node_modules/@astrojs/tailwind": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@astrojs/tailwind/-/tailwind-6.0.2.tgz", + "integrity": "sha512-j3mhLNeugZq6A8dMNXVarUa8K6X9AW+QHU9u3lKNrPLMHhOQ0S7VeWhHwEeJFpEK1BTKEUY1U78VQv2gN6hNGg==", + "license": "MIT", + "dependencies": { + "autoprefixer": "^10.4.21", + "postcss": "^8.5.3", + "postcss-load-config": "^4.0.2" + }, + "peerDependencies": { + "astro": "^3.0.0 || ^4.0.0 || ^5.0.0", + "tailwindcss": "^3.0.24" + } + }, + "node_modules/@astrojs/tailwind/node_modules/postcss-load-config": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-4.0.2.tgz", + "integrity": "sha512-bSVhyJGL00wMVoPUzAVAnbEoWyqRxkjv64tUl427SKnPrENtq6hJwUojroMz2VB+Q1edmi4IfrAPpami5VVgMQ==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "lilconfig": "^3.0.0", + "yaml": "^2.3.4" + }, + "engines": { + "node": ">= 14" + }, + "peerDependencies": { + "postcss": ">=8.0.9", + "ts-node": ">=9.0.0" + }, + "peerDependenciesMeta": { + "postcss": { + "optional": true + }, + "ts-node": { + "optional": true + } + } + }, "node_modules/@astrojs/telemetry": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/@astrojs/telemetry/-/telemetry-3.3.0.tgz", @@ -1411,6 +1590,41 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, "node_modules/@oslojs/encoding": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@oslojs/encoding/-/encoding-1.1.0.tgz", @@ -1777,63 +1991,65 @@ ] }, "node_modules/@shikijs/core": { - "version": "3.21.0", - "resolved": "https://registry.npmjs.org/@shikijs/core/-/core-3.21.0.tgz", - "integrity": "sha512-AXSQu/2n1UIQekY8euBJlvFYZIw0PHY63jUzGbrOma4wPxzznJXTXkri+QcHeBNaFxiiOljKxxJkVSoB3PjbyA==", + "version": "1.29.2", + "resolved": "https://registry.npmjs.org/@shikijs/core/-/core-1.29.2.tgz", + "integrity": "sha512-vju0lY9r27jJfOY4Z7+Rt/nIOjzJpZ3y+nYpqtUZInVoXQ/TJZcfGnNOGnKjFdVZb8qexiCuSlZRKcGfhhTTZQ==", "license": "MIT", "dependencies": { - "@shikijs/types": "3.21.0", - "@shikijs/vscode-textmate": "^10.0.2", + "@shikijs/engine-javascript": "1.29.2", + "@shikijs/engine-oniguruma": "1.29.2", + "@shikijs/types": "1.29.2", + "@shikijs/vscode-textmate": "^10.0.1", "@types/hast": "^3.0.4", - "hast-util-to-html": "^9.0.5" + "hast-util-to-html": "^9.0.4" } }, "node_modules/@shikijs/engine-javascript": { - "version": "3.21.0", - "resolved": "https://registry.npmjs.org/@shikijs/engine-javascript/-/engine-javascript-3.21.0.tgz", - "integrity": "sha512-ATwv86xlbmfD9n9gKRiwuPpWgPENAWCLwYCGz9ugTJlsO2kOzhOkvoyV/UD+tJ0uT7YRyD530x6ugNSffmvIiQ==", + "version": "1.29.2", + "resolved": "https://registry.npmjs.org/@shikijs/engine-javascript/-/engine-javascript-1.29.2.tgz", + "integrity": "sha512-iNEZv4IrLYPv64Q6k7EPpOCE/nuvGiKl7zxdq0WFuRPF5PAE9PRo2JGq/d8crLusM59BRemJ4eOqrFrC4wiQ+A==", "license": "MIT", "dependencies": { - "@shikijs/types": "3.21.0", - "@shikijs/vscode-textmate": "^10.0.2", - "oniguruma-to-es": "^4.3.4" + "@shikijs/types": "1.29.2", + "@shikijs/vscode-textmate": "^10.0.1", + "oniguruma-to-es": "^2.2.0" } }, "node_modules/@shikijs/engine-oniguruma": { - "version": "3.21.0", - "resolved": "https://registry.npmjs.org/@shikijs/engine-oniguruma/-/engine-oniguruma-3.21.0.tgz", - "integrity": "sha512-OYknTCct6qiwpQDqDdf3iedRdzj6hFlOPv5hMvI+hkWfCKs5mlJ4TXziBG9nyabLwGulrUjHiCq3xCspSzErYQ==", + "version": "1.29.2", + "resolved": "https://registry.npmjs.org/@shikijs/engine-oniguruma/-/engine-oniguruma-1.29.2.tgz", + "integrity": "sha512-7iiOx3SG8+g1MnlzZVDYiaeHe7Ez2Kf2HrJzdmGwkRisT7r4rak0e655AcM/tF9JG/kg5fMNYlLLKglbN7gBqA==", "license": "MIT", "dependencies": { - "@shikijs/types": "3.21.0", - "@shikijs/vscode-textmate": "^10.0.2" + "@shikijs/types": "1.29.2", + "@shikijs/vscode-textmate": "^10.0.1" } }, "node_modules/@shikijs/langs": { - "version": "3.21.0", - "resolved": "https://registry.npmjs.org/@shikijs/langs/-/langs-3.21.0.tgz", - "integrity": "sha512-g6mn5m+Y6GBJ4wxmBYqalK9Sp0CFkUqfNzUy2pJglUginz6ZpWbaWjDB4fbQ/8SHzFjYbtU6Ddlp1pc+PPNDVA==", + "version": "1.29.2", + "resolved": "https://registry.npmjs.org/@shikijs/langs/-/langs-1.29.2.tgz", + "integrity": "sha512-FIBA7N3LZ+223U7cJDUYd5shmciFQlYkFXlkKVaHsCPgfVLiO+e12FmQE6Tf9vuyEsFe3dIl8qGWKXgEHL9wmQ==", "license": "MIT", "dependencies": { - "@shikijs/types": "3.21.0" + "@shikijs/types": "1.29.2" } }, "node_modules/@shikijs/themes": { - "version": "3.21.0", - "resolved": "https://registry.npmjs.org/@shikijs/themes/-/themes-3.21.0.tgz", - "integrity": "sha512-BAE4cr9EDiZyYzwIHEk7JTBJ9CzlPuM4PchfcA5ao1dWXb25nv6hYsoDiBq2aZK9E3dlt3WB78uI96UESD+8Mw==", + "version": "1.29.2", + "resolved": "https://registry.npmjs.org/@shikijs/themes/-/themes-1.29.2.tgz", + "integrity": "sha512-i9TNZlsq4uoyqSbluIcZkmPL9Bfi3djVxRnofUHwvx/h6SRW3cwgBC5SML7vsDcWyukY0eCzVN980rqP6qNl9g==", "license": "MIT", "dependencies": { - "@shikijs/types": "3.21.0" + "@shikijs/types": "1.29.2" } }, "node_modules/@shikijs/types": { - "version": "3.21.0", - "resolved": "https://registry.npmjs.org/@shikijs/types/-/types-3.21.0.tgz", - "integrity": "sha512-zGrWOxZ0/+0ovPY7PvBU2gIS9tmhSUUt30jAcNV0Bq0gb2S98gwfjIs1vxlmH5zM7/4YxLamT6ChlqqAJmPPjA==", + "version": "1.29.2", + "resolved": "https://registry.npmjs.org/@shikijs/types/-/types-1.29.2.tgz", + "integrity": "sha512-VJjK0eIijTZf0QSTODEXCqinjBn0joAHQ+aPSBzrv4O2d/QSbsMw+ZeSRx03kV34Hy7NzUvV/7NqfYGRLrASmw==", "license": "MIT", "dependencies": { - "@shikijs/vscode-textmate": "^10.0.2", + "@shikijs/vscode-textmate": "^10.0.1", "@types/hast": "^3.0.4" } }, @@ -1843,6 +2059,19 @@ "integrity": "sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==", "license": "MIT" }, + "node_modules/@tailwindcss/typography": { + "version": "0.5.19", + "resolved": "https://registry.npmjs.org/@tailwindcss/typography/-/typography-0.5.19.tgz", + "integrity": "sha512-w31dd8HOx3k9vPtcQh5QHP9GwKcgbMp87j58qi6xgiBnFFtKEAgCWnDw4qUT8aHwkCp8bKvb/KGKWWHedP0AAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "postcss-selector-parser": "6.0.10" + }, + "peerDependencies": { + "tailwindcss": ">=3.0.0 || insiders || >=4.0.0-alpha.20 || >=4.0.0-beta.1" + } + }, "node_modules/@types/babel__core": { "version": "7.20.5", "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", @@ -1947,6 +2176,16 @@ "@types/unist": "*" } }, + "node_modules/@types/node": { + "version": "25.0.6", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.0.6.tgz", + "integrity": "sha512-NNu0sjyNxpoiW3YuVFfNz7mxSQ+S4X2G28uqg2s+CzoqoQjLPsWSbsFFyztIAqt2vb8kfEAsJNepMGPTxFDx3Q==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.16.0" + } + }, "node_modules/@types/react": { "version": "19.2.8", "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.8.tgz", @@ -2092,6 +2331,12 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/any-promise": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", + "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", + "license": "MIT" + }, "node_modules/anymatch": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", @@ -2117,6 +2362,12 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/arg": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", + "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==", + "license": "MIT" + }, "node_modules/argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", @@ -2237,6 +2488,148 @@ "sharp": "^0.34.0" } }, + "node_modules/astro/node_modules/@shikijs/core": { + "version": "3.21.0", + "resolved": "https://registry.npmjs.org/@shikijs/core/-/core-3.21.0.tgz", + "integrity": "sha512-AXSQu/2n1UIQekY8euBJlvFYZIw0PHY63jUzGbrOma4wPxzznJXTXkri+QcHeBNaFxiiOljKxxJkVSoB3PjbyA==", + "license": "MIT", + "dependencies": { + "@shikijs/types": "3.21.0", + "@shikijs/vscode-textmate": "^10.0.2", + "@types/hast": "^3.0.4", + "hast-util-to-html": "^9.0.5" + } + }, + "node_modules/astro/node_modules/@shikijs/engine-javascript": { + "version": "3.21.0", + "resolved": "https://registry.npmjs.org/@shikijs/engine-javascript/-/engine-javascript-3.21.0.tgz", + "integrity": "sha512-ATwv86xlbmfD9n9gKRiwuPpWgPENAWCLwYCGz9ugTJlsO2kOzhOkvoyV/UD+tJ0uT7YRyD530x6ugNSffmvIiQ==", + "license": "MIT", + "dependencies": { + "@shikijs/types": "3.21.0", + "@shikijs/vscode-textmate": "^10.0.2", + "oniguruma-to-es": "^4.3.4" + } + }, + "node_modules/astro/node_modules/@shikijs/engine-oniguruma": { + "version": "3.21.0", + "resolved": "https://registry.npmjs.org/@shikijs/engine-oniguruma/-/engine-oniguruma-3.21.0.tgz", + "integrity": "sha512-OYknTCct6qiwpQDqDdf3iedRdzj6hFlOPv5hMvI+hkWfCKs5mlJ4TXziBG9nyabLwGulrUjHiCq3xCspSzErYQ==", + "license": "MIT", + "dependencies": { + "@shikijs/types": "3.21.0", + "@shikijs/vscode-textmate": "^10.0.2" + } + }, + "node_modules/astro/node_modules/@shikijs/langs": { + "version": "3.21.0", + "resolved": "https://registry.npmjs.org/@shikijs/langs/-/langs-3.21.0.tgz", + "integrity": "sha512-g6mn5m+Y6GBJ4wxmBYqalK9Sp0CFkUqfNzUy2pJglUginz6ZpWbaWjDB4fbQ/8SHzFjYbtU6Ddlp1pc+PPNDVA==", + "license": "MIT", + "dependencies": { + "@shikijs/types": "3.21.0" + } + }, + "node_modules/astro/node_modules/@shikijs/themes": { + "version": "3.21.0", + "resolved": "https://registry.npmjs.org/@shikijs/themes/-/themes-3.21.0.tgz", + "integrity": "sha512-BAE4cr9EDiZyYzwIHEk7JTBJ9CzlPuM4PchfcA5ao1dWXb25nv6hYsoDiBq2aZK9E3dlt3WB78uI96UESD+8Mw==", + "license": "MIT", + "dependencies": { + "@shikijs/types": "3.21.0" + } + }, + "node_modules/astro/node_modules/@shikijs/types": { + "version": "3.21.0", + "resolved": "https://registry.npmjs.org/@shikijs/types/-/types-3.21.0.tgz", + "integrity": "sha512-zGrWOxZ0/+0ovPY7PvBU2gIS9tmhSUUt30jAcNV0Bq0gb2S98gwfjIs1vxlmH5zM7/4YxLamT6ChlqqAJmPPjA==", + "license": "MIT", + "dependencies": { + "@shikijs/vscode-textmate": "^10.0.2", + "@types/hast": "^3.0.4" + } + }, + "node_modules/astro/node_modules/oniguruma-to-es": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/oniguruma-to-es/-/oniguruma-to-es-4.3.4.tgz", + "integrity": "sha512-3VhUGN3w2eYxnTzHn+ikMI+fp/96KoRSVK9/kMTcFqj1NRDh2IhQCKvYxDnWePKRXY/AqH+Fuiyb7VHSzBjHfA==", + "license": "MIT", + "dependencies": { + "oniguruma-parser": "^0.12.1", + "regex": "^6.0.1", + "regex-recursion": "^6.0.2" + } + }, + "node_modules/astro/node_modules/regex": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/regex/-/regex-6.1.0.tgz", + "integrity": "sha512-6VwtthbV4o/7+OaAF9I5L5V3llLEsoPyq9P1JVXkedTP33c7MfCG0/5NOPcSJn0TzXcG9YUrR0gQSWioew3LDg==", + "license": "MIT", + "dependencies": { + "regex-utilities": "^2.3.0" + } + }, + "node_modules/astro/node_modules/regex-recursion": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/regex-recursion/-/regex-recursion-6.0.2.tgz", + "integrity": "sha512-0YCaSCq2VRIebiaUviZNs0cBz1kg5kVS2UKUfNIx8YVs1cN3AV7NTctO5FOKBA+UT2BPJIWZauYHPqJODG50cg==", + "license": "MIT", + "dependencies": { + "regex-utilities": "^2.3.0" + } + }, + "node_modules/astro/node_modules/shiki": { + "version": "3.21.0", + "resolved": "https://registry.npmjs.org/shiki/-/shiki-3.21.0.tgz", + "integrity": "sha512-N65B/3bqL/TI2crrXr+4UivctrAGEjmsib5rPMMPpFp1xAx/w03v8WZ9RDDFYteXoEgY7qZ4HGgl5KBIu1153w==", + "license": "MIT", + "dependencies": { + "@shikijs/core": "3.21.0", + "@shikijs/engine-javascript": "3.21.0", + "@shikijs/engine-oniguruma": "3.21.0", + "@shikijs/langs": "3.21.0", + "@shikijs/themes": "3.21.0", + "@shikijs/types": "3.21.0", + "@shikijs/vscode-textmate": "^10.0.2", + "@types/hast": "^3.0.4" + } + }, + "node_modules/autoprefixer": { + "version": "10.4.23", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.23.tgz", + "integrity": "sha512-YYTXSFulfwytnjAPlw8QHncHJmlvFKtczb8InXaAx9Q0LbfDnfEYDE55omerIJKihhmU61Ft+cAOSzQVaBUmeA==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/autoprefixer" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "browserslist": "^4.28.1", + "caniuse-lite": "^1.0.30001760", + "fraction.js": "^5.3.4", + "picocolors": "^1.1.1", + "postcss-value-parser": "^4.2.0" + }, + "bin": { + "autoprefixer": "bin/autoprefixer" + }, + "engines": { + "node": "^10 || ^12 || >=14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, "node_modules/axobject-query": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz", @@ -2271,6 +2664,18 @@ "baseline-browser-mapping": "dist/cli.js" } }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/boolbase": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", @@ -2299,6 +2704,18 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/browserslist": { "version": "4.28.1", "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz", @@ -2344,6 +2761,15 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/camelcase-css": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz", + "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==", + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, "node_modules/caniuse-lite": { "version": "1.0.30001764", "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001764.tgz", @@ -2730,6 +3156,12 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/didyoumean": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", + "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==", + "license": "Apache-2.0" + }, "node_modules/diff": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/diff/-/diff-5.2.0.tgz", @@ -2833,6 +3265,12 @@ "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", "license": "MIT" }, + "node_modules/emoji-regex-xs": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex-xs/-/emoji-regex-xs-1.0.0.tgz", + "integrity": "sha512-LRlerrMYoIDrT6jgpeZ2YYl/L8EulRTt5hQcYjy5AInh7HWXKimpqx68aknBFpGL2+/IcogTcaydJEgaTmOpDg==", + "license": "MIT" + }, "node_modules/entities": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", @@ -3048,6 +3486,43 @@ "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", "license": "MIT" }, + "node_modules/fast-glob": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fastq": { + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz", + "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==", + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, "node_modules/fdir": { "version": "6.5.0", "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", @@ -3065,6 +3540,18 @@ } } }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/flattie": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/flattie/-/flattie-1.1.1.tgz", @@ -3095,6 +3582,19 @@ "node": ">=20" } }, + "node_modules/fraction.js": { + "version": "5.3.4", + "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-5.3.4.tgz", + "integrity": "sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==", + "license": "MIT", + "engines": { + "node": "*" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/rawify" + } + }, "node_modules/fsevents": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", @@ -3109,6 +3609,15 @@ "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/gensync": { "version": "1.0.0-beta.2", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", @@ -3130,12 +3639,37 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/get-tsconfig": { + "version": "4.13.0", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.13.0.tgz", + "integrity": "sha512-1VKTZJCwBrvbd+Wn3AOgQP/2Av+TfTCOlE4AcRJE72W1ksZXbAx8PPBR9RzgTeSPzlPMHrbANMH3LbltH73wxQ==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, "node_modules/github-slugger": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/github-slugger/-/github-slugger-2.0.0.tgz", "integrity": "sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw==", "license": "ISC" }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, "node_modules/h3": { "version": "1.15.4", "resolved": "https://registry.npmjs.org/h3/-/h3-1.15.4.tgz", @@ -3153,6 +3687,18 @@ "uncrypto": "^0.1.3" } }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/hast-util-from-html": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/hast-util-from-html/-/hast-util-from-html-2.0.3.tgz", @@ -3456,6 +4002,33 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "license": "MIT", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-core-module": { + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-decimal": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-2.0.1.tgz", @@ -3481,6 +4054,15 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/is-fullwidth-code-point": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", @@ -3490,6 +4072,18 @@ "node": ">=8" } }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/is-hexadecimal": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-2.0.1.tgz", @@ -3518,6 +4112,15 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, "node_modules/is-plain-obj": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", @@ -3545,6 +4148,15 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/jiti": { + "version": "1.21.7", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.7.tgz", + "integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==", + "license": "MIT", + "bin": { + "jiti": "bin/jiti.js" + } + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -3596,6 +4208,24 @@ "node": ">=6" } }, + "node_modules/lilconfig": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", + "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==", + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antonk52" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "license": "MIT" + }, "node_modules/longest-streak": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-3.1.0.tgz", @@ -3612,6 +4242,15 @@ "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", "license": "ISC" }, + "node_modules/lucide-react": { + "version": "0.468.0", + "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.468.0.tgz", + "integrity": "sha512-6koYRhnM2N0GGZIdXzSeiNwguv1gt/FAjZOiPl76roBi3xKEXa4WmfpxgQwTTL4KipXjefrnf3oV4IsYhi4JFA==", + "license": "ISC", + "peerDependencies": { + "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0-rc" + } + }, "node_modules/magic-string": { "version": "0.30.21", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", @@ -3962,6 +4601,15 @@ "integrity": "sha512-IEn+pegP1aManZuckezWCO+XZQDplx1366JoVhTpMpBB1sPey/SbveZQUosKiKiGYjg1wH4pMlNgXbCiYgihQA==", "license": "CC0-1.0" }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, "node_modules/micromark": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/micromark/-/micromark-4.0.2.tgz", @@ -4679,6 +5327,31 @@ ], "license": "MIT" }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/micromatch/node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/mrmime": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.1.tgz", @@ -4694,6 +5367,17 @@ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "license": "MIT" }, + "node_modules/mz": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", + "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", + "license": "MIT", + "dependencies": { + "any-promise": "^1.0.0", + "object-assign": "^4.0.1", + "thenify-all": "^1.0.0" + } + }, "node_modules/nanoid": { "version": "3.3.11", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", @@ -4773,6 +5457,24 @@ "url": "https://github.com/fb55/nth-check?sponsor=1" } }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-hash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", + "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, "node_modules/ofetch": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/ofetch/-/ofetch-1.5.1.tgz", @@ -4797,14 +5499,14 @@ "license": "MIT" }, "node_modules/oniguruma-to-es": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/oniguruma-to-es/-/oniguruma-to-es-4.3.4.tgz", - "integrity": "sha512-3VhUGN3w2eYxnTzHn+ikMI+fp/96KoRSVK9/kMTcFqj1NRDh2IhQCKvYxDnWePKRXY/AqH+Fuiyb7VHSzBjHfA==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/oniguruma-to-es/-/oniguruma-to-es-2.3.0.tgz", + "integrity": "sha512-bwALDxriqfKGfUufKGGepCzu9x7nJQuoRoAFp4AnwehhC2crqrDIAP/uN2qdlsAvSMpeRC3+Yzhqc7hLmle5+g==", "license": "MIT", "dependencies": { - "oniguruma-parser": "^0.12.1", - "regex": "^6.0.1", - "regex-recursion": "^6.0.2" + "emoji-regex-xs": "^1.0.0", + "regex": "^5.1.1", + "regex-recursion": "^5.1.1" } }, "node_modules/p-limit": { @@ -4911,6 +5613,12 @@ "url": "https://github.com/inikulin/parse5?sponsor=1" } }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "license": "MIT" + }, "node_modules/piccolore": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/piccolore/-/piccolore-0.1.3.tgz", @@ -4935,6 +5643,24 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pirates": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz", + "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==", + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, "node_modules/postcss": { "version": "8.5.6", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", @@ -4963,6 +5689,148 @@ "node": "^10 || ^12 || >=14" } }, + "node_modules/postcss-import": { + "version": "15.1.0", + "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz", + "integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==", + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.0.0", + "read-cache": "^1.0.0", + "resolve": "^1.1.7" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "postcss": "^8.0.0" + } + }, + "node_modules/postcss-js": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.1.0.tgz", + "integrity": "sha512-oIAOTqgIo7q2EOwbhb8UalYePMvYoIeRY2YKntdpFQXNosSu3vLrniGgmH9OKs/qAkfoj5oB3le/7mINW1LCfw==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "camelcase-css": "^2.0.1" + }, + "engines": { + "node": "^12 || ^14 || >= 16" + }, + "peerDependencies": { + "postcss": "^8.4.21" + } + }, + "node_modules/postcss-load-config": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-6.0.1.tgz", + "integrity": "sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "lilconfig": "^3.1.1" + }, + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "jiti": ">=1.21.0", + "postcss": ">=8.0.9", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + }, + "postcss": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/postcss-nested": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.2.0.tgz", + "integrity": "sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "postcss-selector-parser": "^6.1.1" + }, + "engines": { + "node": ">=12.0" + }, + "peerDependencies": { + "postcss": "^8.2.14" + } + }, + "node_modules/postcss-nested/node_modules/postcss-selector-parser": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz", + "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==", + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-selector-parser": { + "version": "6.0.10", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.10.tgz", + "integrity": "sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==", + "dev": true, + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", + "license": "MIT" + }, "node_modules/prismjs": { "version": "1.30.0", "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.30.0.tgz", @@ -4995,6 +5863,26 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, "node_modules/radix3": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/radix3/-/radix3-1.1.2.tgz", @@ -5031,6 +5919,15 @@ "node": ">=0.10.0" } }, + "node_modules/read-cache": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", + "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==", + "license": "MIT", + "dependencies": { + "pify": "^2.3.0" + } + }, "node_modules/readdirp": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", @@ -5112,20 +6009,21 @@ } }, "node_modules/regex": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/regex/-/regex-6.1.0.tgz", - "integrity": "sha512-6VwtthbV4o/7+OaAF9I5L5V3llLEsoPyq9P1JVXkedTP33c7MfCG0/5NOPcSJn0TzXcG9YUrR0gQSWioew3LDg==", + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/regex/-/regex-5.1.1.tgz", + "integrity": "sha512-dN5I359AVGPnwzJm2jN1k0W9LPZ+ePvoOeVMMfqIMFz53sSwXkxaJoxr50ptnsC771lK95BnTrVSZxq0b9yCGw==", "license": "MIT", "dependencies": { "regex-utilities": "^2.3.0" } }, "node_modules/regex-recursion": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/regex-recursion/-/regex-recursion-6.0.2.tgz", - "integrity": "sha512-0YCaSCq2VRIebiaUviZNs0cBz1kg5kVS2UKUfNIx8YVs1cN3AV7NTctO5FOKBA+UT2BPJIWZauYHPqJODG50cg==", + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/regex-recursion/-/regex-recursion-5.1.1.tgz", + "integrity": "sha512-ae7SBCbzVNrIjgSbh7wMznPcQel1DNlDtzensnFxpiNpXt1U2ju/bHugH422r+4LAVS1FpW1YCwilmnNsjum9w==", "license": "MIT", "dependencies": { + "regex": "^5.1.1", "regex-utilities": "^2.3.0" } }, @@ -5306,6 +6204,36 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/resolve": { + "version": "1.22.11", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz", + "integrity": "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==", + "license": "MIT", + "dependencies": { + "is-core-module": "^2.16.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "devOptional": true, + "license": "MIT", + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } + }, "node_modules/retext": { "version": "9.0.0", "resolved": "https://registry.npmjs.org/retext/-/retext-9.0.0.tgz", @@ -5367,6 +6295,16 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, "node_modules/rollup": { "version": "4.55.1", "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.55.1.tgz", @@ -5411,6 +6349,29 @@ "fsevents": "~2.3.2" } }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, "node_modules/sax": { "version": "1.4.4", "resolved": "https://registry.npmjs.org/sax/-/sax-1.4.4.tgz", @@ -5484,18 +6445,18 @@ } }, "node_modules/shiki": { - "version": "3.21.0", - "resolved": "https://registry.npmjs.org/shiki/-/shiki-3.21.0.tgz", - "integrity": "sha512-N65B/3bqL/TI2crrXr+4UivctrAGEjmsib5rPMMPpFp1xAx/w03v8WZ9RDDFYteXoEgY7qZ4HGgl5KBIu1153w==", + "version": "1.29.2", + "resolved": "https://registry.npmjs.org/shiki/-/shiki-1.29.2.tgz", + "integrity": "sha512-njXuliz/cP+67jU2hukkxCNuH1yUi4QfdZZY+sMr5PPrIyXSu5iTb/qYC4BiWWB0vZ+7TbdvYUCeL23zpwCfbg==", "license": "MIT", "dependencies": { - "@shikijs/core": "3.21.0", - "@shikijs/engine-javascript": "3.21.0", - "@shikijs/engine-oniguruma": "3.21.0", - "@shikijs/langs": "3.21.0", - "@shikijs/themes": "3.21.0", - "@shikijs/types": "3.21.0", - "@shikijs/vscode-textmate": "^10.0.2", + "@shikijs/core": "1.29.2", + "@shikijs/engine-javascript": "1.29.2", + "@shikijs/engine-oniguruma": "1.29.2", + "@shikijs/langs": "1.29.2", + "@shikijs/themes": "1.29.2", + "@shikijs/types": "1.29.2", + "@shikijs/vscode-textmate": "^10.0.1", "@types/hast": "^3.0.4" } }, @@ -5609,6 +6570,49 @@ "inline-style-parser": "0.2.7" } }, + "node_modules/sucrase": { + "version": "3.35.1", + "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.1.tgz", + "integrity": "sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw==", + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.2", + "commander": "^4.0.0", + "lines-and-columns": "^1.1.6", + "mz": "^2.7.0", + "pirates": "^4.0.1", + "tinyglobby": "^0.2.11", + "ts-interface-checker": "^0.1.9" + }, + "bin": { + "sucrase": "bin/sucrase", + "sucrase-node": "bin/sucrase-node" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/sucrase/node_modules/commander": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", + "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/svgo": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/svgo/-/svgo-4.0.0.tgz", @@ -5634,6 +6638,137 @@ "url": "https://opencollective.com/svgo" } }, + "node_modules/tailwindcss": { + "version": "3.4.19", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.19.tgz", + "integrity": "sha512-3ofp+LL8E+pK/JuPLPggVAIaEuhvIz4qNcf3nA1Xn2o/7fb7s/TYpHhwGDv1ZU3PkBluUVaF8PyCHcm48cKLWQ==", + "license": "MIT", + "dependencies": { + "@alloc/quick-lru": "^5.2.0", + "arg": "^5.0.2", + "chokidar": "^3.6.0", + "didyoumean": "^1.2.2", + "dlv": "^1.1.3", + "fast-glob": "^3.3.2", + "glob-parent": "^6.0.2", + "is-glob": "^4.0.3", + "jiti": "^1.21.7", + "lilconfig": "^3.1.3", + "micromatch": "^4.0.8", + "normalize-path": "^3.0.0", + "object-hash": "^3.0.0", + "picocolors": "^1.1.1", + "postcss": "^8.4.47", + "postcss-import": "^15.1.0", + "postcss-js": "^4.0.1", + "postcss-load-config": "^4.0.2 || ^5.0 || ^6.0", + "postcss-nested": "^6.2.0", + "postcss-selector-parser": "^6.1.2", + "resolve": "^1.22.8", + "sucrase": "^3.35.0" + }, + "bin": { + "tailwind": "lib/cli.js", + "tailwindcss": "lib/cli.js" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tailwindcss/node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "license": "MIT", + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/tailwindcss/node_modules/chokidar/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/tailwindcss/node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/tailwindcss/node_modules/postcss-selector-parser": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz", + "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==", + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/tailwindcss/node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "license": "MIT", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/thenify": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", + "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", + "license": "MIT", + "dependencies": { + "any-promise": "^1.0.0" + } + }, + "node_modules/thenify-all": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", + "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==", + "license": "MIT", + "dependencies": { + "thenify": ">= 3.1.0 < 4" + }, + "engines": { + "node": ">=0.8" + } + }, "node_modules/tiny-inflate": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/tiny-inflate/-/tiny-inflate-1.0.3.tgz", @@ -5665,6 +6800,18 @@ "url": "https://github.com/sponsors/SuperchupuDev" } }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, "node_modules/trim-lines": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz", @@ -5685,6 +6832,12 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/ts-interface-checker": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", + "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==", + "license": "Apache-2.0" + }, "node_modules/tsconfck": { "version": "3.1.6", "resolved": "https://registry.npmjs.org/tsconfck/-/tsconfck-3.1.6.tgz", @@ -5712,6 +6865,510 @@ "license": "0BSD", "optional": true }, + "node_modules/tsx": { + "version": "4.21.0", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.21.0.tgz", + "integrity": "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "esbuild": "~0.27.0", + "get-tsconfig": "^4.7.5" + }, + "bin": { + "tsx": "dist/cli.mjs" + }, + "engines": { + "node": ">=18.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + } + }, + "node_modules/tsx/node_modules/@esbuild/aix-ppc64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.2.tgz", + "integrity": "sha512-GZMB+a0mOMZs4MpDbj8RJp4cw+w1WV5NYD6xzgvzUJ5Ek2jerwfO2eADyI6ExDSUED+1X8aMbegahsJi+8mgpw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/android-arm": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.2.tgz", + "integrity": "sha512-DVNI8jlPa7Ujbr1yjU2PfUSRtAUZPG9I1RwW4F4xFB1Imiu2on0ADiI/c3td+KmDtVKNbi+nffGDQMfcIMkwIA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/android-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.2.tgz", + "integrity": "sha512-pvz8ZZ7ot/RBphf8fv60ljmaoydPU12VuXHImtAs0XhLLw+EXBi2BLe3OYSBslR4rryHvweW5gmkKFwTiFy6KA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/android-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.2.tgz", + "integrity": "sha512-z8Ank4Byh4TJJOh4wpz8g2vDy75zFL0TlZlkUkEwYXuPSgX8yzep596n6mT7905kA9uHZsf/o2OJZubl2l3M7A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/darwin-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.2.tgz", + "integrity": "sha512-davCD2Zc80nzDVRwXTcQP/28fiJbcOwvdolL0sOiOsbwBa72kegmVU0Wrh1MYrbuCL98Omp5dVhQFWRKR2ZAlg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/darwin-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.2.tgz", + "integrity": "sha512-ZxtijOmlQCBWGwbVmwOF/UCzuGIbUkqB1faQRf5akQmxRJ1ujusWsb3CVfk/9iZKr2L5SMU5wPBi1UWbvL+VQA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/freebsd-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.2.tgz", + "integrity": "sha512-lS/9CN+rgqQ9czogxlMcBMGd+l8Q3Nj1MFQwBZJyoEKI50XGxwuzznYdwcav6lpOGv5BqaZXqvBSiB/kJ5op+g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/freebsd-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.2.tgz", + "integrity": "sha512-tAfqtNYb4YgPnJlEFu4c212HYjQWSO/w/h/lQaBK7RbwGIkBOuNKQI9tqWzx7Wtp7bTPaGC6MJvWI608P3wXYA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-arm": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.2.tgz", + "integrity": "sha512-vWfq4GaIMP9AIe4yj1ZUW18RDhx6EPQKjwe7n8BbIecFtCQG4CfHGaHuh7fdfq+y3LIA2vGS/o9ZBGVxIDi9hw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.2.tgz", + "integrity": "sha512-hYxN8pr66NsCCiRFkHUAsxylNOcAQaxSSkHMMjcpx0si13t1LHFphxJZUiGwojB1a/Hd5OiPIqDdXONia6bhTw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-ia32": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.2.tgz", + "integrity": "sha512-MJt5BRRSScPDwG2hLelYhAAKh9imjHK5+NE/tvnRLbIqUWa+0E9N4WNMjmp/kXXPHZGqPLxggwVhz7QP8CTR8w==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-loong64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.2.tgz", + "integrity": "sha512-lugyF1atnAT463aO6KPshVCJK5NgRnU4yb3FUumyVz+cGvZbontBgzeGFO1nF+dPueHD367a2ZXe1NtUkAjOtg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-mips64el": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.2.tgz", + "integrity": "sha512-nlP2I6ArEBewvJ2gjrrkESEZkB5mIoaTswuqNFRv/WYd+ATtUpe9Y09RnJvgvdag7he0OWgEZWhviS1OTOKixw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-ppc64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.2.tgz", + "integrity": "sha512-C92gnpey7tUQONqg1n6dKVbx3vphKtTHJaNG2Ok9lGwbZil6DrfyecMsp9CrmXGQJmZ7iiVXvvZH6Ml5hL6XdQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-riscv64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.2.tgz", + "integrity": "sha512-B5BOmojNtUyN8AXlK0QJyvjEZkWwy/FKvakkTDCziX95AowLZKR6aCDhG7LeF7uMCXEJqwa8Bejz5LTPYm8AvA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-s390x": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.2.tgz", + "integrity": "sha512-p4bm9+wsPwup5Z8f4EpfN63qNagQ47Ua2znaqGH6bqLlmJ4bx97Y9JdqxgGZ6Y8xVTixUnEkoKSHcpRlDnNr5w==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.2.tgz", + "integrity": "sha512-uwp2Tip5aPmH+NRUwTcfLb+W32WXjpFejTIOWZFw/v7/KnpCDKG66u4DLcurQpiYTiYwQ9B7KOeMJvLCu/OvbA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/netbsd-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.2.tgz", + "integrity": "sha512-Kj6DiBlwXrPsCRDeRvGAUb/LNrBASrfqAIok+xB0LxK8CHqxZ037viF13ugfsIpePH93mX7xfJp97cyDuTZ3cw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/netbsd-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.2.tgz", + "integrity": "sha512-HwGDZ0VLVBY3Y+Nw0JexZy9o/nUAWq9MlV7cahpaXKW6TOzfVno3y3/M8Ga8u8Yr7GldLOov27xiCnqRZf0tCA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/openbsd-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.2.tgz", + "integrity": "sha512-DNIHH2BPQ5551A7oSHD0CKbwIA/Ox7+78/AWkbS5QoRzaqlev2uFayfSxq68EkonB+IKjiuxBFoV8ESJy8bOHA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/openbsd-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.2.tgz", + "integrity": "sha512-/it7w9Nb7+0KFIzjalNJVR5bOzA9Vay+yIPLVHfIQYG/j+j9VTH84aNB8ExGKPU4AzfaEvN9/V4HV+F+vo8OEg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/openharmony-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.2.tgz", + "integrity": "sha512-LRBbCmiU51IXfeXk59csuX/aSaToeG7w48nMwA6049Y4J4+VbWALAuXcs+qcD04rHDuSCSRKdmY63sruDS5qag==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/sunos-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.2.tgz", + "integrity": "sha512-kMtx1yqJHTmqaqHPAzKCAkDaKsffmXkPHThSfRwZGyuqyIeBvf08KSsYXl+abf5HDAPMJIPnbBfXvP2ZC2TfHg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/win32-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.2.tgz", + "integrity": "sha512-Yaf78O/B3Kkh+nKABUF++bvJv5Ijoy9AN1ww904rOXZFLWVc5OLOfL56W+C8F9xn5JQZa3UX6m+IktJnIb1Jjg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/win32-ia32": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.2.tgz", + "integrity": "sha512-Iuws0kxo4yusk7sw70Xa2E2imZU5HoixzxfGCdxwBdhiDgt9vX9VUCBhqcwY7/uh//78A1hMkkROMJq9l27oLQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/win32-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.2.tgz", + "integrity": "sha512-sRdU18mcKf7F+YgheI/zGf5alZatMUTKj/jNS6l744f9u3WFu4v7twcUI9vu4mknF4Y9aDlblIie0IM+5xxaqQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/esbuild": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.2.tgz", + "integrity": "sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw==", + "devOptional": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.27.2", + "@esbuild/android-arm": "0.27.2", + "@esbuild/android-arm64": "0.27.2", + "@esbuild/android-x64": "0.27.2", + "@esbuild/darwin-arm64": "0.27.2", + "@esbuild/darwin-x64": "0.27.2", + "@esbuild/freebsd-arm64": "0.27.2", + "@esbuild/freebsd-x64": "0.27.2", + "@esbuild/linux-arm": "0.27.2", + "@esbuild/linux-arm64": "0.27.2", + "@esbuild/linux-ia32": "0.27.2", + "@esbuild/linux-loong64": "0.27.2", + "@esbuild/linux-mips64el": "0.27.2", + "@esbuild/linux-ppc64": "0.27.2", + "@esbuild/linux-riscv64": "0.27.2", + "@esbuild/linux-s390x": "0.27.2", + "@esbuild/linux-x64": "0.27.2", + "@esbuild/netbsd-arm64": "0.27.2", + "@esbuild/netbsd-x64": "0.27.2", + "@esbuild/openbsd-arm64": "0.27.2", + "@esbuild/openbsd-x64": "0.27.2", + "@esbuild/openharmony-arm64": "0.27.2", + "@esbuild/sunos-x64": "0.27.2", + "@esbuild/win32-arm64": "0.27.2", + "@esbuild/win32-ia32": "0.27.2", + "@esbuild/win32-x64": "0.27.2" + } + }, "node_modules/type-fest": { "version": "4.41.0", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz", @@ -5756,6 +7413,13 @@ "integrity": "sha512-Ql87qFHB3s/De2ClA9e0gsnS6zXG27SkTiSJwjCc9MebbfapQfuPzumMIUMi38ezPZVNFcHI9sUIepeQfw8J8Q==", "license": "MIT" }, + "node_modules/undici-types": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", + "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", + "devOptional": true, + "license": "MIT" + }, "node_modules/unified": { "version": "11.0.5", "resolved": "https://registry.npmjs.org/unified/-/unified-11.0.5.tgz", @@ -6048,6 +7712,12 @@ "browserslist": ">= 4.21.0" } }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "license": "MIT" + }, "node_modules/vfile": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.3.tgz", @@ -6246,6 +7916,21 @@ "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", "license": "ISC" }, + "node_modules/yaml": { + "version": "2.8.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.2.tgz", + "integrity": "sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A==", + "license": "ISC", + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14.6" + }, + "funding": { + "url": "https://github.com/sponsors/eemeli" + } + }, "node_modules/yargs-parser": { "version": "21.1.1", "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", diff --git a/package.json b/package.json index 7852b08..7f731b9 100644 --- a/package.json +++ b/package.json @@ -7,15 +7,29 @@ "dev": "astro dev", "build": "astro build", "preview": "astro preview", - "astro": "astro" + "astro": "astro", + "test": "npm run test:smoke", + "test:smoke": "tsx ./scripts/smoke-test.ts", + "test:links": "tsx ./scripts/test-links.ts" }, "dependencies": { "@astrojs/mdx": "^4.3.13", "@astrojs/react": "^4.4.2", + "@astrojs/tailwind": "^6.0.2", "@types/react": "^19.2.8", "@types/react-dom": "^19.2.3", "astro": "^5.16.8", + "lucide-react": "^0.468.0", "react": "^19.2.3", - "react-dom": "^19.2.3" + "react-dom": "^19.2.3", + "shiki": "^1.24.2", + "tailwindcss": "^3.4.0" + }, + "devDependencies": { + "@tailwindcss/typography": "^0.5.15", + "@types/node": "^25.0.6", + "autoprefixer": "^10.4.20", + "postcss": "^8.4.49", + "tsx": "^4.21.0" } } diff --git a/postcss.config.js b/postcss.config.js new file mode 100644 index 0000000..e99ebc2 --- /dev/null +++ b/postcss.config.js @@ -0,0 +1,6 @@ +export default { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, +} \ No newline at end of file diff --git a/scripts/smoke-test.ts b/scripts/smoke-test.ts new file mode 100644 index 0000000..14f3997 --- /dev/null +++ b/scripts/smoke-test.ts @@ -0,0 +1,576 @@ +#!/usr/bin/env tsx + +/** + * Simple smoke test for the blog + * Tests: Build succeeds, key files exist, data is valid + */ + +import { execSync } from 'child_process'; +import fs from 'fs'; +import path from 'path'; + +console.log('🔍 Running smoke tests for mintel.me blog...\n'); + +let passed = 0; +let failed = 0; + +function test(name: string, fn: () => void): void { + try { + fn(); + console.log(`✅ ${name}`); + passed++; + } catch (error) { + console.log(`❌ ${name}`); + if (error instanceof Error) { + console.log(` Error: ${error.message}`); + } + failed++; + } +} + +// Test 1: Check required files exist +test('Required files exist', () => { + const requiredFiles = [ + 'package.json', + 'astro.config.mjs', + 'tailwind.config.js', + 'src/pages/index.astro', + 'src/layouts/BaseLayout.astro', + 'src/data/blogPosts.ts', + 'src/styles/global.css' + ]; + + for (const file of requiredFiles) { + if (!fs.existsSync(path.join(process.cwd(), file))) { + throw new Error(`Missing file: ${file}`); + } + } +}); + +// Test 2: Validate blog posts data +test('Blog posts data is valid', () => { + const blogPostsPath = path.join(process.cwd(), 'src/data/blogPosts.ts'); + const content = fs.readFileSync(blogPostsPath, 'utf8'); + + // Check for basic structure + if (!content.includes('export const blogPosts')) { + throw new Error('blogPosts export not found'); + } + + // Check for required fields in posts + const postsMatch = content.match(/\{[^}]+\}/g); + if (!postsMatch || postsMatch.length === 0) { + throw new Error('No posts found in blogPosts.ts'); + } + + // Validate at least one post has required fields + const firstPost = postsMatch[0]; + const requiredFields = ['title', 'description', 'date', 'slug', 'tags']; + + for (const field of requiredFields) { + if (!firstPost.includes(field)) { + throw new Error(`First post missing required field: ${field}`); + } + } +}); + +// Test 3: Check Astro config +test('Astro configuration is valid', () => { + const configPath = path.join(process.cwd(), 'astro.config.mjs'); + const content = fs.readFileSync(configPath, 'utf8'); + + if (!content.includes('site:')) { + throw new Error('site URL not configured'); + } + + if (!content.includes('react()')) { + throw new Error('React integration not configured'); + } +}); + +// Test 4: Validate Tailwind config +test('Tailwind configuration is valid', () => { + const configPath = path.join(process.cwd(), 'tailwind.config.js'); + const content = fs.readFileSync(configPath, 'utf8'); + + if (!content.includes('content:')) { + throw new Error('content paths not configured'); + } + + if (!content.includes('plugins:')) { + throw new Error('plugins not configured'); + } +}); + +// Test 5: Check for syntax errors in key components +test('Key components have valid syntax', () => { + const components = [ + 'src/components/MediumCard.tsx', + 'src/components/SearchBar.tsx', + 'src/components/ArticleBlockquote.tsx' + ]; + + for (const component of components) { + const componentPath = path.join(process.cwd(), component); + if (fs.existsSync(componentPath)) { + const content = fs.readFileSync(componentPath, 'utf8'); + + // Basic syntax checks + if (content.includes('import') && !content.includes('export')) { + throw new Error(`${component}: has imports but no exports`); + } + + // Check for balanced braces + const openBraces = (content.match(/{/g) || []).length; + const closeBraces = (content.match(/}/g) || []).length; + if (openBraces !== closeBraces) { + throw new Error(`${component}: unbalanced braces`); + } + } + } +}); + +// Test 6: Check global styles +test('Global styles include required classes', () => { + const stylesPath = path.join(process.cwd(), 'src/styles/global.css'); + const content = fs.readFileSync(stylesPath, 'utf8'); + + const requiredClasses = [ + '.container', + '.highlighter-tag', + '.post-card', + '.article-content', + '.search-box' + ]; + + for (const className of requiredClasses) { + if (!content.includes(className)) { + throw new Error(`Missing required class: ${className}`); + } + } +}); + +// Test 7: Verify package.json scripts +test('Package.json has required scripts', () => { + const packagePath = path.join(process.cwd(), 'package.json'); + const content = fs.readFileSync(packagePath, 'utf8'); + + const requiredScripts = ['dev', 'build', 'preview', 'test:smoke']; + + for (const script of requiredScripts) { + if (!content.includes(`"${script}"`)) { + throw new Error(`Missing script: ${script}`); + } + } +}); + +// Test 8: Check for single-page blog functionality +test('Single-page blog functionality exists', () => { + const functionalityFiles = [ + 'src/pages/index.astro', // Single page with everything + 'src/components/SearchBar.tsx', // Search component + 'src/components/MediumCard.tsx' // Post cards + ]; + + for (const file of functionalityFiles) { + const filePath = path.join(process.cwd(), file); + if (!fs.existsSync(filePath)) { + throw new Error(`Missing functionality file: ${file}`); + } + } +}); + +// Test 9: Check that all blog posts load in the home page +test('All blog posts load in home page', () => { + const homePagePath = path.join(process.cwd(), 'src/pages/index.astro'); + const homeContent = fs.readFileSync(homePagePath, 'utf8'); + + // Check that it imports blogPosts + if (!homeContent.includes('import') || !homeContent.includes('blogPosts')) { + throw new Error('Home page does not import blogPosts'); + } + + // Check that it renders posts + if (!homeContent.includes('allPosts.map') && !homeContent.includes('blogPosts.map')) { + throw new Error('Home page does not render blog posts'); + } + + // Check that MediumCard is imported and used + if (!homeContent.includes('MediumCard')) { + throw new Error('MediumCard component not used in home page'); + } +}); + +// Test 10: Verify blog posts have all required fields +test('Blog posts have all required fields', () => { + const blogPostsPath = path.join(process.cwd(), 'src/data/blogPosts.ts'); + const content = fs.readFileSync(blogPostsPath, 'utf8'); + + // Extract all posts + const posts = content.match(/\{[^}]+\}/g) || []; + + if (posts.length === 0) { + throw new Error('No posts found in blogPosts.ts'); + } + + // Check each post has required fields + const requiredFields = ['title', 'description', 'date', 'slug', 'tags']; + + posts.forEach((post, index) => { + for (const field of requiredFields) { + if (!post.includes(field)) { + throw new Error(`Post ${index + 1} missing required field: ${field}`); + } + } + }); +}); + +// Test 11: Verify individual blog post pages exist +test('Individual blog post pages exist', () => { + const blogDir = path.join(process.cwd(), 'src/pages/blog'); + const files = fs.readdirSync(blogDir); + + const astroFiles = files.filter(f => f.endsWith('.astro')); + + if (astroFiles.length === 0) { + throw new Error('No blog post files found'); + } + + // Check that [slug].astro exists for dynamic routing + if (!files.includes('[slug].astro')) { + throw new Error('Missing [slug].astro for dynamic blog post routing'); + } + + // Check that individual post files exist + const requiredPosts = ['first-note.astro', 'debugging-tips.astro']; + for (const post of requiredPosts) { + if (!astroFiles.includes(post)) { + throw new Error(`Missing blog post file: ${post}`); + } + } +}); + +// Test 12: Verify MediumCard links to individual posts +test('MediumCard links to individual post pages', () => { + const mediumCardPath = path.join(process.cwd(), 'src/components/MediumCard.tsx'); + const content = fs.readFileSync(mediumCardPath, 'utf8'); + + // Should contain href to blog posts + if (!content.includes('href={`/blog/')) { + throw new Error('MediumCard should contain links to individual post pages'); + } + + // Should contain tags + if (!content.includes('')) { + throw new Error('MediumCard should contain anchor tags'); + } + + // Should also have tag filtering + if (!content.includes('onClick')) { + throw new Error('MediumCard should have onClick handlers for tags'); + } +}); + +// Test 13: Verify home page has no navigation +test('Home page has no navigation links', () => { + const homePagePath = path.join(process.cwd(), 'src/pages/index.astro'); + const content = fs.readFileSync(homePagePath, 'utf8'); + + // Should not contain navigation links + if (content.includes('href="/about"') || content.includes('href="/blog"')) { + throw new Error('Home page contains navigation links'); + } + + // Should not contain nav or header navigation + if (content.includes(' { + const blogDir = path.join(process.cwd(), 'src/pages/blog'); + const files = fs.readdirSync(blogDir); + + const astroFiles = files.filter(f => f.endsWith('.astro') && f !== '[slug].astro'); + + for (const file of astroFiles) { + const content = fs.readFileSync(path.join(blogDir, file), 'utf8'); + + if (content.includes('import BlogLayout')) { + throw new Error(`${file} still imports BlogLayout which doesn't exist`); + } + + if (!content.includes('import BaseLayout')) { + throw new Error(`${file} doesn't import BaseLayout`); + } + } +}); + +// Test 15: Verify blog post pages have proper structure +test('Blog posts have proper structure', () => { + const blogDir = path.join(process.cwd(), 'src/pages/blog'); + const files = fs.readdirSync(blogDir); + + const astroFiles = files.filter(f => f.endsWith('.astro') && f !== '[slug].astro'); + + for (const file of astroFiles) { + const content = fs.readFileSync(path.join(blogDir, file), 'utf8'); + + // Should close BaseLayout properly + if (!content.includes('')) { + throw new Error(`${file} doesn't close BaseLayout properly`); + } + + // Should have article wrapper + if (!content.includes('')) { + throw new Error(`${file} missing header section`); + } + } +}); + +// Test 16: Verify all imports in blog posts are valid +test('All imports in blog posts are valid', () => { + const blogDir = path.join(process.cwd(), 'src/pages/blog'); + const files = fs.readdirSync(blogDir); + + const astroFiles = files.filter(f => f.endsWith('.astro') && f !== '[slug].astro'); + + for (const file of astroFiles) { + const content = fs.readFileSync(path.join(blogDir, file), 'utf8'); + + // Extract all imports + const importMatches = content.match(/import\s+.*?\s+from\s+['"]([^'"]+)['"]/g) || []; + + for (const importLine of importMatches) { + // Extract the path + const pathMatch = importLine.match(/from\s+['"]([^'"]+)['"]/); + if (!pathMatch) continue; + + const importPath = pathMatch[1]; + + // Skip relative imports for now, just check they don't reference BlogLayout + if (importPath.includes('BlogLayout')) { + throw new Error(`${file} imports BlogLayout which doesn't exist`); + } + + // Check that BaseLayout is imported correctly + if (importPath.includes('BaseLayout') && !importPath.includes('../../layouts/BaseLayout')) { + throw new Error(`${file} has incorrect BaseLayout import path`); + } + } + } +}); + +// Test 17: Verify tag pages exist and are valid +test('Tag pages exist and are valid', () => { + const tagPagePath = path.join(process.cwd(), 'src/pages/tags/[tag].astro'); + + if (!fs.existsSync(tagPagePath)) { + throw new Error('Tag page [tag].astro does not exist'); + } + + const content = fs.readFileSync(tagPagePath, 'utf8'); + + // Should import BaseLayout + if (!content.includes('import BaseLayout')) { + throw new Error('Tag page does not import BaseLayout'); + } + + // Should have getStaticPaths + if (!content.includes('getStaticPaths')) { + throw new Error('Tag page missing getStaticPaths function'); + } + + // Should render posts + if (!content.includes('posts.map')) { + throw new Error('Tag page does not render posts'); + } +}); + +// Test 18: Verify [slug].astro page exists and is valid +test('Dynamic slug page exists and is valid', () => { + const slugPagePath = path.join(process.cwd(), 'src/pages/blog/[slug].astro'); + + if (!fs.existsSync(slugPagePath)) { + throw new Error('Dynamic slug page [slug].astro does not exist'); + } + + const content = fs.readFileSync(slugPagePath, 'utf8'); + + // Should import BaseLayout + if (!content.includes('import BaseLayout')) { + throw new Error('Slug page does not import BaseLayout'); + } + + // Should have getStaticPaths + if (!content.includes('getStaticPaths')) { + throw new Error('Slug page missing getStaticPaths function'); + } + + // Should render post content + if (!content.includes('post.title') || !content.includes('post.description')) { + throw new Error('Slug page does not render post data'); + } +}); + +// Test 19: Verify no syntax errors in any Astro files +test('No syntax errors in Astro files', () => { + const astroFiles = [ + 'src/pages/index.astro', + 'src/pages/blog/[slug].astro', + 'src/pages/blog/first-note.astro', + 'src/pages/blog/debugging-tips.astro', + 'src/pages/tags/[tag].astro', + 'src/layouts/BaseLayout.astro' + ]; + + for (const file of astroFiles) { + const filePath = path.join(process.cwd(), file); + if (!fs.existsSync(filePath)) { + throw new Error(`File ${file} does not exist`); + } + + const content = fs.readFileSync(filePath, 'utf8'); + + // Check for balanced HTML tags + const openTags = (content.match(/<[^/][^>]*>/g) || []).length; + const closeTags = (content.match(/<\/[^>]+>/g) || []).length; + + // Check for balanced braces in script sections + const scriptSections = content.match(/---[\s\S]*?---/g) || []; + let totalOpenBraces = 0; + let totalCloseBraces = 0; + + for (const section of scriptSections) { + totalOpenBraces += (section.match(/{/g) || []).length; + totalCloseBraces += (section.match(/}/g) || []).length; + } + + if (totalOpenBraces !== totalCloseBraces) { + throw new Error(`${file} has unbalanced braces in script section`); + } + } +}); + +// Test 20: Verify blogPosts.ts can be imported without errors +test('blogPosts.ts can be imported without errors', () => { + const blogPostsPath = path.join(process.cwd(), 'src/data/blogPosts.ts'); + const content = fs.readFileSync(blogPostsPath, 'utf8'); + + // Check for basic syntax + if (!content.includes('export const blogPosts')) { + throw new Error('blogPosts.ts does not export blogPosts'); + } + + // Check that it's valid TypeScript + if (content.includes('interface') || content.includes('type')) { + // If it has types, they should be properly formatted + const openBraces = (content.match(/{/g) || []).length; + const closeBraces = (content.match(/}/g) || []).length; + if (openBraces !== closeBraces) { + throw new Error('blogPosts.ts has unbalanced braces'); + } + } +}); + +// Test 21: Verify Astro build succeeds (catches runtime errors) +test('Astro build succeeds without runtime errors', () => { + try { + // Try to build the project - this will catch runtime errors + execSync('npm run build', { + cwd: process.cwd(), + stdio: 'pipe', + timeout: 60000 + }); + } catch (error: any) { + const stderr = error.stderr?.toString() || ''; + const stdout = error.stdout?.toString() || ''; + const combined = stderr + stdout; + + // Check for common runtime errors + if (combined.includes('children.trim is not a function')) { + throw new Error('Build failed: children.trim error - component children issue'); + } + + if (combined.includes('is not a function')) { + throw new Error(`Build failed with runtime error: ${combined.substring(0, 200)}`); + } + + if (combined.includes('Build failed')) { + throw new Error(`Build failed: ${combined.substring(0, 300)}`); + } + + // If it's just a warning, that's okay + if (combined.includes('warning')) { + return; + } + + throw new Error(`Build failed for unknown reason: ${combined.substring(0, 200)}`); + } +}); + +// Test 22: Verify all components can be imported +test('All components can be imported without errors', () => { + const components = [ + 'src/components/MediumCard.tsx', + 'src/components/SearchBar.tsx', + 'src/components/ArticleBlockquote.tsx', + 'src/components/ArticleHeading.tsx', + 'src/components/ArticleParagraph.tsx', + 'src/components/ArticleList.tsx', + 'src/components/Footer.tsx' + ]; + + for (const component of components) { + const componentPath = path.join(process.cwd(), component); + if (!fs.existsSync(componentPath)) { + throw new Error(`Component ${component} does not exist`); + } + + const content = fs.readFileSync(componentPath, 'utf8'); + + // Check for common issues + if (content.includes('children.trim') && !content.includes('children?.trim')) { + throw new Error(`${component}: uses children.trim without null check`); + } + + // Extract JSX content (between return and end of component) + const jsxMatch = content.match(/return\s*\(([\s\S]*?)\)\s*;?\s*\}\s*$/); + if (jsxMatch) { + const jsxContent = jsxMatch[1]; + + // Only count actual HTML/JSX tags, not TypeScript types + // Filter out tags that are clearly TypeScript (like , , etc.) + const openTags = (jsxContent.match(/<([A-Za-z][A-Za-z0-9-]*)(\s|>)/g) || []).length; + const closeTags = (jsxContent.match(/<\/[A-Za-z][A-Za-z0-9-]*>/g) || []).length; + + if (openTags !== closeTags) { + throw new Error(`${component}: unbalanced JSX tags (open: ${openTags}, close: ${closeTags})`); + } + } + } +}); + +// Summary +console.log('\n' + '='.repeat(50)); +console.log(`Tests passed: ${passed}`); +console.log(`Tests failed: ${failed}`); +console.log('='.repeat(50)); + +if (failed === 0) { + console.log('\n🎉 All smoke tests passed! Your blog is ready to use.'); + console.log('\nTo start development:'); + console.log(' npm run dev'); + console.log('\nTo build for production:'); + console.log(' npm run build'); + process.exit(0); +} else { + console.log('\n❌ Some tests failed. Please check the errors above.'); + process.exit(1); +} \ No newline at end of file diff --git a/scripts/test-links.ts b/scripts/test-links.ts new file mode 100644 index 0000000..d1a88c1 --- /dev/null +++ b/scripts/test-links.ts @@ -0,0 +1,178 @@ +#!/usr/bin/env tsx + +/** + * Simple link checker for the blog + * Tests: All internal links work, no broken references + */ + +import fs from 'fs'; +import path from 'path'; + +console.log('🔗 Checking links and references...\n'); + +let passed = 0; +let failed = 0; + +function test(name: string, fn: () => void): void { + try { + fn(); + console.log(`✅ ${name}`); + passed++; + } catch (error) { + console.log(`❌ ${name}`); + if (error instanceof Error) { + console.log(` Error: ${error.message}`); + } + failed++; + } +} + +// Test 1: Check that all referenced blog posts exist +test('All blog post files exist', () => { + const blogDir = path.join(process.cwd(), 'src/pages/blog'); + const files = fs.readdirSync(blogDir); + + // Get all .astro files + const astroFiles = files.filter(f => f.endsWith('.astro')); + + if (astroFiles.length === 0) { + throw new Error('No blog post files found'); + } + + // Check blogPosts.ts references exist + const blogPostsPath = path.join(process.cwd(), 'src/data/blogPosts.ts'); + const content = fs.readFileSync(blogPostsPath, 'utf8'); + + // Extract slug references + const slugMatches = content.match(/slug:\s*['"]([^'"]+)['"]/g); + if (!slugMatches) { + throw new Error('No slugs found in blogPosts.ts'); + } + + const slugs = slugMatches.map(m => m.match(/['"]([^'"]+)['"]/)?.[1]); + + for (const slug of slugs) { + const expectedFile = `${slug}.astro`; + if (!astroFiles.includes(expectedFile)) { + throw new Error(`Blog post file missing: ${expectedFile} (referenced in blogPosts.ts)`); + } + } +}); + +// Test 2: Check that tag pages reference valid tags +test('Tag references are valid', () => { + const blogPostsPath = path.join(process.cwd(), 'src/data/blogPosts.ts'); + const content = fs.readFileSync(blogPostsPath, 'utf8'); + + // Extract tags + const tagsMatch = content.match(/tags:\s*\[([^\]]+)\]/); + if (!tagsMatch) { + throw new Error('No tags found in blogPosts.ts'); + } + + const tagsContent = tagsMatch[1]; + const tags = tagsContent.match(/['"]([^'"]+)['"]/g)?.map(t => t.replace(/['"]/g, '')); + + if (!tags || tags.length === 0) { + throw new Error('No tags extracted'); + } + + // Check that tag page exists + const tagPagePath = path.join(process.cwd(), 'src/pages/tags/[tag].astro'); + if (!fs.existsSync(tagPagePath)) { + throw new Error('Tag page template missing'); + } +}); + +// Test 3: Check component imports +test('All component imports are valid', () => { + const components = [ + 'src/components/MediumCard.tsx', + 'src/components/SearchBar.tsx', + 'src/components/ArticleBlockquote.tsx', + 'src/components/ArticleHeading.tsx', + 'src/components/ArticleList.tsx', + 'src/components/ArticleParagraph.tsx' + ]; + + for (const component of components) { + const componentPath = path.join(process.cwd(), component); + if (!fs.existsSync(componentPath)) { + throw new Error(`Component missing: ${component}`); + } + + // Check for export + const content = fs.readFileSync(componentPath, 'utf8'); + if (!content.includes('export')) { + throw new Error(`Component has no exports: ${component}`); + } + } +}); + +// Test 4: Check page structure +test('All required pages exist', () => { + const requiredPages = [ + 'src/pages/index.astro', + 'src/pages/about.astro', + 'src/pages/blog.astro', + 'src/pages/tags/[tag].astro' + ]; + + for (const page of requiredPages) { + const pagePath = path.join(process.cwd(), page); + if (!fs.existsSync(pagePath)) { + throw new Error(`Required page missing: ${page}`); + } + } +}); + +// Test 5: Check layout structure +test('Layout files are valid', () => { + const layouts = [ + 'src/layouts/BaseLayout.astro', + 'src/layouts/BlogLayout.astro' + ]; + + for (const layout of layouts) { + const layoutPath = path.join(process.cwd(), layout); + if (!fs.existsSync(layoutPath)) { + throw new Error(`Layout missing: ${layout}`); + } + + // Check for basic structure + const content = fs.readFileSync(layoutPath, 'utf8'); + if (!content.includes(' { + const baseLayoutPath = path.join(process.cwd(), 'src/layouts/BaseLayout.astro'); + const content = fs.readFileSync(baseLayoutPath, 'utf8'); + + if (!content.includes('global.css')) { + throw new Error('BaseLayout does not import global.css'); + } + + const globalCssPath = path.join(process.cwd(), 'src/styles/global.css'); + if (!fs.existsSync(globalCssPath)) { + throw new Error('Global CSS file missing'); + } +}); + +// Summary +console.log('\n' + '='.repeat(50)); +console.log(`Tests passed: ${passed}`); +console.log(`Tests failed: ${failed}`); +console.log('='.repeat(50)); + +if (failed === 0) { + console.log('\n🎉 All link checks passed! Your blog structure is solid.'); + console.log('\nYour blog is ready to use!'); + process.exit(0); +} else { + console.log('\n❌ Some checks failed. Please fix the errors above.'); + process.exit(1); +} \ No newline at end of file diff --git a/src/components/ArticleBlockquote.tsx b/src/components/ArticleBlockquote.tsx index b0989a6..2130bbd 100644 --- a/src/components/ArticleBlockquote.tsx +++ b/src/components/ArticleBlockquote.tsx @@ -11,16 +11,90 @@ export const Blockquote: React.FC = ({ children, className = '' ); -export const CodeBlock: React.FC<{ children: React.ReactNode; className?: string }> = ({ children, className = '' }) => ( -
-    
-      {children}
-    
-  
-); +interface CodeBlockProps { + children: string; + language?: string; + showLineNumbers?: boolean; + className?: string; +} + +// Simple syntax highlighting for common languages +const highlightCode = (code: string, language: string): string => { + code = code.trim(); + + const patterns: Record = { + comment: [/#[^\n]*/g, /\/\/[^\n]*/g, /\/\*[\s\S]*?\*\//g], + string: [/["'`][^"'`]*["'`]/g], + number: [/\b\d+\b/g], + keyword: [ + /\b(def|return|if|else|for|while|import|from|class|try|except|with|as|lambda|yield|async|await|pass|break|continue)\b/g, + /\b(function|const|let|var|if|else|for|while|return|import|export|class|new|try|catch|finally)\b/g, + /\b(package|import|class|public|private|protected|static|void|return|if|else|for|while|try|catch)\b/g + ], + function: [/\b[a-zA-Z_]\w*(?=\s*\()/g], + operator: [/[\+\-\*\/=<>!&|]+/g], + punctuation: [/[\[\]{}(),;.:]/g], + tag: [/<\/?[a-zA-Z][^>]*>/g], + attr: [/\b[a-zA-Z-]+(?=\=)/g], + attrValue: [/="[^"]*"/g], + }; + + let highlighted = code; + + const order = ['comment', 'string', 'number', 'keyword', 'function', 'operator', 'punctuation', 'tag', 'attr', 'attrValue']; + + order.forEach(type => { + patterns[type].forEach(pattern => { + highlighted = highlighted.replace(pattern, match => { + return `${match}`; + }); + }); + }); + + return highlighted; +}; + +export const CodeBlock: React.FC = ({ + children, + language = 'text', + showLineNumbers = false, + className = '' +}) => { + const code = (typeof children === 'string' ? children : String(children)).trim(); + const highlighted = language !== 'text' ? highlightCode(code, language) : code; + const lines = code.split('\n'); + + return ( +
+ {language !== 'text' && ( +
+ {language} +
+ )} +
+        {showLineNumbers ? (
+          
+
+ {lines.map((_, i) => ( +
{i + 1}
+ ))} +
+
+ +
+
+ ) : ( + + )} +
+
+ ); +}; export const InlineCode: React.FC<{ children: React.ReactNode; className?: string }> = ({ children, className = '' }) => ( - + {children} ); \ No newline at end of file diff --git a/src/components/BlogPostCard.tsx b/src/components/BlogPostCard.tsx deleted file mode 100644 index 382a651..0000000 --- a/src/components/BlogPostCard.tsx +++ /dev/null @@ -1,48 +0,0 @@ -import React from 'react'; - -interface BlogPostCardProps { - title: string; - description: string; - date: string; - slug: string; - tags?: string[]; -} - -export const BlogPostCard: React.FC = ({ - title, - description, - date, - slug, - tags = [] -}) => { - const formattedDate = new Date(date).toLocaleDateString('en-US', { - year: 'numeric', - month: 'long', - day: 'numeric' - }); - - return ( -
- ); -}; \ No newline at end of file diff --git a/src/components/Container.tsx b/src/components/Container.tsx deleted file mode 100644 index 7633ba7..0000000 --- a/src/components/Container.tsx +++ /dev/null @@ -1,23 +0,0 @@ -import React from 'react'; - -interface ContainerProps { - children: React.ReactNode; - className?: string; - maxWidth?: string; -} - -export const Container: React.FC = ({ - children, - className = '', - maxWidth = 'max-w-4xl' -}) => ( -
- {children} -
-); - -export const ArticleContainer: React.FC = ({ children, className = '' }) => ( - - {children} - -); \ No newline at end of file diff --git a/src/components/Footer.tsx b/src/components/Footer.tsx new file mode 100644 index 0000000..80deae8 --- /dev/null +++ b/src/components/Footer.tsx @@ -0,0 +1,36 @@ +import React from 'react'; + +export const Footer: React.FC = () => { + const currentYear = new Date().getFullYear(); + + return ( +
+
+ {/* Main footer content - all centered */} +
+
+
+ M +
+ Marc Mintel +
+ +

+ Write things down. Don't forget. Maybe help someone. +

+ +
+ © {currentYear} +
+
+ + {/* Subtle tagline */} +
+

+ A public notebook of digital problem solving +

+
+
+
+ ); +}; diff --git a/src/components/Hero.tsx b/src/components/Hero.tsx new file mode 100644 index 0000000..9d9136c --- /dev/null +++ b/src/components/Hero.tsx @@ -0,0 +1,54 @@ +import { BookOpen, Code2, Terminal, Wrench } from 'lucide-react'; +import React from 'react'; + +export const Hero: React.FC = () => { + return ( +
+ {/* Background pattern */} +
+
+
+ +
+
+ {/* Main heading */} +

+ Digital Problem Solver +

+ +

+ I work on Digital problems and build tools, scripts, and systems to solve them. +

+ + {/* Quick stats or focus areas */} +
+
+ +
Code
+
+
+ +
Tools
+
+
+ +
Automation
+
+
+ +
Learning
+
+
+ + {/* Topics */} +
+ Topics: Vibe coding with AI • Debugging • Mac tools • Automation • Small scripts • Learning notes • FOSS +
+
+
+
+ ); +}; \ No newline at end of file diff --git a/src/components/MediumCard.astro b/src/components/MediumCard.astro new file mode 100644 index 0000000..3825b7b --- /dev/null +++ b/src/components/MediumCard.astro @@ -0,0 +1,130 @@ +--- +import Tag from './Tag.astro'; + +interface Props { + post: { + title: string; + description: string; + date: string; + slug: string; + tags?: string[]; + }; +} + +const { post } = Astro.props; +const { title, description, date, slug, tags = [] } = post; + +const formattedDate = new Date(date).toLocaleDateString('en-US', { + month: 'short', + day: 'numeric', + year: 'numeric' +}); + +// Calculate reading time +const wordCount = description.split(/\s+/).length; +const readingTime = Math.max(1, Math.ceil(wordCount / 200)); +--- + +
+
+

+ {title} +

+ + {formattedDate} + +
+ +

+ {description} +

+ +
+ + {readingTime} min + + {tags.length > 0 && ( +
+ {tags.map((tag: string) => ( + + {tag} + + ))} +
+ )} +
+
+ + \ No newline at end of file diff --git a/src/components/SearchBar.tsx b/src/components/SearchBar.tsx new file mode 100644 index 0000000..9741a0e --- /dev/null +++ b/src/components/SearchBar.tsx @@ -0,0 +1,141 @@ +import React, { useState, useRef, useEffect } from 'react'; + +export const SearchBar: React.FC = () => { + const [query, setQuery] = useState(''); + const inputRef = useRef(null); + const [isFocused, setIsFocused] = useState(false); + + const handleInput = (e: React.ChangeEvent) => { + const value = e.target.value; + setQuery(value); + + // Trigger search functionality + if (typeof window !== 'undefined') { + const postsContainer = document.getElementById('posts-container'); + const allPosts = postsContainer?.querySelectorAll('.post-card') as NodeListOf; + + if (!allPosts) return; + + const queryLower = value.toLowerCase().trim(); + + allPosts.forEach((post: HTMLElement) => { + const title = post.querySelector('h3')?.textContent?.toLowerCase() || ''; + const description = post.querySelector('.post-excerpt')?.textContent?.toLowerCase() || ''; + const tags = Array.from(post.querySelectorAll('.highlighter-tag')) + .map(tag => tag.textContent?.toLowerCase() || '') + .join(' '); + + const searchableText = `${title} ${description} ${tags}`; + + if (searchableText.includes(queryLower) || queryLower === '') { + post.style.display = 'block'; + post.style.opacity = '1'; + post.style.transform = 'scale(1)'; + } else { + post.style.display = 'none'; + post.style.opacity = '0'; + post.style.transform = 'scale(0.95)'; + } + }); + } + }; + + const clearSearch = () => { + setQuery(''); + if (inputRef.current) { + inputRef.current.value = ''; + inputRef.current.focus(); + } + + // Reset all posts + if (typeof window !== 'undefined') { + const postsContainer = document.getElementById('posts-container'); + const allPosts = postsContainer?.querySelectorAll('.post-card') as NodeListOf; + + allPosts?.forEach((post: HTMLElement) => { + post.style.display = 'block'; + post.style.opacity = '1'; + post.style.transform = 'scale(1)'; + }); + } + }; + + const handleKeyDown = (e: React.KeyboardEvent) => { + if (e.key === 'Escape') { + clearSearch(); + } + }; + + return ( +
+
+ {/* Search input wrapper */} +
+ setIsFocused(true)} + onBlur={() => setIsFocused(false)} + aria-label="Search blog posts" + /> + + {/* Search icon */} +
+ + + +
+ + {/* Clear button */} + {query && ( + + )} +
+ + {/* Search hint */} +
+ ESC + to clear +
+
+ + {/* Active filter indicator */} + {query && ( +
+ + Searching for: "{query}" + + +
+ )} + + {/* Results count (hidden by default, shown via JS) */} +
+
+ ); +}; \ No newline at end of file diff --git a/src/components/Tag.astro b/src/components/Tag.astro new file mode 100644 index 0000000..bd062e2 --- /dev/null +++ b/src/components/Tag.astro @@ -0,0 +1,125 @@ +--- +interface Props { + tag: string; + index: number; + className?: string; +} + +const { tag, index, className = '' } = Astro.props; + +// Color mapping based on tag content for variety +const getColorClass = (tag: string) => { + const tagLower = tag.toLowerCase(); + if (tagLower.includes('meta') || tagLower.includes('learning')) return 'highlighter-yellow'; + if (tagLower.includes('debug') || tagLower.includes('tools')) return 'highlighter-pink'; + if (tagLower.includes('ai') || tagLower.includes('automation')) return 'highlighter-blue'; + if (tagLower.includes('script') || tagLower.includes('code')) return 'highlighter-green'; + return 'highlighter-yellow'; // default +}; + +const colorClass = getColorClass(tag); +--- + + + {tag} + + + + + + + + + + + \ No newline at end of file diff --git a/src/layouts/BaseLayout.astro b/src/layouts/BaseLayout.astro index 2c08d7a..73d5c8e 100644 --- a/src/layouts/BaseLayout.astro +++ b/src/layouts/BaseLayout.astro @@ -1,18 +1,14 @@ --- +import '../styles/global.css'; +import { Footer } from '../components/Footer'; +import { Hero } from '../components/Hero'; + interface Props { title: string; description?: string; } const { title, description = "Technical problem solver's blog - practical insights and learning notes" } = Astro.props; - -// About info from context -const aboutInfo = { - name: "Marc Mintel", - role: "Technical problem solver", - email: "marc@mintel.me", - location: "Vulkaneifel, Germany" -}; --- @@ -20,81 +16,166 @@ const aboutInfo = { - {title} | {aboutInfo.name} + {title} | Marc Mintel + - + - -
- -
-
-
-
-

- {aboutInfo.name} -

-

{aboutInfo.role}

-
- -
-
-
- + + +
-
+
-
-
-

- A public notebook of things I figured out, mistakes I made, and tools I tested. -

-
-
+
+ + + + + + + + +
- - \ No newline at end of file + diff --git a/src/layouts/BlogLayout.astro b/src/layouts/BlogLayout.astro deleted file mode 100644 index 91bde3b..0000000 --- a/src/layouts/BlogLayout.astro +++ /dev/null @@ -1,133 +0,0 @@ ---- -import type { CollectionEntry } from 'astro:content'; -import BaseLayout from './BaseLayout.astro'; - -interface Props { - post: CollectionEntry<'blog'>; -} - -const { post } = Astro.props; -const { title, description, date, tags } = post.data; -const formattedDate = new Date(date).toLocaleDateString('en-US', { - year: 'numeric', - month: 'long', - day: 'numeric' -}); ---- - - -
-
-

- {title} -

-
- - {tags && tags.length > 0 && ( -
- {tags.map(tag => ( - - {tag} - - ))} -
- )} -
-

- {description} -

-
- -
- -
-
-
- - \ No newline at end of file diff --git a/src/pages/blog/[slug].astro b/src/pages/blog/[slug].astro new file mode 100644 index 0000000..c0034f8 --- /dev/null +++ b/src/pages/blog/[slug].astro @@ -0,0 +1,716 @@ +--- +import BaseLayout from '../../layouts/BaseLayout.astro'; +import Tag from '../../components/Tag.astro'; +import { blogPosts } from '../../data/blogPosts'; +import { CodeBlock, InlineCode } from '../../components/ArticleBlockquote'; +import { H2, H3 } from '../../components/ArticleHeading'; +import { Paragraph, LeadParagraph } from '../../components/ArticleParagraph'; +import { UL, LI } from '../../components/ArticleList'; + +export async function getStaticPaths() { + return blogPosts.map(post => ({ + params: { slug: post.slug }, + props: { post } + })); +} + +const { post } = Astro.props; + +const formattedDate = new Date(post.date).toLocaleDateString('en-US', { + month: 'long', + day: 'numeric', + year: 'numeric' +}); + +// Calculate reading time +const wordCount = post.description.split(/\s+/).length + 100; +const readingTime = Math.max(1, Math.ceil(wordCount / 200)); + +// Generate unique clap key for this post +const clapKey = `claps_${post.slug}`; +--- + + + +
+ + + + + +
+ +
+
+
+ +

+ {post.title} +

+ + +
+ + + + + + + {readingTime} min + +
+ + +

+ {post.description} +

+ + + {post.tags && post.tags.length > 0 && ( +
+ {post.tags.map((tag: string, index: number) => ( + + ))} +
+ )} +
+
+
+ + +
+
+

{post.description}

+ + {post.slug === 'first-note' && ( + <> + + This blog is a public notebook. It's where I document things I learn, problems I solve, and tools I test. + +

Why write in public?

+ + I forget things. Writing them down helps. Making them public helps me think more clearly and might help someone else. + +

What to expect

+
    +
  • Short entries, usually under 500 words
  • +
  • Practical solutions to specific problems
  • +
  • Notes on tools and workflows
  • +
  • Mistakes and what I learned
  • +
+ + )} + + {post.slug === 'debugging-tips' && ( + <> + + Sometimes the simplest debugging tool is the best one. Print statements get a bad reputation, but they're often exactly what you need. + +

Why print statements work

+ + Debuggers are powerful, but they change how your code runs. Print statements don't. + + +{`def process_data(data): + print(f"Processing {len(data)} items") + result = expensive_operation(data) + print(f"Operation result: {result}") + return result`} + + + )} +
+ + +
+ +
+
+
+ + + + + + + +
diff --git a/src/pages/blog/debugging-tips.astro b/src/pages/blog/debugging-tips.astro deleted file mode 100644 index 6f9de38..0000000 --- a/src/pages/blog/debugging-tips.astro +++ /dev/null @@ -1,79 +0,0 @@ ---- -import BlogLayout from '../../layouts/BlogLayout.astro'; -import { H2, H3 } from '../../components/ArticleHeading'; -import { Paragraph, LeadParagraph } from '../../components/ArticleParagraph'; -import { UL, LI } from '../../components/ArticleList'; -import { CodeBlock, InlineCode } from '../../components/ArticleBlockquote'; - -const post = { - data: { - title: "Debugging with print statements", - description: "When printf debugging is actually the right tool", - date: new Date("2024-01-20"), - tags: ["debugging", "tools"] - } -}; ---- - - - - Sometimes the simplest debugging tool is the best one. Print statements get a bad reputation, but they're often exactly what you need. - - -

Why print statements work

- - - Debuggers are powerful, but they change how your code runs. Print statements don't. They let you see what's actually happening in the real execution flow. - - -

When to use them

- -
    -
  • Quick investigation of unexpected behavior
  • -
  • Understanding data flow through multiple functions
  • -
  • Checking state at specific points in time
  • -
  • When setting up a debugger feels like overkill
  • -
- -

Make them useful

- - - Bad print statements create noise. Good ones tell you exactly what you need to know. - - - -{`def process_data(data): - # Bad: What does this even mean? - print("debug 1") - - # Good: Clear context and value - print(f"Processing {len(data)} items") - - result = expensive_operation(data) - - # Good: Show the important intermediate result - print(f"Operation result: {result}") - - return result`} - - -

What to print

- -
    -
  • Variable values at key points
  • -
  • Function entry/exit with parameters
  • -
  • Loop iterations with counters
  • -
  • Conditional branches taken
  • -
  • Timing information for performance
  • -
- -

Temporary by design

- - - The beauty of print statements is that they're temporary. Add them, get your answer, remove them. No setup, no cleanup, no commitment. - - - - Sometimes the most sophisticated tool is the one you can use in 5 seconds and throw away in 10. - -
\ No newline at end of file diff --git a/src/pages/blog/first-note.astro b/src/pages/blog/first-note.astro deleted file mode 100644 index 15e4945..0000000 --- a/src/pages/blog/first-note.astro +++ /dev/null @@ -1,69 +0,0 @@ ---- -import BlogLayout from '../../layouts/BlogLayout.astro'; -import { H2, H3 } from '../../components/ArticleHeading'; -import { Paragraph, LeadParagraph } from '../../components/ArticleParagraph'; -import { UL, LI } from '../../components/ArticleList'; -import { Blockquote, InlineCode } from '../../components/ArticleBlockquote'; - -const post = { - data: { - title: "Starting this blog", - description: "Why I'm writing things down in public", - date: new Date("2024-01-15"), - tags: ["meta", "learning"] - } -}; ---- - - - - This blog is a public notebook. It's where I document things I learn, problems I solve, and tools I test. - - -

Why write in public?

- - - I forget things. Writing them down helps. Making them public helps me think more clearly and might help someone else. - - - - The goal isn't to teach or impress. It's to document. If you find something useful here, great. If not, that's fine too. - - -

What to expect

- -
    -
  • Short entries, usually under 500 words
  • -
  • Practical solutions to specific problems
  • -
  • Notes on tools and workflows
  • -
  • Mistakes and what I learned
  • -
  • Occasional deep dives when needed
  • -
- -

How I work

- - - My process is simple: - - -
    -
  • I try things
  • -
  • I break things
  • -
  • I fix things
  • -
  • I write down what I learned
  • -
- -
- Understanding doesn't expire. Finished projects do. -
- -

Tools I use

- - - Mostly standard Unix tools, Python, shell scripts, and whatever gets the job done. I prefer simple over clever. - - - - If you're building things and solving problems, maybe you'll find something useful here. If not, thanks for reading anyway. - -
\ No newline at end of file diff --git a/src/pages/index.astro b/src/pages/index.astro index 06f7acf..f92ff2f 100644 --- a/src/pages/index.astro +++ b/src/pages/index.astro @@ -1,49 +1,398 @@ --- import BaseLayout from '../layouts/BaseLayout.astro'; -import { BlogPostCard } from '../components/BlogPostCard'; +import MediumCard from '../components/MediumCard.astro'; +import { SearchBar } from '../components/SearchBar'; +import Tag from '../components/Tag.astro'; import { blogPosts } from '../data/blogPosts'; // Sort posts by date -const posts = [...blogPosts].sort((a, b) => +const allPosts = [...blogPosts].sort((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime() ); + +// Get unique tags +const allTags = [...new Set(allPosts.flatMap(post => post.tags))]; --- - -
- -
-

- I work on technical problems and build tools, scripts, and systems to solve them. - Sometimes that means code, sometimes automation, sometimes AI, sometimes something else. - The tool is secondary. The problem comes first. -

-

- Topics: Vibe coding with AI • Debugging • Mac tools • Automation • Small scripts • Learning notes • FOSS -

-
+ + - -
-

Recent Notes

- - {posts.length === 0 ? ( -
-

No posts yet. Check back soon!

+ +
+
+ +
+

+ Marc Mintel +

+

+ "A public notebook of things I figured out, mistakes I made, and tools I tested." +

+
+ + + Vulkaneifel, Germany + + + Digital problem solver +
+
+
+
+ + +
+
+ +
+
+ + + {allTags.length > 0 && ( +
+

Topics

+
+ {allTags.map((tag, index) => ( + + + + ))} +
+
+ )} + + +
+
+ {allPosts.length === 0 ? ( +
+

No posts yet. Check back soon!

) : ( -
- {posts.map(post => ( - - ))} -
+ allPosts.map(post => ( + + + + )) )} -
-
- \ No newline at end of file +
+ + + + +
+ + + + +
diff --git a/src/pages/tags/[tag].astro b/src/pages/tags/[tag].astro new file mode 100644 index 0000000..e6a3904 --- /dev/null +++ b/src/pages/tags/[tag].astro @@ -0,0 +1,42 @@ +--- +import BaseLayout from '../../layouts/BaseLayout.astro'; +import { blogPosts } from '../../data/blogPosts'; +import MediumCard from '../../components/MediumCard.astro'; + +export async function getStaticPaths() { + const allTags = [...new Set(blogPosts.flatMap(post => post.tags))]; + + return allTags.map(tag => ({ + params: { tag }, + props: { tag } + })); +} + +const { tag } = Astro.props; +const posts = blogPosts.filter(post => post.tags.includes(tag)); +--- + + +
+
+

+ Posts tagged {tag} +

+

+ {posts.length} post{posts.length === 1 ? '' : 's'} +

+
+ +
+ {posts.map(post => ( + + ))} +
+ + +
+
\ No newline at end of file diff --git a/src/styles/global.css b/src/styles/global.css new file mode 100644 index 0000000..0674656 --- /dev/null +++ b/src/styles/global.css @@ -0,0 +1,579 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; + +/* Medium-inspired clean reading experience */ +@layer base { + html { + scroll-behavior: smooth; + } + + body { + @apply bg-white text-slate-800 font-serif antialiased; + font-family: 'Georgia', 'Times New Roman', serif; + line-height: 1.75; + } + + /* Typography */ + h1, h2, h3, h4, h5, h6 { + @apply font-sans font-bold text-slate-900; + } + + h1 { + @apply text-3xl md:text-4xl leading-tight mb-8; + font-family: 'Inter', sans-serif; + letter-spacing: -0.025em; + } + + h2 { + @apply text-2xl md:text-3xl leading-tight mb-6 mt-12; + font-family: 'Inter', sans-serif; + letter-spacing: -0.025em; + } + + h3 { + @apply text-xl md:text-2xl leading-tight mb-4 mt-8; + font-family: 'Inter', sans-serif; + letter-spacing: -0.025em; + } + + h4 { + @apply text-lg md:text-xl leading-tight mb-3 mt-6; + font-family: 'Inter', sans-serif; + letter-spacing: -0.025em; + } + + p { + @apply mb-6 text-base leading-relaxed text-slate-700; + } + + .lead { + @apply text-xl md:text-2xl text-slate-600 mb-10 leading-relaxed; + font-weight: 400; + } + + a { + @apply text-blue-600 hover:text-blue-800 transition-colors; + } + + ul, ol { + @apply ml-6 mb-6; + } + + li { + @apply mb-2; + } + + code { + @apply bg-slate-100 px-1.5 py-0.5 rounded font-mono text-sm text-slate-700; + } + + blockquote { + @apply border-l-4 border-slate-300 pl-6 italic text-slate-600 my-6; + } + + /* Code formatting */ + code { + @apply bg-slate-100 px-1.5 py-0.5 rounded font-mono text-sm text-slate-700; + } + + /* Inline code */ + :not(pre) > code { + @apply text-pink-600 bg-pink-50 border border-pink-200; + } + + /* Code blocks */ + pre { + @apply bg-slate-900 text-slate-100 p-4 rounded-lg overflow-x-auto my-6 border border-slate-700; + font-family: 'JetBrains Mono', 'Fira Code', 'Consolas', monospace; + font-size: 0.875rem; + line-height: 1.6; + } + + pre code { + @apply bg-transparent text-slate-100 px-0 py-0 border-0; + font-family: inherit; + } + + /* Syntax highlighting colors */ + .token.comment { @apply text-slate-500 italic; } + .token.keyword { @apply text-purple-400 font-semibold; } + .token.string { @apply text-green-400; } + .token.number { @apply text-orange-400; } + .token.function { @apply text-blue-400; } + .token.operator { @apply text-slate-300; } + .token.punctuation { @apply text-slate-400; } + .token.class-name { @apply text-yellow-400 font-semibold; } + + /* Line numbers wrapper */ + .line-numbers { + counter-reset: line; + } + + .line-numbers .line { + counter-increment: line; + position: relative; + padding-left: 2.5rem; + } + + .line-numbers .line::before { + content: counter(line); + position: absolute; + left: 0; + width: 2rem; + text-align: right; + color: #64748b; + user-select: none; + } + + /* Focus styles - Firefox compatible */ + a:focus, + button:focus, + [tabindex]:focus, + .post-link:focus, + .highlighter-tag:focus, + .clap-button-top:focus, + .share-button-top:focus, + #back-btn-top:focus, + #back-btn-bottom:focus, + #back-to-top:focus, + input:focus { + outline: 2px solid #3b82f6; + outline-offset: 2px; + box-shadow: 0 0 0 4px rgba(59, 130, 246, 0.2); + } + + /* Remove default outline in favor of custom styles */ + a:focus-visible, + button:focus-visible, + input:focus-visible { + outline: 2px solid #3b82f6; + outline-offset: 2px; + } + + /* High contrast mode support */ + @media (prefers-contrast: high) { + a:focus, + button:focus, + input:focus { + outline: 3px solid #000; + outline-offset: 2px; + } + } +} + +/* Medium-inspired components */ +@layer components { + .container { + @apply max-w-4xl mx-auto px-6 py-10; + } + + .wide-container { + @apply max-w-5xl mx-auto px-6 py-12; + } + + .narrow-container { + @apply max-w-2xl mx-auto px-6 py-8; + } + + /* Header - removed for single page design */ + + /* Blog post card - refined with better spacing */ + .post-card { + @apply mb-12 last:mb-0; + } + + .post-card h3 { + @apply text-xl font-semibold mb-3 hover:text-blue-600 transition-colors cursor-pointer; + font-weight: 600; + } + + .post-meta { + @apply text-sm text-slate-500 font-sans mb-4; + } + + .post-excerpt { + @apply text-slate-700 mb-5 leading-relaxed; + } + + .post-tags { + @apply flex flex-wrap gap-2; + } + + .tag { + @apply text-xs font-sans bg-slate-100 text-slate-700 px-2 py-1 rounded hover:bg-slate-200 transition-colors cursor-pointer; + } + + .read-more { + @apply text-sm font-semibold text-blue-600 hover:text-blue-800 font-sans inline-flex items-center; + } + + /* Article page */ + .article-header { + @apply mb-10; + } + + .article-title { + @apply text-4xl md:text-5xl font-bold mb-4; + } + + .article-meta { + @apply text-sm text-slate-500 font-sans mb-6; + } + + .article-content { + @apply text-lg leading-relaxed; + } + + .article-content p { + @apply mb-7; + } + + .article-content h2 { + @apply text-2xl font-bold mt-10 mb-4; + } + + .article-content h3 { + @apply text-xl font-bold mt-8 mb-3; + } + + .article-content ul, + .article-content ol { + @apply ml-6 mb-7; + } + + .article-content li { + @apply mb-2; + } + + .article-content blockquote { + @apply border-l-4 border-slate-400 pl-6 italic text-slate-600 my-8 text-xl; + } + + /* Enhanced code blocks for articles */ + .article-content pre { + @apply bg-slate-900 text-slate-100 p-5 rounded-lg overflow-x-auto my-6 border border-slate-700; + font-family: 'JetBrains Mono', 'Fira Code', 'Consolas', monospace; + font-size: 0.875rem; + line-height: 1.6; + position: relative; + } + + .article-content pre code { + @apply bg-transparent px-0 py-0; + font-family: inherit; + white-space: pre; + display: block; + } + + /* Line numbers for code blocks */ + .article-content pre.line-numbers { + padding-left: 3.5rem; + } + + .article-content pre.line-numbers::before { + content: attr(data-line-numbers); + position: absolute; + left: 0; + top: 0; + bottom: 0; + width: 2.5rem; + background: #1e293b; + color: #64748b; + text-align: right; + padding: 1.25rem 0.5rem 1.25rem 0; + font-family: inherit; + font-size: inherit; + line-height: 1.6; + border-right: 1px solid #334155; + user-select: none; + overflow: hidden; + } + + /* Inline code in articles */ + .article-content p code, + .article-content li code, + .article-content blockquote code { + @apply bg-slate-100 text-pink-600 px-1.5 py-0.5 rounded font-mono text-sm border border-pink-200; + } + + /* Syntax highlighting classes */ + .article-content .token.comment { @apply text-slate-500 italic; } + .article-content .token.keyword { @apply text-purple-400 font-semibold; } + .article-content .token.string { @apply text-green-400; } + .article-content .token.number { @apply text-orange-400; } + .article-content .token.function { @apply text-blue-400; } + .article-content .token.operator { @apply text-slate-300; } + .article-content .token.punctuation { @apply text-slate-400; } + .article-content .token.class-name { @apply text-yellow-400 font-semibold; } + .article-content .token.tag { @apply text-red-400; } + .article-content .token.attr-name { @apply text-purple-400; } + .article-content .token.attr-value { @apply text-green-400; } + + /* Code language badge */ + .article-content pre::after { + content: attr(data-language); + position: absolute; + top: 0.5rem; + right: 0.5rem; + background: #334155; + color: #e2e8f0; + padding: 0.125rem 0.5rem; + border-radius: 0.25rem; + font-size: 0.75rem; + text-transform: uppercase; + letter-spacing: 0.05em; + font-family: 'Inter', sans-serif; + } + + /* About page sections */ + .about-section { + @apply mb-10 pb-8 border-b border-slate-200 last:border-0; + } + + .about-section h2 { + @apply text-2xl font-bold mb-4; + } + + .about-list { + @apply space-y-2; + } + + .about-list li { + @apply flex items-start; + } + + .about-list li::before { + content: "→"; + @apply mr-2 text-blue-600 font-bold; + } + + /* Footer - removed for single page design */ + + /* Simple button */ + .btn { + @apply inline-block px-5 py-2 bg-slate-900 text-white font-sans font-medium hover:bg-slate-700 transition-colors no-underline rounded; + } + + /* Highlighter-style tags - lovely note-taking style */ + .highlighter-tag { + @apply inline-block text-xs font-bold px-2 py-0.5 rounded cursor-pointer transition-all duration-200; + position: relative; + transform: rotate(-1deg); + box-shadow: 2px 2px 0 rgba(0,0,0,0.1); + } + + .highlighter-yellow { + @apply bg-yellow-300 text-yellow-900; + background: linear-gradient(180deg, rgba(255,235,59,0.9) 0%, rgba(255,213,79,0.9) 100%); + } + + .highlighter-yellow:hover { + transform: rotate(-2deg) scale(1.1); + box-shadow: 3px 3px 0 rgba(0,0,0,0.15); + } + + .highlighter-pink { + @apply bg-pink-300 text-pink-900; + background: linear-gradient(180deg, rgba(255,167,209,0.9) 0%, rgba(255,122,175,0.9) 100%); + } + + .highlighter-pink:hover { + transform: rotate(-2deg) scale(1.1); + box-shadow: 3px 3px 0 rgba(0,0,0,0.15); + } + + .highlighter-green { + @apply bg-green-300 text-green-900; + background: linear-gradient(180deg, rgba(129,199,132,0.9) 0%, rgba(102,187,106,0.9) 100%); + } + + .highlighter-green:hover { + transform: rotate(-2deg) scale(1.1); + box-shadow: 3px 3px 0 rgba(0,0,0,0.15); + } + + .highlighter-blue { + @apply bg-blue-300 text-blue-900; + background: linear-gradient(180deg, rgba(100,181,246,0.9) 0%, rgba(66,165,245,0.9) 100%); + } + + .highlighter-blue:hover { + transform: rotate(-2deg) scale(1.1); + box-shadow: 3px 3px 0 rgba(0,0,0,0.15); + } + + /* Scribble underline for emphasis */ + .scribble-emphasis { + position: relative; + display: inline-block; + font-weight: 600; + } + + .scribble-emphasis::after { + content: ''; + position: absolute; + bottom: -2px; + left: -1px; + right: -1px; + height: 7px; + background: url("data:image/svg+xml,%3Csvg width='40' height='8' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M2 4 Q10 2, 20 4 T38 4' stroke='%23FFEB3B' stroke-width='3' fill='none' stroke-linecap='round'/%3E%3C/svg%3E") repeat-x; + background-size: 40px 8px; + opacity: 0.7; + z-index: -1; + } + + /* Sticky note effect */ + .sticky-note { + @apply p-4 rounded shadow-sm border; + background: linear-gradient(135deg, #fff9c4 0%, #fff59d 100%); + border-color: #f9a825; + transform: rotate(-0.5deg); + box-shadow: 2px 2px 5px rgba(0,0,0,0.1); + } + + .sticky-note:hover { + transform: rotate(0deg) scale(1.02); + box-shadow: 3px 3px 8px rgba(0,0,0,0.15); + } + + /* Handwritten style */ + .handwritten { + font-family: 'Comic Sans MS', 'Chalkboard SE', 'Comic Neue', cursive; + font-weight: 500; + letter-spacing: -0.3px; + } + + /* Search box styling */ + .search-box { + @apply w-full px-4 py-3 border-2 border-slate-200 rounded-lg focus:outline-none focus:border-blue-400 transition-colors; + background: rgba(255,255,255,0.9); + backdrop-filter: blur(10px); + } + + .search-box::placeholder { + @apply text-slate-400; + } + + /* Tag cloud */ + .tag-cloud { + @apply flex flex-wrap gap-2 items-center; + } + + /* Empty state */ + .empty-state { + @apply text-center py-12 text-slate-500; + } + + .empty-state svg { + @apply mx-auto mb-4 text-slate-300; + } + + /* Line clamp utility for text truncation */ + .line-clamp-3 { + display: -webkit-box; + -webkit-line-clamp: 3; + -webkit-box-orient: vertical; + overflow: hidden; + } + + /* Subtle gradient backgrounds */ + .subtle-gradient { + background: linear-gradient(135deg, rgba(255,255,255,0.1) 0%, rgba(248,250,252,0.1) 100%); + } + + /* Enhanced focus states */ + .focus-ring { + @apply focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 focus:ring-offset-white; + } + + /* Reading progress indicator */ + .reading-progress { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 3px; + background: linear-gradient(90deg, #3b82f6 0%, #8b5cf6 100%); + transform-origin: left; + z-index: 50; + } + + /* Smooth animations */ + .animate-fade-in { + animation: fadeIn 0.5s ease-in-out; + } + + @keyframes fadeIn { + from { opacity: 0; transform: translateY(10px); } + to { opacity: 1; transform: translateY(0); } + } + + /* Enhanced typography for better readability */ + .text-balance { + text-wrap: balance; + } + + /* Subtle shadow variations */ + .shadow-soft { + box-shadow: 0 2px 8px rgba(0,0,0,0.04), 0 1px 3px rgba(0,0,0,0.08); + } + + .shadow-hover { + box-shadow: 0 4px 16px rgba(0,0,0,0.06), 0 2px 6px rgba(0,0,0,0.12); + } + + /* Improved button styles */ + .btn-primary { + @apply bg-blue-600 text-white font-sans font-medium px-6 py-3 rounded-lg hover:bg-blue-700 focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 transition-all duration-200; + } + + .btn-secondary { + @apply bg-white text-slate-700 font-sans font-medium px-6 py-3 rounded-lg border border-slate-200 hover:bg-slate-50 focus:ring-2 focus:ring-slate-500 focus:ring-offset-2 transition-all duration-200; + } + + /* Status indicators */ + .status-indicator { + @apply inline-flex items-center gap-2 px-3 py-1 rounded-full text-xs font-medium; + } + + .status-published { + @apply bg-green-100 text-green-800; + } + + .status-draft { + @apply bg-yellow-100 text-yellow-800; + } + + /* Responsive grid improvements */ + .grid-responsive { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); + gap: 2rem; + } + + /* Better spacing utilities */ + .space-y-fluid > * + * { + margin-top: clamp(1rem, 2vw, 2rem); + } + + /* Enhanced link styles */ + .link-enhanced { + @apply text-blue-600 hover:text-blue-800 transition-colors duration-200 relative; + } + + .link-enhanced::after { + content: ''; + position: absolute; + bottom: -1px; + left: 0; + width: 0; + height: 2px; + background: linear-gradient(90deg, #3b82f6, #8b5cf6); + transition: width 0.3s ease; + } + + .link-enhanced:hover::after { + width: 100%; + } +} \ No newline at end of file diff --git a/tailwind.config.js b/tailwind.config.js new file mode 100644 index 0000000..98e4a32 --- /dev/null +++ b/tailwind.config.js @@ -0,0 +1,68 @@ +/** @type {import('tailwindcss').Config} */ +module.exports = { + content: [ + './src/**/*.{astro,html,js,jsx,md,mdx,ts,tsx}', + ], + theme: { + extend: { + colors: { + primary: { + 50: '#eff6ff', + 100: '#dbeafe', + 200: '#bfdbfe', + 300: '#93c5fd', + 400: '#60a5fa', + 500: '#3b82f6', + 600: '#2563eb', + 700: '#1d4ed8', + 800: '#1e40af', + 900: '#1e3a8a', + }, + slate: { + 850: '#1e293b', + 900: '#0f172a', + 950: '#020617', + } + }, + fontFamily: { + sans: ['Inter', 'system-ui', 'sans-serif'], + mono: ['JetBrains Mono', 'monospace'], + }, + typography: (theme) => ({ + DEFAULT: { + css: { + color: theme('colors.slate.800'), + a: { + color: theme('colors.primary.600'), + '&:hover': { + color: theme('colors.primary.700'), + }, + }, + }, + }, + }), + animation: { + 'fade-in': 'fadeIn 0.5s ease-in-out', + 'slide-up': 'slideUp 0.6s ease-out', + 'slide-down': 'slideDown 0.6s ease-out', + }, + keyframes: { + fadeIn: { + '0%': { opacity: '0' }, + '100%': { opacity: '1' }, + }, + slideUp: { + '0%': { transform: 'translateY(20px)', opacity: '0' }, + '100%': { transform: 'translateY(0)', opacity: '1' }, + }, + slideDown: { + '0%': { transform: 'translateY(-20px)', opacity: '0' }, + '100%': { transform: 'translateY(0)', opacity: '1' }, + }, + }, + }, + }, + plugins: [ + require('@tailwindcss/typography'), + ], +} \ No newline at end of file