learn and share notes on atproto (wip) 🦉 malfestio.stormlightlabs.org/
readability solid axum atproto srs

feat: add Open Graph image generation, SEO metadata, and local environment configuration.

+5 -33
docs/todo.md
··· 26 26 - **(Done) Milestone I**: Social Layer v1: Follow graph, Feeds (Follows/Trending), Forking workflow, and Threaded comments. 27 27 - Full-text search with pg_trgm/unaccent, visibility filtering, and unified search index. 28 28 - Tag taxonomy and Discovery page with top tags. 29 - 30 - ### Milestone J - Static Content & Landing Page 31 - 32 - #### Deliverables 33 - 34 - **Marketing/Static Site:** 35 - 36 - - [x] Landing page with hero section, feature highlights 37 - - Hero has graph paper/grid background 38 - - Floating flash cards, notes animations 39 - - [x] "How it works" section with study flow visualization 40 - - [x] About page with team/mission 41 - 42 - **App Vision Content:** 43 - 44 - - [x] Onboarding flow with persona selection (Learner/Creator/Curator) 45 - - [x] Empty states with helpful prompts for new users 46 - - [x] Help center/FAQ section (with beta development notice) 47 - - [x] Tutorial/walkthrough for first deck creation 48 - 49 - **SEO & Meta:** 50 - 51 - - [ ] Open Graph / Twitter Card meta tags 52 - - [ ] Scripted with HTML2Canvas/PNG generation 53 - - Graph paper background 54 - - Floating flash cards, notes (scribbles instead of text) 55 - - [ ] Sitemap.xml generation 56 - - [ ] robots.txt configuration 57 - 58 - #### Acceptance 59 - 60 - - New visitors understand the value proposition within 10 seconds. 61 - - Onboarding flow guides users to create their first deck. 29 + - **(Done) Milestone J**: Static Content & Landing Page. 30 + - Marketing & Static Site: Landing page, "How it works", About page, Onboarding flow, Empty states, Help center/FAQ section. 31 + - SEO & Meta: Open Graph / Twitter Card meta tags, Sitemap.xml generation, robots.txt configuration. 32 + - Onboarding Flow & Help Center/FAQ 33 + - Empty States 62 34 63 35 ### Milestone K - AppView Indexing 64 36
+18
web/index.html
··· 6 6 <link rel="icon" type="image/svg+xml" href="/favicon.svg" /> 7 7 <meta name="viewport" content="width=device-width, initial-scale=1.0" /> 8 8 <title>Malfestio</title> 9 + <meta name="description" 10 + content="Master complex topics with spaced repetition, linked notes, and active recall. Share your decks, notes, and discoveries with the community." /> 11 + 12 + <!-- Open Graph --> 13 + <meta property="og:type" content="website" /> 14 + <meta property="og:url" content="https://malfestio.stormlightlabs.org/" /> 15 + <meta property="og:title" content="Malfestio — Learning on the AT Protocol" /> 16 + <meta property="og:description" 17 + content="Master complex topics with spaced repetition, linked notes, and active recall. Share your decks, notes, and discoveries with the community." /> 18 + <meta property="og:image" content="https://malfestio.stormlightlabs.org/og-image.png" /> 19 + 20 + <!-- Twitter Card --> 21 + <meta name="twitter:card" content="summary_large_image" /> 22 + <meta name="twitter:url" content="https://malfestio.stormlightlabs.org/" /> 23 + <meta name="twitter:title" content="Malfestio — Learning on the AT Protocol" /> 24 + <meta name="twitter:description" 25 + content="Master complex topics with spaced repetition, linked notes, and active recall. Share your decks, notes, and discoveries with the community." /> 26 + <meta name="twitter:image" content="https://malfestio.stormlightlabs.org/og-image.png" /> 9 27 </head> 10 28 11 29 <body>
+6 -2
web/package.json
··· 5 5 "type": "module", 6 6 "scripts": { 7 7 "dev": "vite", 8 - "build": "tsc -b && vite build", 8 + "build": "pnpm run generate:og && tsc -b && vite build", 9 9 "preview": "vite preview", 10 10 "test": "vitest", 11 11 "check": "tsc --noEmit --project tsconfig.app.json", 12 - "lint": "eslint ." 12 + "lint": "eslint .", 13 + "generate:og": "tsx scripts/generate-og-image.ts" 13 14 }, 14 15 "dependencies": { 15 16 "@fontsource-variable/figtree": "^5.2.8", ··· 34 35 "@egoist/tailwindcss-icons": "^1.9.0", 35 36 "@eslint/js": "^9.39.2", 36 37 "@iconify-json/bi": "^1.2.7", 38 + "@resvg/resvg-js": "^2.6.2", 37 39 "@solidjs/testing-library": "^0.8.10", 38 40 "@testing-library/jest-dom": "^6.9.1", 39 41 "@testing-library/user-event": "^14.6.1", ··· 43 45 "eslint-plugin-solid": "^0.14.5", 44 46 "globals": "^16.5.0", 45 47 "jsdom": "^27.4.0", 48 + "satori": "^0.18.3", 49 + "tsx": "^4.21.0", 46 50 "typescript": "~5.9.3", 47 51 "typescript-eslint": "^8.50.1", 48 52 "vite": "npm:rolldown-vite@7.2.5",
+587 -17
web/pnpm-lock.yaml
··· 25 25 version: 0.15.4(solid-js@1.9.10) 26 26 '@tailwindcss/vite': 27 27 specifier: ^4.1.18 28 - version: 4.1.18(rolldown-vite@7.2.5(@types/node@24.10.4)(jiti@2.6.1)) 28 + version: 4.1.18(rolldown-vite@7.2.5(@types/node@24.10.4)(jiti@2.6.1)(tsx@4.21.0)) 29 29 clsx: 30 30 specifier: ^2.1.1 31 31 version: 2.1.1 ··· 72 72 '@iconify-json/bi': 73 73 specifier: ^1.2.7 74 74 version: 1.2.7 75 + '@resvg/resvg-js': 76 + specifier: ^2.6.2 77 + version: 2.6.2 75 78 '@solidjs/testing-library': 76 79 specifier: ^0.8.10 77 80 version: 0.8.10(@solidjs/router@0.15.4(solid-js@1.9.10))(solid-js@1.9.10) ··· 99 102 jsdom: 100 103 specifier: ^27.4.0 101 104 version: 27.4.0 105 + satori: 106 + specifier: ^0.18.3 107 + version: 0.18.3 108 + tsx: 109 + specifier: ^4.21.0 110 + version: 4.21.0 102 111 typescript: 103 112 specifier: ~5.9.3 104 113 version: 5.9.3 ··· 107 116 version: 8.50.1(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) 108 117 vite: 109 118 specifier: npm:rolldown-vite@7.2.5 110 - version: rolldown-vite@7.2.5(@types/node@24.10.4)(jiti@2.6.1) 119 + version: rolldown-vite@7.2.5(@types/node@24.10.4)(jiti@2.6.1)(tsx@4.21.0) 111 120 vite-plugin-solid: 112 121 specifier: ^2.11.10 113 - version: 2.11.10(@testing-library/jest-dom@6.9.1)(rolldown-vite@7.2.5(@types/node@24.10.4)(jiti@2.6.1))(solid-js@1.9.10) 122 + version: 2.11.10(@testing-library/jest-dom@6.9.1)(rolldown-vite@7.2.5(@types/node@24.10.4)(jiti@2.6.1)(tsx@4.21.0))(solid-js@1.9.10) 114 123 vitest: 115 124 specifier: ^4.0.16 116 - version: 4.0.16(@types/node@24.10.4)(jiti@2.6.1)(jsdom@27.4.0) 125 + version: 4.0.16(@types/node@24.10.4)(jiti@2.6.1)(jsdom@27.4.0)(tsx@4.21.0) 117 126 118 127 packages: 119 128 ··· 269 278 '@emnapi/wasi-threads@1.1.0': 270 279 resolution: {integrity: sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ==} 271 280 281 + '@esbuild/aix-ppc64@0.27.2': 282 + resolution: {integrity: sha512-GZMB+a0mOMZs4MpDbj8RJp4cw+w1WV5NYD6xzgvzUJ5Ek2jerwfO2eADyI6ExDSUED+1X8aMbegahsJi+8mgpw==} 283 + engines: {node: '>=18'} 284 + cpu: [ppc64] 285 + os: [aix] 286 + 287 + '@esbuild/android-arm64@0.27.2': 288 + resolution: {integrity: sha512-pvz8ZZ7ot/RBphf8fv60ljmaoydPU12VuXHImtAs0XhLLw+EXBi2BLe3OYSBslR4rryHvweW5gmkKFwTiFy6KA==} 289 + engines: {node: '>=18'} 290 + cpu: [arm64] 291 + os: [android] 292 + 293 + '@esbuild/android-arm@0.27.2': 294 + resolution: {integrity: sha512-DVNI8jlPa7Ujbr1yjU2PfUSRtAUZPG9I1RwW4F4xFB1Imiu2on0ADiI/c3td+KmDtVKNbi+nffGDQMfcIMkwIA==} 295 + engines: {node: '>=18'} 296 + cpu: [arm] 297 + os: [android] 298 + 299 + '@esbuild/android-x64@0.27.2': 300 + resolution: {integrity: sha512-z8Ank4Byh4TJJOh4wpz8g2vDy75zFL0TlZlkUkEwYXuPSgX8yzep596n6mT7905kA9uHZsf/o2OJZubl2l3M7A==} 301 + engines: {node: '>=18'} 302 + cpu: [x64] 303 + os: [android] 304 + 305 + '@esbuild/darwin-arm64@0.27.2': 306 + resolution: {integrity: sha512-davCD2Zc80nzDVRwXTcQP/28fiJbcOwvdolL0sOiOsbwBa72kegmVU0Wrh1MYrbuCL98Omp5dVhQFWRKR2ZAlg==} 307 + engines: {node: '>=18'} 308 + cpu: [arm64] 309 + os: [darwin] 310 + 311 + '@esbuild/darwin-x64@0.27.2': 312 + resolution: {integrity: sha512-ZxtijOmlQCBWGwbVmwOF/UCzuGIbUkqB1faQRf5akQmxRJ1ujusWsb3CVfk/9iZKr2L5SMU5wPBi1UWbvL+VQA==} 313 + engines: {node: '>=18'} 314 + cpu: [x64] 315 + os: [darwin] 316 + 317 + '@esbuild/freebsd-arm64@0.27.2': 318 + resolution: {integrity: sha512-lS/9CN+rgqQ9czogxlMcBMGd+l8Q3Nj1MFQwBZJyoEKI50XGxwuzznYdwcav6lpOGv5BqaZXqvBSiB/kJ5op+g==} 319 + engines: {node: '>=18'} 320 + cpu: [arm64] 321 + os: [freebsd] 322 + 323 + '@esbuild/freebsd-x64@0.27.2': 324 + resolution: {integrity: sha512-tAfqtNYb4YgPnJlEFu4c212HYjQWSO/w/h/lQaBK7RbwGIkBOuNKQI9tqWzx7Wtp7bTPaGC6MJvWI608P3wXYA==} 325 + engines: {node: '>=18'} 326 + cpu: [x64] 327 + os: [freebsd] 328 + 329 + '@esbuild/linux-arm64@0.27.2': 330 + resolution: {integrity: sha512-hYxN8pr66NsCCiRFkHUAsxylNOcAQaxSSkHMMjcpx0si13t1LHFphxJZUiGwojB1a/Hd5OiPIqDdXONia6bhTw==} 331 + engines: {node: '>=18'} 332 + cpu: [arm64] 333 + os: [linux] 334 + 335 + '@esbuild/linux-arm@0.27.2': 336 + resolution: {integrity: sha512-vWfq4GaIMP9AIe4yj1ZUW18RDhx6EPQKjwe7n8BbIecFtCQG4CfHGaHuh7fdfq+y3LIA2vGS/o9ZBGVxIDi9hw==} 337 + engines: {node: '>=18'} 338 + cpu: [arm] 339 + os: [linux] 340 + 341 + '@esbuild/linux-ia32@0.27.2': 342 + resolution: {integrity: sha512-MJt5BRRSScPDwG2hLelYhAAKh9imjHK5+NE/tvnRLbIqUWa+0E9N4WNMjmp/kXXPHZGqPLxggwVhz7QP8CTR8w==} 343 + engines: {node: '>=18'} 344 + cpu: [ia32] 345 + os: [linux] 346 + 347 + '@esbuild/linux-loong64@0.27.2': 348 + resolution: {integrity: sha512-lugyF1atnAT463aO6KPshVCJK5NgRnU4yb3FUumyVz+cGvZbontBgzeGFO1nF+dPueHD367a2ZXe1NtUkAjOtg==} 349 + engines: {node: '>=18'} 350 + cpu: [loong64] 351 + os: [linux] 352 + 353 + '@esbuild/linux-mips64el@0.27.2': 354 + resolution: {integrity: sha512-nlP2I6ArEBewvJ2gjrrkESEZkB5mIoaTswuqNFRv/WYd+ATtUpe9Y09RnJvgvdag7he0OWgEZWhviS1OTOKixw==} 355 + engines: {node: '>=18'} 356 + cpu: [mips64el] 357 + os: [linux] 358 + 359 + '@esbuild/linux-ppc64@0.27.2': 360 + resolution: {integrity: sha512-C92gnpey7tUQONqg1n6dKVbx3vphKtTHJaNG2Ok9lGwbZil6DrfyecMsp9CrmXGQJmZ7iiVXvvZH6Ml5hL6XdQ==} 361 + engines: {node: '>=18'} 362 + cpu: [ppc64] 363 + os: [linux] 364 + 365 + '@esbuild/linux-riscv64@0.27.2': 366 + resolution: {integrity: sha512-B5BOmojNtUyN8AXlK0QJyvjEZkWwy/FKvakkTDCziX95AowLZKR6aCDhG7LeF7uMCXEJqwa8Bejz5LTPYm8AvA==} 367 + engines: {node: '>=18'} 368 + cpu: [riscv64] 369 + os: [linux] 370 + 371 + '@esbuild/linux-s390x@0.27.2': 372 + resolution: {integrity: sha512-p4bm9+wsPwup5Z8f4EpfN63qNagQ47Ua2znaqGH6bqLlmJ4bx97Y9JdqxgGZ6Y8xVTixUnEkoKSHcpRlDnNr5w==} 373 + engines: {node: '>=18'} 374 + cpu: [s390x] 375 + os: [linux] 376 + 377 + '@esbuild/linux-x64@0.27.2': 378 + resolution: {integrity: sha512-uwp2Tip5aPmH+NRUwTcfLb+W32WXjpFejTIOWZFw/v7/KnpCDKG66u4DLcurQpiYTiYwQ9B7KOeMJvLCu/OvbA==} 379 + engines: {node: '>=18'} 380 + cpu: [x64] 381 + os: [linux] 382 + 383 + '@esbuild/netbsd-arm64@0.27.2': 384 + resolution: {integrity: sha512-Kj6DiBlwXrPsCRDeRvGAUb/LNrBASrfqAIok+xB0LxK8CHqxZ037viF13ugfsIpePH93mX7xfJp97cyDuTZ3cw==} 385 + engines: {node: '>=18'} 386 + cpu: [arm64] 387 + os: [netbsd] 388 + 389 + '@esbuild/netbsd-x64@0.27.2': 390 + resolution: {integrity: sha512-HwGDZ0VLVBY3Y+Nw0JexZy9o/nUAWq9MlV7cahpaXKW6TOzfVno3y3/M8Ga8u8Yr7GldLOov27xiCnqRZf0tCA==} 391 + engines: {node: '>=18'} 392 + cpu: [x64] 393 + os: [netbsd] 394 + 395 + '@esbuild/openbsd-arm64@0.27.2': 396 + resolution: {integrity: sha512-DNIHH2BPQ5551A7oSHD0CKbwIA/Ox7+78/AWkbS5QoRzaqlev2uFayfSxq68EkonB+IKjiuxBFoV8ESJy8bOHA==} 397 + engines: {node: '>=18'} 398 + cpu: [arm64] 399 + os: [openbsd] 400 + 401 + '@esbuild/openbsd-x64@0.27.2': 402 + resolution: {integrity: sha512-/it7w9Nb7+0KFIzjalNJVR5bOzA9Vay+yIPLVHfIQYG/j+j9VTH84aNB8ExGKPU4AzfaEvN9/V4HV+F+vo8OEg==} 403 + engines: {node: '>=18'} 404 + cpu: [x64] 405 + os: [openbsd] 406 + 407 + '@esbuild/openharmony-arm64@0.27.2': 408 + resolution: {integrity: sha512-LRBbCmiU51IXfeXk59csuX/aSaToeG7w48nMwA6049Y4J4+VbWALAuXcs+qcD04rHDuSCSRKdmY63sruDS5qag==} 409 + engines: {node: '>=18'} 410 + cpu: [arm64] 411 + os: [openharmony] 412 + 413 + '@esbuild/sunos-x64@0.27.2': 414 + resolution: {integrity: sha512-kMtx1yqJHTmqaqHPAzKCAkDaKsffmXkPHThSfRwZGyuqyIeBvf08KSsYXl+abf5HDAPMJIPnbBfXvP2ZC2TfHg==} 415 + engines: {node: '>=18'} 416 + cpu: [x64] 417 + os: [sunos] 418 + 419 + '@esbuild/win32-arm64@0.27.2': 420 + resolution: {integrity: sha512-Yaf78O/B3Kkh+nKABUF++bvJv5Ijoy9AN1ww904rOXZFLWVc5OLOfL56W+C8F9xn5JQZa3UX6m+IktJnIb1Jjg==} 421 + engines: {node: '>=18'} 422 + cpu: [arm64] 423 + os: [win32] 424 + 425 + '@esbuild/win32-ia32@0.27.2': 426 + resolution: {integrity: sha512-Iuws0kxo4yusk7sw70Xa2E2imZU5HoixzxfGCdxwBdhiDgt9vX9VUCBhqcwY7/uh//78A1hMkkROMJq9l27oLQ==} 427 + engines: {node: '>=18'} 428 + cpu: [ia32] 429 + os: [win32] 430 + 431 + '@esbuild/win32-x64@0.27.2': 432 + resolution: {integrity: sha512-sRdU18mcKf7F+YgheI/zGf5alZatMUTKj/jNS6l744f9u3WFu4v7twcUI9vu4mknF4Y9aDlblIie0IM+5xxaqQ==} 433 + engines: {node: '>=18'} 434 + cpu: [x64] 435 + os: [win32] 436 + 272 437 '@eslint-community/eslint-utils@4.9.0': 273 438 resolution: {integrity: sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g==} 274 439 engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} ··· 391 556 '@oxc-project/types@0.97.0': 392 557 resolution: {integrity: sha512-lxmZK4xFrdvU0yZiDwgVQTCvh2gHWBJCBk5ALsrtsBWhs0uDIi+FTOnXRQeQfs304imdvTdaakT/lqwQ8hkOXQ==} 393 558 559 + '@resvg/resvg-js-android-arm-eabi@2.6.2': 560 + resolution: {integrity: sha512-FrJibrAk6v29eabIPgcTUMPXiEz8ssrAk7TXxsiZzww9UTQ1Z5KAbFJs+Z0Ez+VZTYgnE5IQJqBcoSiMebtPHA==} 561 + engines: {node: '>= 10'} 562 + cpu: [arm] 563 + os: [android] 564 + 565 + '@resvg/resvg-js-android-arm64@2.6.2': 566 + resolution: {integrity: sha512-VcOKezEhm2VqzXpcIJoITuvUS/fcjIw5NA/w3tjzWyzmvoCdd+QXIqy3FBGulWdClvp4g+IfUemigrkLThSjAQ==} 567 + engines: {node: '>= 10'} 568 + cpu: [arm64] 569 + os: [android] 570 + 571 + '@resvg/resvg-js-darwin-arm64@2.6.2': 572 + resolution: {integrity: sha512-nmok2LnAd6nLUKI16aEB9ydMC6Lidiiq2m1nEBDR1LaaP7FGs4AJ90qDraxX+CWlVuRlvNjyYJTNv8qFjtL9+A==} 573 + engines: {node: '>= 10'} 574 + cpu: [arm64] 575 + os: [darwin] 576 + 577 + '@resvg/resvg-js-darwin-x64@2.6.2': 578 + resolution: {integrity: sha512-GInyZLjgWDfsVT6+SHxQVRwNzV0AuA1uqGsOAW+0th56J7Nh6bHHKXHBWzUrihxMetcFDmQMAX1tZ1fZDYSRsw==} 579 + engines: {node: '>= 10'} 580 + cpu: [x64] 581 + os: [darwin] 582 + 583 + '@resvg/resvg-js-linux-arm-gnueabihf@2.6.2': 584 + resolution: {integrity: sha512-YIV3u/R9zJbpqTTNwTZM5/ocWetDKGsro0SWp70eGEM9eV2MerWyBRZnQIgzU3YBnSBQ1RcxRZvY/UxwESfZIw==} 585 + engines: {node: '>= 10'} 586 + cpu: [arm] 587 + os: [linux] 588 + 589 + '@resvg/resvg-js-linux-arm64-gnu@2.6.2': 590 + resolution: {integrity: sha512-zc2BlJSim7YR4FZDQ8OUoJg5holYzdiYMeobb9pJuGDidGL9KZUv7SbiD4E8oZogtYY42UZEap7dqkkYuA91pg==} 591 + engines: {node: '>= 10'} 592 + cpu: [arm64] 593 + os: [linux] 594 + 595 + '@resvg/resvg-js-linux-arm64-musl@2.6.2': 596 + resolution: {integrity: sha512-3h3dLPWNgSsD4lQBJPb4f+kvdOSJHa5PjTYVsWHxLUzH4IFTJUAnmuWpw4KqyQ3NA5QCyhw4TWgxk3jRkQxEKg==} 597 + engines: {node: '>= 10'} 598 + cpu: [arm64] 599 + os: [linux] 600 + 601 + '@resvg/resvg-js-linux-x64-gnu@2.6.2': 602 + resolution: {integrity: sha512-IVUe+ckIerA7xMZ50duAZzwf1U7khQe2E0QpUxu5MBJNao5RqC0zwV/Zm965vw6D3gGFUl7j4m+oJjubBVoftw==} 603 + engines: {node: '>= 10'} 604 + cpu: [x64] 605 + os: [linux] 606 + 607 + '@resvg/resvg-js-linux-x64-musl@2.6.2': 608 + resolution: {integrity: sha512-UOf83vqTzoYQO9SZ0fPl2ZIFtNIz/Rr/y+7X8XRX1ZnBYsQ/tTb+cj9TE+KHOdmlTFBxhYzVkP2lRByCzqi4jQ==} 609 + engines: {node: '>= 10'} 610 + cpu: [x64] 611 + os: [linux] 612 + 613 + '@resvg/resvg-js-win32-arm64-msvc@2.6.2': 614 + resolution: {integrity: sha512-7C/RSgCa+7vqZ7qAbItfiaAWhyRSoD4l4BQAbVDqRRsRgY+S+hgS3in0Rxr7IorKUpGE69X48q6/nOAuTJQxeQ==} 615 + engines: {node: '>= 10'} 616 + cpu: [arm64] 617 + os: [win32] 618 + 619 + '@resvg/resvg-js-win32-ia32-msvc@2.6.2': 620 + resolution: {integrity: sha512-har4aPAlvjnLcil40AC77YDIk6loMawuJwFINEM7n0pZviwMkMvjb2W5ZirsNOZY4aDbo5tLx0wNMREp5Brk+w==} 621 + engines: {node: '>= 10'} 622 + cpu: [ia32] 623 + os: [win32] 624 + 625 + '@resvg/resvg-js-win32-x64-msvc@2.6.2': 626 + resolution: {integrity: sha512-ZXtYhtUr5SSaBrUDq7DiyjOFJqBVL/dOBN7N/qmi/pO0IgiWW/f/ue3nbvu9joWE5aAKDoIzy/CxsY0suwGosQ==} 627 + engines: {node: '>= 10'} 628 + cpu: [x64] 629 + os: [win32] 630 + 631 + '@resvg/resvg-js@2.6.2': 632 + resolution: {integrity: sha512-xBaJish5OeGmniDj9cW5PRa/PtmuVU3ziqrbr5xJj901ZDN4TosrVaNZpEiLZAxdfnhAe7uQ7QFWfjPe9d9K2Q==} 633 + engines: {node: '>= 10'} 634 + 394 635 '@rolldown/binding-android-arm64@1.0.0-beta.50': 395 636 resolution: {integrity: sha512-XlEkrOIHLyGT3avOgzfTFSjG+f+dZMw+/qd+Y3HLN86wlndrB/gSimrJCk4gOhr1XtRtEKfszpadI3Md4Z4/Ag==} 396 637 engines: {node: ^20.19.0 || >=22.12.0} ··· 476 717 477 718 '@rolldown/pluginutils@1.0.0-beta.50': 478 719 resolution: {integrity: sha512-5e76wQiQVeL1ICOZVUg4LSOVYg9jyhGCin+icYozhsUzM+fHE7kddi1bdiE0jwVqTfkjba3jUFbEkoC9WkdvyA==} 720 + 721 + '@shuding/opentype.js@1.4.0-beta.0': 722 + resolution: {integrity: sha512-3NgmNyH3l/Hv6EvsWJbsvpcpUba6R8IREQ83nH83cyakCw7uM1arZKNfHwv1Wz6jgqrF/j4x5ELvR6PnK9nTcA==} 723 + engines: {node: '>= 8.0.0'} 724 + hasBin: true 479 725 480 726 '@solid-primitives/props@3.2.2': 481 727 resolution: {integrity: sha512-lZOTwFJajBrshSyg14nBMEP0h8MXzPowGO0s3OeiR3z6nXHTfj0FhzDtJMv+VYoRJKQHG2QRnJTgCzK6erARAw==} ··· 826 1072 balanced-match@1.0.2: 827 1073 resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} 828 1074 1075 + base64-js@0.0.8: 1076 + resolution: {integrity: sha512-3XSA2cR/h/73EzlXXdU6YNycmYI7+kicTxks4eJg2g39biHR84slg2+des+p7iHYhbRg/udIS4TD53WabcOUkw==} 1077 + engines: {node: '>= 0.4'} 1078 + 829 1079 baseline-browser-mapping@2.9.11: 830 1080 resolution: {integrity: sha512-Sg0xJUNDU1sJNGdfGWhVHX0kkZ+HWcvmVymJbj6NSgZZmW/8S9Y2HQ5euytnIgakgxN6papOAWiwDo1ctFDcoQ==} 831 1081 hasBin: true ··· 847 1097 callsites@3.1.0: 848 1098 resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} 849 1099 engines: {node: '>=6'} 1100 + 1101 + camelize@1.0.1: 1102 + resolution: {integrity: sha512-dU+Tx2fsypxTgtLoE36npi3UqcjSSMNYfkqgmoEhtZrraP5VWq0K7FkWVTYa8eMPtnU/G2txVsfdCJTn9uzpuQ==} 850 1103 851 1104 caniuse-lite@1.0.30001761: 852 1105 resolution: {integrity: sha512-JF9ptu1vP2coz98+5051jZ4PwQgd2ni8A+gYSN7EA7dPKIMf0pDlSUxhdmVOaV3/fYK5uWBkgSXJaRLr4+3A6g==} ··· 901 1154 resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} 902 1155 engines: {node: '>= 8'} 903 1156 1157 + css-background-parser@0.1.0: 1158 + resolution: {integrity: sha512-2EZLisiZQ+7m4wwur/qiYJRniHX4K5Tc9w93MT3AS0WS1u5kaZ4FKXlOTBhOjc+CgEgPiGY+fX1yWD8UwpEqUA==} 1159 + 1160 + css-box-shadow@1.0.0-3: 1161 + resolution: {integrity: sha512-9jaqR6e7Ohds+aWwmhe6wILJ99xYQbfmK9QQB9CcMjDbTxPZjwEmUQpU91OG05Xgm8BahT5fW+svbsQGjS/zPg==} 1162 + 1163 + css-color-keywords@1.0.0: 1164 + resolution: {integrity: sha512-FyyrDHZKEjXDpNJYvVsV960FiqQyXc/LlYmsxl2BcdMb2WPx0OGRVgTg55rPSyLSNMqP52R9r8geSp7apN3Ofg==} 1165 + engines: {node: '>=4'} 1166 + 1167 + css-gradient-parser@0.0.17: 1168 + resolution: {integrity: sha512-w2Xy9UMMwlKtou0vlRnXvWglPAceXCTtcmVSo8ZBUvqCV5aXEFP/PC6d+I464810I9FT++UACwTD5511bmGPUg==} 1169 + engines: {node: '>=16'} 1170 + 1171 + css-to-react-native@3.2.0: 1172 + resolution: {integrity: sha512-e8RKaLXMOFii+02mOlqwjbD00KSEKqblnpO9e++1aXS1fPQOpS1YoqdVHBqPjHNoxeF2mimzVqawm2KCbEdtHQ==} 1173 + 904 1174 css-tree@3.1.0: 905 1175 resolution: {integrity: sha512-0eW44TGN5SQXU1mWSkKwFstI/22X2bG1nYzZTYMAWjylYURhse752YgbE4Cx46AC+bAvI+/dYTPRk1LqSUnu6w==} 906 1176 engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0} ··· 957 1227 electron-to-chromium@1.5.267: 958 1228 resolution: {integrity: sha512-0Drusm6MVRXSOJpGbaSVgcQsuB4hEkMpHXaVstcPmhu5LIedxs1xNK/nIxmQIU/RPC0+1/o0AVZfBTkTNJOdUw==} 959 1229 1230 + emoji-regex-xs@2.0.1: 1231 + resolution: {integrity: sha512-1QFuh8l7LqUcKe24LsPUNzjrzJQ7pgRwp1QMcZ5MX6mFplk2zQ08NVCM84++1cveaUUYtcCYHmeFEuNg16sU4g==} 1232 + engines: {node: '>=10.0.0'} 1233 + 960 1234 enhanced-resolve@5.18.4: 961 1235 resolution: {integrity: sha512-LgQMM4WXU3QI+SYgEc2liRgznaD5ojbmY3sb8LxyguVkIg5FxdpTkvk72te2R38/TGKxH634oLxXRGY6d7AP+Q==} 962 1236 engines: {node: '>=10.13.0'} ··· 968 1242 es-module-lexer@1.7.0: 969 1243 resolution: {integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==} 970 1244 1245 + esbuild@0.27.2: 1246 + resolution: {integrity: sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw==} 1247 + engines: {node: '>=18'} 1248 + hasBin: true 1249 + 971 1250 escalade@3.2.0: 972 1251 resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} 973 1252 engines: {node: '>=6'} 1253 + 1254 + escape-html@1.0.3: 1255 + resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==} 974 1256 975 1257 escape-string-regexp@4.0.0: 976 1258 resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} ··· 1056 1338 picomatch: 1057 1339 optional: true 1058 1340 1341 + fflate@0.7.4: 1342 + resolution: {integrity: sha512-5u2V/CDW15QM1XbbgS+0DfPxVB+jUKhWEKuuFuHncbk3tEEqzmoXL+2KyOFuKGqOnmdIy0/davWF1CkuwtibCw==} 1343 + 1059 1344 file-entry-cache@8.0.0: 1060 1345 resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==} 1061 1346 engines: {node: '>=16.0.0'} ··· 1093 1378 gensync@1.0.0-beta.2: 1094 1379 resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} 1095 1380 engines: {node: '>=6.9.0'} 1381 + 1382 + get-tsconfig@4.13.0: 1383 + resolution: {integrity: sha512-1VKTZJCwBrvbd+Wn3AOgQP/2Av+TfTCOlE4AcRJE72W1ksZXbAx8PPBR9RzgTeSPzlPMHrbANMH3LbltH73wxQ==} 1096 1384 1097 1385 glob-parent@6.0.2: 1098 1386 resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} ··· 1128 1416 1129 1417 hast-util-whitespace@3.0.0: 1130 1418 resolution: {integrity: sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==} 1419 + 1420 + hex-rgb@4.3.0: 1421 + resolution: {integrity: sha512-Ox1pJVrDCyGHMG9CFg1tmrRUMRPRsAWYc/PinY0XzJU4K7y7vjNoLKIQ7BR5UJMCxNN8EM1MNDmHWA/B3aZUuw==} 1422 + engines: {node: '>=6'} 1131 1423 1132 1424 hey-listen@1.0.8: 1133 1425 resolution: {integrity: sha512-COpmrF2NOg4TBWUJ5UVyaCU2A88wEMkUPK4hNqyCkqHbxT92BbvfjoSozkAIIm6XhicGlJHhFdullInrdhwU8Q==} ··· 1332 1624 resolution: {integrity: sha512-utfs7Pr5uJyyvDETitgsaqSyjCb2qNRAtuqUeWIAKztsOYdcACf2KtARYXg2pSvhkt+9NfoaNY7fxjl6nuMjIQ==} 1333 1625 engines: {node: '>= 12.0.0'} 1334 1626 1627 + linebreak@1.1.0: 1628 + resolution: {integrity: sha512-MHp03UImeVhB7XZtjd0E4n6+3xr5Dq/9xI/5FptGk5FrbDR3zagPa2DS6U8ks/3HjbKWG9Q1M2ufOzxV2qLYSQ==} 1629 + 1335 1630 local-pkg@1.1.2: 1336 1631 resolution: {integrity: sha512-arhlxbFRmoQHl33a0Zkle/YWlmNwoyt6QNZEIJcqNbdrsix5Lvc4HyyI3EnwxTYlZYc32EbYrQ8SzEZ7dqgg9A==} 1337 1632 engines: {node: '>=14'} ··· 1502 1797 package-manager-detector@1.6.0: 1503 1798 resolution: {integrity: sha512-61A5ThoTiDG/C8s8UMZwSorAGwMJ0ERVGj2OjoW5pAalsNOg15+iQiPzrLJ4jhZ1HJzmC2PIHT2oEiH3R5fzNA==} 1504 1799 1800 + pako@0.2.9: 1801 + resolution: {integrity: sha512-NUcwaKxUxWrZLpDG+z/xZaCgQITkA/Dv4V/T6bw7VON6l1Xz/VnrBqrYjZQ12TamKHzITTfOEIYUj48y2KXImA==} 1802 + 1505 1803 parent-module@1.0.1: 1506 1804 resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} 1507 1805 engines: {node: '>=6'} 1806 + 1807 + parse-css-color@0.2.1: 1808 + resolution: {integrity: sha512-bwS/GGIFV3b6KS4uwpzCFj4w297Yl3uqnSgIPsoQkx7GMLROXfMnWvxfNkL0oh8HVhZA4hvJoEoEIqonfJ3BWg==} 1508 1809 1509 1810 parse5@7.3.0: 1510 1811 resolution: {integrity: sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==} ··· 1535 1836 1536 1837 pkg-types@2.3.0: 1537 1838 resolution: {integrity: sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig==} 1839 + 1840 + postcss-value-parser@4.2.0: 1841 + resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==} 1538 1842 1539 1843 postcss@8.5.6: 1540 1844 resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==} ··· 1588 1892 resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} 1589 1893 engines: {node: '>=4'} 1590 1894 1895 + resolve-pkg-maps@1.0.0: 1896 + resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} 1897 + 1591 1898 rolldown-vite@7.2.5: 1592 1899 resolution: {integrity: sha512-u09tdk/huMiN8xwoiBbig197jKdCamQTtOruSalOzbqGje3jdHiV0njQlAW0YvzoahkirFePNQ4RYlfnRQpXZA==} 1593 1900 engines: {node: ^20.19.0 || >=22.12.0} ··· 1632 1939 resolution: {integrity: sha512-JFULvCNl/anKn99eKjOSEubi0lLmNqQDAjyEMME2T4CwezUDL0i6t1O9xZsu2OMehPnV2caNefWpGF+8TnzB6A==} 1633 1940 engines: {node: ^20.19.0 || >=22.12.0} 1634 1941 hasBin: true 1942 + 1943 + satori@0.18.3: 1944 + resolution: {integrity: sha512-T3DzWNmnrfVmk2gCIlAxLRLbGkfp3K7TyRva+Byyojqu83BNvnMeqVeYRdmUw4TKCsyH4RiQ/KuF/I4yEzgR5A==} 1945 + engines: {node: '>=16'} 1635 1946 1636 1947 saxes@6.0.0: 1637 1948 resolution: {integrity: sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==} ··· 1693 2004 std-env@3.10.0: 1694 2005 resolution: {integrity: sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==} 1695 2006 2007 + string.prototype.codepointat@0.2.1: 2008 + resolution: {integrity: sha512-2cBVCj6I4IOvEnjgO/hWqXjqBGsY+zwPmHl12Srk9IXSZ56Jwwmy+66XO5Iut/oQVR7t5ihYdLB0GMa4alEUcg==} 2009 + 1696 2010 stringify-entities@4.0.4: 1697 2011 resolution: {integrity: sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==} 1698 2012 ··· 1723 2037 tapable@2.3.0: 1724 2038 resolution: {integrity: sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==} 1725 2039 engines: {node: '>=6'} 2040 + 2041 + tiny-inflate@1.0.3: 2042 + resolution: {integrity: sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw==} 1726 2043 1727 2044 tinybench@2.9.0: 1728 2045 resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} ··· 1769 2086 tslib@2.8.1: 1770 2087 resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} 1771 2088 2089 + tsx@4.21.0: 2090 + resolution: {integrity: sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==} 2091 + engines: {node: '>=18.0.0'} 2092 + hasBin: true 2093 + 1772 2094 type-check@0.4.0: 1773 2095 resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} 1774 2096 engines: {node: '>= 0.8.0'} ··· 1791 2113 undici-types@7.16.0: 1792 2114 resolution: {integrity: sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==} 1793 2115 2116 + unicode-trie@2.0.0: 2117 + resolution: {integrity: sha512-x7bc76x0bm4prf1VLg79uhAzKw8DVboClSN5VxJuQ+LKDOVEW9CdH+VY7SP+vX7xCYQqzzgQpFqz15zeLvAtZQ==} 2118 + 1794 2119 unified@11.0.5: 1795 2120 resolution: {integrity: sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==} 1796 2121 ··· 1931 2256 yocto-queue@0.1.0: 1932 2257 resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} 1933 2258 engines: {node: '>=10'} 2259 + 2260 + yoga-layout@3.2.1: 2261 + resolution: {integrity: sha512-0LPOt3AxKqMdFBZA3HBAt/t/8vIKq7VaQYbuA8WxCgung+p9TVyKRYdpvCb80HcdTN2NkbIKbhNwKUfm3tQywQ==} 1934 2262 1935 2263 zwitch@2.0.4: 1936 2264 resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==} ··· 2124 2452 tslib: 2.8.1 2125 2453 optional: true 2126 2454 2455 + '@esbuild/aix-ppc64@0.27.2': 2456 + optional: true 2457 + 2458 + '@esbuild/android-arm64@0.27.2': 2459 + optional: true 2460 + 2461 + '@esbuild/android-arm@0.27.2': 2462 + optional: true 2463 + 2464 + '@esbuild/android-x64@0.27.2': 2465 + optional: true 2466 + 2467 + '@esbuild/darwin-arm64@0.27.2': 2468 + optional: true 2469 + 2470 + '@esbuild/darwin-x64@0.27.2': 2471 + optional: true 2472 + 2473 + '@esbuild/freebsd-arm64@0.27.2': 2474 + optional: true 2475 + 2476 + '@esbuild/freebsd-x64@0.27.2': 2477 + optional: true 2478 + 2479 + '@esbuild/linux-arm64@0.27.2': 2480 + optional: true 2481 + 2482 + '@esbuild/linux-arm@0.27.2': 2483 + optional: true 2484 + 2485 + '@esbuild/linux-ia32@0.27.2': 2486 + optional: true 2487 + 2488 + '@esbuild/linux-loong64@0.27.2': 2489 + optional: true 2490 + 2491 + '@esbuild/linux-mips64el@0.27.2': 2492 + optional: true 2493 + 2494 + '@esbuild/linux-ppc64@0.27.2': 2495 + optional: true 2496 + 2497 + '@esbuild/linux-riscv64@0.27.2': 2498 + optional: true 2499 + 2500 + '@esbuild/linux-s390x@0.27.2': 2501 + optional: true 2502 + 2503 + '@esbuild/linux-x64@0.27.2': 2504 + optional: true 2505 + 2506 + '@esbuild/netbsd-arm64@0.27.2': 2507 + optional: true 2508 + 2509 + '@esbuild/netbsd-x64@0.27.2': 2510 + optional: true 2511 + 2512 + '@esbuild/openbsd-arm64@0.27.2': 2513 + optional: true 2514 + 2515 + '@esbuild/openbsd-x64@0.27.2': 2516 + optional: true 2517 + 2518 + '@esbuild/openharmony-arm64@0.27.2': 2519 + optional: true 2520 + 2521 + '@esbuild/sunos-x64@0.27.2': 2522 + optional: true 2523 + 2524 + '@esbuild/win32-arm64@0.27.2': 2525 + optional: true 2526 + 2527 + '@esbuild/win32-ia32@0.27.2': 2528 + optional: true 2529 + 2530 + '@esbuild/win32-x64@0.27.2': 2531 + optional: true 2532 + 2127 2533 '@eslint-community/eslint-utils@4.9.0(eslint@9.39.2(jiti@2.6.1))': 2128 2534 dependencies: 2129 2535 eslint: 9.39.2(jiti@2.6.1) ··· 2271 2677 2272 2678 '@oxc-project/types@0.97.0': {} 2273 2679 2680 + '@resvg/resvg-js-android-arm-eabi@2.6.2': 2681 + optional: true 2682 + 2683 + '@resvg/resvg-js-android-arm64@2.6.2': 2684 + optional: true 2685 + 2686 + '@resvg/resvg-js-darwin-arm64@2.6.2': 2687 + optional: true 2688 + 2689 + '@resvg/resvg-js-darwin-x64@2.6.2': 2690 + optional: true 2691 + 2692 + '@resvg/resvg-js-linux-arm-gnueabihf@2.6.2': 2693 + optional: true 2694 + 2695 + '@resvg/resvg-js-linux-arm64-gnu@2.6.2': 2696 + optional: true 2697 + 2698 + '@resvg/resvg-js-linux-arm64-musl@2.6.2': 2699 + optional: true 2700 + 2701 + '@resvg/resvg-js-linux-x64-gnu@2.6.2': 2702 + optional: true 2703 + 2704 + '@resvg/resvg-js-linux-x64-musl@2.6.2': 2705 + optional: true 2706 + 2707 + '@resvg/resvg-js-win32-arm64-msvc@2.6.2': 2708 + optional: true 2709 + 2710 + '@resvg/resvg-js-win32-ia32-msvc@2.6.2': 2711 + optional: true 2712 + 2713 + '@resvg/resvg-js-win32-x64-msvc@2.6.2': 2714 + optional: true 2715 + 2716 + '@resvg/resvg-js@2.6.2': 2717 + optionalDependencies: 2718 + '@resvg/resvg-js-android-arm-eabi': 2.6.2 2719 + '@resvg/resvg-js-android-arm64': 2.6.2 2720 + '@resvg/resvg-js-darwin-arm64': 2.6.2 2721 + '@resvg/resvg-js-darwin-x64': 2.6.2 2722 + '@resvg/resvg-js-linux-arm-gnueabihf': 2.6.2 2723 + '@resvg/resvg-js-linux-arm64-gnu': 2.6.2 2724 + '@resvg/resvg-js-linux-arm64-musl': 2.6.2 2725 + '@resvg/resvg-js-linux-x64-gnu': 2.6.2 2726 + '@resvg/resvg-js-linux-x64-musl': 2.6.2 2727 + '@resvg/resvg-js-win32-arm64-msvc': 2.6.2 2728 + '@resvg/resvg-js-win32-ia32-msvc': 2.6.2 2729 + '@resvg/resvg-js-win32-x64-msvc': 2.6.2 2730 + 2274 2731 '@rolldown/binding-android-arm64@1.0.0-beta.50': 2275 2732 optional: true 2276 2733 ··· 2316 2773 optional: true 2317 2774 2318 2775 '@rolldown/pluginutils@1.0.0-beta.50': {} 2776 + 2777 + '@shuding/opentype.js@1.4.0-beta.0': 2778 + dependencies: 2779 + fflate: 0.7.4 2780 + string.prototype.codepointat: 0.2.1 2319 2781 2320 2782 '@solid-primitives/props@3.2.2(solid-js@1.9.10)': 2321 2783 dependencies: ··· 2413 2875 '@tailwindcss/oxide-win32-arm64-msvc': 4.1.18 2414 2876 '@tailwindcss/oxide-win32-x64-msvc': 4.1.18 2415 2877 2416 - '@tailwindcss/vite@4.1.18(rolldown-vite@7.2.5(@types/node@24.10.4)(jiti@2.6.1))': 2878 + '@tailwindcss/vite@4.1.18(rolldown-vite@7.2.5(@types/node@24.10.4)(jiti@2.6.1)(tsx@4.21.0))': 2417 2879 dependencies: 2418 2880 '@tailwindcss/node': 4.1.18 2419 2881 '@tailwindcss/oxide': 4.1.18 2420 2882 tailwindcss: 4.1.18 2421 - vite: rolldown-vite@7.2.5(@types/node@24.10.4)(jiti@2.6.1) 2883 + vite: rolldown-vite@7.2.5(@types/node@24.10.4)(jiti@2.6.1)(tsx@4.21.0) 2422 2884 2423 2885 '@testing-library/dom@10.4.1': 2424 2886 dependencies: ··· 2605 3067 chai: 6.2.2 2606 3068 tinyrainbow: 3.0.3 2607 3069 2608 - '@vitest/mocker@4.0.16(rolldown-vite@7.2.5(@types/node@24.10.4)(jiti@2.6.1))': 3070 + '@vitest/mocker@4.0.16(rolldown-vite@7.2.5(@types/node@24.10.4)(jiti@2.6.1)(tsx@4.21.0))': 2609 3071 dependencies: 2610 3072 '@vitest/spy': 4.0.16 2611 3073 estree-walker: 3.0.3 2612 3074 magic-string: 0.30.21 2613 3075 optionalDependencies: 2614 - vite: rolldown-vite@7.2.5(@types/node@24.10.4)(jiti@2.6.1) 3076 + vite: rolldown-vite@7.2.5(@types/node@24.10.4)(jiti@2.6.1)(tsx@4.21.0) 2615 3077 2616 3078 '@vitest/pretty-format@4.0.16': 2617 3079 dependencies: ··· 2688 3150 2689 3151 balanced-match@1.0.2: {} 2690 3152 3153 + base64-js@0.0.8: {} 3154 + 2691 3155 baseline-browser-mapping@2.9.11: {} 2692 3156 2693 3157 bidi-js@1.0.3: ··· 2712 3176 update-browserslist-db: 1.2.3(browserslist@4.28.1) 2713 3177 2714 3178 callsites@3.1.0: {} 3179 + 3180 + camelize@1.0.1: {} 2715 3181 2716 3182 caniuse-lite@1.0.30001761: {} 2717 3183 ··· 2754 3220 shebang-command: 2.0.0 2755 3221 which: 2.0.2 2756 3222 3223 + css-background-parser@0.1.0: {} 3224 + 3225 + css-box-shadow@1.0.0-3: {} 3226 + 3227 + css-color-keywords@1.0.0: {} 3228 + 3229 + css-gradient-parser@0.0.17: {} 3230 + 3231 + css-to-react-native@3.2.0: 3232 + dependencies: 3233 + camelize: 1.0.1 3234 + css-color-keywords: 1.0.0 3235 + postcss-value-parser: 4.2.0 3236 + 2757 3237 css-tree@3.1.0: 2758 3238 dependencies: 2759 3239 mdn-data: 2.12.2 ··· 2800 3280 2801 3281 electron-to-chromium@1.5.267: {} 2802 3282 3283 + emoji-regex-xs@2.0.1: {} 3284 + 2803 3285 enhanced-resolve@5.18.4: 2804 3286 dependencies: 2805 3287 graceful-fs: 4.2.11 ··· 2809 3291 2810 3292 es-module-lexer@1.7.0: {} 2811 3293 3294 + esbuild@0.27.2: 3295 + optionalDependencies: 3296 + '@esbuild/aix-ppc64': 0.27.2 3297 + '@esbuild/android-arm': 0.27.2 3298 + '@esbuild/android-arm64': 0.27.2 3299 + '@esbuild/android-x64': 0.27.2 3300 + '@esbuild/darwin-arm64': 0.27.2 3301 + '@esbuild/darwin-x64': 0.27.2 3302 + '@esbuild/freebsd-arm64': 0.27.2 3303 + '@esbuild/freebsd-x64': 0.27.2 3304 + '@esbuild/linux-arm': 0.27.2 3305 + '@esbuild/linux-arm64': 0.27.2 3306 + '@esbuild/linux-ia32': 0.27.2 3307 + '@esbuild/linux-loong64': 0.27.2 3308 + '@esbuild/linux-mips64el': 0.27.2 3309 + '@esbuild/linux-ppc64': 0.27.2 3310 + '@esbuild/linux-riscv64': 0.27.2 3311 + '@esbuild/linux-s390x': 0.27.2 3312 + '@esbuild/linux-x64': 0.27.2 3313 + '@esbuild/netbsd-arm64': 0.27.2 3314 + '@esbuild/netbsd-x64': 0.27.2 3315 + '@esbuild/openbsd-arm64': 0.27.2 3316 + '@esbuild/openbsd-x64': 0.27.2 3317 + '@esbuild/openharmony-arm64': 0.27.2 3318 + '@esbuild/sunos-x64': 0.27.2 3319 + '@esbuild/win32-arm64': 0.27.2 3320 + '@esbuild/win32-ia32': 0.27.2 3321 + '@esbuild/win32-x64': 0.27.2 3322 + 2812 3323 escalade@3.2.0: {} 3324 + 3325 + escape-html@1.0.3: {} 2813 3326 2814 3327 escape-string-regexp@4.0.0: {} 2815 3328 ··· 2914 3427 optionalDependencies: 2915 3428 picomatch: 4.0.3 2916 3429 3430 + fflate@0.7.4: {} 3431 + 2917 3432 file-entry-cache@8.0.0: 2918 3433 dependencies: 2919 3434 flat-cache: 4.0.1 ··· 2940 3455 optional: true 2941 3456 2942 3457 gensync@1.0.0-beta.2: {} 3458 + 3459 + get-tsconfig@4.13.0: 3460 + dependencies: 3461 + resolve-pkg-maps: 1.0.0 2943 3462 2944 3463 glob-parent@6.0.2: 2945 3464 dependencies: ··· 2982 3501 hast-util-whitespace@3.0.0: 2983 3502 dependencies: 2984 3503 '@types/hast': 3.0.4 3504 + 3505 + hex-rgb@4.3.0: {} 2985 3506 2986 3507 hey-listen@1.0.8: {} 2987 3508 ··· 3156 3677 lightningcss-win32-arm64-msvc: 1.30.2 3157 3678 lightningcss-win32-x64-msvc: 1.30.2 3158 3679 3680 + linebreak@1.1.0: 3681 + dependencies: 3682 + base64-js: 0.0.8 3683 + unicode-trie: 2.0.0 3684 + 3159 3685 local-pkg@1.1.2: 3160 3686 dependencies: 3161 3687 mlly: 1.8.0 ··· 3409 3935 3410 3936 package-manager-detector@1.6.0: {} 3411 3937 3938 + pako@0.2.9: {} 3939 + 3412 3940 parent-module@1.0.1: 3413 3941 dependencies: 3414 3942 callsites: 3.1.0 3415 3943 3944 + parse-css-color@0.2.1: 3945 + dependencies: 3946 + color-name: 1.1.4 3947 + hex-rgb: 4.3.0 3948 + 3416 3949 parse5@7.3.0: 3417 3950 dependencies: 3418 3951 entities: 6.0.1 ··· 3442 3975 confbox: 0.2.2 3443 3976 exsolve: 1.0.8 3444 3977 pathe: 2.0.3 3978 + 3979 + postcss-value-parser@4.2.0: {} 3445 3980 3446 3981 postcss@8.5.6: 3447 3982 dependencies: ··· 3511 4046 3512 4047 resolve-from@4.0.0: {} 3513 4048 3514 - rolldown-vite@7.2.5(@types/node@24.10.4)(jiti@2.6.1): 4049 + resolve-pkg-maps@1.0.0: {} 4050 + 4051 + rolldown-vite@7.2.5(@types/node@24.10.4)(jiti@2.6.1)(tsx@4.21.0): 3515 4052 dependencies: 3516 4053 '@oxc-project/runtime': 0.97.0 3517 4054 fdir: 6.5.0(picomatch@4.0.3) ··· 3524 4061 '@types/node': 24.10.4 3525 4062 fsevents: 2.3.3 3526 4063 jiti: 2.6.1 4064 + tsx: 4.21.0 3527 4065 3528 4066 rolldown@1.0.0-beta.50: 3529 4067 dependencies: ··· 3545 4083 '@rolldown/binding-win32-ia32-msvc': 1.0.0-beta.50 3546 4084 '@rolldown/binding-win32-x64-msvc': 1.0.0-beta.50 3547 4085 4086 + satori@0.18.3: 4087 + dependencies: 4088 + '@shuding/opentype.js': 1.4.0-beta.0 4089 + css-background-parser: 0.1.0 4090 + css-box-shadow: 1.0.0-3 4091 + css-gradient-parser: 0.0.17 4092 + css-to-react-native: 3.2.0 4093 + emoji-regex-xs: 2.0.1 4094 + escape-html: 1.0.3 4095 + linebreak: 1.1.0 4096 + parse-css-color: 0.2.1 4097 + postcss-value-parser: 4.2.0 4098 + yoga-layout: 3.2.1 4099 + 3548 4100 saxes@6.0.0: 3549 4101 dependencies: 3550 4102 xmlchars: 2.2.0 ··· 3599 4151 stackback@0.0.2: {} 3600 4152 3601 4153 std-env@3.10.0: {} 4154 + 4155 + string.prototype.codepointat@0.2.1: {} 3602 4156 3603 4157 stringify-entities@4.0.4: 3604 4158 dependencies: ··· 3627 4181 3628 4182 tapable@2.3.0: {} 3629 4183 4184 + tiny-inflate@1.0.3: {} 4185 + 3630 4186 tinybench@2.9.0: {} 3631 4187 3632 4188 tinyexec@1.0.2: {} ··· 3662 4218 3663 4219 tslib@2.8.1: {} 3664 4220 4221 + tsx@4.21.0: 4222 + dependencies: 4223 + esbuild: 0.27.2 4224 + get-tsconfig: 4.13.0 4225 + optionalDependencies: 4226 + fsevents: 2.3.3 4227 + 3665 4228 type-check@0.4.0: 3666 4229 dependencies: 3667 4230 prelude-ls: 1.2.1 ··· 3682 4245 ufo@1.6.1: {} 3683 4246 3684 4247 undici-types@7.16.0: {} 4248 + 4249 + unicode-trie@2.0.0: 4250 + dependencies: 4251 + pako: 0.2.9 4252 + tiny-inflate: 1.0.3 3685 4253 3686 4254 unified@11.0.5: 3687 4255 dependencies: ··· 3736 4304 '@types/unist': 3.0.3 3737 4305 vfile-message: 4.0.3 3738 4306 3739 - vite-plugin-solid@2.11.10(@testing-library/jest-dom@6.9.1)(rolldown-vite@7.2.5(@types/node@24.10.4)(jiti@2.6.1))(solid-js@1.9.10): 4307 + vite-plugin-solid@2.11.10(@testing-library/jest-dom@6.9.1)(rolldown-vite@7.2.5(@types/node@24.10.4)(jiti@2.6.1)(tsx@4.21.0))(solid-js@1.9.10): 3740 4308 dependencies: 3741 4309 '@babel/core': 7.28.5 3742 4310 '@types/babel__core': 7.20.5 ··· 3744 4312 merge-anything: 5.1.7 3745 4313 solid-js: 1.9.10 3746 4314 solid-refresh: 0.6.3(solid-js@1.9.10) 3747 - vite: rolldown-vite@7.2.5(@types/node@24.10.4)(jiti@2.6.1) 3748 - vitefu: 1.1.1(rolldown-vite@7.2.5(@types/node@24.10.4)(jiti@2.6.1)) 4315 + vite: rolldown-vite@7.2.5(@types/node@24.10.4)(jiti@2.6.1)(tsx@4.21.0) 4316 + vitefu: 1.1.1(rolldown-vite@7.2.5(@types/node@24.10.4)(jiti@2.6.1)(tsx@4.21.0)) 3749 4317 optionalDependencies: 3750 4318 '@testing-library/jest-dom': 6.9.1 3751 4319 transitivePeerDependencies: 3752 4320 - supports-color 3753 4321 3754 - vitefu@1.1.1(rolldown-vite@7.2.5(@types/node@24.10.4)(jiti@2.6.1)): 4322 + vitefu@1.1.1(rolldown-vite@7.2.5(@types/node@24.10.4)(jiti@2.6.1)(tsx@4.21.0)): 3755 4323 optionalDependencies: 3756 - vite: rolldown-vite@7.2.5(@types/node@24.10.4)(jiti@2.6.1) 4324 + vite: rolldown-vite@7.2.5(@types/node@24.10.4)(jiti@2.6.1)(tsx@4.21.0) 3757 4325 3758 - vitest@4.0.16(@types/node@24.10.4)(jiti@2.6.1)(jsdom@27.4.0): 4326 + vitest@4.0.16(@types/node@24.10.4)(jiti@2.6.1)(jsdom@27.4.0)(tsx@4.21.0): 3759 4327 dependencies: 3760 4328 '@vitest/expect': 4.0.16 3761 - '@vitest/mocker': 4.0.16(rolldown-vite@7.2.5(@types/node@24.10.4)(jiti@2.6.1)) 4329 + '@vitest/mocker': 4.0.16(rolldown-vite@7.2.5(@types/node@24.10.4)(jiti@2.6.1)(tsx@4.21.0)) 3762 4330 '@vitest/pretty-format': 4.0.16 3763 4331 '@vitest/runner': 4.0.16 3764 4332 '@vitest/snapshot': 4.0.16 ··· 3775 4343 tinyexec: 1.0.2 3776 4344 tinyglobby: 0.2.15 3777 4345 tinyrainbow: 3.0.3 3778 - vite: rolldown-vite@7.2.5(@types/node@24.10.4)(jiti@2.6.1) 4346 + vite: rolldown-vite@7.2.5(@types/node@24.10.4)(jiti@2.6.1)(tsx@4.21.0) 3779 4347 why-is-node-running: 2.3.0 3780 4348 optionalDependencies: 3781 4349 '@types/node': 24.10.4 ··· 3826 4394 yallist@3.1.1: {} 3827 4395 3828 4396 yocto-queue@0.1.0: {} 4397 + 4398 + yoga-layout@3.2.1: {} 3829 4399 3830 4400 zwitch@2.0.4: {}
web/public/og-image.png

This is a binary file and will not be displayed.

+3
web/public/robots.txt
··· 1 + User-agent: * 2 + Allow: / 3 + Sitemap: https://malfestio.stormlightlabs.org/sitemap.xml
+23
web/public/sitemap.xml
··· 1 + <?xml version="1.0" encoding="UTF-8"?> 2 + <urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"> 3 + <url> 4 + <loc>https://malfestio.stormlightlabs.org/</loc> 5 + <changefreq>weekly</changefreq> 6 + <priority>1.0</priority> 7 + </url> 8 + <url> 9 + <loc>https://malfestio.stormlightlabs.org/about</loc> 10 + <changefreq>monthly</changefreq> 11 + <priority>0.7</priority> 12 + </url> 13 + <url> 14 + <loc>https://malfestio.stormlightlabs.org/login</loc> 15 + <changefreq>monthly</changefreq> 16 + <priority>0.5</priority> 17 + </url> 18 + <url> 19 + <loc>https://malfestio.stormlightlabs.org/help</loc> 20 + <changefreq>monthly</changefreq> 21 + <priority>0.6</priority> 22 + </url> 23 + </urlset>
+247
web/scripts/generate-og-image.ts
··· 1 + /** 2 + * OpenGraph Image Generator 3 + * 4 + * Generates a 1200x630 OG image matching the hero section styling. 5 + * 6 + * Run with: pnpm run generate:og 7 + */ 8 + import { Resvg } from "@resvg/resvg-js"; 9 + import { writeFileSync } from "fs"; 10 + import { dirname, join } from "path"; 11 + import satori from "satori"; 12 + import { fileURLToPath } from "url"; 13 + 14 + const __dirname = dirname(fileURLToPath(import.meta.url)); 15 + 16 + const WIDTH = 1200; 17 + const HEIGHT = 630; 18 + const GRID_SIZE = 32; 19 + 20 + /** 21 + * Fetches a font from Google Fonts as a TTF file. 22 + * 23 + * We use an older user-agent to get TTF instead of woff2. 24 + * 25 + * @param family - The font family to fetch. 26 + * @param weight - The font weight to fetch. 27 + * @returns A Promise that resolves to the font file as an ArrayBuffer. 28 + */ 29 + async function fetchFont(family: string, weight: number): Promise<ArrayBuffer> { 30 + const url = `https://fonts.googleapis.com/css2?family=${encodeURIComponent(family)}:wght@${weight}&display=swap`; 31 + 32 + const cssRes = await fetch(url, { 33 + headers: { "User-Agent": "Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.1; Trident/6.0)" }, 34 + }); 35 + const css = await cssRes.text(); 36 + 37 + const fontUrlMatch = css.match(/src: url\(([^)]+)\)/); 38 + if (!fontUrlMatch) { 39 + throw new Error(`Could not find font URL for ${family}`); 40 + } 41 + 42 + const fontRes = await fetch(fontUrlMatch[1]); 43 + return fontRes.arrayBuffer(); 44 + } 45 + 46 + /** 47 + * Generates a grid pattern as SVG. 48 + * 49 + * @returns An object representing the grid pattern. 50 + */ 51 + function GridPattern() { 52 + const lines: { x1: number; y1: number; x2: number; y2: number }[] = []; 53 + 54 + for (let x = 0; x <= WIDTH; x += GRID_SIZE) { 55 + lines.push({ x1: x, y1: 0, x2: x, y2: HEIGHT }); 56 + } 57 + 58 + for (let y = 0; y <= HEIGHT; y += GRID_SIZE) { 59 + lines.push({ x1: 0, y1: y, x2: WIDTH, y2: y }); 60 + } 61 + 62 + return { 63 + type: "div", 64 + props: { 65 + style: { position: "absolute", top: 0, left: 0, width: WIDTH, height: HEIGHT, display: "flex" }, 66 + children: { 67 + type: "svg", 68 + props: { 69 + width: WIDTH, 70 + height: HEIGHT, 71 + children: lines.map((line, i) => ({ 72 + type: "line", 73 + props: { 74 + key: i, 75 + x1: line.x1, 76 + y1: line.y1, 77 + x2: line.x2, 78 + y2: line.y2, 79 + stroke: "rgba(85, 85, 85, 0.3)", 80 + strokeWidth: 1, 81 + }, 82 + })), 83 + }, 84 + }, 85 + }, 86 + }; 87 + } 88 + 89 + function NoteCard( 90 + config: { x: number; y: number; width: number; bgColor: string; borderColor: string; scribbleColor: string }, 91 + ) { 92 + return { 93 + type: "div", 94 + props: { 95 + style: { 96 + position: "absolute", 97 + left: config.x, 98 + top: config.y, 99 + width: config.width, 100 + height: 80, 101 + backgroundColor: config.bgColor, 102 + border: `1px solid ${config.borderColor}`, 103 + borderRadius: 8, 104 + padding: 12, 105 + display: "flex", 106 + flexDirection: "column", 107 + gap: 8, 108 + }, 109 + children: [{ 110 + type: "div", 111 + props: { style: { width: "80%", height: 8, backgroundColor: config.scribbleColor, borderRadius: 4 } }, 112 + }, { 113 + type: "div", 114 + props: { style: { width: "60%", height: 8, backgroundColor: config.scribbleColor, borderRadius: 4 } }, 115 + }], 116 + }, 117 + }; 118 + } 119 + 120 + const cards = [ 121 + NoteCard({ 122 + x: 800, 123 + y: 60, 124 + width: 180, 125 + bgColor: "rgba(37, 99, 235, 0.2)", 126 + borderColor: "rgba(59, 130, 246, 0.3)", 127 + scribbleColor: "rgba(147, 197, 253, 0.5)", 128 + }), 129 + NoteCard({ 130 + x: 950, 131 + y: 180, 132 + width: 160, 133 + bgColor: "rgba(147, 51, 234, 0.2)", 134 + borderColor: "rgba(168, 85, 247, 0.3)", 135 + scribbleColor: "rgba(216, 180, 254, 0.5)", 136 + }), 137 + NoteCard({ 138 + x: 820, 139 + y: 300, 140 + width: 170, 141 + bgColor: "rgba(234, 88, 12, 0.2)", 142 + borderColor: "rgba(251, 146, 60, 0.3)", 143 + scribbleColor: "rgba(253, 186, 116, 0.5)", 144 + }), 145 + NoteCard({ 146 + x: 920, 147 + y: 420, 148 + width: 200, 149 + bgColor: "rgba(14, 116, 144, 0.2)", 150 + borderColor: "rgba(34, 211, 238, 0.3)", 151 + scribbleColor: "rgba(103, 232, 249, 0.5)", 152 + }), 153 + ]; 154 + 155 + const ogImage = { 156 + type: "div", 157 + props: { 158 + style: { 159 + width: WIDTH, 160 + height: HEIGHT, 161 + backgroundColor: "#000000", 162 + display: "flex", 163 + flexDirection: "column", 164 + position: "relative", 165 + }, 166 + children: [GridPattern(), ...cards, { 167 + type: "div", 168 + props: { 169 + style: { position: "absolute", left: 60, top: 180, display: "flex", flexDirection: "column" }, 170 + children: [{ 171 + type: "div", 172 + props: { 173 + style: { fontSize: 96, fontFamily: "Source Serif 4", fontWeight: 500, color: "#ffffff", lineHeight: 1.1 }, 174 + children: "Learning on", 175 + }, 176 + }, { 177 + type: "div", 178 + props: { 179 + style: { fontSize: 96, fontFamily: "Source Serif 4", fontWeight: 500, color: "#737373", lineHeight: 1.1 }, 180 + children: "the AT Protocol.", 181 + }, 182 + }], 183 + }, 184 + }, { 185 + type: "div", 186 + props: { 187 + style: { 188 + position: "absolute", 189 + left: 60, 190 + bottom: 50, 191 + fontSize: 48, 192 + fontFamily: "Figtree", 193 + fontWeight: 600, 194 + color: "#ffffff", 195 + }, 196 + children: "Malfestio", 197 + }, 198 + }, { 199 + type: "div", 200 + props: { 201 + style: { 202 + position: "absolute", 203 + right: 72, 204 + bottom: 50, 205 + fontSize: 32, 206 + fontFamily: "Figtree", 207 + fontWeight: 500, 208 + textShadow: "0px 0px 10px rgba(0, 0, 0, 0.5)", 209 + color: "#737373", 210 + }, 211 + children: "malfestio.stormlightlabs.org", 212 + }, 213 + }], 214 + }, 215 + }; 216 + 217 + async function main() { 218 + console.log("Generating OpenGraph image..."); 219 + console.log("Fetching fonts from Google Fonts..."); 220 + 221 + const [sourceSerif, figtree] = await Promise.all([fetchFont("Source Serif 4", 500), fetchFont("Figtree", 600)]); 222 + 223 + console.log("Rendering SVG..."); 224 + 225 + const svg = await satori(ogImage, { 226 + width: WIDTH, 227 + height: HEIGHT, 228 + fonts: [{ name: "Source Serif 4", data: sourceSerif, weight: 500, style: "normal" }, { 229 + name: "Figtree", 230 + data: figtree, 231 + weight: 600, 232 + style: "normal", 233 + }], 234 + }); 235 + 236 + console.log("Converting to PNG..."); 237 + 238 + const resvg = new Resvg(svg, { fitTo: { mode: "width", value: WIDTH } }); 239 + const pngBuffer = resvg.render().asPng(); 240 + const outputPath = join(__dirname, "..", "public", "og-image.png"); 241 + writeFileSync(outputPath, pngBuffer); 242 + 243 + console.log(`✓ Generated: ${outputPath}`); 244 + console.log(` Dimensions: ${WIDTH}x${HEIGHT}px`); 245 + } 246 + 247 + main().catch(console.error);