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
13 changes: 13 additions & 0 deletions .claude/rules/pages.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
---
paths:
- "**/*.html"
- "sitemap.xml"
- "tools/deploy.sh"
---

# A new page, or a new top-level file or folder of the site

- Copy the `<head>` of an existing page, with its Content-Security-Policy line.
- Add the URL to `sitemap.xml` (`tools/check.py` fails without it).
- Add a new top-level file or folder to the `SITE` list in `tools/deploy.sh`, or it is not published.
- Update "Contents" in the title block of `index.html` and the sheet numbers of its sections.
14 changes: 14 additions & 0 deletions .claude/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"hooks": {
"SessionStart": [
{
"hooks": [
{
"type": "command",
"command": "n=$(ls \"${CLAUDE_PROJECT_DIR:-.}\"/.review/notes/*.json 2>/dev/null | wc -l); if [ \"$n\" -gt 0 ]; then echo \"$n open review notes in .review/notes/ (skill handling-review-notes)\"; fi; true"
}
]
}
]
}
}
74 changes: 74 additions & 0 deletions .claude/skills/adding-a-thought/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
---
name: adding-a-thought
description: Use when Tim gives a link (a web page, a tweet or X post, a paper, a repository, a video) or an idea and wants to think it through as a new thought on t7d.dev, when he wants to revise an existing thought, change its status, or add feedback that people gave on X or LinkedIn. Also for "new thought", "add this to my thoughts", "write a thought about".
---

# Adding a thought

The thoughts in `thoughts/<slug>/` are Tim's archive of ideas he works through and comes back to. Anyone can read them, but they are mostly for him: each keeps where it started, what it rests on, its status and how it changed. The reference page is `thoughts/humans-md/index.html`.

## 1. Gather the starting point

- Read what Tim gave you. For a web page use WebFetch, or the `web-fetch` agent. X often blocks fetches: try the post's URL, then a reader or embed URL (`https://publish.twitter.com/oembed?url=<post>`), and if that fails, ask Tim to paste the text. Never guess what a post says.
- Note the author, the date and one sentence of what it claims. These go into "Starting points".

## 2. Think it through with Tim before you write

Ask 2 to 4 questions per round with AskUserQuestion, and wait for the answers. Tim wants to iterate on the idea, not get a finished report. Good questions:

- What caught his interest: the claim, the method, or a use for his own work?
- His own practice today. The thought must be honest about it ("Mostly I go by feel" is fine, inventing a habit is not).
- Where he disagrees or is unsure.
- The form of the figures: what should move, what should be switchable.

After a research round, share what you found with one or two more questions before you write.

## 3. Research

- Small: a few WebSearch and WebFetch calls yourself.
- Big, or when Tim asks for it: the `deep-research` workflow, with the refined question as args. While it runs, ask Tim the questions from step 2.
- Check every number and quote against a primary source. Mark what you could only see in a search snippet, and leave it out or paraphrase it. Name who coined a term only with a dated source (for example the first commit).

## 4. Write

The voice is DESIGN.md "Wording": first person, short sentences, plain words, no slogans. A thought is not a report:

- Open with the question or the itch, in Tim's words.
- One section per step of the thinking. Headings name their content.
- Figures carry the ideas that are easier to see than to read. Use `figure.anim` (DESIGN.md "Animated diagram"): a drawing that plays once on screen, a two-state switch, parts to tap, a running token. Draw a narrow version (`.dg-n`) for phones. Use only the `.dg` classes, no colours, no `style` attributes.
- End with what Tim takes from it: a rule, a draft, a file, or open questions.

## 5. The page

Copy `thoughts/humans-md/index.html` and keep its structure:

1. `<head>`: title, description, canonical, `og:*`, `article:published_time` (the start date). The feeds and `llms.txt` are made from it at deploy time.
2. `.post-head`: crumb to `/thoughts/`, `h1`, `.sub`, a label with topics, and `.tmeta`: Status, Started, Last revised.
3. `.toc` with every `h2`.
4. The text, then in this order: Open questions (if any), Starting points, Sources (`ol.refs`, `id="s1"`...), Revisions (`ul.dates`), Feedback.

Status is one of `Draft` (still forming), `Settled` (Tim stands by it) and `Changed my mind`. Set it only as Tim says; a new thought is `Draft`.

Then:

- Add it at the top of the list in `thoughts/index.html` (date, status, title, one sentence).
- Add it as the first `.proj` box in the Thoughts section of `index.html`, with an inline SVG cover. Keep two boxes there; the list holds the rest. Update the count in "Contents" in the title block.
- Add the URL to `sitemap.xml`.
- A file to download (like `HUMANS.md`) goes into the thought's folder.

