diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..c524790 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,9 @@ +node_modules +dist +.astro +.git +.vscode +.DS_Store +npm-debug.log* +Dockerfile +docker-compose*.yml diff --git a/.forgejo/workflows/build.yml b/.forgejo/workflows/build.yml new file mode 100644 index 0000000..2d463f4 --- /dev/null +++ b/.forgejo/workflows/build.yml @@ -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 diff --git a/DEPLOYMENT.md b/DEPLOYMENT.md index 282ecf9..accb7ce 100644 --- a/DEPLOYMENT.md +++ b/DEPLOYMENT.md @@ -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 -- `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 +Each site has its own root feed at `/rss.xml`, for example: -## 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` -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 +## Local Commands -## Deployment Steps +Build all sites: -1. **Build the site**: `npm run build` -2. **Deploy to Netlify**: Connect your Git repository to Netlify -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 +```bash +npm run build +``` -## Example URLs +Build one site: -Local development: -- `http://localhost:4323/art/algorithmic-sketches` +```bash +BLOG_SITE=puzzles npm run build:site +``` -Production: -- `https://art.neeldhara.blog/algorithmic-sketches` -- `https://neeldhara.blog/art/algorithmic-sketches` (direct access) +Run one site locally: -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: -1. Each subdomain homepage loads correctly (e.g., `art.neeldhara.blog`) -2. Individual blog posts load correctly (e.g., `art.neeldhara.blog/algorithmic-sketches`) -3. RSS feeds work (e.g., `art.neeldhara.blog/rss.xml`) +```bash +docker compose build +``` + +## 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 +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. diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..05dca2b --- /dev/null +++ b/Dockerfile @@ -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 diff --git a/astro.config.mjs b/astro.config.mjs index 20a3900..9d7bf29 100644 --- a/astro.config.mjs +++ b/astro.config.mjs @@ -6,12 +6,14 @@ import sitemap from "@astrojs/sitemap"; import react from "@astrojs/react"; // @ts-ignore - TailwindCSS v4 Vite plugin has type compatibility issues with Astro import tailwindcss from "@tailwindcss/vite"; +import { ACTIVE_BLOG_SITE } from "./src/blog-sites.js"; // https://astro.build/config export default defineConfig({ - site: "https://neeldhara.blog", + site: ACTIVE_BLOG_SITE.url, integrations: [mdx(), sitemap(), react()], output: "static", + outDir: `dist/${ACTIVE_BLOG_SITE.key}`, vite: { // @ts-ignore - Suppress TypeScript error for TailwindCSS v4 plugin compatibility diff --git a/deploy/nginx.conf b/deploy/nginx.conf new file mode 100644 index 0000000..22d084d --- /dev/null +++ b/deploy/nginx.conf @@ -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; + } +} diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..e2923d4 --- /dev/null +++ b/docker-compose.yml @@ -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 diff --git a/netlify.toml b/netlify.toml deleted file mode 100644 index 8b8594a..0000000 --- a/netlify.toml +++ /dev/null @@ -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 diff --git a/package.json b/package.json index 9fffafc..83e6e83 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,16 @@ "author": "Shadcnblocks.com", "scripts": { "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", "astro": "astro", "format": "prettier -w ." diff --git a/scripts/build-all-sites.mjs b/scripts/build-all-sites.mjs new file mode 100644 index 0000000..1a47411 --- /dev/null +++ b/scripts/build-all-sites.mjs @@ -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); + } +} diff --git a/src/blog-sites.js b/src/blog-sites.js new file mode 100644 index 0000000..cae51a7 --- /dev/null +++ b/src/blog-sites.js @@ -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(); diff --git a/src/components/problems/README.md b/src/components/problems/README.md index ed147d5..bd2d9b0 100644 --- a/src/components/problems/README.md +++ b/src/components/problems/README.md @@ -5,7 +5,8 @@ This directory contains the interactive React components for the "100 Prisoners ## Files Created ### 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 @@ -38,6 +39,7 @@ This directory contains the interactive React components for the "100 Prisoners ## Features All components are: + - ✅ Fully interactive with React hooks - ✅ Styled with Tailwind CSS classes - ✅ Responsive for mobile and desktop @@ -47,12 +49,15 @@ All components are: ## How It Works ### 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. ### The Naive Strategy + Each prisoner opens 50 random boxes. Probability of success: (1/2)^100 ≈ 0% ### The Clever Strategy (Loop Following) + 1. Open the box with your own number 2. Look at the number inside 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%! ### 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: 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 Inspired by Nicky Case's "Parable of the Polygons", these components aim to: + - Make abstract mathematical concepts tangible through interaction - Allow readers to explore and discover patterns themselves - Provide immediate visual feedback @@ -95,6 +102,7 @@ Inspired by Nicky Case's "Parable of the Polygons", these components aim to: ## Future Enhancements Potential additions: + - Auto-play mode for the game simulator using the loop strategy - Animation showing cycle formation in real-time - Monte Carlo simulation running multiple trials diff --git a/src/components/sections/all-blogs.tsx b/src/components/sections/all-blogs.tsx index 43c935f..13c0055 100644 --- a/src/components/sections/all-blogs.tsx +++ b/src/components/sections/all-blogs.tsx @@ -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 { @@ -7,81 +7,13 @@ import { CardDescription, CardHeader, } from "@/components/ui/card"; +import { BLOG_SITES } from "@/blog-sites.js"; -const features = [ - { - title: "BFS", - description: "Thematic roundups based on conferences or techniques.", - 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", - }, -]; +const features = BLOG_SITES.map((site) => ({ + title: site.title, + description: site.description, + href: site.url, +})); export default function AllBlogs() { return ( @@ -89,14 +21,12 @@ export default function AllBlogs() {

