MGros commited on
Commit
2ea9108
·
verified ·
1 Parent(s): e4ce48c

L'affiage graphique du bras ne fonctionne toujours pas - Follow Up Deployment

Browse files
Files changed (1) hide show
  1. index.html +561 -470
index.html CHANGED
@@ -1,524 +1,615 @@
1
  <!DOCTYPE html>
2
- <html lang="en">
3
  <head>
4
  <meta charset="UTF-8">
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
- <title>The Operator - FDI Terminal</title>
7
  <script src="https://cdn.tailwindcss.com"></script>
8
- <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
9
  <style>
10
- @import url('https://fonts.googleapis.com/css2?family=VT323&display=swap');
11
-
12
- body {
13
- font-family: 'VT323', monospace;
14
- background-color: #000;
15
- color: #00ff00;
16
- overflow: hidden;
17
- position: relative;
18
- }
19
-
20
- .crt::before {
21
- content: " ";
22
- display: block;
23
- position: absolute;
24
- top: 0;
25
- left: 0;
26
- bottom: 0;
27
- right: 0;
28
- background: linear-gradient(rgba(18, 16, 16, 0) 50%, rgba(0, 0, 0, 0.25) 50%);
29
- background-size: 100% 4px;
30
- z-index: 100;
31
- pointer-events: none;
32
- }
33
-
34
- .crt::after {
35
- content: " ";
36
- display: block;
37
- position: absolute;
38
- top: 0;
39
- left: 0;
40
- bottom: 0;
41
- right: 0;
42
- background: rgba(0, 30, 0, 0.05);
43
- z-index: 100;
44
- pointer-events: none;
45
- }
46
-
47
- .scanlines {
48
- position: relative;
49
- overflow: hidden;
50
- }
51
-
52
- .scanlines::before {
53
- content: "";
54
- position: absolute;
55
- top: 0;
56
- left: 0;
57
- width: 100%;
58
- height: 100%;
59
- background: repeating-linear-gradient(
60
- to bottom,
61
- transparent 0%,
62
- rgba(0, 255, 0, 0.05) 0.5%,
63
- transparent 1%
64
- );
65
- animation: scanline 8s linear infinite;
66
- z-index: 1001;
67
- pointer-events: none;
68
- }
69
-
70
- @keyframes scanline {
71
- from { transform: translateY(0%); }
72
- to { transform: translateY(-100%); }
73
  }
74
-
75
- .glow {
76
- text-shadow: 0 0 5px #00ff00;
 
 
77
  }
78
-
79
- .window {
80
- box-shadow: 0 0 10px #00ff00;
81
- border: 1px solid #00ff00;
82
  }
83
-
84
- .terminal-text {
85
- animation: blink 1s step-end infinite;
86
  }
87
-
88
- @keyframes blink {
89
- from, to { opacity: 1; }
90
- 50% { opacity: 0; }
91
  }
92
-
93
- .pixelated {
94
- image-rendering: pixelated;
95
- image-rendering: -moz-crisp-edges;
96
- image-rendering: crisp-edges;
97
- }
98
-
99
- .noise {
100
- background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyCAYAAAAeP4ixAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAABOSURBVGhD7c4xEQAgDAAxhHSj+5+2jAgywL0kO7O7AwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP4zX14KZ3Z3B5QAAAAASUVORK5CYII=");
101
- opacity: 0.05;
102
- position: absolute;
103
- top: 0;
104
- left: 0;
105
- width: 100%;
106
- height: 100%;
107
- pointer-events: none;
108
  }
109
  </style>
110
  </head>
111
- <body class="crt scanlines h-screen w-screen overflow-hidden">
112
- <div class="noise"></div>
113
-
114
- <!-- Main Container -->
115
- <div class="container mx-auto h-full p-4 flex flex-col">
116
- <!-- Header -->
117
- <div class="flex justify-between items-center mb-2">
118
- <div class="text-2xl glow">
119
- <span class="text-amber-400">FDI</span> TERMINAL v3.1.4
120
- </div>
121
- <div class="text-sm">
122
- <span class="text-amber-400">OPERATOR:</span> EVAN TANNER
123
- </div>
124
- <div class="text-sm">
125
- <span class="text-amber-400">DATE:</span> <span id="current-date">05/12/1991</span>
126
- </div>
127
- </div>
128
 
129
- <!-- Main Content -->
130
- <div class="flex flex-1 gap-4 overflow-hidden">
131
- <!-- Left Panel -->
132
- <div class="w-1/4 flex flex-col gap-4">
133
- <!-- Case Info -->
134
- <div class="window bg-black p-3 flex-1">
135
- <div class="border-b border-green-500 pb-1 mb-2 flex justify-between items-center">
136
- <span class="text-lg glow">CASE #X-2479</span>
137
- <span class="text-xs">CLASSIFIED</span>
138
- </div>
139
- <div class="text-sm space-y-2">
140
- <p><span class="text-amber-400">LOCATION:</span> BLACKWOOD FACILITY</p>
141
- <p><span class="text-amber-400">STATUS:</span> <span class="text-red-400">CRITICAL</span></p>
142
- <p><span class="text-amber-400">AGENT:</span> R. KOWALSKI</p>
143
- <p><span class="text-amber-400">LAST CONTACT:</span> 23:47</p>
144
- </div>
145
- <div class="mt-4 border-t border-green-500 pt-2">
146
- <p class="text-xs">SUSPECTED ACTIVITY:</p>
147
- <p class="text-red-400">UNAUTHORIZED ACCESS TO SCADA SYSTEMS</p>
148
- <p class="text-red-400">POTENTIAL SABOTAGE</p>
 
149
  </div>
150
  </div>
151
 
152
- <!-- Tools -->
153
- <div class="window bg-black p-3">
154
- <div class="border-b border-green-500 pb-1 mb-2">
155
- <span class="text-lg glow">TOOLS</span>
156
- </div>
157
- <div class="grid grid-cols-2 gap-2">
158
- <button class="bg-green-900 hover:bg-green-800 p-1 text-xs" onclick="openTool('chemScan')">CHEMSCAN</button>
159
- <button class="bg-green-900 hover:bg-green-800 p-1 text-xs" onclick="openTool('dataDecrypt')">DATA DECRYPT</button>
160
- <button class="bg-green-900 hover:bg-green-800 p-1 text-xs" onclick="openTool('videoAnalyzer')">VIDEO ANALYZER</button>
161
- <button class="bg-green-900 hover:bg-green-800 p-1 text-xs" onclick="openTool('comms')">COMMS LINK</button>
162
- <button class="bg-green-900 hover:bg-green-800 p-1 text-xs" onclick="openTool('scada')">SCADA ACCESS</button>
163
- <button class="bg-green-900 hover:bg-green-800 p-1 text-xs" onclick="openTool('logs')">SYSTEM LOGS</button>
164
- </div>
165
- </div>
166
- </div>
167
-
168
- <!-- Center Panel -->
169
- <div class="w-2/4 flex flex-col gap-4">
170
- <!-- Main Terminal -->
171
- <div class="window bg-black p-3 flex-1 relative">
172
- <div class="border-b border-green-500 pb-1 mb-2">
173
- <span class="text-lg glow">MAIN TERMINAL</span>
174
- </div>
175
- <div id="terminal-output" class="h-5/6 overflow-y-auto text-sm font-mono leading-snug">
176
- <p>> INITIALIZING FDI TERMINAL...</p>
177
- <p>> LOADING CASE FILES...</p>
178
- <p>> ESTABLISHING SECURE CONNECTION...</p>
179
- <p class="text-amber-400">> WARNING: UNAUTHORIZED ACCESS DETECTED IN SYSTEM 4B</p>
180
- <p>> LAST KNOWN AGENT STATUS: IN COMMUNICATION WITH SUSPECT</p>
181
- <p>> SCADA SYSTEMS SHOW ABNORMAL PRESSURE READINGS IN NORTH WING</p>
182
- <p class="text-red-400">> ALERT: FIRE SUPPRESSION SYSTEM DISABLED IN SECTOR D</p>
183
- <p>> ENTER COMMAND OR SELECT TOOL TO PROCEED<span class="terminal-text">_</span></p>
184
- </div>
185
- <div class="absolute bottom-3 left-3 right-3">
186
- <div class="flex">
187
- <span class="mr-2">></span>
188
- <input type="text" id="command-input" class="bg-black text-green-500 flex-1 outline-none font-mono" autofocus>
189
- </div>
190
- </div>
191
  </div>