## 6. Check, let Tim review, ship

Follow CLAUDE.md: serve the site (`python3 -m http.server 8000`), look at 1440, 390 and 320 px in both themes, no sideways scroll, no console errors. Click every switch and pick, and look at the narrow drawings. Commit on the session's branch and push.

Tim reviews a thought as a Claude artifact before it is published: run `python3 tools/preview-artifact.py thoughts/<slug> <scratchpad>/preview-<slug>` and publish its `index.html` with the Artifact tool, with every other printed file in `files`. After a change, build again and publish the same `file_path`, so the link stays the same. Comments on the artifact reach the session: answer them and change the page in the repository, never only in the bundle. `tools/ship.sh <pr>` merges and checks the live site when Tim asks for it.

## Revising a thought

- Change the text, set "Last revised", and add one line at the top of Revisions: the date and what changed, and why ("Changed the third rule after feedback on LinkedIn").
- Change the status only as Tim says.
- Feedback: quote or summarise what people said, with a link to the post, and what it changed. If it changed nothing, say so.
- Keep the old start date in `article:published_time`.

## Sharing a thought

To draft X and LinkedIn posts for a thought and bring the replies back, use the skill `sharing-a-thought` from `Tim-Conrad/Tim-Conrad-Skills`. It records each post and the feedback in the thought's Feedback section, with the revision rules above.
20 changes: 20 additions & 0 deletions .claude/skills/handling-review-notes/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
---
name: handling-review-notes
description: Use when Tim mentions notes or feedback on the review site (the tab »Note«), when the session start says there are open review notes, or when you change tools/review/ or docker-compose.dev.yml.
---

# Handling review notes

Tim writes notes on the review site: the tab »Note« at the right edge. The review site is this clone, served by `tools/review/devserver.py` in the container `t7d-review` (`docker-compose.dev.yml`, host name in `.env` as `REVIEW_HOST`). It shows the checked-out branch at once and loads the page again when a file changes. A note is a JSON file in `.review/notes/`.

1. The SessionStart hook (`.claude/settings.json`) prints the number of open notes in `.review/notes/`. Tell Tim how many are open, and list them with `ls .review/notes/*.json`.
2. Read the note: `note`, `path`, `viewport`, `theme`, `scroll`, `target` (`selector`, `text`, `rect`), `selection`, `branch`, `commit`.
3. Run `uv run --no-project --with playwright python3 tools/review/show.py <id>` (more ids in one call). Look at `.review/notes/<id>.png`. The red frame is the part that Tim pointed at.
4. Do the work with the rules of this file: a branch, `DESIGN.md`, the look at three widths.
5. After the fix, run `show.py <id> --suffix after` and compare the two pictures.
6. Add an `"answer"` field to the note: what changed, and the commit. Move the note to `.review/notes/done/`. Tim sees the answer under »My notes« in the tab.

- Keep notes and their pictures out of git. `.review/` and `.env` are ignored.
- A change to `devserver.py` needs `docker restart t7d-review`. A change to `review.js` or `review.css` needs only a page load.
- The server adds only `connect-src 'self'` to the CSP of a page. Keep it that way, so that the review site keeps the rules of the real site.
- Tests: `python3 -m unittest tools/review/test_devserver.py`.
2 changes: 1 addition & 1 deletion .claude/skills/updating-product-reels/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ Before a run: start the dev server in `../wortzauber` with `python3 devserver.py
`ffmpeg -i <work>/kleinbahn-reel.mp4 -vf "fps=16/39,scale=320:180,tile=4x4" -frames:v 1 sheet.jpg` (16 pictures over a 39 s reel).
Check that each caption sits on its own shot, that no caption covers a speech bubble or a panel, and that the end card is readable.
4. `build.py publish`. The stills come from the sources, not from the reel, so they carry no caption. Which frames they are is at the top of `build.py` (`KB_PICS`, `WZ_PICS`, `WZ_PHONE`). If a shot or a take changed, check that the alt texts in `projects/<game>/index.html` still describe the pictures.
5. Serve the site (`python3 -m http.server 8000` in this repo) and look at `/projects/kleinbahn/`, `/projects/wortzauber/` and `/#projects` at 1440 and 390 px wide. The page must not scroll sideways.
5. Serve the site (`python3 -m http.server 8000` in this repo) and look at `/projects/kleinbahn/`, `/projects/wortzauber/` and `/#projects` at 1440, 390 and 320 px wide, in the light and the dark theme (CLAUDE.md). The page must not scroll sideways.
6. Commit the files in `assets/projects/` with the change.

## Common mistakes
Expand Down
15 changes: 15 additions & 0 deletions .github/workflows/check.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
name: Check the pages

