From 8c6e7930db54383f46bf7ccd8c45473e7d88c97e Mon Sep 17 00:00:00 2001 From: 3dtours Date: Thu, 17 Sep 2026 17:43:03 +0700 Subject: [PATCH] Add self-contained docker/ stack for the web UI MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `docker/` now holds the whole web build — frontend (Vite + React + CanvasKit), backend (Fastify + SQLite) and the compose file — so the folder can be moved to another machine and run without the React Native project: cd docker && cp .env.example .env && docker compose up -d --build Only `${WEB_PORT:-8090}` is published; nginx serves the SPA and proxies /api to the `api` container over Docker's DNS. Photos never reach the server. The shared render code is vendored into `docker/frontend/shared/` and aliased to a CanvasKit shim, so the app's own frameUtils/toneShader/jpegDpi run unchanged. Fix the all-black render on GPU surfaces: `MakeWebGLCanvasSurface` creates a separate WebGL context per call, and a texture from one context cannot be sampled by a surface on another — so any pass that drew a snapshot onto a second surface (output sharpen, screen sharpen, polaroid/wallframe cards) came out solid black, while the raster fallback was correct. Use one shared GrDirectContext + MakeRenderTarget instead. Verified in headless Chromium against the running stack: 12MP JPEG in, preview mean=120.5 sd=60.5, export 2048x1536 mean=107.2 sd=62.1, JFIF density 300/300, EXIF present, no console errors; health/signup/login/me/recipes all 2xx through the nginx proxy. --- WEB_PLAN.md | 7 +- docker/.env.example | 3 + docker/.gitignore | 2 + docker/README.md | 80 + docker/backend/.dockerignore | 6 + docker/backend/.gitignore | 2 + docker/backend/Dockerfile | 20 + docker/backend/package-lock.json | 1653 ++++++++++++++ docker/backend/package.json | 22 + docker/backend/src/db.ts | 158 ++ docker/backend/src/server.ts | 191 ++ docker/backend/tsconfig.json | 18 + docker/docker-compose.yml | 26 + docker/frontend/.dockerignore | 5 + docker/frontend/.gitignore | 3 + docker/frontend/Dockerfile | 14 + docker/frontend/index.html | 15 + docker/frontend/nginx.conf | 40 + docker/frontend/package-lock.json | 1908 +++++++++++++++++ docker/frontend/package.json | 29 + .../public/assets/Cousine-Regular.ttf | Bin 0 -> 296856 bytes .../frontend/public/assets/NotoEmoji-GPS.ttf | Bin 0 -> 2564 bytes docker/frontend/public/assets/wallframe.png | Bin 0 -> 261257 bytes docker/frontend/scripts/copy-wasm.mjs | 16 + docker/frontend/shared/types/index.ts | 142 ++ docker/frontend/shared/utils/cinemaShader.ts | 138 ++ docker/frontend/shared/utils/colorUtils.ts | 313 +++ .../frontend/shared/utils/defaultRecipes.ts | 303 +++ docker/frontend/shared/utils/exifWrite.ts | 475 ++++ docker/frontend/shared/utils/frameUtils.ts | 204 ++ docker/frontend/shared/utils/jpegDpi.ts | 126 ++ docker/frontend/shared/utils/paramDefs.ts | 202 ++ docker/frontend/shared/utils/skiaImage.ts | 89 + docker/frontend/shared/utils/toneShader.ts | 254 +++ docker/frontend/src/App.tsx | 547 +++++ docker/frontend/src/Landing.tsx | 84 + docker/frontend/src/api.ts | 45 + docker/frontend/src/config/tiers.ts | 19 + docker/frontend/src/engine/assets.ts | 33 + docker/frontend/src/engine/exportEngine.ts | 690 ++++++ docker/frontend/src/engine/imageOps.ts | 58 + docker/frontend/src/engine/skiaShim.ts | 213 ++ docker/frontend/src/i18n/I18nProvider.tsx | 64 + docker/frontend/src/i18n/en.ts | 124 ++ docker/frontend/src/i18n/vi.ts | 131 ++ docker/frontend/src/main.tsx | 21 + docker/frontend/src/styles/app.css | 208 ++ docker/frontend/src/styles/tokens.css | 105 + docker/frontend/src/theme/ThemeProvider.tsx | 37 + docker/frontend/src/theme/themes.ts | 41 + docker/frontend/src/ui/AuthModal.tsx | 71 + docker/frontend/src/ui/ChipColumn.tsx | 78 + docker/frontend/src/ui/ImageStage.tsx | 120 ++ docker/frontend/src/ui/ToolRail.tsx | 34 + docker/frontend/src/ui/TopBar.tsx | 128 ++ docker/frontend/tsconfig.json | 21 + docker/frontend/vite.config.ts | 25 + 57 files changed, 9359 insertions(+), 2 deletions(-) create mode 100644 docker/.env.example create mode 100644 docker/.gitignore create mode 100644 docker/README.md create mode 100644 docker/backend/.dockerignore create mode 100644 docker/backend/.gitignore create mode 100644 docker/backend/Dockerfile create mode 100644 docker/backend/package-lock.json create mode 100644 docker/backend/package.json create mode 100644 docker/backend/src/db.ts create mode 100644 docker/backend/src/server.ts create mode 100644 docker/backend/tsconfig.json create mode 100644 docker/docker-compose.yml create mode 100644 docker/frontend/.dockerignore create mode 100644 docker/frontend/.gitignore create mode 100644 docker/frontend/Dockerfile create mode 100644 docker/frontend/index.html create mode 100644 docker/frontend/nginx.conf create mode 100644 docker/frontend/package-lock.json create mode 100644 docker/frontend/package.json create mode 100644 docker/frontend/public/assets/Cousine-Regular.ttf create mode 100644 docker/frontend/public/assets/NotoEmoji-GPS.ttf create mode 100644 docker/frontend/public/assets/wallframe.png create mode 100644 docker/frontend/scripts/copy-wasm.mjs create mode 100644 docker/frontend/shared/types/index.ts create mode 100644 docker/frontend/shared/utils/cinemaShader.ts create mode 100644 docker/frontend/shared/utils/colorUtils.ts create mode 100644 docker/frontend/shared/utils/defaultRecipes.ts create mode 100644 docker/frontend/shared/utils/exifWrite.ts create mode 100644 docker/frontend/shared/utils/frameUtils.ts create mode 100644 docker/frontend/shared/utils/jpegDpi.ts create mode 100644 docker/frontend/shared/utils/paramDefs.ts create mode 100644 docker/frontend/shared/utils/skiaImage.ts create mode 100644 docker/frontend/shared/utils/toneShader.ts create mode 100644 docker/frontend/src/App.tsx create mode 100644 docker/frontend/src/Landing.tsx create mode 100644 docker/frontend/src/api.ts create mode 100644 docker/frontend/src/config/tiers.ts create mode 100644 docker/frontend/src/engine/assets.ts create mode 100644 docker/frontend/src/engine/exportEngine.ts create mode 100644 docker/frontend/src/engine/imageOps.ts create mode 100644 docker/frontend/src/engine/skiaShim.ts create mode 100644 docker/frontend/src/i18n/I18nProvider.tsx create mode 100644 docker/frontend/src/i18n/en.ts create mode 100644 docker/frontend/src/i18n/vi.ts create mode 100644 docker/frontend/src/main.tsx create mode 100644 docker/frontend/src/styles/app.css create mode 100644 docker/frontend/src/styles/tokens.css create mode 100644 docker/frontend/src/theme/ThemeProvider.tsx create mode 100644 docker/frontend/src/theme/themes.ts create mode 100644 docker/frontend/src/ui/AuthModal.tsx create mode 100644 docker/frontend/src/ui/ChipColumn.tsx create mode 100644 docker/frontend/src/ui/ImageStage.tsx create mode 100644 docker/frontend/src/ui/ToolRail.tsx create mode 100644 docker/frontend/src/ui/TopBar.tsx create mode 100644 docker/frontend/tsconfig.json create mode 100644 docker/frontend/vite.config.ts diff --git a/WEB_PLAN.md b/WEB_PLAN.md index 11bca15..064bc1e 100644 --- a/WEB_PLAN.md +++ b/WEB_PLAN.md @@ -212,13 +212,15 @@ Chạy cùng bộ ảnh trên app (native/Skia) và web; so diff + mắt; tối Exit: diff trong ngưỡng đã chấp nhận ở P6; render 12MP web < ~3s. ### W9 — Docker + deploy + docs -`docker compose up -d --build`; kiểm tra qua `IP:8090`; hướng dẫn gắn Nginx Proxy Manager (domain, TLS, `X-Forwarded-*`); `web/README.md` (RUNBOOK: port, domain, backup `./data`, đổi `TIER` config); cập nhật `README.md` gốc; commit + push. +`docker compose up -d --build`; kiểm tra qua `IP:8090`; hướng dẫn gắn Nginx Proxy Manager (domain, TLS, `X-Forwarded-*`); `docker/README.md` (RUNBOOK: port, domain, backup `./data`, đổi `TIER` config); commit + push. Exit: mở webUI từ máy khác trong LAN; đăng ký/đăng nhập/demo/export chạy; container tự lên lại sau reboot. +**Trạng thái**: `docker/` đã tự chứa và chạy được — `docker/docker-compose.yml` (service `frontend` + `api`), `docker/backend/` (Fastify + SQLite), `docker/frontend/` (Vite + CanvasKit + nginx). Chạy `docker compose up -d --build` trong `docker/`, UI ở `:8090`; đã kiểm chứng health/signup/login/recipes và export ảnh thật end-to-end trong Chromium headless. Còn lại: gắn domain/TLS qua Nginx Proxy Manager, cập nhật `README.md` gốc. + ## Rủi ro - ~~**CanvasKit không có emoji màu**~~ → **đã loại ở W0**: `NotoEmoji-GPS.ttf` cung cấp đủ 📍/📷, không cần fallback Canvas2D. - ~~**Skia API shim** (`MakeMatrixConvolution` khác chữ ký)~~ → **đã loại ở W0**: không tồn tại trong CanvasKit, thay bằng `CLARITY_SKSL`. -- **Hiệu năng**: raster CPU chậm gấp ~1000 lần WebGL → engine bắt buộc dùng `MakeWebGLCanvasSurface`; máy không có WebGL (hoặc WebGL bị block) phải báo lỗi rõ thay vì âm thầm chạy CPU. +- ~~**Hiệu năng**: raster CPU chậm gấp ~1000 lần WebGL~~ → **đã chốt**: engine dùng GPU surface, nhưng **một `GrDirectContext` dùng chung** + `MakeRenderTarget` cho mọi surface. `MakeWebGLCanvasSurface` gọi nhiều lần tạo nhiều WebGL context riêng → snapshot của surface này vẽ sang surface kia ra **đen thui** (đo được: 12MP JPEG + output sharpen, GL mean 0.0 vs raster 107.0). Không có WebGL thì fallback raster `MakeSurface` — đúng pipeline, chỉ chậm hơn. - **wasm ~8MB** (build `full`, gzip ~3MB): lazy-load, nginx cache dài hạn. - **RAM 12MP** trong browser (bitmap ~48MB + surface) — giới hạn worker pool 1–2, giải phóng bitmap sau export; batch render tuần tự, không song song. - **HDF/vignette** dùng gradient + blend — kiểm tra tương đương CanvasKit. @@ -243,4 +245,5 @@ Exit: mở webUI từ máy khác trong LAN; đăng ký/đăng nhập/demo/export - [ ] W6 landing page - [ ] W7 batch export - [ ] W8 parity & perf +- [x] `docker/` tự chứa + chạy được (code FE/BE nằm trong `docker/`, compose + Dockerfile + `.env.example` + README) — đã verify end-to-end - [ ] W9 Docker + deploy + docs diff --git a/docker/.env.example b/docker/.env.example new file mode 100644 index 0000000..ae97f74 --- /dev/null +++ b/docker/.env.example @@ -0,0 +1,3 @@ +# Host port the web UI is published on. Everything else is internal: the API is +# only reachable through nginx at /api/. +WEB_PORT=8090 diff --git a/docker/.gitignore b/docker/.gitignore new file mode 100644 index 0000000..24f9f8d --- /dev/null +++ b/docker/.gitignore @@ -0,0 +1,2 @@ +.env +data/ diff --git a/docker/README.md b/docker/README.md new file mode 100644 index 0000000..bf98003 --- /dev/null +++ b/docker/README.md @@ -0,0 +1,80 @@ +# RecipesCam web — self-contained stack + +A Docker-hosted web build of RecipesCam. Everything it needs is in this folder: +move it to another machine, run two commands, and the app is up. It does **not** +need the React Native project around it. + +```bash +cp .env.example .env +docker compose up -d --build +# → http://localhost:8090 +``` + +## What runs where + +| Service | Image | Role | +| --- | --- | --- | +| `frontend` | `nginx:1.27-alpine` (built by `frontend/Dockerfile`) | Static SPA + `/api/` reverse proxy | +| `api` | `node:22-slim` (built by `backend/Dockerfile`) | Accounts + saved recipes, SQLite on `./data` | + +`frontend` resolves `api` through Docker's embedded DNS and proxies `/api/*` to +it — that is why the API container is named `api` and why it is not published on +the host. Only `${WEB_PORT:-8090}` is exposed. + +Photos never leave the browser. The CanvasKit render pipeline (grade, frame, +watermarks, JPEG encode) runs in the visitor's tab; the API only stores recipes +as JSON. + +## Layout + +``` +docker-compose.yml the stack +.env.example WEB_PORT +data/ SQLite (created on first run, gitignored) +backend/ Fastify + better-sqlite3 API, own Dockerfile +frontend/ Vite + React + CanvasKit SPA, own Dockerfile + nginx.conf + shared/ vendored copies of the app's types + utils (see below) + src/engine/ skiaShim.ts (CanvasKit) + exportEngine.ts (render pipeline) +``` + +### Vendored files + +`frontend/shared/{types/index.ts,utils/*.ts}` are byte-identical copies of +`src/types/index.ts` and ten `src/utils/*.ts` files from the React Native +project (`@shopify/react-native-skia` is aliased to `src/engine/skiaShim.ts` in +`vite.config.ts` + `tsconfig.json`, so those files compile unchanged): + +`cinemaShader` `colorUtils` `defaultRecipes` `exifWrite` `frameUtils` `jpegDpi` +`paramDefs` `skiaImage` `toneShader`. + +When the app changes one of them, copy it back in — the renderer is only +"parity" for as long as these stay in sync: + +```bash +cd /docker/frontend/shared/utils +cp /src/utils/.ts . +``` + +## Operations + +```bash +docker compose logs -f api # API log +docker compose restart api # after backend/src changes (rebuild: --build) +docker compose down # stop; ./data survives +``` + +Backup is the `./data` folder — that is the whole database. + +## Checks + +```bash +curl -s http://localhost:8090/api/health # {"ok":true} +curl -sI http://localhost:8090/ # 200, index.html +``` + +Then open the UI, drop a photo in, and confirm the preview shows the picture and +EXPORT downloads a JPEG that opens. The preview going solid black while the +export still reports a plausible size is the one failure mode worth knowing: it +means the CanvasKit GPU surfaces lost their shared `GrDirectContext` (see +`frontend/src/engine/skiaShim.ts`), and with no GPU the raster fallback renders +the same pipeline correctly, just slower. diff --git a/docker/backend/.dockerignore b/docker/backend/.dockerignore new file mode 100644 index 0000000..673ce8b --- /dev/null +++ b/docker/backend/.dockerignore @@ -0,0 +1,6 @@ +node_modules +dist +data +*.log +Dockerfile +.dockerignore diff --git a/docker/backend/.gitignore b/docker/backend/.gitignore new file mode 100644 index 0000000..b947077 --- /dev/null +++ b/docker/backend/.gitignore @@ -0,0 +1,2 @@ +node_modules/ +dist/ diff --git a/docker/backend/Dockerfile b/docker/backend/Dockerfile new file mode 100644 index 0000000..5d46c6e --- /dev/null +++ b/docker/backend/Dockerfile @@ -0,0 +1,20 @@ +# RecipesCam API — self-contained build (context = this folder). +# Builder compiles TypeScript; runtime carries production deps only. +FROM node:22-slim AS builder +WORKDIR /app +COPY package.json package-lock.json ./ +RUN npm ci +COPY tsconfig.json ./ +COPY src ./src +RUN npm run build && npm prune --omit=dev + +FROM node:22-slim +ENV NODE_ENV=production \ + PORT=3000 \ + DATA_DIR=/data +WORKDIR /app +COPY --from=builder /app/node_modules ./node_modules +COPY --from=builder /app/dist ./dist +COPY package.json ./ +EXPOSE 3000 +CMD ["node", "dist/server.js"] diff --git a/docker/backend/package-lock.json b/docker/backend/package-lock.json new file mode 100644 index 0000000..21fdcfe --- /dev/null +++ b/docker/backend/package-lock.json @@ -0,0 +1,1653 @@ +{ + "name": "recipescam-api", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "recipescam-api", + "version": "1.0.0", + "dependencies": { + "better-sqlite3": "^12.11.1", + "fastify": "^5.12.5" + }, + "devDependencies": { + "@types/better-sqlite3": "^9.6.0", + "@types/node": "^22.20.3", + "tsx": "^4.23.13", + "typescript": "^5.9.3" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.2.tgz", + "integrity": "sha512-XExcO+dvLKvVtNTibSTBej1NCAbaGhWn9Ww1ZPx80qsahhPFe/8jgWP0IchNe0F3HwkU7n8ejhH8bjonqht8mQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.2.tgz", + "integrity": "sha512-kXXoiPVVGQcnIYGOeaovwOURpniDBpSq4A03qkQ+BMQqtGG6HYap3xne9C1O1yo4TR3qxlCX5IqqmX6fFo2Lqg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.2.tgz", + "integrity": "sha512-5YfKeeI8qWfBZIX+u2xZC3Zlb3Os/gLS2sbEKM+I4ZOcsWmHS2WLysCcQZDAFRslDUU5Oiq44gf6PYN1vGwG5A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.2.tgz", + "integrity": "sha512-O387ite7SzUyCcy3JQX4P4bLtEA7bLLkx+esve5JHnyYfNTxcVpXZo9jhdB0lTKN44gztELTdU7nS8Nr16Fs1Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.2.tgz", + "integrity": "sha512-n4KqkOQrraxHJcgjM1RvwbigfQKIKJVpM7xp+KsxiyUSrRdIXnt73VhrPAx0fV44hgfmIVKjxMN9J1t5jySVkw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.2.tgz", + "integrity": "sha512-uq6suIWYP37qzGddBKPw5QEQPi6HiLGsO7UmkpfyaYNQ3D+rN6w6WfwH+nuqcGXWvawGwxOEroO4YGnFh95azw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.2.tgz", + "integrity": "sha512-n+I0BTSRIoy+d6RPKnEVwql5UwBJolytvY4mAOIEJorKlqgPII8ix6slVVrfZ5Tnj7glIZvloylbB/EJPMWEXw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.2.tgz", + "integrity": "sha512-78XJTJkvPs0kz2w61301PJjXl4g7q3JqiYMZ/M/yVI73EHBrCRTgkhu9oqG7vPqq+a/yadEW8aD+agKlk5xrmg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.2.tgz", + "integrity": "sha512-XlDnu2q5yoqems+xay6wSAcg9DDD7K9RLKZEBOMZm3ckNpJBvOX20tSfby8KfrrhINDyv9V2YVZKY/SpoGJI8w==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.2.tgz", + "integrity": "sha512-pW4AC0P3it8c7do9MVM4p51FzHzdM/TZrerurgRcHJ2WTa1VQ1CIq18xncfpBJw4ojkiZZrKW2yIBWBP92j6Ug==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.2.tgz", + "integrity": "sha512-CYbnj78HsIeA+DhgUKgFCfvNsTHFhMMrinUrMZpDXJXKN8T3XViTZ/+wtHeVxEWY8ewSzTFN+nRmSwO2tZaLUQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.2.tgz", + "integrity": "sha512-buwkd8nsph4R+ajRvw0qM5Hja/TXQow3ptzWO2EbG/cqcIkHloRrdlBtQlshyYGTNFvfkfJ5tpPLVkY4DtsPfQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.2.tgz", + "integrity": "sha512-ZVykbDyk7519VwiNb9Lcj9m8XM6v5V9uKPvrEMkkEedVewf+0itkhahp4HDpgERXhwLRpWFypsGbG/J8s0QjJA==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.2.tgz", + "integrity": "sha512-CAXl+Dtd9UUuJd8pKKdwh6MLm3MUMiqMPmhZ3tTSXPqfyQ3vDl6R5hZdZ/kYojK4ofXtdfSv1tFq8XzWx3heNQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.2.tgz", + "integrity": "sha512-GeXCej4IQtU1B+QlDV8W/RRvbzI3O/Stss+/bCXv4lZls5WGRtu2a+3JkA3i4qIUlMXpcHebWpF8AkJhATowuA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.2.tgz", + "integrity": "sha512-3H1weTYZPxt/WOhByszQZybS9w5lKzUn1FDMsgEChbHWQwHYQQRfBxgCcZvPhjHfKyJjIievvMmEUawJrdY9Dg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.2.tgz", + "integrity": "sha512-4xTZr1FUmSoQW4XIWmit3tzQrUTZM+N3P0XV8xROKYF50XfI7xeO90+1bZvNwxIufQ9hDQVRJH5YhgPVF8A/HQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.2.tgz", + "integrity": "sha512-sSATRjPeDBg3pdgHoQfoYBob11Kk1FGa9lui5RIHZCoCkJa9QKlvl3/vKz2usCmYYjs7ymJR/2Nnsqe+Hjt5nw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.2.tgz", + "integrity": "sha512-lqnzCV+mM0gIADaKihiCg6ifgfU2L3h5E33rNQBN1Y4MaVGnzryzmvvf7UHxprpQdE8hpqLolJ9Rl+SkIRDpyw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.2.tgz", + "integrity": "sha512-AL2qJILH7lNjrDmCQDvdxMfAUIv8KMNZOvrwAQ8i8//ntL9FflhOyMJ8OZSMBb8/AWXe3/5v5S20y3zCoZWKoQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.2.tgz", + "integrity": "sha512-QtiuPytchRyC4rwUKhexJdQKvDuZ6hWloi3igqPQNUJCS1/v9EiO3UTOXR6A3FoMo4fnAKbWJdqaIwhOzh8qEw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.2.tgz", + "integrity": "sha512-WkhYDmpTjLvGlScA1rwjRUmhl4k8oXR3cIbtqWmELgU/dFeHHlEllxDvdWcNJV9rbzCexB5vz8gtNewWLgCT7Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.2.tgz", + "integrity": "sha512-GPMSkTOtMnv2U2F8gxe4Io6qmVs+YKyp832Etqqxr0hFngmXQ3rzwytelm3GIn7T4VviRUlf3sOgBOiTdvaf7g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.2.tgz", + "integrity": "sha512-PIhhEkE9uPBleRBrQEJpUn7MBnibZzbGzYWPmY3x+YoVg/95zbjB4CxPPOQ8l5tYYM4mMaCthF8/1DIfBQQyWQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.2.tgz", + "integrity": "sha512-YmJbfTlvU7Sdn9BB+4PRES4oB6pxgS37MAONj+hBr/cpXS1aBPKXxNnDbu+QCWPj0o9dgyxeq79g6c5P8KeuYA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.2.tgz", + "integrity": "sha512-5ebpxr3nWMzrL/rnUI755Jkuee0bHL/Gq0WTF9lvcpv73wAp5eu8MfBUgWK9bhWvZjj7yX8etf/8tI8Ney695g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@fastify/ajv-compiler": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/@fastify/ajv-compiler/-/ajv-compiler-4.0.6.tgz", + "integrity": "sha512-NtuzM0SfaMJbGlnjr9LWQUN5LzgSrbB8tf/wRZNas+4E1O/Nmzl53e7ruT61HDZyRCJGC6FxIogmNZO1c5ETBA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT", + "dependencies": { + "ajv": "^8.12.0", + "ajv-formats": "^3.0.1", + "fast-uri": "^4.0.0" + } + }, + "node_modules/@fastify/error": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@fastify/error/-/error-4.2.0.tgz", + "integrity": "sha512-RSo3sVDXfHskiBZKBPRgnQTtIqpi/7zhJOEmAxCiBcM7d0uwdGdxLlsCaLzGs8v8NnxIRlfG0N51p5yFaOentQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT" + }, + "node_modules/@fastify/fast-json-stringify-compiler": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@fastify/fast-json-stringify-compiler/-/fast-json-stringify-compiler-5.1.0.tgz", + "integrity": "sha512-PxcYtKLbQ8Z+yApiqjK8FwxIwvEj38k2OiLc17u8dkJSlmfi2wHHPaSnaoqBPQqtvF8YVsDgDpP2snDCfFrpfw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT", + "dependencies": { + "fast-json-stringify": "^7.0.0" + } + }, + "node_modules/@fastify/forwarded": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@fastify/forwarded/-/forwarded-3.0.2.tgz", + "integrity": "sha512-NE8HgKLgYejV9lDpqkEFaDKMLYelJBVfHekhB0UKvX0ghagXRJqg68feg8er1NPXxG4N9i6vPxzt8E+3wHfcmA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT" + }, + "node_modules/@fastify/merge-json-schemas": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/@fastify/merge-json-schemas/-/merge-json-schemas-0.2.1.tgz", + "integrity": "sha512-OA3KGBCy6KtIvLf8DINC5880o5iBlDX4SxzLQS8HorJAbqluzLRn80UXU0bxZn7UOFhFgpRJDasfwn9nG4FG4A==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT", + "dependencies": { + "dequal": "^2.0.3" + } + }, + "node_modules/@fastify/proxy-addr": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/@fastify/proxy-addr/-/proxy-addr-5.1.1.tgz", + "integrity": "sha512-zv07Y9GEuDsJPegZoDFd4SDWaZOW8N2pa0GSrYmKpId/tjt1Hgo3BjZBVjdVpfVrHaA+Qv5jawtS2O50J5xM9g==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT", + "dependencies": { + "@fastify/forwarded": "^3.0.0", + "ipaddr.js": "^2.1.0" + } + }, + "node_modules/@pinojs/redact": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@pinojs/redact/-/redact-0.4.0.tgz", + "integrity": "sha512-k2ENnmBugE/rzQfEcdWHcCY+/FM3VLzH9cYEsbdsoqrvzAKRhUZeRNhAZvB8OitQJ1TBed3yqWtdjzS6wJKBwg==", + "license": "MIT" + }, + "node_modules/@types/better-sqlite3": { + "version": "9.6.0", + "resolved": "https://registry.npmjs.org/@types/better-sqlite3/-/better-sqlite3-9.6.0.tgz", + "integrity": "sha512-ZEEwBSgMu7GYJOynoagg5X9JbxfL6dTJDsgViJIqh67jV44kyOr9RXfmFjLK5rzC4MWssP06t9hu/JwGDnUbCg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/node": { + "version": "22.20.3", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.20.3.tgz", + "integrity": "sha512-DZmzkmwHzXrLPAXPyKNDzlIwMMUZCVacoD25ywdy5YTKGbOx/2ld+Q38Im2zJ0vBuZP5Prd3VZutKZyXwkOS8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/abstract-logging": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/abstract-logging/-/abstract-logging-2.0.1.tgz", + "integrity": "sha512-2BjRTZxTPvheOvGbBslFSYOUkr+SjPtOnrLP33f+VIWLzezQpZcqVg7ja3L4dBXmzzgwT+a029jRx5PCi3JuiA==", + "license": "MIT" + }, + "node_modules/ajv": { + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", + "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-formats": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz", + "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==", + "license": "MIT", + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/ajv/node_modules/fast-uri": { + "version": "3.1.8", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.8.tgz", + "integrity": "sha512-GZMtZUTNRpOVIECoXwLNZS5xUGE+mVNbTB8h/7Rwh2TFWcBQiPzTgyZi05BF9UMZKkLJv8XBRJTlU7zg8+ZfMg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/atomic-sleep": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/atomic-sleep/-/atomic-sleep-1.0.0.tgz", + "integrity": "sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==", + "license": "MIT", + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/avvio": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/avvio/-/avvio-9.3.0.tgz", + "integrity": "sha512-g2tQ7LE7oOSqDfwEm3M+ZCMTJc7KiZCdJ4UwyZJb5ckTKyYu50OYmvv0mCFXPuYXoM4zkSt8zM9XQ9KCvxA74A==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT", + "dependencies": { + "@fastify/error": "^4.0.0", + "fastq": "^1.17.1" + } + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/better-sqlite3": { + "version": "12.11.1", + "resolved": "https://registry.npmjs.org/better-sqlite3/-/better-sqlite3-12.11.1.tgz", + "integrity": "sha512-dq9AtApgg5PGFtBzPFSBl3HZQjHok5gaQCM6zh2Yk0aSmDCs1CbnVI8/HgASQkNKsWFpseIO9beg5xxpYhbIfA==", + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "bindings": "^1.5.0", + "prebuild-install": "^7.1.1" + }, + "engines": { + "node": "20.x || 22.x || 23.x || 24.x || 25.x || 26.x" + } + }, + "node_modules/bindings": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", + "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", + "license": "MIT", + "dependencies": { + "file-uri-to-path": "1.0.0" + } + }, + "node_modules/bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "license": "MIT", + "dependencies": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "node_modules/chownr": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", + "license": "ISC" + }, + "node_modules/cookie": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.1.1.tgz", + "integrity": "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/decompress-response": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", + "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", + "license": "MIT", + "dependencies": { + "mimic-response": "^3.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", + "license": "MIT", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/end-of-stream": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", + "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", + "license": "MIT", + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/esbuild": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.2.tgz", + "integrity": "sha512-HKVLS8dvII+xoKW9kmqxbRKrnWEXfJJr/FZhhJmiqIB0e053QNYFqOBouTMO/k5sID4MvCiUCvv8b9M4h32wIA==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.28.2", + "@esbuild/android-arm": "0.28.2", + "@esbuild/android-arm64": "0.28.2", + "@esbuild/android-x64": "0.28.2", + "@esbuild/darwin-arm64": "0.28.2", + "@esbuild/darwin-x64": "0.28.2", + "@esbuild/freebsd-arm64": "0.28.2", + "@esbuild/freebsd-x64": "0.28.2", + "@esbuild/linux-arm": "0.28.2", + "@esbuild/linux-arm64": "0.28.2", + "@esbuild/linux-ia32": "0.28.2", + "@esbuild/linux-loong64": "0.28.2", + "@esbuild/linux-mips64el": "0.28.2", + "@esbuild/linux-ppc64": "0.28.2", + "@esbuild/linux-riscv64": "0.28.2", + "@esbuild/linux-s390x": "0.28.2", + "@esbuild/linux-x64": "0.28.2", + "@esbuild/netbsd-arm64": "0.28.2", + "@esbuild/netbsd-x64": "0.28.2", + "@esbuild/openbsd-arm64": "0.28.2", + "@esbuild/openbsd-x64": "0.28.2", + "@esbuild/openharmony-arm64": "0.28.2", + "@esbuild/sunos-x64": "0.28.2", + "@esbuild/win32-arm64": "0.28.2", + "@esbuild/win32-ia32": "0.28.2", + "@esbuild/win32-x64": "0.28.2" + } + }, + "node_modules/expand-template": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz", + "integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==", + "license": "(MIT OR WTFPL)", + "engines": { + "node": ">=6" + } + }, + "node_modules/fast-decode-uri-component": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/fast-decode-uri-component/-/fast-decode-uri-component-1.0.1.tgz", + "integrity": "sha512-WKgKWg5eUxvRZGwW8FvfbaH7AXSh2cL+3j5fMGzUMCxWBJ3dV3a7Wz8y2f/uQ0e3B6WmodD3oS54jTQ9HVTIIg==", + "license": "MIT" + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "license": "MIT" + }, + "node_modules/fast-json-stringify": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fast-json-stringify/-/fast-json-stringify-7.0.1.tgz", + "integrity": "sha512-eRSayARSbbwlBjpP4vnTTIRD5QPcIrmihPxDeN1DtKnHPg66UuJLx+8hlK1kaFdjvzyQ/dzALoi4vwAQ+T+iZA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT", + "dependencies": { + "@fastify/merge-json-schemas": "^0.2.0", + "ajv": "^8.12.0", + "ajv-formats": "^3.0.1", + "fast-uri": "^4.0.0", + "json-schema-ref-resolver": "^3.0.0", + "rfdc": "^1.2.0" + } + }, + "node_modules/fast-querystring": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/fast-querystring/-/fast-querystring-1.1.2.tgz", + "integrity": "sha512-g6KuKWmFXc0fID8WWH0jit4g0AGBoJhCkJMb1RmbsSEUNvQ+ZC8D6CUZ+GtF8nMzSPXnhiePyyqqipzNNEnHjg==", + "license": "MIT", + "dependencies": { + "fast-decode-uri-component": "^1.0.1" + } + }, + "node_modules/fast-uri": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-4.1.5.tgz", + "integrity": "sha512-vZeoMRB4epNr7QfdHxel7te/RcX16CxyXI07JCCTFWZA2s4v1azGNESRj+2EoaHSaWFL/Z3GmKT2jF6A202jLg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/fastify": { + "version": "5.12.5", + "resolved": "https://registry.npmjs.org/fastify/-/fastify-5.12.5.tgz", + "integrity": "sha512-OB2k1dlxs5/NAABqeKV2FUHkSD2BbENsCak8yULVcymn3fHIPDVa9TI3SDnJSWYSllZmSYuZXy2gTnsT+Sut1A==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT", + "dependencies": { + "@fastify/ajv-compiler": "^4.0.5", + "@fastify/error": "^4.0.0", + "@fastify/fast-json-stringify-compiler": "^5.0.0", + "@fastify/proxy-addr": "^5.0.0", + "abstract-logging": "^2.0.1", + "avvio": "^9.0.0", + "fast-json-stringify": "^7.0.0", + "find-my-way": "^9.6.0", + "light-my-request": "^6.0.0", + "pino": "^9.14.0 || ^10.1.0", + "process-warning": "^5.1.0", + "rfdc": "^1.3.1", + "secure-json-parse": "^4.0.0", + "semver": "^7.6.0", + "toad-cache": "^3.7.0" + } + }, + "node_modules/fastq": { + "version": "1.20.3", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.3.tgz", + "integrity": "sha512-XKv5nnLs6nLF71NgiKJLIZFLkPyIEuOselLG7ujZnGrRfQK8HpvY+WqKhAJUAdLomwVHErVS4LfxFlPq0/FTAw==", + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/file-uri-to-path": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", + "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==", + "license": "MIT" + }, + "node_modules/find-my-way": { + "version": "9.9.0", + "resolved": "https://registry.npmjs.org/find-my-way/-/find-my-way-9.9.0.tgz", + "integrity": "sha512-sJsgZ1sQH2UDuowPuMKg8az7Qc8F0jnj+SKkFWU/+T0xcFlgV5skgXOGUqmQzOdmW6ALA7AhJINWx3qFBkbLHA==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-querystring": "^1.0.0", + "safe-regex2": "^5.0.0" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/fs-constants": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", + "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", + "license": "MIT" + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/github-from-package": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", + "integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==", + "license": "MIT" + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "license": "ISC" + }, + "node_modules/ipaddr.js": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.5.0.tgz", + "integrity": "sha512-aq+t5NAc+cS6rZQQVWC2x98CPqGtKKTMDd4Gaodv0wShnItdKg/51djkGJ1hqH+Oy0ivDftCbSLCQob8zso01w==", + "license": "MIT", + "engines": { + "node": ">= 10" + } + }, + "node_modules/json-schema-ref-resolver": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/json-schema-ref-resolver/-/json-schema-ref-resolver-3.0.0.tgz", + "integrity": "sha512-hOrZIVL5jyYFjzk7+y7n5JDzGlU8rfWDuYyHwGa2WA8/pcmMHezp2xsVwxrebD/Q9t8Nc5DboieySDpCp4WG4A==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT", + "dependencies": { + "dequal": "^2.0.3" + } + }, + "node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "license": "MIT" + }, + "node_modules/light-my-request": { + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/light-my-request/-/light-my-request-6.6.0.tgz", + "integrity": "sha512-CHYbu8RtboSIoVsHZ6Ye4cj4Aw/yg2oAFimlF7mNvfDV192LR7nDiKtSIfCuLT7KokPSTn/9kfVLm5OGN0A28A==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause", + "dependencies": { + "cookie": "^1.0.1", + "process-warning": "^4.0.0", + "set-cookie-parser": "^2.6.0" + } + }, + "node_modules/light-my-request/node_modules/process-warning": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/process-warning/-/process-warning-4.0.1.tgz", + "integrity": "sha512-3c2LzQ3rY9d0hc1emcsHhfT9Jwz0cChib/QN89oME2R451w5fy3f0afAhERFZAwrbDU43wk12d0ORBpDVME50Q==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT" + }, + "node_modules/mimic-response": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", + "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/mkdirp-classic": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", + "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", + "license": "MIT" + }, + "node_modules/napi-build-utils": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-2.0.0.tgz", + "integrity": "sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==", + "license": "MIT" + }, + "node_modules/node-abi": { + "version": "3.96.0", + "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.96.0.tgz", + "integrity": "sha512-rebQ/lz7i0EkoLzUVSrKRzA69zMkwLp95kKMWoMDkkM00Suxz0D7zEQPwRml5fQum24mj7bPvmlgLAmu2JCiYg==", + "license": "MIT", + "dependencies": { + "semver": "^7.3.5" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/on-exit-leak-free": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/on-exit-leak-free/-/on-exit-leak-free-2.1.2.tgz", + "integrity": "sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==", + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/pino": { + "version": "10.3.1", + "resolved": "https://registry.npmjs.org/pino/-/pino-10.3.1.tgz", + "integrity": "sha512-r34yH/GlQpKZbU1BvFFqOjhISRo1MNx1tWYsYvmj6KIRHSPMT2+yHOEb1SG6NMvRoHRF0a07kCOox/9yakl1vg==", + "license": "MIT", + "dependencies": { + "@pinojs/redact": "^0.4.0", + "atomic-sleep": "^1.0.0", + "on-exit-leak-free": "^2.1.0", + "pino-abstract-transport": "^3.0.0", + "pino-std-serializers": "^7.0.0", + "process-warning": "^5.0.0", + "quick-format-unescaped": "^4.0.3", + "real-require": "^0.2.0", + "safe-stable-stringify": "^2.3.1", + "sonic-boom": "^4.0.1", + "thread-stream": "^4.0.0" + }, + "bin": { + "pino": "bin.js" + } + }, + "node_modules/pino-abstract-transport": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pino-abstract-transport/-/pino-abstract-transport-3.0.0.tgz", + "integrity": "sha512-wlfUczU+n7Hy/Ha5j9a/gZNy7We5+cXp8YL+X+PG8S0KXxw7n/JXA3c46Y0zQznIJ83URJiwy7Lh56WLokNuxg==", + "license": "MIT", + "dependencies": { + "split2": "^4.0.0" + } + }, + "node_modules/pino-std-serializers": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/pino-std-serializers/-/pino-std-serializers-7.1.0.tgz", + "integrity": "sha512-BndPH67/JxGExRgiX1dX0w1FvZck5Wa4aal9198SrRhZjH3GxKQUKIBnYJTdj2HDN3UQAS06HlfcSbQj2OHmaw==", + "license": "MIT" + }, + "node_modules/prebuild-install": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.3.tgz", + "integrity": "sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==", + "deprecated": "No longer maintained. Please contact the author of the relevant native addon; alternatives are available.", + "license": "MIT", + "dependencies": { + "detect-libc": "^2.0.0", + "expand-template": "^2.0.3", + "github-from-package": "0.0.0", + "minimist": "^1.2.3", + "mkdirp-classic": "^0.5.3", + "napi-build-utils": "^2.0.0", + "node-abi": "^3.3.0", + "pump": "^3.0.0", + "rc": "^1.2.7", + "simple-get": "^4.0.0", + "tar-fs": "^2.0.0", + "tunnel-agent": "^0.6.0" + }, + "bin": { + "prebuild-install": "bin.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/process-warning": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/process-warning/-/process-warning-5.1.0.tgz", + "integrity": "sha512-jQSaVHsPgtyw60e1rQ/A+/ArPEj/S8pS/vFnyGa/gYFXrKk/6RuDkoqVDQ5NI5MmS01698ltlAk0NoDBNLujRw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT" + }, + "node_modules/pump": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.4.tgz", + "integrity": "sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==", + "license": "MIT", + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/quick-format-unescaped": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/quick-format-unescaped/-/quick-format-unescaped-4.0.4.tgz", + "integrity": "sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==", + "license": "MIT" + }, + "node_modules/rc": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "license": "(BSD-2-Clause OR MIT OR Apache-2.0)", + "dependencies": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "bin": { + "rc": "cli.js" + } + }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/real-require": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/real-require/-/real-require-0.2.0.tgz", + "integrity": "sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==", + "license": "MIT", + "engines": { + "node": ">= 12.13.0" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ret": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/ret/-/ret-0.5.0.tgz", + "integrity": "sha512-I1XxrZSQ+oErkRR4jYbAyEEu2I0avBvvMM5JN+6EBprOGRCs63ENqZ3vjavq8fBw2+62G5LF5XelKwuJpcvcxw==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rfdc": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz", + "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==", + "license": "MIT" + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/safe-regex2": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/safe-regex2/-/safe-regex2-5.1.1.tgz", + "integrity": "sha512-mOSBvHGDZMuIEZMdOz/aCEYDCv0E7nfcNsIhUF+/P+xC7Hyf3FkvymqgPbg9D1EdSGu+uKbJgy09K/RKKc7kJA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT", + "dependencies": { + "ret": "~0.5.0" + }, + "bin": { + "safe-regex2": "bin/safe-regex2.js" + } + }, + "node_modules/safe-stable-stringify": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.5.0.tgz", + "integrity": "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/secure-json-parse": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/secure-json-parse/-/secure-json-parse-4.1.0.tgz", + "integrity": "sha512-l4KnYfEyqYJxDwlNVyRfO2E4NTHfMKAWdUuA8J0yve2Dz/E/PdBepY03RvyJpssIpRFwJoCD55wA+mEDs6ByWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/semver": { + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/set-cookie-parser": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.2.tgz", + "integrity": "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==", + "license": "MIT" + }, + "node_modules/simple-concat": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", + "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/simple-get": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-4.0.1.tgz", + "integrity": "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "decompress-response": "^6.0.0", + "once": "^1.3.1", + "simple-concat": "^1.0.0" + } + }, + "node_modules/sonic-boom": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-4.2.1.tgz", + "integrity": "sha512-w6AxtubXa2wTXAUsZMMWERrsIRAdrK0Sc+FUytWvYAhBJLyuI4llrMIC1DtlNSdI99EI86KZum2MMq3EAZlF9Q==", + "license": "MIT", + "dependencies": { + "atomic-sleep": "^1.0.0" + } + }, + "node_modules/split2": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", + "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==", + "license": "ISC", + "engines": { + "node": ">= 10.x" + } + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/tar-fs": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.5.tgz", + "integrity": "sha512-OboTd8mmMhZDNPV+UjQcK9yKAatXu2aJ+r1w4im1Otd4M4fl2hwvdoXUxIYHFTHWK/3y3FarBP70v3vwmGlOxw==", + "license": "MIT", + "dependencies": { + "chownr": "^1.1.1", + "mkdirp-classic": "^0.5.2", + "pump": "^3.0.0", + "tar-stream": "^2.1.4" + } + }, + "node_modules/tar-stream": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", + "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", + "license": "MIT", + "dependencies": { + "bl": "^4.0.3", + "end-of-stream": "^1.4.1", + "fs-constants": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/thread-stream": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/thread-stream/-/thread-stream-4.2.0.tgz", + "integrity": "sha512-e2zZ96wSChazBsbENf/Pcm/4swHt2cEKQ92rhUjkL9GCKiTDJIaTBenjE/m9DXi0QBmTMDkFDdOomUy20A1tDQ==", + "license": "MIT", + "dependencies": { + "real-require": "^1.0.0" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/thread-stream/node_modules/real-require": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/real-require/-/real-require-1.0.0.tgz", + "integrity": "sha512-P4nbQYQfePJxRSmY+v/KINxVucm4NF3p3s7pJveMTtom52FR4YGltUQLB8idDXwDDWW+eYrWDFbuzUnjoWHF7g==", + "license": "MIT" + }, + "node_modules/toad-cache": { + "version": "3.7.4", + "resolved": "https://registry.npmjs.org/toad-cache/-/toad-cache-3.7.4.tgz", + "integrity": "sha512-m1TdR/rvT7kgGJZhspNtXdsdYk0fddFpJJFlG5s+UkPFo6lkLoZ3YLOaovPYjq1R75NP5JfeTlSHaOsE09peCg==", + "license": "MIT", + "engines": { + "node": ">=20" + } + }, + "node_modules/tsx": { + "version": "4.23.13", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.23.13.tgz", + "integrity": "sha512-BL5MGkRln6aDYhb0xbQlEAGw743BaZYWdbWtdJOBriYJboKgUUYCadFp2/FpBBZquBC/ezNBn7wMMPx7FDZUDw==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "~0.28.0" + }, + "bin": { + "tsx": "dist/cli.mjs" + }, + "engines": { + "node": ">=18.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + } + }, + "node_modules/tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", + "license": "Apache-2.0", + "dependencies": { + "safe-buffer": "^5.0.1" + }, + "engines": { + "node": "*" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "license": "MIT" + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "license": "ISC" + } + } +} diff --git a/docker/backend/package.json b/docker/backend/package.json new file mode 100644 index 0000000..0b18882 --- /dev/null +++ b/docker/backend/package.json @@ -0,0 +1,22 @@ +{ + "name": "recipescam-api", + "version": "1.0.0", + "private": true, + "description": "RecipesCam web API - accounts + user recipes (server never receives photos)", + "main": "dist/server.js", + "scripts": { + "build": "tsc", + "start": "node dist/server.js", + "dev": "tsx watch src/server.ts" + }, + "dependencies": { + "better-sqlite3": "^12.11.1", + "fastify": "^5.12.5" + }, + "devDependencies": { + "@types/better-sqlite3": "^9.6.0", + "@types/node": "^22.20.3", + "tsx": "^4.23.13", + "typescript": "^5.9.3" + } +} diff --git a/docker/backend/src/db.ts b/docker/backend/src/db.ts new file mode 100644 index 0000000..7da132d --- /dev/null +++ b/docker/backend/src/db.ts @@ -0,0 +1,158 @@ +import Database from 'better-sqlite3'; +import { mkdirSync } from 'node:fs'; +import { join } from 'node:path'; +import { randomBytes, scryptSync, timingSafeEqual } from 'node:crypto'; + +export const SESSION_COOKIE = 'rc_session'; +export const SESSION_MAX_AGE_S = 30 * 24 * 60 * 60; // 30 days +export const MAX_RECIPE_BYTES = 256 * 1024; + +const DATA_DIR = process.env.DATA_DIR || './data'; +mkdirSync(DATA_DIR, { recursive: true }); + +export const db = new Database(join(DATA_DIR, 'recipescam.db')); +db.pragma('journal_mode = WAL'); + +db.exec(` +CREATE TABLE IF NOT EXISTS users ( + id INTEGER PRIMARY KEY, + email TEXT UNIQUE NOT NULL, + password_hash TEXT NOT NULL, + created_at TEXT NOT NULL +); +CREATE TABLE IF NOT EXISTS sessions ( + token TEXT PRIMARY KEY, + user_id INTEGER NOT NULL, + expires_at TEXT NOT NULL +); +CREATE TABLE IF NOT EXISTS recipes ( + id INTEGER PRIMARY KEY, + user_id INTEGER NOT NULL, + name TEXT NOT NULL, + json TEXT NOT NULL, + created_at TEXT NOT NULL, + updated_at TEXT NOT NULL +); +CREATE INDEX IF NOT EXISTS idx_sessions_user ON sessions(user_id); +CREATE INDEX IF NOT EXISTS idx_recipes_user ON recipes(user_id); +`); + +export type User = { id: number; email: string }; +export type Recipe = { + id: number; + name: string; + recipe: unknown; + createdAt: string; + updatedAt: string; +}; + +// scrypt: per-user random salt, stored as "salt:hash" (hex). +const SCRYPT = { N: 16384, r: 8, p: 1, keylen: 32 } as const; + +export function hashPassword(password: string): string { + const salt = randomBytes(16).toString('hex'); + const hash = scryptSync(password, salt, SCRYPT.keylen, SCRYPT).toString('hex'); + return `${salt}:${hash}`; +} + +export function verifyPassword(password: string, stored: string): boolean { + const [salt, hash] = stored.split(':'); + if (!salt || !hash) return false; + const expected = Buffer.from(hash, 'hex'); + const actual = scryptSync(password, salt, SCRYPT.keylen, SCRYPT); + return expected.length === actual.length && timingSafeEqual(expected, actual); +} + +// Burned on unknown-email logins so response time does not leak account existence. +export const DUMMY_HASH = hashPassword('invalid-password-placeholder'); + +const now = () => new Date().toISOString(); + +export function createUser(email: string, password: string): User | null { + try { + const info = db + .prepare('INSERT INTO users (email, password_hash, created_at) VALUES (?, ?, ?)') + .run(email, hashPassword(password), now()); + return { id: Number(info.lastInsertRowid), email }; + } catch (err) { + if ((err as { code?: string }).code === 'SQLITE_CONSTRAINT_UNIQUE') return null; + throw err; + } +} + +export function findUserByEmail(email: string): (User & { password_hash: string }) | undefined { + return db + .prepare('SELECT id, email, password_hash FROM users WHERE email = ?') + .get(email) as (User & { password_hash: string }) | undefined; +} + +export function findUserById(id: number): User | undefined { + return db.prepare('SELECT id, email FROM users WHERE id = ?').get(id) as User | undefined; +} + +export function createSession(userId: number): string { + const token = randomBytes(32).toString('hex'); + const expiresAt = new Date(Date.now() + SESSION_MAX_AGE_S * 1000).toISOString(); + db.prepare('INSERT INTO sessions (token, user_id, expires_at) VALUES (?, ?, ?)').run( + token, + userId, + expiresAt, + ); + return token; +} + +export function sessionUser(token: string): User | undefined { + const row = db + .prepare('SELECT token, user_id AS userId, expires_at AS expiresAt FROM sessions WHERE token = ?') + .get(token) as { token: string; userId: number; expiresAt: string } | undefined; + if (!row) return undefined; + if (row.expiresAt <= now()) { + db.prepare('DELETE FROM sessions WHERE token = ?').run(row.token); // lazy cleanup + return undefined; + } + return findUserById(row.userId); +} + +export function deleteSession(token: string): void { + db.prepare('DELETE FROM sessions WHERE token = ?').run(token); +} + +export function listRecipes(userId: number): Recipe[] { + const rows = db + .prepare( + 'SELECT id, name, json, created_at AS createdAt, updated_at AS updatedAt FROM recipes WHERE user_id = ? ORDER BY updated_at DESC, id DESC', + ) + .all(userId) as { id: number; name: string; json: string; createdAt: string; updatedAt: string }[]; + return rows.map((r) => ({ id: r.id, name: r.name, recipe: JSON.parse(r.json), createdAt: r.createdAt, updatedAt: r.updatedAt })); +} + +export function getRecipe(userId: number, id: number): Recipe | undefined { + const row = db + .prepare( + 'SELECT id, name, json, created_at AS createdAt, updated_at AS updatedAt FROM recipes WHERE id = ? AND user_id = ?', + ) + .get(id, userId) as { id: number; name: string; json: string; createdAt: string; updatedAt: string } | undefined; + return row && { id: row.id, name: row.name, recipe: JSON.parse(row.json), createdAt: row.createdAt, updatedAt: row.updatedAt }; +} + +export function createRecipe(userId: number, name: string, recipe: unknown): Recipe { + const ts = now(); + const info = db + .prepare('INSERT INTO recipes (user_id, name, json, created_at, updated_at) VALUES (?, ?, ?, ?, ?)') + .run(userId, name, JSON.stringify(recipe), ts, ts); + const id = Number(info.lastInsertRowid); + return { id, name, recipe, createdAt: ts, updatedAt: ts }; +} + +export function updateRecipe(userId: number, id: number, name: string, recipe: unknown): Recipe | undefined { + const ts = now(); + const info = db + .prepare('UPDATE recipes SET name = ?, json = ?, updated_at = ? WHERE id = ? AND user_id = ?') + .run(name, JSON.stringify(recipe), ts, id, userId); + if (info.changes === 0) return undefined; + return getRecipe(userId, id); +} + +export function deleteRecipe(userId: number, id: number): boolean { + return db.prepare('DELETE FROM recipes WHERE id = ? AND user_id = ?').run(id, userId).changes > 0; +} diff --git a/docker/backend/src/server.ts b/docker/backend/src/server.ts new file mode 100644 index 0000000..9e973a9 --- /dev/null +++ b/docker/backend/src/server.ts @@ -0,0 +1,191 @@ +import Fastify, { type FastifyReply, type FastifyRequest } from 'fastify'; +import { + MAX_RECIPE_BYTES, + SESSION_COOKIE, + SESSION_MAX_AGE_S, + DUMMY_HASH, + createRecipe, + createSession, + createUser, + deleteRecipe, + deleteSession, + findUserByEmail, + listRecipes, + sessionUser, + updateRecipe, + verifyPassword, + type Recipe, + type User, +} from './db'; + +const PORT = Number(process.env.PORT || 3000); +const HOST = '0.0.0.0'; + +const EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; +const MAX_EMAIL = 254; +const MIN_PASSWORD = 8; +const MAX_PASSWORD = 200; +const MAX_NAME = 120; + +const app = Fastify({ logger: true, bodyLimit: 1024 * 1024 }); + +// Bodyless DELETE/logout requests still often carry Content-Type: application/json. +app.addContentTypeParser('application/json', { parseAs: 'string' }, (_req, body, done) => { + const raw = (body as string).trim(); + if (raw === '') return done(null, undefined); + try { + done(null, JSON.parse(raw)); + } catch { + done(Object.assign(new Error('invalid JSON body'), { statusCode: 400 })); + } +}); + +// Single error shape for the whole API: { error: "..." } +app.setErrorHandler((err, req, reply) => { + const e = err as { statusCode?: number; message?: string }; + const status = e.statusCode && e.statusCode >= 400 ? e.statusCode : 500; + if (status >= 500) req.log.error(err); + reply.status(status).send({ error: status >= 500 ? 'internal_error' : (e.message ?? 'error') }); +}); +app.setNotFoundHandler((_req, reply) => reply.status(404).send({ error: 'not_found' })); + +// ---- cookie helpers (hand-rolled: only one cookie, no plugin needed) ---- +function cookieOf(req: FastifyRequest, name: string): string | undefined { + const raw = req.headers.cookie; + if (!raw) return undefined; + for (const part of raw.split(';')) { + const eq = part.indexOf('='); + if (eq === -1) continue; + if (part.slice(0, eq).trim() === name) return part.slice(eq + 1).trim(); + } + return undefined; +} + +function setSession(reply: FastifyReply, token: string): void { + reply.header( + 'set-cookie', + `${SESSION_COOKIE}=${token}; Path=/; HttpOnly; SameSite=Lax; Max-Age=${SESSION_MAX_AGE_S}`, + ); +} + +function clearSession(reply: FastifyReply): void { + reply.header('set-cookie', `${SESSION_COOKIE}=; Path=/; HttpOnly; SameSite=Lax; Max-Age=0`); +} + +// ---- validation at the trust boundary ---- +type Json = Record; + +function bodyOf(req: FastifyRequest): Json | undefined { + const b = req.body as unknown; + return b !== null && typeof b === 'object' && !Array.isArray(b) ? (b as Json) : undefined; +} + +function credentials(b: Json): { email: string; password: string } | string { + const email = typeof b.email === 'string' ? b.email.trim().toLowerCase() : ''; + const password = typeof b.password === 'string' ? b.password : ''; + if (!email || email.length > MAX_EMAIL || !EMAIL_RE.test(email)) return 'invalid email'; + if (password.length < MIN_PASSWORD || password.length > MAX_PASSWORD) + return `password must be ${MIN_PASSWORD}-${MAX_PASSWORD} characters`; + return { email, password }; +} + +function recipePayload(b: Json): { name: string; recipe: Json } | string { + const name = typeof b.name === 'string' ? b.name.trim() : ''; + const recipe = b.recipe; + if (!name || name.length > MAX_NAME) return `name must be 1-${MAX_NAME} characters`; + if (recipe === null || typeof recipe !== 'object' || Array.isArray(recipe)) return 'recipe must be an object'; + if (Buffer.byteLength(JSON.stringify(recipe)) > MAX_RECIPE_BYTES) return 'recipe too large'; + return { name, recipe: recipe as Json }; +} + +function auth(req: FastifyRequest): User | undefined { + const token = cookieOf(req, SESSION_COOKIE); + return token ? sessionUser(token) : undefined; +} + +// ---- routes ---- +app.get('/api/health', async () => ({ ok: true })); + +app.post('/api/auth/signup', async (req, reply) => { + const b = bodyOf(req); + if (!b) return reply.status(400).send({ error: 'invalid body' }); + const creds = credentials(b); + if (typeof creds === 'string') return reply.status(400).send({ error: creds }); + if (findUserByEmail(creds.email)) return reply.status(409).send({ error: 'email already registered' }); + const user = createUser(creds.email, creds.password); + if (!user) return reply.status(409).send({ error: 'email already registered' }); + setSession(reply, createSession(user.id)); + return reply.status(201).send({ user }); +}); + +app.post('/api/auth/login', async (req, reply) => { + const b = bodyOf(req); + if (!b || typeof b.email !== 'string' || typeof b.password !== 'string') + return reply.status(400).send({ error: 'invalid body' }); + const email = b.email.trim().toLowerCase(); + const row = findUserByEmail(email); + const ok = verifyPassword(b.password, row?.password_hash ?? DUMMY_HASH); + if (!row || !ok) return reply.status(401).send({ error: 'invalid credentials' }); + setSession(reply, createSession(row.id)); + return reply.status(200).send({ user: { id: row.id, email: row.email } }); +}); + +app.post('/api/auth/logout', async (req, reply) => { + const token = cookieOf(req, SESSION_COOKIE); + if (token) deleteSession(token); + clearSession(reply); + return reply.status(204).send(); +}); + +app.get('/api/auth/me', async (req, reply) => { + const user = auth(req); + if (!user) return reply.status(401).send({ error: 'unauthorized' }); + return reply.status(200).send({ user }); +}); + +app.get('/api/recipes', async (req, reply) => { + const user = auth(req); + if (!user) return reply.status(401).send({ error: 'unauthorized' }); + return reply.status(200).send({ recipes: listRecipes(user.id) }); +}); + +app.post('/api/recipes', async (req, reply) => { + const user = auth(req); + if (!user) return reply.status(401).send({ error: 'unauthorized' }); + const b = bodyOf(req); + const payload = b && recipePayload(b); + if (typeof payload === 'string' || !payload) + return reply.status(payload === 'recipe too large' ? 413 : 400).send({ error: payload ?? 'invalid body' }); + const recipe: Recipe = createRecipe(user.id, payload.name, payload.recipe); + return reply.status(201).send({ recipe }); +}); + +app.put<{ Params: { id: string } }>('/api/recipes/:id', async (req, reply) => { + const user = auth(req); + if (!user) return reply.status(401).send({ error: 'unauthorized' }); + const id = Number(req.params.id); + if (!Number.isInteger(id) || id <= 0) return reply.status(404).send({ error: 'recipe not found' }); + const b = bodyOf(req); + const payload = b && recipePayload(b); + if (typeof payload === 'string' || !payload) + return reply.status(payload === 'recipe too large' ? 413 : 400).send({ error: payload ?? 'invalid body' }); + const recipe = updateRecipe(user.id, id, payload.name, payload.recipe); + if (!recipe) return reply.status(404).send({ error: 'recipe not found' }); + return reply.status(200).send({ recipe }); +}); + +app.delete<{ Params: { id: string } }>('/api/recipes/:id', async (req, reply) => { + const user = auth(req); + if (!user) return reply.status(401).send({ error: 'unauthorized' }); + const id = Number(req.params.id); + if (!Number.isInteger(id) || id <= 0) return reply.status(404).send({ error: 'recipe not found' }); + if (!deleteRecipe(user.id, id)) return reply.status(404).send({ error: 'recipe not found' }); + return reply.status(204).send(); +}); + +app + .listen({ port: PORT, host: HOST }) + .catch((err) => { + app.log.error(err); + process.exit(1); + }); diff --git a/docker/backend/tsconfig.json b/docker/backend/tsconfig.json new file mode 100644 index 0000000..3ec504e --- /dev/null +++ b/docker/backend/tsconfig.json @@ -0,0 +1,18 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "commonjs", + "moduleResolution": "node", + "lib": ["ES2022"], + "types": ["node"], + "outDir": "dist", + "rootDir": "src", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "declaration": false, + "sourceMap": false + }, + "include": ["src/**/*.ts"] +} diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml new file mode 100644 index 0000000..1df2a7c --- /dev/null +++ b/docker/docker-compose.yml @@ -0,0 +1,26 @@ +# RecipesCam web — the whole stack. Copy this folder anywhere, then: +# cp .env.example .env +# docker compose up -d --build +name: recipescam-web + +services: + # Accounts + saved recipes. Never sees a photo: every pixel of the render + # pipeline runs in the visitor's browser. + api: + build: ./backend + restart: unless-stopped + environment: + DATA_DIR: /data + volumes: + # SQLite (WAL) lives on the host so a rebuild never loses accounts. + - ./data:/data + + # Static SPA + /api reverse proxy. "api" is the DNS name nginx.conf proxies to, + # so this service name is load-bearing. + frontend: + build: ./frontend + restart: unless-stopped + depends_on: + - api + ports: + - "${WEB_PORT:-8090}:80" diff --git a/docker/frontend/.dockerignore b/docker/frontend/.dockerignore new file mode 100644 index 0000000..7125fe8 --- /dev/null +++ b/docker/frontend/.dockerignore @@ -0,0 +1,5 @@ +node_modules +dist +public/wasm +*.log +.env diff --git a/docker/frontend/.gitignore b/docker/frontend/.gitignore new file mode 100644 index 0000000..de5b0aa --- /dev/null +++ b/docker/frontend/.gitignore @@ -0,0 +1,3 @@ +node_modules/ +dist/ +public/wasm/ diff --git a/docker/frontend/Dockerfile b/docker/frontend/Dockerfile new file mode 100644 index 0000000..ba19e00 --- /dev/null +++ b/docker/frontend/Dockerfile @@ -0,0 +1,14 @@ +# Build stage. Node 22 (glibc) so nothing here depends on the host. +FROM node:22-slim AS build +WORKDIR /app +COPY package.json package-lock.json ./ +RUN npm ci +COPY . . +# tsc --noEmit && vite build; the prebuild hook copies canvaskit.wasm into public/ +RUN npm run build + +# Runtime: static files only. The photo pipeline runs in the visitor's browser. +FROM nginx:1.27-alpine +COPY nginx.conf /etc/nginx/conf.d/default.conf +COPY --from=build /app/dist /usr/share/nginx/html +EXPOSE 80 diff --git a/docker/frontend/index.html b/docker/frontend/index.html new file mode 100644 index 0000000..360074d --- /dev/null +++ b/docker/frontend/index.html @@ -0,0 +1,15 @@ + + + + + + + + RecipesCam — web + + + +
+ + + diff --git a/docker/frontend/nginx.conf b/docker/frontend/nginx.conf new file mode 100644 index 0000000..bee49e9 --- /dev/null +++ b/docker/frontend/nginx.conf @@ -0,0 +1,40 @@ +# Static SPA + API proxy. TLS/domain are terminated outside (Nginx Proxy +# Manager), so this container only ever speaks plain HTTP on port 80. +server { + listen 80; + server_name _; + root /usr/share/nginx/html; + index index.html; + + gzip on; + gzip_comp_level 5; + gzip_min_length 1024; + gzip_types text/css application/javascript application/json application/wasm image/svg+xml; + + # canvas.wasm is ~8MB: immutable, hashed or not, it never changes under a + # given build. Fonts and the wall-frame artwork are likewise static. + location ~* ^/(wasm|assets)/ { + expires 30d; + add_header Cache-Control "public, immutable"; + try_files $uri =404; + } + + location /api/ { + # Resolved per request through Docker's embedded DNS, so the frontend can + # start before the API container without nginx refusing to boot. + resolver 127.0.0.11 valid=10s ipv6=off; + set $api_upstream http://api:3000; + proxy_pass $api_upstream$request_uri; + proxy_http_version 1.1; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + client_max_body_size 4m; + } + + # SPA fallback: /app, /login and /signup are all index.html. + location / { + try_files $uri $uri/ /index.html; + } +} diff --git a/docker/frontend/package-lock.json b/docker/frontend/package-lock.json new file mode 100644 index 0000000..2201e05 --- /dev/null +++ b/docker/frontend/package-lock.json @@ -0,0 +1,1908 @@ +{ + "name": "recipescam-web", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "recipescam-web", + "version": "0.1.0", + "dependencies": { + "canvaskit-wasm": "^0.42.0", + "exifr": "^7.1.3", + "react": "^18.3.1", + "react-dom": "^18.3.1" + }, + "devDependencies": { + "@types/node": "^22.20.3", + "@types/react": "^18.3.12", + "@types/react-dom": "^18.3.1", + "@vitejs/plugin-react": "^4.3.4", + "typescript": "^5.7.2", + "vite": "^6.0.7" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz", + "integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.29.7", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.7.tgz", + "integrity": "sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.7.tgz", + "integrity": "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.7", + "@babel/helper-compilation-targets": "^7.29.7", + "@babel/helper-module-transforms": "^7.29.7", + "@babel/helpers": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/template": "^7.29.7", + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/generator": { + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.8.tgz", + "integrity": "sha512-gZbepsdh3WDtgZKWL+vTPh71LSBrm/Y4/QDZBVCcYfmeTEEuoOYwlSy+G1StfJg+/Zy550u/3TATbm7qDbbMtg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.8", + "@babel/types": "^7.29.8", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.29.7.tgz", + "integrity": "sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.29.7", + "@babel/helper-validator-option": "^7.29.7", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.29.7.tgz", + "integrity": "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.29.7.tgz", + "integrity": "sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.29.7.tgz", + "integrity": "sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7", + "@babel/traverse": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.29.7.tgz", + "integrity": "sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", + "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", + "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.29.7.tgz", + "integrity": "sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.7.tgz", + "integrity": "sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.8.tgz", + "integrity": "sha512-E8lTAYNB1KW+FH+VGJuZM1ioAx2E6oVlvQFRrf5P8ZZmsiJXYAD9vTFV7yyEURNzgh1dFqMZuO6tUwcARbqFCA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.8" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-self": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.29.7.tgz", + "integrity": "sha512-TL0hMc9xzy86VD31nUiwzd5otRAcyEPcsegCxolO0PvcXuH1v0kECe/UIznYFihpkvU5wg/jk4v0TTEFfm53fw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-source": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.29.7.tgz", + "integrity": "sha512-06IyK09H3wi4cGbhDBwp5gUGo0IKtnYa8tyTiephirPCK6fbobVGiXMMI5zLQ4aKEYP3wZ3ArU44o+8KMrSG/Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/template": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.29.7.tgz", + "integrity": "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.8.tgz", + "integrity": "sha512-I5z7H3bf/41ktsNVLtpN0wAa336HkqIHQ5BuPLEhTkt1jVSyZpeNKIzTgEWmlxjdg81R0IgUCcaE+Ok3NvrfZg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.8", + "@babel/helper-globals": "^7.29.7", + "@babel/parser": "^7.29.8", + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.8", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.8.tgz", + "integrity": "sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", + "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz", + "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz", + "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz", + "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz", + "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz", + "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz", + "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz", + "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz", + "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz", + "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz", + "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz", + "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz", + "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz", + "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz", + "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz", + "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz", + "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz", + "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz", + "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz", + "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz", + "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz", + "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz", + "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz", + "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz", + "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz", + "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.6.0.tgz", + "integrity": "sha512-T7jf+5zgsZHwNJ4lvQ7/aezbyk0nNX+zJVWpmHA7VYsEx7a7qr5Rg5IbtJFqkgze5Y2sruq1RUY8Q837Od7iFw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@napi-rs/lzma-linux-x64-gnu": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/@napi-rs/lzma-linux-x64-gnu/-/lzma-linux-x64-gnu-1.5.1.tgz", + "integrity": "sha512-oTXEIha4SsuXdTA4Iyskj0kpdx2yVXdhd75c2v3xGrHFfVMsbhTPZU/nMPL4sWKo4pBHm3aucLaqGlF696dTyQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^22.20 || ^24.12 || >=25" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-beta.27", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz", + "integrity": "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.63.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.63.3.tgz", + "integrity": "sha512-w3Jnvi1ocaVm/c7yVPpfB98XeSRBMyzp6njL5MVVbGyXjpmUkN+s6Hp4t0PqhGCCaI1ZHMKXt/w0lA1RCaLVcw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.63.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.63.3.tgz", + "integrity": "sha512-uI/ESiaIbbRYAEhzy8PCUWDp1hB0bjAqM06mW9flOoNO4Q8DQpeoREhBR5Hegfl+wpXiguyJv6XSPzEN7OxyHQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.63.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.63.3.tgz", + "integrity": "sha512-oxhrd1jmXLwWZ83eQYDXxuqRdkqkzrjR3JobKeuUyfdNZo11FuQIvqEOZhyIT7OBHxXoGslDDjN0cQcM6T0TqQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.63.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.63.3.tgz", + "integrity": "sha512-7/YiIMghVE8DrxKvNdorAaJVdriOFgOIpdStnPx8ppx5zfTwC3jBCSEAIzB7JD5404m65THl6H93UTTVUvypmg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.63.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.63.3.tgz", + "integrity": "sha512-GXFZRRoMAytaI5z6N3Zhfw0WL18Q0M8r95D5hlC4GqE/lGk8pbSJNUBoOWDfbm6dTciqHj2nU87tI5f6XhQiOg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.63.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.63.3.tgz", + "integrity": "sha512-77W+8X3ddYgPxUpB8nZFQs2Mq+wc4HVlcSRtApXLjYBcnPMkttrSnU8VwKQjeWYhMsITHFs5cWBQ8vz1Q+5RHQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.63.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.63.3.tgz", + "integrity": "sha512-FVkwK+iUC+mq+GipVK46rRVticfAPtvPUNlqlGXUDxdVk/UGjQiiiUVPUrEXdSpU2ufU0XxLGyTqDtBidDOVmg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.63.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.63.3.tgz", + "integrity": "sha512-+aGU1t3398yQOVj1Bz8o3e+KtswxAPvO+mtxtNdfXYMkXIHu7XhhkCD7/DEH9q8tF8uhDnMWvfpUKI8y1sZJsg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.63.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.63.3.tgz", + "integrity": "sha512-cR0kjpRXR2KJ2oQK8E2KTPtphs+b9hZ8IhTZubNryt/RsqgdOZBQ2Zq0q5UedtiIi0rs3jVhJh55RE1ZHUVGUA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.63.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.63.3.tgz", + "integrity": "sha512-y1RYi4Q3/9ByVWSSt9kX2ustE0B7kFYbJ6zZdVZVyqopZs3yhCTwRfrjIX4vezUJInma/Gs6BOFDJg7yZmJ0IQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.63.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.63.3.tgz", + "integrity": "sha512-DNhEA5viIj3Z5bZLE4z4oV8N5ozWqDwyt7T6KG7VdLDJ0nW+rNOYlphBl4/3HQkK75qipPLsVOfStHHOwN9WSg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.63.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.63.3.tgz", + "integrity": "sha512-17gQCqrIpXBX2Cmi9/TygnVOqGbzsba/iaqcYSL8FY7lNugg+7AiYNs5c5nKWD+NRQha36Sa0CqkJqH4XVHwnQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.63.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.63.3.tgz", + "integrity": "sha512-6LwVnZRIyINpdku/yOcI8Tm9YqLmhHK5emmlOOnW9tO0SYEm1FmKPcsSAGp0NBlqR2P04xaND4jvN6sTHqhq8A==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.63.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.63.3.tgz", + "integrity": "sha512-xMUqkTXlEUtI/p5AAukMwBRr1enU3efsTeF+bskeFfk8t1C9rcC8sLREcZXmTfAXEbvRdJVSonVJez3TMlbR3w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.63.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.63.3.tgz", + "integrity": "sha512-S3E94co9F9WRRqEaUoQZ38K1gCz6KiM+nL7/3ijq7fDGF3OznjS5TasgYITlvl27GQKtu4lOAOsr5MFwkijvOA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.63.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.63.3.tgz", + "integrity": "sha512-1QtRDwG42x5BJI3s9mxu5rEjDnfbSnk20HQ9/ylTAYnSwYwxMVb+Vgu34wzzTQ7ogqBybebgQNUDAvZVQ38DbA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.63.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.63.3.tgz", + "integrity": "sha512-BQhejF6ZXOpxbngiNTP12GCGQeaDVL2QXGeBVViKIYzFHM5RKxTxwUMB1fr1BeNFphFMpnRqC5QSXFSa4z6UQw==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.63.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.63.3.tgz", + "integrity": "sha512-SXagRwnI2Wlwlitllu59UK/nGVbD1CKPcNqDplHwIC4BqJcpXFjD32d1R/RbuISa95HdQrZM3/7v4bKiowFaLA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.63.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.63.3.tgz", + "integrity": "sha512-2IPozoEALRCziGqE8O9KMK60PMu5TS1huv4fwoeCexj+WjmcwFtX9CTOVbfXCUqcELAubEwRFPYlzb/WvwY2HQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.63.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.63.3.tgz", + "integrity": "sha512-AoxqosUHT9IX54hFn2TiN6A7d6ZKTtE6pd2bqWtqkkNJ6HJGaU6FRouGX8L1O7R/ZwsnCnpQrHzb4pDEx+UHRQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.63.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.63.3.tgz", + "integrity": "sha512-d+CaftKgmkFBzCwezMqqy1d0QNNYugqLCMcYVQWBy5SS2YfeMP8Q8ripkgx9O8IyBXXLHrJ+aaCV4U96usv6Yg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.63.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.63.3.tgz", + "integrity": "sha512-xXlDF6nR1eOuXbdDy5Hl5fmtY7teUDevF/k0O7IPoZe4Tpmdv+lgdE5JRsnhQtt37ql9P0VF2kAN9a0OCZdo+Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.63.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.63.3.tgz", + "integrity": "sha512-YtXAgLN+JP7Ay6qG3eWhc7IHMQPzLc8r3uvhAvlJIoCz/4Q32+Bl9Fmnywidh8v1GOIMmymjovfqY9ETAtysvA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.63.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.63.3.tgz", + "integrity": "sha512-WuWtSJRNo549vzcfZyEgfqb6zeSgn1F+UE5kQ+BCjzz0W4MGCjntUHkZVc1VRuAM7+ULaSyhiPxD1spyewFvkQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.63.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.63.3.tgz", + "integrity": "sha512-+lIKX7O0+IGe7WuhATaAMMeT7B76vfhXH/l9wLQL+nvyhbw2ohYCKIdWL56JfDu75CWt5oKRP4QFH/jkMtBquA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", + "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", + "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.2" + } + }, + "node_modules/@types/estree": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "22.20.3", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.20.3.tgz", + "integrity": "sha512-DZmzkmwHzXrLPAXPyKNDzlIwMMUZCVacoD25ywdy5YTKGbOx/2ld+Q38Im2zJ0vBuZP5Prd3VZutKZyXwkOS8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/@types/prop-types": { + "version": "15.7.15", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz", + "integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/react": { + "version": "18.3.31", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.31.tgz", + "integrity": "sha512-vfEqpXTvwT91yhmwdfouStN2hSKwTvyRs8qpLfADyrq/kxDw0hZM7Wk9Ug1FELj8hIby+S/+kQCSRFF32nv2Qw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/prop-types": "*", + "csstype": "^3.2.2" + } + }, + "node_modules/@types/react-dom": { + "version": "18.3.7", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.7.tgz", + "integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "^18.0.0" + } + }, + "node_modules/@vitejs/plugin-react": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.7.0.tgz", + "integrity": "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.28.0", + "@babel/plugin-transform-react-jsx-self": "^7.27.1", + "@babel/plugin-transform-react-jsx-source": "^7.27.1", + "@rolldown/pluginutils": "1.0.0-beta.27", + "@types/babel__core": "^7.20.5", + "react-refresh": "^0.17.0" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "peerDependencies": { + "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" + } + }, + "node_modules/@webgpu/types": { + "version": "0.1.21", + "resolved": "https://registry.npmjs.org/@webgpu/types/-/types-0.1.21.tgz", + "integrity": "sha512-pUrWq3V5PiSGFLeLxoGqReTZmiiXwY3jRkIG5sLLKjyqNxrwm/04b4nw7LSmGWJcKk59XOM/YRTUwOzo4MMlow==", + "license": "BSD-3-Clause" + }, + "node_modules/baseline-browser-mapping": { + "version": "2.11.24", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.11.24.tgz", + "integrity": "sha512-hYrgxie335U08WqICoGqKRzV1HFXv6zdxwJE4ekCb80CM9a0SVVsN4QPwT67RraRo+9h8IATk6uxHJw7QSkdOg==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/browserslist": { + "version": "4.29.0", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.29.0.tgz", + "integrity": "sha512-3GSvyjvDI4Dur1Meg2BekJquu5uF+9R9a1+5M1Mde192eZoXbeXjzgOsgqPS2V8D5wrrip0gR5Hf/GhWQ9ZzaA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "baseline-browser-mapping": "^2.11.23", + "caniuse-lite": "^1.0.30001810", + "electron-to-chromium": "^1.5.427", + "node-releases": "^2.0.55", + "update-browserslist-db": "^1.3.3" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001810", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001810.tgz", + "integrity": "sha512-TITQPUkaz+aVk5GL6NhOdwk1aEaNTSDPsGFWrTuhKGtjTF70jL/Oht2W4c6rXUe5fu7Ie19VIahAXHIIiWWNeg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/canvaskit-wasm": { + "version": "0.42.0", + "resolved": "https://registry.npmjs.org/canvaskit-wasm/-/canvaskit-wasm-0.42.0.tgz", + "integrity": "sha512-N3ikotmzYwDVAcpQKDMptBT2P5pIEWS9ywdmrQA2awwaMF9cxKEFuxZZHv9ay1Em9kDjxDsPtwUzDm1uNj136g==", + "license": "BSD-3-Clause", + "dependencies": { + "@webgpu/types": "0.1.21" + } + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.430", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.430.tgz", + "integrity": "sha512-e1QEj72Y4zd8RlNZVmoTg+iCOSVwpk05IOiiQwdrkwCSVlZfPthevErhE+nckGd2YbsXfp1SkisznhGVIXP2NQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/esbuild": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz", + "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.12", + "@esbuild/android-arm": "0.25.12", + "@esbuild/android-arm64": "0.25.12", + "@esbuild/android-x64": "0.25.12", + "@esbuild/darwin-arm64": "0.25.12", + "@esbuild/darwin-x64": "0.25.12", + "@esbuild/freebsd-arm64": "0.25.12", + "@esbuild/freebsd-x64": "0.25.12", + "@esbuild/linux-arm": "0.25.12", + "@esbuild/linux-arm64": "0.25.12", + "@esbuild/linux-ia32": "0.25.12", + "@esbuild/linux-loong64": "0.25.12", + "@esbuild/linux-mips64el": "0.25.12", + "@esbuild/linux-ppc64": "0.25.12", + "@esbuild/linux-riscv64": "0.25.12", + "@esbuild/linux-s390x": "0.25.12", + "@esbuild/linux-x64": "0.25.12", + "@esbuild/netbsd-arm64": "0.25.12", + "@esbuild/netbsd-x64": "0.25.12", + "@esbuild/openbsd-arm64": "0.25.12", + "@esbuild/openbsd-x64": "0.25.12", + "@esbuild/openharmony-arm64": "0.25.12", + "@esbuild/sunos-x64": "0.25.12", + "@esbuild/win32-arm64": "0.25.12", + "@esbuild/win32-ia32": "0.25.12", + "@esbuild/win32-x64": "0.25.12" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/exifr": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/exifr/-/exifr-7.1.3.tgz", + "integrity": "sha512-g/aje2noHivrRSLbAUtBPWFbxKdKhgj/xr1vATDdUXPOFYJlQ62Ft0oy+72V6XLIpDJfHs6gXLbBLAolqOXYRw==", + "license": "MIT" + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "license": "MIT" + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "license": "MIT", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.19", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.19.tgz", + "integrity": "sha512-Y2tUNy4ouw6tq5oDSKeQYGOyhkUBhNOcGV/02KC+6kd9eDGqdZd++mjMiIDilrBYvjEnCYvVtsuHCuP+okSfug==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/node-releases": { + "version": "2.0.56", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.56.tgz", + "integrity": "sha512-x0InOIyzgdk+eyaWaRJFH5snEtiImgBgblZ2CyPrLmqqcuMQkEvcDPHbzqbD8eDsSeJbVOjn+crzyzHaM4D+/A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.7.tgz", + "integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.5.28", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.28.tgz", + "integrity": "sha512-RRuzqDtt5Y9h3quz5hWhK+TPnsmVs6WwSU6LkJMeY4HstUEDuYTG8UJSdawMRzmzAtV+KEoG8N3Qg2qLy5vM/A==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.18", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/react": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", + "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", + "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0", + "scheduler": "^0.23.2" + }, + "peerDependencies": { + "react": "^18.3.1" + } + }, + "node_modules/react-refresh": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz", + "integrity": "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/rollup": { + "version": "4.63.3", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.63.3.tgz", + "integrity": "sha512-1i2XreiAoMMXuPGD6Msj2xWrMMkHojNRKivInxGQcg7/1KuPuYlfUutLyh4drnOxUTHX9cHI4wFoat8D/NKaBw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.9" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@napi-rs/lzma-linux-x64-gnu": "1.5.1", + "@rollup/rollup-android-arm-eabi": "4.63.3", + "@rollup/rollup-android-arm64": "4.63.3", + "@rollup/rollup-darwin-arm64": "4.63.3", + "@rollup/rollup-darwin-x64": "4.63.3", + "@rollup/rollup-freebsd-arm64": "4.63.3", + "@rollup/rollup-freebsd-x64": "4.63.3", + "@rollup/rollup-linux-arm-gnueabihf": "4.63.3", + "@rollup/rollup-linux-arm-musleabihf": "4.63.3", + "@rollup/rollup-linux-arm64-gnu": "4.63.3", + "@rollup/rollup-linux-arm64-musl": "4.63.3", + "@rollup/rollup-linux-loong64-gnu": "4.63.3", + "@rollup/rollup-linux-loong64-musl": "4.63.3", + "@rollup/rollup-linux-ppc64-gnu": "4.63.3", + "@rollup/rollup-linux-ppc64-musl": "4.63.3", + "@rollup/rollup-linux-riscv64-gnu": "4.63.3", + "@rollup/rollup-linux-riscv64-musl": "4.63.3", + "@rollup/rollup-linux-s390x-gnu": "4.63.3", + "@rollup/rollup-linux-x64-gnu": "4.63.3", + "@rollup/rollup-linux-x64-musl": "4.63.3", + "@rollup/rollup-openbsd-x64": "4.63.3", + "@rollup/rollup-openharmony-arm64": "4.63.3", + "@rollup/rollup-win32-arm64-msvc": "4.63.3", + "@rollup/rollup-win32-ia32-msvc": "4.63.3", + "@rollup/rollup-win32-x64-gnu": "4.63.3", + "@rollup/rollup-win32-x64-msvc": "4.63.3", + "fsevents": "~2.3.2" + } + }, + "node_modules/scheduler": { + "version": "0.23.2", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", + "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + } + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/update-browserslist-db": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.3.3.tgz", + "integrity": "sha512-pJ2sYawQS0R/WI928Gj5GlPhTGzbMelq0+4INtSYNDV9ErKJcX6xjGWkoG/VnB3dpUm00zALaqkrUD77pO5TDQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/vite": { + "version": "6.4.3", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.3.tgz", + "integrity": "sha512-NTKlcQjlAK7MlQoyb6LgaqHc8sso/pVyUJYWMws3jg21uTJw/LddqIFPcPqP6PzpgbIcZyKI85sFE4HBrQDA8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.25.0", + "fdir": "^6.4.4", + "picomatch": "^4.0.2", + "postcss": "^8.5.3", + "rollup": "^4.34.9", + "tinyglobby": "^0.2.13" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "jiti": ">=1.21.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + } + } +} diff --git a/docker/frontend/package.json b/docker/frontend/package.json new file mode 100644 index 0000000..6d0857d --- /dev/null +++ b/docker/frontend/package.json @@ -0,0 +1,29 @@ +{ + "name": "recipescam-web", + "private": true, + "version": "0.1.0", + "type": "module", + "scripts": { + "prepare-wasm": "node scripts/copy-wasm.mjs", + "predev": "npm run prepare-wasm", + "dev": "vite", + "prebuild": "npm run prepare-wasm", + "build": "tsc --noEmit && vite build", + "typecheck": "tsc --noEmit", + "preview": "vite preview --port 4173" + }, + "dependencies": { + "canvaskit-wasm": "^0.42.0", + "exifr": "^7.1.3", + "react": "^18.3.1", + "react-dom": "^18.3.1" + }, + "devDependencies": { + "@types/node": "^22.20.3", + "@types/react": "^18.3.12", + "@types/react-dom": "^18.3.1", + "@vitejs/plugin-react": "^4.3.4", + "typescript": "^5.7.2", + "vite": "^6.0.7" + } +} diff --git a/docker/frontend/public/assets/Cousine-Regular.ttf b/docker/frontend/public/assets/Cousine-Regular.ttf new file mode 100644 index 0000000000000000000000000000000000000000..e01ff6f05338a180c65ebadb5617f732d941e961 GIT binary patch literal 296856 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�}%2I2~8J(47F+qiqf))xZ~L#=vSp3`JBLqP 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 uQ)ly=(epP!Z3XH*vj(EyFYId1Ou(7

