From 4dd1bec3277fe38c11e90c59ba5b99e0adaae92d Mon Sep 17 00:00:00 2001 From: Lef Date: Sun, 1 Mar 2026 17:10:41 +0100 Subject: [PATCH 1/5] feat: add Claude AI collaboration scaffolding Add CLAUDE.md developer guide and .claude/ directory with context files, skills, and settings for effective AI-assisted development. --- .claude/context/deployment.md | 32 +++++++ .claude/context/project-structure.md | 55 ++++++++++++ .claude/context/tufte-patterns.md | 110 ++++++++++++++++++++++++ .claude/settings.json | 10 +++ .claude/skills/creating-post/SKILL.md | 46 ++++++++++ .claude/skills/updating-styles/SKILL.md | 35 ++++++++ CLAUDE.md | 79 +++++++++++++++++ 7 files changed, 367 insertions(+) create mode 100644 .claude/context/deployment.md create mode 100644 .claude/context/project-structure.md create mode 100644 .claude/context/tufte-patterns.md create mode 100644 .claude/settings.json create mode 100644 .claude/skills/creating-post/SKILL.md create mode 100644 .claude/skills/updating-styles/SKILL.md create mode 100644 CLAUDE.md diff --git a/.claude/context/deployment.md b/.claude/context/deployment.md new file mode 100644 index 0000000..2925406 --- /dev/null +++ b/.claude/context/deployment.md @@ -0,0 +1,32 @@ +# Deployment + +## Pipeline + +GitHub Actions workflow: `.github/workflows/cicd.yml` + +**Trigger**: Push to `src/**` or `.github/workflows/**` + +**Steps**: +1. Checkout repo (with Git LFS) +2. Detect branch name +3. Configure AWS credentials +4. Create/select S3 bucket (main → production bucket, branches → `{branch}.{bucket}`) +5. Sync `src/` directory to S3 with public-read ACL +6. Configure S3 static website hosting (index: `index.html`, error: `404.html`) +7. Apply bucket policy from `.github/workflows/policy.json` +8. For non-main branches: create Cloudflare CNAME record for subdomain +9. Purge Cloudflare cache + +## Branch Strategy + +| Branch | URL | Auto-created | +|--------|-----|-------------| +| `main` | https://lef.fyi | No (production) | +| `feature-x` | https://feature-x.lef.fyi | Yes (DNS + S3 bucket) | + +## Secrets (referenced in CI, never stored in repo) + +- `AWS_ACCESS_KEY`, `AWS_SECRET_ACCESS_KEY`, `AWS_REGION` — S3 access +- `AWS_S3_BUCKET` — production bucket name +- `CLOUDFLARE_ZONE_ID`, `CLOUDFLARE_DNS_SECRET_API_TOKEN` — DNS + cache management +- `EMAIL` — Cloudflare auth email diff --git a/.claude/context/project-structure.md b/.claude/context/project-structure.md new file mode 100644 index 0000000..766fb34 --- /dev/null +++ b/.claude/context/project-structure.md @@ -0,0 +1,55 @@ +# Project Structure + +``` +lef-web/ +├── src/ # Deployable website root +│ ├── index.html # Homepage +│ ├── 404.html # Error page +│ ├── pages/ +│ │ └── posts/ # Blog posts +│ │ ├── 20220305-my-first-post.html +│ │ └── 20220319-a-very-poetic-python.html +│ ├── media/ # All media assets +│ │ ├── 00-common/ # Shared assets (banner image) +│ │ └── YYYYMMDD-slug/ # Post-specific media directories +│ ├── tufte.css # Source stylesheet +│ ├── tufte.min.css # Minified stylesheet (generated, commit both) +│ ├── et-book/ # ET Book font files (Roman, Bold, Italic, Display variants) +│ ├── favicon.ico +│ ├── robots.txt +│ └── sitemap.txt # Generated by tools/src/sitemap_generator.py +├── tools/ # Python utilities +│ ├── pyproject.toml # Poetry config (Python 3.9+, black formatter) +│ ├── poetry.lock +│ └── src/ +│ └── sitemap_generator.py # Generates sitemap.txt from src/ directory +├── .github/ +│ └── workflows/ +│ ├── cicd.yml # Deploy pipeline (S3 + Cloudflare) +│ └── policy.json # S3 bucket policy template +├── .claude/ # Claude AI collaboration config +│ ├── settings.json # Permissions +│ ├── context/ # Domain knowledge files +│ └── skills/ # Step-by-step procedures +├── .vscode/ # Editor config +│ ├── settings.json # Spell checker words +│ ├── launch.json # Debug configs +│ └── html.code-snippets # HTML authoring snippets +├── .gitattributes # Git LFS: *.png, *.jpg, *.jpeg +├── .gitignore # .DS_Store, node_modules +├── package.json # npm: clean-css-cli, http-server +└── CLAUDE.md # Primary Claude developer guide +``` + +## Naming Conventions + +- Post files: `YYYYMMDD-slug.html` (date prefix for chronological sorting) +- Media directories: match the post name (`YYYYMMDD-slug/`) +- Common/shared media: `00-common/` prefix ensures it sorts first +- CSS: edit `tufte.css`, always regenerate `tufte.min.css` + +## Git LFS + +Tracked via `.gitattributes`: +- `*.png` +- `*.jpg` / `*.jpeg` diff --git a/.claude/context/tufte-patterns.md b/.claude/context/tufte-patterns.md new file mode 100644 index 0000000..5197af1 --- /dev/null +++ b/.claude/context/tufte-patterns.md @@ -0,0 +1,110 @@ +# Tufte CSS HTML Patterns + +Reference for all HTML patterns used in this project. Copy and adapt as needed. + +## Page Template + +```html + + + + + +PAGE TITLE | Lef adores you ❤️ + + + + + + + + +
+
+

