Spaces:
Running
Running
Bibhu Mishra Claude Sonnet 4.6 commited on
Commit ·
6c2eb4a
1
Parent(s): 1deac60
feat: add PWA support with manifest, service worker, and icons
Browse filesAdds vite-plugin-pwa (Workbox) for installable PWA experience:
- Web app manifest with brand colors, icons, standalone display mode
- Service worker with NetworkFirst for /api/* and CacheFirst for static assets
- 192x192, 512x512, and 180x180 apple-touch-icon PNGs generated from brand gradient
- PWA meta tags in index.html for iOS and Android home screen installation
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- generate-pwa-icons.js +58 -0
- package-lock.json +0 -0
- ui/index.html +6 -0
- ui/package.json +1 -0
- ui/public/apple-touch-icon.png +0 -0
- ui/public/pwa-192x192.png +0 -0
- ui/public/pwa-512x512.png +0 -0
- ui/vite.config.ts +43 -0
generate-pwa-icons.js
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env node
|
| 2 |
+
// Generates solid-color PNG icons for PWA manifest using only Node built-ins.
|
| 3 |
+
const zlib = require('zlib');
|
| 4 |
+
const fs = require('fs');
|
| 5 |
+
const path = require('path');
|
| 6 |
+
|
| 7 |
+
function u32be(n) {
|
| 8 |
+
const b = Buffer.alloc(4);
|
| 9 |
+
b.writeUInt32BE(n, 0);
|
| 10 |
+
return b;
|
| 11 |
+
}
|
| 12 |
+
|
| 13 |
+
function crc32(buf) {
|
| 14 |
+
let c = 0xFFFFFFFF;
|
| 15 |
+
const t = [];
|
| 16 |
+
for (let n = 0; n < 256; n++) {
|
| 17 |
+
let v = n;
|
| 18 |
+
for (let k = 0; k < 8; k++) v = (v & 1) ? (0xEDB88320 ^ (v >>> 1)) : (v >>> 1);
|
| 19 |
+
t[n] = v;
|
| 20 |
+
}
|
| 21 |
+
for (let i = 0; i < buf.length; i++) c = (c >>> 8) ^ t[(c ^ buf[i]) & 0xFF];
|
| 22 |
+
return (c ^ 0xFFFFFFFF) >>> 0;
|
| 23 |
+
}
|
| 24 |
+
|
| 25 |
+
function chunk(type, data) {
|
| 26 |
+
const tb = Buffer.from(type, 'ascii');
|
| 27 |
+
return Buffer.concat([u32be(data.length), tb, data, u32be(crc32(Buffer.concat([tb, data])))]);
|
| 28 |
+
}
|
| 29 |
+
|
| 30 |
+
function makePNG(w, h, topR, topG, topB, botR, botG, botB) {
|
| 31 |
+
const sig = Buffer.from([137, 80, 78, 71, 13, 10, 26, 10]);
|
| 32 |
+
const ihdr = chunk('IHDR', Buffer.concat([u32be(w), u32be(h), Buffer.from([8, 2, 0, 0, 0])]));
|
| 33 |
+
|
| 34 |
+
const rows = [];
|
| 35 |
+
for (let y = 0; y < h; y++) {
|
| 36 |
+
const t = y / (h - 1);
|
| 37 |
+
const r = Math.round(topR + (botR - topR) * t);
|
| 38 |
+
const g = Math.round(topG + (botG - topG) * t);
|
| 39 |
+
const b = Math.round(topB + (botB - topB) * t);
|
| 40 |
+
const row = Buffer.alloc(1 + w * 3);
|
| 41 |
+
row[0] = 0;
|
| 42 |
+
for (let x = 0; x < w; x++) { row[1 + x*3] = r; row[2 + x*3] = g; row[3 + x*3] = b; }
|
| 43 |
+
rows.push(row);
|
| 44 |
+
}
|
| 45 |
+
const idat = chunk('IDAT', zlib.deflateSync(Buffer.concat(rows)));
|
| 46 |
+
const iend = chunk('IEND', Buffer.alloc(0));
|
| 47 |
+
return Buffer.concat([sig, ihdr, idat, iend]);
|
| 48 |
+
}
|
| 49 |
+
|
| 50 |
+
// Brand gradient: #1e40af → #1e3a5f (top to bottom)
|
| 51 |
+
const [tR, tG, tB] = [0x1e, 0x40, 0xaf];
|
| 52 |
+
const [bR, bG, bB] = [0x1e, 0x3a, 0x5f];
|
| 53 |
+
|
| 54 |
+
const out = path.join(__dirname, 'ui/public');
|
| 55 |
+
fs.writeFileSync(path.join(out, 'pwa-192x192.png'), makePNG(192, 192, tR, tG, tB, bR, bG, bB));
|
| 56 |
+
fs.writeFileSync(path.join(out, 'pwa-512x512.png'), makePNG(512, 512, tR, tG, tB, bR, bG, bB));
|
| 57 |
+
fs.writeFileSync(path.join(out, 'apple-touch-icon.png'), makePNG(180, 180, tR, tG, tB, bR, bG, bB));
|
| 58 |
+
console.log('PWA icons written to ui/public/');
|
package-lock.json
CHANGED
|
The diff for this file is too large to render.
See raw diff
|
|
|
ui/index.html
CHANGED
|
@@ -4,7 +4,13 @@
|
|
| 4 |
<meta charset="UTF-8">
|
| 5 |
<link rel="icon" type="image/svg+xml" href="/favicon.svg">
|
| 6 |
<link rel="icon" type="image/x-icon" href="/favicon.ico" sizes="32x32">
|
|
|
|
| 7 |
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 8 |
<meta name="description" content="AlphaForge — AI-Powered Trading Intelligence. Autonomous paper trading with GPT-4o driven market analysis.">
|
| 9 |
<title>AlphaForge</title>
|
| 10 |
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
|
|
| 4 |
<meta charset="UTF-8">
|
| 5 |
<link rel="icon" type="image/svg+xml" href="/favicon.svg">
|
| 6 |
<link rel="icon" type="image/x-icon" href="/favicon.ico" sizes="32x32">
|
| 7 |
+
<link rel="apple-touch-icon" href="/apple-touch-icon.png">
|
| 8 |
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 9 |
+
<meta name="theme-color" content="#1e40af">
|
| 10 |
+
<meta name="mobile-web-app-capable" content="yes">
|
| 11 |
+
<meta name="apple-mobile-web-app-capable" content="yes">
|
| 12 |
+
<meta name="apple-mobile-web-app-status-bar-style" content="default">
|
| 13 |
+
<meta name="apple-mobile-web-app-title" content="AlphaForge">
|
| 14 |
<meta name="description" content="AlphaForge — AI-Powered Trading Intelligence. Autonomous paper trading with GPT-4o driven market analysis.">
|
| 15 |
<title>AlphaForge</title>
|
| 16 |
<link rel="preconnect" href="https://fonts.googleapis.com">
|
ui/package.json
CHANGED
|
@@ -36,6 +36,7 @@
|
|
| 36 |
"oxlint": "~1.60.0",
|
| 37 |
"typescript": "~6.0.0",
|
| 38 |
"vite": "^8.0.8",
|
|
|
|
| 39 |
"vite-plugin-vue-devtools": "^8.1.1",
|
| 40 |
"vue-tsc": "^3.2.6"
|
| 41 |
},
|
|
|
|
| 36 |
"oxlint": "~1.60.0",
|
| 37 |
"typescript": "~6.0.0",
|
| 38 |
"vite": "^8.0.8",
|
| 39 |
+
"vite-plugin-pwa": "^1.3.0",
|
| 40 |
"vite-plugin-vue-devtools": "^8.1.1",
|
| 41 |
"vue-tsc": "^3.2.6"
|
| 42 |
},
|
ui/public/apple-touch-icon.png
ADDED
|
|
ui/public/pwa-192x192.png
ADDED
|
ui/public/pwa-512x512.png
ADDED
|
ui/vite.config.ts
CHANGED
|
@@ -4,6 +4,7 @@ import path from 'node:path'
|
|
| 4 |
import { defineConfig } from 'vite'
|
| 5 |
import vue from '@vitejs/plugin-vue'
|
| 6 |
import vueDevTools from 'vite-plugin-vue-devtools'
|
|
|
|
| 7 |
|
| 8 |
// https://vite.dev/config/
|
| 9 |
export default defineConfig({
|
|
@@ -12,6 +13,48 @@ export default defineConfig({
|
|
| 12 |
plugins: [
|
| 13 |
vue(),
|
| 14 |
vueDevTools(),
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 15 |
],
|
| 16 |
resolve: {
|
| 17 |
alias: {
|
|
|
|
| 4 |
import { defineConfig } from 'vite'
|
| 5 |
import vue from '@vitejs/plugin-vue'
|
| 6 |
import vueDevTools from 'vite-plugin-vue-devtools'
|
| 7 |
+
import { VitePWA } from 'vite-plugin-pwa'
|
| 8 |
|
| 9 |
// https://vite.dev/config/
|
| 10 |
export default defineConfig({
|
|
|
|
| 13 |
plugins: [
|
| 14 |
vue(),
|
| 15 |
vueDevTools(),
|
| 16 |
+
VitePWA({
|
| 17 |
+
registerType: 'autoUpdate',
|
| 18 |
+
includeAssets: ['favicon.svg', 'favicon.ico', 'apple-touch-icon.png'],
|
| 19 |
+
manifest: {
|
| 20 |
+
name: 'AlphaForge',
|
| 21 |
+
short_name: 'AlphaForge',
|
| 22 |
+
description: 'AI-Powered autonomous paper trading intelligence',
|
| 23 |
+
theme_color: '#1e40af',
|
| 24 |
+
background_color: '#f3f4f6',
|
| 25 |
+
display: 'standalone',
|
| 26 |
+
start_url: '/',
|
| 27 |
+
scope: '/',
|
| 28 |
+
icons: [
|
| 29 |
+
{ src: 'pwa-192x192.png', sizes: '192x192', type: 'image/png' },
|
| 30 |
+
{ src: 'pwa-512x512.png', sizes: '512x512', type: 'image/png' },
|
| 31 |
+
{ src: 'pwa-512x512.png', sizes: '512x512', type: 'image/png', purpose: 'any maskable' },
|
| 32 |
+
],
|
| 33 |
+
},
|
| 34 |
+
workbox: {
|
| 35 |
+
navigateFallback: '/index.html',
|
| 36 |
+
globPatterns: ['**/*.{js,css,html,ico,png,svg,woff2}'],
|
| 37 |
+
runtimeCaching: [
|
| 38 |
+
{
|
| 39 |
+
urlPattern: /^.*\/api\/.*/i,
|
| 40 |
+
handler: 'NetworkFirst',
|
| 41 |
+
options: {
|
| 42 |
+
cacheName: 'api-cache',
|
| 43 |
+
networkTimeoutSeconds: 10,
|
| 44 |
+
cacheableResponse: { statuses: [0, 200] },
|
| 45 |
+
},
|
| 46 |
+
},
|
| 47 |
+
{
|
| 48 |
+
urlPattern: /^https:\/\/fonts\.googleapis\.com\/.*/i,
|
| 49 |
+
handler: 'CacheFirst',
|
| 50 |
+
options: {
|
| 51 |
+
cacheName: 'google-fonts-cache',
|
| 52 |
+
cacheableResponse: { statuses: [0, 200] },
|
| 53 |
+
},
|
| 54 |
+
},
|
| 55 |
+
],
|
| 56 |
+
},
|
| 57 |
+
}),
|
| 58 |
],
|
| 59 |
resolve: {
|
| 60 |
alias: {
|