Add polished UX: tabs, kinetic scroll, animations, search, typography

- Tabbed interface with duplicate detection, overflow scroll arrows,
  mouse wheel support, and animated enter/exit transitions
- Kinetic scrolling on right-mouse drag with iOS-style rubber band
  overscroll and damped spring snapback (content area and sidebar)
- Framer Motion animations on sidebar, context menu, drop zone,
  focus mode, tabs, and all modals
- Full-text search with real-time DOM highlighting and match navigation
- Custom OverlayScrollbars replacing native scrollbars
- Tauri native drag-and-drop replacing broken browser drag events
- UI scale control (View menu spinner) persisted to localStorage
- Content zoom (Ctrl+Scroll) and width adjustment (Shift+Scroll)
- Right-click context menu with copy, zoom-adjusted positioning,
  and boundary clamping
- Text selection preserved on right-click via visual overlay
- Focus mode hides title/menu bars with animated transitions
- Premium editorial typography: progressive heading color cascade,
  gradient-fade horizontal rules, accent-colored list markers,
  subtle persistent link underlines, refined blockquotes and code blocks
- Sidebar table of contents with "No headings" empty state
- Markdown breaks and typographer enabled
- New app icons and updated README with full feature documentation
This commit is contained in:
Your Name
2026-02-14 11:53:55 +02:00
parent 88a75a1fd9
commit 2af6b7ebe7
27 changed files with 2769 additions and 469 deletions

7
.gitignore vendored
View File

