Download designs/a-reacteur.html from PascalF53/jarvis-local: direct link, hf CLI and curl.
- Browser
- Download file 13.9 kB
-
https://huggingface.co/PascalF53/jarvis-local/resolve/main/designs/a-reacteur.html
- Command line
-
hf download hf://PascalF53/jarvis-local/designs/a-reacteur.html
-
curl -L -o a-reacteur.html https://huggingface.co/PascalF53/jarvis-local/resolve/main/designs/a-reacteur.html
13.9 kB
| <html lang="fr"> | |
| <head> | |
| <meta charset="utf-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1"> | |
| <title>JARVIS · Proposition A — Réacteur</title> | |
| <link rel="preconnect" href="https://fonts.googleapis.com"> | |
| <link href="https://fonts.googleapis.com/css2?family=Rajdhani:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet"> | |
| <style> | |
| /* Direction A — "Réacteur" : l'esprit Iron Man, épuré. | |
| Une seule couleur vive (cyan) + un accent ambre réservé aux alertes, | |
| l'orbe au centre, tout le reste orbite autour en anneaux discrets. */ | |
| :root { | |
| --bg: #03070d; --bg2: #071321; | |
| --cyan: #5ee6ff; --cyan-2: #2aa8cc; --cyan-dim: rgba(94,230,255,.14); | |
| --amber: #ffb547; --ok: #5dffb0; --err: #ff5f7a; | |
| --text: #d8f4ff; --muted: #5f8aa3; --line: rgba(94,230,255,.16); | |
| --hud: "Rajdhani", sans-serif; --mono: "JetBrains Mono", monospace; | |
| } | |
| * { box-sizing: border-box; margin: 0; padding: 0; } | |
| html, body { height: 100%; } | |
| body { | |
| background: radial-gradient(900px 700px at 50% 45%, var(--bg2), var(--bg) 70%); | |
| color: var(--text); font-family: var(--hud); overflow: hidden; | |
| } | |
| /* fine concentric guides behind everything */ | |
| body::before { | |
| content: ""; position: fixed; inset: 0; pointer-events: none; | |
| background: repeating-radial-gradient(circle at 50% 45%, transparent 0 79px, rgba(94,230,255,.035) 80px); | |
| } | |
| /* ---------- top bar */ | |
| header { | |
| position: fixed; top: 0; left: 0; right: 0; height: 56px; z-index: 5; | |
| display: flex; align-items: center; gap: 22px; padding: 0 28px; | |
| font-family: var(--mono); font-size: 11px; letter-spacing: .22em; color: var(--muted); | |
| text-transform: uppercase; border-bottom: 1px solid var(--line); | |
| background: linear-gradient(180deg, rgba(3,7,13,.9), transparent); | |
| } | |
| header .brand { color: var(--cyan); font-weight: 500; letter-spacing: .4em; } | |
| header .brand b { color: var(--text); font-weight: 500; } | |
| header .sep { flex: 1; } | |
| header .pill { display: flex; align-items: center; gap: 7px; } | |
| header .pill i { width: 6px; height: 6px; border-radius: 50%; background: var(--ok); box-shadow: 0 0 8px var(--ok); } | |
| #clock { color: var(--text); } | |
| /* ---------- stage */ | |
| main { position: fixed; inset: 56px 0 0 0; display: grid; place-items: center; } | |
| #orbWrap { position: relative; width: min(62vh, 560px); aspect-ratio: 1; } | |
| #orb { width: 100%; height: 100%; display: block; cursor: pointer; } | |
| #state { | |
| position: absolute; left: 50%; bottom: -6px; transform: translateX(-50%); | |
| font-family: var(--mono); font-size: 11px; letter-spacing: .5em; text-transform: uppercase; | |
| color: var(--cyan); text-shadow: 0 0 14px rgba(94,230,255,.7); white-space: nowrap; | |
| } | |
| #state small { color: var(--muted); letter-spacing: .2em; margin-left: 10px; } | |
| /* subtitles: user line (dim) above, JARVIS line (bright) below */ | |
| #subs { position: absolute; left: 50%; bottom: 7vh; transform: translateX(-50%); width: min(820px, 80vw); text-align: center; } | |
| #you { font-size: 15px; color: var(--muted); min-height: 22px; font-style: italic; } | |
| #you:not(:empty)::before { content: "VOUS › "; font-family: var(--mono); font-style: normal; font-size: 10px; letter-spacing: .25em; color: var(--cyan-2); } | |
| #say { margin-top: 8px; font-size: 22px; font-weight: 500; line-height: 1.35; min-height: 30px; text-shadow: 0 0 22px rgba(94,230,255,.25); } | |
| /* ---------- left: holo cards */ | |
| #cards { position: fixed; left: 28px; top: 84px; width: 320px; display: flex; flex-direction: column; gap: 12px; z-index: 4; } | |
| .card { | |
| position: relative; padding: 14px 16px 14px 18px; | |
| background: linear-gradient(120deg, rgba(94,230,255,.08), rgba(7,19,33,.85) 50%); | |
| border: 1px solid var(--line); backdrop-filter: blur(8px); | |
| clip-path: polygon(0 0, calc(100% - 14px) 0, 100% 14px, 100% 100%, 14px 100%, 0 calc(100% - 14px)); | |
| animation: in .5s cubic-bezier(.2,.9,.25,1); | |
| } | |
| .card::before { content: ""; position: absolute; left: 0; top: 14px; bottom: 14px; width: 2px; background: var(--cyan); box-shadow: 0 0 10px var(--cyan); } | |
| .card.result::before { background: var(--ok); box-shadow: 0 0 10px var(--ok); } | |
| .card h3 { font-family: var(--mono); font-size: 10px; letter-spacing: .3em; text-transform: uppercase; color: var(--cyan); margin-bottom: 8px; font-weight: 500; } | |
| .card.result h3 { color: var(--ok); } | |
| .card p, .card li { font-size: 14.5px; line-height: 1.5; } | |
| .card ul { list-style: none; } | |
| .card li { display: flex; justify-content: space-between; border-bottom: 1px dashed var(--line); padding: 3px 0; } | |
| .card li b { font-family: var(--mono); font-weight: 500; color: var(--amber); font-size: 13px; } | |
| @keyframes in { from { opacity: 0; transform: translateX(-30px); filter: brightness(2.5); } } | |
| /* ---------- right: tasks as a vertical rail */ | |
| #rail { position: fixed; right: 28px; top: 84px; width: 300px; z-index: 4; } | |
| #rail h2 { font-family: var(--mono); font-size: 10px; letter-spacing: .32em; color: var(--muted); text-transform: uppercase; margin-bottom: 14px; display: flex; justify-content: space-between; } | |
| .task { position: relative; padding: 0 0 18px 22px; border-left: 1px solid var(--line); margin-left: 5px; animation: in .4s ease; } | |
| .task::before { content: ""; position: absolute; left: -6px; top: 2px; width: 11px; height: 11px; border-radius: 50%; border: 2px solid var(--cyan); background: var(--bg); box-shadow: 0 0 10px var(--cyan); animation: pulse 1.2s infinite alternate; } | |
| .task.done::before { border-color: var(--ok); background: var(--ok); box-shadow: 0 0 10px var(--ok); animation: none; } | |
| .task .t { font-size: 15px; font-weight: 600; } | |
| .task .m { font-family: var(--mono); font-size: 10.5px; color: var(--muted); margin-top: 3px; letter-spacing: .08em; } | |
| .task .m .eng { color: var(--amber); } | |
| .task .bar { height: 2px; background: var(--line); margin-top: 8px; overflow: hidden; } | |
| .task .bar i { display: block; height: 100%; width: 35%; background: var(--cyan); animation: scan 1.4s linear infinite; } | |
| .task.done .bar i { width: 100%; background: var(--ok); animation: none; } | |
| @keyframes pulse { to { box-shadow: 0 0 2px var(--cyan); } } | |
| @keyframes scan { from { transform: translateX(-100%); } to { transform: translateX(300%); } } | |
| /* ---------- bottom-left: mic + telemetry */ | |
| footer { | |
| position: fixed; left: 28px; bottom: 22px; z-index: 5; display: flex; gap: 18px; align-items: center; | |
| font-family: var(--mono); font-size: 10.5px; color: var(--muted); letter-spacing: .12em; | |
| } | |
| select { background: transparent; color: var(--muted); border: 1px solid var(--line); padding: 5px 8px; font: inherit; } | |
| footer b { color: var(--text); font-weight: 400; } | |
| .badge { position: fixed; right: 28px; bottom: 22px; font-family: var(--mono); font-size: 10px; letter-spacing: .25em; color: var(--muted); } | |
| </style> | |
| </head> | |
| <body> | |
| <header> | |
| <span class="brand">J.A.R.V.I.S. <b>· MK VIII</b></span> | |
| <span class="pill"><i></i> 100 % local</span> | |
| <span class="pill">qwen3 · whisper · kokoro</span> | |
| <span class="sep"></span> | |
| <span id="clock">--:--:--</span> | |
| </header> | |
| <main> | |
| <div id="orbWrap"> | |
| <canvas id="orb"></canvas> | |
| <div id="state">en veille</div> | |
| </div> | |
| <div id="subs"><div id="you"></div><div id="say"></div></div> | |
| </main> | |
| <section id="cards"></section> | |
| <aside id="rail"><h2><span>Tâches</span><span id="count">0</span></h2><div id="tasks"></div></aside> | |
| <footer> | |
| <select><option>🎙 Micro — Shure MV7</option></select> | |
| <span>latence <b id="lat">0,74 s</b></span> | |
| <span>vram <b>24,1 / 32 Go</b></span> | |
| </footer> | |
| <div class="badge">PROPOSITION A · RÉACTEUR · démo</div> | |
| <script> | |
| ; | |
| /* ------------------------------------------------ orb: layered arc reactor */ | |
| const cv = document.getElementById("orb"), cx2 = cv.getContext("2d"); | |
| const fit = () => { const r = cv.getBoundingClientRect(); cv.width = r.width * devicePixelRatio; cv.height = r.height * devicePixelRatio; }; | |
| addEventListener("resize", fit); fit(); | |
| let level = 0, target = 0, mode = "idle", t = 0; | |
| const TAU = Math.PI * 2; | |
| const COL = { idle: [94,230,255], listening: [94,230,255], thinking: [255,181,71], speaking: [93,255,176] }; | |
| let col = [...COL.idle]; | |
| function draw() { | |
| requestAnimationFrame(draw); t += 1 / 60; | |
| level += (target - level) * 0.18; | |
| col = col.map((c, i) => c + (COL[mode][i] - c) * 0.06); | |
| const C = col.map(Math.round).join(","); | |
| const w = cv.width, h = cv.height, cx = w / 2, cy = h / 2, d = devicePixelRatio; | |
| const R = Math.min(w, h) * 0.17 * (1 + level * 0.12); | |
| cx2.clearRect(0, 0, w, h); | |
| // halo | |
| let g = cx2.createRadialGradient(cx, cy, R * .3, cx, cy, R * 2.9); | |
| g.addColorStop(0, `rgba(${C},${.22 + level * .25})`); g.addColorStop(1, "transparent"); | |
| cx2.fillStyle = g; cx2.fillRect(0, 0, w, h); | |
| // radial spectrum (the voice made visible) | |
| const bars = 96; | |
| for (let i = 0; i < bars; i++) { | |
| const a = i / bars * TAU - Math.PI / 2; | |
| const n = Math.abs(Math.sin(i * 1.7 + t * 5) * Math.sin(i * .31 - t * 3)); | |
| const len = R * (.06 + level * .55 * n + (mode === "thinking" ? .05 * Math.sin(t * 6 + i / 4) ** 2 : 0)); | |
| const r0 = R * 1.32; | |
| cx2.strokeStyle = `rgba(${C},${.35 + n * .5})`; cx2.lineWidth = 2.2 * d; | |
| cx2.beginPath(); cx2.moveTo(cx + r0 * Math.cos(a), cy + r0 * Math.sin(a)); | |
| cx2.lineTo(cx + (r0 + len) * Math.cos(a), cy + (r0 + len) * Math.sin(a)); cx2.stroke(); | |
| } | |
| // segmented coil ring | |
| cx2.save(); cx2.translate(cx, cy); cx2.rotate(t * (mode === "thinking" ? .9 : .18)); | |
| for (let i = 0; i < 10; i++) { | |
| const a0 = i / 10 * TAU; | |
| cx2.strokeStyle = `rgba(${C},.85)`; cx2.lineWidth = 6 * d; | |
| cx2.beginPath(); cx2.arc(0, 0, R * 1.16, a0, a0 + TAU / 10 * .7); cx2.stroke(); | |
| } | |
| cx2.restore(); | |
| // outer graduated ring + rotating reticle | |
| cx2.save(); cx2.translate(cx, cy); cx2.rotate(-t * .07); | |
| for (let i = 0; i < 120; i++) { | |
| const a = i / 120 * TAU, big = i % 10 === 0; | |
| cx2.strokeStyle = big ? "rgba(255,181,71,.8)" : `rgba(${C},.22)`; cx2.lineWidth = (big ? 2 : 1) * d; | |
| const r0 = R * 2.05, r1 = R * (big ? 2.18 : 2.11); | |
| cx2.beginPath(); cx2.moveTo(r0 * Math.cos(a), r0 * Math.sin(a)); cx2.lineTo(r1 * Math.cos(a), r1 * Math.sin(a)); cx2.stroke(); | |
| } | |
| cx2.restore(); | |
| [[1.9, .35, 1], [2.32, .12, -1.6]].forEach(([k, span, sp]) => { | |
| cx2.save(); cx2.translate(cx, cy); cx2.rotate(t * sp * .4); | |
| cx2.strokeStyle = `rgba(${C},.6)`; cx2.lineWidth = 1.6 * d; | |
| cx2.beginPath(); cx2.arc(0, 0, R * k, 0, TAU * span); cx2.stroke(); cx2.restore(); | |
| }); | |
| // core | |
| g = cx2.createRadialGradient(cx, cy, 0, cx, cy, R); | |
| g.addColorStop(0, "rgba(245,255,255,1)"); g.addColorStop(.35, `rgba(${C},.95)`); g.addColorStop(1, "rgba(5,25,45,.95)"); | |
| cx2.fillStyle = g; cx2.beginPath(); cx2.arc(cx, cy, R * .95, 0, TAU); cx2.fill(); | |
| // triangle (MK signature) | |
| cx2.save(); cx2.translate(cx, cy); cx2.rotate(-t * .05); | |
| cx2.strokeStyle = "rgba(255,255,255,.55)"; cx2.lineWidth = 1.5 * d; cx2.beginPath(); | |
| for (let i = 0; i <= 3; i++) { const a = i / 3 * TAU - Math.PI / 2; const p = [R * .62 * Math.cos(a), R * .62 * Math.sin(a)]; i ? cx2.lineTo(...p) : cx2.moveTo(...p); } | |
| cx2.stroke(); cx2.restore(); | |
| } | |
| draw(); | |
| /* ------------------------------------------------ demo conversation */ | |
| const $ = id => document.getElementById(id); | |
| setInterval(() => $("clock").textContent = new Date().toLocaleTimeString("fr-FR"), 1000); | |
| const LABEL = { idle: "en veille", listening: "écoute", thinking: "réflexion", speaking: "jarvis parle" }; | |
| function setMode(m, sub = "") { mode = m; $("state").innerHTML = LABEL[m] + (sub ? `<small>${sub}</small>` : ""); } | |
| const sleep = ms => new Promise(r => setTimeout(r, ms)); | |
| async function type(el, text, speed = 28) { el.textContent = ""; for (const ch of text) { el.textContent += ch; await sleep(speed); } } | |
| setInterval(() => { target = mode === "listening" || mode === "speaking" ? .35 + Math.random() * .65 : mode === "thinking" ? .12 : 0; }, 90); | |
| function card(title, html, kind = "") { | |
| const c = document.createElement("div"); c.className = "card " + kind; | |
| c.innerHTML = `<h3>${title}</h3>${html}`; $("cards").prepend(c); | |
| while ($("cards").children.length > 3) $("cards").lastChild.remove(); | |
| } | |
| function task(title, engine) { | |
| const el = document.createElement("div"); el.className = "task"; | |
| el.innerHTML = `<div class="t">${title}</div><div class="m"><span class="eng">${engine}</span> · <span class="s">en cours</span> · <span class="c">0 s</span></div><div class="bar"><i></i></div>`; | |
| $("tasks").prepend(el); $("count").textContent = $("tasks").children.length; | |
| const t0 = Date.now(), iv = setInterval(() => el.querySelector(".c").textContent = Math.round((Date.now() - t0) / 1000) + " s", 1000); | |
| return () => { clearInterval(iv); el.classList.add("done"); el.querySelector(".s").textContent = "terminé"; }; | |
| } | |
| async function demo() { | |
| for (;;) { | |
| setMode("idle"); $("you").textContent = ""; $("say").textContent = ""; await sleep(1800); | |
| setMode("listening"); await type($("you"), "Lance une veille sur les CVE critiques de la semaine.", 45); | |
| setMode("thinking", "0,2 s"); await sleep(700); | |
| setMode("speaking"); await type($("say"), "Très bien, Pascal. Je lance la veille, je vous préviens dès qu'elle est prête."); | |
| const done = task("Veille CVE critiques", "local"); | |
| await sleep(1400); setMode("idle"); await sleep(3200); | |
| done(); | |
| setMode("speaking"); $("you").textContent = ""; | |
| await type($("say"), "La veille est prête : trois vulnérabilités critiques, dont une activement exploitée."); | |
| card("Veille CVE · semaine 40", `<ul><li>Exemple · passerelle VPN <b>9.8</b></li><li>Exemple · messagerie <b>9.1</b></li><li>Exemple · Kubernetes <b>8.8</b></li></ul>`, "result"); | |
| await sleep(2600); | |
| } | |
| } | |
| demo(); | |
| </script> | |
| </body> | |
| </html> | |