Compare commits

...

7 Commits

Author SHA1 Message Date
mi
ecbc75e447 📝 license 2025-11-14 15:37:41 +10:00
mi
a79f80a2ea 📝 hosting options 2025-11-14 15:30:11 +10:00
mi
6f1a986661 📝 Rarebit comparison 2025-11-14 15:27:15 +10:00
mi
9f7416427e 📝 introduction section 2025-11-14 15:22:10 +10:00
mi
103c283c61 📝 update all the things 2025-11-14 15:17:15 +10:00
mi
0e42cc3f33 🐛 fix content loading for comic 2025-11-14 15:10:50 +10:00
mi
0fb120c54f 🐛 fix content loading for comic nav 2025-11-14 15:07:04 +10:00
7 changed files with 236 additions and 18 deletions

21
LICENSE Normal file
View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2025 Tomasita Cabrera
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

200
README.md
View File

@@ -2,24 +2,120 @@
A Flask-based webcomic website with server-side rendering using Jinja2 templates. A Flask-based webcomic website with server-side rendering using Jinja2 templates.
## What is This?
**Sunday Comics** is a simple, ready-to-use website for publishing your webcomic online. If you're an artist or comic creator who wants to share your work on the web without dealing with complex platforms or databases, this is for you.
**What you get:**
- A clean, professional website to display your comics
- Easy navigation for readers (first, previous, next, latest buttons)
- An archive page where readers can browse all your comics
- RSS feed so readers can subscribe to updates
- Mobile-friendly design that works on phones and tablets
- No database required - just upload images and edit a simple text file
**Perfect for:**
- Independent comic artists starting their first webcomic
- Artists who want full control over their comic's presentation
- Creators who prefer simple file-based management over databases
- Anyone looking for a lightweight, customizable comic platform
**How it works:**
You add your comics by uploading image files and adding basic information (title, date, description) to a configuration file. The website handles everything else - displaying comics with navigation, creating an archive, generating an RSS feed, and making your comics shareable on social media.
No coding knowledge required for basic use - just follow the instructions below to add comics and customize your site's appearance.
## How Does This Compare to Rarebit?
[Rarebit](https://rarebit.neocities.org/) is an excellent static webcomic template that inspired this project. Sunday Comics offers a different approach with its own strengths:
**Sunday Comics:**
- Server-side application (Flask/Python) that runs on a web server
- Comics stored in a Python file - edit text to add comics
- Includes an RSS feed generator and helper scripts
- API endpoints for programmatic access
- Markdown support for rich-formatted content
- More flexibility for custom features and integrations
- Requires Python installation and basic server setup
**Rarebit:**
- Purely static HTML/CSS/JavaScript files
- Comics are inferred from static images upload - edit a JS to customize
- Can be hosted for free on GitHub Pages, Neocities, etc.
- No server or programming language required
- Simpler deployment - just upload files
**Which should you choose?**
- Choose **Rarebit** if you want the simplest possible setup and free hosting
- Choose **Sunday Comics** if you want server-side features, plan to add custom functionality, or prefer working with Python
- Both are great options - it depends on your technical comfort level and hosting preferences
Sunday Comics is meant as another option in the webcomic toolkit, not a replacement for Rarebit. Many creators might even use both for different projects!
## Simple Hosting Options
Don't have a server? No problem! Here are beginner-friendly options to get your comic online:
### PythonAnywhere (Recommended for Beginners)
**Best for:** First-time deployments, testing your comic site
- **Free tier available** (with pythonanywhere.com subdomain)
- **No credit card required** for free tier
- **Setup:** Upload your files through their web interface, configure a few settings
- **Great beginner tutorials** available on their site
- Visit: [pythonanywhere.com](https://www.pythonanywhere.com)
### Render
**Best for:** Automatic deployments from GitHub
- **Free tier available** (site sleeps after inactivity)
- **Setup:** Connect your GitHub repository, Render deploys automatically
- **No server management needed** - just push code to GitHub
- Custom domain support on free tier
- Visit: [render.com](https://render.com)
### Railway
**Best for:** Quick deployment with modern interface
- **Free trial credits** included ($5/month after)
- **Setup:** Connect GitHub, deploy with one click
- **Automatic deployments** when you update your code
- Simple dashboard to monitor your site
- Visit: [railway.app](https://railway.app)
### DigitalOcean App Platform
**Best for:** Scaling as your audience grows
- **$5/month** for basic tier (no free tier)
- **Very reliable** for growing comics with traffic
- **Automatic scaling** handles traffic spikes
- One-click deploy from GitHub
- Visit: [digitalocean.com/products/app-platform](https://www.digitalocean.com/products/app-platform)
### What You'll Need:
1. Your comic files and images
2. A GitHub account (free) for most modern hosting platforms
3. Basic familiarity with uploading files or pushing to GitHub
**Complete beginner?** Start with **PythonAnywhere** - it has the gentlest learning curve and requires no GitHub knowledge. You can always migrate to other platforms later as you get more comfortable.
## Features ## Features
- Comic viewer with navigation (First, Previous, Next, Latest) - Comic viewer with navigation (First, Previous, Next, Latest)
- Client-side navigation using JSON API (no page reloads) - Client-side navigation using JSON API (no page reloads)
- Archive page with thumbnail grid - Archive page with thumbnail grid
- RSS feed support - 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 - JSON API for programmatic access
- Open Graph and Twitter Card metadata for social sharing - Open Graph and Twitter Card metadata for social sharing
- Responsive design
- Server-side rendering with Jinja2 - Server-side rendering with Jinja2
- Clean, comic-focused layout
## Project Structure ## Project Structure
``` ```
sunday/ sunday/
├── app.py # Main Flask application ├── app.py # Main Flask application
├── comics_data.py # Comic data (edit this to add comics) ├── comics_data.py # Comic data and configuration
├── requirements.txt # Python dependencies ├── requirements.txt # Python dependencies
├── Dockerfile # Production Docker image ├── Dockerfile # Production Docker image
├── docker-compose.yml # Docker Compose configuration ├── docker-compose.yml # Docker Compose configuration
@@ -27,12 +123,15 @@ sunday/
├── scripts/ # Utility scripts ├── scripts/ # Utility scripts
│ ├── add_comic.py # Script to add new comic entries │ ├── add_comic.py # Script to add new comic entries
│ └── generate_rss.py # Script to generate RSS feed │ └── 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 ├── templates/ # Jinja2 templates
│ ├── base.html # Base template with navigation │ ├── base.html # Base template with navigation
│ ├── index.html # Latest comic page │ ├── index.html # Latest comic page
│ ├── comic.html # Individual comic viewer │ ├── comic.html # Individual comic viewer
│ ├── archive.html # Archive grid │ ├── archive.html # Archive grid
│ ├── about.html # About page │ ├── page.html # Generic markdown page template
│ └── 404.html # Error page │ └── 404.html # Error page
└── static/ # Static files └── static/ # Static files
├── css/ ├── css/
@@ -41,7 +140,8 @@ sunday/
│ └── comic-nav.js # Client-side navigation │ └── comic-nav.js # Client-side navigation
├── images/ # Image directory ├── images/ # Image directory
│ ├── comics/ # Comic images │ ├── comics/ # Comic images
── thumbs/ # Thumbnail images for archive ── thumbs/ # Thumbnail images for archive
│ └── icons/ # Navigation and social icons (optional)
└── feed.rss # RSS feed (generated) └── feed.rss # RSS feed (generated)
``` ```
@@ -80,6 +180,32 @@ export PORT=3000
python app.py 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 ## Adding Comics
Comics are stored in the `COMICS` list in `comics_data.py`. Each comic entry: Comics are stored in the `COMICS` list in `comics_data.py`. Each comic entry:
@@ -88,10 +214,13 @@ Comics are stored in the `COMICS` list in `comics_data.py`. Each comic entry:
{ {
'number': 1, # Comic number (required, sequential) 'number': 1, # Comic number (required, sequential)
'filename': 'comic-001.png', # Image filename (required) 'filename': 'comic-001.png', # Image filename (required)
'mobile_filename': 'comic-001-mobile.png', # Optional mobile version
'date': '2025-01-01', # Publication date (required) 'date': '2025-01-01', # Publication date (required)
'alt_text': 'Alt text for comic', # Accessibility text (required) 'alt_text': 'Alt text for comic', # Accessibility text (required)
'title': 'Comic Title', # Title (optional, shows #X if absent) 'title': 'Comic Title', # Title (optional, shows #X if absent)
'author_note': 'Optional note' # Author note (optional) 'author_note': 'Optional note', # Author note (optional, plain text)
'full_width': True, # Optional: override FULL_WIDTH_DEFAULT
'plain': True # Optional: override PLAIN_DEFAULT
} }
``` ```
@@ -120,6 +249,22 @@ python scripts/generate_rss.py
``` ```
This creates/updates `static/feed.rss` 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 ## Production Deployment
For production, you should **NOT** use Flask's built-in development server. Choose one of the following deployment methods: For production, you should **NOT** use Flask's built-in development server. Choose one of the following deployment methods:
@@ -217,15 +362,32 @@ For larger comic archives, consider replacing the `COMICS` list with a database:
## Customization ## Customization
### Branding ### Branding
- Update "Sunday Comics" references in `templates/base.html` - Update `COMIC_NAME` in `comics_data.py` to change your comic's name
- Update site title and footer text - Update `SITE_URL` in `comics_data.py` for production deployment
- Customize logo by setting `LOGO_IMAGE` and `LOGO_MODE`
### Styling ### Styling
- Modify `static/css/style.css` to change colors, fonts, and layout - Modify `static/css/style.css` to change colors, fonts, and layout
- Current color scheme uses blue (#3498db) and dark blue-gray (#2c3e50) - Current color scheme uses blue (#3498db) and dark blue-gray (#2c3e50)
### About Page ### About Page
- Edit `templates/about.html` to add your bio and comic information - 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 ## Pages
@@ -269,9 +431,25 @@ The app exposes a JSON API for programmatic access:
## Credits ## Credits
- Inspired by [Rarebit](https://rarebit.neocities.org/)
- 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)
## License ## License & Content Ownership
[Add your license here] This software is licensed under the **MIT License** - see the [LICENSE](LICENSE) file for details.
### Your Comics Are Yours
The Sunday Comics software is open source and free to use, but **any comics, artwork, and content you create and publish using this platform remain your intellectual property**. The software license does not grant any rights to your creative work.
Think of it like using a camera: the camera might be shared, but the photos you take with it are yours. Sunday Comics is the camera, your comics are the photos.
### What This Means
- ✅ You can use Sunday Comics for free, forever
- ✅ You can modify the code however you want
- ✅ You can use it for commercial projects
- ✅ Your comics and artwork remain 100% yours
- ✅ No attribution required (but always appreciated!)
- ✅ No revenue sharing or licensing fees

8
app.py
View File

@@ -1,3 +1,7 @@
# Sunday Comics - A simple webcomic platform
# Copyright (c) 2025 Tomasita Cabrera
# Licensed under the MIT License - see LICENSE file for details
import os import os
from datetime import datetime from datetime import datetime
from flask import Flask, render_template, abort, jsonify, request from flask import Flask, render_template, abort, jsonify, request
@@ -131,7 +135,9 @@ def comic(comic_id):
comic = get_comic_by_number(comic_id) comic = get_comic_by_number(comic_id)
if not comic: if not comic:
abort(404) abort(404)
return render_template('comic.html', title=f"Comic #{comic_id}", # Use comic title if present, otherwise use #X format (matching client-side behavior)
page_title = comic.get('title', f"#{comic_id}")
return render_template('comic.html', title=page_title,
comic=comic, total_comics=len(COMICS)) comic=comic, total_comics=len(COMICS))

View File

@@ -1,4 +1,7 @@
# Comic data # Sunday Comics - Comic data configuration
# Copyright (c) 2025 Tomasita Cabrera
# Licensed under the MIT License - see LICENSE file for details
#
# Edit this file to add, remove, or modify comics # Edit this file to add, remove, or modify comics
# Global setting: The name of your comic/website # Global setting: The name of your comic/website

View File

@@ -1,4 +1,8 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
# Sunday Comics - Add comic script
# Copyright (c) 2025 Tomasita Cabrera
# Licensed under the MIT License - see LICENSE file for details
""" """
Script to add a new comic entry to comics_data.py with reasonable defaults Script to add a new comic entry to comics_data.py with reasonable defaults
""" """

View File

@@ -1,4 +1,8 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
# Sunday Comics - RSS feed generator
# Copyright (c) 2025 Tomasita Cabrera
# Licensed under the MIT License - see LICENSE file for details
""" """
Script to generate an RSS feed for the comic Script to generate an RSS feed for the comic
""" """

View File

@@ -46,7 +46,7 @@
} }
// Show/hide header based on plain mode // Show/hide header based on plain mode
const header = document.querySelector('.comic-header'); let header = document.querySelector('.comic-header');
if (comic.plain) { if (comic.plain) {
if (header) { if (header) {
header.style.display = 'none'; header.style.display = 'none';
@@ -60,10 +60,11 @@
newHeader.className = 'comic-header'; newHeader.className = 'comic-header';
newHeader.innerHTML = '<h1></h1><p class="comic-date"></p>'; newHeader.innerHTML = '<h1></h1><p class="comic-date"></p>';
container.insertBefore(newHeader, container.firstChild); container.insertBefore(newHeader, container.firstChild);
header = newHeader; // Update reference to the newly created element
} }
// Update title and date // Update title and date using the header reference
document.querySelector('.comic-header h1').textContent = title; header.querySelector('h1').textContent = title;
document.querySelector('.comic-date').textContent = comic.date; header.querySelector('.comic-date').textContent = comic.date;
} }
// Update image and its link // Update image and its link
@@ -74,7 +75,7 @@
updateComicImageLink(comic.number); updateComicImageLink(comic.number);
// Update author note // Update author note
const transcriptDiv = document.querySelector('.comic-transcript'); let transcriptDiv = document.querySelector('.comic-transcript');
if (comic.author_note) { if (comic.author_note) {
if (!transcriptDiv) { if (!transcriptDiv) {
const container = document.querySelector('.comic-container'); const container = document.querySelector('.comic-container');
@@ -82,6 +83,7 @@
newDiv.className = 'comic-transcript'; newDiv.className = 'comic-transcript';
newDiv.innerHTML = '<h3>Author Note</h3>'; newDiv.innerHTML = '<h3>Author Note</h3>';
container.appendChild(newDiv); container.appendChild(newDiv);
transcriptDiv = newDiv; // Update reference to the newly created element
} }
// Clear existing content after the h3 // Clear existing content after the h3