diff --git a/index.html b/index.html index 91d0f77..6f175ef 100644 --- a/index.html +++ b/index.html @@ -3,9 +3,14 @@ - + + + + + + diff --git a/package.json b/package.json index 839c03d..96b7cd0 100644 --- a/package.json +++ b/package.json @@ -22,7 +22,9 @@ "test:admin": "node admin-test.mjs", "test:unit": "node --import tsx --test server/lib/scheduling.test.ts server/lib/time.test.ts server/lib/metrics.test.ts", "test:booking": "node server/scripts/booking-e2e.mjs", - "audit:visual": "node visual-audit.mjs" + "audit:visual": "node visual-audit.mjs", + "generate:pwa-icons": "node scripts/generate-pwa-icons.mjs", + "test:pwa": "node pwa-e2e.mjs" }, "dependencies": { "@dnd-kit/core": "^6.1.0", diff --git a/public/icon-192.png b/public/icon-192.png new file mode 100644 index 0000000..3a1d80f Binary files /dev/null and b/public/icon-192.png differ diff --git a/public/icon-512.png b/public/icon-512.png new file mode 100644 index 0000000..a19de4d Binary files /dev/null and b/public/icon-512.png differ diff --git a/public/manifest.webmanifest b/public/manifest.webmanifest new file mode 100644 index 0000000..20e148b --- /dev/null +++ b/public/manifest.webmanifest @@ -0,0 +1,15 @@ +{ + "name": "AgendaMax", + "short_name": "AgendaMax", + "description": "Gestión visual de citas, empleados e ingresos para tu negocio.", + "start_url": "/", + "scope": "/", + "display": "standalone", + "orientation": "portrait-primary", + "background_color": "#f6f7fb", + "theme_color": "#3b66ff", + "icons": [ + { "src": "/icon-192.png", "sizes": "192x192", "type": "image/png" }, + { "src": "/icon-512.png", "sizes": "512x512", "type": "image/png", "purpose": "any maskable" } + ] +} diff --git a/scripts/generate-pwa-icons.mjs b/scripts/generate-pwa-icons.mjs new file mode 100644 index 0000000..165248b --- /dev/null +++ b/scripts/generate-pwa-icons.mjs @@ -0,0 +1,20 @@ +import { chromium } from "playwright"; +import fs from "node:fs/promises"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; + +const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), ".."); +const svg = await fs.readFile(path.join(root, "public", "favicon.svg"), "utf8"); +const svgDataUrl = `data:image/svg+xml;base64,${Buffer.from(svg).toString("base64")}`; +const browser = await chromium.launch({ headless: true }); + +try { + for (const size of [192, 512]) { + const page = await browser.newPage({ viewport: { width: size, height: size }, deviceScaleFactor: 1 }); + await page.setContent(``); + await page.screenshot({ path: path.join(root, "public", `icon-${size}.png`), type: "png" }); + await page.close(); + } +} finally { + await browser.close(); +}