Appearance
HTML Templates
Use Mustache-like syntax to create dynamic HTML based on settings.
Basic Syntax
Variables
Insert setting values with double braces:
html
<div class="my-panel">{{ message }}</div>Conditionals
Show content based on boolean settings:
html
{{#if enabled}}
<div class="overlay">Content shown when enabled is true</div>
{{/if}}Inverse Conditionals
Show content when a setting is false:
html
{{#unless enabled}}
<div class="notice">Plugin is disabled</div>
{{/unless}}Iteration
Loop over arrays:
html
{{#each items}}
<li>{{ this }}</li>
{{/each}}Position Helper
Use the position setting to place elements:
Settings:
javascript
settings: [
{
id: 'position',
type: 'select',
options: ['top-left', 'top-right', 'bottom-left', 'bottom-right'],
default: 'top-left'
}
]CSS:
css
.my-panel { position: fixed; }
.my-panel.top-left { top: 10px; left: 10px; }
.my-panel.top-right { top: 10px; right: 10px; }
.my-panel.bottom-left { bottom: 10px; left: 10px; }
.my-panel.bottom-right { bottom: 10px; right: 10px; }Template:
html
{{#if enabled}}
<div class="my-panel {{ position }}">
Content
</div>
{{/if}}Complete Example
plugin.js:
javascript
module.exports = {
name: 'Debug Panel',
version: '1.0.0',
inject: ['style.css', 'template.html', 'script.js'],
settings: [
{ id: 'showFps', type: 'checkbox', label: 'Show FPS', default: true },
{ id: 'showMemory', type: 'checkbox', label: 'Show Memory', default: false },
{ id: 'position', type: 'select', options: ['top-left', 'top-right', 'bottom-left', 'bottom-right'], default: 'top-left' }
]
};template.html:
html
{{#if showFps}}
<div class="debug-panel {{ position }}" id="debug-panel">
{{#if showFps}}
<div class="debug-row">
<span class="debug-label">FPS</span>
<span class="debug-value" id="fps-value">--</span>
</div>
{{/if}}
{{#if showMemory}}
<div class="debug-row">
<span class="debug-label">MEM</span>
<span class="debug-value" id="mem-value">--</span>
</div>
{{/if}}
</div>
{{/if}}style.css:
css
.debug-panel {
position: fixed;
padding: 8px 12px;
background: rgba(0, 0, 0, 0.85);
color: #fff;
font-family: monospace;
font-size: 12px;
border-radius: 4px;
z-index: 99999;
}
.debug-panel.top-left { top: 10px; left: 10px; }
.debug-panel.top-right { top: 10px; right: 10px; }
.debug-panel.bottom-left { bottom: 10px; left: 10px; }
.debug-panel.bottom-right { bottom: 10px; right: 10px; }
.debug-row {
display: flex;
justify-content: space-between;
gap: 20px;
}
.debug-label { color: #888; }
.debug-value { color: #4ade80; font-weight: bold; }script.js:
javascript
const { showFps, showMemory } = GEMSHELL_SETTINGS;
if (!showFps && !showMemory) return;
const fpsEl = document.getElementById('fps-value');
const memEl = document.getElementById('mem-value');
let frames = 0;
let lastTime = performance.now();
function update() {
frames++;
const now = performance.now();
if (now - lastTime >= 1000) {
if (fpsEl) fpsEl.textContent = frames;
frames = 0;
lastTime = now;
if (memEl && performance.memory) {
const mb = (performance.memory.usedJSHeapSize / 1048576).toFixed(1);
memEl.textContent = mb + ' MB';
}
}
requestAnimationFrame(update);
}
update();Template Processing
Templates are processed at build time:
- GemShell reads your
template.html - Replaces
with setting values - Evaluates conditionals and loops
- Injects the result into the game's HTML
This means the final game HTML is static - no runtime template processing.