on:
pull_request:

permissions:
contents: read

jobs:
check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: CSP rules and sitemap
run: python3 tools/check.py
2 changes: 2 additions & 0 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Check the pages
run: python3 tools/check.py
- name: Publish the site files to gh-pages
env:
GIT_AUTHOR_NAME: github-actions[bot]
Expand Down
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
__pycache__/
# made by tools/gen-feeds.py at deploy time
/llms.txt
/thoughts/feed.xml
/blog/feed.xml
.review/
.env
.preview/
2 changes: 1 addition & 1 deletion 404.html
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
<a class="brand" href="/">Tim Conrad</a>
<nav class="nav__links" aria-label="Site">
<a href="/#games">Games</a>
<a href="/blog/">Blog</a>
<a href="/thoughts/">Thoughts</a>
<a class="opt3" href="/#research">Research</a>
<a class="opt2" href="/#verify">PGP key</a>
<a class="opt2" href="/#contact">Contact</a>
Expand Down
71 changes: 15 additions & 56 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -1,74 +1,33 @@
# CLAUDE.md

t7d.dev: the personal site of Tim Conrad. Plain HTML, CSS and a little JavaScript, with no build step and no dependencies.
t7d.dev: the personal site of Tim Conrad. Plain HTML, CSS and a little JavaScript, with no build step and no dependencies. `DESIGN.md` says how it looks and sounds; `README.md` holds the deploy and the common changes.

## Read first
## Check your work

- `DESIGN.md` - how the site looks and sounds. Read it before you change a page, a style or any wording.
- `README.md` - what is on the site, the repository layout, the deploy and the common changes.

## Project map

- `index.html`, `404.html` - front page and not-found page.
- `research/`, `projects/`, `blog/`, `CVE/` - one folder with an `index.html` per page. `blog/<slug>/` is one post, `blog/` the list. `CVE/` only redirects an old address.
- `assets/site.css` - all styles and the colour tokens.
- `assets/theme.js`, `assets/site.js` - the only scripts.
- `assets/projects/` - reels, posters and stills of the two games.
- `tools/deploy.sh`, `.github/workflows/deploy.yml` - the deploy to `gh-pages`.
- `/sealed/<site>/` (only on `gh-pages`) - encrypted sites that the sealed admin app (`Tim-Conrad/sealed`) pushes. Their login page uses `assets/site.css` (`.sealed`).
- `tools/reels/` - the scripts that film the games and cut the reels, and `timeline.mjs` with its stages file, which films the timeline clips.
- `assets/projects/timeline/` - the timeline clips and posters of the game pages.
- `python3 tools/check.py`: the Content-Security-Policy rules and the sitemap of every page. CI runs it on pull requests and before each deploy.
- After a change to a page, a style or any wording: serve the site (`python3 -m http.server 8000` in the repository root; all paths are absolute) and look at it at 1440, 390 and 320 px wide, in the light and the dark theme. The page must not scroll sideways, and the browser console must show no errors.
- Show what you ran and what you saw, not only "it works".

## Rules for every task

- Follow `DESIGN.md`. The two rules that matter most: the site is black and white with colour only in pictures and videos, and the wording is plain with no slogans or overstatement.
- Read `DESIGN.md` before you change a page, a style or any wording. The two rules that matter most: the site is black and white with colour only in pictures and videos, and the wording is plain with no slogans or overstatement.
- Claim only what is true. Do not invent facts about Tim, the research or the games.
- Do not rewrite the text of the write-up under `research/`. It is Tim's own.
- A new blog post goes into `blog/<slug>/`, at the top of the list in `blog/index.html` and of the Blog section of `index.html`, and into `sitemap.xml`. Copy the `<head>` of the existing post, with its description and `article:published_time`: `llms.txt` and the RSS feed are made from them at deploy time (`tools/gen-feeds.py`). Do not commit those two files.
- Keep the Content-Security-Policy of each page strict: no inline scripts or styles, no `style=""` attributes, and no request to another site on page load.
- `master` is the source. Never edit `gh-pages` by hand: the workflow publishes it after every push to `master`, and the sealed admin app publishes `sealed/` on it. `tools/deploy.sh` must keep `sealed/`.
- A new or revised thought: use the skill `adding-a-thought`. `llms.txt` and the feeds are made at deploy time (`tools/gen-feeds.py`); do not commit them.
- Keep the Content-Security-Policy of each page strict: no inline scripts or styles, no `style=""` attributes, and no request to another site on page load. `assets/theme.js` and `assets/site.js` are the only scripts.
- `master` is the source. Never edit `gh-pages` by hand: the workflow publishes it after every push to `master`, and the sealed admin app (`Tim-Conrad/sealed`) publishes encrypted sites to `sealed/` on it, a folder that exists only there. `tools/deploy.sh` must keep `sealed/`.
- Classes and scripts that the sealed login page uses (`.sealed`, `.label`, `.btn`, `.nav`, `#theme-toggle`, `/assets/site.js`) are used by another repository too. Keep them, or change `client/themes/t7d/login.html` in sealed with them.
- `blog/` and `CVE/` only redirect old addresses (the thoughts were a blog until November 2026). Do not add pages there.

