← back to Beverlyhillsvideos
social/poster/caption.mjs
82 lines
// caption.mjs — assemble an on-brand IG caption + rotating hashtags for a film.
// Voice + hashtag pools lifted from social/LAUNCH-PACK.md and social/CAPTIONS.md.
// Always-on core tags (broad reach + brand + place).
const CORE = ['#beverlyhills', '#beverlyhillsvideos', '#90210', '#rodeodrive'];
// Rotating pools — pick a spread so every post's tag set differs slightly.
const DINING = [
'#finedining', '#beverlyhillsrestaurants', '#luxurydining', '#laeats',
'#losangelesfood', '#eaterla', '#californiacuisine', '#michelin',
'#wheretoeat', '#foodie', '#luxurylifestyle', '#visitbeverlyhills',
'#luxurytravel', '#cityguide', '#cinematic',
];
// Deterministic-ish rotation by index so consecutive days don't repeat sets.
function rotate(pool, n, seed) {
const out = [];
const start = Math.abs(seed) % pool.length;
for (let i = 0; out.length < n && i < pool.length; i++) {
out.push(pool[(start + i) % pool.length]);
}
return out;
}
// Store films name real brands, so every store caption carries a trademark /
// no-affiliation disclaimer (compliance guardrail C, TK-10309).
const STORE_DISCLAIMER =
'Beverly Hills Videos is an independent editorial guide — not affiliated with, sponsored by, or endorsed by the brands shown. Trademarks belong to their respective owners.';
const SHOPPING = [
'#luxuryshopping', '#rodeodrivestyle', '#designerfashion', '#luxurylifestyle',
'#beverlyhillsshopping', '#hautecouture', '#luxurybrands', '#shoppingdistrict',
'#luxurytravel', '#cityguide', '#style', '#fashion', '#visitbeverlyhills', '#cinematic',
];
// Category → natural SEO keyword phrase (Instagram indexes caption text).
const KW = {
Fashion: 'designer fashion', Jewelry: 'fine jewelry', Watches: 'luxury watches',
Accessories: 'luxury accessories', Beauty: 'beauty & skincare', Fragrance: 'fine fragrance',
Eyewear: 'designer eyewear', Home: 'luxury home', Luggage: 'luxury luggage',
};
function storeKw(cat) {
const c = (cat || '').split(/[\/·]/)[0].trim();
return KW[c] || 'luxury retail';
}
export function buildCaption(film, seedIndex = 0) {
const isStore = film.type === 'store';
const blurb = (film.blurb || '').trim().replace(/\s+/g, ' ');
const pool = isStore ? SHOPPING : DINING;
const tags = [...CORE, ...rotate(pool, 11, seedIndex)];
if (isStore) {
const cat = film.category || 'Luxury';
const kw = storeKw(cat);
const hook = `${film.name} — ${cat} on Rodeo Drive, Beverly Hills.${blurb ? ' ' + blurb : ''}`;
const body = [
hook,
'',
`📍 Rodeo Drive · Beverly Hills, CA 90210`,
`🛍️ ${kw} in Beverly Hills — designer boutiques & flagship stores on Rodeo Drive.`,
`🎬 Explore the full Beverly Hills shopping & dining guide → link in bio`,
'',
STORE_DISCLAIMER,
'',
tags.join(' '),
].join('\n');
return body.slice(0, 2190);
}
const hook = blurb ? `${film.name}. ${blurb}` : `${film.name} — Beverly Hills, in film.`;
const body = [
hook,
'',
`📍 Beverly Hills, CA 90210 · Fine dining`,
`🍽️ Where to eat in Beverly Hills — restaurant films, menus & the interactive map.`,
`🎬 Full guide → link in bio`,
'',
tags.join(' '),
].join('\n');
return body.slice(0, 2190);
}