DIY — Safety Callout System
A manual-style safety callout system for DIY and maker guides with four severity levels — Danger, Warning, Tip, and Note — each with an icon plate, stencil label, and optional read-more expander. Includes compact inline strips, a dismissible variant remembered via localStorage, numbered-step embedded callouts, a required-PPE variant with icon chips, and a live builder panel that generates copy-ready HTML with a toast confirmation.
MCP
Code
:root {
--blueprint: #0f2a4a;
--blueprint-d: #0a1e36;
--blueprint-l: #16385f;
--grid: rgba(140, 180, 220, 0.14);
--grid-major: rgba(140, 180, 220, 0.26);
--orange: #ff6b35;
--orange-d: #e0521f;
--orange-soft: #ffe3d6;
--kraft: #d9c7a7;
--kraft-l: #efe6d4;
--ink: #1c2733;
--ink-2: #3d4a57;
--muted: #6b7885;
--bg: #f4f1ea;
--white: #ffffff;
--line: rgba(28, 39, 51, 0.12);
--line-2: rgba(28, 39, 51, 0.2);
--ok: #2f9e6f;
--warn: #d98a2b;
--danger: #d4503e;
--tip-blue: #2a6db5;
--r-sm: 6px;
--r-md: 12px;
--r-lg: 18px;
--shadow-1: 0 1px 2px rgba(28, 39, 51, 0.06), 0 4px 14px rgba(28, 39, 51, 0.07);
--shadow-2: 0 2px 6px rgba(28, 39, 51, 0.08), 0 10px 28px rgba(28, 39, 51, 0.12);
--mono: "JetBrains Mono", ui-monospace, monospace;
}
* {
box-sizing: border-box;
}
body {
margin: 0;
font-family: "Inter", system-ui, -apple-system, sans-serif;
background: var(--bg);
color: var(--ink);
line-height: 1.5;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
/* ============ MONO BADGES ============ */
.mono-badge {
display: inline-block;
font-family: var(--mono);
font-size: 0.7rem;
font-weight: 700;
letter-spacing: 0.08em;
padding: 4px 10px;
border-radius: var(--r-sm);
background: var(--orange);
color: var(--blueprint-d);
}
.mono-badge.ghost {
background: transparent;
border: 1px solid rgba(140, 180, 220, 0.4);
color: var(--kraft);
}
.mono-badge.ghost-dark {
background: transparent;
border: 1px solid var(--line-2);
color: var(--ink-2);
}
.mono-inline {
font-family: var(--mono);
font-size: 0.85em;
font-weight: 600;
background: rgba(28, 39, 51, 0.07);
border-radius: 4px;
padding: 1px 5px;
white-space: nowrap;
}
/* ============ HERO ============ */
.hero {
background-color: var(--blueprint);
background-image:
linear-gradient(var(--grid) 1px, transparent 1px),
linear-gradient(90deg, var(--grid) 1px, transparent 1px),
linear-gradient(var(--grid-major) 1px, transparent 1px),
linear-gradient(90deg, var(--grid-major) 1px, transparent 1px);
background-size: 8px 8px, 8px 8px, 80px 80px, 80px 80px;
color: var(--white);
padding: 56px 24px 52px;
border-bottom: 4px solid var(--orange);
}
.hero-inner {
max-width: 860px;
margin: 0 auto;
}
.hero-kicker {
display: flex;
gap: 10px;
flex-wrap: wrap;
margin-bottom: 18px;
}
.hero-title {
margin: 0 0 14px;
font-size: clamp(1.9rem, 5vw, 3rem);
font-weight: 800;
text-transform: uppercase;
letter-spacing: 0.06em;
line-height: 1.1;
}
.hero-sub {
margin: 0 0 24px;
max-width: 60ch;
color: var(--kraft-l);
font-size: 1.02rem;
}
.hero-sub strong {
color: var(--white);
}
.hero-legend {
display: flex;
gap: 10px;
flex-wrap: wrap;
}
.legend-chip {
display: inline-flex;
align-items: center;
gap: 8px;
font-family: var(--mono);
font-size: 0.72rem;
font-weight: 700;
letter-spacing: 0.1em;
padding: 6px 12px;
border-radius: 999px;
border: 1px solid rgba(140, 180, 220, 0.35);
color: var(--kraft-l);
background: rgba(10, 30, 54, 0.5);
}
.legend-chip .dot {
width: 9px;
height: 9px;
border-radius: 50%;
flex: none;
}
.legend-chip.lv-danger .dot { background: var(--danger); }
.legend-chip.lv-warning .dot { background: var(--warn); }
.legend-chip.lv-tip .dot { background: #6aa9e8; }
.legend-chip.lv-note .dot { background: var(--kraft); }
/* ============ PAGE / SECTIONS ============ */
.page {
max-width: 860px;
margin: 0 auto;
padding: 40px 24px 60px;
}
.section {
margin-bottom: 52px;
}
.section-head {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
margin-bottom: 20px;
padding-bottom: 10px;
border-bottom: 2px solid var(--line-2);
}
.section-title {
margin: 0;
font-size: 1.05rem;
font-weight: 800;
text-transform: uppercase;
letter-spacing: 0.09em;
color: var(--ink);
}
/* ============ CALLOUT CORE ============ */
.callout {
display: flex;
align-items: stretch;
gap: 0;
background: var(--white);
border: 1px solid var(--line);
border-left-width: 0;
border-radius: var(--r-md);
overflow: hidden;
box-shadow: var(--shadow-1);
margin-bottom: 16px;
transition: box-shadow 0.2s ease, transform 0.2s ease;
position: relative;
}
.callout:hover {
box-shadow: var(--shadow-2);
transform: translateY(-1px);
}
.callout-plate {
flex: none;
width: 58px;
display: flex;
align-items: flex-start;
justify-content: center;
padding-top: 20px;
color: var(--white);
}
.callout-plate .glyph {
width: 26px;
height: 26px;
}
.callout-body {
flex: 1;
min-width: 0;
padding: 16px 20px 18px;
}
.callout-label {
display: inline-block;
font-family: var(--mono);
font-size: 0.68rem;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.16em;
margin-bottom: 6px;
}
.callout-title {
margin: 0 0 6px;
font-size: 1.02rem;
font-weight: 700;
color: var(--ink);
}
.callout-text {
margin: 0;
font-size: 0.92rem;
color: var(--ink-2);
}
/* level colorways */
.callout--danger { border-color: rgba(212, 80, 62, 0.35); }
.callout--danger .callout-plate { background: var(--danger); }
.callout--danger .callout-label { color: var(--danger); }
.callout--warning { border-color: rgba(217, 138, 43, 0.4); }
.callout--warning .callout-plate { background: var(--warn); }
.callout--warning .callout-label { color: #a86414; }
.callout--tip { border-color: rgba(42, 109, 181, 0.35); }
.callout--tip .callout-plate { background: var(--tip-blue); }
.callout--tip .callout-label { color: var(--tip-blue); }
.callout--note { border-color: var(--line-2); }
.callout--note .callout-plate { background: var(--ink-2); }
.callout--note .callout-label { color: var(--muted); }
.callout--ppe { border-color: rgba(255, 107, 53, 0.5); }
.callout--ppe .callout-plate { background: var(--orange); }
.callout--ppe .callout-label { color: var(--orange-d); }
/* ============ EXPANDER ============ */
.callout-expander {
margin-top: 12px;
border-top: 1px dashed var(--line-2);
padding-top: 10px;
}
.expander-btn {
display: inline-flex;
align-items: center;
gap: 8px;
background: none;
border: none;
padding: 4px 2px;
cursor: pointer;
font-family: var(--mono);
font-size: 0.76rem;
font-weight: 600;
letter-spacing: 0.04em;
color: var(--ink-2);
border-radius: var(--r-sm);
}
.expander-btn:hover {
color: var(--orange-d);
}
.expander-btn:focus-visible {
outline: 2px solid var(--orange);
outline-offset: 2px;
}
.expander-btn .chev {
width: 15px;
height: 15px;
transition: transform 0.25s ease;
}
.expander-btn[aria-expanded="true"] .chev {
transform: rotate(180deg);
}
.expander-panel {
display: grid;
grid-template-rows: 0fr;
transition: grid-template-rows 0.28s ease;
}
.expander-panel > * {
overflow: hidden;
}
.callout-expander.is-open .expander-panel {
grid-template-rows: 1fr;
}
.expander-list {
margin: 8px 0 2px;
padding-left: 20px;
font-size: 0.88rem;
color: var(--ink-2);
}
.expander-list li {
margin-bottom: 4px;
}
.expander-text {
margin: 8px 0 2px;
font-size: 0.88rem;
color: var(--ink-2);
}
/* ============ COMPACT STRIPS ============ */
.strip {
display: flex;
align-items: center;
gap: 10px;
padding: 9px 14px;
border-radius: var(--r-sm);
border: 1px solid;
margin-bottom: 10px;
font-size: 0.88rem;
background: var(--white);
transition: transform 0.15s ease;
}
.strip:hover {
transform: translateX(3px);
}
.strip-glyph {
width: 17px;
height: 17px;
flex: none;
}
.strip-label {
font-family: var(--mono);
font-size: 0.68rem;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.14em;
flex: none;
}
.strip-text {
color: var(--ink-2);
min-width: 0;
}
.strip--danger { border-color: rgba(212, 80, 62, 0.45); background: #fdf1ef; }
.strip--danger .strip-glyph, .strip--danger .strip-label { color: var(--danger); }
.strip--warning { border-color: rgba(217, 138, 43, 0.5); background: #fdf6ec; }
.strip--warning .strip-glyph, .strip--warning .strip-label { color: #a86414; }
.strip--tip { border-color: rgba(42, 109, 181, 0.4); background: #eff5fc; }
.strip--tip .strip-glyph, .strip--tip .strip-label { color: var(--tip-blue); }
/* ============ DISMISSIBLE ============ */
.callout--dismissible {
padding-right: 0;
}
.dismiss-btn {
position: absolute;
top: 10px;
right: 10px;
width: 32px;
height: 32px;
display: flex;
align-items: center;
justify-content: center;
border: 1px solid var(--line);
border-radius: var(--r-sm);
background: var(--white);
color: var(--muted);
cursor: pointer;
transition: color 0.15s ease, border-color 0.15s ease, background 0.15s ease;
}
.dismiss-btn:hover {
color: var(--danger);
border-color: rgba(212, 80, 62, 0.45);
background: #fdf1ef;
}
.dismiss-btn:focus-visible {
outline: 2px solid var(--orange);
outline-offset: 2px;
}
.dismiss-btn svg {
width: 15px;
height: 15px;
}
.callout.is-dismissing {
opacity: 0;
transform: translateY(-6px) scale(0.99);
transition: opacity 0.25s ease, transform 0.25s ease;
}
.dismissed-note {
display: flex;
align-items: center;
gap: 10px;
flex-wrap: wrap;
border: 1px dashed var(--line-2);
border-radius: var(--r-md);
padding: 12px 16px;
font-size: 0.85rem;
color: var(--muted);
background: var(--kraft-l);
}
.restore-btn {
border: none;
background: none;
padding: 2px 4px;
font-family: var(--mono);
font-size: 0.76rem;
font-weight: 700;
color: var(--orange-d);
cursor: pointer;
text-decoration: underline;
text-underline-offset: 3px;
border-radius: 4px;
}
.restore-btn:hover {
color: var(--orange);
}
.restore-btn:focus-visible {
outline: 2px solid var(--orange);
outline-offset: 2px;
}
/* ============ STEPS ============ */
.steps {
list-style: none;
margin: 0;
padding: 0;
}
.step {
display: flex;
gap: 18px;
padding-bottom: 26px;
position: relative;
}
.step:not(:last-child)::before {
content: "";
position: absolute;
left: 21px;
top: 46px;
bottom: 4px;
width: 2px;
background: repeating-linear-gradient(
to bottom,
var(--line-2) 0 6px,
transparent 6px 12px
);
}
.step-num {
flex: none;
width: 44px;
height: 44px;
border-radius: var(--r-sm);
background: var(--blueprint);
color: var(--kraft-l);
font-family: var(--mono);
font-size: 0.95rem;
font-weight: 700;
display: flex;
align-items: center;
justify-content: center;
box-shadow: var(--shadow-1);
}
.step-num--flag {
background: var(--danger);
color: var(--white);
}
.step-content {
flex: 1;
min-width: 0;
padding-top: 4px;
}
.step-title {
margin: 0 0 4px;
font-size: 1rem;
font-weight: 700;
}
.step-text {
margin: 0 0 12px;
font-size: 0.92rem;
color: var(--ink-2);
}
.callout--embedded {
margin-bottom: 0;
}
.callout--embedded .callout-body {
padding: 12px 16px 14px;
}
.callout--embedded .callout-plate {
width: 46px;
padding-top: 14px;
}
.callout--embedded .callout-plate .glyph {
width: 20px;
height: 20px;
}
/* ============ PPE CHIPS ============ */
.ppe-chips {
display: flex;
flex-wrap: wrap;
gap: 10px;
margin-top: 14px;
}
.ppe-chip {
display: inline-flex;
align-items: center;
gap: 8px;
padding: 8px 14px;
border-radius: 999px;
border: 1.5px solid var(--orange);
background: var(--orange-soft);
color: var(--orange-d);
font-size: 0.82rem;
font-weight: 600;
transition: background 0.15s ease, transform 0.15s ease;
}
.ppe-chip:hover {
background: var(--orange);
color: var(--white);
transform: translateY(-1px);
}
.ppe-chip svg {
width: 17px;
height: 17px;
flex: none;
}
/* ============ BUILDER ============ */
.builder {
display: grid;
grid-template-columns: minmax(0, 5fr) minmax(0, 7fr);
gap: 22px;
align-items: start;
}
.builder-form {
background: var(--kraft-l);
border: 1px solid var(--line);
border-radius: var(--r-lg);
padding: 20px;
background-image:
linear-gradient(rgba(28, 39, 51, 0.045) 1px, transparent 1px),
linear-gradient(90deg, rgba(28, 39, 51, 0.045) 1px, transparent 1px);
background-size: 16px 16px;
}
.builder-field {
border: none;
margin: 0 0 18px;
padding: 0;
}
.builder-field:last-child {
margin-bottom: 0;
}
.builder-legend,
.builder-label {
display: block;
font-family: var(--mono);
font-size: 0.7rem;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.12em;
color: var(--ink-2);
margin-bottom: 8px;
padding: 0;
}
.level-picker {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 8px;
}
.level-option {
position: relative;
display: block;
cursor: pointer;
}
.level-option input {
position: absolute;
opacity: 0;
pointer-events: none;
}
.level-face {
display: block;
text-align: center;
padding: 9px 6px;
border-radius: var(--r-sm);
border: 1.5px solid var(--line-2);
background: var(--white);
font-family: var(--mono);
font-size: 0.74rem;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.08em;
color: var(--ink-2);
transition: border-color 0.15s ease, background 0.15s ease, color 0.15s ease,
box-shadow 0.15s ease;
}
.level-option:hover .level-face {
border-color: var(--ink-2);
}
.level-option input:focus-visible + .level-face {
outline: 2px solid var(--orange);
outline-offset: 2px;
}
.level-option.lv-danger input:checked + .level-face {
border-color: var(--danger);
background: #fdf1ef;
color: var(--danger);
box-shadow: inset 0 0 0 1px var(--danger);
}
.level-option.lv-warning input:checked + .level-face {
border-color: var(--warn);
background: #fdf6ec;
color: #a86414;
box-shadow: inset 0 0 0 1px var(--warn);
}
.level-option.lv-tip input:checked + .level-face {
border-color: var(--tip-blue);
background: #eff5fc;
color: var(--tip-blue);
box-shadow: inset 0 0 0 1px var(--tip-blue);
}
.level-option.lv-note input:checked + .level-face {
border-color: var(--ink-2);
background: rgba(28, 39, 51, 0.06);
color: var(--ink);
box-shadow: inset 0 0 0 1px var(--ink-2);
}
.builder-input {
width: 100%;
border: 1.5px solid var(--line-2);
border-radius: var(--r-sm);
padding: 10px 12px;
font-family: "Inter", system-ui, sans-serif;
font-size: 0.9rem;
color: var(--ink);
background: var(--white);
transition: border-color 0.15s ease, box-shadow 0.15s ease;
}
.builder-input:focus {
outline: none;
border-color: var(--orange);
box-shadow: 0 0 0 3px rgba(255, 107, 53, 0.2);
}
.builder-textarea {
resize: vertical;
min-height: 70px;
}
.builder-out {
min-width: 0;
}
.builder-preview-head,
.builder-code-head {
display: flex;
align-items: center;
justify-content: space-between;
gap: 10px;
margin-bottom: 10px;
}
.builder-code-head {
margin-top: 18px;
}
.builder-preview {
min-height: 90px;
}
.builder-preview .callout {
margin-bottom: 0;
}
.copy-btn {
display: inline-flex;
align-items: center;
gap: 8px;
padding: 8px 16px;
border: none;
border-radius: var(--r-sm);
background: var(--orange);
color: var(--blueprint-d);
font-family: "Inter", system-ui, sans-serif;
font-size: 0.82rem;
font-weight: 700;
cursor: pointer;
box-shadow: var(--shadow-1);
transition: background 0.15s ease, transform 0.1s ease;
}
.copy-btn:hover {
background: var(--orange-d);
color: var(--white);
}
.copy-btn:active {
transform: scale(0.97);
}
.copy-btn:focus-visible {
outline: 2px solid var(--blueprint);
outline-offset: 2px;
}
.copy-btn svg {
width: 15px;
height: 15px;
}
.builder-code {
width: 100%;
border: 1px solid rgba(140, 180, 220, 0.3);
border-radius: var(--r-md);
background: var(--blueprint-d);
color: var(--kraft-l);
font-family: var(--mono);
font-size: 0.76rem;
line-height: 1.6;
padding: 14px 16px;
resize: vertical;
white-space: pre;
overflow-x: auto;
}
.builder-code:focus {
outline: 2px solid var(--orange);
outline-offset: 2px;
}
/* ============ FOOTER ============ */
.page-foot {
text-align: center;
padding-top: 8px;
border-top: 1px dashed var(--line-2);
color: var(--muted);
}
.page-foot .mono-inline {
background: none;
font-size: 0.7rem;
letter-spacing: 0.1em;
white-space: normal;
}
/* ============ TOAST ============ */
.toast {
position: fixed;
left: 50%;
bottom: 26px;
transform: translate(-50%, 16px);
background: var(--blueprint-d);
color: var(--kraft-l);
font-family: var(--mono);
font-size: 0.78rem;
font-weight: 600;
letter-spacing: 0.04em;
padding: 11px 20px;
border-radius: var(--r-md);
border: 1px solid rgba(140, 180, 220, 0.35);
box-shadow: var(--shadow-2);
opacity: 0;
pointer-events: none;
transition: opacity 0.25s ease, transform 0.25s ease;
z-index: 100;
max-width: calc(100vw - 40px);
}
.toast.show {
opacity: 1;
transform: translate(-50%, 0);
}
/* ============ MOTION / RESPONSIVE ============ */
@media (prefers-reduced-motion: reduce) {
* {
transition-duration: 0.01ms !important;
animation-duration: 0.01ms !important;
}
}
@media (max-width: 800px) {
.builder {
grid-template-columns: 1fr;
}
}
@media (max-width: 520px) {
.hero {
padding: 40px 18px 38px;
}
.page {
padding: 30px 16px 48px;
}
.callout-plate {
width: 46px;
padding-top: 16px;
}
.callout-plate .glyph {
width: 21px;
height: 21px;
}
.callout-body {
padding: 13px 14px 15px;
}
.strip {
flex-wrap: wrap;
row-gap: 4px;
}
.step {
gap: 12px;
}
.step-num {
width: 36px;
height: 36px;
font-size: 0.82rem;
}
.step:not(:last-child)::before {
left: 17px;
top: 40px;
}
.section-head {
flex-wrap: wrap;
}
.level-picker {
grid-template-columns: 1fr 1fr;
}
.builder-form {
padding: 16px;
}
}/* DIY Safety Callout System — interactions
Expanders, dismissible (localStorage), and the live callout builder. */
(function () {
"use strict";
/* ---------- toast helper ---------- */
var toastEl = document.getElementById("toast");
var toastTimer = null;
function toast(msg) {
if (!toastEl) return;
toastEl.textContent = msg;
toastEl.classList.add("show");
clearTimeout(toastTimer);
toastTimer = setTimeout(function () {
toastEl.classList.remove("show");
}, 2200);
}
/* ---------- read-more expanders ---------- */
document.querySelectorAll(".expander-btn").forEach(function (btn) {
btn.addEventListener("click", function () {
var wrap = btn.closest(".callout-expander");
var open = wrap.classList.toggle("is-open");
btn.setAttribute("aria-expanded", open ? "true" : "false");
});
});
/* ---------- dismissible callout (localStorage remember) ---------- */
var dismissible = document.getElementById("dismissible-callout");
var dismissedNote = document.getElementById("dismissed-note");
var restoreBtn = document.getElementById("restore-dismissed");
function storageGet(key) {
try {
return window.localStorage.getItem(key);
} catch (e) {
return null;
}
}
function storageSet(key, value) {
try {
if (value === null) window.localStorage.removeItem(key);
else window.localStorage.setItem(key, value);
} catch (e) {
/* private mode — degrade to session-only behavior */
}
}
if (dismissible && dismissedNote) {
var key = "callout-dismissed:" + dismissible.dataset.dismissKey;
if (storageGet(key) === "1") {
dismissible.hidden = true;
dismissedNote.hidden = false;
}
var dismissBtn = dismissible.querySelector(".dismiss-btn");
if (dismissBtn) {
dismissBtn.addEventListener("click", function () {
dismissible.classList.add("is-dismissing");
setTimeout(function () {
dismissible.hidden = true;
dismissible.classList.remove("is-dismissing");
dismissedNote.hidden = false;
storageSet(key, "1");
toast("Warning dismissed — remembered on this device");
}, 250);
});
}
if (restoreBtn) {
restoreBtn.addEventListener("click", function () {
storageSet(key, null);
dismissedNote.hidden = true;
dismissible.hidden = false;
toast("Warning restored");
});
}
}
/* ---------- live builder ---------- */
var LEVELS = {
danger: {
label: "Danger",
role: ' role="alert"',
glyph:
'<svg viewBox="0 0 24 24" class="glyph"><path d="M13 2 4 14h6l-1.5 8L19 9h-6l1.5-7z" fill="currentColor"/></svg>'
},
warning: {
label: "Warning",
role: ' role="alert"',
glyph:
'<svg viewBox="0 0 24 24" class="glyph"><path d="M12 2c1 3.5-1.5 5-1.5 7.5 0 1.3.9 2.2 1.9 2.4-.5-1.4.4-2.3 1.2-3.1 1.9 1.5 3.4 3.7 3.4 6.2A5.9 5.9 0 0 1 11 21a5.9 5.9 0 0 1-6-6c0-4.6 4.3-6.5 7-13z" fill="currentColor"/></svg>'
},
tip: {
label: "Pro Tip",
role: "",
glyph:
'<svg viewBox="0 0 24 24" class="glyph"><path d="M12 2a7 7 0 0 0-4 12.8c.7.5 1 1.3 1 2.2h6c0-.9.3-1.7 1-2.2A7 7 0 0 0 12 2zM9.5 19h5v1a1.5 1.5 0 0 1-1.5 1.5h-2A1.5 1.5 0 0 1 9.5 20v-1z" fill="currentColor"/></svg>'
},
note: {
label: "Note",
role: "",
glyph:
'<svg viewBox="0 0 24 24" class="glyph"><path d="M12 2a4 4 0 0 0-4 4c0 2.9 2.4 4.9 3.3 8.1.1.5.3.9.7.9s.6-.4.7-.9C13.6 10.9 16 8.9 16 6a4 4 0 0 0-4-4zm0 2.5A1.5 1.5 0 1 1 12 7.5 1.5 1.5 0 0 1 12 4.5zM11 17h2v5h-2z" fill="currentColor"/></svg>'
}
};
var form = document.getElementById("builder-form");
var titleInput = document.getElementById("builder-title");
var textInput = document.getElementById("builder-text");
var preview = document.getElementById("builder-preview");
var codeOut = document.getElementById("builder-code");
var copyBtn = document.getElementById("copy-html");
function escapeHtml(str) {
return String(str)
.replace(/&/g, "&")
.replace(/</g, "<")
.replace(/>/g, ">")
.replace(/"/g, """);
}
function currentLevel() {
var checked = form && form.querySelector('input[name="level"]:checked');
return (checked && checked.value) || "danger";
}
function buildMarkup(level, title, text, indent) {
var cfg = LEVELS[level];
var pad = indent ? " " : "";
var lines = [
'<div class="callout callout--' + level + '"' + cfg.role + ">",
pad + ' <div class="callout-plate" aria-hidden="true">',
pad + " " + cfg.glyph,
pad + " </div>",
pad + ' <div class="callout-body">',
pad + ' <span class="callout-label">' + cfg.label + "</span>",
pad + ' <h3 class="callout-title">' + escapeHtml(title) + "</h3>",
pad + ' <p class="callout-text">' + escapeHtml(text) + "</p>",
pad + " </div>",
pad + "</div>"
];
return lines.join("\n");
}
function render() {
if (!form || !preview || !codeOut) return;
var level = currentLevel();
var title = titleInput.value.trim() || "Untitled callout";
var text = textInput.value.trim() || "Describe the hazard or advice here.";
preview.innerHTML = buildMarkup(level, title, text, false);
codeOut.value = buildMarkup(level, title, text, false);
}
if (form) {
form.addEventListener("input", render);
form.addEventListener("change", render);
form.addEventListener("submit", function (e) {
e.preventDefault();
});
render();
}
if (copyBtn && codeOut) {
copyBtn.addEventListener("click", function () {
var html = codeOut.value;
function done() {
toast("Callout HTML copied to clipboard");
}
function fallback() {
codeOut.focus();
codeOut.select();
try {
document.execCommand("copy");
done();
} catch (e) {
toast("Copy failed — select the code manually");
}
}
if (navigator.clipboard && navigator.clipboard.writeText) {
navigator.clipboard.writeText(html).then(done, fallback);
} else {
fallback();
}
});
}
/* ---------- PPE chip playful ack ---------- */
document.querySelectorAll(".ppe-chip").forEach(function (chip) {
chip.addEventListener("click", function () {
toast(chip.textContent.trim() + " — checked");
});
});
})();<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>DIY — Safety Callout System</title>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&family=JetBrains+Mono:wght@400;600;700&display=swap" rel="stylesheet" />
<link rel="stylesheet" href="style.css" />
</head>
<body>
<!-- ============ HERO ============ -->
<header class="hero">
<div class="hero-inner">
<div class="hero-kicker">
<span class="mono-badge">SPEC SC-04</span>
<span class="mono-badge ghost">REV 2.3 · 2026</span>
</div>
<h1 class="hero-title">Safety Callout System</h1>
<p class="hero-sub">
Four severity levels for workshop manuals — from stop-everything danger blocks to
friendly pro tips. Every callout ships with an icon plate, stencil label, and
optional expander. Built for the <strong>Bolt&Bench Field Manual</strong> series.
</p>
<div class="hero-legend" role="list" aria-label="Callout severity levels">
<span class="legend-chip lv-danger" role="listitem"><i class="dot"></i>DANGER</span>
<span class="legend-chip lv-warning" role="listitem"><i class="dot"></i>WARNING</span>
<span class="legend-chip lv-tip" role="listitem"><i class="dot"></i>TIP</span>
<span class="legend-chip lv-note" role="listitem"><i class="dot"></i>NOTE</span>
</div>
</div>
</header>
<main class="page">
<!-- ============ STANDARD CALLOUTS ============ -->
<section class="section" aria-labelledby="std-h">
<div class="section-head">
<h2 id="std-h" class="section-title">01 · Standard Callouts</h2>
<span class="mono-badge ghost-dark">4 LEVELS</span>
</div>
<!-- DANGER -->
<div class="callout callout--danger" role="alert">
<div class="callout-plate" aria-hidden="true">
<svg viewBox="0 0 24 24" class="glyph"><path d="M13 2 4 14h6l-1.5 8L19 9h-6l1.5-7z" fill="currentColor"/></svg>
</div>
<div class="callout-body">
<span class="callout-label">Danger</span>
<h3 class="callout-title">High voltage inside the Volta-Werk PSU-900</h3>
<p class="callout-text">
Capacitors inside the supply hold a lethal charge for up to
<span class="mono-inline">15 min</span> after unplugging. Never open the case
until the bleed LED (<span class="mono-inline">D7</span>) is fully dark.
</p>
<div class="callout-expander">
<button class="expander-btn" type="button" aria-expanded="false">
<svg viewBox="0 0 24 24" class="chev" aria-hidden="true"><path d="M6 9l6 6 6-6" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"/></svg>
Read discharge procedure
</button>
<div class="expander-panel">
<ol class="expander-list">
<li>Unplug the unit and wait a full 15 minutes with the switch in <span class="mono-inline">OFF</span>.</li>
<li>Verify <span class="mono-inline">< 5 V</span> across C3/C4 with a multimeter before touching anything.</li>
<li>Use a 10 kΩ / 5 W bleeder resistor on insulated leads — never a screwdriver short.</li>
</ol>
</div>
</div>
</div>
</div>
<!-- WARNING -->
<div class="callout callout--warning" role="alert">
<div class="callout-plate" aria-hidden="true">
<svg viewBox="0 0 24 24" class="glyph"><path d="M12 2c1 3.5-1.5 5-1.5 7.5 0 1.3.9 2.2 1.9 2.4-.5-1.4.4-2.3 1.2-3.1 1.9 1.5 3.4 3.7 3.4 6.2A5.9 5.9 0 0 1 11 21a5.9 5.9 0 0 1-6-6c0-4.6 4.3-6.5 7-13z" fill="currentColor"/></svg>
</div>
<div class="callout-body">
<span class="callout-label">Warning</span>
<h3 class="callout-title">Hot surface — soldering station tip reaches 380 °C</h3>
<p class="callout-text">
The Fluxhound FH-60 iron stays above burn temperature for
<span class="mono-inline">~4 min</span> after power-off. Always return it to the
spring holder — never rest it on the kraft mat.
</p>
<div class="callout-expander">
<button class="expander-btn" type="button" aria-expanded="false">
<svg viewBox="0 0 24 24" class="chev" aria-hidden="true"><path d="M6 9l6 6 6-6" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"/></svg>
Read cool-down notes
</button>
<div class="expander-panel">
<p class="expander-text">
Set the station to standby (<span class="mono-inline">150 °C</span>) between joints.
If you smell scorched flux, wipe the tip on brass wool — a wet sponge thermal-shocks
the plating and shortens tip life.
</p>
</div>
</div>
</div>
</div>
<!-- TIP -->
<div class="callout callout--tip">
<div class="callout-plate" aria-hidden="true">
<svg viewBox="0 0 24 24" class="glyph"><path d="M12 2a7 7 0 0 0-4 12.8c.7.5 1 1.3 1 2.2h6c0-.9.3-1.7 1-2.2A7 7 0 0 0 12 2zM9.5 19h5v1a1.5 1.5 0 0 1-1.5 1.5h-2A1.5 1.5 0 0 1 9.5 20v-1z" fill="currentColor"/></svg>
</div>
<div class="callout-body">
<span class="callout-label">Pro Tip</span>
<h3 class="callout-title">Pre-tin both wires before joining them</h3>
<p class="callout-text">
A thin coat of solder on each conductor means the splice flows in under
<span class="mono-inline">2 s</span> — less heat into the insulation and a
joint that actually holds.
</p>
</div>
</div>
<!-- NOTE -->
<div class="callout callout--note">
<div class="callout-plate" aria-hidden="true">
<svg viewBox="0 0 24 24" class="glyph"><path d="M12 2a4 4 0 0 0-4 4c0 2.9 2.4 4.9 3.3 8.1.1.5.3.9.7.9s.6-.4.7-.9C13.6 10.9 16 8.9 16 6a4 4 0 0 0-4-4zm0 2.5A1.5 1.5 0 1 1 12 7.5 1.5 1.5 0 0 1 12 4.5zM11 17h2v5h-2z" fill="currentColor"/></svg>
</div>
<div class="callout-body">
<span class="callout-label">Note</span>
<h3 class="callout-title">Kit contents vary by batch</h3>
<p class="callout-text">
Kits shipped after batch <span class="mono-inline">BB-2604</span> include the
revised strain-relief clip (<span class="mono-inline">P/N 114-C</span>). Older
kits use the friction sleeve — both work with this guide.
</p>
</div>
</div>
</section>
<!-- ============ COMPACT + DISMISSIBLE ============ -->
<section class="section" aria-labelledby="var-h">
<div class="section-head">
<h2 id="var-h" class="section-title">02 · Compact & Dismissible</h2>
<span class="mono-badge ghost-dark">INLINE STRIPS</span>
</div>
<div class="strip strip--danger" role="alert">
<svg viewBox="0 0 24 24" class="strip-glyph" aria-hidden="true"><path d="M13 2 4 14h6l-1.5 8L19 9h-6l1.5-7z" fill="currentColor"/></svg>
<span class="strip-label">Danger</span>
<span class="strip-text">Mains wiring ahead — isolate the breaker before step 6.</span>
</div>
<div class="strip strip--warning" role="alert">
<svg viewBox="0 0 24 24" class="strip-glyph" aria-hidden="true"><path d="M12 2c1 3.5-1.5 5-1.5 7.5 0 1.3.9 2.2 1.9 2.4-.5-1.4.4-2.3 1.2-3.1 1.9 1.5 3.4 3.7 3.4 6.2A5.9 5.9 0 0 1 11 21a5.9 5.9 0 0 1-6-6c0-4.6 4.3-6.5 7-13z" fill="currentColor"/></svg>
<span class="strip-label">Warning</span>
<span class="strip-text">Heat gun exhaust exceeds 300 °C at the nozzle.</span>
</div>
<div class="strip strip--tip">
<svg viewBox="0 0 24 24" class="strip-glyph" aria-hidden="true"><path d="M12 2a7 7 0 0 0-4 12.8c.7.5 1 1.3 1 2.2h6c0-.9.3-1.7 1-2.2A7 7 0 0 0 12 2zM9.5 19h5v1a1.5 1.5 0 0 1-1.5 1.5h-2A1.5 1.5 0 0 1 9.5 20v-1z" fill="currentColor"/></svg>
<span class="strip-label">Tip</span>
<span class="strip-text">Label both ends of every wire before you cut it.</span>
</div>
<div class="dismiss-wrap">
<div class="callout callout--warning callout--dismissible" id="dismissible-callout" data-dismiss-key="sc-workbench-clamp" role="alert">
<div class="callout-plate" aria-hidden="true">
<svg viewBox="0 0 24 24" class="glyph"><path d="M12 2c1 3.5-1.5 5-1.5 7.5 0 1.3.9 2.2 1.9 2.4-.5-1.4.4-2.3 1.2-3.1 1.9 1.5 3.4 3.7 3.4 6.2A5.9 5.9 0 0 1 11 21a5.9 5.9 0 0 1-6-6c0-4.6 4.3-6.5 7-13z" fill="currentColor"/></svg>
</div>
<div class="callout-body">
<span class="callout-label">Warning · Dismissible</span>
<h3 class="callout-title">Clamp the workpiece before drilling</h3>
<p class="callout-text">
Hand-holding sheet stock under a drill press is the #1 cause of workshop
injuries in our incident log. Two clamps minimum on anything smaller than
<span class="mono-inline">300 mm</span>. This notice stays hidden once
dismissed (stored locally on this device).
</p>
</div>
<button class="dismiss-btn" type="button" aria-label="Dismiss this warning">
<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M6 6l12 12M18 6L6 18" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round"/></svg>
</button>
</div>
<div class="dismissed-note" id="dismissed-note" hidden>
<span class="mono-inline">SC-DISMISSED</span> Warning hidden on this device.
<button class="restore-btn" type="button" id="restore-dismissed">Restore it</button>
</div>
</div>
</section>
<!-- ============ NUMBERED STEP EMBED ============ -->
<section class="section" aria-labelledby="step-h">
<div class="section-head">
<h2 id="step-h" class="section-title">03 · Embedded in Steps</h2>
<span class="mono-badge ghost-dark">GUIDE FLOW</span>
</div>
<ol class="steps">
<li class="step">
<div class="step-num" aria-hidden="true">04</div>
<div class="step-content">
<h3 class="step-title">Route the harness through the chassis grommet</h3>
<p class="step-text">
Feed the 6-wire harness through grommet <span class="mono-inline">G2</span>
and leave <span class="mono-inline">80 mm</span> of slack on the board side.
</p>
</div>
</li>
<li class="step">
<div class="step-num step-num--flag" aria-hidden="true">05</div>
<div class="step-content">
<h3 class="step-title">Connect the battery leads</h3>
<p class="step-text">
Red to <span class="mono-inline">BAT+</span>, black to
<span class="mono-inline">BAT−</span>. Torque the terminal screws to
<span class="mono-inline">0.4 N·m</span>.
</p>
<div class="callout callout--danger callout--embedded" role="alert">
<div class="callout-plate" aria-hidden="true">
<svg viewBox="0 0 24 24" class="glyph"><path d="M13 2 4 14h6l-1.5 8L19 9h-6l1.5-7z" fill="currentColor"/></svg>
</div>
<div class="callout-body">
<span class="callout-label">Danger</span>
<p class="callout-text">
Reversed polarity destroys the BMS instantly and can vent the cell.
Triple-check lead colors before tightening.
</p>
</div>
</div>
</div>
</li>
<li class="step">
<div class="step-num" aria-hidden="true">06</div>
<div class="step-content">
<h3 class="step-title">Close the enclosure</h3>
<p class="step-text">
Seat the lid gasket and drive all six <span class="mono-inline">M3×8</span>
screws in a star pattern.
</p>
<div class="callout callout--tip callout--embedded">
<div class="callout-plate" aria-hidden="true">
<svg viewBox="0 0 24 24" class="glyph"><path d="M12 2a7 7 0 0 0-4 12.8c.7.5 1 1.3 1 2.2h6c0-.9.3-1.7 1-2.2A7 7 0 0 0 12 2zM9.5 19h5v1a1.5 1.5 0 0 1-1.5 1.5h-2A1.5 1.5 0 0 1 9.5 20v-1z" fill="currentColor"/></svg>
</div>
<div class="callout-body">
<span class="callout-label">Pro Tip</span>
<p class="callout-text">
Start every screw two turns by hand — self-tappers cross-thread ABS
bosses if you lead with the driver.
</p>
</div>
</div>
</div>
</li>
</ol>
</section>
<!-- ============ PPE VARIANT ============ -->
<section class="section" aria-labelledby="ppe-h">
<div class="section-head">
<h2 id="ppe-h" class="section-title">04 · Required PPE</h2>
<span class="mono-badge ghost-dark">GEAR CHECK</span>
</div>
<div class="callout callout--ppe" role="alert">
<div class="callout-plate" aria-hidden="true">
<svg viewBox="0 0 24 24" class="glyph"><path d="M12 2 4 5v6c0 5 3.4 9.4 8 11 4.6-1.6 8-6 8-11V5l-8-3zm-1.2 13.6-3.4-3.4 1.4-1.4 2 2 4.4-4.4 1.4 1.4-5.8 5.8z" fill="currentColor"/></svg>
</div>
<div class="callout-body">
<span class="callout-label">Required PPE</span>
<h3 class="callout-title">Gear up before the resin pour (steps 09–12)</h3>
<p class="callout-text">
Two-part epoxy from the CastRight CR-2 kit is a skin and respiratory
sensitizer while curing. All three items are mandatory in this section.
</p>
<div class="ppe-chips" role="list" aria-label="Required protective equipment">
<span class="ppe-chip" role="listitem">
<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M2 9a3 3 0 0 1 3-3h14a3 3 0 0 1 3 3v3a3 3 0 0 1-3 3h-3.2a2.8 2.8 0 0 1-2.5-1.5.9.9 0 0 0-1.6 0A2.8 2.8 0 0 1 9.2 15H5a3 3 0 0 1-3-3V9zm3-1a1 1 0 0 0-1 1v3a1 1 0 0 0 1 1h4.2c.3 0 .6-.2.8-.5a2.9 2.9 0 0 1 5 0c.2.3.5.5.8.5H19a1 1 0 0 0 1-1V9a1 1 0 0 0-1-1H5z" fill="currentColor"/></svg>
Safety goggles
</span>
<span class="ppe-chip" role="listitem">
<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M9 2a2 2 0 0 0-2 2v8.3l-1.6-1.6a1.7 1.7 0 0 0-2.4 2.4l4.6 4.9A6 6 0 0 0 12 20h1a5 5 0 0 0 5-5V7a1.5 1.5 0 0 0-3 0v3h-1V5a1.5 1.5 0 0 0-3 0v5h-1V4a1 1 0 0 0-1-1z" fill="currentColor"/></svg>
Nitrile gloves
</span>
<span class="ppe-chip" role="listitem">
<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M12 4c-3.5 0-6.5 1.2-8 2.2V13a8 8 0 0 0 16 0V6.2C18.5 5.2 15.5 4 12 4zm0 5a3.5 3.5 0 0 1 3.5 3.5c0 2.2-1.7 4.5-3.5 4.5s-3.5-2.3-3.5-4.5A3.5 3.5 0 0 1 12 9z" fill="currentColor"/></svg>
Half-mask respirator
</span>
</div>
</div>
</div>
</section>
<!-- ============ LIVE BUILDER ============ -->
<section class="section" aria-labelledby="builder-h">
<div class="section-head">
<h2 id="builder-h" class="section-title">05 · Callout Builder</h2>
<span class="mono-badge ghost-dark">LIVE</span>
</div>
<div class="builder">
<form class="builder-form" id="builder-form" aria-label="Callout builder controls">
<fieldset class="builder-field">
<legend class="builder-legend">Severity level</legend>
<div class="level-picker" role="radiogroup" aria-label="Severity level">
<label class="level-option lv-danger">
<input type="radio" name="level" value="danger" checked />
<span class="level-face">Danger</span>
</label>
<label class="level-option lv-warning">
<input type="radio" name="level" value="warning" />
<span class="level-face">Warning</span>
</label>
<label class="level-option lv-tip">
<input type="radio" name="level" value="tip" />
<span class="level-face">Tip</span>
</label>
<label class="level-option lv-note">
<input type="radio" name="level" value="note" />
<span class="level-face">Note</span>
</label>
</div>
</fieldset>
<div class="builder-field">
<label class="builder-label" for="builder-title">Title</label>
<input class="builder-input" id="builder-title" type="text" maxlength="90"
value="Unplug the router before opening the bit collet" />
</div>
<div class="builder-field">
<label class="builder-label" for="builder-text">Body text</label>
<textarea class="builder-input builder-textarea" id="builder-text" rows="3"
maxlength="280">A bumped power switch at 24,000 RPM will not give you a second chance. Kill the plug, not just the switch.</textarea>
</div>
</form>
<div class="builder-out">
<div class="builder-preview-head">
<span class="mono-badge ghost-dark">PREVIEW</span>
</div>
<div class="builder-preview" id="builder-preview" aria-live="polite"></div>
<div class="builder-code-head">
<span class="mono-badge ghost-dark">GENERATED HTML</span>
<button class="copy-btn" type="button" id="copy-html">
<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M8 4h10a2 2 0 0 1 2 2v12h-2V6H8V4zm-2 4h10a2 2 0 0 1 2 2v10a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V10a2 2 0 0 1 2-2zm0 2v10h10V10H6z" fill="currentColor"/></svg>
Copy HTML
</button>
</div>
<textarea class="builder-code" id="builder-code" rows="9" readonly
aria-label="Generated callout HTML" spellcheck="false"></textarea>
</div>
</div>
</section>
<footer class="page-foot">
<span class="mono-inline">BOLT&BENCH FIELD MANUAL · SPEC SC-04 · FICTIONAL DEMO</span>
</footer>
</main>
<div class="toast" id="toast" role="status" aria-live="polite"></div>
<script src="script.js"></script>
</body>
</html>Safety Callout System
A complete callout vocabulary for workshop-style manuals, styled with the blueprint-and-kraft maker aesthetic. Four severity levels cover the whole range — red Danger (high-voltage capacitor discharge), amber Warning (hot soldering-iron surface), blue Pro Tip, and a neutral Note — each built from a colored icon plate with an SVG glyph, a stencil-uppercase mono label, a title, and body text. Danger and Warning callouts carry role="alert", and the deeper ones add a smooth “read more” expander for procedures and cool-down notes.
Beyond the standard block, the system ships variants for real guide layouts: compact inline strips for one-line hazards, a dismissible warning whose hidden state is remembered in localStorage (with a restore link), callouts embedded inside a numbered step list with a dashed connector rail, and a safety-orange Required PPE block with hoverable icon chips for goggles, gloves, and respirator.
The live builder panel at the bottom makes the system practical: pick a severity, type a title and body, and the preview re-renders instantly alongside generated, escaped HTML in a blueprint-dark code box. One click copies it to the clipboard with a toast confirmation, using a execCommand fallback when the Clipboard API is unavailable.
Illustrative UI only — always follow real safety guidance when working with tools, electronics, or repairs.