From f83595f3c3428f1b09d51b0309b80a4645b01a2a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roman=20Kr=C4=8Dek?= Date: Thu, 17 Jul 2025 15:40:54 +0200 Subject: [PATCH] Google auth done --- .env.example | 2 + .github/copilot-instructions.md | 7 + .github/core-instructions.md | 208 +++++ .github/done.md | 40 + .github/styling.md | 363 ++++++++ package-lock.json | 846 +++++++++++++++++- package.json | 10 + src/hooks.server.ts | 12 + src/lib/components/Splash.svelte | 41 + src/lib/components/Wizard.svelte | 59 ++ src/lib/components/wizard/StepAuth.svelte | 80 ++ .../components/wizard/StepColumnMap.svelte | 4 + src/lib/components/wizard/StepGallery.svelte | 4 + src/lib/components/wizard/StepGenerate.svelte | 4 + .../components/wizard/StepRowFilter.svelte | 4 + .../components/wizard/StepSheetSearch.svelte | 0 src/lib/google.ts | 112 +++ src/lib/stores.ts | 142 +++ src/routes/+layout.svelte | 9 + src/routes/+page.svelte | 13 +- 20 files changed, 1956 insertions(+), 4 deletions(-) create mode 100644 .env.example create mode 100644 .github/copilot-instructions.md create mode 100644 .github/core-instructions.md create mode 100644 .github/done.md create mode 100644 .github/styling.md create mode 100644 src/hooks.server.ts create mode 100644 src/lib/components/Splash.svelte create mode 100644 src/lib/components/Wizard.svelte create mode 100644 src/lib/components/wizard/StepAuth.svelte create mode 100644 src/lib/components/wizard/StepColumnMap.svelte create mode 100644 src/lib/components/wizard/StepGallery.svelte create mode 100644 src/lib/components/wizard/StepGenerate.svelte create mode 100644 src/lib/components/wizard/StepRowFilter.svelte create mode 100644 src/lib/components/wizard/StepSheetSearch.svelte create mode 100644 src/lib/google.ts create mode 100644 src/lib/stores.ts diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..9716867 --- /dev/null +++ b/.env.example @@ -0,0 +1,2 @@ +# Your Google Cloud OAuth 2.0 Client ID +VITE_GOOGLE_CLIENT_ID="YOUR_GOOGLE_CLIENT_ID_HERE" diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md new file mode 100644 index 0000000..317d1c4 --- /dev/null +++ b/.github/copilot-instructions.md @@ -0,0 +1,7 @@ +# Base directions +- You are a helpful AI assistant that helps developers write code. +- This code is written in Svelte 5 +- It's important to only use modern Svelte 5 syntax, runes, and features. +- Use styling from ".github/styling.md" for any UI components. +- Refer to the ".github/core-instructions.md" for the overall structure of the application. +- Generate ".github/done.md" file to see what is done and what is not. Check it when you start and finish a task. \ No newline at end of file diff --git a/.github/core-instructions.md b/.github/core-instructions.md new file mode 100644 index 0000000..3ef5445 --- /dev/null +++ b/.github/core-instructions.md @@ -0,0 +1,208 @@ +# Copilot Instruction Set – **Google‑Sheet → Photo‑PDF** Wizard (SvelteKit) + +> **Goal** +> A privacy‑first SvelteKit SPA that guides a user through importing a Google Sheet, mapping its columns, validating/culling rows, reviewing & cropping photos, and finally exporting two A4‑PDFs (text sheet + photo sheet). No personal data ever leaves the browser after the tab closes. + +--- + +## 0. Tech Stack & Design Constraints + +| Area | Decision | +| ---------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------- | +| Front‑end | **SvelteKit** (latest) – SPA mode (`prerender = false`) | +| State | **Svelte Stores** (writable & derived). No persistence beyond sessionStorage/IndexedDB. | +| Styling | **Tailwind CSS** + `@tailwind/typography`, base theme. Page background `bg-gray-50`. Use DaisyUI or shadcn‑ui components if needed. | +| Auth & API | Google Identity Services (popup flow). Scopes: `spreadsheets`, `drive` (read/write). | +| Data storage | • Structured data → **IndexedDB** via `idb` helper. | +| • Image blobs → **Cache Storage** (`caches.open'photos-v1') a | | +| Face detection | **Mediapipe BlazeFace via TF.js (lite)** in a Web Worker. Confidence ≥ 0.8. | +| Virtual list | `svelte-virtual` (react‑window equivalent). | +| PDF | **pdf-lib** (via ESM import). Embed custom font **Lato** (ttf provided in `/static/fonts`). | + +--- + +## 1. User Stories & Acceptance Criteria + +1. **US‑01 Splash** – As a visitor I see a logo + “Start” button (no data loaded). Clicking starts the wizard. +2. **US‑02 Sign‑In** – I authenticate with Google in a popup and grant the requested scopes. On success, token is cached in memory only. +3. **US‑03 Sheet Search** – I type part of a Sheet name; a fuzzy list of my sheets appears (max 20). I select one. +4. **US‑04 Column Mapping** – Columns A–F preview (first 3 data rows). Under each header is a dropdown: Name / Surname / Nationality / Birthday / Picture URL / Ignore. All five fields must be mapped exactly once. Previous mapping is remembered for future sessions in `localStorage` (key: `columnHints`). +5. **US‑05 Row Validate & Select** – I see rows in a virtualized table with validation states. Invalid rows (missing data, age < 18 or > 40) are pre‑flagged. I can tick checkboxes to include/exclude. No in‑app editing. +6. **US‑06 Photo Gallery** – Lazy virtual grid of thumbnails. Auto face‑crop overlay (dashed rectangle). Pictures with 0 or >1 faces are floated to top & flagged “Need manual crop”. I can drag/resize a fixed‑ratio rectangle to override. Cropped area is stored as `{x,y,w,h}` in store. +7. **US‑07 Progress** – While downloading or processing images / faces, I see a determinate progress bar (0‑100%). Each stage (download, face detect, PDF gen). +8. **US‑08 Generate PDFs** – I click “Generate”. Two PDFs download: + + * `people_data.pdf` – text‑only; grid fills as many boxes per A4 as fit (auto calculate cols/rows). Each box uses provided coordinates (see §4). + * `people_photos.pdf` – same grid but with cropped headshot @ modest DPI, name beneath. +9. **US‑09 Cleanup** – On window/tab unload, all IndexedDB records and CacheStorage entries are cleared. + +--- + +## 2. Component Tree (Svelte) + +``` ++Layout.svelte (Tailwind container) + ├─ Splash.svelte + ├─ Wizard.svelte (stateful) + ├─ StepAuth.svelte + ├─ StepSheetSearch.svelte + ├─ StepColumnMap.svelte + ├─ StepRowFilter.svelte + ├─ StepGallery.svelte + └─ StepGenerate.svelte +``` + +### Shared Stores (`src/lib/stores.ts`) + +```ts +export const session = writable<{ + token?: string; + user?: { name: string; email: string }; +}>({}); + +export const sheetData = writable(); // after mapping +export const pictures = writable>(); +export const cropRects = writable>(); +``` + +--- + +## 3. Data Flow + +1. **Auth** ⇒ `session.token` set. +2. **Sheet fetch** (Google Sheets API) ⇒ raw 2D array. +3. **Mapping** ⇒ transform to `RowData` objects. +4. **Validation** ⇒ add `valid` flag; excluded rows removed from downstream. +5. **Download IMG** (Drive/HTTP) ⇒ cache → `pictures` store. +6. **Face Detect** (Worker) ⇒ proposed crop rectangle. +7. **Manual Crop** ⇒ update `cropRects`. +8. **Generate PDFs** ⇒ read `RowData`, `pictures`, `cropRects`. + +--- + +## 4. PDF Layout Spec (mm → points helper) + +* **Font**: Lato Regular, 10 pt. Embed once per doc via `pdfDoc.embedFont()`. +* **Grid**: auto compute `colWidth = 70 mm`, `rowHeight = 35 mm` for text; `imageBox = 30 mm × 30 mm` centred. +* **Coordinates (text sheet)** – within each cell (0,0 is cell top‑left): + + * Full Name – x 5 mm, y 5 mm + * Nationality – x 5 mm, y 12 mm + * Birthday – x 5 mm, y 19 mm +* **Coordinates (photo sheet)** + + * Image top‑left x 5 mm, y 5 mm (square) + * Name text centre below at y (5 + imageBox + 3 mm) +* Fit rows top→bottom, left→right. When page full, add new page. + +--- + +## 5. Key Helpers + +### `googleClient.ts` + +* `initAuth(): Promise` +* `listSheets(query): Promise` (fuzzy) +* `getSheetValues(id, range): Promise` + +### `faceWorker.ts` (Web Worker) + +* Loads BlazeFace lite model once. +* `detectFaces(arrayBuffer) → {faces: Face[], blobURL}` + +### `pdfGenerator.ts` + +* `generateTextPDF(rows: RowData[]): Uint8Array` +* `generatePhotoPDF(rows: RowData[], crops: CropMap): Uint8Array` + +--- + +## 6. Progress Bus (simple derived store) + +```ts +export const progress = writable<{ stage: string; done: number; total: number }>(); +``` + +Set from download loop and face detect worker. + +--- + +## 7. Error Handling + +* Auth popup closed ⇒ toast “Sign‑in cancelled”. +* Sheet fetch 404 ⇒ return to StepSheetSearch with error banner. +* Image download error ⇒ mark row `pictureError = true`, bubble to top. + +--- + +## 8. Folder Structure Skeleton + +``` +src/ + lib/ + stores.ts + googleClient.ts + faceWorker.ts + pdfGenerator.ts + routes/ + +layout.svelte + index.svelte (Splash) + wizard/ + +page.svelte (mount Wizard.svelte) + components/ (each Step*.svelte) + workers/ + faceWorker.js +static/ + fonts/Lato-Regular.ttf +``` + +--- + +## 9. Copilot Prompt Comments (templates) + +### Example: `googleClient.ts` + +```ts +/** + * Google API wrapper – Browser‑only, no refresh tokens. + * Prereqs: `` in root. + * + * Helper responsibilities: + * 1. Launch popup OAuth (scopes: spreadsheets, drive). + * 2. List up to 20 spreadsheets matching fuzzy query. + * 3. Download a specified range (A:Z) as 2‑D array. + * 4. Use in‑memory token, auto‑refresh not required. + */ +``` + +### Example: `StepGallery.svelte` + +```svelte + +``` + +Copy‑paste these comment headers into the top of each file to prime GitHub Copilot with context. + +--- + +## 10. Outstanding TODOs (owner = you) + +* [ ] Provide exact mm → point conversion util if different spec required. +* [ ] Supply precise image/text box sizes & page margins. +* [ ] Drop in `Lato-Regular.ttf` into `/static/fonts`. +* [ ] Decide on logo / brand in Splash. +* [ ] Accessibility pass once UI stabilises. + +--- + +### End of base instruction set + +Feel free to ask for tweaks or deeper code snippets! diff --git a/.github/done.md b/.github/done.md new file mode 100644 index 0000000..5c13166 --- /dev/null +++ b/.github/done.md @@ -0,0 +1,40 @@ +# ESN Card Generator - Project Status + +## ✅ Completed +- Basic SvelteKit project structure +- Package.json with SvelteKit and Tailwind setup + +## 🔄 In Progress +- Scaffolding application structure + +## ❌ To Do +- [ ] Install required dependencies (pdf-lib, idb, mediapipe, etc.) +- [ ] Create Svelte stores (stores.ts) +- [ ] Create component structure: + - [ ] Splash.svelte + - [ ] Wizard.svelte + - [ ] StepAuth.svelte + - [ ] StepSheetSearch.svelte + - [ ] StepColumnMap.svelte + - [ ] StepRowFilter.svelte + - [ ] StepGallery.svelte + - [ ] StepGenerate.svelte +- [ ] Setup Google Identity Services integration +- [ ] Implement Google Sheets API integration +- [ ] Implement Google Drive API integration +- [ ] Setup IndexedDB storage utilities +- [ ] Setup Cache Storage for images +- [ ] Implement face detection with MediaPipe +- [ ] Create PDF generation with pdf-lib +- [ ] Add Lato font assets +- [ ] Implement virtual scrolling for large datasets +- [ ] Add data validation logic +- [ ] Create image cropping interface +- [ ] Add progress indicators +- [ ] Implement cleanup on tab close +- [ ] Testing and polish + +## Notes +- Using Svelte 5 with modern runes syntax +- Following Tailwind CSS styling guidelines +- Privacy-first approach - no data persistence beyond session diff --git a/.github/styling.md b/.github/styling.md new file mode 100644 index 0000000..03c8ac5 --- /dev/null +++ b/.github/styling.md @@ -0,0 +1,363 @@ +# Oveall Styling Guide + +This document outlines the design system and styling conventions used in the application. Use this as a reference when creating new applications with similar visual design. + +## Table of Contents +- [Color Palette](#color-palette) +- [Typography](#typography) +- [Layout Patterns](#layout-patterns) +- [Component Patterns](#component-patterns) +- [Form Elements](#form-elements) +- [Buttons](#buttons) +- [Cards and Containers](#cards-and-containers) +- [Navigation](#navigation) +- [Tables](#tables) +- [Loading States](#loading-states) +- [Toast Notifications](#toast-notifications) +- [Responsive Design](#responsive-design) + +## Color Palette + +### Primary Colors +- **Blue**: Primary action color + - `bg-blue-600` / `text-blue-600` - Primary buttons, links + - `bg-blue-700` / `text-blue-700` - Hover states + - `bg-blue-50` / `text-blue-800` - Info notifications + - `border-blue-600` / `focus:ring-blue-600` - Focus states + +### Gray Scale +- **Text Colors**: + - `text-gray-900` - Primary text (headings, important content) + - `text-gray-700` - Secondary text (labels, descriptions) + - `text-gray-500` - Tertiary text (metadata, placeholders) + +- **Background Colors**: + - `bg-white` - Main content backgrounds + - `bg-gray-50` - Page backgrounds, subtle sections + - `bg-gray-100` - Disabled form fields + - `bg-gray-200` - Loading placeholders + +- **Border Colors**: + - `border-gray-300` - Standard borders (cards, inputs) + - `border-gray-200` - Subtle borders (table rows) + +### Status Colors +- **Success**: `bg-green-50 text-green-800 border-green-300` +- **Warning**: `bg-yellow-50 text-yellow-800 border-yellow-300` +- **Error**: `bg-red-50 text-red-800 border-red-300` +- **Info**: `bg-blue-50 text-blue-800 border-blue-300` + +### Accent Colors +- **Red**: `text-red-600` / `hover:text-red-700` - Danger actions (sign out) +- **Green**: `text-green-600` - Success indicators + +## Typography + +### Headings +```html + +

