-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathindex.html
More file actions
747 lines (680 loc) · 63.5 KB
/
Copy pathindex.html
File metadata and controls
747 lines (680 loc) · 63.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="description" content="Astralis — Simulateur gravitationnel N-corps 3D haute précision. Intégrateur Yoshida, PN1 optionnel, tableau de bord PyQt5, export Parquet. Gratuit pour Windows." />
<title>Astralis — Simulateur Gravitationnel 3D</title>
<link rel="icon" href="astralis_logo.ico" type="image/x-icon" />
<link rel="apple-touch-icon" href="astralis_logo.png" />
<meta property="og:type" content="website" />
<meta property="og:title" content="Astralis — Simulateur Gravitationnel 3D" />
<meta property="og:description" content="Simulateur N-corps 3D avec intégrateur Yoshida, dashboard PyQt5 et export Parquet. Gratuit pour Windows." />
<meta property="og:image" content="https://l-capdevila.github.io/Astralis/astralis_logo.png" />
<meta property="og:url" content="https://l-capdevila.github.io/Astralis/" />
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:title" content="Astralis — Simulateur Gravitationnel 3D" />
<meta name="twitter:description" content="Simulateur N-corps 3D haute précision — gratuit pour Windows." />
<meta name="twitter:image" content="https://l-capdevila.github.io/Astralis/astralis_logo.png" />
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.0/css/all.min.css" />
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link href="https://fonts.googleapis.com/css2?family=Orbitron:wght@400;600;700;900&family=Exo+2:wght@300;400;500;600&family=Space+Mono:wght@400;700&display=swap" rel="stylesheet" />
<style>
:root {
--bg-body: #03030a;
--bg-panel: #08071a;
--bg-card: #0e0c24;
--accent: #FFD166;
--accent2: #9B5DE5;
--accent-hover: #F8C94A;
--border: #2a1f4a;
--border-glow: rgba(155,93,229,.45);
--text: #EEE8FF;
--muted: #8a7aaa;
--glow: rgba(255,209,102,.45);
--nebula1: #4a1d8a;
--nebula2: #8B1a6B;
--nebula3: #1a0a3a;
--star-color: #FFD166;
}
* { box-sizing: border-box; margin: 0; padding: 0; }
html { scroll-behavior: smooth; }
body { background: var(--bg-body); color: var(--text); font-family: 'Exo 2', sans-serif; overflow-x: hidden; cursor: none; }
/* ── CUSTOM CURSOR ── */
#cursor { position: fixed; width: 12px; height: 12px; border: 2px solid var(--accent); border-radius: 50%; pointer-events: none; z-index: 9999; transform: translate(-50%,-50%); transition: width .2s, height .2s, background .2s; mix-blend-mode: screen; }
#cursor-aura { position: fixed; width: 36px; height: 36px; border: 1px solid rgba(155,93,229,.45); border-radius: 50%; pointer-events: none; z-index: 9998; transform: translate(-50%,-50%); transition: transform .22s ease; }
.cursor-particle { position: fixed; width: 4px; height: 4px; border-radius: 50%; background: var(--accent); pointer-events: none; z-index: 9997; animation: particle-fade .6s ease forwards; }
@keyframes particle-fade { 0%{opacity:.8;transform:translate(-50%,-50%) scale(1)} 100%{opacity:0;transform:translate(-50%,-50%) scale(0)} }
/* ── BACKGROUND CANVAS ── */
#canvas-bg { position: fixed; inset: 0; z-index: 0; pointer-events: none; }
/* ── STARFIELD ── */
#starfield { position: fixed; inset: 0; z-index: 0; pointer-events: none; overflow: hidden; }
.star { position: absolute; border-radius: 50%; background: #fff; opacity: 0; animation: twinkle var(--d,4s) ease-in-out infinite var(--delay,0s); }
@keyframes twinkle { 0%,100%{opacity:0} 50%{opacity:var(--op,.7)} }
/* ── NEBULAE ── */
.nebula { position: fixed; border-radius: 50%; filter: blur(120px); pointer-events: none; z-index: 0; }
.nebula-1 { width:900px;height:900px;background:radial-gradient(circle,#3d1070 0%,#1a0540 40%,transparent 70%);top:-300px;right:-300px;opacity:.55;animation:nebula-drift 22s ease-in-out infinite alternate; }
.nebula-2 { width:700px;height:700px;background:radial-gradient(circle,#6b0f50 0%,#2a0530 40%,transparent 70%);bottom:0;left:-250px;opacity:.45;animation:nebula-drift 30s ease-in-out infinite alternate-reverse; }
.nebula-3 { width:500px;height:500px;background:radial-gradient(circle,#1a0855 0%,transparent 70%);top:35%;left:25%;opacity:.4;animation:nebula-drift 38s ease-in-out infinite alternate; }
.nebula-4 { width:350px;height:350px;background:radial-gradient(circle,rgba(255,180,50,.1) 0%,transparent 70%);top:15%;right:15%;opacity:.35;animation:nebula-drift 25s ease-in-out infinite alternate-reverse; }
.nebula-5 { width:600px;height:600px;background:radial-gradient(circle,#8B1a6B 0%,transparent 70%);top:55%;right:5%;opacity:.25;animation:nebula-drift 34s ease-in-out infinite alternate; }
@keyframes nebula-drift { 0%{transform:translate(0,0) scale(1)} 100%{transform:translate(50px,35px) scale(1.18)} }
/* ── OVERLAYS ── */
#grid-overlay { display:none; }
#scanlines { position: fixed; inset: 0; z-index: 2; pointer-events: none; background: repeating-linear-gradient(0deg, transparent, transparent 3px, rgba(0,0,0,.03) 3px, rgba(0,0,0,.03) 6px); }
#laser-scan { display:none; }
#scroll-progress { position: fixed; top: 0; left: 0; height: 2px; z-index: 200; background: linear-gradient(90deg, var(--accent2), var(--accent)); box-shadow: 0 0 10px var(--accent); transition: width .1s linear; width: 0; }
/* ── LAYOUT ── */
section { position: relative; z-index: 3; }
.container { max-width: 1200px; margin: 0 auto; padding: 0 1.5rem; }
.section-label { font-family:'Space Mono',monospace;font-size:.72rem;color:var(--accent);letter-spacing:.18em;text-transform:uppercase;margin-bottom:.75rem; }
h2.section-title { font-family:'Orbitron',sans-serif;font-size:clamp(1.6rem,4vw,2.4rem);font-weight:700;letter-spacing:.05em;margin-bottom:1rem;text-shadow:0 0 30px rgba(155,93,229,.4); }
.section-desc { color:var(--muted);font-size:1.05rem;line-height:1.75;max-width:680px; }
.divider { width:60px;height:3px;background:linear-gradient(90deg,var(--accent),var(--accent2),transparent);margin-bottom:3rem;border-radius:2px;box-shadow:0 0 10px var(--accent); }
/* ── SCROLL REVEAL ── */
.reveal { opacity:0;transform:translateY(28px);transition:opacity .7s ease,transform .7s ease; }
.reveal.visible { opacity:1;transform:translateY(0); }
.reveal-left { opacity:0;transform:translateX(-40px);transition:opacity .7s ease,transform .7s ease; }
.reveal-left.visible { opacity:1;transform:translateX(0); }
.reveal-right { opacity:0;transform:translateX(40px);transition:opacity .7s ease,transform .7s ease; }
.reveal-right.visible { opacity:1;transform:translateX(0); }
/* ── NAV ── */
nav { position:fixed;top:0;width:100%;z-index:100;background:rgba(3,3,10,.82);backdrop-filter:blur(24px) saturate(1.4);border-bottom:1px solid var(--border);transition:background .3s,box-shadow .3s; }
nav.scrolled { background:rgba(3,3,10,.96);box-shadow:0 4px 30px rgba(0,0,0,.6); }
.nav-inner { max-width:1200px;margin:0 auto;display:flex;align-items:center;justify-content:space-between;padding:.85rem 1.5rem; }
.logo { display:flex;align-items:center;gap:.65rem;font-family:'Orbitron',sans-serif;font-weight:700;font-size:1.15rem;letter-spacing:.18em;color:var(--text);text-decoration:none;transition:text-shadow .3s; }
.logo:hover { text-shadow:0 0 20px var(--accent); }
.logo-icon { width:34px;height:34px;border:2px solid var(--accent);border-radius:8px;display:flex;align-items:center;justify-content:center;box-shadow:0 0 12px rgba(255,209,102,.5),inset 0 0 8px rgba(255,209,102,.12);overflow:hidden;position:relative; }
.logo-icon img.logo-img { width:100%;height:100%;object-fit:cover;border-radius:6px;display:block; }
.logo-icon svg { width:20px;height:20px; }
.logo-icon svg.logo-fallback { display:none; }
.logo-icon.is-fallback svg.logo-fallback { display:block; }
.logo-icon.is-fallback img { display:none !important; }
.nav-links { display:flex;align-items:center;gap:1.25rem;flex-wrap:wrap; }
.nav-links a { color:var(--muted);text-decoration:none;font-size:.85rem;font-weight:500;transition:color .2s,text-shadow .2s;position:relative; }
.nav-links a::after { content:'';position:absolute;bottom:-4px;left:0;right:0;height:1px;background:var(--accent);transform:scaleX(0);transition:transform .3s;box-shadow:0 0 6px var(--accent); }
.nav-links a:hover { color:var(--accent);text-shadow:0 0 10px rgba(0,229,255,.5); }
.nav-links a:hover::after { transform:scaleX(1); }
.btn-lang { background:transparent;border:1px solid var(--border);color:var(--muted);padding:.3rem .75rem;border-radius:6px;font-size:.78rem;cursor:pointer;font-family:'Space Mono',monospace;transition:all .2s; }
.btn-lang:hover { border-color:var(--accent);color:var(--accent);box-shadow:0 0 10px rgba(0,229,255,.2); }
/* ── HERO ── */
#hero { min-height:100vh;display:flex;align-items:center;justify-content:center;text-align:center;padding:7rem 1.5rem 4rem; }
.hero-badge { display:inline-flex;align-items:center;gap:.5rem;border:1px solid rgba(155,93,229,.45);border-radius:999px;padding:.35rem 1rem;font-size:.78rem;color:var(--muted);font-family:'Space Mono',monospace;margin-bottom:2rem;background:rgba(155,93,229,.06);backdrop-filter:blur(8px);box-shadow:0 0 20px rgba(155,93,229,.12),inset 0 1px 0 rgba(255,209,102,.12);animation:badge-appear .8s ease .2s both; }
@keyframes badge-appear { from{opacity:0;transform:translateY(-12px)} to{opacity:1;transform:translateY(0)} }
.hero-badge .dot { width:7px;height:7px;border-radius:50%;background:var(--accent);box-shadow:0 0 8px var(--accent);animation:pulse-dot 2s ease-in-out infinite; }
@keyframes pulse-dot { 0%,100%{opacity:1;box-shadow:0 0 8px var(--accent)} 50%{opacity:.3;box-shadow:0 0 3px var(--accent)} }
/* GLITCH TITLE */
h1.title { font-family:'Orbitron',sans-serif;font-size:clamp(3rem,10vw,7rem);font-weight:900;letter-spacing:.25em;padding-left:.25em;text-align:center;width:100%;line-height:1;color:var(--text);text-shadow:0 0 60px rgba(255,209,102,.4),0 0 120px rgba(155,93,229,.3);margin:0 auto 1.5rem;position:relative;animation:title-appear 1s ease .4s both; }
@keyframes title-appear { from{opacity:0;transform:scale(.95);letter-spacing:.5em} to{opacity:1;transform:scale(1);letter-spacing:.25em} }
h1.title span { color:var(--accent);text-shadow:0 0 30px var(--accent),0 0 60px rgba(255,209,102,.5); }
h1.title::before,h1.title::after { content:attr(data-text);position:absolute;top:0;left:0;right:0;font-family:'Orbitron',sans-serif;font-size:inherit;font-weight:900;letter-spacing:.25em;padding-left:.25em;pointer-events:none; }
h1.title::before { color:#d4a8ff;text-shadow:none;clip-path:polygon(0 30%,100% 30%,100% 50%,0 50%);animation:glitch-1 5s infinite steps(1);opacity:0; }
h1.title::after { color:#ff9055;text-shadow:none;clip-path:polygon(0 62%,100% 62%,100% 76%,0 76%);animation:glitch-2 5s infinite steps(1);opacity:0; }
@keyframes glitch-1 { 0%,93%{opacity:0;transform:none}94%{opacity:.8;transform:translateX(-3px)}95%{opacity:0}96%{opacity:.5;transform:translateX(3px)}97%,100%{opacity:0;transform:none} }
@keyframes glitch-2 { 0%,93%{opacity:0;transform:none}94%{opacity:.6;transform:translateX(3px)}95%{opacity:0}96%{opacity:.4;transform:translateX(-3px)}97%,100%{opacity:0;transform:none} }
.hero-slogan { font-size:clamp(1rem,2.5vw,1.2rem);color:var(--muted);line-height:1.7;max-width:620px;margin:0 auto 2rem;font-weight:300;animation:fade-up .8s ease .7s both; }
.hero-slogan em { color:var(--accent-hover);font-style:normal;font-weight:500; }
@keyframes fade-up { from{opacity:0;transform:translateY(20px)} to{opacity:1;transform:translateY(0)} }
.hero-actions { display:flex;flex-wrap:wrap;gap:1rem;justify-content:center;align-items:center;animation:fade-up .8s ease .9s both; }
.btn-download { display:inline-flex;align-items:center;gap:.75rem;background:linear-gradient(135deg,var(--accent),#e8a500,var(--accent2));color:#0a0818;font-family:'Orbitron',sans-serif;font-weight:700;font-size:.9rem;letter-spacing:.08em;padding:.95rem 2.2rem;border-radius:10px;text-decoration:none;box-shadow:0 0 30px rgba(255,209,102,.45),0 4px 20px rgba(0,0,0,.5);transition:all .3s;position:relative;overflow:hidden; }
.btn-download::before { content:'';position:absolute;top:0;left:-100%;width:100%;height:100%;background:linear-gradient(90deg,transparent,rgba(255,255,255,.3),transparent);transition:left .4s; }
.btn-download:hover::before { left:100%; }
.btn-download:hover { transform:translateY(-3px) scale(1.02);box-shadow:0 0 50px rgba(255,209,102,.65),0 8px 30px rgba(0,0,0,.5); }
.btn-secondary { display:inline-flex;align-items:center;gap:.5rem;border:1px solid var(--border);color:var(--text);padding:.95rem 1.8rem;border-radius:10px;text-decoration:none;font-size:.9rem;transition:all .25s;backdrop-filter:blur(8px);background:rgba(255,255,255,.03); }
.btn-secondary:hover { border-color:var(--accent2);color:var(--accent);box-shadow:0 0 20px rgba(155,93,229,.25);background:rgba(155,93,229,.07); }
.hero-meta { margin-top:1.25rem;font-size:.8rem;color:var(--muted);display:flex;flex-wrap:wrap;align-items:center;justify-content:center;gap:.5rem 1rem;animation:fade-up .8s ease 1.1s both; }
.hero-meta i { color:var(--accent); }
/* STATS */
.stats-row { display:grid;grid-template-columns:repeat(auto-fit,minmax(140px,1fr));gap:1rem;margin-top:3rem;animation:fade-up .8s ease 1.3s both; }
.stat-card { background:var(--bg-card);border:1px solid var(--border);border-radius:12px;padding:1.25rem;text-align:center;position:relative;overflow:hidden;transition:all .3s; }
.stat-card::before { content:'';position:absolute;inset:0;background:radial-gradient(circle at 50% 0%,rgba(155,93,229,.1),transparent 70%);opacity:0;transition:opacity .3s; }
.stat-card:hover { border-color:var(--border-glow);transform:translateY(-4px);box-shadow:0 0 20px rgba(155,93,229,.18); }
.stat-card:hover::before { opacity:1; }
.stat-card strong { display:block;font-family:'Orbitron',sans-serif;font-size:1.8rem;color:var(--accent);margin-bottom:.25rem;text-shadow:0 0 20px rgba(255,209,102,.6); }
.stat-card span { font-size:.8rem;color:var(--muted); }
/* CORNER DECORATIONS */
.corner-deco { position:absolute;width:12px;height:12px;pointer-events:none;opacity:.6; }
.corner-deco.tl { top:8px;left:8px;border-top:2px solid var(--accent);border-left:2px solid var(--accent); }
.corner-deco.tr { top:8px;right:8px;border-top:2px solid var(--accent);border-right:2px solid var(--accent); }
.corner-deco.bl { bottom:8px;left:8px;border-bottom:2px solid var(--accent);border-left:2px solid var(--accent); }
.corner-deco.br { bottom:8px;right:8px;border-bottom:2px solid var(--accent);border-right:2px solid var(--accent); }
/* ABOUT */
#about { padding:5rem 0; }
.about-grid { display:grid;grid-template-columns:1fr 1fr;gap:2.5rem;margin-top:2.5rem;align-items:start; }
.about-card { background:var(--bg-card);border:1px solid var(--border);border-radius:14px;padding:1.75rem;position:relative;overflow:hidden;transition:all .35s; }
.about-card::before { content:'';position:absolute;top:0;left:0;right:0;height:2px;background:linear-gradient(90deg,transparent,var(--accent2),var(--accent),transparent);transform:scaleX(0);transition:transform .4s; }
.about-card:hover { border-color:var(--border-glow);transform:translateY(-4px);box-shadow:0 8px 40px rgba(0,0,0,.4),0 0 25px rgba(155,93,229,.12); }
.about-card:hover::before { transform:scaleX(1); }
.about-card h3 { font-family:'Orbitron',sans-serif;font-size:.95rem;margin-bottom:.75rem;color:var(--accent); }
.about-card p,.about-card li { color:var(--muted);font-size:.92rem;line-height:1.7; }
.about-card ul { padding-left:1.2rem; }
/* GALLERY */
#gallery { padding:5rem 0;background:linear-gradient(180deg,transparent,rgba(8,7,26,.5),transparent); }
.gallery-filters { display:flex;flex-wrap:wrap;gap:.5rem;margin:2rem 0 1.5rem; }
.gallery-filter { background:var(--bg-card);border:1px solid var(--border);color:var(--muted);padding:.45rem 1rem;border-radius:999px;font-size:.78rem;cursor:pointer;font-family:'Space Mono',monospace;transition:all .2s; }
.gallery-filter.active,.gallery-filter:hover { border-color:var(--accent2);color:var(--accent);background:rgba(155,93,229,.1);box-shadow:0 0 12px rgba(155,93,229,.2); }
.gallery-featured { margin-bottom:2rem;border-radius:16px;overflow:hidden;border:1px solid var(--border);background:var(--bg-card);cursor:pointer;transition:all .4s;position:relative; }
.gallery-featured:hover { box-shadow:0 0 50px rgba(155,93,229,.25),0 20px 60px rgba(0,0,0,.5);border-color:var(--border-glow); }
.gallery-featured img { width:100%;max-height:480px;object-fit:cover;display:block;transition:transform .6s; }
.gallery-featured:hover img { transform:scale(1.02); }
.gallery-featured-info { padding:1.25rem 1.5rem; }
.gallery-featured-info h3 { font-size:1.1rem;margin-bottom:.35rem; }
.gallery-featured-info p { color:var(--muted);font-size:.88rem; }
.gallery-grid { display:grid;grid-template-columns:repeat(auto-fill,minmax(280px,1fr));gap:1.25rem; }
.gallery-card { background:var(--bg-card);border:1px solid var(--border);border-radius:14px;overflow:hidden;transition:all .35s;cursor:pointer;position:relative; }
.gallery-card:hover { border-color:var(--accent2);transform:translateY(-6px);box-shadow:0 16px 40px rgba(0,0,0,.4),0 0 30px rgba(155,93,229,.2); }
.gallery-img-container { width:100%;aspect-ratio:16/9;background:var(--bg-panel);position:relative;overflow:hidden; }
.gallery-img-container img { width:100%;height:100%;object-fit:cover;transition:transform .5s; }
.gallery-card:hover .gallery-img-container img { transform:scale(1.08); }
.gallery-fallback { position:absolute;inset:0;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:.75rem;background:linear-gradient(135deg,#080618,#0e0c24); }
.gallery-fallback i { font-size:2rem;color:var(--accent2);opacity:.45; }
.gallery-fallback span { font-family:'Space Mono',monospace;font-size:.72rem;color:var(--muted);padding:0 1rem;text-align:center;word-break:break-all; }
.gallery-info { padding:1rem 1.15rem; }
.gallery-info h3 { font-size:.92rem;font-weight:600;margin-bottom:.25rem; }
.gallery-info p { font-size:.8rem;color:var(--muted);line-height:1.5; }
.gallery-tag { display:inline-block;margin-top:.5rem;font-size:.65rem;font-family:'Space Mono',monospace;color:var(--accent);background:rgba(255,209,102,.08);border:1px solid rgba(255,209,102,.25);border-radius:4px;padding:.1rem .45rem; }
.gallery-notice { margin:1rem 0 1.5rem;padding:.85rem 1.1rem;border-radius:10px;border:1px dashed rgba(155,93,229,.4);background:rgba(155,93,229,.06);color:var(--muted);font-size:.85rem;line-height:1.6; }
.gallery-notice code { font-family:'Space Mono',monospace;color:var(--accent);font-size:.78rem; }
/* LIGHTBOX */
#lightbox { display:none;position:fixed;inset:0;z-index:200;background:rgba(0,0,0,.95);align-items:center;justify-content:center;padding:1.5rem;backdrop-filter:blur(10px); }
#lightbox.open { display:flex;animation:lb-appear .3s ease; }
@keyframes lb-appear { from{opacity:0} to{opacity:1} }
#lightbox img { max-width:95vw;max-height:85vh;border-radius:8px;border:1px solid var(--border);box-shadow:0 0 60px rgba(155,93,229,.2); }
#lightbox .lb-close { position:absolute;top:1.25rem;right:1.5rem;background:none;border:none;color:var(--text);font-size:2rem;cursor:pointer;transition:color .2s; }
#lightbox .lb-close:hover { color:var(--accent); }
#lightbox .lb-caption { position:absolute;bottom:1.5rem;left:50%;transform:translateX(-50%);text-align:center;color:var(--muted);font-size:.9rem;max-width:90vw; }
/* TABS */
#tabs-section { padding:5rem 0; }
.tabs-grid { display:grid;grid-template-columns:repeat(3,1fr);gap:1rem;margin-top:2rem; }
.tab-card { background:var(--bg-panel);border:1px solid var(--border);border-radius:12px;padding:1.25rem;transition:all .3s;position:relative;overflow:hidden; }
.tab-card::before { content:'';position:absolute;bottom:0;left:0;right:0;height:2px;background:linear-gradient(90deg,transparent,var(--accent2),var(--accent),transparent);transform:scaleX(0);transition:transform .3s; }
.tab-card:hover { border-color:rgba(155,93,229,.45);transform:translateY(-3px);box-shadow:0 8px 30px rgba(0,0,0,.4),0 0 20px rgba(155,93,229,.1); }
.tab-card:hover::before { transform:scaleX(1); }
.tab-card i { color:var(--accent2);font-size:1.2rem;margin-bottom:.65rem;display:block;transition:text-shadow .3s; }
.tab-card:hover i { color:var(--accent);text-shadow:0 0 15px var(--accent); }
.tab-card h3 { font-size:.88rem;font-weight:600;margin-bottom:.35rem; }
.tab-card p { font-size:.8rem;color:var(--muted);line-height:1.55; }
/* FEATURES */
#features { padding:5rem 0; }
.features-grid { display:grid;grid-template-columns:repeat(auto-fit,minmax(260px,1fr));gap:1.25rem;margin-top:2.5rem; }
.feature-card { background:var(--bg-card);border:1px solid var(--border);border-radius:14px;padding:1.5rem;transition:all .35s;position:relative;overflow:hidden; }
.feature-card:hover { border-color:rgba(155,93,229,.5);transform:translateY(-5px);box-shadow:0 12px 40px rgba(0,0,0,.4),0 0 30px rgba(155,93,229,.12); }
.feat-icon { width:48px;height:48px;border-radius:12px;background:rgba(155,93,229,.08);border:1px solid rgba(155,93,229,.3);display:flex;align-items:center;justify-content:center;margin-bottom:1rem;transition:all .3s; }
.feature-card:hover .feat-icon { background:rgba(155,93,229,.16);box-shadow:0 0 20px rgba(155,93,229,.3); }
.feat-icon i { color:var(--accent2);transition:color .3s,text-shadow .3s; }
.feature-card:hover .feat-icon i { color:var(--accent);text-shadow:0 0 10px var(--accent); }
.feature-card h3 { font-family:'Orbitron',sans-serif;font-size:.82rem;letter-spacing:.05em;margin-bottom:.5rem; }
.feature-card p { font-size:.85rem;color:var(--muted);line-height:1.65; }
.feature-tag { display:inline-block;margin-top:.75rem;font-size:.68rem;font-family:'Space Mono',monospace;color:var(--accent);background:rgba(255,209,102,.08);border:1px solid rgba(255,209,102,.25);border-radius:4px;padding:.12rem .45rem; }
/* USAGE */
#usage { padding:5rem 0;background:linear-gradient(180deg,transparent,rgba(8,7,26,.4),transparent); }
.workflow { display:grid;grid-template-columns:repeat(auto-fit,minmax(220px,1fr));gap:1.5rem;margin-top:2.5rem; }
.workflow-step { text-align:center;padding:1.5rem; }
.workflow-num { width:52px;height:52px;border-radius:50%;border:2px solid var(--accent2);display:flex;align-items:center;justify-content:center;margin:0 auto 1rem;font-family:'Orbitron',sans-serif;color:var(--accent);font-weight:700;background:rgba(155,93,229,.07);box-shadow:0 0 20px rgba(155,93,229,.25);transition:all .3s; }
.workflow-step:hover .workflow-num { background:rgba(155,93,229,.18);box-shadow:0 0 35px rgba(155,93,229,.45),0 0 15px rgba(255,209,102,.2);transform:scale(1.1); }
.workflow-step h3 { font-size:.95rem;margin-bottom:.4rem; }
.workflow-step p { font-size:.85rem;color:var(--muted);line-height:1.6; }
/* INSTALL */
#install { padding:5rem 0; }
.steps-grid { display:grid;grid-template-columns:repeat(auto-fit,minmax(250px,1fr));gap:1.5rem;margin-top:2.5rem; }
.step-card { background:var(--bg-panel);border:1px solid var(--border);border-radius:14px;padding:1.75rem;text-align:center;transition:all .3s;position:relative;overflow:hidden; }
.step-card:hover { border-color:var(--accent2);transform:translateY(-4px);box-shadow:0 10px 40px rgba(0,0,0,.4),0 0 25px rgba(155,93,229,.12); }
.step-number { font-family:'Orbitron',sans-serif;font-size:.68rem;color:var(--accent);letter-spacing:.1em;margin-bottom:.75rem; }
.step-icon-wrap { width:64px;height:64px;border-radius:50%;background:rgba(155,93,229,.07);border:2px solid rgba(155,93,229,.3);display:flex;align-items:center;justify-content:center;margin:0 auto 1rem;transition:all .3s; }
.step-card:hover .step-icon-wrap { box-shadow:0 0 25px rgba(155,93,229,.35);background:rgba(155,93,229,.15); }
.step-icon-wrap i { font-size:1.5rem;color:var(--accent2); }
.step-card h3 { font-size:1rem;margin-bottom:.4rem; }
.step-card p { font-size:.85rem;color:var(--muted);line-height:1.6; }
/* REQUIREMENTS */
#requirements { padding:4rem 0 5rem; }
.req-grid { display:grid;grid-template-columns:repeat(auto-fit,minmax(200px,1fr));gap:1rem;margin-top:1.5rem; }
.req-item { display:flex;align-items:center;gap:.75rem;background:var(--bg-card);border:1px solid var(--border);border-radius:10px;padding:1rem;font-size:.88rem;color:var(--muted);transition:all .3s; }
.req-item:hover { border-color:var(--border-glow);transform:translateX(4px);box-shadow:0 0 15px rgba(155,93,229,.1); }
.req-item i { color:var(--accent2);width:20px;text-align:center;transition:color .3s,text-shadow .3s; }
.req-item:hover i { color:var(--accent);text-shadow:0 0 10px var(--accent); }
/* FOOTER */
footer { background:var(--bg-panel);border-top:1px solid var(--border);padding:3rem 1.5rem;text-align:center;position:relative;z-index:3;overflow:hidden; }
footer::before { content:'';position:absolute;top:0;left:0;right:0;height:1px;background:linear-gradient(90deg,transparent,var(--accent2),var(--accent),transparent);box-shadow:0 0 20px rgba(155,93,229,.5); }
.footer-logo { font-family:'Orbitron',sans-serif;font-size:1.15rem;font-weight:700;letter-spacing:.18em;margin-bottom:1rem;text-shadow:0 0 20px rgba(255,209,102,.45); }
footer p { color:var(--muted);font-size:.85rem;line-height:1.8;max-width:680px;margin:0 auto 1rem; }
footer a { color:var(--accent);text-decoration:none;transition:text-shadow .2s; }
footer a:hover { text-decoration:underline;text-shadow:0 0 10px var(--accent); }
.footer-copy { font-size:.75rem;color:#4a3a6a;font-family:'Space Mono',monospace; }
/* HOLO CARD EFFECT */
@keyframes holo-sweep { 0%{background-position:-200% 0} 100%{background-position:200% 0} }
.holo-card { position:relative;overflow:hidden; }
.holo-card::after { content:'';position:absolute;inset:0;pointer-events:none;background:linear-gradient(105deg,transparent 20%,rgba(155,93,229,.05) 30%,rgba(255,209,102,.07) 45%,rgba(155,93,229,.04) 60%,transparent 70%);background-size:200% 100%;opacity:0;transition:opacity .3s;animation:holo-sweep 3s linear infinite paused; }
.holo-card:hover::after { opacity:1;animation-play-state:running; }
@media (max-width:768px) {
.nav-links .hide-mobile { display:none; }
.about-grid { grid-template-columns:1fr; }
h1.title { letter-spacing:.12em;padding-left:.12em; }
.tabs-grid { grid-template-columns:repeat(2,1fr); }
#cursor,#cursor-aura { display:none; }
body { cursor:auto; }
}
@media (max-width:480px) { .tabs-grid { grid-template-columns:1fr; } }
</style>
</head>
<body>
<canvas id="canvas-bg"></canvas>
<div id="starfield"></div>
<div id="grid-overlay"></div>
<div class="nebula nebula-1"></div>
<div class="nebula nebula-2"></div>
<div class="nebula nebula-3"></div>
<div class="nebula nebula-4"></div>
<div class="nebula nebula-5"></div>
<div id="scanlines"></div>
<div id="laser-scan"></div>
<div id="scroll-progress"></div>
<div id="cursor"></div>
<div id="cursor-aura"></div>
<nav id="main-nav">
<div class="nav-inner">
<a href="#hero" class="logo">
<div class="logo-icon" id="navLogoIcon">
<img class="logo-img" src="astralis_logo.png" alt="Astralis" onerror="document.getElementById('navLogoIcon').classList.add('is-fallback')" />
<svg class="logo-fallback" viewBox="0 0 24 24" fill="none"><polygon points="12,2 22,20 2,20" fill="none" stroke="#00E5FF" stroke-width="1.8"/><circle cx="12" cy="12" r="3" fill="#00E5FF" opacity=".8"/></svg>
</div>
ASTRALIS
</a>
<div class="nav-links">
<a href="#about" class="hide-mobile" data-i18n="nav.about">À propos</a>
<a href="#gallery" class="hide-mobile" data-i18n="nav.gallery">Galerie</a>
<a href="#features" class="hide-mobile" data-i18n="nav.features">Fonctionnalités</a>
<a href="#install" class="hide-mobile" data-i18n="nav.install">Installation</a>
<a href="https://github.com/L-Capdevila/Astralis" target="_blank" rel="noopener"><i class="fab fa-github"></i> GitHub</a>
<button class="btn-lang" id="langToggle" onclick="toggleLang()">EN</button>
</div>
</div>
</nav>
<section id="hero">
<div class="container hero-content" style="text-align:center;">
<div class="hero-badge"><span class="dot"></span><span data-i18n="hero.badge">1.0.3 — Disponible pour Windows</span></div>
<h1 class="title" data-text="ASTRALIS">ASTR<span>A</span>LIS</h1>
<p class="hero-slogan" data-i18n="hero.slogan">Simulez la gravité à l'échelle céleste. Moteur <em>N-corps 3D</em> Yoshida, neuf onglets d'analyse scientifique, interface complète sous Windows.</p>
<div class="hero-actions">
<a href="https://github.com/L-Capdevila/Astralis/releases/latest/download/Setup_Astralis.exe" class="btn-download" download="Setup_Astralis.exe" rel="noopener">
<i class="fab fa-windows"></i><span data-i18n="hero.download">Télécharger pour Windows</span>
</a>
<a href="#gallery" class="btn-secondary"><i class="fas fa-images"></i> <span data-i18n="hero.gallery">Voir la galerie</span></a>
</div>
<div class="hero-meta">
<span><i class="fas fa-shield-alt"></i> <span data-i18n="hero.meta">Gratuit · Open Source · Windows 10/11</span></span>
<span><i class="fas fa-database"></i> <span data-i18n="hero.meta2">Export Parquet · CSV (Distances, Stabilité)</span></span>
</div>
<div class="stats-row">
<div class="stat-card holo-card"><div class="corner-deco tl"></div><div class="corner-deco br"></div><strong><span class="stat-counter" data-target="9">0</span></strong><span data-i18n="stat.tabs">Onglets d'analyse</span></div>
<div class="stat-card holo-card"><div class="corner-deco tl"></div><div class="corner-deco br"></div><strong>Y4</strong><span data-i18n="stat.yoshida">Intégrateur Yoshida</span></div>
<div class="stat-card holo-card"><div class="corner-deco tl"></div><div class="corner-deco br"></div><strong>PN1</strong><span data-i18n="stat.pn1">PN1 (option)</span></div>
<div class="stat-card holo-card"><div class="corner-deco tl"></div><div class="corner-deco br"></div><strong>N</strong><span data-i18n="stat.nbodies">Corps multiples</span></div>
</div>
</div>
</section>
<section id="about">
<div class="container">
<p class="section-label reveal" data-i18n="about.label">// Le projet</p>
<h2 class="section-title reveal" data-i18n="about.title">Astralis, qu'est-ce que c'est ?</h2>
<p class="section-desc reveal" data-i18n="about.desc">Astralis est un simulateur gravitationnel N-corps 3D capable de reproduire la dynamique de systèmes célestes complexes sur de longues durées.</p>
<div class="divider reveal"></div>
<div class="about-grid">
<div class="about-card holo-card reveal-left" style="transition-delay:.1s"><div class="corner-deco tl"></div><div class="corner-deco br"></div><h3 data-i18n="about.card1.title">Moteur de simulation</h3><p data-i18n="about.card1.desc">Calcul N-corps 3D vectorisé avec Numba, intégrateur symplectique Yoshida 4, pas adaptatif Sundman, checkpoints périodiques et reprise sur checkpoint.</p></div>
<div class="about-card holo-card reveal-right" style="transition-delay:.2s"><div class="corner-deco tl"></div><div class="corner-deco br"></div><h3 data-i18n="about.card2.title">Tableau de bord PyQt5</h3><p data-i18n="about.card2.desc">Configurez vos corps, lancez le moteur : à la fin de la simulation, le dashboard recharge automatiquement le Parquet.</p></div>
<div class="about-card holo-card reveal-left" style="transition-delay:.3s"><div class="corner-deco tl"></div><div class="corner-deco br"></div><h3 data-i18n="about.card3.title">Public visé</h3><ul><li data-i18n="about.list1">Étudiants et passionnés d'astronomie</li><li data-i18n="about.list2">Projets de mécanique céleste et de recherche</li><li data-i18n="about.list3">Validation et exploration de stabilité orbitale</li></ul></div>
<div class="about-card holo-card reveal-right" style="transition-delay:.4s"><div class="corner-deco tl"></div><div class="corner-deco br"></div><h3 data-i18n="about.card4.title">Crédits</h3><p data-i18n="about.card4.desc">Conception et direction : L. Capdevila. Développement : collaboration IA (Claude & Gemini). Projet open source indépendant.</p></div>
</div>
</div>
</section>
<section id="gallery">
<div class="container">
<p class="section-label reveal" data-i18n="gallery.label">// Captures d'écran</p>
<h2 class="section-title reveal" data-i18n="gallery.title">Galerie</h2>
<p class="section-desc reveal" data-i18n="gallery.desc">Explorez visuellement Astralis — onglets d'analyse interactifs et panneau de simulation.</p>
<div class="divider reveal"></div>
<p class="gallery-notice" id="galleryNotice" data-i18n="gallery.notice">Astuce : déposez vos captures PNG dans le dossier <code>gallery/</code> (ex. <code>02-orbites.png</code>) — elles remplaceront automatiquement les aperçus.</p>
<div class="gallery-filters" id="galleryFilters"></div>
<div id="galleryFeatured" class="gallery-featured" style="display:none;"></div>
<div class="gallery-grid" id="galleryGrid"></div>
</div>
</section>
<section id="tabs-section">
<div class="container">
<p class="section-label reveal" data-i18n="tabs.label">// Interface</p>
<h2 class="section-title reveal" data-i18n="tabs.title">Les 9 onglets du dashboard</h2>
<p class="section-desc reveal" data-i18n="tabs.desc">Chaque onglet offre une vue dédiée — analyse scientifique ou contrôle du moteur de simulation.</p>
<div class="tabs-grid" id="tabsGrid"></div>
</div>
</section>
<section id="features">
<div class="container">
<p class="section-label reveal" data-i18n="features.label">// Moteur physique</p>
<h2 class="section-title reveal" data-i18n="features.title">Fonctionnalités techniques</h2>
<div class="divider reveal"></div>
<div class="features-grid" id="featuresGrid"></div>
</div>
</section>
<section id="usage">
<div class="container" style="text-align:center;">
<p class="section-label reveal" data-i18n="usage.label">// Workflow</p>
<h2 class="section-title reveal" data-i18n="usage.title">Comment ça marche ?</h2>
<p class="section-desc reveal" style="margin:0 auto;" data-i18n="usage.desc">De la configuration à l'analyse des résultats en quatre étapes.</p>
<div class="workflow" id="workflowGrid"></div>
</div>
</section>
<section id="install">
<div class="container" style="text-align:center;">
<p class="section-label reveal" data-i18n="install.label">// Installation</p>
<h2 class="section-title reveal" data-i18n="install.title">Télécharger & installer</h2>
<div class="divider reveal" style="margin:1.5rem auto 0;"></div>
<div class="steps-grid" id="installSteps"></div>
<div style="margin-top:2rem;" class="reveal">
<a href="https://github.com/L-Capdevila/Astralis/releases/latest/download/Setup_Astralis.exe" class="btn-download" download rel="noopener">
<i class="fab fa-windows"></i> <span data-i18n="hero.download">Télécharger pour Windows</span>
</a>
</div>
</div>
</section>
<section id="requirements">
<div class="container">
<p class="section-label reveal" data-i18n="req.label">// Configuration</p>
<h2 class="section-title reveal" data-i18n="req.title">Prérequis système</h2>
<div class="req-grid" id="reqGrid"></div>
</div>
</section>
<footer>
<div class="footer-logo">ASTRALIS</div>
<p data-i18n="footer.desc">Projet scientifique conçu par L. Capdevila — simulateur gravitationnel open source.</p>
<p><a href="https://github.com/L-Capdevila/Astralis" target="_blank" rel="noopener"><i class="fab fa-github"></i> github.com/L-Capdevila/Astralis</a></p>
<div style="width:40px;height:2px;background:var(--accent);margin:1.5rem auto;opacity:.5;box-shadow:0 0 8px var(--accent);"></div>
<p class="footer-copy" data-i18n="footer.copy">© L. Capdevila — Open Source</p>
</footer>
<div id="lightbox" onclick="closeLightbox(event)">
<button class="lb-close" onclick="closeLightbox(event)" aria-label="Fermer">×</button>
<img id="lightboxImg" src="" alt="" />
<p class="lb-caption" id="lightboxCaption"></p>
</div>
<script>
/* ── CUSTOM CURSOR ── */
(function(){
const cur=document.getElementById('cursor'),aura=document.getElementById('cursor-aura');
let lp=0;
document.addEventListener('mousemove',e=>{
cur.style.left=e.clientX+'px';cur.style.top=e.clientY+'px';
aura.style.left=e.clientX+'px';aura.style.top=e.clientY+'px';
const now=Date.now();
if(now-lp>45){lp=now;const p=document.createElement('div');p.className='cursor-particle';p.style.cssText=`left:${e.clientX}px;top:${e.clientY}px;`;document.body.appendChild(p);setTimeout(()=>p.remove(),620);}
});
})();
/* ── SCROLL PROGRESS ── */
window.addEventListener('scroll',()=>{
const d=document.documentElement,s=d.scrollTop,m=d.scrollHeight-d.clientHeight;
document.getElementById('scroll-progress').style.width=(s/m*100)+'%';
document.getElementById('main-nav').classList.toggle('scrolled',s>60);
});
/* ── STARFIELD ── */
(function(){
const sf=document.getElementById('starfield');
for(let i=0;i<220;i++){const s=document.createElement('div');s.className='star';const sz=Math.random()*2.8+.4;s.style.cssText=`width:${sz}px;height:${sz}px;left:${Math.random()*100}%;top:${Math.random()*100}%;--d:${(Math.random()*6+2).toFixed(1)}s;--delay:-${(Math.random()*8).toFixed(1)}s;--op:${(Math.random()*.7+.2).toFixed(2)}`;sf.appendChild(s);}
})();
/* ── ORBITAL CANVAS ── */
(function(){
const canvas=document.getElementById('canvas-bg'),ctx=canvas.getContext('2d');
function resize(){canvas.width=window.innerWidth;canvas.height=window.innerHeight;}
resize();window.addEventListener('resize',resize);
// Central star
const star={x:0,y:0,r:4,pulsePhase:0};
// Orbiting bodies
const orbits=[
{a:120,b:70,angle:0,speed:.00045,r:3,color:'rgba(255,209,102,',trail:[]},
{a:200,b:130,angle:1.2,speed:.00028,r:2.5,color:'rgba(155,93,229,',trail:[]},
{a:290,b:180,angle:2.5,speed:.00018,r:2,color:'rgba(180,120,255,',trail:[]},
{a:370,b:240,angle:0.8,speed:.00012,r:2,color:'rgba(255,150,80,',trail:[]},
];
const TRAIL_LEN=90;
function frame(){
ctx.clearRect(0,0,canvas.width,canvas.height);
const cx=canvas.width/2,cy=canvas.height/2+60;
star.x=cx;star.y=cy;star.pulsePhase+=0.015;
// Draw orbits (ellipses)
orbits.forEach(o=>{
ctx.save();ctx.translate(cx,cy);
ctx.beginPath();
ctx.ellipse(0,0,o.a,o.b,0,0,Math.PI*2);
ctx.strokeStyle='rgba(155,93,229,0.07)';ctx.lineWidth=1;ctx.stroke();
ctx.restore();
});
// Update & draw bodies
orbits.forEach(o=>{
o.angle+=o.speed;
const x=cx+Math.cos(o.angle)*o.a;
const y=cy+Math.sin(o.angle)*o.b;
o.trail.push({x,y});
if(o.trail.length>TRAIL_LEN)o.trail.shift();
// Trail
o.trail.forEach((pt,i)=>{
const alpha=((i/TRAIL_LEN)*.25).toFixed(3);
ctx.beginPath();ctx.arc(pt.x,pt.y,o.r*.5,0,Math.PI*2);
ctx.fillStyle=o.color+alpha+')';ctx.fill();
});
// Body
const grd=ctx.createRadialGradient(x,y,0,x,y,o.r*2.5);
grd.addColorStop(0,o.color+'0.95)');grd.addColorStop(1,o.color+'0)');
ctx.beginPath();ctx.arc(x,y,o.r*2.5,0,Math.PI*2);ctx.fillStyle=grd;ctx.fill();
ctx.beginPath();ctx.arc(x,y,o.r,0,Math.PI*2);ctx.fillStyle=o.color+'1)';ctx.fill();
});
// Central star
const pulse=Math.sin(star.pulsePhase)*.3+.7;
const sg=ctx.createRadialGradient(cx,cy,0,cx,cy,18*pulse);
sg.addColorStop(0,'rgba(255,240,180,0.95)');
sg.addColorStop(.4,'rgba(255,209,102,0.6)');
sg.addColorStop(1,'rgba(255,180,50,0)');
ctx.beginPath();ctx.arc(cx,cy,18*pulse,0,Math.PI*2);ctx.fillStyle=sg;ctx.fill();
ctx.beginPath();ctx.arc(cx,cy,4,0,Math.PI*2);ctx.fillStyle='rgba(255,255,220,0.9)';ctx.fill();
requestAnimationFrame(frame);
}
frame();
})();
/* ── SCROLL REVEAL ── */
(function(){
const obs=new IntersectionObserver(entries=>entries.forEach(e=>{if(e.isIntersecting)e.target.classList.add('visible');}),{threshold:.1});
document.querySelectorAll('.reveal,.reveal-left,.reveal-right').forEach(el=>obs.observe(el));
})();
/* ── COUNTER ANIMATION ── */
(function(){
const obs=new IntersectionObserver(entries=>entries.forEach(e=>{if(e.isIntersecting){const el=e.target,target=+el.dataset.target;let c=0,step=target/30;const t=setInterval(()=>{c=Math.min(c+step,target);el.textContent=Math.round(c);if(c>=target)clearInterval(t);},40);obs.unobserve(el);}}),{threshold:.5});
document.querySelectorAll('.stat-counter').forEach(el=>obs.observe(el));
})();
/* ── HOLO CARD TILT ── */
function applyHoloTilt(){
document.querySelectorAll('.holo-card').forEach(card=>{
card.addEventListener('mousemove',e=>{
const r=card.getBoundingClientRect(),x=e.clientX-r.left,y=e.clientY-r.top,cx=r.width/2,cy=r.height/2;
card.style.transform=`translateY(-4px) perspective(600px) rotateX(${((y-cy)/cy)*5}deg) rotateY(${((x-cx)/cx)*-5}deg)`;
});
card.addEventListener('mouseleave',()=>{card.style.transform='';});
});
}
/* ── DATA ── */
function gallerySources(item){const stem=item.file.replace(/\.(png|jpe?g|webp)$/i,'');return[item.file,stem+'.svg',...(item.fallback||[])];}
const GALLERY_ITEMS=[
{id:'02',file:'gallery/02-orbites.png',fallback:['orbital-view-3d.png'],cat:'analysis',fr:{title:'Onglet Orbites',desc:'Trajectoires 2D et 3D, filtres de corps et repères.'},en:{title:'Orbits tab',desc:'2D and 3D trajectories with body filters and frames.'}},
{id:'03',file:'gallery/03-energie.png',fallback:['energy-plot.png'],cat:'analysis',fr:{title:'Onglet Énergie',desc:"Dérive d'énergie totale et moment cinétique."},en:{title:'Energy tab',desc:'Total energy drift and angular momentum.'}},
{id:'04',file:'gallery/04-distances.png',fallback:[],cat:'analysis',fr:{title:'Onglet Distances',desc:'Distances inter-corps et histogrammes statistiques.'},en:{title:'Distances tab',desc:'Inter-body distances and statistical histograms.'}},
{id:'05',file:'gallery/05-stabilite.png',fallback:[],cat:'analysis',fr:{title:'Onglet Stabilité',desc:'Éléments orbitaux de Kepler dans le temps.'},en:{title:'Stability tab',desc:'Kepler orbital elements over time.'}},
{id:'06',file:'gallery/06-phase.png',fallback:[],cat:'analysis',fr:{title:'Diagramme de phase',desc:'Vitesse vs distance — boucles fermées si orbite stable.'},en:{title:'Phase diagram',desc:'Velocity vs distance — closed loops indicate stable orbits.'}},
{id:'07',file:'gallery/07-densite.png',fallback:[],cat:'analysis',fr:{title:'Densité spatiale',desc:'Carte de chaleur des zones les plus fréquentées.'},en:{title:'Spatial density',desc:'Heat map of most frequented spatial zones.'}},
{id:'08',file:'gallery/08-simulation.png',fallback:['parameters-panel.png'],cat:'simulation',fr:{title:'Onglet Simulation',desc:'Presets, paramètres physiques et lancement du moteur.'},en:{title:'Simulation tab',desc:'Presets, physics settings and engine launch.'}},
{id:'09',file:'gallery/09-resume.png',fallback:[],cat:'analysis',fr:{title:'Onglet Résumé',desc:"Fiche d'identité de chaque corps."},en:{title:'Summary tab',desc:'Per-body report card.'}},
{id:'10',file:'gallery/10-vitesses.png',fallback:[],cat:'analysis',fr:{title:'Onglet Vitesses',desc:'Évolution des vitesses de chaque corps.'},en:{title:'Velocities tab',desc:'Velocity evolution for each body.'}},
];
const FEATURES=[
{icon:'fa-infinity',tag:'symplectic · Y4',fr:{t:'Intégrateur Yoshida 4',d:"Schéma symplectique d'ordre 4 — conservation hamiltonienne sur de longues durées simulées."},en:{t:'Yoshida 4 integrator',d:'4th-order symplectic scheme — Hamiltonian conservation over extended simulated timescales.'}},
{icon:'fa-tachometer-alt',tag:'adaptive',fr:{t:'Pas adaptatif Sundman',d:'Pas de temps réduit automatiquement lors des rapprochements — meilleure précision dans les zones critiques.'},en:{t:'Sundman adaptive step',d:'Timestep automatically reduced during close encounters — higher accuracy in critical zones.'}},
{icon:'fa-atom',tag:'PN1 · option',fr:{t:'Relativité post-newtonienne',d:"Approximation PN1 (1er ordre) activable — précession des périhélies, sans ondes gravitationnelles."},en:{t:'Post-Newtonian relativity',d:'Optional PN1 (1st-order) approximation — perihelion precession, without gravitational waves.'}},
{icon:'fa-compress-alt',tag:'J2 · option',fr:{t:'Aplatissement J2',d:'Coefficient J2·R² configurable par corps — oblativité pour les orbites basses et les satellites.'},en:{t:'J2 flattening',d:'Per-body J2·R² coefficient — oblateness for low orbits and satellite dynamics.'}},
{icon:'fa-water',tag:'k2 · option',fr:{t:'Marées gravitationnelles k2',d:'Coefficient de Love k₂ par corps — couples de marée et transfert de moment angulaire.'},en:{t:'Tidal k2 forces',d:'Per-body Love number k₂ — tidal torques and angular momentum transfer.'}},
{icon:'fa-save',tag:'checkpoint',fr:{t:'Checkpoints & reprise',d:'Sauvegardes périodiques automatiques — reprenez avec --resume sans tout recalculer.'},en:{t:'Checkpoints & resume',d:'Automatic periodic saves — resume with --resume without recomputing from scratch.'}},
{icon:'fa-file-export',tag:'parquet',fr:{t:'Export Parquet / CSV',d:'Sortie principale en Parquet. Export CSV depuis les onglets Distances et Stabilité.'},en:{t:'Parquet / CSV export',d:'Primary Parquet output. CSV export from Distances and Stability tabs.'}},
{icon:'fa-chart-bar',tag:'dashboard',fr:{t:"9 onglets d'analyse",d:'Orbites, énergie, distances, vitesses, stabilité képlérienne, phase, densité spatiale, résumé et simulation.'},en:{t:'9 analysis tabs',d:'Orbits, energy, distances, velocities, Keplerian stability, phase, spatial density, summary and simulation.'}},
];
const DASHBOARD_TABS=[
{icon:'fa-globe',fr:{t:'Orbites',d:'Trajectoires 2D et 3D, zoom interactif, filtres de corps et repères de référence.'},en:{t:'Orbits',d:'2D and 3D trajectories, interactive zoom, body filters and reference frames.'}},
{icon:'fa-ruler-horizontal',fr:{t:'Distances',d:'Courbes temporelles et histogrammes statistiques des distances inter-corps.'},en:{t:'Distances',d:'Time curves and statistical histograms of inter-body distances.'}},
{icon:'fa-bolt',fr:{t:'Énergie',d:'Dérive relative ΔE/E₀ et conservation du moment cinétique.'},en:{t:'Energy',d:'Relative drift ΔE/E₀ and angular momentum conservation.'}},
{icon:'fa-running',fr:{t:'Vitesses',d:'Évolution temporelle des normes et composantes de vitesse pour chaque corps.'},en:{t:'Velocities',d:'Time evolution of velocity norms and components for each body.'}},
{icon:'fa-anchor',fr:{t:'Stabilité',d:'Éléments orbitaux de Kepler (e, a, i, ω, Ω) en fonction du temps simulé.'},en:{t:'Stability',d:'Keplerian orbital elements (e, a, i, ω, Ω) as a function of simulated time.'}},
{icon:'fa-project-diagram',fr:{t:'Phase',d:'Espace des phases vitesse vs distance — les boucles fermées signalent une orbite stable.'},en:{t:'Phase',d:'Phase space velocity vs distance — closed loops indicate stable orbits.'}},
{icon:'fa-th',fr:{t:'Densité spatiale',d:'Carte de chaleur des zones les plus fréquentées par les corps au cours de la simulation.'},en:{t:'Spatial density',d:'Heat map of the most frequently visited spatial zones throughout the simulation.'}},
{icon:'fa-list-alt',fr:{t:'Résumé',d:"Fiche d'identité complète de chaque corps : période, excentricité, demi-grand axe…"},en:{t:'Summary',d:'Complete report card for each body: period, eccentricity, semi-major axis…'}},
{icon:'fa-play-circle',fr:{t:'Simulation',d:'Choix du preset, configuration des paramètres physiques et lancement du moteur.'},en:{t:'Simulation',d:'Preset selection, physics parameter configuration and engine launch.'}},
];
let currentLang='fr',appVersion='1.0.3',galleryFilter='all',loadedRealGalleryCount=0;
const HTML_I18N_KEYS=new Set(['hero.slogan','gallery.notice']);
const i18n={
fr:{
'nav.about':'À propos','nav.gallery':'Galerie','nav.features':'Fonctionnalités','nav.install':'Installation',
'hero.badge':'1.0.3 — Disponible pour Windows',
'hero.slogan':"Simulez la gravité à l'échelle céleste. Moteur <em>N-corps 3D</em> Yoshida, neuf onglets d'analyse scientifique, interface complète sous Windows.",
'hero.download':'Télécharger pour Windows','hero.gallery':'Voir la galerie',
'hero.meta':'Gratuit · Open Source · Windows 10/11','hero.meta2':'Export Parquet · CSV (Distances, Stabilité)',
'stat.tabs':"Onglets d'analyse",'stat.yoshida':'Intégrateur Yoshida','stat.pn1':'PN1 (option)','stat.nbodies':'Corps multiples',
'about.label':'✦ Le projet','about.title':"Astralis, qu'est-ce que c'est ?",'about.desc':"Astralis est un simulateur gravitationnel N-corps 3D capable de reproduire la dynamique de systèmes célestes complexes sur de longues durées. Conçu pour la précision numérique, il embarque un tableau de bord d'analyse complet directement intégré à l'interface.",
'about.card1.title':'Moteur de simulation','about.card1.desc':"Calcul N-corps 3D vectorisé avec Numba, intégrateur symplectique Yoshida 4, pas adaptatif Sundman, checkpoints périodiques et reprise sur checkpoint. Les données sont exportées en Parquet.",
'about.card2.title':'Tableau de bord PyQt5','about.card2.desc':"Configurez vos corps, lancez le moteur : à la fin de la simulation, le dashboard recharge automatiquement le Parquet. Analysez ensuite orbites 2D/3D, énergie, stabilité képlérienne, diagrammes de phase et cartes de densité.",
'about.card3.title':'Public visé','about.list1':"Étudiants et passionnés d'astronomie",'about.list2':'Projets de mécanique céleste et de recherche','about.list3':'Validation et exploration de stabilité orbitale',
'about.card4.title':'Crédits','about.card4.desc':'Conception et direction : L. Capdevila. Développement : collaboration IA (Claude & Gemini). Projet open source et indépendant.',
'gallery.label':"✦ Captures d'écran",'gallery.title':'Galerie','gallery.desc':"Explorez visuellement Astralis — onglets d'analyse interactifs et panneau de simulation.",
'gallery.filter.all':'Tous','gallery.filter.analysis':'Analyse','gallery.filter.simulation':'Simulation',
'gallery.notice':'Astuce : déposez vos captures PNG dans le dossier <code>gallery/</code> (ex. <code>02-orbites.png</code>) — elles remplaceront automatiquement les aperçus.',
'tabs.label':'✦ Interface','tabs.title':'Les 9 onglets du dashboard','tabs.desc':'Chaque onglet offre une vue dédiée — analyse scientifique ou contrôle du moteur de simulation.',
'features.label':'✦ Moteur physique','features.title':'Fonctionnalités techniques',
'usage.label':'✦ Workflow','usage.title':'Comment ça marche ?','usage.desc':"De la configuration initiale à l'analyse des résultats — quatre étapes simples.",
'usage.s1.t':'Choisir un preset','usage.s1.d':'Système Soleil-Terre-Lune, binaire stellaire, ou configuration entièrement personnalisée.',
'usage.s2.t':'Lancer la simulation','usage.s2.d':'Le moteur calcule les trajectoires et exporte automatiquement les données en Parquet.',
'usage.s3.t':'Charger les résultats','usage.s3.d':'À la fin du calcul, le dashboard détecte et charge automatiquement le fichier Parquet de sortie.',
'usage.s4.t':'Analyser','usage.s4.d':'Explorez orbites, énergie et stabilité. Export CSV depuis Distances et Stabilité ; export PNG sur les graphiques.',
'install.label':'✦ Installation','install.title':'Télécharger & installer',
'install.s1.t':'Télécharger','install.s1.d':'Récupérez Setup_Astralis.exe directement depuis la page GitHub Releases.',
'install.s2.t':'Installer','install.s2.d':"Lancez l'installateur. Si SmartScreen s'affiche : Informations complémentaires → Exécuter quand même.",
'install.s3.t':'Lancer','install.s3.d':'Double-cliquez sur Astralis pour ouvrir le tableau de bord.',
'install.s4.t':'Simuler','install.s4.d':'Onglet Simulation → choisissez un preset → appuyez sur ▶ Lancer.',
'req.label':'✦ Configuration','req.title':'Prérequis système',
'req.r1':'Windows 10 ou 11 (64 bits)','req.r2':"~120 Mo d'espace disque",'req.r3':'4 Go RAM recommandés','req.r4':"Python non requis — tout inclus dans l'installateur",
'footer.desc':'Projet scientifique conçu par L. Capdevila — simulateur gravitationnel N-corps open source.',
'footer.copy':`© ${new Date().getFullYear()} L. Capdevila — Open Source`,
},
en:{
'nav.about':'About','nav.gallery':'Gallery','nav.features':'Features','nav.install':'Install',
'hero.badge':'1.0.3 — Available for Windows',
'hero.slogan':'Simulate gravity at a celestial scale. <em>N-body 3D</em> Yoshida engine, nine scientific analysis tabs, full-featured Windows interface.',
'hero.download':'Download for Windows','hero.gallery':'View gallery',
'hero.meta':'Free · Open Source · Windows 10/11','hero.meta2':'Parquet export · CSV (Distances, Stability)',
'stat.tabs':'Analysis tabs','stat.yoshida':'Yoshida integrator','stat.pn1':'PN1 (optional)','stat.nbodies':'Multiple bodies',
'about.label':'✦ The project','about.title':'What is Astralis?','about.desc':'Astralis is a 3D N-body gravitational simulator capable of reproducing the dynamics of complex celestial systems over long timescales. Built for numerical accuracy, it includes a fully integrated scientific analysis dashboard.',
'about.card1.title':'Simulation engine','about.card1.desc':'Numba-accelerated 3D N-body computation, Yoshida 4 symplectic integrator, Sundman adaptive step, periodic checkpoints and resume. Data exported as Parquet.',
'about.card2.title':'PyQt5 dashboard','about.card2.desc':'Configure bodies and run the engine — when the run finishes, the dashboard automatically reloads the Parquet output. Then explore 2D/3D orbits, energy, stability, phase diagrams and density maps.',
'about.card3.title':'Who is it for?','about.list1':'Students and astronomy enthusiasts','about.list2':'Celestial mechanics and research projects','about.list3':'Orbital stability exploration and validation',
'about.card4.title':'Credits','about.card4.desc':'Design & direction: L. Capdevila. Development: AI collaboration (Claude & Gemini). Independent open-source project.',
'gallery.label':'✦ Screenshots','gallery.title':'Gallery','gallery.desc':'Visually explore Astralis — interactive analysis tabs and simulation panel.',
'gallery.filter.all':'All','gallery.filter.analysis':'Analysis','gallery.filter.simulation':'Simulation',
'gallery.notice':'Tip: drop your PNG screenshots in the <code>gallery/</code> folder (e.g. <code>02-orbites.png</code>) — they will replace previews automatically.',
'tabs.label':'✦ Interface','tabs.title':'The 9 dashboard tabs','tabs.desc':'Each tab offers a dedicated view — scientific analysis or simulation engine control.',
'features.label':'✦ Physics engine','features.title':'Technical features',
'usage.label':'✦ Workflow','usage.title':'How it works','usage.desc':'From initial setup to results analysis — four straightforward steps.',
'usage.s1.t':'Pick a preset','usage.s1.d':'Sun-Earth-Moon, stellar binary, or fully custom system.',
'usage.s2.t':'Run simulation','usage.s2.d':'The engine computes trajectories and automatically exports Parquet data.',
'usage.s3.t':'Load results','usage.s3.d':'When the run completes, the dashboard automatically detects and loads the output Parquet file.',
'usage.s4.t':'Analyze','usage.s4.d':'Explore orbits, energy and stability. CSV export from Distances and Stability tabs; PNG export on charts.',
'install.label':'✦ Installation','install.title':'Download & install',
'install.s1.t':'Download','install.s1.d':'Get Setup_Astralis.exe directly from the GitHub Releases page.',
'install.s2.t':'Install','install.s2.d':'Run the installer. If SmartScreen appears: More info → Run anyway.',
'install.s3.t':'Launch','install.s3.d':'Double-click Astralis to open the dashboard.',
'install.s4.t':'Simulate','install.s4.d':'Simulation tab → pick a preset → press ▶ Run.',
'req.label':'✦ System','req.title':'Requirements',
'req.r1':'Windows 10 or 11 (64-bit)','req.r2':'~120 MB disk space','req.r3':'4 GB RAM recommended','req.r4':'No Python required — everything bundled in the installer',
'footer.desc':'Scientific project by L. Capdevila — N-body open-source gravitational simulator.',
'footer.copy':`© ${new Date().getFullYear()} L. Capdevila — Open Source`,
}
};
function t(k){return i18n[currentLang][k]||k;}
function badgeText(l){return l==='fr'?`${appVersion} — Disponible pour Windows`:`${appVersion} — Available for Windows`;}
function updateVersionBadge(){i18n.fr['hero.badge']=badgeText('fr');i18n.en['hero.badge']=badgeText('en');const el=document.querySelector('[data-i18n="hero.badge"]');if(el)el.textContent=badgeText(currentLang);}
async function fetchLatestVersion(){try{const res=await fetch('https://api.github.com/repos/L-Capdevila/Astralis/releases/latest');if(!res.ok)return;const data=await res.json();const tag=(data.tag_name||'').replace(/^v/i,'');if(tag){appVersion=tag;updateVersionBadge();}}catch(_){}}
function tryImageSrc(img,sources,idx){if(idx>=sources.length){img.style.display='none';const fb=img.nextElementSibling;if(fb)fb.style.display='flex';return;}const src=sources[idx];img.onload=()=>{img.style.display='block';const fb=img.nextElementSibling;if(fb)fb.style.display='none';};img.onerror=()=>tryImageSrc(img,sources,idx+1);img.src=src;}
function isRealPng(src){return/\.(png|jpe?g|webp)$/i.test(src)&&!/\.svg$/i.test(src);}
function updateGalleryNotice(){const n=document.getElementById('galleryNotice');if(n)n.style.display=loadedRealGalleryCount>0?'none':'block';}
function checkGalleryLoaded(){const seen=new Set();document.querySelectorAll('#galleryGrid img,#galleryFeatured img').forEach(img=>{if(img.complete&&img.naturalWidth>0&&img.src&&isRealPng(img.src)){try{seen.add(new URL(img.src,location.href).pathname);}catch(_){seen.add(img.src);}}});loadedRealGalleryCount=seen.size;updateGalleryNotice();}
function buildGalleryCard(item,featured){
const loc=item[currentLang],sources=gallerySources(item),catLabel={analysis:t('gallery.filter.analysis'),simulation:t('gallery.filter.simulation')}[item.cat]||item.cat;
const card=document.createElement('div');card.className=featured?'gallery-featured':'gallery-card';card.dataset.cat=item.cat;
card.innerHTML=`<div class="gallery-img-container"><img alt="${loc.title}" /><div class="gallery-fallback" style="display:flex;"><i class="fas fa-image"></i><span>${item.file}</span></div></div><div class="${featured?'gallery-featured-info':'gallery-info'}"><h3>${loc.title}</h3><p>${loc.desc}</p>${featured?'':`<span class="gallery-tag">${catLabel}</span>`}</div>`;
const img=card.querySelector('img');tryImageSrc(img,sources,0);
card.addEventListener('click',()=>{if(img.style.display!=='none'&&img.src)openLightbox(img.src,loc.title);});
return card;
}
function renderGallery(){
const grid=document.getElementById('galleryGrid'),featured=document.getElementById('galleryFeatured');
grid.innerHTML='';featured.innerHTML='';featured.style.display='none';loadedRealGalleryCount=0;
const items=GALLERY_ITEMS.filter(it=>galleryFilter==='all'||it.cat===galleryFilter);
items.forEach((item,i)=>{grid.appendChild(buildGalleryCard(item,false));if(i===0){featured.appendChild(buildGalleryCard(item,true));featured.style.display='block';}});
setTimeout(checkGalleryLoaded,900);
}
function renderGalleryFilters(){
const el=document.getElementById('galleryFilters');el.innerHTML='';
[['all','gallery.filter.all'],['analysis','gallery.filter.analysis'],['simulation','gallery.filter.simulation']].forEach(([val,key])=>{
const btn=document.createElement('button');btn.className='gallery-filter'+(galleryFilter===val?' active':'');btn.textContent=t(key);
btn.onclick=()=>{galleryFilter=val;renderGalleryFilters();renderGallery();};el.appendChild(btn);
});
}
function reObserve(){
const obs=new IntersectionObserver(entries=>entries.forEach(e=>{if(e.isIntersecting)e.target.classList.add('visible');}),{threshold:.1});
document.querySelectorAll('.reveal:not(.visible),.reveal-left:not(.visible),.reveal-right:not(.visible)').forEach(el=>obs.observe(el));
applyHoloTilt();
}
function renderFeatures(){
document.getElementById('featuresGrid').innerHTML=FEATURES.map((f,i)=>{const loc=f[currentLang];return`<div class="feature-card holo-card reveal" style="transition-delay:${i*.07}s"><div class="corner-deco tl"></div><div class="corner-deco br"></div><div class="feat-icon"><i class="fas ${f.icon}"></i></div><h3>${loc.t}</h3><p>${loc.d}</p><span class="feature-tag">${f.tag}</span></div>`;}).join('');
reObserve();
}
function renderTabs(){
document.getElementById('tabsGrid').innerHTML=DASHBOARD_TABS.map((tab,i)=>{const loc=tab[currentLang];return`<div class="tab-card reveal" style="transition-delay:${i*.06}s"><i class="fas ${tab.icon}"></i><h3>${loc.t}</h3><p>${loc.d}</p></div>`;}).join('');
reObserve();
}
function renderWorkflow(){
const steps=['usage.s1','usage.s2','usage.s3','usage.s4'];
document.getElementById('workflowGrid').innerHTML=steps.map((k,i)=>`<div class="workflow-step reveal" style="transition-delay:${i*.12}s"><div class="workflow-num">${i+1}</div><h3>${t(k+'.t')}</h3><p>${t(k+'.d')}</p></div>`).join('');
reObserve();
}
function renderInstall(){
const steps=['install.s1','install.s2','install.s3','install.s4'];
document.getElementById('installSteps').innerHTML=steps.map((k,i)=>`<div class="step-card reveal" style="transition-delay:${i*.1}s"><p class="step-number">${String(i+1).padStart(2,'0')}</p><h3>${t(k+'.t')}</h3><p>${t(k+'.d')}</p></div>`).join('');
reObserve();
}
function renderReq(){
document.getElementById('reqGrid').innerHTML=['req.r1','req.r2','req.r3','req.r4'].map((k,i)=>{const icons=['fa-windows','fa-hdd','fa-memory','fa-check-circle'];return`<div class="req-item reveal" style="transition-delay:${i*.08}s"><i class="fas ${icons[i]}"></i><span>${t(k)}</span></div>`;}).join('');
reObserve();
}
function openLightbox(src,caption){document.getElementById('lightboxImg').src=src;document.getElementById('lightboxCaption').textContent=caption;document.getElementById('lightbox').classList.add('open');}
function closeLightbox(e){if(e.target.id==='lightbox'||e.target.classList.contains('lb-close'))document.getElementById('lightbox').classList.remove('open');}
function applyI18nElement(el,key){
if(key==='hero.badge'){el.textContent=badgeText(currentLang);}
else if(HTML_I18N_KEYS.has(key)&&i18n[currentLang][key]){el.innerHTML=i18n[currentLang][key];}
else if(i18n[currentLang][key]){el.textContent=i18n[currentLang][key];}
}
function toggleLang(){
currentLang=currentLang==='fr'?'en':'fr';
document.getElementById('langToggle').textContent=currentLang==='fr'?'EN':'FR';
document.documentElement.lang=currentLang;
document.querySelectorAll('[data-i18n]').forEach(el=>applyI18nElement(el,el.getAttribute('data-i18n')));
renderGalleryFilters();renderGallery();renderFeatures();renderTabs();renderWorkflow();renderInstall();renderReq();
}
document.addEventListener('DOMContentLoaded',()=>{
updateVersionBadge();fetchLatestVersion();
document.querySelectorAll('[data-i18n]').forEach(el=>applyI18nElement(el,el.getAttribute('data-i18n')));
renderGalleryFilters();renderGallery();renderFeatures();renderTabs();renderWorkflow();renderInstall();renderReq();
const fc=document.querySelector('.footer-copy');if(fc)fc.textContent=t('footer.copy');
applyHoloTilt();
});
</script>
</body>
</html>