← back to Ventura Corridor
iter 118+119: /magazine.html admin meta strip + /buildings/:bldg/issue per-building mini-issue — /api/magazine SELECT now includes mf.taps + mf.taps_breakdown so cards can show outbound-click counts; /magazine.html cards add a JetBrains Mono meta strip showing ⏱ generated date + 👁 views (metal) + ↗ taps (metal-glow) + Nc editorial length + model name (visible only when values > 0); GET /buildings/:bldg/issue renders a centered Cormorant 54px italic landing page for one building w/ all its featured tenants (cat tag · suite · headline-link · subhead · biz meta) above an 'Also at this address · not yet featured' tail listing un-featured tenants w/ suite numbers; /magazine/:id reader's building-mates section now includes 'Building issue ↗' link
e2338c1e1f1098c80f56fee1190112137da5953b · 2026-05-06 18:07:10 -0700 · SteveStudio2
Files touched
M public/magazine.htmlM src/server/index.ts
Diff
commit e2338c1e1f1098c80f56fee1190112137da5953b
Author: SteveStudio2 <steve@designerwallcoverings.com>
Date: Wed May 6 18:07:10 2026 -0700
iter 118+119: /magazine.html admin meta strip + /buildings/:bldg/issue per-building mini-issue — /api/magazine SELECT now includes mf.taps + mf.taps_breakdown so cards can show outbound-click counts; /magazine.html cards add a JetBrains Mono meta strip showing ⏱ generated date + 👁 views (metal) + ↗ taps (metal-glow) + Nc editorial length + model name (visible only when values > 0); GET /buildings/:bldg/issue renders a centered Cormorant 54px italic landing page for one building w/ all its featured tenants (cat tag · suite · headline-link · subhead · biz meta) above an 'Also at this address · not yet featured' tail listing un-featured tenants w/ suite numbers; /magazine/:id reader's building-mates section now includes 'Building issue ↗' link
---
public/magazine.html | 7 ++++
src/server/index.ts | 91 ++++++++++++++++++++++++++++++++++++++++++++++++++--
2 files changed, 96 insertions(+), 2 deletions(-)
diff --git a/public/magazine.html b/public/magazine.html
index ae9e25b..b1d599b 100644
--- a/public/magazine.html
+++ b/public/magazine.html
@@ -381,6 +381,13 @@ async function load() {
<div class="subhead">${escHtml(r.subhead || '')}</div>
<p class="lede">${escHtml(r.editorial || '')}</p>
${r.pull_quote ? `<div class="pull">"${escHtml(r.pull_quote)}"</div>` : ''}
+ <div style="display:flex;gap:14px;font-family:'JetBrains Mono',monospace;font-size:9px;letter-spacing:.1em;color:var(--ink-mute);margin-top:10px;border-top:1px dotted var(--rule);padding-top:8px">
+ <span title="Generated">⏱ ${new Date(r.generated_at).toLocaleDateString('en-US',{month:'short',day:'numeric'})}</span>
+ ${r.views > 0 ? `<span title="Reader-page views" style="color:var(--metal)">👁 ${r.views}</span>` : ''}
+ ${r.taps > 0 ? `<span title="Outbound taps (share/sponsor/etc)" style="color:var(--metal-glow)">↗ ${r.taps}</span>` : ''}
+ ${r.editorial ? `<span title="Editorial length">${r.editorial.length}c</span>` : ''}
+ <span style="color:var(--ink-mute)">${escHtml(r.model || '')}</span>
+ </div>
<footer>
<div>
<div class="biz">${escHtml(r.name)}</div>
diff --git a/src/server/index.ts b/src/server/index.ts
index 7f075e8..80b09c1 100644
--- a/src/server/index.ts
+++ b/src/server/index.ts
@@ -90,6 +90,7 @@ const ADMIN_PATHS = [
/^\/api\/crawl(\/.*)?$/i,
/^\/postcards(\.html)?\/?$/i,
/^\/buildings(\.html)?\/?$/i,
+ /^\/buildings\/[^/]+\/issue\/?$/i,
/^\/api\/buildings(\.csv|\/.*)?$/i,
/^\/pitch\/\d+\/?$/i,
];
@@ -2015,7 +2016,7 @@ app.get('/api/magazine', async (req, res) => {
const r = await query(
`SELECT mf.id, mf.business_id, mf.headline, mf.subhead, mf.editorial, mf.pull_quote,
mf.category_tag, mf.photo_url, mf.status, mf.model, mf.generated_at,
- mf.reviewed_at, mf.published_at, mf.views, mf.notes,
+ mf.reviewed_at, mf.published_at, mf.views, mf.taps, mf.taps_breakdown, mf.notes,
b.name, b.address, b.city, b.zip,
b.raw->>'primary_naics_description' AS naics,
be.ad_signals,
@@ -2443,6 +2444,89 @@ app.post('/api/magazine/review-drafts', express.json(), async (req, res) => {
// /sponsor/:id — owner-claim form prefilled with the business + feature. Submits
// via POST /api/sponsor/inquiry which emails Steve via George Gmail.
+// /buildings/:bldg/issue — mini-issue showing every feature for one building.
+// Powers a future "this building's tenants" landing page; permalinkable.
+app.get('/buildings/:bldg/issue', async (req, res) => {
+ try {
+ const bldg = decodeURIComponent(req.params.bldg);
+ const r = await query(
+ `SELECT mf.id, mf.headline, mf.subhead, mf.editorial, mf.pull_quote, mf.category_tag,
+ b.name AS biz_name, bc.suite, b.city
+ FROM v_building_canonical bc
+ JOIN businesses b ON b.id = bc.id
+ LEFT JOIN magazine_features mf ON mf.business_id = bc.id
+ WHERE bc.bldg_address = $1
+ ORDER BY mf.id IS NULL ASC, bc.suite NULLS LAST, b.name`,
+ [bldg]
+ );
+ const featured = r.rows.filter((x: any) => x.id);
+ const unfeatured = r.rows.filter((x: any) => !x.id);
+ const esc = (s: any) => String(s ?? '').replace(/[&<>"']/g, c => ({'&':'&','<':'<','>':'>','"':'"',"'":'''}[c]!));
+ res.setHeader('Content-Type', 'text/html; charset=utf-8');
+ res.send(`<!doctype html><html><head><meta charset="utf-8">
+<title>${esc(bldg)} — The Corridor</title>
+<meta name="viewport" content="width=device-width,initial-scale=1">
+<style>
+@import url('https://fonts.googleapis.com/css2?family=Cormorant+Garamond:ital,wght@0,300;0,400;0,500;1,400;1,500&family=Inter:wght@300;400;500&display=swap');
+:root{--paper:#faf6ee;--ink:#1a1815;--ink-mute:#6e6356;--metal:#8a6d3b;--accent:#6a3a1a;--rule:#d8cdb8}
+*{box-sizing:border-box}html,body{margin:0;background:var(--paper);color:var(--ink);font-family:'Inter',sans-serif;font-weight:300;line-height:1.55}
+header{text-align:center;padding:48px 24px 28px;border-bottom:4px double var(--ink)}
+header .kicker{font-size:9px;letter-spacing:.4em;text-transform:uppercase;color:var(--metal);font-weight:500}
+header h1{font-family:'Cormorant Garamond',serif;font-style:italic;font-weight:500;font-size:54px;margin:8px 0 0;color:var(--ink);letter-spacing:-0.02em}
+header h1 em{font-style:normal;color:var(--metal)}
+header .deck{font-family:'Cormorant Garamond',serif;font-style:italic;font-size:16px;color:var(--ink-mute);margin-top:8px}
+main{max-width:780px;margin:0 auto;padding:36px 24px 80px}
+.feature{margin-bottom:36px;padding-bottom:24px;border-bottom:1px solid var(--rule)}
+.feature:last-of-type{border-bottom:none}
+.feature .cat{display:inline-block;background:var(--ink);color:var(--paper);padding:3px 10px;font-size:9px;letter-spacing:.32em;text-transform:uppercase;font-weight:500;margin-bottom:8px}
+.feature .suite{font-size:9px;letter-spacing:.32em;text-transform:uppercase;color:var(--metal);margin-left:8px}
+.feature h2{font-family:'Cormorant Garamond',serif;font-style:italic;font-weight:500;font-size:28px;line-height:1.05;margin:0 0 6px}
+.feature h2 a{color:var(--ink);text-decoration:none}
+.feature .subhead{font-family:'Cormorant Garamond',serif;font-style:italic;font-size:15px;color:var(--ink-mute);margin:0 0 14px}
+.feature .biz{font-size:11px;letter-spacing:.06em;color:var(--ink-mute);font-family:'JetBrains Mono',monospace}
+.unfeatured{margin-top:36px;padding:18px;background:rgba(184,153,104,0.06);border:1px solid var(--rule);font-size:13px}
+.unfeatured h3{font-family:'Cormorant Garamond',serif;font-style:italic;font-weight:500;font-size:18px;color:var(--metal);margin:0 0 10px}
+.unfeatured ul{list-style:none;padding:0;margin:0}
+.unfeatured li{padding:5px 0;border-top:1px dotted var(--rule);display:flex;justify-content:space-between;font-size:13px;color:var(--ink-mute)}
+.unfeatured li:first-child{border-top:none}
+.unfeatured li b{color:var(--ink);font-weight:500}
+.toolbar{position:fixed;top:14px;right:14px;display:flex;gap:6px;z-index:100}
+.toolbar a{font-size:9px;letter-spacing:.22em;text-transform:uppercase;color:var(--ink-mute);background:var(--paper);padding:5px 10px;border:1px solid var(--rule);text-decoration:none}
+.toolbar a:hover{color:var(--metal);border-color:var(--metal)}
+</style></head><body>
+<div class="toolbar">
+ <a href="/issue">All issues</a>
+ <a href="/buildings.html?bldg=${encodeURIComponent(bldg)}">Roster</a>
+</div>
+<header>
+ <div class="kicker">The Corridor · in this building</div>
+ <h1>${esc(bldg)}</h1>
+ <div class="deck">${featured.length} feature${featured.length === 1 ? '' : 's'} · ${r.rowCount} tenant${r.rowCount === 1 ? '' : 's'} on file</div>
+</header>
+<main>
+${featured.length === 0 ? `<p style="text-align:center;font-style:italic;color:var(--ink-mute);padding:60px 0">No features yet for this building.</p>` :
+featured.map((f: any) => `
+ <article class="feature">
+ <div>
+ <span class="cat">${esc(f.category_tag || 'feature')}</span>
+ ${f.suite ? `<span class="suite">Suite ${esc(f.suite)}</span>` : ''}
+ </div>
+ <h2><a href="/magazine/${f.id}">${esc(f.headline || f.biz_name)}</a></h2>
+ <div class="subhead">${esc(f.subhead || '')}</div>
+ <div class="biz">${esc(f.biz_name)}${f.suite ? ' · #' + esc(f.suite) : ''} · ${esc(f.city || '')}</div>
+ </article>
+`).join('')}
+${unfeatured.length > 0 ? `<div class="unfeatured">
+ <h3>Also at this address · not yet featured</h3>
+ <ul>${unfeatured.map((u: any) => `<li><b>${esc(u.biz_name)}</b><span>${u.suite ? '#' + esc(u.suite) : ''}</span></li>`).join('')}</ul>
+</div>` : ''}
+</main>
+</body></html>`);
+ } catch (e: any) {
+ res.status(500).send('error: ' + e.message);
+ }
+});
+
app.get('/sponsor/:id', async (req, res) => {
const id = parseInt(req.params.id, 10);
if (!Number.isFinite(id)) return res.status(400).send('bad id');
@@ -2903,7 +2987,10 @@ footer{margin-top:48px;padding:24px 0;text-align:center;font-size:10px;letter-sp
<div style="margin-top:8px;font-size:10px;color:var(--ink-mute);font-style:italic">From the DW outreach pipeline that produces the magazine — every walk, every reply, every status change.</div>
</details>` : ''}
${mates.rows.length > 0 ? `<section style="margin-top:48px;padding-top:24px;border-top:1px solid var(--rule)">
- <div style="font-size:9px;letter-spacing:.32em;text-transform:uppercase;color:var(--ink-mute);margin-bottom:14px">In the same building · ${esc(bldg)}</div>
+ <div style="display:flex;justify-content:space-between;align-items:baseline;font-size:9px;letter-spacing:.32em;text-transform:uppercase;color:var(--ink-mute);margin-bottom:14px">
+ <span>In the same building · ${esc(bldg)}</span>
+ <a href="/buildings/${encodeURIComponent(bldg)}/issue" style="color:var(--metal);text-decoration:none">Building issue ↗</a>
+ </div>
<div style="display:flex;flex-direction:column;gap:6px">
${mates.rows.map((m: any) => m.id ? `<a href="/magazine/${m.id}" style="color:var(--ink);text-decoration:none;display:flex;justify-content:space-between;align-items:baseline;padding:6px 0;border-bottom:1px dotted var(--rule)">
<span style="font-family:'Cormorant Garamond',serif;font-style:italic;font-size:16px">${esc(m.headline || m.biz_name)}</span>
← cfd5c5a iter 115+116+117: og/twitter/JSON-LD meta + /api/magazine.js
·
back to Ventura Corridor
·
iter 120: public /find — reader-facing finder by suite/stree 93a2f1a →