Custom UI: Complete Example
A full custom UI with profile selector, material swatches, width slider, diamond options, and live price display.
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Custom Wedding Band Configurator</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { font-family: 'Segoe UI', system-ui, sans-serif; background: #fafafa; }
.app { display: flex; flex-direction: column; height: 100vh; }
#viewer { flex: 1; min-height: 300px; }
.controls {
background: #fff;
border-top: 1px solid #e0e0e0;
padding: 20px 24px;
overflow-x: auto;
}
.control-row {
display: flex;
align-items: center;
gap: 16px;
margin-bottom: 16px;
}
.control-row:last-child { margin-bottom: 0; }
.control-label {
font-size: 12px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.5px;
color: #888;
width: 80px;
flex-shrink: 0;
}
.btn-group { display: flex; gap: 8px; flex-wrap: wrap; }
.btn {
padding: 8px 16px;
border: 1px solid #ddd;
border-radius: 6px;
background: #fff;
cursor: pointer;
font-size: 13px;
transition: all 0.15s;
}
.btn:hover { border-color: #999; }
.btn.active { background: #333; color: #fff; border-color: #333; }
.swatch {
width: 36px; height: 36px;
border-radius: 50%;
border: 2px solid #ddd;
cursor: pointer;
transition: all 0.15s;
}
.swatch:hover { border-color: #999; }
.swatch.active { border-color: #333; box-shadow: 0 0 0 2px #333; }
.swatch-white { background: linear-gradient(135deg, #e8e8e8, #d0d0d0); }
.swatch-yellow { background: linear-gradient(135deg, #f0d060, #c8a840); }
.swatch-rose { background: linear-gradient(135deg, #e8b8a8, #d09888); }
.slider { width: 200px; accent-color: #333; }
.slider-value { font-size: 14px; color: #333; min-width: 60px; }
.price-bar {
display: flex;
justify-content: space-between;
align-items: center;
padding: 16px 24px;
background: #333;
color: #fff;
}
.price { font-size: 24px; font-weight: 600; }
.band-toggle { display: flex; gap: 8px; }
.band-btn {
padding: 6px 16px;
border: 1px solid rgba(255,255,255,0.3);
border-radius: 4px;
background: transparent;
color: #fff;
cursor: pointer;
font-size: 13px;
}
.band-btn.active { background: rgba(255,255,255,0.2); border-color: #fff; }
</style>
</head>
<body>
<div class="app">
<div id="viewer"></div>
<div class="controls">
<div class="control-row">
<span class="control-label">Profile</span>
<div id="profiles" class="btn-group"></div>
</div>
<div class="control-row">
<span class="control-label">Metal</span>
<div id="metals" class="btn-group"></div>
</div>
<div class="control-row">
<span class="control-label">Width</span>
<input id="width" class="slider" type="range" min="2" max="10" step="0.1" value="4" />
<span id="width-val" class="slider-value">4.0 mm</span>
</div>
<div class="control-row">
<span class="control-label">Diamonds</span>
<div id="diamonds" class="btn-group"></div>
</div>
</div>
<div class="price-bar">
<div class="band-toggle">
<button class="band-btn active" onclick="switchBand('her')">Her Ring</button>
<button class="band-btn" onclick="switchBand('his')">His Ring</button>
</div>
<div class="price" id="price">-</div>
</div>
</div>
<script src="https://releases.ijewel3d.com/libs/mini-viewer/latest/bundle.iife.js"></script>
<script>
let api;
// Initialize viewer in headless mode
new ijewelViewer.Viewer(document.getElementById('viewer'), {
name: 'Wedding Band Builder',
version: 'v5',
basePath: 'https://your-cdn.com/wbb-assets/',
plugins: {
WeddingBandBuilder: {
manifestUrl: 'wedding-band-project.json',
showUI: false,
},
},
}, {
showCard: false, showSwitchNode: false, showUiButtons: false,
showConfigurator: false, showZoomButtons: false, enableZoom: true,
});
window.addEventListener('ijewel-viewer-ready', (e) => {
api = e.detail.viewer.getPluginByType('WeddingBandBuilder').controller;
// Build profile buttons
const profilesEl = document.getElementById('profiles');
api.getAvailableProfiles().forEach((p, i) => {
const btn = document.createElement('button');
btn.className = `btn${i === 0 ? ' active' : ''}`;
btn.textContent = p.name;
btn.onclick = async () => {
await api.setProfile(p.index);
profilesEl.querySelectorAll('.btn').forEach(b => b.classList.remove('active'));
btn.classList.add('active');
};
profilesEl.appendChild(btn);
});
// Build metal swatches
const metalsEl = document.getElementById('metals');
const colorMap = { White: 'swatch-white', Yellow: 'swatch-yellow', Rose: 'swatch-rose' };
api.getAvailableMetals().forEach((m, i) => {
const sw = document.createElement('button');
sw.className = `swatch ${colorMap[m.id] || ''}${i === 0 ? ' active' : ''}`;
sw.title = m.name;
sw.onclick = () => {
api.setMaterial(1, m.id, 'Polished');
metalsEl.querySelectorAll('.swatch').forEach(s => s.classList.remove('active'));
sw.classList.add('active');
};
metalsEl.appendChild(sw);
});
// Build diamond buttons
const diamondsEl = document.getElementById('diamonds');
['None', ...api.getAvailableSettingTypes().filter(t => t !== 'none')].forEach((t, i) => {
const btn = document.createElement('button');
btn.className = `btn${i === 0 ? ' active' : ''}`;
btn.textContent = t;
btn.onclick = () => {
if (t === 'None') api.setDiamonds(null);
else api.setDiamonds({ settingType: t });
diamondsEl.querySelectorAll('.btn').forEach(b => b.classList.remove('active'));
btn.classList.add('active');
};
diamondsEl.appendChild(btn);
});
// Width slider
const widthSlider = document.getElementById('width');
const widthVal = document.getElementById('width-val');
const dims = api.getDimensions();
widthSlider.value = dims.widthMm;
widthVal.textContent = `${dims.widthMm.toFixed(1)} mm`;
widthSlider.addEventListener('input', (e) => {
const mm = parseFloat(e.target.value);
api.setWidth(mm);
widthVal.textContent = `${mm.toFixed(1)} mm`;
});
// Price updates
api.events.on('price:updated', (data) => {
if (data.pricing) {
document.getElementById('price').textContent =
`$${data.pricing.totalUsd.toFixed(2)}`;
}
});
// Load initial price
const price = api.getPrice();
if (price) {
document.getElementById('price').textContent =
`$${price.totalUsd.toFixed(2)}`;
}
});
function switchBand(name) {
if (!api) return;
api.switchBand(name);
document.querySelectorAll('.band-btn').forEach(b => {
b.classList.toggle('active', b.textContent.toLowerCase().includes(name));
});
}
</script>
</body>
</html>