🤖 ai transparency

This commit is contained in:
mi
2025-11-15 08:51:57 +10:00
parent 8c6481c48c
commit b8abcd5566
2 changed files with 275 additions and 0 deletions

228
CLAUDE.md Normal file
View File

@@ -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/<id>` - 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/<id>` - 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/<id>`
- 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: `<header>`, `<nav>`, `<main>`, `<footer>`
- `tabindex="-1"` on programmatically focusable elements (not in tab order)
### CSS Accessibility
- `.sr-only` class: Hides content visually but keeps it accessible to screen readers
- `.skip-to-main` class: Positioned off-screen until focused, then slides into view
- Focus styles use `outline` property (never remove without replacement)
- Disabled buttons use both color and opacity for visibility
### JavaScript Accessibility
The `comic-nav.js` file handles:
1. **Announcements**: Updates `#comic-announcer` text content on navigation
2. **Focus**: Calls `.focus()` on `#comic-image-focus` after loading comic
3. **ARIA attributes**: Dynamically adds/removes `aria-disabled` on navigation buttons
4. **Boundary feedback**: Announces "Already at first/latest comic" at navigation limits
### Maintaining Accessibility
When adding features:
- Ensure all images have meaningful alt text
- Test with keyboard only (no mouse)
- Verify focus indicators are visible
- Check ARIA labels on icon buttons
- Test with screen readers when possible (VoiceOver, NVDA)
- Maintain semantic HTML structure
## Testing Approach
No test suite currently exists. When adding tests, consider:
- Comic retrieval and enrichment logic
- API endpoint responses
- Date formatting edge cases
- Markdown rendering for author notes and about page
- Accessibility: keyboard navigation, ARIA attributes, focus management

View File

@@ -46,6 +46,7 @@ A Flask-based webcomic website with server-side rendering using Jinja2 templates
- [Pages](#pages) - [Pages](#pages)
- [API Endpoints](#api-endpoints) - [API Endpoints](#api-endpoints)
- [Credits](#credits) - [Credits](#credits)
- [AI Usage Transparency](#ai-usage-transparency)
- [License & Content Ownership](#license--content-ownership) - [License & Content Ownership](#license--content-ownership)
## What is This? ## What is This?
@@ -636,6 +637,52 @@ The app exposes a JSON API for programmatic access:
- Favicon generated using [favicon.io](https://favicon.io) - Favicon generated using [favicon.io](https://favicon.io)
- Example comics sourced from Boots and Her Buddies comic strip at [Comic Book Plus](https://comicbookplus.com/?cid=2561) - Example comics sourced from Boots and Her Buddies comic strip at [Comic Book Plus](https://comicbookplus.com/?cid=2561)
## AI Usage Transparency
> I want it to be clear that this section was written by a human!
This project has been built using Claude Code - an AI-assisted development tool. Considering several factors, I feel the
need to be transparent about this.
**My position on AI:**
I **do not** condone the use of AI in regard to artistic expression, communication, or as a replacement
real human beings. After all, _what is the point of being alive if we leave it all to machines?_
All artwork you see produced as assets here or in any of my other projects are made by real human beings and will
remain that way. Images generated from AI are an infringement on copyright and intellectual property. I fully believe
that the sourcing of data for models should be regulated. I also believe that human creative workers should be celebrated
and well compensated.
Being a text-only platform, my choice in Claude is their claim to be an "ethical" AI provider. Of course, we can trust
these vendors only so much. Should they act as advertised, there are also environmental considerations. My hope is that
this will improve over time. As with data sourcing, I also hope this will be regulated.
**How AI is used:**
This project's code is written primarily by AI. The application itself *does not* and *will never* have
any AI integration. When you start up the project, it is completely standalone just like any normal website. Your
content remains yours.
**So, why use AI?**
I am interested in illustrating and making comics, not so much spending my time writing code. AI is a means to an end.
Its use here is purely to produce a convenient platform to be used for artwork created by real people.
Should you choose to adopt this, the work you publish remains yours, as it should always be.
For the most part, I am comfortable with its use in technical matters. You can see that with the code on this project.
This is a sentiment I generally share with the software development industry which has naturally embraced the new
technology.
**Why bother with transparency?**
As controversial as the technology is, I believe in being transparent about its use. If you happen to like the look of
Sunday and want to use that, you should know just in case you are fully anti-AI. This is a position I completely respect.
If you have any concerns or are outright put off, I highly recommend looking at [Rarebit](https://rarebit.neocities.org/)
which is completely written by humans (and easier to get into if your technical knowledge is limited).
## License & Content Ownership ## License & Content Ownership
This software is licensed under the **MIT License** - see the [LICENSE](LICENSE) file for details. This software is licensed under the **MIT License** - see the [LICENSE](LICENSE) file for details.