← back to Dw Slideshow Gen

lib/design.js

72 lines

// lib/design.js — read a project DESIGN.md into brand tokens, with DW-luxe defaults.
// Tolerant parser: every field has a default, and `found:false` is returned when no
// DESIGN.md is present so callers can warn. One source of truth for fonts/colors/logo
// across every slideshow.
import fs from 'node:fs'
import { exists } from './util.js'

// Designer Wallcoverings house defaults — warm-charcoal ground, cream type, brass accent,
// Didot display serif (fashion-magazine luxe) over Avenir body.
export const DW_DEFAULTS = {
  brand: 'Designer Wallcoverings',
  kicker: 'DESIGNER WALLCOVERINGS',
  url: 'designerwallcoverings.com',
  // fonts: family alias -> font file we register at render time
  fonts: {
    display: { family: 'Didot', path: '/System/Library/Fonts/Supplemental/Didot.ttc' },
    body:    { family: 'Avenir', path: '/System/Library/Fonts/Avenir.ttc' },
  },
  colors: {
    bgTop: '#1C1B19',   // warm charcoal
    bgBot: '#2A2723',
    ink:   '#F5F0E6',   // warm cream
    accent:'#C2A45A',   // muted brass/gold
    muted: '#B9B2A4',   // soft taupe for subtitles
  },
  cta: { label: 'Shop the collection', sub: 'designerwallcoverings.com' },
  voice: 'editorial, calm, design-forward — speaks to interior designers and discerning homeowners',
  hashtags: ['#designerwallcoverings', '#wallcovering', '#interiordesign', '#wallpaper', '#homedecor'],
}

// extremely tolerant key:value-ish extractor from a DESIGN.md
function pull(md, keys) {
  for (const k of keys) {
    const re = new RegExp(`(?:^|\\n)\\s*(?:[-*]\\s*)?\\*{0,2}${k}\\*{0,2}\\s*[:=]\\s*(.+)`, 'i')
    const m = md.match(re)
    if (m) return m[1]
      .replace(/<!--.*?-->/g, '')   // strip trailing HTML comments
      .replace(/\*/g, '')           // strip markdown bold markers
      .trim()
      .replace(/^["'`]|["'`]$/g, '')
  }
  return null
}

export function loadDesign(designPath) {
  const d = structuredClone(DW_DEFAULTS)
  if (!designPath || !exists(designPath)) return { ...d, found: false, source: null }
  const md = fs.readFileSync(designPath, 'utf8')

  const brand  = pull(md, ['brand', 'brand name', 'name'])
  const url    = pull(md, ['url', 'website', 'domain'])
  const accent = pull(md, ['accent', 'accent color', 'primary'])
  const ink    = pull(md, ['ink', 'text color', 'foreground'])
  const bgTop  = pull(md, ['bg', 'background', 'bg top', 'background top'])
  const bgBot  = pull(md, ['bg bottom', 'background bottom'])
  const cta    = pull(md, ['cta', 'call to action', 'cta label'])
  const voice  = pull(md, ['voice', 'tone'])
  const tags   = pull(md, ['hashtags', 'tags'])

  if (brand) { d.brand = brand; d.kicker = brand.toUpperCase() }
  if (url)   { d.url = url; d.cta.sub = url }
  if (accent) d.colors.accent = accent
  if (ink)    d.colors.ink = ink
  if (bgTop)  d.colors.bgTop = bgTop
  if (bgBot)  d.colors.bgBot = bgBot || bgTop
  if (cta)    d.cta.label = cta
  if (voice)  d.voice = voice
  if (tags)   d.hashtags = tags.split(/[,\s]+/).filter(Boolean).map(t => t.startsWith('#') ? t : '#' + t).slice(0, 5)

  return { ...d, found: true, source: designPath }
}