192
 
193
- <!-- Evidence -->
194
- <div class="window bg-black p-3">
195
- <div class="border-b border-green-500 pb-1 mb-2">
196
- <span class="text-lg glow">EVIDENCE</span>
 
197
  </div>
198
- <div class="flex gap-2 overflow-x-auto">
199
- <div class="border border-green-500 p-1 flex flex-col items-center" onclick="viewEvidence('video1')">
200
- <div class="w-16 h-16 bg-gray-800 flex items-center justify-center">
201
- <i class="fas fa-video text-green-500"></i>
202
- </div>
203
- <span class="text-xs mt-1">SURV_001.avi</span>
204
- </div>
205
- <div class="border border-green-500 p-1 flex flex-col items-center" onclick="viewEvidence('chem1')">
206
- <div class="w-16 h-16 bg-gray-800 flex items-center justify-center">
207
- <i class="fas fa-flask text-green-500"></i>
208
- </div>
209
- <span class="text-xs mt-1">CHEM_004.dat</span>
210
- </div>
211
- <div class="border border-green-500 p-1 flex flex-col items-center" onclick="viewEvidence('doc1')">
212
- <div class="w-16 h-16 bg-gray-800 flex items-center justify-center">
213
- <i class="fas fa-file-alt text-green-500"></i>
214
- </div>
215
- <span class="text-xs mt-1">MEMO_023.txt</span>
216
- </div>
217
- <div class="border border-green-500 p-1 flex flex-col items-center" onclick="viewEvidence('audio1')">
218
- <div class="w-16 h-16 bg-gray-800 flex items-center justify-center">
219
- <i class="fas fa-microphone text-green-500"></i>
220
- </div>
221
- <span class="text-xs mt-1">AUDIO_007.wav</span>
222
- </div>
223
- <div class="border border-green-500 p-1 flex flex-col items-center" onclick="viewEvidence('scada1')">
224
- <div class="w-16 h-16 bg-gray-800 flex items-center justify-center">
225
- <i class="fas fa-chart-line text-green-500"></i>
226
- </div>
227
- <span class="text-xs mt-1">SCADA_LOG.rdt</span>
228
- </div>
229
  </div>
230
  </div>
231
  </div>
232
 
233
- <!-- Right Panel -->
234
- <div class="w-1/4 flex flex-col gap-4">
235
- <!-- Comms -->
236
- <div class="window bg-black p-3 flex-1">
237
- <div class="border-b border-green-500 pb-1 mb-2">
238
- <span class="text-lg glow">COMMS CHANNEL</span>
239
- </div>
240
- <div id="comms-log" class="h-64 overflow-y-auto text-xs space-y-1">
241
- <p class="text-amber-400">[23:47] AGENT KOWALSKI: I'm in position. No visual on suspect yet.</p>
242
- <p class="text-amber-400">[23:49] AGENT KOWALSKI: Found something... security logs show unauthorized access.</p>
243
- <p class="text-amber-400">[23:52] AGENT KOWALSKI: There's a chemical spill in sector C. Doesn't look accidental.</p>
244
- <p class="text-red-400">[23:55] AGENT KOWALSKI: I see someone! They're tampering with the control panel!</p>
245
- <p class="text-red-400">[23:56] AGENT KOWALSKI: They're wearing an FDI badge but I don't recognize them.</p>
246
- <p class="text-red-400">[23:57] AGENT KOWALSKI: They've spotted me! I'm pursuing on foot!</p>
247
- <p class="text-red-400">[23:58] AGENT KOWALSKI: They're heading to the server room!</p>
248
- <p class="text-gray-500">[23:59] COMMS LOST...</p>
249
- </div>
250
- <div class="mt-2 border-t border-green-500 pt-2">
251
- <div class="flex">
252
- <input type="text" class="bg-black text-green-500 flex-1 outline-none text-xs" placeholder="TYPE MESSAGE...">
253
- <button class="bg-green-900 hover:bg-green-800 px-2 text-xs">SEND</button>
254
- </div>
255
- </div>
256
- </div>
257
 
258
- <!-- Alerts -->
259
- <div class="window bg-black p-3">
260
- <div class="border-b border-green-500 pb-1 mb-2">
261
- <span class="text-lg glow">ALERTS</span>
262
- </div>
263
- <div class="space-y-2">
264
- <div class="border border-red-500 p-2">
265
- <p class="text-xs text-red-400">SCADA ALERT: PRESSURE BUILDING IN NORTH WING</p>
266
- <p class="text-xs">Current: 4.7 MPa | Safe limit: 3.2 MPa</p>
267
  </div>
268
- <div class="border border-amber-500 p-2">
269
- <p class="text-xs text-amber-400">SECURITY ALERT: SERVER ROOM ACCESS</p>
270
- <p class="text-xs">Unauthorized user: ID 4473</p>
271
  </div>
272
- <div class="border border-green-500 p-2">
273
- <p class="text-xs">SYSTEM ALERT: BACKUP POWER ACTIVATED</p>
 
274
  </div>
275
  </div>
276
  </div>
