Operator screen
Presets
Instrument presets applied to gauges and tanks.
index.htm as it is written with the smart-industrial package; industrial-demos.css is the screens' shared layout
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" lang="en">
<head>
<title>Instrument Presets - Smart.Industrial</title>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="stylesheet" type="text/css" href="node_modules/smart-industrial/source/styles/smart.default.css" />
<link rel="stylesheet" type="text/css" href="node_modules/smart-industrial/source/styles/smart.industrial.css" />
<link rel="stylesheet" type="text/css" href="node_modules/smart-industrial/source/styles/smart.industrial-dark.css" />
<link rel="stylesheet" type="text/css" href="industrial-demos.css" />
<link rel="stylesheet" type="text/css" href="styles.css" />
</head>
<body class="viewport" theme="industrial-dark">
<div class="page">
<header class="page-header">
<div>
<h1>Instrument presets</h1>
<p class="page-sub">
Every instrument below is a stock gauge, tank or slider configured by a single call.
Range, units, precision, tick density and alarm bands come from the preset.
</p>
</div>
<button class="hmi-theme-toggle" id="themeToggle" type="button">Light</button>
</header>
<section class="usage">
<pre><code><smart-gauge id="p1"></smart-gauge>
Smart.Industrial.apply(document.getElementById('p1'), 'pressure-gauge');
Smart.Industrial.apply(tank, 'level-tank', { max: 5000, unit: 'L' }); // with overrides
Smart.Industrial.get('flow-meter'); // property bag, for frameworks</code></pre>
</section>
<section class="gallery" id="gallery"></section>
<footer class="page-footer">
<span id="presetCount">—</span>
<span>Bands are theme-driven: <code>industrial-normal</code> stays neutral gray, only
<code>advisory</code>, <code>warning</code> and <code>critical</code> carry colour.</span>
</footer>
</div>
<!-- scripts -->
<script type="module" src="node_modules/smart-industrial/source/smart.industrial.js"></script>
<script type="module" src="index.js"></script>
<script>
/* Shown once every element has rendered, so nothing moves on the way in (styles/demos.css). */
addEventListener('load', function () {
var all = Array.prototype.slice.call(document.querySelectorAll('*')),
ready = function () { document.body.classList.add('ready'); };
Promise.all(all.map(function (e) { return e.localName.indexOf('smart-') === 0 ? customElements.whenDefined(e.localName) : null; })).then(function () {
return Promise.all(all.filter(function (e) { return typeof e.whenRendered === 'function' && !e.isRendered; }).map(function (e) { return e.whenRendered(); }));
}).then(function () { requestAnimationFrame(function () { requestAnimationFrame(ready); }); }, ready);
});
</script>
<script>
/* The site's light/dark switch is the reader's choice: a screen opened from the light site
in a new tab opens light too. Framed, the site's page sets the theme itself. */
addEventListener('load', function () {
var light = false,
toggle = document.querySelector('.hmi-theme-toggle');
try { light = window.top === window && localStorage.getItem('smart-industrial-dark') === 'false'; } catch (e) { }
/* after the screen's own onload, which is where its button starts listening */
if (light && toggle) { setTimeout(function () { if (document.body.getAttribute('theme') === 'industrial-dark') { toggle.click(); } }, 0); }
});
</script>
</body>
</html>
index.js
/// <reference path="../../../source/typescript/smart.elements.d.ts" />
/* Preset gallery.
*
* The whole page is generated from Smart.Industrial.list() and .meta, so any
* preset added to the module shows up here without touching this file. */
window.onload = function () {
const Industrial = Smart.Industrial,
gallery = document.getElementById('gallery'),
presetCount = document.getElementById('presetCount'),
themeToggle = document.getElementById('themeToggle'),
names = Industrial.list();
/* Live values so the instruments are not all frozen at their minimum -
a wall of needles pinned at zero says nothing about the preset. */
const animated = [];
function card(name) {
const info = Industrial.meta[name] || {},
element = info.element || 'smart-gauge',
figure = document.createElement('div');
figure.className = 'card card-' + element.replace('smart-', '');
figure.innerHTML =
'<div class="card-head">' +
'<span class="card-title">' + (info.label || name) + '</span>' +
'<span class="card-element"><' + element + '></span>' +
'</div>' +
'<div class="card-stage"></div>' +
'<p class="card-note">' + (info.note || '') + '</p>' +
'<code class="card-code" tabindex="0">Industrial.apply(el, \'' + name + '\')</code>';
const instrument = document.createElement(element);
figure.querySelector('.card-stage').appendChild(instrument);
Industrial.apply(instrument, name);
const bag = Industrial.get(name);
animated.push({ element: instrument, min: bag.min, max: bag.max, phase: Math.random() * Math.PI * 2 });
return figure;
}
names.forEach(function (name) {
gallery.appendChild(card(name));
});
presetCount.textContent = names.length + ' presets';
/* ── Theme ───────────────────────────────────────────────────────── */
function applyTheme(theme) {
document.body.setAttribute('theme', theme);
const elements = document.querySelectorAll('smart-gauge, smart-tank, smart-slider');
for (let i = 0; i < elements.length; i++) {
elements[i].theme = theme;
}
themeToggle.textContent = theme === 'industrial-dark' ? 'Light' : 'Dark';
}
themeToggle.addEventListener('click', function () {
applyTheme(document.body.getAttribute('theme') === 'industrial-dark' ? 'industrial' : 'industrial-dark');
});
applyTheme('industrial-dark');
/* Sweep each instrument slowly through the middle of its range so the
alarm bands and the needle are both visible at a glance. */
let tick = 0;
setInterval(function () {
tick += 0.04;
animated.forEach(function (item) {
const span = item.max - item.min,
centre = item.min + span * 0.55,
swing = span * 0.3;
item.element.value = centre + Math.sin(tick + item.phase) * swing;
});
}, 120);
};
styles.css
/* Preset gallery - a developer reference page, so it reads as documentation
rather than as an operator screen, but uses the same industrial tokens. */
html,
body {
margin: 0;
}
body.viewport {
background: var(--smart-industrial-canvas, var(--smart-body-background-color));
color: var(--smart-text-color);
font-family: var(--smart-font-family);
font-size: 13px;
}
.page {
max-width: 1440px;
margin: 0 auto;
padding: 22px;
}
/* ── Header ──────────────────────────────────────────────────────────── */
.page-header {
display: flex;
align-items: flex-start;
justify-content: space-between;
gap: 24px;
padding-bottom: 18px;
border-bottom: 1px solid var(--smart-border);
}
.page-header h1 {
font-size: 22px;
font-weight: 600;
letter-spacing: .04em;
margin: 0 0 6px;
}
.page-sub {
color: var(--smart-text-secondary-color);
margin: 0;
max-width: 70ch;
}
.hmi-theme-toggle {
background: transparent;
border: 1px solid var(--smart-border);
border-radius: var(--smart-border-radius);
color: var(--smart-text-secondary-color);
cursor: pointer;
flex: none;
font-family: inherit;
font-size: 11px;
letter-spacing: .06em;
padding: 6px 14px;
text-transform: uppercase;
}
.hmi-theme-toggle:hover {
background: var(--smart-ui-state-hover);
border-color: var(--smart-ui-state-border-hover);
color: var(--smart-ui-state-color-hover);
}
/* ── Usage block ─────────────────────────────────────────────────────── */
.usage {
margin: 18px 0 24px;
background: var(--smart-industrial-panel-background);
border: 1px solid var(--smart-industrial-panel-border);
border-left: 3px solid var(--smart-primary);
overflow-x: auto;
}
.usage pre {
margin: 0;
padding: 14px 16px;
}
.usage code {
color: var(--smart-text-secondary-color);
font-family: Consolas, "Courier New", monospace;
font-size: 12px;
line-height: 1.7;
white-space: pre;
}
/* ── Gallery ─────────────────────────────────────────────────────────── */
.gallery {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
gap: 14px;
}
.card {
display: flex;
flex-direction: column;
background: var(--smart-industrial-panel-background);
border: 1px solid var(--smart-industrial-panel-border);
padding: 12px;
}
.card-head {
display: flex;
align-items: baseline;
justify-content: space-between;
gap: 10px;
margin-bottom: 8px;
}
.card-title {
font-weight: 500;
}
.card-element {
color: var(--smart-text-tertiary-color);
font-family: Consolas, "Courier New", monospace;
font-size: 10px;
}
.card-stage {
display: flex;
align-items: center;
justify-content: center;
min-height: 220px;
}
/* Square box: a gauge takes its radius from the smaller side, so a wide,
short stage would draw the scale larger than the needle can reach. */
.card-stage smart-gauge {
width: 215px;
height: 215px;
}
.card-stage smart-tank {
width: 120px;
height: 215px;
}
.card-slider .card-stage {
min-height: 90px;
padding: 0 6px;
}
.card-stage smart-slider {
width: 100%;
}
.card-note {
color: var(--smart-text-tertiary-color);
font-size: 11px;
margin: 8px 0 10px;
min-height: 2.6em;
}
.card-code {
background: var(--smart-ui-state-hover);
color: var(--smart-text-secondary-color);
display: block;
font-family: Consolas, "Courier New", monospace;
font-size: 11px;
overflow-x: auto;
padding: 6px 8px;
white-space: nowrap;
}
/* ── Footer ──────────────────────────────────────────────────────────── */
.page-footer {
display: flex;
align-items: center;
gap: 18px;
flex-wrap: wrap;
margin-top: 22px;
padding-top: 16px;
border-top: 1px solid var(--smart-border);
/* On the canvas, not on a panel: a step darker, so this needs the
secondary colour to clear the contrast floor. */
color: var(--smart-text-secondary-color);
font-size: 11px;
}
.page-footer code {
background: var(--smart-ui-state-hover);
font-family: Consolas, "Courier New", monospace;
padding: 1px 5px;
}