<div align="center">

# Jellybloom

**A desktop media client for Jellyfin that actually feels good to use.**

Built for people who run their own servers. No cloud, no accounts, no subscriptions, no gatekeepers between you and your library.

<br>

<img src="https://img.shields.io/badge/version-0.1.0-6B8AFF?style=flat-square&labelColor=282C33" alt="Version">
<img src="https://img.shields.io/badge/platform-Windows-6B8AFF?style=flat-square&labelColor=282C33" alt="Platform">
<img src="https://img.shields.io/badge/React-19-61DAFB?style=flat-square&logo=react&logoColor=white" alt="React">
<img src="https://img.shields.io/badge/Tauri-v2-24C8D8?style=flat-square&logo=tauri&logoColor=white" alt="Tauri">
<img src="https://img.shields.io/badge/license-CC0_Public_Domain-6B8AFF?style=flat-square&labelColor=282C33" alt="License">

</div>

<br>

## 🏴 Why Jellybloom?

You already run a Jellyfin server. You already own the media. So why does using it still feel like using a web app designed by committee?

Jellybloom is a ground-up desktop client for Windows. It talks directly to your Jellyfin server and makes browsing, discovering, and watching actually pleasant. Fast, dense, responsive, and fully under your control. No cloud dependencies. No data collection. No feature gates. No one profits from your watch history except you.

<br>

## 🎬 What it does

A few things worth calling out:

- Direct Jellyfin server connection with support for multiple servers and a live dashboard
- TMDB integration for metadata, backdrops, posters, logos, and the entire Discover section
- OpenSubtitles built into the player - search, download, and adjust style without leaving the video
- Sonarr and Radarr request buttons on every title page, with status tracking
- Trakt.tv scrobbling via device-code auth, no browser redirects
- Fanart.tv for high-quality artwork when Jellyfin or TMDB come up short
- Full ASS and SSA subtitle rendering through libass-wasm, plus SRT, WebVTT, and text
- Audio graph with compressor and parametric EQ, plus passthrough to your receiver
- Picture filters for brightness, contrast, saturation, hue, and gamma on badly-encoded files
- Virtualized poster grids that stay smooth with thousands of items
- Fuzzy search across titles, people, and collections with saved search history
- Watch diary with ratings, rewatch counts, notes, and export to Markdown, JSON, or Letterboxd CSV
- Bookmarks with notes, A-B looping, trickplay thumbnails, sleep timer, and a "still watching?" check
- SyncPlay groups and casting to other Jellyfin sessions on your network
- Fully rebindable keyboard shortcuts and full keyboard navigation throughout

### 📺 Browse

The home page is built from shelves you curate yourself, or you can let the app suggest rows based on what you actually watch. The poster grid uses virtualized scrolling so it stays smooth even with thousands of items. Right-click any poster for a quick look without losing your place. A notification bell tracks new episodes and movies, deduplicated by series so a full-season drop does not flood you. Search is fuzzy and unified across titles, people, and collections, and saved searches stick around if you find yourself looking for the same things repeatedly.

### 🎬 The player

This is where most of the work went. The player is built on Vidstack with hls.js, and the entire chrome layer is custom. ASS and SSA subtitles render natively through libass-wasm so styled fansubs look exactly as intended, with SRT, WebVTT, and plain text supported too. You can search OpenSubtitles from inside the player, tweak size, font, outline, color, background opacity, and positioning. A picture filter adjusts brightness, contrast, saturation, hue, and gamma for badly-encoded sources. An audio graph gives you a compressor and parametric EQ. Audio passthrough lets your receiver handle decoding when it can.

Beyond the basics: bookmarks with notes shown as dots on the scrubber, A-B looping, chapter navigation, trickplay thumbnails, speed control from 0.5x to 3x, a sleep timer, and a "still watching?" prompt that checks in after a few episodes so your stats do not run away. The resume prompt asks first so you do not spoil a cold open. The end card shows what is next as the credits roll. Pop out a mini player to keep watching while doing other things. Cast to another Jellyfin session on your network, or join a SyncPlay group to watch together with friends. Trakt.tv scrobbling is optional and uses device-code auth, no redirects. Every keyboard action can be rebound.

### 📄 Detail pages

