// Retro icon packs for the wayBetter Store (not bundled in the app: they are installed from the // Store, the same way as any pack). Both are our own drawings; nothing is traced or extracted from // Windows, from X or from any icon archive (the Windows icon archives on the web carry Microsoft's // copyright, and the plan bans them). The look is the idea of the era, drawn from scratch. // retro-98 16 by 16 pixel art on the 98 desktop palette: black outline, silver, navy, teal. // retro-xp vector glyphs in the manner of 2001: gel fills, a dark edge, a lit top. // Both draw every name X's left rail uses (home .. compose, and the .active forms) and the action bar; // retro-98 also History, Creator Studio and Articles, and ships bundled as the Windows 98 theme's pack. // Generated into store/x/icons// by scripts/build-store-icons.mjs. import { sanitizeIconPack, type IconGlyph, type IconName, type IconPack } from '../../../../../waybetter/packages/runtime/src/themeIcons.ts'; // --------------------------------------------------------------------------------------------- // retro-98: rows of 16 letters, `.` is transparent const P98: Record = { k: '#000000', w: '#ffffff', s: '#c0c0c0', g: '#808080', n: '#000080', b: '#0000ff', c: '#008080', t: '#00ffff', y: '#ffff00', d: '#808000', r: '#ff0000', m: '#800000', p: '#ff80c0', e: '#00a000', o: '#ff8000', u: '#800080', }; /** Rows to a glyph of 1.5px squares on the 24px box; same-colored cells are merged into rectangles; the outline is drawn last. */ export function rasterGlyph(name: string, rows: readonly string[]): IconGlyph { if (rows.length !== 16) throw new Error(`${name}: ${rows.length} rows, 16 needed`); rows.forEach((row, y) => { if (row.length !== 16) throw new Error(`${name}: row ${y} has ${row.length} cells, 16 needed (${row})`); for (const ch of row) if (ch !== '.' && !P98[ch]) throw new Error(`${name}: row ${y} has an unknown color "${ch}"`); }); const taken = rows.map(() => new Array(16).fill(false)); const byColor = new Map(); const f = (n: number) => +(n * 1.5).toFixed(1); for (let y = 0; y < 16; y++) { for (let x = 0; x < 16; x++) { const key = rows[y]![x]!; if (key === '.' || taken[y]![x]) continue; let w = 1; while (x + w < 16 && rows[y]![x + w] === key && !taken[y]![x + w]) w++; let h = 1; grow: while (y + h < 16) { for (let i = 0; i < w; i++) if (rows[y + h]![x + i] !== key || taken[y + h]![x + i]) break grow; h++; } for (let j = 0; j < h; j++) for (let i = 0; i < w; i++) taken[y + j]![x + i] = true; const list = byColor.get(key) ?? []; list.push(`M${f(x)} ${f(y)}h${f(w)}v${f(h)}h-${f(w)}z`); byColor.set(key, list); } } const keys = [...byColor.keys()].sort((a, b) => (a === 'k' ? 1 : 0) - (b === 'k' ? 1 : 0)); if (keys.length > 6) throw new Error(`${name}: ${keys.length} colors, a glyph has 6 layers at most`); return { layers: keys.map(key => ({ d: byColor.get(key)!.join(''), fill: P98[key]! })) }; } const ART98: Record = { home: [ '................', '.......kk.......', '......krrk......', '.....krrrrk.....', '....krrrrrrk....', '...krrrrrrrrk...', '..krrrrrrrrrrk..', '.kkkkkkkkkkkkkk.', '..kwwwwwwwwwwk..', '..kwbbbwwbbbwk..', '..kwbbbwwbbbwk..', '..kwwwwkkkkwwk..', '..kwwwwkmmkwwk..', '..kwwwwkmmkwwk..', '..kkkkkkkkkkkk..', '................', ], search: [ '................', '...kkkkkk.......', '..kwwwwwwk......', '.kwwwwwwwwk.....', '.kwtwwwwwwk.....', '.kwtwwwwwwk.....', '.kwwwwwwwwk.....', '.kwwwwwwwwk.....', '..kwwwwwwkk.....', '...kkkkkksgk....', '........kgggk...', '.........kgggk..', '..........kgggk.', '...........kgk..', '............k...', '................', ], bell: [ '................', '.......kk.......', '......kyyk......', '.....kywyyk.....', '....kywyyyyk....', '....kywyyyyk....', '....kyyyyyyk....', '...kyyyyyyyyk...', '...kyyyyyyyyk...', '..kyyyyyyyyyyk..', '..kddddddddddk..', '..kkkkkkkkkkkk..', '.....kkddkk.....', '......kddk......', '.......kk.......', '................', ], mail: [ '................', '................', '.kkkkkkkkkkkkkk.', '.kwwwwwwwwwwwwk.', '.kkwwwwwwwwwwkk.', '.kwkwwwwwwwwkwk.', '.kwwkwwwwwwkwwk.', '.kwwwkwwwwkwwwk.', '.kwwwwkwwkwwwwk.', '.kwwwwwkkwwwwwk.', '.kwwwwwwwwwwwwk.', '.kssssssssssssk.', '.kssssssssssssk.', '.kkkkkkkkkkkkkk.', '................', '................', ], monitor: [ '................', '..kkkkkkkkkkkk..', '..kssssssssssk..', '..ksnnnnnnnnsk..', '..ksnttnnnnnsk..', '..ksnttnnnwnsk..', '..ksnnnnnwwwsk..', '..ksnnnnnnwnsk..', '..ksnnnnnnnnsk..', '..kssssssssssk..', '..kkkkkggkkkkk..', '......kssk......', '....kkssssk.....', '...kssssssssk...', '...kkkkkkkkkk...', '................', ], folder: [ '................', '................', '..kkkkk.........', '.kyyyyyk........', '.kyyyyyykkkkkkk.', '.kywwwwwwwwwwyk.', '.kywyyyyyyyyyyk.', '.kyyyyyyyyyyyyk.', '.kyyyyyyyyyyyyk.', '.kyyyyyyyyyyyyk.', '.kyyyyyyyyyyyyk.', '.kyyyyyyyyyyyyk.', '.kddddddddddddk.', '.kkkkkkkkkkkkkk.', '................', '................', ], notepad: [ '................', '..kkkkkkkkkkk...', '..kwwwwwwwwwk...', '..kwkkkkkkkwk...', '..kwwwwwwwwwk...', '..kwkkkkkkkwk...', '..kwwwwwwwwwk...', '..kwkkkkkkwwk...', '..kwwwwwwwwwk...', '..kwkkkkkkkwk...', '..kwwwwwwwwwk...', '..kwkkkkwwwwk...', '..kwwwwwwwwwk...', '..kkkkkkkkkkk...', '................', '................', ], people: [ '................', '...kkkk..kkkk...', '..kyyyyk.kyyyk..', '..kykkyk.kkyyk..', '..kyyyyk.kyyyk..', '...kkkk..kkkk...', '..kccccck.kuuuk.', '.kcccccccckuuuuk', '.kccccccckkuuuuk', '.kcccccccckuuuuk', '.kkkkkkkkkkkkkkk', '................', '................', '................', '................', '................', ], badge: [ '................', '.......kk.......', '......kyyk......', '.....kyyyyk.....', '.kkkkkywyykkkk..', '.kyyyyywyyyyyk..', '..kyyyyyyyyyk...', '...kyyyyyyyk....', '...kyyywyyyk....', '..kyyywwwyyyk...', '..kyyywyywyyk...', '.kyyyk..kyyyk...', '.kkkk....kkkk...', '................', '................', '................', ], person: [ '................', '......kkkk......', '.....kyyyyk.....', '....kyyyyyyk....', '....kykyykyk....', '....kyyyyyyk....', '....kyykkyyk....', '.....kyyyyk.....', '....kkkkkkkk....', '...kcccccccck...', '..kcccccccccck..', '..kcccccccccck..', '..kcccccccccck..', '..kkkkkkkkkkkk..', '................', '................', ], // Three white dots in a black frame: the frame reads on the silver desktop, the white on a dark one. dots: [ '................', '................', '................', '................', '................', '.kkkk.kkkk.kkkk.', '.kwwk.kwwk.kwwk.', '.kwwk.kwwk.kwwk.', '.kkkk.kkkk.kkkk.', '................', '................', '................', '................', '................', '................', '................', ], pencil: [ '............kk..', '...........kmmk.', '..........kyymk.', '.........kyyyk..', '........kyyyk...', '.......kyyyk....', '......kyyyk.....', '.....kyyyk......', '....kyyyk.......', '...kdyyk........', '...kddk.........', '..kwwk..........', '..kkkk..........', '................', '................', '................', ], balloon: [ '................', '..kkkkkkkkkkkk..', '.kwwwwwwwwwwwwk.', '.kwwwwwwwwwwwwk.', '.kwwkkkkkkkkwwk.', '.kwwwwwwwwwwwwk.', '.kwwkkkkkkwwwwk.', '.kwwwwwwwwwwwwk.', '..kkkkwwkkkkkk..', '....kwwk........', '....kwk.........', '....kk..........', '................', '................', '................', '................', ], // Off: white arrows like the other outlined actions (X draws repost muted until it is on); on: green. arrows: [ '................', '.....k..........', '....kwk.........', '...kwwwkkkkkk...', '..kwwwwwwwwwwk..', '...kwwwkkkkkwk..', '....kwk.....kwk.', '.....k......kwk.', '.kwk......k.....', '.kwk.....kwk....', '.kwkkkkkkwwwk...', '..kwwwwwwwwwwk..', '...kkkkkwwwk....', '.........kwk....', '..........k.....', '................', ], arrowsOn: [ '................', '.....k..........', '....kek.........', '...keeekkkkkk...', '..keeeeeeeeeek..', '...keeekkkkkek..', '....kek.....kek.', '.....k......kek.', '.kek......k.....', '.kek.....kek....', '.kekkkkkkeeek...', '..keeeeeeeeeek..', '...kkkkkeeek....', '.........kek....', '..........k.....', '................', ], heart: [ '................', '..kkk.....kkk...', '.kpppk...kpppk..', 'kpwppkkkkpppppk.', 'kpwppppppppppk..', 'kppppppppppppk..', 'kppppppppppppk..', '.kppppppppppk...', '..kppppppppk....', '...kppppppk.....', '....kppppk......', '.....kppk.......', '......kk........', '................', '................', '................', ], heartOn: [ '................', '..kkk.....kkk...', '.krrrk...krrrk..', 'krwrrrkkkrrrrrk.', 'krwrrrrrrrrrrk..', 'krrrrrrrrrrrrk..', 'krrrrrrrrrrrrk..', '.krrrrrrrrrrk...', '..krrrrrrrrk....', '...krrrrrrk.....', '....krrrrk......', '.....krrk.......', '......kk........', '................', '................', '................', ], bars: [ '................', '................', '...........kkk..', '...........kbk..', '...........kbk..', '.......kkk.kbk..', '.......kbk.kbk..', '.......kbk.kbk..', '...kkk.kbk.kbk..', '...kbk.kbk.kbk..', '...kbk.kbk.kbk..', '...kbk.kbk.kbk..', '..kkkkkkkkkkkkk.', '................', '................', '................', ], share: [ '.......kk.......', '......kwwk......', '.....kwwwwk.....', '....kwwwwwwk....', '...kkkwwwwkkk...', '.....kwwwwk.....', '.....kwwwwk.....', '.....kwwwwk.....', '..kk.kkkkkk.kk..', '..ks........sk..', '..ks........sk..', '..kssssssssssk..', '..kkkkkkkkkkkk..', '................', '................', '................', ], picture: [ '................', '.kkkkkkkkkkkkkk.', '.kttttttttyyttk.', '.kttttttttyyttk.', '.kttttttttttttk.', '.ktttttteettttk.', '.kttttteeeetttk.', '.kttteeeeeeettk.', '.kteeeeeeeeeetk.', '.keeeeddeeeeeek.', '.keeeeeeeeeeeek.', '.kkkkkkkkkkkkkk.', '................', '................', '................', '................', ], bookmark: [ '................', '...kkkkkkkkkk...', '...kwwwwwwwwk...', '...kwwwwwwwwk...', '...kwwwwwwwwk...', '...kwwwwwwwwk...', '...kwwwwwwwwk...', '...kwwwwwwwwk...', '...kwwwwwwwwk...', '...kwwwwwwwwk...', '...kwwwkkwwwk...', '...kwwkk.kwwk...', '...kwkk...kwk...', '...kkk.....kk...', '................', '................', ], bookmarkOn: [ '................', '...kkkkkkkkkk...', '...kbbbbbbbbk...', '...kbwbbbbbbk...', '...kbwbbbbbbk...', '...kbbbbbbbbk...', '...kbbbbbbbbk...', '...kbbbbbbbbk...', '...kbbbbbbbbk...', '...kbbbbbbbbk...', '...kbbbkkbbbk...', '...kbbkk.kbbk...', '...kbkk...kbk...', '...kkk.....kk...', '................', '................', ], gif: [ '................', '................', 'kkkkkkkkkkkkkkkk', 'knnnnnnnnnnnnnnk', 'knnnnnnnnnnnnnnk', 'knnwwwnwwwnwwwnk', 'knnwnnnnwnnwnnnk', 'knnwnnnnwnnwwnnk', 'knnwnwnnwnnwnnnk', 'knnwwwnwwwnwnnnk', 'knnnnnnnnnnnnnnk', 'knnnnnnnnnnnnnnk', 'kkkkkkkkkkkkkkkk', '................', '................', '................', ], poll: [ '................', '................', '.kkkkkkkkkkkkkk.', '.kwwwwwwwwwwwwk.', '.kwkkkkkkkwwwwk.', '.kwkbbbbbkwwwwk.', '.kwkkkkkkkwwwwk.', '.kwkkkkkkkkkkwk.', '.kwkeeeeeeeekwk.', '.kwkkkkkkkkkkwk.', '.kwwwwwwwwwwwwk.', '.kkkkkkkkkkkkkk.', '................', '................', '................', '................', ], smile: [ '................', '.....kkkkkk.....', '...kkyyyyyykk...', '..kyyyyyyyyyyk..', '..kyykkyykkyyk..', '.kyyykkyykkyyyk.', '.kyyyyyyyyyyyyk.', '.kykyyyyyyyykyk.', '.kyykyyyyyykyyk.', '..kyykkkkkkyyk..', '..kyyyyyyyyyyk..', '...kkyyyyyykk...', '.....kkkkkk.....', '................', '................', '................', ], hourglass: [ '................', '...kkkkkkkkkk...', '...kssssssssk...', '....kwwwwwwk....', '....kwyyyyyk....', '.....kwyyyk.....', '......kyyk......', '.......kk.......', '......kwyk......', '.....kwwyyk.....', '....kwwyyyyk....', '....kyyyyyyk....', '...kssssssssk...', '...kkkkkkkkkk...', '................', '................', ], clapper: [ '................', '.kkkkkkkkkkkkkk.', '.kwkkwwkkwwkkwk.', '.kkkkkkkkkkkkkk.', '.kggggggggggggk.', '.kgwwwwwwwwwwgk.', '.kgwnnrnnnnnwgk.', '.kgwnnrrnnnnwgk.', '.kgwnnrrrnnnwgk.', '.kgwnnrrnnnnwgk.', '.kgwnnrnnnnnwgk.', '.kgwwwwwwwwwwgk.', '.kggggggggggggk.', '.kkkkkkkkkkkkkk.', '................', '................', ], page: [ '................', '..kkkkkkkkk.....', '..kwwwwwwwkk....', '..kwnnnnwwkgk...', '..kwwwwwwwkkkk..', '..kwnnnnnnwwwk..', '..kwwwwwwwwwwk..', '..kwnnnnnnnnwk..', '..kwwwwwwwwwwk..', '..kwnnnnnnnnwk..', '..kwwwwwwwwwwk..', '..kwnnnnnwwwwk..', '..kwwwwwwwwwwk..', '..kkkkkkkkkkkk..', '................', '................', ], clock: [ '................', '.....kkkkkk.....', '...kkwwwwwwkk...', '..kwwwwkwwwwwk..', '..kwwwwkwwwwwk..', '.kwwwwwkwwwwwwk.', '.kwwwwwkwwwwwwk.', '.kwwwwwkkkkwwwk.', '.kwwwwwwwwwwwwk.', '.kwwwwwwwwwwwwk.', '..kwwwwwwwwwwk..', '..kwwwwwwwwwwk..', '...kkwwwwwwkk...', '.....kkkkkk.....', '................', '................', ], pin: [ '................', '.....kkkkkk.....', '....krrrrrrk....', '...krrwrrrrrrk..', '...krwwrrrrrrk..', '...krrrrkkrrrk..', '...krrrrkkrrrk..', '....krrrrrrrk...', '.....krrrrrk....', '......krrrk.....', '.......krk......', '........k.......', '................', '................', '................', '................', ], lock: [ '................', '.....kkkkkk.....', '....kgwwwwgk....', '....kw....wk....', '....kw....wk....', '....kw....wk....', '..kkkkkkkkkkkk..', '..kyyyyyyyyyyk..', '..kywwwwwwwwdk..', '..kyyyykkyyyyk..', '..kyyyykkyyyyk..', '..kyyyyyyyyyyk..', '..kddddddddddk..', '..kkkkkkkkkkkk..', '................', '................', ], check: [ '................', '.....kkkkkk.....', '...kkbbbbbbkk...', '..kbbbbbbbbbbk..', '.kbbbbbbbbbwwbk.', '.kbbbbbbbbwwbbk.', '.kbwwbbbbwwbbbk.', '.kbbwwbbwwbbbbk.', '.kbbbwwwwbbbbbk.', '..kbbbbwwbbbbk..', '..kbbbbbbbbbbk..', '...kkbbbbbbkk...', '.....kkkkkk.....', '................', '................', '................', ], back: [ '................', '................', '.......k........', '......kk........', '.....kgk........', '....kgwwkkkkkkk.', '...kgwwwwwwwwwk.', '..kgwwwwwwwwwwk.', '...kgwwwwwwwwk..', '....kgwwkkkkkk..', '.....kgk........', '......kk........', '.......k........', '................', '................', '................', ], // A bright red cross in a black frame (the dark red of the first draft vanished on dark grounds). close: [ '................', '................', '..kk........kk..', '..krk......krk..', '...krk....krk...', '....krk..krk....', '.....krkkrk.....', '......krrk......', '.....krkkrk.....', '....krk..krk....', '...krk....krk...', '..krk......krk..', '..kk........kk..', '................', '................', '................', ], gear: [ '................', '......kkkk......', '...kk.kssk.kk...', '..ksskksskksk...', '...kssssssssk...', '..kksskkkksskk..', '.kssskkwwkksssk.', '.kssskwwwwksssk.', '.kssskwwwwksssk.', '..kksskkkksskk..', '...kssssssssk...', '..ksskksskksk...', '...kk.kssk.kk...', '......kkkk......', '................', '................', ], }; // Anything a hand-typed row got wrong is reported by rasterGlyph with the icon's name and the row. // --------------------------------------------------------------------------------------------- // retro-xp: vector gel, 24 by 24 const n2 = (n: number) => +n.toFixed(2); const circ = (cx: number, cy: number, r: number) => `M${n2(cx - r)} ${n2(cy)}a${n2(r)} ${n2(r)} 0 1 0 ${n2(2 * r)} 0a${n2(r)} ${n2(r)} 0 1 0 ${n2(-2 * r)} 0z`; const ell = (cx: number, cy: number, rx: number, ry: number) => `M${n2(cx - rx)} ${n2(cy)}a${n2(rx)} ${n2(ry)} 0 1 0 ${n2(2 * rx)} 0a${n2(rx)} ${n2(ry)} 0 1 0 ${n2(-2 * rx)} 0z`; const rr = (x: number, y: number, w: number, h: number, r: number) => `M${n2(x + r)} ${n2(y)}h${n2(w - 2 * r)}a${n2(r)} ${n2(r)} 0 0 1 ${n2(r)} ${n2(r)}v${n2(h - 2 * r)}a${n2(r)} ${n2(r)} 0 0 1 ${n2(-r)} ${n2(r)}h${n2(-(w - 2 * r))}a${n2(r)} ${n2(r)} 0 0 1 ${n2(-r)} ${n2(-r)}v${n2(-(h - 2 * r))}a${n2(r)} ${n2(r)} 0 0 1 ${n2(r)} ${n2(-r)}z`; const poly = (...pts: Array<[number, number]>) => `M${pts.map(([x, y]) => `${n2(x)} ${n2(y)}`).join('L')}z`; /** A straight bar of width `w` from one point to another. */ const bar = (x1: number, y1: number, x2: number, y2: number, w: number) => { const dx = x2 - x1, dy = y2 - y1, len = Math.hypot(dx, dy) || 1; const px = (-dy / len) * (w / 2), py = (dx / len) * (w / 2); return poly([x1 + px, y1 + py], [x2 + px, y2 + py], [x2 - px, y2 - py], [x1 - px, y1 - py]); }; const star = (cx: number, cy: number, R: number, r: number, points = 5) => poly(...Array.from({ length: points * 2 }, (_, i): [number, number] => { const angle = -Math.PI / 2 + (i * Math.PI) / points, radius = i % 2 ? r : R; return [cx + radius * Math.cos(angle), cy + radius * Math.sin(angle)]; })); type Layer = { d: string; fill: string | { from: string; to: string }; opacity?: number }; const flat = (d: string, fill: string): Layer => ({ d, fill }); const gel = (d: string, from: string, to: string): Layer => ({ d, fill: { from, to } }); const sheen = (d: string, opacity = 0.42): Layer => ({ d, fill: { from: '#ffffff', to: '#ffffff' }, opacity }); const glyph = (...layers: Layer[]): IconGlyph => { if (layers.length > 6) throw new Error(`a glyph has 6 layers at most (${layers.length})`); return { layers }; }; const EDGE = { blue: '#143c8c', red: '#7a1a10', green: '#17591f', gold: '#7a5410', gray: '#3b4256', brown: '#5a3410' }; const BLUE: [string, string] = ['#8fc0ff', '#2563d6']; const RED: [string, string] = ['#ff9a80', '#d2301a']; const GREEN: [string, string] = ['#a6ec8a', '#2f9e2b']; const GOLD: [string, string] = ['#ffe9a0', '#eaa21a']; const PAPER: [string, string] = ['#ffffff', '#dfe8f6']; const XP: Record = { home: glyph( flat(rr(3.4, 10.4, 17.2, 11.6, 1.2), '#6a5a2a'), gel(rr(4.4, 11.4, 15.2, 9.6, 0.6), '#fff9dc', '#e8d28c'), flat(poly([12, 1.6], [23.4, 12.4], [0.6, 12.4]), EDGE.red), gel(poly([12, 4.2], [20.6, 11.2], [3.4, 11.2]), ...RED), gel(rr(9.8, 14.4, 4.4, 6.6, 0.8), '#6fa8ff', '#1c4fb8'), sheen(poly([12, 4.6], [16.4, 8.2], [7.6, 8.2]), 0.3), ), explore: glyph( flat(bar(15.2, 15.2, 21.4, 21.4, 4.6), EDGE.brown), gel(bar(15.2, 15.2, 21.2, 21.2, 3), '#f6bd5a', '#b8651a'), flat(circ(10, 10, 8), EDGE.blue), gel(circ(10, 10, 6.9), ...BLUE), gel(circ(10, 10, 4.9), '#f2f9ff', '#a8cfff'), sheen(ell(8.5, 7.4, 3.1, 1.8), 0.7), ), bell: glyph( flat('M12 1.8a1.6 1.6 0 0 1 1.6 1.6v.5a6.4 6.4 0 0 1 4.4 6.1v4.7l2.2 3.6H3.8L6 14.7V10a6.4 6.4 0 0 1 4.4-6.1v-.5A1.6 1.6 0 0 1 12 1.8z', EDGE.gold), gel('M12 3.2a.6.6 0 0 1 .6.6v1.1a5.2 5.2 0 0 1 4.2 5.1v4.9l1.7 2.9H5.5l1.7-2.9V10a5.2 5.2 0 0 1 4.2-5.1V3.8a.6.6 0 0 1 .6-.6z', ...GOLD), flat(circ(12, 20.4, 2.2), EDGE.gold), gel(circ(12, 20.4, 1.3), '#ffe28a', '#d58b10'), sheen(ell(9.6, 9, 1.6, 3.2), 0.55), ), messages: glyph( flat(rr(1.6, 4.6, 20.8, 14.8, 1.8), EDGE.blue), gel(rr(2.6, 5.6, 18.8, 12.8, 1), ...PAPER), flat(poly([3, 6.6], [12, 13.6], [21, 6.6], [21, 8.4], [12, 15.4], [3, 8.4]), '#2a62d4'), sheen(poly([3.2, 6], [12, 11.8], [20.8, 6]), 0.4), ), grok: glyph( flat(circ(12, 12, 10.4), '#10162e'), gel(circ(12, 12, 9.4), '#6a78a8', '#141a3a'), flat(bar(6.4, 17.6, 17.6, 6.4, 2.2), '#ffffff'), flat(poly([11, 5.6], [18.4, 5.6], [18.4, 13]), '#ffffff'), sheen(ell(9.6, 6.6, 4.4, 2.4), 0.3), ), folder: glyph( flat('M2.2 5.4A1.6 1.6 0 0 1 3.8 3.8h5.4l2 2.2h9A1.6 1.6 0 0 1 21.8 7.6v11.2a1.6 1.6 0 0 1-1.6 1.6H3.8a1.6 1.6 0 0 1-1.6-1.6z', EDGE.gold), gel('M3.2 5.6a.8.8 0 0 1 .8-.8h4.8l2 2.2h9a.8.8 0 0 1 .8.8v10.8a.8.8 0 0 1-.8.8H4a.8.8 0 0 1-.8-.8z', '#fff1b0', '#f0b72a'), gel(rr(3.2, 9.2, 17.6, 10, 0.8), '#ffe27a', '#e69a12'), sheen(rr(4, 9.8, 16, 3.2, 0.6), 0.45), ), lists: glyph( flat(rr(4.2, 1.8, 15.6, 20.4, 1.4), EDGE.gray), gel(rr(5.2, 2.8, 13.6, 18.4, 0.7), ...PAPER), flat(rr(7.4, 6.2, 9.2, 1.6, 0.8), '#2a62d4'), flat(rr(7.4, 10.2, 9.2, 1.6, 0.8), '#2a62d4'), flat(rr(7.4, 14.2, 9.2, 1.6, 0.8), '#2a62d4'), flat(rr(7.4, 17.8, 5.4, 1.4, 0.7), '#2a62d4'), ), people: glyph( flat(circ(8, 7.6, 4.2), EDGE.blue), gel(circ(8, 7.6, 3.2), '#ffe2c0', '#e8a86c'), flat('M1.2 21a6.8 6.8 0 0 1 13.6 0z', EDGE.blue), gel('M2.4 20.2a5.6 5.6 0 0 1 11.2 0z', ...BLUE), flat(circ(17, 8.4, 3.6), EDGE.red), gel('M12.2 21a4.8 4.8 0 0 1 9.6 0z', ...RED), ), star: glyph( flat(star(12, 12.6, 11, 4.9), EDGE.gold), gel(star(12, 12.8, 9.6, 4.1), ...GOLD), sheen(poly([12, 4.2], [14, 9.4], [12, 10.4], [10, 9.4]), 0.55), ), person: glyph( flat(circ(12, 7, 5.4), EDGE.blue), gel(circ(12, 7, 4.4), '#ffe2c0', '#e8a86c'), flat('M2.6 22a9.4 9.4 0 0 1 18.8 0z', EDGE.blue), gel('M3.8 21a8.2 8.2 0 0 1 16.4 0z', ...BLUE), sheen(ell(10.4, 5.4, 2.2, 1.4), 0.7), ), more: glyph( flat(rr(1.4, 7.6, 21.2, 8.8, 4.4), EDGE.blue), gel(rr(2.4, 8.6, 19.2, 6.8, 3.4), ...BLUE), flat(circ(7, 12, 1.5), '#ffffff'), flat(circ(12, 12, 1.5), '#ffffff'), flat(circ(17, 12, 1.5), '#ffffff'), sheen(rr(3.4, 9, 17.2, 2.8, 1.4), 0.4), ), compose: glyph( flat(rr(2.4, 3.6, 15.6, 18, 1.4), EDGE.gray), gel(rr(3.4, 4.6, 13.6, 16, 0.7), ...PAPER), flat(bar(11.4, 16.4, 20.6, 4.4, 4.2), EDGE.gold), gel(bar(11.6, 16, 20.2, 4.8, 2.6), '#ffe27a', '#e69a12'), flat(poly([9.8, 20.4], [10.6, 15.6], [14, 17.6]), '#3b4256'), flat(poly([19.2, 3.4], [21.6, 2.2], [22.2, 5.4]), '#e0472b'), ), reply: glyph( flat('M3 3.4h18a1.6 1.6 0 0 1 1.6 1.6v10.2a1.6 1.6 0 0 1-1.6 1.6H12.4L7 21.6v-4.8H3A1.6 1.6 0 0 1 1.4 15.2V5A1.6 1.6 0 0 1 3 3.4z', EDGE.blue), gel('M3.2 4.6h17.6a.6.6 0 0 1 .6.6v9.8a.6.6 0 0 1-.6.6h-9L8.2 19v-3.4H3.2a.6.6 0 0 1-.6-.6V5.2a.6.6 0 0 1 .6-.6z', '#c4e0ff', '#4c8fe8'), sheen(rr(3.6, 5.2, 16.8, 4, 0.6), 0.5), ), // Off: brushed-steel arrows (X shows repost muted until it is on); on: the green gel. repost: glyph( flat('M3 11V9a3 3 0 0 1 3-3h8V2.6l6.4 5.2-6.4 5.2V9.6H7v1.4z', EDGE.gray), gel('M4.2 10.2V9.2a2.2 2.2 0 0 1 2.2-2.2h8.8V4.8l4.2 3.4-4.2 3.4V9.4H7z', '#f2f5fa', '#9aa4b8'), flat('M21 13v2a3 3 0 0 1-3 3h-8v3.4L3.6 16.2 10 11v3.4h7v-1.4z', EDGE.gray), gel('M19.8 13.8v1a2.2 2.2 0 0 1-2.2 2.2H8.8v2.2l-4.2-3.4 4.2-3.4v2h8z', '#f2f5fa', '#9aa4b8'), ), repostOn: glyph( flat('M3 11V9a3 3 0 0 1 3-3h8V2.6l6.4 5.2-6.4 5.2V9.6H7v1.4z', EDGE.green), gel('M4.2 10.2V9.2a2.2 2.2 0 0 1 2.2-2.2h8.8V4.8l4.2 3.4-4.2 3.4V9.4H7z', '#d6ffb8', '#38b52a'), flat('M21 13v2a3 3 0 0 1-3 3h-8v3.4L3.6 16.2 10 11v3.4h7v-1.4z', EDGE.green), gel('M19.8 13.8v1a2.2 2.2 0 0 1-2.2 2.2H8.8v2.2l-4.2-3.4 4.2-3.4v2h8z', '#d6ffb8', '#38b52a'), sheen(rr(6, 7.2, 8, 1.2, 0.6), 0.6), ), heart: glyph( flat('M12 22.4L2.4 12.8A6 6 0 0 1 12 5.4a6 6 0 0 1 9.6 7.4z', '#8a5568'), gel('M12 20.8L3.8 12.6A4.6 4.6 0 0 1 12 6.9a4.6 4.6 0 0 1 8.2 5.7z', '#fff0f6', '#f0a6c4'), sheen(ell(8, 9.4, 2.6, 1.5), 0.7), ), heartOn: glyph( flat('M12 22.4L2.4 12.8A6 6 0 0 1 12 5.4a6 6 0 0 1 9.6 7.4z', '#7a0f2c'), gel('M12 20.8L3.8 12.6A4.6 4.6 0 0 1 12 6.9a4.6 4.6 0 0 1 8.2 5.7z', '#ff8fb0', '#d4123f'), sheen(ell(8, 9.4, 2.6, 1.5), 0.6), ), views: glyph( flat(rr(2.2, 13, 5.2, 8.8, 1), EDGE.blue), gel(rr(3, 13.8, 3.6, 7.2, 0.5), ...BLUE), flat(rr(9.4, 4.6, 5.2, 17.2, 1), EDGE.green), gel(rr(10.2, 5.4, 3.6, 15.6, 0.5), ...GREEN), flat(rr(16.6, 9, 5.2, 12.8, 1), EDGE.red), gel(rr(17.4, 9.8, 3.6, 11.2, 0.5), ...RED), ), share: glyph( flat(poly([12, 1.6], [19, 8.8], [14.4, 8.8], [14.4, 15.4], [9.6, 15.4], [9.6, 8.8], [5, 8.8]), EDGE.green), gel(poly([12, 3.8], [16.4, 8], [13.2, 8], [13.2, 14.2], [10.8, 14.2], [10.8, 8], [7.6, 8]), ...GREEN), flat(rr(2.6, 13.4, 18.8, 8.2, 1.4), EDGE.gray), gel(rr(3.6, 14.4, 16.8, 6.2, 0.8), '#f4f7fc', '#b8c6e0'), ), bookmark: glyph( flat('M6 2h12a1.4 1.4 0 0 1 1.4 1.4V22.2L12 16.8 4.6 22.2V3.4A1.4 1.4 0 0 1 6 2z', EDGE.blue), gel('M6.2 3.2h11.6v15.8L12 14.8 6.2 19z', '#e8f2ff', '#8ab6f4'), sheen(rr(6.6, 3.6, 10.8, 3.4, 0.5), 0.5), ), bookmarkOn: glyph( flat('M6 2h12a1.4 1.4 0 0 1 1.4 1.4V22.2L12 16.8 4.6 22.2V3.4A1.4 1.4 0 0 1 6 2z', EDGE.blue), gel('M6.2 3.2h11.6v15.8L12 14.8 6.2 19z', '#8fc0ff', '#1f55c4'), sheen(rr(6.6, 3.6, 10.8, 3.4, 0.5), 0.45), ), back: glyph( flat(circ(12, 12, 10.6), '#0f4a12'), gel(circ(12, 12, 9.6), '#a6ec8a', '#2b9a28'), flat(poly([5.6, 12], [11.6, 6.2], [11.6, 9.6], [18.4, 9.6], [18.4, 14.4], [11.6, 14.4], [11.6, 17.8]), '#ffffff'), sheen(ell(12, 6.6, 7, 3.4), 0.4), ), close: glyph( flat(rr(2, 2, 20, 20, 3.4), '#ffffff'), gel(rr(3, 3, 18, 18, 2.6), '#f6a58f', '#d2301a'), flat(bar(7.6, 7.6, 16.4, 16.4, 2.6), '#ffffff'), flat(bar(16.4, 7.6, 7.6, 16.4, 2.6), '#ffffff'), sheen(rr(3.8, 3.8, 16.4, 6.4, 1.8), 0.35), ), media: glyph( flat(rr(1.6, 3.6, 20.8, 16.8, 1.6), EDGE.gray), gel(rr(2.6, 4.6, 18.8, 14.8, 0.8), '#b8e0ff', '#5aa8f0'), flat('M2.6 19.4V15l5.6-5 5.2 4.4 3-2.4 4.8 4.4v3z', '#2f8a2a'), flat(circ(16.6, 8.6, 2), '#ffd84a'), sheen(rr(3.2, 5.2, 17.6, 4, 0.6), 0.3), ), gif: glyph( flat(rr(1.6, 5, 20.8, 14, 2.4), EDGE.blue), gel(rr(2.6, 6, 18.8, 12, 1.6), '#8fc0ff', '#2563d6'), // "GIF" in block letters, legible at 16px (G: top, side, foot, the hook in; I; F: stem, top, bar). flat([rr(4.6, 8.4, 4.8, 1.6, 0.3), rr(4.6, 8.4, 1.6, 7.2, 0.3), rr(4.6, 14, 4.8, 1.6, 0.3), rr(7.8, 11.6, 1.6, 4, 0.3), rr(7, 11.6, 2.4, 1.4, 0.3), rr(11.2, 8.4, 1.6, 7.2, 0.3), rr(14.4, 8.4, 1.6, 7.2, 0.3), rr(14.4, 8.4, 5, 1.6, 0.3), rr(14.4, 11.4, 3.8, 1.4, 0.3)].join(''), '#ffffff'), sheen(rr(3, 6.4, 18, 4, 1.2), 0.3), ), poll: glyph( flat(rr(2, 2.6, 20, 18.8, 1.6), EDGE.gray), gel(rr(3, 3.6, 18, 16.8, 0.8), ...PAPER), flat(rr(5, 6.2, 12, 2.4, 1.2), '#2563d6'), flat(rr(5, 10.8, 8, 2.4, 1.2), '#2f9e2b'), flat(rr(5, 15.4, 5, 2.4, 1.2), '#d2301a'), ), smile: glyph( flat(circ(12, 12, 10.6), '#7a5410'), gel(circ(12, 12, 9.6), '#fff3a0', '#f2b81a'), flat(ell(8.6, 9.6, 1.2, 1.8), '#3a2a08'), flat(ell(15.4, 9.6, 1.2, 1.8), '#3a2a08'), flat('M6.4 14a6.4 6.4 0 0 0 11.2 0z', '#3a2a08'), sheen(ell(10.4, 6, 5, 2.6), 0.55), ), clock: glyph( flat(circ(12, 12, 10.6), EDGE.blue), gel(circ(12, 12, 9.4), '#ffffff', '#cfdcf4'), flat(bar(12, 12, 12, 5.4, 1.8), '#143c8c'), flat(bar(12, 12, 16.6, 14.8, 1.8), '#143c8c'), flat(circ(12, 12, 1.4), '#d2301a'), sheen(ell(10.4, 6, 5, 2.2), 0.5), ), pin: glyph( flat('M12 22.6C8 17.6 4.8 14 4.8 9.8a7.2 7.2 0 0 1 14.4 0c0 4.2-3.2 7.8-7.2 12.8z', EDGE.red), gel('M12 20.6C9 16.8 6.2 13.6 6.2 9.8a5.8 5.8 0 0 1 11.6 0c0 3.8-2.8 7-5.8 10.8z', ...RED), flat(circ(12, 9.8, 2.4), '#ffffff'), sheen(ell(9.6, 6.6, 2, 1.8), 0.6), ), lock: glyph( flat('M7 10.4V7.6a5 5 0 0 1 10 0v2.8h-2.4V7.6a2.6 2.6 0 0 0-5.2 0v2.8z', '#4a4f5c'), flat(rr(3.6, 10, 16.8, 12, 2), EDGE.gold), gel(rr(4.6, 11, 14.8, 10, 1.2), ...GOLD), flat(circ(12, 15.2, 1.8), '#5a3a08'), flat(rr(11.2, 15.4, 1.6, 3.6, 0.8), '#5a3a08'), sheen(rr(5, 11.4, 14, 3.4, 1), 0.45), ), verified: glyph( flat(star(12, 12, 11.4, 9.4, 9), '#10408f'), gel(circ(12, 12, 8.6), '#8fc4ff', '#1f6fe0'), flat('M7.4 12.2l3.2 3.2 6-6.6-1.6-1.5-4.4 4.9-1.6-1.6z', '#ffffff'), sheen(ell(10.4, 7.4, 4.4, 2.2), 0.45), ), gear: glyph( flat(star(12, 12, 11.4, 9, 8), EDGE.gray), gel(circ(12, 12, 8.6), '#f4f7fc', '#a6b4cf'), flat(circ(12, 12, 3.6), EDGE.gray), gel(circ(12, 12, 2.6), '#ffffff', '#c8d4ea'), ), }; // --------------------------------------------------------------------------------------------- type Names = Partial>; const px = (key: keyof typeof ART98) => rasterGlyph(key, ART98[key]!); const G98: Names = { home: px('home'), 'home.active': px('home'), explore: px('search'), 'explore.active': px('search'), notifications: px('bell'), 'notifications.active': px('bell'), messages: px('mail'), 'messages.active': px('mail'), grok: px('monitor'), bookmarks: px('folder'), 'bookmarks.active': px('folder'), lists: px('notepad'), communities: px('people'), premium: px('badge'), profile: px('person'), 'profile.active': px('person'), more: px('dots'), compose: px('pencil'), reply: px('balloon'), repost: px('arrows'), 'repost.on': px('arrowsOn'), like: px('heart'), 'like.on': px('heartOn'), views: px('bars'), bookmark: px('bookmark'), 'bookmark.on': px('bookmarkOn'), share: px('share'), verified: px('check'), back: px('back'), close: px('close'), search: px('search'), settings: px('gear'), media: px('picture'), gif: px('gif'), poll: px('poll'), emoji: px('smile'), schedule: px('clock'), location: px('pin'), lock: px('lock'), history: px('hourglass'), studio: px('clapper'), articles: px('page'), }; const GXP: Names = { home: XP.home!, 'home.active': XP.home!, explore: XP.explore!, 'explore.active': XP.explore!, notifications: XP.bell!, 'notifications.active': XP.bell!, messages: XP.messages!, 'messages.active': XP.messages!, grok: XP.grok!, bookmarks: XP.folder!, 'bookmarks.active': XP.folder!, lists: XP.lists!, communities: XP.people!, premium: XP.star!, profile: XP.person!, 'profile.active': XP.person!, more: XP.more!, compose: XP.compose!, reply: XP.reply!, repost: XP.repost!, 'repost.on': XP.repostOn!, like: XP.heart!, 'like.on': XP.heartOn!, views: XP.views!, bookmark: XP.bookmark!, 'bookmark.on': XP.bookmarkOn!, share: XP.share!, verified: XP.verified!, back: XP.back!, close: XP.close!, search: XP.explore!, settings: XP.gear!, media: XP.media!, gif: XP.gif!, poll: XP.poll!, emoji: XP.smile!, schedule: XP.clock!, location: XP.pin!, lock: XP.lock!, history: XP.clock!, studio: XP.media!, articles: XP.lists!, }; const OWN = { author: 'wayBetter for X', license: 'own', source: 'drawn for wayBetter for X; no asset is taken from Windows or from any icon archive', credits: 'wayBetter for X contributors' }; const documents = [ { id: 'retro-98', name: 'Retro 98', description: 'Sixteen-pixel desktop icons in the sixteen-color palette of 1998: black outlines, silver, navy and teal. Every icon of the left navigation and the action bar.', ...OWN, style: { stroke: 1.75, cap: 'square', join: 'miter', scale: 1 }, glyphs: G98, }, { id: 'retro-xp', name: 'Retro XP', description: 'Glossy gel icons in the manner of 2001: a dark edge, a lit top, bright blue, green and gold. Every icon of the left navigation and the action bar.', ...OWN, style: { stroke: 1.75, cap: 'round', join: 'round', scale: 1 }, glyphs: GXP, }, ]; /** The retro packs, validated like any imported pack. */ export const RETRO_PACKS: IconPack[] = documents.map(doc => { const pack = sanitizeIconPack(doc); if (!pack) throw new Error(`retro icon pack ${doc.id} is not valid`); const wanted = Object.keys(doc.glyphs).length; if (Object.keys(pack.glyphs).length !== wanted) throw new Error(`retro icon pack ${doc.id}: ${wanted - Object.keys(pack.glyphs).length} glyph(s) were refused by the sanitizer`); return pack; }); /** A retro pack as the JSON document it is published as (`.icons.json`). */ export const retroDocument = (id: string) => { const pack = RETRO_PACKS.find(item => item.id === id); return pack ? { $schema: 'https://unpkg.com/waybetter/icons.schema.json', ...pack } : null; };