# Sunday Comics - Webcomic Flask App A Flask-based webcomic website with server-side rendering using Jinja2 templates. ## Features - Comic viewer with navigation (First, Previous, Next, Latest) - Client-side navigation using JSON API (no page reloads) - Archive page with thumbnail grid - RSS feed support - Markdown support for author notes and about page - Optional icon-based navigation (comic navigation, header, and social links) - Configurable logo and header/footer images - Mobile-responsive with optional mobile-specific comic images - Full-width and plain (headerless) display modes - JSON API for programmatic access - Open Graph and Twitter Card metadata for social sharing - Server-side rendering with Jinja2 ## Project Structure ``` sunday/ ├── app.py # Main Flask application ├── comics_data.py # Comic data and configuration ├── requirements.txt # Python dependencies ├── Dockerfile # Production Docker image ├── docker-compose.yml # Docker Compose configuration ├── .dockerignore # Docker build exclusions ├── scripts/ # Utility scripts │ ├── add_comic.py # Script to add new comic entries │ └── generate_rss.py # Script to generate RSS feed ├── content/ # Markdown content │ ├── about.md # About page content │ └── author_notes/ # Author notes for comics (by date) ├── templates/ # Jinja2 templates │ ├── base.html # Base template with navigation │ ├── index.html # Latest comic page │ ├── comic.html # Individual comic viewer │ ├── archive.html # Archive grid │ ├── page.html # Generic markdown page template │ └── 404.html # Error page └── static/ # Static files ├── css/ │ └── style.css # Main stylesheet ├── js/ │ └── comic-nav.js # Client-side navigation ├── images/ # Image directory │ ├── comics/ # Comic images │ ├── thumbs/ # Thumbnail images for archive │ └── icons/ # Navigation and social icons (optional) └── feed.rss # RSS feed (generated) ``` ## Setup 1. Install dependencies: ```bash pip install -r requirements.txt ``` 2. Run the application: ```bash python app.py ``` 3. Visit http://127.0.0.1:3000 in your browser ## Environment Variables The app can be configured via environment variables: - `SECRET_KEY` - Flask secret key (defaults to 'your-secret-key') - `PORT` - Port to run on (defaults to 3000) - `DEBUG` - Enable debug mode (defaults to False) **Development:** ```bash export DEBUG=True python app.py ``` **Production:** ```bash export SECRET_KEY="your-secure-random-secret-key" export PORT=3000 python app.py ``` ## Configuration The `comics_data.py` file contains both comic data and global configuration options: ### Global Settings ```python COMIC_NAME = 'Sunday Comics' # Your comic/website name SITE_URL = 'http://localhost:3000' # Your domain (update for production) FULL_WIDTH_DEFAULT = False # Make all comics full-width by default PLAIN_DEFAULT = False # Hide header/remove borders by default LOGO_IMAGE = 'logo.png' # Path to logo (relative to static/images/) LOGO_MODE = 'beside' # 'beside' or 'replace' title with logo HEADER_IMAGE = None # Optional header image path FOOTER_IMAGE = None # Optional footer image path COMPACT_FOOTER = False # Display footer in compact mode ARCHIVE_FULL_WIDTH = True # Full-width archive with 4 columns USE_COMIC_NAV_ICONS = True # Use icons for comic navigation buttons USE_HEADER_NAV_ICONS = True # Show icons in main header navigation USE_FOOTER_SOCIAL_ICONS = True # Use icons for social links SOCIAL_INSTAGRAM = None # Instagram URL (or None) SOCIAL_YOUTUBE = None # YouTube URL (or None) SOCIAL_EMAIL = None # Email mailto link (or None) API_SPEC_LINK = None # API documentation link (or None) ``` ## Adding Comics Comics are stored in the `COMICS` list in `comics_data.py`. Each comic entry: ```python { 'number': 1, # Comic number (required, sequential) 'filename': 'comic-001.png', # Image filename (required) 'mobile_filename': 'comic-001-mobile.png', # Optional mobile version 'date': '2025-01-01', # Publication date (required) 'alt_text': 'Alt text for comic', # Accessibility text (required) 'title': 'Comic Title', # Title (optional, shows #X if absent) 'author_note': 'Optional note', # Author note (optional, plain text) 'full_width': True, # Optional: override FULL_WIDTH_DEFAULT 'plain': True # Optional: override PLAIN_DEFAULT } ``` ### Adding a New Comic **Option 1: Use the script (recommended)** ```bash # Add comic entry only python scripts/add_comic.py # Add comic entry AND create markdown file for author notes python scripts/add_comic.py -m ``` This will automatically add a new entry with defaults. The `-m` flag creates a markdown file in `content/author_notes/{date}.md` with a template. Then edit `comics_data.py` to customize. **Option 2: Manual** 1. Save your comic image in `static/images/comics/` (e.g., `comic-001.png`) 2. Optionally, create a thumbnail in `static/images/thumbs/` with the same filename 3. Add the comic entry to the `COMICS` list in `comics_data.py` ### Generating RSS Feed After adding comics, regenerate the RSS feed: ```bash python scripts/generate_rss.py ``` This creates/updates `static/feed.rss` ### Markdown Support **Author Notes:** Create markdown files in `content/author_notes/{date}.md` (e.g., `2025-01-01.md`) for rich-formatted author notes. Markdown author notes take precedence over the `author_note` field in `comics_data.py` and render as HTML. Example: ```bash # Create author note for a specific comic echo "# My First Comic\n\nThis is **bold** text!" > content/author_notes/2025-01-01.md ``` **About Page:** The `/about` route renders `content/about.md` as HTML. Edit this file to customize your about page with markdown formatting. **Note:** Client-side navigation displays author notes as plain text. Markdown author notes only render properly on initial page load (server-side rendering). For full markdown rendering, users need to refresh the page or navigate directly to the comic URL. ## Production Deployment For production, you should **NOT** use Flask's built-in development server. Choose one of the following deployment methods: ### Option 1: Docker (Recommended) **1. Generate a secure secret key:** ```bash python -c "import secrets; print(secrets.token_hex(32))" ``` **2. Create a `.env` file:** ```bash SECRET_KEY=your-generated-secret-key-here ``` **3. Build and run with Docker Compose:** ```bash docker-compose up -d ``` **Or build and run manually:** ```bash # Build the image docker build -t sunday-comics . # Run the container docker run -d \ -p 3000:3000 \ -e SECRET_KEY="your-secret-key" \ -v $(pwd)/comics_data.py:/app/comics_data.py:ro \ -v $(pwd)/static/images:/app/static/images:ro \ --name sunday-comics \ sunday-comics ``` **View logs:** ```bash docker-compose logs -f ``` ### Option 2: Manual Deployment with Gunicorn **1. Generate a Secure Secret Key** ```bash python -c "import secrets; print(secrets.token_hex(32))" ``` **2. Set Environment Variables** ```bash export SECRET_KEY="generated-secret-key-from-above" export DEBUG=False export PORT=3000 ``` **3. Use a Production WSGI Server** **Install Gunicorn:** ```bash pip install gunicorn ``` **Run with Gunicorn:** ```bash gunicorn app:app --bind 0.0.0.0:3000 --workers 4 ``` ### Using a Reverse Proxy (Recommended) Set up Nginx or another reverse proxy in front of your app for: - HTTPS/SSL termination - Static file serving - Load balancing - Better security ### Additional Production Considerations - Use a process manager (systemd, supervisor) for non-Docker deployments - Set appropriate file permissions - Enable HTTPS with Let's Encrypt - Consider using a CDN for static assets - Monitor logs and performance - Set up automated backups of `comics_data.py` ## Upgrading to a Database For larger comic archives, consider replacing the `COMICS` list with a database: - SQLite for simple setups - PostgreSQL/MySQL for production - Use Flask-SQLAlchemy for ORM support ## Customization ### Branding - Update `COMIC_NAME` in `comics_data.py` to change your comic's name - Update `SITE_URL` in `comics_data.py` for production deployment - Customize logo by setting `LOGO_IMAGE` and `LOGO_MODE` ### Styling - Modify `static/css/style.css` to change colors, fonts, and layout - Current color scheme uses blue (#3498db) and dark blue-gray (#2c3e50) ### About Page - Edit `content/about.md` to add your bio and comic information (supports markdown) ### Icon Navigation To use icon navigation: 1. Set `USE_COMIC_NAV_ICONS = True` in `comics_data.py` 2. Add icons to `static/images/icons/`: - Comic navigation: `first.png`, `previous.png`, `next.png`, `latest.png` - Header navigation: `alert.png`, `archive.png`, `info.png` - Social links: `instagram.png`, `youtube.png`, `mail.png` ### Social Links Configure social media links in `comics_data.py`: ```python SOCIAL_INSTAGRAM = 'https://instagram.com/yourhandle' SOCIAL_YOUTUBE = 'https://youtube.com/@yourchannel' SOCIAL_EMAIL = 'mailto:your@email.com' ``` ## Pages - `/` - Shows the latest comic - `/comic/` - View a specific comic by number - `/archive` - Browse all comics in a grid - `/about` - About the comic and author - `/static/feed.rss` - RSS feed ## API Endpoints The app exposes a JSON API for programmatic access: - **GET `/api/comics`** - Returns all comics as a JSON array ```json [ { "number": 1, "title": "First Comic", "filename": "comic-001.png", "date": "2025-01-01", "alt_text": "The very first comic", "author_note": "This is where your comic journey begins!" } ] ``` - **GET `/api/comics/`** - Returns a specific comic as JSON ```json { "number": 1, "title": "First Comic", "filename": "comic-001.png", "date": "2025-01-01", "alt_text": "The very first comic", "author_note": "This is where your comic journey begins!" } ``` Returns 404 with `{"error": "Comic not found"}` if the comic doesn't exist. ## Credits - Inspired by [Rarebit](https://rarebit.neocities.org/) - 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) ## License [Add your license here]