-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathindex.html
More file actions
299 lines (270 loc) · 13.5 KB
/
index.html
File metadata and controls
299 lines (270 loc) · 13.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Runback - 运动轨迹回放</title>
<meta name="description" content="加载 FIT 文件,在地图上动画播放运动轨迹">
<!-- 高德地图 JS API -->
<script>
// 加载高德地图 API
// 生产环境: 从 Cloudflare Function 获取 Key
// 本地开发: 从 .apikey 文件获取 Key
(async function loadAMapAPI() {
let apiKey = null;
// 1. 尝试从 URL 参数获取(用于调试)
const urlParams = new URLSearchParams(window.location.search);
apiKey = urlParams.get('key');
// 2. 尝试从 Cloudflare Function 获取(生产环境)
if (!apiKey) {
try {
const response = await fetch('/api/amap-key');
if (response.ok) {
const data = await response.json();
apiKey = data.key;
}
} catch (e) {
console.log('Cloudflare Function not available, trying local file...');
}
}
// 3. 尝试从 .apikey 文件获取(本地开发)
if (!apiKey) {
try {
const response = await fetch('.apikey');
if (response.ok) {
const text = await response.text();
const match = text.match(/AMAP_API_KEY=(.+)/);
if (match && match[1]) {
apiKey = match[1].trim();
}
}
} catch (e) {
console.log('.apikey file not found');
}
}
if (apiKey) {
const script = document.createElement('script');
script.src = `https://webapi.amap.com/maps?v=2.0&key=${apiKey}`;
script.onload = () => {
console.log('AMap API loaded successfully');
// 加载截图插件
const screenshotScript = document.createElement('script');
screenshotScript.src = 'https://cdn.jsdelivr.net/npm/@amap/screenshot/dist/index.js';
screenshotScript.onload = () => {
console.log('AMap Screenshot plugin loaded');
window.dispatchEvent(new Event('AMapLoaded'));
};
screenshotScript.onerror = () => {
console.warn('Screenshot plugin failed to load, continuing without it');
window.dispatchEvent(new Event('AMapLoaded'));
};
document.head.appendChild(screenshotScript);
};
script.onerror = () => console.error('Failed to load AMap API');
document.head.appendChild(script);
} else {
console.error('AMap API Key not found');
}
})();
</script>
<!-- FIT 文件使用内置原生解析器,无需外部库 -->
<!-- Chart.js for data visualization -->
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.1/dist/chart.umd.min.js"></script>
<!-- Google Fonts -->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
<!-- Icons -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
<link rel="stylesheet" href="styles.css">
<link rel="stylesheet" href="landing-page.css">
</head>
<body>
<div class="app-container">
<!-- 顶部导航栏 -->
<header class="header">
<div class="header-left">
<div class="logo">
<i class="fas fa-running"></i>
<span>Runback</span>
</div>
</div>
<div class="header-right">
<button id="btn-new-file" class="btn-icon" title="加载新文件">
<i class="fas fa-file-upload"></i>
</button>
<button id="theme-toggle" class="btn-icon" title="切换主题">
<i class="fas fa-moon"></i>
</button>
<div class="menu-container hidden">
<!-- Menu removed as per design -->
</div>
</div>
</header>
<!-- 主内容区 -->
<main class="main-content">
<!-- 地图容器 -->
<div class="map-wrapper">
<div id="map" class="map-container"></div>
<!-- Runback 启动页 - 完全匹配设计稿V2 -->
<div id="landing-page" class="landing-page">
<!-- 左侧:3D运动人物 + 渐变背景 -->
<div class="landing-split-left">
<div class="landing-gradient-bg"></div>
<div class="landing-hero-image"></div>
</div>
<!-- 右侧:品牌内容区 -->
<div class="landing-split-right">
<!-- Logo区域 -->
<div class="landing-brand">
<h1 class="landing-logo">Runback</h1>
<p class="landing-tagline">重温你的运动时刻</p>
<p class="landing-tagline-en">Relive Your Running Moments</p>
</div>
<!-- 功能特性卡片 -->
<div class="landing-features-list">
<!-- Feature 1 -->
<div class="landing-feature-card">
<div class="feature-icon">
<i class="fas fa-map-marked-alt"></i>
</div>
<div class="feature-content">
<h3>轨迹回放</h3>
<p>在地图上动态回放你的运动路线</p>
</div>
</div>
<!-- Feature 2 -->
<div class="landing-feature-card">
<div class="feature-icon">
<i class="fas fa-chart-bar"></i>
</div>
<div class="feature-content">
<h3>数据分析</h3>
<p>深入分析配速、心率、海拔数据</p>
</div>
</div>
<!-- Feature 3 -->
<div class="landing-feature-card">
<div class="feature-icon">
<i class="fas fa-play-circle"></i>
</div>
<div class="feature-content">
<h3>实时回放</h3>
<p>同步数据和视频,重现运动过程</p>
</div>
</div>
</div>
<!-- 上传按钮 -->
<div class="landing-cta">
<input type="file" id="landing-file-input" accept=".fit,.gpx,.tcx,.kml" multiple hidden>
<button id="landing-upload-btn" class="landing-upload-button">
<span>上传运动数据</span>
<i class="fas fa-arrow-up"></i>
</button>
<p class="landing-formats">支持: GPX, TCX, FIT, KML(最多4个文件)</p>
</div>
</div>
</div>
<!-- 地图控制按钮 -->
<div class="map-controls">
<button id="zoom-in" class="btn-icon" title="放大">
<i class="fas fa-plus"></i>
</button>
<button id="zoom-out" class="btn-icon" title="缩小">
<i class="fas fa-minus"></i>
</button>
<button id="fit-bounds" class="btn-icon" title="适应轨迹">
<i class="fas fa-expand"></i>
</button>
</div>
<!-- 数据面板(可展开) -->
<div id="data-panel" class="data-panel hidden">
<!-- 面板主内容区(折叠时隐藏) -->
<div class="panel-content">
<!-- 数据摘要区(多用户支持) -->
<div id="data-summary" class="data-summary">
<!-- 由 JavaScript 动态生成多用户数据行 -->
</div>
<!-- 图表区(展开时显示:配速/心率/海拔) -->
<div id="charts-container" class="charts-container collapsed">
<div class="chart-wrapper">
<div class="chart-overlay">
<span class="chart-title">配速</span>
<div id="chart-pace-values" class="chart-values">
<!-- 多用户配速值由 JS 动态生成 -->
</div>
</div>
<div class="chart-body">
<canvas id="pace-chart"></canvas>
</div>
</div>
<div class="chart-wrapper">
<div class="chart-overlay">
<span class="chart-title">心率</span>
<div id="chart-heartrate-values" class="chart-values">
<!-- 多用户心率值由 JS 动态生成 -->
</div>
</div>
<div class="chart-body">
<canvas id="heartrate-chart"></canvas>
</div>
</div>
<div class="chart-wrapper">
<div class="chart-overlay">
<span class="chart-title">海拔</span>
<div id="chart-altitude-values" class="chart-values">
<!-- 多用户海拔值由 JS 动态生成 -->
</div>
</div>
<div class="chart-body">
<canvas id="altitude-chart"></canvas>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- 播放控制栏 -->
<div id="player-controls" class="player-controls hidden">
<div class="player-timeline">
<span id="current-time" class="time-display">00:00:00</span>
<div id="timeline" class="timeline">
<div id="timeline-progress" class="timeline-progress"></div>
<div id="timeline-handle" class="timeline-handle"></div>
</div>
<span id="total-time" class="time-display">00:00:00</span>
</div>
<div class="player-buttons">
<button id="btn-begin" class="btn-icon" title="跳转到开始">
<i class="fas fa-step-backward"></i>
</button>
<button id="btn-play" class="btn-icon btn-play" title="播放">
<i class="fas fa-play"></i>
</button>
<button id="btn-end" class="btn-icon" title="跳转到结束">
<i class="fas fa-step-forward"></i>
</button>
<div class="speed-control">
<select id="speed-select" class="speed-select">
<option value="1">1x</option>
<option value="10">10x</option>
<option value="30">30x</option>
<option value="60">60x</option>
<option value="120" selected>120x</option>
<option value="180">180x</option>
<option value="240">240x</option>
</select>
</div>
<!-- 录制按钮 -->
<div class="record-control">
<button id="btn-record" class="btn-icon" title="录制视频">
<i class="fas fa-circle"></i>
</button>
</div>
</div>
</div>
</main>
</div>
<script src="app.js"></script>
</body>
</html>