diff options
| -rw-r--r-- | bun.lock | 109 | ||||
| -rw-r--r-- | package.json | 5 | ||||
| -rw-r--r-- | src/components/EventCard.astro | 63 | ||||
| -rw-r--r-- | src/components/EventNavigation.astro | 54 | ||||
| -rw-r--r-- | src/components/Footer.astro | 4 | ||||
| -rw-r--r-- | src/consts.ts | 6 | ||||
| -rw-r--r-- | src/content.config.ts | 16 | ||||
| -rw-r--r-- | src/content/events/beginning/index.mdx | 9 | ||||
| -rw-r--r-- | src/content/events/second/index.mdx | 8 | ||||
| -rw-r--r-- | src/lib/data-utils.ts | 54 | ||||
| -rw-r--r-- | src/lib/utils.ts | 10 | ||||
| -rw-r--r-- | src/pages/events/[...id].astro | 168 | ||||
| -rw-r--r-- | src/pages/events/[...page].astro | 60 | ||||
| -rw-r--r-- | src/pages/index.astro | 90 | ||||
| -rw-r--r-- | src/pages/types/[...id].astro | 53 | ||||
| -rw-r--r-- | src/pages/types/index.astro | 35 | ||||
| -rw-r--r-- | src/types.ts | 2 |
17 files changed, 665 insertions, 81 deletions
@@ -14,6 +14,7 @@ "@expressive-code/plugin-collapsible-sections": "^0.40.2", "@expressive-code/plugin-line-numbers": "^0.40.2", "@iconify-json/lucide": "^1.2.26", + "@radix-ui/react-checkbox": "^1.3.3", "@shikijs/rehype": "^3.4.0", "@tailwindcss/vite": "^4.0.7", "@types/react": "19.0.0", @@ -282,7 +283,7 @@ "@radix-ui/number": ["@radix-ui/number@1.1.1", "", {}, "sha512-MkKCwxlXTgz6CFoJx3pCwn07GKp36+aZyu/u2Ln2VrA5DcdyCZkASEDBTd8x5whTQQL5CiYf4prXKLcgQdv29g=="], - "@radix-ui/primitive": ["@radix-ui/primitive@1.1.2", "", {}, "sha512-XnbHrrprsNqZKQhStrSwgRUQzoCI1glLzdw79xiZPoofhGICeZRSQ3dIxAKH1gb3OHfNf4d6f+vAv3kil2eggA=="], + "@radix-ui/primitive": ["@radix-ui/primitive@1.1.3", "", {}, "sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg=="], "@radix-ui/react-accessible-icon": ["@radix-ui/react-accessible-icon@1.1.7", "", { "dependencies": { "@radix-ui/react-visually-hidden": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" } }, "sha512-XM+E4WXl0OqUJFovy6GjmxxFyx9opfCAIUku4dlKRd5YEPqt4kALOkQOp0Of6reHuUkJuiPBEc5k0o4z4lTC8A=="], @@ -296,7 +297,7 @@ "@radix-ui/react-avatar": ["@radix-ui/react-avatar@1.1.10", "", { "dependencies": { "@radix-ui/react-context": "1.1.2", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-is-hydrated": "0.1.0", "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" } }, "sha512-V8piFfWapM5OmNCXTzVQY+E1rDa53zY+MQ4Y7356v4fFz6vqCyUtIz2rUD44ZEdwg78/jKmMJHj07+C/Z/rcog=="], - "@radix-ui/react-checkbox": ["@radix-ui/react-checkbox@1.3.2", "", { "dependencies": { "@radix-ui/primitive": "1.1.2", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-presence": "1.1.4", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-controllable-state": "1.2.2", "@radix-ui/react-use-previous": "1.1.1", "@radix-ui/react-use-size": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" } }, "sha512-yd+dI56KZqawxKZrJ31eENUwqc1QSqg4OZ15rybGjF2ZNwMO+wCyHzAVLRp9qoYJf7kYy0YpZ2b0JCzJ42HZpA=="], + "@radix-ui/react-checkbox": ["@radix-ui/react-checkbox@1.3.3", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-presence": "1.1.5", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-controllable-state": "1.2.2", "@radix-ui/react-use-previous": "1.1.1", "@radix-ui/react-use-size": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-wBbpv+NQftHDdG86Qc0pIyXk5IR3tM8Vd0nWLKDcX8nNn4nXFOFwsKuqw2okA/1D/mpaAkmuyndrPJTYDNZtFw=="], "@radix-ui/react-collapsible": ["@radix-ui/react-collapsible@1.1.11", "", { "dependencies": { "@radix-ui/primitive": "1.1.2", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-presence": "1.1.4", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-controllable-state": "1.2.2", "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" } }, "sha512-2qrRsVGSCYasSz1RFOorXwl0H7g7J1frQtgpQgYrt+MOidtPAINHn9CPovQXb83r8ahapdx3Tu0fa/pdFFSdPg=="], @@ -344,7 +345,7 @@ "@radix-ui/react-portal": ["@radix-ui/react-portal@1.1.9", "", { "dependencies": { "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" } }, "sha512-bpIxvq03if6UNwXZ+HTK71JLh4APvnXntDc6XOX8UVq4XQOVl7lwok0AvIl+b8zgCw3fSaVTZMpAPPagXbKmHQ=="], - "@radix-ui/react-presence": ["@radix-ui/react-presence@1.1.4", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" } }, "sha512-ueDqRbdc4/bkaQT3GIpLQssRlFgWaL/U2z/S31qRwwLWoxHLgry3SIfCwhxeQNbirEUXFa+lq3RL3oBYXtcmIA=="], + "@radix-ui/react-presence": ["@radix-ui/react-presence@1.1.5", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-/jfEwNDdQVBCNvjkGit4h6pMOzq8bHkopq458dPt2lMjx+eBQUohZNG9A7DtO/O5ukSbxuaNGXMjHicgwy6rQQ=="], "@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.3", "", { "dependencies": { "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" } }, "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ=="], @@ -1466,9 +1467,83 @@ "@iconify/tools/svgo": ["svgo@3.3.2", "", { "dependencies": { "@trysound/sax": "0.2.0", "commander": "^7.2.0", "css-select": "^5.1.0", "css-tree": "^2.3.1", "css-what": "^6.1.0", "csso": "^5.0.5", "picocolors": "^1.0.0" }, "bin": "bin/svgo" }, "sha512-OoohrmuUlBs8B8o6MB2Aevn+pRIH9zDALSR+6hhqVfa6fRwG/Qw9VUMSMW9VNg2CFc/MTIfabtdOVl9ODIJjpw=="], - "@rollup/pluginutils/estree-walker": ["estree-walker@2.0.2", "", {}, "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="], + "@radix-ui/react-accordion/@radix-ui/primitive": ["@radix-ui/primitive@1.1.2", "", {}, "sha512-XnbHrrprsNqZKQhStrSwgRUQzoCI1glLzdw79xiZPoofhGICeZRSQ3dIxAKH1gb3OHfNf4d6f+vAv3kil2eggA=="], + + "@radix-ui/react-alert-dialog/@radix-ui/primitive": ["@radix-ui/primitive@1.1.2", "", {}, "sha512-XnbHrrprsNqZKQhStrSwgRUQzoCI1glLzdw79xiZPoofhGICeZRSQ3dIxAKH1gb3OHfNf4d6f+vAv3kil2eggA=="], + + "@radix-ui/react-collapsible/@radix-ui/primitive": ["@radix-ui/primitive@1.1.2", "", {}, "sha512-XnbHrrprsNqZKQhStrSwgRUQzoCI1glLzdw79xiZPoofhGICeZRSQ3dIxAKH1gb3OHfNf4d6f+vAv3kil2eggA=="], + + "@radix-ui/react-collapsible/@radix-ui/react-presence": ["@radix-ui/react-presence@1.1.4", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" } }, "sha512-ueDqRbdc4/bkaQT3GIpLQssRlFgWaL/U2z/S31qRwwLWoxHLgry3SIfCwhxeQNbirEUXFa+lq3RL3oBYXtcmIA=="], + + "@radix-ui/react-context-menu/@radix-ui/primitive": ["@radix-ui/primitive@1.1.2", "", {}, "sha512-XnbHrrprsNqZKQhStrSwgRUQzoCI1glLzdw79xiZPoofhGICeZRSQ3dIxAKH1gb3OHfNf4d6f+vAv3kil2eggA=="], + + "@radix-ui/react-dialog/@radix-ui/primitive": ["@radix-ui/primitive@1.1.2", "", {}, "sha512-XnbHrrprsNqZKQhStrSwgRUQzoCI1glLzdw79xiZPoofhGICeZRSQ3dIxAKH1gb3OHfNf4d6f+vAv3kil2eggA=="], + + "@radix-ui/react-dialog/@radix-ui/react-presence": ["@radix-ui/react-presence@1.1.4", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" } }, "sha512-ueDqRbdc4/bkaQT3GIpLQssRlFgWaL/U2z/S31qRwwLWoxHLgry3SIfCwhxeQNbirEUXFa+lq3RL3oBYXtcmIA=="], + + "@radix-ui/react-dismissable-layer/@radix-ui/primitive": ["@radix-ui/primitive@1.1.2", "", {}, "sha512-XnbHrrprsNqZKQhStrSwgRUQzoCI1glLzdw79xiZPoofhGICeZRSQ3dIxAKH1gb3OHfNf4d6f+vAv3kil2eggA=="], + + "@radix-ui/react-dropdown-menu/@radix-ui/primitive": ["@radix-ui/primitive@1.1.2", "", {}, "sha512-XnbHrrprsNqZKQhStrSwgRUQzoCI1glLzdw79xiZPoofhGICeZRSQ3dIxAKH1gb3OHfNf4d6f+vAv3kil2eggA=="], + + "@radix-ui/react-form/@radix-ui/primitive": ["@radix-ui/primitive@1.1.2", "", {}, "sha512-XnbHrrprsNqZKQhStrSwgRUQzoCI1glLzdw79xiZPoofhGICeZRSQ3dIxAKH1gb3OHfNf4d6f+vAv3kil2eggA=="], + + "@radix-ui/react-hover-card/@radix-ui/primitive": ["@radix-ui/primitive@1.1.2", "", {}, "sha512-XnbHrrprsNqZKQhStrSwgRUQzoCI1glLzdw79xiZPoofhGICeZRSQ3dIxAKH1gb3OHfNf4d6f+vAv3kil2eggA=="], + + "@radix-ui/react-hover-card/@radix-ui/react-presence": ["@radix-ui/react-presence@1.1.4", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" } }, "sha512-ueDqRbdc4/bkaQT3GIpLQssRlFgWaL/U2z/S31qRwwLWoxHLgry3SIfCwhxeQNbirEUXFa+lq3RL3oBYXtcmIA=="], + + "@radix-ui/react-menu/@radix-ui/primitive": ["@radix-ui/primitive@1.1.2", "", {}, "sha512-XnbHrrprsNqZKQhStrSwgRUQzoCI1glLzdw79xiZPoofhGICeZRSQ3dIxAKH1gb3OHfNf4d6f+vAv3kil2eggA=="], + + "@radix-ui/react-menu/@radix-ui/react-presence": ["@radix-ui/react-presence@1.1.4", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" } }, "sha512-ueDqRbdc4/bkaQT3GIpLQssRlFgWaL/U2z/S31qRwwLWoxHLgry3SIfCwhxeQNbirEUXFa+lq3RL3oBYXtcmIA=="], + + "@radix-ui/react-menubar/@radix-ui/primitive": ["@radix-ui/primitive@1.1.2", "", {}, "sha512-XnbHrrprsNqZKQhStrSwgRUQzoCI1glLzdw79xiZPoofhGICeZRSQ3dIxAKH1gb3OHfNf4d6f+vAv3kil2eggA=="], + + "@radix-ui/react-navigation-menu/@radix-ui/primitive": ["@radix-ui/primitive@1.1.2", "", {}, "sha512-XnbHrrprsNqZKQhStrSwgRUQzoCI1glLzdw79xiZPoofhGICeZRSQ3dIxAKH1gb3OHfNf4d6f+vAv3kil2eggA=="], + + "@radix-ui/react-navigation-menu/@radix-ui/react-presence": ["@radix-ui/react-presence@1.1.4", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" } }, "sha512-ueDqRbdc4/bkaQT3GIpLQssRlFgWaL/U2z/S31qRwwLWoxHLgry3SIfCwhxeQNbirEUXFa+lq3RL3oBYXtcmIA=="], + + "@radix-ui/react-one-time-password-field/@radix-ui/primitive": ["@radix-ui/primitive@1.1.2", "", {}, "sha512-XnbHrrprsNqZKQhStrSwgRUQzoCI1glLzdw79xiZPoofhGICeZRSQ3dIxAKH1gb3OHfNf4d6f+vAv3kil2eggA=="], + + "@radix-ui/react-password-toggle-field/@radix-ui/primitive": ["@radix-ui/primitive@1.1.2", "", {}, "sha512-XnbHrrprsNqZKQhStrSwgRUQzoCI1glLzdw79xiZPoofhGICeZRSQ3dIxAKH1gb3OHfNf4d6f+vAv3kil2eggA=="], + + "@radix-ui/react-popover/@radix-ui/primitive": ["@radix-ui/primitive@1.1.2", "", {}, "sha512-XnbHrrprsNqZKQhStrSwgRUQzoCI1glLzdw79xiZPoofhGICeZRSQ3dIxAKH1gb3OHfNf4d6f+vAv3kil2eggA=="], + + "@radix-ui/react-popover/@radix-ui/react-presence": ["@radix-ui/react-presence@1.1.4", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" } }, "sha512-ueDqRbdc4/bkaQT3GIpLQssRlFgWaL/U2z/S31qRwwLWoxHLgry3SIfCwhxeQNbirEUXFa+lq3RL3oBYXtcmIA=="], + + "@radix-ui/react-radio-group/@radix-ui/primitive": ["@radix-ui/primitive@1.1.2", "", {}, "sha512-XnbHrrprsNqZKQhStrSwgRUQzoCI1glLzdw79xiZPoofhGICeZRSQ3dIxAKH1gb3OHfNf4d6f+vAv3kil2eggA=="], + + "@radix-ui/react-radio-group/@radix-ui/react-presence": ["@radix-ui/react-presence@1.1.4", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" } }, "sha512-ueDqRbdc4/bkaQT3GIpLQssRlFgWaL/U2z/S31qRwwLWoxHLgry3SIfCwhxeQNbirEUXFa+lq3RL3oBYXtcmIA=="], + + "@radix-ui/react-roving-focus/@radix-ui/primitive": ["@radix-ui/primitive@1.1.2", "", {}, "sha512-XnbHrrprsNqZKQhStrSwgRUQzoCI1glLzdw79xiZPoofhGICeZRSQ3dIxAKH1gb3OHfNf4d6f+vAv3kil2eggA=="], + + "@radix-ui/react-scroll-area/@radix-ui/primitive": ["@radix-ui/primitive@1.1.2", "", {}, "sha512-XnbHrrprsNqZKQhStrSwgRUQzoCI1glLzdw79xiZPoofhGICeZRSQ3dIxAKH1gb3OHfNf4d6f+vAv3kil2eggA=="], - "ansi-align/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], + "@radix-ui/react-scroll-area/@radix-ui/react-presence": ["@radix-ui/react-presence@1.1.4", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" } }, "sha512-ueDqRbdc4/bkaQT3GIpLQssRlFgWaL/U2z/S31qRwwLWoxHLgry3SIfCwhxeQNbirEUXFa+lq3RL3oBYXtcmIA=="], + + "@radix-ui/react-select/@radix-ui/primitive": ["@radix-ui/primitive@1.1.2", "", {}, "sha512-XnbHrrprsNqZKQhStrSwgRUQzoCI1glLzdw79xiZPoofhGICeZRSQ3dIxAKH1gb3OHfNf4d6f+vAv3kil2eggA=="], + + "@radix-ui/react-slider/@radix-ui/primitive": ["@radix-ui/primitive@1.1.2", "", {}, "sha512-XnbHrrprsNqZKQhStrSwgRUQzoCI1glLzdw79xiZPoofhGICeZRSQ3dIxAKH1gb3OHfNf4d6f+vAv3kil2eggA=="], + + "@radix-ui/react-switch/@radix-ui/primitive": ["@radix-ui/primitive@1.1.2", "", {}, "sha512-XnbHrrprsNqZKQhStrSwgRUQzoCI1glLzdw79xiZPoofhGICeZRSQ3dIxAKH1gb3OHfNf4d6f+vAv3kil2eggA=="], + + "@radix-ui/react-tabs/@radix-ui/primitive": ["@radix-ui/primitive@1.1.2", "", {}, "sha512-XnbHrrprsNqZKQhStrSwgRUQzoCI1glLzdw79xiZPoofhGICeZRSQ3dIxAKH1gb3OHfNf4d6f+vAv3kil2eggA=="], + + "@radix-ui/react-tabs/@radix-ui/react-presence": ["@radix-ui/react-presence@1.1.4", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" } }, "sha512-ueDqRbdc4/bkaQT3GIpLQssRlFgWaL/U2z/S31qRwwLWoxHLgry3SIfCwhxeQNbirEUXFa+lq3RL3oBYXtcmIA=="], + + "@radix-ui/react-toast/@radix-ui/primitive": ["@radix-ui/primitive@1.1.2", "", {}, "sha512-XnbHrrprsNqZKQhStrSwgRUQzoCI1glLzdw79xiZPoofhGICeZRSQ3dIxAKH1gb3OHfNf4d6f+vAv3kil2eggA=="], + + "@radix-ui/react-toast/@radix-ui/react-presence": ["@radix-ui/react-presence@1.1.4", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" } }, "sha512-ueDqRbdc4/bkaQT3GIpLQssRlFgWaL/U2z/S31qRwwLWoxHLgry3SIfCwhxeQNbirEUXFa+lq3RL3oBYXtcmIA=="], + + "@radix-ui/react-toggle/@radix-ui/primitive": ["@radix-ui/primitive@1.1.2", "", {}, "sha512-XnbHrrprsNqZKQhStrSwgRUQzoCI1glLzdw79xiZPoofhGICeZRSQ3dIxAKH1gb3OHfNf4d6f+vAv3kil2eggA=="], + + "@radix-ui/react-toggle-group/@radix-ui/primitive": ["@radix-ui/primitive@1.1.2", "", {}, "sha512-XnbHrrprsNqZKQhStrSwgRUQzoCI1glLzdw79xiZPoofhGICeZRSQ3dIxAKH1gb3OHfNf4d6f+vAv3kil2eggA=="], + + "@radix-ui/react-toolbar/@radix-ui/primitive": ["@radix-ui/primitive@1.1.2", "", {}, "sha512-XnbHrrprsNqZKQhStrSwgRUQzoCI1glLzdw79xiZPoofhGICeZRSQ3dIxAKH1gb3OHfNf4d6f+vAv3kil2eggA=="], + + "@radix-ui/react-tooltip/@radix-ui/primitive": ["@radix-ui/primitive@1.1.2", "", {}, "sha512-XnbHrrprsNqZKQhStrSwgRUQzoCI1glLzdw79xiZPoofhGICeZRSQ3dIxAKH1gb3OHfNf4d6f+vAv3kil2eggA=="], + + "@radix-ui/react-tooltip/@radix-ui/react-presence": ["@radix-ui/react-presence@1.1.4", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" } }, "sha512-ueDqRbdc4/bkaQT3GIpLQssRlFgWaL/U2z/S31qRwwLWoxHLgry3SIfCwhxeQNbirEUXFa+lq3RL3oBYXtcmIA=="], + + "@rollup/pluginutils/estree-walker": ["estree-walker@2.0.2", "", {}, "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="], "anymatch/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="], @@ -1478,8 +1553,6 @@ "boxen/string-width": ["string-width@7.2.0", "", { "dependencies": { "emoji-regex": "^10.3.0", "get-east-asian-width": "^1.0.0", "strip-ansi": "^7.1.0" } }, "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ=="], - "cliui/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], - "cliui/wrap-ansi": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="], "csso/css-tree": ["css-tree@2.2.1", "", { "dependencies": { "mdn-data": "2.0.28", "source-map-js": "^1.0.1" } }, "sha512-OA0mILzGc1kCOCSJerOeqDxDQ4HOh+G8NbOJFOTgOCzpw7fCBubk0fEyxp8AgOL/jvLgYA/uV0cMbe43ElF1JA=="], @@ -1500,15 +1573,15 @@ "prompts/kleur": ["kleur@3.0.3", "", {}, "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w=="], - "sharp/semver": ["semver@7.7.3", "", { "bin": "bin/semver.js" }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="], + "radix-ui/@radix-ui/primitive": ["@radix-ui/primitive@1.1.2", "", {}, "sha512-XnbHrrprsNqZKQhStrSwgRUQzoCI1glLzdw79xiZPoofhGICeZRSQ3dIxAKH1gb3OHfNf4d6f+vAv3kil2eggA=="], - "sitemap/@types/node": ["@types/node@17.0.45", "", {}, "sha512-w+tIMs3rq2afQdsPJlODhoUEKzFP1ayaoyl1CcnwtIlsVe7K7bA1NGm4s3PraqTLlXnbIN84zuBlxBWo1u9BLw=="], + "radix-ui/@radix-ui/react-checkbox": ["@radix-ui/react-checkbox@1.3.2", "", { "dependencies": { "@radix-ui/primitive": "1.1.2", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-presence": "1.1.4", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-controllable-state": "1.2.2", "@radix-ui/react-use-previous": "1.1.1", "@radix-ui/react-use-size": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" } }, "sha512-yd+dI56KZqawxKZrJ31eENUwqc1QSqg4OZ15rybGjF2ZNwMO+wCyHzAVLRp9qoYJf7kYy0YpZ2b0JCzJ42HZpA=="], - "string-width/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], + "radix-ui/@radix-ui/react-presence": ["@radix-ui/react-presence@1.1.4", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" } }, "sha512-ueDqRbdc4/bkaQT3GIpLQssRlFgWaL/U2z/S31qRwwLWoxHLgry3SIfCwhxeQNbirEUXFa+lq3RL3oBYXtcmIA=="], - "typescript-auto-import-cache/semver": ["semver@7.7.2", "", { "bin": "bin/semver.js" }, "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA=="], + "sitemap/@types/node": ["@types/node@17.0.45", "", {}, "sha512-w+tIMs3rq2afQdsPJlODhoUEKzFP1ayaoyl1CcnwtIlsVe7K7bA1NGm4s3PraqTLlXnbIN84zuBlxBWo1u9BLw=="], - "unifont/css-tree": ["css-tree@3.1.0", "", { "dependencies": { "mdn-data": "2.12.2", "source-map-js": "^1.0.1" } }, "sha512-0eW44TGN5SQXU1mWSkKwFstI/22X2bG1nYzZTYMAWjylYURhse752YgbE4Cx46AC+bAvI+/dYTPRk1LqSUnu6w=="], + "typescript-auto-import-cache/semver": ["semver@7.7.2", "", { "bin": "bin/semver.js" }, "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA=="], "unstorage/chokidar": ["chokidar@5.0.0", "", { "dependencies": { "readdirp": "^5.0.0" } }, "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw=="], @@ -1550,26 +1623,16 @@ "@iconify/tools/svgo/css-tree": ["css-tree@2.3.1", "", { "dependencies": { "mdn-data": "2.0.30", "source-map-js": "^1.0.1" } }, "sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw=="], - "ansi-align/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], - - "ansi-align/string-width/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], - "boxen/string-width/emoji-regex": ["emoji-regex@10.4.0", "", {}, "sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw=="], "boxen/string-width/strip-ansi": ["strip-ansi@7.1.0", "", { "dependencies": { "ansi-regex": "^6.0.1" } }, "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ=="], - "cliui/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], - "cliui/wrap-ansi/ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], "csso/css-tree/mdn-data": ["mdn-data@2.0.28", "", {}, "sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g=="], "mlly/pkg-types/confbox": ["confbox@0.1.8", "", {}, "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w=="], - "string-width/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], - - "unifont/css-tree/mdn-data": ["mdn-data@2.12.2", "", {}, "sha512-IEn+pegP1aManZuckezWCO+XZQDplx1366JoVhTpMpBB1sPey/SbveZQUosKiKiGYjg1wH4pMlNgXbCiYgihQA=="], - "unstorage/chokidar/readdirp": ["readdirp@5.0.0", "", {}, "sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ=="], "widest-line/string-width/emoji-regex": ["emoji-regex@10.4.0", "", {}, "sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw=="], @@ -1586,8 +1649,6 @@ "@iconify/tools/svgo/css-tree/mdn-data": ["mdn-data@2.0.30", "", {}, "sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA=="], - "ansi-align/string-width/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], - "boxen/string-width/strip-ansi/ansi-regex": ["ansi-regex@6.1.0", "", {}, "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA=="], "widest-line/string-width/strip-ansi/ansi-regex": ["ansi-regex@6.1.0", "", {}, "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA=="], diff --git a/package.json b/package.json index cb8cb21..835cb3e 100644 --- a/package.json +++ b/package.json @@ -21,11 +21,12 @@ "@expressive-code/plugin-collapsible-sections": "^0.40.2", "@expressive-code/plugin-line-numbers": "^0.40.2", "@iconify-json/lucide": "^1.2.26", + "@radix-ui/react-checkbox": "^1.3.3", + "@shikijs/rehype": "^3.4.0", "@tailwindcss/vite": "^4.0.7", "@types/react": "19.0.0", "@types/react-dom": "19.0.0", "astro": "^5.7.13", - "rehype-expressive-code": "^0.40.2", "astro-icon": "^1.1.5", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", @@ -33,9 +34,9 @@ "radix-ui": "^1.3.4", "react": "19.0.0", "react-dom": "19.0.0", + "rehype-expressive-code": "^0.40.2", "rehype-external-links": "^3.0.0", "rehype-katex": "^7.0.1", - "@shikijs/rehype": "^3.4.0", "remark-emoji": "^5.0.1", "remark-math": "^6.0.0", "tailwind-merge": "^3.3.0", diff --git a/src/components/EventCard.astro b/src/components/EventCard.astro new file mode 100644 index 0000000..b615c2d --- /dev/null +++ b/src/components/EventCard.astro @@ -0,0 +1,63 @@ +--- +import { Badge } from '@/components/ui/badge' +import { formatDate, iconMappings } from '@/lib/utils' +import { Icon } from 'astro-icon/components' +import { Image } from 'astro:assets' +import type { CollectionEntry } from 'astro:content' +import Link from './Link.astro' + +interface Props { + entry: CollectionEntry<'events'> +} + +const { entry } = Astro.props +const formattedDate = formatDate(entry.data.date) +--- + +<div + class="hover:bg-muted/50 rounded-xl border p-4 transition-colors duration-300 ease-in-out" +> + <Link + href={`/${entry.collection}/${entry.id}`} + class="flex flex-col gap-4 sm:flex-row" + > + { + entry.data.image && ( + <div class="w-full sm:max-w-3xs sm:shrink-0"> + <Image + src={entry.data.image} + alt={entry.data.title} + width={1200} + height={630} + class="object-cover" + /> + </div> + ) + } + + <div class="grow"> + <h3 class="mb-1 text-lg font-medium">{entry.data.title}</h3> + <p class="text-muted-foreground mb-2 text-sm">{entry.data.description}</p> + + <div + class="text-muted-foreground mb-2 flex flex-wrap items-center gap-x-2 text-xs" + > + <span>{formattedDate}</span> + </div> + + { + entry.data.type && ( + <div class="flex flex-wrap gap-2"> + <Badge variant="muted" className="flex items-center gap-x-1"> + <Icon + name={iconMappings[entry.data.type] || 'lucide:hash'} + class="size-3" + /> + {entry.data.type} + </Badge> + </div> + ) + } + </div> + </Link> +</div> diff --git a/src/components/EventNavigation.astro b/src/components/EventNavigation.astro new file mode 100644 index 0000000..f1b76ce --- /dev/null +++ b/src/components/EventNavigation.astro @@ -0,0 +1,54 @@ +--- +import Link from '@/components/Link.astro' +import { buttonVariants } from '@/components/ui/button' +import { cn } from '@/lib/utils' +import { Icon } from 'astro-icon/components' + +const { newerEvent, olderEvent } = Astro.props +--- + +<nav class={cn('col-start-2 grid grid-cols-1 gap-4', 'sm:grid-cols-2')}> + <Link + href={olderEvent ? `/events/${olderEvent.id}#event-title` : '#'} + class={cn( + buttonVariants({ variant: 'outline' }), + 'rounded-lg group flex items-center justify-start size-full', + !olderEvent && 'pointer-events-none opacity-50 cursor-not-allowed', + )} + aria-disabled={!olderEvent} + > + <Icon + name="lucide:arrow-left" + class="mr-2 size-4 transition-transform group-hover:-translate-x-1" + /> + <div class="flex flex-col items-start overflow-hidden text-wrap"> + <span class="text-muted-foreground text-left text-xs"> + Previous Event + </span> + <span class="w-full text-left text-sm text-balance text-ellipsis"> + {olderEvent?.data.title || "You're at the oldest event!"} + </span> + </div> + </Link> + + <Link + href={newerEvent ? `/events/${newerEvent.id}#event-title` : '#'} + class={cn( + buttonVariants({ variant: 'outline' }), + 'rounded-lg group flex items-center justify-end size-full', + !newerEvent && 'pointer-events-none opacity-50 cursor-not-allowed', + )} + aria-disabled={!newerEvent} + > + <div class="flex flex-col items-end overflow-hidden text-wrap"> + <span class="text-muted-foreground text-right text-xs"> Next Event </span> + <span class="w-full text-right text-sm text-balance text-ellipsis"> + {newerEvent?.data.title || "You're at the newest event!"} + </span> + </div> + <Icon + name="lucide:arrow-right" + class="ml-2 size-4 transition-transform group-hover:translate-x-1" + /> + </Link> +</nav> diff --git a/src/components/Footer.astro b/src/components/Footer.astro index 7ff1e50..6b37226 100644 --- a/src/components/Footer.astro +++ b/src/components/Footer.astro @@ -15,10 +15,10 @@ import SocialIcons from './SocialIcons.astro' <Separator orientation="vertical" className="hidden h-4! sm:block" /> <p class="text-muted-foreground text-sm"> Made with 🤍 by <Link - href="https://github.com/jktrn" + href="https://codeberg.org/fosscet/" class="text-foreground" external - underline>enscribe</Link + underline>fosscet</Link >! </p> </div> diff --git a/src/consts.ts b/src/consts.ts index 53d9f3a..c66ef3e 100644 --- a/src/consts.ts +++ b/src/consts.ts @@ -8,7 +8,9 @@ export const SITE: Site = { author: 'fosscet', locale: 'en-US', featuredPostCount: 2, + featuredEventsCount: 2, postsPerPage: 3, + eventsPerPage: 3, } export const NAV_LINKS: SocialLink[] = [ @@ -24,6 +26,10 @@ export const NAV_LINKS: SocialLink[] = [ href: '/about', label: 'about', }, + { + href: '/events', + label: 'events', + }, ] export const SOCIAL_LINKS: SocialLink[] = [ diff --git a/src/content.config.ts b/src/content.config.ts index ed8186c..1c01708 100644 --- a/src/content.config.ts +++ b/src/content.config.ts @@ -1,6 +1,20 @@ import { glob } from 'astro/loaders' import { defineCollection, z } from 'astro:content' +const events = defineCollection({ + loader: glob({ pattern: '**/*.{md,mdx}', base: './src/content/events' }), + schema: ({ image }) => + z.object({ + title: z.string(), + description: z.string(), + date: z.coerce.date(), + order: z.number().optional(), + image: image().optional(), + type: z.string().optional(), + draft: z.boolean().optional(), + }), +}) + const blog = defineCollection({ loader: glob({ pattern: '**/*.{md,mdx}', base: './src/content/blog' }), schema: ({ image }) => @@ -46,4 +60,4 @@ const projects = defineCollection({ }), }) -export const collections = { blog, authors, projects } +export const collections = { events, blog, authors, projects } diff --git a/src/content/events/beginning/index.mdx b/src/content/events/beginning/index.mdx new file mode 100644 index 0000000..7427578 --- /dev/null +++ b/src/content/events/beginning/index.mdx @@ -0,0 +1,9 @@ +--- +title: '2024 Event' +description: 'This is a dummy event conducted in the year 2024.' +date: 2024-06-01 +type: 'workshop' +--- + + +Just for testing purposes only diff --git a/src/content/events/second/index.mdx b/src/content/events/second/index.mdx new file mode 100644 index 0000000..79661e6 --- /dev/null +++ b/src/content/events/second/index.mdx @@ -0,0 +1,8 @@ +--- +title: '2025 Competition' +description: 'This is a competition event conducted in 2025.' +date: 2025-12-01 +type: 'competition' +--- + +I will prbbly create a component for adding the result of competition in the near future diff --git a/src/lib/data-utils.ts b/src/lib/data-utils.ts index f6e566a..ab0176f 100644 --- a/src/lib/data-utils.ts +++ b/src/lib/data-utils.ts @@ -21,6 +21,25 @@ export async function getAllPostsAndSubposts(): Promise< .sort((a, b) => b.data.date.valueOf() - a.data.date.valueOf()) } +export async function getAllEvents(): Promise<CollectionEntry<'events'>[]> { + const events = await getCollection('events') + return events + .sort((a, b) => b.data.date.valueOf() - a.data.date.valueOf()) +} + +export function groupEventsByYear( + events: CollectionEntry<'events'>[], +): Record<string, CollectionEntry<'events'>[]> { + return events.reduce( + (acc: Record<string, CollectionEntry<'events'>[]>, post) => { + const year = post.data.date.getFullYear().toString() + ;(acc[year] ??= []).push(post) + return acc + }, + {}, + ) +} + export async function getAllProjects(): Promise<CollectionEntry<'projects'>[]> { const projects = await getCollection('projects') return projects.sort((a, b) => { @@ -40,6 +59,14 @@ export async function getAllTags(): Promise<Map<string, number>> { }, new Map<string, number>()) } +export async function getAllTypes(): Promise<Map<string, number>> { + const events = await getAllEvents() + return events.reduce((acc, event) => { + acc.set(event.data.type, (acc.get(event.data.type) || 0) + 1) + return acc + }, new Map<string, number>()) +} + export async function getAdjacentPosts(currentId: string): Promise<{ newer: CollectionEntry<'blog'> | null older: CollectionEntry<'blog'> | null @@ -99,6 +126,19 @@ export async function getAdjacentPosts(currentId: string): Promise<{ } } +export async function getAdjacentEvents(currentId: string): Promise<{ + newer: CollectionEntry<'events'> | null + older: CollectionEntry<'events'> | null + parent: CollectionEntry<'events'> | null +}> { + const allEvents = await getAllEvents() + const currentIndex = allEvents.findIndex((event) => event.id === currentId) + const newer = currentIndex > 0 ? allEvents[currentIndex - 1] : null + const older = + currentIndex < allEvents.length - 1 ? allEvents[currentIndex + 1] : null + return { newer, older, parent: null } +} + export async function getPostsByAuthor( authorId: string, ): Promise<CollectionEntry<'blog'>[]> { @@ -113,6 +153,13 @@ export async function getPostsByTag( return posts.filter((post) => post.data.tags?.includes(tag)) } +export async function getEventsByType( + type: string, +): Promise<CollectionEntry<'events'>[]> { + const events = await getAllEvents() + return events.filter((event) => event.data.type === type) +} + export async function getRecentPosts( count: number, ): Promise<CollectionEntry<'blog'>[]> { @@ -120,6 +167,13 @@ export async function getRecentPosts( return posts.slice(0, count) } +export async function getRecentEvents( + count: number, +): Promise<CollectionEntry<'events'>[]> { + const events = await getAllEvents() + return events.slice(0, count) +} + export async function getSortedTags(): Promise< { tag: string; count: number }[] > { diff --git a/src/lib/utils.ts b/src/lib/utils.ts index d4d8119..e2e005b 100644 --- a/src/lib/utils.ts +++ b/src/lib/utils.ts @@ -35,3 +35,13 @@ export function getHeadingMargin(depth: number): string { } return margins[depth] || '' } +export function capitalizeFirstLetter(val: string): string { + return String(val).charAt(0).toUpperCase() + String(val).slice(1); +} + +export const iconMappings = { + "workshop": "lucide:hammer", + "competition": "lucide:trophy", + "game": "lucide:gamepad-2", + "quiz": "lucide:brain" +} diff --git a/src/pages/events/[...id].astro b/src/pages/events/[...id].astro new file mode 100644 index 0000000..44d8ec4 --- /dev/null +++ b/src/pages/events/[...id].astro @@ -0,0 +1,168 @@ +--- +import Breadcrumbs from '@/components/Breadcrumbs.astro' +import EventNavigation from '@/components/EventNavigation.astro' +import TOCHeader from '@/components/TOCHeader.astro' +import TOCSidebar from '@/components/TOCSidebar.astro' +import { badgeVariants } from '@/components/ui/badge' +import { Button } from '@/components/ui/button' +import Layout from '@/layouts/Layout.astro' +import { + getAdjacentEvents, + getAllEvents, + getTOCSections, +} from '@/lib/data-utils' +import { formatDate, iconMappings } from '@/lib/utils' +import { Icon } from 'astro-icon/components' +import { Image } from 'astro:assets' +import { render } from 'astro:content' + +export async function getStaticPaths() { + const events = await getAllEvents() + return events.map((event) => ({ + params: { id: event.id }, + props: event, + })) +} + +const event = Astro.props +const currentPostId = Astro.params.id +const navigation = await getAdjacentEvents(currentPostId) +const { Content, headings } = await render(event) + +const tocSections = await getTOCSections(currentPostId) +--- + +<Layout> + { + headings?.length > 0 && + !(headings.length === 1 && headings[0].text === event.data.title) && ( + <TOCHeader slot="table-of-contents" headings={headings} /> + ) + } + + <section + class="grid grid-cols-[minmax(0px,1fr)_min(calc(var(--breakpoint-md)-2rem),100%)_minmax(0px,1fr)] gap-y-6" + > + <div class="col-start-2"> + <Breadcrumbs + items={[{ href: '/events', label: 'Events', icon: 'lucide:calendar' }]} + /> + </div> + + { + event.data.image && ( + <Image + src={event.data.image} + alt={event.data.title} + width={1200} + height={630} + class="col-span-full mx-auto w-full max-w-5xl object-cover" + /> + ) + } + + <section class="col-start-2 flex flex-col gap-y-6 text-center"> + <div class="flex flex-col"> + <h1 + class="mb-2 scroll-mt-31 text-3xl leading-tight font-medium sm:text-4xl" + id="event-title" + > + {event.data.title} + </h1> + + <div + class="text-muted-foreground divide-border mb-4 flex flex-col items-center justify-center divide-y text-xs sm:flex-row sm:flex-wrap sm:divide-x sm:divide-y-0 sm:text-sm" + > + <div + class="flex w-full items-center justify-center gap-2 py-2 sm:w-fit sm:px-2 sm:py-0 first:sm:pl-0 last:sm:pr-0" + > + <span>{formatDate(event.data.date)}</span> + </div> + </div> + <div class="flex flex-wrap justify-center gap-2"> + { + event.data.type && ( + <a + href={`/types/${event.data.type}`} + class={badgeVariants({ variant: 'muted' })} + > + <Icon + name={iconMappings[event.data.type] || 'lucide:hash'} + class="size-3" + /> + {event.data.type} + </a> + ) + } + </div> + </div> + </section> + + { + tocSections.length > 0 && ( + <TOCSidebar sections={tocSections} currentPostId={currentPostId} /> + ) + } + + <article class="prose col-start-2 max-w-none"> + <Content /> + </article> + + <EventNavigation + newerEvent={navigation.newer} + olderEvent={navigation.older} + /> + </section> + + <Button + variant="outline" + size="icon" + className="group fixed right-8 bottom-8 z-50 hidden" + id="scroll-to-top" + title="Scroll to top" + aria-label="Scroll to top" + > + <Icon + name="lucide:arrow-up" + class="mx-auto size-4 transition-all group-hover:-translate-y-0.5" + /> + </Button> + + <script> + document.addEventListener('astro:page-load', () => { + const scrollToTopButton = document.getElementById('scroll-to-top') + const footer = document.querySelector('footer') + + if (scrollToTopButton && footer) { + scrollToTopButton.addEventListener('click', () => { + window.scrollTo({ top: 0, behavior: 'smooth' }) + }) + + window.addEventListener('scroll', () => { + const footerRect = footer.getBoundingClientRect() + const isFooterVisible = footerRect.top <= window.innerHeight + + scrollToTopButton.classList.toggle( + 'hidden', + window.scrollY <= 300 || isFooterVisible, + ) + }) + } + }) + </script> +</Layout> + +<script> + const ensureKatexStyles = () => { + if (!document.querySelector('.katex')) return + if (document.querySelector('link[href*="katex.min.css"]')) return + + const link = document.createElement('link') + link.rel = 'stylesheet' + link.href = 'https://cdn.jsdelivr.net/npm/katex@0.16.22/dist/katex.min.css' + document.head.appendChild(link) + } + + document.addEventListener('astro:page-load', ensureKatexStyles) + document.addEventListener('astro:after-swap', ensureKatexStyles) +</script> diff --git a/src/pages/events/[...page].astro b/src/pages/events/[...page].astro new file mode 100644 index 0000000..2625ab0 --- /dev/null +++ b/src/pages/events/[...page].astro @@ -0,0 +1,60 @@ +--- +import Breadcrumbs from '@/components/Breadcrumbs.astro' +import EventCard from '@/components/EventCard.astro' +import PageHead from '@/components/PageHead.astro' +import PaginationComponent from '@/components/ui/pagination' +import { SITE } from '@/consts' +import Layout from '@/layouts/Layout.astro' +import { getAllEvents, groupEventsByYear } from '@/lib/data-utils' +import type { PaginateFunction } from 'astro' + +export async function getStaticPaths({ + paginate, +}: { + paginate: PaginateFunction +}) { + const allEvents = await getAllEvents() + return paginate(allEvents, { pageSize: SITE.eventsPerPage }) +} + +const { page } = Astro.props + +const eventsByYear = groupEventsByYear(page.data) +const years = Object.keys(eventsByYear).sort( + (a, b) => parseInt(b) - parseInt(a), +) +--- + +<Layout class="max-w-3xl"> + <PageHead slot="head" title="Events" /> + <Breadcrumbs + items={[ + { label: 'Events', href: '/events', icon: 'lucide:calendar-range' }, + { label: `Page ${page.currentPage}`, icon: 'lucide:calendar' }, + ]} + /> + + <div class="flex min-h-[calc(100vh-18rem)] flex-col gap-y-8"> + { + years.map((year) => ( + <section class="flex flex-col gap-y-4"> + <div class="font-medium">{year}</div> + <ul class="flex flex-col gap-4"> + {eventsByYear[year].map((event) => ( + <li> + <EventCard entry={event} /> + </li> + ))} + </ul> + </section> + )) + } + </div> + + <PaginationComponent + currentPage={page.currentPage} + totalPages={page.lastPage} + baseUrl="/events/" + client:load + /> +</Layout> diff --git a/src/pages/index.astro b/src/pages/index.astro index 019283d..9549a7c 100644 --- a/src/pages/index.astro +++ b/src/pages/index.astro @@ -1,13 +1,15 @@ --- import BlogCard from '@/components/BlogCard.astro' +import EventCard from '@/components/EventCard.astro' import Link from '@/components/Link.astro' import PageHead from '@/components/PageHead.astro' import { buttonVariants } from '@/components/ui/button' import { SITE } from '@/consts' import Layout from '@/layouts/Layout.astro' -import { getRecentPosts } from '@/lib/data-utils' +import { getRecentPosts, getRecentEvents } from '@/lib/data-utils' const blog = await getRecentPosts(SITE.featuredPostCount) +const events = await getRecentEvents(SITE.featuredEventsCount) --- <Layout class="max-w-3xl"> @@ -16,69 +18,53 @@ const blog = await getRecentPosts(SITE.featuredPostCount) <div class="flex flex-col space-y-1.5 p-6"> <h3 class="text-3xl leading-none font-medium">er·u·dite</h3> <p class="text-muted-foreground text-sm"> - /ˈer(y)əˌdīt/ • <span class="font-medium">adjective</span> + /ˈer(y)əˌdīt/ • <span class="font-medium">adjective</span> </p> </div> - <div class="p-6 pt-0"> - <p class="text-muted-foreground mb-2 text-sm"> - astro-erudite is an opinionated, unstyled static blogging template built - with <Link - href="https://astro.build" - class="text-foreground" - external - underline>Astro</Link - >, <Link - href="https://tailwindcss.com" - class="text-foreground" - external - underline>Tailwind</Link - >, and <Link - href="https://ui.shadcn.com" - class="text-foreground" - external - underline>shadcn/ui</Link - >. Extraordinarily loosely based on the <Link - href="https://astro-micro.vercel.app/" - class="text-foreground" - external - underline>Astro Micro</Link - > theme. - </p> - <p class="text-muted-foreground text-sm"> - To use this template, check out the <Link - href="https://github.com/jktrn/astro-erudite" - class="text-foreground" - underline - external>GitHub</Link - > repository. To learn more about why this template exists, read this blog - post: <Link - href="/blog/the-state-of-static-blogs" - class="text-foreground" - underline>The State of Static Blogs in 2024</Link - >. - </p> + </section> + <section class="flex flex-col gap-y-4"> + <h2 class="text-2xl font-medium">Latest events</h2> + <ul class="flex flex-col gap-y-4"> + { + events.map((event) => ( + <li> + <EventCard entry={event} /> + </li> + )) + } + </ul> + <div class="flex justify-center"> + <Link + href="/events" + class={buttonVariants({ variant: 'ghost' }) + ' group'} + > + See all posts <span + class="ml-1.5 transition-transform group-hover:translate-x-1" + >→</span + > + </Link> </div> </section> <section class="flex flex-col gap-y-4"> <h2 class="text-2xl font-medium">Latest posts</h2> <ul class="flex flex-col gap-y-4"> { - blog.map((post) => ( - <li> - <BlogCard entry={post} /> - </li> - )) + blog.map((post) => ( + <li> + <BlogCard entry={post} /> + </li> + )) } </ul> <div class="flex justify-center"> <Link - href="/blog" - class={buttonVariants({ variant: 'ghost' }) + ' group'} - > - See all posts <span - class="ml-1.5 transition-transform group-hover:translate-x-1" - >→</span - > + href="/blog" + class={buttonVariants({ variant: 'ghost' }) + ' group'} + > + See all posts <span + class="ml-1.5 transition-transform group-hover:translate-x-1" + >→</span + > </Link> </div> </section> diff --git a/src/pages/types/[...id].astro b/src/pages/types/[...id].astro new file mode 100644 index 0000000..26640a9 --- /dev/null +++ b/src/pages/types/[...id].astro @@ -0,0 +1,53 @@ +--- +import Breadcrumbs from '@/components/Breadcrumbs.astro' +import PageHead from '@/components/PageHead.astro' +import Layout from '@/layouts/Layout.astro' +import { getAllTypes, getEventsByType } from '@/lib/data-utils' +import { capitalizeFirstLetter, iconMappings } from '@/lib/utils' +import EventCard from '../../components/EventCard.astro' + +export async function getStaticPaths() { + const typeMap = await getAllTypes() + const uniqueTags = Array.from(typeMap.keys()) + + return Promise.all( + uniqueTags.map(async (type) => { + const events = await getEventsByType(type) + return { + params: { id: type }, + props: { + type, + events, + }, + } + }), + ) +} + +const { type, events } = Astro.props +--- + +<Layout class="max-w-3xl"> + <PageHead + slot="head" + title={`${capitalizeFirstLetter(type)} Events`} + description={`A collection of ${type} events.`} + noindex + /> + <Breadcrumbs + items={[ + { href: '/types', label: 'Event types', icon: 'lucide:calendar' }, + { label: type, icon: iconMappings[type] || 'lucide:ticket' }, + ]} + /> + + <ul class="flex flex-col gap-y-4"> + { + events.map((event) => ( + <li> + <EventCard entry={event} /> + </li> + )) + } + </ul> +</Layout> diff --git a/src/pages/types/index.astro b/src/pages/types/index.astro new file mode 100644 index 0000000..c269c22 --- /dev/null +++ b/src/pages/types/index.astro @@ -0,0 +1,35 @@ +--- +import Breadcrumbs from '@/components/Breadcrumbs.astro' +import Link from '@/components/Link.astro' +import PageHead from '@/components/PageHead.astro' +import { badgeVariants } from '@/components/ui/badge' +import Layout from '@/layouts/Layout.astro' +import { getAllTypes } from '@/lib/data-utils' +import { iconMappings } from '@/lib/utils' +import { Icon } from 'astro-icon/components' + +const types = await getAllTypes() +--- + +<Layout class="max-w-3xl"> + <PageHead slot="head" title="Tags" /> + <Breadcrumbs items={[{ label: 'Tags', icon: 'lucide:tags' }]} /> + + <div class="flex flex-col gap-4"> + <div class="flex flex-wrap gap-2"> + {console.log([...types.entries()].map((a) => console.log(a)))} + { + [...types.entries()].map(([type, count]) => ( + <Link + href={`/types/${type}`} + class={badgeVariants({ variant: 'muted' })} + > + <Icon name={iconMappings[type] || 'lucide:hash'} class="size-3" /> + {type} + <span class="text-muted-foreground ml-1.5">({count})</span> + </Link> + )) + } + </div> + </div> +</Layout> diff --git a/src/types.ts b/src/types.ts index b03a955..4fedcdd 100644 --- a/src/types.ts +++ b/src/types.ts @@ -5,7 +5,9 @@ export type Site = { author: string locale: string featuredPostCount: number + featuredEventsCount: number postsPerPage: number + eventsPerPage: number } export type SocialLink = { |
