Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions .claude/context/deployment.md
Original file line number Diff line number Diff line change
@@ -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
55 changes: 55 additions & 0 deletions .claude/context/project-structure.md
Original file line number Diff line number Diff line change
@@ -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 # uv config (Python 3.9+, ruff linter)
│ ├── uv.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`
110 changes: 110 additions & 0 deletions .claude/context/tufte-patterns.md
Original file line number Diff line number Diff line change
@@ -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
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta charset="UTF-8">
<title>PAGE TITLE | Lef adores you ❤️</title>
<link rel="icon" href="/favicon.ico" type="image/x-icon">
<link rel="stylesheet" href="/tufte.min.css"/>
</head>
<body>
<a href="/">
<img class="full-width" src="/media/00-common/lef_eyes_2021-06.jpg">
</a>
<img style="position: relative; opacity: 0; z-index: -9;" src="/media/00-common/lef_eyes_2021-06.jpg">
<article>
<section>
<h1>PAGE TITLE</h1>
<p>First paragraph.</p>
</section>
</article>
<footer><a href="/">Return home</a></footer>
</body>
</html>
```

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 `<p>`:

```html
<label class="sidenote-number"></label>
<label id="margin-toggle-button" for="unique-id" class="margin-toggle">⊕</label>
<input type="checkbox" id="unique-id" class="margin-toggle">
<span class="sidenote marginnote">
<em>Note content here.</em>
</span>
```

- Desktop: appears in the right margin with a superscript number
- Mobile: collapses behind a ⊕ toggle button (CSS-only, no JS)

## Sidenote with Image

```html
<label class="sidenote-number"></label>
<label id="margin-toggle-button" for="unique-id" class="margin-toggle">⊕</label>
<input type="checkbox" id="unique-id" class="margin-toggle">
<span class="sidenote marginnote">
<img src="/media/YYYYMMDD-slug/image.png">
</span>
```

## Full-Width Image

```html
<img class="full-width" src="/media/YYYYMMDD-slug/image.jpg">
```

## Blockquote

```html
<blockquote>
<h3>Title</h3>
Content here.
</blockquote>
```

## Inline Code

```html
<code>some_command</code>
```

## Links

Same tab:
```html
<a href="/pages/posts/YYYYMMDD-slug.html">Link text</a>
```

New tab (external links):
```html
<a href="https://example.com" target="_blank" rel="noopener noreferrer">Link text</a>
```

## Section Structure

Posts use `<article>` with multiple `<section>` children. Each section typically has a heading:

```html
<article>
<section>
<h1>Post Title</h1>
<p>Intro content...</p>
</section>
<section>
<h2>Subsection</h2>
<p>More content...</p>
</section>
</article>
```
10 changes: 10 additions & 0 deletions .claude/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"permissions": {
"allow": [
"Bash(npm run start)",
"Bash(npm run cssmin)",
"Bash(npx http-server*)",
"Bash(cd tools && uv run python*)"
]
}
}
46 changes: 46 additions & 0 deletions .claude/skills/creating-post/SKILL.md
Original file line number Diff line number Diff line change
@@ -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 `<article>` > `<section>` 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 && uv 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
35 changes: 35 additions & 0 deletions .claude/skills/updating-styles/SKILL.md
Original file line number Diff line number Diff line change
@@ -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)
Loading