← back to Ventura Corridor
iter 154: /magazine/:id/ad-pack.html — short/medium/long copy-ready brief variants for biz owners + reader-page link
a80fd42611e2099d7dd706ed35f7bab0a3e8cff4 · 2026-05-06 19:23:43 -0700 · SteveStudio2
Files touched
Diff
commit a80fd42611e2099d7dd706ed35f7bab0a3e8cff4
Author: SteveStudio2 <stevestudio2@SteveStacStudio.lan>
Date: Wed May 6 19:23:43 2026 -0700
iter 154: /magazine/:id/ad-pack.html — short/medium/long copy-ready brief variants for biz owners + reader-page link
---
src/server/index.ts | 110 ++++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 110 insertions(+)
diff --git a/src/server/index.ts b/src/server/index.ts
index 15349eb..cb50859 100644
--- a/src/server/index.ts
+++ b/src/server/index.ts
@@ -73,6 +73,7 @@ const ADMIN_PATHS = [
/^\/magazine(\.html)?\/?$/i,
/^\/api\/magazine(\.csv|\/.*)?$/i,
/^\/magazine\/\d+\/?$/i,
+ /^\/magazine\/\d+\/(ad-pack|print)\.html\/?$/i,
/^\/share\/\d+\/?$/i,
/^\/issue(\/[\w-]+)?\/?$/i,
/^\/issue\.pdf\/?$/i,
@@ -3086,6 +3087,114 @@ app.get('/api/find', async (req, res) => {
}
});
+// /magazine/:id/ad-pack.html — copy-ready ad-brief variants for the feature.
+// Three lengths: short (Twitter/X / GBP), medium (FB/IG), long (full editorial extract).
+// Steve hands this URL to a business owner so they can pick the brief that suits.
+app.get('/magazine/:id/ad-pack.html', async (req, res) => {
+ const id = parseInt(req.params.id, 10);
+ if (!Number.isFinite(id)) return res.status(400).send('bad id');
+ const r = await query(
+ `SELECT mf.headline, mf.subhead, mf.editorial, mf.pull_quote, mf.category_tag,
+ b.name AS biz_name, b.address AS biz_address, b.city
+ FROM magazine_features mf JOIN businesses b ON b.id = mf.business_id
+ WHERE mf.id = $1`, [id]
+ );
+ if (r.rowCount === 0) return res.status(404).send('not found');
+ const f = r.rows[0];
+ const esc = (s: any) => String(s ?? '').replace(/[&<>"']/g, c => ({'&':'&','<':'<','>':'>','"':'"',"'":'''}[c]!));
+ // Three brief variants — all derived from existing fields, no new gen needed.
+ const tagline = String(f.subhead || (f.editorial || '').split(/[.!?]\s/)[0] || '').slice(0, 90);
+ const short = String(f.subhead || tagline).slice(0, 90);
+ const medium = (() => {
+ const lede = String(f.editorial || '').split(/(?<=[.!?])\s+/).slice(0, 2).join(' ');
+ return lede.slice(0, 280);
+ })();
+ const long = String(f.editorial || '').slice(0, 540).replace(/\s+\S*$/, '') + '…';
+ const tags = ['#TheCorridor', '#VenturaBoulevard', '#LosAngeles'];
+ if (f.category_tag) tags.push('#' + String(f.category_tag).replace(/[^a-z0-9]/gi, ''));
+
+ res.setHeader('Content-Type', 'text/html; charset=utf-8');
+ res.send(`<!doctype html><html lang="en"><head><meta charset="utf-8">
+<title>Ad pack · ${esc(f.headline || f.biz_name)}</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,400;1,400;1,500&family=Inter:wght@300;400;500&family=JetBrains+Mono:wght@200;300&display=swap');
+:root{--paper:#faf6ee;--ink:#1a1815;--ink-mute:#6e6356;--metal:#8a6d3b;--metal-glow:#b89968;--accent:#6a3a1a;--rule:#d8cdb8}
+*{box-sizing:border-box}body{margin:0;background:var(--paper);color:var(--ink);font-family:'Inter',sans-serif;font-weight:300;line-height:1.55;padding:36px 24px;max-width:780px;margin:0 auto}
+.kicker{font-size:9px;letter-spacing:.4em;text-transform:uppercase;color:var(--metal);font-weight:500}
+h1{font-family:'Cormorant Garamond',serif;font-style:italic;font-weight:500;font-size:42px;margin:8px 0 6px;color:var(--ink);line-height:1.05}
+.biz{font-family:'Cormorant Garamond',serif;font-style:italic;color:var(--ink-mute);margin-bottom:28px;font-size:16px}
+.variant{margin:24px 0;padding:24px 22px;border:1px solid var(--rule);background:rgba(184,153,104,0.04);position:relative}
+.variant h3{font-family:'Cormorant Garamond',serif;font-style:italic;color:var(--metal);margin:0 0 4px;font-size:22px;font-weight:500}
+.variant .where{font-size:10px;letter-spacing:.32em;text-transform:uppercase;color:var(--ink-mute);margin-bottom:16px}
+.variant .where b{color:var(--metal-glow)}
+.variant .text{font-family:'Cormorant Garamond',serif;font-style:italic;font-size:18px;line-height:1.55;color:var(--ink);white-space:pre-wrap;margin-bottom:14px}
+.variant .meta{font-family:'JetBrains Mono',monospace;font-size:11px;color:var(--ink-mute);margin-top:6px}
+.variant .copy{position:absolute;top:14px;right:14px;font-size:9px;letter-spacing:.32em;text-transform:uppercase;color:var(--metal);background:transparent;border:1px solid var(--rule);padding:6px 12px;cursor:pointer;font-family:'Inter',sans-serif}
+.variant .copy:hover{border-color:var(--metal)}
+.variant .copy.ok{color:var(--accent);border-color:var(--accent)}
+.tags{font-family:'JetBrains Mono',monospace;font-size:12px;color:var(--metal);margin-top:18px}
+.foot{margin-top:36px;font-size:11px;color:var(--ink-mute);text-align:center;border-top:1px solid var(--rule);padding-top:18px}
+.foot a{color:var(--metal);text-decoration:none}
+@media print{.copy{display:none}body{padding:8px}}
+</style></head><body>
+<div class="kicker">The Corridor · ad pack</div>
+<h1>${esc(f.headline || f.biz_name)}</h1>
+<div class="biz">${esc(f.biz_name)}${f.biz_address ? ' · ' + esc(f.biz_address) : ''}${f.city ? ' · ' + esc(f.city) : ''}</div>
+
+<div class="variant">
+ <h3>Short</h3>
+ <div class="where">For: <b>Twitter / X · Google Business Profile · SMS</b></div>
+ <button class="copy" onclick="copyText(this, ${JSON.stringify(short).replace(/"/g, '"')})">Copy</button>
+ <div class="text">${esc(short)}</div>
+ <div class="meta">${short.length} chars</div>
+</div>
+
+<div class="variant">
+ <h3>Medium</h3>
+ <div class="where">For: <b>Facebook · Instagram caption · LinkedIn</b></div>
+ <button class="copy" onclick="copyText(this, ${JSON.stringify(medium + '\n\n' + tags.join(' ')).replace(/"/g, '"')})">Copy</button>
+ <div class="text">${esc(medium)}</div>
+ <div class="tags">${tags.join(' ')}</div>
+ <div class="meta">${medium.length} chars + tags</div>
+</div>
+
+<div class="variant">
+ <h3>Long</h3>
+ <div class="where">For: <b>Newsletter · About-us page · Press kit</b></div>
+ <button class="copy" onclick="copyText(this, ${JSON.stringify(long).replace(/"/g, '"')})">Copy</button>
+ <div class="text">${esc(long)}</div>
+ <div class="meta">${long.length} chars</div>
+</div>
+
+${f.pull_quote ? `<div class="variant">
+ <h3>Pull quote</h3>
+ <div class="where">For: <b>Hero callout · Stand-alone graphic</b></div>
+ <button class="copy" onclick="copyText(this, ${JSON.stringify('"' + f.pull_quote + '"').replace(/"/g, '"')})">Copy</button>
+ <div class="text" style="font-size:24px;color:var(--accent)">"${esc(f.pull_quote)}"</div>
+ <div class="meta">${(f.pull_quote || '').length} chars</div>
+</div>` : ''}
+
+<div class="foot">
+ Source: <a href="/magazine/${id}">/magazine/${id}</a> ·
+ <a href="/share/${id}">share card</a> ·
+ <a href="/api/magazine/${id}/audio.m4a">audio</a> ·
+ <a href="javascript:window.print()">print</a>
+</div>
+
+<script>
+function copyText(btn, txt) {
+ navigator.clipboard.writeText(txt).then(() => {
+ const o = btn.textContent;
+ btn.textContent = '✓ copied';
+ btn.classList.add('ok');
+ setTimeout(() => { btn.textContent = o; btn.classList.remove('ok'); }, 1500);
+ });
+}
+</script>
+</body></html>`);
+});
+
// /magazine/:id/print.html — minimal print-only single-feature, no toolbar/carousels
app.get('/magazine/:id/print.html', async (req, res) => {
const id = parseInt(req.params.id, 10);
@@ -3877,6 +3986,7 @@ footer{margin-top:48px;padding:24px 0;text-align:center;font-size:10px;letter-sp
<div>
<a href="/api/magazine/${id}/tap?to=share&dest=${encodeURIComponent(`${baseUrl}/share/${id}`)}" target="_blank">📱 IG share-card</a>
<a href="/api/magazine/${id}/audio.aiff" target="_blank" title="Listen — Samantha reads this feature">🔊 Listen</a>
+ <a href="/magazine/${id}/ad-pack.html" target="_blank" title="Copy-ready ad briefs">📋 Ad pack</a>
<a href="/api/magazine/${id}/tap?to=sponsor&dest=${encodeURIComponent(`${baseUrl}/sponsor/${id}`)}" style="color:var(--accent,#6a3a1a);border-color:var(--accent,#6a3a1a)">⭐ Sponsor</a>
<a href="javascript:window.print()">🖨 Print</a>
<a href="/pitches.html?id=${f.business_id}" target="_blank">Pipeline ↗</a>
← b1955e0 iter 153: today.html ticker rail — '/api/magazine/activity'
·
back to Ventura Corridor
·
iter 155: /coverage.html + /api/coverage — corridor coverage 1feb0f9 →