Files
shd.build/scripts/render-brand-assets.mjs
T
jb dd84dff055
Validate and deploy production / validate (push) Successful in 23s
Validate and deploy staging / validate (push) Successful in 50s
Validate and deploy production / deploy (push) Failing after 40s
Validate and deploy staging / deploy (push) Failing after 7s
feat: add community publishing and news syndication
2026-08-01 21:42:09 -04:00

183 lines
7.9 KiB
JavaScript

import fs from 'node:fs';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import opentype from 'opentype.js';
import sharp from 'sharp';
const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
const outputDirectory = path.join(root, 'static', 'brand');
const fontDirectory = path.join(
root,
'node_modules',
'@fontsource',
'barlow',
'files',
);
const heavy = opentype.parse(
fs.readFileSync(path.join(fontDirectory, 'barlow-latin-900-normal.woff'))
.buffer,
);
const medium = opentype.parse(
fs.readFileSync(path.join(fontDirectory, 'barlow-latin-700-normal.woff'))
.buffer,
);
fs.mkdirSync(outputDirectory, { recursive: true });
const colors = {
background: '#0a0c0d',
surface: '#111415',
border: '#303536',
grid: '#2a3031',
orange: '#f6a821',
white: '#ecebe5',
};
function trackedPath(font, value, x, baseline, size, tracking = 0) {
let cursor = x;
let data = '';
for (const character of value) {
const glyph = font.charToGlyph(character);
data += glyph.getPath(cursor, baseline, size).toPathData(2);
cursor += (glyph.advanceWidth * size) / font.unitsPerEm + tracking;
}
return { data, width: cursor - x - tracking };
}
function emblem(x, y, size) {
const scale = size / 100;
return `
<g transform="translate(${x} ${y}) scale(${scale})">
<g fill="none" stroke="${colors.orange}" stroke-width="12" stroke-linecap="butt">
<path d="M73 20A38 38 0 0 0 15 63"/>
<path d="M85 37A38 38 0 0 1 55 88"/>
<path d="M42 87A38 38 0 0 1 18 70"/>
</g>
<rect x="72" y="23" width="10" height="10" fill="${colors.orange}"/>
<path d="M62 35H35V65H62M68 43V57" fill="none" stroke="${colors.white}" stroke-width="9" stroke-linecap="butt" stroke-linejoin="miter"/>
</g>`;
}
async function writePng(svg, filename, width, height) {
await sharp(Buffer.from(svg))
.resize(width, height, { fit: 'fill' })
.png({ compressionLevel: 9, adaptiveFiltering: true })
.toFile(path.join(outputDirectory, filename));
}
const transparentLogo = `
<svg xmlns="http://www.w3.org/2000/svg" width="1024" height="1024" viewBox="0 0 1024 1024">
${emblem(72, 72, 880)}
</svg>`;
const serverIcon = `
<svg xmlns="http://www.w3.org/2000/svg" width="1024" height="1024" viewBox="0 0 1024 1024">
<rect width="1024" height="1024" fill="${colors.background}"/>
<path d="M0 192H1024M0 512H1024M0 832H1024M192 0V1024M512 0V1024M832 0V1024" stroke="${colors.grid}" stroke-width="2" opacity=".32"/>
<rect x="96" y="96" width="832" height="832" fill="none" stroke="${colors.border}" stroke-width="3"/>
${emblem(132, 132, 760)}
</svg>`;
const shd = trackedPath(heavy, 'SHD', 0, 0, 190, -3);
const build = trackedPath(heavy, '.build', 0, 0, 190, -2);
const descriptor = trackedPath(medium, 'RESURGENCE DATA & TOOLS', 0, 0, 43, 7);
const wordmarkWidth = shd.width + build.width;
const lockupX = 600;
const wordmarkBaseline = 590;
const banner = `
<svg xmlns="http://www.w3.org/2000/svg" width="1920" height="1080" viewBox="0 0 1920 1080">
<defs>
<radialGradient id="signal" cx="34%" cy="58%" r="54%">
<stop offset="0" stop-color="#2b2416"/>
<stop offset=".44" stop-color="#141617"/>
<stop offset="1" stop-color="${colors.background}"/>
</radialGradient>
<linearGradient id="fade" x1="0" x2="1">
<stop stop-color="${colors.orange}" stop-opacity=".22"/>
<stop offset=".7" stop-color="${colors.orange}" stop-opacity="0"/>
</linearGradient>
</defs>
<rect width="1920" height="1080" fill="${colors.background}"/>
<rect y="96" width="1920" height="984" fill="url(#signal)"/>
<path d="M0 96H1920M0 864H1920M192 96V1080M1728 96V1080" stroke="${colors.border}" stroke-width="2"/>
<path d="M0 224H1920M0 352H1920M0 480H1920M0 608H1920M0 736H1920M320 96V1080M448 96V1080M576 96V1080M704 96V1080M832 96V1080M960 96V1080M1088 96V1080M1216 96V1080M1344 96V1080M1472 96V1080M1600 96V1080" stroke="${colors.grid}" stroke-width="1" opacity=".22"/>
<path d="M192 864H1260" stroke="url(#fade)" stroke-width="3"/>
<path d="M1534 210h194v194" fill="none" stroke="${colors.orange}" stroke-width="4" opacity=".42"/>
<path d="M386 756A272 272 0 0 1 386 212" fill="none" stroke="${colors.border}" stroke-width="2"/>
${emblem(188, 314, 330)}
<g transform="translate(${lockupX} ${wordmarkBaseline})">
<path d="${shd.data}" fill="${colors.white}"/>
<path d="${build.data}" fill="${colors.orange}" transform="translate(${shd.width} 0)"/>
<rect y="34" width="${Math.max(wordmarkWidth, descriptor.width)}" height="2" fill="${colors.border}"/>
<path d="${descriptor.data}" fill="${colors.white}" transform="translate(0 98)"/>
</g>
<g transform="translate(1540 820)">
<rect width="188" height="56" fill="${colors.surface}" stroke="${colors.border}" stroke-width="2"/>
<rect x="16" y="23" width="10" height="10" fill="${colors.orange}"/>
<path d="M42 28H158" stroke="${colors.orange}" stroke-width="2"/>
<path d="M166 22l8 6-8 6" fill="none" stroke="${colors.orange}" stroke-width="2"/>
</g>
</svg>`;
const inviteSplash = `
<svg xmlns="http://www.w3.org/2000/svg" width="1920" height="1080" viewBox="0 0 1920 1080">
<defs>
<radialGradient id="left-signal" cx="8%" cy="52%" r="58%">
<stop offset="0" stop-color="#3b2d15"/>
<stop offset=".38" stop-color="#171819"/>
<stop offset="1" stop-color="${colors.background}"/>
</radialGradient>
<radialGradient id="right-signal" cx="94%" cy="45%" r="50%">
<stop offset="0" stop-color="#241e13"/>
<stop offset=".48" stop-color="#111415"/>
<stop offset="1" stop-color="${colors.background}" stop-opacity="0"/>
</radialGradient>
<linearGradient id="relay-left" x1="0" x2="1">
<stop stop-color="${colors.orange}" stop-opacity=".8"/>
<stop offset="1" stop-color="${colors.orange}" stop-opacity="0"/>
</linearGradient>
<linearGradient id="relay-right" x1="1" x2="0">
<stop stop-color="${colors.orange}" stop-opacity=".55"/>
<stop offset="1" stop-color="${colors.orange}" stop-opacity="0"/>
</linearGradient>
</defs>
<rect width="1920" height="1080" fill="${colors.background}"/>
<rect width="1920" height="1080" fill="url(#left-signal)"/>
<rect width="1920" height="1080" fill="url(#right-signal)"/>
<path d="M0 120H1920M0 960H1920M160 0V1080M1760 0V1080" stroke="${colors.border}" stroke-width="2"/>
<path d="M0 288H1920M0 456H1920M0 624H1920M0 792H1920M328 0V1080M496 0V1080M664 0V1080M832 0V1080M1000 0V1080M1168 0V1080M1336 0V1080M1504 0V1080" stroke="${colors.grid}" stroke-width="1" opacity=".16"/>
<g opacity=".92">${emblem(-92, 224, 620)}</g>
<g opacity=".66">${emblem(1465, 326, 410)}</g>
<path d="M0 170H605" stroke="url(#relay-left)" stroke-width="4"/>
<path d="M0 910H500" stroke="url(#relay-left)" stroke-width="3"/>
<path d="M1335 188H1920" stroke="url(#relay-right)" stroke-width="3"/>
<path d="M1450 892H1920" stroke="url(#relay-right)" stroke-width="4"/>
<g fill="${colors.orange}">
<rect x="560" y="164" width="12" height="12"/>
<rect x="486" y="904" width="10" height="10"/>
<rect x="1348" y="182" width="12" height="12"/>
<rect x="1436" y="886" width="12" height="12"/>
</g>
<path d="M610 170h90M1310 188h-92M540 910h118M1400 892h-112" stroke="${colors.white}" stroke-width="2" opacity=".22"/>
<rect x="650" y="120" width="620" height="840" fill="${colors.background}" opacity=".42"/>
</svg>`;
await writePng(transparentLogo, 'shd-core-segments.png', 1024, 1024);
await writePng(serverIcon, 'shd-build-discord-icon.png', 1024, 1024);
await writePng(banner, 'shd-build-discord-banner-1920x1080.png', 1920, 1080);
await writePng(
inviteSplash,
'shd-build-discord-invite-background-1920x1080.png',
1920,
1080,
);
await sharp(
path.join(outputDirectory, 'shd-build-discord-banner-1920x1080.png'),
)
.resize(960, 540)
.png({ compressionLevel: 9, adaptiveFiltering: true })
.toFile(path.join(outputDirectory, 'shd-build-discord-banner-960x540.png'));
console.log(`Rendered brand assets to ${outputDirectory}`);