🤖 ai transparency
This commit is contained in:
228
CLAUDE.md
Normal file
228
CLAUDE.md
Normal 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
|
||||||
47
README.md
47
README.md
@@ -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.
|
||||||
|
|||||||
Reference in New Issue
Block a user