diff --git a/.gitignore b/.gitignore index a547bf3..d78d1aa 100644 --- a/.gitignore +++ b/.gitignore @@ -12,6 +12,13 @@ dist dist-ssr *.local +# Tauri build artifacts +src-tauri/target/ + +# Dev tool files +icon-*.html +nul + # Editor directories and files .vscode/* !.vscode/extensions.json diff --git a/README.md b/README.md index c1f7803..4a1dda5 100644 --- a/README.md +++ b/README.md @@ -7,78 +7,119 @@ License

-A beautiful, distraction-free markdown reader for Windows. Vesper renders your markdown files with elegant typography and a refined dark aesthetic inspired by iA Writer, giving you the ultimate reading experience. +A beautiful, distraction-free markdown reader for Windows. Vesper renders your markdown files with premium editorial typography and a refined dark aesthetic, giving you the ultimate reading experience. --- ## Features -### File Management -- **Open Files** - Use the File menu or drag-and-drop markdown files directly onto the window -- **Tabbed Interface** - Open multiple files in tabs and switch between them seamlessly -- **Recent Files** - Files open in new tabs automatically -- **Supported Formats** - `.md`, `.markdown`, and `.txt` files +### Markdown Rendering -### Reading Experience -- **Premium Typography** - Carefully tuned typography with Inter font family for optimal readability -- **Syntax Highlighting** - Code blocks are beautifully highlighted using highlight.js with a matching dark theme -- **Markdown Extensions** - Full support for: +- Full CommonMark support via **markdown-it** with extensions: + - Syntax highlighting for 190+ languages (highlight.js) - Task lists (checkboxes) - - Superscript and subscript - - Strikethrough and highlighting - - Tables with elegant styling - - Blockquotes with visual depth + - Superscript and subscript (`^text^`, `~text~`) + - ==Highlighted text== via `mark` + - Smart quotes, em-dashes, and ellipses (typographer) + - Line break preservation + - Auto-linking of URLs + - HTML passthrough +- Premium dark-mode typography tuned for long-form reading: + - Inter Variable at 17px with 1.7 line-height + - ~65 character measure (680px max-width) for optimal readability + - Progressive heading color cascade (H1 near-white through H6 muted) + - Accent-colored list markers + - Gradient-fade horizontal rules + - Subtle persistent link underlines that intensify on hover + - Refined blockquote, table, and code block styling + - JetBrains Mono Variable for code at 14px with ligature support + +### Tabbed Interface + +- Open multiple documents in tabs +- Duplicate detection: re-opening an already-open file switches to its existing tab instead of creating a duplicate +- Animated tab enter/exit with layout transitions +- Scroll arrows appear when tabs overflow the bar, with hold-to-scroll +- Mouse wheel scrolling over the tab bar +- New tabs automatically scroll into view +- Close individual tabs with the X button or `Ctrl+W` ### Navigation -- **Table of Contents Sidebar** - Automatically generated from markdown headings (H1-H6) -- **Click-to-Navigate** - Click any heading in the sidebar to jump to that section -- **Resizable Sidebar** - Drag the sidebar edge to resize it to your preference -### Search -- **In-Document Search** - Press `Ctrl+F` to search within the current document -- **Match Navigation** - Navigate between matches with previous/next buttons -- **Match Counter** - See how many matches were found +- **Table of Contents sidebar** auto-generated from document headings (H1-H6), with indentation by level +- Click any heading in the sidebar to smooth-scroll to that section +- Sidebar opens even when a document has no headings (shows "No headings" message) +- Resizable sidebar via drag handle +- **Full-text search** (`Ctrl+F`) with real-time DOM highlighting, match counter, and Enter to cycle through matches -### Window Controls -- **Custom Frameless Window** - Sleek title bar with custom controls -- **Window Controls** - Minimize, maximize/restore, and close buttons -- **Draggable Title Area** - Drag the window by clicking the title bar -- **Focus Mode** - Press `F11` to hide all UI chrome and focus purely on content +### Reading Experience -### Keyboard Shortcuts +- **Focus Mode** (`F11`) hides the title bar and menu bar for immersive reading +- **Content zoom** via `Ctrl+Scroll` (50%--200%) +- **Content width** adjustment via `Shift+Scroll` (400px--1200px) +- **UI scale** (50%--200%) with a spinner in the View menu, persisted across sessions via localStorage +- **Kinetic scrolling** on right-mouse-button drag with iOS-style rubber band overscroll and damped spring snapback +- **Custom scrollbars** via OverlayScrollbars -- thin, auto-hiding after 800ms, accent-colored on hover +- Window state (size, position, maximized) remembered between sessions via `tauri-plugin-window-state` + +### File Handling + +- Open files via the File menu, `Ctrl+O` dialog, or drag-and-drop from Explorer +- Supports `.md`, `.markdown`, and `.txt` files +- Native Tauri drag-and-drop integration (receives real file system paths) + +### UI Details + +- Custom frameless title bar with minimize, maximize/restore, and close buttons +- Draggable title area for window repositioning +- Menu bar with File, View, and Help menus +- Right-click context menu with: Copy (when text is selected), Open File, Toggle Search, Toggle Sidebar, Focus Mode, Keyboard Shortcuts, About, Exit +- Text selection preserved visually on right-click via overlay rendering +- Context menu stays within window bounds and respects UI zoom +- All panels, modals, tabs, and menus animate with Framer Motion (slide, fade, scale) +- Subtle noise texture overlay on the app background +- Keyboard shortcuts modal listing all bindings + +--- + +## Keyboard Shortcuts | Shortcut | Action | -|----------|--------| +|---|---| | `Ctrl+O` | Open file | | `Ctrl+W` | Close current tab | | `Ctrl+Q` | Exit application | | `Ctrl+F` | Toggle search | | `Ctrl+Shift+S` | Toggle sidebar | | `F11` | Toggle focus mode | -| `Escape` | Close search or sidebar | - -### Context Menu -Right-click anywhere in the content area for quick access to: -- Open File -- Find (search) -- Toggle Sidebar -- Focus Mode -- Keyboard Shortcuts -- About +| `Escape` | Close search / sidebar | +| `Ctrl+Scroll` | Zoom content in / out | +| `Shift+Scroll` | Adjust content width | --- ## Installation ### Prerequisites + - Windows 10 or later -- WebView2 Runtime (usually pre-installed on Windows 10/11) +- WebView2 Runtime (pre-installed on Windows 10/11) ### Download -Download the latest release from the [Releases](https://github.com/yourusername/vesper/releases) page. + +Download the latest installer from the [Releases](https://github.com/yourusername/vesper/releases) page: + +- **NSIS installer** (`.exe`) -- recommended, includes uninstaller +- **MSI installer** (`.msi`) -- alternative for enterprise/group policy deployment +- **Portable exe** -- standalone `vesper.exe` with no installation required ### Build from Source +**Prerequisites:** +- [Node.js](https://nodejs.org/) v18+ +- [Rust](https://www.rust-lang.org/tools/install) (stable toolchain) +- [Tauri v2 prerequisites](https://v2.tauri.app/start/prerequisites/) + ```bash # Clone the repository git clone https://github.com/yourusername/vesper.git @@ -87,98 +128,95 @@ cd vesper # Install dependencies npm install -# Run in development mode +# Run in development mode (hot-reload on localhost:1420) npm run tauri dev # Build for production npm run tauri build ``` +Build outputs: +- `src-tauri/target/release/vesper.exe` -- portable executable +- `src-tauri/target/release/bundle/msi/Vesper_1.0.0_x64_en-US.msi` -- MSI installer +- `src-tauri/target/release/bundle/nsis/Vesper_1.0.0_x64-setup.exe` -- NSIS installer + --- ## Technology Stack -- **Framework**: [Tauri 2.0](https://tauri.app/) - Lightweight, secure desktop framework -- **Frontend**: React 19 + TypeScript + Vite -- **Styling**: Tailwind CSS v4 + daisyUI v5 -- **Markdown**: [markdown-it](https://github.com/markdown-it/markdown-it) with plugins -- **Syntax Highlighting**: [highlight.js](https://highlightjs.org/) -- **Animations**: [Framer Motion](https://www.framer.com/motion/) -- **Icons**: [Lucide React](https://lucide.dev/) +| Layer | Technology | +|---|---| +| Runtime | [Tauri v2](https://tauri.app/) (Rust + WebView2) | +| Frontend | React 19, TypeScript, Vite 7 | +| Styling | Tailwind CSS 4, DaisyUI 5, custom CSS | +| Markdown | [markdown-it](https://github.com/markdown-it/markdown-it) with plugins (task-lists, sup, sub, mark) | +| Syntax Highlighting | [highlight.js](https://highlightjs.org/) | +| Animation | [Framer Motion](https://www.framer.com/motion/) | +| Fonts | [Inter Variable](https://rsms.me/inter/), [JetBrains Mono Variable](https://www.jetbrains.com/lp/mono/) | +| Icons | [Lucide React](https://lucide.dev/) | +| Scrollbars | [OverlayScrollbars](https://kingsora.github.io/OverlayScrollbars/) | + +### Tauri Plugins + +- `tauri-plugin-dialog` -- native file open dialog +- `tauri-plugin-fs` -- file system read access +- `tauri-plugin-window-state` -- persist and restore window size, position, and maximized state +- `tauri-plugin-opener` -- system default app launcher --- ## Design Philosophy -Vesper was designed with these principles in mind: - -1. **Content First** - The reader is the primary focus. All UI elements can be hidden. -2. **Typography Matters** - Reading should be comfortable. We use Inter with carefully tuned line heights (1.7), optimal line width (~65 characters), and generous whitespace. -3. **Dark by Default** - The deep onyx base color (#282C33) reduces eye strain and looks professional. -4. **Keyboard Driven** - Power users can do everything with keyboard shortcuts. -5. **Minimalist Chrome** - The interface gets out of your way. +1. **Content First** -- the reader is the primary focus; all UI chrome can be hidden +2. **Typography Matters** -- Inter Variable at 17px, 1.7 line-height, ~65 character measure, generous whitespace +3. **Dark by Default** -- deep onyx base color (#282C33) reduces eye strain +4. **Keyboard Driven** -- every feature accessible via keyboard shortcuts +5. **Minimalist Chrome** -- the interface gets out of your way --- ## Color Palette | Color | Hex | Usage | -|-------|-----|-------| +|---|---|---| | Base | `#282C33` | Main window background | -| Surface | `#2D323B` | Elevated elements, cards | -| Overlay | `#333942` | Dropdowns, modals | +| Surface | `#252A31` | Title bar, sidebar | +| Elevated | `#2D333C` | Active tab, modals | +| Overlay | `#333942` | Dropdowns, context menu | | Text Primary | `#E4E6EB` | Main content | -| Text Secondary | `#ABB0B8` | UI labels | -| Accent | `#6B8AFF` | Links, highlights | +| Text Secondary | `#ABB0B8` | UI labels, muted text | +| Text Tertiary | `#6B7280` | Placeholders, hints | +| Accent | `#6B8AFF` | Interactive elements, highlights | +| Link | `#7CA9F7` | Hyperlinks | --- ## Project Structure ``` -vesper/ -├── src/ -│ ├── App.tsx # Main application component -│ ├── main.tsx # Entry point -│ └── styles.css # All styling -├── src-tauri/ -│ ├── src/ -│ │ └── main.rs # Rust backend -│ ├── Cargo.toml -│ └── tauri.conf.json # Tauri configuration -├── index.html -├── package.json -└── README.md +md-reader/ + src/ + App.tsx # Main application component (all UI logic) + main.tsx # React entry point + styles.css # All styles -- variables, UI components, markdown typography + src-tauri/ + src/ + main.rs # Rust entry point + lib.rs # Tauri builder and plugin registration + tauri.conf.json # Tauri window, bundle, and build configuration + Cargo.toml # Rust dependencies + icons/ # App icons (ICO, PNG, ICNS) + index.html + package.json + vite.config.ts + tsconfig.json ``` --- -## Keyboard Accessibility - -Vesper is fully keyboard accessible. Every feature can be accessed without a mouse: - -- **Menu Navigation** - Use the menu bar with keyboard -- **Tab Switching** - Close tabs with `Ctrl+W` -- **Search** - Find text with `Ctrl+F` -- **Focus Mode** - Immerse yourself with `F11` - ---- - -## Contributing - -Contributions are welcome! Please feel free to submit a Pull Request. - -1. Fork the repository -2. Create your feature branch (`git checkout -b feature/amazing-feature`) -3. Commit your changes (`git commit -m 'Add some amazing feature'`) -4. Push to the branch (`git push origin feature/amazing-feature`) -5. Open a Pull Request - ---- - ## License -This project is dedicated to the public domain under the CC0 1.0 Universal (CC0 1.0) Public Domain Dedication - see the [LICENSE](LICENSE) file for details. +This project is dedicated to the public domain under the CC0 1.0 Universal (CC0 1.0) Public Domain Dedication -- see the [LICENSE](LICENSE) file for details. --- @@ -187,4 +225,4 @@ This project is dedicated to the public domain under the CC0 1.0 Universal (CC0 - [iA Writer](https://ia.net/writer) for design inspiration - [Tauri](https://tauri.app/) for the excellent desktop framework - [markdown-it](https://github.com/markdown-it/markdown-it) for robust markdown parsing -- [highlight.js](https://highlightjs.org/) for beautiful code syntax +- [highlight.js](https://highlightjs.org/) for syntax highlighting diff --git a/package-lock.json b/package-lock.json index fc99de5..541761e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,13 +1,15 @@ { - "name": "DarkMark", - "version": "0.1.0", + "name": "vesper", + "version": "1.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "DarkMark", - "version": "0.1.0", + "name": "vesper", + "version": "1.0.0", "dependencies": { + "@fontsource-variable/inter": "^5.2.8", + "@fontsource-variable/jetbrains-mono": "^5.2.8", "@tailwindcss/typography": "^0.5.19", "@tailwindcss/vite": "^4.1.18", "@tauri-apps/api": "^2", @@ -18,13 +20,14 @@ "daisyui": "^5.5.18", "framer-motion": "^12.34.0", "highlight.js": "^11.11.1", - "lenis": "^1.3.17", "lucide-react": "^0.564.0", "markdown-it": "^14.1.1", "markdown-it-mark": "^4.0.0", "markdown-it-sub": "^2.0.0", "markdown-it-sup": "^2.0.0", "markdown-it-task-lists": "^2.1.1", + "overlayscrollbars": "^2.14.0", + "overlayscrollbars-react": "^0.5.6", "react": "^19.1.0", "react-dom": "^19.1.0", "tailwindcss": "^4.1.18" @@ -35,6 +38,9 @@ "@types/react": "^19.1.8", "@types/react-dom": "^19.1.6", "@vitejs/plugin-react": "^4.6.0", + "png-to-ico": "^3.0.1", + "puppeteer-core": "^24.37.3", + "sharp": "^0.34.5", "typescript": "~5.8.3", "vite": "^7.0.4" } @@ -321,6 +327,16 @@ "node": ">=6.9.0" } }, + "node_modules/@emnapi/runtime": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.8.1.tgz", + "integrity": "sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg==", + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, "node_modules/@esbuild/aix-ppc64": { "version": "0.27.3", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.3.tgz", @@ -737,6 +753,514 @@ "node": ">=18" } }, + "node_modules/@fontsource-variable/inter": { + "version": "5.2.8", + "resolved": "https://registry.npmjs.org/@fontsource-variable/inter/-/inter-5.2.8.tgz", + "integrity": "sha512-kOfP2D+ykbcX/P3IFnokOhVRNoTozo5/JxhAIVYLpea/UBmCQ/YWPBfWIDuBImXX/15KH+eKh4xpEUyS2sQQGQ==", + "license": "OFL-1.1", + "funding": { + "url": "https://github.com/sponsors/ayuhito" + } + }, + "node_modules/@fontsource-variable/jetbrains-mono": { + "version": "5.2.8", + "resolved": "https://registry.npmjs.org/@fontsource-variable/jetbrains-mono/-/jetbrains-mono-5.2.8.tgz", + "integrity": "sha512-WBA9elru6Jdp5df2mES55wuOO0WIrn3kpXnI4+W2ek5u3ZgLS9XS4gmIlcQhiZOWEKl95meYdvK7xI+ETLCq/Q==", + "license": "OFL-1.1", + "funding": { + "url": "https://github.com/sponsors/ayuhito" + } + }, + "node_modules/@img/colour": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@img/colour/-/colour-1.0.0.tgz", + "integrity": "sha512-A5P/LfWGFSl6nsckYtjw9da+19jB8hkJ6ACTGcDfEJ0aE+l2n2El7dsVM7UVHZQ9s2lmYMWlrS21YLy2IR1LUw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@img/sharp-darwin-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.34.5.tgz", + "integrity": "sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-arm64": "1.2.4" + } + }, + "node_modules/@img/sharp-darwin-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.34.5.tgz", + "integrity": "sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-x64": "1.2.4" + } + }, + "node_modules/@img/sharp-libvips-darwin-arm64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.2.4.tgz", + "integrity": "sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-darwin-x64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.2.4.tgz", + "integrity": "sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.2.4.tgz", + "integrity": "sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.2.4.tgz", + "integrity": "sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-ppc64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.2.4.tgz", + "integrity": "sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-riscv64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-riscv64/-/sharp-libvips-linux-riscv64-1.2.4.tgz", + "integrity": "sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-s390x": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.2.4.tgz", + "integrity": "sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-x64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.2.4.tgz", + "integrity": "sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-arm64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.2.4.tgz", + "integrity": "sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-x64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.2.4.tgz", + "integrity": "sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-linux-arm": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.34.5.tgz", + "integrity": "sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.34.5.tgz", + "integrity": "sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm64": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-ppc64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.34.5.tgz", + "integrity": "sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-ppc64": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-riscv64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-riscv64/-/sharp-linux-riscv64-0.34.5.tgz", + "integrity": "sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-riscv64": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-s390x": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.34.5.tgz", + "integrity": "sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-s390x": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.34.5.tgz", + "integrity": "sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-x64": "1.2.4" + } + }, + "node_modules/@img/sharp-linuxmusl-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.34.5.tgz", + "integrity": "sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-arm64": "1.2.4" + } + }, + "node_modules/@img/sharp-linuxmusl-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.34.5.tgz", + "integrity": "sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-x64": "1.2.4" + } + }, + "node_modules/@img/sharp-wasm32": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.34.5.tgz", + "integrity": "sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==", + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", + "optional": true, + "dependencies": { + "@emnapi/runtime": "^1.7.0" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.34.5.tgz", + "integrity": "sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-ia32": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.34.5.tgz", + "integrity": "sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.34.5.tgz", + "integrity": "sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, "node_modules/@jridgewell/gen-mapping": { "version": "0.3.13", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", @@ -782,6 +1306,41 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@puppeteer/browsers": { + "version": "2.12.1", + "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-2.12.1.tgz", + "integrity": "sha512-fXa6uXLxfslBlus3MEpW8S6S9fe5RwmAE5Gd8u3krqOwnkZJV3/lQJiY3LaFdTctLLqJtyMgEUGkbDnRNf6vbQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "debug": "^4.4.3", + "extract-zip": "^2.0.1", + "progress": "^2.0.3", + "proxy-agent": "^6.5.0", + "semver": "^7.7.4", + "tar-fs": "^3.1.1", + "yargs": "^17.7.2" + }, + "bin": { + "browsers": "lib/cjs/main-cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@puppeteer/browsers/node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/@rolldown/pluginutils": { "version": "1.0.0-beta.27", "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz", @@ -1646,6 +2205,13 @@ "@tauri-apps/api": "^2.8.0" } }, + "node_modules/@tootallnate/quickjs-emscripten": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@tootallnate/quickjs-emscripten/-/quickjs-emscripten-0.23.0.tgz", + "integrity": "sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/babel__core": { "version": "7.20.5", "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", @@ -1722,6 +2288,16 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/node": { + "version": "22.19.11", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.11.tgz", + "integrity": "sha512-BH7YwL6rA93ReqeQS1c4bsPpcfOmJasG+Fkr6Y59q83f9M1WcBRHR2vM+P9eOisYRcN3ujQoiZY8uk5W+1WL8w==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, "node_modules/@types/react": { "version": "19.2.14", "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.14.tgz", @@ -1742,6 +2318,17 @@ "@types/react": "^19.2.0" } }, + "node_modules/@types/yauzl": { + "version": "2.10.3", + "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.3.tgz", + "integrity": "sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@vitejs/plugin-react": { "version": "4.7.0", "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.7.0.tgz", @@ -1763,12 +2350,173 @@ "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" } }, + "node_modules/agent-base": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "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==", + "dev": true, + "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==", + "dev": true, + "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": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", "license": "Python-2.0" }, + "node_modules/ast-types": { + "version": "0.13.4", + "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.13.4.tgz", + "integrity": "sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w==", + "dev": true, + "license": "MIT", + "dependencies": { + "tslib": "^2.0.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/b4a": { + "version": "1.7.4", + "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.7.4.tgz", + "integrity": "sha512-u20zJLDaSWpxaZ+zaAkEIB2dZZ1o+DF4T/MRbmsvGp9nletHOyiai19OzX1fF8xUBYsO1bPXxODvcd0978pnug==", + "dev": true, + "license": "Apache-2.0", + "peerDependencies": { + "react-native-b4a": "*" + }, + "peerDependenciesMeta": { + "react-native-b4a": { + "optional": true + } + } + }, + "node_modules/bare-events": { + "version": "2.8.2", + "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.8.2.tgz", + "integrity": "sha512-riJjyv1/mHLIPX4RwiK+oW9/4c3TEUeORHKefKAKnZ5kyslbN+HXowtbaVEqt4IMUB7OXlfixcs6gsFeo/jhiQ==", + "dev": true, + "license": "Apache-2.0", + "peerDependencies": { + "bare-abort-controller": "*" + }, + "peerDependenciesMeta": { + "bare-abort-controller": { + "optional": true + } + } + }, + "node_modules/bare-fs": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/bare-fs/-/bare-fs-4.5.4.tgz", + "integrity": "sha512-POK4oplfA7P7gqvetNmCs4CNtm9fNsx+IAh7jH7GgU0OJdge2rso0R20TNWVq6VoWcCvsTdlNDaleLHGaKx8CA==", + "dev": true, + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "bare-events": "^2.5.4", + "bare-path": "^3.0.0", + "bare-stream": "^2.6.4", + "bare-url": "^2.2.2", + "fast-fifo": "^1.3.2" + }, + "engines": { + "bare": ">=1.16.0" + }, + "peerDependencies": { + "bare-buffer": "*" + }, + "peerDependenciesMeta": { + "bare-buffer": { + "optional": true + } + } + }, + "node_modules/bare-os": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/bare-os/-/bare-os-3.6.2.tgz", + "integrity": "sha512-T+V1+1srU2qYNBmJCXZkUY5vQ0B4FSlL3QDROnKQYOqeiQR8UbjNHlPa+TIbM4cuidiN9GaTaOZgSEgsvPbh5A==", + "dev": true, + "license": "Apache-2.0", + "optional": true, + "engines": { + "bare": ">=1.14.0" + } + }, + "node_modules/bare-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/bare-path/-/bare-path-3.0.0.tgz", + "integrity": "sha512-tyfW2cQcB5NN8Saijrhqn0Zh7AnFNsnczRcuWODH0eYAXBsJ5gVxAUuNr7tsHSC6IZ77cA0SitzT+s47kot8Mw==", + "dev": true, + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "bare-os": "^3.0.1" + } + }, + "node_modules/bare-stream": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/bare-stream/-/bare-stream-2.7.0.tgz", + "integrity": "sha512-oyXQNicV1y8nc2aKffH+BUHFRXmx6VrPzlnaEvMhram0nPBrKcEdcyBg5r08D0i8VxngHFAiVyn1QKXpSG0B8A==", + "dev": true, + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "streamx": "^2.21.0" + }, + "peerDependencies": { + "bare-buffer": "*", + "bare-events": "*" + }, + "peerDependenciesMeta": { + "bare-buffer": { + "optional": true + }, + "bare-events": { + "optional": true + } + } + }, + "node_modules/bare-url": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/bare-url/-/bare-url-2.3.2.tgz", + "integrity": "sha512-ZMq4gd9ngV5aTMa5p9+UfY0b3skwhHELaDkhEHetMdX0LRkW9kzaym4oo/Eh+Ghm0CCDuMTsRIGM/ytUc1ZYmw==", + "dev": true, + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "bare-path": "^3.0.0" + } + }, "node_modules/baseline-browser-mapping": { "version": "2.9.19", "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.19.tgz", @@ -1779,6 +2527,16 @@ "baseline-browser-mapping": "dist/cli.js" } }, + "node_modules/basic-ftp": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/basic-ftp/-/basic-ftp-5.1.0.tgz", + "integrity": "sha512-RkaJzeJKDbaDWTIPiJwubyljaEPwpVWkm9Rt5h9Nd6h7tEXTJ3VB4qxdZBioV7JO5yLUaOKwz7vDOzlncUsegw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.0.0" + } + }, "node_modules/browserslist": { "version": "4.28.1", "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz", @@ -1813,6 +2571,16 @@ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" } }, + "node_modules/buffer-crc32": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", + "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, "node_modules/caniuse-lite": { "version": "1.0.30001769", "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001769.tgz", @@ -1834,6 +2602,55 @@ ], "license": "CC-BY-4.0" }, + "node_modules/chromium-bidi": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-14.0.0.tgz", + "integrity": "sha512-9gYlLtS6tStdRWzrtXaTMnqcM4dudNegMXJxkR0I/CXObHalYeYcAMPrL19eroNZHtJ8DQmu1E+ZNOYu/IXMXw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "mitt": "^3.0.1", + "zod": "^3.24.1" + }, + "peerDependencies": { + "devtools-protocol": "*" + } + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "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==", + "dev": true, + "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==", + "dev": true, + "license": "MIT" + }, "node_modules/convert-source-map": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", @@ -1869,6 +2686,16 @@ "url": "https://github.com/saadeghi/daisyui?sponsor=1" } }, + "node_modules/data-uri-to-buffer": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-6.0.2.tgz", + "integrity": "sha512-7hvf7/GW8e86rW0ptuwS3OcBGDjIi6SZva7hCyWC0yYry2cOPmLIjXAUHI6DK2HsnwJd9ifmt57i8eV2n4YNpw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, "node_modules/debug": { "version": "4.4.3", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", @@ -1887,6 +2714,21 @@ } } }, + "node_modules/degenerator": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/degenerator/-/degenerator-5.0.1.tgz", + "integrity": "sha512-TllpMR/t0M5sqCXfj85i4XaAzxmS5tVA16dqvdkMwGmzI+dXLXnw3J+3Vdv7VKw+ThlTMboK6i9rnZ6Nntj5CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ast-types": "^0.13.4", + "escodegen": "^2.1.0", + "esprima": "^4.0.1" + }, + "engines": { + "node": ">= 14" + } + }, "node_modules/detect-libc": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", @@ -1896,6 +2738,13 @@ "node": ">=8" } }, + "node_modules/devtools-protocol": { + "version": "0.0.1566079", + "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1566079.tgz", + "integrity": "sha512-MJfAEA1UfVhSs7fbSQOG4czavUp1ajfg6prlAN0+cmfa2zNjaIbvq8VneP7do1WAQQIvgNJWSMeP6UyI90gIlQ==", + "dev": true, + "license": "BSD-3-Clause" + }, "node_modules/electron-to-chromium": { "version": "1.5.286", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.286.tgz", @@ -1903,6 +2752,23 @@ "dev": true, "license": "ISC" }, + "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==", + "dev": true, + "license": "MIT" + }, + "node_modules/end-of-stream": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", + "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", + "dev": true, + "license": "MIT", + "dependencies": { + "once": "^1.4.0" + } + }, "node_modules/enhanced-resolve": { "version": "5.19.0", "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.19.0.tgz", @@ -1979,6 +2845,110 @@ "node": ">=6" } }, + "node_modules/escodegen": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.1.0.tgz", + "integrity": "sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esprima": "^4.0.1", + "estraverse": "^5.2.0", + "esutils": "^2.0.2" + }, + "bin": { + "escodegen": "bin/escodegen.js", + "esgenerate": "bin/esgenerate.js" + }, + "engines": { + "node": ">=6.0" + }, + "optionalDependencies": { + "source-map": "~0.6.1" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, + "license": "BSD-2-Clause", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/events-universal": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/events-universal/-/events-universal-1.0.1.tgz", + "integrity": "sha512-LUd5euvbMLpwOF8m6ivPCbhQeSiYVNb8Vs0fQ8QjXo0JTkEHpz8pxdQf0gStltaPpw0Cca8b39KxvK9cfKRiAw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "bare-events": "^2.7.0" + } + }, + "node_modules/extract-zip": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz", + "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "debug": "^4.1.1", + "get-stream": "^5.1.0", + "yauzl": "^2.10.0" + }, + "bin": { + "extract-zip": "cli.js" + }, + "engines": { + "node": ">= 10.17.0" + }, + "optionalDependencies": { + "@types/yauzl": "^2.9.1" + } + }, + "node_modules/fast-fifo": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz", + "integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/fd-slicer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", + "integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "pend": "~1.2.0" + } + }, "node_modules/fdir": { "version": "6.5.0", "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", @@ -2047,6 +3017,47 @@ "node": ">=6.9.0" } }, + "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==", + "dev": true, + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-stream": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "dev": true, + "license": "MIT", + "dependencies": { + "pump": "^3.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/get-uri": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/get-uri/-/get-uri-6.0.5.tgz", + "integrity": "sha512-b1O07XYq8eRuVzBNgJLstU6FYc1tS6wnMtF1I1D9lE8LxZSOGZ7LhxN54yPP6mGw5f2CkXY2BQUL9Fx41qvcIg==", + "dev": true, + "license": "MIT", + "dependencies": { + "basic-ftp": "^5.0.2", + "data-uri-to-buffer": "^6.0.2", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, "node_modules/graceful-fs": { "version": "4.2.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", @@ -2062,6 +3073,54 @@ "node": ">=12.0.0" } }, + "node_modules/http-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/ip-address": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.1.0.tgz", + "integrity": "sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, + "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==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/jiti": { "version": "2.6.1", "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz", @@ -2104,32 +3163,6 @@ "node": ">=6" } }, - "node_modules/lenis": { - "version": "1.3.17", - "resolved": "https://registry.npmjs.org/lenis/-/lenis-1.3.17.tgz", - "integrity": "sha512-k9T9rgcxne49ggJOvXCraWn5dt7u2mO+BNkhyu6yxuEnm9c092kAW5Bus5SO211zUvx7aCCEtzy9UWr0RB+oJw==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/darkroomengineering" - }, - "peerDependencies": { - "@nuxt/kit": ">=3.0.0", - "react": ">=17.0.0", - "vue": ">=3.0.0" - }, - "peerDependenciesMeta": { - "@nuxt/kit": { - "optional": true - }, - "react": { - "optional": true - }, - "vue": { - "optional": true - } - } - }, "node_modules/lightningcss": { "version": "1.30.2", "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.30.2.tgz", @@ -2463,6 +3496,23 @@ "integrity": "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==", "license": "MIT" }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/mitt": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.1.tgz", + "integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==", + "dev": true, + "license": "MIT" + }, "node_modules/motion-dom": { "version": "12.34.0", "resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-12.34.0.tgz", @@ -2503,6 +3553,16 @@ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, + "node_modules/netmask": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/netmask/-/netmask-2.0.2.tgz", + "integrity": "sha512-dBpDMdxv9Irdq66304OLfEmQ9tbNRFnFTuZiLo+bD+r332bBmMJ8GBLXklIXXgxd3+v9+KUnZaUR5PJMa75Gsg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4.0" + } + }, "node_modules/node-releases": { "version": "2.0.27", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz", @@ -2510,6 +3570,73 @@ "dev": true, "license": "MIT" }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/overlayscrollbars": { + "version": "2.14.0", + "resolved": "https://registry.npmjs.org/overlayscrollbars/-/overlayscrollbars-2.14.0.tgz", + "integrity": "sha512-RjV0pqc79kYhQLC3vTcLRb5GLpI1n6qh0Oua3g+bGH4EgNOJHVBGP7u0zZtxoAa0dkHlAqTTSYRb9MMmxNLjig==", + "license": "MIT" + }, + "node_modules/overlayscrollbars-react": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/overlayscrollbars-react/-/overlayscrollbars-react-0.5.6.tgz", + "integrity": "sha512-E5To04bL5brn9GVCZ36SnfGanxa2I2MDkWoa4Cjo5wol7l+diAgi4DBc983V7l2nOk/OLJ6Feg4kySspQEGDBw==", + "license": "MIT", + "peerDependencies": { + "overlayscrollbars": "^2.0.0", + "react": ">=16.8.0" + } + }, + "node_modules/pac-proxy-agent": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/pac-proxy-agent/-/pac-proxy-agent-7.2.0.tgz", + "integrity": "sha512-TEB8ESquiLMc0lV8vcd5Ql/JAKAoyzHFXaStwjkzpOpC5Yv+pIzLfHvjTSdf3vpa2bMiUQrg9i6276yn8666aA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@tootallnate/quickjs-emscripten": "^0.23.0", + "agent-base": "^7.1.2", + "debug": "^4.3.4", + "get-uri": "^6.0.1", + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.6", + "pac-resolver": "^7.0.1", + "socks-proxy-agent": "^8.0.5" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/pac-resolver": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/pac-resolver/-/pac-resolver-7.0.1.tgz", + "integrity": "sha512-5NPgf87AT2STgwa2ntRMr45jTKrYBGkVU36yT0ig/n/GMAa3oPqhZfIQ2kMEimReg0+t9kZViDVZ83qfVUlckg==", + "dev": true, + "license": "MIT", + "dependencies": { + "degenerator": "^5.0.0", + "netmask": "^2.0.2" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/pend": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", + "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==", + "dev": true, + "license": "MIT" + }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", @@ -2528,6 +3655,34 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/png-to-ico": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/png-to-ico/-/png-to-ico-3.0.1.tgz", + "integrity": "sha512-S8BOAoaGd9gT5uaemQ62arIY3Jzco7Uc7LwUTqRyqJDTsKqOAiyfyN4dSdT0D+Zf8XvgztgpRbM5wnQd7EgYwg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "^22.10.3", + "minimist": "^1.2.8", + "pngjs": "^7.0.0" + }, + "bin": { + "png-to-ico": "bin/cli.js" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/pngjs": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-7.0.0.tgz", + "integrity": "sha512-LKWqWJRhstyYo9pGvgor/ivk2w94eSjE3RGVuzLGlr3NmD8bf7RcYGze1mNdEHRP6TRP6rMuDHk5t44hnTRyow==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.19.0" + } + }, "node_modules/postcss": { "version": "8.5.6", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", @@ -2569,6 +3724,64 @@ "node": ">=4" } }, + "node_modules/progress": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/proxy-agent": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-6.5.0.tgz", + "integrity": "sha512-TmatMXdr2KlRiA2CyDu8GqR8EjahTG3aY3nXjdzFyoZbmB8hrBsTyMezhULIXKnC0jpfjlmiZ3+EaCzoInSu/A==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "^4.3.4", + "http-proxy-agent": "^7.0.1", + "https-proxy-agent": "^7.0.6", + "lru-cache": "^7.14.1", + "pac-proxy-agent": "^7.1.0", + "proxy-from-env": "^1.1.0", + "socks-proxy-agent": "^8.0.5" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/proxy-agent/node_modules/lru-cache": { + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", + "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "dev": true, + "license": "MIT" + }, + "node_modules/pump": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.3.tgz", + "integrity": "sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==", + "dev": true, + "license": "MIT", + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, "node_modules/punycode.js": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode.js/-/punycode.js-2.3.1.tgz", @@ -2578,6 +3791,25 @@ "node": ">=6" } }, + "node_modules/puppeteer-core": { + "version": "24.37.3", + "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-24.37.3.tgz", + "integrity": "sha512-fokQ8gv+hNgsRWqVuP5rUjGp+wzV5aMTP3fcm8ekNabmLGlJdFHas1OdMscAH9Gzq4Qcf7cfI/Pe6wEcAqQhqg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@puppeteer/browsers": "2.12.1", + "chromium-bidi": "14.0.0", + "debug": "^4.4.3", + "devtools-protocol": "0.0.1566079", + "typed-query-selector": "^2.12.0", + "webdriver-bidi-protocol": "0.4.1", + "ws": "^8.19.0" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/react": { "version": "19.2.4", "resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz", @@ -2609,6 +3841,16 @@ "node": ">=0.10.0" } }, + "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==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/rollup": { "version": "4.57.1", "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.57.1.tgz", @@ -2669,6 +3911,116 @@ "semver": "bin/semver.js" } }, + "node_modules/sharp": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.34.5.tgz", + "integrity": "sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==", + "dev": true, + "hasInstallScript": true, + "license": "Apache-2.0", + "dependencies": { + "@img/colour": "^1.0.0", + "detect-libc": "^2.1.2", + "semver": "^7.7.3" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-darwin-arm64": "0.34.5", + "@img/sharp-darwin-x64": "0.34.5", + "@img/sharp-libvips-darwin-arm64": "1.2.4", + "@img/sharp-libvips-darwin-x64": "1.2.4", + "@img/sharp-libvips-linux-arm": "1.2.4", + "@img/sharp-libvips-linux-arm64": "1.2.4", + "@img/sharp-libvips-linux-ppc64": "1.2.4", + "@img/sharp-libvips-linux-riscv64": "1.2.4", + "@img/sharp-libvips-linux-s390x": "1.2.4", + "@img/sharp-libvips-linux-x64": "1.2.4", + "@img/sharp-libvips-linuxmusl-arm64": "1.2.4", + "@img/sharp-libvips-linuxmusl-x64": "1.2.4", + "@img/sharp-linux-arm": "0.34.5", + "@img/sharp-linux-arm64": "0.34.5", + "@img/sharp-linux-ppc64": "0.34.5", + "@img/sharp-linux-riscv64": "0.34.5", + "@img/sharp-linux-s390x": "0.34.5", + "@img/sharp-linux-x64": "0.34.5", + "@img/sharp-linuxmusl-arm64": "0.34.5", + "@img/sharp-linuxmusl-x64": "0.34.5", + "@img/sharp-wasm32": "0.34.5", + "@img/sharp-win32-arm64": "0.34.5", + "@img/sharp-win32-ia32": "0.34.5", + "@img/sharp-win32-x64": "0.34.5" + } + }, + "node_modules/sharp/node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/smart-buffer": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", + "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks": { + "version": "2.8.7", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.7.tgz", + "integrity": "sha512-HLpt+uLy/pxB+bum/9DzAgiKS8CX1EvbWxI4zlmgGCExImLdiad2iCwXT5Z4c9c3Eq8rP2318mPW2c+QbtjK8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ip-address": "^10.0.1", + "smart-buffer": "^4.2.0" + }, + "engines": { + "node": ">= 10.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks-proxy-agent": { + "version": "8.0.5", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.5.tgz", + "integrity": "sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "^4.3.4", + "socks": "^2.8.3" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "license": "BSD-3-Clause", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/source-map-js": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", @@ -2678,6 +4030,46 @@ "node": ">=0.10.0" } }, + "node_modules/streamx": { + "version": "2.23.0", + "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.23.0.tgz", + "integrity": "sha512-kn+e44esVfn2Fa/O0CPFcex27fjIL6MkVae0Mm6q+E6f0hWv578YCERbv+4m02cjxvDsPKLnmxral/rR6lBMAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "events-universal": "^1.0.0", + "fast-fifo": "^1.3.2", + "text-decoder": "^1.1.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==", + "dev": true, + "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==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/tailwindcss": { "version": "4.1.18", "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.18.tgz", @@ -2697,6 +4089,43 @@ "url": "https://opencollective.com/webpack" } }, + "node_modules/tar-fs": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.1.1.tgz", + "integrity": "sha512-LZA0oaPOc2fVo82Txf3gw+AkEd38szODlptMYejQUhndHMLQ9M059uXR+AfS7DNo0NpINvSqDsvyaCrBVkptWg==", + "dev": true, + "license": "MIT", + "dependencies": { + "pump": "^3.0.0", + "tar-stream": "^3.1.5" + }, + "optionalDependencies": { + "bare-fs": "^4.0.1", + "bare-path": "^3.0.0" + } + }, + "node_modules/tar-stream": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.1.7.tgz", + "integrity": "sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "b4a": "^1.6.4", + "fast-fifo": "^1.2.0", + "streamx": "^2.15.0" + } + }, + "node_modules/text-decoder": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/text-decoder/-/text-decoder-1.2.6.tgz", + "integrity": "sha512-27FeW5GQFDfw0FpwMQhMagB7BztOOlmjcSRi97t2oplhKVTZtp0DZbSegSaXS5IIC6mxMvBG4AR1Sgc6BX3CQg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "b4a": "^1.6.4" + } + }, "node_modules/tinyglobby": { "version": "0.2.15", "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", @@ -2719,6 +4148,13 @@ "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", "license": "0BSD" }, + "node_modules/typed-query-selector": { + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/typed-query-selector/-/typed-query-selector-2.12.0.tgz", + "integrity": "sha512-SbklCd1F0EiZOyPiW192rrHZzZ5sBijB6xM+cpmrwDqObvdtunOHHIk9fCGsoK5JVIYXoyEp4iEdE3upFH3PAg==", + "dev": true, + "license": "MIT" + }, "node_modules/typescript": { "version": "5.8.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", @@ -2739,6 +4175,13 @@ "integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==", "license": "MIT" }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "devOptional": true, + "license": "MIT" + }, "node_modules/update-browserslist-db": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", @@ -2850,12 +4293,126 @@ } } }, + "node_modules/webdriver-bidi-protocol": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/webdriver-bidi-protocol/-/webdriver-bidi-protocol-0.4.1.tgz", + "integrity": "sha512-ARrjNjtWRRs2w4Tk7nqrf2gBI0QXWuOmMCx2hU+1jUt6d00MjMxURrhxhGbrsoiZKJrhTSTzbIrc554iKI10qw==", + "dev": true, + "license": "Apache-2.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==", + "dev": true, + "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/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/ws": { + "version": "8.19.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.19.0.tgz", + "integrity": "sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, "node_modules/yallist": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", "dev": true, "license": "ISC" + }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/yauzl": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", + "integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-crc32": "~0.2.3", + "fd-slicer": "~1.1.0" + } + }, + "node_modules/zod": { + "version": "3.25.76", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", + "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } } } } diff --git a/package.json b/package.json index 2fff988..df6e737 100644 --- a/package.json +++ b/package.json @@ -1,42 +1 @@ -{ -"name": "vesper", - "private": true, - "version": "1.0.0", - "type": "module", - "scripts": { - "dev": "vite", - "build": "tsc && vite build", - "preview": "vite preview", - "tauri": "tauri" - }, - "dependencies": { - "@tailwindcss/typography": "^0.5.19", - "@tailwindcss/vite": "^4.1.18", - "@tauri-apps/api": "^2", - "@tauri-apps/plugin-dialog": "^2.6.0", - "@tauri-apps/plugin-fs": "^2.4.5", - "@tauri-apps/plugin-opener": "^2", - "@tauri-apps/plugin-window-state": "^2.4.1", - "daisyui": "^5.5.18", - "framer-motion": "^12.34.0", -"highlight.js": "^11.11.1", - "lucide-react": "^0.564.0", - "markdown-it": "^14.1.1", - "markdown-it-mark": "^4.0.0", - "markdown-it-sub": "^2.0.0", - "markdown-it-sup": "^2.0.0", - "markdown-it-task-lists": "^2.1.1", - "react": "^19.1.0", - "react-dom": "^19.1.0", - "tailwindcss": "^4.1.18" - }, - "devDependencies": { - "@tauri-apps/cli": "^2", - "@types/markdown-it": "^14.1.2", - "@types/react": "^19.1.8", - "@types/react-dom": "^19.1.6", - "@vitejs/plugin-react": "^4.6.0", - "typescript": "~5.8.3", - "vite": "^7.0.4" - } -} +{"name":"vesper","private":true,"version":"1.0.0","type":"module","scripts":{"dev":"vite","build":"tsc && vite build","preview":"vite preview","tauri":"tauri"},"dependencies":{"@fontsource-variable/inter":"^5.2.8","@fontsource-variable/jetbrains-mono":"^5.2.8","@tailwindcss/typography":"^0.5.19","@tailwindcss/vite":"^4.1.18","@tauri-apps/api":"^2","@tauri-apps/plugin-dialog":"^2.6.0","@tauri-apps/plugin-fs":"^2.4.5","@tauri-apps/plugin-opener":"^2","@tauri-apps/plugin-window-state":"^2.4.1","daisyui":"^5.5.18","framer-motion":"^12.34.0","highlight.js":"^11.11.1","lucide-react":"^0.564.0","markdown-it":"^14.1.1","markdown-it-mark":"^4.0.0","markdown-it-sub":"^2.0.0","markdown-it-sup":"^2.0.0","markdown-it-task-lists":"^2.1.1","overlayscrollbars":"^2.14.0","overlayscrollbars-react":"^0.5.6","react":"^19.1.0","react-dom":"^19.1.0","tailwindcss":"^4.1.18"},"devDependencies":{"@tauri-apps/cli":"^2","@types/markdown-it":"^14.1.2","@types/react":"^19.1.8","@types/react-dom":"^19.1.6","@vitejs/plugin-react":"^4.6.0","png-to-ico":"^3.0.1","puppeteer-core":"^24.37.3","sharp":"^0.34.5","typescript":"~5.8.3","vite":"^7.0.4"}} \ No newline at end of file diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index 9f1996b..19fc8b7 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -596,20 +596,6 @@ dependencies = [ "syn 2.0.115", ] -[[package]] -name = "darkmark" -version = "0.1.0" -dependencies = [ - "serde", - "serde_json", - "tauri", - "tauri-build", - "tauri-plugin-dialog", - "tauri-plugin-fs", - "tauri-plugin-opener", - "tauri-plugin-window-state", -] - [[package]] name = "darling" version = "0.21.3" @@ -4308,6 +4294,20 @@ version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" +[[package]] +name = "vesper" +version = "0.1.0" +dependencies = [ + "serde", + "serde_json", + "tauri", + "tauri-build", + "tauri-plugin-dialog", + "tauri-plugin-fs", + "tauri-plugin-opener", + "tauri-plugin-window-state", +] + [[package]] name = "vswhom" version = "0.1.0" diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index ef6af5f..a0a138b 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "darkmark" +name = "vesper" version = "0.1.0" description = "A beautiful markdown reader" authors = ["you"] @@ -8,7 +8,7 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [lib] -name = "darkmark_lib" +name = "vesper_lib" crate-type = ["staticlib", "cdylib", "rlib"] [build-dependencies] diff --git a/src-tauri/icons/128x128.png b/src-tauri/icons/128x128.png index 6be5e50..245f83c 100644 Binary files a/src-tauri/icons/128x128.png and b/src-tauri/icons/128x128.png differ diff --git a/src-tauri/icons/128x128@2x.png b/src-tauri/icons/128x128@2x.png index e81bece..2046f1c 100644 Binary files a/src-tauri/icons/128x128@2x.png and b/src-tauri/icons/128x128@2x.png differ diff --git a/src-tauri/icons/32x32.png b/src-tauri/icons/32x32.png index a437dd5..1f4aa87 100644 Binary files a/src-tauri/icons/32x32.png and b/src-tauri/icons/32x32.png differ diff --git a/src-tauri/icons/Square107x107Logo.png b/src-tauri/icons/Square107x107Logo.png index 0ca4f27..d6efd7e 100644 Binary files a/src-tauri/icons/Square107x107Logo.png and b/src-tauri/icons/Square107x107Logo.png differ diff --git a/src-tauri/icons/Square142x142Logo.png b/src-tauri/icons/Square142x142Logo.png index b81f820..9f53099 100644 Binary files a/src-tauri/icons/Square142x142Logo.png and b/src-tauri/icons/Square142x142Logo.png differ diff --git a/src-tauri/icons/Square150x150Logo.png b/src-tauri/icons/Square150x150Logo.png index 624c7bf..88b8303 100644 Binary files a/src-tauri/icons/Square150x150Logo.png and b/src-tauri/icons/Square150x150Logo.png differ diff --git a/src-tauri/icons/Square284x284Logo.png b/src-tauri/icons/Square284x284Logo.png index c021d2b..517a8f0 100644 Binary files a/src-tauri/icons/Square284x284Logo.png and b/src-tauri/icons/Square284x284Logo.png differ diff --git a/src-tauri/icons/Square30x30Logo.png b/src-tauri/icons/Square30x30Logo.png index 6219700..24779a4 100644 Binary files a/src-tauri/icons/Square30x30Logo.png and b/src-tauri/icons/Square30x30Logo.png differ diff --git a/src-tauri/icons/Square310x310Logo.png b/src-tauri/icons/Square310x310Logo.png index f9bc048..96eaded 100644 Binary files a/src-tauri/icons/Square310x310Logo.png and b/src-tauri/icons/Square310x310Logo.png differ diff --git a/src-tauri/icons/Square44x44Logo.png b/src-tauri/icons/Square44x44Logo.png index d5fbfb2..7eb935e 100644 Binary files a/src-tauri/icons/Square44x44Logo.png and b/src-tauri/icons/Square44x44Logo.png differ diff --git a/src-tauri/icons/Square71x71Logo.png b/src-tauri/icons/Square71x71Logo.png index 63440d7..1ce6414 100644 Binary files a/src-tauri/icons/Square71x71Logo.png and b/src-tauri/icons/Square71x71Logo.png differ diff --git a/src-tauri/icons/Square89x89Logo.png b/src-tauri/icons/Square89x89Logo.png index f3f705a..ff7b2df 100644 Binary files a/src-tauri/icons/Square89x89Logo.png and b/src-tauri/icons/Square89x89Logo.png differ diff --git a/src-tauri/icons/StoreLogo.png b/src-tauri/icons/StoreLogo.png index 4556388..51804f9 100644 Binary files a/src-tauri/icons/StoreLogo.png and b/src-tauri/icons/StoreLogo.png differ diff --git a/src-tauri/icons/icon.ico b/src-tauri/icons/icon.ico index b3636e4..a0af3ba 100644 Binary files a/src-tauri/icons/icon.ico and b/src-tauri/icons/icon.ico differ diff --git a/src-tauri/icons/icon.png b/src-tauri/icons/icon.png index e1cd261..145bfa0 100644 Binary files a/src-tauri/icons/icon.png and b/src-tauri/icons/icon.png differ diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index 571ae7a..2c6f7bd 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -2,5 +2,5 @@ #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] fn main() { - darkmark_lib::run() + vesper_lib::run() } diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index 9128ab1..54145a6 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -21,7 +21,7 @@ "transparent": false, "resizable": true, "center": true, - "dragDropEnabled": false + "dragDropEnabled": true } ], "security": { diff --git a/src/App.tsx b/src/App.tsx index 04d80a7..141a926 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,7 +1,8 @@ -import { useState, useEffect, useCallback, useRef } from "react"; +import { useState, useEffect, useLayoutEffect, useCallback, useRef, useMemo } from "react"; import { open } from "@tauri-apps/plugin-dialog"; import { readTextFile } from "@tauri-apps/plugin-fs"; import { getCurrentWindow } from "@tauri-apps/api/window"; +import { getCurrentWebview } from "@tauri-apps/api/webview"; import MarkdownIt from "markdown-it"; import hljs from "highlight.js"; import { motion, AnimatePresence } from "framer-motion"; @@ -9,10 +10,11 @@ import TaskLists from "markdown-it-task-lists"; import sup from "markdown-it-sup"; import sub from "markdown-it-sub"; import mark from "markdown-it-mark"; -import { - Minus, Square, X, FileText, Search, PanelLeft, - Keyboard, Info, ChevronLeft, ChevronRight, - FileInput, FolderOpen +import { OverlayScrollbarsComponent } from "overlayscrollbars-react"; +import "overlayscrollbars/overlayscrollbars.css"; +import { + Minus, Plus, Square, X, FileText, + ChevronLeft, ChevronRight, FolderOpen, FileDown } from "lucide-react"; interface Tab { id: string; title: string; content: string; path: string; } @@ -20,6 +22,7 @@ interface Heading { id: string; text: string; level: number; } const md: MarkdownIt = new MarkdownIt({ html: true, + breaks: true, linkify: true, typographer: true, highlight: function(str: string, lang: string): string { @@ -32,12 +35,215 @@ const md: MarkdownIt = new MarkdownIt({ } }).use(TaskLists).use(sup).use(sub).use(mark); +const materialEase = [0.4, 0, 0.2, 1] as const; +const smoothTransition = { duration: 0.2, ease: materialEase }; + +// Saved selection range — set in mousedown (before browser clears it), read in contextmenu handler +let _savedSelectionRange: Range | null = null; + +// Reusable kinetic scroll + iOS overscroll setup for any scrollable area +function setupKineticScroll( + viewport: HTMLElement, + contentEl: HTMLElement, + kineticDragRef: { current: boolean } +): () => void { + const DRAG_THRESHOLD = 5; + let isTracking = false; + let isDragging = false; + let startX = 0; + let startY = 0; + let startScrollTop = 0; + let velocityY = 0; + let lastY = 0; + let lastTime = 0; + let momentumRaf: number | null = null; + let springRaf: number | null = null; + let overscrollOffset = 0; + + const cancelAnimations = () => { + if (momentumRaf) { cancelAnimationFrame(momentumRaf); momentumRaf = null; } + if (springRaf) { cancelAnimationFrame(springRaf); springRaf = null; } + }; + + const rubberBand = (offset: number, dimension: number): number => { + const c = 0.55; + const x = Math.abs(offset); + return Math.sign(offset) * (1.0 - (1.0 / ((x * c / dimension) + 1.0))) * dimension; + }; + + const setOverscroll = (offset: number) => { + overscrollOffset = offset; + contentEl.style.transform = offset !== 0 ? `translateY(${offset}px)` : ''; + contentEl.style.transition = ''; + }; + + const springBack = () => { + cancelAnimations(); + let position = overscrollOffset; + let velocity = 0; + const stiffness = 60; + const damping = 14; + const dt = 1 / 60; + + const animate = () => { + const acceleration = -stiffness * position - damping * velocity; + velocity += acceleration * dt; + position += velocity * dt; + + if (Math.abs(position) < 0.5 && Math.abs(velocity) < 0.5) { + setOverscroll(0); + return; + } + setOverscroll(position); + springRaf = requestAnimationFrame(animate); + }; + springRaf = requestAnimationFrame(animate); + }; + + const applyMomentum = () => { + cancelAnimations(); + let v = velocityY; + const friction = 0.99; + + const animate = () => { + v *= friction; + if (Math.abs(v) < 0.05) return; + + const desiredTop = viewport.scrollTop - v; + + // Top edge + if (desiredTop < 0) { + viewport.scrollTop = 0; + const stretch = rubberBand(Math.abs(v) * 10, viewport.clientHeight); + setOverscroll(Math.abs(stretch)); + springBack(); + return; + } + + // Set scroll and let browser clamp to the real maximum + viewport.scrollTop = desiredTop; + const actualTop = viewport.scrollTop; + + // Bottom edge: browser clamped scrollTop below what we asked for + if (desiredTop - actualTop > 1) { + const stretch = rubberBand(Math.abs(v) * 10, viewport.clientHeight); + setOverscroll(-Math.abs(stretch)); + springBack(); + return; + } + + momentumRaf = requestAnimationFrame(animate); + }; + momentumRaf = requestAnimationFrame(animate); + }; + + const onMouseDown = (e: MouseEvent) => { + if (e.button !== 2) return; + const target = e.target as HTMLElement; + if (target.closest('.os-scrollbar')) return; + + // If text is selected, don't start kinetic scroll + const sel = window.getSelection(); + if (sel && sel.toString().length > 0) return; + + cancelAnimations(); + if (overscrollOffset !== 0) setOverscroll(0); + + isTracking = true; + isDragging = false; + startX = e.clientX; + startY = e.clientY; + startScrollTop = viewport.scrollTop; + lastY = e.clientY; + lastTime = performance.now(); + velocityY = 0; + }; + + const onMouseMove = (e: MouseEvent) => { + if (!isTracking) return; + + if (!isDragging) { + const dx = e.clientX - startX; + const dy = e.clientY - startY; + if (Math.sqrt(dx * dx + dy * dy) < DRAG_THRESHOLD) return; + isDragging = true; + document.body.style.cursor = 'grabbing'; + document.body.style.userSelect = 'none'; + } + + e.preventDefault(); + + const now = performance.now(); + const dt = now - lastTime; + const dy = e.clientY - lastY; + + if (dt > 0) { + velocityY = velocityY * 0.85 + (dy / dt) * 16 * 0.15; + } + + lastY = e.clientY; + lastTime = now; + + const scrollDelta = startY - e.clientY; + const desiredTop = startScrollTop + scrollDelta; + + // Top edge + if (desiredTop < 0) { + viewport.scrollTop = 0; + setOverscroll(rubberBand(Math.abs(desiredTop), viewport.clientHeight)); + } else { + // Set scroll and let browser clamp + viewport.scrollTop = desiredTop; + const actualTop = viewport.scrollTop; + + // Bottom edge: browser clamped + if (desiredTop - actualTop > 1) { + setOverscroll(-rubberBand(desiredTop - actualTop, viewport.clientHeight)); + } else { + if (overscrollOffset !== 0) setOverscroll(0); + } + } + }; + + const onMouseUp = () => { + if (!isTracking) return; + isTracking = false; + + if (isDragging) { + isDragging = false; + kineticDragRef.current = true; + document.body.style.cursor = ''; + document.body.style.userSelect = ''; + + if (overscrollOffset !== 0) { + springBack(); + } else if (Math.abs(velocityY) > 0.5) { + applyMomentum(); + } + } + }; + + viewport.addEventListener('mousedown', onMouseDown); + document.addEventListener('mousemove', onMouseMove); + document.addEventListener('mouseup', onMouseUp); + + return () => { + cancelAnimations(); + viewport.removeEventListener('mousedown', onMouseDown); + document.removeEventListener('mousemove', onMouseMove); + document.removeEventListener('mouseup', onMouseUp); + document.body.style.cursor = ''; + document.body.style.userSelect = ''; + contentEl.style.transform = ''; + }; +} + function App() { const [tabs, setTabs] = useState([]); const [activeTabId, setActiveTabId] = useState(null); const [showSidebar, setShowSidebar] = useState(false); const [showSearch, setShowSearch] = useState(false); - const [focusMode, setFocusMode] = useState(false); + const [focusMode, setFocusMode] = useState(true); const [isDraggingOver, setIsDraggingOver] = useState(false); const [searchQuery, setSearchQuery] = useState(""); const [searchMatches, setSearchMatches] = useState([]); @@ -47,16 +253,30 @@ function App() { const [showShortcutsModal, setShowShortcutsModal] = useState(false); const [showAboutModal, setShowAboutModal] = useState(false); const [zoom, setZoom] = useState(100); + const [uiZoom, setUiZoom] = useState(() => { + const saved = localStorage.getItem('vesper-ui-zoom'); + return saved ? parseInt(saved, 10) : 100; + }); const [contentWidth, setContentWidth] = useState(680); - const [sidebarWidth, setSidebarWidth] = useState(220); + const [sidebarWidth, setSidebarWidth] = useState(() => { + const saved = localStorage.getItem('vesper-sidebar-width'); + return saved ? parseInt(saved, 10) : 220; + }); const [searchTriggerCount, setSearchTriggerCount] = useState(0); const [resizeStartState, setResizeStartState] = useState<{ startX: number; startWidth: number } | null>(null); - const [contextMenu, setContextMenu] = useState<{ x: number; y: number } | null>(null); - + const [contextMenu, setContextMenu] = useState<{ x: number; y: number; selectedText: string } | null>(null); + const [selectionRects, setSelectionRects] = useState<{ left: number; top: number; width: number; height: number }[]>([]); + const contentRef = useRef(null); const sidebarRef = useRef(null); const searchInputRef = useRef(null); const menuItemRefs = useRef<{ [key: string]: HTMLDivElement | null }>({}); + const kineticDragActiveRef = useRef(false); + const contextMenuRef = useRef(null); + const tabScrollRef = useRef(null); + const [tabScrollState, setTabScrollState] = useState({ canLeft: false, canRight: false }); + const tabsRef = useRef(tabs); + tabsRef.current = tabs; const [appWindow, setAppWindow] = useState | null>(null); const activeTab = tabs.find(t => t.id === activeTabId) || null; @@ -65,11 +285,46 @@ function App() { setAppWindow(getCurrentWindow()); }, []); + // Capture-phase mousedown: save selection range before browser clears it + useEffect(() => { + const saveSelection = (e: MouseEvent) => { + if (e.button === 2) { + const sel = window.getSelection(); + if (sel && sel.rangeCount > 0 && sel.toString().length > 0) { + _savedSelectionRange = sel.getRangeAt(0).cloneRange(); + } else { + _savedSelectionRange = null; + } + } + }; + document.addEventListener('mousedown', saveSelection, true); + return () => document.removeEventListener('mousedown', saveSelection, true); + }, []); + useEffect(() => { const handleContextMenu = (e: MouseEvent) => { + e.preventDefault(); + if (kineticDragActiveRef.current) { + kineticDragActiveRef.current = false; + return; + } if (activeTab) { - e.preventDefault(); - setContextMenu({ x: e.clientX, y: e.clientY }); + const selectedText = window.getSelection()?.toString() || ''; + const zf = uiZoom / 100; + const x = e.clientX / zf; + const y = e.clientY / zf; + + // Build selection overlay rects from saved range (before browser cleared it) + let rects: { left: number; top: number; width: number; height: number }[] = []; + if (selectedText && _savedSelectionRange) { + const clientRects = _savedSelectionRange.getClientRects(); + rects = Array.from(clientRects).map(r => ({ + left: r.left / zf, top: r.top / zf, width: r.width / zf, height: r.height / zf + })); + } + + setContextMenu({ x, y, selectedText }); + setSelectionRects(rects); } }; const handleClick = () => setContextMenu(null); @@ -79,7 +334,38 @@ function App() { document.removeEventListener('contextmenu', handleContextMenu); document.removeEventListener('click', handleClick); }; - }, [activeTab]); + }, [activeTab, uiZoom]); + + // Clear selection overlay when context menu closes + useEffect(() => { + if (!contextMenu) setSelectionRects(prev => prev.length > 0 ? [] : prev); + }, [contextMenu]); + + // Clamp context menu within window bounds after render + useLayoutEffect(() => { + const menu = contextMenuRef.current; + if (!menu || !contextMenu) return; + const zf = uiZoom / 100; + // Menu rect is in viewport pixels (already scaled by zoom) + const rect = menu.getBoundingClientRect(); + // Convert menu dimensions to zoomed coordinate space + const menuW = rect.width / zf; + const menuH = rect.height / zf; + // Window dimensions in zoomed coordinate space + const winW = window.innerWidth / zf; + const winH = window.innerHeight / zf; + + let x = contextMenu.x; + let y = contextMenu.y; + + if (x + menuW > winW) x = winW - menuW; + if (y + menuH > winH) y = winH - menuH; + if (x < 0) x = 0; + if (y < 0) y = 0; + + menu.style.left = `${x}px`; + menu.style.top = `${y}px`; + }, [contextMenu, uiZoom]); const parseHeadings = useCallback((content: string) => { const headingRegex = /^(#{1,6})\s+(.+)$/gm; @@ -91,49 +377,161 @@ function App() { setHeadings(parsed); }, []); - const renderMarkdown = useCallback((content: string) => ({ __html: md.render(content) }), []); + // Render markdown to HTML — memoized so it only re-runs when content changes + const renderedHtml = useMemo(() => { + if (!activeTab) return ''; + return md.render(activeTab.content); + }, [activeTab]); - const handleFileDrop = useCallback(async (e: React.DragEvent) => { - e.preventDefault(); - e.stopPropagation(); - setIsDraggingOver(false); - - const files = Array.from(e.dataTransfer.files); - const mdFile = files.find(f => f.name.endsWith('.md') || f.name.endsWith('.markdown') || f.name.endsWith('.txt')); - - if (mdFile) { - const content = await mdFile.text(); - const newTab: Tab = { id: Date.now().toString(), title: mdFile.name, content, path: mdFile.name }; - setTabs(prev => [...prev, newTab]); - setActiveTabId(newTab.id); - parseHeadings(content); + // Inject search highlights into the HTML string (survives React re-renders) + const { highlightedHtml, highlightCount } = useMemo(() => { + if (!searchQuery || !renderedHtml) return { highlightedHtml: renderedHtml, highlightCount: 0 }; + + const escaped = searchQuery.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); + const regex = new RegExp(escaped, 'gi'); + let count = 0; + + // Split on HTML tags, only highlight text segments + const parts = renderedHtml.split(/(<[^>]*>)/); + const highlighted = parts.map(part => { + if (part.startsWith('<')) return part; + return part.replace(regex, (match) => { + const idx = count++; + return `${match}`; + }); + }).join(''); + + return { highlightedHtml: highlighted, highlightCount: count }; + }, [renderedHtml, searchQuery]); + + // Sync highlight count with search matches state + useEffect(() => { + setSearchMatches(Array.from({ length: highlightCount }, (_, i) => i)); + if (highlightCount === 0) setCurrentMatchIndex(0); + }, [highlightCount]); + + // Scroll to active match and toggle active class + useEffect(() => { + const container = document.querySelector('.markdown-content'); + if (!container || highlightCount === 0) return; + + container.querySelectorAll('.search-highlight-active').forEach(el => { + el.classList.remove('search-highlight-active'); + }); + + const active = container.querySelector(`[data-match-index="${currentMatchIndex}"]`); + if (active) { + active.classList.add('search-highlight-active'); + active.scrollIntoView({ behavior: 'smooth', block: 'center' }); } + }, [currentMatchIndex, highlightCount, searchTriggerCount]); + + const updateTabScrollState = useCallback(() => { + const el = tabScrollRef.current; + if (!el) { setTabScrollState({ canLeft: false, canRight: false }); return; } + setTabScrollState({ + canLeft: el.scrollLeft > 0, + canRight: el.scrollLeft < el.scrollWidth - el.clientWidth - 1, + }); + }, []); + + const tabScrollInterval = useRef | null>(null); + + const startScrollingTabs = useCallback((dir: 'left' | 'right') => { + const el = tabScrollRef.current; + if (!el) return; + const step = dir === 'left' ? -8 : 8; + el.scrollBy({ left: step }); + updateTabScrollState(); + tabScrollInterval.current = setInterval(() => { + el.scrollBy({ left: step }); + updateTabScrollState(); + }, 16); + }, [updateTabScrollState]); + + const stopScrollingTabs = useCallback(() => { + if (tabScrollInterval.current) { + clearInterval(tabScrollInterval.current); + tabScrollInterval.current = null; + } + }, []); + + const scrollTabsToEnd = useCallback(() => { + const el = tabScrollRef.current; + if (!el) return; + requestAnimationFrame(() => { + el.scrollTo({ left: el.scrollWidth, behavior: 'smooth' }); + setTimeout(updateTabScrollState, 300); + }); + }, [updateTabScrollState]); + + const scrollToTabIndex = useCallback((index: number) => { + const el = tabScrollRef.current; + if (!el) return; + requestAnimationFrame(() => { + const tab = el.children[0]?.children[index] as HTMLElement | undefined; + if (tab) tab.scrollIntoView({ behavior: 'smooth', block: 'nearest', inline: 'nearest' }); + setTimeout(updateTabScrollState, 300); + }); + }, [updateTabScrollState]); + + // Tauri native drag-and-drop (bypasses browser event interception) + useEffect(() => { + let unlisten: (() => void) | undefined; + getCurrentWebview().onDragDropEvent(async (event) => { + if (event.payload.type === 'enter') { + setIsDraggingOver(true); + } else if (event.payload.type === 'leave') { + setIsDraggingOver(false); + } else if (event.payload.type === 'drop') { + setIsDraggingOver(false); + const paths = event.payload.paths; + const mdPath = paths.find((p: string) => p.endsWith('.md') || p.endsWith('.markdown') || p.endsWith('.txt')); + if (mdPath) { + try { + const existing = tabsRef.current.find(t => t.path === mdPath); + if (existing) { + setActiveTabId(existing.id); + parseHeadings(existing.content); + scrollToTabIndex(tabsRef.current.indexOf(existing)); + } else { + const content = await readTextFile(mdPath); + const title = mdPath.split(/[/\\]/).pop() || 'Untitled'; + const newTab: Tab = { id: Date.now().toString(), title, content, path: mdPath }; + setTabs(prev => [...prev, newTab]); + setActiveTabId(newTab.id); + parseHeadings(content); + scrollTabsToEnd(); + } + } catch (err) { + console.error('Failed to read dropped file:', err); + } + } + } + }).then(fn => { unlisten = fn; }); + return () => { unlisten?.(); }; }, [parseHeadings]); - const handleDragOver = useCallback((e: React.DragEvent) => { - e.preventDefault(); - e.stopPropagation(); - setIsDraggingOver(true); - }, []); - - const handleDragLeave = useCallback((e: React.DragEvent) => { - e.preventDefault(); - e.stopPropagation(); - setIsDraggingOver(false); - }, []); - const handleOpenDialog = useCallback(async () => { try { const selected = await open({ multiple: false, filters: [{ name: 'Markdown', extensions: ['md', 'markdown', 'txt'] }] }); if (selected) { - const content = await readTextFile(selected); - const newTab: Tab = { id: Date.now().toString(), title: selected.split(/[/\\]/).pop() || 'Untitled', content, path: selected }; - setTabs(prev => [...prev, newTab]); - setActiveTabId(newTab.id); - parseHeadings(content); + const existing = tabsRef.current.find(t => t.path === selected); + if (existing) { + setActiveTabId(existing.id); + parseHeadings(existing.content); + scrollToTabIndex(tabsRef.current.indexOf(existing)); + } else { + const content = await readTextFile(selected); + const newTab: Tab = { id: Date.now().toString(), title: selected.split(/[/\\]/).pop() || 'Untitled', content, path: selected }; + setTabs(prev => [...prev, newTab]); + setActiveTabId(newTab.id); + parseHeadings(content); + scrollTabsToEnd(); + } } } catch (err) { console.error('Failed to open file:', err); } - }, [parseHeadings]); + }, [parseHeadings, scrollTabsToEnd, scrollToTabIndex]); const closeTab = useCallback((id: string) => { setTabs(prev => { @@ -148,6 +546,25 @@ function App() { }); }, [activeTabId, parseHeadings]); + // Track tab scroll state + mouse wheel horizontal scroll + useEffect(() => { + updateTabScrollState(); + const el = tabScrollRef.current; + if (!el) return; + const onScroll = () => updateTabScrollState(); + const onWheel = (e: WheelEvent) => { + if (e.deltaY !== 0) { + e.preventDefault(); + el.scrollBy({ left: e.deltaY }); + updateTabScrollState(); + } + }; + el.addEventListener('scroll', onScroll, { passive: true }); + el.addEventListener('wheel', onWheel, { passive: false }); + window.addEventListener('resize', onScroll); + return () => { el.removeEventListener('scroll', onScroll); el.removeEventListener('wheel', onWheel); window.removeEventListener('resize', onScroll); }; + }, [tabs.length, updateTabScrollState]); + const closeWindow = useCallback(async () => { await appWindow?.close(); }, [appWindow]); const scrollToHeading = useCallback((text: string) => { @@ -156,39 +573,6 @@ function App() { if (target) target.scrollIntoView({ behavior: 'smooth', block: 'start' }); }, []); - useEffect(() => { - if (!searchQuery || !activeTab) { - setSearchMatches([]); - return; - } - const regex = new RegExp(searchQuery.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'gi'); - const matches: number[] = []; - let match; - while ((match = regex.exec(activeTab.content)) !== null) { - matches.push(match.index); - } - setSearchMatches(matches); - setCurrentMatchIndex(0); - }, [searchQuery, activeTab]); - - useEffect(() => { - if (searchMatches.length > 0 && contentRef.current) { - const contentEl = contentRef.current; - const textContent = contentEl.innerText || ''; - const regex = new RegExp(searchQuery.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'gi'); - const matchIdx = searchMatches[currentMatchIndex]; - if (matchIdx !== undefined) { - const matchPos = textContent.indexOf(searchQuery, Math.max(0, matchIdx - 50)); - if (matchPos !== -1) { - const element = contentEl.querySelector(`[data-search-index="${currentMatchIndex}"]`); - if (element) { - element.scrollIntoView({ behavior: 'smooth', block: 'center' }); - } - } - } - } - }, [searchTriggerCount, searchMatches, currentMatchIndex, searchQuery]); - useEffect(() => { const handleKeyDown = (e: KeyboardEvent) => { if (e.ctrlKey || e.metaKey) { @@ -212,17 +596,13 @@ function App() { setZoom(z => Math.max(50, Math.min(200, z + delta))); } else if (e.shiftKey) { e.preventDefault(); - setContentWidth(w => Math.max(400, Math.min(1200, w + e.deltaY))); + setContentWidth(w => Math.max(400, Math.min(1200, w - e.deltaY))); } }; document.addEventListener('wheel', handleWheel, { passive: false }); return () => document.removeEventListener('wheel', handleWheel); }, []); - useEffect(() => { - document.documentElement.style.zoom = `${zoom}%`; - }, [zoom]); - useEffect(() => { let isDraggingWindow = false; const handleMouseDown = (e: MouseEvent) => { @@ -241,6 +621,42 @@ function App() { }; }, [appWindow, focusMode]); + // Kinetic scroll: right-click + drag with iOS-style elastic overscroll + // Applied to both content area and sidebar + useEffect(() => { + let cancelled = false; + const cleanups: (() => void)[] = []; + + requestAnimationFrame(() => { requestAnimationFrame(() => { + if (cancelled) return; + + // Content area + const contentWrapper = document.querySelector('.content-area-scroll-wrapper'); + if (contentWrapper) { + const viewport = contentWrapper.querySelector('[data-overlayscrollbars-viewport]') as HTMLElement; + const contentDiv = contentWrapper.querySelector('.content-area-scroll') as HTMLElement; + if (viewport && contentDiv) { + cleanups.push(setupKineticScroll(viewport, contentDiv, kineticDragActiveRef)); + } + } + + // Sidebar + const sidebarWrapper = document.querySelector('.sidebar-scroll-wrapper'); + if (sidebarWrapper) { + const viewport = sidebarWrapper.querySelector('[data-overlayscrollbars-viewport]') as HTMLElement; + const sidebarDiv = sidebarWrapper.querySelector('.sidebar') as HTMLElement; + if (viewport && sidebarDiv) { + cleanups.push(setupKineticScroll(viewport, sidebarDiv, kineticDragActiveRef)); + } + } + }); }); + + return () => { + cancelled = true; + cleanups.forEach(fn => fn()); + }; + }, [activeTabId, showSidebar]); + const startSidebarResize = (e: React.MouseEvent) => { e.preventDefault(); e.stopPropagation(); @@ -250,7 +666,7 @@ function App() { useEffect(() => { if (!resizeStartState) return; const handleMouseMove = (e: MouseEvent) => { - const zoomFactor = zoom / 100; + const zoomFactor = uiZoom / 100; const diff = (e.clientX - resizeStartState.startX) / zoomFactor; const newWidth = Math.max(150, Math.min(400, resizeStartState.startWidth + diff)); setSidebarWidth(newWidth); @@ -266,98 +682,139 @@ function App() { document.removeEventListener('mousemove', handleMouseMove); document.removeEventListener('mouseup', handleMouseUp); }; - }, [resizeStartState, zoom]); + }, [resizeStartState, uiZoom]); + + // Persist sidebar width to localStorage + useEffect(() => { + localStorage.setItem('vesper-sidebar-width', String(Math.round(sidebarWidth))); + }, [sidebarWidth]); + + // Persist UI zoom to localStorage + useEffect(() => { + localStorage.setItem('vesper-ui-zoom', String(Math.round(uiZoom))); + }, [uiZoom]); + + const osScrollbarOptions = { + scrollbars: { autoHide: 'scroll' as const, autoHideDelay: 800, theme: 'os-theme-dark' as const } + }; - const focusModeClass = focusMode ? 'focus-mode' : ''; - return ( -
- {isDraggingOver && ( -
-
-
📄
-
Drop markdown file here
-
-
- )} -
-
-
- Vesper -
-
-
- - - -
-
- -
-
{ menuItemRefs.current['file'] = el; }} onClick={() => setMenuOpen(prev => prev === 'file' ? null : 'file')}>File
-
{ menuItemRefs.current['view'] = el; }} onClick={() => setMenuOpen(prev => prev === 'view' ? null : 'view')}>View
-
{ menuItemRefs.current['help'] = el; }} onClick={() => setMenuOpen(prev => prev === 'help' ? null : 'help')}>Help
- - {menuOpen === 'file' && menuItemRefs.current['file'] && ( - - - -
- -
- )} - {menuOpen === 'view' && menuItemRefs.current['view'] && ( - - - - - - )} - {menuOpen === 'help' && menuItemRefs.current['help'] && ( - - - - - )} -
-
- +
- {tabs.length > 1 && ( - -
- {tabs.map(tab => ( -
{ setActiveTabId(tab.id); parseHeadings(tab.content); }}> - {tab.title} - -
- ))} + {isDraggingOver && ( + + + + + +
Drop markdown file here
+
.md, .markdown, or .txt
+
+
+ )} + + + + {!focusMode && ( + +
+
+ Vesper +
+
+
+ + +
)}
-
- {showSidebar && headings.length > 0 && ( -
-
-
Contents
- {headings.map(h => )} -
-
-
+ + {!focusMode && ( + +
{ menuItemRefs.current['file'] = el; }} onClick={() => setMenuOpen(prev => prev === 'file' ? null : 'file')}>File
+
{ menuItemRefs.current['view'] = el; }} onClick={() => setMenuOpen(prev => prev === 'view' ? null : 'view')}>View
+
{ menuItemRefs.current['help'] = el; }} onClick={() => setMenuOpen(prev => prev === 'help' ? null : 'help')}>Help
+ + {menuOpen === 'file' && menuItemRefs.current['file'] && ( + + + +
+ +
+ )} + {menuOpen === 'view' && menuItemRefs.current['view'] && ( + + + + +
+
e.stopPropagation()}> + UI Scale +
+ + {uiZoom}% + +
+
+
+ )} + {menuOpen === 'help' && menuItemRefs.current['help'] && ( + + + + + )} +
+
)} +
+ + + {tabs.length > 1 && ( + + +
+ + {tabs.map(tab => ( + { setActiveTabId(tab.id); parseHeadings(tab.content); }}> + {tab.title} + + + ))} + +
+ +
+ )} +
+ +
+ + {showSidebar && ( + + +
+
Contents
+ {headings.length > 0 + ? headings.map(h => ) + :
No headings
+ } +
+
+
+
+ )} +
{showSearch && activeTab && ( - - { setSearchQuery(e.target.value); }} onKeyDown={e => { if (e.key === 'Enter') { setSearchTriggerCount(c => c + 1); searchInputRef.current?.blur(); } }} /> + + { setSearchQuery(e.target.value); }} onKeyDown={e => { if (e.key === 'Enter') { e.preventDefault(); setCurrentMatchIndex(i => (i + 1) % Math.max(searchMatches.length, 1)); setSearchTriggerCount(c => c + 1); } }} autoFocus /> {searchMatches.length > 0 && ( <> @@ -370,50 +827,62 @@ function App() { )} - -
- {activeTab ? ( -
- ) : ( - - -

Vesper

-

A beautiful markdown reader

- -

Or drag and drop a markdown file here

-
- )} -
+ + +
+ {activeTab ? ( +
+ ) : ( + + +

Vesper

+

A beautiful markdown reader

+ +

Or drag and drop a markdown file here

+
+ )} +
+
{showShortcutsModal && ( - setShowShortcutsModal(false)}> - e.stopPropagation()}> -
-

Keyboard Shortcuts

- + setShowShortcutsModal(false)}> + e.stopPropagation()}> +
+