CURRENTLY ACTIVE BLOGS

-
-
+
{features.map((feature, index) => ( - {/* */}

{feature.title}

{feature.description} diff --git a/src/components/sections/blog-posts.tsx b/src/components/sections/blog-posts.tsx index 27287d5..00f8ec8 100644 --- a/src/components/sections/blog-posts.tsx +++ b/src/components/sections/blog-posts.tsx @@ -6,24 +6,30 @@ import { Separator } from "@/components/ui/separator"; import { Calendar, Clock, User } from "lucide-react"; 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 const featuredPost = posts[0]; const remainingPosts = posts.slice(1); + const hrefFor = (post: any) => + collection ? `/${collection}/${post.id}/` : `/${post.id}/`; return (
- - -
- -
+
+ +
{/* Featured Post */}
@@ -80,7 +86,7 @@ const BlogPosts = ({ posts, collection }: { posts: any[]; collection: string })
-

{greeting}

-

Welcome to my Astro website!

-``` - -## 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. diff --git a/src/content/blog/post-2.md b/src/content/blog/post-2.md deleted file mode 100644 index 5d52d4c..0000000 --- a/src/content/blog/post-2.md +++ /dev/null @@ -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! diff --git a/src/content/blog/post-3.md b/src/content/blog/post-3.md deleted file mode 100644 index 88ee50f..0000000 --- a/src/content/blog/post-3.md +++ /dev/null @@ -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. diff --git a/src/content/blog/post-4.md b/src/content/blog/post-4.md deleted file mode 100644 index 64df5be..0000000 --- a/src/content/blog/post-4.md +++ /dev/null @@ -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. diff --git a/src/content/blog/post-5.mdx b/src/content/blog/post-5.mdx deleted file mode 100644 index 13d6b72..0000000 --- a/src/content/blog/post-5.mdx +++ /dev/null @@ -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"; - - - Embedded component in MDX - - -## 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. diff --git a/src/content/config.ts b/src/content/config.ts index 495ae77..41abd05 100644 --- a/src/content/config.ts +++ b/src/content/config.ts @@ -1,5 +1,5 @@ -import { glob } from 'astro/loaders'; -import { defineCollection, z } from 'astro:content'; +import { glob } from "astro/loaders"; +import { defineCollection, z } from "astro:content"; const blogSchema = z.object({ title: z.string(), @@ -11,28 +11,19 @@ const blogSchema = z.object({ authorName: z.string().optional(), }); -const blog = defineCollection({ - loader: glob({ base: "./src/content/blog", pattern: "**/*.{md,mdx}" }), - schema: blogSchema, -}); - -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}" }), +const research = defineCollection({ + loader: glob({ + base: "./src/content/research", + pattern: "**/*.{md,mdx}", + }), schema: blogSchema, }); const puzzles = defineCollection({ - loader: glob({ base: "./src/content/puzzles", pattern: "**/*.{md,mdx}" }), + loader: glob({ + base: "./src/content/puzzles", + pattern: "**/*.{md,mdx}", + }), schema: blogSchema, }); @@ -51,21 +42,6 @@ const vibes = defineCollection({ 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({ loader: glob({ base: "./src/content/art", pattern: "**/*.{md,mdx}" }), schema: blogSchema, @@ -77,17 +53,11 @@ const poetry = defineCollection({ }); export const collections = { - blog, - bfs, - dfs, - problems, - puzzles, - reviews, - reflections, + research, vibes, - workflows, - magic, - contests, - art, + puzzles, + reflections, poetry, + reviews, + art, }; diff --git a/src/content/problems/100-prisoners.mdx b/src/content/puzzles/100-prisoners.mdx similarity index 100% rename from src/content/problems/100-prisoners.mdx rename to src/content/puzzles/100-prisoners.mdx diff --git a/src/content/problems/dynamic-programming-gems.md b/src/content/puzzles/dynamic-programming-gems.md similarity index 100% rename from src/content/problems/dynamic-programming-gems.md rename to src/content/puzzles/dynamic-programming-gems.md diff --git a/src/content/magic/four-aces-miracle.md b/src/content/puzzles/four-aces-miracle.md similarity index 100% rename from src/content/magic/four-aces-miracle.md rename to src/content/puzzles/four-aces-miracle.md diff --git a/src/content/magic/gilbreath-principle.md b/src/content/puzzles/gilbreath-principle.md similarity index 100% rename from src/content/magic/gilbreath-principle.md rename to src/content/puzzles/gilbreath-principle.md diff --git a/src/content/problems/graph-algorithms-assessment.md b/src/content/puzzles/graph-algorithms-assessment.md similarity index 100% rename from src/content/problems/graph-algorithms-assessment.md rename to src/content/puzzles/graph-algorithms-assessment.md diff --git a/src/content/contests/ioi-2023-analysis.md b/src/content/puzzles/ioi-2023-analysis.md similarity index 100% rename from src/content/contests/ioi-2023-analysis.md rename to src/content/puzzles/ioi-2023-analysis.md diff --git a/src/content/contests/projecteuler-favorites.md b/src/content/puzzles/projecteuler-favorites.md similarity index 100% rename from src/content/contests/projecteuler-favorites.md rename to src/content/puzzles/projecteuler-favorites.md diff --git a/src/content/bfs/conference-highlights-2024.md b/src/content/research/conference-highlights-2024.md similarity index 100% rename from src/content/bfs/conference-highlights-2024.md rename to src/content/research/conference-highlights-2024.md diff --git a/src/content/bfs/distributed-algorithms.md b/src/content/research/distributed-algorithms.md similarity index 100% rename from src/content/bfs/distributed-algorithms.md rename to src/content/research/distributed-algorithms.md diff --git a/src/content/workflows/git-automation.md b/src/content/research/git-automation.md similarity index 100% rename from src/content/workflows/git-automation.md rename to src/content/research/git-automation.md diff --git a/src/content/workflows/obsidian-academic.md b/src/content/research/obsidian-academic.md similarity index 100% rename from src/content/workflows/obsidian-academic.md rename to src/content/research/obsidian-academic.md diff --git a/src/content/dfs/paper-review-approximation.md b/src/content/research/paper-review-approximation.md similarity index 100% rename from src/content/dfs/paper-review-approximation.md rename to src/content/research/paper-review-approximation.md diff --git a/src/content/dfs/paper-review-graph-decomposition.md b/src/content/research/paper-review-graph-decomposition.md similarity index 100% rename from src/content/dfs/paper-review-graph-decomposition.md rename to src/content/research/paper-review-graph-decomposition.md diff --git a/src/content/bfs/quantum-algorithms.md b/src/content/research/quantum-algorithms.md similarity index 100% rename from src/content/bfs/quantum-algorithms.md rename to src/content/research/quantum-algorithms.md diff --git a/src/pages/art/[...slug].astro b/src/pages/[...slug].astro similarity index 61% rename from src/pages/art/[...slug].astro rename to src/pages/[...slug].astro index 825d4c1..5d3caf6 100644 --- a/src/pages/art/[...slug].astro +++ b/src/pages/[...slug].astro @@ -1,30 +1,29 @@ --- -import { type CollectionEntry, getCollection } from 'astro:content'; +import { getCollection, render } 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'; +import { ACTIVE_BLOG_SITE } from '@/blog-sites.js'; +import { SITE_TITLE } from '@/consts'; export async function getStaticPaths() { - const posts = await getCollection('art'); + const posts = await getCollection(ACTIVE_BLOG_SITE.key); return posts.map((post) => ({ params: { slug: post.id }, props: post, })); } -type Props = CollectionEntry<'art'>; const post = Astro.props; -const { Content } = await render(post); - +const { Content } = await render(post); --- +
- +
- + -
\ No newline at end of file + diff --git a/src/pages/art/index.astro b/src/pages/art/index.astro deleted file mode 100644 index f79ab9f..0000000 --- a/src/pages/art/index.astro +++ /dev/null @@ -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.'; ---- - - - - diff --git a/src/pages/art/rss.xml.js b/src/pages/art/rss.xml.js deleted file mode 100644 index c9951db..0000000 --- a/src/pages/art/rss.xml.js +++ /dev/null @@ -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}/`, - })), - }); -} diff --git a/src/pages/bfs/[...slug].astro b/src/pages/bfs/[...slug].astro deleted file mode 100644 index 719091f..0000000 --- a/src/pages/bfs/[...slug].astro +++ /dev/null @@ -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); - ---- - -
- - - -
- - -
\ No newline at end of file diff --git a/src/pages/bfs/index.astro b/src/pages/bfs/index.astro deleted file mode 100644 index 6ae408c..0000000 --- a/src/pages/bfs/index.astro +++ /dev/null @@ -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.'; ---- - - -
-
-
-

