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