WUtEox8#3c@5|}? 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

B#w6eEE%=wp66DR)0$cdSX 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

EZFFblw% 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

YbamWbd&=N79;j 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%0DN}ikpL4^H>rs6^vjKc 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 literal 0 HcmV?d00001 diff --git a/docker/frontend/scripts/copy-wasm.mjs b/docker/frontend/scripts/copy-wasm.mjs new file mode 100644 index 0000000..cfef15c --- /dev/null +++ b/docker/frontend/scripts/copy-wasm.mjs @@ -0,0 +1,16 @@ +// canvaskit-wasm resolves its wasm at runtime, not through the bundler, so the +// binary has to be a served asset. 8MB in the repo is worse than 8MB copied out +// of node_modules at build time (the Docker build stage does the same). +import { copyFileSync, mkdirSync, existsSync } from 'node:fs'; +import { dirname } from 'node:path'; + +const from = 'node_modules/canvaskit-wasm/bin/full/canvaskit.wasm'; +const to = 'public/wasm/canvaskit.wasm'; + +if (!existsSync(from)) { + console.error(`missing ${from} — run npm install first`); + process.exit(1); +} +mkdirSync(dirname(to), { recursive: true }); +copyFileSync(from, to); +console.log(`copied ${from} -> ${to}`); diff --git a/docker/frontend/shared/types/index.ts b/docker/frontend/shared/types/index.ts new file mode 100644 index 0000000..0a0fff6 --- /dev/null +++ b/docker/frontend/shared/types/index.ts @@ -0,0 +1,142 @@ +export type FrameId = 'none' | 'polaroid' | 'classic-white' | 'cinematic' | 'wallframe'; + +// Viewfinder/composition aspect-ratio selector. Convention is an UPRIGHT +// (portrait) picture: "4:3" = standing 3:4 photo (w/h = 3/4), "3:2" = standing +// 2:3 photo (w/h = 2/3). "full" = current fullscreen behaviour (no ratio crop). +// Wall Frame ignores the ratio (frame + export keep fullscreen as before). +export type AspectRatio = 'full' | '4:3' | '3:2'; +// Full-frame metering behaviour. Evaluative = the camera's default whole-scene +// metering (offset 0). Highlight-weighted protects bright areas by underexposing +// (~ -0.7EV) — the camera lib exposes no real metering-mode API, so this is the +// closest honest approximation via the existing EV path. +export type MeterMode = 'evaluative' | 'highlight'; +// Camera shutter sound. OS policy may force the click on in some regions; +// there is no native volume control (OFF/ON only). +export type ShutterSound = 'on' | 'off'; +// LED flash for photo capture (never a live torch): 'auto' lets the capture +// pipeline decide from the metered scene, 'on' always fires, 'off' never does. +export type FlashMode = 'off' | 'auto' | 'on'; +export type StartupMode = 'default' | 'last'; +// One photo in the in-app session history (recent captures + library exports). +// uri = app-cache JPEG; assetId (when savedToLibrary) = the MediaLibrary asset +// so the trash button can remove the gallery copy too. +export interface PhotoEntry { + id: string; + uri: string; + recipeName: string; + savedToLibrary: boolean; + assetId?: string | null; +} +// w/h of the final upright photo for each non-full choice. +export const ASPECT_RATIO_W_H: Record, number> = { + '4:3': 3 / 4, + '3:2': 2 / 3, +}; + +// FRAME-tab CROP. Ratio convention is w:h (so 2:3 is an upright crop, 16:9 a +// wide one); 'free' is a user-dragged rectangle instead of a fixed shape. +export type CropRatio = 'none' | 'free' | '1:1' | '2:3' | '3:2' | '3:4' | '4:3' | '16:9'; +// Keep-rectangle as 0..1 fractions of the (post-rotation) source photo. +export interface CropRect { + x: number; + y: number; + w: number; + h: number; +} +// w/h of the kept area for each fixed crop choice. +export const CROP_W_H: Record, number> = { + '1:1': 1, + '2:3': 2 / 3, + '3:2': 3 / 2, + '3:4': 3 / 4, + '4:3': 4 / 3, + '16:9': 16 / 9, +}; +// FREE crop starts as a visible inset rectangle the user can immediately grab. +export const DEFAULT_CROP_RECT: CropRect = { x: 0.1, y: 0.1, w: 0.8, h: 0.8 }; +// Smallest FREE-crop side, in source fractions. +export const MIN_CROP_FRAC = 0.08; + +// Cinematic seasonal atmosphere grade. Not a fake re-light: it reads the +// photo's EXISTING exposure — warm tint + soft glow on areas already lit, +// cool tint in shade, plus a seasonal atmospheric haze/fog veil. +export type CinemaSeason = 'spring' | 'summer' | 'autumn' | 'winter'; + +export type BaseFilter = 'classic-neg' | 'classic-chrome' | 'provia' | 'velvia' | 'astia' | 'eterna' | 'monochrome' | 'leica' | 'leica-vivid' | 'none'; + +export interface ColorAdjustments { + exposure: number; // -10 to +10 (mapped to matrix multiplier or offset) + contrast: number; // -10 to +10 + saturation: number; // -10 to +10 + temperature: number; // 2500 to 10000 (Kelvin) + tint: number; // -10 to +10 + wbRed?: number; // Fuji WB fine-tune, Red axis (-9..9) — extra red-channel gain + wbBlue?: number; // Fuji WB fine-tune, Blue axis (-9..9) — extra blue-channel gain + highlight: number; // -10 to +10 (- pulls highlights down / roll, + brightens) + shadow: number; // -10 to +10 (+ lifts shadows, - deepens) + denoise: number; // -10 to +10 (+ = blur sigma; - = add film grain back) + clarity: number; // -10 to +10 (mapped to matrix convolution / bloom) + grain: number; // 0 to 10 (mapped to noise turbulence opacity/scale) + colorChrome: 'none' | 'weak' | 'strong'; // Chrome effect + colorChromeBlue: 'none' | 'weak' | 'strong'; // Chrome effect (Blue) + dynamicRange: 'auto' | 100 | 200 | 400; // Fuji-style DR tone curve (auto/100 = no extra curve) + sharpening: number; // -10 to +10 (+ = screen sharpen; - = soften the frame) + hdf?: number; // 0 to 10 (Highlight Diffusion Filter: glow on the highlights only) + vignette?: number; // 0 to 10 (darken the four corners; 0 = off) + vibrance?: number; // -10 to +10 (chroma-masked saturation: vivid colours move least) + exposureCompensation: number; // -3 to +3 EV. Camera: AE bias (hardware). Library: 2^EV matrix gain. +} + +export interface Recipe { + id: string; + name: string; + isCustom?: boolean; + baseFilter: BaseFilter; + adjustments: ColorAdjustments; + frameId?: FrameId; // e.g. 'none', 'polaroid', 'classic-white', 'cinematic', 'wallframe' + cinema?: CinemaSeason | null; // seasonal atmosphere grade (optional) + useGeotag: boolean; +} + +export interface GPSInfo { + latitude: number; + longitude: number; + locality?: string; + country?: string; + timestamp: number; + source?: 'exif' | 'mediaLibrary' | 'device'; // where the GPS data came from +} + +// Custom text watermark (WATERMARK tab -> CUSTOM WATERMARK chip): enabled flag, +// the typed text, and its anchor position as 0..1 fractions of the photo area +// (0.5/0.5 = centered). The anchor is the text's BASELINE plus a small 0.35em +// drop in both renderers — keep the formula in sync across Viewfinder + +// exportEngine. +export interface CustomWatermark { + enabled: boolean; + text: string; + x: number; + y: number; + // Styling set by the COLOR / SIZE / FONT chips. Absent on marks saved before + // those controls existed — the renderers then fall back to amber, 1x and the + // bundled Cousine face, i.e. exactly the previous look. + color?: string; + size?: number; // multiplier on the base 3.2%-of-width text size + font?: string | null; // OS font family; null/absent = bundled font +} + +// GPS watermark placement (WATERMARK tab drag + pinch): x/y are 0..1 fractions +// of the SAME area the custom mark uses (frame card / photo rect / screen), and +// the anchor is the top-left corner of the two-line block. size multiplies the +// base 3.2%-of-width size. Defaults reproduce the old fixed top-left look. +export interface GpsWatermark { + x: number; + y: number; + size: number; + // Styling set by the WATERMARK tab's PLACE NAME / TIME / COLOR chips. Absent + // on marks saved before those controls existed — the renderers then fall back + // to amber text with both lines shown, i.e. exactly the previous look. + color?: string; + showName?: boolean; + showTime?: boolean; +} diff --git a/docker/frontend/shared/utils/cinemaShader.ts b/docker/frontend/shared/utils/cinemaShader.ts new file mode 100644 index 0000000..8efc322 --- /dev/null +++ b/docker/frontend/shared/utils/cinemaShader.ts @@ -0,0 +1,138 @@ +import { CinemaSeason } from '../types'; + +// Cinematic seasonal atmosphere grade. Deliberately NOT a fake re-light: a +// still photo has no depth, so "moving the sun" would look plastic. Instead +// this reads the photo's EXISTING exposure and pushes it a certain way: +// - lit areas get the season's warm/golden tint + a soft sun glow (halation) +// - shadow side gets a complementary cool tint +// - a seasonal haze/fog veil blends toward a fog color (strongest in winter) +// That reads as "sunlight / morning mist" without inventing new light. +// +// Uniforms are 16 flat floats (scalar form keeps the JSX uniforms +// object and the worklet/export flat arrays in the exact same order): +// strength, contrast, saturation, haze, +// warmR, warmG, warmB, warmAmt, +// coolR, coolG, coolB, coolAmt, +// fogR, fogG, fogB, fogAmt +export const CINEMA_SKSL = ` +uniform shader src; +uniform float strength; +uniform float contrast; +uniform float saturation; +uniform float haze; +uniform float warmR; +uniform float warmG; +uniform float warmB; +uniform float warmAmt; +uniform float coolR; +uniform float coolG; +uniform float coolB; +uniform float coolAmt; +uniform float fogR; +uniform float fogG; +uniform float fogB; +uniform float fogAmt; +vec4 main(vec2 xy) { + vec4 c = src.eval(xy); + float t = clamp(dot(c.rgb, vec3(0.2126, 0.7152, 0.0722)), 0.0, 1.0); + vec3 r = c.rgb; + // Filmic-ish gentle contrast on the luma (keeps hue). + r = mix(r, clamp((r - 0.5) * (1.0 + contrast * strength) + 0.5, 0.0, 1.0), strength); + // Saturation. + float l = dot(r, vec3(0.2126, 0.7152, 0.0722)); + r = mix(vec3(l), r, mix(1.0, saturation, strength)); + // Season tints follow the EXISTING light: warm on lit areas, cool in shade. + float lit = smoothstep(0.30, 1.0, t); + float shad = 1.0 - smoothstep(0.0, 0.45, t); + vec3 warm = vec3(warmR, warmG, warmB); + vec3 cool = vec3(coolR, coolG, coolB); + r *= 1.0 + (warm - 1.0) * lit * warmAmt * strength; + r *= 1.0 + (cool - 1.0) * shad * coolAmt * strength; + // Soft sun glow at the brightest points (halation of existing highlights). + float glow = smoothstep(0.65, 1.0, t); + r += warm * glow * warmAmt * 0.45 * strength; + // Atmospheric haze/fog veil (lightest shadow side => misty air). + vec3 fog = vec3(fogR, fogG, fogB); + r = mix(r, fog, fogAmt * strength * (0.3 + 0.7 * shad)); + return vec4(clamp(r, 0.0, 1.0), c.a); +} +`; + +export interface CinemaUniformSet { + season: CinemaSeason; + // 16 flat floats — order matches CINEMA_SKSL declaration order above. + flat: number[]; + // Name-keyed, for the JSX form. + uniforms: Record; +} + +const ZERO: Record = {}; + +const SEASONS: Record = { + // Soft fresh light, gentle warm haze, barely-there cool in shade. + spring: { + name: 'CINEMA SPRING', + values: [ + 0.6, 0.08, 1.04, 0.18, + 1.02, 0.998, 0.95, 0.3, + 0.97, 0.995, 1.03, 0.12, + 0.95, 0.96, 0.98, 0.16, + ], + }, + // Golden hour-ish high sun: strong warm on lit, bluish shade, crisp. + summer: { + name: 'CINEMA SUMMER', + values: [ + 0.75, 0.2, 1.12, 0.1, + 1.08, 1.04, 0.88, 0.4, + 0.93, 0.97, 1.06, 0.2, + 1.0, 0.97, 0.88, 0.05, + ], + }, + // Amber low sun + warm atmospheric haze. + autumn: { + name: 'CINEMA AUTUMN', + values: [ + 0.8, 0.16, 1.1, 0.24, + 1.12, 1.02, 0.78, 0.42, + 1.02, 0.98, 0.88, 0.16, + 1.0, 0.93, 0.75, 0.18, + ], + }, + // Cold winter fog: desaturated, blue-grey, heavy mist, no warm sun. + winter: { + name: 'CINEMA WINTER', + values: [ + 0.65, -0.02, 0.88, 0.38, + 0.99, 0.995, 1.02, 0.06, + 0.94, 0.97, 1.08, 0.3, + 0.9, 0.93, 0.97, 0.45, + ], + }, +}; + +const KEYS = [ + 'strength', 'contrast', 'saturation', 'haze', + 'warmR', 'warmG', 'warmB', 'warmAmt', + 'coolR', 'coolG', 'coolB', 'coolAmt', + 'fogR', 'fogG', 'fogB', 'fogAmt', +] as const; + +export function getCinemaUniforms(season: CinemaSeason | null | undefined): CinemaUniformSet | null { + if (!season) return null; + const s = SEASONS[season]; + if (!s) return null; + const uniforms: Record = {}; + KEYS.forEach((k, i) => (uniforms[k] = s.values[i])); + return { season, flat: s.values, uniforms }; +} + +// Zeroed 16-float block — used to turn the cinema pass OFF in the worklet +// synchronizable (strength === 0 means inactive there). +export function cinemaOffFlat(): number[] { + return [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; +} + +export function cinemaIsActive(flat: number[] | null | undefined): boolean { + return !!flat && flat.length === 16 && flat[0] > 0; +} diff --git a/docker/frontend/shared/utils/colorUtils.ts b/docker/frontend/shared/utils/colorUtils.ts new file mode 100644 index 0000000..4a69a24 --- /dev/null +++ b/docker/frontend/shared/utils/colorUtils.ts @@ -0,0 +1,313 @@ +import { ColorAdjustments, BaseFilter } from '../types'; + +// Kelvin to RGB compensation for White Balance. +// The WB setting names the light the camera is told to expect, so its gain is +// the INVERSE of the Planckian (blackbody) colour at K: set 10000K under a +// daylight scene (5500K) and the camera thinks the light is bluer than it is +// and warms the picture up; set 2500K and it goes cool/blue. Pinned to unity at +// 5500K, the app's neutral reference. +// +// Two things a hand-drawn linear ramp gets wrong, both of them visible on skin: +// - moving the three channels independently (the old one lifted green 20% at +// 10000K) shifts LUMINANCE as well as colour, so a warm shot went yellow- +// green rather than orange. Here every gain is divided by its own Rec.709 +// luma, so a neutral pixel keeps its level and only the cast moves. +// - the excursion was arbitrary. These are the physical ratios of the sRGB +// blackbody colours, tamed by a square root: the raw linear-light ratio +// (2.1x on blue at 2500K) over-corrects once it lands on already tone-mapped +// pixels, which is exactly the domain a 4x5 colour matrix works in. +export function kelvinToRGB(kelvin: number): { r: number; g: number; b: number } { + const k = Math.min(10000, Math.max(2500, kelvin)); + const light = planckianSRGB(k); + const ref = planckianSRGB(5500); + return normalizeGainLuma([ + Math.sqrt(ref.r / Math.max(light.r, 1e-4)), + Math.sqrt(ref.g / Math.max(light.g, 1e-4)), + Math.sqrt(ref.b / Math.max(light.b, 1e-4)), + ]); +} + +// sRGB-encoded colour of a blackbody radiator at K, 0..1 (Tanner Helland's fit +// of the Planckian locus; exact enough from 1000K up, and exactly neutral where +// the app pins unity). Blue collapses to 0 below ~1900K, hence the guard in the +// caller. +function planckianSRGB(kelvin: number): { r: number; g: number; b: number } { + const t = kelvin / 100; + const r = t <= 66 ? 255 : 329.698727446 * Math.pow(t - 60, -0.1332047592); + const g = + t <= 66 + ? 99.4708025861 * Math.log(t) - 161.1195681661 + : 288.1221695283 * Math.pow(t - 60, -0.0755148492); + const b = t >= 66 ? 255 : t <= 19 ? 0 : 138.5177312231 * Math.log(t - 10) - 305.0447927307; + const norm = (v: number) => Math.min(1, Math.max(0, v / 255)); + return { r: norm(r), g: norm(g), b: norm(b) }; +} + +// Divide a set of channel gains by their own luma: the CAST moves, the exposure +// does not. Channels still clip where they must — this only keeps a neutral +// pixel sitting at its neutral level. +function normalizeGainLuma(gain: number[]): { r: number; g: number; b: number } { + const luma = 0.2126 * gain[0] + 0.7152 * gain[1] + 0.0722 * gain[2]; + const s = luma > 0 ? 1 / luma : 1; + return { r: gain[0] * s, g: gain[1] * s, b: gain[2] * s }; +} + +// Generate a 4x5 ColorMatrix (array of 20 floats) based on base style and +// adjustments. Tone-domain knobs (dynamicRange/highlight/shadow) and the +// exposure-compensation EV gain are intentionally NOT part of the matrix — +// dynamicRange/highlight/shadow run as a tone shader (see toneShader.ts), +// and EV is applied either as hardware AE bias (camera) or by the caller via +// applyExposureGain (library), never doubled. +export function getSkiaColorMatrix(baseFilter: BaseFilter, adj: ColorAdjustments): number[] { + const exposure = adj.exposure; + const contrast = adj.contrast; + const saturation = adj.saturation; + const temperature = adj.temperature; + const tint = adj.tint; + // colorChrome/colorChromeBlue are deliberately unused here — the chrome + // effects run in the tone shader (per-pixel chroma), not the matrix. + + // 1. Start with Identity Matrix + let matrix = [ + 1, 0, 0, 0, 0, + 0, 1, 0, 0, 0, + 0, 0, 1, 0, 0, + 0, 0, 0, 1, 0, + ]; + + // 2. Base Filters + if (baseFilter === 'classic-neg') { + // Classic Neg (Superia-like): restrained colour, higher contrast around + // mid-grey, reds a touch deeper. Its signature split tone — green/cyan + // darks drifting to warm brights — cannot live in a linear matrix (one cast + // would hit both ends), so it ships in FILM_TONE (toneShader.ts) instead. + matrix = [ + 0.9961, 0.0773, 0.0078, 0, -0.0406, + 0.0226, 1.0298, 0.0076, 0, -0.0300, + 0.0224, 0.0750, 0.9520, 0, -0.0247, + 0, 0, 0, 1, 0, + ]; + } else if (baseFilter === 'provia') { + // Provia (Standard): the reference stock — balanced, faithful, easy on the + // eye. Mild saturation and a shallow contrast lift around mid-grey, with no + // channel cast, so neutrals stay neutral and only the chroma deepens. + matrix = [ + 1.1492, -0.0901, -0.0091, 0, -0.0250, + -0.0268, 1.0859, -0.0091, 0, -0.0250, + -0.0268, -0.0901, 1.1669, 0, -0.0250, + 0, 0, 0, 1, 0, + ]; + } else if (baseFilter === 'velvia') { + // Velvia (Vivid): the loud one — saturation pushed very hard onto green, + // blue and red alike, on top of strong contrast with deep, dense darks. + // Saturated reds, skies and greens are meant to ride the clip; that hard + // edge is the look. + matrix = [ + 1.3895, -0.2540, -0.0256, 0, -0.0550, + -0.0772, 1.2355, -0.0261, 0, -0.0661, + -0.0775, -0.2603, 1.4756, 0, -0.0689, + 0, 0, 0, 1, 0, + ]; + } else if (baseFilter === 'classic-chrome') { + // Classic Chrome: magazine-reportage look. Saturation is pulled well down + // while the G/B rows are crossed so blue skies lean teal/cyan (green comes + // up towards blue, with blue kept high). The heavy shadow crush that goes + // with it is the `sh` entry in FILM_TONE — a matrix alone cannot deepen one + // end of the curve without dragging the rest. + matrix = [ + 0.7792, 0.2188, 0.0220, 0, -0.0100, + 0.0652, 0.7186, 0.2362, 0, -0.0100, + 0.0652, 0.3973, 0.5575, 0, -0.0100, + 0, 0, 0, 1, 0, + ]; + } else if (baseFilter === 'astia') { + // Astia (Soft): gentler than standard — contrast below neutral (shadows + // lift, highlights ease off) and colour held back, with a faint warm/pink + // bias that flatters skin. The outdoor-portrait stock. + matrix = [ + 0.9162, 0.0201, 0.0020, 0, 0.0428, + 0.0058, 0.9030, 0.0020, 0, 0.0386, + 0.0059, 0.0197, 0.8944, 0, 0.0400, + 0, 0, 0, 1, 0, + ]; + } else if (baseFilter === 'eterna') { + // Eterna: cinema — flat (low contrast), muted color, gentle highlight rolloff. + matrix = [ + 0.94, 0.02, 0.02, 0, 0.02, + 0.02, 0.96, 0.02, 0, 0.02, + 0.02, 0.02, 0.94, 0, 0.02, + 0, 0, 0, 1, 0, + ]; + } else if (baseFilter === 'leica') { + // LC STREETLIFE CLASSIC — the rangefinder character rather than a saturation + // push. What makes this file read as one is the separation BETWEEN the + // hues, so the deepening lives in the off-diagonal terms, not in a global + // chroma gain: + // - the three rows sum to 1.00/1.00/0.995, so greys stay exactly grey and + // every saturated colour gains ~5% through channel separation; Velvia + // pushes 39%, this stays quiet ("dậm đà, không gắt"). + // - a small uniform offset deepens the darks: solid shadows that keep their + // texture, no lifted black and no crushed toe. + // - red up / blue down with green held at 1.00 is the warm European cast: + // it lands on skin as warmth, never as the yellow-green a warm gain on + // all three channels would give. + matrix = [ + 1.060, -0.045, -0.010, 0, -0.010, + -0.030, 1.055, -0.025, 0, -0.008, + -0.025, -0.020, 1.040, 0, -0.005, + 0, 0, 0, 1, 0, + ]; + } else if (baseFilter === 'leica-vivid') { + // LC STREETLIFE VIVID — the same slanted channel separation pushed into the + // digital era: ~20% of chroma gain (still well under Velvia's 39%, so it + // sparkles instead of shouting) on the same warm R>G>B bias CLASSIC has, so + // VIVID is a brighter read of one look rather than a second, neutral one. + // No positive lift here: the brightness is the sim's own exposure value + // (defaultRecipes), which keeps the matrix inside 0..1 and pure white out of + // the clip — white lands at most at 1.000 on the red row. + matrix = [ + 1.125, -0.100, -0.010, 0, -0.015, + -0.055, 1.110, -0.055, 0, -0.005, + -0.050, -0.070, 1.100, 0, -0.005, + 0, 0, 0, 1, 0, + ]; + } else if (baseFilter === 'monochrome') { + // Acros: pure BT.709 grayscale, zero colour leakage, and NO gain or offset. + // The weights sum to 1.0 and the offset is 0, so this matrix is a straight + // luminance readout: nothing clips at either end and every grey step the + // sensor recorded survives. (An earlier 1.10x / -0.05 version plugged every + // black below ~5% and blew every white above ~95% — exactly the fine + // graduation this stock is prized for.) The deep black and the highlight + // shoulder live in FILM_TONE (toneShader.ts), where the curve can be shaped + // one end at a time without touching the midtones. (Grain/sharpness stay + // the user's call — a film sim here is the colour grade only.) + const r = 0.2126; + const g = 0.7152; + const b = 0.0722; + matrix = [ + r, g, b, 0, 0, + r, g, b, 0, 0, + r, g, b, 0, 0, + 0, 0, 0, 1, 0, + ]; + } + + // 3. Apply Saturation adjustment (standard color matrix transformation) + if (baseFilter !== 'monochrome' && saturation !== 0) { + const s = 1 + (saturation / 10) * 0.5; // -10 maps to 0.5x, +10 maps to 1.5x saturation + const invS = 1 - s; + const r = 0.213 * invS; + const g = 0.715 * invS; + const b = 0.072 * invS; + + const satMat = [ + r + s, g, b, 0, 0, + r, g + s, b, 0, 0, + r, g, b + s, 0, 0, + 0, 0, 0, 1, 0, + ]; + + matrix = multiplyMatrices(satMat, matrix); + } + + // 4. Color Chrome / Color Chrome FX Blue are NOT here: both deepen only the + // already-chromatic pixels, which needs per-pixel chroma (see TONE_SKSL) — a + // matrix gain on R/B is a global saturation push that also moves greys, and a + // blue-only gain tints the white point. `colorChrome`/`colorChromeBlue` are + // consumed by getToneUniforms instead. + + // 5. White Balance (Kelvin Temperature and Tint) — skip for monochrome to prevent color leakage + if (baseFilter !== 'monochrome') { + const rgbTemp = kelvinToRGB(temperature); + // Tint is the green↔magenta axis, and it is symmetric: + pushes magenta + // (red and blue up, green down), - pushes green (green up, red and blue + // down). The old version only ever ADDED a channel (magenta lifted R+B, + // green lifted G), so the knob brightened the frame instead of tinting it. + // Normalising the product keeps that at a tint shift, not an exposure one. + const tintMagenta = (tint / 10) * 0.08; + const wbGain = normalizeGainLuma([ + rgbTemp.r * (1 + tintMagenta), + rgbTemp.g * (1 - tintMagenta), + rgbTemp.b * (1 + tintMagenta), + ]); + + const wbMat = [ + wbGain.r, 0, 0, 0, 0, + 0, wbGain.g, 0, 0, 0, + 0, 0, wbGain.b, 0, 0, + 0, 0, 0, 1, 0, + ]; + matrix = multiplyMatrices(wbMat, matrix); + } + + // 5b. Fuji WB fine-tune Red/Blue axis (the CREATE modal's R/B offsets). These + // axes are independent channel gains on top of the Kelvin/tint preset: each + // step nudges its channel ~1.1% toward/away from neutral, matching the + // perceptible size of one Fuji fine-tune step (-9..+9). + const wbRed = adj.wbRed ?? 0; + const wbBlue = adj.wbBlue ?? 0; + if (baseFilter !== 'monochrome' && (wbRed !== 0 || wbBlue !== 0)) { + const shiftMat = [ + 1 + (wbRed / 9) * 0.1, 0, 0, 0, 0, + 0, 1, 0, 0, 0, + 0, 0, 1 + (wbBlue / 9) * 0.1, 0, 0, + 0, 0, 0, 1, 0, + ]; + matrix = multiplyMatrices(shiftMat, matrix); + } + + // 6. Exposure & Contrast — applied to ALL base filters (monochrome included) so + // both keep affecting mono shots. Contrast scales around mid-grey 0.5. + if (exposure !== 0 || contrast !== 0) { + const expScale = 1 + (exposure / 10) * 0.2; // -10 → 0.8x, +10 → 1.2x + const expOffset = (exposure / 10) * 0.15; + const conScale = 1 + (contrast / 10) * 0.25; // -10 → 0.75x, +10 → 1.25x + const conOffset = 0.5 * (1 - conScale); + const adjMat = [ + expScale * conScale, 0, 0, 0, expOffset + conOffset * expScale, + 0, expScale * conScale, 0, 0, expOffset + conOffset * expScale, + 0, 0, expScale * conScale, 0, expOffset + conOffset * expScale, + 0, 0, 0, 1, 0, + ]; + matrix = multiplyMatrices(adjMat, matrix); + } + + return matrix; +} + +// Apply photographic EV compensation (stops) as a linear gain: 2^EV on every +// RGB channel (offsets scale too, matching true exposure). Only used for +// stills that were NOT exposed with a hardware AE bias (library images). +export function applyExposureGain(matrix: number[], evStops: number): number[] { + if (!evStops) return matrix; + const s = Math.pow(2, evStops); + const gainMat = [ + s, 0, 0, 0, 0, + 0, s, 0, 0, 0, + 0, 0, s, 0, 0, + 0, 0, 0, 1, 0, + ]; + return multiplyMatrices(gainMat, matrix); +} + +// 4x5 Matrix multiplication utility: A * B +function multiplyMatrices(a: number[], b: number[]): number[] { + const result = new Array(20).fill(0); + for (let r = 0; r < 4; r++) { + for (let c = 0; c < 4; c++) { + result[r * 5 + c] = + a[r * 5 + 0] * b[0 * 5 + c] + + a[r * 5 + 1] * b[1 * 5 + c] + + a[r * 5 + 2] * b[2 * 5 + c] + + a[r * 5 + 3] * b[3 * 5 + c]; + } + // Handle the 5th column translation + result[r * 5 + 4] = + a[r * 5 + 0] * b[0 * 5 + 4] + + a[r * 5 + 1] * b[1 * 5 + 4] + + a[r * 5 + 2] * b[2 * 5 + 4] + + a[r * 5 + 3] * b[3 * 5 + 4] + + a[r * 5 + 4]; + } + return result; +} diff --git a/docker/frontend/shared/utils/defaultRecipes.ts b/docker/frontend/shared/utils/defaultRecipes.ts new file mode 100644 index 0000000..22cb950 --- /dev/null +++ b/docker/frontend/shared/utils/defaultRecipes.ts @@ -0,0 +1,303 @@ +import { Recipe, ColorAdjustments } from '../types'; + +// Fallback values for every adjustable knob. Old recipes/sessions persisted in +// AsyncStorage lack the newer fields — merge this under stored JSON to keep +// every adjustment well-defined (see storageUtils.getAllRecipes / App session). +export const DEFAULT_ADJUSTMENTS: ColorAdjustments = { + exposure: 0, + contrast: 0, + saturation: 0, + temperature: 5500, + tint: 0, + wbRed: 0, + wbBlue: 0, + highlight: 0, + shadow: 0, + denoise: 0, + clarity: 0, + grain: 0, + colorChrome: 'none', + colorChromeBlue: 'none', + dynamicRange: 'auto', + sharpening: 0, + exposureCompensation: 0, +}; + +// Standard Fuji film simulations. These are the fixed "look" presets shown at +// the front of the PRESETS chip row: tapping one applies the simulation — its +// baseFilter colour matrix plus whatever the stock itself carries outside the +// matrix (its FILM_TONE curve, and a grain amount when the grain IS the look, +// as on Acros). Every other knob still resets to its neutral default (0 / +// auto), so a sim is a clean starting point, never a baked recipe; any further +// look (DR, CC, contrast, more grain) is added by the user afterwards. +export interface FilmSim { + id: string; // Recipe-like id used only while the sim is the current selection + name: string; + baseFilter: Recipe['baseFilter']; + adjustments: ColorAdjustments; +} + +export const FILM_SIMS: FilmSim[] = [ + { id: 'sim-provia', name: 'PROVIPES', baseFilter: 'provia', adjustments: { ...DEFAULT_ADJUSTMENTS } }, + { id: 'sim-velvia', name: 'VELVIPES', baseFilter: 'velvia', adjustments: { ...DEFAULT_ADJUSTMENTS } }, + { id: 'sim-classic-chrome', name: 'CLASSIC CHRIPES', baseFilter: 'classic-chrome', adjustments: { ...DEFAULT_ADJUSTMENTS } }, + { id: 'sim-classic-neg', name: 'CLASSIC NEGIPES', baseFilter: 'classic-neg', adjustments: { ...DEFAULT_ADJUSTMENTS } }, + { id: 'sim-astia', name: 'ASTIPES', baseFilter: 'astia', adjustments: { ...DEFAULT_ADJUSTMENTS } }, + { id: 'sim-eterna', name: 'ETERNIPES', baseFilter: 'eterna', adjustments: { ...DEFAULT_ADJUSTMENTS } }, + { id: 'sim-acros', name: 'ACRIPES', baseFilter: 'monochrome', adjustments: { ...DEFAULT_ADJUSTMENTS, grain: 3 } }, + // The rangefinder digital look (M/Q/SL) is not a film stock, so it ships as its own + // pair instead of one look that has to compromise: + // CLASSIC — the stock character: deep solid darks, natural shadows, rich but + // quiet colour. Its micro-contrast is CLARITY (a local unsharp mask in both + // renderers), never a global saturation boost, and a whisker of grain + // keeps it feeling like film rather than digital sharpness. + // VIVID — the same channel separation opened up for the digital era: lifted + // exposure, lifted shadows, a little colour. It reads bright and flattering + // straight off the shutter, no editing pass needed. + // Both share the 'leica'/'leica-vivid' matrices; the tonal half is the sim's + // own adjustments, which every apply path now carries into the panel. + { id: 'sim-leica', name: 'LC STREETLIFE CLASSIC', baseFilter: 'leica', adjustments: { ...DEFAULT_ADJUSTMENTS, clarity: 2, grain: 1 } }, + { id: 'sim-leica-vivid', name: 'LC STREETLIFE VIVID', baseFilter: 'leica-vivid', adjustments: { ...DEFAULT_ADJUSTMENTS, exposure: 2, saturation: 2, clarity: 2, shadow: 1, highlight: -1 } }, +]; + +// Film sim → full Recipe (never persisted; sims are applied on the fly). +export function filmSimToRecipe(sim: FilmSim): Recipe { + return { + id: sim.id, + name: sim.name, + baseFilter: sim.baseFilter, + adjustments: { ...sim.adjustments }, + frameId: 'none', + useGeotag: true, + }; +} + +export const DEFAULT_RECIPES: Recipe[] = [ + { + id: 'classic-neg-default', + name: 'CLASSIC NEGIPES.', + baseFilter: 'classic-neg', + adjustments: { + exposure: 2, + contrast: 1, + saturation: -1, + temperature: 6300, + tint: 2, + highlight: -2, + shadow: 1, + denoise: 1, + clarity: 2, + grain: 4, + colorChrome: 'weak', + colorChromeBlue: 'none', + dynamicRange: 'auto', + sharpening: 0, + exposureCompensation: 0, + }, + frameId: 'none', + useGeotag: true, + }, + { + id: 'velvia-default', + name: 'VELVIPES VIVID', + baseFilter: 'velvia', + adjustments: { + exposure: 0, + contrast: 2, + saturation: 3, + temperature: 5500, + tint: 0, + highlight: 1, + shadow: -1, + denoise: 2, + clarity: 4, + grain: 1, + colorChrome: 'strong', + colorChromeBlue: 'none', + dynamicRange: 'auto', + sharpening: 0, + exposureCompensation: 0, + }, + frameId: 'none', + useGeotag: true, + }, + { + id: 'provia-default', + name: 'PROVIPES STD', + baseFilter: 'provia', + adjustments: { + exposure: 1, + contrast: 0, + saturation: 0, + temperature: 5000, + tint: 0, + highlight: 0, + shadow: 0, + denoise: 2, + clarity: 1, + grain: 2, + colorChrome: 'none', + colorChromeBlue: 'none', + dynamicRange: 'auto', + sharpening: 0, + exposureCompensation: 0, + }, + frameId: 'none', + useGeotag: true, + }, + { + id: 'acros-default', + name: 'ACRIPES MONO', + baseFilter: 'monochrome', + adjustments: { + exposure: 0, + // Low: the deep black comes from FILM_TONE's toe and the micro-contrast + // from CLARITY, both of which add separation WITHOUT flattening the grey + // ramp. The old 4 / highlight 3 / shadow -2 stack was three global + // contrast moves on top of a matrix that was already clipping — it + // bought its deep black by throwing away every step below 9% and every + // one above 91%. + contrast: 2, + saturation: 0, + temperature: 5000, + tint: 0, + highlight: 0, + shadow: 0, + denoise: 0, + clarity: 3, + grain: 5, + colorChrome: 'none', + colorChromeBlue: 'none', + dynamicRange: 'auto', + sharpening: 0, + exposureCompensation: 0, + }, + frameId: 'none', + useGeotag: true, + }, + { + id: 'retro-amber-default', + name: 'RETRO AMBER 🏮', + baseFilter: 'classic-neg', + adjustments: { + exposure: 3, + contrast: 2, + saturation: 1, + temperature: 7500, + tint: -2, + highlight: -3, + shadow: 2, + denoise: 3, + clarity: -2, + grain: 5, + colorChrome: 'strong', + colorChromeBlue: 'none', + dynamicRange: 'auto', + sharpening: 0, + exposureCompensation: 0, + }, + frameId: 'classic-white', + useGeotag: true, + }, + + { + id: 'cinema-spring-default', + name: 'CINEMA SPRING', + baseFilter: 'none', + adjustments: { + exposure: 0, + contrast: 1, + saturation: 1, + temperature: 5800, + tint: 0, + highlight: -1, + shadow: 1, + denoise: 2, + clarity: 1, + grain: 3, + colorChrome: 'none', + colorChromeBlue: 'none', + dynamicRange: 'auto', + sharpening: 0, + exposureCompensation: 0, + }, + frameId: 'none', + cinema: 'spring', + useGeotag: true, + }, + { + id: 'cinema-summer-default', + name: 'CINEMA SUMMER', + baseFilter: 'none', + adjustments: { + exposure: 0, + contrast: 2, + saturation: 2, + temperature: 6000, + tint: -1, + highlight: 1, + shadow: -1, + denoise: 1, + clarity: 2, + grain: 2, + colorChrome: 'weak', + colorChromeBlue: 'none', + dynamicRange: 'auto', + sharpening: 0, + exposureCompensation: 0, + }, + frameId: 'none', + cinema: 'summer', + useGeotag: true, + }, + { + id: 'cinema-autumn-default', + name: 'CINEMA AUTUMN', + baseFilter: 'none', + adjustments: { + exposure: 0, + contrast: 1, + saturation: 2, + temperature: 7200, + tint: -2, + highlight: -2, + shadow: 1, + denoise: 2, + clarity: 0, + grain: 4, + colorChrome: 'weak', + colorChromeBlue: 'none', + dynamicRange: 'auto', + sharpening: 0, + exposureCompensation: 0, + }, + frameId: 'none', + cinema: 'autumn', + useGeotag: true, + }, + { + id: 'cinema-winter-default', + name: 'CINEMA WINTER', + baseFilter: 'none', + adjustments: { + exposure: 1, + contrast: -1, + saturation: -2, + temperature: 4500, + tint: 2, + highlight: -2, + shadow: 2, + denoise: 3, + clarity: -2, + grain: 3, + colorChrome: 'none', + colorChromeBlue: 'weak', + dynamicRange: 'auto', + sharpening: 0, + exposureCompensation: 0, + }, + frameId: 'none', + cinema: 'winter', + useGeotag: true, + }, +]; diff --git a/docker/frontend/shared/utils/exifWrite.ts b/docker/frontend/shared/utils/exifWrite.ts new file mode 100644 index 0000000..14818d1 --- /dev/null +++ b/docker/frontend/shared/utils/exifWrite.ts @@ -0,0 +1,475 @@ +// Pure JPEG/EXIF writer — no imports, so the codec can be exercised directly. +// +// Skia re-encodes the photo, which drops the original EXIF block (see +// jpegDpi.ts). This rebuilds the APP1/EXIF segment: it reads the SOURCE +// photo's TIFF, keeps every tag it does not own, and stamps the fields this app +// is responsible for — software + version, capture date + zone, GPS, pixel +// dimensions — plus the baseline tags a conforming EXIF block must carry +// (ExifVersion, FlashPixVersion, ComponentsConfiguration, ColorSpace, Interop). +// Same in-place byte approach as jpegDpi.ts: no dependency, no native module. +// +// ponytail: IFD1 (the embedded thumbnail) and MakerNote are dropped. A carried +// thumbnail would show the PRE-export pixels, and MakerNote holds absolute +// offsets that a relocated TIFF invalidates. Read them back when a use case +// needs them. + +// Stamped into every file this app writes (kept in step with app.json). +export const SOFTWARE = 'RecipesCam 1.2'; + +export interface ExifStamp { + software?: string; + // Shutter instant. null/undefined = keep the loaded photo's own + // DateTimeOriginal and only fill it in when the source has none. + dateTime?: Date | null; + gps?: { latitude: number; longitude: number } | null; + // Place name shown on the Gallery frame (EXIF 0x889d). Stock photos carry it + // whenever the capture was geotagged; ours have to state it from the same + // info the watermark prints. + locationName?: string | null; + width?: number | null; + height?: number | null; + dpi?: number | null; + // Device identity for the Gallery watermark blob (ref. watermarkBlob). Only + // used when the source photo carries no blob of its own. + device?: DeviceIdentity | null; +} + +/** Raw device identity for the HyperOS Gallery watermark tag (0x889e). */ +export interface DeviceIdentity { + device: string; // ro.product.device codename, e.g. "aurora" + marketName: string; // ro.product.marketname, e.g. "Xiaomi 14 Ultra" + manufacturer: string; // Build.MANUFACTURER, e.g. "Xiaomi" + model: string; // Build.MODEL — fallback when marketName is empty +} + +interface Entry { + tag: number; + type: number; + count: number; + data: Uint8Array; + ref?: 'exif' | 'gps' | 'interop'; // IFD pointer entry — value patched during layout +} + +const BYTE = 1, ASCII = 2, SHORT = 3, LONG = 4, RATIONAL = 5, UNDEFINED = 7, SLONG = 9, SRATIONAL = 10; + +const TYPE_SIZE: Record = { + [BYTE]: 1, [ASCII]: 1, [SHORT]: 2, [LONG]: 4, [RATIONAL]: 8, + [UNDEFINED]: 1, [SLONG]: 4, [SRATIONAL]: 8, +}; + +// Tags this module owns, or that would dangle once the TIFF is rebuilt. +const DROP = new Set([ + 0x0112, // Orientation — the exported pixels are already upright (stamped back as 1) + 0x0100, 0x0101, // ImageWidth / ImageLength (stamped from the real export size) + 0x011a, 0x011b, 0x0128, // XResolution / YResolution / ResolutionUnit (stamped when dpi is set) + 0x0131, 0x0132, // Software / DateTime + 0x8769, 0x8825, 0xa005, 0x014a, // Exif / GPS / Interop / SubIFD pointers (rebuilt) + 0x0200, 0x0201, 0x0202, // thumbnail offset/length + 0x927c, // MakerNote — internal offsets do not survive relocation +]); + +// --- little-endian writers ------------------------------------------------- + +function u16w(b: Uint8Array, p: number, v: number): void { + b[p] = v & 0xff; + b[p + 1] = (v >> 8) & 0xff; +} + +function u32w(b: Uint8Array, p: number, v: number): void { + b[p] = v & 0xff; + b[p + 1] = (v >> 8) & 0xff; + b[p + 2] = (v >> 16) & 0xff; + b[p + 3] = (v >> 24) & 0xff; +} + +// --- entry builders -------------------------------------------------------- + +// ASCII-typed byte string, NUL-terminated as EXIF requires. The bytes are UTF-8: +// a place name ("TAM KỲ, ĐÀ NẴNG") or a market name can carry accents, and the +// charCodeAt & 0xff shortcut would mangle them into latin-1. Pure-ASCII text +// encodes byte-for-byte the same as before. +function asciiEntry(tag: number, text: string): Entry { + const bytes: number[] = []; + for (const ch of text) { + const cp = ch.codePointAt(0) as number; + if (cp < 0x80) bytes.push(cp); + else if (cp < 0x800) bytes.push(0xc0 | (cp >> 6), 0x80 | (cp & 0x3f)); + else if (cp < 0x10000) bytes.push(0xe0 | (cp >> 12), 0x80 | ((cp >> 6) & 0x3f), 0x80 | (cp & 0x3f)); + else bytes.push(0xf0 | (cp >> 18), 0x80 | ((cp >> 12) & 0x3f), 0x80 | ((cp >> 6) & 0x3f), 0x80 | (cp & 0x3f)); + } + bytes.push(0); + return { tag, type: ASCII, count: bytes.length, data: new Uint8Array(bytes) }; +} + +function shortEntry(tag: number, v: number): Entry { + const data = new Uint8Array(2); + u16w(data, 0, v); + return { tag, type: SHORT, count: 1, data }; +} + +// UNDEFINED value: a raw byte string whose length IS the count (ExifVersion etc). +function undefinedEntry(tag: number, text: string): Entry { + const data = new Uint8Array(text.length); + for (let i = 0; i < text.length; i++) data[i] = text.charCodeAt(i) & 0xff; + return { tag, type: UNDEFINED, count: data.length, data }; +} + +function longEntry(tag: number, v: number): Entry { + const data = new Uint8Array(4); + u32w(data, 0, v); + return { tag, type: LONG, count: 1, data }; +} + +function rationalEntry(tag: number, num: number, den: number, type = RATIONAL): Entry { + const data = new Uint8Array(8); + u32w(data, 0, num); + u32w(data, 4, den); + return { tag, type, count: 1, data }; +} + +// GPS coordinate: 3 RATIONALs (degrees, minutes, seconds), denominator 10000 +// for the seconds so ~0.1 m survives. +function gpsCoordEntry(tag: number, value: number): Entry { + const abs = Math.abs(value); + let deg = Math.floor(abs); + let min = Math.floor((abs - deg) * 60); + let sec = Math.round(((abs - deg) * 60 - min) * 60 * 10000); + if (sec >= 600000) { sec = 0; min += 1; } + if (min >= 60) { min = 0; deg += 1; } + const data = new Uint8Array(24); + u32w(data, 0, deg); u32w(data, 4, 1); + u32w(data, 8, min); u32w(data, 12, 1); + u32w(data, 16, sec); u32w(data, 20, 10000); + return { tag, type: RATIONAL, count: 3, data }; +} + +// A big-endian source TIFF hands us big-endian value bytes; they are re-emitted +// into a little-endian TIFF, so every numeric value must be swapped first +// (ASCII/BYTE/UNDEFINED are byte strings and stay as they are). +function swapToLittleEndian(data: Uint8Array, type: number): Uint8Array { + const unit = type === SHORT ? 2 : type === LONG || type === SLONG ? 4 : type === RATIONAL || type === SRATIONAL ? 4 : 0; + if (!unit) return data; + // `new Uint8Array(data)` (not data.slice()) — the input may be a Node Buffer, + // whose slice() aliases the source and would corrupt the caller's bytes. + const out = new Uint8Array(data); + for (let p = 0; p + unit <= out.length; p += unit) { + for (let i = 0; i < unit >> 1; i++) { + const a = out[p + i]; + out[p + i] = out[p + unit - 1 - i]; + out[p + unit - 1 - i] = a; + } + } + return out; +} + +function asciiValue(e: Entry | undefined): string | null { + if (!e || e.type !== ASCII || e.data.length === 0) return null; + let s = ''; + for (let i = 0; i < e.data.length && e.data[i] !== 0; i++) s += String.fromCharCode(e.data[i]); + return s || null; +} + +// "YYYY:MM:DD HH:MM:SS" — the EXIF date format (colons, not dashes). +function exifDate(d: Date): string { + const p = (n: number) => String(n).padStart(2, '0'); + return `${d.getFullYear()}:${p(d.getMonth() + 1)}:${p(d.getDate())} ${p(d.getHours())}:${p(d.getMinutes())}:${p(d.getSeconds())}`; +} + +// "±HH:MM" — the OffsetTime* tag format. Framing tools read it to place a +// capture correctly, and it is what makes the date unambiguous without GPS. +function tzOffset(d: Date): string { + const mins = -d.getTimezoneOffset(); + const a = Math.abs(mins); + const p = (n: number) => String(n).padStart(2, '0'); + return `${mins < 0 ? '-' : '+'}${p(Math.floor(a / 60))}:${p(a % 60)}`; +} + +// The watermark blob HyperOS Gallery reads back (EXIF tag 0x889e) before it +// will open its watermark editor: without it, a photo edited here fails with +// "cannot recognize the parameters". Only the stock camera app writes this tag +// — CameraX/HAL never does — so a camera capture has to synthesize it from the +// same props the stock app derives it from. Shape matches the stock bytes: +// {"buildDevice":"aurora",...,"waterLogo":"XIAOMI","waterName":"14 Ultra"}. +function watermarkBlob(d: DeviceIdentity): string { + const brand = d.manufacturer.trim(); + const market = d.marketName.trim(); + // "Xiaomi 14 Ultra" -> "14 Ultra": Gallery prints waterName, not the brand. + const waterName = market.toLowerCase().startsWith(`${brand.toLowerCase()} `) + ? market.slice(brand.length + 1) + : market || d.model; + return JSON.stringify({ + buildDevice: d.device, + customize: '', + cvLensName: '', + filterName: '', + livephotoInfo: '', + version: 2, + waterLogo: brand.toUpperCase(), + waterName, + }); +} + +// --- parse ----------------------------------------------------------------- + +interface ParsedTiff { + ifd0: Entry[]; + exif: Entry[]; + gps: Entry[]; + interop: Entry[]; +} + +function parseTiff(tiff: Uint8Array): ParsedTiff { + const none: ParsedTiff = { ifd0: [], exif: [], gps: [], interop: [] }; + if (tiff.length < 8) return none; + const le = tiff[0] === 0x49 && tiff[1] === 0x49; + const be = tiff[0] === 0x4d && tiff[1] === 0x4d; + if (!le && !be) return none; + + const u16 = (p: number): number => (le ? tiff[p] | (tiff[p + 1] << 8) : (tiff[p] << 8) | tiff[p + 1]); + const u32 = (p: number): number => + le + ? (tiff[p] | (tiff[p + 1] << 8) | (tiff[p + 2] << 16) | (tiff[p + 3] << 24)) >>> 0 + : ((tiff[p] << 24) | (tiff[p + 1] << 16) | (tiff[p + 2] << 8) | tiff[p + 3]) >>> 0; + + const read = (off: number): { list: Entry[]; ptr: Record } => { + const out: { list: Entry[]; ptr: Record } = { list: [], ptr: {} }; + if (off <= 0 || off + 2 > tiff.length) return out; + const n = u16(off); + for (let i = 0; i < n; i++) { + const p = off + 2 + i * 12; + if (p + 12 > tiff.length) break; + const tag = u16(p); + const type = u16(p + 2); + const count = u32(p + 4); + if (tag === 0x8769 || tag === 0x8825 || tag === 0xa005) { + out.ptr[tag] = u32(p + 8); // sub-IFD pointers are rebuilt, never copied + continue; + } + const size = TYPE_SIZE[type]; + if (!size || DROP.has(tag)) continue; + const len = size * count; + if (len <= 0 || len > 4 * 1024 * 1024) continue; // ignore absurd/overflowing counts + if (len <= 4) { + out.list.push({ tag, type, count, data: be ? swapToLittleEndian(tiff.slice(p + 8, p + 8 + len), type) : tiff.slice(p + 8, p + 8 + len) }); + } else { + const o = u32(p + 8); + if (o + len > tiff.length) continue; + const raw = tiff.slice(o, o + len); + out.list.push({ tag, type, count, data: be ? swapToLittleEndian(raw, type) : raw }); + } + } + return out; + }; + + const a = read(u32(4)); + const exifRead = a.ptr[0x8769] ? read(a.ptr[0x8769]) : { list: [], ptr: {} }; + return { + ifd0: a.list, + exif: exifRead.list, + gps: a.ptr[0x8825] ? read(a.ptr[0x8825]).list : [], + interop: exifRead.ptr[0xa005] ? read(exifRead.ptr[0xa005]).list : [], + }; +} + +// Locate the APP1/EXIF segment: start/end bound the whole segment, tiff the +// payload after the "Exif\0\0" signature. +function findExifSegment(jpeg: Uint8Array): { start: number; end: number; tiff: Uint8Array } | null { + if (jpeg.length < 4 || jpeg[0] !== 0xff || jpeg[1] !== 0xd8) return null; + let i = 2; + while (i + 1 < jpeg.length && jpeg[i] === 0xff) { + const start = i; + while (jpeg[i] === 0xff) i++; + const marker = jpeg[i]; + i++; + if (marker === 0xd9 || marker === 0xda) return null; // EOI / SOS — headers only + if (marker === 0x01 || (marker >= 0xd0 && marker <= 0xd7)) continue; + if (i + 2 > jpeg.length) return null; + const len = (jpeg[i] << 8) | jpeg[i + 1]; + if (len < 2 || i + len > jpeg.length) return null; + const dataStart = i + 2; + if ( + marker === 0xe1 && len >= 8 && + jpeg[dataStart] === 0x45 && jpeg[dataStart + 1] === 0x78 && jpeg[dataStart + 2] === 0x69 && + jpeg[dataStart + 3] === 0x66 && jpeg[dataStart + 4] === 0x00 && jpeg[dataStart + 5] === 0x00 + ) { + return { start, end: i + len, tiff: jpeg.subarray(dataStart + 6, i + len) }; + } + i += len; + } + return null; +} + +// --- serialize ------------------------------------------------------------- + +function serializeTiff(ifd0: Entry[], exif: Entry[], gps: Entry[], interop: Entry[]): Uint8Array { + const ifdLen = (n: number) => 2 + n * 12 + 4; + const outLen = (es: Entry[]) => { + let s = 0; + for (const e of es) if (e.data.length > 4) s += (e.data.length + 1) & ~1; + return s; + }; + + const o0 = 8; + const d0 = o0 + ifdLen(ifd0.length); + const oE = d0 + outLen(ifd0); + const dE = oE + ifdLen(exif.length); + const oG = dE + outLen(exif); + const dG = oG + ifdLen(gps.length); + const oI = dG + outLen(gps); + const dI = oI + ifdLen(interop.length); + const t = new Uint8Array(dI + outLen(interop)); + + t[0] = 0x49; t[1] = 0x49; t[2] = 0x2a; t[3] = 0x00; // "II", 42 + u32w(t, 4, o0); + + const writeIfd = (off: number, es: Entry[], dataOff: number): void => { + u16w(t, off, es.length); + let c = dataOff; + es.forEach((e, i) => { + const p = off + 2 + i * 12; + u16w(t, p, e.tag); + u16w(t, p + 2, e.type); + u32w(t, p + 4, e.count); + if (e.ref === 'exif') { u32w(t, p + 8, oE); return; } + if (e.ref === 'gps') { u32w(t, p + 8, oG); return; } + if (e.ref === 'interop') { u32w(t, p + 8, oI); return; } + if (e.data.length <= 4) { t.set(e.data, p + 8); return; } + u32w(t, p + 8, c); + t.set(e.data, c); + c += (e.data.length + 1) & ~1; // values start on a word boundary + }); + u32w(t, off + 2 + es.length * 12, 0); // no IFD1 + }; + + writeIfd(o0, ifd0, d0); + writeIfd(oE, exif, dE); + writeIfd(oG, gps, dG); + writeIfd(oI, interop, dI); + return t; +} + +/** + * Rebuilds the JPEG's APP1/EXIF block: `sourceJpeg` (the loaded photo) donates + * every tag this module does not own; `stamp` sets the ones the app owns. + * Returns a new array; input is never modified. A JPEG that cannot take the + * segment (not a JPEG, EXIF larger than 64 KB) comes back untouched. + */ +export function writeJpegExif(jpeg: Uint8Array, stamp: ExifStamp, sourceJpeg?: Uint8Array | null): Uint8Array { + if (jpeg.length < 4 || jpeg[0] !== 0xff || jpeg[1] !== 0xd8) return jpeg; + + // An unreadable source (content:// uri, IO error) must not wipe the tags the + // jpeg already carries, so fall back to the jpeg's own EXIF block. + const src = findExifSegment(sourceJpeg && sourceJpeg.length > 4 ? sourceJpeg : jpeg); + const parsed = src ? parseTiff(src.tiff) : { ifd0: [], exif: [], gps: [], interop: [] }; + const ifd0 = new Map(parsed.ifd0.map((e) => [e.tag, e])); + const exif = new Map(parsed.exif.map((e) => [e.tag, e])); + const gps = new Map(parsed.gps.map((e) => [e.tag, e])); + const interop = new Map(parsed.interop.map((e) => [e.tag, e])); + + const now = exifDate(stamp.dateTime ?? new Date()); + const keepSourceDate = !stamp.dateTime; + const sourceDate = asciiValue(ifd0.get(0x0132)) ?? asciiValue(exif.get(0x9003)); + const dateStr = keepSourceDate ? sourceDate ?? now : now; + + if (!keepSourceDate || !ifd0.has(0x0132)) ifd0.set(0x0132, asciiEntry(0x0132, dateStr)); + if (!keepSourceDate || !exif.has(0x9003)) exif.set(0x9003, asciiEntry(0x9003, dateStr)); + if (!keepSourceDate || !exif.has(0x9004)) exif.set(0x9004, asciiEntry(0x9004, dateStr)); + if (!keepSourceDate) { + // We own the timestamp for camera shots, so we also state its zone. + const off = asciiEntry(0x9010, tzOffset(stamp.dateTime as Date)); + ifd0.set(0x9010, off); // OffsetTime (0th IFD) + exif.set(0x9011, { ...off, tag: 0x9011 }); // OffsetTimeOriginal + exif.set(0x9012, { ...off, tag: 0x9012 }); // OffsetTimeDigitized + } + ifd0.set(0x0131, asciiEntry(0x0131, stamp.software ?? SOFTWARE)); + ifd0.set(0x0112, shortEntry(0x0112, 1)); // upright — the pixels are already straight + + // Baseline tags a conforming EXIF block is expected to carry. Framing apps + // reject a file that omits them, and HALs (the emulator's especially) do not + // always supply them, so fill the gaps and keep whatever the source had. + if (!exif.has(0x9000)) exif.set(0x9000, undefinedEntry(0x9000, '0230')); // ExifVersion + if (!exif.has(0xa000)) exif.set(0xa000, undefinedEntry(0xa000, '0100')); // FlashPixVersion + if (!exif.has(0x9101)) exif.set(0x9101, { tag: 0x9101, type: UNDEFINED, count: 4, data: new Uint8Array([1, 2, 3, 0]) }); // YCbCr + if (!exif.has(0xa001)) exif.set(0xa001, shortEntry(0xa001, 65535)); // ColorSpace = uncalibrated + if (!interop.has(0x0001)) interop.set(0x0001, asciiEntry(0x0001, 'R98')); // InteropIndex + if (!interop.has(0x0002)) interop.set(0x0002, undefinedEntry(0x0002, '0100')); // InteropVersion + + if (stamp.width && stamp.height) { + ifd0.set(0x0100, longEntry(0x0100, stamp.width)); + ifd0.set(0x0101, longEntry(0x0101, stamp.height)); + exif.set(0xa002, longEntry(0xa002, stamp.width)); // PixelXDimension + exif.set(0xa003, longEntry(0xa003, stamp.height)); // PixelYDimension + } + + if (stamp.dpi && stamp.dpi > 0) { + ifd0.set(0x011a, rationalEntry(0x011a, stamp.dpi, 1)); + ifd0.set(0x011b, rationalEntry(0x011b, stamp.dpi, 1)); + ifd0.set(0x0128, shortEntry(0x0128, 2)); // resolution unit = inch + } + + // HyperOS Gallery refuses to open its watermark editor without 0x889e, and a + // CameraX capture never carries it. Never overwrite a blob the source has. + const xiaomi = !!stamp.device && /xiaomi|redmi|poco/i.test(stamp.device.manufacturer); + if (stamp.device && xiaomi && !exif.has(0x889e)) { + exif.set(0x889e, asciiEntry(0x889e, watermarkBlob(stamp.device))); + } + + // Stock Xiaomi photos name the device in 0x9a00 and in Model (0x0110) as the + // MARKET name ("Xiaomi 14 Ultra"); the framing tool matches those against its + // known-device list and reports an unknown device otherwise. A CameraX capture + // only has the raw codename, so state the market name. A source that already + // carries 0x9a00 is a stock photo of some other device — leave it alone. + if (stamp.device && xiaomi && !ifd0.has(0x9a00)) { + const market = (stamp.device.marketName || stamp.device.model).trim(); + if (market) { + ifd0.set(0x9a00, asciiEntry(0x9a00, market)); + ifd0.set(0x0110, asciiEntry(0x0110, market)); + } + } + + // Place name the frame prints (0x889d) — stock writes it in both IFDs. + const place = stamp.locationName?.trim(); + if (place) { + if (!ifd0.has(0x889d)) ifd0.set(0x889d, asciiEntry(0x889d, place)); + if (!exif.has(0x889d)) exif.set(0x889d, asciiEntry(0x889d, place)); + } + + if (stamp.gps) { + const { latitude: lat, longitude: lon } = stamp.gps; + gps.set(0x0001, asciiEntry(0x0001, lat >= 0 ? 'N' : 'S')); + gps.set(0x0002, gpsCoordEntry(0x0002, lat)); + gps.set(0x0003, asciiEntry(0x0003, lon >= 0 ? 'E' : 'W')); + gps.set(0x0004, gpsCoordEntry(0x0004, lon)); + } + + const list0 = [...ifd0.values()]; + if (exif.size > 0) list0.push({ tag: 0x8769, type: LONG, count: 1, data: new Uint8Array(4), ref: 'exif' }); + if (gps.size > 0) list0.push({ tag: 0x8825, type: LONG, count: 1, data: new Uint8Array(4), ref: 'gps' }); + list0.sort((a, b) => a.tag - b.tag); + const listE = [...exif.values()].sort((a, b) => a.tag - b.tag); + const listG = [...gps.values()].sort((a, b) => a.tag - b.tag); + if (interop.size > 0) listE.push({ tag: 0xa005, type: LONG, count: 1, data: new Uint8Array(4), ref: 'interop' }); + listE.sort((a, b) => a.tag - b.tag); + const listI = [...interop.values()].sort((a, b) => a.tag - b.tag); + + const tiff = serializeTiff(list0, listE, listG, listI); + const payload = 2 + 6 + tiff.length; + if (payload > 0xffff) return jpeg; // APP1 length is a 16-bit field + + const app1 = new Uint8Array(2 + payload); + app1[0] = 0xff; app1[1] = 0xe1; + // APP1 length is a big-endian 16-bit field (the TIFF block after it is little-endian) + app1[2] = (payload >> 8) & 0xff; + app1[3] = payload & 0xff; + app1.set([0x45, 0x78, 0x69, 0x66, 0x00, 0x00], 4); // "Exif\0\0" + app1.set(tiff, 10); + + const old = findExifSegment(jpeg); + const head = old ? jpeg.subarray(0, old.start) : jpeg.subarray(0, 2); + const tail = old ? jpeg.subarray(old.end) : jpeg.subarray(2); + const out = new Uint8Array(head.length + app1.length + tail.length); + out.set(head, 0); + out.set(app1, head.length); + out.set(tail, head.length + app1.length); + return out; +} diff --git a/docker/frontend/shared/utils/frameUtils.ts b/docker/frontend/shared/utils/frameUtils.ts new file mode 100644 index 0000000..f4290d9 --- /dev/null +++ b/docker/frontend/shared/utils/frameUtils.ts @@ -0,0 +1,204 @@ +import { SkCanvas, SkPaint, Skia, BlendMode } from '@shopify/react-native-skia'; +import { FrameId } from '../types'; + +export interface FrameDefinition { + id: FrameId; + name: string; +} + +export const FRAMES: FrameDefinition[] = [ + { id: 'none', name: 'NO FRAME' }, + { id: 'classic-white', name: 'CLASSIC BORDER' }, + { id: 'polaroid', name: 'RETRO INSTANT' }, + { id: 'wallframe', name: 'WALL FRAME' }, +]; + +// Real Polaroid print proportions (88 x 107 mm card, 79 x 79 mm image print). +// All fractions are relative to the CARD WIDTH so the layout scales to any +// pixel size while keeping the physical look. +export const POLAROID_CARD = { + // Horizontal + top margin: (88 - 79)/2 / 88 mm each side. + sideMargin: (88 - 79) / 2 / 88, // ≈ 0.05114 + // Bottom deck: remaining height of the 107 mm card below the 79 mm print, + // minus the top margin: (107 - 79 - 4.5) / 88 mm. + bottomDeck: (107 - 79 - (88 - 79) / 2) / 88, // ≈ 0.26705 +} as const; +// Image window width as a fraction of card width (79 / 88 mm). +export const POLAROID_WIN_W = 1 - 2 * POLAROID_CARD.sideMargin; // ≈ 0.89773 + +export interface PolaroidLayout { + card: { x: number; y: number; w: number; h: number }; + window: { x: number; y: number; w: number; h: number }; +} + +// Wall frame: the PNG artwork lives at the project root (wallframe.png, +// 4000x3117 RGBA, transparent window cols 675..3322 x rows 675..2441). Only the +// geometry lives here; the bitmap itself is drawn by the caller. The phone is +// portrait, so the app shows the artwork ROTATED 90° CW (original top edge → +// right edge): a portrait frame whose transparent window is a fixed 2:3 +// vertical opening. The photo is cover-cropped (center) into that opening — the +// mat of a real frame hides the print edges. WALLFRAME_W/H and WALLFRAME_WIN +// are the rotated display orientation; the affine in wallframeLayout maps the +// source PNG pixels onto the frame rect so the drawn opening and the photo +// window always line up. +export const WALLFRAME_PNG_W = 4000; // source PNG width +export const WALLFRAME_PNG_H = 3117; // source PNG height +export const WALLFRAME_W = 3117; // rotated display width (= PNG height) +export const WALLFRAME_H = 4000; // rotated display height (= PNG width) +// Transparent window as fractions of the rotated image, derived from the same +// affine used to draw the bitmap: X = w - (y/3117)w, Y = (x/4000)h. +export const WALLFRAME_WIN = { + left: 676 / 3117, // ≈ 0.21688 + top: 675 / 4000, // 0.16875 + w: 1766 / 3117, // ≈ 0.56657 + h: 2647 / 4000, // 0.66175 +} as const; +// Landscape hang: the PNG's OWN orientation, so nothing is rotated — the +// canvas is the artwork (4000x3117) and the opening is the source window as +// fractions of it. Same 3:2 mat opening, hung the other way round. +export const WALLFRAME_LAND_W = WALLFRAME_PNG_W; // 4000 +export const WALLFRAME_LAND_H = WALLFRAME_PNG_H; // 3117 +export const WALLFRAME_LAND_WIN = { + left: 675 / WALLFRAME_PNG_W, // 0.16875 + top: 675 / WALLFRAME_PNG_H, // ≈ 0.21688 + w: 2647 / WALLFRAME_PNG_W, // 0.66175 + h: 1766 / WALLFRAME_PNG_H, // ≈ 0.56657 +} as const; + +export interface WallframeLayout { + frame: { x: number; y: number; w: number; h: number }; + window: { x: number; y: number; w: number; h: number }; + // Row-major Skia matrix that rotates the source PNG (WALLFRAME_PNG_W/H) 90° CW + // into the frame rect. Feed it to (viewfinder) or concat it on + // the canvas (export) before drawing the bitmap. + drawMatrix: number[]; +} + +// Geometry of the wall-frame inside an arbitrary container. The frame is +// contain-fitted ('contain', centered, letterboxed) for the live viewfinder, +// or fills the container exactly ('fill', export canvas already at frame size). +// `landscape` hangs the artwork the other way round: same PNG, no rotation. +export function wallframeLayout( + contW: number, + contH: number, + mode: 'contain' | 'fill' = 'contain', + landscape = false +): WallframeLayout { + const FW = landscape ? WALLFRAME_LAND_W : WALLFRAME_W; + const FH = landscape ? WALLFRAME_LAND_H : WALLFRAME_H; + const WIN = landscape ? WALLFRAME_LAND_WIN : WALLFRAME_WIN; + let frameW: number; + let frameH: number; + if (mode === 'fill') { + frameW = contW; + frameH = contH; + } else { + const s = Math.min(contW / FW, contH / FH); + frameW = FW * s; + frameH = FH * s; + } + const fx = (contW - frameW) / 2; + const fy = (contH - frameH) / 2; + const scale = frameW / FW; // == frameH / FH + return { + frame: { x: fx, y: fy, w: frameW, h: frameH }, + window: { + x: fx + WIN.left * frameW, + y: fy + WIN.top * frameH, + w: WIN.w * frameW, + h: WIN.h * frameH, + }, + // Portrait: the source PNG must be turned 90° CW onto the canvas. + // Landscape: it already sits the right way up — scale and centre it. + drawMatrix: landscape + ? [scale, 0, fx, 0, scale, fy, 0, 0, 1] + : [0, -scale, fx + frameW, scale, 0, fy, 0, 0, 1], + }; +} + +// Geometry of a retro-polaroid card inside an arbitrary container. The photo is +// NEVER cropped: the image window keeps the photo aspect ratio (photoW/photoH) +// and the card grows/shrinks around it, so the whole picture always fits the +// window. 'contain' fits the full card inside the container (centered, with +// letterbox bars); 'fill' sizes the container exactly to the natural card +// proportions (used at export where the canvas IS the card). +export function polaroidLayout( + contW: number, + contH: number, + photoAspect: number, // photo width / photo height (>1 = landscape) + mode: 'contain' | 'fill' = 'contain' +): PolaroidLayout { + const S = POLAROID_CARD; + // Height of the card once the photo window (width WIN_W of the card width, + // height = that / photoAspect) is stacked below the top margin and above the + // bottom deck: cardH = cardW*(sideMargin + bottomDeck) + cardW*WIN_W/photoAspect + const naturalAspect = S.sideMargin + S.bottomDeck + POLAROID_WIN_W / photoAspect; // cardH/cardW + + let cardW: number; + let cardH: number; + if (mode === 'fill') { + cardW = contW; + cardH = contH; + } else { + cardW = Math.min(contW, contH / naturalAspect); + cardH = cardW * naturalAspect; + } + const cx = (contW - cardW) / 2; + const cy = (contH - cardH) / 2; + + const top = S.sideMargin * cardW; + const side = S.sideMargin * cardW; + let winW = POLAROID_WIN_W * cardW; + let winH = winW / photoAspect; + // Container aspect may leave less vertical room than the natural card (e.g. + // phone camera feed) — shrink the window to the available space instead of + // overflowing the card. + const maxWinH = cardH - top - S.bottomDeck * cardW; + if (winH > maxWinH) { + winH = maxWinH; + winW = winH * photoAspect; + } + return { + card: { x: cx, y: cy, w: cardW, h: cardH }, + window: { x: cx + (cardW - winW) / 2, y: cy + top, w: winW, h: winH }, + }; +} + +export function drawFrameOnCanvas( + canvas: SkCanvas, + width: number, + height: number, + frameId: FrameId +) { + if (frameId === 'none') return; + + const paint = Skia.Paint(); + + if (frameId === 'classic-white') { + // A clean white border around the picture + paint.setColor(Skia.Color('#ffffff')); + paint.setStyle(0); // Fill + + const borderWidth = Math.min(width, height) * 0.05; // 5% border + + // Top border + canvas.drawRect(Skia.XYWHRect(0, 0, width, borderWidth), paint); + // Bottom border + canvas.drawRect(Skia.XYWHRect(0, height - borderWidth, width, borderWidth), paint); + // Left border + canvas.drawRect(Skia.XYWHRect(0, 0, borderWidth, height), paint); + // Right border + canvas.drawRect(Skia.XYWHRect(width - borderWidth, 0, borderWidth, height), paint); + } else if (frameId === 'cinematic') { + // Cinematic black bars on top and bottom + paint.setColor(Skia.Color('#000000')); + paint.setStyle(0); // Fill + + const barHeight = height * 0.12; // 12% cinematic letterbox height + + // Top letterbox + canvas.drawRect(Skia.XYWHRect(0, 0, width, barHeight), paint); + // Bottom letterbox + canvas.drawRect(Skia.XYWHRect(0, height - barHeight, width, barHeight), paint); + } +} diff --git a/docker/frontend/shared/utils/jpegDpi.ts b/docker/frontend/shared/utils/jpegDpi.ts new file mode 100644 index 0000000..976b211 --- /dev/null +++ b/docker/frontend/shared/utils/jpegDpi.ts @@ -0,0 +1,126 @@ +// Pure JPEG DPI metadata patcher. +// Re-encoding a JPEG through Skia drops the original EXIF/JFIF headers, so the +// DPI stamp ("72 dpi") is lost or defaulted. DPI is header metadata only — pixel +// dimensions never change — but print pipelines read it, so this rewrites the +// JFIF density (and EXIF XResolution/YResolution when present) to the requested +// value. All writes are same-length, so no segment offsets move. +// +// If the JPEG carries neither JFIF nor EXIF, a minimal JFIF APP0 segment is +// inserted right after SOI. + +function u16(bytes: Uint8Array, p: number): number { + return (bytes[p] << 8) | bytes[p + 1]; +} +function setU16(bytes: Uint8Array, p: number, v: number): void { + bytes[p] = (v >> 8) & 0xff; + bytes[p + 1] = v & 0xff; +} + +// Patches `bytes` in place for JFIF; returns the (possibly rebuilt) array. +export function patchJpegDpi(input: Uint8Array, dpi: number): Uint8Array { + if (input.length < 2 || input[0] !== 0xff || input[1] !== 0xd8) return input; + const bytes = input.slice(); // work on a copy; insertion path rebuilds anyway + const dpiLo = dpi & 0xff; + const dpiHi = (dpi >> 8) & 0xff; + + let sawJfif = false; + let sawExif = false; + + let i = 2; + while (i + 1 < bytes.length && bytes[i] === 0xff) { + while (bytes[i] === 0xff) i++; + const marker = bytes[i]; + i++; + if (marker === 0xd9 || marker === 0xda) break; // EOI / SOS — headers only + if (marker === 0x01 || (marker >= 0xd0 && marker <= 0xd7)) continue; + if (i + 2 > bytes.length) break; + const len = u16(bytes, i); + if (len < 2 || i + len > bytes.length) break; + const dataStart = i + 2; + const payloadLen = len - 2; + + // APP0 JFIF: [FF E0] len [5x id "JFIF\0"][2 ver][1 units][2 Xd][2 Yd][1 thW][1 thH]... + if (marker === 0xe0 && payloadLen >= 14 && bytes[dataStart] === 0x4a && + bytes[dataStart + 1] === 0x46 && bytes[dataStart + 2] === 0x49 && + bytes[dataStart + 3] === 0x46 && bytes[dataStart + 4] === 0x00) { + sawJfif = true; + bytes[dataStart + 7] = 1; // units = dots per inch + bytes[dataStart + 8] = dpiHi; + bytes[dataStart + 9] = dpiLo; + bytes[dataStart + 10] = dpiHi; + bytes[dataStart + 11] = dpiLo; + } + + // APP1 EXIF: [FF E1] len "Exif\0\0" then TIFF + if (marker === 0xe1 && payloadLen >= 14 && + bytes[dataStart] === 0x45 && bytes[dataStart + 1] === 0x78 && + bytes[dataStart + 2] === 0x69 && bytes[dataStart + 3] === 0x66 && + bytes[dataStart + 4] === 0x00 && bytes[dataStart + 5] === 0x00) { + sawExif = true; + const tiff = dataStart + 6; + const le = bytes[tiff] === 0x49 && bytes[tiff + 1] === 0x49; + const be = bytes[tiff] === 0x4d && bytes[tiff + 1] === 0x4d; + if ((le || be) && tiff + 8 <= bytes.length) { + const read32 = (p: number): number => + le + ? bytes[p] | (bytes[p + 1] << 8) | (bytes[p + 2] << 16) | (bytes[p + 3] << 24) + : ((bytes[p] << 24) | (bytes[p + 1] << 16) | (bytes[p + 2] << 8) | bytes[p + 3]) >>> 0; + const write32 = (p: number, v: number): void => { + if (le) { + bytes[p] = v & 0xff; bytes[p + 1] = (v >> 8) & 0xff; + bytes[p + 2] = (v >> 16) & 0xff; bytes[p + 3] = (v >> 24) & 0xff; + } else { + bytes[p] = (v >> 24) & 0xff; bytes[p + 1] = (v >> 16) & 0xff; + bytes[p + 2] = (v >> 8) & 0xff; bytes[p + 3] = v & 0xff; + } + }; + // TIFF integer fields (tag/type/count/values) follow the file byte order + const u16r = (p: number): number => + le ? bytes[p] | (bytes[p + 1] << 8) : (bytes[p] << 8) | bytes[p + 1]; + const u16w = (p: number, v: number): void => { + if (le) { bytes[p] = v & 0xff; bytes[p + 1] = (v >> 8) & 0xff; } + else { bytes[p] = (v >> 8) & 0xff; bytes[p + 1] = v & 0xff; } + }; + const ifdOff = read32(tiff + 4); + if (ifdOff + 2 <= bytes.length - tiff) { + const ifd = tiff + ifdOff; + const count = u16r(ifd); + for (let e = 0; e < count; e++) { + const entry = ifd + 2 + e * 12; + if (entry + 12 > bytes.length) break; + const tag = u16r(entry); + const type = u16r(entry + 2); + const cnt = read32(entry + 4); + if ((tag === 0x011a || tag === 0x011b) && type === 5 && cnt === 1) { + // RATIONAL: entry+8 holds offset to (num, den), relative to TIFF start + const rat = tiff + read32(entry + 8); + if (rat + 8 <= bytes.length) write32(rat, dpi); // keep denominator + } else if (tag === 0x0128 && type === 3 && cnt === 1) { + // ResolutionUnit (2 = inch): normalize to inch so the new density reads correctly + u16w(entry + 8, 2); + } + } + } + } + } + + i += len; + } + + if (sawJfif || sawExif) return bytes; + + // Neither segment exists — insert a minimal JFIF APP0 (density = dpi inch) after SOI. + const app0 = new Uint8Array([ + 0xff, 0xe0, 0x00, 0x10, + 0x4a, 0x46, 0x49, 0x46, 0x00, // "JFIF\0" + 0x01, 0x01, // version 1.01 + 0x01, // units: inch + dpiHi, dpiLo, dpiHi, dpiLo, + 0x00, 0x00, // no thumbnail + ]); + const out = new Uint8Array(bytes.length + app0.length); + out[0] = 0xff; out[1] = 0xd8; + out.set(app0, 2); + out.set(bytes.subarray(2), 2 + app0.length); + return out; +} diff --git a/docker/frontend/shared/utils/paramDefs.ts b/docker/frontend/shared/utils/paramDefs.ts new file mode 100644 index 0000000..464fa79 --- /dev/null +++ b/docker/frontend/shared/utils/paramDefs.ts @@ -0,0 +1,202 @@ +import { ColorAdjustments } from '../types'; + +// Static metadata for the continuous (slider) adjustments of the IQ/WB/FILTERS +// tabs — shared between AdjustmentPanel (chips + slider rows) and App (which +// needs the open row's bounds to drive drag-on-the-image adjustment). The get +// /set accessors are stateless so one table serves every render. +export interface ContinuousParamDef { + key: string; + label: string; + min: number; + max: number; + step: number; + defaultValue: number; + display: (v: number) => string; + get: (a: ColorAdjustments) => number; + set: (v: number) => Partial; + // PRO-only knob: the LITE build greys the chip out (PRO corner tag) and + // cannot open its row, so the value stays at its default there. + pro?: boolean; +} + +const sign = (v: number) => (v > 0 ? `+${v}` : String(v)); +const oneEv = (v: number) => `${v > 0 ? '+' : ''}${v.toFixed(1)} EV`; + +export const PARAM_DEFS: { + iq: ContinuousParamDef[]; + wb: ContinuousParamDef[]; + filters: ContinuousParamDef[]; +} = { + iq: [ + { + key: 'exposure', + label: 'EXPOSURE', + min: -10, + max: 10, + step: 1, + defaultValue: 0, + display: sign, + get: (a) => a.exposure, + set: (v) => ({ exposure: v }), + }, + { + key: 'ev', + label: 'EV', + min: -3, + max: 3, + step: 0.5, + defaultValue: 0, + display: oneEv, + get: (a) => a.exposureCompensation ?? 0, + set: (v) => ({ exposureCompensation: v }), + }, + { + key: 'contrast', + label: 'CONTRAST', + min: -10, + max: 10, + step: 1, + defaultValue: 0, + display: sign, + get: (a) => a.contrast, + set: (v) => ({ contrast: v }), + }, + { + key: 'color', + label: 'COLOR', + min: -10, + max: 10, + step: 1, + defaultValue: 0, + display: sign, + get: (a) => a.saturation, + set: (v) => ({ saturation: v }), + }, + { + key: 'vibrance', + label: 'VIBRANCE', + min: -10, + max: 10, + step: 1, + defaultValue: 0, + display: sign, + get: (a) => a.vibrance ?? 0, + set: (v) => ({ vibrance: v }), + }, + { + key: 'highlight', + label: 'HIGHLIGHT', + min: -10, + max: 10, + step: 1, + defaultValue: 0, + display: sign, + get: (a) => a.highlight ?? 0, + set: (v) => ({ highlight: v }), + }, + { + key: 'shadow', + label: 'SHADOW', + min: -10, + max: 10, + step: 1, + defaultValue: 0, + display: sign, + get: (a) => a.shadow ?? 0, + set: (v) => ({ shadow: v }), + }, + ], + wb: [ + { + key: 'temperature', + label: 'COLOR TEMP', + min: 2500, + max: 10000, + step: 100, + defaultValue: 5500, + display: (v) => `${v}K`, + get: (a) => a.temperature, + set: (v) => ({ temperature: v }), + }, + { + key: 'tint', + label: 'TINT', + min: -10, + max: 10, + step: 1, + defaultValue: 0, + display: sign, + get: (a) => a.tint ?? 0, + set: (v) => ({ tint: v }), + }, + ], + filters: [ + { + key: 'denoise', + label: 'NOISE REDUCTION', + min: -10, + max: 10, + step: 1, + defaultValue: 0, + display: String, + get: (a) => a.denoise, + set: (v) => ({ denoise: v }), + }, + { + key: 'clarity', + label: 'CLARITY', + min: -10, + max: 10, + step: 1, + defaultValue: 0, + display: sign, + get: (a) => a.clarity, + set: (v) => ({ clarity: v }), + }, + { + key: 'sharpening', + label: 'SHARPENING', + min: -10, + max: 10, + step: 1, + defaultValue: 0, + display: String, + get: (a) => a.sharpening ?? 0, + set: (v) => ({ sharpening: v }), + }, + { + key: 'grain', + label: 'MONOCHROME GRAIN', + min: 0, + max: 10, + step: 1, + defaultValue: 0, + display: String, + get: (a) => a.grain, + set: (v) => ({ grain: v }), + }, + { + key: 'hdf', + label: 'HDF EFFECT', + pro: true, + min: 0, + max: 10, + step: 1, + defaultValue: 0, + display: String, + get: (a) => a.hdf ?? 0, + set: (v) => ({ hdf: v }), + }, + { + key: 'vignette', + label: 'VIGNETTING', + min: 0, + max: 10, + step: 1, + defaultValue: 0, + display: String, + get: (a) => a.vignette ?? 0, + set: (v) => ({ vignette: v }), + }, + ], +}; diff --git a/docker/frontend/shared/utils/skiaImage.ts b/docker/frontend/shared/utils/skiaImage.ts new file mode 100644 index 0000000..03a69ab --- /dev/null +++ b/docker/frontend/shared/utils/skiaImage.ts @@ -0,0 +1,89 @@ +import { Skia, type SkImage } from '@shopify/react-native-skia'; + +// Turn a decoded image 90deg clockwise into a fresh SkImage. Used by the FRAME +// tab so a library photo can go landscape <-> portrait before it enters a frame: +// every consumer reads width()/height(), so the pixel-dimension swap carries the +// new orientation through the polaroid window aspect, the cover crop and the +// watermark areas without a single extra layout branch. +// ponytail: offscreen surface, same fallback as exportEngine.createSurface; a +// matrix-based draw would avoid the copy if this ever shows up in a profile. +export function rotateSkImage90(image: SkImage | null): SkImage | null { + if (!image) return null; + const w = image.width(); + const h = image.height(); + if (!(w > 0 && h > 0)) return null; + // CPU-backed on purpose: a GPU (MakeOffscreen) snapshot keeps its pixels in a + // context texture that is gone by the time the draws a frame later — + // the preview then renders the photo completely black. Raster pixels survive + // the trip to the render thread. + const surface = Skia.Surface.Make(h, w); + if (!surface) return image; // keep the unrotated photo on allocation failure + try { + const canvas = surface.getCanvas(); + canvas.clear(Skia.Color('transparent')); + canvas.translate(h, 0); + canvas.rotate(90, 0, 0); + canvas.drawImage(image, 0, 0); + return surface.makeImageSnapshot() ?? image; + } finally { + // The raster snapshot refcounts the pixels, so the surface can go now + // instead of leaving one full-size copy behind per rotation. + surface.dispose(); + } +} + +// Fine rotation about the centre (the FRAME tab's STRAIGHTEN), keeping the +// original pixel dimensions: the content is scaled up just enough that the +// rotated rectangle still covers the frame, so no empty corner shows. The +// scale is the axis-aligned wrap of the counter-rotated frame — slightly +// conservative, which crops a hair more rather than leaking a corner. +// ponytail: one offscreen surface per call, same trade as rotateSkImage90. +export function rotateSkImageBy(image: SkImage | null, degrees: number): SkImage | null { + if (!image) return null; + const w = image.width(); + const h = image.height(); + if (!(w > 0 && h > 0)) return null; + if (!degrees) return image; + const rad = (degrees * Math.PI) / 180; + const cos = Math.abs(Math.cos(rad)); + const sin = Math.abs(Math.sin(rad)); + const k = Math.max((w * cos + h * sin) / w, (w * sin + h * cos) / h); + const surface = Skia.Surface.Make(w, h); + if (!surface) return image; // keep the unrotated photo on allocation failure + try { + const canvas = surface.getCanvas(); + canvas.clear(Skia.Color('transparent')); + canvas.translate(w / 2, h / 2); + canvas.rotate(degrees, 0, 0); + canvas.scale(k, k); + canvas.drawImage(image, -w / 2, -h / 2); + return surface.makeImageSnapshot() ?? image; + } finally { + surface.dispose(); // see rotateSkImage90 + } +} + +// The whole FRAME-tab photo orientation in one call: the quarter turn first +// (rotateSkImage90 swaps the pixel dimensions, which then propagates through +// every consumer unchanged), then the fine STRAIGHTEN on top of it. +export function applyPhotoRotation( + image: SkImage | null, + quarter = 0, + straighten = 0 +): SkImage | null { + let out = image; + const turns = Math.round(quarter / 90) % 4; + for (let i = 0; i < turns; i++) { + const next = rotateSkImage90(out); + // A 180/270 turn allocates one full-size copy per turn: the intermediate is + // dead as soon as the next one exists (never the caller's input image). + if (next && next !== out && i > 0) out.dispose(); + out = next ?? out; + } + if (straighten) { + const s = rotateSkImageBy(out, straighten); + if (s && s !== out && out !== image) out.dispose(); // see above + out = s ?? out; + } + return out; +} diff --git a/docker/frontend/shared/utils/toneShader.ts b/docker/frontend/shared/utils/toneShader.ts new file mode 100644 index 0000000..2325f36 --- /dev/null +++ b/docker/frontend/shared/utils/toneShader.ts @@ -0,0 +1,254 @@ +import { BaseFilter, ColorAdjustments } from '../types'; + +// Tone-domain adjustments (Fuji-style Dynamic Range + Highlight/Shadow). +// SkSL runtime effect over a child image shader. +// +// Lightness/chroma split: the curve moves the luma and the colour difference +// (rgb - luma) carries the hue through with most of its chroma. Scaling R,G,B +// by one gain keeps the *ratio* but crushes absolute chroma — that is what +// turned saturated blues black under -SH and bright colours grey under -HL. +// +// Wide, soft knees so the knobs reach like a tone curve instead of biting only +// at the very ends: HL rides only the top (0.65..1.00) so it leaves the greys +// and midtones alone (a 0.45 knee dragged a mid-grey down) while SH rides the +// lower half (0.00..0.55), and the 0.50 midpoint never moves. Earlier narrow +// knees (0.80..1.00 / 0.00..0.30) made both sliders look dead on any photo +// without true whites or blacks. +// +// Both knobs are pure additive shifts of the luma. That keeps the curve +// monotonic (steepest knee slope 4.29/unit * 0.22 = 0.94, and the two knees +// never overlap), so a brighter input can never come out darker. The earlier +// multiplicative form was NOT monotonic: with hl=-1 a grey 0.73 came out +// darker than 0.80. +// +// dr - DR strength 0..1: lifts shadows slightly and rolls highlights +// (Fuji extended DR); 0/auto/DR100 = no extra curve. +// hl - highlight -1..1: + lifts toward white, - rolls the bright side down. +// sh - shadow -1..1: + lifts the dark side, - deepens it. +// vib - vibrance -1..1: chroma-masked saturation. It rides along in this shader +// (rather than the colour matrix) because it needs per-pixel chroma: +// already-vivid pixels move least, so skins/skies deepen without the neon +// clip a plain Saturation boost causes. +// shT/hlT - split tone (per-channel RGB bias, -1..1 each): a fixed cast applied +// to the shadows and/or the highlights only. A 4x5 colour matrix cannot do +// this — it is one linear map, so any cast it applies must also hit the +// midtones and the opposite end. Classic Neg wants green/cyan shadows with +// warm highlights at once, so the stock ships these values and the pass +// stays active for it even when every user knob is 0. All-zero still = no +// pass. +export const TONE_SKSL = ` +uniform shader src; +uniform float dr; +uniform float hl; +uniform float sh; +uniform float vib; +uniform float shTr; +uniform float shTg; +uniform float shTb; +uniform float hlTr; +uniform float hlTg; +uniform float hlTb; +uniform float cc; +uniform float ccb; +vec4 main(vec2 xy) { + vec4 c = src.eval(xy); + vec3 rgb = clamp(c.rgb, 0.0, 1.0); + float t = clamp(dot(rgb, vec3(0.2126, 0.7152, 0.0722)), 0.0, 1.0); + float hlMask = smoothstep(0.65, 1.00, t); + float shMask = 1.0 - smoothstep(0.00, 0.55, t); + // NOTE: never name a local 'out' — it is a reserved SkSL qualifier. + float o = t + hl * 0.22 * hlMask + sh * 0.34 * shMask; + // Dynamic range: gentle shadow lift + highlight roll (protect brights). + o += dr * 0.12 * shMask * (1.0 - t); + o -= dr * 0.18 * hlMask * t; + o = clamp(o, 0.0, 1.0); + // Lightness takes the curve, hue stays: the colour difference is gained + // only part-way so darkening cannot collapse a colour to black and lifting + // cannot blow a dark saturated colour out to white. + float cg = clamp(o / max(t, 0.0004), 0.55, 1.35); + rgb = clamp(vec3(o) + (rgb - vec3(t)) * cg, 0.0, 1.0); + // Split tone (stock look): the shadows and the highlights may each carry + // their own tint, so the two ends of the curve can drift opposite ways + // (Classic Neg: green-cyan darks, warm brights) without touching mid-greys. + rgb = clamp(rgb + vec3(shTr, shTg, shTb) * shMask + vec3(hlTr, hlTg, hlTb) * hlMask, 0.0, 1.0); + // Color Chrome / Color Chrome FX Blue: the two stock-dialed colour effects + // DEEPEN what is already chromatic and leave neutrals exactly where they are + // (Fuji: "deeper tone in highly saturated colour"; FX Blue does it for the + // blue/cyan side only). Both therefore need per-pixel chroma — a 4x5 colour + // matrix is one linear map, so any gain it applies also moves greys, and a + // blue-only gain drags the whole white point. + float mxc = max(max(rgb.r, rgb.g), rgb.b); + float mnc = min(min(rgb.r, rgb.g), rgb.b); + // Chroma ratio with a small floor: a near-black pixel with a hair of cast + // has ratio 1.0 but no colour to deepen, and must stay put. + float ccChroma = (mxc - mnc) / max(mxc, 0.10); + // Color Chrome rides the chroma itself: a muted colour barely moves, a vivid + // one gains density. The 0.25 knee keeps skin, haze and pastels untouched. + float ccMask = cc * smoothstep(0.25, 0.85, ccChroma); + // FX Blue: only where blue clearly leads red AND green (so magenta/purple + // stay out), and richest in a bright blue — a dark blue has no tonality left + // to deepen. + float ccbBlue = clamp((rgb.b - rgb.r) * 2.0, 0.0, 1.0) * clamp((rgb.b - 0.5 * (rgb.r + rgb.g) + 0.05) * 3.0, 0.0, 1.0); + float ccbMask = ccb * ccbBlue * smoothstep(0.15, 0.60, ccChroma) * smoothstep(0.20, 0.70, t); + float deep = clamp(ccMask + ccbMask, 0.0, 1.0); + // Density = lightness down with the colour difference riding along, so hue is + // preserved and the colour cannot collapse toward black (same reason the tone + // curve above keeps chroma). A touch of chroma is given up as it deepens. + float l3 = dot(rgb, vec3(0.2126, 0.7152, 0.0722)); + rgb = clamp(vec3(l3 * (1.0 - 0.28 * deep)) + (rgb - vec3(l3)) * (1.0 - 0.10 * deep), 0.0, 1.0); + // Vibrance: push the LESS-saturated pixels harder than the vivid ones. + float l2 = dot(rgb, vec3(0.2126, 0.7152, 0.0722)); + float mx = max(max(rgb.r, rgb.g), rgb.b); + float mn = min(min(rgb.r, rgb.g), rgb.b); + float chroma = mx > 0.0001 ? (mx - mn) / mx : 0.0; + float kv = 1.0 + vib * 0.75 * (1.0 - chroma); + return vec4(clamp(mix(vec3(l2), rgb, kv), 0.0, 1.0), c.a); +} +`; + +// Bright Pass Filter for the HDF EFFECT pass (HDF), SkSL over a child image +// shader — the pattern TONE_SKSL above already proved on device. +// +// Per channel the old 2.5*in-1.5 curve only zeroed a channel that was dark +// *itself*: a saturated blue (B = 1.0) came out of it fully lit, so a dark blue +// shadow bloomed and a dark saturated colour smeared its hue into the darks. +// Photoshop's Bright Pass filters on the LUMINANCE instead: one knee decides how +// much light a pixel carries, and one gain scales all three channels, so below +// the knee the output is exactly 0.0 (Screen against black = no-op, the shadows +// are untouched) and above it every channel keeps its ratio — the hue cannot +// drift, only the brightness blooms. +// +// Knee t0..t1 = 0.55..0.85: the soft end of the old hard 0.6 cut (2.5*0.6-1.5 = +// 0), wide enough that a specular ramps in instead of clipping on/off. +export const GLOW_T0 = 0.55; +export const GLOW_T1 = 0.85; + +export const GLOW_SKSL = ` +uniform shader src; +uniform float t0; +uniform float t1; +vec4 main(vec2 xy) { + vec4 c = src.eval(xy); + float luma = dot(clamp(c.rgb, 0.0, 1.0), vec3(0.2126, 0.7152, 0.0722)); + return vec4(c.rgb * smoothstep(t0, t1, luma), c.a); +} +`; + +// Flat uniform buffer for `makeShaderWithChildren` — same order as GLOW_SKSL's +// declarations (t0, t1). +export function glowUniformArray(): number[] { + 'worklet'; + return [GLOW_T0, GLOW_T1]; +} + +// Same values for the declarative path, which indexes uniforms by +// NAME (a flat array is only valid for the JS makeShaderWithChildren API). +export const GLOW_UNIFORMS = { t0: GLOW_T0, t1: GLOW_T1 }; + +// CLARITY (positive): unsharp 3x3 with epsilon 0 — the kernel export pass 4 +// builds with MakeMatrixConvolution, re-expressed as a plain shader because RN +// Skia 2.6 exposes no convolution image filter to the declarative JSX writer. +// `px` is one ORIGINAL image pixel expressed in the caller's canvas units, so +// the preview, the camera worklet and the file all sharpen at the same radius. +export const CLARITY_SKSL = ` +uniform shader src; +uniform float a; +uniform float2 px; +vec4 main(vec2 xy) { + vec4 c = src.eval(xy); + vec4 s = src.eval(xy + float2(0.0, -px.y)) + + src.eval(xy + float2(0.0, px.y)) + + src.eval(xy + float2(-px.x, 0.0)) + + src.eval(xy + float2( px.x, 0.0)); + return vec4(clamp(c.rgb * (1.0 + 4.0 * a) - a * s.rgb, 0.0, 1.0), c.a); +} +`; + +// Named uniforms for , same names as CLARITY_SKSL declares. +export function clarityUniforms(a: number, pxX: number, pxY: number) { + return { a, px: [pxX, pxY] }; +} + +export interface ToneUniforms { + // All zero → no tone adjustment needed (caller can skip the shader pass). + dr: number; // 0..1 + hl: number; // -1..1 (adjustments.highlight / 10) + sh: number; // -1..1 (adjustments.shadow / 10) + vib: number; // -1..1 (adjustments.vibrance / 10) + shT: [number, number, number]; // shadow split-tone RGB bias, -1..1 + hlT: [number, number, number]; // highlight split-tone RGB bias, -1..1 + cc: number; // 0..1 Color Chrome depth (0 = 'none') + ccb: number; // 0..1 Color Chrome FX Blue depth (0 = 'none') +} + +// Per-stock tone pass. Fuji's Classic stocks are not a plain colour matrix: +// Classic Neg splits its tone (green-cyan darks / warm brights) and Classic +// Chrome crushes the shadows hard while muting colour. Those two parts live +// here instead of in the 4x5 matrix, which cannot move one end of the curve +// without also moving the other. +const FILM_TONE: Partial>> = { + 'classic-chrome': { sh: -0.28 }, + 'classic-neg': { shT: [-0.018, 0.009, 0.013], hlT: [0.024, 0.008, -0.012] }, + // Acros. A black-and-white stock IS its grey ramp, so this entry only shapes + // the two ENDS and leaves the middle an identity: a smooth shadow toe that + // reaches a true black (no film-base lift, no flat grey wash) and a highlight + // shoulder that stops just short of white instead of clipping a cloud to + // paper. Mid-tones see neither mask, so the ramp keeps every step the matrix + // handed over — which is what 'deep black' costs in a colour stock and does + // not have to cost here. + // Gains are TONE_SKSL's own (sh * 0.34, hl * 0.22), so -0.12 puts the toe at + // ~5% and -0.05 trims the top ~1%. + monochrome: { sh: -0.12, hl: -0.05 }, +}; + +export function getToneUniforms(adj: ColorAdjustments, baseFilter?: BaseFilter): ToneUniforms { + const drRaw = adj.dynamicRange ?? 'auto'; + const dr = drRaw === 'auto' || drRaw === 100 ? 0 : (drRaw - 100) / 300; + const hl = Math.max(-1, Math.min(1, (adj.highlight ?? 0) / 10)); + const sh = Math.max(-1, Math.min(1, (adj.shadow ?? 0) / 10)); + const vib = Math.max(-1, Math.min(1, (adj.vibrance ?? 0) / 10)); + const film = (baseFilter && FILM_TONE[baseFilter]) || {}; + const shT: [number, number, number] = film.shT ?? [0, 0, 0]; + const hlT: [number, number, number] = film.hlT ?? [0, 0, 0]; + // Color Chrome depth per stop of the UI's none/weak/strong. A chrome set is a + // monochrome look, so both are forced off there: the effect is colour-only + // (the preview/export matrix skips them for monochrome for the same reason). + const colour = baseFilter !== 'monochrome'; + const chromeDepth = (v: ColorAdjustments['colorChrome'] | undefined) => + !colour || v === 'none' || v == null ? 0 : v === 'strong' ? 0.9 : 0.45; + const blueDepth = (v: ColorAdjustments['colorChromeBlue'] | undefined) => + !colour || v === 'none' || v == null ? 0 : v === 'strong' ? 1.0 : 0.5; + return { + dr, + hl: hl + (film.hl ?? 0), + sh: sh + (film.sh ?? 0), + vib, + shT, + hlT, + cc: chromeDepth(adj.colorChrome), + ccb: blueDepth(adj.colorChromeBlue), + }; +} + +// Flat uniform buffer for `makeShaderWithChildren` / `` — the +// order must match TONE_SKSL's declarations. +export function toneUniformArray(u: ToneUniforms): number[] { + return [u.dr, u.hl, u.sh, u.vib, u.shT[0], u.shT[1], u.shT[2], u.hlT[0], u.hlT[1], u.hlT[2], u.cc, u.ccb]; +} + +export function toneIsActive(u: ToneUniforms): boolean { + return ( + u.dr !== 0 || + u.hl !== 0 || + u.sh !== 0 || + u.vib !== 0 || + u.shT[0] !== 0 || + u.shT[1] !== 0 || + u.shT[2] !== 0 || + u.hlT[0] !== 0 || + u.hlT[1] !== 0 || + u.hlT[2] !== 0 || + u.cc !== 0 || + u.ccb !== 0 + ); +} diff --git a/docker/frontend/src/App.tsx b/docker/frontend/src/App.tsx new file mode 100644 index 0000000..391b08c --- /dev/null +++ b/docker/frontend/src/App.tsx @@ -0,0 +1,547 @@ +import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; +import { ToolRail, type TabId } from './ui/ToolRail'; +import { ChipRow, Section, SliderRow, type ChipDef, type SliderDef } from './ui/ChipColumn'; +import { ImageStage } from './ui/ImageStage'; +import { TopBar } from './ui/TopBar'; +import { AuthModal } from './ui/AuthModal'; +import { useI18n } from './i18n/I18nProvider'; +import { loadEngine } from './engine/assets'; +import { renderPhoto, type RenderAssets } from './engine/exportEngine'; +import { download, fileToBytes, readGps, resizedJpeg } from './engine/imageOps'; +import { api, type SavedRecipe, type User } from './api'; +import { TIERS } from './config/tiers'; +import { DEFAULT_ADJUSTMENTS, DEFAULT_RECIPES, FILM_SIMS, filmSimToRecipe } from '../shared/utils/defaultRecipes'; +import { PARAM_DEFS, type ContinuousParamDef } from '../shared/utils/paramDefs'; +import { writeJpegExif } from '../shared/utils/exifWrite'; +import { CROP_W_H, type CropRatio, type FrameId, type GPSInfo, type Recipe } from '../shared/types'; +import type { MsgKey } from './i18n/vi'; + +// Preview renders on a downscaled copy; export always re-renders the original +// bytes, so nothing shown here is baked into the file. +const PREVIEW_MAX = 1600; +const PREVIEW_DEBOUNCE_MS = 120; + +const SECTION_TITLE: Record = { + presets: 'sec.filmSims', + favorited: 'sec.saved', + light: 'tab.light', + wb: 'tab.wb', + fx: 'tab.fx', + frame: 'sec.frame', +}; + +const FRAMES: { id: FrameId; label: MsgKey }[] = [ + { id: 'none', label: 'frame.none' }, + { id: 'polaroid', label: 'frame.polaroid' }, + { id: 'classic-white', label: 'frame.classic' }, + { id: 'cinematic', label: 'frame.cinematic' }, + { id: 'wallframe', label: 'frame.wall' }, +]; + +// ponytail: nothing here is a rectangle the user drags yet, so only the fixed +// ratios are listed — 'free' (DEFAULT_CROP_RECT + drag on the canvas) lands +// with the crop overlay in W3. The type already carries it. +const CROPS: Exclude[] = ['none', '1:1', '2:3', '3:2', '3:4', '4:3', '16:9']; +const ROTATIONS = [0, 90, 180, 270] as const; +const TEMP_PRESETS: { label: string; kelvin: number }[] = [ + { label: 'AUTO', kelvin: 5500 }, + { label: 'DAYLIGHT', kelvin: 5600 }, + { label: 'DAYLIGHT -3R', kelvin: 5800 }, + { label: 'CLOUDY', kelvin: 6500 }, + { label: 'SHADE', kelvin: 7500 }, + { label: 'TUNGSTEN', kelvin: 3200 }, + { label: 'FLUOR', kelvin: 4000 }, +]; + +interface LoadedSource { + bytes: Uint8Array; + name: string; + url: string; +} + +export function Workspace() { + const { t } = useI18n(); + + const [engine, setEngine] = useState<{ assets: RenderAssets; gpu: boolean } | null>(null); + const [error, setError] = useState(null); + const [busy, setBusy] = useState(null); + + const [source, setSource] = useState(null); + const [previewBytes, setPreviewBytes] = useState(null); + const [previewUrl, setPreviewUrl] = useState(null); + const [rendered, setRendered] = useState<{ w: number; h: number; ms: number } | null>(null); + + const [recipe, setRecipe] = useState(() => ({ + ...DEFAULT_RECIPES[0], + id: 'web-current', + name: DEFAULT_RECIPES[0]?.name ?? 'PROVIA', + })); + const [simId, setSimId] = useState(null); + const [frameId, setFrameId] = useState('none'); + const [crop, setCrop] = useState>('none'); + const [rotation, setRotation] = useState<0 | 90 | 180 | 270>(0); + const [openParam, setOpenParam] = useState(null); + const [tab, setTab] = useState('presets'); + const [peek, setPeek] = useState(false); + + const [gps, setGps] = useState(null); + const [gpsManual, setGpsManual] = useState({ lat: '', lng: '' }); + const [place, setPlace] = useState(''); + const [useGeotag, setUseGeotag] = useState(false); + const [markText, setMarkText] = useState(''); + const [markOn, setMarkOn] = useState(false); + + const [undo, setUndo] = useState([]); + const [exporting, setExporting] = useState(false); + const [user, setUser] = useState(null); + const [saved, setSaved] = useState([]); + const [authOpen, setAuthOpen] = useState(false); + + const renderToken = useRef(0); + + // --- boot ----------------------------------------------------------------- + useEffect(() => { + let alive = true; + loadEngine() + .then((loaded) => { + if (!alive) return; + setEngine(loaded); + if (!loaded.gpu) setError(t('err.noWebgl')); + }) + .catch((err) => alive && setError(String(err))); + api + .me() + .then((r) => alive && setUser(r.user)) + .catch(() => undefined); // signed out is a valid state — the demo needs no account + return () => { + alive = false; + }; + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + useEffect(() => { + if (!user) { + setSaved([]); + return; + } + api.listRecipes().then((r) => setSaved(r.recipes)).catch(() => setSaved([])); + }, [user]); + + // --- preview render ------------------------------------------------------- + useEffect(() => { + if (!engine || !previewBytes) return; + const token = ++renderToken.current; + const timer = setTimeout(async () => { + setBusy(t('act.exporting')); + const started = performance.now(); + try { + const out = await renderPhoto({ + source: previewBytes, + recipe, + frameId, + useGeotag, + gpsInfo: gps, + assets: engine.assets, + options: { + sharpen: true, + photoRotation: rotation, + cropWH: crop === 'none' ? undefined : CROP_W_H[crop], + watermark: markOn && markText ? { text: markText, x: 0.5, y: 0.92, color: '#ffffff', size: 1 } : null, + gpsWatermark: useGeotag && gps ? { x: 0.06, y: 0.7, size: 1.2, showName: true, showTime: true } : null, + }, + }); + if (token !== renderToken.current) return; + if (!out) throw new Error('render returned null'); + const url = URL.createObjectURL(new Blob([out.bytes as BlobPart], { type: 'image/jpeg' })); + setPreviewUrl((prev) => { + if (prev) URL.revokeObjectURL(prev); + return url; + }); + setRendered({ w: out.width, h: out.height, ms: Math.round(performance.now() - started) }); + } catch (err) { + if (token === renderToken.current) setError(t('err.generic', { msg: String(err) })); + } finally { + if (token === renderToken.current) setBusy(null); + } + }, PREVIEW_DEBOUNCE_MS); + return () => clearTimeout(timer); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [engine, previewBytes, recipe, frameId, crop, rotation, useGeotag, gps, markOn, markText]); + + // --- input --------------------------------------------------------------- + const loadFile = useCallback( + async (file: File) => { + setError(null); + setBusy(t('auth.busy')); + try { + const bytes = await fileToBytes(file); + const preview = await resizedJpeg(bytes, PREVIEW_MAX); + const found = await readGps(bytes); + setSource((prev) => { + if (prev) URL.revokeObjectURL(prev.url); + return { bytes, name: file.name, url: URL.createObjectURL(file) }; + }); + setPreviewBytes(preview); + setGps(found); + setUseGeotag(!!found); + if (!found) setGpsManual({ lat: '', lng: '' }); + } catch { + setError(t('err.decode')); + } finally { + setBusy(null); + } + }, + [t] + ); + + useEffect(() => { + const onPaste = (e: ClipboardEvent) => { + const file = e.clipboardData?.files?.[0]; + if (file) void loadFile(file); + }; + window.addEventListener('paste', onPaste); + return () => window.removeEventListener('paste', onPaste); + }, [loadFile]); + + // --- recipe edits --------------------------------------------------------- + const remember = useCallback(() => setUndo((stack) => [...stack.slice(-19), recipe]), [recipe]); + + const applyRecipe = useCallback( + (next: Recipe) => { + remember(); + setRecipe(next); + setSimId(next.isCustom ? null : next.id); + }, + [remember] + ); + + const setAdjustment = useCallback( + (patch: Partial) => { + setRecipe((prev) => ({ ...prev, isCustom: true, adjustments: { ...prev.adjustments, ...patch } })); + }, + [] + ); + + const reset = useCallback(() => { + remember(); + setRecipe((prev) => ({ ...prev, isCustom: true, adjustments: { ...DEFAULT_ADJUSTMENTS }, baseFilter: 'none', cinema: null })); + setSimId(null); + setFrameId('none'); + setCrop('none'); + setRotation(0); + setMarkOn(false); + setOpenParam(null); + }, [remember]); + + const undoLast = useCallback(() => { + setUndo((stack) => { + const last = stack[stack.length - 1]; + if (last) setRecipe(last); + return stack.slice(0, -1); + }); + }, []); + + const saveRecipe = useCallback(async () => { + if (!user) { + setAuthOpen(true); + return; + } + const name = window.prompt(t('save.name'), recipe.name); + if (!name) return; + try { + const created = await api.createRecipe(name, { ...recipe, name }); + setSaved((list) => [created.recipe, ...list]); + setRecipe((prev) => ({ ...prev, name })); + } catch (err) { + setError(t('err.generic', { msg: String(err) })); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [recipe, user, t]); + + const exportPhoto = useCallback(async () => { + if (!engine || !source) return; + setExporting(true); + setError(null); + try { + const tier = TIERS[user ? 'user' : 'guest']; + const src = tier.maxExportDim ? await resizedJpeg(source.bytes, tier.maxExportDim, 0.95) : source.bytes; + const out = await renderPhoto({ + source: src, + recipe, + frameId, + useGeotag, + gpsInfo: gps, + assets: engine.assets, + options: { + dpi: 300, + sharpen: true, + photoRotation: rotation, + cropWH: crop === 'none' ? undefined : CROP_W_H[crop], + watermark: markOn && markText + ? { text: markText, x: 0.5, y: 0.92, color: '#ffffff', size: 1 } + : tier.watermark + ? { text: tier.watermark, x: 0.5, y: 0.94, color: '#ffffff', size: 1 } + : null, + gpsWatermark: useGeotag && gps ? { x: 0.06, y: 0.7, size: 1.2, showName: true, showTime: true } : null, + }, + }); + if (!out) throw new Error('render returned null'); + + // Encode → JFIF density 300 (inside renderPhoto) → EXIF rebuild, the same + // order the app uses. The source photo donates its camera/lens tags. + const bytes = writeJpegExif( + out.bytes, + { + dateTime: new Date(), + gps: gps ? { latitude: gps.latitude, longitude: gps.longitude } : null, + width: out.width, + height: out.height, + dpi: 300, + locationName: place || gps?.locality || null, + }, + source.bytes + ); + const stamp = new Date().toISOString().replace(/[-:T]/g, '').slice(0, 14); + download(bytes, `recipescam_${(recipe.name || 'photo').replace(/\W+/g, '-').toLowerCase()}_${stamp}.jpg`); + } catch (err) { + setError(t('err.generic', { msg: String(err) })); + } finally { + setExporting(false); + } + }, [engine, source, recipe, frameId, useGeotag, gps, rotation, crop, markOn, markText, place, user, t]); + + // --- chips ---------------------------------------------------------------- + const paramChips = (defs: ContinuousParamDef[]): ChipDef[] => + defs.map((def) => ({ + key: def.key, + label: def.label, + value: def.display(def.get(recipe.adjustments)), + active: openParam === def.key, + pro: def.pro && !user, + disabled: !!def.pro && !user, + onClick: () => setOpenParam(openParam === def.key ? null : def.key), + })); + + const slider: SliderDef | null = useMemo(() => { + if (!openParam) return null; + const all = [...PARAM_DEFS.iq, ...PARAM_DEFS.wb, ...PARAM_DEFS.filters]; + const def = all.find((d) => d.key === openParam); + if (!def) return null; + return { + key: def.key, + label: def.label, + value: def.get(recipe.adjustments), + min: def.min, + max: def.max, + step: def.step, + display: def.display(def.get(recipe.adjustments)), + onChange: (value) => setAdjustment(def.set(value)), + onClose: () => setOpenParam(null), + }; + }, [openParam, recipe.adjustments, setAdjustment]); + + const chips: ChipDef[] = useMemo(() => { + if (tab === 'presets') { + return [ + ...FILM_SIMS.map((sim) => ({ + key: sim.id, + label: sim.name, + active: simId === sim.id, + onClick: () => applyRecipe(filmSimToRecipe(sim)), + })), + ...DEFAULT_RECIPES.slice(0, 6).map((preset) => ({ + key: preset.id, + label: preset.name, + active: recipe.name === preset.name && !recipe.isCustom, + onClick: () => applyRecipe({ ...preset }), + })), + ]; + } + if (tab === 'favorited') { + return saved.map((entry) => ({ + key: String(entry.id), + label: entry.name, + active: false, + onClick: () => applyRecipe({ ...(entry.recipe as Recipe), name: entry.name }), + })); + } + if (tab === 'light') return paramChips(PARAM_DEFS.iq); + if (tab === 'wb') { + return [ + ...TEMP_PRESETS.map((preset) => ({ + key: preset.label, + label: preset.label, + active: Math.round(recipe.adjustments.temperature) === preset.kelvin, + onClick: () => setAdjustment({ temperature: preset.kelvin }), + })), + ...paramChips(PARAM_DEFS.wb), + ]; + } + if (tab === 'fx') return paramChips(PARAM_DEFS.filters); + return []; + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [tab, saved, simId, recipe, openParam, user, applyRecipe, setAdjustment]); + + const status = busy ? busy : rendered ? t('meta.rendered', { ms: rendered.ms, w: rendered.w, h: rendered.h }) : t('meta.ready'); + + return ( +

