aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authornolawz <amananudhiu@gmail.com>2026-03-03 04:04:23 +0530
committernolawz <amananudhiu@gmail.com>2026-03-03 04:04:23 +0530
commit45026eee4c93bbbe6141dd23a664abe6e9ff3ca2 (patch)
tree3d5762cb539d88cf1fc464ab07f9ecd49182ad08
parentd41eff9eccf66644c4c60f53cb516bf079937a28 (diff)
Added events section and components required for that
-rw-r--r--bun.lock109
-rw-r--r--package.json5
-rw-r--r--src/components/EventCard.astro63
-rw-r--r--src/components/EventNavigation.astro54
-rw-r--r--src/components/Footer.astro4
-rw-r--r--src/consts.ts6
-rw-r--r--src/content.config.ts16
-rw-r--r--src/content/events/beginning/index.mdx9
-rw-r--r--src/content/events/second/index.mdx8
-rw-r--r--src/lib/data-utils.ts54
-rw-r--r--src/lib/utils.ts10
-rw-r--r--src/pages/events/[...id].astro168
-rw-r--r--src/pages/events/[...page].astro60
-rw-r--r--src/pages/index.astro90
-rw-r--r--src/pages/types/[...id].astro53
-rw-r--r--src/pages/types/index.astro35
-rw-r--r--src/types.ts2
17 files changed, 665 insertions, 81 deletions
diff --git a/bun.lock b/bun.lock
index 7bd4de1..fea5dad 100644
--- a/bun.lock
+++ b/bun.lock
@@ -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/ &bull; <span class="font-medium">adjective</span>
+ /ˈer(y)əˌdīt/ &bull; <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"
+ >&rarr;</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"
- >&rarr;</span
- >
+ href="/blog"
+ class={buttonVariants({ variant: 'ghost' }) + ' group'}
+ >
+ See all posts <span
+ class="ml-1.5 transition-transform group-hover:translate-x-1"
+ >&rarr;</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 = {