Operator screen
Batch
An ISA-88 unit procedure, the batch record and release with an electronic signature.
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>Batch Control - 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-elements.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="hmi-screen">
<header class="hmi-header">
<div class="hmi-identity">
<span class="hmi-plant">BATCH</span>
<span class="hmi-area">Unit R-201 · Reactor · ISA-88 unit procedure</span>
</div>
<div class="hmi-header-actions">
<span class="bt-batch" id="batchId">B-2026-0912</span>
<span class="hmi-conn"><span class="hmi-conn-dot" id="unitDot"></span><span id="unitLabel">R-201 · idle</span></span>
<span class="hmi-clock" id="clock">--:--:--</span>
<button class="hmi-theme-toggle" id="themeToggle" type="button">Light</button>
</div>
</header>
<smart-alarm-banner id="alarmBanner" empty-message="No unit alarms"></smart-alarm-banner>
<section class="bt-top">
<div class="hmi-panel">
<div class="hmi-panel-header">
<span>Unit procedure</span>
<span class="hmi-panel-note" id="procedureNote">Not started</span>
</div>
<div class="hmi-panel-content bt-procedure">
<smart-state-machine id="machine" model="isa88" view="compact" label="R-201" interactive show-history history-length="6"></smart-state-machine>
<div class="bt-recipe">
<div class="bt-recipe-row"><span>Recipe</span><b>IBU-400-C v13</b></div>
<div class="bt-recipe-row"><span>Product</span><b>Ibuprofen 400 mg granulate</b></div>
<div class="bt-recipe-row"><span>Batch size</span><b>2,400 kg</b></div>
<div class="bt-recipe-row"><span>Started</span><b id="startedAt">—</b></div>
<div class="bt-recipe-row"><span>Elapsed</span><b id="elapsed">00:00</b></div>
</div>
<smart-permissive id="permissives" label="Start permissives" mode="permissive" show-first-out></smart-permissive>
</div>
</div>
<div class="hmi-panel">
<div class="hmi-panel-header">
<span>Phases</span>
<span class="hmi-panel-note" id="phaseNote">Charge A → Discharge</span>
</div>
<div class="hmi-panel-content bt-phases">
<table class="hmi-table bt-phase-table" aria-label="Recipe phases">
<thead>
<tr>
<th scope="col">#</th>
<th scope="col">Phase</th>
<th scope="col">Parameters</th>
<th scope="col">Planned</th>
<th scope="col">Progress</th>
<th scope="col">State</th>
</tr>
</thead>
<tbody id="phaseRows"></tbody>
</table>
</div>
</div>
</section>
<section class="bt-middle">
<div class="hmi-panel">
<div class="hmi-panel-header">
<span>Unit conditions</span>
<span class="hmi-panel-note">The controller's values; the faceplates only request</span>
</div>
<div class="hmi-panel-content bt-conditions">
<smart-faceplate id="tic201" tag="TIC-201" description="Jacket temperature" unit="°C" min="0" max="150" precision-digits="1" mode="auto"></smart-faceplate>
<smart-faceplate id="sic201" tag="SIC-201" description="Agitator speed" unit="rpm" min="0" max="200" precision-digits="0" mode="auto"></smart-faceplate>
<div class="bt-tiles">
<smart-status-tile id="tileLevel" label="Reactor level" unit="%" precision-digits="1"></smart-status-tile>
<smart-status-tile id="tilePressure" label="Reactor pressure" unit="barg" precision-digits="2"></smart-status-tile>
<smart-status-tile id="tileMass" label="Charged mass" unit="kg" precision-digits="0"></smart-status-tile>
<smart-status-tile id="tilePhase" label="Active phase"></smart-status-tile>
</div>
</div>
</div>
<div class="hmi-panel">
<div class="hmi-panel-header">
<span>Batch trend</span>
<span class="hmi-panel-note">TIC-201 PV and SP, reactor pressure</span>
</div>
<div class="hmi-panel-content bt-trend">
<smart-trend id="trend" live time-span="180000" show-readout show-pens label="Batch B-2026-0912"></smart-trend>
</div>
</div>
</section>
<section class="bt-bottom">
<div class="hmi-panel">
<div class="hmi-panel-header">
<span>Batch record</span>
<span class="hmi-panel-note" id="recordNote">Every phase transition, command and deviation, hash-chained</span>
</div>
<div class="hmi-panel-content bt-record">
<smart-audit-trail id="record" label="Batch record B-2026-0912" max-rows="200" show-review></smart-audit-trail>
</div>
</div>
<div class="hmi-panel">
<div class="hmi-panel-header">
<span>Release</span>
<span class="hmi-panel-note" id="releaseNote">Available when the procedure is complete</span>
</div>
<div class="hmi-panel-content bt-release">
<div class="bt-release-summary" id="releaseSummary">
<div class="bt-recipe-row"><span>Yield</span><b id="yield">—</b></div>
<div class="bt-recipe-row"><span>Deviations</span><b id="deviations">0</b></div>
<div class="bt-recipe-row"><span>Operator holds</span><b id="holds">0</b></div>
</div>
<button class="hmi-button hmi-button-primary" id="release" type="button" disabled>Sign for release</button>
<smart-esignature id="sign" label="Batch release" lock-meaning meaning="approved" require-reason auto-close="2000"></smart-esignature>
<smart-signature-block id="releaseBlock"></smart-signature-block>
</div>
</div>
</section>
</div>
<!-- scripts -->
<script type="module" src="node_modules/smart-industrial/source/modules/smart.industrial.audit.js"></script>
<script type="module" src="node_modules/smart-industrial/source/modules/smart.alarmbanner.js"></script>
<script type="module" src="node_modules/smart-industrial/source/modules/smart.statemachine.js"></script>
<script type="module" src="node_modules/smart-industrial/source/modules/smart.permissive.js"></script>
<script type="module" src="node_modules/smart-industrial/source/modules/smart.faceplate.js"></script>
<script type="module" src="node_modules/smart-industrial/source/modules/smart.statustile.js"></script>
<script type="module" src="node_modules/smart-industrial/source/modules/smart.trend.js"></script>
<script type="module" src="node_modules/smart-industrial/source/modules/smart.audittrail.js"></script>
<script type="module" src="node_modules/smart-industrial/source/modules/smart.esignature.js"></script>
<script type="module" src="node_modules/smart-industrial/source/modules/smart.signatureblock.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" />
/* Batch control.
*
* An ISA-88 unit procedure on a reactor: the unit's state machine, the recipe's phases, the
* conditions the phases drive, a batch trend, and the batch record that every transition,
* command and deviation is written to - hash-chained, and signed for release at the end.
*
* The screen commands nothing directly. The state machine raises commandRequest, the
* faceplates raise setpointChange, the signature panel raises signRequest; a simulated unit
* controller answers each after a moment, and only its answer moves anything on screen.
* Replace `unit` with the batch executive and the screen is unchanged. */
window.onload = function () {
const Audit = window.Smart.Industrial.Audit,
el = function (id) {
return document.getElementById(id);
},
machine = el('machine'),
permissives = el('permissives'),
tic = el('tic201'),
sic = el('sic201'),
trend = el('trend'),
record = el('record'),
sign = el('sign'),
banner = el('alarmBanner');
const OPERATOR = { id: 'jdoe', name: 'John Doe' },
BATCH = 'B-2026-0912';
/* ── The recipe ─────────────────────────────────────────────────────── */
/* Durations are seconds, compressed: a real phase runs for the better part of an hour. */
const PHASES = [
{ id: 'charge-a', name: 'Charge A', params: 'Solvent 1,600 kg', seconds: 14, level: 62, mass: 1600, temperature: 22, speed: 60 },
{ id: 'charge-b', name: 'Charge B', params: 'API 800 kg', seconds: 12, level: 90, mass: 2400, temperature: 22, speed: 90 },
{ id: 'heat', name: 'Heat', params: 'TIC-201 SP 85 °C, ramp 2 °C/min', seconds: 18, level: 90, mass: 2400, temperature: 85, speed: 120 },
{ id: 'react', name: 'React', params: 'Hold 85 °C, 120 rpm', seconds: 24, level: 90, mass: 2400, temperature: 85, speed: 120 },
{ id: 'sample', name: 'Sample', params: 'Operator sample, lab result', seconds: 10, level: 90, mass: 2400, temperature: 85, speed: 60 },
{ id: 'cool', name: 'Cool', params: 'TIC-201 SP 30 °C', seconds: 18, level: 90, mass: 2400, temperature: 30, speed: 90 },
{ id: 'discharge', name: 'Discharge', params: 'To centrifuge C-301', seconds: 14, level: 4, mass: 0, temperature: 30, speed: 0 }
];
/* ── The batch record ───────────────────────────────────────────────── */
const trail = new Audit.Trail({ system: 'R-201 batch executive' });
trail.subscribe(function (entry) {
record.addEntry(entry);
el('recordNote').textContent = trail.length + ' entries, chain ' + (trail.verify().ok ? 'intact' : 'broken');
});
function log(action, field, oldValue, newValue, reason, signature) {
trail.append({
user: OPERATOR.id, userName: OPERATOR.name, action: action, record: BATCH,
field: field, oldValue: oldValue, newValue: newValue, reason: reason, signature: signature
});
}
/* ── The simulated unit ─────────────────────────────────────────────── */
const unit = {
state: 'Idle',
phaseIndex: -1,
phaseProgress: 0, /* seconds into the phase */
phaseDone: [],
temperature: 22,
temperatureSp: 22,
speed: 0,
speedSp: 0,
level: 4,
pressure: 0.05,
mass: 0,
startedAt: null,
elapsed: 0,
holds: 0,
deviations: 0,
alarms: []
};
function phase() {
return unit.phaseIndex >= 0 && unit.phaseIndex < PHASES.length ? PHASES[unit.phaseIndex] : null;
}
/* The controller answers a command after a moment, through the state the machine
asked to go to. Acting states complete on their own after their work is done. */
machine.addEventListener('commandRequest', function (event) {
const command = event.detail.command,
target = event.detail.to;
log('command', 'unit state', unit.state, target, command);
setTimeout(function () {
setState(target);
if (command === 'Hold') {
unit.holds++;
el('holds').textContent = unit.holds;
}
if (command === 'Start') {
unit.startedAt = Date.now();
unit.elapsed = 0;
el('startedAt').textContent = new Date().toLocaleTimeString('en-GB', { hour12: false });
beginPhase(0);
}
if (command === 'Reset') {
resetUnit();
}
}, 600);
});
function setState(state) {
const before = unit.state;
unit.state = state;
machine.state = state;
machine.enteredAt = Date.now();
if (before !== state) {
log('update', 'unit state', before, state);
}
el('unitDot').className = 'hmi-conn-dot' + (state === 'Running' ? '' : (state === 'Aborted' || state === 'Aborting' ? ' hmi-conn-error' : ' hmi-conn-connecting'));
el('unitLabel').textContent = 'R-201 · ' + state.toLowerCase();
el('procedureNote').textContent = state === 'Idle' ? 'Not started' : state === 'Complete' ? 'Procedure complete' : state;
/* Acting states finish by themselves. */
const next = machine.nextAutomatic();
if (next && state !== 'Running') {
setTimeout(function () {
if (unit.state === state) {
setState(next);
}
}, 1500);
}
if (state === 'Complete') {
el('release').disabled = false;
el('releaseNote').textContent = 'Procedure complete - sign to release';
el('yield').textContent = '98.4 %';
}
}
function beginPhase(index) {
const before = phase();
unit.phaseIndex = index;
unit.phaseProgress = 0;
const current = phase();
if (current) {
unit.temperatureSp = current.temperature;
unit.speedSp = current.speed;
tic.setpoint = unit.temperatureSp;
sic.setpoint = unit.speedSp;
log('update', 'phase', before ? before.name : '', current.name, 'recipe step ' + (index + 1));
}
renderPhases();
}
function resetUnit() {
unit.phaseIndex = -1;
unit.phaseDone = [];
unit.phaseProgress = 0;
unit.temperatureSp = 22;
unit.speedSp = 0;
unit.level = 4;
unit.mass = 0;
unit.startedAt = null;
unit.elapsed = 0;
el('startedAt').textContent = '—';
el('release').disabled = true;
el('releaseNote').textContent = 'Available when the procedure is complete';
el('yield').textContent = '—';
renderPhases();
}
/* Four ticks a second: the physics, the phase clock, the trend, the tiles. */
setInterval(function () {
const dt = 0.25,
running = unit.state === 'Running',
current = phase();
/* Temperature and speed follow their setpoints; level and mass follow the phase. */
unit.temperature += (unit.temperatureSp - unit.temperature) * 0.08 + (Math.random() - 0.5) * 0.3;
unit.speed += (unit.speedSp - unit.speed) * 0.2;
if (current && running) {
unit.level += (current.level - unit.level) * 0.1;
unit.mass += (current.mass - unit.mass) * 0.1;
}
unit.pressure = Math.max(0, (unit.temperature - 22) * 0.012 + (Math.random() - 0.5) * 0.01);
if (running && current) {
unit.phaseProgress += dt;
unit.elapsed += dt;
/* Heat cannot finish until the jacket is at temperature; that is the deviation
the record notes when it takes too long. */
const atTemperature = Math.abs(unit.temperature - unit.temperatureSp) < 2,
due = unit.phaseProgress >= current.seconds && (current.id !== 'heat' || atTemperature);
if (current.id === 'heat' && unit.phaseProgress > current.seconds * 1.6 && !unit.heatDeviation) {
unit.heatDeviation = true;
unit.deviations++;
el('deviations').textContent = unit.deviations;
log('deviation', 'TIC-201', unit.temperatureSp, Number(unit.temperature.toFixed(1)), 'heat phase over planned time');
raiseAlarm('TIC-201.DEV', 'Jacket temperature slow to reach setpoint', 2);
}
if (due) {
unit.phaseDone.push(current.id);
if (unit.phaseIndex + 1 < PHASES.length) {
beginPhase(unit.phaseIndex + 1);
}
else {
unit.phaseIndex = PHASES.length;
renderPhases();
setState('Complete');
}
}
}
/* Report. */
tic.processValue = Number(unit.temperature.toFixed(1));
tic.output = Math.max(0, Math.min(100, 50 + (unit.temperatureSp - unit.temperature) * 4));
sic.processValue = Math.round(unit.speed);
sic.output = Math.max(0, Math.min(100, unit.speedSp / 2));
el('tileLevel').value = unit.level;
el('tilePressure').value = unit.pressure;
el('tileMass').value = unit.mass;
el('tilePhase').value = current ? current.name : (unit.state === 'Complete' ? 'Complete' : '—');
el('tilePhase').state = running ? 'ok' : (unit.state === 'Idle' || unit.state === 'Complete' ? 'neutral' : 'warning');
el('tilePressure').state = unit.pressure > 1 ? 'warning' : 'ok';
el('elapsed').textContent = clock(unit.elapsed);
trend.push({ timestamp: Date.now(), temperature: unit.temperature, setpoint: unit.temperatureSp, pressure: unit.pressure });
renderProgress();
}, 250);
function clock(seconds) {
const s = Math.floor(seconds);
return ('0' + Math.floor(s / 60)).slice(-2) + ':' + ('0' + (s % 60)).slice(-2);
}
/* ── Alarms ─────────────────────────────────────────────────────────── */
function raiseAlarm(tag, message, priority) {
const alarm = { id: tag + '-' + Date.now(), tag: tag, message: message, priority: priority, timestamp: Date.now(), acknowledged: false, active: true };
unit.alarms.push(alarm);
banner.alarms = unit.alarms.slice();
}
banner.addEventListener('acknowledge', function (event) {
const acknowledged = event.detail.alarm || {};
unit.alarms.forEach(function (alarm) {
if (alarm.id === acknowledged.id) {
alarm.acknowledged = true;
}
});
/* An acknowledgement is a batch event too. */
log('acknowledge', 'alarm', 'unacknowledged', 'acknowledged', acknowledged.tag || '');
banner.alarms = unit.alarms.slice();
setTimeout(function () {
unit.alarms = unit.alarms.filter(function (alarm) {
return !alarm.acknowledged;
});
banner.alarms = unit.alarms.slice();
}, 4000);
});
/* ── Faceplate requests: the controller decides ────────────────────── */
tic.addEventListener('setpointChange', function (event) {
const requested = Number(event.detail.value);
setTimeout(function () {
log('update', 'TIC-201 setpoint', unit.temperatureSp, requested, 'operator request in ' + (phase() ? phase().name : unit.state));
unit.temperatureSp = requested;
tic.setpoint = requested;
}, 700);
});
sic.addEventListener('setpointChange', function (event) {
const requested = Number(event.detail.value);
setTimeout(function () {
log('update', 'SIC-201 setpoint', unit.speedSp, requested, 'operator request');
unit.speedSp = requested;
sic.setpoint = requested;
}, 700);
});
/* ── Permissives ────────────────────────────────────────────────────── */
permissives.conditions = [
{ id: 'p1', label: 'Reactor empty and clean', satisfied: true },
{ id: 'p2', label: 'Charge valves closed', satisfied: true },
{ id: 'p3', label: 'Jacket supply available', satisfied: true },
{ id: 'p4', label: 'Recipe approved (v13)', satisfied: true },
{ id: 'p5', label: 'Operator qualified for R-201', satisfied: true }
];
/* ── Phases table ───────────────────────────────────────────────────── */
function renderPhases() {
const body = el('phaseRows');
body.innerHTML = '';
PHASES.forEach(function (item, index) {
const done = unit.phaseDone.indexOf(item.id) !== -1,
active = index === unit.phaseIndex,
tr = document.createElement('tr');
tr.className = 'bt-phase' + (active ? ' bt-phase-active' : '') + (done ? ' bt-phase-done' : '');
tr.innerHTML =
'<td class="hmi-num">' + (index + 1) + '</td>' +
'<td><b>' + item.name + '</b></td>' +
'<td>' + item.params + '</td>' +
'<td class="hmi-num">' + clock(item.seconds) + '</td>' +
'<td><div class="bt-bar" role="progressbar" aria-valuemin="0" aria-valuemax="100" aria-valuenow="' + (done ? 100 : 0) + '" aria-label="' + item.name + ' progress"><div class="bt-bar-fill" data-phase="' + item.id + '" style="width:' + (done ? 100 : 0) + '%"></div></div></td>' +
'<td class="bt-phase-state" data-phase="' + item.id + '">' + (done ? 'Complete' : active ? 'Active' : 'Pending') + '</td>';
body.appendChild(tr);
});
}
function renderProgress() {
const current = phase();
if (!current) {
return;
}
const fill = document.querySelector('.bt-bar-fill[data-phase="' + current.id + '"]'),
percent = Math.min(100, unit.phaseProgress / current.seconds * 100);
if (fill) {
fill.style.width = percent + '%';
fill.parentNode.setAttribute('aria-valuenow', Math.round(percent));
}
const state = document.querySelector('.bt-phase-state[data-phase="' + current.id + '"]');
if (state) {
state.textContent = unit.state === 'Running' ? 'Active · ' + Math.round(percent) + ' %' : unit.state;
}
}
/* ── Release ────────────────────────────────────────────────────────── */
el('release').addEventListener('click', function () {
const batchRecord = {
id: BATCH, title: 'Batch record ' + BATCH,
fields: [
{ label: 'Recipe', value: 'IBU-400-C v13' },
{ label: 'Yield', value: el('yield').textContent },
{ label: 'Deviations', value: unit.deviations },
{ label: 'Record entries', value: trail.length },
{ label: 'Record head', value: trail.head.substring(0, 16) + '…' }
]
};
sign.sign(batchRecord).then(function (signature) {
el('releaseBlock').signature = signature;
el('releaseBlock').record = batchRecord;
el('release').disabled = true;
el('releaseNote').textContent = 'Released';
log('sign', 'batch release', 'complete', 'released', signature.reason, signature);
}, function () { });
});
sign.addEventListener('signRequest', function (event) {
setTimeout(function () {
if (event.detail.userId === 'asmith' && event.detail.password === 'qa2026') {
sign.accept({ userName: 'Alice Smith' });
}
else {
sign.reject(event.detail.userId === 'jdoe' ? 'operators cannot release a batch' : '');
}
}, 600);
});
record.addEventListener('reviewRequest', function (event) {
log('review', 'batch record', '', event.detail.count + ' entries', 'shift review');
});
/* ── Start-up ───────────────────────────────────────────────────────── */
trend.pens = [
{ field: 'temperature', label: 'TIC-201 PV', unit: '°C', min: 0, max: 150, precision: 1 },
{ field: 'setpoint', label: 'TIC-201 SP', unit: '°C', min: 0, max: 150, precision: 1 },
{ field: 'pressure', label: 'PT-201', unit: 'barg', min: 0, max: 2, precision: 2 }
];
tic.setpoint = 22;
sic.setpoint = 0;
tic.alarmLimits = { hi: 95, hiHi: 105 };
machine.state = 'Idle';
machine.mode = 'Production';
log('login', 'R-201 HMI', '', OPERATOR.id);
log('download', 'recipe', 'IBU-400-C v12', 'IBU-400-C v13', 'change control CC-2026-041');
renderPhases();
setInterval(function () {
el('clock').textContent = new Date().toLocaleTimeString('en-GB', { hour12: false });
}, 1000);
const toggle = el('themeToggle');
toggle.addEventListener('click', function () {
const dark = document.body.getAttribute('theme') === 'industrial-dark';
document.body.setAttribute('theme', dark ? 'industrial' : 'industrial-dark');
toggle.textContent = dark ? 'Dark' : 'Light';
});
};
styles.css
/* Batch control screen. Colours come from the industrial theme tokens; the only saturated
colour belongs to an alarm priority or the active phase's progress. */
html,
body {
height: 100%;
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;
}
.bt-batch {
font-family: Consolas, Monaco, monospace;
font-size: 13px;
font-weight: 700;
letter-spacing: 0.04em;
padding: 2px 8px;
border: 1px solid var(--smart-industrial-panel-border);
border-radius: 3px;
}
/* ── Rows ────────────────────────────────────────────────────────────── */
.bt-top,
.bt-middle,
.bt-bottom {
display: grid;
gap: 10px;
min-width: 0;
}
.bt-top {
grid-template-columns: minmax(360px, 2fr) minmax(420px, 3fr);
}
.bt-middle {
grid-template-columns: minmax(420px, 3fr) minmax(360px, 2fr);
}
.bt-bottom {
grid-template-columns: minmax(480px, 3fr) minmax(300px, 1fr);
}
@media (max-width: 1100px) {
.bt-top,
.bt-middle,
.bt-bottom {
grid-template-columns: 1fr;
}
}
/* ── Unit procedure ──────────────────────────────────────────────────── */
.bt-procedure {
display: grid;
grid-template-columns: auto 1fr;
gap: 12px 16px;
align-items: start;
}
.bt-procedure smart-permissive {
grid-column: 1 / -1;
}
.bt-recipe {
display: flex;
flex-direction: column;
gap: 4px;
font-size: 12px;
}
.bt-recipe-row {
display: flex;
justify-content: space-between;
gap: 12px;
padding: 4px 8px;
border-bottom: 1px solid var(--smart-industrial-panel-border);
}
.bt-recipe-row b {
font-variant-numeric: tabular-nums;
text-align: right;
}
/* ── Phases ──────────────────────────────────────────────────────────── */
.bt-phases {
padding: 0;
}
.bt-phase-table td {
vertical-align: middle;
}
.bt-phase-active td {
background: var(--smart-industrial-panel-header-background);
}
.bt-phase-done {
opacity: 0.7;
}
.bt-bar {
width: 140px;
height: 8px;
/* a groove in both themes (the readout background is a display's black) */
background: var(--smart-industrial-canvas);
border: 1px solid var(--smart-industrial-panel-border);
border-radius: 4px;
overflow: hidden;
}
.bt-bar-fill {
height: 100%;
background: var(--smart-industrial-running, var(--smart-primary));
}
.bt-phase-done .bt-bar-fill {
background: var(--smart-industrial-ok);
}
.bt-phase-state {
white-space: nowrap;
}
/* ── Conditions ──────────────────────────────────────────────────────── */
.bt-conditions {
display: grid;
grid-template-columns: auto auto 1fr;
gap: 12px;
align-items: start;
}
.bt-tiles {
display: grid;
grid-template-columns: repeat(2, minmax(140px, 1fr));
gap: 8px;
}
@media (max-width: 900px) {
.bt-conditions {
grid-template-columns: 1fr;
}
}
.bt-trend smart-trend {
width: 100%;
height: 300px;
}
/* ── Record and release ──────────────────────────────────────────────── */
.bt-record {
padding: 0;
--smart-audit-max-height: 320px;
}
.bt-release {
display: flex;
flex-direction: column;
gap: 12px;
}
.bt-release-summary {
display: flex;
flex-direction: column;
gap: 4px;
font-size: 12px;
}
.bt-release smart-esignature {
width: 100%;
}
Components on this screen
Alarm BannerAudit TrailE-SignatureFaceplatePermissiveSignature BlockState MachineStatus TileTrend