Every title gets a rich detail page that pulls from multiple sources. The hero shows a backdrop, poster, and logo overlay from Jellyfin or TMDB, plus ratings and action buttons. Cast and crew are clickable, with in-library matches highlighted in a person's filmography. Awards come from Wikidata. Filming locations are mapped with Leaflet. Reception aggregates IMDb, TMDB, Rotten Tomatoes, Metacritic, and your own ratings. Reviews, trailers, trivia, and tech specs round it out, with badge icons for Dolby Vision, Atmos, DTS:X, and others. Anime filler detection flags filler and mixed-canon episodes. If you have multiple versions of a file, a selector lets you pick. The watch timeline draws from your diary. Your ratings, rewatch counts, and notes stay local. The diary lets you log watches and export to Markdown, JSON, or Letterboxd CSV. If something is missing, you can request it from Radarr or Sonarr without leaving the page.

### 🔭 Discover

Find things you do not already own. Needs a free TMDB API key. There is a personalized spotlight based on your top genre, a trending pick, mood chips that resolve to filtered rows, a roulette for random highly-rated titles, decade strips, canonical lists, an "on this day" feature, and a library gap finder that shows the highest-rated missing titles in genres you already watch. Browse by genre, language, studio, or network, with inline expansion and advanced filters for year range, rating, vote count, and sort order.

### 👤 Profile and stats

Track your watch streak, genre breakdown, year-in-review summary, personally top-rated titles, and recent diary entries.

### ⚙️ Settings

Connect to multiple Jellyfin servers with a live dashboard. Configure playback, audio, subtitle, and display preferences. Toggle home page rows and detail page sections. Set up TMDB, Fanart.tv, Trakt.tv, Sonarr, and Radarr. Manage personal data, privacy controls, and rebind every keyboard shortcut.

<br>

## ♿ Accessibility

Jellybloom targets WCAG 2.2 AAA conformance because a media client that only works for some people is not really a media client.

| Area | Details |
|:-----|:--------|
| **Semantic HTML** | Proper landmarks, ARIA roles for menus, dialogs, tabs, and status regions |
| **Full keyboard navigation** | Every interactive element reachable and operable without a mouse |
| **Focus management** | Visible focus indicators on all controls, focus traps in modals with restore on close |
| **High contrast** | All themes meet AAA contrast ratios (7:1+ for normal text) |
| **Reduced motion** | Respects prefers-reduced-motion - all animations disabled when active |
| **Screen readers** | ARIA live regions for dynamic content, proper labels on icon-only buttons |
| **Touch targets** | All interactive controls meet 24x24px minimum |

<br>

## 📦 Getting started

### Prerequisites

- Windows 10 or later
- WebView2 Runtime (pre-installed on Windows 10/11)
- Node.js 18+
- Rust (latest stable)
- Tauri v2 prerequisites

### Development

```bash
git clone https://git.lashman.live/lashman/jellybloom.git
cd jellybloom
npm install
```

```bash
npm run dev          # browser dev
npm run tauri:dev    # desktop dev
npm run build        # production web build
npm run tauri:build  # production desktop build
```

<br>

## 🔧 Technology stack

| | Layer | Technology |
|:--|:--|:--|
| **Runtime** | Desktop shell | [Tauri v2](https://tauri.app/) |
| **Frontend** | UI framework | React 19, TypeScript, Vite |
| **Styling** | CSS | Tailwind CSS 4 |
| **State** | Management | Zustand |
| **Data** | Fetching and caching | TanStack Query |
| **Lists** | Virtualization | TanStack Virtual |
| **Playback** | Media engine | Vidstack, hls.js |
| **Subtitles** | Rendering | libass-wasm |
| **Animation** | Transitions | Framer Motion |
| **Maps** | Locations | Leaflet |
| **Search** | Fuzzy matching | Fuse.js |
| **Primitives** | Accessibility | Radix UI |

<br>

## 🗂️ Project structure

```
jellybloom/
├── src/
│   ├── api/                 API clients (Jellyfin, TMDB, Fanart, etc.)
│   ├── components/          UI components (player, detail, discover, layout)
│   ├── hooks/               Data hooks and player logic
│   ├── lib/                 Utilities, formatters, device profile
│   ├── pages/               Route pages
│   ├── stores/              Zustand stores
│   └── types/               TypeScript declarations
├── src-tauri/               Rust backend
│   ├── src/
│   ├── capabilities/
│   └── icons/
├── public/                  Static assets
├── package.json
├── vite.config.ts
└── tsconfig.json
```

<br>

## 📜 License

Dedicated to the public domain under [CC0 1.0 Universal](LICENSE).

No copyright, no restrictions, no permission needed. Take it, use it, change it, share it. The culture belongs to everyone.
