secubox-deb/docs/hardware/smart-strip/simulator.html
CyberMind-FR 32fecbf010 feat(hardware): Add Smart-Strip HMI module (SBX-STR-01) + fix lite profile build
Smart-Strip v1.1:
- RP2350A MCU with TrustZone-M + AT42QT2120 touch IC
- 6× RGB LEDs (SK6812-MINI-E) + 6 capacitive touch zones
- Dual-mode: USB-C 2.0 (HID+CDC) / I²C (0x42)
- Hamiltonien sweep AUTH→MESH following SecuBox charte
- Parser CDC with whitelist grammar (no eval, no dynamic alloc)
- Interactive HTML simulator included

Build fixes:
- Skip X11/kiosk packages for SECUBOX_LITE=1 profiles
- Skip netdata/glances for lite profiles (ESPRESSObin)
- Skip LXC for lite profiles (limited storage)
- ESPRESSObin image now fits in 3584M (548M compressed)

Files:
- docs/hardware/smart-strip-v1.1.md (550-line spec)
- docs/hardware/smart-strip/simulator.html
- packages/secubox-smart-strip/firmware/{parser,ring_buffer}.{c,h}
- packages/secubox-smart-strip/host/secubox_smart_strip.py
- wiki/Smart-Strip.md

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-04-27 19:03:08 +02:00

