← 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);
}