diff options
| -rw-r--r-- | main.py | 5 | ||||
| -rw-r--r-- | routers/about.py | 43 | ||||
| -rw-r--r-- | templates/about.html | 102 | ||||
| -rw-r--r-- | templates/base.html | 1 | ||||
| -rw-r--r-- | templates/login.html | 12 | ||||
| -rw-r--r-- | templates/profile.html | 3 |
6 files changed, 163 insertions, 3 deletions
@@ -9,14 +9,14 @@ from starlette.middleware.sessions import SessionMiddleware from starlette.middleware.base import BaseHTTPMiddleware from database import init_db -from routers import auth, films, imports as imports_router, profile, stats, tmdb +from routers import about, auth, films, imports as imports_router, profile, stats, tmdb load_dotenv() class AuthMiddleware(BaseHTTPMiddleware): async def dispatch(self, request: Request, call_next): - public_paths = {"/login", "/logout", "/tyler", "/films/partial"} + public_paths = {"/about", "/login", "/logout", "/tyler", "/films/partial"} path = request.url.path if path.startswith("/static") or path in public_paths: @@ -42,6 +42,7 @@ app.add_middleware(AuthMiddleware) app.add_middleware(SessionMiddleware, secret_key=session_secret) app.mount("/static", StaticFiles(directory="static"), name="static") +app.include_router(about.router) app.include_router(auth.router) app.include_router(profile.router) app.include_router(tmdb.router) diff --git a/routers/about.py b/routers/about.py new file mode 100644 index 0000000..fe28524 --- /dev/null +++ b/routers/about.py @@ -0,0 +1,43 @@ +import random +from fastapi import APIRouter, Depends, Request +from fastapi.templating import Jinja2Templates +from sqlalchemy.orm import Session + +from database import get_db +from models import Film + +router = APIRouter(tags=["about"]) +templates = Jinja2Templates(directory="templates") + + +@router.get("/about", name="about") +def about(request: Request, db: Session = Depends(get_db)): + films_by_rating = {} + + for stars in (1, 2, 3): + all_films = ( + db.query(Film) + .filter(Film.shelf == "diary", Film.stars == stars) + .all() + ) + + # Deduplicate by title, keeping most recent watch per title + seen_titles = {} + for film in sorted(all_films, key=lambda f: f.date_watched or "", reverse=True): + if film.title not in seen_titles: + seen_titles[film.title] = film + + # Randomize and take up to 3 + unique_films = list(seen_titles.values()) + random.shuffle(unique_films) + films_by_rating[str(stars)] = unique_films[:3] + + return templates.TemplateResponse( + request=request, + name="about.html", + context={ + "request": request, + "active_page": "about", + "films_by_rating": films_by_rating, + }, + ) diff --git a/templates/about.html b/templates/about.html new file mode 100644 index 0000000..ab64142 --- /dev/null +++ b/templates/about.html @@ -0,0 +1,102 @@ +{% extends "base.html" %} + +{% block content %} +<section class="page-heading"> + <p class="eyebrow">How it works</p> + <div class="page-heading-row"> + <h1>Rating system</h1> + </div> +</section> + +<div style="max-width: 900px; margin: 0 auto; padding: 0 20px; line-height: 1.7; color: var(--text);"> + <p style="margin-bottom: 48px; color: var(--muted); font-size: 18px;"> + This rubric is descriptive, not prescriptive — a way to read the ratings, not a formula for assigning them. Stars reflect something felt, not a checklist passed. + </p> + + <!-- ✦ Good --> + <div style="margin-bottom: 56px;"> + <h2 style="color: var(--accent); margin: 0 0 12px 0; font-size: 20px;">✦ Good</h2> + <p style="margin: 0 0 20px 0; color: var(--muted);"> + Films that earned their runtime. You were engaged throughout, walked away with something — an image that lingered, a line that landed, an emotion that felt true. You'd send this to someone whose taste you understand. + </p> + {% if films_by_rating.get("1") %} + <p style="margin: 20px 0 12px 0; font-size: 13px; color: var(--subtle); text-transform: uppercase; letter-spacing: 0.5px;">Examples from your diary</p> + <div style="display: flex; gap: 16px; flex-wrap: wrap; margin-top: 12px;"> + {% for film in films_by_rating["1"] %} + <div style="width: 100px; text-align: center;"> + <div class="poster-frame" style="width: 100px; margin-bottom: 8px;"> + {% if film.poster_url %} + <img src="{{ film.poster_url }}" alt="{{ film.title }} poster" loading="lazy" style="width: 100%; height: 100%; object-fit: cover;"> + {% else %} + <span style="display: flex; align-items: center; justify-content: center; height: 100%; background: var(--panel); color: var(--accent); font-size: 24px; font-weight: bold;">{{ film.title[:1] }}</span> + {% endif %} + </div> + <p style="margin: 0 0 4px 0; font-size: 13px; line-height: 1.3; word-break: break-word;">{{ film.title }}</p> + {% if film.year %}<p style="margin: 0; font-size: 12px; color: var(--muted);">{{ film.year }}</p>{% endif %} + </div> + {% endfor %} + </div> + {% endif %} + </div> + + <!-- ✦✦ Excellent --> + <div style="margin-bottom: 56px;"> + <h2 style="color: var(--accent); margin: 0 0 12px 0; font-size: 20px;">✦✦ Excellent</h2> + <p style="margin: 0 0 20px 0; color: var(--muted);"> + Films that stay. Something in the execution — the direction, the performances, the way it holds an idea — lifted it into a different register. Days later it's still running in the background. You don't just mention it; you push it. + </p> + {% if films_by_rating.get("2") %} + <p style="margin: 20px 0 12px 0; font-size: 13px; color: var(--subtle); text-transform: uppercase; letter-spacing: 0.5px;">Examples from your diary</p> + <div style="display: flex; gap: 16px; flex-wrap: wrap; margin-top: 12px;"> + {% for film in films_by_rating["2"] %} + <div style="width: 100px; text-align: center;"> + <div class="poster-frame" style="width: 100px; margin-bottom: 8px;"> + {% if film.poster_url %} + <img src="{{ film.poster_url }}" alt="{{ film.title }} poster" loading="lazy" style="width: 100%; height: 100%; object-fit: cover;"> + {% else %} + <span style="display: flex; align-items: center; justify-content: center; height: 100%; background: var(--panel); color: var(--accent); font-size: 24px; font-weight: bold;">{{ film.title[:1] }}</span> + {% endif %} + </div> + <p style="margin: 0 0 4px 0; font-size: 13px; line-height: 1.3; word-break: break-word;">{{ film.title }}</p> + {% if film.year %}<p style="margin: 0; font-size: 12px; color: var(--muted);">{{ film.year }}</p>{% endif %} + </div> + {% endfor %} + </div> + {% endif %} + </div> + + <!-- ✦✦✦ Exceptional --> + <div style="margin-bottom: 56px;"> + <h2 style="color: var(--accent); margin: 0 0 12px 0; font-size: 20px;">✦✦✦ Exceptional</h2> + <p style="margin: 0 0 20px 0; color: var(--muted);"> + Films that feel necessary. They reframe how you see the medium, or the world, or both. You come back to them. You reference them without meaning to. You can't fully explain why they matter — only that they do, and probably always will. + </p> + {% if films_by_rating.get("3") %} + <p style="margin: 20px 0 12px 0; font-size: 13px; color: var(--subtle); text-transform: uppercase; letter-spacing: 0.5px;">Examples from your diary</p> + <div style="display: flex; gap: 16px; flex-wrap: wrap; margin-top: 12px;"> + {% for film in films_by_rating["3"] %} + <div style="width: 100px; text-align: center;"> + <div class="poster-frame" style="width: 100px; margin-bottom: 8px;"> + {% if film.poster_url %} + <img src="{{ film.poster_url }}" alt="{{ film.title }} poster" loading="lazy" style="width: 100%; height: 100%; object-fit: cover;"> + {% else %} + <span style="display: flex; align-items: center; justify-content: center; height: 100%; background: var(--panel); color: var(--accent); font-size: 24px; font-weight: bold;">{{ film.title[:1] }}</span> + {% endif %} + </div> + <p style="margin: 0 0 4px 0; font-size: 13px; line-height: 1.3; word-break: break-word;">{{ film.title }}</p> + {% if film.year %}<p style="margin: 0; font-size: 12px; color: var(--muted);">{{ film.year }}</p>{% endif %} + </div> + {% endfor %} + </div> + {% endif %} + </div> + + <p style="margin: 48px 0 0 0; padding-top: 40px; border-top: 1px solid var(--line); color: var(--muted); font-size: 16px;"> + The 3s resist defense. That's the point. Films you can explain with rules are just very good; the exceptional ones arrived on their own terms. The rubric exists to calibrate the 1s and 2s — the 3s took care of themselves. + </p> + + <div style="margin-top: 60px; padding-top: 40px; padding-bottom: 60px; border-top: 1px solid var(--line); text-align: center;"> + <a href="/tyler" class="button-link" style="display: inline-block; padding: 12px 24px; background: var(--accent); color: #0b0b0a; border-radius: 6px; text-decoration: none; font-weight: 500; font-size: 14px;">View Profile</a> + </div> +</div> +{% endblock %} diff --git a/templates/base.html b/templates/base.html index 925d161..4e814ad 100644 --- a/templates/base.html +++ b/templates/base.html @@ -18,6 +18,7 @@ <a class="{% if active_shelf == 'queue' %}is-active{% endif %}" href="/queue">Queue</a> <a class="{% if active_shelf == 'abandoned' %}is-active{% endif %}" href="/abandoned">Abandoned</a> <a class="{% if active_page == 'stats' %}is-active{% endif %}" href="/stats">Stats</a> + <a class="{% if active_page == 'about' %}is-active{% endif %}" href="/about">About</a> <a class="{% if active_page == 'import' %}is-active{% endif %}" href="/import">Import</a> <a class="button-link" href="/films/new">Add Film</a> <form method="post" action="/logout" style="display: contents;"> diff --git a/templates/login.html b/templates/login.html index c164531..a8caae3 100644 --- a/templates/login.html +++ b/templates/login.html @@ -56,5 +56,17 @@ Sign In </button> </form> + + <div style="margin-top: 40px; padding-top: 40px; border-top: 1px solid var(--line); text-align: center;"> + <p style="margin: 0 0 16px 0; color: var(--muted); font-size: 14px;">Want to learn more?</p> + <div style="display: flex; flex-direction: column; gap: 12px;"> + <a href="/about" style="display: inline-block; padding: 11px 24px; background: var(--panel); color: var(--text); border: 1px solid var(--line); border-radius: 6px; text-decoration: none; font-weight: 500; font-size: 14px;">About the Rating System</a> + <a href="/tyler" style="display: inline-block; padding: 11px 24px; background: var(--panel); color: var(--text); border: 1px solid var(--line); border-radius: 6px; text-decoration: none; font-weight: 500; font-size: 14px;">View Profile</a> + </div> + </div> + + <footer style="text-align: center; padding: 40px 20px; color: var(--muted); font-size: 12px; margin-top: 60px; border-top: 1px solid var(--line);"> + <p style="margin: 0;">Made with <a href="https://git.tylerhoang.xyz/lumi.git/" style="color: var(--accent); text-decoration: none;">Lumière</a></p> + </footer> </div> {% endblock %} diff --git a/templates/profile.html b/templates/profile.html index 745ed32..7c847f4 100644 --- a/templates/profile.html +++ b/templates/profile.html @@ -13,6 +13,7 @@ <a class="brand" href="/">Lumière</a> <nav class="nav-actions" aria-label="Primary"> <a href="/tyler">Profile</a> + <a href="/about">About</a> </nav> </header> @@ -142,7 +143,7 @@ </main> <footer style="text-align: center; padding: 40px 20px; color: var(--muted); font-size: 12px; border-top: 1px solid var(--line); margin-top: 60px;"> - <p style="margin: 0;">Made with <a href="https://github.com/tylerhoang/lumiere" style="color: var(--accent); text-decoration: none;">Lumière</a></p> + <p style="margin: 0;">Made with <a href="https://git.tylerhoang.xyz/lumi.git" style="color: var(--accent); text-decoration: none;">Lumière</a></p> </footer> </div> |
