# Making a Muse skin

A skin can change everything about the player: colors and fonts, and also the layout itself. A
skin can move the cover and buttons, change their shape, draw a card, a pill, a turntable or a
cassette deck, and decide what shows on hover.

Muse comes with these skins:

| Skin | What it is |
|---|---|
| **Default** | Translucent window tinted by the cover art |
| **Pill** | Round spinning cover tucked against a white pill |
| **Card** | Square artwork card with controls floating over the art |
| **Turntable** | Spinning record and a tone arm that swings in when you press play |
| **Tape** | Cassette deck; the reels turn and the tape winds across as the song plays |
| **Midnight**, **Paper**, **Mono** | The default layout with different colors and fonts |

Their files are in `Resources/Skins/` in the source code, or in `Muse.app/Contents/Resources/Skins/`
in the app. Copying one is the quickest way to start.

## Where skins live

A skin is a folder with a `skin.json` file in it, plus any images it uses:

```
~/Library/Application Support/Muse/Skins/My Skin/skin.json
```

Choose a skin from the **Skins** menu. Hovering over a skin there previews it; click to keep it. Muse reloads skins whenever you switch back to it, so you
can edit `skin.json` and see the result straight away. **Skins → Reload Skins** (⇧⌘R) lists any
problems.

### From the command line

```bash
muse skin new "My Skin" --from Turntable   # copy a skin to edit
muse skin validate "~/Library/Application Support/Muse/Skins/My Skin"
muse skin apply "My Skin"
muse skin snapshot ~/Desktop/my-skin.png   # screenshot of the player (--mini, --menubar)
```

`muse skin schema` prints the full JSON Schema. Editors such as VS Code use it for autocomplete if
you save it and add `"$schema": "./skin.schema.json"`. Setup for `muse` is in the README.

## The file

Every field is optional:

```json
{
  "version": 2,
  "name": "My Skin",
  "author": "You",
  "description": "One line about it",
  "appearance": "dark",
  "colors": { "accent": "#FF9F0A", "textSecondary": "#FFFFFF99" },
  "font": { "design": "rounded" },
  "window": { "chrome": "none" },
  "layout": {
    "main": { "type": "hstack", "width": 420, "children": [ … ] },
    "mini": { … }
  }
}
```

- **Without `layout`**, a skin only recolors the default player. Midnight, Paper and Mono work this
  way.
- **With `layout`**, the window becomes transparent and only what your layout draws is visible, so
  you can make any shape. Give the root node a `width`: that's the starting width. Add `minWidth`
  and `maxWidth` (or `"fill"`) to let people resize the window between them; without them the width
  is fixed. Set `"window": { "chrome": "standard" }` to
  keep a normal macOS window with traffic lights instead.
- **`layout.mini`** is optional. Without it, the mini player shows just the cover art.
- **`layout.menubar`** is optional: the player in the menu bar popover. Without it, the Default
  skin's is used, in your accent color.
- **`vars`** names values you use more than once: `"vars": { "gap": 8, "panel": { "light": "#FFF",
  "dark": "#1E1E21" } }`, then write `"$gap"` or `"$panel"` anywhere. The Default skin uses this so
  its player and menu bar popover share the same spacing.
- **Playlist:** put the `playlist` node inside the player's own panel (after a `divider` with
  `"when": "playlist"`). It slides up from the bottom when shown.

### Top-level settings

| Field | Values |
|---|---|
| `appearance` | `system`, `light` or `dark` |
| `colors.accent`, `text`, `textSecondary`, `textTertiary`, `track`, `progress`, `rowHover`, `rowCurrent`, `separator`, `background` | Theme colors. Layout nodes can use these names instead of hex values |
| `font` | `{ "family": "Avenir Next" }` or `{ "design": "rounded" \| "serif" \| "monospaced" }`: the default font for all text |
| `window.chrome` | `none` (default when there is a layout) or `standard` |
| `window.margin` | Transparent space around a chrome-less layout (default 0: the window ends where the layout does) |
| `window.material`, `backgroundImage`, `artTint` | Background for standard-chrome windows |
| `cover.size`, `cover.cornerRadius`, `visualizer.bars` | Defaults for the cover and visualizer |

### Colors

- `"#RRGGBB"`, or `"#RRGGBBAA"` (the last two digits are opacity).
- A theme color name: `accent`, `text`, `textSecondary`, `textTertiary`, `track`, `progress`,
  `rowHover`, `rowCurrent`, `separator`, `background`, `white`, `black`, `clear`.
- `{ "light": "#…", "dark": "#…" }` for a different color in light and dark mode.

## Layout nodes

A layout is a tree. Each node has a `type`, and containers have `children`.

**Containers**

| type | |
|---|---|
| `vstack` | Children top to bottom |
| `hstack` | Children left to right. Negative `spacing` makes them overlap |
| `zstack` / `panel` | Children layered on top of each other; later ones are drawn on top |
| `spacer` | Flexible space. With `width`/`height` it becomes a fixed box you can color (dots, stripes, lights) |
| `divider` | A thin line |

**Song info**

