commit d5030f6456a1ebf6031d89f387e2f88a74380855 Author: mi Date: Thu Nov 6 21:18:44 2025 +1000 :tada: initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3228387 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +.idea +.venv diff --git a/README.md b/README.md new file mode 100644 index 0000000..7829b4d --- /dev/null +++ b/README.md @@ -0,0 +1,101 @@ +# 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) +- Archive page with thumbnail grid +- Responsive design +- Server-side rendering with Jinja2 +- Clean, comic-focused layout + +## Project Structure + +``` +sunday/ +├── app.py # Main Flask application +├── requirements.txt # Python dependencies +├── templates/ # Jinja2 templates +│ ├── base.html # Base template with navigation +│ ├── index.html # Latest comic page +│ ├── comic.html # Individual comic viewer +│ ├── archive.html # Archive grid +│ ├── about.html # About page +│ ├── contact.html # Contact page +│ └── 404.html # Error page +└── static/ # Static files + ├── css/ + │ └── style.css # Main stylesheet + ├── js/ + └── images/ # Comic images + └── thumbs/ # Thumbnail images for archive +``` + +## 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:5000 in your browser + +## Adding Comics + +Currently, comics are stored in the `COMICS` list in `app.py`. Each comic needs: + +```python +{ + 'number': 1, # Comic number (sequential) + 'title': 'Comic Title', # Title of the comic + 'filename': 'comic-001.png', # Image filename + 'date': '2025-01-01', # Publication date + 'alt_text': 'Alt text for comic', # Accessibility text + 'transcript': 'Optional transcript' # Optional transcript +} +``` + +### Adding a New Comic + +1. Save your comic image in `static/images/` (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 `app.py` + +## Upgrading to a Database + +For production use, 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 "Sunday Comics" references in `templates/base.html` +- Update site title and footer text + +### 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 `templates/about.html` to add your bio and comic information + +## 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 +- `/contact` - Contact form + +## License + +[Add your license here] diff --git a/app.py b/app.py new file mode 100644 index 0000000..bd55307 --- /dev/null +++ b/app.py @@ -0,0 +1,95 @@ +from flask import Flask, render_template, abort + +app = Flask(__name__) + +# Configuration +app.config['SECRET_KEY'] = 'your-secret-key-here' # Change this in production + +# Sample comic data (in production, this would come from a database) +COMICS = [ + { + 'number': 1, + 'title': 'First Comic', + 'filename': 'comic-001.png', + 'date': '2025-01-01', + 'alt_text': 'The very first comic', + 'transcript': 'This is where your comic journey begins!' + }, + { + 'number': 2, + 'title': 'Second Comic', + 'filename': 'comic-002.png', + 'date': '2025-01-08', + 'alt_text': 'The second comic', + 'transcript': 'The adventure continues...' + }, + { + 'number': 3, + 'title': 'Third Comic', + 'filename': 'comic-003.png', + 'date': '2025-01-15', + 'alt_text': 'The third comic', + 'transcript': 'Things are getting interesting!' + }, +] + + +def get_comic_by_number(number): + """Get a comic by its number""" + for comic in COMICS: + if comic['number'] == number: + return comic + return None + + +def get_latest_comic(): + """Get the most recent comic""" + if COMICS: + return COMICS[-1] + return None + + +@app.route('/') +def index(): + """Home page - shows latest comic""" + comic = get_latest_comic() + if not comic: + return render_template('index.html', title='Latest Comic', + comic=None, total_comics=0) + return render_template('index.html', title='Latest Comic', + comic=comic, total_comics=len(COMICS)) + + +@app.route('/comic/') +def comic(comic_id): + """View a specific comic""" + comic = get_comic_by_number(comic_id) + if not comic: + abort(404) + return render_template('comic.html', title=f"Comic #{comic_id}", + comic=comic, total_comics=len(COMICS)) + + +@app.route('/archive') +def archive(): + """Archive page showing all comics""" + # Reverse order to show newest first + comics = list(reversed(COMICS)) + return render_template('archive.html', title='Archive', + comics=comics) + + +@app.route('/about') +def about(): + """About page""" + return render_template('about.html', title='About') + + +@app.errorhandler(404) +def page_not_found(e): + """404 error handler""" + return render_template('404.html', title='Page Not Found'), 404 + + +if __name__ == '__main__': + app.run(debug=True) \ No newline at end of file diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..c204bef --- /dev/null +++ b/requirements.txt @@ -0,0 +1 @@ +Flask==3.0.0 \ No newline at end of file diff --git a/static/css/style.css b/static/css/style.css new file mode 100644 index 0000000..cb4d2cf --- /dev/null +++ b/static/css/style.css @@ -0,0 +1,357 @@ +/* Reset and base styles */ +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +body { + font-family: 'Courier New', Courier, monospace; + line-height: 1.5; + color: #000; + background-color: #fff; +} + +.container { + max-width: 900px; + margin: 0 auto; + padding: 0 1rem; +} + +/* Header and Navigation */ +header { + border-bottom: 3px solid #000; + padding: 1.5rem 0; + margin-bottom: 2rem; +} + +nav .container { + display: flex; + justify-content: space-between; + align-items: center; + flex-wrap: wrap; + gap: 1rem; +} + +.nav-brand a { + color: #000; + text-decoration: none; + font-size: 1.5rem; + font-weight: bold; + text-transform: uppercase; + letter-spacing: 2px; +} + +.nav-links { + display: flex; + list-style: none; + gap: 1.5rem; +} + +.nav-links a { + color: #000; + text-decoration: none; + text-transform: lowercase; +} + +.nav-links a:hover, +.nav-links a.active { + text-decoration: line-through; +} + +/* Main content */ +main { + min-height: calc(100vh - 250px); + padding: 0 0 3rem 0; +} + +/* Page header */ +.page-header { + margin-bottom: 2rem; + padding-bottom: 1rem; + border-bottom: 2px solid #000; +} + +.page-header h1 { + color: #000; + font-size: 2rem; + text-transform: uppercase; + letter-spacing: 1px; +} + +.page-header p { + margin-top: 0.5rem; + font-size: 0.9rem; +} + +/* Content sections */ +.about-content { + max-width: 700px; +} + +.about-content h2 { + color: #000; + margin-top: 2rem; + margin-bottom: 0.5rem; + text-transform: uppercase; + font-size: 1.2rem; + letter-spacing: 1px; +} + +.about-content p { + margin-bottom: 1rem; +} + +.about-content ul { + margin-left: 1.5rem; + margin-bottom: 1rem; +} + +.about-content li { + margin-bottom: 0.3rem; +} + +/* Button */ +.btn { + display: inline-block; + padding: 0.6rem 1.2rem; + background-color: #000; + color: #fff; + text-decoration: none; + border: 2px solid #000; + cursor: pointer; + font-size: 0.9rem; + font-family: 'Courier New', Courier, monospace; + text-transform: uppercase; + letter-spacing: 1px; +} + +.btn:hover { + background-color: #fff; + color: #000; +} + +/* Error page */ +.error-page { + text-align: center; + padding: 4rem 1rem; +} + +.error-page h1 { + font-size: 6rem; + color: #000; + margin-bottom: 1rem; +} + +.error-page h2 { + color: #000; + margin-bottom: 1rem; + text-transform: uppercase; +} + +.error-page p { + color: #000; + margin-bottom: 2rem; +} + +/* Comic Container */ +.comic-container { + border: 3px solid #000; + padding: 1rem; +} + +.comic-header { + margin-bottom: 1.5rem; + padding-bottom: 1rem; + border-bottom: 2px solid #000; +} + +.comic-header h1 { + color: #000; + font-size: 1.5rem; + margin-bottom: 0.25rem; + text-transform: uppercase; + letter-spacing: 1px; +} + +.comic-date { + color: #000; + font-size: 0.85rem; +} + +/* Comic Image */ +.comic-image { + text-align: center; + margin-bottom: 1.5rem; + border: 2px solid #000; + padding: 0.5rem; + background: #fff; +} + +.comic-image img { + max-width: 100%; + height: auto; + display: block; + margin: 0 auto; +} + +/* Comic Navigation */ +.comic-navigation { + border-top: 2px solid #000; + padding-top: 1rem; + margin-bottom: 1rem; +} + +.nav-buttons { + display: flex; + justify-content: center; + align-items: center; + gap: 0.5rem; + flex-wrap: wrap; +} + +.btn-nav { + padding: 0.4rem 0.8rem; + font-size: 0.8rem; +} + +.btn-disabled { + background-color: #fff; + color: #999; + border-color: #999; + cursor: not-allowed; +} + +.btn-disabled:hover { + background-color: #fff; + color: #999; +} + +.comic-number { + padding: 0.4rem 0.8rem; + background-color: #000; + color: #fff; + border: 2px solid #000; + font-weight: bold; + margin: 0 0.25rem; + font-size: 0.8rem; + text-transform: uppercase; +} + +/* Comic Transcript */ +.comic-transcript { + border: 2px solid #000; + padding: 1rem; + margin-top: 1rem; +} + +.comic-transcript h3 { + color: #000; + margin-bottom: 0.5rem; + text-transform: uppercase; + font-size: 0.9rem; + letter-spacing: 1px; +} + +.comic-transcript p { + color: #000; + line-height: 1.6; + font-size: 0.9rem; +} + +/* Archive Content */ +.archive-content { + margin-top: 1rem; +} + +.archive-grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(180px, 1fr)); + gap: 1rem; +} + +.archive-item { + border: 2px solid #000; + overflow: hidden; +} + +.archive-item:hover { + background: #f0f0f0; +} + +.archive-item a { + text-decoration: none; + color: #000; + display: block; +} + +.archive-item img { + width: 100%; + height: 120px; + object-fit: cover; + display: block; + border-bottom: 2px solid #000; +} + +.archive-info { + padding: 0.75rem; +} + +.archive-info h3 { + color: #000; + font-size: 0.85rem; + margin-bottom: 0.25rem; + line-height: 1.3; + text-transform: uppercase; +} + +.archive-date { + color: #000; + font-size: 0.75rem; +} + +/* Responsive adjustments */ +@media (max-width: 768px) { + .nav-brand a { + font-size: 1.2rem; + } + + .nav-links { + gap: 1rem; + } + + .btn-nav { + padding: 0.3rem 0.6rem; + font-size: 0.75rem; + } + + .comic-number { + padding: 0.3rem 0.6rem; + font-size: 0.75rem; + } + + .archive-grid { + grid-template-columns: repeat(auto-fill, minmax(140px, 1fr)); + gap: 0.75rem; + } + + .comic-container { + padding: 0.75rem; + } +} + +/* Footer */ +footer { + border-top: 3px solid #000; + text-align: center; + padding: 2rem 0; + margin-top: 3rem; +} + +footer p { + margin: 0; + color: #000; + font-size: 0.85rem; + text-transform: uppercase; + letter-spacing: 1px; +} \ No newline at end of file diff --git a/templates/404.html b/templates/404.html new file mode 100644 index 0000000..ed904d2 --- /dev/null +++ b/templates/404.html @@ -0,0 +1,10 @@ +{% extends "base.html" %} + +{% block content %} +
+

