diff --git a/.kilo/plans/1784212500972-vision-camera-skia-frame-processor.md b/.kilo/plans/1784212500972-vision-camera-skia-frame-processor.md
new file mode 100644
index 0000000..6f9843f
--- /dev/null
+++ b/.kilo/plans/1784212500972-vision-camera-skia-frame-processor.md
@@ -0,0 +1,106 @@
+# Kế hoạch: Real-time Skia filter trên camera live view dùng vision-camera v4
+
+## Vấn đề
+`expo-camera` render camera preview lên native surface (PreviewView/SurfaceView) không thể intercept bằng Skia. Periodic capture bị từ chối vì mất live view.
+
+## Giải pháp
+Dùng `react-native-vision-camera@4.7.3` với `useSkiaFrameProcessor` — frame processor chạy native thread, mỗi frame camera được vẽ qua Skia với ColorMatrix trước khi hiển thị.
+
+## Các bước thực hiện
+
+### Bước 1: Cài react-native-vision-camera@4.7.3
+```bash
+npm install react-native-vision-camera@4.7.3
+```
+- v4.7.3 là latest v4, có export `useSkiaFrameProcessor` và `frameProcessor` prop trên `Camera`
+- v5+ đã xoá Skia integration, không dùng được
+
+### Bước 2: Thêm Expo Config Plugin
+Sửa `app.json`:
+- Thay `expo-camera` plugin bằng `react-native-vision-camera` plugin (v4 có plugin riêng)
+- Thêm `withEnableFrameProcessorsAndroid` để bật frame processor trên Android
+```json
+"plugins": [
+ "react-native-vision-camera",
+ [
+ "expo-camera",
+ {
+ "cameraPermission": "..."
+ }
+ ]
+]
+```
+Cần kiểm tra: v4 có expo plugin riêng (`withVisionCamera`) hay dùng thẳng tên package.
+
+### Bước 3: Cập nhật App.tsx
+- Import `useCameraPermission` từ `react-native-vision-camera` thay vì `useCameraPermissions` từ `expo-camera`
+- Props: `hasPermission`, `requestPermission`
+
+### Bước 4: Viết lại Viewfinder.tsx
+Component mới dùng:
+- `Camera` từ vision-camera với prop `frameProcessor={frameProcessor}`
+- `useSkiaFrameProcessor` hook — worklet chạy native, mỗi frame:
+ 1. Tạo `Skia.Paint()` với `Skia.ColorFilter.MakeMatrix(colorMatrix)`
+ 2. Gọi `frame.render(paint)` — vẽ frame qua color filter
+- `colorMatrix` là `SharedValue` từ react-native-reanimated (để worklet đọc được)
+- Grain noise, frame border, GPS watermark: giữ nguyên overlay React Native / Skia Canvas trên top
+
+```tsx
+const App = () => {
+ const frameProcessor = useSkiaFrameProcessor((frame) => {
+ 'worklet';
+ const paint = Skia.Paint();
+ paint.setColorFilter(Skia.ColorFilter.MakeMatrix(colorMatrixSV.value));
+ frame.render(paint);
+ }, [colorMatrixSV]);
+
+ return (
+
+ );
+};
+```
+
+### Bước 5: Loại bỏ expo-camera khỏi code
+- `CameraView` → `Camera` (vision-camera)
+- Xoá imports không dùng
+- Giữ `expo-camera` trong package.json (không gỡ, tránh lỗi autolinking)
+
+### Bước 6: Re-prebuild & build APK
+```bash
+rm -rf android
+npx expo prebuild --platform android
+echo "sdk.dir=/home/locpham/android-sdk" > android/local.properties
+cd android && ./gradlew assembleDebug
+```
+(Không chạy `clean` — tránh lỗi CMake codegen paths)
+
+## API đã xác nhận (v4.7.3)
+| Export | Path |
+|---|---|
+| `Camera` | `react-native-vision-camera` |
+| `useCameraDevice` | `react-native-vision-camera` |
+| `useCameraPermission` | `react-native-vision-camera` |
+| `useSkiaFrameProcessor` | `react-native-vision-camera` |
+| `SkiaCameraCanvas` | `react-native-vision-camera` |
+| Expo plugin (`withVisionCamera`) | `lib/module/expo-plugin/withVisionCamera` |
+
+## Rủi ro & Xử lý
+| Rủi ro | Xử lý |
+|---|---|
+| `react-native-worklets-core` conflict | Đã có sẵn qua `react-native-reanimated` dependency |
+| CameraX dependency (`NoClassDefFoundError`) | v4 dùng `CameraX` không phải Nitro — build an toàn hơn v5 |
+| CMake codegen paths lỗi sau `clean` | Không dùng `./gradlew clean`, dùng `--rerun-tasks` nếu cần |
+| Frame processor không chạy trên Android | Bật flag qua Expo plugin: `withEnableFrameProcessorsAndroid` |
+
+## Kiểm thử
+1. Build APK thành công
+2. Camera live view hiển thị với filter:
+ - B&W/monochrome → true grayscale
+ - Saturation, temp, tint, exposure thay đổi real-time
+3. Grain noise, frame border, GPS watermark vẫn hoạt động
+4. Library mode không bị ảnh hưởng
diff --git a/CAMERA_PORT_ANDROID.md b/CAMERA_PORT_ANDROID.md
new file mode 100644
index 0000000..d3c717b
--- /dev/null
+++ b/CAMERA_PORT_ANDROID.md
@@ -0,0 +1,258 @@
+# Camera trực tiếp: chỉ port những gì web có mà Android chưa có
+
+Phạm vi tài liệu: **chỉ liệt kê delta web-only**. Mọi thứ Android đã có thì ghi nhận một dòng rồi bỏ qua,
+không mô tả cách làm lại.
+
+- Web (tham chiếu): `docker/frontend/` — Vite/React/TS, CanvasKit WASM. Camera thêm ở `cb1839e` +
+ `6814b05`.
+- Android (đích): `App.tsx` + `src/` ở gốc repo — Expo/RN, `react-native-vision-camera-skia` (worklet +
+ GPU texture).
+
+Quy ước trích dẫn: cột **Web** trỏ vào file trong `docker/frontend/`; cột **Android** trỏ vào `App.tsx` /
+`src/` ở gốc repo.
+
+**Tóm tắt:** web-only đáng port chỉ có **3** việc — P1 (quan trắc FPS/độ phân giải), P2 (gate PRO ở lối
+vào camera, *cần quyết định*), P3 (luồng "chụp rồi chỉnh"). Web-only còn lại hoặc không port được, hoặc
+Android đã có bản tương đương. §6 ghi thêm phương án thay thế: đóng gói chính web studio thành app mobile.
+
+---
+
+## 1. Đối chiếu tính năng — cái nào thực sự web-only
+
+| | **Web** | **Android** | Delta |
+|---|---|---|---|
+| Lối vào | Nút `OPEN CAMERA` + badge `PRO` (`ui/ImageStage.tsx:1443-1444`, `:2002-2006`) | Chế độ `mode === 'camera'` (`App.tsx:1675`, `:1978`) | khác chỗ đặt, không phải thiếu |
+| Gate | **Khoá cả camera** sau PRO: `cameraSupported() ? () => (pro ? setShooting(true) : promptPro()) : undefined` (`App.tsx:3237-3241`), `pro = !!user?.verified` (`:522`) | Gate ở **lúc render look**: `proLookInUse` (`App.tsx:1269`), `promptPro` (`:1275-1285`). `CameraControls.tsx` không có `pro` nào | **WEB-ONLY → P2** |
+| Số đo trên viewfinder | `{w}×{h} · {fps} fps` (`ui/CameraView.tsx:214-218`, `i18n/en.ts:40`) | Không hiển thị gì (nhịp tim `frameBeatSync` chỉ phục vụ watchdog) | **WEB-ONLY → P1** |
+| Ảnh chụp đi đâu | Still **full cảm biến → studio**; look do studio áp, file ghi sau bằng export chung (`docker/frontend/src/App.tsx:902-918`) | Render look **ngay** → file + gallery + `pushPhoto`; **không mở editor** (`App.tsx:1476-1489`) | **WEB-ONLY → P3** |
+| Xem trước khi chỉnh | Cùng `renderPhoto`, khác đích ra (`drawTo` so với `encodeToBytes`) | Cùng SkSL, chạy trong worklet | tương đương |
+| Lens | Chỉ `facingMode user/environment` (`engine/camera.ts:12-14`) | Flip, zoom 1–10x, ultra-wide 0.5x qua module Camera2 ẩn (`Viewfinder.tsx:599-713`), RAW DNG (`App.tsx:337-340`) | Android hơn |
+| Panel khi đang ngắm | Rail + cột PRESETS vẫn cạnh viewfinder (`.stage-slot`, `styles/app.css:723-750`) | Overlay dp + panel ngay dưới khung (`Viewfinder.tsx:2774-2819`) | tương đương |
+| Chuỗi UI | i18n EN/VI, 9 khoá `cam.*` (`i18n/en.ts:36-44`, `i18n/vi.ts:45-53`) | Hardcode tiếng Anh (app không có hạ tầng i18n) | khác phạm vi, xem P6 |
+| Chống treo phiên | không cần (rAF tự dừng/tiếp) | Watchdog remount sau ~6 s không có khung (`Viewfinder.tsx:1846-1900`) | Android hơn |
+
+---
+
+## 2. Delta kỹ thuật theo lớp
+
+| Lớp | Web | Android | Kết luận |
+|---|---|---|---|
+| Nhận biết khả năng | `navigator.mediaDevices?.getUserMedia` + yêu cầu **secure context** (`engine/camera.ts:27-32`) | `useCameraPermission()` (`App.tsx:322`), manifest `CAMERA` | web-only, **không port** (không có khái niệm tương ứng) |
+| Phân loại lỗi | `err.name === 'NotAllowedError'` → `cam.denied`, còn lại `cam.fail` (`CameraView.tsx:176-181`) | 3 alert riêng: `Permission Needed` / `Camera Not Ready` / `Capture Failed` (`App.tsx:1338`, `:1348`, `:1499`) | ngữ nghĩa đã tương đương |
+| Mở phiên | `getUserMedia` → `