commit 41df82ec75ea3849b76b6d13360d24904ca2e173
parent 80fb7f680dd97246273846599c9e333a6e9d3fdc
Author: finwo <finwo@pm.me>
Date: Sun, 14 Sep 2025 22:47:52 +0200
Added icons
Diffstat:
9 files changed, 331 insertions(+), 92 deletions(-)
diff --git a/.github/workflows/build-and-deploy.yml b/.github/workflows/build-and-deploy.yml
@@ -16,6 +16,7 @@ jobs:
- name: Install and Build
run: |
+ sudo apt install -yq ffmpeg
echo "BASEURL_APP='\"https://cq.finwo.net/#!\"'" > packages/app/.env
npm install
npm run build
diff --git a/.gitignore b/.gitignore
@@ -1 +1,2 @@
node_modules/
+.DS_Store
diff --git a/icon.html b/icon.html
@@ -0,0 +1,138 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta charset="utf-8"/>
+ </head>
+ <body>
+ <canvas id="org" width="1024" height="1024"></canvas>
+ <script>
+ (async () => {
+ let ctx = org.getContext('2d');
+ let idata = ctx.getImageData(0, 0, org.width, org.height);
+ let center = [];
+ let radius = 128;
+
+ for(let y=0; y<org.height; y++) {
+ for(let x=0; x<org.width; x++) {
+ const idx = ((y*org.width)+x)*4;
+ idata.data[idx+0] = 34;
+ idata.data[idx+1] = 34;
+ idata.data[idx+2] = 34;
+ idata.data[idx+3] = 255;
+ }
+ }
+ ctx.putImageData(idata, 0, 0);
+ await new Promise(r => requestAnimationFrame(r));
+
+ // octagon outer
+ center = [512,512];
+ radius = 360;
+ for(let x=0; x<org.width; x++) {
+ for(let y=0; y<org.height; y++) {
+ const idx = ((y*org.width)+x)*4;
+ let xoff = x-center[0];
+ let yoff = y-center[1];
+ if (Math.abs(xoff) > radius) continue;
+ if (Math.abs(yoff) > radius) continue;
+ if (Math.abs(xoff)+Math.abs(yoff) > (radius*1.45)) continue;
+ idata.data[idx+0] = 255;
+ idata.data[idx+1] = 255;
+ idata.data[idx+2] = 255;
+ }
+ }
+ ctx.putImageData(idata, 0, 0);
+ await new Promise(r => requestAnimationFrame(r));
+
+ // octagon inner
+ center = [512,512];
+ radius = 264;
+ for(let x=0; x<org.width; x++) {
+ for(let y=0; y<org.height; y++) {
+ const idx = ((y*org.width)+x)*4;
+ let xoff = x-center[0];
+ let yoff = y-center[1];
+ if (Math.abs(xoff) > radius) continue;
+ if (Math.abs(yoff) > radius) continue;
+ if (Math.abs(xoff)+Math.abs(yoff) > (radius*1.45)) continue;
+ idata.data[idx+0] = 34;
+ idata.data[idx+1] = 34;
+ idata.data[idx+2] = 34;
+ }
+ }
+ ctx.putImageData(idata, 0, 0);
+ await new Promise(r => requestAnimationFrame(r));
+
+ // Remove bottom right
+ for(let x=600; x<org.width; x++) {
+ for(let y=600; y<org.height; y++) {
+ const idx = ((y*org.width)+x)*4;
+ idata.data[idx+0] = 34;
+ idata.data[idx+1] = 34;
+ idata.data[idx+2] = 34;
+ }
+ }
+ ctx.putImageData(idata, 0, 0);
+ await new Promise(r => requestAnimationFrame(r));
+
+ // // Extend top bar
+ // for(let x=512; x<(512+312); x++) {
+ // for(let y=(512-360); y<(512-264); y++) {
+ // const idx = ((y*org.width)+x)*4;
+ // idata.data[idx+0] = 255;
+ // idata.data[idx+1] = 255;
+ // idata.data[idx+2] = 255;
+ // }
+ // }
+ // ctx.putImageData(idata, 0, 0);
+ // await new Promise(r => requestAnimationFrame(r));
+ // for(let x=512; x<(512+324); x++) {
+ // for(let y=(512+264+1); y<(512+360+1); y++) {
+ // const idx = ((y*org.width)+x)*4;
+ // idata.data[idx+0] = 255;
+ // idata.data[idx+1] = 255;
+ // idata.data[idx+2] = 255;
+ // }
+ // }
+ // ctx.putImageData(idata, 0, 0);
+ // await new Promise(r => requestAnimationFrame(r));
+
+ // Diagonal, mixes Q into C
+ for(let x=512; x<(512+360+1); x++) {
+ for(let y=512; y<(512+360+1); y++) {
+ if (Math.abs(x-y) > (360-264)/1.41) continue;
+ const idx = ((y*org.width)+x)*4;
+ idata.data[idx+0] = 255;
+ idata.data[idx+1] = 255;
+ idata.data[idx+2] = 255;
+ }
+ }
+ ctx.putImageData(idata, 0, 0);
+ await new Promise(r => requestAnimationFrame(r));
+
+ // Stretch lower side
+ for(let y=1024; y>488; y--) {
+ for(let x=0; x<1024; x++) {
+ const idx = ((y*org.width)+x)*4;
+ idata.data[idx+0] = idata.data[idx-(org.width*128)+0];
+ idata.data[idx+1] = idata.data[idx-(org.width*128)+1];
+ idata.data[idx+2] = idata.data[idx-(org.width*128)+2];
+ }
+ }
+ ctx.putImageData(idata, 0, 0);
+ await new Promise(r => requestAnimationFrame(r));
+
+ // Stretch upper side
+ for(let y=0; y<488; y++) {
+ for(let x=0; x<1024; x++) {
+ const idx = ((y*org.width)+x)*4;
+ idata.data[idx+0] = idata.data[idx+(org.width*128)+0];
+ idata.data[idx+1] = idata.data[idx+(org.width*128)+1];
+ idata.data[idx+2] = idata.data[idx+(org.width*128)+2];
+ }
+ }
+ ctx.putImageData(idata, 0, 0);
+ await new Promise(r => requestAnimationFrame(r));
+ })();
+ </script>
+ </body>
+</html>
diff --git a/packages/app/build.js b/packages/app/build.js
@@ -0,0 +1,112 @@
+#!/usr/bin/env node
+
+require('dotenv').config();
+const cpy = require('cpy').default;
+const fs = require('node:fs');
+const { dirname } = require('node:path');
+const { fileURLToPath } = require('node:url');
+const { exec } = require('node:child_process');
+const esbuild = require('esbuild');
+const { nodeModulesPolyfillPlugin } = require('esbuild-plugins-node-modules-polyfill');
+
+// const __dirname = dirname(fileURLToPath(import.meta.url));
+
+const entryPoints = {
+ 'main': __dirname + '/src/main.ts',
+};
+
+const define = {
+ 'process.env.BASEURL_APP': process.env.BASEURL_APP || '"http://localhost:4000/#!"',
+};
+
+const config = {
+ format: 'esm',
+ platform: 'browser',
+ target: ['chrome108','firefox107'],
+ mainFields: ['browser','module','main'],
+ bundle: true,
+ outdir: __dirname + '/dist',
+ entryPoints: Object.values(entryPoints),
+ minify: false,
+ define,
+
+ jsxFactory : 'm',
+ jsxFragment : '"["',
+
+ loader: {
+ '.eot' : 'dataurl',
+ '.html' : 'text',
+ '.wasm' : 'dataurl',
+ '.png' : 'dataurl',
+ '.svg' : 'dataurl',
+ '.ttf' : 'dataurl',
+ '.woff' : 'dataurl',
+ '.woff2': 'dataurl',
+ },
+
+ plugins: [
+ nodeModulesPolyfillPlugin({
+ globals: {
+ Buffer: true
+ }
+ })
+ ],
+
+};
+
+const buildList = [];
+const styles = ['global.css'];
+
+esbuild
+ .build(config)
+ .then(async () => {
+ try { fs.mkdirSync('./dist/assets'); } catch { /* empty */ }
+ const r = await cpy(__dirname + '/src/assets/*', __dirname + '/dist/assets');
+// try { fs.mkdirSync('./dist/AppModule'); } catch { /* empty */ }
+ fs.copyFileSync(`./src/global.css`, `./dist/global.css`);
+ for(const name of Object.keys(entryPoints)) {
+ buildList.push(`${name}.js`);
+ try {
+ fs.statSync(config.outdir + `/${name}.css`);
+ styles.push(`${name}.css`);
+ } catch(e) {
+ // Intentionally empty
+ }
+ }
+
+ fs.writeFileSync(config.outdir + `/index.html`, `<!DOCTYPE html>
+<html>
+ <head>
+ <meta charset="utf-8">
+ <meta name="viewport" content="width=device-width,initial-scale=1">
+ <link rel="icon" href="/assets/favicon.ico">
+ <link rel="apple-touch-icon" href="/assets/apple-touch-icon.png">
+ <link rel="icon" type="image/png" sizes="192x192" href="/assets/icon-192x192.png">
+ <link rel="icon" type="image/png" sizes="512x512" href="/assets/icon-512x512.png">
+ <link rel="manifest" href="/assets/manifest.json"/>
+ ${styles.map(name => `<link rel="preload" as="style" href="${name}" onload="this.onload=null;this.rel='stylesheet'"/>`).join('\n ')}
+ </head>
+ <body>
+ ${buildList.map(name => `<script type="module" src="${name}" defer></script>`).join('\n ')}
+ </body>
+</html>
+`);
+
+ // Build favicons
+ await new Promise(done => {
+ exec(`ffmpeg -n -i ${config.outdir}/assets/icon.png -vf scale=32:32 -sws_flags area ${config.outdir}/assets/favicon.ico`, done);
+ });
+ await new Promise(done => {
+ exec(`ffmpeg -n -i ${config.outdir}/assets/icon.png -vf scale=180:180 -sws_flags area ${config.outdir}/assets/apple-touch-icon.png`, done);
+ });
+
+ // Build app icons
+ const manifest = require(__dirname+'/dist/assets/manifest.json');
+ for(const format of manifest.icons) {
+ const scale = format.sizes.split('x').join(':');
+ await new Promise(done => {
+ exec(`ffmpeg -n -i ${config.outdir}/assets/icon.png -vf scale=${scale} -sws_flags area ${config.outdir}${format.src}`, done);
+ });
+ }
+
+ })
diff --git a/packages/app/esbuild.js b/packages/app/esbuild.js
@@ -1,90 +0,0 @@
-#!/usr/bin/env node
-
-require('dotenv').config();
-const cpy = require('cpy').default;
-const fs = require('node:fs');
-const { dirname } = require('node:path');
-const { fileURLToPath } = require('node:url');
-const esbuild = require('esbuild');
-const { nodeModulesPolyfillPlugin } = require('esbuild-plugins-node-modules-polyfill');
-
-// const __dirname = dirname(fileURLToPath(import.meta.url));
-
-const entryPoints = {
- 'main': __dirname + '/src/main.ts',
-};
-
-const define = {
- 'process.env.BASEURL_APP': process.env.BASEURL_APP || '"http://localhost:4000/#!"',
-};
-
-const config = {
- format: 'esm',
- platform: 'browser',
- target: ['chrome108','firefox107'],
- mainFields: ['browser','module','main'],
- bundle: true,
- outdir: __dirname + '/dist',
- entryPoints: Object.values(entryPoints),
- minify: false,
- define,
-
- jsxFactory : 'm',
- jsxFragment : '"["',
-
- loader: {
- '.eot' : 'dataurl',
- '.html' : 'text',
- '.wasm' : 'dataurl',
- '.png' : 'dataurl',
- '.svg' : 'dataurl',
- '.ttf' : 'dataurl',
- '.woff' : 'dataurl',
- '.woff2': 'dataurl',
- },
-
- plugins: [
- nodeModulesPolyfillPlugin({
- globals: {
- Buffer: true
- }
- })
- ],
-
-};
-
-const buildList = [];
-const styles = ['global.css'];
-
-esbuild
- .build(config)
- .then(async () => {
- try { fs.mkdirSync('./dist/assets'); } catch { /* empty */ }
- const r = await cpy(__dirname + '/src/assets/*', __dirname + '/dist/assets');
-// try { fs.mkdirSync('./dist/AppModule'); } catch { /* empty */ }
- fs.copyFileSync(`./src/global.css`, `./dist/global.css`);
- for(const name of Object.keys(entryPoints)) {
- buildList.push(`${name}.js`);
- try {
- fs.statSync(config.outdir + `/${name}.css`);
- styles.push(`${name}.css`);
- } catch(e) {
- // Intentionally empty
- }
- }
-
- fs.writeFileSync(config.outdir + `/index.html`, `<!DOCTYPE html>
-<html>
- <head>
- <meta charset="utf-8">
- <meta name="viewport" content="width=device-width,initial-scale=1">
- <link rel="manifest" href="/assets/manifest.json"/>
- ${styles.map(name => `<link rel="preload" as="style" href="${name}" onload="this.onload=null;this.rel='stylesheet'"/>`).join('\n ')}
- </head>
- <body>
- ${buildList.map(name => `<script type="module" src="${name}" defer></script>`).join('\n ')}
- </body>
-</html>
-`);
-
- })
diff --git a/packages/app/package.json b/packages/app/package.json
@@ -7,7 +7,7 @@
"watch": "npx -y nodemon -w src -e css,ts,tsx --exec npm -- run build",
"serve": "PORT=4000 npx -y serve dist",
"dev": "npx -y concurrently 'npm run watch' 'npm run serve'",
- "build": "node esbuild.js"
+ "build": "node build.js"
},
"keywords": [],
"author": "",
diff --git a/packages/app/src/assets/icon.png b/packages/app/src/assets/icon.png
Binary files differ.
diff --git a/packages/app/src/assets/manifest.json b/packages/app/src/assets/manifest.json
@@ -1,7 +1,83 @@
{
"short_name": "CQ",
"name": "Seek You",
- "icons": [],
+ "icons": [
+ {
+ "src": "/assets/icon-512.png",
+ "sizes": "512x512",
+ "type": "image/png"
+ },
+ {
+ "src": "/assets/icon-384.png",
+ "sizes": "384x384",
+ "type": "image/png"
+ },
+ {
+ "src": "/assets/icon-256.png",
+ "sizes": "256x256",
+ "type": "image/png"
+ },
+ {
+ "src": "/assets/icon-192.png",
+ "sizes": "192x192",
+ "type": "image/png"
+ },
+ {
+ "src": "/assets/icon-180.png",
+ "sizes": "180x180",
+ "type": "image/png"
+ },
+ {
+ "src": "/assets/icon-152.png",
+ "sizes": "152x152",
+ "type": "image/png"
+ },
+ {
+ "src": "/assets/icon-144.png",
+ "sizes": "144x144",
+ "type": "image/png"
+ },
+ {
+ "src": "/assets/icon-120.png",
+ "sizes": "120x120",
+ "type": "image/png"
+ },
+ {
+ "src": "/assets/icon-114.png",
+ "sizes": "114x114",
+ "type": "image/png"
+ },
+ {
+ "src": "/assets/icon-96.png",
+ "sizes": "96x96",
+ "type": "image/png"
+ },
+ {
+ "src": "/assets/icon-76.png",
+ "sizes": "76x76",
+ "type": "image/png"
+ },
+ {
+ "src": "/assets/icon-72.png",
+ "sizes": "72x72",
+ "type": "image/png"
+ },
+ {
+ "src": "/assets/icon-60.png",
+ "sizes": "60x60",
+ "type": "image/png"
+ },
+ {
+ "src": "/assets/icon-57.png",
+ "sizes": "57x57",
+ "type": "image/png"
+ },
+ {
+ "src": "/assets/icon-48.png",
+ "sizes": "48x48",
+ "type": "image/png"
+ }
+ ],
"start_url": "/",
"display": "standalone",
"theme_color": "black",
diff --git a/packages/app/src/component/screen-account-create.tsx b/packages/app/src/component/screen-account-create.tsx
@@ -46,6 +46,7 @@ export const AccountCreateScreen = {
if (step === 4) {
await new Promise(r => setTimeout(r, 10));
const outputEl = document.getElementById('buildOutput') || document.createElement('div');
+ outputEl.innerText += '\n';
// // Store seed (TODO: don't)
// const accountSeedHandle = await getFileHandle(`/local/accounts/${vnode.state.accountId}/seed.txt`, { create: true })