BFS

-

{SITE_DESCRIPTION}

-
- -
-
-
diff --git a/src/pages/bfs/rss.xml.js b/src/pages/bfs/rss.xml.js deleted file mode 100644 index ebcf200..0000000 --- a/src/pages/bfs/rss.xml.js +++ /dev/null @@ -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}/`, - })), - }); -} diff --git a/src/pages/blog/[...slug].astro b/src/pages/blog/[...slug].astro deleted file mode 100644 index b761ff1..0000000 --- a/src/pages/blog/[...slug].astro +++ /dev/null @@ -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); - ---- - -
- - - -
- - -
\ No newline at end of file diff --git a/src/pages/blog/index.astro b/src/pages/blog/index.astro deleted file mode 100644 index 8b91065..0000000 --- a/src/pages/blog/index.astro +++ /dev/null @@ -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."; ---- - - - - - - diff --git a/src/pages/contests/[...slug].astro b/src/pages/contests/[...slug].astro deleted file mode 100644 index 92d3d83..0000000 --- a/src/pages/contests/[...slug].astro +++ /dev/null @@ -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); - ---- - -
- - - -
- - -
\ No newline at end of file diff --git a/src/pages/contests/index.astro b/src/pages/contests/index.astro deleted file mode 100644 index 17300d2..0000000 --- a/src/pages/contests/index.astro +++ /dev/null @@ -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.'; ---- - - -
-
-
-

Contests

-

{SITE_DESCRIPTION}

-
- -
-
-
diff --git a/src/pages/contests/rss.xml.js b/src/pages/contests/rss.xml.js deleted file mode 100644 index a8866b1..0000000 --- a/src/pages/contests/rss.xml.js +++ /dev/null @@ -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}/`, - })), - }); -} diff --git a/src/pages/dfs/[...slug].astro b/src/pages/dfs/[...slug].astro deleted file mode 100644 index 12851f5..0000000 --- a/src/pages/dfs/[...slug].astro +++ /dev/null @@ -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); - ---- - -
- - - -
- - -
\ No newline at end of file diff --git a/src/pages/dfs/index.astro b/src/pages/dfs/index.astro deleted file mode 100644 index efefdcf..0000000 --- a/src/pages/dfs/index.astro +++ /dev/null @@ -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.'; ---- - - -
-
-
-

