← back to Allnewsdaily
Front page: 2-min live-refresh loop + countdown counter + big banner ads
0337f69f413adf03441c2aa2c8967df9f287fb61 · 2026-09-09 12:45:56 -0700 · Steve
- Client polls /api/wire every 120s and re-renders the splash + wire columns
in place (no full reload, so AdSense units don't re-request every cycle).
Server-rendered content stays the initial paint (SEO/AdSense-safe).
- Live "counter at top": LIVE WIRE ticker bar with a MM:SS countdown to the
next refresh, a cycle counter, and a live "updated · N/M sources" readout.
- Three big banner ad zones (top leaderboard, mid, bottom): render a real
AdSense responsive display unit when AD_SLOT_TOP/MID/BOTTOM is set, else a
labeled house placeholder so the zones are visibly present.
- Server wire rebuild cadence now follows REFRESH_MS (default 120s) to match.
Verified on a static-wire test instance: countdown ticks 1s cadence, all 3
banner zones render (776x132 / 752x152 / 752x132), 3 cols / 60 stories paint,
/api/wire returns 60 stories.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PV1DXsyryWT3rLttpPXd4u
Files touched
Diff
commit 0337f69f413adf03441c2aa2c8967df9f287fb61
Author: Steve <steve@designerwallcoverings.com>
Date: Wed Sep 9 12:45:56 2026 -0700
Front page: 2-min live-refresh loop + countdown counter + big banner ads
- Client polls /api/wire every 120s and re-renders the splash + wire columns
in place (no full reload, so AdSense units don't re-request every cycle).
Server-rendered content stays the initial paint (SEO/AdSense-safe).
- Live "counter at top": LIVE WIRE ticker bar with a MM:SS countdown to the
next refresh, a cycle counter, and a live "updated · N/M sources" readout.
- Three big banner ad zones (top leaderboard, mid, bottom): render a real
AdSense responsive display unit when AD_SLOT_TOP/MID/BOTTOM is set, else a
labeled house placeholder so the zones are visibly present.
- Server wire rebuild cadence now follows REFRESH_MS (default 120s) to match.
Verified on a static-wire test instance: countdown ticks 1s cadence, all 3
banner zones render (776x132 / 752x152 / 752x132), 3 cols / 60 stories paint,
/api/wire returns 60 stories.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PV1DXsyryWT3rLttpPXd4u
---
server.js | 94 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--
1 file changed, 91 insertions(+), 3 deletions(-)
diff --git a/server.js b/server.js
index b3efbb7..9ba2869 100644
--- a/server.js
+++ b/server.js
@@ -11,6 +11,15 @@ const WIRE_STATIC = process.env.WIRE_STATIC === '1';
const app = express();
const PORT = process.env.PORT || 9788;
+// Client countdown + server wire cadence (Steve: poll the wire on an infinite loop, refresh every 2 min).
+const REFRESH_MS = parseInt(process.env.REFRESH_MS || '120000', 10); // 2 minutes
+// Optional real AdSense display-unit slot IDs per banner position. When a slot is unset a labeled
+// house placeholder renders instead, so the big banner zones are visible without a live slot wired.
+const AD_CLIENT = process.env.AD_CLIENT || 'ca-pub-5278231299883833';
+const AD_SLOT_TOP = process.env.AD_SLOT_TOP || '';
+const AD_SLOT_MID = process.env.AD_SLOT_MID || '';
+const AD_SLOT_BOTTOM = process.env.AD_SLOT_BOTTOM || '';
+
const OUTLETS_PATH = path.join(__dirname, 'data', 'outlets.json');
const LIVE_STATUS_PATH = path.join(__dirname, 'data', 'live-status.json');
@@ -99,13 +108,23 @@ function renderFront() {
<div class="tagline-r">${esc(today)}</div>
</header>
<div class="updated">${esc(updated)} · <a href="/directory">outlet directory</a> · <a href="/guides">guides</a></div>
+<div class="ticker" id="ticker" aria-live="polite">
+ <span class="tk-live"><span class="tk-blink"></span> LIVE WIRE</span>
+ <span class="tk-seg">Next refresh in <b id="tk-count">2:00</b></span>
+ <span class="tk-seg">Cycle <b id="tk-cycle">#1</b></span>
+ <span class="tk-seg tk-upd" id="tk-upd">${esc(updated)}</span>
+</div>
<hr class="rule">
+${bannerAd('top')}
<main>
- ${splashHtml}
+ <div id="wire-splash">${splashHtml}</div>
+ ${bannerAd('mid')}
<hr class="rule thin">
<div class="grid">
- ${columnsHtml}
+ <div id="wire-cols" style="display:contents">
+ ${columnsHtml}
+ </div>
<aside class="col rail">
<h2 class="colhead">COLUMNISTS</h2>
${columnistsHtml}
@@ -115,8 +134,47 @@ function renderFront() {
<a class="railitem" href="/directory">All 98 outlets — live TV & broadcast →</a>
</aside>
</div>
+ ${bannerAd('bottom')}
</main>
+<script>
+(function(){
+ var REFRESH_MS = ${REFRESH_MS};
+ var cycle = 1, remain = REFRESH_MS;
+ var elCount = document.getElementById('tk-count');
+ var elCycle = document.getElementById('tk-cycle');
+ var elUpd = document.getElementById('tk-upd');
+ var elSplash= document.getElementById('wire-splash');
+ var elCols = document.getElementById('wire-cols');
+ function esc(s){return String(s==null?'':s).replace(/[&<>"]/g,function(c){return {'&':'&','<':'<','>':'>','"':'"'}[c];});}
+ function fmtTime(ms){var s=Math.max(0,Math.round(ms/1000));var m=Math.floor(s/60);var r=s%60;return m+':'+(r<10?'0':'')+r;}
+ function fmtET(iso){try{return new Date(iso).toLocaleTimeString('en-US',{hour:'numeric',minute:'2-digit',timeZone:'America/New_York'})+' ET';}catch(e){return '';}}
+ function story(it){return '<a class="story" href="'+esc(it.link)+'" target="_blank" rel="noopener nofollow">'+esc(it.topic)+'<span class="src"> — '+esc(it.outlet)+'</span></a>';}
+ function paint(w){
+ if(w.splash && elSplash){ elSplash.innerHTML='<a class="splash" href="'+esc(w.splash.link)+'" target="_blank" rel="noopener nofollow">'+esc(w.splash.topic)+'<span class="src"> — '+esc(w.splash.outlet)+'</span></a>'; }
+ if(Array.isArray(w.columns) && elCols){
+ elCols.innerHTML = w.columns.map(function(c){
+ return '<section class="col"><h2 class="colhead">'+esc(c.title)+'</h2>'+(c.items||[]).map(story).join('')+'</section>';
+ }).join('');
+ }
+ if(w.updatedAt && elUpd){ elUpd.textContent = 'Updated '+fmtET(w.updatedAt)+' · '+(w.sourcesOk||0)+'/'+(w.sources||0)+' sources'; }
+ }
+ function refresh(){
+ fetch('/api/wire',{cache:'no-store'})
+ .then(function(r){return r.json();})
+ .then(function(w){ paint(w); cycle++; if(elCycle) elCycle.textContent='#'+cycle; })
+ .catch(function(){})
+ .then(function(){ remain = REFRESH_MS; });
+ }
+ setInterval(function(){
+ remain -= 1000;
+ if(elCount) elCount.textContent = fmtTime(remain);
+ if(remain <= 0){ remain = REFRESH_MS; refresh(); }
+ }, 1000);
+ if(elCount) elCount.textContent = fmtTime(remain);
+})();
+</script>
+
<footer class="foot">
<nav><a href="/directory">Directory</a> · <a href="/guides">Guides</a> · <a href="/about">About</a> · <a href="/contact">Contact</a> · <a href="/privacy">Privacy</a></nav>
<p>Topic lines are original one-sentence summaries written by All News Daily; every link goes to the source outlet's own reporting. All News Daily does not republish articles. © ${new Date().getFullYear()} All News Daily.</p>
@@ -149,6 +207,21 @@ app.get('/sitemap.xml', (_q, res) => {
// Turns the site from a bare outbound-link directory into a real publisher.
const esc = (s) => String(s).replace(/[&<>"]/g, (c) => ({ '&': '&', '<': '<', '>': '>', '"': '"' }[c]));
const ADSENSE = '<script async src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-5278231299883833" crossorigin="anonymous"></script>';
+
+// Big banner ad zone. Renders a live AdSense responsive display unit when the position's slot ID
+// is configured (env AD_SLOT_TOP/MID/BOTTOM); otherwise a clearly-labeled house placeholder so the
+// zone is visibly present without a wired slot. Wiring real slots needs an AdSense-portal step (gated).
+function bannerAd(pos) {
+ const slot = pos === 'top' ? AD_SLOT_TOP : pos === 'mid' ? AD_SLOT_MID : AD_SLOT_BOTTOM;
+ if (slot) {
+ return `<div class="adzone adzone-${pos}"><div class="ad-label">Advertisement</div>`
+ + `<ins class="adsbygoogle" style="display:block" data-ad-client="${esc(AD_CLIENT)}" data-ad-slot="${esc(slot)}" data-ad-format="auto" data-full-width-responsive="true"></ins>`
+ + `<script>(adsbygoogle=window.adsbygoogle||[]).push({});</script></div>`;
+ }
+ return `<div class="adzone adzone-${pos} adzone-house"><div class="ad-label">Advertisement</div>`
+ + `<div class="ad-house"><span class="ad-house-brand">ALL NEWS DAILY</span>`
+ + `<span class="ad-house-sub">Premium banner placement — 970×90 · 728×90 · responsive</span></div></div>`;
+}
const FRONT_CSS = `
:root{--ink:#111;--link:#0000cc;--vis:#551a8b;--red:#c00;--rule:#000;--bg:#f8f7f2;--src:#6a6a6a}
*{box-sizing:border-box}
@@ -181,6 +254,21 @@ main{max-width:1180px;margin:0 auto;padding:0 12px 60px}
.foot nav{margin-bottom:8px}.foot nav a{color:var(--link)}
@media(max-width:960px){.grid{grid-template-columns:repeat(2,1fr)}.col:nth-child(3){border-left:0;padding-left:0}}
@media(max-width:560px){.grid{grid-template-columns:1fr}.col{border-left:0;padding-left:0}.masthead{grid-template-columns:1fr}.tagline-l,.tagline-r{text-align:center}}
+/* Live counter / ticker bar */
+.ticker{display:flex;flex-wrap:wrap;align-items:center;gap:8px 18px;justify-content:center;max-width:1180px;margin:0 auto;padding:6px 16px 4px;font-family:Arial,Helvetica,sans-serif;font-size:12px;text-transform:uppercase;letter-spacing:.6px;color:#333}
+.tk-live{display:inline-flex;align-items:center;gap:7px;font-weight:800;color:var(--red)}
+.tk-blink{width:9px;height:9px;border-radius:50%;background:var(--red);box-shadow:0 0 0 3px rgba(204,0,0,.22);animation:tkpulse 1.3s ease-in-out infinite}
+@keyframes tkpulse{0%,100%{opacity:1}50%{opacity:.3}}
+.tk-seg b{color:#000;font-weight:800;font-variant-numeric:tabular-nums}
+.tk-upd{color:#666;letter-spacing:.4px}
+/* Big banner ad zones */
+.adzone{max-width:1180px;margin:14px auto;padding:9px 12px 12px;border:1px dashed #cbc9bf;background:#efeee7}
+.adzone-top{margin-top:8px}
+.ad-label{font:11px/1 Arial,Helvetica,sans-serif;text-transform:uppercase;letter-spacing:1.6px;color:#9a978c;text-align:center;margin-bottom:8px}
+.ad-house{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:6px;min-height:90px;background:repeating-linear-gradient(45deg,#f7f6f0,#f7f6f0 12px,#f1efe6 12px,#f1efe6 24px);border:1px solid #dddacd}
+.adzone-mid .ad-house{min-height:110px}
+.ad-house-brand{font:800 22px/1 'Times New Roman',Times,serif;letter-spacing:1.5px;color:#111;text-transform:uppercase}
+.ad-house-sub{font:12px/1 Arial,Helvetica,sans-serif;color:#8a8779}
`;
const GUIDE_CSS = `body{margin:0;background:#0d1117;color:#e6edf3;font:17px/1.7 -apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif}
a{color:#58a6ff}.wrap{max-width:760px;margin:0 auto;padding:28px 22px 80px}
@@ -316,7 +404,7 @@ function runChecker() {
const POLL_INTERVAL_MS = parseInt(process.env.POLL_INTERVAL_MS || '90000', 10);
-const WIRE_REFRESH_MS = parseInt(process.env.WIRE_REFRESH_MS || '600000', 10); // 10 min
+const WIRE_REFRESH_MS = parseInt(process.env.WIRE_REFRESH_MS || String(REFRESH_MS), 10); // default 2 min (matches client)
app.listen(PORT, () => {
console.log(`allnewsdaily listening on http://0.0.0.0:${PORT}`);
← 996668c feed balance: cap 3 items per outlet per column so no single
·
back to Allnewsdaily
·
Wire live AdSense display units into the 3 banner zones ef95156 →