mirror of
https://github.com/CyberMind-FR/secubox-deb.git
synced 2026-07-28 21:17:36 +00:00
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>
1209 lines
33 KiB
HTML
1209 lines
33 KiB
HTML
<!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>
|