@@ -12,6 +12,13 @@ dist
dist-ssr dist-ssr
*.local *.local
# Tauri build artifacts
src-tauri/target/
# Dev tool files
icon-*.html
nul
# Editor directories and files # Editor directories and files
.vscode/* .vscode/*
!.vscode/extensions.json !.vscode/extensions.json

234
README.md
View File

@@ -7,78 +7,119 @@
<img src="https://img.shields.io/badge/license-CC0-green" alt="License"> <img src="https://img.shields.io/badge/license-CC0-green" alt="License">
</p> </p>
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 ## Features
### File Management ### Markdown Rendering
- **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
### Reading Experience - Full CommonMark support via **markdown-it** with extensions:
- **Premium Typography** - Carefully tuned typography with Inter font family for optimal readability - Syntax highlighting for 190+ languages (highlight.js)
- **Syntax Highlighting** - Code blocks are beautifully highlighted using highlight.js with a matching dark theme
- **Markdown Extensions** - Full support for:
- Task lists (checkboxes) - Task lists (checkboxes)
- Superscript and subscript - Superscript and subscript (`^text^`, `~text~`)
- Strikethrough and highlighting - ==Highlighted text== via `mark`
- Tables with elegant styling - Smart quotes, em-dashes, and ellipses (typographer)
- Blockquotes with visual depth - 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 ### 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 - **Table of Contents sidebar** auto-generated from document headings (H1-H6), with indentation by level
- **In-Document Search** - Press `Ctrl+F` to search within the current document - Click any heading in the sidebar to smooth-scroll to that section
- **Match Navigation** - Navigate between matches with previous/next buttons - Sidebar opens even when a document has no headings (shows "No headings" message)
- **Match Counter** - See how many matches were found - 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 ### Reading Experience
- **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
### 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 | | Shortcut | Action |
|----------|--------| |---|---|
| `Ctrl+O` | Open file | | `Ctrl+O` | Open file |
| `Ctrl+W` | Close current tab | | `Ctrl+W` | Close current tab |
| `Ctrl+Q` | Exit application | | `Ctrl+Q` | Exit application |
| `Ctrl+F` | Toggle search | | `Ctrl+F` | Toggle search |
| `Ctrl+Shift+S` | Toggle sidebar | | `Ctrl+Shift+S` | Toggle sidebar |
| `F11` | Toggle focus mode | | `F11` | Toggle focus mode |
| `Escape` | Close search or sidebar | | `Escape` | Close search / sidebar |
| `Ctrl+Scroll` | Zoom content in / out |
### Context Menu | `Shift+Scroll` | Adjust content width |
Right-click anywhere in the content area for quick access to:
- Open File
- Find (search)
- Toggle Sidebar
- Focus Mode
- Keyboard Shortcuts
- About
--- ---
## Installation ## Installation
### Prerequisites ### Prerequisites
- Windows 10 or later - Windows 10 or later
- WebView2 Runtime (usually pre-installed on Windows 10/11) - WebView2 Runtime (pre-installed on Windows 10/11)
### Download ### 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 ### 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 ```bash
# Clone the repository # Clone the repository
git clone https://github.com/yourusername/vesper.git git clone https://github.com/yourusername/vesper.git
@@ -87,98 +128,95 @@ cd vesper
# Install dependencies # Install dependencies
npm install npm install
# Run in development mode # Run in development mode (hot-reload on localhost:1420)
npm run tauri dev npm run tauri dev
# Build for production # Build for production
npm run tauri build 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 ## Technology Stack
- **Framework**: [Tauri 2.0](https://tauri.app/) - Lightweight, secure desktop framework | Layer | Technology |
- **Frontend**: React 19 + TypeScript + Vite |---|---|
- **Styling**: Tailwind CSS v4 + daisyUI v5 | Runtime | [Tauri v2](https://tauri.app/) (Rust + WebView2) |
- **Markdown**: [markdown-it](https://github.com/markdown-it/markdown-it) with plugins | Frontend | React 19, TypeScript, Vite 7 |
- **Syntax Highlighting**: [highlight.js](https://highlightjs.org/) | Styling | Tailwind CSS 4, DaisyUI 5, custom CSS |
- **Animations**: [Framer Motion](https://www.framer.com/motion/) | Markdown | [markdown-it](https://github.com/markdown-it/markdown-it) with plugins (task-lists, sup, sub, mark) |
- **Icons**: [Lucide React](https://lucide.dev/) | 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 ## Design Philosophy
Vesper was designed with these principles in mind: 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
1. **Content First** - The reader is the primary focus. All UI elements can be hidden. 3. **Dark by Default** -- deep onyx base color (#282C33) reduces eye strain
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. 4. **Keyboard Driven** -- every feature accessible via keyboard shortcuts
3. **Dark by Default** - The deep onyx base color (#282C33) reduces eye strain and looks professional. 5. **Minimalist Chrome** -- the interface gets out of your way
4. **Keyboard Driven** - Power users can do everything with keyboard shortcuts.
5. **Minimalist Chrome** - The interface gets out of your way.
--- ---
## Color Palette ## Color Palette
| Color | Hex | Usage | | Color | Hex | Usage |
|-------|-----|-------| |---|---|---|
| Base | `#282C33` | Main window background | | Base | `#282C33` | Main window background |
| Surface | `#2D323B` | Elevated elements, cards | | Surface | `#252A31` | Title bar, sidebar |
| Overlay | `#333942` | Dropdowns, modals | | Elevated | `#2D333C` | Active tab, modals |
| Overlay | `#333942` | Dropdowns, context menu |
| Text Primary | `#E4E6EB` | Main content | | Text Primary | `#E4E6EB` | Main content |
| Text Secondary | `#ABB0B8` | UI labels | | Text Secondary | `#ABB0B8` | UI labels, muted text |
| Accent | `#6B8AFF` | Links, highlights | | Text Tertiary | `#6B7280` | Placeholders, hints |
| Accent | `#6B8AFF` | Interactive elements, highlights |
| Link | `#7CA9F7` | Hyperlinks |
--- ---
## Project Structure ## Project Structure
``` ```
vesper/ md-reader/
├── src/ src/
├── App.tsx # Main application component App.tsx # Main application component (all UI logic)
├── main.tsx # Entry point main.tsx # React entry point
└── styles.css # All styling styles.css # All styles -- variables, UI components, markdown typography
├── src-tauri/ src-tauri/
├── src/ src/
└── main.rs # Rust backend main.rs # Rust entry point
│ ├── Cargo.toml lib.rs # Tauri builder and plugin registration
└── tauri.conf.json # Tauri configuration tauri.conf.json # Tauri window, bundle, and build configuration
├── index.html Cargo.toml # Rust dependencies
├── package.json icons/ # App icons (ICO, PNG, ICNS)
└── README.md 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 ## 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 - [iA Writer](https://ia.net/writer) for design inspiration
- [Tauri](https://tauri.app/) for the excellent desktop framework - [Tauri](https://tauri.app/) for the excellent desktop framework
- [markdown-it](https://github.com/markdown-it/markdown-it) for robust markdown parsing - [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

1619
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -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":{"@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"}}
"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"
}
}

28
src-tauri/Cargo.lock generated
View File

@@ -596,20 +596,6 @@ dependencies = [
"syn 2.0.115", "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]] [[package]]
name = "darling" name = "darling"
version = "0.21.3" version = "0.21.3"
@@ -4308,6 +4294,20 @@ version = "0.9.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" 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]] [[package]]
name = "vswhom" name = "vswhom"
version = "0.1.0" version = "0.1.0"

View File

@@ -1,5 +1,5 @@
[package] [package]
name = "darkmark" name = "vesper"
version = "0.1.0" version = "0.1.0"
description = "A beautiful markdown reader" description = "A beautiful markdown reader"
authors = ["you"] authors = ["you"]
@@ -8,7 +8,7 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[lib] [lib]
name = "darkmark_lib" name = "vesper_lib"
crate-type = ["staticlib", "cdylib", "rlib"] crate-type = ["staticlib", "cdylib", "rlib"]
[build-dependencies] [build-dependencies]

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 KiB

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.8 KiB

After

Width:  |  Height:  |  Size: 63 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 974 B

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.9 KiB

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.6 KiB

After

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 903 B

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.4 KiB

After

Width:  |  Height:  |  Size: 85 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

After

Width:  |  Height:  |  Size: 9.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 85 KiB

After

Width:  |  Height:  |  Size: 279 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 198 KiB

View File

@@ -2,5 +2,5 @@
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
fn main() { fn main() {
darkmark_lib::run() vesper_lib::run()
} }

View File

@@ -21,7 +21,7 @@
"transparent": false, "transparent": false,
"resizable": true, "resizable": true,
"center": true, "center": true,
"dragDropEnabled": false "dragDropEnabled": true
} }
], ],
"security": { "security": {

File diff suppressed because it is too large Load Diff

View File

@@ -3,15 +3,8 @@ import ReactDOM from "react-dom/client";
import App from "./App"; import App from "./App";
import "./styles.css"; import "./styles.css";
console.log("main.tsx loading..."); ReactDOM.createRoot(document.getElementById("root")!).render(
<React.StrictMode>
try { <App />
ReactDOM.createRoot(document.getElementById("root")!).render( </React.StrictMode>,
<React.StrictMode> );
<App />
</React.StrictMode>,
);
console.log("React rendered successfully");
} catch (e) {
console.error("Error rendering:", e);
}

View File

@@ -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"; @import "tailwindcss";
@plugin "daisyui"; @plugin "daisyui";
@plugin "@tailwindcss/typography"; @plugin "@tailwindcss/typography";
@plugin "daisyui/theme" { @plugin "daisyui/theme" {
name: "darkmark"; name: "vesper";
default: true; default: true;
prefersdark: true; prefersdark: true;
@@ -69,7 +70,7 @@
--color-border-strong: rgba(255, 255, 255, 0.12); --color-border-strong: rgba(255, 255, 255, 0.12);
/* Typography */ /* 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; font-size: 14px;
line-height: 1.55; line-height: 1.55;
color: var(--color-text-primary); color: var(--color-text-primary);
@@ -239,6 +240,7 @@ html, body, #root {
border-bottom: 1px solid var(--color-border-subtle); border-bottom: 1px solid var(--color-border-subtle);
padding: 0 12px; padding: 0 12px;
gap: 2px; gap: 2px;
overflow: hidden;
} }
.menu-item { .menu-item {
@@ -259,6 +261,16 @@ html, body, #root {
color: var(--color-text-primary); 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 */
.menu-dropdown { .menu-dropdown {
position: absolute; position: absolute;
@@ -321,6 +333,52 @@ html, body, #root {
font-weight: 700; 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 */
.tab-bar { .tab-bar {
display: flex; display: flex;
@@ -331,6 +389,32 @@ html, body, #root {
overflow: hidden; 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 { .tab-scroll-container {
display: flex; display: flex;
align-items: center; align-items: center;
@@ -338,35 +422,30 @@ html, body, #root {
overflow-x: auto; overflow-x: auto;
overflow-y: hidden; overflow-y: hidden;
gap: 2px; gap: 2px;
padding: 0 8px; padding: 0 4px;
scrollbar-width: thin; flex: 1;
min-width: 0;
scrollbar-width: none;
} }
.tab-scroll-container::-webkit-scrollbar { .tab-scroll-container::-webkit-scrollbar {
height: 4px; display: none;
}
.tab-scroll-container::-webkit-scrollbar-track {
background: transparent;
}
.tab-scroll-container::-webkit-scrollbar-thumb {
background: var(--color-border);
border-radius: 2px;
} }
.tab { .tab {
display: flex; display: flex;
align-items: center; align-items: center;
gap: 8px; gap: 6px;
height: 30px; height: 30px;
padding: 0 14px; padding: 0 12px;
background: transparent; background: transparent;
border: none; border: none;
border-radius: var(--radius-md); border-radius: var(--radius-md);
color: var(--color-text-secondary); color: var(--color-text-secondary);
cursor: pointer; cursor: pointer;
white-space: nowrap; white-space: nowrap;
flex-shrink: 0;
overflow: hidden;
transition: all var(--transition-fast); transition: all var(--transition-fast);
} }
@@ -381,7 +460,6 @@ html, body, #root {
} }
.tab-title { .tab-title {
max-width: 120px;
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
font-size: 12px; font-size: 12px;
@@ -400,6 +478,7 @@ html, body, #root {
color: var(--color-text-tertiary); color: var(--color-text-tertiary);
cursor: pointer; cursor: pointer;
opacity: 0; opacity: 0;
flex-shrink: 0;
transition: all var(--transition-fast); transition: all var(--transition-fast);
} }
@@ -453,8 +532,6 @@ html, body, #root {
min-height: 0; min-height: 0;
width: 100%; width: 100%;
padding-bottom: 20px; padding-bottom: 20px;
overflow-y: auto;
overflow-x: hidden;
} }
.sidebar-resize-handle { .sidebar-resize-handle {
@@ -485,6 +562,13 @@ html, body, #root {
z-index: 1; z-index: 1;
} }
.sidebar-empty {
padding: 12px 18px;
font-size: 12px;
color: var(--color-text-disabled);
font-style: italic;
}
.sidebar-item { .sidebar-item {
display: block; display: block;
width: 100%; width: 100%;
@@ -531,11 +615,9 @@ html, body, #root {
} }
.content-area-scroll { .content-area-scroll {
height: 100%; min-height: 100%;
padding: 48px 64px; padding: 48px 64px;
box-sizing: border-box; box-sizing: border-box;
overflow-y: auto;
overflow-x: hidden;
} }
/* Search bar */ /* Search bar */
@@ -614,7 +696,8 @@ html, body, #root {
left: 0; left: 0;
right: 0; right: 0;
bottom: 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; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
@@ -625,30 +708,40 @@ html, body, #root {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
align-items: center; align-items: center;
gap: 16px; gap: 14px;
padding: 56px 80px; padding: 48px 72px;
border: 2px dashed var(--color-accent); border: 2px dashed rgba(107, 138, 255, 0.5);
border-radius: var(--radius-lg); border-radius: 16px;
background-color: var(--color-accent-subtle); 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 { .drop-zone-icon-wrapper {
font-size: 48px; 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 { .drop-zone-text {
font-size: 15px; font-size: 16px;
color: var(--color-text-primary); 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 */ .drop-zone-hint {
.focus-mode .title-bar, font-size: 12px;
.focus-mode .tab-bar, color: var(--color-text-tertiary);
.focus-mode .menu-bar { font-weight: 450;
display: none !important;
} }
/* Focus mode - animated via framer-motion, extra padding for content */
.focus-mode .content-area-scroll { .focus-mode .content-area-scroll {
padding: 64px 96px; padding: 64px 96px;
} }
@@ -662,7 +755,7 @@ html, body, #root {
height: 100%; height: 100%;
color: var(--color-text-secondary); color: var(--color-text-secondary);
text-align: center; 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 { .welcome-icon {
@@ -768,6 +861,7 @@ html, body, #root {
box-shadow: var(--shadow-modal); box-shadow: var(--shadow-modal);
min-width: 360px; min-width: 360px;
max-width: 480px; max-width: 480px;
width: 90vw;
display: block; display: block;
visibility: visible; visibility: visible;
opacity: 1; opacity: 1;
@@ -778,6 +872,103 @@ html, body, #root {
z-index: 10003 !important; 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 { .modal-header {
display: flex; display: flex;
align-items: center; align-items: center;
@@ -873,6 +1064,24 @@ html, body, #root {
margin-bottom: 8px; 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 { .shortcut-item {
display: flex; display: flex;
align-items: center; align-items: center;
@@ -901,7 +1110,7 @@ html, body, #root {
/* Premium typography settings */ /* Premium typography settings */
font-size: 17px; font-size: 17px;
line-height: 1.7; 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; font-weight: 400;
letter-spacing: -0.01em; letter-spacing: -0.01em;
font-feature-settings: "liga" 1, "kern" 1, "calt" 1; font-feature-settings: "liga" 1, "kern" 1, "calt" 1;
@@ -913,15 +1122,15 @@ html, body, #root {
text-spacing-trim: trim-both; text-spacing-trim: trim-both;
} }
/* Refined heading hierarchy */ /* Refined heading hierarchy — progressive color cascade */
.markdown-content h1, .markdown-content h1,
.markdown-content h2, .markdown-content h2,
.markdown-content h3, .markdown-content h3,
.markdown-content h4, .markdown-content h4,
.markdown-content h5, .markdown-content h5,
.markdown-content h6 { .markdown-content h6 {
color: var(--color-text-primary); color: #F0F1F3;
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: 600; font-weight: 600;
line-height: 1.2; line-height: 1.2;
letter-spacing: -0.025em; letter-spacing: -0.025em;
@@ -933,7 +1142,8 @@ html, body, #root {
.markdown-content h1 { .markdown-content h1 {
font-size: 2.35rem; font-size: 2.35rem;
font-weight: 700; font-weight: 700;
letter-spacing: -0.04em; letter-spacing: -0.03em;
color: #F5F6F7;
margin-top: 0; margin-top: 0;
margin-bottom: 0.8em; margin-bottom: 0.8em;
padding-bottom: 0.55em; padding-bottom: 0.55em;
@@ -942,6 +1152,7 @@ html, body, #root {
.markdown-content h2 { .markdown-content h2 {
font-size: 1.7rem; font-size: 1.7rem;
color: #EDEEF0;
margin-top: 1.85em; margin-top: 1.85em;
padding-bottom: 0.4em; padding-bottom: 0.4em;
border-bottom: 1px solid var(--color-border-subtle); border-bottom: 1px solid var(--color-border-subtle);
@@ -949,22 +1160,25 @@ html, body, #root {
.markdown-content h3 { .markdown-content h3 {
font-size: 1.3rem; font-size: 1.3rem;
color: #E4E5E8;
} }
.markdown-content h4 { .markdown-content h4 {
font-size: 1.15rem; font-size: 1.15rem;
font-weight: 500; font-weight: 500;
color: #D8DADE;
} }
.markdown-content h5 { .markdown-content h5 {
font-size: 1rem; font-size: 1rem;
font-weight: 500; font-weight: 500;
color: #CBCED3;
} }
.markdown-content h6 { .markdown-content h6 {
font-size: 0.92rem; font-size: 0.92rem;
font-weight: 500; font-weight: 500;
color: var(--color-text-secondary); color: #ABB0B8;
} }
/* Paragraphs with refined spacing */ /* Paragraphs with refined spacing */
@@ -974,18 +1188,19 @@ html, body, #root {
line-height: 1.7; line-height: 1.7;
} }
/* Links with subtle interaction */ /* Links with subtle persistent underline */
.markdown-content a { .markdown-content a {
color: #7CA9F7; color: #7CA9F7;
text-decoration: none; text-decoration: underline;
text-decoration-color: rgba(124, 169, 247, 0.3);
text-underline-offset: 3px; text-underline-offset: 3px;
text-decoration-thickness: 1px; text-decoration-thickness: 1px;
transition: all 0.2s ease; transition: color 0.2s ease, text-decoration-color 0.2s ease;
} }
.markdown-content a:hover { .markdown-content a:hover {
color: #93B8F9; color: #93B8F9;
text-decoration: underline; text-decoration-color: rgba(147, 184, 249, 0.7);
text-decoration-thickness: 1.5px; text-decoration-thickness: 1.5px;
} }
@@ -1023,12 +1238,19 @@ html, body, #root {
list-style-position: outside; list-style-position: outside;
} }
.markdown-content ol li::marker { .markdown-content li::marker {
color: #7CA9F7; color: #7CA9F7;
}
.markdown-content ol li::marker {
font-weight: 500; font-weight: 500;
font-size: 0.9em; font-size: 0.9em;
} }
.markdown-content ul li::marker {
font-size: 0.7em;
}
/* Fix ALL nested content inside list items - remove extra margins */ /* Fix ALL nested content inside list items - remove extra margins */
.markdown-content li > p { .markdown-content li > p {
margin-top: 0; margin-top: 0;
@@ -1098,18 +1320,17 @@ html, body, #root {
.markdown-content blockquote { .markdown-content blockquote {
margin: 1.65em 0; margin: 1.65em 0;
padding: 1.1em 1.4em; padding: 1.1em 1.4em;
border-left: 5px solid #7CA9F7; border-left: 3px solid #7CA9F7;
background-color: rgba(124, 169, 247, 0.05); background-color: rgba(124, 169, 247, 0.04);
border-radius: 10px; border-radius: 0 8px 8px 0;
color: #A8ACB0; color: #B0B4B9;
font-style: normal; font-style: normal;
line-height: 1.7; line-height: 1.7;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
} }
.markdown-content blockquote p { .markdown-content blockquote p {
margin-bottom: 0.5em; margin-bottom: 0.5em;
color: #A8ACB0; color: #B0B4B9;
} }
.markdown-content blockquote p:last-child { .markdown-content blockquote p:last-child {
@@ -1118,13 +1339,13 @@ html, body, #root {
/* Inline code */ /* Inline code */
.markdown-content code:not(pre 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; padding: 0.22em 0.48em;
border-radius: 4px; border-radius: 5px;
font-family: 'JetBrains Mono', 'SF Mono', 'Fira Code', monospace; font-family: 'JetBrains Mono Variable', 'JetBrains Mono', 'SF Mono', 'Fira Code', monospace;
font-size: 0.88em; font-size: 0.88em;
font-weight: 400; font-weight: 400;
color: #F79E7E; color: #E0A88A;
letter-spacing: -0.02em; letter-spacing: -0.02em;
} }
@@ -1142,8 +1363,8 @@ html, body, #root {
} }
.markdown-content pre code { .markdown-content pre code {
font-family: 'JetBrains Mono', 'SF Mono', 'Fira Code', monospace; font-family: 'JetBrains Mono Variable', 'JetBrains Mono', 'SF Mono', 'Fira Code', monospace;
font-size: 13px; font-size: 14px;
line-height: 1.7; line-height: 1.7;
color: #E4E6EB; color: #E4E6EB;
background: none; background: none;
@@ -1257,12 +1478,12 @@ html, body, #root {
background-color: rgba(124, 169, 247, 0.05); background-color: rgba(124, 169, 247, 0.05);
} }
/* Horizontal rule */ /* Horizontal rule — gradient fade */
.markdown-content hr { .markdown-content hr {
border: none; border: none;
height: 1px; height: 1px;
background-color: var(--color-border); background: linear-gradient(to right, transparent, rgba(255, 255, 255, 0.1), transparent);
margin: 2.2em 0; margin: 2.5em 0;
} }
/* Images */ /* Images */
@@ -1335,6 +1556,41 @@ html, body, #root {
border-radius: 8px; 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 */ /* Reduced motion */
@media (prefers-reduced-motion: reduce) { @media (prefers-reduced-motion: reduce) {
*, *,

View File

@@ -8,6 +8,9 @@ const host = process.env.TAURI_DEV_HOST;
// https://vite.dev/config/ // https://vite.dev/config/
export default defineConfig(async () => ({ export default defineConfig(async () => ({
plugins: [react(), tailwindcss()], plugins: [react(), tailwindcss()],
build: {
chunkSizeWarningLimit: 1600,
},
// Vite options tailored for Tauri development and only applied in `tauri dev` or `tauri build` // Vite options tailored for Tauri development and only applied in `tauri dev` or `tauri build`
// //