oficina / meet Public

Clone
git clone https://amadan.net/oficina/meet

Plain git — no account needed to clone.

Download

Download this file

1import { readFile, writeFile, mkdir } from 'node:fs/promises';
2import { fileURLToPath } from 'node:url';
3
4const root = new URL('./', import.meta.url);
5const icons = JSON.parse(await readFile(new URL('icons.json', root), 'utf8'));
6const bodies = new Map();
7for (const item of icons) {
8 const source = await readFile(new URL(`upstream/${item.icon}.svg`, root), 'utf8');
9 bodies.set(item.id, source.match(/<svg[^>]*>([\s\S]*?)<\/svg>/)[1].trim());
10}
11const glyph = (item, x, y, size) => `<g transform="translate(${x} ${y}) scale(${size / 24})" fill="none" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">${bodies.get(item.id)}</g>`;
12// All seven use the same 24-unit Lucide canvas, occupying 70% of the tile.
13const tile = (item, x, y, size, radius = 0) => `<rect x="${x}" y="${y}" width="${size}" height="${size}" rx="${radius}" fill="${item.colour}"/>${glyph(item, x + size * .15, y + size * .15, size * .7)}`;
14const svg = (width, height, content, title) => `<svg xmlns="http://www.w3.org/2000/svg" width="${width}" height="${height}" viewBox="0 0 ${width} ${height}" role="img" aria-label="${title}"><title>${title}</title>${content}</svg>\n`;
15await mkdir(new URL('svg/', root), { recursive: true });
16for (const item of icons) {
17 await writeFile(new URL(`svg/${item.id}.svg`, root), svg(1024, 1024, tile(item, 0, 0, 1024), item.name));
18}
19let sheet = `<rect width="1400" height="790" fill="#F6F5F2"/><g font-family="Arial, sans-serif"><text x="64" y="77" font-size="32" fill="#252932" font-weight="700">Oficina</text><text x="64" y="111" font-size="16" fill="#606571">One family. Seven marks. White Lucide symbols on solid colour.</text>`;
20for (let i = 0; i < icons.length; i++) {
21 const item = icons[i];
22 const x = 64 + i * 184;
23 sheet += tile(item, x, 165, 144, 30);
24 sheet += `<text x="${x}" y="345" font-size="20" font-weight="700" fill="#252932">${item.name}</text><text x="${x}" y="372" font-size="13" fill="#606571">${item.icon}</text><text x="${x}" y="395" font-size="13" fill="#606571">${item.colour}</text>`;
25 for (const [size, y] of [[48, 450], [32, 525], [16, 588]]) sheet += tile(item, x, y, size, size * .21);
26}
27sheet += `<text x="64" y="666" font-size="14" fill="#606571">Actual sizes above: 48, 32 and 16 px. Rounded corners are a preview mask only.</text><text x="64" y="733" font-size="14" fill="#606571">Square, opaque masters · Shared padding · 2-unit strokes · No gradients or shadows</text></g>`;
28await writeFile(new URL('preview.svg', root), svg(1400, 790, sheet, 'Oficina app icon set'));
29console.log('Generated seven SVG masters and preview.svg');
30
31if (process.argv.includes('--render')) {
32 const { chromium } = await import('playwright');
33 const browser = await chromium.launch({ headless: true });
34 try {
35 const page = await browser.newPage({ viewport: { width: 1400, height: 790 }, deviceScaleFactor: 1 });
36 await page.goto(new URL('preview.svg', root).href);
37 await page.screenshot({ path: fileURLToPath(new URL('preview.png', root)) });
38 await mkdir(new URL('png/', root), { recursive: true });
39 for (const item of icons) {
40 const source = await readFile(new URL(`svg/${item.id}.svg`, root), 'utf8');
41 for (const size of [16, 32, 48, 180, 192, 512, 1024]) {
42 const png = await page.evaluate(async ({ source, size }) => {
43 const img = new Image();
44 img.src = 'data:image/svg+xml;base64,' + btoa(source);
45 await img.decode();
46 const canvas = document.createElementNS('http://www.w3.org/1999/xhtml', 'canvas');
47 canvas.width = canvas.height = size;
48 canvas.getContext('2d', { alpha: false }).drawImage(img, 0, 0, size, size);
49 return canvas.toDataURL('image/png').split(',')[1];
50 }, { source, size });
51 await writeFile(new URL(`png/${item.id}-${size}.png`, root), Buffer.from(png, 'base64'));
52 }
53 }
54 console.log('Rendered preview.png and seven PNG sizes per app');
55 } finally {
56 await browser.close();
57 }
58}
59