From b8abcd55663ec1c0be89a77f7580567b4e95661b Mon Sep 17 00:00:00 2001 From: mi Date: Sat, 15 Nov 2025 08:51:57 +1000 Subject: [PATCH] :robot: ai transparency --- CLAUDE.md | 228 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ README.md | 47 +++++++++++ 2 files changed, 275 insertions(+) create mode 100644 CLAUDE.md diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..e6fe659 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,228 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Project Overview + +Sunday Comics is a Flask-based webcomic website with server-side rendering and client-side navigation. Comics are stored as simple Python dictionaries in `comics_data.py`, making the system easy to manage without a database. + +## Development Commands + +**Run the development server:** +```bash +python app.py +``` +Server runs on http://127.0.0.1:3000 by default. + +**Enable debug mode:** +```bash +export DEBUG=True +python app.py +``` + +**Add a new comic:** +```bash +python scripts/add_comic.py +``` +This creates a new entry in `comics_data.py` with defaults. Edit the file afterwards to customize title, alt_text, author_note, etc. + +**Generate RSS feed:** +```bash +python scripts/generate_rss.py +``` +Run this after adding/updating comics to regenerate `static/feed.rss`. + +## Architecture + +### Data Layer: comics_data.py +Comics are stored as a Python list called `COMICS`. Each comic is a dictionary with: +- `number` (required): Sequential comic number +- `filename` (required): Image filename in `static/images/comics/` +- `date` (required): Publication date in YYYY-MM-DD format +- `alt_text` (required): Accessibility text +- `title` (optional): Comic title (defaults to "#X" if absent) +- `author_note` (optional): Plain text note +- `author_note_md` (optional): Markdown file for author note (just filename like "2025-01-01.md" looks in `content/author_notes/`, or path like "special/note.md" relative to `content/`). Takes precedence over `author_note`. +- `full_width` (optional): Override global FULL_WIDTH_DEFAULT setting +- `plain` (optional): Override global PLAIN_DEFAULT setting (hides header/border) +- `section` (optional): Section/chapter title (e.g., "Chapter 1: Origins"). Add to first comic of a new section. + +Global configuration in `comics_data.py`: +- `COMIC_NAME`: Your comic/website name +- `COPYRIGHT_NAME`: Name to display in copyright notice (defaults to COMIC_NAME if not set) +- `FULL_WIDTH_DEFAULT`: Set to True to make all comics full-width by default +- `PLAIN_DEFAULT`: Set to True to hide header/remove borders by default +- `ARCHIVE_FULL_WIDTH`: Set to True to make archive page full-width with 4 columns +- `SECTIONS_ENABLED`: Set to True to enable section headers on archive page (uses `section` field from comics) +- `HEADER_IMAGE`: Path relative to `static/images/` for site header image (set to None to disable) +- `FOOTER_IMAGE`: Path relative to `static/images/` for site footer image (set to None to disable) +- `BANNER_IMAGE`: Path relative to `static/images/` for shareable banner (set to None to hide "Link to Us" section) +- `COMPACT_FOOTER`: Display footer in compact single-line mode +- `USE_COMIC_NAV_ICONS`: Set to True to use icon images for comic navigation buttons instead of text (requires icons in `static/images/icons/`) +- `USE_HEADER_NAV_ICONS`: Set to True to display icons next to main header navigation text (uses alert.png, archive.png, info.png) +- `USE_FOOTER_SOCIAL_ICONS`: Set to True to use icons instead of text for footer social links (uses instagram.png, youtube.png, mail.png, alert.png) +- `SOCIAL_INSTAGRAM`: Instagram URL (set to None to hide) +- `SOCIAL_YOUTUBE`: YouTube URL (set to None to hide) +- `SOCIAL_EMAIL`: Email mailto link (set to None to hide) + +### Markdown Support + +**Author Notes:** Add `author_note_md` field to comic entries with a filename (e.g., `"2025-01-01.md"`) or path relative to `content/` (e.g., `"special/note.md"`). Just a filename looks in `content/author_notes/`. The markdown content is rendered as HTML and takes precedence over the plain text `author_note` field. + +**About Page:** The `/about` route renders `content/about.md` as HTML using the markdown library. + +### Flask Application: app.py + +**Core Functions:** +- `enrich_comic(comic)`: Adds computed properties (full_width, plain, formatted_date, author_note from markdown) +- `get_comic_by_number(number)`: Retrieves and enriches a comic by number +- `get_latest_comic()`: Returns the last comic in the COMICS list +- `format_comic_date(date_str)`: Converts YYYY-MM-DD to "Day name, Month name day, year" +- `get_author_note_from_file(filename)`: Loads markdown author note from file. Filename alone looks in `content/author_notes/`, paths are relative to `content/`. +- `group_comics_by_section(comics_list)`: Groups comics by section based on `section` field. Returns list of (section_title, comics) tuples. + +**Routes:** +- `/` - Latest comic (index.html) +- `/comic/` - Specific comic viewer (comic.html) +- `/archive` - Grid of all comics, newest first (archive.html) +- `/about` - Renders markdown from content/about.md (page.html) +- `/api/comics` - JSON array of all comics +- `/api/comics/` - JSON for a specific comic + +### Client-Side Navigation: static/js/comic-nav.js + +Provides SPA-like navigation without page reloads: +- Fetches comics from `/api/comics/` +- Updates DOM with `displayComic(comic)` function +- Handles navigation buttons and image click-through +- Uses History API to maintain proper URLs and browser back/forward +- Shows/hides header based on plain mode +- Adjusts container for full_width mode +- Updates author notes dynamically (plain text only; markdown rendering requires page reload) + +**Note:** Client-side navigation displays author notes as plain text. Markdown author notes only render properly on initial page load (server-side). + +### Templates + +Built with Jinja2, extending `base.html`: +- `base.html`: Contains navigation header, footer, metadata tags (Open Graph, Twitter Cards) +- `index.html` & `comic.html`: Display comics with navigation buttons +- `archive.html`: Grid layout with thumbnails from `static/images/thumbs/` +- `page.html`: Generic template for markdown content (used by /about) + +Global context variables injected into all templates: +- `comic_name`: Site/comic name from `comics_data.py` +- `copyright_name`: Copyright name from `comics_data.py` (defaults to `comic_name` if not set) +- `current_year`: Current year (auto-generated from system time) +- `header_image`: Site header image path from `comics_data.py` +- `footer_image`: Site footer image path from `comics_data.py` +- `banner_image`: Shareable banner image path from `comics_data.py` +- `compact_footer`: Boolean for footer style from `comics_data.py` +- `use_comic_nav_icons`: Boolean for comic navigation icons from `comics_data.py` +- `use_header_nav_icons`: Boolean for main header navigation icons from `comics_data.py` +- `use_footer_social_icons`: Boolean for footer social link icons from `comics_data.py` +- `social_instagram`: Instagram URL from `comics_data.py` +- `social_youtube`: YouTube URL from `comics_data.py` +- `social_email`: Email link from `comics_data.py` + +## Static Assets + +- `static/images/comics/`: Full-size comic images +- `static/images/thumbs/`: Thumbnails for archive page (optional, same filename as comic) +- `static/images/icons/`: Navigation icons (first.png, previous.png, next.png, latest.png) used when `USE_ICON_NAV` is True +- `static/images/`: Header images and other site graphics +- `static/feed.rss`: Generated RSS feed (run `scripts/generate_rss.py`) + +## Important Implementation Details + +1. **Comic ordering**: COMICS list order determines comic sequence. Last item is the "latest" comic. + +2. **Enrichment pattern**: Always use `enrich_comic()` before passing comics to templates or APIs. This adds computed properties like `full_width`, `plain`, and `formatted_date`. + +3. **Date formatting**: The `format_comic_date()` function uses `%d` with lstrip('0') for cross-platform compatibility (not all systems support `%-d`). + +4. **Author notes hierarchy**: If `author_note_md` field is specified, the markdown file is loaded and rendered as HTML, taking precedence over the plain text `author_note` field. When markdown is used, `author_note_is_html` is set to True. + +5. **Settings cascade**: Global settings (FULL_WIDTH_DEFAULT, PLAIN_DEFAULT) apply unless overridden per-comic with `full_width` or `plain` keys. + +6. **Navigation state**: Client-side navigation reads `data-total-comics` and `data-comic-number` from the `.comic-container` element to manage button states. + +7. **Comic icon navigation**: When `USE_COMIC_NAV_ICONS` is True, templates use `.btn-icon-nav` class with icon images instead of text buttons. JavaScript automatically detects icon mode and applies appropriate classes. Disabled icons have reduced opacity (0.3). + +8. **Archive sections**: When `SECTIONS_ENABLED` is True, comics with a `section` field will start a new section on the archive page. Only add the `section` field to the first comic of each new section. All subsequent comics belong to that section until a new `section` field is encountered. + +## Production Deployment + +The app uses Flask's development server by default. For production: + +**Recommended: Docker** +```bash +docker-compose up -d +``` + +**Alternative: Gunicorn** +```bash +pip install gunicorn +export SECRET_KEY="$(python -c 'import secrets; print(secrets.token_hex(32))')" +export DEBUG=False +gunicorn app:app --bind 0.0.0.0:3000 --workers 4 +``` + +Environment variables: +- `SECRET_KEY`: Flask secret key (generate with `secrets.token_hex(32)`) +- `PORT`: Server port (default: 3000) +- `DEBUG`: Debug mode (default: False) + +## Accessibility Implementation + +Sunday Comics follows WCAG 2.1 Level AA guidelines. When modifying the site, maintain these accessibility features: + +### Keyboard Navigation +- **Focus indicators**: All interactive elements have visible 3px outlines when focused (defined in `static/css/style.css`) +- **Skip to main content**: First focusable element on every page, appears at top when focused +- **Keyboard shortcuts**: Arrow keys (Left/Right), Home, End for comic navigation (handled in `static/js/comic-nav.js`) +- **Focus management**: After navigation, focus programmatically moves to `#comic-image-focus` element + +### Screen Reader Support +- **ARIA live region**: `#comic-announcer` element announces comic changes with `aria-live="polite"` +- **ARIA labels**: All icon buttons have descriptive `aria-label` attributes +- **ARIA disabled**: Disabled navigation buttons include `aria-disabled="true"` +- **Empty alt text**: Decorative icons (next to text labels) use `alt=""` to prevent redundant announcements +- **Clickable images**: Links wrapping comic images have `aria-label="Click to view next comic"` + +### Semantic HTML +- Proper heading hierarchy (h1 → h2 → h3) +- `lang="en"` on html element +- Semantic elements: `
`, `