v2: Hero images, tag pages, 16 seed articles, FAL-generated visuals, nginx asset serving
This commit is contained in:
@@ -408,6 +408,20 @@ def sitemap():
|
||||
return Response(build_sitemap_xml(), mimetype="application/xml")
|
||||
|
||||
|
||||
@app.route("/tag/<tag>")
|
||||
def tag_page(tag):
|
||||
"""Aggregate all articles with a given tag."""
|
||||
db = get_db()
|
||||
rows = db.execute("""
|
||||
SELECT * FROM articles WHERE status='published' AND keywords LIKE ?
|
||||
ORDER BY published_at DESC LIMIT 30
|
||||
""", (f"%{tag}%",)).fetchall()
|
||||
articles = [dict(r) for r in rows]
|
||||
record_view(f"/tag/{tag}")
|
||||
return render_template_string(TAG_TEMPLATE, **IDENTITY,
|
||||
tag=tag, articles=articles, domain=DOMAIN, vertical=VERTICAL)
|
||||
|
||||
|
||||
# ─── Analytics Collector ──────────────────────────────────────────
|
||||
@app.route("/a/ping")
|
||||
def analytics_ping():
|
||||
@@ -680,7 +694,8 @@ HOME_TEMPLATE = """<!DOCTYPE html>
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
<section class="hero">
|
||||
<section class="hero" style="background: linear-gradient(180deg, transparent 0%, var(--bg) 85%), url('/assets/hero.png') center/cover no-repeat; min-height: 420px; display: flex; flex-direction: column; justify-content: center; position: relative;">
|
||||
<div style="padding: 2rem 0;">
|
||||
<h1>{{ tagline }}</h1>
|
||||
<p>{{ description }}</p>
|
||||
<div class="hero-stats">
|
||||
@@ -688,6 +703,7 @@ HOME_TEMPLATE = """<!DOCTYPE html>
|
||||
<span><strong>{{ total_views }}</strong> readers</span>
|
||||
<span>Updated <strong>daily</strong></span>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div class="content">
|
||||
@@ -1010,6 +1026,51 @@ SEARCH_TEMPLATE = """<!DOCTYPE html>
|
||||
</html>"""
|
||||
|
||||
|
||||
TAG_TEMPLATE = """<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>{{ tag }} — {{ name }}</title>
|
||||
<style>
|
||||
:root{--bg:{{ bg }};--card-bg:{{ card_bg }};--text:#e2e8f0;--text-muted:#94a3b8;--primary:{{ primary }};--accent:{{ accent }};--gradient:{{ gradient }};--border:rgba(255,255,255,0.08);--font-heading:{{ font_heading }}}
|
||||
*,*::before,*::after{box-sizing:border-box;margin:0;padding:0}
|
||||
body{font-family:'Inter',sans-serif;background:var(--bg);color:var(--text);min-height:100vh}
|
||||
header{position:sticky;top:0;background:rgba(15,7,32,0.85);backdrop-filter:blur(20px);border-bottom:1px solid var(--border)}
|
||||
nav{max-width:900px;margin:0 auto;display:flex;justify-content:space-between;align-items:center;padding:0.75rem 1.5rem}
|
||||
.logo{font-family:var(--font-heading);font-weight:700;font-size:1.2rem;background:var(--gradient);-webkit-background-clip:text;-webkit-text-fill-color:transparent;text-decoration:none}
|
||||
main{max-width:900px;margin:0 auto;padding:3rem 1.5rem}
|
||||
h1{font-family:var(--font-heading);font-size:2rem;margin-bottom:0.5rem}
|
||||
h1 span{background:var(--gradient);-webkit-background-clip:text;-webkit-text-fill-color:transparent}
|
||||
.count{color:var(--text-muted);margin-bottom:2rem}
|
||||
.card{background:var(--card-bg);border:1px solid var(--border);border-radius:8px;padding:1.25rem;margin-bottom:0.75rem;text-decoration:none;display:block;color:inherit;transition:border-color 0.2s}
|
||||
.card:hover{border-color:var(--primary)}
|
||||
.card h3{font-size:1.05rem;margin-bottom:0.25rem}
|
||||
.card p{color:var(--text-muted);font-size:0.88rem}
|
||||
.card .meta{font-size:0.75rem;color:var(--text-muted);margin-top:0.5rem}
|
||||
footer{border-top:1px solid var(--border);padding:2rem 1.5rem;text-align:center;color:var(--text-muted);font-size:0.8rem}
|
||||
a{color:var(--accent)}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<header><nav><a href="/" class="logo">{{ name }}</a></nav></header>
|
||||
<main>
|
||||
<h1>#<span>{{ tag }}</span></h1>
|
||||
<p class="count">{{ articles|length }} article{% if articles|length != 1 %}s{% endif %}</p>
|
||||
{% for a in articles %}
|
||||
<a href="/articles/{{ a.slug }}" class="card">
|
||||
<h3>{{ a.title }}</h3>
|
||||
<p>{{ a.excerpt[:180] }}</p>
|
||||
<div class="meta">{{ a.reading_time }} min read · {{ a.published_at[:10] }}</div>
|
||||
</a>
|
||||
{% endfor %}
|
||||
{% if not articles %}
|
||||
<p style="color:var(--text-muted)">No articles tagged "{{ tag }}" yet.</p>
|
||||
{% endif %}
|
||||
</main>
|
||||
<footer>© {{ domain }}</footer>
|
||||
</body>
|
||||
</html>"""
|
||||
|
||||
NOT_FOUND_TEMPLATE = """<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
|
||||
Reference in New Issue
Block a user