Set up multisite Dokploy deployment
Some checks are pending
Build / build (push) Waiting to run

This commit is contained in:
Neeldhara Misra 2026-06-13 19:45:59 +02:00
parent 56f49d988d
commit 80c51bdfea
75 changed files with 383 additions and 1838 deletions

9
.dockerignore Normal file
View file

@ -0,0 +1,9 @@
node_modules
dist
.astro
.git
.vscode
.DS_Store
npm-debug.log*
Dockerfile
docker-compose*.yml

View file

@ -0,0 +1,17 @@
name: Build
on:
push:
branches:
- main
pull_request:
jobs:
build:
runs-on: docker
container:
image: node:22
steps:
- uses: actions/checkout@v4
- run: npm ci
- run: npm run build

View file

@ -1,54 +1,102 @@
# Multi-Blog Subdomain Deployment Guide # Dokploy + Forgejo Deployment
This project deploys 12 separate blogs to individual subdomains using Netlify. This repo is set up as one Dockerized multisite Astro app. The build creates seven static sites and Nginx serves the correct one based on the incoming subdomain.
## Subdomain Structure ## Sites
Each blog collection gets its own subdomain: | Host | Static output | Content collection |
| ---------------------------- | ------------------ | ------------------------- |
| `research.neeldhara.blog` | `dist/research` | `src/content/research` |
| `vibes.neeldhara.blog` | `dist/vibes` | `src/content/vibes` |
| `puzzles.neeldhara.blog` | `dist/puzzles` | `src/content/puzzles` |
| `reflections.neeldhara.blog` | `dist/reflections` | `src/content/reflections` |
| `poetry.neeldhara.blog` | `dist/poetry` | `src/content/poetry` |
| `reviews.neeldhara.blog` | `dist/reviews` | `src/content/reviews` |
| `art.neeldhara.blog` | `dist/art` | `src/content/art` |
- `art.neeldhara.blog` → Art blog posts Each site has its own root feed at `/rss.xml`, for example:
- `bfs.neeldhara.blog` → BFS algorithm posts
- `dfs.neeldhara.blog` → DFS algorithm posts
- `problems.neeldhara.blog` → Problem-solving posts
- `puzzles.neeldhara.blog` → Puzzle posts
- `reviews.neeldhara.blog` → Review posts
- `reflections.neeldhara.blog` → Reflection posts
- `vibes.neeldhara.blog` → Vibes posts
- `workflows.neeldhara.blog` → Workflow posts
- `magic.neeldhara.blog` → Mathematical magic posts
- `contests.neeldhara.blog` → Contest posts
- `poetry.neeldhara.blog` → Poetry posts
- `blog.neeldhara.blog` → Main blog posts
## How It Works - `https://research.neeldhara.blog/rss.xml`
- `https://puzzles.neeldhara.blog/rss.xml`
1. **Main Site**: All content is built and deployed to `neeldhara.blog` ## Local Commands
2. **Subdomain Redirects**: Each subdomain (e.g., `art.neeldhara.blog`) redirects to the main site with the appropriate path (e.g., `neeldhara.blog/art/`)
3. **Netlify Configuration**: The `netlify.toml` file handles all the redirect rules
## Deployment Steps Build all sites:
1. **Build the site**: `npm run build` ```bash
2. **Deploy to Netlify**: Connect your Git repository to Netlify npm run build
3. **Configure domains**: Set up the custom domains in Netlify: ```
- Main site: `neeldhara.blog`
- Subdomains: `art.neeldhara.blog`, `bfs.neeldhara.blog`, etc.
4. **DNS Configuration**: Point all subdomains to your Netlify site
## Example URLs Build one site:
Local development: ```bash
- `http://localhost:4323/art/algorithmic-sketches` BLOG_SITE=puzzles npm run build:site
```
Production: Run one site locally:
- `https://art.neeldhara.blog/algorithmic-sketches`
- `https://neeldhara.blog/art/algorithmic-sketches` (direct access)
Both URLs serve the same content thanks to the redirect configuration. ```bash
BLOG_SITE=research npm run dev:site
```
## Testing Build the production container locally:
After deployment, verify that: ```bash
1. Each subdomain homepage loads correctly (e.g., `art.neeldhara.blog`) docker compose build
2. Individual blog posts load correctly (e.g., `art.neeldhara.blog/algorithmic-sketches`) ```
3. RSS feeds work (e.g., `art.neeldhara.blog/rss.xml`)
## Forgejo Setup
1. Create an empty Forgejo repository, for example `neeldhara/blogs`.
2. Point this local repo at Forgejo:
```bash
git remote set-url origin <forgejo-ssh-or-https-url>
git push -u origin main
```
3. Enable Forgejo Actions if you want CI. The workflow in `.forgejo/workflows/build.yml` runs `npm ci` and `npm run build` on pushes and pull requests.
Forgejo Actions uses GitHub-Actions-like workflow syntax, but it is not identical to GitHub Actions. Keep this workflow intentionally simple unless the runner setup is known.
## Dokploy Setup
Use the Docker Compose deployment path.
1. In Dokploy, create a new Compose app.
2. Connect it to the Forgejo repository. If Dokploy cannot access Forgejo directly, add a deploy key or use the repository HTTPS URL with credentials configured in Dokploy.
3. Use the repository root as the build context.
4. Use `docker-compose.yml` from this repo.
5. Route all seven domains to the `blogs` service on port `80`.
6. Enable HTTPS certificates for each domain in Dokploy.
The container builds all seven sites with `npm run build`. Nginx reads the `Host` header and serves the matching directory using `deploy/nginx.conf`.
## DNS
Point these DNS records at the Dokploy host:
```text
research.neeldhara.blog
vibes.neeldhara.blog
puzzles.neeldhara.blog
reflections.neeldhara.blog
poetry.neeldhara.blog
reviews.neeldhara.blog
art.neeldhara.blog
```
Use `A` records if pointing directly at the server IP, or `CNAME` records if you front Dokploy with another canonical hostname.
## Verification
After deployment:
```bash
curl -I https://research.neeldhara.blog/
curl -I https://research.neeldhara.blog/rss.xml
curl -I https://puzzles.neeldhara.blog/100-prisoners/
curl -I https://puzzles.neeldhara.blog/rss.xml
```
Expected result: `200 OK` for each.

19
Dockerfile Normal file
View file

@ -0,0 +1,19 @@
FROM node:22-alpine AS build
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci
COPY . .
RUN npm run build
FROM nginx:1.27-alpine AS runtime
COPY deploy/nginx.conf /etc/nginx/conf.d/default.conf
COPY --from=build /app/dist /usr/share/nginx/html
EXPOSE 80
HEALTHCHECK --interval=30s --timeout=3s --start-period=10s --retries=3 \
CMD wget -q --spider http://127.0.0.1/nginx-health || exit 1

View file

