Skip to content

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:

  1. GemShell reads your template.html
  2. Replaces with setting values
  3. Evaluates conditionals and loops
  4. Injects the result into the game's HTML

This means the final game HTML is static - no runtime template processing.