#!/usr/bin/env node /** * Build the owner flag sheet the orbital Cockpit marks a satellite's owner * country with (`src/ui/ownerFlag.js`): every flag in OWNER_COUNTRY_CODES * (`src/layers/satellites/classes.js`), drawn from the flag-icons 4:3 SVGs * at twice the readout's 20 × 15 px, in one PNG the app loads at startup. * * src/data/local_data/owner_flags/owner_flags.png the sheet * src/ui/ownerFlagSheet.js its layout (generated) * * Flag i (in the sorted code list) sits at column i % COLUMNS, row * floor(i / COLUMNS), each cell followed by a GUTTER of transparent pixels. A rerun with the same flag-icons and sharp versions * reproduces both files. * * Usage: node scripts/build-owner-flags.mjs */ import { readFile, writeFile } from 'node:fs/promises'; import { createRequire } from 'node:module'; import path from 'node:path'; import { fileURLToPath } from 'node:url'; import * as prettier from 'prettier'; import sharp from 'sharp'; import { OWNER_COUNTRY_CODES } from '../src/layers/satellites/classes.js'; const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..'); const require = createRequire(import.meta.url); const FLAGS_DIR = path.join( path.dirname(require.resolve('flag-icons/package.json')), 'flags/4x3', ); const FLAG_ICONS_VERSION = require('flag-icons/package.json').version; /** One flag's cell, CSS pixels × 2. */ export const CELL = Object.freeze({ width: 40, height: 30 }); /** * Transparent sheet pixels after each flag, so a browser smoothing a scaled * flag (a 125% display) never reaches into its neighbour. */ export const GUTTER = 2; export const COLUMNS = 16; const SHEET = path.join( ROOT, 'src/data/local_data/owner_flags/owner_flags.png', ); const LAYOUT = path.join(ROOT, 'src/ui/ownerFlagSheet.js'); async function flagCell(code) { const svg = await readFile(path.join(FLAGS_DIR, `${code.toLowerCase()}.svg`)); return sharp(svg, { density: 300 }) .resize(CELL.width, CELL.height, { fit: 'fill' }) .png() .toBuffer(); } const codes = [...OWNER_COUNTRY_CODES]; const rows = Math.ceil(codes.length / COLUMNS); const pitchX = CELL.width + GUTTER; const pitchY = CELL.height + GUTTER; const cells = await Promise.all(codes.map(flagCell)); const sheet = await sharp({ create: { width: COLUMNS * pitchX, height: rows * pitchY, channels: 4, background: { r: 0, g: 0, b: 0, alpha: 0 }, }, }) .composite( cells.map((input, i) => ({ input, left: (i % COLUMNS) * pitchX, top: Math.floor(i / COLUMNS) * pitchY, })), ) .png({ compressionLevel: 9, palette: true, quality: 100, effort: 10 }) .toBuffer(); await writeFile(SHEET, sheet); const layout = `// Generated by scripts/build-owner-flags.mjs from flag-icons ${FLAG_ICONS_VERSION}; do not edit. /** The owner flag sheet's layout: flag i at column i % columns, row i / columns. */ export const OWNER_FLAG_SHEET = Object.freeze({ cellWidthPx: ${CELL.width}, cellHeightPx: ${CELL.height}, gutterPx: ${GUTTER}, columns: ${COLUMNS}, rows: ${rows}, codes: Object.freeze(${JSON.stringify(codes)}), }); `; await writeFile( LAYOUT, await prettier.format(layout, { ...(await prettier.resolveConfig(LAYOUT)), filepath: LAYOUT, }), ); console.log( `Wrote ${codes.length} flags (${COLUMNS} × ${rows}), ${(sheet.length / 1024).toFixed(1)} KB.`, );