+ 0} + onUndo={undoLast} + onReset={reset} + onExport={exportPhoto} + onSave={saveRecipe} + onAuth={() => setAuthOpen(true)} + onLogout={async () => { + await api.logout().catch(() => undefined); + setUser(null); + }} + /> + +
+ + +
+ {tab === 'frame' ? ( + <> +
+ ({ + key: frame.id, + label: t(frame.label), + active: frameId === frame.id, + onClick: () => { + remember(); + setFrameId(frame.id); + }, + }))} + /> +
+
+ ({ + key: ratio, + label: t(`crop.${ratio}` as 'crop.none'), + active: crop === ratio, + onClick: () => setCrop(ratio), + }))} + /> +
+
+ ({ + key: String(deg), + label: t(`rot.${deg}` as 'rot.0'), + active: rotation === deg, + onClick: () => setRotation(deg), + }))} + /> +
+
+ setMarkOn(!markOn), + }, + ]} + /> + {markOn ? ( + setMarkText(e.target.value)} + /> + ) : null} +
+
+ setUseGeotag(!useGeotag), + }, + ]} + /> + setPlace(e.target.value)} + /> +

{gps ? t('wm.hasExif') : t('wm.noExif')}

+ {!gps ? ( +
+ setGpsManual((prev) => ({ ...prev, lat: e.target.value }))} + onBlur={() => { + const lat = Number(gpsManual.lat); + const lng = Number(gpsManual.lng); + if (Number.isFinite(lat) && Number.isFinite(lng) && gpsManual.lat && gpsManual.lng) { + setGps({ latitude: lat, longitude: lng, locality: place, timestamp: Date.now(), source: 'device' }); + setUseGeotag(true); + } + }} + /> + setGpsManual((prev) => ({ ...prev, lng: e.target.value }))} + /> +
+ ) : null} +
+ + ) : ( + <> +
+ + {tab === 'favorited' && saved.length === 0 ?

{t('sec.savedEmpty')}

: null} +
+ {slider ? : null} + + )} +
+ + void loadFile(file)} + /> +
+ + {authOpen ? ( + setAuthOpen(false)} + onDone={async () => { + setAuthOpen(false); + const me = await api.me().catch(() => null); + if (me) setUser(me.user); + }} + /> + ) : null} +
+ ); +} diff --git a/docker/frontend/src/Landing.tsx b/docker/frontend/src/Landing.tsx new file mode 100644 index 0000000..214a6fa --- /dev/null +++ b/docker/frontend/src/Landing.tsx @@ -0,0 +1,84 @@ +import { useI18n, LANGS } from './i18n/I18nProvider'; +import { ACCENTS, useTheme } from './theme/ThemeProvider'; + +// Landing page. The store links are placeholders on purpose — the real URLs are +// still unknown (see WEB_PLAN.md, W6); they live in one place so filling them in +// later is a one-line change. +const STORE_LINKS = { + play: '#', + appstore: '#', +}; + +export function Landing() { + const { t, lang, setLang } = useI18n(); + const { mode, accent, setMode, setAccent } = useTheme(); + + return ( + + ); +} diff --git a/docker/frontend/src/api.ts b/docker/frontend/src/api.ts new file mode 100644 index 0000000..b82b871 --- /dev/null +++ b/docker/frontend/src/api.ts @@ -0,0 +1,45 @@ +// Thin wrapper over the API container. Same origin in production (nginx proxies +// /api), the Vite dev server proxies it too — so no base URL, no CORS. +import type { Recipe } from '../shared/types'; + +export interface User { + id: number; + email: string; +} + +export interface SavedRecipe { + id: number; + name: string; + recipe: unknown; + createdAt: string; + updatedAt: string; +} + +async function call(path: string, init?: RequestInit): Promise { + const res = await fetch(`/api${path}`, { + credentials: 'same-origin', + headers: init?.body ? { 'content-type': 'application/json' } : undefined, + ...init, + }); + if (res.status === 204) return undefined as T; + const text = await res.text(); + const body = text ? JSON.parse(text) : {}; + if (!res.ok) throw new Error(body.error ?? `HTTP ${res.status}`); + return body as T; +} + +export const api = { + me: () => call<{ user: User }>('/auth/me'), + signup: (email: string, password: string) => + call<{ user: User }>('/auth/signup', { method: 'POST', body: JSON.stringify({ email, password }) }), + login: (email: string, password: string) => + call<{ user: User }>('/auth/login', { method: 'POST', body: JSON.stringify({ email, password }) }), + logout: () => call('/auth/logout', { method: 'POST' }), + + listRecipes: () => call<{ recipes: SavedRecipe[] }>('/recipes'), + createRecipe: (name: string, recipe: Recipe) => + call<{ recipe: SavedRecipe }>('/recipes', { method: 'POST', body: JSON.stringify({ name, recipe }) }), + updateRecipe: (id: number, name: string, recipe: Recipe) => + call<{ recipe: SavedRecipe }>(`/recipes/${id}`, { method: 'PUT', body: JSON.stringify({ name, recipe }) }), + deleteRecipe: (id: number) => call(`/recipes/${id}`, { method: 'DELETE' }), +}; diff --git a/docker/frontend/src/config/tiers.ts b/docker/frontend/src/config/tiers.ts new file mode 100644 index 0000000..a5caf58 --- /dev/null +++ b/docker/frontend/src/config/tiers.ts @@ -0,0 +1,19 @@ +// One table for the guest/user split, so the policy can change without touching +// the UI. Guest = the landing + a capped, watermarked demo; user = full tools, +// full-resolution export, saved recipes. + +export type TierId = 'guest' | 'user'; + +export interface Tier { + id: TierId; + /** Longest edge of an exported photo, or null for the original size. */ + maxExportDim: number | null; + /** Text burned into every export, or null for a clean file. */ + watermark: string | null; + batchExport: boolean; +} + +export const TIERS: Record = { + guest: { id: 'guest', maxExportDim: 2048, watermark: 'RecipesCam demo', batchExport: false }, + user: { id: 'user', maxExportDim: null, watermark: null, batchExport: true }, +}; diff --git a/docker/frontend/src/engine/assets.ts b/docker/frontend/src/engine/assets.ts new file mode 100644 index 0000000..5a8a50d --- /dev/null +++ b/docker/frontend/src/engine/assets.ts @@ -0,0 +1,33 @@ +import { initSkia, hasGpu } from './skiaShim'; +import type { RenderAssets } from './exportEngine'; + +// One CanvasKit instance per page, one copy of the three render assets. The wasm +// file is served from /wasm/ (scripts/copy-wasm.mjs puts it there at build time); +// the fonts and the wall-frame artwork are in public/assets. +let assets: RenderAssets | null = null; + +async function buf(url: string): Promise { + const res = await fetch(url); + if (!res.ok) throw new Error(`fetch ${url}: ${res.status}`); + return res.arrayBuffer(); +} + +// The engine is only usable on a WebGL-backed surface (see skiaShim.ts), so the +// app has to know whether it got one: reporting "no WebGL" beats silently +// spending minutes per photo on the raster fallback. +export function probeGpu(): boolean { + return hasGpu(); +} + +export async function loadEngine(): Promise<{ assets: RenderAssets; gpu: boolean }> { + await initSkia({ locateFile: (file) => `/wasm/${file}` }); + if (!assets) { + const [textFont, emojiFont, wallframePng] = await Promise.all([ + buf('/assets/Cousine-Regular.ttf'), + buf('/assets/NotoEmoji-GPS.ttf'), + buf('/assets/wallframe.png'), + ]); + assets = { textFont, emojiFont, wallframe: new Uint8Array(wallframePng) }; + } + return { assets, gpu: probeGpu() }; +} diff --git a/docker/frontend/src/engine/exportEngine.ts b/docker/frontend/src/engine/exportEngine.ts new file mode 100644 index 0000000..f1ea761 --- /dev/null +++ b/docker/frontend/src/engine/exportEngine.ts @@ -0,0 +1,690 @@ +// Full-resolution render pipeline for the web build. +// +// Port of `src/utils/exportEngine.ts` (React Native / Skia JSI) onto CanvasKit, +// step for step, so the file a browser downloads matches the file the phone +// writes. What changed and why (W0 probe, measured — see skiaShim.ts header): +// - `Skia.Data.fromURI` + expo file APIs → the caller hands in the JPEG bytes +// and gets JPEG bytes back; the file save / gallery / MediaLibrary tail is +// gone (the browser downloads a Blob instead). +// - `MakeMatrixConvolution` does not exist in CanvasKit. The two convolution +// steps (CLARITY, output sharpen) run through `CLARITY_SKSL` from +// `toneShader.ts` — the same kernel `[0,-a,0,-a,1+4a,-a,0,-a,0]`, evaluated +// as a shader pass. `convolve` replaces the pixels outright (the shader's +// output is opaque and covers the same rect), so no clearing pass is needed. +// - The negative-side blurs stay on `ImageFilter.MakeBlur`, but a blur cannot +// be composited over the un-blurred content, so those passes snapshot, clear +// and redraw. Order matches RN's nested filter chain: denoise → clarity → +// mist → soften. +// - EXIF rewrite (`writeJpegExif`) is not ported yet; `patchJpegDpi(300)` +// alone keeps the print size metadata. +// ponytail: EXIF says "W4 if the web export needs the camera/lens tags". + +import { Skia } from './skiaShim'; +import type { Recipe, GPSInfo, FrameId, AspectRatio, CropRect } from '../../shared/types'; +import { ASPECT_RATIO_W_H } from '../../shared/types'; +import { getSkiaColorMatrix, applyExposureGain } from '../../shared/utils/colorUtils'; +import { + TONE_SKSL, + GLOW_SKSL, + CLARITY_SKSL, + getToneUniforms, + toneIsActive, + toneUniformArray, + glowUniformArray, +} from '../../shared/utils/toneShader'; +import { CINEMA_SKSL, getCinemaUniforms, cinemaIsActive } from '../../shared/utils/cinemaShader'; +import { + drawFrameOnCanvas, + polaroidLayout, + POLAROID_CARD, + POLAROID_WIN_W, + wallframeLayout, + WALLFRAME_W, + WALLFRAME_H, + WALLFRAME_LAND_W, + WALLFRAME_LAND_H, +} from '../../shared/utils/frameUtils'; +import { patchJpegDpi } from '../../shared/utils/jpegDpi'; +import { applyPhotoRotation } from '../../shared/utils/skiaImage'; + +export interface RenderAssets { + textFont: ArrayBuffer; // assets/Cousine-Regular.ttf + emojiFont: ArrayBuffer; // assets/NotoEmoji-GPS.ttf + wallframe: Uint8Array; // wallframe.png +} + +export interface RenderOptions { + dpi?: number; + sharpen?: boolean; + evFromCamera?: boolean; + aspect?: AspectRatio; + cropWH?: number; + cropRect?: CropRect | null; + watermark?: { text: string; x: number; y: number; color?: string; size?: number; font?: string | null } | null; + liteMark?: { text: string; x: number; y: number; color?: string; size?: number; font?: string | null } | null; + watermarkRotation?: number; + gpsWatermark?: { x: number; y: number; size: number; color?: string; showName?: boolean; showTime?: boolean } | null; + wallframeLandscape?: boolean; + frameWindowZoom?: { s: number; u: number; v: number } | null; + photoRotation?: 0 | 90 | 180 | 270; + photoStraighten?: number; +} + +export interface RenderInput { + source: Uint8Array; + recipe: Recipe; + frameId: FrameId; + useGeotag?: boolean; + gpsInfo?: GPSInfo | null; + options?: RenderOptions; + assets: RenderAssets; +} + +export interface RenderResult { + bytes: Uint8Array; + width: number; + height: number; +} + +type SkDisposable = { dispose(): void }; + +function disposeAll(items: SkDisposable[]): void { + for (let i = items.length - 1; i >= 0; i--) { + try { + items[i].dispose(); + } catch { + /* already released */ + } + } +} + +function release(owned: SkDisposable[], item: SkDisposable | null): void { + if (!item) return; + const i = owned.indexOf(item); + if (i >= 0) owned.splice(i, 1); + disposeAll([item]); +} + +function createSurface(width: number, height: number) { + return Skia.Surface.MakeOffscreen(width, height) ?? Skia.Surface.Make(width, height); +} + +// A WebGL surface batches draws; the readback paths (snapshot, encode) must see +// them submitted first. A no-op on the raster fallback. +function flush(surface: any): void { + if (typeof surface?.flush === 'function') surface.flush(); +} + +// --- shared runtime effects ------------------------------------------------- + +let sharpenEffect: any = null; +let toneEffect: any = null; +let cinemaEffect: any = null; +let glowEffect: any = null; + +function effects() { + if (!sharpenEffect) sharpenEffect = Skia.RuntimeEffect.Make(CLARITY_SKSL); + if (!toneEffect) toneEffect = Skia.RuntimeEffect.Make(TONE_SKSL); + if (!cinemaEffect) cinemaEffect = Skia.RuntimeEffect.Make(CINEMA_SKSL); + if (!glowEffect) glowEffect = Skia.RuntimeEffect.Make(GLOW_SKSL); + return { sharpenEffect, toneEffect, cinemaEffect, glowEffect }; +} + +// CLARITY_SKSL uniforms are (a, px.x, px.y); px = one source pixel = 1 unit on +// a 1:1 export canvas, so the radius matches what the preview tuned. +function convolvePaint(srcImage: any, amount: number): any { + const { sharpenEffect: effect } = effects(); + if (!effect) return null; + const paint = Skia.Paint(); + const child = srcImage.makeShaderOptions( + Skia.TileMode.Clamp, Skia.TileMode.Clamp, Skia.FilterMode.Linear, Skia.MipmapMode.None + ); + const shader = effect.makeShaderWithChildren([amount, 1, 1], [child]); + if (!shader) { + paint.dispose(); + child.dispose(); + return null; + } + paint.setShader(shader); + return { paint, shader, child }; +} + +// The convolution must sample the ALREADY GRADED pixels (RN runs it as the +// outermost filter of the draw), so the pass reads a snapshot of the surface +// and writes the sharpened result back over the same rect. +function convPass(canvas: any, surface: any, w: number, h: number, amount: number): void { + flush(surface); + const snap = surface.makeImageSnapshot(); + if (!snap) return; + drawConvolved(canvas, snap, w, h, amount); + snap.dispose(); +} + +// Replaces the pixels under the rect with the convolved result. +function drawConvolved(canvas: any, srcImage: any, w: number, h: number, amount: number): void { + const built = convolvePaint(srcImage, amount); + if (!built) return; + canvas.drawRect(Skia.XYWHRect(0, 0, w, h), built.paint); + disposeAll([built.paint, built.shader, built.child]); +} + +function convolveImage(image: any, amount: number): any { + const w = image.width(); + const h = image.height(); + const surf = createSurface(w, h); + if (!surf) return image; + try { + drawConvolved(surf.getCanvas(), image, w, h, amount); + return surf.makeImageSnapshot() ?? image; + } finally { + surf.dispose(); + } +} + +function screenSharpenImage(image: any, amount = 0.5): any { + return convolveImage(image, amount); +} + +// Snapshot → clear → blur redraw: the blur must not sit on top of the sharp +// pixels it is meant to replace. +function drawBlurred(canvas: any, surface: any, w: number, h: number, sigma: number): void { + flush(surface); + const snap = surface.makeImageSnapshot(); + const paint = Skia.Paint(); + const filter = Skia.ImageFilter.MakeBlur(sigma, sigma, Skia.TileMode.Clamp, null); + paint.setImageFilter(filter); + canvas.clear(Skia.Color('transparent')); + canvas.drawImage(snap, 0, 0, paint); + disposeAll([paint, filter, snap]); +} + +// --- fonts / artwork -------------------------------------------------------- + +let faces: { typeface: any; iconTypeface: any } | null = null; +let wallframeImage: any = null; + +function loadFaces(assets: RenderAssets) { + if (faces) return faces; + const typeface = Skia.Typeface.MakeFreeTypeFaceFromData(assets.textFont); + const iconTypeface = Skia.Typeface.MakeFreeTypeFaceFromData(assets.emojiFont); + if (!typeface || !iconTypeface) return null; + faces = { typeface, iconTypeface }; + // Named-family lookup for the custom watermark's FONT option: register the + // bundled face so at least that one name resolves on web. + try { + Skia.FontMgr.System().registerFace(typeface, 'Cousine'); + } catch { + /* provider optional */ + } + return faces; +} + +function loadWallframe(assets: RenderAssets) { + if (wallframeImage) return wallframeImage; + wallframeImage = Skia.Image.MakeImageFromEncoded(assets.wallframe); + return wallframeImage; +} + +export async function renderPhoto(input: RenderInput): Promise { + const { recipe, frameId, options } = input; + const owned: SkDisposable[] = []; + const own = (obj: T | null | undefined): T => { + if (obj) owned.push(obj as unknown as SkDisposable); + return obj as T; + }; + try { + const skImage0 = Skia.Image.MakeImageFromEncoded(input.source); + if (!skImage0) { + console.error('Failed to parse source image'); + return null; + } + own(skImage0); + let skImage = skImage0; + + const rotated = applyPhotoRotation(skImage, options?.photoRotation, options?.photoStraighten); + if (rotated && rotated !== skImage) { + own(rotated); + release(owned, skImage); + skImage = rotated; + } + let width = skImage.width(); + let height = skImage.height(); + + // 1b. Composition / FRAME-tab crop — identical maths to the RN engine. + const aspectWH = options?.aspect && options.aspect !== 'full' ? ASPECT_RATIO_W_H[options.aspect] : null; + const cropRect = options?.cropRect ?? null; + const ratioWH = cropRect ? null : options?.cropWH ?? aspectWH; + if ((cropRect || ratioWH) && frameId !== 'wallframe') { + const curAspect = width / height; + let cropW = width; + let cropH = height; + let srcX = 0; + let srcY = 0; + if (cropRect) { + srcX = Math.max(0, Math.min(width - 1, Math.round(cropRect.x * width))); + srcY = Math.max(0, Math.min(height - 1, Math.round(cropRect.y * height))); + cropW = Math.max(1, Math.min(width - srcX, Math.round(cropRect.w * width))); + cropH = Math.max(1, Math.min(height - srcY, Math.round(cropRect.h * height))); + } else if (curAspect > (ratioWH as number)) { + cropW = Math.round(height * (ratioWH as number)); + srcX = Math.round((width - cropW) / 2); + } else { + cropH = Math.round(width / (ratioWH as number)); + srcY = Math.round((height - cropH) / 2); + } + const cropSurface = createSurface(cropW, cropH); + if (!cropSurface) return null; + own(cropSurface); + cropSurface.getCanvas().drawImageRect( + skImage, + Skia.XYWHRect(srcX, srcY, cropW, cropH), + Skia.XYWHRect(0, 0, cropW, cropH), + own(Skia.Paint()) + ); + const cropped = cropSurface.makeImageSnapshot(); + if (!cropped) return null; + own(cropped); + release(owned, skImage); + release(owned, cropSurface); + skImage = cropped; + width = cropW; + height = cropH; + } + + // 2. Render surface. + const surface = createSurface(width, height); + if (!surface) return null; + own(surface); + const canvas = surface.getCanvas(); + const paint = own(Skia.Paint()); + + const adjustments = recipe.adjustments; + + // 3. Colour matrix (+2^EV gain unless the camera already biased the shot). + const evStops = adjustments.exposureCompensation ?? 0; + const evMatrix = options?.evFromCamera + ? getSkiaColorMatrix(recipe.baseFilter, adjustments) + : applyExposureGain(getSkiaColorMatrix(recipe.baseFilter, adjustments), evStops); + const colorFilter = own(Skia.ColorFilter.MakeMatrix(evMatrix)); + paint.setColorFilter(colorFilter); + + // 3b. Tone shader. + const tone = getToneUniforms(adjustments, recipe.baseFilter); + let toneShader: any = null; + if (toneIsActive(tone)) { + const { toneEffect: effect } = effects(); + if (effect) { + const imageShader = own( + skImage.makeShaderOptions( + Skia.TileMode.Clamp, Skia.TileMode.Clamp, Skia.FilterMode.Linear, Skia.MipmapMode.None + ) + ); + toneShader = own(effect.makeShaderWithChildren(toneUniformArray(tone), [imageShader])); + if (toneShader) paint.setShader(toneShader); + } + } + + // 3c. Cinema seasonal grade (cinema → tone → image). + const cinema = getCinemaUniforms(recipe.cinema); + let paintShader = toneShader; + if (cinema && cinemaIsActive(cinema.flat)) { + const { cinemaEffect: effect } = effects(); + if (effect) { + const child = + toneShader ?? + own(skImage.makeShaderOptions( + Skia.TileMode.Clamp, Skia.TileMode.Clamp, Skia.FilterMode.Linear, Skia.MipmapMode.None + )); + const cs = own(effect.makeShaderWithChildren(cinema.flat, [child])); + if (cs) { + paintShader = cs; + paint.setShader(paintShader); + } + } + } + + // 4. Denoise (blur only — CLARITY moved to its own pass below, see header). + if (adjustments.denoise > 0) { + const sigma = (adjustments.denoise / 10) * 0.6; + paint.setImageFilter(own(Skia.ImageFilter.MakeBlur(sigma, sigma, Skia.TileMode.Clamp, null))); + } + + // 5. Draw the graded photo. + if (paintShader) { + canvas.drawRect(Skia.XYWHRect(0, 0, width, height), paint); + } else { + canvas.drawImage(skImage, 0, 0, paint); + } + + // 4b. CLARITY (positive) — convolution outside the denoise blur, the same + // nesting RN builds with MakeMatrixConvolution(input = previous filter). + if (adjustments.clarity > 0) { + convPass(canvas, surface, width, height, (adjustments.clarity / 10) * 0.8); + } else if (adjustments.clarity < 0) { + const mistSigma = Math.abs(adjustments.clarity / 10) * 4; + drawBlurred(canvas, surface, width, height, mistSigma); + } + // Negative SHARPENING takes edge enhancement back out — after the conv, so + // it cannot blur away what the CLARITY pass just added. + const sharpKnob = adjustments.sharpening ?? 0; + if (sharpKnob < 0) { + drawBlurred(canvas, surface, width, height, (Math.abs(sharpKnob) / 10) * 3); + } + + // 5b. HDF bright-pass bloom. + if ((adjustments.hdf ?? 0) > 0) { + const strength = (adjustments.hdf ?? 0) / 10; + const sigma = width * (0.004 + 0.015 * strength); + const { glowEffect: effect } = effects(); + const srcShader = + paintShader ?? + own(skImage.makeShaderOptions( + Skia.TileMode.Clamp, Skia.TileMode.Clamp, Skia.FilterMode.Linear, Skia.MipmapMode.None + )); + const glowShader = + effect != null ? own(effect.makeShaderWithChildren(glowUniformArray(), [srcShader])) : null; + const glowPaint = own(Skia.Paint()); + glowPaint.setBlendMode(Skia.BlendMode.Screen); + glowPaint.setAlphaf(0.15 + 0.35 * strength); + if (glowShader != null) { + glowPaint.setShader(glowShader); + glowPaint.setImageFilter( + own(Skia.ImageFilter.MakeCompose( + own(Skia.ImageFilter.MakeBlur(sigma, sigma, Skia.TileMode.Clamp, null)), + own(Skia.ImageFilter.MakeColorFilter(colorFilter, null)) + )) + ); + canvas.drawRect(Skia.XYWHRect(0, 0, width, height), glowPaint); + } else { + const hiMatrix = [ + 2.5, 0, 0, 0, -1.5, + 0, 2.5, 0, 0, -1.5, + 0, 0, 2.5, 0, -1.5, + 0, 0, 0, 1, 0, + ]; + glowPaint.setImageFilter( + own(Skia.ImageFilter.MakeCompose( + own(Skia.ImageFilter.MakeBlur(sigma, sigma, Skia.TileMode.Clamp, null)), + own(Skia.ImageFilter.MakeColorFilter( + own(Skia.ColorFilter.MakeCompose( + own(Skia.ColorFilter.MakeMatrix(hiMatrix)), + colorFilter + )), + null + )) + )) + ); + if (paintShader) { + glowPaint.setShader(paintShader); + canvas.drawRect(Skia.XYWHRect(0, 0, width, height), glowPaint); + } else { + canvas.drawImage(skImage, 0, 0, glowPaint); + } + } + } + + // 6. Monochrome grain overlay (negative NR re-grains, same ceiling as GRAIN). + const grainAmount = Math.min( + 10, + Math.max(0, adjustments.grain) + Math.max(0, -(adjustments.denoise ?? 0)) + ); + if (grainAmount > 0) { + const grainPaint = own(Skia.Paint()); + grainPaint.setBlendMode(Skia.BlendMode.Overlay); + grainPaint.setAlphaf(grainAmount / 20); + const noiseEffect = own(Skia.RuntimeEffect.Make(` + uniform float u; + float grainHash(vec2 q) { + return fract(sin(dot(q, vec2(12.9898, 78.233))) * 43758.5453); + } + vec4 main(vec2 pos) { + vec2 p = pos.xy / max(u, 1.0); + vec2 i = floor(p); + vec2 f = p - i; + f = f * f * (3.0 - 2.0 * f); + float g00 = grainHash(i); + float g10 = grainHash(i + vec2(1.0, 0.0)); + float g01 = grainHash(i + vec2(0.0, 1.0)); + float g11 = grainHash(i + vec2(1.0, 1.0)); + return vec4(vec3(mix(mix(g00, g10, f.x), mix(g01, g11, f.x), f.y)), 1.0); + } + `)); + const noiseShader = noiseEffect ? own(noiseEffect.makeShader([width / 1080])) : null; + if (noiseShader) { + grainPaint.setShader(noiseShader); + canvas.drawRect(Skia.XYWHRect(0, 0, width, height), grainPaint); + } + } + + // 6b. Vignette. + if ((adjustments.vignette ?? 0) > 0) { + const v = Math.min(10, adjustments.vignette ?? 0) / 10; + const vignetteShader = own(Skia.Shader.MakeRadialGradient( + Skia.Point(width / 2, height / 2), + Math.hypot(width, height) / 2, + [Skia.Color('rgba(0,0,0,0)'), Skia.Color('rgba(0,0,0,0)'), Skia.Color('rgba(0,0,0,1)')], + [0, 0.45, 1], + Skia.TileMode.Clamp + )); + if (vignetteShader) { + const vignettePaint = own(Skia.Paint()); + vignettePaint.setShader(vignetteShader); + vignettePaint.setAlphaf(0.9 * v); + canvas.drawRect(Skia.XYWHRect(0, 0, width, height), vignettePaint); + } + } + + release(owned, skImage); + + // 7. Frame. + let outputSurface = surface; + let stampCanvas = canvas; + let stampW = width; + let stampH = height; + let cardSource: any = null; + if (frameId === 'polaroid') { + flush(surface); + const photoImg = own(surface.makeImageSnapshot()); + cardSource = photoImg; + const outW = width; + const frameAspect = + ratioWH ?? + (options?.photoRotation === 90 || options?.photoRotation === 270 + ? height / width + : width / height); + const outH = Math.round( + width * (POLAROID_CARD.sideMargin + POLAROID_CARD.bottomDeck) + + (width * POLAROID_WIN_W) / frameAspect + ); + const cardSurface = createSurface(outW, outH); + if (!cardSurface) return null; + own(cardSurface); + const cardCanvas = cardSurface.getCanvas(); + const bgPaint = own(Skia.Paint()); + bgPaint.setColor(Skia.Color('#faf9f6')); + cardCanvas.drawRect(Skia.XYWHRect(0, 0, outW, outH), bgPaint); + const pl = polaroidLayout(outW, outH, frameAspect, 'contain'); + const win = pl.window; + const plainPaint = own(Skia.Paint()); + const pz = options?.frameWindowZoom ?? null; + const pwz = pz && pz.s > 1.001 ? pz : null; + const pVisW = pwz ? width / pwz.s : width; + const pVisH = pwz ? height / pwz.s : height; + const pSx = pwz ? Math.min(Math.max(width * pwz.u - pVisW / 2, 0), width - pVisW) : 0; + const pSy = pwz ? Math.min(Math.max(height * pwz.v - pVisH / 2, 0), height - pVisH) : 0; + const srcAspect = width / height; + let dstW = win.w; + let dstH = dstW / srcAspect; + if (dstH > win.h) { + dstH = win.h; + dstW = dstH * srcAspect; + } + cardCanvas.drawImageRect( + photoImg, + Skia.XYWHRect(pSx, pSy, pVisW, pVisH), + Skia.XYWHRect(win.x + (win.w - dstW) / 2, win.y + (win.h - dstH) / 2, dstW, dstH), + plainPaint + ); + const seamPaint = own(Skia.Paint()); + seamPaint.setColor(Skia.Color('#e5e5e5')); + seamPaint.setStyle(1); + seamPaint.setStrokeWidth(1); + cardCanvas.drawRect(Skia.XYWHRect(win.x + 0.5, win.y + 0.5, win.w - 1, win.h - 1), seamPaint); + outputSurface = cardSurface; + stampCanvas = cardCanvas; + stampW = outW; + stampH = outH; + } else if (frameId === 'wallframe') { + flush(surface); + const photoImg = own(surface.makeImageSnapshot()); + cardSource = photoImg; + const pw = photoImg.width(); + const ph = photoImg.height(); + const wallLand = options?.wallframeLandscape === true; + const outW = wallLand ? WALLFRAME_LAND_W : WALLFRAME_W; + const outH = wallLand ? WALLFRAME_LAND_H : WALLFRAME_H; + const cardSurface = createSurface(outW, outH); + if (!cardSurface) return null; + own(cardSurface); + const cardCanvas = cardSurface.getCanvas(); + const wfImage = loadWallframe(input.assets); + if (!wfImage) { + console.error('Failed to decode wallframe.png'); + return null; + } + const wl = wallframeLayout(outW, outH, 'fill', wallLand); + const win = wl.window; + const plainPaint = own(Skia.Paint()); + const scale = Math.max(win.w / pw, win.h / ph); + const srcW = win.w / scale; + const srcH = win.h / scale; + const planeX = (pw - srcW) / 2; + const planeY = (ph - srcH) / 2; + const wz = options?.frameWindowZoom ?? null; + const wZoom = wz && wz.s > 1.001 ? wz : null; + const wVisW = wZoom ? srcW / wZoom.s : srcW; + const wVisH = wZoom ? srcH / wZoom.s : srcH; + const wSx = wZoom + ? Math.min(Math.max(planeX + srcW * wZoom.u - wVisW / 2, planeX), planeX + srcW - wVisW) + : planeX; + const wSy = wZoom + ? Math.min(Math.max(planeY + srcH * wZoom.v - wVisH / 2, planeY), planeY + srcH - wVisH) + : planeY; + cardCanvas.drawImageRect( + photoImg, + Skia.XYWHRect(wSx, wSy, wVisW, wVisH), + Skia.XYWHRect(win.x, win.y, win.w, win.h), + plainPaint + ); + cardCanvas.save(); + if (wallLand) { + cardCanvas.drawImage(wfImage, 0, 0, plainPaint); + } else { + cardCanvas.translate(outW, 0); + cardCanvas.rotate(90, 0, 0); + cardCanvas.drawImage(wfImage, 0, 0, plainPaint); + } + cardCanvas.restore(); + outputSurface = cardSurface; + stampCanvas = cardCanvas; + stampW = outW; + stampH = outH; + } else { + drawFrameOnCanvas(canvas, width, height, frameId); + } + if (cardSource) { + release(owned, cardSource); + release(owned, surface); + } + + // 8. GPS + custom watermark. + const customWm = options?.watermark ?? null; + const liteWm = options?.liteMark ?? null; + if ((input.useGeotag && input.gpsInfo) || customWm || liteWm) { + const loaded = loadFaces(input.assets); + if (loaded) { + const { typeface, iconTypeface } = loaded; + const fontSize = Math.round(stampW * 0.032); + const textPaint = own(Skia.Paint()); + textPaint.setColor(Skia.Color('#f59e0b')); + const iconPaint = own(Skia.Paint()); + iconPaint.setColor(Skia.Color('#ffffff')); + + if (input.useGeotag && input.gpsInfo) { + const locationName = input.gpsInfo.locality || 'STREET VIEW'; + const d = new Date(input.gpsInfo.timestamp); + const pad = (n: number) => String(n).padStart(2, '0'); + const timestampStr = `${pad(d.getDate())}/${pad(d.getMonth() + 1)}/${d.getFullYear()} ${pad(d.getHours())}:${pad(d.getMinutes())}`; + const gpsPos = options?.gpsWatermark ?? null; + const clamp01 = (v: number) => Math.max(0, Math.min(1, v)); + const gpsSize = Math.round(fontSize * (gpsPos?.size ?? 1)); + const gpsFont = own(Skia.Font(typeface, gpsSize)); + const gpsIconFont = own(Skia.Font(iconTypeface, gpsSize)); + const gpsGap = Math.round(gpsSize * 1.6); + const baseX = clamp01(gpsPos?.x ?? 0.05) * stampW; + const baseY = clamp01(gpsPos?.y ?? 0.1) * stampH; + const lines: { icon: string; text: string }[] = []; + if (gpsPos?.showName !== false) lines.push({ icon: '📍', text: locationName }); + if (gpsPos?.showTime !== false) lines.push({ icon: '📷', text: timestampStr }); + textPaint.setColor(Skia.Color(gpsPos?.color ?? '#f59e0b')); + const wmRot = options?.watermarkRotation ?? 0; + stampCanvas.save(); + if (wmRot) stampCanvas.rotate(wmRot, baseX, baseY); + lines.forEach((line, i) => { + const rowY = baseY + Math.round(gpsSize * (1.1 + i * 1.2)); + stampCanvas.drawText(line.icon, baseX, rowY, iconPaint, gpsIconFont); + stampCanvas.drawText(line.text, baseX + gpsGap, rowY, textPaint, gpsFont); + }); + stampCanvas.restore(); + } + + if (customWm || liteWm) { + const clamp01 = (v: number) => Math.max(0, Math.min(1, v)); + const wmRot = options?.watermarkRotation ?? 0; + for (const mark of [customWm, liteWm]) { + if (!mark) continue; + const wmSize = Math.round(fontSize * (mark.size ?? 1)); + const wmFace = mark.font ? Skia.FontMgr.System().matchFamilyStyle(mark.font) : null; + const wmFont = own(Skia.Font(wmFace ?? typeface, wmSize)); + const wmPaint = own(Skia.Paint()); + wmPaint.setColor(Skia.Color(mark.color ?? '#f59e0b')); + const tx = clamp01(mark.x) * stampW; + const ty = clamp01(mark.y) * stampH + Math.round(wmSize * 0.35); + stampCanvas.save(); + if (wmRot) stampCanvas.rotate(wmRot, tx, ty); + stampCanvas.drawText(mark.text, tx, ty, wmPaint, wmFont); + stampCanvas.restore(); + } + } + } + } + + // 9. Snapshot, optional output sharpen, JPEG encode + DPI patch. + flush(outputSurface); + let resultImage = own(outputSurface.makeImageSnapshot()); + const sharpenAmount = + sharpKnob > 0 ? (sharpKnob / 10) * 0.8 : sharpKnob < 0 ? 0 : options?.sharpen ? 0.5 : 0; + if (sharpenAmount > 0) { + const sharpened = screenSharpenImage(resultImage, sharpenAmount); + if (sharpened && sharpened !== resultImage) { + own(sharpened); + release(owned, resultImage); + resultImage = sharpened; + } + } + let bytes = resultImage.encodeToBytes(Skia.ImageFormat.JPEG, 95); + if (!bytes || bytes.length === 0) { + console.error('Failed to encode image to JPEG'); + return null; + } + if (options?.dpi && options.dpi > 0) { + bytes = patchJpegDpi(bytes, options.dpi); + } + return { bytes, width: resultImage.width(), height: resultImage.height() }; + } catch (error) { + console.error('Render failed:', error); + return null; + } finally { + disposeAll(owned); + } +} diff --git a/docker/frontend/src/engine/imageOps.ts b/docker/frontend/src/engine/imageOps.ts new file mode 100644 index 0000000..13b8b9f --- /dev/null +++ b/docker/frontend/src/engine/imageOps.ts @@ -0,0 +1,58 @@ +import exifr from 'exifr'; +import type { GPSInfo } from '../../shared/types'; + +// Browser-side input handling: turn a dropped File into the source bytes the +// engine wants, plus the two derived things the UI needs — a smaller copy for +// the live preview, and whatever GPS the photo already carries. + +export async function fileToBytes(file: File | Blob): Promise { + return new Uint8Array(await file.arrayBuffer()); +} + +// Preview renders run on a downscaled copy: the grade is pixel-wise, so grading +// a 1600px copy shows the same look, and it keeps the slider responsive. Export +// always runs on the original bytes. +export async function resizedJpeg(bytes: Uint8Array, maxDim: number, quality = 0.92): Promise { + const bitmap = await createImageBitmap(new Blob([bytes as BlobPart])); + try { + const longest = Math.max(bitmap.width, bitmap.height); + if (longest <= maxDim) return bytes; + const scale = maxDim / longest; + const w = Math.max(1, Math.round(bitmap.width * scale)); + const h = Math.max(1, Math.round(bitmap.height * scale)); + const canvas = new OffscreenCanvas(w, h); + const ctx = canvas.getContext('2d'); + if (!ctx) return bytes; + ctx.drawImage(bitmap, 0, 0, w, h); + const blob = await canvas.convertToBlob({ type: 'image/jpeg', quality }); + return new Uint8Array(await blob.arrayBuffer()); + } finally { + bitmap.close(); + } +} + +// EXIF GPS of the loaded photo, in the shape the renderer + EXIF writer expect. +// Returns null when the photo has none — the caller then offers manual entry. +export async function readGps(bytes: Uint8Array): Promise { + try { + const gps = await exifr.gps(bytes); + if (!gps || typeof gps.latitude !== 'number' || typeof gps.longitude !== 'number') return null; + return { + latitude: gps.latitude, + longitude: gps.longitude, + timestamp: Date.now(), + source: 'exif', + }; + } catch { + return null; + } +} + +export function download(bytes: Uint8Array, filename: string): void { + const url = URL.createObjectURL(new Blob([bytes as BlobPart], { type: 'image/jpeg' })); + const a = document.createElement('a'); + a.href = url; + a.download = filename; + a.click(); + setTimeout(() => URL.revokeObjectURL(url), 10_000); +} diff --git a/docker/frontend/src/engine/skiaShim.ts b/docker/frontend/src/engine/skiaShim.ts new file mode 100644 index 0000000..e95999f --- /dev/null +++ b/docker/frontend/src/engine/skiaShim.ts @@ -0,0 +1,213 @@ +// CanvasKit-backed stand-in for `@shopify/react-native-skia`. +// +// The web build aliases the package name to THIS module (vite `resolve.alias`, +// tsconfig `paths`), so every renderer we share with the app — `frameUtils.ts`, +// `skiaImage.ts`, the ported `exportEngine.ts` — runs unchanged on CanvasKit. +// +// W0 probe results that shaped this file (measured in Node, canvaskit-wasm 0.42): +// - the default build CANNOT encode JPEG (`encodeToBytes(JPEG)` → null), the +// `canvaskit-wasm/full` build can → always import the full build (+0.9MB wasm); +// - `Skia.ImageFilter.MakeMatrixConvolution` DOES NOT EXIST in CanvasKit → the +// two convolution steps (CLARITY, output sharpen) are re-implemented as an +// SkSL runtime effect, see convolve.ts; +// - `Surface.MakeOffscreen` does not exist either → one shared GrDirectContext +// + `MakeRenderTarget` for GPU, raster `MakeSurface` as the no-GL fallback +// (see gpuSurface below for why the context must be shared); +// - `Skia.Font`/`Typeface.MakeFreeTypeFaceFromData`/`Canvas.drawText` all work, +// and `assets/NotoEmoji-GPS.ttf` really paints 📍/📷 (1731 lit pixels in the +// probe) → GPS watermark needs no Canvas2D fallback. + +import CanvasKitInit from 'canvaskit-wasm/full'; + +// Live binding: modules import `{ Skia }` once, `initSkia()` fills it in before +// the first render. ESM live bindings make the late assignment visible. +export let Skia: any = null; +export let CK: any = null; +// `frameUtils.ts` imports `BlendMode` as a value, so it has to be a live +// binding too — filled in once CanvasKit is up, long before any draw. +export let BlendMode: any = null; + +export type SkCanvas = any; +export type SkPaint = any; +export type SkImage = any; +export type SkShader = any; +export type SkSurface = any; +export type SkTypeface = any; +export type SkFont = any; + +// CanvasKit's EMBind objects are released with `delete()`; the RN Skia API (and +// therefore the shared renderers) calls `dispose()`. +const DISPOSABLE = [ + 'Image', 'Surface', 'Paint', 'Shader', 'ImageFilter', 'ColorFilter', + 'Typeface', 'Font', 'RuntimeEffect', 'Path', 'Canvas', 'BlendMode', +]; + +function patchPrototypes(canvasKit: any): void { + for (const name of DISPOSABLE) { + const proto = canvasKit[name]?.prototype; + if (proto && typeof proto.delete === 'function' && typeof proto.dispose !== 'function') { + proto.dispose = function dispose() { this.delete(); }; + } + } + // frameUtils draws strokes with the numeric RN style (0 = fill, 1 = stroke). + const setStyle = canvasKit.Paint.prototype.setStyle; + canvasKit.Paint.prototype.setStyle = function (style: any) { + return setStyle.call( + this, + typeof style === 'number' + ? (style === 1 ? canvasKit.PaintStyle.Stroke : canvasKit.PaintStyle.Fill) + : style + ); + }; +} + +export interface InitSkiaOptions { + // Browser: path of canvaskit.wasm (Vite serves it as an asset). Node: omitted. + locateFile?: (file: string) => string; +} + +function rasterSurface(w: number, h: number) { + return CK.MakeSurface(w, h); +} + +// ONE GrDirectContext for every surface. `MakeWebGLCanvasSurface` makes its own +// WebGL context per call, and a texture from one context cannot be sampled by a +// surface on another: the snapshot of surface A drawn onto surface B came back +// solid black (measured — 12MP JPEG + output sharpen, preview and export alike, +// GL 0.0 vs raster 107.0 mean). Sharing the context fixes it. +let grContext: any = null; + +function ensureGrContext(): any { + if (grContext) return grContext; + if (typeof OffscreenCanvas === 'undefined') return null; + try { + grContext = CK.MakeWebGLContext(CK.GetWebGLContext(new OffscreenCanvas(8, 8))); + } catch { + grContext = null; + } + return grContext; +} + +function gpuSurface(w: number, h: number) { + const ctx = ensureGrContext(); + if (!ctx) return null; + try { + return CK.MakeRenderTarget(ctx, w, h) ?? null; + } catch { + return null; // no GL context available — caller falls back to raster + } +} + +// Callers (the app's engine banner) ask the same context the renderer will use, +// so "GPU ok" cannot disagree with what the pipeline actually gets. +export function hasGpu(): boolean { + return !!ensureGrContext(); +} + +export async function initSkia(options: InitSkiaOptions = {}): Promise { + if (Skia) return Skia; + const canvasKit = await (CanvasKitInit as any)( + options.locateFile ? { locateFile: options.locateFile } : undefined + ); + CK = canvasKit; + BlendMode = canvasKit.BlendMode; + patchPrototypes(canvasKit); + + Skia = { + // --- geometry / color --------------------------------------------------- + XYWHRect: (x: number, y: number, w: number, h: number) => canvasKit.XYWHRect(x, y, w, h), + Point: (x: number, y: number) => [x, y], + Color: (value: string) => + value === 'transparent' + ? canvasKit.Color(0, 0, 0, 0) + : canvasKit.parseColorString(value), + Color4f: (r: number, g: number, b: number, a: number) => canvasKit.Color4f(r, g, b, a), + + // --- factories ---------------------------------------------------------- + Paint: () => new canvasKit.Paint(), + Font: (typeface: any, size: number) => new canvasKit.Font(typeface, size), + Data: { + // CanvasKit exposes no Data class; callers pass bytes straight through. + fromBytes: (bytes: Uint8Array) => bytes, + }, + Image: { + MakeImageFromEncoded: (data: Uint8Array) => + canvasKit.MakeImageFromEncoded( + data instanceof Uint8Array ? data : new Uint8Array(data as ArrayBuffer) + ), + }, + Typeface: { + MakeFreeTypeFaceFromData: (data: ArrayBuffer) => + canvasKit.Typeface.MakeFreeTypeFaceFromData(data), + }, + ColorFilter: { + MakeMatrix: (m: number[]) => canvasKit.ColorFilter.MakeMatrix(m), + MakeCompose: (outer: any, inner: any) => canvasKit.ColorFilter.MakeCompose(outer, inner), + }, + ImageFilter: { + MakeBlur: (sx: number, sy: number, tile: any, input: any) => + canvasKit.ImageFilter.MakeBlur(sx, sy, tile, input ?? null), + MakeCompose: (outer: any, inner: any) => + canvasKit.ImageFilter.MakeCompose(outer ?? null, inner ?? null), + MakeColorFilter: (cf: any, input: any) => + canvasKit.ImageFilter.MakeColorFilter(cf, input ?? null), + }, + Shader: { + MakeRadialGradient: ( + center: number[], radius: number, colors: any[], pos: number[], mode: any + ) => canvasKit.Shader.MakeRadialGradient(center, radius, colors, pos, mode), + }, + RuntimeEffect: { + Make: (sksl: string) => canvasKit.RuntimeEffect.Make(sksl), + }, + Surface: { + // A WebGL-backed surface is not a nicety here, it is the difference + // between usable and unusable: the same 12MP pipeline that finishes in + // well under a second on a GL surface took 3.2s for ONE tone pass and + // 13.7s for the CLARITY tap in the Node CPU raster benchmark. Raster is + // the fallback for a context-less environment (Node spikes, tests). + Make: (w: number, h: number) => rasterSurface(w, h), + MakeOffscreen: (w: number, h: number) => gpuSurface(w, h) ?? rasterSurface(w, h), + }, + FontMgr: { + System: () => fontMgr, + }, + + // --- enums -------------------------------------------------------------- + BlendMode: canvasKit.BlendMode, + TileMode: canvasKit.TileMode, + FilterMode: canvasKit.FilterMode, + MipmapMode: canvasKit.MipmapMode, + FontStyle: canvasKit.FontStyle, + ImageFormat: canvasKit.ImageFormat, + ColorType: canvasKit.ColorType, + ColorSpace: canvasKit.ColorSpace, + AlphaType: canvasKit.AlphaType, + PaintStyle: canvasKit.PaintStyle, + }; + + fontMgr = makeFontMgr(canvasKit); + return Skia; +} + +// Named-family lookup for the custom watermark's FONT option. Only faces the +// bundle ships can be matched on web; anything else falls back to the caller's +// default typeface — the honest behaviour when there is no OS font service. +let fontMgr: any = null; + +function makeFontMgr(canvasKit: any) { + const provider = canvasKit.TypefaceFontProvider.Make(); + return { + registerFace(typeface: any, familyName: string) { + provider.registerFont(typeface, familyName); + }, + matchFamilyStyle(family: string) { + if (!family) return null; + try { + return provider.matchFamilyStyle?.(family, canvasKit.FontStyle.Normal) ?? null; + } catch { + return null; + } + }, + }; +} diff --git a/docker/frontend/src/i18n/I18nProvider.tsx b/docker/frontend/src/i18n/I18nProvider.tsx new file mode 100644 index 0000000..b093a07 --- /dev/null +++ b/docker/frontend/src/i18n/I18nProvider.tsx @@ -0,0 +1,64 @@ +import { createContext, useCallback, useContext, useEffect, useMemo, useState, type ReactNode } from 'react'; +import { vi, type Dict, type MsgKey } from './vi'; +import { en } from './en'; + +// Two dictionaries, no i18n library: the whole set is a few KB of text and the +// `Dict` type makes a missing English key a build error. Reach for a library +// (or lazy-loading) when the dictionaries outgrow the bundle — not before. + +export type Lang = 'vi' | 'en'; + +const DICTS: Record = { vi, en }; +const KEY = 'rc.lang'; + +function readLang(): Lang { + const stored = localStorage.getItem(KEY); + if (stored === 'vi' || stored === 'en') return stored; + return (navigator.language || '').toLowerCase().startsWith('vi') ? 'vi' : 'en'; +} + +interface I18nValue { + lang: Lang; + setLang: (lang: Lang) => void; + t: (key: MsgKey, params?: Record) => string; +} + +const Ctx = createContext(null); + +export function I18nProvider({ children }: { children: ReactNode }) { + const [lang, setLangState] = useState(readLang); + + const setLang = useCallback((next: Lang) => { + localStorage.setItem(KEY, next); + setLangState(next); + }, []); + + useEffect(() => { + document.documentElement.lang = lang; + }, [lang]); + + const value = useMemo(() => ({ + lang, + setLang, + t: (key, params) => { + let text: string = DICTS[lang][key] ?? vi[key] ?? key; + if (params) { + for (const [name, v] of Object.entries(params)) text = text.replace(`{${name}}`, String(v)); + } + return text; + }, + }), [lang, setLang]); + + return {children}; +} + +export function useI18n(): I18nValue { + const value = useContext(Ctx); + if (!value) throw new Error('useI18n used outside I18nProvider'); + return value; +} + +export const LANGS: { id: Lang; label: string }[] = [ + { id: 'vi', label: vi['lang.name'] }, + { id: 'en', label: en['lang.name'] }, +]; diff --git a/docker/frontend/src/i18n/en.ts b/docker/frontend/src/i18n/en.ts new file mode 100644 index 0000000..f7a96b1 --- /dev/null +++ b/docker/frontend/src/i18n/en.ts @@ -0,0 +1,124 @@ +import type { Dict } from './vi'; + +export const en: Dict = { + 'lang.name': 'English', + + 'nav.demo': 'TRY DEMO', + 'nav.login': 'LOG IN', + 'nav.signup': 'SIGN UP', + 'nav.logout': 'Log out', + 'nav.account': 'Account', + 'nav.theme': 'Theme', + 'nav.language': 'Language', + 'nav.back': 'Back to home', + 'nav.guest': 'Guest', + + 'act.undo': 'UNDO', + 'act.reset': 'RESET', + 'act.export': 'EXPORT', + 'act.exporting': 'Exporting…', + 'act.cancel': 'Cancel', + + 'land.title': 'The same colour recipes, now on your desktop', + 'land.lede': + 'RecipesCam turns a photo into a film-looking one with recipes. The web build runs the app\u2019s exact colour pipeline: same recipes, same frames, same GPS stamp.', + 'land.ctaDemo': 'OPEN THE DEMO', + 'land.ctaSignup': 'CREATE AN ACCOUNT', + 'land.f1t': 'Film recipes', + 'land.f1d': 'PROVIA, VELVIA, CLASSIC CHROME… plus EXPOSURE, WB, CLARITY, HDF and VIGNETTING.', + 'land.f2t': 'Frames & watermark', + 'land.f2d': 'Polaroid, classic border, WALL FRAME, custom text and a GPS stamp read from the photo\u2019s EXIF.', + 'land.f3t': 'Processed on your own machine', + 'land.f3d': 'The photo never leaves the browser. The server only keeps accounts and the recipes you save.', + 'land.badgePlay': 'Get it on Google Play', + 'land.badgeApp': 'Download on the App Store', + 'land.storeSoon': 'Coming soon', + + 'drop.title': 'Drag & drop a photo here', + 'drop.hint': 'or click to pick one · paste from the clipboard with Ctrl+V', + 'drop.replace': 'Choose another photo', + 'drop.peeking': 'SHOWING THE ORIGINAL', + 'drop.peekHint': 'Hold right-click or the \\ key to compare with the original', + 'drop.demoLimit': 'Demo build: exports are capped at {px}px and carry a watermark.', + + 'tab.presets': 'PRESETS', + 'tab.favorited': 'FAVORITED', + 'tab.light': 'LIGHT', + 'tab.wb': 'WB', + 'tab.fx': 'FX', + 'tab.frame': 'FRAME', + + 'sec.filmSims': 'PHOTO STYLE', + 'sec.recipes': 'RECIPES', + 'sec.saved': 'SAVED', + 'sec.savedEmpty': 'No saved recipes yet. Log in and hit SAVE to sync them.', + 'sec.frame': 'FRAME', + 'sec.crop': 'CROP', + 'sec.rotate': 'ROTATE', + 'sec.watermark': 'WATERMARK', + 'sec.gps': 'GPS', + + 'wb.temp': 'COLOR TEMP', + + 'frame.none': 'NO FRAME', + 'frame.polaroid': 'RETRO INSTANT', + 'frame.classic': 'CLASSIC BORDER', + 'frame.cinematic': 'CINEMATIC', + 'frame.wall': 'WALL FRAME', + 'frame.wallLand': 'WALL LANDSCAPE', + + 'crop.none': 'NO CROP', + 'crop.1:1': '1:1', + 'crop.2:3': '2:3', + 'crop.3:2': '3:2', + 'crop.3:4': '3:4', + 'crop.4:3': '4:3', + 'crop.16:9': '16:9', + 'crop.free': 'FREE', + + 'rot.0': '0°', + 'rot.90': '90°', + 'rot.180': '180°', + 'rot.270': '270°', + + 'wm.text': 'TEXT', + 'wm.color': 'COLOR', + 'wm.size': 'SIZE', + 'wm.gps': 'GPS STAMP', + 'wm.place': 'PLACE NAME', + 'wm.placePh': 'e.g. HANOI', + 'wm.manual': 'ENTER COORDINATES', + 'wm.lat': 'Latitude', + 'wm.lng': 'Longitude', + 'wm.hasExif': 'GPS read from the photo.', + 'wm.noExif': 'This photo has no GPS — type it in if you want the stamp.', + + 'auth.loginTitle': 'Log in', + 'auth.signupTitle': 'Create an account', + 'auth.email': 'Email', + 'auth.password': 'Password', + 'auth.passwordHint': 'At least 8 characters', + 'auth.noAccount': 'No account yet? Sign up', + 'auth.haveAccount': 'Already have an account? Log in', + 'auth.continueDemo': 'Keep using the demo', + 'auth.busy': 'Working…', + 'auth.loggedInAs': 'Signed in as {email}', + + 'save.title': 'Save recipe', + 'save.name': 'Recipe name', + 'save.save': 'SAVE', + 'save.saved': 'Saved "{name}"', + 'save.needLogin': 'Log in to save a recipe.', + + 'err.noWebgl': + 'The browser refused a WebGL context — this build needs WebGL to process photos (the CPU path is thousands of times slower).', + 'err.decode': 'Could not read this image. Try another JPG or PNG.', + 'err.generic': 'Something went wrong: {msg}', + 'err.offline': 'Could not reach the server.', + + 'meta.ready': 'Ready', + 'meta.rendered': 'Rendered in {ms}ms · {w}×{h}', + 'meta.exported': 'Downloaded {name} ({kb}KB, 300 ppi)', +}; + +export default en; diff --git a/docker/frontend/src/i18n/vi.ts b/docker/frontend/src/i18n/vi.ts new file mode 100644 index 0000000..7347d15 --- /dev/null +++ b/docker/frontend/src/i18n/vi.ts @@ -0,0 +1,131 @@ +// Vietnamese dictionary — the source of truth for key names (`en.ts` is typed +// as `Dict`, so a missing or misspelled key there is a typecheck error). +// Not translated anywhere, in any language: film-sim names, user recipe names, +// user watermark text — they are proper nouns, see WEB_PLAN.md. + +export const vi = { + 'lang.name': 'Tiếng Việt', + + 'nav.demo': 'DÙNG THỬ', + 'nav.login': 'ĐĂNG NHẬP', + 'nav.signup': 'ĐĂNG KÝ', + 'nav.logout': 'Đăng xuất', + 'nav.account': 'Tài khoản', + 'nav.theme': 'Giao diện', + 'nav.language': 'Ngôn ngữ', + 'nav.back': 'Về trang chủ', + 'nav.guest': 'Khách', + + 'act.undo': 'HOÀN TÁC', + 'act.reset': 'ĐẶT LẠI', + 'act.export': 'XUẤT ẢNH', + 'act.exporting': 'Đang xuất…', + 'act.cancel': 'Huỷ', + + 'land.title': 'Cùng công thức màu, giờ trên máy tính', + 'land.lede': + 'RecipesCam biến ảnh chụp thành ảnh có "chất" bằng công thức phim. Bản web chạy đúng pipeline màu của app: cùng công thức, cùng khung, cùng dấu GPS.', + 'land.ctaDemo': 'DÙNG THỬ NGAY', + 'land.ctaSignup': 'TẠO TÀI KHOẢN', + 'land.f1t': 'Công thức phim', + 'land.f1d': 'PROVIA, VELVIA, CLASSIC CHROME… cộng bộ chỉnh tay EXPOSURE, WB, CLARITY, HDF, VIGNETTING.', + 'land.f2t': 'Khung & watermark', + 'land.f2d': 'Khung polaroid, viền classic, WALL FRAME, watermark chữ và dấu GPS đọc từ EXIF của ảnh.', + 'land.f3t': 'Xử lý ngay trên máy bạn', + 'land.f3d': 'Ảnh không bao giờ rời khỏi trình duyệt. Máy chủ chỉ giữ tài khoản và công thức bạn lưu.', + 'land.badgePlay': 'Tải trên Google Play', + 'land.badgeApp': 'Tải trên App Store', + 'land.storeSoon': 'Sắp có', + + 'drop.title': 'Kéo & thả ảnh vào đây', + 'drop.hint': 'hoặc bấm để chọn ảnh · dán từ clipboard bằng Ctrl+V', + 'drop.replace': 'Đổi ảnh khác', + 'drop.peeking': 'ĐANG XEM ẢNH GỐC', + 'drop.peekHint': 'Giữ chuột phải hoặc phím \\ để so ảnh gốc', + 'drop.demoLimit': 'Bản demo: ảnh xuất ra tối đa {px}px và có watermark.', + + 'tab.presets': 'PRESETS', + 'tab.favorited': 'FAVORITED', + 'tab.light': 'LIGHT', + 'tab.wb': 'WB', + 'tab.fx': 'FX', + 'tab.frame': 'FRAME', + + 'sec.filmSims': 'PHOTO STYLE', + 'sec.recipes': 'RECIPES', + 'sec.saved': 'ĐÃ LƯU', + 'sec.savedEmpty': 'Chưa lưu công thức nào. Đăng nhập rồi bấm LƯU để đồng bộ.', + 'sec.frame': 'KHUNG', + 'sec.crop': 'CẮT', + 'sec.rotate': 'XOAY', + 'sec.watermark': 'WATERMARK', + 'sec.gps': 'GPS', + + 'wb.temp': 'COLOR TEMP', + + 'frame.none': 'KHÔNG KHUNG', + 'frame.polaroid': 'RETRO INSTANT', + 'frame.classic': 'VIỀN CLASSIC', + 'frame.cinematic': 'ĐIỆN ẢNH', + 'frame.wall': 'WALL FRAME', + 'frame.wallLand': 'WALL LANDSCAPE', + + 'crop.none': 'KHÔNG CẮT', + 'crop.1:1': '1:1', + 'crop.2:3': '2:3', + 'crop.3:2': '3:2', + 'crop.3:4': '3:4', + 'crop.4:3': '4:3', + 'crop.16:9': '16:9', + 'crop.free': 'TỰ DO', + + 'rot.0': '0°', + 'rot.90': '90°', + 'rot.180': '180°', + 'rot.270': '270°', + + 'wm.text': 'CHỮ', + 'wm.color': 'MÀU', + 'wm.size': 'CỠ', + 'wm.gps': 'DẤU GPS', + 'wm.place': 'TÊN ĐỊA ĐIỂM', + 'wm.placePh': 'VD: HÀ NỘI', + 'wm.manual': 'NHẬP TOẠ ĐỘ', + 'wm.lat': 'Vĩ độ', + 'wm.lng': 'Kinh độ', + 'wm.hasExif': 'Đã đọc GPS từ ảnh.', + 'wm.noExif': 'Ảnh không có GPS — nhập tay nếu muốn đóng dấu.', + + 'auth.loginTitle': 'Đăng nhập', + 'auth.signupTitle': 'Tạo tài khoản', + 'auth.email': 'Email', + 'auth.password': 'Mật khẩu', + 'auth.passwordHint': 'Ít nhất 8 ký tự', + 'auth.noAccount': 'Chưa có tài khoản? Đăng ký', + 'auth.haveAccount': 'Đã có tài khoản? Đăng nhập', + 'auth.continueDemo': 'Tiếp tục dùng thử', + 'auth.busy': 'Đang xử lý…', + 'auth.loggedInAs': 'Đã đăng nhập: {email}', + + 'save.title': 'Lưu công thức', + 'save.name': 'Tên công thức', + 'save.save': 'LƯU', + 'save.saved': 'Đã lưu "{name}"', + 'save.needLogin': 'Đăng nhập để lưu công thức.', + + 'err.noWebgl': + 'Trình duyệt không cấp được WebGL — bản dựng này cần WebGL để xử lý ảnh (chạy CPU chậm gấp hàng nghìn lần).', + 'err.decode': 'Không đọc được ảnh này. Thử JPG hoặc PNG khác.', + 'err.generic': 'Có lỗi xảy ra: {msg}', + 'err.offline': 'Không gọi được máy chủ.', + + 'meta.ready': 'Sẵn sàng', + 'meta.rendered': 'Kết xuất {ms}ms · {w}×{h}', + 'meta.exported': 'Đã tải {name} ({kb}KB, 300 ppi)', +} as const; + +// `vi` is `as const` so the key set is exact; `Dict` widens the values to +// string, which is what lets `en.ts` hold different text under the same keys +// while a missing or misspelled key stays a typecheck error. +export type Dict = { [K in keyof typeof vi]: string }; +export type MsgKey = keyof Dict; diff --git a/docker/frontend/src/main.tsx b/docker/frontend/src/main.tsx new file mode 100644 index 0000000..04be10c --- /dev/null +++ b/docker/frontend/src/main.tsx @@ -0,0 +1,21 @@ +import { createRoot } from 'react-dom/client'; +import './styles/tokens.css'; +import './styles/app.css'; +import { ThemeProvider } from './theme/ThemeProvider'; +import { I18nProvider } from './i18n/I18nProvider'; +import { Landing } from './Landing'; +import { Workspace } from './App'; + +// Two routes, no router: the landing page and the workspace. nginx serves +// index.html for both (SPA fallback), so this is just a pathname check. +const isWorkspace = window.location.pathname.startsWith('/app'); +const root = document.getElementById('root'); +if (!root) throw new Error('#root missing'); + +// No StrictMode: it double-invokes effects, which would load CanvasKit twice and +// run the render pipeline twice on every mount. +createRoot(root).render( + + {isWorkspace ? : } + +); diff --git a/docker/frontend/src/styles/app.css b/docker/frontend/src/styles/app.css new file mode 100644 index 0000000..a11e328 --- /dev/null +++ b/docker/frontend/src/styles/app.css @@ -0,0 +1,208 @@ +/* Workspace layout: header strip on top, then rail | chips | stage. + Under 860px the rail becomes a bottom bar and the chips a horizontal strip, + which is the same shape the phone app uses. */ + +.shell { display: flex; flex-direction: column; height: 100%; } + +.header { + display: flex; + align-items: center; + gap: 10px; + padding: 8px 12px; + border-bottom: 1px solid var(--border-soft); + background: var(--bg-elev); + flex-wrap: wrap; +} +.brand { font-weight: 700; letter-spacing: 0.04em; } +.brand small { color: var(--text-dim); font-weight: 400; margin-left: 6px; letter-spacing: 0; } +.header .spacer { flex: 1 1 auto; } +.recipe-name { + font-family: var(--mono); + font-size: 12px; + color: var(--text-dim); + border: 1px solid var(--border-soft); + border-radius: 999px; + padding: 3px 10px; + max-width: 320px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} +.menu-wrap { position: relative; } + +.workspace { display: flex; flex: 1 1 auto; min-height: 0; } + +/* --- tool rail ---------------------------------------------------------- */ +.rail { + width: var(--rail-w); + flex: 0 0 auto; + border-right: 1px solid var(--border-soft); + background: var(--bg-sunken); + display: flex; + flex-direction: column; + padding: 6px; + gap: 4px; + overflow-y: auto; +} +.rail button { + border: 1px solid transparent; + background: transparent; + border-radius: var(--radius-sm); + padding: 9px 4px; + cursor: pointer; + display: flex; + flex-direction: column; + align-items: center; + gap: 4px; + font-size: 10px; + letter-spacing: 0.06em; + color: var(--text-dim); +} +.rail button .glyph { font-size: 17px; line-height: 1; } +.rail button[aria-pressed="true"] { background: var(--accent-soft); color: var(--accent); border-color: var(--accent-line); } +.rail button:hover { color: var(--text); } + +/* --- chip column -------------------------------------------------------- */ +.chips { + width: var(--chip-w); + flex: 0 0 auto; + border-right: 1px solid var(--border-soft); + background: var(--bg); + overflow-y: auto; + padding: 10px; + display: flex; + flex-direction: column; + gap: 8px; +} +.chips h2 { margin: 0 0 2px; font-size: 11px; letter-spacing: 0.14em; color: var(--text-dim); font-weight: 600; } +.chip-row { display: flex; flex-wrap: wrap; gap: 6px; } +.chip { + border: 1px solid var(--border); + background: var(--bg-elev); + border-radius: 999px; + padding: 5px 11px; + font-size: 12px; + cursor: pointer; + display: inline-flex; + align-items: center; + gap: 6px; +} +.chip[aria-pressed="true"], .chip.on { border-color: var(--accent); color: var(--accent); background: var(--accent-soft); } +.chip .val { color: var(--text-dim); font-family: var(--mono); font-size: 11px; } +.chip .pro { font-size: 9px; letter-spacing: 0.08em; color: var(--accent); } +.chip:disabled { opacity: 0.4; cursor: not-allowed; } + +.slider-row { border-top: 1px solid var(--border-soft); padding-top: 10px; display: flex; flex-direction: column; gap: 6px; } +.slider-row .head { display: flex; justify-content: space-between; font-size: 11px; letter-spacing: 0.1em; color: var(--text-dim); } +.slider-row .head b { color: var(--accent); font-family: var(--mono); letter-spacing: 0; } +input[type="range"] { width: 100%; accent-color: var(--accent); } +.hint { color: var(--text-dim); font-size: 12px; line-height: 1.5; } + +/* --- stage -------------------------------------------------------------- */ +.stage { + flex: 1 1 auto; + min-width: 0; + background: var(--bg-sunken); + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 12px; + padding: 16px; + position: relative; +} +.dropzone { + border: 2px dashed var(--border); + border-radius: var(--radius); + padding: 40px 28px; + text-align: center; + color: var(--text-dim); + cursor: pointer; + background: var(--bg); + max-width: 520px; +} +.dropzone.over { border-color: var(--accent); color: var(--text); background: var(--accent-soft); } +.dropzone strong { display: block; color: var(--text); margin-bottom: 6px; font-size: 16px; } +.canvas-wrap { position: relative; display: flex; align-items: center; justify-content: center; min-height: 0; flex: 1 1 auto; width: 100%; } +.canvas-wrap img { max-width: 100%; max-height: 100%; object-fit: contain; border-radius: var(--radius-sm); box-shadow: var(--shadow); } +.busy { + position: absolute; + inset: auto 0 12px 0; + margin: 0 auto; + width: fit-content; + background: var(--bg-elev); + border: 1px solid var(--border); + border-radius: 999px; + padding: 5px 12px; + font-size: 12px; + color: var(--text-dim); + box-shadow: var(--shadow); +} +.stage .toolbar { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; justify-content: center; } +.err { color: var(--danger); font-size: 12px; max-width: 520px; text-align: center; } + +/* --- landing ------------------------------------------------------------ */ +.landing { max-width: 960px; margin: 0 auto; padding: 56px 20px 72px; } +.landing h1 { font-size: clamp(30px, 5vw, 52px); line-height: 1.1; margin: 0 0 14px; } +.landing .lede { font-size: 17px; color: var(--text-dim); max-width: 620px; line-height: 1.6; } +.landing .cta { display: flex; gap: 10px; margin: 24px 0 44px; flex-wrap: wrap; } +.features { display: grid; grid-template-columns: repeat(auto-fit, minmax(210px, 1fr)); gap: 14px; } +.card { border: 1px solid var(--border); border-radius: var(--radius); padding: 16px; background: var(--bg-elev); } +.card h3 { margin: 0 0 6px; font-size: 14px; } +.card p { margin: 0; color: var(--text-dim); line-height: 1.55; } +.badges { display: flex; gap: 10px; margin-top: 34px; flex-wrap: wrap; } +.badge { + border: 1px solid var(--border); + border-radius: var(--radius-sm); + padding: 9px 14px; + text-decoration: none; + color: var(--text); + display: flex; + flex-direction: column; + line-height: 1.3; + background: var(--bg-elev); +} +.badge small { color: var(--text-dim); font-size: 10px; letter-spacing: 0.08em; } + +/* --- auth dialog -------------------------------------------------------- */ +.modal-backdrop { + position: fixed; + inset: 0; + background: rgba(0, 0, 0, 0.5); + display: flex; + align-items: center; + justify-content: center; + z-index: 60; + padding: 16px; +} +.modal { + background: var(--bg-elev); + border: 1px solid var(--border); + border-radius: var(--radius); + padding: 20px; + width: 100%; + max-width: 360px; + box-shadow: var(--shadow); + display: flex; + flex-direction: column; + gap: 10px; +} +.modal h2 { margin: 0; font-size: 17px; } + +/* --- responsive --------------------------------------------------------- */ +@media (max-width: 860px) { + .workspace { flex-direction: column; } + .rail { + order: 2; + width: auto; + flex-direction: row; + border-right: none; + border-top: 1px solid var(--border-soft); + overflow-x: auto; + padding: 4px; + } + .rail button { flex: 1 0 auto; min-width: 62px; } + .chips { order: 1; width: auto; border-right: none; border-bottom: 1px solid var(--border-soft); max-height: 42vh; } + .stage { order: 0; flex: 1 1 auto; } + .chip-row { flex-wrap: nowrap; overflow-x: auto; padding-bottom: 4px; } +} diff --git a/docker/frontend/src/styles/tokens.css b/docker/frontend/src/styles/tokens.css new file mode 100644 index 0000000..85c93e1 --- /dev/null +++ b/docker/frontend/src/styles/tokens.css @@ -0,0 +1,105 @@ +/* Theme tokens. Switching = two attributes on , set by ThemeProvider — + no React re-render, no per-component colour code. Adding an accent group is + one 3-line block below; see src/theme/themes.ts for the picker's list. */ + +:root { + --radius: 10px; + --radius-sm: 7px; + --rail-w: 84px; + --chip-w: 300px; + --font: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; + --mono: ui-monospace, SFMono-Regular, Menlo, monospace; +} + +/* --- base themes --------------------------------------------------------- */ +[data-theme="light"] { + --bg: #ffffff; + --bg-sunken: #f6f7f9; + --bg-elev: #ffffff; + --border: #dcdfe4; + --border-soft: #eceef1; + --text: #14181d; + --text-dim: #5c6773; + --danger: #c2402f; + --accent-l: 42%; + --accent-fg: #ffffff; + --shadow: 0 1px 2px rgba(0, 0, 0, 0.06), 0 8px 24px rgba(0, 0, 0, 0.06); +} + +[data-theme="dark"] { + --bg: #000000; + --bg-sunken: #0b0b0c; + --bg-elev: #131315; + --border: #2a2a2e; + --border-soft: #1d1d20; + --text: #f2f3f5; + --text-dim: #9aa2ad; + --danger: #ff6b57; + --accent-l: 60%; + --accent-fg: #0a0a0a; + --shadow: 0 1px 2px rgba(0, 0, 0, 0.5), 0 10px 30px rgba(0, 0, 0, 0.45); +} + +/* --- accent groups ------------------------------------------------------ */ +[data-accent="amber"] { --accent-h: 33; --accent-s: 92%; } +[data-accent="blue"] { --accent-h: 214; --accent-s: 88%; } +[data-accent="emerald"] { --accent-h: 158; --accent-s: 68%; } +[data-accent="rose"] { --accent-h: 343; --accent-s: 82%; } +[data-accent="violet"] { --accent-h: 265; --accent-s: 78%; } + +:root { + --accent: hsl(var(--accent-h) var(--accent-s) var(--accent-l)); + --accent-hover: hsl(var(--accent-h) var(--accent-s) calc(var(--accent-l) + 7%)); + --accent-soft: hsl(var(--accent-h) var(--accent-s) var(--accent-l) / 0.14); + --accent-line: hsl(var(--accent-h) var(--accent-s) var(--accent-l) / 0.45); +} + +/* --- base elements ------------------------------------------------------ */ +* { box-sizing: border-box; } +html, body, #root { height: 100%; margin: 0; } +body { + background: var(--bg); + color: var(--text); + font-family: var(--font); + font-size: 14px; + -webkit-font-smoothing: antialiased; +} +button { font: inherit; color: inherit; } +a { color: var(--accent); } + +.btn { + border: 1px solid var(--border); + background: var(--bg-elev); + color: var(--text); + padding: 7px 12px; + border-radius: var(--radius-sm); + cursor: pointer; + white-space: nowrap; +} +.btn:hover { border-color: var(--accent-line); } +.btn.primary { background: var(--accent); border-color: var(--accent); color: var(--accent-fg); font-weight: 600; } +.btn.primary:hover { background: var(--accent-hover); } +.btn:disabled { opacity: 0.45; cursor: not-allowed; } +.btn.ghost { background: transparent; border-color: transparent; color: var(--text-dim); } +.btn.ghost:hover { color: var(--text); background: var(--bg-sunken); } + +.field { + width: 100%; + padding: 8px 10px; + border-radius: var(--radius-sm); + border: 1px solid var(--border); + background: var(--bg); + color: var(--text); +} +.popover { + position: absolute; + right: 0; + top: calc(100% + 6px); + z-index: 40; + min-width: 220px; + background: var(--bg-elev); + border: 1px solid var(--border); + border-radius: var(--radius); + box-shadow: var(--shadow); + padding: 10px; +} diff --git a/docker/frontend/src/theme/ThemeProvider.tsx b/docker/frontend/src/theme/ThemeProvider.tsx new file mode 100644 index 0000000..a6f1b81 --- /dev/null +++ b/docker/frontend/src/theme/ThemeProvider.tsx @@ -0,0 +1,37 @@ +import { createContext, useCallback, useContext, useEffect, useMemo, useState, type ReactNode } from 'react'; +import { ACCENTS, applyTheme, readAccent, readTheme, type AccentId, type ThemeMode } from './themes'; + +interface ThemeValue { + mode: ThemeMode; + accent: AccentId; + setMode: (mode: ThemeMode) => void; + setAccent: (accent: AccentId) => void; +} + +const Ctx = createContext(null); + +// The tokens live in CSS; this provider only owns the two attributes on +// and the persisted choice. No component ever reads a colour from JS. +export function ThemeProvider({ children }: { children: ReactNode }) { + const [mode, setModeState] = useState(readTheme); + const [accent, setAccentState] = useState(readAccent); + + useEffect(() => { + applyTheme(mode, accent); + }, [mode, accent]); + + const setMode = useCallback((next: ThemeMode) => setModeState(next), []); + const setAccent = useCallback((next: AccentId) => setAccentState(next), []); + + const value = useMemo(() => ({ mode, accent, setMode, setAccent }), [mode, accent, setMode, setAccent]); + return {children}; +} + +export function useTheme(): ThemeValue { + const value = useContext(Ctx); + if (!value) throw new Error('useTheme used outside ThemeProvider'); + return value; +} + +export { ACCENTS }; +export type { AccentId, ThemeMode }; diff --git a/docker/frontend/src/theme/themes.ts b/docker/frontend/src/theme/themes.ts new file mode 100644 index 0000000..50b11c2 --- /dev/null +++ b/docker/frontend/src/theme/themes.ts @@ -0,0 +1,41 @@ +// Theme data only — the CSS in styles/tokens.css owns the values. Adding an +// accent group is one entry here plus one 1-line block in the stylesheet. + +export type ThemeMode = 'light' | 'dark'; +export type AccentId = 'amber' | 'blue' | 'emerald' | 'rose' | 'violet'; + +export const ACCENTS: { id: AccentId; label: string }[] = [ + { id: 'amber', label: 'Amber' }, + { id: 'blue', label: 'Blue' }, + { id: 'emerald', label: 'Emerald' }, + { id: 'rose', label: 'Rose' }, + { id: 'violet', label: 'Violet' }, +]; + +export const THEME_COLOR: Record = { + light: '#ffffff', + dark: '#0a0a0a', +}; + +const KEY_THEME = 'rc.theme'; +const KEY_ACCENT = 'rc.accent'; + +export function readTheme(): ThemeMode { + const stored = localStorage.getItem(KEY_THEME); + if (stored === 'light' || stored === 'dark') return stored; + return window.matchMedia?.('(prefers-color-scheme: light)').matches ? 'light' : 'dark'; +} + +export function readAccent(): AccentId { + const stored = localStorage.getItem(KEY_ACCENT); + return ACCENTS.some((a) => a.id === stored) ? (stored as AccentId) : 'amber'; +} + +export function applyTheme(mode: ThemeMode, accent: AccentId): void { + const root = document.documentElement; + root.dataset.theme = mode; + root.dataset.accent = accent; + document.querySelector('meta[name="theme-color"]')?.setAttribute('content', THEME_COLOR[mode]); + localStorage.setItem(KEY_THEME, mode); + localStorage.setItem(KEY_ACCENT, accent); +} diff --git a/docker/frontend/src/ui/AuthModal.tsx b/docker/frontend/src/ui/AuthModal.tsx new file mode 100644 index 0000000..6406a3e --- /dev/null +++ b/docker/frontend/src/ui/AuthModal.tsx @@ -0,0 +1,71 @@ +import { useState } from 'react'; +import { useI18n } from '../i18n/I18nProvider'; +import { api } from '../api'; + +// Email + password only: the cookie is set by the API container, this dialog +// just drives it. The demo is always one click away — the app is usable signed +// out, and only export resolution / saving are gated (see config/tiers.ts). +export function AuthModal({ + onClose, + onDone, +}: { + onClose: () => void; + onDone: () => void; +}) { + const { t } = useI18n(); + const [mode, setMode] = useState<'login' | 'signup'>('login'); + const [email, setEmail] = useState(''); + const [password, setPassword] = useState(''); + const [busy, setBusy] = useState(false); + const [error, setError] = useState(null); + + const submit = async (e: React.FormEvent) => { + e.preventDefault(); + setBusy(true); + setError(null); + try { + await (mode === 'login' ? api.login(email, password) : api.signup(email, password)); + onDone(); + } catch (err) { + setError(err instanceof Error ? err.message : String(err)); + } finally { + setBusy(false); + } + }; + + return ( +
e.target === e.currentTarget && onClose()}> +
+

