v6: Cloud fallback, quality gate, format diversity, real fact-check, scroll depth, email capture, dual-model research, vision-verified images, network footer

This commit is contained in:
drjones
2026-08-03 23:18:52 -07:00
parent 1b1b1e7805
commit f5efb69905
2 changed files with 431 additions and 150 deletions

View File

@@ -234,6 +234,14 @@ def init_db():
CREATE INDEX IF NOT EXISTS idx_pageviews_created ON pageviews(created_at);
CREATE INDEX IF NOT EXISTS idx_articles_published ON articles(published_at);
CREATE INDEX IF NOT EXISTS idx_articles_slug ON articles(slug);
CREATE TABLE IF NOT EXISTS subscribers (
id INTEGER PRIMARY KEY AUTOINCREMENT,
email TEXT UNIQUE NOT NULL,
vertical TEXT DEFAULT '',
confirmed INTEGER DEFAULT 0,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
""")
@@ -601,6 +609,22 @@ def api_learn():
})
@app.route("/api/subscribe", methods=["POST"])
def api_subscribe():
"""Email newsletter signup."""
email = (request.json or {}).get("email", "").strip().lower()
if not email or "@" not in email:
return jsonify({"error": "invalid email"}), 400
db = get_db()
try:
db.execute("INSERT OR IGNORE INTO subscribers (email, vertical) VALUES (?, ?)",
(email, VERTICAL))
db.commit()
return jsonify({"status": "subscribed"})
except Exception as e:
return jsonify({"error": str(e)}), 500
@app.route("/health")
def health():
db = get_db()
@@ -1024,6 +1048,13 @@ ARTICLE_TEMPLATE = """<!DOCTYPE html>
.network-link span{display:block;color:var(--text-muted);font-size:0.7rem;font-weight:400;margin-top:0.1rem}
.footer-bottom{display:flex;justify-content:space-between;align-items:center;flex-wrap:wrap;gap:1rem;color:var(--text-muted);font-size:0.8rem}
.footer-bottom a{color:var(--text-muted);text-decoration:none}
/* Newsletter */
.newsletter-box{background:var(--card-bg);border:1px solid var(--border);border-radius:12px;padding:1.25rem;margin-top:2rem;text-align:center}
.newsletter-box h4{font-family:var(--font-heading);font-size:1rem;margin-bottom:0.75rem}
.subscribe-form{display:flex;gap:0.5rem;max-width:400px;margin:0 auto}
.subscribe-form input{flex:1;padding:0.6rem 0.75rem;background:var(--bg);border:1px solid var(--border);border-radius:6px;color:var(--text);font-size:0.9rem}
.subscribe-form button{background:var(--gradient);color:white;border:none;padding:0.6rem 1.25rem;border-radius:6px;cursor:pointer;font-weight:600;font-size:0.9rem}
</style>
</head>
<body>
@@ -1077,11 +1108,21 @@ ARTICLE_TEMPLATE = """<!DOCTYPE html>
</div>
</div>
<div class="newsletter-box">
<h4>📬 Get new articles by email</h4>
<form class="subscribe-form" onsubmit="subscribe(event)">
<input type="email" id="sub-email" placeholder="your@email.com" required>
<button type="submit">Subscribe</button>
</form>
<div id="sub-msg" style="margin-top:0.5rem;font-size:0.8rem;display:none"></div>
<p style="font-size:0.7rem;color:var(--text-muted);margin-top:0.5rem">No spam. Just new articles from {{ name }}.</p>
</div>
{% if related %}
<section class="related">
<h2>Continue Reading</h2>
<div class="related-grid">
{% for r in related %}
{% for r in related[:3] %}
<a href="/articles/{{ r.slug }}" class="related-card">
<h4>{{ r.title[:60] }}</h4>
<div class="meta">{{ r.reading_time }} min · {{ r.published_at[:10] }}</div>
@@ -1139,6 +1180,36 @@ ARTICLE_TEMPLATE = """<!DOCTYPE html>
toc.appendChild(li);
});
})();
// Scroll depth tracking
(function(){
var fired = {25:false,50:false,75:false,100:false};
window.addEventListener('scroll', function(){
var pct = Math.round((window.scrollY / (document.documentElement.scrollHeight - window.innerHeight)) * 100);
[25,50,75,100].forEach(function(threshold){
if(pct >= threshold && !fired[threshold]){
fired[threshold] = true;
new Image().src = '/a/ping?p=/articles/{{ article.slug }}&d=' + threshold;
}
});
});
// Time on page ping every 30s
setInterval(function(){
new Image().src = '/a/ping?p=/articles/{{ article.slug }}&t=1';
}, 30000);
})();
function subscribe(e){
e.preventDefault();
var email = document.getElementById('sub-email').value;
var msg = document.getElementById('sub-msg');
fetch('/api/subscribe', {method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({email:email})})
.then(function(r){ return r.json(); })
.then(function(d){
msg.style.display = 'block';
msg.textContent = d.status === 'subscribed' ? '✅ Subscribed! Welcome.' : '' + (d.error || 'Error');
});
}
</script>
<img src="/a/ping?p=/articles/{{ article.slug }}" alt="" width="1" height="1" style="display:none">
</body>