fuzzyworld

nordic-skating / files / tools / icons.mjs

icons.mjs

2.4 KB · history · blame

1/**
2 * Make the app's icons from assets/skate-shoes.png, in the sizes a phone asks for when
3 * the app is put on its home screen, and the one a browser shows on its tab. The pictures
4 * are kept in static/, so this is only run when the artwork or its colours change.
5 *
6 * npm run icons
7 *
8 * The artwork is a skate by Icongeek26, from Flaticon, as are the pictures on the app's
9 * buttons (assets/), all used under the Flaticon free
10 * licence, which requires attribution (README, Attribution). It is black line on nothing;
11 * only its shape is used. On a home screen it is drawn pale on the colour of black ice.
12 * On a browser's tab it is as it was drawn: black, on nothing, so that the tab shows
13 * through it; and white on nothing for a browser whose tabs are dark, where black would
14 * not show.
15 */
16import { readFileSync } from 'node:fs'
17import { chromium } from 'playwright'
18
19const skate = `data:image/png;base64,${readFileSync('assets/skate-shoes.png').toString('base64')}`
20const ICE = { ink: '#e8f4ff', paper: 'linear-gradient(#0b1a24,#1d3a4c)' }
21/** The file, its side in pixels, how much of that the skate fills, and its colours. */
22const ICONS = [
23 ['static/icon-192.png', 192, 0.72, ICE],
24 ['static/icon-512.png', 512, 0.72, ICE],
25 // For Android, which crops this one to whatever shape its home screen uses: the skate
26 // is kept inside the middle, where nothing of it is cropped away.
27 ['static/icon-maskable-512.png', 512, 0.54, ICE],
28 // For an iPhone's home screen, which rounds the corners itself.
29 ['static/apple-touch-icon.png', 180, 0.72, ICE],
30 // For a browser's tab.
31 ['static/favicon.png', 96, 1, { ink: '#000', paper: 'transparent' }],
32 ['static/favicon-dark.png', 96, 1, { ink: '#fff', paper: 'transparent' }],
33]
34
35const browser = await chromium.launch()
36try {
37 for (const [file, side, fill, { ink, paper }] of ICONS) {
38 const page = await browser.newPage({ viewport: { width: side, height: side } })
39 const inner = Math.round(side * fill)
40 await page.setContent(`<body style="margin:0;display:grid;place-items:center;height:100vh;background:${paper}">
41 <div style="width:${inner}px;height:${inner}px;background:${ink};
42 -webkit-mask:url(${skate}) center/contain no-repeat;mask:url(${skate}) center/contain no-repeat"></div></body>`)
43 await page.screenshot({ path: file, omitBackground: paper === 'transparent' })
44 await page.close()
45 console.log(` ${file}`)
46 }
47} finally {
48 await browser.close()
49}