diff --git a/AGENTS.md b/AGENTS.md
new file mode 100644
index 0000000..ff303a3
--- /dev/null
+++ b/AGENTS.md
@@ -0,0 +1,129 @@
+# RealBrain Design System — instructions for coding agents
+
+Copy this file into a RealBrain repository as `AGENTS.md` (or append it to `CLAUDE.md`). It tells an agent how to apply the design system without being reminded each time. Adjust the one path in "Source of truth" to wherever the repository vendors `tokens.css`.
+
+## Source of truth
+
+- In this repository: `src/tokens.css` — a vendored copy of https://realbrain.cc/design-system/tokens.css. Never edit it by hand; refresh it with `curl -o src/tokens.css https://realbrain.cc/design-system/tokens.css`.
+- Full guide: https://realbrain.cc/design-system/design-system.md
+- Short reference for agents: https://realbrain.cc/design-system/llms.txt
+- Tokens as JSON (DTCG): https://realbrain.cc/design-system/tokens.json
+
+Read the guide's §2 (non-negotiables) and §6 (components) before writing or editing any CSS, component, or UI copy.
+
+## Rules
+
+1. **Tokens only.** Every colour, size, radius, shadow, duration, and easing in component code is a `var(--token)` from `tokens.css`. No raw hex, no raw px, no `rgba()`.
+2. **One accent per screen.** Exactly one `.btn-primary` (or one accent-coloured action). Everything else is `.btn-secondary`, `.btn-ghost`, or neutral text.
+3. **Weight ≤ 600.** Never `font-weight: 700` or `bold`. The font import does not even load 700.
+4. **Flat.** No `linear-gradient`, `radial-gradient`, `backdrop-filter`, `text-shadow`, glow, or decorative `box-shadow`. `--shadow-*` is only for surfaces that are actually elevated (menus, modals, toasts).
+5. **Never white on accent.** Text and icons on `--accent` use `--accent-fg`.
+6. **Semantic colour is never the only signal.** Use `.status.ok|warn|err|info` (dot plus text) or an icon plus text.
+7. **Copy:** sentence case everywhere. Buttons are verbs ("Sign in", "Create project"). "Sign in", never "Log in" or "Login". Errors say what happened and what to do next.
+8. **One density per product.** `.density-comfortable` (default, 44px controls) or `.density-compact` (32px) on a container. Never both.
+9. **Use the existing component classes** (`.btn`, `.field`, `.input`, `.select`, `.checkbox`, `.radio`, `.toggle`, `.card`, `dialog.modal`, `.toast`, `.tabs`, `.table`, `.status`, `.scaffold`, `.empty`, `.skeleton`). Do not re-style them locally. Do not build a div-based fake of a native control.
+10. **Motion goes through `--dur-1/2/3` and `--ease`.** Nothing else. Reduced motion is handled globally by `tokens.css`.
+
+## When the system doesn't have what you need
+
+Do not invent a value. Build the piece from existing tokens if you can. If you genuinely need a new token or component, ship the work with a clearly marked proposal in your final message:
+
+```
+⚑ NEW TOKEN --{category}-{name}: {value}
+ why nothing existing fits: …
+```
+
+Do the same for drift you notice in existing code (`⚑ DRIFT file:line #3b82f6 → --accent`) rather than silently patching it.
+
+## Self-check before you finish
+
+Run these from the repository root (adjust `src` and the excluded path). Every command should print nothing:
+
+```sh
+# raw colours or px outside the token file
+grep -rnE --include=*.css --include=*.vue --include=*.jsx --include=*.tsx --include=*.svelte \
+ --exclude=tokens.css '#[0-9a-fA-F]{3,8}\b|rgba?\(|\b[0-9]+px\b' src
+
+# forbidden weight and effects
+grep -rnE --include=*.css --include=*.vue --include=*.jsx --include=*.tsx --include=*.svelte \
+ --exclude=tokens.css 'font-weight:\s*(700|800|900|bold)|linear-gradient|radial-gradient|backdrop-filter|text-shadow' src
+
+# copy rules
+grep -rniE --exclude-dir=node_modules 'log ?in|Sign In\b|Log In\b' src
+```
+
+Then check by eye: one `.btn-primary` per screen, focus rings visible when tabbing, and every status or badge has text next to its colour.
+
+## Quick reference
+
+
+```
+--bg #080808 light: #F7F7F9
+--surface #121216 light: #FFFFFF
+--elevated #1A1A20 light: #FFFFFF
+--text-1 #F4F4F6 light: #16161C
+--text-2 #A9A9B4 light: #4C4C58
+--text-3 #83838F light: #6E6E7A
+--accent #8FA8FF light: #3450C8
+--accent-fg #080808 light: #FFFFFF
+--accent-hover #9EB5FF light: #4C6BD2
+--accent-pressed #6173B1 light: #21358A
+--success #3ECF8E light: #177A4E
+--warning #E8B23D light: #8A5A00
+--error #F26D6D light: #C2373C
+--info #62B0F5 light: #1D62A8
+--border #26262E light: #E3E3E9
+--border-strong #3A3A46 light: #C6C6D0
+--font-ui 'IBM Plex Sans', system-ui, sans-serif
+--font-mono 'IBM Plex Mono', ui-monospace, monospace
+type steps display:60/64 8:40/48 7:30/38 6:24/32 5:20/28 4:17/26 3:15/24 2:13/20 1:12/16
+weights 400 · 500 · 600 (never 700)
+space 4px 8px 12px 16px 24px 32px 48px 64px 96px
+radius sm 6px · md 10px · lg 16px · full 999px
+motion 120ms · 200ms · 360ms · ease cubic-bezier(.22,.61,.36,1)
+layout marketing 1200px · app 1440px · control-h 44px (compact 32px)
+```
+
+
+Light theme: `document.documentElement.setAttribute('data-theme', 'light')`. Dark: remove the attribute.
+
+---
+
+## Untrace specifics
+
+Applied to this repository on 2026-09-05, design system v2.0.0.
+
+- `src/tokens.css` is the vendored copy. `src/app.css` imports it and adds only
+ app-level composition (layout, dropzone, stepper, disclosure, tiles, the
+ mobile action bar). No component from `tokens.css` is restyled locally.
+- Density is **comfortable** throughout — this is a consumer surface.
+- Dark is the default. `src/lib/theme.js` reads a saved choice first and falls
+ back to the operating system, and `index.html` applies the result before
+ first paint so the page never flashes the wrong surface.
+- One `.btn-primary` per step of the tool: the dropzone step has none, review
+ has "Clean image", download has "Download clean image".
+- There is no Tailwind and no PostCSS. The design system is plain CSS and the
+ app needs nothing on top of it.
+
+### Open token proposals
+
+```
+NEW TOKEN --border-w: 1px
+ why nothing existing fits: every surface in the system draws a 1px
+ hairline, but the width itself is only ever written literally inside
+ tokens.css. Defined locally in src/app.css until the system adopts it.
+
+NEW TOKEN --topbar-h: 56px
+ why nothing existing fits: no layout token covers a sticky product bar.
+ Derived locally as calc(var(--space-7) + var(--space-2)).
+
+NEW TOKEN breakpoints — 560px / 768px / 900px
+ why nothing existing fits: the system documents container maximums
+ (--max-marketing, --max-app) but no breakpoint scale. Custom properties
+ cannot be used in a media query, so a literal is unavoidable; this app
+ pins itself to exactly these three widths.
+```
+
+`src/lib/clean.js` contains one literal hex, `JPEG_MATTE = "#FFFFFF"`. That is
+image data written to a canvas, not interface styling, so it is intentionally
+not a token.
diff --git a/README.md b/README.md
index 93fbb3f..31ebb4e 100644
--- a/README.md
+++ b/README.md
@@ -1,98 +1,80 @@
-# Untrace - Advanced EXIF Metadata & AI Content Credential Remover
+# Untrace
-**Remove all metadata from images. Keep your privacy.**
+**Remove content credentials from an image.**
-Clean your photos from location data, AI signatures, C2PA content credentials, and other tracking information. Works 100% in your browser - no uploads, no tracking.
+Untrace strips C2PA content credentials, AI generation tags, GPS coordinates
+and EXIF metadata from a photo. It runs entirely in the browser — the file is
+read and rewritten in the tab, never uploaded.
-## Features
+
-**[•] Complete Privacy Protection**
+## What it removes
-- Removes GPS location data
-- Strips camera information
-- Cleans AI generation tags (ChatGPT, DALL-E, Midjourney, Stable Diffusion)
-- Eliminates C2PA content credentials and provenance data
-- Removes JUMBF metadata containers
-- Clears Adobe Firefly and Google Imagen signatures
-- Eliminates timestamps and device info
+| | |
+|---|---|
+| C2PA content credentials | Manifests, claims, assertions and signatures from the Content Authenticity Initiative |
+| AI generation tags | ChatGPT, DALL·E, Midjourney, Stable Diffusion, Adobe Firefly, Google Imagen |
+| JUMBF containers | The APP11 and APP2 boxes that carry provenance inside a JPEG |
+| GPS location | Latitude, longitude, altitude, bearing and their timestamp |
+| Camera and device | Make, model, lens, serial numbers, exposure and shooting settings |
+| Timestamps | When the photo was taken, digitised and last modified |
+| XMP and IPTC | Creator names, captions, keywords, copyright, editing history |
+| Comments and thumbnails | User comments, maker notes, embedded previews |
+| PNG text chunks | tEXt, iTXt and zTXt blocks, including generation prompts |
-**[✓] Enhanced AI Content Credential Removal**
+Supported input: JPEG, PNG, TIFF, HEIC, AVIF. Output is JPEG, or PNG when the
+original was a small PNG.
-- Advanced detection of AI generation markers
-- C2PA (Content Authenticity Initiative) signature removal
-- DALL-E and OpenAI watermark stripping
-- Midjourney metadata cleaning
-- Stable Diffusion tag removal
-- Adobe Content Authenticity removal
-- JUMBF (JPEG Universal Metadata Box Format) cleaning
+## How it works
-**[✓] Secure & Private**
+Detection and removal are two separate passes.
-- No server uploads - everything happens in your browser
-- No tracking or data collection
-- Supports JPEG, PNG, TIFF, HEIC, AVIF
+**Detection** (`src/lib/metadata.js`) reads the file twice: once through
+[exifr](https://github.com/MikeKovarik/exifr) for structured segments, and once
+as raw bytes to find C2PA strings and JUMBF boxes that exifr does not surface.
+The results are merged and bucketed into named sections, then turned into the
+findings list the review step renders.
-**[↕] Easy to Use**
+**Removal** (`src/lib/clean.js`) decodes the image to pixels on a canvas and
+re-encodes it. Nothing carries over, so every metadata container is dropped at
+once rather than deleting known tags one by one and leaving the rest behind.
+The output is then re-read to confirm it is clean; if anything survived, a
+second pass forces JPEG on a white matte.
-- Drag & drop interface
-- Instant preview of what data will be removed
-- One-click cleaning
-- Mobile-friendly design
+If the browser cannot decode or re-encode the file, the original bytes are
+returned and the interface says so — it never reports a clean that did not
+happen.
-## Quick Start
+## Design
-1. **Upload** - Drag your image to the page
-2. **Review** - See what metadata was found
-3. **Clean** - Click "Remove Metadata"
-4. **Download** - Save your privacy-safe image
+The interface follows the
+[RealBrain Design System](https://realbrain.cc/design-system/) v2.0. Tokens are
+vendored at `src/tokens.css`; refresh with:
-## For Developers
+```bash
+curl -o src/tokens.css https://realbrain.cc/design-system/tokens.css
+```
+
+`AGENTS.md` carries the rules, the self-check commands, and this repository's
+open token proposals. Read it before touching any CSS.
+
+## Development
```bash
npm install
-npm run dev
-
-npm run build
+npm run dev # vite dev server
+npm run build # production build to dist/
+npm run preview # serve the build
```
-**Built with:** Svelte + Vite + exifr
+Stack: Svelte 4, Vite 5, exifr. No CSS framework — the design system is plain
+CSS.
-## Why Use Untrace?
+## Who it is for
-Modern images contain hidden data that can compromise your privacy:
-
-- **Location data** reveals where photos were taken
-- **AI signatures** show which tools generated images (DALL-E, Midjourney, ChatGPT, Stable Diffusion)
-- **C2PA content credentials** track the entire creation and editing history
-- **Device info** identifies your camera/phone
-- **Timestamps** reveal when images were created
-- **JUMBF containers** store complex metadata structures
-- **Content Authenticity** markers link images to specific AI tools
-- **Provenance data** creates a digital paper trail
-
-Untrace removes all this data while keeping your images looking exactly the same.
-
-## Supported AI Tools & Formats
-
-**AI Generation Platforms:**
-
-- OpenAI DALL-E & ChatGPT
-- Midjourney
-- Stable Diffusion
-- Adobe Firefly
-- Google Imagen
-- Runway ML
-- NightCafe
-- Artbreeder
-
-**Content Credential Standards:**
-
-- C2PA (Content Provenance and Authenticity)
-- Content Authenticity Initiative (CAI)
-- JUMBF (JPEG Universal Metadata Box Format)
-- Adobe Content Credentials
-- Project Origin signatures
+Journalists protecting sources, activists maintaining anonymity, photographers
+selling stock, and anyone who would rather a photo not carry its own history.
## License
-MIT License - Free for personal and commercial use.
+MIT.
diff --git a/index.html b/index.html
index 7aa146e..052869d 100644
--- a/index.html
+++ b/index.html
@@ -1,132 +1,268 @@
-
+
+
-
-
-
-
-
-
-
-
-
-
- Untrace - Remove EXIF Metadata & AI Credentials from Images Online |
- Privacy Tool
-
+ Remove content credentials from an image – free online remover | Untrace
-
-
-
+
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
-
+
-
-
-
+
+
-
-
+
-
-
-
-
-
+
-
-
-
+
+
+ Untrace removes C2PA content credentials, AI generation tags and EXIF
+ metadata from images. It reads and rewrites the file inside your
+ browser, so JavaScript has to be enabled for it to work.
+
+
+
+
diff --git a/package-lock.json b/package-lock.json
index bcad250..ccb4ede 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -12,30 +12,12 @@
},
"devDependencies": {
"@sveltejs/vite-plugin-svelte": "^3.1.2",
- "@tailwindcss/postcss": "^4.1.10",
- "@tailwindcss/typography": "^0.5.16",
- "@tailwindcss/vite": "^4.1.10",
- "autoprefixer": "^10.4.21",
"gh-pages": "^6.1.0",
- "postcss": "^8.5.6",
+ "lightningcss": "^1.30.1",
"svelte": "^4.2.0",
- "tailwindcss": "^4.1.10",
"vite": "^5.0.0"
}
},
- "node_modules/@alloc/quick-lru": {
- "version": "5.2.0",
- "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz",
- "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
"node_modules/@ampproject/remapping": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz",
@@ -441,19 +423,6 @@
"node": ">=12"
}
},
- "node_modules/@isaacs/fs-minipass": {
- "version": "4.0.1",
- "resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz",
- "integrity": "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "minipass": "^7.0.4"
- },
- "engines": {
- "node": ">=18.0.0"
- }
- },
"node_modules/@jridgewell/gen-mapping": {
"version": "0.3.8",
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz",
@@ -866,313 +835,6 @@
"vite": "^5.0.0"
}
},
- "node_modules/@tailwindcss/node": {
- "version": "4.1.10",
- "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.1.10.tgz",
- "integrity": "sha512-2ACf1znY5fpRBwRhMgj9ZXvb2XZW8qs+oTfotJ2C5xR0/WNL7UHZ7zXl6s+rUqedL1mNi+0O+WQr5awGowS3PQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@ampproject/remapping": "^2.3.0",
- "enhanced-resolve": "^5.18.1",
- "jiti": "^2.4.2",
- "lightningcss": "1.30.1",
- "magic-string": "^0.30.17",
- "source-map-js": "^1.2.1",
- "tailwindcss": "4.1.10"
- }
- },
- "node_modules/@tailwindcss/oxide": {
- "version": "4.1.10",
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.1.10.tgz",
- "integrity": "sha512-v0C43s7Pjw+B9w21htrQwuFObSkio2aV/qPx/mhrRldbqxbWJK6KizM+q7BF1/1CmuLqZqX3CeYF7s7P9fbA8Q==",
- "dev": true,
- "hasInstallScript": true,
- "license": "MIT",
- "dependencies": {
- "detect-libc": "^2.0.4",
- "tar": "^7.4.3"
- },
- "engines": {
- "node": ">= 10"
- },
- "optionalDependencies": {
- "@tailwindcss/oxide-android-arm64": "4.1.10",
- "@tailwindcss/oxide-darwin-arm64": "4.1.10",
- "@tailwindcss/oxide-darwin-x64": "4.1.10",
- "@tailwindcss/oxide-freebsd-x64": "4.1.10",
- "@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.10",
- "@tailwindcss/oxide-linux-arm64-gnu": "4.1.10",
- "@tailwindcss/oxide-linux-arm64-musl": "4.1.10",
- "@tailwindcss/oxide-linux-x64-gnu": "4.1.10",
- "@tailwindcss/oxide-linux-x64-musl": "4.1.10",
- "@tailwindcss/oxide-wasm32-wasi": "4.1.10",
- "@tailwindcss/oxide-win32-arm64-msvc": "4.1.10",
- "@tailwindcss/oxide-win32-x64-msvc": "4.1.10"
- }
- },
- "node_modules/@tailwindcss/oxide-android-arm64": {
- "version": "4.1.10",
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.1.10.tgz",
- "integrity": "sha512-VGLazCoRQ7rtsCzThaI1UyDu/XRYVyH4/EWiaSX6tFglE+xZB5cvtC5Omt0OQ+FfiIVP98su16jDVHDEIuH4iQ==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "android"
- ],
- "engines": {
- "node": ">= 10"
- }
- },
- "node_modules/@tailwindcss/oxide-darwin-arm64": {
- "version": "4.1.10",
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.1.10.tgz",
- "integrity": "sha512-ZIFqvR1irX2yNjWJzKCqTCcHZbgkSkSkZKbRM3BPzhDL/18idA8uWCoopYA2CSDdSGFlDAxYdU2yBHwAwx8euQ==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "darwin"
- ],
- "engines": {
- "node": ">= 10"
- }
- },
- "node_modules/@tailwindcss/oxide-darwin-x64": {
- "version": "4.1.10",
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.1.10.tgz",
- "integrity": "sha512-eCA4zbIhWUFDXoamNztmS0MjXHSEJYlvATzWnRiTqJkcUteSjO94PoRHJy1Xbwp9bptjeIxxBHh+zBWFhttbrQ==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "darwin"
- ],
- "engines": {
- "node": ">= 10"
- }
- },
- "node_modules/@tailwindcss/oxide-freebsd-x64": {
- "version": "4.1.10",
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.1.10.tgz",
- "integrity": "sha512-8/392Xu12R0cc93DpiJvNpJ4wYVSiciUlkiOHOSOQNH3adq9Gi/dtySK7dVQjXIOzlpSHjeCL89RUUI8/GTI6g==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "freebsd"
- ],
- "engines": {
- "node": ">= 10"
- }
- },
- "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": {
- "version": "4.1.10",
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.1.10.tgz",
- "integrity": "sha512-t9rhmLT6EqeuPT+MXhWhlRYIMSfh5LZ6kBrC4FS6/+M1yXwfCtp24UumgCWOAJVyjQwG+lYva6wWZxrfvB+NhQ==",
- "cpu": [
- "arm"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">= 10"
- }
- },
- "node_modules/@tailwindcss/oxide-linux-arm64-gnu": {
- "version": "4.1.10",
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.1.10.tgz",
- "integrity": "sha512-3oWrlNlxLRxXejQ8zImzrVLuZ/9Z2SeKoLhtCu0hpo38hTO2iL86eFOu4sVR8cZc6n3z7eRXXqtHJECa6mFOvA==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">= 10"
- }
- },
- "node_modules/@tailwindcss/oxide-linux-arm64-musl": {
- "version": "4.1.10",
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.1.10.tgz",
- "integrity": "sha512-saScU0cmWvg/Ez4gUmQWr9pvY9Kssxt+Xenfx1LG7LmqjcrvBnw4r9VjkFcqmbBb7GCBwYNcZi9X3/oMda9sqQ==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">= 10"
- }
- },
- "node_modules/@tailwindcss/oxide-linux-x64-gnu": {
- "version": "4.1.10",
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.1.10.tgz",
- "integrity": "sha512-/G3ao/ybV9YEEgAXeEg28dyH6gs1QG8tvdN9c2MNZdUXYBaIY/Gx0N6RlJzfLy/7Nkdok4kaxKPHKJUlAaoTdA==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">= 10"
- }
- },
- "node_modules/@tailwindcss/oxide-linux-x64-musl": {
- "version": "4.1.10",
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.1.10.tgz",
- "integrity": "sha512-LNr7X8fTiKGRtQGOerSayc2pWJp/9ptRYAa4G+U+cjw9kJZvkopav1AQc5HHD+U364f71tZv6XamaHKgrIoVzA==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">= 10"
- }
- },
- "node_modules/@tailwindcss/oxide-wasm32-wasi": {
- "version": "4.1.10",
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.1.10.tgz",
- "integrity": "sha512-d6ekQpopFQJAcIK2i7ZzWOYGZ+A6NzzvQ3ozBvWFdeyqfOZdYHU66g5yr+/HC4ipP1ZgWsqa80+ISNILk+ae/Q==",
- "bundleDependencies": [
- "@napi-rs/wasm-runtime",
- "@emnapi/core",
- "@emnapi/runtime",
- "@tybys/wasm-util",
- "@emnapi/wasi-threads",
- "tslib"
- ],
- "cpu": [
- "wasm32"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "dependencies": {
- "@emnapi/core": "^1.4.3",
- "@emnapi/runtime": "^1.4.3",
- "@emnapi/wasi-threads": "^1.0.2",
- "@napi-rs/wasm-runtime": "^0.2.10",
- "@tybys/wasm-util": "^0.9.0",
- "tslib": "^2.8.0"
- },
- "engines": {
- "node": ">=14.0.0"
- }
- },
- "node_modules/@tailwindcss/oxide-win32-arm64-msvc": {
- "version": "4.1.10",
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.1.10.tgz",
- "integrity": "sha512-i1Iwg9gRbwNVOCYmnigWCCgow8nDWSFmeTUU5nbNx3rqbe4p0kRbEqLwLJbYZKmSSp23g4N6rCDmm7OuPBXhDA==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "win32"
- ],
- "engines": {
- "node": ">= 10"
- }
- },
- "node_modules/@tailwindcss/oxide-win32-x64-msvc": {
- "version": "4.1.10",
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.1.10.tgz",
- "integrity": "sha512-sGiJTjcBSfGq2DVRtaSljq5ZgZS2SDHSIfhOylkBvHVjwOsodBhnb3HdmiKkVuUGKD0I7G63abMOVaskj1KpOA==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "win32"
- ],
- "engines": {
- "node": ">= 10"
- }
- },
- "node_modules/@tailwindcss/postcss": {
- "version": "4.1.10",
- "resolved": "https://registry.npmjs.org/@tailwindcss/postcss/-/postcss-4.1.10.tgz",
- "integrity": "sha512-B+7r7ABZbkXJwpvt2VMnS6ujcDoR2OOcFaqrLIo1xbcdxje4Vf+VgJdBzNNbrAjBj/rLZ66/tlQ1knIGNLKOBQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@alloc/quick-lru": "^5.2.0",
- "@tailwindcss/node": "4.1.10",
- "@tailwindcss/oxide": "4.1.10",
- "postcss": "^8.4.41",
- "tailwindcss": "4.1.10"
- }
- },
- "node_modules/@tailwindcss/typography": {
- "version": "0.5.16",
- "resolved": "https://registry.npmjs.org/@tailwindcss/typography/-/typography-0.5.16.tgz",
- "integrity": "sha512-0wDLwCVF5V3x3b1SGXPCDcdsbDHMBe+lkFzBRaHeLvNi+nrrnZ1lA18u+OTWO8iSWU2GxUOCvlXtDuqftc1oiA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "lodash.castarray": "^4.4.0",
- "lodash.isplainobject": "^4.0.6",
- "lodash.merge": "^4.6.2",
- "postcss-selector-parser": "6.0.10"
- },
- "peerDependencies": {
- "tailwindcss": ">=3.0.0 || insiders || >=4.0.0-alpha.20 || >=4.0.0-beta.1"
- }
- },
- "node_modules/@tailwindcss/vite": {
- "version": "4.1.10",
- "resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.1.10.tgz",
- "integrity": "sha512-QWnD5HDY2IADv+vYR82lOhqOlS1jSCUUAmfem52cXAhRTKxpDh3ARX8TTXJTCCO7Rv7cD2Nlekabv02bwP3a2A==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@tailwindcss/node": "4.1.10",
- "@tailwindcss/oxide": "4.1.10",
- "tailwindcss": "4.1.10"
- },
- "peerDependencies": {
- "vite": "^5.2.0 || ^6"
- }
- },
"node_modules/@types/estree": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
@@ -1220,44 +882,6 @@
"dev": true,
"license": "MIT"
},
- "node_modules/autoprefixer": {
- "version": "10.4.21",
- "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.21.tgz",
- "integrity": "sha512-O+A6LWV5LDHSJD3LjHYoNi4VLsj/Whi7k6zG12xTYaU4cQ8oxQGckXNX8cRHK5yOZ/ppVHe0ZBXGzSV9jXdVbQ==",
- "dev": true,
- "funding": [
- {
- "type": "opencollective",
- "url": "https://opencollective.com/postcss/"
- },
- {
- "type": "tidelift",
- "url": "https://tidelift.com/funding/github/npm/autoprefixer"
- },
- {
- "type": "github",
- "url": "https://github.com/sponsors/ai"
- }
- ],
- "license": "MIT",
- "dependencies": {
- "browserslist": "^4.24.4",
- "caniuse-lite": "^1.0.30001702",
- "fraction.js": "^4.3.7",
- "normalize-range": "^0.1.2",
- "picocolors": "^1.1.1",
- "postcss-value-parser": "^4.2.0"
- },
- "bin": {
- "autoprefixer": "bin/autoprefixer"
- },
- "engines": {
- "node": "^10 || ^12 || >=14"
- },
- "peerDependencies": {
- "postcss": "^8.1.0"
- }
- },
"node_modules/axobject-query": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz",
@@ -1281,70 +905,6 @@
"node": ">=8"
}
},
- "node_modules/browserslist": {
- "version": "4.25.0",
- "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.25.0.tgz",
- "integrity": "sha512-PJ8gYKeS5e/whHBh8xrwYK+dAvEj7JXtz6uTucnMRB8OiGTsKccFekoRrjajPBHV8oOY+2tI4uxeceSimKwMFA==",
- "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": {
- "caniuse-lite": "^1.0.30001718",
- "electron-to-chromium": "^1.5.160",
- "node-releases": "^2.0.19",
- "update-browserslist-db": "^1.1.3"
- },
- "bin": {
- "browserslist": "cli.js"
- },
- "engines": {
- "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
- }
- },
- "node_modules/caniuse-lite": {
- "version": "1.0.30001724",
- "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001724.tgz",
- "integrity": "sha512-WqJo7p0TbHDOythNTqYujmaJTvtYRZrjpP8TCvH6Vb9CYJerJNKamKzIWOM4BkQatWj9H2lYulpdAQNBe7QhNA==",
- "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/chownr": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz",
- "integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==",
- "dev": true,
- "license": "BlueOak-1.0.0",
- "engines": {
- "node": ">=18"
- }
- },
"node_modules/code-red": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/code-red/-/code-red-1.0.4.tgz",
@@ -1390,19 +950,6 @@
"node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0"
}
},
- "node_modules/cssesc": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz",
- "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==",
- "dev": true,
- "license": "MIT",
- "bin": {
- "cssesc": "bin/cssesc"
- },
- "engines": {
- "node": ">=4"
- }
- },
"node_modules/debug": {
"version": "4.4.1",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz",
@@ -1454,13 +1001,6 @@
"node": ">=8"
}
},
- "node_modules/electron-to-chromium": {
- "version": "1.5.171",
- "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.171.tgz",
- "integrity": "sha512-scWpzXEJEMrGJa4Y6m/tVotb0WuvNmasv3wWVzUAeCgKU0ToFOhUW6Z+xWnRQANMYGxN4ngJXIThgBJOqzVPCQ==",
- "dev": true,
- "license": "ISC"
- },
"node_modules/email-addresses": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/email-addresses/-/email-addresses-5.0.0.tgz",
@@ -1468,20 +1008,6 @@
"dev": true,
"license": "MIT"
},
- "node_modules/enhanced-resolve": {
- "version": "5.18.1",
- "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.1.tgz",
- "integrity": "sha512-ZSW3ma5GkcQBIpwZTSRAI8N71Uuwgs93IezB7mf7R60tC8ZbJideoDNKjHn2O9KIlx6rkGTTEk1xUCK2E1Y2Yg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "graceful-fs": "^4.2.4",
- "tapable": "^2.2.0"
- },
- "engines": {
- "node": ">=10.13.0"
- }
- },
"node_modules/esbuild": {
"version": "0.21.5",
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz",
@@ -1521,16 +1047,6 @@
"@esbuild/win32-x64": "0.21.5"
}
},
- "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/escape-string-regexp": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
@@ -1657,20 +1173,6 @@
"node": ">=8"
}
},
- "node_modules/fraction.js": {
- "version": "4.3.7",
- "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz",
- "integrity": "sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": "*"
- },
- "funding": {
- "type": "patreon",
- "url": "https://github.com/sponsors/rawify"
- }
- },
"node_modules/fs-extra": {
"version": "11.3.0",
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.0.tgz",
@@ -1818,16 +1320,6 @@
"@types/estree": "^1.0.6"
}
},
- "node_modules/jiti": {
- "version": "2.4.2",
- "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.4.2.tgz",
- "integrity": "sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A==",
- "dev": true,
- "license": "MIT",
- "bin": {
- "jiti": "lib/jiti-cli.mjs"
- }
- },
"node_modules/jsonfile": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz",
@@ -2110,27 +1602,6 @@
"node": ">=8"
}
},
- "node_modules/lodash.castarray": {
- "version": "4.4.0",
- "resolved": "https://registry.npmjs.org/lodash.castarray/-/lodash.castarray-4.4.0.tgz",
- "integrity": "sha512-aVx8ztPv7/2ULbArGJ2Y42bG1mEQ5mGjpdvrbJcJFU3TbYybe+QlLS4pst9zV52ymy2in1KpFPiZnAOATxD4+Q==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/lodash.isplainobject": {
- "version": "4.0.6",
- "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz",
- "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/lodash.merge": {
- "version": "4.6.2",
- "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
- "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==",
- "dev": true,
- "license": "MIT"
- },
"node_modules/magic-string": {
"version": "0.30.17",
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz",
@@ -2188,45 +1659,6 @@
"node": ">=8.6"
}
},
- "node_modules/minipass": {
- "version": "7.1.2",
- "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz",
- "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==",
- "dev": true,
- "license": "ISC",
- "engines": {
- "node": ">=16 || 14 >=14.17"
- }
- },
- "node_modules/minizlib": {
- "version": "3.0.2",
- "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.0.2.tgz",
- "integrity": "sha512-oG62iEk+CYt5Xj2YqI5Xi9xWUeZhDI8jjQmC5oThVH5JGCTgIjr7ciJDzC7MBzYd//WvR1OTmP5Q38Q8ShQtVA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "minipass": "^7.1.2"
- },
- "engines": {
- "node": ">= 18"
- }
- },
- "node_modules/mkdirp": {
- "version": "3.0.1",
- "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-3.0.1.tgz",
- "integrity": "sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==",
- "dev": true,
- "license": "MIT",
- "bin": {
- "mkdirp": "dist/cjs/src/bin.js"
- },
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/sponsors/isaacs"
- }
- },
"node_modules/ms": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
@@ -2253,23 +1685,6 @@
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
}
},
- "node_modules/node-releases": {
- "version": "2.0.19",
- "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz",
- "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/normalize-range": {
- "version": "0.1.2",
- "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz",
- "integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=0.10.0"
- }
- },
"node_modules/p-limit": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
@@ -2403,27 +1818,6 @@
"node": "^10 || ^12 || >=14"
}
},
- "node_modules/postcss-selector-parser": {
- "version": "6.0.10",
- "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.10.tgz",
- "integrity": "sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "cssesc": "^3.0.0",
- "util-deprecate": "^1.0.2"
- },
- "engines": {
- "node": ">=4"
- }
- },
- "node_modules/postcss-value-parser": {
- "version": "4.2.0",
- "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz",
- "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==",
- "dev": true,
- "license": "MIT"
- },
"node_modules/queue-microtask": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
@@ -2602,41 +1996,6 @@
"svelte": "^3.19.0 || ^4.0.0"
}
},
- "node_modules/tailwindcss": {
- "version": "4.1.10",
- "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.10.tgz",
- "integrity": "sha512-P3nr6WkvKV/ONsTzj6Gb57sWPMX29EPNPopo7+FcpkQaNsrNpZ1pv8QmrYI2RqEKD7mlGqLnGovlcYnBK0IqUA==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/tapable": {
- "version": "2.2.2",
- "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.2.tgz",
- "integrity": "sha512-Re10+NauLTMCudc7T5WLFLAwDhQ0JWdrMK+9B2M8zR5hRExKmsRDCBA7/aV/pNJFltmBFO5BAMlQFi/vq3nKOg==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=6"
- }
- },
- "node_modules/tar": {
- "version": "7.4.3",
- "resolved": "https://registry.npmjs.org/tar/-/tar-7.4.3.tgz",
- "integrity": "sha512-5S7Va8hKfV7W5U6g3aYxXmlPoZVAwUMy9AOKyF2fVuZa2UD3qZjg578OrLRt8PcNN1PleVaL/5/yYATNL0ICUw==",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "@isaacs/fs-minipass": "^4.0.0",
- "chownr": "^3.0.0",
- "minipass": "^7.1.2",
- "minizlib": "^3.0.1",
- "mkdirp": "^3.0.1",
- "yallist": "^5.0.0"
- },
- "engines": {
- "node": ">=18"
- }
- },
"node_modules/to-regex-range": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
@@ -2673,44 +2032,6 @@
"node": ">= 10.0.0"
}
},
- "node_modules/update-browserslist-db": {
- "version": "1.1.3",
- "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz",
- "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==",
- "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/util-deprecate": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
- "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
- "dev": true,
- "license": "MIT"
- },
"node_modules/vite": {
"version": "5.4.19",
"resolved": "https://registry.npmjs.org/vite/-/vite-5.4.19.tgz",
@@ -2785,16 +2106,6 @@
"optional": true
}
}
- },
- "node_modules/yallist": {
- "version": "5.0.0",
- "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz",
- "integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==",
- "dev": true,
- "license": "BlueOak-1.0.0",
- "engines": {
- "node": ">=18"
- }
}
}
}
diff --git a/package.json b/package.json
index a74a943..65f5e53 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "untrace",
- "description": "Clean your images. Keep your identity.",
+ "description": "Remove C2PA content credentials, AI generation tags and EXIF metadata from images, entirely in the browser.",
"version": "1.0.0",
"type": "module",
"scripts": {
@@ -11,18 +11,13 @@
},
"devDependencies": {
"@sveltejs/vite-plugin-svelte": "^3.1.2",
- "@tailwindcss/postcss": "^4.1.10",
- "@tailwindcss/typography": "^0.5.16",
- "@tailwindcss/vite": "^4.1.10",
- "autoprefixer": "^10.4.21",
"gh-pages": "^6.1.0",
- "postcss": "^8.5.6",
"svelte": "^4.2.0",
- "tailwindcss": "^4.1.10",
- "vite": "^5.0.0"
+ "vite": "^5.0.0",
+ "lightningcss": "^1.30.1"
},
"dependencies": {
"exifr": "^7.1.3"
},
- "homepage": "https://developersharif.github.io/untrace"
+ "homepage": "https://untrace.realbrain.cc/"
}
diff --git a/postcss.config.js b/postcss.config.js
deleted file mode 100644
index 1c87846..0000000
--- a/postcss.config.js
+++ /dev/null
@@ -1,6 +0,0 @@
-export default {
- plugins: {
- '@tailwindcss/postcss': {},
- autoprefixer: {},
- },
-}
diff --git a/public/favicon-16x16.svg b/public/favicon-16x16.svg
index b23c985..2b45932 100644
--- a/public/favicon-16x16.svg
+++ b/public/favicon-16x16.svg
@@ -1,23 +1,5 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+ U
diff --git a/public/favicon-32x32.svg b/public/favicon-32x32.svg
index 2aacbef..2b45932 100644
--- a/public/favicon-32x32.svg
+++ b/public/favicon-32x32.svg
@@ -1,27 +1,5 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+ U
diff --git a/public/favicon-maskable.svg b/public/favicon-maskable.svg
new file mode 100644
index 0000000..f7f6def
--- /dev/null
+++ b/public/favicon-maskable.svg
@@ -0,0 +1,5 @@
+
+
+ U
+
diff --git a/public/favicon.svg b/public/favicon.svg
index 2ff2704..2b45932 100644
--- a/public/favicon.svg
+++ b/public/favicon.svg
@@ -1,32 +1,5 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+ U
diff --git a/public/manifest.json b/public/manifest.json
index 56626e8..bb59c0d 100644
--- a/public/manifest.json
+++ b/public/manifest.json
@@ -1,19 +1,29 @@
{
- "name": "Untrace",
+ "id": "/",
+ "name": "Untrace — content credentials and metadata remover",
"short_name": "Untrace",
- "description": "EXIF Metadata & AI Content Credential Remover",
+ "description": "Remove C2PA content credentials, AI generation tags and EXIF metadata from images, entirely in your browser.",
"start_url": "/",
+ "scope": "/",
"display": "standalone",
- "background_color": "#ffffff",
- "theme_color": "#3b82f6",
+ "background_color": "#080808",
+ "theme_color": "#080808",
"icons": [
{
- "src": "vite.svg",
+ "src": "/favicon.svg",
"sizes": "any",
- "type": "image/svg+xml"
+ "type": "image/svg+xml",
+ "purpose": "any"
+ },
+ {
+ "src": "/favicon-maskable.svg",
+ "sizes": "any",
+ "type": "image/svg+xml",
+ "purpose": "maskable"
}
],
"categories": ["productivity", "utilities", "photo"],
"lang": "en",
+ "dir": "ltr",
"orientation": "any"
}
diff --git a/public/sitemap.xml b/public/sitemap.xml
index 08a1a86..f711bfc 100644
--- a/public/sitemap.xml
+++ b/public/sitemap.xml
@@ -1,8 +1,8 @@
- https://untrace.realbrain.cc/untrace/
- 2025-06-23
+ https://untrace.realbrain.cc/
+ 2026-09-05
weekly
1.0
diff --git a/public/thumb.jpg b/public/thumb.jpg
index 9b5e26e..b10c8d1 100644
Binary files a/public/thumb.jpg and b/public/thumb.jpg differ
diff --git a/public/vite.svg b/public/vite.svg
deleted file mode 100644
index a41de18..0000000
--- a/public/vite.svg
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
diff --git a/src/App.svelte b/src/App.svelte
index 68e1cf3..f46a467 100644
--- a/src/App.svelte
+++ b/src/App.svelte
@@ -1,335 +1,126 @@
-
+
-
-
-
-
-
-
-
Upload Your Image
+
+
+
+ C2PA · AI credentials · EXIF
+ Remove content credentials from an image
+
+ Untrace strips C2PA content credentials, AI generation tags, GPS
+ coordinates and EXIF metadata from a photo — free, and without the file
+ ever leaving your device.
+
+
+ Free
+ No sign-in
+ Open source
-
-
-
- {#if selectedFile}
-
-
-
-
Original
- {#if metadata}
-
- {showMetadata ? "Hide" : "Show"} Metadata
-
- {/if}
-
+
-
-
-
+
+ {#each assurances as item}
+
+ {item.title}
+ {item.detail}
+
+ {/each}
+
-
-
- {selectedFile.name} • {(selectedFile.size / 1024).toFixed(1)} KB
-
- {#if metadata && metadata.message && !metadata.hasMetadata}
-
✓ No metadata detected
- {:else if metadata}
-
- ⚠️ {Object.keys(metadata).filter(
- (key) => !["fileType", "hasMetadata", "message"].includes(key)
- ).length} metadata entries found
-
- {/if}
-
-
- {#if showMetadata && metadata}
-
-
-
- {/if}
-
-
-
-
Clean Metadata
-
-
- {#if isProcessing}
-
- {/if}
-
- {#if processedImage}
-
-
✅ Clean Image
-
-
-
-
-
-
- {(processedImage.size / 1024).toFixed(1)} KB •
- {processedImage.compressionRatio
- ? `${processedImage.compressionRatio > 0 ? "-" : "+"}${Math.abs(processedImage.compressionRatio)}% size`
- : "Processed"}
-
- {#if processedImage.wasSecondPass}
-
- ⚠️ Used enhanced cleaning (second pass)
-
- {/if}
-
✓ All EXIF metadata removed
-
✓ GPS location data cleared
-
✓ AI content credentials stripped
-
✓ C2PA provenance data removed
-
✓ Camera & device info cleared
-
-
-
-
-
-
- Download Cleaned Image
-
-
- {/if}
-
-
- {/if}
-
-
-
-
🔐 Privacy & Security
-
- {showPrivacyInfo ? "Hide" : "Show"} Details
-
-
-
-
-
- ✓ 100% client-side
- processing
- •
- ✓ No data collection
- •
- ✓ Open source
-
-
-
- {#if showPrivacyInfo}
-
- {/if}
-
+
+
+
+
+
+
+
diff --git a/src/app.css b/src/app.css
index d473327..7727081 100644
--- a/src/app.css
+++ b/src/app.css
@@ -1,470 +1,640 @@
-@import "tailwindcss";
+/* ==========================================================================
+ Untrace — application stylesheet
+ ==========================================================================
+ Design system: RealBrain v2.0. Tokens come from ./tokens.css (vendored,
+ never edited by hand). Everything below composes those tokens only.
+ Refresh the vendored copy with:
+ curl -o src/tokens.css https://realbrain.cc/design-system/tokens.css
+ ========================================================================== */
+
+@import "./tokens.css";
+
+/* --------------------------------------------------------------------------
+ Values the design system has no token for yet. Flagged for the system
+ owners rather than sprinkled around as raw values:
+
+ NEW TOKEN --border-w: 1px — hairline width, used by every surface
+ NEW TOKEN --topbar-h: 56px — sticky product bar height
+ (derived: --space-7 + --space-2)
+ NEW TOKEN breakpoints — 560px / 768px / 900px
+
+ Breakpoints cannot be custom properties: a media query is evaluated before
+ the cascade, so var() is not available there. Until the system ships
+ @custom-media or a documented scale, every file in this app uses exactly
+ these three widths and no others.
+ -------------------------------------------------------------------------- */
:root {
- --primary-color: #2563eb;
- --primary-hover: #1d4ed8;
- --secondary-color: #64748b;
- --success-color: #10b981;
- --danger-color: #ef4444;
- --warning-color: #f59e0b;
-
- --bg-primary: #ffffff;
- --bg-secondary: #f8fafc;
- --bg-tertiary: #f1f5f9;
- --text-primary: #0f172a;
- --text-secondary: #475569;
- --text-muted: #94a3b8;
- --border-color: #e2e8f0;
- --shadow: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1);
- --shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.1),
- 0 4px 6px -4px rgb(0 0 0 / 0.1);
-
- /* Dark mode colors */
- --dark-bg-primary: #0f172a;
- --dark-bg-secondary: #1e293b;
- --dark-bg-tertiary: #334155;
- --dark-text-primary: #f8fafc;
- --dark-text-secondary: #cbd5e1;
- --dark-text-muted: #64748b;
- --dark-border-color: #334155;
+ --border-w: 1px;
+ --topbar-h: calc(var(--space-7) + var(--space-2));
}
-[data-theme="dark"] {
- --bg-primary: var(--dark-bg-primary);
- --bg-secondary: var(--dark-bg-secondary);
- --bg-tertiary: var(--dark-bg-tertiary);
- --text-primary: var(--dark-text-primary);
- --text-secondary: var(--dark-text-secondary);
- --text-muted: var(--dark-text-muted);
- --border-color: var(--dark-border-color);
+/* An
carrying .btn is a button, not prose: the base underline-on-hover
+ from tokens.css does not belong on it. */
+a.btn:hover {
+ text-decoration: none;
}
-* {
- margin: 0;
- padding: 0;
- box-sizing: border-box;
+/* --------------------------------------------------------------------------
+ Layout
+ -------------------------------------------------------------------------- */
+.wrap {
+ width: 100%;
+ max-width: var(--max-marketing);
+ margin-inline: auto;
+ padding-inline: var(--space-4);
}
-html {
- scroll-behavior: smooth;
+@media (min-width: 768px) {
+ .wrap {
+ padding-inline: var(--space-6);
+ }
}
-body {
- font-family: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
- sans-serif;
- line-height: 1.6;
- color: var(--text-primary);
- background-color: var(--bg-primary);
- transition: background-color 0.3s ease, color 0.3s ease;
+.stack {
+ display: grid;
+ gap: var(--space-7);
}
-.container {
- max-width: 1200px;
- margin: 0 auto;
- padding: 0 1rem;
+@media (min-width: 768px) {
+ .stack {
+ gap: var(--space-9);
+ }
}
-.btn {
+/* --------------------------------------------------------------------------
+ Product bar
+ -------------------------------------------------------------------------- */
+.topbar {
+ position: sticky;
+ top: 0;
+ z-index: 20;
+ height: var(--topbar-h);
+ display: flex;
+ align-items: center;
+ background: var(--bg);
+ border-bottom: var(--border-w) solid var(--border);
+}
+
+.topbar .inner {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: var(--space-3);
+}
+
+.wordmark {
display: inline-flex;
align-items: center;
- justify-content: center;
- gap: 0.5rem;
- padding: 0.75rem 1.5rem;
- border: none;
- border-radius: 0.5rem;
+ gap: var(--space-3);
+ color: var(--text-1);
+ font-size: var(--fs-3);
+ line-height: var(--lh-3);
font-weight: 600;
- font-size: 0.875rem;
- cursor: pointer;
- transition: all 0.2s ease;
+ letter-spacing: -0.01em;
+}
+
+.wordmark:hover {
text-decoration: none;
- outline: none;
- position: relative;
- overflow: hidden;
}
-.btn:focus-visible {
- outline: 2px solid var(--primary-color);
- outline-offset: 2px;
+/* --------------------------------------------------------------------------
+ Section headings
+ -------------------------------------------------------------------------- */
+.eyebrow {
+ font-family: var(--font-mono);
+ font-size: var(--fs-1);
+ line-height: var(--lh-1);
+ color: var(--accent);
+ letter-spacing: 0.1em;
+ text-transform: uppercase;
}
-.btn-primary {
- background-color: var(--primary-color);
- color: white;
-}
-
-.btn-primary:hover:not(:disabled) {
- background-color: var(--primary-hover);
- transform: translateY(-1px);
- box-shadow: var(--shadow-lg);
-}
-
-.btn-secondary {
- background-color: var(--bg-tertiary);
- color: var(--text-secondary);
- border: 1px solid var(--border-color);
-}
-
-.btn-secondary:hover:not(:disabled) {
- background-color: var(--bg-secondary);
- transform: translateY(-1px);
-}
-
-.btn-success {
- background-color: var(--success-color);
- color: white;
-}
-
-.btn-success:hover:not(:disabled) {
- background-color: #059669;
- transform: translateY(-1px);
-}
-
-.btn:disabled {
- opacity: 0.6;
- cursor: not-allowed;
- transform: none;
-}
-
-.btn-lg {
- padding: 1rem 2rem;
- font-size: 1rem;
-}
-
-.card {
- background-color: var(--bg-primary);
- border: 1px solid var(--border-color);
- border-radius: 0.75rem;
- box-shadow: var(--shadow);
- padding: 1.5rem;
- transition: all 0.2s ease;
-}
-
-.card:hover {
- box-shadow: var(--shadow-lg);
-}
-
-.upload-area {
- border: 2px dashed var(--border-color);
- border-radius: 0.75rem;
- padding: 3rem 2rem;
- text-align: center;
- transition: all 0.3s ease;
- cursor: pointer;
- background-color: var(--bg-secondary);
-}
-
-.upload-area:hover {
- border-color: var(--primary-color);
- background-color: var(--bg-tertiary);
-}
-
-.upload-area.dragover {
- border-color: var(--primary-color);
- background-color: rgba(37, 99, 235, 0.05);
- transform: scale(1.02);
-}
-
-.upload-icon {
- width: 3rem;
- height: 3rem;
- margin: 0 auto 1rem;
- color: var(--text-muted);
-}
-
-.metadata-viewer {
- background-color: var(--bg-secondary);
- border: 1px solid var(--border-color);
- border-radius: 0.5rem;
- padding: 1rem;
- font-family: "Monaco", "Menlo", monospace;
- font-size: 0.875rem;
- max-height: 300px;
- overflow-y: auto;
- white-space: pre-wrap;
-}
-
-.progress-bar {
- width: 100%;
- height: 0.5rem;
- background-color: var(--bg-tertiary);
- border-radius: 0.25rem;
- overflow: hidden;
-}
-
-.progress-fill {
- height: 100%;
- background-color: var(--primary-color);
- transition: width 0.3s ease;
-}
-
-.image-preview {
- max-width: 100%;
- max-height: 300px;
- border-radius: 0.5rem;
- box-shadow: var(--shadow);
-}
-
-.status-message {
- padding: 0.75rem 1rem;
- border-radius: 0.5rem;
- font-weight: 500;
- margin: 1rem 0;
-}
-
-.status-success {
- background-color: rgba(16, 185, 129, 0.1);
- color: var(--success-color);
- border: 1px solid rgba(16, 185, 129, 0.2);
-}
-
-.status-error {
- background-color: rgba(239, 68, 68, 0.1);
- color: var(--danger-color);
- border: 1px solid rgba(239, 68, 68, 0.2);
-}
-
-.status-warning {
- background-color: rgba(245, 158, 11, 0.1);
- color: var(--warning-color);
- border: 1px solid rgba(245, 158, 11, 0.2);
-}
-
-.grid {
+.section-head {
display: grid;
- gap: 1.5rem;
+ gap: var(--space-2);
+ margin-bottom: var(--space-5);
+ max-width: calc(var(--space-9) * 6);
}
-.grid-2 {
- grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
-}
-
-.flex {
- display: flex;
-}
-
-.flex-col {
- flex-direction: column;
-}
-
-.items-center {
- align-items: center;
-}
-
-.justify-center {
- justify-content: center;
-}
-
-.justify-between {
- justify-content: space-between;
-}
-
-.gap-4 {
- gap: 1rem;
-}
-
-.gap-6 {
- gap: 1.5rem;
-}
-
-.text-center {
- text-align: center;
-}
-
-.text-sm {
- font-size: 0.875rem;
-}
-
-.text-lg {
- font-size: 1.125rem;
-}
-
-.text-xl {
- font-size: 1.25rem;
-}
-
-.text-2xl {
- font-size: 1.5rem;
-}
-
-.text-3xl {
- font-size: 1.875rem;
-}
-
-.font-bold {
- font-weight: 700;
-}
-
-.font-semibold {
+.section-head h2 {
+ font-size: var(--fs-6);
+ line-height: var(--lh-6);
font-weight: 600;
+ letter-spacing: -0.01em;
}
-.text-muted {
- color: var(--text-muted);
+.section-head p {
+ color: var(--text-2);
+ font-size: var(--fs-3);
+ line-height: var(--lh-3);
}
-.text-secondary {
- color: var(--text-secondary);
+@media (min-width: 768px) {
+ .section-head h2 {
+ font-size: var(--fs-7);
+ line-height: var(--lh-7);
+ }
}
-.mb-2 {
- margin-bottom: 0.5rem;
+/* --------------------------------------------------------------------------
+ Hero
+ -------------------------------------------------------------------------- */
+.lede {
+ display: grid;
+ gap: var(--space-4);
+ justify-items: start;
+ padding-block: var(--space-7) var(--space-6);
}
-.mb-4 {
- margin-bottom: 1rem;
+.lede h1 {
+ font-size: var(--fs-7);
+ line-height: var(--lh-7);
+ font-weight: 600;
+ letter-spacing: -0.02em;
+ max-width: calc(var(--space-9) * 8);
}
-.mb-6 {
- margin-bottom: 1.5rem;
+.lede p {
+ color: var(--text-2);
+ font-size: var(--fs-4);
+ line-height: var(--lh-4);
+ max-width: calc(var(--space-9) * 6);
}
-.mt-4 {
- margin-top: 1rem;
+@media (min-width: 768px) {
+ .lede {
+ padding-block: var(--space-8) var(--space-7);
+ }
+
+ .lede h1 {
+ font-size: var(--fs-8);
+ line-height: var(--lh-8);
+ }
}
-.mt-6 {
- margin-top: 1.5rem;
+/* --------------------------------------------------------------------------
+ Chips — neutral mono labels for formats and counts
+ -------------------------------------------------------------------------- */
+.chip {
+ display: inline-flex;
+ align-items: center;
+ gap: var(--space-2);
+ padding: var(--space-1) var(--space-3);
+ border: var(--border-w) solid var(--border);
+ border-radius: var(--radius-full);
+ background: var(--surface);
+ color: var(--text-2);
+ font-family: var(--font-mono);
+ font-size: var(--fs-1);
+ line-height: var(--lh-1);
+ white-space: nowrap;
}
-.py-2 {
- padding-top: 0.5rem;
- padding-bottom: 0.5rem;
+.chip-row {
+ display: flex;
+ flex-wrap: wrap;
+ gap: var(--space-2);
}
-.py-4 {
- padding-top: 1rem;
- padding-bottom: 1rem;
+/* --------------------------------------------------------------------------
+ Stepper
+ -------------------------------------------------------------------------- */
+.stepper {
+ display: grid;
+ grid-template-columns: repeat(3, 1fr);
+ gap: var(--space-2);
+ padding-bottom: var(--space-4);
+ border-bottom: var(--border-w) solid var(--border);
+ margin-bottom: var(--space-5);
}
-.py-8 {
- padding-top: 2rem;
- padding-bottom: 2rem;
+.step {
+ display: flex;
+ align-items: center;
+ gap: var(--space-2);
+ min-width: 0;
+ color: var(--text-3);
+ font-size: var(--fs-1);
+ line-height: var(--lh-1);
+ font-weight: 500;
}
-.px-4 {
- padding-left: 1rem;
- padding-right: 1rem;
+.step .num {
+ display: inline-grid;
+ place-content: center;
+ flex: none;
+ width: var(--space-5);
+ height: var(--space-5);
+ border-radius: var(--radius-full);
+ border: var(--border-w) solid var(--border-strong);
+ font-family: var(--font-mono);
+ font-size: var(--fs-1);
+ line-height: 1;
+ transition:
+ background var(--dur-2) var(--ease),
+ border-color var(--dur-2) var(--ease),
+ color var(--dur-2) var(--ease);
}
-.hidden {
+.step .label {
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+
+.step[data-state="current"] {
+ color: var(--text-1);
+}
+
+.step[data-state="current"] .num {
+ background: var(--accent);
+ border-color: var(--accent);
+ color: var(--accent-fg);
+}
+
+.step[data-state="done"] {
+ color: var(--text-2);
+}
+
+.step[data-state="done"] .num {
+ background: var(--surface);
+ border-color: var(--success);
+ color: var(--success);
+}
+
+/* --------------------------------------------------------------------------
+ Dropzone
+ -------------------------------------------------------------------------- */
+.dropzone {
+ display: grid;
+ justify-items: center;
+ align-content: center;
+ gap: var(--space-3);
+ width: 100%;
+ min-height: calc(var(--space-9) * 2);
+ padding: var(--space-7) var(--space-5);
+ text-align: center;
+ background: var(--bg);
+ border: var(--border-w) dashed var(--border-strong);
+ border-radius: var(--radius-md);
+ color: inherit;
+ font-family: var(--font-ui);
+ cursor: pointer;
+ transition:
+ border-color var(--dur-2) var(--ease),
+ background var(--dur-2) var(--ease);
+}
+
+.dropzone:hover:not(:disabled) {
+ border-color: var(--text-3);
+ background: var(--surface);
+}
+
+.dropzone[data-drag="true"] {
+ border-color: var(--accent);
+ border-style: solid;
+ background: color-mix(in oklab, var(--accent), transparent 94%);
+}
+
+.dropzone:disabled {
+ opacity: 0.45;
+ cursor: not-allowed;
+}
+
+.dropzone .glyph {
+ display: grid;
+ place-content: center;
+ width: calc(var(--space-6) + var(--space-2));
+ height: calc(var(--space-6) + var(--space-2));
+ border-radius: var(--radius-md);
+ border: var(--border-w) solid var(--border-strong);
+ color: var(--text-3);
+ transition: color var(--dur-2) var(--ease);
+}
+
+.dropzone[data-drag="true"] .glyph {
+ color: var(--accent);
+ border-color: var(--accent);
+}
+
+.dropzone .title {
+ font-size: var(--fs-4);
+ line-height: var(--lh-4);
+ font-weight: 600;
+ color: var(--text-1);
+}
+
+.dropzone .hint {
+ font-size: var(--fs-2);
+ line-height: var(--lh-2);
+ color: var(--text-3);
+ max-width: calc(var(--space-9) * 4);
+}
+
+/* --------------------------------------------------------------------------
+ Image preview
+ -------------------------------------------------------------------------- */
+.preview {
+ display: grid;
+ place-content: center;
+ padding: var(--space-4);
+ border: var(--border-w) solid var(--border);
+ border-radius: var(--radius-md);
+ background: var(--bg);
+}
+
+.preview img {
+ display: block;
+ max-width: 100%;
+ max-height: 34vh;
+ border-radius: var(--radius-sm);
+}
+
+@media (min-width: 768px) {
+ .preview img {
+ max-height: 40vh;
+ }
+}
+
+/* --------------------------------------------------------------------------
+ Key/value + finding rows
+ -------------------------------------------------------------------------- */
+.rows {
+ display: grid;
+ border: var(--border-w) solid var(--border);
+ border-radius: var(--radius-md);
+ overflow: hidden;
+ background: var(--surface);
+}
+
+.rows > * + * {
+ border-top: var(--border-w) solid var(--border);
+}
+
+.row {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: var(--space-3);
+ padding: var(--space-3) var(--space-4);
+ font-size: var(--fs-2);
+ line-height: var(--lh-2);
+}
+
+.row .k {
+ color: var(--text-2);
+ min-width: 0;
+}
+
+.row .v {
+ font-family: var(--font-mono);
+ color: var(--text-1);
+ text-align: right;
+ white-space: nowrap;
+}
+
+/* --------------------------------------------------------------------------
+ Raw metadata dump
+ -------------------------------------------------------------------------- */
+.raw {
+ margin: 0;
+ padding: var(--space-4);
+ max-height: calc(var(--space-9) * 3);
+ overflow: auto;
+ background: var(--bg);
+ border: var(--border-w) solid var(--border);
+ border-radius: var(--radius-md);
+ font-family: var(--font-mono);
+ font-size: var(--fs-1);
+ line-height: var(--lh-2);
+ color: var(--text-2);
+ white-space: pre-wrap;
+ overflow-wrap: anywhere;
+ tab-size: 2;
+}
+
+/* --------------------------------------------------------------------------
+ Disclosure (FAQ + expandable panels)
+ -------------------------------------------------------------------------- */
+.disclosure {
+ border: var(--border-w) solid var(--border);
+ border-radius: var(--radius-md);
+ background: var(--surface);
+}
+
+.disclosure + .disclosure {
+ margin-top: var(--space-2);
+}
+
+.disclosure > summary {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: var(--space-3);
+ padding: var(--space-4);
+ min-height: var(--control-h);
+ cursor: pointer;
+ list-style: none;
+ font-size: var(--fs-3);
+ line-height: var(--lh-3);
+ font-weight: 500;
+ color: var(--text-1);
+ transition: color var(--dur-1) var(--ease);
+}
+
+.disclosure > summary::-webkit-details-marker {
display: none;
}
-.fade-in {
- animation: fadeIn 0.3s ease-in;
+.disclosure > summary:hover {
+ color: var(--accent);
}
-@keyframes fadeIn {
- from {
- opacity: 0;
- transform: translateY(10px);
- }
- to {
- opacity: 1;
- transform: translateY(0);
+.disclosure > summary .caret {
+ flex: none;
+ color: var(--text-3);
+ transition: transform var(--dur-2) var(--ease);
+}
+
+.disclosure[open] > summary .caret {
+ transform: rotate(180deg);
+}
+
+.disclosure .body {
+ padding: 0 var(--space-4) var(--space-4);
+ color: var(--text-2);
+ font-size: var(--fs-2);
+ line-height: var(--lh-3);
+}
+
+.disclosure .body a {
+ text-decoration: underline;
+}
+
+/* --------------------------------------------------------------------------
+ Numbered how-to steps
+ -------------------------------------------------------------------------- */
+.howto {
+ display: grid;
+ gap: var(--space-4);
+ counter-reset: howto;
+ list-style: none;
+}
+
+@media (min-width: 768px) {
+ .howto {
+ grid-template-columns: repeat(3, 1fr);
+ gap: var(--space-5);
}
}
-.slide-up {
- animation: slideUp 0.4s ease-out;
+.howto > li {
+ counter-increment: howto;
+ display: grid;
+ gap: var(--space-2);
+ padding: var(--space-5);
+ background: var(--surface);
+ border: var(--border-w) solid var(--border);
+ border-radius: var(--radius-md);
}
-@keyframes slideUp {
- from {
- opacity: 0;
- transform: translateY(20px);
- }
- to {
- opacity: 1;
- transform: translateY(0);
+.howto > li::before {
+ content: counter(howto);
+ display: grid;
+ place-content: center;
+ width: var(--space-6);
+ height: var(--space-6);
+ border-radius: var(--radius-sm);
+ background: var(--elevated);
+ border: var(--border-w) solid var(--border-strong);
+ color: var(--accent);
+ font-family: var(--font-mono);
+ font-size: var(--fs-2);
+ line-height: 1;
+ margin-bottom: var(--space-1);
+}
+
+.howto h3 {
+ font-size: var(--fs-3);
+ line-height: var(--lh-3);
+ font-weight: 600;
+}
+
+.howto p {
+ color: var(--text-2);
+ font-size: var(--fs-2);
+ line-height: var(--lh-2);
+}
+
+/* --------------------------------------------------------------------------
+ Feature / removal grid
+ -------------------------------------------------------------------------- */
+.tiles {
+ display: grid;
+ gap: var(--space-3);
+ grid-template-columns: 1fr;
+}
+
+@media (min-width: 560px) {
+ .tiles {
+ grid-template-columns: repeat(2, 1fr);
}
}
-/* Responsive Design */
-@media (max-width: 768px) {
- .container {
- padding: 0 0.5rem;
- }
-
- .card {
- padding: 1rem;
- }
-
- .upload-area {
- padding: 2rem 1rem;
- }
-
- .btn {
- padding: 0.625rem 1.25rem;
- font-size: 0.8rem;
- }
-
- .btn-lg {
- padding: 0.875rem 1.75rem;
- font-size: 0.9rem;
- }
-
- .text-3xl {
- font-size: 1.5rem;
- }
-
- .text-2xl {
- font-size: 1.25rem;
+@media (min-width: 900px) {
+ .tiles {
+ grid-template-columns: repeat(3, 1fr);
}
}
-/* Theme Toggle */
-.theme-toggle {
- position: fixed;
- top: 1rem;
- right: 1rem;
- z-index: 1000;
- background: var(--bg-primary);
- border: 1px solid var(--border-color);
- border-radius: 50%;
- width: 3rem;
- height: 3rem;
- display: flex;
- align-items: center;
- justify-content: center;
- cursor: pointer;
- box-shadow: var(--shadow);
- transition: all 0.2s ease;
+.tile {
+ display: grid;
+ gap: var(--space-1);
+ padding: var(--space-4);
+ background: var(--surface);
+ border: var(--border-w) solid var(--border);
+ border-radius: var(--radius-md);
}
-.theme-toggle:hover {
- transform: scale(1.1);
- box-shadow: var(--shadow-lg);
+.tile h3 {
+ font-size: var(--fs-2);
+ line-height: var(--lh-2);
+ font-weight: 600;
+ color: var(--text-1);
}
-/* Accessibility */
-@media (prefers-reduced-motion: reduce) {
- * {
- animation-duration: 0.01ms !important;
- animation-iteration-count: 1 !important;
- transition-duration: 0.01ms !important;
+.tile p {
+ font-size: var(--fs-1);
+ line-height: var(--lh-2);
+ color: var(--text-3);
+}
+
+/* --------------------------------------------------------------------------
+ Mobile action bar — keeps the one primary action reachable with a thumb
+ -------------------------------------------------------------------------- */
+.action-bar {
+ position: sticky;
+ bottom: 0;
+ z-index: 10;
+ display: grid;
+ gap: var(--space-2);
+ padding: var(--space-3) var(--space-4)
+ calc(var(--space-3) + env(safe-area-inset-bottom));
+ margin-inline: calc(var(--space-4) * -1);
+ background: var(--surface);
+ border-top: var(--border-w) solid var(--border);
+}
+
+@media (min-width: 768px) {
+ .action-bar {
+ position: static;
+ display: flex;
+ gap: var(--space-3);
+ padding: 0;
+ margin-inline: 0;
+ background: transparent;
+ border-top: 0;
}
}
-/* Focus styles for better accessibility */
-input[type="file"]:focus-visible + label {
- outline: 2px solid var(--primary-color);
- outline-offset: 2px;
+.btn-block {
+ width: 100%;
}
-/* Custom scrollbar */
-::-webkit-scrollbar {
- width: 6px;
+@media (min-width: 768px) {
+ .btn-block {
+ width: auto;
+ }
}
-::-webkit-scrollbar-track {
- background: var(--bg-tertiary);
+/* --------------------------------------------------------------------------
+ Footer
+ -------------------------------------------------------------------------- */
+.site-footer {
+ padding-block: var(--space-7);
+ border-top: var(--border-w) solid var(--border);
+ color: var(--text-3);
+ font-size: var(--fs-2);
+ line-height: var(--lh-2);
}
-::-webkit-scrollbar-thumb {
- background: var(--text-muted);
- border-radius: 3px;
+/* --------------------------------------------------------------------------
+ Utilities that earn their keep
+ -------------------------------------------------------------------------- */
+.visually-hidden {
+ position: absolute;
+ width: var(--border-w);
+ height: var(--border-w);
+ margin: calc(var(--border-w) * -1);
+ padding: 0;
+ overflow: hidden;
+ clip-path: inset(50%);
+ white-space: nowrap;
+ border: 0;
}
-::-webkit-scrollbar-thumb:hover {
- background: var(--text-secondary);
+.divider {
+ height: var(--border-w);
+ background: var(--border);
+ border: 0;
}
diff --git a/src/lib/Dropzone.svelte b/src/lib/Dropzone.svelte
new file mode 100644
index 0000000..38690da
--- /dev/null
+++ b/src/lib/Dropzone.svelte
@@ -0,0 +1,130 @@
+
+
+
+
+ fileInput.click()}
+ on:dragenter={onDragEnter}
+ on:dragover|preventDefault
+ on:dragleave={onDragLeave}
+ on:drop={onDrop}
+>
+
+
+ {dragging ? "Drop to scan it" : "Drop an image, or choose a file"}
+
+
+ Nothing is uploaded. The image is read and rewritten inside this tab.
+
+
+ JPEG
+ PNG
+ TIFF
+ HEIC
+ AVIF
+
+
+
+
+ You can also paste an image with Ctrl +V .
+
+
+
+
+
diff --git a/src/lib/Faq.svelte b/src/lib/Faq.svelte
new file mode 100644
index 0000000..e6ffc23
--- /dev/null
+++ b/src/lib/Faq.svelte
@@ -0,0 +1,33 @@
+
+
+
+
+
Questions
+
Content credentials, answered
+
+
+
+ {#each faqs as item, index}
+
+
+ {item.q}
+
+
+
+
+ {/each}
+
+
+
+
diff --git a/src/lib/Footer.svelte b/src/lib/Footer.svelte
index 32f17a0..90ddd73 100644
--- a/src/lib/Footer.svelte
+++ b/src/lib/Footer.svelte
@@ -1,48 +1,142 @@
-