Download designs/c-commandement.html from PascalF53/jarvis-local: direct link, hf CLI and curl.
- Browser
- Download file 14.2 kB
-
https://huggingface.co/PascalF53/jarvis-local/resolve/main/designs/c-commandement.html
- Command line
-
hf download hf://PascalF53/jarvis-local/designs/c-commandement.html
-
curl -L -o c-commandement.html https://huggingface.co/PascalF53/jarvis-local/resolve/main/designs/c-commandement.html
14.2 kB
| <html lang="fr"> | |
| <head> | |
| <meta charset="utf-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1"> | |
| <title>JARVIS · Proposition C — Commandement</title> | |
| <link rel="preconnect" href="https://fonts.googleapis.com"> | |
| <link href="https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@400;500&family=IBM+Plex+Sans+Condensed:wght@400;500;600&display=swap" rel="stylesheet"> | |
| <style> | |
| /* Direction C — "Commandement" : console de SOC. | |
| L'orbe devient un radar, la conversation un journal horodaté, | |
| et on voit en permanence l'état de la machine (latences, VRAM, moteurs). */ | |
| :root { | |
| --bg: #0a0d0d; --panel: #0f1414; --panel-2: #131a1a; --line: #1f2a29; --line-2: #2b3a38; | |
| --green: #3df5a6; --green-d: #1c7a55; --amber: #ffc94a; --red: #ff5a5a; --blue: #58c6ff; | |
| --text: #d6e4e1; --muted: #6b807c; | |
| --mono: "IBM Plex Mono", monospace; --cond: "IBM Plex Sans Condensed", sans-serif; | |
| } | |
| * { box-sizing: border-box; margin: 0; padding: 0; } | |
| html, body { height: 100%; } | |
| body { | |
| background: var(--bg); color: var(--text); font-family: var(--cond); overflow: hidden; | |
| display: grid; grid-template-rows: 44px 1fr 34px; grid-template-columns: 340px 1fr 340px; | |
| grid-template-areas: "top top top" "log stage side" "bot bot bot"; gap: 1px; background-color: var(--line); | |
| } | |
| body > * { background: var(--bg); } | |
| /* ---------- top status bar */ | |
| #top { grid-area: top; display: flex; align-items: center; gap: 26px; padding: 0 18px; font-family: var(--mono); font-size: 11.5px; color: var(--muted); } | |
| #top .brand { color: var(--green); font-weight: 500; letter-spacing: .18em; } | |
| #top .k b { color: var(--text); font-weight: 400; } | |
| #top .k.ok b { color: var(--green); } | |
| #top .sp { flex: 1; } | |
| #top #clock { color: var(--amber); } | |
| .ph { display: flex; justify-content: space-between; align-items: center; height: 34px; padding: 0 14px; border-bottom: 1px solid var(--line); font-family: var(--mono); font-size: 10.5px; letter-spacing: .16em; text-transform: uppercase; color: var(--muted); background: var(--panel); } | |
| .ph b { color: var(--green); font-weight: 500; } | |
| /* ---------- left: conversation journal */ | |
| #log { grid-area: log; display: flex; flex-direction: column; min-height: 0; } | |
| #lines { flex: 1; overflow: hidden; padding: 12px 14px; display: flex; flex-direction: column; justify-content: flex-end; gap: 10px; font-size: 14px; line-height: 1.45; } | |
| .ln { display: grid; grid-template-columns: 54px 1fr; gap: 8px; animation: fade .3s; } | |
| .ln time { font-family: var(--mono); font-size: 11px; color: var(--muted); padding-top: 2px; } | |
| .ln .who { font-family: var(--mono); font-size: 10.5px; letter-spacing: .12em; display: block; margin-bottom: 1px; } | |
| .ln.you .who { color: var(--blue); } .ln.jv .who { color: var(--green); } .ln.sys .who { color: var(--amber); } | |
| .ln.sys { color: var(--muted); font-size: 13px; } | |
| .cursor::after { content: "▌"; color: var(--green); animation: blink .8s steps(1) infinite; } | |
| @keyframes blink { 50% { opacity: 0; } } | |
| @keyframes fade { from { opacity: 0; transform: translateY(6px); } } | |
| /* ---------- centre: radar + oscilloscope */ | |
| #stage { grid-area: stage; position: relative; display: grid; grid-template-rows: 1fr 120px; min-height: 0; } | |
| #radarBox { position: relative; display: grid; place-items: center; min-height: 0; } | |
| #radar { width: min(100%, 64vh); aspect-ratio: 1; cursor: pointer; } | |
| #mode { position: absolute; top: 14px; left: 16px; font-family: var(--mono); font-size: 11px; letter-spacing: .2em; } | |
| #mode span { padding: 3px 8px; border: 1px solid var(--green-d); color: var(--green); } | |
| #mode.thinking span { border-color: var(--amber); color: var(--amber); } | |
| #scope { width: 100%; height: 120px; border-top: 1px solid var(--line); background: var(--panel); display: block; } | |
| /* ---------- right: tasks + telemetry + findings */ | |
| #side { grid-area: side; display: flex; flex-direction: column; min-height: 0; overflow: hidden; } | |
| table { width: 100%; border-collapse: collapse; font-size: 13px; } | |
| td { padding: 8px 14px; border-bottom: 1px solid var(--line); vertical-align: top; } | |
| td.st { font-family: var(--mono); font-size: 10.5px; width: 78px; } | |
| .run { color: var(--amber); } .ok { color: var(--green); } | |
| td small { display: block; font-family: var(--mono); font-size: 10.5px; color: var(--muted); margin-top: 2px; } | |
| .meters { padding: 12px 14px; display: grid; gap: 10px; font-family: var(--mono); font-size: 11px; color: var(--muted); } | |
| .meter { display: grid; grid-template-columns: 70px 1fr 54px; align-items: center; gap: 10px; } | |
| .meter .b { height: 6px; background: var(--panel-2); border: 1px solid var(--line-2); } | |
| .meter .b i { display: block; height: 100%; background: var(--green); transition: width .6s; } | |
| .meter .v { color: var(--text); text-align: right; } | |
| .finding { margin: 10px 14px; border: 1px solid var(--line-2); border-left: 3px solid var(--red); background: var(--panel); padding: 10px 12px; animation: fade .4s; } | |
| .finding h4 { font-family: var(--mono); font-size: 10.5px; letter-spacing: .14em; color: var(--red); margin-bottom: 6px; font-weight: 500; } | |
| .finding div { display: flex; justify-content: space-between; font-size: 13px; padding: 2px 0; } | |
| .finding b { font-family: var(--mono); font-weight: 500; color: var(--amber); } | |
| /* ---------- bottom */ | |
| #bot { grid-area: bot; display: flex; align-items: center; gap: 18px; padding: 0 18px; font-family: var(--mono); font-size: 11px; color: var(--muted); } | |
| #bot kbd { border: 1px solid var(--line-2); padding: 1px 6px; color: var(--text); font: inherit; } | |
| select { background: var(--panel); color: var(--text); border: 1px solid var(--line-2); padding: 3px 6px; font: inherit; } | |
| #bot .sp { flex: 1; } | |
| </style> | |
| </head> | |
| <body> | |
| <div id="top"> | |
| <span class="brand">■ JARVIS // CONSOLE</span> | |
| <span class="k ok">état <b>OPÉRATIONNEL</b></span> | |
| <span class="k">llm <b>qwen3-30b-a3b</b></span> | |
| <span class="k">agent <b>local + claude</b></span> | |
| <span class="k">réseau <b>aucune sortie</b></span> | |
| <span class="sp"></span> | |
| <span id="clock">--:--:--</span> | |
| </div> | |
| <section id="log"> | |
| <div class="ph">journal de session <b id="cnt">0</b></div> | |
| <div id="lines"></div> | |
| </section> | |
| <section id="stage"> | |
| <div id="radarBox"> | |
| <div id="mode"><span>VEILLE</span></div> | |
| <canvas id="radar"></canvas> | |
| </div> | |
| <canvas id="scope"></canvas> | |
| </section> | |
| <aside id="side"> | |
| <div class="ph">tâches <b id="tc">0 actives</b></div> | |
| <table><tbody id="tasks"></tbody></table> | |
| <div class="ph">télémétrie</div> | |
| <div class="meters"> | |
| <div class="meter"><span>oreille</span><div class="b"><i id="m1" style="width:12%"></i></div><span class="v" id="v1">0,18 s</span></div> | |
| <div class="meter"><span>cerveau</span><div class="b"><i id="m2" style="width:30%"></i></div><span class="v" id="v2">0,31 s</span></div> | |
| <div class="meter"><span>voix</span><div class="b"><i id="m3" style="width:18%"></i></div><span class="v" id="v3">0,22 s</span></div> | |
| <div class="meter"><span>vram</span><div class="b"><i style="width:75%;background:var(--amber)"></i></div><span class="v">24/32 Go</span></div> | |
| </div> | |
| <div id="finds"></div> | |
| </aside> | |
| <div id="bot"> | |
| <select><option>MIC · Shure MV7</option></select> | |
| <span><kbd>Espace</kbd> micro on/off</span> | |
| <span><kbd>Échap</kbd> interrompre</span> | |
| <span class="sp"></span> | |
| <span>PROPOSITION C · COMMANDEMENT · démo</span> | |
| </div> | |
| <script> | |
| ; | |
| const $ = id => document.getElementById(id); | |
| const TAU = Math.PI * 2; | |
| let mode = "idle", level = 0, target = 0, t = 0; | |
| /* ------------------------------------------------ radar */ | |
| const rc = $("radar"), r = rc.getContext("2d"); | |
| const sc = $("scope"), s = sc.getContext("2d"); | |
| const fit = () => [rc, sc].forEach(c => { const b = c.getBoundingClientRect(); c.width = b.width * devicePixelRatio; c.height = b.height * devicePixelRatio; }); | |
| addEventListener("resize", fit); fit(); | |
| const blips = []; | |
| const wave = new Array(240).fill(0); | |
| function frame() { | |
| requestAnimationFrame(frame); t += 1 / 60; level += (target - level) * .2; | |
| const w = rc.width, h = rc.height, cx = w / 2, cy = h / 2, R = Math.min(w, h) * .44, d = devicePixelRatio; | |
| const G = mode === "thinking" ? "255,201,74" : "61,245,166"; | |
| r.clearRect(0, 0, w, h); | |
| // rings + crosshair | |
| r.strokeStyle = "rgba(61,245,166,.16)"; r.lineWidth = 1 * d; | |
| for (let i = 1; i <= 4; i++) { r.beginPath(); r.arc(cx, cy, R * i / 4, 0, TAU); r.stroke(); } | |
| r.beginPath(); r.moveTo(cx - R, cy); r.lineTo(cx + R, cy); r.moveTo(cx, cy - R); r.lineTo(cx, cy + R); r.stroke(); | |
| // bearing ticks | |
| for (let i = 0; i < 72; i++) { | |
| const a = i / 72 * TAU, k = i % 6 === 0 ? .94 : .97; | |
| r.beginPath(); r.moveTo(cx + R * k * Math.cos(a), cy + R * k * Math.sin(a)); r.lineTo(cx + R * Math.cos(a), cy + R * Math.sin(a)); r.stroke(); | |
| } | |
| // sweep (faster while thinking) | |
| const sweep = t * (mode === "thinking" ? 3.2 : 1.1); | |
| const grad = r.createConicGradient ? r.createConicGradient(sweep - .9, cx, cy) : null; | |
| if (grad) { | |
| grad.addColorStop(0, `rgba(${G},0)`); grad.addColorStop(.14, `rgba(${G},.28)`); grad.addColorStop(.143, `rgba(${G},0)`); | |
| r.fillStyle = grad; r.beginPath(); r.arc(cx, cy, R, 0, TAU); r.fill(); | |
| } | |
| // voice = pulsing core rings | |
| const core = R * (.16 + level * .12); | |
| r.fillStyle = `rgba(${G},.12)`; r.beginPath(); r.arc(cx, cy, core * 1.8, 0, TAU); r.fill(); | |
| r.fillStyle = `rgba(${G},.9)`; r.beginPath(); r.arc(cx, cy, core * .55, 0, TAU); r.fill(); | |
| r.strokeStyle = `rgba(${G},.7)`; r.lineWidth = 1.5 * d; r.beginPath(); r.arc(cx, cy, core, 0, TAU); r.stroke(); | |
| // blips = running / finished tasks | |
| blips.forEach(b => { | |
| const x = cx + R * b.r * Math.cos(b.a), y = cy + R * b.r * Math.sin(b.a); | |
| const age = ((sweep - b.a) % TAU + TAU) % TAU, glow = Math.max(.25, 1 - age / TAU); | |
| r.fillStyle = b.done ? `rgba(61,245,166,${glow})` : `rgba(255,201,74,${glow})`; | |
| r.beginPath(); r.arc(x, y, 5 * d, 0, TAU); r.fill(); | |
| r.font = `${11 * d}px IBM Plex Mono`; r.fillText(b.label, x + 10 * d, y + 4 * d); | |
| }); | |
| // oscilloscope | |
| wave.push(mode === "listening" || mode === "speaking" ? (Math.random() - .5) * level * 1.6 : (Math.random() - .5) * .03); | |
| wave.shift(); | |
| const W = sc.width, H = sc.height; | |
| s.clearRect(0, 0, W, H); | |
| s.strokeStyle = "rgba(61,245,166,.08)"; s.lineWidth = 1; | |
| for (let x = 0; x < W; x += 40 * d) { s.beginPath(); s.moveTo(x, 0); s.lineTo(x, H); s.stroke(); } | |
| s.strokeStyle = mode === "listening" ? "#58c6ff" : `rgb(${G})`; s.lineWidth = 1.6 * d; s.beginPath(); | |
| wave.forEach((v, i) => { const x = i / (wave.length - 1) * W, y = H / 2 + v * H * .9; i ? s.lineTo(x, y) : s.moveTo(x, y); }); | |
| s.stroke(); | |
| } | |
| frame(); | |
| /* ------------------------------------------------ demo */ | |
| setInterval(() => $("clock").textContent = new Date().toLocaleTimeString("fr-FR"), 1000); | |
| setInterval(() => { target = mode === "listening" || mode === "speaking" ? .3 + Math.random() * .7 : mode === "thinking" ? .1 : 0; }, 80); | |
| const sleep = ms => new Promise(res => setTimeout(res, ms)); | |
| const now = () => new Date().toLocaleTimeString("fr-FR"); | |
| const MODES = { idle: "VEILLE", listening: "ÉCOUTE", thinking: "ANALYSE", speaking: "RÉPONSE" }; | |
| function setMode(m) { mode = m; $("mode").className = m; $("mode").firstChild.textContent = MODES[m]; } | |
| function line(kind, who, text = "") { | |
| const el = document.createElement("div"); el.className = "ln " + kind; | |
| el.innerHTML = `<time>${now().slice(0, 5)}</time><div><span class="who">${who}</span><span class="tx">${text}</span></div>`; | |
| $("lines").append(el); while ($("lines").children.length > 14) $("lines").firstChild.remove(); | |
| $("cnt").textContent = +$("cnt").textContent + 1; | |
| return el.querySelector(".tx"); | |
| } | |
| async function type(el, text, speed = 26) { el.classList.add("cursor"); for (const ch of text) { el.textContent += ch; await sleep(speed); } el.classList.remove("cursor"); } | |
| function meters(a, b, c) { | |
| [[a, "m1", "v1"], [b, "m2", "v2"], [c, "m3", "v3"]].forEach(([v, m, l]) => { $(m).style.width = Math.min(100, v * 100) + "%"; $(l).textContent = v.toFixed(2).replace(".", ",") + " s"; }); | |
| } | |
| let active = 0; | |
| function task(title, engine) { | |
| const tr = document.createElement("tr"); | |
| tr.innerHTML = `<td class="st run">● RUN</td><td>${title}<small>${engine} · ${now()}</small></td>`; | |
| $("tasks").prepend(tr); active++; $("tc").textContent = active + " active" + (active > 1 ? "s" : ""); | |
| const b = { a: Math.random() * TAU, r: .5 + Math.random() * .35, label: title.split(" ")[0].toUpperCase(), done: false }; | |
| blips.push(b); if (blips.length > 4) blips.shift(); | |
| return () => { tr.firstChild.className = "st ok"; tr.firstChild.textContent = "✓ OK"; b.done = true; active--; $("tc").textContent = active + " actives"; }; | |
| } | |
| async function demo() { | |
| for (;;) { | |
| setMode("idle"); await sleep(1600); | |
| setMode("listening"); const u = line("you", "PASCAL"); await type(u, "Lance une veille sur les CVE critiques de la semaine.", 40); | |
| setMode("thinking"); await sleep(600); meters(.18 + Math.random() * .1, .3 + Math.random() * .15, .2 + Math.random() * .08); | |
| setMode("speaking"); const j = line("jv", "JARVIS"); await type(j, "Très bien, Pascal. Je lance la veille."); | |
| const done = task("Veille CVE critiques", "local · qwen3"); | |
| line("sys", "SYSTÈME", "tâche lancée sur le modèle local"); | |
| setMode("idle"); await sleep(4200); | |
| done(); line("sys", "SYSTÈME", "tâche terminée · notification envoyée"); | |
| setMode("speaking"); const j2 = line("jv", "JARVIS"); await type(j2, "Trois vulnérabilités critiques, dont une activement exploitée."); | |
| const f = document.createElement("div"); f.className = "finding"; | |
| f.innerHTML = `<h4>▲ VEILLE CVE · SEMAINE 40</h4><div><span>Exemple · passerelle VPN</span><b>9.8</b></div><div><span>Exemple · messagerie</span><b>9.1</b></div><div><span>Exemple · Kubernetes</span><b>8.8</b></div>`; | |
| $("finds").prepend(f); while ($("finds").children.length > 2) $("finds").lastChild.remove(); | |
| await sleep(2600); | |
| } | |
| } | |
| demo(); | |
| </script> | |
| </body> | |
| </html> | |