From ce6b564e2b04724656a487f3dce03590129608a8 Mon Sep 17 00:00:00 2001 From: Thomas Joise Date: Sat, 18 Jul 2026 01:21:36 +0530 Subject: [PATCH] feat(pwa/d25): installable PWA + phone polish MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Web manifest (standalone, id/scope/start_url=/, teal theme) + icon set generated from the HQ mark: pwa-192, pwa-512, maskable-512 (full-bleed safe-zone), apple-touch-icon 180, icon.svg. sharp added as a devDep for generation. - Service worker (public/sw.js): installable + fast repeat loads without faking offline. NEVER touches /api or /share (always live); navigations network-first with an app-shell fallback; hashed assets cache-first. Registered from main.tsx in production builds only (skips Vite dev/HMR). - index.html: manifest link, apple-touch-icon, apple/mobile-web-app meta, viewport-fit=cover; vite-env.d.ts adds vite/client types. - Phone polish (app.css): safe-area insets, 16px inputs (kills iOS focus-zoom), 40px tap targets, bottom-sheet dialogs, 2-up stat cards — on top of the existing <900px drawer + scrolling tables. typecheck + web build clean, web tests 4/4. Co-Authored-By: Claude Fable 5 --- apps/hq-web/index.html | 9 +- apps/hq-web/package.json | 1 + apps/hq-web/public/apple-touch-icon.png | Bin 0 -> 3158 bytes apps/hq-web/public/icon.svg | 12 + apps/hq-web/public/manifest.webmanifest | 19 + apps/hq-web/public/maskable-512.png | Bin 0 -> 11106 bytes apps/hq-web/public/pwa-192.png | Bin 0 -> 4342 bytes apps/hq-web/public/pwa-512.png | Bin 0 -> 15923 bytes apps/hq-web/public/sw.js | 61 +++ apps/hq-web/src/app.css | 25 + apps/hq-web/src/main.tsx | 8 + apps/hq-web/src/vite-env.d.ts | 1 + package-lock.json | 602 ++++++++++++++++++++++++ 13 files changed, 737 insertions(+), 1 deletion(-) create mode 100644 apps/hq-web/public/apple-touch-icon.png create mode 100644 apps/hq-web/public/icon.svg create mode 100644 apps/hq-web/public/manifest.webmanifest create mode 100644 apps/hq-web/public/maskable-512.png create mode 100644 apps/hq-web/public/pwa-192.png create mode 100644 apps/hq-web/public/pwa-512.png create mode 100644 apps/hq-web/public/sw.js create mode 100644 apps/hq-web/src/vite-env.d.ts diff --git a/apps/hq-web/index.html b/apps/hq-web/index.html index df55a91..dac890f 100644 --- a/apps/hq-web/index.html +++ b/apps/hq-web/index.html @@ -2,9 +2,16 @@ - + + + + + + + + SiMS HQ diff --git a/apps/hq-web/package.json b/apps/hq-web/package.json index 9f37f2a..bff7492 100644 --- a/apps/hq-web/package.json +++ b/apps/hq-web/package.json @@ -21,6 +21,7 @@ "@types/react": "^19.0.0", "@types/react-dom": "^19.0.0", "@vitejs/plugin-react": "^4.3.0", + "sharp": "^0.35.3", "vite": "^6.0.0" } } diff --git a/apps/hq-web/public/apple-touch-icon.png b/apps/hq-web/public/apple-touch-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..c17f435f3ed7fb9fa4abc816d3cebf07ec6a265a GIT binary patch literal 3158 zcmd5<_fr#E6NN`_p*?z}sem9Q)C7?jY7huTItmz&h|+`)2u*sC5|koHZ-PFMUZsXm zl#cWsdP}I%LB72I;?3N>`^)Z~-8pB@ocqoYq0L0kOHW2d#-sz+Fec@ie}ncf(p^>l z;RPwtxx+0z$;cSk{|$1o^h|CtGDcb*4VVc!lbCh+#AJh~=bZh-G|6Jr#AU2Tt}e?a zv8e5{ex`%$HP+j>njsr^Et1QrFu@?~1*d|MvxbqYn@EY6vFgWhY0!zkZ03S>H7(O8 za#%DHJrF1^cDKIlQ$lw2P<7wYpbyY7(|4$8=(Kw8vU$jNXj)Jt{p(+-#}T{B8G#m?i?XX?h8QniGX zI~{`glr%hY#<%TQa^{0n$Z3fBxy0vq4;q>?JX{#f5;uf}`BFDah&cb;N^l>pAt%N} zBLHAAI;#2*p;5y%`6p%ajU( z|FaF7e2YKnD))+u2c#C>*LwDJZ(nnG)uu2Vlg?d*ajM0D+JM)e-Zxz&+@#+ahm&Fc z!Y;+8A>tBZY;xMRhOs55{j#<@#69R3`fXP#N0835zXmDy`$1CTrTJxb->OHEwW{TO zL>C67)Gyon@exNDMWsf<0J1i_>CJTu$5+3gFQDf_MG7U}{N;PqOh0UDJ~n;Znr%HB zhbC!>0l#v**rm9m^D85E{m93{KW`*3N@;fX_!2CwNqZ1MCFx`Rf;f|V$5e5wgkCMU z-N?${0;G&w;Qa#_;EY8^Noz-K9a!d1ZY*wXzCU&fOZ?q6^4+!{BMQ!bmG|27?6${V zh%68qPdBO9>2^P}7;NwK>${&Hr^|VOo<(#5EB1k>V3hq}w}JoVBPfgMA>w$+`*_pXS2en+%J>^Dtu$3C^^+t`F|A$6Z$%dDm#sfj{c&O z5%}Ot$Vby;Km6S3#4gk2DMDA*sMJ6Y5uH_iFzT0ZIF~^rLK6~-K2cC(oSg1acsGgT zX82VsjO(VuLJ6~dseUq=fnJ9XLMg~FTpcKz2Ct+=QJusX03m~gYb)Mbpw!Cu8!=m4 zwd2r~KseKcL^W*|CYM+k?)rEQ{1_bWOfg4xn2t~PbM~ECltnv}(sm1VYtpcS_6s47qO6T_0FGP4vqF@!jHm8n}CZ z$WHltQ;$}(yyqsMhpJV-x0lEpWclpXVb zDu~NZq1|S*~ z2|nNHxe)=6)lQz_@zbDIy3=Ij_&e$xVx{(QKbEd1{zS2>Oyq9oBdpQW7TY)IH@sPr zcfr~5v<7*8rRDSm*&LfSGmh*)%BXkv+!tLMa#7N3zo3#knZnDpF+NE>s-}?4JtCHQC znWl(>pD_j}@xgp9M#h(uiw*MeCS9MujPhFuFsjcBjdDE zoTrAA!d*~ZK~n}wWrov^7EsQgS2ss`gO;)1b)8^U7HG;SwRhZi%p*s9B_JK*g*|BVp(uJjS=gCFERch<#NZxtI zypyF-^dQ5M9Hd@Soum=*{zHs9# zIVK#x-rK%ndnJFm)^xhTk_Qz08y{bKy*(eCweo_{yR!1L`vod7KW_A^8>SzAIRbb;y6yu$Z#2 z$Kk^SLE$i#I2iRl?7c=DOeVTpFpLZeV8hYVNJ{J@WCCPqwR$0V7dx~kARW@~!UF0Q z9CoFep@=jK{R#0jM9}YsN9%R02y?F$4m(CSIJgTh1&4W8;~iy8*y7aTSB9#^DO7W8 ztnqKB3RFUIMxu9B+n=#6k!&utxzd8zQSfQx(htS#sFi~NHRNpt+Fd;Q@CNRzK?Os{ zFmi1`#Ll^~?%XNNm*6^3a%RkJdWOk0#z|2uqwqKBB36xVna0kF9?Gm8hBbkr;M5GZ zFUvf}-;a>=h9bb&u*>VHLvni*z1oxLd+w|5M{hp&Nr3r6lBviMA+4x^id$w(L`vJso918AND{F0{{R3 literal 0 HcmV?d00001 diff --git a/apps/hq-web/public/icon.svg b/apps/hq-web/public/icon.svg new file mode 100644 index 0000000..3836a97 --- /dev/null +++ b/apps/hq-web/public/icon.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/apps/hq-web/public/manifest.webmanifest b/apps/hq-web/public/manifest.webmanifest new file mode 100644 index 0000000..d121b56 --- /dev/null +++ b/apps/hq-web/public/manifest.webmanifest @@ -0,0 +1,19 @@ +{ + "name": "SiMS HQ Console", + "short_name": "SiMS HQ", + "description": "The internal ops console — clients, documents, payments, tickets and reminders.", + "id": "/", + "start_url": "/", + "scope": "/", + "display": "standalone", + "orientation": "any", + "background_color": "#0f1512", + "theme_color": "#115e59", + "categories": ["business", "productivity"], + "icons": [ + { "src": "icon.svg", "sizes": "any", "type": "image/svg+xml", "purpose": "any" }, + { "src": "pwa-192.png", "sizes": "192x192", "type": "image/png", "purpose": "any" }, + { "src": "pwa-512.png", "sizes": "512x512", "type": "image/png", "purpose": "any" }, + { "src": "maskable-512.png", "sizes": "512x512", "type": "image/png", "purpose": "maskable" } + ] +} diff --git a/apps/hq-web/public/maskable-512.png b/apps/hq-web/public/maskable-512.png new file mode 100644 index 0000000000000000000000000000000000000000..0ecad5a9e9a5932c5a99ddb2db9d557cffc9963d GIT binary patch literal 11106 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 literal 0 HcmV?d00001 diff --git a/apps/hq-web/public/pwa-192.png b/apps/hq-web/public/pwa-192.png new file mode 100644 index 0000000000000000000000000000000000000000..c7ceaa1ed78b3251032d6856d730a676ff84304b GIT binary patch literal 4342 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 literal 0 HcmV?d00001 diff --git a/apps/hq-web/public/sw.js b/apps/hq-web/public/sw.js new file mode 100644 index 0000000..58e4e05 --- /dev/null +++ b/apps/hq-web/public/sw.js @@ -0,0 +1,61 @@ +/* + * SiMS HQ service worker — makes the console installable (PWA) and fast on repeat + * visits, without pretending to be a fully offline app (it is an online ops console). + * + * Strategy, deliberately conservative: + * - /api and /share are NEVER touched — always straight to the network (live data, + * auth, the public PDF route). The SW must never cache or stale those. + * - Navigations (opening a page) are network-first, falling back to the cached app + * shell so a flaky connection still opens the UI instead of the browser error page. + * - Hashed static assets (/assets/*, icons) are cache-first: their names change every + * build, so a cached copy is always the right version and new builds self-populate. + * - Only GET, same-origin. Everything else falls through to the network. + */ +const CACHE = 'sims-hq-v1' +const SHELL = ['/', '/index.html', '/manifest.webmanifest', '/favicon.svg', '/icon.svg'] + +self.addEventListener('install', (event) => { + event.waitUntil(caches.open(CACHE).then((c) => c.addAll(SHELL)).then(() => self.skipWaiting())) +}) + +self.addEventListener('activate', (event) => { + event.waitUntil( + caches.keys() + .then((keys) => Promise.all(keys.filter((k) => k !== CACHE).map((k) => caches.delete(k)))) + .then(() => self.clients.claim()), + ) +}) + +self.addEventListener('fetch', (event) => { + const req = event.request + if (req.method !== 'GET') return + const url = new URL(req.url) + if (url.origin !== self.location.origin) return + // Never intercept the API or the public share route — always live. + if (url.pathname.startsWith('/api') || url.pathname.startsWith('/share')) return + + // App navigations: network-first with a shell fallback. + if (req.mode === 'navigate') { + event.respondWith( + fetch(req) + .then((res) => { + const copy = res.clone() + caches.open(CACHE).then((c) => c.put('/', copy)).catch(() => {}) + return res + }) + .catch(() => caches.match('/').then((m) => m || caches.match('/index.html'))), + ) + return + } + + // Static assets: cache-first, then populate. + event.respondWith( + caches.match(req).then((hit) => hit || fetch(req).then((res) => { + if (res.ok && res.type === 'basic') { + const copy = res.clone() + caches.open(CACHE).then((c) => c.put(req, copy)).catch(() => {}) + } + return res + }).catch(() => hit)), + ) +}) diff --git a/apps/hq-web/src/app.css b/apps/hq-web/src/app.css index 0f0586c..6028178 100644 --- a/apps/hq-web/src/app.css +++ b/apps/hq-web/src/app.css @@ -250,3 +250,28 @@ main#main:focus { outline: none; } table.wf tbody tr[role='button']:focus-visible { outline: none; } table.wf tbody tr[role='button']:focus-visible td { background: var(--accent-soft); } table.wf tbody tr[role='button']:focus-visible td:first-child { box-shadow: inset 3px 0 0 var(--accent); } + +/* ============================================================================ + PWA / phone polish (D25). The shell is already responsive (drawer < 900px, + scrolling tables). These add the touch-device niceties on top. + ============================================================================ */ + +/* Notched-phone safe areas (index.html sets viewport-fit=cover). */ +.hq-topbar { padding-left: max(12px, env(safe-area-inset-left)); padding-right: max(12px, env(safe-area-inset-right)); } +.hq-content { padding-bottom: max(16px, env(safe-area-inset-bottom)); } +.hq-side { padding-left: env(safe-area-inset-left); } + +@media (max-width: 640px) { + /* iOS zooms the viewport when a focused field's font is < 16px — pin inputs to 16px. */ + input.wf, select.wf, textarea.wf { font-size: 16px; } + /* Comfortable tap targets on touch. */ + button.wf, select.wf, .wf-dialog-x { min-height: 40px; } + .wf-dialog-x { min-width: 40px; } + /* Dialogs become a bottom sheet: full width, flush to the bottom, taller. */ + .wf-dialog-back { padding: 0; align-items: flex-end; } + .wf-dialog { max-width: 100%; max-height: 92vh; border-radius: 16px 16px 0 0; } + /* Page toolbars stack instead of overflowing the width. */ + .wf-toolbar { gap: 6px; } + /* Stat cards go one-per-row so numbers stay legible. */ + .wf-stats { grid-template-columns: 1fr 1fr; } +} diff --git a/apps/hq-web/src/main.tsx b/apps/hq-web/src/main.tsx index f03873d..b260fd8 100644 --- a/apps/hq-web/src/main.tsx +++ b/apps/hq-web/src/main.tsx @@ -59,3 +59,11 @@ initTheme() createRoot(document.getElementById('root')!).render( , ) + +// PWA: register the service worker in production builds only (a SW in Vite dev +// interferes with HMR). Failure is non-fatal — the app runs fine without it. +if (import.meta.env.PROD && 'serviceWorker' in navigator) { + window.addEventListener('load', () => { + navigator.serviceWorker.register('/sw.js').catch(() => { /* PWA is progressive — ignore */ }) + }) +} diff --git a/apps/hq-web/src/vite-env.d.ts b/apps/hq-web/src/vite-env.d.ts new file mode 100644 index 0000000..11f02fe --- /dev/null +++ b/apps/hq-web/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/package-lock.json b/package-lock.json index ed96dd1..3c836a7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -52,6 +52,7 @@ "@types/react": "^19.0.0", "@types/react-dom": "^19.0.0", "@vitejs/plugin-react": "^4.3.0", + "sharp": "^0.35.3", "vite": "^6.0.0" } }, @@ -484,6 +485,17 @@ "node": ">=6.9.0" } }, + "node_modules/@emnapi/runtime": { + "version": "1.11.2", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.2.tgz", + "integrity": "sha512-kyOl3X0DuTiT1h2ft8r2fYO8JYtU9a9Xis/zBSiGArNaagCOWx90N1k2wxp18czFDH+OgcWGb5ZP/XMt3dcyPA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, "node_modules/@esbuild/win32-x64": { "version": "0.28.1", "cpu": [ @@ -513,6 +525,533 @@ "url": "https://github.com/sponsors/ayuhito" } }, + "node_modules/@img/colour": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@img/colour/-/colour-1.1.0.tgz", + "integrity": "sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@img/sharp-darwin-arm64": { + "version": "0.35.3", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.35.3.tgz", + "integrity": "sha512-RMnFX7YQsMoh7lWfcM4NEHHymBX/rLuKNPVM84XE9ONPcaSCDgE7CHIHpSgPcO2xcRthgBy1HfNO319mwhIAkg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-arm64": "1.3.2" + } + }, + "node_modules/@img/sharp-darwin-x64": { + "version": "0.35.3", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.35.3.tgz", + "integrity": "sha512-Xo+5uFBtLN0BKqieTxiFzFPQAUlBbbH5iBKyRX/z1JrbnYsHTfKJnUfL8+p2TPXr1pXqao4eeL4Rl144uDpK9w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-x64": "1.3.2" + } + }, + "node_modules/@img/sharp-freebsd-wasm32": { + "version": "0.35.3", + "resolved": "https://registry.npmjs.org/@img/sharp-freebsd-wasm32/-/sharp-freebsd-wasm32-0.35.3.tgz", + "integrity": "sha512-lUxcqWIj2wMQ9BrwNjngcr1gWUr5xgaGThBRqPPalIC2n67Cqj1uPh8NnA/ZhAg8hUbKl+kVHKwgUIwe6ZYPrg==", + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "dependencies": { + "@img/sharp-wasm32": "0.35.3" + }, + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-darwin-arm64": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.3.2.tgz", + "integrity": "sha512-9J6ypZFpQBj4YnePGoq/S38w6nz+vqg5WZLrLGY4YuSemdMq47GMLBPO42MzwdGwpg/agZ7xzZcFHa48xlywfg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-darwin-x64": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.3.2.tgz", + "integrity": "sha512-m2pW1n6cns9VaubNwsZ+c3CRYjxNQWgJ5gPlnL1nbBcpkBvFm6SCFN5o0psFHI8w9n11NKhFkeEDns98tiqbEw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.3.2.tgz", + "integrity": "sha512-1eMLzy92I4J6rmi4mAT8yC3HxOtniyGELlzGbNMLLeqe052ahFQ0h6LFq+lh5DsDIdYViIDst08abvSbcEdLXQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm64": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.3.2.tgz", + "integrity": "sha512-dqVSFynCox4C/J8kT16V7SIFAns0IjgLwkvYT7p8LQVmJ5OS5b6tI9IGflxTeuBS//zXeFIUbwt5dwxyZ17cnA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-ppc64": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.3.2.tgz", + "integrity": "sha512-3z0NHDxD6n5I9gc05U1eW1AyRm+Gznzq3naMrthPNqE6oYykcogW0l/jfpJdjYnuNl8R7yI9pNbE1XiUeyq0Aw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-riscv64": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-riscv64/-/sharp-libvips-linux-riscv64-1.3.2.tgz", + "integrity": "sha512-bsb4rI+NldGOsXuej2r8OdSS8+zXDVaCWxyWrcv6kneTOlgAHtZABRzBBCwdsPiD90J4myNJuHpg6kA20ImW/w==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-s390x": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.3.2.tgz", + "integrity": "sha512-/ABshyj8gCpyIrNXnHn4LorDJ0HHm1VhXPBlxZ8zAtfVPAaSafXPGn+sUSIRiwaSBy0mmFjSjiXI5mkcwdChKQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-x64": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.3.2.tgz", + "integrity": "sha512-ITPEtgffGJ0S6G9dRyw/366tJQqFRcHWPHhC+Stpg3Z8AEMrDrTr2lhdz4f/Y/HMbRh//7Z5mBzEpVdi62Oc3w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-arm64": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.3.2.tgz", + "integrity": "sha512-zE9EdiUzUmg5mDT5a1rk5fYJ6GWPloTwWBYDS14naqHsL+EaMpDj1AWnpLgh3u0YCORv2Tt50wrcrpYqkP97Kw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-x64": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.3.2.tgz", + "integrity": "sha512-m0lrLiUt+lBYnCFr8qV/65yMR4E/c7/wf78I5eKTdkEakFAlZ9QlzEM3QIhhAwVeUhLAHLcCq7a7Vszq/oFNZQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-linux-arm": { + "version": "0.35.3", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.35.3.tgz", + "integrity": "sha512-affVWCTLooy8TSxbDx2qkzuDeaWLNVBA+P//FNBirHsXpP2fuBhk5AuboYUnrDnzoXes8GFjpTx0SBFOCRg+FA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm": "1.3.2" + } + }, + "node_modules/@img/sharp-linux-arm64": { + "version": "0.35.3", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.35.3.tgz", + "integrity": "sha512-QgKDspHPnrU+GQ55XPhGwyhC8acLVOOSyAvo1oVfFmrIXLkDNmGWzAfDZ4xK8oSA1qBQrALcHX0G5UZni/SuFQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm64": "1.3.2" + } + }, + "node_modules/@img/sharp-linux-ppc64": { + "version": "0.35.3", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.35.3.tgz", + "integrity": "sha512-sMd8rDxmpLOwv/7N44klFjOD5DUO7FLdjiXDI0hoxYaf7Ar262dQIEkosE98bps+5HPLtp/EvNqeqQtOycP/IA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-ppc64": "1.3.2" + } + }, + "node_modules/@img/sharp-linux-riscv64": { + "version": "0.35.3", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-riscv64/-/sharp-linux-riscv64-0.35.3.tgz", + "integrity": "sha512-0Eob78yjlYPfL5vMNWAW55l3R9Y6BQS/gOfe0ZcP9mEz9ohhKSt4im1hayiknXgf8AWrFqMvJcKIdmLmEe7yeQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-riscv64": "1.3.2" + } + }, + "node_modules/@img/sharp-linux-s390x": { + "version": "0.35.3", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.35.3.tgz", + "integrity": "sha512-KgAxQ0DxpNOq1rG2t5cgTgShJFGSuU7XO45cqC+1NVOuZnP6tlgZRuSYOfNupGkHID0o3cJOsw4DVeJpMovcGw==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-s390x": "1.3.2" + } + }, + "node_modules/@img/sharp-linux-x64": { + "version": "0.35.3", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.35.3.tgz", + "integrity": "sha512-8pqvxubL2PGdhlPy6GLqzDYMUjyRmKAwKHYKixpdJYBUK7PJ0C029XdsnpFIdgRZG68fZiGdHVWcKPvtiPB4cA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-x64": "1.3.2" + } + }, + "node_modules/@img/sharp-linuxmusl-arm64": { + "version": "0.35.3", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.35.3.tgz", + "integrity": "sha512-Vz0iQjzzcSX3HCbfwFfCSG/9SCIqyO0mH2sXyiHaAYfBk0cRsCWXRyQYX0ovCK/PAQBbTzQ0dsPQHh5MAFL59w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-arm64": "1.3.2" + } + }, + "node_modules/@img/sharp-linuxmusl-x64": { + "version": "0.35.3", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.35.3.tgz", + "integrity": "sha512-6O1NPKcDVj9QEdg7Hx549EX8U0rp6yXQERqru6yRN7fGBn32UvIRJUlWnk+8xDCiG76hXVBbX82NZ/ZKr0euIg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-x64": "1.3.2" + } + }, + "node_modules/@img/sharp-wasm32": { + "version": "0.35.3", + "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.35.3.tgz", + "integrity": "sha512-cZ0XkcYGpHZkqW6iCkqTcmUC0CD9DhD5d/qeZlZkfRBn6GnHniZXLUo5+9xw8Iv76YE6LQFN9YNBlKREcCG76w==", + "dev": true, + "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", + "optional": true, + "dependencies": { + "@emnapi/runtime": "^1.11.1" + }, + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-webcontainers-wasm32": { + "version": "0.35.3", + "resolved": "https://registry.npmjs.org/@img/sharp-webcontainers-wasm32/-/sharp-webcontainers-wasm32-0.35.3.tgz", + "integrity": "sha512-2rnq7bX3NzeR2T4YWgz8qiG4h3TSdMe+vN1iQXpJleSJ3SM5zQ8Fy2SyyXAWlbxpEZ2Y+Z4u1BePgJEYbSy80Q==", + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@img/sharp-wasm32": "0.35.3" + }, + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-arm64": { + "version": "0.35.3", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.35.3.tgz", + "integrity": "sha512-4bPwFdMbeC4JQ8L8LOyWp6nsHcboP5fxkp6iPOXz2Vg49R42TuMs2whkJ5OAP4/Ul035qOzy0AecOF9VOscn4w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-ia32": { + "version": "0.35.3", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.35.3.tgz", + "integrity": "sha512-r53mXsBN6lFUDiST764SvgwUdHAqM4rPAiDzAmf4fLoB6X/rkfyTrLCg6+g17wJJiCmB3JYgHuUldCWUIRFSXw==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-x64": { + "version": "0.35.3", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.35.3.tgz", + "integrity": "sha512-D4y1vNeZrIIJCN+uHaWVtH86B+aCrdMYYjicy9pXHvbGZeGYLLSd3wdVuC37FxVXlU1ARsk84eKWfWMXGYEqvA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, "node_modules/@jridgewell/gen-mapping": { "version": "0.3.13", "dev": true, @@ -3046,6 +3585,69 @@ "version": "1.2.0", "license": "ISC" }, + "node_modules/sharp": { + "version": "0.35.3", + "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.35.3.tgz", + "integrity": "sha512-ej0zVHuZGHCiABXcNxeYhpRnPNPAcvbG8RMdBAhDAxLKkCRVSpK3Iyu7qbqw3JMzoj0REeM6f3tJLtVwl0023Q==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@img/colour": "^1.1.0", + "detect-libc": "^2.1.2", + "semver": "^7.8.5" + }, + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-darwin-arm64": "0.35.3", + "@img/sharp-darwin-x64": "0.35.3", + "@img/sharp-freebsd-wasm32": "0.35.3", + "@img/sharp-libvips-darwin-arm64": "1.3.2", + "@img/sharp-libvips-darwin-x64": "1.3.2", + "@img/sharp-libvips-linux-arm": "1.3.2", + "@img/sharp-libvips-linux-arm64": "1.3.2", + "@img/sharp-libvips-linux-ppc64": "1.3.2", + "@img/sharp-libvips-linux-riscv64": "1.3.2", + "@img/sharp-libvips-linux-s390x": "1.3.2", + "@img/sharp-libvips-linux-x64": "1.3.2", + "@img/sharp-libvips-linuxmusl-arm64": "1.3.2", + "@img/sharp-libvips-linuxmusl-x64": "1.3.2", + "@img/sharp-linux-arm": "0.35.3", + "@img/sharp-linux-arm64": "0.35.3", + "@img/sharp-linux-ppc64": "0.35.3", + "@img/sharp-linux-riscv64": "0.35.3", + "@img/sharp-linux-s390x": "0.35.3", + "@img/sharp-linux-x64": "0.35.3", + "@img/sharp-linuxmusl-arm64": "0.35.3", + "@img/sharp-linuxmusl-x64": "0.35.3", + "@img/sharp-webcontainers-wasm32": "0.35.3", + "@img/sharp-win32-arm64": "0.35.3", + "@img/sharp-win32-ia32": "0.35.3", + "@img/sharp-win32-x64": "0.35.3" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/sharp/node_modules/semver": { + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/side-channel": { "version": "1.1.1", "license": "MIT",