r0astr Skin System¶
The r0astr skin system allows complete customization of the UI layout and visual theme through modular skin packages.
Architecture¶
Skins are self-contained packages that define: - HTML Templates - Panel layout, slider controls, UI components - CSS Theme - Visual styling, colors, spacing - Manifest - Metadata and configuration
Creating a New Skin¶
1. Create Skin Directory¶
public/skins/
└── your-skin-name/
├── skin.json # Manifest
├── theme.css # Visual styling
└── templates/
├── panel.html # Panel structure
├── slider.html # Slider with label
└── slider-collapsed.html # Compact slider
2. Create Manifest (skin.json)¶
{
"name": "Your Skin Name",
"version": "1.0.0",
"author": "Your Name",
"description": "Brief description",
"layoutType": "tree",
"cssVariables": {
"--primary-color": "#00ff88",
"--panel-bg": "rgba(0, 0, 0, 0.8)"
},
"templates": {
"panel": "panel.html",
"slider": "slider.html",
"sliderCollapsed": "slider-collapsed.html"
},
"hoverTargets": [
{
"id": "menu-trigger",
"controls": [".top-menu-bar"],
"hint": "subtle-glow"
}
]
}
Manifest Fields¶
- name - Display name for the skin
- version - Semantic version (1.0.0)
- author - Creator name
- description - Brief description
- layoutType - Layout style identifier
- cssVariables - CSS custom properties to override
- templates - Template file mappings
- hoverTargets (optional) - Array of hover interaction configs
3. Create Templates¶
Templates use Mustache-style {{variable}} placeholders.
panel.html - Panel structure:
<details{{expanded}}>
<summary>
<span class="panel-number-badge">{{panelNumber}}</span>
<span class="panel-title">{{title}}</span>
<div class="panel-actions">
<button class="btn-playback" data-card="{{panelId}}">
<span class="material-icons">play_arrow</span>
</button>
<button class="btn-delete" style="{{deleteButtonStyle}}">
<span class="material-icons">delete</span>
</button>
</div>
</summary>
<div class="panel-editor-container">
<div class="code-editor" id="editor-{{panelId}}"></div>
<div class="error-message" data-card="{{panelId}}"></div>
</div>
</details>
<div class="panel-controls-container">
<div class="leaf-viz">
<div id="viz-container-{{panelId}}"></div>
</div>
</div>
slider.html - Labeled slider:
<label>{{label}}</label>
<input type="range"
min="{{min}}"
max="{{max}}"
step="{{step}}"
value="{{value}}"
data-slider-id="{{sliderId}}">
<span class="slider-value">{{valueFormatted}}</span>
slider-collapsed.html - Compact slider:
<input type="range"
min="{{min}}"
max="{{max}}"
step="{{step}}"
value="{{value}}"
data-slider-id="{{sliderId}}">
4. Create Theme CSS (theme.css)¶
Override CSS variables or add custom styles:
:root {
--primary-color: #ff6b35;
--panel-bg: rgba(20, 20, 30, 0.95);
--panel-border: 2px solid var(--primary-color);
}
.level-panel {
border-radius: 12px;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.4);
}
.panel-number-badge {
background: var(--primary-color);
color: black;
}
5. Configure Hover Targets (Optional)¶
Hover targets allow you to create interactive regions that trigger UI elements.
Hover Target Configuration¶
Each hover target in the manifest has:
Fields:
id- Unique identifier for CSS targetingcontrols- Array of CSS selectors for elements to show on hoverhint- Visual hint type (see below)
Visual Hint Types¶
subtle-glow- Faint edge glow (good for edge triggers)pulse- Pulsing animation (draws attention)visible- Always visible (like a button)banner- Uses existing element visibilitynone- Completely invisible (discovery by accident)
CSS Implementation¶
Position and style hover targets in your theme CSS:
/* Position the hover target */
.skin-hover-target[data-hover-id="menu-trigger"] {
top: 0;
left: 0;
width: 20px;
height: 100vh;
background: linear-gradient(to right, rgba(255, 107, 53, 0.2), transparent);
transition: all 0.3s ease;
}
/* Optional: Enhance on hover */
.skin-hover-target[data-hover-id="menu-trigger"]:hover {
width: 30px;
box-shadow: 0 0 20px rgba(255, 107, 53, 0.5);
}
/* Position the controlled element */
.top-menu-bar {
top: 50%;
left: 0;
transform: translateX(-100%) translateY(-50%);
transition: transform 0.3s ease;
}
/* Show element on hover */
.skin-hover-target[data-hover-id="menu-trigger"]:hover ~ .top-menu-bar,
.top-menu-bar:hover {
transform: translateX(0) translateY(-50%);
opacity: 1;
pointer-events: auto;
}
Example: Multi-Point Hover (Glass Skin)¶
{
"hoverTargets": [
{
"id": "left-edge-menu",
"controls": [".top-menu-bar"],
"hint": "subtle-glow"
},
{
"id": "bottom-metronome",
"controls": [".metronome-section"],
"hint": "visible"
}
]
}
CSS:
/* Left edge menu trigger */
.skin-hover-target[data-hover-id="left-edge-menu"] {
top: 0;
left: 0;
width: 15px;
height: 100vh;
background: linear-gradient(to right, rgba(0, 212, 255, 0.15), transparent);
}
/* Bottom metronome trigger */
.skin-hover-target[data-hover-id="bottom-metronome"] {
bottom: 0;
left: 0;
right: 0;
height: 30px;
background: rgba(0, 0, 0, 0.3);
backdrop-filter: blur(8px);
}
Required CSS Classes¶
Your templates MUST include these classes for JS functionality:
| Class | Purpose | Required On |
|---|---|---|
.panel-tree |
Main panel container | Existing in index.html |
.level-panel |
Individual panel wrapper | Panel template |
.panel-number-badge |
Panel number | Panel template |
.panel-title |
Editable title | Panel template |
.code-editor |
CodeMirror mount point | Panel template |
.panel-controls-container |
Slider/viz area | Panel template |
.btn-playback |
Play/pause button | Panel template |
.btn-delete |
Delete button | Panel template |
.leaf-slider |
Slider wrapper | Slider template |
.slider-value |
Value display | Slider template |
Template Variables¶
Panel Template¶
{{panelId}}- Unique panel ID (e.g., "panel-1234567890"){{panelNumber}}- Panel number (0, 1, 2, ...){{title}}- Panel title (e.g., "Instrument 1"){{expanded}}- " open" or "" for details state{{deleteButtonStyle}}- "display: none;" for master panel, "" otherwise
Slider Template¶
{{sliderId}}- Unique slider ID{{label}}- Slider label (e.g., "Cutoff"){{min}}- Minimum value{{max}}- Maximum value{{step}}- Step size{{value}}- Current value (number){{valueFormatted}}- Formatted value (e.g., "123.45")
Activating Your Skin¶
- Place skin folder in
public/skins/your-skin-name/ - Build:
npm run build - Open Settings → Integrations → UI Skin
- Select your skin from dropdown
- Save settings
- UI hot-reloads automatically - no page refresh needed!
Development Workflow
- CSS changes: Vite HMR updates automatically
- Template changes: Require page reload
- Manifest changes: Require page reload
- Skin switching: Hot-reloads (panel state preserved)
Skin Storage¶
- Browser: localStorage (
r0astr-settings.skin) - Electron: app userData folder
- GitHub Pages: localStorage (persists across sessions)
All modes work offline with bundled skins.
Example Skins¶
Minimal Skin¶
Stripped-down layout with no badges, minimal controls.
Grid Layout¶
Traditional card grid instead of tree structure.
WinAmp Classic¶
Retro WinAmp-inspired design with skinnable sprites.
Development Tips¶
- Live Reload CSS: Edit theme.css - Vite HMR updates automatically
- Template Changes: Require page reload
- Test with multiple panels: Create 3-4 panels to test overflow
- Check responsive behavior: Test at different window sizes
- Validate required classes: Missing classes break JS functionality
Troubleshooting¶
Skin doesn't load: - Check browser console for fetch errors - Verify skin.json is valid JSON - Check template file paths in manifest
UI broken after skin change: - Verify all required CSS classes present - Check template placeholders match expected variables - Fallback: Reset to default skin in localStorage
Hover targets not working:
- Ensure z-index is above all UI elements (use z-index: 10101)
- Check that hidden elements have pointer-events: none
- Verify hover selectors use ~ for sibling combinators
- Remember hover targets are injected as direct body children
Page won't reload:
- Clear localStorage: localStorage.removeItem('r0astr-settings')
- Hard refresh: Cmd+Shift+R (Mac) / Ctrl+Shift+R (Windows)
Contributing Skins¶
Share your skins! Create a PR with:
1. Skin folder in public/skins/
2. Screenshot in docs/skins/
3. Update this README with your skin description
License¶
Skins inherit the project's AGPL-3.0 license unless otherwise specified in skin.json.