diff --git a/.gitignore b/.gitignore index 1a21d5c..51eeb37 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,7 @@ doc/ build/ .obsidian/ + +# Comments server secrets and data +comments-server/.env +comments-server/data/ diff --git a/CLAUDE.md b/CLAUDE.md index 5fa59a5..d1e8179 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -43,8 +43,9 @@ Output to build/ (dev) or deploy/ (prod) - `ultima.lua` - Main entry point (CLI args, orchestration) - `config.lua` - TOML configuration loader - `lock_files.lua` - Change tracking/cache system - - `templates/engine.lua` - Custom templating engine + - `template_engine.lua` - Custom templating engine - `pandoc/processor.lua` - Pandoc filter for frontmatter + - `templates/` - Shared default templates (fallback for sites) - `utils/` - File I/O, formatters, functional utilities - `sites/{site}/` - Site-specific content @@ -59,7 +60,7 @@ Output to build/ (dev) or deploy/ (prod) Templates use `.htmlua` extension with custom syntax (all expressions end with `}}`): - `{{ expression }}` - Variable/expression output - `{% lua code }}` - Lua control flow (if/for/end) -- Escaping: `\{` for literal braces +- Escaping: `\{` for literal opening braces (closing braces `}` don't need escaping) Template context includes: `config`, `content`, `metadata`, `title`, `links`, `generate_absolute_path()` @@ -87,3 +88,9 @@ Each site deploys via git subtree to a separate branch (`deploy-{site}`). The ma - Lock files track file checksums to avoid unnecessary rewrites; use `-f` flag to force rebuild - `content_type: "media"` references static files instead of rendering markdown - `draft: true` hides content in production builds + +## Documentation Conventions + +- Use first person plural ("we", "our") rather than second person ("you", "your") +- Keep documentation concise and focused on implementation details +- Place project documentation in `docs/` directory diff --git a/comments-server/Caddyfile b/comments-server/Caddyfile new file mode 100644 index 0000000..49e0f22 --- /dev/null +++ b/comments-server/Caddyfile @@ -0,0 +1,96 @@ +# Remark42 Comment Server +# Replace {$REMARK_DOMAIN} with your actual domain (e.g., comments.dupe.sh) +# Or set the REMARK_DOMAIN environment variable + +{ + auto_https off +} + +:80 { + # CORS headers applied to ALL responses (including redirects) + header { + Access-Control-Allow-Origin "*" + Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS" + Access-Control-Allow-Headers "Content-Type, Authorization" + defer + } + + # Serve site-specific CSS based on Referer header containing site_id + @dupe_sh { + header_regexp Referer site_id=dupe_sh + path /web/remark.css + } + route @dupe_sh { + header Content-Type "text/css; charset=utf-8" + root * /srv/static + rewrite * /theme-dupe_sh.css + file_server + } + + @rorschach { + header_regexp Referer site_id=rorschach + path /web/remark.css + } + route @rorschach { + header Content-Type "text/css; charset=utf-8" + root * /srv/static + rewrite * /theme-rorschach.css + file_server + } + + @whwg { + header_regexp Referer site_id=whwg + path /web/remark.css + } + route @whwg { + header Content-Type "text/css; charset=utf-8" + root * /srv/static + rewrite * /theme-whwg.css + file_server + } + + # Default theme fallback for unknown sites + route /web/remark.css { + header Content-Type "text/css; charset=utf-8" + root * /srv/static + rewrite * /theme-default.css + file_server + } + + route /web/last-comments.css { + header Content-Type "text/css; charset=utf-8" + root * /srv/static + rewrite * /last-comments-custom.css + file_server + } + + route /web-orig/*.css { + uri replace /web-orig/ /web/ + reverse_proxy remark42:8080 { + header_down Content-Type "text/css; charset=utf-8" + header_down -X-Content-Type-Options + } + } + + route /web-orig/* { + uri replace /web-orig/ /web/ + reverse_proxy remark42:8080 + } + + route /static/* { + root * /srv + file_server + } + + reverse_proxy remark42:8080 { + # Override CSP to allow fonts from any source + header_down Content-Security-Policy "default-src 'none'; base-uri 'none'; form-action 'none'; connect-src 'self'; frame-src 'self' mailto:; img-src *; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; font-src * data:; object-src 'none'; frame-ancestors *;" + } + + log { + output file /data/access.log { + roll_size 10mb + roll_keep 3 + } + } +} diff --git a/comments-server/README.md b/comments-server/README.md new file mode 100644 index 0000000..76a4999 --- /dev/null +++ b/comments-server/README.md @@ -0,0 +1,152 @@ +# Remark42 Comment Server + +Self-hosted comment system for Ultima-powered sites. + +## Prerequisites + +- A VPS or server with Docker and Docker Compose installed +- A domain/subdomain pointed to the server (e.g., `comments.dupe.sh`) +- (Optional) GitHub OAuth app for GitHub login + +## Quick Start + +### 1. Copy configuration + +```bash +cp .env.example .env +``` + +### 2. Edit `.env` with your settings + +Required settings: +- `REMARK_URL` - Full URL where comments will be hosted (e.g., `https://comments.dupe.sh`) +- `REMARK_DOMAIN` - Domain without protocol (e.g., `comments.dupe.sh`) +- `SECRET` - Random string for JWT signing (generate with `openssl rand -base64 32`) +- `SITE_ID` - Site identifier(s), comma-separated for multiple sites +- `ADMIN_SHARED_ID` - Your admin user ID (see below) + +### 3. Set up GitHub OAuth (recommended) + +1. Go to [GitHub Developer Settings](https://github.com/settings/developers) +2. Create a new OAuth App: + - **Application name**: Your site comments + - **Homepage URL**: `https://comments.dupe.sh` + - **Authorization callback URL**: `https://comments.dupe.sh/auth/github/callback` +3. Copy the Client ID and Client Secret to your `.env` file + +### 4. Find your GitHub user ID + +```bash +curl -s https://api.github.com/users/YOUR_USERNAME | jq .id +``` + +Use this as `ADMIN_SHARED_ID` with format: `github_YOURID` + +### 5. Start the server + +```bash +docker compose up -d +``` + +### 6. Verify it's running + +```bash +# Check container status +docker compose ps + +# Check logs +docker compose logs -f remark42 + +# Test the API +curl https://comments.dupe.sh/api/v1/config +``` + +## Configuration + +### Multiple Sites + +To support multiple Ultima sites, use comma-separated site IDs: + +```env +SITE_ID=dupe_sh,rorschach,whwg +``` + +Each site uses its own `site_id` in the Ultima `config.toml`. + +### Authentication Options + +| Provider | Environment Variables | +|----------|----------------------| +| GitHub | `AUTH_GITHUB_CID`, `AUTH_GITHUB_CSEC` | +| Anonymous | `AUTH_ANON=true` | +| Email | `AUTH_EMAIL_ENABLE=true`, SMTP settings | + +### Notifications + +Set `NOTIFY_TYPE` to receive notifications for new comments: + +- `telegram` - Requires `NOTIFY_TELEGRAM_TOKEN` and `NOTIFY_TELEGRAM_CHAN` +- `email` - Uses SMTP settings +- `slack` - Requires webhook URL +- `none` - Disabled (default) + +## Maintenance + +### Backup data + +```bash +# Stop containers +docker compose stop + +# Backup data directory +tar -czvf remark42-backup-$(date +%Y%m%d).tar.gz data/ + +# Restart +docker compose start +``` + +### Update Remark42 + +```bash +docker compose pull +docker compose up -d +``` + +### View logs + +```bash +docker compose logs -f remark42 +docker compose logs -f caddy +``` + +## Admin Interface + +Access the admin panel at: `https://comments.dupe.sh/web/admin` + +You must be logged in with an account matching `ADMIN_SHARED_ID`. + +## Troubleshooting + +### SSL certificate issues + +Caddy automatically obtains Let's Encrypt certificates. If issues occur: + +```bash +# Check Caddy logs +docker compose logs caddy + +# Ensure port 80 and 443 are open +# Ensure DNS is properly configured +``` + +### CORS errors + +The Caddyfile includes CORS headers. If you see CORS errors, verify: +1. The `REMARK_URL` matches the actual URL +2. Your site's domain is accessible + +### Comments not loading + +1. Check browser console for errors +2. Verify `site_id` matches between Remark42 and Ultima config +3. Check Remark42 logs: `docker compose logs remark42` diff --git a/comments-server/docker-compose.yml b/comments-server/docker-compose.yml new file mode 100644 index 0000000..a9d4fae --- /dev/null +++ b/comments-server/docker-compose.yml @@ -0,0 +1,61 @@ +services: + remark42: + image: umputun/remark42:latest + container_name: remark42 + restart: unless-stopped + environment: + - REMARK_URL=${REMARK_URL} + - SECRET=${SECRET} + - SITE=${SITE_ID} + - ADMIN_SHARED_ID=${ADMIN_SHARED_ID} + # Auth providers (uncomment and configure as needed) + - AUTH_GITHUB_CID=${AUTH_GITHUB_CID:-} + - AUTH_GITHUB_CSEC=${AUTH_GITHUB_CSEC:-} + # Anonymous comments (disable in production if unwanted) + - AUTH_ANON=${AUTH_ANON:-true} + # Email auth (optional) + - AUTH_EMAIL_ENABLE=${AUTH_EMAIL_ENABLE:-false} + - SMTP_HOST=${SMTP_HOST:-} + - SMTP_PORT=${SMTP_PORT:-} + - SMTP_USERNAME=${SMTP_USERNAME:-} + - SMTP_PASSWORD=${SMTP_PASSWORD:-} + - SMTP_TLS=${SMTP_TLS:-true} + - AUTH_EMAIL_FROM=${AUTH_EMAIL_FROM:-} + # Notifications (optional) + - NOTIFY_TYPE=${NOTIFY_TYPE:-none} + - NOTIFY_TELEGRAM_TOKEN=${NOTIFY_TELEGRAM_TOKEN:-} + - NOTIFY_TELEGRAM_CHAN=${NOTIFY_TELEGRAM_CHAN:-} + # Moderation + - ADMIN_SHARED_EMAIL=${ADMIN_SHARED_EMAIL:-} + # Rate limiting + - MAX_COMMENT_SIZE=2048 + - LOW_SCORE=-5 + - CRITICAL_SCORE=-10 + volumes: + - ./data:/srv/var:Z + ports: + - "127.0.0.1:8080:8080" + logging: + driver: "json-file" + options: + max-size: "10m" + max-file: "3" + + caddy: + image: caddy:2-alpine + container_name: caddy + restart: unless-stopped + ports: + - "8081:80" + - "8443:443" + volumes: + - ./Caddyfile:/etc/caddy/Caddyfile:ro,Z + - ./static:/srv/static:ro,Z + - caddy_data:/data + - caddy_config:/config + depends_on: + - remark42 + +volumes: + caddy_data: + caddy_config: diff --git a/comments-server/static/BerkeleyMono-Regular.woff b/comments-server/static/BerkeleyMono-Regular.woff new file mode 100644 index 0000000..041d639 --- /dev/null +++ b/comments-server/static/BerkeleyMono-Regular.woff @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ad1dffe358c814a5ea8ee8ba0b157893970bc019a195cc09500e07ac80d8e2e4 +size 40708 diff --git a/comments-server/static/BerkeleyMono-Regular.woff2 b/comments-server/static/BerkeleyMono-Regular.woff2 new file mode 100644 index 0000000..cbde351 --- /dev/null +++ b/comments-server/static/BerkeleyMono-Regular.woff2 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ef207076b3bcbd0f60a37075d96b91bcec2322a47c1bf109535659de9fe7ca46 +size 37736 diff --git a/comments-server/static/last-comments-custom.css b/comments-server/static/last-comments-custom.css new file mode 100644 index 0000000..216843e --- /dev/null +++ b/comments-server/static/last-comments-custom.css @@ -0,0 +1,7 @@ +/* Import original Remark42 last-comments styles */ +@import '/web-orig/last-comments.css'; + +/* Custom overrides */ +body { + font-family: 'berkeley_mono_regular', ui-monospace, monospace; +} diff --git a/comments-server/static/remark-base.css b/comments-server/static/remark-base.css new file mode 100644 index 0000000..23a133f --- /dev/null +++ b/comments-server/static/remark-base.css @@ -0,0 +1,216 @@ +/* Import original Remark42 styles */ +@import '/web-orig/remark.css'; + +/* Load the custom font from comments server */ +@font-face { + font-family: 'berkeley_mono_regular'; + src: url('/static/BerkeleyMono-Regular.woff2') format('woff2'), + url('/static/BerkeleyMono-Regular.woff') format('woff'); + font-weight: normal; + font-style: normal; +} + +/* Base font and background for everything */ +body { + font-family: 'berkeley_mono_regular', ui-monospace, SFMono-Regular, monospace !important; + padding: 0 !important; + background-color: var(--ultima-bg) !important; + color: var(--ultima-text) !important; +} + +button, +input, +select, +textarea { + font-family: 'berkeley_mono_regular', ui-monospace, SFMono-Regular, monospace !important; +} + +/* Root container */ +.root { + color: var(--ultima-text) !important; +} + +/* Links */ +a { + color: var(--ultima-primary) !important; +} + +a:hover { + color: var(--ultima-primary-dark) !important; +} + +/* Comment form */ +.comment-form { + background-color: var(--ultima-bg) !important; + border-color: var(--ultima-border) !important; +} + +.comment-form_theme_light { + background-color: var(--ultima-bg) !important; + border-color: var(--ultima-border) !important; +} + +/* Textarea */ +.comment-form__field { + background-color: var(--ultima-bg) !important; + color: var(--ultima-text) !important; + border: 1px solid var(--ultima-border) !important; + border-radius: 0 !important; +} + +/* Control panel (toolbar area) */ +.comment-form__control-panel { + background-color: var(--ultima-bg-highlight) !important; +} + +/* Actions area (buttons) */ +.comment-form__actions { + background-color: var(--ultima-bg-highlight) !important; +} + +/* All buttons - remove border radius */ +.button, +.C_A { + border-radius: 0 !important; +} + +/* Primary button (Send) */ +.button_kind_primary { + background-color: var(--ultima-primary) !important; + border-radius: 0 !important; +} + +/* Secondary button (Preview) */ +.button_kind_secondary { + background-color: var(--ultima-bg) !important; + color: var(--ultima-text) !important; + border-radius: 0 !important; +} + +/* Reply button */ +.C_A.C_G { + color: var(--ultima-primary) !important; +} + +/* Comment text */ +.comment__text, +.raw-content { + font-family: 'berkeley_mono_regular', ui-monospace, SFMono-Regular, monospace !important; +} + +/* Username */ +.comment__username { + font-family: 'berkeley_mono_regular', ui-monospace, SFMono-Regular, monospace !important; + color: var(--ultima-text-muted) !important; +} + +/* Timestamp */ +.comment__time { + font-family: 'berkeley_mono_regular', ui-monospace, SFMono-Regular, monospace !important; + color: var(--ultima-text-muted) !important; +} + +/* Auth panel */ +.auth-panel { + font-family: 'berkeley_mono_regular', ui-monospace, SFMono-Regular, monospace !important; +} + +/* Sort picker */ +.sort-picker, +.select { + font-family: 'berkeley_mono_regular', ui-monospace, SFMono-Regular, monospace !important; +} + +/* Markdown help text */ +.comment-form__markdown, +.comment-form__rss { + font-family: 'berkeley_mono_regular', ui-monospace, SFMono-Regular, monospace !important; + color: var(--ultima-text-muted) !important; +} + +.comment-form__markdown-link { + color: var(--ultima-primary) !important; +} + +.comment-form__markdown-link:hover { + color: var(--ultima-primary-dark) !important; +} + +/* Copyright */ +.root__copyright { + font-family: 'berkeley_mono_regular', ui-monospace, SFMono-Regular, monospace !important; + color: var(--ultima-text-muted) !important; +} + +.root__copyright-link { + color: var(--ultima-text-muted) !important; +} + +/* =========================================== + Sign In Flow + =========================================== */ + +/* Auth container */ +.auth { + font-family: 'berkeley_mono_regular', ui-monospace, SFMono-Regular, monospace !important; +} + +/* Sign In button */ +.auth-button { + font-family: 'berkeley_mono_regular', ui-monospace, SFMono-Regular, monospace !important; + background-color: var(--ultima-bg-highlight) !important; + color: var(--ultima-text) !important; + border: 1px solid var(--ultima-border) !important; + border-radius: 0 !important; +} + +/* Dropdown panel */ +.auth-dropdown { + background-color: var(--ultima-bg) !important; + border: 1px solid var(--ultima-border) !important; + border-radius: 0 !important; +} + +/* Auth form */ +.auth-form { + font-family: 'berkeley_mono_regular', ui-monospace, SFMono-Regular, monospace !important; +} + +/* Form titles */ +.auth-form-title { + font-family: 'berkeley_mono_regular', ui-monospace, SFMono-Regular, monospace !important; + color: var(--ultima-text) !important; +} + +/* OAuth buttons container */ +.oauth { + font-family: 'berkeley_mono_regular', ui-monospace, SFMono-Regular, monospace !important; +} + +/* OAuth button (GitHub, etc) */ +.oauth-button { + border-radius: 0 !important; + background-color: var(--ultima-bg-highlight) !important; +} + +/* Divider ("or") */ +.auth-divider { + color: var(--ultima-text-muted) !important; +} + +/* Username input */ +.auth-input-username { + font-family: 'berkeley_mono_regular', ui-monospace, SFMono-Regular, monospace !important; + background-color: var(--ultima-bg) !important; + color: var(--ultima-text) !important; + border: 1px solid var(--ultima-border) !important; + border-radius: 0 !important; +} + +/* Submit button */ +.auth-submit { + font-family: 'berkeley_mono_regular', ui-monospace, SFMono-Regular, monospace !important; + background-color: var(--ultima-primary) !important; + color: white !important; + border-radius: 0 !important; +} diff --git a/comments-server/static/remark-custom.css b/comments-server/static/remark-custom.css new file mode 100644 index 0000000..7fe0396 --- /dev/null +++ b/comments-server/static/remark-custom.css @@ -0,0 +1,163 @@ +/* Import original Remark42 styles */ +@import '/web-orig/remark.css'; + +/* Load the custom font from comments server */ +@font-face { + font-family: 'berkeley_mono_regular'; + src: url('/static/BerkeleyMono-Regular.woff2') format('woff2'), + url('/static/BerkeleyMono-Regular.woff') format('woff'); + font-weight: normal; + font-style: normal; +} + +/* Custom overrides to match Ultima site themes */ + +/* Color variables matching dupe_sh theme.css */ +:root { + --ultima-bg: #f0f4fc; + --ultima-text: #333333; + --ultima-text-muted: #6b7280; + --ultima-primary: #3498db; + --ultima-primary-dark: #2471a3; + --ultima-border: #dae1f0; + --ultima-bg-highlight: #dae1f0; +} + +/* Base font and background for everything */ +body { + font-family: 'berkeley_mono_regular', ui-monospace, SFMono-Regular, monospace !important; + padding: 0 !important; + background-color: var(--ultima-bg) !important; + color: var(--ultima-text) !important; +} + +button, +input, +select, +textarea { + font-family: 'berkeley_mono_regular', ui-monospace, SFMono-Regular, monospace !important; +} + +/* Root container */ +.root { + color: var(--ultima-text) !important; +} + +/* Links */ +a { + color: var(--ultima-primary) !important; +} + +a:hover { + color: var(--ultima-primary-dark) !important; +} + +/* Comment form */ +.comment-form { + background-color: var(--ultima-bg) !important; + border-color: var(--ultima-border) !important; +} + +.comment-form_theme_light { + background-color: var(--ultima-bg) !important; + border-color: var(--ultima-border) !important; +} + +/* Textarea */ +.comment-form__field { + background-color: var(--ultima-bg) !important; + color: var(--ultima-text) !important; + border: 1px solid var(--ultima-border) !important; + border-radius: 0 !important; +} + +/* Control panel (toolbar area) */ +.comment-form__control-panel { + background-color: var(--ultima-bg-highlight) !important; +} + +/* Actions area (buttons) */ +.comment-form__actions { + background-color: var(--ultima-bg-highlight) !important; +} + +/* All buttons - remove border radius */ +.button, +.C_A { + border-radius: 0 !important; +} + +/* Primary button (Send) */ +.button_kind_primary { + background-color: var(--ultima-primary) !important; + border-radius: 0 !important; +} + +.button_kind_primary:hover { + background-color: var(--ultima-primary-dark) !important; +} + +/* Secondary button (Preview) */ +.button_kind_secondary { + background-color: var(--ultima-bg) !important; + color: var(--ultima-text) !important; + border-radius: 0 !important; +} + +.button_kind_secondary:hover { + box-shadow: inset 0 0 0 2px var(--ultima-primary) !important; +} + +/* Reply button */ +.C_A.C_G { + color: var(--ultima-primary) !important; +} + +.C_A.C_G:hover { + background-color: var(--ultima-bg-highlight) !important; +} + +/* Comment text */ +.comment__text, +.raw-content { + font-family: 'berkeley_mono_regular', ui-monospace, SFMono-Regular, monospace !important; +} + +/* Username */ +.comment__username { + font-family: 'berkeley_mono_regular', ui-monospace, SFMono-Regular, monospace !important; + color: var(--ultima-text-muted) !important; +} + +/* Timestamp */ +.comment__time { + font-family: 'berkeley_mono_regular', ui-monospace, SFMono-Regular, monospace !important; + color: var(--ultima-text-muted) !important; +} + +/* Auth panel */ +.auth-panel { + font-family: 'berkeley_mono_regular', ui-monospace, SFMono-Regular, monospace !important; +} + +/* Sort picker */ +.sort-picker, +.select { + font-family: 'berkeley_mono_regular', ui-monospace, SFMono-Regular, monospace !important; +} + +/* Markdown help text */ +.comment-form__markdown, +.comment-form__rss { + font-family: 'berkeley_mono_regular', ui-monospace, SFMono-Regular, monospace !important; +} + +/* Copyright */ +.root__copyright { + font-family: 'berkeley_mono_regular', ui-monospace, SFMono-Regular, monospace !important; + color: var(--ultima-text-muted) !important; +} + +.root__copyright-link { + color: var(--ultima-text-muted) !important; +} diff --git a/comments-server/static/theme-default.css b/comments-server/static/theme-default.css new file mode 100644 index 0000000..d64e7a9 --- /dev/null +++ b/comments-server/static/theme-default.css @@ -0,0 +1,20 @@ +/* Default theme - neutral grey */ +@import '/static/remark-base.css'; + +:root { + /* Ultima theme variables */ + --ultima-bg: #f5f5f5; + --ultima-text: #333333; + --ultima-text-muted: #6b7280; + --ultima-primary: #555555; + --ultima-primary-dark: #333333; + --ultima-border: #e0e0e0; + --ultima-bg-highlight: #e8e8e8; + + /* Remark42 CSS variables for hover/focus states */ + --primary-color: 85, 85, 85; /* #555555 in RGB for rgba() */ + --color9: #333333; /* toolbar hover */ + --color15: #555555; /* textarea focus border */ + --color33: #555555; /* secondary button hover, link hover */ + --color47: rgba(85, 85, 85, 0.2); /* textarea focus box-shadow */ +} diff --git a/comments-server/static/theme-dupe_sh.css b/comments-server/static/theme-dupe_sh.css new file mode 100644 index 0000000..56984e4 --- /dev/null +++ b/comments-server/static/theme-dupe_sh.css @@ -0,0 +1,20 @@ +/* dupe_sh theme - Blue */ +@import '/static/remark-base.css'; + +:root { + /* Ultima theme variables */ + --ultima-bg: #f0f4fc; + --ultima-text: #333333; + --ultima-text-muted: #6b7280; + --ultima-primary: #3498db; + --ultima-primary-dark: #2471a3; + --ultima-border: #dae1f0; + --ultima-bg-highlight: #dae1f0; + + /* Remark42 CSS variables for hover/focus states */ + --primary-color: 52, 152, 219; /* #3498db in RGB for rgba() */ + --color9: #2471a3; /* toolbar hover */ + --color15: #3498db; /* textarea focus border */ + --color33: #3498db; /* secondary button hover, link hover */ + --color47: rgba(52, 152, 219, 0.2); /* textarea focus box-shadow */ +} diff --git a/comments-server/static/theme-rorschach.css b/comments-server/static/theme-rorschach.css new file mode 100644 index 0000000..b1456dc --- /dev/null +++ b/comments-server/static/theme-rorschach.css @@ -0,0 +1,20 @@ +/* rorschach theme - Dusty Rose/Maroon */ +@import '/static/remark-base.css'; + +:root { + /* Ultima theme variables */ + --ultima-bg: #f1e5e5; + --ultima-text: #2a2a2e; + --ultima-text-muted: #6b7280; + --ultima-primary: #8c4848; + --ultima-primary-dark: #6d3636; + --ultima-border: #e0d4d4; + --ultima-bg-highlight: #e0d4d4; + + /* Remark42 CSS variables for hover/focus states */ + --primary-color: 140, 72, 72; /* #8c4848 in RGB for rgba() */ + --color9: #6d3636; /* toolbar hover */ + --color15: #8c4848; /* textarea focus border */ + --color33: #8c4848; /* secondary button hover, link hover */ + --color47: rgba(140, 72, 72, 0.2); /* textarea focus box-shadow */ +} diff --git a/comments-server/static/theme-whwg.css b/comments-server/static/theme-whwg.css new file mode 100644 index 0000000..ac37b7d --- /dev/null +++ b/comments-server/static/theme-whwg.css @@ -0,0 +1,20 @@ +/* whwg theme - Lavender/Purple */ +@import '/static/remark-base.css'; + +:root { + /* Ultima theme variables */ + --ultima-bg: #f3eefc; + --ultima-text: #333333; + --ultima-text-muted: #6b7280; + --ultima-primary: #8c62c7; + --ultima-primary-dark: #4a437d; + --ultima-border: #eae4f8; + --ultima-bg-highlight: #eae4f8; + + /* Remark42 CSS variables for hover/focus states */ + --primary-color: 140, 98, 199; /* #8c62c7 in RGB for rgba() */ + --color9: #4a437d; /* toolbar hover */ + --color15: #8c62c7; /* textarea focus border */ + --color33: #8c62c7; /* secondary button hover, link hover */ + --color47: rgba(140, 98, 199, 0.2); /* textarea focus box-shadow */ +} diff --git a/sites/dupe_sh/config.toml b/sites/dupe_sh/config.toml index 5ce3582..821d60a 100644 --- a/sites/dupe_sh/config.toml +++ b/sites/dupe_sh/config.toml @@ -16,3 +16,10 @@ default = "page.htmlua" index_page = "index.htmlua" post = "post.htmlua" feed = "feed.xmlua" + +[comments] +enabled = true +remark_url = "http://localhost:8081" +site_id = "dupe_sh" +theme = "light" +locale = "en" diff --git a/sites/dupe_sh/dupe_sh.lock b/sites/dupe_sh/dupe_sh.lock index 8f67061..772ebd7 100644 --- a/sites/dupe_sh/dupe_sh.lock +++ b/sites/dupe_sh/dupe_sh.lock @@ -1,296 +1,296 @@ { "files":{ - "sites/dupe_sh/content/bookshelf/curriculums/interview prep/querying and data models/transactions.md":{ - "last_modified_ts":"2024-11-03T21:50:06Z", - "checksum":"a67474a959bf7b07d57d58c4716f3cc5", + "sites/dupe_sh/content/bookshelf/on programming/rules-of-programming.md":{ + "file_size":"887.00 B", + "checksum":"9fc067e00c9f74e729e0301156f1e71c", "metadata_checksum":"99914b932bd37a50b983c5e7c90ae93b", - "file_size":"4.67 KiB" + "last_modified_ts":"2025-03-07T21:58:44Z" }, - "sites/dupe_sh/static/images/clothing-studies.jpeg":{ - "last_modified_ts":"2024-10-12T20:26:59Z", - "checksum":"aa64f142161d23e706f9244e59b7c661", - "file_size":"819.97 KiB" + "sites/dupe_sh/content/gallery/clothing studies.md":{ + "last_modified_ts":"2024-10-12T20:23:04Z", + "file_size":"653.00 B", + "checksum":"68b329da9893e34099c7d8ad5cb9c940" }, - "sites/dupe_sh/content/bookshelf/curriculums/deep systems programming.md":{ - "last_modified_ts":"2024-10-05T18:37:40Z", - "checksum":"a602efece60585f0e87a3be352fc18d2", + "sites/dupe_sh/content/bookshelf/curriculums/interview prep/asynchronous communication/event sourcing.md":{ + "file_size":"1.15 KiB", + "checksum":"68b329da9893e34099c7d8ad5cb9c940", + "metadata_checksum":"944a05b23d2595a498236bf855b22841", + "last_modified_ts":"2026-01-02T00:13:04Z" + }, + "sites/dupe_sh/content/links.md":{ + "file_size":"1.72 KiB", + "checksum":"07e32fa4c19522c36c644bffe460aba8", "metadata_checksum":"99914b932bd37a50b983c5e7c90ae93b", - "file_size":"818.00 B" + "last_modified_ts":"2025-12-31T21:01:20Z" }, - "sites/dupe_sh/content/bookshelf/on programming/providing evidence.md":{ - "last_modified_ts":"2024-10-15T14:34:26Z", - "checksum":"5d3e620bba38281b872b2dc7aad81781", - "metadata_checksum":"25c79beb7593ccacdb6a8afdd1849157", - "file_size":"754.00 B" + "sites/dupe_sh/content/bookshelf/curriculums/interview prep/storing data/embedded databases.md":{ + "file_size":"1.49 KiB", + "checksum":"0ff400082f145fe9ea840e5694e0f06d", + "metadata_checksum":"7498978242ee48be1c319a3bd00cf364", + "last_modified_ts":"2026-01-02T00:13:04Z" + }, + "sites/dupe_sh/content/bookshelf/curriculums/interview prep/programming paradigms/concurrency, parallelism, multithreading.md":{ + "file_size":"3.52 KiB", + "checksum":"7a03db5c1b5813615b5fbd881831b596", + "metadata_checksum":"bee4d5e6d4b89da8fbf5a46d1a1d5bfc", + "last_modified_ts":"2026-01-02T00:13:04Z" + }, + "sites/dupe_sh/static/images/sea-turtle.jpeg":{ + "last_modified_ts":"2024-10-13T20:11:40Z", + "file_size":"5.22 MiB", + "checksum":"5708d1af46d585f62c6c130088414916" + }, + "sites/dupe_sh/content/bookshelf/curriculums/interview prep/about.md":{ + "file_size":"1.54 KiB", + "checksum":"6d0178df801206ecc429d289504f0bb8", + "metadata_checksum":"80c7c22e899eb25e10c6963f4f33379e", + "last_modified_ts":"2026-01-02T00:13:04Z" }, "sites/dupe_sh/content/bookshelf/curriculums/interview prep/python programming cheatsheet.md":{ - "last_modified_ts":"2024-12-13T23:28:25Z", + "file_size":"25.43 KiB", "checksum":"d30b12a93e43e789cad9d1ebbc0930c0", "metadata_checksum":"99914b932bd37a50b983c5e7c90ae93b", - "file_size":"25.43 KiB" + "last_modified_ts":"2024-12-13T23:28:25Z" + }, + "sites/dupe_sh/content/workshop/project ultima.md":{ + "file_size":"651.00 B", + "checksum":"68b329da9893e34099c7d8ad5cb9c940", + "metadata_checksum":"25c79beb7593ccacdb6a8afdd1849157", + "last_modified_ts":"2024-10-17T01:49:37Z" + }, + "sites/dupe_sh/content/gallery/index.toml":{ + "file_size":"1.03 KiB", + "checksum":"1f3dcf0b6d1af3b3e41396cc760202d2", + "metadata_checksum":"99914b932bd37a50b983c5e7c90ae93b", + "last_modified_ts":"2026-01-02T05:50:59Z" }, "sites/dupe_sh/content/bookshelf/curriculums/interview prep/distributed systems/concepts & techniques.md":{ - "last_modified_ts":"2024-11-27T20:43:04Z", + "file_size":"1.61 KiB", "checksum":"69fdbe43b4253c580a57158ced96783c", "metadata_checksum":"99914b932bd37a50b983c5e7c90ae93b", - "file_size":"1.61 KiB" - }, - "sites/dupe_sh/content/manuscripts/in-n-out, my brief stint at princeton.md":{ - "last_modified_ts":"2026-01-02T00:57:56Z", - "checksum":"2d5685285dce74f30e5b23c5263a6d50", - "metadata_checksum":"11d78d29b522756507d9b95bc09f485b", - "file_size":"7.16 KiB" + "last_modified_ts":"2024-11-27T20:43:04Z" }, - "sites/dupe_sh/content/bookshelf/curriculums/interview prep/coding and algorithms concepts.md":{ - "last_modified_ts":"2024-12-12T18:07:02Z", - "checksum":"5d63d59f71a996d6309a86c77d1d8da4", - "metadata_checksum":"99914b932bd37a50b983c5e7c90ae93b", - "file_size":"55.20 KiB" + "sites/dupe_sh/content/bookshelf/curriculums/interview prep/case studies/bluesky.md":{ + "file_size":"5.08 KiB", + "checksum":"afcd7a02670134d14ab1116410026021", + "metadata_checksum":"2724605debf3aa660ed858e5f676e55c", + "last_modified_ts":"2026-01-02T00:13:04Z" }, - "sites/dupe_sh/content/manuscripts/on art.md":{ - "last_modified_ts":"2024-10-17T01:49:39Z", - "checksum":"64219dd239fb3f367d3a3cb6a1b40fdd", + "sites/dupe_sh/content/bookshelf/on programming/providing evidence.md":{ + "file_size":"754.00 B", + "checksum":"5d3e620bba38281b872b2dc7aad81781", "metadata_checksum":"25c79beb7593ccacdb6a8afdd1849157", - "file_size":"846.00 B" - }, - "sites/dupe_sh/content/now.md":{ - "last_modified_ts":"2026-01-02T00:23:58Z", - "checksum":"49c75e17e40e3550c6df239400229a22", - "metadata_checksum":"99914b932bd37a50b983c5e7c90ae93b", - "file_size":"3.52 KiB" - }, - "sites/dupe_sh/static/images/blue_sky.png":{ - "last_modified_ts":"2024-11-20T21:15:12Z", - "checksum":"9cf52c4279fa4c7d38d5f8a50febd019", - "file_size":"275.68 KiB" + "last_modified_ts":"2024-10-15T14:34:26Z" }, - "sites/dupe_sh/content/bookshelf/curriculums/interview prep/about.md":{ - "last_modified_ts":"2026-01-02T00:13:04Z", - "checksum":"6d0178df801206ecc429d289504f0bb8", - "metadata_checksum":"80c7c22e899eb25e10c6963f4f33379e", - "file_size":"1.54 KiB" + "sites/dupe_sh/content/bookshelf/curriculums/interview prep/APIs & web technologies/reverse proxies.md":{ + "file_size":"1.36 KiB", + "checksum":"5904f6f7714e85a14c28f1fda040eb39", + "metadata_checksum":"2724605debf3aa660ed858e5f676e55c", + "last_modified_ts":"2026-01-02T00:13:04Z" }, "sites/dupe_sh/content/bookshelf/curriculums/interview prep/good old coding/stacks on stacks.md":{ - "last_modified_ts":"2026-01-02T00:13:04Z", + "file_size":"6.02 KiB", "checksum":"4090fba8035b9684c05e2defb9ca9a88", "metadata_checksum":"20c17d2187eab0c1ddaba72941b0d6bb", - "file_size":"6.02 KiB" + "last_modified_ts":"2026-01-02T00:13:04Z" + }, + "sites/dupe_sh/content/bookshelf/curriculums/interview prep/domain specific/authorization & authentication.md":{ + "file_size":"3.49 KiB", + "checksum":"c3395697155d1132142de2f5d4ca8629", + "metadata_checksum":"15cb1aa6213a41c4c954d7f0671570da", + "last_modified_ts":"2026-01-02T00:13:04Z" + }, + "sites/dupe_sh/content/bookshelf/curriculums/interview prep/APIs & web technologies/communication protocols.md":{ + "file_size":"2.22 KiB", + "checksum":"a49cf59b643ac3df6980f2bb500fa65d", + "metadata_checksum":"8182a6ff42c407dfd1c84d20de92b9d5", + "last_modified_ts":"2026-01-02T00:13:04Z" + }, + "sites/dupe_sh/content/bookshelf/curriculums/interview prep/caching/in-memory KV stores.md":{ + "file_size":"685.00 B", + "checksum":"ebeab9b718f38eed92d622e6ea4e53e7", + "metadata_checksum":"99914b932bd37a50b983c5e7c90ae93b", + "last_modified_ts":"2024-11-03T19:56:33Z" + }, + "sites/dupe_sh/content/about.md":{ + "file_size":"2.51 KiB", + "checksum":"f00374f8ecd52052a22bd134031a233f", + "metadata_checksum":"99914b932bd37a50b983c5e7c90ae93b", + "last_modified_ts":"2026-01-02T00:38:13Z" + }, + "sites/dupe_sh/content/draft.md":{ + "last_modified_ts":"2024-10-13T19:04:05Z", + "file_size":"642.00 B", + "checksum":"68b329da9893e34099c7d8ad5cb9c940" }, "sites/dupe_sh/content/bookshelf/curriculums/interview prep/interview categories.md":{ - "last_modified_ts":"2026-01-02T00:13:04Z", + "file_size":"1.99 KiB", "checksum":"a91ee23d2e5f9713de80a72df3dd4a70", "metadata_checksum":"2724605debf3aa660ed858e5f676e55c", - "file_size":"1.99 KiB" + "last_modified_ts":"2026-01-02T00:13:04Z" + }, + "sites/dupe_sh/content/manuscripts/2025 - an artistic and personal retrospective.md":{ + "file_size":"2.91 KiB", + "checksum":"1ae982a2b5e23e70b4afebc98a4a6e3c", + "metadata_checksum":"b48282664096d84a283eeb60e08b0488", + "last_modified_ts":"2026-01-02T00:13:04Z" }, "sites/dupe_sh/content/bookshelf/curriculums/interview prep/storing data/replication.md":{ - "last_modified_ts":"2024-11-03T20:37:19Z", + "file_size":"735.00 B", "checksum":"c56b73ee94c49f96b83362430ea56a16", "metadata_checksum":"99914b932bd37a50b983c5e7c90ae93b", - "file_size":"735.00 B" + "last_modified_ts":"2024-11-03T20:37:19Z" }, - "sites/dupe_sh/content/bookshelf/snippets.md":{ - "last_modified_ts":"2024-11-27T20:43:07Z", - "checksum":"7ada7dbd14bc2440d457fb3d4be5fbb4", + "sites/dupe_sh/content/bookshelf/on programming/ocaml-notes.md":{ + "file_size":"1.08 KiB", + "checksum":"f379a8ed8b3fb45719694618ffba9349", "metadata_checksum":"99914b932bd37a50b983c5e7c90ae93b", - "file_size":"5.63 KiB" - }, - "sites/dupe_sh/content/manuscripts/not another.md":{ - "last_modified_ts":"2024-10-15T14:34:25Z", - "checksum":"68b329da9893e34099c7d8ad5cb9c940", - "metadata_checksum":"25c79beb7593ccacdb6a8afdd1849157", - "file_size":"648.00 B" - }, - "sites/dupe_sh/content/gallery/clothing studies.md":{ - "last_modified_ts":"2024-10-12T20:23:04Z", - "checksum":"68b329da9893e34099c7d8ad5cb9c940", - "file_size":"653.00 B" + "last_modified_ts":"2025-03-07T21:58:43Z" }, "sites/dupe_sh/content/wikilinks-demo.md":{ - "last_modified_ts":"2026-01-02T00:53:05Z", + "file_size":"3.36 KiB", "checksum":"eb25350b108aca9e37c1924e23f251cf", "metadata_checksum":"caa62fb70e900d2203b1eebf326027dc", - "file_size":"3.36 KiB" - }, - "sites/dupe_sh/content/bookshelf/curriculums/interview prep/programming paradigms/concurrency, parallelism, multithreading.md":{ - "last_modified_ts":"2026-01-02T00:13:04Z", - "checksum":"7a03db5c1b5813615b5fbd881831b596", - "metadata_checksum":"bee4d5e6d4b89da8fbf5a46d1a1d5bfc", - "file_size":"3.52 KiB" + "last_modified_ts":"2026-01-02T00:53:05Z" }, - "sites/dupe_sh/content/bookshelf/on programming.md":{ - "last_modified_ts":"2024-10-05T17:56:47Z", - "checksum":"8584372367be600244777464368285d8", - "file_size":"1.14 KiB" + "sites/dupe_sh/content/consciousness.md":{ + "file_size":"946.00 B", + "checksum":"f1daa5f4708a28b872ba8894b9ebe7a0", + "metadata_checksum":"25c79beb7593ccacdb6a8afdd1849157", + "last_modified_ts":"2024-10-25T19:43:51Z" }, - "sites/dupe_sh/content/bookshelf/curriculums/interview prep/APIs & web technologies/reverse proxies.md":{ - "last_modified_ts":"2026-01-02T00:13:04Z", - "checksum":"5904f6f7714e85a14c28f1fda040eb39", - "metadata_checksum":"2724605debf3aa660ed858e5f676e55c", - "file_size":"1.36 KiB" + "sites/dupe_sh/content/manuscripts/on art.md":{ + "file_size":"846.00 B", + "checksum":"64219dd239fb3f367d3a3cb6a1b40fdd", + "metadata_checksum":"25c79beb7593ccacdb6a8afdd1849157", + "last_modified_ts":"2024-10-17T01:49:39Z" }, - "sites/dupe_sh/content/gallery/index.toml":{ - "last_modified_ts":"2026-01-02T05:50:59Z", - "checksum":"1f3dcf0b6d1af3b3e41396cc760202d2", + "sites/dupe_sh/content/bookshelf/curriculums/interview prep/querying and data models/transactions.md":{ + "file_size":"4.67 KiB", + "checksum":"a67474a959bf7b07d57d58c4716f3cc5", "metadata_checksum":"99914b932bd37a50b983c5e7c90ae93b", - "file_size":"1.03 KiB" + "last_modified_ts":"2024-11-03T21:50:06Z" }, - "sites/dupe_sh/content/workshop/project ultima.md":{ - "last_modified_ts":"2024-10-17T01:49:37Z", - "checksum":"68b329da9893e34099c7d8ad5cb9c940", - "metadata_checksum":"25c79beb7593ccacdb6a8afdd1849157", - "file_size":"651.00 B" + "sites/dupe_sh/static/images/blue_sky.png":{ + "last_modified_ts":"2024-11-20T21:15:12Z", + "file_size":"275.68 KiB", + "checksum":"9cf52c4279fa4c7d38d5f8a50febd019" }, - "sites/dupe_sh/content/about.md":{ - "last_modified_ts":"2026-01-02T00:38:13Z", - "checksum":"f00374f8ecd52052a22bd134031a233f", - "metadata_checksum":"99914b932bd37a50b983c5e7c90ae93b", - "file_size":"2.51 KiB" + "sites/dupe_sh/content/bookshelf/curriculums/interview prep/good old coding/recursive backtracking.md":{ + "file_size":"4.14 KiB", + "checksum":"98f409fb2a27c8740f825a2e81c1396e", + "metadata_checksum":"38130b6a2e7961bed79fe22ad7ce4ae7", + "last_modified_ts":"2026-01-02T00:13:04Z" }, - "sites/dupe_sh/content/manuscripts/2025 - an artistic and personal retrospective.md":{ - "last_modified_ts":"2026-01-02T00:13:04Z", - "checksum":"1ae982a2b5e23e70b4afebc98a4a6e3c", - "metadata_checksum":"b48282664096d84a283eeb60e08b0488", - "file_size":"2.91 KiB" + "sites/dupe_sh/content/bookshelf/curriculums/interview prep/asynchronous communication/sagas.md":{ + "file_size":"1.14 KiB", + "checksum":"68b329da9893e34099c7d8ad5cb9c940", + "metadata_checksum":"944a05b23d2595a498236bf855b22841", + "last_modified_ts":"2026-01-02T00:13:04Z" }, - "sites/dupe_sh/content/bookshelf/on programming/ocaml-notes.md":{ - "last_modified_ts":"2025-03-07T21:58:43Z", - "checksum":"f379a8ed8b3fb45719694618ffba9349", + "sites/dupe_sh/content/now.md":{ + "file_size":"3.52 KiB", + "checksum":"49c75e17e40e3550c6df239400229a22", "metadata_checksum":"99914b932bd37a50b983c5e7c90ae93b", - "file_size":"1.08 KiB" + "last_modified_ts":"2026-01-02T00:23:58Z" }, - "sites/dupe_sh/content/bookshelf/on programming/rust cheatsheet.md":{ - "last_modified_ts":"2026-01-02T00:13:04Z", - "checksum":"9f8599b1099a1f4068bfc4175b4e0d37", - "metadata_checksum":"80c7c22e899eb25e10c6963f4f33379e", - "file_size":"5.81 KiB" + "sites/dupe_sh/content/test.md":{ + "file_size":"870.00 B", + "checksum":"84eb0b8ff0a859ac8d3e3b1f40cf0155", + "metadata_checksum":"25c79beb7593ccacdb6a8afdd1849157", + "last_modified_ts":"2024-11-27T21:15:07Z" + }, + "sites/dupe_sh/content/manuscripts/not another.md":{ + "file_size":"648.00 B", + "checksum":"68b329da9893e34099c7d8ad5cb9c940", + "metadata_checksum":"25c79beb7593ccacdb6a8afdd1849157", + "last_modified_ts":"2024-10-15T14:34:25Z" }, "sites/dupe_sh/content/bookshelf/curriculums/interview prep/asynchronous communication/transactional outbox.md":{ - "last_modified_ts":"2026-01-02T00:13:04Z", + "file_size":"1.15 KiB", "checksum":"68b329da9893e34099c7d8ad5cb9c940", "metadata_checksum":"944a05b23d2595a498236bf855b22841", - "file_size":"1.15 KiB" + "last_modified_ts":"2026-01-02T00:13:04Z" }, - "sites/dupe_sh/content/bookshelf/curriculums/interview prep/caching/in-memory KV stores.md":{ - "last_modified_ts":"2024-11-03T19:56:33Z", - "checksum":"ebeab9b718f38eed92d622e6ea4e53e7", + "sites/dupe_sh/content/bookshelf/on programming/tuples.md":{ + "file_size":"1.18 KiB", + "checksum":"f6df6be54414add401be7303d2cb9801", "metadata_checksum":"99914b932bd37a50b983c5e7c90ae93b", - "file_size":"685.00 B" + "last_modified_ts":"2024-10-16T03:18:02Z" }, - "sites/dupe_sh/content/bookshelf/curriculums/interview prep/case studies/bluesky.md":{ - "last_modified_ts":"2026-01-02T00:13:04Z", - "checksum":"afcd7a02670134d14ab1116410026021", - "metadata_checksum":"2724605debf3aa660ed858e5f676e55c", - "file_size":"5.08 KiB" - }, - "sites/dupe_sh/content/home.md":{ - "last_modified_ts":"2024-10-05T17:56:46Z", - "checksum":"64624689c2551cbbbff0ceae9b4b1c1b", - "file_size":"2.04 KiB" + "sites/dupe_sh/content/bookshelf/snippets.md":{ + "file_size":"5.63 KiB", + "checksum":"7ada7dbd14bc2440d457fb3d4be5fbb4", + "metadata_checksum":"99914b932bd37a50b983c5e7c90ae93b", + "last_modified_ts":"2024-11-27T20:43:07Z" }, "sites/dupe_sh/content/manuscripts/off on a coding adventure.md":{ - "last_modified_ts":"2026-01-02T00:56:22Z", + "file_size":"1.71 KiB", "checksum":"db1dc115b3dec4a2b5af31ae96a6c2fe", "metadata_checksum":"3c041f5ef3d1bf5bd841ac85fe39772a", - "file_size":"1.71 KiB" - }, - "sites/dupe_sh/content/bookshelf/curriculums/interview prep/domain specific/authorization & authentication.md":{ - "last_modified_ts":"2026-01-02T00:13:04Z", - "checksum":"c3395697155d1132142de2f5d4ca8629", - "metadata_checksum":"15cb1aa6213a41c4c954d7f0671570da", - "file_size":"3.49 KiB" + "last_modified_ts":"2026-01-02T00:56:22Z" }, - "sites/dupe_sh/content/bookshelf/on programming/tuples.md":{ - "last_modified_ts":"2024-10-16T03:18:02Z", - "checksum":"f6df6be54414add401be7303d2cb9801", - "metadata_checksum":"99914b932bd37a50b983c5e7c90ae93b", - "file_size":"1.18 KiB" - }, - "sites/dupe_sh/content/links.md":{ - "last_modified_ts":"2025-12-31T21:01:20Z", - "checksum":"07e32fa4c19522c36c644bffe460aba8", - "metadata_checksum":"99914b932bd37a50b983c5e7c90ae93b", - "file_size":"1.72 KiB" + "sites/dupe_sh/content/home.md":{ + "last_modified_ts":"2024-10-05T17:56:46Z", + "file_size":"2.04 KiB", + "checksum":"64624689c2551cbbbff0ceae9b4b1c1b" }, - "sites/dupe_sh/static/images/sea-turtle.jpeg":{ - "last_modified_ts":"2024-10-13T20:11:40Z", - "checksum":"5708d1af46d585f62c6c130088414916", - "file_size":"5.22 MiB" + "sites/dupe_sh/content/bookshelf/curriculums/interview prep/distributed systems/main concepts.md":{ + "last_modified_ts":"2024-11-03T21:29:21Z", + "file_size":"918.00 B", + "checksum":"42c05d0def64ac706765175a64a56a3d" }, - "sites/dupe_sh/content/bookshelf/curriculums/interview prep/asynchronous communication/sagas.md":{ - "last_modified_ts":"2026-01-02T00:13:04Z", - "checksum":"68b329da9893e34099c7d8ad5cb9c940", - "metadata_checksum":"944a05b23d2595a498236bf855b22841", - "file_size":"1.14 KiB" + "sites/dupe_sh/static/images/clothing-studies.jpeg":{ + "last_modified_ts":"2024-10-12T20:26:59Z", + "file_size":"819.97 KiB", + "checksum":"aa64f142161d23e706f9244e59b7c661" }, - "sites/dupe_sh/content/bookshelf/curriculums/interview prep/good old coding/recursive backtracking.md":{ - "last_modified_ts":"2026-01-02T00:13:04Z", - "checksum":"98f409fb2a27c8740f825a2e81c1396e", - "metadata_checksum":"38130b6a2e7961bed79fe22ad7ce4ae7", - "file_size":"4.14 KiB" + "sites/dupe_sh/content/bookshelf/curriculums/interview prep/domain specific/financial applications.md":{ + "file_size":"1.31 KiB", + "checksum":"1392ba39bd2e0fa672aedaef3dbd2d35", + "metadata_checksum":"2724605debf3aa660ed858e5f676e55c", + "last_modified_ts":"2026-01-02T00:13:04Z" }, - "sites/dupe_sh/content/bookshelf/curriculums/interview prep/storing data/db data structures.md":{ - "last_modified_ts":"2024-11-03T20:37:19Z", - "checksum":"3a7a4e6ca6076bd0fda11b1789513779", + "sites/dupe_sh/content/bookshelf/curriculums/deep systems programming.md":{ + "file_size":"818.00 B", + "checksum":"a602efece60585f0e87a3be352fc18d2", "metadata_checksum":"99914b932bd37a50b983c5e7c90ae93b", - "file_size":"911.00 B" + "last_modified_ts":"2024-10-05T18:37:40Z" }, - "sites/dupe_sh/content/consciousness.md":{ - "last_modified_ts":"2024-10-25T19:43:51Z", - "checksum":"f1daa5f4708a28b872ba8894b9ebe7a0", - "metadata_checksum":"25c79beb7593ccacdb6a8afdd1849157", - "file_size":"946.00 B" + "sites/dupe_sh/content/bookshelf/on programming/rust cheatsheet.md":{ + "file_size":"5.81 KiB", + "checksum":"9f8599b1099a1f4068bfc4175b4e0d37", + "metadata_checksum":"80c7c22e899eb25e10c6963f4f33379e", + "last_modified_ts":"2026-01-02T00:13:04Z" }, - "sites/dupe_sh/content/bookshelf/curriculums/interview prep/APIs & web technologies/communication protocols.md":{ - "last_modified_ts":"2026-01-02T00:13:04Z", - "checksum":"a49cf59b643ac3df6980f2bb500fa65d", - "metadata_checksum":"8182a6ff42c407dfd1c84d20de92b9d5", - "file_size":"2.22 KiB" + "sites/dupe_sh/content/bookshelf/on programming.md":{ + "last_modified_ts":"2024-10-05T17:56:47Z", + "file_size":"1.14 KiB", + "checksum":"8584372367be600244777464368285d8" }, "sites/dupe_sh/content/bookshelf/curriculums/interview prep/systems design core concepts.md":{ - "last_modified_ts":"2024-11-20T01:31:11Z", + "file_size":"6.39 KiB", "checksum":"ff100967b137460a598c4c364127db94", "metadata_checksum":"99914b932bd37a50b983c5e7c90ae93b", - "file_size":"6.39 KiB" - }, - "sites/dupe_sh/content/bookshelf/curriculums/interview prep/asynchronous communication/event sourcing.md":{ - "last_modified_ts":"2026-01-02T00:13:04Z", - "checksum":"68b329da9893e34099c7d8ad5cb9c940", - "metadata_checksum":"944a05b23d2595a498236bf855b22841", - "file_size":"1.15 KiB" + "last_modified_ts":"2024-11-20T01:31:11Z" }, - "sites/dupe_sh/content/bookshelf/on programming/rules-of-programming.md":{ - "last_modified_ts":"2025-03-07T21:58:44Z", - "checksum":"9fc067e00c9f74e729e0301156f1e71c", + "sites/dupe_sh/content/bookshelf/curriculums/interview prep/coding and algorithms concepts.md":{ + "file_size":"55.20 KiB", + "checksum":"5d63d59f71a996d6309a86c77d1d8da4", "metadata_checksum":"99914b932bd37a50b983c5e7c90ae93b", - "file_size":"887.00 B" - }, - "sites/dupe_sh/content/bookshelf/curriculums/interview prep/distributed systems/main concepts.md":{ - "last_modified_ts":"2024-11-03T21:29:21Z", - "checksum":"42c05d0def64ac706765175a64a56a3d", - "file_size":"918.00 B" - }, - "sites/dupe_sh/content/draft.md":{ - "last_modified_ts":"2024-10-13T19:04:05Z", - "checksum":"68b329da9893e34099c7d8ad5cb9c940", - "file_size":"642.00 B" - }, - "sites/dupe_sh/content/bookshelf/curriculums/interview prep/domain specific/financial applications.md":{ - "last_modified_ts":"2026-01-02T00:13:04Z", - "checksum":"1392ba39bd2e0fa672aedaef3dbd2d35", - "metadata_checksum":"2724605debf3aa660ed858e5f676e55c", - "file_size":"1.31 KiB" + "last_modified_ts":"2024-12-12T18:07:02Z" }, - "sites/dupe_sh/content/test.md":{ - "last_modified_ts":"2024-11-27T21:15:07Z", - "checksum":"84eb0b8ff0a859ac8d3e3b1f40cf0155", - "metadata_checksum":"25c79beb7593ccacdb6a8afdd1849157", - "file_size":"870.00 B" + "sites/dupe_sh/content/manuscripts/in-n-out, my brief stint at princeton.md":{ + "file_size":"7.16 KiB", + "checksum":"2d5685285dce74f30e5b23c5263a6d50", + "metadata_checksum":"11d78d29b522756507d9b95bc09f485b", + "last_modified_ts":"2026-01-02T00:57:56Z" }, - "sites/dupe_sh/content/bookshelf/curriculums/interview prep/storing data/embedded databases.md":{ - "last_modified_ts":"2026-01-02T00:13:04Z", - "checksum":"0ff400082f145fe9ea840e5694e0f06d", - "metadata_checksum":"7498978242ee48be1c319a3bd00cf364", - "file_size":"1.49 KiB" + "sites/dupe_sh/content/bookshelf/curriculums/interview prep/storing data/db data structures.md":{ + "file_size":"911.00 B", + "checksum":"3a7a4e6ca6076bd0fda11b1789513779", + "metadata_checksum":"99914b932bd37a50b983c5e7c90ae93b", + "last_modified_ts":"2024-11-03T20:37:19Z" } } } \ No newline at end of file diff --git a/sites/rorschach/config.toml b/sites/rorschach/config.toml index 9d21517..bd3a542 100644 --- a/sites/rorschach/config.toml +++ b/sites/rorschach/config.toml @@ -16,3 +16,10 @@ default = "page.htmlua" index_page = "index.htmlua" post = "post.htmlua" feed = "feed.xmlua" + +[comments] +enabled = true +remark_url = "http://localhost:8081" +site_id = "rorschach" +theme = "light" +locale = "en" diff --git a/sites/rorschach/rorschach.lock b/sites/rorschach/rorschach.lock index 8469bde..b74eda6 100644 --- a/sites/rorschach/rorschach.lock +++ b/sites/rorschach/rorschach.lock @@ -1,70 +1,70 @@ { "files":{ - "sites/rorschach/content/commentary/art for a dying empire.md":{ - "metadata_checksum":"07f10f34f6d12ef5423c86d4068c22d1", + "sites/rorschach/content/commentary/the republic that was promised.md":{ + "file_size":"854.00 B", "checksum":"68b329da9893e34099c7d8ad5cb9c940", - "last_modified_ts":"2025-03-09T00:55:13Z", - "file_size":"846.00 B" + "metadata_checksum":"3f2c8a94c35fe1318873340803152d05", + "last_modified_ts":"2025-03-09T00:55:12Z" }, - "sites/rorschach/content/commentary/imagine you are siri keeton.md":{ - "metadata_checksum":"2724605debf3aa660ed858e5f676e55c", - "checksum":"68b329da9893e34099c7d8ad5cb9c940", - "last_modified_ts":"2025-03-09T00:55:13Z", - "file_size":"851.00 B" + "sites/rorschach/content/commentary/on artificial intelligence.md":{ + "file_size":"1.37 KiB", + "checksum":"ae6e8e1db0848aab039950c63055db74", + "metadata_checksum":"25c79beb7593ccacdb6a8afdd1849157", + "last_modified_ts":"2026-01-02T05:24:08Z" }, - "sites/rorschach/content/commentary/the battle of my life.md":{ - "metadata_checksum":"9f2d2b1c553e9823fbff13c636ea0b0f", - "checksum":"e11d0bdc043d51ac853d1ba86071d654", - "last_modified_ts":"2025-03-09T00:55:12Z", - "file_size":"1.04 KiB" + "sites/rorschach/content/ledger/favorite books.md":{ + "file_size":"949.00 B", + "checksum":"e0db7ee20903be932ada7a375dae9c36", + "metadata_checksum":"e4c83b14c87571a119a708be215693a3", + "last_modified_ts":"2025-03-09T00:55:14Z" }, "sites/rorschach/content/commentary/the task of our time.md":{ - "metadata_checksum":"f1d249741d9b684ae9372fafd72052aa", + "file_size":"1.05 KiB", "checksum":"f306f539e5c906b01cc3bf74384281a1", - "last_modified_ts":"2026-01-02T05:24:08Z", - "file_size":"1.05 KiB" - }, - "sites/rorschach/content/projects.md":{ - "metadata_checksum":"67d12d2007aadd4c13b2ba853e7066ba", - "checksum":"0fbef9e6883406a9125478982ff5edd0", - "last_modified_ts":"2025-03-09T00:55:13Z", - "file_size":"1.09 KiB" + "metadata_checksum":"f1d249741d9b684ae9372fafd72052aa", + "last_modified_ts":"2026-01-02T05:24:08Z" }, - "sites/rorschach/content/quips.md":{ - "metadata_checksum":"717c88b9fcf790ba6c28a37db6b55831", + "sites/rorschach/content/commentary/art for a dying empire.md":{ + "file_size":"846.00 B", "checksum":"68b329da9893e34099c7d8ad5cb9c940", - "last_modified_ts":"2026-01-02T05:24:08Z", - "file_size":"745.00 B" + "metadata_checksum":"07f10f34f6d12ef5423c86d4068c22d1", + "last_modified_ts":"2025-03-09T00:55:13Z" }, - "sites/rorschach/content/commentary/on artificial intelligence.md":{ - "metadata_checksum":"25c79beb7593ccacdb6a8afdd1849157", - "checksum":"ae6e8e1db0848aab039950c63055db74", - "last_modified_ts":"2026-01-02T05:24:08Z", - "file_size":"1.37 KiB" + "sites/rorschach/content/commentary/what is real is not.md":{ + "file_size":"1.03 KiB", + "checksum":"56f5f58f5c6409ae030c2b532b38de02", + "metadata_checksum":"0d7beaf9bcc7e492c72ba3b857071b07", + "last_modified_ts":"2025-03-09T00:55:12Z" }, "sites/rorschach/content/about.md":{ - "metadata_checksum":"99914b932bd37a50b983c5e7c90ae93b", + "file_size":"812.00 B", "checksum":"e18be70a3981580f1e7464786e561b24", - "last_modified_ts":"2025-03-09T00:55:13Z", - "file_size":"812.00 B" - }, - "sites/rorschach/content/commentary/what is real is not.md":{ - "metadata_checksum":"0d7beaf9bcc7e492c72ba3b857071b07", - "checksum":"56f5f58f5c6409ae030c2b532b38de02", - "last_modified_ts":"2025-03-09T00:55:12Z", - "file_size":"1.03 KiB" + "metadata_checksum":"99914b932bd37a50b983c5e7c90ae93b", + "last_modified_ts":"2025-03-09T00:55:13Z" }, - "sites/rorschach/content/commentary/the republic that was promised.md":{ - "metadata_checksum":"3f2c8a94c35fe1318873340803152d05", + "sites/rorschach/content/quips.md":{ + "file_size":"745.00 B", "checksum":"68b329da9893e34099c7d8ad5cb9c940", - "last_modified_ts":"2025-03-09T00:55:12Z", - "file_size":"854.00 B" + "metadata_checksum":"717c88b9fcf790ba6c28a37db6b55831", + "last_modified_ts":"2026-01-02T05:24:08Z" }, - "sites/rorschach/content/ledger/favorite books.md":{ - "metadata_checksum":"e4c83b14c87571a119a708be215693a3", - "checksum":"e0db7ee20903be932ada7a375dae9c36", - "last_modified_ts":"2025-03-09T00:55:14Z", - "file_size":"949.00 B" + "sites/rorschach/content/commentary/the battle of my life.md":{ + "file_size":"1.04 KiB", + "checksum":"e11d0bdc043d51ac853d1ba86071d654", + "metadata_checksum":"9f2d2b1c553e9823fbff13c636ea0b0f", + "last_modified_ts":"2025-03-09T00:55:12Z" + }, + "sites/rorschach/content/projects.md":{ + "file_size":"1.09 KiB", + "checksum":"0fbef9e6883406a9125478982ff5edd0", + "metadata_checksum":"67d12d2007aadd4c13b2ba853e7066ba", + "last_modified_ts":"2025-03-09T00:55:13Z" + }, + "sites/rorschach/content/commentary/imagine you are siri keeton.md":{ + "file_size":"851.00 B", + "checksum":"68b329da9893e34099c7d8ad5cb9c940", + "metadata_checksum":"2724605debf3aa660ed858e5f676e55c", + "last_modified_ts":"2025-03-09T00:55:13Z" } } } \ No newline at end of file diff --git a/sites/whwg/config.toml b/sites/whwg/config.toml index 8034ac9..2485a42 100644 --- a/sites/whwg/config.toml +++ b/sites/whwg/config.toml @@ -16,3 +16,10 @@ default = "page.htmlua" index_page = "index.htmlua" post = "post.htmlua" feed = "feed.xmlua" + +[comments] +enabled = true +remark_url = "http://localhost:8081" +site_id = "whwg" +theme = "light" +locale = "en" diff --git a/sites/whwg/whwg.lock b/sites/whwg/whwg.lock index 30de0d7..9f4a831 100644 --- a/sites/whwg/whwg.lock +++ b/sites/whwg/whwg.lock @@ -1,16 +1,16 @@ { "files":{ - "sites/whwg/content/about.md":{ - "metadata_checksum":"99914b932bd37a50b983c5e7c90ae93b", - "checksum":"95541378cfc2a3c2d9815075d2169945", - "file_size":"1.38 KiB", - "last_modified_ts":"2025-03-09T00:33:35Z" - }, "sites/whwg/content/newsletters/newsletter 1 - introductions, plans, hopes, dreams, fears!.md":{ - "metadata_checksum":"a465803e3babaf789d34c3b12817b6b3", "checksum":"68b329da9893e34099c7d8ad5cb9c940", + "metadata_checksum":"a465803e3babaf789d34c3b12817b6b3", "file_size":"879.00 B", "last_modified_ts":"2025-03-09T00:33:35Z" + }, + "sites/whwg/content/about.md":{ + "checksum":"95541378cfc2a3c2d9815075d2169945", + "metadata_checksum":"99914b932bd37a50b983c5e7c90ae93b", + "file_size":"1.38 KiB", + "last_modified_ts":"2025-03-09T00:33:35Z" } } } \ No newline at end of file diff --git a/src/static/css/main.css b/src/static/css/main.css index 5f04e2a..94ddddf 100644 --- a/src/static/css/main.css +++ b/src/static/css/main.css @@ -732,3 +732,21 @@ a { min-width: auto; } } + +/* Comments Section (Remark42) */ +.comments-section { + margin-top: 3rem; + padding-top: 2rem; + border-top: 1px solid var(--color-border); +} + +.comments-section h2 { + margin-bottom: 1.5rem; + font-size: 1.25rem; + color: var(--color-text); +} + +/* Remark42 container styling */ +#remark42 { + /* Remark42 renders in a cross-origin iframe, so we can only style the container */ +} diff --git a/src/templates/post.htmlua b/src/templates/post.htmlua index 58809bc..94b725e 100644 --- a/src/templates/post.htmlua +++ b/src/templates/post.htmlua @@ -42,4 +42,27 @@ discuss {% end }} + {% if config.comments and config.comments.enabled and metadata.comments ~= false then }} +
+

Comments

+
+ + +
+ {% end }} +