404

+

Page Not Found

+

Sorry, the page you're looking for doesn't exist.

+ Go Home +
+{% endblock %} \ No newline at end of file diff --git a/templates/about.html b/templates/about.html new file mode 100644 index 0000000..e2aa361 --- /dev/null +++ b/templates/about.html @@ -0,0 +1,23 @@ +{% extends "base.html" %} + +{% block content %} + + +
+

Welcome to Sunday Comics, a webcomic about life, humor, and everything in between.

+ +

About the Comic

+

Sunday Comics is updated regularly with new strips. Each comic tells a story, shares a laugh, or offers a unique perspective on everyday situations.

+ +

About the Author

+

Sunday Comics is created by [Your Name]. [Add your bio here]

+ +

Updates

+

New comics are posted [specify your schedule, e.g., "every Monday and Thursday" or "weekly"].

+ +

Support

+

If you enjoy Sunday Comics, consider sharing it with friends or supporting the comic through [Patreon/Ko-fi/etc.].

+
+{% endblock %} \ No newline at end of file diff --git a/templates/archive.html b/templates/archive.html new file mode 100644 index 0000000..d166d00 --- /dev/null +++ b/templates/archive.html @@ -0,0 +1,26 @@ +{% extends "base.html" %} + +{% block content %} + + +
+
+ {% for comic in comics %} + + {% endfor %} +
+
+{% endblock %} diff --git a/templates/base.html b/templates/base.html new file mode 100644 index 0000000..424004b --- /dev/null +++ b/templates/base.html @@ -0,0 +1,40 @@ + + + + + + {% block title %}{{ title }}{% endblock %} - Sunday Comics + + {% block extra_css %}{% endblock %} + + +
+ +
+ +
+
+ {% block content %}{% endblock %} +
+
+ +
+
+

© 2025 Sunday Comics. All rights reserved.

+
+
+ + {% block extra_js %}{% endblock %} + + \ No newline at end of file diff --git a/templates/comic.html b/templates/comic.html new file mode 100644 index 0000000..e8a1843 --- /dev/null +++ b/templates/comic.html @@ -0,0 +1,45 @@ +{% extends "base.html" %} + +{% block content %} +
+
+

{{ comic.title }}

+

{{ comic.date }}

+
+ +
+ {{ comic.title }} +
+ +
+ +
+ + {% if comic.transcript %} +
+

Transcript

+

{{ comic.transcript }}

+
+ {% endif %} +
+{% endblock %} diff --git a/templates/index.html b/templates/index.html new file mode 100644 index 0000000..6465e71 --- /dev/null +++ b/templates/index.html @@ -0,0 +1,45 @@ +{% extends "base.html" %} + +{% block content %} +
+
+

{{ comic.title }}

+

{{ comic.date }}

+
+ +
+ {{ comic.title }} +
+ +
+ +
+ + {% if comic.transcript %} +
+

Transcript

+

{{ comic.transcript }}

+
+ {% endif %} +
+{% endblock %} \ No newline at end of file