Sebastiankay commited on
Commit
76f8171
·
verified ·
1 Parent(s): 5c3de31

Update templates/map.html

Browse files
Files changed (1) hide show
  1. templates/map.html +93 -1162
templates/map.html CHANGED
@@ -1,1170 +1,101 @@
1
  <!DOCTYPE html>
2
- <html lang="en">
3
-
4
- <head>
5
- <meta charset="UTF-8" />
6
- <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
-
8
- <!--title>Tarkov Map with friends</title-->
9
- <title>{{ maps[0].name }} | Map with friends</title>
10
- <link rel="icon" type="image/x-icon" href="/static/favicon.ico" />
11
- <link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.3/dist/leaflet.css" />
12
- <!--link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/leaflet-groupedlayercontrol/0.6.1/leaflet.groupedlayercontrol.css"/-->
13
- <script src="https://unpkg.com/leaflet@1.9.3/dist/leaflet.js"></script>
14
- <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.7.2/css/all.min.css" />
15
- <!--link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bulma@1.0.2/css/bulma.min.css" /-->
16
- <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.7/dist/css/bootstrap.min.css" rel="stylesheet">
17
- <link rel="stylesheet" href="/static/style.css" />
18
- </head>
19
-
20
- <body data-bs-theme="dark">
21
- <nav class="navbar navbar-expand-sm navbar-dark fixed-top">
22
- <div class="container-fluid">
23
- <a class="navbar-brand" href="#">Tarkov Map</a>
24
- <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNavDropdown" aria-controls="navbarNavDropdown" aria-expanded="false" aria-label="Toggle navigation">
25
- <span class="navbar-toggler-icon"></span>
26
- </button>
27
- <div class="collapse navbar-collapse" id="navbarNavDropdown">
28
- <ul class="navbar-nav">
29
- <li class="nav-item">
30
- <a class="nav-link active" aria-current="page" href="#">Home</a>
31
- </li>
32
- <li class="nav-item dropdown">
33
- <a class="nav-link dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown" aria-expanded="false">
34
- Maps
35
- </a>
36
- <ul class="dropdown-menu">
37
- <li><a href="/map/woods" class="dropdown-item">Woods</a></li>
38
- <li><a href="/map/shoreline" class="dropdown-item">Shoreline</a></li>
39
- <li><a href="/map/lighthouse" class="dropdown-item">Lighthouse</a></li>
40
- <li><a href="/map/customs" class="dropdown-item">Customs</a></li>
41
- <li><a href="/map/interchange" class="dropdown-item">Interchange</a></li>
42
- <li><a href="/map/streets-of-tarkov" class="dropdown-item">Streets of Tarkov</a></li>
43
- <li><a href="/map/ground-zero" class="dropdown-item">Ground Zero</a></li>
44
- <li><a href="/map/reserve" class="dropdown-item">Reserve</a></li>
45
- <li><a href="/map/factory" class="dropdown-item">Factory</a></li>
46
- </ul>
47
- </li>
48
- </ul>
49
- </div>
50
- </div>
51
- </nav>
52
- <!--nav class="navbar is-fixed-top" role="navigation" aria-label="main navigation">
53
- <div class="navbar-brand">
54
- Tarkov Map
55
- </div>
56
-
57
- <div id="navbarBasicExample" class="navbar-menu">
58
- <div class="navbar-start">
59
- <a class="navbar-item">
60
- Home
61
- </a>
62
- <div class="navbar-item has-dropdown is-hoverable">
63
- <a class="navbar-link">Maps</a>
64
- <div class="navbar-dropdown is-boxed">
65
-
66
  </div>
67
- </div>
68
- </div>
69
-
70
- <div class="navbar-end">
71
- <div class="navbar-item">
72
- <button class="button is-dark js-modal-trigger" data-target="settings-modal">
73
- <span class="icon is-small">
74
- <i class="fas fa-cog"></i>
75
- </span>
76
  </button>
77
  </div>
78
  </div>