@ -6,12 +6,14 @@ import sitemap from "@astrojs/sitemap";
import react from "@astrojs/react"; import react from "@astrojs/react";
// @ts-ignore - TailwindCSS v4 Vite plugin has type compatibility issues with Astro // @ts-ignore - TailwindCSS v4 Vite plugin has type compatibility issues with Astro
import tailwindcss from "@tailwindcss/vite"; import tailwindcss from "@tailwindcss/vite";
import { ACTIVE_BLOG_SITE } from "./src/blog-sites.js";
// https://astro.build/config // https://astro.build/config
export default defineConfig({ export default defineConfig({
site: "https://neeldhara.blog", site: ACTIVE_BLOG_SITE.url,
integrations: [mdx(), sitemap(), react()], integrations: [mdx(), sitemap(), react()],
output: "static", output: "static",
outDir: `dist/${ACTIVE_BLOG_SITE.key}`,
vite: { vite: {
// @ts-ignore - Suppress TypeScript error for TailwindCSS v4 plugin compatibility // @ts-ignore - Suppress TypeScript error for TailwindCSS v4 plugin compatibility

36
deploy/nginx.conf Normal file
View file

@ -0,0 +1,36 @@
map $host $blog_site {
default research;
research.neeldhara.blog research;
vibes.neeldhara.blog vibes;
puzzles.neeldhara.blog puzzles;
reflections.neeldhara.blog reflections;
poetry.neeldhara.blog poetry;
reviews.neeldhara.blog reviews;
art.neeldhara.blog art;
}
server {
listen 80 default_server;
server_name _;
root /usr/share/nginx/html/$blog_site;
index index.html;
access_log /var/log/nginx/access.log;
error_log /var/log/nginx/error.log warn;
location = /nginx-health {
access_log off;
return 204;
}
location ~* \.(?:css|js|mjs|png|jpg|jpeg|gif|svg|webp|ico|woff2?)$ {
try_files $uri =404;
expires 1y;
add_header Cache-Control "public, immutable";
}
location / {
try_files $uri $uri/ /404.html;
}
}

13
docker-compose.yml Normal file
View file

@ -0,0 +1,13 @@
services:
blogs:
build:
context: .
restart: unless-stopped
expose:
- "80"
healthcheck:
test: ["CMD", "wget", "-q", "--spider", "http://127.0.0.1/nginx-health"]
interval: 30s
timeout: 3s
retries: 3
start_period: 10s

View file

@ -1,522 +0,0 @@
# Main site configuration
[build]
publish = "dist"
command = "npm run build"
# Rewrite rules for subdomain routing (preserves subdomain in location bar)
# Note: These require domain aliases to be configured in Netlify dashboard
# Static asset rewrites for all subdomains
[[redirects]]
from = "https://art.neeldhara.blog/_astro/*"
to = "/_astro/:splat"
status = 200
force = true
[[redirects]]
from = "https://art.neeldhara.blog/assets/*"
to = "/assets/:splat"
status = 200
force = true
# Asset rewrites for bfs subdomain
[[redirects]]
from = "https://bfs.neeldhara.blog/_astro/*"
to = "/_astro/:splat"
status = 200
force = true
[[redirects]]
from = "https://bfs.neeldhara.blog/assets/*"
to = "/assets/:splat"
status = 200
force = true
# Asset rewrites for dfs subdomain
[[redirects]]
from = "https://dfs.neeldhara.blog/_astro/*"
to = "/_astro/:splat"
status = 200
force = true
[[redirects]]
from = "https://dfs.neeldhara.blog/assets/*"
to = "/assets/:splat"
status = 200
force = true
# Asset rewrites for problems subdomain
[[redirects]]
from = "https://problems.neeldhara.blog/_astro/*"
to = "/_astro/:splat"
status = 200
force = true
[[redirects]]
from = "https://problems.neeldhara.blog/assets/*"
to = "/assets/:splat"
status = 200
force = true
# Asset rewrites for puzzles subdomain
[[redirects]]
from = "https://puzzles.neeldhara.blog/_astro/*"
to = "/_astro/:splat"
status = 200
force = true
[[redirects]]
from = "https://puzzles.neeldhara.blog/assets/*"
to = "/assets/:splat"
status = 200
force = true
# Asset rewrites for reviews subdomain
[[redirects]]
from = "https://reviews.neeldhara.blog/_astro/*"
to = "/_astro/:splat"
status = 200
force = true
[[redirects]]
from = "https://reviews.neeldhara.blog/assets/*"
to = "/assets/:splat"
status = 200
force = true
# Asset rewrites for reflections subdomain
[[redirects]]
from = "https://reflections.neeldhara.blog/_astro/*"
to = "/_astro/:splat"
status = 200
force = true
[[redirects]]
from = "https://reflections.neeldhara.blog/assets/*"
to = "/assets/:splat"
status = 200
force = true
# Asset rewrites for vibes subdomain
[[redirects]]
from = "https://vibes.neeldhara.blog/_astro/*"
to = "/_astro/:splat"
status = 200
force = true
[[redirects]]
from = "https://vibes.neeldhara.blog/assets/*"
to = "/assets/:splat"
status = 200
force = true
# Asset rewrites for workflows subdomain
[[redirects]]
from = "https://workflows.neeldhara.blog/_astro/*"
to = "/_astro/:splat"
status = 200
force = true
[[redirects]]
from = "https://workflows.neeldhara.blog/assets/*"
to = "/assets/:splat"
status = 200
force = true
# Asset rewrites for magic subdomain
[[redirects]]
from = "https://magic.neeldhara.blog/_astro/*"
to = "/_astro/:splat"
status = 200
force = true
[[redirects]]
from = "https://magic.neeldhara.blog/assets/*"
to = "/assets/:splat"
status = 200
force = true
# Asset rewrites for contests subdomain
[[redirects]]
from = "https://contests.neeldhara.blog/_astro/*"
to = "/_astro/:splat"
status = 200
force = true
[[redirects]]
from = "https://contests.neeldhara.blog/assets/*"
to = "/assets/:splat"
status = 200
force = true
# Asset rewrites for poetry subdomain
[[redirects]]
from = "https://poetry.neeldhara.blog/_astro/*"
to = "/_astro/:splat"
status = 200
force = true
[[redirects]]
from = "https://poetry.neeldhara.blog/assets/*"
to = "/assets/:splat"
status = 200
force = true
# Asset rewrites for blog subdomain
[[redirects]]
from = "https://blog.neeldhara.blog/_astro/*"
to = "/_astro/:splat"
status = 200
force = true
[[redirects]]
from = "https://blog.neeldhara.blog/assets/*"
to = "/assets/:splat"
status = 200
force = true
# Special redirects for global pages
[[redirects]]
from = "https://art.neeldhara.blog/latest"
to = "https://neeldhara.blog/latest"
status = 301
force = true
[[redirects]]
from = "https://art.neeldhara.blog/about"
to = "https://neeldhara.blog/about"
status = 301
force = true
# Fix double-path issue (e.g. /art/art/slug → /art/slug)
[[redirects]]
from = "https://art.neeldhara.blog/art/*"
to = "https://art.neeldhara.blog/:splat"
status = 301
force = true
# Content rewrites
[[redirects]]
from = "https://art.neeldhara.blog/*"
to = "/art/:splat"
status = 200
force = true
# Special redirects for global pages - BFS
[[redirects]]
from = "https://bfs.neeldhara.blog/latest"
to = "https://neeldhara.blog/latest"
status = 301
force = true
[[redirects]]
from = "https://bfs.neeldhara.blog/about"
to = "https://neeldhara.blog/about"
status = 301
force = true
# Fix double-path issue for BFS
[[redirects]]
from = "https://bfs.neeldhara.blog/bfs/*"
to = "https://bfs.neeldhara.blog/:splat"
status = 301
force = true
[[redirects]]
from = "https://bfs.neeldhara.blog/*"
to = "/bfs/:splat"
status = 200
force = true
# Special redirects for global pages - DFS
[[redirects]]
from = "https://dfs.neeldhara.blog/latest"
to = "https://neeldhara.blog/latest"
status = 301
force = true
[[redirects]]
from = "https://dfs.neeldhara.blog/about"
to = "https://neeldhara.blog/about"
status = 301
force = true
# Fix double-path issue for DFS
[[redirects]]
from = "https://dfs.neeldhara.blog/dfs/*"
to = "https://dfs.neeldhara.blog/:splat"
status = 301
force = true
[[redirects]]
from = "https://dfs.neeldhara.blog/*"
to = "/dfs/:splat"
status = 200
force = true
# Special redirects for global pages - PROBLEMS
[[redirects]]
from = "https://problems.neeldhara.blog/latest"
to = "https://neeldhara.blog/latest"
status = 301
force = true
[[redirects]]
from = "https://problems.neeldhara.blog/about"
to = "https://neeldhara.blog/about"
status = 301
force = true
# Fix double-path issue for PROBLEMS
[[redirects]]
from = "https://problems.neeldhara.blog/problems/*"
to = "https://problems.neeldhara.blog/:splat"
status = 301
force = true
[[redirects]]
from = "https://problems.neeldhara.blog/*"
to = "/problems/:splat"
status = 200
force = true
# Special redirects for global pages - PUZZLES
[[redirects]]
from = "https://puzzles.neeldhara.blog/latest"
to = "https://neeldhara.blog/latest"
status = 301
force = true
[[redirects]]
from = "https://puzzles.neeldhara.blog/about"
to = "https://neeldhara.blog/about"
status = 301
force = true
# Fix double-path issue for PUZZLES
[[redirects]]
from = "https://puzzles.neeldhara.blog/puzzles/*"
to = "https://puzzles.neeldhara.blog/:splat"
status = 301
force = true
[[redirects]]
from = "https://puzzles.neeldhara.blog/*"
to = "/puzzles/:splat"
status = 200
force = true
# Special redirects for global pages - REVIEWS
[[redirects]]
from = "https://reviews.neeldhara.blog/latest"
to = "https://neeldhara.blog/latest"
status = 301
force = true
[[redirects]]
from = "https://reviews.neeldhara.blog/about"
to = "https://neeldhara.blog/about"
status = 301
force = true
# Fix double-path issue for REVIEWS
[[redirects]]
from = "https://reviews.neeldhara.blog/reviews/*"
to = "https://reviews.neeldhara.blog/:splat"
status = 301
force = true
[[redirects]]
from = "https://reviews.neeldhara.blog/*"
to = "/reviews/:splat"
status = 200
force = true
# Special redirects for global pages - REFLECTIONS
[[redirects]]
from = "https://reflections.neeldhara.blog/latest"
to = "https://neeldhara.blog/latest"
status = 301
force = true
[[redirects]]
from = "https://reflections.neeldhara.blog/about"
to = "https://neeldhara.blog/about"
status = 301
force = true
# Fix double-path issue for REFLECTIONS
[[redirects]]
from = "https://reflections.neeldhara.blog/reflections/*"
to = "https://reflections.neeldhara.blog/:splat"
status = 301
force = true
[[redirects]]
from = "https://reflections.neeldhara.blog/*"
to = "/reflections/:splat"
status = 200
force = true
# Special redirects for global pages - VIBES
[[redirects]]
from = "https://vibes.neeldhara.blog/latest"
to = "https://neeldhara.blog/latest"
status = 301
force = true
[[redirects]]
from = "https://vibes.neeldhara.blog/about"
to = "https://neeldhara.blog/about"
status = 301
force = true
# Fix double-path issue for VIBES
[[redirects]]
from = "https://vibes.neeldhara.blog/vibes/*"
to = "https://vibes.neeldhara.blog/:splat"
status = 301
force = true
[[redirects]]
from = "https://vibes.neeldhara.blog/*"
to = "/vibes/:splat"
status = 200
force = true
# Special redirects for global pages - WORKFLOWS
[[redirects]]
from = "https://workflows.neeldhara.blog/latest"
to = "https://neeldhara.blog/latest"
status = 301
force = true
[[redirects]]
from = "https://workflows.neeldhara.blog/about"
to = "https://neeldhara.blog/about"
status = 301
force = true
# Fix double-path issue for WORKFLOWS
[[redirects]]
from = "https://workflows.neeldhara.blog/workflows/*"
to = "https://workflows.neeldhara.blog/:splat"
status = 301
force = true
[[redirects]]
from = "https://workflows.neeldhara.blog/*"
to = "/workflows/:splat"
status = 200
force = true
# Special redirects for global pages - MAGIC
[[redirects]]
from = "https://magic.neeldhara.blog/latest"
to = "https://neeldhara.blog/latest"
status = 301
force = true
[[redirects]]
from = "https://magic.neeldhara.blog/about"
to = "https://neeldhara.blog/about"
status = 301
force = true
# Fix double-path issue for MAGIC
[[redirects]]
from = "https://magic.neeldhara.blog/magic/*"
to = "https://magic.neeldhara.blog/:splat"
status = 301
force = true
[[redirects]]
from = "https://magic.neeldhara.blog/*"
to = "/magic/:splat"
status = 200
force = true
# Special redirects for global pages - CONTESTS
[[redirects]]
from = "https://contests.neeldhara.blog/latest"
to = "https://neeldhara.blog/latest"
status = 301
force = true
[[redirects]]
from = "https://contests.neeldhara.blog/about"
to = "https://neeldhara.blog/about"
status = 301
force = true
# Fix double-path issue for CONTESTS
[[redirects]]
from = "https://contests.neeldhara.blog/contests/*"
to = "https://contests.neeldhara.blog/:splat"
status = 301
force = true
[[redirects]]
from = "https://contests.neeldhara.blog/*"
to = "/contests/:splat"
status = 200
force = true
# Special redirects for global pages - POETRY
[[redirects]]
from = "https://poetry.neeldhara.blog/latest"
to = "https://neeldhara.blog/latest"
status = 301
force = true
[[redirects]]
from = "https://poetry.neeldhara.blog/about"
to = "https://neeldhara.blog/about"
status = 301
force = true
# Fix double-path issue for POETRY
[[redirects]]
from = "https://poetry.neeldhara.blog/poetry/*"
to = "https://poetry.neeldhara.blog/:splat"
status = 301
force = true
[[redirects]]
from = "https://poetry.neeldhara.blog/*"
to = "/poetry/:splat"
status = 200
force = true
# Special redirects for global pages - BLOG
[[redirects]]
from = "https://blog.neeldhara.blog/latest"
to = "https://neeldhara.blog/latest"
status = 301
force = true
[[redirects]]
from = "https://blog.neeldhara.blog/about"
to = "https://neeldhara.blog/about"
status = 301
force = true
# Fix double-path issue for BLOG
[[redirects]]
from = "https://blog.neeldhara.blog/blog/*"
to = "https://blog.neeldhara.blog/:splat"
status = 301
force = true
# Main blog redirect
[[redirects]]
from = "https://blog.neeldhara.blog/*"
to = "/blog/:splat"
status = 200
force = true
# Fallback for main domain
[[redirects]]
from = "/*"
to = "/index.html"
status = 200

View file

@ -5,7 +5,16 @@
"author": "Shadcnblocks.com", "author": "Shadcnblocks.com",
"scripts": { "scripts": {
"dev": "astro dev", "dev": "astro dev",
"build": "astro build", "dev:site": "astro dev",
"build": "node scripts/build-all-sites.mjs",
"build:site": "astro build",
"build:research": "BLOG_SITE=research astro build",
"build:vibes": "BLOG_SITE=vibes astro build",
"build:puzzles": "BLOG_SITE=puzzles astro build",
"build:reflections": "BLOG_SITE=reflections astro build",
"build:poetry": "BLOG_SITE=poetry astro build",
"build:reviews": "BLOG_SITE=reviews astro build",
"build:art": "BLOG_SITE=art astro build",
"preview": "astro preview", "preview": "astro preview",
"astro": "astro", "astro": "astro",
"format": "prettier -w ." "format": "prettier -w ."

View file

@ -0,0 +1,21 @@
import { spawnSync } from "node:child_process";
import { rmSync } from "node:fs";
import { BLOG_SITES } from "../src/blog-sites.js";
rmSync("dist", { force: true, recursive: true });
for (const site of BLOG_SITES) {
console.log(`\nBuilding ${site.title} -> dist/${site.key}`);
const result = spawnSync("npx", ["astro", "build"], {
env: {
...process.env,
BLOG_SITE: site.key,
},
stdio: "inherit",
});
if (result.status !== 0) {
process.exit(result.status ?? 1);
}
}

75
src/blog-sites.js Normal file
View file

@ -0,0 +1,75 @@
export const BLOG_SITES = [
{
key: "research",
title: "Research",
description:
"Notes from papers I am reading, and mini-surveys of themes I'm learning about.",
url: "https://research.neeldhara.blog",
},
{
key: "vibes",
title: "Vibes",
description:
"Conversations with LLMs with an intent to create something meaningful.",
url: "https://vibes.neeldhara.blog",
},
{
key: "puzzles",
title: "Puzzles",
description:
"Problems, puzzles, contest editorials, and mathematical magic.",
url: "https://puzzles.neeldhara.blog",
},
{
key: "reflections",
title: "Reflections",
description: "Random musings, pseudo-advice, how-tos. Core dumped.",
url: "https://reflections.neeldhara.blog",
},
{
key: "poetry",
title: "Poetry",
description: "Failed attempts at organizing thoughts in verse.",
url: "https://poetry.neeldhara.blog",
},
{
key: "reviews",
title: "Reviews",
description:
"Reviews of things I use or consume: software, books, hardware, etc.",
url: "https://reviews.neeldhara.blog",
},
{
key: "art",
title: "Art",
description:
"This exists so I can stay accountable to my doodling practice :)",
url: "https://art.neeldhara.blog",
},
];
export const DEFAULT_BLOG_SITE_KEY = "research";
export const BLOG_SITE_KEYS = BLOG_SITES.map((site) => site.key);
function getEnvironmentBlogSiteKey() {
if (typeof process === "undefined") {
return undefined;
}
return process.env?.BLOG_SITE;
}
export function getBlogSite(key = getEnvironmentBlogSiteKey()) {
const normalizedKey = key || DEFAULT_BLOG_SITE_KEY;
const site = BLOG_SITES.find((candidate) => candidate.key === normalizedKey);
if (!site) {
throw new Error(
`Unknown BLOG_SITE "${normalizedKey}". Expected one of: ${BLOG_SITE_KEYS.join(", ")}`,
);
}
return site;
}
export const ACTIVE_BLOG_SITE = getBlogSite();

View file

@ -5,7 +5,8 @@ This directory contains the interactive React components for the "100 Prisoners
## Files Created ## Files Created
### Blog Post ### Blog Post
- **`src/content/problems/100-prisoners.mdx`** - The main blog post with embedded interactive components
- **`src/content/puzzles/100-prisoners.mdx`** - The main blog post with embedded interactive components
### Interactive Components ### Interactive Components
@ -38,6 +39,7 @@ This directory contains the interactive React components for the "100 Prisoners
## Features ## Features
All components are: All components are:
- ✅ Fully interactive with React hooks - ✅ Fully interactive with React hooks
- ✅ Styled with Tailwind CSS classes - ✅ Styled with Tailwind CSS classes
- ✅ Responsive for mobile and desktop - ✅ Responsive for mobile and desktop
@ -47,12 +49,15 @@ All components are:
## How It Works ## How It Works
### The Problem ### The Problem
100 prisoners must each find their own number among 100 boxes by opening at most 50 boxes. If all prisoners succeed, they go free. They can agree on a strategy beforehand but cannot communicate during the challenge. 100 prisoners must each find their own number among 100 boxes by opening at most 50 boxes. If all prisoners succeed, they go free. They can agree on a strategy beforehand but cannot communicate during the challenge.
### The Naive Strategy ### The Naive Strategy
Each prisoner opens 50 random boxes. Probability of success: (1/2)^100 ≈ 0% Each prisoner opens 50 random boxes. Probability of success: (1/2)^100 ≈ 0%
### The Clever Strategy (Loop Following) ### The Clever Strategy (Loop Following)
1. Open the box with your own number 1. Open the box with your own number
2. Look at the number inside 2. Look at the number inside
3. Open the box with that number 3. Open the box with that number
@ -61,6 +66,7 @@ Each prisoner opens 50 random boxes. Probability of success: (1/2)^100 ≈ 0%
This exploits the cycle structure of permutations. Success probability: ~31.18%! This exploits the cycle structure of permutations. Success probability: ~31.18%!
### Key Mathematical Insight ### Key Mathematical Insight
The prisoners succeed if and only if the permutation of numbers in boxes has no cycle longer than n/2. For n=100, the probability of this is: The prisoners succeed if and only if the permutation of numbers in boxes has no cycle longer than n/2. For n=100, the probability of this is:
P(success) = 1 - Σ(1/k) for k=51 to 100 ≈ 0.3118 P(success) = 1 - Σ(1/k) for k=51 to 100 ≈ 0.3118
@ -86,6 +92,7 @@ import StrategyComparison from '@/components/problems/StrategyComparison.tsx';
## Design Philosophy ## Design Philosophy
Inspired by Nicky Case's "Parable of the Polygons", these components aim to: Inspired by Nicky Case's "Parable of the Polygons", these components aim to:
- Make abstract mathematical concepts tangible through interaction - Make abstract mathematical concepts tangible through interaction
- Allow readers to explore and discover patterns themselves - Allow readers to explore and discover patterns themselves
- Provide immediate visual feedback - Provide immediate visual feedback
@ -95,6 +102,7 @@ Inspired by Nicky Case's "Parable of the Polygons", these components aim to:
## Future Enhancements ## Future Enhancements
Potential additions: Potential additions:
- Auto-play mode for the game simulator using the loop strategy - Auto-play mode for the game simulator using the loop strategy
- Animation showing cycle formation in real-time - Animation showing cycle formation in real-time
- Monte Carlo simulation running multiple trials - Monte Carlo simulation running multiple trials

View file

@ -1,4 +1,4 @@
import { ChevronRight, Timer, Wallet, Terminal, Calendar } from "lucide-react"; import { ChevronRight } from "lucide-react";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { import {
@ -7,81 +7,13 @@ import {
CardDescription, CardDescription,
CardHeader, CardHeader,
} from "@/components/ui/card"; } from "@/components/ui/card";
import { BLOG_SITES } from "@/blog-sites.js";
const features = [ const features = BLOG_SITES.map((site) => ({
{ title: site.title,
title: "BFS", description: site.description,
description: "Thematic roundups based on conferences or techniques.", href: site.url,
icon: Timer, }));
href: "/bfs",
},
{
title: "DFS",
description: "Each post is typically a closer look at a paper I have enjoyed reading.",
icon: Wallet,
href: "/dfs",
},
{
title: "Problems",
description: "Highlights from problem sets that feature in assessments.",
icon: Terminal,
href: "/problems",
},
{
title: "Puzzles",
description: "Favorite puzzles, typically interactive and playable. Solutions invited.",
icon: Calendar,
href: "/puzzles",
},
{
title: "Reviews",
description: "Reviews of things I use or consume: software, books, hardware, etc.",
icon: Timer,
href: "/reviews",
},
{
title: "Reflections",
description: "Random musings, pseudo-advice, how-tos. Core dumped.",
icon: Wallet,
href: "/reflections",
},
{
title: "Vibes",
description: "Conversations with LLMs with an intent to create something meaningful.",
icon: Terminal,
href: "/vibes",
},
{
title: "Workflows",
description: "Little automations and rituals that I've found useful.",
icon: Calendar,
href: "/workflows",
},
{
title: "Magic",
description: "Self-working card tricks: thoughts from a classroom context.",
icon: Timer,
href: "/magic",
},
{
title: "Contests",
description: "Editorials from contest problems, mostly informatics and math",
icon: Wallet,
href: "/contests",
},
{
title: "Art",
description: "This exists so I can stay accountable to my doodling practice :)",
icon: Terminal,
href: "/art",
},
{
title: "Poetry",
description: "Failed attempts at organizing thoughts in verse.",
icon: Calendar,
href: "/poetry",
},
];
export default function AllBlogs() { export default function AllBlogs() {
return ( return (
@ -89,14 +21,12 @@ export default function AllBlogs() {
<div className="container max-w-5xl"> <div className="container max-w-5xl">
<div className="text-center"> <div className="text-center">
<h3 className="mini-title">CURRENTLY ACTIVE BLOGS</h3> <h3 className="mini-title">CURRENTLY ACTIVE BLOGS</h3>
</div> </div>
<div className="mt-8 grid grid-cols-2 gap-2.5 md:mt-12 lg:mt-20 lg:grid-cols-4 lg:gap-6"> <div className="mt-8 grid grid-cols-1 gap-2.5 sm:grid-cols-2 md:mt-12 lg:mt-20 lg:grid-cols-3 lg:gap-6">
{features.map((feature, index) => ( {features.map((feature, index) => (
<Card key={index} className="flex flex-col"> <Card key={index} className="flex flex-col">
<CardHeader className="max-md:p-3"> <CardHeader className="max-md:p-3">
{/* <feature.icon className="text-primary size-8" /> */}
<h3 className="text-lg font-semibold">{feature.title}</h3> <h3 className="text-lg font-semibold">{feature.title}</h3>
<CardDescription className="text-foreground mt-4 font-medium"> <CardDescription className="text-foreground mt-4 font-medium">
{feature.description} {feature.description}

View file

@ -6,15 +6,21 @@ import { Separator } from "@/components/ui/separator";
import { Calendar, Clock, User } from "lucide-react"; import { Calendar, Clock, User } from "lucide-react";
import { PlusSigns } from "../icons/plus-signs"; import { PlusSigns } from "../icons/plus-signs";
const BlogPosts = ({ posts, collection }: { posts: any[]; collection: string }) => { const BlogPosts = ({
posts,
collection = "",
}: {
posts: any[];
collection?: string;
}) => {
// Get the first post as the featured post // Get the first post as the featured post
const featuredPost = posts[0]; const featuredPost = posts[0];
const remainingPosts = posts.slice(1); const remainingPosts = posts.slice(1);
const hrefFor = (post: any) =>
collection ? `/${collection}/${post.id}/` : `/${post.id}/`;
return ( return (
<div className="relative py-16 md:py-28 lg:py-32"> <div className="relative py-16 md:py-28 lg:py-32">
<div className="absolute -inset-40 z-[-1] [mask-image:radial-gradient(circle_at_center,black_0%,black_20%,transparent_75%)]"> <div className="absolute -inset-40 z-[-1] [mask-image:radial-gradient(circle_at_center,black_0%,black_20%,transparent_75%)]">
<PlusSigns className="text-foreground/[0.05] h-full w-full" /> <PlusSigns className="text-foreground/[0.05] h-full w-full" />
</div> </div>
@ -23,7 +29,7 @@ const BlogPosts = ({ posts, collection }: { posts: any[]; collection: string })
<section className="mt-8"> <section className="mt-8">
<div className="container max-w-6xl"> <div className="container max-w-6xl">
<a <a
href={`/${collection}/${featuredPost.id}/`} href={hrefFor(featuredPost)}
className="bg-card group relative mb-16 overflow-hidden rounded-xl border shadow-md" className="bg-card group relative mb-16 overflow-hidden rounded-xl border shadow-md"
> >
<div className="flex flex-col gap-6 lg:flex-row"> <div className="flex flex-col gap-6 lg:flex-row">
@ -80,7 +86,7 @@ const BlogPosts = ({ posts, collection }: { posts: any[]; collection: string })
<a <a
key={post.id} key={post.id}
className="bg-card group rounded-xl border shadow-sm transition-all hover:shadow-md" className="bg-card group rounded-xl border shadow-sm transition-all hover:shadow-md"
href={`/${collection}/${post.id}/`} href={hrefFor(post)}
> >
<div className="p-2"> <div className="p-2">
<img <img

View file

@ -1,29 +1,22 @@
// Place any global data in this file. // Place any global data in this file.
// You can import this data from anywhere in your site by using the `import` keyword. // You can import this data from anywhere in your site by using the `import` keyword.
export const SITE_TITLE = "charter - Modern Astro Template"; import { ACTIVE_BLOG_SITE } from "./blog-sites.js";
export const SITE_DESCRIPTION =
"A modern, fully featured Astro template built with Shadcn/UI, TailwindCSS and TypeScript, perfect for your next web application."; export const SITE_TITLE = ACTIVE_BLOG_SITE.title;
export const SITE_DESCRIPTION = ACTIVE_BLOG_SITE.description;
export const SITE_URL = ACTIVE_BLOG_SITE.url;
export const SITE_METADATA = { export const SITE_METADATA = {
title: { title: {
default: SITE_TITLE, default: SITE_TITLE,
template: "%s | charter", template: `%s | ${SITE_TITLE}`,
}, },
description: SITE_DESCRIPTION, description: SITE_DESCRIPTION,
keywords: [ keywords: ["Neeldhara", "blog", SITE_TITLE, "research", "writing"],
"Astro", authors: [{ name: "Neeldhara" }],
"React", creator: "Neeldhara",
"JavaScript", publisher: "Neeldhara",
"TypeScript",
"TailwindCSS",
"Template",
"Shadcn/UI",
"Web Development",
],
authors: [{ name: "charter Team" }],
creator: "charter Team",
publisher: "charter",
robots: { robots: {
index: true, index: true,
follow: true, follow: true,
@ -42,13 +35,13 @@ export const SITE_METADATA = {
openGraph: { openGraph: {
title: SITE_TITLE, title: SITE_TITLE,
description: SITE_DESCRIPTION, description: SITE_DESCRIPTION,
siteName: "charter", siteName: SITE_TITLE,
images: [ images: [
{ {
url: "/og-image.jpg", url: "/og-image.jpg",
width: 1200, width: 1200,
height: 630, height: 630,
alt: "charter - Modern Astro Template", alt: SITE_TITLE,
}, },
], ],
}, },
@ -57,6 +50,6 @@ export const SITE_METADATA = {
title: SITE_TITLE, title: SITE_TITLE,
description: SITE_DESCRIPTION, description: SITE_DESCRIPTION,
images: ["/og-image.jpg"], images: ["/og-image.jpg"],
creator: "@charter", creator: "@neeldhara",
}, },
}; };

View file

@ -1,84 +0,0 @@
---
title: "Building Websites with Astro"
description: "Discover how Astro is revolutionizing web development with its unique approach to building fast, content-focused websites. Learn about its key features, performance benefits, and why developers are making the switch."
pubDate: "Jul 08 2022"
image: "https://images.unsplash.com/photo-1741610748460-fb2e33cc6390?w=400&auto=format&fit=crop&q=60&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwcm9maWxlLXBhZ2V8MXx8fGVufDB8fHx8fA%3D%3D"
authorImage: "/avatar/avatar1.png"
authorName: "John Doe"
---
# Building Websites with Astro
Astro has emerged as one of the most exciting web frameworks in recent years, offering developers a fresh approach to building modern websites. As a "content-focused" framework, Astro prioritizes delivering lightning-fast performance while maintaining developer experience. Let's explore what makes Astro special and why you might want to consider it for your next project.
## What is Astro?
Astro is an all-in-one web framework designed to deliver lightning-fast performance with a modern developer experience. Unlike traditional frameworks that send large JavaScript bundles to the client, Astro generates static HTML by default and only ships JavaScript when absolutely necessary - a concept they call "Islands Architecture."
## Key Features That Make Astro Stand Out
### 1. Zero-JS by Default
One of Astro's most compelling features is its approach to JavaScript. While frameworks like React or Vue send entire JavaScript applications to the browser, Astro strips away unnecessary JavaScript, resulting in significantly faster page loads. Your components are rendered to HTML during the build process, and JavaScript is only shipped when needed for interactivity.
### 2. Component Islands
Astro introduces the concept of "Islands" - interactive UI components that exist within a sea of static, lightweight HTML. This approach allows you to use your favorite UI frameworks (React, Vue, Svelte, etc.) where you need interactivity, while keeping the rest of your site lightweight.
### 3. Flexible Content Sources
Whether your content lives in Markdown files, MDX, a headless CMS, or an API, Astro makes it easy to pull in content from anywhere. The built-in content collections API provides type safety and excellent developer experience when working with content.
### 4. Fast by Default
Websites built with Astro are incredibly fast because they ship less JavaScript, utilize efficient hydration strategies, and employ optimized asset handling. This performance-first approach results in better Core Web Vitals scores and improved user experience.
## Getting Started with Astro
Setting up an Astro project is straightforward:
```bash
# Create a new project with npm
npm create astro@latest
# Or with yarn
yarn create astro
# Or with pnpm
pnpm create astro
```
The CLI will guide you through the setup process, offering templates and configuration options to get you started quickly.
## Building Your First Astro Site
Astro's file-based routing system makes it intuitive to create pages. Simply add a `.astro` file to the `src/pages` directory, and it becomes a route in your site. For example, `src/pages/about.astro` becomes `/about/` in your built site.
A basic Astro component looks like this:
```astro
---
// Component Script (runs at build time)
const greeting = "Hello, Astro!";
---
<!-- Component Template -->
<h1>{greeting}</h1>
<p>Welcome to my Astro website!</p>
```
## Why Developers Are Switching to Astro
The web development landscape is constantly evolving, and Astro represents a shift toward performance-focused frameworks that prioritize the end-user experience. Developers are choosing Astro because:
1. It delivers exceptional performance out of the box
2. It allows them to use their favorite UI frameworks
3. It simplifies content management with built-in Markdown support
4. It provides an excellent developer experience with hot module reloading and TypeScript integration
5. It scales from simple blogs to complex applications
## Conclusion
Astro offers a compelling alternative to traditional JavaScript frameworks, especially for content-rich websites where performance matters. By shipping less JavaScript and focusing on what truly matters for user experience, Astro helps developers build faster, more efficient websites without sacrificing modern features or developer experience.
If you're starting a new project or considering a framework switch, Astro deserves a serious look. Its unique approach to web development might just be the perfect fit for your next website.

View file

@ -1,45 +0,0 @@
---
title: "The Joy of Urban Gardening"
description: "Growing plants in the city might seem challenging, but it's one of the most rewarding hobbies I've discovered."
pubDate: "Jun 19 2024"
image: "https://images.unsplash.com/photo-1741091742846-99cca6f6437b?q=80&w=1286&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D"
authorImage: "/avatar/avatar1.png"
authorName: "John Doe"
---
Growing plants in the city might seem challenging, but it's one of the most rewarding hobbies I've discovered. Three months ago, I transformed my tiny apartment balcony into a thriving green space, and the journey has been nothing short of magical.
## Getting Started
Starting an urban garden doesn't require much. Here's what you need:
- A few containers (recycled plastic bottles work great!)
- Quality potting soil
- Seeds or starter plants
- Basic gardening tools
- Patience and curiosity
## My First Harvest
Last weekend, I harvested my first batch of cherry tomatoes. The feeling of eating something you've grown yourself is indescribable - a perfect blend of pride, accomplishment, and pure flavor that store-bought produce simply can't match.
![Fresh tomatoes from the garden](https://images.unsplash.com/photo-1733149701930-4c982b24be66?q=80&w=1586&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D)
## Unexpected Benefits
Beyond the obvious joy of fresh produce, my little garden has:
1. Reduced my stress levels significantly
2. Connected me with a community of fellow urban gardeners
3. Taught me patience and mindfulness
4. Improved the air quality in my apartment
## Challenges and Solutions
Of course, it hasn't all been smooth sailing. Limited sunlight was my biggest challenge, but through research and experimentation, I discovered several shade-tolerant herbs that thrive in my north-facing balcony.
> "To plant a garden is to believe in tomorrow." — Audrey Hepburn
If you're considering starting your own urban garden, just take the plunge! Start small, learn as you go, and watch as your concrete jungle transforms into a green paradise.
Happy gardening!

View file

@ -1,63 +0,0 @@
---
title: "The Art of Simplicity"
description: "In today's fast-paced, information-saturated world, we often overlook the profound power of simplicity."
pubDate: "Jul 15 2022"
image: "https://images.unsplash.com/photo-1741610739548-29e17473f70b?q=80&w=1286&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D"
authorImage: "/avatar/avatar2.png"
authorName: "Jane Doe"
---
## Finding Clarity in a Complex World
In today's fast-paced, information-saturated world, we often overlook the profound power of simplicity. As our lives become increasingly complex, with endless notifications, mounting responsibilities, and constant connectivity, the value of simplicity grows exponentially. Simple solutions tend to be more elegant, more maintainable, and often more effective than complex ones.
### The Problem with Complexity
Complexity surrounds us. Our smartphones have countless apps, our workplaces implement intricate processes, and even our leisure activities often involve elaborate planning. While some complexity is unavoidable and even necessary, excessive complexity creates problems:
- **Mental fatigue**: Processing complex information drains our cognitive resources
- **Decision paralysis**: Too many options can lead to indecision or poor choices
- **Increased errors**: Complex systems have more potential failure points
- **Maintenance burden**: Complex solutions require more upkeep over time
### Embracing Simplicity as a Philosophy
Simplicity isn't merely about having fewer things or streamlining processes—it's a mindset. When we approach problems with simplicity as our guide, we naturally focus on what truly matters.
Whether you're designing a product, writing code, organizing your home, or planning your career, embracing simplicity can lead to better outcomes. Here are five principles to consider:
1. **Focus on essentials**: Identify what truly matters and eliminate the rest. Ask yourself: "What is the core purpose here?"
2. **Reduce cognitive load**: Make things intuitive and easy to understand. Information should flow naturally, requiring minimal mental effort.
3. **One purpose, one element**: Each component of your solution should serve a clear, singular purpose.
4. **Iterate and refine**: Start simple, then improve based on feedback. Perfect solutions rarely emerge fully-formed.
5. **Question additions**: Before adding anything new, ask whether it truly enhances the core purpose or merely adds complexity.
### Simplicity in Practice
Implementing simplicity requires intentional effort. Here are some practical ways to bring more simplicity into different areas of life:
**In work**: Focus on one task at a time. Break large projects into smaller, manageable steps. Eliminate unnecessary meetings and streamline communication channels.
**In design**: Remove decorative elements that don't serve a purpose. Use consistent patterns and familiar conventions. Prioritize clarity over cleverness.
**In communication**: Use plain language. Be concise. Structure information logically.
**In daily life**: Declutter physical spaces. Establish routines for recurring tasks. Limit consumption of news and social media.
### The Rewards of Simplicity
As Leonardo da Vinci once said, "Simplicity is the ultimate sophistication." When we successfully implement simplicity, we gain:
- **Clarity**: A clearer understanding of what matters
- **Efficiency**: Less wasted time and energy
- **Reliability**: Fewer points of failure
- **Accessibility**: Solutions that more people can use and understand
- **Peace of mind**: Reduced mental burden and stress
By striving for simplicity in our work and lives, we can achieve greater focus, efficiency, and satisfaction. The journey toward simplicity is ongoing—a continuous process of evaluation and refinement.
What areas in your life could benefit from a simpler approach? The answer might be simpler than you think.

View file

@ -1,40 +0,0 @@
---
title: "Modern Architecture"
description: "Exploring how contemporary design balances aesthetics with functionality"
pubDate: "Jul 22 2022"
image: "https://images.unsplash.com/photo-1741091756497-10c964acc4f6?q=80&w=1286&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D"
authorImage: "/avatar/avatar1.png"
authorName: "John Doe"
---
# A delicate balance between artistic vision and practical necessity
The 21st century has witnessed a remarkable transformation in how we conceptualize and construct our built environment, with sustainability, technology, and human experience emerging as central themes.
## The Foundations of Contemporary Design
The roots of today's architectural innovations can be traced back to the early 20th century, when pioneers like Le Corbusier, Frank Lloyd Wright, and Ludwig Mies van der Rohe challenged conventional wisdom. Their revolutionary approach—emphasizing clean lines, open spaces, and the honest expression of materials—continues to influence designers worldwide.
## Sustainable Architecture: Building for the Future
Today's architectural landscape is increasingly defined by environmental consciousness. Green roofs, solar panels, and passive cooling systems have become standard features rather than novelties. Architects now consider a building's entire lifecycle, from the sourcing of materials to eventual demolition or repurposing. This holistic approach has given rise to remarkable structures like Singapore's Gardens by the Bay and Seattle's Bullitt Center, which generates more energy than it consumes.
## The Digital Revolution in Design
Computational design and Building Information Modeling (BIM) have revolutionized the architect's toolkit. Complex geometries that would have been impossible to realize a generation ago are now commonplace, enabling the fluid forms of Zaha Hadid's Heydar Aliyev Center or Frank Gehry's Guggenheim Museum Bilbao. These digital tools not only expand creative possibilities but also improve precision, reduce waste, and facilitate collaboration among diverse stakeholders.
## Adaptive Reuse: Honoring the Past
As urban spaces become increasingly precious, architects have embraced the challenge of breathing new life into existing structures. The High Line in New York City transformed an abandoned railway into a beloved public park, while the Tate Modern in London reimagined a power station as a world-class art museum. These projects demonstrate how thoughtful design can preserve historical character while meeting contemporary needs.
## Human-Centered Design
Perhaps the most significant shift in architectural thinking has been the renewed focus on human experience. Designers now prioritize natural light, air quality, and intuitive navigation—elements that enhance wellbeing and productivity. Public spaces are conceived as democratic forums that foster community and connection, reflecting architecture's profound social responsibility.
## The Future of Architecture
As we look ahead, the boundaries between natural and built environments continue to blur. Biomimicry—drawing inspiration from nature's time-tested patterns—offers promising solutions to complex design challenges. Meanwhile, advances in materials science are yielding self-healing concrete, transparent aluminum, and other innovations that will reshape our physical world.
The most exciting architectural developments may lie at the intersection of multiple disciplines. When architects collaborate with ecologists, sociologists, and technologists, they create spaces that are not merely functional or beautiful, but truly responsive to human needs and planetary constraints.
In this era of rapid change and global challenges, architecture remains a powerful expression of our values and aspirations—a concrete manifestation of how we wish to live, work, and interact with our world.

View file

@ -1,33 +0,0 @@
---
title: "Using MDX"
description: "MDX is a special flavor of Markdown that supports embedded JavaScript & JSX syntax"
pubDate: "Jun 01 2024"
image: "https://images.unsplash.com/photo-1737386385296-f3e58b8cf21d?q=80&w=1286&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D"
authorImage: "/avatar/avatar2.png"
authorName: "Jane Doe"
---
This theme comes with the [@astrojs/mdx](https://docs.astro.build/en/guides/integrations-guide/mdx/) integration installed and configured in your `astro.config.mjs` config file. If you prefer not to use MDX, you can disable support by removing the integration from your config file.
## Why MDX?
MDX is a special flavor of Markdown that supports embedded JavaScript & JSX syntax. This unlocks the ability to [mix JavaScript and UI Components into your Markdown content](https://docs.astro.build/en/guides/markdown-content/#mdx-features) for things like interactive charts or alerts.
If you have existing content authored in MDX, this integration will hopefully make migrating to Astro a breeze.
## Example
Here is how you import and use a UI component inside of MDX.
When you open this page in the browser, you should see the clickable button below.
import HeaderLink from "@/components/elements/header-link";
<HeaderLink href="#" onclick="alert('clicked!')">
Embedded component in MDX
</HeaderLink>
## More Links
- [MDX Syntax Documentation](https://mdxjs.com/docs/what-is-mdx)
- [Astro Usage Documentation](https://docs.astro.build/en/guides/markdown-content/#markdown-and-mdx-pages)
- **Note:** [Client Directives](https://docs.astro.build/en/reference/directives-reference/#client-directives) are still required to create interactive components. Otherwise, all components in your MDX will render as static HTML (no JavaScript) by default.

View file

@ -1,5 +1,5 @@
import { glob } from 'astro/loaders'; import { glob } from "astro/loaders";
import { defineCollection, z } from 'astro:content'; import { defineCollection, z } from "astro:content";
const blogSchema = z.object({ const blogSchema = z.object({
title: z.string(), title: z.string(),
@ -11,28 +11,19 @@ const blogSchema = z.object({
authorName: z.string().optional(), authorName: z.string().optional(),
}); });
const blog = defineCollection({ const research = defineCollection({
loader: glob({ base: "./src/content/blog", pattern: "**/*.{md,mdx}" }), loader: glob({
schema: blogSchema, base: "./src/content/research",
}); pattern: "**/*.{md,mdx}",
}),
const bfs = defineCollection({
loader: glob({ base: "./src/content/bfs", pattern: "**/*.{md,mdx}" }),
schema: blogSchema,
});
const dfs = defineCollection({
loader: glob({ base: "./src/content/dfs", pattern: "**/*.{md,mdx}" }),
schema: blogSchema,
});
const problems = defineCollection({
loader: glob({ base: "./src/content/problems", pattern: "**/*.{md,mdx}" }),
schema: blogSchema, schema: blogSchema,
}); });
const puzzles = defineCollection({ const puzzles = defineCollection({
loader: glob({ base: "./src/content/puzzles", pattern: "**/*.{md,mdx}" }), loader: glob({
base: "./src/content/puzzles",
pattern: "**/*.{md,mdx}",
}),
schema: blogSchema, schema: blogSchema,
}); });
@ -51,21 +42,6 @@ const vibes = defineCollection({
schema: blogSchema, schema: blogSchema,
}); });
const workflows = defineCollection({
loader: glob({ base: "./src/content/workflows", pattern: "**/*.{md,mdx}" }),
schema: blogSchema,
});
const magic = defineCollection({
loader: glob({ base: "./src/content/magic", pattern: "**/*.{md,mdx}" }),
schema: blogSchema,
});
const contests = defineCollection({
loader: glob({ base: "./src/content/contests", pattern: "**/*.{md,mdx}" }),
schema: blogSchema,
});
const art = defineCollection({ const art = defineCollection({
loader: glob({ base: "./src/content/art", pattern: "**/*.{md,mdx}" }), loader: glob({ base: "./src/content/art", pattern: "**/*.{md,mdx}" }),
schema: blogSchema, schema: blogSchema,
@ -77,17 +53,11 @@ const poetry = defineCollection({
}); });
export const collections = { export const collections = {
blog, research,
bfs,
dfs,
problems,
puzzles,
reviews,
reflections,
vibes, vibes,
workflows, puzzles,
magic, reflections,
contests,
art,
poetry, poetry,
reviews,
art,
}; };

View file

@ -1,27 +1,26 @@
--- ---
import { type CollectionEntry, getCollection } from 'astro:content'; import { getCollection, render } from 'astro:content';
import DefaultLayout from '@/layouts/DefaultLayout.astro'; import DefaultLayout from '@/layouts/DefaultLayout.astro';
import { BlogPost } from '@/components/sections/blog-post'; import { BlogPost } from '@/components/sections/blog-post';
import { SITE_TITLE, SITE_DESCRIPTION } from '@/consts';
import NewsletterSignup from '@/components/sections/newsletter-signup'; import NewsletterSignup from '@/components/sections/newsletter-signup';
import { render } from 'astro:content'; import { ACTIVE_BLOG_SITE } from '@/blog-sites.js';
import { SITE_TITLE } from '@/consts';
export async function getStaticPaths() { export async function getStaticPaths() {
const posts = await getCollection('art'); const posts = await getCollection(ACTIVE_BLOG_SITE.key);
return posts.map((post) => ({ return posts.map((post) => ({
params: { slug: post.id }, params: { slug: post.id },
props: post, props: post,
})); }));
} }
type Props = CollectionEntry<'art'>;
const post = Astro.props; const post = Astro.props;
const { Content } = await render(post); const { Content } = await render(post);
--- ---
<DefaultLayout title={`${post.data.title} | ${SITE_TITLE}`} description={post.data.description}> <DefaultLayout title={`${post.data.title} | ${SITE_TITLE}`} description={post.data.description}>
<div class="py-16 md:py-28 lg:py-32"> <div class="py-16 md:py-28 lg:py-32">
<BlogPost post={post} client:only='react'> <BlogPost post={post} client:only="react">
<Content /> <Content />
</BlogPost> </BlogPost>
</div> </div>

View file

@ -1,16 +0,0 @@
---
import { getCollection } from 'astro:content';
import DefaultLayout from '@/layouts/DefaultLayout.astro';
import { BlogPosts } from '@/components/sections/blog-posts';
const posts = (await getCollection('art')).sort(
(a, b) => new Date(b.data.pubDate).valueOf() - new Date(a.data.pubDate).valueOf()
);
const SITE_TITLE = 'Art - Visual Explorations in Computer Science';
const SITE_DESCRIPTION = 'Algorithmic sketches, generative art, and visual representations of computer science concepts.';
---
<DefaultLayout title={SITE_TITLE} description={SITE_DESCRIPTION}>
<BlogPosts posts={posts} collection="art" client:only='react' />
</DefaultLayout>

View file

@ -1,15 +0,0 @@
import rss from "@astrojs/rss";
import { getCollection } from "astro:content";
export async function GET(context) {
const posts = await getCollection("art");
return rss({
title: "Art - Visual Explorations in Computer Science",
description: "Algorithmic sketches, generative art, and visual representations of computer science concepts.",
site: context.site,
items: posts.map((post) => ({
...post.data,
link: `/art/${post.id}/`,
})),
});
}

View file

@ -1,30 +0,0 @@
---
import { type CollectionEntry, getCollection } from 'astro:content';
import DefaultLayout from '@/layouts/DefaultLayout.astro';
import { BlogPost } from '@/components/sections/blog-post';
import { SITE_TITLE, SITE_DESCRIPTION } from '@/consts';
import NewsletterSignup from '@/components/sections/newsletter-signup';
import { render } from 'astro:content';
export async function getStaticPaths() {
const posts = await getCollection('bfs');
return posts.map((post) => ({
params: { slug: post.id },
props: post,
}));
}
type Props = CollectionEntry<'bfs'>;
const post = Astro.props;
const { Content } = await render(post);
---
<DefaultLayout title={`${post.data.title} | ${SITE_TITLE}`} description={post.data.description}>
<div class="py-16 md:py-28 lg:py-32">
<BlogPost post={post} client:only='react'>
<Content />
</BlogPost>
</div>
<NewsletterSignup client:load />
</DefaultLayout>

View file

@ -1,24 +0,0 @@
---
import { getCollection } from 'astro:content';
import DefaultLayout from '@/layouts/DefaultLayout.astro';
import { BlogPosts } from '@/components/sections/blog-posts';
const posts = (await getCollection('bfs')).sort(
(a, b) => b.data.pubDate.valueOf() - a.data.pubDate.valueOf(),
);
const SITE_TITLE = 'BFS - Breadth-First Search Through Computer Science';
const SITE_DESCRIPTION = 'Conference highlights, algorithmic breakthroughs, and cutting-edge research in computer science.';
---
<DefaultLayout title={SITE_TITLE} description={SITE_DESCRIPTION}>
<div class="py-16 md:py-28 lg:py-32">
<div class="container max-w-5xl">
<div class="mb-12">
<h1 class="text-4xl font-bold mb-4">BFS</h1>
<p class="text-xl text-muted-foreground">{SITE_DESCRIPTION}</p>
</div>
<BlogPosts posts={posts} collection="bfs" client:only='react' />
</div>
</div>
</DefaultLayout>

View file

@ -1,15 +0,0 @@
import rss from "@astrojs/rss";
import { getCollection } from "astro:content";
export async function GET(context) {
const posts = await getCollection("bfs");
return rss({
title: "BFS - Breadth-First Search Through Computer Science",
description: "Conference highlights, algorithmic breakthroughs, and cutting-edge research in computer science.",
site: context.site,
items: posts.map((post) => ({
...post.data,
link: `/bfs/${post.id}/`,
})),
});
}

View file

@ -1,30 +0,0 @@
---
import { type CollectionEntry, getCollection } from 'astro:content';
import DefaultLayout from '@/layouts/DefaultLayout.astro';
import { BlogPost } from '@/components/sections/blog-post';
import { SITE_TITLE, SITE_DESCRIPTION } from '@/consts';
import NewsletterSignup from '@/components/sections/newsletter-signup';
import { render } from 'astro:content';
export async function getStaticPaths() {
const posts = await getCollection('blog');
return posts.map((post) => ({
params: { slug: post.id },
props: post,
}));
}
type Props = CollectionEntry<'blog'>;
const post = Astro.props;
const { Content } = await render(post);
---
<DefaultLayout title={`${post.data.title} | ${SITE_TITLE}`} description={post.data.description}>
<div class="py-16 md:py-28 lg:py-32">
<BlogPost post={post} client:only='react'>
<Content />
</BlogPost>
</div>
<NewsletterSignup client:load />
</DefaultLayout>

View file

@ -1,20 +0,0 @@
---
import DefaultLayout from '@/layouts/DefaultLayout.astro';
import { SITE_TITLE, SITE_DESCRIPTION } from '@/consts';
import { getCollection } from 'astro:content';
import { BlogPosts } from '@/components/sections/blog-posts';
import NewsletterSignup from '@/components/sections/newsletter-signup';
const posts = (await getCollection('blog')).sort(
(a, b) => b.data.pubDate.valueOf() - a.data.pubDate.valueOf(),
);
const blogTitle = `Blog | ${SITE_TITLE}`;
const blogDescription = "Explore our blog for insightful articles, personal reflections and updates from our team.";
---
<DefaultLayout title={blogTitle} description={blogDescription}>
<BlogPosts posts={posts} collection="blog" client:only='react' />
<NewsletterSignup client:load />
</DefaultLayout>

View file

@ -1,30 +0,0 @@
---
import { type CollectionEntry, getCollection } from 'astro:content';
import DefaultLayout from '@/layouts/DefaultLayout.astro';
import { BlogPost } from '@/components/sections/blog-post';
import { SITE_TITLE, SITE_DESCRIPTION } from '@/consts';
import NewsletterSignup from '@/components/sections/newsletter-signup';
import { render } from 'astro:content';
export async function getStaticPaths() {
const posts = await getCollection('contests');
return posts.map((post) => ({
params: { slug: post.id },
props: post,
}));
}
type Props = CollectionEntry<'contests'>;
const post = Astro.props;
const { Content } = await render(post);
---
<DefaultLayout title={`${post.data.title} | ${SITE_TITLE}`} description={post.data.description}>
<div class="py-16 md:py-28 lg:py-32">
<BlogPost post={post} client:only='react'>
<Content />
</BlogPost>
</div>
<NewsletterSignup client:load />
</DefaultLayout>

View file

@ -1,24 +0,0 @@
---
import { getCollection } from 'astro:content';
import DefaultLayout from '@/layouts/DefaultLayout.astro';
import { BlogPosts } from '@/components/sections/blog-posts';
const posts = (await getCollection('contests')).sort(
(a, b) => b.data.pubDate.valueOf() - a.data.pubDate.valueOf(),
);
const SITE_TITLE = 'Contests - Competitive Programming and Problem Analysis';
const SITE_DESCRIPTION = 'Analysis of programming contests, problem breakdowns, and strategies for competitive programming.';
---
<DefaultLayout title={SITE_TITLE} description={SITE_DESCRIPTION}>
<div class="py-16 md:py-28 lg:py-32">
<div class="container max-w-5xl">
<div class="mb-12">
<h1 class="text-4xl font-bold mb-4">Contests</h1>
<p class="text-xl text-muted-foreground">{SITE_DESCRIPTION}</p>
</div>
<BlogPosts posts={posts} collection="contests" client:only='react' />
</div>
</div>
</DefaultLayout>

View file

@ -1,15 +0,0 @@
import rss from "@astrojs/rss";
import { getCollection } from "astro:content";
export async function GET(context) {
const posts = await getCollection("contests");
return rss({
title: "Contests - Competitive Programming and Problem Analysis",
description: "Analysis of programming contests, problem breakdowns, and strategies for competitive programming.",
site: context.site,
items: posts.map((post) => ({
...post.data,
link: `/contests/${post.id}/`,
})),
});
}

View file

@ -1,30 +0,0 @@
---
import { type CollectionEntry, getCollection } from 'astro:content';
import DefaultLayout from '@/layouts/DefaultLayout.astro';
import { BlogPost } from '@/components/sections/blog-post';
import { SITE_TITLE, SITE_DESCRIPTION } from '@/consts';
import NewsletterSignup from '@/components/sections/newsletter-signup';
import { render } from 'astro:content';
export async function getStaticPaths() {
const posts = await getCollection('dfs');
return posts.map((post) => ({
params: { slug: post.id },
props: post,
}));
}
type Props = CollectionEntry<'dfs'>;
const post = Astro.props;
const { Content } = await render(post);
---
<DefaultLayout title={`${post.data.title} | ${SITE_TITLE}`} description={post.data.description}>
<div class="py-16 md:py-28 lg:py-32">
<BlogPost post={post} client:only='react'>
<Content />
</BlogPost>
</div>
<NewsletterSignup client:load />
</DefaultLayout>

View file

@ -1,24 +0,0 @@
---
import { getCollection } from 'astro:content';
import DefaultLayout from '@/layouts/DefaultLayout.astro';
import { BlogPosts } from '@/components/sections/blog-posts';
const posts = (await getCollection('dfs')).sort(
(a, b) => b.data.pubDate.valueOf() - a.data.pubDate.valueOf(),
);
const SITE_TITLE = 'DFS - Deep Dives into Papers and Research';
const SITE_DESCRIPTION = 'In-depth analysis of research papers, algorithmic techniques, and theoretical computer science.';
---
<DefaultLayout title={SITE_TITLE} description={SITE_DESCRIPTION}>
<div class="py-16 md:py-28 lg:py-32">
<div class="container max-w-5xl">
<div class="mb-12">
<h1 class="text-4xl font-bold mb-4">DFS</h1>
<p class="text-xl text-muted-foreground">{SITE_DESCRIPTION}</p>
</div>
<BlogPosts posts={posts} collection="dfs" client:only='react' />
</div>
</div>
</DefaultLayout>

View file

@ -1,15 +0,0 @@
import rss from "@astrojs/rss";
import { getCollection } from "astro:content";
export async function GET(context) {
const posts = await getCollection("dfs");
return rss({
title: "DFS - Deep Dives into Papers and Research",
description: "In-depth analysis of research papers, algorithmic techniques, and theoretical computer science.",
site: context.site,
items: posts.map((post) => ({
...post.data,
link: `/dfs/${post.id}/`,
})),
});
}

View file

@ -1,10 +1,23 @@
--- ---
import { getCollection } from 'astro:content';
import DefaultLayout from '@/layouts/DefaultLayout.astro'; import DefaultLayout from '@/layouts/DefaultLayout.astro';
import { SITE_TITLE, SITE_DESCRIPTION } from '../consts'; import { SITE_TITLE, SITE_DESCRIPTION } from '@/consts';
import { ACTIVE_BLOG_SITE } from '@/blog-sites.js';
import { BlogPosts } from '@/components/sections/blog-posts';
import FeaturedPosts from '@/components/sections/featured-posts'; const posts = (await getCollection(ACTIVE_BLOG_SITE.key)).sort(
(a, b) => b.data.pubDate.valueOf() - a.data.pubDate.valueOf(),
);
--- ---
<DefaultLayout title={SITE_TITLE} description={SITE_DESCRIPTION}> <DefaultLayout title={SITE_TITLE} description={SITE_DESCRIPTION}>
<FeaturedPosts /> <div class="py-16 md:py-28 lg:py-32">
<div class="container max-w-5xl">
<div class="mb-12">
<h1 class="text-4xl font-bold mb-4">{ACTIVE_BLOG_SITE.title}</h1>
<p class="text-xl text-muted-foreground">{ACTIVE_BLOG_SITE.description}</p>
</div>
<BlogPosts posts={posts} client:only="react" />
</div>
</div>
</DefaultLayout> </DefaultLayout>

View file

@ -1,30 +0,0 @@
---
import { type CollectionEntry, getCollection } from 'astro:content';
import DefaultLayout from '@/layouts/DefaultLayout.astro';
import { BlogPost } from '@/components/sections/blog-post';
import { SITE_TITLE, SITE_DESCRIPTION } from '@/consts';
import NewsletterSignup from '@/components/sections/newsletter-signup';
import { render } from 'astro:content';
export async function getStaticPaths() {
const posts = await getCollection('magic');
return posts.map((post) => ({
params: { slug: post.id },
props: post,
}));
}
type Props = CollectionEntry<'magic'>;
const post = Astro.props;
const { Content } = await render(post);
---
<DefaultLayout title={`${post.data.title} | ${SITE_TITLE}`} description={post.data.description}>
<div class="py-16 md:py-28 lg:py-32">
<BlogPost post={post} client:only='react'>
<Content />
</BlogPost>
</div>
<NewsletterSignup client:load />
</DefaultLayout>

View file

@ -1,24 +0,0 @@
---
import { getCollection } from 'astro:content';
import DefaultLayout from '@/layouts/DefaultLayout.astro';
import { BlogPosts } from '@/components/sections/blog-posts';
const posts = (await getCollection('magic')).sort(
(a, b) => b.data.pubDate.valueOf() - a.data.pubDate.valueOf(),
);
const SITE_TITLE = 'Magic - Mathematical Card Tricks and Illusions';
const SITE_DESCRIPTION = 'Self-working card tricks, mathematical principles in magic, and the beauty of algorithmic illusions.';
---
<DefaultLayout title={SITE_TITLE} description={SITE_DESCRIPTION}>
<div class="py-16 md:py-28 lg:py-32">
<div class="container max-w-5xl">
<div class="mb-12">
<h1 class="text-4xl font-bold mb-4">Magic</h1>
<p class="text-xl text-muted-foreground">{SITE_DESCRIPTION}</p>
</div>
<BlogPosts posts={posts} collection="magic" client:only='react' />
</div>
</div>
</DefaultLayout>

View file

@ -1,15 +0,0 @@
import rss from "@astrojs/rss";
import { getCollection } from "astro:content";
export async function GET(context) {
const posts = await getCollection("magic");
return rss({
title: "Magic - Mathematical Card Tricks and Illusions",
description: "Self-working card tricks, mathematical principles in magic, and the beauty of algorithmic illusions.",
site: context.site,
items: posts.map((post) => ({
...post.data,
link: `/magic/${post.id}/`,
})),
});
}

View file

@ -1,30 +0,0 @@
---
import { type CollectionEntry, getCollection } from 'astro:content';
import DefaultLayout from '@/layouts/DefaultLayout.astro';
import { BlogPost } from '@/components/sections/blog-post';
import { SITE_TITLE, SITE_DESCRIPTION } from '@/consts';
import NewsletterSignup from '@/components/sections/newsletter-signup';
import { render } from 'astro:content';
export async function getStaticPaths() {
const posts = await getCollection('poetry');
return posts.map((post) => ({
params: { slug: post.id },
props: post,
}));
}
type Props = CollectionEntry<'poetry'>;
const post = Astro.props;
const { Content } = await render(post);
---
<DefaultLayout title={`${post.data.title} | ${SITE_TITLE}`} description={post.data.description}>
<div class="py-16 md:py-28 lg:py-32">
<BlogPost post={post} client:only='react'>
<Content />
</BlogPost>
</div>
<NewsletterSignup client:load />
</DefaultLayout>

View file

@ -1,24 +0,0 @@
---
import { getCollection } from 'astro:content';
import DefaultLayout from '@/layouts/DefaultLayout.astro';
import { BlogPosts } from '@/components/sections/blog-posts';
const posts = (await getCollection('poetry')).sort(
(a, b) => new Date(b.data.pubDate).valueOf() - new Date(a.data.pubDate).valueOf()
);
const SITE_TITLE = 'Poetry - Code in Verse';
const SITE_DESCRIPTION = 'Programming concepts expressed through haikus, sonnets, and creative verse.';
---
<DefaultLayout title={SITE_TITLE} description={SITE_DESCRIPTION}>
<div class="py-16 md:py-28 lg:py-32">
<div class="container max-w-5xl">
<div class="mb-12">
<h1 class="text-4xl font-bold mb-4">Poetry</h1>
<p class="text-xl text-muted-foreground">{SITE_DESCRIPTION}</p>
</div>
<BlogPosts posts={posts} collection="poetry" client:only='react' />
</div>
</div>
</DefaultLayout>

View file

@ -1,15 +0,0 @@
import rss from "@astrojs/rss";
import { getCollection } from "astro:content";
export async function GET(context) {
const posts = await getCollection("poetry");
return rss({
title: "Poetry - Code in Verse",
description: "Programming concepts expressed through haikus, sonnets, and creative verse.",
site: context.site,
items: posts.map((post) => ({
...post.data,
link: `/poetry/${post.id}/`,
})),
});
}

View file

@ -1,30 +0,0 @@
---
import { type CollectionEntry, getCollection } from 'astro:content';
import DefaultLayout from '@/layouts/DefaultLayout.astro';
import { BlogPost } from '@/components/sections/blog-post';
import { SITE_TITLE, SITE_DESCRIPTION } from '@/consts';
import NewsletterSignup from '@/components/sections/newsletter-signup';
import { render } from 'astro:content';
export async function getStaticPaths() {
const posts = await getCollection('problems');
return posts.map((post) => ({
params: { slug: post.id },
props: post,
}));
}
type Props = CollectionEntry<'problems'>;
const post = Astro.props;
const { Content } = await render(post);
---
<DefaultLayout title={`${post.data.title} | ${SITE_TITLE}`} description={post.data.description}>
<div class="py-16 md:py-28 lg:py-32">
<BlogPost post={post} client:only='react'>
<Content />
</BlogPost>
</div>
<NewsletterSignup client:load />
</DefaultLayout>

View file

@ -1,24 +0,0 @@
---
import { getCollection } from 'astro:content';
import DefaultLayout from '@/layouts/DefaultLayout.astro';
import { BlogPosts } from '@/components/sections/blog-posts';
const posts = (await getCollection('problems')).sort(
(a, b) => b.data.pubDate.valueOf() - a.data.pubDate.valueOf(),
);
const SITE_TITLE = 'Problems - Algorithmic Challenges and Solutions';
const SITE_DESCRIPTION = 'Interesting problems from contests, assessments, and real-world applications with detailed solutions.';
---
<DefaultLayout title={SITE_TITLE} description={SITE_DESCRIPTION}>
<div class="py-16 md:py-28 lg:py-32">
<div class="container max-w-5xl">
<div class="mb-12">
<h1 class="text-4xl font-bold mb-4">Problems</h1>
<p class="text-xl text-muted-foreground">{SITE_DESCRIPTION}</p>
</div>
<BlogPosts posts={posts} collection="problems" client:only='react' />
</div>
</div>
</DefaultLayout>

View file

@ -1,15 +0,0 @@
import rss from "@astrojs/rss";
import { getCollection } from "astro:content";
export async function GET(context) {
const posts = await getCollection("problems");
return rss({
title: "Problems - Algorithmic Challenges and Solutions",
description: "Interesting problems from contests, assessments, and real-world applications with detailed solutions.",
site: context.site,
items: posts.map((post) => ({
...post.data,
link: `/problems/${post.id}/`,
})),
});
}

View file

@ -1,30 +0,0 @@
---
import { type CollectionEntry, getCollection } from 'astro:content';
import DefaultLayout from '@/layouts/DefaultLayout.astro';
import { BlogPost } from '@/components/sections/blog-post';
import { SITE_TITLE, SITE_DESCRIPTION } from '@/consts';
import NewsletterSignup from '@/components/sections/newsletter-signup';
import { render } from 'astro:content';
export async function getStaticPaths() {
const posts = await getCollection('puzzles');
return posts.map((post) => ({
params: { slug: post.id },
props: post,
}));
}
type Props = CollectionEntry<'puzzles'>;
const post = Astro.props;
const { Content } = await render(post);
---
<DefaultLayout title={`${post.data.title} | ${SITE_TITLE}`} description={post.data.description}>
<div class="py-16 md:py-28 lg:py-32">
<BlogPost post={post} client:only='react'>
<Content />
</BlogPost>
</div>
<NewsletterSignup client:load />
</DefaultLayout>

View file

@ -1,24 +0,0 @@
---
import { getCollection } from 'astro:content';
import DefaultLayout from '@/layouts/DefaultLayout.astro';
import { BlogPosts } from '@/components/sections/blog-posts';
const posts = (await getCollection('puzzles')).sort(
(a, b) => b.data.pubDate.valueOf() - a.data.pubDate.valueOf(),
);
const SITE_TITLE = 'Puzzles - Logic, Mathematics, and Interactive Challenges';
const SITE_DESCRIPTION = 'Mathematical puzzles, logic problems, and interactive challenges that teach algorithmic thinking.';
---
<DefaultLayout title={SITE_TITLE} description={SITE_DESCRIPTION}>
<div class="py-16 md:py-28 lg:py-32">
<div class="container max-w-5xl">
<div class="mb-12">
<h1 class="text-4xl font-bold mb-4">Puzzles</h1>
<p class="text-xl text-muted-foreground">{SITE_DESCRIPTION}</p>
</div>
<BlogPosts posts={posts} collection="puzzles" client:only='react' />
</div>
</div>
</DefaultLayout>

View file

@ -1,15 +0,0 @@
import rss from "@astrojs/rss";
import { getCollection } from "astro:content";
export async function GET(context) {
const posts = await getCollection("puzzles");
return rss({
title: "Puzzles - Logic, Mathematics, and Interactive Challenges",
description: "Mathematical puzzles, logic problems, and interactive challenges that teach algorithmic thinking.",
site: context.site,
items: posts.map((post) => ({
...post.data,
link: `/puzzles/${post.id}/`,
})),
});
}

View file

@ -1,30 +0,0 @@
---
import { type CollectionEntry, getCollection } from 'astro:content';
import DefaultLayout from '@/layouts/DefaultLayout.astro';
import { BlogPost } from '@/components/sections/blog-post';
import { SITE_TITLE, SITE_DESCRIPTION } from '@/consts';
import NewsletterSignup from '@/components/sections/newsletter-signup';
import { render } from 'astro:content';
export async function getStaticPaths() {
const posts = await getCollection('reflections');
return posts.map((post) => ({
params: { slug: post.id },
props: post,
}));
}
type Props = CollectionEntry<'reflections'>;
const post = Astro.props;
const { Content } = await render(post);
---
<DefaultLayout title={`${post.data.title} | ${SITE_TITLE}`} description={post.data.description}>
<div class="py-16 md:py-28 lg:py-32">
<BlogPost post={post} client:only='react'>
<Content />
</BlogPost>
</div>
<NewsletterSignup client:load />
</DefaultLayout>

View file

@ -1,24 +0,0 @@
---
import { getCollection } from 'astro:content';
import DefaultLayout from '@/layouts/DefaultLayout.astro';
import { BlogPosts } from '@/components/sections/blog-posts';
const posts = (await getCollection('reflections')).sort(
(a, b) => b.data.pubDate.valueOf() - a.data.pubDate.valueOf(),
);
const SITE_TITLE = 'Reflections - Thoughts on Teaching, Learning, and Life';
const SITE_DESCRIPTION = 'Personal reflections on academia, teaching philosophy, and the journey of continuous learning.';
---
<DefaultLayout title={SITE_TITLE} description={SITE_DESCRIPTION}>
<div class="py-16 md:py-28 lg:py-32">
<div class="container max-w-5xl">
<div class="mb-12">
<h1 class="text-4xl font-bold mb-4">Reflections</h1>
<p class="text-xl text-muted-foreground">{SITE_DESCRIPTION}</p>
</div>
<BlogPosts posts={posts} collection="reflections" client:only='react' />
</div>
</div>
</DefaultLayout>

View file

@ -1,15 +0,0 @@
import rss from "@astrojs/rss";
import { getCollection } from "astro:content";
export async function GET(context) {
const posts = await getCollection("reflections");
return rss({
title: "Reflections - Thoughts on Teaching, Learning, and Life",
description: "Personal reflections on academia, teaching philosophy, and the journey of continuous learning.",
site: context.site,
items: posts.map((post) => ({
...post.data,
link: `/reflections/${post.id}/`,
})),
});
}

View file

@ -1,30 +0,0 @@
---
import { type CollectionEntry, getCollection } from 'astro:content';
import DefaultLayout from '@/layouts/DefaultLayout.astro';
import { BlogPost } from '@/components/sections/blog-post';
import { SITE_TITLE, SITE_DESCRIPTION } from '@/consts';
import NewsletterSignup from '@/components/sections/newsletter-signup';
import { render } from 'astro:content';
export async function getStaticPaths() {
const posts = await getCollection('reviews');
return posts.map((post) => ({
params: { slug: post.id },
props: post,
}));
}
type Props = CollectionEntry<'reviews'>;
const post = Astro.props;
const { Content } = await render(post);
---
<DefaultLayout title={`${post.data.title} | ${SITE_TITLE}`} description={post.data.description}>
<div class="py-16 md:py-28 lg:py-32">
<BlogPost post={post} client:only='react'>
<Content />
</BlogPost>
</div>
<NewsletterSignup client:load />
</DefaultLayout>

View file

@ -1,24 +0,0 @@
---
import { getCollection } from 'astro:content';
import DefaultLayout from '@/layouts/DefaultLayout.astro';
import { BlogPosts } from '@/components/sections/blog-posts';
const posts = (await getCollection('reviews')).sort(
(a, b) => b.data.pubDate.valueOf() - a.data.pubDate.valueOf(),
);
const SITE_TITLE = 'Reviews - Tools, Books, and Technology';
const SITE_DESCRIPTION = 'In-depth reviews of tools, books, hardware, and software for academics and developers.';
---
<DefaultLayout title={SITE_TITLE} description={SITE_DESCRIPTION}>
<div class="py-16 md:py-28 lg:py-32">
<div class="container max-w-5xl">
<div class="mb-12">
<h1 class="text-4xl font-bold mb-4">Reviews</h1>
<p class="text-xl text-muted-foreground">{SITE_DESCRIPTION}</p>
</div>
<BlogPosts posts={posts} collection="reviews" client:only='react' />
</div>
</div>
</DefaultLayout>

View file

@ -1,15 +0,0 @@
import rss from "@astrojs/rss";
import { getCollection } from "astro:content";
export async function GET(context) {
const posts = await getCollection("reviews");
return rss({
title: "Reviews - Tools, Books, and Technology",
description: "In-depth reviews of tools, books, hardware, and software for academics and developers.",
site: context.site,
items: posts.map((post) => ({
...post.data,
link: `/reviews/${post.id}/`,
})),
});
}

View file

@ -1,16 +1,20 @@
import rss from "@astrojs/rss"; import rss from "@astrojs/rss";
import { getCollection } from "astro:content"; import { getCollection } from "astro:content";
import { SITE_TITLE, SITE_DESCRIPTION } from "../consts"; import { SITE_TITLE, SITE_DESCRIPTION } from "../consts";
import { ACTIVE_BLOG_SITE } from "../blog-sites.js";
export async function GET(context) { export async function GET(context) {
const posts = await getCollection("blog"); const posts = (await getCollection(ACTIVE_BLOG_SITE.key)).sort(
(a, b) => b.data.pubDate.valueOf() - a.data.pubDate.valueOf(),
);
return rss({ return rss({
title: SITE_TITLE, title: SITE_TITLE,
description: SITE_DESCRIPTION, description: SITE_DESCRIPTION,
site: context.site, site: context.site,
items: posts.map((post) => ({ items: posts.map((post) => ({
...post.data, ...post.data,
link: `/blog/${post.id}/`, link: `/${post.id}/`,
})), })),
}); });
} }

View file

@ -1,30 +0,0 @@
---
import { type CollectionEntry, getCollection } from 'astro:content';
import DefaultLayout from '@/layouts/DefaultLayout.astro';
import { BlogPost } from '@/components/sections/blog-post';
import { SITE_TITLE, SITE_DESCRIPTION } from '@/consts';
import NewsletterSignup from '@/components/sections/newsletter-signup';
import { render } from 'astro:content';
export async function getStaticPaths() {
const posts = await getCollection('vibes');
return posts.map((post) => ({
params: { slug: post.id },
props: post,
}));
}
type Props = CollectionEntry<'vibes'>;
const post = Astro.props;
const { Content } = await render(post);
---
<DefaultLayout title={`${post.data.title} | ${SITE_TITLE}`} description={post.data.description}>
<div class="py-16 md:py-28 lg:py-32">
<BlogPost post={post} client:only='react'>
<Content />
</BlogPost>
</div>
<NewsletterSignup client:load />
</DefaultLayout>

View file

@ -1,24 +0,0 @@
---
import { getCollection } from 'astro:content';
import DefaultLayout from '@/layouts/DefaultLayout.astro';
import { BlogPosts } from '@/components/sections/blog-posts';
const posts = (await getCollection('vibes')).sort(
(a, b) => b.data.pubDate.valueOf() - a.data.pubDate.valueOf(),
);
const SITE_TITLE = 'Vibes - AI Conversations and Creative Explorations';
const SITE_DESCRIPTION = 'Philosophical dialogues with AI, creative coding experiments, and explorations at the intersection of technology and consciousness.';
---
<DefaultLayout title={SITE_TITLE} description={SITE_DESCRIPTION}>
<div class="py-16 md:py-28 lg:py-32">
<div class="container max-w-5xl">
<div class="mb-12">
<h1 class="text-4xl font-bold mb-4">Vibes</h1>
<p class="text-xl text-muted-foreground">{SITE_DESCRIPTION}</p>
</div>
<BlogPosts posts={posts} collection="vibes" client:only='react' />
</div>
</div>
</DefaultLayout>

View file

@ -1,15 +0,0 @@
import rss from "@astrojs/rss";
import { getCollection } from "astro:content";
export async function GET(context) {
const posts = await getCollection("vibes");
return rss({
title: "Vibes - AI Conversations and Creative Explorations",
description: "Philosophical dialogues with AI, creative coding experiments, and explorations at the intersection of technology and consciousness.",
site: context.site,
items: posts.map((post) => ({
...post.data,
link: `/vibes/${post.id}/`,
})),
});
}

View file

@ -1,30 +0,0 @@
---
import { type CollectionEntry, getCollection } from 'astro:content';
import DefaultLayout from '@/layouts/DefaultLayout.astro';
import { BlogPost } from '@/components/sections/blog-post';
import { SITE_TITLE, SITE_DESCRIPTION } from '@/consts';
import NewsletterSignup from '@/components/sections/newsletter-signup';
import { render } from 'astro:content';
export async function getStaticPaths() {
const posts = await getCollection('workflows');
return posts.map((post) => ({
params: { slug: post.id },
props: post,
}));
}
type Props = CollectionEntry<'workflows'>;
const post = Astro.props;
const { Content } = await render(post);
---
<DefaultLayout title={`${post.data.title} | ${SITE_TITLE}`} description={post.data.description}>
<div class="py-16 md:py-28 lg:py-32">
<BlogPost post={post} client:only='react'>
<Content />
</BlogPost>
</div>
<NewsletterSignup client:load />
</DefaultLayout>

View file

@ -1,24 +0,0 @@
---
import { getCollection } from 'astro:content';
import DefaultLayout from '@/layouts/DefaultLayout.astro';
import { BlogPosts } from '@/components/sections/blog-posts';
const posts = (await getCollection('workflows')).sort(
(a, b) => b.data.pubDate.valueOf() - a.data.pubDate.valueOf(),
);
const SITE_TITLE = 'Workflows - Productivity and Automation';
const SITE_DESCRIPTION = 'Efficient workflows, automation scripts, and productivity tools for developers and academics.';
---
<DefaultLayout title={SITE_TITLE} description={SITE_DESCRIPTION}>
<div class="py-16 md:py-28 lg:py-32">
<div class="container max-w-5xl">
<div class="mb-12">
<h1 class="text-4xl font-bold mb-4">Workflows</h1>
<p class="text-xl text-muted-foreground">{SITE_DESCRIPTION}</p>
</div>
<BlogPosts posts={posts} collection="workflows" client:only='react' />
</div>
</div>
</DefaultLayout>

View file

@ -1,15 +0,0 @@
import rss from "@astrojs/rss";
import { getCollection } from "astro:content";
export async function GET(context) {
const posts = await getCollection("workflows");
return rss({
title: "Workflows - Productivity and Automation",
description: "Efficient workflows, automation scripts, and productivity tools for developers and academics.",
site: context.site,
items: posts.map((post) => ({
...post.data,
link: `/workflows/${post.id}/`,
})),
});
}