From 4691e5f5dcf3fab174c95a5e5cd6414a05bdd329 Mon Sep 17 00:00:00 2001 From: Cytrogen Date: Fri, 27 Feb 2026 17:14:49 -0500 Subject: [PATCH] Initialize Realm: Phase 1 redirect + Phase 2 design system MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Caddy 301 redirect config: cytrogen.icu → blog.cytrogen.icu - CSS design system: oklch color tokens, @layer cascade, @property animations - Base index.html with inline SVG sprite (sigil, navigation, dividers) - Contact page placeholder --- .gitignore | 1 + CLAUDE.md | 88 ++++++ caddy/phase1-redirect.example.Caddyfile | 51 ++++ css/realm.css | 344 ++++++++++++++++++++++++ index.html | 94 +++++++ pages/contact.html | 32 +++ robots.txt | 4 + 7 files changed, 614 insertions(+) create mode 100644 .gitignore create mode 100644 CLAUDE.md create mode 100644 caddy/phase1-redirect.example.Caddyfile create mode 100644 css/realm.css create mode 100644 index.html create mode 100644 pages/contact.html create mode 100644 robots.txt diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..4c5f2067e39fc25d9553143dacc5f4c1aacac23c --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.claude/ diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000000000000000000000000000000000000..b6ba8c9bd76e13bb4c83b88e33a8a5ac582244c0 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,88 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Project Overview + +Realm is the visual exploration portal for cytrogen.icu. It replaces the root domain's previous blog with a Castlevania-inspired territory map. The blog lives at blog.cytrogen.icu. + +**Design language**: Deep cold tones (oklch blues), high-saturation orange as the sole accent, flat geometry, no strokes, no gradients, large color blocks. + +**Tech stack**: Pure HTML + CSS. Zero JavaScript. All SVG is inline. No build tools, no bundler, no framework. + +## Deployment + +Push-to-deploy via bare repo on VPS (accessible as `ssh vps-user`): + +``` +git push origin main +``` + +The post-receive hook at `~/repos/realm.git/hooks/post-receive` performs: +1. Checkout to temp dir +2. rsync to `/var/www/realm` (excludes `.git`, `caddy/`, `CLAUDE.md`) +3. Push to local Sourcehut instance (`ssh://git@localhost:22222/~cytrogen/realm`) + +Caddy config lives at `/etc/caddy/Caddyfile` on the VPS (backup at `~/vps-deploy/Caddyfile`). Editing requires sudo. The `caddy/` directory in this repo contains reference snippets only. + +### VPS Infrastructure + +| Subdomain | Service | Backend | +|---|---|---| +| cytrogen.icu | Phase 1: 301 → blog.cytrogen.icu | Caddy redirect | +| blog.cytrogen.icu | Hexo blog (static) | `/var/www/blog.cytrogen.icu` | +| git.cytrogen.icu | Sourcehut git | localhost:5003 | +| meta.cytrogen.icu | Sourcehut meta | localhost:5001 | +| rss.cytrogen.icu | FreshRSS | localhost:8096 | +| status.cytrogen.icu | Beszel | localhost:8090 | +| mail.cytrogen.icu | Stalwart | localhost:8080 | + +Blog deploys via `~/repos/blog.git` → builds Hexo → `/var/www/blog.cytrogen.icu` → pushes to Sourcehut (`blog-local` + `blog-public`). + +## Architecture + +### CSS (`css/realm.css`) + +Single stylesheet using `@layer` for cascade control: +``` +@layer reset, tokens, base, layout, components, utilities; +``` + +- **tokens**: oklch color variables, spacing, typography scales, motion durations on `:root` +- **components**: SVG fill classes (`.svg-icon`, `.svg-interactive`), navigation, panels +- Typed `@property` declarations at file top enable smooth color transitions on SVG fills +- `prefers-reduced-motion` kills all transition durations globally + +Colors use oklch with relative color syntax (`oklch(from var(--accent) ...)`) for derived variants. + +### SVG Conventions + +All SVG assets are inline in the HTML DOM — never `` or ``. + +- Reusable shapes: `` in a hidden sprite block, referenced via `` +- Decorative SVG: `aria-hidden="true" focusable="false"` +- Informative SVG: `role="img" aria-labelledby="title-id"` with `` as first child +- Paths use CSS classes for fills — no inline `fill` attributes +- No `stroke` attributes anywhere. No gradients. Fill-only flat geometry. +- Use `viewBox` only — omit explicit `width`/`height` on `<symbol>` for responsive scaling + +### Page Structure + +- `index.html` — Realm entry, contains the SVG sprite `<defs>` block and navigation +- `pages/contact.html` — 领地联络处 (Contact) +- `caddy/` — Reference Caddyfile snippets (not deployed) + +## Migration Phases + +1. **Phase 1** (done): 301 redirect cytrogen.icu → blog.cytrogen.icu, blog files at `/var/www/blog.cytrogen.icu` +2. **Phase 2** (current): CSS/SVG design system established in `css/realm.css` +3. **Phase 3** (planned): Full scene layout — Castlevania-style layered environment or game-map navigation. CSS classes `.realm-scene`, `.realm-layer--far/mid/near` are pre-defined in the layout layer. +4. **Phase 4** (planned): Realm goes live at cytrogen.icu root (serve from `/var/www/realm`), remove 301 redirects + +## Design Constraints + +- No JavaScript unless absolutely unavoidable +- All interactivity via CSS (`:hover`, `:focus-visible`, transitions, `@property` animations) +- SVG complexity must degrade gracefully — use container queries or media queries to hide detail at small sizes +- Text-only fallback for terminals/Gemini-style access (planned: separate gopher/gemini protocol support) +- Accessibility: skip decorative SVG via `aria-hidden`, label informative SVG, respect `prefers-reduced-motion` diff --git a/caddy/phase1-redirect.example.Caddyfile b/caddy/phase1-redirect.example.Caddyfile new file mode 100644 index 0000000000000000000000000000000000000000..9658d69e1afd29bb283f065f9b3b1915eb4b6632 --- /dev/null +++ b/caddy/phase1-redirect.example.Caddyfile @@ -0,0 +1,51 @@ +# Phase 1: Blog migration — cytrogen.icu → blog.cytrogen.icu +# +# This is a reference copy of the redirect blocks deployed to /etc/caddy/Caddyfile. +# The production Caddyfile also includes blocks for sourcehut, beszel, freshrss, +# stalwart, etc. — only the realm-relevant portions are shown here. +# +# Status: DEPLOYED 2026-02-27 + +# Blog now served from /var/www/blog.cytrogen.icu +blog.cytrogen.icu { + root * /var/www/blog.cytrogen.icu + file_server + encode gzip + + @static path *.css *.js *.jpg *.jpeg *.png *.gif *.ico *.svg *.webp *.woff *.woff2 *.ttf *.eot + header @static Cache-Control "public, max-age=2592000, immutable" + + @html path *.html + header @html Cache-Control "public, max-age=3600, must-revalidate" + + handle_errors { + @4xx expression `{err.status_code} >= 400 && {err.status_code} < 500` + handle @4xx { + rewrite * /{err.status_code}.html + file_server + } + @5xx expression `{err.status_code} >= 500` + handle @5xx { + rewrite * /{err.status_code}.html + file_server + } + } + + try_files {path} {path}/ {path}.html +} + +# 301 permanent redirect — root domain to blog subdomain +cytrogen.icu, www.cytrogen.icu { + header Link `<https://blog.cytrogen.icu{uri}>; rel="canonical"` + header X-Robots-Tag "noindex, nofollow" + redir https://blog.cytrogen.icu{uri} permanent +} + +# Phase 4: Replace the redirect block above with: +# +# cytrogen.icu, www.cytrogen.icu { +# root * /var/www/realm +# file_server +# encode gzip +# try_files {path} {path}/ {path}.html +# } diff --git a/css/realm.css b/css/realm.css new file mode 100644 index 0000000000000000000000000000000000000000..021f949b9c7f7f243409b87aa16ba607abc8927e --- /dev/null +++ b/css/realm.css @@ -0,0 +1,344 @@ +/* + * Realm — Design System + * Castlevania-inspired: deep cold tones, orange accent, flat geometry + * + * Architecture: CSS @layer for cascade control + * Colors: oklch with relative color syntax + * SVG: fill-only, no stroke, CSS-controlled + */ + +@layer reset, tokens, base, layout, components, utilities; + +/* ================================================================ + @property — typed custom properties for smooth animation + ================================================================ */ + +@property --fill-current { + syntax: "<color>"; + inherits: true; + initial-value: oklch(0.85 0.02 240); +} + +@property --fill-accent { + syntax: "<color>"; + inherits: true; + initial-value: oklch(0.72 0.18 55); +} + +/* ================================================================ + RESET + ================================================================ */ + +@layer reset { + *, + *::before, + *::after { + box-sizing: border-box; + margin: 0; + padding: 0; + } + + html { + -webkit-text-size-adjust: 100%; + text-size-adjust: 100%; + } + + img, + svg { + display: block; + max-width: 100%; + } + + a { + color: inherit; + text-decoration: none; + } + + ul, + ol { + list-style: none; + } +} + +/* ================================================================ + TOKENS + ================================================================ */ + +@layer tokens { + :root { + /* === Background scale (deep cold) === */ + --bg-abyss: oklch(0.10 0.02 260); + --bg-deep: oklch(0.15 0.03 255); + --bg-mid: oklch(0.22 0.04 250); + --bg-surface: oklch(0.28 0.04 245); + --bg-elevated: oklch(0.35 0.04 240); + + /* === Text scale === */ + --text-primary: oklch(0.85 0.02 240); + --text-secondary: oklch(0.60 0.03 250); + --text-dim: oklch(0.42 0.03 255); + + /* === Accent: high-saturation orange (sole emphasis) === */ + --accent: oklch(0.72 0.18 55); + --accent-bright: oklch(from var(--accent) calc(l + 0.1) c h); + --accent-dim: oklch(from var(--accent) calc(l - 0.15) c h); + --accent-glow: oklch(from var(--accent) l c h / 0.3); + + /* === Spacing === */ + --space-xs: 0.25rem; + --space-sm: 0.5rem; + --space-md: 1rem; + --space-lg: 2rem; + --space-xl: 4rem; + + /* === Typography === */ + --font-body: system-ui, -apple-system, "Segoe UI", sans-serif; + --font-mono: ui-monospace, "Cascadia Code", "Fira Code", monospace; + --text-sm: clamp(0.8rem, 0.75rem + 0.25vw, 0.875rem); + --text-base: clamp(0.95rem, 0.9rem + 0.25vw, 1.05rem); + --text-lg: clamp(1.2rem, 1rem + 1vw, 1.75rem); + --text-xl: clamp(1.8rem, 1.4rem + 2vw, 3rem); + + /* === Motion === */ + --ease-default: cubic-bezier(0.4, 0, 0.2, 1); + --ease-out: cubic-bezier(0, 0, 0.2, 1); + --duration-fast: 150ms; + --duration-normal: 300ms; + --duration-slow: 600ms; + } + + @media (prefers-reduced-motion: reduce) { + :root { + --duration-fast: 0ms; + --duration-normal: 0ms; + --duration-slow: 0ms; + } + } +} + +/* ================================================================ + BASE + ================================================================ */ + +@layer base { + html { + color-scheme: dark; + } + + body { + font-family: var(--font-body); + font-size: var(--text-base); + line-height: 1.6; + color: var(--text-primary); + background-color: var(--bg-abyss); + min-height: 100dvh; + overflow-x: hidden; + } + + h1, + h2, + h3 { + line-height: 1.2; + color: var(--text-primary); + } + + h1 { font-size: var(--text-xl); } + h2 { font-size: var(--text-lg); } + h3 { font-size: var(--text-base); } + + p { + color: var(--text-secondary); + } + + code { + font-family: var(--font-mono); + font-size: 0.9em; + } + + ::selection { + background: var(--accent-glow); + color: var(--text-primary); + } +} + +/* ================================================================ + LAYOUT + ================================================================ */ + +@layer layout { + .realm { + display: grid; + place-items: center; + min-height: 100dvh; + padding: var(--space-lg); + } + + .realm-container { + width: 100%; + max-width: 72rem; + margin-inline: auto; + padding-inline: var(--space-md); + } + + /* Phase 3 scene viewport — reserves full-screen space */ + .realm-scene { + position: relative; + width: 100%; + min-height: 100dvh; + overflow: hidden; + } + + /* Scene depth layers (Phase 3) */ + .realm-layer { + position: absolute; + inset: 0; + width: 100%; + height: 100%; + } + + .realm-layer--far { z-index: 1; } + .realm-layer--mid { z-index: 2; } + .realm-layer--near { z-index: 3; } + .realm-layer--ui { z-index: 10; } +} + +/* ================================================================ + COMPONENTS + ================================================================ */ + +@layer components { + /* --- Navigation --- */ + + .realm-nav { + display: flex; + flex-direction: column; + gap: var(--space-md); + align-items: center; + text-align: center; + } + + .realm-nav a { + display: inline-flex; + align-items: center; + gap: var(--space-sm); + font-size: var(--text-lg); + color: var(--text-secondary); + transition: color var(--duration-normal) var(--ease-default); + } + + .realm-nav a:hover, + .realm-nav a:focus-visible { + color: var(--accent); + } + + .realm-nav a:focus-visible { + outline: 2px solid var(--accent); + outline-offset: 4px; + } + + /* --- Realm Title / Sigil --- */ + + .realm-sigil { + display: flex; + flex-direction: column; + align-items: center; + gap: var(--space-lg); + margin-block-end: var(--space-xl); + } + + .realm-sigil svg { + width: clamp(6rem, 15vw, 12rem); + height: auto; + } + + .realm-sigil__title { + font-size: var(--text-xl); + letter-spacing: 0.1em; + text-transform: uppercase; + color: var(--text-primary); + } + + .realm-sigil__subtitle { + font-size: var(--text-sm); + letter-spacing: 0.2em; + text-transform: uppercase; + color: var(--text-dim); + } + + /* --- SVG Components --- */ + + /* Fill defaults: all SVG paths inherit from CSS, not inline attributes */ + .svg-icon { + fill: var(--fill-current); + transition: fill var(--duration-normal) var(--ease-default); + } + + .svg-icon--accent { + fill: var(--fill-accent); + } + + /* Interactive SVG elements */ + .svg-interactive { + cursor: pointer; + transition: + fill var(--duration-normal) var(--ease-default), + opacity var(--duration-fast) var(--ease-default), + transform var(--duration-normal) var(--ease-default); + transform-origin: center; + } + + .svg-interactive:hover { + fill: var(--accent); + transform: scale(1.05); + } + + /* Decorative divider */ + .realm-divider { + width: 100%; + max-width: 20rem; + height: auto; + margin-inline: auto; + opacity: 0.3; + } + + .realm-divider path { + fill: var(--text-dim); + } + + /* --- Card / Panel --- */ + + .realm-panel { + background: var(--bg-surface); + padding: var(--space-lg); + container-type: inline-size; + } + + @container (min-width: 30rem) { + .realm-panel { + padding: var(--space-xl); + } + } +} + +/* ================================================================ + UTILITIES + ================================================================ */ + +@layer utilities { + .visually-hidden { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip: rect(0, 0, 0, 0); + white-space: nowrap; + border: 0; + } + + .text-accent { color: var(--accent); } + .text-dim { color: var(--text-dim); } + .fill-accent { fill: var(--accent); } + .fill-primary { fill: var(--text-primary); } +} diff --git a/index.html b/index.html new file mode 100644 index 0000000000000000000000000000000000000000..e728562ee7273ab028b1280d5ed9844c284fe839 --- /dev/null +++ b/index.html @@ -0,0 +1,94 @@ +<!DOCTYPE html> +<html lang="en"> +<head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <title>Realm — cytrogen.icu + + + + + + + + + +
+ +
+ + Realm sigil — a castle tower + + +

Realm

+

cytrogen.icu

+
+ + + + + + + + + +
+ + + diff --git a/pages/contact.html b/pages/contact.html new file mode 100644 index 0000000000000000000000000000000000000000..46ccfb74084f643a0630be32cff6a490d75d660c --- /dev/null +++ b/pages/contact.html @@ -0,0 +1,32 @@ + + + + + + Contact — Realm + + + + + + +
+
+

领地联络处

+

Contact

+
+ +
+

Reach out via the channels listed below.

+ +
+ + +
+ + + diff --git a/robots.txt b/robots.txt new file mode 100644 index 0000000000000000000000000000000000000000..91179c7982fcdd8757adef097784864c7f9032b8 --- /dev/null +++ b/robots.txt @@ -0,0 +1,4 @@ +User-agent: * +Allow: / + +Sitemap: https://cytrogen.icu/sitemap.xml