Clinic — Vitals Input Panel
A triage vitals capture panel for nursing workflows, with paired blood-pressure inputs plus heart rate, temperature, SpO2, respiratory rate, weight and height. Each field carries its unit and a normal-range hint, and flags itself in warning or critical color in real time as values fall outside range. Height and weight auto-compute a BMI badge with category, and a Save action summarizes every captured reading, timestamps it and toasts confirmation.
MCP
Kod
:root {
--teal: #129c93;
--teal-d: #0c7a73;
--teal-700: #0a655f;
--teal-50: #e7f5f3;
--coral: #ff7a66;
--coral-soft: #ffe6df;
--ink: #16322f;
--ink-2: #3a534f;
--muted: #6b827e;
--bg: #f1f7f6;
--white: #ffffff;
--line: rgba(16, 50, 47, 0.1);
--line-2: rgba(16, 50, 47, 0.18);
--ok: #2f9e6f;
--warn: #d98a2b;
--danger: #d4503e;
--font: "Inter", system-ui, -apple-system, sans-serif;
--r-sm: 8px;
--r-md: 14px;
--r-lg: 20px;
--shadow-1: 0 1px 2px rgba(16, 50, 47, 0.05), 0 4px 14px rgba(16, 50, 47, 0.06);
--shadow-2: 0 16px 40px rgba(12, 122, 115, 0.16);
}
*,
*::before,
*::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
font-family: var(--font);
background: var(--bg);
color: var(--ink);
-webkit-font-smoothing: antialiased;
line-height: 1.5;
}
/* ── Layout ── */
.vitals {
max-width: 680px;
margin: 0 auto;
padding: 32px 20px 56px;
display: flex;
flex-direction: column;
gap: 22px;
}
/* ── Header ── */
.vitals-head {
display: flex;
flex-direction: column;
gap: 16px;
}
.head-top {
display: flex;
align-items: flex-start;
justify-content: space-between;
gap: 14px;
}
.eyebrow {
font-size: 0.74rem;
font-weight: 700;
letter-spacing: 0.08em;
text-transform: uppercase;
color: var(--teal-d);
}
.vitals-head h1 {
font-size: 1.6rem;
font-weight: 800;
letter-spacing: -0.02em;
margin-top: 2px;
}
/* ── BMI pill ── */
.bmi-pill {
display: inline-flex;
align-items: center;
gap: 8px;
background: var(--white);
border: 1px solid var(--line);
border-radius: 999px;
padding: 8px 14px;
font-size: 0.82rem;
font-weight: 600;
color: var(--muted);
box-shadow: var(--shadow-1);
white-space: nowrap;
transition: background 0.2s, border-color 0.2s, color 0.2s;
}
.bmi-pill strong {
font-size: 1.05rem;
font-weight: 800;
color: var(--ink);
}
.bmi-cat {
font-size: 0.72rem;
font-weight: 700;
padding: 2px 8px;
border-radius: 999px;
background: var(--teal-50);
color: var(--teal-d);
text-transform: capitalize;
}
.bmi-pill[data-cat="under"] {
border-color: rgba(217, 138, 43, 0.4);
}
.bmi-pill[data-cat="under"] .bmi-cat {
background: rgba(217, 138, 43, 0.16);
color: var(--warn);
}
.bmi-pill[data-cat="normal"] {
border-color: rgba(47, 158, 111, 0.4);
}
.bmi-pill[data-cat="normal"] .bmi-cat {
background: rgba(47, 158, 111, 0.16);
color: var(--ok);
}
.bmi-pill[data-cat="over"] {
border-color: rgba(217, 138, 43, 0.4);
}
.bmi-pill[data-cat="over"] .bmi-cat {
background: rgba(217, 138, 43, 0.16);
color: var(--warn);
}
.bmi-pill[data-cat="obese"] {
border-color: rgba(212, 80, 62, 0.4);
}
.bmi-pill[data-cat="obese"] .bmi-cat {
background: rgba(212, 80, 62, 0.14);
color: var(--danger);
}
/* ── Patient row ── */
.patient {
display: flex;
align-items: center;
gap: 12px;
background: var(--white);
border: 1px solid var(--line);
border-radius: var(--r-md);
padding: 12px 16px;
box-shadow: var(--shadow-1);
}
.avatar {
display: grid;
place-items: center;
width: 40px;
height: 40px;
flex-shrink: 0;
border-radius: 50%;
background: linear-gradient(150deg, var(--teal-50), #d9efec);
color: var(--teal-700);
font-size: 0.84rem;
font-weight: 800;
letter-spacing: 0.02em;
}
.patient-meta {
display: flex;
flex-direction: column;
min-width: 0;
}
.patient-name {
font-size: 0.98rem;
font-weight: 700;
letter-spacing: -0.01em;
}
.patient-sub {
font-size: 0.82rem;
color: var(--muted);
}
/* ── Timestamp ── */
.stamp {
font-size: 0.82rem;
font-weight: 600;
color: var(--muted);
display: inline-flex;
align-items: center;
gap: 7px;
}
.stamp::before {
content: "";
width: 7px;
height: 7px;
border-radius: 50%;
background: var(--coral);
flex-shrink: 0;
}
.stamp.is-saved {
color: var(--ok);
}
.stamp.is-saved::before {
background: var(--ok);
}
/* ── Form grid ── */
.grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 14px;
}
.span-2 {
grid-column: 1 / -1;
}
/* ── Field ── */
.field {
background: var(--white);
border: 1px solid var(--line);
border-radius: var(--r-md);
padding: 16px;
box-shadow: var(--shadow-1);
border-left: 3px solid var(--line);
transition: border-color 0.18s, box-shadow 0.18s;
}
.field.is-warn {
border-left-color: var(--warn);
}
.field.is-danger {
border-left-color: var(--danger);
}
.field.is-ok {
border-left-color: var(--ok);
}
.field-head {
display: flex;
align-items: center;
justify-content: space-between;
gap: 8px;
margin-bottom: 10px;
}
.field-head label {
font-size: 0.88rem;
font-weight: 700;
color: var(--ink);
transition: color 0.18s;
}
.field.is-warn .field-head label {
color: var(--warn);
}
.field.is-danger .field-head label {
color: var(--danger);
}
/* ── Flag chip ── */
.flag {
font-size: 0.68rem;
font-weight: 700;
padding: 3px 9px;
border-radius: 999px;
white-space: nowrap;
letter-spacing: 0.02em;
}
.flag[data-level="warn"] {
background: rgba(217, 138, 43, 0.16);
color: var(--warn);
}
.flag[data-level="danger"] {
background: rgba(212, 80, 62, 0.14);
color: var(--danger);
}
/* ── Inputs ── */
.input-wrap,
.bp-input {
display: flex;
align-items: baseline;
gap: 8px;
border: 1px solid var(--line-2);
border-radius: var(--r-sm);
padding: 10px 12px;
background: #fbfdfc;
transition: border-color 0.15s, box-shadow 0.15s, background 0.15s;
}
.input-wrap:focus-within,
.bp-input:focus-within {
border-color: var(--teal);
background: var(--white);
box-shadow: 0 0 0 3px rgba(18, 156, 147, 0.14);
}
.field.is-warn .input-wrap,
.field.is-warn .bp-input.is-flagged {
border-color: rgba(217, 138, 43, 0.5);
}
.field.is-danger .input-wrap,
.field.is-danger .bp-input.is-flagged {
border-color: rgba(212, 80, 62, 0.5);
}
.input-wrap input,
.bp-input input {
border: none;
outline: none;
background: transparent;
font: inherit;
font-size: 1.2rem;
font-weight: 700;
color: var(--ink);
width: 100%;
min-width: 0;
letter-spacing: -0.01em;
}
.input-wrap input::placeholder,
.bp-input input::placeholder {
color: var(--muted);
font-weight: 600;
opacity: 0.55;
}
/* hide native spinners for a cleaner clinical look */
input[type="number"]::-webkit-outer-spin-button,
input[type="number"]::-webkit-inner-spin-button {
-webkit-appearance: none;
margin: 0;
}
input[type="number"] {
-moz-appearance: textfield;
appearance: textfield;
}
.unit {
font-size: 0.78rem;
font-weight: 700;
color: var(--muted);
white-space: nowrap;
flex-shrink: 0;
}
/* ── Blood pressure paired row ── */
.bp-row {
display: flex;
align-items: center;
gap: 12px;
}
.bp-input {
flex: 1;
min-width: 0;
}
.bp-sep {
font-size: 1.4rem;
font-weight: 300;
color: var(--line-2);
}
/* ── Hint ── */
.hint {
font-size: 0.78rem;
color: var(--muted);
margin-top: 9px;
}
/* ── Footer ── */
.form-foot {
display: flex;
align-items: center;
justify-content: space-between;
gap: 16px;
flex-wrap: wrap;
margin-top: 4px;
}
.legend {
display: flex;
align-items: center;
gap: 6px;
font-size: 0.78rem;
font-weight: 600;
color: var(--muted);
flex-wrap: wrap;
}
.dot {
width: 9px;
height: 9px;
border-radius: 50%;
margin-left: 8px;
}
.dot:first-child {
margin-left: 0;
}
.dot.ok {
background: var(--ok);
}
.dot.warn {
background: var(--warn);
}
.dot.danger {
background: var(--danger);
}
.foot-actions {
display: flex;
gap: 10px;
}
/* ── Buttons ── */
.btn {
border: none;
border-radius: 10px;
padding: 11px 20px;
font: inherit;
font-weight: 700;
font-size: 0.88rem;
cursor: pointer;
transition: transform 0.12s, background 0.15s, border-color 0.15s, color 0.15s;
}
.btn:active {
transform: translateY(1px);
}
.btn:focus-visible {
outline: 2px solid var(--teal);
outline-offset: 2px;
}
.btn.ghost {
background: var(--white);
border: 1px solid var(--line-2);
color: var(--ink-2);
}
.btn.ghost:hover {
background: var(--teal-50);
border-color: var(--teal);
color: var(--teal-d);
}
.btn.solid {
background: var(--teal-d);
color: #fff;
box-shadow: 0 6px 16px rgba(12, 122, 115, 0.22);
}
.btn.solid:hover {
background: var(--teal-700);
}
/* ── Toast ── */
.toast {
position: fixed;
left: 50%;
bottom: 26px;
transform: translateX(-50%);
background: var(--ink);
color: #fff;
padding: 13px 20px;
border-radius: 12px;
font-size: 0.9rem;
font-weight: 500;
box-shadow: var(--shadow-2);
z-index: 50;
max-width: 90vw;
line-height: 1.45;
}
@media (max-width: 520px) {
.vitals {
padding: 24px 16px 48px;
}
.head-top {
flex-direction: column;
align-items: stretch;
}
.bmi-pill {
align-self: flex-start;
}
.grid {
grid-template-columns: 1fr;
}
.bp-row {
flex-direction: column;
align-items: stretch;
gap: 8px;
}
.bp-sep {
display: none;
}
.form-foot {
flex-direction: column;
align-items: stretch;
}
.foot-actions {
justify-content: flex-end;
}
}// ── Toast ──────────────────────────────────────────────────────────────────
const toast = document.getElementById("toast");
function showToast(msg) {
toast.textContent = msg;
toast.hidden = false;
clearTimeout(showToast._t);
showToast._t = setTimeout(() => (toast.hidden = true), 3200);
}
// ── Elements ─────────────────────────────────────────────────────────────────
const form = document.getElementById("vitals-form");
const stamp = document.getElementById("stamp");
const bmiPill = document.getElementById("bmi-pill");
const bmiValue = document.getElementById("bmi-value");
const bmiCat = document.getElementById("bmi-cat");
// Single inputs to range-check (BP handled separately because it's paired).
const rangeInputs = ["hr", "temp", "spo2", "rr"].map((id) => document.getElementById(id));
const sys = document.getElementById("sys");
const dia = document.getElementById("dia");
const weight = document.getElementById("weight");
const height = document.getElementById("height");
// ── Range evaluation ─────────────────────────────────────────────────────────
// Returns "" (empty/ok-unknown), "ok", "warn" (out of normal) or "danger" (critical).
function evaluate(input) {
const raw = input.value.trim();
if (raw === "") return "";
const v = parseFloat(raw);
if (Number.isNaN(v)) return "";
const low = parseFloat(input.dataset.low);
const high = parseFloat(input.dataset.high);
const critLow = parseFloat(input.dataset.critLow);
const critHigh = parseFloat(input.dataset.critHigh);
if (v < critLow || v > critHigh) return "danger";
if (v < low || v > high) return "warn";
return "ok";
}
const FLAG_TEXT = {
high: "Above range",
low: "Below range",
critHigh: "Critically high",
critLow: "Critically low",
};
function flagLabel(input, level) {
const v = parseFloat(input.value);
const high = parseFloat(input.dataset.high);
if (level === "danger") return v > high ? FLAG_TEXT.critHigh : FLAG_TEXT.critLow;
return v > high ? FLAG_TEXT.high : FLAG_TEXT.low;
}
function applyField(fieldset, level) {
fieldset.classList.toggle("is-ok", level === "ok");
fieldset.classList.toggle("is-warn", level === "warn");
fieldset.classList.toggle("is-danger", level === "danger");
}
function setFlag(name, level, text) {
const flag = document.querySelector(`.flag[data-flag="${name}"]`);
if (!flag) return;
if (level === "warn" || level === "danger") {
flag.hidden = false;
flag.dataset.level = level;
flag.textContent = text;
} else {
flag.hidden = true;
flag.removeAttribute("data-level");
flag.textContent = "";
}
}
// Rank levels so the worse of two BP readings wins the field styling.
const RANK = { "": 0, ok: 1, warn: 2, danger: 3 };
function worse(a, b) {
return RANK[a] >= RANK[b] ? a : b;
}
// ── Per-vital handlers ───────────────────────────────────────────────────────
function checkSingle(input) {
const level = evaluate(input);
const fieldset = input.closest(".field");
applyField(fieldset, level);
const name = fieldset.dataset.vital;
setFlag(name, level, level === "ok" || level === "" ? "" : flagLabel(input, level));
}
function checkBP() {
const sLevel = evaluate(sys);
const dLevel = evaluate(dia);
const field = sys.closest(".field");
const combined = worse(sLevel, dLevel);
applyField(field, combined);
sys.closest(".bp-input").classList.toggle("is-flagged", sLevel === "warn" || sLevel === "danger");
dia.closest(".bp-input").classList.toggle("is-flagged", dLevel === "warn" || dLevel === "danger");
if (combined === "warn" || combined === "danger") {
// Surface the more clinically relevant reading in the flag.
const lead = RANK[sLevel] >= RANK[dLevel] ? sys : dia;
setFlag("bp", combined, flagLabel(lead, combined));
} else {
setFlag("bp", combined, "");
}
}
// ── BMI ──────────────────────────────────────────────────────────────────────
function computeBMI() {
const w = parseFloat(weight.value);
const h = parseFloat(height.value);
if (Number.isNaN(w) || Number.isNaN(h) || w <= 0 || h <= 0) {
bmiValue.textContent = "—";
bmiCat.textContent = "awaiting data";
bmiPill.dataset.cat = "none";
return null;
}
const m = h / 100;
const bmi = w / (m * m);
const rounded = bmi.toFixed(1);
bmiValue.textContent = rounded;
let cat, label;
if (bmi < 18.5) {
cat = "under";
label = "underweight";
} else if (bmi < 25) {
cat = "normal";
label = "normal";
} else if (bmi < 30) {
cat = "over";
label = "overweight";
} else {
cat = "obese";
label = "obese";
}
bmiCat.textContent = label;
bmiPill.dataset.cat = cat;
return { bmi: rounded, label };
}
// ── Timestamp ────────────────────────────────────────────────────────────────
function nowLabel() {
const d = new Date();
const time = d.toLocaleTimeString([], { hour: "numeric", minute: "2-digit" });
const date = d.toLocaleDateString([], { month: "short", day: "numeric", year: "numeric" });
return `${date} · ${time}`;
}
function markEdited() {
stamp.classList.remove("is-saved");
stamp.textContent = "Editing — not yet saved";
}
// ── Wire up live checking ────────────────────────────────────────────────────
rangeInputs.forEach((input) => {
input.addEventListener("input", () => {
checkSingle(input);
markEdited();
});
});
[sys, dia].forEach((input) => {
input.addEventListener("input", () => {
checkBP();
markEdited();
});
});
[weight, height].forEach((input) => {
input.addEventListener("input", () => {
computeBMI();
markEdited();
});
});
// ── Save ─────────────────────────────────────────────────────────────────────
form.addEventListener("submit", (e) => {
e.preventDefault();
const captured = [];
const flagged = [];
const single = [
{ input: sys, label: "Systolic", unit: "mmHg" },
{ input: dia, label: "Diastolic", unit: "mmHg" },
{ input: document.getElementById("hr"), label: "HR", unit: "bpm" },
{ input: document.getElementById("temp"), label: "Temp", unit: "°C" },
{ input: document.getElementById("spo2"), label: "SpO₂", unit: "%" },
{ input: document.getElementById("rr"), label: "RR", unit: "/min" },
];
single.forEach(({ input, label, unit }) => {
const v = input.value.trim();
if (v === "") return;
captured.push(`${label} ${v}${unit}`);
const level = evaluate(input);
if (level === "warn" || level === "danger") flagged.push(label);
});
const bmi = computeBMI();
if (weight.value.trim() !== "") captured.push(`Wt ${weight.value.trim()}kg`);
if (height.value.trim() !== "") captured.push(`Ht ${height.value.trim()}cm`);
if (bmi) captured.push(`BMI ${bmi.bmi} (${bmi.label})`);
if (captured.length === 0) {
showToast("Enter at least one vital before saving.");
return;
}
stamp.classList.add("is-saved");
stamp.textContent = `Saved ${nowLabel()} · by Nurse on duty`;
const summary = captured.join(" · ");
if (flagged.length) {
showToast(`Vitals saved (${captured.length}). Flagged for review: ${flagged.join(", ")}.`);
} else {
showToast(`Vitals saved — all in range. ${summary}.`);
}
});
// ── Reset ────────────────────────────────────────────────────────────────────
form.addEventListener("reset", () => {
// Defer so the native reset clears values first.
setTimeout(() => {
document.querySelectorAll(".field").forEach((f) => {
f.classList.remove("is-ok", "is-warn", "is-danger");
});
document.querySelectorAll(".bp-input").forEach((b) => b.classList.remove("is-flagged"));
document.querySelectorAll(".flag").forEach((f) => {
f.hidden = true;
f.removeAttribute("data-level");
f.textContent = "";
});
computeBMI();
stamp.classList.remove("is-saved");
stamp.textContent = "Captured — not yet saved";
showToast("Form cleared.");
}, 0);
});<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link
rel="stylesheet"
href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap"
/>
<link rel="stylesheet" href="style.css" />
<title>Record Vitals · Northpoint Clinic</title>
</head>
<body>
<main class="vitals">
<header class="vitals-head">
<div class="head-top">
<div>
<p class="eyebrow">Northpoint Clinic · Triage</p>
<h1>Record vitals</h1>
</div>
<span class="bmi-pill" id="bmi-pill" data-cat="none" aria-live="polite">
BMI <strong id="bmi-value">—</strong>
<span class="bmi-cat" id="bmi-cat">awaiting data</span>
</span>
</div>
<p class="patient">
<span class="avatar" aria-hidden="true">AC</span>
<span class="patient-meta">
<span class="patient-name">Amara Castellanos</span>
<span class="patient-sub">MRN 40-8821 · 34 y · Seen by Dr. Lena Okafor</span>
</span>
</p>
<p class="stamp" id="stamp" aria-live="polite">Captured — not yet saved</p>
</header>
<form class="grid" id="vitals-form" novalidate>
<!-- Blood pressure (paired) -->
<fieldset class="field span-2" data-vital="bp">
<div class="field-head">
<label id="lbl-bp">Blood pressure</label>
<span class="flag" data-flag="bp" hidden></span>
</div>
<div class="bp-row">
<div class="bp-input">
<input
type="number"
id="sys"
inputmode="numeric"
aria-labelledby="lbl-bp"
aria-describedby="hint-bp"
placeholder="120"
min="40"
max="300"
data-low="90"
data-high="120"
data-crit-low="80"
data-crit-high="180"
/>
<span class="unit">sys</span>
</div>
<span class="bp-sep" aria-hidden="true">/</span>
<div class="bp-input">
<input
type="number"
id="dia"
inputmode="numeric"
aria-label="Diastolic blood pressure"
aria-describedby="hint-bp"
placeholder="80"
min="20"
max="200"
data-low="60"
data-high="80"
data-crit-low="50"
data-crit-high="120"
/>
<span class="unit">dia · mmHg</span>
</div>
</div>
<p class="hint" id="hint-bp">Normal 90–120 / 60–80 mmHg</p>
</fieldset>
<!-- Heart rate -->
<fieldset class="field" data-vital="hr">
<div class="field-head">
<label for="hr" id="lbl-hr">Heart rate</label>
<span class="flag" data-flag="hr" hidden></span>
</div>
<div class="input-wrap">
<input
type="number"
id="hr"
inputmode="numeric"
aria-describedby="hint-hr"
placeholder="72"
min="20"
max="250"
data-low="60"
data-high="100"
data-crit-low="40"
data-crit-high="130"
/>
<span class="unit">bpm</span>
</div>
<p class="hint" id="hint-hr">Normal 60–100 bpm</p>
</fieldset>
<!-- Temperature -->
<fieldset class="field" data-vital="temp">
<div class="field-head">
<label for="temp" id="lbl-temp">Temperature</label>
<span class="flag" data-flag="temp" hidden></span>
</div>
<div class="input-wrap">
<input
type="number"
id="temp"
inputmode="decimal"
step="0.1"
aria-describedby="hint-temp"
placeholder="36.8"
min="30"
max="44"
data-low="36.1"
data-high="37.5"
data-crit-low="35"
data-crit-high="38.5"
/>
<span class="unit">°C</span>
</div>
<p class="hint" id="hint-temp">Normal 36.1–37.5 °C</p>
</fieldset>
<!-- SpO2 -->
<fieldset class="field" data-vital="spo2">
<div class="field-head">
<label for="spo2" id="lbl-spo2">SpO₂</label>
<span class="flag" data-flag="spo2" hidden></span>
</div>
<div class="input-wrap">
<input
type="number"
id="spo2"
inputmode="numeric"
aria-describedby="hint-spo2"
placeholder="98"
min="50"
max="100"
data-low="95"
data-high="100"
data-crit-low="90"
data-crit-high="100"
/>
<span class="unit">%</span>
</div>
<p class="hint" id="hint-spo2">Normal 95–100 %</p>
</fieldset>
<!-- Respiratory rate -->
<fieldset class="field" data-vital="rr">
<div class="field-head">
<label for="rr" id="lbl-rr">Respiratory rate</label>
<span class="flag" data-flag="rr" hidden></span>
</div>
<div class="input-wrap">
<input
type="number"
id="rr"
inputmode="numeric"
aria-describedby="hint-rr"
placeholder="16"
min="4"
max="60"
data-low="12"
data-high="20"
data-crit-low="8"
data-crit-high="24"
/>
<span class="unit">/min</span>
</div>
<p class="hint" id="hint-rr">Normal 12–20 breaths/min</p>
</fieldset>
<!-- Weight -->
<fieldset class="field" data-vital="weight">
<div class="field-head">
<label for="weight" id="lbl-weight">Weight</label>
</div>
<div class="input-wrap">
<input
type="number"
id="weight"
inputmode="decimal"
step="0.1"
aria-describedby="hint-weight"
placeholder="64.0"
min="1"
max="400"
/>
<span class="unit">kg</span>
</div>
<p class="hint" id="hint-weight">Used to compute BMI</p>
</fieldset>
<!-- Height -->
<fieldset class="field" data-vital="height">
<div class="field-head">
<label for="height" id="lbl-height">Height</label>
</div>
<div class="input-wrap">
<input
type="number"
id="height"
inputmode="numeric"
aria-describedby="hint-height"
placeholder="168"
min="30"
max="260"
/>
<span class="unit">cm</span>
</div>
<p class="hint" id="hint-height">Used to compute BMI</p>
</fieldset>
<footer class="form-foot span-2">
<p class="legend">
<span class="dot ok" aria-hidden="true"></span> In range
<span class="dot warn" aria-hidden="true"></span> Out of range
<span class="dot danger" aria-hidden="true"></span> Critical
</p>
<div class="foot-actions">
<button type="reset" class="btn ghost" id="clear">Clear</button>
<button type="submit" class="btn solid" id="save">Save vitals</button>
</div>
</footer>
</form>
</main>
<div class="toast" id="toast" hidden role="status" aria-live="polite"></div>
<script src="script.js"></script>
</body>
</html>Vitals Input Panel
A calm, focused panel for recording a patient’s vitals at triage. It opens with the patient context — name, MRN, age and attending clinician — alongside a live BMI badge. Below, a tidy two-column grid of number inputs covers blood pressure (paired systolic / diastolic), heart rate, temperature, SpO₂, respiratory rate, weight and height. Every field shows its unit inline and a quiet normal-range hint beneath it.
As you type, each reading is evaluated against its reference range. Values that drift out of normal tint the field label and left rail amber and surface an Above range or Below range chip; critical readings escalate to red with a Critically high / Critically low flag. Weight and height feed an automatically computed BMI value and category — underweight, normal, overweight or obese — colored to match. A timestamp line tracks the panel’s state, shifting from Editing — not yet saved to a saved confirmation once recorded.
Saving gathers only the fields you actually filled in, builds a compact summary of every captured value, notes any vitals flagged for review, stamps the time and clinician, and confirms with a toast. Clear resets every field, flag and the BMI badge in one tap. It’s all vanilla JS — no frameworks, no build step — and keyboard- and screen-reader-friendly throughout.
Illustrative UI only — not intended for real medical use.