{mode === 'login' ? t('auth.loginTitle') : t('auth.signupTitle')}

+ setEmail(e.target.value)} + /> + setPassword(e.target.value)} + /> +

{t('auth.passwordHint')}

+ {error ?

{error}

: null} + + + +
+
+ ); +} diff --git a/docker/frontend/src/ui/ChipColumn.tsx b/docker/frontend/src/ui/ChipColumn.tsx new file mode 100644 index 0000000..34078ee --- /dev/null +++ b/docker/frontend/src/ui/ChipColumn.tsx @@ -0,0 +1,78 @@ +import type { ReactNode } from 'react'; + +// One column, one open slider row at a time — the same shape as the app's +// AdjustmentPanel: a chip row per group, and the tapped chip's row opens +// underneath it. +export interface ChipDef { + key: string; + label: string; + value?: string; + active?: boolean; + pro?: boolean; + disabled?: boolean; + onClick: () => void; +} + +export interface SliderDef { + key: string; + label: string; + value: number; + min: number; + max: number; + step: number; + display: string; + onChange: (value: number) => void; + onClose: () => void; +} + +export function ChipRow({ chips }: { chips: ChipDef[] }) { + return ( +
+ {chips.map((chip) => ( + + ))} +
+ ); +} + +export function SliderRow({ slider }: { slider: SliderDef }) { + return ( +
+
+ {slider.label} + {slider.display} +
+ slider.onChange(Number(e.target.value))} + /> +
+ +
+
+ ); +} + +export function Section({ title, children }: { title: string; children: ReactNode }) { + return ( +
+

{title}

+ {children} +
+ ); +} diff --git a/docker/frontend/src/ui/ImageStage.tsx b/docker/frontend/src/ui/ImageStage.tsx new file mode 100644 index 0000000..822e6f4 --- /dev/null +++ b/docker/frontend/src/ui/ImageStage.tsx @@ -0,0 +1,120 @@ +import { useCallback, useEffect, useRef, useState } from 'react'; +import { useI18n } from '../i18n/I18nProvider'; + +// The centre column: drop target, preview canvas, and the PEEK comparison. +// Dragging a photo anywhere on the page works, not just on the frame — the +// whole stage is the drop zone. +export function ImageStage({ + previewUrl, + originalUrl, + busy, + error, + peek, + onPeek, + onFile, +}: { + previewUrl: string | null; + originalUrl: string | null; + busy: string | null; + error: string | null; + peek: boolean; + onPeek: (on: boolean) => void; + onFile: (file: File) => void; +}) { + const { t } = useI18n(); + const inputRef = useRef(null); + const [over, setOver] = useState(false); + + // \ toggles PEEK; the key is the app's own shortcut, kept identical on web. + useEffect(() => { + const down = (e: KeyboardEvent) => { + if (e.key === '\\') onPeek(true); + }; + const up = (e: KeyboardEvent) => { + if (e.key === '\\') onPeek(false); + }; + window.addEventListener('keydown', down); + window.addEventListener('keyup', up); + return () => { + window.removeEventListener('keydown', down); + window.removeEventListener('keyup', up); + }; + }, [onPeek]); + + const stop = useCallback((e: React.DragEvent) => { + e.preventDefault(); + e.stopPropagation(); + }, []); + + const drop = useCallback( + (e: React.DragEvent) => { + stop(e); + setOver(false); + const file = e.dataTransfer?.files?.[0]; + if (file) onFile(file); + }, + [onFile, stop] + ); + + return ( +
{ + stop(e); + setOver(true); + }} + onDragLeave={() => setOver(false)} + onDrop={drop} + onContextMenu={(e) => e.preventDefault()} + onMouseDown={(e) => { + if (e.button === 2) onPeek(true); + }} + onMouseUp={() => onPeek(false)} + > + {!previewUrl ? ( +
inputRef.current?.click()} + onDragOver={(e) => { + stop(e); + setOver(true); + }} + onDrop={drop} + > + {t('drop.title')} + {t('drop.hint')} +

{t('drop.demoLimit', { px: 2048 })}

+
+ ) : ( +
+ preview + {peek ?
{t('drop.peeking')}
: null} +
+ )} + + {previewUrl ? ( +
+ + {t('drop.peekHint')} +
+ ) : null} + + {busy ?
{busy}
: null} + {error ?

{error}

: null} + + { + const file = e.target.files?.[0]; + if (file) onFile(file); + e.target.value = ''; + }} + /> +
+ ); +} diff --git a/docker/frontend/src/ui/ToolRail.tsx b/docker/frontend/src/ui/ToolRail.tsx new file mode 100644 index 0000000..fc82dd0 --- /dev/null +++ b/docker/frontend/src/ui/ToolRail.tsx @@ -0,0 +1,34 @@ +import type { MsgKey } from '../i18n/vi'; +import { useI18n } from '../i18n/I18nProvider'; + +// The app's six tabs, in the same order as the phone rail. The rail is vertical +// on desktop and becomes the bottom bar under 860px (see styles/app.css). +export type TabId = 'presets' | 'favorited' | 'light' | 'wb' | 'fx' | 'frame'; + +export const TABS: { id: TabId; label: MsgKey; glyph: string }[] = [ + { id: 'presets', label: 'tab.presets', glyph: '◉' }, + { id: 'favorited', label: 'tab.favorited', glyph: '★' }, + { id: 'light', label: 'tab.light', glyph: '☀' }, + { id: 'wb', label: 'tab.wb', glyph: '◐' }, + { id: 'fx', label: 'tab.fx', glyph: '✦' }, + { id: 'frame', label: 'tab.frame', glyph: '▣' }, +]; + +export function ToolRail({ tab, onTab }: { tab: TabId; onTab: (tab: TabId) => void }) { + const { t } = useI18n(); + return ( + + ); +} diff --git a/docker/frontend/src/ui/TopBar.tsx b/docker/frontend/src/ui/TopBar.tsx new file mode 100644 index 0000000..6af5286 --- /dev/null +++ b/docker/frontend/src/ui/TopBar.tsx @@ -0,0 +1,128 @@ +import { useEffect, useRef, useState } from 'react'; +import { useI18n, LANGS } from '../i18n/I18nProvider'; +import { ACCENTS, useTheme } from '../theme/ThemeProvider'; +import type { User } from '../api'; + +// Header: the app's TopBar minus everything camera-only, plus the two controls +// the web build adds — theme (light/dark + accent group) and language. +export function TopBar({ + recipeName, + user, + exporting, + canUndo, + onUndo, + onReset, + onExport, + onSave, + onAuth, + onLogout, +}: { + recipeName: string; + user: User | null; + exporting: boolean; + canUndo: boolean; + onUndo: () => void; + onReset: () => void; + onExport: () => void; + onSave: () => void; + onAuth: () => void; + onLogout: () => void; +}) { + const { t, lang, setLang } = useI18n(); + const { mode, accent, setMode, setAccent } = useTheme(); + const [menu, setMenu] = useState<'theme' | 'lang' | 'account' | null>(null); + const wrap = useRef(null); + + useEffect(() => { + if (!menu) return; + const away = (e: MouseEvent) => { + if (!wrap.current?.contains(e.target as Node)) setMenu(null); + }; + document.addEventListener('mousedown', away); + return () => document.removeEventListener('mousedown', away); + }, [menu]); + + return ( +
+ + RecipesCam web + + {recipeName} + + + + + + + +
+ + {menu === 'theme' ? ( +
+
+ + +
+
+ {ACCENTS.map((entry) => ( + + ))} +
+
+ ) : null} +
+ +
+ + {menu === 'lang' ? ( +
+ {LANGS.map((entry) => ( + + ))} +
+ ) : null} +
+ +
+ + {menu === 'account' ? ( +
+ {user ? ( + <> +

{t('auth.loggedInAs', { email: user.email })}

+ + + ) : ( + + )} +
+ ) : null} +
+
+ ); +} diff --git a/docker/frontend/tsconfig.json b/docker/frontend/tsconfig.json new file mode 100644 index 0000000..9ea4d1d --- /dev/null +++ b/docker/frontend/tsconfig.json @@ -0,0 +1,21 @@ +{ + "compilerOptions": { + "target": "ES2022", + "lib": ["ES2022", "DOM", "DOM.Iterable"], + "module": "ESNext", + "moduleResolution": "bundler", + "jsx": "react-jsx", + "strict": true, + "noEmit": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "isolatedModules": true, + "allowImportingTsExtensions": true, + "types": ["vite/client", "node"], + "baseUrl": ".", + "paths": { + "@shopify/react-native-skia": ["./src/engine/skiaShim.ts"] + } + }, + "include": ["src", "shared", "scripts", "vite.config.ts"] +} diff --git a/docker/frontend/vite.config.ts b/docker/frontend/vite.config.ts new file mode 100644 index 0000000..d86eb27 --- /dev/null +++ b/docker/frontend/vite.config.ts @@ -0,0 +1,25 @@ +import { defineConfig } from 'vite'; +import react from '@vitejs/plugin-react'; +import { fileURLToPath, URL } from 'node:url'; + +// The app is shared source with the React Native app: `frameUtils.ts` and +// `skiaImage.ts` import `@shopify/react-native-skia`, and the alias below swaps +// that for the CanvasKit adapter. Same trick the W0 spike used, now for the app. +export default defineConfig({ + plugins: [react()], + resolve: { + alias: { + '@shopify/react-native-skia': fileURLToPath(new URL('./src/engine/skiaShim.ts', import.meta.url)), + }, + }, + build: { + target: 'es2022', + // canvaskit-wasm's glue JS is ~2.5MB on its own; the wasm is fetched at + // runtime from /wasm/, never bundled. + chunkSizeWarningLimit: 9000, + }, + server: { + port: 5173, + proxy: { '/api': process.env.API_ORIGIN ?? 'http://localhost:3000' }, + }, +});

