MoriMori
Mori

Theme Studio

Reskin the entire app in one click. Pick one of six presets, then tune any color, corner, or type size — it applies live everywhere, and imported themes are validated as data, never run as code.

One click, the whole app

Theme Studio is a runtime skin for Mori. Under the hood the app's colors are CSS custom properties on every surface; Theme Studio writes new values for them straight onto the app, so a single change cascades to the sidebar, chat, workbench, and every panel at once — no restart, no reload, nothing to rebuild.

Open it from Systems → Theme Studio. Your choice is saved locally and re-applied before the interface even paints on the next launch, so there's no flash of the old look while the app boots.

  • Presets and edits apply instantly as you click and drag.
  • Everything persists across restarts on this machine.
  • Nothing here touches your account or syncs — a theme is a local preference.

The six presets

Six curated looks ship built in. The preset gallery shows a live mini-preview of each — its real sidebar, panel, and swatch colors — so you can see a theme before you commit to it.

  • Mori — the signature icy-blue look. Calm, neutral, pro. The default.
  • Midnight — deep black-blue. Maximum contrast, minimum glare.
  • Paper — clean light mode. Ink on paper, gentle on the eyes.
  • Terminal — green-on-black CRT. Phosphor glow, monospace everything, tightened corners.
  • Sakura — soft pink on warm dark. Petals at dusk.
  • Slate — neutral grey. No hue, no distraction, all signal.

Terminal is a full skin, not just a color swap: it switches the entire interface font to monospace, tightens the corner radius, and turns on a subtle CRT flourish — the app keys that off the active theme so the look stays coherent.

The token editor

Every preset is a starting point you can edit. The editor exposes each color as its own swatch — click the chip for a color picker, or type an exact value into the hex field beside it. Tokens are grouped so the picker stays legible:

  • Surfaces — background, secondary, tertiary, input, hover, and elevated fills.
  • Text — primary, secondary, and muted.
  • Borders — border, focus border, and subtle border.
  • Accent — the accent, its hover shade, and a secondary accent.
  • Semantic — danger, danger-hover, success, and warning.

Beyond color there are three shape-and-type knobs: a corner-radius scale, a font-size scale, and a Sans / Mono switch for the interface font. The accent glows used around focused and active elements are derived automatically from your Accent color, so a single accent change re-tints the whole app consistently — you never tune those by hand.

The moment you edit any token, the theme forks into a Custom theme, still tagged with the preset you started from (so the Terminal flourish and labels stay coherent). A Reset button snaps you back to the clean preset whenever you want the original.

Export and import

A finished theme is portable. From the header you can Copy JSON to the clipboard or Export it as a .json file to share, back up, or move to another machine. Import opens a drawer where you paste a theme's JSON and hit Apply — it loads instantly as your active Custom theme.

The export is a plain, human-readable object: the base preset it came from, every color token, and the radius, font-size, and font-family values.

{
  "$schema": "mori-theme/v1",
  "presetId": "terminal",
  "colors": {
    "bg-primary": "#010401",
    "text-primary": "#b6ffc4",
    "accent": "#35ff6b"
  },
  "radiusScale": 0.55,
  "fontScale": 1,
  "fontSans": "'JetBrains Mono', 'Cascadia Code', monospace"
}

Imported themes are data, not code

A theme file comes from outside the app, so Theme Studio treats every imported value as untrusted data and parses it strictly — it can never smuggle anything executable into the interface.

  • Only recognized token keys survive the import; anything else is dropped.
  • A color is accepted only if it's a plain hex or a numeric rgb/rgba/hsl/hsla value. Anything containing url(...), a CSS expression, javascript:, or braces is rejected outright — an imported theme can never trigger a resource load.
  • Font values must be plain family names and separators; the size and radius scales are clamped to safe ranges.
  • A file that carries no recognized tokens at all is refused with a clear reason, so pasting the wrong JSON fails loudly instead of silently breaking your look.

The result: you can trade themes freely without trading trust. The worst a bad file can do is get rejected.