mistpe commited on
Commit
2630eff
·
verified ·
1 Parent(s): 10a4ad4

Upload 7 files

Browse files
Files changed (8) hide show
  1. .gitattributes +1 -0
  2. banner.svg +10 -0
  3. cloud.png +0 -0
  4. footprints-cloud.png +0 -0
  5. footprints-white.png +0 -0
  6. footprints.png +0 -0
  7. index.html +444 -0
  8. map-bg.png +3 -0
.gitattributes CHANGED
@@ -33,3 +33,4 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
 
 
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
36
+ map-bg.png filter=lfs diff=lfs merge=lfs -text
banner.svg ADDED
cloud.png ADDED
footprints-cloud.png ADDED
footprints-white.png ADDED
footprints.png ADDED
index.html ADDED
@@ -0,0 +1,444 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>The Marauder's Map</title>
7
+ <link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css" />
8
+ <script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script>
9
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/4.0.1/socket.io.js"></script>
10
+ <style>
11
+ html,
12
+ body {
13
+ width: 100%;
14
+ height: 100%;
15
+ margin: 0;
16
+ }
17
+
18
+ .map-container {
19
+ width: 1440px;
20
+ height: 900px;
21
+ position: relative;
22
+ background-image: url('./map-bg.png');
23
+ }
24
+
25
+ .map {
26
+ width: 100%;
27
+ height: 100%;
28
+ background-image: url('./map.png');
29
+ mix-blend-mode: multiply;
30
+ filter: grayscale(1);
31
+ position: relative;
32
+ }
33
+
34
+ .map .mask {
35
+ width: 100%;
36
+ height: 100%;
37
+ background-image: url("./cloud.png");
38
+ mix-blend-mode: lighten;
39
+ animation: 3s linear 0s magic forwards;
40
+ position: absolute;
41
+ top: 0;
42
+ left: 0;
43
+ }
44
+
45
+ #leaflet-map {
46
+ width: 100%;
47
+ height: 100%;
48
+ position: absolute;
49
+ top: 0;
50
+ left: 0;
51
+ background: transparent;
52
+ }
53
+
54
+ .track {
55
+ position: absolute;
56
+ top: 0;
57
+ width: 1440px;
58
+ height: 900px;
59
+ mix-blend-mode: multiply;
60
+ opacity: .7;
61
+ pointer-events: none;
62
+ }
63
+
64
+ .footprint {
65
+ position: absolute;
66
+ pointer-events: none;
67
+ }
68
+
69
+ .footprint .foot {
70
+ position: absolute;
71
+ width: 10px;
72
+ height: 22px;
73
+ background-image: url('./footprints.png');
74
+ background-size: 40px;
75
+ background-repeat: no-repeat;
76
+ background-position-x: 10px;
77
+ animation: 1s linear 0s footsteps forwards;
78
+ }
79
+
80
+ .footprint .foot::after {
81
+ display: block;
82
+ content: '';
83
+ width: 100%;
84
+ height: 100%;
85
+ background-image: url('./footprints-cloud.png');
86
+ background-repeat: no-repeat;
87
+ background-size: 20px 22px;
88
+ mix-blend-mode: lighten;
89
+ animation: 8s linear 1s footHide forwards;
90
+ opacity: 0;
91
+ }
92
+
93
+ .footprint.last .foot::after {
94
+ animation-play-state: paused;
95
+ }
96
+
97
+ @keyframes footHide {
98
+ 0% {
99
+ opacity: 0;
100
+ }
101
+
102
+ 25% {
103
+ opacity: 1;
104
+ filter: brightness(1);
105
+ }
106
+
107
+ 100% {
108
+ opacity: 1;
109
+ filter: brightness(10);
110
+ }
111
+ }
112
+
113
+ .footprint .foot.right::after {
114
+ background-position-x: -10px;
115
+ }
116
+
117
+ @keyframes footsteps {
118
+ 0% {
119
+ background-position-x: 0px;
120
+ }
121
+
122
+ 25% {
123
+ background-position-x: 0px;
124
+ }
125
+
126
+ 25.1% {
127
+ background-position-x: -10px;
128
+ }
129
+
130
+ 50% {
131
+ background-position-x: -10px;
132
+ }
133
+
134
+ 50.1% {
135
+ background-position-x: -20px;
136
+ }
137
+
138
+ 75% {
139
+ background-position-x: -20px;
140
+ }
141
+
142
+ 75.1% {
143
+ background-position-x: -30px;
144
+ }
145
+
146
+ 100% {
147
+ background-position-x: -30px;
148
+ }
149
+ }
150
+
151
+ .footprint .foot.left {
152
+ left: -5px;
153
+ top: 7px;
154
+ }
155
+
156
+ .footprint .foot.right {
157
+ left: 5px;
158
+ top: -7px;
159
+ background-position-y: -22px;
160
+ animation-delay: 1s;
161
+ }
162
+
163
+ @keyframes magic {
164
+ from {
165
+ filter: brightness(10);
166
+ }
167
+
168
+ to {
169
+ filter: brightness(0);
170
+ }
171
+ }
172
+
173
+ .name-banner {
174
+ position: absolute;
175
+ width: 160px;
176
+ height: 40px;
177
+ background-image: url('./banner.svg');
178
+ background-repeat: no-repeat;
179
+ background-size: 160px 40px;
180
+ text-align: center;
181
+ line-height: 30px;
182
+ font-family: 'Apple Chancery';
183
+ z-index: 1000;
184
+ animation: 1s linear 0s nameShow forwards;
185
+ }
186
+
187
+ @keyframes nameShow {
188
+ from {
189
+ opacity: 0;
190
+ }
191
+
192
+ to {
193
+ opacity: 1;
194
+ }
195
+ }
196
+
197
+ .user-controls {
198
+ position: fixed;
199
+ top: 20px;
200
+ right: 20px;
201
+ z-index: 1000;
202
+ background: rgba(222, 184, 135, 0.9);
203
+ padding: 15px;
204
+ border-radius: 8px;
205
+ border: 2px solid #8b4513;
206
+ }
207
+
208
+ .user-controls input {
209
+ padding: 8px 12px;
210
+ margin-right: 10px;
211
+ border: 1px solid #8b4513;
212
+ border-radius: 4px;
213
+ font-family: 'Apple Chancery', cursive;
214
+ }
215
+
216
+ .user-controls button {
217
+ padding: 8px 16px;
218
+ background: #8b4513;
219
+ color: #fff;
220
+ border: none;
221
+ border-radius: 4px;
222
+ cursor: pointer;
223
+ font-family: 'Apple Chancery', cursive;
224
+ }
225
+
226
+ .leaflet-container {
227
+ background: transparent !important;
228
+ }
229
+
230
+ .leaflet-control-attribution {
231
+ display: none;
232
+ }
233
+ </style>
234
+ </head>
235
+
236
+ <body>
237
+ <div class="map-container">
238
+ <div class="map">
239
+ <div id="mapMask" class="mask"></div>
240
+ </div>
241
+ <div id="leaflet-map"></div>
242
+ <div id="track" class="track"></div>
243
+ <div class="user-controls">
244
+ <input type="text" id="username" placeholder="输入你的名字" maxlength="20">
245
+ <button onclick="updateUsername()">更新名字</button>
246
+ </div>
247
+ </div>
248
+
249
+ <script>
250
+ let map;
251
+ let socket;
252
+ let username = '陌生访客_' + Math.floor(Math.random() * 1000);
253
+ let userMarker;
254
+ let otherUsers = {};
255
+ let lastPosition = null;
256
+ let lastUpdateTime = 0;
257
+ const updateInterval = 1000; // 位置更新最小间隔(毫秒)
258
+
259
+ function initSocket() {
260
+ socket = io();
261
+
262
+ socket.on('connect', () => {
263
+ console.log('Connected to server');
264
+ });
265
+
266
+ socket.on('disconnect', () => {
267
+ console.log('Disconnected from server');
268
+ });
269
+
270
+ socket.on('users_update', (users) => {
271
+ updateOtherUsers(users);
272
+ });
273
+
274
+ socket.on('user_disconnected', (data) => {
275
+ removeUser(data.username);
276
+ });
277
+ }
278
+
279
+ function createFootprint() {
280
+ const footprint = document.createElement('div');
281
+ footprint.className = 'footprint';
282
+
283
+ const footLeft = document.createElement('div');
284
+ const footRight = document.createElement('div');
285
+
286
+ footLeft.className = 'foot left';
287
+ footRight.className = 'foot right';
288
+
289
+ footprint.appendChild(footLeft);
290
+ footprint.appendChild(footRight);
291
+
292
+ return footprint;
293
+ }
294
+
295
+ function getAngle(p0, p1) {
296
+ const deltaX = p1.lng - p0.lng;
297
+ const deltaY = p1.lat - p0.lat;
298
+ return Math.atan2(deltaY, deltaX);
299
+ }
300
+
301
+ function createUserIcon(name) {
302
+ const icon = document.createElement('div');
303
+ icon.className = 'name-banner';
304
+ icon.textContent = name;
305
+
306
+ return L.divIcon({
307
+ className: 'user-marker',
308
+ html: icon.outerHTML,
309
+ iconSize: [160, 40],
310
+ iconAnchor: [80, 20]
311
+ });
312
+ }
313
+
314
+ function placeFootprint(position, angle) {
315
+ const footprint = createFootprint();
316
+ const point = map.latLngToLayerPoint([position.lat, position.lng]);
317
+
318
+ footprint.style.left = `${point.x}px`;
319
+ footprint.style.top = `${point.y}px`;
320
+ footprint.style.transform = `rotate(${angle + Math.PI/2}rad)`;
321
+
322
+ document.querySelector('#track').appendChild(footprint);
323
+
324
+ setTimeout(() => {
325
+ footprint.remove();
326
+ }, 15000);
327
+ }
328
+
329
+ function updateUserPosition(position) {
330
+ const now = Date.now();
331
+ if (now - lastUpdateTime < updateInterval) return;
332
+ lastUpdateTime = now;
333
+
334
+ const { latitude, longitude } = position.coords;
335
+ const newPosition = { lat: latitude, lng: longitude };
336
+
337
+ if (!userMarker) {
338
+ userMarker = L.marker([latitude, longitude], {
339
+ icon: createUserIcon(username)
340
+ }).addTo(map);
341
+ map.setView([latitude, longitude], 16);
342
+ } else {
343
+ userMarker.setLatLng([latitude, longitude]);
344
+ }
345
+
346
+ if (lastPosition) {
347
+ const angle = getAngle(lastPosition, newPosition);
348
+ placeFootprint(newPosition, angle);
349
+ }
350
+
351
+ lastPosition = newPosition;
352
+
353
+ socket.emit('update_location', {
354
+ username: username,
355
+ location: newPosition
356
+ });
357
+ }
358
+
359
+ function updateOtherUsers(users) {
360
+ for (const [name, data] of Object.entries(users)) {
361
+ if (name === username) continue;
362
+
363
+ if (!otherUsers[name]) {
364
+ otherUsers[name] = L.marker([data.location.lat, data.location.lng], {
365
+ icon: createUserIcon(name)
366
+ }).addTo(map);
367
+ } else {
368
+ const oldPos = otherUsers[name].getLatLng();
369
+ const newPos = L.latLng(data.location.lat, data.location.lng);
370
+ otherUsers[name].setLatLng(newPos);
371
+
372
+ const angle = getAngle(
373
+ { lat: oldPos.lat, lng: oldPos.lng },
374
+ { lat: newPos.lat, lng: newPos.lng }
375
+ );
376
+ placeFootprint(data.location, angle);
377
+ }
378
+ }
379
+
380
+ // 移除已断开连接的用户
381
+ for (const name of Object.keys(otherUsers)) {
382
+ if (!users[name]) {
383
+ map.removeLayer(otherUsers[name]);
384
+ delete otherUsers[name];
385
+ }
386
+ }
387
+ }
388
+
389
+ function removeUser(username) {
390
+ if (otherUsers[username]) {
391
+ map.removeLayer(otherUsers[username]);
392
+ delete otherUsers[username];
393
+ }
394
+ }
395
+
396
+ function updateUsername() {
397
+ const newName = document.getElementById('username').value.trim();
398
+ if (newName) {
399
+ const oldUsername = username;
400
+ username = newName;
401
+ if (userMarker) {
402
+ userMarker.setIcon(createUserIcon(username));
403
+ }
404
+ socket.emit('update_username', {
405
+ old_username: oldUsername,
406
+ new_username: username
407
+ });
408
+ }
409
+ }
410
+
411
+ function initMap() {
412
+ map = L.map('leaflet-map', {
413
+ center: [39.9042, 116.4074],
414
+ zoom: 16,
415
+ zoomControl: false,
416
+ attributionControl: false
417
+ });
418
+
419
+ L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
420
+ maxZoom: 19
421
+ }).addTo(map);
422
+
423
+ // 等待cloud.png动画结束后开始初始化其他功能
424
+ const mapMask = document.getElementById('mapMask');
425
+ mapMask.addEventListener('animationend', () => {
426
+ initSocket();
427
+
428
+ if ("geolocation" in navigator) {
429
+ navigator.geolocation.watchPosition(
430
+ updateUserPosition,
431
+ error => console.error("Error getting location:", error),
432
+ { enableHighAccuracy: true }
433
+ );
434
+ } else {
435
+ alert("你的浏览器不支持地理定位功能");
436
+ }
437
+ });
438
+ }
439
+
440
+ // 启动应用
441
+ initMap();
442
+ </script>
443
+ </body>
444
+ </html>
map-bg.png ADDED

Git LFS Details

  • SHA256: 832e9a6d8acc3f167d5d55149d427c9a55ea1ada730317fada086a9addebc895
  • Pointer size: 132 Bytes
  • Size of remote file: 2.54 MB