DFS

-

{SITE_DESCRIPTION}

-
- -
-
-
diff --git a/src/pages/dfs/rss.xml.js b/src/pages/dfs/rss.xml.js deleted file mode 100644 index e414457..0000000 --- a/src/pages/dfs/rss.xml.js +++ /dev/null @@ -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}/`, - })), - }); -} diff --git a/src/pages/index.astro b/src/pages/index.astro index d53b387..1343474 100644 --- a/src/pages/index.astro +++ b/src/pages/index.astro @@ -1,10 +1,23 @@ --- +import { getCollection } from 'astro:content'; 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(), +); --- - - \ No newline at end of file +
+
+
+

{ACTIVE_BLOG_SITE.title}

+

{ACTIVE_BLOG_SITE.description}

+
+ +
+
+ diff --git a/src/pages/magic/[...slug].astro b/src/pages/magic/[...slug].astro deleted file mode 100644 index 838670d..0000000 --- a/src/pages/magic/[...slug].astro +++ /dev/null @@ -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); - ---- - -
- - - -
- - -
\ No newline at end of file diff --git a/src/pages/magic/index.astro b/src/pages/magic/index.astro deleted file mode 100644 index 8968166..0000000 --- a/src/pages/magic/index.astro +++ /dev/null @@ -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.'; ---- - - -
-
-
-

Magic

-

{SITE_DESCRIPTION}

-
- -
-
-
diff --git a/src/pages/magic/rss.xml.js b/src/pages/magic/rss.xml.js deleted file mode 100644 index 397f2dd..0000000 --- a/src/pages/magic/rss.xml.js +++ /dev/null @@ -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}/`, - })), - }); -} diff --git a/src/pages/poetry/[...slug].astro b/src/pages/poetry/[...slug].astro deleted file mode 100644 index 559bc30..0000000 --- a/src/pages/poetry/[...slug].astro +++ /dev/null @@ -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); - ---- - -
- - - -
- - -
\ No newline at end of file diff --git a/src/pages/poetry/index.astro b/src/pages/poetry/index.astro deleted file mode 100644 index 478d399..0000000 --- a/src/pages/poetry/index.astro +++ /dev/null @@ -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.'; ---- - - -
-
-
-

