diff --git a/.prettierignore b/.prettierignore
new file mode 100644
index 0000000..53dafda
--- /dev/null
+++ b/.prettierignore
@@ -0,0 +1,13 @@
+# Dependencies
+node_modules
+
+# OS
+.DS_Store
+
+# Build output
+dist
+stats.html
+
+# Favicon generation output
+**/favicons/**
+*.auto-generated*
diff --git a/astro.config.ts b/astro.config.ts
index 0db81c8..16b004c 100644
--- a/astro.config.ts
+++ b/astro.config.ts
@@ -15,5 +15,10 @@ export default defineConfig({
],
vite: {
plugins: [visualizer()],
+ resolve: {
+ alias: {
+ 'date-fns/locale': 'date-fns/locale/index.js',
+ },
+ },
},
});
diff --git a/package-lock.json b/package-lock.json
index 4dd2875..f6edcb4 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -23,6 +23,7 @@
"astro-compress": "1.1.28",
"concurrently": "7.6.0",
"date-fns": "2.29.3",
+ "favicons": "7.0.2",
"iconify-icon-names": "1.1.0",
"immer": "9.0.18",
"locales-ts": "1.0.0",
@@ -35,6 +36,7 @@
"puppeteer-report": "3.1.0",
"rollup-plugin-visualizer": "5.9.0",
"tailwindcss": "3.2.4",
+ "ts-node": "10.9.1",
"type-fest": "3.5.3",
"typescript": "4.9.4"
},
@@ -610,6 +612,28 @@
"partytown": "bin/partytown.cjs"
}
},
+ "node_modules/@cspotcode/source-map-support": {
+ "version": "0.8.1",
+ "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz",
+ "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==",
+ "dev": true,
+ "dependencies": {
+ "@jridgewell/trace-mapping": "0.3.9"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@cspotcode/source-map-support/node_modules/@jridgewell/trace-mapping": {
+ "version": "0.3.9",
+ "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz",
+ "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==",
+ "dev": true,
+ "dependencies": {
+ "@jridgewell/resolve-uri": "^3.0.3",
+ "@jridgewell/sourcemap-codec": "^1.4.10"
+ }
+ },
"node_modules/@emmetio/abbreviation": {
"version": "2.2.3",
"resolved": "https://registry.npmjs.org/@emmetio/abbreviation/-/abbreviation-2.2.3.tgz",
@@ -1109,6 +1133,30 @@
"node": ">=10.13.0"
}
},
+ "node_modules/@tsconfig/node10": {
+ "version": "1.0.9",
+ "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz",
+ "integrity": "sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==",
+ "dev": true
+ },
+ "node_modules/@tsconfig/node12": {
+ "version": "1.0.11",
+ "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz",
+ "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==",
+ "dev": true
+ },
+ "node_modules/@tsconfig/node14": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz",
+ "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==",
+ "dev": true
+ },
+ "node_modules/@tsconfig/node16": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.3.tgz",
+ "integrity": "sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ==",
+ "dev": true
+ },
"node_modules/@types/acorn": {
"version": "4.0.6",
"resolved": "https://registry.npmjs.org/@types/acorn/-/acorn-4.0.6.tgz",
@@ -2605,6 +2653,12 @@
"node": ">=14"
}
},
+ "node_modules/create-require": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz",
+ "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==",
+ "dev": true
+ },
"node_modules/cross-fetch": {
"version": "3.1.5",
"resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.5.tgz",
@@ -3445,6 +3499,12 @@
"node": ">=6"
}
},
+ "node_modules/escape-html": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
+ "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==",
+ "dev": true
+ },
"node_modules/escape-string-regexp": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
@@ -3634,6 +3694,20 @@
"reusify": "^1.0.4"
}
},
+ "node_modules/favicons": {
+ "version": "7.0.2",
+ "resolved": "https://registry.npmjs.org/favicons/-/favicons-7.0.2.tgz",
+ "integrity": "sha512-M/qE3ERHOBu0+Op+61jx8CdvOnSKrrl2zxUPpoGgsNyfjuGqRsK80zYoA5Uwdxl8QM4egfhBWZp1j7KK3YnOMg==",
+ "dev": true,
+ "dependencies": {
+ "escape-html": "^1.0.3",
+ "sharp": "^0.31.1",
+ "xml2js": "^0.4.23"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
"node_modules/fd-slicer": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz",
@@ -4660,9 +4734,9 @@
"dev": true
},
"node_modules/json5": {
- "version": "2.2.1",
- "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz",
- "integrity": "sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==",
+ "version": "2.2.3",
+ "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
+ "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==",
"dev": true,
"bin": {
"json5": "lib/cli.js"
@@ -4864,6 +4938,12 @@
"sourcemap-codec": "^1.4.8"
}
},
+ "node_modules/make-error": {
+ "version": "1.3.6",
+ "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz",
+ "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==",
+ "dev": true
+ },
"node_modules/markdown-table": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-3.0.2.tgz",
@@ -7511,6 +7591,12 @@
"suf-log": "^2.5.3"
}
},
+ "node_modules/sax": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz",
+ "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==",
+ "dev": true
+ },
"node_modules/scheduler": {
"version": "0.23.0",
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz",
@@ -8238,6 +8324,73 @@
"url": "https://github.com/sponsors/wooorm"
}
},
+ "node_modules/ts-node": {
+ "version": "10.9.1",
+ "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz",
+ "integrity": "sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==",
+ "dev": true,
+ "dependencies": {
+ "@cspotcode/source-map-support": "^0.8.0",
+ "@tsconfig/node10": "^1.0.7",
+ "@tsconfig/node12": "^1.0.7",
+ "@tsconfig/node14": "^1.0.0",
+ "@tsconfig/node16": "^1.0.2",
+ "acorn": "^8.4.1",
+ "acorn-walk": "^8.1.1",
+ "arg": "^4.1.0",
+ "create-require": "^1.1.0",
+ "diff": "^4.0.1",
+ "make-error": "^1.1.1",
+ "v8-compile-cache-lib": "^3.0.1",
+ "yn": "3.1.1"
+ },
+ "bin": {
+ "ts-node": "dist/bin.js",
+ "ts-node-cwd": "dist/bin-cwd.js",
+ "ts-node-esm": "dist/bin-esm.js",
+ "ts-node-script": "dist/bin-script.js",
+ "ts-node-transpile-only": "dist/bin-transpile.js",
+ "ts-script": "dist/bin-script-deprecated.js"
+ },
+ "peerDependencies": {
+ "@swc/core": ">=1.2.50",
+ "@swc/wasm": ">=1.2.50",
+ "@types/node": "*",
+ "typescript": ">=2.7"
+ },
+ "peerDependenciesMeta": {
+ "@swc/core": {
+ "optional": true
+ },
+ "@swc/wasm": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/ts-node/node_modules/acorn-walk": {
+ "version": "8.2.0",
+ "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz",
+ "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
+ "node_modules/ts-node/node_modules/arg": {
+ "version": "4.1.3",
+ "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz",
+ "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==",
+ "dev": true
+ },
+ "node_modules/ts-node/node_modules/diff": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz",
+ "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.3.1"
+ }
+ },
"node_modules/tsconfig-resolver": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/tsconfig-resolver/-/tsconfig-resolver-3.0.1.tgz",
@@ -8613,6 +8766,12 @@
"node": ">=8"
}
},
+ "node_modules/v8-compile-cache-lib": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz",
+ "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==",
+ "dev": true
+ },
"node_modules/vfile": {
"version": "5.3.4",
"resolved": "https://registry.npmjs.org/vfile/-/vfile-5.3.4.tgz",
@@ -9336,6 +9495,28 @@
}
}
},
+ "node_modules/xml2js": {
+ "version": "0.4.23",
+ "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.23.tgz",
+ "integrity": "sha512-ySPiMjM0+pLDftHgXY4By0uswI3SPKLDw/i3UXbnO8M/p28zqexCUoPmQFrYD+/1BzhGJSs2i1ERWKJAtiLrug==",
+ "dev": true,
+ "dependencies": {
+ "sax": ">=0.6.0",
+ "xmlbuilder": "~11.0.0"
+ },
+ "engines": {
+ "node": ">=4.0.0"
+ }
+ },
+ "node_modules/xmlbuilder": {
+ "version": "11.0.1",
+ "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz",
+ "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==",
+ "dev": true,
+ "engines": {
+ "node": ">=4.0"
+ }
+ },
"node_modules/xtend": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
@@ -9447,6 +9628,15 @@
"fd-slicer": "~1.1.0"
}
},
+ "node_modules/yn": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz",
+ "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==",
+ "dev": true,
+ "engines": {
+ "node": ">=6"
+ }
+ },
"node_modules/yocto-queue": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
@@ -9918,6 +10108,27 @@
"integrity": "sha512-Zbr2Eo0AQ4yzmQr/36/h+6LKjmdVBB3Q5cGzO6rtlIKB/IOpbQVUZW+XAnhpJmJr9sIF97OZjgbhG9k7Sjn4yw==",
"dev": true
},
+ "@cspotcode/source-map-support": {
+ "version": "0.8.1",
+ "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz",
+ "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==",
+ "dev": true,
+ "requires": {
+ "@jridgewell/trace-mapping": "0.3.9"
+ },
+ "dependencies": {
+ "@jridgewell/trace-mapping": {
+ "version": "0.3.9",
+ "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz",
+ "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==",
+ "dev": true,
+ "requires": {
+ "@jridgewell/resolve-uri": "^3.0.3",
+ "@jridgewell/sourcemap-codec": "^1.4.10"
+ }
+ }
+ }
+ },
"@emmetio/abbreviation": {
"version": "2.2.3",
"resolved": "https://registry.npmjs.org/@emmetio/abbreviation/-/abbreviation-2.2.3.tgz",
@@ -10316,6 +10527,30 @@
"integrity": "sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==",
"dev": true
},
+ "@tsconfig/node10": {
+ "version": "1.0.9",
+ "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz",
+ "integrity": "sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==",
+ "dev": true
+ },
+ "@tsconfig/node12": {
+ "version": "1.0.11",
+ "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz",
+ "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==",
+ "dev": true
+ },
+ "@tsconfig/node14": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz",
+ "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==",
+ "dev": true
+ },
+ "@tsconfig/node16": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.3.tgz",
+ "integrity": "sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ==",
+ "dev": true
+ },
"@types/acorn": {
"version": "4.0.6",
"resolved": "https://registry.npmjs.org/@types/acorn/-/acorn-4.0.6.tgz",
@@ -11473,6 +11708,12 @@
"path-type": "^4.0.0"
}
},
+ "create-require": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz",
+ "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==",
+ "dev": true
+ },
"cross-fetch": {
"version": "3.1.5",
"resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.5.tgz",
@@ -12002,6 +12243,12 @@
"integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==",
"dev": true
},
+ "escape-html": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
+ "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==",
+ "dev": true
+ },
"escape-string-regexp": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
@@ -12142,6 +12389,17 @@
"reusify": "^1.0.4"
}
},
+ "favicons": {
+ "version": "7.0.2",
+ "resolved": "https://registry.npmjs.org/favicons/-/favicons-7.0.2.tgz",
+ "integrity": "sha512-M/qE3ERHOBu0+Op+61jx8CdvOnSKrrl2zxUPpoGgsNyfjuGqRsK80zYoA5Uwdxl8QM4egfhBWZp1j7KK3YnOMg==",
+ "dev": true,
+ "requires": {
+ "escape-html": "^1.0.3",
+ "sharp": "^0.31.1",
+ "xml2js": "^0.4.23"
+ }
+ },
"fd-slicer": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz",
@@ -12872,9 +13130,9 @@
"dev": true
},
"json5": {
- "version": "2.2.1",
- "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz",
- "integrity": "sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==",
+ "version": "2.2.3",
+ "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
+ "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==",
"dev": true
},
"jsonc-parser": {
@@ -13028,6 +13286,12 @@
"sourcemap-codec": "^1.4.8"
}
},
+ "make-error": {
+ "version": "1.3.6",
+ "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz",
+ "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==",
+ "dev": true
+ },
"markdown-table": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-3.0.2.tgz",
@@ -14847,6 +15111,12 @@
"suf-log": "^2.5.3"
}
},
+ "sax": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz",
+ "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==",
+ "dev": true
+ },
"scheduler": {
"version": "0.23.0",
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz",
@@ -15379,6 +15649,47 @@
"integrity": "sha512-AqTiAOLcj85xS7vQ8QkAV41hPDIJ71XJB4RCUrzo/1GM2CQwhkJGaf9Hgr7BOugMRpgGUrqRg/DrBDl4H40+8g==",
"dev": true
},
+ "ts-node": {
+ "version": "10.9.1",
+ "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz",
+ "integrity": "sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==",
+ "dev": true,
+ "requires": {
+ "@cspotcode/source-map-support": "^0.8.0",
+ "@tsconfig/node10": "^1.0.7",
+ "@tsconfig/node12": "^1.0.7",
+ "@tsconfig/node14": "^1.0.0",
+ "@tsconfig/node16": "^1.0.2",
+ "acorn": "^8.4.1",
+ "acorn-walk": "^8.1.1",
+ "arg": "^4.1.0",
+ "create-require": "^1.1.0",
+ "diff": "^4.0.1",
+ "make-error": "^1.1.1",
+ "v8-compile-cache-lib": "^3.0.1",
+ "yn": "3.1.1"
+ },
+ "dependencies": {
+ "acorn-walk": {
+ "version": "8.2.0",
+ "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz",
+ "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==",
+ "dev": true
+ },
+ "arg": {
+ "version": "4.1.3",
+ "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz",
+ "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==",
+ "dev": true
+ },
+ "diff": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz",
+ "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==",
+ "dev": true
+ }
+ }
+ },
"tsconfig-resolver": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/tsconfig-resolver/-/tsconfig-resolver-3.0.1.tgz",
@@ -15632,6 +15943,12 @@
"sade": "^1.7.3"
}
},
+ "v8-compile-cache-lib": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz",
+ "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==",
+ "dev": true
+ },
"vfile": {
"version": "5.3.4",
"resolved": "https://registry.npmjs.org/vfile/-/vfile-5.3.4.tgz",
@@ -16051,6 +16368,22 @@
"dev": true,
"requires": {}
},
+ "xml2js": {
+ "version": "0.4.23",
+ "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.23.tgz",
+ "integrity": "sha512-ySPiMjM0+pLDftHgXY4By0uswI3SPKLDw/i3UXbnO8M/p28zqexCUoPmQFrYD+/1BzhGJSs2i1ERWKJAtiLrug==",
+ "dev": true,
+ "requires": {
+ "sax": ">=0.6.0",
+ "xmlbuilder": "~11.0.0"
+ }
+ },
+ "xmlbuilder": {
+ "version": "11.0.1",
+ "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz",
+ "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==",
+ "dev": true
+ },
"xtend": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
@@ -16140,6 +16473,12 @@
"fd-slicer": "~1.1.0"
}
},
+ "yn": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz",
+ "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==",
+ "dev": true
+ },
"yocto-queue": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
diff --git a/package.json b/package.json
index f39746e..f7fe197 100644
--- a/package.json
+++ b/package.json
@@ -13,9 +13,10 @@
"dev": "astro dev",
"build": "astro build",
"preview": "astro preview",
- "generate-cv": "node scripts/generate-cv.cjs",
- "prettier:check": "prettier --check --ignore-path .gitignore .",
- "prettier:write": "prettier --write --ignore-path .gitignore .",
+ "generate-cv": "ts-node scripts/generate-cv.ts",
+ "generate-favicons": "ts-node scripts/generate-favicons.ts",
+ "prettier:check": "prettier --check .",
+ "prettier:write": "prettier --write .",
"astro:check": "astro check",
"ts:check": "tsc --jsx preserve --skipLibCheck",
"check": "concurrently npm:*:check"
@@ -36,6 +37,7 @@
"astro-compress": "1.1.28",
"concurrently": "7.6.0",
"date-fns": "2.29.3",
+ "favicons": "7.0.2",
"iconify-icon-names": "1.1.0",
"immer": "9.0.18",
"locales-ts": "1.0.0",
@@ -48,6 +50,7 @@
"puppeteer-report": "3.1.0",
"rollup-plugin-visualizer": "5.9.0",
"tailwindcss": "3.2.4",
+ "ts-node": "10.9.1",
"type-fest": "3.5.3",
"typescript": "4.9.4"
}
diff --git a/public/favicon.svg b/public/favicon.svg
deleted file mode 100644
index 0f39062..0000000
--- a/public/favicon.svg
+++ /dev/null
@@ -1,13 +0,0 @@
-
diff --git a/public/favicons/android-chrome-192x192.png b/public/favicons/android-chrome-192x192.png
new file mode 100644
index 0000000..5b8e6b6
Binary files /dev/null and b/public/favicons/android-chrome-192x192.png differ
diff --git a/public/favicons/android-chrome-512x512.png b/public/favicons/android-chrome-512x512.png
new file mode 100644
index 0000000..8b65072
Binary files /dev/null and b/public/favicons/android-chrome-512x512.png differ
diff --git a/public/favicons/apple-touch-icon.png b/public/favicons/apple-touch-icon.png
new file mode 100644
index 0000000..1098e52
Binary files /dev/null and b/public/favicons/apple-touch-icon.png differ
diff --git a/public/favicons/favicon-16x16.png b/public/favicons/favicon-16x16.png
new file mode 100644
index 0000000..9dbbf4a
Binary files /dev/null and b/public/favicons/favicon-16x16.png differ
diff --git a/public/favicons/favicon-32x32.png b/public/favicons/favicon-32x32.png
new file mode 100644
index 0000000..4d5433f
Binary files /dev/null and b/public/favicons/favicon-32x32.png differ
diff --git a/public/favicons/favicon.ico b/public/favicons/favicon.ico
new file mode 100644
index 0000000..ded715d
Binary files /dev/null and b/public/favicons/favicon.ico differ
diff --git a/public/favicons/manifest.webmanifest b/public/favicons/manifest.webmanifest
new file mode 100644
index 0000000..d0df906
--- /dev/null
+++ b/public/favicons/manifest.webmanifest
@@ -0,0 +1,26 @@
+{
+ "name": "Mark Freeman - Senior React Developer",
+ "short_name": "Mark Freeman - Senior React Developer",
+ "description": "Lorem ipsum dolor sit amet, consectetur adipiscing elit. In sodales ac dui at vestibulum. In condimentum metus id dui tincidunt, in blandit mi vehicula.",
+ "dir": "auto",
+ "lang": "en-US",
+ "display": "standalone",
+ "orientation": "any",
+ "start_url": ".",
+ "background_color": "#fff",
+ "theme_color": "#fff",
+ "icons": [
+ {
+ "src": "/favicons/android-chrome-192x192.png",
+ "sizes": "192x192",
+ "type": "image/png",
+ "purpose": "any"
+ },
+ {
+ "src": "/favicons/android-chrome-512x512.png",
+ "sizes": "512x512",
+ "type": "image/png",
+ "purpose": "any"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/scripts/generate-cv.cjs b/scripts/generate-cv.cjs
deleted file mode 100644
index 31332ad..0000000
--- a/scripts/generate-cv.cjs
+++ /dev/null
@@ -1,50 +0,0 @@
-const { exec } = require('node:child_process');
-const path = require('node:path');
-const fs = require('node:fs');
-const puppeteer = require('puppeteer');
-const report = require('puppeteer-report');
-
-const waitFor = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
-
-const retry = async ({ promise, retries, retryTime }) => {
- try {
- return await promise();
- } catch (error) {
- if (retries <= 0) throw error;
-
- await waitFor(retryTime);
-
- return await retry({ promise, retries: retries - 1, retryTime });
- }
-};
-
-const config = {
- path: path.join(__dirname, '..', 'public', 'cv.pdf'),
- format: 'A4',
- printBackground: true,
- margin: { top: '10mm', right: '10mm', bottom: '10mm', left: '10mm' },
-};
-
-const main = async () => {
- const child = exec('npm run dev');
-
- const browser = await puppeteer.launch({ headless: true });
-
- const page = await browser.newPage();
-
- await page.setViewport({ width: 794, height: 1122, deviceScaleFactor: 2 });
-
- await retry({
- promise: () => page.goto('http://localhost:3000/pdf', { waitUntil: 'networkidle0' }),
- retries: 5,
- retryTime: 1000,
- });
-
- await report.pdfPage(page, config);
-
- await browser.close();
-
- child.kill();
-};
-
-main();
diff --git a/scripts/generate-cv.ts b/scripts/generate-cv.ts
new file mode 100644
index 0000000..3325dca
--- /dev/null
+++ b/scripts/generate-cv.ts
@@ -0,0 +1,59 @@
+import { exec } from 'node:child_process';
+import * as path from 'node:path';
+import * as puppeteer from 'puppeteer';
+import { pdfPage } from 'puppeteer-report';
+
+const waitFor = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));
+
+const goTo = async (page: puppeteer.Page, url: string) => {
+ await page.goto(url, { waitUntil: 'networkidle0' });
+};
+
+type GoToReturn = ReturnType;
+
+interface RetryOptions {
+ promise: () => GoToReturn;
+ retries: number;
+ retryTime: number;
+}
+
+const retry = async ({ promise, retries, retryTime }: RetryOptions): GoToReturn => {
+ try {
+ return await promise();
+ } catch (error) {
+ if (retries <= 0) throw error;
+
+ await waitFor(retryTime);
+
+ return await retry({ promise, retries: retries - 1, retryTime });
+ }
+};
+
+const main = async () => {
+ const child = exec('npm run dev');
+
+ const browser = await puppeteer.launch({ headless: true });
+
+ const page = await browser.newPage();
+
+ await page.setViewport({ width: 794, height: 1122, deviceScaleFactor: 2 });
+
+ await retry({
+ promise: () => goTo(page, 'http://localhost:3000/pdf'),
+ retries: 5,
+ retryTime: 1000,
+ });
+
+ await pdfPage(page, {
+ path: path.join(__dirname, '..', 'public', 'cv.pdf'),
+ format: 'A4',
+ printBackground: true,
+ margin: { top: '10mm', right: '10mm', bottom: '10mm', left: '10mm' },
+ });
+
+ await browser.close();
+
+ child.kill();
+};
+
+main();
diff --git a/scripts/generate-favicons.ts b/scripts/generate-favicons.ts
new file mode 100644
index 0000000..dfc990e
--- /dev/null
+++ b/scripts/generate-favicons.ts
@@ -0,0 +1,53 @@
+import { favicons, config as faviconsConfig, FaviconFile, FaviconImage } from 'favicons';
+import config from '../src/data/config';
+import { mkdir, writeFile, rm } from 'fs/promises';
+import { existsSync } from 'fs';
+
+const faviconsDirectory = './public/favicons';
+
+const saveFile = async (file: FaviconFile | FaviconImage) => {
+ await writeFile(`${faviconsDirectory}/${file.name}`, file.contents);
+ console.log(`${file.name} has been created successfully`);
+};
+
+(async () => {
+ const { faviconPath } = config.meta;
+
+ const response = await favicons(`.${faviconPath}`, {
+ ...faviconsConfig.defaults,
+ path: '/favicons',
+ appName: config.meta.title,
+ appDescription: config.meta.description,
+ appShortName: config.meta.title,
+ lang: config.i18n.locale.code,
+ start_url: '.',
+ icons: {
+ android: ['android-chrome-192x192.png', 'android-chrome-512x512.png'],
+ windows: false,
+ yandex: false,
+ appleStartup: false,
+ appleIcon: ['apple-touch-icon.png'],
+ favicons: ['favicon-16x16.png', 'favicon-32x32.png', 'favicon.ico'],
+ },
+ });
+
+ if (existsSync(faviconsDirectory)) {
+ await rm(faviconsDirectory, { recursive: true });
+ }
+
+ await mkdir(faviconsDirectory);
+
+ await Promise.all([...response.images, ...response.files].map(saveFile));
+
+ const comments = [
+ '\n',
+ '\n',
+ ];
+
+ const formattedHtml = response.html.map((line) => line.replace('>', '/>')).join('\n');
+
+ const pathToFaviconsFile = './src/web/head/favicons.auto-generated.astro';
+
+ await writeFile(pathToFaviconsFile, [...comments, formattedHtml, '\n']);
+ console.log(`${pathToFaviconsFile} has been updated successfully`);
+})();
diff --git a/src/data/config.ts b/src/data/config.ts
index b0816fa..c2fa3a4 100644
--- a/src/data/config.ts
+++ b/src/data/config.ts
@@ -1,5 +1,5 @@
import type { Config } from '@/types/data';
-import enUS from 'date-fns/locale/en-US/index.js';
+import { enUS } from 'date-fns/locale';
import type { ReadonlyDeep } from 'type-fest';
const config = {
@@ -14,7 +14,7 @@ const config = {
title: 'Mark Freeman - Senior React Developer',
description:
'Lorem ipsum dolor sit amet, consectetur adipiscing elit. In sodales ac dui at vestibulum. In condimentum metus id dui tincidunt, in blandit mi vehicula.',
- favicon: '/favicon.svg',
+ faviconPath: '/src/assets/my-image.jpeg',
},
pdf: {
footer:
diff --git a/src/types/config/meta-config.types.ts b/src/types/config/meta-config.types.ts
index 5cff761..585f202 100644
--- a/src/types/config/meta-config.types.ts
+++ b/src/types/config/meta-config.types.ts
@@ -20,11 +20,11 @@ export interface MetaConfig {
description: string;
/**
- * [WEB] URL or path to the page's favicon.
+ * [WEB] Absolute path to the image used for favicons generation.
*
* Specified icon will be displayed next to the page title in browser tab.
*/
- favicon: string;
+ faviconPath: string;
/**
* [WEB] Title used in open graph links.
diff --git a/src/web/components/head.astro b/src/web/components/head.astro
new file mode 100644
index 0000000..b9d8868
--- /dev/null
+++ b/src/web/components/head.astro
@@ -0,0 +1,19 @@
+---
+import Favicons from '@/web/head/favicons.auto-generated.astro';
+import InitialTheme from '@/web/head/initial-theme.astro';
+import Meta from '@/web/head/meta.astro';
+
+import type { MetaConfig } from '@/types/config/meta-config.types';
+
+interface Props {
+ meta: MetaConfig;
+}
+
+const { meta } = Astro.props;
+---
+
+
+
+
+
+
diff --git a/src/web/components/layout.astro b/src/web/components/layout.astro
index f6fe997..9a9745c 100644
--- a/src/web/components/layout.astro
+++ b/src/web/components/layout.astro
@@ -1,6 +1,7 @@
---
import type { Data } from '@/types/data';
import Analytics from '@/web/analytics/analytics.astro';
+import Head from './head.astro';
export interface Props extends Pick {}
@@ -9,33 +10,7 @@ const { meta, i18n } = Astro.props;
-
-
-
-
- {meta.title}
-
-
-
-
- {meta.ogImage && }
-
-
+
diff --git a/src/web/head/favicons.auto-generated.astro b/src/web/head/favicons.auto-generated.astro
new file mode 100644
index 0000000..4f83c43
--- /dev/null
+++ b/src/web/head/favicons.auto-generated.astro
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/web/head/initial-theme.astro b/src/web/head/initial-theme.astro
new file mode 100644
index 0000000..1cf2700
--- /dev/null
+++ b/src/web/head/initial-theme.astro
@@ -0,0 +1,16 @@
+
diff --git a/src/web/head/meta.astro b/src/web/head/meta.astro
new file mode 100644
index 0000000..89499a1
--- /dev/null
+++ b/src/web/head/meta.astro
@@ -0,0 +1,18 @@
+---
+import type { MetaConfig } from '@/types/config/meta-config.types';
+
+interface Props {
+ meta: MetaConfig;
+}
+
+const { meta } = Astro.props;
+---
+
+
+
+
+{meta.title}
+
+
+
+{meta.ogImage && }
diff --git a/tsconfig.json b/tsconfig.json
index 1a1e3ad..20c9449 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -35,5 +35,11 @@
"@/*": ["src/*"]
}
},
- "include": ["**/*.ts", "**/*.tsx", "**/*.cjs", "**/*.astro"]
+ "include": ["**/*.ts", "**/*.tsx", "**/*.cjs", "**/*.astro"],
+ "ts-node": {
+ "transpileOnly": true,
+ "compilerOptions": {
+ "module": "CommonJS"
+ }
+ }
}