Skinning Guide v2.0

Everything you need to build a MOLTamp skin. A skin is a folder with a JSON manifest and a CSS theme. No JavaScript, no build tools. For inspiration, browse the skins gallery or see what's possible with widgets.

Quick Start

cp -r ~/Moltamp/skins/_template ~/Moltamp/skins/my-skin
  1. Edit skin.json with your info
  2. Edit theme.css — set your palette in :root, style elements with var()
  3. Drop art in assets/, reference as url('./assets/file.ext')
  4. Open MOLTamp Settings → Skins, select your skin

The Rules

Non-negotiable. The validator enforces them.

1. All colors live in :root

Every color value must be a CSS variable in :root. Never hardcode a color in a selector.

/* WRONG */
.moltamp-panel-left { background: #070b07; }

/* RIGHT */
:root { --skin-panel-bg: #070b07; }
.moltamp-panel-left { background: var(--skin-panel-bg); }

Why: Color overrides inject a :root block on top of your skin. Hardcoded colors can't be reached.

2. Contract variables come first

The 6 chrome and 21 terminal variables are the contract. Override them in :root. Extend with --skin-* prefixed variables.

3. Never hide panels

No display: none on panels. Users toggle visibility in Settings. Style them freely — visibility is the user's choice.

4. No executable content

  • No external URLs (https:, http:)
  • No @import
  • No expression(), javascript:
  • CSS only. Zero JS.

5. Don't hide the permission dialog

These trigger validator warnings: visibility: hidden, opacity: 0, pointer-events: none

6. No backgrounds on .moltamp-vibes

The vibes panel is always transparent. Visual content must come from GIF widget slots in skin.json, not CSS background. MOLTamp strips vibes backgrounds at load time.

7. Pseudo-elements need pointer-events: none

Every ::before and ::after must include pointer-events: none. Without it, they block right-click menus, drag handles, and widget interaction. Auto-fixed at load time, but declare it explicitly.

.moltamp-panel-right::before {
  content: '';
  position: absolute;
  inset: 0;
  background: var(--skin-overlay);
  pointer-events: none;  /* always include this */
}

8. Assets stay in assets/

  • Reference with url('./assets/filename.ext')
  • Supported images: PNG, JPG, WebP, GIF, SVG, AVIF
  • Supported fonts: WOFF2, WOFF, TTF, OTF
  • Max 10MB per file, 20MB per skin
  • theme.css max 100KB
  • No nested directories, no path traversal

9. Gate every visual effect behind an --effect-* variable

Every visual flourish must be controllable from the Effects panel. The validator scans your theme.css for six families and warns on upload when any of them isn't gated:

FamilyWhat it catches
Motionanimation, animation-name
Glowbox-shadow on any ::before/::after, box-shadow with blur ≥ 20px on regular elements, filter: drop-shadow()
Frostfilter: blur(), backdrop-filter
Overlaymix-blend-mode, repeating-linear-gradient, repeating-radial-gradient, animated GIF/WebP/APNG backgrounds
Distortiontransform: perspective/skew/rotate3d/matrix3d, mask, clip-path
Color shiftfilter: brightness/saturate/contrast/hue-rotate/invert/sepia/grayscale

Declare the variable in :root and gate the effect so 0 means invisible or neutral. Effect variables run 0 (off) to 2 (200%), where 1 is your intended look.

:root {
  --effect-mint-glow: 1;
}

.moltamp-terminal {
  box-shadow: inset 0 0 calc(15px * var(--effect-mint-glow)) var(--skin-glow);
}

10. Don't declare dead toggles

Every --effect-X you declare in :root must be read by at least one rule via var(--effect-X). The Effects panel discovers toggles by scanning :root, so an unread variable renders a switch that does nothing. Either wire it up or remove it — the validator flags it either way.

Skin Format

skins/my-skin/
  skin.json          ← Required: manifest
  theme.css          ← Required: all styles
  assets/            ← Optional: images, GIFs, SVGs

skin.json

{
  "id": "my-skin",
  "name": "My Skin",
  "version": "1.0.0",
  "author": "Your Name",
  "description": "A short description.",
  "engine": "1.0"
}

Required: id, name, version, engine. author and description are recommended but not enforced. id must match ^[a-zA-Z0-9_-]+$, and engine must be "1.0".

Optional manifest fields

FieldPurpose
tagsFreeform tags for search and filtering
featuredFlags the skin for the Featured section (featuring is curated)
effectsLabels and descriptions for your custom effects
vibesGIF widget slots for the vibes panel
defaultLayoutThe full layout your skin ships with — see below

defaultLayout

Ship a layout and your skin arrives fully arranged: themed tab names, curated widgets, tuned panel sizes. Applied the first time a user selects your skin; they can override anything afterwards.

"defaultLayout": {
  "tabAssignments": {
    "intel": ["events", "files", "agents", "git"],
    "signal": ["custom-1", "custom-2"]
  },
  "customTabs": {
    "custom-1": { "label": "SENSORS", "widgets": ["system", "weather"] },
    "custom-2": { "label": "COMMS", "widgets": ["music", "visualizer"] }
  },
  "dimensions": {
    "leftWidth": 260, "rightWidth": 180, "vibesHeight": 180,
    "bottomHeight": 26, "panelScale": 1, "terminalScale": 1
  },
  "panelVisibility": {
    "showVibes": true, "showLeft": true, "showRight": true, "showBottom": true
  },
  "vibesDeck": {
    "slots": [
      { "widgetId": "gif", "gifSrc": "assets/vibes.gif", "gifFit": "cover" },
      { "widgetId": "visualizer" }
    ],
    "widths": [60, 40]
  },
  "visualizerPreset": "bars"
}

vibesDeck is the full form of the vibes panel: the same slots as the top-level vibes block, plus a widths array of percentages controlling how the deck divides horizontally. If you omit it, MOLTamp falls back to vibes.slots at equal widths. visualizerPreset sets which visualizer the visualizer widget uses under your skin.

Variable Contract

Override these in :root. This is the API between MOLTamp and your skin.

Terminal Colors (21 vars)

VariablePurposeDefault
--t-foregroundMain text#e0e0e8
--t-backgroundTerminal background#0a0a0f
--t-cursorCursor color#d4a036
--t-cursor-accentCursor text when selected#0a0a0f
--t-selectionSelection highlightrgba(212,160,54,0.25)
--t-blackANSI 0#1a1a2e
--t-redANSI 1#d43636
--t-greenANSI 2#36d480
--t-yellowANSI 3#d4a036
--t-blueANSI 4#3678d4
--t-magentaANSI 5#a036d4
--t-cyanANSI 6#36b5d4
--t-whiteANSI 7#e0e0e8
--t-bright-blackANSI 8#3a3a52
--t-bright-redANSI 9#ff5555
--t-bright-greenANSI 10#50fa7b
--t-bright-yellowANSI 11#f1c232
--t-bright-blueANSI 12#6299e6
--t-bright-magentaANSI 13#c678dd
--t-bright-cyanANSI 14#56d4ef
--t-bright-whiteANSI 15#ffffff

Chrome Colors (6 vars)

VariablePurposeDefault
--c-chrome-bgPanel backgrounds#0a0a0f
--c-chrome-textUI text#9898a8
--c-chrome-borderBorders, dividers#1a1a2e
--c-chrome-accentHighlights, active#d4a036
--c-chrome-dimMuted text#606070
--c-chrome-hoverHover backgrounds#1e1e32

Effects (6 vars)

VariableTypePurposeDefault
--effect-scanlines0 or 1Scanline overlay0
--effect-glow0 or 1Inner glow0
--effect-crt0 or 1CRT curvature0
--scanline-opacity0-1Scanline intensity0.04
--glow-intensitypixelsGlow blur12px
--c-glowcolorGlow colorrgba(212,160,54,0.15)

Typography & Layout

VariableDefault
--font-terminal'SF Mono', monospace
--font-chrome'Inter', sans-serif
--font-size14px
--radius4px

Targetable Elements

Layout

ClassWhat
.moltamp-shellRoot container
.moltamp-titlebarTop title bar
.moltamp-vibesTop banner — your canvas
.moltamp-panel-leftLeft panel (Intel)
.moltamp-panel-rightRight panel (Signal)
.moltamp-panel-bottomBottom bar (Telemetry)
.moltamp-statusbarStatus bar

Terminal

ClassWhat
.moltamp-terminalTerminal container (xterm.js lives here)
.moltamp-tabbarTab bar container
.moltamp-tabbar-stripScrolling strip holding the session tabs
.moltamp-tabIndividual session tab
.moltamp-tab-activeCurrently active tab

The terminal is a real PTY, not a rendered transcript. Claude's output, tool calls, thinking blocks, code and your own typing are all terminal text painted by xterm.js — there are no message, tool-call, code-block or input elements to select. You style it by setting the ANSI palette (--t-foreground, --t-background, --t-black through --t-bright-white, --t-cursor, --t-selection) and by styling the .moltamp-terminal container itself.

Tabs & Attention Badges

MOLTamp 3.2.0 made every session a tab — up to 50 of them, in a strip that scrolls horizontally once they overflow rather than squeezing tabs into slivers. Cmd+P opens a searchable session manager.

A tab badges when its session needs the user, in one of two states:

StateColorMeans
Waitingvar(--t-yellow)The agent is blocked on you — permission prompt, or idle waiting for input
Donevar(--t-green)The agent finished its turn while that tab was unfocused

The colors are yours. Badge colors come straight from your terminal palette, so setting --t-yellow and --t-green restyles both the tab badge and the Cmd+P list for free. Pick those two with the tab bar in mind, not just the terminal — if your yellow disappears against your panel background, so does the badge.

The pulse animation is not yours. The badge breathes via a built-in animation applied as an inline style, so a skin can't slow it, speed it, or switch it off — and you shouldn't declare an --effect-* gate for it. It's a system signal, like the error flash: deliberately not skinnable, so a skin can never make "this session needs you" invisible. Style the tab chrome and let the badge do its job.

The Vibes Panel

The .moltamp-vibes div is your hero canvas.

Backgrounds are not allowed on .moltamp-vibes. The vibes panel is always transparent — all visual content (GIFs, images) must come from GIF widget slots defined in skin.json, not from CSS background or background-image. MOLTamp enforces this with an !important override at load time.

// skin.json
{
  "vibes": {
    "slots": [
      { "widgetId": "gif", "gifSrc": "assets/vibes.gif", "gifFit": "cover" }
    ]
  }
}

Use ::before and ::after for overlay effects — these are still allowed:

.moltamp-vibes::before {
  content: '';
  position: absolute;
  inset: 0;
  background: var(--skin-overlay);
  mix-blend-mode: multiply;
  pointer-events: none;
}

Reactive Data Attributes

Set on .moltamp-shell — changes with Claude's state.

Activity Level

ValueMeaningUse for
data-activity="idle"No outputDim, calm
data-activity="low"Some outputModerate
data-activity="high"StreamingBright, intense

Shell State

ValueMeaning
idleWaiting for input
thinkingClaude is reasoning
streamingOutputting text
tool-useCalling a tool
permissionWaiting for permission
errorSomething broke

Live CSS Properties

Set on .moltamp-shell for data-driven visuals:

PropertyDescription
--data-context-pctContext window used %
--data-cost-centsSession cost in cents
--data-tokens-inInput tokens (thousands)
--data-tokens-outOutput tokens (thousands)
--data-rate-5h5-hour rate limit %
--data-agentsActive subagent count
--data-git-changedChanged file count

Example: Context gauge as pie chart:

.moltamp-context-gauge {
  width: 80px; height: 80px;
  border-radius: 50%;
  background: conic-gradient(
    var(--c-chrome-accent) calc(var(--data-context-pct) * 1%),
    var(--c-chrome-border) 0
  );
}

Reactive Animations

Bind animations to data attributes:

[data-activity="high"] .moltamp-vibes {
  filter: brightness(1.2) saturate(1.1);
  animation: skin-glow 2s ease-in-out infinite;
}

[data-shell-state="thinking"] .moltamp-vibes {
  animation: skin-pulse 1.5s ease-in-out infinite;
}

[data-shell-state="error"] .moltamp-vibes {
  box-shadow: inset 0 0 20px var(--skin-error-color);
}

Prefer transform and opacity for performance. Keep durations under 5s.

Sharing

Export: Settings → Skins → Export — saves as .zip

Import: Settings → Skins → Import — select .zip

Ready for an audience? Publish your skin to the community gallery so others can discover and download it.

Preflight (Auto-Fixes)

MOLTamp applies these overrides after your skin CSS loads. They protect users from common mistakes — especially in AI-generated skins.

WhatOverrideWhy
.moltamp-vibes backgroundbackground: transparent !importantVibes visuals come from GIF widget slots
All ::before / ::afterpointer-events: none !importantPseudo-elements must never block clicks

For AI-generated skins

If using ChatGPT, Claude, or another AI to generate a skin, paste this with your prompt:

Rules: All colors must be CSS variables in :root.
Use --skin-* prefix for custom vars.
Never set background on .moltamp-vibes — use skin.json vibes slots for images.
Every ::before/::after MUST have pointer-events: none.
No external URLs, no @import, no JS.
skin.json requires id, name, version, engine: "1.0".
theme.css max 100KB; assets max 10MB per file, 20MB total.

Gate EVERY effect behind an --effect-* variable (0 = off, 1 = default, 2 = max).
The validator checks six families:
  MOTION      — animation, animation-name
  GLOW        — box-shadow on ::before/::after, box-shadow blur >= 20px, drop-shadow
  FROST       — filter: blur(), backdrop-filter
  OVERLAY     — mix-blend-mode, repeating gradients, animated gif/webp/apng backgrounds
  DISTORTION  — transform: perspective/skew/rotate3d/matrix3d, mask, clip-path
  COLOR SHIFT — filter: brightness/saturate/contrast/hue-rotate/invert/sepia/grayscale
Every --effect-X declared in :root must be READ by a rule via var(--effect-X).

The terminal is a raw xterm.js PTY. There are NO message, tool-call, thinking,
code-block or input elements — style it via the --t-* ANSI palette only.

Ship a defaultLayout in skin.json with themed tab names and curated widgets.

Pre-Share Checklist

  • All colors defined as variables in :root
  • Zero hardcoded hex/rgb outside :root
  • Contract variables (--c-*, --t-*) overridden
  • Custom variables use --skin-* prefix
  • No background or background-image on .moltamp-vibes (use GIF widget slots)
  • Every ::before/::after has pointer-events: none
  • No external URLs or @import
  • Every effect gated behind an --effect-* variable (all six families)
  • No dead toggles — every --effect-X in :root is read by a rule
  • defaultLayout in skin.json with themed tab names
  • --t-yellow and --t-green stay legible against the tab bar (they drive the attention badges)
  • Assets under 10MB each, 20MB total; theme.css under 100KB
  • Tested at different panel sizes
  • Right-click context menus work on all panels
  • Permission dialog still visible