Page Title

+ + +

Section Title

+

Subsection Title

+``` + +### Body Text +```html + +

Important content

+ + +

Regular content

+

Longer content blocks

+ + +Label +Form Label + + +

Helper text

+``` + +### Text Utilities +- **Font Weight**: `font-bold`, `font-semibold`, `font-medium` +- **Text Alignment**: `text-center`, `text-left` +- **Line Height**: `leading-relaxed` for longer text blocks + +## Layout Patterns + +### Container Pattern +```html +
+ +
+``` + +### Grid Layouts +```html + +
+
+
+
+ + +
+ +
+``` + +### Spacing +- **Standard spacing**: `space-y-6`, `gap-6` - Between major sections +- **Component spacing**: `mb-4`, `mt-6`, `p-6` - Around components +- **Small spacing**: `gap-3`, `mb-2`, `mt-2` - Between related elements +- **Container padding**: `p-4`, `p-6` - Internal container spacing + +## Component Patterns + +### Card Structure +```html +
+
+

Title

+ +
+ +
+``` + +### Avatar/Profile Picture +```html +
+ {initials} +
+``` + +## Form Elements + +### Input Fields +```html + + + + + +``` + +### Textarea +```html + +``` + +### Select Dropdown +```html + +``` + +### Form Labels +```html + +``` + +## Buttons + +### Primary Buttons +```html + +``` + +### Secondary/Outline Buttons +```html + +``` + +### Danger/Red Buttons +```html + +``` + +### Button States +- **Loading**: Replace text with "Loading..." or "Saving..." +- **Disabled**: `disabled:cursor-not-allowed disabled:opacity-50` + +## Cards and Containers + +### Standard Card +```html +
+ +
+``` + +### Card with Header Actions +```html +
+
+