Poetry

-

{SITE_DESCRIPTION}

-
- -
-
-
diff --git a/src/pages/poetry/rss.xml.js b/src/pages/poetry/rss.xml.js deleted file mode 100644 index 2a5e752..0000000 --- a/src/pages/poetry/rss.xml.js +++ /dev/null @@ -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}/`, - })), - }); -} diff --git a/src/pages/problems/[...slug].astro b/src/pages/problems/[...slug].astro deleted file mode 100644 index 718d30f..0000000 --- a/src/pages/problems/[...slug].astro +++ /dev/null @@ -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); - ---- - -
- - - -
- - -
\ No newline at end of file diff --git a/src/pages/problems/index.astro b/src/pages/problems/index.astro deleted file mode 100644 index 3e9a275..0000000 --- a/src/pages/problems/index.astro +++ /dev/null @@ -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.'; ---- - - -
-
-
-

Problems

-

{SITE_DESCRIPTION}

-
- -
-
-
diff --git a/src/pages/problems/rss.xml.js b/src/pages/problems/rss.xml.js deleted file mode 100644 index bcb97e5..0000000 --- a/src/pages/problems/rss.xml.js +++ /dev/null @@ -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}/`, - })), - }); -} diff --git a/src/pages/puzzles/[...slug].astro b/src/pages/puzzles/[...slug].astro deleted file mode 100644 index 44959bc..0000000 --- a/src/pages/puzzles/[...slug].astro +++ /dev/null @@ -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); - ---- - -
- - - -
- - -
\ No newline at end of file diff --git a/src/pages/puzzles/index.astro b/src/pages/puzzles/index.astro deleted file mode 100644 index 9a6a76b..0000000 --- a/src/pages/puzzles/index.astro +++ /dev/null @@ -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.'; ---- - - -
-
-
-

Puzzles

-

{SITE_DESCRIPTION}

