# Desktop Pet for Obsidian > **Project**: ![license](https://img.shields.io/badge/license-MIT-blue.svg) ![version](https://img.shields.io/badge/version-1.0.2-brightgreen.svg) ![obsidian](https://img.shields.io/badge/Obsidian-1.0+-blueviolet.svg) ![three.js](https://img.shields.io/badge/three.js-0.160-orange.svg) A cute **3D cartoon robot** desktop pet floating inside your Obsidian window. Built with parametric geometry (Sphere / Cylinder / Capsule / Torus), **zero external assets**, install and play. --- ## โœจ Features - ๐ŸŽจ **3D Cartoon Robot** โ€” Rounded head + dot eyes (with highlights) + antenna + chest core + blush, all parametric, no external assets - ๐Ÿ–ฑ๏ธ **Drag & Click** โ€” Drag with mouse, touch or stylus to reposition; click triggers scale pulse + happy eyes + speech bubble - ๐Ÿ’ค **Idle Animations** โ€” Breathing float, gentle sway, random blinks (2.5โ€“6s), arm swing, antenna wiggle - ๐ŸŽญ **5 Color Schemes** โ€” Soft Blue, Mint, Coral Pink, Lavender, Warm Orange - ๐Ÿ“ **Size Slider** โ€” 0.5x to 2.0x - ๐ŸŒ— **Theme Aware** โ€” Bubble & shadow use Obsidian CSS variables (`--background-secondary`, `--background-modifier-box-shadow`), auto-follow theme - ๐ŸŽš๏ธ **Status Bar Toggle** โ€” One-click show/hide, also via `Ctrl+P` command palette (status bar is desktop-only) - ๐Ÿ”Œ **Fully Transparent** โ€” Doesn't block Obsidian content (outer `pointer-events: none`, only pet is interactive) - โ™ป๏ธ **Full Lifecycle** โ€” `onunload` releases renderer / geometry / material / rAF / listeners ## ๐Ÿ“ธ Screenshots ![Settings panel โ€” pet floating in the bottom-right corner with a speech bubble](images/desktoppet.jpg) *Settings panel โ€” visibility toggle, size slider (1.25x), primary color, position X/Y and reset. The pet itself is visible in the bottom-right corner, showing a speech bubble.* ## โš™๏ธ Development Setup 1. **Install Node.js 18+** (20 LTS recommended) 2. **Clone or open the project**: ```bash cd obsidian-desktoppet npm install ``` 3. **Build commands**: ```bash npm run dev # watch mode: rebuild on save (non-minified, inline sourcemap) npm run build # production build: tsc type-check + minified main.js npm run build:dev # one-off development build (non-minified, inline sourcemap; exits when done) ``` ## ๐Ÿ“ฆ Install to Obsidian 1. Run `npm run build` to generate `main.js` 2. Open Obsidian โ†’ Settings โ†’ Third-party plugins โ†’ Disable Restricted Mode 3. Copy the **build artifacts only** into `.obsidian/plugins/desktoppet/`: ``` /.obsidian/plugins/desktoppet/ โ”œโ”€โ”€ main.js # required (build artifact) โ”œโ”€โ”€ manifest.json # required โ””โ”€โ”€ styles.css # required ``` Source `.ts` files are **not** needed in the plugin directory. 4. Restart Obsidian (or refresh the plugin list) 5. Search "Desktop Pet" in the plugin list and enable > **Note**: Works on both desktop and mobile (`isDesktopOnly: false`). Interaction is built on **Pointer Events**, so mouse, touch and stylus all drive the same drag / click logic. ## ๐ŸŽฎ Usage - **Command palette** โ€” `Ctrl+P` โ†’ search "Show/Hide Pet" - **Status bar** โ€” Robot icon in the bottom-right corner, click to toggle (desktop only) - **Drag** โ€” Press and drag the pet (mouse, touch or stylus) to any position - **Click** โ€” Click the pet body โ†’ scale pulse + happy eyes + speech bubble - **Settings** โ€” Settings โ†’ Community plugins โ†’ Desktop Pet - Visibility toggle - Pet size (0.5x โ€“ 2.0x) - Primary color (5 soft schemes) - Position X / Y (pixels) - Reset to default ## ๐Ÿ“š Documentation - [User Guide (English)](docs/en/UserGuide.md) - [็”จๆˆทไฝฟ็”จๆŒ‡ๅ—๏ผˆไธญๆ–‡๏ผ‰](docs/zh-cn/UserGuide.md) - [Changelog](CHANGELOG.md) ยท [Contributing](CONTRIBUTING.md) ยท [Security Policy](SECURITY.md) ## ๐Ÿ—‚๏ธ Project Structure ``` desktoppet/ โ”œโ”€โ”€ main.ts # Plugin entry (Plugin class + lifecycle + commands + status bar) โ”œโ”€โ”€ pet/ โ”‚ โ”œโ”€โ”€ PetScene.ts # Three.js scene (renderer / scene / camera / lights / rAF loop) โ”‚ โ”œโ”€โ”€ PetModel.ts # Parametric 3D robot model (geometry composition) โ”‚ โ”œโ”€โ”€ PetAnimator.ts # Animation controller (idle / blink / click / drag tilt) โ”‚ โ””โ”€โ”€ PetInteraction.ts # Interaction layer (Pointer Events drag + click) โ”œโ”€โ”€ settings/ โ”‚ โ””โ”€โ”€ SettingsTab.ts # Settings panel + DEFAULT_SETTINGS โ”œโ”€โ”€ styles.css # Floating container / bubble / status bar styles (theme aware) โ”œโ”€โ”€ manifest.json # Obsidian plugin manifest โ”œโ”€โ”€ package.json # npm dependencies โ”œโ”€โ”€ tsconfig.json # TypeScript config โ”œโ”€โ”€ esbuild.config.mjs # Build script โ”œโ”€โ”€ images/ # README screenshots โ””โ”€โ”€ README.md # This file ``` ## ๐Ÿ› ๏ธ Tech Stack - **Language** โ€” TypeScript (strict mode, no `any`) - **3D Rendering** โ€” Three.js `0.160.x` - **Build** โ€” esbuild + Obsidian standard plugin template - **Dependencies** โ€” Only `three` at runtime; everything else is dev-only ## ๐Ÿ” Key Implementation Details ### Primary Color Replacement `PetModel` tags the material it builds for the body/antenna/limbs with `material.userData.isPrimary = true`. `PetScene.applyColor()` walks the model and swaps exactly those materials to the target color โ€” nothing else is touched. No color-value heuristics ("white/dark/blush are not primary"): a heuristic both under-matches (the primary color could never be changed back if it happened to equal the detail color) and over-matches (any new auxiliary material of an unexpected shade would be repainted as primary). ### Click Pulse Click feedback (scale pulse + happy eyes + antenna bounce) is driven entirely by the animation loop's `elapsed` clock: ```ts scalePulse = 1 + Math.sin(t * Math.PI) * 0.3; group.scale.setScalar(scalePulse); ``` `PetAnimator.triggerClickFeedback()` reads the cached `lastElapsed` written by `update()`. It must **not** query `performance.now()` โ€” that clock counts from page load while the loop's `elapsed` counts from scene creation, so mixing the two made `elapsed - clickStartAt` permanently negative and the feedback never converged. The pet's visual size is controlled solely by the container / canvas size; the model itself always stays at 1:1 inside it. ### Speed-Aware Drag Tilt During drag, compute a tilt target from the X velocity (`-0.3 ~ +0.3` rad), then smooth it with a first-order low-pass filter: ```ts tiltCurrent += (tiltTarget - tiltCurrent) * Math.min(1, delta * 8); ``` The filter runs every frame (not only while dragging), so releasing the pointer lets the tilt decay back to zero instead of staying frozen at its last angle. ### Transparent Background - `WebGLRenderer({ alpha: true })` + `setClearColor(0x000000, 0)` - CSS container `pointer-events: none`, only inner canvas `pointer-events: auto` - Outer layer doesn't block Obsidian content interaction - While hidden, the canvas gets an explicit `pointer-events: none` โ€” `opacity: 0` alone still receives hits ## โš ๏ธ Known Limitations - On touch devices the pet area captures the pointer (`touch-action: none`), so scrolling / zooming gestures that *start on the pet itself* are suppressed by design โ€” drag it from elsewhere if you need to scroll - Position is clamped to the viewport, so the pet can never be dragged (or configured) off-screen; on a very small window a large pet will sit pinned to the top-left corner - Speech bubble appears at the top of the container; may be clipped if the pet is near the window top edge - No hover effect yet (could add slight scale + highlight boost) ## ๐Ÿš€ Future Directions - Hover effects - Multiple robot shapes (sphere / block / humanoid) - Emotion system (fall asleep after idle) - Custom keyboard shortcuts - Multi-touch gestures (e.g. pinch to resize) - More phrases, multi-language - Obsidian note integration (e.g. open new note on click, show last-modified time) ## ๐Ÿ“„ License MIT ยฉ xiaosong8584