diff --git a/apps/site/package.json b/apps/site/package.json
index 443fd2c..b27fb8c 100644
--- a/apps/site/package.json
+++ b/apps/site/package.json
@@ -16,16 +16,21 @@
"dependencies": {
"@astrojs/rss": "^4.0.19",
"@astrojs/sitemap": "^3.7.3",
+ "@fontsource-variable/fraunces": "^5.3.0",
+ "@fontsource-variable/newsreader": "^5.3.0",
+ "@fontsource/jetbrains-mono": "^5.3.0",
"astro": "^7.2.9",
"marked": "^18.0.11",
"pagefind": "^1.5.2",
- "smol-toml": "^1.8.0"
+ "smol-toml": "^1.8.0",
+ "three": "^0.185.1"
},
"allowScripts": {
"esbuild": true
},
"devDependencies": {
"@astrojs/check": "^0.9.10",
+ "@types/three": "^0.185.4",
"typescript": "^5.9.3"
}
}
diff --git a/apps/site/src/content.config.ts b/apps/site/src/content.config.ts
index d85d392..581a56a 100644
--- a/apps/site/src/content.config.ts
+++ b/apps/site/src/content.config.ts
@@ -18,6 +18,8 @@ const writings = defineCollection({
updated: z.coerce.date().optional(),
draft: z.boolean().default(false),
canonical_url: z.string().optional(),
+ wordCount: z.number().optional(),
+ readingMinutes: z.number().optional(),
taxonomies,
extra: z
.object({
diff --git a/apps/site/src/layouts/Base.astro b/apps/site/src/layouts/Base.astro
index 86fba5e..3ce2f34 100644
--- a/apps/site/src/layouts/Base.astro
+++ b/apps/site/src/layouts/Base.astro
@@ -1,4 +1,8 @@
---
+import "@fontsource-variable/fraunces";
+import "@fontsource-variable/newsreader";
+import "@fontsource/jetbrains-mono";
+import "../styles/global.css";
import { siteConfig } from "../site.config";
interface Props {
@@ -7,66 +11,145 @@ interface Props {
}
const { title, description } = Astro.props;
const metaDescription = description ?? siteConfig.description;
+const currentPath = Astro.url.pathname;
---
- {title} — andwati.com
+ {title} — {siteConfig.title}
+
-
-
-
-
+
+
diff --git a/apps/site/src/lib/toml-content-loader.ts b/apps/site/src/lib/toml-content-loader.ts
index 66c4f71..39966a7 100644
--- a/apps/site/src/lib/toml-content-loader.ts
+++ b/apps/site/src/lib/toml-content-loader.ts
@@ -19,6 +19,15 @@ function slugify(text: string): string {
.replace(/^-+|-+$/g, "");
}
+/** Strips inline markdown syntax for use as plain-text TOC labels/slugs. */
+function stripInlineMarkdown(text: string): string {
+ return text
+ .replace(/\*\*(.*?)\*\*/g, "$1")
+ .replace(/\*(.*?)\*/g, "$1")
+ .replace(/`(.*?)`/g, "$1")
+ .replace(/\[(.*?)\]\([^)]*\)/g, "$1");
+}
+
// Collects headings for the file currently being parsed (reset before each
// `marked.parse()` call below) so Astro's `render(entry)` can expose them
// as `headings`, matching the shape a built-in markdown loader would give.
@@ -27,9 +36,11 @@ let currentHeadings: Heading[] = [];
const marked = new Marked({
renderer: {
heading(token) {
- const slug = slugify(token.text);
- currentHeadings.push({ depth: token.depth, slug, text: token.text });
- return `${token.text}`;
+ const plainText = stripInlineMarkdown(token.text);
+ const slug = slugify(plainText);
+ const html = this.parser.parseInline(token.tokens);
+ currentHeadings.push({ depth: token.depth, slug, text: plainText });
+ return `${html}`;
},
},
});
@@ -80,10 +91,13 @@ export function tomlContentLoader(contentDir: string): Loader {
const relPath = relative(dirUrl.pathname, file);
const id = relPath.replace(/\/index\.md$/, "").replace(/\.md$/, "");
currentHeadings = [];
- const html = await marked.parse(body.trim());
+ const trimmedBody = body.trim();
+ const html = await marked.parse(trimmedBody);
+ const wordCount = trimmedBody.split(/\s+/).filter(Boolean).length;
+ const readingMinutes = Math.max(1, Math.round(wordCount / 200));
store.set({
id,
- data,
+ data: { ...data, wordCount, readingMinutes },
rendered: { html, metadata: { headings: currentHeadings } },
});
}
diff --git a/apps/site/src/pages/writings/[...slug].astro b/apps/site/src/pages/writings/[...slug].astro
index f0d1d42..bd3c205 100644
--- a/apps/site/src/pages/writings/[...slug].astro
+++ b/apps/site/src/pages/writings/[...slug].astro
@@ -55,6 +55,7 @@ const tocHeadings = headings.filter((h) => h.depth <= 3);
{entry.data.title}
{entry.data.date.toDateString()}
+ {entry.data.readingMinutes && <> · {entry.data.readingMinutes} min read>}
{entry.data.updated && <> · updated {entry.data.updated.toDateString()}>}
{tags.map((tag) => #{tag})}
diff --git a/apps/site/src/pages/writings/index.astro b/apps/site/src/pages/writings/index.astro
index 426e9d5..f68f3ad 100644
--- a/apps/site/src/pages/writings/index.astro
+++ b/apps/site/src/pages/writings/index.astro
@@ -13,7 +13,10 @@ const writings = (
writings.map((entry) => (
-
{entry.data.date.toDateString()}
+
+ {entry.data.date.toDateString()}
+ {entry.data.readingMinutes && <> · {entry.data.readingMinutes} min read>}
+
{entry.data.description &&
{entry.data.description}
}
{entry.data.taxonomies?.tags.map((tag) =>
#{tag})}
diff --git a/apps/site/src/site.config.ts b/apps/site/src/site.config.ts
index d594e91..93e06f1 100644
--- a/apps/site/src/site.config.ts
+++ b/apps/site/src/site.config.ts
@@ -16,6 +16,12 @@ export const siteConfig = {
{ name: "Bookshelf", url: "/bookshelf/" },
{ name: "Blogs", url: "/blogs/" },
],
+ social: {
+ github: "https://github.com/andwati",
+ twitter: "https://twitter.com/andwati_",
+ youtube: "https://www.youtube.com/@pwnforfunandprofit",
+ email: "andwatiian@gmail.com",
+ },
tagDescriptions: {
linux:
"Practical notes on running Arch-based Linux distributions, from WiFi hotspot quirks to database setup.",
diff --git a/apps/site/src/styles/global.css b/apps/site/src/styles/global.css
new file mode 100644
index 0000000..bba2b27
--- /dev/null
+++ b/apps/site/src/styles/global.css
@@ -0,0 +1,177 @@
+@import "./tokens.css";
+
+*,
+*::before,
+*::after {
+ box-sizing: border-box;
+}
+
+html {
+ -webkit-text-size-adjust: 100%;
+}
+
+body {
+ margin: 0;
+ background: var(--bg);
+ color: var(--text);
+ font-family: var(--font-body);
+ font-size: var(--font-size-base);
+ line-height: 1.65;
+ transition:
+ background-color 0.3s var(--ease-out),
+ color 0.3s var(--ease-out);
+}
+
+::selection {
+ background: var(--accent-muted);
+ color: var(--text);
+}
+
+a {
+ color: inherit;
+}
+
+img,
+svg {
+ max-width: 100%;
+ display: block;
+}
+
+h1,
+h2,
+h3 {
+ font-family: var(--font-display);
+ font-weight: 600;
+ font-style: italic;
+ line-height: 1.15;
+ letter-spacing: -0.01em;
+ margin: 0 0 var(--space-4);
+}
+
+h1 {
+ font-size: var(--font-size-3xl);
+ display: inline-block;
+ position: relative;
+}
+
+h1::after {
+ content: "";
+ position: absolute;
+ left: 0.05em;
+ right: 0.4em;
+ bottom: 0.08em;
+ height: 0.09em;
+ background: var(--accent);
+ opacity: 0.55;
+ z-index: -1;
+ border-radius: 1px;
+}
+
+h2 {
+ font-size: var(--font-size-2xl);
+}
+
+h3 {
+ font-size: var(--font-size-xl);
+}
+
+p {
+ margin: 0 0 var(--space-5);
+}
+
+code,
+pre {
+ font-family: var(--font-mono);
+ font-size: 0.9em;
+}
+
+pre {
+ padding: var(--space-4);
+ overflow-x: auto;
+ background: var(--bg-elevated);
+ border-radius: var(--radius-md);
+ border: 1px solid var(--border);
+}
+
+.container {
+ max-width: 42rem;
+ margin-inline: auto;
+ padding-inline: var(--space-5);
+}
+
+.eyebrow {
+ font-family: var(--font-mono);
+ font-size: var(--font-size-sm);
+ letter-spacing: 0.08em;
+ text-transform: uppercase;
+ color: var(--text-muted);
+}
+
+.meta {
+ font-family: var(--font-mono);
+ font-size: var(--font-size-sm);
+ color: var(--text-muted);
+}
+
+.tag {
+ display: inline-block;
+ font-family: var(--font-mono);
+ font-size: var(--font-size-sm);
+ color: var(--text-muted);
+ margin-right: var(--space-3);
+ text-decoration: none;
+}
+
+.tag:hover {
+ color: var(--accent);
+}
+
+.card {
+ padding-block: var(--space-5);
+ border-bottom: 1px solid var(--border);
+}
+
+.card h3 {
+ margin-bottom: var(--space-1);
+}
+
+.card h3 a {
+ text-decoration: none;
+}
+
+.card h3 a:hover {
+ color: var(--accent);
+}
+
+.button {
+ display: inline-flex;
+ align-items: center;
+ gap: var(--space-2);
+ font-family: var(--font-mono);
+ font-size: var(--font-size-sm);
+ letter-spacing: 0.04em;
+ text-decoration: none;
+ color: var(--text);
+ border: 1px solid var(--border);
+ border-radius: var(--radius-sm);
+ padding: var(--space-2) var(--space-4);
+ transition:
+ border-color 0.2s var(--ease-out),
+ color 0.2s var(--ease-out);
+}
+
+.button:hover {
+ border-color: var(--accent);
+ color: var(--accent);
+}
+
+@media (prefers-reduced-motion: reduce) {
+ *,
+ *::before,
+ *::after {
+ animation-duration: 0.001ms !important;
+ animation-iteration-count: 1 !important;
+ transition-duration: 0.001ms !important;
+ scroll-behavior: auto !important;
+ }
+}
diff --git a/apps/site/src/styles/tokens.css b/apps/site/src/styles/tokens.css
new file mode 100644
index 0000000..dfa1b67
--- /dev/null
+++ b/apps/site/src/styles/tokens.css
@@ -0,0 +1,66 @@
+/**
+ * Design tokens. Dark-first (matches the reflective, slightly technical
+ * tone of the content), with a warm paper-toned light mode — not pure
+ * black/white either way, so the palette reads considered rather than
+ * default browser colors.
+ */
+:root {
+ --bg: #faf7f1;
+ --bg-elevated: #f1ece0;
+ --text: #1c1a15;
+ --text-muted: #6f6a5c;
+ --border: rgba(28, 26, 21, 0.12);
+ --accent: #d1502f;
+ --accent-muted: rgba(209, 80, 47, 0.12);
+
+ --font-display: "Fraunces Variable", ui-serif, Georgia, serif;
+ --font-body: "Newsreader Variable", ui-serif, Georgia, serif;
+ --font-mono: "JetBrains Mono", ui-monospace, "SF Mono", monospace;
+
+ --font-size-sm: clamp(0.8rem, 0.77rem + 0.15vw, 0.875rem);
+ --font-size-base: clamp(1rem, 0.96rem + 0.2vw, 1.0625rem);
+ --font-size-lg: clamp(1.125rem, 1.05rem + 0.35vw, 1.25rem);
+ --font-size-xl: clamp(1.375rem, 1.2rem + 0.8vw, 1.75rem);
+ --font-size-2xl: clamp(1.75rem, 1.4rem + 1.6vw, 2.5rem);
+ --font-size-3xl: clamp(2.25rem, 1.7rem + 2.6vw, 3.5rem);
+
+ --space-1: 0.25rem;
+ --space-2: 0.5rem;
+ --space-3: 0.75rem;
+ --space-4: 1rem;
+ --space-5: 1.5rem;
+ --space-6: 2rem;
+ --space-7: 3rem;
+ --space-8: 4.5rem;
+
+ --radius-sm: 4px;
+ --radius-md: 10px;
+
+ --ease-out: cubic-bezier(0.16, 1, 0.3, 1);
+
+ color-scheme: light;
+}
+
+@media (prefers-color-scheme: dark) {
+ :root:not([data-theme="light"]) {
+ --bg: #0d0d0b;
+ --bg-elevated: #171613;
+ --text: #ece7db;
+ --text-muted: #8e897b;
+ --border: rgba(236, 231, 219, 0.12);
+ --accent: #ff6b45;
+ --accent-muted: rgba(255, 107, 69, 0.15);
+ color-scheme: dark;
+ }
+}
+
+:root[data-theme="dark"] {
+ --bg: #0d0d0b;
+ --bg-elevated: #171613;
+ --text: #ece7db;
+ --text-muted: #8e897b;
+ --border: rgba(236, 231, 219, 0.12);
+ --accent: #ff6b45;
+ --accent-muted: rgba(255, 107, 69, 0.15);
+ color-scheme: dark;
+}
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 4df8fa4..31910ab 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -78,6 +78,15 @@ importers:
'@astrojs/sitemap':
specifier: ^3.7.3
version: 3.7.3
+ '@fontsource-variable/fraunces':
+ specifier: ^5.3.0
+ version: 5.3.0
+ '@fontsource-variable/newsreader':
+ specifier: ^5.3.0
+ version: 5.3.0
+ '@fontsource/jetbrains-mono':
+ specifier: ^5.3.0
+ version: 5.3.0
astro:
specifier: ^7.2.9
version: 7.2.9(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.3)(@types/node@20.19.43)(terser@5.51.2)(yaml@2.9.0)
@@ -90,10 +99,16 @@ importers:
smol-toml:
specifier: ^1.8.0
version: 1.8.0
+ three:
+ specifier: ^0.185.1
+ version: 0.185.1
devDependencies:
'@astrojs/check':
specifier: ^0.9.10
version: 0.9.10(prettier@3.9.6)(typescript@5.9.3)
+ '@types/three':
+ specifier: ^0.185.4
+ version: 0.185.4
typescript:
specifier: ^5.9.3
version: 5.9.3
@@ -581,6 +596,9 @@ packages:
'@dabh/diagnostics@2.0.8':
resolution: {integrity: sha512-R4MSXTVnuMzGD7bzHdW2ZhhdPC/igELENcq5IjEverBvq5hn1SXCWcsi6eSsdWP0/Ur+SItRRjAktmdoX/8R/Q==}
+ '@dimforge/rapier3d-compat@0.12.0':
+ resolution: {integrity: sha512-uekIGetywIgopfD97oDL5PfeezkFpNhwlzlaEYNOA0N6ghdsOvh/HYjSMek5Q2O1PYvRSDFcqFVJl4r4ZBwOow==}
+
'@discoveryjs/json-ext@0.6.3':
resolution: {integrity: sha512-4B4OijXeVNOPZlYA2oEwWOTkzyltLao+xbotHQeqN++Rv27Y6s818+n2Qkp8q+Fxhn0t/5lA5X1Mxktud8eayQ==}
engines: {node: '>=14.17.0'}
@@ -999,6 +1017,15 @@ packages:
'@floating-ui/utils@0.2.12':
resolution: {integrity: sha512-HpCo8tmWzLVad5s2d19EhAz5zqrrQ6s69qd6moPMQvkOuSwDT1YgRfWSVuc4ennqrgv3OHppiOGMQ7oC13yIww==}
+ '@fontsource-variable/fraunces@5.3.0':
+ resolution: {integrity: sha512-9BYGySn4AHEJdgp9Z28tQ3X+laJMEOITXkQarZXeloWQZDq5oOvXJ3kDA8c7MGIfpogIaZfjrQBqmda8POOCKA==}
+
+ '@fontsource-variable/newsreader@5.3.0':
+ resolution: {integrity: sha512-rrzYi43qMpbzwuFtf9OkWH8sxAPVPcQQQEwXpPtwaKYeJ8yVg5aLs5kawmo1f2Q1t1M38TLmEKCkGVDsYwgdFw==}
+
+ '@fontsource/jetbrains-mono@5.3.0':
+ resolution: {integrity: sha512-fqDfB5I9f1p1TV486aUgB9t8zP84P0O1FtQR5Ol9vjwPy+S+EIGlVYm1cvj2W5shcZMTg2nZFdVMoH5wFu8a1A==}
+
'@formatjs/ecma402-abstract@1.18.2':
resolution: {integrity: sha512-+QoPW4csYALsQIl8GbN14igZzDbuwzcpWrku9nyMXlaqAlwRBgl5V+p0vWMGFqHOw37czNXaP/lEk4wbLgcmtA==}
@@ -2940,6 +2967,9 @@ packages:
'@tokenizer/token@0.3.0':
resolution: {integrity: sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==}
+ '@tweenjs/tween.js@23.1.3':
+ resolution: {integrity: sha512-vJmvvwFxYuGnF2axRtPYocag6Clbb5YS7kLL+SO/TeVFzHqDIWrNKYtcsPMibjDx9O+bu+psAy9NKfWklassUA==}
+
'@tybys/wasm-util@0.10.3':
resolution: {integrity: sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg==}
@@ -3102,6 +3132,12 @@ packages:
'@types/serve-static@2.2.0':
resolution: {integrity: sha512-8mam4H1NHLtu7nmtalF7eyBH14QyOASmcxHhSfEoRyr0nP/YdoesEtU+uSRvMe96TW/HPTtkoKqQLl53N7UXMQ==}
+ '@types/stats.js@0.17.4':
+ resolution: {integrity: sha512-jIBvWWShCvlBqBNIZt0KAshWpvSjhkwkEu4ZUcASoAvhmrgAUI2t1dXrjSL4xXVLB4FznPrIsX3nKXFl/Dt4vA==}
+
+ '@types/three@0.185.4':
+ resolution: {integrity: sha512-gAsBIC07NIFrxjbf7tH2t71c38uulFfk/RFoC7FNBSjMRAQ8J1x/RBvusX0N5PJouaYFJawXQqfCQ0RKUx/1nA==}
+
'@types/through@0.0.33':
resolution: {integrity: sha512-HsJ+z3QuETzP3cswwtzt2vEIiHBk/dCcHGhbmG5X3ecnwFD/lPrMpliGXxSCg03L9AhrdwA4Oz/qfspkDW+xGQ==}
@@ -3120,6 +3156,9 @@ packages:
'@types/use-sync-external-store@0.0.3':
resolution: {integrity: sha512-EwmlvuaxPNej9+T4v5AuBPJa2x2UOJVdjCtDHgcDqitUeOtjnJKJ+apYjVcAoBEMjKW1VVFGZLUb5+qqa09XFA==}
+ '@types/webxr@0.5.24':
+ resolution: {integrity: sha512-h8fgEd/DpoS9CBrjEQXR+dIDraopAEfu4wYVNY2tEPwk60stPWhvZMf4Foo5FakuQ7HFZoa8WceaWFervK2Ovg==}
+
'@ucast/core@1.10.2':
resolution: {integrity: sha512-ons5CwXZ/51wrUPfoduC+cO7AS1/wRb0ybpQJ9RrssossDxVy4t49QxWoWgfBDvVKsz9VXzBk9z0wqTdZ+Cq8g==}
@@ -4377,6 +4416,9 @@ packages:
fflate@0.7.5:
resolution: {integrity: sha512-QieYf//cis6ywHNi5qW1+PXPQ4bC+XVJAtS4AXIML8P76GroEiOxm/oQtn1f02UkJY1+KsXMJcC+R2v/Eg4G3g==}
+ fflate@0.8.3:
+ resolution: {integrity: sha512-tbZNuJrLwGUp3zshBtdy4W+ORxZuIh8a5ilyIEQDC5rY1f3U20JMry0Ll3WBzU58EZKsEuJFXhb5gwv8CsPvgA==}
+
file-selector@2.1.2:
resolution: {integrity: sha512-QgXo+mXTe8ljeqUFaX3QVHc5osSItJ/Km+xpocx0aSqWGMSCf6qYs/VnzZgS864Pjn5iceMRFigeAV7AfTlaig==}
engines: {node: '>= 12'}
@@ -5550,6 +5592,9 @@ packages:
merge-stream@2.0.0:
resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==}
+ meshoptimizer@1.1.1:
+ resolution: {integrity: sha512-oRFNWJRDA/WTrVj7NWvqa5HqE1t9MYDj2VaWirQCzCCrAd2GHrqR/sQezCxiWATPNlKTcRaPRHPJwIRoPBAp5g==}
+
methods@1.1.2:
resolution: {integrity: sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==}
engines: {node: '>= 0.6'}
@@ -7050,6 +7095,9 @@ packages:
thenify@3.3.1:
resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==}
+ three@0.185.1:
+ resolution: {integrity: sha512-5aojFCXKwnjBRZvUnt3WFfEcvUJgkN5LlijRFN95hMy8WVkG4I0QNcJE+OuWvuJ0bOdStrbfXn0pkd6/QyiAlg==}
+
throttleit@2.1.0:
resolution: {integrity: sha512-nt6AMGKW1p/70DF/hGBdJB57B8Tspmbp5gfJ8ilhLnt7kkr2ye7hzD6NVG8GGErk2HWF34igrL2CXmNIkzKqKw==}
engines: {node: '>=18'}
@@ -8425,6 +8473,8 @@ snapshots:
enabled: 2.0.0
kuler: 2.0.0
+ '@dimforge/rapier3d-compat@0.12.0': {}
+
'@discoveryjs/json-ext@0.6.3': {}
'@dnd-kit/accessibility@3.1.1(react@18.3.1)':
@@ -8735,6 +8785,12 @@ snapshots:
'@floating-ui/utils@0.2.12': {}
+ '@fontsource-variable/fraunces@5.3.0': {}
+
+ '@fontsource-variable/newsreader@5.3.0': {}
+
+ '@fontsource/jetbrains-mono@5.3.0': {}
+
'@formatjs/ecma402-abstract@1.18.2':
dependencies:
'@formatjs/intl-localematcher': 0.5.4
@@ -11424,6 +11480,8 @@ snapshots:
'@tokenizer/token@0.3.0': {}
+ '@tweenjs/tween.js@23.1.3': {}
+
'@tybys/wasm-util@0.10.3':
dependencies:
tslib: 2.8.1
@@ -11627,6 +11685,17 @@ snapshots:
'@types/http-errors': 2.0.5
'@types/node': 20.19.43
+ '@types/stats.js@0.17.4': {}
+
+ '@types/three@0.185.4':
+ dependencies:
+ '@dimforge/rapier3d-compat': 0.12.0
+ '@tweenjs/tween.js': 23.1.3
+ '@types/stats.js': 0.17.4
+ '@types/webxr': 0.5.24
+ fflate: 0.8.3
+ meshoptimizer: 1.1.1
+
'@types/through@0.0.33':
dependencies:
'@types/node': 20.19.43
@@ -11642,6 +11711,8 @@ snapshots:
'@types/use-sync-external-store@0.0.3': {}
+ '@types/webxr@0.5.24': {}
+
'@ucast/core@1.10.2': {}
'@ucast/js@3.1.0':
@@ -13057,6 +13128,8 @@ snapshots:
fflate@0.7.5: {}
+ fflate@0.8.3: {}
+
file-selector@2.1.2:
dependencies:
tslib: 2.8.1
@@ -14351,6 +14424,8 @@ snapshots:
merge-stream@2.0.0: {}
+ meshoptimizer@1.1.1: {}
+
methods@1.1.2: {}
micromark-core-commonmark@2.0.3:
@@ -16070,6 +16145,8 @@ snapshots:
dependencies:
any-promise: 1.3.0
+ three@0.185.1: {}
+
throttleit@2.1.0: {}
through2@4.0.2: