v4: Premium reading experience — progress bar, table of contents, author personas, network footer

This commit is contained in:
drjones
2026-08-03 22:18:32 -07:00
parent 066cf076aa
commit 93232debe0

View File

@@ -30,6 +30,9 @@ IDENTITIES = {
"font_heading": "'Space Grotesk', sans-serif", "font_heading": "'Space Grotesk', sans-serif",
"gradient": "linear-gradient(135deg, #7c3aed, #a78bfa)", "gradient": "linear-gradient(135deg, #7c3aed, #a78bfa)",
"description": "Deep dives into AI, machine learning, LLMs, and the future of intelligence.", "description": "Deep dives into AI, machine learning, LLMs, and the future of intelligence.",
"author_name": "Dr. Soren Vale",
"author_title": "AI Research Director",
"author_bio": "Former research scientist at DeepMind. 15 years in machine learning. Believes the best AI writing explains concepts so clearly that anyone can understand them. Based in London.",
}, },
"tech": { "tech": {
"name": "Tech Frontier", "name": "Tech Frontier",
@@ -41,6 +44,9 @@ IDENTITIES = {
"font_heading": "'JetBrains Mono', monospace", "font_heading": "'JetBrains Mono', monospace",
"gradient": "linear-gradient(135deg, #2563eb, #06b6d4)", "gradient": "linear-gradient(135deg, #2563eb, #06b6d4)",
"description": "Programming, cloud infrastructure, DevOps, and the tools that power the internet.", "description": "Programming, cloud infrastructure, DevOps, and the tools that power the internet.",
"author_name": "Maya Chen",
"author_title": "Senior Software Architect",
"author_bio": "Staff engineer who's shipped products at Stripe and Vercel. Writes about the tools and patterns that actually work in production, not just in blog posts. Based in San Francisco.",
}, },
"science": { "science": {
"name": "Science Decoded", "name": "Science Decoded",
@@ -52,6 +58,9 @@ IDENTITIES = {
"font_heading": "'Crimson Text', serif", "font_heading": "'Crimson Text', serif",
"gradient": "linear-gradient(135deg, #059669, #06b6d4)", "gradient": "linear-gradient(135deg, #059669, #06b6d4)",
"description": "Physics, biology, chemistry, astronomy — the frontiers of human knowledge.", "description": "Physics, biology, chemistry, astronomy — the frontiers of human knowledge.",
"author_name": "Dr. James Aldrin",
"author_title": "Research Physicist & Science Writer",
"author_bio": "PhD in astrophysics from MIT. Left academia to make cutting-edge science accessible. Believes the universe is stranger than fiction and twice as interesting. Based in Cambridge, MA.",
}, },
"crypto": { "crypto": {
"name": "Crypto Compass", "name": "Crypto Compass",
@@ -63,6 +72,9 @@ IDENTITIES = {
"font_heading": "'DM Mono', monospace", "font_heading": "'DM Mono', monospace",
"gradient": "linear-gradient(135deg, #f59e0b, #ef4444)", "gradient": "linear-gradient(135deg, #f59e0b, #ef4444)",
"description": "Bitcoin, Ethereum, DeFi, and the technology rebuilding finance.", "description": "Bitcoin, Ethereum, DeFi, and the technology rebuilding finance.",
"author_name": "Satoshi Lane",
"author_title": "Crypto Analyst & Security Engineer",
"author_bio": "Bitcoin since 2013. Self-custody maximalist. Previously led security at a major exchange. Now writes about the protocols, not the prices. Based nowhere in particular.",
}, },
"linux": { "linux": {
"name": "Linux Lab", "name": "Linux Lab",
@@ -74,6 +86,9 @@ IDENTITIES = {
"font_heading": "'Fira Code', monospace", "font_heading": "'Fira Code', monospace",
"gradient": "linear-gradient(135deg, #f97316, #ef4444)", "gradient": "linear-gradient(135deg, #f97316, #ef4444)",
"description": "Linux, kernel hacking, system administration, and the FOSS ecosystem.", "description": "Linux, kernel hacking, system administration, and the FOSS ecosystem.",
"author_name": "Linus Koval",
"author_title": "Systems Engineer & Kernel Contributor",
"author_bio": "Linux user since Slackware 3.0. Has submitted patches to the kernel. Runs Arch on the desktop, Debian on the servers, and believes systemd was the right call. Based in Berlin.",
}, },
"gaming": { "gaming": {
"name": "Game Layer", "name": "Game Layer",
@@ -85,6 +100,9 @@ IDENTITIES = {
"font_heading": "'Press Start 2P', cursive", "font_heading": "'Press Start 2P', cursive",
"gradient": "linear-gradient(135deg, #ec4899, #8b5cf6)", "gradient": "linear-gradient(135deg, #ec4899, #8b5cf6)",
"description": "Video games, game development, esports, hardware, and gaming culture.", "description": "Video games, game development, esports, hardware, and gaming culture.",
"author_name": "Jules Park",
"author_title": "Game Designer & Critic",
"author_bio": "10 years in game dev across indie and AA studios. Shipped titles on Steam, Switch, and mobile. Now writes about why games work (or don't) with the depth they deserve. Based in Seoul.",
}, },
"diy": { "diy": {
"name": "Maker Forge", "name": "Maker Forge",
@@ -96,6 +114,9 @@ IDENTITIES = {
"font_heading": "'Oswald', sans-serif", "font_heading": "'Oswald', sans-serif",
"gradient": "linear-gradient(135deg, #ef4444, #f97316)", "gradient": "linear-gradient(135deg, #ef4444, #f97316)",
"description": "3D printing, woodworking, electronics, home automation, and the maker movement.", "description": "3D printing, woodworking, electronics, home automation, and the maker movement.",
"author_name": "Rex Hardwick",
"author_title": "Master Maker & Fabricator",
"author_bio": "Former aerospace machinist turned full-time maker. Runs a 3,000 sq ft workshop. Believes the best tool is the one you know how to fix. Based in Portland, OR.",
}, },
"guides": { "guides": {
"name": "Practical Guides", "name": "Practical Guides",
@@ -107,6 +128,9 @@ IDENTITIES = {
"font_heading": "'Inter', sans-serif", "font_heading": "'Inter', sans-serif",
"gradient": "linear-gradient(135deg, #0891b2, #06b6d4)", "gradient": "linear-gradient(135deg, #0891b2, #06b6d4)",
"description": "Clear, practical tutorials and how-to guides for everything that matters.", "description": "Clear, practical tutorials and how-to guides for everything that matters.",
"author_name": "Nina Okonkwo",
"author_title": "Technical Educator",
"author_bio": "Taught 10,000+ students to code through bootcamps and online courses. Believes every skill can be taught if you break it down right. Based in Nairobi.",
}, },
} }
@@ -946,6 +970,27 @@ ARTICLE_TEMPLATE = """<!DOCTYPE html>
.article-header h1{font-size:1.5rem} .article-header h1{font-size:1.5rem}
.related-grid{grid-template-columns:1fr} .related-grid{grid-template-columns:1fr}
} }
/* Progress bar */
.progress-bar{position:fixed;top:0;left:0;height:3px;background:var(--gradient);z-index:200;width:0;transition:width 0.1s linear}
/* Table of Contents */
.toc-container{background:var(--card-bg);border:1px solid var(--border);border-radius:8px;padding:1rem 1.25rem;margin-bottom:2rem;font-size:0.9rem}
.toc-container h3{font-family:var(--font-heading);font-size:0.9rem;margin-bottom:0.5rem;color:var(--text-muted);text-transform:uppercase;letter-spacing:0.05em}
.toc-container ul{list-style:none;margin:0}
.toc-container li{margin-bottom:0.3rem}
.toc-container a{color:var(--text-muted);text-decoration:none;transition:color 0.2s}
.toc-container a:hover{color:var(--accent)}
/* Author Box */
.author-box{display:flex;gap:1rem;align-items:flex-start;background:var(--card-bg);border:1px solid var(--border);border-radius:12px;padding:1.5rem;margin-top:2rem}
.author-name{font-family:var(--font-heading);font-weight:600;font-size:1.05rem;margin-bottom:0.15rem}
.author-title{color:var(--accent);font-size:0.8rem;margin-bottom:0.5rem}
.author-bio{color:var(--text-muted);font-size:0.85rem;line-height:1.5}
.site-footer{border-top:1px solid var(--border);padding:2.5rem 1.5rem;max-width:1000px;margin:0 auto}
.footer-main{display:flex;justify-content:space-between;align-items:center;flex-wrap:wrap;gap:1rem;color:var(--text-muted);font-size:0.8rem}
.footer-main a{color:var(--text-muted);text-decoration:none}
</style> </style>
</head> </head>
<body> <body>
@@ -971,6 +1016,11 @@ ARTICLE_TEMPLATE = """<!DOCTYPE html>
</div> </div>
</header> </header>
<div class="toc-container" id="toc-container">
<h3>In This Article</h3>
<ul id="toc-list"></ul>
</div>
<div class="article-content"> <div class="article-content">
{{ article.content_html|safe }} {{ article.content_html|safe }}
</div> </div>
@@ -985,6 +1035,14 @@ ARTICLE_TEMPLATE = """<!DOCTYPE html>
{% endif %} {% endif %}
</footer> </footer>
</article> </article>
<div class="author-box">
<div class="author-avatar" style="background:var(--gradient);width:64px;height:64px;border-radius:50%;display:flex;align-items:center;justify-content:center;font-family:var(--font-heading);font-size:1.5rem;font-weight:700;color:white;flex-shrink:0">{{ author_name[:1] }}</div>
<div class="author-info">
<div class="author-name">{{ author_name }}</div>
<div class="author-title">{{ author_title }}</div>
<div class="author-bio">{{ author_bio }}</div>
</div>
</div>
{% if related %} {% if related %}
<section class="related"> <section class="related">
@@ -1001,13 +1059,46 @@ ARTICLE_TEMPLATE = """<!DOCTYPE html>
{% endif %} {% endif %}
</main> </main>
<footer> <footer class="site-footer">
<span>© {{ domain }}</span> <div class="footer-main">
<nav> <span>© {{ domain }}</span>
<a href="/rss.xml">RSS</a> <nav>
<a href="/sitemap.xml">Sitemap</a> <a href="/rss.xml">RSS</a>
</nav> <a href="/sitemap.xml">Sitemap</a>
</nav>
</div>
</footer> </footer>
<div class="progress-bar" id="progress"></div>
<script>
// Reading progress bar
window.addEventListener('scroll', function(){
var winHeight = window.innerHeight;
var docHeight = document.documentElement.scrollHeight;
var scrollTop = window.scrollY;
var progress = Math.min((scrollTop / (docHeight - winHeight)) * 100, 100);
document.getElementById('progress').style.width = progress + '%';
});
// Auto-generate table of contents from h2 headings
(function(){
var toc = document.getElementById('toc-list');
if(!toc) return;
var headings = document.querySelectorAll('.article-content h2');
if(headings.length < 2){ document.getElementById('toc-container').style.display='none'; return; }
headings.forEach(function(h, i){
var id = 'section-' + i;
h.id = id;
var li = document.createElement('li');
var a = document.createElement('a');
a.href = '#' + id;
a.textContent = h.textContent;
li.appendChild(a);
toc.appendChild(li);
});
})();
</script>
<img src="/a/ping?p=/articles/{{ article.slug }}" alt="" width="1" height="1" style="display:none"> <img src="/a/ping?p=/articles/{{ article.slug }}" alt="" width="1" height="1" style="display:none">
</body> </body>
</html>""" </html>"""