277
- </div>
278
- </div>
279
-
280
- <!-- Status Bar -->
281
- <div class="mt-2 text-xs flex justify-between">
282
- <div>
283
- <span class="text-amber-400">STATUS:</span> ACTIVE | <span class="text-amber-400">CONNECTION:</span> ENCRYPTED | <span class="text-amber-400">POWER:</span> 98%
284
- </div>
285
- <div>
286
- <span class="text-amber-400">FDI HEADQUARTERS</span> - ALL RIGHTS RESERVED 1991
287
- </div>
288
- </div>
289
- </div>
290
-
291
- <!-- Tool Windows (Hidden by default) -->
292
- <div id="chemScan-window" class="window bg-black absolute top-1/4 left-1/4 w-1/2 h-1/2 p-3 hidden" style="transform: translate(-50%, -50%); z-index: 1000;">
293
- <div class="flex justify-between items-center border-b border-green-500 pb-1 mb-2">
294
- <span class="text-lg glow">CHEMSCAN v2.4</span>
295
- <button onclick="closeTool('chemScan')" class="text-red-400 hover:text-red-300">X</button>
296
- </div>
297
- <div class="flex h-5/6">
298
- <div class="w-1/3 border-r border-green-500 pr-2">
299
- <p class="text-sm mb-2">SELECT SAMPLE:</p>
300
- <div class="space-y-1">
301
- <button class="w-full text-left p-1 hover:bg-green-900 text-xs">CHEM_001.dat</button>
302
- <button class="w-full text-left p-1 hover:bg-green-900 text-xs">CHEM_002.dat</button>
303
- <button class="w-full text-left p-1 hover:bg-green-900 text-xs">CHEM_003.dat</button>
304
- <button class="w-full text-left p-1 bg-green-900 text-xs">CHEM_004.dat</button>
305
- </div>
306
- </div>
307
- <div class="w-2/3 pl-2">
308
- <p class="text-sm mb-2">ANALYSIS RESULTS:</p>
309
- <div class="border border-green-500 p-2 h-4/5 overflow-y-auto text-xs">
310
- <p>> SAMPLE: CHEM_004.dat</p>
311
- <p>> SOURCE: NORTH WING, SECTOR C</p>
312
- <p>> COLLECTED: 05/12/1991 23:30</p>
313
- <p class="mt-2 text-amber-400">> COMPOSITION ANALYSIS:</p>
314
- <p>- 45% TRICHLOROETHYLENE</p>
315
- <p>- 30% METHYL ETHYL KETONE</p>
316
- <p>- 15% UNKNOWN COMPOUND</p>
317
- <p>- 10% WATER</p>
318
- <p class="mt-2 text-red-400">> WARNING: UNKNOWN COMPOUND MATCHES EXPERIMENTAL FORMULA X-247</p>
319
- <p class="text-red-400">> CAUTION: HIGHLY VOLATILE MIXTURE</p>
320
- </div>
321
- <button class="mt-2 bg-green-900 hover:bg-green-800 p-1 text-xs w-full">EXPORT REPORT</button>
322
- </div>
323
- </div>
324
- </div>
325
-
326
- <div id="videoAnalyzer-window" class="window bg-black absolute top-1/4 left-1/4 w-1/2 h-1/2 p-3 hidden" style="transform: translate(-50%, -50%); z-index: 1000;">
327
- <div class="flex justify-between items-center border-b border-green-500 pb-1 mb-2">
328
- <span class="text-lg glow">VIDEO ANALYZER v1.7</span>
329
- <button onclick="closeTool('videoAnalyzer')" class="text-red-400 hover:text-red-300">X</button>
330
- </div>
331
- <div class="flex h-5/6">
332
- <div class="w-1/3 border-r border-green-500 pr-2">
333
- <p class="text-sm mb-2">SELECT FILE:</p>
334
- <div class="space-y-1">
335
- <button class="w-full text-left p-1 bg-green-900 text-xs">SURV_001.avi</button>
336
- <button class="w-full text-left p-1 hover:bg-green-900 text-xs">SURV_002.avi</button>
337
- <button class="w-full text-left p-1 hover:bg-green-900 text-xs">SURV_003.avi</button>
338
- </div>
339
- <div class="mt-4">
340
- <p class="text-sm mb-2">TOOLS:</p>
341
- <button class="w-full p-1 hover:bg-green-900 text-xs mb-1">ENHANCE</button>
342
- <button class="w-full p-1 hover:bg-green-900 text-xs mb-1">FRAME ANALYSIS</button>
343
- <button class="w-full p-1 hover:bg-green-900 text-xs">AUDIO ISOLATION</button>
344
  </div>
345
- </div>
346
- <div class="w-2/3 pl-2 flex flex-col">
347
- <div class="border border-green-500 h-48 mb-2 flex items-center justify-center bg-gray-900">
348
- <div class="text-center">
349
- <i class="fas fa-video text-green-500 text-4xl mb-2"></i>
350
- <p class="text-xs">VIDEO PLAYBACK</p>
351
  </div>
352
  </div>
353
- <div class="border border-green-500 p-2 flex-1 overflow-y-auto text-xs">
354
- <p>> FILE: SURV_001.avi</p>
355
- <p>> SOURCE: NORTH WING CAMERA 4B</p>
356
- <p>> TIMESTAMP: 05/12/1991 23:55:12</p>
357
- <p class="mt-2 text-amber-400">> ANALYSIS:</p>
358
- <p>- SINGLE SUBJECT VISIBLE</p>
359
- <p>- MALE, APPROX 180CM</p>
360
- <p>- WEARING FDI SECURITY UNIFORM</p>
361
- <p>- ID BADGE VISIBLE BUT BLURRED</p>
362
- <p class="text-red-400">- CARRYING UNKNOWN DEVICE</p>
363
- <p class="mt-2">> AUDIO TRANSCRIPT:</p>
364
- <p>"...just a few more minutes... systems will be offline... HAL will be pleased..."</p>
365
  </div>
366
  </div>
367
  </div>
368
- </div>
369
-
370
- <div id="scada-window" class="window bg-black absolute top-1/4 left-1/4 w-1/2 h-1/2 p-3 hidden" style="transform: translate(-50%, -50%); z-index: 1000;">
371
- <div class="flex justify-between items-center border-b border-green-500 pb-1 mb-2">
372
- <span class="text-lg glow">SCADA MONITOR v4.2</span>
373
- <button onclick="closeTool('scada')" class="text-red-400 hover:text-red-300">X</button>
374
- </div>
375
- <div class="flex h-5/6">
376
- <div class="w-1/3 border-r border-green-500 pr-2">
377
- <p class="text-sm mb-2">SYSTEMS:</p>
378
- <div class="space-y-1">
379
- <button class="w-full text-left p-1 hover:bg-green-900 text-xs">POWER DISTRIBUTION</button>
380
- <button class="w-full text-left p-1 hover:bg-green-900 text-xs">HVAC</button>
381
- <button class="w-full text-left p-1 bg-green-900 text-xs">PRESSURE SYSTEMS</button>
382
- <button class="w-full text-left p-1 hover:bg-green-900 text-xs">FIRE SUPPRESSION</button>
383
- <button class="w-full text-left p-1 hover:bg-green-900 text-xs">SECURITY</button>
384
- </div>
385
- </div>
386
- <div class="w-2/3 pl-2">
387
- <p class="text-sm mb-2">PRESSURE SYSTEMS STATUS:</p>
388
- <div class="border border-green-500 p-2 h-4/5 overflow-y-auto text-xs">
389
- <div class="mb-4">
390
- <p class="text-amber-400">NORTH WING:</p>
391
- <div class="w-full bg-gray-800 h-4 mt-1">
392
- <div class="bg-red-500 h-full" style="width: 90%;"></div>
393
- </div>
394
- <p>4.7 MPa / 3.2 MPa MAX</p>
395
- <p class="text-red-400">CRITICAL OVERPRESSURE</p>
396
- </div>
397
- <div class="mb-4">
398
- <p class="text-amber-400">SOUTH WING:</p>
399
- <div class="w-full bg-gray-800 h-4 mt-1">
400
- <div class="bg-green-500 h-full" style="width: 45%;"></div>
401
- </div>
402
- <p>2.1 MPa / 3.2 MPa MAX</p>
403
- <p>NORMAL</p>
404
- </div>
405
- <div class="mb-4">
406
- <p class="text-amber-400">EAST WING:</p>
407
- <div class="w-full bg-gray-800 h-4 mt-1">
408
- <div class="bg-amber-500 h-full" style="width: 70%;"></div>
409
- </div>
410
- <p>3.0 MPa / 3.2 MPa MAX</p>
411
- <p class="text-amber-400">ELEVATED</p>
412
- </div>
413
- <div>
414
- <p class="text-amber-400">WEST WING:</p>
415
- <div class="w-full bg-gray-800 h-4 mt-1">
416
- <div class="bg-green-500 h-full" style="width: 30%;"></div>
417
- </div>
418
- <p>1.4 MPa / 3.2 MPa MAX</p>
419
- <p>NORMAL</p>
420
- </div>
421
- </div>
422
- <button class="mt-2 bg-green-900 hover:bg-green-800 p-1 text-xs w-full">OVERRIDE CONTROL</button>
423
  </div>