1209 lines
33 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>SecuBox Smart-Strip v1.1 — Mockup réactif</title>
<style>
* { box-sizing: border-box; margin: 0; padding: 0; }
:root {
--auth: #C04E24;
--wall: #9A6010;
--boot: #803018;
--mind: #3D35A0;
--root: #0A5840;
--mesh: #104A88;
--pcb-base: #141414;
--pcb-mask: #0e0e0e;
--pcb-edge: #2a2a2a;
--silk: #ececec;
--silk-dim: #888;
--copper: #6a5530;
--ui-bg: #0a0a0a;
--ui-panel: #131313;
--ui-border: #1f1f1f;
--ui-border-soft: #2a2a2a;
--ui-text: #d8d8d8;
--ui-muted: #6e6e6e;
--ui-accent: #6acdd1;
--cdc-out: #6acdd1;
--cdc-in: #d4a14c;
--cdc-err: #c8543a;
--cdc-sys: #6e6e6e;
--mono: 'JetBrains Mono', 'SF Mono', 'Cascadia Code', 'Fira Code',
ui-monospace, Consolas, monospace;
--sans: 'Inter', 'Space Grotesk', system-ui, -apple-system, sans-serif;
}
html, body {
background: radial-gradient(ellipse at 50% 0%, #1a1a1a 0%, #050505 70%);
color: var(--ui-text);
font-family: var(--sans);
font-size: 14px;
line-height: 1.5;
min-height: 100vh;
overflow-x: hidden;
}
body {
padding: 32px 16px 64px;
display: flex;
flex-direction: column;
align-items: center;
}
header {
text-align: center;
margin-bottom: 24px;
user-select: none;
}
.brand-tag {
font-family: var(--mono);
font-size: 10px;
letter-spacing: 0.25em;
color: var(--ui-muted);
text-transform: uppercase;
margin-bottom: 6px;
}
h1 {
font-size: 26px;
font-weight: 500;
letter-spacing: 0.01em;
margin-bottom: 4px;
color: #f0f0f0;
}
.subtitle {
font-family: var(--mono);
font-size: 11px;
color: var(--ui-muted);
letter-spacing: 0.05em;
}
main {
width: 100%;
max-width: 920px;
display: flex;
flex-direction: column;
align-items: center;
gap: 28px;
}
/* ============================================================
PCB STAGE
============================================================ */
.stage {
position: relative;
perspective: 1400px;
padding: 40px 0 50px;
width: 100%;
display: flex;
justify-content: center;
}
.pcb {
position: relative;
width: 720px;
max-width: 100%;
aspect-ratio: 6 / 1;
background: var(--pcb-mask);
border-radius: 5px;
transform: rotateX(10deg) rotateY(-1deg);
transition: transform 0.5s cubic-bezier(0.2, 0.7, 0.2, 1);
box-shadow:
0 60px 80px -30px rgba(0, 0, 0, 0.9),
0 30px 40px -10px rgba(0, 0, 0, 0.6),
inset 0 1px 0 rgba(255, 255, 255, 0.05),
inset 0 -1px 0 rgba(0, 0, 0, 0.5);
background-image:
/* trace hint texture */
linear-gradient(115deg, rgba(180, 140, 70, 0.018) 0%, transparent 25%),
radial-gradient(ellipse at 65% 80%, rgba(180, 140, 70, 0.025) 0%, transparent 40%),
radial-gradient(ellipse at 30% 20%, rgba(255, 255, 255, 0.015) 0%, transparent 40%),
/* subtle FR-4 weave noise */
repeating-linear-gradient(0deg,
transparent 0px, transparent 1px,
rgba(255, 255, 255, 0.006) 1px, rgba(255, 255, 255, 0.006) 2px);
background-size: 100% 100%, 100% 100%, 100% 100%, 4px 4px;
}
.pcb:hover {
transform: rotateX(2deg) rotateY(0deg);
}
.pcb-edge-light {
position: absolute;
top: 0; left: 8%; right: 8%;
height: 1px;
background: linear-gradient(90deg,
transparent, rgba(255, 255, 255, 0.12), rgba(255, 255, 255, 0.18),
rgba(255, 255, 255, 0.12), transparent);
pointer-events: none;
}
/* Silkscreen branding */
.silk {
position: absolute;
font-family: var(--mono);
color: var(--silk);
font-weight: 500;
letter-spacing: 0.04em;
user-select: none;
pointer-events: none;
}
.silk.tl { top: 7%; left: 5.5%; font-size: 11px; }
.silk.tr { top: 7%; right: 6%; font-size: 9px; color: var(--silk-dim); }
.silk.bl { bottom: 7%; left: 5.5%; font-size: 9px; color: var(--silk-dim); }
.silk.br { bottom: 7%; right: 6%; font-size: 9px; color: var(--silk-dim); }
/* Connectors */
.usbc {
position: absolute;
left: -3px;
top: 28%;
width: 22px;
height: 44%;
background: #050505;
border: 1px solid #3a3a3a;
border-radius: 2px;
box-shadow:
inset 0 0 0 1px #1c1c1c,
inset 0 0 6px rgba(255, 255, 255, 0.04);
}
.usbc::after {
content: '';
position: absolute;
inset: 4px 3px;
background: linear-gradient(180deg, #1d1d1d 0%, #0a0a0a 100%);
border-radius: 1px;
}
.jst {
position: absolute;
right: -1px;
top: 35%;
width: 24px;
height: 30%;
background: linear-gradient(180deg, #f0f0f0 0%, #d8d8d8 100%);
border: 1px solid #888;
border-radius: 1px;
box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.6);
}
.jst::after {
content: '';
position: absolute;
inset: 3px 2px;
background:
repeating-linear-gradient(90deg,
#aaa 0px, #aaa 2.5px,
#ccc 2.5px, #ccc 5.5px);
border-radius: 0.5px;
}
/* Visible SMD components — purely decorative, fixed positions */
.smd {
position: absolute;
background: #050505;
border: 0.5px solid #1f1f1f;
border-radius: 0.5px;
pointer-events: none;
box-shadow: inset 0 0 0 0.5px rgba(255, 255, 255, 0.03);
}
.smd-mcu {
/* center-ish, larger QFN package */
bottom: 18%;
left: 36%;
width: 22px;
height: 22px;
background:
radial-gradient(circle at 30% 30%, rgba(255,255,255,0.06), transparent 60%),
#060606;
}
.smd-touch {
/* AT42QT2120 to the right of MCU */
bottom: 18%;
left: 56%;
width: 18px;
height: 18px;
background:
radial-gradient(circle at 30% 30%, rgba(255,255,255,0.05), transparent 60%),
#060606;
}
.smd-flash {
bottom: 22%;
left: 28%;
width: 14px;
height: 9px;
}
.smd-ldo {
bottom: 19%;
left: 23%;
width: 10px;
height: 8px;
}
.smd-xtal {
bottom: 19%;
left: 45%;
width: 12px;
height: 5px;
border-radius: 2px;
}
.smd-passive {
width: 4px;
height: 2px;
background: #161616;
}
/* Reference designators in dim silkscreen */
.refdes {
position: absolute;
font-family: var(--mono);
font-size: 6px;
color: rgba(236, 236, 236, 0.35);
pointer-events: none;
user-select: none;
}
/* LED groups */
.led-row {
position: absolute;
top: 16%;
left: 8%;
right: 8%;
height: 60%;
display: grid;
grid-template-columns: repeat(6, 1fr);
gap: 0;
}
.led-group {
position: relative;
cursor: pointer;
display: flex;
flex-direction: column;
align-items: center;
user-select: none;
}
.touch-zone {
position: absolute;
inset: -10% -8% -10% -8%;
border: 1px dashed transparent;
border-radius: 4px;
transition: all 0.15s ease;
pointer-events: none;
}
.led-group:hover .touch-zone {
border-color: rgba(236, 236, 236, 0.18);
background: rgba(255, 255, 255, 0.025);
}
.led-group.touched .touch-zone {
border-color: rgba(236, 236, 236, 0.4);
background: rgba(255, 255, 255, 0.06);
}
/* The LED itself */
.led {
width: 14px;
height: 14px;
border-radius: 50%;
margin-top: 8%;
background: radial-gradient(circle at 35% 35%, #2a2a2a 0%, #0a0a0a 100%);
position: relative;
transition: all 0.25s ease;
box-shadow:
inset 0 1px 1px rgba(255, 255, 255, 0.08),
inset 0 -1px 1px rgba(0, 0, 0, 0.4);
}
.led.on {
background: radial-gradient(circle at 35% 35%,
color-mix(in srgb, var(--c) 80%, white) 0%,
var(--c) 50%,
color-mix(in srgb, var(--c) 60%, black) 100%);
box-shadow:
inset 0 1px 1px rgba(255, 255, 255, 0.3),
0 0 6px var(--c),
0 0 14px var(--c),
0 0 28px color-mix(in srgb, var(--c) 60%, transparent),
0 0 50px color-mix(in srgb, var(--c) 40%, transparent);
}
/* Halo on PCB beneath each lit LED */
.halo {
position: absolute;
top: 8%;
left: 50%;
transform: translate(-50%, -10%);
width: 60px;
height: 60px;
border-radius: 50%;
pointer-events: none;
opacity: 0;
transition: opacity 0.25s ease;
background: radial-gradient(circle, var(--c) 0%, transparent 60%);
filter: blur(3px);
mix-blend-mode: screen;
}
.led-group.lit .halo {
opacity: 0.55;
}
/* Silkscreen icon */
.icon {
width: 22px;
height: 22px;
margin-top: 12%;
color: var(--silk);
opacity: 0.92;
filter: drop-shadow(0 0 1px rgba(0, 0, 0, 0.6));
}
.icon svg { width: 100%; height: 100%; display: block; }
.label {
font-family: var(--mono);
font-size: 9.5px;
font-weight: 500;
letter-spacing: 0.08em;
color: var(--silk);
margin-top: 4%;
}
/* ============================================================
DASHBOARD : controls + status side by side
============================================================ */
.dashboard {
width: 100%;
display: grid;
grid-template-columns: 1.4fr 1fr;
gap: 16px;
}
.panel {
background: var(--ui-panel);
border: 1px solid var(--ui-border);
border-radius: 4px;
padding: 16px 18px;
}
.panel-title {
font-family: var(--mono);
font-size: 10px;
letter-spacing: 0.18em;
text-transform: uppercase;
color: var(--ui-muted);
margin-bottom: 12px;
display: flex;
align-items: center;
gap: 8px;
}
.panel-title::before {
content: '';
width: 4px;
height: 4px;
border-radius: 50%;
background: var(--ui-accent);
box-shadow: 0 0 4px var(--ui-accent);
}
.controls-group {
display: flex;
flex-wrap: wrap;
gap: 6px;
margin-bottom: 14px;
}
.controls-group:last-child { margin-bottom: 0; }
.controls-label {
font-family: var(--mono);
font-size: 9px;
color: var(--ui-muted);
text-transform: uppercase;
letter-spacing: 0.12em;
width: 100%;
margin-bottom: 2px;
}
button {
background: var(--ui-bg);
border: 1px solid var(--ui-border-soft);
color: var(--ui-text);
padding: 7px 13px;
font-family: var(--mono);
font-size: 11px;
letter-spacing: 0.04em;
cursor: pointer;
transition: all 0.12s ease;
border-radius: 2px;
white-space: nowrap;
}
button:hover {
background: #1c1c1c;
border-color: #444;
color: white;
}
button:active { transform: translateY(1px); }
button.active {
background: #222;
border-color: var(--ui-accent);
color: white;
box-shadow: 0 0 0 1px rgba(106, 205, 209, 0.2);
}
button.danger.active {
border-color: #c8543a;
box-shadow: 0 0 0 1px rgba(200, 84, 58, 0.3);
}
/* Status */
.status-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 8px 18px;
font-family: var(--mono);
font-size: 11px;
}
.status-row {
display: flex;
justify-content: space-between;
border-bottom: 1px dashed #1a1a1a;
padding-bottom: 4px;
}
.status-key { color: var(--ui-muted); }
.status-val {
color: var(--ui-text);
text-align: right;
font-variant-numeric: tabular-nums;
}
.mode-pill {
display: inline-block;
padding: 1px 6px;
border-radius: 2px;
background: rgba(106, 205, 209, 0.1);
color: var(--ui-accent);
border: 1px solid rgba(106, 205, 209, 0.3);
font-size: 10px;
}
/* ============================================================
CONSOLE
============================================================ */
.console {
width: 100%;
background: #050505;
border: 1px solid var(--ui-border);
border-radius: 4px;
font-family: var(--mono);
font-size: 11px;
line-height: 1.7;
height: 200px;
overflow: hidden;
position: relative;
}
.console-header {
background: var(--ui-panel);
border-bottom: 1px solid var(--ui-border);
padding: 8px 14px;
display: flex;
justify-content: space-between;
align-items: center;
font-size: 10px;
letter-spacing: 0.12em;
color: var(--ui-muted);
text-transform: uppercase;
}
.console-header .dot {
width: 6px;
height: 6px;
border-radius: 50%;
background: #2dc06b;
box-shadow: 0 0 4px #2dc06b;
display: inline-block;
margin-right: 6px;
animation: pulse 2s infinite;
}
@keyframes pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.4; }
}
.console-body {
padding: 10px 14px;
height: calc(100% - 36px);
overflow-y: auto;
scrollbar-width: thin;
scrollbar-color: #2a2a2a transparent;
}
.console-body::-webkit-scrollbar { width: 6px; }
.console-body::-webkit-scrollbar-track { background: transparent; }
.console-body::-webkit-scrollbar-thumb { background: #2a2a2a; border-radius: 3px; }
.log-line {
white-space: pre;
opacity: 0;
animation: fadeIn 0.2s ease forwards;
}
@keyframes fadeIn {
to { opacity: 1; }
}
.log-ts { color: var(--ui-muted); }
.log-out { color: var(--cdc-out); }
.log-in { color: var(--cdc-in); }
.log-err { color: var(--cdc-err); }
.log-sys { color: var(--cdc-sys); font-style: italic; }
/* ============================================================
FOOTER
============================================================ */
footer {
margin-top: 32px;
font-family: var(--mono);
font-size: 10px;
color: var(--ui-muted);
letter-spacing: 0.1em;
text-align: center;
}
/* ============================================================
RESPONSIVE
============================================================ */
@media (max-width: 760px) {
body { padding: 24px 12px 40px; }
.pcb { width: 100%; }
.dashboard { grid-template-columns: 1fr; }
.icon { width: 16px; height: 16px; }
.label { font-size: 8.5px; }
.silk.tl { font-size: 9px; }
.silk.tr, .silk.bl, .silk.br { font-size: 7.5px; }
.led { width: 11px; height: 11px; }
}
@media (max-width: 480px) {
h1 { font-size: 20px; }
.stage { padding: 24px 0 32px; }
.pcb { transform: rotateX(0deg); }
.pcb:hover { transform: rotateX(0deg); }
.icon { width: 13px; height: 13px; }
.label { font-size: 7px; letter-spacing: 0.04em; }
}
</style>
</head>
<body>
<header>
<div class="brand-tag">CyberMind</div>
<h1>SecuBox Smart-Strip v1.1</h1>
<div class="subtitle">SBX-STR-01 · Mockup réactif · Touche les zones, lance les animations</div>
</header>
<main>
<!-- PCB STAGE ============================================== -->
<section class="stage">
<div class="pcb" id="pcb">
<div class="pcb-edge-light"></div>
<div class="silk tl">SecuBox</div>
<div class="silk tr">SBX-STR-01 v1.1</div>
<div class="silk bl">CyberMind</div>
<div class="silk br">85 × 15 mm</div>
<div class="usbc"></div>
<div class="jst"></div>
<!-- decorative SMD components -->
<div class="smd smd-ldo"></div>
<div class="smd smd-flash"></div>
<div class="smd smd-mcu"></div>
<div class="smd smd-xtal"></div>
<div class="smd smd-touch"></div>
<div class="smd smd-passive" style="bottom:14%; left:30%;"></div>
<div class="smd smd-passive" style="bottom:14%; left:34%;"></div>
<div class="smd smd-passive" style="bottom:14%; left:62%;"></div>
<div class="smd smd-passive" style="bottom:14%; left:66%;"></div>
<div class="refdes" style="bottom: 30%; left: 23%;">U4</div>
<div class="refdes" style="bottom: 33%; left: 28%;">U2</div>
<div class="refdes" style="bottom: 42%; left: 36%;">U1</div>
<div class="refdes" style="bottom: 26%; left: 45%;">Y1</div>
<div class="refdes" style="bottom: 38%; left: 56%;">U3</div>
<!-- LED row -->
<div class="led-row">
<div class="led-group" data-led="0" data-name="AUTH" style="--c: var(--auth);">
<div class="halo"></div>
<div class="touch-zone"></div>
<div class="led"></div>
<div class="icon">
<!-- padlock -->
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round">
<rect x="5.5" y="11" width="13" height="9.5" rx="1.5" fill="currentColor" stroke="none"/>
<path d="M8.5 11 V7.5 a3.5 3.5 0 0 1 7 0 V11"/>
</svg>
</div>
<div class="label">AUTH</div>
</div>
<div class="led-group" data-led="1" data-name="WALL" style="--c: var(--wall);">
<div class="halo"></div>
<div class="touch-zone"></div>
<div class="led"></div>
<div class="icon">
<!-- shield -->
<svg viewBox="0 0 24 24" fill="currentColor" stroke="currentColor" stroke-width="1" stroke-linejoin="round">
<path d="M12 3 L19 6 V12.5 Q19 18 12 21 Q5 18 5 12.5 V6 Z"/>
</svg>
</div>
<div class="label">WALL</div>
</div>
<div class="led-group" data-led="2" data-name="BOOT" style="--c: var(--boot);">
<div class="halo"></div>
<div class="touch-zone"></div>
<div class="led"></div>
<div class="icon">
<!-- gear -->
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round">
<circle cx="12" cy="12" r="3.2"/>
<line x1="12" y1="3" x2="12" y2="6"/>
<line x1="12" y1="18" x2="12" y2="21"/>
<line x1="3" y1="12" x2="6" y2="12"/>
<line x1="18" y1="12" x2="21" y2="12"/>
<line x1="5.5" y1="5.5" x2="7.6" y2="7.6"/>
<line x1="16.4" y1="16.4" x2="18.5" y2="18.5"/>
<line x1="18.5" y1="5.5" x2="16.4" y2="7.6"/>
<line x1="7.6" y1="16.4" x2="5.5" y2="18.5"/>
</svg>
</div>
<div class="label">BOOT</div>
</div>
<div class="led-group" data-led="3" data-name="MIND" style="--c: var(--mind);">
<div class="halo"></div>
<div class="touch-zone"></div>
<div class="led"></div>
<div class="icon">
<!-- brain-circuit -->
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round">
<path d="M9.5 5 Q4.5 5 4.5 11 Q4.5 14 7 16 Q4.5 20 9.5 20"/>
<path d="M14.5 5 Q19.5 5 19.5 11 Q19.5 14 17 16 Q19.5 20 14.5 20"/>
<line x1="12" y1="5" x2="12" y2="20"/>
<circle cx="12" cy="12" r="1.5" fill="currentColor"/>
<circle cx="8" cy="9" r="0.9" fill="currentColor"/>
<circle cx="16" cy="14" r="0.9" fill="currentColor"/>
</svg>
</div>
<div class="label">MIND</div>
</div>
<div class="led-group" data-led="4" data-name="ROOT" style="--c: var(--root);">
<div class="halo"></div>
<div class="touch-zone"></div>
<div class="led"></div>
<div class="icon">
<!-- root system -->
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round">
<line x1="12" y1="2" x2="12" y2="22"/>
<path d="M12 16 L7 22"/>
<path d="M12 16 L17 22"/>
<path d="M12 9 L8.5 14"/>
<path d="M12 9 L15.5 14"/>
<path d="M12 4 L9.5 7"/>
<path d="M12 4 L14.5 7"/>
</svg>
</div>
<div class="label">ROOT</div>
</div>
<div class="led-group" data-led="5" data-name="MESH" style="--c: var(--mesh);">
<div class="halo"></div>
<div class="touch-zone"></div>
<div class="led"></div>
<div class="icon">
<!-- mesh nodes -->
<svg viewBox="0 0 24 24" fill="currentColor" stroke="currentColor" stroke-width="0.8">
<line x1="12" y1="5" x2="6" y2="11" stroke-width="1.1"/>
<line x1="12" y1="5" x2="18" y2="11" stroke-width="1.1"/>
<line x1="6" y1="11" x2="9" y2="18" stroke-width="1.1"/>
<line x1="18" y1="11" x2="15" y2="18" stroke-width="1.1"/>
<line x1="9" y1="18" x2="15" y2="18" stroke-width="1.1"/>
<line x1="6" y1="11" x2="18" y2="11" stroke-width="1.1"/>
<circle cx="12" cy="5" r="1.8"/>
<circle cx="6" cy="11" r="1.8"/>
<circle cx="18" cy="11" r="1.8"/>
<circle cx="9" cy="18" r="1.8"/>
<circle cx="15" cy="18" r="1.8"/>
</svg>
</div>
<div class="label">MESH</div>
</div>
</div>
</div>
</section>
<!-- DASHBOARD ============================================== -->
<section class="dashboard">
<div class="panel">
<div class="panel-title">Animations</div>
<div class="controls-group">
<span class="controls-label">État</span>
<button data-anim="off">Off</button>
<button data-anim="charte">Charte SecuBox</button>
<button data-anim="white">Blanc max</button>
</div>
<div class="controls-group">
<span class="controls-label">Effets</span>
<button data-anim="sweep">Hamiltonien sweep</button>
<button data-anim="breathe">Breathing</button>
<button data-anim="boot">Séquence boot</button>
<button data-anim="panic" class="danger">Panic mode</button>
</div>
<div class="controls-group">
<span class="controls-label">Mode transport</span>
<button data-mode="usb" class="active">USB primary</button>
<button data-mode="i2c">I²C secondary</button>
</div>
</div>
<div class="panel">
<div class="panel-title">État du module</div>
<div class="status-grid">
<div class="status-row"><span class="status-key">Mode</span><span class="status-val"><span class="mode-pill" id="s-mode">USB</span></span></div>
<div class="status-row"><span class="status-key">FW</span><span class="status-val">1.1.0</span></div>
<div class="status-row"><span class="status-key">Anim</span><span class="status-val" id="s-anim">charte</span></div>
<div class="status-row"><span class="status-key">HBT</span><span class="status-val" id="s-hbt">OK</span></div>
<div class="status-row"><span class="status-key">TOUCH</span><span class="status-val" id="s-touch">0x00</span></div>
<div class="status-row"><span class="status-key">CMD_OK</span><span class="status-val" id="s-cmdok">0</span></div>
<div class="status-row"><span class="status-key">DROP_GR</span><span class="status-val" id="s-dropgr">0</span></div>
<div class="status-row"><span class="status-key">Uptime</span><span class="status-val" id="s-up">00:00</span></div>
</div>
</div>
</section>
<!-- CONSOLE ============================================== -->
<section class="console">
<div class="console-header">
<span><span class="dot"></span>/dev/ttyACM0 · 115200 8N1</span>
<span id="con-mode">composite HID + CDC</span>
</div>
<div class="console-body" id="console"></div>
</section>
</main>
<footer>
CyberMind · SBX-STR-01 v1.1 · Notre-Dame-du-Cruet · 2026
</footer>
<script>
'use strict';
// ============================================================
// Constants
// ============================================================
const LED_NAMES = ['AUTH', 'WALL', 'BOOT', 'MIND', 'ROOT', 'MESH'];
const CHARTE = {
AUTH: '#C04E24',
WALL: '#9A6010',
BOOT: '#803018',
MIND: '#3D35A0',
ROOT: '#0A5840',
MESH: '#104A88',
};
const CHARTE_RGB = {
AUTH: [192, 78, 36],
WALL: [154, 96, 16],
BOOT: [128, 48, 24],
MIND: [61, 53, 160],
ROOT: [10, 88, 64],
MESH: [16, 74, 136],
};
// ============================================================
// State
// ============================================================
const state = {
anim: 'charte',
mode: 'usb', // 'usb' | 'i2c'
touchLatch: 0, // bitmask
cmdOk: 0,
dropGrammar: 0,
startMs: Date.now(),
hbtMissedMs: 0,
};
let animTimer = null;
let breatheRaf = null;
// ============================================================
// DOM helpers
// ============================================================
const $ = (s) => document.querySelector(s);
const groups = () => document.querySelectorAll('.led-group');
const led = (idx) => groups()[idx].querySelector('.led');
// ============================================================
// LED control
// ============================================================
function setLed(idx, color, opacity = 1) {
const g = groups()[idx];
const el = g.querySelector('.led');
if (color === null) {
el.classList.remove('on');
g.classList.remove('lit');
g.style.removeProperty('--c');
el.style.opacity = '';
g.querySelector('.halo').style.opacity = '';
} else {
g.style.setProperty('--c', color);
el.classList.add('on');
g.classList.add('lit');
el.style.opacity = opacity;
g.querySelector('.halo').style.opacity = (0.55 * opacity).toFixed(2);
}
}
function setAll(color) {
for (let i = 0; i < 6; i++) setLed(i, color);
}
function setCharte() {
for (let i = 0; i < 6; i++) {
setLed(i, CHARTE[LED_NAMES[i]]);
}
}
// ============================================================
// Animation engine
// ============================================================
function stopAnim() {
if (animTimer) { clearInterval(animTimer); animTimer = null; }
if (breatheRaf) { cancelAnimationFrame(breatheRaf); breatheRaf = null; }
}
function animSweep() {
let pos = 0;
setAll(null);
animTimer = setInterval(() => {
setAll(null);
setLed(pos, '#ffffff');
pos = (pos + 1) % 6;
}, 220);
}
function animPanic() {
// ROOT (idx 4) flashes red, others sweep reverse with red glow
let revPos = 5;
let phase = 0;
setAll(null);
animTimer = setInterval(() => {
setAll(null);
// ROOT flashes
if (phase % 2 === 0) setLed(4, '#ff2828');
// reverse sweep red
if (revPos !== 4) setLed(revPos, '#ff5050');
revPos = revPos > 0 ? revPos - 1 : 5;
phase++;
}, 160);
}
function animBreathe() {
setCharte();
let t = 0;
function step() {
t += 0.06;
const intensity = 0.35 + 0.65 * (Math.sin(t) * 0.5 + 0.5);
for (let i = 0; i < 6; i++) {
setLed(i, CHARTE[LED_NAMES[i]], intensity);
}
breatheRaf = requestAnimationFrame(step);
}
step();
}
function animBoot() {
setAll(null);
let i = 0;
animTimer = setInterval(() => {
if (i >= 6) {
clearInterval(animTimer);
animTimer = null;
setTimeout(() => {
if (state.anim === 'boot') {
state.anim = 'charte';
$('#s-anim').textContent = 'charte';
highlightActive('button[data-anim="charte"]');
setCharte();
log('out', '< boot complete, settled into CHARTE');
}
}, 600);
return;
}
setLed(i, CHARTE[LED_NAMES[i]]);
log('out', cdcCmd('SET_LED', `${i} ${CHARTE_RGB[LED_NAMES[i]].join(' ')}`));
i++;
}, 320);
}
// ============================================================
// Console
// ============================================================
const consoleBody = $('#console');
function log(kind, text) {
const line = document.createElement('div');
line.className = 'log-line';
const elapsed = ((Date.now() - state.startMs) / 1000).toFixed(2).padStart(7, ' ');
const tsSpan = `<span class="log-ts">[${elapsed}]</span> `;
let body;
switch (kind) {
case 'out': body = `<span class="log-out">${text}</span>`; break;
case 'in': body = `<span class="log-in">${text}</span>`; break;
case 'err': body = `<span class="log-err">${text}</span>`; break;
default: body = `<span class="log-sys">${text}</span>`;
}
line.innerHTML = tsSpan + body;
consoleBody.appendChild(line);
// Auto-scroll
consoleBody.scrollTop = consoleBody.scrollHeight;
// Cap at 200 lines
while (consoleBody.children.length > 200) {
consoleBody.removeChild(consoleBody.firstChild);
}
}
function cdcCmd(verb, args) {
return state.mode === 'usb'
? `> ${verb}${args ? ' ' + args : ''}`
: i2cCmd(verb, args);
}
function i2cCmd(verb, args) {
// Translate to I²C register write notation
switch (verb) {
case 'SET_LED': {
const [idx, ...rgb] = args.split(' ');
const reg = (0x10 + parseInt(idx, 10) * 3).toString(16).padStart(2, '0');
return `> WR 0x42 [0x${reg}, ${rgb.join(', ')}]`;
}
case 'SET_ALL': {
const rgb = args.split(' ');
return `> WR 0x42 [0x10..0x21, ${rgb.join(', ')} × 6]`;
}
case 'ANIM': return `> WR 0x42 [0x30, ${args}]`;
case 'HBT': return `> WR 0x42 [0x40, 0x55]`;
case 'RESET': return `> WR 0x42 [0x00, 0x80]`;
case 'STATUS': return `> RD 0x42 [0x01]`;
default: return `> ${verb} ${args || ''}`;
}
}
function pidCount() {
state.cmdOk++;
$('#s-cmdok').textContent = state.cmdOk;
}
// ============================================================
// Animation dispatcher
// ============================================================
function runAnim(name) {
stopAnim();
state.anim = name;
$('#s-anim').textContent = name;
// Reset opacity overrides from previous breathe
groups().forEach((g) => {
g.querySelector('.led').style.opacity = '';
g.querySelector('.halo').style.opacity = '';
});
switch (name) {
case 'off':
setAll(null);
log('out', cdcCmd('SET_ALL', '0 0 0'));
break;
case 'charte':
setCharte();
LED_NAMES.forEach((n, i) => {
log('out', cdcCmd('SET_LED', `${i} ${CHARTE_RGB[n].join(' ')}`));
});
break;
case 'white':
setAll('#ffffff');
log('out', cdcCmd('SET_ALL', '255 255 255'));
break;
case 'sweep':
animSweep();
log('out', cdcCmd('ANIM', '1'));
break;
case 'panic':
animPanic();
log('err', cdcCmd('ANIM', '2'));
log('err', '! PANIC MODE — heartbeat lost');
break;
case 'breathe':
animBreathe();
log('out', cdcCmd('ANIM', '3'));
break;
case 'boot':
animBoot();
log('sys', '… boot sequence start');
break;
}
pidCount();
}
// ============================================================
// Touch handlers
// ============================================================
groups().forEach((group, idx) => {
group.addEventListener('click', () => {
const name = LED_NAMES[idx];
// Haptic feedback if available
if (navigator.vibrate) navigator.vibrate(8);
// Visual feedback
group.classList.add('touched');
setTimeout(() => group.classList.remove('touched'), 250);
// White flash on the LED
const el = led(idx);
const wasOn = el.classList.contains('on');
const prevColor = group.style.getPropertyValue('--c');
setLed(idx, '#ffffff');
setTimeout(() => {
if (wasOn) setLed(idx, prevColor || CHARTE[name]);
else setLed(idx, null);
}, 180);
// Latch
state.touchLatch |= (1 << idx);
$('#s-touch').textContent = '0x' + state.touchLatch.toString(16).padStart(2, '0').toUpperCase();
// Console
if (state.mode === 'usb') {
log('in', `< HID consumer key F${13 + idx} (pad ${name})`);
} else {
log('in', `< IRQ INT_FLAGS=0x${(1 << idx).toString(16).padStart(2, '0').toUpperCase()} (pad ${name})`);
}
// Auto-clear latch after 3 s for the demo
setTimeout(() => {
state.touchLatch &= ~(1 << idx);
$('#s-touch').textContent = '0x' + state.touchLatch.toString(16).padStart(2, '0').toUpperCase();
}, 3000);
});
});
// ============================================================
// Button handlers
// ============================================================
function highlightActive(selector) {
document.querySelectorAll('button[data-anim]').forEach((b) => b.classList.remove('active'));
const b = document.querySelector(selector);
if (b) b.classList.add('active');
}
document.querySelectorAll('button[data-anim]').forEach((b) => {
b.addEventListener('click', () => {
highlightActive(`button[data-anim="${b.dataset.anim}"]`);
runAnim(b.dataset.anim);
});
});
document.querySelectorAll('button[data-mode]').forEach((b) => {
b.addEventListener('click', () => {
document.querySelectorAll('button[data-mode]').forEach((x) => x.classList.remove('active'));
b.classList.add('active');
state.mode = b.dataset.mode;
$('#s-mode').textContent = state.mode === 'usb' ? 'USB' : 'I²C';
$('#con-mode').textContent = state.mode === 'usb'
? 'composite HID + CDC'
: 'i2c-1 @ 0x42 · 400 kHz';
log('sys', state.mode === 'usb'
? '/// switch to USB primary — CDC ACM enumerated as ttyACM0'
: '/// switch to I²C secondary — slave @ 0x42 ack');
});
});
// ============================================================
// Heartbeat + uptime
// ============================================================
setInterval(() => {
// Simulate periodic heartbeat traffic
state.hbtMissedMs = 0;
if (Math.random() < 0.18) {
log('out', cdcCmd('HBT'));
}
$('#s-hbt').textContent = 'OK · ' + Math.floor(Math.random() * 200 + 100) + ' ms';
}, 1500);
setInterval(() => {
const sec = Math.floor((Date.now() - state.startMs) / 1000);
const m = Math.floor(sec / 60).toString().padStart(2, '0');
const s = (sec % 60).toString().padStart(2, '0');
$('#s-up').textContent = `${m}:${s}`;
}, 1000);
// ============================================================
// Boot sequence
// ============================================================
window.addEventListener('load', () => {
log('sys', '/// SecuBox Smart-Strip v1.1 — SBX-STR-01');
log('sys', '/// FW=1.1.0 CPU=RP2350A TZ-M=enabled');
log('sys', '/// VBUS detected — entering USB primary mode');
log('out', '> STATUS');
setTimeout(() => log('in', '< FW=1.1.0 MODE=USB UPTIME_S=0 CMD_OK=0'), 220);
setTimeout(() => {
setCharte();
LED_NAMES.forEach((n, i) => {
log('out', cdcCmd('SET_LED', `${i} ${CHARTE_RGB[n].join(' ')}`));
});
state.cmdOk = 6;
$('#s-cmdok').textContent = '6';
}, 500);
highlightActive('button[data-anim="charte"]');
});
</script>
</body>
</html>