Title

+
+ +
+
+ +
+``` + +## Navigation + +### Top Navigation +```html + +``` + +## Tables + +### Standard Table +```html +
+ + + + + + + + + + + +
Header
Data
+
+``` + +### Definition List (Key-Value Pairs) +```html +
+
+
Key
+
Value
+
+
+``` + +## Loading States + +### Skeleton Loading +```html +
+
+
+
+``` + +### Loading Spinner +```html +
+
+
+``` + +## Toast Notifications + +### Toast Container Structure +```html +
+
+
+ +
+
+

{message}

+
+ +
+
+``` + +### Toast Color Variants +- **Success**: `border-green-300 bg-green-50 text-green-800` +- **Warning**: `border-yellow-300 bg-yellow-50 text-yellow-800` +- **Info**: `border-blue-300 bg-blue-50 text-blue-800` +- **Error**: `border-red-300 bg-red-50 text-red-800` + +## Responsive Design + +### Breakpoints +- **Mobile First**: Default styles for mobile +- **sm**: `sm:` prefix for small screens and up +- **lg**: `lg:` prefix for large screens and up + +### Common Responsive Patterns +```html + +
+ + +
+ + +
+``` + +## Common Utility Classes + +### Flexbox +- `flex items-center justify-between` - Header with title and actions +- `flex items-start gap-3` - Toast notification layout +- `flex flex-col` - Vertical stacking +- `flex-grow` - Fill available space + +### Positioning +- `relative` / `absolute` - Positioning contexts +- `fixed bottom-6 left-1/2 -translate-x-1/2` - Centered fixed positioning + +### Visibility +- `hidden` / `block` - Show/hide elements +- `overflow-hidden` - Clip content +- `overflow-x-auto` - Horizontal scroll for tables + +### Borders and Shadows +- `rounded-md` - Standard border radius for all components +- `rounded-full` - Circular elements (avatars) +- `shadow-lg` - Toast notifications and elevated elements +- `shadow-none` - Remove default shadows when needed + +## Design Tokens Summary + +### Standardized Values +- **Border Radius**: `rounded-md` (6px) for all rectangular components +- **Border Colors**: `border-gray-300` (standard), `border-gray-200` (subtle) +- **Focus States**: `focus:border-blue-600 focus:ring-blue-600` +- **Spacing**: `gap-4` (1rem), `gap-6` (1.5rem), `p-4` (1rem), `p-6` (1.5rem) +- **Font Weights**: `font-medium` for buttons and emphasis, `font-semibold` for headings, `font-bold` for titles +- **Status Border**: All status colors use `-300` shade for borders (e.g., `border-green-300`) + +This styling guide captures the core design patterns used throughout the ScanWave application. Follow these conventions to maintain visual consistency across your new applications. diff --git a/package-lock.json b/package-lock.json index bedb2a2..399a93a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7,6 +7,16 @@ "": { "name": "esn-card-generator", "version": "0.0.1", + "dependencies": { + "@tensorflow/tfjs": "^4.22.0", + "@tensorflow/tfjs-backend-webgl": "^4.22.0", + "@types/gapi": "^0.0.47", + "@types/gapi.client.drive": "^3.0.15", + "@types/gapi.client.sheets": "^4.0.20201031", + "@types/google.accounts": "^0.0.17", + "idb": "^8.0.3", + "pdf-lib": "^1.17.1" + }, "devDependencies": { "@sveltejs/adapter-node": "^5.2.12", "@sveltejs/kit": "^2.22.0", @@ -531,6 +541,54 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@maxim_mazurok/gapi.client.discovery-v1": { + "version": "0.1.20200806", + "resolved": "https://registry.npmjs.org/@maxim_mazurok/gapi.client.discovery-v1/-/gapi.client.discovery-v1-0.1.20200806.tgz", + "integrity": "sha512-Wl6UfmZVDdWbY3PUu8E2ULk9RPLjnMqp/iOA4tcK8Ne+U/GmlnWP/e34IaZNGArfl7iXJNOG+/3Rj9L9jQyF9Q==", + "license": "MIT", + "dependencies": { + "@types/gapi.client": "*", + "@types/gapi.client.discovery-v1": "*" + } + }, + "node_modules/@maxim_mazurok/gapi.client.drive-v3": { + "version": "0.0.20250715", + "resolved": "https://registry.npmjs.org/@maxim_mazurok/gapi.client.drive-v3/-/gapi.client.drive-v3-0.0.20250715.tgz", + "integrity": "sha512-9rfKfuarhnYSjk+TEqWCVxBf5/efsnonU35hZSYDRvLH2TAz/fuWxaEC8YeuAhJJXj85YdpHXgZbCWg888i9Hw==", + "license": "MIT", + "dependencies": { + "@types/gapi.client": "*", + "@types/gapi.client.discovery-v1": "*" + } + }, + "node_modules/@maxim_mazurok/gapi.client.sheets-v4": { + "version": "0.0.20250708", + "resolved": "https://registry.npmjs.org/@maxim_mazurok/gapi.client.sheets-v4/-/gapi.client.sheets-v4-0.0.20250708.tgz", + "integrity": "sha512-yNaJHX6wYyjqGW87bnZmp+yeDhGPLyafnpV6ZyliD2/BkXF4UDk1u1BSc/z3fO5tR7WPEnzzGQN1cCmysTVDkQ==", + "license": "MIT", + "dependencies": { + "@types/gapi.client": "*", + "@types/gapi.client.discovery-v1": "*" + } + }, + "node_modules/@pdf-lib/standard-fonts": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@pdf-lib/standard-fonts/-/standard-fonts-1.0.0.tgz", + "integrity": "sha512-hU30BK9IUN/su0Mn9VdlVKsWBS6GyhVfqjwl1FjZN4TxP6cCw0jP2w7V3Hf5uX7M0AZJ16vey9yE0ny7Sa59ZA==", + "license": "MIT", + "dependencies": { + "pako": "^1.0.6" + } + }, + "node_modules/@pdf-lib/upng": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@pdf-lib/upng/-/upng-1.0.1.tgz", + "integrity": "sha512-dQK2FUMQtowVP00mtIksrlZhdFXQZPC+taih1q4CvPZ5vqdxR/LKBaFg0oAfzd1GlHZXXSPdQfzQnt+ViGvEIQ==", + "license": "MIT", + "dependencies": { + "pako": "^1.0.10" + } + }, "node_modules/@polka/url": { "version": "1.0.0-next.29", "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.29.tgz", @@ -1289,6 +1347,119 @@ "vite": "^5.2.0 || ^6 || ^7" } }, + "node_modules/@tensorflow/tfjs": { + "version": "4.22.0", + "resolved": "https://registry.npmjs.org/@tensorflow/tfjs/-/tfjs-4.22.0.tgz", + "integrity": "sha512-0TrIrXs6/b7FLhLVNmfh8Sah6JgjBPH4mZ8JGb7NU6WW+cx00qK5BcAZxw7NCzxj6N8MRAIfHq+oNbPUNG5VAg==", + "license": "Apache-2.0", + "dependencies": { + "@tensorflow/tfjs-backend-cpu": "4.22.0", + "@tensorflow/tfjs-backend-webgl": "4.22.0", + "@tensorflow/tfjs-converter": "4.22.0", + "@tensorflow/tfjs-core": "4.22.0", + "@tensorflow/tfjs-data": "4.22.0", + "@tensorflow/tfjs-layers": "4.22.0", + "argparse": "^1.0.10", + "chalk": "^4.1.0", + "core-js": "3.29.1", + "regenerator-runtime": "^0.13.5", + "yargs": "^16.0.3" + }, + "bin": { + "tfjs-custom-module": "dist/tools/custom_module/cli.js" + } + }, + "node_modules/@tensorflow/tfjs-backend-cpu": { + "version": "4.22.0", + "resolved": "https://registry.npmjs.org/@tensorflow/tfjs-backend-cpu/-/tfjs-backend-cpu-4.22.0.tgz", + "integrity": "sha512-1u0FmuLGuRAi8D2c3cocHTASGXOmHc/4OvoVDENJayjYkS119fcTcQf4iHrtLthWyDIPy3JiPhRrZQC9EwnhLw==", + "license": "Apache-2.0", + "dependencies": { + "@types/seedrandom": "^2.4.28", + "seedrandom": "^3.0.5" + }, + "engines": { + "yarn": ">= 1.3.2" + }, + "peerDependencies": { + "@tensorflow/tfjs-core": "4.22.0" + } + }, + "node_modules/@tensorflow/tfjs-backend-webgl": { + "version": "4.22.0", + "resolved": "https://registry.npmjs.org/@tensorflow/tfjs-backend-webgl/-/tfjs-backend-webgl-4.22.0.tgz", + "integrity": "sha512-H535XtZWnWgNwSzv538czjVlbJebDl5QTMOth4RXr2p/kJ1qSIXE0vZvEtO+5EC9b00SvhplECny2yDewQb/Yg==", + "license": "Apache-2.0", + "dependencies": { + "@tensorflow/tfjs-backend-cpu": "4.22.0", + "@types/offscreencanvas": "~2019.3.0", + "@types/seedrandom": "^2.4.28", + "seedrandom": "^3.0.5" + }, + "engines": { + "yarn": ">= 1.3.2" + }, + "peerDependencies": { + "@tensorflow/tfjs-core": "4.22.0" + } + }, + "node_modules/@tensorflow/tfjs-converter": { + "version": "4.22.0", + "resolved": "https://registry.npmjs.org/@tensorflow/tfjs-converter/-/tfjs-converter-4.22.0.tgz", + "integrity": "sha512-PT43MGlnzIo+YfbsjM79Lxk9lOq6uUwZuCc8rrp0hfpLjF6Jv8jS84u2jFb+WpUeuF4K33ZDNx8CjiYrGQ2trQ==", + "license": "Apache-2.0", + "peerDependencies": { + "@tensorflow/tfjs-core": "4.22.0" + } + }, + "node_modules/@tensorflow/tfjs-core": { + "version": "4.22.0", + "resolved": "https://registry.npmjs.org/@tensorflow/tfjs-core/-/tfjs-core-4.22.0.tgz", + "integrity": "sha512-LEkOyzbknKFoWUwfkr59vSB68DMJ4cjwwHgicXN0DUi3a0Vh1Er3JQqCI1Hl86GGZQvY8ezVrtDIvqR1ZFW55A==", + "license": "Apache-2.0", + "dependencies": { + "@types/long": "^4.0.1", + "@types/offscreencanvas": "~2019.7.0", + "@types/seedrandom": "^2.4.28", + "@webgpu/types": "0.1.38", + "long": "4.0.0", + "node-fetch": "~2.6.1", + "seedrandom": "^3.0.5" + }, + "engines": { + "yarn": ">= 1.3.2" + } + }, + "node_modules/@tensorflow/tfjs-core/node_modules/@types/offscreencanvas": { + "version": "2019.7.3", + "resolved": "https://registry.npmjs.org/@types/offscreencanvas/-/offscreencanvas-2019.7.3.tgz", + "integrity": "sha512-ieXiYmgSRXUDeOntE1InxjWyvEelZGP63M+cGuquuRLuIKKT1osnkXjxev9B7d1nXSug5vpunx+gNlbVxMlC9A==", + "license": "MIT" + }, + "node_modules/@tensorflow/tfjs-data": { + "version": "4.22.0", + "resolved": "https://registry.npmjs.org/@tensorflow/tfjs-data/-/tfjs-data-4.22.0.tgz", + "integrity": "sha512-dYmF3LihQIGvtgJrt382hSRH4S0QuAp2w1hXJI2+kOaEqo5HnUPG0k5KA6va+S1yUhx7UBToUKCBHeLHFQRV4w==", + "license": "Apache-2.0", + "dependencies": { + "@types/node-fetch": "^2.1.2", + "node-fetch": "~2.6.1", + "string_decoder": "^1.3.0" + }, + "peerDependencies": { + "@tensorflow/tfjs-core": "4.22.0", + "seedrandom": "^3.0.5" + } + }, + "node_modules/@tensorflow/tfjs-layers": { + "version": "4.22.0", + "resolved": "https://registry.npmjs.org/@tensorflow/tfjs-layers/-/tfjs-layers-4.22.0.tgz", + "integrity": "sha512-lybPj4ZNj9iIAPUj7a8ZW1hg8KQGfqWLlCZDi9eM/oNKCCAgchiyzx8OrYoWmRrB+AM6VNEeIT+2gZKg5ReihA==", + "license": "Apache-2.0 AND MIT", + "peerDependencies": { + "@tensorflow/tfjs-core": "4.22.0" + } + }, "node_modules/@types/cookie": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.6.0.tgz", @@ -1303,6 +1474,84 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/gapi": { + "version": "0.0.47", + "resolved": "https://registry.npmjs.org/@types/gapi/-/gapi-0.0.47.tgz", + "integrity": "sha512-/ZsLuq6BffMgbKMtZyDZ8vwQvTyKhKQ1G2K6VyWCgtHHhfSSXbk4+4JwImZiTjWNXfI2q1ZStAwFFHSkNoTkHA==", + "license": "MIT" + }, + "node_modules/@types/gapi.client": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/gapi.client/-/gapi.client-1.0.8.tgz", + "integrity": "sha512-qJQUmmumbYym3Amax0S8CVzuSngcXsC1fJdwRS2zeW5lM63zXkw4wJFP+bG0jzgi0R6EsJKoHnGNVTDbOyG1ng==", + "license": "MIT" + }, + "node_modules/@types/gapi.client.discovery-v1": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/@types/gapi.client.discovery-v1/-/gapi.client.discovery-v1-0.0.4.tgz", + "integrity": "sha512-uevhRumNE65F5mf2gABLaReOmbFSXONuzFZjNR3dYv6BmkHg+wciubHrfBAsp3554zNo3Dcg6dUAlwMqQfpwjQ==", + "license": "MIT", + "dependencies": { + "@maxim_mazurok/gapi.client.discovery-v1": "latest" + } + }, + "node_modules/@types/gapi.client.drive": { + "version": "3.0.15", + "resolved": "https://registry.npmjs.org/@types/gapi.client.drive/-/gapi.client.drive-3.0.15.tgz", + "integrity": "sha512-qEfI0LxUBadOLmym4FkaNGpI4ibBCBPJHiUFWKIv0GIp7yKT2d+wztJYKr9giIRecErUCF+jGSDw1fzTZ6hPVQ==", + "deprecated": "use @types/gapi.client.drive-v3 instead; see https://github.com/Maxim-Mazurok/google-api-typings-generator/issues/652 for details", + "license": "MIT", + "dependencies": { + "@maxim_mazurok/gapi.client.drive-v3": "latest" + } + }, + "node_modules/@types/gapi.client.sheets": { + "version": "4.0.20201031", + "resolved": "https://registry.npmjs.org/@types/gapi.client.sheets/-/gapi.client.sheets-4.0.20201031.tgz", + "integrity": "sha512-1Aiu11rNNoyPDHW6v8TVcSmlDN+MkxSuafwiawaK5YqZ+uYA+O63vjUvkK+3qNduSLh7D9qBJc/8GGwgN6gsTw==", + "deprecated": "use @types/gapi.client.sheets-v4 instead; see https://github.com/Maxim-Mazurok/google-api-typings-generator/issues/652 for details", + "license": "MIT", + "dependencies": { + "@maxim_mazurok/gapi.client.sheets-v4": "latest" + } + }, + "node_modules/@types/google.accounts": { + "version": "0.0.17", + "resolved": "https://registry.npmjs.org/@types/google.accounts/-/google.accounts-0.0.17.tgz", + "integrity": "sha512-ZDOmep4Zslca9pFJ7oXgg/DdirGU+tPuWlXRGfIFNqMtBvkEgzktY292O3V03Ul6AA7ye4CDVZlMnd9m1FeIeg==", + "license": "MIT" + }, + "node_modules/@types/long": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.2.tgz", + "integrity": "sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA==", + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "24.0.14", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.0.14.tgz", + "integrity": "sha512-4zXMWD91vBLGRtHK3YbIoFMia+1nqEz72coM42C5ETjnNCa/heoj7NT1G67iAfOqMmcfhuCZ4uNpyz8EjlAejw==", + "license": "MIT", + "dependencies": { + "undici-types": "~7.8.0" + } + }, + "node_modules/@types/node-fetch": { + "version": "2.6.12", + "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.12.tgz", + "integrity": "sha512-8nneRWKCg3rMtF69nLQJnOYUcbafYeFSjqkw3jCRLsqkWFlHaoQrr5mXmofFGOx3DKn7UfmBMyov8ySvLRVldA==", + "license": "MIT", + "dependencies": { + "@types/node": "*", + "form-data": "^4.0.0" + } + }, + "node_modules/@types/offscreencanvas": { + "version": "2019.3.0", + "resolved": "https://registry.npmjs.org/@types/offscreencanvas/-/offscreencanvas-2019.3.0.tgz", + "integrity": "sha512-esIJx9bQg+QYF0ra8GnvfianIY8qWB0GBx54PK5Eps6m+xTj86KLavHv6qDhzKcu5UUOgNfJ2pWaIIV7TRUd9Q==", + "license": "MIT" + }, "node_modules/@types/resolve": { "version": "1.20.2", "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.20.2.tgz", @@ -1310,6 +1559,18 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/seedrandom": { + "version": "2.4.34", + "resolved": "https://registry.npmjs.org/@types/seedrandom/-/seedrandom-2.4.34.tgz", + "integrity": "sha512-ytDiArvrn/3Xk6/vtylys5tlY6eo7Ane0hvcx++TKo6RxQXuVfW0AF/oeWqAj9dN29SyhtawuXstgmPlwNcv/A==", + "license": "MIT" + }, + "node_modules/@webgpu/types": { + "version": "0.1.38", + "resolved": "https://registry.npmjs.org/@webgpu/types/-/types-0.1.38.tgz", + "integrity": "sha512-7LrhVKz2PRh+DD7+S+PVaFd5HxaWQvoMqBbsV9fNJO1pjUs1P8bM2vQVNfk+3URTqbuTI7gkXi0rfsN0IadoBA==", + "license": "BSD-3-Clause" + }, "node_modules/acorn": { "version": "8.15.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", @@ -1323,6 +1584,39 @@ "node": ">=0.4.0" } }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "license": "MIT", + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, "node_modules/aria-query": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.2.tgz", @@ -1333,6 +1627,12 @@ "node": ">= 0.4" } }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, "node_modules/axobject-query": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz", @@ -1343,6 +1643,35 @@ "node": ">= 0.4" } }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, "node_modules/chokidar": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", @@ -1369,6 +1698,17 @@ "node": ">=18" } }, + "node_modules/cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, "node_modules/clsx": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", @@ -1379,6 +1719,36 @@ "node": ">=6" } }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "license": "MIT" + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/commondir": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", @@ -1396,6 +1766,17 @@ "node": ">= 0.6" } }, + "node_modules/core-js": { + "version": "3.29.1", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.29.1.tgz", + "integrity": "sha512-+jwgnhg6cQxKYIIjGtAHq2nwUOolo9eoFZ4sHfUH09BLXBgxnH4gA0zEd+t+BO2cNB8idaBtZFcFTRjQJRJmAw==", + "hasInstallScript": true, + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, "node_modules/debug": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", @@ -1424,6 +1805,15 @@ "node": ">=0.10.0" } }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/detect-libc": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.4.tgz", @@ -1441,6 +1831,26 @@ "dev": true, "license": "MIT" }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT" + }, "node_modules/enhanced-resolve": { "version": "5.18.2", "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.2.tgz", @@ -1455,6 +1865,51 @@ "node": ">=10.13.0" } }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/esbuild": { "version": "0.25.6", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.6.tgz", @@ -1497,6 +1952,15 @@ "@esbuild/win32-x64": "0.25.6" } }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/esm-env": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/esm-env/-/esm-env-1.2.2.tgz", @@ -1536,6 +2000,22 @@ } } }, + "node_modules/form-data": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz", + "integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/fsevents": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", @@ -1555,12 +2035,69 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "dev": true, "license": "MIT", "funding": { "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/graceful-fs": { "version": "4.2.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", @@ -1568,11 +2105,46 @@ "dev": true, "license": "ISC" }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/hasown": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", - "dev": true, "license": "MIT", "dependencies": { "function-bind": "^1.1.2" @@ -1581,6 +2153,12 @@ "node": ">= 0.4" } }, + "node_modules/idb": { + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/idb/-/idb-8.0.3.tgz", + "integrity": "sha512-LtwtVyVYO5BqRvcsKuB2iUMnHwPVByPCXFXOpuU96IZPPoPN6xjOGxZQ74pgSVVLQWtUOYgyeL4GE98BY5D3wg==", + "license": "ISC" + }, "node_modules/is-core-module": { "version": "2.16.1", "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", @@ -1597,6 +2175,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/is-module": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz", @@ -1880,6 +2467,12 @@ "dev": true, "license": "MIT" }, + "node_modules/long": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", + "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==", + "license": "Apache-2.0" + }, "node_modules/magic-string": { "version": "0.30.17", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz", @@ -1890,6 +2483,36 @@ "@jridgewell/sourcemap-codec": "^1.5.0" } }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/minipass": { "version": "7.1.2", "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", @@ -1975,6 +2598,32 @@ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, + "node_modules/node-fetch": { + "version": "2.6.13", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.13.tgz", + "integrity": "sha512-StxNAxh15zr77QvvkmveSQ8uCQ4+v5FkvNTj0OESmiHu+VRi/gXArXtkWMElOsOUNLtUEvI4yS+rdtOHZTwlQA==", + "license": "MIT", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/pako": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", + "license": "(MIT AND Zlib)" + }, "node_modules/path-parse": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", @@ -1982,6 +2631,18 @@ "dev": true, "license": "MIT" }, + "node_modules/pdf-lib": { + "version": "1.17.1", + "resolved": "https://registry.npmjs.org/pdf-lib/-/pdf-lib-1.17.1.tgz", + "integrity": "sha512-V/mpyJAoTsN4cnP31vc0wfNA1+p20evqqnap0KLoRUN0Yk/p3wN52DOEsL4oBFcLdb76hlpKPtzJIgo67j/XLw==", + "license": "MIT", + "dependencies": { + "@pdf-lib/standard-fonts": "^1.0.0", + "@pdf-lib/upng": "^1.0.1", + "pako": "^1.0.11", + "tslib": "^1.11.1" + } + }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", @@ -2159,6 +2820,21 @@ "url": "https://paulmillr.com/funding/" } }, + "node_modules/regenerator-runtime": { + "version": "0.13.11", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", + "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==", + "license": "MIT" + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/resolve": { "version": "1.22.10", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", @@ -2233,6 +2909,32 @@ "node": ">=6" } }, + "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/seedrandom": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/seedrandom/-/seedrandom-3.0.5.tgz", + "integrity": "sha512-8OwmbklUNzwezjGInmZ+2clQmExQPvomqjL7LFqOYqtmuxRgQYqOD3mHaU+MvZn5FLUeVxVfQjwLZW/n/JFuqg==", + "license": "MIT" + }, "node_modules/set-cookie-parser": { "version": "2.7.1", "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.1.tgz", @@ -2265,6 +2967,59 @@ "node": ">=0.10.0" } }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "license": "BSD-3-Clause" + }, + "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/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/supports-preserve-symlinks-flag": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", @@ -2400,6 +3155,18 @@ "node": ">=6" } }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "license": "MIT" + }, + "node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "license": "0BSD" + }, "node_modules/typescript": { "version": "5.8.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", @@ -2414,6 +3181,12 @@ "node": ">=14.17" } }, + "node_modules/undici-types": { + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.8.0.tgz", + "integrity": "sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw==", + "license": "MIT" + }, "node_modules/uuid": { "version": "11.1.0", "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.1.0.tgz", @@ -2536,6 +3309,48 @@ } } }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "license": "BSD-2-Clause" + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "license": "MIT", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "license": "ISC", + "engines": { + "node": ">=10" + } + }, "node_modules/yallist": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz", @@ -2546,6 +3361,33 @@ "node": ">=18" } }, + "node_modules/yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "license": "MIT", + "dependencies": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs-parser": { + "version": "20.2.9", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", + "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", + "license": "ISC", + "engines": { + "node": ">=10" + } + }, "node_modules/zimmerframe": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/zimmerframe/-/zimmerframe-1.1.2.tgz", diff --git a/package.json b/package.json index 495277c..e5bd68f 100644 --- a/package.json +++ b/package.json @@ -27,5 +27,15 @@ "typescript": "^5.0.0", "vite": "^7.0.4", "vite-plugin-devtools-json": "^0.2.0" + }, + "dependencies": { + "@tensorflow/tfjs": "^4.22.0", + "@tensorflow/tfjs-backend-webgl": "^4.22.0", + "@types/gapi": "^0.0.47", + "@types/gapi.client.drive": "^3.0.15", + "@types/gapi.client.sheets": "^4.0.20201031", + "@types/google.accounts": "^0.0.17", + "idb": "^8.0.3", + "pdf-lib": "^1.17.1" } } diff --git a/src/hooks.server.ts b/src/hooks.server.ts new file mode 100644 index 0000000..94a5fc9 --- /dev/null +++ b/src/hooks.server.ts @@ -0,0 +1,12 @@ +import type { Handle } from '@sveltejs/kit'; + +export const handle: Handle = async ({ event, resolve }) => { + const response = await resolve(event); + + // Allow popups to use window.opener for Google Identity Services + response.headers.set('Cross-Origin-Opener-Policy', 'same-origin-allow-popups'); + // Disable Cross-Origin-Embedder-Policy for this application + response.headers.set('Cross-Origin-Embedder-Policy', 'unsafe-none'); + + return response; +}; diff --git a/src/lib/components/Splash.svelte b/src/lib/components/Splash.svelte new file mode 100644 index 0000000..86874ea --- /dev/null +++ b/src/lib/components/Splash.svelte @@ -0,0 +1,41 @@ + + +
+
+
+ +
+ ESN +
+ +

+ ESN Card Generator +

+ +

+ Transform your Google Sheets into professional ESN membership cards with photos. + Privacy-first: all processing happens in your browser. +

+ +
+

✓ Import data from Google Sheets

+

✓ Automatic face detection and cropping

+

✓ Generate text and photo PDFs

+

✓ No data stored on our servers

+
+
+ + +
+
diff --git a/src/lib/components/Wizard.svelte b/src/lib/components/Wizard.svelte new file mode 100644 index 0000000..5a1e140 --- /dev/null +++ b/src/lib/components/Wizard.svelte @@ -0,0 +1,59 @@ + + +
+
+ +
+
+

+ {stepTitles[$currentStep - 1]} +

+ + Step {$currentStep} of {steps.length} + +
+ + +
+
+
+
+ + +
+ +
+ + +
+ +
+
+
diff --git a/src/lib/components/wizard/StepAuth.svelte b/src/lib/components/wizard/StepAuth.svelte new file mode 100644 index 0000000..b7647c5 --- /dev/null +++ b/src/lib/components/wizard/StepAuth.svelte @@ -0,0 +1,80 @@ + + +
+
+
+
+ + + + +
+ +

+ Connect to Google +

+ +

+ Sign in with your Google account to access your Google Sheets and Google Drive for photo downloads. +

+ +
+

Required permissions:

+

• View your Google Spreadsheets

+

• View and manage the files in your Google Drive

+
+
+ + {#if $isSignedIn} + +
+
+ + + + Authenticated +
+ +

+ You are signed in. +

+ +
+ + + +
+
+ {:else} + + + {/if} +
+
diff --git a/src/lib/components/wizard/StepColumnMap.svelte b/src/lib/components/wizard/StepColumnMap.svelte new file mode 100644 index 0000000..c2e9a9f --- /dev/null +++ b/src/lib/components/wizard/StepColumnMap.svelte @@ -0,0 +1,4 @@ +
+

Map Columns

+

Column mapping functionality will be implemented here.

+
diff --git a/src/lib/components/wizard/StepGallery.svelte b/src/lib/components/wizard/StepGallery.svelte new file mode 100644 index 0000000..9696c76 --- /dev/null +++ b/src/lib/components/wizard/StepGallery.svelte @@ -0,0 +1,4 @@ +
+

Review Photos

+

Photo gallery and review functionality will be implemented here.

+
diff --git a/src/lib/components/wizard/StepGenerate.svelte b/src/lib/components/wizard/StepGenerate.svelte new file mode 100644 index 0000000..ab34f01 --- /dev/null +++ b/src/lib/components/wizard/StepGenerate.svelte @@ -0,0 +1,4 @@ +
+

Generate PDFs

+

PDF generation functionality will be implemented here.

+
diff --git a/src/lib/components/wizard/StepRowFilter.svelte b/src/lib/components/wizard/StepRowFilter.svelte new file mode 100644 index 0000000..79c4b4b --- /dev/null +++ b/src/lib/components/wizard/StepRowFilter.svelte @@ -0,0 +1,4 @@ +
+

Filter Rows

+

Row filtering functionality will be implemented here.

+
diff --git a/src/lib/components/wizard/StepSheetSearch.svelte b/src/lib/components/wizard/StepSheetSearch.svelte new file mode 100644 index 0000000..e69de29 diff --git a/src/lib/google.ts b/src/lib/google.ts new file mode 100644 index 0000000..91ec77c --- /dev/null +++ b/src/lib/google.ts @@ -0,0 +1,112 @@ +import { writable } from 'svelte/store'; + +const GOOGLE_CLIENT_ID = import.meta.env.VITE_GOOGLE_CLIENT_ID; + +export const isGoogleApiReady = writable(false); +export const isSignedIn = writable(false); + +let tokenClient: google.accounts.oauth2.TokenClient; + +const TOKEN_KEY = 'google_oauth_token'; +export function initGoogleClient(callback: () => void) { + const script = document.createElement('script'); + script.src = 'https://apis.google.com/js/api.js'; + script.onload = () => { + gapi.load('client', async () => { + await gapi.client.init({ + // NOTE: API KEY IS NOT REQUIRED FOR THIS IMPLEMENTATION + // apiKey: 'YOUR_API_KEY', + discoveryDocs: [ + 'https://www.googleapis.com/discovery/v1/apis/drive/v3/rest', + 'https://www.googleapis.com/discovery/v1/apis/sheets/v4/rest', + ], + }); + isGoogleApiReady.set(true); + // Restore token from storage if available + const saved = localStorage.getItem(TOKEN_KEY); + if (saved) { + try { + const data = JSON.parse(saved); + if (data.access_token && data.expires_at && data.expires_at > Date.now()) { + gapi.client.setToken({ access_token: data.access_token }); + isSignedIn.set(true); + } else { + localStorage.removeItem(TOKEN_KEY); + } + } catch { + localStorage.removeItem(TOKEN_KEY); + } + } + callback(); + }); + }; + document.body.appendChild(script); + + const scriptGsi = document.createElement('script'); + scriptGsi.src = 'https://accounts.google.com/gsi/client'; + scriptGsi.onload = () => { + tokenClient = google.accounts.oauth2.initTokenClient({ + client_id: GOOGLE_CLIENT_ID, + scope: 'https://www.googleapis.com/auth/drive.readonly https://www.googleapis.com/auth/spreadsheets.readonly', + callback: (tokenResponse) => { + if (tokenResponse?.access_token) { + // Set token in gapi client + gapi.client.setToken({ access_token: tokenResponse.access_token }); + isSignedIn.set(true); + // Persist token with expiration + const expiresInSeconds = tokenResponse.expires_in + ? Number(tokenResponse.expires_in) + : 0; + const expiresInMs = expiresInSeconds * 1000; + const record = { + access_token: tokenResponse.access_token, + expires_at: expiresInMs ? Date.now() + expiresInMs : Date.now() + 3600 * 1000 + }; + localStorage.setItem(TOKEN_KEY, JSON.stringify(record)); + } + }, + }); + }; + document.body.appendChild(scriptGsi); +} + +export function handleSignIn() { + if (gapi.client.getToken() === null) { + tokenClient.requestAccessToken({ prompt: 'consent' }); + } else { + tokenClient.requestAccessToken({ prompt: '' }); + } +} + +export function handleSignOut() { + const token = gapi.client.getToken(); + if (token !== null) { + google.accounts.oauth2.revoke(token.access_token, () => { + gapi.client.setToken(null); + isSignedIn.set(false); + }); + } +} + +export async function searchSheets(query: string) { + if (!gapi.client.drive) { + throw new Error('Google Drive API not loaded'); + } + const response = await gapi.client.drive.files.list({ + q: `mimeType='application/vnd.google-apps.spreadsheet' and name contains '${query}'`, + fields: 'files(id, name, iconLink, webViewLink)', + pageSize: 20, + }); + return response.result.files || []; +} + +export async function getSheetData(spreadsheetId: string, range: string) { + if (!gapi.client.sheets) { + throw new Error('Google Sheets API not loaded'); + } + const response = await gapi.client.sheets.spreadsheets.values.get({ + spreadsheetId, + range, + }); + return response.result.values || []; +} diff --git a/src/lib/stores.ts b/src/lib/stores.ts new file mode 100644 index 0000000..0c67e0a --- /dev/null +++ b/src/lib/stores.ts @@ -0,0 +1,142 @@ +import { writable, derived } from 'svelte/store'; + +// User session and authentication +export const session = writable<{ + token?: string; + user?: { name: string; email: string }; +}>({}); + +// Raw sheet data after import +export const rawSheetData = writable([]); + +// Column mapping configuration +export const columnMapping = writable<{ + name?: number; + surname?: number; + nationality?: number; + birthday?: number; + pictureUrl?: number; +}>({}); + +// Processed row data after mapping and validation +export interface RowData { + id: string; + name: string; + surname: string; + nationality: string; + birthday: string; + pictureUrl: string; + valid: boolean; + included: boolean; + age?: number; + validationErrors: string[]; +} + +export const sheetData = writable([]); + +// Picture storage and metadata +export interface PictureBlobInfo { + id: string; + blob: Blob; + url: string; + downloaded: boolean; + faceDetected: boolean; + faceCount: number; +} + +export const pictures = writable>({}); + +// Crop rectangles for each photo +export interface Crop { + x: number; + y: number; + width: number; + height: number; +} + +export const cropRects = writable>({}); + +// Wizard state management +export const currentStep = writable(0); + +export const steps = [ + 'splash', + 'auth', + 'search', + 'mapping', + 'validation', + 'gallery', + 'generate' +] as const; + +export type WizardStep = typeof steps[number]; + +export const currentStepName = derived( + currentStep, + ($currentStep) => steps[$currentStep] +); + +// Progress tracking +export interface ProgressState { + stage: string; + current: number; + total: number; + message: string; +} + +export const progress = writable({ + stage: '', + current: 0, + total: 0, + message: '' +}); + +// Google Sheets list for search +export interface SheetInfo { + id: string; + name: string; + url: string; +} + +export const availableSheets = writable([]); + +// Selected sheet +export const selectedSheet = writable(null); + +// Validation derived stores +export const validRowCount = derived( + sheetData, + ($sheetData) => $sheetData.filter(row => row.valid && row.included).length +); + +export const invalidRowCount = derived( + sheetData, + ($sheetData) => $sheetData.filter(row => !row.valid).length +); + +export const totalRowCount = derived( + sheetData, + ($sheetData) => $sheetData.length +); + +// Face detection status +export const faceDetectionProgress = writable<{ + completed: number; + total: number; + currentImage: string; +}>({ + completed: 0, + total: 0, + currentImage: '' +}); + +// PDF generation status +export const pdfGenerationStatus = writable<{ + generating: boolean; + stage: 'preparing' | 'text-pdf' | 'photo-pdf' | 'complete'; + progress: number; +}>({ + generating: false, + stage: 'preparing', + progress: 0 +}); diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte index b93e9ba..370e597 100644 --- a/src/routes/+layout.svelte +++ b/src/routes/+layout.svelte @@ -1,7 +1,16 @@ {@render children()} diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte index cc88df0..c0d2968 100644 --- a/src/routes/+page.svelte +++ b/src/routes/+page.svelte @@ -1,2 +1,11 @@ -

Welcome to SvelteKit

-

Visit svelte.dev/docs/kit to read the documentation

+ + +{#if $currentStep === 0} + +{:else} + +{/if}