-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathscripts.js
More file actions
1178 lines (1093 loc) · 46.4 KB
/
Copy pathscripts.js
File metadata and controls
1178 lines (1093 loc) · 46.4 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
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
const members = [
{
name: 'Aleksandra Reja',
image: 'images/member-profiles/ola.webp',
linkedin: 'https://www.linkedin.com/in/aleksandra-reja-a736353a8/',
tags: ['Frontend', 'Web', 'Design']
},
{
name: 'Damian Cybula',
image: 'images/member-profiles/damian.webp',
linkedin: 'https://www.linkedin.com/in/damian-cybula/',
tags: ['AI', 'Hardware', 'Bioinżynieria']
},
{
name: 'Jakub Goleman',
image: 'images/member-profiles/kuba.webp',
linkedin: 'https://www.linkedin.com/in/jakubgoleman/',
tags: ['Mechanika', 'Hardware', 'Pitch']
},
{
name: 'Jan Domański',
image: 'images/member-profiles/janek.webp',
linkedin: 'https://www.linkedin.com/in/jandomanski77/',
tags: ['Biznes', 'Product', 'FinTech']
},
{
name: 'Kacper Gałan',
image: 'images/member-profiles/kacper.webp',
linkedin: 'https://www.linkedin.com/in/kacpergalan/',
tags: ['Data Science', 'ML', 'Analytics']
},
{
name: 'Kamila Bąk',
image: 'images/member-profiles/kamila.webp',
linkedin: 'https://www.linkedin.com/in/kamila-b%C4%85k-376b69397/',
tags: ['Community', 'Content', 'Social Media']
},
{
name: 'Karol Kowal',
image: 'images/member-profiles/karol.webp',
linkedin: 'https://www.linkedin.com/in/kowal-karol/',
tags: ['Data Science', 'ML Ops', 'Events']
},
{
name: 'Kasia Kuczyńska',
image: 'images/member-profiles/kasiak.webp',
linkedin: 'https://www.linkedin.com/in/katarzyna-kuczy%C5%84ska/',
tags: ['Research', 'Community', 'Marketing']
},
{
name: 'Katarzyna Widłak',
image: 'images/member-profiles/kasiaw.webp',
linkedin: 'https://www.linkedin.com/in/katarzyna--wid%C5%82ak/',
tags: ['AI', 'Community', 'Research', 'Elektrotechnika']
},
{
name: 'Krystian Góźdź',
image: 'images/member-profiles/krystian.webp',
linkedin: 'https://www.linkedin.com/in/krystian-gozdz/',
tags: ['Cloud', 'Backend', 'LLM']
},
{
name: 'Maja Sykuła',
image: 'images/member-profiles/maja.webp',
linkedin: 'https://www.linkedin.com/in/maja-syku%C5%82a-6a24493b7/',
tags: ['Events', 'Community', 'Organizacja']
},
{
name: 'Maks Kozieł',
image: 'images/member-profiles/maks.webp',
linkedin: 'https://www.linkedin.com/in/maks-koziel/',
tags: ['AI', 'Growth', 'Strategy']
},
{
name: 'Mikołaj Krzywicki',
image: 'images/member-profiles/mikolaj.webp',
linkedin: 'https://www.linkedin.com/in/miko%C5%82aj-krzywicki/',
tags: ['Cybersec', 'ML', 'Space']
},
{
name: 'Nadia Klimek',
image: 'images/member-profiles/nadia.webp',
linkedin: 'https://www.linkedin.com/in/nadia-klimek-a15652390/',
tags: ['NLP', 'MedTech', 'Chatbot']
},
{
name: 'Piotr Niemiec',
image: 'images/member-profiles/piotrek.webp',
linkedin: 'https://www.linkedin.com/in/piotrniemiec/',
tags: ['Data', 'Operations', 'Mobility']
},
{
name: 'Sandra Zaremba',
image: 'images/member-profiles/sandra.webp',
linkedin: 'https://www.linkedin.com/in/sandra-zaremba-4466a4389/',
tags: ['Design', 'Communications', 'Brand']
},
{
name: 'Szymon Gazdowicz',
image: 'images/member-profiles/szymon.webp',
linkedin: 'https://www.linkedin.com/in/szymon-gazdowicz/',
tags: ['Data Science', 'AgriTech', 'Satellite']
},
{
name: 'Zuzanna Marciniak',
image: 'images/member-profiles/zuzia.webp',
linkedin: 'https://www.linkedin.com/in/zuzanna-marciniak-54391938a/',
tags: ['UX/UI', 'Product', 'Accessibility']
},
{
name: 'Czekamy na Ciebie',
linkedin: '/thanks/',
tags: [],
invite: true
}
];
const projects = [
{
id: 'braidak',
title: 'brAIdak',
category: 'czerwiec 2026',
description: 'Zwycięski projekt asystenta AI dla Urzędu Miasta Lublin. Zero-cloud RAG na modelu Bielik, ChromaDB, funkcjonalność TTS i czytelna mapa POI — suwerenna AI na nieustrukturyzowanych danych miasta.',
image: 'images/projects/braidak-moai-pl.webp',
alt: 'Grafika projektu brAIdak: asystent AI dla Urzędu Miasta Lublin.',
tags: ['GovTech', 'RAG', 'Bielik']
},
{
id: 'banking-innovation',
title: 'Banking Innovation',
category: 'maj 2026',
description: 'Zwycięski projekt 4. edycji SFB. Architektura inteligentnego systemu wykrywania zagrożeń dla klientów banku oraz kierowania celowanych powiadomień do najbardziej narażonych grup.',
image: 'images/projects/banking-innovation-moai-pl.webp',
alt: 'Grafika projektu Banking Innovation: system wykrywania zagrożeń i powiadomień dla klientów banku.',
tags: ['FinTech', 'Cybersec', 'AI', 'ML']
},
{
id: 'obrona-cywilna',
title: 'Obrona Cywilna Nowej Generacji',
category: 'kwiecień 2026',
description: 'Aplikacja webowa LubSHIELD z warstwą dla obywateli i dashbordem zarządzania kryzysowego dla samorządów. Jasny interfejs na czas pokoju i zagrożenia.',
image: 'images/projects/obrona-cywilna-moai-pl.webp',
alt: 'Grafika projektu LubShield: inteligentny geospatial decision dashboard dla Marszałka Województwa Lubelskiego.',
tags: ['GovTech', 'Web', 'Bezpieczeństwo', 'Dual Use']
},
{
id: 'ledger',
title: 'Secure Enclave Wallets',
category: 'kwiecień 2026',
description: 'Projekt z hackathonu Civil42 2026: portfele pre-seedowane i konsola transakcji offline pod scenariusze kryzysowe oraz logistykę terenową — z trybem online, partycji sieci i awaryjnym.',
image: 'images/projects/ledger-moai-pl.webp',
alt: 'Dashboard Secure Enclave Wallets: portfele kryzysowe i konsola transakcji offline.',
tags: ['GovTech', 'Bezpieczeństwo', 'FinTech']
},
{
id: 'moodify',
title: 'Moodify',
category: 'marzec 2026',
description: 'Model rekomendacyjny, który zamienia opis nastroju i wybrany gatunek w spersonalizowaną playlistę. Mood i gatunek trafiają na wejście, na wyjściu — zestaw utworów dopasowany do chwili.',
cover: 'magenta',
tags: ['Muzyka', 'NLP', 'Recsys']
},
{
id: 'inticheck',
title: 'IntiCheck',
category: 'marzec 2026',
description: 'System triage\'u dermatologicznego oparty na sieciach neuronowych. Segreguje zgłoszenia ze zdjęć skóry według ryzyka, a finalną decyzję kliniczną zawsze zostawia lekarzowi.',
cover: 'rose',
tags: ['MedTech', 'Sieci neuronowe', 'Bezpieczeństwo']
},
{
id: 'taxifleet',
title: 'TaxiFleet',
category: 'marzec 2026',
description: 'Platforma do monitoringu i analizy pracy kierowców floty taxi. Zbiera sygnały z każdego kursu i pokazuje odchylenia w zespole, zanim staną się widoczne w rozliczeniach.',
cover: 'gold',
tags: ['Mobility', 'Data', 'ML']
},
{
id: 'planet-twin',
title: 'Cyfrowy bliźniak planety',
category: 'marzec 2026',
description: 'Zwycięski projekt hackathonu Hack4Sages — cyfrowy bliźniak egzoplanety TRAPPIST-1e o nazwie ExoStress Twin. Na danych NASA symuluje w czasie rzeczywistym klimat i transport masy wody, uwzględniając rozbłyski gwiezdne i ich wpływ na biosygnatury.',
cover: 'indigo',
tags: ['Space', 'NASA', 'Symulacja']
},
{
id: 'promo-wz',
title: 'Promocja Wydziału Zarządzania',
category: 'styczeń 2026',
description: 'Prezes MoAI zapoczątkował Zespół ds. promocji Wydziału Zarządzania, złożony ze studentów i pracowników wydziału. Zespół stworzył strategię promocji, kampanie marketingowe i zaktualizuje kanały dotarcia do studentów.',
image: 'images/projects/promo-wz-moai-pl.webp',
alt: 'Materiały zespołu promocji Wydziału Zarządzania Politechniki Lubelskiej.',
tags: ['Marketing', 'Community', 'Uczelnia']
},
{
id: 'bydgo',
title: 'BydGO',
category: 'grudzień 2025',
description: 'Interaktywny przewodnik po Bydgoszczy, przygotowany na HackNation dla Ministerstwa Sportu i Turystyki. Mapa, trasy i statystyki zwiedzania pomagają odkrywać miasto we własnym tempie.',
image: 'images/projects/bydgo-moai-pl.webp',
alt: 'Makieta aplikacji BydGO: mapa Bydgoszczy, trasy i statystyki zwiedzania.',
tags: ['GovTech', 'Turystyka', 'HackNation']
},
{
id: 'ml-admin',
title: 'ML dla administracji publicznej',
category: 'grudzień 2025',
description: 'Legis 2.0 z HackNation: modele ML do analizowania aktów prawnych i pilnowania ich spójności, z warstwą dla urzędnika i obywatela — tłumaczenie przepisów na prosty język bez oddawania danych w obcą chmurę.',
image: 'images/projects/legis-moai-pl.webp',
alt: 'Ekran logowania Legis 2.0: prawo, które Cię rozumie, wejście przez Węzeł Krajowy.',
tags: ['GovTech', 'NLP', 'Prawo']
},
{
id: 'esa-agri',
title: 'Dane satelitarne ESA dla rolnictwa',
category: 'listopad 2025',
description: 'Model analizujący zobrazowania satelitarne Europejskiej Agencji Kosmicznej na potrzeby rolników, firm i agencji rządowych. Wydobywa z danych orbitalnych to, czego nie widać z poziomu pola.',
image: 'images/projects/esa-agri-moai-pl.webp',
alt: 'Slajd projektu AgrAI: wskaźnik NDVI na danych Sentinel-2 i makieta aplikacji z mapą pola.',
tags: ['Space', 'ML', 'AgriTech']
},
{
id: 'slower',
title: 'slower',
category: 'listopad 2025',
description: 'Aplikacja z hackathonu CASSINI: nawigacja slow travellingu na danych Copernicus. Szuka trasy o najniższej emisji CO₂ i mapuje „zielone oazy” zamiast iść wyłącznie najszybszą drogą.',
image: 'images/projects/slower-moai-pl.webp',
alt: 'Makiety aplikacji slower: mapa oaz i eco-route na danych Copernicus wokół Wrocławia.',
tags: ['Space', 'ESA', 'Mobility']
},
{
id: 'jetbrains-co2',
title: 'Monitor CO₂ w JetBrains',
category: 'listopad 2025',
description: 'Wtyczka do środowiska JetBrains, nagrodzona na hackathonie BHL. W czasie rzeczywistym śledzi emisję CO₂ generowaną przez kod, wykrywa nieoptymalne wzorce w Pythonie i podsuwa poprawki z pomocą asystenta AI, zanim energochłonny kod trafi na produkcję.',
image: 'images/projects/jetbrains-co2-moai-pl.webp',
alt: 'Demo wtyczki codeGreen w PyCharm: analiza efektywności kodu i śladu węglowego.',
tags: ['DevTools', 'GreenTech', 'AI']
},
{
id: 'cardioscan',
title: 'CardioScan',
category: 'listopad 2025',
description: 'Narzędzie do analizy szmerów serca na zbiorze CirCor DigiScope — model diagnostyczny z czułością i swoistością rzędu 90%.',
image: 'images/projects/cardioscan-moai-pl.webp',
alt: 'Interfejs CardioScan: terminal diagnostyczny z przykładami przypadków osłuchowych.',
tags: ['MedTech', 'ML', 'Audio']
},
{
id: 'plant-nn',
title: 'Inteligentna uprawa roślin',
category: 'czerwiec 2025',
description: 'Model sieci neuronowej sterujący parametrami uprawy roślin. Zamiast ręcznego strojenia warunków wzrostu, sieć dobiera je samodzielnie na podstawie danych z uprawy — krok w stronę powtarzalnej, innowacyjnej hodowli.',
image: 'images/projects/plant-nn-moai-pl.webp',
alt: 'Kompozycja projektu inteligentnej uprawy: stanowisko z kodem i uprawa bazylii pod lampą LED.',
tags: ['Research', 'ML', 'AgriTech']
},
{
id: 'dynamic-ui',
title: 'Dynamiczny interfejs użytkownika',
category: 'czerwiec 2025',
description: 'Case study dynamicznego interfejsu wspomaganego przez AI, zrealizowane na żywym briefie Comarchu podczas 3. edycji SFB. Warstwa UX/UI dopasowuje się do zachowania użytkownika i prowadzi go przez ścieżkę sprzedaży.',
cover: 'wine',
tags: ['Marketing', 'AI', 'UX/UI', 'Sprzedaż']
},
{
id: 'julia',
title: 'Julia — wirtualny asystent pacjenta',
category: 'czerwiec 2025',
description: 'Julia — wirtualny asystent pacjenta i zwycięski projekt 3. edycji SFB, zrealizowany dla Luxmedu. Umawia wizyty, tłumaczy procedury, przedstawia lekarzy i nawiguje po stronie centrum medycznego.',
image: 'images/projects/julia-moai-pl.webp',
alt: 'Grafika Julii, wirtualnego asystenta pacjenta zrealizowanego dla Luxmedu.',
tags: ['Chatbot', 'MedTech', 'NLP']
},
{
id: 'onhelo',
title: 'OnHelo',
category: 'maj 2024',
description: 'Aplikacja fitness z elementami medtech, zwyciężczyni 2. edycji Studenckiego Festiwalu Biznesu. Modele uczenia maszynowego pilnują poprawnej techniki wykonywania ćwiczeń i ograniczają ryzyko urazu.',
image: 'images/projects/onhelo-moai-pl.webp',
alt: 'Makiety aplikacji OnHelo: konfiguracja profilu, kontakt ze specjalistą, treningi i postępy.',
tags: ['Fitness', 'MedTech', 'ML']
}
];
const events = [
{
id: 'it-unplugged',
title: 'IT Unplugged',
category: 'czerwiec 2026',
description: 'Wzięliśmy udział w konferencji IT Unplugged, słuchając prelekcji o AI, inżynierii danych, architekturze systemów, cyberbezpieczeństwie i wellbeingu zawodowym — i wracając z nowymi kontaktami w branży IT.',
image: 'images/events/it-unplugged-2026-moai-pl.webp',
alt: 'Panel IT Unplugged 2026: trzy osoby na scenie w fotelach, niebieskie oświetlenie i napis IT UNPLUGGED.',
tags: ['Konferencja', 'IT', 'Networking']
},
{
id: 'urban-lab',
title: 'Urban Lab Challenge',
category: 'czerwiec 2026',
description: 'Zajęliśmy 1. miejsce i zdobyliśmy nagrodę główną Urzędu Miasta Lublin. Zbudowaliśmy „brAIdak” — agenta AI na architekturze zero-cloud RAG z modelem Bielik, z głosową dostępnością i mapą punktów POI.',
image: 'images/events/urban-lab-challenge-2026-moai-pl.webp',
alt: 'Zespół MoAI z dyplomem za 1. miejsce w Urban Lab Challenge — Lublin City of Energy.',
tags: ['Hackathon', 'GovTech', 'Zwycięstwo']
},
{
id: 'sfb4',
title: 'Studencki Festiwal Biznesu IV',
category: 'maj 2026',
description: 'Trzy zespoły MoAI wzięły udział w IV edycji SFB, pracując z briefami Starostwa, CEWAR i UniCredit. Trzeci rok z rzędu wygraliśmy — tym razem projektem „Banking Innovation” dla UniCredit.',
image: 'images/events/studencki-festiwal-biznesu-iv-2026-moai-pl.webp',
alt: 'Zespół MoAI i organizatorzy na scenie IV edycji Studenckiego Festiwalu Biznesu z czekiem nagrody 8000 zł.',
tags: ['Biznes', 'Pitch', 'Zwycięstwo']
},
{
id: 'hackology-2',
title: 'Hackology 2',
category: 'maj 2026',
description: 'Dwie drużyny MoAI wzięły udział w hackathonie Hackology 2, podczas którego uczestnicy projektowali rozwiązania przemysłowe z dziedziny przetwarzania obrazów.',
image: 'images/events/hackology-2-2026-moai-pl.webp',
alt: 'Zdjęcie grupowe uczestników Hackology 2 na scenie, w tle partner strategiczny Asseco.',
tags: ['Hackathon', 'Organizacja', 'Partnerstwo']
},
{
id: 'cybermil',
title: 'Finał CTF CyberMil',
category: 'maj 2026',
description: 'Prezes MoAI z międzywydziałowym zespołem reprezentował Politechnikę Lubelską podczas Finału Akademii CyberMil w jednostce MON w Warszawie.',
image: 'images/events/final-ctf-cybermil-2026-moai-pl.webp',
alt: 'Uczestnicy finału CTF Akademii CyberMil 6 maja 2026, w tym osoby w mundurach, na tle slajdu wydarzenia.',
tags: ['Cybersecurity', 'CTF', 'MON']
},
{
id: 'erasmus-startup',
title: 'Erasmus StartUp Projects',
category: 'kwiecień 2026',
description: 'Zaprosiliśmy studentów zagranicznych z programu Erasmus do współpracy nad projektami biznesowo-badawczymi. Stworzyliśmy dwa zespoły, które zaprezentowały swoje pomysły podczas Konferencji StartUp Horizon, gdzie zajęliśmy 1. i 2. miejsce.',
image: 'images/events/erasmus-startup-projects-2026-moai-pl.webp',
alt: 'Sala wykładowa podczas prezentacji zespołu Erasmus StartUp Projects: slajd Our Team i banery Wydziału Zarządzania.',
tags: ['Erasmus', 'Startup', 'Sympozjum']
},
{
id: 'bielik-google',
title: 'Warsztaty Bielik AI × Google',
category: 'kwiecień 2026',
description: 'Jeden z naszych członków poprowadził warsztaty organizowane z Google for Developers i SpeakLeash. Wspólnie uruchomiliśmy infrastrukturę LLM Bielik w Google Cloud, zbudowaliśmy bazę wektorową w BigQuery i przeprowadziliśmy orkiestrację całego systemu RAG.',
image: 'images/events/warsztaty-bielik-ai-google-2026-moai-pl.webp',
alt: 'Uczestnicy warsztatów Bielik AI × Google na tle slajdu Eskadra Bielika.',
tags: ['LLM', 'Google Cloud', 'RAG']
},
{
id: 'civil42-2026',
title: 'Hackathon Civil42 2026',
category: 'kwiecień 2026',
description: 'Wzięliśmy udział w hackathonie obrony cywilnej Civil42, organizowanym przez Instytut 42. Zbudowaliśmy aplikację webową wspierającą obywateli i samorządy w sytuacjach kryzysowych, testując nasze umiejętności w praktyce i poznając ekspertów bezpieczeństwa cywilnego.',
image: 'images/events/hackathon-civil42-2026-moai-pl.webp',
alt: 'Powitanie uczestników hackathonu Civil42 w 2026 roku w przestrzeni Instytutu 42.',
tags: ['Hackathon', 'Cyber', 'Obrona cywilna']
},
{
id: 'dni-otwarte-2026',
title: 'Dni Otwarte Politechniki Lubelskiej 2026',
category: 'kwiecień 2026',
description: 'Przygotowaliśmy stoisko kierunku Sztuczna Inteligencja w Biznesie na Dniach Otwartych PL. Pokazywaliśmy modele AR, śledzenie gestów i edycję obrazów, a przyszłym studentom opowiadaliśmy o studiach i oprowadzaliśmy ich po kampusie.',
image: 'images/events/dni-otwarte-politechniki-lubelskiej-2026-moai-pl.webp',
alt: 'Członkowie MoAI przy stoisku z monitorami na Dniach Otwartych Politechniki Lubelskiej 2026.',
tags: ['Rekrutacja', 'AR', 'Uczelnia']
},
{
id: 'mit-ai-summit',
title: 'MIT AI Summit',
category: 'marzec 2026',
description: 'Reprezentacja MoAI wzięła udział w konferencji MIT AI Summit w Warszawie. Słuchaliśmy ekspertów jak prof. Włodzisław Duch i prof. Piotr Sankowski. Dyskusje o przyszłości AI i budowaniu odpornych systemów zderzyły wiedzę akademicką z realiami branży.',
image: 'images/events/mit-ai-summit-2026-moai-pl.webp',
alt: 'Scena MIT AI Summit z panelem From Lab to Market: Rewiring Polish Science for the 21st Century.',
tags: ['Konferencja', 'AI', 'Warszawa']
},
{
id: 'seminarium',
title: 'Międzywydziałowe Seminarium Naukowe',
category: 'marzec 2026',
description: 'Zaprezentowaliśmy na Międzywydziałowym Seminarium Naukowym trzy projekty: TaxiFleet do zarządzania flotą, IntiCheck do diagnozy zmian skórnych na zdjęciach oraz Moodify do sugerowania treści na podstawie nastroju.',
image: 'images/events/miedzywydzialowe-seminarium-naukowe-2026-moai-pl.webp',
alt: 'Sala podczas Międzywydziałowego Seminarium Naukowego: publiczność i prelegent przy ekranie.',
tags: ['Nauka', 'Prelekcja', 'AI']
},
{
id: 'pollubmy-marketing',
title: 'Pollubmy Marketing',
category: 'marzec 2026',
description: 'Wystąpiliśmy na konferencji Pollubmy Marketing z prelekcją o upstream modelingu z rodziny metod inferencji kauzalnej — jak modele ML pomagają agencjom trafiać w potrzeby biznesowe i ograniczają koszt kampanii.',
image: 'images/events/iv-konferencja-pollubmy-marketing-2026-moai-pl.webp',
alt: 'Aula IV Konferencji Naukowej Marketing Okiem Młodych Naukowców: publiczność i ekran z tytułem wydarzenia.',
tags: ['Konferencja', 'Marketing', 'Causal ML']
},
{
id: 'ensemble-warsaw',
title: 'Ensemble AI — Warszawa',
category: 'marzec 2026',
description: 'Rozwiązaliśmy wszystkie zadania hackathonu Ensemble AI w Warszawie i zajęliśmy 6. miejsce na blisko 50 zespołów, awansując do etapu prezentacji finałowych. Tematy obejmowały ML w diagnostyce medycznej, biologię molekularną i bezpieczeństwo dużych modeli językowych.',
image: 'images/events/ensemble-ai-warszawa-2026-moai-pl.webp',
alt: 'Zespół MoAI na scenie Ensemble AI w Warszawie podczas prezentacji finałowej.',
tags: ['Hackathon', 'MedTech', 'LLM']
},
{
id: 'hack4sages',
title: 'Hack4Sages',
category: 'marzec 2026',
description: 'Zwyciężyliśmy w międzynarodowym Hack4Sages wśród ponad 200 uczestników z 6 kontynentów. Nasz cyfrowy bliźniak planety TRAPPIST-1e wyprzedził zespoły z Harvardu i Cambridge, a we wrześniu prezentujemy go na konferencji Origins Federation w ETH Zürich.',
cover: 'ice',
tags: ['Hackathon', 'Space', 'Zwycięstwo']
},
{
id: 'brave-community',
title: 'II spotkanie Brave Community',
category: 'marzec 2026',
description: 'Nawiązaliśmy współpracę z lubelską społecznością Brave Community podczas jej II spotkania. Regularnie bierzemy udział w kolejnych edycjach i szykujemy własne wystąpienie na jedno z najbliższych spotkań.',
image: 'images/events/ii-spotkanie-brave-community-2026-moai-pl.webp',
alt: 'Uczestnicy II spotkania Brave Community na stopniach auli, zdjęcie grupowe.',
tags: ['Community', 'Lublin', 'Networking']
},
{
id: 'polska-siega-gwiazd',
title: 'Polska Sięga Gwiazd',
category: 'grudzień 2025',
description: 'Wzięliśmy udział w spotkaniu ze Sławoszem Uznańskim, astronautą misji IGNIS, gdzie mogliśmy zadać mu pytania na żywo. Po wydarzeniu poznaliśmy przedstawicieli innych kół naukowych i instytucji partnerskich.',
image: 'images/events/polska-siega-gwiazd-2025-moai-pl.webp',
alt: 'Sławosz Uznański w kombinezonie lotniczym na scenie spotkania Polska Sięga Gwiazd.',
tags: ['Space', 'Spotkanie', 'Networking']
},
{
id: 'hacknation',
title: 'Hackathon HackNation',
category: 'grudzień 2025',
description: 'Zmierzyliśmy się z 16 zadaniami w 24 godziny na HackNation w Bydgoszczy, organizowanym przez Ministerstwo Cyfryzacji, MF, MON i MNiSW. Powstały m.in. BydGO dla Ministerstwa Sportu i Turystyki oraz Legis 2.0 do analizy aktów prawnych.',
image: 'images/events/hackathon-hacknation-2025-moai-pl.webp',
alt: 'Zespół MoAI na scenie HackNation z czerwonymi tablicami układającymi napis HACK NATION.',
tags: ['Hackathon', 'GovTech', 'ML']
},
{
id: 'hackathon-google',
title: 'Hackathon Google',
category: 'grudzień 2025',
description: 'Wzięliśmy udział w hackathonie Google w Warszawie, prototypując rozwiązania z mentorami w krótkich, intensywnych sprintach zespołowych nad realnymi problemami biznesowymi.',
image: 'images/events/hackathon-google-2025-moai-pl.webp',
alt: 'Zespół MoAI na Hackathonie Google w Warszawie, grudzień 2025.',
tags: ['Hackathon', 'AI', 'Google']
},
{
id: 'akademia-inzynierska',
title: '46. Zgromadzenie Akademii Inżynierskiej',
category: 'grudzień 2025',
description: 'Podczas 46. Zgromadzenia Akademii Inżynierskiej zaprezentowaliśmy dotychczasowe osiągnięcia i plany MoAI, reprezentując Politechnikę Lubelską. Mieliśmy też okazję zobaczyć na żywo technologie prezentowane przez inne koła naukowe.',
image: 'images/events/46-zgromadzenie-akademii-inzynierskiej-2025-moai-pl.webp',
alt: 'Prezentacja koła MoAI z slajdem we are MoAI, LinkedIn i Instagram podczas zgromadzenia Akademii Inżynierskiej.',
tags: ['Nauka', 'Prestige', 'Uczelnia']
},
{
id: 'bhl',
title: 'Finał hackathonu BHL',
category: 'listopad 2025',
description: 'Zdobyliśmy 3. miejsce w kategorii AI na 11. edycji hackathonu BHL w Warszawie. Zbudowaliśmy wtyczkę do JetBrains, która śledzi emisję CO₂ generowaną przez kod i podsuwa poprawki z pomocą AI.',
image: 'images/events/final-hackathonu-bhl-2025-moai-pl.webp',
alt: 'Zespół MoAI z dyplomami i torbami na scenie finału hackathonu BHL.',
tags: ['Hackathon', 'GreenTech', 'Finał']
},
{
id: 'cassini',
title: 'Hackathon CASSINI',
category: 'listopad 2025',
description: 'Siedmioosobowy zespół MoAI spędził trzy dni na hackathonie CASSINI we Wrocławiu, szukając zastosowań danych satelitarnych ESA. Zbudowaliśmy aplikację redefiniującą koncepcję slow travellingu — spersonalizowane trasy wzmocnione danymi prosto z orbity.',
image: 'images/events/hackathon-cassini-2025-moai-pl.webp',
alt: 'Zespół MoAI na hackathonie CASSINI we Wrocławiu.',
tags: ['Hackathon', 'Space', 'ESA']
},
{
id: 'targi-kol',
title: 'IV Targi Kół Naukowych PL',
category: 'październik 2025',
description: 'Na IV Targach Kół Naukowych PL pokazywaliśmy modele generujące światy gier wideo oraz Stable Diffusion do tworzenia obrazów — promując AI i ML wśród studentów i uczniów szkół ponadpodstawowych.',
image: 'images/events/iv-targi-kol-naukowych-pl-2025-moai-pl.webp',
alt: 'Stoisko MoAI na IV Targach Kół Naukowych Politechniki Lubelskiej, październik 2025.',
tags: ['Uczelnia', 'ML', 'Pokazy']
},
{
id: 'warsztaty-matematyczne',
title: 'Warsztaty matematyczne dla nowych studentów',
category: 'październik 2025',
description: 'Zorganizowaliśmy warsztaty matematyczne dla nowych studentów — wspólnie przypomnieliśmy podstawy, żeby łatwiej wystartować na uczelni i przygotować się do pierwszych kolokwiów.',
image: 'images/events/warsztaty-matematyczne-dla-nowych-studentow-2025-moai-pl.webp',
alt: 'Warsztaty matematyczne MoAI: studenci przy ławkach, prowadzący przy zielonej tablicy.',
tags: ['Warsztaty', 'Matematyka', 'Uczelnia']
},
{
id: 'sfb3',
title: 'Studencki Festiwal Biznesu III',
category: 'czerwiec 2025',
description: 'Znów wygraliśmy Studencki Festiwal Biznesu — jeden zespół zaprezentował chatbota Julię dla Luxmedu, a drugi równolegle pracował na żywym briefie Comarchu nad dynamicznym interfejsem wspomaganym AI.',
image: 'images/events/studencki-festiwal-biznesu-iii-2025-moai-pl.webp',
alt: 'Zespół MoAI na III edycji Studenckiego Festiwalu Biznesu, na tle napisu Festiwal Biznesu.',
tags: ['Biznes', 'Pitch', 'Zwycięstwo']
},
{
id: 'ghostday',
title: 'GhostDay 2025',
category: 'maj 2025',
description: 'Pojechaliśmy na konferencję GhostDay: Applied Machine Learning w Poznaniu, żeby posłuchać ekspertów i wymienić doświadczenia z innymi entuzjastami ML. Wróciliśmy pełni nowych pomysłów i kontaktów.',
image: 'images/events/ghostday-2025-moai-pl.webp',
alt: 'Scena GhostDay 2025: prelegent przy slajdzie z wzorami, obok baner Ghost Day.',
tags: ['Konferencja', 'AI', 'Networking']
},
{
id: 'spaceshield',
title: 'Hackathon SpaceShield',
category: 'maj 2025',
description: 'Dotarliśmy do finału hackathonu SpaceShield w Stalowej Woli. Nasz projekt TITAN wzmacniał odporność systemów łączności kryzysowej na zakłócenia i podsłuch.',
image: 'images/events/hackathon-spaceshield-2025-moai-pl.webp',
alt: 'Zespół MoAI na finale hackathonu SpaceShield w Stalowej Woli.',
tags: ['Hackathon', 'Space', 'Finał']
},
{
id: 'civil42-2025',
title: 'Hackathon Civil42 2025',
category: 'maj 2025',
description: 'Wzięliśmy udział w hackathonie Civil42, budując aplikację webową wspierającą obywateli i samorządy w sytuacjach kryzysowych.',
image: 'images/events/hackathon-civil42-2025-moai-pl.webp',
alt: 'Uczestnicy hackathonu Civil42 w maju 2025 w przestrzeni Instytutu 42.',
tags: ['Hackathon', 'Cyber', 'Obrona cywilna']
},
{
id: 'dni-otwarte-2025',
title: 'Dni Otwarte Politechniki Lubelskiej 2025',
category: 'kwiecień 2025',
description: 'Postawiliśmy stoisko na Dniach Otwartych PL — pokazywaliśmy nasze projekty, rozmawialiśmy z kandydatami i zapraszaliśmy do koła.',
image: 'images/events/dni-otwarte-politechniki-lubelskiej-2025-moai-pl.webp',
alt: 'Korytarz Politechniki Lubelskiej podczas Dni Otwartych 2025, stoisko i rozmowy z kandydatami.',
tags: ['Rekrutacja', 'Uczelnia', 'Pokazy']
},
{
id: 'ensemble-krakow',
title: 'Ensemble AI — Kraków',
category: 'marzec 2025',
description: 'Awansowaliśmy do finału Ensemble AI w Krakowie, zajmując 4. miejsce w rankingu na zadaniach przygotowanych przez CISPA Helmholtz Center.',
image: 'images/events/ensemble-ai-krakow-2025-moai-pl.webp',
alt: 'Zespół MoAI na międzynarodowym hackathonie Ensemble AI w Krakowie.',
tags: ['Hackathon', 'AI', 'Finał']
},
{
id: 'sfb2',
title: 'Studencki Festiwal Biznesu II',
category: 'maj 2024',
description: 'Zdobyliśmy 1. miejsce w II edycji Studenckiego Festiwalu Biznesu z aplikacją fitness OnHelo — pierwszy duży sukces MoAI na scenie biznesowej.',
image: 'images/events/studencki-festiwal-biznesu-ii-2024-moai-pl.webp',
alt: 'Zespół MoAI z czekiem 5000 zł na II edycji Studenckiego Festiwalu Biznesu, 24 maja 2024.',
tags: ['Biznes', 'Pitch', 'Współpraca']
},
{
id: 'sympozjum-xxxii',
title: 'XXXII Sympozjum Naukowe',
category: 'maj 2024',
description: 'Wystąpiliśmy na XXXII Międzynarodowym Sympozjum Naukowym „AI – szansa czy zagrożenie?”, prezentując kompetencje koła i nasze projekty badawcze przed społecznością akademicką Politechniki Lubelskiej.',
image: 'images/events/xxxii-miedzynarodowe-sympozjum-naukowe-2024-moai-pl.webp',
alt: 'Aula XXXII Międzynarodowego Sympozjum Naukowego na Politechnice Lubelskiej, 15–17 maja 2024: ekran z tytułem AI – szansa czy zagrożenie?',
tags: ['Nauka', 'Prelekcja', 'Uczelnia']
},
{
id: 'pip',
title: 'Konferencja 105-lecia Państwowej Inspekcji Pracy',
category: 'październik 2024',
description: 'Wystąpiliśmy na konferencji 105-lecia Państwowej Inspekcji Pracy z referatem o AI na rynku pracy — o wyzwaniach regulacyjnych i szansach, jakie ta technologia otwiera przed pracownikami i instytucjami.',
image: 'images/events/konferencja-105-lecia-pip-2024-moai-pl.webp',
alt: 'Wystąpienie zespołu MoAI na konferencji 105-lecia Państwowej Inspekcji Pracy.',
tags: ['Konferencja', 'Prawo', 'Rynek pracy']
}
];
const qs = (sel) => document.querySelector(sel);
const qsa = (sel) => Array.from(document.querySelectorAll(sel));
const basePath = document.querySelector('script[src*="../scripts.js"]') ? '../' : './';
function shuffle(array) {
const copy = [...array];
for (let i = copy.length - 1; i > 0; i -= 1) {
const j = Math.floor(Math.random() * (i + 1));
[copy[i], copy[j]] = [copy[j], copy[i]];
}
return copy;
}
function createInviteCover() {
const cover = document.createElement('div');
cover.className = 'member-card__invite-cover';
cover.setAttribute('aria-hidden', 'true');
cover.innerHTML = `
<span class="member-card__invite-blob member-card__invite-blob--a"></span>
<span class="member-card__invite-blob member-card__invite-blob--b"></span>
<span class="member-card__invite-blob member-card__invite-blob--c"></span>
<span class="member-card__invite-glass"></span>
<svg class="invite-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round">
<g class="invite-icon__avatar">
<path d="M8 7a4 4 0 1 0 8 0a4 4 0 0 0 -8 0" />
<path d="M6 21v-2a4 4 0 0 1 4 -4h4" />
</g>
<g transform="translate(12.05 11.85) scale(0.62)">
<g class="invite-icon__q">
<path class="invite-icon__q-mark" pathLength="1" stroke-width="2.9" d="M8 8a3.5 3 0 0 1 3.5 -3h1a3.5 3 0 0 1 3.5 3a3 3 0 0 1 -2 3a3 4 0 0 0 -2 4" />
<path class="invite-icon__q-dot" pathLength="1" stroke-width="3.6" d="M12 19l0 .01" />
</g>
</g>
</svg>
`;
return cover;
}
function renderMembers(targetId, {
limit = members.length,
includeCta = false,
randomize = false,
pinInviteLast = false,
includeInvite = true
} = {}) {
const target = qs(`#${targetId}`);
if (!target) return;
let list;
if (pinInviteLast) {
const inviteMember = includeInvite ? members.find((member) => member.invite) : null;
const regularMembers = members.filter((member) => !member.invite);
const source = randomize ? shuffle(regularMembers) : regularMembers;
const regularLimit = Math.max(0, limit - (inviteMember ? 1 : 0));
list = source.slice(0, regularLimit);
if (inviteMember && list.length < limit) {
list.push(inviteMember);
}
} else {
const pool = includeInvite ? members : members.filter((member) => !member.invite);
const source = randomize ? shuffle(pool) : pool;
list = source.slice(0, limit);
}
target.innerHTML = '';
list.forEach((member) => {
const card = document.createElement('article');
card.className = 'member-card fade-in';
const media = document.createElement('div');
media.className = 'member-card__media';
const body = document.createElement('div');
body.className = 'member-card__body';
const name = document.createElement('p');
name.className = 'member-card__name';
if (member.invite) {
name.innerHTML = MoAI.t('invite.name');
} else {
name.textContent = member.name;
}
body.appendChild(name);
if (member.tags?.length) {
const tagsWrap = document.createElement('div');
tagsWrap.className = 'tags';
member.tags.forEach((tag) => {
const chip = document.createElement('span');
chip.className = 'tag';
chip.textContent = MoAI.translateTag(tag);
tagsWrap.appendChild(chip);
});
body.appendChild(tagsWrap);
}
const overlay = document.createElement('a');
overlay.className = 'member-card__link';
overlay.href = member.linkedin;
overlay.target = '_blank';
overlay.rel = 'noopener';
overlay.ariaLabel = `LinkedIn ${member.name}`;
overlay.innerHTML = `<span><img class="member-card__link-icon" src="${basePath}icons/linkedin.svg" alt="" aria-hidden="true"> LinkedIn</span>`;
if (member.invite) {
card.classList.add('member-card--invite');
overlay.removeAttribute('target');
overlay.removeAttribute('rel');
overlay.ariaLabel = MoAI.t('invite.cta');
overlay.innerHTML = `<span>${MoAI.t('invite.cta')}</span>`;
media.append(createInviteCover(), overlay);
} else {
const img = document.createElement('img');
img.className = 'member-card__photo';
img.src = basePath + member.image;
img.alt = member.name;
img.loading = 'lazy';
img.draggable = false;
img.setAttribute('data-protect', 'true');
media.append(img, overlay);
}
card.append(media, body);
target.appendChild(card);
});
if (includeCta) {
const cta = document.createElement('article');
cta.className = 'member-card member-card--cta fade-in';
const body = document.createElement('div');
body.className = 'member-card__body';
const title = document.createElement('p');
title.className = 'member-card__name';
title.textContent = MoAI.t('members.ctaTitle');
const copy = document.createElement('p');
copy.className = 'muted';
copy.textContent = MoAI.t('members.ctaCopy');
const btn = document.createElement('a');
btn.className = 'btn btn--primary';
btn.textContent = MoAI.t('members.ctaBtn');
btn.href = '/members/';
body.append(title, copy, btn);
cta.append(body);
target.appendChild(cta);
}
}
function appendCardMedia(media, item) {
if (item.image) {
const img = document.createElement('img');
img.src = basePath + item.image;
img.alt = item.alt || item.title;
img.loading = 'lazy';
img.draggable = false;
img.setAttribute('data-protect', 'true');
media.appendChild(img);
return;
}
const cover = document.createElement('div');
cover.className = 'project-card__cover';
cover.dataset.cover = item.cover || 'azure';
cover.setAttribute('aria-hidden', 'true');
['a', 'b', 'c'].forEach((key) => {
const blob = document.createElement('span');
blob.className = `project-card__blob project-card__blob--${key}`;
cover.appendChild(blob);
});
const sheen = document.createElement('span');
sheen.className = 'project-card__cover-glass';
cover.appendChild(sheen);
media.appendChild(cover);
}
function createProjectCard(project, { showTags = false, linkLabel, showCategory = true, kind = 'project' } = {}) {
const item = MoAI.localizeEntry(project, kind);
const card = document.createElement('article');
card.className = 'project-card fade-in';
const media = document.createElement('div');
media.className = 'project-card__media';
appendCardMedia(media, item);
const body = document.createElement('div');
body.className = 'project-card__body';
const hasDisplayCategory = showCategory
&& typeof item.category === 'string'
&& item.category.trim().toLowerCase() !== 'repo';
const title = document.createElement('h3');
title.className = 'project-card__title';
title.textContent = item.title;
const desc = document.createElement('p');
desc.className = 'project-card__desc';
desc.textContent = item.description;
if (hasDisplayCategory) {
const meta = document.createElement('div');
meta.className = 'project-card__meta';
meta.textContent = item.category;
body.append(meta, title, desc);
} else {
body.append(title, desc);
}
if (showTags && item.tags?.length) {
const tagsWrap = document.createElement('div');
tagsWrap.className = 'tags project-card__tags';
item.tags.forEach((tag) => {
const chip = document.createElement('span');
chip.className = 'tag';
chip.textContent = tag;
tagsWrap.appendChild(chip);
});
body.appendChild(tagsWrap);
}
if (item.github) {
const link = document.createElement('a');
link.className = 'btn btn--ghost project-card__cta';
link.href = item.github;
link.target = '_blank';
link.rel = 'noopener';
link.textContent = item.github.replace('https://', '');
body.appendChild(link);
}
card.append(media, body);
return card;
}
function hasMainImage(item) {
return typeof item?.image === 'string' && item.image.trim() !== '';
}
const PINNED_PROJECTS = ['plant-nn', 'slower', 'promo-wz'];
const PINNED_EVENTS = ['urban-lab', 'hacknation', 'sfb3'];
function withPinnedFirst(data, pinnedIds = []) {
if (!pinnedIds.length) return data;
const pinSet = new Set(pinnedIds);
const pinned = pinnedIds
.map((id) => data.find((item) => item.id === id))
.filter(Boolean);
const rest = data.filter((item) => !pinSet.has(item.id));
return [...pinned, ...rest];
}
function pickHomepageCards(data, count, { random = false, requireImage = false, pinnedIds = [] } = {}) {
const ordered = withPinnedFirst(data, pinnedIds);
if (pinnedIds.length) return ordered.slice(0, count);
const pool = requireImage ? ordered.filter(hasMainImage) : ordered;
if (!random) return pool.slice(0, count);
if (requireImage) return shuffle(pool).slice(0, count);
const withImage = [];
const withoutImage = [];
pool.forEach((item) => {
if (hasMainImage(item)) withImage.push(item);
else withoutImage.push(item);
});
return [...shuffle(withImage), ...shuffle(withoutImage)].slice(0, count);
}
function renderProjects(targetId, data, { count = data.length, random = false, showTags = false, linkLabel, showCategory = true, requireImage = false, kind = 'project', pinnedIds = [] } = {}) {
const target = qs(`#${targetId}`);
if (!target || !data?.length) return;
const slice = pickHomepageCards(data, count, { random, requireImage, pinnedIds });
target.innerHTML = '';
slice.forEach((project) => target.appendChild(createProjectCard(project, { showTags, linkLabel, showCategory, kind })));
}
function renderListPage(targetId, data, heading = 'Lista', showMeta = true) {
const target = qs(`#${targetId}`);
if (!target) return;
target.innerHTML = '';
data.forEach((item) => {
const row = document.createElement('article');
row.className = 'list-item fade-in';
const title = document.createElement('h3');
title.className = 'list-item__title';
title.textContent = item.title;
const rawMeta = item.category || heading;
const hasDisplayMeta = showMeta
&& typeof rawMeta === 'string'
&& rawMeta.trim().toLowerCase() !== 'repo';
const desc = document.createElement('p');
desc.className = 'list-item__desc';
desc.textContent = item.description;
row.append(title);
if (hasDisplayMeta) {
const meta = document.createElement('p');
meta.className = 'list-item__meta';
meta.textContent = rawMeta;
row.appendChild(meta);
}
row.appendChild(desc);
if (item.tags?.length) {
const tagsWrap = document.createElement('div');
tagsWrap.className = 'tags';
item.tags.forEach((tag) => {
const chip = document.createElement('span');
chip.className = 'tag';
chip.textContent = tag;
tagsWrap.appendChild(chip);
});
row.appendChild(tagsWrap);
}
if (item.github) {
const link = document.createElement('a');
link.className = 'btn btn--ghost project-card__cta';
link.href = item.github;
link.target = '_blank';
link.rel = 'noopener';
link.textContent = item.github.replace('https://', '');
row.appendChild(link);
}
target.appendChild(row);
});
}
function handlePreloader() {
const loader = qs('#preloader');
if (!loader) return;
window.addEventListener('load', () => {
setTimeout(() => loader.classList.add('hidden'), 250);
});
}
function blockInteractions() {
const targets = qsa('[data-protect], .hero__image');
targets.forEach((el) => {
el.addEventListener('contextmenu', (e) => e.preventDefault());
el.addEventListener('dragstart', (e) => e.preventDefault());
});
document.addEventListener('contextmenu', (e) => {
const allow = e.target.closest('h1, h2, h3, h4, p, .member-card__name, .list-item__title, .list-item__desc, .list-item__meta, a[href^="mailto"], .selectable, .selectable-text');
if (allow) return;
e.preventDefault();
});
}
function observeFadeIns() {
const observer = new IntersectionObserver((entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
entry.target.classList.add('visible');
observer.unobserve(entry.target);
}
});
}, { threshold: 0.15 });
qsa('.fade-in').forEach((el) => observer.observe(el));
}
function updateFooterYear() {
const currentYear = String(new Date().getFullYear());
qsa('[data-current-year]').forEach((el) => {
el.textContent = currentYear;
});
}
const gapAlignedNavHashes = new Set(['#events', '#projects', '#join']);
function clampScrollTop(top) {
const maxTop = Math.max(0, document.documentElement.scrollHeight - window.innerHeight);
return Math.min(Math.max(0, top), maxTop);
}
function smoothScrollToPosition(top) {
window.scrollTo({ top: clampScrollTop(top), behavior: 'smooth' });
}
function getNavMetrics() {
const nav = qs('.nav');
if (!nav) return null;
const rect = nav.getBoundingClientRect();
return {
top: rect.top,
bottom: rect.bottom,
height: rect.height,
center: rect.top + (rect.height / 2)
};
}
function getSectionFromHash(hash) {
if (!hash || !hash.startsWith('#')) return null;
const target = qs(hash);
if (!target) return null;
return target.classList.contains('section') ? target : target.closest('.section');
}
function getDefaultSectionTop(section) {
const nav = getNavMetrics();
const sectionTop = window.scrollY + section.getBoundingClientRect().top;
if (!nav) return sectionTop;
return sectionTop - nav.bottom - 8;
}
function getGapAlignedSectionTop(section) {
const nav = getNavMetrics();
const sections = qsa('.page .section');
const sectionIndex = sections.indexOf(section);
if (!nav || sectionIndex <= 0) return getDefaultSectionTop(section);
const previousSection = sections[sectionIndex - 1];
const previousBottom = window.scrollY + previousSection.getBoundingClientRect().bottom;
const currentTop = window.scrollY + section.getBoundingClientRect().top;
const gap = currentTop - previousBottom;
if (gap > nav.height + 8) {
return previousBottom + (gap / 2) - nav.center;
}