79
- </div>
80
- </nav-->
81
-
82
- <div id="map"></div>
83
-
84
-
85
- <div id="settings-modal" class="modal">
86
- <div class="modal-background"></div>
87
-
88
- <div class="modal-content container">
89
- <div class="box">
90
- <section class="hero">
91
- <div class="hero-body">
92
- <p class="title has-text-weight-bold">EINSTELLUNGEN</p>
93
- <hr class="title-line">
94
- <p class="subtitle">Subtitle</p>
95
- </div>
96
- </section>
97
- <!-- Your content -->
98
- <section class="section">
99
- <div class="content">
100
- <p>
101
- Hier die Einstellungen basierend auf der config.ini file.
102
-
103
- {#
104
- [mainconfig]
105
- eft_log_older_path = E:/Battlestate Games/Escape from Tarkov/Logs/
106
- eft_screenshots_folder_path = C:/Users/Sebastian/Documents/Escape from Tarkov/Screenshots/
107
- delete_screenshots = True
108
- eft_show_exits_key = o
109
- eft_take_screenshot_key = del #}
110
- </p>
111
- </div>
112
- </section>
113
  </div>
114
  </div>
115
 
116
- <button class="modal-close is-large" aria-label="close"></button>
117
- </div>
118
-
119
- <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.7/dist/js/bootstrap.bundle.min.js"></script>
120
- <script src="/static/js/leaflet.controlGroupedlayer.js"></script>
121
- <script src="/static/js/leaflet.controlCoordinates.js"></script>
122
- <script>
123
- // GLOBAL VARIABLE TO HOLD THE CURRENT MARKER
124
- const playerMarkers = {}
125
- let currentMarker = null
126
- const client_id = Date.now()
127
- const mapData = {{ maps[0] | tojson }}
128
- console.info(mapData)
129
- //const transform = [0.1855, 113.1, 0.1855, 167.8]
130
- const transform = mapData.transform
131
- const bounds = mapData.bounds
132
- const svgBounds = mapData.svgBounds ? mapData.svgBounds : bounds
133
- const coordinateRotation = mapData.coordinateRotation ? mapData.coordinateRotation : 0
134
- const svgPath = mapData.svgPath
135
- const imageUrl = mapData.svgPath
136
- const minZoom = mapData.minZoom ? mapData.minZoom : 1
137
- const maxZoom = mapData.maxZoom ? mapData.maxZoom : 6
138
- const showElevation = false;
139
- const showStaticMarkers = false
140
- const wsgroup = "1234"
141
-
142
- let currentZoom = 3;
143
-
144
- //MARK: INIT WEBSOCKET
145
- // Initialize WebSocket connection
146
- const ws = new WebSocket(`wss://sebastiankay-eft-group-map-websocket.hf.space/ws`);
147
-
148
- // WebSocket event handlers
149
- ws.onmessage = function (event) {
150
- console.log(event.data);
151
- const data = JSON.parse(event.data);
152
-
153
- switch (data.type) {
154
- case "coordinates":
155
- const parsedData = data.data;
156
- localStorage.setItem("last_marker", JSON.stringify(parsedData));
157
- loadLocalData();
158
- break;
159
-
160
- case "location_map":
161
- const map_name = data.data.map.toLowerCase().replaceAll(" ", "-");
162
- localStorage.setItem("last_map_name", map_name);
163
- localStorage.removeItem("last_marker");
164
- removeAllMarkers(); // Clear all markers when the map changes
165
- if (!location.pathname.includes(map_name)) {
166
- location.pathname = `/map/${map_name}`;
167
- } else {
168
- location.reload();
169
- }
170
- break;
171
-
172
- case "new_rade_data":
173
- console.log(data.data);
174
- const radeData = JSON.parse(data.data);
175
- localStorage.setItem("rade_data", JSON.stringify(radeData));
176
- loadLocalData();
177
- break;
178
- }
179
- };
180
-
181
- ws.onopen = function (event) {
182
- ws.send(JSON.stringify({ type: "join", group: wsgroup }));
183
- };
184
-
185
- // Function to remove all markers
186
- function removeAllMarkers() {
187
- for (const playername in playerMarkers) {
188
- if (playerMarkers.hasOwnProperty(playername)) {
189
- map.removeLayer(playerMarkers[playername]);
190
- }
191
- }
192
- Object.keys(playerMarkers).forEach(key => delete playerMarkers[key]);
193
- console.log("Alle Marker wurden entfernt.");
194
- }
195
-
196
- // Function to load and display local marker data
197
- function loadLocalData() {
198
- const map_name = localStorage.getItem("last_map_name");
199
- if (map_name) {
200
- if (!location.pathname.includes(map_name)) {
201
- localStorage.removeItem("last_marker");
202
- }
203
- } else {
204
- localStorage.removeItem("last_marker");
205
- }
206
-
207
- const markerData = localStorage.getItem("last_marker");
208
- if (markerData) {
209
- const parsedData = JSON.parse(markerData);
210
- // Use the new addMarker function with all required parameters
211
- addMarker(
212
- parsedData.x,
213
- parsedData.y,
214
- parsedData.z,
215
- parsedData.timestamp,
216
- parsedData.preview || false,
217
- parsedData.actualmap || "Unbekannte Map",
218
- parsedData.playername || "Unnamed Player",
219
- parsedData.markercolor || false
220
- );
221
- }
222
- }
223
-
224
- const images = {
225
- 'container_bank-cash-register': 'container_cash-register',
226
- 'container_bank-safe': 'container_safe',
227
- 'container_buried-barrel-cache': 'container_buried-barrel-cache',
228
- 'container_cash-register': 'container_cash-register',
229
- 'container_cash-register-tar2-2': 'container_cash-register',
230
- 'container_dead-civilian': 'container_dead-scav',
231
- 'container_dead-scav': 'container_dead-scav',
232
- 'container_festive-airdrop-supply-crate': 'container_festive-airdrop-supply-crate',
233
- 'container_pmc-body': 'container_dead-scav',
234
- 'container_civilian-body': 'container_dead-scav',
235
- 'container_drawer': 'container_drawer',
236
- 'container_duffle-bag': 'container_duffle-bag',
237
- 'container_grenade-box': 'container_grenade-box',
238
- 'container_ground-cache': 'container_ground-cache',
239
- 'container_jacket': 'container_jacket',
240
- 'container_lab-technician-body': 'container_dead-scav',
241
- 'container_medbag-smu06': 'container_medbag-smu06',
242
- 'container_medcase': 'container_medcase',
243
- 'container_medical-supply-crate': 'container_crate',
244
- 'container_pc-block': 'container_pc-block',
245
- 'container_plastic-suitcase': 'container_plastic-suitcase',
246
- 'container_ration-supply-crate': 'container_crate',
247
- 'container_safe': 'container_safe',
248
- 'container_scav-body': 'container_dead-scav',
249
- 'container_shturmans-stash': 'container_weapon-box',
250
- 'container_technical-supply-crate': 'container_crate',
251
- 'container_toolbox': 'container_toolbox',
252
- 'container_weapon-box': 'container_weapon-box',
253
- 'container_wooden-ammo-box': 'container_wooden-ammo-box',
254
- 'container_wooden-crate': 'container_wooden-crate',
255
- 'extract_pmc': 'extract_pmc',
256
- 'extract_scav': 'extract_scav',
257
- 'extract_shared': 'extract_shared',
258
- 'extract_transit': 'extract_transit',
259
- 'hazard': 'hazard',
260
- 'hazard_mortar': 'hazard_mortar',
261
- 'hazard_minefield': 'hazard',
262
- 'hazard_sniper': 'hazard',
263
- 'key': 'key',
264
- 'lock': 'lock',
265
- 'loose_loot': 'loose_loot',
266
- 'quest_item': 'quest_item',
267
- 'quest_objective': 'quest_objective',
268
- 'spawn_sniper_scav': 'spawn_sniper_scav',
269
- 'spawn_bloodhound': 'spawn_bloodhound',
270
- 'spawn_boss': 'spawn_boss',
271
- 'spawn_cultist-priest': 'spawn_cultist-priest',
272
- 'spawn_pmc': 'spawn_pmc',
273
- 'spawn_rogue': 'spawn_rogue',
274
- 'spawn_scav': 'spawn_scav',
275
- 'stationarygun': 'stationarygun',
276
- 'switch': 'switch',
277
- };
278
-
279
- const categories = {
280
- 'extract_pmc': 'PMC',
281
- 'extract_shared': 'Shared',
282
- 'extract_scav': 'Scav',
283
- 'extract_transit': 'Transit',
284
- 'spawn_sniper_scav': 'Sniper Scav',
285
- 'spawn_pmc': 'PMC',
286
- 'spawn_scav': 'Scav',
287
- 'spawn_boss': 'Boss',
288
- 'quest_item': 'Item',
289
- 'quest_objective': 'Objective',
290
- 'lock': 'Locks',
291
- 'lever': 'Lever',
292
- 'stationarygun': 'Stationary Gun',
293
- 'switch': 'Switch',
294
- 'place-names': 'Place Names',
295
- };
296
-
297
- function getCRS(transform) {
298
- let scaleX = 1;
299
- let scaleY = 1;
300
- let marginX = 0;
301
- let marginY = 0;
302
- if (transform) {
303
- scaleX = transform[0];
304
- scaleY = transform[2] * -1;
305
- marginX = transform[1];
306
- marginY = transform[3];
307
- }
308
- return L.extend({}, L.CRS.Simple, {
309
- transformation: new L.Transformation(scaleX, marginX, scaleY, marginY),
310
- projection: L.extend({}, L.Projection.LonLat, {
311
- project: latLng => {
312
- return L.Projection.LonLat.project(applyRotation(latLng, coordinateRotation));
313
- },
314
- unproject: point => {
315
- return applyRotation(L.Projection.LonLat.unproject(point), coordinateRotation * -1);
316
- },
317
- }),
318
- });
319
- }
320
-
321
- function applyRotation(latLng, rotation) {
322
- if (!latLng.lng && !latLng.lat) {
323
- return L.latLng(0, 0);
324
- }
325
- if (!rotation) {
326
- return latLng;
327
- }
328
-
329
- const angleInRadians = (rotation * Math.PI) / 180;
330
- const cosAngle = Math.cos(angleInRadians);
331
- const sinAngle = Math.sin(angleInRadians);
332
-
333
- const { lng: x, lat: y } = latLng;
334
- const rotatedX = x * cosAngle - y * sinAngle;
335
- const rotatedY = x * sinAngle + y * cosAngle;
336
- return L.latLng(rotatedY, rotatedX);
337
- }
338
-
339
- function pos(position) {
340
- return [position.z, position.x];
341
- }
342
-
343
- function addElevation(item, popup) {
344
- if (!showElevation) {
345
- return;
346
- }
347
- const elevationContent = L.DomUtil.create('div', undefined, popup);
348
- elevationContent.textContent = `Elevation: ${item.position.y.toFixed(2)}`;
349
- if (item.top && item.bottom && item.top !== item.position.y && item.bottom !== item.position.y) {
350
- const heightContent = L.DomUtil.create('div', undefined, popup);
351
- heightContent.textContent = `Top ${item.top.toFixed(2)}, bottom: ${item.bottom.toFixed(2)}`;
352
- }
353
- }
354
-
355
- function markerIsOnLayer(marker, layer) {
356
- if (!layer) {
357
- return true;
358
- }
359
- var top = marker.options.top || marker.options.position.y;
360
- var bottom = marker.options.bottom || marker.options.position.y;
361
- for (const extent of layer.options.extents) {
362
- if (top >= extent.height[0] && bottom < extent.height[1]) {
363
- let containedType = 'partial';
364
- if (bottom >= extent.height[0] && top <= extent.height[1]) {
365
- containedType = 'full';
366
- }
367
- if (extent.bounds) {
368
- for (const boundsArray of extent.bounds) {
369
- const bounds = getBounds(boundsArray);
370
- if (bounds.contains(pos(marker.options.position))) {
371
- return containedType;
372
- }
373
- }
374
- } else {
375
- return containedType;
376
- }
377
- }
378
- }
379
- return false;
380
- }
381
-
382
- function markerIsOnActiveLayer(marker) {
383
- if (!marker.options.position) {
384
- return true;
385
- }
386
-
387
- const map = marker._map;
388
-
389
- // check if marker is completely contained by inactive layer
390
- const overlays = map.layerControl._layers.map(l => l.layer).filter(l => Boolean(l.options.extents) && l.options.overlay);
391
- for (const layer of overlays) {
392
- for (const extent of layer.options.extents) {
393
- if (markerIsOnLayer(marker, layer) === 'full' && !map.hasLayer(layer) && extent.bounds) {
394
- return false;
395
- }
396
- }
397
- }
398
-
399
- // check if marker is on active overlay
400
- const activeOverlay = Object.values(map._layers).find(l => l.options?.extents && l.options?.overlay);
401
- if (activeOverlay && markerIsOnLayer(marker, activeOverlay)) {
402
- return true;
403
- }
404
-
405
- // check if marker is on base layer
406
- const baseLayer = Object.values(map._layers).find(l => l.options?.extents && !L.options?.overlay);
407
- if (!activeOverlay && markerIsOnLayer(marker, baseLayer)) {
408
- return true;
409
- }
410
-
411
- return false;
412
- }
413
-
414
- function checkMarkerForActiveLayers(event) {
415
- const marker = event.target || event;
416
- const outline = marker.options.outline;
417
- const onLevel = markerIsOnActiveLayer(marker);
418
- if (onLevel) {
419
- marker._icon?.classList.remove('off-level');
420
- if (outline) {
421
- outline._path?.classList.remove('off-level');
422
- }
423
- } else {
424
- marker._icon?.classList.add('off-level');
425
- if (outline) {
426
- outline._path?.classList.add('off-level');
427
- }
428
- }
429
- }
430
-
431
- function activateMarkerLayer(event) {
432
- const marker = event.target || event;
433
- if (markerIsOnActiveLayer(marker)) {
434
- return;
435
- }
436
- const activeLayers = Object.values(marker._map._layers).filter(l => l.options?.extents && l.options?.overlay);
437
- for (const layer of activeLayers) {
438
- layer.removeFrom(marker._map);
439
- }
440
- const heightLayers = marker._map.layerControl._layers.filter(l => l.layer.options.extents && l.layer.options.overlay).map(l => l.layer);
441
- for (const layer of heightLayers) {
442
- if (markerIsOnLayer(marker, layer)) {
443
- layer.addTo(marker._map);
444
- break;
445
- }
446
- }
447
- }
448
-
449
- const getALink = (path, contents) => {
450
- const a = L.DomUtil.create('a');
451
- a.setAttribute('href', path);
452
- a.setAttribute('target', '_blank');
453
- a.append(contents);
454
- // a.addEventListener('click', (event) => {
455
- // navigate(path);
456
- // event.preventDefault();
457
- // });
458
- return a;
459
- };
460
-
461
- function getScaledBounds(bounds, scaleFactor) {
462
- // Calculate the center point of the bounds
463
- const centerX = (bounds[0][0] + bounds[1][0]) / 2;
464
- const centerY = (bounds[0][1] + bounds[1][1]) / 2;
465
-
466
- // Calculate the new width and height
467
- const width = bounds[1][0] - bounds[0][0];
468
- const height = bounds[1][1] - bounds[0][1];
469
- const newWidth = width * scaleFactor;
470
- const newHeight = height * scaleFactor;
471
-
472
- // Update the coordinates of the two points defining the bounds
473
- const newBounds = [
474
- [centerY - newHeight / 2, centerX - newWidth / 2],
475
- [centerY + newHeight / 2, centerX + newWidth / 2]
476
- ];
477
-
478
- // console.log("Initial Rectangle:", bounds);
479
- // console.log("Scaled Rectangle:", newBounds);
480
- // console.log("Center:", L.bounds(bounds).getCenter(true));
481
-
482
- return newBounds;
483
- }
484
-
485
- // Erstelle die Karte
486
- //const map = L.map('map').setView([0, 0], 2);
487
-
488
- const map = L.map('map', {
489
- maxBounds: getScaledBounds(svgBounds, 1.5),
490
- //maxBounds: bounds,
491
- center: [0, 0],
492
- zoom: 2,
493
- minZoom: minZoom,
494
- maxZoom: maxZoom,
495
- zoomSnap: 0.1,
496
- scrollWheelZoom: true,
497
- wheelPxPerZoomLevel: 120,
498
- crs: getCRS(transform),
499
- attributionControl: false,
500
- id: "wwoodsMap",
501
- });
502
-
503
- const layerControl = L.control.groupedLayers(null, null, {
504
- position: 'topleft',
505
- collapsed: true,
506
- groupCheckboxes: true,
507
- groupsCollapsable: true,
508
- exclusiveOptionalGroups: ['Levels'],
509
- }).addTo(map);
510
- layerControl.on('overlayToggle', (e) => {
511
- const layerState = e.detail;
512
- if (layerState.checked) {
513
- mapViewRef.current.layer = layerState.key;
514
- } else {
515
- mapViewRef.current.layer = undefined;
516
- }
517
- });
518
- layerControl.on('layerToggle', (e) => {
519
- const layerState = e.detail;
520
- if (!layerState.checked) {
521
- mapSettingsRef.current.hiddenLayers.push(layerState.key);
522
- } else {
523
- mapViewRef.current.layer = layerState.key;
524
- mapSettingsRef.current.hiddenLayers = mapSettingsRef.current.hiddenLayers.filter(key => key !== layerState.key);
525
- }
526
- updateSavedMapSettings();
527
- });
528
- layerControl.on('groupToggle', (e) => {
529
- const groupState = e.detail;
530
- for (const groupLayer of layerControl._layers) {
531
- if (groupLayer.group?.key !== groupState.key) {
532
- continue;
533
- }
534
- if (!groupState.checked) {
535
- mapSettingsRef.current.hiddenLayers.push(groupLayer.key);
536
- } else {
537
- mapSettingsRef.current.hiddenLayers = mapSettingsRef.current.hiddenLayers.filter(key => key !== groupLayer.key);
538
- }
539
- }
540
- if (!groupState.checked) {
541
- mapSettingsRef.current.hiddenGroups.push(groupState.key);
542
- } else {
543
- mapSettingsRef.current.hiddenGroups = mapSettingsRef.current.hiddenGroups.filter(key => key !== groupState.key);
544
- }
545
- updateSavedMapSettings();
546
- });
547
- layerControl.on('groupCollapseToggle', (e) => {
548
- const groupState = e.detail;
549
- if (groupState.collapsed) {
550
- mapSettingsRef.current.collapsedGroups.push(groupState.key);
551
-
552
- } else {
553
- mapSettingsRef.current.collapsedGroups = mapSettingsRef.current.collapsedGroups.filter(key => key !== groupState.key);
554
- }
555
- updateSavedMapSettings();
556
- });
557
-
558
- const getLayerOptions = (layerKey, groupKey, layerName) => {
559
- return {
560
- groupKey,
561
- layerKey,
562
- groupName: groupKey,
563
- layerName: layerName || categories[layerKey] || layerKey,
564
- //groupHidden: Boolean(mapSettingsRef.current.hiddenGroups?.includes(groupKey)),
565
- //layerHidden: Boolean(mapSettingsRef.current.hiddenLayers?.includes(layerKey)),
566
- image: images[layerKey] ? "/static/maps/interactive/${images[layerKey]}.png" : undefined,
567
- //groupCollapsed: Boolean(mapSettingsRef.current.collapsedGroups?.includes(groupKey)),
568
- };
569
- };
570
-
571
- const addLayer = (layer, layerKey, groupKey, layerName) => {
572
- layer.key = layerKey;
573
- const layerOptions = getLayerOptions(layerKey, groupKey, layerName);
574
- if (!layerOptions.layerHidden) {
575
- layer.addTo(map);
576
- }
577
- layerControl.addOverlay(layer, layerOptions.layerName, layerOptions);
578
- };
579
- map.layerControl = layerControl;
580
-
581
- // Hinzufügen des Image-Overlays
582
- const overlay = L.imageOverlay(imageUrl, getBounds(svgBounds));
583
- overlay.addTo(map);
584
-
585
- function checkMarkerBounds(position, markerBounds) {
586
- if (position.x < markerBounds.TL.x) markerBounds.TL.x = position.x;
587
- if (position.z > markerBounds.TL.z) markerBounds.TL.z = position.z;
588
- if (position.x > markerBounds.BR.x) markerBounds.BR.x = position.x;
589
- if (position.z < markerBounds.BR.z) markerBounds.BR.z = position.z;
590
- }
591
-
592
- function getBounds(bounds) {
593
- if (!bounds) {
594
- return undefined;
595
- }
596
- return L.latLngBounds([bounds[0][1], bounds[0][0]], [bounds[1][1], bounds[1][0]]);
597
- //return [[bounds[0][1], bounds[0][0]], [bounds[1][1], bounds[1][0]]];
598
- }
599
-
600
- function mouseHoverOutline(event) {
601
- const outline = event.target.options.outline;
602
- if (event.originalEvent.type === 'mouseover') {
603
- outline._path.classList.remove('not-shown');
604
- } else if (!outline._path.classList.contains('force-show')) {
605
- outline._path.classList.add('not-shown');
606
- }
607
- }
608
-
609
- function toggleForceOutline(event) {
610
- const outline = event.target.options.outline;
611
- outline._path.classList.toggle('force-show');
612
- if (outline._path.classList.contains('force-show')) {
613
- outline._path.classList.remove('not-shown');
614
- }
615
- activateMarkerLayer(event);
616
- }
617
-
618
- function activateMarkerLayer(event) {
619
- const marker = event.target || event;
620
- if (markerIsOnActiveLayer(marker)) {
621
- return;
622
- }
623
- const activeLayers = Object.values(marker._map._layers).filter(l => l.options?.extents && l.options?.overlay);
624
- for (const layer of activeLayers) {
625
- layer.removeFrom(marker._map);
626
- }
627
- const heightLayers = marker._map.layerControl._layers.filter(l => l.layer.options.extents && l.layer.options.overlay).map(l => l.layer);
628
- for (const layer of heightLayers) {
629
- if (markerIsOnLayer(marker, layer)) {
630
- layer.addTo(marker._map);
631
- break;
632
- }
633
- }
634
- }
635
-
636
- function outlineToPoly(outline) {
637
- if (!outline) return [];
638
- return outline.map(vector => [vector.z, vector.x]);
639
- }
640
-
641
- const layerOptions = {
642
- maxZoom: maxZoom,
643
- maxNativeZoom: maxZoom,
644
- extents: [
645
- {
646
- height: [Number.MIN_SAFE_INTEGER, Number.MAX_SAFE_INTEGER],
647
- bounds: [bounds],
648
- }
649
- ],
650
- type: 'map-layer',
651
- };
652
- let tileLayer = false;
653
- const baseLayers = [];
654
- const tileSize = 256;
655
- let svgLayer = false;
656
- if (svgPath) {
657
- const svgBounds2 = svgBounds ? getBounds(svgBounds) : bounds;
658
- svgLayer = L.imageOverlay(svgPath, svgBounds2, layerOptions);
659
- baseLayers.push(svgLayer);
660
- }
661
-
662
- const positionIsInBounds = (position) => {
663
- return getBounds(bounds).contains(pos(position));
664
- };
665
-
666
- let markerBounds = {
667
- 'TL': { x: Number.MAX_SAFE_INTEGER, z: Number.MIN_SAFE_INTEGER },
668
- 'BR': { x: Number.MIN_SAFE_INTEGER, z: Number.MAX_SAFE_INTEGER }
669
- }
670
-
671
- if (mapData.labels?.length > 0) {
672
- const labelsGroup = L.layerGroup();
673
- const defaultHeight = ((layerOptions.extents[0].height[1] - layerOptions.extents[0].height[0]) / 2) + layerOptions.extents[0].height[0];
674
- for (const label of mapData.labels) {
675
- const fontSize = label.size ? label.size : 100;
676
- const height = label.position.length < 3 ? defaultHeight : label.position[2];
677
- const rotation = label.rotation ? label.rotation : 0;
678
- L.marker(pos({ x: label.position[0], z: label.position[1] }), {
679
- icon: L.divIcon({ html: `<div class="label" style="font-size: ${fontSize}%; transform: translate3d(-50%, -50%, 0) rotate(${rotation}deg)">${label.text}</div>`, className: 'map-area-label', layers: baseLayers, }),
680
- interactive: false,
681
- zIndexOffset: -100000,
682
- position: {
683
- x: label.position[0],
684
- y: height,
685
- z: label.position[1],
686
- },
687
- top: typeof label.top !== 'undefined' ? label.top : 1000,
688
- bottom: typeof label.bottom !== 'undefined' ? label.bottom : -1000,
689
- }).addTo(labelsGroup);
690
- }
691
- addLayer(labelsGroup, 'place-names', 'Landmarks');
692
- //labelsGroup.addTo(map);
693
- }
694
-
695
- // Add spawns
696
- if (mapData.spawns.length > 0) {
697
- const spawnLayers = {
698
- 'pmc': L.layerGroup(),
699
- 'scav': L.layerGroup(),
700
- 'sniper_scav': L.layerGroup(),
701
- 'boss': L.layerGroup(),
702
- 'cultist-priest': L.layerGroup(),
703
- 'rogue': L.layerGroup(),
704
- 'bloodhound': L.layerGroup(),
705
- }
706
- for (const spawn of mapData.spawns) {
707
- if (!positionIsInBounds(spawn.position)) {
708
- continue;
709
- }
710
- let spawnType = '';
711
- let bosses = [];
712
-
713
- if (spawn.categories.includes('boss')) {
714
- bosses = mapData.bosses.filter(boss => boss.spawnLocations.some(sl => sl.spawnKey === spawn.zoneName));
715
- if (bosses.length === 0) {
716
- if (spawn.categories.includes('bot') && spawn.sides.includes('scav')) {
717
- spawnType = 'scav';
718
- }
719
- else {
720
- console.error(`Unusual spawn: ${spawn.sides}, ${spawn.categories}`);
721
- continue;
722
- }
723
- }
724
- else if (bosses.length === 1 && (bosses[0].normalizedName === 'bloodhound' || bosses[0].normalizedName === 'cultist-priest' || bosses[0].normalizedName === 'rogue')) {
725
- spawnType = bosses[0].normalizedName;
726
- }
727
- else {
728
- spawnType = 'boss';
729
- }
730
- } else if (spawn.categories.includes('sniper')) {
731
- spawnType = 'sniper_scav';
732
- } else if (spawn.sides.includes('scav')) {
733
- if (spawn.categories.includes('bot') || spawn.categories.includes('all')) {
734
- spawnType = 'scav';
735
- }
736
- else {
737
- console.error(`Unusual spawn: ${spawn.sides}, ${spawn.categories}`);
738
- continue;
739
- }
740
- }
741
- else if (spawn.categories.includes('player')) {
742
- if (spawn.sides.includes('pmc') || spawn.sides.includes('all')) {
743
- spawnType = 'pmc'
744
- }
745
- else {
746
- console.error(`Unusual spawn: ${spawn.sides}, ${spawn.categories}`);
747
- continue;
748
- }
749
- }
750
- else {
751
- console.error(`Unusual spawn: ${spawn.sides}, ${spawn.categories}`);
752
- continue;
753
- }
754
-
755
- const spawnIcon = L.icon({
756
- iconUrl: `/static/maps/interactive/spawn_${spawnType}.png`,
757
- iconSize: [24, 24],
758
- popupAnchor: [0, -12],
759
- });
760
-
761
- if (spawnType === 'pmc') {
762
- spawnIcon.iconAnchor = [12, 24];
763
- spawnIcon.popupAnchor = [0, -24];
764
- }
765
-
766
- const popupContent = L.DomUtil.create('div')
767
- if (spawn.categories.includes('boss') && bosses.length > 0) {
768
- bosses = bosses.reduce((unique, current) => {
769
- if (!unique.some(b => b.normalizedName === current.normalizedName)) {
770
- unique.push(current)
771
- if (!categories[`spawn_${current.normalizedName}`]) {
772
- categories[`spawn_${current.normalizedName}`] = current.name
773
- }
774
- }
775
- return unique;
776
- }, []);
777
- const bossList = L.DomUtil.create('div', undefined, popupContent)
778
- for (const boss of bosses) {
779
- if (bossList.childNodes.length > 0) {
780
- const comma = L.DomUtil.create('span', undefined, bossList)
781
- comma.textContent = ', '
782
- }
783
- bossList.append(getALink(`https://escapefromtarkov.fandom.com/wiki/Special:Search?scope=internal&query=${boss.name}`, `${boss.name} (${Math.round(boss.spawnChance * 100)}%)`))
784
- }
785
- }
786
- else {
787
- const spawnDiv = L.DomUtil.create('div', undefined, popupContent)
788
- spawnDiv.textContent = categories[`spawn_${spawnType}`]
789
- }
790
- addElevation(spawn, popupContent)
791
-
792
- const marker = L.marker(pos(spawn.position), {
793
- icon: spawnIcon,
794
- position: spawn.position,
795
- });
796
- if (popupContent.childNodes.length > 0) {
797
- marker.bindPopup(L.popup().setContent(popupContent))
798
- }
799
- marker.position = spawn.position
800
- marker.on('add', checkMarkerForActiveLayers)
801
- marker.on('click', activateMarkerLayer)
802
- marker.addTo(spawnLayers[spawnType])
803
-
804
- checkMarkerBounds(spawn.position, markerBounds)
805
- }
806
- for (const key in spawnLayers) {
807
- if (Object.keys(spawnLayers[key]._layers).length > 0) {
808
- addLayer(spawnLayers[key], `spawn_${key}`, 'Spawns')
809
- }
810
- }
811
- }
812
-
813
- //add extracts
814
- if (mapData.extracts.length > 0) {
815
- const extractLayers = {
816
- pmc: L.layerGroup(),
817
- scav: L.layerGroup(),
818
- shared: L.layerGroup(),
819
- }
820
- const zIndexOffsets = {
821
- pmc: 150,
822
- shared: 125,
823
- scav: 100,
824
- };
825
- for (const extract of mapData.extracts) {
826
- const faction = extract.faction ?? 'shared';
827
- if (!positionIsInBounds(extract.position)) {
828
- //continue;
829
- }
830
- const colorMap = {
831
- scav: '#ff7800',
832
- pmc: '#00e599',
833
- shared: '#00e4e5',
834
- }
835
- const rect = L.polygon(outlineToPoly(extract.outline), { color: colorMap[faction], weight: 1, className: 'not-shown' });
836
- const extractIcon = L.divIcon({
837
- className: 'extract-icon',
838
- html: `<img src="/static/maps/interactive/extract_${faction}.png"/><span class="extract-name ${faction}">${extract.name}</span>`,
839
- iconAnchor: [12, 12]
840
- });
841
- const extractMarker = L.marker(pos(extract.position), {
842
- icon: extractIcon,
843
- title: extract.name,
844
- zIndexOffset: zIndexOffsets[faction],
845
- position: extract.position,
846
- top: extract.top,
847
- bottom: extract.bottom,
848
- outline: rect,
849
- id: extract.id,
850
- });
851
- extractMarker.on('mouseover', mouseHoverOutline);
852
- extractMarker.on('mouseout', mouseHoverOutline);
853
- extractMarker.on('click', toggleForceOutline);
854
- if (extract.switches?.length > 0) {
855
- const popup = L.DomUtil.create('div');
856
- const textElement = L.DomUtil.create('div');
857
- textElement.textContent = `${tMaps('Activated by')}:`;
858
- popup.appendChild(textElement);
859
- for (const sw of extract.switches) {
860
- const linkElement = getPoiLinkElement(sw.id, 'switch');
861
- const nameElement = L.DomUtil.create('span');
862
- nameElement.innerHTML = `<strong>${sw.name}</strong>`;
863
- linkElement.append(nameElement);
864
- popup.appendChild(linkElement);
865
- }
866
- addElevation(extract, popup);
867
- extractMarker.bindPopup(L.popup().setContent(popup));
868
- } else if (showElevation) {
869
- const popup = L.DomUtil.create('div');
870
- addElevation(extract, popup);
871
- extractMarker.bindPopup(L.popup().setContent(popup));
872
- }
873
- extractMarker.on('add', checkMarkerForActiveLayers);
874
- L.layerGroup([rect, extractMarker]).addTo(extractLayers[faction]);
875
-
876
- checkMarkerBounds(extract.position, markerBounds);
877
- }
878
- if (mapData.transits.length > 0) {
879
- extractLayers.transit = L.layerGroup();
880
-
881
- for (const transit of mapData.transits) {
882
- if (!positionIsInBounds(transit.position)) {
883
- //continue;
884
- }
885
- const rect = L.polygon(outlineToPoly(transit.outline), { color: '#e53500', weight: 1, className: 'not-shown' });
886
- const transitIcon = L.divIcon({
887
- className: 'extract-icon',
888
- html: `<img src="/static/maps/interactive/extract_transit.png"/><span class="extract-name transit">${transit.description}</span>`,
889
- iconAnchor: [12, 12]
890
- });
891
- const transitMarker = L.marker(pos(transit.position), {
892
- icon: transitIcon,
893
- title: transit.description,
894
- zIndexOffset: zIndexOffsets.pmc,
895
- position: transit.position,
896
- top: transit.top,
897
- bottom: transit.bottom,
898
- outline: rect,
899
- id: transit.id,
900
- });
901
- transitMarker.on('mouseover', mouseHoverOutline);
902
- transitMarker.on('mouseout', mouseHoverOutline);
903
- transitMarker.on('click', toggleForceOutline);
904
- if (showElevation) {
905
- const popup = L.DomUtil.create('div');
906
- addElevation(transit, popup);
907
- transitMarker.bindPopup(L.popup().setContent(popup));
908
- }
909
- transitMarker.on('add', checkMarkerForActiveLayers);
910
- L.layerGroup([rect, transitMarker]).addTo(extractLayers.transit);
911
-
912
- checkMarkerBounds(transit.position, markerBounds);
913
- }
914
- }
915
- for (const key in extractLayers) {
916
- if (Object.keys(extractLayers[key]._layers).length > 0) {
917
- addLayer(extractLayers[key], `extract_${key}`, 'Extracts');
918
- }
919
- }
920
- }
921
-
922
- // Add static items
923
- if (showStaticMarkers) {
924
- for (const category in mapData) {
925
- const markerLayer = L.layerGroup();
926
-
927
- const items = mapData[category];
928
- for (const item of items) {
929
- const itemIcon = L.icon({
930
- iconUrl: `/static/maps/interactive/${category}.png`,
931
- iconSize: [24, 24],
932
- popupAnchor: [0, -12],
933
- //className: layerIncludesMarker(heightLayer, item) ? '' : 'off-level',
934
- });
935
- L.marker(pos(item.position), { icon: itemIcon, position: item.position })
936
- .bindPopup(L.popup().setContent(`${item.name}<br>Elevation: ${item.position.y}`))
937
- .addTo(markerLayer);
938
-
939
- checkMarkerBounds(item.position, markerBounds);
940
- }
941
-
942
- if (items.length > 0) {
943
- var section;
944
- if (category.startsWith('extract')) {
945
- section = 'Extracts';
946
- }
947
- else if (category.startsWith('spawn')) {
948
- section = 'Spawns';
949
- }
950
- else {
951
- section = 'Lootable Items';
952
- }
953
- markerLayer.addTo(map);
954
- addLayer(markerLayer, category, section);
955
- // layerControl.addOverlay(markerLayer, `<img src='${process.env.PUBLIC_URL}/maps/interactive/${category}.png' class='control-item-image' /> ${categories[category] || category}`, section);
956
- }
957
- }
958
- }
959
-
960
- // Erstelle ein neues Control-Element
961
- const customControl = L.Control.extend({
962
- onAdd: function (map) {
963
- this.container = L.DomUtil.create('div', 'custom-control leaflet-control-layers leaflet-control-layers-expanded')
964
- this.container.innerHTML = '<h2>Add Marker to Pos: </h2>'
965
- return this.container
966
- },
967
-
968
- updateText: function (html) {
969
- if (this.container) {
970
- this.container.innerHTML = html
971
- }
972
- },
973
-
974
- clearText: function () {
975
- if (this.container) {
976
- this.container.innerHTML = ''
977
- }
978
- },
979
-
980
- onRemove: function (map) {
981
- // Nichts zu tun hier
982
- }
983
- });
984
- const myControl = new customControl({ position: 'topright' })
985
- map.addControl(myControl)
986
-
987
- function startCountdown(elementId) {
988
- const element = document.getElementById(elementId);
989
- if (!element) {
990
- console.error(`Element with id ${elementId} not found.`);
991
- return;
992
- }
993
-
994
- //let secondsStart = Math.floor(new Date().getTime() / 1000.0) - parseInt(element.getAttribute('data-rade-end')) - 60
995
- let secondsStart = parseInt(element.getAttribute('data-seconds')) - 60
996
- let seconds = secondsStart
997
- if (isNaN(secondsStart)) {
998
- console.error('Invalid data-seconds attribute.');
999
- return;
1000
- }
1001
-
1002
- const intervalId = setInterval(() => {
1003
- if (!document.hidden) {
1004
- //seconds = (secondsStart - parseInt(Math.floor(new Date().getTime() / 1000.0)))
1005
- //localStorage.setItem('rade_data', JSON.stringify({ remaining_time: seconds }));
1006
- seconds--
1007
-
1008
- if (seconds <= 0) {
1009
- clearInterval(intervalId);
1010
- element.textContent = '0:00:00';
1011
- myControl.updateText("")
1012
- document.querySelector("head > title").textContent = `${mapData.name} | Map with friends`
1013
- return;
1014
- }
1015
-
1016
- //seconds--;
1017
- const hours = Math.floor(seconds / 3600);
1018
- const minutes = Math.floor((seconds % 3600) / 60);
1019
- const remainingSeconds = seconds % 60;
1020
-
1021
- const timeString = `${hours}:${minutes.toString().padStart(2, '0')}:${remainingSeconds.toString().padStart(2, '0')}`;
1022
- element.textContent = timeString;
1023
- document.querySelector("head > title").textContent = `${mapData.name} | ${timeString}`
1024
- }
1025
- }, 1000);
1026
- }
1027
-
1028
- function addNewRadeData(new_html) {
1029
- myControl.updateText(new_html)
1030
- startCountdown("rade_time_remain")
1031
- };
1032
-
1033
- //MARK: ADDMARKER
1034
- // Funktion zum Hinzufügen eines Markers
1035
- function addMarker(x, y, z, timestamp, preview, actualmap, playername, markercolor) {
1036
-
1037
- // Validate position
1038
- const position = {
1039
- x: parseFloat(x),
1040
- y: parseFloat(y),
1041
- z: parseFloat(z)
1042
- };
1043
-
1044
- if (!positionIsInBounds(position)) {
1045
- console.error("Position außerhalb der Karte:", position);
1046
- return;
1047
- }
1048
-
1049
- // SVG-Icon als String mit dynamischer Farbe
1050
- let markerColor;
1051
- if (markercolor) {
1052
- markerColor = '#' + markercolor;
1053
- } else {
1054
- markerColor = 'currentColor';
1055
- }
1056
- const svgString = `
1057
- <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 76 76" class="marker-svg">
1058
- <path d="M60.8 50.5 38 72.9 15.2 50.5 8.1 3.9 38 13.3l29.8-9.4-7 46.6z" style="stroke-linecap:round;stroke-linejoin:round;fill:#fff;stroke:#fff;stroke-width:4.4px"/><path d="M58.8 49.4 38 69.9 17.1 49.4 10.7 6.9 38 15.5l27.2-8.6-6.4 42.5z" style="fill:${markerColor};stroke:#000;stroke-width:4px;stroke-linecap:round;stroke-linejoin:round"/>
1059
- </svg>
1060
- `;
1061
- const svgString2 = `
1062
- <svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 384 512' class="marker-svg">
1063
- <path fill='${markerColor}' d='M172.3 501.7C27 291 0 269.4 0 192 0 86 86 0 192 0s192 86 192 192c0 77.4-27 99-172.3 309.7-9.5 13.8-29.9 13.8-39.5 0zM192 272c44.2 0 80-35.8 80-80s-35.8-80-80-80-80 35.8-80 80 35.8 80 80 80z'/>
1064
- </svg>
1065
- `;
1066
-
1067
- // DivIcon mit dynamischem SVG erstellen
1068
- const markerIcon = L.divIcon({
1069
- html: svgString,
1070
- className: 'custom-marker',
1071
- iconSize: [32, 32],
1072
- iconAnchor: [16, 32],
1073
- popupAnchor: [0, -32]
1074
- });
1075
-
1076
- const playerMarkerName = playername.toLowerCase().replaceAll(" ", "-")
1077
-
1078
- // Remove old marker for this player, if it exists
1079
- if (playerMarkers[playerMarkerName]) {
1080
- map.removeLayer(playerMarkers[playerMarkerName]);
1081
- }
1082
-
1083
- // Create new marker
1084
- playerMarkers[playerMarkerName] = L.marker(pos(position), {
1085
- icon: markerIcon,
1086
- position: position,
1087
- title: `Koordinaten: ${x}, ${y}, ${z}`,
1088
- riseOnHover: true,
1089
- zIndexOffset: 400
1090
- });
1091
-
1092
- // Popup mit Informationen erstellen
1093
- const popupContent = `
1094
- <div class="marker-popup">
1095
- ${playername ? `<strong>Player: ${playername}</strong>` : ''}
1096
- <strong>Koordinaten:</strong>
1097
- <span>X: ${x} Y: ${y} Z: ${z}</span>
1098
- ${preview ? `<img class="preview-image" src="${preview}"><br>` : ''}
1099
- <small>${timestamp}</small>
1100
- </div>
1101
- `;
1102
- playerMarkers[playerMarkerName].bindPopup(popupContent, {
1103
- maxWidth: 250,
1104
- minWidth: 150,
1105
- autoClose: true,
1106
- closeOnClick: true
1107
- });
1108
-
1109
- // Marker zur Karte hinzufügen
1110
- playerMarkers[playerMarkerName].addTo(map);
1111
-
1112
- // Karte auf Marker zentrieren
1113
- map.setView(pos(position), map.getZoom(), {
1114
- animate: true,
1115
- duration: 0.5
1116
- });
1117
-
1118
- console.log("Neuer Marker gesetzt für " + playername + ": " + position);
1119
- }
1120
-
1121
- document.onload = loadLocalData()
1122
-
1123
-
1124
- // MODAL Trigger
1125
- document.addEventListener('DOMContentLoaded', () => {
1126
- // Functions to open and close a modal
1127
- function openModal($el) {
1128
- $el.classList.add('is-active');
1129
- }
1130
-
1131
- function closeModal($el) {
1132
- $el.classList.remove('is-active');
1133
- }
1134
-
1135
- function closeAllModals() {
1136
- (document.querySelectorAll('.modal') || []).forEach(($modal) => {
1137
- closeModal($modal);
1138
- });
1139
- }
1140
-
1141
- // Add a click event on buttons to open a specific modal
1142
- (document.querySelectorAll('.js-modal-trigger') || []).forEach(($trigger) => {
1143
- const modal = $trigger.dataset.target;
1144
- const $target = document.getElementById(modal);
1145
-
1146
- $trigger.addEventListener('click', () => {
1147
- openModal($target);
1148
- });
1149
- });
1150
-
1151
- // Add a click event on various child elements to close the parent modal
1152
- (document.querySelectorAll('.modal-background, .modal-close, .modal-card-head .delete, .modal-card-foot .button') || []).forEach(($close) => {
1153
- const $target = $close.closest('.modal');
1154
-
1155
- $close.addEventListener('click', () => {
1156
- closeModal($target);
1157
- });
1158
- });
1159
-
1160
- // Add a keyboard event to close all modals
1161
- document.addEventListener('keydown', (event) => {
1162
- if (event.key === "Escape") {
1163
- closeAllModals();
1164
- }
1165
- });
1166
- });
1167
- </script>
1168
- </body>
1169
-
1170
- </html>
 
1
  <!DOCTYPE html>
2
+ <html lang="en" class="h-full bg-gray-900">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>{{ maps[0].name }} | Map with friends</title>
7
+
8
+ <!-- Favicon -->
9
+ <link rel="icon" type="image/x-icon" href="/static/favicon.ico" />
10
+
11
+ <!-- Leaflet -->
12
+ <link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.3/dist/leaflet.css" />
13
+ <script src="https://unpkg.com/leaflet@1.9.3/dist/leaflet.js"></script>
14
+
15
+ <!-- Tailwind -->
16
+ <script src="https://cdn.tailwindcss.com"></script>
17
+
18
+ <!-- Custom Styles -->
19
+ <link rel="stylesheet" href="/static/style.css" />
20
+ </head>
21
+
22
+ <body class="h-full flex flex-col text-gray-200">
23
+ <!-- Navbar -->
24
+ <nav class="bg-gray-800 border-b border-gray-700">
25
+ <div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
26
+ <div class="flex items-center justify-between h-16">
27
+ <div class="flex items-center">
28
+ <a href="#" class="text-xl font-bold text-white">Tarkov Map</a>
29
+ <div class="ml-10 space-x-4 hidden md:flex">
30
+ <a href="/" class="text-gray-300 hover:text-white">Home</a>
31
+ <div class="relative group">
32
+ <button class="text-gray-300 hover:text-white inline-flex items-center">
33
+ Maps <svg class="ml-1 w-4 h-4" fill="currentColor" viewBox="0 0 20 20"><path d="M5 7l5 5 5-5H5z" /></svg>
34
+ </button>
35
+ <div class="absolute hidden group-hover:block mt-2 w-48 bg-gray-800 border border-gray-700 rounded-lg shadow-lg">
36
+ <a href="/map/woods" class="block px-4 py-2 hover:bg-gray-700">Woods</a>
37
+ <a href="/map/shoreline" class="block px-4 py-2 hover:bg-gray-700">Shoreline</a>
38
+ <a href="/map/lighthouse" class="block px-4 py-2 hover:bg-gray-700">Lighthouse</a>
39
+ <a href="/map/customs" class="block px-4 py-2 hover:bg-gray-700">Customs</a>
40
+ <a href="/map/interchange" class="block px-4 py-2 hover:bg-gray-700">Interchange</a>
41
+ <a href="/map/streets-of-tarkov" class="block px-4 py-2 hover:bg-gray-700">Streets of Tarkov</a>
42
+ <a href="/map/ground-zero" class="block px-4 py-2 hover:bg-gray-700">Ground Zero</a>
43
+ <a href="/map/reserve" class="block px-4 py-2 hover:bg-gray-700">Reserve</a>
44
+ <a href="/map/factory" class="block px-4 py-2 hover:bg-gray-700">Factory</a>
45
+ </div>
46
+ </div>
47
+ </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
48
  </div>
49
+ <!-- Settings Button -->
50
+ <button onclick="openModal('settings-modal')" class="text-gray-300 hover:text-white">
51
+ <i class="fas fa-cog"></i>
 
 
 
 
 
 
52
  </button>
53
  </div>
54
  </div>
55
+ </nav>
56
+
57
+ <!-- Map -->
58
+ <div id="map" class="flex-1 w-full"></div>
59
+
60
+ <!-- Settings Modal -->
61
+ <div id="settings-modal" class="fixed inset-0 hidden items-center justify-center bg-black bg-opacity-70 z-50">
62
+ <div class="bg-gray-800 rounded-xl shadow-lg max-w-lg w-full p-6 relative">
63
+ <button onclick="closeModal('settings-modal')" class="absolute top-3 right-3 text-gray-400 hover:text-white">✕</button>
64
+ <h2 class="text-xl font-bold mb-4">Einstellungen</h2>
65
+ <p class="text-sm text-gray-400">Hier die Einstellungen basierend auf der config.ini file.</p>
66
+ <pre class="mt-4 text-xs bg-gray-900 p-2 rounded text-gray-300">
67
+ [mainconfig]
68
+ eft_log_older_path = E:/Battlestate Games/Escape from Tarkov/Logs/
69
+ eft_screenshots_folder_path = C:/Users/Sebastian/Documents/Escape from Tarkov/Screenshots/
70
+ delete_screenshots = True
71
+ eft_show_exits_key = o
72
+ eft_take_screenshot_key = del
73
+ </pre
74
+ >
 
 
 
 
 
 
 
 
 
 
 
 
 
 
75
  </div>
76
  </div>
77
 
78
+ <!-- Scripts -->
79
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.7.2/js/all.min.js"></script>
80
+ <script src="/static/js/leaflet.controlGroupedlayer.js"></script>
81
+ <script src="/static/js/leaflet.controlCoordinates.js"></script>
82
+ <script>
83
+ const mapData = {{ maps[0] | tojson }}
84
+ console.info(mapData)
85
+ </script>
86
+
87
+ <script src="/static/js/map.js"></script>
88
+
89
+ <script>
90
+ // Simple modal logic
91
+ function openModal(id) {
92
+ document.getElementById(id).classList.remove("hidden")
93
+ document.getElementById(id).classList.add("flex")
94
+ }
95
+ function closeModal(id) {
96
+ document.getElementById(id).classList.add("hidden")
97
+ document.getElementById(id).classList.remove("flex")
98
+ }
99
+ </script>
100
+ </body>
101
+ </html>