| type | |
|---|---|
| `title`, `artist`, `album`, `details` | Song text. The title scrolls when it's too long |
| `text` | Any text, with placeholders: `{title}` `{artist}` `{album}` `{year}` `{genre}` `{format}` `{elapsed}` `{remaining}` `{total}` `{index}` `{count}` |
| `time` | `format`: `elapsed`, `remaining` (click to switch to total), `total` or `both` |
| `cover` | Cover art (the mascot when there is none). Click it to edit the song info; right-click for Track Details and Show in Finder |

**Controls**

| type | |
|---|---|
| `button` | `action`: `play` `previous` `next` `stop` `shuffle` `repeat` `mute` `playlist` `mini` `pin` `close` `add` `details` `edit` (Edit Info window) `menubar` (move to the menu bar) `window` (show the player window). `style`: `plain` `circle` `filled` `outline`. `size`, `color`, `tint`. `icon`: an SF Symbol name or an image file; `"a\|b"` uses `a` when off and `b` when on |
| `progress` | Song position; drag to seek. `style`: `bar` `ring` `arc`. `thickness`, `color` (filled part), `tint` (track) |
| `volume` | Volume slider. Set `icons: false` to hide the speaker icons |
| `visualizer` | `bars` (1–24), `style`: `bars` or `led`, `color` |
| `playlist` | The song list. Give it a `height`, and usually `"when": "playlist"` |

**Pictures**

| type | |
|---|---|
| `image` | An image file next to `skin.json`. `fit`, and `tint` to recolor it |
| `icon` | A non-clickable SF Symbol |
| `vinyl` | A record with the cover as its label; spins while playing. `color`, `tint` |
| `tonearm` | Swings onto the record while playing. Its pivot is near the top right of its box; place it over a `vinyl` with `offset` |
| `cassette` | A tape whose reels turn and whose tape winds across as the song plays. `color` (shell), `tint` (stripes), `text` (label) |

### Properties every node accepts

| Property | |
|---|---|
| `width`, `height`, `minWidth`, `maxWidth`, `minHeight`, `maxHeight` | Points, or `"fill"` to take all the space available |
| `padding` | `12`, `[vertical, horizontal]`, `[top, right, bottom, left]`, or `{ "top": … }` |
| `alignment` | `center` `leading` `trailing` `top` `bottom` `topLeading` … |
| `spacing` | Gap between children |
| `offset` | `[x, y]`: moves a node without moving the others. Use it for overlaps |
| `zIndex` | Drawing order among siblings |
| `background` | A color; `{ "gradient": [colors], "angle": 90 }`; `{ "material": "translucent" }` (blur); `{ "image": "bg.png" }`; `{ "artwork": { "blur": 40, "opacity": 0.8 } }` (blurred cover art); or an array of layers |
| `cornerRadius`, `shape` | `shape`: `rounded`, `circle`, `capsule` or `rectangle` |
| `border` | `{ "color": "#FFFFFF33", "width": 1 }` |
| `shadow` | `{ "color": "#00000059", "radius": 16, "x": 0, "y": 8 }`. Only for things inside the layout: the window ends at the layout's edges, so an outer shadow would be cut off |
| `clip` | Crop the content to the shape |
| `opacity`, `rotation`, `scale` | |
| `spin` | `true`, or seconds per turn: rotates while music plays |
| `when` | Only show the node in some states: `playing` `paused` `hover` `playlist` `hasArtwork` `hasTrack` `mini` `pinned` `muted`. Put `!` in front to negate and join with `&`, e.g. `"hover&!playing"` |
| `help` | Tooltip |

## Example: a small pill

```json
{
  "name": "Tiny",
  "appearance": "dark",
  "layout": {
    "main": {
      "type": "hstack", "width": 320, "spacing": 12, "padding": [10, 16, 10, 10],
      "background": "#1C1C1EEE", "shape": "capsule",
      "children": [
        { "type": "cover", "width": 44, "height": 44, "shape": "circle", "spin": 10 },
        { "type": "vstack", "alignment": "leading", "spacing": 2, "maxWidth": "fill",
          "children": [
            { "type": "title", "font": { "size": 13, "weight": "semibold" } },
            { "type": "progress", "thickness": 3 }
          ] },
        { "type": "button", "action": "play", "style": "filled", "size": 14 },
        { "type": "button", "action": "next", "size": 13 }
      ]
    }
  }
}
```

## Tips

- **Dragging the window:** drag anywhere on a background to move the window.
- **Buttons to include:** the built-in skins all have `playlist` (with a `playlist` node), `mini`
  (and a `layout.mini` with a way back), `shuffle`, `repeat`, a `volume` or `mute`, `add`, `pin`
  and `menubar`. Editing is on the cover (click or right-click), so no `edit` button is needed. Do the same so nothing is out of reach.
- **Empty text:** song text that is empty (no artist, for example) takes no space.
- **Overlaps:** use `hstack` with negative `spacing` (see Pill), or a `zstack` with `offset`s (see
  Turntable).
- **Hover controls:** put buttons in a `zstack` over the cover with `"when": "hover"`.
- **Sharing:** zip the skin folder and send it. To install a skin, unzip it into the Skins folder
  or run `muse skin apply path/to/folder`.


---

Machine-readable: the JSON Schema is at https://tinymuse.app/skin.schema.json; the built-in skins are at
https://tinymuse.app/skins/<Name>.json; agents should follow https://tinymuse.app/skills/muse-skins/SKILL.md.