424
  </div>
425
  </div>
426
-
427
  <script>
428
- // Simulate terminal typing
429
- function typeWriter(element, text, speed) {
430
- let i = 0;
431
- element.innerHTML = '';
432
- function typing() {
433
- if (i < text.length) {
434
- element.innerHTML += text.charAt(i);
435
- i++;
436
- setTimeout(typing, speed);
437
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
438
  }
439
- typing();
440
- }
441
-
442
- // Handle command input
443
- document.getElementById('command-input').addEventListener('keypress', function(e) {
444
- if (e.key === 'Enter') {
445
- const command = this.value;
446
- this.value = '';
447
-
448
- const output = document.getElementById('terminal-output');
449
- output.innerHTML += `<p>> ${command}</p>`;
450
-
451
- // Process command
452
- if (command.toLowerCase() === 'help') {
453
- output.innerHTML += `<p>AVAILABLE COMMANDS: HELP, CHEMSCAN, VIDEO, SCADA, COMMS, LOGS, CLEAR</p>`;
454
- } else if (command.toLowerCase() === 'clear') {
455
- output.innerHTML = '';
456
- } else if (command.toLowerCase() === 'chemscan') {
457
- openTool('chemScan');
458
- } else if (command.toLowerCase() === 'video') {
459
- openTool('videoAnalyzer');
460
- } else if (command.toLowerCase() === 'scada') {
461
- openTool('scada');
462
- } else {
463
- output.innerHTML += `<p>COMMAND NOT RECOGNIZED. TYPE HELP FOR AVAILABLE COMMANDS.</p>`;
464
  }
465
 
466
- output.innerHTML += `<p>> ENTER COMMAND OR SELECT TOOL TO PROCEED<span class="terminal-text">_</span></p>`;
467
- output.scrollTop = output.scrollHeight;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
468
  }
469
- });
470
-
471
- // Tool functions
472
- function openTool(tool) {
473
- document.getElementById(`${tool}-window`).classList.remove('hidden');
474
- }
475
-
476
- function closeTool(tool) {
477
- document.getElementById(`${tool}-window`).classList.add('hidden');
478
- }
479
-
480
- // Evidence viewer
481
- function viewEvidence(type) {
482
- const output = document.getElementById('terminal-output');
483
 
484
- if (type === 'video1') {
485
- output.innerHTML += `<p>> OPENING SURVEILLANCE FOOTAGE: SURV_001.avi</p>`;
486
- output.innerHTML += `<p>> TIMESTAMP: 23:55:12 | LOCATION: NORTH WING 4B</p>`;
487
- output.innerHTML += `<p>> SUBJECT VISIBLE: MALE, 180CM, FDI UNIFORM</p>`;
488
- output.innerHTML += `<p class="text-red-400">> WARNING: UNAUTHORIZED ACCESS DETECTED</p>`;
489
- } else if (type === 'chem1') {
490
- output.innerHTML += `<p>> ANALYZING CHEMICAL SAMPLE: CHEM_004.dat</p>`;
491
- output.innerHTML += `<p>> COMPOSITION: TRICHLOROETHYLENE (45%), MEK (30%), UNKNOWN (15%), WATER (10%)</p>`;
492
- output.innerHTML += `<p class="text-red-400">> WARNING: UNKNOWN COMPOUND MATCHES EXPERIMENTAL FORMULA X-247</p>`;
493
- } else if (type === 'doc1') {
494
- output.innerHTML += `<p>> VIEWING DOCUMENT: MEMO_023.txt</p>`;
495
- output.innerHTML += `<p>> SUBJECT: SECURITY OVERRIDE PROCEDURES</p>`;
496
- output.innerHTML += `<p>> NOTE: "HAL HAS ACCESS TO ALL SYSTEMS. DO NOT QUESTION."</p>`;
 
 
 
 
 
 
 
 
 
497
  }
498
 
499
- output.innerHTML += `<p>> ENTER COMMAND OR SELECT TOOL TO PROCEED<span class="terminal-text">_</span></p>`;
500
- output.scrollTop = output.scrollHeight;
501
- }
502
-
503
- // Simulate real-time updates
504
- setInterval(() => {
505
- const commsLog = document.getElementById('comms-log');
506
- const now = new Date();
507
- const timeStr = `[${now.getHours().toString().padStart(2, '0')}:${now.getMinutes().toString().padStart(2, '0')}]`;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
508
 
509
- // Random chance to add a new comms message
510
- if (Math.random() > 0.9) {
511
- const messages = [
512
- `${timeStr} SYSTEM ALERT: PRESSURE IN NORTH WING INCREASING`,
513
- `${timeStr} WARNING: TEMPERATURE SPIKE IN SERVER ROOM`,
514
- `${timeStr} SECURITY ALERT: MOTION DETECTED IN SECTOR D`,
515
- `${timeStr} AGENT KOWALSKI: *STATIC* ...need backup... *STATIC*`
516
- ];
517
-
518
- commsLog.innerHTML += `<p class="text-red-400">${messages[Math.floor(Math.random() * messages.length)]}</p>`;
519
- commsLog.scrollTop = commsLog.scrollHeight;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
520
  }
521
- }, 10000);
 
 
 
 
522
  </script>
523
  <p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=MGros/operator" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body>
524
  </html>
 
1
  <!DOCTYPE html>
2
+ <html lang="fr">
3
  <head>
4
  <meta charset="UTF-8">
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Simulation Cinématique Inverse - Bras Robotique 2D</title>
7
  <script src="https://cdn.tailwindcss.com"></script>
8
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.7/MathJax.js?config=TeX-MML-AM_CHTML"></script>
9
  <style>
10
+ canvas {
11
+ touch-action: none;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
12
  }
13
+ .formula-container {
14
+ background-color: #f8fafc;
15
+ border-radius: 0.5rem;
16
+ padding: 1rem;
17
+ font-family: 'Courier New', monospace;
18
  }
19
+ .variable {
20
+ color: #3b82f6;
21
+ font-weight: bold;
 
22
  }
23
+ .value {
24
+ color: #10b981;
25
+ font-weight: bold;
26
  }
27
+ .error {
28
+ color: #ef4444;
29
+ font-weight: bold;
 
30
  }
31
+ .workspace {
32
+ fill: rgba(59, 130, 246, 0.1);
33
+ stroke: #3b82f6;
34
+ stroke-width: 1;
 
 
 
 
 
 
 
 
 
 
 
 
35
  }
36
  </style>
37
  </head>
38
+ <body class="bg-gray-50 min-h-screen">
39
+ <div class="container mx-auto px-4 py-8">
40
+ <h1 class="text-3xl font-bold text-center text-blue-600 mb-6">Simulation de Cinématique Inverse</h1>
41
+ <p class="text-center text-gray-600 mb-8">Explorez visuellement et mathématiquement la cinématique inverse d'un bras robotique à 2 articulations</p>
 
 
 
 
 
 
 
 
 
 
 
 
 
42
 
43
+ <div class="grid grid-cols-1 lg:grid-cols-3 gap-8">
44
+ <!-- Canvas et contrôles -->
45
+ <div class="lg:col-span-2 bg-white rounded-xl shadow-md p-6">
46
+ <div class="flex flex-col md:flex-row justify-between items-center mb-4">
47
+ <h2 class="text-xl font-semibold text-gray-800 mb-2 md:mb-0">Représentation du Bras Robotique</h2>
48
+ <div class="flex space-x-4">
49
+ <div>
50
+ <label for="l1" class="block text-sm font-medium text-gray-700">L₁ (cm)</label>
51
+ <input type="range" id="l1" min="50" max="200" value="100" class="w-24 mt-1">
52
+ <span id="l1-value" class="ml-2">100</span>
53
+ </div>
54
+ <div>
55
+ <label for="l2" class="block text-sm font-medium text-gray-700">L₂ (cm)</label>
56
+ <input type="range" id="l2" min="50" max="200" value="100" class="w-24 mt-1">
57
+ <span id="l2-value" class="ml-2">100</span>
58
+ </div>
59
+ <div>
60
+ <label for="l3" class="block text-sm font-medium text-gray-700">L₃ (cm)</label>
61
+ <input type="range" id="l3" min="50" max="200" value="100" class="w-24 mt-1">
62
+ <span id="l3-value" class="ml-2">100</span>
63
+ </div>
64
  </div>
65
  </div>
66
 
67
+ <div class="relative border border-gray-200 rounded-lg overflow-hidden">
68
+ <canvas id="robotCanvas" width="600" height="400" class="w-full bg-gray-50"></canvas>
69
+ <div id="error-message" class="absolute top-4 right-4 bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded hidden"></div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
70
  </div>
71
 
72
+ <div class="mt-4 grid grid-cols-1 md:grid-cols-3 gap-4">
73
+ <div>
74
+ <button id="toggleTrace" class="w-full bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded">
75
+ Activer Trace
76
+ </button>
77
  </div>
78
+ <div>
79
+ <label for="targetX" class="block text-sm font-medium text-gray-700">Position cible X (cm)</label>
80
+ <input type="number" id="targetX" class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500">
81
+ </div>
82
+ <div>
83
+ <label for="targetY" class="block text-sm font-medium text-gray-700">Position cible Y (cm)</label>
84
+ <input type="number" id="targetY" class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500">
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
85
  </div>
86
  </div>
87
  </div>
88
 
89
+ <!-- Formules et résultats -->
90
+ <div class="bg-white rounded-xl shadow-md p-6">
91
+ <h2 class="text-xl font-semibold text-gray-800 mb-4">Calcul des Angles</h2>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
92
 
93
+ <div class="mb-6">
94
+ <h3 class="font-medium text-gray-700 mb-2">Angles calculés :</h3>
95
+ <div class="grid grid-cols-2 gap-4">
96
+ <div class="bg-blue-50 p-3 rounded-lg">
97
+ <p class="text-sm text-gray-600">θ₁ (épaule)</p>
98
+ <p id="theta1-value" class="text-xl font-bold text-blue-600">0°</p>
 
 
 
99
  </div>
100
+ <div class="bg-green-50 p-3 rounded-lg">
101
+ <p class="text-sm text-gray-600">θ₂ (coude)</p>
102
+ <p id="theta2-value" class="text-xl font-bold text-green-600">0°</p>
103
  </div>
104
+ <div class="bg-purple-50 p-3 rounded-lg">
105
+ <p class="text-sm text-gray-600">θ₃ (poignet)</p>
106
+ <p id="theta3-value" class="text-xl font-bold text-purple-600">0°</p>
107
  </div>
108
  </div>
109
  </div>
110
+
111
+ <div class="mb-6">
112
+ <h3 class="font-medium text-gray-700 mb-2">Équations directes :</h3>
113
+ <div class="formula-container">
114
+ <p>\[ x = L_1 \cos(\theta_1) + L_2 \cos(\theta_1 + \theta_2) + L_3 \cos(\theta_1 + \theta_2 + \theta_3) \]</p>
115
+ <p>\[ y = L_1 \sin(\theta_1) + L_2 \sin(\theta_1 + \theta_2) + L_3 \sin(\theta_1 + \theta_2 + \theta_3) \]</p>
116
+ </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
117
  </div>
118
+
119
+ <div class="mb-6">
120
+ <h3 class="font-medium text-gray-700 mb-2">Cinématique inverse :</h3>
121
+ <div class="formula-container">
122
+ <p>\[ \theta_2 = \arccos\left(\frac{x^2 + y^2 - L_1^2 - L_2^2}{2 L_1 L_2}\right) \]</p>
123
+ <p>\[ \theta_1 = \arctan2(y, x) - \arctan2\left(\frac{L_2 \sin(\theta_2)}{L_1 + L_2 \cos(\theta_2)}\right) \]</p>
124
  </div>
125
  </div>
126
+
127
+ <div>
128
+ <h3 class="font-medium text-gray-700 mb-2">Variables :</h3>
129
+ <ul class="space-y-2 text-sm">
130
+ <li><span class="variable">L₁</span> : Longueur du premier segment = <span id="l1-formula" class="value">100</span> cm</li>
131
+ <li><span class="variable">L₂</span> : Longueur du deuxième segment = <span id="l2-formula" class="value">100</span> cm</li>
132
+ <li><span class="variable">L₃</span> : Longueur du troisième segment = <span id="l3-formula" class="value">100</span> cm</li>
133
+ <li><span class="variable">(x, y)</span> : Position cible = (<span id="x-formula" class="value">0</span>, <span id="y-formula" class="value">0</span>) cm</li>
134
+ <li><span class="variable">θ₁</span> : Angle de l'épaule = <span id="theta1-formula" class="value">0</span>°</li>
135
+ <li><span class="variable">θ₂</span> : Angle du coude = <span id="theta2-formula" class="value">0</span>°</li>
136
+ </ul>
 
137
  </div>
138
  </div>
139
  </div>
140
+
141
+ <div class="mt-8 bg-white rounded-xl shadow-md p-6">
142
+ <h2 class="text-xl font-semibold text-gray-800 mb-4">Explications</h2>
143
+ <div class="prose max-w-none">
144
+ <p>Cette simulation illustre le principe de <strong>cinématique inverse</strong> pour un bras robotique à 2 degrés de liberté :</p>
145
+ <ul class="list-disc pl-5 space-y-2">
146
+ <li><strong>Cinématique directe</strong> : Calcul de la position (x,y) de l'effecteur à partir des angles des articulations.</li>
147
+ <li><strong>Cinématique inverse</strong> : Calcul des angles des articulations nécessaires pour atteindre une position (x,y) donnée.</li>
148
+ </ul>
149
+ <p class="mt-4">Pour utiliser la simulation :</p>
150
+ <ol class="list-decimal pl-5 space-y-2">
151
+ <li>Déplacez le point d'extrémité (en rouge) avec la souris</li>
152
+ <li>Ou entrez des coordonnées cible dans les champs X et Y</li>
153
+ <li>Modifiez les longueurs L₁ et L₂ pour explorer différents cas</li>
154
+ <li>Observez en temps réel les angles calculés et les formules</li>
155
+ </ol>
156
+ <p class="mt-4">Si la position cible est inaccessible (hors de la zone atteignable), un message d'erreur s'affichera.</p>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
157
  </div>
158
  </div>
159
  </div>
160
+
161
  <script>
162
+ document.addEventListener('DOMContentLoaded', function() {
163
+ // Configuration de MathJax
164
+ MathJax.Hub.Config({
165
+ tex2jax: {
166
+ inlineMath: [['$','$'], ['\\(','\\)']],
167
+ processEscapes: true
 
 
 
168
  }
169
+ });
170
+
171
+ // Éléments du DOM
172
+ const canvas = document.getElementById('robotCanvas');
173
+ const ctx = canvas.getContext('2d');
174
+ const l1Slider = document.getElementById('l1');
175
+ const l2Slider = document.getElementById('l2');
176
+ const l1Value = document.getElementById('l1-value');
177
+ const l2Value = document.getElementById('l2-value');
178
+ const targetXInput = document.getElementById('targetX');
179
+ const targetYInput = document.getElementById('targetY');
180
+ const theta1Display = document.getElementById('theta1-value');
181
+ const theta2Display = document.getElementById('theta2-value');
182
+ const l1Formula = document.getElementById('l1-formula');
183
+ const l2Formula = document.getElementById('l2-formula');
184
+ const xFormula = document.getElementById('x-formula');
185
+ const yFormula = document.getElementById('y-formula');
186
+ const theta1Formula = document.getElementById('theta1-formula');
187
+ const theta2Formula = document.getElementById('theta2-formula');
188
+ const errorMessage = document.getElementById('error-message');
189
+
190
+ // Paramètres initiaux
191
+ let L1 = parseInt(l1Slider.value);
192
+ let L2 = parseInt(l2Slider.value);
193
+ let L3 = parseInt(l3Slider.value);
194
+ let targetX = 150;
195
+ let targetY = 50;
196
+ let theta1 = 0;
197
+ let theta2 = 0;
198
+ let theta3 = 0;
199
+ let isDragging = false;
200
+ let showWorkspace = true;
201
+ let traceEnabled = false;
202
+ let tracePoints = [];
203
+ let drawingHistory = [];
204
+ let isDrawing = false;
205
+
206
+ // Mise à l'échelle pour le canvas
207
+ const scale = 1.5;
208
+ const originX = canvas.width / 2;
209
+ const originY = canvas.height / 2;
210
+
211
+ // Fonction pour calculer la cinématique inverse
212
+ function inverseKinematics(x, y) {
213
+ const xScaled = x / scale;
214
+ const yScaled = y / scale;
215
+
216
+ // Vérifier si la position est atteignable
217
+ const distance = Math.sqrt(xScaled*xScaled + yScaled*yScaled);
218
+ const sumL = L1 + L2 + L3;
219
+ const diffL = Math.max(0, L1 - L2 - L3, L2 - L1 - L3, L3 - L1 - L2);
220
+
221
+ if (distance > sumL || distance < diffL) {
222
+ return null; // Position inaccessible
223
+ }
224
+
225
+ // Pour 3 articulations, nous utiliserons une approche géométrique
226
+ // Nous fixons θ3 à 0 pour simplifier (orientation finale fixe)
227
+ const theta3 = 0;
228
+
229
+ // Calcul des coordonnées du "coude virtuel"
230
+ const targetDistance = Math.sqrt(xScaled*xScaled + yScaled*yScaled);
231
+ const effectiveDistance = targetDistance - L3; // On retire L3 car θ3=0
232
+
233
+ // Vérification à nouveau pour L1 et L2
234
+ if (effectiveDistance > L1 + L2 || effectiveDistance < Math.abs(L1 - L2)) {
235
+ return null;
236
+ }
237
+
238
+ // Calcul de θ2 pour le bras à 2 segments (L1 et L2)
239
+ const cosTheta2 = (effectiveDistance*effectiveDistance - L1*L1 - L2*L2) / (2 * L1 * L2);
240
+ const clampedCosTheta2 = Math.max(-1, Math.min(1, cosTheta2));
241
+ const theta2 = Math.acos(clampedCosTheta2);
242
+
243
+ // Calcul de θ1
244
+ const k1 = L1 + L2 * Math.cos(theta2);
245
+ const k2 = L2 * Math.sin(theta2);
246
+ const theta1 = Math.atan2(yScaled, xScaled) - Math.atan2(k2, k1);
247
+
248
+ return { theta1, theta2, theta3 };
249
  }
250
+
251
+ // Fonction pour dessiner le bras robotique
252
+ function drawRobot() {
253
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
254
+
255
+ // Dessiner la zone atteignable (workspace)
256
+ if (showWorkspace) {
257
+ ctx.beginPath();
258
+ ctx.arc(originX, originY, (L1 + L2) * scale, 0, 2 * Math.PI);
259
+ ctx.fillStyle = 'rgba(59, 130, 246, 0.1)';
260
+ ctx.fill();
261
+ ctx.strokeStyle = '#3b82f6';
262
+ ctx.lineWidth = 1;
263
+ ctx.stroke();
264
+
265
+ if (L1 !== L2) {
266
+ ctx.beginPath();
267
+ ctx.arc(originX, originY, Math.abs(L1 - L2) * scale, 0, 2 * Math.PI);
268
+ ctx.fillStyle = 'rgba(255, 255, 255, 0.7)';
269
+ ctx.fill();
270
+ ctx.strokeStyle = '#3b82f6';
271
+ ctx.lineWidth = 1;
272
+ ctx.stroke();
273
+ }
 
274
  }
275
 
276
+ // Calculer les positions des articulations
277
+ const shoulderX = originX;
278
+ const shoulderY = originY;
279
+ const elbowX = shoulderX + L1 * Math.cos(theta1) * scale;
280
+ const elbowY = shoulderY + L1 * Math.sin(theta1) * scale;
281
+ const wristX = elbowX + L2 * Math.cos(theta1 + theta2) * scale;
282
+ const wristY = elbowY + L2 * Math.sin(theta1 + theta2) * scale;
283
+ const handX = wristX + L3 * Math.cos(theta1 + theta2 + theta3) * scale;
284
+ const handY = wristY + L3 * Math.sin(theta1 + theta2 + theta3) * scale;
285
+
286
+ // Dessiner les segments du bras
287
+ ctx.beginPath();
288
+ ctx.moveTo(shoulderX, shoulderY);
289
+ ctx.lineTo(elbowX, elbowY);
290
+ ctx.lineWidth = 8;
291
+ ctx.strokeStyle = '#4b5563';
292
+ ctx.stroke();
293
+
294
+ ctx.beginPath();
295
+ ctx.moveTo(elbowX, elbowY);
296
+ ctx.lineTo(wristX, wristY);
297
+ ctx.lineWidth = 8;
298
+ ctx.strokeStyle = '#4b5563';
299
+ ctx.stroke();
300
+
301
+ ctx.beginPath();
302
+ ctx.moveTo(wristX, wristY);
303
+ ctx.lineTo(handX, handY);
304
+ ctx.lineWidth = 8;
305
+ ctx.strokeStyle = '#4b5563';
306
+ ctx.stroke();
307
+
308
+ // Dessiner les articulations
309
+ ctx.beginPath();
310
+ ctx.arc(shoulderX, shoulderY, 10, 0, 2 * Math.PI);
311
+ ctx.fillStyle = '#3b82f6';
312
+ ctx.fill();
313
+
314
+ ctx.beginPath();
315
+ ctx.arc(elbowX, elbowY, 10, 0, 2 * Math.PI);
316
+ ctx.fillStyle = '#10b981';
317
+ ctx.fill();
318
+
319
+ ctx.beginPath();
320
+ ctx.arc(wristX, wristY, 10, 0, 2 * Math.PI);
321
+ ctx.fillStyle = '#8b5cf6';
322
+ ctx.fill();
323
+
324
+ // Dessiner l'effecteur (main)
325
+ ctx.beginPath();
326
+ ctx.arc(handX, handY, 12, 0, 2 * Math.PI);
327
+ ctx.fillStyle = '#ef4444';
328
+ ctx.fill();
329
+
330
+ // Dessiner la trace si activée
331
+ if (traceEnabled && tracePoints.length > 1) {
332
+ ctx.beginPath();
333
+ ctx.moveTo(tracePoints[0].x, tracePoints[0].y);
334
+ for (let i = 1; i < tracePoints.length; i++) {
335
+ ctx.lineTo(tracePoints[i].x, tracePoints[i].y);
336
+ }
337
+ ctx.strokeStyle = 'rgba(239, 68, 68, 0.5)';
338
+ ctx.lineWidth = 2;
339
+ ctx.stroke();
340
+ }
341
+
342
+ // Dessiner l'historique de dessin
343
+ if (drawingHistory.length > 1) {
344
+ ctx.beginPath();
345
+ ctx.moveTo(drawingHistory[0].x, drawingHistory[0].y);
346
+ for (let i = 1; i < drawingHistory.length; i++) {
347
+ ctx.lineTo(drawingHistory[i].x, drawingHistory[i].y);
348
+ }
349
+ ctx.strokeStyle = 'rgba(59, 130, 246, 0.7)';
350
+ ctx.lineWidth = 3;
351
+ ctx.stroke();
352
+ }
353
+
354
+ // Dessiner la cible si elle est différente de la position actuelle
355
+ const tolerance = 5;
356
+ if (Math.abs(handX - targetX) > tolerance || Math.abs(handY - targetY) > tolerance) {
357
+ ctx.beginPath();
358
+ ctx.arc(targetX, targetY, 8, 0, 2 * Math.PI);
359
+ ctx.fillStyle = 'rgba(239, 68, 68, 0.5)';
360
+ ctx.fill();
361
+ ctx.strokeStyle = '#ef4444';
362
+ ctx.lineWidth = 2;
363
+ ctx.stroke();
364
+ }
365
+
366
+ // Mettre à jour les valeurs affichées
367
+ updateDisplayValues(handX, handY);
368
+ }
369
+
370
+ // Mettre à jour les valeurs affichées
371
+ function updateDisplayValues(handX, handY) {
372
+ const x = (handX - originX) / scale;
373
+ const y = (handY - originY) / scale;
374
+
375
+ theta1Display.textContent = `${(theta1 * 180 / Math.PI).toFixed(1)}°`;
376
+ theta2Display.textContent = `${(theta2 * 180 / Math.PI).toFixed(1)}°`;
377
+
378
+ l1Formula.textContent = L1;
379
+ l2Formula.textContent = L2;
380
+ xFormula.textContent = x.toFixed(1);
381
+ yFormula.textContent = y.toFixed(1);
382
+ theta1Formula.textContent = (theta1 * 180 / Math.PI).toFixed(1);
383
+ theta2Formula.textContent = (theta2 * 180 / Math.PI).toFixed(1);
384
+ theta3Formula.textContent = (theta3 * 180 / Math.PI).toFixed(1);
385
+
386
+ // Mettre à jour MathJax
387
+ MathJax.Hub.Queue(["Typeset", MathJax.Hub]);
388
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
389
 
390
+ // Mettre à jour la position cible
391
+ function updateTargetPosition(x, y) {
392
+ targetX = x;
393
+ targetY = y;
394
+
395
+ // Mettre à jour les champs de saisie
396
+ targetXInput.value = ((x - originX) / scale).toFixed(1);
397
+ targetYInput.value = ((y - originY) / scale).toFixed(1);
398
+
399
+ // Calculer la cinématique inverse
400
+ const result = inverseKinematics(x - originX, y - originY);
401
+
402
+ if (result) {
403
+ theta1 = result.theta1;
404
+ theta2 = result.theta2;
405
+ errorMessage.classList.add('hidden');
406
+ } else {
407
+ errorMessage.textContent = "Position inaccessible !";
408
+ errorMessage.classList.remove('hidden');
409
+ }
410
+
411
+ drawRobot();
412
  }
413
 
414
+ // Événements pour les sliders
415
+ l1Slider.addEventListener('input', function() {
416
+ L1 = parseInt(this.value);
417
+ l1Value.textContent = L1;
418
+ l1Formula.textContent = L1;
419
+ updateTargetPosition(targetX, targetY);
420
+ });
421
+
422
+ l2Slider.addEventListener('input', function() {
423
+ L2 = parseInt(this.value);
424
+ l2Value.textContent = L2;
425
+ l2Formula.textContent = L2;
426
+ updateTargetPosition(targetX, targetY);
427
+ });
428
+
429
+ l3Slider.addEventListener('input', function() {
430
+ L3 = parseInt(this.value);
431
+ l3Value.textContent = L3;
432
+ l3Formula.textContent = L3;
433
+ updateTargetPosition(targetX, targetY);
434
+ });
435
+
436
+ // Événements pour les champs de saisie
437
+ targetXInput.addEventListener('change', function() {
438
+ const x = parseFloat(this.value) * scale + originX;
439
+ updateTargetPosition(x, targetY);
440
+ });
441
+
442
+ targetYInput.addEventListener('change', function() {
443
+ const y = parseFloat(this.value) * scale + originY;
444
+ updateTargetPosition(targetX, y);
445
+ });
446
+
447
+ // Événements pour le canvas (drag and drop)
448
+ canvas.addEventListener('mousedown', function(e) {
449
+ const rect = canvas.getBoundingClientRect();
450
+ const x = e.clientX - rect.left;
451
+ const y = e.clientY - rect.top;
452
+
453
+ // Vérifier si on clique près de l'effecteur ou de la cible
454
+ const handX = originX + (L1 * Math.cos(theta1) + L2 * Math.cos(theta1 + theta2)) * scale;
455
+ const handY = originY + (L1 * Math.sin(theta1) + L2 * Math.sin(theta1 + theta2)) * scale;
456
+
457
+ const distToHand = Math.sqrt((x - handX)**2 + (y - handY)**2);
458
+ const distToTarget = Math.sqrt((x - targetX)**2 + (y - targetY)**2);
459
+
460
+ if (distToHand < 20 || distToTarget < 20) {
461
+ isDragging = true;
462
+ updateTargetPosition(x, y);
463
+ }
464
+ });
465
+
466
+ canvas.addEventListener('mousemove', function(e) {
467
+ if (isDragging) {
468
+ const rect = canvas.getBoundingClientRect();
469
+ const x = e.clientX - rect.left;
470
+ const y = e.clientY - rect.top;
471
+ updateTargetPosition(x, y);
472
+ }
473
+ });
474
+
475
+ canvas.addEventListener('mouseup', function() {
476
+ isDragging = false;
477
+ });
478
 
479
+ canvas.addEventListener('mouseleave', function() {
480
+ isDragging = false;
481
+ });
482
+
483
+ // Support tactile
484
+ canvas.addEventListener('touchstart', function(e) {
485
+ e.preventDefault();
486
+ const rect = canvas.getBoundingClientRect();
487
+ const touch = e.touches[0];
488
+ const x = touch.clientX - rect.left;
489
+ const y = touch.clientY - rect.top;
490
+
491
+ const handX = originX + (L1 * Math.cos(theta1) + L2 * Math.cos(theta1 + theta2)) * scale;
492
+ const handY = originY + (L1 * Math.sin(theta1) + L2 * Math.sin(theta1 + theta2)) * scale;
493
+
494
+ const distToHand = Math.sqrt((x - handX)**2 + (y - handY)**2);
495
+ const distToTarget = Math.sqrt((x - targetX)**2 + (y - targetY)**2);
496
+
497
+ if (distToHand < 40 || distToTarget < 40) {
498
+ isDragging = true;
499
+ updateTargetPosition(x, y);
500
+ }
501
+ });
502
+
503
+ canvas.addEventListener('touchmove', function(e) {
504
+ e.preventDefault();
505
+ if (isDragging && e.touches.length === 1) {
506
+ const rect = canvas.getBoundingClientRect();
507
+ const touch = e.touches[0];
508
+ const x = touch.clientX - rect.left;
509
+ const y = touch.clientY - rect.top;
510
+ updateTargetPosition(x, y);
511
+ }
512
+ });
513
+
514
+ canvas.addEventListener('touchend', function() {
515
+ isDragging = false;
516
+ });
517
+
518
+ // Gestion du bouton de trace
519
+ const toggleTraceBtn = document.getElementById('toggleTrace');
520
+ toggleTraceBtn.addEventListener('click', function() {
521
+ traceEnabled = !traceEnabled;
522
+ if (traceEnabled) {
523
+ this.textContent = 'Désactiver Trace';
524
+ tracePoints = []; // Réinitialiser la trace
525
+ drawingHistory = []; // Réinitialiser l'historique
526
+ } else {
527
+ this.textContent = 'Activer Trace';
528
+ }
529
+ drawRobot();
530
+ });
531
+
532
+ // Activation du dessin au clic sur le canvas
533
+ canvas.addEventListener('mousedown', function(e) {
534
+ const rect = canvas.getBoundingClientRect();
535
+ const x = e.clientX - rect.left;
536
+ const y = e.clientY - rect.top;
537
+
538
+ // Commencer un nouveau dessin
539
+ isDrawing = true;
540
+ drawingHistory = [{x, y}];
541
+ });
542
+
543
+ canvas.addEventListener('mousemove', function(e) {
544
+ if (isDrawing) {
545
+ const rect = canvas.getBoundingClientRect();
546
+ const x = e.clientX - rect.left;
547
+ const y = e.clientY - rect.top;
548
+
549
+ // Ajouter le point à l'historique
550
+ drawingHistory.push({x, y});
551
+
552
+ // Limiter la taille de l'historique pour la performance
553
+ if (drawingHistory.length > 1000) {
554
+ drawingHistory.shift();
555
+ }
556
+
557
+ // Mettre à jour la position cible
558
+ updateTargetPosition(x, y);
559
+ } else if (isDragging) {
560
+ const rect = canvas.getBoundingClientRect();
561
+ const x = e.clientX - rect.left;
562
+ const y = e.clientY - rect.top;
563
+ updateTargetPosition(x, y);
564
+ }
565
+ });
566
+
567
+ canvas.addEventListener('mouseup', function() {
568
+ isDragging = false;
569
+ isDrawing = false;
570
+ });
571
+
572
+ canvas.addEventListener('mouseleave', function() {
573
+ isDragging = false;
574
+ isDrawing = false;
575
+ });
576
+
577
+ // Mettre à jour la position cible avec gestion de la trace
578
+ function updateTargetPosition(x, y) {
579
+ targetX = x;
580
+ targetY = y;
581
+
582
+ if (traceEnabled) {
583
+ tracePoints.push({x, y});
584
+ // Limiter le nombre de points pour performance
585
+ if (tracePoints.length > 500) {
586
+ tracePoints.shift();
587
+ }
588
+ }
589
+
590
+ // Mettre à jour les champs de saisie
591
+ targetXInput.value = ((x - originX) / scale).toFixed(1);
592
+ targetYInput.value = ((y - originY) / scale).toFixed(1);
593
+
594
+ // Calculer la cinématique inverse
595
+ const result = inverseKinematics(x - originX, y - originY);
596
+
597
+ if (result) {
598
+ theta1 = result.theta1;
599
+ theta2 = result.theta2;
600
+ errorMessage.classList.add('hidden');
601
+ } else {
602
+ errorMessage.textContent = "Position inaccessible !";
603
+ errorMessage.classList.remove('hidden');
604
+ }
605
+
606
+ drawRobot();
607
  }
608
+
609
+ // Initialisation
610
+ updateTargetPosition(originX + 100 * scale, originY);
611
+ drawRobot();
612
+ });
613
  </script>
614
  <p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=MGros/operator" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body>
615
  </html>