🎉 initial commit
This commit is contained in:
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
.idea
|
||||||
|
.venv
|
||||||
101
README.md
Normal file
101
README.md
Normal file
@@ -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/<id>` - 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]
|
||||||
95
app.py
Normal file
95
app.py
Normal file
@@ -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/<int:comic_id>')
|
||||||
|
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)
|
||||||
1
requirements.txt
Normal file
1
requirements.txt
Normal file
@@ -0,0 +1 @@
|
|||||||
|
Flask==3.0.0
|
||||||
357
static/css/style.css
Normal file
357
static/css/style.css
Normal file
@@ -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;
|
||||||
|
}
|
||||||
10
templates/404.html
Normal file
10
templates/404.html
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="error-page">
|
||||||
|
<h1>404</h1>
|
||||||
|
<h2>Page Not Found</h2>
|
||||||
|
<p>Sorry, the page you're looking for doesn't exist.</p>
|
||||||
|
<a href="{{ url_for('index') }}" class="btn">Go Home</a>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
23
templates/about.html
Normal file
23
templates/about.html
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="page-header">
|
||||||
|
<h1>About Sunday Comics</h1>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<section class="about-content">
|
||||||
|
<p>Welcome to Sunday Comics, a webcomic about life, humor, and everything in between.</p>
|
||||||
|
|
||||||
|
<h2>About the Comic</h2>
|
||||||
|
<p>Sunday Comics is updated regularly with new strips. Each comic tells a story, shares a laugh, or offers a unique perspective on everyday situations.</p>
|
||||||
|
|
||||||
|
<h2>About the Author</h2>
|
||||||
|
<p>Sunday Comics is created by [Your Name]. [Add your bio here]</p>
|
||||||
|
|
||||||
|
<h2>Updates</h2>
|
||||||
|
<p>New comics are posted [specify your schedule, e.g., "every Monday and Thursday" or "weekly"].</p>
|
||||||
|
|
||||||
|
<h2>Support</h2>
|
||||||
|
<p>If you enjoy Sunday Comics, consider sharing it with friends or supporting the comic through [Patreon/Ko-fi/etc.].</p>
|
||||||
|
</section>
|
||||||
|
{% endblock %}
|
||||||
26
templates/archive.html
Normal file
26
templates/archive.html
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="page-header">
|
||||||
|
<h1>Comic Archive</h1>
|
||||||
|
<p>Browse all {{ comics|length }} comics</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<section class="archive-content">
|
||||||
|
<div class="archive-grid">
|
||||||
|
{% for comic in comics %}
|
||||||
|
<div class="archive-item">
|
||||||
|
<a href="{{ url_for('comic', comic_id=comic.number) }}">
|
||||||
|
<img src="{{ url_for('static', filename='images/thumbs/' + comic.filename) }}"
|
||||||
|
alt="{{ comic.title }}"
|
||||||
|
onerror="this.src='{{ url_for('static', filename='images/' + comic.filename) }}'">
|
||||||
|
<div class="archive-info">
|
||||||
|
<h3>#{{ comic.number }}: {{ comic.title }}</h3>
|
||||||
|
<p class="archive-date">{{ comic.date }}</p>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
{% endblock %}
|
||||||
40
templates/base.html
Normal file
40
templates/base.html
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>{% block title %}{{ title }}{% endblock %} - Sunday Comics</title>
|
||||||
|
<link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}">
|
||||||
|
{% block extra_css %}{% endblock %}
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<header>
|
||||||
|
<nav>
|
||||||
|
<div class="container">
|
||||||
|
<div class="nav-brand">
|
||||||
|
<a href="{{ url_for('index') }}">Sunday Comics</a>
|
||||||
|
</div>
|
||||||
|
<ul class="nav-links">
|
||||||
|
<li><a href="{{ url_for('index') }}" {% if request.endpoint == 'index' %}class="active"{% endif %}>Latest</a></li>
|
||||||
|
<li><a href="{{ url_for('archive') }}" {% if request.endpoint == 'archive' %}class="active"{% endif %}>Archive</a></li>
|
||||||
|
<li><a href="{{ url_for('about') }}" {% if request.endpoint == 'about' %}class="active"{% endif %}>About</a></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<main>
|
||||||
|
<div class="container">
|
||||||
|
{% block content %}{% endblock %}
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<footer>
|
||||||
|
<div class="container">
|
||||||
|
<p>© 2025 Sunday Comics. All rights reserved.</p>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
|
|
||||||
|
{% block extra_js %}{% endblock %}
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
45
templates/comic.html
Normal file
45
templates/comic.html
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="comic-container">
|
||||||
|
<div class="comic-header">
|
||||||
|
<h1>{{ comic.title }}</h1>
|
||||||
|
<p class="comic-date">{{ comic.date }}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="comic-image">
|
||||||
|
<img src="{{ url_for('static', filename='images/' + comic.filename) }}"
|
||||||
|
alt="{{ comic.title }}"
|
||||||
|
title="{{ comic.alt_text }}">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="comic-navigation">
|
||||||
|
<div class="nav-buttons">
|
||||||
|
{% if comic.number > 1 %}
|
||||||
|
<a href="{{ url_for('comic', comic_id=1) }}" class="btn btn-nav">First</a>
|
||||||
|
<a href="{{ url_for('comic', comic_id=comic.number - 1) }}" class="btn btn-nav">Previous</a>
|
||||||
|
{% else %}
|
||||||
|
<span class="btn btn-nav btn-disabled">First</span>
|
||||||
|
<span class="btn btn-nav btn-disabled">Previous</span>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<span class="comic-number">Comic #{{ comic.number }}</span>
|
||||||
|
|
||||||
|
{% if comic.number < total_comics %}
|
||||||
|
<a href="{{ url_for('comic', comic_id=comic.number + 1) }}" class="btn btn-nav">Next</a>
|
||||||
|
<a href="{{ url_for('comic', comic_id=total_comics) }}" class="btn btn-nav">Latest</a>
|
||||||
|
{% else %}
|
||||||
|
<span class="btn btn-nav btn-disabled">Next</span>
|
||||||
|
<span class="btn btn-nav btn-disabled">Latest</span>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% if comic.transcript %}
|
||||||
|
<div class="comic-transcript">
|
||||||
|
<h3>Transcript</h3>
|
||||||
|
<p>{{ comic.transcript }}</p>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
45
templates/index.html
Normal file
45
templates/index.html
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="comic-container">
|
||||||
|
<div class="comic-header">
|
||||||
|
<h1>{{ comic.title }}</h1>
|
||||||
|
<p class="comic-date">{{ comic.date }}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="comic-image">
|
||||||
|
<img src="{{ url_for('static', filename='images/' + comic.filename) }}"
|
||||||
|
alt="{{ comic.title }}"
|
||||||
|
title="{{ comic.alt_text }}">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="comic-navigation">
|
||||||
|
<div class="nav-buttons">
|
||||||
|
{% if comic.number > 1 %}
|
||||||
|
<a href="{{ url_for('comic', comic_id=1) }}" class="btn btn-nav">First</a>
|
||||||
|
<a href="{{ url_for('comic', comic_id=comic.number - 1) }}" class="btn btn-nav">Previous</a>
|
||||||
|
{% else %}
|
||||||
|
<span class="btn btn-nav btn-disabled">First</span>
|
||||||
|
<span class="btn btn-nav btn-disabled">Previous</span>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<span class="comic-number">Comic #{{ comic.number }}</span>
|
||||||
|
|
||||||
|
{% if comic.number < total_comics %}
|
||||||
|
<a href="{{ url_for('comic', comic_id=comic.number + 1) }}" class="btn btn-nav">Next</a>
|
||||||
|
<a href="{{ url_for('comic', comic_id=total_comics) }}" class="btn btn-nav">Latest</a>
|
||||||
|
{% else %}
|
||||||
|
<span class="btn btn-nav btn-disabled">Next</span>
|
||||||
|
<span class="btn btn-nav btn-disabled">Latest</span>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% if comic.transcript %}
|
||||||
|
<div class="comic-transcript">
|
||||||
|
<h3>Transcript</h3>
|
||||||
|
<p>{{ comic.transcript }}</p>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
Reference in New Issue
Block a user