-
- -
-
-
diff --git a/src/pages/puzzles/rss.xml.js b/src/pages/puzzles/rss.xml.js deleted file mode 100644 index 301b0ce..0000000 --- a/src/pages/puzzles/rss.xml.js +++ /dev/null @@ -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}/`, - })), - }); -} diff --git a/src/pages/reflections/[...slug].astro b/src/pages/reflections/[...slug].astro deleted file mode 100644 index e108fc0..0000000 --- a/src/pages/reflections/[...slug].astro +++ /dev/null @@ -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); - ---- - -
- - - -
- - -
\ No newline at end of file diff --git a/src/pages/reflections/index.astro b/src/pages/reflections/index.astro deleted file mode 100644 index fb61f9f..0000000 --- a/src/pages/reflections/index.astro +++ /dev/null @@ -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.'; ---- - - -
-
-
-

Reflections

-

{SITE_DESCRIPTION}

-
- -
-
-
diff --git a/src/pages/reflections/rss.xml.js b/src/pages/reflections/rss.xml.js deleted file mode 100644 index 8030e6d..0000000 --- a/src/pages/reflections/rss.xml.js +++ /dev/null @@ -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}/`, - })), - }); -} diff --git a/src/pages/reviews/[...slug].astro b/src/pages/reviews/[...slug].astro deleted file mode 100644 index 51458a5..0000000 --- a/src/pages/reviews/[...slug].astro +++ /dev/null @@ -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); - ---- - -
- - - -
- - -
\ No newline at end of file diff --git a/src/pages/reviews/index.astro b/src/pages/reviews/index.astro deleted file mode 100644 index be84470..0000000 --- a/src/pages/reviews/index.astro +++ /dev/null @@ -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.'; ---- - - -
-
-
-

Reviews

-

{SITE_DESCRIPTION}

-
- -
-
-
diff --git a/src/pages/reviews/rss.xml.js b/src/pages/reviews/rss.xml.js deleted file mode 100644 index 60b6924..0000000 --- a/src/pages/reviews/rss.xml.js +++ /dev/null @@ -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}/`, - })), - }); -} diff --git a/src/pages/rss.xml.js b/src/pages/rss.xml.js index fb3993e..ec2ce2d 100644 --- a/src/pages/rss.xml.js +++ b/src/pages/rss.xml.js @@ -1,16 +1,20 @@ import rss from "@astrojs/rss"; import { getCollection } from "astro:content"; import { SITE_TITLE, SITE_DESCRIPTION } from "../consts"; +import { ACTIVE_BLOG_SITE } from "../blog-sites.js"; 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({ title: SITE_TITLE, description: SITE_DESCRIPTION, site: context.site, items: posts.map((post) => ({ ...post.data, - link: `/blog/${post.id}/`, + link: `/${post.id}/`, })), }); } diff --git a/src/pages/vibes/[...slug].astro b/src/pages/vibes/[...slug].astro deleted file mode 100644 index 4f9bcde..0000000 --- a/src/pages/vibes/[...slug].astro +++ /dev/null @@ -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); - ---- - -
- - - -
- - -
\ No newline at end of file diff --git a/src/pages/vibes/index.astro b/src/pages/vibes/index.astro deleted file mode 100644 index 09718ac..0000000 --- a/src/pages/vibes/index.astro +++ /dev/null @@ -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.'; ---- - - -
-
-
-

Vibes

-

{SITE_DESCRIPTION}

-
- -
-
-
diff --git a/src/pages/vibes/rss.xml.js b/src/pages/vibes/rss.xml.js deleted file mode 100644 index 54cc5b5..0000000 --- a/src/pages/vibes/rss.xml.js +++ /dev/null @@ -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}/`, - })), - }); -} diff --git a/src/pages/workflows/[...slug].astro b/src/pages/workflows/[...slug].astro deleted file mode 100644 index 6670994..0000000 --- a/src/pages/workflows/[...slug].astro +++ /dev/null @@ -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); - ---- - -
- - - -
- - -
\ No newline at end of file diff --git a/src/pages/workflows/index.astro b/src/pages/workflows/index.astro deleted file mode 100644 index 54126db..0000000 --- a/src/pages/workflows/index.astro +++ /dev/null @@ -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.'; ---- - - -
-
-
-

Workflows

-

{SITE_DESCRIPTION}

-
- -
-
-
diff --git a/src/pages/workflows/rss.xml.js b/src/pages/workflows/rss.xml.js deleted file mode 100644 index b78229a..0000000 --- a/src/pages/workflows/rss.xml.js +++ /dev/null @@ -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}/`, - })), - }); -}