Operator screen
Showcase
A process mimic, faceplates, a trend and the alarm list on one screen.
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>Smart.UI 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="hero">
<div class="hero-copy">
<span class="eyebrow">Smart.UI Industrial</span>
<h1>Operator screens and test dashboards built with Web Components.</h1>
<p class="lede">
Gauges, trends, alarms and test screens as framework-independent Web Components. Use them in any framework and connect them to the data you already have.
</p>
<div class="hero-actions">
<a class="cta cta-primary" href="../overview/index.htm">Open the plant overview</a>
<a class="cta" href="../presets/index.htm">Browse instrument presets</a>
</div>
</div>
<div class="hero-stage">
<div class="stage-instruments">
<smart-gauge id="heroPressure"></smart-gauge>
<smart-gauge id="heroFlow"></smart-gauge>
<smart-gauge id="heroVibration"></smart-gauge>
<smart-tank id="heroLevel"></smart-tank>
</div>
<span class="stage-caption">Live: standard components configured with presets</span>
</div>
<button class="hmi-theme-toggle" id="themeToggle" type="button">Light</button>
</header>
<section class="credential">
<div class="credential-body">
<p class="credential-fact">
Smart UI components run in commercial engineering software.
<a href="https://www.htmlelements.com/testimonials/" target="_blank" rel="noopener">What a customer says about them, with screenshots of their product</a>.
</p>
</div>
</section>
<section class="pillars">
<article class="pillar">
<h2>Instrument components</h2>
<p>
Logarithmic and date scales, custom needles, mechanical actions, and integer coercion from <code>int8</code> to <code>uint64</code> for register values. Date and time entry with sub-millisecond resolution for sequence-of-events records.
</p>
<a class="pillar-link" href="../presets/index.htm">14 instrument presets →</a>
</article>
<article class="pillar">
<h2>Screens to start from</h2>
<p>
Complete operator screens: overview, trend, mimic, alarms, asset detail, test sequence, sequence of events, connectivity and AI analysis. Each screen keeps its data in one file, so connecting your own data means replacing one function.
</p>
<a class="pillar-link" href="../overview/index.htm">See the screens →</a>
</article>
<article class="pillar">
<h2>An ISA-101 theme</h2>
<p>
An ISA-101 high-performance theme in light and dark. A grey canvas, desaturated equipment and saturated colour reserved for abnormal states, so colour on the screen always has a meaning.
</p>
<a class="pillar-link" href="../alarms/index.htm">See it under alarm →</a>
</article>
</section>
<section class="screens">
<h2 class="section-title">The screen library</h2>
<div class="screen-grid">
<a class="screen-card" href="../overview/index.htm">
<span class="screen-name">Plant overview</span>
<span class="screen-desc">KPI tiles, instrument row, live trend, tank levels, equipment status</span>
</a>
<a class="screen-card" href="../trend/index.htm">
<span class="screen-name">Trend / historian</span>
<span class="screen-desc">Multi-pen trend with pen scaling, time windows and live/paused modes</span>
</a>
<a class="screen-card" href="../alarms/index.htm">
<span class="screen-name">Alarm management</span>
<span class="screen-desc">ISA-18.2 priorities, banner, acknowledgement and alarm-rate tracking</span>
</a>
<a class="screen-card" href="../mimic/index.htm">
<span class="screen-name">Process mimic</span>
<span class="screen-desc">Live P&ID-style flow diagram in plain SVG, with equipment states and animated flow</span>
</a>
<a class="screen-card" href="../asset-detail/index.htm">
<span class="screen-name">Asset detail</span>
<span class="screen-desc">Instrument cluster, setpoint entry, properties and maintenance history</span>
</a>
<a class="screen-card" href="../test-sequence/index.htm">
<span class="screen-name">Test sequence</span>
<span class="screen-desc">Step tree, measurements against limits, verdict and session yield</span>
</a>
<a class="screen-card" href="../connectivity/index.htm">
<span class="screen-name">Connectivity</span>
<span class="screen-desc">Tags bound into components through Connect, with quality, source timestamps and stale detection</span>
</a>
<a class="screen-card" href="../sequence-of-events/index.htm">
<span class="screen-name">Sequence of events</span>
<span class="screen-desc">A feeder trip at sub-millisecond resolution, compared with the same record truncated to 1 ms</span>
</a>
<a class="screen-card" href="../ai-analysis/index.htm">
<span class="screen-name">AI analysis</span>
<span class="screen-desc">Alarm flood summary, shift report and test run report from data reduced before it is sent to the model</span>
</a>
<a class="screen-card" href="../presets/index.htm">
<span class="screen-name">Instrument presets</span>
<span class="screen-desc">Every preset with the line of code that configures it</span>
</a>
</div>
</section>
<section class="data">
<h2 class="section-title">Bring your own data</h2>
<p class="data-lede">
There is no tag layer or protocol to adopt. The components expose plain properties, so they can be connected to OPC UA, MQTT, a historian, a REST endpoint or a WebSocket feed.
</p>
<pre class="data-code"><code>// Every screen isolates its source in one file. This is the whole seam.
PlantData.subscribe(function (state) {
pressureGauge.value = state.pressure;
flowGauge.value = state.flow;
levelTank.value = state.level;
});</code></pre>
</section>
<footer class="page-footer">
<span>Smart.Industrial is a product line of Smart UI, licensed separately from the suite.</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" />
/* Showcase / landing page.
*
* The hero instruments are the argument the page is making, so they run live
* off the same presets a customer would use - nothing here is a picture. */
window.onload = function () {
const Industrial = Smart.Industrial,
themeToggle = document.getElementById('themeToggle');
const instruments = [
{ element: Industrial.apply(document.getElementById('heroPressure'), 'pressure-gauge'), preset: 'pressure-gauge', phase: 0 },
{ element: Industrial.apply(document.getElementById('heroFlow'), 'flow-meter'), preset: 'flow-meter', phase: 1.7 },
{ element: Industrial.apply(document.getElementById('heroVibration'), 'vibration-gauge'), preset: 'vibration-gauge', phase: 3.1 },
{ element: Industrial.apply(document.getElementById('heroLevel'), 'level-tank'), preset: 'level-tank', phase: 4.4 }
];
instruments.forEach(function (item) {
const bag = Industrial.get(item.preset);
item.min = bag.min;
item.max = bag.max;
});
function applyTheme(theme) {
document.body.setAttribute('theme', theme);
const elements = document.querySelectorAll('smart-gauge, smart-tank');
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 through the mid range: enough movement to read as live, not so much
that the instruments sit permanently in an alarm band on a landing page. */
let tick = 0;
setInterval(function () {
tick += 0.035;
instruments.forEach(function (item) {
const span = item.max - item.min,
centre = item.min + span * 0.5,
swing = span * 0.26;
item.element.value = centre + Math.sin(tick + item.phase) * swing;
});
}, 120);
};
styles.css
/* Smart.UI Industrial showcase / landing page. Uses the industrial theme
tokens so the page itself demonstrates the theme it is selling. */
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: 14px;
line-height: 1.6;
}
.page {
max-width: 1320px;
margin: 0 auto;
padding: 26px 24px 60px;
}
.section-title {
font-size: 13px;
font-weight: 600;
letter-spacing: .12em;
margin: 0 0 18px;
text-transform: uppercase;
/* 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);
}
/* ── Hero ────────────────────────────────────────────────────────────── */
.hero {
position: relative;
display: grid;
grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
gap: 40px;
align-items: center;
padding: 24px 0 40px;
}
.eyebrow {
color: var(--smart-primary);
font-size: 11px;
font-weight: 600;
letter-spacing: .18em;
text-transform: uppercase;
}
.hero h1 {
font-size: 34px;
font-weight: 300;
line-height: 1.25;
margin: 12px 0 16px;
max-width: 18ch;
}
.lede {
color: var(--smart-text-secondary-color);
margin: 0 0 24px;
max-width: 52ch;
}
.hero-actions {
display: flex;
flex-wrap: wrap;
gap: 10px;
}
.cta {
/* on the panel surface: a light border on the light canvas read as plain text */
background: var(--smart-industrial-panel-background, transparent);
border: 1px solid var(--smart-industrial-panel-border, var(--smart-border));
border-radius: var(--smart-border-radius);
color: var(--smart-text-secondary-color);
display: inline-block;
font-size: 12px;
letter-spacing: .06em;
padding: 10px 20px;
text-decoration: none;
text-transform: uppercase;
}
.cta:hover {
background: var(--smart-ui-state-hover);
border-color: var(--smart-ui-state-border-hover);
color: var(--smart-ui-state-color-hover);
}
.cta-primary {
background: var(--smart-primary);
border-color: var(--smart-primary);
color: var(--smart-primary-color);
}
.cta-primary:hover {
background: var(--smart-primary);
border-color: var(--smart-primary);
color: var(--smart-primary-color);
opacity: .88;
}
.hero-stage {
display: flex;
flex-direction: column;
align-items: center;
gap: 10px;
padding: 18px;
background: var(--smart-industrial-panel-background);
border: 1px solid var(--smart-industrial-panel-border);
}
.stage-instruments {
display: grid;
grid-template-columns: repeat(3, auto) auto;
gap: 6px;
align-items: center;
justify-content: center;
}
/* Square boxes: a gauge takes its radius from the smaller side. */
.stage-instruments smart-gauge {
width: 168px;
height: 168px;
}
.stage-instruments smart-tank {
width: 96px;
height: 168px;
}
.stage-caption {
color: var(--smart-text-tertiary-color);
font-size: 11px;
letter-spacing: .05em;
}
.hmi-theme-toggle {
position: absolute;
top: 0;
right: 0;
background: var(--smart-industrial-panel-background, transparent);
border: 1px solid var(--smart-industrial-panel-border, var(--smart-border));
border-radius: var(--smart-border-radius);
color: var(--smart-text-secondary-color);
cursor: pointer;
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);
}
/* ── Who uses the components ───────────────────────────────────────────────────── */
.credential {
display: flex;
align-items: stretch;
gap: 22px;
padding: 22px 24px;
background: var(--smart-industrial-panel-background);
border: 1px solid var(--smart-industrial-panel-border);
border-left: 3px solid var(--smart-primary);
}
.credential-body {
min-width: 0;
}
.credential-fact {
font-size: 16px;
margin: 0;
}
/* the link to the testimonial, in the theme's accent: the browser's own blue was dark blue on the dark theme */
.credential-fact a {
color: var(--smart-industrial-accent-text, var(--smart-primary));
text-underline-offset: 2px;
}
.credential-fact strong {
font-weight: 600;
}
/* Placeholder styling is deliberately unfinished-looking: this block must not
be mistaken for approved copy if the page is screenshotted or published. */
.credential-quote[data-placeholder] {
display: flex;
flex-direction: column;
gap: 3px;
margin: 16px 0 0;
padding: 12px 14px;
border: 1px dashed var(--smart-industrial-alarm-warning);
background: transparent;
}
.quote-placeholder-label {
color: var(--smart-industrial-alarm-warning-text, var(--smart-industrial-alarm-warning));
font-size: 11px;
font-weight: 600;
letter-spacing: .08em;
text-transform: uppercase;
}
.quote-placeholder-note {
color: var(--smart-text-tertiary-color);
font-size: 12px;
}
/* ── Pillars ─────────────────────────────────────────────────────────── */
.pillars {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
gap: 14px;
margin: 40px 0;
}
.pillar {
padding: 20px;
background: var(--smart-industrial-panel-background);
border: 1px solid var(--smart-industrial-panel-border);
}
.pillar h2 {
font-size: 16px;
font-weight: 500;
margin: 0 0 10px;
}
.pillar p {
color: var(--smart-text-secondary-color);
margin: 0 0 14px;
}
.pillar code {
background: var(--smart-ui-state-hover);
font-family: Consolas, "Courier New", monospace;
font-size: 12px;
padding: 1px 5px;
}
.pillar-link {
color: var(--smart-primary);
font-size: 12px;
letter-spacing: .04em;
text-decoration: none;
}
.pillar-link:hover {
text-decoration: underline;
}
/* ── Screen grid ─────────────────────────────────────────────────────── */
.screens {
margin: 44px 0;
}
.screen-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
gap: 12px;
}
.screen-card {
display: flex;
flex-direction: column;
gap: 6px;
padding: 16px 18px;
background: var(--smart-industrial-panel-background);
border: 1px solid var(--smart-industrial-panel-border);
border-left: 3px solid var(--smart-border);
text-decoration: none;
}
.screen-card:hover {
border-left-color: var(--smart-primary);
background: var(--smart-ui-state-hover);
}
.screen-name {
color: var(--smart-text-color);
font-size: 15px;
font-weight: 500;
}
.screen-desc {
color: var(--smart-text-tertiary-color);
font-size: 12px;
line-height: 1.5;
}
/* ── Data section ────────────────────────────────────────────────────── */
.data {
margin: 44px 0;
}
.data-lede {
color: var(--smart-text-secondary-color);
margin: 0 0 18px;
max-width: 76ch;
}
.data-code {
margin: 0;
padding: 16px 18px;
background: var(--smart-industrial-panel-background);
border: 1px solid var(--smart-industrial-panel-border);
border-left: 3px solid var(--smart-primary);
overflow-x: auto;
}
.data-code code {
color: var(--smart-text-secondary-color);
font-family: Consolas, "Courier New", monospace;
font-size: 12.5px;
line-height: 1.75;
white-space: pre;
}
/* ── Footer ──────────────────────────────────────────────────────────── */
.page-footer {
margin-top: 44px;
padding-top: 18px;
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: 12px;
}
/* ── Responsive ──────────────────────────────────────────────────────── */
/* The instruments row is about 620px: in half of anything narrower than 1,200 it ran past the
page, so the words go above the stage there rather than beside it. */
@media (max-width: 1200px) {
.hero {
grid-template-columns: 1fr;
gap: 28px;
padding-top: 44px;
}
.hero h1 {
font-size: 28px;
max-width: none;
}
}
@media (max-width: 1000px) {
.stage-instruments {
grid-template-columns: repeat(2, auto);
}
}
@media (max-width: 620px) {
.credential {
flex-direction: column;
}
.credential-mark {
width: auto;
padding: 10px;
}
}