<important if="you change a page, a style, a component or any wording">
Read `DESIGN.md` first. After the change, serve the site and look at it at 1440, 390 and 320 px wide, in the light and the dark theme. The page must not scroll sideways, and the browser console must show no errors.
</important>

<important if="you need to preview or check the site">
Run `python3 -m http.server 8000` in the repository root and open `http://localhost:8000`. All paths are absolute, so serve from the root.
</important>

<important if="Tim mentions notes or feedback on the site, .review/notes/ has files, or you change tools/review/ or docker-compose.dev.yml">
Tim writes notes on the review site: the tab »Note« at the right edge. The review site is this clone, served by `tools/review/devserver.py` in the container `t7d-review` (`docker-compose.dev.yml`, host name in `.env` as `REVIEW_HOST`). It shows the checked-out branch at once and loads the page again when a file changes. A note is a JSON file in `.review/notes/`.

1. At the start of a session, list `.review/notes/*.json`. Tell Tim how many notes are open.
2. Read the note: `note`, `path`, `viewport`, `theme`, `scroll`, `target` (`selector`, `text`, `rect`), `selection`, `branch`, `commit`.
3. Run `uv run --no-project --with playwright python3 tools/review/show.py <id>` (more ids in one call). Look at `.review/notes/<id>.png`. The red frame is the part that Tim pointed at.
4. Do the work with the rules of this file: a branch, `DESIGN.md`, the look at three widths.
5. After the fix, run `show.py <id> --suffix after` and compare the two pictures.
6. Add an `"answer"` field to the note: what changed, and the commit. Move the note to `.review/notes/done/`. Tim sees the answer under »My notes« in the tab.

- Keep notes and their pictures out of git. `.review/` and `.env` are ignored.
- A change to `devserver.py` needs `docker restart t7d-review`. A change to `review.js` or `review.css` needs only a page load.
- The server adds only `connect-src 'self'` to the CSP of a page. Keep it that way, so that the review site keeps the rules of the real site.
- Tests: `python3 -m unittest tools/review/test_devserver.py`.
</important>

<important if="you add a page, or a top-level file or folder that belongs to the site">
- Copy the `<head>` of an existing page, with its Content-Security-Policy line.
- Add the URL to `sitemap.xml`.
- Add a new top-level file or folder to the `SITE` list in `tools/deploy.sh`, or it is not published.
- Update "Contents" in the title block of `index.html` and the sheet numbers of its sections.
</important>

<important if="Kleinbahn or Wortzauber changed, or a reel, a poster, a still or a caption must change">
Use the skill `updating-product-reels`. It needs the two game repositories (`Tim-Conrad/tiny-train` and `Tim-Conrad/wortzauber`) cloned next to this one.
<important if="you show Tim a change for review">
Show a changed page as a Claude artifact, not a local server: `python3 tools/preview-artifact.py <page folder> <out dir>` builds the bundle; publish `<out dir>/index.html` with the Artifact tool and pass the other printed files in `files`. Republish the same `file_path` after each change, so the link stays. The review site (skill `handling-review-notes`) is where Tim leaves notes on his own.
</important>

<important if="a game got new merged pull requests, or its timeline ("How it changed") must grow or a timeline clip must be filmed again">
Use the skill `updating-game-timeline`. It films one clip per PR at that PR's merge commit and needs the game repository cloned next to this one, with its full history.
<important if="Tim mentions notes or feedback on the review site, or you change tools/review/ or docker-compose.dev.yml">
Use the skill `handling-review-notes`. A hook prints the number of open notes at the start of a session.
</important>

<important if="you deploy, or the live site does not show a change">
See "Deploy" in `README.md`. GitHub Pages serves the `gh-pages` branch at https://t7d.dev.
To merge a PR and see it live, run `tools/ship.sh <pr>` after the checks of the change. If it stops, read its last line, fix the cause, and run it again: a merged PR skips the merge.
When Tim asks to ship a PR, run `tools/ship.sh <pr>` after the checks of the change. Merging publishes to t7d.dev, so do not run it unasked. If it stops, read its last line, fix the cause, and run it again: a merged PR skips the merge.
</important>
Loading
Loading