Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 | 2x 2x 2x 197x 197x 197x 852x 44x 44x 808x 197x 197x 96x 96x 96x 2x 2x 96x 96x 96x 96x 96x 384x 384x 96x 126x 104x | /**
* The Shade Card social preview (1200×630): a black cover board with an
* index-red tab on card stock, beside a laminate-teal shade strip. One
* template for the /og/[type].svg route and scripts/generate-og-images.ts,
* which rasterises it to the public/og/*.png files pages reference.
*
* Colours are the light-theme values from src/styles/global.css (an image
* cannot read CSS custom properties). Type is a system sans: resvg cannot
* read Anek Latin's woff2, and it renders only exact weights (700, not 760).
*/
import { escapeXml } from "./og-utils";
const C = {
ground: "#EFF0EB", // --background-base, card stock
cover: "#141615", // --cover
onCover: "#EFF0EB", // --on-cover
onCoverMuted: "#B4B9B3", // --on-cover-muted
index: "#D23A1F", // --index
strip: ["#D3E6DF", "#79AC9E", "#2F6F62", "#1C4C43"], // --primary-100/300/500/700
};
const FONT = "'Helvetica Neue', Helvetica, Arial, 'DejaVu Sans', sans-serif";
const LOGO_PATH =
"M 66.42 118.08 C66.19,117.85 66.00,108.29 66.00,96.83 L 66.00 76.00 L 62.93 76.00 C60.57,76.00 59.93,75.60 60.18,74.29 C60.43,73.01 62.73,72.14 69.50,70.78 C74.44,69.79 79.05,69.19 79.74,69.46 C80.70,69.82 81.00,75.31 81.00,92.47 L 81.00 115.00 L 102.50 115.00 L 124.00 115.00 L 124.00 86.54 L 124.00 58.09 L 99.25 41.08 C85.64,31.72 73.78,24.05 72.90,24.03 C71.33,24.00 27.69,53.24 23.23,57.31 C20.97,59.37 20.97,59.42 21.23,86.94 L 21.50 114.50 L 38.25 114.77 C52.23,115.00 55.00,115.29 55.00,116.52 C55.00,117.77 52.28,118.00 37.62,118.00 C24.94,118.00 19.97,117.66 19.21,116.75 C17.96,115.23 17.11,58.10 18.32,56.24 C18.77,55.54 21.69,53.26 24.82,51.17 C30.27,47.52 37.14,42.82 59.91,27.16 C65.88,23.06 71.37,20.00 72.78,20.00 C74.13,20.00 77.32,21.50 79.87,23.34 C82.41,25.18 93.05,32.42 103.50,39.44 C113.95,46.46 123.74,53.51 125.25,55.11 L 128.00 58.03 L 128.00 86.32 C128.00,107.40 127.68,115.04 126.75,116.29 C125.64,117.78 122.23,117.99 96.17,118.23 C80.03,118.38 66.64,118.31 66.42,118.08 ZM 70.00 63.33 C69.18,63.01 67.26,61.60 65.75,60.19 C62.00,56.69 61.86,50.05 65.45,46.45 C68.83,43.08 75.75,43.02 79.59,46.33 C83.41,49.61 83.62,56.83 80.00,60.45 C77.44,63.01 72.69,64.38 70.00,63.33 Z";
/** Greedy word wrap by character count; the last line takes any overflow. */
export function wrapLines(text: string, maxChars: number, maxLines: number): string[] {
const lines: string[] = [];
let line = "";
for (const word of text.split(/\s+/).filter(Boolean)) {
if (line && `${line} ${word}`.length > maxChars && lines.length < maxLines - 1) {
lines.push(line);
line = word;
} else {
line = line ? `${line} ${word}` : word;
}
}
Eif (line) lines.push(line);
return lines;
}
export function shadeCardSvg({ title, subtitle }: { title: string; subtitle: string }): string {
// Cover board: x 64–824, y 88–566. Text column: x 104–784.
let size = 60;
let lines = wrapLines(title, 22, 3);
if (lines.length > 2) {
size = 52;
lines = wrapLines(title, 26, 3);
}
const lead = Math.round(size * 1.04);
const titleTop = 214 + size;
const subLines = wrapLines(subtitle, 46, 2);
const subTop = titleTop + (lines.length - 1) * lead + 56;
const chips = C.strip
.map((fill, i) => {
const x = 856 + i * 72;
return `<rect x="${x}" y="88" width="64" height="478" rx="4" fill="${fill}" stroke="rgba(20,22,21,0.08)"/>
<circle cx="${x + 16}" cy="104" r="6" fill="${C.ground}"/>`;
})
.join("\n ");
return `<?xml version="1.0" encoding="UTF-8"?>
<svg width="1200" height="630" viewBox="0 0 1200 630" xmlns="http://www.w3.org/2000/svg">
<rect width="1200" height="630" fill="${C.ground}"/>
<path d="M112 88 V64 a4 4 0 0 1 4 -4 H240 a4 4 0 0 1 4 4 V88 Z" fill="${C.index}"/>
<rect x="64" y="88" width="760" height="478" rx="4" fill="${C.cover}"/>
<circle cx="80" cy="104" r="6" fill="${C.ground}"/>
<g transform="translate(98, 118) scale(0.34)"><path d="${LOGO_PATH}" fill="${C.onCover}"/></g>
<text x="150" y="152" font-size="30" font-weight="700" font-family="${FONT}" fill="${C.onCover}">Interioring</text>
${lines
.map(
(l, i) =>
`<text x="104" y="${titleTop + i * lead}" font-size="${size}" font-weight="700" font-family="${FONT}" fill="${C.onCover}">${escapeXml(l)}</text>`,
)
.join("\n ")}
${subLines
.map(
(l, i) =>
`<text x="104" y="${subTop + i * 34}" font-size="26" font-family="${FONT}" fill="${C.onCoverMuted}">${escapeXml(l)}</text>`,
)
.join("\n ")}
<rect x="104" y="500" width="680" height="1" fill="${C.onCover}" fill-opacity="0.2"/>
<text x="104" y="534" font-size="17" letter-spacing="2.4" font-family="${FONT}" fill="${C.onCoverMuted}">INTERIORING.COM</text>
${chips}
</svg>`;
}
|