YG*#JOz{zYp5PZ|e6w@Baaai&!%N literal 0 HcmV?d00001 diff --git a/docker/frontend/public/assets/NotoEmoji-GPS.ttf b/docker/frontend/public/assets/NotoEmoji-GPS.ttf new file mode 100644 index 0000000000000000000000000000000000000000..207709b676934b741808ab961994da4cc5312883 GIT binary patch literal 2564 zcmZuzZERat8Gg?__rv$vwd48PZqqEWoseW`O)4k$B-E~}x6x@TsINgcp@PMy!4xp?N) z?-Qvm9?hMdgx(ALEJn}h*f&l++jju*q&&W(qp+X2*Yz9NA1C7H^CxpBc76QYe+@V)FFaDY-y1sAspB7YT#cc6^RV{|j)(dBpcV$ziM8z#bC4<1}-6?s$DT zuxFLL9w>-z|2Z`(#!r5??>ltec~`%z{vOanlF)+Sr;Ry2l-Nzz>AHT|jntTHaA_m< z_uwT+WO|fjqmx}B1-14mZ^59oG4c=Tl=PS{ph%1}Nsw^cOf2LBhYlT}XX>Cv4cZtJ zyS0%)8-pApoqtMsqnqofPml;8<1?{d!yQpQsN3aYo~``B{X`_TvgUr9iNzksTzB5p zV(3JGcG7No9)^j|&QyOo-It6+!ltTn9_$N-`aFSlG`T3>4@6Gmm`m=kT6Xs5?HivYt#~X|WTiZ0=@%pUZ?yZBbhM2{ zb;XdIS|iO|4R``Owgn>Cq3iV~>~S6|Yo|VXmPY8S^csB^dz0u)1^d%|@nj?%G}ZRc zBzmM&|I<=mvQ-M3eyJ;&7AwR8RW)ozB2WEKN0fLnol3=1@u2wnJKN(4H5`nGfx~e) zgi`&89S)a}{-fL97Ky((yEu1g zadvj`(%jBg)%smOxAG<{lKn_Vx1N%%}==|Dqh`$J4cM+u8AJt?lB23 z1WCVw?3yv|${UEp8~G>+tB3Q8D1wB(N!)Qax5v$kEL+hr!C)gYGrSZ+&)~b2nYr&r zi(3^@OoXIs?8nd0`*cA<#;XYMCe#=9Dt^;611eST7mLOF)i%>C7HwJZk2F>(udi3j z<+9^2%^Sp%5%!f=mmjRGz;>lkG%(sJ1*ZKh`zL(}k(;)l)(=8i(f`zlCwm0Tc+b=B5Oj%FD5 zONJpiU;*MBZ!ib1t*@=GmQbN;7|i0enj`HhmARq8ux>CYje4n6v8&Z`rB&C={c}Fa8)bMg=CDOL0pUU~5em z&>joDvRc%Zcnj5*KPeOnAK#%kl?o*lT_qbGx^F4wvRQ_yTHIO0-UtB~P?nh`*|u$Q zh_a>DA=^0bjgTob9uKKYg~CUlu96yRDd_R|TESE*4~oTmW%3t`WFi=uCSd@B;=d6Q zomsMoShpkqx}a1`psJ-P21v1z_S%EHckez}tC6H_3kHLJougc+mTi)KQ21o3mElts z7Vy!ywao_Bv{H$42)5NEf-Nr_MhTl|D9~9t1gcd)HA|6HkYp*2z|JO~m4xqRA^Q+h z)L|1h{74({DI{uh3#hsURYgIi(t3@!2jM+3Ib}B?iM$cPsIfe1lu{~@Vptruh9x~x k$!29-RK_g{ct+eW*0l9M2F|Fgci@gaO3dh%j$$tVAGeln1ONa4 literal 0 HcmV?d00001 diff --git a/docker/frontend/public/assets/wallframe.png b/docker/frontend/public/assets/wallframe.png new file mode 100644 index 0000000000000000000000000000000000000000..c3cfd0ad1103c0c7451a9ffa06d420e2dc668b73 GIT binary patch literal 261257 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