← back to Small Business Builder

src/lib/svg-hero.js

89 lines

// Deterministic SVG hero pattern — used both as mockup background and as
// builds-index card background. 6 patterns, hashed off seed. Pure data-URI,
// no network, no images.

function hashToInt(s, max) {
  let h = 5381;
  for (let i = 0; i < s.length; i += 1) h = (h * 33 + s.charCodeAt(i)) >>> 0;
  return h % max;
}

export function svgHero(seed, c1 = '#111111', c2 = '#facc15') {
  const v = hashToInt(seed, 6);
  const a = encodeURIComponent(c1);
  const b = encodeURIComponent(c2);
  const svgs = [
    `<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 600 800'><rect width='600' height='800' fill='${a}'/><g fill='none' stroke='${b}' stroke-width='2' opacity='.5'>${[...Array(12)].map((_,i)=>`<circle cx='300' cy='400' r='${30+i*32}'/>`).join('')}</g></svg>`,
    `<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 600 800'><rect width='600' height='800' fill='${a}'/><g stroke='${b}' stroke-width='10' opacity='.6'>${[...Array(20)].map((_,i)=>`<line x1='${-200+i*60}' y1='0' x2='${200+i*60}' y2='800'/>`).join('')}</g></svg>`,
    `<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 600 800'><rect width='600' height='800' fill='${a}'/><g fill='${b}'>${[...Array(10)].flatMap((_,r)=>[...Array(8)].map((__,c)=>`<circle cx='${30+c*75}' cy='${30+r*82}' r='8'/>`)).join('')}</g></svg>`,
    `<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 600 800'><rect width='600' height='800' fill='${a}'/><polyline fill='none' stroke='${b}' stroke-width='12' opacity='.7' points='${[...Array(40)].map((_,i)=>`${i*20},${i%2?100:50}`).join(' ')}'/><polyline fill='none' stroke='${b}' stroke-width='12' opacity='.7' points='${[...Array(40)].map((_,i)=>`${i*20},${i%2?500:450}`).join(' ')}'/></svg>`,
    `<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 600 800'><rect width='600' height='800' fill='${a}'/><rect x='80' y='60' width='320' height='400' fill='${b}' opacity='.6'/><rect x='200' y='220' width='320' height='480' fill='${b}' opacity='.4'/><rect x='40' y='400' width='280' height='320' fill='${b}' opacity='.7'/></svg>`,
    `<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 600 800'><rect width='600' height='800' fill='${a}'/><g stroke='${b}' stroke-width='4' opacity='.55'>${[...Array(36)].map((_,i)=>`<line x1='300' y1='400' x2='${300+Math.cos(i/36*Math.PI*2)*600}' y2='${400+Math.sin(i/36*Math.PI*2)*600}'/>`).join('')}</g><circle cx='300' cy='400' r='80' fill='${b}'/></svg>`,
  ];
  const svg = svgs[v].replace(/'/g, '"');
  return `data:image/svg+xml;utf8,${encodeURIComponent(svg)}`;
}

const VERT_PALETTE = {
  salon:        ['#1f1117', '#f4a8c0'],
  restaurant:   ['#1a0e0a', '#e8843e'],
  lawyer:       ['#0f1830', '#88aae0'],
  vet:          ['#0a2024', '#5fc09a'],
  advocacy:     ['#0a0a0a', '#dc2626'],
  'saas-landing': ['#0d141f', '#7aa6ff'],
  ecom:         ['#1a0d24', '#cf76e6'],
  generic:      ['#0a0a0a', '#facc15'],
};

export function paletteFor(vertical) {
  return VERT_PALETTE[vertical] || VERT_PALETTE.generic;
}

// Build a CSS stylesheet containing all 6 patterns × 8 verticals = 48 classes.
// Cards reference by class name → page weight stays flat regardless of card
// count.
export function svgHeroStylesheet() {
  const verts = Object.keys(VERT_PALETTE);
  const lines = [];
  for (const v of verts) {
    const [c1, c2] = VERT_PALETTE[v];
    for (let p = 0; p < 6; p += 1) {
      const seed = `pattern-${p}`; // forces hash to land on pattern p? no — better to pass forced index
    }
  }
  // Simpler: encode 48 patterns directly by forcing `v` (variant) without hash
  // — accept (vertical, pattern_idx) as the class signature.
  const out = [];
  for (const v of verts) {
    const [c1, c2] = VERT_PALETTE[v];
    for (let p = 0; p < 6; p += 1) {
      const uri = svgHeroForceVariant(p, c1, c2);
      out.push(`.bg-${v.replace('-', '_')}-${p}{background-image:linear-gradient(180deg,rgba(255,255,255,.92) 0%,rgba(255,255,255,.86) 60%,rgba(255,255,255,.96) 100%),url('${uri}');background-size:cover;background-position:center}`);
    }
  }
  return out.join('\n');
}

export function classFor(seed, vertical) {
  let h = 5381;
  for (let i = 0; i < seed.length; i += 1) h = (h * 33 + seed.charCodeAt(i)) >>> 0;
  const p = h % 6;
  const v = (vertical && VERT_PALETTE[vertical]) ? vertical : 'generic';
  return `bg-${v.replace('-', '_')}-${p}`;
}

function svgHeroForceVariant(v, c1, c2) {
  const a = encodeURIComponent(c1);
  const b = encodeURIComponent(c2);
  const svgs = [
    `<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 600 800'><rect width='600' height='800' fill='${a}'/><g fill='none' stroke='${b}' stroke-width='2' opacity='.5'>${[...Array(12)].map((_,i)=>`<circle cx='300' cy='400' r='${30+i*32}'/>`).join('')}</g></svg>`,
    `<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 600 800'><rect width='600' height='800' fill='${a}'/><g stroke='${b}' stroke-width='10' opacity='.6'>${[...Array(20)].map((_,i)=>`<line x1='${-200+i*60}' y1='0' x2='${200+i*60}' y2='800'/>`).join('')}</g></svg>`,
    `<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 600 800'><rect width='600' height='800' fill='${a}'/><g fill='${b}'>${[...Array(10)].flatMap((_,r)=>[...Array(8)].map((__,c)=>`<circle cx='${30+c*75}' cy='${30+r*82}' r='8'/>`)).join('')}</g></svg>`,
    `<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 600 800'><rect width='600' height='800' fill='${a}'/><polyline fill='none' stroke='${b}' stroke-width='12' opacity='.7' points='${[...Array(40)].map((_,i)=>`${i*20},${i%2?100:50}`).join(' ')}'/><polyline fill='none' stroke='${b}' stroke-width='12' opacity='.7' points='${[...Array(40)].map((_,i)=>`${i*20},${i%2?500:450}`).join(' ')}'/></svg>`,
    `<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 600 800'><rect width='600' height='800' fill='${a}'/><rect x='80' y='60' width='320' height='400' fill='${b}' opacity='.6'/><rect x='200' y='220' width='320' height='480' fill='${b}' opacity='.4'/><rect x='40' y='400' width='280' height='320' fill='${b}' opacity='.7'/></svg>`,
    `<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 600 800'><rect width='600' height='800' fill='${a}'/><g stroke='${b}' stroke-width='4' opacity='.55'>${[...Array(36)].map((_,i)=>`<line x1='300' y1='400' x2='${300+Math.cos(i/36*Math.PI*2)*600}' y2='${400+Math.sin(i/36*Math.PI*2)*600}'/>`).join('')}</g><circle cx='300' cy='400' r='80' fill='${b}'/></svg>`,
  ];
  const svg = svgs[v].replace(/'/g, '"');
  return `data:image/svg+xml;utf8,${encodeURIComponent(svg)}`;
}