# dsh-gif-background

[![CI](https://github.com/alcohol-101/dsh-gif-background/actions/workflows/ci.yml/badge.svg)](https://github.com/alcohol-101/dsh-gif-background/actions/workflows/ci.yml)

[English](README.md) | [中文](README.zh.md)

A standalone plugin that adds a custom background (image / GIF / animated wallpaper) to the [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) (DSH) Web GUI: one enable switch plus a local asset library, with two display mechanisms that cooperate with the official theme and the dsh-web-ui skin center.

## Features

- Enable / disable switch (persisted in localStorage, enabled by default).
- Asset library: add (upload `gif / jpg / jpeg / png / webp`, 100 MB limit), delete, rename, refresh. Deleting the asset currently in use falls back to the built-in background.
- Official theme (no skin): the background is painted onto the layout frame's own `background-image` and a translucent token override sheet lets the panels show it through. The built-in art is served through a short API URL, because multi-MB base64 data URLs inside CSS declarations are silently dropped by the browser parser.
- Skin mode: coexists with the skins from the dsh-web-ui skin center; the backdrop layer sits behind the skin's own surfaces and the skin's palette is left untouched.
- The asset API is loopback-only and never exposed beyond the local machine.

## Install

Prerequisites: DSH 0.1.x. Restart `dsh web` after installing, then hard-refresh the browser (Ctrl+F5).

### From npm (recommended)

```sh
dsh plugin --profile web add dsh-gif-background
```

### From this repository (development)

```sh
git clone https://github.com/alcohol-101/dsh-gif-background.git
dsh plugin --profile web add "link:<path-to-the-cloned-repo>"
```

## Usage

Settings → Plugins → the **Custom Background** card:

- tick the enable switch to show / hide the background;
- **Add** uploads a local image or GIF and selects it;
- click a list row to switch; **rename** edits inline (Enter confirms, Esc cancels); **x** deletes;
- **Refresh** rescans the asset directory — you can also drop files into `gifs/` and hit refresh.

## How it works

Two mechanisms, switched automatically by page state:

1. **Official default theme** (no skin-center skin active): the current background is painted onto the AppFrame element itself, and an rgba override sheet for the surface tokens (`--dsw-alias-bg-base`, `-bg-layer-1/2/3`, `-bg-module-platform`, `--dsw-specific-sidebar-fill`; light and dark pairs) is injected so the panels become translucent.
2. **Skin mode**: only the fixed full-viewport layer at `z-index:-1` plus a scrim layer remain; the skin's own translucent `#root` surface lets the backdrop through naturally.

Note: the dsh-web-ui skin center keeps a `data-dsh-skin-center` scope attribute on `<body>` even when no skin is active. Older versions mistook that for an active skin and never painted the official theme; the current version whitelists it and only treats the skins' own body attributes as skins.

## Repository layout

```
dsh-gif-background/
├── client/
│   ├── client.template.js   # browser-half source (__GIF_BASE64__ placeholder)
│   └── build.mjs            # builds lib/client.js, embedding gifs/builtin.gif
├── lib/
│   ├── index.js             # host half: loopback-only asset API
│   └── client.js            # browser bundle (committed so link installs need no build)
├── gifs/
│   └── builtin.gif          # built-in background (uploads stay local, gitignored)
├── docs/维护指南.md          # code walkthrough for maintainers (Chinese)
├── .github/workflows/ci.yml # CI: rebuild check + legacy identifier guard
├── cordis.patch.yml         # dsh bundle patch: declares the plugin row
├── CONTRIBUTING.md          # contribution guide
└── package.json             # dsh.client injects + exports
```

## Development

- Browser half: edit `client/client.template.js`, then `node client/build.mjs` to rebuild `lib/client.js`.
- Host half: edit `lib/index.js`.
- Host changes require restarting `dsh web`; browser-only changes only need Ctrl+F5.
- CI runs on every push and fails if the committed `lib/client.js` is stale or legacy identifiers reappear; failures notify you by email.
- New to the codebase? Start with `docs/维护指南.md` (a commented walkthrough in Chinese) and `CONTRIBUTING.md`.

Pitfalls worth recording:

- Multi-MB base64 inside a CSS `background-image` value is silently dropped by the parser → the built-in background goes through a short API URL instead.
- `MutationObserver` fires for `setAttribute` even when the value is unchanged (whatwg/dom#520) → every attribute write is guarded against the same value; an unguarded write retriggers the observer forever and freezes the page.
- The webserver `prefix` route matches `pathname.startsWith(prefix + '/')`, so the prefix must not carry a trailing slash; register one `exact` route per path and dispatch by HTTP method.
- See the skin-detection note in "How it works".

## Making your own GIF wallpaper

A short ffmpeg recipe (scale + dither palette, infinite loop):

```sh
ffmpeg -y -i input.mp4 -t 33 \
  -vf "fps=8,scale=840:-1:flags=lanczos,split[s0][s1];[s0]palettegen=max_colors=160:stats_mode=diff[p];[s1][p]paletteuse=dither=floyd_steinberg:diff_mode=rectangle" \
  -loop 0 builtin.gif
```

For a seamless loop, cut the segment and crossfade it onto itself (`xfade`) before palettizing.

## Troubleshooting

**The switch is on but no background shows under the official theme?**

Hard-refresh (Ctrl+F5), then check in the DevTools console:

1. `document.body.attributes` should contain `data-dsh-gif-bg="on"` and NOT `data-dsh-gif-bg-skin`;
2. `document.querySelector('[data-dsh-frame]').style.backgroundImage` should contain the asset URL;
3. `getComputedStyle(document.body).getPropertyValue('--dsw-alias-bg-base')` should be an rgba value.

If step 1 fails, a skin-center skin is active — in skin mode the background shows behind the skin's panels.

**Want a different panel translucency under the official theme?**

Edit the rgba values in `SKINLESS_CSS` in `client/client.template.js` (both light and dark pairs), rebuild, then Ctrl+F5.

## License

[Apache-2.0](LICENSE)