PAGE TITLE

+

First paragraph.

+
+
+ + + +``` + +Note: The invisible duplicate banner image is a spacing hack — do not remove it. + +## Sidenote (Numbered Marginal Note) + +Each sidenote needs a **unique id** within the page. Place inline within a `

`: + +```html + + + + + Note content here. + +``` + +- Desktop: appears in the right margin with a superscript number +- Mobile: collapses behind a ⊕ toggle button (CSS-only, no JS) + +## Sidenote with Image + +```html + + + + + + +``` + +## Full-Width Image + +```html + +``` + +## Blockquote + +```html +

+

Title

+ Content here. +
+``` + +## Inline Code + +```html +some_command +``` + +## Links + +Same tab: +```html +Link text +``` + +New tab (external links): +```html +Link text +``` + +## Section Structure + +Posts use `
` with multiple `
` children. Each section typically has a heading: + +```html +
+
+

Post Title

+

Intro content...

+
+
+

Subsection

+

More content...

+
+
+``` diff --git a/.claude/settings.json b/.claude/settings.json new file mode 100644 index 0000000..83338bc --- /dev/null +++ b/.claude/settings.json @@ -0,0 +1,10 @@ +{ + "permissions": { + "allow": [ + "Bash(npm run start)", + "Bash(npm run cssmin)", + "Bash(npx http-server*)", + "Bash(cd tools && poetry run python*)" + ] + } +} diff --git a/.claude/skills/creating-post/SKILL.md b/.claude/skills/creating-post/SKILL.md new file mode 100644 index 0000000..1f5fac9 --- /dev/null +++ b/.claude/skills/creating-post/SKILL.md @@ -0,0 +1,46 @@ +--- +name: creating-post +description: Create a new blog post with the standard Tufte CSS template and update all references. +--- + +# Creating a Blog Post + +## When to Use +When creating a new page or blog post for lef.fyi. + +## Steps + +1. **Create the HTML file** + - Path: `src/pages/posts/YYYYMMDD-slug.html` + - Use today's date (YYYYMMDD format) and a URL-friendly slug + - Start from the page template in `.claude/context/tufte-patterns.md` + +2. **Create media directory** (if the post has images) + - Path: `src/media/YYYYMMDD-slug/` + - Name must match the post filename (without `.html`) + +3. **Write the content** + - Use `
` > `
` structure + - Add sidenotes for supplementary information (see tufte-patterns.md) + - Each sidenote `id` must be unique within the page + - Use external link pattern (`target="_blank" rel="noopener noreferrer"`) for off-site links + +4. **Add to homepage** + - Edit `src/index.html` + - Add a link to the new post in the appropriate section + +5. **Update sitemap** + - Run: `cd tools && poetry run python src/sitemap_generator.py https://lef.fyi ../src --except "et-book"` + - Or manually add the URL to `src/sitemap.txt` + +6. **Preview locally** + - Run: `npm run start` + - Check at http://127.0.0.1:8080/pages/posts/YYYYMMDD-slug.html + - Verify sidenotes toggle correctly on narrow viewport + +## Verification +- Page loads without errors +- Banner image displays and links to homepage +- Sidenotes display in margin on desktop, toggle on mobile +- Footer "Return home" link works +- Post appears on homepage diff --git a/.claude/skills/updating-styles/SKILL.md b/.claude/skills/updating-styles/SKILL.md new file mode 100644 index 0000000..03915f9 --- /dev/null +++ b/.claude/skills/updating-styles/SKILL.md @@ -0,0 +1,35 @@ +--- +name: updating-styles +description: Modify the Tufte CSS stylesheet and regenerate the minified version. +--- + +# Updating Styles + +## When to Use +When making any CSS changes to the website's appearance. + +## Steps + +1. **Edit the source stylesheet** + - Path: `src/tufte.css` + - Never edit `tufte.min.css` directly — it's generated + +2. **Regenerate minified CSS** + - Run: `npm run cssmin` + - This overwrites `src/tufte.min.css` from `src/tufte.css` + +3. **Preview changes** + - Run: `npm run start` + - Check multiple pages (homepage + a post) to verify nothing broke + - Test responsive behavior at narrow widths (sidenote toggles, full-width images) + +4. **Commit both files** + - Always commit `tufte.css` and `tufte.min.css` together + +## Key CSS Architecture +- Base width: 55% for text, right margin reserved for sidenotes +- Responsive: orientation-based media queries (portrait mode triggers mobile layout) +- Sidenotes use CSS counters for automatic numbering +- Full-width images use `max-width: none` to escape the text column +- Colors: background `#f9fefc`, text `#443b36`, accent `#dc5945` +- Font: ET Book (custom, loaded from `et-book/` directory) diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..8431854 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,79 @@ +# lef-web — lef.fyi + +Static personal website built with hand-coded HTML and Tufte CSS. Hosted on AWS S3 with Cloudflare CDN. + +**Repo**: `git@github.com:Lef-F/lef-web.git` +**Live**: https://lef.fyi + +## Architecture + +``` +src/ → Deployable website (HTML, CSS, fonts, media) +tools/ → Python utilities (sitemap generator) +.github/ → CI/CD (GitHub Actions → S3 + Cloudflare) +.claude/ → Claude context, skills, settings +``` + +All pages are hand-written HTML — no static site generator, no templating engine, no JavaScript. + +## Development Commands + +```shell +npm run start # Local server at http://127.0.0.1:8080 +npm run cssmin # Minify tufte.css → tufte.min.css (MUST run after CSS changes) +``` + +Sitemap generation (requires poetry env in `tools/`): +```shell +cd tools && poetry run python src/sitemap_generator.py https://lef.fyi ../src --except "et-book" +``` + +## Conventions + +### File Naming +- Blog posts: `src/pages/posts/YYYYMMDD-slug.html` +- Post media: `src/media/YYYYMMDD-slug/` (matching the post date+slug) +- Shared media: `src/media/00-common/` + +### HTML Patterns +Every page follows this structure: +- `` with viewport meta, charset, title (`Page Title | Lef adores you ❤️`), favicon, tufte.min.css +- Banner: clickable full-width image linking to `/` + invisible duplicate for spacing +- Content in `
` > `
` elements +- `