Keyboard Shortcuts

+
-
-

File

-
Open FileCtrl+O
-
Close TabCtrl+W
-
ExitCtrl+Q
+
+
+

File

+
Open FileCtrl+O
+
Close TabCtrl+W
+
ExitCtrl+Q
-

View

-
Toggle SearchCtrl+F
-
Toggle SidebarCtrl+Shift+S
-
Focus ModeF11
+
+

View

+
Toggle SearchCtrl+F
+
Toggle SidebarCtrl+Shift+S
+
Focus ModeF11
+
+
+

Navigation

+
+
Close Search / SidebarEsc
+
Zoom In / OutCtrl+Scroll
+
Content WidthShift+Scroll
+
)} {showAboutModal && ( - setShowAboutModal(false)}> - e.stopPropagation()}> + setShowAboutModal(false)}> + e.stopPropagation()}>

About Vesper

@@ -436,18 +905,36 @@ function App() { )} - {contextMenu && ( -
- - -
- - -
- - + {selectionRects.length > 0 && ( +
+ {selectionRects.map((r, i) => ( +
+ ))}
)} + + + {contextMenu && ( + + {contextMenu.selectedText && ( + <> + +
+ + )} + +
+ + + +
+ + +
+ +
+ )} +
); } diff --git a/src/main.tsx b/src/main.tsx index 9de8f43..3589701 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -3,15 +3,8 @@ import ReactDOM from "react-dom/client"; import App from "./App"; import "./styles.css"; -console.log("main.tsx loading..."); - -try { - ReactDOM.createRoot(document.getElementById("root")!).render( - - - , - ); - console.log("React rendered successfully"); -} catch (e) { - console.error("Error rendering:", e); -} +ReactDOM.createRoot(document.getElementById("root")!).render( + + + , +); diff --git a/src/styles.css b/src/styles.css index a261eac..b31a4dd 100644 --- a/src/styles.css +++ b/src/styles.css @@ -1,10 +1,11 @@ -@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap'); +@import "@fontsource-variable/inter"; +@import "@fontsource-variable/jetbrains-mono"; @import "tailwindcss"; @plugin "daisyui"; @plugin "@tailwindcss/typography"; @plugin "daisyui/theme" { - name: "darkmark"; + name: "vesper"; default: true; prefersdark: true; @@ -69,7 +70,7 @@ --color-border-strong: rgba(255, 255, 255, 0.12); /* Typography */ - font-family: 'Inter', -apple-system, 'SF Pro Display', 'Segoe UI', system-ui, sans-serif; + font-family: 'Inter Variable', 'Inter', -apple-system, 'SF Pro Display', 'Segoe UI', system-ui, sans-serif; font-size: 14px; line-height: 1.55; color: var(--color-text-primary); @@ -239,6 +240,7 @@ html, body, #root { border-bottom: 1px solid var(--color-border-subtle); padding: 0 12px; gap: 2px; + overflow: hidden; } .menu-item { @@ -259,6 +261,16 @@ html, body, #root { color: var(--color-text-primary); } +/* Menu backdrop — invisible overlay to catch outside clicks */ +.menu-backdrop { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + z-index: 99; +} + /* Menu dropdown */ .menu-dropdown { position: absolute; @@ -321,6 +333,52 @@ html, body, #root { font-weight: 700; } +/* Zoom spinner in View menu */ +.menu-dropdown-zoom { + display: flex; + align-items: center; + justify-content: space-between; + padding: 6px 12px; + color: var(--color-text-secondary); + font-size: 12px; + cursor: default; +} + +.zoom-spinner { + display: flex; + align-items: center; + gap: 2px; +} + +.zoom-spinner-btn { + display: flex; + align-items: center; + justify-content: center; + width: 22px; + height: 22px; + background: transparent; + border: 1px solid var(--color-border-subtle); + border-radius: var(--radius-sm); + color: var(--color-text-secondary); + cursor: pointer; + transition: all 0.15s ease; +} + +.zoom-spinner-btn:hover { + background-color: var(--color-bg-hover); + color: var(--color-text-primary); + border-color: var(--color-text-disabled); +} + +.zoom-spinner-value { + min-width: 38px; + text-align: center; + font-size: 11px; + font-weight: 500; + font-family: 'SF Mono', 'Menlo', monospace; + color: var(--color-text-primary); +} + /* Tab bar */ .tab-bar { display: flex; @@ -331,6 +389,32 @@ html, body, #root { overflow: hidden; } +.tab-scroll-arrow { + display: flex; + align-items: center; + justify-content: center; + width: 24px; + height: 100%; + background: transparent; + border: none; + color: var(--color-text-tertiary); + cursor: pointer; + flex-shrink: 0; + transition: all var(--transition-fast); + opacity: 0; + pointer-events: none; +} + +.tab-scroll-arrow.visible { + opacity: 1; + pointer-events: auto; +} + +.tab-scroll-arrow:hover { + color: var(--color-text-primary); + background-color: var(--color-bg-hover); +} + .tab-scroll-container { display: flex; align-items: center; @@ -338,35 +422,30 @@ html, body, #root { overflow-x: auto; overflow-y: hidden; gap: 2px; - padding: 0 8px; - scrollbar-width: thin; + padding: 0 4px; + flex: 1; + min-width: 0; + scrollbar-width: none; } .tab-scroll-container::-webkit-scrollbar { - height: 4px; -} - -.tab-scroll-container::-webkit-scrollbar-track { - background: transparent; -} - -.tab-scroll-container::-webkit-scrollbar-thumb { - background: var(--color-border); - border-radius: 2px; + display: none; } .tab { display: flex; align-items: center; - gap: 8px; + gap: 6px; height: 30px; - padding: 0 14px; + padding: 0 12px; background: transparent; border: none; border-radius: var(--radius-md); color: var(--color-text-secondary); cursor: pointer; white-space: nowrap; + flex-shrink: 0; + overflow: hidden; transition: all var(--transition-fast); } @@ -381,7 +460,6 @@ html, body, #root { } .tab-title { - max-width: 120px; overflow: hidden; text-overflow: ellipsis; font-size: 12px; @@ -400,6 +478,7 @@ html, body, #root { color: var(--color-text-tertiary); cursor: pointer; opacity: 0; + flex-shrink: 0; transition: all var(--transition-fast); } @@ -453,8 +532,6 @@ html, body, #root { min-height: 0; width: 100%; padding-bottom: 20px; - overflow-y: auto; - overflow-x: hidden; } .sidebar-resize-handle { @@ -485,6 +562,13 @@ html, body, #root { z-index: 1; } +.sidebar-empty { + padding: 12px 18px; + font-size: 12px; + color: var(--color-text-disabled); + font-style: italic; +} + .sidebar-item { display: block; width: 100%; @@ -531,11 +615,9 @@ html, body, #root { } .content-area-scroll { - height: 100%; + min-height: 100%; padding: 48px 64px; box-sizing: border-box; - overflow-y: auto; - overflow-x: hidden; } /* Search bar */ @@ -614,7 +696,8 @@ html, body, #root { left: 0; right: 0; bottom: 0; - background-color: rgba(30, 33, 40, 0.92); + background-color: rgba(20, 22, 28, 0.88); + backdrop-filter: blur(8px); display: flex; align-items: center; justify-content: center; @@ -625,30 +708,40 @@ html, body, #root { display: flex; flex-direction: column; align-items: center; - gap: 16px; - padding: 56px 80px; - border: 2px dashed var(--color-accent); - border-radius: var(--radius-lg); - background-color: var(--color-accent-subtle); + gap: 14px; + padding: 48px 72px; + border: 2px dashed rgba(107, 138, 255, 0.5); + border-radius: 16px; + background-color: rgba(107, 138, 255, 0.06); + box-shadow: 0 0 0 1px rgba(107, 138, 255, 0.1), 0 24px 64px rgba(0, 0, 0, 0.4); } -.drop-zone-icon { - font-size: 48px; +.drop-zone-icon-wrapper { + display: flex; + align-items: center; + justify-content: center; + width: 72px; + height: 72px; + border-radius: 50%; + background: rgba(107, 138, 255, 0.1); + color: var(--color-accent); + margin-bottom: 4px; } .drop-zone-text { - font-size: 15px; + font-size: 16px; color: var(--color-text-primary); - font-weight: 500; + font-weight: 600; + letter-spacing: -0.01em; } -/* Focus mode - hide title bar, tabs, and menu, but allow sidebar/search */ -.focus-mode .title-bar, -.focus-mode .tab-bar, -.focus-mode .menu-bar { - display: none !important; +.drop-zone-hint { + font-size: 12px; + color: var(--color-text-tertiary); + font-weight: 450; } +/* Focus mode - animated via framer-motion, extra padding for content */ .focus-mode .content-area-scroll { padding: 64px 96px; } @@ -662,7 +755,7 @@ html, body, #root { height: 100%; color: var(--color-text-secondary); text-align: center; - font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif; + font-family: 'Inter Variable', 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif; } .welcome-icon { @@ -768,6 +861,7 @@ html, body, #root { box-shadow: var(--shadow-modal); min-width: 360px; max-width: 480px; + width: 90vw; display: block; visibility: visible; opacity: 1; @@ -778,6 +872,103 @@ html, body, #root { z-index: 10003 !important; } +/* Shortcuts dialog — uses its own class to avoid DaisyUI .modal conflicts */ +.shortcuts-dialog { + background-color: var(--color-bg-elevated); + border: 1px solid var(--color-border); + border-radius: var(--radius-lg); + box-shadow: var(--shadow-modal); + width: 640px; + max-width: 92vw; + max-height: 80vh; + overflow-y: auto; + position: relative; + z-index: 10003; +} + +.shortcuts-dialog-header { + display: flex; + align-items: center; + justify-content: space-between; + padding: 20px 24px; + border-bottom: 1px solid var(--color-border-subtle); +} + +.shortcuts-dialog-title { + font-size: 16px; + font-weight: 600; + color: var(--color-text-primary); +} + +.shortcuts-dialog-close { + width: 28px; + height: 28px; + display: flex; + align-items: center; + justify-content: center; + background: transparent; + border: none; + border-radius: var(--radius-sm); + color: var(--color-text-tertiary); + cursor: pointer; + transition: all var(--transition-fast); +} + +.shortcuts-dialog-close:hover { + background-color: var(--color-bg-hover); + color: var(--color-text-primary); +} + +.shortcuts-dialog-body { + padding: 24px; + display: grid; + grid-template-columns: 1fr 1fr; + gap: 24px 32px; +} + +.shortcuts-dialog-group-title { + font-size: 11px; + font-weight: 600; + color: var(--color-accent); + text-transform: uppercase; + letter-spacing: 0.08em; + margin-bottom: 12px; + padding-bottom: 8px; + border-bottom: 1px solid var(--color-border-subtle); +} + +.shortcuts-dialog-item { + display: flex; + align-items: center; + justify-content: space-between; + padding: 7px 0; + font-size: 13px; + color: var(--color-text-secondary); + gap: 16px; +} + +.shortcuts-dialog-item kbd { + font-family: 'JetBrains Mono Variable', 'SF Mono', 'Menlo', monospace; + font-size: 11px; + padding: 4px 10px; + background-color: var(--color-bg-surface); + border: 1px solid var(--color-border); + border-radius: var(--radius-sm); + color: var(--color-text-primary); + white-space: nowrap; + flex-shrink: 0; +} + +.shortcuts-dialog-nav-grid { + grid-column: 1 / -1; +} + +.shortcuts-dialog-nav-grid .shortcuts-dialog-items-row { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 0 32px; +} + .modal-header { display: flex; align-items: center; @@ -873,6 +1064,24 @@ html, body, #root { margin-bottom: 8px; } +.shortcut { + display: flex; + align-items: center; + justify-content: space-between; + padding: 6px 0; + font-size: 13px; + color: var(--color-text-secondary); +} + +.shortcut kbd { + font-family: 'SF Mono', 'Menlo', monospace; + font-size: 11px; + padding: 3px 8px; + background-color: var(--color-bg-surface); + border-radius: var(--radius-sm); + color: var(--color-text-primary); +} + .shortcut-item { display: flex; align-items: center; @@ -901,7 +1110,7 @@ html, body, #root { /* Premium typography settings */ font-size: 17px; line-height: 1.7; - font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif; + font-family: 'Inter Variable', 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif; font-weight: 400; letter-spacing: -0.01em; font-feature-settings: "liga" 1, "kern" 1, "calt" 1; @@ -913,15 +1122,15 @@ html, body, #root { text-spacing-trim: trim-both; } -/* Refined heading hierarchy */ +/* Refined heading hierarchy — progressive color cascade */ .markdown-content h1, .markdown-content h2, .markdown-content h3, .markdown-content h4, .markdown-content h5, .markdown-content h6 { - color: var(--color-text-primary); - font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif; + color: #F0F1F3; + font-family: 'Inter Variable', 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif; font-weight: 600; line-height: 1.2; letter-spacing: -0.025em; @@ -933,7 +1142,8 @@ html, body, #root { .markdown-content h1 { font-size: 2.35rem; font-weight: 700; - letter-spacing: -0.04em; + letter-spacing: -0.03em; + color: #F5F6F7; margin-top: 0; margin-bottom: 0.8em; padding-bottom: 0.55em; @@ -942,6 +1152,7 @@ html, body, #root { .markdown-content h2 { font-size: 1.7rem; + color: #EDEEF0; margin-top: 1.85em; padding-bottom: 0.4em; border-bottom: 1px solid var(--color-border-subtle); @@ -949,22 +1160,25 @@ html, body, #root { .markdown-content h3 { font-size: 1.3rem; + color: #E4E5E8; } .markdown-content h4 { font-size: 1.15rem; font-weight: 500; + color: #D8DADE; } .markdown-content h5 { font-size: 1rem; font-weight: 500; + color: #CBCED3; } .markdown-content h6 { font-size: 0.92rem; font-weight: 500; - color: var(--color-text-secondary); + color: #ABB0B8; } /* Paragraphs with refined spacing */ @@ -974,18 +1188,19 @@ html, body, #root { line-height: 1.7; } -/* Links with subtle interaction */ +/* Links with subtle persistent underline */ .markdown-content a { color: #7CA9F7; - text-decoration: none; + text-decoration: underline; + text-decoration-color: rgba(124, 169, 247, 0.3); text-underline-offset: 3px; text-decoration-thickness: 1px; - transition: all 0.2s ease; + transition: color 0.2s ease, text-decoration-color 0.2s ease; } .markdown-content a:hover { color: #93B8F9; - text-decoration: underline; + text-decoration-color: rgba(147, 184, 249, 0.7); text-decoration-thickness: 1.5px; } @@ -1023,12 +1238,19 @@ html, body, #root { list-style-position: outside; } -.markdown-content ol li::marker { +.markdown-content li::marker { color: #7CA9F7; +} + +.markdown-content ol li::marker { font-weight: 500; font-size: 0.9em; } +.markdown-content ul li::marker { + font-size: 0.7em; +} + /* Fix ALL nested content inside list items - remove extra margins */ .markdown-content li > p { margin-top: 0; @@ -1098,18 +1320,17 @@ html, body, #root { .markdown-content blockquote { margin: 1.65em 0; padding: 1.1em 1.4em; - border-left: 5px solid #7CA9F7; - background-color: rgba(124, 169, 247, 0.05); - border-radius: 10px; - color: #A8ACB0; + border-left: 3px solid #7CA9F7; + background-color: rgba(124, 169, 247, 0.04); + border-radius: 0 8px 8px 0; + color: #B0B4B9; font-style: normal; line-height: 1.7; - box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1); } .markdown-content blockquote p { margin-bottom: 0.5em; - color: #A8ACB0; + color: #B0B4B9; } .markdown-content blockquote p:last-child { @@ -1118,13 +1339,13 @@ html, body, #root { /* Inline code */ .markdown-content code:not(pre code) { - background-color: rgba(124, 169, 247, 0.12); + background-color: rgba(255, 255, 255, 0.06); padding: 0.22em 0.48em; - border-radius: 4px; - font-family: 'JetBrains Mono', 'SF Mono', 'Fira Code', monospace; + border-radius: 5px; + font-family: 'JetBrains Mono Variable', 'JetBrains Mono', 'SF Mono', 'Fira Code', monospace; font-size: 0.88em; font-weight: 400; - color: #F79E7E; + color: #E0A88A; letter-spacing: -0.02em; } @@ -1142,8 +1363,8 @@ html, body, #root { } .markdown-content pre code { - font-family: 'JetBrains Mono', 'SF Mono', 'Fira Code', monospace; - font-size: 13px; + font-family: 'JetBrains Mono Variable', 'JetBrains Mono', 'SF Mono', 'Fira Code', monospace; + font-size: 14px; line-height: 1.7; color: #E4E6EB; background: none; @@ -1257,12 +1478,12 @@ html, body, #root { background-color: rgba(124, 169, 247, 0.05); } -/* Horizontal rule */ +/* Horizontal rule — gradient fade */ .markdown-content hr { border: none; height: 1px; - background-color: var(--color-border); - margin: 2.2em 0; + background: linear-gradient(to right, transparent, rgba(255, 255, 255, 0.1), transparent); + margin: 2.5em 0; } /* Images */ @@ -1335,6 +1556,41 @@ html, body, #root { border-radius: 8px; } +/* Search highlights — high specificity to override Tailwind reset */ +span.search-highlight { + background-color: rgba(251, 191, 36, 0.3) !important; + color: inherit !important; + border-radius: 2px; + padding: 1px 0; + transition: background-color 0.15s ease, outline-color 0.15s ease; +} + +span.search-highlight.search-highlight-active { + background-color: rgba(251, 191, 36, 0.6) !important; + outline: 2px solid rgba(251, 191, 36, 0.7); + outline-offset: 1px; +} + +/* OverlayScrollbars custom theme */ +.os-theme-dark { + --os-handle-bg: rgba(255, 255, 255, 0.12); + --os-handle-bg-hover: var(--color-accent); + --os-handle-bg-active: var(--color-accent-hover); + --os-size: 8px; + --os-handle-border-radius: 4px; + --os-padding-perpendicular: 2px; + --os-padding-axis: 2px; + --os-handle-interactive-area-offset: 4px; +} + +.os-theme-dark .os-scrollbar { + transition: opacity 0.3s ease; +} + +.os-theme-dark .os-scrollbar-handle { + transition: background-color 0.2s ease, opacity 0.2s ease; +} + /* Reduced motion */ @media (prefers-reduced-motion: reduce) { *, diff --git a/vite.config.ts b/vite.config.ts index a8960e3..b13ffe4 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -8,6 +8,9 @@ const host = process.env.TAURI_DEV_HOST; // https://vite.dev/config/ export default defineConfig(async () => ({ plugins: [react(), tailwindcss()], + build: { + chunkSizeWarningLimit: 1600, + }, // Vite options tailored for Tauri development and only applied in `tauri dev` or `tauri build` //