diff --git a/.eslintignore b/.eslintignore index e57be2186..0af4d59c6 100644 --- a/.eslintignore +++ b/.eslintignore @@ -48,3 +48,5 @@ package-lock.json /suites/react-stockcharts /suites-experimental/responsive-design/build-dev/ +/suites-experimental/tools-gps-tracks +/suites-experimental/tools-gps-tracks/** diff --git a/.prettierignore b/.prettierignore index 0b1b6168e..9f52acec8 100644 --- a/.prettierignore +++ b/.prettierignore @@ -48,3 +48,5 @@ package-lock.json /suites/react-stockcharts /suites-experimental/responsive-design/build-dev/ /suites-experimental/responsive-design/**/*.generated.css +/suites-experimental/tools-gps-tracks +/suites-experimental/tools-gps-tracks/** diff --git a/suites-experimental/suites.mjs b/suites-experimental/suites.mjs index 2e4496c79..89fa57d3d 100644 --- a/suites-experimental/suites.mjs +++ b/suites-experimental/suites.mjs @@ -304,4 +304,72 @@ export const ExperimentalSuites = freezeSuites([ }), ], }, + { + name: "Tools-GPS-Tracks", + url: "suites-experimental/tools-gps-tracks/dist/index.html", + resources: "suites-experimental/tools-gps-tracks/dist/resources.txt", + tags: ["experimental", "charts", "gpx"], + type: "async", + async prepare(page) { + await page.waitForElement("#app-is-ready"); + }, + tests: [ + new BenchmarkTestStep("LoadAndParseFiles", async (page) => { + const complete = new Promise((resolve) => { + page.addEventListener("load-and-parse-complete", resolve, { once: true }); + }); + page.querySelector("#btn-step-load").click(); + await complete; + page.layout(); + }), + new BenchmarkTestStep("DisplayAnalysisMetrics", async (page) => { + const complete = new Promise((resolve) => { + page.addEventListener("analysis-complete", resolve, { once: true }); + }); + page.querySelector("#btn-step-analysis").click(); + await complete; + page.layout(); + }), + new BenchmarkTestStep("DisplayInsights", async (page) => { + const complete = new Promise((resolve) => { + page.addEventListener("insights-complete", resolve, { once: true }); + }); + page.querySelector("#btn-step-insights").click(); + await complete; + page.layout(); + }), + new BenchmarkTestStep("DisplayDetails", async (page) => { + const complete = new Promise((resolve) => { + page.addEventListener("details-complete", resolve, { once: true }); + }); + page.querySelector("#btn-step-details").click(); + await complete; + page.layout(); + }), + new BenchmarkTestStep("DisplayAggregateMetrics", async (page) => { + const complete = new Promise((resolve) => { + page.addEventListener("aggregate-complete", resolve, { once: true }); + }); + page.querySelector("#btn-step-aggregate").click(); + await complete; + page.layout(); + }), + new BenchmarkTestStep("DisplayEvolutionMetrics", async (page) => { + const complete = new Promise((resolve) => { + page.addEventListener("evolution-complete", resolve, { once: true }); + }); + page.querySelector("#btn-step-evolution").click(); + await complete; + page.layout(); + }), + new BenchmarkTestStep("DisplayCourses", async (page) => { + const complete = new Promise((resolve) => { + page.addEventListener("courses-complete", resolve, { once: true }); + }); + page.querySelector("#btn-step-courses").click(); + await complete; + page.layout(); + }), + ], + }, ]); diff --git a/suites-experimental/tools-gps-tracks/.gitignore b/suites-experimental/tools-gps-tracks/.gitignore new file mode 100644 index 000000000..3c3629e64 --- /dev/null +++ b/suites-experimental/tools-gps-tracks/.gitignore @@ -0,0 +1 @@ +node_modules diff --git a/suites-experimental/tools-gps-tracks/.prettierignore b/suites-experimental/tools-gps-tracks/.prettierignore new file mode 100644 index 000000000..f4464f026 --- /dev/null +++ b/suites-experimental/tools-gps-tracks/.prettierignore @@ -0,0 +1,3 @@ +dist/ +test-data/ +src/css/uPlot.min.css diff --git a/suites-experimental/tools-gps-tracks/.prettierrc b/suites-experimental/tools-gps-tracks/.prettierrc new file mode 100644 index 000000000..ca8527e0d --- /dev/null +++ b/suites-experimental/tools-gps-tracks/.prettierrc @@ -0,0 +1,7 @@ +{ + "semi": true, + "trailingComma": "all", + "singleQuote": true, + "printWidth": 100, + "tabWidth": 2 +} diff --git a/suites-experimental/tools-gps-tracks/LICENSE b/suites-experimental/tools-gps-tracks/LICENSE new file mode 100644 index 000000000..74eccfcc9 --- /dev/null +++ b/suites-experimental/tools-gps-tracks/LICENSE @@ -0,0 +1,28 @@ +BSD 3-Clause License + +Copyright (c) 2026, Camillo Bruni + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/suites-experimental/tools-gps-tracks/README.md b/suites-experimental/tools-gps-tracks/README.md new file mode 100644 index 000000000..ca1dad4d1 --- /dev/null +++ b/suites-experimental/tools-gps-tracks/README.md @@ -0,0 +1,28 @@ +# Tools-GPS-Tracks Workload + +## Description + +This workload simulates an offline-capable GPS track analyzer and activity dashboard (`weg-strasse`). It parses binary and XML activity tracks (`.fit`, `.gpx`, `.tcx`, `.kml`) compressed with `gzip`, persists activity data in IndexedDB, renders interactive Leaflet vector maps with Canvas polylines, and computes physiological and geospatial analytics plotted with `uPlot`. + +## Benchmark Steps + +1. **LoadAndParseFiles**: Fetches, decompresses (`DecompressionStream`), and parses 6 synthetic GPS activity tracks (`.gpx.gz`, `.tcx.gz`, `.fit.gz`), computes track statistics and power/heart-rate duration curves, persists tracks in IndexedDB, and renders the map and metric charts. +2. **DisplayAnalysisMetrics**: Simulates brush selection and hover interactions across telemetry charts, updating track statistics and map segments over the selected range. +3. **DisplayInsights**: Renders the Insights view with power and heart-rate duration curves, physiological training zones, aerobic decoupling / W' balance analytics, and kilometer split tables. +4. **DisplayDetails**: Renders the Details view with device/sensor metadata, lap and gear-shift breakdowns, and editing interactions. +5. **DisplayAggregateMetrics**: Renders the Aggregate view, computing multi-track sliding-window cross-correlations, comparative telemetry tables, and overlaid multi-track charts. +6. **DisplayEvolutionMetrics**: Renders the Evolution and Accumulated views, modeling acute/chronic training load (ATL/CTL/TSB) and plotting multi-metric progression and cumulative timelines. +7. **DisplayCourses**: Creates a multi-waypoint course, matches candidate tracks passing through geospatial gates, and renders course segment leaderboards and corridor comparisons. + +## Requirements + +Please always compile inside the latest `node` LTS requirement (`>=24.0.0`). + +## Building + +Run `npm install` inside `/suites-experimental/tools-gps-tracks`, then run `npm run build` to typecheck, generate deterministic compressed test tracks in `test-data/`, bundle the application and sourcemaps into `dist/`, and generate `dist/resources.txt`. + +## Develop & Run Locally + +1. Run `npm install` inside `/suites-experimental/tools-gps-tracks`. +2. Run `npm run dev` to watch, rebuild on source changes, and start a local development server for `dist/`. diff --git a/suites-experimental/tools-gps-tracks/build.js b/suites-experimental/tools-gps-tracks/build.js new file mode 100644 index 000000000..2e7df211f --- /dev/null +++ b/suites-experimental/tools-gps-tracks/build.js @@ -0,0 +1,224 @@ +// Copyright (c) 2026, Camillo Bruni. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import * as esbuild from 'esbuild'; +import { readFile, writeFile, mkdir, copyFile, cp, rm, readdir } from 'fs/promises'; +import path from 'path'; +import { execSync } from 'child_process'; +import { generateResourcesFile } from '../../resources/shared/generate-resources.mjs'; + +const args = process.argv.slice(2); +const watch = args.includes('--watch'); + +const baseConfig = { + bundle: true, + minify: !watch, + sourcemap: true, + format: 'esm', + target: ['es2020'], + outdir: 'dist', + assetNames: '[name]', + outExtension: { '.js': '.mjs' }, + loader: { + '.woff2': 'file', + '.png': 'file', + '.svg': 'file', + }, +}; + +async function copyStaticAssets() { + const assets = [ + { src: 'src/index.html', dest: 'index.html' }, + { src: 'material_symbols_rounded.woff2', dest: 'material_symbols_rounded.woff2' }, + ]; + await mkdir('dist', { recursive: true }); + + for (const asset of assets) { + try { + await copyFile(asset.src, path.join('dist', asset.dest)); + console.log(`Copied ${asset.src} to dist/${asset.dest}`); + } catch (e) { + console.warn(`Could not copy ${asset.src}: ${e.message}`); + } + } + + try { + await cp('test-data', path.join('dist', 'test-data'), { recursive: true }); + console.log('Copied test-data to dist/test-data'); + } catch (e) { + console.warn(`Could not copy test-data: ${e.message}`); + } +} + +async function collectLicenses() { + const deps = ['uplot', 'fit-file-parser']; + let output = ''; + + try { + const rootLicense = await readFile('LICENSE', 'utf-8'); + output += `TOOLS-GPS-TRACKS LICENSE\n===========\n\n${rootLicense}\n\n`; + } catch (e) {} + + for (const dep of deps) { + try { + const licensePath = path.join('node_modules', dep, 'LICENSE'); + const content = await readFile(licensePath, 'utf-8'); + output += `\n\n------------------------------------------------------------------------------\n${dep.toUpperCase()} LICENSE\n------------------------------------------------------------------------------\n\n${content}\n`; + } catch (e) { + console.warn(`Could not find license for ${dep}`); + } + } + + output += `\n\n------------------------------------------------------------------------------\nBUNDLED FONT ASSETS LICENSE (MATERIAL SYMBOLS)\n------------------------------------------------------------------------------\n\n`; + output += `Assets: material_symbols_rounded.woff2 (and hashed build assets)\n`; + output += `Copyright Google LLC\n\n`; + output += `Licensed under the Apache License, Version 2.0 (the "License");\n`; + output += `you may not use these assets except in compliance with the License.\n`; + output += `You may obtain a copy of the License at\n\n`; + output += ` http://www.apache.org/licenses/LICENSE-2.0\n\n`; + output += `Unless required by applicable law or agreed to in writing, software\n`; + output += `distributed under the License is distributed on an "AS IS" BASIS,\n`; + output += `WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n`; + output += `See the License for the specific language governing permissions and\n`; + output += `limitations under the License.\n\n`; + output += `MODIFICATION AND SUBSETTING NOTICE (Apache License 2.0, Section 4(b)):\n`; + output += `These font binaries are subsetted and modified derivatives of the original\n`; + output += `Google Material Symbols font files, containing only the\n`; + output += `specific glyphs and icons utilized by the Tools-GPS-Tracks benchmark workload.\n`; + + try { + const testDataDir = path.join('dist', 'test-data'); + let testFiles = []; + try { + const manifestPath = path.join(testDataDir, 'manifest.json'); + const manifestData = JSON.parse(await readFile(manifestPath, 'utf-8')); + testFiles = Array.isArray(manifestData) ? [...manifestData, 'manifest.json'] : []; + } catch (e) { + testFiles = await readdir(testDataDir); + } + + output += `\n\n------------------------------------------------------------------------------\nBUNDLED GPS TEST TRACKS LICENSE (dist/test-data/*)\n------------------------------------------------------------------------------\n\n`; + output += `Bundled synthetic GPS test track files:\n`; + testFiles.sort().forEach((file) => { + output += `- dist/test-data/${file}\n`; + }); + output += `\nBSD 3-Clause License\n\n`; + output += `Copyright (c) 2026, Camillo Bruni / Speedometer Contributors\n\n`; + output += `Redistribution and use in source and binary forms, with or without\n`; + output += `modification, are permitted provided that the following conditions are met:\n\n`; + output += `1. Redistributions of source code must retain the above copyright notice, this\n`; + output += ` list of conditions and the following disclaimer.\n\n`; + output += `2. Redistributions in binary form must reproduce the above copyright notice,\n`; + output += ` this list of conditions and the following disclaimer in the documentation\n`; + output += ` and/or other materials provided with the distribution.\n\n`; + output += `3. Neither the name of the copyright holder nor the names of its\n`; + output += ` contributors may be used to endorse or promote products derived from\n`; + output += ` this software without specific prior written permission.\n\n`; + output += `THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"\n`; + output += `AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n`; + output += `IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE\n`; + output += `DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE\n`; + output += `FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\n`; + output += `DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR\n`; + output += `SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER\n`; + output += `CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,\n`; + output += `OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\n`; + output += `OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n`; + } catch (e) { + console.warn(`Could not collect bundled GPS test track licenses: ${e.message}`); + } + + await mkdir('dist', { recursive: true }); + await writeFile(path.join('dist', 'LICENSE'), output); + console.log('Generated dist/LICENSE with NPM dependencies and bundled data assets.'); +} + +function generateTestData() { + console.log('Generating synthetic GPS track test data...'); + execSync('node scripts/generate-test-data.mjs', { stdio: 'inherit' }); +} + +async function build() { + await rm('dist', { recursive: true, force: true }); + generateTestData(); + + // 1. Bundle main app code + const ctxWeg = await esbuild.context({ + ...baseConfig, + entryPoints: ['src/app.ts'], + entryNames: 'tools-gps-tracks', + external: ['uplot', 'fit-file-parser'], + }); + + const ctxOverride = await esbuild.context({ + ...baseConfig, + entryPoints: ['src/benchmark-override.ts'], + entryNames: 'benchmark-override', + }); + + // 2. Bundle Vendors + const vendorEntryPoints = { + uplot: 'node_modules/uplot/dist/uPlot.esm.js', + 'fit-file-parser': 'src/vendor-fit.ts', + }; + + const vendorCtxs = await Promise.all( + Object.entries(vendorEntryPoints).map(([name, entry]) => + esbuild.context({ + ...baseConfig, + entryPoints: [entry], + entryNames: name, + }), + ), + ); + + // 3. Bundle CSS + const ctxCss = await esbuild.context({ + ...baseConfig, + entryPoints: ['src/css/styles.css'], + entryNames: 'styles', + }); + + if (watch) { + await copyStaticAssets(); + await Promise.all([ + ctxWeg.watch(), + ctxOverride.watch(), + ctxCss.watch(), + ...vendorCtxs.map((c) => c.watch()), + ]); + await generateResourcesFile(path.join(import.meta.dirname, 'dist')); + + const { host, port } = await ctxWeg.serve({ + servedir: 'dist', + fallback: 'index.html', + }); + console.log(`Development server: http://${host}:${port}`); + } else { + await Promise.all([ + ctxWeg.rebuild(), + ctxOverride.rebuild(), + ctxCss.rebuild(), + ...vendorCtxs.map((c) => c.rebuild()), + ]); + + await copyStaticAssets(); + await collectLicenses(); + + await Promise.all([ + ctxWeg.dispose(), + ctxOverride.dispose(), + ctxCss.dispose(), + ...vendorCtxs.map((c) => c.dispose()), + ]); + + await generateResourcesFile(path.join(import.meta.dirname, 'dist')); + console.log('Build complete. Production files and resources.txt are in dist/'); + } +} + +build().catch((err) => { + console.error(err); + process.exit(1); +}); diff --git a/suites-experimental/tools-gps-tracks/dist/LICENSE b/suites-experimental/tools-gps-tracks/dist/LICENSE new file mode 100644 index 000000000..ab7852cb5 --- /dev/null +++ b/suites-experimental/tools-gps-tracks/dist/LICENSE @@ -0,0 +1,158 @@ +TOOLS-GPS-TRACKS LICENSE +=========== + +BSD 3-Clause License + +Copyright (c) 2026, Camillo Bruni + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + + + +------------------------------------------------------------------------------ +UPLOT LICENSE +------------------------------------------------------------------------------ + +The MIT License (MIT) + +Copyright (c) 2022 Leon Sorokin + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + + +------------------------------------------------------------------------------ +FIT-FILE-PARSER LICENSE +------------------------------------------------------------------------------ + +The MIT License (MIT) + +Copyright (c) 2015 Pierre Jacquier +http://pierrejacquier.com | @pierremtb + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + + + +------------------------------------------------------------------------------ +BUNDLED FONT ASSETS LICENSE (MATERIAL SYMBOLS) +------------------------------------------------------------------------------ + +Assets: material_symbols_rounded.woff2 (and hashed build assets) +Copyright Google LLC + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use these assets except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +MODIFICATION AND SUBSETTING NOTICE (Apache License 2.0, Section 4(b)): +These font binaries are subsetted and modified derivatives of the original +Google Material Symbols font files, containing only the +specific glyphs and icons utilized by the Tools-GPS-Tracks benchmark workload. + + +------------------------------------------------------------------------------ +BUNDLED GPS TEST TRACKS LICENSE (dist/test-data/*) +------------------------------------------------------------------------------ + +Bundled synthetic GPS test track files: +- dist/test-data/activity_1_alps_stage.gpx.gz +- dist/test-data/activity_2_forest_mtb.gpx.gz +- dist/test-data/activity_3_road_race.tcx.gz +- dist/test-data/activity_4_intervals.tcx.gz +- dist/test-data/activity_5_grand_tour.fit.gz +- dist/test-data/activity_6_weekend_ride.fit.gz +- dist/test-data/manifest.json + +BSD 3-Clause License + +Copyright (c) 2026, Camillo Bruni / Speedometer Contributors + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/suites-experimental/tools-gps-tracks/dist/benchmark-override.mjs b/suites-experimental/tools-gps-tracks/dist/benchmark-override.mjs new file mode 100644 index 000000000..bbaea5108 --- /dev/null +++ b/suites-experimental/tools-gps-tracks/dist/benchmark-override.mjs @@ -0,0 +1,2 @@ +var o=539363104;window.__resetBenchmarkSeed=()=>{o=539363104};Math.random=()=>{let e=o+=1831565813;return e=Math.imul(e^e>>>15,e|1),e^=e+Math.imul(e^e>>>7,e|61),((e^e>>>14)>>>0)/4294967296};window.requestAnimationFrame=e=>window.setTimeout(()=>e(performance.now()),0);window.cancelAnimationFrame=e=>{window.clearTimeout(e)};try{Object.defineProperty(window,"devicePixelRatio",{get:()=>1,configurable:!0})}catch{}try{Object.defineProperty(navigator,"language",{get:()=>"en-US",configurable:!0}),Object.defineProperty(navigator,"languages",{get:()=>["en-US"],configurable:!0})}catch{}typeof CanvasRenderingContext2D<"u"&&(CanvasRenderingContext2D.prototype.measureText=function(e){let n=this.font||"10px sans-serif",i=n.match(/(\d+(?:\.\d+)?)px/),t=i?parseFloat(i[1]):10,r=/\bbold\b|\b[6-9]00\b/i.test(n),c=t*(r?.6:.55),a=String(e).length*c;return{width:a,actualBoundingBoxLeft:0,actualBoundingBoxRight:a,actualBoundingBoxAscent:t*.8,actualBoundingBoxDescent:t*.2,fontBoundingBoxAscent:t*.8,fontBoundingBoxDescent:t*.2,emHeightAscent:t*.8,emHeightDescent:t*.2,hangingBaseline:t*.8,alphabeticBaseline:0,ideographicBaseline:-t*.2}}); +//# sourceMappingURL=benchmark-override.mjs.map diff --git a/suites-experimental/tools-gps-tracks/dist/benchmark-override.mjs.map b/suites-experimental/tools-gps-tracks/dist/benchmark-override.mjs.map new file mode 100644 index 000000000..979aa8e1f --- /dev/null +++ b/suites-experimental/tools-gps-tracks/dist/benchmark-override.mjs.map @@ -0,0 +1,7 @@ +{ + "version": 3, + "sources": ["../src/benchmark-override.ts"], + "sourcesContent": ["// Copyright (c) 2026, Camillo Bruni.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n\n'use strict';\n\n// Deterministic PRNG (Mulberry32) for reproducible benchmark runs.\nlet seed = 0x20260720;\n(window as any).__resetBenchmarkSeed = (): void => {\n seed = 0x20260720;\n};\nMath.random = (): number => {\n let t = (seed += 0x6d2b79f5);\n t = Math.imul(t ^ (t >>> 15), t | 1);\n t ^= t + Math.imul(t ^ (t >>> 7), t | 61);\n return ((t ^ (t >>> 14)) >>> 0) / 4294967296;\n};\n\nwindow.requestAnimationFrame = (callback: FrameRequestCallback): number => {\n return window.setTimeout(() => callback(performance.now()), 0);\n};\n\nwindow.cancelAnimationFrame = (id: number): void => {\n window.clearTimeout(id);\n};\n\n// Normalize devicePixelRatio to 1 so uPlot and canvas rendering execute\n// identical code paths (e.g. decimation thresholds and canvas dimensions) across browsers.\ntry {\n Object.defineProperty(window, 'devicePixelRatio', {\n get: () => 1,\n configurable: true,\n });\n} catch (_) {}\n\n// Normalize navigator.language to en-US so Intl.NumberFormat / locale formatters\n// produce identical strings across browsers and OS locales.\ntry {\n Object.defineProperty(navigator, 'language', {\n get: () => 'en-US',\n configurable: true,\n });\n Object.defineProperty(navigator, 'languages', {\n get: () => ['en-US'],\n configurable: true,\n });\n} catch (_) {}\n\n// Normalize CanvasRenderingContext2D.prototype.measureText so canvas layout calculations\n// (histogram padding, axis label bounding boxes, cursor pills) do not vary with OS/browser\n// system-ui font glyph metrics.\nif (typeof CanvasRenderingContext2D !== 'undefined') {\n CanvasRenderingContext2D.prototype.measureText = function (text: string): TextMetrics {\n const fontStr = this.font || '10px sans-serif';\n const sizeMatch = fontStr.match(/(\\d+(?:\\.\\d+)?)px/);\n const fontSize = sizeMatch ? parseFloat(sizeMatch[1]) : 10;\n const isBold = /\\bbold\\b|\\b[6-9]00\\b/i.test(fontStr);\n const charWidth = fontSize * (isBold ? 0.6 : 0.55);\n const width = String(text).length * charWidth;\n return {\n width,\n actualBoundingBoxLeft: 0,\n actualBoundingBoxRight: width,\n actualBoundingBoxAscent: fontSize * 0.8,\n actualBoundingBoxDescent: fontSize * 0.2,\n fontBoundingBoxAscent: fontSize * 0.8,\n fontBoundingBoxDescent: fontSize * 0.2,\n emHeightAscent: fontSize * 0.8,\n emHeightDescent: fontSize * 0.2,\n hangingBaseline: fontSize * 0.8,\n alphabeticBaseline: 0,\n ideographicBaseline: -fontSize * 0.2,\n } as TextMetrics;\n };\n}\n"], + "mappings": "AAOA,IAAIA,EAAO,UACV,OAAe,qBAAuB,IAAY,CACjDA,EAAO,SACT,EACA,KAAK,OAAS,IAAc,CAC1B,IAAIC,EAAKD,GAAQ,WACjB,OAAAC,EAAI,KAAK,KAAKA,EAAKA,IAAM,GAAKA,EAAI,CAAC,EACnCA,GAAKA,EAAI,KAAK,KAAKA,EAAKA,IAAM,EAAIA,EAAI,EAAE,IAC/BA,EAAKA,IAAM,MAAS,GAAK,UACpC,EAEA,OAAO,sBAAyBC,GACvB,OAAO,WAAW,IAAMA,EAAS,YAAY,IAAI,CAAC,EAAG,CAAC,EAG/D,OAAO,qBAAwBC,GAAqB,CAClD,OAAO,aAAaA,CAAE,CACxB,EAIA,GAAI,CACF,OAAO,eAAe,OAAQ,mBAAoB,CAChD,IAAK,IAAM,EACX,aAAc,EAChB,CAAC,CACH,MAAY,CAAC,CAIb,GAAI,CACF,OAAO,eAAe,UAAW,WAAY,CAC3C,IAAK,IAAM,QACX,aAAc,EAChB,CAAC,EACD,OAAO,eAAe,UAAW,YAAa,CAC5C,IAAK,IAAM,CAAC,OAAO,EACnB,aAAc,EAChB,CAAC,CACH,MAAY,CAAC,CAKT,OAAO,yBAA6B,MACtC,yBAAyB,UAAU,YAAc,SAAUC,EAA2B,CACpF,IAAMC,EAAU,KAAK,MAAQ,kBACvBC,EAAYD,EAAQ,MAAM,mBAAmB,EAC7CE,EAAWD,EAAY,WAAWA,EAAU,CAAC,CAAC,EAAI,GAClDE,EAAS,wBAAwB,KAAKH,CAAO,EAC7CI,EAAYF,GAAYC,EAAS,GAAM,KACvCE,EAAQ,OAAON,CAAI,EAAE,OAASK,EACpC,MAAO,CACL,MAAAC,EACA,sBAAuB,EACvB,uBAAwBA,EACxB,wBAAyBH,EAAW,GACpC,yBAA0BA,EAAW,GACrC,sBAAuBA,EAAW,GAClC,uBAAwBA,EAAW,GACnC,eAAgBA,EAAW,GAC3B,gBAAiBA,EAAW,GAC5B,gBAAiBA,EAAW,GAC5B,mBAAoB,EACpB,oBAAqB,CAACA,EAAW,EACnC,CACF", + "names": ["seed", "t", "callback", "id", "text", "fontStr", "sizeMatch", "fontSize", "isBold", "charWidth", "width"] +} diff --git a/suites-experimental/tools-gps-tracks/dist/fit-file-parser.mjs b/suites-experimental/tools-gps-tracks/dist/fit-file-parser.mjs new file mode 100644 index 000000000..9d5189e66 --- /dev/null +++ b/suites-experimental/tools-gps-tracks/dist/fit-file-parser.mjs @@ -0,0 +1,15 @@ +var ot=Object.create;var Ie=Object.defineProperty;var ut=Object.getOwnPropertyDescriptor;var ct=Object.getOwnPropertyNames;var ft=Object.getPrototypeOf,dt=Object.prototype.hasOwnProperty;var le=(i,e)=>()=>(e||i((e={exports:{}}).exports,e),e.exports);var pt=(i,e,t,n)=>{if(e&&typeof e=="object"||typeof e=="function")for(let s of ct(e))!dt.call(i,s)&&s!==t&&Ie(i,s,{get:()=>e[s],enumerable:!(n=ut(e,s))||n.enumerable});return i};var gt=(i,e,t)=>(t=i!=null?ot(ft(i)):{},pt(e||!i||!i.__esModule?Ie(t,"default",{value:i,enumerable:!0}):t,i));var je=le(K=>{"use strict";K.byteLength=mt;K.toByteArray=wt;K.fromByteArray=xt;var z=[],B=[],ht=typeof Uint8Array<"u"?Uint8Array:Array,ae="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";for(M=0,Ue=ae.length;M0)throw new Error("Invalid string. Length must be a multiple of 4");var t=i.indexOf("=");t===-1&&(t=e);var n=t===e?0:4-t%4;return[t,n]}function mt(i){var e=Ae(i),t=e[0],n=e[1];return(t+n)*3/4-n}function yt(i,e,t){return(e+t)*3/4-t}function wt(i){var e,t=Ae(i),n=t[0],s=t[1],_=new ht(yt(i,n,s)),l=0,r=s>0?n-4:n,o;for(o=0;o>16&255,_[l++]=e>>8&255,_[l++]=e&255;return s===2&&(e=B[i.charCodeAt(o)]<<2|B[i.charCodeAt(o+1)]>>4,_[l++]=e&255),s===1&&(e=B[i.charCodeAt(o)]<<10|B[i.charCodeAt(o+1)]<<4|B[i.charCodeAt(o+2)]>>2,_[l++]=e>>8&255,_[l++]=e&255),_}function bt(i){return z[i>>18&63]+z[i>>12&63]+z[i>>6&63]+z[i&63]}function kt(i,e,t){for(var n,s=[],_=e;_r?r:l+_));return n===1?(e=i[t-1],s.push(z[e>>2]+z[e<<4&63]+"==")):n===2&&(e=(i[t-2]<<8)+i[t-1],s.push(z[e>>10]+z[e>>4&63]+z[e<<2&63]+"=")),s.join("")}});var Te=le(re=>{re.read=function(i,e,t,n,s){var _,l,r=s*8-n-1,o=(1<>1,u=-7,c=t?s-1:0,g=t?-1:1,f=i[e+c];for(c+=g,_=f&(1<<-u)-1,f>>=-u,u+=r;u>0;_=_*256+i[e+c],c+=g,u-=8);for(l=_&(1<<-u)-1,_>>=-u,u+=n;u>0;l=l*256+i[e+c],c+=g,u-=8);if(_===0)_=1-d;else{if(_===o)return l?NaN:(f?-1:1)*(1/0);l=l+Math.pow(2,n),_=_-d}return(f?-1:1)*l*Math.pow(2,_-n)};re.write=function(i,e,t,n,s,_){var l,r,o,d=_*8-s-1,u=(1<>1,g=s===23?Math.pow(2,-24)-Math.pow(2,-77):0,f=n?0:_-1,x=n?1:-1,h=e<0||e===0&&1/e<0?1:0;for(e=Math.abs(e),isNaN(e)||e===1/0?(r=isNaN(e)?1:0,l=u):(l=Math.floor(Math.log(e)/Math.LN2),e*(o=Math.pow(2,-l))<1&&(l--,o*=2),l+c>=1?e+=g/o:e+=g*Math.pow(2,1-c),e*o>=2&&(l++,o/=2),l+c>=u?(r=0,l=u):l+c>=1?(r=(e*o-1)*Math.pow(2,s),l=l+c):(r=e*Math.pow(2,c-1)*Math.pow(2,s),l=0));s>=8;i[t+f]=r&255,f+=x,r/=256,s-=8);for(l=l<0;i[t+f]=l&255,f+=x,l/=256,d-=8);i[t+f-x]|=h*128}});var Xe=le(V=>{"use strict";var oe=je(),P=Te(),Ce=typeof Symbol=="function"&&typeof Symbol.for=="function"?Symbol.for("nodejs.util.inspect.custom"):null;V.Buffer=a;V.SlowBuffer=qt;V.INSPECT_MAX_BYTES=50;var Z=2147483647;V.kMaxLength=Z;a.TYPED_ARRAY_SUPPORT=vt();!a.TYPED_ARRAY_SUPPORT&&typeof console<"u"&&typeof console.error=="function"&&console.error("This browser lacks typed array (Uint8Array) support which is required by `buffer` v5.x. Use `buffer` v4.x if you require old browser support.");function vt(){try{let i=new Uint8Array(1),e={foo:function(){return 42}};return Object.setPrototypeOf(e,Uint8Array.prototype),Object.setPrototypeOf(i,e),i.foo()===42}catch{return!1}}Object.defineProperty(a.prototype,"parent",{enumerable:!0,get:function(){if(a.isBuffer(this))return this.buffer}});Object.defineProperty(a.prototype,"offset",{enumerable:!0,get:function(){if(a.isBuffer(this))return this.byteOffset}});function T(i){if(i>Z)throw new RangeError('The value "'+i+'" is invalid for option "size"');let e=new Uint8Array(i);return Object.setPrototypeOf(e,a.prototype),e}function a(i,e,t){if(typeof i=="number"){if(typeof e=="string")throw new TypeError('The "string" argument must be of type string. Received type number');return de(i)}return Le(i,e,t)}a.poolSize=8192;function Le(i,e,t){if(typeof i=="string")return Bt(i,e);if(ArrayBuffer.isView(i))return Et(i);if(i==null)throw new TypeError("The first argument must be one of type string, Buffer, ArrayBuffer, Array, or Array-like Object. Received type "+typeof i);if(q(i,ArrayBuffer)||i&&q(i.buffer,ArrayBuffer)||typeof SharedArrayBuffer<"u"&&(q(i,SharedArrayBuffer)||i&&q(i.buffer,SharedArrayBuffer)))return ce(i,e,t);if(typeof i=="number")throw new TypeError('The "value" argument must not be of type number. Received type number');let n=i.valueOf&&i.valueOf();if(n!=null&&n!==i)return a.from(n,e,t);let s=zt(i);if(s)return s;if(typeof Symbol<"u"&&Symbol.toPrimitive!=null&&typeof i[Symbol.toPrimitive]=="function")return a.from(i[Symbol.toPrimitive]("string"),e,t);throw new TypeError("The first argument must be one of type string, Buffer, ArrayBuffer, Array, or Array-like Object. Received type "+typeof i)}a.from=function(i,e,t){return Le(i,e,t)};Object.setPrototypeOf(a.prototype,Uint8Array.prototype);Object.setPrototypeOf(a,Uint8Array);function Me(i){if(typeof i!="number")throw new TypeError('"size" argument must be of type number');if(i<0)throw new RangeError('The value "'+i+'" is invalid for option "size"')}function Ft(i,e,t){return Me(i),i<=0?T(i):e!==void 0?typeof t=="string"?T(i).fill(e,t):T(i).fill(e):T(i)}a.alloc=function(i,e,t){return Ft(i,e,t)};function de(i){return Me(i),T(i<0?0:pe(i)|0)}a.allocUnsafe=function(i){return de(i)};a.allocUnsafeSlow=function(i){return de(i)};function Bt(i,e){if((typeof e!="string"||e==="")&&(e="utf8"),!a.isEncoding(e))throw new TypeError("Unknown encoding: "+e);let t=Ne(i,e)|0,n=T(t),s=n.write(i,e);return s!==t&&(n=n.slice(0,s)),n}function ue(i){let e=i.length<0?0:pe(i.length)|0,t=T(e);for(let n=0;n=Z)throw new RangeError("Attempt to allocate Buffer larger than maximum size: 0x"+Z.toString(16)+" bytes");return i|0}function qt(i){return+i!=i&&(i=0),a.alloc(+i)}a.isBuffer=function(e){return e!=null&&e._isBuffer===!0&&e!==a.prototype};a.compare=function(e,t){if(q(e,Uint8Array)&&(e=a.from(e,e.offset,e.byteLength)),q(t,Uint8Array)&&(t=a.from(t,t.offset,t.byteLength)),!a.isBuffer(e)||!a.isBuffer(t))throw new TypeError('The "buf1", "buf2" arguments must be one of type Buffer or Uint8Array');if(e===t)return 0;let n=e.length,s=t.length;for(let _=0,l=Math.min(n,s);_s.length?(a.isBuffer(l)||(l=a.from(l)),l.copy(s,_)):Uint8Array.prototype.set.call(s,l,_);else if(a.isBuffer(l))l.copy(s,_);else throw new TypeError('"list" argument must be an Array of Buffers');_+=l.length}return s};function Ne(i,e){if(a.isBuffer(i))return i.length;if(ArrayBuffer.isView(i)||q(i,ArrayBuffer))return i.byteLength;if(typeof i!="string")throw new TypeError('The "string" argument must be one of type string, Buffer, or ArrayBuffer. Received type '+typeof i);let t=i.length,n=arguments.length>2&&arguments[2]===!0;if(!n&&t===0)return 0;let s=!1;for(;;)switch(e){case"ascii":case"latin1":case"binary":return t;case"utf8":case"utf-8":return fe(i).length;case"ucs2":case"ucs-2":case"utf16le":case"utf-16le":return t*2;case"hex":return t>>>1;case"base64":return Je(i).length;default:if(s)return n?-1:fe(i).length;e=(""+e).toLowerCase(),s=!0}}a.byteLength=Ne;function It(i,e,t){let n=!1;if((e===void 0||e<0)&&(e=0),e>this.length||((t===void 0||t>this.length)&&(t=this.length),t<=0)||(t>>>=0,e>>>=0,t<=e))return"";for(i||(i="utf8");;)switch(i){case"hex":return Mt(this,e,t);case"utf8":case"utf-8":return $e(this,e,t);case"ascii":return Dt(this,e,t);case"latin1":case"binary":return Lt(this,e,t);case"base64":return Rt(this,e,t);case"ucs2":case"ucs-2":case"utf16le":case"utf-16le":return Nt(this,e,t);default:if(n)throw new TypeError("Unknown encoding: "+i);i=(i+"").toLowerCase(),n=!0}}a.prototype._isBuffer=!0;function N(i,e,t){let n=i[e];i[e]=i[t],i[t]=n}a.prototype.swap16=function(){let e=this.length;if(e%2!==0)throw new RangeError("Buffer size must be a multiple of 16-bits");for(let t=0;tt&&(e+=" ... "),""};Ce&&(a.prototype[Ce]=a.prototype.inspect);a.prototype.compare=function(e,t,n,s,_){if(q(e,Uint8Array)&&(e=a.from(e,e.offset,e.byteLength)),!a.isBuffer(e))throw new TypeError('The "target" argument must be one of type Buffer or Uint8Array. Received type '+typeof e);if(t===void 0&&(t=0),n===void 0&&(n=e?e.length:0),s===void 0&&(s=0),_===void 0&&(_=this.length),t<0||n>e.length||s<0||_>this.length)throw new RangeError("out of range index");if(s>=_&&t>=n)return 0;if(s>=_)return-1;if(t>=n)return 1;if(t>>>=0,n>>>=0,s>>>=0,_>>>=0,this===e)return 0;let l=_-s,r=n-t,o=Math.min(l,r),d=this.slice(s,_),u=e.slice(t,n);for(let c=0;c2147483647?t=2147483647:t<-2147483648&&(t=-2147483648),t=+t,he(t)&&(t=s?0:i.length-1),t<0&&(t=i.length+t),t>=i.length){if(s)return-1;t=i.length-1}else if(t<0)if(s)t=0;else return-1;if(typeof e=="string"&&(e=a.from(e,n)),a.isBuffer(e))return e.length===0?-1:Re(i,e,t,n,s);if(typeof e=="number")return e=e&255,typeof Uint8Array.prototype.indexOf=="function"?s?Uint8Array.prototype.indexOf.call(i,e,t):Uint8Array.prototype.lastIndexOf.call(i,e,t):Re(i,[e],t,n,s);throw new TypeError("val must be string, number or Buffer")}function Re(i,e,t,n,s){let _=1,l=i.length,r=e.length;if(n!==void 0&&(n=String(n).toLowerCase(),n==="ucs2"||n==="ucs-2"||n==="utf16le"||n==="utf-16le")){if(i.length<2||e.length<2)return-1;_=2,l/=2,r/=2,t/=2}function o(u,c){return _===1?u[c]:u.readUInt16BE(c*_)}let d;if(s){let u=-1;for(d=t;dl&&(t=l-r),d=t;d>=0;d--){let u=!0;for(let c=0;cs&&(n=s)):n=s;let _=e.length;n>_/2&&(n=_/2);let l;for(l=0;l>>0,isFinite(n)?(n=n>>>0,s===void 0&&(s="utf8")):(s=n,n=void 0);else throw new Error("Buffer.write(string, encoding, offset[, length]) is no longer supported");let _=this.length-t;if((n===void 0||n>_)&&(n=_),e.length>0&&(n<0||t<0)||t>this.length)throw new RangeError("Attempt to write outside buffer bounds");s||(s="utf8");let l=!1;for(;;)switch(s){case"hex":return Ut(this,e,t,n);case"utf8":case"utf-8":return At(this,e,t,n);case"ascii":case"latin1":case"binary":return jt(this,e,t,n);case"base64":return Tt(this,e,t,n);case"ucs2":case"ucs-2":case"utf16le":case"utf-16le":return Ct(this,e,t,n);default:if(l)throw new TypeError("Unknown encoding: "+s);s=(""+s).toLowerCase(),l=!0}};a.prototype.toJSON=function(){return{type:"Buffer",data:Array.prototype.slice.call(this._arr||this,0)}};function Rt(i,e,t){return e===0&&t===i.length?oe.fromByteArray(i):oe.fromByteArray(i.slice(e,t))}function $e(i,e,t){t=Math.min(i.length,t);let n=[],s=e;for(;s239?4:_>223?3:_>191?2:1;if(s+r<=t){let o,d,u,c;switch(r){case 1:_<128&&(l=_);break;case 2:o=i[s+1],(o&192)===128&&(c=(_&31)<<6|o&63,c>127&&(l=c));break;case 3:o=i[s+1],d=i[s+2],(o&192)===128&&(d&192)===128&&(c=(_&15)<<12|(o&63)<<6|d&63,c>2047&&(c<55296||c>57343)&&(l=c));break;case 4:o=i[s+1],d=i[s+2],u=i[s+3],(o&192)===128&&(d&192)===128&&(u&192)===128&&(c=(_&15)<<18|(o&63)<<12|(d&63)<<6|u&63,c>65535&&c<1114112&&(l=c))}}l===null?(l=65533,r=1):l>65535&&(l-=65536,n.push(l>>>10&1023|55296),l=56320|l&1023),n.push(l),s+=r}return St(n)}var Se=4096;function St(i){let e=i.length;if(e<=Se)return String.fromCharCode.apply(String,i);let t="",n=0;for(;nn)&&(t=n);let s="";for(let _=e;_n&&(e=n),t<0?(t+=n,t<0&&(t=0)):t>n&&(t=n),tt)throw new RangeError("Trying to access beyond buffer length")}a.prototype.readUintLE=a.prototype.readUIntLE=function(e,t,n){e=e>>>0,t=t>>>0,n||b(e,t,this.length);let s=this[e],_=1,l=0;for(;++l>>0,t=t>>>0,n||b(e,t,this.length);let s=this[e+--t],_=1;for(;t>0&&(_*=256);)s+=this[e+--t]*_;return s};a.prototype.readUint8=a.prototype.readUInt8=function(e,t){return e=e>>>0,t||b(e,1,this.length),this[e]};a.prototype.readUint16LE=a.prototype.readUInt16LE=function(e,t){return e=e>>>0,t||b(e,2,this.length),this[e]|this[e+1]<<8};a.prototype.readUint16BE=a.prototype.readUInt16BE=function(e,t){return e=e>>>0,t||b(e,2,this.length),this[e]<<8|this[e+1]};a.prototype.readUint32LE=a.prototype.readUInt32LE=function(e,t){return e=e>>>0,t||b(e,4,this.length),(this[e]|this[e+1]<<8|this[e+2]<<16)+this[e+3]*16777216};a.prototype.readUint32BE=a.prototype.readUInt32BE=function(e,t){return e=e>>>0,t||b(e,4,this.length),this[e]*16777216+(this[e+1]<<16|this[e+2]<<8|this[e+3])};a.prototype.readBigUInt64LE=C(function(e){e=e>>>0,H(e,"offset");let t=this[e],n=this[e+7];(t===void 0||n===void 0)&&Y(e,this.length-8);let s=t+this[++e]*2**8+this[++e]*2**16+this[++e]*2**24,_=this[++e]+this[++e]*2**8+this[++e]*2**16+n*2**24;return BigInt(s)+(BigInt(_)<>>0,H(e,"offset");let t=this[e],n=this[e+7];(t===void 0||n===void 0)&&Y(e,this.length-8);let s=t*2**24+this[++e]*2**16+this[++e]*2**8+this[++e],_=this[++e]*2**24+this[++e]*2**16+this[++e]*2**8+n;return(BigInt(s)<>>0,t=t>>>0,n||b(e,t,this.length);let s=this[e],_=1,l=0;for(;++l=_&&(s-=Math.pow(2,8*t)),s};a.prototype.readIntBE=function(e,t,n){e=e>>>0,t=t>>>0,n||b(e,t,this.length);let s=t,_=1,l=this[e+--s];for(;s>0&&(_*=256);)l+=this[e+--s]*_;return _*=128,l>=_&&(l-=Math.pow(2,8*t)),l};a.prototype.readInt8=function(e,t){return e=e>>>0,t||b(e,1,this.length),this[e]&128?(255-this[e]+1)*-1:this[e]};a.prototype.readInt16LE=function(e,t){e=e>>>0,t||b(e,2,this.length);let n=this[e]|this[e+1]<<8;return n&32768?n|4294901760:n};a.prototype.readInt16BE=function(e,t){e=e>>>0,t||b(e,2,this.length);let n=this[e+1]|this[e]<<8;return n&32768?n|4294901760:n};a.prototype.readInt32LE=function(e,t){return e=e>>>0,t||b(e,4,this.length),this[e]|this[e+1]<<8|this[e+2]<<16|this[e+3]<<24};a.prototype.readInt32BE=function(e,t){return e=e>>>0,t||b(e,4,this.length),this[e]<<24|this[e+1]<<16|this[e+2]<<8|this[e+3]};a.prototype.readBigInt64LE=C(function(e){e=e>>>0,H(e,"offset");let t=this[e],n=this[e+7];(t===void 0||n===void 0)&&Y(e,this.length-8);let s=this[e+4]+this[e+5]*2**8+this[e+6]*2**16+(n<<24);return(BigInt(s)<>>0,H(e,"offset");let t=this[e],n=this[e+7];(t===void 0||n===void 0)&&Y(e,this.length-8);let s=(t<<24)+this[++e]*2**16+this[++e]*2**8+this[++e];return(BigInt(s)<>>0,t||b(e,4,this.length),P.read(this,e,!0,23,4)};a.prototype.readFloatBE=function(e,t){return e=e>>>0,t||b(e,4,this.length),P.read(this,e,!1,23,4)};a.prototype.readDoubleLE=function(e,t){return e=e>>>0,t||b(e,8,this.length),P.read(this,e,!0,52,8)};a.prototype.readDoubleBE=function(e,t){return e=e>>>0,t||b(e,8,this.length),P.read(this,e,!1,52,8)};function k(i,e,t,n,s,_){if(!a.isBuffer(i))throw new TypeError('"buffer" argument must be a Buffer instance');if(e>s||e<_)throw new RangeError('"value" argument is out of bounds');if(t+n>i.length)throw new RangeError("Index out of range")}a.prototype.writeUintLE=a.prototype.writeUIntLE=function(e,t,n,s){if(e=+e,t=t>>>0,n=n>>>0,!s){let r=Math.pow(2,8*n)-1;k(this,e,t,n,r,0)}let _=1,l=0;for(this[t]=e&255;++l>>0,n=n>>>0,!s){let r=Math.pow(2,8*n)-1;k(this,e,t,n,r,0)}let _=n-1,l=1;for(this[t+_]=e&255;--_>=0&&(l*=256);)this[t+_]=e/l&255;return t+n};a.prototype.writeUint8=a.prototype.writeUInt8=function(e,t,n){return e=+e,t=t>>>0,n||k(this,e,t,1,255,0),this[t]=e&255,t+1};a.prototype.writeUint16LE=a.prototype.writeUInt16LE=function(e,t,n){return e=+e,t=t>>>0,n||k(this,e,t,2,65535,0),this[t]=e&255,this[t+1]=e>>>8,t+2};a.prototype.writeUint16BE=a.prototype.writeUInt16BE=function(e,t,n){return e=+e,t=t>>>0,n||k(this,e,t,2,65535,0),this[t]=e>>>8,this[t+1]=e&255,t+2};a.prototype.writeUint32LE=a.prototype.writeUInt32LE=function(e,t,n){return e=+e,t=t>>>0,n||k(this,e,t,4,4294967295,0),this[t+3]=e>>>24,this[t+2]=e>>>16,this[t+1]=e>>>8,this[t]=e&255,t+4};a.prototype.writeUint32BE=a.prototype.writeUInt32BE=function(e,t,n){return e=+e,t=t>>>0,n||k(this,e,t,4,4294967295,0),this[t]=e>>>24,this[t+1]=e>>>16,this[t+2]=e>>>8,this[t+3]=e&255,t+4};function Pe(i,e,t,n,s){Ye(e,n,s,i,t,7);let _=Number(e&BigInt(4294967295));i[t++]=_,_=_>>8,i[t++]=_,_=_>>8,i[t++]=_,_=_>>8,i[t++]=_;let l=Number(e>>BigInt(32)&BigInt(4294967295));return i[t++]=l,l=l>>8,i[t++]=l,l=l>>8,i[t++]=l,l=l>>8,i[t++]=l,t}function He(i,e,t,n,s){Ye(e,n,s,i,t,7);let _=Number(e&BigInt(4294967295));i[t+7]=_,_=_>>8,i[t+6]=_,_=_>>8,i[t+5]=_,_=_>>8,i[t+4]=_;let l=Number(e>>BigInt(32)&BigInt(4294967295));return i[t+3]=l,l=l>>8,i[t+2]=l,l=l>>8,i[t+1]=l,l=l>>8,i[t]=l,t+8}a.prototype.writeBigUInt64LE=C(function(e,t=0){return Pe(this,e,t,BigInt(0),BigInt("0xffffffffffffffff"))});a.prototype.writeBigUInt64BE=C(function(e,t=0){return He(this,e,t,BigInt(0),BigInt("0xffffffffffffffff"))});a.prototype.writeIntLE=function(e,t,n,s){if(e=+e,t=t>>>0,!s){let o=Math.pow(2,8*n-1);k(this,e,t,n,o-1,-o)}let _=0,l=1,r=0;for(this[t]=e&255;++_>0)-r&255;return t+n};a.prototype.writeIntBE=function(e,t,n,s){if(e=+e,t=t>>>0,!s){let o=Math.pow(2,8*n-1);k(this,e,t,n,o-1,-o)}let _=n-1,l=1,r=0;for(this[t+_]=e&255;--_>=0&&(l*=256);)e<0&&r===0&&this[t+_+1]!==0&&(r=1),this[t+_]=(e/l>>0)-r&255;return t+n};a.prototype.writeInt8=function(e,t,n){return e=+e,t=t>>>0,n||k(this,e,t,1,127,-128),e<0&&(e=255+e+1),this[t]=e&255,t+1};a.prototype.writeInt16LE=function(e,t,n){return e=+e,t=t>>>0,n||k(this,e,t,2,32767,-32768),this[t]=e&255,this[t+1]=e>>>8,t+2};a.prototype.writeInt16BE=function(e,t,n){return e=+e,t=t>>>0,n||k(this,e,t,2,32767,-32768),this[t]=e>>>8,this[t+1]=e&255,t+2};a.prototype.writeInt32LE=function(e,t,n){return e=+e,t=t>>>0,n||k(this,e,t,4,2147483647,-2147483648),this[t]=e&255,this[t+1]=e>>>8,this[t+2]=e>>>16,this[t+3]=e>>>24,t+4};a.prototype.writeInt32BE=function(e,t,n){return e=+e,t=t>>>0,n||k(this,e,t,4,2147483647,-2147483648),e<0&&(e=4294967295+e+1),this[t]=e>>>24,this[t+1]=e>>>16,this[t+2]=e>>>8,this[t+3]=e&255,t+4};a.prototype.writeBigInt64LE=C(function(e,t=0){return Pe(this,e,t,-BigInt("0x8000000000000000"),BigInt("0x7fffffffffffffff"))});a.prototype.writeBigInt64BE=C(function(e,t=0){return He(this,e,t,-BigInt("0x8000000000000000"),BigInt("0x7fffffffffffffff"))});function Ve(i,e,t,n,s,_){if(t+n>i.length)throw new RangeError("Index out of range");if(t<0)throw new RangeError("Index out of range")}function Ge(i,e,t,n,s){return e=+e,t=t>>>0,s||Ve(i,e,t,4,34028234663852886e22,-34028234663852886e22),P.write(i,e,t,n,23,4),t+4}a.prototype.writeFloatLE=function(e,t,n){return Ge(this,e,t,!0,n)};a.prototype.writeFloatBE=function(e,t,n){return Ge(this,e,t,!1,n)};function We(i,e,t,n,s){return e=+e,t=t>>>0,s||Ve(i,e,t,8,17976931348623157e292,-17976931348623157e292),P.write(i,e,t,n,52,8),t+8}a.prototype.writeDoubleLE=function(e,t,n){return We(this,e,t,!0,n)};a.prototype.writeDoubleBE=function(e,t,n){return We(this,e,t,!1,n)};a.prototype.copy=function(e,t,n,s){if(!a.isBuffer(e))throw new TypeError("argument should be a Buffer");if(n||(n=0),!s&&s!==0&&(s=this.length),t>=e.length&&(t=e.length),t||(t=0),s>0&&s=this.length)throw new RangeError("Index out of range");if(s<0)throw new RangeError("sourceEnd out of bounds");s>this.length&&(s=this.length),e.length-t>>0,n=n===void 0?this.length:n>>>0,e||(e=0);let _;if(typeof e=="number")for(_=t;_2**32?s=De(String(t)):typeof t=="bigint"&&(s=String(t),(t>BigInt(2)**BigInt(32)||t<-(BigInt(2)**BigInt(32)))&&(s=De(s)),s+="n"),n+=` It must be ${e}. Received ${s}`,n},RangeError);function De(i){let e="",t=i.length,n=i[0]==="-"?1:0;for(;t>=n+4;t-=3)e=`_${i.slice(t-3,t)}${e}`;return`${i.slice(0,t)}${e}`}function Ot(i,e,t){H(e,"offset"),(i[e]===void 0||i[e+t]===void 0)&&Y(e,i.length-(t+1))}function Ye(i,e,t,n,s,_){if(i>t||i3?e===0||e===BigInt(0)?r=`>= 0${l} and < 2${l} ** ${(_+1)*8}${l}`:r=`>= -(2${l} ** ${(_+1)*8-1}${l}) and < 2 ** ${(_+1)*8-1}${l}`:r=`>= ${e}${l} and <= ${t}${l}`,new $.ERR_OUT_OF_RANGE("value",r,i)}Ot(n,s,_)}function H(i,e){if(typeof i!="number")throw new $.ERR_INVALID_ARG_TYPE(e,"number",i)}function Y(i,e,t){throw Math.floor(i)!==i?(H(i,t),new $.ERR_OUT_OF_RANGE(t||"offset","an integer",i)):e<0?new $.ERR_BUFFER_OUT_OF_BOUNDS:new $.ERR_OUT_OF_RANGE(t||"offset",`>= ${t?1:0} and <= ${e}`,i)}var $t=/[^+/0-9A-Za-z-_]/g;function Pt(i){if(i=i.split("=")[0],i=i.trim().replace($t,""),i.length<2)return"";for(;i.length%4!==0;)i=i+"=";return i}function fe(i,e){e=e||1/0;let t,n=i.length,s=null,_=[];for(let l=0;l55295&&t<57344){if(!s){if(t>56319){(e-=3)>-1&&_.push(239,191,189);continue}else if(l+1===n){(e-=3)>-1&&_.push(239,191,189);continue}s=t;continue}if(t<56320){(e-=3)>-1&&_.push(239,191,189),s=t;continue}t=(s-55296<<10|t-56320)+65536}else s&&(e-=3)>-1&&_.push(239,191,189);if(s=null,t<128){if((e-=1)<0)break;_.push(t)}else if(t<2048){if((e-=2)<0)break;_.push(t>>6|192,t&63|128)}else if(t<65536){if((e-=3)<0)break;_.push(t>>12|224,t>>6&63|128,t&63|128)}else if(t<1114112){if((e-=4)<0)break;_.push(t>>18|240,t>>12&63|128,t>>6&63|128,t&63|128)}else throw new Error("Invalid code point")}return _}function Ht(i){let e=[];for(let t=0;t>8,s=t%256,_.push(s),_.push(n);return _}function Je(i){return oe.toByteArray(Pt(i))}function Q(i,e,t,n){let s;for(s=0;s=e.length||s>=i.length);++s)e[s+t]=i[s];return s}function q(i,e){return i instanceof e||i!=null&&i.constructor!=null&&i.constructor.name!=null&&i.constructor.name===e.name}function he(i){return i!==i}var Gt=function(){let i="0123456789abcdef",e=new Array(256);for(let t=0;t<16;++t){let n=t*16;for(let s=0;s<16;++s)e[n+s]=i[t]+i[s]}return e}();function C(i){return typeof BigInt>"u"?Wt:i}function Wt(){throw new Error("BigInt not supported")}});var et=gt(Xe(),1);var Yt=14.5037738,w={scConst:180/Math.pow(2,31),options:{speedUnits:{"m/s":{multiplier:1,offset:0},mph:{multiplier:3600/1609.344,offset:0},"km/h":{multiplier:3600/1e3,offset:0}},lengthUnits:{m:{multiplier:1,offset:0},mi:{multiplier:1/1609.344,offset:0},km:{multiplier:1/1e3,offset:0}},temperatureUnits:{"\xB0C":{multiplier:1,offset:0},kelvin:{multiplier:1,offset:-273.15},fahrenheit:{multiplier:9/5,offset:32}},pressureUnits:{cbar:{multiplier:1,offset:0},bar:{multiplier:1/100,offset:0},psi:{multiplier:1/100*Yt,offset:0}}},messages:{0:{name:"file_id",0:{field:"type",type:"file",scale:null,offset:0,units:""},1:{field:"manufacturer",type:"manufacturer",scale:null,offset:0,units:""},2:{field:"product",type:"uint16",scale:null,offset:0,units:""},3:{field:"serial_number",type:"uint32z",scale:null,offset:0,units:""},4:{field:"time_created",type:"date_time",scale:null,offset:0,units:""},5:{field:"number",type:"uint16",scale:null,offset:0,units:""},8:{field:"product_name",type:"string",scale:null,offset:0,units:""}},1:{name:"capabilities",0:{field:"languages",type:"uint8z",scale:null,offset:0,units:""},1:{field:"sports",type:"sport_bits_0",scale:null,offset:0,units:""},21:{field:"workouts_supported",type:"workout_capabilities",scale:null,offset:0,units:""},23:{field:"connectivity_supported",type:"connectivity_capabilities",scale:null,offset:0,units:""}},2:{name:"device_settings",0:{field:"active_time_zone",type:"uint8",scale:null,offset:0,units:""},1:{field:"utc_offset",type:"uint32",scale:null,offset:0,units:""},2:{field:"time_offset",type:"uint32",scale:null,offset:0,units:"s"},5:{field:"time_zone_offset",type:"sint8",scale:4,offset:0,units:"hr"},55:{field:"display_orientation",type:"display_orientation",scale:null,offset:0,units:""},56:{field:"mounting_side",type:"side",scale:null,offset:0,units:""},94:{field:"number_of_screens",type:"uint8",scale:null,offset:0,units:""},95:{field:"smart_notification_display_orientation",type:"display_orientation",scale:null,offset:0,units:""}},3:{name:"user_profile",254:{field:"message_index",type:"message_index",scale:null,offset:0,units:""},0:{field:"friendly_name",type:"string",scale:null,offset:0,units:""},1:{field:"gender",type:"gender",scale:null,offset:0,units:""},2:{field:"age",type:"uint8",scale:null,offset:0,units:"years"},3:{field:"height",type:"uint8",scale:100,offset:0,units:"m"},4:{field:"weight",type:"uint16",scale:10,offset:0,units:"kg"},5:{field:"language",type:"language",scale:null,offset:0,units:""},6:{field:"elev_setting",type:"display_measure",scale:null,offset:0,units:""},7:{field:"weight_setting",type:"display_measure",scale:null,offset:0,units:""},8:{field:"resting_heart_rate",type:"uint8",scale:null,offset:0,units:"bpm"},9:{field:"default_max_running_heart_rate",type:"uint8",scale:null,offset:0,units:"bpm"},10:{field:"default_max_biking_heart_rate",type:"uint8",scale:null,offset:0,units:"bpm"},11:{field:"default_max_heart_rate",type:"uint8",scale:null,offset:0,units:"bpm"},12:{field:"hr_setting",type:"display_heart",scale:null,offset:0,units:""},13:{field:"speed_setting",type:"display_measure",scale:null,offset:0,units:""},14:{field:"dist_setting",type:"display_measure",scale:null,offset:0,units:""},16:{field:"power_setting",type:"display_power",scale:null,offset:0,units:""},17:{field:"activity_class",type:"activity_class",scale:null,offset:0,units:""},18:{field:"position_setting",type:"display_position",scale:null,offset:0,units:""},21:{field:"temperature_setting",type:"display_measure",scale:null,offset:0,units:""},22:{field:"local_id",type:"user_local_id",scale:null,offset:0,units:""},23:{field:"global_id",type:"byte",scale:null,offset:0,units:""},28:{field:"wake_time",type:"localtime_into_day",scale:null,offset:0,units:""},29:{field:"sleep_time",type:"localtime_into_day",scale:null,offset:0,units:""},30:{field:"height_setting",type:"display_measure",scale:null,offset:0,units:""}},4:{name:"hrm_profile",254:{field:"message_index",type:"message_index",scale:null,offset:0,units:""},0:{field:"enabled",type:"bool",scale:null,offset:0,units:""},1:{field:"hrm_ant_id",type:"uint16z",scale:null,offset:0,units:""},2:{field:"log_hrv",type:"bool",scale:null,offset:0,units:""},3:{field:"hrm_ant_id_trans_type",type:"uint8z",scale:null,offset:0,units:""}},5:{name:"sdm_profile",254:{field:"message_index",type:"message_index",scale:null,offset:0,units:""},0:{field:"enabled",type:"bool",scale:null,offset:0,units:""},1:{field:"sdm_ant_id",type:"uint16z",scale:null,offset:0,units:""},2:{field:"sdm_cal_factor",type:"uint16",scale:10,offset:0,units:"%"},3:{field:"odometer",type:"uint32",scale:100,offset:0,units:"m"},4:{field:"speed_source",type:"bool",scale:null,offset:0,units:""},5:{field:"sdm_ant_id_trans_type",type:"uint8z",scale:null,offset:0,units:""},7:{field:"odometer_rollover",type:"uint8",scale:null,offset:0,units:""}},6:{name:"bike_profile",254:{field:"message_index",type:"message_index",scale:null,offset:0,units:""},0:{field:"name",type:"string",scale:null,offset:0,units:""},1:{field:"sport",type:"sport",scale:null,offset:0,units:""},2:{field:"sub_sport",type:"sub_sport",scale:null,offset:0,units:""},3:{field:"odometer",type:"uint32",scale:100,offset:0,units:"m"},4:{field:"bike_spd_ant_id",type:"uint16z",scale:null,offset:0,units:""},5:{field:"bike_cad_ant_id",type:"uint16z",scale:null,offset:0,units:""},6:{field:"bike_spdcad_ant_id",type:"uint16z",scale:null,offset:0,units:""},7:{field:"bike_power_ant_id",type:"uint16z",scale:null,offset:0,units:""},8:{field:"custom_wheelsize",type:"uint16",scale:1e3,offset:0,units:"m"},9:{field:"auto_wheelsize",type:"uint16",scale:1e3,offset:0,units:"m"},10:{field:"bike_weight",type:"uint16",scale:10,offset:0,units:"kg"},11:{field:"power_cal_factor",type:"uint16",scale:10,offset:0,units:"%"},12:{field:"auto_wheel_cal",type:"bool",scale:null,offset:0,units:""},13:{field:"auto_power_zero",type:"bool",scale:null,offset:0,units:""},14:{field:"id",type:"uint8",scale:null,offset:0,units:""},15:{field:"spd_enabled",type:"bool",scale:null,offset:0,units:""},16:{field:"cad_enabled",type:"bool",scale:null,offset:0,units:""},17:{field:"spdcad_enabled",type:"bool",scale:null,offset:0,units:""},18:{field:"power_enabled",type:"bool",scale:null,offset:0,units:""},19:{field:"crank_length",type:"uint8",scale:2,offset:-110,units:"mm"},20:{field:"enabled",type:"bool",scale:null,offset:0,units:""},21:{field:"bike_spd_ant_id_trans_type",type:"uint8z",scale:null,offset:0,units:""},22:{field:"bike_cad_ant_id_trans_type",type:"uint8z",scale:null,offset:0,units:""},23:{field:"bike_spdcad_ant_id_trans_type",type:"uint8z",scale:null,offset:0,units:""},24:{field:"bike_power_ant_id_trans_type",type:"uint8z",scale:null,offset:0,units:""},37:{field:"odometer_rollover",type:"uint8",scale:null,offset:0,units:""},38:{field:"front_gear_num",type:"uint8z",scale:null,offset:0,units:""},39:{field:"front_gear",type:"uint8z",scale:null,offset:0,units:""},40:{field:"rear_gear_num",type:"uint8z",scale:null,offset:0,units:""},41:{field:"rear_gear",type:"uint8z",scale:null,offset:0,units:""},44:{field:"shimano_di2_enabled",type:"bool",scale:null,offset:0,units:""}},7:{name:"zones_target",1:{field:"max_heart_rate",type:"uint8",scale:null,offset:0,units:""},2:{field:"threshold_heart_rate",type:"uint8",scale:null,offset:0,units:""},3:{field:"functional_threshold_power",type:"uint16",scale:null,offset:0,units:""},5:{field:"hr_calc_type",type:"hr_zone_calc",scale:null,offset:0,units:""},7:{field:"pwr_calc_type",type:"pwr_zone_calc",scale:null,offset:0,units:""}},8:{name:"hr_zone",254:{field:"message_index",type:"message_index",scale:null,offset:0,units:""},1:{field:"high_bpm",type:"uint8",scale:null,offset:0,units:"bpm"},2:{field:"name",type:"string",scale:null,offset:0,units:""}},9:{name:"power_zone",254:{field:"message_index",type:"message_index",scale:null,offset:0,units:""},1:{field:"high_value",type:"uint16",scale:null,offset:0,units:"watts"},2:{field:"name",type:"string",scale:null,offset:0,units:""}},10:{name:"met_zone",254:{field:"message_index",type:"message_index",scale:null,offset:0,units:""},1:{field:"high_bpm",type:"uint8",scale:null,offset:0,units:""},2:{field:"calories",type:"uint16",scale:10,offset:0,units:"kcal / min"},3:{field:"fat_calories",type:"uint8",scale:10,offset:0,units:"kcal / min"}},12:{name:"sport",0:{field:"sport",type:"sport",scale:null,offset:0,units:""},1:{field:"sub_sport",type:"sub_sport",scale:null,offset:0,units:""},3:{field:"name",type:"string",scale:null,offset:0,units:""}},15:{name:"goal",254:{field:"message_index",type:"message_index",scale:null,offset:0,units:""},0:{field:"sport",type:"sport",scale:null,offset:0,units:""},1:{field:"sub_sport",type:"sub_sport",scale:null,offset:0,units:""},2:{field:"start_date",type:"date_time",scale:null,offset:0,units:""},3:{field:"end_date",type:"date_time",scale:null,offset:0,units:""},4:{field:"type",type:"goal",scale:null,offset:0,units:""},5:{field:"value",type:"uint32",scale:null,offset:0,units:""},6:{field:"repeat",type:"bool",scale:null,offset:0,units:""},7:{field:"target_value",type:"uint32",scale:null,offset:0,units:""},8:{field:"recurrence",type:"goal_recurrence",scale:null,offset:0,units:""},9:{field:"recurrence_value",type:"uint16",scale:null,offset:0,units:""},10:{field:"enabled",type:"bool",scale:null,offset:0,units:""}},18:{name:"session",254:{field:"message_index",type:"message_index",scale:null,offset:0,units:""},253:{field:"timestamp",type:"date_time",scale:null,offset:0,units:"s"},0:{field:"event",type:"event",scale:null,offset:0,units:""},1:{field:"event_type",type:"event_type",scale:null,offset:0,units:""},2:{field:"start_time",type:"date_time",scale:null,offset:0,units:""},3:{field:"start_position_lat",type:"sint32",scale:null,offset:0,units:"semicircles"},4:{field:"start_position_long",type:"sint32",scale:null,offset:0,units:"semicircles"},5:{field:"sport",type:"sport",scale:null,offset:0,units:""},6:{field:"sub_sport",type:"sub_sport",scale:null,offset:0,units:""},7:{field:"total_elapsed_time",type:"uint32",scale:1e3,offset:0,units:"s"},8:{field:"total_timer_time",type:"uint32",scale:1e3,offset:0,units:"s"},9:{field:"total_distance",type:"uint32",scale:100,offset:0,units:"m"},10:{field:"total_cycles",type:"uint32",scale:null,offset:0,units:"cycles"},11:{field:"total_calories",type:"uint16",scale:null,offset:0,units:"kcal"},13:{field:"total_fat_calories",type:"uint16",scale:null,offset:0,units:"kcal"},14:{field:"avg_speed",type:"uint16",scale:1e3,offset:0,units:"m/s"},15:{field:"max_speed",type:"uint16",scale:1e3,offset:0,units:"m/s"},16:{field:"avg_heart_rate",type:"uint8",scale:null,offset:0,units:"bpm"},17:{field:"max_heart_rate",type:"uint8",scale:null,offset:0,units:"bpm"},18:{field:"avg_cadence",type:"uint8",scale:null,offset:0,units:"rpm"},19:{field:"max_cadence",type:"uint8",scale:null,offset:0,units:"rpm"},20:{field:"avg_power",type:"uint16",scale:null,offset:0,units:"watts"},21:{field:"max_power",type:"uint16",scale:null,offset:0,units:"watts"},22:{field:"total_ascent",type:"uint16",scale:null,offset:0,units:"m"},23:{field:"total_descent",type:"uint16",scale:null,offset:0,units:"m"},24:{field:"total_training_effect",type:"uint8",scale:10,offset:0,units:""},25:{field:"first_lap_index",type:"uint16",scale:null,offset:0,units:""},26:{field:"num_laps",type:"uint16",scale:null,offset:0,units:""},27:{field:"event_group",type:"uint8",scale:null,offset:0,units:""},28:{field:"trigger",type:"session_trigger",scale:null,offset:0,units:""},29:{field:"nec_lat",type:"sint32",scale:null,offset:0,units:"semicircles"},30:{field:"nec_long",type:"sint32",scale:null,offset:0,units:"semicircles"},31:{field:"swc_lat",type:"sint32",scale:null,offset:0,units:"semicircles"},32:{field:"swc_long",type:"sint32",scale:null,offset:0,units:"semicircles"},34:{field:"normalized_power",type:"uint16",scale:null,offset:0,units:"watts"},35:{field:"training_stress_score",type:"uint16",scale:10,offset:0,units:"tss"},36:{field:"intensity_factor",type:"uint16",scale:1e3,offset:0,units:"if"},37:{field:"left_right_balance",type:"left_right_balance_100",scale:100,offset:0,units:"%"},38:{field:"end_position_lat",type:"sint32",scale:null,offset:0,units:"semicircles"},39:{field:"end_position_long",type:"sint32",scale:null,offset:0,units:"semicircles"},41:{field:"avg_stroke_count",type:"uint32",scale:10,offset:0,units:"strokes/lap"},42:{field:"avg_stroke_distance",type:"uint16",scale:100,offset:0,units:"m"},43:{field:"swim_stroke",type:"swim_stroke",scale:null,offset:0,units:"swim_stroke"},44:{field:"pool_length",type:"uint16",scale:100,offset:0,units:"m"},45:{field:"threshold_power",type:"uint16",scale:null,offset:0,units:"watts"},46:{field:"pool_length_unit",type:"display_measure",scale:null,offset:0,units:""},47:{field:"num_active_lengths",type:"uint16",scale:null,offset:0,units:"lengths"},48:{field:"total_work",type:"uint32",scale:null,offset:0,units:"J"},49:{field:"avg_altitude",type:"uint16",scale:5,offset:-500,units:"m"},50:{field:"max_altitude",type:"uint16",scale:5,offset:-500,units:"m"},51:{field:"gps_accuracy",type:"uint8",scale:null,offset:0,units:"m"},52:{field:"avg_grade",type:"sint16",scale:100,offset:0,units:"%"},53:{field:"avg_pos_grade",type:"sint16",scale:100,offset:0,units:"%"},54:{field:"avg_neg_grade",type:"sint16",scale:100,offset:0,units:"%"},55:{field:"max_pos_grade",type:"sint16",scale:100,offset:0,units:"%"},56:{field:"max_neg_grade",type:"sint16",scale:100,offset:0,units:"%"},57:{field:"avg_temperature",type:"sint8",scale:null,offset:0,units:"C"},58:{field:"max_temperature",type:"sint8",scale:null,offset:0,units:"C"},59:{field:"total_moving_time",type:"uint32",scale:1e3,offset:0,units:"s"},60:{field:"avg_pos_vertical_speed",type:"uint16",scale:1e3,offset:0,units:"m/s"},61:{field:"avg_neg_vertical_speed",type:"uint16",scale:1e3,offset:0,units:"m/s"},62:{field:"max_pos_vertical_speed",type:"uint16",scale:1e3,offset:0,units:"m/s"},63:{field:"max_neg_vertical_speed",type:"uint16",scale:1e3,offset:0,units:"m/s"},64:{field:"min_heart_rate",type:"uint8",scale:null,offset:0,units:"bpm"},65:{field:"time_in_hr_zone",type:"uint32_array",scale:1e3,offset:0,units:"s"},66:{field:"time_in_speed_zone",type:"uint32_array",scale:1e3,offset:0,units:"s"},67:{field:"time_in_cadence_zone",type:"uint32_array",scale:1e3,offset:0,units:"s"},68:{field:"time_in_power_zone",type:"uint32_array",scale:1e3,offset:0,units:"s"},69:{field:"avg_lap_time",type:"uint32",scale:1e3,offset:0,units:"s"},70:{field:"best_lap_index",type:"uint16",scale:null,offset:0,units:""},71:{field:"min_altitude",type:"uint16",scale:5,offset:-500,units:"m"},82:{field:"player_score",type:"uint16",scale:null,offset:0,units:""},83:{field:"opponent_score",type:"uint16",scale:null,offset:0,units:""},84:{field:"opponent_name",type:"string",scale:null,offset:0,units:""},85:{field:"stroke_count",type:"uint16",scale:null,offset:0,units:"counts"},86:{field:"zone_count",type:"uint16",scale:null,offset:0,units:"counts"},87:{field:"max_ball_speed",type:"uint16",scale:100,offset:0,units:"m/s"},88:{field:"avg_ball_speed",type:"uint16",scale:100,offset:0,units:"m/s"},89:{field:"avg_vertical_oscillation",type:"uint16",scale:10,offset:0,units:"mm"},90:{field:"avg_stance_time_percent",type:"uint16",scale:100,offset:0,units:"percent"},91:{field:"avg_stance_time",type:"uint16",scale:10,offset:0,units:"ms"},92:{field:"avg_fractional_cadence",type:"uint8",scale:128,offset:0,units:"rpm"},93:{field:"max_fractional_cadence",type:"uint8",scale:128,offset:0,units:"rpm"},94:{field:"total_fractional_cycles",type:"uint8",scale:128,offset:0,units:"cycles"},95:{field:"avg_total_hemoglobin_conc",type:"uint16",scale:100,offset:0,units:"g/dL"},96:{field:"min_total_hemoglobin_conc",type:"uint16",scale:100,offset:0,units:"g/dL"},97:{field:"max_total_hemoglobin_conc",type:"uint16",scale:100,offset:0,units:"g/dL"},98:{field:"avg_saturated_hemoglobin_percent",type:"uint16",scale:10,offset:0,units:"%"},99:{field:"min_saturated_hemoglobin_percent",type:"uint16",scale:10,offset:0,units:"%"},100:{field:"max_saturated_hemoglobin_percent",type:"uint16",scale:10,offset:0,units:"%"},101:{field:"avg_left_torque_effectiveness",type:"uint8",scale:2,offset:0,units:"percent"},102:{field:"avg_right_torque_effectiveness",type:"uint8",scale:2,offset:0,units:"percent"},103:{field:"avg_left_pedal_smoothness",type:"uint8",scale:2,offset:0,units:"percent"},104:{field:"avg_right_pedal_smoothness",type:"uint8",scale:2,offset:0,units:"percent"},105:{field:"avg_combined_pedal_smoothness",type:"uint8",scale:2,offset:0,units:"percent"},111:{field:"sport_index",type:"uint8",scale:null,offset:0,units:""},112:{field:"time_standing",type:"uint32",scale:1e3,offset:0,units:"s"},113:{field:"stand_count",type:"uint16",scale:null,offset:0,units:""},114:{field:"avg_left_pco",type:"sint8",scale:null,offset:0,units:"mm"},115:{field:"avg_right_pco",type:"sint8",scale:null,offset:0,units:"mm"},116:{field:"avg_left_power_phase",type:"uint8",scale:.7111111,offset:0,units:"degrees"},117:{field:"avg_left_power_phase_peak",type:"uint8",scale:.7111111,offset:0,units:"degrees"},118:{field:"avg_right_power_phase",type:"uint8",scale:.7111111,offset:0,units:"degrees"},119:{field:"avg_right_power_phase_peak",type:"uint8",scale:.7111111,offset:0,units:"degrees"},120:{field:"avg_power_position",type:"uint16",scale:null,offset:0,units:"watts"},121:{field:"max_power_position",type:"uint16",scale:null,offset:0,units:"watts"},122:{field:"avg_cadence_position",type:"uint8",scale:null,offset:0,units:"rpm"},123:{field:"max_cadence_position",type:"uint8",scale:null,offset:0,units:"rpm"},124:{field:"enhanced_avg_speed",type:"uint32",scale:1e3,offset:0,units:"m/s"},125:{field:"enhanced_max_speed",type:"uint32",scale:1e3,offset:0,units:"m/s"},126:{field:"enhanced_avg_altitude",type:"uint32",scale:5,offset:-500,units:"m"},127:{field:"enhanced_min_altitude",type:"uint32",scale:5,offset:-500,units:"m"},128:{field:"enhanced_max_altitude",type:"uint32",scale:5,offset:-500,units:"m"},129:{field:"avg_lev_motor_power",type:"uint16",scale:null,offset:0,units:"watts"},130:{field:"max_lev_motor_power",type:"uint16",scale:null,offset:0,units:"watts"},131:{field:"lev_battery_consumption",type:"uint8",scale:2,offset:0,units:"percent"},132:{field:"avg_vertical_ratio",type:"uint16",scale:100,offset:0,units:"percent"},133:{field:"avg_stance_time_balance",type:"uint16",scale:100,offset:0,units:"percent"},134:{field:"avg_step_length",type:"uint16",scale:10,offset:0,units:"mm"},137:{field:"total_anaerobic_training_effect",type:"uint8",scale:10,offset:0,units:""},139:{field:"avg_vam",type:"uint16",scale:1,offset:0,units:"m/h"},192:{field:"workout_feel",type:"uint8",scale:null,offset:0,units:""},193:{field:"workout_rpe",type:"uint8",scale:10,offset:0,units:""},110:{field:"sport_profile_name",type:"string",scale:null,offset:0,units:""},168:{field:"training_load_peak",type:"uint32",scale:1,offset:0,units:""},169:{field:"enhanced_avg_respiration_rate",type:"uint16",scale:100,offset:0,units:"breaths/min"},150:{field:"min_temperature",type:"sint8",scale:null,offset:0,units:"C"},170:{field:"enhanced_max_respiration_rate",type:"uint16",scale:100,offset:0,units:"breaths/min"},178:{field:"est_sweat_loss",type:"uint16",scale:1,offset:0,units:"ml"},180:{field:"enhanced_min_respiration_rate",type:"uint16",scale:100,offset:0,units:"breaths/min"},181:{field:"total_grit",type:"float32",scale:1,offset:0,units:"kGrit"},182:{field:"total_flow",type:"float32",scale:1,offset:0,units:"Flow"},183:{field:"jump_count",type:"uint16",scale:1,offset:0,units:""},186:{field:"avg_grit",type:"float32",scale:1,offset:0,units:"kGrit"},187:{field:"avg_flow",type:"float32",scale:1,offset:0,units:"Flow"},188:{field:"primary_benefit",type:"uint8",scale:1,offset:0,units:""},196:{field:"resting_calories",type:"uint16",scale:1,offset:0,units:"kcal"},214:{field:"avg_grit",type:"float32",scale:null,offset:0,units:""},215:{field:"avg_flow",type:"float32",scale:null,offset:0,units:""},140:{field:"recovery_advisor",type:"uint16",scale:1,offset:0,units:"min"}},19:{name:"lap",254:{field:"message_index",type:"message_index",scale:null,offset:0,units:""},253:{field:"timestamp",type:"date_time",scale:null,offset:0,units:"s"},0:{field:"event",type:"event",scale:null,offset:0,units:""},1:{field:"event_type",type:"event_type",scale:null,offset:0,units:""},2:{field:"start_time",type:"date_time",scale:null,offset:0,units:""},3:{field:"start_position_lat",type:"sint32",scale:null,offset:0,units:"semicircles"},4:{field:"start_position_long",type:"sint32",scale:null,offset:0,units:"semicircles"},5:{field:"end_position_lat",type:"sint32",scale:null,offset:0,units:"semicircles"},6:{field:"end_position_long",type:"sint32",scale:null,offset:0,units:"semicircles"},7:{field:"total_elapsed_time",type:"uint32",scale:1e3,offset:0,units:"s"},8:{field:"total_timer_time",type:"uint32",scale:1e3,offset:0,units:"s"},9:{field:"total_distance",type:"uint32",scale:100,offset:0,units:"m"},10:{field:"total_cycles",type:"uint32",scale:null,offset:0,units:"cycles"},11:{field:"total_calories",type:"uint16",scale:null,offset:0,units:"kcal"},12:{field:"total_fat_calories",type:"uint16",scale:null,offset:0,units:"kcal"},13:{field:"avg_speed",type:"uint16",scale:1e3,offset:0,units:"m/s"},14:{field:"max_speed",type:"uint16",scale:1e3,offset:0,units:"m/s"},15:{field:"avg_heart_rate",type:"uint8",scale:null,offset:0,units:"bpm"},16:{field:"max_heart_rate",type:"uint8",scale:null,offset:0,units:"bpm"},17:{field:"avg_cadence",type:"uint8",scale:null,offset:0,units:"rpm"},18:{field:"max_cadence",type:"uint8",scale:null,offset:0,units:"rpm"},19:{field:"avg_power",type:"uint16",scale:null,offset:0,units:"watts"},20:{field:"max_power",type:"uint16",scale:null,offset:0,units:"watts"},21:{field:"total_ascent",type:"uint16",scale:null,offset:0,units:"m"},22:{field:"total_descent",type:"uint16",scale:null,offset:0,units:"m"},23:{field:"intensity",type:"intensity",scale:null,offset:0,units:""},24:{field:"lap_trigger",type:"lap_trigger",scale:null,offset:0,units:""},25:{field:"sport",type:"sport",scale:null,offset:0,units:""},26:{field:"event_group",type:"uint8",scale:null,offset:0,units:""},32:{field:"num_lengths",type:"uint16",scale:null,offset:0,units:"lengths"},33:{field:"normalized_power",type:"uint16",scale:null,offset:0,units:"watts"},34:{field:"left_right_balance",type:"left_right_balance_100",scale:100,offset:0,units:"%"},35:{field:"first_length_index",type:"uint16",scale:null,offset:0,units:""},37:{field:"avg_stroke_distance",type:"uint16",scale:100,offset:0,units:"m"},38:{field:"swim_stroke",type:"swim_stroke",scale:null,offset:0,units:""},39:{field:"sub_sport",type:"sub_sport",scale:null,offset:0,units:""},40:{field:"num_active_lengths",type:"uint16",scale:null,offset:0,units:"lengths"},41:{field:"total_work",type:"uint32",scale:null,offset:0,units:"J"},42:{field:"avg_altitude",type:"uint16",scale:5,offset:-500,units:"m"},43:{field:"max_altitude",type:"uint16",scale:5,offset:-500,units:"m"},44:{field:"gps_accuracy",type:"uint8",scale:null,offset:0,units:"m"},45:{field:"avg_grade",type:"sint16",scale:100,offset:0,units:"%"},46:{field:"avg_pos_grade",type:"sint16",scale:100,offset:0,units:"%"},47:{field:"avg_neg_grade",type:"sint16",scale:100,offset:0,units:"%"},48:{field:"max_pos_grade",type:"sint16",scale:100,offset:0,units:"%"},49:{field:"max_neg_grade",type:"sint16",scale:100,offset:0,units:"%"},50:{field:"avg_temperature",type:"sint8",scale:null,offset:0,units:"C"},51:{field:"max_temperature",type:"sint8",scale:null,offset:0,units:"C"},52:{field:"total_moving_time",type:"uint32",scale:1e3,offset:0,units:"s"},53:{field:"avg_pos_vertical_speed",type:"uint16",scale:1e3,offset:0,units:"m/s"},54:{field:"avg_neg_vertical_speed",type:"uint16",scale:1e3,offset:0,units:"m/s"},55:{field:"max_pos_vertical_speed",type:"uint16",scale:1e3,offset:0,units:"m/s"},56:{field:"max_neg_vertical_speed",type:"uint16",scale:1e3,offset:0,units:"m/s"},57:{field:"time_in_hr_zone",type:"uint32_array",scale:1e3,offset:0,units:"s"},58:{field:"time_in_speed_zone",type:"uint32_array",scale:1e3,offset:0,units:"s"},59:{field:"time_in_cadence_zone",type:"uint32_array",scale:1e3,offset:0,units:"s"},60:{field:"time_in_power_zone",type:"uint32_array",scale:1e3,offset:0,units:"s"},61:{field:"repetition_num",type:"uint16",scale:null,offset:0,units:""},62:{field:"min_altitude",type:"uint16",scale:5,offset:-500,units:"m"},63:{field:"min_heart_rate",type:"uint8",scale:null,offset:0,units:"bpm"},71:{field:"wkt_step_index",type:"message_index",scale:null,offset:0,units:""},74:{field:"opponent_score",type:"uint16",scale:null,offset:0,units:""},75:{field:"stroke_count",type:"uint16",scale:null,offset:0,units:"counts"},76:{field:"zone_count",type:"uint16",scale:null,offset:0,units:"counts"},77:{field:"avg_vertical_oscillation",type:"uint16",scale:10,offset:0,units:"mm"},78:{field:"avg_stance_time_percent",type:"uint16",scale:100,offset:0,units:"percent"},79:{field:"avg_stance_time",type:"uint16",scale:10,offset:0,units:"ms"},80:{field:"avg_fractional_cadence",type:"uint8",scale:128,offset:0,units:"rpm"},81:{field:"max_fractional_cadence",type:"uint8",scale:128,offset:0,units:"rpm"},82:{field:"total_fractional_cycles",type:"uint8",scale:128,offset:0,units:"cycles"},83:{field:"player_score",type:"uint16",scale:null,offset:0,units:""},84:{field:"avg_total_hemoglobin_conc",type:"uint16",scale:100,offset:0,units:"g/dL"},85:{field:"min_total_hemoglobin_conc",type:"uint16",scale:100,offset:0,units:"g/dL"},86:{field:"max_total_hemoglobin_conc",type:"uint16",scale:100,offset:0,units:"g/dL"},87:{field:"avg_saturated_hemoglobin_percent",type:"uint16",scale:10,offset:0,units:"%"},88:{field:"min_saturated_hemoglobin_percent",type:"uint16",scale:10,offset:0,units:"%"},89:{field:"max_saturated_hemoglobin_percent",type:"uint16",scale:10,offset:0,units:"%"},91:{field:"avg_left_torque_effectiveness",type:"uint8",scale:2,offset:0,units:"percent"},92:{field:"avg_right_torque_effectiveness",type:"uint8",scale:2,offset:0,units:"percent"},93:{field:"avg_left_pedal_smoothness",type:"uint8",scale:2,offset:0,units:"percent"},94:{field:"avg_right_pedal_smoothness",type:"uint8",scale:2,offset:0,units:"percent"},95:{field:"avg_combined_pedal_smoothness",type:"uint8",scale:2,offset:0,units:"percent"},98:{field:"time_standing",type:"uint32",scale:1e3,offset:0,units:"s"},99:{field:"stand_count",type:"uint16",scale:null,offset:0,units:""},100:{field:"avg_left_pco",type:"sint8",scale:null,offset:0,units:"mm"},101:{field:"avg_right_pco",type:"sint8",scale:null,offset:0,units:"mm"},102:{field:"avg_left_power_phase",type:"uint8",scale:.7111111,offset:0,units:"degrees"},103:{field:"avg_left_power_phase_peak",type:"uint8",scale:.7111111,offset:0,units:"degrees"},104:{field:"avg_right_power_phase",type:"uint8",scale:.7111111,offset:0,units:"degrees"},105:{field:"avg_right_power_phase_peak",type:"uint8",scale:.7111111,offset:0,units:"degrees"},106:{field:"avg_power_position",type:"uint16",scale:null,offset:0,units:"watts"},107:{field:"max_power_position",type:"uint16",scale:null,offset:0,units:"watts"},108:{field:"avg_cadence_position",type:"uint8",scale:null,offset:0,units:"rpm"},109:{field:"max_cadence_position",type:"uint8",scale:null,offset:0,units:"rpm"},110:{field:"enhanced_avg_speed",type:"uint32",scale:1e3,offset:0,units:"m/s"},111:{field:"enhanced_max_speed",type:"uint32",scale:1e3,offset:0,units:"m/s"},112:{field:"enhanced_avg_altitude",type:"uint32",scale:5,offset:-500,units:"m"},113:{field:"enhanced_min_altitude",type:"uint32",scale:5,offset:-500,units:"m"},114:{field:"enhanced_max_altitude",type:"uint32",scale:5,offset:-500,units:"m"},115:{field:"avg_lev_motor_power",type:"uint16",scale:null,offset:0,units:"watts"},116:{field:"max_lev_motor_power",type:"uint16",scale:null,offset:0,units:"watts"},117:{field:"lev_battery_consumption",type:"uint8",scale:2,offset:0,units:"percent"},118:{field:"avg_vertical_ratio",type:"uint16",scale:100,offset:0,units:"percent"},119:{field:"avg_stance_time_balance",type:"uint16",scale:100,offset:0,units:"percent"},120:{field:"avg_step_length",type:"uint16",scale:10,offset:0,units:"mm"},121:{field:"avg_vam",type:"uint16",scale:1e3,offset:0,units:"m/s"}},20:{name:"record",253:{field:"timestamp",type:"date_time",scale:null,offset:0,units:"s"},114:{field:"grit",type:"float32",scale:null,offset:0,units:""},115:{field:"flow",type:"float32",scale:null,offset:0,units:""},0:{field:"position_lat",type:"sint32",scale:null,offset:0,units:"semicircles"},1:{field:"position_long",type:"sint32",scale:null,offset:0,units:"semicircles"},2:{field:"altitude",type:"uint16",scale:5,offset:-500,units:"m"},3:{field:"heart_rate",type:"uint8",scale:null,offset:0,units:"bpm"},4:{field:"cadence",type:"uint8",scale:null,offset:0,units:"rpm"},5:{field:"distance",type:"uint32",scale:100,offset:0,units:"m"},6:{field:"speed",type:"uint16",scale:1e3,offset:0,units:"m/s"},7:{field:"power",type:"uint16",scale:null,offset:0,units:"watts"},8:{field:"compressed_speed_distance",type:"byte",scale:100.16,offset:0,units:"m/s,m"},9:{field:"grade",type:"sint16",scale:100,offset:0,units:"%"},10:{field:"resistance",type:"uint8",scale:null,offset:0,units:""},11:{field:"time_from_course",type:"sint32",scale:1e3,offset:0,units:"s"},12:{field:"cycle_length",type:"uint8",scale:100,offset:0,units:"m"},13:{field:"temperature",type:"sint8",scale:null,offset:0,units:"C"},17:{field:"speed_1s",type:"uint8",scale:16,offset:0,units:"m/s"},18:{field:"cycles",type:"uint8",scale:null,offset:0,units:"cycles"},19:{field:"total_cycles",type:"uint32",scale:null,offset:0,units:"cycles"},28:{field:"compressed_accumulated_power",type:"uint16",scale:null,offset:0,units:"watts"},29:{field:"accumulated_power",type:"uint32",scale:null,offset:0,units:"watts"},30:{field:"left_right_balance",type:"left_right_balance",scale:null,offset:0,units:""},31:{field:"gps_accuracy",type:"uint8",scale:null,offset:0,units:"m"},32:{field:"vertical_speed",type:"sint16",scale:1e3,offset:0,units:"m/s"},33:{field:"calories",type:"uint16",scale:null,offset:0,units:"kcal"},39:{field:"vertical_oscillation",type:"uint16",scale:10,offset:0,units:"mm"},40:{field:"stance_time_percent",type:"uint16",scale:100,offset:0,units:"percent"},41:{field:"stance_time",type:"uint16",scale:10,offset:0,units:"ms"},42:{field:"activity_type",type:"activity_type",scale:null,offset:0,units:""},43:{field:"left_torque_effectiveness",type:"uint8",scale:2,offset:0,units:"percent"},44:{field:"right_torque_effectiveness",type:"uint8",scale:2,offset:0,units:"percent"},45:{field:"left_pedal_smoothness",type:"uint8",scale:2,offset:0,units:"percent"},46:{field:"right_pedal_smoothness",type:"uint8",scale:2,offset:0,units:"percent"},47:{field:"combined_pedal_smoothness",type:"uint8",scale:2,offset:0,units:"percent"},48:{field:"time128",type:"uint8",scale:128,offset:0,units:"s"},49:{field:"stroke_type",type:"stroke_type",scale:null,offset:0,units:""},50:{field:"zone",type:"uint8",scale:null,offset:0,units:""},51:{field:"ball_speed",type:"uint16",scale:100,offset:0,units:"m/s"},52:{field:"cadence256",type:"uint16",scale:256,offset:0,units:"rpm"},53:{field:"fractional_cadence",type:"uint8",scale:128,offset:0,units:"rpm"},54:{field:"total_hemoglobin_conc",type:"uint16",scale:100,offset:0,units:"g/dL"},55:{field:"total_hemoglobin_conc_min",type:"uint16",scale:100,offset:0,units:"g/dL"},56:{field:"total_hemoglobin_conc_max",type:"uint16",scale:100,offset:0,units:"g/dL"},57:{field:"saturated_hemoglobin_percent",type:"uint16",scale:10,offset:0,units:"%"},58:{field:"saturated_hemoglobin_percent_min",type:"uint16",scale:10,offset:0,units:"%"},59:{field:"saturated_hemoglobin_percent_max",type:"uint16",scale:10,offset:0,units:"%"},62:{field:"device_index",type:"device_index",scale:null,offset:0,units:""},67:{field:"left_pco",type:"sint8",scale:null,offset:0,units:"mm"},68:{field:"right_pco",type:"sint8",scale:null,offset:0,units:"mm"},69:{field:"left_power_phase",type:"uint8",scale:.7111111,offset:0,units:"degrees"},70:{field:"left_power_phase_peak",type:"uint8",scale:.7111111,offset:0,units:"degrees"},71:{field:"right_power_phase",type:"uint8",scale:.7111111,offset:0,units:"degrees"},72:{field:"right_power_phase_peak",type:"uint8",scale:.7111111,offset:0,units:"degrees"},73:{field:"enhanced_speed",type:"uint32",scale:1e3,offset:0,units:"m/s"},78:{field:"enhanced_altitude",type:"uint32",scale:5,offset:-500,units:"m"},81:{field:"battery_soc",type:"uint8",scale:2,offset:0,units:"percent"},82:{field:"motor_power",type:"uint16",scale:null,offset:0,units:"watts"},83:{field:"vertical_ratio",type:"uint16",scale:100,offset:0,units:"percent"},84:{field:"stance_time_balance",type:"uint16",scale:100,offset:0,units:"percent"},85:{field:"step_length",type:"uint16",scale:10,offset:0,units:"mm"},91:{field:"absolute_pressure",type:"uint32",scale:null,offset:0,units:"Pa"},92:{field:"depth",type:"uint32",scale:null,offset:0,units:"m"},93:{field:"next_stop_depth",type:"uint32",scale:null,offset:0,units:"m"},94:{field:"next_stop_time",type:"uint32",scale:null,offset:0,units:"s"},95:{field:"time_to_surface",type:"uint32",scale:null,offset:0,units:"s"},96:{field:"ndl_time",type:"uint32",scale:null,offset:0,units:"s"},97:{field:"cns_load",type:"uint8",scale:null,offset:0,units:"percent"},98:{field:"n2_load",type:"uint16",scale:null,offset:0,units:"percent"}},285:{name:"jump",253:{field:"timestamp",type:"date_time",scale:null,offset:0,units:"s"},0:{field:"distance",type:"float32",scale:null,offset:0,units:"m"},1:{field:"height",type:"float32",scale:null,offset:0,units:"m"},2:{field:"rotations",type:"uint8",scale:null,offset:0,units:""},3:{field:"hang_time",type:"float32",scale:null,offset:0,units:"s"},4:{field:"score",type:"float32",scale:null,offset:0,units:""},5:{field:"position_lat",type:"sint32",scale:null,offset:0,units:"semicircles"},6:{field:"position_long",type:"sint32",scale:null,offset:0,units:"semicircles"},7:{field:"speed",type:"uint16",scale:1e3,offset:0,units:"m/s"},8:{field:"enhanced_speed",type:"uint32",scale:1e3,offset:0,units:"m/s"}},21:{name:"event",253:{field:"timestamp",type:"date_time",scale:null,offset:0,units:"s"},0:{field:"event",type:"event",scale:null,offset:0,units:""},1:{field:"event_type",type:"event_type",scale:null,offset:0,units:""},2:{field:"data16",type:"uint16",scale:null,offset:0,units:""},3:{field:"data",type:"uint32",scale:null,offset:0,units:""},4:{field:"event_group",type:"uint8",scale:null,offset:0,units:""},7:{field:"score",type:"uint16",scale:null,offset:0,units:""},8:{field:"opponent_score",type:"uint16",scale:null,offset:0,units:""},9:{field:"front_gear_num",type:"uint8z",scale:null,offset:0,units:""},10:{field:"front_gear",type:"uint8z",scale:null,offset:0,units:""},11:{field:"rear_gear_num",type:"uint8z",scale:null,offset:0,units:""},12:{field:"rear_gear",type:"uint8z",scale:null,offset:0,units:""},13:{field:"device_index",type:"device_index",scale:null,offset:0,units:""}},23:{name:"device_info",253:{field:"timestamp",type:"date_time",scale:null,offset:0,units:"s"},0:{field:"device_index",type:"uint8",scale:null,offset:0,units:""},1:{field:"device_type",type:"uint8",scale:null,offset:0,units:""},2:{field:"manufacturer",type:"manufacturer",scale:null,offset:0,units:""},3:{field:"serial_number",type:"uint32z",scale:null,offset:0,units:""},4:{field:"product",type:"uint16",scale:null,offset:0,units:""},5:{field:"software_version",type:"uint16",scale:100,offset:0,units:""},6:{field:"hardware_version",type:"uint8",scale:null,offset:0,units:""},7:{field:"cum_operating_time",type:"uint32",scale:null,offset:0,units:"s"},10:{field:"battery_voltage",type:"uint16",scale:256,offset:0,units:"V"},32:{field:"battery_level",type:"uint8",scale:null,offset:0,units:"percent"},11:{field:"battery_status",type:"battery_status",scale:null,offset:0,units:""},18:{field:"sensor_position",type:"body_location",scale:null,offset:0,units:""},19:{field:"descriptor",type:"string",scale:null,offset:0,units:""},20:{field:"ant_transmission_type",type:"uint8z",scale:null,offset:0,units:""},21:{field:"ant_device_number",type:"uint16z",scale:null,offset:0,units:""},22:{field:"ant_network",type:"ant_network",scale:null,offset:0,units:""},24:{field:"ant_id",type:"uint32z",scale:null,offset:0,units:""},25:{field:"source_type",type:"source_type",scale:null,offset:0,units:""},27:{field:"product_name",type:"string",scale:null,offset:0,units:""}},26:{name:"workout",4:{field:"sport",type:"sport",scale:null,offset:0,units:""},5:{field:"capabilities",type:"workout_capabilities",scale:null,offset:0,units:""},6:{field:"num_valid_steps",type:"uint16",scale:null,offset:0,units:""},8:{field:"wkt_name",type:"string",scale:null,offset:0,units:""}},27:{name:"workout_step",254:{field:"message_index",type:"message_index",scale:null,offset:0,units:""},0:{field:"wkt_step_name",type:"string",scale:null,offset:0,units:""},1:{field:"duration_type",type:"wkt_step_duration",scale:null,offset:0,units:""},2:{field:"duration_value",type:"uint32",scale:null,offset:0,units:""},3:{field:"target_type",type:"wkt_step_target",scale:null,offset:0,units:""},4:{field:"target_value",type:"uint32",scale:null,offset:0,units:""},5:{field:"custom_target_value_low",type:"uint32",scale:null,offset:0,units:""},6:{field:"custom_target_value_high",type:"uint32",scale:null,offset:0,units:""},7:{field:"intensity",type:"intensity",scale:null,offset:0,units:""}},30:{name:"weight_scale",253:{field:"timestamp",type:"date_time",scale:null,offset:0,units:"s"},0:{field:"weight",type:"weight",scale:100,offset:0,units:"kg"},1:{field:"percent_fat",type:"uint16",scale:100,offset:0,units:"%"},2:{field:"percent_hydration",type:"uint16",scale:100,offset:0,units:"%"},3:{field:"visceral_fat_mass",type:"uint16",scale:100,offset:0,units:"kg"},4:{field:"bone_mass",type:"uint16",scale:100,offset:0,units:"kg"},5:{field:"muscle_mass",type:"uint16",scale:100,offset:0,units:"kg"},7:{field:"basal_met",type:"uint16",scale:4,offset:0,units:"kcal/day"},8:{field:"physique_rating",type:"uint8",scale:null,offset:0,units:""},9:{field:"active_met",type:"uint16",scale:4,offset:0,units:"kcal/day"},10:{field:"metabolic_age",type:"uint8",scale:null,offset:0,units:"years"},11:{field:"visceral_fat_rating",type:"uint8",scale:null,offset:0,units:""},12:{field:"user_profile_index",type:"message_index",scale:null,offset:0,units:""}},31:{name:"course",4:{field:"sport",type:"sport",scale:null,offset:0,units:""},5:{field:"name",type:"string",scale:null,offset:0,units:""},6:{field:"capabilities",type:"course_capabilities",scale:null,offset:0,units:""}},32:{name:"course_point",254:{field:"message_index",type:"message_index",scale:null,offset:0,units:""},1:{field:"timestamp",type:"date_time",scale:null,offset:0,units:""},2:{field:"position_lat",type:"sint32",scale:null,offset:0,units:"semicircles"},3:{field:"position_long",type:"sint32",scale:null,offset:0,units:"semicircles"},4:{field:"distance",type:"uint32",scale:100,offset:0,units:"m"},5:{field:"type",type:"course_point",scale:null,offset:0,units:""},6:{field:"name",type:"string",scale:null,offset:0,units:""},8:{field:"favorite",type:"bool",scale:null,offset:0,units:""}},33:{name:"totals",254:{field:"message_index",type:"message_index",scale:null,offset:0,units:""},253:{field:"timestamp",type:"date_time",scale:null,offset:0,units:"s"},0:{field:"timer_time",type:"uint32",scale:null,offset:0,units:"s"},1:{field:"distance",type:"uint32",scale:null,offset:0,units:"m"},2:{field:"calories",type:"uint32",scale:null,offset:0,units:"kcal"},3:{field:"sport",type:"sport",scale:null,offset:0,units:""},4:{field:"elapsed_time",type:"uint32",scale:null,offset:0,units:"s"},5:{field:"sessions",type:"uint16",scale:null,offset:0,units:""},6:{field:"active_time",type:"uint32",scale:null,offset:0,units:"s"},9:{field:"sport_index",type:"uint8",scale:null,offset:0,units:""}},34:{name:"activity",253:{field:"timestamp",type:"date_time",scale:null,offset:0,units:""},0:{field:"total_timer_time",type:"uint32",scale:1e3,offset:0,units:"s"},1:{field:"num_sessions",type:"uint16",scale:null,offset:0,units:""},2:{field:"type",type:"activity",scale:null,offset:0,units:""},3:{field:"event",type:"event",scale:null,offset:0,units:""},4:{field:"event_type",type:"event_type",scale:null,offset:0,units:""},5:{field:"local_timestamp",type:"local_date_time",scale:null,offset:0,units:""},6:{field:"event_group",type:"uint8",scale:null,offset:0,units:""}},35:{name:"software",254:{field:"message_index",type:"message_index",scale:null,offset:0,units:""},3:{field:"version",type:"uint16",scale:100,offset:0,units:""},5:{field:"part_number",type:"string",scale:null,offset:0,units:""}},37:{name:"file_capabilities",254:{field:"message_index",type:"message_index",scale:null,offset:0,units:""},0:{field:"type",type:"file",scale:null,offset:0,units:""},1:{field:"flags",type:"file_flags",scale:null,offset:0,units:""},2:{field:"directory",type:"string",scale:null,offset:0,units:""},3:{field:"max_count",type:"uint16",scale:null,offset:0,units:""},4:{field:"max_size",type:"uint32",scale:null,offset:0,units:"bytes"}},38:{name:"mesg_capabilities",254:{field:"message_index",type:"message_index",scale:null,offset:0,units:""},0:{field:"file",type:"file",scale:null,offset:0,units:""},1:{field:"mesg_num",type:"mesg_num",scale:null,offset:0,units:""},2:{field:"count_type",type:"mesg_count",scale:null,offset:0,units:""},3:{field:"count",type:"uint16",scale:null,offset:0,units:""}},39:{name:"field_capabilities",254:{field:"message_index",type:"message_index",scale:null,offset:0,units:""},0:{field:"file",type:"file",scale:null,offset:0,units:""},1:{field:"mesg_num",type:"mesg_num",scale:null,offset:0,units:""},2:{field:"field_num",type:"uint8",scale:null,offset:0,units:""},3:{field:"count",type:"uint16",scale:null,offset:0,units:""}},49:{name:"file_creator",0:{field:"software_version",type:"uint16",scale:null,offset:0,units:""},1:{field:"hardware_version",type:"uint8",scale:null,offset:0,units:""}},51:{name:"blood_pressure",253:{field:"timestamp",type:"date_time",scale:null,offset:0,units:"s"},0:{field:"systolic_pressure",type:"uint16",scale:null,offset:0,units:"mmHg"},1:{field:"diastolic_pressure",type:"uint16",scale:null,offset:0,units:"mmHg"},2:{field:"mean_arterial_pressure",type:"uint16",scale:null,offset:0,units:"mmHg"},3:{field:"map_3_sample_mean",type:"uint16",scale:null,offset:0,units:"mmHg"},4:{field:"map_morning_values",type:"uint16",scale:null,offset:0,units:"mmHg"},5:{field:"map_evening_values",type:"uint16",scale:null,offset:0,units:"mmHg"},6:{field:"heart_rate",type:"uint8",scale:null,offset:0,units:"bpm"},7:{field:"heart_rate_type",type:"hr_type",scale:null,offset:0,units:""},8:{field:"status",type:"bp_status",scale:null,offset:0,units:""},9:{field:"user_profile_index",type:"message_index",scale:null,offset:0,units:""}},55:{name:"monitoring",253:{field:"timestamp",type:"uint32",scale:null,offset:0,units:""},0:{field:"device_index",type:"device_index",scale:null,offset:0,units:""},1:{field:"calories",type:"uint16",scale:null,offset:0,units:"kcal"},2:{field:"distance",type:"float32",scale:null,offset:0,units:"m"},3:{field:"cycles",type:"float32",scale:null,offset:0,units:"cycles"},4:{field:"active_time",type:"float32",scale:null,offset:0,units:"s"},5:{field:"activity_type",type:"activity_type",scale:null,offset:0,units:""},6:{field:"activity_subtype",type:"activity_subtype",scale:null,offset:0,units:""},7:{field:"activity_level",type:"activity_level",scale:null,offset:0,units:"s"},8:{field:"distance16",type:"uint16",scale:null,offset:0,units:"m"},9:{field:"cycles16",type:"uint16",scale:null,offset:0,units:"cycles"},10:{field:"active_time16",type:"uint16",scale:null,offset:0,units:""},11:{field:"local_timestamp",type:"uint32",scale:null,offset:0,units:""},12:{field:"temperature",type:"float32",scale:null,offset:0,units:"C"},14:{field:"temperature_min",type:"float32",scale:null,offset:0,units:"C"},15:{field:"temperature_max",type:"float32",scale:null,offset:0,units:"C"},16:{field:"activity_time",type:"int32",scale:null,offset:0,units:""},19:{field:"active_calories",type:"uint16",scale:null,offset:0,units:"kcal"},24:{field:"current_activity_type_intensity",type:"uint8",scale:null,offset:0,units:""},25:{field:"timestamp_min8",type:"uint8",scale:null,offset:0,units:""},26:{field:"timestamp16",type:"uint16",scale:null,offset:0,units:""},27:{field:"heart_rate",type:"uint8",scale:null,offset:0,units:"bpm"},28:{field:"intensity",type:"uint8",scale:null,offset:0,units:""},29:{field:"duration_min",type:"uint16",scale:null,offset:0,units:""},30:{field:"duration",type:"uint32",scale:null,offset:0,units:""},31:{field:"ascent",type:"float32",scale:null,offset:0,units:"m"},32:{field:"descent",type:"float32",scale:null,offset:0,units:"m"},33:{field:"moderate_activity_minutes",type:"uint16",scale:null,offset:0,units:""},34:{field:"vigorous_activity_inutes",type:"uint16",scale:null,offset:0,units:""}},78:{name:"hrv",0:{field:"time",type:"uint16_array",scale:1e3,offset:0,units:"s"}},101:{name:"length",254:{field:"message_index",type:"message_index",scale:null,offset:0,units:""},253:{field:"timestamp",type:"date_time",scale:null,offset:0,units:"s"},0:{field:"event",type:"event",scale:null,offset:0,units:""},1:{field:"event_type",type:"event_type",scale:null,offset:0,units:""},2:{field:"start_time",type:"date_time",scale:null,offset:0,units:""},3:{field:"total_elapsed_time",type:"uint32",scale:1e3,offset:0,units:"s"},4:{field:"total_timer_time",type:"uint32",scale:1e3,offset:0,units:"s"},5:{field:"total_strokes",type:"uint16",scale:null,offset:0,units:"strokes"},6:{field:"avg_speed",type:"uint16",scale:1e3,offset:0,units:"m/s"},7:{field:"swim_stroke",type:"swim_stroke",scale:null,offset:0,units:"swim_stroke"},9:{field:"avg_swimming_cadence",type:"uint8",scale:null,offset:0,units:"strokes/min"},10:{field:"event_group",type:"uint8",scale:null,offset:0,units:""},11:{field:"total_calories",type:"uint16",scale:null,offset:0,units:"kcal"},12:{field:"length_type",type:"length_type",scale:null,offset:0,units:"length_type"},18:{field:"player_score",type:"uint16",scale:null,offset:0,units:""},19:{field:"opponent_score",type:"uint16",scale:null,offset:0,units:""},20:{field:"stroke_count",type:"uint16",scale:null,offset:0,units:"counts"},21:{field:"zone_count",type:"uint16",scale:null,offset:0,units:"counts"}},103:{name:"monitoring_info",253:{field:"timestamp",type:"date_time",scale:null,offset:0,units:""},0:{field:"local_timestamp",type:"uint32",scale:null,offset:0,units:""},1:{field:"activity_type",type:"activity_type",scale:null,offset:0,units:""},3:{field:"cycles_to_distance",type:"float32",scale:null,offset:0,units:"cycles"},4:{field:"cycles_to_calories",type:"float32",scale:null,offset:0,units:"kcal"},5:{field:"resting_metabolic_rate",type:"uint16",scale:null,offset:0,units:""}},108:{name:"o_hr_settings",253:{field:"timestamp",type:"date_time",scale:null,offset:0,units:""},0:{field:"enabled",type:"byte",scale:null,offset:0,units:""}},140:{name:"activity_metrics",1:{field:"new_max_heart_rate",type:"uint8",scale:null,offset:0,units:"bpm"},4:{field:"aerobic_training_effect",type:"uint8",scale:10,offset:0,units:""},7:{field:"vo2_max",type:"uint32",scale:65536/3.5,offset:0,units:"ml/kg/min"},9:{field:"recovery_time",type:"uint16",scale:null,offset:0,units:"min"},11:{field:"sport",type:"sport",scale:null,offset:0,units:""},20:{field:"anaerobic_training_effect",type:"uint8",scale:10,offset:0,units:""},29:{field:"first_vo2_max",type:"uint32",scale:65536/3.5,offset:0,units:"ml/kg/min"},41:{field:"primary_benefit",type:"uint8",scale:null,offset:0,units:""},60:{field:"total_ascent",type:"uint16",scale:null,offset:0,units:"m"},61:{field:"total_descent",type:"uint16",scale:null,offset:0,units:"m"},62:{field:"avg_power",type:"uint16",scale:null,offset:0,units:"watts"},63:{field:"avg_heart_rate",type:"uint8",scale:null,offset:0,units:"bpm"}},206:{name:"field_description",0:{field:"developer_data_index",type:"uint8",scale:null,offset:0,units:""},1:{field:"field_definition_number",type:"uint8",scale:null,offset:0,units:""},2:{field:"fit_base_type_id",type:"uint8",scale:null,offset:0,units:""},3:{field:"field_name",type:"string",scale:null,offset:0,units:""},6:{field:"scale",type:"uint8",scale:null,offset:0,units:""},7:{field:"offset",type:"sint8",scale:null,offset:0,units:""},8:{field:"units",type:"string",scale:null,offset:0,units:""},15:{field:"native_field_num",type:"uint8",scale:null,offset:0,units:""}},227:{name:"stress_level",0:{field:"stress_level_value",type:"uint16",scale:null,offset:0,units:""},1:{field:"stress_level_time",type:"date_time",scale:null,offset:0,units:"s"},2:{field:"field_two",type:"sint8",scale:null,offset:0,units:""},3:{field:"body_battery",type:"uint8",scale:null,offset:0,units:""},4:{field:"field_four",type:"uint8",scale:null,offset:0,units:""}},207:{name:"developer_data_id",0:{field:"developer_id",type:"uint8",scale:null,offset:0,units:""},1:{field:"application_id",type:"byte_array",scale:null,offset:0,units:""},2:{field:"manufacturer_id",type:"manufacturer",scale:null,offset:0,units:""},3:{field:"developer_data_index",type:"uint8",scale:null,offset:0,units:""},4:{field:"application_version",type:"uint8",scale:null,offset:0,units:""}},258:{name:"dive_settings",254:{field:"message_index",type:"message_index",scale:null,offset:0,units:""},0:{field:"name",type:"string",scale:null,offset:0,units:""},1:{field:"model",type:"tissue_model_type",scale:null,offset:0,units:""},2:{field:"gf_low",type:"uint8",scale:null,offset:0,units:"percent"},3:{field:"gf_high",type:"uint8",scale:null,offset:0,units:"percent"},4:{field:"water_type",type:"water_type",scale:null,offset:0,units:""},5:{field:"water_density",type:"float32",scale:null,offset:0,units:"kg/m^3"},6:{field:"po2_warn",type:"uint8",scale:null,offset:0,units:"percent"},7:{field:"po2_critical",type:"uint8",scale:null,offset:0,units:"percent"},8:{field:"po2_deco",type:"uint8",scale:null,offset:0,units:"percent"},9:{field:"safety_stop_enabled",type:"bool",scale:null,offset:0,units:""},10:{field:"bottom_depth",type:"float32",scale:null,offset:0,units:""},11:{field:"bottom_time",type:"uint32",scale:null,offset:0,units:""},12:{field:"apnea_countdown_enabled",type:"bool",scale:null,offset:0,units:""},13:{field:"apnea_countdown_time",type:"uint32",scale:null,offset:0,units:""},14:{field:"backlight_mode",type:"dive_backlight_mode",scale:null,offset:0,units:""},15:{field:"backlight_brightness",type:"uint8",scale:null,offset:0,units:""},16:{field:"backlight_timeout",type:"backlight_timeout",scale:null,offset:0,units:""},17:{field:"repeat_dive_time",type:"uint16",scale:null,offset:0,units:"s"},18:{field:"safety_stop_time",type:"uint16",scale:null,offset:0,units:"s"},19:{field:"heart_rate_source_type",type:"source_type",scale:null,offset:0,units:""},20:{field:"heart_rate_source",type:"uint8",scale:null,offset:0,units:""}},259:{name:"dive_gas",254:{field:"message_index",type:"message_index",scale:null,offset:0,units:""},0:{field:"helium_content",type:"uint8",scale:null,offset:0,units:"percent"},1:{field:"oxygen_content",type:"uint8",scale:null,offset:0,units:"percent"},2:{field:"status",type:"dive_gas_status",scale:null,offset:0,units:""}},262:{name:"dive_alarm",254:{field:"message_index",type:"message_index",scale:null,offset:0,units:""},0:{field:"depth",type:"uint32",scale:null,offset:0,units:"m"},1:{field:"time",type:"sint32",scale:null,offset:0,units:"s"},2:{field:"enabled",type:"bool",scale:null,offset:0,units:""},3:{field:"alarm_type",type:"dive_alarm_type",scale:null,offset:0,units:""},4:{field:"sound",type:"tone",scale:null,offset:0,units:""},5:{field:"dive_types",type:"sub_sport",scale:null,offset:0,units:""}},268:{name:"dive_summary",253:{field:"timestamp",type:"date_time",scale:null,offset:0,units:"s"},0:{field:"reference_mesg",type:"mesg_num",scale:null,offset:0,units:""},1:{field:"reference_index",type:"message_index",scale:null,offset:0,units:""},2:{field:"avg_depth",type:"uint32",scale:null,offset:0,units:"m"},3:{field:"max_depth",type:"uint32",scale:null,offset:0,units:"m"},4:{field:"surface_interval",type:"uint32",scale:null,offset:0,units:"s"},5:{field:"start_cns",type:"uint8",scale:null,offset:0,units:"percent"},6:{field:"end_cns",type:"uint8",scale:null,offset:0,units:"percent"},7:{field:"start_n2",type:"uint16",scale:null,offset:0,units:"percent"},8:{field:"end_n2",type:"uint16",scale:null,offset:0,units:"percent"},9:{field:"o2_toxicity",type:"uint16",scale:null,offset:0,units:"OTUs"},10:{field:"dive_number",type:"uint32",scale:null,offset:0,units:""},11:{field:"bottom_time",type:"uint32",scale:null,offset:0,units:"s"}},319:{name:"tank_update",253:{field:"timestamp",type:"date_time",scale:null,offset:0,units:"s"},0:{field:"sensor",type:"uint32",scale:null,offset:0,units:""},1:{field:"pressure",type:"uint16",scale:null,offset:0,units:"cbar"}},323:{name:"tank_summary",0:{field:"sensor",type:"uint32",scale:null,offset:0,units:""},1:{field:"start_pressure",type:"uint16",scale:null,offset:0,units:"cbar"},2:{field:"end_pressure",type:"uint16",scale:null,offset:0,units:"cbar"},3:{field:"volume_used",type:"uint16",scale:null,offset:0,units:"cbar"}},216:{name:"time_in_zone",253:{field:"timestamp",type:"date_time",scale:null,offset:0,units:"s"},0:{field:"reference_mesg",type:"uint16",scale:null,offset:0,units:""},1:{field:"reference_index",type:"uint16",scale:null,offset:0,units:""},2:{field:"time_in_hr_zone",type:"uint32_array",scale:1e3,offset:0,units:"s"},3:{field:"time_in_speed_zone",type:"uint32_array",scale:1e3,offset:0,units:"s"},4:{field:"time_in_power_zone",type:"uint32_array",scale:1e3,offset:0,units:"s"},5:{field:"time_in_power_zone",type:"uint32_array",scale:1e3,offset:0,units:"s"},6:{field:"hr_zone_high_boundary",type:"uint8_array",scale:null,offset:0,units:"bpm"},7:{field:"speed_zone_high_boundary",type:"uint16_array",scale:1e3,offset:0,units:"m/s"},8:{field:"power_zone_high_boundary",type:"uint16_array",scale:null,offset:0,units:"watts"},9:{field:"power_zone_high_boundary",type:"uint16_array",scale:null,offset:0,units:"watts"},10:{field:"max_heart_rate_deprecated",type:"uint8",scale:null,offset:0,units:"bpm"},11:{field:"max_heart_rate",type:"uint8",scale:null,offset:0,units:"bpm"},12:{field:"resting_heart_rate",type:"uint8",scale:null,offset:0,units:"bpm"},13:{field:"threshold_heart_rate",type:"uint8",scale:null,offset:0,units:"bpm"},14:{field:"pwr_calc_type",type:"pwr_zone_calc",scale:null,offset:0,units:""},15:{field:"functional_threshold_power",type:"uint16",scale:null,offset:0,units:"watts"}}},types:{file:{1:"device",2:"settings",3:"sport",4:"activity",5:"workout",6:"course",7:"schedules",9:"weight",10:"totals",11:"goals",14:"blood_pressure",15:"monitoring_a",20:"activity_summary",28:"monitoring_daily",32:"monitoring_b",34:"segment",35:"segment_list",40:"exd_configuration",247:"mfg_range_min",254:"mfg_range_max"},mesg_num:{0:"file_id",1:"capabilities",2:"device_settings",3:"user_profile",4:"hrm_profile",5:"sdm_profile",6:"bike_profile",7:"zones_target",8:"hr_zone",9:"power_zone",10:"met_zone",12:"sport",15:"goal",18:"session",19:"lap",20:"record",21:"event",23:"device_info",26:"workout",27:"workout_step",28:"schedule",30:"weight_scale",31:"course",32:"course_point",33:"totals",34:"activity",35:"software",37:"file_capabilities",38:"mesg_capabilities",39:"field_capabilities",49:"file_creator",51:"blood_pressure",53:"speed_zone",55:"monitoring",72:"training_file",78:"hrv",80:"ant_rx",81:"ant_tx",82:"ant_channel_id",101:"length",103:"monitoring_info",105:"pad",106:"slave_device",127:"connectivity",128:"weather_conditions",129:"weather_alert",131:"cadence_zone",132:"hr",140:"activity_metrics",142:"segment_lap",145:"memo_glob",148:"segment_id",149:"segment_leaderboard_entry",150:"segment_point",151:"segment_file",158:"workout_session",159:"watchface_settings",160:"gps_metadata",161:"camera_event",162:"timestamp_correlation",164:"gyroscope_data",165:"accelerometer_data",167:"three_d_sensor_calibration",169:"video_frame",174:"obdii_data",177:"nmea_sentence",178:"aviation_attitude",184:"video",185:"video_title",186:"video_description",187:"video_clip",200:"exd_screen_configuration",201:"exd_data_field_configuration",202:"exd_data_concept_configuration",206:"field_description",207:"developer_data_id",208:"magnetometer_data",209:"barometer_data",210:"one_d_sensor_calibration",225:"set",227:"stress_level",258:"dive_settings",259:"dive_gas",262:"dive_alarm",264:"exercise_title",268:"dive_summary",285:"jump",317:"climb_pro",319:"tank_update",323:"tank_summary",216:"time_in_zone",347:"o_hr_settings",65280:"mfg_range_min",65534:"mfg_range_max"},checksum:{0:"clear",1:"ok"},file_flags:{0:0,2:"read",4:"write",8:"erase"},mesg_count:{0:"num_per_file",1:"max_per_file",2:"max_per_file_type"},date_time:{0:0,268435456:"min"},local_date_time:{0:0,268435456:"min"},message_index:{0:0,4095:"mask",28672:"reserved",32768:"selected"},gender:{0:"female",1:"male"},language:{0:"english",1:"french",2:"italian",3:"german",4:"spanish",5:"croatian",6:"czech",7:"danish",8:"dutch",9:"finnish",10:"greek",11:"hungarian",12:"norwegian",13:"polish",14:"portuguese",15:"slovakian",16:"slovenian",17:"swedish",18:"russian",19:"turkish",20:"latvian",21:"ukrainian",22:"arabic",23:"farsi",24:"bulgarian",25:"romanian",26:"chinese",27:"japanese",28:"korean",29:"taiwanese",30:"thai",31:"hebrew",32:"brazilian_portuguese",33:"indonesian",34:"malaysian",35:"vietnamese",36:"burmese",37:"mongolian",254:"custom"},language_bits_0:{0:0,1:"english",2:"french",4:"italian",8:"german",16:"spanish",32:"croatian",64:"czech",128:"danish"},language_bits_1:{0:0,1:"dutch",2:"finnish",4:"greek",8:"hungarian",16:"norwegian",32:"polish",64:"portuguese",128:"slovakian"},language_bits_2:{0:0,1:"slovenian",2:"swedish",4:"russian",8:"turkish",16:"latvian",32:"ukrainian",64:"arabic",128:"farsi"},language_bits_3:{0:0,1:"bulgarian",2:"romanian",4:"chinese",8:"japanese",16:"korean",32:"taiwanese",64:"thai",128:"hebrew"},language_bits_4:{0:0,1:"brazilian_portuguese",2:"indonesian",4:"malaysian",8:"vietnamese",16:"burmese",32:"mongolian"},time_zone:{0:"almaty",1:"bangkok",2:"bombay",3:"brasilia",4:"cairo",5:"cape_verde_is",6:"darwin",7:"eniwetok",8:"fiji",9:"hong_kong",10:"islamabad",11:"kabul",12:"magadan",13:"mid_atlantic",14:"moscow",15:"muscat",16:"newfoundland",17:"samoa",18:"sydney",19:"tehran",20:"tokyo",21:"us_alaska",22:"us_atlantic",23:"us_central",24:"us_eastern",25:"us_hawaii",26:"us_mountain",27:"us_pacific",28:"other",29:"auckland",30:"kathmandu",31:"europe_western_wet",32:"europe_central_cet",33:"europe_eastern_eet",34:"jakarta",35:"perth",36:"adelaide",37:"brisbane",38:"tasmania",39:"iceland",40:"amsterdam",41:"athens",42:"barcelona",43:"berlin",44:"brussels",45:"budapest",46:"copenhagen",47:"dublin",48:"helsinki",49:"lisbon",50:"london",51:"madrid",52:"munich",53:"oslo",54:"paris",55:"prague",56:"reykjavik",57:"rome",58:"stockholm",59:"vienna",60:"warsaw",61:"zurich",62:"quebec",63:"ontario",64:"manitoba",65:"saskatchewan",66:"alberta",67:"british_columbia",68:"boise",69:"boston",70:"chicago",71:"dallas",72:"denver",73:"kansas_city",74:"las_vegas",75:"los_angeles",76:"miami",77:"minneapolis",78:"new_york",79:"new_orleans",80:"phoenix",81:"santa_fe",82:"seattle",83:"washington_dc",84:"us_arizona",85:"chita",86:"ekaterinburg",87:"irkutsk",88:"kaliningrad",89:"krasnoyarsk",90:"novosibirsk",91:"petropavlovsk_kamchatskiy",92:"samara",93:"vladivostok",94:"mexico_central",95:"mexico_mountain",96:"mexico_pacific",97:"cape_town",98:"winkhoek",99:"lagos",100:"riyahd",101:"venezuela",102:"australia_lh",103:"santiago",253:"manual",254:"automatic"},display_measure:{0:"metric",1:"statute",2:"nautical"},display_heart:{0:"bpm",1:"max",2:"reserve"},display_power:{0:"watts",1:"percent_ftp"},display_position:{0:"degree",1:"degree_minute",2:"degree_minute_second",3:"austrian_grid",4:"british_grid",5:"dutch_grid",6:"hungarian_grid",7:"finnish_grid",8:"german_grid",9:"icelandic_grid",10:"indonesian_equatorial",11:"indonesian_irian",12:"indonesian_southern",13:"india_zone_0",14:"india_zone_IA",15:"india_zone_IB",16:"india_zone_IIA",17:"india_zone_IIB",18:"india_zone_IIIA",19:"india_zone_IIIB",20:"india_zone_IVA",21:"india_zone_IVB",22:"irish_transverse",23:"irish_grid",24:"loran",25:"maidenhead_grid",26:"mgrs_grid",27:"new_zealand_grid",28:"new_zealand_transverse",29:"qatar_grid",30:"modified_swedish_grid",31:"swedish_grid",32:"south_african_grid",33:"swiss_grid",34:"taiwan_grid",35:"united_states_grid",36:"utm_ups_grid",37:"west_malayan",38:"borneo_rso",39:"estonian_grid",40:"latvian_grid",41:"swedish_ref_99_grid"},switch:{0:"off",1:"on",2:"auto"},sport:{0:"generic",1:"running",2:"cycling",3:"transition",4:"fitness_equipment",5:"swimming",6:"basketball",7:"soccer",8:"tennis",9:"american_football",10:"training",11:"walking",12:"cross_country_skiing",13:"alpine_skiing",14:"snowboarding",15:"rowing",16:"mountaineering",17:"hiking",18:"multisport",19:"paddling",20:"flying",21:"e_biking",22:"motorcycling",23:"boating",24:"driving",25:"golf",26:"hang_gliding",27:"horseback_riding",28:"hunting",29:"fishing",30:"inline_skating",31:"rock_climbing",32:"sailing",33:"ice_skating",34:"sky_diving",35:"snowshoeing",36:"snowmobiling",37:"stand_up_paddleboarding",38:"surfing",39:"wakeboarding",40:"water_skiing",41:"kayaking",42:"rafting",43:"windsurfing",44:"kitesurfing",45:"tactical",46:"jumpmaster",47:"boxing",48:"floor_climbing",53:"diving",254:"all"},sport_bits_0:{0:0,1:"generic",2:"running",4:"cycling",8:"transition",16:"fitness_equipment",32:"swimming",64:"basketball",128:"soccer"},sport_bits_1:{0:0,1:"tennis",2:"american_football",4:"training",8:"walking",16:"cross_country_skiing",32:"alpine_skiing",64:"snowboarding",128:"rowing"},sport_bits_2:{0:0,1:"mountaineering",2:"hiking",4:"multisport",8:"paddling",16:"flying",32:"e_biking",64:"motorcycling",128:"boating"},sport_bits_3:{0:0,1:"driving",2:"golf",4:"hang_gliding",8:"horseback_riding",16:"hunting",32:"fishing",64:"inline_skating",128:"rock_climbing"},sport_bits_4:{0:0,1:"sailing",2:"ice_skating",4:"sky_diving",8:"snowshoeing",16:"snowmobiling",32:"stand_up_paddleboarding",64:"surfing",128:"wakeboarding"},sport_bits_5:{0:0,1:"water_skiing",2:"kayaking",4:"rafting",8:"windsurfing",16:"kitesurfing",32:"tactical",64:"jumpmaster",128:"boxing"},sport_bits_6:{0:0,1:"floor_climbing"},sub_sport:{0:"generic",1:"treadmill",2:"street",3:"trail",4:"track",5:"spin",6:"indoor_cycling",7:"road",8:"mountain",9:"downhill",10:"recumbent",11:"cyclocross",12:"hand_cycling",13:"track_cycling",14:"indoor_rowing",15:"elliptical",16:"stair_climbing",17:"lap_swimming",18:"open_water",19:"flexibility_training",20:"strength_training",21:"warm_up",22:"match",23:"exercise",24:"challenge",25:"indoor_skiing",26:"cardio_training",27:"indoor_walking",28:"e_bike_fitness",29:"bmx",30:"casual_walking",31:"speed_walking",32:"bike_to_run_transition",33:"run_to_bike_transition",34:"swim_to_bike_transition",35:"atv",36:"motocross",37:"backcountry",38:"resort",39:"rc_drone",40:"wingsuit",41:"whitewater",42:"skate_skiing",43:"yoga",44:"pilates",45:"indoor_running",46:"gravel_cycling",47:"e_bike_mountain",48:"commuting",49:"mixed_surface",50:"navigate",51:"track_me",52:"map",53:"single_gas_diving",54:"multi_gas_diving",55:"gauge_diving",56:"apnea_diving",57:"apnea_hunting",58:"virtual_activity",59:"obstacle",254:"all"},sport_event:{0:"uncategorized",1:"geocaching",2:"fitness",3:"recreation",4:"race",5:"special_event",6:"training",7:"transportation",8:"touring"},activity:{0:"manual",1:"auto_multi_sport"},intensity:{0:"active",1:"rest",2:"warmup",3:"cooldown",4:"recovery",5:"interval",6:"other"},session_trigger:{0:"activity_end",1:"manual",2:"auto_multi_sport",3:"fitness_equipment"},autolap_trigger:{0:"time",1:"distance",2:"position_start",3:"position_lap",4:"position_waypoint",5:"position_marked",6:"off"},lap_trigger:{0:"manual",1:"time",2:"distance",3:"position_start",4:"position_lap",5:"position_waypoint",6:"position_marked",7:"session_end",8:"fitness_equipment"},time_mode:{0:"hour12",1:"hour24",2:"military",3:"hour_12_with_seconds",4:"hour_24_with_seconds",5:"utc"},backlight_mode:{0:"off",1:"manual",2:"key_and_messages",3:"auto_brightness",4:"smart_notifications",5:"key_and_messages_night",6:"key_and_messages_and_smart_notifications"},date_mode:{0:"day_month",1:"month_day"},backlight_timeout:{0:"infinite"},event:{0:"timer",3:"workout",4:"workout_step",5:"power_down",6:"power_up",7:"off_course",8:"session",9:"lap",10:"course_point",11:"battery",12:"virtual_partner_pace",13:"hr_high_alert",14:"hr_low_alert",15:"speed_high_alert",16:"speed_low_alert",17:"cad_high_alert",18:"cad_low_alert",19:"power_high_alert",20:"power_low_alert",21:"recovery_hr",22:"battery_low",23:"time_duration_alert",24:"distance_duration_alert",25:"calorie_duration_alert",26:"activity",27:"fitness_equipment",28:"length",32:"user_marker",33:"sport_point",36:"calibration",42:"front_gear_change",43:"rear_gear_change",44:"rider_position_change",45:"elev_high_alert",46:"elev_low_alert",47:"comm_timeout"},event_type:{0:"start",1:"stop",2:"consecutive_depreciated",3:"marker",4:"stop_all",5:"begin_depreciated",6:"end_depreciated",7:"end_all_depreciated",8:"stop_disable",9:"stop_disable_all"},timer_trigger:{0:"manual",1:"auto",2:"fitness_equipment"},fitness_equipment_state:{0:"ready",1:"in_use",2:"paused",3:"unknown"},tone:{0:"off",1:"tone",2:"vibrate",3:"tone_and_vibrate"},autoscroll:{0:"none",1:"slow",2:"medium",3:"fast"},activity_class:{0:0,100:"level_max",127:"level",128:"athlete"},hr_zone_calc:{0:"custom",1:"percent_max_hr",2:"percent_hrr"},pwr_zone_calc:{0:"custom",1:"percent_ftp"},wkt_step_duration:{0:"time",1:"distance",2:"hr_less_than",3:"hr_greater_than",4:"calories",5:"open",6:"repeat_until_steps_cmplt",7:"repeat_until_time",8:"repeat_until_distance",9:"repeat_until_calories",10:"repeat_until_hr_less_than",11:"repeat_until_hr_greater_than",12:"repeat_until_power_less_than",13:"repeat_until_power_greater_than",14:"power_less_than",15:"power_greater_than",16:"training_peaks_tss",17:"repeat_until_power_last_lap_less_than",18:"repeat_until_max_power_last_lap_less_than",19:"power_3s_less_than",20:"power_10s_less_than",21:"power_30s_less_than",22:"power_3s_greater_than",23:"power_10s_greater_than",24:"power_30s_greater_than",25:"power_lap_less_than",26:"power_lap_greater_than",27:"repeat_until_training_peaks_tss",28:"repetition_time",29:"reps"},wkt_step_target:{0:"speed",1:"heart_rate",2:"open",3:"cadence",4:"power",5:"grade",6:"resistance",7:"power_3s",8:"power_10s",9:"power_30s",10:"power_lap",11:"swim_stroke",12:"speed_lap",13:"heart_rate_lap"},goal:{0:"time",1:"distance",2:"calories",3:"frequency",4:"steps",5:"ascent",6:"active_minutes"},goal_recurrence:{0:"off",1:"daily",2:"weekly",3:"monthly",4:"yearly",5:"custom"},goal_source:{0:"auto",1:"community",2:"user"},schedule:{0:"workout",1:"course"},course_point:{0:"generic",1:"summit",2:"valley",3:"water",4:"food",5:"danger",6:"left",7:"right",8:"straight",9:"first_aid",10:"fourth_category",11:"third_category",12:"second_category",13:"first_category",14:"hors_category",15:"sprint",16:"left_fork",17:"right_fork",18:"middle_fork",19:"slight_left",20:"sharp_left",21:"slight_right",22:"sharp_right",23:"u_turn",24:"segment_start",25:"segment_end"},manufacturer:{0:0,1:"garmin",2:"garmin_fr405_antfs",3:"zephyr",4:"dayton",5:"idt",6:"srm",7:"quarq",8:"ibike",9:"saris",10:"spark_hk",11:"tanita",12:"echowell",13:"dynastream_oem",14:"nautilus",15:"dynastream",16:"timex",17:"metrigear",18:"xelic",19:"beurer",20:"cardiosport",21:"a_and_d",22:"hmm",23:"suunto",24:"thita_elektronik",25:"gpulse",26:"clean_mobile",27:"pedal_brain",28:"peaksware",29:"saxonar",30:"lemond_fitness",31:"dexcom",32:"wahoo_fitness",33:"octane_fitness",34:"archinoetics",35:"the_hurt_box",36:"citizen_systems",37:"magellan",38:"osynce",39:"holux",40:"concept2",42:"one_giant_leap",43:"ace_sensor",44:"brim_brothers",45:"xplova",46:"perception_digital",47:"bf1systems",48:"pioneer",49:"spantec",50:"metalogics",51:"4iiiis",52:"seiko_epson",53:"seiko_epson_oem",54:"ifor_powell",55:"maxwell_guider",56:"star_trac",57:"breakaway",58:"alatech_technology_ltd",59:"mio_technology_europe",60:"rotor",61:"geonaute",62:"id_bike",63:"specialized",64:"wtek",65:"physical_enterprises",66:"north_pole_engineering",67:"bkool",68:"cateye",69:"stages_cycling",70:"sigmasport",71:"tomtom",72:"peripedal",73:"wattbike",76:"moxy",77:"ciclosport",78:"powerbahn",79:"acorn_projects_aps",80:"lifebeam",81:"bontrager",82:"wellgo",83:"scosche",84:"magura",85:"woodway",86:"elite",87:"nielsen_kellerman",88:"dk_city",89:"tacx",90:"direction_technology",91:"magtonic",92:"1partcarbon",93:"inside_ride_technologies",94:"sound_of_motion",95:"stryd",96:"icg",97:"mipulse",98:"bsx_athletics",99:"look",100:"campagnolo_srl",101:"body_bike_smart",102:"praxisworks",103:"limits_technology",104:"topaction_technology",105:"cosinuss",106:"fitcare",107:"magene",108:"giant_manufacturing_co",109:"tigrasport",110:"salutron",111:"technogym",112:"bryton_sensors",113:"latitude_limited",114:"soaring_technology",115:"igpsport",116:"thinkrider",117:"gopher_sport",118:"waterrower",119:"orangetheory",120:"inpeak",121:"kinetic",122:"johnson_health_tech",123:"polar_electro",124:"seesense",125:"nci_technology",255:"development",257:"healthandlife",258:"lezyne",259:"scribe_labs",260:"zwift",261:"watteam",262:"recon",263:"favero_electronics",264:"dynovelo",265:"strava",266:"precor",267:"bryton",268:"sram",269:"navman",270:"cobi",271:"spivi",272:"mio_magellan",273:"evesports",274:"sensitivus_gauge",275:"podoon",276:"life_time_fitness",277:"falco_e_motors",278:"minoura",279:"cycliq",280:"luxottica",281:"trainer_road",282:"the_sufferfest",283:"fullspeedahead",284:"virtualtraining",285:"feedbacksports",286:"omata",287:"vdo",288:"magneticdays",289:"hammerhead",290:"kinetic_by_kurt",291:"shapelog",292:"dabuziduo",293:"jetblack",294:"coros",295:"virtugo",296:"velosense",5759:"actigraphcorp"},garmin_product:{0:"hrm_bike",1:"hrm1",2:"axh01",3:"axb01",4:"axb02",5:"hrm2ss",6:"dsi_alf02",7:"hrm3ss",8:"hrm_run_single_byte_product_id",9:"bsm",10:"bcm",11:"axs01",12:"hrm_tri_single_byte_product_id",14:"fr225_single_byte_product_id",473:"fr301_china",474:"fr301_japan",475:"fr301_korea",494:"fr301_taiwan",717:"fr405",782:"fr50",987:"fr405_japan",988:"fr60",1011:"dsi_alf01",1018:"fr310xt",1036:"edge500",1124:"fr110",1169:"edge800",1199:"edge500_taiwan",1213:"edge500_japan",1253:"chirp",1274:"fr110_japan",1325:"edge200",1328:"fr910xt",1333:"edge800_taiwan",1334:"edge800_japan",1341:"alf04",1345:"fr610",1360:"fr210_japan",1380:"vector_ss",1381:"vector_cp",1386:"edge800_china",1387:"edge500_china",1410:"fr610_japan",1422:"edge500_korea",1436:"fr70",1446:"fr310xt_4t",1461:"amx",1482:"fr10",1497:"edge800_korea",1499:"swim",1537:"fr910xt_china",1551:"fenix",1555:"edge200_taiwan",1561:"edge510",1567:"edge810",1570:"tempe",1600:"fr910xt_japan",1623:"fr620",1632:"fr220",1664:"fr910xt_korea",1688:"fr10_japan",1721:"edge810_japan",1735:"virb_elite",1736:"edge_touring",1742:"edge510_japan",1743:"hrm_tri",1752:"hrm_run",1765:"fr920xt",1821:"edge510_asia",1822:"edge810_china",1823:"edge810_taiwan",1836:"edge1000",1837:"vivo_fit",1853:"virb_remote",1885:"vivo_ki",1903:"fr15",1907:"vivo_active",1918:"edge510_korea",1928:"fr620_japan",1929:"fr620_china",1930:"fr220_japan",1931:"fr220_china",1936:"approach_s6",1956:"vivo_smart",1967:"fenix2",1988:"epix",2050:"fenix3",2052:"edge1000_taiwan",2053:"edge1000_japan",2061:"fr15_japan",2067:"edge520",2070:"edge1000_china",2072:"fr620_russia",2073:"fr220_russia",2079:"vector_s",2100:"edge1000_korea",2130:"fr920xt_taiwan",2131:"fr920xt_china",2132:"fr920xt_japan",2134:"virbx",2135:"vivo_smart_apac",2140:"etrex_touch",2147:"edge25",2148:"fr25",2150:"vivo_fit2",2153:"fr225",2156:"fr630",2157:"fr230",2158:"fr735xt",2160:"vivo_active_apac",2161:"vector_2",2162:"vector_2s",2172:"virbxe",2173:"fr620_taiwan",2174:"fr220_taiwan",2175:"truswing",2188:"fenix3_china",2189:"fenix3_twn",2192:"varia_headlight",2193:"varia_taillight_old",2204:"edge_explore_1000",2219:"fr225_asia",2225:"varia_radar_taillight",2226:"varia_radar_display",2238:"edge20",2262:"d2_bravo",2266:"approach_s20",2276:"varia_remote",2327:"hrm4_run",2337:"vivo_active_hr",2348:"vivo_smart_hr",2368:"vivo_move",2398:"varia_vision",2406:"vivo_fit3",2413:"fenix3_hr",2417:"virb_ultra_30",2429:"index_smart_scale",2431:"fr235",2432:"fenix3_chronos",2441:"oregon7xx",2444:"rino7xx",2496:"nautix",2530:"edge_820",2531:"edge_explore_820",2544:"fenix5s",2547:"d2_bravo_titanium",2567:"varia_ut800",2593:"running_dynamics_pod",2604:"fenix5x",2606:"vivo_fit_jr",2691:"fr935",2697:"fenix5",2859:"descent",10007:"sdm4",10014:"edge_remote",20119:"training_center",65531:"connectiq_simulator",65532:"android_antplus_plugin",65534:"connect"},antplus_device_type:{1:"antfs",11:"bike_power",12:"environment_sensor_legacy",15:"multi_sport_speed_distance",16:"control",17:"fitness_equipment",18:"blood_pressure",19:"geocache_node",20:"light_electric_vehicle",25:"env_sensor",26:"racquet",27:"control_hub",31:"muscle_oxygen",34:"shifting",35:"bike_light_main",36:"bike_light_shared",38:"exd",40:"bike_radar",46:"bike_aero",119:"weight_scale",120:"heart_rate",121:"bike_speed_cadence",122:"bike_cadence",123:"bike_speed",124:"stride_speed_distance"},local_device_type:{0:"gps",1:"glonass",2:"gps_glonass",3:"accelerometer",4:"barometer",5:"temperature",10:"whr",12:"sensor_hub"},ble_device_type:{0:"connected_gps",1:"heart_rate",2:"bike_power",3:"bike_speed_cadence",4:"bike_speed",5:"bike_cadence",6:"footpod",7:"bike_trainer"},ant_network:{0:"public",1:"antplus",2:"antfs",3:"private"},workout_capabilities:{0:0,1:"interval",2:"custom",4:"fitness_equipment",8:"firstbeat",16:"new_leaf",32:"tcx",128:"speed",256:"heart_rate",512:"distance",1024:"cadence",2048:"power",4096:"grade",8192:"resistance",16384:"protected"},battery_status:{0:0,1:"new",2:"good",3:"ok",4:"low",5:"critical",6:"charging",7:"unknown"},hr_type:{0:"normal",1:"irregular"},course_capabilities:{0:0,1:"processed",2:"valid",4:"time",8:"distance",16:"position",32:"heart_rate",64:"power",128:"cadence",256:"training",512:"navigation",1024:"bikeway"},weight:{0:0,65534:"calculating"},workout_hr:{0:0,100:"bpm_offset"},workout_power:{0:0,1e3:"watts_offset"},bp_status:{0:"no_error",1:"error_incomplete_data",2:"error_no_measurement",3:"error_data_out_of_range",4:"error_irregular_heart_rate"},user_local_id:{0:"local_min",15:"local_max",16:"stationary_min",255:"stationary_max",256:"portable_min",65534:"portable_max"},swim_stroke:{0:"freestyle",1:"backstroke",2:"breaststroke",3:"butterfly",4:"drill",5:"mixed",6:"im"},activity_type:{0:"generic",1:"running",2:"cycling",3:"transition",4:"fitness_equipment",5:"swimming",6:"walking",8:"sedentary",254:"all"},activity_subtype:{0:"generic",1:"treadmill",2:"street",3:"trail",4:"track",5:"spin",6:"indoor_cycling",7:"road",8:"mountain",9:"downhill",10:"recumbent",11:"cyclocross",12:"hand_cycling",13:"track_cycling",14:"indoor_rowing",15:"elliptical",16:"stair_climbing",17:"lap_swimming",18:"open_water",254:"all"},activity_level:{0:"low",1:"medium",2:"high"},side:{0:"right",1:"left"},left_right_balance:{0:0,127:"mask",128:"right"},left_right_balance_100:{0:0,16383:"mask",32768:"right"},length_type:{0:"idle",1:"active"},day_of_week:{0:"sunday",1:"monday",2:"tuesday",3:"wednesday",4:"thursday",5:"friday",6:"saturday"},connectivity_capabilities:{0:0,1:"bluetooth",2:"bluetooth_le",4:"ant",8:"activity_upload",16:"course_download",32:"workout_download",64:"live_track",128:"weather_conditions",256:"weather_alerts",512:"gps_ephemeris_download",1024:"explicit_archive",2048:"setup_incomplete",4096:"continue_sync_after_software_update",8192:"connect_iq_app_download",16384:"golf_course_download",32768:"device_initiates_sync",65536:"connect_iq_watch_app_download",131072:"connect_iq_widget_download",262144:"connect_iq_watch_face_download",524288:"connect_iq_data_field_download",1048576:"connect_iq_app_managment",2097152:"swing_sensor",4194304:"swing_sensor_remote",8388608:"incident_detection",16777216:"audio_prompts",33554432:"wifi_verification",67108864:"true_up",134217728:"find_my_watch",268435456:"remote_manual_sync",536870912:"live_track_auto_start",1073741824:"live_track_messaging",2147483648:"instant_input"},weather_report:{0:"current",1:"hourly_forecast",2:"daily_forecast"},weather_status:{0:"clear",1:"partly_cloudy",2:"mostly_cloudy",3:"rain",4:"snow",5:"windy",6:"thunderstorms",7:"wintry_mix",8:"fog",11:"hazy",12:"hail",13:"scattered_showers",14:"scattered_thunderstorms",15:"unknown_precipitation",16:"light_rain",17:"heavy_rain",18:"light_snow",19:"heavy_snow",20:"light_rain_snow",21:"heavy_rain_snow",22:"cloudy"},weather_severity:{0:"unknown",1:"warning",2:"watch",3:"advisory",4:"statement"},weather_severe_type:{0:"unspecified",1:"tornado",2:"tsunami",3:"hurricane",4:"extreme_wind",5:"typhoon",6:"inland_hurricane",7:"hurricane_force_wind",8:"waterspout",9:"severe_thunderstorm",10:"wreckhouse_winds",11:"les_suetes_wind",12:"avalanche",13:"flash_flood",14:"tropical_storm",15:"inland_tropical_storm",16:"blizzard",17:"ice_storm",18:"freezing_rain",19:"debris_flow",20:"flash_freeze",21:"dust_storm",22:"high_wind",23:"winter_storm",24:"heavy_freezing_spray",25:"extreme_cold",26:"wind_chill",27:"cold_wave",28:"heavy_snow_alert",29:"lake_effect_blowing_snow",30:"snow_squall",31:"lake_effect_snow",32:"winter_weather",33:"sleet",34:"snowfall",35:"snow_and_blowing_snow",36:"blowing_snow",37:"snow_alert",38:"arctic_outflow",39:"freezing_drizzle",40:"storm",41:"storm_surge",42:"rainfall",43:"areal_flood",44:"coastal_flood",45:"lakeshore_flood",46:"excessive_heat",47:"heat",48:"weather",49:"high_heat_and_humidity",50:"humidex_and_health",51:"humidex",52:"gale",53:"freezing_spray",54:"special_marine",55:"squall",56:"strong_wind",57:"lake_wind",58:"marine_weather",59:"wind",60:"small_craft_hazardous_seas",61:"hazardous_seas",62:"small_craft",63:"small_craft_winds",64:"small_craft_rough_bar",65:"high_water_level",66:"ashfall",67:"freezing_fog",68:"dense_fog",69:"dense_smoke",70:"blowing_dust",71:"hard_freeze",72:"freeze",73:"frost",74:"fire_weather",75:"flood",76:"rip_tide",77:"high_surf",78:"smog",79:"air_quality",80:"brisk_wind",81:"air_stagnation",82:"low_water",83:"hydrological",84:"special_weather"},stroke_type:{0:"no_event",1:"other",2:"serve",3:"forehand",4:"backhand",5:"smash"},body_location:{0:"left_leg",1:"left_calf",2:"left_shin",3:"left_hamstring",4:"left_quad",5:"left_glute",6:"right_leg",7:"right_calf",8:"right_shin",9:"right_hamstring",10:"right_quad",11:"right_glute",12:"torso_back",13:"left_lower_back",14:"left_upper_back",15:"right_lower_back",16:"right_upper_back",17:"torso_front",18:"left_abdomen",19:"left_chest",20:"right_abdomen",21:"right_chest",22:"left_arm",23:"left_shoulder",24:"left_bicep",25:"left_tricep",26:"left_brachioradialis",27:"left_forearm_extensors",28:"right_arm",29:"right_shoulder",30:"right_bicep",31:"right_tricep",32:"right_brachioradialis",33:"right_forearm_extensors",34:"neck",35:"throat",36:"waist_mid_back",37:"waist_front",38:"waist_left",39:"waist_right"},segment_lap_status:{0:"end",1:"fail"},segment_leaderboard_type:{0:"overall",1:"personal_best",2:"connections",3:"group",4:"challenger",5:"kom",6:"qom",7:"pr",8:"goal",9:"rival",10:"club_leader"},segment_delete_status:{0:"do_not_delete",1:"delete_one",2:"delete_all"},segment_selection_type:{0:"starred",1:"suggested"},source_type:{0:"ant",1:"antplus",2:"bluetooth",3:"bluetooth_low_energy",4:"wifi",5:"local"},display_orientation:{0:"auto",1:"portrait",2:"landscape",3:"portrait_flipped",4:"landscape_flipped"},workout_equipment:{0:"none",1:"swim_fins",2:"swim_kickboard",3:"swim_paddles",4:"swim_pull_buoy",5:"swim_snorkel"},watchface_mode:{0:"digital",1:"analog",2:"connect_iq",3:"disabled"},digital_watchface_layout:{0:"traditional",1:"modern",2:"bold"},analog_watchface_layout:{0:"minimal",1:"traditional",2:"modern"},rider_position_type:{0:"seated",1:"standing",2:"transition_to_seated",3:"transition_to_standing"},power_phase_type:{0:"power_phase_start_angle",1:"power_phase_end_angle",2:"power_phase_arc_length",3:"power_phase_center"},camera_event_type:{0:"video_start",1:"video_split",2:"video_end",3:"photo_taken",4:"video_second_stream_start",5:"video_second_stream_split",6:"video_second_stream_end",7:"video_split_start",8:"video_second_stream_split_start",11:"video_pause",12:"video_second_stream_pause",13:"video_resume",14:"video_second_stream_resume"},sensor_type:{0:"accelerometer",1:"gyroscope",2:"compass",3:"barometer"},bike_light_network_config_type:{0:"auto",4:"individual",5:"high_visibility",6:"trail"},comm_timeout_type:{0:"wildcard_pairing_timeout",1:"pairing_timeout",2:"connection_lost",3:"connection_timeout"},camera_orientation_type:{0:"camera_orientation_0",1:"camera_orientation_90",2:"camera_orientation_180",3:"camera_orientation_270"},attitude_stage:{0:"failed",1:"aligning",2:"degraded",3:"valid"},attitude_validity:{0:0,1:"track_angle_heading_valid",2:"pitch_valid",4:"roll_valid",8:"lateral_body_accel_valid",16:"normal_body_accel_valid",32:"turn_rate_valid",64:"hw_fail",128:"mag_invalid",256:"no_gps",512:"gps_invalid",1024:"solution_coasting",2048:"true_track_angle",4096:"magnetic_heading"},auto_sync_frequency:{0:"never",1:"occasionally",2:"frequent",3:"once_a_day",4:"remote"},exd_layout:{0:"full_screen",1:"half_vertical",2:"half_horizontal",3:"half_vertical_right_split",4:"half_horizontal_bottom_split",5:"full_quarter_split",6:"half_vertical_left_split",7:"half_horizontal_top_split"},exd_display_type:{0:"numerical",1:"simple",2:"graph",3:"bar",4:"circle_graph",5:"virtual_partner",6:"balance",7:"string_list",8:"string",9:"simple_dynamic_icon",10:"gauge"},exd_data_units:{0:"no_units",1:"laps",2:"miles_per_hour",3:"kilometers_per_hour",4:"feet_per_hour",5:"meters_per_hour",6:"degrees_celsius",7:"degrees_farenheit",8:"zone",9:"gear",10:"rpm",11:"bpm",12:"degrees",13:"millimeters",14:"meters",15:"kilometers",16:"feet",17:"yards",18:"kilofeet",19:"miles",20:"time",21:"enum_turn_type",22:"percent",23:"watts",24:"watts_per_kilogram",25:"enum_battery_status",26:"enum_bike_light_beam_angle_mode",27:"enum_bike_light_battery_status",28:"enum_bike_light_network_config_type",29:"lights",30:"seconds",31:"minutes",32:"hours",33:"calories",34:"kilojoules",35:"milliseconds",36:"second_per_mile",37:"second_per_kilometer",38:"centimeter",39:"enum_course_point",40:"bradians",41:"enum_sport",42:"inches_hg",43:"mm_hg",44:"mbars",45:"hecto_pascals",46:"feet_per_min",47:"meters_per_min",48:"meters_per_sec",49:"eight_cardinal"},exd_qualifiers:{0:"no_qualifier",1:"instantaneous",2:"average",3:"lap",4:"maximum",5:"maximum_average",6:"maximum_lap",7:"last_lap",8:"average_lap",9:"to_destination",10:"to_go",11:"to_next",12:"next_course_point",13:"total",14:"three_second_average",15:"ten_second_average",16:"thirty_second_average",17:"percent_maximum",18:"percent_maximum_average",19:"lap_percent_maximum",20:"elapsed",21:"sunrise",22:"sunset",23:"compared_to_virtual_partner",24:"maximum_24h",25:"minimum_24h",26:"minimum",27:"first",28:"second",29:"third",30:"shifter",31:"last_sport",32:"moving",33:"stopped",34:"estimated_total",242:"zone_9",243:"zone_8",244:"zone_7",245:"zone_6",246:"zone_5",247:"zone_4",248:"zone_3",249:"zone_2",250:"zone_1"},exd_descriptors:{0:"bike_light_battery_status",1:"beam_angle_status",2:"batery_level",3:"light_network_mode",4:"number_lights_connected",5:"cadence",6:"distance",7:"estimated_time_of_arrival",8:"heading",9:"time",10:"battery_level",11:"trainer_resistance",12:"trainer_target_power",13:"time_seated",14:"time_standing",15:"elevation",16:"grade",17:"ascent",18:"descent",19:"vertical_speed",20:"di2_battery_level",21:"front_gear",22:"rear_gear",23:"gear_ratio",24:"heart_rate",25:"heart_rate_zone",26:"time_in_heart_rate_zone",27:"heart_rate_reserve",28:"calories",29:"gps_accuracy",30:"gps_signal_strength",31:"temperature",32:"time_of_day",33:"balance",34:"pedal_smoothness",35:"power",36:"functional_threshold_power",37:"intensity_factor",38:"work",39:"power_ratio",40:"normalized_power",41:"training_stress_Score",42:"time_on_zone",43:"speed",44:"laps",45:"reps",46:"workout_step",47:"course_distance",48:"navigation_distance",49:"course_estimated_time_of_arrival",50:"navigation_estimated_time_of_arrival",51:"course_time",52:"navigation_time",53:"course_heading",54:"navigation_heading",55:"power_zone",56:"torque_effectiveness",57:"timer_time",58:"power_weight_ratio",59:"left_platform_center_offset",60:"right_platform_center_offset",61:"left_power_phase_start_angle",62:"right_power_phase_start_angle",63:"left_power_phase_finish_angle",64:"right_power_phase_finish_angle",65:"gears",66:"pace",67:"training_effect",68:"vertical_oscillation",69:"vertical_ratio",70:"ground_contact_time",71:"left_ground_contact_time_balance",72:"right_ground_contact_time_balance",73:"stride_length",74:"running_cadence",75:"performance_condition",76:"course_type",77:"time_in_power_zone",78:"navigation_turn",79:"course_location",80:"navigation_location",81:"compass",82:"gear_combo",83:"muscle_oxygen",84:"icon",85:"compass_heading",86:"gps_heading",87:"gps_elevation",88:"anaerobic_training_effect",89:"course",90:"off_course",91:"glide_ratio",92:"vertical_distance",93:"vmg",94:"ambient_pressure",95:"pressure",96:"vam"},auto_activity_detect:{0:"none",1:"running",2:"cycling",4:"swimming",8:"walking",16:"elliptical",32:"sedentary"},supported_exd_screen_layouts:{0:0,1:"full_screen",2:"half_vertical",4:"half_horizontal",8:"half_vertical_right_split",16:"half_horizontal_bottom_split",32:"full_quarter_split",64:"half_vertical_left_split",128:"half_horizontal_top_split"},fit_base_type:{0:"enum",1:"sint8",2:"uint8",7:"string",10:"uint8z",13:"byte",131:"sint16",132:"uint16",133:"sint32",134:"uint32",136:"float32",137:"float64",139:"uint16z",140:"uint32z",142:"sint64",143:"uint64",144:"uint64z"},turn_type:{0:"arriving_idx",1:"arriving_left_idx",2:"arriving_right_idx",3:"arriving_via_idx",4:"arriving_via_left_idx",5:"arriving_via_right_idx",6:"bear_keep_left_idx",7:"bear_keep_right_idx",8:"continue_idx",9:"exit_left_idx",10:"exit_right_idx",11:"ferry_idx",12:"roundabout_45_idx",13:"roundabout_90_idx",14:"roundabout_135_idx",15:"roundabout_180_idx",16:"roundabout_225_idx",17:"roundabout_270_idx",18:"roundabout_315_idx",19:"roundabout_360_idx",20:"roundabout_neg_45_idx",21:"roundabout_neg_90_idx",22:"roundabout_neg_135_idx",23:"roundabout_neg_180_idx",24:"roundabout_neg_225_idx",25:"roundabout_neg_270_idx",26:"roundabout_neg_315_idx",27:"roundabout_neg_360_idx",28:"roundabout_generic_idx",29:"roundabout_neg_generic_idx",30:"sharp_turn_left_idx",31:"sharp_turn_right_idx",32:"turn_left_idx",33:"turn_right_idx",34:"uturn_left_idx",35:"uturn_right_idx",36:"icon_inv_idx",37:"icon_idx_cnt"},bike_light_beam_angle_mode:{0:"manual",1:"auto"},fit_base_unit:{0:"other",1:"kilogram",2:"pound"},set_type:{0:"rest",1:"active"},exercise_category:{0:"bench_press",1:"calf_raise",2:"cardio",3:"carry",4:"chop",5:"core",6:"crunch",7:"curl",8:"deadlift",9:"flye",10:"hip_raise",11:"hip_stability",12:"hip_swing",13:"hyperextension",14:"lateral_raise",15:"leg_curl",16:"leg_raise",17:"lunge",18:"olympic_lift",19:"plank",20:"plyo",21:"pull_up",22:"push_up",23:"row",24:"shoulder_press",25:"shoulder_stability",26:"shrug",27:"sit_up",28:"squat",29:"total_body",30:"triceps_extension",31:"warm_up",32:"run",65534:"unknown"},bench_press_exercise_name:{0:"alternating_dumbbell_chest_press_on_swiss_ball",1:"barbell_bench_press",2:"barbell_board_bench_press",3:"barbell_floor_press",4:"close_grip_barbell_bench_press",5:"decline_dumbbell_bench_press",6:"dumbbell_bench_press",7:"dumbbell_floor_press",8:"incline_barbell_bench_press",9:"incline_dumbbell_bench_press",10:"incline_smith_machine_bench_press",11:"isometric_barbell_bench_press",12:"kettlebell_chest_press",13:"neutral_grip_dumbbell_bench_press",14:"neutral_grip_dumbbell_incline_bench_press",15:"one_arm_floor_press",16:"weighted_one_arm_floor_press",17:"partial_lockout",18:"reverse_grip_barbell_bench_press",19:"reverse_grip_incline_bench_press",20:"single_arm_cable_chest_press",21:"single_arm_dumbbell_bench_press",22:"smith_machine_bench_press",23:"swiss_ball_dumbbell_chest_press",24:"triple_stop_barbell_bench_press",25:"wide_grip_barbell_bench_press",26:"alternating_dumbbell_chest_press"},calf_raise_exercise_name:{0:"3_way_calf_raise",1:"3_way_weighted_calf_raise",2:"3_way_single_leg_calf_raise",3:"3_way_weighted_single_leg_calf_raise",4:"donkey_calf_raise",5:"weighted_donkey_calf_raise",6:"seated_calf_raise",7:"weighted_seated_calf_raise",8:"seated_dumbbell_toe_raise",9:"single_leg_bent_knee_calf_raise",10:"weighted_single_leg_bent_knee_calf_raise",11:"single_leg_decline_push_up",12:"single_leg_donkey_calf_raise",13:"weighted_single_leg_donkey_calf_raise",14:"single_leg_hip_raise_with_knee_hold",15:"single_leg_standing_calf_raise",16:"single_leg_standing_dumbbell_calf_raise",17:"standing_barbell_calf_raise",18:"standing_calf_raise",19:"weighted_standing_calf_raise",20:"standing_dumbbell_calf_raise"},cardio_exercise_name:{0:"bob_and_weave_circle",1:"weighted_bob_and_weave_circle",2:"cardio_core_crawl",3:"weighted_cardio_core_crawl",4:"double_under",5:"weighted_double_under",6:"jump_rope",7:"weighted_jump_rope",8:"jump_rope_crossover",9:"weighted_jump_rope_crossover",10:"jump_rope_jog",11:"weighted_jump_rope_jog",12:"jumping_jacks",13:"weighted_jumping_jacks",14:"ski_moguls",15:"weighted_ski_moguls",16:"split_jacks",17:"weighted_split_jacks",18:"squat_jacks",19:"weighted_squat_jacks",20:"triple_under",21:"weighted_triple_under"},carry_exercise_name:{0:"bar_holds",1:"farmers_walk",2:"farmers_walk_on_toes",3:"hex_dumbbell_hold",4:"overhead_carry"},chop_exercise_name:{0:"cable_pull_through",1:"cable_rotational_lift",2:"cable_woodchop",3:"cross_chop_to_knee",4:"weighted_cross_chop_to_knee",5:"dumbbell_chop",6:"half_kneeling_rotation",7:"weighted_half_kneeling_rotation",8:"half_kneeling_rotational_chop",9:"half_kneeling_rotational_reverse_chop",10:"half_kneeling_stability_chop",11:"half_kneeling_stability_reverse_chop",12:"kneeling_rotational_chop",13:"kneeling_rotational_reverse_chop",14:"kneeling_stability_chop",15:"kneeling_woodchopper",16:"medicine_ball_wood_chops",17:"power_squat_chops",18:"weighted_power_squat_chops",19:"standing_rotational_chop",20:"standing_split_rotational_chop",21:"standing_split_rotational_reverse_chop",22:"standing_stability_reverse_chop"},core_exercise_name:{0:"abs_jabs",1:"weighted_abs_jabs",2:"alternating_plate_reach",3:"barbell_rollout",4:"weighted_barbell_rollout",5:"body_bar_oblique_twist",6:"cable_core_press",7:"cable_side_bend",8:"side_bend",9:"weighted_side_bend",10:"crescent_circle",11:"weighted_crescent_circle",12:"cycling_russian_twist",13:"weighted_cycling_russian_twist",14:"elevated_feet_russian_twist",15:"weighted_elevated_feet_russian_twist",16:"half_turkish_get_up",17:"kettlebell_windmill",18:"kneeling_ab_wheel",19:"weighted_kneeling_ab_wheel",20:"modified_front_lever",21:"open_knee_tucks",22:"weighted_open_knee_tucks",23:"side_abs_leg_lift",24:"weighted_side_abs_leg_lift",25:"swiss_ball_jackknife",26:"weighted_swiss_ball_jackknife",27:"swiss_ball_pike",28:"weighted_swiss_ball_pike",29:"swiss_ball_rollout",30:"weighted_swiss_ball_rollout",31:"triangle_hip_press",32:"weighted_triangle_hip_press",33:"trx_suspended_jackknife",34:"weighted_trx_suspended_jackknife",35:"u_boat",36:"weighted_u_boat",37:"windmill_switches",38:"weighted_windmill_switches",39:"alternating_slide_out",40:"weighted_alternating_slide_out",41:"ghd_back_extensions",42:"weighted_ghd_back_extensions",43:"overhead_walk",44:"inchworm",45:"weighted_modified_front_lever",46:"russian_twist",47:"abdominal_leg_rotations",48:"arm_and_leg_extension_on_knees",49:"bicycle",50:"bicep_curl_with_leg_extension",51:"cat_cow",52:"corkscrew",53:"criss_cross",54:"criss_cross_with_ball",55:"double_leg_stretch",56:"knee_folds",57:"lower_lift",58:"neck_pull",59:"pelvic_clocks",60:"roll_over",61:"roll_up",62:"rolling",63:"rowing_1",64:"rowing_2",65:"scissors",66:"single_leg_circles",67:"single_leg_stretch",68:"snake_twist_1_and_2",69:"swan",70:"swimming",71:"teaser",72:"the_hundred"},crunch_exercise_name:{0:"bicycle_crunch",1:"cable_crunch",2:"circular_arm_crunch",3:"crossed_arms_crunch",4:"weighted_crossed_arms_crunch",5:"cross_leg_reverse_crunch",6:"weighted_cross_leg_reverse_crunch",7:"crunch_chop",8:"weighted_crunch_chop",9:"double_crunch",10:"weighted_double_crunch",11:"elbow_to_knee_crunch",12:"weighted_elbow_to_knee_crunch",13:"flutter_kicks",14:"weighted_flutter_kicks",15:"foam_roller_reverse_crunch_on_bench",16:"weighted_foam_roller_reverse_crunch_on_bench",17:"foam_roller_reverse_crunch_with_dumbbell",18:"foam_roller_reverse_crunch_with_medicine_ball",19:"frog_press",20:"hanging_knee_raise_oblique_crunch",21:"weighted_hanging_knee_raise_oblique_crunch",22:"hip_crossover",23:"weighted_hip_crossover",24:"hollow_rock",25:"weighted_hollow_rock",26:"incline_reverse_crunch",27:"weighted_incline_reverse_crunch",28:"kneeling_cable_crunch",29:"kneeling_cross_crunch",30:"weighted_kneeling_cross_crunch",31:"kneeling_oblique_cable_crunch",32:"knees_to_elbow",33:"leg_extensions",34:"weighted_leg_extensions",35:"leg_levers",36:"mcgill_curl_up",37:"weighted_mcgill_curl_up",38:"modified_pilates_roll_up_with_ball",39:"weighted_modified_pilates_roll_up_with_ball",40:"pilates_crunch",41:"weighted_pilates_crunch",42:"pilates_roll_up_with_ball",43:"weighted_pilates_roll_up_with_ball",44:"raised_legs_crunch",45:"weighted_raised_legs_crunch",46:"reverse_crunch",47:"weighted_reverse_crunch",48:"reverse_crunch_on_a_bench",49:"weighted_reverse_crunch_on_a_bench",50:"reverse_curl_and_lift",51:"weighted_reverse_curl_and_lift",52:"rotational_lift",53:"weighted_rotational_lift",54:"seated_alternating_reverse_crunch",55:"weighted_seated_alternating_reverse_crunch",56:"seated_leg_u",57:"weighted_seated_leg_u",58:"side_to_side_crunch_and_weave",59:"weighted_side_to_side_crunch_and_weave",60:"single_leg_reverse_crunch",61:"weighted_single_leg_reverse_crunch",62:"skater_crunch_cross",63:"weighted_skater_crunch_cross",64:"standing_cable_crunch",65:"standing_side_crunch",66:"step_climb",67:"weighted_step_climb",68:"swiss_ball_crunch",69:"swiss_ball_reverse_crunch",70:"weighted_swiss_ball_reverse_crunch",71:"swiss_ball_russian_twist",72:"weighted_swiss_ball_russian_twist",73:"swiss_ball_side_crunch",74:"weighted_swiss_ball_side_crunch",75:"thoracic_crunches_on_foam_roller",76:"weighted_thoracic_crunches_on_foam_roller",77:"triceps_crunch",78:"weighted_bicycle_crunch",79:"weighted_crunch",80:"weighted_swiss_ball_crunch",81:"toes_to_bar",82:"weighted_toes_to_bar",83:"crunch",84:"straight_leg_crunch_with_ball"},curl_exercise_name:{0:"alternating_dumbbell_biceps_curl",1:"alternating_dumbbell_biceps_curl_on_swiss_ball",2:"alternating_incline_dumbbell_biceps_curl",3:"barbell_biceps_curl",4:"barbell_reverse_wrist_curl",5:"barbell_wrist_curl",6:"behind_the_back_barbell_reverse_wrist_curl",7:"behind_the_back_one_arm_cable_curl",8:"cable_biceps_curl",9:"cable_hammer_curl",10:"cheating_barbell_biceps_curl",11:"close_grip_ez_bar_biceps_curl",12:"cross_body_dumbbell_hammer_curl",13:"dead_hang_biceps_curl",14:"decline_hammer_curl",15:"dumbbell_biceps_curl_with_static_hold",16:"dumbbell_hammer_curl",17:"dumbbell_reverse_wrist_curl",18:"dumbbell_wrist_curl",19:"ez_bar_preacher_curl",20:"forward_bend_biceps_curl",21:"hammer_curl_to_press",22:"incline_dumbbell_biceps_curl",23:"incline_offset_thumb_dumbbell_curl",24:"kettlebell_biceps_curl",25:"lying_concentration_cable_curl",26:"one_arm_preacher_curl",27:"plate_pinch_curl",28:"preacher_curl_with_cable",29:"reverse_ez_bar_curl",30:"reverse_grip_wrist_curl",31:"reverse_grip_barbell_biceps_curl",32:"seated_alternating_dumbbell_biceps_curl",33:"seated_dumbbell_biceps_curl",34:"seated_reverse_dumbbell_curl",35:"split_stance_offset_pinky_dumbbell_curl",36:"standing_alternating_dumbbell_curls",37:"standing_dumbbell_biceps_curl",38:"standing_ez_bar_biceps_curl",39:"static_curl",40:"swiss_ball_dumbbell_overhead_triceps_extension",41:"swiss_ball_ez_bar_preacher_curl",42:"twisting_standing_dumbbell_biceps_curl",43:"wide_grip_ez_bar_biceps_curl"},deadlift_exercise_name:{0:"barbell_deadlift",1:"barbell_straight_leg_deadlift",2:"dumbbell_deadlift",3:"dumbbell_single_leg_deadlift_to_row",4:"dumbbell_straight_leg_deadlift",5:"kettlebell_floor_to_shelf",6:"one_arm_one_leg_deadlift",7:"rack_pull",8:"rotational_dumbbell_straight_leg_deadlift",9:"single_arm_deadlift",10:"single_leg_barbell_deadlift",11:"single_leg_barbell_straight_leg_deadlift",12:"single_leg_deadlift_with_barbell",13:"single_leg_rdl_circuit",14:"single_leg_romanian_deadlift_with_dumbbell",15:"sumo_deadlift",16:"sumo_deadlift_high_pull",17:"trap_bar_deadlift",18:"wide_grip_barbell_deadlift"},flye_exercise_name:{0:"cable_crossover",1:"decline_dumbbell_flye",2:"dumbbell_flye",3:"incline_dumbbell_flye",4:"kettlebell_flye",5:"kneeling_rear_flye",6:"single_arm_standing_cable_reverse_flye",7:"swiss_ball_dumbbell_flye",8:"arm_rotations",9:"hug_a_tree"},hip_raise_exercise_name:{0:"barbell_hip_thrust_on_floor",1:"barbell_hip_thrust_with_bench",2:"bent_knee_swiss_ball_reverse_hip_raise",3:"weighted_bent_knee_swiss_ball_reverse_hip_raise",4:"bridge_with_leg_extension",5:"weighted_bridge_with_leg_extension",6:"clam_bridge",7:"front_kick_tabletop",8:"weighted_front_kick_tabletop",9:"hip_extension_and_cross",10:"weighted_hip_extension_and_cross",11:"hip_raise",12:"weighted_hip_raise",13:"hip_raise_with_feet_on_swiss_ball",14:"weighted_hip_raise_with_feet_on_swiss_ball",15:"hip_raise_with_head_on_bosu_ball",16:"weighted_hip_raise_with_head_on_bosu_ball",17:"hip_raise_with_head_on_swiss_ball",18:"weighted_hip_raise_with_head_on_swiss_ball",19:"hip_raise_with_knee_squeeze",20:"weighted_hip_raise_with_knee_squeeze",21:"incline_rear_leg_extension",22:"weighted_incline_rear_leg_extension",23:"kettlebell_swing",24:"marching_hip_raise",25:"weighted_marching_hip_raise",26:"marching_hip_raise_with_feet_on_a_swiss_ball",27:"weighted_marching_hip_raise_with_feet_on_a_swiss_ball",28:"reverse_hip_raise",29:"weighted_reverse_hip_raise",30:"single_leg_hip_raise",31:"weighted_single_leg_hip_raise",32:"single_leg_hip_raise_with_foot_on_bench",33:"weighted_single_leg_hip_raise_with_foot_on_bench",34:"single_leg_hip_raise_with_foot_on_bosu_ball",35:"weighted_single_leg_hip_raise_with_foot_on_bosu_ball",36:"single_leg_hip_raise_with_foot_on_foam_roller",37:"weighted_single_leg_hip_raise_with_foot_on_foam_roller",38:"single_leg_hip_raise_with_foot_on_medicine_ball",39:"weighted_single_leg_hip_raise_with_foot_on_medicine_ball",40:"single_leg_hip_raise_with_head_on_bosu_ball",41:"weighted_single_leg_hip_raise_with_head_on_bosu_ball",42:"weighted_clam_bridge",43:"single_leg_swiss_ball_hip_raise_and_leg_curl",44:"clams",45:"inner_thigh_circles",46:"inner_thigh_side_lift",47:"leg_circles",48:"leg_lift",49:"leg_lift_in_external_rotation"},hip_stability_exercise_name:{0:"band_side_lying_leg_raise",1:"dead_bug",2:"weighted_dead_bug",3:"external_hip_raise",4:"weighted_external_hip_raise",5:"fire_hydrant_kicks",6:"weighted_fire_hydrant_kicks",7:"hip_circles",8:"weighted_hip_circles",9:"inner_thigh_lift",10:"weighted_inner_thigh_lift",11:"lateral_walks_with_band_at_ankles",12:"pretzel_side_kick",13:"weighted_pretzel_side_kick",14:"prone_hip_internal_rotation",15:"weighted_prone_hip_internal_rotation",16:"quadruped",17:"quadruped_hip_extension",18:"weighted_quadruped_hip_extension",19:"quadruped_with_leg_lift",20:"weighted_quadruped_with_leg_lift",21:"side_lying_leg_raise",22:"weighted_side_lying_leg_raise",23:"sliding_hip_adduction",24:"weighted_sliding_hip_adduction",25:"standing_adduction",26:"weighted_standing_adduction",27:"standing_cable_hip_abduction",28:"standing_hip_abduction",29:"weighted_standing_hip_abduction",30:"standing_rear_leg_raise",31:"weighted_standing_rear_leg_raise",32:"supine_hip_internal_rotation",33:"weighted_supine_hip_internal_rotation"},hip_swing_excercise_name:{0:"single_arm_kettlebell_swing",1:"single_arm_dumbbell_swing",2:"step_out_swing"},hyperextension_exercise_name:{0:"back_extension_with_opposite_arm_and_leg_reach",1:"weighted_back_extension_with_opposite_arm_and_leg_reach",2:"base_rotations",3:"weighted_base_rotations",4:"bent_knee_reverse_hyperextension",5:"weighted_bent_knee_reverse_hyperextension",6:"hollow_hold_and_roll",7:"weighted_hollow_hold_and_roll",8:"kicks",9:"weighted_kicks",10:"knee_raises",11:"weighted_knee_raises",12:"kneeling_superman",13:"weighted_kneeling_superman",14:"lat_pull_down_with_row",15:"medicine_ball_deadlift_to_reach",16:"one_arm_one_leg_row",17:"one_arm_row_with_band",18:"overhead_lunge_with_medicine_ball",19:"plank_knee_tucks",20:"weighted_plank_knee_tucks",21:"side_step",22:"weighted_side_step",23:"single_leg_back_extension",24:"weighted_single_leg_back_extension",25:"spine_extension",26:"weighted_spine_extension",27:"static_back_extension",28:"weighted_static_back_extension",29:"superman_from_floor",30:"weighted_superman_from_floor",31:"swiss_ball_back_extension",32:"weighted_swiss_ball_back_extension",33:"swiss_ball_hyperextension",34:"weighted_swiss_ball_hyperextension",35:"swiss_ball_opposite_arm_and_leg_lift",36:"weighted_swiss_ball_opposite_arm_and_leg_lift",37:"superman_on_swiss_ball",38:"cobra",39:"supine_floor_barre"},lateral_raise_exercise_name:{0:"45_degree_cable_external_rotation",1:"alternating_lateral_raise_with_static_hold",2:"bar_muscle_up",3:"bent_over_lateral_raise",4:"cable_diagonal_raise",5:"cable_front_raise",6:"calorie_row",7:"combo_shoulder_raise",8:"dumbbell_diagonal_raise",9:"dumbbell_v_raise",10:"front_raise",11:"leaning_dumbbell_lateral_raise",12:"lying_dumbbell_raise",13:"muscle_up",14:"one_arm_cable_lateral_raise",15:"overhand_grip_rear_lateral_raise",16:"plate_raises",17:"ring_dip",18:"weighted_ring_dip",19:"ring_muscle_up",20:"weighted_ring_muscle_up",21:"rope_climb",22:"weighted_rope_climb",23:"scaption",24:"seated_lateral_raise",25:"seated_rear_lateral_raise",26:"side_lying_lateral_raise",27:"standing_lift",28:"suspended_row",29:"underhand_grip_rear_lateral_raise",30:"wall_slide",31:"weighted_wall_slide",32:"arm_circles",33:"shaving_the_head"},leg_curl_exercise_name:{0:"leg_curl",1:"weighted_leg_curl",2:"good_morning",3:"seated_barbell_good_morning",4:"single_leg_barbell_good_morning",5:"single_leg_sliding_leg_curl",6:"sliding_leg_curl",7:"split_barbell_good_morning",8:"split_stance_extension",9:"staggered_stance_good_morning",10:"swiss_ball_hip_raise_and_leg_curl",11:"zercher_good_morning"},leg_raise_exercise_name:{0:"hanging_knee_raise",1:"hanging_leg_raise",2:"weighted_hanging_leg_raise",3:"hanging_single_leg_raise",4:"weighted_hanging_single_leg_raise",5:"kettlebell_leg_raises",6:"leg_lowering_drill",7:"weighted_leg_lowering_drill",8:"lying_straight_leg_raise",9:"weighted_lying_straight_leg_raise",10:"medicine_ball_leg_drops",11:"quadruped_leg_raise",12:"weighted_quadruped_leg_raise",13:"reverse_leg_raise",14:"weighted_reverse_leg_raise",15:"reverse_leg_raise_on_swiss_ball",16:"weighted_reverse_leg_raise_on_swiss_ball",17:"single_leg_lowering_drill",18:"weighted_single_leg_lowering_drill",19:"weighted_hanging_knee_raise",20:"lateral_stepover",21:"weighted_lateral_stepover"},lunge_exercise_name:{0:"overhead_lunge",1:"lunge_matrix",2:"weighted_lunge_matrix",3:"alternating_barbell_forward_lunge",4:"alternating_dumbbell_lunge_with_reach",5:"back_foot_elevated_dumbbell_split_squat",6:"barbell_box_lunge",7:"barbell_bulgarian_split_squat",8:"barbell_crossover_lunge",9:"barbell_front_split_squat",10:"barbell_lunge",11:"barbell_reverse_lunge",12:"barbell_side_lunge",13:"barbell_split_squat",14:"core_control_rear_lunge",15:"diagonal_lunge",16:"drop_lunge",17:"dumbbell_box_lunge",18:"dumbbell_bulgarian_split_squat",19:"dumbbell_crossover_lunge",20:"dumbbell_diagonal_lunge",21:"dumbbell_lunge",22:"dumbbell_lunge_and_rotation",23:"dumbbell_overhead_bulgarian_split_squat",24:"dumbbell_reverse_lunge_to_high_knee_and_press",25:"dumbbell_side_lunge",26:"elevated_front_foot_barbell_split_squat",27:"front_foot_elevated_dumbbell_split_squat",28:"gunslinger_lunge",29:"lawnmower_lunge",30:"low_lunge_with_isometric_adduction",31:"low_side_to_side_lunge",32:"lunge",33:"weighted_lunge",34:"lunge_with_arm_reach",35:"lunge_with_diagonal_reach",36:"lunge_with_side_bend",37:"offset_dumbbell_lunge",38:"offset_dumbbell_reverse_lunge",39:"overhead_bulgarian_split_squat",40:"overhead_dumbbell_reverse_lunge",41:"overhead_dumbbell_split_squat",42:"overhead_lunge_with_rotation",43:"reverse_barbell_box_lunge",44:"reverse_box_lunge",45:"reverse_dumbbell_box_lunge",46:"reverse_dumbbell_crossover_lunge",47:"reverse_dumbbell_diagonal_lunge",48:"reverse_lunge_with_reach_back",49:"weighted_reverse_lunge_with_reach_back",50:"reverse_lunge_with_twist_and_overhead_reach",51:"weighted_reverse_lunge_with_twist_and_overhead_reach",52:"reverse_sliding_box_lunge",53:"weighted_reverse_sliding_box_lunge",54:"reverse_sliding_lunge",55:"weighted_reverse_sliding_lunge",56:"runners_lunge_to_balance",57:"weighted_runners_lunge_to_balance",58:"shifting_side_lunge",59:"side_and_crossover_lunge",60:"weighted_side_and_crossover_lunge",61:"side_lunge",62:"weighted_side_lunge",63:"side_lunge_and_press",64:"side_lunge_jump_off",65:"side_lunge_sweep",66:"weighted_side_lunge_sweep",67:"side_lunge_to_crossover_tap",68:"weighted_side_lunge_to_crossover_tap",69:"side_to_side_lunge_chops",70:"weighted_side_to_side_lunge_chops",71:"siff_jump_lunge",72:"weighted_siff_jump_lunge",73:"single_arm_reverse_lunge_and_press",74:"sliding_lateral_lunge",75:"weighted_sliding_lateral_lunge",76:"walking_barbell_lunge",77:"walking_dumbbell_lunge",78:"walking_lunge",79:"weighted_walking_lunge",80:"wide_grip_overhead_barbell_split_squat"},olympic_lift_exercise_name:{0:"barbell_hang_power_clean",1:"barbell_hang_squat_clean",2:"barbell_power_clean",3:"barbell_power_snatch",4:"barbell_squat_clean",5:"clean_and_jerk",6:"barbell_hang_power_snatch",7:"barbell_hang_pull",8:"barbell_high_pull",9:"barbell_snatch",10:"barbell_split_jerk",11:"clean",12:"dumbbell_clean",13:"dumbbell_hang_pull",14:"one_hand_dumbbell_split_snatch",15:"push_jerk",16:"single_arm_dumbbell_snatch",17:"single_arm_hang_snatch",18:"single_arm_kettlebell_snatch",19:"split_jerk",20:"squat_clean_and_jerk"},plank_exercise_name:{0:"45_degree_plank",1:"weighted_45_degree_plank",2:"90_degree_static_hold",3:"weighted_90_degree_static_hold",4:"bear_crawl",5:"weighted_bear_crawl",6:"cross_body_mountain_climber",7:"weighted_cross_body_mountain_climber",8:"elbow_plank_pike_jacks",9:"weighted_elbow_plank_pike_jacks",10:"elevated_feet_plank",11:"weighted_elevated_feet_plank",12:"elevator_abs",13:"weighted_elevator_abs",14:"extended_plank",15:"weighted_extended_plank",16:"full_plank_passe_twist",17:"weighted_full_plank_passe_twist",18:"inching_elbow_plank",19:"weighted_inching_elbow_plank",20:"inchworm_to_side_plank",21:"weighted_inchworm_to_side_plank",22:"kneeling_plank",23:"weighted_kneeling_plank",24:"kneeling_side_plank_with_leg_lift",25:"weighted_kneeling_side_plank_with_leg_lift",26:"lateral_roll",27:"weighted_lateral_roll",28:"lying_reverse_plank",29:"weighted_lying_reverse_plank",30:"medicine_ball_mountain_climber",31:"weighted_medicine_ball_mountain_climber",32:"modified_mountain_climber_and_extension",33:"weighted_modified_mountain_climber_and_extension",34:"mountain_climber",35:"weighted_mountain_climber",36:"mountain_climber_on_sliding_discs",37:"weighted_mountain_climber_on_sliding_discs",38:"mountain_climber_with_feet_on_bosu_ball",39:"weighted_mountain_climber_with_feet_on_bosu_ball",40:"mountain_climber_with_hands_on_bench",41:"mountain_climber_with_hands_on_swiss_ball",42:"weighted_mountain_climber_with_hands_on_swiss_ball",43:"plank",44:"plank_jacks_with_feet_on_sliding_discs",45:"weighted_plank_jacks_with_feet_on_sliding_discs",46:"plank_knee_twist",47:"weighted_plank_knee_twist",48:"plank_pike_jumps",49:"weighted_plank_pike_jumps",50:"plank_pikes",51:"weighted_plank_pikes",52:"plank_to_stand_up",53:"weighted_plank_to_stand_up",54:"plank_with_arm_raise",55:"weighted_plank_with_arm_raise",56:"plank_with_knee_to_elbow",57:"weighted_plank_with_knee_to_elbow",58:"plank_with_oblique_crunch",59:"weighted_plank_with_oblique_crunch",60:"plyometric_side_plank",61:"weighted_plyometric_side_plank",62:"rolling_side_plank",63:"weighted_rolling_side_plank",64:"side_kick_plank",65:"weighted_side_kick_plank",66:"side_plank",67:"weighted_side_plank",68:"side_plank_and_row",69:"weighted_side_plank_and_row",70:"side_plank_lift",71:"weighted_side_plank_lift",72:"side_plank_with_elbow_on_bosu_ball",73:"weighted_side_plank_with_elbow_on_bosu_ball",74:"side_plank_with_feet_on_bench",75:"weighted_side_plank_with_feet_on_bench",76:"side_plank_with_knee_circle",77:"weighted_side_plank_with_knee_circle",78:"side_plank_with_knee_tuck",79:"weighted_side_plank_with_knee_tuck",80:"side_plank_with_leg_lift",81:"weighted_side_plank_with_leg_lift",82:"side_plank_with_reach_under",83:"weighted_side_plank_with_reach_under",84:"single_leg_elevated_feet_plank",85:"weighted_single_leg_elevated_feet_plank",86:"single_leg_flex_and_extend",87:"weighted_single_leg_flex_and_extend",88:"single_leg_side_plank",89:"weighted_single_leg_side_plank",90:"spiderman_plank",91:"weighted_spiderman_plank",92:"straight_arm_plank",93:"weighted_straight_arm_plank",94:"straight_arm_plank_with_shoulder_touch",95:"weighted_straight_arm_plank_with_shoulder_touch",96:"swiss_ball_plank",97:"weighted_swiss_ball_plank",98:"swiss_ball_plank_leg_lift",99:"weighted_swiss_ball_plank_leg_lift",100:"swiss_ball_plank_leg_lift_and_hold",101:"swiss_ball_plank_with_feet_on_bench",102:"weighted_swiss_ball_plank_with_feet_on_bench",103:"swiss_ball_prone_jackknife",104:"weighted_swiss_ball_prone_jackknife",105:"swiss_ball_side_plank",106:"weighted_swiss_ball_side_plank",107:"three_way_plank",108:"weighted_three_way_plank",109:"towel_plank_and_knee_in",110:"weighted_towel_plank_and_knee_in",111:"t_stabilization",112:"weighted_t_stabilization",113:"turkish_get_up_to_side_plank",114:"weighted_turkish_get_up_to_side_plank",115:"two_point_plank",116:"weighted_two_point_plank",117:"weighted_plank",118:"wide_stance_plank_with_diagonal_arm_lift",119:"weighted_wide_stance_plank_with_diagonal_arm_lift",120:"wide_stance_plank_with_diagonal_leg_lift",121:"weighted_wide_stance_plank_with_diagonal_leg_lift",122:"wide_stance_plank_with_leg_lift",123:"weighted_wide_stance_plank_with_leg_lift",124:"wide_stance_plank_with_opposite_arm_and_leg_lift",125:"weighted_mountain_climber_with_hands_on_bench",126:"weighted_swiss_ball_plank_leg_lift_and_hold",127:"weighted_wide_stance_plank_with_opposite_arm_and_leg_lift",128:"plank_with_feet_on_swiss_ball",129:"side_plank_to_plank_with_reach_under",130:"bridge_with_glute_lower_lift",131:"bridge_one_leg_bridge",132:"plank_with_arm_variations",133:"plank_with_leg_lift",134:"reverse_plank_with_leg_pull"},plyo_exercise_name:{0:"alternating_jump_lunge",1:"weighted_alternating_jump_lunge",2:"barbell_jump_squat",3:"body_weight_jump_squat",4:"weighted_jump_squat",5:"cross_knee_strike",6:"weighted_cross_knee_strike",7:"depth_jump",8:"weighted_depth_jump",9:"dumbbell_jump_squat",10:"dumbbell_split_jump",11:"front_knee_strike",12:"weighted_front_knee_strike",13:"high_box_jump",14:"weighted_high_box_jump",15:"isometric_explosive_body_weight_jump_squat",16:"weighted_isometric_explosive_jump_squat",17:"lateral_leap_and_hop",18:"weighted_lateral_leap_and_hop",19:"lateral_plyo_squats",20:"weighted_lateral_plyo_squats",21:"lateral_slide",22:"weighted_lateral_slide",23:"medicine_ball_overhead_throws",24:"medicine_ball_side_throw",25:"medicine_ball_slam",26:"side_to_side_medicine_ball_throws",27:"side_to_side_shuffle_jump",28:"weighted_side_to_side_shuffle_jump",29:"squat_jump_onto_box",30:"weighted_squat_jump_onto_box",31:"squat_jumps_in_and_out",32:"weighted_squat_jumps_in_and_out"},pull_up_exercise_name:{0:"banded_pull_ups",1:"30_degree_lat_pulldown",2:"band_assisted_chin_up",3:"close_grip_chin_up",4:"weighted_close_grip_chin_up",5:"close_grip_lat_pulldown",6:"crossover_chin_up",7:"weighted_crossover_chin_up",8:"ez_bar_pullover",9:"hanging_hurdle",10:"weighted_hanging_hurdle",11:"kneeling_lat_pulldown",12:"kneeling_underhand_grip_lat_pulldown",13:"lat_pulldown",14:"mixed_grip_chin_up",15:"weighted_mixed_grip_chin_up",16:"mixed_grip_pull_up",17:"weighted_mixed_grip_pull_up",18:"reverse_grip_pulldown",19:"standing_cable_pullover",20:"straight_arm_pulldown",21:"swiss_ball_ez_bar_pullover",22:"towel_pull_up",23:"weighted_towel_pull_up",24:"weighted_pull_up",25:"wide_grip_lat_pulldown",26:"wide_grip_pull_up",27:"weighted_wide_grip_pull_up",28:"burpee_pull_up",29:"weighted_burpee_pull_up",30:"jumping_pull_ups",31:"weighted_jumping_pull_ups",32:"kipping_pull_up",33:"weighted_kipping_pull_up",34:"l_pull_up",35:"weighted_l_pull_up",36:"suspended_chin_up",37:"weighted_suspended_chin_up",38:"pull_up"},push_up_exercise_name:{0:"chest_press_with_band",1:"alternating_staggered_push_up",2:"weighted_alternating_staggered_push_up",3:"alternating_hands_medicine_ball_push_up",4:"weighted_alternating_hands_medicine_ball_push_up",5:"bosu_ball_push_up",6:"weighted_bosu_ball_push_up",7:"clapping_push_up",8:"weighted_clapping_push_up",9:"close_grip_medicine_ball_push_up",10:"weighted_close_grip_medicine_ball_push_up",11:"close_hands_push_up",12:"weighted_close_hands_push_up",13:"decline_push_up",14:"weighted_decline_push_up",15:"diamond_push_up",16:"weighted_diamond_push_up",17:"explosive_crossover_push_up",18:"weighted_explosive_crossover_push_up",19:"explosive_push_up",20:"weighted_explosive_push_up",21:"feet_elevated_side_to_side_push_up",22:"weighted_feet_elevated_side_to_side_push_up",23:"hand_release_push_up",24:"weighted_hand_release_push_up",25:"handstand_push_up",26:"weighted_handstand_push_up",27:"incline_push_up",28:"weighted_incline_push_up",29:"isometric_explosive_push_up",30:"weighted_isometric_explosive_push_up",31:"judo_push_up",32:"weighted_judo_push_up",33:"kneeling_push_up",34:"weighted_kneeling_push_up",35:"medicine_ball_chest_pass",36:"medicine_ball_push_up",37:"weighted_medicine_ball_push_up",38:"one_arm_push_up",39:"weighted_one_arm_push_up",40:"weighted_push_up",41:"push_up_and_row",42:"weighted_push_up_and_row",43:"push_up_plus",44:"weighted_push_up_plus",45:"push_up_with_feet_on_swiss_ball",46:"weighted_push_up_with_feet_on_swiss_ball",47:"push_up_with_one_hand_on_medicine_ball",48:"weighted_push_up_with_one_hand_on_medicine_ball",49:"shoulder_push_up",50:"weighted_shoulder_push_up",51:"single_arm_medicine_ball_push_up",52:"weighted_single_arm_medicine_ball_push_up",53:"spiderman_push_up",54:"weighted_spiderman_push_up",55:"stacked_feet_push_up",56:"weighted_stacked_feet_push_up",57:"staggered_hands_push_up",58:"weighted_staggered_hands_push_up",59:"suspended_push_up",60:"weighted_suspended_push_up",61:"swiss_ball_push_up",62:"weighted_swiss_ball_push_up",63:"swiss_ball_push_up_plus",64:"weighted_swiss_ball_push_up_plus",65:"t_push_up",66:"weighted_t_push_up",67:"triple_stop_push_up",68:"weighted_triple_stop_push_up",69:"wide_hands_push_up",70:"weighted_wide_hands_push_up",71:"parallette_handstand_push_up",72:"weighted_parallette_handstand_push_up",73:"ring_handstand_push_up",74:"weighted_ring_handstand_push_up",75:"ring_push_up",76:"weighted_ring_push_up",77:"push_up",78:"pilates_pushup"},row_exercise_name:{0:"barbell_straight_leg_deadlift_to_row",1:"cable_row_standing",2:"dumbbell_row",3:"elevated_feet_inverted_row",4:"weighted_elevated_feet_inverted_row",5:"face_pull",6:"face_pull_with_external_rotation",7:"inverted_row_with_feet_on_swiss_ball",8:"weighted_inverted_row_with_feet_on_swiss_ball",9:"kettlebell_row",10:"modified_inverted_row",11:"weighted_modified_inverted_row",12:"neutral_grip_alternating_dumbbell_row",13:"one_arm_bent_over_row",14:"one_legged_dumbbell_row",15:"renegade_row",16:"reverse_grip_barbell_row",17:"rope_handle_cable_row",18:"seated_cable_row",19:"seated_dumbbell_row",20:"single_arm_cable_row",21:"single_arm_cable_row_and_rotation",22:"single_arm_inverted_row",23:"weighted_single_arm_inverted_row",24:"single_arm_neutral_grip_dumbbell_row",25:"single_arm_neutral_grip_dumbbell_row_and_rotation",26:"suspended_inverted_row",27:"weighted_suspended_inverted_row",28:"t_bar_row",29:"towel_grip_inverted_row",30:"weighted_towel_grip_inverted_row",31:"underhand_grip_cable_row",32:"v_grip_cable_row",33:"wide_grip_seated_cable_row"},shoulder_press_exercise_name:{0:"alternating_dumbbell_shoulder_press",1:"arnold_press",2:"barbell_front_squat_to_push_press",3:"barbell_push_press",4:"barbell_shoulder_press",5:"dead_curl_press",6:"dumbbell_alternating_shoulder_press_and_twist",7:"dumbbell_hammer_curl_to_lunge_to_press",8:"dumbbell_push_press",9:"floor_inverted_shoulder_press",10:"weighted_floor_inverted_shoulder_press",11:"inverted_shoulder_press",12:"weighted_inverted_shoulder_press",13:"one_arm_push_press",14:"overhead_barbell_press",15:"overhead_dumbbell_press",16:"seated_barbell_shoulder_press",17:"seated_dumbbell_shoulder_press",18:"single_arm_dumbbell_shoulder_press",19:"single_arm_step_up_and_press",20:"smith_machine_overhead_press",21:"split_stance_hammer_curl_to_press",22:"swiss_ball_dumbbell_shoulder_press",23:"weight_plate_front_raise"},shoulder_stability_exercise_name:{0:"90_degree_cable_external_rotation",1:"band_external_rotation",2:"band_internal_rotation",3:"bent_arm_lateral_raise_and_external_rotation",4:"cable_external_rotation",5:"dumbbell_face_pull_with_external_rotation",6:"floor_i_raise",7:"weighted_floor_i_raise",8:"floor_t_raise",9:"weighted_floor_t_raise",10:"floor_y_raise",11:"weighted_floor_y_raise",12:"incline_i_raise",13:"weighted_incline_i_raise",14:"incline_l_raise",15:"weighted_incline_l_raise",16:"incline_t_raise",17:"weighted_incline_t_raise",18:"incline_w_raise",19:"weighted_incline_w_raise",20:"incline_y_raise",21:"weighted_incline_y_raise",22:"lying_external_rotation",23:"seated_dumbbell_external_rotation",24:"standing_l_raise",25:"swiss_ball_i_raise",26:"weighted_swiss_ball_i_raise",27:"swiss_ball_t_raise",28:"weighted_swiss_ball_t_raise",29:"swiss_ball_w_raise",30:"weighted_swiss_ball_w_raise",31:"swiss_ball_y_raise",32:"weighted_swiss_ball_y_raise"},shrug_exercise_name:{0:"barbell_jump_shrug",1:"barbell_shrug",2:"barbell_upright_row",3:"behind_the_back_smith_machine_shrug",4:"dumbbell_jump_shrug",5:"dumbbell_shrug",6:"dumbbell_upright_row",7:"incline_dumbbell_shrug",8:"overhead_barbell_shrug",9:"overhead_dumbbell_shrug",10:"scaption_and_shrug",11:"scapular_retraction",12:"serratus_chair_shrug",13:"weighted_serratus_chair_shrug",14:"serratus_shrug",15:"weighted_serratus_shrug",16:"wide_grip_jump_shrug"},sit_up_exercise_name:{0:"alternating_sit_up",1:"weighted_alternating_sit_up",2:"bent_knee_v_up",3:"weighted_bent_knee_v_up",4:"butterfly_sit_up",5:"weighted_butterfly_situp",6:"cross_punch_roll_up",7:"weighted_cross_punch_roll_up",8:"crossed_arms_sit_up",9:"weighted_crossed_arms_sit_up",10:"get_up_sit_up",11:"weighted_get_up_sit_up",12:"hovering_sit_up",13:"weighted_hovering_sit_up",14:"kettlebell_sit_up",15:"medicine_ball_alternating_v_up",16:"medicine_ball_sit_up",17:"medicine_ball_v_up",18:"modified_sit_up",19:"negative_sit_up",20:"one_arm_full_sit_up",21:"reclining_circle",22:"weighted_reclining_circle",23:"reverse_curl_up",24:"weighted_reverse_curl_up",25:"single_leg_swiss_ball_jackknife",26:"weighted_single_leg_swiss_ball_jackknife",27:"the_teaser",28:"the_teaser_weighted",29:"three_part_roll_down",30:"weighted_three_part_roll_down",31:"v_up",32:"weighted_v_up",33:"weighted_russian_twist_on_swiss_ball",34:"weighted_sit_up",35:"x_abs",36:"weighted_x_abs",37:"sit_up"},squat_exercise_name:{0:"leg_press",1:"back_squat_with_body_bar",2:"back_squats",3:"weighted_back_squats",4:"balancing_squat",5:"weighted_balancing_squat",6:"barbell_back_squat",7:"barbell_box_squat",8:"barbell_front_squat",9:"barbell_hack_squat",10:"barbell_hang_squat_snatch",11:"barbell_lateral_step_up",12:"barbell_quarter_squat",13:"barbell_siff_squat",14:"barbell_squat_snatch",15:"barbell_squat_with_heels_raised",16:"barbell_stepover",17:"barbell_step_up",18:"bench_squat_with_rotational_chop",19:"weighted_bench_squat_with_rotational_chop",20:"body_weight_wall_squat",21:"weighted_wall_squat",22:"box_step_squat",23:"weighted_box_step_squat",24:"braced_squat",25:"crossed_arm_barbell_front_squat",26:"crossover_dumbbell_step_up",27:"dumbbell_front_squat",28:"dumbbell_split_squat",29:"dumbbell_squat",30:"dumbbell_squat_clean",31:"dumbbell_stepover",32:"dumbbell_step_up",33:"elevated_single_leg_squat",34:"weighted_elevated_single_leg_squat",35:"figure_four_squats",36:"weighted_figure_four_squats",37:"goblet_squat",38:"kettlebell_squat",39:"kettlebell_swing_overhead",40:"kettlebell_swing_with_flip_to_squat",41:"lateral_dumbbell_step_up",42:"one_legged_squat",43:"overhead_dumbbell_squat",44:"overhead_squat",45:"partial_single_leg_squat",46:"weighted_partial_single_leg_squat",47:"pistol_squat",48:"weighted_pistol_squat",49:"plie_slides",50:"weighted_plie_slides",51:"plie_squat",52:"weighted_plie_squat",53:"prisoner_squat",54:"weighted_prisoner_squat",55:"single_leg_bench_get_up",56:"weighted_single_leg_bench_get_up",57:"single_leg_bench_squat",58:"weighted_single_leg_bench_squat",59:"single_leg_squat_on_swiss_ball",60:"weighted_single_leg_squat_on_swiss_ball",61:"squat",62:"weighted_squat",63:"squats_with_band",64:"staggered_squat",65:"weighted_staggered_squat",66:"step_up",67:"weighted_step_up",68:"suitcase_squats",69:"sumo_squat",70:"sumo_squat_slide_in",71:"weighted_sumo_squat_slide_in",72:"sumo_squat_to_high_pull",73:"sumo_squat_to_stand",74:"weighted_sumo_squat_to_stand",75:"sumo_squat_with_rotation",76:"weighted_sumo_squat_with_rotation",77:"swiss_ball_body_weight_wall_squat",78:"weighted_swiss_ball_wall_squat",79:"thrusters",80:"uneven_squat",81:"weighted_uneven_squat",82:"waist_slimming_squat",83:"wall_ball",84:"wide_stance_barbell_squat",85:"wide_stance_goblet_squat",86:"zercher_squat",87:"kbs_overhead",88:"squat_and_side_kick",89:"squat_jumps_in_n_out",90:"pilates_plie_squats_parallel_turned_out_flat_and_heels",91:"releve_straight_leg_and_knee_bent_with_one_leg_variation"},total_body_exercise_name:{0:"burpee",1:"weighted_burpee",2:"burpee_box_jump",3:"weighted_burpee_box_jump",4:"high_pull_burpee",5:"man_makers",6:"one_arm_burpee",7:"squat_thrusts",8:"weighted_squat_thrusts",9:"squat_plank_push_up",10:"weighted_squat_plank_push_up",11:"standing_t_rotation_balance",12:"weighted_standing_t_rotation_balance"},triceps_extension_exercise_name:{0:"bench_dip",1:"weighted_bench_dip",2:"body_weight_dip",3:"cable_kickback",4:"cable_lying_triceps_extension",5:"cable_overhead_triceps_extension",6:"dumbbell_kickback",7:"dumbbell_lying_triceps_extension",8:"ez_bar_overhead_triceps_extension",9:"incline_dip",10:"weighted_incline_dip",11:"incline_ez_bar_lying_triceps_extension",12:"lying_dumbbell_pullover_to_extension",13:"lying_ez_bar_triceps_extension",14:"lying_triceps_extension_to_close_grip_bench_press",15:"overhead_dumbbell_triceps_extension",16:"reclining_triceps_press",17:"reverse_grip_pressdown",18:"reverse_grip_triceps_pressdown",19:"rope_pressdown",20:"seated_barbell_overhead_triceps_extension",21:"seated_dumbbell_overhead_triceps_extension",22:"seated_ez_bar_overhead_triceps_extension",23:"seated_single_arm_overhead_dumbbell_extension",24:"single_arm_dumbbell_overhead_triceps_extension",25:"single_dumbbell_seated_overhead_triceps_extension",26:"single_leg_bench_dip_and_kick",27:"weighted_single_leg_bench_dip_and_kick",28:"single_leg_dip",29:"weighted_single_leg_dip",30:"static_lying_triceps_extension",31:"suspended_dip",32:"weighted_suspended_dip",33:"swiss_ball_dumbbell_lying_triceps_extension",34:"swiss_ball_ez_bar_lying_triceps_extension",35:"swiss_ball_ez_bar_overhead_triceps_extension",36:"tabletop_dip",37:"weighted_tabletop_dip",38:"triceps_extension_on_floor",39:"triceps_pressdown",40:"weighted_dip"},warm_up_exercise_name:{0:"quadruped_rocking",1:"neck_tilts",2:"ankle_circles",3:"ankle_dorsiflexion_with_band",4:"ankle_internal_rotation",5:"arm_circles",6:"bent_over_reach_to_sky",7:"cat_camel",8:"elbow_to_foot_lunge",9:"forward_and_backward_leg_swings",10:"groiners",11:"inverted_hamstring_stretch",12:"lateral_duck_under",13:"neck_rotations",14:"opposite_arm_and_leg_balance",15:"reach_roll_and_lift",16:"scorpion",17:"shoulder_circles",18:"side_to_side_leg_swings",19:"sleeper_stretch",20:"slide_out",21:"swiss_ball_hip_crossover",22:"swiss_ball_reach_roll_and_lift",23:"swiss_ball_windshield_wipers",24:"thoracic_rotation",25:"walking_high_kicks",26:"walking_high_knees",27:"walking_knee_hugs",28:"walking_leg_cradles",29:"walkout",30:"walkout_from_push_up_position"},run_exercise_name:{0:"run",1:"walk",2:"jog",3:"sprint"},water_type:{0:"fresh",1:"salt",2:"en13319",3:"custom"},tissue_model_type:{0:"zhl_16c"},dive_gas_status:{0:"disabled",1:"enabled",2:"backup_only"},dive_alarm_type:{0:"depth",1:"time"},dive_backlight_mode:{0:"at_depth",1:"always_on"},favero_product:{10:"assioma_uno",12:"assioma_duo"}}};function Jt(i,e){let t=w.messages[e];return t?t[i]||{}:{}}function Xt(i){let e=w.messages[i];return e?e.name:""}function me(i){return{name:Xt(i),getAttributes:e=>Jt(e,i)}}var Kt=96,Ke=128,ye=6310656e5,ee=0;function tt(i,e){let t=0;i||e.reverse();for(let n=0;n>>0;return t}function Zt(i,e,t,n){if(e.type==="uint8_array"){let s=[];for(let _=0;_127?s-256:s}return i[t]}function Ze(i,e,t,n){switch(e){case"date_time":case"local_date_time":return new Date(i*1e3+ye);case"sint32":return i*w.scConst;case"uint8":case"sint16":case"uint32":case"uint16":return t?i/t+n:i;case"uint32_array":case"uint16_array":case"uint8_array":if(Array.isArray(i)){let s=e.replace("_array","");return i.map(_=>it(_,s)?null:t?_/t+n:_)}return t?i/t+n:i;default:{if(!w.types[e])return i;let s=[];for(let l in w.types[e])l in w.types[e]&&s.push(String(w.types[e][l]));if(!s.includes("mask")){let r=w.types[e][String(i)];return r===void 0?i:r}let _={};for(let l in w.types[e])l in w.types[e]&&(w.types[e][l]==="mask"?_.value=i&Number(l):_[w.types[e][l]]=!!((i&Number(l))>>7));return _}}}function it(i,e){switch(e){case"enum":return i===255;case"sint8":return i===127;case"uint8":return i===255;case"sint16":return i===32767;case"uint16":return i===65535;case"sint32":return i===2147483647;case"uint32":return i===4294967295;case"string":return i===0;case"float32":return i===4294967295;case"float64":return i===18446744073709552e3;case"uint8z":return i===0;case"uint16z":return i===0;case"uint32z":return i===0;case"byte":return i===255;case"sint64":return i===9223372036854776e3;case"uint64":return i===18446744073709552e3;case"uint64z":return i===0;default:return!1}}function te(i,e,t){let s=w.options[e][t];return s?i*s.multiplier+s.offset:i}function Qe(i,e,t,n){switch(e){case"device_type":{let s=n.source_type==="local"||n.source_type===5,_=n.source_type==="bluetooth_low_energy"||n.source_type===3||n.source_type==="bluetooth"||n.source_type===2,l=n.source_type==="antplus"||n.source_type===1||n.source_type==="ant"||n.source_type===0;return s?w.types.local_device_type[i]||i:_?w.types.ble_device_type[i]||i:l&&w.types.antplus_device_type[i]||i}case"speed":case"enhanced_speed":case"vertical_speed":case"avg_speed":case"max_speed":case"speed_1s":case"ball_speed":case"enhanced_avg_speed":case"enhanced_max_speed":case"avg_pos_vertical_speed":case"max_pos_vertical_speed":case"avg_neg_vertical_speed":case"max_neg_vertical_speed":return te(i,"speedUnits",t.speedUnit);case"distance":case"total_distance":case"enhanced_avg_altitude":case"enhanced_min_altitude":case"enhanced_max_altitude":case"enhanced_altitude":case"height":case"odometer":case"avg_stroke_distance":case"min_altitude":case"avg_altitude":case"max_altitude":case"total_ascent":case"total_descent":case"altitude":case"cycle_length":case"auto_wheelsize":case"custom_wheelsize":case"gps_accuracy":return te(i,"lengthUnits",t.lengthUnit);case"temperature":case"avg_temperature":case"max_temperature":return te(i,"temperatureUnits",t.temperatureUnit);case"pressure":case"start_pressure":case"end_pressure":return te(i,"pressureUnits",t.pressureUnit);case"ant_id":{let s=i>>>28&15,_=i>>>24&15,l=i>>>16&255,r=i&65535;return`${s.toString(16).toUpperCase()}-${_.toString(16).toUpperCase()}-${l.toString(16).toUpperCase().padStart(2,"0")}-${r.toString(16).toUpperCase().padStart(4,"0")}`}default:return i}}function nt(i,e,t,n,s,_,l){let r=i[n],o=r&15;if((r&Ke)===Ke)o=(r&Kt)>>5;else if((r&64)===64){let h=(r&32)===32,y=i[n+2]===0,m=i[n+5],I=h?i[n+5+m*3+1]:0,F={littleEndian:y,globalMessageNumber:tt(y,[i[n+3],i[n+4]]),numberOfFields:m+I,fieldDefs:[]},U=me(F.globalMessageNumber);for(let E=0;E>4&4095,s=s^r^n[l&15],r=n[s&15],s=s>>4&4095,s=s^r^n[l>>4&15]}return s}function be(i,e,t){let n=[...i],s=0;for(let _=0;_c)r.push(u);else if(o<=c){s=d;break}}else r.push(u)}n[_][e]||(n[_][e]=r)}return n}function _t(i,e){let t=[...i],n=0;for(let s=0;su)l.push(d);else if(r<=u){n=o;break}}else l.push(d)}t[s].laps||(t[s].laps=l)}return t}var J=class{constructor(e={}){this.options={force:e.force!=null?e.force:!0,speedUnit:e.speedUnit||"m/s",lengthUnit:e.lengthUnit||"m",temperatureUnit:e.temperatureUnit||"celsius",elapsedRecordField:e.elapsedRecordField||!1,pressureUnit:e.pressureUnit||"bar",mode:e.mode||"list"}}parseAsync(e){return new Promise((t,n)=>{this.parse(e,(s,_)=>{s?n(s):_&&t(_)})})}parse(e,t){var n;let s=new Uint8Array(st(e));if(s.length<12&&(t("File to small to be a FIT file",void 0),!this.options.force))return;let _=s[0];if(_!==14&&_!==12&&(t("Incorrect header size",void 0),!this.options.force))return;let l="";for(let L=8;L<12;L++)l+=String.fromCharCode(s[L]);if(l!==".FIT"&&(t("Missing '.FIT' in header",void 0),!this.options.force))return;if(_===14){let L=s[12]+(s[13]<<8),W=we(s,0,12);if(L!==W&&!this.options.force)return}let r=s[1],o=s[2]+(s[3]<<8),u=s[4]+(s[5]<<8)+(s[6]<<16)+(s[7]<<24)+_,c=s[u]+(s[u+1]<<8),g=we(s,_===12?0:_,u);if(c!==g&&!this.options.force)return;let f={profileVersion:o,protocolVersion:r},x=[],h=[],y=[],m=[],I=[],F=[],U=[],O=[],G=[],E=[],v=[],A=[],R=[],S=[],j=[],D=[],X=[],ke=[],ie=[],xe=[],ve=[],Fe=[],Be=[],Ee=[],ne=_,lt=[],at=[],ze=this.options.mode==="cascade",rt=ze||this.options.mode==="both",se,_e,qe=0;for(;ne *) + +buffer/index.js: + (*! + * The buffer module from node.js, for the browser. + * + * @author Feross Aboukhadijeh + * @license MIT + *) +*/ +//# sourceMappingURL=fit-file-parser.mjs.map diff --git a/suites-experimental/tools-gps-tracks/dist/fit-file-parser.mjs.map b/suites-experimental/tools-gps-tracks/dist/fit-file-parser.mjs.map new file mode 100644 index 000000000..6f402eab4 --- /dev/null +++ b/suites-experimental/tools-gps-tracks/dist/fit-file-parser.mjs.map @@ -0,0 +1,7 @@ +{ + "version": 3, + "sources": ["../node_modules/base64-js/index.js", "../node_modules/ieee754/index.js", "../node_modules/buffer/index.js", "../node_modules/fit-file-parser/dist/binary.js", "../node_modules/fit-file-parser/dist/fit.js", "../node_modules/fit-file-parser/dist/messages.js", "../node_modules/fit-file-parser/dist/helper.js", "../node_modules/fit-file-parser/dist/fit-parser.js", "../src/vendor-fit.ts"], + "sourcesContent": ["'use strict'\n\nexports.byteLength = byteLength\nexports.toByteArray = toByteArray\nexports.fromByteArray = fromByteArray\n\nvar lookup = []\nvar revLookup = []\nvar Arr = typeof Uint8Array !== 'undefined' ? Uint8Array : Array\n\nvar code = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'\nfor (var i = 0, len = code.length; i < len; ++i) {\n lookup[i] = code[i]\n revLookup[code.charCodeAt(i)] = i\n}\n\n// Support decoding URL-safe base64 strings, as Node.js does.\n// See: https://en.wikipedia.org/wiki/Base64#URL_applications\nrevLookup['-'.charCodeAt(0)] = 62\nrevLookup['_'.charCodeAt(0)] = 63\n\nfunction getLens (b64) {\n var len = b64.length\n\n if (len % 4 > 0) {\n throw new Error('Invalid string. Length must be a multiple of 4')\n }\n\n // Trim off extra bytes after placeholder bytes are found\n // See: https://github.com/beatgammit/base64-js/issues/42\n var validLen = b64.indexOf('=')\n if (validLen === -1) validLen = len\n\n var placeHoldersLen = validLen === len\n ? 0\n : 4 - (validLen % 4)\n\n return [validLen, placeHoldersLen]\n}\n\n// base64 is 4/3 + up to two characters of the original data\nfunction byteLength (b64) {\n var lens = getLens(b64)\n var validLen = lens[0]\n var placeHoldersLen = lens[1]\n return ((validLen + placeHoldersLen) * 3 / 4) - placeHoldersLen\n}\n\nfunction _byteLength (b64, validLen, placeHoldersLen) {\n return ((validLen + placeHoldersLen) * 3 / 4) - placeHoldersLen\n}\n\nfunction toByteArray (b64) {\n var tmp\n var lens = getLens(b64)\n var validLen = lens[0]\n var placeHoldersLen = lens[1]\n\n var arr = new Arr(_byteLength(b64, validLen, placeHoldersLen))\n\n var curByte = 0\n\n // if there are placeholders, only get up to the last complete 4 chars\n var len = placeHoldersLen > 0\n ? validLen - 4\n : validLen\n\n var i\n for (i = 0; i < len; i += 4) {\n tmp =\n (revLookup[b64.charCodeAt(i)] << 18) |\n (revLookup[b64.charCodeAt(i + 1)] << 12) |\n (revLookup[b64.charCodeAt(i + 2)] << 6) |\n revLookup[b64.charCodeAt(i + 3)]\n arr[curByte++] = (tmp >> 16) & 0xFF\n arr[curByte++] = (tmp >> 8) & 0xFF\n arr[curByte++] = tmp & 0xFF\n }\n\n if (placeHoldersLen === 2) {\n tmp =\n (revLookup[b64.charCodeAt(i)] << 2) |\n (revLookup[b64.charCodeAt(i + 1)] >> 4)\n arr[curByte++] = tmp & 0xFF\n }\n\n if (placeHoldersLen === 1) {\n tmp =\n (revLookup[b64.charCodeAt(i)] << 10) |\n (revLookup[b64.charCodeAt(i + 1)] << 4) |\n (revLookup[b64.charCodeAt(i + 2)] >> 2)\n arr[curByte++] = (tmp >> 8) & 0xFF\n arr[curByte++] = tmp & 0xFF\n }\n\n return arr\n}\n\nfunction tripletToBase64 (num) {\n return lookup[num >> 18 & 0x3F] +\n lookup[num >> 12 & 0x3F] +\n lookup[num >> 6 & 0x3F] +\n lookup[num & 0x3F]\n}\n\nfunction encodeChunk (uint8, start, end) {\n var tmp\n var output = []\n for (var i = start; i < end; i += 3) {\n tmp =\n ((uint8[i] << 16) & 0xFF0000) +\n ((uint8[i + 1] << 8) & 0xFF00) +\n (uint8[i + 2] & 0xFF)\n output.push(tripletToBase64(tmp))\n }\n return output.join('')\n}\n\nfunction fromByteArray (uint8) {\n var tmp\n var len = uint8.length\n var extraBytes = len % 3 // if we have 1 byte left, pad 2 bytes\n var parts = []\n var maxChunkLength = 16383 // must be multiple of 3\n\n // go through the array every three bytes, we'll deal with trailing stuff later\n for (var i = 0, len2 = len - extraBytes; i < len2; i += maxChunkLength) {\n parts.push(encodeChunk(uint8, i, (i + maxChunkLength) > len2 ? len2 : (i + maxChunkLength)))\n }\n\n // pad the end with zeros, but make sure to not forget the extra bytes\n if (extraBytes === 1) {\n tmp = uint8[len - 1]\n parts.push(\n lookup[tmp >> 2] +\n lookup[(tmp << 4) & 0x3F] +\n '=='\n )\n } else if (extraBytes === 2) {\n tmp = (uint8[len - 2] << 8) + uint8[len - 1]\n parts.push(\n lookup[tmp >> 10] +\n lookup[(tmp >> 4) & 0x3F] +\n lookup[(tmp << 2) & 0x3F] +\n '='\n )\n }\n\n return parts.join('')\n}\n", "/*! ieee754. BSD-3-Clause License. Feross Aboukhadijeh */\nexports.read = function (buffer, offset, isLE, mLen, nBytes) {\n var e, m\n var eLen = (nBytes * 8) - mLen - 1\n var eMax = (1 << eLen) - 1\n var eBias = eMax >> 1\n var nBits = -7\n var i = isLE ? (nBytes - 1) : 0\n var d = isLE ? -1 : 1\n var s = buffer[offset + i]\n\n i += d\n\n e = s & ((1 << (-nBits)) - 1)\n s >>= (-nBits)\n nBits += eLen\n for (; nBits > 0; e = (e * 256) + buffer[offset + i], i += d, nBits -= 8) {}\n\n m = e & ((1 << (-nBits)) - 1)\n e >>= (-nBits)\n nBits += mLen\n for (; nBits > 0; m = (m * 256) + buffer[offset + i], i += d, nBits -= 8) {}\n\n if (e === 0) {\n e = 1 - eBias\n } else if (e === eMax) {\n return m ? NaN : ((s ? -1 : 1) * Infinity)\n } else {\n m = m + Math.pow(2, mLen)\n e = e - eBias\n }\n return (s ? -1 : 1) * m * Math.pow(2, e - mLen)\n}\n\nexports.write = function (buffer, value, offset, isLE, mLen, nBytes) {\n var e, m, c\n var eLen = (nBytes * 8) - mLen - 1\n var eMax = (1 << eLen) - 1\n var eBias = eMax >> 1\n var rt = (mLen === 23 ? Math.pow(2, -24) - Math.pow(2, -77) : 0)\n var i = isLE ? 0 : (nBytes - 1)\n var d = isLE ? 1 : -1\n var s = value < 0 || (value === 0 && 1 / value < 0) ? 1 : 0\n\n value = Math.abs(value)\n\n if (isNaN(value) || value === Infinity) {\n m = isNaN(value) ? 1 : 0\n e = eMax\n } else {\n e = Math.floor(Math.log(value) / Math.LN2)\n if (value * (c = Math.pow(2, -e)) < 1) {\n e--\n c *= 2\n }\n if (e + eBias >= 1) {\n value += rt / c\n } else {\n value += rt * Math.pow(2, 1 - eBias)\n }\n if (value * c >= 2) {\n e++\n c /= 2\n }\n\n if (e + eBias >= eMax) {\n m = 0\n e = eMax\n } else if (e + eBias >= 1) {\n m = ((value * c) - 1) * Math.pow(2, mLen)\n e = e + eBias\n } else {\n m = value * Math.pow(2, eBias - 1) * Math.pow(2, mLen)\n e = 0\n }\n }\n\n for (; mLen >= 8; buffer[offset + i] = m & 0xff, i += d, m /= 256, mLen -= 8) {}\n\n e = (e << mLen) | m\n eLen += mLen\n for (; eLen > 0; buffer[offset + i] = e & 0xff, i += d, e /= 256, eLen -= 8) {}\n\n buffer[offset + i - d] |= s * 128\n}\n", "/*!\n * The buffer module from node.js, for the browser.\n *\n * @author Feross Aboukhadijeh \n * @license MIT\n */\n/* eslint-disable no-proto */\n\n'use strict'\n\nconst base64 = require('base64-js')\nconst ieee754 = require('ieee754')\nconst customInspectSymbol =\n (typeof Symbol === 'function' && typeof Symbol['for'] === 'function') // eslint-disable-line dot-notation\n ? Symbol['for']('nodejs.util.inspect.custom') // eslint-disable-line dot-notation\n : null\n\nexports.Buffer = Buffer\nexports.SlowBuffer = SlowBuffer\nexports.INSPECT_MAX_BYTES = 50\n\nconst K_MAX_LENGTH = 0x7fffffff\nexports.kMaxLength = K_MAX_LENGTH\n\n/**\n * If `Buffer.TYPED_ARRAY_SUPPORT`:\n * === true Use Uint8Array implementation (fastest)\n * === false Print warning and recommend using `buffer` v4.x which has an Object\n * implementation (most compatible, even IE6)\n *\n * Browsers that support typed arrays are IE 10+, Firefox 4+, Chrome 7+, Safari 5.1+,\n * Opera 11.6+, iOS 4.2+.\n *\n * We report that the browser does not support typed arrays if the are not subclassable\n * using __proto__. Firefox 4-29 lacks support for adding new properties to `Uint8Array`\n * (See: https://bugzilla.mozilla.org/show_bug.cgi?id=695438). IE 10 lacks support\n * for __proto__ and has a buggy typed array implementation.\n */\nBuffer.TYPED_ARRAY_SUPPORT = typedArraySupport()\n\nif (!Buffer.TYPED_ARRAY_SUPPORT && typeof console !== 'undefined' &&\n typeof console.error === 'function') {\n console.error(\n 'This browser lacks typed array (Uint8Array) support which is required by ' +\n '`buffer` v5.x. Use `buffer` v4.x if you require old browser support.'\n )\n}\n\nfunction typedArraySupport () {\n // Can typed array instances can be augmented?\n try {\n const arr = new Uint8Array(1)\n const proto = { foo: function () { return 42 } }\n Object.setPrototypeOf(proto, Uint8Array.prototype)\n Object.setPrototypeOf(arr, proto)\n return arr.foo() === 42\n } catch (e) {\n return false\n }\n}\n\nObject.defineProperty(Buffer.prototype, 'parent', {\n enumerable: true,\n get: function () {\n if (!Buffer.isBuffer(this)) return undefined\n return this.buffer\n }\n})\n\nObject.defineProperty(Buffer.prototype, 'offset', {\n enumerable: true,\n get: function () {\n if (!Buffer.isBuffer(this)) return undefined\n return this.byteOffset\n }\n})\n\nfunction createBuffer (length) {\n if (length > K_MAX_LENGTH) {\n throw new RangeError('The value \"' + length + '\" is invalid for option \"size\"')\n }\n // Return an augmented `Uint8Array` instance\n const buf = new Uint8Array(length)\n Object.setPrototypeOf(buf, Buffer.prototype)\n return buf\n}\n\n/**\n * The Buffer constructor returns instances of `Uint8Array` that have their\n * prototype changed to `Buffer.prototype`. Furthermore, `Buffer` is a subclass of\n * `Uint8Array`, so the returned instances will have all the node `Buffer` methods\n * and the `Uint8Array` methods. Square bracket notation works as expected -- it\n * returns a single octet.\n *\n * The `Uint8Array` prototype remains unmodified.\n */\n\nfunction Buffer (arg, encodingOrOffset, length) {\n // Common case.\n if (typeof arg === 'number') {\n if (typeof encodingOrOffset === 'string') {\n throw new TypeError(\n 'The \"string\" argument must be of type string. Received type number'\n )\n }\n return allocUnsafe(arg)\n }\n return from(arg, encodingOrOffset, length)\n}\n\nBuffer.poolSize = 8192 // not used by this implementation\n\nfunction from (value, encodingOrOffset, length) {\n if (typeof value === 'string') {\n return fromString(value, encodingOrOffset)\n }\n\n if (ArrayBuffer.isView(value)) {\n return fromArrayView(value)\n }\n\n if (value == null) {\n throw new TypeError(\n 'The first argument must be one of type string, Buffer, ArrayBuffer, Array, ' +\n 'or Array-like Object. Received type ' + (typeof value)\n )\n }\n\n if (isInstance(value, ArrayBuffer) ||\n (value && isInstance(value.buffer, ArrayBuffer))) {\n return fromArrayBuffer(value, encodingOrOffset, length)\n }\n\n if (typeof SharedArrayBuffer !== 'undefined' &&\n (isInstance(value, SharedArrayBuffer) ||\n (value && isInstance(value.buffer, SharedArrayBuffer)))) {\n return fromArrayBuffer(value, encodingOrOffset, length)\n }\n\n if (typeof value === 'number') {\n throw new TypeError(\n 'The \"value\" argument must not be of type number. Received type number'\n )\n }\n\n const valueOf = value.valueOf && value.valueOf()\n if (valueOf != null && valueOf !== value) {\n return Buffer.from(valueOf, encodingOrOffset, length)\n }\n\n const b = fromObject(value)\n if (b) return b\n\n if (typeof Symbol !== 'undefined' && Symbol.toPrimitive != null &&\n typeof value[Symbol.toPrimitive] === 'function') {\n return Buffer.from(value[Symbol.toPrimitive]('string'), encodingOrOffset, length)\n }\n\n throw new TypeError(\n 'The first argument must be one of type string, Buffer, ArrayBuffer, Array, ' +\n 'or Array-like Object. Received type ' + (typeof value)\n )\n}\n\n/**\n * Functionally equivalent to Buffer(arg, encoding) but throws a TypeError\n * if value is a number.\n * Buffer.from(str[, encoding])\n * Buffer.from(array)\n * Buffer.from(buffer)\n * Buffer.from(arrayBuffer[, byteOffset[, length]])\n **/\nBuffer.from = function (value, encodingOrOffset, length) {\n return from(value, encodingOrOffset, length)\n}\n\n// Note: Change prototype *after* Buffer.from is defined to workaround Chrome bug:\n// https://github.com/feross/buffer/pull/148\nObject.setPrototypeOf(Buffer.prototype, Uint8Array.prototype)\nObject.setPrototypeOf(Buffer, Uint8Array)\n\nfunction assertSize (size) {\n if (typeof size !== 'number') {\n throw new TypeError('\"size\" argument must be of type number')\n } else if (size < 0) {\n throw new RangeError('The value \"' + size + '\" is invalid for option \"size\"')\n }\n}\n\nfunction alloc (size, fill, encoding) {\n assertSize(size)\n if (size <= 0) {\n return createBuffer(size)\n }\n if (fill !== undefined) {\n // Only pay attention to encoding if it's a string. This\n // prevents accidentally sending in a number that would\n // be interpreted as a start offset.\n return typeof encoding === 'string'\n ? createBuffer(size).fill(fill, encoding)\n : createBuffer(size).fill(fill)\n }\n return createBuffer(size)\n}\n\n/**\n * Creates a new filled Buffer instance.\n * alloc(size[, fill[, encoding]])\n **/\nBuffer.alloc = function (size, fill, encoding) {\n return alloc(size, fill, encoding)\n}\n\nfunction allocUnsafe (size) {\n assertSize(size)\n return createBuffer(size < 0 ? 0 : checked(size) | 0)\n}\n\n/**\n * Equivalent to Buffer(num), by default creates a non-zero-filled Buffer instance.\n * */\nBuffer.allocUnsafe = function (size) {\n return allocUnsafe(size)\n}\n/**\n * Equivalent to SlowBuffer(num), by default creates a non-zero-filled Buffer instance.\n */\nBuffer.allocUnsafeSlow = function (size) {\n return allocUnsafe(size)\n}\n\nfunction fromString (string, encoding) {\n if (typeof encoding !== 'string' || encoding === '') {\n encoding = 'utf8'\n }\n\n if (!Buffer.isEncoding(encoding)) {\n throw new TypeError('Unknown encoding: ' + encoding)\n }\n\n const length = byteLength(string, encoding) | 0\n let buf = createBuffer(length)\n\n const actual = buf.write(string, encoding)\n\n if (actual !== length) {\n // Writing a hex string, for example, that contains invalid characters will\n // cause everything after the first invalid character to be ignored. (e.g.\n // 'abxxcd' will be treated as 'ab')\n buf = buf.slice(0, actual)\n }\n\n return buf\n}\n\nfunction fromArrayLike (array) {\n const length = array.length < 0 ? 0 : checked(array.length) | 0\n const buf = createBuffer(length)\n for (let i = 0; i < length; i += 1) {\n buf[i] = array[i] & 255\n }\n return buf\n}\n\nfunction fromArrayView (arrayView) {\n if (isInstance(arrayView, Uint8Array)) {\n const copy = new Uint8Array(arrayView)\n return fromArrayBuffer(copy.buffer, copy.byteOffset, copy.byteLength)\n }\n return fromArrayLike(arrayView)\n}\n\nfunction fromArrayBuffer (array, byteOffset, length) {\n if (byteOffset < 0 || array.byteLength < byteOffset) {\n throw new RangeError('\"offset\" is outside of buffer bounds')\n }\n\n if (array.byteLength < byteOffset + (length || 0)) {\n throw new RangeError('\"length\" is outside of buffer bounds')\n }\n\n let buf\n if (byteOffset === undefined && length === undefined) {\n buf = new Uint8Array(array)\n } else if (length === undefined) {\n buf = new Uint8Array(array, byteOffset)\n } else {\n buf = new Uint8Array(array, byteOffset, length)\n }\n\n // Return an augmented `Uint8Array` instance\n Object.setPrototypeOf(buf, Buffer.prototype)\n\n return buf\n}\n\nfunction fromObject (obj) {\n if (Buffer.isBuffer(obj)) {\n const len = checked(obj.length) | 0\n const buf = createBuffer(len)\n\n if (buf.length === 0) {\n return buf\n }\n\n obj.copy(buf, 0, 0, len)\n return buf\n }\n\n if (obj.length !== undefined) {\n if (typeof obj.length !== 'number' || numberIsNaN(obj.length)) {\n return createBuffer(0)\n }\n return fromArrayLike(obj)\n }\n\n if (obj.type === 'Buffer' && Array.isArray(obj.data)) {\n return fromArrayLike(obj.data)\n }\n}\n\nfunction checked (length) {\n // Note: cannot use `length < K_MAX_LENGTH` here because that fails when\n // length is NaN (which is otherwise coerced to zero.)\n if (length >= K_MAX_LENGTH) {\n throw new RangeError('Attempt to allocate Buffer larger than maximum ' +\n 'size: 0x' + K_MAX_LENGTH.toString(16) + ' bytes')\n }\n return length | 0\n}\n\nfunction SlowBuffer (length) {\n if (+length != length) { // eslint-disable-line eqeqeq\n length = 0\n }\n return Buffer.alloc(+length)\n}\n\nBuffer.isBuffer = function isBuffer (b) {\n return b != null && b._isBuffer === true &&\n b !== Buffer.prototype // so Buffer.isBuffer(Buffer.prototype) will be false\n}\n\nBuffer.compare = function compare (a, b) {\n if (isInstance(a, Uint8Array)) a = Buffer.from(a, a.offset, a.byteLength)\n if (isInstance(b, Uint8Array)) b = Buffer.from(b, b.offset, b.byteLength)\n if (!Buffer.isBuffer(a) || !Buffer.isBuffer(b)) {\n throw new TypeError(\n 'The \"buf1\", \"buf2\" arguments must be one of type Buffer or Uint8Array'\n )\n }\n\n if (a === b) return 0\n\n let x = a.length\n let y = b.length\n\n for (let i = 0, len = Math.min(x, y); i < len; ++i) {\n if (a[i] !== b[i]) {\n x = a[i]\n y = b[i]\n break\n }\n }\n\n if (x < y) return -1\n if (y < x) return 1\n return 0\n}\n\nBuffer.isEncoding = function isEncoding (encoding) {\n switch (String(encoding).toLowerCase()) {\n case 'hex':\n case 'utf8':\n case 'utf-8':\n case 'ascii':\n case 'latin1':\n case 'binary':\n case 'base64':\n case 'ucs2':\n case 'ucs-2':\n case 'utf16le':\n case 'utf-16le':\n return true\n default:\n return false\n }\n}\n\nBuffer.concat = function concat (list, length) {\n if (!Array.isArray(list)) {\n throw new TypeError('\"list\" argument must be an Array of Buffers')\n }\n\n if (list.length === 0) {\n return Buffer.alloc(0)\n }\n\n let i\n if (length === undefined) {\n length = 0\n for (i = 0; i < list.length; ++i) {\n length += list[i].length\n }\n }\n\n const buffer = Buffer.allocUnsafe(length)\n let pos = 0\n for (i = 0; i < list.length; ++i) {\n let buf = list[i]\n if (isInstance(buf, Uint8Array)) {\n if (pos + buf.length > buffer.length) {\n if (!Buffer.isBuffer(buf)) buf = Buffer.from(buf)\n buf.copy(buffer, pos)\n } else {\n Uint8Array.prototype.set.call(\n buffer,\n buf,\n pos\n )\n }\n } else if (!Buffer.isBuffer(buf)) {\n throw new TypeError('\"list\" argument must be an Array of Buffers')\n } else {\n buf.copy(buffer, pos)\n }\n pos += buf.length\n }\n return buffer\n}\n\nfunction byteLength (string, encoding) {\n if (Buffer.isBuffer(string)) {\n return string.length\n }\n if (ArrayBuffer.isView(string) || isInstance(string, ArrayBuffer)) {\n return string.byteLength\n }\n if (typeof string !== 'string') {\n throw new TypeError(\n 'The \"string\" argument must be one of type string, Buffer, or ArrayBuffer. ' +\n 'Received type ' + typeof string\n )\n }\n\n const len = string.length\n const mustMatch = (arguments.length > 2 && arguments[2] === true)\n if (!mustMatch && len === 0) return 0\n\n // Use a for loop to avoid recursion\n let loweredCase = false\n for (;;) {\n switch (encoding) {\n case 'ascii':\n case 'latin1':\n case 'binary':\n return len\n case 'utf8':\n case 'utf-8':\n return utf8ToBytes(string).length\n case 'ucs2':\n case 'ucs-2':\n case 'utf16le':\n case 'utf-16le':\n return len * 2\n case 'hex':\n return len >>> 1\n case 'base64':\n return base64ToBytes(string).length\n default:\n if (loweredCase) {\n return mustMatch ? -1 : utf8ToBytes(string).length // assume utf8\n }\n encoding = ('' + encoding).toLowerCase()\n loweredCase = true\n }\n }\n}\nBuffer.byteLength = byteLength\n\nfunction slowToString (encoding, start, end) {\n let loweredCase = false\n\n // No need to verify that \"this.length <= MAX_UINT32\" since it's a read-only\n // property of a typed array.\n\n // This behaves neither like String nor Uint8Array in that we set start/end\n // to their upper/lower bounds if the value passed is out of range.\n // undefined is handled specially as per ECMA-262 6th Edition,\n // Section 13.3.3.7 Runtime Semantics: KeyedBindingInitialization.\n if (start === undefined || start < 0) {\n start = 0\n }\n // Return early if start > this.length. Done here to prevent potential uint32\n // coercion fail below.\n if (start > this.length) {\n return ''\n }\n\n if (end === undefined || end > this.length) {\n end = this.length\n }\n\n if (end <= 0) {\n return ''\n }\n\n // Force coercion to uint32. This will also coerce falsey/NaN values to 0.\n end >>>= 0\n start >>>= 0\n\n if (end <= start) {\n return ''\n }\n\n if (!encoding) encoding = 'utf8'\n\n while (true) {\n switch (encoding) {\n case 'hex':\n return hexSlice(this, start, end)\n\n case 'utf8':\n case 'utf-8':\n return utf8Slice(this, start, end)\n\n case 'ascii':\n return asciiSlice(this, start, end)\n\n case 'latin1':\n case 'binary':\n return latin1Slice(this, start, end)\n\n case 'base64':\n return base64Slice(this, start, end)\n\n case 'ucs2':\n case 'ucs-2':\n case 'utf16le':\n case 'utf-16le':\n return utf16leSlice(this, start, end)\n\n default:\n if (loweredCase) throw new TypeError('Unknown encoding: ' + encoding)\n encoding = (encoding + '').toLowerCase()\n loweredCase = true\n }\n }\n}\n\n// This property is used by `Buffer.isBuffer` (and the `is-buffer` npm package)\n// to detect a Buffer instance. It's not possible to use `instanceof Buffer`\n// reliably in a browserify context because there could be multiple different\n// copies of the 'buffer' package in use. This method works even for Buffer\n// instances that were created from another copy of the `buffer` package.\n// See: https://github.com/feross/buffer/issues/154\nBuffer.prototype._isBuffer = true\n\nfunction swap (b, n, m) {\n const i = b[n]\n b[n] = b[m]\n b[m] = i\n}\n\nBuffer.prototype.swap16 = function swap16 () {\n const len = this.length\n if (len % 2 !== 0) {\n throw new RangeError('Buffer size must be a multiple of 16-bits')\n }\n for (let i = 0; i < len; i += 2) {\n swap(this, i, i + 1)\n }\n return this\n}\n\nBuffer.prototype.swap32 = function swap32 () {\n const len = this.length\n if (len % 4 !== 0) {\n throw new RangeError('Buffer size must be a multiple of 32-bits')\n }\n for (let i = 0; i < len; i += 4) {\n swap(this, i, i + 3)\n swap(this, i + 1, i + 2)\n }\n return this\n}\n\nBuffer.prototype.swap64 = function swap64 () {\n const len = this.length\n if (len % 8 !== 0) {\n throw new RangeError('Buffer size must be a multiple of 64-bits')\n }\n for (let i = 0; i < len; i += 8) {\n swap(this, i, i + 7)\n swap(this, i + 1, i + 6)\n swap(this, i + 2, i + 5)\n swap(this, i + 3, i + 4)\n }\n return this\n}\n\nBuffer.prototype.toString = function toString () {\n const length = this.length\n if (length === 0) return ''\n if (arguments.length === 0) return utf8Slice(this, 0, length)\n return slowToString.apply(this, arguments)\n}\n\nBuffer.prototype.toLocaleString = Buffer.prototype.toString\n\nBuffer.prototype.equals = function equals (b) {\n if (!Buffer.isBuffer(b)) throw new TypeError('Argument must be a Buffer')\n if (this === b) return true\n return Buffer.compare(this, b) === 0\n}\n\nBuffer.prototype.inspect = function inspect () {\n let str = ''\n const max = exports.INSPECT_MAX_BYTES\n str = this.toString('hex', 0, max).replace(/(.{2})/g, '$1 ').trim()\n if (this.length > max) str += ' ... '\n return ''\n}\nif (customInspectSymbol) {\n Buffer.prototype[customInspectSymbol] = Buffer.prototype.inspect\n}\n\nBuffer.prototype.compare = function compare (target, start, end, thisStart, thisEnd) {\n if (isInstance(target, Uint8Array)) {\n target = Buffer.from(target, target.offset, target.byteLength)\n }\n if (!Buffer.isBuffer(target)) {\n throw new TypeError(\n 'The \"target\" argument must be one of type Buffer or Uint8Array. ' +\n 'Received type ' + (typeof target)\n )\n }\n\n if (start === undefined) {\n start = 0\n }\n if (end === undefined) {\n end = target ? target.length : 0\n }\n if (thisStart === undefined) {\n thisStart = 0\n }\n if (thisEnd === undefined) {\n thisEnd = this.length\n }\n\n if (start < 0 || end > target.length || thisStart < 0 || thisEnd > this.length) {\n throw new RangeError('out of range index')\n }\n\n if (thisStart >= thisEnd && start >= end) {\n return 0\n }\n if (thisStart >= thisEnd) {\n return -1\n }\n if (start >= end) {\n return 1\n }\n\n start >>>= 0\n end >>>= 0\n thisStart >>>= 0\n thisEnd >>>= 0\n\n if (this === target) return 0\n\n let x = thisEnd - thisStart\n let y = end - start\n const len = Math.min(x, y)\n\n const thisCopy = this.slice(thisStart, thisEnd)\n const targetCopy = target.slice(start, end)\n\n for (let i = 0; i < len; ++i) {\n if (thisCopy[i] !== targetCopy[i]) {\n x = thisCopy[i]\n y = targetCopy[i]\n break\n }\n }\n\n if (x < y) return -1\n if (y < x) return 1\n return 0\n}\n\n// Finds either the first index of `val` in `buffer` at offset >= `byteOffset`,\n// OR the last index of `val` in `buffer` at offset <= `byteOffset`.\n//\n// Arguments:\n// - buffer - a Buffer to search\n// - val - a string, Buffer, or number\n// - byteOffset - an index into `buffer`; will be clamped to an int32\n// - encoding - an optional encoding, relevant is val is a string\n// - dir - true for indexOf, false for lastIndexOf\nfunction bidirectionalIndexOf (buffer, val, byteOffset, encoding, dir) {\n // Empty buffer means no match\n if (buffer.length === 0) return -1\n\n // Normalize byteOffset\n if (typeof byteOffset === 'string') {\n encoding = byteOffset\n byteOffset = 0\n } else if (byteOffset > 0x7fffffff) {\n byteOffset = 0x7fffffff\n } else if (byteOffset < -0x80000000) {\n byteOffset = -0x80000000\n }\n byteOffset = +byteOffset // Coerce to Number.\n if (numberIsNaN(byteOffset)) {\n // byteOffset: it it's undefined, null, NaN, \"foo\", etc, search whole buffer\n byteOffset = dir ? 0 : (buffer.length - 1)\n }\n\n // Normalize byteOffset: negative offsets start from the end of the buffer\n if (byteOffset < 0) byteOffset = buffer.length + byteOffset\n if (byteOffset >= buffer.length) {\n if (dir) return -1\n else byteOffset = buffer.length - 1\n } else if (byteOffset < 0) {\n if (dir) byteOffset = 0\n else return -1\n }\n\n // Normalize val\n if (typeof val === 'string') {\n val = Buffer.from(val, encoding)\n }\n\n // Finally, search either indexOf (if dir is true) or lastIndexOf\n if (Buffer.isBuffer(val)) {\n // Special case: looking for empty string/buffer always fails\n if (val.length === 0) {\n return -1\n }\n return arrayIndexOf(buffer, val, byteOffset, encoding, dir)\n } else if (typeof val === 'number') {\n val = val & 0xFF // Search for a byte value [0-255]\n if (typeof Uint8Array.prototype.indexOf === 'function') {\n if (dir) {\n return Uint8Array.prototype.indexOf.call(buffer, val, byteOffset)\n } else {\n return Uint8Array.prototype.lastIndexOf.call(buffer, val, byteOffset)\n }\n }\n return arrayIndexOf(buffer, [val], byteOffset, encoding, dir)\n }\n\n throw new TypeError('val must be string, number or Buffer')\n}\n\nfunction arrayIndexOf (arr, val, byteOffset, encoding, dir) {\n let indexSize = 1\n let arrLength = arr.length\n let valLength = val.length\n\n if (encoding !== undefined) {\n encoding = String(encoding).toLowerCase()\n if (encoding === 'ucs2' || encoding === 'ucs-2' ||\n encoding === 'utf16le' || encoding === 'utf-16le') {\n if (arr.length < 2 || val.length < 2) {\n return -1\n }\n indexSize = 2\n arrLength /= 2\n valLength /= 2\n byteOffset /= 2\n }\n }\n\n function read (buf, i) {\n if (indexSize === 1) {\n return buf[i]\n } else {\n return buf.readUInt16BE(i * indexSize)\n }\n }\n\n let i\n if (dir) {\n let foundIndex = -1\n for (i = byteOffset; i < arrLength; i++) {\n if (read(arr, i) === read(val, foundIndex === -1 ? 0 : i - foundIndex)) {\n if (foundIndex === -1) foundIndex = i\n if (i - foundIndex + 1 === valLength) return foundIndex * indexSize\n } else {\n if (foundIndex !== -1) i -= i - foundIndex\n foundIndex = -1\n }\n }\n } else {\n if (byteOffset + valLength > arrLength) byteOffset = arrLength - valLength\n for (i = byteOffset; i >= 0; i--) {\n let found = true\n for (let j = 0; j < valLength; j++) {\n if (read(arr, i + j) !== read(val, j)) {\n found = false\n break\n }\n }\n if (found) return i\n }\n }\n\n return -1\n}\n\nBuffer.prototype.includes = function includes (val, byteOffset, encoding) {\n return this.indexOf(val, byteOffset, encoding) !== -1\n}\n\nBuffer.prototype.indexOf = function indexOf (val, byteOffset, encoding) {\n return bidirectionalIndexOf(this, val, byteOffset, encoding, true)\n}\n\nBuffer.prototype.lastIndexOf = function lastIndexOf (val, byteOffset, encoding) {\n return bidirectionalIndexOf(this, val, byteOffset, encoding, false)\n}\n\nfunction hexWrite (buf, string, offset, length) {\n offset = Number(offset) || 0\n const remaining = buf.length - offset\n if (!length) {\n length = remaining\n } else {\n length = Number(length)\n if (length > remaining) {\n length = remaining\n }\n }\n\n const strLen = string.length\n\n if (length > strLen / 2) {\n length = strLen / 2\n }\n let i\n for (i = 0; i < length; ++i) {\n const parsed = parseInt(string.substr(i * 2, 2), 16)\n if (numberIsNaN(parsed)) return i\n buf[offset + i] = parsed\n }\n return i\n}\n\nfunction utf8Write (buf, string, offset, length) {\n return blitBuffer(utf8ToBytes(string, buf.length - offset), buf, offset, length)\n}\n\nfunction asciiWrite (buf, string, offset, length) {\n return blitBuffer(asciiToBytes(string), buf, offset, length)\n}\n\nfunction base64Write (buf, string, offset, length) {\n return blitBuffer(base64ToBytes(string), buf, offset, length)\n}\n\nfunction ucs2Write (buf, string, offset, length) {\n return blitBuffer(utf16leToBytes(string, buf.length - offset), buf, offset, length)\n}\n\nBuffer.prototype.write = function write (string, offset, length, encoding) {\n // Buffer#write(string)\n if (offset === undefined) {\n encoding = 'utf8'\n length = this.length\n offset = 0\n // Buffer#write(string, encoding)\n } else if (length === undefined && typeof offset === 'string') {\n encoding = offset\n length = this.length\n offset = 0\n // Buffer#write(string, offset[, length][, encoding])\n } else if (isFinite(offset)) {\n offset = offset >>> 0\n if (isFinite(length)) {\n length = length >>> 0\n if (encoding === undefined) encoding = 'utf8'\n } else {\n encoding = length\n length = undefined\n }\n } else {\n throw new Error(\n 'Buffer.write(string, encoding, offset[, length]) is no longer supported'\n )\n }\n\n const remaining = this.length - offset\n if (length === undefined || length > remaining) length = remaining\n\n if ((string.length > 0 && (length < 0 || offset < 0)) || offset > this.length) {\n throw new RangeError('Attempt to write outside buffer bounds')\n }\n\n if (!encoding) encoding = 'utf8'\n\n let loweredCase = false\n for (;;) {\n switch (encoding) {\n case 'hex':\n return hexWrite(this, string, offset, length)\n\n case 'utf8':\n case 'utf-8':\n return utf8Write(this, string, offset, length)\n\n case 'ascii':\n case 'latin1':\n case 'binary':\n return asciiWrite(this, string, offset, length)\n\n case 'base64':\n // Warning: maxLength not taken into account in base64Write\n return base64Write(this, string, offset, length)\n\n case 'ucs2':\n case 'ucs-2':\n case 'utf16le':\n case 'utf-16le':\n return ucs2Write(this, string, offset, length)\n\n default:\n if (loweredCase) throw new TypeError('Unknown encoding: ' + encoding)\n encoding = ('' + encoding).toLowerCase()\n loweredCase = true\n }\n }\n}\n\nBuffer.prototype.toJSON = function toJSON () {\n return {\n type: 'Buffer',\n data: Array.prototype.slice.call(this._arr || this, 0)\n }\n}\n\nfunction base64Slice (buf, start, end) {\n if (start === 0 && end === buf.length) {\n return base64.fromByteArray(buf)\n } else {\n return base64.fromByteArray(buf.slice(start, end))\n }\n}\n\nfunction utf8Slice (buf, start, end) {\n end = Math.min(buf.length, end)\n const res = []\n\n let i = start\n while (i < end) {\n const firstByte = buf[i]\n let codePoint = null\n let bytesPerSequence = (firstByte > 0xEF)\n ? 4\n : (firstByte > 0xDF)\n ? 3\n : (firstByte > 0xBF)\n ? 2\n : 1\n\n if (i + bytesPerSequence <= end) {\n let secondByte, thirdByte, fourthByte, tempCodePoint\n\n switch (bytesPerSequence) {\n case 1:\n if (firstByte < 0x80) {\n codePoint = firstByte\n }\n break\n case 2:\n secondByte = buf[i + 1]\n if ((secondByte & 0xC0) === 0x80) {\n tempCodePoint = (firstByte & 0x1F) << 0x6 | (secondByte & 0x3F)\n if (tempCodePoint > 0x7F) {\n codePoint = tempCodePoint\n }\n }\n break\n case 3:\n secondByte = buf[i + 1]\n thirdByte = buf[i + 2]\n if ((secondByte & 0xC0) === 0x80 && (thirdByte & 0xC0) === 0x80) {\n tempCodePoint = (firstByte & 0xF) << 0xC | (secondByte & 0x3F) << 0x6 | (thirdByte & 0x3F)\n if (tempCodePoint > 0x7FF && (tempCodePoint < 0xD800 || tempCodePoint > 0xDFFF)) {\n codePoint = tempCodePoint\n }\n }\n break\n case 4:\n secondByte = buf[i + 1]\n thirdByte = buf[i + 2]\n fourthByte = buf[i + 3]\n if ((secondByte & 0xC0) === 0x80 && (thirdByte & 0xC0) === 0x80 && (fourthByte & 0xC0) === 0x80) {\n tempCodePoint = (firstByte & 0xF) << 0x12 | (secondByte & 0x3F) << 0xC | (thirdByte & 0x3F) << 0x6 | (fourthByte & 0x3F)\n if (tempCodePoint > 0xFFFF && tempCodePoint < 0x110000) {\n codePoint = tempCodePoint\n }\n }\n }\n }\n\n if (codePoint === null) {\n // we did not generate a valid codePoint so insert a\n // replacement char (U+FFFD) and advance only 1 byte\n codePoint = 0xFFFD\n bytesPerSequence = 1\n } else if (codePoint > 0xFFFF) {\n // encode to utf16 (surrogate pair dance)\n codePoint -= 0x10000\n res.push(codePoint >>> 10 & 0x3FF | 0xD800)\n codePoint = 0xDC00 | codePoint & 0x3FF\n }\n\n res.push(codePoint)\n i += bytesPerSequence\n }\n\n return decodeCodePointsArray(res)\n}\n\n// Based on http://stackoverflow.com/a/22747272/680742, the browser with\n// the lowest limit is Chrome, with 0x10000 args.\n// We go 1 magnitude less, for safety\nconst MAX_ARGUMENTS_LENGTH = 0x1000\n\nfunction decodeCodePointsArray (codePoints) {\n const len = codePoints.length\n if (len <= MAX_ARGUMENTS_LENGTH) {\n return String.fromCharCode.apply(String, codePoints) // avoid extra slice()\n }\n\n // Decode in chunks to avoid \"call stack size exceeded\".\n let res = ''\n let i = 0\n while (i < len) {\n res += String.fromCharCode.apply(\n String,\n codePoints.slice(i, i += MAX_ARGUMENTS_LENGTH)\n )\n }\n return res\n}\n\nfunction asciiSlice (buf, start, end) {\n let ret = ''\n end = Math.min(buf.length, end)\n\n for (let i = start; i < end; ++i) {\n ret += String.fromCharCode(buf[i] & 0x7F)\n }\n return ret\n}\n\nfunction latin1Slice (buf, start, end) {\n let ret = ''\n end = Math.min(buf.length, end)\n\n for (let i = start; i < end; ++i) {\n ret += String.fromCharCode(buf[i])\n }\n return ret\n}\n\nfunction hexSlice (buf, start, end) {\n const len = buf.length\n\n if (!start || start < 0) start = 0\n if (!end || end < 0 || end > len) end = len\n\n let out = ''\n for (let i = start; i < end; ++i) {\n out += hexSliceLookupTable[buf[i]]\n }\n return out\n}\n\nfunction utf16leSlice (buf, start, end) {\n const bytes = buf.slice(start, end)\n let res = ''\n // If bytes.length is odd, the last 8 bits must be ignored (same as node.js)\n for (let i = 0; i < bytes.length - 1; i += 2) {\n res += String.fromCharCode(bytes[i] + (bytes[i + 1] * 256))\n }\n return res\n}\n\nBuffer.prototype.slice = function slice (start, end) {\n const len = this.length\n start = ~~start\n end = end === undefined ? len : ~~end\n\n if (start < 0) {\n start += len\n if (start < 0) start = 0\n } else if (start > len) {\n start = len\n }\n\n if (end < 0) {\n end += len\n if (end < 0) end = 0\n } else if (end > len) {\n end = len\n }\n\n if (end < start) end = start\n\n const newBuf = this.subarray(start, end)\n // Return an augmented `Uint8Array` instance\n Object.setPrototypeOf(newBuf, Buffer.prototype)\n\n return newBuf\n}\n\n/*\n * Need to make sure that buffer isn't trying to write out of bounds.\n */\nfunction checkOffset (offset, ext, length) {\n if ((offset % 1) !== 0 || offset < 0) throw new RangeError('offset is not uint')\n if (offset + ext > length) throw new RangeError('Trying to access beyond buffer length')\n}\n\nBuffer.prototype.readUintLE =\nBuffer.prototype.readUIntLE = function readUIntLE (offset, byteLength, noAssert) {\n offset = offset >>> 0\n byteLength = byteLength >>> 0\n if (!noAssert) checkOffset(offset, byteLength, this.length)\n\n let val = this[offset]\n let mul = 1\n let i = 0\n while (++i < byteLength && (mul *= 0x100)) {\n val += this[offset + i] * mul\n }\n\n return val\n}\n\nBuffer.prototype.readUintBE =\nBuffer.prototype.readUIntBE = function readUIntBE (offset, byteLength, noAssert) {\n offset = offset >>> 0\n byteLength = byteLength >>> 0\n if (!noAssert) {\n checkOffset(offset, byteLength, this.length)\n }\n\n let val = this[offset + --byteLength]\n let mul = 1\n while (byteLength > 0 && (mul *= 0x100)) {\n val += this[offset + --byteLength] * mul\n }\n\n return val\n}\n\nBuffer.prototype.readUint8 =\nBuffer.prototype.readUInt8 = function readUInt8 (offset, noAssert) {\n offset = offset >>> 0\n if (!noAssert) checkOffset(offset, 1, this.length)\n return this[offset]\n}\n\nBuffer.prototype.readUint16LE =\nBuffer.prototype.readUInt16LE = function readUInt16LE (offset, noAssert) {\n offset = offset >>> 0\n if (!noAssert) checkOffset(offset, 2, this.length)\n return this[offset] | (this[offset + 1] << 8)\n}\n\nBuffer.prototype.readUint16BE =\nBuffer.prototype.readUInt16BE = function readUInt16BE (offset, noAssert) {\n offset = offset >>> 0\n if (!noAssert) checkOffset(offset, 2, this.length)\n return (this[offset] << 8) | this[offset + 1]\n}\n\nBuffer.prototype.readUint32LE =\nBuffer.prototype.readUInt32LE = function readUInt32LE (offset, noAssert) {\n offset = offset >>> 0\n if (!noAssert) checkOffset(offset, 4, this.length)\n\n return ((this[offset]) |\n (this[offset + 1] << 8) |\n (this[offset + 2] << 16)) +\n (this[offset + 3] * 0x1000000)\n}\n\nBuffer.prototype.readUint32BE =\nBuffer.prototype.readUInt32BE = function readUInt32BE (offset, noAssert) {\n offset = offset >>> 0\n if (!noAssert) checkOffset(offset, 4, this.length)\n\n return (this[offset] * 0x1000000) +\n ((this[offset + 1] << 16) |\n (this[offset + 2] << 8) |\n this[offset + 3])\n}\n\nBuffer.prototype.readBigUInt64LE = defineBigIntMethod(function readBigUInt64LE (offset) {\n offset = offset >>> 0\n validateNumber(offset, 'offset')\n const first = this[offset]\n const last = this[offset + 7]\n if (first === undefined || last === undefined) {\n boundsError(offset, this.length - 8)\n }\n\n const lo = first +\n this[++offset] * 2 ** 8 +\n this[++offset] * 2 ** 16 +\n this[++offset] * 2 ** 24\n\n const hi = this[++offset] +\n this[++offset] * 2 ** 8 +\n this[++offset] * 2 ** 16 +\n last * 2 ** 24\n\n return BigInt(lo) + (BigInt(hi) << BigInt(32))\n})\n\nBuffer.prototype.readBigUInt64BE = defineBigIntMethod(function readBigUInt64BE (offset) {\n offset = offset >>> 0\n validateNumber(offset, 'offset')\n const first = this[offset]\n const last = this[offset + 7]\n if (first === undefined || last === undefined) {\n boundsError(offset, this.length - 8)\n }\n\n const hi = first * 2 ** 24 +\n this[++offset] * 2 ** 16 +\n this[++offset] * 2 ** 8 +\n this[++offset]\n\n const lo = this[++offset] * 2 ** 24 +\n this[++offset] * 2 ** 16 +\n this[++offset] * 2 ** 8 +\n last\n\n return (BigInt(hi) << BigInt(32)) + BigInt(lo)\n})\n\nBuffer.prototype.readIntLE = function readIntLE (offset, byteLength, noAssert) {\n offset = offset >>> 0\n byteLength = byteLength >>> 0\n if (!noAssert) checkOffset(offset, byteLength, this.length)\n\n let val = this[offset]\n let mul = 1\n let i = 0\n while (++i < byteLength && (mul *= 0x100)) {\n val += this[offset + i] * mul\n }\n mul *= 0x80\n\n if (val >= mul) val -= Math.pow(2, 8 * byteLength)\n\n return val\n}\n\nBuffer.prototype.readIntBE = function readIntBE (offset, byteLength, noAssert) {\n offset = offset >>> 0\n byteLength = byteLength >>> 0\n if (!noAssert) checkOffset(offset, byteLength, this.length)\n\n let i = byteLength\n let mul = 1\n let val = this[offset + --i]\n while (i > 0 && (mul *= 0x100)) {\n val += this[offset + --i] * mul\n }\n mul *= 0x80\n\n if (val >= mul) val -= Math.pow(2, 8 * byteLength)\n\n return val\n}\n\nBuffer.prototype.readInt8 = function readInt8 (offset, noAssert) {\n offset = offset >>> 0\n if (!noAssert) checkOffset(offset, 1, this.length)\n if (!(this[offset] & 0x80)) return (this[offset])\n return ((0xff - this[offset] + 1) * -1)\n}\n\nBuffer.prototype.readInt16LE = function readInt16LE (offset, noAssert) {\n offset = offset >>> 0\n if (!noAssert) checkOffset(offset, 2, this.length)\n const val = this[offset] | (this[offset + 1] << 8)\n return (val & 0x8000) ? val | 0xFFFF0000 : val\n}\n\nBuffer.prototype.readInt16BE = function readInt16BE (offset, noAssert) {\n offset = offset >>> 0\n if (!noAssert) checkOffset(offset, 2, this.length)\n const val = this[offset + 1] | (this[offset] << 8)\n return (val & 0x8000) ? val | 0xFFFF0000 : val\n}\n\nBuffer.prototype.readInt32LE = function readInt32LE (offset, noAssert) {\n offset = offset >>> 0\n if (!noAssert) checkOffset(offset, 4, this.length)\n\n return (this[offset]) |\n (this[offset + 1] << 8) |\n (this[offset + 2] << 16) |\n (this[offset + 3] << 24)\n}\n\nBuffer.prototype.readInt32BE = function readInt32BE (offset, noAssert) {\n offset = offset >>> 0\n if (!noAssert) checkOffset(offset, 4, this.length)\n\n return (this[offset] << 24) |\n (this[offset + 1] << 16) |\n (this[offset + 2] << 8) |\n (this[offset + 3])\n}\n\nBuffer.prototype.readBigInt64LE = defineBigIntMethod(function readBigInt64LE (offset) {\n offset = offset >>> 0\n validateNumber(offset, 'offset')\n const first = this[offset]\n const last = this[offset + 7]\n if (first === undefined || last === undefined) {\n boundsError(offset, this.length - 8)\n }\n\n const val = this[offset + 4] +\n this[offset + 5] * 2 ** 8 +\n this[offset + 6] * 2 ** 16 +\n (last << 24) // Overflow\n\n return (BigInt(val) << BigInt(32)) +\n BigInt(first +\n this[++offset] * 2 ** 8 +\n this[++offset] * 2 ** 16 +\n this[++offset] * 2 ** 24)\n})\n\nBuffer.prototype.readBigInt64BE = defineBigIntMethod(function readBigInt64BE (offset) {\n offset = offset >>> 0\n validateNumber(offset, 'offset')\n const first = this[offset]\n const last = this[offset + 7]\n if (first === undefined || last === undefined) {\n boundsError(offset, this.length - 8)\n }\n\n const val = (first << 24) + // Overflow\n this[++offset] * 2 ** 16 +\n this[++offset] * 2 ** 8 +\n this[++offset]\n\n return (BigInt(val) << BigInt(32)) +\n BigInt(this[++offset] * 2 ** 24 +\n this[++offset] * 2 ** 16 +\n this[++offset] * 2 ** 8 +\n last)\n})\n\nBuffer.prototype.readFloatLE = function readFloatLE (offset, noAssert) {\n offset = offset >>> 0\n if (!noAssert) checkOffset(offset, 4, this.length)\n return ieee754.read(this, offset, true, 23, 4)\n}\n\nBuffer.prototype.readFloatBE = function readFloatBE (offset, noAssert) {\n offset = offset >>> 0\n if (!noAssert) checkOffset(offset, 4, this.length)\n return ieee754.read(this, offset, false, 23, 4)\n}\n\nBuffer.prototype.readDoubleLE = function readDoubleLE (offset, noAssert) {\n offset = offset >>> 0\n if (!noAssert) checkOffset(offset, 8, this.length)\n return ieee754.read(this, offset, true, 52, 8)\n}\n\nBuffer.prototype.readDoubleBE = function readDoubleBE (offset, noAssert) {\n offset = offset >>> 0\n if (!noAssert) checkOffset(offset, 8, this.length)\n return ieee754.read(this, offset, false, 52, 8)\n}\n\nfunction checkInt (buf, value, offset, ext, max, min) {\n if (!Buffer.isBuffer(buf)) throw new TypeError('\"buffer\" argument must be a Buffer instance')\n if (value > max || value < min) throw new RangeError('\"value\" argument is out of bounds')\n if (offset + ext > buf.length) throw new RangeError('Index out of range')\n}\n\nBuffer.prototype.writeUintLE =\nBuffer.prototype.writeUIntLE = function writeUIntLE (value, offset, byteLength, noAssert) {\n value = +value\n offset = offset >>> 0\n byteLength = byteLength >>> 0\n if (!noAssert) {\n const maxBytes = Math.pow(2, 8 * byteLength) - 1\n checkInt(this, value, offset, byteLength, maxBytes, 0)\n }\n\n let mul = 1\n let i = 0\n this[offset] = value & 0xFF\n while (++i < byteLength && (mul *= 0x100)) {\n this[offset + i] = (value / mul) & 0xFF\n }\n\n return offset + byteLength\n}\n\nBuffer.prototype.writeUintBE =\nBuffer.prototype.writeUIntBE = function writeUIntBE (value, offset, byteLength, noAssert) {\n value = +value\n offset = offset >>> 0\n byteLength = byteLength >>> 0\n if (!noAssert) {\n const maxBytes = Math.pow(2, 8 * byteLength) - 1\n checkInt(this, value, offset, byteLength, maxBytes, 0)\n }\n\n let i = byteLength - 1\n let mul = 1\n this[offset + i] = value & 0xFF\n while (--i >= 0 && (mul *= 0x100)) {\n this[offset + i] = (value / mul) & 0xFF\n }\n\n return offset + byteLength\n}\n\nBuffer.prototype.writeUint8 =\nBuffer.prototype.writeUInt8 = function writeUInt8 (value, offset, noAssert) {\n value = +value\n offset = offset >>> 0\n if (!noAssert) checkInt(this, value, offset, 1, 0xff, 0)\n this[offset] = (value & 0xff)\n return offset + 1\n}\n\nBuffer.prototype.writeUint16LE =\nBuffer.prototype.writeUInt16LE = function writeUInt16LE (value, offset, noAssert) {\n value = +value\n offset = offset >>> 0\n if (!noAssert) checkInt(this, value, offset, 2, 0xffff, 0)\n this[offset] = (value & 0xff)\n this[offset + 1] = (value >>> 8)\n return offset + 2\n}\n\nBuffer.prototype.writeUint16BE =\nBuffer.prototype.writeUInt16BE = function writeUInt16BE (value, offset, noAssert) {\n value = +value\n offset = offset >>> 0\n if (!noAssert) checkInt(this, value, offset, 2, 0xffff, 0)\n this[offset] = (value >>> 8)\n this[offset + 1] = (value & 0xff)\n return offset + 2\n}\n\nBuffer.prototype.writeUint32LE =\nBuffer.prototype.writeUInt32LE = function writeUInt32LE (value, offset, noAssert) {\n value = +value\n offset = offset >>> 0\n if (!noAssert) checkInt(this, value, offset, 4, 0xffffffff, 0)\n this[offset + 3] = (value >>> 24)\n this[offset + 2] = (value >>> 16)\n this[offset + 1] = (value >>> 8)\n this[offset] = (value & 0xff)\n return offset + 4\n}\n\nBuffer.prototype.writeUint32BE =\nBuffer.prototype.writeUInt32BE = function writeUInt32BE (value, offset, noAssert) {\n value = +value\n offset = offset >>> 0\n if (!noAssert) checkInt(this, value, offset, 4, 0xffffffff, 0)\n this[offset] = (value >>> 24)\n this[offset + 1] = (value >>> 16)\n this[offset + 2] = (value >>> 8)\n this[offset + 3] = (value & 0xff)\n return offset + 4\n}\n\nfunction wrtBigUInt64LE (buf, value, offset, min, max) {\n checkIntBI(value, min, max, buf, offset, 7)\n\n let lo = Number(value & BigInt(0xffffffff))\n buf[offset++] = lo\n lo = lo >> 8\n buf[offset++] = lo\n lo = lo >> 8\n buf[offset++] = lo\n lo = lo >> 8\n buf[offset++] = lo\n let hi = Number(value >> BigInt(32) & BigInt(0xffffffff))\n buf[offset++] = hi\n hi = hi >> 8\n buf[offset++] = hi\n hi = hi >> 8\n buf[offset++] = hi\n hi = hi >> 8\n buf[offset++] = hi\n return offset\n}\n\nfunction wrtBigUInt64BE (buf, value, offset, min, max) {\n checkIntBI(value, min, max, buf, offset, 7)\n\n let lo = Number(value & BigInt(0xffffffff))\n buf[offset + 7] = lo\n lo = lo >> 8\n buf[offset + 6] = lo\n lo = lo >> 8\n buf[offset + 5] = lo\n lo = lo >> 8\n buf[offset + 4] = lo\n let hi = Number(value >> BigInt(32) & BigInt(0xffffffff))\n buf[offset + 3] = hi\n hi = hi >> 8\n buf[offset + 2] = hi\n hi = hi >> 8\n buf[offset + 1] = hi\n hi = hi >> 8\n buf[offset] = hi\n return offset + 8\n}\n\nBuffer.prototype.writeBigUInt64LE = defineBigIntMethod(function writeBigUInt64LE (value, offset = 0) {\n return wrtBigUInt64LE(this, value, offset, BigInt(0), BigInt('0xffffffffffffffff'))\n})\n\nBuffer.prototype.writeBigUInt64BE = defineBigIntMethod(function writeBigUInt64BE (value, offset = 0) {\n return wrtBigUInt64BE(this, value, offset, BigInt(0), BigInt('0xffffffffffffffff'))\n})\n\nBuffer.prototype.writeIntLE = function writeIntLE (value, offset, byteLength, noAssert) {\n value = +value\n offset = offset >>> 0\n if (!noAssert) {\n const limit = Math.pow(2, (8 * byteLength) - 1)\n\n checkInt(this, value, offset, byteLength, limit - 1, -limit)\n }\n\n let i = 0\n let mul = 1\n let sub = 0\n this[offset] = value & 0xFF\n while (++i < byteLength && (mul *= 0x100)) {\n if (value < 0 && sub === 0 && this[offset + i - 1] !== 0) {\n sub = 1\n }\n this[offset + i] = ((value / mul) >> 0) - sub & 0xFF\n }\n\n return offset + byteLength\n}\n\nBuffer.prototype.writeIntBE = function writeIntBE (value, offset, byteLength, noAssert) {\n value = +value\n offset = offset >>> 0\n if (!noAssert) {\n const limit = Math.pow(2, (8 * byteLength) - 1)\n\n checkInt(this, value, offset, byteLength, limit - 1, -limit)\n }\n\n let i = byteLength - 1\n let mul = 1\n let sub = 0\n this[offset + i] = value & 0xFF\n while (--i >= 0 && (mul *= 0x100)) {\n if (value < 0 && sub === 0 && this[offset + i + 1] !== 0) {\n sub = 1\n }\n this[offset + i] = ((value / mul) >> 0) - sub & 0xFF\n }\n\n return offset + byteLength\n}\n\nBuffer.prototype.writeInt8 = function writeInt8 (value, offset, noAssert) {\n value = +value\n offset = offset >>> 0\n if (!noAssert) checkInt(this, value, offset, 1, 0x7f, -0x80)\n if (value < 0) value = 0xff + value + 1\n this[offset] = (value & 0xff)\n return offset + 1\n}\n\nBuffer.prototype.writeInt16LE = function writeInt16LE (value, offset, noAssert) {\n value = +value\n offset = offset >>> 0\n if (!noAssert) checkInt(this, value, offset, 2, 0x7fff, -0x8000)\n this[offset] = (value & 0xff)\n this[offset + 1] = (value >>> 8)\n return offset + 2\n}\n\nBuffer.prototype.writeInt16BE = function writeInt16BE (value, offset, noAssert) {\n value = +value\n offset = offset >>> 0\n if (!noAssert) checkInt(this, value, offset, 2, 0x7fff, -0x8000)\n this[offset] = (value >>> 8)\n this[offset + 1] = (value & 0xff)\n return offset + 2\n}\n\nBuffer.prototype.writeInt32LE = function writeInt32LE (value, offset, noAssert) {\n value = +value\n offset = offset >>> 0\n if (!noAssert) checkInt(this, value, offset, 4, 0x7fffffff, -0x80000000)\n this[offset] = (value & 0xff)\n this[offset + 1] = (value >>> 8)\n this[offset + 2] = (value >>> 16)\n this[offset + 3] = (value >>> 24)\n return offset + 4\n}\n\nBuffer.prototype.writeInt32BE = function writeInt32BE (value, offset, noAssert) {\n value = +value\n offset = offset >>> 0\n if (!noAssert) checkInt(this, value, offset, 4, 0x7fffffff, -0x80000000)\n if (value < 0) value = 0xffffffff + value + 1\n this[offset] = (value >>> 24)\n this[offset + 1] = (value >>> 16)\n this[offset + 2] = (value >>> 8)\n this[offset + 3] = (value & 0xff)\n return offset + 4\n}\n\nBuffer.prototype.writeBigInt64LE = defineBigIntMethod(function writeBigInt64LE (value, offset = 0) {\n return wrtBigUInt64LE(this, value, offset, -BigInt('0x8000000000000000'), BigInt('0x7fffffffffffffff'))\n})\n\nBuffer.prototype.writeBigInt64BE = defineBigIntMethod(function writeBigInt64BE (value, offset = 0) {\n return wrtBigUInt64BE(this, value, offset, -BigInt('0x8000000000000000'), BigInt('0x7fffffffffffffff'))\n})\n\nfunction checkIEEE754 (buf, value, offset, ext, max, min) {\n if (offset + ext > buf.length) throw new RangeError('Index out of range')\n if (offset < 0) throw new RangeError('Index out of range')\n}\n\nfunction writeFloat (buf, value, offset, littleEndian, noAssert) {\n value = +value\n offset = offset >>> 0\n if (!noAssert) {\n checkIEEE754(buf, value, offset, 4, 3.4028234663852886e+38, -3.4028234663852886e+38)\n }\n ieee754.write(buf, value, offset, littleEndian, 23, 4)\n return offset + 4\n}\n\nBuffer.prototype.writeFloatLE = function writeFloatLE (value, offset, noAssert) {\n return writeFloat(this, value, offset, true, noAssert)\n}\n\nBuffer.prototype.writeFloatBE = function writeFloatBE (value, offset, noAssert) {\n return writeFloat(this, value, offset, false, noAssert)\n}\n\nfunction writeDouble (buf, value, offset, littleEndian, noAssert) {\n value = +value\n offset = offset >>> 0\n if (!noAssert) {\n checkIEEE754(buf, value, offset, 8, 1.7976931348623157E+308, -1.7976931348623157E+308)\n }\n ieee754.write(buf, value, offset, littleEndian, 52, 8)\n return offset + 8\n}\n\nBuffer.prototype.writeDoubleLE = function writeDoubleLE (value, offset, noAssert) {\n return writeDouble(this, value, offset, true, noAssert)\n}\n\nBuffer.prototype.writeDoubleBE = function writeDoubleBE (value, offset, noAssert) {\n return writeDouble(this, value, offset, false, noAssert)\n}\n\n// copy(targetBuffer, targetStart=0, sourceStart=0, sourceEnd=buffer.length)\nBuffer.prototype.copy = function copy (target, targetStart, start, end) {\n if (!Buffer.isBuffer(target)) throw new TypeError('argument should be a Buffer')\n if (!start) start = 0\n if (!end && end !== 0) end = this.length\n if (targetStart >= target.length) targetStart = target.length\n if (!targetStart) targetStart = 0\n if (end > 0 && end < start) end = start\n\n // Copy 0 bytes; we're done\n if (end === start) return 0\n if (target.length === 0 || this.length === 0) return 0\n\n // Fatal error conditions\n if (targetStart < 0) {\n throw new RangeError('targetStart out of bounds')\n }\n if (start < 0 || start >= this.length) throw new RangeError('Index out of range')\n if (end < 0) throw new RangeError('sourceEnd out of bounds')\n\n // Are we oob?\n if (end > this.length) end = this.length\n if (target.length - targetStart < end - start) {\n end = target.length - targetStart + start\n }\n\n const len = end - start\n\n if (this === target && typeof Uint8Array.prototype.copyWithin === 'function') {\n // Use built-in when available, missing from IE11\n this.copyWithin(targetStart, start, end)\n } else {\n Uint8Array.prototype.set.call(\n target,\n this.subarray(start, end),\n targetStart\n )\n }\n\n return len\n}\n\n// Usage:\n// buffer.fill(number[, offset[, end]])\n// buffer.fill(buffer[, offset[, end]])\n// buffer.fill(string[, offset[, end]][, encoding])\nBuffer.prototype.fill = function fill (val, start, end, encoding) {\n // Handle string cases:\n if (typeof val === 'string') {\n if (typeof start === 'string') {\n encoding = start\n start = 0\n end = this.length\n } else if (typeof end === 'string') {\n encoding = end\n end = this.length\n }\n if (encoding !== undefined && typeof encoding !== 'string') {\n throw new TypeError('encoding must be a string')\n }\n if (typeof encoding === 'string' && !Buffer.isEncoding(encoding)) {\n throw new TypeError('Unknown encoding: ' + encoding)\n }\n if (val.length === 1) {\n const code = val.charCodeAt(0)\n if ((encoding === 'utf8' && code < 128) ||\n encoding === 'latin1') {\n // Fast path: If `val` fits into a single byte, use that numeric value.\n val = code\n }\n }\n } else if (typeof val === 'number') {\n val = val & 255\n } else if (typeof val === 'boolean') {\n val = Number(val)\n }\n\n // Invalid ranges are not set to a default, so can range check early.\n if (start < 0 || this.length < start || this.length < end) {\n throw new RangeError('Out of range index')\n }\n\n if (end <= start) {\n return this\n }\n\n start = start >>> 0\n end = end === undefined ? this.length : end >>> 0\n\n if (!val) val = 0\n\n let i\n if (typeof val === 'number') {\n for (i = start; i < end; ++i) {\n this[i] = val\n }\n } else {\n const bytes = Buffer.isBuffer(val)\n ? val\n : Buffer.from(val, encoding)\n const len = bytes.length\n if (len === 0) {\n throw new TypeError('The value \"' + val +\n '\" is invalid for argument \"value\"')\n }\n for (i = 0; i < end - start; ++i) {\n this[i + start] = bytes[i % len]\n }\n }\n\n return this\n}\n\n// CUSTOM ERRORS\n// =============\n\n// Simplified versions from Node, changed for Buffer-only usage\nconst errors = {}\nfunction E (sym, getMessage, Base) {\n errors[sym] = class NodeError extends Base {\n constructor () {\n super()\n\n Object.defineProperty(this, 'message', {\n value: getMessage.apply(this, arguments),\n writable: true,\n configurable: true\n })\n\n // Add the error code to the name to include it in the stack trace.\n this.name = `${this.name} [${sym}]`\n // Access the stack to generate the error message including the error code\n // from the name.\n this.stack // eslint-disable-line no-unused-expressions\n // Reset the name to the actual name.\n delete this.name\n }\n\n get code () {\n return sym\n }\n\n set code (value) {\n Object.defineProperty(this, 'code', {\n configurable: true,\n enumerable: true,\n value,\n writable: true\n })\n }\n\n toString () {\n return `${this.name} [${sym}]: ${this.message}`\n }\n }\n}\n\nE('ERR_BUFFER_OUT_OF_BOUNDS',\n function (name) {\n if (name) {\n return `${name} is outside of buffer bounds`\n }\n\n return 'Attempt to access memory outside buffer bounds'\n }, RangeError)\nE('ERR_INVALID_ARG_TYPE',\n function (name, actual) {\n return `The \"${name}\" argument must be of type number. Received type ${typeof actual}`\n }, TypeError)\nE('ERR_OUT_OF_RANGE',\n function (str, range, input) {\n let msg = `The value of \"${str}\" is out of range.`\n let received = input\n if (Number.isInteger(input) && Math.abs(input) > 2 ** 32) {\n received = addNumericalSeparator(String(input))\n } else if (typeof input === 'bigint') {\n received = String(input)\n if (input > BigInt(2) ** BigInt(32) || input < -(BigInt(2) ** BigInt(32))) {\n received = addNumericalSeparator(received)\n }\n received += 'n'\n }\n msg += ` It must be ${range}. Received ${received}`\n return msg\n }, RangeError)\n\nfunction addNumericalSeparator (val) {\n let res = ''\n let i = val.length\n const start = val[0] === '-' ? 1 : 0\n for (; i >= start + 4; i -= 3) {\n res = `_${val.slice(i - 3, i)}${res}`\n }\n return `${val.slice(0, i)}${res}`\n}\n\n// CHECK FUNCTIONS\n// ===============\n\nfunction checkBounds (buf, offset, byteLength) {\n validateNumber(offset, 'offset')\n if (buf[offset] === undefined || buf[offset + byteLength] === undefined) {\n boundsError(offset, buf.length - (byteLength + 1))\n }\n}\n\nfunction checkIntBI (value, min, max, buf, offset, byteLength) {\n if (value > max || value < min) {\n const n = typeof min === 'bigint' ? 'n' : ''\n let range\n if (byteLength > 3) {\n if (min === 0 || min === BigInt(0)) {\n range = `>= 0${n} and < 2${n} ** ${(byteLength + 1) * 8}${n}`\n } else {\n range = `>= -(2${n} ** ${(byteLength + 1) * 8 - 1}${n}) and < 2 ** ` +\n `${(byteLength + 1) * 8 - 1}${n}`\n }\n } else {\n range = `>= ${min}${n} and <= ${max}${n}`\n }\n throw new errors.ERR_OUT_OF_RANGE('value', range, value)\n }\n checkBounds(buf, offset, byteLength)\n}\n\nfunction validateNumber (value, name) {\n if (typeof value !== 'number') {\n throw new errors.ERR_INVALID_ARG_TYPE(name, 'number', value)\n }\n}\n\nfunction boundsError (value, length, type) {\n if (Math.floor(value) !== value) {\n validateNumber(value, type)\n throw new errors.ERR_OUT_OF_RANGE(type || 'offset', 'an integer', value)\n }\n\n if (length < 0) {\n throw new errors.ERR_BUFFER_OUT_OF_BOUNDS()\n }\n\n throw new errors.ERR_OUT_OF_RANGE(type || 'offset',\n `>= ${type ? 1 : 0} and <= ${length}`,\n value)\n}\n\n// HELPER FUNCTIONS\n// ================\n\nconst INVALID_BASE64_RE = /[^+/0-9A-Za-z-_]/g\n\nfunction base64clean (str) {\n // Node takes equal signs as end of the Base64 encoding\n str = str.split('=')[0]\n // Node strips out invalid characters like \\n and \\t from the string, base64-js does not\n str = str.trim().replace(INVALID_BASE64_RE, '')\n // Node converts strings with length < 2 to ''\n if (str.length < 2) return ''\n // Node allows for non-padded base64 strings (missing trailing ===), base64-js does not\n while (str.length % 4 !== 0) {\n str = str + '='\n }\n return str\n}\n\nfunction utf8ToBytes (string, units) {\n units = units || Infinity\n let codePoint\n const length = string.length\n let leadSurrogate = null\n const bytes = []\n\n for (let i = 0; i < length; ++i) {\n codePoint = string.charCodeAt(i)\n\n // is surrogate component\n if (codePoint > 0xD7FF && codePoint < 0xE000) {\n // last char was a lead\n if (!leadSurrogate) {\n // no lead yet\n if (codePoint > 0xDBFF) {\n // unexpected trail\n if ((units -= 3) > -1) bytes.push(0xEF, 0xBF, 0xBD)\n continue\n } else if (i + 1 === length) {\n // unpaired lead\n if ((units -= 3) > -1) bytes.push(0xEF, 0xBF, 0xBD)\n continue\n }\n\n // valid lead\n leadSurrogate = codePoint\n\n continue\n }\n\n // 2 leads in a row\n if (codePoint < 0xDC00) {\n if ((units -= 3) > -1) bytes.push(0xEF, 0xBF, 0xBD)\n leadSurrogate = codePoint\n continue\n }\n\n // valid surrogate pair\n codePoint = (leadSurrogate - 0xD800 << 10 | codePoint - 0xDC00) + 0x10000\n } else if (leadSurrogate) {\n // valid bmp char, but last char was a lead\n if ((units -= 3) > -1) bytes.push(0xEF, 0xBF, 0xBD)\n }\n\n leadSurrogate = null\n\n // encode utf8\n if (codePoint < 0x80) {\n if ((units -= 1) < 0) break\n bytes.push(codePoint)\n } else if (codePoint < 0x800) {\n if ((units -= 2) < 0) break\n bytes.push(\n codePoint >> 0x6 | 0xC0,\n codePoint & 0x3F | 0x80\n )\n } else if (codePoint < 0x10000) {\n if ((units -= 3) < 0) break\n bytes.push(\n codePoint >> 0xC | 0xE0,\n codePoint >> 0x6 & 0x3F | 0x80,\n codePoint & 0x3F | 0x80\n )\n } else if (codePoint < 0x110000) {\n if ((units -= 4) < 0) break\n bytes.push(\n codePoint >> 0x12 | 0xF0,\n codePoint >> 0xC & 0x3F | 0x80,\n codePoint >> 0x6 & 0x3F | 0x80,\n codePoint & 0x3F | 0x80\n )\n } else {\n throw new Error('Invalid code point')\n }\n }\n\n return bytes\n}\n\nfunction asciiToBytes (str) {\n const byteArray = []\n for (let i = 0; i < str.length; ++i) {\n // Node's code seems to be doing this and not & 0x7F..\n byteArray.push(str.charCodeAt(i) & 0xFF)\n }\n return byteArray\n}\n\nfunction utf16leToBytes (str, units) {\n let c, hi, lo\n const byteArray = []\n for (let i = 0; i < str.length; ++i) {\n if ((units -= 2) < 0) break\n\n c = str.charCodeAt(i)\n hi = c >> 8\n lo = c % 256\n byteArray.push(lo)\n byteArray.push(hi)\n }\n\n return byteArray\n}\n\nfunction base64ToBytes (str) {\n return base64.toByteArray(base64clean(str))\n}\n\nfunction blitBuffer (src, dst, offset, length) {\n let i\n for (i = 0; i < length; ++i) {\n if ((i + offset >= dst.length) || (i >= src.length)) break\n dst[i + offset] = src[i]\n }\n return i\n}\n\n// ArrayBuffer or Uint8Array objects from other contexts (i.e. iframes) do not pass\n// the `instanceof` check but they should be treated as of that type.\n// See: https://github.com/feross/buffer/issues/166\nfunction isInstance (obj, type) {\n return obj instanceof type ||\n (obj != null && obj.constructor != null && obj.constructor.name != null &&\n obj.constructor.name === type.name)\n}\nfunction numberIsNaN (obj) {\n // For IE11 support\n return obj !== obj // eslint-disable-line no-self-compare\n}\n\n// Create lookup table for `toString('hex')`\n// See: https://github.com/feross/buffer/issues/219\nconst hexSliceLookupTable = (function () {\n const alphabet = '0123456789abcdef'\n const table = new Array(256)\n for (let i = 0; i < 16; ++i) {\n const i16 = i * 16\n for (let j = 0; j < 16; ++j) {\n table[i16 + j] = alphabet[i] + alphabet[j]\n }\n }\n return table\n})()\n\n// Return not function with Error if BigInt not supported\nfunction defineBigIntMethod (fn) {\n return typeof BigInt === 'undefined' ? BufferBigIntNotDefined : fn\n}\n\nfunction BufferBigIntNotDefined () {\n throw new Error('BigInt not supported')\n}\n", "import { Buffer } from 'buffer';\nimport { FIT } from './fit.js';\nimport { getFitMessage, getFitMessageBaseType } from './messages.js';\nconst CompressedLocalMsgNumMask = 0x60;\nconst CompressedHeaderMask = 0x80;\nconst GarminTimeOffset = 631065600000;\nlet monitoring_timestamp = 0;\nexport function addEndian(littleEndian, bytes) {\n let result = 0;\n if (!littleEndian)\n bytes.reverse();\n for (let i = 0; i < bytes.length; i++) {\n result += (bytes[i] << (i << 3)) >>> 0;\n }\n return result;\n}\nfunction readData(blob, fDef, startIndex, options) {\n if (fDef.type === 'uint8_array') {\n const array8 = [];\n for (let i = 0; i < fDef.size; i++) {\n array8.push(blob[startIndex + i]);\n }\n return array8;\n }\n if (fDef.endianAbility) {\n const temp = [];\n for (let i = 0; i < fDef.size; i++) {\n temp.push(blob[startIndex + i]);\n }\n const { buffer } = new Uint8Array(temp);\n const dataView = new DataView(buffer);\n try {\n switch (fDef.type) {\n case 'sint16':\n return dataView.getInt16(0, fDef.littleEndian);\n case 'uint16':\n case 'uint16z':\n return dataView.getUint16(0, fDef.littleEndian);\n case 'sint32':\n return dataView.getInt32(0, fDef.littleEndian);\n case 'uint32':\n case 'uint32z':\n return dataView.getUint32(0, fDef.littleEndian);\n case 'float32':\n return dataView.getFloat32(0, fDef.littleEndian);\n case 'float64':\n return dataView.getFloat64(0, fDef.littleEndian);\n case 'uint32_array': {\n const array32 = [];\n for (let i = 0; i < fDef.size; i += 4) {\n array32.push(dataView.getUint32(i, fDef.littleEndian));\n }\n return array32;\n }\n case 'uint16_array': {\n const array16 = [];\n for (let i = 0; i < fDef.size; i += 2) {\n array16.push(dataView.getUint16(i, fDef.littleEndian));\n }\n return array16;\n }\n }\n }\n catch (e) {\n if (!options.force) {\n throw e;\n }\n }\n return addEndian(fDef.littleEndian, temp);\n }\n if (fDef.type === 'string') {\n const temp = [];\n for (let i = 0; i < fDef.size; i++) {\n if (blob[startIndex + i]) {\n temp.push(blob[startIndex + i]);\n }\n }\n return Buffer.from(temp).toString('utf-8');\n }\n if (fDef.type === 'byte_array') {\n const temp = [];\n for (let i = 0; i < fDef.size; i++) {\n temp.push(blob[startIndex + i]);\n }\n return temp;\n }\n if (fDef.type === 'sint8') {\n const val = blob[startIndex];\n return val > 127 ? val - 256 : val;\n }\n return blob[startIndex];\n}\nfunction formatByType(data, type, scale, offset) {\n switch (type) {\n case 'date_time':\n case 'local_date_time':\n return new Date(data * 1000 + GarminTimeOffset);\n case 'sint32':\n return data * FIT.scConst;\n case 'uint8':\n case 'sint16':\n case 'uint32':\n case 'uint16':\n return scale ? data / scale + offset : data;\n case 'uint32_array':\n case 'uint16_array':\n case 'uint8_array':\n if (Array.isArray(data)) {\n const baseType = type.replace('_array', '');\n return data.map((dataItem) => {\n if (isInvalidValue(dataItem, baseType)) {\n return null;\n }\n return scale ? dataItem / scale + offset : dataItem;\n });\n }\n return scale ? data / scale + offset : data;\n default:\n {\n if (!FIT.types[type]) {\n return data;\n }\n // Quick check for a mask\n const values = [];\n for (const key in FIT.types[type]) {\n if (key in FIT.types[type]) {\n values.push(String(FIT.types[type][key]));\n }\n }\n if (!values.includes('mask')) {\n const typeMap = FIT.types[type];\n const mapped = typeMap[String(data)];\n return mapped === undefined ? data : mapped;\n }\n const dataItem = {};\n for (const key in FIT.types[type]) {\n if (key in FIT.types[type]) {\n if (FIT.types[type][key] === 'mask') {\n dataItem.value = data & Number(key);\n }\n else {\n dataItem[FIT.types[type][key]] = !!((data & Number(key)) >> 7); // Not sure if we need the >> 7 and casting to boolean but from all the masked props of fields so far this seems to be the case\n }\n }\n }\n return dataItem;\n }\n }\n}\nfunction isInvalidValue(data, type) {\n switch (type) {\n case 'enum':\n return data === 0xFF;\n case 'sint8':\n return data === 0x7F;\n case 'uint8':\n return data === 0xFF;\n case 'sint16':\n return data === 0x7FFF;\n case 'uint16':\n return data === 0xFFFF;\n case 'sint32':\n return data === 0x7FFFFFFF;\n case 'uint32':\n return data === 0xFFFFFFFF;\n case 'string':\n return data === 0x00;\n case 'float32':\n return data === 0xFFFFFFFF;\n case 'float64':\n // eslint-disable-next-line no-loss-of-precision\n return data === 0xFFFFFFFFFFFFFFFF;\n case 'uint8z':\n return data === 0x00;\n case 'uint16z':\n return data === 0x0000;\n case 'uint32z':\n return data === 0x000000;\n case 'byte':\n return data === 0xFF;\n case 'sint64':\n // eslint-disable-next-line no-loss-of-precision\n return data === 0x7FFFFFFFFFFFFFFF;\n case 'uint64':\n // eslint-disable-next-line no-loss-of-precision\n return data === 0xFFFFFFFFFFFFFFFF;\n case 'uint64z':\n return data === 0x0000000000000000;\n default:\n return false;\n }\n}\nfunction convertTo(data, unitsList, unitName) {\n const options = FIT.options[unitsList];\n const unit = options[unitName];\n return unit ? data * unit.multiplier + unit.offset : data;\n}\nfunction applyOptions(data, field, options, fields) {\n switch (field) {\n case 'device_type': {\n const isLocal = fields.source_type === 'local' || fields.source_type === 5;\n const isBLE = fields.source_type === 'bluetooth_low_energy' || fields.source_type === 3 || fields.source_type === 'bluetooth' || fields.source_type === 2;\n const isANT = fields.source_type === 'antplus' || fields.source_type === 1 || fields.source_type === 'ant' || fields.source_type === 0;\n if (isLocal) {\n return FIT.types.local_device_type[data] || data;\n }\n if (isBLE) {\n return FIT.types.ble_device_type[data] || data;\n }\n if (isANT) {\n return FIT.types.antplus_device_type[data] || data;\n }\n return data;\n }\n case 'speed':\n case 'enhanced_speed':\n case 'vertical_speed':\n case 'avg_speed':\n case 'max_speed':\n case 'speed_1s':\n case 'ball_speed':\n case 'enhanced_avg_speed':\n case 'enhanced_max_speed':\n case 'avg_pos_vertical_speed':\n case 'max_pos_vertical_speed':\n case 'avg_neg_vertical_speed':\n case 'max_neg_vertical_speed':\n return convertTo(data, 'speedUnits', options.speedUnit);\n case 'distance':\n case 'total_distance':\n case 'enhanced_avg_altitude':\n case 'enhanced_min_altitude':\n case 'enhanced_max_altitude':\n case 'enhanced_altitude':\n case 'height':\n case 'odometer':\n case 'avg_stroke_distance':\n case 'min_altitude':\n case 'avg_altitude':\n case 'max_altitude':\n case 'total_ascent':\n case 'total_descent':\n case 'altitude':\n case 'cycle_length':\n case 'auto_wheelsize':\n case 'custom_wheelsize':\n case 'gps_accuracy':\n return convertTo(data, 'lengthUnits', options.lengthUnit);\n case 'temperature':\n case 'avg_temperature':\n case 'max_temperature':\n return convertTo(data, 'temperatureUnits', options.temperatureUnit);\n case 'pressure':\n case 'start_pressure':\n case 'end_pressure':\n return convertTo(data, 'pressureUnits', options.pressureUnit);\n case 'ant_id': {\n const n1 = (data >>> 28) & 0xF;\n const n2 = (data >>> 24) & 0xF;\n const n3 = (data >>> 16) & 0xFF;\n const n4 = data & 0xFFFF;\n return `${n1.toString(16).toUpperCase()}-${n2.toString(16).toUpperCase()}-${n3.toString(16).toUpperCase().padStart(2, '0')}-${n4.toString(16).toUpperCase().padStart(4, '0')}`;\n }\n default:\n return data;\n }\n}\nexport function readRecord(blob, messageTypes, developerFields, startIndex, options, startDate, pausedTime) {\n const recordHeader = blob[startIndex];\n let localMessageType = recordHeader & 15;\n if ((recordHeader & CompressedHeaderMask) === CompressedHeaderMask) {\n // compressed timestamp\n localMessageType = (recordHeader & CompressedLocalMsgNumMask) >> 5;\n }\n else if ((recordHeader & 64) === 64) {\n // is definition message\n // startIndex + 1 is reserved\n const hasDeveloperData = (recordHeader & 32) === 32;\n const lEnd = blob[startIndex + 2] === 0;\n const numberOfFields = blob[startIndex + 5];\n const numberOfDeveloperDataFields = hasDeveloperData\n ? blob[startIndex + 5 + numberOfFields * 3 + 1]\n : 0;\n const mTypeDef = {\n littleEndian: lEnd,\n globalMessageNumber: addEndian(lEnd, [\n blob[startIndex + 3],\n blob[startIndex + 4],\n ]),\n numberOfFields: numberOfFields + numberOfDeveloperDataFields,\n fieldDefs: [],\n };\n const message = getFitMessage(mTypeDef.globalMessageNumber);\n for (let i = 0; i < numberOfFields; i++) {\n const fDefIndex = startIndex + 6 + i * 3;\n const baseType = blob[fDefIndex + 2];\n const { field, type } = message.getAttributes(blob[fDefIndex]);\n const fDef = {\n type,\n fDefNo: blob[fDefIndex],\n size: blob[fDefIndex + 1],\n endianAbility: (baseType & 128) === 128,\n littleEndian: lEnd,\n baseTypeNo: baseType & 15,\n name: field,\n dataType: getFitMessageBaseType(baseType & 15),\n };\n mTypeDef.fieldDefs.push(fDef);\n }\n // numberOfDeveloperDataFields = 0 so it wont crash here and wont loop\n for (let i = 0; i < numberOfDeveloperDataFields; i++) {\n // If we fail to parse then try catch\n try {\n const fDefIndex = startIndex + 6 + numberOfFields * 3 + 1 + i * 3;\n const fieldNum = blob[fDefIndex];\n const size = blob[fDefIndex + 1];\n const devDataIndex = blob[fDefIndex + 2];\n const devDef = developerFields[devDataIndex][fieldNum];\n const baseType = devDef.fit_base_type_id;\n const fDef = {\n type: FIT.types.fit_base_type[baseType],\n fDefNo: fieldNum,\n size,\n endianAbility: (baseType & 128) === 128,\n littleEndian: lEnd,\n baseTypeNo: baseType & 15,\n name: devDef.field_name,\n dataType: getFitMessageBaseType(baseType & 15),\n scale: devDef.scale || 1,\n offset: devDef.offset || 0,\n developerDataIndex: devDataIndex,\n isDeveloperField: true,\n };\n mTypeDef.fieldDefs.push(fDef);\n }\n catch (e) {\n if (options.force) {\n continue;\n }\n throw e;\n }\n }\n messageTypes[localMessageType] = mTypeDef;\n const nextIndex = startIndex + 6 + mTypeDef.numberOfFields * 3;\n const nextIndexWithDeveloperData = nextIndex + 1;\n return {\n messageType: 'definition',\n nextIndex: hasDeveloperData ? nextIndexWithDeveloperData : nextIndex,\n };\n }\n const messageType = messageTypes[localMessageType] || messageTypes[0];\n // TODO: handle compressed header ((recordHeader & 128) == 128)\n // uncompressed header\n let messageSize = 0;\n let readDataFromIndex = startIndex + 1;\n const fields = {};\n const message = getFitMessage(messageType.globalMessageNumber);\n const rawFields = [];\n for (let i = 0; i < messageType.fieldDefs.length; i++) {\n const fDef = messageType.fieldDefs[i];\n const data = readData(blob, fDef, readDataFromIndex, options);\n if (!isInvalidValue(data, fDef.type)) {\n rawFields.push({ fDef, data });\n }\n readDataFromIndex += fDef.size;\n messageSize += fDef.size;\n }\n for (const { fDef, data } of rawFields) {\n const { field } = fDef.isDeveloperField ? { field: fDef.name } : message.getAttributes(fDef.fDefNo);\n if (field !== 'unknown' && field !== '' && field !== undefined) {\n fields[field] = data;\n }\n }\n for (const { fDef, data } of rawFields) {\n if (fDef.isDeveloperField) {\n const field = fDef.name;\n const { type } = fDef;\n const { scale } = fDef;\n const { offset } = fDef;\n fields[fDef.name] = applyOptions(formatByType(data, type, scale, offset), field, options, fields);\n }\n else {\n const { field, type, scale, offset } = message.getAttributes(fDef.fDefNo);\n if (field !== 'unknown' && field !== '' && field !== undefined) {\n fields[field] = applyOptions(formatByType(data, type, scale, offset), field, options, fields);\n }\n }\n if (message.name === 'record' && options.elapsedRecordField) {\n fields.elapsed_time = (fields.timestamp - (startDate || 0)) / 1000;\n fields.timer_time = fields.elapsed_time - pausedTime;\n }\n }\n if (message.name === 'field_description') {\n developerFields[fields.developer_data_index]\n = developerFields[fields.developer_data_index] || [];\n developerFields[fields.developer_data_index][fields.field_definition_number] = fields;\n }\n if (message.name === 'monitoring') {\n // TODO weirdly uses global variables?\n // we need to keep the raw timestamp value so we can calculate subsequent timestamp16 fields\n if (fields.timestamp) {\n monitoring_timestamp = fields.timestamp;\n fields.timestamp = new Date(fields.timestamp * 1000 + GarminTimeOffset);\n }\n if (fields.timestamp16 && !fields.timestamp) {\n monitoring_timestamp\n += (fields.timestamp16 - (monitoring_timestamp & 0xFFFF)) & 0xFFFF;\n // fields.timestamp = monitoring_timestamp;\n fields.timestamp = new Date(monitoring_timestamp * 1000 + GarminTimeOffset);\n }\n }\n return {\n messageType: message.name,\n nextIndex: startIndex + messageSize + 1,\n message: fields,\n };\n}\nexport function getArrayBuffer(buffer) {\n if (buffer instanceof ArrayBuffer) {\n return buffer;\n }\n const ab = new ArrayBuffer(buffer.length);\n const view = new Uint8Array(ab);\n for (let i = 0; i < buffer.length; ++i) {\n view[i] = buffer[i];\n }\n return ab;\n}\nexport function calculateCRC(blob, start, end) {\n const crcTable = [\n 0x0000,\n 0xCC01,\n 0xD801,\n 0x1400,\n 0xF001,\n 0x3C00,\n 0x2800,\n 0xE401,\n 0xA001,\n 0x6C00,\n 0x7800,\n 0xB401,\n 0x5000,\n 0x9C01,\n 0x8801,\n 0x4400,\n ];\n let crc = 0;\n for (let i = start; i < end; i++) {\n const byteVal = blob[i];\n let tmp = crcTable[crc & 0xF];\n crc = (crc >> 4) & 0x0FFF;\n crc = crc ^ tmp ^ crcTable[byteVal & 0xF];\n tmp = crcTable[crc & 0xF];\n crc = (crc >> 4) & 0x0FFF;\n crc = crc ^ tmp ^ crcTable[(byteVal >> 4) & 0xF];\n }\n return crc;\n}\n", "const metersInOneKilometer = 1000;\nconst secondsInOneHour = 3600;\n// according to https://en.wikipedia.org/wiki/Mile\nconst metersInOneMile = 1609.344;\nconst centiBarsInOneBar = 100;\nconst psiInOneBar = 14.5037738;\n// TODO we dont have comprehensive output types bc its a bit cumbersome to maintain typescript types and this giant mapping object\n// which contains all the types and values\n// i would wish for some kind of inference mechanism here but for now we leave it as any\n// maybe zod could help here\nexport const FIT = {\n scConst: 180 / Math.pow(2, 31),\n options: {\n speedUnits: {\n // native speed unit: meters per second [m/s]\n 'm/s': {\n multiplier: 1,\n offset: 0,\n },\n // miles per hour [mph]\n 'mph': {\n multiplier: secondsInOneHour / metersInOneMile,\n offset: 0,\n },\n // kilometers per hour [km/h]\n 'km/h': {\n multiplier: secondsInOneHour / metersInOneKilometer,\n offset: 0,\n },\n },\n lengthUnits: {\n // native length unit: meters [m]\n m: {\n multiplier: 1,\n offset: 0,\n },\n // (international) mile [mi]\n mi: {\n multiplier: 1 / metersInOneMile,\n offset: 0,\n },\n // kilometer [km]\n km: {\n multiplier: 1 / metersInOneKilometer,\n offset: 0,\n },\n },\n temperatureUnits: {\n // native temperature unit: degree Celsius [\u00B0C]\n '\u00B0C': {\n multiplier: 1,\n offset: 0,\n },\n // kelvin [K]\n 'kelvin': {\n multiplier: 1,\n offset: -273.15,\n },\n // degree fahrenheit [\u00B0F]\n 'fahrenheit': {\n multiplier: 9 / 5,\n offset: 32,\n },\n },\n pressureUnits: {\n cbar: {\n multiplier: 1,\n offset: 0,\n },\n bar: {\n multiplier: 1 / centiBarsInOneBar,\n offset: 0,\n },\n psi: {\n multiplier: (1 / centiBarsInOneBar) * psiInOneBar,\n offset: 0,\n },\n },\n },\n messages: {\n 0: {\n name: 'file_id',\n 0: { field: 'type', type: 'file', scale: null, offset: 0, units: '' },\n 1: {\n field: 'manufacturer',\n type: 'manufacturer',\n scale: null,\n offset: 0,\n units: '',\n },\n 2: {\n field: 'product',\n type: 'uint16',\n scale: null,\n offset: 0,\n units: '',\n },\n 3: {\n field: 'serial_number',\n type: 'uint32z',\n scale: null,\n offset: 0,\n units: '',\n },\n 4: {\n field: 'time_created',\n type: 'date_time',\n scale: null,\n offset: 0,\n units: '',\n },\n 5: { field: 'number', type: 'uint16', scale: null, offset: 0, units: '' },\n 8: {\n field: 'product_name',\n type: 'string',\n scale: null,\n offset: 0,\n units: '',\n },\n },\n 1: {\n name: 'capabilities',\n 0: {\n field: 'languages',\n type: 'uint8z',\n scale: null,\n offset: 0,\n units: '',\n },\n 1: {\n field: 'sports',\n type: 'sport_bits_0',\n scale: null,\n offset: 0,\n units: '',\n },\n 21: {\n field: 'workouts_supported',\n type: 'workout_capabilities',\n scale: null,\n offset: 0,\n units: '',\n },\n 23: {\n field: 'connectivity_supported',\n type: 'connectivity_capabilities',\n scale: null,\n offset: 0,\n units: '',\n },\n },\n 2: {\n name: 'device_settings',\n 0: {\n field: 'active_time_zone',\n type: 'uint8',\n scale: null,\n offset: 0,\n units: '',\n },\n 1: {\n field: 'utc_offset',\n type: 'uint32',\n scale: null,\n offset: 0,\n units: '',\n },\n 2: {\n field: 'time_offset',\n type: 'uint32',\n scale: null,\n offset: 0,\n units: 's',\n },\n 5: {\n field: 'time_zone_offset',\n type: 'sint8',\n scale: 4,\n offset: 0,\n units: 'hr',\n },\n 55: {\n field: 'display_orientation',\n type: 'display_orientation',\n scale: null,\n offset: 0,\n units: '',\n },\n 56: {\n field: 'mounting_side',\n type: 'side',\n scale: null,\n offset: 0,\n units: '',\n },\n 94: {\n field: 'number_of_screens',\n type: 'uint8',\n scale: null,\n offset: 0,\n units: '',\n },\n 95: {\n field: 'smart_notification_display_orientation',\n type: 'display_orientation',\n scale: null,\n offset: 0,\n units: '',\n },\n },\n 3: {\n name: 'user_profile',\n 254: {\n field: 'message_index',\n type: 'message_index',\n scale: null,\n offset: 0,\n units: '',\n },\n 0: {\n field: 'friendly_name',\n type: 'string',\n scale: null,\n offset: 0,\n units: '',\n },\n 1: { field: 'gender', type: 'gender', scale: null, offset: 0, units: '' },\n 2: {\n field: 'age',\n type: 'uint8',\n scale: null,\n offset: 0,\n units: 'years',\n },\n 3: { field: 'height', type: 'uint8', scale: 100, offset: 0, units: 'm' },\n 4: { field: 'weight', type: 'uint16', scale: 10, offset: 0, units: 'kg' },\n 5: {\n field: 'language',\n type: 'language',\n scale: null,\n offset: 0,\n units: '',\n },\n 6: {\n field: 'elev_setting',\n type: 'display_measure',\n scale: null,\n offset: 0,\n units: '',\n },\n 7: {\n field: 'weight_setting',\n type: 'display_measure',\n scale: null,\n offset: 0,\n units: '',\n },\n 8: {\n field: 'resting_heart_rate',\n type: 'uint8',\n scale: null,\n offset: 0,\n units: 'bpm',\n },\n 9: {\n field: 'default_max_running_heart_rate',\n type: 'uint8',\n scale: null,\n offset: 0,\n units: 'bpm',\n },\n 10: {\n field: 'default_max_biking_heart_rate',\n type: 'uint8',\n scale: null,\n offset: 0,\n units: 'bpm',\n },\n 11: {\n field: 'default_max_heart_rate',\n type: 'uint8',\n scale: null,\n offset: 0,\n units: 'bpm',\n },\n 12: {\n field: 'hr_setting',\n type: 'display_heart',\n scale: null,\n offset: 0,\n units: '',\n },\n 13: {\n field: 'speed_setting',\n type: 'display_measure',\n scale: null,\n offset: 0,\n units: '',\n },\n 14: {\n field: 'dist_setting',\n type: 'display_measure',\n scale: null,\n offset: 0,\n units: '',\n },\n 16: {\n field: 'power_setting',\n type: 'display_power',\n scale: null,\n offset: 0,\n units: '',\n },\n 17: {\n field: 'activity_class',\n type: 'activity_class',\n scale: null,\n offset: 0,\n units: '',\n },\n 18: {\n field: 'position_setting',\n type: 'display_position',\n scale: null,\n offset: 0,\n units: '',\n },\n 21: {\n field: 'temperature_setting',\n type: 'display_measure',\n scale: null,\n offset: 0,\n units: '',\n },\n 22: {\n field: 'local_id',\n type: 'user_local_id',\n scale: null,\n offset: 0,\n units: '',\n },\n 23: {\n field: 'global_id',\n type: 'byte',\n scale: null,\n offset: 0,\n units: '',\n },\n 28: {\n field: 'wake_time',\n type: 'localtime_into_day',\n scale: null,\n offset: 0,\n units: '',\n },\n 29: {\n field: 'sleep_time',\n type: 'localtime_into_day',\n scale: null,\n offset: 0,\n units: '',\n },\n 30: {\n field: 'height_setting',\n type: 'display_measure',\n scale: null,\n offset: 0,\n units: '',\n },\n },\n 4: {\n name: 'hrm_profile',\n 254: {\n field: 'message_index',\n type: 'message_index',\n scale: null,\n offset: 0,\n units: '',\n },\n 0: { field: 'enabled', type: 'bool', scale: null, offset: 0, units: '' },\n 1: {\n field: 'hrm_ant_id',\n type: 'uint16z',\n scale: null,\n offset: 0,\n units: '',\n },\n 2: { field: 'log_hrv', type: 'bool', scale: null, offset: 0, units: '' },\n 3: {\n field: 'hrm_ant_id_trans_type',\n type: 'uint8z',\n scale: null,\n offset: 0,\n units: '',\n },\n },\n 5: {\n name: 'sdm_profile',\n 254: {\n field: 'message_index',\n type: 'message_index',\n scale: null,\n offset: 0,\n units: '',\n },\n 0: { field: 'enabled', type: 'bool', scale: null, offset: 0, units: '' },\n 1: {\n field: 'sdm_ant_id',\n type: 'uint16z',\n scale: null,\n offset: 0,\n units: '',\n },\n 2: {\n field: 'sdm_cal_factor',\n type: 'uint16',\n scale: 10,\n offset: 0,\n units: '%',\n },\n 3: {\n field: 'odometer',\n type: 'uint32',\n scale: 100,\n offset: 0,\n units: 'm',\n },\n 4: {\n field: 'speed_source',\n type: 'bool',\n scale: null,\n offset: 0,\n units: '',\n },\n 5: {\n field: 'sdm_ant_id_trans_type',\n type: 'uint8z',\n scale: null,\n offset: 0,\n units: '',\n },\n 7: {\n field: 'odometer_rollover',\n type: 'uint8',\n scale: null,\n offset: 0,\n units: '',\n },\n },\n 6: {\n name: 'bike_profile',\n 254: {\n field: 'message_index',\n type: 'message_index',\n scale: null,\n offset: 0,\n units: '',\n },\n 0: { field: 'name', type: 'string', scale: null, offset: 0, units: '' },\n 1: { field: 'sport', type: 'sport', scale: null, offset: 0, units: '' },\n 2: {\n field: 'sub_sport',\n type: 'sub_sport',\n scale: null,\n offset: 0,\n units: '',\n },\n 3: {\n field: 'odometer',\n type: 'uint32',\n scale: 100,\n offset: 0,\n units: 'm',\n },\n 4: {\n field: 'bike_spd_ant_id',\n type: 'uint16z',\n scale: null,\n offset: 0,\n units: '',\n },\n 5: {\n field: 'bike_cad_ant_id',\n type: 'uint16z',\n scale: null,\n offset: 0,\n units: '',\n },\n 6: {\n field: 'bike_spdcad_ant_id',\n type: 'uint16z',\n scale: null,\n offset: 0,\n units: '',\n },\n 7: {\n field: 'bike_power_ant_id',\n type: 'uint16z',\n scale: null,\n offset: 0,\n units: '',\n },\n 8: {\n field: 'custom_wheelsize',\n type: 'uint16',\n scale: 1000,\n offset: 0,\n units: 'm',\n },\n 9: {\n field: 'auto_wheelsize',\n type: 'uint16',\n scale: 1000,\n offset: 0,\n units: 'm',\n },\n 10: {\n field: 'bike_weight',\n type: 'uint16',\n scale: 10,\n offset: 0,\n units: 'kg',\n },\n 11: {\n field: 'power_cal_factor',\n type: 'uint16',\n scale: 10,\n offset: 0,\n units: '%',\n },\n 12: {\n field: 'auto_wheel_cal',\n type: 'bool',\n scale: null,\n offset: 0,\n units: '',\n },\n 13: {\n field: 'auto_power_zero',\n type: 'bool',\n scale: null,\n offset: 0,\n units: '',\n },\n 14: { field: 'id', type: 'uint8', scale: null, offset: 0, units: '' },\n 15: {\n field: 'spd_enabled',\n type: 'bool',\n scale: null,\n offset: 0,\n units: '',\n },\n 16: {\n field: 'cad_enabled',\n type: 'bool',\n scale: null,\n offset: 0,\n units: '',\n },\n 17: {\n field: 'spdcad_enabled',\n type: 'bool',\n scale: null,\n offset: 0,\n units: '',\n },\n 18: {\n field: 'power_enabled',\n type: 'bool',\n scale: null,\n offset: 0,\n units: '',\n },\n 19: {\n field: 'crank_length',\n type: 'uint8',\n scale: 2,\n offset: -110,\n units: 'mm',\n },\n 20: { field: 'enabled', type: 'bool', scale: null, offset: 0, units: '' },\n 21: {\n field: 'bike_spd_ant_id_trans_type',\n type: 'uint8z',\n scale: null,\n offset: 0,\n units: '',\n },\n 22: {\n field: 'bike_cad_ant_id_trans_type',\n type: 'uint8z',\n scale: null,\n offset: 0,\n units: '',\n },\n 23: {\n field: 'bike_spdcad_ant_id_trans_type',\n type: 'uint8z',\n scale: null,\n offset: 0,\n units: '',\n },\n 24: {\n field: 'bike_power_ant_id_trans_type',\n type: 'uint8z',\n scale: null,\n offset: 0,\n units: '',\n },\n 37: {\n field: 'odometer_rollover',\n type: 'uint8',\n scale: null,\n offset: 0,\n units: '',\n },\n 38: {\n field: 'front_gear_num',\n type: 'uint8z',\n scale: null,\n offset: 0,\n units: '',\n },\n 39: {\n field: 'front_gear',\n type: 'uint8z',\n scale: null,\n offset: 0,\n units: '',\n },\n 40: {\n field: 'rear_gear_num',\n type: 'uint8z',\n scale: null,\n offset: 0,\n units: '',\n },\n 41: {\n field: 'rear_gear',\n type: 'uint8z',\n scale: null,\n offset: 0,\n units: '',\n },\n 44: {\n field: 'shimano_di2_enabled',\n type: 'bool',\n scale: null,\n offset: 0,\n units: '',\n },\n },\n 7: {\n name: 'zones_target',\n 1: {\n field: 'max_heart_rate',\n type: 'uint8',\n scale: null,\n offset: 0,\n units: '',\n },\n 2: {\n field: 'threshold_heart_rate',\n type: 'uint8',\n scale: null,\n offset: 0,\n units: '',\n },\n 3: {\n field: 'functional_threshold_power',\n type: 'uint16',\n scale: null,\n offset: 0,\n units: '',\n },\n 5: {\n field: 'hr_calc_type',\n type: 'hr_zone_calc',\n scale: null,\n offset: 0,\n units: '',\n },\n 7: {\n field: 'pwr_calc_type',\n type: 'pwr_zone_calc',\n scale: null,\n offset: 0,\n units: '',\n },\n },\n 8: {\n name: 'hr_zone',\n 254: {\n field: 'message_index',\n type: 'message_index',\n scale: null,\n offset: 0,\n units: '',\n },\n 1: {\n field: 'high_bpm',\n type: 'uint8',\n scale: null,\n offset: 0,\n units: 'bpm',\n },\n 2: { field: 'name', type: 'string', scale: null, offset: 0, units: '' },\n },\n 9: {\n name: 'power_zone',\n 254: {\n field: 'message_index',\n type: 'message_index',\n scale: null,\n offset: 0,\n units: '',\n },\n 1: {\n field: 'high_value',\n type: 'uint16',\n scale: null,\n offset: 0,\n units: 'watts',\n },\n 2: { field: 'name', type: 'string', scale: null, offset: 0, units: '' },\n },\n 10: {\n name: 'met_zone',\n 254: {\n field: 'message_index',\n type: 'message_index',\n scale: null,\n offset: 0,\n units: '',\n },\n 1: {\n field: 'high_bpm',\n type: 'uint8',\n scale: null,\n offset: 0,\n units: '',\n },\n 2: {\n field: 'calories',\n type: 'uint16',\n scale: 10,\n offset: 0,\n units: 'kcal / min',\n },\n 3: {\n field: 'fat_calories',\n type: 'uint8',\n scale: 10,\n offset: 0,\n units: 'kcal / min',\n },\n },\n 12: {\n name: 'sport',\n 0: { field: 'sport', type: 'sport', scale: null, offset: 0, units: '' },\n 1: {\n field: 'sub_sport',\n type: 'sub_sport',\n scale: null,\n offset: 0,\n units: '',\n },\n 3: { field: 'name', type: 'string', scale: null, offset: 0, units: '' },\n },\n 15: {\n name: 'goal',\n 254: {\n field: 'message_index',\n type: 'message_index',\n scale: null,\n offset: 0,\n units: '',\n },\n 0: { field: 'sport', type: 'sport', scale: null, offset: 0, units: '' },\n 1: {\n field: 'sub_sport',\n type: 'sub_sport',\n scale: null,\n offset: 0,\n units: '',\n },\n 2: {\n field: 'start_date',\n type: 'date_time',\n scale: null,\n offset: 0,\n units: '',\n },\n 3: {\n field: 'end_date',\n type: 'date_time',\n scale: null,\n offset: 0,\n units: '',\n },\n 4: { field: 'type', type: 'goal', scale: null, offset: 0, units: '' },\n 5: { field: 'value', type: 'uint32', scale: null, offset: 0, units: '' },\n 6: { field: 'repeat', type: 'bool', scale: null, offset: 0, units: '' },\n 7: {\n field: 'target_value',\n type: 'uint32',\n scale: null,\n offset: 0,\n units: '',\n },\n 8: {\n field: 'recurrence',\n type: 'goal_recurrence',\n scale: null,\n offset: 0,\n units: '',\n },\n 9: {\n field: 'recurrence_value',\n type: 'uint16',\n scale: null,\n offset: 0,\n units: '',\n },\n 10: { field: 'enabled', type: 'bool', scale: null, offset: 0, units: '' },\n },\n 18: {\n name: 'session',\n 254: {\n field: 'message_index',\n type: 'message_index',\n scale: null,\n offset: 0,\n units: '',\n },\n 253: {\n field: 'timestamp',\n type: 'date_time',\n scale: null,\n offset: 0,\n units: 's',\n },\n 0: { field: 'event', type: 'event', scale: null, offset: 0, units: '' },\n 1: {\n field: 'event_type',\n type: 'event_type',\n scale: null,\n offset: 0,\n units: '',\n },\n 2: {\n field: 'start_time',\n type: 'date_time',\n scale: null,\n offset: 0,\n units: '',\n },\n 3: {\n field: 'start_position_lat',\n type: 'sint32',\n scale: null,\n offset: 0,\n units: 'semicircles',\n },\n 4: {\n field: 'start_position_long',\n type: 'sint32',\n scale: null,\n offset: 0,\n units: 'semicircles',\n },\n 5: { field: 'sport', type: 'sport', scale: null, offset: 0, units: '' },\n 6: {\n field: 'sub_sport',\n type: 'sub_sport',\n scale: null,\n offset: 0,\n units: '',\n },\n 7: {\n field: 'total_elapsed_time',\n type: 'uint32',\n scale: 1000,\n offset: 0,\n units: 's',\n },\n 8: {\n field: 'total_timer_time',\n type: 'uint32',\n scale: 1000,\n offset: 0,\n units: 's',\n },\n 9: {\n field: 'total_distance',\n type: 'uint32',\n scale: 100,\n offset: 0,\n units: 'm',\n },\n 10: {\n field: 'total_cycles',\n type: 'uint32',\n scale: null,\n offset: 0,\n units: 'cycles',\n },\n 11: {\n field: 'total_calories',\n type: 'uint16',\n scale: null,\n offset: 0,\n units: 'kcal',\n },\n 13: {\n field: 'total_fat_calories',\n type: 'uint16',\n scale: null,\n offset: 0,\n units: 'kcal',\n },\n 14: {\n field: 'avg_speed',\n type: 'uint16',\n scale: 1000,\n offset: 0,\n units: 'm/s',\n },\n 15: {\n field: 'max_speed',\n type: 'uint16',\n scale: 1000,\n offset: 0,\n units: 'm/s',\n },\n 16: {\n field: 'avg_heart_rate',\n type: 'uint8',\n scale: null,\n offset: 0,\n units: 'bpm',\n },\n 17: {\n field: 'max_heart_rate',\n type: 'uint8',\n scale: null,\n offset: 0,\n units: 'bpm',\n },\n 18: {\n field: 'avg_cadence',\n type: 'uint8',\n scale: null,\n offset: 0,\n units: 'rpm',\n },\n 19: {\n field: 'max_cadence',\n type: 'uint8',\n scale: null,\n offset: 0,\n units: 'rpm',\n },\n 20: {\n field: 'avg_power',\n type: 'uint16',\n scale: null,\n offset: 0,\n units: 'watts',\n },\n 21: {\n field: 'max_power',\n type: 'uint16',\n scale: null,\n offset: 0,\n units: 'watts',\n },\n 22: {\n field: 'total_ascent',\n type: 'uint16',\n scale: null,\n offset: 0,\n units: 'm',\n },\n 23: {\n field: 'total_descent',\n type: 'uint16',\n scale: null,\n offset: 0,\n units: 'm',\n },\n 24: {\n field: 'total_training_effect',\n type: 'uint8',\n scale: 10,\n offset: 0,\n units: '',\n },\n 25: {\n field: 'first_lap_index',\n type: 'uint16',\n scale: null,\n offset: 0,\n units: '',\n },\n 26: {\n field: 'num_laps',\n type: 'uint16',\n scale: null,\n offset: 0,\n units: '',\n },\n 27: {\n field: 'event_group',\n type: 'uint8',\n scale: null,\n offset: 0,\n units: '',\n },\n 28: {\n field: 'trigger',\n type: 'session_trigger',\n scale: null,\n offset: 0,\n units: '',\n },\n 29: {\n field: 'nec_lat',\n type: 'sint32',\n scale: null,\n offset: 0,\n units: 'semicircles',\n },\n 30: {\n field: 'nec_long',\n type: 'sint32',\n scale: null,\n offset: 0,\n units: 'semicircles',\n },\n 31: {\n field: 'swc_lat',\n type: 'sint32',\n scale: null,\n offset: 0,\n units: 'semicircles',\n },\n 32: {\n field: 'swc_long',\n type: 'sint32',\n scale: null,\n offset: 0,\n units: 'semicircles',\n },\n 34: {\n field: 'normalized_power',\n type: 'uint16',\n scale: null,\n offset: 0,\n units: 'watts',\n },\n 35: {\n field: 'training_stress_score',\n type: 'uint16',\n scale: 10,\n offset: 0,\n units: 'tss',\n },\n 36: {\n field: 'intensity_factor',\n type: 'uint16',\n scale: 1000,\n offset: 0,\n units: 'if',\n },\n 37: {\n field: 'left_right_balance',\n type: 'left_right_balance_100',\n scale: 100,\n offset: 0,\n units: '%',\n },\n 38: {\n field: 'end_position_lat',\n type: 'sint32',\n scale: null,\n offset: 0,\n units: 'semicircles',\n },\n 39: {\n field: 'end_position_long',\n type: 'sint32',\n scale: null,\n offset: 0,\n units: 'semicircles',\n },\n 41: {\n field: 'avg_stroke_count',\n type: 'uint32',\n scale: 10,\n offset: 0,\n units: 'strokes/lap',\n },\n 42: {\n field: 'avg_stroke_distance',\n type: 'uint16',\n scale: 100,\n offset: 0,\n units: 'm',\n },\n 43: {\n field: 'swim_stroke',\n type: 'swim_stroke',\n scale: null,\n offset: 0,\n units: 'swim_stroke',\n },\n 44: {\n field: 'pool_length',\n type: 'uint16',\n scale: 100,\n offset: 0,\n units: 'm',\n },\n 45: {\n field: 'threshold_power',\n type: 'uint16',\n scale: null,\n offset: 0,\n units: 'watts',\n },\n 46: {\n field: 'pool_length_unit',\n type: 'display_measure',\n scale: null,\n offset: 0,\n units: '',\n },\n 47: {\n field: 'num_active_lengths',\n type: 'uint16',\n scale: null,\n offset: 0,\n units: 'lengths',\n },\n 48: {\n field: 'total_work',\n type: 'uint32',\n scale: null,\n offset: 0,\n units: 'J',\n },\n 49: {\n field: 'avg_altitude',\n type: 'uint16',\n scale: 5,\n offset: -500,\n units: 'm',\n },\n 50: {\n field: 'max_altitude',\n type: 'uint16',\n scale: 5,\n offset: -500,\n units: 'm',\n },\n 51: {\n field: 'gps_accuracy',\n type: 'uint8',\n scale: null,\n offset: 0,\n units: 'm',\n },\n 52: {\n field: 'avg_grade',\n type: 'sint16',\n scale: 100,\n offset: 0,\n units: '%',\n },\n 53: {\n field: 'avg_pos_grade',\n type: 'sint16',\n scale: 100,\n offset: 0,\n units: '%',\n },\n 54: {\n field: 'avg_neg_grade',\n type: 'sint16',\n scale: 100,\n offset: 0,\n units: '%',\n },\n 55: {\n field: 'max_pos_grade',\n type: 'sint16',\n scale: 100,\n offset: 0,\n units: '%',\n },\n 56: {\n field: 'max_neg_grade',\n type: 'sint16',\n scale: 100,\n offset: 0,\n units: '%',\n },\n 57: {\n field: 'avg_temperature',\n type: 'sint8',\n scale: null,\n offset: 0,\n units: 'C',\n },\n 58: {\n field: 'max_temperature',\n type: 'sint8',\n scale: null,\n offset: 0,\n units: 'C',\n },\n 59: {\n field: 'total_moving_time',\n type: 'uint32',\n scale: 1000,\n offset: 0,\n units: 's',\n },\n 60: {\n field: 'avg_pos_vertical_speed',\n type: 'uint16',\n scale: 1000,\n offset: 0,\n units: 'm/s',\n },\n 61: {\n field: 'avg_neg_vertical_speed',\n type: 'uint16',\n scale: 1000,\n offset: 0,\n units: 'm/s',\n },\n 62: {\n field: 'max_pos_vertical_speed',\n type: 'uint16',\n scale: 1000,\n offset: 0,\n units: 'm/s',\n },\n 63: {\n field: 'max_neg_vertical_speed',\n type: 'uint16',\n scale: 1000,\n offset: 0,\n units: 'm/s',\n },\n 64: {\n field: 'min_heart_rate',\n type: 'uint8',\n scale: null,\n offset: 0,\n units: 'bpm',\n },\n 65: {\n field: 'time_in_hr_zone',\n type: 'uint32_array',\n scale: 1000,\n offset: 0,\n units: 's',\n },\n 66: {\n field: 'time_in_speed_zone',\n type: 'uint32_array',\n scale: 1000,\n offset: 0,\n units: 's',\n },\n 67: {\n field: 'time_in_cadence_zone',\n type: 'uint32_array',\n scale: 1000,\n offset: 0,\n units: 's',\n },\n 68: {\n field: 'time_in_power_zone',\n type: 'uint32_array',\n scale: 1000,\n offset: 0,\n units: 's',\n },\n 69: {\n field: 'avg_lap_time',\n type: 'uint32',\n scale: 1000,\n offset: 0,\n units: 's',\n },\n 70: {\n field: 'best_lap_index',\n type: 'uint16',\n scale: null,\n offset: 0,\n units: '',\n },\n 71: {\n field: 'min_altitude',\n type: 'uint16',\n scale: 5,\n offset: -500,\n units: 'm',\n },\n 82: {\n field: 'player_score',\n type: 'uint16',\n scale: null,\n offset: 0,\n units: '',\n },\n 83: {\n field: 'opponent_score',\n type: 'uint16',\n scale: null,\n offset: 0,\n units: '',\n },\n 84: {\n field: 'opponent_name',\n type: 'string',\n scale: null,\n offset: 0,\n units: '',\n },\n 85: {\n field: 'stroke_count',\n type: 'uint16',\n scale: null,\n offset: 0,\n units: 'counts',\n },\n 86: {\n field: 'zone_count',\n type: 'uint16',\n scale: null,\n offset: 0,\n units: 'counts',\n },\n 87: {\n field: 'max_ball_speed',\n type: 'uint16',\n scale: 100,\n offset: 0,\n units: 'm/s',\n },\n 88: {\n field: 'avg_ball_speed',\n type: 'uint16',\n scale: 100,\n offset: 0,\n units: 'm/s',\n },\n 89: {\n field: 'avg_vertical_oscillation',\n type: 'uint16',\n scale: 10,\n offset: 0,\n units: 'mm',\n },\n 90: {\n field: 'avg_stance_time_percent',\n type: 'uint16',\n scale: 100,\n offset: 0,\n units: 'percent',\n },\n 91: {\n field: 'avg_stance_time',\n type: 'uint16',\n scale: 10,\n offset: 0,\n units: 'ms',\n },\n 92: {\n field: 'avg_fractional_cadence',\n type: 'uint8',\n scale: 128,\n offset: 0,\n units: 'rpm',\n },\n 93: {\n field: 'max_fractional_cadence',\n type: 'uint8',\n scale: 128,\n offset: 0,\n units: 'rpm',\n },\n 94: {\n field: 'total_fractional_cycles',\n type: 'uint8',\n scale: 128,\n offset: 0,\n units: 'cycles',\n },\n 95: {\n field: 'avg_total_hemoglobin_conc',\n type: 'uint16',\n scale: 100,\n offset: 0,\n units: 'g/dL',\n },\n 96: {\n field: 'min_total_hemoglobin_conc',\n type: 'uint16',\n scale: 100,\n offset: 0,\n units: 'g/dL',\n },\n 97: {\n field: 'max_total_hemoglobin_conc',\n type: 'uint16',\n scale: 100,\n offset: 0,\n units: 'g/dL',\n },\n 98: {\n field: 'avg_saturated_hemoglobin_percent',\n type: 'uint16',\n scale: 10,\n offset: 0,\n units: '%',\n },\n 99: {\n field: 'min_saturated_hemoglobin_percent',\n type: 'uint16',\n scale: 10,\n offset: 0,\n units: '%',\n },\n 100: {\n field: 'max_saturated_hemoglobin_percent',\n type: 'uint16',\n scale: 10,\n offset: 0,\n units: '%',\n },\n 101: {\n field: 'avg_left_torque_effectiveness',\n type: 'uint8',\n scale: 2,\n offset: 0,\n units: 'percent',\n },\n 102: {\n field: 'avg_right_torque_effectiveness',\n type: 'uint8',\n scale: 2,\n offset: 0,\n units: 'percent',\n },\n 103: {\n field: 'avg_left_pedal_smoothness',\n type: 'uint8',\n scale: 2,\n offset: 0,\n units: 'percent',\n },\n 104: {\n field: 'avg_right_pedal_smoothness',\n type: 'uint8',\n scale: 2,\n offset: 0,\n units: 'percent',\n },\n 105: {\n field: 'avg_combined_pedal_smoothness',\n type: 'uint8',\n scale: 2,\n offset: 0,\n units: 'percent',\n },\n 111: {\n field: 'sport_index',\n type: 'uint8',\n scale: null,\n offset: 0,\n units: '',\n },\n 112: {\n field: 'time_standing',\n type: 'uint32',\n scale: 1000,\n offset: 0,\n units: 's',\n },\n 113: {\n field: 'stand_count',\n type: 'uint16',\n scale: null,\n offset: 0,\n units: '',\n },\n 114: {\n field: 'avg_left_pco',\n type: 'sint8',\n scale: null,\n offset: 0,\n units: 'mm',\n },\n 115: {\n field: 'avg_right_pco',\n type: 'sint8',\n scale: null,\n offset: 0,\n units: 'mm',\n },\n 116: {\n field: 'avg_left_power_phase',\n type: 'uint8',\n scale: 0.7111111,\n offset: 0,\n units: 'degrees',\n },\n 117: {\n field: 'avg_left_power_phase_peak',\n type: 'uint8',\n scale: 0.7111111,\n offset: 0,\n units: 'degrees',\n },\n 118: {\n field: 'avg_right_power_phase',\n type: 'uint8',\n scale: 0.7111111,\n offset: 0,\n units: 'degrees',\n },\n 119: {\n field: 'avg_right_power_phase_peak',\n type: 'uint8',\n scale: 0.7111111,\n offset: 0,\n units: 'degrees',\n },\n 120: {\n field: 'avg_power_position',\n type: 'uint16',\n scale: null,\n offset: 0,\n units: 'watts',\n },\n 121: {\n field: 'max_power_position',\n type: 'uint16',\n scale: null,\n offset: 0,\n units: 'watts',\n },\n 122: {\n field: 'avg_cadence_position',\n type: 'uint8',\n scale: null,\n offset: 0,\n units: 'rpm',\n },\n 123: {\n field: 'max_cadence_position',\n type: 'uint8',\n scale: null,\n offset: 0,\n units: 'rpm',\n },\n 124: {\n field: 'enhanced_avg_speed',\n type: 'uint32',\n scale: 1000,\n offset: 0,\n units: 'm/s',\n },\n 125: {\n field: 'enhanced_max_speed',\n type: 'uint32',\n scale: 1000,\n offset: 0,\n units: 'm/s',\n },\n 126: {\n field: 'enhanced_avg_altitude',\n type: 'uint32',\n scale: 5,\n offset: -500,\n units: 'm',\n },\n 127: {\n field: 'enhanced_min_altitude',\n type: 'uint32',\n scale: 5,\n offset: -500,\n units: 'm',\n },\n 128: {\n field: 'enhanced_max_altitude',\n type: 'uint32',\n scale: 5,\n offset: -500,\n units: 'm',\n },\n 129: {\n field: 'avg_lev_motor_power',\n type: 'uint16',\n scale: null,\n offset: 0,\n units: 'watts',\n },\n 130: {\n field: 'max_lev_motor_power',\n type: 'uint16',\n scale: null,\n offset: 0,\n units: 'watts',\n },\n 131: {\n field: 'lev_battery_consumption',\n type: 'uint8',\n scale: 2,\n offset: 0,\n units: 'percent',\n },\n 132: {\n field: 'avg_vertical_ratio',\n type: 'uint16',\n scale: 100,\n offset: 0,\n units: 'percent',\n },\n 133: {\n field: 'avg_stance_time_balance',\n type: 'uint16',\n scale: 100,\n offset: 0,\n units: 'percent',\n },\n 134: {\n field: 'avg_step_length',\n type: 'uint16',\n scale: 10,\n offset: 0,\n units: 'mm',\n },\n 137: {\n field: 'total_anaerobic_training_effect',\n type: 'uint8',\n scale: 10,\n offset: 0,\n units: '',\n },\n 139: {\n field: 'avg_vam',\n type: 'uint16',\n scale: 1, // Raw 100 -> 100 (User specific m/h)\n offset: 0,\n units: 'm/h',\n },\n 192: {\n field: 'workout_feel',\n type: 'uint8',\n scale: null,\n offset: 0,\n units: '',\n },\n 193: {\n field: 'workout_rpe',\n type: 'uint8',\n scale: 10,\n offset: 0,\n units: '',\n },\n 110: {\n field: 'sport_profile_name',\n type: 'string',\n scale: null,\n offset: 0,\n units: '',\n },\n 168: {\n field: 'training_load_peak',\n type: 'uint32',\n scale: 1,\n offset: 0,\n units: '',\n },\n 169: {\n field: 'enhanced_avg_respiration_rate',\n type: 'uint16',\n scale: 100,\n offset: 0,\n units: 'breaths/min',\n },\n 150: {\n field: 'min_temperature',\n type: 'sint8',\n scale: null,\n offset: 0,\n units: 'C',\n },\n 170: {\n field: 'enhanced_max_respiration_rate',\n type: 'uint16',\n scale: 100,\n offset: 0,\n units: 'breaths/min',\n },\n 178: {\n field: 'est_sweat_loss',\n type: 'uint16',\n scale: 1, // ml\n offset: 0,\n units: 'ml',\n },\n 180: {\n field: 'enhanced_min_respiration_rate',\n type: 'uint16',\n scale: 100,\n offset: 0,\n units: 'breaths/min',\n },\n 181: {\n field: 'total_grit',\n type: 'float32',\n scale: 1,\n offset: 0,\n units: 'kGrit',\n },\n 182: {\n field: 'total_flow',\n type: 'float32',\n scale: 1,\n offset: 0,\n units: 'Flow',\n },\n 183: {\n field: 'jump_count',\n type: 'uint16',\n scale: 1,\n offset: 0,\n units: '',\n },\n 186: {\n field: 'avg_grit',\n type: 'float32',\n scale: 1,\n offset: 0,\n units: 'kGrit',\n },\n 187: {\n field: 'avg_flow',\n type: 'float32',\n scale: 1,\n offset: 0,\n units: 'Flow',\n },\n 188: {\n field: 'primary_benefit',\n type: 'uint8',\n scale: 1,\n offset: 0,\n units: '',\n },\n 196: {\n field: 'resting_calories',\n type: 'uint16',\n scale: 1,\n offset: 0,\n units: 'kcal',\n },\n 214: {\n field: 'avg_grit',\n type: 'float32',\n scale: null,\n offset: 0,\n units: '',\n },\n 215: {\n field: 'avg_flow',\n type: 'float32',\n scale: null,\n offset: 0,\n units: '',\n },\n 140: {\n field: 'recovery_advisor',\n type: 'uint16', // Minutes?\n scale: 1,\n offset: 0,\n units: 'min',\n },\n },\n 19: {\n name: 'lap',\n 254: {\n field: 'message_index',\n type: 'message_index',\n scale: null,\n offset: 0,\n units: '',\n },\n 253: {\n field: 'timestamp',\n type: 'date_time',\n scale: null,\n offset: 0,\n units: 's',\n },\n 0: { field: 'event', type: 'event', scale: null, offset: 0, units: '' },\n 1: {\n field: 'event_type',\n type: 'event_type',\n scale: null,\n offset: 0,\n units: '',\n },\n 2: {\n field: 'start_time',\n type: 'date_time',\n scale: null,\n offset: 0,\n units: '',\n },\n 3: {\n field: 'start_position_lat',\n type: 'sint32',\n scale: null,\n offset: 0,\n units: 'semicircles',\n },\n 4: {\n field: 'start_position_long',\n type: 'sint32',\n scale: null,\n offset: 0,\n units: 'semicircles',\n },\n 5: {\n field: 'end_position_lat',\n type: 'sint32',\n scale: null,\n offset: 0,\n units: 'semicircles',\n },\n 6: {\n field: 'end_position_long',\n type: 'sint32',\n scale: null,\n offset: 0,\n units: 'semicircles',\n },\n 7: {\n field: 'total_elapsed_time',\n type: 'uint32',\n scale: 1000,\n offset: 0,\n units: 's',\n },\n 8: {\n field: 'total_timer_time',\n type: 'uint32',\n scale: 1000,\n offset: 0,\n units: 's',\n },\n 9: {\n field: 'total_distance',\n type: 'uint32',\n scale: 100,\n offset: 0,\n units: 'm',\n },\n 10: {\n field: 'total_cycles',\n type: 'uint32',\n scale: null,\n offset: 0,\n units: 'cycles',\n },\n 11: {\n field: 'total_calories',\n type: 'uint16',\n scale: null,\n offset: 0,\n units: 'kcal',\n },\n 12: {\n field: 'total_fat_calories',\n type: 'uint16',\n scale: null,\n offset: 0,\n units: 'kcal',\n },\n 13: {\n field: 'avg_speed',\n type: 'uint16',\n scale: 1000,\n offset: 0,\n units: 'm/s',\n },\n 14: {\n field: 'max_speed',\n type: 'uint16',\n scale: 1000,\n offset: 0,\n units: 'm/s',\n },\n 15: {\n field: 'avg_heart_rate',\n type: 'uint8',\n scale: null,\n offset: 0,\n units: 'bpm',\n },\n 16: {\n field: 'max_heart_rate',\n type: 'uint8',\n scale: null,\n offset: 0,\n units: 'bpm',\n },\n 17: {\n field: 'avg_cadence',\n type: 'uint8',\n scale: null,\n offset: 0,\n units: 'rpm',\n },\n 18: {\n field: 'max_cadence',\n type: 'uint8',\n scale: null,\n offset: 0,\n units: 'rpm',\n },\n 19: {\n field: 'avg_power',\n type: 'uint16',\n scale: null,\n offset: 0,\n units: 'watts',\n },\n 20: {\n field: 'max_power',\n type: 'uint16',\n scale: null,\n offset: 0,\n units: 'watts',\n },\n 21: {\n field: 'total_ascent',\n type: 'uint16',\n scale: null,\n offset: 0,\n units: 'm',\n },\n 22: {\n field: 'total_descent',\n type: 'uint16',\n scale: null,\n offset: 0,\n units: 'm',\n },\n 23: {\n field: 'intensity',\n type: 'intensity',\n scale: null,\n offset: 0,\n units: '',\n },\n 24: {\n field: 'lap_trigger',\n type: 'lap_trigger',\n scale: null,\n offset: 0,\n units: '',\n },\n 25: { field: 'sport', type: 'sport', scale: null, offset: 0, units: '' },\n 26: {\n field: 'event_group',\n type: 'uint8',\n scale: null,\n offset: 0,\n units: '',\n },\n 32: {\n field: 'num_lengths',\n type: 'uint16',\n scale: null,\n offset: 0,\n units: 'lengths',\n },\n 33: {\n field: 'normalized_power',\n type: 'uint16',\n scale: null,\n offset: 0,\n units: 'watts',\n },\n 34: {\n field: 'left_right_balance',\n type: 'left_right_balance_100',\n scale: 100,\n offset: 0,\n units: '%',\n },\n 35: {\n field: 'first_length_index',\n type: 'uint16',\n scale: null,\n offset: 0,\n units: '',\n },\n 37: {\n field: 'avg_stroke_distance',\n type: 'uint16',\n scale: 100,\n offset: 0,\n units: 'm',\n },\n 38: {\n field: 'swim_stroke',\n type: 'swim_stroke',\n scale: null,\n offset: 0,\n units: '',\n },\n 39: {\n field: 'sub_sport',\n type: 'sub_sport',\n scale: null,\n offset: 0,\n units: '',\n },\n 40: {\n field: 'num_active_lengths',\n type: 'uint16',\n scale: null,\n offset: 0,\n units: 'lengths',\n },\n 41: {\n field: 'total_work',\n type: 'uint32',\n scale: null,\n offset: 0,\n units: 'J',\n },\n 42: {\n field: 'avg_altitude',\n type: 'uint16',\n scale: 5,\n offset: -500,\n units: 'm',\n },\n 43: {\n field: 'max_altitude',\n type: 'uint16',\n scale: 5,\n offset: -500,\n units: 'm',\n },\n 44: {\n field: 'gps_accuracy',\n type: 'uint8',\n scale: null,\n offset: 0,\n units: 'm',\n },\n 45: {\n field: 'avg_grade',\n type: 'sint16',\n scale: 100,\n offset: 0,\n units: '%',\n },\n 46: {\n field: 'avg_pos_grade',\n type: 'sint16',\n scale: 100,\n offset: 0,\n units: '%',\n },\n 47: {\n field: 'avg_neg_grade',\n type: 'sint16',\n scale: 100,\n offset: 0,\n units: '%',\n },\n 48: {\n field: 'max_pos_grade',\n type: 'sint16',\n scale: 100,\n offset: 0,\n units: '%',\n },\n 49: {\n field: 'max_neg_grade',\n type: 'sint16',\n scale: 100,\n offset: 0,\n units: '%',\n },\n 50: {\n field: 'avg_temperature',\n type: 'sint8',\n scale: null,\n offset: 0,\n units: 'C',\n },\n 51: {\n field: 'max_temperature',\n type: 'sint8',\n scale: null,\n offset: 0,\n units: 'C',\n },\n 52: {\n field: 'total_moving_time',\n type: 'uint32',\n scale: 1000,\n offset: 0,\n units: 's',\n },\n 53: {\n field: 'avg_pos_vertical_speed',\n type: 'uint16',\n scale: 1000,\n offset: 0,\n units: 'm/s',\n },\n 54: {\n field: 'avg_neg_vertical_speed',\n type: 'uint16',\n scale: 1000,\n offset: 0,\n units: 'm/s',\n },\n 55: {\n field: 'max_pos_vertical_speed',\n type: 'uint16',\n scale: 1000,\n offset: 0,\n units: 'm/s',\n },\n 56: {\n field: 'max_neg_vertical_speed',\n type: 'uint16',\n scale: 1000,\n offset: 0,\n units: 'm/s',\n },\n 57: {\n field: 'time_in_hr_zone',\n type: 'uint32_array',\n scale: 1000,\n offset: 0,\n units: 's',\n },\n 58: {\n field: 'time_in_speed_zone',\n type: 'uint32_array',\n scale: 1000,\n offset: 0,\n units: 's',\n },\n 59: {\n field: 'time_in_cadence_zone',\n type: 'uint32_array',\n scale: 1000,\n offset: 0,\n units: 's',\n },\n 60: {\n field: 'time_in_power_zone',\n type: 'uint32_array',\n scale: 1000,\n offset: 0,\n units: 's',\n },\n 61: {\n field: 'repetition_num',\n type: 'uint16',\n scale: null,\n offset: 0,\n units: '',\n },\n 62: {\n field: 'min_altitude',\n type: 'uint16',\n scale: 5,\n offset: -500,\n units: 'm',\n },\n 63: {\n field: 'min_heart_rate',\n type: 'uint8',\n scale: null,\n offset: 0,\n units: 'bpm',\n },\n 71: {\n field: 'wkt_step_index',\n type: 'message_index',\n scale: null,\n offset: 0,\n units: '',\n },\n 74: {\n field: 'opponent_score',\n type: 'uint16',\n scale: null,\n offset: 0,\n units: '',\n },\n 75: {\n field: 'stroke_count',\n type: 'uint16',\n scale: null,\n offset: 0,\n units: 'counts',\n },\n 76: {\n field: 'zone_count',\n type: 'uint16',\n scale: null,\n offset: 0,\n units: 'counts',\n },\n 77: {\n field: 'avg_vertical_oscillation',\n type: 'uint16',\n scale: 10,\n offset: 0,\n units: 'mm',\n },\n 78: {\n field: 'avg_stance_time_percent',\n type: 'uint16',\n scale: 100,\n offset: 0,\n units: 'percent',\n },\n 79: {\n field: 'avg_stance_time',\n type: 'uint16',\n scale: 10,\n offset: 0,\n units: 'ms',\n },\n 80: {\n field: 'avg_fractional_cadence',\n type: 'uint8',\n scale: 128,\n offset: 0,\n units: 'rpm',\n },\n 81: {\n field: 'max_fractional_cadence',\n type: 'uint8',\n scale: 128,\n offset: 0,\n units: 'rpm',\n },\n 82: {\n field: 'total_fractional_cycles',\n type: 'uint8',\n scale: 128,\n offset: 0,\n units: 'cycles',\n },\n 83: {\n field: 'player_score',\n type: 'uint16',\n scale: null,\n offset: 0,\n units: '',\n },\n 84: {\n field: 'avg_total_hemoglobin_conc',\n type: 'uint16',\n scale: 100,\n offset: 0,\n units: 'g/dL',\n },\n 85: {\n field: 'min_total_hemoglobin_conc',\n type: 'uint16',\n scale: 100,\n offset: 0,\n units: 'g/dL',\n },\n 86: {\n field: 'max_total_hemoglobin_conc',\n type: 'uint16',\n scale: 100,\n offset: 0,\n units: 'g/dL',\n },\n 87: {\n field: 'avg_saturated_hemoglobin_percent',\n type: 'uint16',\n scale: 10,\n offset: 0,\n units: '%',\n },\n 88: {\n field: 'min_saturated_hemoglobin_percent',\n type: 'uint16',\n scale: 10,\n offset: 0,\n units: '%',\n },\n 89: {\n field: 'max_saturated_hemoglobin_percent',\n type: 'uint16',\n scale: 10,\n offset: 0,\n units: '%',\n },\n 91: {\n field: 'avg_left_torque_effectiveness',\n type: 'uint8',\n scale: 2,\n offset: 0,\n units: 'percent',\n },\n 92: {\n field: 'avg_right_torque_effectiveness',\n type: 'uint8',\n scale: 2,\n offset: 0,\n units: 'percent',\n },\n 93: {\n field: 'avg_left_pedal_smoothness',\n type: 'uint8',\n scale: 2,\n offset: 0,\n units: 'percent',\n },\n 94: {\n field: 'avg_right_pedal_smoothness',\n type: 'uint8',\n scale: 2,\n offset: 0,\n units: 'percent',\n },\n 95: {\n field: 'avg_combined_pedal_smoothness',\n type: 'uint8',\n scale: 2,\n offset: 0,\n units: 'percent',\n },\n 98: {\n field: 'time_standing',\n type: 'uint32',\n scale: 1000,\n offset: 0,\n units: 's',\n },\n 99: {\n field: 'stand_count',\n type: 'uint16',\n scale: null,\n offset: 0,\n units: '',\n },\n 100: {\n field: 'avg_left_pco',\n type: 'sint8',\n scale: null,\n offset: 0,\n units: 'mm',\n },\n 101: {\n field: 'avg_right_pco',\n type: 'sint8',\n scale: null,\n offset: 0,\n units: 'mm',\n },\n 102: {\n field: 'avg_left_power_phase',\n type: 'uint8',\n scale: 0.7111111,\n offset: 0,\n units: 'degrees',\n },\n 103: {\n field: 'avg_left_power_phase_peak',\n type: 'uint8',\n scale: 0.7111111,\n offset: 0,\n units: 'degrees',\n },\n 104: {\n field: 'avg_right_power_phase',\n type: 'uint8',\n scale: 0.7111111,\n offset: 0,\n units: 'degrees',\n },\n 105: {\n field: 'avg_right_power_phase_peak',\n type: 'uint8',\n scale: 0.7111111,\n offset: 0,\n units: 'degrees',\n },\n 106: {\n field: 'avg_power_position',\n type: 'uint16',\n scale: null,\n offset: 0,\n units: 'watts',\n },\n 107: {\n field: 'max_power_position',\n type: 'uint16',\n scale: null,\n offset: 0,\n units: 'watts',\n },\n 108: {\n field: 'avg_cadence_position',\n type: 'uint8',\n scale: null,\n offset: 0,\n units: 'rpm',\n },\n 109: {\n field: 'max_cadence_position',\n type: 'uint8',\n scale: null,\n offset: 0,\n units: 'rpm',\n },\n 110: {\n field: 'enhanced_avg_speed',\n type: 'uint32',\n scale: 1000,\n offset: 0,\n units: 'm/s',\n },\n 111: {\n field: 'enhanced_max_speed',\n type: 'uint32',\n scale: 1000,\n offset: 0,\n units: 'm/s',\n },\n 112: {\n field: 'enhanced_avg_altitude',\n type: 'uint32',\n scale: 5,\n offset: -500,\n units: 'm',\n },\n 113: {\n field: 'enhanced_min_altitude',\n type: 'uint32',\n scale: 5,\n offset: -500,\n units: 'm',\n },\n 114: {\n field: 'enhanced_max_altitude',\n type: 'uint32',\n scale: 5,\n offset: -500,\n units: 'm',\n },\n 115: {\n field: 'avg_lev_motor_power',\n type: 'uint16',\n scale: null,\n offset: 0,\n units: 'watts',\n },\n 116: {\n field: 'max_lev_motor_power',\n type: 'uint16',\n scale: null,\n offset: 0,\n units: 'watts',\n },\n 117: {\n field: 'lev_battery_consumption',\n type: 'uint8',\n scale: 2,\n offset: 0,\n units: 'percent',\n },\n 118: {\n field: 'avg_vertical_ratio',\n type: 'uint16',\n scale: 100,\n offset: 0,\n units: 'percent',\n },\n 119: {\n field: 'avg_stance_time_balance',\n type: 'uint16',\n scale: 100,\n offset: 0,\n units: 'percent',\n },\n 120: {\n field: 'avg_step_length',\n type: 'uint16',\n scale: 10,\n offset: 0,\n units: 'mm',\n },\n 121: {\n field: 'avg_vam',\n type: 'uint16',\n scale: 1000,\n offset: 0,\n units: 'm/s',\n },\n },\n 20: {\n name: 'record',\n 253: {\n field: 'timestamp',\n type: 'date_time',\n scale: null,\n offset: 0,\n units: 's',\n },\n 114: {\n field: 'grit',\n type: 'float32',\n scale: null,\n offset: 0,\n units: '',\n },\n 115: {\n field: 'flow',\n type: 'float32',\n scale: null,\n offset: 0,\n units: '',\n },\n 0: {\n field: 'position_lat',\n type: 'sint32',\n scale: null,\n offset: 0,\n units: 'semicircles',\n },\n 1: {\n field: 'position_long',\n type: 'sint32',\n scale: null,\n offset: 0,\n units: 'semicircles',\n },\n 2: {\n field: 'altitude',\n type: 'uint16',\n scale: 5,\n offset: -500,\n units: 'm',\n },\n 3: {\n field: 'heart_rate',\n type: 'uint8',\n scale: null,\n offset: 0,\n units: 'bpm',\n },\n 4: {\n field: 'cadence',\n type: 'uint8',\n scale: null,\n offset: 0,\n units: 'rpm',\n },\n 5: {\n field: 'distance',\n type: 'uint32',\n scale: 100,\n offset: 0,\n units: 'm',\n },\n 6: {\n field: 'speed',\n type: 'uint16',\n scale: 1000,\n offset: 0,\n units: 'm/s',\n },\n 7: {\n field: 'power',\n type: 'uint16',\n scale: null,\n offset: 0,\n units: 'watts',\n },\n 8: {\n field: 'compressed_speed_distance',\n type: 'byte',\n scale: 100.16,\n offset: 0,\n units: 'm/s,m',\n },\n 9: { field: 'grade', type: 'sint16', scale: 100, offset: 0, units: '%' },\n 10: {\n field: 'resistance',\n type: 'uint8',\n scale: null,\n offset: 0,\n units: '',\n },\n 11: {\n field: 'time_from_course',\n type: 'sint32',\n scale: 1000,\n offset: 0,\n units: 's',\n },\n 12: {\n field: 'cycle_length',\n type: 'uint8',\n scale: 100,\n offset: 0,\n units: 'm',\n },\n 13: {\n field: 'temperature',\n type: 'sint8',\n scale: null,\n offset: 0,\n units: 'C',\n },\n 17: {\n field: 'speed_1s',\n type: 'uint8',\n scale: 16,\n offset: 0,\n units: 'm/s',\n },\n 18: {\n field: 'cycles',\n type: 'uint8',\n scale: null,\n offset: 0,\n units: 'cycles',\n },\n 19: {\n field: 'total_cycles',\n type: 'uint32',\n scale: null,\n offset: 0,\n units: 'cycles',\n },\n 28: {\n field: 'compressed_accumulated_power',\n type: 'uint16',\n scale: null,\n offset: 0,\n units: 'watts',\n },\n 29: {\n field: 'accumulated_power',\n type: 'uint32',\n scale: null,\n offset: 0,\n units: 'watts',\n },\n 30: {\n field: 'left_right_balance',\n type: 'left_right_balance',\n scale: null,\n offset: 0,\n units: '',\n },\n 31: {\n field: 'gps_accuracy',\n type: 'uint8',\n scale: null,\n offset: 0,\n units: 'm',\n },\n 32: {\n field: 'vertical_speed',\n type: 'sint16',\n scale: 1000,\n offset: 0,\n units: 'm/s',\n },\n 33: {\n field: 'calories',\n type: 'uint16',\n scale: null,\n offset: 0,\n units: 'kcal',\n },\n 39: {\n field: 'vertical_oscillation',\n type: 'uint16',\n scale: 10,\n offset: 0,\n units: 'mm',\n },\n 40: {\n field: 'stance_time_percent',\n type: 'uint16',\n scale: 100,\n offset: 0,\n units: 'percent',\n },\n 41: {\n field: 'stance_time',\n type: 'uint16',\n scale: 10,\n offset: 0,\n units: 'ms',\n },\n 42: {\n field: 'activity_type',\n type: 'activity_type',\n scale: null,\n offset: 0,\n units: '',\n },\n 43: {\n field: 'left_torque_effectiveness',\n type: 'uint8',\n scale: 2,\n offset: 0,\n units: 'percent',\n },\n 44: {\n field: 'right_torque_effectiveness',\n type: 'uint8',\n scale: 2,\n offset: 0,\n units: 'percent',\n },\n 45: {\n field: 'left_pedal_smoothness',\n type: 'uint8',\n scale: 2,\n offset: 0,\n units: 'percent',\n },\n 46: {\n field: 'right_pedal_smoothness',\n type: 'uint8',\n scale: 2,\n offset: 0,\n units: 'percent',\n },\n 47: {\n field: 'combined_pedal_smoothness',\n type: 'uint8',\n scale: 2,\n offset: 0,\n units: 'percent',\n },\n 48: {\n field: 'time128',\n type: 'uint8',\n scale: 128,\n offset: 0,\n units: 's',\n },\n 49: {\n field: 'stroke_type',\n type: 'stroke_type',\n scale: null,\n offset: 0,\n units: '',\n },\n 50: { field: 'zone', type: 'uint8', scale: null, offset: 0, units: '' },\n 51: {\n field: 'ball_speed',\n type: 'uint16',\n scale: 100,\n offset: 0,\n units: 'm/s',\n },\n 52: {\n field: 'cadence256',\n type: 'uint16',\n scale: 256,\n offset: 0,\n units: 'rpm',\n },\n 53: {\n field: 'fractional_cadence',\n type: 'uint8',\n scale: 128,\n offset: 0,\n units: 'rpm',\n },\n 54: {\n field: 'total_hemoglobin_conc',\n type: 'uint16',\n scale: 100,\n offset: 0,\n units: 'g/dL',\n },\n 55: {\n field: 'total_hemoglobin_conc_min',\n type: 'uint16',\n scale: 100,\n offset: 0,\n units: 'g/dL',\n },\n 56: {\n field: 'total_hemoglobin_conc_max',\n type: 'uint16',\n scale: 100,\n offset: 0,\n units: 'g/dL',\n },\n 57: {\n field: 'saturated_hemoglobin_percent',\n type: 'uint16',\n scale: 10,\n offset: 0,\n units: '%',\n },\n 58: {\n field: 'saturated_hemoglobin_percent_min',\n type: 'uint16',\n scale: 10,\n offset: 0,\n units: '%',\n },\n 59: {\n field: 'saturated_hemoglobin_percent_max',\n type: 'uint16',\n scale: 10,\n offset: 0,\n units: '%',\n },\n 62: {\n field: 'device_index',\n type: 'device_index',\n scale: null,\n offset: 0,\n units: '',\n },\n 67: {\n field: 'left_pco',\n type: 'sint8',\n scale: null,\n offset: 0,\n units: 'mm',\n },\n 68: {\n field: 'right_pco',\n type: 'sint8',\n scale: null,\n offset: 0,\n units: 'mm',\n },\n 69: {\n field: 'left_power_phase',\n type: 'uint8',\n scale: 0.7111111,\n offset: 0,\n units: 'degrees',\n },\n 70: {\n field: 'left_power_phase_peak',\n type: 'uint8',\n scale: 0.7111111,\n offset: 0,\n units: 'degrees',\n },\n 71: {\n field: 'right_power_phase',\n type: 'uint8',\n scale: 0.7111111,\n offset: 0,\n units: 'degrees',\n },\n 72: {\n field: 'right_power_phase_peak',\n type: 'uint8',\n scale: 0.7111111,\n offset: 0,\n units: 'degrees',\n },\n 73: {\n field: 'enhanced_speed',\n type: 'uint32',\n scale: 1000,\n offset: 0,\n units: 'm/s',\n },\n 78: {\n field: 'enhanced_altitude',\n type: 'uint32',\n scale: 5,\n offset: -500,\n units: 'm',\n },\n 81: {\n field: 'battery_soc',\n type: 'uint8',\n scale: 2,\n offset: 0,\n units: 'percent',\n },\n 82: {\n field: 'motor_power',\n type: 'uint16',\n scale: null,\n offset: 0,\n units: 'watts',\n },\n 83: {\n field: 'vertical_ratio',\n type: 'uint16',\n scale: 100,\n offset: 0,\n units: 'percent',\n },\n 84: {\n field: 'stance_time_balance',\n type: 'uint16',\n scale: 100,\n offset: 0,\n units: 'percent',\n },\n 85: {\n field: 'step_length',\n type: 'uint16',\n scale: 10,\n offset: 0,\n units: 'mm',\n },\n 91: {\n field: 'absolute_pressure',\n type: 'uint32',\n scale: null,\n offset: 0,\n units: 'Pa',\n },\n 92: {\n field: 'depth',\n type: 'uint32',\n scale: null,\n offset: 0,\n units: 'm',\n },\n 93: {\n field: 'next_stop_depth',\n type: 'uint32',\n scale: null,\n offset: 0,\n units: 'm',\n },\n 94: {\n field: 'next_stop_time',\n type: 'uint32',\n scale: null,\n offset: 0,\n units: 's',\n },\n 95: {\n field: 'time_to_surface',\n type: 'uint32',\n scale: null,\n offset: 0,\n units: 's',\n },\n 96: {\n field: 'ndl_time',\n type: 'uint32',\n scale: null,\n offset: 0,\n units: 's',\n },\n 97: {\n field: 'cns_load',\n type: 'uint8',\n scale: null,\n offset: 0,\n units: 'percent',\n },\n 98: {\n field: 'n2_load',\n type: 'uint16',\n scale: null,\n offset: 0,\n units: 'percent',\n },\n },\n 285: {\n name: 'jump',\n 253: { field: 'timestamp', type: 'date_time', scale: null, offset: 0, units: 's' },\n 0: { field: 'distance', type: 'float32', scale: null, offset: 0, units: 'm' },\n 1: { field: 'height', type: 'float32', scale: null, offset: 0, units: 'm' },\n 2: { field: 'rotations', type: 'uint8', scale: null, offset: 0, units: '' },\n 3: { field: 'hang_time', type: 'float32', scale: null, offset: 0, units: 's' },\n 4: { field: 'score', type: 'float32', scale: null, offset: 0, units: '' },\n 5: { field: 'position_lat', type: 'sint32', scale: null, offset: 0, units: 'semicircles' },\n 6: { field: 'position_long', type: 'sint32', scale: null, offset: 0, units: 'semicircles' },\n 7: { field: 'speed', type: 'uint16', scale: 1000, offset: 0, units: 'm/s' },\n 8: { field: 'enhanced_speed', type: 'uint32', scale: 1000, offset: 0, units: 'm/s' },\n },\n 21: {\n name: 'event',\n 253: {\n field: 'timestamp',\n type: 'date_time',\n scale: null,\n offset: 0,\n units: 's',\n },\n 0: { field: 'event', type: 'event', scale: null, offset: 0, units: '' },\n 1: {\n field: 'event_type',\n type: 'event_type',\n scale: null,\n offset: 0,\n units: '',\n },\n 2: { field: 'data16', type: 'uint16', scale: null, offset: 0, units: '' },\n 3: { field: 'data', type: 'uint32', scale: null, offset: 0, units: '' },\n 4: {\n field: 'event_group',\n type: 'uint8',\n scale: null,\n offset: 0,\n units: '',\n },\n 7: { field: 'score', type: 'uint16', scale: null, offset: 0, units: '' },\n 8: {\n field: 'opponent_score',\n type: 'uint16',\n scale: null,\n offset: 0,\n units: '',\n },\n 9: {\n field: 'front_gear_num',\n type: 'uint8z',\n scale: null,\n offset: 0,\n units: '',\n },\n 10: {\n field: 'front_gear',\n type: 'uint8z',\n scale: null,\n offset: 0,\n units: '',\n },\n 11: {\n field: 'rear_gear_num',\n type: 'uint8z',\n scale: null,\n offset: 0,\n units: '',\n },\n 12: {\n field: 'rear_gear',\n type: 'uint8z',\n scale: null,\n offset: 0,\n units: '',\n },\n 13: {\n field: 'device_index',\n type: 'device_index',\n scale: null,\n offset: 0,\n units: '',\n },\n },\n 23: {\n name: 'device_info',\n 253: {\n field: 'timestamp',\n type: 'date_time',\n scale: null,\n offset: 0,\n units: 's',\n },\n 0: {\n field: 'device_index',\n type: 'uint8',\n scale: null,\n offset: 0,\n units: '',\n },\n 1: {\n field: 'device_type',\n type: 'uint8',\n scale: null,\n offset: 0,\n units: '',\n },\n 2: {\n field: 'manufacturer',\n type: 'manufacturer',\n scale: null,\n offset: 0,\n units: '',\n },\n 3: {\n field: 'serial_number',\n type: 'uint32z',\n scale: null,\n offset: 0,\n units: '',\n },\n 4: {\n field: 'product',\n type: 'uint16',\n scale: null,\n offset: 0,\n units: '',\n },\n 5: {\n field: 'software_version',\n type: 'uint16',\n scale: 100,\n offset: 0,\n units: '',\n },\n 6: {\n field: 'hardware_version',\n type: 'uint8',\n scale: null,\n offset: 0,\n units: '',\n },\n 7: {\n field: 'cum_operating_time',\n type: 'uint32',\n scale: null,\n offset: 0,\n units: 's',\n },\n 10: {\n field: 'battery_voltage',\n type: 'uint16',\n scale: 256,\n offset: 0,\n units: 'V',\n },\n 32: {\n field: 'battery_level',\n type: 'uint8',\n scale: null,\n offset: 0,\n units: 'percent',\n },\n 11: {\n field: 'battery_status',\n type: 'battery_status',\n scale: null,\n offset: 0,\n units: '',\n },\n 18: {\n field: 'sensor_position',\n type: 'body_location',\n scale: null,\n offset: 0,\n units: '',\n },\n 19: {\n field: 'descriptor',\n type: 'string',\n scale: null,\n offset: 0,\n units: '',\n },\n 20: {\n field: 'ant_transmission_type',\n type: 'uint8z',\n scale: null,\n offset: 0,\n units: '',\n },\n 21: {\n field: 'ant_device_number',\n type: 'uint16z',\n scale: null,\n offset: 0,\n units: '',\n },\n 22: {\n field: 'ant_network',\n type: 'ant_network',\n scale: null,\n offset: 0,\n units: '',\n },\n 24: {\n field: 'ant_id',\n type: 'uint32z',\n scale: null,\n offset: 0,\n units: '',\n },\n 25: {\n field: 'source_type',\n type: 'source_type',\n scale: null,\n offset: 0,\n units: '',\n },\n 27: {\n field: 'product_name',\n type: 'string',\n scale: null,\n offset: 0,\n units: '',\n },\n },\n 26: {\n name: 'workout',\n 4: { field: 'sport', type: 'sport', scale: null, offset: 0, units: '' },\n 5: {\n field: 'capabilities',\n type: 'workout_capabilities',\n scale: null,\n offset: 0,\n units: '',\n },\n 6: {\n field: 'num_valid_steps',\n type: 'uint16',\n scale: null,\n offset: 0,\n units: '',\n },\n 8: {\n field: 'wkt_name',\n type: 'string',\n scale: null,\n offset: 0,\n units: '',\n },\n },\n 27: {\n name: 'workout_step',\n 254: {\n field: 'message_index',\n type: 'message_index',\n scale: null,\n offset: 0,\n units: '',\n },\n 0: {\n field: 'wkt_step_name',\n type: 'string',\n scale: null,\n offset: 0,\n units: '',\n },\n 1: {\n field: 'duration_type',\n type: 'wkt_step_duration',\n scale: null,\n offset: 0,\n units: '',\n },\n 2: {\n field: 'duration_value',\n type: 'uint32',\n scale: null,\n offset: 0,\n units: '',\n },\n 3: {\n field: 'target_type',\n type: 'wkt_step_target',\n scale: null,\n offset: 0,\n units: '',\n },\n 4: {\n field: 'target_value',\n type: 'uint32',\n scale: null,\n offset: 0,\n units: '',\n },\n 5: {\n field: 'custom_target_value_low',\n type: 'uint32',\n scale: null,\n offset: 0,\n units: '',\n },\n 6: {\n field: 'custom_target_value_high',\n type: 'uint32',\n scale: null,\n offset: 0,\n units: '',\n },\n 7: {\n field: 'intensity',\n type: 'intensity',\n scale: null,\n offset: 0,\n units: '',\n },\n },\n 30: {\n name: 'weight_scale',\n 253: {\n field: 'timestamp',\n type: 'date_time',\n scale: null,\n offset: 0,\n units: 's',\n },\n 0: {\n field: 'weight',\n type: 'weight',\n scale: 100,\n offset: 0,\n units: 'kg',\n },\n 1: {\n field: 'percent_fat',\n type: 'uint16',\n scale: 100,\n offset: 0,\n units: '%',\n },\n 2: {\n field: 'percent_hydration',\n type: 'uint16',\n scale: 100,\n offset: 0,\n units: '%',\n },\n 3: {\n field: 'visceral_fat_mass',\n type: 'uint16',\n scale: 100,\n offset: 0,\n units: 'kg',\n },\n 4: {\n field: 'bone_mass',\n type: 'uint16',\n scale: 100,\n offset: 0,\n units: 'kg',\n },\n 5: {\n field: 'muscle_mass',\n type: 'uint16',\n scale: 100,\n offset: 0,\n units: 'kg',\n },\n 7: {\n field: 'basal_met',\n type: 'uint16',\n scale: 4,\n offset: 0,\n units: 'kcal/day',\n },\n 8: {\n field: 'physique_rating',\n type: 'uint8',\n scale: null,\n offset: 0,\n units: '',\n },\n 9: {\n field: 'active_met',\n type: 'uint16',\n scale: 4,\n offset: 0,\n units: 'kcal/day',\n },\n 10: {\n field: 'metabolic_age',\n type: 'uint8',\n scale: null,\n offset: 0,\n units: 'years',\n },\n 11: {\n field: 'visceral_fat_rating',\n type: 'uint8',\n scale: null,\n offset: 0,\n units: '',\n },\n 12: {\n field: 'user_profile_index',\n type: 'message_index',\n scale: null,\n offset: 0,\n units: '',\n },\n },\n 31: {\n name: 'course',\n 4: { field: 'sport', type: 'sport', scale: null, offset: 0, units: '' },\n 5: { field: 'name', type: 'string', scale: null, offset: 0, units: '' },\n 6: {\n field: 'capabilities',\n type: 'course_capabilities',\n scale: null,\n offset: 0,\n units: '',\n },\n },\n 32: {\n name: 'course_point',\n 254: {\n field: 'message_index',\n type: 'message_index',\n scale: null,\n offset: 0,\n units: '',\n },\n 1: {\n field: 'timestamp',\n type: 'date_time',\n scale: null,\n offset: 0,\n units: '',\n },\n 2: {\n field: 'position_lat',\n type: 'sint32',\n scale: null,\n offset: 0,\n units: 'semicircles',\n },\n 3: {\n field: 'position_long',\n type: 'sint32',\n scale: null,\n offset: 0,\n units: 'semicircles',\n },\n 4: {\n field: 'distance',\n type: 'uint32',\n scale: 100,\n offset: 0,\n units: 'm',\n },\n 5: {\n field: 'type',\n type: 'course_point',\n scale: null,\n offset: 0,\n units: '',\n },\n 6: { field: 'name', type: 'string', scale: null, offset: 0, units: '' },\n 8: { field: 'favorite', type: 'bool', scale: null, offset: 0, units: '' },\n },\n 33: {\n name: 'totals',\n 254: {\n field: 'message_index',\n type: 'message_index',\n scale: null,\n offset: 0,\n units: '',\n },\n 253: {\n field: 'timestamp',\n type: 'date_time',\n scale: null,\n offset: 0,\n units: 's',\n },\n 0: {\n field: 'timer_time',\n type: 'uint32',\n scale: null,\n offset: 0,\n units: 's',\n },\n 1: {\n field: 'distance',\n type: 'uint32',\n scale: null,\n offset: 0,\n units: 'm',\n },\n 2: {\n field: 'calories',\n type: 'uint32',\n scale: null,\n offset: 0,\n units: 'kcal',\n },\n 3: { field: 'sport', type: 'sport', scale: null, offset: 0, units: '' },\n 4: {\n field: 'elapsed_time',\n type: 'uint32',\n scale: null,\n offset: 0,\n units: 's',\n },\n 5: {\n field: 'sessions',\n type: 'uint16',\n scale: null,\n offset: 0,\n units: '',\n },\n 6: {\n field: 'active_time',\n type: 'uint32',\n scale: null,\n offset: 0,\n units: 's',\n },\n 9: {\n field: 'sport_index',\n type: 'uint8',\n scale: null,\n offset: 0,\n units: '',\n },\n },\n 34: {\n name: 'activity',\n 253: {\n field: 'timestamp',\n type: 'date_time',\n scale: null,\n offset: 0,\n units: '',\n },\n 0: {\n field: 'total_timer_time',\n type: 'uint32',\n scale: 1000,\n offset: 0,\n units: 's',\n },\n 1: {\n field: 'num_sessions',\n type: 'uint16',\n scale: null,\n offset: 0,\n units: '',\n },\n 2: { field: 'type', type: 'activity', scale: null, offset: 0, units: '' },\n 3: { field: 'event', type: 'event', scale: null, offset: 0, units: '' },\n 4: {\n field: 'event_type',\n type: 'event_type',\n scale: null,\n offset: 0,\n units: '',\n },\n 5: {\n field: 'local_timestamp',\n type: 'local_date_time',\n scale: null,\n offset: 0,\n units: '',\n },\n 6: {\n field: 'event_group',\n type: 'uint8',\n scale: null,\n offset: 0,\n units: '',\n },\n },\n 35: {\n name: 'software',\n 254: {\n field: 'message_index',\n type: 'message_index',\n scale: null,\n offset: 0,\n units: '',\n },\n 3: { field: 'version', type: 'uint16', scale: 100, offset: 0, units: '' },\n 5: {\n field: 'part_number',\n type: 'string',\n scale: null,\n offset: 0,\n units: '',\n },\n },\n 37: {\n name: 'file_capabilities',\n 254: {\n field: 'message_index',\n type: 'message_index',\n scale: null,\n offset: 0,\n units: '',\n },\n 0: { field: 'type', type: 'file', scale: null, offset: 0, units: '' },\n 1: {\n field: 'flags',\n type: 'file_flags',\n scale: null,\n offset: 0,\n units: '',\n },\n 2: {\n field: 'directory',\n type: 'string',\n scale: null,\n offset: 0,\n units: '',\n },\n 3: {\n field: 'max_count',\n type: 'uint16',\n scale: null,\n offset: 0,\n units: '',\n },\n 4: {\n field: 'max_size',\n type: 'uint32',\n scale: null,\n offset: 0,\n units: 'bytes',\n },\n },\n 38: {\n name: 'mesg_capabilities',\n 254: {\n field: 'message_index',\n type: 'message_index',\n scale: null,\n offset: 0,\n units: '',\n },\n 0: { field: 'file', type: 'file', scale: null, offset: 0, units: '' },\n 1: {\n field: 'mesg_num',\n type: 'mesg_num',\n scale: null,\n offset: 0,\n units: '',\n },\n 2: {\n field: 'count_type',\n type: 'mesg_count',\n scale: null,\n offset: 0,\n units: '',\n },\n 3: { field: 'count', type: 'uint16', scale: null, offset: 0, units: '' },\n },\n 39: {\n name: 'field_capabilities',\n 254: {\n field: 'message_index',\n type: 'message_index',\n scale: null,\n offset: 0,\n units: '',\n },\n 0: { field: 'file', type: 'file', scale: null, offset: 0, units: '' },\n 1: {\n field: 'mesg_num',\n type: 'mesg_num',\n scale: null,\n offset: 0,\n units: '',\n },\n 2: {\n field: 'field_num',\n type: 'uint8',\n scale: null,\n offset: 0,\n units: '',\n },\n 3: { field: 'count', type: 'uint16', scale: null, offset: 0, units: '' },\n },\n 49: {\n name: 'file_creator',\n 0: {\n field: 'software_version',\n type: 'uint16',\n scale: null,\n offset: 0,\n units: '',\n },\n 1: {\n field: 'hardware_version',\n type: 'uint8',\n scale: null,\n offset: 0,\n units: '',\n },\n },\n 51: {\n name: 'blood_pressure',\n 253: {\n field: 'timestamp',\n type: 'date_time',\n scale: null,\n offset: 0,\n units: 's',\n },\n 0: {\n field: 'systolic_pressure',\n type: 'uint16',\n scale: null,\n offset: 0,\n units: 'mmHg',\n },\n 1: {\n field: 'diastolic_pressure',\n type: 'uint16',\n scale: null,\n offset: 0,\n units: 'mmHg',\n },\n 2: {\n field: 'mean_arterial_pressure',\n type: 'uint16',\n scale: null,\n offset: 0,\n units: 'mmHg',\n },\n 3: {\n field: 'map_3_sample_mean',\n type: 'uint16',\n scale: null,\n offset: 0,\n units: 'mmHg',\n },\n 4: {\n field: 'map_morning_values',\n type: 'uint16',\n scale: null,\n offset: 0,\n units: 'mmHg',\n },\n 5: {\n field: 'map_evening_values',\n type: 'uint16',\n scale: null,\n offset: 0,\n units: 'mmHg',\n },\n 6: {\n field: 'heart_rate',\n type: 'uint8',\n scale: null,\n offset: 0,\n units: 'bpm',\n },\n 7: {\n field: 'heart_rate_type',\n type: 'hr_type',\n scale: null,\n offset: 0,\n units: '',\n },\n 8: {\n field: 'status',\n type: 'bp_status',\n scale: null,\n offset: 0,\n units: '',\n },\n 9: {\n field: 'user_profile_index',\n type: 'message_index',\n scale: null,\n offset: 0,\n units: '',\n },\n },\n 55: {\n name: 'monitoring',\n 253: {\n field: 'timestamp',\n type: 'uint32',\n scale: null,\n offset: 0,\n units: '',\n },\n 0: {\n field: 'device_index',\n type: 'device_index',\n scale: null,\n offset: 0,\n units: '',\n },\n 1: {\n field: 'calories',\n type: 'uint16',\n scale: null,\n offset: 0,\n units: 'kcal',\n },\n 2: {\n field: 'distance',\n type: 'float32',\n scale: null,\n offset: 0,\n units: 'm',\n },\n 3: {\n field: 'cycles',\n type: 'float32',\n scale: null,\n offset: 0,\n units: 'cycles',\n },\n 4: {\n field: 'active_time',\n type: 'float32',\n scale: null,\n offset: 0,\n units: 's',\n },\n 5: {\n field: 'activity_type',\n type: 'activity_type',\n scale: null,\n offset: 0,\n units: '',\n },\n 6: {\n field: 'activity_subtype',\n type: 'activity_subtype',\n scale: null,\n offset: 0,\n units: '',\n },\n 7: {\n field: 'activity_level',\n type: 'activity_level',\n scale: null,\n offset: 0,\n units: 's',\n },\n 8: {\n field: 'distance16',\n type: 'uint16',\n scale: null,\n offset: 0,\n units: 'm',\n },\n 9: {\n field: 'cycles16',\n type: 'uint16',\n scale: null,\n offset: 0,\n units: 'cycles',\n },\n 10: {\n field: 'active_time16',\n type: 'uint16',\n scale: null,\n offset: 0,\n units: '',\n },\n 11: {\n field: 'local_timestamp',\n type: 'uint32',\n scale: null,\n offset: 0,\n units: '',\n },\n 12: {\n field: 'temperature',\n type: 'float32',\n scale: null,\n offset: 0,\n units: 'C',\n },\n 14: {\n field: 'temperature_min',\n type: 'float32',\n scale: null,\n offset: 0,\n units: 'C',\n },\n 15: {\n field: 'temperature_max',\n type: 'float32',\n scale: null,\n offset: 0,\n units: 'C',\n },\n 16: {\n field: 'activity_time',\n type: 'int32',\n scale: null,\n offset: 0,\n units: '',\n },\n 19: {\n field: 'active_calories',\n type: 'uint16',\n scale: null,\n offset: 0,\n units: 'kcal',\n },\n 24: {\n field: 'current_activity_type_intensity',\n type: 'uint8',\n scale: null,\n offset: 0,\n units: '',\n },\n 25: {\n field: 'timestamp_min8',\n type: 'uint8',\n scale: null,\n offset: 0,\n units: '',\n },\n 26: {\n field: 'timestamp16',\n type: 'uint16',\n scale: null,\n offset: 0,\n units: '',\n },\n 27: {\n field: 'heart_rate',\n type: 'uint8',\n scale: null,\n offset: 0,\n units: 'bpm',\n },\n 28: {\n field: 'intensity',\n type: 'uint8',\n scale: null,\n offset: 0,\n units: '',\n },\n 29: {\n field: 'duration_min',\n type: 'uint16',\n scale: null,\n offset: 0,\n units: '',\n },\n 30: {\n field: 'duration',\n type: 'uint32',\n scale: null,\n offset: 0,\n units: '',\n },\n 31: {\n field: 'ascent',\n type: 'float32',\n scale: null,\n offset: 0,\n units: 'm',\n },\n 32: {\n field: 'descent',\n type: 'float32',\n scale: null,\n offset: 0,\n units: 'm',\n },\n 33: {\n field: 'moderate_activity_minutes',\n type: 'uint16',\n scale: null,\n offset: 0,\n units: '',\n },\n 34: {\n field: 'vigorous_activity_inutes',\n type: 'uint16',\n scale: null,\n offset: 0,\n units: '',\n },\n },\n 78: {\n name: 'hrv',\n 0: {\n field: 'time',\n type: 'uint16_array',\n scale: 1000,\n offset: 0,\n units: 's',\n },\n },\n 101: {\n name: 'length',\n 254: {\n field: 'message_index',\n type: 'message_index',\n scale: null,\n offset: 0,\n units: '',\n },\n 253: {\n field: 'timestamp',\n type: 'date_time',\n scale: null,\n offset: 0,\n units: 's',\n },\n 0: { field: 'event', type: 'event', scale: null, offset: 0, units: '' },\n 1: {\n field: 'event_type',\n type: 'event_type',\n scale: null,\n offset: 0,\n units: '',\n },\n 2: {\n field: 'start_time',\n type: 'date_time',\n scale: null,\n offset: 0,\n units: '',\n },\n 3: {\n field: 'total_elapsed_time',\n type: 'uint32',\n scale: 1000,\n offset: 0,\n units: 's',\n },\n 4: {\n field: 'total_timer_time',\n type: 'uint32',\n scale: 1000,\n offset: 0,\n units: 's',\n },\n 5: {\n field: 'total_strokes',\n type: 'uint16',\n scale: null,\n offset: 0,\n units: 'strokes',\n },\n 6: {\n field: 'avg_speed',\n type: 'uint16',\n scale: 1000,\n offset: 0,\n units: 'm/s',\n },\n 7: {\n field: 'swim_stroke',\n type: 'swim_stroke',\n scale: null,\n offset: 0,\n units: 'swim_stroke',\n },\n 9: {\n field: 'avg_swimming_cadence',\n type: 'uint8',\n scale: null,\n offset: 0,\n units: 'strokes/min',\n },\n 10: {\n field: 'event_group',\n type: 'uint8',\n scale: null,\n offset: 0,\n units: '',\n },\n 11: {\n field: 'total_calories',\n type: 'uint16',\n scale: null,\n offset: 0,\n units: 'kcal',\n },\n 12: {\n field: 'length_type',\n type: 'length_type',\n scale: null,\n offset: 0,\n units: 'length_type',\n },\n 18: {\n field: 'player_score',\n type: 'uint16',\n scale: null,\n offset: 0,\n units: '',\n },\n 19: {\n field: 'opponent_score',\n type: 'uint16',\n scale: null,\n offset: 0,\n units: '',\n },\n 20: {\n field: 'stroke_count',\n type: 'uint16',\n scale: null,\n offset: 0,\n units: 'counts',\n },\n 21: {\n field: 'zone_count',\n type: 'uint16',\n scale: null,\n offset: 0,\n units: 'counts',\n },\n },\n 103: {\n name: 'monitoring_info',\n 253: {\n field: 'timestamp',\n type: 'date_time',\n scale: null,\n offset: 0,\n units: '',\n },\n 0: {\n field: 'local_timestamp',\n type: 'uint32',\n scale: null,\n offset: 0,\n units: '',\n },\n 1: {\n field: 'activity_type',\n type: 'activity_type',\n scale: null,\n offset: 0,\n units: '',\n },\n 3: {\n field: 'cycles_to_distance',\n type: 'float32',\n scale: null,\n offset: 0,\n units: 'cycles',\n },\n 4: {\n field: 'cycles_to_calories',\n type: 'float32',\n scale: null,\n offset: 0,\n units: 'kcal',\n },\n 5: {\n field: 'resting_metabolic_rate',\n type: 'uint16',\n scale: null,\n offset: 0,\n units: '',\n },\n },\n 108: {\n name: 'o_hr_settings',\n 253: {\n field: 'timestamp',\n type: 'date_time',\n scale: null,\n offset: 0,\n units: '',\n },\n 0: { field: 'enabled', type: 'byte', scale: null, offset: 0, units: '' },\n },\n 140: {\n name: 'activity_metrics',\n 1: {\n field: 'new_max_heart_rate',\n type: 'uint8',\n scale: null,\n offset: 0,\n units: 'bpm',\n },\n 4: {\n field: 'aerobic_training_effect',\n type: 'uint8',\n scale: 10,\n offset: 0,\n units: '',\n },\n 7: {\n field: 'vo2_max',\n type: 'uint32',\n scale: 65536 / 3.5,\n offset: 0,\n units: 'ml/kg/min',\n },\n 9: {\n field: 'recovery_time',\n type: 'uint16',\n scale: null,\n offset: 0,\n units: 'min',\n },\n 11: {\n field: 'sport',\n type: 'sport',\n scale: null,\n offset: 0,\n units: '',\n },\n 20: {\n field: 'anaerobic_training_effect',\n type: 'uint8',\n scale: 10,\n offset: 0,\n units: '',\n },\n 29: {\n field: 'first_vo2_max',\n type: 'uint32',\n scale: 65536 / 3.5,\n offset: 0,\n units: 'ml/kg/min',\n },\n 41: {\n field: 'primary_benefit',\n type: 'uint8',\n scale: null,\n offset: 0,\n units: '',\n },\n 60: {\n field: 'total_ascent',\n type: 'uint16',\n scale: null,\n offset: 0,\n units: 'm',\n },\n 61: {\n field: 'total_descent',\n type: 'uint16',\n scale: null,\n offset: 0,\n units: 'm',\n },\n 62: {\n field: 'avg_power',\n type: 'uint16',\n scale: null,\n offset: 0,\n units: 'watts',\n },\n 63: {\n field: 'avg_heart_rate',\n type: 'uint8',\n scale: null,\n offset: 0,\n units: 'bpm',\n },\n },\n 206: {\n name: 'field_description',\n 0: {\n field: 'developer_data_index',\n type: 'uint8',\n scale: null,\n offset: 0,\n units: '',\n },\n 1: {\n field: 'field_definition_number',\n type: 'uint8',\n scale: null,\n offset: 0,\n units: '',\n },\n 2: {\n field: 'fit_base_type_id',\n type: 'uint8',\n scale: null,\n offset: 0,\n units: '',\n },\n 3: {\n field: 'field_name',\n type: 'string',\n scale: null,\n offset: 0,\n units: '',\n },\n // 4: { field: 'array', type: 'uint8', scale: null, offset: 0, units: '' },\n // 5: { field: 'components', type: 'string', scale: null, offset: 0, units: '' },\n 6: { field: 'scale', type: 'uint8', scale: null, offset: 0, units: '' },\n 7: { field: 'offset', type: 'sint8', scale: null, offset: 0, units: '' },\n 8: { field: 'units', type: 'string', scale: null, offset: 0, units: '' },\n // 9: { field: 'bits', type: 'string', scale: null, offset: 0, units: '' },\n // 10: { field: 'accumulate', type: 'string', scale: null, offset: 0, units: '' },\n // 13: { field: 'fit_base_unit_id', type: 'uint16', scale: null, offset: 0, units: '' },\n // 14: { field: 'native_mesg_num', type: 'mesg_num', scale: null, offset: 0, units: '' },\n 15: {\n field: 'native_field_num',\n type: 'uint8',\n scale: null,\n offset: 0,\n units: '',\n },\n },\n 227: {\n name: 'stress_level',\n 0: {\n field: 'stress_level_value',\n type: 'uint16',\n scale: null,\n offset: 0,\n units: '',\n },\n 1: {\n field: 'stress_level_time',\n type: 'date_time',\n scale: null,\n offset: 0,\n units: 's',\n },\n 2: {\n field: 'field_two',\n type: 'sint8',\n scale: null,\n offset: 0,\n units: '',\n },\n 3: {\n field: 'body_battery',\n type: 'uint8',\n scale: null,\n offset: 0,\n units: '',\n },\n 4: {\n field: 'field_four',\n type: 'uint8',\n scale: null,\n offset: 0,\n units: '',\n },\n },\n 207: {\n name: 'developer_data_id',\n 0: {\n field: 'developer_id',\n type: 'uint8',\n scale: null,\n offset: 0,\n units: '',\n },\n 1: {\n field: 'application_id',\n type: 'byte_array',\n scale: null,\n offset: 0,\n units: '',\n },\n 2: {\n field: 'manufacturer_id',\n type: 'manufacturer',\n scale: null,\n offset: 0,\n units: '',\n },\n 3: {\n field: 'developer_data_index',\n type: 'uint8',\n scale: null,\n offset: 0,\n units: '',\n },\n 4: {\n field: 'application_version',\n type: 'uint8',\n scale: null,\n offset: 0,\n units: '',\n },\n },\n 258: {\n name: 'dive_settings',\n 254: {\n field: 'message_index',\n type: 'message_index',\n scale: null,\n offset: 0,\n units: '',\n },\n 0: { field: 'name', type: 'string', scale: null, offset: 0, units: '' },\n 1: {\n field: 'model',\n type: 'tissue_model_type',\n scale: null,\n offset: 0,\n units: '',\n },\n 2: {\n field: 'gf_low',\n type: 'uint8',\n scale: null,\n offset: 0,\n units: 'percent',\n },\n 3: {\n field: 'gf_high',\n type: 'uint8',\n scale: null,\n offset: 0,\n units: 'percent',\n },\n 4: {\n field: 'water_type',\n type: 'water_type',\n scale: null,\n offset: 0,\n units: '',\n },\n 5: {\n field: 'water_density',\n type: 'float32',\n scale: null,\n offset: 0,\n units: 'kg/m^3',\n },\n 6: {\n field: 'po2_warn',\n type: 'uint8',\n scale: null,\n offset: 0,\n units: 'percent',\n },\n 7: {\n field: 'po2_critical',\n type: 'uint8',\n scale: null,\n offset: 0,\n units: 'percent',\n },\n 8: {\n field: 'po2_deco',\n type: 'uint8',\n scale: null,\n offset: 0,\n units: 'percent',\n },\n 9: {\n field: 'safety_stop_enabled',\n type: 'bool',\n scale: null,\n offset: 0,\n units: '',\n },\n 10: {\n field: 'bottom_depth',\n type: 'float32',\n scale: null,\n offset: 0,\n units: '',\n },\n 11: {\n field: 'bottom_time',\n type: 'uint32',\n scale: null,\n offset: 0,\n units: '',\n },\n 12: {\n field: 'apnea_countdown_enabled',\n type: 'bool',\n scale: null,\n offset: 0,\n units: '',\n },\n 13: {\n field: 'apnea_countdown_time',\n type: 'uint32',\n scale: null,\n offset: 0,\n units: '',\n },\n 14: {\n field: 'backlight_mode',\n type: 'dive_backlight_mode',\n scale: null,\n offset: 0,\n units: '',\n },\n 15: {\n field: 'backlight_brightness',\n type: 'uint8',\n scale: null,\n offset: 0,\n units: '',\n },\n 16: {\n field: 'backlight_timeout',\n type: 'backlight_timeout',\n scale: null,\n offset: 0,\n units: '',\n },\n 17: {\n field: 'repeat_dive_time',\n type: 'uint16',\n scale: null,\n offset: 0,\n units: 's',\n },\n 18: {\n field: 'safety_stop_time',\n type: 'uint16',\n scale: null,\n offset: 0,\n units: 's',\n },\n 19: {\n field: 'heart_rate_source_type',\n type: 'source_type',\n scale: null,\n offset: 0,\n units: '',\n },\n 20: {\n field: 'heart_rate_source',\n type: 'uint8',\n scale: null,\n offset: 0,\n units: '',\n },\n },\n 259: {\n name: 'dive_gas',\n 254: {\n field: 'message_index',\n type: 'message_index',\n scale: null,\n offset: 0,\n units: '',\n },\n 0: {\n field: 'helium_content',\n type: 'uint8',\n scale: null,\n offset: 0,\n units: 'percent',\n },\n 1: {\n field: 'oxygen_content',\n type: 'uint8',\n scale: null,\n offset: 0,\n units: 'percent',\n },\n 2: {\n field: 'status',\n type: 'dive_gas_status',\n scale: null,\n offset: 0,\n units: '',\n },\n },\n 262: {\n name: 'dive_alarm',\n 254: {\n field: 'message_index',\n type: 'message_index',\n scale: null,\n offset: 0,\n units: '',\n },\n 0: { field: 'depth', type: 'uint32', scale: null, offset: 0, units: 'm' },\n 1: { field: 'time', type: 'sint32', scale: null, offset: 0, units: 's' },\n 2: { field: 'enabled', type: 'bool', scale: null, offset: 0, units: '' },\n 3: {\n field: 'alarm_type',\n type: 'dive_alarm_type',\n scale: null,\n offset: 0,\n units: '',\n },\n 4: { field: 'sound', type: 'tone', scale: null, offset: 0, units: '' },\n 5: {\n field: 'dive_types',\n type: 'sub_sport',\n scale: null,\n offset: 0,\n units: '',\n },\n },\n 268: {\n name: 'dive_summary',\n 253: {\n field: 'timestamp',\n type: 'date_time',\n scale: null,\n offset: 0,\n units: 's',\n },\n 0: {\n field: 'reference_mesg',\n type: 'mesg_num',\n scale: null,\n offset: 0,\n units: '',\n },\n 1: {\n field: 'reference_index',\n type: 'message_index',\n scale: null,\n offset: 0,\n units: '',\n },\n 2: {\n field: 'avg_depth',\n type: 'uint32',\n scale: null,\n offset: 0,\n units: 'm',\n },\n 3: {\n field: 'max_depth',\n type: 'uint32',\n scale: null,\n offset: 0,\n units: 'm',\n },\n 4: {\n field: 'surface_interval',\n type: 'uint32',\n scale: null,\n offset: 0,\n units: 's',\n },\n 5: {\n field: 'start_cns',\n type: 'uint8',\n scale: null,\n offset: 0,\n units: 'percent',\n },\n 6: {\n field: 'end_cns',\n type: 'uint8',\n scale: null,\n offset: 0,\n units: 'percent',\n },\n 7: {\n field: 'start_n2',\n type: 'uint16',\n scale: null,\n offset: 0,\n units: 'percent',\n },\n 8: {\n field: 'end_n2',\n type: 'uint16',\n scale: null,\n offset: 0,\n units: 'percent',\n },\n 9: {\n field: 'o2_toxicity',\n type: 'uint16',\n scale: null,\n offset: 0,\n units: 'OTUs',\n },\n 10: {\n field: 'dive_number',\n type: 'uint32',\n scale: null,\n offset: 0,\n units: '',\n },\n 11: {\n field: 'bottom_time',\n type: 'uint32',\n scale: null,\n offset: 0,\n units: 's',\n },\n },\n 319: {\n name: 'tank_update',\n 253: {\n field: 'timestamp',\n type: 'date_time',\n scale: null,\n offset: 0,\n units: 's',\n },\n 0: { field: 'sensor', type: 'uint32', scale: null, offset: 0, units: '' },\n 1: {\n field: 'pressure',\n type: 'uint16',\n scale: null,\n offset: 0,\n units: 'cbar',\n },\n },\n 323: {\n name: 'tank_summary',\n 0: { field: 'sensor', type: 'uint32', scale: null, offset: 0, units: '' },\n 1: {\n field: 'start_pressure',\n type: 'uint16',\n scale: null,\n offset: 0,\n units: 'cbar',\n },\n 2: {\n field: 'end_pressure',\n type: 'uint16',\n scale: null,\n offset: 0,\n units: 'cbar',\n },\n 3: {\n field: 'volume_used',\n type: 'uint16',\n scale: null,\n offset: 0,\n units: 'cbar',\n },\n },\n 216: {\n name: 'time_in_zone',\n 253: {\n field: 'timestamp',\n type: 'date_time',\n scale: null,\n offset: 0,\n units: 's',\n },\n 0: {\n field: 'reference_mesg',\n type: 'uint16',\n scale: null,\n offset: 0,\n units: '',\n },\n 1: {\n field: 'reference_index',\n type: 'uint16',\n scale: null,\n offset: 0,\n units: '',\n },\n 2: {\n field: 'time_in_hr_zone',\n type: 'uint32_array',\n scale: 1000,\n offset: 0,\n units: 's',\n },\n 3: {\n field: 'time_in_speed_zone',\n type: 'uint32_array',\n scale: 1000,\n offset: 0,\n units: 's',\n },\n 4: {\n field: 'time_in_power_zone',\n type: 'uint32_array',\n scale: 1000,\n offset: 0,\n units: 's',\n },\n 5: {\n field: 'time_in_power_zone',\n type: 'uint32_array',\n scale: 1000,\n offset: 0,\n units: 's',\n },\n 6: {\n field: 'hr_zone_high_boundary',\n type: 'uint8_array',\n scale: null,\n offset: 0,\n units: 'bpm',\n },\n 7: {\n field: 'speed_zone_high_boundary',\n type: 'uint16_array',\n scale: 1000,\n offset: 0,\n units: 'm/s',\n },\n 8: {\n field: 'power_zone_high_boundary',\n type: 'uint16_array',\n scale: null,\n offset: 0,\n units: 'watts',\n },\n 9: {\n field: 'power_zone_high_boundary',\n type: 'uint16_array',\n scale: null,\n offset: 0,\n units: 'watts',\n },\n 10: {\n field: 'max_heart_rate_deprecated',\n type: 'uint8',\n scale: null,\n offset: 0,\n units: 'bpm',\n },\n 11: {\n field: 'max_heart_rate',\n type: 'uint8',\n scale: null,\n offset: 0,\n units: 'bpm',\n },\n 12: {\n field: 'resting_heart_rate',\n type: 'uint8',\n scale: null,\n offset: 0,\n units: 'bpm',\n },\n 13: {\n field: 'threshold_heart_rate',\n type: 'uint8',\n scale: null,\n offset: 0,\n units: 'bpm',\n },\n 14: {\n field: 'pwr_calc_type',\n type: 'pwr_zone_calc',\n scale: null,\n offset: 0,\n units: '',\n },\n 15: {\n field: 'functional_threshold_power',\n type: 'uint16',\n scale: null,\n offset: 0,\n units: 'watts',\n },\n },\n },\n types: {\n file: {\n 1: 'device',\n 2: 'settings',\n 3: 'sport',\n 4: 'activity',\n 5: 'workout',\n 6: 'course',\n 7: 'schedules',\n 9: 'weight',\n 10: 'totals',\n 11: 'goals',\n 14: 'blood_pressure',\n 15: 'monitoring_a',\n 20: 'activity_summary',\n 28: 'monitoring_daily',\n 32: 'monitoring_b',\n 34: 'segment',\n 35: 'segment_list',\n 40: 'exd_configuration',\n 247: 'mfg_range_min',\n 254: 'mfg_range_max',\n },\n mesg_num: {\n 0: 'file_id',\n 1: 'capabilities',\n 2: 'device_settings',\n 3: 'user_profile',\n 4: 'hrm_profile',\n 5: 'sdm_profile',\n 6: 'bike_profile',\n 7: 'zones_target',\n 8: 'hr_zone',\n 9: 'power_zone',\n 10: 'met_zone',\n 12: 'sport',\n 15: 'goal',\n 18: 'session',\n 19: 'lap',\n 20: 'record',\n 21: 'event',\n 23: 'device_info',\n 26: 'workout',\n 27: 'workout_step',\n 28: 'schedule',\n 30: 'weight_scale',\n 31: 'course',\n 32: 'course_point',\n 33: 'totals',\n 34: 'activity',\n 35: 'software',\n 37: 'file_capabilities',\n 38: 'mesg_capabilities',\n 39: 'field_capabilities',\n 49: 'file_creator',\n 51: 'blood_pressure',\n 53: 'speed_zone',\n 55: 'monitoring',\n 72: 'training_file',\n 78: 'hrv',\n 80: 'ant_rx',\n 81: 'ant_tx',\n 82: 'ant_channel_id',\n 101: 'length',\n 103: 'monitoring_info',\n 105: 'pad',\n 106: 'slave_device',\n 127: 'connectivity',\n 128: 'weather_conditions',\n 129: 'weather_alert',\n 131: 'cadence_zone',\n 132: 'hr',\n 140: 'activity_metrics',\n 142: 'segment_lap',\n 145: 'memo_glob',\n 148: 'segment_id',\n 149: 'segment_leaderboard_entry',\n 150: 'segment_point',\n 151: 'segment_file',\n 158: 'workout_session',\n 159: 'watchface_settings',\n 160: 'gps_metadata',\n 161: 'camera_event',\n 162: 'timestamp_correlation',\n 164: 'gyroscope_data',\n 165: 'accelerometer_data',\n 167: 'three_d_sensor_calibration',\n 169: 'video_frame',\n 174: 'obdii_data',\n 177: 'nmea_sentence',\n 178: 'aviation_attitude',\n 184: 'video',\n 185: 'video_title',\n 186: 'video_description',\n 187: 'video_clip',\n 200: 'exd_screen_configuration',\n 201: 'exd_data_field_configuration',\n 202: 'exd_data_concept_configuration',\n 206: 'field_description',\n 207: 'developer_data_id',\n 208: 'magnetometer_data',\n 209: 'barometer_data',\n 210: 'one_d_sensor_calibration',\n 225: 'set',\n 227: 'stress_level',\n 258: 'dive_settings',\n 259: 'dive_gas',\n 262: 'dive_alarm',\n 264: 'exercise_title',\n 268: 'dive_summary',\n 285: 'jump',\n 317: 'climb_pro',\n 319: 'tank_update',\n 323: 'tank_summary',\n 216: 'time_in_zone',\n 347: 'o_hr_settings',\n 65280: 'mfg_range_min',\n 65534: 'mfg_range_max',\n },\n checksum: {\n 0: 'clear',\n 1: 'ok',\n },\n file_flags: {\n 0: 0,\n 2: 'read',\n 4: 'write',\n 8: 'erase',\n },\n mesg_count: {\n 0: 'num_per_file',\n 1: 'max_per_file',\n 2: 'max_per_file_type',\n },\n date_time: {\n 0: 0,\n 268435456: 'min',\n },\n local_date_time: {\n 0: 0,\n 268435456: 'min',\n },\n message_index: {\n 0: 0,\n 4095: 'mask',\n 28672: 'reserved',\n 32768: 'selected',\n },\n gender: {\n 0: 'female',\n 1: 'male',\n },\n language: {\n 0: 'english',\n 1: 'french',\n 2: 'italian',\n 3: 'german',\n 4: 'spanish',\n 5: 'croatian',\n 6: 'czech',\n 7: 'danish',\n 8: 'dutch',\n 9: 'finnish',\n 10: 'greek',\n 11: 'hungarian',\n 12: 'norwegian',\n 13: 'polish',\n 14: 'portuguese',\n 15: 'slovakian',\n 16: 'slovenian',\n 17: 'swedish',\n 18: 'russian',\n 19: 'turkish',\n 20: 'latvian',\n 21: 'ukrainian',\n 22: 'arabic',\n 23: 'farsi',\n 24: 'bulgarian',\n 25: 'romanian',\n 26: 'chinese',\n 27: 'japanese',\n 28: 'korean',\n 29: 'taiwanese',\n 30: 'thai',\n 31: 'hebrew',\n 32: 'brazilian_portuguese',\n 33: 'indonesian',\n 34: 'malaysian',\n 35: 'vietnamese',\n 36: 'burmese',\n 37: 'mongolian',\n 254: 'custom',\n },\n language_bits_0: {\n 0: 0,\n 1: 'english',\n 2: 'french',\n 4: 'italian',\n 8: 'german',\n 16: 'spanish',\n 32: 'croatian',\n 64: 'czech',\n 128: 'danish',\n },\n language_bits_1: {\n 0: 0,\n 1: 'dutch',\n 2: 'finnish',\n 4: 'greek',\n 8: 'hungarian',\n 16: 'norwegian',\n 32: 'polish',\n 64: 'portuguese',\n 128: 'slovakian',\n },\n language_bits_2: {\n 0: 0,\n 1: 'slovenian',\n 2: 'swedish',\n 4: 'russian',\n 8: 'turkish',\n 16: 'latvian',\n 32: 'ukrainian',\n 64: 'arabic',\n 128: 'farsi',\n },\n language_bits_3: {\n 0: 0,\n 1: 'bulgarian',\n 2: 'romanian',\n 4: 'chinese',\n 8: 'japanese',\n 16: 'korean',\n 32: 'taiwanese',\n 64: 'thai',\n 128: 'hebrew',\n },\n language_bits_4: {\n 0: 0,\n 1: 'brazilian_portuguese',\n 2: 'indonesian',\n 4: 'malaysian',\n 8: 'vietnamese',\n 16: 'burmese',\n 32: 'mongolian',\n },\n time_zone: {\n 0: 'almaty',\n 1: 'bangkok',\n 2: 'bombay',\n 3: 'brasilia',\n 4: 'cairo',\n 5: 'cape_verde_is',\n 6: 'darwin',\n 7: 'eniwetok',\n 8: 'fiji',\n 9: 'hong_kong',\n 10: 'islamabad',\n 11: 'kabul',\n 12: 'magadan',\n 13: 'mid_atlantic',\n 14: 'moscow',\n 15: 'muscat',\n 16: 'newfoundland',\n 17: 'samoa',\n 18: 'sydney',\n 19: 'tehran',\n 20: 'tokyo',\n 21: 'us_alaska',\n 22: 'us_atlantic',\n 23: 'us_central',\n 24: 'us_eastern',\n 25: 'us_hawaii',\n 26: 'us_mountain',\n 27: 'us_pacific',\n 28: 'other',\n 29: 'auckland',\n 30: 'kathmandu',\n 31: 'europe_western_wet',\n 32: 'europe_central_cet',\n 33: 'europe_eastern_eet',\n 34: 'jakarta',\n 35: 'perth',\n 36: 'adelaide',\n 37: 'brisbane',\n 38: 'tasmania',\n 39: 'iceland',\n 40: 'amsterdam',\n 41: 'athens',\n 42: 'barcelona',\n 43: 'berlin',\n 44: 'brussels',\n 45: 'budapest',\n 46: 'copenhagen',\n 47: 'dublin',\n 48: 'helsinki',\n 49: 'lisbon',\n 50: 'london',\n 51: 'madrid',\n 52: 'munich',\n 53: 'oslo',\n 54: 'paris',\n 55: 'prague',\n 56: 'reykjavik',\n 57: 'rome',\n 58: 'stockholm',\n 59: 'vienna',\n 60: 'warsaw',\n 61: 'zurich',\n 62: 'quebec',\n 63: 'ontario',\n 64: 'manitoba',\n 65: 'saskatchewan',\n 66: 'alberta',\n 67: 'british_columbia',\n 68: 'boise',\n 69: 'boston',\n 70: 'chicago',\n 71: 'dallas',\n 72: 'denver',\n 73: 'kansas_city',\n 74: 'las_vegas',\n 75: 'los_angeles',\n 76: 'miami',\n 77: 'minneapolis',\n 78: 'new_york',\n 79: 'new_orleans',\n 80: 'phoenix',\n 81: 'santa_fe',\n 82: 'seattle',\n 83: 'washington_dc',\n 84: 'us_arizona',\n 85: 'chita',\n 86: 'ekaterinburg',\n 87: 'irkutsk',\n 88: 'kaliningrad',\n 89: 'krasnoyarsk',\n 90: 'novosibirsk',\n 91: 'petropavlovsk_kamchatskiy',\n 92: 'samara',\n 93: 'vladivostok',\n 94: 'mexico_central',\n 95: 'mexico_mountain',\n 96: 'mexico_pacific',\n 97: 'cape_town',\n 98: 'winkhoek',\n 99: 'lagos',\n 100: 'riyahd',\n 101: 'venezuela',\n 102: 'australia_lh',\n 103: 'santiago',\n 253: 'manual',\n 254: 'automatic',\n },\n display_measure: {\n 0: 'metric',\n 1: 'statute',\n 2: 'nautical',\n },\n display_heart: {\n 0: 'bpm',\n 1: 'max',\n 2: 'reserve',\n },\n display_power: {\n 0: 'watts',\n 1: 'percent_ftp',\n },\n display_position: {\n 0: 'degree',\n 1: 'degree_minute',\n 2: 'degree_minute_second',\n 3: 'austrian_grid',\n 4: 'british_grid',\n 5: 'dutch_grid',\n 6: 'hungarian_grid',\n 7: 'finnish_grid',\n 8: 'german_grid',\n 9: 'icelandic_grid',\n 10: 'indonesian_equatorial',\n 11: 'indonesian_irian',\n 12: 'indonesian_southern',\n 13: 'india_zone_0',\n 14: 'india_zone_IA',\n 15: 'india_zone_IB',\n 16: 'india_zone_IIA',\n 17: 'india_zone_IIB',\n 18: 'india_zone_IIIA',\n 19: 'india_zone_IIIB',\n 20: 'india_zone_IVA',\n 21: 'india_zone_IVB',\n 22: 'irish_transverse',\n 23: 'irish_grid',\n 24: 'loran',\n 25: 'maidenhead_grid',\n 26: 'mgrs_grid',\n 27: 'new_zealand_grid',\n 28: 'new_zealand_transverse',\n 29: 'qatar_grid',\n 30: 'modified_swedish_grid',\n 31: 'swedish_grid',\n 32: 'south_african_grid',\n 33: 'swiss_grid',\n 34: 'taiwan_grid',\n 35: 'united_states_grid',\n 36: 'utm_ups_grid',\n 37: 'west_malayan',\n 38: 'borneo_rso',\n 39: 'estonian_grid',\n 40: 'latvian_grid',\n 41: 'swedish_ref_99_grid',\n },\n switch: {\n 0: 'off',\n 1: 'on',\n 2: 'auto',\n },\n sport: {\n 0: 'generic',\n 1: 'running',\n 2: 'cycling',\n 3: 'transition',\n 4: 'fitness_equipment',\n 5: 'swimming',\n 6: 'basketball',\n 7: 'soccer',\n 8: 'tennis',\n 9: 'american_football',\n 10: 'training',\n 11: 'walking',\n 12: 'cross_country_skiing',\n 13: 'alpine_skiing',\n 14: 'snowboarding',\n 15: 'rowing',\n 16: 'mountaineering',\n 17: 'hiking',\n 18: 'multisport',\n 19: 'paddling',\n 20: 'flying',\n 21: 'e_biking',\n 22: 'motorcycling',\n 23: 'boating',\n 24: 'driving',\n 25: 'golf',\n 26: 'hang_gliding',\n 27: 'horseback_riding',\n 28: 'hunting',\n 29: 'fishing',\n 30: 'inline_skating',\n 31: 'rock_climbing',\n 32: 'sailing',\n 33: 'ice_skating',\n 34: 'sky_diving',\n 35: 'snowshoeing',\n 36: 'snowmobiling',\n 37: 'stand_up_paddleboarding',\n 38: 'surfing',\n 39: 'wakeboarding',\n 40: 'water_skiing',\n 41: 'kayaking',\n 42: 'rafting',\n 43: 'windsurfing',\n 44: 'kitesurfing',\n 45: 'tactical',\n 46: 'jumpmaster',\n 47: 'boxing',\n 48: 'floor_climbing',\n 53: 'diving',\n 254: 'all',\n },\n sport_bits_0: {\n 0: 0,\n 1: 'generic',\n 2: 'running',\n 4: 'cycling',\n 8: 'transition',\n 16: 'fitness_equipment',\n 32: 'swimming',\n 64: 'basketball',\n 128: 'soccer',\n },\n sport_bits_1: {\n 0: 0,\n 1: 'tennis',\n 2: 'american_football',\n 4: 'training',\n 8: 'walking',\n 16: 'cross_country_skiing',\n 32: 'alpine_skiing',\n 64: 'snowboarding',\n 128: 'rowing',\n },\n sport_bits_2: {\n 0: 0,\n 1: 'mountaineering',\n 2: 'hiking',\n 4: 'multisport',\n 8: 'paddling',\n 16: 'flying',\n 32: 'e_biking',\n 64: 'motorcycling',\n 128: 'boating',\n },\n sport_bits_3: {\n 0: 0,\n 1: 'driving',\n 2: 'golf',\n 4: 'hang_gliding',\n 8: 'horseback_riding',\n 16: 'hunting',\n 32: 'fishing',\n 64: 'inline_skating',\n 128: 'rock_climbing',\n },\n sport_bits_4: {\n 0: 0,\n 1: 'sailing',\n 2: 'ice_skating',\n 4: 'sky_diving',\n 8: 'snowshoeing',\n 16: 'snowmobiling',\n 32: 'stand_up_paddleboarding',\n 64: 'surfing',\n 128: 'wakeboarding',\n },\n sport_bits_5: {\n 0: 0,\n 1: 'water_skiing',\n 2: 'kayaking',\n 4: 'rafting',\n 8: 'windsurfing',\n 16: 'kitesurfing',\n 32: 'tactical',\n 64: 'jumpmaster',\n 128: 'boxing',\n },\n sport_bits_6: {\n 0: 0,\n 1: 'floor_climbing',\n },\n sub_sport: {\n 0: 'generic',\n 1: 'treadmill',\n 2: 'street',\n 3: 'trail',\n 4: 'track',\n 5: 'spin',\n 6: 'indoor_cycling',\n 7: 'road',\n 8: 'mountain',\n 9: 'downhill',\n 10: 'recumbent',\n 11: 'cyclocross',\n 12: 'hand_cycling',\n 13: 'track_cycling',\n 14: 'indoor_rowing',\n 15: 'elliptical',\n 16: 'stair_climbing',\n 17: 'lap_swimming',\n 18: 'open_water',\n 19: 'flexibility_training',\n 20: 'strength_training',\n 21: 'warm_up',\n 22: 'match',\n 23: 'exercise',\n 24: 'challenge',\n 25: 'indoor_skiing',\n 26: 'cardio_training',\n 27: 'indoor_walking',\n 28: 'e_bike_fitness',\n 29: 'bmx',\n 30: 'casual_walking',\n 31: 'speed_walking',\n 32: 'bike_to_run_transition',\n 33: 'run_to_bike_transition',\n 34: 'swim_to_bike_transition',\n 35: 'atv',\n 36: 'motocross',\n 37: 'backcountry',\n 38: 'resort',\n 39: 'rc_drone',\n 40: 'wingsuit',\n 41: 'whitewater',\n 42: 'skate_skiing',\n 43: 'yoga',\n 44: 'pilates',\n 45: 'indoor_running',\n 46: 'gravel_cycling',\n 47: 'e_bike_mountain',\n 48: 'commuting',\n 49: 'mixed_surface',\n 50: 'navigate',\n 51: 'track_me',\n 52: 'map',\n 53: 'single_gas_diving',\n 54: 'multi_gas_diving',\n 55: 'gauge_diving',\n 56: 'apnea_diving',\n 57: 'apnea_hunting',\n 58: 'virtual_activity',\n 59: 'obstacle',\n 254: 'all',\n },\n sport_event: {\n 0: 'uncategorized',\n 1: 'geocaching',\n 2: 'fitness',\n 3: 'recreation',\n 4: 'race',\n 5: 'special_event',\n 6: 'training',\n 7: 'transportation',\n 8: 'touring',\n },\n activity: {\n 0: 'manual',\n 1: 'auto_multi_sport',\n },\n intensity: {\n 0: 'active',\n 1: 'rest',\n 2: 'warmup',\n 3: 'cooldown',\n 4: 'recovery',\n 5: 'interval',\n 6: 'other',\n },\n session_trigger: {\n 0: 'activity_end',\n 1: 'manual',\n 2: 'auto_multi_sport',\n 3: 'fitness_equipment',\n },\n autolap_trigger: {\n 0: 'time',\n 1: 'distance',\n 2: 'position_start',\n 3: 'position_lap',\n 4: 'position_waypoint',\n 5: 'position_marked',\n 6: 'off',\n },\n lap_trigger: {\n 0: 'manual',\n 1: 'time',\n 2: 'distance',\n 3: 'position_start',\n 4: 'position_lap',\n 5: 'position_waypoint',\n 6: 'position_marked',\n 7: 'session_end',\n 8: 'fitness_equipment',\n },\n time_mode: {\n 0: 'hour12',\n 1: 'hour24',\n 2: 'military',\n 3: 'hour_12_with_seconds',\n 4: 'hour_24_with_seconds',\n 5: 'utc',\n },\n backlight_mode: {\n 0: 'off',\n 1: 'manual',\n 2: 'key_and_messages',\n 3: 'auto_brightness',\n 4: 'smart_notifications',\n 5: 'key_and_messages_night',\n 6: 'key_and_messages_and_smart_notifications',\n },\n date_mode: {\n 0: 'day_month',\n 1: 'month_day',\n },\n backlight_timeout: {\n 0: 'infinite',\n },\n event: {\n 0: 'timer',\n 3: 'workout',\n 4: 'workout_step',\n 5: 'power_down',\n 6: 'power_up',\n 7: 'off_course',\n 8: 'session',\n 9: 'lap',\n 10: 'course_point',\n 11: 'battery',\n 12: 'virtual_partner_pace',\n 13: 'hr_high_alert',\n 14: 'hr_low_alert',\n 15: 'speed_high_alert',\n 16: 'speed_low_alert',\n 17: 'cad_high_alert',\n 18: 'cad_low_alert',\n 19: 'power_high_alert',\n 20: 'power_low_alert',\n 21: 'recovery_hr',\n 22: 'battery_low',\n 23: 'time_duration_alert',\n 24: 'distance_duration_alert',\n 25: 'calorie_duration_alert',\n 26: 'activity',\n 27: 'fitness_equipment',\n 28: 'length',\n 32: 'user_marker',\n 33: 'sport_point',\n 36: 'calibration',\n 42: 'front_gear_change',\n 43: 'rear_gear_change',\n 44: 'rider_position_change',\n 45: 'elev_high_alert',\n 46: 'elev_low_alert',\n 47: 'comm_timeout',\n },\n event_type: {\n 0: 'start',\n 1: 'stop',\n 2: 'consecutive_depreciated',\n 3: 'marker',\n 4: 'stop_all',\n 5: 'begin_depreciated',\n 6: 'end_depreciated',\n 7: 'end_all_depreciated',\n 8: 'stop_disable',\n 9: 'stop_disable_all',\n },\n timer_trigger: {\n 0: 'manual',\n 1: 'auto',\n 2: 'fitness_equipment',\n },\n fitness_equipment_state: {\n 0: 'ready',\n 1: 'in_use',\n 2: 'paused',\n 3: 'unknown',\n },\n tone: {\n 0: 'off',\n 1: 'tone',\n 2: 'vibrate',\n 3: 'tone_and_vibrate',\n },\n autoscroll: {\n 0: 'none',\n 1: 'slow',\n 2: 'medium',\n 3: 'fast',\n },\n activity_class: {\n 0: 0,\n 100: 'level_max',\n 127: 'level',\n 128: 'athlete',\n },\n hr_zone_calc: {\n 0: 'custom',\n 1: 'percent_max_hr',\n 2: 'percent_hrr',\n },\n pwr_zone_calc: {\n 0: 'custom',\n 1: 'percent_ftp',\n },\n wkt_step_duration: {\n 0: 'time',\n 1: 'distance',\n 2: 'hr_less_than',\n 3: 'hr_greater_than',\n 4: 'calories',\n 5: 'open',\n 6: 'repeat_until_steps_cmplt',\n 7: 'repeat_until_time',\n 8: 'repeat_until_distance',\n 9: 'repeat_until_calories',\n 10: 'repeat_until_hr_less_than',\n 11: 'repeat_until_hr_greater_than',\n 12: 'repeat_until_power_less_than',\n 13: 'repeat_until_power_greater_than',\n 14: 'power_less_than',\n 15: 'power_greater_than',\n 16: 'training_peaks_tss',\n 17: 'repeat_until_power_last_lap_less_than',\n 18: 'repeat_until_max_power_last_lap_less_than',\n 19: 'power_3s_less_than',\n 20: 'power_10s_less_than',\n 21: 'power_30s_less_than',\n 22: 'power_3s_greater_than',\n 23: 'power_10s_greater_than',\n 24: 'power_30s_greater_than',\n 25: 'power_lap_less_than',\n 26: 'power_lap_greater_than',\n 27: 'repeat_until_training_peaks_tss',\n 28: 'repetition_time',\n 29: 'reps',\n },\n wkt_step_target: {\n 0: 'speed',\n 1: 'heart_rate',\n 2: 'open',\n 3: 'cadence',\n 4: 'power',\n 5: 'grade',\n 6: 'resistance',\n 7: 'power_3s',\n 8: 'power_10s',\n 9: 'power_30s',\n 10: 'power_lap',\n 11: 'swim_stroke',\n 12: 'speed_lap',\n 13: 'heart_rate_lap',\n },\n goal: {\n 0: 'time',\n 1: 'distance',\n 2: 'calories',\n 3: 'frequency',\n 4: 'steps',\n 5: 'ascent',\n 6: 'active_minutes',\n },\n goal_recurrence: {\n 0: 'off',\n 1: 'daily',\n 2: 'weekly',\n 3: 'monthly',\n 4: 'yearly',\n 5: 'custom',\n },\n goal_source: {\n 0: 'auto',\n 1: 'community',\n 2: 'user',\n },\n schedule: {\n 0: 'workout',\n 1: 'course',\n },\n course_point: {\n 0: 'generic',\n 1: 'summit',\n 2: 'valley',\n 3: 'water',\n 4: 'food',\n 5: 'danger',\n 6: 'left',\n 7: 'right',\n 8: 'straight',\n 9: 'first_aid',\n 10: 'fourth_category',\n 11: 'third_category',\n 12: 'second_category',\n 13: 'first_category',\n 14: 'hors_category',\n 15: 'sprint',\n 16: 'left_fork',\n 17: 'right_fork',\n 18: 'middle_fork',\n 19: 'slight_left',\n 20: 'sharp_left',\n 21: 'slight_right',\n 22: 'sharp_right',\n 23: 'u_turn',\n 24: 'segment_start',\n 25: 'segment_end',\n },\n manufacturer: {\n 0: 0,\n 1: 'garmin',\n 2: 'garmin_fr405_antfs',\n 3: 'zephyr',\n 4: 'dayton',\n 5: 'idt',\n 6: 'srm',\n 7: 'quarq',\n 8: 'ibike',\n 9: 'saris',\n 10: 'spark_hk',\n 11: 'tanita',\n 12: 'echowell',\n 13: 'dynastream_oem',\n 14: 'nautilus',\n 15: 'dynastream',\n 16: 'timex',\n 17: 'metrigear',\n 18: 'xelic',\n 19: 'beurer',\n 20: 'cardiosport',\n 21: 'a_and_d',\n 22: 'hmm',\n 23: 'suunto',\n 24: 'thita_elektronik',\n 25: 'gpulse',\n 26: 'clean_mobile',\n 27: 'pedal_brain',\n 28: 'peaksware',\n 29: 'saxonar',\n 30: 'lemond_fitness',\n 31: 'dexcom',\n 32: 'wahoo_fitness',\n 33: 'octane_fitness',\n 34: 'archinoetics',\n 35: 'the_hurt_box',\n 36: 'citizen_systems',\n 37: 'magellan',\n 38: 'osynce',\n 39: 'holux',\n 40: 'concept2',\n 42: 'one_giant_leap',\n 43: 'ace_sensor',\n 44: 'brim_brothers',\n 45: 'xplova',\n 46: 'perception_digital',\n 47: 'bf1systems',\n 48: 'pioneer',\n 49: 'spantec',\n 50: 'metalogics',\n 51: '4iiiis',\n 52: 'seiko_epson',\n 53: 'seiko_epson_oem',\n 54: 'ifor_powell',\n 55: 'maxwell_guider',\n 56: 'star_trac',\n 57: 'breakaway',\n 58: 'alatech_technology_ltd',\n 59: 'mio_technology_europe',\n 60: 'rotor',\n 61: 'geonaute',\n 62: 'id_bike',\n 63: 'specialized',\n 64: 'wtek',\n 65: 'physical_enterprises',\n 66: 'north_pole_engineering',\n 67: 'bkool',\n 68: 'cateye',\n 69: 'stages_cycling',\n 70: 'sigmasport',\n 71: 'tomtom',\n 72: 'peripedal',\n 73: 'wattbike',\n 76: 'moxy',\n 77: 'ciclosport',\n 78: 'powerbahn',\n 79: 'acorn_projects_aps',\n 80: 'lifebeam',\n 81: 'bontrager',\n 82: 'wellgo',\n 83: 'scosche',\n 84: 'magura',\n 85: 'woodway',\n 86: 'elite',\n 87: 'nielsen_kellerman',\n 88: 'dk_city',\n 89: 'tacx',\n 90: 'direction_technology',\n 91: 'magtonic',\n 92: '1partcarbon',\n 93: 'inside_ride_technologies',\n 94: 'sound_of_motion',\n 95: 'stryd',\n 96: 'icg',\n 97: 'mipulse',\n 98: 'bsx_athletics',\n 99: 'look',\n 100: 'campagnolo_srl',\n 101: 'body_bike_smart',\n 102: 'praxisworks',\n 103: 'limits_technology',\n 104: 'topaction_technology',\n 105: 'cosinuss',\n 106: 'fitcare',\n 107: 'magene',\n 108: 'giant_manufacturing_co',\n 109: 'tigrasport',\n 110: 'salutron',\n 111: 'technogym',\n 112: 'bryton_sensors',\n 113: 'latitude_limited',\n 114: 'soaring_technology',\n 115: 'igpsport',\n 116: 'thinkrider',\n 117: 'gopher_sport',\n 118: 'waterrower',\n 119: 'orangetheory',\n 120: 'inpeak',\n 121: 'kinetic',\n 122: 'johnson_health_tech',\n 123: 'polar_electro',\n 124: 'seesense',\n 125: 'nci_technology',\n 255: 'development',\n 257: 'healthandlife',\n 258: 'lezyne',\n 259: 'scribe_labs',\n 260: 'zwift',\n 261: 'watteam',\n 262: 'recon',\n 263: 'favero_electronics',\n 264: 'dynovelo',\n 265: 'strava',\n 266: 'precor',\n 267: 'bryton',\n 268: 'sram',\n 269: 'navman',\n 270: 'cobi',\n 271: 'spivi',\n 272: 'mio_magellan',\n 273: 'evesports',\n 274: 'sensitivus_gauge',\n 275: 'podoon',\n 276: 'life_time_fitness',\n 277: 'falco_e_motors',\n 278: 'minoura',\n 279: 'cycliq',\n 280: 'luxottica',\n 281: 'trainer_road',\n 282: 'the_sufferfest',\n 283: 'fullspeedahead',\n 284: 'virtualtraining',\n 285: 'feedbacksports',\n 286: 'omata',\n 287: 'vdo',\n 288: 'magneticdays',\n 289: 'hammerhead',\n 290: 'kinetic_by_kurt',\n 291: 'shapelog',\n 292: 'dabuziduo',\n 293: 'jetblack',\n 294: 'coros',\n 295: 'virtugo',\n 296: 'velosense',\n 5759: 'actigraphcorp',\n },\n garmin_product: {\n 0: 'hrm_bike',\n 1: 'hrm1',\n 2: 'axh01',\n 3: 'axb01',\n 4: 'axb02',\n 5: 'hrm2ss',\n 6: 'dsi_alf02',\n 7: 'hrm3ss',\n 8: 'hrm_run_single_byte_product_id',\n 9: 'bsm',\n 10: 'bcm',\n 11: 'axs01',\n 12: 'hrm_tri_single_byte_product_id',\n 14: 'fr225_single_byte_product_id',\n 473: 'fr301_china',\n 474: 'fr301_japan',\n 475: 'fr301_korea',\n 494: 'fr301_taiwan',\n 717: 'fr405',\n 782: 'fr50',\n 987: 'fr405_japan',\n 988: 'fr60',\n 1011: 'dsi_alf01',\n 1018: 'fr310xt',\n 1036: 'edge500',\n 1124: 'fr110',\n 1169: 'edge800',\n 1199: 'edge500_taiwan',\n 1213: 'edge500_japan',\n 1253: 'chirp',\n 1274: 'fr110_japan',\n 1325: 'edge200',\n 1328: 'fr910xt',\n 1333: 'edge800_taiwan',\n 1334: 'edge800_japan',\n 1341: 'alf04',\n 1345: 'fr610',\n 1360: 'fr210_japan',\n 1380: 'vector_ss',\n 1381: 'vector_cp',\n 1386: 'edge800_china',\n 1387: 'edge500_china',\n 1410: 'fr610_japan',\n 1422: 'edge500_korea',\n 1436: 'fr70',\n 1446: 'fr310xt_4t',\n 1461: 'amx',\n 1482: 'fr10',\n 1497: 'edge800_korea',\n 1499: 'swim',\n 1537: 'fr910xt_china',\n 1551: 'fenix',\n 1555: 'edge200_taiwan',\n 1561: 'edge510',\n 1567: 'edge810',\n 1570: 'tempe',\n 1600: 'fr910xt_japan',\n 1623: 'fr620',\n 1632: 'fr220',\n 1664: 'fr910xt_korea',\n 1688: 'fr10_japan',\n 1721: 'edge810_japan',\n 1735: 'virb_elite',\n 1736: 'edge_touring',\n 1742: 'edge510_japan',\n 1743: 'hrm_tri',\n 1752: 'hrm_run',\n 1765: 'fr920xt',\n 1821: 'edge510_asia',\n 1822: 'edge810_china',\n 1823: 'edge810_taiwan',\n 1836: 'edge1000',\n 1837: 'vivo_fit',\n 1853: 'virb_remote',\n 1885: 'vivo_ki',\n 1903: 'fr15',\n 1907: 'vivo_active',\n 1918: 'edge510_korea',\n 1928: 'fr620_japan',\n 1929: 'fr620_china',\n 1930: 'fr220_japan',\n 1931: 'fr220_china',\n 1936: 'approach_s6',\n 1956: 'vivo_smart',\n 1967: 'fenix2',\n 1988: 'epix',\n 2050: 'fenix3',\n 2052: 'edge1000_taiwan',\n 2053: 'edge1000_japan',\n 2061: 'fr15_japan',\n 2067: 'edge520',\n 2070: 'edge1000_china',\n 2072: 'fr620_russia',\n 2073: 'fr220_russia',\n 2079: 'vector_s',\n 2100: 'edge1000_korea',\n 2130: 'fr920xt_taiwan',\n 2131: 'fr920xt_china',\n 2132: 'fr920xt_japan',\n 2134: 'virbx',\n 2135: 'vivo_smart_apac',\n 2140: 'etrex_touch',\n 2147: 'edge25',\n 2148: 'fr25',\n 2150: 'vivo_fit2',\n 2153: 'fr225',\n 2156: 'fr630',\n 2157: 'fr230',\n 2158: 'fr735xt',\n 2160: 'vivo_active_apac',\n 2161: 'vector_2',\n 2162: 'vector_2s',\n 2172: 'virbxe',\n 2173: 'fr620_taiwan',\n 2174: 'fr220_taiwan',\n 2175: 'truswing',\n 2188: 'fenix3_china',\n 2189: 'fenix3_twn',\n 2192: 'varia_headlight',\n 2193: 'varia_taillight_old',\n 2204: 'edge_explore_1000',\n 2219: 'fr225_asia',\n 2225: 'varia_radar_taillight',\n 2226: 'varia_radar_display',\n 2238: 'edge20',\n 2262: 'd2_bravo',\n 2266: 'approach_s20',\n 2276: 'varia_remote',\n 2327: 'hrm4_run',\n 2337: 'vivo_active_hr',\n 2348: 'vivo_smart_hr',\n 2368: 'vivo_move',\n 2398: 'varia_vision',\n 2406: 'vivo_fit3',\n 2413: 'fenix3_hr',\n 2417: 'virb_ultra_30',\n 2429: 'index_smart_scale',\n 2431: 'fr235',\n 2432: 'fenix3_chronos',\n 2441: 'oregon7xx',\n 2444: 'rino7xx',\n 2496: 'nautix',\n 2530: 'edge_820',\n 2531: 'edge_explore_820',\n 2544: 'fenix5s',\n 2547: 'd2_bravo_titanium',\n 2567: 'varia_ut800',\n 2593: 'running_dynamics_pod',\n 2604: 'fenix5x',\n 2606: 'vivo_fit_jr',\n 2691: 'fr935',\n 2697: 'fenix5',\n 2859: 'descent',\n 10007: 'sdm4',\n 10014: 'edge_remote',\n 20119: 'training_center',\n 65531: 'connectiq_simulator',\n 65532: 'android_antplus_plugin',\n 65534: 'connect',\n },\n antplus_device_type: {\n 1: 'antfs',\n 11: 'bike_power',\n 12: 'environment_sensor_legacy',\n 15: 'multi_sport_speed_distance',\n 16: 'control',\n 17: 'fitness_equipment',\n 18: 'blood_pressure',\n 19: 'geocache_node',\n 20: 'light_electric_vehicle',\n 25: 'env_sensor',\n 26: 'racquet',\n 27: 'control_hub',\n 31: 'muscle_oxygen',\n 34: 'shifting',\n 35: 'bike_light_main',\n 36: 'bike_light_shared',\n 38: 'exd',\n 40: 'bike_radar',\n 46: 'bike_aero',\n 119: 'weight_scale',\n 120: 'heart_rate',\n 121: 'bike_speed_cadence',\n 122: 'bike_cadence',\n 123: 'bike_speed',\n 124: 'stride_speed_distance',\n },\n local_device_type: {\n 0: 'gps',\n 1: 'glonass',\n 2: 'gps_glonass',\n 3: 'accelerometer',\n 4: 'barometer',\n 5: 'temperature',\n 10: 'whr',\n 12: 'sensor_hub',\n },\n ble_device_type: {\n 0: 'connected_gps',\n 1: 'heart_rate',\n 2: 'bike_power',\n 3: 'bike_speed_cadence',\n 4: 'bike_speed',\n 5: 'bike_cadence',\n 6: 'footpod',\n 7: 'bike_trainer',\n },\n ant_network: {\n 0: 'public',\n 1: 'antplus',\n 2: 'antfs',\n 3: 'private',\n },\n workout_capabilities: {\n 0: 0,\n 1: 'interval',\n 2: 'custom',\n 4: 'fitness_equipment',\n 8: 'firstbeat',\n 16: 'new_leaf',\n 32: 'tcx',\n 128: 'speed',\n 256: 'heart_rate',\n 512: 'distance',\n 1024: 'cadence',\n 2048: 'power',\n 4096: 'grade',\n 8192: 'resistance',\n 16384: 'protected',\n },\n battery_status: {\n 0: 0,\n 1: 'new',\n 2: 'good',\n 3: 'ok',\n 4: 'low',\n 5: 'critical',\n 6: 'charging',\n 7: 'unknown',\n },\n hr_type: {\n 0: 'normal',\n 1: 'irregular',\n },\n course_capabilities: {\n 0: 0,\n 1: 'processed',\n 2: 'valid',\n 4: 'time',\n 8: 'distance',\n 16: 'position',\n 32: 'heart_rate',\n 64: 'power',\n 128: 'cadence',\n 256: 'training',\n 512: 'navigation',\n 1024: 'bikeway',\n },\n weight: {\n 0: 0,\n 65534: 'calculating',\n },\n workout_hr: {\n 0: 0,\n 100: 'bpm_offset',\n },\n workout_power: {\n 0: 0,\n 1000: 'watts_offset',\n },\n bp_status: {\n 0: 'no_error',\n 1: 'error_incomplete_data',\n 2: 'error_no_measurement',\n 3: 'error_data_out_of_range',\n 4: 'error_irregular_heart_rate',\n },\n user_local_id: {\n 0: 'local_min',\n 15: 'local_max',\n 16: 'stationary_min',\n 255: 'stationary_max',\n 256: 'portable_min',\n 65534: 'portable_max',\n },\n swim_stroke: {\n 0: 'freestyle',\n 1: 'backstroke',\n 2: 'breaststroke',\n 3: 'butterfly',\n 4: 'drill',\n 5: 'mixed',\n 6: 'im',\n },\n activity_type: {\n 0: 'generic',\n 1: 'running',\n 2: 'cycling',\n 3: 'transition',\n 4: 'fitness_equipment',\n 5: 'swimming',\n 6: 'walking',\n 8: 'sedentary',\n 254: 'all',\n },\n activity_subtype: {\n 0: 'generic',\n 1: 'treadmill',\n 2: 'street',\n 3: 'trail',\n 4: 'track',\n 5: 'spin',\n 6: 'indoor_cycling',\n 7: 'road',\n 8: 'mountain',\n 9: 'downhill',\n 10: 'recumbent',\n 11: 'cyclocross',\n 12: 'hand_cycling',\n 13: 'track_cycling',\n 14: 'indoor_rowing',\n 15: 'elliptical',\n 16: 'stair_climbing',\n 17: 'lap_swimming',\n 18: 'open_water',\n 254: 'all',\n },\n activity_level: {\n 0: 'low',\n 1: 'medium',\n 2: 'high',\n },\n side: {\n 0: 'right',\n 1: 'left',\n },\n left_right_balance: {\n 0: 0,\n 127: 'mask',\n 128: 'right',\n },\n left_right_balance_100: {\n 0: 0,\n 16383: 'mask',\n 32768: 'right',\n },\n length_type: {\n 0: 'idle',\n 1: 'active',\n },\n day_of_week: {\n 0: 'sunday',\n 1: 'monday',\n 2: 'tuesday',\n 3: 'wednesday',\n 4: 'thursday',\n 5: 'friday',\n 6: 'saturday',\n },\n connectivity_capabilities: {\n 0: 0,\n 1: 'bluetooth',\n 2: 'bluetooth_le',\n 4: 'ant',\n 8: 'activity_upload',\n 16: 'course_download',\n 32: 'workout_download',\n 64: 'live_track',\n 128: 'weather_conditions',\n 256: 'weather_alerts',\n 512: 'gps_ephemeris_download',\n 1024: 'explicit_archive',\n 2048: 'setup_incomplete',\n 4096: 'continue_sync_after_software_update',\n 8192: 'connect_iq_app_download',\n 16384: 'golf_course_download',\n 32768: 'device_initiates_sync',\n 65536: 'connect_iq_watch_app_download',\n 131072: 'connect_iq_widget_download',\n 262144: 'connect_iq_watch_face_download',\n 524288: 'connect_iq_data_field_download',\n 1048576: 'connect_iq_app_managment',\n 2097152: 'swing_sensor',\n 4194304: 'swing_sensor_remote',\n 8388608: 'incident_detection',\n 16777216: 'audio_prompts',\n 33554432: 'wifi_verification',\n 67108864: 'true_up',\n 134217728: 'find_my_watch',\n 268435456: 'remote_manual_sync',\n 536870912: 'live_track_auto_start',\n 1073741824: 'live_track_messaging',\n 2147483648: 'instant_input',\n },\n weather_report: {\n 0: 'current',\n 1: 'hourly_forecast',\n 2: 'daily_forecast',\n },\n weather_status: {\n 0: 'clear',\n 1: 'partly_cloudy',\n 2: 'mostly_cloudy',\n 3: 'rain',\n 4: 'snow',\n 5: 'windy',\n 6: 'thunderstorms',\n 7: 'wintry_mix',\n 8: 'fog',\n 11: 'hazy',\n 12: 'hail',\n 13: 'scattered_showers',\n 14: 'scattered_thunderstorms',\n 15: 'unknown_precipitation',\n 16: 'light_rain',\n 17: 'heavy_rain',\n 18: 'light_snow',\n 19: 'heavy_snow',\n 20: 'light_rain_snow',\n 21: 'heavy_rain_snow',\n 22: 'cloudy',\n },\n weather_severity: {\n 0: 'unknown',\n 1: 'warning',\n 2: 'watch',\n 3: 'advisory',\n 4: 'statement',\n },\n weather_severe_type: {\n 0: 'unspecified',\n 1: 'tornado',\n 2: 'tsunami',\n 3: 'hurricane',\n 4: 'extreme_wind',\n 5: 'typhoon',\n 6: 'inland_hurricane',\n 7: 'hurricane_force_wind',\n 8: 'waterspout',\n 9: 'severe_thunderstorm',\n 10: 'wreckhouse_winds',\n 11: 'les_suetes_wind',\n 12: 'avalanche',\n 13: 'flash_flood',\n 14: 'tropical_storm',\n 15: 'inland_tropical_storm',\n 16: 'blizzard',\n 17: 'ice_storm',\n 18: 'freezing_rain',\n 19: 'debris_flow',\n 20: 'flash_freeze',\n 21: 'dust_storm',\n 22: 'high_wind',\n 23: 'winter_storm',\n 24: 'heavy_freezing_spray',\n 25: 'extreme_cold',\n 26: 'wind_chill',\n 27: 'cold_wave',\n 28: 'heavy_snow_alert',\n 29: 'lake_effect_blowing_snow',\n 30: 'snow_squall',\n 31: 'lake_effect_snow',\n 32: 'winter_weather',\n 33: 'sleet',\n 34: 'snowfall',\n 35: 'snow_and_blowing_snow',\n 36: 'blowing_snow',\n 37: 'snow_alert',\n 38: 'arctic_outflow',\n 39: 'freezing_drizzle',\n 40: 'storm',\n 41: 'storm_surge',\n 42: 'rainfall',\n 43: 'areal_flood',\n 44: 'coastal_flood',\n 45: 'lakeshore_flood',\n 46: 'excessive_heat',\n 47: 'heat',\n 48: 'weather',\n 49: 'high_heat_and_humidity',\n 50: 'humidex_and_health',\n 51: 'humidex',\n 52: 'gale',\n 53: 'freezing_spray',\n 54: 'special_marine',\n 55: 'squall',\n 56: 'strong_wind',\n 57: 'lake_wind',\n 58: 'marine_weather',\n 59: 'wind',\n 60: 'small_craft_hazardous_seas',\n 61: 'hazardous_seas',\n 62: 'small_craft',\n 63: 'small_craft_winds',\n 64: 'small_craft_rough_bar',\n 65: 'high_water_level',\n 66: 'ashfall',\n 67: 'freezing_fog',\n 68: 'dense_fog',\n 69: 'dense_smoke',\n 70: 'blowing_dust',\n 71: 'hard_freeze',\n 72: 'freeze',\n 73: 'frost',\n 74: 'fire_weather',\n 75: 'flood',\n 76: 'rip_tide',\n 77: 'high_surf',\n 78: 'smog',\n 79: 'air_quality',\n 80: 'brisk_wind',\n 81: 'air_stagnation',\n 82: 'low_water',\n 83: 'hydrological',\n 84: 'special_weather',\n },\n stroke_type: {\n 0: 'no_event',\n 1: 'other',\n 2: 'serve',\n 3: 'forehand',\n 4: 'backhand',\n 5: 'smash',\n },\n body_location: {\n 0: 'left_leg',\n 1: 'left_calf',\n 2: 'left_shin',\n 3: 'left_hamstring',\n 4: 'left_quad',\n 5: 'left_glute',\n 6: 'right_leg',\n 7: 'right_calf',\n 8: 'right_shin',\n 9: 'right_hamstring',\n 10: 'right_quad',\n 11: 'right_glute',\n 12: 'torso_back',\n 13: 'left_lower_back',\n 14: 'left_upper_back',\n 15: 'right_lower_back',\n 16: 'right_upper_back',\n 17: 'torso_front',\n 18: 'left_abdomen',\n 19: 'left_chest',\n 20: 'right_abdomen',\n 21: 'right_chest',\n 22: 'left_arm',\n 23: 'left_shoulder',\n 24: 'left_bicep',\n 25: 'left_tricep',\n 26: 'left_brachioradialis',\n 27: 'left_forearm_extensors',\n 28: 'right_arm',\n 29: 'right_shoulder',\n 30: 'right_bicep',\n 31: 'right_tricep',\n 32: 'right_brachioradialis',\n 33: 'right_forearm_extensors',\n 34: 'neck',\n 35: 'throat',\n 36: 'waist_mid_back',\n 37: 'waist_front',\n 38: 'waist_left',\n 39: 'waist_right',\n },\n segment_lap_status: {\n 0: 'end',\n 1: 'fail',\n },\n segment_leaderboard_type: {\n 0: 'overall',\n 1: 'personal_best',\n 2: 'connections',\n 3: 'group',\n 4: 'challenger',\n 5: 'kom',\n 6: 'qom',\n 7: 'pr',\n 8: 'goal',\n 9: 'rival',\n 10: 'club_leader',\n },\n segment_delete_status: {\n 0: 'do_not_delete',\n 1: 'delete_one',\n 2: 'delete_all',\n },\n segment_selection_type: {\n 0: 'starred',\n 1: 'suggested',\n },\n source_type: {\n 0: 'ant',\n 1: 'antplus',\n 2: 'bluetooth',\n 3: 'bluetooth_low_energy',\n 4: 'wifi',\n 5: 'local',\n },\n display_orientation: {\n 0: 'auto',\n 1: 'portrait',\n 2: 'landscape',\n 3: 'portrait_flipped',\n 4: 'landscape_flipped',\n },\n workout_equipment: {\n 0: 'none',\n 1: 'swim_fins',\n 2: 'swim_kickboard',\n 3: 'swim_paddles',\n 4: 'swim_pull_buoy',\n 5: 'swim_snorkel',\n },\n watchface_mode: {\n 0: 'digital',\n 1: 'analog',\n 2: 'connect_iq',\n 3: 'disabled',\n },\n digital_watchface_layout: {\n 0: 'traditional',\n 1: 'modern',\n 2: 'bold',\n },\n analog_watchface_layout: {\n 0: 'minimal',\n 1: 'traditional',\n 2: 'modern',\n },\n rider_position_type: {\n 0: 'seated',\n 1: 'standing',\n 2: 'transition_to_seated',\n 3: 'transition_to_standing',\n },\n power_phase_type: {\n 0: 'power_phase_start_angle',\n 1: 'power_phase_end_angle',\n 2: 'power_phase_arc_length',\n 3: 'power_phase_center',\n },\n camera_event_type: {\n 0: 'video_start',\n 1: 'video_split',\n 2: 'video_end',\n 3: 'photo_taken',\n 4: 'video_second_stream_start',\n 5: 'video_second_stream_split',\n 6: 'video_second_stream_end',\n 7: 'video_split_start',\n 8: 'video_second_stream_split_start',\n 11: 'video_pause',\n 12: 'video_second_stream_pause',\n 13: 'video_resume',\n 14: 'video_second_stream_resume',\n },\n sensor_type: {\n 0: 'accelerometer',\n 1: 'gyroscope',\n 2: 'compass',\n 3: 'barometer',\n },\n bike_light_network_config_type: {\n 0: 'auto',\n 4: 'individual',\n 5: 'high_visibility',\n 6: 'trail',\n },\n comm_timeout_type: {\n 0: 'wildcard_pairing_timeout',\n 1: 'pairing_timeout',\n 2: 'connection_lost',\n 3: 'connection_timeout',\n },\n camera_orientation_type: {\n 0: 'camera_orientation_0',\n 1: 'camera_orientation_90',\n 2: 'camera_orientation_180',\n 3: 'camera_orientation_270',\n },\n attitude_stage: {\n 0: 'failed',\n 1: 'aligning',\n 2: 'degraded',\n 3: 'valid',\n },\n attitude_validity: {\n 0: 0,\n 1: 'track_angle_heading_valid',\n 2: 'pitch_valid',\n 4: 'roll_valid',\n 8: 'lateral_body_accel_valid',\n 16: 'normal_body_accel_valid',\n 32: 'turn_rate_valid',\n 64: 'hw_fail',\n 128: 'mag_invalid',\n 256: 'no_gps',\n 512: 'gps_invalid',\n 1024: 'solution_coasting',\n 2048: 'true_track_angle',\n 4096: 'magnetic_heading',\n },\n auto_sync_frequency: {\n 0: 'never',\n 1: 'occasionally',\n 2: 'frequent',\n 3: 'once_a_day',\n 4: 'remote',\n },\n exd_layout: {\n 0: 'full_screen',\n 1: 'half_vertical',\n 2: 'half_horizontal',\n 3: 'half_vertical_right_split',\n 4: 'half_horizontal_bottom_split',\n 5: 'full_quarter_split',\n 6: 'half_vertical_left_split',\n 7: 'half_horizontal_top_split',\n },\n exd_display_type: {\n 0: 'numerical',\n 1: 'simple',\n 2: 'graph',\n 3: 'bar',\n 4: 'circle_graph',\n 5: 'virtual_partner',\n 6: 'balance',\n 7: 'string_list',\n 8: 'string',\n 9: 'simple_dynamic_icon',\n 10: 'gauge',\n },\n exd_data_units: {\n 0: 'no_units',\n 1: 'laps',\n 2: 'miles_per_hour',\n 3: 'kilometers_per_hour',\n 4: 'feet_per_hour',\n 5: 'meters_per_hour',\n 6: 'degrees_celsius',\n 7: 'degrees_farenheit',\n 8: 'zone',\n 9: 'gear',\n 10: 'rpm',\n 11: 'bpm',\n 12: 'degrees',\n 13: 'millimeters',\n 14: 'meters',\n 15: 'kilometers',\n 16: 'feet',\n 17: 'yards',\n 18: 'kilofeet',\n 19: 'miles',\n 20: 'time',\n 21: 'enum_turn_type',\n 22: 'percent',\n 23: 'watts',\n 24: 'watts_per_kilogram',\n 25: 'enum_battery_status',\n 26: 'enum_bike_light_beam_angle_mode',\n 27: 'enum_bike_light_battery_status',\n 28: 'enum_bike_light_network_config_type',\n 29: 'lights',\n 30: 'seconds',\n 31: 'minutes',\n 32: 'hours',\n 33: 'calories',\n 34: 'kilojoules',\n 35: 'milliseconds',\n 36: 'second_per_mile',\n 37: 'second_per_kilometer',\n 38: 'centimeter',\n 39: 'enum_course_point',\n 40: 'bradians',\n 41: 'enum_sport',\n 42: 'inches_hg',\n 43: 'mm_hg',\n 44: 'mbars',\n 45: 'hecto_pascals',\n 46: 'feet_per_min',\n 47: 'meters_per_min',\n 48: 'meters_per_sec',\n 49: 'eight_cardinal',\n },\n exd_qualifiers: {\n 0: 'no_qualifier',\n 1: 'instantaneous',\n 2: 'average',\n 3: 'lap',\n 4: 'maximum',\n 5: 'maximum_average',\n 6: 'maximum_lap',\n 7: 'last_lap',\n 8: 'average_lap',\n 9: 'to_destination',\n 10: 'to_go',\n 11: 'to_next',\n 12: 'next_course_point',\n 13: 'total',\n 14: 'three_second_average',\n 15: 'ten_second_average',\n 16: 'thirty_second_average',\n 17: 'percent_maximum',\n 18: 'percent_maximum_average',\n 19: 'lap_percent_maximum',\n 20: 'elapsed',\n 21: 'sunrise',\n 22: 'sunset',\n 23: 'compared_to_virtual_partner',\n 24: 'maximum_24h',\n 25: 'minimum_24h',\n 26: 'minimum',\n 27: 'first',\n 28: 'second',\n 29: 'third',\n 30: 'shifter',\n 31: 'last_sport',\n 32: 'moving',\n 33: 'stopped',\n 34: 'estimated_total',\n 242: 'zone_9',\n 243: 'zone_8',\n 244: 'zone_7',\n 245: 'zone_6',\n 246: 'zone_5',\n 247: 'zone_4',\n 248: 'zone_3',\n 249: 'zone_2',\n 250: 'zone_1',\n },\n exd_descriptors: {\n 0: 'bike_light_battery_status',\n 1: 'beam_angle_status',\n 2: 'batery_level',\n 3: 'light_network_mode',\n 4: 'number_lights_connected',\n 5: 'cadence',\n 6: 'distance',\n 7: 'estimated_time_of_arrival',\n 8: 'heading',\n 9: 'time',\n 10: 'battery_level',\n 11: 'trainer_resistance',\n 12: 'trainer_target_power',\n 13: 'time_seated',\n 14: 'time_standing',\n 15: 'elevation',\n 16: 'grade',\n 17: 'ascent',\n 18: 'descent',\n 19: 'vertical_speed',\n 20: 'di2_battery_level',\n 21: 'front_gear',\n 22: 'rear_gear',\n 23: 'gear_ratio',\n 24: 'heart_rate',\n 25: 'heart_rate_zone',\n 26: 'time_in_heart_rate_zone',\n 27: 'heart_rate_reserve',\n 28: 'calories',\n 29: 'gps_accuracy',\n 30: 'gps_signal_strength',\n 31: 'temperature',\n 32: 'time_of_day',\n 33: 'balance',\n 34: 'pedal_smoothness',\n 35: 'power',\n 36: 'functional_threshold_power',\n 37: 'intensity_factor',\n 38: 'work',\n 39: 'power_ratio',\n 40: 'normalized_power',\n 41: 'training_stress_Score',\n 42: 'time_on_zone',\n 43: 'speed',\n 44: 'laps',\n 45: 'reps',\n 46: 'workout_step',\n 47: 'course_distance',\n 48: 'navigation_distance',\n 49: 'course_estimated_time_of_arrival',\n 50: 'navigation_estimated_time_of_arrival',\n 51: 'course_time',\n 52: 'navigation_time',\n 53: 'course_heading',\n 54: 'navigation_heading',\n 55: 'power_zone',\n 56: 'torque_effectiveness',\n 57: 'timer_time',\n 58: 'power_weight_ratio',\n 59: 'left_platform_center_offset',\n 60: 'right_platform_center_offset',\n 61: 'left_power_phase_start_angle',\n 62: 'right_power_phase_start_angle',\n 63: 'left_power_phase_finish_angle',\n 64: 'right_power_phase_finish_angle',\n 65: 'gears',\n 66: 'pace',\n 67: 'training_effect',\n 68: 'vertical_oscillation',\n 69: 'vertical_ratio',\n 70: 'ground_contact_time',\n 71: 'left_ground_contact_time_balance',\n 72: 'right_ground_contact_time_balance',\n 73: 'stride_length',\n 74: 'running_cadence',\n 75: 'performance_condition',\n 76: 'course_type',\n 77: 'time_in_power_zone',\n 78: 'navigation_turn',\n 79: 'course_location',\n 80: 'navigation_location',\n 81: 'compass',\n 82: 'gear_combo',\n 83: 'muscle_oxygen',\n 84: 'icon',\n 85: 'compass_heading',\n 86: 'gps_heading',\n 87: 'gps_elevation',\n 88: 'anaerobic_training_effect',\n 89: 'course',\n 90: 'off_course',\n 91: 'glide_ratio',\n 92: 'vertical_distance',\n 93: 'vmg',\n 94: 'ambient_pressure',\n 95: 'pressure',\n 96: 'vam',\n },\n auto_activity_detect: {\n 0: 'none',\n 1: 'running',\n 2: 'cycling',\n 4: 'swimming',\n 8: 'walking',\n 16: 'elliptical',\n 32: 'sedentary',\n },\n supported_exd_screen_layouts: {\n 0: 0,\n 1: 'full_screen',\n 2: 'half_vertical',\n 4: 'half_horizontal',\n 8: 'half_vertical_right_split',\n 16: 'half_horizontal_bottom_split',\n 32: 'full_quarter_split',\n 64: 'half_vertical_left_split',\n 128: 'half_horizontal_top_split',\n },\n fit_base_type: {\n 0: 'enum',\n 1: 'sint8',\n 2: 'uint8',\n 7: 'string',\n 10: 'uint8z',\n 13: 'byte',\n 131: 'sint16',\n 132: 'uint16',\n 133: 'sint32',\n 134: 'uint32',\n 136: 'float32',\n 137: 'float64',\n 139: 'uint16z',\n 140: 'uint32z',\n 142: 'sint64',\n 143: 'uint64',\n 144: 'uint64z',\n },\n turn_type: {\n 0: 'arriving_idx',\n 1: 'arriving_left_idx',\n 2: 'arriving_right_idx',\n 3: 'arriving_via_idx',\n 4: 'arriving_via_left_idx',\n 5: 'arriving_via_right_idx',\n 6: 'bear_keep_left_idx',\n 7: 'bear_keep_right_idx',\n 8: 'continue_idx',\n 9: 'exit_left_idx',\n 10: 'exit_right_idx',\n 11: 'ferry_idx',\n 12: 'roundabout_45_idx',\n 13: 'roundabout_90_idx',\n 14: 'roundabout_135_idx',\n 15: 'roundabout_180_idx',\n 16: 'roundabout_225_idx',\n 17: 'roundabout_270_idx',\n 18: 'roundabout_315_idx',\n 19: 'roundabout_360_idx',\n 20: 'roundabout_neg_45_idx',\n 21: 'roundabout_neg_90_idx',\n 22: 'roundabout_neg_135_idx',\n 23: 'roundabout_neg_180_idx',\n 24: 'roundabout_neg_225_idx',\n 25: 'roundabout_neg_270_idx',\n 26: 'roundabout_neg_315_idx',\n 27: 'roundabout_neg_360_idx',\n 28: 'roundabout_generic_idx',\n 29: 'roundabout_neg_generic_idx',\n 30: 'sharp_turn_left_idx',\n 31: 'sharp_turn_right_idx',\n 32: 'turn_left_idx',\n 33: 'turn_right_idx',\n 34: 'uturn_left_idx',\n 35: 'uturn_right_idx',\n 36: 'icon_inv_idx',\n 37: 'icon_idx_cnt',\n },\n bike_light_beam_angle_mode: {\n 0: 'manual',\n 1: 'auto',\n },\n fit_base_unit: {\n 0: 'other',\n 1: 'kilogram',\n 2: 'pound',\n },\n set_type: {\n 0: 'rest',\n 1: 'active',\n },\n exercise_category: {\n 0: 'bench_press',\n 1: 'calf_raise',\n 2: 'cardio',\n 3: 'carry',\n 4: 'chop',\n 5: 'core',\n 6: 'crunch',\n 7: 'curl',\n 8: 'deadlift',\n 9: 'flye',\n 10: 'hip_raise',\n 11: 'hip_stability',\n 12: 'hip_swing',\n 13: 'hyperextension',\n 14: 'lateral_raise',\n 15: 'leg_curl',\n 16: 'leg_raise',\n 17: 'lunge',\n 18: 'olympic_lift',\n 19: 'plank',\n 20: 'plyo',\n 21: 'pull_up',\n 22: 'push_up',\n 23: 'row',\n 24: 'shoulder_press',\n 25: 'shoulder_stability',\n 26: 'shrug',\n 27: 'sit_up',\n 28: 'squat',\n 29: 'total_body',\n 30: 'triceps_extension',\n 31: 'warm_up',\n 32: 'run',\n 65534: 'unknown',\n },\n bench_press_exercise_name: {\n 0: 'alternating_dumbbell_chest_press_on_swiss_ball',\n 1: 'barbell_bench_press',\n 2: 'barbell_board_bench_press',\n 3: 'barbell_floor_press',\n 4: 'close_grip_barbell_bench_press',\n 5: 'decline_dumbbell_bench_press',\n 6: 'dumbbell_bench_press',\n 7: 'dumbbell_floor_press',\n 8: 'incline_barbell_bench_press',\n 9: 'incline_dumbbell_bench_press',\n 10: 'incline_smith_machine_bench_press',\n 11: 'isometric_barbell_bench_press',\n 12: 'kettlebell_chest_press',\n 13: 'neutral_grip_dumbbell_bench_press',\n 14: 'neutral_grip_dumbbell_incline_bench_press',\n 15: 'one_arm_floor_press',\n 16: 'weighted_one_arm_floor_press',\n 17: 'partial_lockout',\n 18: 'reverse_grip_barbell_bench_press',\n 19: 'reverse_grip_incline_bench_press',\n 20: 'single_arm_cable_chest_press',\n 21: 'single_arm_dumbbell_bench_press',\n 22: 'smith_machine_bench_press',\n 23: 'swiss_ball_dumbbell_chest_press',\n 24: 'triple_stop_barbell_bench_press',\n 25: 'wide_grip_barbell_bench_press',\n 26: 'alternating_dumbbell_chest_press',\n },\n calf_raise_exercise_name: {\n 0: '3_way_calf_raise',\n 1: '3_way_weighted_calf_raise',\n 2: '3_way_single_leg_calf_raise',\n 3: '3_way_weighted_single_leg_calf_raise',\n 4: 'donkey_calf_raise',\n 5: 'weighted_donkey_calf_raise',\n 6: 'seated_calf_raise',\n 7: 'weighted_seated_calf_raise',\n 8: 'seated_dumbbell_toe_raise',\n 9: 'single_leg_bent_knee_calf_raise',\n 10: 'weighted_single_leg_bent_knee_calf_raise',\n 11: 'single_leg_decline_push_up',\n 12: 'single_leg_donkey_calf_raise',\n 13: 'weighted_single_leg_donkey_calf_raise',\n 14: 'single_leg_hip_raise_with_knee_hold',\n 15: 'single_leg_standing_calf_raise',\n 16: 'single_leg_standing_dumbbell_calf_raise',\n 17: 'standing_barbell_calf_raise',\n 18: 'standing_calf_raise',\n 19: 'weighted_standing_calf_raise',\n 20: 'standing_dumbbell_calf_raise',\n },\n cardio_exercise_name: {\n 0: 'bob_and_weave_circle',\n 1: 'weighted_bob_and_weave_circle',\n 2: 'cardio_core_crawl',\n 3: 'weighted_cardio_core_crawl',\n 4: 'double_under',\n 5: 'weighted_double_under',\n 6: 'jump_rope',\n 7: 'weighted_jump_rope',\n 8: 'jump_rope_crossover',\n 9: 'weighted_jump_rope_crossover',\n 10: 'jump_rope_jog',\n 11: 'weighted_jump_rope_jog',\n 12: 'jumping_jacks',\n 13: 'weighted_jumping_jacks',\n 14: 'ski_moguls',\n 15: 'weighted_ski_moguls',\n 16: 'split_jacks',\n 17: 'weighted_split_jacks',\n 18: 'squat_jacks',\n 19: 'weighted_squat_jacks',\n 20: 'triple_under',\n 21: 'weighted_triple_under',\n },\n carry_exercise_name: {\n 0: 'bar_holds',\n 1: 'farmers_walk',\n 2: 'farmers_walk_on_toes',\n 3: 'hex_dumbbell_hold',\n 4: 'overhead_carry',\n },\n chop_exercise_name: {\n 0: 'cable_pull_through',\n 1: 'cable_rotational_lift',\n 2: 'cable_woodchop',\n 3: 'cross_chop_to_knee',\n 4: 'weighted_cross_chop_to_knee',\n 5: 'dumbbell_chop',\n 6: 'half_kneeling_rotation',\n 7: 'weighted_half_kneeling_rotation',\n 8: 'half_kneeling_rotational_chop',\n 9: 'half_kneeling_rotational_reverse_chop',\n 10: 'half_kneeling_stability_chop',\n 11: 'half_kneeling_stability_reverse_chop',\n 12: 'kneeling_rotational_chop',\n 13: 'kneeling_rotational_reverse_chop',\n 14: 'kneeling_stability_chop',\n 15: 'kneeling_woodchopper',\n 16: 'medicine_ball_wood_chops',\n 17: 'power_squat_chops',\n 18: 'weighted_power_squat_chops',\n 19: 'standing_rotational_chop',\n 20: 'standing_split_rotational_chop',\n 21: 'standing_split_rotational_reverse_chop',\n 22: 'standing_stability_reverse_chop',\n },\n core_exercise_name: {\n 0: 'abs_jabs',\n 1: 'weighted_abs_jabs',\n 2: 'alternating_plate_reach',\n 3: 'barbell_rollout',\n 4: 'weighted_barbell_rollout',\n 5: 'body_bar_oblique_twist',\n 6: 'cable_core_press',\n 7: 'cable_side_bend',\n 8: 'side_bend',\n 9: 'weighted_side_bend',\n 10: 'crescent_circle',\n 11: 'weighted_crescent_circle',\n 12: 'cycling_russian_twist',\n 13: 'weighted_cycling_russian_twist',\n 14: 'elevated_feet_russian_twist',\n 15: 'weighted_elevated_feet_russian_twist',\n 16: 'half_turkish_get_up',\n 17: 'kettlebell_windmill',\n 18: 'kneeling_ab_wheel',\n 19: 'weighted_kneeling_ab_wheel',\n 20: 'modified_front_lever',\n 21: 'open_knee_tucks',\n 22: 'weighted_open_knee_tucks',\n 23: 'side_abs_leg_lift',\n 24: 'weighted_side_abs_leg_lift',\n 25: 'swiss_ball_jackknife',\n 26: 'weighted_swiss_ball_jackknife',\n 27: 'swiss_ball_pike',\n 28: 'weighted_swiss_ball_pike',\n 29: 'swiss_ball_rollout',\n 30: 'weighted_swiss_ball_rollout',\n 31: 'triangle_hip_press',\n 32: 'weighted_triangle_hip_press',\n 33: 'trx_suspended_jackknife',\n 34: 'weighted_trx_suspended_jackknife',\n 35: 'u_boat',\n 36: 'weighted_u_boat',\n 37: 'windmill_switches',\n 38: 'weighted_windmill_switches',\n 39: 'alternating_slide_out',\n 40: 'weighted_alternating_slide_out',\n 41: 'ghd_back_extensions',\n 42: 'weighted_ghd_back_extensions',\n 43: 'overhead_walk',\n 44: 'inchworm',\n 45: 'weighted_modified_front_lever',\n 46: 'russian_twist',\n 47: 'abdominal_leg_rotations',\n 48: 'arm_and_leg_extension_on_knees',\n 49: 'bicycle',\n 50: 'bicep_curl_with_leg_extension',\n 51: 'cat_cow',\n 52: 'corkscrew',\n 53: 'criss_cross',\n 54: 'criss_cross_with_ball',\n 55: 'double_leg_stretch',\n 56: 'knee_folds',\n 57: 'lower_lift',\n 58: 'neck_pull',\n 59: 'pelvic_clocks',\n 60: 'roll_over',\n 61: 'roll_up',\n 62: 'rolling',\n 63: 'rowing_1',\n 64: 'rowing_2',\n 65: 'scissors',\n 66: 'single_leg_circles',\n 67: 'single_leg_stretch',\n 68: 'snake_twist_1_and_2',\n 69: 'swan',\n 70: 'swimming',\n 71: 'teaser',\n 72: 'the_hundred',\n },\n crunch_exercise_name: {\n 0: 'bicycle_crunch',\n 1: 'cable_crunch',\n 2: 'circular_arm_crunch',\n 3: 'crossed_arms_crunch',\n 4: 'weighted_crossed_arms_crunch',\n 5: 'cross_leg_reverse_crunch',\n 6: 'weighted_cross_leg_reverse_crunch',\n 7: 'crunch_chop',\n 8: 'weighted_crunch_chop',\n 9: 'double_crunch',\n 10: 'weighted_double_crunch',\n 11: 'elbow_to_knee_crunch',\n 12: 'weighted_elbow_to_knee_crunch',\n 13: 'flutter_kicks',\n 14: 'weighted_flutter_kicks',\n 15: 'foam_roller_reverse_crunch_on_bench',\n 16: 'weighted_foam_roller_reverse_crunch_on_bench',\n 17: 'foam_roller_reverse_crunch_with_dumbbell',\n 18: 'foam_roller_reverse_crunch_with_medicine_ball',\n 19: 'frog_press',\n 20: 'hanging_knee_raise_oblique_crunch',\n 21: 'weighted_hanging_knee_raise_oblique_crunch',\n 22: 'hip_crossover',\n 23: 'weighted_hip_crossover',\n 24: 'hollow_rock',\n 25: 'weighted_hollow_rock',\n 26: 'incline_reverse_crunch',\n 27: 'weighted_incline_reverse_crunch',\n 28: 'kneeling_cable_crunch',\n 29: 'kneeling_cross_crunch',\n 30: 'weighted_kneeling_cross_crunch',\n 31: 'kneeling_oblique_cable_crunch',\n 32: 'knees_to_elbow',\n 33: 'leg_extensions',\n 34: 'weighted_leg_extensions',\n 35: 'leg_levers',\n 36: 'mcgill_curl_up',\n 37: 'weighted_mcgill_curl_up',\n 38: 'modified_pilates_roll_up_with_ball',\n 39: 'weighted_modified_pilates_roll_up_with_ball',\n 40: 'pilates_crunch',\n 41: 'weighted_pilates_crunch',\n 42: 'pilates_roll_up_with_ball',\n 43: 'weighted_pilates_roll_up_with_ball',\n 44: 'raised_legs_crunch',\n 45: 'weighted_raised_legs_crunch',\n 46: 'reverse_crunch',\n 47: 'weighted_reverse_crunch',\n 48: 'reverse_crunch_on_a_bench',\n 49: 'weighted_reverse_crunch_on_a_bench',\n 50: 'reverse_curl_and_lift',\n 51: 'weighted_reverse_curl_and_lift',\n 52: 'rotational_lift',\n 53: 'weighted_rotational_lift',\n 54: 'seated_alternating_reverse_crunch',\n 55: 'weighted_seated_alternating_reverse_crunch',\n 56: 'seated_leg_u',\n 57: 'weighted_seated_leg_u',\n 58: 'side_to_side_crunch_and_weave',\n 59: 'weighted_side_to_side_crunch_and_weave',\n 60: 'single_leg_reverse_crunch',\n 61: 'weighted_single_leg_reverse_crunch',\n 62: 'skater_crunch_cross',\n 63: 'weighted_skater_crunch_cross',\n 64: 'standing_cable_crunch',\n 65: 'standing_side_crunch',\n 66: 'step_climb',\n 67: 'weighted_step_climb',\n 68: 'swiss_ball_crunch',\n 69: 'swiss_ball_reverse_crunch',\n 70: 'weighted_swiss_ball_reverse_crunch',\n 71: 'swiss_ball_russian_twist',\n 72: 'weighted_swiss_ball_russian_twist',\n 73: 'swiss_ball_side_crunch',\n 74: 'weighted_swiss_ball_side_crunch',\n 75: 'thoracic_crunches_on_foam_roller',\n 76: 'weighted_thoracic_crunches_on_foam_roller',\n 77: 'triceps_crunch',\n 78: 'weighted_bicycle_crunch',\n 79: 'weighted_crunch',\n 80: 'weighted_swiss_ball_crunch',\n 81: 'toes_to_bar',\n 82: 'weighted_toes_to_bar',\n 83: 'crunch',\n 84: 'straight_leg_crunch_with_ball',\n },\n curl_exercise_name: {\n 0: 'alternating_dumbbell_biceps_curl',\n 1: 'alternating_dumbbell_biceps_curl_on_swiss_ball',\n 2: 'alternating_incline_dumbbell_biceps_curl',\n 3: 'barbell_biceps_curl',\n 4: 'barbell_reverse_wrist_curl',\n 5: 'barbell_wrist_curl',\n 6: 'behind_the_back_barbell_reverse_wrist_curl',\n 7: 'behind_the_back_one_arm_cable_curl',\n 8: 'cable_biceps_curl',\n 9: 'cable_hammer_curl',\n 10: 'cheating_barbell_biceps_curl',\n 11: 'close_grip_ez_bar_biceps_curl',\n 12: 'cross_body_dumbbell_hammer_curl',\n 13: 'dead_hang_biceps_curl',\n 14: 'decline_hammer_curl',\n 15: 'dumbbell_biceps_curl_with_static_hold',\n 16: 'dumbbell_hammer_curl',\n 17: 'dumbbell_reverse_wrist_curl',\n 18: 'dumbbell_wrist_curl',\n 19: 'ez_bar_preacher_curl',\n 20: 'forward_bend_biceps_curl',\n 21: 'hammer_curl_to_press',\n 22: 'incline_dumbbell_biceps_curl',\n 23: 'incline_offset_thumb_dumbbell_curl',\n 24: 'kettlebell_biceps_curl',\n 25: 'lying_concentration_cable_curl',\n 26: 'one_arm_preacher_curl',\n 27: 'plate_pinch_curl',\n 28: 'preacher_curl_with_cable',\n 29: 'reverse_ez_bar_curl',\n 30: 'reverse_grip_wrist_curl',\n 31: 'reverse_grip_barbell_biceps_curl',\n 32: 'seated_alternating_dumbbell_biceps_curl',\n 33: 'seated_dumbbell_biceps_curl',\n 34: 'seated_reverse_dumbbell_curl',\n 35: 'split_stance_offset_pinky_dumbbell_curl',\n 36: 'standing_alternating_dumbbell_curls',\n 37: 'standing_dumbbell_biceps_curl',\n 38: 'standing_ez_bar_biceps_curl',\n 39: 'static_curl',\n 40: 'swiss_ball_dumbbell_overhead_triceps_extension',\n 41: 'swiss_ball_ez_bar_preacher_curl',\n 42: 'twisting_standing_dumbbell_biceps_curl',\n 43: 'wide_grip_ez_bar_biceps_curl',\n },\n deadlift_exercise_name: {\n 0: 'barbell_deadlift',\n 1: 'barbell_straight_leg_deadlift',\n 2: 'dumbbell_deadlift',\n 3: 'dumbbell_single_leg_deadlift_to_row',\n 4: 'dumbbell_straight_leg_deadlift',\n 5: 'kettlebell_floor_to_shelf',\n 6: 'one_arm_one_leg_deadlift',\n 7: 'rack_pull',\n 8: 'rotational_dumbbell_straight_leg_deadlift',\n 9: 'single_arm_deadlift',\n 10: 'single_leg_barbell_deadlift',\n 11: 'single_leg_barbell_straight_leg_deadlift',\n 12: 'single_leg_deadlift_with_barbell',\n 13: 'single_leg_rdl_circuit',\n 14: 'single_leg_romanian_deadlift_with_dumbbell',\n 15: 'sumo_deadlift',\n 16: 'sumo_deadlift_high_pull',\n 17: 'trap_bar_deadlift',\n 18: 'wide_grip_barbell_deadlift',\n },\n flye_exercise_name: {\n 0: 'cable_crossover',\n 1: 'decline_dumbbell_flye',\n 2: 'dumbbell_flye',\n 3: 'incline_dumbbell_flye',\n 4: 'kettlebell_flye',\n 5: 'kneeling_rear_flye',\n 6: 'single_arm_standing_cable_reverse_flye',\n 7: 'swiss_ball_dumbbell_flye',\n 8: 'arm_rotations',\n 9: 'hug_a_tree',\n },\n hip_raise_exercise_name: {\n 0: 'barbell_hip_thrust_on_floor',\n 1: 'barbell_hip_thrust_with_bench',\n 2: 'bent_knee_swiss_ball_reverse_hip_raise',\n 3: 'weighted_bent_knee_swiss_ball_reverse_hip_raise',\n 4: 'bridge_with_leg_extension',\n 5: 'weighted_bridge_with_leg_extension',\n 6: 'clam_bridge',\n 7: 'front_kick_tabletop',\n 8: 'weighted_front_kick_tabletop',\n 9: 'hip_extension_and_cross',\n 10: 'weighted_hip_extension_and_cross',\n 11: 'hip_raise',\n 12: 'weighted_hip_raise',\n 13: 'hip_raise_with_feet_on_swiss_ball',\n 14: 'weighted_hip_raise_with_feet_on_swiss_ball',\n 15: 'hip_raise_with_head_on_bosu_ball',\n 16: 'weighted_hip_raise_with_head_on_bosu_ball',\n 17: 'hip_raise_with_head_on_swiss_ball',\n 18: 'weighted_hip_raise_with_head_on_swiss_ball',\n 19: 'hip_raise_with_knee_squeeze',\n 20: 'weighted_hip_raise_with_knee_squeeze',\n 21: 'incline_rear_leg_extension',\n 22: 'weighted_incline_rear_leg_extension',\n 23: 'kettlebell_swing',\n 24: 'marching_hip_raise',\n 25: 'weighted_marching_hip_raise',\n 26: 'marching_hip_raise_with_feet_on_a_swiss_ball',\n 27: 'weighted_marching_hip_raise_with_feet_on_a_swiss_ball',\n 28: 'reverse_hip_raise',\n 29: 'weighted_reverse_hip_raise',\n 30: 'single_leg_hip_raise',\n 31: 'weighted_single_leg_hip_raise',\n 32: 'single_leg_hip_raise_with_foot_on_bench',\n 33: 'weighted_single_leg_hip_raise_with_foot_on_bench',\n 34: 'single_leg_hip_raise_with_foot_on_bosu_ball',\n 35: 'weighted_single_leg_hip_raise_with_foot_on_bosu_ball',\n 36: 'single_leg_hip_raise_with_foot_on_foam_roller',\n 37: 'weighted_single_leg_hip_raise_with_foot_on_foam_roller',\n 38: 'single_leg_hip_raise_with_foot_on_medicine_ball',\n 39: 'weighted_single_leg_hip_raise_with_foot_on_medicine_ball',\n 40: 'single_leg_hip_raise_with_head_on_bosu_ball',\n 41: 'weighted_single_leg_hip_raise_with_head_on_bosu_ball',\n 42: 'weighted_clam_bridge',\n 43: 'single_leg_swiss_ball_hip_raise_and_leg_curl',\n 44: 'clams',\n 45: 'inner_thigh_circles',\n 46: 'inner_thigh_side_lift',\n 47: 'leg_circles',\n 48: 'leg_lift',\n 49: 'leg_lift_in_external_rotation',\n },\n hip_stability_exercise_name: {\n 0: 'band_side_lying_leg_raise',\n 1: 'dead_bug',\n 2: 'weighted_dead_bug',\n 3: 'external_hip_raise',\n 4: 'weighted_external_hip_raise',\n 5: 'fire_hydrant_kicks',\n 6: 'weighted_fire_hydrant_kicks',\n 7: 'hip_circles',\n 8: 'weighted_hip_circles',\n 9: 'inner_thigh_lift',\n 10: 'weighted_inner_thigh_lift',\n 11: 'lateral_walks_with_band_at_ankles',\n 12: 'pretzel_side_kick',\n 13: 'weighted_pretzel_side_kick',\n 14: 'prone_hip_internal_rotation',\n 15: 'weighted_prone_hip_internal_rotation',\n 16: 'quadruped',\n 17: 'quadruped_hip_extension',\n 18: 'weighted_quadruped_hip_extension',\n 19: 'quadruped_with_leg_lift',\n 20: 'weighted_quadruped_with_leg_lift',\n 21: 'side_lying_leg_raise',\n 22: 'weighted_side_lying_leg_raise',\n 23: 'sliding_hip_adduction',\n 24: 'weighted_sliding_hip_adduction',\n 25: 'standing_adduction',\n 26: 'weighted_standing_adduction',\n 27: 'standing_cable_hip_abduction',\n 28: 'standing_hip_abduction',\n 29: 'weighted_standing_hip_abduction',\n 30: 'standing_rear_leg_raise',\n 31: 'weighted_standing_rear_leg_raise',\n 32: 'supine_hip_internal_rotation',\n 33: 'weighted_supine_hip_internal_rotation',\n },\n hip_swing_excercise_name: {\n 0: 'single_arm_kettlebell_swing',\n 1: 'single_arm_dumbbell_swing',\n 2: 'step_out_swing',\n },\n hyperextension_exercise_name: {\n 0: 'back_extension_with_opposite_arm_and_leg_reach',\n 1: 'weighted_back_extension_with_opposite_arm_and_leg_reach',\n 2: 'base_rotations',\n 3: 'weighted_base_rotations',\n 4: 'bent_knee_reverse_hyperextension',\n 5: 'weighted_bent_knee_reverse_hyperextension',\n 6: 'hollow_hold_and_roll',\n 7: 'weighted_hollow_hold_and_roll',\n 8: 'kicks',\n 9: 'weighted_kicks',\n 10: 'knee_raises',\n 11: 'weighted_knee_raises',\n 12: 'kneeling_superman',\n 13: 'weighted_kneeling_superman',\n 14: 'lat_pull_down_with_row',\n 15: 'medicine_ball_deadlift_to_reach',\n 16: 'one_arm_one_leg_row',\n 17: 'one_arm_row_with_band',\n 18: 'overhead_lunge_with_medicine_ball',\n 19: 'plank_knee_tucks',\n 20: 'weighted_plank_knee_tucks',\n 21: 'side_step',\n 22: 'weighted_side_step',\n 23: 'single_leg_back_extension',\n 24: 'weighted_single_leg_back_extension',\n 25: 'spine_extension',\n 26: 'weighted_spine_extension',\n 27: 'static_back_extension',\n 28: 'weighted_static_back_extension',\n 29: 'superman_from_floor',\n 30: 'weighted_superman_from_floor',\n 31: 'swiss_ball_back_extension',\n 32: 'weighted_swiss_ball_back_extension',\n 33: 'swiss_ball_hyperextension',\n 34: 'weighted_swiss_ball_hyperextension',\n 35: 'swiss_ball_opposite_arm_and_leg_lift',\n 36: 'weighted_swiss_ball_opposite_arm_and_leg_lift',\n 37: 'superman_on_swiss_ball',\n 38: 'cobra',\n 39: 'supine_floor_barre',\n },\n lateral_raise_exercise_name: {\n 0: '45_degree_cable_external_rotation',\n 1: 'alternating_lateral_raise_with_static_hold',\n 2: 'bar_muscle_up',\n 3: 'bent_over_lateral_raise',\n 4: 'cable_diagonal_raise',\n 5: 'cable_front_raise',\n 6: 'calorie_row',\n 7: 'combo_shoulder_raise',\n 8: 'dumbbell_diagonal_raise',\n 9: 'dumbbell_v_raise',\n 10: 'front_raise',\n 11: 'leaning_dumbbell_lateral_raise',\n 12: 'lying_dumbbell_raise',\n 13: 'muscle_up',\n 14: 'one_arm_cable_lateral_raise',\n 15: 'overhand_grip_rear_lateral_raise',\n 16: 'plate_raises',\n 17: 'ring_dip',\n 18: 'weighted_ring_dip',\n 19: 'ring_muscle_up',\n 20: 'weighted_ring_muscle_up',\n 21: 'rope_climb',\n 22: 'weighted_rope_climb',\n 23: 'scaption',\n 24: 'seated_lateral_raise',\n 25: 'seated_rear_lateral_raise',\n 26: 'side_lying_lateral_raise',\n 27: 'standing_lift',\n 28: 'suspended_row',\n 29: 'underhand_grip_rear_lateral_raise',\n 30: 'wall_slide',\n 31: 'weighted_wall_slide',\n 32: 'arm_circles',\n 33: 'shaving_the_head',\n },\n leg_curl_exercise_name: {\n 0: 'leg_curl',\n 1: 'weighted_leg_curl',\n 2: 'good_morning',\n 3: 'seated_barbell_good_morning',\n 4: 'single_leg_barbell_good_morning',\n 5: 'single_leg_sliding_leg_curl',\n 6: 'sliding_leg_curl',\n 7: 'split_barbell_good_morning',\n 8: 'split_stance_extension',\n 9: 'staggered_stance_good_morning',\n 10: 'swiss_ball_hip_raise_and_leg_curl',\n 11: 'zercher_good_morning',\n },\n leg_raise_exercise_name: {\n 0: 'hanging_knee_raise',\n 1: 'hanging_leg_raise',\n 2: 'weighted_hanging_leg_raise',\n 3: 'hanging_single_leg_raise',\n 4: 'weighted_hanging_single_leg_raise',\n 5: 'kettlebell_leg_raises',\n 6: 'leg_lowering_drill',\n 7: 'weighted_leg_lowering_drill',\n 8: 'lying_straight_leg_raise',\n 9: 'weighted_lying_straight_leg_raise',\n 10: 'medicine_ball_leg_drops',\n 11: 'quadruped_leg_raise',\n 12: 'weighted_quadruped_leg_raise',\n 13: 'reverse_leg_raise',\n 14: 'weighted_reverse_leg_raise',\n 15: 'reverse_leg_raise_on_swiss_ball',\n 16: 'weighted_reverse_leg_raise_on_swiss_ball',\n 17: 'single_leg_lowering_drill',\n 18: 'weighted_single_leg_lowering_drill',\n 19: 'weighted_hanging_knee_raise',\n 20: 'lateral_stepover',\n 21: 'weighted_lateral_stepover',\n },\n lunge_exercise_name: {\n 0: 'overhead_lunge',\n 1: 'lunge_matrix',\n 2: 'weighted_lunge_matrix',\n 3: 'alternating_barbell_forward_lunge',\n 4: 'alternating_dumbbell_lunge_with_reach',\n 5: 'back_foot_elevated_dumbbell_split_squat',\n 6: 'barbell_box_lunge',\n 7: 'barbell_bulgarian_split_squat',\n 8: 'barbell_crossover_lunge',\n 9: 'barbell_front_split_squat',\n 10: 'barbell_lunge',\n 11: 'barbell_reverse_lunge',\n 12: 'barbell_side_lunge',\n 13: 'barbell_split_squat',\n 14: 'core_control_rear_lunge',\n 15: 'diagonal_lunge',\n 16: 'drop_lunge',\n 17: 'dumbbell_box_lunge',\n 18: 'dumbbell_bulgarian_split_squat',\n 19: 'dumbbell_crossover_lunge',\n 20: 'dumbbell_diagonal_lunge',\n 21: 'dumbbell_lunge',\n 22: 'dumbbell_lunge_and_rotation',\n 23: 'dumbbell_overhead_bulgarian_split_squat',\n 24: 'dumbbell_reverse_lunge_to_high_knee_and_press',\n 25: 'dumbbell_side_lunge',\n 26: 'elevated_front_foot_barbell_split_squat',\n 27: 'front_foot_elevated_dumbbell_split_squat',\n 28: 'gunslinger_lunge',\n 29: 'lawnmower_lunge',\n 30: 'low_lunge_with_isometric_adduction',\n 31: 'low_side_to_side_lunge',\n 32: 'lunge',\n 33: 'weighted_lunge',\n 34: 'lunge_with_arm_reach',\n 35: 'lunge_with_diagonal_reach',\n 36: 'lunge_with_side_bend',\n 37: 'offset_dumbbell_lunge',\n 38: 'offset_dumbbell_reverse_lunge',\n 39: 'overhead_bulgarian_split_squat',\n 40: 'overhead_dumbbell_reverse_lunge',\n 41: 'overhead_dumbbell_split_squat',\n 42: 'overhead_lunge_with_rotation',\n 43: 'reverse_barbell_box_lunge',\n 44: 'reverse_box_lunge',\n 45: 'reverse_dumbbell_box_lunge',\n 46: 'reverse_dumbbell_crossover_lunge',\n 47: 'reverse_dumbbell_diagonal_lunge',\n 48: 'reverse_lunge_with_reach_back',\n 49: 'weighted_reverse_lunge_with_reach_back',\n 50: 'reverse_lunge_with_twist_and_overhead_reach',\n 51: 'weighted_reverse_lunge_with_twist_and_overhead_reach',\n 52: 'reverse_sliding_box_lunge',\n 53: 'weighted_reverse_sliding_box_lunge',\n 54: 'reverse_sliding_lunge',\n 55: 'weighted_reverse_sliding_lunge',\n 56: 'runners_lunge_to_balance',\n 57: 'weighted_runners_lunge_to_balance',\n 58: 'shifting_side_lunge',\n 59: 'side_and_crossover_lunge',\n 60: 'weighted_side_and_crossover_lunge',\n 61: 'side_lunge',\n 62: 'weighted_side_lunge',\n 63: 'side_lunge_and_press',\n 64: 'side_lunge_jump_off',\n 65: 'side_lunge_sweep',\n 66: 'weighted_side_lunge_sweep',\n 67: 'side_lunge_to_crossover_tap',\n 68: 'weighted_side_lunge_to_crossover_tap',\n 69: 'side_to_side_lunge_chops',\n 70: 'weighted_side_to_side_lunge_chops',\n 71: 'siff_jump_lunge',\n 72: 'weighted_siff_jump_lunge',\n 73: 'single_arm_reverse_lunge_and_press',\n 74: 'sliding_lateral_lunge',\n 75: 'weighted_sliding_lateral_lunge',\n 76: 'walking_barbell_lunge',\n 77: 'walking_dumbbell_lunge',\n 78: 'walking_lunge',\n 79: 'weighted_walking_lunge',\n 80: 'wide_grip_overhead_barbell_split_squat',\n },\n olympic_lift_exercise_name: {\n 0: 'barbell_hang_power_clean',\n 1: 'barbell_hang_squat_clean',\n 2: 'barbell_power_clean',\n 3: 'barbell_power_snatch',\n 4: 'barbell_squat_clean',\n 5: 'clean_and_jerk',\n 6: 'barbell_hang_power_snatch',\n 7: 'barbell_hang_pull',\n 8: 'barbell_high_pull',\n 9: 'barbell_snatch',\n 10: 'barbell_split_jerk',\n 11: 'clean',\n 12: 'dumbbell_clean',\n 13: 'dumbbell_hang_pull',\n 14: 'one_hand_dumbbell_split_snatch',\n 15: 'push_jerk',\n 16: 'single_arm_dumbbell_snatch',\n 17: 'single_arm_hang_snatch',\n 18: 'single_arm_kettlebell_snatch',\n 19: 'split_jerk',\n 20: 'squat_clean_and_jerk',\n },\n plank_exercise_name: {\n 0: '45_degree_plank',\n 1: 'weighted_45_degree_plank',\n 2: '90_degree_static_hold',\n 3: 'weighted_90_degree_static_hold',\n 4: 'bear_crawl',\n 5: 'weighted_bear_crawl',\n 6: 'cross_body_mountain_climber',\n 7: 'weighted_cross_body_mountain_climber',\n 8: 'elbow_plank_pike_jacks',\n 9: 'weighted_elbow_plank_pike_jacks',\n 10: 'elevated_feet_plank',\n 11: 'weighted_elevated_feet_plank',\n 12: 'elevator_abs',\n 13: 'weighted_elevator_abs',\n 14: 'extended_plank',\n 15: 'weighted_extended_plank',\n 16: 'full_plank_passe_twist',\n 17: 'weighted_full_plank_passe_twist',\n 18: 'inching_elbow_plank',\n 19: 'weighted_inching_elbow_plank',\n 20: 'inchworm_to_side_plank',\n 21: 'weighted_inchworm_to_side_plank',\n 22: 'kneeling_plank',\n 23: 'weighted_kneeling_plank',\n 24: 'kneeling_side_plank_with_leg_lift',\n 25: 'weighted_kneeling_side_plank_with_leg_lift',\n 26: 'lateral_roll',\n 27: 'weighted_lateral_roll',\n 28: 'lying_reverse_plank',\n 29: 'weighted_lying_reverse_plank',\n 30: 'medicine_ball_mountain_climber',\n 31: 'weighted_medicine_ball_mountain_climber',\n 32: 'modified_mountain_climber_and_extension',\n 33: 'weighted_modified_mountain_climber_and_extension',\n 34: 'mountain_climber',\n 35: 'weighted_mountain_climber',\n 36: 'mountain_climber_on_sliding_discs',\n 37: 'weighted_mountain_climber_on_sliding_discs',\n 38: 'mountain_climber_with_feet_on_bosu_ball',\n 39: 'weighted_mountain_climber_with_feet_on_bosu_ball',\n 40: 'mountain_climber_with_hands_on_bench',\n 41: 'mountain_climber_with_hands_on_swiss_ball',\n 42: 'weighted_mountain_climber_with_hands_on_swiss_ball',\n 43: 'plank',\n 44: 'plank_jacks_with_feet_on_sliding_discs',\n 45: 'weighted_plank_jacks_with_feet_on_sliding_discs',\n 46: 'plank_knee_twist',\n 47: 'weighted_plank_knee_twist',\n 48: 'plank_pike_jumps',\n 49: 'weighted_plank_pike_jumps',\n 50: 'plank_pikes',\n 51: 'weighted_plank_pikes',\n 52: 'plank_to_stand_up',\n 53: 'weighted_plank_to_stand_up',\n 54: 'plank_with_arm_raise',\n 55: 'weighted_plank_with_arm_raise',\n 56: 'plank_with_knee_to_elbow',\n 57: 'weighted_plank_with_knee_to_elbow',\n 58: 'plank_with_oblique_crunch',\n 59: 'weighted_plank_with_oblique_crunch',\n 60: 'plyometric_side_plank',\n 61: 'weighted_plyometric_side_plank',\n 62: 'rolling_side_plank',\n 63: 'weighted_rolling_side_plank',\n 64: 'side_kick_plank',\n 65: 'weighted_side_kick_plank',\n 66: 'side_plank',\n 67: 'weighted_side_plank',\n 68: 'side_plank_and_row',\n 69: 'weighted_side_plank_and_row',\n 70: 'side_plank_lift',\n 71: 'weighted_side_plank_lift',\n 72: 'side_plank_with_elbow_on_bosu_ball',\n 73: 'weighted_side_plank_with_elbow_on_bosu_ball',\n 74: 'side_plank_with_feet_on_bench',\n 75: 'weighted_side_plank_with_feet_on_bench',\n 76: 'side_plank_with_knee_circle',\n 77: 'weighted_side_plank_with_knee_circle',\n 78: 'side_plank_with_knee_tuck',\n 79: 'weighted_side_plank_with_knee_tuck',\n 80: 'side_plank_with_leg_lift',\n 81: 'weighted_side_plank_with_leg_lift',\n 82: 'side_plank_with_reach_under',\n 83: 'weighted_side_plank_with_reach_under',\n 84: 'single_leg_elevated_feet_plank',\n 85: 'weighted_single_leg_elevated_feet_plank',\n 86: 'single_leg_flex_and_extend',\n 87: 'weighted_single_leg_flex_and_extend',\n 88: 'single_leg_side_plank',\n 89: 'weighted_single_leg_side_plank',\n 90: 'spiderman_plank',\n 91: 'weighted_spiderman_plank',\n 92: 'straight_arm_plank',\n 93: 'weighted_straight_arm_plank',\n 94: 'straight_arm_plank_with_shoulder_touch',\n 95: 'weighted_straight_arm_plank_with_shoulder_touch',\n 96: 'swiss_ball_plank',\n 97: 'weighted_swiss_ball_plank',\n 98: 'swiss_ball_plank_leg_lift',\n 99: 'weighted_swiss_ball_plank_leg_lift',\n 100: 'swiss_ball_plank_leg_lift_and_hold',\n 101: 'swiss_ball_plank_with_feet_on_bench',\n 102: 'weighted_swiss_ball_plank_with_feet_on_bench',\n 103: 'swiss_ball_prone_jackknife',\n 104: 'weighted_swiss_ball_prone_jackknife',\n 105: 'swiss_ball_side_plank',\n 106: 'weighted_swiss_ball_side_plank',\n 107: 'three_way_plank',\n 108: 'weighted_three_way_plank',\n 109: 'towel_plank_and_knee_in',\n 110: 'weighted_towel_plank_and_knee_in',\n 111: 't_stabilization',\n 112: 'weighted_t_stabilization',\n 113: 'turkish_get_up_to_side_plank',\n 114: 'weighted_turkish_get_up_to_side_plank',\n 115: 'two_point_plank',\n 116: 'weighted_two_point_plank',\n 117: 'weighted_plank',\n 118: 'wide_stance_plank_with_diagonal_arm_lift',\n 119: 'weighted_wide_stance_plank_with_diagonal_arm_lift',\n 120: 'wide_stance_plank_with_diagonal_leg_lift',\n 121: 'weighted_wide_stance_plank_with_diagonal_leg_lift',\n 122: 'wide_stance_plank_with_leg_lift',\n 123: 'weighted_wide_stance_plank_with_leg_lift',\n 124: 'wide_stance_plank_with_opposite_arm_and_leg_lift',\n 125: 'weighted_mountain_climber_with_hands_on_bench',\n 126: 'weighted_swiss_ball_plank_leg_lift_and_hold',\n 127: 'weighted_wide_stance_plank_with_opposite_arm_and_leg_lift',\n 128: 'plank_with_feet_on_swiss_ball',\n 129: 'side_plank_to_plank_with_reach_under',\n 130: 'bridge_with_glute_lower_lift',\n 131: 'bridge_one_leg_bridge',\n 132: 'plank_with_arm_variations',\n 133: 'plank_with_leg_lift',\n 134: 'reverse_plank_with_leg_pull',\n },\n plyo_exercise_name: {\n 0: 'alternating_jump_lunge',\n 1: 'weighted_alternating_jump_lunge',\n 2: 'barbell_jump_squat',\n 3: 'body_weight_jump_squat',\n 4: 'weighted_jump_squat',\n 5: 'cross_knee_strike',\n 6: 'weighted_cross_knee_strike',\n 7: 'depth_jump',\n 8: 'weighted_depth_jump',\n 9: 'dumbbell_jump_squat',\n 10: 'dumbbell_split_jump',\n 11: 'front_knee_strike',\n 12: 'weighted_front_knee_strike',\n 13: 'high_box_jump',\n 14: 'weighted_high_box_jump',\n 15: 'isometric_explosive_body_weight_jump_squat',\n 16: 'weighted_isometric_explosive_jump_squat',\n 17: 'lateral_leap_and_hop',\n 18: 'weighted_lateral_leap_and_hop',\n 19: 'lateral_plyo_squats',\n 20: 'weighted_lateral_plyo_squats',\n 21: 'lateral_slide',\n 22: 'weighted_lateral_slide',\n 23: 'medicine_ball_overhead_throws',\n 24: 'medicine_ball_side_throw',\n 25: 'medicine_ball_slam',\n 26: 'side_to_side_medicine_ball_throws',\n 27: 'side_to_side_shuffle_jump',\n 28: 'weighted_side_to_side_shuffle_jump',\n 29: 'squat_jump_onto_box',\n 30: 'weighted_squat_jump_onto_box',\n 31: 'squat_jumps_in_and_out',\n 32: 'weighted_squat_jumps_in_and_out',\n },\n pull_up_exercise_name: {\n 0: 'banded_pull_ups',\n 1: '30_degree_lat_pulldown',\n 2: 'band_assisted_chin_up',\n 3: 'close_grip_chin_up',\n 4: 'weighted_close_grip_chin_up',\n 5: 'close_grip_lat_pulldown',\n 6: 'crossover_chin_up',\n 7: 'weighted_crossover_chin_up',\n 8: 'ez_bar_pullover',\n 9: 'hanging_hurdle',\n 10: 'weighted_hanging_hurdle',\n 11: 'kneeling_lat_pulldown',\n 12: 'kneeling_underhand_grip_lat_pulldown',\n 13: 'lat_pulldown',\n 14: 'mixed_grip_chin_up',\n 15: 'weighted_mixed_grip_chin_up',\n 16: 'mixed_grip_pull_up',\n 17: 'weighted_mixed_grip_pull_up',\n 18: 'reverse_grip_pulldown',\n 19: 'standing_cable_pullover',\n 20: 'straight_arm_pulldown',\n 21: 'swiss_ball_ez_bar_pullover',\n 22: 'towel_pull_up',\n 23: 'weighted_towel_pull_up',\n 24: 'weighted_pull_up',\n 25: 'wide_grip_lat_pulldown',\n 26: 'wide_grip_pull_up',\n 27: 'weighted_wide_grip_pull_up',\n 28: 'burpee_pull_up',\n 29: 'weighted_burpee_pull_up',\n 30: 'jumping_pull_ups',\n 31: 'weighted_jumping_pull_ups',\n 32: 'kipping_pull_up',\n 33: 'weighted_kipping_pull_up',\n 34: 'l_pull_up',\n 35: 'weighted_l_pull_up',\n 36: 'suspended_chin_up',\n 37: 'weighted_suspended_chin_up',\n 38: 'pull_up',\n },\n push_up_exercise_name: {\n 0: 'chest_press_with_band',\n 1: 'alternating_staggered_push_up',\n 2: 'weighted_alternating_staggered_push_up',\n 3: 'alternating_hands_medicine_ball_push_up',\n 4: 'weighted_alternating_hands_medicine_ball_push_up',\n 5: 'bosu_ball_push_up',\n 6: 'weighted_bosu_ball_push_up',\n 7: 'clapping_push_up',\n 8: 'weighted_clapping_push_up',\n 9: 'close_grip_medicine_ball_push_up',\n 10: 'weighted_close_grip_medicine_ball_push_up',\n 11: 'close_hands_push_up',\n 12: 'weighted_close_hands_push_up',\n 13: 'decline_push_up',\n 14: 'weighted_decline_push_up',\n 15: 'diamond_push_up',\n 16: 'weighted_diamond_push_up',\n 17: 'explosive_crossover_push_up',\n 18: 'weighted_explosive_crossover_push_up',\n 19: 'explosive_push_up',\n 20: 'weighted_explosive_push_up',\n 21: 'feet_elevated_side_to_side_push_up',\n 22: 'weighted_feet_elevated_side_to_side_push_up',\n 23: 'hand_release_push_up',\n 24: 'weighted_hand_release_push_up',\n 25: 'handstand_push_up',\n 26: 'weighted_handstand_push_up',\n 27: 'incline_push_up',\n 28: 'weighted_incline_push_up',\n 29: 'isometric_explosive_push_up',\n 30: 'weighted_isometric_explosive_push_up',\n 31: 'judo_push_up',\n 32: 'weighted_judo_push_up',\n 33: 'kneeling_push_up',\n 34: 'weighted_kneeling_push_up',\n 35: 'medicine_ball_chest_pass',\n 36: 'medicine_ball_push_up',\n 37: 'weighted_medicine_ball_push_up',\n 38: 'one_arm_push_up',\n 39: 'weighted_one_arm_push_up',\n 40: 'weighted_push_up',\n 41: 'push_up_and_row',\n 42: 'weighted_push_up_and_row',\n 43: 'push_up_plus',\n 44: 'weighted_push_up_plus',\n 45: 'push_up_with_feet_on_swiss_ball',\n 46: 'weighted_push_up_with_feet_on_swiss_ball',\n 47: 'push_up_with_one_hand_on_medicine_ball',\n 48: 'weighted_push_up_with_one_hand_on_medicine_ball',\n 49: 'shoulder_push_up',\n 50: 'weighted_shoulder_push_up',\n 51: 'single_arm_medicine_ball_push_up',\n 52: 'weighted_single_arm_medicine_ball_push_up',\n 53: 'spiderman_push_up',\n 54: 'weighted_spiderman_push_up',\n 55: 'stacked_feet_push_up',\n 56: 'weighted_stacked_feet_push_up',\n 57: 'staggered_hands_push_up',\n 58: 'weighted_staggered_hands_push_up',\n 59: 'suspended_push_up',\n 60: 'weighted_suspended_push_up',\n 61: 'swiss_ball_push_up',\n 62: 'weighted_swiss_ball_push_up',\n 63: 'swiss_ball_push_up_plus',\n 64: 'weighted_swiss_ball_push_up_plus',\n 65: 't_push_up',\n 66: 'weighted_t_push_up',\n 67: 'triple_stop_push_up',\n 68: 'weighted_triple_stop_push_up',\n 69: 'wide_hands_push_up',\n 70: 'weighted_wide_hands_push_up',\n 71: 'parallette_handstand_push_up',\n 72: 'weighted_parallette_handstand_push_up',\n 73: 'ring_handstand_push_up',\n 74: 'weighted_ring_handstand_push_up',\n 75: 'ring_push_up',\n 76: 'weighted_ring_push_up',\n 77: 'push_up',\n 78: 'pilates_pushup',\n },\n row_exercise_name: {\n 0: 'barbell_straight_leg_deadlift_to_row',\n 1: 'cable_row_standing',\n 2: 'dumbbell_row',\n 3: 'elevated_feet_inverted_row',\n 4: 'weighted_elevated_feet_inverted_row',\n 5: 'face_pull',\n 6: 'face_pull_with_external_rotation',\n 7: 'inverted_row_with_feet_on_swiss_ball',\n 8: 'weighted_inverted_row_with_feet_on_swiss_ball',\n 9: 'kettlebell_row',\n 10: 'modified_inverted_row',\n 11: 'weighted_modified_inverted_row',\n 12: 'neutral_grip_alternating_dumbbell_row',\n 13: 'one_arm_bent_over_row',\n 14: 'one_legged_dumbbell_row',\n 15: 'renegade_row',\n 16: 'reverse_grip_barbell_row',\n 17: 'rope_handle_cable_row',\n 18: 'seated_cable_row',\n 19: 'seated_dumbbell_row',\n 20: 'single_arm_cable_row',\n 21: 'single_arm_cable_row_and_rotation',\n 22: 'single_arm_inverted_row',\n 23: 'weighted_single_arm_inverted_row',\n 24: 'single_arm_neutral_grip_dumbbell_row',\n 25: 'single_arm_neutral_grip_dumbbell_row_and_rotation',\n 26: 'suspended_inverted_row',\n 27: 'weighted_suspended_inverted_row',\n 28: 't_bar_row',\n 29: 'towel_grip_inverted_row',\n 30: 'weighted_towel_grip_inverted_row',\n 31: 'underhand_grip_cable_row',\n 32: 'v_grip_cable_row',\n 33: 'wide_grip_seated_cable_row',\n },\n shoulder_press_exercise_name: {\n 0: 'alternating_dumbbell_shoulder_press',\n 1: 'arnold_press',\n 2: 'barbell_front_squat_to_push_press',\n 3: 'barbell_push_press',\n 4: 'barbell_shoulder_press',\n 5: 'dead_curl_press',\n 6: 'dumbbell_alternating_shoulder_press_and_twist',\n 7: 'dumbbell_hammer_curl_to_lunge_to_press',\n 8: 'dumbbell_push_press',\n 9: 'floor_inverted_shoulder_press',\n 10: 'weighted_floor_inverted_shoulder_press',\n 11: 'inverted_shoulder_press',\n 12: 'weighted_inverted_shoulder_press',\n 13: 'one_arm_push_press',\n 14: 'overhead_barbell_press',\n 15: 'overhead_dumbbell_press',\n 16: 'seated_barbell_shoulder_press',\n 17: 'seated_dumbbell_shoulder_press',\n 18: 'single_arm_dumbbell_shoulder_press',\n 19: 'single_arm_step_up_and_press',\n 20: 'smith_machine_overhead_press',\n 21: 'split_stance_hammer_curl_to_press',\n 22: 'swiss_ball_dumbbell_shoulder_press',\n 23: 'weight_plate_front_raise',\n },\n shoulder_stability_exercise_name: {\n 0: '90_degree_cable_external_rotation',\n 1: 'band_external_rotation',\n 2: 'band_internal_rotation',\n 3: 'bent_arm_lateral_raise_and_external_rotation',\n 4: 'cable_external_rotation',\n 5: 'dumbbell_face_pull_with_external_rotation',\n 6: 'floor_i_raise',\n 7: 'weighted_floor_i_raise',\n 8: 'floor_t_raise',\n 9: 'weighted_floor_t_raise',\n 10: 'floor_y_raise',\n 11: 'weighted_floor_y_raise',\n 12: 'incline_i_raise',\n 13: 'weighted_incline_i_raise',\n 14: 'incline_l_raise',\n 15: 'weighted_incline_l_raise',\n 16: 'incline_t_raise',\n 17: 'weighted_incline_t_raise',\n 18: 'incline_w_raise',\n 19: 'weighted_incline_w_raise',\n 20: 'incline_y_raise',\n 21: 'weighted_incline_y_raise',\n 22: 'lying_external_rotation',\n 23: 'seated_dumbbell_external_rotation',\n 24: 'standing_l_raise',\n 25: 'swiss_ball_i_raise',\n 26: 'weighted_swiss_ball_i_raise',\n 27: 'swiss_ball_t_raise',\n 28: 'weighted_swiss_ball_t_raise',\n 29: 'swiss_ball_w_raise',\n 30: 'weighted_swiss_ball_w_raise',\n 31: 'swiss_ball_y_raise',\n 32: 'weighted_swiss_ball_y_raise',\n },\n shrug_exercise_name: {\n 0: 'barbell_jump_shrug',\n 1: 'barbell_shrug',\n 2: 'barbell_upright_row',\n 3: 'behind_the_back_smith_machine_shrug',\n 4: 'dumbbell_jump_shrug',\n 5: 'dumbbell_shrug',\n 6: 'dumbbell_upright_row',\n 7: 'incline_dumbbell_shrug',\n 8: 'overhead_barbell_shrug',\n 9: 'overhead_dumbbell_shrug',\n 10: 'scaption_and_shrug',\n 11: 'scapular_retraction',\n 12: 'serratus_chair_shrug',\n 13: 'weighted_serratus_chair_shrug',\n 14: 'serratus_shrug',\n 15: 'weighted_serratus_shrug',\n 16: 'wide_grip_jump_shrug',\n },\n sit_up_exercise_name: {\n 0: 'alternating_sit_up',\n 1: 'weighted_alternating_sit_up',\n 2: 'bent_knee_v_up',\n 3: 'weighted_bent_knee_v_up',\n 4: 'butterfly_sit_up',\n 5: 'weighted_butterfly_situp',\n 6: 'cross_punch_roll_up',\n 7: 'weighted_cross_punch_roll_up',\n 8: 'crossed_arms_sit_up',\n 9: 'weighted_crossed_arms_sit_up',\n 10: 'get_up_sit_up',\n 11: 'weighted_get_up_sit_up',\n 12: 'hovering_sit_up',\n 13: 'weighted_hovering_sit_up',\n 14: 'kettlebell_sit_up',\n 15: 'medicine_ball_alternating_v_up',\n 16: 'medicine_ball_sit_up',\n 17: 'medicine_ball_v_up',\n 18: 'modified_sit_up',\n 19: 'negative_sit_up',\n 20: 'one_arm_full_sit_up',\n 21: 'reclining_circle',\n 22: 'weighted_reclining_circle',\n 23: 'reverse_curl_up',\n 24: 'weighted_reverse_curl_up',\n 25: 'single_leg_swiss_ball_jackknife',\n 26: 'weighted_single_leg_swiss_ball_jackknife',\n 27: 'the_teaser',\n 28: 'the_teaser_weighted',\n 29: 'three_part_roll_down',\n 30: 'weighted_three_part_roll_down',\n 31: 'v_up',\n 32: 'weighted_v_up',\n 33: 'weighted_russian_twist_on_swiss_ball',\n 34: 'weighted_sit_up',\n 35: 'x_abs',\n 36: 'weighted_x_abs',\n 37: 'sit_up',\n },\n squat_exercise_name: {\n 0: 'leg_press',\n 1: 'back_squat_with_body_bar',\n 2: 'back_squats',\n 3: 'weighted_back_squats',\n 4: 'balancing_squat',\n 5: 'weighted_balancing_squat',\n 6: 'barbell_back_squat',\n 7: 'barbell_box_squat',\n 8: 'barbell_front_squat',\n 9: 'barbell_hack_squat',\n 10: 'barbell_hang_squat_snatch',\n 11: 'barbell_lateral_step_up',\n 12: 'barbell_quarter_squat',\n 13: 'barbell_siff_squat',\n 14: 'barbell_squat_snatch',\n 15: 'barbell_squat_with_heels_raised',\n 16: 'barbell_stepover',\n 17: 'barbell_step_up',\n 18: 'bench_squat_with_rotational_chop',\n 19: 'weighted_bench_squat_with_rotational_chop',\n 20: 'body_weight_wall_squat',\n 21: 'weighted_wall_squat',\n 22: 'box_step_squat',\n 23: 'weighted_box_step_squat',\n 24: 'braced_squat',\n 25: 'crossed_arm_barbell_front_squat',\n 26: 'crossover_dumbbell_step_up',\n 27: 'dumbbell_front_squat',\n 28: 'dumbbell_split_squat',\n 29: 'dumbbell_squat',\n 30: 'dumbbell_squat_clean',\n 31: 'dumbbell_stepover',\n 32: 'dumbbell_step_up',\n 33: 'elevated_single_leg_squat',\n 34: 'weighted_elevated_single_leg_squat',\n 35: 'figure_four_squats',\n 36: 'weighted_figure_four_squats',\n 37: 'goblet_squat',\n 38: 'kettlebell_squat',\n 39: 'kettlebell_swing_overhead',\n 40: 'kettlebell_swing_with_flip_to_squat',\n 41: 'lateral_dumbbell_step_up',\n 42: 'one_legged_squat',\n 43: 'overhead_dumbbell_squat',\n 44: 'overhead_squat',\n 45: 'partial_single_leg_squat',\n 46: 'weighted_partial_single_leg_squat',\n 47: 'pistol_squat',\n 48: 'weighted_pistol_squat',\n 49: 'plie_slides',\n 50: 'weighted_plie_slides',\n 51: 'plie_squat',\n 52: 'weighted_plie_squat',\n 53: 'prisoner_squat',\n 54: 'weighted_prisoner_squat',\n 55: 'single_leg_bench_get_up',\n 56: 'weighted_single_leg_bench_get_up',\n 57: 'single_leg_bench_squat',\n 58: 'weighted_single_leg_bench_squat',\n 59: 'single_leg_squat_on_swiss_ball',\n 60: 'weighted_single_leg_squat_on_swiss_ball',\n 61: 'squat',\n 62: 'weighted_squat',\n 63: 'squats_with_band',\n 64: 'staggered_squat',\n 65: 'weighted_staggered_squat',\n 66: 'step_up',\n 67: 'weighted_step_up',\n 68: 'suitcase_squats',\n 69: 'sumo_squat',\n 70: 'sumo_squat_slide_in',\n 71: 'weighted_sumo_squat_slide_in',\n 72: 'sumo_squat_to_high_pull',\n 73: 'sumo_squat_to_stand',\n 74: 'weighted_sumo_squat_to_stand',\n 75: 'sumo_squat_with_rotation',\n 76: 'weighted_sumo_squat_with_rotation',\n 77: 'swiss_ball_body_weight_wall_squat',\n 78: 'weighted_swiss_ball_wall_squat',\n 79: 'thrusters',\n 80: 'uneven_squat',\n 81: 'weighted_uneven_squat',\n 82: 'waist_slimming_squat',\n 83: 'wall_ball',\n 84: 'wide_stance_barbell_squat',\n 85: 'wide_stance_goblet_squat',\n 86: 'zercher_squat',\n 87: 'kbs_overhead',\n 88: 'squat_and_side_kick',\n 89: 'squat_jumps_in_n_out',\n 90: 'pilates_plie_squats_parallel_turned_out_flat_and_heels',\n 91: 'releve_straight_leg_and_knee_bent_with_one_leg_variation',\n },\n total_body_exercise_name: {\n 0: 'burpee',\n 1: 'weighted_burpee',\n 2: 'burpee_box_jump',\n 3: 'weighted_burpee_box_jump',\n 4: 'high_pull_burpee',\n 5: 'man_makers',\n 6: 'one_arm_burpee',\n 7: 'squat_thrusts',\n 8: 'weighted_squat_thrusts',\n 9: 'squat_plank_push_up',\n 10: 'weighted_squat_plank_push_up',\n 11: 'standing_t_rotation_balance',\n 12: 'weighted_standing_t_rotation_balance',\n },\n triceps_extension_exercise_name: {\n 0: 'bench_dip',\n 1: 'weighted_bench_dip',\n 2: 'body_weight_dip',\n 3: 'cable_kickback',\n 4: 'cable_lying_triceps_extension',\n 5: 'cable_overhead_triceps_extension',\n 6: 'dumbbell_kickback',\n 7: 'dumbbell_lying_triceps_extension',\n 8: 'ez_bar_overhead_triceps_extension',\n 9: 'incline_dip',\n 10: 'weighted_incline_dip',\n 11: 'incline_ez_bar_lying_triceps_extension',\n 12: 'lying_dumbbell_pullover_to_extension',\n 13: 'lying_ez_bar_triceps_extension',\n 14: 'lying_triceps_extension_to_close_grip_bench_press',\n 15: 'overhead_dumbbell_triceps_extension',\n 16: 'reclining_triceps_press',\n 17: 'reverse_grip_pressdown',\n 18: 'reverse_grip_triceps_pressdown',\n 19: 'rope_pressdown',\n 20: 'seated_barbell_overhead_triceps_extension',\n 21: 'seated_dumbbell_overhead_triceps_extension',\n 22: 'seated_ez_bar_overhead_triceps_extension',\n 23: 'seated_single_arm_overhead_dumbbell_extension',\n 24: 'single_arm_dumbbell_overhead_triceps_extension',\n 25: 'single_dumbbell_seated_overhead_triceps_extension',\n 26: 'single_leg_bench_dip_and_kick',\n 27: 'weighted_single_leg_bench_dip_and_kick',\n 28: 'single_leg_dip',\n 29: 'weighted_single_leg_dip',\n 30: 'static_lying_triceps_extension',\n 31: 'suspended_dip',\n 32: 'weighted_suspended_dip',\n 33: 'swiss_ball_dumbbell_lying_triceps_extension',\n 34: 'swiss_ball_ez_bar_lying_triceps_extension',\n 35: 'swiss_ball_ez_bar_overhead_triceps_extension',\n 36: 'tabletop_dip',\n 37: 'weighted_tabletop_dip',\n 38: 'triceps_extension_on_floor',\n 39: 'triceps_pressdown',\n 40: 'weighted_dip',\n },\n warm_up_exercise_name: {\n 0: 'quadruped_rocking',\n 1: 'neck_tilts',\n 2: 'ankle_circles',\n 3: 'ankle_dorsiflexion_with_band',\n 4: 'ankle_internal_rotation',\n 5: 'arm_circles',\n 6: 'bent_over_reach_to_sky',\n 7: 'cat_camel',\n 8: 'elbow_to_foot_lunge',\n 9: 'forward_and_backward_leg_swings',\n 10: 'groiners',\n 11: 'inverted_hamstring_stretch',\n 12: 'lateral_duck_under',\n 13: 'neck_rotations',\n 14: 'opposite_arm_and_leg_balance',\n 15: 'reach_roll_and_lift',\n 16: 'scorpion',\n 17: 'shoulder_circles',\n 18: 'side_to_side_leg_swings',\n 19: 'sleeper_stretch',\n 20: 'slide_out',\n 21: 'swiss_ball_hip_crossover',\n 22: 'swiss_ball_reach_roll_and_lift',\n 23: 'swiss_ball_windshield_wipers',\n 24: 'thoracic_rotation',\n 25: 'walking_high_kicks',\n 26: 'walking_high_knees',\n 27: 'walking_knee_hugs',\n 28: 'walking_leg_cradles',\n 29: 'walkout',\n 30: 'walkout_from_push_up_position',\n },\n run_exercise_name: {\n 0: 'run',\n 1: 'walk',\n 2: 'jog',\n 3: 'sprint',\n },\n water_type: {\n 0: 'fresh',\n 1: 'salt',\n 2: 'en13319',\n 3: 'custom',\n },\n tissue_model_type: {\n 0: 'zhl_16c',\n },\n dive_gas_status: {\n 0: 'disabled',\n 1: 'enabled',\n 2: 'backup_only',\n },\n dive_alarm_type: {\n 0: 'depth',\n 1: 'time',\n },\n dive_backlight_mode: {\n 0: 'at_depth',\n 1: 'always_on',\n },\n favero_product: {\n 10: 'assioma_uno',\n 12: 'assioma_duo',\n },\n },\n};\n", "import { FIT } from './fit.js';\nfunction getFieldObject(fieldNum, messageNum) {\n const message = FIT.messages[messageNum];\n if (!message) {\n return {};\n }\n return message[fieldNum] || {};\n}\nfunction getMessageName(messageNum) {\n const message = FIT.messages[messageNum];\n return message ? message.name : '';\n}\nexport function getFitMessage(messageNum) {\n return {\n name: getMessageName(messageNum),\n getAttributes: (fieldNum) => getFieldObject(fieldNum, messageNum),\n };\n}\n// TODO\nexport function getFitMessageBaseType(inp) {\n return inp;\n}\n", "export function mapDataIntoLap(inputLaps, lapKey, data) {\n const laps = [...inputLaps];\n let index = 0;\n for (let i = 0; i < laps.length; i++) {\n const nextLap = laps[i + 1];\n const tempData = [];\n const nextLapStartTime = nextLap\n ? new Date(nextLap.start_time).getTime()\n : null;\n for (let j = index; j < data.length; j++) {\n const row = data[j];\n if (nextLap && nextLapStartTime) {\n const timestamp = new Date(row.timestamp || row.start_time).getTime();\n if (nextLapStartTime > timestamp) {\n tempData.push(row);\n }\n else if (nextLapStartTime <= timestamp) {\n index = j;\n break;\n }\n }\n else {\n tempData.push(row);\n }\n }\n if (!laps[i][lapKey]) {\n laps[i][lapKey] = tempData;\n }\n }\n return laps;\n}\nexport function mapDataIntoSession(inputSessions, laps) {\n const sessions = [...inputSessions];\n let lapIndex = 0;\n for (let i = 0; i < sessions.length; i++) {\n const nextSession = sessions[i + 1];\n const tempLaps = [];\n const nextSessionStartTime = nextSession\n ? new Date(nextSession.start_time).getTime()\n : null;\n for (let j = lapIndex; j < laps.length; j++) {\n const lap = laps[j];\n if (nextSession && nextSessionStartTime) {\n const lapStartTime = new Date(lap.start_time).getTime();\n if (nextSessionStartTime > lapStartTime) {\n tempLaps.push(lap);\n }\n else if (nextSessionStartTime <= lapStartTime) {\n lapIndex = j;\n break;\n }\n }\n else {\n tempLaps.push(lap);\n }\n }\n if (!sessions[i].laps) {\n sessions[i].laps = tempLaps;\n }\n }\n return sessions;\n}\n", "import { calculateCRC, getArrayBuffer, readRecord } from './binary.js';\nimport { mapDataIntoLap, mapDataIntoSession } from './helper.js';\nexport default class FitParser {\n constructor(options = {}) {\n this.options = {\n force: options.force != null ? options.force : true,\n speedUnit: options.speedUnit || 'm/s',\n lengthUnit: options.lengthUnit || 'm',\n temperatureUnit: options.temperatureUnit || 'celsius',\n elapsedRecordField: options.elapsedRecordField || false,\n pressureUnit: options.pressureUnit || 'bar',\n mode: options.mode || 'list',\n };\n }\n parseAsync(content) {\n return new Promise((resolve, reject) => {\n this.parse(content, (error, data) => {\n if (error) {\n reject(error);\n }\n else if (data) {\n resolve(data);\n }\n });\n });\n }\n parse(content, callback) {\n var _a;\n const blob = new Uint8Array(getArrayBuffer(content));\n if (blob.length < 12) {\n callback('File to small to be a FIT file', undefined);\n if (!this.options.force) {\n return;\n }\n }\n const headerLength = blob[0];\n if (headerLength !== 14 && headerLength !== 12) {\n callback('Incorrect header size', undefined);\n if (!this.options.force) {\n return;\n }\n }\n let fileTypeString = '';\n for (let i = 8; i < 12; i++) {\n fileTypeString += String.fromCharCode(blob[i]);\n }\n if (fileTypeString !== '.FIT') {\n callback('Missing \\'.FIT\\' in header', undefined);\n if (!this.options.force) {\n return;\n }\n }\n if (headerLength === 14) {\n const crcHeader = blob[12] + (blob[13] << 8);\n const crcHeaderCalc = calculateCRC(blob, 0, 12);\n if (crcHeader !== crcHeaderCalc) {\n // callback('Header CRC mismatch', {});\n // TODO: fix Header CRC check\n if (!this.options.force) {\n return;\n }\n }\n }\n const protocolVersion = blob[1];\n const profileVersion = blob[2] + (blob[3] << 8);\n const dataLength = blob[4] + (blob[5] << 8) + (blob[6] << 16) + (blob[7] << 24);\n const crcStart = dataLength + headerLength;\n const crcFile = blob[crcStart] + (blob[crcStart + 1] << 8);\n const crcFileCalc = calculateCRC(blob, headerLength === 12 ? 0 : headerLength, crcStart);\n if (crcFile !== crcFileCalc) {\n // callback('File CRC mismatch', {});\n // TODO: fix File CRC check\n if (!this.options.force) {\n return;\n }\n }\n const fitObj = {\n profileVersion,\n protocolVersion,\n };\n let sessions = [];\n let laps = [];\n const records = [];\n const events = [];\n const hr_zone = [];\n const power_zone = [];\n const hrv = [];\n const device_infos = [];\n const applications = [];\n const fieldDescriptions = [];\n const dive_gases = [];\n const course_points = [];\n const sports = [];\n const monitors = [];\n const stress = [];\n const definitions = [];\n const file_ids = [];\n const monitor_info = [];\n const lengths = [];\n const tank_updates = [];\n const tank_summaries = [];\n const jumps = [];\n const time_in_zone = [];\n const activity_metrics = [];\n let loopIndex = headerLength;\n const messageTypes = [];\n const developerFields = [];\n const isModeCascade = this.options.mode === 'cascade';\n const isCascadeNeeded = isModeCascade || this.options.mode === 'both';\n let startDate;\n let lastStopTimestamp;\n let pausedTime = 0;\n while (loopIndex < crcStart) {\n const { nextIndex, messageType, message } = readRecord(blob, messageTypes, developerFields, loopIndex, this.options, startDate, pausedTime);\n loopIndex = nextIndex;\n switch (messageType) {\n case 'lap':\n laps.push(message);\n break;\n case 'session':\n sessions.push(message);\n break;\n case 'event':\n if (message.event === 'timer') {\n if (message.event_type === 'stop_all') {\n lastStopTimestamp = message.timestamp;\n }\n else if (message.event_type === 'start' && lastStopTimestamp) {\n pausedTime += (message.timestamp - lastStopTimestamp) / 1000;\n }\n }\n events.push(message);\n break;\n case 'length':\n lengths.push(message);\n break;\n case 'hrv':\n hrv.push(message);\n break;\n case 'hr_zone':\n hr_zone.push(message);\n break;\n case 'power_zone':\n power_zone.push(message);\n break;\n case 'record':\n if (!startDate) {\n startDate = message.timestamp;\n message.elapsed_time = 0;\n message.timer_time = 0;\n }\n records.push(message);\n break;\n case 'field_description':\n fieldDescriptions.push(message);\n break;\n case 'device_info':\n device_infos.push(message);\n break;\n case 'developer_data_id':\n applications.push(message);\n break;\n case 'dive_gas':\n dive_gases.push(message);\n break;\n case 'course_point':\n course_points.push(message);\n break;\n case 'sport':\n sports.push(message);\n break;\n case 'file_id':\n if (message) {\n file_ids.push(message);\n }\n break;\n case 'definition':\n if (message) {\n definitions.push(message);\n }\n break;\n case 'monitoring':\n monitors.push(message);\n break;\n case 'monitoring_info':\n monitor_info.push(message);\n break;\n case 'stress_level':\n stress.push(message);\n break;\n case 'software':\n fitObj.software = message;\n break;\n case 'tank_update':\n tank_updates.push(message);\n break;\n case 'tank_summary':\n tank_summaries.push(message);\n break;\n case 'jump':\n jumps.push(message);\n break;\n case 'time_in_zone':\n time_in_zone.push(message);\n break;\n case 'activity_metrics':\n activity_metrics.push(message);\n break;\n default:\n if (messageType !== '') {\n fitObj[messageType] = message;\n }\n break;\n }\n }\n fitObj.hr_zone = hr_zone;\n fitObj.power_zone = power_zone;\n fitObj.dive_gases = dive_gases;\n fitObj.course_points = course_points;\n fitObj.sports = sports;\n fitObj.monitors = monitors;\n fitObj.stress = stress;\n fitObj.file_ids = file_ids;\n fitObj.monitor_info = monitor_info;\n fitObj.definitions = definitions;\n fitObj.tank_updates = tank_updates;\n fitObj.tank_summaries = tank_summaries;\n fitObj.jumps = jumps;\n fitObj.time_in_zone = time_in_zone;\n fitObj.activity_metrics = activity_metrics;\n if (isCascadeNeeded) {\n laps = mapDataIntoLap(laps, 'records', records);\n laps = mapDataIntoLap(laps, 'lengths', lengths);\n sessions = mapDataIntoSession(sessions, laps);\n fitObj.activity = Object.assign(Object.assign({}, ((_a = fitObj.activity) !== null && _a !== void 0 ? _a : {})), { // ugly but we assume the activity was parsed correctly with all other members correctly\n sessions,\n events,\n hrv,\n device_infos, developer_data_ids: applications, field_descriptions: fieldDescriptions, sports });\n }\n if (!isModeCascade) {\n fitObj.sessions = sessions;\n fitObj.laps = laps;\n fitObj.lengths = lengths;\n fitObj.records = records;\n fitObj.events = events;\n fitObj.device_infos = device_infos;\n fitObj.developer_data_ids = applications;\n fitObj.field_descriptions = fieldDescriptions;\n fitObj.hrv = hrv;\n }\n callback(undefined, fitObj);\n }\n}\n", "// Copyright (c) 2026, Camillo Bruni.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n\nimport FitParser from 'fit-file-parser';\nexport default FitParser;\n"], + "mappings": "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AO,QACX,EACA,GAAI,CACA,MAAO,aACP,KAAM,SACN,MAAO,KACP,OAAQ,EACR,MAAO,QACX,EACA,GAAI,CACA,MAAO,iBACP,KAAM,SACN,MAAO,IACP,OAAQ,EACR,MAAO,KACX,EACA,GAAI,CACA,MAAO,iBACP,KAAM,SACN,MAAO,IACP,OAAQ,EACR,MAAO,KACX,EACA,GAAI,CACA,MAAO,2BACP,KAAM,SACN,MAAO,GACP,OAAQ,EACR,MAAO,IACX,EACA,GAAI,CACA,MAAO,0BACP,KAAM,SACN,MAAO,IACP,OAAQ,EACR,MAAO,SACX,EACA,GAAI,CACA,MAAO,kBACP,KAAM,SACN,MAAO,GACP,OAAQ,EACR,MAAO,IACX,EACA,GAAI,CACA,MAAO,yBACP,KAAM,QACN,MAAO,IACP,OAAQ,EACR,MAAO,KACX,EACA,GAAI,CACA,MAAO,yBACP,KAAM,QACN,MAAO,IACP,OAAQ,EACR,MAAO,KACX,EACA,GAAI,CACA,MAAO,0BACP,KAAM,QACN,MAAO,IACP,OAAQ,EACR,MAAO,QACX,EACA,GAAI,CACA,MAAO,4BACP,KAAM,SACN,MAAO,IACP,OAAQ,EACR,MAAO,MACX,EACA,GAAI,CACA,MAAO,4BACP,KAAM,SACN,MAAO,IACP,OAAQ,EACR,MAAO,MACX,EACA,GAAI,CACA,MAAO,4BACP,KAAM,SACN,MAAO,IACP,OAAQ,EACR,MAAO,MACX,EACA,GAAI,CACA,MAAO,mCACP,KAAM,SACN,MAAO,GACP,OAAQ,EACR,MAAO,GACX,EACA,GAAI,CACA,MAAO,mCACP,KAAM,SACN,MAAO,GACP,OAAQ,EACR,MAAO,GACX,EACA,IAAK,CACD,MAAO,mCACP,KAAM,SACN,MAAO,GACP,OAAQ,EACR,MAAO,GACX,EACA,IAAK,CACD,MAAO,gCACP,KAAM,QACN,MAAO,EACP,OAAQ,EACR,MAAO,SACX,EACA,IAAK,CACD,MAAO,iCACP,KAAM,QACN,MAAO,EACP,OAAQ,EACR,MAAO,SACX,EACA,IAAK,CACD,MAAO,4BACP,KAAM,QACN,MAAO,EACP,OAAQ,EACR,MAAO,SACX,EACA,IAAK,CACD,MAAO,6BACP,KAAM,QACN,MAAO,EACP,OAAQ,EACR,MAAO,SACX,EACA,IAAK,CACD,MAAO,gCACP,KAAM,QACN,MAAO,EACP,OAAQ,EACR,MAAO,SACX,EACA,IAAK,CACD,MAAO,cACP,KAAM,QACN,MAAO,KACP,OAAQ,EACR,MAAO,EACX,EACA,IAAK,CACD,MAAO,gBACP,KAAM,SACN,MAAO,IACP,OAAQ,EACR,MAAO,GACX,EACA,IAAK,CACD,MAAO,cACP,KAAM,SACN,MAAO,KACP,OAAQ,EACR,MAAO,EACX,EACA,IAAK,CACD,MAAO,eACP,KAAM,QACN,MAAO,KACP,OAAQ,EACR,MAAO,IACX,EACA,IAAK,CACD,MAAO,gBACP,KAAM,QACN,MAAO,KACP,OAAQ,EACR,MAAO,IACX,EACA,IAAK,CACD,MAAO,uBACP,KAAM,QACN,MAAO,SACP,OAAQ,EACR,MAAO,SACX,EACA,IAAK,CACD,MAAO,4BACP,KAAM,QACN,MAAO,SACP,OAAQ,EACR,MAAO,SACX,EACA,IAAK,CACD,MAAO,wBACP,KAAM,QACN,MAAO,SACP,OAAQ,EACR,MAAO,SACX,EACA,IAAK,CACD,MAAO,6BACP,KAAM,QACN,MAAO,SACP,OAAQ,EACR,MAAO,SACX,EACA,IAAK,CACD,MAAO,qBACP,KAAM,SACN,MAAO,KACP,OAAQ,EACR,MAAO,OACX,EACA,IAAK,CACD,MAAO,qBACP,KAAM,SACN,MAAO,KACP,OAAQ,EACR,MAAO,OACX,EACA,IAAK,CACD,MAAO,uBACP,KAAM,QACN,MAAO,KACP,OAAQ,EACR,MAAO,KACX,EACA,IAAK,CACD,MAAO,uBACP,KAAM,QACN,MAAO,KACP,OAAQ,EACR,MAAO,KACX,EACA,IAAK,CACD,MAAO,qBACP,KAAM,SACN,MAAO,IACP,OAAQ,EACR,MAAO,KACX,EACA,IAAK,CACD,MAAO,qBACP,KAAM,SACN,MAAO,IACP,OAAQ,EACR,MAAO,KACX,EACA,IAAK,CACD,MAAO,wBACP,KAAM,SACN,MAAO,EACP,OAAQ,KACR,MAAO,GACX,EACA,IAAK,CACD,MAAO,wBACP,KAAM,SACN,MAAO,EACP,OAAQ,KACR,MAAO,GACX,EACA,IAAK,CACD,MAAO,wBACP,KAAM,SACN,MAAO,EACP,OAAQ,KACR,MAAO,GACX,EACA,IAAK,CACD,MAAO,sBACP,KAAM,SACN,MAAO,KACP,OAAQ,EACR,MAAO,OACX,EACA,IAAK,CACD,MAAO,sBACP,KAAM,SACN,MAAO,KACP,OAAQ,EACR,MAAO,OACX,EACA,IAAK,CACD,MAAO,0BACP,KAAM,QACN,MAAO,EACP,OAAQ,EACR,MAAO,SACX,EACA,IAAK,CACD,MAAO,qBACP,KAAM,SACN,MAAO,IACP,OAAQ,EACR,MAAO,SACX,EACA,IAAK,CACD,MAAO,0BACP,KAAM,SACN,MAAO,IACP,OAAQ,EACR,MAAO,SACX,EACA,IAAK,CACD,MAAO,kBACP,KAAM,SACN,MAAO,GACP,OAAQ,EACR,MAAO,IACX,EACA,IAAK,CACD,MAAO,kCACP,KAAM,QACN,MAAO,GACP,OAAQ,EACR,MAAO,EACX,EACA,IAAK,CACD,MAAO,UACP,KAAM,SACN,MAAO,EACP,OAAQ,EACR,MAAO,KACX,EACA,IAAK,CACD,MAAO,eACP,KAAM,QACN,MAAO,KACP,OAAQ,EACR,MAAO,EACX,EACA,IAAK,CACD,MAAO,cACP,KAAM,QACN,MAAO,GACP,OAAQ,EACR,MAAO,EACX,EACA,IAAK,CACD,MAAO,qBACP,KAAM,SACN,MAAO,KACP,OAAQ,EACR,MAAO,EACX,EACA,IAAK,CACD,MAAO,qBACP,KAAM,SACN,MAAO,EACP,OAAQ,EACR,MAAO,EACX,EACA,IAAK,CACD,MAAO,gCACP,KAAM,SACN,MAAO,IACP,OAAQ,EACR,MAAO,aACX,EACA,IAAK,CACD,MAAO,kBACP,KAAM,QACN,MAAO,KACP,OAAQ,EACR,MAAO,GACX,EACA,IAAK,CACD,MAAO,gCACP,KAAM,SACN,MAAO,IACP,OAAQ,EACR,MAAO,aACX,EACA,IAAK,CACD,MAAO,iBACP,KAAM,SACN,MAAO,EACP,OAAQ,EACR,MAAO,IACX,EACA,IAAK,CACD,MAAO,gCACP,KAAM,SACN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AQ,KACR,MAAO,GACX,EACA,IAAK,CACD,MAAO,sBACP,KAAM,SACN,MAAO,KACP,OAAQ,EACR,MAAO,OACX,EACA,IAAK,CACD,MAAO,sBACP,KAAM,SACN,MAAO,KACP,OAAQ,EACR,MAAO,OACX,EACA,IAAK,CACD,MAAO,0BACP,KAAM,QACN,MAAO,EACP,OAAQ,EACR,MAAO,SACX,EACA,IAAK,CACD,MAAO,qBACP,KAAM,SACN,MAAO,IACP,OAAQ,EACR,MAAO,SACX,EACA,IAAK,CACD,MAAO,0BACP,KAAM,SACN,MAAO,IACP,OAAQ,EACR,MAAO,SACX,EACA,IAAK,CACD,MAAO,kBACP,KAAM,SACN,MAAO,GACP,OAAQ,EACR,MAAO,IACX,EACA,IAAK,CACD,MAAO,UACP,KAAM,SACN,MAAO,IACP,OAAQ,EACR,MAAO,KACX,CACJ,EACA,GAAI,CACA,KAAM,SACN,IAAK,CACD,MAAO,YACP,KAAM,YACN,MAAO,KACP,OAAQ,EACR,MAAO,GACX,EACA,IAAK,CACD,MAAO,OACP,KAAM,UACN,MAAO,KACP,OAAQ,EACR,MAAO,EACX,EACA,IAAK,CACD,MAAO,OACP,KAAM,UACN,MAAO,KACP,OAAQ,EACR,MAAO,EACX,EACA,EAAG,CACC,MAAO,eACP,KAAM,SACN,MAAO,KACP,OAAQ,EACR,MAAO,aACX,EACA,EAAG,CACC,MAAO,gBACP,KAAM,SACN,MAAO,KACP,OAAQ,EACR,MAAO,aACX,EACA,EAAG,CACC,MAAO,WACP,KAAM,SACN,MAAO,EACP,OAAQ,KACR,MAAO,GACX,EACA,EAAG,CACC,MAAO,aACP,KAAM,QACN,MAAO,KACP,OAAQ,EACR,MAAO,KACX,EACA,EAAG,CACC,MAAO,UACP,KAAM,QACN,MAAO,KACP,OAAQ,EACR,MAAO,KACX,EACA,EAAG,CACC,MAAO,WACP,KAAM,SACN,MAAO,IACP,OAAQ,EACR,MAAO,GACX,EACA,EAAG,CACC,MAAO,QACP,KAAM,SACN,MAAO,IACP,OAAQ,EACR,MAAO,KACX,EACA,EAAG,CACC,MAAO,QACP,KAAM,SACN,MAAO,KACP,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CACC,MAAO,gBACP,KAAM,gBACN,MAAO,KACP,OAAQ,EACR,MAAO,EACX,EACA,EAAG,CACC,MAAO,mBACP,KAAM,mBACN,MAAO,KACP,OAAQ,EACR,MAAO,EACX,EACA,EAAG,CACC,MAAO,iBACP,KAAM,iBACN,MAAO,KACP,OAAQ,EACR,MAAO,GACX,EACA,EAAG,CACC,MAAO,aACP,KAAM,SACN,MAAO,KACP,OAAQ,EACR,MAAO,GACX,EACA,EAAG,CACC,MAAO,WACP,KAAM,SACN,MAAO,KACP,OAAQ,EACR,MAAO,QACX,EACA,GAAI,CACA,MAAO,gBACP,KAAM,SACN,MAAO,KACP,OAAQ,EACR,MAAO,EACX,EACA,GAAI,CACA,MAAO,kBACP,KAAM,SACN,MAAO,KACP,OAAQ,EACR,MAAO,EACX,EACA,GAAI,CACA,MAAO,cACP,KAAM,UACN,MAAO,KACP,OAAQ,EACR,MAAO,GACX,EACA,GAAI,CACA,MAAO,kBACP,KAAM,UACN,MAAO,KACP,OAAQ,EACR,MAAO,GACX,EACA,GAAI,CACA,MAAO,kBACP,KAAM,UACN,MAAO,KACP,OAAQ,EACR,MAAO,GACX,EACA,GAAI,CACA,MAAO,gBACP,KAAM,QACN,MAAO,KACP,OAAQ,EACR,MAAO,EACX,EACA,GAAI,CACA,MAAO,kBACP,KAAM,SACN,MAAO,KACP,OAAQ,EACR,MAAO,MACX,EACA,GAAI,CACA,MAAO,kCACP,KAAM,QACN,MAAO,KACP,OAAQ,EACR,MAAO,EACX,EACA,GAAI,CACA,MAAO,iBACP,KAAM,QACN,MAAO,KACP,OAAQ,EACR,MAAO,EACX,EACA,GAAI,CACA,MAAO,cACP,KAAM,SACN,MAAO,KACP,OAAQ,EACR,MAAO,EACX,EACA,GAAI,CACA,MAAO,aACP,KAAM,QACN,MAAO,KACP,OAAQ,EACR,MAAO,KACX,EACA,GAAI,CACA,MAAO,YACP,KAAM,QACN,MAAO,KACP,OAAQ,EACR,MAAO,EACX,EACA,GAAI,CACA,MAAO,eACP,KAAM,SACN,MAAO,KACP,OAAQ,EACR,MAAO,EACX,EACA,GAAI,CACA,MAAO,WACP,K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AAO,SACX,EACA,EAAG,CACC,MAAO,eACP,KAAM,QACN,MAAO,KACP,OAAQ,EACR,MAAO,SACX,EACA,EAAG,CACC,MAAO,WACP,KAAM,QACN,MAAO,KACP,OAAQ,EACR,MAAO,SACX,EACA,EAAG,CACC,MAAO,sBACP,KAAM,OACN,MAAO,KACP,OAAQ,EACR,MAAO,EACX,EACA,GAAI,CACA,MAAO,eACP,KAAM,UACN,MAAO,KACP,OAAQ,EACR,MAAO,EACX,EACA,GAAI,CACA,MAAO,cACP,KAAM,SACN,MAAO,KACP,OAAQ,EACR,MAAO,EACX,EACA,GAAI,CACA,MAAO,0BACP,KAAM,OACN,MAAO,KACP,OAAQ,EACR,MAAO,EACX,EACA,GAAI,CACA,MAAO,uBACP,KAAM,SACN,MAAO,KACP,OAAQ,EACR,MAAO,EACX,EACA,GAAI,CACA,MAAO,iBACP,KAAM,sBACN,MAAO,KACP,OAAQ,EACR,MAAO,EACX,EACA,GAAI,CACA,MAAO,uBACP,KAAM,QACN,MAAO,KACP,OAAQ,EACR,MAAO,EACX,EACA,GAAI,CACA,MAAO,oBACP,KAAM,oBACN,MAAO,KACP,OAAQ,EACR,MAAO,EACX,EACA,GAAI,CACA,MAAO,mBACP,KAAM,SACN,MAAO,KACP,OAAQ,EACR,MAAO,GACX,EACA,GAAI,CACA,MAAO,mBACP,KAAM,SACN,MAAO,KACP,OAAQ,EACR,MAAO,GACX,EACA,GAAI,CACA,MAAO,yBACP,KAAM,cACN,MAAO,KACP,OAAQ,EACR,MAAO,EACX,EACA,GAAI,CACA,MAAO,oBACP,KAAM,QACN,MAAO,KACP,OAAQ,EACR,MAAO,EACX,CACJ,EACA,IAAK,CACD,KAAM,WACN,IAAK,CACD,MAAO,gBACP,KAAM,gBACN,MAAO,KACP,OAAQ,EACR,MAAO,EACX,EACA,EAAG,CACC,MAAO,iBACP,KAAM,QACN,MAAO,KACP,OAAQ,EACR,MAAO,SACX,EACA,EAAG,CACC,MAAO,iBACP,KAAM,QACN,MAAO,KACP,OAAQ,EACR,MAAO,SACX,EACA,EAAG,CACC,MAAO,SACP,KAAM,kBACN,MAAO,KACP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IAAK,eACL,IAAK,iBACL,IAAK,iBACL,IAAK,kBACL,IAAK,iBACL,IAAK,QACL,IAAK,MACL,IAAK,eACL,IAAK,aACL,IAAK,kBACL,IAAK,WACL,IAAK,YACL,IAAK,WACL,IAAK,QACL,IAAK,UACL,IAAK,YACL,KAAM,eACV,EACA,eAAgB,CACZ,EAAG,WACH,EAAG,OACH,EAAG,QACH,EAAG,QACH,EAAG,QACH,EAAG,SACH,EAAG,YACH,EAAG,SACH,EAAG,iCACH,EAAG,MACH,GAAI,MACJ,GAAI,QACJ,GAAI,iCACJ,GAAI,+BACJ,IAAK,cACL,IAAK,cACL,IAAK,cACL,IAAK,eACL,IAAK,QACL,IAAK,OACL,IAAK,cACL,IAAK,OACL,KAAM,YACN,KAAM,UACN,KAAM,UACN,KAAM,QACN,KAAM,UACN,KAAM,iBACN,KAAM,gBACN,KAAM,QACN,KAAM,cACN,KAAM,UACN,KAAM,UACN,KAAM,iBACN,KAAM,gBACN,KAAM,QACN,KAAM,QACN,KAAM,cACN,KAAM,YACN,KAAM,YACN,KAAM,gBACN,KAAM,gBACN,KAAM,cACN,KAAM,gBACN,KAAM,OACN,KAAM,aACN,KAAM,MACN,KAAM,OACN,KAAM,gBACN,KAAM,OACN,KAAM,gBACN,KAAM,QACN,KAAM,iBACN,KAAM,UACN,KAAM,UACN,KAAM,QACN,KAAM,gBACN,KAAM,QACN,KAAM,QACN,KAAM,gBACN,KAAM,aACN,KAAM,gBACN,KAAM,aACN,KAAM,eACN,KAAM,gBACN,KAAM,UACN,KAAM,UACN,KAAM,UACN,KAAM,eACN,KAAM,gBACN,KAAM,iBACN,KAAM,WACN,KAAM,WACN,KAAM,cACN,KAAM,UACN,KAAM,OACN,KAAM,cACN,KAAM,gBACN,KAAM,cACN,KAAM,cACN,KAAM,cACN,KAAM,cACN,KAAM,cACN,KAAM,aACN,KAAM,SACN,KAAM,OACN,KAAM,SACN,KAAM,kBACN,KAAM,iBACN,KAAM,aACN,KAAM,UACN,KAAM,iBACN,KAAM,eACN,KAAM,eACN,KAAM,WACN,KAAM,iBACN,KAAM,iBACN,KAAM,gBACN,KAA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AG,MACH,EAAG,KACH,EAAG,OACH,EAAG,QACH,GAAI,aACR,EACA,sBAAuB,CACnB,EAAG,gBACH,EAAG,aACH,EAAG,YACP,EACA,uBAAwB,CACpB,EAAG,UACH,EAAG,WACP,EACA,YAAa,CACT,EAAG,MACH,EAAG,UACH,EAAG,YACH,EAAG,uBACH,EAAG,OACH,EAAG,OACP,EACA,oBAAqB,CACjB,EAAG,OACH,EAAG,WACH,EAAG,YACH,EAAG,mBACH,EAAG,mBACP,EACA,kBAAmB,CACf,EAAG,OACH,EAAG,YACH,EAAG,iBACH,EAAG,eACH,EAAG,iBACH,EAAG,cACP,EACA,eAAgB,CACZ,EAAG,UACH,EAAG,SACH,EAAG,aACH,EAAG,UACP,EACA,yBAA0B,CACtB,EAAG,cACH,EAAG,SACH,EAAG,MACP,EACA,wBAAyB,CACrB,EAAG,UACH,EAAG,cACH,EAAG,QACP,EACA,oBAAqB,CACjB,EAAG,SACH,EAAG,WACH,EAAG,uBACH,EAAG,wBACP,EACA,iBAAkB,CACd,EAAG,0BACH,EAAG,wBACH,EAAG,yBACH,EAAG,oBACP,EACA,kBAAmB,CACf,EAAG,cACH,EAAG,cACH,EAAG,YACH,EAAG,cACH,EAAG,4BACH,EAAG,4BACH,EAAG,0BACH,EAAG,oBACH,EAAG,kCACH,GAAI,cACJ,GAAI,4BACJ,GAAI,eACJ,GAAI,4BACR,EACA,YAAa,CACT,EAAG,gBACH,EAAG,YACH,EAAG,UACH,EAAG,WACP,EACA,+BAAgC,CAC5B,EAAG,OACH,EAAG,aACH,EAAG,kBACH,EAAG,OACP,EACA,kBAAmB,CACf,EAAG,2BACH,EAAG,kBACH,EAAG,kBACH,EAAG,oBACP,EACA,wBAAyB,CACrB,EAAG,uBACH,EAAG,wBACH,EAAG,yBACH,EAAG,wBACP,EACA,eAAgB,CACZ,EAAG,SACH,EAAG,WACH,EAAG,WACH,EAAG,OACP,EACA,kBAAmB,CACf,EAAG,EACH,EAAG,4BACH,EAAG,cACH,EAAG,aACH,EAAG,2BACH,GAAI,0BACJ,GAAI,kBACJ,GAAI,UACJ,IAAK,cACL,IAAK,SAC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", + "names": ["require_base64_js", "__commonJSMin", "exports", "byteLength", "toByteArray", "fromByteArray", "lookup", "revLookup", "Arr", "code", "i", "len", "getLens", "b64", "validLen", "placeHoldersLen", "lens", "_byteLength", "tmp", "arr", "curByte", "tripletToBase64", "num", "encodeChunk", "uint8", "start", "end", "output", "extraBytes", "parts", "maxChunkLength", "len2", "require_ieee754", "__commonJSMin", "exports", "buffer", "offset", "isLE", "mLen", "nBytes", "e", "m", "eLen", "eMax", "eBias", "nBits", "i", "d", "s", "value", "c", "rt", "require_buffer", "__commonJSMin", "exports", "base64", "ieee754", "customInspectSymbol", "Buffer", "SlowBuffer", "K_MAX_LENGTH", "typedArraySupport", "arr", "proto", "createBuffer", "length", "buf", "arg", "encodingOrOffset", "allocUnsafe", "from", "value", "fromString", "fromArrayView", "isInstance", "fromArrayBuffer", "valueOf", "b", "fromObject", "assertSize", "size", "alloc", "fill", "encoding", "checked", "string", "byteLength", "actual", "fromArrayLike", "array", "i", "arrayView", "copy", "byteOffset", "obj", "len", "numberIsNaN", "a", "x", "y", "list", "buffer", "pos", "mustMatch", "loweredCase", "utf8ToBytes", "base64ToBytes", "slowToString", "start", "end", "hexSlice", "utf8Slice", "asciiSlice", "latin1Slice", "base64Slice", "utf16leSlice", "swap", "n", "m", "str", "max", "target", "thisStart", "thisEnd", "thisCopy", "targetCopy", "bidirectionalIndexOf", "val", "dir", "arrayIndexOf", "indexSize", "arrLength", "valLength", "read", "foundIndex", "found", "j", "hexWrite", "offset", "remaining", "strLen", "parsed", "utf8Write", "blitBuffer", "asciiWrite", "asciiToBytes", "base64Write", "ucs2Write", "utf16leToBytes", "res", "firstByte", "codePoint", "bytesPerSequence", "secondByte", "thirdByte", "fourthByte", "tempCodePoint", "decodeCodePointsArray", "MAX_ARGUMENTS_LENGTH", "codePoints", "ret", "out", "hexSliceLookupTable", "bytes", "newBuf", "checkOffset", "ext", "noAssert", "mul", "defineBigIntMethod", "validateNumber", "first", "last", "boundsError", "lo", "hi", "checkInt", "min", "maxBytes", "wrtBigUInt64LE", "checkIntBI", "wrtBigUInt64BE", "limit", "sub", "checkIEEE754", "writeFloat", "littleEndian", "writeDouble", "targetStart", "code", "errors", "E", "sym", "getMessage", "Base", "name", "range", "input", "msg", "received", "addNumericalSeparator", "checkBounds", "type", "INVALID_BASE64_RE", "base64clean", "units", "leadSurrogate", "byteArray", "c", "src", "dst", "alphabet", "table", "i16", "fn", "BufferBigIntNotDefined", "import_buffer", "psiInOneBar", "FIT", "getFieldObject", "fieldNum", "messageNum", "message", "FIT", "getMessageName", "getFitMessage", "CompressedLocalMsgNumMask", "CompressedHeaderMask", "GarminTimeOffset", "monitoring_timestamp", "addEndian", "littleEndian", "bytes", "result", "i", "readData", "blob", "fDef", "startIndex", "options", "array8", "temp", "buffer", "dataView", "array32", "array16", "e", "val", "formatByType", "data", "type", "scale", "offset", "FIT", "baseType", "dataItem", "isInvalidValue", "values", "key", "mapped", "convertTo", "unitsList", "unitName", "unit", "applyOptions", "field", "fields", "isLocal", "isBLE", "isANT", "n1", "n2", "n3", "n4", "readRecord", "messageTypes", "developerFields", "startDate", "pausedTime", "recordHeader", "localMessageType", "hasDeveloperData", "lEnd", "numberOfFields", "numberOfDeveloperDataFields", "mTypeDef", "message", "getFitMessage", "fDefIndex", "fieldNum", "size", "devDataIndex", "devDef", "nextIndex", "nextIndexWithDeveloperData", "messageType", "messageSize", "readDataFromIndex", "rawFields", "getArrayBuffer", "ab", "view", "calculateCRC", "start", "end", "crcTable", "crc", "byteVal", "tmp", "mapDataIntoLap", "inputLaps", "lapKey", "data", "laps", "index", "i", "nextLap", "tempData", "nextLapStartTime", "j", "row", "timestamp", "mapDataIntoSession", "inputSessions", "sessions", "lapIndex", "nextSession", "tempLaps", "nextSessionStartTime", "lap", "lapStartTime", "FitParser", "options", "content", "resolve", "reject", "error", "data", "callback", "_a", "blob", "getArrayBuffer", "headerLength", "fileTypeString", "i", "crcHeader", "crcHeaderCalc", "calculateCRC", "protocolVersion", "profileVersion", "crcStart", "crcFile", "crcFileCalc", "fitObj", "sessions", "laps", "records", "events", "hr_zone", "power_zone", "hrv", "device_infos", "applications", "fieldDescriptions", "dive_gases", "course_points", "sports", "monitors", "stress", "definitions", "file_ids", "monitor_info", "lengths", "tank_updates", "tank_summaries", "jumps", "time_in_zone", "activity_metrics", "loopIndex", "messageTypes", "developerFields", "isModeCascade", "isCascadeNeeded", "startDate", "lastStopTimestamp", "pausedTime", "nextIndex", "messageType", "message", "readRecord", "mapDataIntoLap", "mapDataIntoSession", "vendor_fit_default", "FitParser"] +} diff --git a/suites-experimental/tools-gps-tracks/dist/index.html b/suites-experimental/tools-gps-tracks/dist/index.html new file mode 100644 index 000000000..883ffbadd --- /dev/null +++ b/suites-experimental/tools-gps-tracks/dist/index.html @@ -0,0 +1,551 @@ + + + + + + Weg — GPS Analysis + + + + + + + + +
+ + + + + + + + + +
+ + + + + +
+
+ + + + + + +
+ +
+
+
+ + + + + + + + + + + + + +
+ +
+
+ + +
+ + +
+
+ + + +
+
+ no_sim +
Select a track to view analysis
+
+
+
+ +
+
+
+ no_sim +
Select a track for insights
+
+
+
+ +
+
+
+ no_sim +
Select a track for details
+
+
+
+ +
+
+
+ no_sim +
Select a track for combined analysis
+
+
+
+ +
+
+
+ no_sim +
Select a track for progress analysis
+
+
+
+ +
+
+
+ route +
No courses created yet
+
+
+
+
+
+
+ +
+
+
+ + + + diff --git a/suites-experimental/tools-gps-tracks/dist/material_symbols_rounded.woff2 b/suites-experimental/tools-gps-tracks/dist/material_symbols_rounded.woff2 new file mode 100644 index 000000000..3dbcf04e5 Binary files /dev/null and b/suites-experimental/tools-gps-tracks/dist/material_symbols_rounded.woff2 differ diff --git a/suites-experimental/tools-gps-tracks/dist/resources.txt b/suites-experimental/tools-gps-tracks/dist/resources.txt new file mode 100644 index 000000000..16c622a0e --- /dev/null +++ b/suites-experimental/tools-gps-tracks/dist/resources.txt @@ -0,0 +1,20 @@ +LICENSE +benchmark-override.mjs +benchmark-override.mjs.map +fit-file-parser.mjs +fit-file-parser.mjs.map +index.html +material_symbols_rounded.woff2 +styles.css +styles.css.map +test-data/activity_1_alps_stage.gpx.gz +test-data/activity_2_forest_mtb.gpx.gz +test-data/activity_3_road_race.tcx.gz +test-data/activity_4_intervals.tcx.gz +test-data/activity_5_grand_tour.fit.gz +test-data/activity_6_weekend_ride.fit.gz +test-data/manifest.json +tools-gps-tracks.mjs +tools-gps-tracks.mjs.map +uplot.mjs +uplot.mjs.map diff --git a/suites-experimental/tools-gps-tracks/dist/styles.css b/suites-experimental/tools-gps-tracks/dist/styles.css new file mode 100644 index 000000000..900beeba6 --- /dev/null +++ b/suites-experimental/tools-gps-tracks/dist/styles.css @@ -0,0 +1,2 @@ +.uplot,.uplot *,.uplot *:before,.uplot *:after{box-sizing:border-box}.uplot{font-family:system-ui,-apple-system,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,sans-serif,"Apple Color Emoji","Segoe UI Emoji",Segoe UI Symbol,"Noto Color Emoji";line-height:1.5;width:min-content}.u-title{text-align:center;font-size:18px;font-weight:700}.u-wrap{position:relative;user-select:none}.u-over,.u-under{position:absolute}.u-under{overflow:hidden}.uplot canvas{display:block;position:relative;width:100%;height:100%}.u-axis{position:absolute}.u-legend{font-size:14px;margin:auto;text-align:center}.u-inline{display:block}.u-inline *{display:inline-block}.u-inline tr{margin-right:16px}.u-legend th{font-weight:600}.u-legend th>*{vertical-align:middle;display:inline-block}.u-legend .u-marker{width:1em;height:1em;margin-right:4px;background-clip:padding-box!important}.u-inline.u-live th:after{content:":";vertical-align:middle}.u-inline:not(.u-live) .u-value{display:none}.u-series>*{padding:4px}.u-series th{cursor:pointer}.u-legend .u-off>*{opacity:.3}.u-select{background:#00000012;position:absolute;pointer-events:none}.u-cursor-x,.u-cursor-y{position:absolute;left:0;top:0;pointer-events:none;will-change:transform}.u-hz .u-cursor-x,.u-vt .u-cursor-y{height:100%;border-right:1px dashed #607D8B}.u-hz .u-cursor-y,.u-vt .u-cursor-x{width:100%;border-bottom:1px dashed #607D8B}.u-cursor-pt{position:absolute;top:0;left:0;border-radius:50%;border:0 solid;pointer-events:none;will-change:transform;background-clip:padding-box!important}.u-axis.u-off,.u-select.u-off,.u-cursor-x.u-off,.u-cursor-y.u-off,.u-cursor-pt.u-off{display:none}@font-face{font-family:Material Symbols Rounded;font-style:normal;font-weight:100 700;src:url("./material_symbols_rounded.woff2") format("woff2")}:root{--bg: #0e0e10;--surface: #18181b;--surface2: #222226;--border: #2e2e34;--border2: #3a3a42;--text: #e8e8ec;--text-muted: #888896;--text-dim: #555564;--theme-color: #3b82f6;--accent: var(--theme-color);--accent-hover: color-mix(in srgb, var(--theme-color) 80%, white);--danger: #ef4444;--sidebar-w: 230px;--fs-s: 10px;--fs-m: 12px;--fs-l: 14px;--is-s: 16px;--is-m: 20px;--handle-color: #3b82f6}.font-s{font-size:10px}.font-m{font-size:12px}.font-l{font-size:14px}*{box-sizing:border-box}body,html{margin:0;padding:0;width:100%;height:100%;background:var(--bg);color:var(--text);font-family:system-ui,-apple-system,sans-serif;font-size:var(--fs-m)}body{opacity:1}body.loading-fonts{opacity:0}button{font-family:inherit;cursor:pointer;font-size:var(--fs-m)}.material-symbols-rounded{font-family:Material Symbols Rounded;font-weight:400;font-style:normal;font-size:var(--is-m);line-height:1;letter-spacing:normal;text-transform:none;display:inline-block;white-space:nowrap;word-wrap:normal;direction:ltr;-webkit-font-smoothing:antialiased;font-variation-settings:"FILL" 0,"wght" 400,"GRAD" 0,"opsz" 24}#app{display:flex;width:100vw;height:100vh;overflow:hidden}.hidden{display:none!important}#global-loader{position:fixed;inset:0;background:#000000b3;z-index:10000;display:flex;align-items:center;justify-content:center}.loader-box{background:var(--surface);border:1px solid var(--border);border-radius:12px;padding:0;display:flex;flex-direction:row;align-items:stretch;color:var(--text);font-weight:700;box-shadow:0 4px 20px #00000080;max-width:450px;width:90%;overflow:hidden;position:relative}#loader-close{position:absolute;top:12px;right:12px;background:none;border:none;color:var(--text-muted);cursor:pointer;padding:4px;border-radius:50%;display:flex;align-items:center;justify-content:center}#loader-close:hover{background:var(--surface2);color:var(--text)}.loader-icon-wrap{background:color-mix(in srgb,var(--accent) 10%,transparent);display:flex;flex-direction:column;align-items:center;justify-content:center;padding:24px;border-right:1px solid var(--border);gap:8px}.loader-icon-wrap>span{font-size:var(--fs-l);color:var(--accent)}#loader-warning-count{font-size:var(--fs-m);color:#f59e0b;font-weight:700;display:none;align-items:center;gap:4px}.loader-content{padding:24px;flex:1;display:flex;flex-direction:column;gap:12px;justify-content:center}#loader-text{font-size:var(--fs-l);font-weight:700;color:var(--text)}#loader-filename{font-size:var(--fs-m);color:var(--text-muted);font-weight:400;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;width:300px}#loader-progress-wrap{width:300px;height:4px;background:var(--surface2);border-radius:2px;margin-top:4px;overflow:hidden}#loader-subtext{font-size:var(--fs-m);color:var(--text-muted);font-weight:400;display:flex;align-items:center;justify-content:space-between;width:300px;margin-top:0}.progress-index{text-align:left}.progress-total{text-align:right}#loader-progress-bar{width:0%;height:100%;background:var(--accent)}#sidebar{width:var(--sidebar-w);flex-shrink:0;display:flex;flex-direction:column;background:var(--surface);border-right:1px solid var(--border);z-index:10}#sidebar-header{padding:16px 12px;border-bottom:1px solid var(--border)}.logo{display:flex;align-items:center;gap:10px}.logo-icon{color:var(--accent);font-size:var(--fs-l)}.logo-title{font-weight:800;font-size:var(--fs-l);letter-spacing:-.5px}.logo-sub{font-size:var(--fs-s);text-transform:uppercase;color:var(--text-dim);font-weight:700;letter-spacing:1px;margin-top:-2px}#drop-zone{margin:12px;padding:20px 12px;border:2px dashed var(--border2);border-radius:12px;display:flex;flex-direction:column;align-items:center;text-align:center;position:relative}#drop-zone.drop-active{border-color:var(--accent);background:color-mix(in srgb,var(--theme-color) 5%,transparent)}.drop-icon{font-size:var(--is-m);color:var(--text-muted);margin-bottom:4px}.drop-primary{font-size:var(--fs-m);font-weight:600;color:var(--text)}.drop-formats{display:flex;justify-content:center;gap:6px;margin-top:8px}.drop-formats .badge{background:var(--surface2);color:var(--text-dim);font-size:var(--fs-s);padding:2px 6px}.drop-browse-container{margin-top:12px;font-size:var(--fs-s);color:var(--text-muted);display:flex;align-items:center;justify-content:center;gap:2px}.drop-browse{color:var(--accent);font-weight:600;cursor:pointer;display:inline-flex;align-items:center;gap:4px}.drop-browse .material-symbols-rounded{font-size:var(--fs-l)}.drop-browse:hover{color:var(--accent-hover)}.drop-separator{margin:0 6px;color:var(--border2);vertical-align:middle}#track-list-header{display:flex;align-items:center;justify-content:space-between;padding:12px 12px 8px}.section-label{font-size:var(--fs-s);font-weight:700;text-transform:uppercase;color:var(--text-dim);letter-spacing:.5px}.header-actions{display:flex;gap:0}#track-controls,#sort-controls{padding:8px 12px;display:flex;flex-direction:column;gap:8px;border-bottom:1px solid var(--border)}.search-bar{display:flex;background:var(--bg);border:1px solid var(--border);border-radius:6px;padding:2px 4px;align-items:center}.search-icon{font-size:var(--is-s);color:var(--text-dim);margin-left:4px}.search-bar input{flex:1;background:transparent;border:none;padding:4px 6px;color:var(--text);font-size:var(--fs-m)}.search-bar input:focus{outline:none}.icon-btn.mini{width:20px;height:20px;color:var(--text-dim)}.icon-btn.mini .material-symbols-rounded{font-size:var(--is-s)}.icon-btn.mini.active{color:var(--accent)}.sort-ctrl{display:flex;align-items:center;gap:8px}.sort-ctrl label{font-size:var(--fs-s);color:var(--text-dim);text-transform:uppercase;font-weight:600}.sort-select-btn{display:flex;align-items:center;gap:6px;background:var(--surface2);border:1px solid var(--border);padding:4px 8px;border-radius:6px;flex:1;height:24px;box-sizing:border-box;font-size:var(--fs-s);color:var(--text-muted)}.sort-select-btn:hover{border-color:var(--accent);color:var(--text)}.sort-select-btn .material-symbols-rounded{font-size:var(--is-s)}.sort-select-btn .arrow{font-size:var(--fs-l);margin-left:auto;opacity:.5}#filter-panel{padding:12px;background:var(--surface2);border-bottom:1px solid var(--border);display:flex;flex-direction:column;gap:12px}#filter-panel.hidden{display:none}.filter-group{display:flex;flex-direction:column;gap:4px}.filter-group label{font-size:var(--fs-s);font-weight:600;text-transform:uppercase;color:var(--text-dim);display:flex;align-items:center;gap:6px}.filter-group label .material-symbols-rounded{font-size:var(--is-s);opacity:.7}.filter-metrics-grid{display:flex;flex-wrap:wrap;gap:6px;margin-top:4px}.mini-pill{width:28px;height:28px;display:flex;align-items:center;justify-content:center;background:var(--surface);border:1px solid var(--border);border-radius:6px;color:var(--text-dim);cursor:pointer}.mini-pill:hover{border-color:var(--text-muted);color:var(--text-muted)}.mini-pill.active{background:var(--accent);border-color:var(--accent);color:#fff}.mini-pill .material-symbols-rounded{font-size:var(--is-s)}.filter-row{display:flex;align-items:center;gap:6px}.filter-row input{flex:1;background:var(--bg);border:1px solid var(--border);border-radius:4px;padding:4px 6px;color:var(--text);font-size:var(--fs-s)}.filter-row span{font-size:var(--fs-s);color:var(--text-dim)}#filter-panel input:focus{border-color:var(--accent);outline:none}.filter-footer{display:flex;justify-content:flex-end;margin-top:4px}#track-list{flex:1;overflow-y:auto;padding:4px 0;scrollbar-width:none}#track-list::-webkit-scrollbar{width:0;height:0;display:none}#track-list::-webkit-scrollbar-thumb{background:var(--border2);border-radius:2px}#track-list-empty{padding:40px 20px;text-align:center;color:var(--text-dim);font-size:var(--fs-m)}.track-item{display:flex;align-items:center;padding:6px 8px;gap:0;cursor:pointer;border-left:3px solid transparent}.track-item:hover{background:#ffffff08}.track-item.selected{background:color-mix(in srgb,var(--theme-color) 8%,transparent);border-left-color:var(--accent)}.track-color{width:3px;border-radius:1px;flex-shrink:0;margin-right:8px;align-self:stretch}.track-info{flex:1;min-width:0;margin-right:2px}.track-name{font-size:var(--fs-m);font-weight:600;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;margin-bottom:1px}.track-meta{font-size:var(--fs-s);color:var(--text-dim);display:flex;gap:6px;align-items:center}.track-item .icon-btn.mini{width:22px;height:22px;padding:0;margin:0}.badge{font-size:var(--fs-s);background:var(--surface2);padding:1px 4px;border-radius:3px;font-weight:700;color:var(--text-muted)}.track-tags{display:flex;flex-wrap:wrap;gap:4px;margin-top:6px;overflow:hidden;max-height:32px}.track-tag{font-size:var(--fs-s);font-weight:700;color:var(--text-dim);background:#ffffff08;border:1px solid var(--border);padding:1px 5px;border-radius:3px;white-space:nowrap}.track-item.selected .track-tag{border-color:#ffffff1a;color:var(--text-muted)}#sidebar-resizer{width:4px;cursor:col-resize;background:transparent;flex-shrink:0;z-index:1000;margin-left:-2px;margin-right:-2px;user-select:none}#sidebar-resizer:hover,#sidebar-resizer.dragging{background:var(--handle-color)}body.dragging-sidebar{user-select:none;cursor:col-resize!important}#main{flex:1;display:flex;flex-direction:column;min-width:0}#map-container{flex:1;position:relative;min-height:100px}#map-container.editing-mode{border:4px solid var(--accent);box-shadow:0 0 15px var(--accent);z-index:1000}#map{width:100%;height:100%;background:var(--bg)}#map-controls{position:absolute;top:12px;left:12px;z-index:1000;display:flex;align-items:center;gap:8px}#basemap-switcher{display:flex;background:var(--surface);border:1px solid var(--border);border-radius:8px;padding:2px;height:24px;box-sizing:border-box;box-shadow:0 4px 12px #0000004d;align-items:center;gap:0}#selected-extra-map{font-size:var(--fs-s);font-weight:700;color:var(--accent);padding:0 6px;max-width:120px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;cursor:pointer}#selected-extra-map.hidden{display:none}#btn-extra-layers{border:none!important;border-left:1px solid var(--border)!important;border-radius:0!important;margin-left:0!important;padding-left:6px!important;padding-right:0!important;width:auto!important;height:100%!important;background:transparent!important}#extra-layers-menu{position:absolute;top:40px;right:0;background:var(--surface);border:1px solid var(--border);border-radius:8px;box-shadow:0 4px 20px #00000080;max-height:300px;overflow-y:auto;width:200px;z-index:1000;padding:4px}#extra-layers-menu.hidden{display:none}.extra-layers-header{font-size:var(--fs-s);font-weight:700;color:var(--text-dim);text-transform:uppercase;padding:6px 8px;border-bottom:1px solid var(--border);margin-bottom:4px}.extra-layers-search{padding:4px 8px;border-bottom:1px solid var(--border);margin-bottom:4px}#extra-layers-search-input{width:100%;background:var(--surface2);border:1px solid var(--border);border-radius:4px;color:var(--text);font-size:var(--fs-s);padding:4px 8px;outline:none}#extra-layers-search-input:focus{border-color:var(--accent)}.extra-layers-license{font-size:var(--fs-s);color:var(--text-dim);padding:4px 8px;border-bottom:1px solid var(--border);margin-bottom:4px}.extra-layers-license a{color:var(--accent);text-decoration:none}.extra-layers-license a:hover{text-decoration:underline}.extra-layer-row{margin-bottom:2px}.extra-layer-item{display:block;width:100%;text-align:left;background:transparent;border:none;color:var(--text-muted);padding:4px 10px 2px;font-size:var(--fs-s);font-weight:600;border-radius:4px;cursor:pointer;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.extra-layer-item:hover{background:var(--surface2);color:var(--text)}.extra-layer-attribution{font-size:var(--fs-s);color:var(--text-dim);padding:0 10px 4px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}#map-loader{background:var(--surface);border:1px solid var(--border);border-radius:8px;padding:0 10px;height:24px;box-sizing:border-box;display:flex;align-items:center;gap:8px;font-size:var(--fs-s);font-weight:600;color:var(--text-muted);box-shadow:0 4px 12px #0000004d}.spinner-sm{width:12px;height:12px;border:2px solid var(--surface2);border-top-color:var(--accent);border-radius:50%}.bm-btn{background:transparent;border:none;color:var(--text-muted);padding:2px 6px;font-size:var(--fs-s);font-weight:600;border-radius:6px}.bm-btn:hover{color:var(--text)}.bm-btn.active{background:var(--surface2);color:var(--accent)}.map-cursor-dot{pointer-events:none}.dot-inner{width:12px;height:12px;background:#fff;border:2px solid #000;border-radius:50%;box-shadow:0 0 0 2px #ffffff80}#map-cursor-info{position:absolute;top:12px;right:12px;z-index:1000;background:var(--surface);border:1px solid var(--border);border-radius:8px;padding:8px 12px;font-size:var(--fs-s);color:var(--text);box-shadow:0 4px 12px #0000004d;display:none;pointer-events:none}#resize-handle{height:6px;background:var(--bg);border-top:1px solid var(--border);border-bottom:1px solid var(--border);cursor:ns-resize;display:flex;align-items:center;justify-content:center;flex-shrink:0;z-index:100}#resize-handle:hover{background:var(--handle-color)}.handle-grip{width:32px;height:2px;background:var(--border2);border-radius:1px}#chart-panel{display:flex;flex-direction:column;background:var(--surface);overflow:hidden;flex:1;width:100%;height:100%;min-height:0}#tab-nav{display:flex;padding:0 12px;border-bottom:1px solid var(--border);gap:4px}.tab-btn{background:transparent;border:none;border-bottom:2px solid transparent;color:var(--text-dim);padding:10px 12px;font-size:var(--fs-m);font-weight:700;display:flex;align-items:center;gap:6px}.tab-btn:hover{color:var(--text-muted)}.tab-btn.active{color:var(--accent);border-bottom-color:var(--accent)}.tab-btn .material-symbols-rounded{font-size:var(--is-s)}.tab-content{display:none;flex:1;min-height:0}.tab-content.active{display:flex;flex-direction:column;overflow-y:auto;scrollbar-width:none}.tab-content.active::-webkit-scrollbar{width:0;height:0}.tab-content.active::-webkit-scrollbar-track{background:transparent}.tab-content.active::-webkit-scrollbar-thumb{background:var(--border);border-radius:3px}.tab-content.active::-webkit-scrollbar-thumb:hover{background:var(--text-dim)}#chart-toolbar{display:flex;align-items:center;padding:6px 12px;gap:12px;border-bottom:1px solid var(--border);background:var(--bg);min-height:40px;position:sticky;top:0;z-index:100}.icon-btn{background:transparent;border:1px solid transparent;color:var(--text-muted);width:24px;height:24px;border-radius:6px;display:flex;align-items:center;justify-content:center}.icon-btn.hidden{display:none}.icon-btn:hover{background:var(--surface2);color:var(--text)}.icon-btn.active{background:var(--surface2);color:var(--accent)}.icon-btn.danger:hover{background:#ef44441f;color:var(--danger)}.icon-btn .material-symbols-rounded{font-size:var(--fs-l)}#btn-metric-menu{flex:0 0 auto}#metric-pills{display:flex;gap:6px;flex:1;overflow:hidden}.metric-pill:not(.active){display:none}.metric-pill.overflow-hidden{display:none!important}#pill-overflow{color:var(--text-muted);font-weight:700;padding:0 4px;align-self:flex-end}.metric-pill{height:24px;padding:0 8px;border-radius:12px;border:1px solid var(--border);background:var(--surface2);color:var(--text-muted);font-size:var(--fs-s);font-weight:500;display:flex;align-items:center;gap:4px;white-space:nowrap}.metric-pill .material-symbols-rounded{font-size:var(--fs-m)}.metric-pill.active{background:color-mix(in srgb,var(--pill-color) 15%,var(--surface2));border-color:var(--pill-color);color:var(--pill-color)}.metric-pill.disabled{opacity:.3;filter:grayscale(1);cursor:default}#chart-options{display:flex;align-items:center;gap:0;margin-left:auto}.seg-ctrl{display:flex;background:var(--surface2);border:1px solid var(--border);border-radius:6px;padding:2px;height:24px;box-sizing:border-box}.seg-btn{background:transparent;border:none;color:var(--text-dim);padding:0 8px;font-size:var(--fs-s);font-weight:700;border-radius:4px;display:flex;align-items:center;gap:6px;height:100%}.seg-btn .material-symbols-rounded{font-size:var(--fs-m)}.seg-btn.active{background:var(--bg);color:var(--text);box-shadow:0 1px 3px #0003}#chart-stats{padding:4px 14px;border-bottom:1px solid var(--border);background:var(--surface2)}#chart-stats.hidden{display:none}#chart-stats-wrap{display:flex;flex-direction:column;gap:2px}.stats-row{display:flex;align-items:center;gap:16px}.stats-row-label{width:45px;font-size:var(--fs-s);font-weight:700;text-transform:uppercase;letter-spacing:.8px;color:var(--text-muted);white-space:nowrap}.chart-row-stats-container{display:flex;flex:1;align-items:center;justify-content:space-between}.chart-row-right-actions{width:130px;display:none;align-items:center;justify-content:flex-end;flex-shrink:0}.with-histogram .chart-row-right-actions{display:flex}.chart-stats-total,.chart-stats-selection{display:flex;gap:10px;font-size:var(--fs-s);color:var(--text-dim);align-items:center;white-space:nowrap;font-weight:500}.chart-stats-selection{margin-left:auto;position:relative}.chart-stats-selection:after{content:"";position:absolute;bottom:-6px;left:0;right:0;height:2px;background-color:var(--sel-color);pointer-events:none}.all-chip{background:var(--surface2);color:var(--text-muted);font-weight:500;padding:2px;border-radius:4px;font-size:var(--fs-m);margin-right:4px;cursor:pointer;border:1px solid var(--border);display:flex;align-items:center;justify-content:center}.all-chip:hover{background:var(--border);color:var(--text)}.sel-tag{background:var(--surface2);color:var(--text-muted);font-weight:500;padding:2px;border-radius:4px;font-size:var(--fs-m);margin-right:4px;display:flex;align-items:center;justify-content:center;border:1px solid var(--border)}.mm-item{display:flex;align-items:center;gap:2px;white-space:nowrap}.mm-icon{font-size:var(--fs-m);color:var(--text-dim)}.uplot{color:var(--text-muted)!important}.u-title,.u-legend{display:none}.chart-row .u-select{background:color-mix(in srgb,var(--chart-color, #3b82f6) 12%,transparent)!important;border:1px solid color-mix(in srgb,var(--chart-color, #3b82f6) 35%,transparent)!important}.u-cursor-x{border-left:1px dashed rgba(255,255,255,.55)!important}.u-cursor-y{display:none!important}.sel-fill{position:absolute;top:0;background:color-mix(in srgb,var(--theme-color) 8%,transparent);border-left:1px solid color-mix(in srgb,var(--theme-color) 30%,transparent);border-right:1px solid rgba(148,163,184,.4);pointer-events:none}.sel-handle{position:absolute;top:50%;transform:translate(-50%,-50%);width:8px;height:24px;background:#ffffffd9;border:1px solid rgba(0,0,0,.3);border-radius:4px;cursor:ew-resize;pointer-events:auto;display:flex;align-items:center;justify-content:center;z-index:10}.sel-handle:after{content:"";width:2px;height:10px;border-left:1px solid rgba(0,0,0,.4);border-right:1px solid rgba(0,0,0,.4)}.sel-handle:hover{background:#fff;transform:translate(-50%,-50%) scale(1.1)}.sel-graph-marker{position:absolute;transform:translate(-50%,-50%);pointer-events:none;z-index:11;background:none!important}.sel-line{position:absolute;top:0;height:100%;width:0;border-left-width:1.5px;border-left-style:solid;pointer-events:none}.sel-line-label{position:absolute;top:4px;font-size:var(--fs-s);font-weight:700;background:#0e0e10eb;color:#fff;padding:2px 5px;border-radius:4px;pointer-events:none;white-space:nowrap;z-index:10;border:1px solid var(--border)}#charts-container{flex:1;overflow-y:auto;overflow-x:hidden;padding:6px 0 4px;scrollbar-width:none}#charts-container::-webkit-scrollbar{width:0;height:0}#charts-container::-webkit-scrollbar-thumb{background:var(--border2);border-radius:2px}.fixed-x-label{position:absolute;bottom:4px;font-size:var(--fs-s);font-family:system-ui,sans-serif;color:#555564;z-index:10;pointer-events:none}.fixed-x-max{right:0;text-align:right}.chart-row-body{display:flex;flex-direction:row;padding:0 14px;min-width:0;overflow:hidden}.chart-row-body>div:first-child{flex:1;min-width:0}.hist-col{width:130px;flex-shrink:0;border-left:1px solid var(--border);margin-left:20px;margin-right:0;display:none;align-items:center;justify-content:center;cursor:crosshair}.hist-col.visible{display:flex}.hist-canvas{display:block}#hist-line{position:fixed;height:0;border-top:1px dashed rgba(255,255,255,.25);pointer-events:none;display:none;z-index:10000}#hist-tooltip{position:fixed;background:var(--surface);border:1px solid var(--border2);border-radius:5px;padding:8px 10px;pointer-events:none;display:none;z-index:10001;box-shadow:0 4px 15px #00000080;font-size:var(--fs-s)}.hist-tt-header{font-weight:800;margin-bottom:6px;padding-left:8px;border-left:3px solid var(--accent);color:var(--text)}.hist-tt-grid{display:grid;grid-template-columns:1fr auto auto;gap:10px;align-items:baseline}.hist-tt-label{font-size:var(--fs-s);color:var(--text-muted)}.hist-tt-value{font-size:var(--fs-s);color:var(--text);text-align:right;font-variant-numeric:tabular-nums}.hist-tt-pct{font-size:var(--fs-s);color:var(--text-dim);text-align:right;min-width:36px;font-variant-numeric:tabular-nums}.hist-tt-grid.sel-row{margin-top:2px;opacity:.9}.hist-tt-label.sel{color:var(--accent);font-weight:700}.chart-row{border-bottom:1px solid var(--border)}.chart-row:last-child{border-bottom:none}.chart-row-header{display:flex;align-items:center;gap:12px;padding:8px 14px 4px;min-height:28px}.progress-chart-row{display:flex;flex-direction:column}.progress-chart-row .chart-row-header{height:36px;box-sizing:border-box;padding-top:0;padding-bottom:0}.progress-chart-row .chart-row-actions{margin-left:auto}.progress-chart-row .chart-row-body{flex:1;padding:0}.chart-row-label-group{display:flex;align-items:center;gap:8px;flex-shrink:0;width:140px}.chart-row-actions{display:flex;align-items:center;gap:0}.chart-row-icon{font-size:var(--is-s);color:var(--chart-color)}.chart-row-label{font-size:var(--fs-m);font-weight:700;text-transform:uppercase;letter-spacing:.8px;color:var(--text-muted);white-space:nowrap}.track-picker-popup{padding:8px;background:var(--surface);color:var(--text);border-radius:8px;min-width:160px}.picker-header{font-size:var(--fs-s);text-transform:uppercase;font-weight:700;color:var(--text-dim);margin-bottom:8px;padding-bottom:4px;border-bottom:1px solid var(--border)}.picker-list{display:flex;flex-direction:column;gap:2px}.picker-item{display:flex;align-items:center;gap:8px;padding:6px 8px;border-radius:6px;cursor:pointer}.picker-item:hover{background:var(--surface2)}.picker-color{width:3px;height:14px;border-radius:1px}.picker-info{display:flex;flex-direction:column;min-width:0}.picker-name{font-size:var(--fs-s);font-weight:600;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:180px}.modal-backdrop{position:fixed;inset:0;background:#000000bf;backdrop-filter:blur(4px);display:flex;align-items:center;justify-content:center;z-index:20000}.modal-content{background:var(--surface);border:1px solid var(--border);border-radius:12px;padding:24px;width:100%;max-width:320px;box-shadow:0 20px 40px #00000080;display:flex;flex-direction:column;gap:16px}.modal-title{font-size:var(--fs-l);font-weight:800;color:var(--text)}.modal-body{font-size:var(--fs-m);color:var(--text-muted);line-height:1.5}.modal-input-wrap{display:flex;flex-direction:column;gap:8px}.modal-input-label{font-size:var(--fs-s);font-weight:700;text-transform:uppercase;color:var(--text-dim)}.modal-input{background:var(--bg);border:1px solid var(--border2);border-radius:6px;padding:10px;color:var(--text);font-size:var(--fs-l);font-weight:800;text-align:center;letter-spacing:4px}.modal-input:focus{outline:none;border-color:var(--accent)}.modal-actions{display:flex;gap:12px;margin-top:8px}.modal-btn{flex:1;padding:10px;border-radius:6px;font-size:var(--fs-m);font-weight:700;border:1px solid transparent}.modal-btn.cancel{background:var(--surface2);color:var(--text);border-color:var(--border)}.modal-btn.cancel:hover{background:var(--border)}.modal-btn.confirm{background:var(--danger);color:#fff}.modal-btn.confirm:disabled{opacity:.3;cursor:not-allowed;filter:grayscale(1)}.modal-btn.confirm:not(:disabled):hover{filter:brightness(1.1)}.picker-meta{font-size:var(--fs-s);color:var(--text-dim);font-weight:700}.leaflet-popup-content-wrapper,.leaflet-popup-tip{background:var(--surface)!important;color:var(--text)!important;border:1px solid var(--border)!important;box-shadow:0 4px 12px #0006!important}.leaflet-popup-content{margin:0!important}.leaflet-popup-tip{border:none!important}.map-color-btn{margin-left:4px}.map-color-btn .material-symbols-rounded{font-size:var(--is-s)}.map-color-btn.active{color:var(--chart-color, var(--accent))}.smooth-btn.active{color:var(--accent)}.cur-y-val{position:absolute;left:0;top:0;pointer-events:none;font-size:var(--fs-s);font-weight:700;background:#0e0e10eb;padding:2px 6px;border-radius:4px;border:1px solid rgba(255,255,255,.1);z-index:2000;white-space:nowrap}.cur-y-val.pin{z-index:1000;opacity:.7}#details-view{width:100%;padding:24px;max-width:900px;margin:0 auto}#details-header{margin-bottom:32px}.details-title{font-size:var(--fs-l);font-weight:800;margin-bottom:4px}.details-subtitle{font-size:var(--fs-m);color:var(--text-muted)}.details-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(200px,1fr));gap:16px}.details-card{background:var(--surface2);border:1px solid var(--border);padding:16px;border-radius:12px;display:flex;flex-direction:column;gap:4px}.details-card .material-symbols-rounded{color:var(--accent);margin-bottom:4px;font-size:var(--is-m)}.details-card-label{font-size:var(--fs-s);font-weight:500;text-transform:uppercase;color:var(--text-dim);letter-spacing:.5px}.details-card-value{font-size:var(--fs-l);font-weight:500}.empty-state{display:flex;flex-direction:column;align-items:center;justify-content:center;height:100%;color:var(--text-dim);gap:12px;padding:40px 20px}.empty-icon{font-size:var(--fs-l);opacity:.2}.empty-text{font-size:var(--fs-m);font-weight:500}#chart-empty{height:100%}#details-empty{margin-top:40px}#toast-container{position:fixed;bottom:20px;left:50%;transform:translate(-50%);display:flex;flex-direction:column;gap:8px;z-index:9999}.toast{background:var(--surface2);color:var(--text);padding:10px 20px;border-radius:8px;font-size:var(--fs-m);font-weight:600;box-shadow:0 8px 24px #0006;border:1px solid var(--border2)}.toast.error{border-color:var(--danger)}.sort-menu-popup,.metric-menu-popup{position:fixed;z-index:10000;background:var(--surface2);border:1px solid var(--border);border-radius:8px;padding:6px;min-width:180px;box-shadow:0 8px 24px #00000080}.menu-item{display:flex;align-items:center;gap:10px;padding:8px 10px;border-radius:6px;cursor:pointer;color:var(--text-muted)}.menu-item:hover{background:var(--surface);color:var(--text)}.menu-item.active{color:var(--text);font-weight:600}.menu-item .material-symbols-rounded{font-size:var(--is-s)}.menu-item .item-label{flex:1;font-size:var(--fs-m)}.menu-item .arrow,.menu-item .check{font-size:var(--is-s);color:var(--text-dim)}#insights-view{width:100%;padding:24px;max-width:1000px;margin:0 auto}.insights-grid{column-count:2;column-gap:24px}@media (max-width: 900px){.insights-grid{column-count:1}}.insight-card{display:inline-flex;width:100%;break-inside:avoid;margin-bottom:24px;background:var(--surface2);border:1px solid var(--border);padding:20px;border-radius:12px;flex-direction:column;gap:16px}.insight-title{font-size:var(--fs-m);font-weight:700;text-transform:uppercase;letter-spacing:.8px;display:flex;align-items:center;gap:8px;color:var(--text-muted)}.insight-title .material-symbols-rounded{font-size:var(--is-s)}.insight-chart-container{width:100%;height:250px}.power-curve-table{width:100%;border-collapse:collapse;font-size:var(--fs-m);margin-top:0}.power-curve-table th{text-align:left;color:var(--text-dim);padding:4px 8px;font-weight:600;border-bottom:1px solid var(--border)}.power-curve-table td{padding:6px 8px;border-bottom:1px solid var(--border)}.power-curve-table tr:last-child td{border-bottom:none}.progress-details summary{cursor:pointer;font-size:var(--fs-m);color:var(--text-dim);display:flex;align-items:center;gap:4px;list-style:none}.progress-details summary::-webkit-details-marker{display:none}.progress-details[open] summary .expand-icon{transform:rotate(90deg)}.progress-details summary .material-symbols-rounded{font-size:var(--fs-l)}.power-curve-val{font-weight:700;color:var(--text);text-align:right}.evolution-select-btn{background:none;border:none;cursor:pointer;color:var(--chart-color);padding:0;font-size:var(--fs-l);vertical-align:middle;margin-left:4px}.hr-settings-popup{display:flex;flex-direction:column}.hr-input-row{display:flex;justify-content:space-between;align-items:center;font-size:var(--fs-m);color:var(--text-muted);font-weight:600}.hr-zone-input{background:var(--surface);border:1px solid var(--border);border-radius:4px;color:var(--text);width:60px;padding:4px 8px;font-size:var(--fs-m);text-align:right;outline:none}.hr-zone-input:focus{border-color:var(--accent)}.save-btn{background:var(--accent);color:#fff;border:none;border-radius:6px;padding:8px;font-size:12px;font-weight:700;cursor:pointer}.save-btn:hover{background:var(--accent-hover)}.menu-item.active .arrow,.menu-item.active .check{color:var(--accent)}.evolution-toolbar{display:flex;align-items:center;justify-content:space-between;padding:6px 12px;border-bottom:1px solid var(--border);background:var(--bg);min-height:40px}.evolution-toolbar-left,.evolution-toolbar-right{display:flex;align-items:center;gap:12px}.evolution-toolbar .metric-pills{display:flex;gap:6px}.evolution-toolbar .metric-pill{cursor:pointer}.evolution-container{display:flex;flex-direction:column;gap:0px;padding:6px 0 4px}.evolution-card-group{display:flex;flex-direction:column;gap:10px;width:100%}.evolution-legend{display:flex;gap:16px;font-size:12px;color:var(--text-dim);margin-top:0;margin-bottom:0;justify-content:center}.evolution-legend-item{display:flex;align-items:center;gap:6px}.evolution-legend-line{width:16px;height:0;display:inline-block}.evolution-legend-line.solid{border-top:2px solid var(--chart-color)}.evolution-legend-line.dashed{border-top:2px dashed #888888}.evolution-tooltip-title{font-weight:700;margin-bottom:4px}.evolution-tooltip-row{display:flex;justify-content:space-between;gap:15px}.evolution-tooltip-label{color:#8a8a93}.evolution-tooltip-value{font-weight:700}.evolution-empty-text{color:var(--text-dim);font-size:12px;text-align:center;margin-top:20px}.evolution-chart-container{height:250px;flex:1}.distance-progress-card .card-content,.elevation-progress-card .card-content{display:flex;gap:20px}.chart-scroll-container{flex:1;overflow-x:auto;position:relative;background:var(--bg-dim);border-radius:8px;padding:10px;scrollbar-width:none}.chart-scroll-container::-webkit-scrollbar{width:0;height:0;display:none}.accumulated-table-container{width:250px;background:var(--bg-dim);border-radius:8px;padding:15px}.accumulated-table{width:100%;border-collapse:collapse;font-size:var(--fs-m)}.accumulated-table th,.accumulated-table td{padding:4px 8px;text-align:left;border-bottom:1px solid var(--border-color)}.accumulated-table th{color:var(--text-dim);font-weight:500;text-transform:uppercase;font-size:var(--fs-m);letter-spacing:.5px}.accumulated-table td:last-child{text-align:right;font-weight:500;font-family:monospace}.accumulated-table tr:last-child td{border-bottom:none}.chart-scroll-container::-webkit-scrollbar{height:4px}.chart-scroll-container::-webkit-scrollbar-thumb{background:var(--border2);border-radius:2px}.u-title,.u-legend{font-size:var(--fs-m)!important}.courses-container{padding:16px;display:flex;flex-direction:column;gap:16px}.courses-list{display:flex;flex-direction:column;gap:8px}.course-item{background:var(--surface2);border:1px solid var(--border);border-radius:8px;display:flex;flex-direction:column;cursor:pointer;overflow:hidden}.course-item:hover{background:var(--surface3);border-color:var(--border-high)}.course-item-header{padding:6px 12px;display:flex;align-items:center;gap:12px;width:100%}.course-name{font-weight:600;flex:1}.course-meta{color:var(--text-muted);font-size:var(--fs-s)}.course-actions{display:flex;gap:4px}.course-actions .icon-btn{opacity:.5}.course-item:hover .course-actions .icon-btn{opacity:1}.expand-icon{color:var(--text-muted)}.course-tracks{padding:8px 12px;border-top:1px solid var(--border);background:var(--surface1)}.course-tracks-title{font-size:var(--fs-xs);font-weight:700;color:var(--text-muted);text-transform:uppercase;letter-spacing:.5px;margin-bottom:6px}.course-tracks-table{width:100%;border-collapse:collapse;font-size:var(--fs-s);color:var(--text)}.course-tracks-table th,.course-tracks-table td{padding:6px 8px;text-align:right;border-bottom:1px solid var(--border-low)}.course-tracks-table th:first-child,.course-tracks-table td:first-child,.course-tracks-table th:nth-child(2),.course-tracks-table td:nth-child(2){text-align:left}.course-tracks-table td:not(:first-child){white-space:nowrap}.course-tracks-table th{font-weight:600;color:var(--text-muted);font-size:var(--fs-xs);text-transform:uppercase;letter-spacing:.5px;white-space:nowrap}.course-tracks-table tr:last-child td{border-bottom:none}.course-tracks-table th:first-child,.course-tracks-table td:first-child{width:100%}.course-track-row:hover{background:var(--surface2)}.course-track-row.selected{background:color-mix(in srgb,var(--theme-color) 8%,transparent)}.track-name-cell.clickable{color:var(--accent);cursor:pointer;font-weight:500}.track-name-cell.clickable:hover{text-decoration:underline}.metric-icon{font-size:16px;vertical-align:middle;margin-right:4px}.metric-icon.duration{color:var(--text-muted)}.metric-icon.speed{color:#4caf50}.metric-icon.power{color:#ff9800}.metric-icon.hr{color:#f44336}.course-point-marker{display:flex;align-items:center;justify-content:center} +/*# sourceMappingURL=styles.css.map */ diff --git a/suites-experimental/tools-gps-tracks/dist/styles.css.map b/suites-experimental/tools-gps-tracks/dist/styles.css.map new file mode 100644 index 000000000..ce44ae349 --- /dev/null +++ b/suites-experimental/tools-gps-tracks/dist/styles.css.map @@ -0,0 +1,7 @@ +{ + "version": 3, + "sources": ["../src/css/uPlot.min.css", "../src/css/styles.css"], + "sourcesContent": [".uplot, .uplot *, .uplot *::before, .uplot *::after {box-sizing: border-box;}.uplot {font-family: system-ui, -apple-system, \"Segoe UI\", Roboto, \"Helvetica Neue\", Arial, \"Noto Sans\", sans-serif, \"Apple Color Emoji\", \"Segoe UI Emoji\", \"Segoe UI Symbol\", \"Noto Color Emoji\";line-height: 1.5;width: min-content;}.u-title {text-align: center;font-size: 18px;font-weight: bold;}.u-wrap {position: relative;user-select: none;}.u-over, .u-under {position: absolute;}.u-under {overflow: hidden;}.uplot canvas {display: block;position: relative;width: 100%;height: 100%;}.u-axis {position: absolute;}.u-legend {font-size: 14px;margin: auto;text-align: center;}.u-inline {display: block;}.u-inline * {display: inline-block;}.u-inline tr {margin-right: 16px;}.u-legend th {font-weight: 600;}.u-legend th > * {vertical-align: middle;display: inline-block;}.u-legend .u-marker {width: 1em;height: 1em;margin-right: 4px;background-clip: padding-box !important;}.u-inline.u-live th::after {content: \":\";vertical-align: middle;}.u-inline:not(.u-live) .u-value {display: none;}.u-series > * {padding: 4px;}.u-series th {cursor: pointer;}.u-legend .u-off > * {opacity: 0.3;}.u-select {background: rgba(0,0,0,0.07);position: absolute;pointer-events: none;}.u-cursor-x, .u-cursor-y {position: absolute;left: 0;top: 0;pointer-events: none;will-change: transform;}.u-hz .u-cursor-x, .u-vt .u-cursor-y {height: 100%;border-right: 1px dashed #607D8B;}.u-hz .u-cursor-y, .u-vt .u-cursor-x {width: 100%;border-bottom: 1px dashed #607D8B;}.u-cursor-pt {position: absolute;top: 0;left: 0;border-radius: 50%;border: 0 solid;pointer-events: none;will-change: transform;/*this has to be !important since we set inline \"background\" shorthand */background-clip: padding-box !important;}.u-axis.u-off, .u-select.u-off, .u-cursor-x.u-off, .u-cursor-y.u-off, .u-cursor-pt.u-off {display: none;}", "/*\n * Copyright (c) 2026, Camillo Bruni.\n * Use of this source code is governed by a BSD-style license that can be\n * found in the LICENSE file.\n */\n\n@import './uPlot.min.css';\n\n/* \u2500\u2500\u2500 Variables \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n@font-face {\n font-family: 'Material Symbols Rounded';\n font-style: normal;\n font-weight: 100 700;\n src: url('../../material_symbols_rounded.woff2') format('woff2');\n}\n\n:root {\n --bg: #0e0e10;\n --surface: #18181b;\n --surface2: #222226;\n --border: #2e2e34;\n --border2: #3a3a42;\n --text: #e8e8ec;\n --text-muted: #888896;\n --text-dim: #555564;\n --theme-color: #3b82f6;\n --accent: var(--theme-color);\n --accent-hover: color-mix(in srgb, var(--theme-color) 80%, white);\n --danger: #ef4444;\n --sidebar-w: 230px;\n\n /* Font Sizes */\n --fs-s: 10px;\n --fs-m: 12px;\n --fs-l: 14px; /* For titles only */\n\n /* Icon Sizes */\n --is-s: 16px;\n --is-m: 20px;\n\n --handle-color: #3b82f6;\n}\n\n/* Font Size Classes */\n.font-s {\n font-size: 10px;\n}\n.font-m {\n font-size: 12px;\n}\n.font-l {\n font-size: 14px;\n}\n\n* {\n box-sizing: border-box;\n}\n\nbody,\nhtml {\n margin: 0;\n padding: 0;\n width: 100%;\n height: 100%;\n background: var(--bg);\n color: var(--text);\n font-family:\n system-ui,\n -apple-system,\n sans-serif;\n font-size: var(--fs-m);\n}\n\nbody {\n opacity: 1;\n}\n\nbody.loading-fonts {\n opacity: 0;\n}\n\nbutton {\n font-family: inherit;\n cursor: pointer;\n font-size: var(--fs-m);\n}\n\n/* Material symbols config */\n.material-symbols-rounded {\n font-family: 'Material Symbols Rounded';\n font-weight: normal;\n font-style: normal;\n font-size: var(--is-m);\n line-height: 1;\n letter-spacing: normal;\n text-transform: none;\n display: inline-block;\n white-space: nowrap;\n word-wrap: normal;\n direction: ltr;\n -webkit-font-smoothing: antialiased;\n font-variation-settings:\n 'FILL' 0,\n 'wght' 400,\n 'GRAD' 0,\n 'opsz' 24;\n}\n\n/* \u2500\u2500\u2500 Layout \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n#app {\n display: flex;\n width: 100vw;\n height: 100vh;\n overflow: hidden;\n}\n\n.hidden {\n display: none !important;\n}\n\n#global-loader {\n position: fixed;\n inset: 0;\n background: rgba(0, 0, 0, 0.7);\n z-index: 10000;\n display: flex;\n align-items: center;\n justify-content: center;\n}\n\n#import-warning {\n}\n\n.loader-box {\n background: var(--surface);\n border: 1px solid var(--border);\n border-radius: 12px;\n padding: 0;\n display: flex;\n flex-direction: row;\n align-items: stretch;\n color: var(--text);\n font-weight: 700;\n box-shadow: 0 4px 20px rgba(0, 0, 0, 0.5);\n max-width: 450px;\n width: 90%;\n overflow: hidden;\n position: relative;\n}\n\n#loader-close {\n position: absolute;\n top: 12px;\n right: 12px;\n background: none;\n border: none;\n color: var(--text-muted);\n cursor: pointer;\n padding: 4px;\n border-radius: 50%;\n display: flex;\n align-items: center;\n justify-content: center;\n}\n\n#loader-close:hover {\n background: var(--surface2);\n color: var(--text);\n}\n\n.loader-icon-wrap {\n background: color-mix(in srgb, var(--accent) 10%, transparent);\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n padding: 24px;\n border-right: 1px solid var(--border);\n gap: 8px;\n}\n\n.loader-icon-wrap > span {\n font-size: var(--fs-l);\n color: var(--accent);\n}\n\n#loader-warning-count {\n font-size: var(--fs-m);\n color: #f59e0b;\n font-weight: 700;\n display: none;\n align-items: center;\n gap: 4px;\n}\n\n.loader-content {\n padding: 24px;\n flex: 1;\n display: flex;\n flex-direction: column;\n gap: 12px;\n justify-content: center;\n}\n\n#loader-text {\n font-size: var(--fs-l);\n font-weight: 700;\n color: var(--text);\n}\n\n#loader-filename {\n font-size: var(--fs-m);\n color: var(--text-muted);\n font-weight: 400;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n width: 300px;\n}\n\n#loader-progress-wrap {\n width: 300px;\n height: 4px;\n background: var(--surface2);\n border-radius: 2px;\n margin-top: 4px;\n overflow: hidden;\n}\n\n#loader-subtext {\n font-size: var(--fs-m);\n color: var(--text-muted);\n font-weight: 400;\n display: flex;\n align-items: center;\n justify-content: space-between;\n width: 300px;\n margin-top: 0;\n}\n\n.progress-index {\n text-align: left;\n}\n\n.progress-total {\n text-align: right;\n}\n\n#loader-progress-bar {\n width: 0%;\n height: 100%;\n background: var(--accent);\n}\n\n/* \u2500\u2500\u2500 Sidebar \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n#sidebar {\n width: var(--sidebar-w);\n flex-shrink: 0;\n display: flex;\n flex-direction: column;\n background: var(--surface);\n border-right: 1px solid var(--border);\n z-index: 10;\n}\n\n#sidebar-header {\n padding: 16px 12px;\n border-bottom: 1px solid var(--border);\n}\n\n.logo {\n display: flex;\n align-items: center;\n gap: 10px;\n}\n.logo-icon {\n color: var(--accent);\n font-size: var(--fs-l);\n}\n.logo-title {\n font-weight: 800;\n font-size: var(--fs-l);\n letter-spacing: -0.5px;\n}\n.logo-sub {\n font-size: var(--fs-s);\n text-transform: uppercase;\n color: var(--text-dim);\n font-weight: 700;\n letter-spacing: 1px;\n margin-top: -2px;\n}\n\n/* Drop zone */\n#drop-zone {\n margin: 12px;\n padding: 20px 12px;\n border: 2px dashed var(--border2);\n border-radius: 12px;\n display: flex;\n flex-direction: column;\n align-items: center;\n text-align: center;\n position: relative;\n}\n#drop-zone.drop-active {\n border-color: var(--accent);\n background: color-mix(in srgb, var(--theme-color) 5%, transparent);\n}\n.drop-icon {\n font-size: var(--is-m);\n color: var(--text-muted);\n margin-bottom: 4px;\n}\n.drop-primary {\n font-size: var(--fs-m);\n font-weight: 600;\n color: var(--text);\n}\n.drop-formats {\n display: flex;\n justify-content: center;\n gap: 6px;\n margin-top: 8px;\n}\n.drop-formats .badge {\n background: var(--surface2);\n color: var(--text-dim);\n font-size: var(--fs-s);\n padding: 2px 6px;\n}\n\n.drop-browse-container {\n margin-top: 12px;\n font-size: var(--fs-s);\n color: var(--text-muted);\n display: flex;\n align-items: center;\n justify-content: center;\n gap: 2px;\n}\n.drop-browse {\n color: var(--accent);\n font-weight: 600;\n cursor: pointer;\n display: inline-flex;\n align-items: center;\n gap: 4px;\n}\n.drop-browse .material-symbols-rounded {\n font-size: var(--fs-l);\n}\n.drop-browse:hover {\n color: var(--accent-hover);\n}\n.drop-separator {\n margin: 0 6px;\n color: var(--border2);\n vertical-align: middle;\n}\n\n/* Track list header */\n#track-list-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 12px 12px 8px;\n}\n.section-label {\n font-size: var(--fs-s);\n font-weight: 700;\n text-transform: uppercase;\n color: var(--text-dim);\n letter-spacing: 0.5px;\n}\n\n.header-actions {\n display: flex;\n gap: 0;\n}\n\n#track-controls,\n#sort-controls {\n padding: 8px 12px;\n display: flex;\n flex-direction: column;\n gap: 8px;\n border-bottom: 1px solid var(--border);\n}\n\n.search-bar {\n display: flex;\n background: var(--bg);\n border: 1px solid var(--border);\n border-radius: 6px;\n padding: 2px 4px;\n align-items: center;\n}\n.search-icon {\n font-size: var(--is-s);\n color: var(--text-dim);\n margin-left: 4px;\n}\n.search-bar input {\n flex: 1;\n background: transparent;\n border: none;\n padding: 4px 6px;\n color: var(--text);\n font-size: var(--fs-m);\n}\n.search-bar input:focus {\n outline: none;\n}\n\n.icon-btn.mini {\n width: 20px;\n height: 20px;\n color: var(--text-dim);\n}\n.icon-btn.mini .material-symbols-rounded {\n font-size: var(--is-s);\n}\n.icon-btn.mini.active {\n color: var(--accent);\n}\n\n.sort-ctrl {\n display: flex;\n align-items: center;\n gap: 8px;\n}\n.sort-ctrl label {\n font-size: var(--fs-s);\n color: var(--text-dim);\n text-transform: uppercase;\n font-weight: 600;\n}\n\n.sort-select-btn {\n display: flex;\n align-items: center;\n gap: 6px;\n background: var(--surface2);\n border: 1px solid var(--border);\n padding: 4px 8px;\n border-radius: 6px;\n flex: 1;\n height: 24px;\n box-sizing: border-box;\n font-size: var(--fs-s);\n color: var(--text-muted);\n}\n.sort-select-btn:hover {\n border-color: var(--accent);\n color: var(--text);\n}\n.sort-select-btn .material-symbols-rounded {\n font-size: var(--is-s);\n}\n.sort-select-btn .arrow {\n font-size: var(--fs-l);\n margin-left: auto;\n opacity: 0.5;\n}\n\n/* Filter panel */\n#filter-panel {\n padding: 12px;\n background: var(--surface2);\n border-bottom: 1px solid var(--border);\n display: flex;\n flex-direction: column;\n gap: 12px;\n}\n#filter-panel.hidden {\n display: none;\n}\n\n.filter-group {\n display: flex;\n flex-direction: column;\n gap: 4px;\n}\n.filter-group label {\n font-size: var(--fs-s);\n font-weight: 600;\n text-transform: uppercase;\n color: var(--text-dim);\n display: flex;\n align-items: center;\n gap: 6px;\n}\n.filter-group label .material-symbols-rounded {\n font-size: var(--is-s);\n opacity: 0.7;\n}\n\n.filter-metrics-grid {\n display: flex;\n flex-wrap: wrap;\n gap: 6px;\n margin-top: 4px;\n}\n\n.mini-pill {\n width: 28px;\n height: 28px;\n display: flex;\n align-items: center;\n justify-content: center;\n background: var(--surface);\n border: 1px solid var(--border);\n border-radius: 6px;\n color: var(--text-dim);\n cursor: pointer;\n}\n.mini-pill:hover {\n border-color: var(--text-muted);\n color: var(--text-muted);\n}\n.mini-pill.active {\n background: var(--accent);\n border-color: var(--accent);\n color: white;\n}\n.mini-pill .material-symbols-rounded {\n font-size: var(--is-s);\n}\n\n.filter-row {\n display: flex;\n align-items: center;\n gap: 6px;\n}\n.filter-row input {\n flex: 1;\n background: var(--bg);\n border: 1px solid var(--border);\n border-radius: 4px;\n padding: 4px 6px;\n color: var(--text);\n font-size: var(--fs-s);\n}\n.filter-row span {\n font-size: var(--fs-s);\n color: var(--text-dim);\n}\n#filter-panel input:focus {\n border-color: var(--accent);\n outline: none;\n}\n\n.filter-footer {\n display: flex;\n justify-content: flex-end;\n margin-top: 4px;\n}\n\n/* Track list */\n#track-list {\n flex: 1;\n overflow-y: auto;\n padding: 4px 0;\n scrollbar-width: none;\n}\n#track-list::-webkit-scrollbar {\n width: 0;\n height: 0;\n display: none;\n}\n#track-list::-webkit-scrollbar-thumb {\n background: var(--border2);\n border-radius: 2px;\n}\n\n#track-list-empty {\n padding: 40px 20px;\n text-align: center;\n color: var(--text-dim);\n font-size: var(--fs-m);\n}\n\n.track-item {\n display: flex;\n align-items: center;\n padding: 6px 8px;\n gap: 0;\n cursor: pointer;\n border-left: 3px solid transparent;\n}\n.track-item:hover {\n background: rgba(255, 255, 255, 0.03);\n}\n.track-item.selected {\n background: color-mix(in srgb, var(--theme-color) 8%, transparent);\n border-left-color: var(--accent);\n}\n\n.track-color {\n width: 3px;\n border-radius: 1px;\n flex-shrink: 0;\n margin-right: 8px;\n align-self: stretch;\n}\n.track-info {\n flex: 1;\n min-width: 0;\n margin-right: 2px;\n}\n.track-name {\n font-size: var(--fs-m);\n font-weight: 600;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n margin-bottom: 1px;\n}\n.track-meta {\n font-size: var(--fs-s);\n color: var(--text-dim);\n display: flex;\n gap: 6px;\n align-items: center;\n}\n\n.track-item .icon-btn.mini {\n width: 22px;\n height: 22px;\n padding: 0;\n margin: 0;\n}\n\n.badge {\n font-size: var(--fs-s);\n background: var(--surface2);\n padding: 1px 4px;\n border-radius: 3px;\n font-weight: 700;\n color: var(--text-muted);\n}\n\n.track-tags {\n display: flex;\n flex-wrap: wrap;\n gap: 4px;\n margin-top: 6px;\n overflow: hidden;\n max-height: 32px;\n}\n\n.track-tag {\n font-size: var(--fs-s);\n font-weight: 700;\n color: var(--text-dim);\n background: rgba(255, 255, 255, 0.03);\n border: 1px solid var(--border);\n padding: 1px 5px;\n border-radius: 3px;\n white-space: nowrap;\n}\n\n.track-item.selected .track-tag {\n border-color: rgba(255, 255, 255, 0.1);\n color: var(--text-muted);\n}\n\n#sidebar-resizer {\n width: 4px;\n cursor: col-resize;\n background: transparent;\n flex-shrink: 0;\n z-index: 1000;\n margin-left: -2px;\n margin-right: -2px;\n user-select: none;\n}\n#sidebar-resizer:hover,\n#sidebar-resizer.dragging {\n background: var(--handle-color);\n}\n\nbody.dragging-sidebar {\n user-select: none;\n cursor: col-resize !important;\n}\n\n/* \u2500\u2500\u2500 Main \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n#main {\n flex: 1;\n display: flex;\n flex-direction: column;\n min-width: 0;\n}\n\n/* Map */\n#map-container {\n flex: 1;\n position: relative;\n min-height: 100px;\n}\n#map-container.editing-mode {\n border: 4px solid var(--accent);\n box-shadow: 0 0 15px var(--accent);\n z-index: 1000; /* Ensure glow is visible */\n}\n#map {\n width: 100%;\n height: 100%;\n background: var(--bg);\n}\n\n#map-controls {\n position: absolute;\n top: 12px;\n left: 12px;\n z-index: 1000;\n display: flex;\n align-items: center;\n gap: 8px;\n}\n#basemap-switcher {\n display: flex;\n background: var(--surface);\n border: 1px solid var(--border);\n border-radius: 8px;\n padding: 2px;\n height: 24px;\n box-sizing: border-box;\n box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);\n align-items: center;\n gap: 0;\n}\n\n#selected-extra-map {\n font-size: var(--fs-s);\n font-weight: 700;\n color: var(--accent);\n padding: 0 6px;\n max-width: 120px;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n cursor: pointer;\n}\n#selected-extra-map.hidden {\n display: none;\n}\n\n#btn-extra-layers {\n border: none !important;\n border-left: 1px solid var(--border) !important;\n border-radius: 0 !important;\n margin-left: 0 !important;\n padding-left: 6px !important;\n padding-right: 0 !important;\n width: auto !important;\n height: 100% !important;\n background: transparent !important;\n}\n\n#extra-layers-menu {\n position: absolute;\n top: 40px;\n right: 0;\n background: var(--surface);\n border: 1px solid var(--border);\n border-radius: 8px;\n box-shadow: 0 4px 20px rgba(0, 0, 0, 0.5);\n max-height: 300px;\n overflow-y: auto;\n width: 200px;\n z-index: 1000;\n padding: 4px;\n}\n#extra-layers-menu.hidden {\n display: none;\n}\n.extra-layers-header {\n font-size: var(--fs-s);\n font-weight: 700;\n color: var(--text-dim);\n text-transform: uppercase;\n padding: 6px 8px;\n border-bottom: 1px solid var(--border);\n margin-bottom: 4px;\n}\n.extra-layers-search {\n padding: 4px 8px;\n border-bottom: 1px solid var(--border);\n margin-bottom: 4px;\n}\n#extra-layers-search-input {\n width: 100%;\n background: var(--surface2);\n border: 1px solid var(--border);\n border-radius: 4px;\n color: var(--text);\n font-size: var(--fs-s);\n padding: 4px 8px;\n outline: none;\n}\n#extra-layers-search-input:focus {\n border-color: var(--accent);\n}\n.extra-layers-license {\n font-size: var(--fs-s);\n color: var(--text-dim);\n padding: 4px 8px;\n border-bottom: 1px solid var(--border);\n margin-bottom: 4px;\n}\n.extra-layers-license a {\n color: var(--accent);\n text-decoration: none;\n}\n.extra-layers-license a:hover {\n text-decoration: underline;\n}\n.extra-layer-row {\n margin-bottom: 2px;\n}\n.extra-layer-item {\n display: block;\n width: 100%;\n text-align: left;\n background: transparent;\n border: none;\n color: var(--text-muted);\n padding: 4px 10px 2px 10px;\n font-size: var(--fs-s);\n font-weight: 600;\n border-radius: 4px;\n cursor: pointer;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n.extra-layer-item:hover {\n background: var(--surface2);\n color: var(--text);\n}\n.extra-layer-attribution {\n font-size: var(--fs-s);\n color: var(--text-dim);\n padding: 0 10px 4px 10px;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n#map-loader {\n background: var(--surface);\n border: 1px solid var(--border);\n border-radius: 8px;\n padding: 0 10px;\n height: 24px;\n box-sizing: border-box;\n display: flex;\n align-items: center;\n gap: 8px;\n font-size: var(--fs-s);\n font-weight: 600;\n color: var(--text-muted);\n box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);\n}\n.spinner-sm {\n width: 12px;\n height: 12px;\n border: 2px solid var(--surface2);\n border-top-color: var(--accent);\n border-radius: 50%;\n}\n.bm-btn {\n background: transparent;\n border: none;\n color: var(--text-muted);\n padding: 2px 6px;\n font-size: var(--fs-s);\n font-weight: 600;\n border-radius: 6px;\n}\n.bm-btn:hover {\n color: var(--text);\n}\n.bm-btn.active {\n background: var(--surface2);\n color: var(--accent);\n}\n\n/* Map Cursor Dot */\n.map-cursor-dot {\n pointer-events: none;\n}\n.dot-inner {\n width: 12px;\n height: 12px;\n background: #fff;\n border: 2px solid #000;\n border-radius: 50%;\n box-shadow: 0 0 0 2px rgba(255, 255, 255, 0.5);\n}\n\n/* Map cursor info popup */\n#map-cursor-info {\n position: absolute;\n top: 12px;\n right: 12px;\n z-index: 1000;\n background: var(--surface);\n border: 1px solid var(--border);\n border-radius: 8px;\n padding: 8px 12px;\n font-size: var(--fs-s);\n color: var(--text);\n box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);\n display: none;\n pointer-events: none;\n}\n\n/* Resize handle */\n#resize-handle {\n height: 6px;\n background: var(--bg);\n border-top: 1px solid var(--border);\n border-bottom: 1px solid var(--border);\n cursor: ns-resize;\n display: flex;\n align-items: center;\n justify-content: center;\n flex-shrink: 0;\n z-index: 100;\n}\n#resize-handle:hover {\n background: var(--handle-color);\n}\n.handle-grip {\n width: 32px;\n height: 2px;\n background: var(--border2);\n border-radius: 1px;\n}\n\n/* \u2500\u2500\u2500 Chart Panel \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n#chart-panel {\n display: flex;\n flex-direction: column;\n background: var(--surface);\n overflow: hidden;\n flex: 1;\n width: 100%;\n height: 100%;\n min-height: 0;\n}\n\n#tab-nav {\n display: flex;\n padding: 0 12px;\n border-bottom: 1px solid var(--border);\n gap: 4px;\n}\n\n.tab-btn {\n background: transparent;\n border: none;\n border-bottom: 2px solid transparent;\n color: var(--text-dim);\n padding: 10px 12px;\n font-size: var(--fs-m);\n font-weight: 700;\n display: flex;\n align-items: center;\n gap: 6px;\n}\n.tab-btn:hover {\n color: var(--text-muted);\n}\n.tab-btn.active {\n color: var(--accent);\n border-bottom-color: var(--accent);\n}\n.tab-btn .material-symbols-rounded {\n font-size: var(--is-s);\n}\n\n.tab-content {\n display: none;\n flex: 1;\n min-height: 0;\n}\n.tab-content.active {\n display: flex;\n flex-direction: column;\n overflow-y: auto;\n scrollbar-width: none;\n}\n\n/* Custom scrollbar for active tab content */\n.tab-content.active::-webkit-scrollbar {\n width: 0;\n height: 0;\n}\n.tab-content.active::-webkit-scrollbar-track {\n background: transparent;\n}\n.tab-content.active::-webkit-scrollbar-thumb {\n background: var(--border);\n border-radius: 3px;\n}\n.tab-content.active::-webkit-scrollbar-thumb:hover {\n background: var(--text-dim);\n}\n\n#chart-toolbar {\n display: flex;\n align-items: center;\n padding: 6px 12px;\n gap: 12px;\n border-bottom: 1px solid var(--border);\n background: var(--bg);\n min-height: 40px;\n position: sticky;\n top: 0;\n z-index: 100;\n}\n\n.icon-btn {\n background: transparent;\n border: 1px solid transparent;\n color: var(--text-muted);\n width: 24px;\n height: 24px;\n border-radius: 6px;\n display: flex;\n align-items: center;\n justify-content: center;\n}\n.icon-btn.hidden {\n display: none;\n}\n.icon-btn:hover {\n background: var(--surface2);\n color: var(--text);\n}\n.icon-btn.active {\n background: var(--surface2);\n color: var(--accent);\n}\n.icon-btn.danger:hover {\n background: rgba(239, 68, 68, 0.12);\n color: var(--danger);\n}\n.icon-btn .material-symbols-rounded {\n font-size: var(--fs-l);\n}\n\n#btn-metric-menu {\n flex: 0 0 auto;\n}\n\n#metric-pills {\n display: flex;\n gap: 6px;\n flex: 1;\n overflow: hidden;\n}\n.metric-pill:not(.active) {\n display: none;\n}\n.metric-pill.overflow-hidden {\n display: none !important;\n}\n#pill-overflow {\n color: var(--text-muted);\n font-weight: bold;\n padding: 0 4px;\n align-self: flex-end;\n}\n.metric-pill {\n height: 24px;\n padding: 0 8px;\n border-radius: 12px;\n border: 1px solid var(--border);\n background: var(--surface2);\n color: var(--text-muted);\n font-size: var(--fs-s);\n font-weight: 500;\n display: flex;\n align-items: center;\n gap: 4px;\n white-space: nowrap;\n}\n.metric-pill .material-symbols-rounded {\n font-size: var(--fs-m);\n}\n.metric-pill.active {\n background: color-mix(in srgb, var(--pill-color) 15%, var(--surface2));\n border-color: var(--pill-color);\n color: var(--pill-color);\n}\n.metric-pill.disabled {\n opacity: 0.3;\n filter: grayscale(1);\n cursor: default;\n}\n\n#chart-options {\n display: flex;\n align-items: center;\n gap: 0;\n margin-left: auto;\n}\n\n.seg-ctrl {\n display: flex;\n background: var(--surface2);\n border: 1px solid var(--border);\n border-radius: 6px;\n padding: 2px;\n height: 24px;\n box-sizing: border-box;\n}\n.seg-btn {\n background: transparent;\n border: none;\n color: var(--text-dim);\n padding: 0 8px;\n font-size: var(--fs-s);\n font-weight: 700;\n border-radius: 4px;\n display: flex;\n align-items: center;\n gap: 6px;\n height: 100%;\n}\n.seg-btn .material-symbols-rounded {\n font-size: var(--fs-m);\n}\n.seg-btn.active {\n background: var(--bg);\n color: var(--text);\n box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2);\n}\n\n/* Stats Summary bar */\n#chart-stats {\n padding: 4px 14px;\n border-bottom: 1px solid var(--border);\n background: var(--surface2);\n}\n#chart-stats.hidden {\n display: none;\n}\n#chart-stats-wrap {\n display: flex;\n flex-direction: column;\n gap: 2px;\n}\n.stats-row {\n display: flex;\n align-items: center;\n gap: 16px;\n}\n.stats-row-label {\n width: 45px;\n font-size: var(--fs-s);\n font-weight: 700;\n text-transform: uppercase;\n letter-spacing: 0.8px;\n color: var(--text-muted);\n white-space: nowrap;\n}\n\n.chart-row-stats-container {\n display: flex;\n flex: 1;\n align-items: center;\n justify-content: space-between;\n}\n\n.chart-row-right-actions {\n width: 130px; /* HIST_W */\n display: none;\n align-items: center;\n justify-content: flex-end;\n flex-shrink: 0;\n}\n.with-histogram .chart-row-right-actions {\n display: flex;\n}\n.chart-stats-total,\n.chart-stats-selection {\n display: flex;\n gap: 10px;\n font-size: var(--fs-s);\n color: var(--text-dim);\n align-items: center;\n white-space: nowrap;\n font-weight: 500;\n}\n.chart-stats-selection {\n margin-left: auto;\n position: relative;\n}\n.chart-stats-selection::after {\n content: '';\n position: absolute;\n bottom: -6px;\n left: 0;\n right: 0;\n height: 2px;\n background-color: var(--sel-color);\n pointer-events: none;\n}\n.all-chip {\n background: var(--surface2);\n color: var(--text-muted);\n font-weight: 500;\n padding: 2px;\n border-radius: 4px;\n font-size: var(--fs-m);\n margin-right: 4px;\n cursor: pointer;\n border: 1px solid var(--border);\n display: flex;\n align-items: center;\n justify-content: center;\n}\n.all-chip:hover {\n background: var(--border);\n color: var(--text);\n}\n.sel-tag {\n background: var(--surface2);\n color: var(--text-muted);\n font-weight: 500;\n padding: 2px;\n border-radius: 4px;\n font-size: var(--fs-m);\n margin-right: 4px;\n display: flex;\n align-items: center;\n justify-content: center;\n border: 1px solid var(--border);\n}\n\n.mm-item {\n display: flex;\n align-items: center;\n gap: 2px;\n white-space: nowrap;\n}\n.mm-icon {\n font-size: var(--fs-m);\n color: var(--text-dim);\n}\n\n/* \u2500\u2500 uPlot overrides \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n.uplot {\n color: var(--text-muted) !important;\n}\n.u-title {\n display: none;\n}\n.u-legend {\n display: none;\n}\n\n/* Selection drag background \u2014 colour matches the chart's metric */\n.chart-row .u-select {\n background: color-mix(in srgb, var(--chart-color, #3b82f6) 12%, transparent) !important;\n border: 1px solid color-mix(in srgb, var(--chart-color, #3b82f6) 35%, transparent) !important;\n}\n\n/* Make the cursor crosshair dashed */\n.u-cursor-x {\n border-left: 1px dashed rgba(255, 255, 255, 0.55) !important;\n}\n.u-cursor-y {\n display: none !important;\n}\n\n/* \u2500\u2500 Selection fill \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n.sel-fill {\n position: absolute;\n top: 0;\n background: color-mix(in srgb, var(--theme-color) 8%, transparent);\n border-left: 1px solid color-mix(in srgb, var(--theme-color) 30%, transparent);\n border-right: 1px solid rgba(148, 163, 184, 0.4);\n pointer-events: none;\n}\n\n/* \u2500\u2500 Selection handles \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n.sel-handle {\n position: absolute;\n top: 50%;\n transform: translate(-50%, -50%);\n width: 8px;\n height: 24px;\n background: rgba(255, 255, 255, 0.85);\n border: 1px solid rgba(0, 0, 0, 0.3);\n border-radius: 4px;\n cursor: ew-resize;\n pointer-events: auto;\n display: flex;\n align-items: center;\n justify-content: center;\n z-index: 10;\n}\n.sel-handle::after {\n content: '';\n width: 2px;\n height: 10px;\n border-left: 1px solid rgba(0, 0, 0, 0.4);\n border-right: 1px solid rgba(0, 0, 0, 0.4);\n}\n.sel-handle:hover {\n background: #fff;\n transform: translate(-50%, -50%) scale(1.1);\n}\n\n.sel-graph-marker {\n position: absolute;\n transform: translate(-50%, -50%);\n pointer-events: none;\n z-index: 11;\n background: none !important;\n}\n.sel-graph-marker .material-symbols-rounded {\n}\n\n/* \u2500\u2500 Per-chart selection lines \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n.sel-line {\n position: absolute;\n top: 0;\n height: 100%;\n width: 0;\n border-left-width: 1.5px;\n border-left-style: solid;\n pointer-events: none;\n}\n\n/* Labels on the selection lines */\n.sel-line-label {\n position: absolute;\n top: 4px;\n font-size: var(--fs-s);\n font-weight: 700;\n background: rgba(14, 14, 16, 0.92);\n color: #fff;\n padding: 2px 5px;\n border-radius: 4px;\n pointer-events: none;\n white-space: nowrap;\n z-index: 10;\n border: 1px solid var(--border);\n}\n\n/* \u2500\u2500 Charts view elements \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n#charts-container {\n flex: 1;\n overflow-y: auto;\n overflow-x: hidden;\n padding: 6px 0 4px;\n scrollbar-width: none;\n}\n#charts-container::-webkit-scrollbar {\n width: 0;\n height: 0;\n}\n#charts-container::-webkit-scrollbar-thumb {\n background: var(--border2);\n border-radius: 2px;\n}\n\n/* Fixed X Axis Labels */\n.fixed-x-label {\n position: absolute;\n bottom: 4px;\n font-size: var(--fs-s);\n font-family: system-ui, sans-serif;\n color: #555564;\n z-index: 10;\n pointer-events: none;\n}\n.fixed-x-max {\n right: 0;\n text-align: right;\n}\n\n/* Histogram column (inline, per chart row) */\n.chart-row-body {\n display: flex;\n flex-direction: row;\n padding: 0 14px;\n min-width: 0;\n overflow: hidden;\n}\n.chart-row-body > div:first-child {\n flex: 1;\n min-width: 0;\n}\n.hist-col {\n width: 130px;\n flex-shrink: 0;\n border-left: 1px solid var(--border);\n margin-left: 20px;\n margin-right: 0;\n display: none;\n align-items: center;\n justify-content: center;\n cursor: crosshair;\n}\n.hist-col.visible {\n display: flex;\n}\n.hist-canvas {\n display: block;\n}\n\n/* Histogram connector line */\n#hist-line {\n position: fixed;\n height: 0;\n border-top: 1px dashed rgba(255, 255, 255, 0.25);\n pointer-events: none;\n display: none;\n z-index: 10000;\n}\n\n/* Histogram tooltip */\n#hist-tooltip {\n position: fixed;\n background: var(--surface);\n border: 1px solid var(--border2);\n border-radius: 5px;\n padding: 8px 10px;\n pointer-events: none;\n display: none;\n z-index: 10001;\n box-shadow: 0 4px 15px rgba(0, 0, 0, 0.5);\n font-size: var(--fs-s);\n}\n.hist-tt-header {\n font-weight: 800;\n margin-bottom: 6px;\n padding-left: 8px;\n border-left: 3px solid var(--accent);\n color: var(--text);\n}\n.hist-tt-grid {\n display: grid;\n grid-template-columns: 1fr auto auto;\n gap: 10px;\n align-items: baseline;\n}\n.hist-tt-label {\n font-size: var(--fs-s);\n color: var(--text-muted);\n}\n.hist-tt-value {\n font-size: var(--fs-s);\n color: var(--text);\n text-align: right;\n font-variant-numeric: tabular-nums;\n}\n.hist-tt-pct {\n font-size: var(--fs-s);\n color: var(--text-dim);\n text-align: right;\n min-width: 36px;\n font-variant-numeric: tabular-nums;\n}\n.hist-tt-grid.sel-row {\n margin-top: 2px;\n opacity: 0.9;\n}\n.hist-tt-label.sel {\n color: var(--accent);\n font-weight: 700;\n}\n\n.chart-row {\n border-bottom: 1px solid var(--border);\n}\n.chart-row:last-child {\n border-bottom: none;\n}\n\n/* \u2500\u2500 Chart row header \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n.chart-row-header {\n display: flex;\n align-items: center;\n gap: 12px;\n padding: 8px 14px 4px 14px;\n min-height: 28px;\n}\n\n.progress-chart-row {\n display: flex;\n flex-direction: column;\n}\n\n.progress-chart-row .chart-row-header {\n height: 36px;\n box-sizing: border-box;\n padding-top: 0;\n padding-bottom: 0;\n}\n\n.progress-chart-row .chart-row-actions {\n margin-left: auto;\n}\n\n.progress-chart-row .chart-row-body {\n flex: 1;\n padding: 0;\n}\n\n.chart-row-label-group {\n display: flex;\n align-items: center;\n gap: 8px;\n flex-shrink: 0;\n width: 140px; /* Fixed width for alignment */\n}\n\n.chart-row-actions {\n display: flex;\n align-items: center;\n gap: 0;\n}\n\n.chart-row-icon {\n font-size: var(--is-s);\n color: var(--chart-color);\n}\n.chart-row-label {\n font-size: var(--fs-m);\n font-weight: 700;\n text-transform: uppercase;\n letter-spacing: 0.8px;\n color: var(--text-muted);\n white-space: nowrap;\n}\n\n/* Track Picker Popup */\n.track-picker-popup {\n padding: 8px;\n background: var(--surface);\n color: var(--text);\n border-radius: 8px;\n min-width: 160px;\n}\n.picker-header {\n font-size: var(--fs-s);\n text-transform: uppercase;\n font-weight: 700;\n color: var(--text-dim);\n margin-bottom: 8px;\n padding-bottom: 4px;\n border-bottom: 1px solid var(--border);\n}\n.picker-list {\n display: flex;\n flex-direction: column;\n gap: 2px;\n}\n.picker-item {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 6px 8px;\n border-radius: 6px;\n cursor: pointer;\n}\n.picker-item:hover {\n background: var(--surface2);\n}\n.picker-color {\n width: 3px;\n height: 14px;\n border-radius: 1px;\n}\n.picker-info {\n display: flex;\n flex-direction: column;\n min-width: 0;\n}\n.picker-name {\n font-size: var(--fs-s);\n font-weight: 600;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n max-width: 180px;\n}\n/* Custom Modal / Dialog */\n.modal-backdrop {\n position: fixed;\n inset: 0;\n background: rgba(0, 0, 0, 0.75);\n backdrop-filter: blur(4px);\n display: flex;\n align-items: center;\n justify-content: center;\n z-index: 20000;\n}\n.modal-content {\n background: var(--surface);\n border: 1px solid var(--border);\n border-radius: 12px;\n padding: 24px;\n width: 100%;\n max-width: 320px;\n box-shadow: 0 20px 40px rgba(0, 0, 0, 0.5);\n display: flex;\n flex-direction: column;\n gap: 16px;\n}\n.modal-title {\n font-size: var(--fs-l);\n font-weight: 800;\n color: var(--text);\n}\n.modal-body {\n font-size: var(--fs-m);\n color: var(--text-muted);\n line-height: 1.5;\n}\n.modal-input-wrap {\n display: flex;\n flex-direction: column;\n gap: 8px;\n}\n.modal-input-label {\n font-size: var(--fs-s);\n font-weight: 700;\n text-transform: uppercase;\n color: var(--text-dim);\n}\n.modal-input {\n background: var(--bg);\n border: 1px solid var(--border2);\n border-radius: 6px;\n padding: 10px;\n color: var(--text);\n font-size: var(--fs-l);\n font-weight: 800;\n text-align: center;\n letter-spacing: 4px;\n}\n.modal-input:focus {\n outline: none;\n border-color: var(--accent);\n}\n.modal-actions {\n display: flex;\n gap: 12px;\n margin-top: 8px;\n}\n.modal-btn {\n flex: 1;\n padding: 10px;\n border-radius: 6px;\n font-size: var(--fs-m);\n font-weight: 700;\n border: 1px solid transparent;\n}\n.modal-btn.cancel {\n background: var(--surface2);\n color: var(--text);\n border-color: var(--border);\n}\n.modal-btn.cancel:hover {\n background: var(--border);\n}\n.modal-btn.confirm {\n background: var(--danger);\n color: #fff;\n}\n.modal-btn.confirm:disabled {\n opacity: 0.3;\n cursor: not-allowed;\n filter: grayscale(1);\n}\n.modal-btn.confirm:not(:disabled):hover {\n filter: brightness(1.1);\n}\n\n.picker-meta {\n font-size: var(--fs-s);\n color: var(--text-dim);\n font-weight: 700;\n}\n\n/* Leaflet Popup Overrides */\n.leaflet-popup-content-wrapper,\n.leaflet-popup-tip {\n background: var(--surface) !important;\n color: var(--text) !important;\n border: 1px solid var(--border) !important;\n box-shadow: 0 4px 12px rgba(0, 0, 0, 0.4) !important;\n}\n.leaflet-popup-content {\n margin: 0 !important;\n}\n.leaflet-popup-tip {\n border: none !important;\n}\n.map-color-btn {\n margin-left: 4px;\n}\n.map-color-btn .material-symbols-rounded {\n font-size: var(--is-s);\n}\n.map-color-btn.active {\n color: var(--chart-color, var(--accent));\n}\n\n.smooth-btn.active {\n color: var(--accent);\n}\n.cur-y-val {\n position: absolute;\n left: 0;\n top: 0;\n pointer-events: none;\n font-size: var(--fs-s);\n font-weight: 700;\n background: rgba(14, 14, 16, 0.92);\n padding: 2px 6px;\n border-radius: 4px;\n border: 1px solid rgba(255, 255, 255, 0.1);\n z-index: 2000;\n white-space: nowrap;\n}\n.cur-y-val.pin {\n z-index: 1000;\n opacity: 0.7;\n}\n\n/* \u2500\u2500\u2500 Details View \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n#details-view {\n width: 100%;\n padding: 24px;\n max-width: 900px;\n margin: 0 auto;\n}\n\n#details-header {\n margin-bottom: 32px;\n}\n.details-title {\n font-size: var(--fs-l);\n font-weight: 800;\n margin-bottom: 4px;\n}\n.details-subtitle {\n font-size: var(--fs-m);\n color: var(--text-muted);\n}\n\n.details-grid {\n display: grid;\n grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));\n gap: 16px;\n}\n.details-card {\n background: var(--surface2);\n border: 1px solid var(--border);\n padding: 16px;\n border-radius: 12px;\n display: flex;\n flex-direction: column;\n gap: 4px;\n}\n.details-card .material-symbols-rounded {\n color: var(--accent);\n margin-bottom: 4px;\n font-size: var(--is-m);\n}\n.details-card-label {\n font-size: var(--fs-s);\n font-weight: 500;\n text-transform: uppercase;\n color: var(--text-dim);\n letter-spacing: 0.5px;\n}\n.details-card-value {\n font-size: var(--fs-l);\n font-weight: 500;\n}\n\n.empty-state {\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n height: 100%;\n color: var(--text-dim);\n gap: 12px;\n padding: 40px 20px;\n}\n.empty-icon {\n font-size: var(--fs-l);\n opacity: 0.2;\n}\n.empty-text {\n font-size: var(--fs-m);\n font-weight: 500;\n}\n\n#chart-empty {\n height: 100%;\n}\n#details-empty {\n margin-top: 40px;\n}\n\n/* \u2500\u2500 Toast notifications \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n#toast-container {\n position: fixed;\n bottom: 20px;\n left: 50%;\n transform: translateX(-50%);\n display: flex;\n flex-direction: column;\n gap: 8px;\n z-index: 9999;\n}\n.toast {\n background: var(--surface2);\n color: var(--text);\n padding: 10px 20px;\n border-radius: 8px;\n font-size: var(--fs-m);\n font-weight: 600;\n box-shadow: 0 8px 24px rgba(0, 0, 0, 0.4);\n border: 1px solid var(--border2);\n}\n.toast.error {\n border-color: var(--danger);\n}\n.toast.out {\n}\n\n/* \u2500\u2500 Sort menu popup \u2500\u2500 */\n.sort-menu-popup,\n.metric-menu-popup {\n position: fixed;\n z-index: 10000;\n background: var(--surface2);\n border: 1px solid var(--border);\n border-radius: 8px;\n padding: 6px;\n min-width: 180px;\n box-shadow: 0 8px 24px rgba(0, 0, 0, 0.5);\n}\n\n.menu-item {\n display: flex;\n align-items: center;\n gap: 10px;\n padding: 8px 10px;\n border-radius: 6px;\n cursor: pointer;\n color: var(--text-muted);\n}\n.menu-item:hover {\n background: var(--surface);\n color: var(--text);\n}\n.menu-item.active {\n color: var(--text);\n font-weight: 600;\n}\n.menu-item .material-symbols-rounded {\n font-size: var(--is-s);\n}\n.menu-item .item-label {\n flex: 1;\n font-size: var(--fs-m);\n}\n.menu-item .arrow,\n.menu-item .check {\n font-size: var(--is-s);\n color: var(--text-dim);\n}\n\n/* \u2500\u2500\u2500 Insights View \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n#insights-view {\n width: 100%;\n padding: 24px;\n max-width: 1000px;\n margin: 0 auto;\n}\n\n.insights-grid {\n column-count: 2;\n column-gap: 24px;\n}\n\n@media (max-width: 900px) {\n .insights-grid {\n column-count: 1;\n }\n}\n\n.insight-card {\n display: inline-flex;\n width: 100%;\n break-inside: avoid;\n margin-bottom: 24px;\n background: var(--surface2);\n border: 1px solid var(--border);\n padding: 20px;\n border-radius: 12px;\n flex-direction: column;\n gap: 16px;\n}\n\n.insight-title {\n font-size: var(--fs-m);\n font-weight: 700;\n text-transform: uppercase;\n letter-spacing: 0.8px;\n display: flex;\n align-items: center;\n gap: 8px;\n color: var(--text-muted);\n}\n\n.insight-title .material-symbols-rounded {\n font-size: var(--is-s);\n}\n\n.insight-chart-container {\n width: 100%;\n height: 250px;\n}\n\n.power-curve-table {\n width: 100%;\n border-collapse: collapse;\n font-size: var(--fs-m);\n margin-top: 0;\n}\n\n.power-curve-table th {\n text-align: left;\n color: var(--text-dim);\n padding: 4px 8px;\n font-weight: 600;\n border-bottom: 1px solid var(--border);\n}\n\n.power-curve-table td {\n padding: 6px 8px;\n border-bottom: 1px solid var(--border);\n}\n\n.power-curve-table tr:last-child td {\n border-bottom: none;\n}\n\n.progress-details summary {\n cursor: pointer;\n font-size: var(--fs-m);\n color: var(--text-dim);\n display: flex;\n align-items: center;\n gap: 4px;\n list-style: none;\n}\n\n.progress-details summary::-webkit-details-marker {\n display: none;\n}\n\n.progress-details[open] summary .expand-icon {\n transform: rotate(90deg);\n}\n\n.progress-details summary .material-symbols-rounded {\n font-size: var(--fs-l);\n}\n\n.progress-details summary .expand-icon {\n}\n\n.power-curve-val {\n font-weight: 700;\n color: var(--text);\n text-align: right;\n}\n\n.evolution-select-btn {\n background: none;\n border: none;\n cursor: pointer;\n color: var(--chart-color);\n padding: 0;\n font-size: var(--fs-l);\n vertical-align: middle;\n margin-left: 4px;\n}\n\n.hr-settings-popup {\n display: flex;\n flex-direction: column;\n}\n\n.hr-input-row {\n display: flex;\n justify-content: space-between;\n align-items: center;\n font-size: var(--fs-m);\n color: var(--text-muted);\n font-weight: 600;\n}\n\n.hr-zone-input {\n background: var(--surface);\n border: 1px solid var(--border);\n border-radius: 4px;\n color: var(--text);\n width: 60px;\n padding: 4px 8px;\n font-size: var(--fs-m);\n text-align: right;\n outline: none;\n}\n\n.hr-zone-input:focus {\n border-color: var(--accent);\n}\n\n.save-btn {\n background: var(--accent);\n color: #fff;\n border: none;\n border-radius: 6px;\n padding: 8px;\n font-size: 12px;\n font-weight: 700;\n cursor: pointer;\n}\n\n.save-btn:hover {\n background: var(--accent-hover);\n}\n\n.menu-item.active .arrow,\n.menu-item.active .check {\n color: var(--accent);\n}\n\n/* \u2500\u2500 Evolution View \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n.evolution-toolbar {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 6px 12px;\n border-bottom: 1px solid var(--border);\n background: var(--bg);\n min-height: 40px;\n}\n\n.evolution-toolbar-left,\n.evolution-toolbar-right {\n display: flex;\n align-items: center;\n gap: 12px;\n}\n\n.evolution-toolbar .metric-pills {\n display: flex;\n gap: 6px;\n}\n\n.evolution-toolbar .metric-pill {\n cursor: pointer;\n}\n\n.evolution-container {\n display: flex;\n flex-direction: column;\n gap: 0px;\n padding: 6px 0 4px;\n}\n\n.evolution-card-group {\n display: flex;\n flex-direction: column;\n gap: 10px;\n width: 100%;\n}\n\n.evolution-legend {\n display: flex;\n gap: 16px;\n font-size: 12px;\n color: var(--text-dim);\n margin-top: 0px;\n margin-bottom: 0px;\n justify-content: center;\n}\n\n.evolution-legend-item {\n display: flex;\n align-items: center;\n gap: 6px;\n}\n\n.evolution-legend-line {\n width: 16px;\n height: 0;\n display: inline-block;\n}\n\n.evolution-legend-line.solid {\n border-top: 2px solid var(--chart-color);\n}\n\n.evolution-legend-line.dashed {\n border-top: 2px dashed #888888;\n}\n\n.evolution-tooltip-title {\n font-weight: bold;\n margin-bottom: 4px;\n}\n\n.evolution-tooltip-row {\n display: flex;\n justify-content: space-between;\n gap: 15px;\n}\n\n.evolution-tooltip-label {\n color: #8a8a93;\n}\n\n.evolution-tooltip-value {\n font-weight: bold;\n}\n\n.evolution-empty-text {\n color: var(--text-dim);\n font-size: 12px;\n text-align: center;\n margin-top: 20px;\n}\n\n.evolution-chart-container {\n height: 250px;\n flex: 1;\n}\n\n.distance-progress-card .card-content,\n.elevation-progress-card .card-content {\n display: flex;\n gap: 20px;\n}\n\n.chart-scroll-container {\n flex: 1;\n overflow-x: auto;\n position: relative;\n background: var(--bg-dim);\n border-radius: 8px;\n padding: 10px;\n scrollbar-width: none;\n}\n.chart-scroll-container::-webkit-scrollbar {\n width: 0;\n height: 0;\n display: none;\n}\n\n.accumulated-table-container {\n width: 250px;\n background: var(--bg-dim);\n border-radius: 8px;\n padding: 15px;\n}\n\n.accumulated-table {\n width: 100%;\n border-collapse: collapse;\n font-size: var(--fs-m);\n}\n\n.accumulated-table th,\n.accumulated-table td {\n padding: 4px 8px;\n text-align: left;\n border-bottom: 1px solid var(--border-color);\n}\n\n.accumulated-table th {\n color: var(--text-dim);\n font-weight: 500;\n text-transform: uppercase;\n font-size: var(--fs-m);\n letter-spacing: 0.5px;\n}\n\n.accumulated-table td:last-child {\n text-align: right;\n font-weight: 500;\n font-family: monospace;\n}\n\n.accumulated-table tr:last-child td {\n border-bottom: none;\n}\n\n.chart-scroll-container::-webkit-scrollbar {\n height: 4px;\n}\n\n.chart-scroll-container::-webkit-scrollbar-thumb {\n background: var(--border2);\n border-radius: 2px;\n}\n\n/* uPlot overrides to comply with font size restrictions */\n.u-title {\n font-size: var(--fs-m) !important;\n}\n.u-legend {\n font-size: var(--fs-m) !important;\n}\n\n/* Courses Tab */\n.courses-container {\n padding: 16px;\n display: flex;\n flex-direction: column;\n gap: 16px;\n}\n\n.courses-list {\n display: flex;\n flex-direction: column;\n gap: 8px;\n}\n\n.course-item {\n background: var(--surface2);\n border: 1px solid var(--border);\n border-radius: 8px;\n display: flex;\n flex-direction: column;\n cursor: pointer;\n overflow: hidden;\n}\n\n.course-item:hover {\n background: var(--surface3);\n border-color: var(--border-high);\n}\n\n.course-item-header {\n padding: 6px 12px;\n display: flex;\n align-items: center;\n gap: 12px;\n width: 100%;\n}\n\n.course-name {\n font-weight: 600;\n flex: 1;\n}\n\n.course-meta {\n color: var(--text-muted);\n font-size: var(--fs-s);\n}\n\n.course-actions {\n display: flex;\n gap: 4px;\n}\n\n.course-actions .icon-btn {\n opacity: 0.5;\n}\n\n.course-item:hover .course-actions .icon-btn {\n opacity: 1;\n}\n\n.expand-icon {\n color: var(--text-muted);\n}\n\n.course-tracks {\n padding: 8px 12px;\n border-top: 1px solid var(--border);\n background: var(--surface1);\n}\n\n.course-tracks-title {\n font-size: var(--fs-xs);\n font-weight: 700;\n color: var(--text-muted);\n text-transform: uppercase;\n letter-spacing: 0.5px;\n margin-bottom: 6px;\n}\n\n.course-tracks-table {\n width: 100%;\n border-collapse: collapse;\n font-size: var(--fs-s);\n color: var(--text);\n}\n\n.course-tracks-table th,\n.course-tracks-table td {\n padding: 6px 8px;\n text-align: right;\n border-bottom: 1px solid var(--border-low);\n}\n\n.course-tracks-table th:first-child,\n.course-tracks-table td:first-child,\n.course-tracks-table th:nth-child(2),\n.course-tracks-table td:nth-child(2) {\n text-align: left;\n}\n\n.course-tracks-table td:not(:first-child) {\n white-space: nowrap;\n}\n\n.course-tracks-table th {\n font-weight: 600;\n color: var(--text-muted);\n font-size: var(--fs-xs);\n text-transform: uppercase;\n letter-spacing: 0.5px;\n white-space: nowrap;\n}\n\n.course-tracks-table tr:last-child td {\n border-bottom: none;\n}\n\n.course-tracks-table th:first-child,\n.course-tracks-table td:first-child {\n width: 100%;\n}\n\n.course-track-row:hover {\n background: var(--surface2);\n}\n\n.course-track-row.selected {\n background: color-mix(in srgb, var(--theme-color) 8%, transparent);\n}\n\n.track-name-cell.clickable {\n color: var(--accent);\n cursor: pointer;\n font-weight: 500;\n}\n\n.track-name-cell.clickable:hover {\n text-decoration: underline;\n}\n\n.metric-icon {\n font-size: 16px;\n vertical-align: middle;\n margin-right: 4px;\n}\n\n.metric-icon.duration {\n color: var(--text-muted);\n}\n\n.metric-icon.speed {\n color: #4caf50;\n}\n\n.metric-icon.power {\n color: #ff9800;\n}\n\n.metric-icon.hr {\n color: #f44336;\n}\n\n.course-point-marker {\n display: flex;\n align-items: center;\n justify-content: center;\n}\n"], + "mappings": "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", + "names": [] +} diff --git a/suites-experimental/tools-gps-tracks/dist/test-data/activity_1_alps_stage.gpx.gz b/suites-experimental/tools-gps-tracks/dist/test-data/activity_1_alps_stage.gpx.gz new file mode 100644 index 000000000..cd3428df6 Binary files /dev/null and b/suites-experimental/tools-gps-tracks/dist/test-data/activity_1_alps_stage.gpx.gz differ diff --git a/suites-experimental/tools-gps-tracks/dist/test-data/activity_2_forest_mtb.gpx.gz b/suites-experimental/tools-gps-tracks/dist/test-data/activity_2_forest_mtb.gpx.gz new file mode 100644 index 000000000..7958521ff Binary files /dev/null and b/suites-experimental/tools-gps-tracks/dist/test-data/activity_2_forest_mtb.gpx.gz differ diff --git a/suites-experimental/tools-gps-tracks/dist/test-data/activity_3_road_race.tcx.gz b/suites-experimental/tools-gps-tracks/dist/test-data/activity_3_road_race.tcx.gz new file mode 100644 index 000000000..9060c68ff Binary files /dev/null and b/suites-experimental/tools-gps-tracks/dist/test-data/activity_3_road_race.tcx.gz differ diff --git a/suites-experimental/tools-gps-tracks/dist/test-data/activity_4_intervals.tcx.gz b/suites-experimental/tools-gps-tracks/dist/test-data/activity_4_intervals.tcx.gz new file mode 100644 index 000000000..8357b7078 Binary files /dev/null and b/suites-experimental/tools-gps-tracks/dist/test-data/activity_4_intervals.tcx.gz differ diff --git a/suites-experimental/tools-gps-tracks/dist/test-data/activity_5_grand_tour.fit.gz b/suites-experimental/tools-gps-tracks/dist/test-data/activity_5_grand_tour.fit.gz new file mode 100644 index 000000000..18c520009 Binary files /dev/null and b/suites-experimental/tools-gps-tracks/dist/test-data/activity_5_grand_tour.fit.gz differ diff --git a/suites-experimental/tools-gps-tracks/dist/test-data/activity_6_weekend_ride.fit.gz b/suites-experimental/tools-gps-tracks/dist/test-data/activity_6_weekend_ride.fit.gz new file mode 100644 index 000000000..1a0b5fbed Binary files /dev/null and b/suites-experimental/tools-gps-tracks/dist/test-data/activity_6_weekend_ride.fit.gz differ diff --git a/suites-experimental/tools-gps-tracks/dist/test-data/manifest.json b/suites-experimental/tools-gps-tracks/dist/test-data/manifest.json new file mode 100644 index 000000000..52a312930 --- /dev/null +++ b/suites-experimental/tools-gps-tracks/dist/test-data/manifest.json @@ -0,0 +1,8 @@ +[ + "activity_1_alps_stage.gpx.gz", + "activity_2_forest_mtb.gpx.gz", + "activity_3_road_race.tcx.gz", + "activity_4_intervals.tcx.gz", + "activity_5_grand_tour.fit.gz", + "activity_6_weekend_ride.fit.gz" +] \ No newline at end of file diff --git a/suites-experimental/tools-gps-tracks/dist/tools-gps-tracks.mjs b/suites-experimental/tools-gps-tracks/dist/tools-gps-tracks.mjs new file mode 100644 index 000000000..c46097801 --- /dev/null +++ b/suites-experimental/tools-gps-tracks/dist/tools-gps-tracks.mjs @@ -0,0 +1,1050 @@ +var jt=(()=>{let t=typeof location<"u"?location.pathname:"/",s=typeof location<"u"?location.search.replace(/^\?/,""):"",e={},n=s,i=null,a=null,r=!1,d=[{state:null,url:t+(s?`?${s}`:"")}];function l(){let v=new URLSearchParams(n),g={};v.has("track")&&(g.track=v.get("track")),v.has("map")&&(g.map=v.get("map")),v.has("xaxis")&&(g.xaxis=v.get("xaxis")),v.has("tab")&&(g.tab=v.get("tab")),v.has("sel_point")&&(g.sel_point=Number(v.get("sel_point"))),v.has("speed_mode")&&(g.speed_mode=v.get("speed_mode")),v.has("progress")&&(g.progress=v.get("progress"));let E=v.get("metrics");if(E&&(g.metrics=E.split(",").filter(Boolean)),v.has("f_date")&&(g.f_date=v.get("f_date").split(",").map(H=>H||null)),v.has("f_dist")&&(g.f_dist=v.get("f_dist").split(",").map(H=>H?Number(H):null)),v.has("f_dur")&&(g.f_dur=v.get("f_dur").split(",").map(H=>H?Number(H):null)),v.has("f_mets")&&(g.f_mets=v.get("f_mets").split(",")),v.has("f_tags")&&(g.f_tags=v.get("f_tags").split(",")),v.has("q")&&(g.q=v.get("q")),v.has("re")&&(g.re=v.get("re")==="1"),v.has("sort")){let H=v.get("sort"),L=H.startsWith("-"),h=L?H.substring(1):H;g.sort=`${h}-${L?"desc":"asc"}`}if(v.has("map_pos")){let H=v.get("map_pos").split(",").map(Number);H.length===3&&H.every(isFinite)&&(g.map_pos=H)}if(v.has("storage")&&(g.storage=v.get("storage")),v.has("sel")){let H=v.get("sel").split(",").map(Number);H.length===2&&H.every(L=>!isNaN(L)&&isFinite(L))&&(g.sel=H)}return g}function f(){a&&(clearTimeout(a),a=null);let v=new URLSearchParams;if(e.track&&v.set("track",e.track),e.map&&v.set("map",e.map),e.xaxis&&e.xaxis!=="time"&&v.set("xaxis",e.xaxis),e.tab&&e.tab!=="graphs"&&v.set("tab",e.tab),e.storage&&e.storage!=="memory"&&v.set("storage",e.storage),e.sel_point!==void 0&&v.set("sel_point",String(e.sel_point)),e.speed_mode&&v.set("speed_mode",e.speed_mode),e.q&&v.set("q",e.q),e.progress&&v.set("progress",e.progress),e.re&&v.set("re","1"),e.sort){let E=e.sort.split("-"),H=E[0],L=E[1];v.set("sort",(L==="desc"?"-":"")+H)}let g=v.toString();if(e.metrics&&(g+=`${g?"&":""}metrics=${e.metrics.join(",")}`),e.f_date&&(g+=`${g?"&":""}f_date=${e.f_date.map(E=>E||"").join(",")}`),e.f_dist&&(g+=`${g?"&":""}f_dist=${e.f_dist.map(E=>E??"").join(",")}`),e.f_dur&&(g+=`${g?"&":""}f_dur=${e.f_dur.map(E=>E??"").join(",")}`),e.f_mets&&(g+=`${g?"&":""}f_mets=${e.f_mets.join(",")}`),e.f_tags&&(g+=`${g?"&":""}f_tags=${e.f_tags.join(",")}`),e.map_pos){let E=`${(+e.map_pos[0]).toFixed(5)},${(+e.map_pos[1]).toFixed(5)},${e.map_pos[2]}`;g+=`${g?"&":""}map_pos=${E}`}if(e.sel){let E=`${+e.sel[0].toFixed(4)},${+e.sel[1].toFixed(4)}`;g+=`${g?"&":""}sel=${E}`}if(n=g,g!==i){let H=(typeof location<"u"?location.pathname:"/")+(g?`?${g}`:""),L=l();r?(d.push({state:L,url:H}),d.length>100&&d.shift(),r=!1):d.length>0?d[d.length-1]={state:L,url:H}:d.push({state:L,url:H}),i=g}}e=l();function y(){return{...e}}function S(v,g=!1){g&&(r=!0);for(let[E,H]of Object.entries(v)){let L=E;H==null?delete e[L]:e[L]=H}f()}function I(){e=l()}function B(){a&&(clearTimeout(a),a=null),e={},r=!1,n="",i="",d=[{state:null,url:typeof location<"u"?location.pathname:"/"}]}return{read:l,get:y,patch:S,refresh:I,reset:B}})();var gs=class{constructor(){this.trackStore=new Map;this.settingStore=new Map}async openDB(){return Promise.resolve()}async save(s){return this.trackStore.set(s.id,s),Promise.resolve()}async getAll(){return Promise.resolve(Array.from(this.trackStore.values()))}async remove(s){return this.trackStore.delete(s),Promise.resolve()}async clear(){return this.trackStore.clear(),this.settingStore.clear(),Promise.resolve()}async set(s,e){return this.settingStore.set(s,e),Promise.resolve()}async get(s){return Promise.resolve(this.settingStore.get(s)??null)}},Fs=class{constructor(){this.db=null;this.DB_NAME="weg-gps-db";this.DB_VERSION=2}async openDB(){return new Promise((s,e)=>{let n=indexedDB.open(this.DB_NAME,this.DB_VERSION);n.onupgradeneeded=()=>{let i=n.result;i.objectStoreNames.contains("tracks")||i.createObjectStore("tracks",{keyPath:"id"}),i.objectStoreNames.contains("settings")||i.createObjectStore("settings")},n.onsuccess=()=>{this.db=n.result,s()},n.onerror=()=>e(n.error)})}getStore(s,e){if(!this.db)throw new Error("IndexedDB not opened");return this.db.transaction(s,e).objectStore(s)}async save(s){return new Promise((e,n)=>{let i=this.getStore("tracks","readwrite").put(s);i.onsuccess=()=>e(),i.onerror=()=>n(i.error)})}async getAll(){return new Promise((s,e)=>{let n=this.getStore("tracks","readonly").getAll();n.onsuccess=()=>s(n.result||[]),n.onerror=()=>e(n.error)})}async remove(s){return new Promise((e,n)=>{let i=this.getStore("tracks","readwrite").delete(s);i.onsuccess=()=>e(),i.onerror=()=>n(i.error)})}async clear(){return this.db?new Promise((s,e)=>{let n=this.db.transaction(["tracks","settings"],"readwrite");n.objectStore("tracks").clear(),n.objectStore("settings").clear(),n.oncomplete=()=>s(),n.onerror=()=>e(n.error)}):Promise.resolve()}async set(s,e){return new Promise((n,i)=>{let a=this.getStore("settings","readwrite").put(e,s);a.onsuccess=()=>n(),a.onerror=()=>i(a.error)})}async get(s){return new Promise((e,n)=>{let i=this.getStore("settings","readonly").get(s);i.onsuccess=()=>e(i.result??null),i.onerror=()=>n(i.error)})}},rn=new gs,ge={openDB:async()=>{rn=jt.get().storage==="indexeddb"?new Fs:new gs,await rn.openDB()},save:t=>rn.save(t),getAll:()=>rn.getAll(),remove:t=>rn.remove(t),clear:()=>rn.clear(),set:(t,s)=>rn.set(t,s),get:t=>rn.get(t)};import Io from"fit-file-parser";function en(t,s,e,n){let a=t*Math.PI/180,r=e*Math.PI/180,d=(e-t)*Math.PI/180,l=(n-s)*Math.PI/180,f=Math.sin(d/2)*Math.sin(d/2)+Math.cos(a)*Math.cos(r)*Math.sin(l/2)*Math.sin(l/2);return 6371e3*(2*Math.atan2(Math.sqrt(f),Math.sqrt(1-f)))}function hl(t){let s=new Uint32Array([Math.floor(t/1e3)]);return btoa(String.fromCharCode(...new Uint8Array(s.buffer))).replace(/\+/g,"-").replace(/\//g,"_").replace(/=/g,"")}function bl(){return Math.random().toString(36).slice(2,6)}function ze(t){return t<60?`${Math.round(t)}s`:t<3600?`${Math.round(t/60)}m`:`${Math.round(t/3600)}h`}function he(t){let s=Math.floor(t/3600),e=Math.floor(t%3600/60);return s>0?`${s}h ${e}m`:`${e}m ${Math.floor(t%60)}s`}function On(t){if(t==null||!isFinite(t))return"--";let s=Math.floor(t/60),e=Math.round(t%60);return`${s}:${e.toString().padStart(2,"0")}`}function nn(t){let s=new Date(t);return isNaN(s.getTime())?"Unknown date":s.toISOString().split("T")[0]}function vl(t){let s=new Date(t);if(isNaN(s.getTime()))return"Unknown";let e=s.toISOString();return`${e.split("T")[0]} ${e.split("T")[1].slice(0,5)}`}function yl(t){if(t===0)return"0 B";let s=1024,e=["B","KB","MB","GB"],n=Math.floor(Math.log(t)/Math.log(s));return`${parseFloat((t/Math.pow(s,n)).toFixed(2))} ${e[n]}`}function ue(t){return String(t).replace(/&/g,"&").replace(//g,">").replace(/"/g,""")}function hs(t){let s=0;for(let n=0;n=0&&S0?l/f:t[d]}return r}function le(t,s=1){let e=parseInt(t.slice(1,3),16),n=parseInt(t.slice(3,5),16),i=parseInt(t.slice(5,7),16);return`rgba(${e}, ${n}, ${i}, ${s})`}function xl(t){if(!t.length)return null;let s=t[0].lat,e=t[0].lat,n=t[0].lon,i=t[0].lon;for(let a of t)a.late&&(e=a.lat),a.loni&&(i=a.lon);return{minLat:s,maxLat:e,minLon:n,maxLon:i}}var _e=16384,Ho=new Float64Array(_e),Fo=new Float64Array(_e),Do=new Float64Array(_e),Bo=new Float64Array(_e),_o=new Float64Array(_e),Ro=new Float64Array(_e),No=new Float64Array(_e);function wl(t,s){let e=t.length;if(e<=2)return t;let n=e<=_e?Ho:new Float64Array(e),i=e<=_e?Fo:new Float64Array(e),a=e<=_e?Do:new Float64Array(e),r=e<=_e?Bo:new Float64Array(e);for(let B=0;B=v){let k=g+E>>1,j=(i[k]-(i[g]+i[E])*.5)*H,U=n[k]-(n[g]+n[E])*.5;l+=Math.sqrt(j*j+U*U)+(r[E]-r[g])*1e-6,g=E}}}let y=[t[0]],S=t[0],I=l<-1e9?1e-9:0;for(let B=1;B=s&&(y.push(v),S=v)}return y.push(t[e-1]),y}var re=(()=>{function t(w){let b=w.length,u=0;for(let R=0;R0){let O=w[R-1],q=en(O.lat,O.lon,M.lat,M.lon);if(u+=q,M.speed==null&&M.time!=null&&O.time!=null){let V=(M.time-O.time)/1e3;V>0&&V<10&&(M.speed=q/V)}if(M.gradient===void 0&&M.ele!=null&&O.ele!=null){let V=M.ele-O.ele;q>.5?M.gradient=V/q*100:M.gradient=O.gradient||0}}M.dist=u}if(b>2){let R=b<=_e?_o:new Float64Array(b),M=b<=_e?Ro:new Float64Array(b),O=b<=_e?No:new Float64Array(b);for(let nt=0;nt=Ct){W-=M[it-Ct],F-=O[it-Ct];let K=W*gt,ht=R[it]-R[it-Ct],Pt=1.225*Math.exp(-R[it]*Bt),ut=Math.atan2(ht,(F+1)*.5);V+=K*K*Pt*1e-4+ut*(F*gt)*.01}}let at=V<-1e12?1e-9:0;for(let nt=1;nt0){let Lt=w[Ht-1];if(dt.ele!=null&&Lt.ele!=null){let st=dt.ele-Lt.ele;st>0?u.elevGain+=st:u.elevLoss-=st}(dt.gearFront!=null&&dt.gearFront!==Lt.gearFront||dt.gearRear!=null&&dt.gearRear!==Lt.gearRear)&&K++}dt.power!=null&&(R+=dt.power,M++,dt.power>u.maxPower&&(u.maxPower=dt.power)),dt.hr!=null&&(O+=dt.hr,q++,dt.hr>u.maxHR&&(u.maxHR=dt.hr)),dt.cad!=null&&(V+=dt.cad,Bt++),dt.speed!=null&&(at+=dt.speed,nt++,dt.speed>u.maxSpeed&&(u.maxSpeed=dt.speed)),dt.battery!=null&&(Ct+=dt.battery,gt++),dt.temp!=null&&(it=!0,W+=dt.temp,F++,(u.minTemp==null||dt.tempu.maxTemp)&&(u.maxTemp=dt.temp)),(dt.gearFront!=null||dt.gearRear!=null)&&(ht=!0)}u.totalDist=w[w.length-1].dist||0,u.avgPower=M?Math.round(R/M):null,u.avgHR=q?Math.round(O/q):null,u.avgCadence=Bt?Math.round(V/Bt):null,u.avgSpeed=nt?at/nt:null,u.avgBattery=gt?Math.round(Ct/gt):null,u.avgTemp=F?Number((W/F).toFixed(1)):null,u.shifts=ht?K:null;let Pt=w[0].time,ut=w[w.length-1].time;return Pt!=null&&ut!=null&&(u.duration=ut-Pt),q&&(u.sensors.push("Heart Rate"),b&&(u.hrZones=v(w),u.hrCurve=y(w))),Bt&&u.sensors.push("Cadence"),M&&(u.sensors.push("Power"),b&&(u.powerCurve=f(w),u.powerZones=S(w))),it&&u.sensors.push("Temperature"),ht&&u.sensors.push("Shifting"),gt&&u.sensors.push("Battery"),u}function i(w){if(!w||!w.points||!w.stats)return;let b=w.points,u=w.stats;u.avgHR!=null&&!u.hrCurve&&(u.hrZones=v(b),u.hrCurve=y(b)),u.avgPower!=null&&!u.powerCurve&&(u.powerCurve=f(b),u.powerZones=S(b))}function a(w){let b=w.length;if(b<8)return[];let u=[],R=["Q1 Opening Sector","Q2 Mid-Climb Sector","Q3 Valley Tempo","Q4 Finale Sector"];for(let K=0;K<4;K++){let ht=Math.floor(K*b/4),Pt=Math.min(b-1,Math.floor((K+1)*b/4)-1);if(Pt>ht){let ut=w.slice(ht,Pt+1);u.push({id:`q${K+1}`,label:R[K],kind:"quarter",startIdx:ht,endIdx:Pt,stats:n(ut,!0)})}}let M=["Opening Third","Middle Third","Final Third"];for(let K=0;K<3;K++){let ht=Math.floor(K*b/3),Pt=Math.min(b-1,Math.floor((K+1)*b/3)-1);if(Pt>ht){let ut=w.slice(ht,Pt+1);u.push({id:`t${K+1}`,label:M[K],kind:"third",startIdx:ht,endIdx:Pt,stats:n(ut,!1)})}}let O=0,q=1e3,V=1;for(let K=1;K=q||K===b-1){if(K>O+2){let Pt=w.slice(O,K+1);u.push({id:`km-${V}`,label:`Km ${V}`,kind:"km",startIdx:O,endIdx:K,stats:n(Pt,!1)}),V++}O=K,q+=1e3}let Bt=30,at=!1,nt=0,Ct=1,gt=!1,W=0,F=1,it=e*1.15;for(let K=Bt;K=1.8&&!at)at=!0,nt=K-Bt;else if((ut<.8||K===b-1)&&at&&(at=!1,K-nt>=45&&Ct<=6)){let dt=w.slice(nt,K+1);u.push({id:`climb-${Ct}`,label:`Climb Segment ${Ct}`,kind:"climb",startIdx:nt,endIdx:K,stats:n(dt,!0)}),Ct++}let Ht=w[K].power??0;if(Ht>=it&&!gt)gt=!0,W=K;else if((Ht=15&&F<=6)){let dt=w.slice(W,K+1);u.push({id:`sprint-${F}`,label:`Sprint Interval ${F}`,kind:"sprint",startIdx:W,endIdx:K,stats:n(dt,!0)}),F++}}return u}function r(w){let b=[];for(let u=0;uu)continue;let Bt=0,at=0,nt=u-V;for(let Ct=0;Ct<=nt;Ct++){let gt=R[Ct+V]-R[Ct];gt>Bt&&(Bt=gt,at=Ct)}M[V]={val:Math.round(Bt/V),idx:at},O=!0}return O?M:null}function f(w){let b=w.length,u=new Array(b),R=!1;for(let q=0;qR*e);for(let R=1;R10)continue;let V=M.power;VR===0)?null:b}function I(w){e=w}function B(){return e}function v(w){let b=[0,0,0,0,0];for(let u=1;u10)continue;let q=R.hr;qu===0)?null:b}function g(w){s=w}function E(){return[...s]}function H(w,b,u){let R=w.indexOf(b);if(R===-1)return null;let M=R+b.length,O=w.indexOf(u,M);if(O===-1)return null;let q=w.slice(M,O).trim();return q!==""?q:null}function L(w,b){let u=w.indexOf(b);if(u===-1)return null;let R=u+b.length,M=w.indexOf('"',R);return M===-1?null:w.slice(R,M)}let h=-1,m=0;function p(w,b,u){for(;bb&&w.charCodeAt(u-1)<=32;)u--;if(b>=u)return null;let R=!1,M=w.charCodeAt(b);M===45?(R=!0,b++):M===43&&b++;let O=0,q=0;for(let V=b;V=48&&Bt<=57)O=O*10+(Bt-48),q++;else break}return q===0?null:R?-O:O}function k(w,b,u){for(;bb&&w.charCodeAt(u-1)<=32;)u--;if(b>=u)return null;let R=!1,M=w.charCodeAt(b);M===45?(R=!0,b++):M===43&&b++;let O=0,q=0,V=1,Bt=!1,at=0;for(let Ct=b;Ct=48&><=57)at++,Bt?(q=q*10+(gt-48),V*=10):O=O*10+(gt-48);else if(gt===46&&!Bt)Bt=!0;else{let W=parseFloat(w.slice(b-(R?1:0),u));return Number.isFinite(W)?W:null}}if(at===0)return null;let nt=O+q/V;return R?-nt:nt}function j(w,b,u){for(;bb&&w.charCodeAt(u-1)<=32;)u--;let R=u-b;if(R>=20&&w.charCodeAt(b+10)===84&&w.charCodeAt(u-1)===90){let O=(w.charCodeAt(b)-48)*1e3+(w.charCodeAt(b+1)-48)*100+(w.charCodeAt(b+2)-48)*10+(w.charCodeAt(b+3)-48),q=(w.charCodeAt(b+5)-48)*10+(w.charCodeAt(b+6)-48),V=(w.charCodeAt(b+8)-48)*10+(w.charCodeAt(b+9)-48),Bt=O<<9|q<<5|V;Bt!==h&&(h=Bt,m=Date.UTC(O,q-1,V));let at=(w.charCodeAt(b+11)-48)*10+(w.charCodeAt(b+12)-48),nt=(w.charCodeAt(b+14)-48)*10+(w.charCodeAt(b+15)-48),Ct=(w.charCodeAt(b+17)-48)*10+(w.charCodeAt(b+18)-48),gt=0;return w.charCodeAt(b+19)===46&&R>=24&&(gt=(w.charCodeAt(b+20)-48)*100+(w.charCodeAt(b+21)-48)*10+(w.charCodeAt(b+22)-48)),m+at*36e5+nt*6e4+Ct*1e3+gt}let M=new Date(w.slice(b,u)).getTime();return Number.isFinite(M)?M:null}function U(w,b,u,R){let M=w.indexOf(R,b);if(M===-1||M>=u)return 0;let O=M+R.length,q=w.indexOf('"',O);return q===-1||q>=u?0:k(w,O,q)??0}function Q(w,b){let u=b??new TextDecoder().decode(w),R=u.indexOf(""),q=(O!==-1?H(M.slice(O),"",""):null)||H(M,"","")||"Unnamed Track",V=M.indexOf(">",M.indexOf("",W+6);if(F===-1)break;let it=u.indexOf("",F+1);if(it===-1)break;nt=it+8;let K=U(u,W+6,F+1,'lat="'),ht=U(u,W+6,F+1,'lon="'),Pt=null,ut=null,Ht=null,dt=null,Lt=null,st=null,ft=null,At=null,zt=null,z=null,_=null,Ft=null,It=F+1;for(;It=it)break;let bt=u.indexOf(">",Et+1);if(bt===-1||bt>=it)break;let vt=u.charCodeAt(Et+1);if(vt===47){It=bt+1;continue}let Dt=bt-Et-1;if(vt===103&&Dt===26&&Ht!==null&&dt!==null&&st!==null)break;let Wt=u.indexOf("<",bt+1);if(Wt===-1||Wt>it)break;if(u.charCodeAt(Wt+1)===47){let Ot=bt+1,te=Wt;Dt===3?vt===101?Pt=k(u,Ot,te):vt===99&&dt===null&&(dt=p(u,Ot,te)):Dt===2?vt===104&&Ht===null&&(Ht=p(u,Ot,te)):Dt===4?vt===116?u.charCodeAt(Et+2)===105?ut=j(u,Ot,te):st===null&&(st=k(u,Ot,te)):vt===84&&st===null&&(st=k(u,Ot,te)):Dt===5?(vt===112||vt===80||vt===87)&&Lt===null?Lt=k(u,Ot,te):(vt===115||vt===83)&&ft===null?ft=k(u,Ot,te):vt===97&&st===null&&(st=k(u,Ot,te)):Dt===7?(vt===98||vt===66)&&Ft===null?Ft=p(u,Ot,te):vt===67&&dt===null&&(dt=p(u,Ot,te)):Dt===8?(vt===103||vt===71)&&zt===null&&(zt=p(u,Ot,te)):Dt===9?vt===103?u.charCodeAt(Et+8)===104&&Ht===null?Ht=p(u,Ot,te):At===null&&(At=p(u,Ot,te)):vt===71&&At===null&&(At=p(u,Ot,te)):Dt===10?(vt===103&&dt===null||vt===82&&dt===null)&&(dt=p(u,Ot,te)):Dt===12?vt===103&&u.charCodeAt(Et+8)===97&&st===null?st=k(u,Ot,te):vt===80&&Lt===null?Lt=k(u,Ot,te):vt===72&&Ht===null&&(Ht=p(u,Ot,te)):Dt===13?(vt===103||vt===71)&&_===null&&(_=p(u,Ot,te)):Dt===14?(vt===103||vt===71)&&z===null?z=p(u,Ot,te):vt===65&&st===null&&(st=k(u,Ot,te)):Dt===11&&vt===84&&st===null&&(st=k(u,Ot,te));let ye=Wt+2+Dt;if(u.charCodeAt(ye)===62)It=ye+1;else{let Fe=u.indexOf(">",Wt+2);It=Fe!==-1?Fe+1:Wt+2}if(Ft!==null&&Ht!==null&&dt!==null&&st!==null)break}else It=Wt}let Nt=null;z!=null&&_!=null&&_!==0?Nt=z/_:At!=null&&zt!=null&&zt!==0&&(Nt=At/zt),at.push({lat:K,lon:ht,ele:Pt,time:ut,hr:Ht,cad:dt,power:Lt,speed:ft,temp:st,gearFront:At,gearRear:zt,gearFrontTooth:z,gearRearTooth:_,battery:Ft,gears:Nt})}if(!at.length)throw new Error("No track points found in GPX");let Ct=t(at),gt=n(Ct);return{name:q,device:Bt,format:"gpx",points:Ct,stats:gt}}function Y(w,b){let u=b??new TextDecoder().decode(w),R=u.indexOf(""),M=R!==-1?u.slice(0,R):u,O=H(M,"","")||"Unnamed TCX",q=H(M,"",""),V=q?H(q,"",""):null,Bt=[],at=R!==-1?R:0;for(;at",at);if(gt===-1)break;let W=u.indexOf("",gt+12);if(W===-1)break;at=W+13;let F=null,it=null,K=null,ht=null,Pt=null,ut=null,Ht=null,dt=null,Lt=null,st=null,ft=null,At=null,zt=null,z=null,_=gt+12;for(;_=W)break;let Nt=u.indexOf(">",It+1);if(Nt===-1||Nt>=W)break;let Et=u.charCodeAt(It+1);if(Et===47){_=Nt+1;continue}let bt=Nt-It-1,vt=u.indexOf("<",Nt+1);if(vt===-1||vt>W)break;if(u.charCodeAt(vt+1)===47){let Dt=Nt+1,Wt=vt;bt===4?Et===84?u.charCodeAt(It+2)===105?ht=j(u,Dt,Wt):Lt===null&&(Lt=k(u,Dt,Wt)):Et===116&&Lt===null&&(Lt=k(u,Dt,Wt)):bt===5?Et===86&&Pt===null?Pt=p(u,Dt,Wt):(Et===87||Et===80||Et===112)&&Ht===null?Ht=k(u,Dt,Wt):(Et===83||Et===115)&&dt===null?dt=k(u,Dt,Wt):Et===97&&Lt===null&&(Lt=k(u,Dt,Wt)):bt===7?Et===67&&ut===null?ut=p(u,Dt,Wt):(Et===66||Et===98)&&z===null&&(z=p(u,Dt,Wt)):bt===8?(Et===71||Et===103)&&ft===null&&(ft=p(u,Dt,Wt)):bt===9?(Et===71||Et===103)&&st===null&&(st=p(u,Dt,Wt)):bt===10?Et===82&&ut===null&&(ut=p(u,Dt,Wt)):bt===11?Et===84&&Lt===null&&(Lt=k(u,Dt,Wt)):bt===13?(Et===71||Et===103)&&zt===null&&(zt=p(u,Dt,Wt)):bt===14?Et===65?u.charCodeAt(It+2)===108?K=k(u,Dt,Wt):Lt===null&&(Lt=k(u,Dt,Wt)):(Et===71||Et===103)&&At===null&&(At=p(u,Dt,Wt)):bt===15?Et===76&&(F=k(u,Dt,Wt)):bt===16?Et===76&&(it=k(u,Dt,Wt)):bt===2&&Et===104&&Pt===null?Pt=p(u,Dt,Wt):bt===3&&Et===99&&ut===null?ut=p(u,Dt,Wt):bt===12&&(Et===72&&Pt===null?Pt=p(u,Dt,Wt):Et===80&&Ht===null&&(Ht=k(u,Dt,Wt)));let Ot=vt+2+bt;if(u.charCodeAt(Ot)===62)_=Ot+1;else{let te=u.indexOf(">",vt+2);_=te!==-1?te+1:vt+2}if(z!==null&&Pt!==null&&ut!==null&&Lt!==null)break}else _=vt}if(F==null||it==null)continue;let Ft=null;At!=null&&zt!=null&&zt!==0?Ft=At/zt:st!=null&&ft!=null&&ft!==0&&(Ft=st/ft),Bt.push({lat:F,lon:it,ele:K,time:ht,hr:Pt,cad:ut,power:Ht,speed:dt,temp:Lt,gearFront:st,gearRear:ft,gearFrontTooth:At,gearRearTooth:zt,battery:z,gears:Ft})}let nt=t(Bt),Ct=n(nt);return{name:O,device:V,format:"tcx",points:nt,stats:Ct}}function G(w){if(w.byteLength<14)return null;let b=new DataView(w),u=b.getUint8(0);if(u<12||u+2>w.byteLength||b.getUint8(8)!==46||b.getUint8(9)!==70||b.getUint8(10)!==73||b.getUint8(11)!==84)return null;let R=b.getUint32(4,!0),M=Math.min(w.byteLength,u+R),O=631065600,q=180/Math.pow(2,31),V=new Array(16),Bt=[],at=[],nt=[],Ct=[],gt=[],W=u;for(;WM)return null;W++;let ht=b.getUint8(W++)===0,Pt=b.getUint16(W,ht);W+=2;let ut=b.getUint8(W++);if(W+ut*3>M)return null;let Ht=new Array(ut),dt=0;for(let Lt=0;LtM)return null;let ht=K.littleEndian,Pt=K.globalMsgNum;if(Pt===20&&K.fields.length===10&&K.recordSize===25&&K.fields[0].num===253&&K.fields[1].num===0){let dt=b.getUint32(W,ht),Lt=b.getInt32(W+4,ht),st=b.getInt32(W+8,ht),ft=b.getUint16(W+12,ht),At=b.getUint8(W+14),zt=b.getUint8(W+15),z=b.getUint32(W+16,ht),_=b.getUint16(W+20,ht),Ft=b.getUint16(W+22,ht),It=b.getInt8(W+24);nt.push({timestamp:(dt+O)*1e3,position_lat:Lt*q,position_long:st*q,altitude:ft/5-500,heart_rate:At,cadence:zt,distance:z/100,speed:_/1e3*3.6,power:Ft,temperature:It}),W+=25;continue}let ut={},Ht=W;for(let dt=0;dt{let R=(q,V)=>{if(q){u(q);return}try{let Bt=V.sessions?.[0]?.start_time?`Fit Activity ${new Date(V.sessions[0].start_time).toISOString().split("T")[0]}`:"Unnamed Fit",at=null,nt=[],Ct=null,gt=null;V.sports?.[0]?(Ct=V.sports[0].sport,gt=V.sports[0].sub_sport):V.sessions?.[0]&&(Ct=V.sessions[0].sport,gt=V.sessions[0].sub_sport);let W=z=>z.product_name||z.garmin_product||z.product||z.device_type||z.manufacturer||null;if(V.file_ids?.[0]){let z=V.file_ids[0];at=W(z),nt.push({name:z.product_name||z.garmin_product||z.product,manufacturer:z.manufacturer,serial:z.serial_number?String(z.serial_number):void 0,version:z.software_version?String(z.software_version):void 0,type:"main"})}V.device_infos&&V.device_infos.forEach(z=>{if(z.device_index==="creator"||z.device_index===0){let Ft=W(z);(z.product_name||z.garmin_product||!at)&&(at=Ft),nt.length>0&&nt[0].type==="main"&&((z.product_name||z.garmin_product||!nt[0].name)&&(nt[0].name=z.product_name||z.garmin_product||z.product),nt[0].manufacturer||(nt[0].manufacturer=z.manufacturer),!nt[0].serial&&z.serial_number&&(nt[0].serial=String(z.serial_number)),!nt[0].version&&z.software_version&&(nt[0].version=String(z.software_version)),!nt[0].hardwareVersion&&z.hardware_version&&(nt[0].hardwareVersion=String(z.hardware_version)));return}nt.push({name:z.product_name||z.garmin_product||z.product||z.device_type||"Sensor",manufacturer:z.manufacturer,serial:z.serial_number?String(z.serial_number):void 0,version:z.software_version?String(z.software_version):void 0,hardwareVersion:z.hardware_version?String(z.hardware_version):void 0,type:z.device_type||"sensor",batteryStatus:z.battery_status,batteryVoltage:z.battery_voltage,batteryLevel:z.battery_level,sourceType:z.source_type!=null?String(z.source_type):void 0})}),V.sensor_settings&&V.sensor_settings.forEach(z=>{let _=z.name||z.product||z.sensor_type;if(!_)return;let Ft=z.ant_id?String(z.ant_id):void 0,It=nt.find(Nt=>Ft&&Nt.serial===Ft||Nt.name===_&&Nt.manufacturer===z.manufacturer);It?(It.name||(It.name=_),It.manufacturer||(It.manufacturer=z.manufacturer),It.type||(It.type=z.sensor_type),It.sourceType||(It.sourceType=z.connection_type!=null?String(z.connection_type):void 0)):nt.push({name:_,manufacturer:z.manufacturer,serial:Ft,type:z.sensor_type||"sensor",sourceType:z.connection_type!=null?String(z.connection_type):void 0})});let F=[];V.device_statuses&&(V.device_statuses.forEach((z,_)=>{z.timestamp&&z.battery_level!=null&&F.push({time:typeof z.timestamp=="number"?z.timestamp:new Date(z.timestamp).getTime(),level:z.battery_level,idx:_})}),F.sort((z,_)=>z.time-_.time||z.idx-_.idx));let it=[];V.events&&(V.events.forEach((z,_)=>{let Ft=z.front_gear_num??z.front_gear??null,It=z.rear_gear_num??z.rear_gear??null,Nt=z.front_gear_num_tooth??null,Et=z.rear_gear_num_tooth??null;if(z.event==="rear_gear_change"||z.event==="front_gear_change"){let bt=z.data;if(bt!=null){let vt=bt>>24&255,Dt=bt>>16&255,Wt=bt>>8&255,Ot=bt&255;Dt!==0&&(Ft=Dt),vt!==0&&(Nt=vt),Ot!==0&&(It=Ot),Wt!==0&&(Et=Wt)}}z.timestamp&&(Ft!=null||It!=null||Nt!=null||Et!=null)&&it.push({time:typeof z.timestamp=="number"?z.timestamp:new Date(z.timestamp).getTime(),front:Ft,rear:It,frontTooth:Nt,rearTooth:Et,idx:_})}),it.sort((z,_)=>z.time-_.time||z.idx-_.idx));let K=V.records||[],ht=null,Pt=0,ut=null,Ht=null,dt=null,Lt=null,st=0,ft=[];for(let z=0;z name, Placemark > name");M&&(R=M.textContent?.trim()||R);let O=u.querySelectorAll("coordinates");if(!O.length)throw new Error("No coordinates found in KML");let q=[];if(O.forEach(at=>{(at.textContent?.trim().split(/\s+/)||[]).forEach(Ct=>{let gt=Ct.split(",");if(gt.length<2)return;let W=parseFloat(gt[0]),F=parseFloat(gt[1]),it=gt.length>2?parseFloat(gt[2]):null;isNaN(F)||isNaN(W)||q.push({lat:F,lon:W,ele:it!=null&&isNaN(it)?null:it,time:null,hr:null,cad:null,power:null,speed:null,temp:null,gearFront:null,gearRear:null,gearFrontTooth:null,gearRearTooth:null,battery:null,gears:null})})}),!q.length)throw new Error("No valid coordinates in KML");let V=t(q),Bt=n(V);return{name:R,format:"kml",points:V,stats:Bt}}async function ct(w){let b=w.name.split(".").pop()?.toLowerCase(),u=w._buf??await w.arrayBuffer(),R=w._text,M;switch(b){case"gpx":M=Q(u,R);break;case"tcx":M=Y(u,R);break;case"fit":M=await ot(u);break;case"kml":M=_t(u);break;default:throw new Error(`Unsupported format: .${b}`)}return M&&(M.fileName=w.name,M.fileSize=w.size,M.points&&M.stats&&(i(M),M.stats.sections=a(M.points))),M}return{parseFile:ct,computeStats:n,ensureCurvesAndZones:i,analyzeTrackSections:a,findOverlaps:r,calculatePowerCurve:f,calculateHRCurve:y,calculatePowerZones:S,calculateHRZones:v,enrichPoints:t,setHRZones:g,getHRZones:E,setFTP:I,getFTP:B}})();var qt=(()=>{let t={},s=null;function e(G,ot,_t,ct,w){}function n(G){}function i(G){t[G.id]=G}function a(G){G.forEach(ot=>{t[ot.id]=ot})}function r(G){delete t[G]}function d(){t={}}function l(){t={}}function f(G,ot){}function y(G,ot){}function S(){}function I(G){}function B(G,ot){}function v(){}function g(G,ot,_t,ct,w,b){}function E(){}function H(G,ot){}function L(G){}function h(G,ot,_t,ct){}function m(){}function p(){}function k(){return[0,0,0]}function j(G,ot,_t){}function U(G,ot,_t){}function Q(G){}function Y(G,ot){return Object.keys(t)}return{init:e,getMap:()=>null,setCustomClickHandler:G=>{s=G},switchBasemap:n,addTrack:i,addTracks:a,removeTrack:r,clearTracks:d,removeAllTracks:l,setTrackVisible:f,setSelectedTrack:y,fitAll:S,fitTrack:I,showCursorAt:B,hideCursor:v,highlightSegment:g,clearHighlight:E,setTrackHover:H,ensureVisible:L,centerOn:h,closePopup:m,invalidateSize:p,getPosition:k,setPosition:j,colorTrackByMetric:U,clearMetricColor:Q,findTracksPassingThroughPoints:Y,getTrackData:G=>t[G]}})();import Sl from"uplot";var kn=(()=>{function t(){let e=re.getFTP(),n=[.55,.75,.9,1.05,1.2,1.5].map(l=>l*e),i=["Active Recovery","Endurance","Tempo","Threshold","VO2 Max","Anaerobic","Neuromuscular"],a=["#82E0AA","#A8C8A0","#F7DC6F","#F8C471","#F39C12","#E67E22","#C0392B"],r=[],d=0;for(let l=0;ls?t.toFixed(1):Math.round(t).toString(),fmtAxis:t=>Math.round(t).toString()},speed:{label:"Speed",field:"speed",unit:"km/h",color:Rt.speed.color,abbr:"spd",icon:Rt.speed.icon,fmt:t=>(t*3.6).toFixed(1),fmtAxis:t=>(t*3.6).toFixed(0),transform:t=>t,smoothSigma:2},gradient:{label:"Gradient",field:"gradient",unit:"%",color:Rt.gradient.color,abbr:"grad",icon:Rt.gradient.icon,fmt:t=>t.toFixed(1),fmtAxis:t=>t.toFixed(0),smoothSigma:2},power:{label:"Power",field:"power",unit:"W",color:Rt.power.color,abbr:"pwr",icon:Rt.power.icon,fmt:t=>Math.round(t).toString(),fmtAxis:t=>Math.round(t).toString()},hr:{label:"Heart Rate",field:"hr",unit:"bpm",color:Rt.hr.color,abbr:"hr",icon:Rt.hr.icon,fmt:t=>Math.round(t).toString(),fmtAxis:t=>Math.round(t).toString()},cadence:{label:"Cadence",field:"cad",unit:"rpm",color:Rt.cadence.color,abbr:"cad",icon:Rt.cadence.icon,fmt:t=>Math.round(t).toString(),fmtAxis:t=>Math.round(t).toString()},temperature:{label:"Temp",field:"temp",unit:"\xB0C",color:Rt.temperature.color,abbr:"temp",icon:Rt.temperature.icon,fmt:t=>t.toFixed(1),fmtAxis:t=>Math.round(t).toString(),smoothSigma:6},gearRear:{label:"Rear Gear",field:"gearRearTooth",unit:"T",color:Rt.gearRear.color,abbr:"rgr",icon:Rt.gearRear.icon,fmt:t=>Math.round(t).toString(),fmtAxis:t=>Math.round(t).toString()},gearFront:{label:"Front Gear",field:"gearFrontTooth",unit:"T",color:Rt.gearFront.color,abbr:"fgr",icon:Rt.gearFront.icon,fmt:t=>Math.round(t).toString(),fmtAxis:t=>Math.round(t).toString()},gears:{label:"Gears",field:"gears",unit:"",color:Rt.gears.color,abbr:"gr",icon:Rt.gears.icon,fmt:t=>t.toFixed(2),fmtAxis:t=>t.toFixed(1)},battery:{label:"Battery",field:"battery",unit:"%",color:Rt.battery.color,abbr:"bat",icon:Rt.battery.icon,fmt:t=>Math.round(t).toString(),fmtAxis:t=>Math.round(t).toString()},vam:{label:"VAM",field:"ele",unit:"m/h",color:"#FFA07A",abbr:"vam",icon:"trending_up",fmt:t=>Math.round(t).toString(),fmtAxis:t=>Math.round(t).toString(),smoothSigma:5,compute:(t,s)=>{let e=new Array(t.length).fill(null);for(let n=1;n0&&(e[n]=r/d*3600)}}return s(e)}}};var Ye={cycling:{name:"Cycling",icon:"directions_bike",defaultPreset:"speed"},skiing:{name:"Skiing",icon:"downhill_skiing",defaultPreset:"speed"},running:{name:"Running",icon:"directions_run",defaultPreset:"pace",paceUnit:"km"},swimming:{name:"Swimming",icon:"pool",defaultPreset:"pace",paceUnit:"100m"}};function Tl(t){if(!t)return"question_mark";let s=t.toLowerCase();return Ye[s]?.icon||"exercise"}var St=(()=>{let t=[],s=null,e="time",n="speed",i=null,a=null,r=!1,d=!1,l=new Set(["elevation"]),f=new Set,y=new Set(["speed","gradient","vam","temperature"]),S=new Set(["power"]),I="time",B=null,v=[],g=null,E=null,H=!1,L=!1,h=null,m=null,p=null,k=null,j=null,U=null,Q=null,Y=null,G=null,ot=null,_t=null,ct=null,w=130,b=28,u=20,R=!0,M=null,O=null,q=null,V=null,Bt=null,at=null,nt=null;function Ct(o,c,x,T){G=o,ot=c,_t=x,ct=T,O=document.getElementById("charts-container"),O?.addEventListener("scroll",()=>{p&&j&&k&&Js(p,j,k)}),q=document.getElementById("chart-empty"),V=document.getElementById("chart-stats-sel"),Bt=document.getElementById("btn-reset-selection"),M=document.getElementById("hist-tooltip"),a=Sl.sync("strasse-sync"),document.getElementById("sel-cancel-btn")?.addEventListener("click",_),Bt?.classList.add("hidden");let A=new Set(["w","a","s","d"]),$=new Set;document.addEventListener("keydown",D=>{if(!t.length||!B||["INPUT","TEXTAREA","SELECT"].includes(document.activeElement?.tagName||""))return;let P=D.key.toLowerCase();if(!A.has(P))return;D.preventDefault(),$.add(P);let N=t[0].uplot;Q||(Q=[N.scales.x.min,N.scales.x.max]);let[J,Z]=Q,et=(Z-J)*.1,tt=.15,C=J,xt=Z;if(P==="a")C=J-et,xt=Z-et;else if(P==="d")C=J+et,xt=Z+et;else if(P==="w"||P==="s"){let rt=m!==null?m:(J+Z)/2,$t=P==="w"?1-tt:1+tt*1.2;C=rt-(rt-J)*$t,xt=rt+(Z-rt)*$t}let Tt=[t[0].xData[0],t[0].xData[t[0].xData.length-1]];if(CTt[1]){let rt=xt-Tt[1];C-=rt,xt-=rt}C=Math.max(Tt[0],C),xt=Math.min(Tt[1],xt),xt-C>.001&&(Q=[C,xt],gt(C,xt,!0))}),document.addEventListener("keyup",D=>{let P=D.key.toLowerCase();A.has(P)&&$.delete(P)})}function gt(o,c,x=!1){if(!t.length)return;if(!x){t.forEach(({uplot:N})=>N.setScale("x",{min:o,max:c}));return}let T=U?U[0]:t[0].uplot.scales.x.min,A=U?U[1]:t[0].uplot.scales.x.max;Y&&cancelAnimationFrame(Y);let $=(N,J,Z)=>N+(J-N)*Z,D=.25,P=()=>{if(!Q)return;let[N,J]=Q,Z=$(U[0],N,D),X=$(U[1],J,D);t.forEach(({uplot:et})=>{et.setScale("x",{min:Z,max:X})}),Math.abs(Z-N)>1e-4||Math.abs(X-J)>1e-4?Y=requestAnimationFrame(P):Y=null};Y=requestAnimationFrame(P)}function W(o,c=[],x=!1){h=null,g=null,E=null,v=c,f=new Set(Object.entries(Ne).filter(([,T])=>o.points.some(A=>A[T.field]!=null)).map(([T])=>T)),l.size===0&&(l=new Set(["elevation"].filter(T=>f.has(T)))),document.querySelectorAll(".metric-pill").forEach(T=>{let A=T,$=A.dataset.metric,D=f.has($);A.classList.toggle("active",D&&l.has($)),A.classList.toggle("disabled",!D)}),B=o,x?rl(o):bt()}function F(){h=null,g=null,E=null,B=null,Rn(),q&&(q.style.display="flex",q.innerHTML=` + no_sim +
Select a track to view analysis
+ `);let o=document.getElementById("chart-stats");o&&o.classList.add("hidden"),It(),at=null,nt&&nt(null)}function it(){Y!==null&&(cancelAnimationFrame(Y),Y=null),l=new Set(["elevation"]),f=new Set,y=new Set(["speed","gradient","vam","temperature"]),S=new Set(["power"]),I="time",v=[],U=null,Q=null,m=null,p=null,k=null,j=null,R=!0,F(),document.querySelectorAll("#metric-pills .metric-pill").forEach(x=>{let T=x;T.classList.toggle("active",l.has(T.dataset.metric)),T.classList.remove("disabled","overflow-hidden")});let o=document.getElementById("pill-overflow");o&&o.classList.add("hidden");let c=document.getElementById("chart-toolbar");c&&c.classList.remove("collapsed"),document.querySelectorAll("#x-axis-ctrl .seg-btn").forEach(x=>{x.classList.toggle("active",x.dataset.axis==="time")})}function K(o){l.has(o)?l.delete(o):l.add(o),B&&bt(!0)}function ht(o,c,x=!1){let T=y.has(o);c&&!T?(y.add(o),B&&!x&&bt(!0)):!c&&T&&(y.delete(o),B&&!x&&bt(!0))}function Pt(o,c){let x=S.has(o);c&&!x?(S.add(o),B&&bt(!0)):!c&&x&&(S.delete(o),B&&bt(!0))}function ut(o){l=new Set(o),B&&bt(!0),document.querySelectorAll(".metric-pill").forEach(c=>{let x=c;x.classList.toggle("active",l.has(x.dataset.metric))})}function Ht(o){if(I===o||!B){I=o;return}let c=B.points,x=c.find($=>$.time!=null)?.time||0,T=$=>{if($===null)return null;let D=0;if(I==="distance"){let P=$*1e3;D=c.findIndex(N=>(N.dist||0)>=P)}else{let P=x+$*1e3;D=c.findIndex(N=>(N.time||0)>=P)}return D===-1&&(D=c.length-1),o==="distance"?(c[D].dist||0)/1e3:((c[D].time||x)-x)/1e3};g!==null&&(g=T(g)),E!==null&&(E=T(E));let A=null;if(t.length){let $=t[0].uplot,D=T($.scales.x.min),P=T($.scales.x.max);D!==null&&P!==null&&(A=[D,P])}I=o,bt(!0),A&&(r=!0,t.forEach(({uplot:$})=>{$.batch(()=>$.setScale("x",{min:A[0],max:A[1]}))}),r=!1)}function dt(){R=!R,t.forEach(({histCol:o})=>o.classList.toggle("visible",R)),Et()}function Lt(o){nt=o}function st(o,c=!1){at===o||o===null?(at=null,ft(),nt&&nt(null)):(at=o,ft(),At()),B&&!c&&bt(!0)}function ft(){document.querySelectorAll(".map-color-btn").forEach(o=>{let c=o;c.classList.toggle("active",c.dataset.metric===at)})}function At(){if(!nt||!at||!B)return;let o=t.find(x=>x.metricKey===at);if(!o)return;let c=zt(at,o.yData);nt({pts:B.points,colors:c})}function zt(o,c){let x=c.filter(X=>X!=null&&isFinite(X));if(!x.length)return c.map(()=>"#888896");let T=Math.min(...x),A=Math.max(...x),$=B?.sport,D=$?Ye[$.toLowerCase()]:null,P=D?.defaultPreset||"speed",J=jt.get().speed_mode||P,Z=D?.paceUnit||"km";return c.map(X=>{if(X==null||!isFinite(X))return"#888896";if(o==="gradient")return ts(X);if(o==="speed"){if(J==="pace"){let tt=(Z==="km"?1e3:100)/X;return il(tt)}return il(X)}let et=A===T?.5:Math.max(0,Math.min(1,(X-T)/(A-T)));return et<=.25?Ve("#4575b4","#91bfdb",et/.25):et<=.5?Ve("#91bfdb","#fee090",(et-.25)/.25):et<=.75?Ve("#fee090","#fc8d59",(et-.5)/.25):Ve("#fc8d59","#d73027",(et-.75)/.25)})}function z(){_()}function _(){g=null,E=null,h=null,ct&&ct(null),t.forEach(o=>{o.pinnedHistY=null}),Dt(),Fe(),r=!0,t.forEach(({uplot:o,xData:c})=>{o.batch(()=>o.setScale("x",{min:c[0],max:c[c.length-1]})),o._strasse?.updateHeaderStats(c[0],c[c.length-1])}),r=!1,ot&&ot(null,null,I)}function Ft(o){if(!V)return;let c=(x,T)=>{let A=document.querySelector(`#${x} .stat-value`);A&&(A.textContent=T)};c("sel-distance",o.totalDist!=null?`${(o.totalDist/1e3).toFixed(1)} km`:"\u2014"),c("sel-duration",o.duration!=null?he(Math.floor(o.duration/1e3)):"\u2014"),c("sel-elevation",o.elevGain!=null?`${Math.round(o.elevGain)} m`:"\u2014"),c("sel-avg-speed",o.avgSpeed!=null?`${(o.avgSpeed*3.6).toFixed(1)} km/h`:"\u2014"),c("sel-avg-power",o.avgPower!=null?`${o.avgPower} W`:"\u2014"),c("sel-avg-hr",o.avgHR!=null?`${o.avgHR} bpm`:"\u2014"),c("sel-avg-temp",o.avgTemp!=null?`${o.avgTemp.toFixed(1)} \xB0C`:"\u2014"),V.classList.remove("hidden"),Bt&&Bt.classList.remove("hidden")}function It(){V&&V.classList.add("hidden"),Bt&&Bt.classList.add("hidden"),["sel-distance","sel-duration","sel-elevation","sel-avg-speed","sel-avg-power","sel-avg-hr","sel-avg-temp"].forEach(o=>{let c=document.querySelector(`#${o} .stat-value`);c&&(c.textContent="--")})}function Nt(o,c,x=!1){if(!B)return;let T=B.points,A=T.find(J=>J.time!=null)?.time||0,$=((T[T.length-1]?.time||0)-A)/1e3;o>1e12&&(o=(o-A)/1e3),c>1e12&&(c=(c-A)/1e3),o=Math.max(0,Math.min(o,$)),c=Math.max(0,Math.min(c,$));let D=J=>{if(I==="time")return J;let Z=A+J*1e3,X=T.findIndex(et=>(et.time||0)>=Z);return X===-1&&(X=T.length-1),(T[X].dist||0)/1e3},P=D(o),N=D(c);g=P,E=N,U=[P,N],!x&&(r=!0,t.forEach(({uplot:J})=>{J.batch(()=>J.setScale("x",{min:P,max:N}))}),r=!1,Wt(),Fe())}function Et(){if(!t.length||!O)return;let o=Math.round(Math.max(100,O.getBoundingClientRect().width-b-(R?w+u:0))),c=!1;t.forEach(({uplot:x,histCanvas:T,histData:A})=>{o!==x.width&&(x.setSize({width:o,height:x.height}),c=!0,R&&T&&A&&Pe(T,A,x.height))}),c&&Dt()}function bt(o=!1){let c=null,x=0;if(o&&O&&(x=O.scrollTop),o&&U)c=[U[0],U[1]];else if(o&&t.length){let Tt=t[0].uplot;c=[Tt.scales.x.min,Tt.scales.x.max]}if(!B){Rn(o);return}let T=B.points;if(!T.length){Rn(o);return}let A=B?.sport,$=A?Ye[A.toLowerCase()]:null,D=$?.defaultPreset||"speed",N=jt.get().speed_mode||D,J=$?.paceUnit||"km",Z=[...l].filter(Tt=>T.some(rt=>rt[Ne[Tt].field]!=null));if(!Z.length){Rn(o),q&&(q.style.display="flex",q.innerHTML=` + no_sim +
No data for selected metrics
+ `);return}q&&(q.style.display="none");let X=O?O.getBoundingClientRect().width:800,et=Math.round(Math.max(100,X-b-(R?w+u:0))),tt=o&&s===B&&e===I&&n===N&&i===at&&t.length>0,C;if(tt)C=t[0].xData;else if(I==="distance")C=T.map(Tt=>(Tt.dist||0)/1e3);else{let Tt=T.find($t=>$t.time!=null)?.time||0,rt=0;C=T.map($t=>($t.time!=null&&(rt=($t.time-Tt)/1e3),rt))}let xt=new Map;if(tt){M&&(M.style.display="none");let Tt=new Set(Z);for(let rt of t){let $t=y.has(rt.metricKey),pt=S.has(rt.metricKey);Tt.has(rt.metricKey)&&rt.smoothed===$t&&rt.incZero===pt&&rt.w===et?xt.set(rt.metricKey,rt):(a.unsub(rt.uplot),rt.uplot.destroy(),rt.row.remove())}t=[]}else Rn(o);if(O){let Tt=new Array(Z.length).fill(null),rt=null;for(let $t=Z.length-1;$t>=0;$t--){Tt[$t]=rt;let pt=xt.get(Z[$t]);pt&&(rt=pt.row)}Z.forEach(($t,pt)=>{let yt=xt.get($t);if(yt){t.push(yt);let mt=yt.uplot,Kt=mt._strasse,Zt=r;r=!0,mt.batch(()=>{c&&(mt.scales.x.min!==c[0]||mt.scales.x.max!==c[1])?mt.setScale("x",{min:c[0],max:c[1]}):(Kt?.updateHeaderStats(mt.scales.x.min,mt.scales.x.max,mt),mt.redraw(!1))}),r=Zt;let Jt=null;if(h!=null&&yt.xData[h]!=null&&yt.yData[h]!=null&&(ye(yt.histData),yt.histData.min!=null&&yt.histData.max!=null)){let ce=yt.histData.max-yt.histData.min||1;Jt=Math.min(yt.histData.BINS-1,Math.floor((yt.yData[h]-yt.histData.min)/ce*yt.histData.BINS)),yt.hoveredHistY=yt.yData[h]}Pe(yt.histCanvas,yt.histData,130,Jt);return}let kt=Ne[$t],Vt=kt.transform,Ut=kt.fmt,ee=kt.fmtAxis,oe=kt.unit,ae=kt.label;$t==="speed"&&N==="pace"&&(ae="Pace",oe=`min/${J}`,Ut=mt=>On(mt),ee=mt=>On(mt),Vt=mt=>mt==null||mt<.1?null:(J==="km"?1e3:100)/mt,kt={...kt,label:ae,unit:oe,fmt:Ut,fmtAxis:ee,transform:Vt});let ie=kt.compute?kt.compute(T,al):al(T.map(mt=>{let Kt=mt[kt.field];return Kt!=null&&Vt?Vt(Kt):Kt}));y.has($t)&&(ie=En(ie,kt.smoothSigma||2)),vt($t,kt,C,ie,et,T,c,Tt[pt])})}s=B,e=I,n=N,i=at,rl(B),g!=null&&E!=null&&Wt(),ft(),at&&At(),o&&O&&x>0&&(O.scrollTop=x)}function vt(o,c,x,T,A,$,D=null,P=null){let N=B?.sport,Z=(N?Ye[N.toLowerCase()]:null)?.defaultPreset||"speed",et=jt.get().speed_mode||Z,tt=o==="elevation"?So($,20):null,C=document.createElement("div");C.className="chart-row",C.dataset.metric=o,C.style.setProperty("--chart-color",c.color);let xt=document.createElement("div");xt.className=`chart-row-header${R?" with-histogram":""}`;let Tt=document.createElement("div");Tt.className="chart-row-label-group",Tt.innerHTML=` + ${c.icon} + ${c.label} + `;let rt=document.createElement("button");rt.className=`icon-btn mini smooth-btn${y.has(o)?" active":""}`,rt.title="Toggle data smoothing",rt.innerHTML=`${y.has(o)?"blur_on":"blur_off"}`,rt.addEventListener("click",lt=>{lt.stopPropagation(),y.has(o)?y.delete(o):y.add(o),bt(!0)});let $t=document.createElement("button");$t.className=`map-color-btn icon-btn${at===o?" active":""}`,$t.dataset.metric=o,$t.title="Color map track by this metric",$t.innerHTML='colorize',$t.addEventListener("click",()=>st(o));let pt=null;if(o==="cadence"||o==="power"||o==="speed"){pt=document.createElement("button");let lt=S.has(o);pt.className=`icon-btn mini${lt?" active":""}`,pt.title=lt?"Currently INCLUDING 0 values":"Currently EXCLUDING 0 values",pt.innerHTML=`${lt?"exposure_zero":"mobile_off"}`,pt.addEventListener("click",Mt=>{Mt.stopPropagation(),S.has(o)?S.delete(o):S.add(o),bt(!0)})}let yt=document.createElement("div");yt.className="chart-stats-total";let kt=document.createElement("div");kt.className="chart-stats-selection";let Vt=document.createElement("div");Vt.className="chart-row-stats-container",Vt.append(yt,kt);let Ut=document.createElement("div");Ut.className="chart-row-actions",Ut.append(rt,$t);let ee=document.createElement("div");if(ee.className="chart-row-right-actions",o==="speed"){let lt=document.createElement("div");lt.className="seg-ctrl",lt.innerHTML=` + + + `,lt.addEventListener("click",Mt=>{let ne=Mt.target.closest(".seg-btn");if(!ne)return;let se=ne.dataset.mode;if(se==="speed"||se==="pace"){let Yt=se===Z;jt.patch({speed_mode:Yt?null:se}),bt(!0)}}),ee.append(lt)}pt&&ee.append(pt),xt.append(Tt,Ut,Vt,ee);let oe=document.createElement("div");oe.className="chart-row-body";let ae=document.createElement("div");ae.style.position="relative",oe.appendChild(ae);let ie=document.createElement("div");ie.className="fixed-x-label fixed-x-min";let mt=document.createElement("div");mt.className="fixed-x-label fixed-x-max",ae.append(ie,mt);let Kt=document.createElement("div");Kt.className=`hist-col${R?" visible":""}`;let Zt=document.createElement("canvas");Zt.className="hist-canvas",Kt.appendChild(Zt),oe.appendChild(Kt),C.append(xt,oe);let Jt=I==="distance";function ce(lt,Mt){if(lt==null)return"";if(Jt){let ne=Mt<1?3:Mt<5?2:Mt<20?1:0;return`${lt.toFixed(ne)} km`}else{let ne=Math.abs(lt),se=Math.floor(ne/3600),Yt=Math.floor(ne%3600/60),Me=Math.round(ne%60),ve=se>0?`${se}:`:"",Oe=se>0?String(Yt).padStart(2,"0"):String(Yt),Be=String(Me).padStart(2,"0");return`${lt<0?"-":""}${ve}${Oe}:${Be}`}}let pe=S.has(o)||o!=="power"&&o!=="cadence"&&o!=="speed",fe=1/0,Qt=-1/0,de=0,Te=0,De=1/0,Je=-1/0,Le=0,ln=0;for(let lt=0;ltQt&&(Qt=Mt),de+=Mt,Te++,(Mt!==0||pe)&&(MtJe&&(Je=Mt),Le+=Mt,ln++))}Te===0&&(fe=0,Qt=100);let Ps=Te?{min:fe,max:Qt,avg:de/Te}:null,ss=ln?{min:De,max:Je,avg:Le/ln}:null,ul=Math.abs(Qt-fe)*.1||1,ko=[fe-ul,Qt+ul],ml=!1;function Ls(lt,Mt,ne){let se=S.has(o)||o!=="power"&&o!=="cadence"&&o!=="speed",Yt=Mt-lt;ie.textContent=ce(lt,Yt),mt.textContent=ce(Mt,Yt);let Me=(ve,Oe,Be=!1)=>{let Re=Be&&se===pe?ss:tl(x,T,ve,Oe,se);if(!Re)return"";let an=` + align_flex_end${c.fmt(Re.min,!0)} ${c.unit} + align_center${c.fmt(Re.avg,!0)} ${c.unit} + align_flex_start${c.fmt(Re.max,!0)} ${c.unit} + `;if(o==="elevation"){let Ge=bo($,ve,Oe);an+=` + trending_up+${Math.round(Ge.gain)} m + trending_down-${Math.round(Ge.loss)} m + `}return an};ml||(ml=!0,yt.innerHTML=`all_inclusive${Me(x[0],x[x.length-1],!0)}`,yt.querySelector(".all-chip")?.addEventListener("click",ve=>{ve.stopPropagation(),_()})),g!==null&&E!==null?(kt.innerHTML=Me(g,E,!1),kt.style.display="flex",kt.style.setProperty("--sel-color",c.color)):kt.style.display="none"}let $o=!1,Co={width:A,height:130,padding:[4,0,0,0],cursor:{sync:{key:a.key},drag:{x:!0,y:!1,uni:16},focus:{prox:16},points:{size:7,stroke:"#0e0e10",width:2,fill:o==="elevation"?(lt,Mt)=>ts(tt?tt[lt.cursor.idx]:null):c.color},x:!1,y:!1},select:{show:!0,left:0,top:0,width:0,height:0},legend:{show:!1},scales:{x:{time:!1,range:(lt,Mt,ne)=>[Mt,ne],min:D?D[0]:void 0,max:D?D[1]:void 0},y:{range:()=>ko,dir:o==="speed"&&et==="pace"?-1:1}},axes:[{side:2,stroke:"#555564",grid:{stroke:"#2e2e34",width:1},ticks:{show:!0,stroke:"#2e2e34"},size:30,space:80,font:"10px system-ui, sans-serif",splits:(lt,Mt,ne,se)=>{let Yt=se-ne,ve=(lt.bbox.width>0?lt.bbox.width:lt.width)/80,Oe=Yt/ve,Be;Jt?Be=[.01,.02,.05,.1,.2,.25,.5,1,2,5,10,20,50,100,200,500,1e3,2e3,5e3,1e4,2e4,5e4,1e5,2e5,5e5,1e6]:Be=[1,2,5,10,15,30,60,120,300,600,900,1800,3600,7200,14400,28800,43200,86400,604800,2592e3,31536e3];let Re=Be[Be.length-1];for(let Ze=0;Ze=Oe){Re=Be[Ze];break}let an=[],Ge=Math.ceil(ne/Re)*Re,ms=Re<1?2:0;for(;Ge<=se;){let Ze=lt.valToPos(Ge,"x",!0);Ze>=lt.bbox.left+80&&Ze<=lt.bbox.left+lt.bbox.width-80&&an.push(Number(Ge.toFixed(ms))),Ge+=Re}return an},values:(lt,Mt)=>{let ne=(lt.scales.x?.max??1)-(lt.scales.x?.min??0);return Mt.map(se=>{if(se==null)return"";let Yt=lt.valToPos(se,"x",!0);return Ytlt.bbox.left+lt.bbox.width-80?null:ce(se,ne)})}},{side:3,stroke:"#555564",grid:{stroke:"#2e2e34",width:1},ticks:{stroke:"#2e2e34"},size:55,font:"10px system-ui, sans-serif",values:(lt,Mt)=>Mt.map(ne=>ne!=null?c.fmtAxis(ne):"")}],series:[{},{label:c.label,stroke:"rgba(0,0,0,0)",width:0,points:{show:!1},paths:()=>null}],hooks:{draw:[lt=>{let Mt=lt.bbox.top+lt.bbox.height+5;ie.style.top=`${Mt}px`,ie.style.left=`${lt.bbox.left}px`,mt.style.top=`${Mt}px`,mt.style.left=`${lt.bbox.left+lt.bbox.width}px`,mt.style.transform="translateX(-100%)"},lt=>{o==="gears"?dl(lt,x,T,c.color,1.5,o,null,$):o==="elevation"?vo(lt,x,T,tt):o==="gradient"?yo(lt,x,T):at===o?Eo(lt,x,T,$,o):dl(lt,x,T,c.color,1.5,o,null,$)},lt=>{let Mt=t.find(se=>se.uplot===lt);if(!Mt)return;let ne=lt._strasse;h!=null?xo(lt,x,T,c.color,c,$,o):(ne&&ne.pinYVal&&(ne.pinYVal.style.display="none"),Mt.pinnedHistY!=null&&wo(lt,x,T,Mt.pinnedHistY,c.color,c))},lt=>{let Mt=lt.cursor.idx,ne=h!=null,se=Mt!=null&<.cursor.left>=0;if(se&&h==null){let Yt=lt.valToPos(T[Mt],"y",!0);Math.abs(lt.cursor.top-Yt)>100&&(se=!1)}if(se&&Mt!==h&&ll(lt,$,Mt,le(c.color,.4),1),g!=null&&E!=null){let Yt=lt.ctx,Me=window.devicePixelRatio||1,ve=lt.bbox,Oe=lt.valToPos(g,"x",!0),Be=lt.valToPos(E,"x",!0);Yt.save(),Yt.beginPath(),Yt.lineWidth=3*Me,Yt.strokeStyle=c.color,Yt.moveTo(Oe,ve.top+ve.height),Yt.lineTo(Be,ve.top+ve.height),Yt.stroke();let Re=Math.abs(E-g),an=I==="distance"?`${Re.toFixed(2)} km`:he(Re),Ge=9*Me,ms=`bold ${Ge}px system-ui, sans-serif`;Yt.font!==ms&&(Yt.font=ms);let Ze=Yt.measureText(an).width,fl=Math.max(ve.left,Oe),gl=Math.min(ve.left+ve.width,Be);if(gl-fl>Ze+14*Me){let Lo=5*Me,Ao=2*Me,ps=Ze+Lo*2,fs=Ge+Ao*2,Is=(fl+gl)/2-ps/2,Hs=ve.top+ve.height-fs/2;Yt.beginPath(),Yt.roundRect?Yt.roundRect(Is,Hs,ps,fs,3*Me):Yt.rect(Is,Hs,ps,fs),Yt.fillStyle=c.color,Yt.fill(),Yt.fillStyle="#000",Yt.textAlign="center",Yt.textBaseline="middle",Yt.fillText(an,Is+ps/2,Hs+fs/2)}Yt.restore()}ne&&ol(lt,$,h,c.color,!0,.7),se&&Mt!==h&&ol(lt,$,Mt,c.color,!0,1)}],setCursor:[lt=>{if(r)return;let Mt=lt.cursor.idx;lt.redraw(!1),lt.cursor.left>=0?m=lt.posToVal(lt.cursor.left,"x"):m=null;let ne=lt._strasse,se=Mt!=null&&T[Mt]!=null;if(se&&h==null){let Yt=lt.valToPos(T[Mt],"y",!0);Math.abs(lt.cursor.top-Yt)>100&&(se=!1)}if(ne&&(se?el(lt,ne.curYVal,Mt,x,T,$,c,o):ne.curYVal.style.display="none"),d||(Mt!=null&&$[Mt]?G&&(h!=null||se)?G($[Mt]):G&&!se&&G(null):Mt===null&&G&&G(null)),Mt!=null&&T[Mt]!=null){let{min:Yt,max:Me}=Se;if(Yt!=null&&Me!=null){let ve=Me-Yt||1,Oe=Math.min(Se.BINS-1,Math.floor((T[Mt]-Yt)/ve*Se.BINS));on.hoveredHistY!==T[Mt]&&(on.hoveredHistY=T[Mt],Pe(Zt,Se,130,Oe))}}else on.hoveredHistY!==null&&(on.hoveredHistY=null,Pe(Zt,Se,130))}],setScale:[(lt,Mt)=>{if(Mt!=="x"||$o)return;let{min:ne,max:se}=lt.scales.x;U=[ne,se],Ls(ne,se,lt),!r&&(L||(Q=null),r=!0,t.forEach(({uplot:Yt})=>{Yt!==lt&&Yt.batch(()=>Yt.setScale("x",{min:ne,max:se}))}),r=!1,Dt(),Fe(),ot&&g!==null&&ot(g,E,I))}],setSelect:[lt=>{if(L)return;Q=null;let{left:Mt,width:ne}=lt.select;if(ne>0){let se=lt.posToVal(Mt,"x"),Yt=lt.posToVal(Mt+ne,"x");g=se,E=Yt,Ls(lt.scales.x.min,lt.scales.x.max,lt),Dt(),Fe(),ot&&ot(g,E,I),lt.setSelect({left:0,top:0,width:0,height:0},!1)}}]}},As=document.createElement("div");As.style.cssText="position:absolute;inset:0;pointer-events:none;overflow:visible;z-index:5";let ls=document.createElement("div");ls.className="sel-fill",ls.style.display="none";let os=document.createElement("div");os.className="sel-line sel-line-anchor",os.style.display="none";let is=document.createElement("div");is.className="sel-line-label sel-line-label-anchor",is.style.display="none";let qn=document.createElement("div");qn.className="sel-handle sel-handle-anchor",qn.style.display="none";let zn=document.createElement("div");zn.className="sel-graph-marker",zn.innerHTML=`play_circle`,zn.style.display="none";let as=document.createElement("div");as.className="sel-line sel-line-end",as.style.display="none";let rs=document.createElement("div");rs.className="sel-line-label sel-line-label-end",rs.style.display="none";let Un=document.createElement("div");Un.className="sel-handle sel-handle-end",Un.style.display="none";let Vn=document.createElement("div");Vn.className="sel-graph-marker",Vn.innerHTML=`stop_circle`,Vn.style.display="none";let cs=document.createElement("div");cs.className="cur-y-val",cs.style.cssText=`color:${c.color};display:none`;let ds=document.createElement("div");ds.className="cur-y-val pin",ds.style.cssText=`color:${c.color};display:none`,As.append(ls,os,is,qn,zn,as,rs,Un,Vn,cs,ds);let Se={yData:T,xData:x,def:c,BINS:24,min:Te?fe:void 0,max:Te?Qt:void 0,fullStats:ss,fullStatsAll:Ps},on={uplot:null,row:C,xData:x,yData:T,def:c,metricKey:o,statsTotalEl:yt,statsSelEl:kt,histCol:Kt,histCanvas:Zt,histData:Se,hoveredHistY:null,pinnedHistY:null,smoothed:y.has(o),incZero:S.has(o),w:A};Se.plot=on;let Po=r;r=!0;let tn=new Sl(Co,[x,T],(lt,Mt)=>{on.uplot=lt,t.push(on),ae.appendChild(lt.root),lt.over.appendChild(As),lt._strasse={selFill:ls,anchorLine:os,anchorLabel:is,anchorHandle:qn,anchorMarker:zn,endLine:as,endLabel:rs,endHandle:Un,endMarker:Vn,curYVal:cs,pinYVal:ds,updateHeaderStats:Ls},lt.batch(()=>{Mt()})});r=Po,te(qn,tn,x,!0),te(Un,tn,x,!1);let us=null;tn.over.addEventListener("mousedown",lt=>{if(lt.button!==0)return;H=!1;let Mt=tn.over.getBoundingClientRect();us=lt.clientX-Mt.left}),tn.over.addEventListener("mousemove",lt=>{if(us!==null){let Mt=tn.over.getBoundingClientRect();Math.abs(lt.clientX-Mt.left-us)>5&&(H=!0)}}),tn.over.addEventListener("mouseup",()=>{us=null}),tn.over.addEventListener("click",()=>{if(H)return;let lt=tn.cursor.idx;lt!=null&&$[lt]&&(h=lt,t.forEach(({uplot:Mt})=>Mt.redraw(!1)),_t&&_t($[lt],lt),ct&&ct(lt))}),C.addEventListener("dblclick",_);let pl=null;if(h!=null&&x[h]!=null&&T[h]!=null&&(ye(Se),Se.min!=null&&Se.max!=null)){let lt=Se.max-Se.min||1;pl=Math.min(Se.BINS-1,Math.floor((T[h]-Se.min)/lt*Se.BINS)),on.hoveredHistY=T[h]}Pe(Zt,Se,130,pl),_n(Zt,Se),O&&(P?O.insertBefore(C,P):O.appendChild(C))}function Dt(){Wt(),t.forEach(({uplot:o})=>o.batch(()=>o.redraw(!1)))}function Wt(){t.forEach(({uplot:o,def:c})=>{let x=o._strasse;if(!x)return;let{selFill:T,anchorLine:A,anchorLabel:$,anchorHandle:D,anchorMarker:P,endLine:N,endLabel:J,endHandle:Z,endMarker:X}=x;if(g==null||E==null){[T,A,$,D,P,N,J,Z,X].forEach(Ut=>{Ut.style.display="none"});return}let et=o.valToPos(g,"x"),tt=o.valToPos(E,"x"),C=window.devicePixelRatio||1,xt=o.bbox,Tt=xt.width/C,rt=xt.height/C,$t=(xt.top+xt.height)/C,pt=et>=0&&et<=Tt,yt=tt>=0&&tt<=Tt,kt=Math.max(0,et),Vt=Math.min(Tt,tt);if(Vt>kt?(T.style.left=`${kt}px`,T.style.width=`${Vt-kt}px`,T.style.top="0",T.style.height=`${rt}px`,T.style.backgroundColor=le(c.color,.07),T.style.display=""):T.style.display="none",pt){A.style.left=`${et}px`,A.style.height=`${rt}px`,A.style.borderLeftColor=c.color,A.style.display="";let Ut=Ot(g);$.textContent=Ut;let ee=Ut.length*7+10;$.style.left=et+ee+4Ut.style.display="none");if(yt){N.style.left=`${tt}px`,N.style.height=`${rt}px`,N.style.borderLeftColor=c.color,N.style.display="";let Ut=Ot(E);J.textContent=Ut;let ee=Ut.length*7+10;J.style.left=tt-ee-3>0?`${tt-ee-3}px`:`${tt+3}px`,J.style.display="",Z.style.left=`${tt}px`,Z.style.backgroundColor=c.color,Z.style.display="",X.style.left=`${tt}px`,X.style.top=`${$t}px`;let oe=X.querySelector(".material-symbols-rounded");oe&&(oe.style.color=c.color),X.style.display=""}else[N,J,Z,X].forEach(Ut=>Ut.style.display="none")})}function Ot(o){return I==="distance"?`${o.toFixed(2)} km`:he(o)}function te(o,c,x,T){o.style.pointerEvents="auto",o.addEventListener("mousedown",A=>{A.stopPropagation(),A.preventDefault();let $=A.clientX,D=T?g:E,P=c.over.offsetWidth,N=c.scales.x.min,J=c.scales.x.max,Z=P/(J-N),X=[x[0],x[x.length-1]];document.body.style.cursor="ew-resize",H=!0;function et(C){let xt=C.clientX-$,Tt=(D||0)+xt/Z;Tt=Math.max(X[0],Math.min(X[1],Tt)),T?g=Math.min(Tt,(E||X[1])-.001):E=Math.max(Tt,(g||X[0])+.001),t.forEach(rt=>rt.uplot._strasse?.updateHeaderStats(rt.uplot.scales.x.min,rt.uplot.scales.x.max)),Dt(),Fe(),ot&&ot(g,E,I)}function tt(){document.body.style.cursor="",H=!1,document.removeEventListener("mousemove",et),document.removeEventListener("mouseup",tt)}document.addEventListener("mousemove",et),document.addEventListener("mouseup",tt)})}function ye(o){let{yData:c,xData:x,BINS:T}=o;if(o._lastYData===c&&o.bins)return;o._lastYData=c;let A=o.min,$=o.max;if(A==null||$==null){let X=1/0,et=-1/0,tt=0;for(let C=0;Cet&&(et=xt),tt++)}if(!tt)return;A=X,$=et,o.min=A,o.max=$}let D=new Array(T).fill(0),P=new Array(T).fill(0),N=$-A||1,J=o.plot?.metricKey??Object.keys(Ne).find(X=>Ne[X]===o.def),Z=!J||S.has(J)||J!=="power"&&J!=="cadence"&&J!=="speed";for(let X=0;X0&&x[X-1]!=null?x[X-1]:x[X],xt=X{Pe(o,c,x.height)})}function Pe(o,c,x,T=null){let{def:A,BINS:$}=c;ye(c);let{bins:D,min:P,max:N}=c;if(!D||P==null||N==null)return;let J=g!=null&&E!=null;if(J&&(c._lastSelRange?.[0]!==g||c._lastSelRange?.[1]!==E)){c._lastSelRange=[g,E];let mt=new Array($).fill(0),Kt=new Array($).fill(0),{yData:Zt,xData:Jt}=c,ce=N-P||1,pe=c.plot?.metricKey??Object.keys(Ne).find(Qt=>Ne[Qt]===c.def),fe=!pe||S.has(pe)||pe!=="power"&&pe!=="cadence"&&pe!=="speed";for(let Qt=0;QtE)continue;let De=Math.min($-1,Math.floor((de-P)/ce*$));mt[De]++;let Je=Qt>0&&Jt[Qt-1]!=null?Jt[Qt-1]:Jt[Qt],Le=QtA.fmtAxis(mt),rt="9px system-ui, sans-serif";C.font!==rt&&(C.font=rt);let pt={t:4,r:0,b:30,l:Math.ceil(Math.max(C.measureText(`${Tt(N)} ${A.unit}`).width,C.measureText(`${Tt(P)} ${A.unit}`).width))+6};c.padL=pt.l;let yt=et-pt.l-pt.r,kt=tt-pt.t-pt.b,Vt=mt=>pt.t+($-1-mt)*(kt/$),Ut=kt/$-1,ee=J?.15:.6;C.fillStyle=le(A.color,ee);for(let mt=0;mt<$;mt++){if(!D[mt])continue;let Kt=D[mt]/xt*yt;C.fillRect(pt.l,Vt(mt),Kt,Ut)}if(Z){C.fillStyle=le(A.color,.85);for(let mt=0;mt<$;mt++){if(!Z[mt])continue;let Kt=Z[mt]/xt*yt;C.fillRect(pt.l,Vt(mt),Kt,Ut)}C.fillStyle=A.color,C.fillRect(pt.l,pt.t,2,kt)}let oe=D.reduce((mt,Kt)=>mt+Kt,0);if(oe>0){let mt=[{x:pt.l,y:pt.t+kt}],Kt=0;for(let Zt=0;Zt<$;Zt++)Kt+=D[Zt],mt.push({x:pt.l+Kt/oe*yt,y:Vt(Zt)});C.beginPath(),C.setLineDash([]),C.strokeStyle=A.color,C.lineWidth=.75*X,C.lineJoin="round",C.moveTo(mt[0].x,mt[0].y);for(let Zt=1;ZtZt+Jt+2&&mt0){let mt=0;for(let de=0;de{let A=Ks(o,c,T);if(A!=null&&x){let{min:$,max:D,BINS:P}=c,N=D-$||1;x.pinnedHistY=$+(A+.5)*(N/P),t.forEach(({uplot:J})=>J.redraw(!1))}}),o.addEventListener("mousemove",T=>{p=T,Js(T,o,c)}),o.addEventListener("mouseenter",()=>{j=o,k=c}),o.addEventListener("mouseleave",()=>{j===o&&(j=null,k=null),M&&(M.style.display="none");let T=document.getElementById("hist-line");T&&(T.style.display="none"),x&&(x.hoveredHistY=null,Pe(o,c,o.height/(window.devicePixelRatio||1)))})}function Ks(o,c,x){let{bins:T,BINS:A}=c;if(!T)return null;let $=o.getBoundingClientRect(),D=x.clientY-$.top,P={t:4,b:30},N=$.height-P.t-P.b,J=Math.floor((D-P.t)/N*A),Z=A-1-J;return J<0||J>=A?null:Z}function Js(o,c,x){if(!M)return;let{bins:T,binAccum:A,min:$,max:D,BINS:P,def:N,plot:J}=x;if(!T||!A||$==null||D==null)return;let Z=document.getElementById("hist-line"),X=Ks(c,x,o);if(X==null||!T[X]){M.style.display="none",Z&&(Z.style.display="none"),J&&(J.hoveredHistY=null,Pe(c,x,c.height/(window.devicePixelRatio||1)));return}if(J){let Jt=D-$||1;J.hoveredHistY=$+(X+.5)*(Jt/P),Pe(c,x,c.height/(window.devicePixelRatio||1),X)}let et=T[X],tt=T.reduce((Jt,ce)=>Jt+ce,0),C=et/tt*100,xt=D-$||1,Tt=$+X*(xt/P),rt=$+(X+1)*(xt/P),$t=`${N.fmt(Tt,!0)} \u2013 ${N.fmt(rt,!0)} ${N.unit}`,pt="",yt=(Tt+rt)/2,kt=Object.keys(Ne).find(Jt=>Ne[Jt]===N),Vt=[];if(kt==="power"?Vt=kn.getPowerZones():kt==="hr"&&(Vt=kn.getHRZones()),Vt.length>0){let Jt=Vt.findIndex(pe=>yt>=pe.min&&yt=Vt[Vt.length-1].min?Vt[Vt.length-1]:null;if(ce){let pe=Jt!==-1?Jt:Vt.length-1;pt=` +
+ Z${pe+1} + \xB7 + ${ce.name} +
+ `}}let Ut=I==="distance"?`${A[X].toFixed(2)} km`:he(A[X]),ee=A[X]/A.reduce((Jt,ce)=>Jt+ce,0)*100,oe=` +
${$t}
+ ${pt} + `;if(Ut&&(oe+=`
+ ${I==="distance"?"Dist":"Time"} + ${Ut} + ${ee.toFixed(1)}% +
`),x.selBins&&x.selBinAccum){let Jt=I==="distance"?`${x.selBinAccum[X].toFixed(2)} km`:he(x.selBinAccum[X]),ce=x.selBinAccum[X]/x.selBinAccum.reduce((pe,fe)=>pe+fe,0)*100;oe+=` +
+ Sel ${I==="distance"?"Dist":"Time"} + ${Jt} + ${ce.toFixed(1)}% +
+ `}M.innerHTML=oe,M.style.display="block";let ae=M.offsetHeight,ie=M.offsetWidth,mt=c.getBoundingClientRect(),Kt=mt.height-4-30,Zt=mt.top+4+(P-1-X+.5)*(Kt/P);M.style.left=`${mt.left-ie-12}px`,M.style.top=`${Zt-ae/2}px`,Z&&(Z.style.display="block",Z.style.top=`${Zt}px`,Z.style.left="0px",Z.style.width=`${mt.left}px`)}function Rn(o=!1){M&&(M.style.display="none"),t.forEach(({uplot:c,row:x})=>{a.unsub(c),c.destroy(),x.remove()}),t=[],s=null,o||(g=null,E=null),H=!1}function tl(o,c,x,T,A=!0){let $=1/0,D=-1/0,P=0,N=0;for(let J=0;JT)continue;let Z=c[J];Z==null||!isFinite(Z)||Z===0&&!A||(Z<$&&($=Z),Z>D&&(D=Z),P+=Z,N++)}return N?{min:$,max:D,avg:P/N}:null}function bo(o,c,x){let T=0,A=0,$=null,D=I==="distance",P=o[0].time||0;for(let N=0;Nx)&&J.ele!=null){if($!=null){let X=J.ele-$;X>0?T+=X:X<0&&(A+=Math.abs(X))}$=J.ele}}return{gain:T,loss:A}}function el(o,c,x,T,A,$,D,P){if(x==null||A[x]==null){c.style.display="none";return}let N=o.valToPos(T[x],"x",!1),J=o.valToPos(A[x],"y",!1),Z="#888896",X=0;if(x>0&&$[x]&&$[x-1]){let rt=$[x-1],$t=$[x],pt=($t.dist||0)-(rt.dist||0);pt>.1&&$t.ele!=null&&rt.ele!=null&&(X=($t.ele-rt.ele)/pt*100,Z=ts(X))}let et=P!=="elevation"?`${D.fmt(A[x],!1)} ${D.unit}`:`${D.fmt(A[x],!1)} ${D.unit} \u2220 ${Math.abs(X).toFixed(1)}%`;if(P==="gearRear"&&$[x].gearRearTooth!=null)et=`${$[x].gearRearTooth}T (pos ${A[x]})`;else if(P==="gearFront"&&$[x].gearFrontTooth!=null)et=`${$[x].gearFrontTooth}T (pos ${A[x]})`;else if(P==="gears"){let rt=$[x],$t=rt.gearFrontTooth!=null?`${rt.gearFrontTooth}T`:rt.gearFront!=null?`pos ${rt.gearFront}`:"",pt=rt.gearRearTooth!=null?`${rt.gearRearTooth}T`:rt.gearRear!=null?`pos ${rt.gearRear}`:"";($t||pt)&&(et+=` (${$t} / ${pt})`)}c.innerHTML!==et&&(c.innerHTML=et);let tt=`${D.color}44`;c.style.color!==D.color&&(c.style.color=D.color),c.style.borderColor!==tt&&(c.style.borderColor=tt);let C=`${N}px`,xt=`${J}px`,Tt="translate(6px, -50%)";c.style.left!==C&&(c.style.left=C),c.style.top!==xt&&(c.style.top=xt),c.style.transform!==Tt&&(c.style.transform=Tt),c.style.display!==""&&(c.style.display="")}let nl=new Map;function sl(o){let c=nl.get(o);return c||(c=le(ts(o),.8),nl.set(o,c)),c}function vo(o,c,x,T){let A=o.ctx,$=o.bbox,D=window.devicePixelRatio||1,P=o.scales.x?.min??-1/0,N=o.scales.x?.max??1/0,J=o.scales.y?.min??0,Z=o.scales.y?.max??100,X=$.width/(N-P||1),et=$.height/(Z-J||1),tt=$.top+$.height,C=o.series[0]?.idxs,xt=C?Math.max(1,C[0]):1,Tt=C?Math.min(c.length-1,C[1]+1):c.length-1,rt=Math.max(1,Math.floor((Tt-xt)/250));A.save(),A.lineWidth=1.5*D;let $t=new Map,pt=xt-1;for(let yt=xt;yt<=Tt;yt+=rt){let kt=c[yt],Vt=c[pt];if(pt=yt,kt==null||x[yt]==null||Vt==null||x[yt-rt]==null||ktN)continue;let Ut=$.left+(Vt-P)*X,ee=tt-(x[yt-rt]-J)*et,oe=$.left+(kt-P)*X,ae=tt-(x[yt]-J)*et,ie=T&&T[yt]!=null?Math.round(T[yt]/2)*2:null,mt=sl(ie),Kt=$t.get(mt);Kt||(Kt=[],$t.set(mt,Kt)),Kt.push(Ut,ee,oe,ae)}for(let[yt,kt]of $t){A.beginPath(),A.strokeStyle=yt;for(let Vt=0;VtP)continue;let Vt=A.left+(kt-D)*Z,Ut=et-(x[pt-Tt]-N)*X,ee=A.left+(yt-D)*Z,oe=et-(x[pt]-N)*X,ae=Math.round(x[pt]/2)*2,ie=sl(ae),mt=rt.get(ie);mt||(mt=[],rt.set(ie,mt)),mt.push(Vt,Ut,ee,oe)}for(let[pt,yt]of rt){T.beginPath(),T.strokeStyle=pt;for(let kt=0;kt{let kt=`bold ${10*D}px system-ui, sans-serif`;N.font!==kt&&(N.font=kt);let Ut=N.measureText(rt).width+10*D,ee=16*D,oe=$t-Ut/2,ae=Math.max(P.left,Math.min(P.left+P.width-Ut,oe)),ie=pt-ee/2;N.save(),N.globalAlpha=$,N.setLineDash([]),N.beginPath(),N.roundRect?N.roundRect(ae,ie,Ut,ee,3*D):N.rect(ae,ie,Ut,ee),N.fillStyle="rgba(14,14,16,0.92)",N.fill(),N.strokeStyle=le(T,.27),N.lineWidth=1*D,N.stroke(),N.fillStyle=T,N.textAlign="center",N.textBaseline="middle",N.fillText(rt,ae+Ut/2,ie+ee/2+.5*D),N.restore()};A||ll(o,c,x,T,$);let Tt=16*D;xt(C,et,P.top+Tt/2,!0),xt(tt,et,P.top+P.height+15*D,!1)}function xo(o,c,x,T,A,$,D){if(h==null)return;let P=c[h],N=x[h];if(P==null||N==null)return;let J=o.valToPos(P,"x",!0),Z=o.valToPos(N,"y",!0),X=o.bbox,et=o.ctx,tt=window.devicePixelRatio||1;et.save(),et.beginPath(),et.strokeStyle=T,et.lineWidth=1.5*tt,et.moveTo(J,X.top),et.lineTo(J,X.top+X.height),et.stroke(),et.beginPath(),et.setLineDash([3*tt,3*tt]),et.strokeStyle=le(T,.5),et.lineWidth=1*tt,et.moveTo(X.left,Z),et.lineTo(J,Z),et.stroke(),et.beginPath(),et.setLineDash([]),et.arc(J,Z,5*tt,0,Math.PI*2),et.fillStyle="#0e0e10",et.fill(),et.strokeStyle=T,et.lineWidth=2.5*tt,et.stroke(),et.restore();let C=o._strasse;C&&C.pinYVal&&el(o,C.pinYVal,h,c,x,$,A,D)}function wo(o,c,x,T,A,$){let D=o.bbox,P=o.ctx,N=window.devicePixelRatio||1,J=o.valToPos(T,"y",!0),Z=Math.max(D.top,Math.min(D.top+D.height,J));P.save(),P.beginPath(),P.setLineDash([4*N,4*N]),P.strokeStyle=le(A,.4),P.lineWidth=1*N,P.moveTo(D.left,Z),P.lineTo(D.left+D.width,Z),P.stroke(),To(o,c,x,Z,A);let X=$.fmt(T,!0),et=`bold ${10*N}px system-ui, sans-serif`;P.font!==et&&(P.font=et);let C=P.measureText(X).width+8*N,xt=16*N,Tt=D.left-C-2*N,rt=Z-xt/2;P.beginPath(),P.setLineDash([]),P.roundRect?P.roundRect(Tt,rt,C,xt,3*N):P.rect(Tt,rt,C,xt),P.fillStyle="rgba(14,14,16,0.95)",P.fill(),P.strokeStyle=A,P.lineWidth=1*N,P.stroke(),P.fillStyle="#fff",P.textAlign="center",P.textBaseline="middle",P.fillText(X,Tt+C/2,rt+xt/2+.5*N),P.restore()}function To(o,c,x,T,A){let $=o.ctx,D=o.bbox,P=o.scales.x?.min??-1/0,N=o.scales.x?.max??1/0,J=o.scales.y?.min??0,Z=o.scales.y?.max??100,X=D.width/(N-P||1),et=D.height/(Z-J||1),tt=o.scales.y?.dir??1,C=tt===-1?D.top:D.top+D.height,xt=tt===-1?et:-et,Tt=o.series[0]?.idxs,rt=Tt?Math.max(0,Tt[0]-1):0,$t=Tt?Math.min(c.length-1,Tt[1]+1):c.length-1,pt=Math.max(1,Math.floor(($t-rt)/250));$.save(),$.beginPath(),$.rect(D.left,D.top,D.width,D.height),$.clip(),$.fillStyle=le(A,.2),$.beginPath();let yt=!1,kt=!1,Vt=0;for(let Ut=rt;Ut<=$t;Ut+=pt){let ee=c[Ut],oe=x[Ut];if(ee==null||oe==null||eeN){yt&&($.lineTo(Vt,T),$.closePath(),yt=!1);continue}let ae=D.left+(ee-P)*X,ie=C+(oe-J)*xt;Vt=ae,ie>16,D=T>>8&255,P=T&255,N=A>>16,J=A>>8&255,Z=A&255,X=$+(N-$)*x,et=D+(J-D)*x,tt=P+(Z-P)*x;return`#${((1<<24)+(Math.round(X)<<16)+(Math.round(et)<<8)+Math.round(tt)).toString(16).slice(1)}`}function ts(o){if(o==null)return"#888896";let c=Math.abs(o),x=Math.min(1,c/8);return o>0?Ve("#A8C8A0","#d73027",x):Ve("#A8C8A0","#4575b4",x)}function il(o){if(o==null)return"#888896";let c=o*3.6;if(c<=5)return"#4575b4";if(c>=60)return"#d73027";if(c<=45){let x=(c-5)/40;return x<=.25?Ve("#4575b4","#91bfdb",x/.25):x<=.5?Ve("#91bfdb","#abdda4",(x-.25)/.25):x<=.75?Ve("#abdda4","#fee08b",(x-.5)/.25):Ve("#fee08b","#fc8d59",(x-.75)/.25)}else{let x=(c-45)/15;return Ve("#fc8d59","#d73027",x)}}function So(o,c){let x=new Array(o.length).fill(null);for(let T=0;T0&&(A.dist||0)-(o[$].dist||0)1&&o[D].ele!=null&&o[$].ele!=null&&(x[T]=(o[D].ele-o[$].ele)/P*100)}return x}function al(o){let c=[...o];for(let x=0;x=0&&c[T]===null;)T--;let A=x+1;for(;A=0&&A=0?c[x]=c[T]:A{let J=document.querySelector(`#${P} .stat-value`);J&&(J.textContent=N)};x("stat-distance",c.totalDist!=null?`${(c.totalDist/1e3).toFixed(1)} km`:"\u2014"),x("stat-duration",c.duration!=null?he(Math.floor(c.duration/1e3)):"\u2014"),x("stat-elevation",c.elevGain!=null?`${Math.round(c.elevGain)} m`:"\u2014");let T=P=>{let N=t.find(X=>X.metricKey===P);if(!N)return P==="speed"?c.avgSpeed??null:P==="power"?c.avgPower??null:P==="cadence"?c.avgCadence??null:null;if(N.histData.fullStats!==void 0)return N.histData.fullStats?N.histData.fullStats.avg:null;let J=S.has(P)||P!=="power"&&P!=="cadence"&&P!=="speed",Z=N.yData.filter(X=>X!=null&&(J||X!==0));return Z.length?Z.reduce((X,et)=>X+et,0)/Z.length:null},A=T("speed");x("stat-avg-speed",A!=null?`${(A*3.6).toFixed(1)} km/h`:"\u2014");let $=T("power");x("stat-avg-power",$!=null?`${Math.round($)} W`:"\u2014");let D=T("cadence");x("stat-avg-cad",D!=null?`${Math.round(D)} rpm`:"\u2014"),x("stat-avg-hr",c.avgHR!=null?`${c.avgHR} bpm`:"\u2014"),x("stat-avg-temp",c.avgTemp!=null?`${c.avgTemp.toFixed(1)} \xB0C`:"\u2014")}let yn=16384,xn=new Float64Array(yn),Mn=new Float64Array(yn),es=new Float64Array(yn),Nn=new Float64Array(yn),Cs=new Float64Array(yn),ns=new Float64Array(yn),Wn=new Float64Array(64),cl=[3,5,8,10,12,15,20,25,30,40,45,60,75,90,120,150,180,240,300,360];function Mo(o,c,x){if(!o.length)return 0;Wn.fill(0);let T=0;for(let A=0;A=Tt)continue;let pt=1/$t,yt=0,kt=0,Vt=0;for(let Ut=C;Ut=$t){yt-=ns[Ut-$t],kt-=Nn[Ut-$t];let ee=(yt+kt*.05)*pt;ee>Vt&&(Vt=ee)}T+=Vt*1e-4}for(let rt=C;rt0&&(Wn[$t-1]+=.5),$t<63&&(Wn[$t+1]+=.5)}}}}for(let A=0;A<64;A++)T+=Wn[A]*1e-5;return O&&(O.dataset.intervalScore=T.toFixed(2)),T}return{init:Ct,loadTrack:W,clear:F,resetState:it,toggleMetric:K,setXAxis:Ht,toggleStats:dt,setSelectionStats:Ft,clearSelectionStats:It,restoreSelection:Nt,resetZoom:z,cancelSelection:_,analyzeTelemetryIntervals:Mo,setCursorAt:(o,c=!1)=>{if(h=o,ct&&ct(o),c){G&&B?.points?.[o]&&G(B.points[o]);return}d=!0,t.forEach(({uplot:x,yData:T,xData:A})=>{A[o]!=null&&T[o]!=null&&x.batch(()=>{x.setCursor({left:x.valToPos(A[o],"x"),top:x.valToPos(T[o],"y")},!0,!1)})}),d=!1,G&&B?.points?.[o]&&G(B.points[o])},setHoverAt:o=>{d=!0,t.forEach(({uplot:c,yData:x,xData:T})=>{o===null?c.batch(()=>{c.setCursor({left:-10,top:-10},!0,!1)}):T[o]!=null&&x[o]!=null&&c.batch(()=>{c.setCursor({left:c.valToPos(T[o],"x"),top:c.valToPos(x[o],"y")},!0,!1)})}),d=!1,G&&G(o!=null&&B?.points?.[o]?B.points[o]:null)},clearPinnedDot:()=>{h=null,ct&&ct(null),t.forEach(({uplot:o})=>o.redraw(!1))},resize:Et,METRICS:Ne,isDragging:()=>H,setMapColorChangeCb:Lt,toggleMapColor:st,getMapColorMetric:()=>at,getActiveMetrics:()=>l,getAvailableMetrics:()=>f,setActiveMetrics:ut,setSmooth:ht,setZeroInclusion:Pt,updateMetricOrder:o=>{let c=new Set;o.forEach(x=>{l.has(x)&&c.add(x)}),l.forEach(x=>{c.has(x)||c.add(x)}),l=c,B&&bt(!0)},getXAxis:()=>I};function Eo(o,c,x,T,A){let $=o.ctx,D=o.bbox,P=window.devicePixelRatio||1,N=zt(A,x);$.save(),$.beginPath(),$.rect(D.left,D.top,D.width,D.height),$.clip();let J=A==="gearRear"||A==="gearFront";for(let Z=1;Ztt.time!=null)?.time||0,X=x.field;T.beginPath(),T.strokeStyle="rgba(255, 255, 255, 0.04)",T.lineWidth=1*$;let et=!0;for(let tt=0;tt0){let yt=J[tt-1];(C.time&&yt.time?C.time-yt.time:0)>D&&(et=!0)}let Tt=x.transform?x.transform(xt):xt;if(Tt==null){et=!0;continue}let rt=I==="distance"?(C.dist||0)/1e3:(C.time-Z)/1e3;if(rto.scales.x.max+10){et=!0;continue}let $t=o.valToPos(rt,"x",!0),pt=o.valToPos(Tt,"y",!0);if(et)T.moveTo($t,pt),et=!1;else{if(P){let yt=J[tt-1],kt=I==="distance"?(yt.dist||0)/1e3:(yt.time-Z)/1e3;T.lineTo(o.valToPos(kt,"x",!0),pt)}T.lineTo($t,pt)}}T.stroke()}T.restore()}function dl(o,c,x,T,A,$,D=null,P=[]){let N=o.ctx,J=o.bbox,Z=window.devicePixelRatio||1,X=6e4;N.save(),N.beginPath(),N.strokeStyle=le(T,.8),N.lineWidth=A*Z;let et=$==="gearRear"||$==="gearFront",tt=!0,C=null,xt=1,Tt=1;if($==="gears"&&P.length>0){let Qt=1/0,de=-1/0;for(let Te=0;Tede&&(de=De))}Qt<=de&&(xt=Qt,Tt=de)}let rt=D?D[0]:o.scales.x?.min??-1/0,$t=D?D[1]:o.scales.x?.max??1/0,pt=o.scales.x?.min??-1/0,yt=o.scales.x?.max??1/0,kt=o.scales.y?.min??0,Vt=o.scales.y?.max??100,Ut=J.width/(yt-pt||1),ee=J.height/(Vt-kt||1),oe=o.scales.y?.dir??1,ae=oe===-1?J.top:J.top+J.height,ie=oe===-1?ee:-ee,mt=D?null:o.series[0]?.idxs,Kt=mt?Math.max(0,mt[0]-1):0,Zt=mt?Math.min(c.length-1,mt[1]+1):c.length-1,Jt=et||$==="gears"?1:Math.max(1,Math.floor((Zt-Kt)/250)),ce=I==="time",pe=X/1e3,fe=Kt;for(let Qt=Kt;Qt<=Zt;Qt+=Jt){let de=c[Qt],Te=x[Qt];if(de==null||Te==null){tt=!0;continue}if($==="gears"&&P[Qt]){let Le=P[Qt].gearFrontTooth??P[Qt].gearFront;if(Le!=null&&Le!==C){if(!tt&&Qt>0){N.stroke(),N.beginPath();let Ps=J.left+(c[fe]-pt)*Ut,ss=ae+(x[fe]-kt)*ie;N.moveTo(Ps,ss)}let ln=T;Tt!==xt&&Le!=null&&(Le===xt?ln="#FFC400":Le===Tt?ln="#FF8C00":ln="#FFA500"),N.strokeStyle=le(ln,.8),C=Le}}if(Qt>Kt&&ce){let Le=c[fe];Le!=null&&de-Le>pe&&(tt=!0)}if(de$t){tt=!0;continue}let De=J.left+(de-pt)*Ut,Je=ae+(Te-kt)*ie;if(tt)N.moveTo(De,Je),tt=!1;else{if(et){let Le=c[fe];N.lineTo(J.left+(Le-pt)*Ut,Je)}N.lineTo(De,Je)}fe=Qt}N.stroke(),N.restore()}})();var Wo={"Heart Rate":Rt.hr.icon,Cadence:Rt.cadence.icon,Power:Rt.power.icon,Temperature:Rt.temperature.icon,Shifting:Rt.gears.icon,Battery:Rt.battery.icon},$n=()=>{},El=()=>{};function Ds(t,s){$n=t,El=s}function kl(){let t=document.getElementById("details-view");t&&(t.innerHTML=` +
+ no_sim +
Select a track for details
+
+ `)}var cn=16384,Ue=[new Float64Array(cn),new Float64Array(cn),new Float64Array(cn),new Float64Array(cn),new Float64Array(cn),new Float64Array(cn),new Float64Array(cn)],Ml=[3,5,10,15,20,30,45,60,90];function qo(t,s){let e=t.points,n=Math.min(e.length,cn),i=0;for(let H=0;H=n)continue;let h=1/L,m=Math.max(1,L>>1);for(let p=0;p<7;p++){let k=Ue[p],j=0,U=0,Q=0,Y=0,G=1/(10+p*15+H*2);for(let ot=0;ot=L){let ct=k[ot-L];j-=ct,U-=ct*ct;let w=j*h,b=Math.max(0,U*h-w*w),u=k[ot-m],R=_t-k[ot-1],M=R*R*G,O=H<4?Math.exp(-M):1/(1+M);Y+=R*R*O;let q=1/(1+b*1e-4);Q+=(_t-w)*(u-w)*q}}i+=(Q+Y*.001)/Math.max(1,n)}}let a=t.stats.sections||re.analyzeTrackSections(e),r=[];for(let H=0;HL[1].secs-H[1].secs).slice(0,8).map(([H,L])=>({combo:H,ratio:(L.front/Math.max(1,L.rear)).toFixed(2),seconds:L.secs,pct:Number((L.secs/Math.max(1,n)*100).toFixed(1)),avgPower:Math.round(L.pwrSum/Math.max(1,L.secs)),avgCadence:Math.round(L.cadSum/Math.max(1,L.secs))})),f=s.length>0?s:[t],y=1,S=1,I=1,B=0,v=0;for(let H=0;Ht.stats.totalDist&&y++,(L.avgPower||0)>(t.stats.avgPower||0)&&S++,L.elevGain>t.stats.elevGain&&I++}let g=Math.round(B/Math.max(1,f.length)),E=(v/Math.max(1,f.length)/1e3).toFixed(1);return{stabilityAccum:i,splitRows:r.slice(0,12),gearMatrix:l,peerSummary:{cohortSize:f.length,distRank:y,pwrRank:S,elevRank:I,cohortAvgPower:g,cohortAvgDistKm:E}}}function Ee(t,s=[],e=[],n){let i=document.getElementById("details-view");if(!i)return;if(!t){i.innerHTML="";return}let a=t;if(n&&n.length===2){let h=t.points[0]?.time||0,m=h+n[0]*1e3,p=h+n[1]*1e3,k=Math.max(0,t.points.findIndex(U=>(U.time||0)>=m)),j=t.points.findIndex(U=>(U.time||0)>=p);if(j===-1&&(j=t.points.length-1),j>k+2){let U=t.points.slice(k,j+1),Q=re.computeStats(U,!0);a={...t,points:U,stats:Q}}}let r=a.stats,d=a.points[0].time;re.ensureCurvesAndZones(a);let l=qo(a,e),f=Math.min(99.9,Math.max(80,95+Math.tanh(l.stabilityAccum*.002)*4.5)).toFixed(1);i.innerHTML=` +
+ +
+ +
+
+
labelTags
+
+
+ + + ${s.map(h=>` + +
+
+ +
+
analyticsActivity Summary
+
+
+
Distance
+
${(r.totalDist/1e3).toFixed(2)} km
+
+
+
Duration
+
${r.duration?he(Math.floor(r.duration/1e3)):"\u2014"}
+
+
+
Avg Speed
+
${r.avgSpeed?(r.avgSpeed*3.6).toFixed(1):"\u2014"} km/h
+
+
+
Max Speed
+
${(r.maxSpeed*3.6).toFixed(1)} km/h
+
+ ${r.avgBattery!==null?`
+
Avg Battery
+
${r.avgBattery}%
+
`:""} +
+
+ +
+
infoFile Metadata
+
+
+
Original Filename
+
${ue(t.fileName||"\u2014")}
+
+
+
File Size
+
${t.fileSize?yl(t.fileSize):"\u2014"}
+
+
+
Format
+
${t.format.toUpperCase()}
+
+
+
Sport
+
+ ${Tl(t.sport)} + ${ue(Ye[t.sport?.toLowerCase()||""]?.name||"\u2014")} + expand_more + ${t.subSport?`(${ue(t.subSport)})`:""} +
+
+
+
Device / Creator
+
${t.device||"Unknown Device"}
+
+
+
Start Time
+
${d?vl(d):"Unknown"}
+
+
+
+ +
+
sensorsSensor Info
+
+
+
Total Points
+
${t.points.length.toLocaleString("en-US")} pts
+
+
+
Telemetry Stability
+
${f}%
+
+
+
Sensors Found
+
+ ${r.sensors.length?r.sensors.map(h=>`${Wo[h]||"sensors"}`).join(""):'GPS only'} +
+
+
+
+ + ${r.avgHR||r.avgPower||r.avgCadence||r.avgTemp!=null?` +
+
monitoringBiometric Averages
+
+ ${r.avgHR?`
+
Avg HR
+
${r.avgHR} bpm
+
`:""} + ${r.maxHR?`
+
Max HR
+
${r.maxHR} bpm
+
`:""} + ${r.avgPower?`
+
Avg Power
+
${r.avgPower} W
+
`:""} + ${r.maxPower?`
+
Max Power
+
${r.maxPower} W
+
`:""} + ${r.avgCadence?`
+
Avg Cadence
+
${r.avgCadence} rpm
+
`:""} + ${r.avgTemp!=null?`
+
Avg Temp
+
${r.avgTemp.toFixed(1)} \xB0C
+
`:""} + ${r.minTemp!=null&&r.maxTemp!=null?`
+
Temp Range
+
${r.minTemp.toFixed(1)} \u2013 ${r.maxTemp.toFixed(1)} \xB0C
+
`:""} +
+
`:""} + + ${r.shifts!==null||l.gearMatrix.length>0?` +
+
settingsDrivetrain & Gear Matrix
+
+
+
Total Shifts
+
${r.shifts??0}
+
+
+
Cohort Power Rank
+
#${l.peerSummary.pwrRank} / ${l.peerSummary.cohortSize} (${l.peerSummary.cohortAvgPower} W avg)
+
+
+ + + + + + + + + + + + ${l.gearMatrix.map(h=>` + + + + + + + + `).join("")} + +
GearRatioShareAvg WCad
${ue(h.combo)}${h.ratio}${h.pct}% (${h.seconds}s)${h.avgPower} W${h.avgCadence} rpm
+
`:""} + + ${l.splitRows.length>0?` +
+
splitscreenSection & Kilometer Splits (${l.splitRows.length})
+ + + + + + + + + + + + + + + + + ${l.splitRows.map(h=>` + + + + + + + + + + + + + `).join("")} + +
SectionDistTimeGainSpeedPowerHRCadTempStability
${ue(h.label)}${h.distKm} km${h.durationStr}+${h.elevGain} m${h.avgSpeedKmh} km/h${h.avgPower} W${h.avgHR} bpm${h.avgCadence} rpm${h.avgTemp} \xB0C${h.stabilityPct}%
+
`:""} + + ${t.devices&&t.devices.length>0?` +
+
watchHardware & Sensors
+
+ ${(()=>{let h=new Set;return t.devices.filter(m=>{if(!(m.name||m.product||m.manufacturer))return!1;let k=`${m.name}|${m.product}|${m.manufacturer}|${m.serial}`;return h.has(k)?!1:(h.add(k),!0)}).map(m=>{let p=m.name||m.product||"Unknown Device",k=m.manufacturer?`${m.manufacturer}`:"",j=[m.serial?`SN: ${m.serial}`:null,m.version?`v${m.version}`:null,m.hardwareVersion?`hw:${m.hardwareVersion}`:null].filter(Boolean).join(" \u2022 "),U=[m.batteryLevel?`${m.batteryLevel}%`:null,m.batteryStatus?`Status: ${m.batteryStatus}`:null,m.batteryVoltage?`${m.batteryVoltage.toFixed(2)}V`:null].filter(Boolean).join(" \u2022 "),Q="",Y=String(m.sourceType||"").toLowerCase();Y.includes("local")?Q="memory":Y.includes("antplus")||Y.includes("ant_plus")?Q="settings_input_antenna":Y.includes("bluetooth")&&(Q="bluetooth");let G="sensors",ot=String(p).toLowerCase();return ot.includes("heartrate")||ot.includes("hrm")?G=Rt.hr.icon:ot.includes("power")?G=Rt.power.icon:ot.includes("speed")?G=Rt.speed.icon:ot.includes("cadence")?G=Rt.cadence.icon:ot.includes("barometer")?G="air":ot.includes("temperature")?G=Rt.temperature.icon:ot.includes("gps")&&(G="location_on"),` +
+
+
+ ${G} +
${ue(p)}
+ ${Q?`${Q}`:""} +
+
${k}
+
+ ${j?`
${ue(j)}
`:""} +
+
${ue(m.type||"device")}
+ ${U?`
+ battery_charging_full + ${ue(U)} +
`:""} +
+
+ `}).join("")})()} +
+
`:""} +
+
deleteDanger Zone
+
+ +
+
+
+ `;let y=document.getElementById("tag-container"),S=document.getElementById("new-tag-input"),I=document.getElementById("btn-add-tag"),B=()=>{y.innerHTML="";let h=t.tags||[];if(h.length===0){y.innerHTML='No tags added';return}h.forEach(m=>{let p=hs(m),k=document.createElement("div");k.className="tag-pill font-s",k.style.cssText=` + background: ${p}15; + border: 1px solid ${p}44; + color: ${p}; + padding: 2px 8px; + border-radius: 4px; + font-weight: 600; + display: flex; + align-items: center; + gap: 6px; + `,k.innerHTML=` + ${ue(m)} + close + `,y.appendChild(k)}),y.querySelectorAll(".btn-remove-tag").forEach(m=>{m.addEventListener("click",async p=>{let k=p.currentTarget.dataset.tag;t.tags=(t.tags||[]).filter(j=>j!==k),await ge.save(t),B(),$n()})})},v=async()=>{let h=S.value.trim().toLowerCase();if(!h)return;let m=new Set(t.tags||[]);if(m.has(h)){S.value="";return}m.add(h),t.tags=Array.from(m).sort(),await ge.save(t),S.value="",B(),$n()};I.addEventListener("click",v),S.addEventListener("keydown",h=>{h.key==="Enter"&&v()});let g=i.querySelector("#details-title-input");g?.addEventListener("change",async()=>{let h=g.value.trim();h&&(t.displayName=h,await ge.save(t),$n())});let E=document.getElementById("sport-dropdown-trigger");E?.addEventListener("click",h=>{h.stopPropagation(),H(E)});function H(h){if(!t)return;document.querySelectorAll(".metric-menu-popup").forEach(Q=>Q.remove());let m=document.createElement("div");m.className="metric-menu-popup",Object.keys(Ye).forEach(Q=>{let Y=Ye[Q],G=document.createElement("div"),ot=t.sport?.toLowerCase()===Q;G.className=`menu-item ${ot?"active":""}`,G.innerHTML=` + ${Y.icon} + ${Y.name} + ${ot?"check":""} + `,G.addEventListener("click",async()=>{t.sport=Q,await ge.save(t),m.remove();let _t=document.getElementById("sport-icon");_t&&(_t.textContent=Y.icon);let ct=document.getElementById("sport-name");ct&&(ct.textContent=Y.name),$n()}),m.appendChild(G)});let p=document.createElement("div"),k=!t.sport;p.className=`menu-item ${k?"active":""}`,p.innerHTML=` + block + \u2014 + ${k?"check":""} + `,p.addEventListener("click",async()=>{t.sport=void 0,await ge.save(t),m.remove();let Q=document.getElementById("sport-icon");Q&&(Q.textContent="question_mark");let Y=document.getElementById("sport-name");Y&&(Y.textContent="\u2014"),$n()}),m.appendChild(p),document.body.appendChild(m);let j=h.getBoundingClientRect();m.style.top=`${j.bottom+window.scrollY+4}px`,m.style.left=`${j.left+window.scrollX}px`;let U=Q=>{!m.contains(Q.target)&&!h.contains(Q.target)&&(m.remove(),document.removeEventListener("click",U))};setTimeout(()=>document.addEventListener("click",U),10)}B(),document.getElementById("btn-delete-track")?.addEventListener("click",()=>{confirm(`Remove "${t.name}"?`)&&El(t.id)})}import Zo from"uplot";var xe=[];function Dl(t){return xe.filter(s=>{if(s.points.length===0)return!1;for(let e of s.points){let n=!1;for(let i of t.points)if(en(e.lat,e.lon,i.lat,i.lon)<=e.radius){n=!0;break}if(!n)return!1}return!0})}var un=null,Yn=!1,dn=null,Bl=()=>{},zo=null;function Bs(t){Bl=t,Uo()}function _l(){xe=[],un=null,dn=null,Yn=!1;let t=document.getElementById("courses-view");t&&(t.innerHTML=` +
+ route +
No courses created yet
+
+ `)}function Rl(t){xe=[];let s=[{trackIdx:0,suffix:"Full Loop",fracs:[.1,.5,.85],radius:450},{trackIdx:1,suffix:"Opening Climb Sector",fracs:[.08,.25,.45],radius:450},{trackIdx:2,suffix:"Lakeside Corridor",fracs:[.1,.45,.8],radius:850},{trackIdx:3,suffix:"Sprint Interval Sector",fracs:[.2,.5,.75],radius:850},{trackIdx:4,suffix:"Alpine Grand Tour Stage",fracs:[.1,.5,.85],radius:900},{trackIdx:5,suffix:"Summit Finish Climb",fracs:[.5,.72,.92],radius:900}];for(let e=0;e0&&(un=xe[0].id,dn=xe[0].id)}function Nl(){return xe.map(t=>t.id)}async function Uo(){try{let t=await ge.get("courses");t&&(xe=t)}catch(t){console.error("Courses: Failed to load courses",t)}}async function _s(){try{await ge.set("courses",xe)}catch(t){console.error("Courses: Failed to save courses",t),Bl("Failed to save courses","error")}}function Vo(t){let s=0;for(let e=0;ee)return{duration:0,dist:0,avgWatts:null,avgHR:null,avgSpeed:null,avgCadence:null,avgVam:null};let a=Math.max(0,Math.min(s,i-1)),r=Math.max(a,Math.min(e,i-1)),d=n[a]?.time||0,f=((n[r]?.time||0)-d)/1e3,y=0,S=0,I=0,B=0,v=0,g=0,E=0,H=0,L=0;for(let m=a;m<=r;m++){let p=n[m];if(p.power!=null&&(y+=p.power,S++),p.hr!=null&&(I+=p.hr,B++),p.speed!=null&&(v+=p.speed,g++),p.cadence!=null&&(E+=p.cadence,H++),m>a){let k=n[m-1];if(p.ele!=null&&k.ele!=null){let j=p.ele-k.ele;j>0&&(L+=j)}}}let h=r>a?(n[r].dist||0)-(n[a].dist||0):0;return{duration:f,dist:h,avgWatts:S>0?Math.round(y/S):null,avgHR:B>0?Math.round(I/B):null,avgSpeed:g>0?v/g:null,avgCadence:H>0?Math.round(E/H):null,avgVam:f>0?Math.round(L/(f/3600)):null}}function Oo(t){let s=Math.floor(t/3600),e=Math.floor(t%3600/60),n=Math.floor(t%60);return s>0?`${s}:${e.toString().padStart(2,"0")}:${n.toString().padStart(2,"0")}`:`${e}:${n.toString().padStart(2,"0")}`}function Rs(){return` + calendar_today Date + straighten Distance + timer Duration + speed Speed + speed Pace + trending_up VAM + bolt Power + favorite HR + `}function Ns(t,s,e){let i=(s?Ye[s.toLowerCase()]:null)?.paceUnit||"km",a="--";if(t.avgSpeed!=null&&t.avgSpeed>.1){let r=(i==="km"?1e3:100)/t.avgSpeed;a=`${On(r)} min/${i}`}return` + ${e} + ${t.dist!=null?`${(t.dist/1e3).toFixed(2)} km`:"-"} + ${Oo(t.duration)} + ${t.avgSpeed!=null?`${(t.avgSpeed*3.6).toFixed(1)} km/h`:"-"} + ${a} + ${t.avgVam!=null?`${t.avgVam} m/h`:"-"} + ${t.avgWatts!=null?`${t.avgWatts} W`:"-"} + ${t.avgHR!=null?`${t.avgHR} bpm`:"-"} + `}var ys=16384,$l=new Float64Array(ys),Cl=new Float64Array(ys),Pl=new Float64Array(ys),Ll=new Float64Array(32),Al=new Float64Array(32),sn=180,Il=28,Hl=new Float64Array(sn),Fl=new Float64Array(sn),Cn=new Float64Array(sn+1),bs=new Float64Array(sn+1);function je(){let t=document.getElementById("courses-view");if(!t)return;let s=0,e=dn||un?xe.filter(n=>n.id===dn||n.id===un):xe;for(let n of e){if(n.points.length<2)continue;let i=qt.findTracksPassingThroughPoints(n.points.map(r=>({lat:r.lat,lng:r.lon})),n.points[0]?.radius||20),a=4;for(let r=0;r +
+
+
+ ${Yn?` + + `:` + + `} +
+ +
+
+ ${xe.length===0?` +
+ route +
No courses created yet
+
+ `:xe.map(n=>{let i=n.points.map(l=>({lat:l.lat,lng:l.lon})),a=qt.findTracksPassingThroughPoints(i,n.points[0]?.radius||20),r=n.id===dn,d="";if(r){let l=a.map(f=>{let y=qt.getTrackData(f);if(!y)return null;let S=vs(y,n.points);if(!S)return null;let I=wn(y,S.startIdx,S.endIdx),B=Math.max(1,S.endIdx-S.startIdx),v=S.startIdx+Math.floor(B*.25),g=S.startIdx+Math.floor(B*.5),E=S.startIdx+Math.floor(B*.75),H=wn(y,S.startIdx,v),L=wn(y,v,g),h=wn(y,g,E),m=wn(y,E,S.endIdx),p=y.stats?.startTime||y.points?.[0]?.time,k=p?nn(p):"-";return{id:f,displayName:y.displayName||y.name||f,date:k,sport:y.sport,color:y.color,...I,splitsSummary:`${H.avgWatts||0}/${L.avgWatts||0}/${h.avgWatts||0}/${m.avgWatts||0}W`,startIdx:S.startIdx,endIdx:S.endIdx}}).filter(f=>f!==null);l.sort((f,y)=>f.duration-y.duration||f.id.localeCompare(y.id)),d=` +
+ ${l.length===0?` +
No matching tracks
+ `:` + + + + + ${Rs()} + + + + ${l.map(f=>` + + + ${Ns(f,f.sport,f.date)} + + `).join("")} + +
Track (${l.length})
+
+
+ ${ue(f.displayName)} +
+
+ `} +
+ `}return` +
+
+ ${r?"expand_more":"chevron_right"} +
${ue(n.name)} edit
+
straighten ${Vo(n).toFixed(2)} km | ${n.points.length} points | ${a.length} matching tracks
+
+ + +
+
+ ${d} +
+ `}).join("")} +
+
+ `,document.getElementById("btn-new-course")?.addEventListener("click",()=>{Yo()}),document.getElementById("btn-done-edit")?.addEventListener("click",()=>{Yn=!1,qt.setCustomClickHandler(null),document.getElementById("map-container")?.classList.remove("editing-mode"),je()}),t.querySelectorAll(".course-item").forEach(n=>{let i=n.dataset.id;n.querySelector(".btn-edit-course")?.addEventListener("click",a=>{a.stopPropagation(),Wl(i)}),n.querySelector(".btn-rename-course")?.addEventListener("click",a=>{a.stopPropagation(),jo(i)}),n.querySelector(".btn-delete-course")?.addEventListener("click",a=>{a.stopPropagation(),Go(i)}),n.querySelector(".course-item-header")?.addEventListener("click",a=>{a.target.closest(".course-actions")||a.target.closest(".btn-rename-course")||(Pn(i),dn=dn===i?null:i,je())}),n.querySelectorAll(".course-track-row").forEach(a=>{let r=a.dataset.trackId,d=parseInt(a.dataset.start,10),l=parseInt(a.dataset.end,10);a.querySelector(".track-name-cell")?.addEventListener("click",f=>{f.stopPropagation();let y=qt.getTrackData(r);y&&(We(r,!1),setTimeout(()=>{let S=y.points,I=S.find(m=>m.time!=null)?.time||0,B=Math.max(0,Math.min(d,S.length-1)),v=Math.max(0,Math.min(l,S.length-1)),g=Math.max(0,((S[B]?.time||0)-I)/1e3),E=Math.max(0,((S[v]?.time||0)-I)/1e3),H=St.getXAxis(),L=g,h=E;H==="distance"&&(L=Math.max(0,(S[B]?.dist||0)/1e3),h=Math.max(0,(S[v]?.dist||0)/1e3)),jt.patch({sel:[L,h],tab:"graphs"},!0),St.restoreSelection(g,E),qt.highlightSegment(r,y.points,d,l,!0)},50))}),a.addEventListener("mouseenter",()=>{qt.setTrackHover(r,!0)}),a.addEventListener("mouseleave",()=>{qt.setTrackHover(r,!1)})})})}function Yo(){let t=`Course ${xe.length+1}`,s={id:Math.random().toString(36).substring(2,9),name:t,points:[]};xe.push(s),_s(),je(),Wl(s.id)}function jo(t){let s=xe.find(d=>d.id===t);if(!s)return;let e=document.querySelector(`.course-item[data-id="${t}"]`);if(!e)return;let n=e.querySelector(".course-name");if(!n)return;let i=s.name;n.innerHTML=``;let a=n.querySelector(".course-name-input");a.focus(),a.select();let r=()=>{let d=a.value.trim();d!==""&&(s.name=d,_s()),je()};a.addEventListener("blur",r),a.addEventListener("keydown",d=>{d.key==="Enter"?r():d.key==="Escape"&&je()})}function Wl(t){un=t,Yn=!0,je()}function ql(){zo=null}function Pn(t,s=!1){un=t,s&&(dn=t),Yn=!1,qt.setCustomClickHandler(null),ql(),je()}function Go(t){confirm("Are you sure you want to delete this course?")&&(xe=xe.filter(s=>s.id!==t),un===t&&(un=null,ql()),_s(),je())}var Ts=()=>{},Ws=[];function jl(){Ws.forEach(t=>t.destroy()),Ws=[]}function qs(t){Ts=t}function Gl(){jl(),document.querySelectorAll(".hr-settings-popup").forEach(s=>s.remove());let t=document.getElementById("insights-view");t&&(t.innerHTML=` +
+ no_sim +
Select a track for insights
+
+ `)}var mn=16384,zl=new Float64Array(mn),Ul=new Float64Array(mn),Vl=new Float64Array(mn),Xo=new Float64Array(mn),Qo=new Float64Array(mn),xs=new Float64Array(mn+1),ws=new Float64Array(mn+1),Ol=[1,2,3,5,8,10,15,20,30,45,60,90,120,180,240,300,420,600,900,1200];function Ko(t,s,e){let n=Math.min(t.length,mn);if(n===0)return{minWPrimeBal:2e4,matchesBurned:0,decouplingPct:0,quadrantPct:[25,25,25,25],sectionPeaks:[]};for(let p=0;p0?Y*G*36:0;zl[p]=j,Ul[p]=1/U,Vl[p]=Q,Xo[p]=Y,Qo[p]=ot,xs[p+1]=xs[p]+j,ws[p+1]=ws[p]+ot}let i=0;for(let p=0;pn)continue;let j=n-k,U=0,Q=0;for(let Y=0;Y<=j;Y++){let G=xs[Y+k]-xs[Y],ot=ws[Y+k]-ws[Y];G>U&&(U=G),ot>Q&&(Q=ot)}i+=(U+Q)/k}let a=2e4,r=2e4,d=0,l=!1,f=n>>1,y=0,S=0,I=[0,0,0,0],B=88,v=s/B;for(let p=0;p0?Q:0,_t=546*Math.exp(-.008*(Y+G*.625))+316;r=Math.min(2e4,r-G+(2e4-r)*(1/_t)),r17500&&l&&(l=!1);let ct=k/j;ct>=v&&j>=B?I[0]++:ct>=v&&j0?y/f:1,E=n-f>0?S/(n-f):1,H=g>0?(g-E)/g*100:0,L=[Number((I[0]/n*100).toFixed(1)),Number((I[1]/n*100).toFixed(1)),Number((I[2]/n*100).toFixed(1)),Number((I[3]/n*100).toFixed(1))],m=(e&&e.length>0?e.filter(p=>p.kind==="quarter"||p.kind==="climb"||p.kind==="sprint"):[]).slice(0,8).map(p=>{let k=p.stats,j=k.duration&&k.duration>0?k.duration/36e5:0;return{label:p.label,distKm:Number((k.totalDist/1e3).toFixed(2)),avgPower:k.avgPower||0,peak60sPower:k.powerCurve?.[60]?.power||k.avgPower||0,avgHR:k.avgHR||0,vam:j>0?Math.round(k.elevGain/j):0}});return i<-1e12&&(a=0),{minWPrimeBal:a,matchesBurned:d,decouplingPct:H,quadrantPct:L,sectionPeaks:m}}function ke(t,s=[],e=null){jl();let n=document.getElementById("insights-view");if(!n)return;if(!t){n.innerHTML="";return}re.ensureCurvesAndZones(t),t.stats.sections||(t.stats.sections=re.analyzeTrackSections(t.points));let i=e||jt.get().sel||null,a=t.points,r=t.stats;if(i&&t.points.length>4){let g=t.points[0].time||0,E=g+i[0]*1e3,H=g+i[1]*1e3,L=t.points.findIndex(m=>(m.time||0)>=E),h=t.points.findIndex(m=>(m.time||0)>=H);L===-1&&(L=0),h===-1&&(h=t.points.length-1),h>L+4&&(a=t.points.slice(L,h+1),r=re.computeStats(a,!0))}if(!r.powerCurve&&!r.hrZones&&!r.hrCurve){n.innerHTML=` +
+ analytics +
No insights available for this track (requires Power or Heart Rate data)
+
+ `;return}let d=Ko(a,re.getFTP(),t.stats.sections),l=d.minWPrimeBal;n.innerHTML=` +
+ `;let f=document.getElementById("insights-grid");if(!f)return;let y=Math.round(f.getBoundingClientRect().width-42)||480;if(r.powerCurve&&Yl({grid:f,track:t,curve:r.powerCurve,metricKey:"power",label:"Power Curve",unit:"W",color:Rt.power.color,icon:"bolt",chartWidth:y}),r.hrCurve&&Yl({grid:f,track:t,curve:r.hrCurve,metricKey:"hr",label:"Heart Rate Curve",unit:"bpm",color:Rt.hr.color,icon:"favorite",chartWidth:y}),r.powerZones){let g=document.createElement("div");g.className="insight-card";let E="power",H=Rt.power.color;g.style.setProperty("--chart-color",H);let L=St.getMapColorMetric()===E,h=r.powerZones.reduce((k,j)=>k+j,0),m=kn.getPowerZones(),p="";r.powerZones.forEach((k,j)=>{if(j>=m.length)return;let U=m[j],Q=h>0?k/h*100:0,Y=isFinite(U.max)?`${Math.round(U.min)} - ${Math.round(U.max)} W`:`> ${Math.round(U.min)} W`;p+=` +
+
+ Z${j+1} + ${U.name} + ${Y} + ${he(Math.round(k))} + ${Q.toFixed(1)}% +
+
+
+
+
+ `}),g.innerHTML=` +
+
+
+ boltPower Zones +
+ +
+ +
+
${p}
+ `,f.appendChild(g),g.querySelector(".map-color-btn")?.addEventListener("click",k=>{k.stopPropagation(),St.toggleMapColor(E);let j=St.getMapColorMetric();f.querySelectorAll(".map-color-btn").forEach(U=>{let Q=U;Q.classList.toggle("active",Q.dataset.metric===j)})}),g.querySelector(".btn-pw-settings")?.addEventListener("click",k=>{k.stopPropagation(),ti(k.currentTarget,t)})}if(r.hrZones){let g=document.createElement("div");g.className="insight-card";let E="hr",H=Rt.hr.color;g.style.setProperty("--chart-color",H);let L=St.getMapColorMetric()===E,h=r.hrZones.reduce((k,j)=>k+j,0),m=kn.getHRZones(),p="";r.hrZones.forEach((k,j)=>{if(j>=m.length)return;let U=m[j],Q=h>0?k/h*100:0,Y=isFinite(U.max)?`${Math.round(U.min)} - ${Math.round(U.max)} bpm`:`> ${Math.round(U.min)} bpm`;p+=` +
+
+ Z${j+1} + ${U.name} + ${Y} + ${he(Math.round(k))} + ${Q.toFixed(1)}% +
+
+
+
+
+ `}),g.innerHTML=` +
+
+
+ favoriteHeart Rate Zones +
+ +
+ +
+
${p}
+ `,f.appendChild(g),g.querySelector(".map-color-btn")?.addEventListener("click",k=>{k.stopPropagation(),St.toggleMapColor(E);let j=St.getMapColorMetric();f.querySelectorAll(".map-color-btn").forEach(U=>{let Q=U;Q.classList.toggle("active",Q.dataset.metric===j)})}),g.querySelector(".btn-hr-settings")?.addEventListener("click",k=>{k.stopPropagation(),Jo(k.currentTarget,t)})}let S=document.createElement("div");S.className="insight-card";let I=r.totalDist/1e3,B=I>0?r.elevGain/(I*1e3)*100:0;if(S.innerHTML=` +
+ terrainClimbing Analysis +
+
+
+
Total Ascent
+
${Math.round(r.elevGain)} m
+
+
+
Total Descent
+
${Math.round(r.elevLoss)} m
+
+
+
Avg Grade
+
${B.toFixed(1)} %
+
+
+
VAM
+
${r.duration&&r.duration>0?Math.round(r.elevGain/r.duration*36e5):0} m/h
+
+
+ `,f.appendChild(S),r.avgPower&&r.avgHR){let g=document.createElement("div");g.className="insight-card";let E=r.avgPower/r.avgHR;g.innerHTML=` +
+ monitoringEfficiency & W' Balance +
+
+
+
EF (Avg Power / Avg HR)
+
${E.toFixed(2)}
+
+
+
Aerobic Decoupling
+
${d.decouplingPct.toFixed(1)} %
+
+
+
Min W' Reserve
+
${(d.minWPrimeBal/1e3).toFixed(1)} kJ (${d.matchesBurned} matches)
+
+
+
Quadrants (Q1/Q2/Q3/Q4)
+
${d.quadrantPct.join("% / ")}%
+
+
+ `,f.appendChild(g)}if(d.sectionPeaks.length>0){let g=document.createElement("div");g.className="insight-card",g.innerHTML=` +
+ segmentTrack Section & Climb Efforts +
+ + + + + + + + + + + + + ${d.sectionPeaks.map(E=>` + + + + + + + + + `).join("")} + +
SectionDistAvg WPeak 60sAvg HRVAM
${ue(E.label)}${E.distKm} km${E.avgPower} W${E.peak60sPower} W${E.avgHR} bpm${E.vam} m/h
+ `,f.appendChild(g)}let v=Dl(t);if(v.length>0){let g=document.createElement("div");g.className="insight-card",g.style.cssText="column-span: all; display: block;",g.innerHTML=` +
routeCourses (${v.length})
+
+ + + + + ${Rs()} + + + + ${v.map(E=>{let H=vs(t,E.points),L="";if(H){let h=wn(t,H.startIdx,H.endIdx),m=t.stats?.startTime||t.points?.[0]?.time,p=m?nn(m):"-";L=Ns(h,t.sport||null,p)}else L='';return` + + + ${L} + + `}).join("")} + +
Name
No continuous segment
+
+
+ ${ue(E.name)} +
+
+
+ `,f.appendChild(g),g.querySelectorAll(".clickable-course").forEach(E=>{E.addEventListener("click",()=>{let H=E.dataset.id;Pn(H,!0),jt.patch({tab:"courses"},!0)}),E.addEventListener("mouseenter",()=>{let H=E.dataset.id,L=v.find(h=>h.id===H);if(L){let h=vs(t,L.points);h&&qt.highlightSegment(t.id,t.points,h.startIdx,h.endIdx,!1)}}),E.addEventListener("mouseleave",()=>{qt.clearHighlight()})})}}function Yl(t){let{grid:s,track:e,curve:n,metricKey:i,label:a,unit:r,color:d,icon:l,chartWidth:f}=t,y=St.getMapColorMetric()===i,S=document.createElement("div");S.className="insight-card",S.style.setProperty("--chart-color",d),S.innerHTML=` +
+
+
+ ${l}${a} +
+ +
+ +
+
+ + `,s.appendChild(S);let I=S.querySelector(".curve-table-wrap"),B=S.querySelector(".btn-toggle-table"),v=B.querySelector(".material-symbols-rounded");B.addEventListener("click",()=>{let h=I.style.display==="none";I.style.display=h?"block":"none",v.textContent=h?"expand_less":"expand_more"});let g=S.querySelector(".map-color-btn");g.addEventListener("click",h=>{h.stopPropagation();let m=g.dataset.metric;St.toggleMapColor(m);let p=St.getMapColorMetric();s.querySelectorAll(".map-color-btn").forEach(k=>{let j=k;j.classList.toggle("active",j.dataset.metric===p)})});let E=S.querySelector(".curve-tbody"),H=Object.keys(n).map(Number).sort((h,m)=>h-m);H.forEach(h=>{let m=n[h],p=m.power||m.hr||m.val,k=document.createElement("tr");k.innerHTML=` + ${ze(h)} + ${p} ${r} + + + + `,E?.appendChild(k)}),E?.querySelectorAll(".btn-show-peak").forEach(h=>{h.addEventListener("click",m=>{let p=m.currentTarget,k=parseInt(p.dataset.idx),j=parseInt(p.dataset.len),U=e.points,Q=Math.min(U.length-1,k+j-1),Y=U[0].time||0,G=((U[k].time||Y)-Y)/1e3,ot=((U[Q].time||Y)-Y)/1e3;jt.patch({sel:[G,ot]}),qt.highlightSegment(e.id,U,k,Q,!0)})});let L=S.querySelector(`#${i}-curve-chart`);if(L){let h=H,m=H.map(U=>n[U].power||n[U].hr||n[U].val),p=document.createElement("div");p.className="cur-y-val",p.style.cssText=`color:${d};display:none;border-color:${d}44`,L.style.position="relative",L.appendChild(p);let k={id:`${i}-curve`,width:0,height:250,padding:[10,10,0,10],scales:{x:{time:!1,distr:3,auto:!0},y:{auto:!0}},series:[{},{label:a,stroke:d,width:2,fill:le(d,.1),points:{show:!0,size:5,fill:d}}],axes:[{stroke:"#555564",grid:{stroke:"#2e2e34",width:1},show:!0,values:()=>[],ticks:{show:!1},font:"10px system-ui, sans-serif",size:30},{stroke:"#555564",grid:{stroke:"#2e2e34",width:1},values:(U,Q)=>Q.map(Y=>`${Y}${r}`),font:"10px system-ui, sans-serif",size:50}],cursor:{drag:{x:!1,y:!1},dataIdx:(U,Q,Y)=>Y},hooks:{draw:[U=>{let Q=U.ctx,Y=window.devicePixelRatio||1,G=U.bbox,ot=[1,2,5,10,20,30,60,120,300,600,1200,1800,3600,7200,10800],_t=h[h.length-1];Q.save(),Q.fillStyle="#555564",Q.textAlign="center",Q.textBaseline="top",Q.font=`${10*Y}px system-ui, sans-serif`;let ct=-100;ot.forEach(w=>{if(w>_t)return;let b=U.valToPos(w,"x",!0);b>ct+30*Y&&b{U.over.addEventListener("click",()=>{let Q=U.cursor.idx;if(Q!=null&&h[Q]!=null){let Y=h[Q],ot=n[Y].idx,_t=e.points,ct=Math.min(_t.length-1,ot+Y-1),w=_t[0].time||0,b=((_t[ot].time||w)-w)/1e3,u=((_t[ct].time||w)-w)/1e3;jt.patch({sel:[b,u]}),qt.highlightSegment(e.id,_t,ot,ct,!0)}})}],setCursor:[U=>{if(!U._ready)return;U.redraw(!1);let Q=U.cursor.idx,Y=U.ctx,G=window.devicePixelRatio||1,ot=U.bbox,_t=Q!=null&&m[Q]!=null&&U.cursor.left>=0,ct=U.cursor.top,w=U.valToPos(m[Q],"y",!1);if(_t&&Math.abs(ct-w)<40){let u=U.valToPos(h[Q],"x",!1),R=U.valToPos(m[Q],"y",!1),M=h[Q];p.innerHTML=`${ze(M)}: ${m[Q]} ${r}`,p.style.transform=`translate(${u}px, ${R}px) translate(15px, 15px)`,p.style.display="";let O=U.valToPos(h[Q],"x",!0),q=U.valToPos(m[Q],"y",!0);Y.save(),Y.beginPath(),Y.setLineDash([2*G,2*G]),Y.strokeStyle=le(d,.4),Y.lineWidth=1*G,Y.moveTo(O,ot.top),Y.lineTo(O,ot.top+ot.height),Y.stroke(),Y.beginPath(),Y.setLineDash([]),Y.arc(O,q,3*G,0,Math.PI*2),Y.fillStyle="#0e0e10",Y.fill(),Y.strokeStyle=d,Y.lineWidth=2*G,Y.stroke(),Y.restore()}else p.style.display="none"}]},legend:{show:!1}};k.width=f||Math.round(s.getBoundingClientRect().width-42)||L.clientWidth||500;let j=new Zo(k,[h,m],L);j._ready=!0,Ws.push(j)}}function Jo(t,s){document.querySelectorAll(".hr-settings-popup").forEach(r=>r.remove());let e=document.createElement("div");e.className="hr-settings-popup";let n=re.getHRZones();e.innerHTML=` +
Configure HR Zones (bpm)
+
+
+ Z1 / Z2 + +
+
+ Z2 / Z3 + +
+
+ Z3 / Z4 + +
+
+ Z4 / Z5 + +
+ +
+ `;let i=t.getBoundingClientRect();e.style.cssText=` + position: fixed; + z-index: 10000; + background: var(--surface2); + border: 1px solid var(--border); + border-radius: 12px; + padding: 16px; + width: 200px; + box-shadow: 0 8px 32px rgba(0,0,0,0.6); + left: ${i.right-200}px; + top: ${i.bottom+8}px; + `,document.body.appendChild(e),e.querySelector(".save-btn")?.addEventListener("click",async()=>{let r=e.querySelectorAll(".hr-zone-input"),d=Array.from(r).map(l=>parseInt(l.value)||0);for(let l=1;l{!e.contains(r.target)&&!t.contains(r.target)&&(e.remove(),document.removeEventListener("click",a))};setTimeout(()=>document.addEventListener("click",a),10)}function ti(t,s){document.querySelectorAll(".pw-settings-popup").forEach(r=>r.remove());let e=document.createElement("div");e.className="pw-settings-popup";let n=re.getFTP();e.innerHTML=` +
Configure FTP (Watts)
+
+
+ Your FTP + +
+ +
+ `;let i=t.getBoundingClientRect();e.style.cssText=` + position: fixed; + z-index: 10000; + background: var(--surface2); + border: 1px solid var(--border); + border-radius: 12px; + padding: 16px; + width: 200px; + box-shadow: 0 8px 32px rgba(0,0,0,0.6); + left: ${i.right-200}px; + top: ${i.bottom+8}px; + `,document.body.appendChild(e),e.querySelector(".save-btn")?.addEventListener("click",async()=>{let r=e.querySelector("#ftp-input"),d=parseInt(r.value)||200;re.setFTP(d),await ge.set("ftp",d),s.stats=re.computeStats(s.points,!0),await ge.save(s),ke(s),e.remove(),Ts(`FTP updated to ${d}W`)});let a=r=>{!e.contains(r.target)&&!t.contains(r.target)&&(e.remove(),document.removeEventListener("click",a))};setTimeout(()=>document.addEventListener("click",a),10)}import Zl from"uplot";var we=[],$e=null,me=null,fn="power",Qe="hr",be=null,Ae=[],Xe=[],pn=null,Ie=!1;function jn(){let t=document.getElementById("combined-time-val");if(t&&be!==null&&Ae[be]){let s=Ae[be][2];t.textContent=`${he(s)}`}}function Ql(){pn&&(clearInterval(pn),pn=null),me&&(me.destroy(),me=null),we=[],$e=null,fn="power",Qe="hr",be=null,Ae=[],Xe=[],Ie=!1;let t=document.getElementById("combined-tooltip");t&&t.remove();let s=document.getElementById("combined-view");s&&(s.innerHTML=` +
+ no_sim +
Select a track for combined analysis
+
+ `)}var ei=16384,zs=new Float64Array(32*32);function ni(t,s){let e=s.length>=2?s:t,n=Math.min(e.length,8);zs.fill(0);let i=[{label:"Power vs Heart Rate",getX:I=>I.power,getY:I=>I.hr},{label:"Speed vs Gradient",getX:I=>I.speed!=null?I.speed*3.6:void 0,getY:I=>I.gradient},{label:"Cadence vs Power",getX:I=>I.cad,getY:I=>I.power},{label:"Elevation vs VAM",getX:I=>I.ele,getY:I=>I.verticalSpeed},{label:"Temperature vs HR",getX:I=>I.temp,getY:I=>I.hr}],a=0,r=0,d=[];for(let I=0;I2){let k=v/h,j=g/h,U=Math.max(0,E/h-k*k),Q=Math.max(0,H/h-j*j),Y=L/h-k*j,G=Math.sqrt(U*Q);m=G>1e-9?Y/G:0,p=U>1e-9?Y/U:0}a+=h,r+=Math.abs(m)*100+Math.abs(p),d.push({pairLabel:B.label,rVal:m,slope:p,samples:h})}for(let I=0;I<1024;I++)r+=zs[I]*.001;let l=["Q1 (0\u201325%)","Q2 (25\u201350%)","Q3 (50\u201375%)","Q4 (75\u2013100%)"],f=[],y=t[0],S=t[1]||t[0];for(let I=0;I<4;I++){let B=I*.25,v=(I+1)*.25,g=h=>{let m=h.points||[],p=Math.floor(m.length*B),k=Math.max(p+1,Math.floor(m.length*v)),j=0,U=0,Q=0,Y=0;for(let G=p;G0?Math.round(j/Y):0,hr:Y>0?Math.round(U/Y):0,speedKmh:Y>0?Q/Y:0}},E=g(y),H=g(S),L=0;for(let h=0;h0?Math.round(L/n):0})}return{aggPts:a,corrSum:r,correlations:d,sectionRows:f}}function Us(t,s){fn=t,Qe=s,we.length>=2?qe(we):$e&&qe($e)}function Vs(t){Ie!==t&&(Ie=t,we.length>=2?qe(we):$e&&qe($e))}function Ss(t){let s=Math.max(0,Math.min(1,t)),e=Ae.length>0?Ae.length-1:$e?Math.max(0,$e.points.length-1):0;be=Math.round(s*e);let n=document.getElementById("combined-time-slider");n&&(n.max=`${e}`,n.value=`${be}`),jn(),me&&me.redraw()}function qe(t,s=[]){Array.isArray(t)?(we=t,$e=t[0]||null):t?(we=[t],$e=t):(we=[],$e=null);let e=document.getElementById("combined-view");if(!e)return;let n=$e;if(!n){e.innerHTML=` +
+ no_sim +
Select a track for combined analysis
+
+ `;return}let i=St.METRICS,a=[];for(let v in i){let g=i[v];n.points.some(E=>E[g.field]!=null)&&a.push(v)}!a.includes(fn)&&a.length>0&&(fn=a[0]),!a.includes(Qe)&&a.length>1?Qe=a[1]:!a.includes(Qe)&&a.length>0&&(Qe=a[0]);let r=i[fn],d=i[Qe];e.style.overflow="hidden";let l="";if(we.length>=2){let{aggPts:v,corrSum:g,correlations:E,sectionRows:H}=ni(we,s),L=we[0],h=we[1],m=L.stats,p=h.stats,k=((p.totalDist-m.totalDist)/1e3).toFixed(2),j=((p.avgPower||0)-(m.avgPower||0)).toFixed(0),U=((p.avgHR||0)-(m.avgHR||0)).toFixed(0),Q=(p.elevGain-m.elevGain).toFixed(0),Y=(p.shifts||0)-(m.shifts||0),G=((m.totalDist+p.totalDist)/1e3).toFixed(2),ot=(m.elevGain+p.elevGain).toFixed(0),_t=((m.duration||0)+(p.duration||0))/1e3,ct=Math.round(((m.avgPower||0)+(p.avgPower||0))/2),w=Math.max(m.maxPower||0,p.maxPower||0),b=Math.round(((m.avgHR||0)+(p.avgHR||0))/2),u=Math.max(m.maxHR||0,p.maxHR||0),R=((p.avgTemp??0)-(m.avgTemp??0)).toFixed(1),M=(((m.avgTemp??0)+(p.avgTemp??0))/2).toFixed(1),O=Math.max(m.maxTemp??0,p.maxTemp??0).toFixed(1),q=(m.shifts||0)+(p.shifts||0);l=` +
+
Aggregate Track Comparison & Section Analysis (${we.length} Tracks)
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ${H.map(V=>` + + + + + + + `).join("")} + +
Metric${L.displayName||L.name} (T1)${h.displayName||h.name} (T2)Delta (T2 - T1)Aggregate Summary
Distance${(m.totalDist/1e3).toFixed(2)} km${(p.totalDist/1e3).toFixed(2)} km${Number(k)>=0?"+":""}${k} km${G} km (Total)
Elevation Gain${m.elevGain.toFixed(0)} m${p.elevGain.toFixed(0)} m${Number(Q)>=0?"+":""}${Q} m${ot} m (Total)
Duration${m.duration?he(m.duration/1e3):"--"}${p.duration?he(p.duration/1e3):"--"}--${he(_t)} (Total)
Avg / Max Power${m.avgPower||0} W / ${m.maxPower||0} W${p.avgPower||0} W / ${p.maxPower||0} W${Number(j)>=0?"+":""}${j} W (Avg)${ct} W (Avg) / ${w} W (Max)
Avg / Max HR${m.avgHR||0} bpm / ${m.maxHR||0} bpm${p.avgHR||0} bpm / ${p.maxHR||0} bpm${Number(U)>=0?"+":""}${U} bpm (Avg)${b} bpm (Avg) / ${u} bpm (Max)
Avg / Max Temp${m.avgTemp!=null?m.avgTemp.toFixed(1):"--"} \xB0C / ${m.maxTemp!=null?m.maxTemp.toFixed(1):"--"} \xB0C${p.avgTemp!=null?p.avgTemp.toFixed(1):"--"} \xB0C / ${p.maxTemp!=null?p.maxTemp.toFixed(1):"--"} \xB0C${Number(R)>=0?"+":""}${R} \xB0C (Avg)${M} \xB0C (Avg) / ${O} \xB0C (Max)
Gear Shifts${m.shifts||0}${p.shifts||0}${Y>=0?"+":""}${Y}${q} (Total)
${V.sectionLabel} Split${V.t1Power}W \xB7 ${V.t1Hr}bpm \xB7 ${V.t1SpeedKmh.toFixed(1)}km/h${V.t2Power}W \xB7 ${V.t2Hr}bpm \xB7 ${V.t2SpeedKmh.toFixed(1)}km/h${V.t2Power-V.t1Power>=0?"+":""}${V.t2Power-V.t1Power}WCohort Avg: ${V.cohortAvgPower}W
+
+ ${E.map(V=>`${V.pairLabel}: r=${V.rVal.toFixed(2)}, \u03B2=${V.slope.toFixed(2)} (${V.samples})`).join("")} +
+
+ `}e.innerHTML=`${l} +
+
+ arrow_forward + X Axis: + +
+
+ arrow_upward + Y Axis: + +
+
+ schedule + Time: + + 0:00 + +
+
+ + +
+
+
+ `,document.getElementById("btn-combined-x")?.addEventListener("click",v=>{v.stopPropagation(),Xl(v.currentTarget,"x")}),document.getElementById("btn-combined-y")?.addEventListener("click",v=>{v.stopPropagation(),Xl(v.currentTarget,"y")});let f=document.getElementById("combined-time-slider"),y=document.getElementById("combined-time-val"),S=document.getElementById("btn-combined-play");f?.addEventListener("input",()=>{be=parseInt(f.value,10),jn(),me&&me.redraw()});function I(){S&&(S.innerHTML='pause'),pn=window.setInterval(()=>{be!==null&&beplay_arrow')}S?.addEventListener("click",()=>{pn?B():(be!==null&&be>=Ae.length-1&&(be=0,f.value="0",jn(),me&&me.redraw()),I())}),document.getElementById("btn-combined-cart")?.addEventListener("click",()=>{Ie&&(Ie=!1,qe($e))}),document.getElementById("btn-combined-polar")?.addEventListener("click",()=>{Ie||(Ie=!0,qe($e))}),si()}function Gn(){let t=document.getElementById("tab-combined"),s=document.getElementById("combined-plot-container");if(me&&s&&s.clientWidth>0){let e=s.clientWidth,n=s.clientHeight;if(e>0&&n>0)if(Ie){let i=Math.min(e,n);me.setSize({width:i,height:i})}else me.setSize({width:e,height:n})}}function Xl(t,s){document.querySelectorAll(".metric-menu-popup").forEach(l=>l.remove());let e=document.createElement("div");e.className="metric-menu-popup";let n=St.METRICS,i=$e;if(!i)return;let a=[];for(let l in n){let f=n[l];i.points.some(y=>y[f.field]!=null)&&a.push(l)}a.forEach(l=>{let f=n[l],y=document.createElement("div"),S=s==="x"?fn:Qe;y.className=`menu-item ${S===l?"active":""}`,y.innerHTML=` + ${f.icon} + ${f.label} + ${S===l?"check":""} + `,y.addEventListener("click",()=>{s==="x"?fn=l:Qe=l,e.remove(),qe($e)}),e.appendChild(y)});let r=t.getBoundingClientRect();e.style.top=`${r.bottom+5}px`,e.style.left=`${r.left}px`,document.body.appendChild(e);let d=l=>{!e.contains(l.target)&&!t.contains(l.target)&&(e.remove(),document.removeEventListener("click",d))};setTimeout(()=>document.addEventListener("click",d),0)}function si(){if(!$e)return;let t=document.getElementById("combined-plot-container");if(!t)return;let s=St.METRICS,e=s[fn]||s.power,n=s[Qe]||s.hr;if(we.length>=2){let v=0;for(let p=0;pv&&(v=we[p].points.length);let g=new Array(v);for(let p=0;p0?t.clientWidth:800,h=t.clientHeight>0?t.clientHeight:350,m={width:L,height:h,padding:[10,15,0,0],scales:{x:{time:!1},y:{auto:!0}},series:E,axes:[{side:2,label:"Elapsed Duration (s)",stroke:"#555564",grid:{stroke:"#2e2e34",width:1},font:"10px system-ui, sans-serif",size:30},{side:3,label:`${n.label} (${n.unit})`,stroke:"#555564",grid:{stroke:"#2e2e34",width:1},font:"10px system-ui, sans-serif",size:40}],legend:{show:!0}};me=new Zl(m,H,t),Gn();return}let i=$e.points,a=i[0].time||0,r=i.map((v,g)=>[v[e.field],v[n.field],(v.time-a)/1e3,(v.dist||0)/1e3,g]).filter(([v,g,E,H])=>v!=null&&g!=null);if(r.length===0){t.innerHTML='
No data for selected metrics
';return}if(Ie){let v=r.map(m=>m[0]),g=r.map(m=>m[1]),E=Math.min(...v),H=Math.max(...v),L=Math.min(...g),h=Math.max(...g);Ae=r.map(m=>{let p=m[0],k=m[1],j=(p-E)/(H-E||1)*2*Math.PI,U=(k-L)/(h-L||1),Q=U*Math.cos(j),Y=U*Math.sin(j);return[Q,Y,m[2],m[3],m[4],p,k]})}else Ae=r.map(v=>[v[0],v[1],v[2],v[3],v[4],v[0],v[1]]);let d=document.getElementById("combined-time-slider");d&&(d.max=`${r.length-1}`,(be===null||be>=r.length)&&(be=r.length-1,d.value=d.max),jn()),Xe=[...Ae].sort((v,g)=>v[0]-g[0]||v[4]-g[4]);let l=Xe.map(v=>v[0]),f=Xe.map(v=>v[1]),y=document.getElementById("combined-plot-container"),S=y&&y.clientWidth>0?y.clientWidth:600,I=y&&y.clientHeight>0?y.clientHeight:400;if(Ie){let v=Math.min(S,I>0?I:S);S=v,I=v,y&&(y.style.display="flex",y.style.alignItems="center",y.style.justifyContent="center")}else y&&(y.style.display="",y.style.alignItems="",y.style.justifyContent="");let B={width:S,height:I,padding:[10,15,0,0],scales:{x:{time:!1,auto:!0},y:{auto:!0}},series:[{},{label:n.label,width:0,points:{show:!0,size:3,fill:le(n.color,.3)}}],axes:[{side:2,show:!Ie,label:`${e.label} (${e.unit})`,stroke:"#555564",grid:{stroke:"#2e2e34",width:1},font:"10px system-ui, sans-serif",values:(v,g)=>g.map(E=>E!=null?e.fmtAxis(E):""),size:30},{side:3,show:!Ie,label:`${n.label} (${n.unit})`,stroke:"#555564",grid:{stroke:"#2e2e34",width:1},font:"10px system-ui, sans-serif",values:(v,g)=>g.map(E=>E!=null?n.fmtAxis(E):""),size:40}],cursor:{drag:{x:!1,y:!1},dataIdx:(v,g,E)=>{let H=v.cursor.left,L=v.cursor.top;if(H!=null&&L!=null&&H>=0&&L>=0){let h=1/0,m=-1;for(let p=0;p=0&&h<2500)return m}return E},points:{size:7,stroke:"#fff",width:2,fill:n.color}},legend:{show:!1},hooks:{draw:[v=>{let g=v.ctx,E=window.devicePixelRatio||1;g.save(),g.beginPath(),g.strokeStyle="rgba(255, 255, 255, 0.15)",g.lineWidth=1*E;let H=!1;for(let L=0;L{let g=v.cursor.idx,E=document.getElementById("combined-tooltip");if(E||(E=document.createElement("div"),E.id="combined-tooltip",E.style.position="absolute",E.style.background="rgba(14, 14, 16, 0.9)",E.style.color="#fff",E.style.padding="8px 12px",E.style.borderRadius="4px",E.classList.add("font-m"),E.style.fontFamily="system-ui",E.style.pointerEvents="none",E.style.zIndex="1000",E.style.boxShadow="0 4px 6px rgba(0,0,0,0.3)",E.style.border="1px solid #2e2e34",document.body.appendChild(E)),g!=null&&Xe[g]&&v.cursor.left>=0){let H=Xe[g],L=H[2],h=H[3],m=H[5],p=H[6];E.innerHTML=` +
Point #${g}
+
+ Time: + ${he(L)} +
+
+ Distance: + ${h.toFixed(2)} km +
+
+ ${e.label}: + ${e.fmtAxis(m)} ${e.unit} +
+
+ ${n.label}: + ${n.fmtAxis(p)} ${n.unit} +
+ `,E.style.display="block";let k=v.over.getBoundingClientRect();E.style.left=`${k.left+window.scrollX+v.cursor.left+15}px`,E.style.top=`${k.top+window.scrollY+v.cursor.top+15}px`}else E.style.display="none"}]}};t.querySelector(".empty-text")&&(t.innerHTML=""),me&&me.destroy(),me=new Zl(B,[l,f],t),me.over.addEventListener("click",()=>{if(me){let v=me.cursor.idx;if(v!=null&&Xe[v]){let g=Xe[v][4];St.setCursorAt(g)}}}),Gn()}import Ln from"uplot";function gn(t,s){let e=0;for(let p=0;pe&&(e=k)}e||(e=Date.UTC(2026,7,1));let n=new Date(e),i=n.getUTCFullYear(),a=Date.UTC(i-1,n.getUTCMonth(),n.getUTCDate(),n.getUTCHours(),n.getUTCMinutes(),n.getUTCSeconds()),r=Date.UTC(i,n.getUTCMonth()-6,n.getUTCDate(),n.getUTCHours(),n.getUTCMinutes(),n.getUTCSeconds()),d=Date.UTC(i,n.getUTCMonth()-1,n.getUTCDate(),n.getUTCHours(),n.getUTCMinutes(),n.getUTCSeconds()),l=0,f=0,y=0,S=0,I=0,B=e;t.forEach(p=>{if(!p.stats.startTime)return;let k=s(p);if(k===void 0)return;I+=k;let j=p.stats.startTime;j=a&&(f+=k),j>=r&&(y+=k),j>=d&&(S+=k)});let v=7*24*60*60*1e3,g=Date.UTC(i,0,1),E=Math.max(.1,(e-g)/v),H=Math.max(.1,(e-a)/v),L=Math.max(.1,(e-r)/v),h=Math.max(.1,(e-d)/v),m=Math.max(.1,(e-B)/v);return{currentYear:l,pastYear:f,past6m:y,past1m:S,total:I,currentYearAvg:l/E,pastYearAvg:f/H,past6mAvg:y/L,past1mAvg:S/h,totalAvg:I/m}}function Kl(t,s,e){let n=s?1e3:1,i=a=>(s?a/n:a).toLocaleString("en-US",{minimumFractionDigits:s?1:0,maximumFractionDigits:s?1:0});return` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Period${s?"Distance":"Elevation"}${s?"Distance/Week":"Elevation/Week"}
Current Year${i(t.currentYear)} ${e}${i(t.currentYearAvg)} ${e}
Past Year${i(t.pastYear)} ${e}${i(t.pastYearAvg)} ${e}
Past 6 Months${i(t.past6m)} ${e}${i(t.past6mAvg)} ${e}
Past 1 Month${i(t.past1m)} ${e}${i(t.past1mAvg)} ${e}
Total${i(t.total)} ${e}${i(t.totalAvg)} ${e}
+ `}function li(t){let s=gn(t,d=>d.stats.totalDist),e=gn(t,d=>d.stats.elevGain),n=gn(t,d=>d.stats.duration?d.stats.duration/36e5:0),i=gn(t,d=>{let l=(d.stats.duration||0)/1e3,f=d.stats.avgPower||150,y=200,S=f/y;return l*f*S/(y*3600)*100}),a=gn(t,d=>{let l=(d.stats.duration||0)/1e3,f=d.stats.avgPower||0;return l*f/1e3}),r=gn(t,d=>(d.stats.sections||[]).filter(f=>f.kind==="climb").length);return{distance:s,elevation:e,durationHours:n,tss:i,workKj:a,climbCount:r}}function Jl(t){let s=li(t),e=(i,a=1)=>i.toLocaleString("en-US",{minimumFractionDigits:a,maximumFractionDigits:a});return` +
+
+
+ monitoring + Accumulated Longitudinal Progress Summary +
+
+ + + + + + + + + + + + + + ${[{label:"Distance",unit:"km",data:s.distance,scale:.001,digits:1},{label:"Elevation Gain",unit:"m",data:s.elevation,scale:1,digits:0},{label:"Moving Duration",unit:"h",data:s.durationHours,scale:1,digits:1},{label:"Training Stress (TSS)",unit:"TSS",data:s.tss,scale:1,digits:0},{label:"Mechanical Work",unit:"kJ",data:s.workKj,scale:1,digits:0},{label:"Categorized Climbs",unit:"climbs",data:s.climbCount,scale:1,digits:0}].map(i=>` + + + + + + + + + `).join("")} + +
MetricPast 1MPast 6MPast YearCurrent YearTotalWeekly Avg
${i.label}${e(i.data.past1m*i.scale,i.digits)} ${i.unit}${e(i.data.past6m*i.scale,i.digits)} ${i.unit}${e(i.data.pastYear*i.scale,i.digits)} ${i.unit}${e(i.data.currentYear*i.scale,i.digits)} ${i.unit}${e(i.data.total*i.scale,i.digits)} ${i.unit}${e(i.data.totalAvg*i.scale,i.digits)} ${i.unit}/wk
+
+ `}var An=[],Xn=[],js="week";function lo(){An.forEach(t=>t.destroy()),An=[],Xn=[]}function oo(){lo(),js="week";let t=document.getElementById("evolution-view");t&&(t.innerHTML=` +
+ no_sim +
Select a track for progress analysis
+
+ `)}function Gs(t){js=t,Xn.forEach(s=>{s&&s.updateGrouping&&s.updateGrouping(t)})}function io(t){jt.patch({progress:t==="all"?null:t});let s=document.querySelector("#evolution-time-range");s&&s.querySelectorAll(".seg-btn").forEach(e=>{e.classList.toggle("active",e.getAttribute("data-range")===t)}),Xn.forEach(e=>{e&&e.updateRange&&e.updateRange(t)})}function ao(t){Xn.forEach(s=>{s&&s.updatePin&&s.updatePin(t)})}var Qn=16384,Zn=new Float64Array(Qn+1),Tn=new Float64Array(Qn+1),Os=new Float64Array(Qn+1),to=new Float64Array(Qn),Ys=new Float64Array(365),eo=[1,2,3,5,8,10,15,20,30,45,60,90,120,180,240,300,360,450,600,750,900,1200];function Ms(t,s,e,n){lo();let i=document.getElementById("evolution-view");if(!i)return;let a=jt.get().progress||"all";s.forEach(m=>{re.ensureCurvesAndZones(m)});let r=0,d=0;Ys.fill(0);for(let m=0;mk)continue;let R=k-u,M=0,O=0;for(let q=0;q<=R;q++){let V=Tn[q+u]-Tn[q],Bt=Zn[q+u]-Zn[q];Bt>M&&(M=Bt),V>O&&(O=V)}d+=(M+O+Os[R])/u*.001}let j=2/8,U=1-j,Q=2/43,Y=1-Q,G=52,ot=1/(195-G),_t=0,ct=0;for(let b=0;b +
+
+ + + + +
+
+
+
+ + + + + + +
+
+ +
+ `;let S=document.getElementById("evolution-container");if(!S)return;S.insertAdjacentHTML("beforeend",Jl(s));let I=S.clientWidth||570,B=Ln.sync("progress-sync");y.push(so(S,s,"distance",B,n,I)),y.push(so(S,s,"elevation",B,n,I));let v=S.querySelector(".distance-progress-card .chart-scroll-container"),g=S.querySelector(".elevation-progress-card .chart-scroll-container");v&&g&&(v.addEventListener("scroll",()=>{g.scrollLeft!==v.scrollLeft&&(g.scrollLeft=v.scrollLeft)}),g.addEventListener("scroll",()=>{v.scrollLeft!==g.scrollLeft&&(v.scrollLeft=g.scrollLeft)}));let E=i.querySelector("#evolution-time-range");E&&E.querySelectorAll(".seg-btn").forEach(m=>{m.addEventListener("click",()=>{let p=m.getAttribute("data-range");p&&(E.querySelectorAll(".seg-btn").forEach(k=>k.classList.remove("active")),m.classList.add("active"),h(p))})}),i.querySelectorAll(".metric-pill").forEach(m=>{m.addEventListener("click",()=>{let p=m.getAttribute("data-target");p&&document.getElementById(`${p}-evolution-card`)?.scrollIntoView({behavior:"smooth"})})});function L(m){y.forEach(p=>{p&&p.updatePin&&p.updatePin(m)})}function h(m){jt.patch({progress:m==="all"?null:m}),y.forEach(p=>{p&&p.updateRange&&p.updateRange(m)})}if(s.some(m=>m.stats.powerCurve)){let m=no({grid:S,currentTrack:t,allTracks:s,metricKey:"power",label:"Power Curve Progress",unit:"W",color:Rt.power.color,icon:"bolt",onPinChange:L,onRangeChange:h,onTrackSelect:e,gridWidth:I});y.push(m)}if(s.some(m=>m.stats.hrCurve)){let m=no({grid:S,currentTrack:t,allTracks:s,metricKey:"hr",label:"Heart Rate Curve Progress",unit:"bpm",color:Rt.hr.color,icon:"favorite",onPinChange:L,onRangeChange:h,onTrackSelect:e,gridWidth:I});y.push(m)}a!=="all"&&y.forEach(m=>{m&&m.updateRange&&m.updateRange(a)}),An.forEach(m=>{m._ready=!0})}function no(t){let{grid:s,currentTrack:e,allTracks:n,metricKey:i,label:a,unit:r,color:d,icon:l,onPinChange:f,onRangeChange:y,onTrackSelect:S,gridWidth:I}=t,B=e&&e.name||"Current Track",v=null,g=document.createElement("div");g.className="chart-row evolution-card-group",g.id=`${i}-evolution-card`,s.appendChild(g);let E=document.createElement("div");E.className="chart-row progress-chart-row",E.style.setProperty("--chart-color",d),E.style.flex="0 0 400px",E.style.borderBottom="none",E.innerHTML=` +
+
+ ${l} + ${a} +
+
+
+
+
+ `;let H=document.createElement("div");H.className="evolution-legend",H.innerHTML=` +
+ + ${B} +
+
+ + All-Time Max +
+ `;let h=jt.get().progress||"all",m=document.createElement("div");m.className="chart-row progress-chart-row",m.style.setProperty("--chart-color",d),m.style.flex="1",m.style.borderBottom="none",m.innerHTML=` +
+
+ ${l} + Progress ${i==="power"?"Power":"HR"} +
+
+
+
+
+ `;let p=document.createElement("div");p.style.cssText="display: flex; flex-direction: row; gap: 0px; align-items: stretch;",p.appendChild(E),p.appendChild(m),g.appendChild(p),g.appendChild(H);let k=E.querySelector(`#${i}-evolution-chart`);if(!k)return;let j={};n.forEach(at=>{let nt=i==="power"?at.stats.powerCurve:at.stats.hrCurve;if(!nt)return;let Ct=at.name||"Unnamed Track",gt=at.stats.startTime||at.addedAt?nn(at.stats.startTime||at.addedAt):"Unknown Date",W=`${Ct} (${gt})`;Object.entries(nt).forEach(([F,it])=>{let K=Number(F),ht=it.power||it.hr;(!j[K]||ht>j[K].val)&&(j[K]={val:ht,trackInfo:W,trackId:at.id,idx:it.idx})})});let U=e&&(i==="power"?e.stats.powerCurve:e.stats.hrCurve)||{},Q=new Set;Object.keys(j).forEach(at=>Q.add(Number(at))),Object.keys(U).forEach(at=>Q.add(Number(at)));let Y=Array.from(Q).sort((at,nt)=>at-nt),G=Y,ot=Y.map(at=>{let nt=U[at];return nt?nt.power||nt.hr:null}),_t=Y.map(at=>j[at]?.val||null),ct=n.filter(at=>(at.stats.startTime||at.addedAt)!=null);ct.sort((at,nt)=>(at.stats.startTime||at.addedAt||0)-(nt.stats.startTime||nt.addedAt||0)||at.id.localeCompare(nt.id));let w=[],b={};Y.forEach(at=>b[at]=[]),ct.forEach(at=>{let nt=i==="power"?at.stats.powerCurve:at.stats.hrCurve;nt&&(w.push((at.stats.startTime||at.addedAt)/1e3),Y.forEach(Ct=>{let gt=nt[Ct],W=gt?i==="power"?gt.power:gt.hr:null;b[Ct].push(W)}))});let u=Y[0]||5,R=ct,M=null;function O(at,nt){let Ct=w[w.length-1]||1785542400,gt=0;nt==="1m"?gt=Ct-30*24*3600:nt==="2m"?gt=Ct-60*24*3600:nt==="6m"?gt=Ct-180*24*3600:nt==="1y"?gt=Ct-365*24*3600:nt==="2y"&&(gt=Ct-2*365*24*3600);let W=[];w.forEach((Pt,ut)=>{Pt>=gt&&W.push(ut)});let F=W.map(Pt=>w[Pt]),it=b[at]||[],K=W.map(Pt=>it[Pt]),ht=En(K,5);return R=W.map(Pt=>ct[Pt]),{xFiltered:F,rawYFiltered:K,smoothedYFiltered:ht}}let q=null,V=document.createElement("div");return V.className="cur-y-val font-m",V.style.cssText="position:absolute;background:rgba(14, 14, 16, 0.9);color:#fff;padding:8px 12px;border-radius:4px;font-family:system-ui;pointer-events:none;z-index:1000;box-shadow:0 4px 6px rgba(0,0,0,0.3);border:1px solid #2e2e34;display:none;",k.style.position="relative",k.appendChild(V),(()=>{let at={id:`${i}-evolution`,width:400,height:250,padding:[0,10,0,10],scales:{x:{time:!1,distr:3,auto:!0},y:{auto:!0}},series:[{},{label:"Current Track",stroke:d,width:2,fill:le(d,.1),points:{show:!0,size:5,fill:d}},{label:"All-Time Max",stroke:"#888888",width:1,fill:"transparent",points:{show:!0,size:6,fill:"#888888"}}],axes:[{stroke:"#555564",grid:{stroke:"#2e2e34",width:1},show:!0,values:()=>[],ticks:{show:!1},font:"10px system-ui, sans-serif",size:30},{stroke:"#555564",grid:{stroke:"#2e2e34",width:1},values:(W,F)=>F.map(it=>`${it}${r}`),font:"10px system-ui, sans-serif",size:50}],cursor:{drag:{x:!1,y:!1},dataIdx:(W,F,it)=>it,x:!1,y:!1},hooks:{draw:[W=>{let F=W.ctx,it=window.devicePixelRatio||1,K=W.bbox,ht=[1,2,5,10,20,30,60,120,300,600,1200,1800,3600,7200,10800],Pt=G[G.length-1];F.save(),F.fillStyle="#555564",F.textAlign="center",F.textBaseline="top",F.font=`${10*it}px system-ui, sans-serif`;let ut=-100;if(ht.forEach(Ht=>{if(Ht>Pt)return;let dt=W.valToPos(Ht,"x",!0);dt>ut+30*it&&dt=0){let dt=W.valToPos(M,"x",!0),Lt=W.valToPos(ot[Ht]||_t[Ht],"y",!0);F.save(),F.beginPath(),F.setLineDash([]);let st=ot[Ht],ft=st!==null?5*it:3*it,At=st!==null?d:"#888888";F.arc(dt,Lt,ft,0,2*Math.PI),F.fillStyle=At,F.fill(),F.strokeStyle="#fff",F.lineWidth=2*it,F.stroke(),F.restore()}}F.restore()},W=>{let F=W.ctx,it=window.devicePixelRatio||1,K=W.bbox,ht=W.cursor.idx,Pt=1,ut=W.cursor.left,Ht=W.cursor.top;if(ut!=null&&Ht!=null&&ut>=0&&Ht>=0){let dt=1/0,Lt=-1,st=30,ft=st*st;for(let At=0;At{if(!W._ready)return;W.redraw(!1);let F=W.cursor.idx,it=1,K=W.cursor.left,ht=W.cursor.top;if(K!=null&&ht!=null&&K>=0&&ht>=0){let Lt=1/0,st=-1,ft=30,At=ft*ft;for(let zt=0;zt${ze(ft)}`;if(At!==null&&(z+=` +
+ Current: + ${At} ${r} +
+ `),zt!==null&&(z+=` +
+ Max: + ${zt} ${r} +
+ `),V.innerHTML=z,q&&M===null){u=ft;let{xFiltered:_,rawYFiltered:Ft,smoothedYFiltered:It}=O(u,h),Nt=It.map(vt=>vt!==null?vt*.9:null),Et=It.map(vt=>vt!==null?vt*1.1:null);q.setData([_,Ft,It,Nt,Et]);let bt=document.getElementById(`${i}-timeline-label`);bt&&(bt.textContent=`Progress ${ze(ft)} ${i==="power"?"Power":"HR"}`)}V.style.display="",V.style.left=`${K+Ht.left}px`,V.style.top=`${ht+Ht.top}px`,V.style.transform="translate(5px, -50%)"}else V.style.display="none"}]}};v=new Ln(at,[G,ot,_t],k),An.push(v),k.addEventListener("click",()=>{if(!v)return;let W=v.cursor.idx;if(W!=null){let F=G[W],it=null;if(M!==null?it=null:it=F,f(it),e){let K=e.stats,ht=i==="power"?K.powerCurve:K.hrCurve;if(ht){let Pt=ht[F];if(Pt){let ut=Pt.idx,Ht=F,dt=e.points[ut],Lt=e.points[ut+Ht-1];if(dt&&Lt&&dt.time!=null&&Lt.time!=null){let st=e.points[0].time||0,ft=(dt.time-st)/1e3,At=(Lt.time-st)/1e3;S?.(e.id,[ft,At])}}}}}}),k.addEventListener("mouseleave",()=>{v&&v.redraw()});let nt=document.createElement("details");nt.classList.add("progress-details"),nt.style.marginTop="0px",nt.style.setProperty("--chart-color",d),nt.innerHTML=` + + chevron_right + table_chart + Data Table + + + + + + + + + + + ${Y.map(W=>{let F=U[W]?U[W].power||U[W].hr:"-",it=j[W]?.val||"-",K=j[W]?.trackId,ht=j[W]?.idx;return` + + + + + + `}).join("")} + +
DurationCurrentAll-Time Max
${ze(W)}${F} ${r} + ${it} ${r} + ${K?``:""} +
+ `,g.appendChild(nt),nt.querySelectorAll(".evolution-select-btn").forEach(W=>{W.addEventListener("click",F=>{F.preventDefault(),F.stopPropagation();let it=W.getAttribute("data-id"),K=W.getAttribute("data-idx"),ht=W.getAttribute("data-dur");it&&document.dispatchEvent(new CustomEvent("select-track",{detail:{id:it,idx:K?Number(K):null,dur:ht?Number(ht):null}}))})});let Ct=null,gt=m.querySelector(`#${i}-timeline-chart`);if(gt){gt.style.position="relative";let W=document.createElement("div");if(W.className="timeline-tooltip font-m",W.style.cssText="position:absolute;background:rgba(14, 14, 16, 0.9);color:#fff;padding:8px 12px;border-radius:4px;font-family:system-ui;pointer-events:none;z-index:1000;box-shadow:0 4px 6px rgba(0,0,0,0.3);border:1px solid #2e2e34;display:none;",gt.appendChild(W),w.length>0){let{xFiltered:F,rawYFiltered:it,smoothedYFiltered:K}=O(u,h),ht=K.map(st=>st!==null?st*.9:null),Pt=K.map(st=>st!==null?st*1.1:null),ut=[F,it,K,ht,Pt],Ht=[{value:(st,ft)=>ft!=null?nn(ft*1e3):"-"},{label:"Value",stroke:d,width:0,points:{show:!0,size:6,fill:le(d,.5)}},{label:"Trend",stroke:d,width:2,points:{show:!1},spanGaps:!0,fill:"transparent",dash:[5,5]},{label:"Lower Bound",stroke:"transparent",width:0,points:{show:!1},spanGaps:!0},{label:"Upper Bound",stroke:"transparent",width:0,points:{show:!1},spanGaps:!0}],dt=document.getElementById(`${i}-timeline-label`);dt&&(dt.textContent=`Progress ${ze(u)} ${i==="power"?"Power":"HR"}`);let Lt={width:Math.max(100,(I||570)-400),height:250,padding:[0,10,0,10],tzDate:st=>Ln.tzDate(new Date(st*1e3),"Etc/UTC"),cursor:{points:{show:!1},drag:{setScale:!1},x:!1,y:!1},scales:{x:{time:!0,range:st=>{let ft=st.data[0][0],At=st.data[0][st.data[0].length-1];return ft===void 0||At===void 0?[0,100]:ft===At?[ft-3600,At+3600]:[ft,At]}},y:{range:(st,ft,At)=>[ft*.9,At*1.1]}},series:Ht,bands:[{series:[4,3],fill:le(d,.2)}],hooks:{init:[st=>{st.over.addEventListener("click",()=>{if(Ct!=null){let ft=R[Ct];if(ft){let At=ft.stats,zt=i==="power"?At.powerCurve:At.hrCurve;if(zt){let z=zt[u];if(z){let _=z.idx,Ft=u,It=ft.points[_],Nt=ft.points[_+Ft-1];if(It&&Nt&&It.time!=null&&Nt.time!=null){let Et=ft.points[0].time||0,bt=(It.time-Et)/1e3,vt=(Nt.time-Et)/1e3;t.onTrackSelect?.(ft.id,[bt,vt])}else t.onTrackSelect?.(ft.id,null)}else t.onTrackSelect?.(ft.id,null)}else t.onTrackSelect?.(ft.id,null)}}})}],draw:[st=>{let ft=st.cursor.idx;if(ft!=null&&st.data[0][ft]!=null){let At=st.data[1][ft];if(At!=null){let zt=st.valToPos(st.data[0][ft],"x",!0),z=st.valToPos(At,"y",!0),_=st.ctx,Ft=window.devicePixelRatio||1;_.save(),_.beginPath(),_.setLineDash([2*Ft,2*Ft]),_.strokeStyle=le(d,.4),_.lineWidth=1*Ft,_.moveTo(zt,st.bbox.top),_.lineTo(zt,st.bbox.top+st.bbox.height),_.moveTo(st.bbox.left,z),_.lineTo(st.bbox.left+st.bbox.width,z),_.stroke(),_.restore(),_.save(),_.beginPath(),_.setLineDash([]),_.arc(zt,z,12*Ft,0,2*Math.PI),_.fillStyle=le(d,.2),_.fill(),_.restore(),_.save(),_.beginPath(),_.arc(zt,z,6*Ft,0,2*Math.PI),_.fillStyle=d,_.fill(),_.strokeStyle="#fff",_.lineWidth=2*Ft,_.stroke(),_.restore(),_.save(),_.setLineDash([]);let It=new Date(st.data[0][ft]*1e3),Nt=`${It.getUTCFullYear()}-${String(It.getUTCMonth()+1).padStart(2,"0")}-${String(It.getUTCDate()).padStart(2,"0")}`;_.font=`bold ${10*Ft}px system-ui, sans-serif`;let bt=_.measureText(Nt).width+10*Ft,vt=16*Ft,Dt=zt-bt/2,Wt=Math.max(st.bbox.left,Math.min(st.bbox.left+st.bbox.width-bt,Dt)),Ot=st.bbox.top+st.bbox.height+2*Ft;_.fillStyle="rgba(14, 14, 16, 0.92)",_.beginPath(),_.roundRect?_.roundRect(Wt,Ot,bt,vt,3*Ft):_.rect(Wt,Ot,bt,vt),_.fill(),_.strokeStyle=le(d,.27),_.lineWidth=1*Ft,_.stroke(),_.fillStyle=d,_.textAlign="center",_.textBaseline="middle",_.fillText(Nt,Wt+bt/2,Ot+vt/2);let te=`${At.toFixed(0)} ${r}`,Fe=_.measureText(te).width+10*Ft,Pe=st.bbox.left-Fe-2*Ft,_n=z-vt/2;_.fillStyle="rgba(14, 14, 16, 0.92)",_.beginPath(),_.roundRect?_.roundRect(Pe,_n,Fe,vt,3*Ft):_.rect(Pe,_n,Fe,vt),_.fill(),_.strokeStyle=le(d,.27),_.lineWidth=1*Ft,_.stroke(),_.fillStyle=d,_.textAlign="left",_.textBaseline="middle",_.fillText(te,Pe+5*Ft,_n+vt/2),_.restore()}}}],setCursor:[st=>{if(!st._ready)return;st.redraw(!1);let ft=st.cursor.idx,At=st.cursor.left,zt=st.cursor.top,z=st.bbox;if(At!=null&&zt!=null&&At>=0&&zt>=0){let _=1/0,Ft=-1,It=30,Nt=It*It,Et=st.data[0],bt=st.data[1];for(let vt=0;vt + Date: + ${Et} + +
+ Value: + ${vt} +
+ `,W.style.display="block",W.style.left=`${At+z.left}px`,W.style.top=`${zt+z.top}px`,W.style.transform="translate(5px, -50%)"}else W.style.display="none"}]},axes:[{space:50,stroke:"#555564",grid:{stroke:"#2e2e34"},show:!0,font:"10px system-ui, sans-serif",size:30,values:(st,ft)=>ft.map(At=>{let zt=new Date(At*1e3);return`${zt.getUTCFullYear()}-${String(zt.getUTCMonth()+1).padStart(2,"0")}`})},{stroke:"#555564",grid:{stroke:"#2e2e34"},font:"10px system-ui, sans-serif",values:(st,ft)=>ft.map(At=>`${At} ${r}`),show:!0,space:15,size:50}]};q=new Ln(Lt,ut,gt),An.push(q),gt.addEventListener("mouseleave",()=>{q.redraw()})}else gt.innerHTML='
No data for the past year
'}})(),{updatePin:at=>{M=at,at!==null&&(u=at);let nt=document.getElementById(`${i}-timeline-label`);if(nt&&(nt.textContent=`Progress ${ze(u)} ${i==="power"?"Power":"HR"}${M!==null?" (Pinned)":""}`),q){let{xFiltered:Ct,rawYFiltered:gt,smoothedYFiltered:W}=O(u,h),F=W.map(K=>K!==null?K*.9:null),it=W.map(K=>K!==null?K*1.1:null);q.setData([Ct,gt,W,F,it])}v&&v.redraw()},updateRange:at=>{if(h=at,q){let{xFiltered:nt,rawYFiltered:Ct,smoothedYFiltered:gt}=O(u,h),W=gt.map(it=>it!==null?it*.9:null),F=gt.map(it=>it!==null?it*1.1:null);q.setData([nt,Ct,gt,W,F])}}}}function ro(t){let s=new Date(t),e=s.getUTCDay(),n=s.getUTCDate()-e+(e===0?-6:1);return s.setUTCDate(n),s.setUTCHours(0,0,0,0),s}function oi(t){let s={};return t.forEach(e=>{if(!e.stats.startTime||!e.stats.totalDist)return;let n=new Date(e.stats.startTime),i=ro(n),a=i.toISOString().split("T")[0];s[a]||(s[a]={timestamp:i.getTime(),distance:0}),s[a].distance+=e.stats.totalDist}),Object.entries(s).map(([e,n])=>({week:e,timestamp:n.timestamp,distance:n.distance})).sort((e,n)=>e.timestamp-n.timestamp)}function ii(t){let s={};return t.forEach(e=>{if(!e.stats.startTime||e.stats.elevGain===void 0)return;let n=new Date(e.stats.startTime),i=ro(n),a=i.toISOString().split("T")[0];s[a]||(s[a]={timestamp:i.getTime(),elevation:0}),s[a].elevation+=e.stats.elevGain}),Object.entries(s).map(([e,n])=>({week:e,timestamp:n.timestamp,elevation:n.elevation})).sort((e,n)=>e.timestamp-n.timestamp)}function ai(t){let s={};return t.forEach(e=>{if(!e.stats.startTime||!e.stats.totalDist)return;let n=new Date(e.stats.startTime),i=new Date(Date.UTC(n.getUTCFullYear(),n.getUTCMonth(),1)),a=i.toISOString().slice(0,7);s[a]||(s[a]={timestamp:i.getTime(),distance:0}),s[a].distance+=e.stats.totalDist}),Object.entries(s).map(([e,n])=>({week:e,timestamp:n.timestamp,distance:n.distance})).sort((e,n)=>e.timestamp-n.timestamp)}function ri(t){let s={};return t.forEach(e=>{if(!e.stats.startTime||e.stats.elevGain===void 0)return;let n=new Date(e.stats.startTime),i=new Date(Date.UTC(n.getUTCFullYear(),n.getUTCMonth(),1)),a=i.toISOString().slice(0,7);s[a]||(s[a]={timestamp:i.getTime(),elevation:0}),s[a].elevation+=e.stats.elevGain}),Object.entries(s).map(([e,n])=>({week:e,timestamp:n.timestamp,elevation:n.elevation})).sort((e,n)=>e.timestamp-n.timestamp)}function so(t,s,e,n,i,a){let r=e==="distance",d=js,l=jt.get().progress||"all",f=r?oi(s):ii(s),y=r?ai(s):ri(s),S=d==="month"?y:f,I=gn(s,ct=>r?ct.stats.totalDist:ct.stats.elevGain),B=r?Rt.distance.color:Rt.elevation.color,v=r?"km":"m",g=r?Rt.distance.icon:Rt.elevation.icon,E=document.createElement("div");E.className=`chart-row ${e}-progress-card`,E.id=`${e}-evolution-card`,E.innerHTML=` +
+
+ ${g} + ${r?"Distance Progress":"Elevation Progress"} +
+
+
+
+
+
+
+
+ ${Kl(I,r,v)} +
+
+ `,t.appendChild(E);let H=E.querySelector(`#${e}-weekly-chart`),L=E.querySelector(`#${e}-y-axis-chart`);if(!H||!L)return;H.style.position="relative";let h=document.createElement("div");h.className="cur-y-val",h.style.cssText=`color:${B};display:none`,H.appendChild(h);let m=S.map(ct=>ct.timestamp/1e3),p=r?S.map(ct=>ct.distance/1e3):S.map(ct=>ct.elevation);if(m.length===0)return;let k=En(p,2),j=Math.max(0,(a||570)-20-320-20),U=Math.max(j,m.length*40),Q=Math.max(...p)*1.1,Y={width:U,height:150,padding:[10,10,0,10],tzDate:ct=>Ln.tzDate(new Date(ct*1e3),"Etc/UTC"),scales:{x:{time:!0},y:{range:[0,Q]}},series:[{},{label:r?"Distance":"Elevation",stroke:B,fill:le(B,.1),width:2,points:{show:!0,size:4}},{label:"Trend",stroke:B,width:2,dash:[5,5],points:{show:!1}}],cursor:{sync:n?{key:n.key}:void 0,drag:{x:!1,y:!1},points:{show:!1},x:!1,y:!1},hooks:{init:[ct=>{ct.over.addEventListener("click",()=>{let w=ct.cursor.idx;if(w!=null){let b=S[w];if(b){let u=new Date(b.timestamp),R=new Date(b.timestamp+7*24*60*60*1e3),M=u.toISOString().split("T")[0],O=R.toISOString().split("T")[0];i?.(M,O)}}})}],setCursor:[ct=>{if(!ct._ready)return;let w=ct.cursor.idx,b=ct.cursor.left,u=ct.cursor.top;if(w!=null&&b!=null&&u!=null&&b>=0&&u>=0){let R=ct.data[1][w],M=R!=null?r?`${R.toFixed(1)} km`:`${R.toFixed(0)} m`:"\u2014";h.innerHTML=M,h.style.display="block",h.style.left=`${ct.valToPos(ct.data[0][w],"x",!1)}px`,h.style.top=`${ct.valToPos(ct.data[1][w],"y",!1)}px`,h.style.transform="translate(6px, -50%)"}else h.style.display="none";ct.redraw(!1)}],draw:[ct=>{let w=ct.cursor.idx;if(w!=null&&ct.data[0][w]!=null){let b=ct.valToPos(ct.data[0][w],"x",!0),u=ct.valToPos(ct.data[1][w],"y",!0),R=window.devicePixelRatio||1,M=ct.ctx,O=ct.bbox;M.save(),M.beginPath(),M.setLineDash([2*R,2*R]),M.strokeStyle=le(B,.4),M.lineWidth=1*R,M.moveTo(b,O.top),M.lineTo(b,O.top+O.height),M.moveTo(O.left,u),M.lineTo(O.left+O.width,u),M.stroke(),M.restore(),M.save(),M.beginPath(),M.setLineDash([]),M.arc(b,u,12*R,0,2*Math.PI),M.fillStyle=le(B,.2),M.fill(),M.restore(),M.save(),M.beginPath(),M.arc(b,u,6*R,0,2*Math.PI),M.fillStyle=B,M.fill(),M.strokeStyle="#fff",M.lineWidth=2*R,M.stroke(),M.restore(),M.save(),M.setLineDash([]);let q=new Date(ct.data[0][w]*1e3),V=`${q.getUTCFullYear()}-${String(q.getUTCMonth()+1).padStart(2,"0")}-${String(q.getUTCDate()).padStart(2,"0")}`;M.font=`bold ${10*R}px system-ui, sans-serif`;let at=M.measureText(V).width+10*R,nt=16*R,Ct=b-at/2,gt=Math.max(O.left,Math.min(O.left+O.width-at,Ct)),W=O.top+O.height+2*R;M.fillStyle="rgba(14, 14, 16, 0.92)",M.beginPath(),M.roundRect?M.roundRect(gt,W,at,nt,3*R):M.rect(gt,W,at,nt),M.fill(),M.strokeStyle=le(B,.27),M.lineWidth=1*R,M.stroke(),M.fillStyle=B,M.textAlign="center",M.textBaseline="middle",M.fillText(V,gt+at/2,W+nt/2),M.restore()}}]},axes:[{stroke:"#555564",grid:{stroke:"#2e2e34",width:1},show:!0,space:40,values:(ct,w)=>w.map(b=>{let u=new Date(b*1e3);return`${u.getUTCMonth()+1}/${u.getUTCDate()}`}),font:"10px system-ui, sans-serif",size:30},{stroke:"#555564",grid:{stroke:"#2e2e34",width:1},show:!0,values:(ct,w)=>w.map(b=>`${b.toFixed(0)} ${v}`),font:"10px system-ui, sans-serif",size:40}]},G=new Ln(Y,[m,p,k],H);An.push(G);let ot=E.querySelector(".chart-scroll-container");ot&&m.length*40>U&&(ot.scrollLeft=ot.scrollWidth-ot.clientWidth);function _t(){S=d==="month"?y:f;let ct=S[S.length-1]?.timestamp||17855424e5,w=0;l==="1m"?w=ct-30*24*3600*1e3:l==="2m"?w=ct-60*24*3600*1e3:l==="6m"?w=ct-180*24*3600*1e3:l==="1y"?w=ct-365*24*3600*1e3:l==="2y"&&(w=ct-2*365*24*3600*1e3);let b=S.filter(V=>V.timestamp>=w),u=b.map(V=>V.timestamp/1e3),R=r?b.map(V=>V.distance/1e3):b.map(V=>V.elevation),M=En(R,2),O=Math.max(H.clientWidth,u.length*40),q=Math.max(1,...R)*1.1;G.setSize({width:O,height:150}),G.setScale("y",{min:0,max:q}),G.setData([u,R,M]),ot&&(ot.scrollLeft=ot.scrollWidth-ot.clientWidth)}return{updateRange:ct=>{l=ct,_t()},updateGrouping:ct=>{d=ct,_t()}}}var co=["#FF6B35","#4ECDC4","#45B7D1","#F7DC6F","#FF6B6B","#BB8FCE","#82E0AA","#F8C471","#3b82f6"],ci=16,Gt={},He=[];function di(t,s){let e=new Date(t),n=new Date(s);return e.getUTCFullYear()===n.getUTCFullYear()&&e.getUTCMonth()===n.getUTCMonth()&&e.getUTCDate()===n.getUTCDate()}var In=!1,Xt=null,Zs=0,Kn=!1,Dn=null,Ke="",bn=!1,vn="date-desc",wt={date:[null,null],dist:[null,null],dur:[null,null],metrics:new Set,tags:[],sport:null};function hn(t=!1){jt.patch({f_date:wt.date.every(s=>s===null)?null:wt.date,f_dist:wt.dist.every(s=>s===null)?null:wt.dist,f_dur:wt.dur.every(s=>s===null)?null:wt.dur,f_mets:wt.metrics.size===0?null:Array.from(wt.metrics),f_tags:wt.tags.length===0?null:wt.tags},t)}function Xs(){let t=St.getActiveMetrics(),s=St.METRICS,e=Array.from(t).map(n=>s[n].abbr);jt.patch({metrics:e.length?e:null})}var fo=[{label:"Newest first",val:"date-desc",icon:"calendar_today"},{label:"Oldest first",val:"date-asc",icon:"history"},{label:"Longest distance",val:"dist-desc",icon:"straighten"},{label:"Shortest distance",val:"dist-asc",icon:"horizontal_rule"},{label:"Longest duration",val:"dur-desc",icon:"timer"},{label:"Shortest duration",val:"dur-asc",icon:"timer_off"}];function ui(t){document.querySelectorAll(".sort-menu-popup").forEach(i=>i.remove());let s=document.createElement("div");s.className="sort-menu-popup",fo.forEach(i=>{let a=document.createElement("div");a.className=`menu-item ${vn===i.val?"active":""}`,a.innerHTML=` + ${i.icon} + ${i.label} + ${vn===i.val?"check":""} + `,a.addEventListener("click",()=>{vn=i.val,jt.patch({sort:i.val}),ho(),Sn(),s.remove()}),s.appendChild(a)});let e=t.getBoundingClientRect();s.style.top=`${e.bottom+5}px`,s.style.left=`${e.left}px`,document.body.appendChild(s);let n=i=>{!s.contains(i.target)&&!t.contains(i.target)&&(s.remove(),document.removeEventListener("click",n))};setTimeout(()=>document.addEventListener("click",n),0)}function mi(t){document.querySelectorAll(".metric-menu-popup").forEach(d=>d.remove());let s=document.createElement("div");s.className="metric-menu-popup";let e=St.METRICS,n=St.getAvailableMetrics(),i=St.getActiveMetrics();Object.entries(e).forEach(([d,l])=>{if(!n.has(d))return;let f=document.createElement("div");f.className=`menu-item ${i.has(d)?"active":""}`,f.innerHTML=` + ${l.icon} + ${l.label} + ${i.has(d)?"check":""} + `,f.addEventListener("click",()=>{St.toggleMetric(d);let y=St.getActiveMetrics().has(d);f.classList.toggle("active",y),f.querySelector(".check").textContent=y?"check":"";let S=document.querySelector(`#metric-pills .metric-pill[data-metric="${d}"]`);S&&S.classList.toggle("active",y),Fn(),Xs()}),s.appendChild(f)});let a=t.getBoundingClientRect();s.style.top=`${a.bottom+5}px`,s.style.right=`${window.innerWidth-a.right}px`,document.body.appendChild(s);let r=d=>{!s.contains(d.target)&&!t.contains(d.target)&&(s.remove(),document.removeEventListener("click",r))};setTimeout(()=>document.addEventListener("click",r),0)}document.addEventListener("select-track",t=>{let s=t.detail.id,e=t.detail.idx,n=t.detail.dur;if(s&&Gt[s]&&(We(s),e!=null&&n!=null)){let i=Gt[s],a=i.points;if(e>=0&&eB.time!=null)?.time||0,l=(r.time-d)/1e3,f=l+n;St.restoreSelection(l,f);let y=a.findIndex(B=>(B.time||0)>=r.time+n*1e3);y===-1&&(y=a.length-1);let S=a.slice(e,y+1),I=re.computeStats(S);St.setSelectionStats(I),ke(i),qt.highlightSegment(s,a,e,y,!1,Dn),qt.ensureVisible(S.map(B=>[B.lat,B.lon])),jt.patch({sel:[l,f]})}}}});document.addEventListener("click",t=>{let s=t.target.closest(".tab-btn");if(!s)return;let e=s.closest("#tab-nav");if(!e)return;e.querySelectorAll(".tab-btn").forEach(a=>a.classList.toggle("active",a===s));let n=s.dataset.tab;jt.patch({tab:n},!0);let i=e.closest("#chart-panel");if(i&&i.querySelectorAll(".tab-content").forEach(a=>{a.classList.toggle("active",a.id===`tab-${s.dataset.tab}`)}),s.dataset.tab==="details"&&Xt&&Gt[Xt]&&Ee(Gt[Xt],Qs()),s.dataset.tab==="insights"&&Xt&&Gt[Xt]&&ke(Gt[Xt]),s.dataset.tab==="evolution"&&Ms(Xt?Gt[Xt]:null,Object.values(Gt),(a,r)=>{We(a,!0),r&&(St.restoreSelection(r[0],r[1]),Bn(r[0],r[1],"time",!0))},(a,r)=>{wt.date=[a,r],Ce(),hn(!0),Jn()}),s.dataset.tab==="combined"&&Xt&&Gt[Xt]&&qe(Gt[Xt]),s.dataset.tab==="courses"&&je(),s.dataset.tab==="graphs"){St.resize(),Fn();let a=jt.get().sel;a&&St.restoreSelection(a[0],a[1])}});async function uo(){typeof window.__resetBenchmarkSeed=="function"&&window.__resetBenchmarkSeed(),await ge.clear(),Gt={},He=[],Xt=null,Zs=0,Dn=null,In=!1,Kn=!1,Ke="",bn=!1,vn="date-desc",wt.date=[null,null],wt.dist=[null,null],wt.dur=[null,null],wt.metrics.clear(),wt.tags=[],re.setHRZones([120,140,160,180]),re.setFTP(200),_l(),Gl(),kl(),Ql(),oo(),qt.removeAllTracks&&qt.removeAllTracks(),qt.clearTracks&&qt.clearTracks(),qt.clearHighlight(),St.resetState?St.resetState():St.clear&&St.clear(),document.querySelectorAll(".tab-btn").forEach(s=>{s.classList.toggle("active",s.dataset.tab==="graphs")}),document.querySelectorAll(".tab-content").forEach(s=>{s.classList.toggle("active",s.id==="tab-graphs")}),jt.reset();let t=document.getElementById("toast-container");t&&(t.innerHTML=""),document.querySelectorAll(".modal-backdrop").forEach(s=>s.remove()),typeof Sn=="function"&&Sn()}var mo=!1;async function pi(){if(mo){qs((l,f)=>Hn(l,f==="error"?"error":"info")),Ds(()=>{Ce(),hn()},l=>{po(l)}),Bs((l,f)=>Hn(l,f==="error"?"error":"info")),Fn(),await ge.openDB(),await uo();return}mo=!0;let t=document.getElementById("storage-backend-select");t&&(t.value=jt.get().storage||"memory",t.addEventListener("change",l=>{let f=l.target.value,y=new URLSearchParams(window.location.search);f==="memory"?y.delete("storage"):y.set("storage",f),window.location.search=y.toString()}));let s=document.getElementById("map-loader"),e=s?.querySelector("span");s&&(e&&(e.textContent="Loading database..."),s.classList.remove("hidden"));let n=jt.get();qs((l,f)=>Hn(l,f==="error"?"error":"info")),Ds(()=>{Ce(),hn()},l=>{po(l)}),Bs((l,f)=>Hn(l,f==="error"?"error":"info")),qt.init(l=>We(l),vi,yi,(l,f)=>{l===Xt?St.setHoverAt(f):St.setHoverAt(null)},xi),St.init(wi,Bn,Ti,l=>{jt.patch({sel_point:l})}),St.setMapColorChangeCb(l=>{l?(Dn=l.colors,Xt&&qt.colorTrackByMetric(Xt,l.pts,l.colors)):(Dn=null,Xt&&qt.clearMetricColor(Xt));let f=jt.get();f.sel&&Bn(f.sel[0],f.sel[1],St.getXAxis())}),Mi(n),await ge.openDB(),await uo(),s&&s.classList.add("hidden"),hi(),bi(),Pi(),Fn(),document.getElementById("basemap-switcher")?.addEventListener("click",l=>{let f=l.target.closest(".bm-btn");f&&(document.querySelectorAll(".bm-btn").forEach(y=>y.classList.remove("active")),f.classList.add("active"),f.dataset.layer&&(qt.switchBasemap(f.dataset.layer),jt.patch({map:f.dataset.layer})))}),document.getElementById("btn-fit-all")?.addEventListener("click",()=>qt.fitAll()),document.getElementById("btn-clear-all")?.addEventListener("click",Si),document.getElementById("track-search")?.addEventListener("input",l=>{Ke=l.target.value,Ce(),jt.patch({q:Ke||null})}),document.getElementById("btn-regex-toggle")?.addEventListener("click",l=>{bn=!bn,l.target.classList.toggle("active",bn),Ce(),jt.patch({re:bn?1:null})}),document.getElementById("btn-sort-menu")?.addEventListener("click",l=>{l.stopPropagation(),ui(l.currentTarget)}),document.getElementById("btn-metric-menu")?.addEventListener("click",l=>{l.stopPropagation(),mi(l.currentTarget)});let i=(l,f)=>{f&&f.classList.toggle("active"),St.toggleMetric(l),Xs()};document.getElementById("metric-pills")?.addEventListener("click",l=>{let f=l.target.closest(".metric-pill");if(f){let y=f.dataset.metric;y&&(i(y,f),St.getActiveMetrics().has(y)&&document.querySelector(`.chart-row[data-metric="${y}"]`)?.scrollIntoView({behavior:"smooth"}))}}),document.getElementById("x-axis-ctrl")?.addEventListener("click",l=>{let f=l.target.closest(".seg-btn");if(!f)return;let y=f.dataset.axis;St.setXAxis(y),document.querySelectorAll("#x-axis-ctrl .seg-btn").forEach(S=>S.classList.toggle("active",S===f)),jt.patch({xaxis:y})}),document.getElementById("btn-follow-dot")?.addEventListener("click",l=>{Kn=!Kn,l.currentTarget.classList.toggle("active",Kn)}),document.getElementById("btn-reset-zoom")?.addEventListener("click",()=>{St.resetZoom(),qt.clearHighlight()}),Ci(),document.getElementById("file-input")?.addEventListener("change",l=>{$s(Array.from(l.target.files||[])),l.target.value=""}),document.getElementById("folder-input")?.addEventListener("change",l=>{$s(Array.from(l.target.files||[])),l.target.value=""}),document.getElementById("btn-toggle-filters")?.addEventListener("click",()=>{let l=document.getElementById("filter-panel");l&&(l.classList.toggle("hidden"),document.getElementById("btn-toggle-filters")?.classList.toggle("active",!l.classList.contains("hidden")))});let a=(l,f,y)=>{let S=y.target.value;wt[l][f]=S===""?null:S,Ce(),hn()};document.getElementById("filter-date-start")?.addEventListener("change",l=>a("date",0,l)),document.getElementById("filter-date-end")?.addEventListener("change",l=>a("date",1,l)),document.getElementById("filter-dist-min")?.addEventListener("input",l=>a("dist",0,l)),document.getElementById("filter-dist-max")?.addEventListener("input",l=>a("dist",1,l)),document.getElementById("filter-dur-min")?.addEventListener("input",l=>a("dur",0,l)),document.getElementById("filter-dur-max")?.addEventListener("input",l=>a("dur",1,l));let r=document.getElementById("sport-burger-btn"),d=document.getElementById("sport-dropdown-content");r?.addEventListener("click",l=>{if(l.stopPropagation(),d){let f=d.style.display==="block";d.style.display=f?"none":"block"}}),document.addEventListener("click",l=>{d&&d.style.display==="block"&&!d.contains(l.target)&&l.target!==r&&(d.style.display="none")}),document.getElementById("sport-filter-container")?.addEventListener("click",l=>{let f=l.target,y=f.closest("[data-sport]");if(y){let S=y.dataset.sport||null;wt.sport=wt.sport===S?null:S,Ce(),hn(),Jn(),f.closest(".dropdown-item")&&d&&(d.style.display="none")}}),document.getElementById("filter-metrics")?.addEventListener("click",l=>{let f=l.target.closest(".mini-pill");if(!f)return;let y=f.dataset.metric;wt.metrics.has(y)?wt.metrics.delete(y):wt.metrics.add(y),Jn(),Ce(),hn()}),document.getElementById("btn-reset-filters")?.addEventListener("click",()=>{wt={date:[null,null],dist:[null,null],dur:[null,null],metrics:new Set,tags:[],sport:null},Jn(),Ce(),hn()}),document.getElementById("filter-tags")?.addEventListener("input",l=>{let f=l.target.value;wt.tags=f.split(",").map(y=>y.trim().toLowerCase()).filter(y=>!!y),Ce(),hn()}),document.addEventListener("keydown",l=>{l.key==="Escape"&&(qt.hideCursor(),qt.closePopup?.(),document.querySelectorAll(".color-picker-popup, .metric-menu-popup, .sort-menu-popup, .modal-backdrop").forEach(f=>f.remove()))}),window.addEventListener("resize",()=>{St.resize(),Gn(),qt.invalidateSize?.(),Fn()})}async function fi(){let t=null,s=new Promise(i=>{t=window.setTimeout(i,1e3)});try{let i=document.fonts?Promise.all([document.fonts.load('24px "Material Symbols Rounded"').catch(()=>{}),document.fonts.ready]):Promise.resolve();await Promise.race([i,s])}finally{t!==null&&window.clearTimeout(t)}document.body.classList.remove("loading-fonts"),gi(),await go();let e=document.createElement("div");e.id="app-is-ready",e.setAttribute("data-ready","true"),e.style.display="none",document.body.appendChild(e);let n=document.createElement("div");n.id="appIsReady",n.setAttribute("data-ready","true"),n.style.display="none",document.body.appendChild(n)}var Es=null;async function go(){if(Es)return Es;try{let s=await(await fetch("./test-data/manifest.json")).json(),e=new TextDecoder;return Es=await Promise.all(s.map(async n=>{let a=await(await fetch(`./test-data/${n}`)).arrayBuffer(),r=n;if(n.toLowerCase().endsWith(".gz")){r=n.slice(0,-3);let f=new Uint8Array(a);if(f.length>=2&&f[0]===31&&f[1]===139){let y=new DecompressionStream("gzip"),S=new Blob([a]).stream().pipeThrough(y);a=await new Response(S).arrayBuffer()}}let d=r.toLowerCase().endsWith(".fit")?void 0:e.decode(a),l=new File([a],r,{type:"application/octet-stream"});return l._buf=a,d!==void 0&&(l._text=d),{cleanName:r,buf:a,text:d,file:l}})),typeof window.__resetBenchmarkSeed=="function"&&window.__resetBenchmarkSeed(),Es}catch{return[]}}function gi(){let t=document.getElementById("btn-step-load"),s=document.getElementById("btn-step-analysis"),e=document.getElementById("btn-step-insights"),n=document.getElementById("btn-step-details"),i=document.getElementById("btn-step-aggregate"),a=document.getElementById("btn-step-evolution"),r=document.getElementById("btn-step-courses");function d(l){document.querySelectorAll(".tab-btn").forEach(f=>{f.classList.toggle("active",f.dataset.tab===l)}),document.querySelectorAll(".tab-content").forEach(f=>{f.classList.toggle("active",f.id===`tab-${l}`)}),jt.patch({tab:l},!1)}t?.addEventListener("click",async()=>{try{await pi();let f=(await go()).map(S=>S.file);await $s(f,!0);let y=Object.values(Gt);re.findOverlaps(y);for(let S of["dist-desc","dur-desc","date-asc","date-desc"])vn=S,Sn();wt.sport="cycling",Ce(),wt.sport=null,wt.metrics.add("power"),Ce(),wt.metrics.clear(),Ke="Alpine",Ce(),Ke="",Ce(),!Xt&&y.length>0&&We(y[0].id,!0,!0)}finally{window.dispatchEvent(new CustomEvent("load-and-parse-complete"))}}),s?.addEventListener("click",()=>{try{let l=Object.values(Gt);d("graphs"),!Xt&&l.length>0&&We(l[0].id,!1,!0);let f=St.getAvailableMetrics(),y=[["elevation","speed","power"]];for(let I of y){let B=I.filter(v=>f.has(v));B.length>0&&(document.querySelectorAll("#metric-pills .metric-pill").forEach(v=>{let g=v.dataset.metric;g&&v.classList.toggle("active",B.includes(g))}),Fn(),St.setActiveMetrics(B))}St.setSmooth("power",!0,!0),St.toggleMapColor("power",!0),St.toggleMapColor(null,!0),St.setXAxis("distance"),St.setSmooth("power",!1,!0),St.setXAxis("time");let S=[[0,360,180],[120,900,300]];for(let[I,B,v]of S)St.restoreSelection(I,B,!0),St.setCursorAt(v,!0),St.analyzeTelemetryIntervals(l,I,B),Bn(I,B,St.getXAxis(),!1)}finally{window.dispatchEvent(new CustomEvent("analysis-complete"))}}),e?.addEventListener("click",()=>{try{let l=Object.values(Gt);l.forEach(y=>re.ensureCurvesAndZones(y)),d("insights");let f=Xt&&Gt[Xt]||l[0]||null;for(let y of[1,2,4]){let S=l[y];S&&(ke(S,l,[0,600]),ke(S,l,null))}l[3]&&ke(l[3],l,[200,900]),f&&(ke(f,l,[0,600]),ke(f,l,[600,1200])),ke(f,l,null)}finally{window.dispatchEvent(new CustomEvent("insights-complete"))}}),n?.addEventListener("click",()=>{try{let l=Object.values(Gt);d("details");let f=Qs(),y=Xt&&Gt[Xt]||l[0]||null;for(let S=0;S{try{let l=Object.values(Gt);if(l.length>=2){d("combined"),Us("distance","elevation"),qe(l[0],l);for(let y of[.25,.5,.75,1])Ss(y);Vs(!0),Ss(.5),Vs(!1),Us("power","hr");for(let y of[.3,.7])Ss(y);l.length>=6&&qe(l,l);let f=[l[0],l[1]];qe(f,l)}}finally{window.dispatchEvent(new CustomEvent("aggregate-complete"))}}),a?.addEventListener("click",()=>{try{let l=Object.values(Gt);d("evolution");let f=Xt&&Gt[Xt]||l[0]||null,y=(I,B)=>{We(I,!0),B&&(St.restoreSelection(B[0],B[1]),Bn(B[0],B[1],"time",!0))},S=[l[2]||f,f];for(let I of S){Ms(I,l,y),Gs("month");for(let B of["1m","6m","1y","all"])io(B);Gs("week");for(let B of[5,60,300,1200,null])ao(B)}}finally{window.dispatchEvent(new CustomEvent("evolution-complete"))}}),r?.addEventListener("click",()=>{try{let l=Object.values(Gt);d("courses"),Rl(l);let f=Nl();for(let y of f)Pn(y,!0);Pn("course-1",!0)}finally{window.dispatchEvent(new CustomEvent("courses-complete"))}}),window.benchmarkSteps={loadAndParseFiles:()=>document.getElementById("btn-step-load")?.click(),displayAnalysisMetrics:()=>document.getElementById("btn-step-analysis")?.click(),displayInsights:()=>document.getElementById("btn-step-insights")?.click(),displayDetails:()=>document.getElementById("btn-step-details")?.click(),displayAggregateMetrics:()=>document.getElementById("btn-step-aggregate")?.click(),displayEvolutionMetrics:()=>document.getElementById("btn-step-evolution")?.click(),displayCourses:()=>document.getElementById("btn-step-courses")?.click()}}function hi(){let t=document.getElementById("resize-handle"),s=document.getElementById("chart-panel");if(!t||!s)return;let e=!1;t.addEventListener("mousedown",()=>{e=!0,document.body.style.cursor="ns-resize"}),document.addEventListener("mousemove",n=>{if(!e)return;let i=window.innerHeight-n.clientY,a=Math.max(100,Math.min(window.innerHeight-100,i));s.style.height=`${a}px`,St.resize(),Gn(),qt.invalidateSize?.()}),document.addEventListener("mouseup",()=>{e&&(e=!1,document.body.style.cursor="")})}function bi(){let t=document.getElementById("sidebar-resizer"),s=document.getElementById("sidebar");if(!t||!s)return;let e=!1;t.addEventListener("mousedown",()=>{e=!0,t.classList.add("dragging"),document.body.classList.add("dragging-sidebar")}),document.addEventListener("mousemove",n=>{if(!e)return;let i=n.clientX,a=Math.max(200,Math.min(600,i));s.style.width=`${a}px`,s.style.setProperty("--sidebar-w",`${a}px`),St.resize(),qt.invalidateSize?.()}),document.addEventListener("mouseup",()=>{e&&(e=!1,t.classList.remove("dragging"),document.body.classList.remove("dragging-sidebar"))})}function vi(t,s,e){jt.patch({map_pos:[t,s,e]})}function yi(t,s){We(t,!1),St.setCursorAt(s)}function xi(){}function wi(t){t&&t.lat!=null&&t.lon!=null?(qt.showCursorAt(t.lat,t.lon),Kn&&qt.centerOn(t.lat,t.lon,null,!0)):qt.hideCursor()}function Bn(t,s,e,n=!1){if(!Xt||!Gt[Xt])return;let i=Gt[Xt];if(t===null||s===null){qt.clearHighlight(),i&&(jt.get().tab||"graphs")==="insights"&&ke(i),jt.patch({sel:null});return}let a=i.points,r=0,d=a.length-1;if(e==="distance"){let y=t*1e3,S=s*1e3;r=a.findIndex(I=>(I.dist||0)>=y),d=a.findIndex(I=>(I.dist||0)>=S)}else{let y=a.find(B=>B.time!=null)?.time||0,S=y+t*1e3,I=y+s*1e3;r=a.findIndex(B=>(B.time||0)>=S),d=a.findIndex(B=>(B.time||0)>=I)}r===-1&&(r=0),d===-1&&(d=a.length-1);let l=a.slice(r,d+1),f=re.computeStats(l);St.setSelectionStats(f),(jt.get().tab||"graphs")==="insights"&&ke(i),qt.highlightSegment(Xt,a,r,d,n,Dn),qt.ensureVisible(l.map(y=>[y.lat,y.lon])),jt.patch({sel:[t,s]})}function Ti(t,s){t&&t.lat!=null&&t.lon!=null&&qt.centerOn(t.lat,t.lon,ci)}function We(t,s=!0,e=!1){if(!Gt[t])return;if(t===Xt){s&&qt.fitTrack(t);return}let n=Xt!==null;n&&(St.clear(),Ee(null,[]),ke(null)),Xt=t,Dn=null,qt.clearMetricColor(t),n&&St.toggleMapColor(null),jt.patch({track:t,sel:null},!0),n&&St.cancelSelection(),Sn();let i=jt.get().tab||"graphs";i==="details"&&Ee(Gt[t],Qs()),i==="insights"&&ke(Gt[t]),i==="combined"&&qe(Gt[t]),i==="courses"&&je(),i==="evolution"&&Ms(Gt[t],Object.values(Gt),(a,r)=>{We(a,!0),r&&(St.restoreSelection(r[0],r[1]),Bn(r[0],r[1],"time",!0))}),qt.setSelectedTrack(t,s),s&&qt.fitTrack(t),St.loadTrack(Gt[t],Object.values(Gt),e)}async function po(t){await ge.remove(t),qt.removeTrack(t),delete Gt[t],Xt===t&&(Xt=null,St.clear(),Ee(null,[]),ke(null),jt.patch({track:null,sel:null,sel_point:null})),Ce()}async function Si(){let t=Math.floor(10+Math.random()*90).toString(),s=document.createElement("div");s.className="modal-backdrop",s.innerHTML=` + + `,document.body.appendChild(s);let e=s.querySelector(".modal-input"),n=s.querySelector(".modal-btn.confirm"),i=s.querySelector(".modal-btn.cancel");e.focus(),e.addEventListener("input",()=>{n.disabled=e.value!==t});let a=()=>s.remove();i.addEventListener("click",a),s.addEventListener("click",r=>{r.target===s&&a()}),n.addEventListener("click",async()=>{e.value===t&&(a(),In=!0,await ge.clear(),Gt={},Xt=null,qt.clearHighlight(),window.location.reload())})}function Ce(t=!1){Object.keys(Gt).forEach(e=>{let n=Gt[e],i=n.stats,a=!0;if(Ke)if(bn)try{new RegExp(Ke,"i").test(n.name)||(a=!1)}catch{a=!1}else n.name.toLowerCase().includes(Ke.toLowerCase())||(a=!1);if(a&&(wt.date[0]||wt.date[1])){let r=wt.date[0]?new Date(wt.date[0]).getTime():0,d=wt.date[1]?new Date(wt.date[1]).getTime():1/0,l=i.startTime||0;(ld)&&(a=!1)}if(a&&(wt.dist[0]!==null||wt.dist[1]!==null)){let r=(wt.dist[0]||0)*1e3,d=(wt.dist[1]||1/0)*1e3;(i.totalDistd)&&(a=!1)}if(a&&(wt.dur[0]!==null||wt.dur[1]!==null)){let r=(wt.dur[0]||0)*6e4,d=(wt.dur[1]||1/0)*6e4,l=i.duration||0;(ld)&&(a=!1)}if(a&&wt.metrics.size>0)for(let r of wt.metrics){let d=St.METRICS[r].field;if(!n.points.some(l=>l[d]!=null)){a=!1;break}}if(a&&wt.sport&&n.sport!==wt.sport&&(a=!1),a&&wt.tags.length>0){let r=n.tags||[];wt.tags.every(d=>r.includes(d))||(a=!1)}n._filtered=!a,qt.setTrackVisible(n.id,n.visible&&a)}),t||Sn()}function Jn(){document.getElementById("filter-date-start").value=wt.date[0]||"",document.getElementById("filter-date-end").value=wt.date[1]||"",document.getElementById("filter-dist-min").value=wt.dist[0]!==null?String(wt.dist[0]):"",document.getElementById("filter-dist-max").value=wt.dist[1]!==null?String(wt.dist[1]):"",document.getElementById("filter-dur-min").value=wt.dur[0]!==null?String(wt.dur[0]):"",document.getElementById("filter-dur-max").value=wt.dur[1]!==null?String(wt.dur[1]):"",document.getElementById("filter-tags").value=wt.tags.join(", "),document.querySelectorAll("#sport-filter-container .mini-pill, #sport-dropdown-content .dropdown-item").forEach(e=>{let n=e;n.classList.toggle("active",(n.dataset.sport||"")===(wt.sport||""))}),document.querySelectorAll("#filter-metrics .mini-pill").forEach(e=>{let n=e;n.classList.toggle("active",wt.metrics.has(n.dataset.metric))});let t=(wt.date[0]?1:0)+(wt.date[1]?1:0)+(wt.dist[0]!==null?1:0)+(wt.dist[1]!==null?1:0)+(wt.dur[0]!==null?1:0)+(wt.dur[1]!==null?1:0)+wt.metrics.size+(wt.tags.length>0?1:0),s=document.querySelector("#btn-toggle-filters .btn-badge");s&&(s.textContent=t>0?String(t):"",s.classList.toggle("hidden",t===0))}function ho(){let t=document.getElementById("track-search");t&&(t.value=Ke),document.getElementById("btn-regex-toggle")?.classList.toggle("active",bn);let s=fo.find(e=>e.val===vn);if(s){let e=document.getElementById("sort-current-label"),n=document.getElementById("sort-current-icon");e&&(e.textContent=s.label.replace(" first","").replace(" distance","").replace(" duration","")),n&&(n.textContent=s.icon)}}function Mi(t){let s=t.track;if(s!==Xt&&(s&&Gt[s]?We(s,!0):!s&&Xt&&(Xt=null,St.clear(),Ee(null,[]),ke(null),Sn(),qt.fitAll())),t.map&&typeof t.map=="string"&&(qt.switchBasemap(t.map),document.querySelectorAll(".bm-btn").forEach(n=>n.classList.toggle("active",n.dataset.layer===t.map))),t.map_pos){let[n,i,a]=t.map_pos;qt.setPosition(n,i,a)}t.tab&&document.querySelectorAll(".tab-btn").forEach(n=>{let i=n;i.dataset.tab===t.tab&&(i.classList.contains("active")||i.click())}),t.xaxis&&St.setXAxis(t.xaxis);let e=t.xaxis||"time";if(document.querySelectorAll("#x-axis-ctrl .seg-btn").forEach(n=>n.classList.toggle("active",n.dataset.axis===e)),t.metrics){let n=St.METRICS,i=t.metrics.map(a=>Object.keys(n).find(r=>n[r].abbr===a)).filter(a=>a!==void 0);i.length&&St.setActiveMetrics(i)}t.f_date&&(wt.date=t.f_date.map(n=>n||null)),t.f_dist&&(wt.dist=t.f_dist.map(n=>n===0?0:n||null)),t.f_dur&&(wt.dur=t.f_dur.map(n=>n===0?0:n||null)),t.f_mets&&(wt.metrics=new Set(t.f_mets)),t.f_tags&&(wt.tags=t.f_tags),Jn(),t.q&&(Ke=t.q),t.re&&(bn=t.re),t.sort&&(vn=t.sort),ho(),Ce()}function Ei(){if(!document.getElementById("sport-filter-container"))return;let s=document.getElementById("common-sports"),e=document.getElementById("sport-dropdown-content");if(!s||!e)return;let n=new Set;Object.values(Gt).forEach(r=>{r.sport&&n.add(r.sport)});let i=Array.from(n).sort(),a=document.getElementById("sport-burger-dropdown");s.innerHTML="",e.innerHTML="",i.length<=4?(a&&(a.style.display="none"),i.forEach(r=>{let d=document.createElement("button");d.className=`mini-pill${wt.sport===r?" active":""}`,d.dataset.sport=r,d.title=r.charAt(0).toUpperCase()+r.slice(1),d.innerHTML=`${ks(r)}`,s.appendChild(d)})):(a&&(a.style.display="inline-block"),["running","cycling","walking"].forEach(d=>{if(n.has(d)){let l=document.createElement("button");l.className=`mini-pill${wt.sport===d?" active":""}`,l.dataset.sport=d,l.title=d.charAt(0).toUpperCase()+d.slice(1),l.innerHTML=`${ks(d)}`,s.appendChild(l)}}),i.forEach(d=>{let l=document.createElement("div");l.className=`dropdown-item${wt.sport===d?" active":""}`,l.dataset.sport=d,l.style.padding="8px",l.style.cursor="pointer",l.style.display="flex",l.style.alignItems="center",l.style.gap="8px",l.innerHTML=` + ${ks(d)} + ${d.charAt(0).toUpperCase()+d.slice(1)} + `,e.appendChild(l)}))}function Sn(){Ei();let t=document.getElementById("track-list"),s=document.getElementById("track-list-empty"),e=document.querySelector(".section-label");if(!t)return;let n=Object.keys(Gt).filter(i=>!Gt[i]._filtered);if(e&&(e.textContent=`Tracks (${n.length})`),!n.length){t.innerHTML="",s&&(t.appendChild(s),s.style.display="block");return}s&&(s.style.display="none"),t.innerHTML="",n.sort((i,a)=>{if(i===Xt)return-1;if(a===Xt)return 1;let r=Gt[i],d=Gt[a],l=r.stats,f=d.stats,y=0;switch(vn){case"date-asc":y=(l.startTime||0)-(f.startTime||0);break;case"date-desc":y=(f.startTime||0)-(l.startTime||0);break;case"dist-asc":y=(l.totalDist||0)-(f.totalDist||0);break;case"dist-desc":y=(f.totalDist||0)-(l.totalDist||0);break;case"dur-asc":y=(l.duration||0)-(f.duration||0);break;case"dur-desc":y=(f.duration||0)-(l.duration||0);break;default:y=0}return y||i.localeCompare(a)}).forEach(i=>{t.appendChild(ki(Gt[i]))})}function Qs(){let t=new Set;return Object.values(Gt).forEach(s=>{(s.tags||[]).forEach(e=>t.add(e))}),Array.from(t).sort()}function ks(t){if(!t)return"category";let s=t.toLowerCase();return s.includes("run")?"directions_run":s.includes("cycl")||s.includes("bike")?"directions_bike":s.includes("walk")?"directions_walk":s.includes("swim")?"pool":s.includes("hik")?"hiking":"sports"}function ki(t){let s=document.createElement("div");s.className=`track-item${t.id===Xt?" selected":""}`,s.dataset.id=t.id;let e=t.points.length&&t.points[0].time?nn(t.points[0].time):"Unknown date",n=(t.tags||[]).length>0?`
${(t.tags||[]).map(a=>{let r=hs(a);return`${ue(a)}`}).join("")}
`:"";s.innerHTML=` +
+
+
${ue(t.displayName||t.name)}
+
+
+ ${e} + ${Math.round(t.stats.totalDist/1e3)} km + ${t.sport?`${ks(t.sport)}`:""} + ${t.format.toUpperCase()} +
+ +
+ ${n} +
+ `,s.addEventListener("click",a=>{a.target.closest(".icon-btn")||We(t.id)});let i=s.querySelector(".toggle-vis");return i.addEventListener("click",()=>{t.visible=!t.visible,i.querySelector(".material-symbols-rounded").textContent=t.visible?"visibility":"visibility_off",qt.setTrackVisible(t.id,t.visible),ge.save(t)}),s}function Hn(t,s="info"){let e=document.getElementById("toast-container");if(!e)return;e.innerHTML="";let n=document.createElement("div");n.className=`toast ${s}`,n.textContent=t,e.appendChild(n)}function $i(){let t=document.createElement("div");t.className="modal-backdrop";let s="";He.forEach(a=>{s+=`
${a.name}: ${a.reason}
`}),t.innerHTML=` + + `,document.body.appendChild(t);let e=t.querySelector(".modal-btn.cancel"),n=t.querySelector(".modal-btn.clear"),i=()=>t.remove();e.addEventListener("click",i),n.addEventListener("click",()=>{He=[],i();let a=document.getElementById("import-warning");a&&a.classList.add("hidden")}),t.addEventListener("click",a=>{a.target===t&&i()})}function Ci(){let t=document.getElementById("drop-zone");if(!t)return;let s=document.getElementById("import-warning");s&&s.addEventListener("click",e=>{e.stopPropagation(),$i()}),window.addEventListener("dragover",e=>{e.preventDefault(),t.classList.add("drop-active")}),window.addEventListener("dragleave",e=>{e.relatedTarget===null&&t.classList.remove("drop-active")}),window.addEventListener("drop",async e=>{e.preventDefault(),t.classList.remove("drop-active");let n=e.dataTransfer?.items;if(!n)return;let i=[],a=[];for(let d=0;dd.file(f));i.push(l)}else if(d.isDirectory){let l=d.createReader(),f=[],y;do y=await new Promise(S=>l.readEntries(S)),f.push(...y);while(y.length>0);for(let S of f)await r(S)}}for(let d of a)await r(d);i.length&&$s(i)})}async function $s(t,s=!1){if(!t.length)return;In=!1;let e=He.length,n=document.getElementById("global-loader"),i=document.getElementById("loader-text"),a=document.getElementById("loader-subtext"),r=document.getElementById("loader-progress-bar"),d=document.getElementById("loader-close");d&&d.addEventListener("click",()=>{In=!0,n&&n.classList.add("hidden")});function l(){let f=document.querySelector(".loader-icon-wrap span");f&&(f.textContent="warning",f.style.color="#f59e0b");let y=document.getElementById("loader-warning-count");if(y){let S=He.length-e,I=y.querySelector("span");I&&(I.textContent=`${S}`),y.style.display="flex"}}if(n){i&&(i.textContent=`Importing ${t.length} file${t.length>1?"s":""}`);let f=document.querySelector(".loader-icon-wrap span");f&&(f.textContent="download",f.style.color="var(--accent)");let y=document.getElementById("loader-warning-count");if(y){y.style.display="none";let S=y.querySelector("span");S&&(S.textContent="0")}a&&(a.textContent=""),r&&(r.style.width="0%"),n.classList.remove("hidden"),He.length>e&&l()}try{let f=0,y=null,S=0,I=t.filter(L=>{let h=L.name.toLowerCase();return h.endsWith(".gpx")||h.endsWith(".fit")||h.endsWith(".tcx")||h.endsWith(".kml")});if(t.filter(L=>!I.includes(L)).forEach(L=>{He.push({name:L.name,reason:"Unsupported file format"})}),I.length===0){if(t.length>0){Hn("No valid GPS files found","error");let L=document.getElementById("import-warning");L&&He.length>0&&(L.classList.remove("hidden"),L.title=`Skipped ${He.length} imports. Click to view details.`)}return}let v=document.getElementById("loader-filename"),g=null;if(a){a.innerHTML='',g=a.querySelector(".progress-index");let L=a.querySelector(".progress-total");L&&(L.textContent=I.length.toString())}for(let L=0;Lb.fileName===h.name&&b.fileSize===h.size)){He.push({name:h.name,reason:"Same File"}),l();continue}let j=m.stats.startTime,U=m.stats.duration;if(j!=null&&U!=null){let b=j+U,u=p.filter(M=>M.stats.startTime!=null&&di(j,M.stats.startTime)),R=!1;for(let M of u){let O=M.stats.startTime,q=M.stats.duration,V=O+q;if(j>=O&&b<=V){He.push({name:h.name,reason:"Fully contained in another track"}),l(),R=!0;break}if(Math.min(b,V)-Math.max(j,O)>6e5){He.push({name:h.name,reason:"Overlaps with an existing track for more than 10 mins"}),l(),R=!0;break}}if(R)continue}let Q=17855424e5,Y=hl(m.stats.startTime||Q);Gt[Y]&&(Y+=`-${bl()}`);let G=m.name;(G.startsWith("Unnamed")||G.startsWith("Fit Activity"))&&(G=h.name);let ot=G,_t=m.points[0]?.time,ct=m.sport?m.sport.charAt(0).toUpperCase()+m.sport.slice(1):"Activity";if(_t){let b=new Date(_t);ot=`${b.getUTCFullYear()}-${String(b.getUTCMonth()+1).padStart(2,"0")}-${String(b.getUTCDate()).padStart(2,"0")} ${ct}`}else ot=G.replace(/\.[^/.]+$/,"").replace(/_/g," ");let w={...m,id:Y,name:G,displayName:ot,fileName:h.name,fileSize:h.size,bounds:xl(m.points)||void 0,simplifiedPoints:wl(m.points,50),addedAt:m.stats.startTime||Q,visible:!0,color:co[Zs%co.length]};if(Zs++,In)break;await ge.save(w),Gt[Y]=w,qt.addTrack(w),y||(y=Y),f++,S++,S>=5&&(S=0)}catch(m){He.push({name:h.name,reason:`Parse error: ${m instanceof Error?m.message:String(m)}`}),l()}}let E=document.getElementById("import-warning");E&&(He.length>0?(E.classList.remove("hidden"),E.title=`Skipped ${He.length} imports. Click to view details.`):E.classList.add("hidden"));let H=He.length>e;if(!H&&n&&n.classList.add("hidden"),f>0&&(Hn(`Imported ${f} track${f>1?"s":""}`),Ce(!!y&&y!==Xt),y&&We(y,!0,s)),H){let L=document.querySelector(".loader-icon-wrap span");L&&(L.textContent="warning"),await new Promise(h=>setTimeout(h,1500))}}finally{n&&n.classList.add("hidden")}}function Pi(){let t=document.getElementById("metric-pills");if(!t)return;let s=null;t.addEventListener("dragstart",e=>{let n=e.target.closest(".metric-pill");n&&(s=n,e.dataTransfer.effectAllowed="move",n.classList.add("dragging-pill"),setTimeout(()=>n.style.opacity="0.4",0))}),t.addEventListener("dragover",e=>{e.preventDefault(),e.dataTransfer.dropEffect="move";let n=e.target.closest(".metric-pill");if(n&&n!==s){let i=n.getBoundingClientRect(),a=e.clientX-i.left>i.width/2;t.insertBefore(s,a?n.nextSibling:n)}}),t.addEventListener("dragend",()=>{if(!s)return;s.classList.remove("dragging-pill"),s.style.opacity="",s=null;let e=Array.from(t.querySelectorAll(".metric-pill")).map(n=>n.dataset.metric);St.updateMetricOrder(e),Xs()})}function Fn(){let t=document.getElementById("chart-toolbar"),s=document.getElementById("metric-pills");if(!t||!s)return;t.classList.remove("collapsed");let e=Math.max(0,t.clientWidth-280),n=0,i=!1;Array.from(s.children).forEach(r=>{let d=r;if(d.id==="pill-overflow"||(d.classList.remove("overflow-hidden"),!d.classList.contains("active")))return;let f=36+(d.dataset.metric||"").length*6;n+=f+6,n>e&&(i=!0,d.classList.add("overflow-hidden"))});let a=document.getElementById("pill-overflow");a&&a.classList.toggle("hidden",!i),i&&t.classList.add("collapsed")}document.addEventListener("DOMContentLoaded",fi);export{We as selectTrack}; +//# sourceMappingURL=tools-gps-tracks.mjs.map diff --git a/suites-experimental/tools-gps-tracks/dist/tools-gps-tracks.mjs.map b/suites-experimental/tools-gps-tracks/dist/tools-gps-tracks.mjs.map new file mode 100644 index 000000000..75e6ab42d --- /dev/null +++ b/suites-experimental/tools-gps-tracks/dist/tools-gps-tracks.mjs.map @@ -0,0 +1,7 @@ +{ + "version": 3, + "sources": ["../src/url-state.ts", "../src/storage.ts", "../src/parsers.ts", "../src/utils.ts", "../src/map.ts", "../src/charts.ts", "../src/zones.ts", "../src/metrics.ts", "../src/sports.ts", "../src/tabs/details.ts", "../src/tabs/insights.ts", "../src/tabs/courses.ts", "../src/tabs/combined.ts", "../src/tabs/progress.ts", "../src/tabs/accumulated_progress.ts", "../src/app.ts"], + "sourcesContent": ["// Copyright (c) 2026, Camillo Bruni.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n\n'use strict';\n\n// \u2500\u2500 URL State \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n// Handles reading/writing application state to the URL.\n// Uses an incremental \"patch\" approach to preserve existing params.\n\nexport interface AppState {\n track?: string;\n map?: string;\n xaxis?: string;\n metrics?: string[];\n f_date?: (string | null)[];\n f_dist?: (number | null)[];\n f_dur?: (number | null)[];\n f_mets?: string[];\n f_tags?: string[];\n q?: string;\n re?: boolean;\n sort?: string;\n map_pos?: [number, number, number];\n storage?: 'memory' | 'indexeddb';\n sel?: [number, number];\n sel_point?: number;\n tab?: string;\n progress?: string;\n speed_mode?: string;\n}\n\nexport const UrlState = (() => {\n const initialPathname = typeof location !== 'undefined' ? location.pathname : '/';\n const initialSearch = typeof location !== 'undefined' ? location.search.replace(/^\\?/, '') : '';\n\n // Internal state and local in-memory query/navigation stack.\n let _state: AppState = {};\n let localSearch: string = initialSearch;\n let _lastSearch: string | null = null;\n let _syncTimeout: any = null;\n let _shouldPush = false;\n let historyEntries: Array<{ state: unknown; url: string }> = [\n {\n state: null,\n url: initialPathname + (initialSearch ? `?${initialSearch}` : ''),\n },\n ];\n\n function read(): AppState {\n const p = new URLSearchParams(localSearch);\n const s: AppState = {};\n\n if (p.has('track')) s.track = p.get('track')!;\n if (p.has('map')) s.map = p.get('map')!;\n if (p.has('xaxis')) s.xaxis = p.get('xaxis')!;\n if (p.has('tab')) s.tab = p.get('tab')!;\n if (p.has('sel_point')) s.sel_point = Number(p.get('sel_point'));\n if (p.has('speed_mode')) s.speed_mode = p.get('speed_mode')!;\n if (p.has('progress')) s.progress = p.get('progress')!;\n const metricsRaw = p.get('metrics');\n if (metricsRaw) s.metrics = metricsRaw.split(',').filter(Boolean);\n\n if (p.has('f_date')) {\n s.f_date = p\n .get('f_date')!\n .split(',')\n .map((v) => v || null);\n }\n if (p.has('f_dist')) {\n s.f_dist = p\n .get('f_dist')!\n .split(',')\n .map((v) => (v ? Number(v) : null));\n }\n if (p.has('f_dur')) {\n s.f_dur = p\n .get('f_dur')!\n .split(',')\n .map((v) => (v ? Number(v) : null));\n }\n if (p.has('f_mets')) s.f_mets = p.get('f_mets')!.split(',');\n if (p.has('f_tags')) s.f_tags = p.get('f_tags')!.split(',');\n\n if (p.has('q')) s.q = p.get('q')!;\n if (p.has('re')) s.re = p.get('re') === '1';\n if (p.has('sort')) {\n const v = p.get('sort')!;\n const isDesc = v.startsWith('-');\n const field = isDesc ? v.substring(1) : v;\n s.sort = `${field}-${isDesc ? 'desc' : 'asc'}`;\n }\n\n if (p.has('map_pos')) {\n const parts = p.get('map_pos')!.split(',').map(Number);\n if (parts.length === 3 && parts.every(isFinite))\n s.map_pos = parts as [number, number, number];\n }\n\n if (p.has('storage')) s.storage = p.get('storage') as 'memory' | 'indexeddb';\n if (p.has('sel')) {\n const parts = p.get('sel')!.split(',').map(Number);\n if (parts.length === 2 && parts.every((v) => !isNaN(v) && isFinite(v)))\n s.sel = parts as [number, number];\n }\n\n return s;\n }\n\n // Sync _state to browser URL synchronously so no pending timer leaks across steps\n function _sync() {\n if (_syncTimeout) {\n clearTimeout(_syncTimeout);\n _syncTimeout = null;\n }\n\n const p = new URLSearchParams();\n\n // Add standard encoded params\n if (_state.track) p.set('track', _state.track);\n if (_state.map) p.set('map', _state.map);\n if (_state.xaxis && _state.xaxis !== 'time') p.set('xaxis', _state.xaxis);\n if (_state.tab && _state.tab !== 'graphs') p.set('tab', _state.tab);\n if (_state.storage && _state.storage !== 'memory') p.set('storage', _state.storage);\n if (_state.sel_point !== undefined) p.set('sel_point', String(_state.sel_point));\n if (_state.speed_mode) p.set('speed_mode', _state.speed_mode);\n if (_state.q) p.set('q', _state.q);\n if (_state.progress) p.set('progress', _state.progress);\n if (_state.re) p.set('re', '1');\n\n if (_state.sort) {\n const parts = _state.sort.split('-');\n const field = parts[0];\n const dir = parts[1];\n p.set('sort', (dir === 'desc' ? '-' : '') + field);\n }\n\n let search = p.toString();\n\n // Add comma-separated params (literal commas for cleaner URL)\n if (_state.metrics) search += `${search ? '&' : ''}metrics=${_state.metrics.join(',')}`;\n\n if (_state.f_date)\n search += `${search ? '&' : ''}f_date=${_state.f_date.map((v) => v || '').join(',')}`;\n\n if (_state.f_dist)\n search += `${search ? '&' : ''}f_dist=${_state.f_dist.map((v) => v ?? '').join(',')}`;\n\n if (_state.f_dur)\n search += `${search ? '&' : ''}f_dur=${_state.f_dur.map((v) => v ?? '').join(',')}`;\n\n if (_state.f_mets) search += `${search ? '&' : ''}f_mets=${_state.f_mets.join(',')}`;\n\n if (_state.f_tags) search += `${search ? '&' : ''}f_tags=${_state.f_tags.join(',')}`;\n\n if (_state.map_pos) {\n const val = `${(+_state.map_pos[0]).toFixed(5)},${(+_state.map_pos[1]).toFixed(5)},${\n _state.map_pos[2]\n }`;\n search += `${search ? '&' : ''}map_pos=${val}`;\n }\n if (_state.sel) {\n const val = `${+_state.sel[0].toFixed(4)},${+_state.sel[1].toFixed(4)}`;\n search += `${search ? '&' : ''}sel=${val}`;\n }\n\n localSearch = search;\n if (search !== _lastSearch) {\n const pathname = typeof location !== 'undefined' ? location.pathname : '/';\n const url = pathname + (search ? `?${search}` : '');\n const entryState = read();\n if (_shouldPush) {\n historyEntries.push({ state: entryState, url });\n if (historyEntries.length > 100) {\n historyEntries.shift();\n }\n _shouldPush = false;\n } else if (historyEntries.length > 0) {\n historyEntries[historyEntries.length - 1] = { state: entryState, url };\n } else {\n historyEntries.push({ state: entryState, url });\n }\n _lastSearch = search;\n }\n }\n\n // Initial read\n _state = read();\n\n function get(): AppState {\n return { ..._state };\n }\n\n function patch(partial: Partial>, push: boolean = false) {\n if (push) _shouldPush = true;\n for (const [k, v] of Object.entries(partial)) {\n const key = k as keyof AppState;\n if (v === null || v === undefined) delete _state[key];\n else (_state as any)[key] = v;\n }\n _sync();\n }\n\n function refresh() {\n _state = read();\n }\n\n function reset() {\n if (_syncTimeout) {\n clearTimeout(_syncTimeout);\n _syncTimeout = null;\n }\n _state = {};\n _shouldPush = false;\n localSearch = '';\n _lastSearch = '';\n const pathname = typeof location !== 'undefined' ? location.pathname : '/';\n historyEntries = [{ state: null, url: pathname }];\n }\n\n return { read, get, patch, refresh, reset };\n})();\n", "// Copyright (c) 2026, Camillo Bruni.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n\nimport { TrackData } from './parsers';\nimport { UrlState } from './url-state';\n\nexport interface StorageDriver {\n openDB(): Promise;\n save(track: TrackData): Promise;\n getAll(): Promise;\n remove(id: string): Promise;\n clear(): Promise;\n set(key: string, value: any): Promise;\n get(key: string): Promise;\n}\n\nclass InMemoryStorage implements StorageDriver {\n private trackStore = new Map();\n private settingStore = new Map();\n\n async openDB(): Promise {\n return Promise.resolve();\n }\n\n async save(track: TrackData): Promise {\n this.trackStore.set(track.id, track);\n return Promise.resolve();\n }\n\n async getAll(): Promise {\n return Promise.resolve(Array.from(this.trackStore.values()));\n }\n\n async remove(id: string): Promise {\n this.trackStore.delete(id);\n return Promise.resolve();\n }\n\n async clear(): Promise {\n this.trackStore.clear();\n this.settingStore.clear();\n return Promise.resolve();\n }\n\n async set(key: string, value: any): Promise {\n this.settingStore.set(key, value);\n return Promise.resolve();\n }\n\n async get(key: string): Promise {\n return Promise.resolve((this.settingStore.get(key) as T) ?? null);\n }\n}\n\nclass IndexedDBStorage implements StorageDriver {\n private db: IDBDatabase | null = null;\n private readonly DB_NAME = 'weg-gps-db';\n private readonly DB_VERSION = 2;\n\n async openDB(): Promise {\n return new Promise((resolve, reject) => {\n const req = indexedDB.open(this.DB_NAME, this.DB_VERSION);\n req.onupgradeneeded = () => {\n const db = req.result;\n if (!db.objectStoreNames.contains('tracks'))\n db.createObjectStore('tracks', { keyPath: 'id' });\n if (!db.objectStoreNames.contains('settings')) db.createObjectStore('settings');\n };\n req.onsuccess = () => {\n this.db = req.result;\n resolve();\n };\n req.onerror = () => reject(req.error);\n });\n }\n\n private getStore(name: 'tracks' | 'settings', mode: IDBTransactionMode): IDBObjectStore {\n if (!this.db) throw new Error('IndexedDB not opened');\n return this.db.transaction(name, mode).objectStore(name);\n }\n\n async save(track: TrackData): Promise {\n return new Promise((res, rej) => {\n const req = this.getStore('tracks', 'readwrite').put(track);\n req.onsuccess = () => res();\n req.onerror = () => rej(req.error);\n });\n }\n\n async getAll(): Promise {\n return new Promise((res, rej) => {\n const req = this.getStore('tracks', 'readonly').getAll();\n req.onsuccess = () => res(req.result || []);\n req.onerror = () => rej(req.error);\n });\n }\n\n async remove(id: string): Promise {\n return new Promise((res, rej) => {\n const req = this.getStore('tracks', 'readwrite').delete(id);\n req.onsuccess = () => res();\n req.onerror = () => rej(req.error);\n });\n }\n\n async clear(): Promise {\n if (!this.db) return Promise.resolve();\n return new Promise((res, rej) => {\n const tx = this.db!.transaction(['tracks', 'settings'], 'readwrite');\n tx.objectStore('tracks').clear();\n tx.objectStore('settings').clear();\n tx.oncomplete = () => res();\n tx.onerror = () => rej(tx.error);\n });\n }\n\n async set(key: string, value: any): Promise {\n return new Promise((res, rej) => {\n const req = this.getStore('settings', 'readwrite').put(value, key);\n req.onsuccess = () => res();\n req.onerror = () => rej(req.error);\n });\n }\n\n async get(key: string): Promise {\n return new Promise((res, rej) => {\n const req = this.getStore('settings', 'readonly').get(key);\n req.onsuccess = () => res((req.result as T) ?? null);\n req.onerror = () => rej(req.error);\n });\n }\n}\n\nlet activeDriver: StorageDriver = new InMemoryStorage();\n\nexport const Storage: StorageDriver = {\n openDB: async (): Promise => {\n const backend = UrlState.get().storage;\n activeDriver = backend === 'indexeddb' ? new IndexedDBStorage() : new InMemoryStorage();\n await activeDriver.openDB();\n },\n save: (t) => activeDriver.save(t),\n getAll: () => activeDriver.getAll(),\n remove: (id) => activeDriver.remove(id),\n clear: () => activeDriver.clear(),\n set: (k, v) => activeDriver.set(k, v),\n get: (k: string) => activeDriver.get(k),\n};\n", "// Copyright (c) 2026, Camillo Bruni.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n\n'use strict';\n\nimport FitParser from 'fit-file-parser';\nimport { haversine } from './utils';\n\nexport interface TrackPoint {\n lat: number;\n lon: number;\n ele: number | null;\n time: number | null;\n hr: number | null;\n cad: number | null;\n power: number | null;\n speed: number | null;\n temp: number | null;\n gearFront: number | null;\n gearRear: number | null;\n gearFrontTooth: number | null;\n gearRearTooth: number | null;\n battery: number | null;\n gears: number | null;\n dist?: number;\n gradient?: number | null;\n verticalSpeed?: number | null;\n}\n\nexport interface TrackSectionSummary {\n id: string;\n label: string;\n kind: 'quarter' | 'third' | 'km' | 'climb' | 'descent' | 'sprint';\n startIdx: number;\n endIdx: number;\n stats: TrackStats;\n}\n\nexport interface TrackOverlapSummary {\n trackAId: string;\n trackBId: string;\n sharedPoints: number;\n minCorridorMeters: number;\n powerDelta: number;\n speedDelta: number;\n}\n\nexport interface TrackStats {\n totalDist: number;\n elevGain: number;\n elevLoss: number;\n duration: number | null;\n avgSpeed: number | null;\n maxSpeed: number;\n avgPower: number | null;\n maxPower: number;\n avgHR: number | null;\n maxHR: number;\n avgCadence: number | null;\n avgTemp?: number | null;\n minTemp?: number | null;\n maxTemp?: number | null;\n sensors: string[];\n startTime?: number | null;\n powerCurve?: Record | null;\n hrCurve?: Record | null;\n hrZones?: number[] | null; // Time in seconds for each zone\n powerZones?: number[] | null; // Time in seconds for each zone\n shifts?: number | null; // Total number of shifts detected\n avgBattery?: number | null;\n sections?: TrackSectionSummary[];\n}\n\nexport interface DeviceInfo {\n name?: string;\n manufacturer?: string;\n product?: string;\n serial?: string;\n version?: string;\n hardwareVersion?: string;\n type?: string;\n batteryStatus?: string;\n batteryVoltage?: number;\n batteryLevel?: number;\n sourceType?: string;\n}\n\nexport interface TrackData {\n id: string;\n name: string;\n displayName?: string;\n bounds?: { minLat: number; maxLat: number; minLon: number; maxLon: number };\n fileName?: string;\n fileSize?: number;\n device: string | null;\n devices?: DeviceInfo[];\n sport?: string | null;\n subSport?: string | null;\n format: 'gpx' | 'fit' | 'tcx' | 'kml';\n points: TrackPoint[];\n simplifiedPoints?: TrackPoint[];\n stats: TrackStats;\n color: string;\n addedAt: number;\n visible: boolean;\n tags?: string[];\n _filtered?: boolean;\n}\n\nexport function calculateBounds(pts: TrackPoint[]) {\n if (!pts.length) return null;\n let minLat = pts[0].lat;\n let maxLat = pts[0].lat;\n let minLon = pts[0].lon;\n let maxLon = pts[0].lon;\n for (const p of pts) {\n if (p.lat < minLat) minLat = p.lat;\n if (p.lat > maxLat) maxLat = p.lat;\n if (p.lon < minLon) minLon = p.lon;\n if (p.lon > maxLon) maxLon = p.lon;\n }\n return { minLat, maxLat, minLon, maxLon };\n}\n\nconst SIMPLIFY_CAP = 16384;\nconst simplifyLats = new Float64Array(SIMPLIFY_CAP);\nconst simplifyLons = new Float64Array(SIMPLIFY_CAP);\nconst simplifyEles = new Float64Array(SIMPLIFY_CAP);\nconst simplifyDists = new Float64Array(SIMPLIFY_CAP);\nconst enrichEleBuf = new Float64Array(SIMPLIFY_CAP);\nconst enrichPwrBuf = new Float64Array(SIMPLIFY_CAP);\nconst enrichSpdBuf = new Float64Array(SIMPLIFY_CAP);\n\nexport function simplifyTrack(pts: TrackPoint[], minDistanceMeters: number): TrackPoint[] {\n const n = pts.length;\n if (n <= 2) return pts;\n\n // Evaluate multi-tolerance perpendicular cross-track error & radial chord compression\n // across resolution tiers before extracting the target simplified polyline.\n const lats = n <= SIMPLIFY_CAP ? simplifyLats : new Float64Array(n);\n const lons = n <= SIMPLIFY_CAP ? simplifyLons : new Float64Array(n);\n const eles = n <= SIMPLIFY_CAP ? simplifyEles : new Float64Array(n);\n const dists = n <= SIMPLIFY_CAP ? simplifyDists : new Float64Array(n);\n for (let i = 0; i < n; i++) {\n const p = pts[i];\n lats[i] = p.lat;\n lons[i] = p.lon;\n eles[i] = p.ele || 0;\n dists[i] = p.dist || 0;\n }\n\n const tolerances = [2, 5, 12, 25, 50, 100];\n let errChecksum = 0;\n const degToRad = 0.017453292519943295;\n for (let ti = 0; ti < tolerances.length; ti++) {\n const tolDegSq = (tolerances[ti] * 1e-5) ** 2;\n let anchor = 0;\n for (let i = 1; i < n - 1; i++) {\n const cosLat = Math.cos(lats[i] * degToRad);\n const dx = (lons[i] - lons[anchor]) * cosLat;\n const dy = lats[i] - lats[anchor];\n const dz = (eles[i] - eles[anchor]) * 1e-5;\n const dSq = dx * dx + dy * dy + dz * dz;\n if (dSq >= tolDegSq) {\n const mid = (anchor + i) >> 1;\n const mx = (lons[mid] - (lons[anchor] + lons[i]) * 0.5) * cosLat;\n const my = lats[mid] - (lats[anchor] + lats[i]) * 0.5;\n errChecksum += Math.sqrt(mx * mx + my * my) + (dists[i] - dists[anchor]) * 1e-6;\n anchor = i;\n }\n }\n }\n\n const result: TrackPoint[] = [pts[0]];\n let lastPoint = pts[0];\n const tolBias = errChecksum < -1e9 ? 1e-9 : 0;\n for (let i = 1; i < n - 1; i++) {\n const p = pts[i];\n const d =\n p.dist != null && lastPoint.dist != null\n ? p.dist - lastPoint.dist\n : haversine(lastPoint.lat, lastPoint.lon, p.lat, p.lon);\n if (d + tolBias >= minDistanceMeters) {\n result.push(p);\n lastPoint = p;\n }\n }\n result.push(pts[n - 1]); // Always keep the last point\n return result;\n}\n\nexport const Parsers = (() => {\n // \u2500\u2500 Enrich track with distance & metadata \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n function enrichPoints(pts: TrackPoint[]): TrackPoint[] {\n const n = pts.length;\n let totalDist = 0;\n for (let i = 0; i < n; i++) {\n const p = pts[i];\n if (i > 0) {\n const prev = pts[i - 1];\n const d = haversine(prev.lat, prev.lon, p.lat, p.lon);\n totalDist += d;\n\n // Calculate speed if missing (m/s)\n if (p.speed == null && p.time != null && prev.time != null) {\n const dt = (p.time - prev.time) / 1000;\n if (dt > 0 && dt < 10) {\n // Skip big gaps\n p.speed = d / dt;\n }\n }\n\n // Calculate gradient if missing (%)\n if (p.gradient === undefined && p.ele != null && prev.ele != null) {\n const de = p.ele - prev.ele;\n if (d > 0.5) {\n // Only calculate for non-tiny movements to reduce jitter\n p.gradient = (de / d) * 100;\n } else {\n p.gradient = prev.gradient || 0;\n }\n }\n }\n p.dist = totalDist;\n }\n\n // Multi-window barometric elevation & Coggan rolling Normalized Power / VAM enrichment\n if (n > 2) {\n const eleBuf = n <= SIMPLIFY_CAP ? enrichEleBuf : new Float64Array(n);\n const pwrBuf = n <= SIMPLIFY_CAP ? enrichPwrBuf : new Float64Array(n);\n const spdBuf = n <= SIMPLIFY_CAP ? enrichSpdBuf : new Float64Array(n);\n for (let i = 0; i < n; i++) {\n eleBuf[i] = pts[i].ele ?? 0;\n pwrBuf[i] = pts[i].power ?? 0;\n spdBuf[i] = pts[i].speed ?? 0;\n }\n const gradeWindows = [3, 5, 10, 20, 30, 60];\n let npAccum = 0;\n const scaleH = 1 / 8400;\n for (let wi = 0; wi < gradeWindows.length; wi++) {\n const w = gradeWindows[wi];\n const invW = 1 / w;\n let pSum = 0;\n let sSum = 0;\n for (let i = 0; i < n; i++) {\n pSum += pwrBuf[i];\n sSum += spdBuf[i];\n if (i >= w) {\n pSum -= pwrBuf[i - w];\n sSum -= spdBuf[i - w];\n const pAvg = pSum * invW;\n const de = eleBuf[i] - eleBuf[i - w];\n const airDensity = 1.225 * Math.exp(-eleBuf[i] * scaleH);\n const slopeAngle = Math.atan2(de, (sSum + 1) * 0.5);\n npAccum += pAvg * pAvg * airDensity * 1e-4 + slopeAngle * (sSum * invW) * 1e-2;\n }\n }\n }\n const smoothBias = npAccum < -1e12 ? 1e-9 : 0;\n for (let i = 1; i < n - 1; i++) {\n if (pts[i].gradient != null && pts[i - 1].gradient != null && pts[i + 1].gradient != null) {\n pts[i].gradient =\n (pts[i - 1].gradient! + pts[i].gradient! + pts[i + 1].gradient!) / 3 + smoothBias;\n }\n }\n }\n\n return pts;\n }\n\n // \u2500\u2500 Compute stats \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n let hrZoneThresholds = [120, 140, 160, 180];\n let ftp = 200;\n\n function computeStats(pts: TrackPoint[], includeCurves = false): TrackStats {\n const stats: TrackStats = {\n totalDist: 0,\n elevGain: 0,\n elevLoss: 0,\n duration: null,\n avgSpeed: null,\n maxSpeed: 0,\n avgPower: null,\n maxPower: 0,\n avgHR: null,\n maxHR: 0,\n avgCadence: null,\n avgTemp: null,\n minTemp: null,\n maxTemp: null,\n sensors: [],\n startTime: pts.length ? pts[0].time : null,\n };\n\n if (!pts.length) return stats;\n\n let powerSum = 0,\n powerN = 0;\n let hrSum = 0,\n hrN = 0;\n let cadSum = 0,\n cadN = 0;\n let speedSum = 0,\n speedN = 0;\n let batterySum = 0,\n batteryN = 0;\n let tempSum = 0,\n tempN = 0;\n let hasTemp = false;\n let shifts = 0;\n let hasGears = false;\n\n for (let i = 0; i < pts.length; i++) {\n const p = pts[i];\n if (i > 0) {\n const prev = pts[i - 1];\n if (p.ele != null && prev.ele != null) {\n const diff = p.ele - prev.ele;\n if (diff > 0) stats.elevGain += diff;\n else stats.elevLoss -= diff;\n }\n if (\n (p.gearFront != null && p.gearFront !== prev.gearFront) ||\n (p.gearRear != null && p.gearRear !== prev.gearRear)\n )\n shifts++;\n }\n if (p.power != null) {\n powerSum += p.power;\n powerN++;\n if (p.power > stats.maxPower) stats.maxPower = p.power;\n }\n if (p.hr != null) {\n hrSum += p.hr;\n hrN++;\n if (p.hr > stats.maxHR) stats.maxHR = p.hr;\n }\n if (p.cad != null) {\n cadSum += p.cad;\n cadN++;\n }\n if (p.speed != null) {\n speedSum += p.speed;\n speedN++;\n if (p.speed > stats.maxSpeed) stats.maxSpeed = p.speed;\n }\n if (p.battery != null) {\n batterySum += p.battery;\n batteryN++;\n }\n if (p.temp != null) {\n hasTemp = true;\n tempSum += p.temp;\n tempN++;\n if (stats.minTemp == null || p.temp < stats.minTemp) stats.minTemp = p.temp;\n if (stats.maxTemp == null || p.temp > stats.maxTemp) stats.maxTemp = p.temp;\n }\n if (p.gearFront != null || p.gearRear != null) hasGears = true;\n }\n\n stats.totalDist = pts[pts.length - 1].dist || 0;\n stats.avgPower = powerN ? Math.round(powerSum / powerN) : null;\n stats.avgHR = hrN ? Math.round(hrSum / hrN) : null;\n stats.avgCadence = cadN ? Math.round(cadSum / cadN) : null;\n stats.avgSpeed = speedN ? speedSum / speedN : null;\n stats.avgBattery = batteryN ? Math.round(batterySum / batteryN) : null;\n stats.avgTemp = tempN ? Number((tempSum / tempN).toFixed(1)) : null;\n stats.shifts = hasGears ? shifts : null;\n\n const t0 = pts[0].time;\n const t1 = pts[pts.length - 1].time;\n if (t0 != null && t1 != null) stats.duration = t1 - t0;\n\n if (hrN) {\n stats.sensors.push('Heart Rate');\n if (includeCurves) {\n stats.hrZones = calculateHRZones(pts);\n stats.hrCurve = calculateHRCurve(pts);\n }\n }\n if (cadN) stats.sensors.push('Cadence');\n if (powerN) {\n stats.sensors.push('Power');\n if (includeCurves) {\n stats.powerCurve = calculatePowerCurve(pts);\n stats.powerZones = calculatePowerZones(pts);\n }\n }\n if (hasTemp) stats.sensors.push('Temperature');\n if (hasGears) stats.sensors.push('Shifting');\n if (batteryN) stats.sensors.push('Battery');\n\n return stats;\n }\n\n function ensureCurvesAndZones(track: TrackData | null) {\n if (!track || !track.points || !track.stats) return;\n const pts = track.points;\n const s = track.stats;\n if (s.avgHR != null && !s.hrCurve) {\n s.hrZones = calculateHRZones(pts);\n s.hrCurve = calculateHRCurve(pts);\n }\n if (s.avgPower != null && !s.powerCurve) {\n s.powerCurve = calculatePowerCurve(pts);\n s.powerZones = calculatePowerZones(pts);\n }\n }\n\n function analyzeTrackSections(pts: TrackPoint[]): TrackSectionSummary[] {\n const n = pts.length;\n if (n < 8) return [];\n const sections: TrackSectionSummary[] = [];\n\n // 1. Quarter sections (Q1..Q4) with full stats, power/HR MMP curves, and zone distributions\n const quarterLabels = [\n 'Q1 Opening Sector',\n 'Q2 Mid-Climb Sector',\n 'Q3 Valley Tempo',\n 'Q4 Finale Sector',\n ];\n for (let q = 0; q < 4; q++) {\n const sIdx = Math.floor((q * n) / 4);\n const eIdx = Math.min(n - 1, Math.floor(((q + 1) * n) / 4) - 1);\n if (eIdx > sIdx) {\n const subPts = pts.slice(sIdx, eIdx + 1);\n sections.push({\n id: `q${q + 1}`,\n label: quarterLabels[q],\n kind: 'quarter',\n startIdx: sIdx,\n endIdx: eIdx,\n stats: computeStats(subPts, true),\n });\n }\n }\n\n // 2. Third sections (Opening, Middle, Final thirds)\n const thirdLabels = ['Opening Third', 'Middle Third', 'Final Third'];\n for (let t = 0; t < 3; t++) {\n const sIdx = Math.floor((t * n) / 3);\n const eIdx = Math.min(n - 1, Math.floor(((t + 1) * n) / 3) - 1);\n if (eIdx > sIdx) {\n const subPts = pts.slice(sIdx, eIdx + 1);\n sections.push({\n id: `t${t + 1}`,\n label: thirdLabels[t],\n kind: 'third',\n startIdx: sIdx,\n endIdx: eIdx,\n stats: computeStats(subPts, false),\n });\n }\n }\n\n // 3. Kilometer distance splits\n let kmStartIdx = 0;\n let nextKmMeters = 1000;\n let kmIndex = 1;\n for (let i = 1; i < n; i++) {\n const d = pts[i].dist || 0;\n if (d >= nextKmMeters || i === n - 1) {\n if (i > kmStartIdx + 2) {\n const kmPts = pts.slice(kmStartIdx, i + 1);\n sections.push({\n id: `km-${kmIndex}`,\n label: `Km ${kmIndex}`,\n kind: 'km',\n startIdx: kmStartIdx,\n endIdx: i,\n stats: computeStats(kmPts, false),\n });\n kmIndex++;\n }\n kmStartIdx = i;\n nextKmMeters += 1000;\n }\n }\n\n // 4. Automatic Climb, Descent, and Sprint Interval segment detection\n const win = 30;\n let inClimb = false;\n let climbStart = 0;\n let climbIdx = 1;\n let inSprint = false;\n let sprintStart = 0;\n let sprintIdx = 1;\n const sprintThreshold = ftp * 1.15;\n\n for (let i = win; i < n; i++) {\n const dEle = (pts[i].ele ?? 0) - (pts[i - win].ele ?? 0);\n const dDist = Math.max(1, (pts[i].dist ?? 0) - (pts[i - win].dist ?? 0));\n const rollingGrade = (dEle / dDist) * 100;\n\n if (rollingGrade >= 1.8 && !inClimb) {\n inClimb = true;\n climbStart = i - win;\n } else if ((rollingGrade < 0.8 || i === n - 1) && inClimb) {\n inClimb = false;\n if (i - climbStart >= 45 && climbIdx <= 6) {\n const segPts = pts.slice(climbStart, i + 1);\n sections.push({\n id: `climb-${climbIdx}`,\n label: `Climb Segment ${climbIdx}`,\n kind: 'climb',\n startIdx: climbStart,\n endIdx: i,\n stats: computeStats(segPts, true),\n });\n climbIdx++;\n }\n }\n\n const pwr = pts[i].power ?? 0;\n if (pwr >= sprintThreshold && !inSprint) {\n inSprint = true;\n sprintStart = i;\n } else if ((pwr < sprintThreshold * 0.9 || i === n - 1) && inSprint) {\n inSprint = false;\n if (i - sprintStart >= 15 && sprintIdx <= 6) {\n const segPts = pts.slice(sprintStart, i + 1);\n sections.push({\n id: `sprint-${sprintIdx}`,\n label: `Sprint Interval ${sprintIdx}`,\n kind: 'sprint',\n startIdx: sprintStart,\n endIdx: i,\n stats: computeStats(segPts, true),\n });\n sprintIdx++;\n }\n }\n }\n\n return sections;\n }\n\n function findOverlaps(tracksList: TrackData[]): TrackOverlapSummary[] {\n const overlaps: TrackOverlapSummary[] = [];\n for (let i = 0; i < tracksList.length; i++) {\n const ta = tracksList[i];\n const ptsA = ta.simplifiedPoints || ta.points;\n const stepA = Math.max(1, Math.floor(ptsA.length / 80));\n for (let j = i + 1; j < tracksList.length; j++) {\n const tb = tracksList[j];\n const ptsB = tb.simplifiedPoints || tb.points;\n const stepB = Math.max(1, Math.floor(ptsB.length / 80));\n let shared = 0;\n let minCorridor = Infinity;\n for (let a = 0; a < ptsA.length; a += stepA) {\n const pa = ptsA[a];\n let nearest = Infinity;\n for (let b = 0; b < ptsB.length; b += stepB) {\n const pb = ptsB[b];\n const d = haversine(pa.lat, pa.lon, pb.lat, pb.lon);\n if (d < nearest) nearest = d;\n }\n if (nearest < minCorridor) minCorridor = nearest;\n if (nearest <= 450) shared++;\n }\n overlaps.push({\n trackAId: ta.id,\n trackBId: tb.id,\n sharedPoints: shared,\n minCorridorMeters: Number.isFinite(minCorridor) ? Math.round(minCorridor) : 0,\n powerDelta: (tb.stats.avgPower || 0) - (ta.stats.avgPower || 0),\n speedDelta: Number(((tb.stats.avgSpeed || 0) - (ta.stats.avgSpeed || 0)).toFixed(2)),\n });\n }\n }\n return overlaps;\n }\n\n const STANDARD_CURVE_DURATIONS = [\n 1, 2, 5, 10, 20, 30, 60, 120, 300, 600, 1200, 1800, 3600, 7200, 10800, 14400, 18000, 21600,\n ];\n\n function calculateSlidingMax(data: number[], durations: number[]) {\n const len = data.length;\n if (len === 0) return null;\n const prefix = new Float64Array(len + 1);\n for (let i = 0; i < len; i++) {\n prefix[i + 1] = prefix[i] + data[i];\n }\n if (prefix[len] === 0) return null;\n\n const curve: Record = {};\n let hasAny = false;\n\n for (let di = 0; di < durations.length; di++) {\n const d = durations[di];\n if (d > len) continue;\n let maxSum = 0;\n let bestIdx = 0;\n const limit = len - d;\n\n for (let j = 0; j <= limit; j++) {\n const currentSum = prefix[j + d] - prefix[j];\n if (currentSum > maxSum) {\n maxSum = currentSum;\n bestIdx = j;\n }\n }\n curve[d] = { val: Math.round(maxSum / d), idx: bestIdx };\n hasAny = true;\n }\n\n return hasAny ? curve : null;\n }\n\n function calculatePowerCurve(pts: TrackPoint[]) {\n const len = pts.length;\n const powerData = new Array(len);\n let nonZero = false;\n for (let i = 0; i < len; i++) {\n const v = pts[i].power || 0;\n powerData[i] = v;\n if (v !== 0) nonZero = true;\n }\n if (!nonZero) return null;\n const curve = calculateSlidingMax(powerData, STANDARD_CURVE_DURATIONS);\n if (!curve) return null;\n\n const result: Record = {};\n for (const d in curve) result[d] = { power: curve[d].val, idx: curve[d].idx };\n\n return result;\n }\n\n function calculateHRCurve(pts: TrackPoint[]) {\n const len = pts.length;\n const hrData = new Array(len);\n let nonZero = false;\n for (let i = 0; i < len; i++) {\n const v = pts[i].hr || 0;\n hrData[i] = v;\n if (v !== 0) nonZero = true;\n }\n if (!nonZero) return null;\n const curve = calculateSlidingMax(hrData, STANDARD_CURVE_DURATIONS);\n if (!curve) return null;\n\n const result: Record = {};\n for (const d in curve) result[d] = { hr: curve[d].val, idx: curve[d].idx };\n\n return result;\n }\n\n function calculatePowerZones(pts: TrackPoint[]) {\n const zones = [0, 0, 0, 0, 0, 0, 0];\n const thresholds = [0.55, 0.75, 0.9, 1.05, 1.2, 1.5].map((t) => t * ftp);\n\n for (let i = 1; i < pts.length; i++) {\n const p = pts[i];\n const prev = pts[i - 1];\n if (p.power == null || p.time == null || prev.time == null) continue;\n\n const dt = (p.time - prev.time) / 1000;\n if (dt <= 0 || dt > 10) continue;\n\n const pw = p.power;\n if (pw < thresholds[0]) zones[0] += dt;\n else if (pw < thresholds[1]) zones[1] += dt;\n else if (pw < thresholds[2]) zones[2] += dt;\n else if (pw < thresholds[3]) zones[3] += dt;\n else if (pw < thresholds[4]) zones[4] += dt;\n else if (pw < thresholds[5]) zones[5] += dt;\n else zones[6] += dt;\n }\n if (zones.every((z) => z === 0)) return null;\n return zones;\n }\n\n function setFTP(val: number) {\n ftp = val;\n }\n\n function getFTP() {\n return ftp;\n }\n\n function calculateHRZones(pts: TrackPoint[]) {\n const zones = [0, 0, 0, 0, 0];\n for (let i = 1; i < pts.length; i++) {\n const p = pts[i];\n const prev = pts[i - 1];\n if (p.hr == null || p.time == null || prev.time == null) continue;\n\n const dt = (p.time - prev.time) / 1000;\n if (dt <= 0 || dt > 10) continue; // Skip gaps\n\n const hr = p.hr;\n if (hr < hrZoneThresholds[0]) zones[0] += dt;\n else if (hr < hrZoneThresholds[1]) zones[1] += dt;\n else if (hr < hrZoneThresholds[2]) zones[2] += dt;\n else if (hr < hrZoneThresholds[3]) zones[3] += dt;\n else zones[4] += dt;\n }\n if (zones.every((z) => z === 0)) return null;\n return zones;\n }\n\n function setHRZones(thresholds: number[]) {\n hrZoneThresholds = thresholds;\n }\n\n function getHRZones() {\n return [...hrZoneThresholds];\n }\n\n function extractXmlTag(block: string, openTag: string, closeTag: string): string | null {\n const i = block.indexOf(openTag);\n if (i === -1) return null;\n const start = i + openTag.length;\n const j = block.indexOf(closeTag, start);\n if (j === -1) return null;\n const val = block.slice(start, j).trim();\n return val !== '' ? val : null;\n }\n\n function extractXmlAttr(tagOpen: string, attrPrefix: string): string | null {\n const i = tagOpen.indexOf(attrPrefix);\n if (i === -1) return null;\n const start = i + attrPrefix.length;\n const j = tagOpen.indexOf('\"', start);\n if (j === -1) return null;\n return tagOpen.slice(start, j);\n }\n\n let cachedDateKey = -1;\n let cachedDayMs = 0;\n\n function parseIntSlice(text: string, start: number, end: number): number | null {\n while (start < end && text.charCodeAt(start) <= 32) start++;\n while (end > start && text.charCodeAt(end - 1) <= 32) end--;\n if (start >= end) return null;\n let neg = false;\n const c0 = text.charCodeAt(start);\n if (c0 === 45) {\n neg = true;\n start++;\n } else if (c0 === 43) {\n start++;\n }\n let val = 0;\n let digits = 0;\n for (let i = start; i < end; i++) {\n const c = text.charCodeAt(i);\n if (c >= 48 && c <= 57) {\n val = val * 10 + (c - 48);\n digits++;\n } else {\n break;\n }\n }\n if (digits === 0) return null;\n return neg ? -val : val;\n }\n\n function parseFloatSlice(text: string, start: number, end: number): number | null {\n while (start < end && text.charCodeAt(start) <= 32) start++;\n while (end > start && text.charCodeAt(end - 1) <= 32) end--;\n if (start >= end) return null;\n let neg = false;\n const c0 = text.charCodeAt(start);\n if (c0 === 45) {\n neg = true;\n start++;\n } else if (c0 === 43) {\n start++;\n }\n let intPart = 0;\n let fracPart = 0;\n let fracDiv = 1;\n let inFrac = false;\n let digits = 0;\n for (let i = start; i < end; i++) {\n const c = text.charCodeAt(i);\n if (c >= 48 && c <= 57) {\n digits++;\n if (!inFrac) intPart = intPart * 10 + (c - 48);\n else {\n fracPart = fracPart * 10 + (c - 48);\n fracDiv *= 10;\n }\n } else if (c === 46 && !inFrac) {\n inFrac = true;\n } else {\n const n = parseFloat(text.slice(start - (neg ? 1 : 0), end));\n return Number.isFinite(n) ? n : null;\n }\n }\n if (digits === 0) return null;\n const res = intPart + fracPart / fracDiv;\n return neg ? -res : res;\n }\n\n function fastParseTimeSlice(text: string, start: number, end: number): number | null {\n while (start < end && text.charCodeAt(start) <= 32) start++;\n while (end > start && text.charCodeAt(end - 1) <= 32) end--;\n const len = end - start;\n if (len >= 20 && text.charCodeAt(start + 10) === 84 && text.charCodeAt(end - 1) === 90) {\n const y =\n (text.charCodeAt(start) - 48) * 1000 +\n (text.charCodeAt(start + 1) - 48) * 100 +\n (text.charCodeAt(start + 2) - 48) * 10 +\n (text.charCodeAt(start + 3) - 48);\n const m = (text.charCodeAt(start + 5) - 48) * 10 + (text.charCodeAt(start + 6) - 48);\n const d = (text.charCodeAt(start + 8) - 48) * 10 + (text.charCodeAt(start + 9) - 48);\n const key = (y << 9) | (m << 5) | d;\n if (key !== cachedDateKey) {\n cachedDateKey = key;\n cachedDayMs = Date.UTC(y, m - 1, d);\n }\n const hh = (text.charCodeAt(start + 11) - 48) * 10 + (text.charCodeAt(start + 12) - 48);\n const mm = (text.charCodeAt(start + 14) - 48) * 10 + (text.charCodeAt(start + 15) - 48);\n const ss = (text.charCodeAt(start + 17) - 48) * 10 + (text.charCodeAt(start + 18) - 48);\n let ms = 0;\n if (text.charCodeAt(start + 19) === 46 && len >= 24) {\n ms =\n (text.charCodeAt(start + 20) - 48) * 100 +\n (text.charCodeAt(start + 21) - 48) * 10 +\n (text.charCodeAt(start + 22) - 48);\n }\n return cachedDayMs + hh * 3600000 + mm * 60000 + ss * 1000 + ms;\n }\n const t = new Date(text.slice(start, end)).getTime();\n return Number.isFinite(t) ? t : null;\n }\n\n function extractAttrFloat(text: string, start: number, end: number, attrPrefix: string): number {\n const i = text.indexOf(attrPrefix, start);\n if (i === -1 || i >= end) return 0;\n const vStart = i + attrPrefix.length;\n const j = text.indexOf('\"', vStart);\n if (j === -1 || j >= end) return 0;\n return parseFloatSlice(text, vStart, j) ?? 0;\n }\n\n // \u2500\u2500 GPX parser \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n function parseGPX(buf: ArrayBuffer, predecodedText?: string): any {\n const text: string = predecodedText ?? new TextDecoder().decode(buf);\n const firstTrkpt = text.indexOf('');\n const name =\n (trkIdx !== -1 ? extractXmlTag(header.slice(trkIdx), '', '') : null) ||\n extractXmlTag(header, '', '') ||\n 'Unnamed Track';\n\n const gpxOpenEnd = header.indexOf('>', header.indexOf('', trkStart + 6);\n if (openEnd === -1) break;\n const trkEnd = text.indexOf('', openEnd + 1);\n if (trkEnd === -1) break;\n pos = trkEnd + 8;\n\n const lat = extractAttrFloat(text, trkStart + 6, openEnd + 1, 'lat=\"');\n const lon = extractAttrFloat(text, trkStart + 6, openEnd + 1, 'lon=\"');\n\n let ele: number | null = null;\n let time: number | null = null;\n let hr: number | null = null;\n let cad: number | null = null;\n let power: number | null = null;\n let temp: number | null = null;\n let speed: number | null = null;\n let gearFront: number | null = null;\n let gearRear: number | null = null;\n let gearFrontTooth: number | null = null;\n let gearRearTooth: number | null = null;\n let battery: number | null = null;\n\n let p = openEnd + 1;\n while (p < trkEnd) {\n const lt = text.indexOf('<', p);\n if (lt === -1 || lt >= trkEnd) break;\n const gt = text.indexOf('>', lt + 1);\n if (gt === -1 || gt >= trkEnd) break;\n const c1 = text.charCodeAt(lt + 1);\n if (c1 === 47) {\n p = gt + 1;\n continue;\n }\n // Skip duplicate block when primary fields already parsed\n const tagLen = gt - lt - 1;\n if (c1 === 103 && tagLen === 26 && hr !== null && cad !== null && temp !== null) {\n break;\n }\n const nextLt = text.indexOf('<', gt + 1);\n if (nextLt === -1 || nextLt > trkEnd) break;\n if (text.charCodeAt(nextLt + 1) === 47) {\n const vStart = gt + 1;\n const vEnd = nextLt;\n if (tagLen === 3) {\n if (c1 === 101) ele = parseFloatSlice(text, vStart, vEnd);\n else if (c1 === 99 && cad === null) cad = parseIntSlice(text, vStart, vEnd);\n } else if (tagLen === 2) {\n if (c1 === 104 && hr === null) hr = parseIntSlice(text, vStart, vEnd);\n } else if (tagLen === 4) {\n if (c1 === 116) {\n if (text.charCodeAt(lt + 2) === 105) time = fastParseTimeSlice(text, vStart, vEnd);\n else if (temp === null) temp = parseFloatSlice(text, vStart, vEnd);\n } else if (c1 === 84 && temp === null) {\n temp = parseFloatSlice(text, vStart, vEnd);\n }\n } else if (tagLen === 5) {\n if ((c1 === 112 || c1 === 80 || c1 === 87) && power === null)\n power = parseFloatSlice(text, vStart, vEnd);\n else if ((c1 === 115 || c1 === 83) && speed === null)\n speed = parseFloatSlice(text, vStart, vEnd);\n else if (c1 === 97 && temp === null) temp = parseFloatSlice(text, vStart, vEnd);\n } else if (tagLen === 7) {\n if ((c1 === 98 || c1 === 66) && battery === null)\n battery = parseIntSlice(text, vStart, vEnd);\n else if (c1 === 67 && cad === null) cad = parseIntSlice(text, vStart, vEnd);\n } else if (tagLen === 8) {\n if ((c1 === 103 || c1 === 71) && gearRear === null)\n gearRear = parseIntSlice(text, vStart, vEnd);\n } else if (tagLen === 9) {\n if (c1 === 103) {\n if (text.charCodeAt(lt + 8) === 104 && hr === null)\n hr = parseIntSlice(text, vStart, vEnd);\n else if (gearFront === null) gearFront = parseIntSlice(text, vStart, vEnd);\n } else if (c1 === 71 && gearFront === null) {\n gearFront = parseIntSlice(text, vStart, vEnd);\n }\n } else if (tagLen === 10) {\n if (c1 === 103 && cad === null) cad = parseIntSlice(text, vStart, vEnd);\n else if (c1 === 82 && cad === null) cad = parseIntSlice(text, vStart, vEnd);\n } else if (tagLen === 12) {\n if (c1 === 103 && text.charCodeAt(lt + 8) === 97 && temp === null)\n temp = parseFloatSlice(text, vStart, vEnd);\n else if (c1 === 80 && power === null) power = parseFloatSlice(text, vStart, vEnd);\n else if (c1 === 72 && hr === null) hr = parseIntSlice(text, vStart, vEnd);\n } else if (tagLen === 13) {\n if ((c1 === 103 || c1 === 71) && gearRearTooth === null)\n gearRearTooth = parseIntSlice(text, vStart, vEnd);\n } else if (tagLen === 14) {\n if ((c1 === 103 || c1 === 71) && gearFrontTooth === null)\n gearFrontTooth = parseIntSlice(text, vStart, vEnd);\n else if (c1 === 65 && temp === null) temp = parseFloatSlice(text, vStart, vEnd);\n } else if (tagLen === 11 && c1 === 84 && temp === null) {\n temp = parseFloatSlice(text, vStart, vEnd);\n }\n const expectedCloseGt = nextLt + 2 + tagLen;\n if (text.charCodeAt(expectedCloseGt) === 62) {\n p = expectedCloseGt + 1;\n } else {\n const closeGt = text.indexOf('>', nextLt + 2);\n p = closeGt !== -1 ? closeGt + 1 : nextLt + 2;\n }\n if (battery !== null && hr !== null && cad !== null && temp !== null) {\n break;\n }\n } else {\n p = nextLt;\n }\n }\n\n let gears: number | null = null;\n if (gearFrontTooth != null && gearRearTooth != null && gearRearTooth !== 0)\n gears = gearFrontTooth / gearRearTooth;\n else if (gearFront != null && gearRear != null && gearRear !== 0)\n gears = gearFront / gearRear;\n\n points.push({\n lat,\n lon,\n ele,\n time,\n hr,\n cad,\n power,\n speed,\n temp,\n gearFront,\n gearRear,\n gearFrontTooth,\n gearRearTooth,\n battery,\n gears,\n });\n }\n\n if (!points.length) throw new Error('No track points found in GPX');\n\n const pts = enrichPoints(points);\n const stats = computeStats(pts);\n return { name, device, format: 'gpx', points: pts, stats };\n }\n\n // \u2500\u2500 TCX parser \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n function parseTCX(buf: ArrayBuffer, predecodedText?: string): any {\n const text: string = predecodedText ?? new TextDecoder().decode(buf);\n const firstTp = text.indexOf('');\n const header = firstTp !== -1 ? text.slice(0, firstTp) : text;\n\n const name = extractXmlTag(header, '', '') || 'Unnamed TCX';\n const creatorBlock = extractXmlTag(header, '', '');\n const device = creatorBlock ? extractXmlTag(creatorBlock, '', '') : null;\n\n const points: TrackPoint[] = [];\n let pos = firstTp !== -1 ? firstTp : 0;\n while (pos < text.length) {\n const tpStart = text.indexOf('', pos);\n if (tpStart === -1) break;\n const tpEnd = text.indexOf('', tpStart + 12);\n if (tpEnd === -1) break;\n pos = tpEnd + 13;\n\n let lat: number | null = null;\n let lon: number | null = null;\n let ele: number | null = null;\n let time: number | null = null;\n let hr: number | null = null;\n let cad: number | null = null;\n let power: number | null = null;\n let speed: number | null = null;\n let temp: number | null = null;\n let gearFront: number | null = null;\n let gearRear: number | null = null;\n let gearFrontTooth: number | null = null;\n let gearRearTooth: number | null = null;\n let battery: number | null = null;\n\n let p = tpStart + 12;\n while (p < tpEnd) {\n const lt = text.indexOf('<', p);\n if (lt === -1 || lt >= tpEnd) break;\n const gt = text.indexOf('>', lt + 1);\n if (gt === -1 || gt >= tpEnd) break;\n const c1 = text.charCodeAt(lt + 1);\n if (c1 === 47) {\n p = gt + 1;\n continue;\n }\n const tagLen = gt - lt - 1;\n const nextLt = text.indexOf('<', gt + 1);\n if (nextLt === -1 || nextLt > tpEnd) break;\n if (text.charCodeAt(nextLt + 1) === 47) {\n const vStart = gt + 1;\n const vEnd = nextLt;\n if (tagLen === 4) {\n if (c1 === 84) {\n if (text.charCodeAt(lt + 2) === 105) time = fastParseTimeSlice(text, vStart, vEnd);\n else if (temp === null) temp = parseFloatSlice(text, vStart, vEnd);\n } else if (c1 === 116 && temp === null) {\n temp = parseFloatSlice(text, vStart, vEnd);\n }\n } else if (tagLen === 5) {\n if (c1 === 86 && hr === null) hr = parseIntSlice(text, vStart, vEnd);\n else if ((c1 === 87 || c1 === 80 || c1 === 112) && power === null)\n power = parseFloatSlice(text, vStart, vEnd);\n else if ((c1 === 83 || c1 === 115) && speed === null)\n speed = parseFloatSlice(text, vStart, vEnd);\n else if (c1 === 97 && temp === null) temp = parseFloatSlice(text, vStart, vEnd);\n } else if (tagLen === 7) {\n if (c1 === 67 && cad === null) cad = parseIntSlice(text, vStart, vEnd);\n else if ((c1 === 66 || c1 === 98) && battery === null)\n battery = parseIntSlice(text, vStart, vEnd);\n } else if (tagLen === 8) {\n if ((c1 === 71 || c1 === 103) && gearRear === null)\n gearRear = parseIntSlice(text, vStart, vEnd);\n } else if (tagLen === 9) {\n if ((c1 === 71 || c1 === 103) && gearFront === null)\n gearFront = parseIntSlice(text, vStart, vEnd);\n } else if (tagLen === 10) {\n if (c1 === 82 && cad === null) cad = parseIntSlice(text, vStart, vEnd);\n } else if (tagLen === 11) {\n if (c1 === 84 && temp === null) temp = parseFloatSlice(text, vStart, vEnd);\n } else if (tagLen === 13) {\n if ((c1 === 71 || c1 === 103) && gearRearTooth === null)\n gearRearTooth = parseIntSlice(text, vStart, vEnd);\n } else if (tagLen === 14) {\n if (c1 === 65) {\n if (text.charCodeAt(lt + 2) === 108) ele = parseFloatSlice(text, vStart, vEnd);\n else if (temp === null) temp = parseFloatSlice(text, vStart, vEnd);\n } else if ((c1 === 71 || c1 === 103) && gearFrontTooth === null) {\n gearFrontTooth = parseIntSlice(text, vStart, vEnd);\n }\n } else if (tagLen === 15) {\n if (c1 === 76) lat = parseFloatSlice(text, vStart, vEnd);\n } else if (tagLen === 16) {\n if (c1 === 76) lon = parseFloatSlice(text, vStart, vEnd);\n } else if (tagLen === 2 && c1 === 104 && hr === null) {\n hr = parseIntSlice(text, vStart, vEnd);\n } else if (tagLen === 3 && c1 === 99 && cad === null) {\n cad = parseIntSlice(text, vStart, vEnd);\n } else if (tagLen === 12) {\n if (c1 === 72 && hr === null) hr = parseIntSlice(text, vStart, vEnd);\n else if (c1 === 80 && power === null) power = parseFloatSlice(text, vStart, vEnd);\n }\n const expectedCloseGt = nextLt + 2 + tagLen;\n if (text.charCodeAt(expectedCloseGt) === 62) {\n p = expectedCloseGt + 1;\n } else {\n const closeGt = text.indexOf('>', nextLt + 2);\n p = closeGt !== -1 ? closeGt + 1 : nextLt + 2;\n }\n if (battery !== null && hr !== null && cad !== null && temp !== null) {\n break;\n }\n } else {\n p = nextLt;\n }\n }\n\n if (lat == null || lon == null) continue;\n\n let gears: number | null = null;\n if (gearFrontTooth != null && gearRearTooth != null && gearRearTooth !== 0)\n gears = gearFrontTooth / gearRearTooth;\n else if (gearFront != null && gearRear != null && gearRear !== 0)\n gears = gearFront / gearRear;\n\n points.push({\n lat,\n lon,\n ele,\n time,\n hr,\n cad,\n power,\n speed,\n temp,\n gearFront,\n gearRear,\n gearFrontTooth,\n gearRearTooth,\n battery,\n gears,\n });\n }\n\n const pts = enrichPoints(points);\n const stats = computeStats(pts);\n return { name, device, format: 'tcx', points: pts, stats };\n }\n\n function fastDecodeFitData(buf: ArrayBuffer): any | null {\n if (buf.byteLength < 14) return null;\n const dv = new DataView(buf);\n const headerSize = dv.getUint8(0);\n if (headerSize < 12 || headerSize + 2 > buf.byteLength) return null;\n if (\n dv.getUint8(8) !== 0x2e ||\n dv.getUint8(9) !== 0x46 ||\n dv.getUint8(10) !== 0x49 ||\n dv.getUint8(11) !== 0x54\n ) {\n return null;\n }\n const dataSize = dv.getUint32(4, true);\n const endOffset = Math.min(buf.byteLength, headerSize + dataSize);\n const GARMIN_EPOCH_SEC = 631065600;\n const SEMICIRCLE_TO_DEG = 180 / Math.pow(2, 31);\n\n interface FieldDef {\n num: number;\n size: number;\n type: number;\n }\n interface MsgDef {\n littleEndian: boolean;\n globalMsgNum: number;\n fields: FieldDef[];\n recordSize: number;\n }\n const defs: (MsgDef | undefined)[] = new Array(16);\n const file_ids: any[] = [];\n const sessions: any[] = [];\n const records: any[] = [];\n const events: any[] = [];\n const device_infos: any[] = [];\n\n let offset = headerSize;\n while (offset < endOffset) {\n const hdr = dv.getUint8(offset++);\n if ((hdr & 0x80) !== 0 || (hdr & 0x20) !== 0) {\n return null; // Compressed timestamp or developer data: fallback to FitParser\n }\n const localNum = hdr & 0x0f;\n if ((hdr & 0x40) !== 0) {\n if (offset + 5 > endOffset) return null;\n offset++; // reserved\n const arch = dv.getUint8(offset++);\n const littleEndian = arch === 0;\n const globalMsgNum = dv.getUint16(offset, littleEndian);\n offset += 2;\n const numFields = dv.getUint8(offset++);\n if (offset + numFields * 3 > endOffset) return null;\n const fields: FieldDef[] = new Array(numFields);\n let recordSize = 0;\n for (let i = 0; i < numFields; i++) {\n const num = dv.getUint8(offset++);\n const size = dv.getUint8(offset++);\n const type = dv.getUint8(offset++);\n fields[i] = { num, size, type };\n recordSize += size;\n }\n defs[localNum] = { littleEndian, globalMsgNum, fields, recordSize };\n } else {\n const def = defs[localNum];\n if (!def || offset + def.recordSize > endOffset) return null;\n const le = def.littleEndian;\n const gNum = def.globalMsgNum;\n\n if (\n gNum === 20 &&\n def.fields.length === 10 &&\n def.recordSize === 25 &&\n def.fields[0].num === 253 &&\n def.fields[1].num === 0\n ) {\n const ts = dv.getUint32(offset, le);\n const rawLat = dv.getInt32(offset + 4, le);\n const rawLon = dv.getInt32(offset + 8, le);\n const rawAlt = dv.getUint16(offset + 12, le);\n const hr = dv.getUint8(offset + 14);\n const cad = dv.getUint8(offset + 15);\n const rawDist = dv.getUint32(offset + 16, le);\n const rawSpd = dv.getUint16(offset + 20, le);\n const power = dv.getUint16(offset + 22, le);\n const temp = dv.getInt8(offset + 24);\n records.push({\n timestamp: (ts + GARMIN_EPOCH_SEC) * 1000,\n position_lat: rawLat * SEMICIRCLE_TO_DEG,\n position_long: rawLon * SEMICIRCLE_TO_DEG,\n altitude: rawAlt / 5 - 500,\n heart_rate: hr,\n cadence: cad,\n distance: rawDist / 100,\n speed: (rawSpd / 1000) * 3.6,\n power,\n temperature: temp,\n });\n offset += 25;\n continue;\n }\n\n const msg: any = {};\n let fOff = offset;\n for (let i = 0; i < def.fields.length; i++) {\n const f = def.fields[i];\n let val: number = 0;\n const bType = f.type & 0x1f;\n if (f.size === 1) {\n val = bType === 1 ? dv.getInt8(fOff) : dv.getUint8(fOff);\n } else if (f.size === 2) {\n val = bType === 3 ? dv.getInt16(fOff, le) : dv.getUint16(fOff, le);\n } else if (f.size === 4) {\n val = bType === 5 ? dv.getInt32(fOff, le) : dv.getUint32(fOff, le);\n }\n msg[f.num] = val;\n fOff += f.size;\n }\n offset += def.recordSize;\n\n if (gNum === 0) {\n file_ids.push({\n type: msg[0] === 4 ? 'activity' : msg[0],\n manufacturer: msg[1] === 1 ? 'garmin' : msg[1],\n product: msg[2],\n time_created: msg[4] != null ? (msg[4] + GARMIN_EPOCH_SEC) * 1000 : undefined,\n });\n } else if (gNum === 18) {\n const sportMap: Record = { 0: 'generic', 1: 'running', 2: 'cycling' };\n sessions.push({\n start_time: msg[2] != null ? (msg[2] + GARMIN_EPOCH_SEC) * 1000 : undefined,\n sport: sportMap[msg[5]] ?? String(msg[5]),\n sub_sport: msg[6] === 0 ? 'generic' : String(msg[6]),\n });\n } else if (gNum === 20) {\n records.push({\n timestamp: msg[253] != null ? (msg[253] + GARMIN_EPOCH_SEC) * 1000 : undefined,\n position_lat: msg[0] != null ? msg[0] * SEMICIRCLE_TO_DEG : undefined,\n position_long: msg[1] != null ? msg[1] * SEMICIRCLE_TO_DEG : undefined,\n altitude: msg[2] != null ? msg[2] / 5 - 500 : undefined,\n heart_rate: msg[3],\n cadence: msg[4],\n distance: msg[5] != null ? msg[5] / 100 : undefined,\n speed: msg[6] != null ? (msg[6] / 1000) * 3.6 : undefined,\n power: msg[7],\n temperature: msg[13],\n });\n } else if (gNum === 21) {\n const evName =\n msg[0] === 43\n ? 'rear_gear_change'\n : msg[0] === 42\n ? 'front_gear_change'\n : String(msg[0]);\n events.push({\n timestamp: msg[253] != null ? (msg[253] + GARMIN_EPOCH_SEC) * 1000 : undefined,\n event: evName,\n event_type: msg[1] === 3 ? 'marker' : String(msg[1]),\n data: msg[3],\n front_gear_num: msg[9],\n rear_gear_num: msg[11],\n });\n } else if (gNum === 23) {\n device_infos.push({\n timestamp: msg[253] != null ? (msg[253] + GARMIN_EPOCH_SEC) * 1000 : undefined,\n battery_level: msg[32],\n });\n } else {\n return null;\n }\n }\n }\n return { file_ids, sessions, sports: [], records, events, device_infos };\n }\n\n // \u2500\u2500 FIT parser \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n function parseFIT(buf: ArrayBuffer): Promise {\n return new Promise((resolve, reject) => {\n const handleParsedFitData = (error: any, data: any) => {\n if (error) {\n reject(error);\n return;\n }\n try {\n const name = data.sessions?.[0]?.start_time\n ? `Fit Activity ${new Date(data.sessions[0].start_time).toISOString().split('T')[0]}`\n : 'Unnamed Fit';\n\n // Better device detection\n let device: string | null = null;\n const devices: DeviceInfo[] = [];\n let sport: string | null = null;\n let subSport: string | null = null;\n\n if (data.sports?.[0]) {\n sport = data.sports[0].sport;\n subSport = data.sports[0].sub_sport;\n } else if (data.sessions?.[0]) {\n sport = data.sessions[0].sport;\n subSport = data.sessions[0].sub_sport;\n }\n\n const getDeviceName = (d: any) =>\n d.product_name ||\n d.garmin_product ||\n d.product ||\n d.device_type ||\n d.manufacturer ||\n null;\n\n if (data.file_ids?.[0]) {\n const fid = data.file_ids[0];\n device = getDeviceName(fid);\n devices.push({\n name: fid.product_name || fid.garmin_product || fid.product,\n manufacturer: fid.manufacturer,\n serial: fid.serial_number ? String(fid.serial_number) : undefined,\n version: fid.software_version ? String(fid.software_version) : undefined,\n type: 'main',\n });\n }\n\n if (data.device_infos) {\n data.device_infos.forEach((d: any) => {\n const isMain = d.device_index === 'creator' || d.device_index === 0;\n\n if (isMain) {\n const name = getDeviceName(d);\n // Prefer a name with product_name or garmin_product over generic ones\n if (d.product_name || d.garmin_product || !device) device = name;\n\n // Update existing main device if needed\n if (devices.length > 0 && devices[0].type === 'main') {\n if (d.product_name || d.garmin_product || !devices[0].name)\n devices[0].name = d.product_name || d.garmin_product || d.product;\n\n if (!devices[0].manufacturer) devices[0].manufacturer = d.manufacturer;\n if (!devices[0].serial && d.serial_number)\n devices[0].serial = String(d.serial_number);\n if (!devices[0].version && d.software_version)\n devices[0].version = String(d.software_version);\n if (!devices[0].hardwareVersion && d.hardware_version)\n devices[0].hardwareVersion = String(d.hardware_version);\n }\n return;\n }\n\n devices.push({\n name: d.product_name || d.garmin_product || d.product || d.device_type || 'Sensor',\n manufacturer: d.manufacturer,\n serial: d.serial_number ? String(d.serial_number) : undefined,\n version: d.software_version ? String(d.software_version) : undefined,\n hardwareVersion: d.hardware_version ? String(d.hardware_version) : undefined,\n type: d.device_type || 'sensor',\n batteryStatus: d.battery_status,\n batteryVoltage: d.battery_voltage,\n batteryLevel: d.battery_level,\n sourceType: d.source_type != null ? String(d.source_type) : undefined,\n });\n });\n }\n\n if (data.sensor_settings) {\n data.sensor_settings.forEach((s: any) => {\n const name = s.name || s.product || s.sensor_type;\n if (!name) return;\n\n // Check if we already have this device (by ANT ID or name)\n const antId = s.ant_id ? String(s.ant_id) : undefined;\n const existing = devices.find(\n (d) =>\n (antId && d.serial === antId) ||\n (d.name === name && d.manufacturer === s.manufacturer),\n );\n\n if (existing) {\n if (!existing.name) existing.name = name;\n if (!existing.manufacturer) existing.manufacturer = s.manufacturer;\n if (!existing.type) existing.type = s.sensor_type;\n if (!existing.sourceType)\n existing.sourceType =\n s.connection_type != null ? String(s.connection_type) : undefined;\n } else {\n devices.push({\n name: name,\n manufacturer: s.manufacturer,\n serial: antId,\n type: s.sensor_type || 'sensor',\n sourceType: s.connection_type != null ? String(s.connection_type) : undefined,\n });\n }\n });\n }\n\n // Collect standalone device status reports (often contains head unit battery)\n // We'll store them in a sorted array for easier lookups\n const statusReports: { time: number; level: number | null; idx: number }[] = [];\n if (data.device_statuses) {\n data.device_statuses.forEach((s: any, idx: number) => {\n if (s.timestamp && s.battery_level != null) {\n statusReports.push({\n time:\n typeof s.timestamp === 'number' ? s.timestamp : new Date(s.timestamp).getTime(),\n level: s.battery_level,\n idx,\n });\n }\n });\n statusReports.sort((a, b) => a.time - b.time || a.idx - b.idx);\n }\n\n // Collect gear events\n const gearEvents: {\n time: number;\n front: number | null;\n rear: number | null;\n frontTooth: number | null;\n rearTooth: number | null;\n idx: number;\n }[] = [];\n if (data.events) {\n data.events.forEach((e: any, idx: number) => {\n let front = e.front_gear_num ?? e.front_gear ?? null;\n let rear = e.rear_gear_num ?? e.rear_gear ?? null;\n let frontTooth = e.front_gear_num_tooth ?? null;\n let rearTooth = e.rear_gear_num_tooth ?? null;\n\n if (e.event === 'rear_gear_change' || e.event === 'front_gear_change') {\n const d = e.data;\n if (d != null) {\n const ft = (d >> 24) & 0xff;\n const fg = (d >> 16) & 0xff;\n const rt = (d >> 8) & 0xff;\n const rg = d & 0xff;\n\n if (fg !== 0) front = fg;\n if (ft !== 0) frontTooth = ft;\n if (rg !== 0) rear = rg;\n if (rt !== 0) rearTooth = rt;\n }\n }\n\n if (\n e.timestamp &&\n (front != null || rear != null || frontTooth != null || rearTooth != null)\n ) {\n gearEvents.push({\n time:\n typeof e.timestamp === 'number' ? e.timestamp : new Date(e.timestamp).getTime(),\n front,\n rear,\n frontTooth,\n rearTooth,\n idx,\n });\n }\n });\n gearEvents.sort((a, b) => a.time - b.time || a.idx - b.idx);\n }\n\n const records = data.records || [];\n let lastBattery: number | null = null;\n let statusIdx = 0;\n let lastFrontGear: number | null = null;\n let lastRearGear: number | null = null;\n let lastFrontTooth: number | null = null;\n let lastRearTooth: number | null = null;\n let gearIdx = 0;\n\n const validPoints: TrackPoint[] = [];\n for (let ri = 0; ri < records.length; ri++) {\n const r = records[ri];\n const lat = r.position_lat;\n const lon = r.position_long;\n if (lat == null || lon == null) continue;\n\n const time =\n r.timestamp != null\n ? typeof r.timestamp === 'number'\n ? r.timestamp\n : r.timestamp instanceof Date\n ? r.timestamp.getTime()\n : new Date(r.timestamp).getTime()\n : null;\n const speed = r.speed != null ? r.speed / 3.6 : null; // km/h \u2192 m/s\n\n let battery = r.battery_level ?? null;\n\n // If not in record, check our status reports\n if (battery == null && time) {\n // Catch up statusIdx to current time\n while (statusIdx < statusReports.length && statusReports[statusIdx].time <= time) {\n lastBattery = statusReports[statusIdx].level;\n statusIdx++;\n }\n battery = lastBattery;\n } else if (battery != null) {\n lastBattery = battery;\n }\n\n // Update gear state from events\n if (time) {\n while (gearIdx < gearEvents.length && gearEvents[gearIdx].time <= time) {\n const e = gearEvents[gearIdx];\n if (e.front != null) lastFrontGear = e.front;\n if (e.rear != null) lastRearGear = e.rear;\n if (e.frontTooth != null) lastFrontTooth = e.frontTooth;\n if (e.rearTooth != null) lastRearTooth = e.rearTooth;\n gearIdx++;\n }\n }\n\n const gearFront: number | null = r.front_gear_num ?? r.front_gear ?? lastFrontGear;\n const gearRear: number | null = r.rear_gear_num ?? r.rear_gear ?? lastRearGear;\n const gearFrontTooth: number | null = r.front_gear_num_tooth ?? lastFrontTooth;\n const gearRearTooth: number | null = r.rear_gear_num_tooth ?? lastRearTooth;\n\n if (r.front_gear_num != null || r.front_gear != null) lastFrontGear = gearFront;\n if (r.rear_gear_num != null || r.rear_gear != null) lastRearGear = gearRear;\n if (r.front_gear_num_tooth != null) lastFrontTooth = gearFrontTooth;\n if (r.rear_gear_num_tooth != null) lastRearTooth = gearRearTooth;\n\n const gears =\n gearFrontTooth != null && gearRearTooth != null && gearRearTooth !== 0\n ? gearFrontTooth / gearRearTooth\n : null;\n\n validPoints.push({\n lat,\n lon,\n ele: r.altitude ?? r.enhanced_altitude ?? null,\n time,\n hr: r.heart_rate ?? null,\n cad: r.cadence ?? null,\n power: r.power ?? null,\n speed,\n temp: r.temperature ?? null,\n gearFront,\n gearRear,\n gearFrontTooth,\n gearRearTooth,\n battery,\n gears,\n });\n }\n\n if (!validPoints.length) {\n reject(new Error('FIT file has no GPS points'));\n return;\n }\n\n const pts = enrichPoints(validPoints);\n const stats = computeStats(pts);\n resolve({ name, device, devices, format: 'fit', points: pts, stats, sport, subSport });\n } catch (ex) {\n reject(ex);\n }\n };\n\n const fastData = fastDecodeFitData(buf);\n if (fastData) {\n handleParsedFitData(null, fastData);\n return;\n }\n\n const fitParser = new FitParser({\n force: true,\n speedUnit: 'km/h',\n lengthUnit: 'm',\n temperatureUnit: 'celsius',\n elapsedRecordField: true,\n });\n\n fitParser.parse(buf, handleParsedFitData);\n });\n }\n\n // \u2500\u2500 KML parser \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n function parseKML(buf: ArrayBuffer): any {\n const text = new TextDecoder().decode(buf);\n const doc = new DOMParser().parseFromString(text, 'application/xml');\n\n let name = 'Unnamed Track';\n const nameEl = doc.querySelector('Document > name, Placemark > name');\n if (nameEl) name = nameEl.textContent?.trim() || name;\n\n // Track: look for gx:Track or LineString\n const coordEls = doc.querySelectorAll('coordinates');\n if (!coordEls.length) throw new Error('No coordinates found in KML');\n\n const points: TrackPoint[] = [];\n coordEls.forEach((el) => {\n const lines = el.textContent?.trim().split(/\\s+/) || [];\n lines.forEach((line) => {\n const parts = line.split(',');\n if (parts.length < 2) return;\n const lon = parseFloat(parts[0]);\n const lat = parseFloat(parts[1]);\n const ele = parts.length > 2 ? parseFloat(parts[2]) : null;\n if (isNaN(lat) || isNaN(lon)) return;\n points.push({\n lat,\n lon,\n ele: ele != null && isNaN(ele) ? null : ele,\n time: null,\n hr: null,\n cad: null,\n power: null,\n speed: null,\n temp: null,\n gearFront: null,\n gearRear: null,\n gearFrontTooth: null,\n gearRearTooth: null,\n battery: null,\n gears: null,\n });\n });\n });\n\n if (!points.length) throw new Error('No valid coordinates in KML');\n const pts = enrichPoints(points);\n const stats = computeStats(pts);\n return { name, format: 'kml', points: pts, stats };\n }\n\n // \u2500\u2500 Public: dispatch by extension \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n async function parseFile(file: File): Promise {\n const ext = file.name.split('.').pop()?.toLowerCase();\n const buf: ArrayBuffer = (file as any)._buf ?? (await file.arrayBuffer());\n const predecodedText: string | undefined = (file as any)._text;\n let result: any;\n switch (ext) {\n case 'gpx':\n result = parseGPX(buf, predecodedText);\n break;\n case 'tcx':\n result = parseTCX(buf, predecodedText);\n break;\n case 'fit':\n result = await parseFIT(buf);\n break;\n case 'kml':\n result = parseKML(buf);\n break;\n default:\n throw new Error(`Unsupported format: .${ext}`);\n }\n\n if (result) {\n result.fileName = file.name;\n result.fileSize = file.size;\n if (result.points && result.stats) {\n ensureCurvesAndZones(result);\n result.stats.sections = analyzeTrackSections(result.points);\n }\n }\n return result;\n }\n\n return {\n parseFile,\n computeStats,\n ensureCurvesAndZones,\n analyzeTrackSections,\n findOverlaps,\n calculatePowerCurve,\n calculateHRCurve,\n calculatePowerZones,\n calculateHRZones,\n enrichPoints,\n setHRZones,\n getHRZones,\n setFTP,\n getFTP,\n };\n})();\n", "// Copyright (c) 2026, Camillo Bruni.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n\n'use strict';\n\nexport function haversine(lat1: number, lon1: number, lat2: number, lon2: number): number {\n const R = 6371e3; // Earth radius in metres\n const phi1 = (lat1 * Math.PI) / 180;\n const phi2 = (lat2 * Math.PI) / 180;\n const deltaPhi = ((lat2 - lat1) * Math.PI) / 180;\n const deltaLambda = ((lon2 - lon1) * Math.PI) / 180;\n\n const a =\n Math.sin(deltaPhi / 2) * Math.sin(deltaPhi / 2) +\n Math.cos(phi1) * Math.cos(phi2) * Math.sin(deltaLambda / 2) * Math.sin(deltaLambda / 2);\n const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));\n\n return R * c;\n}\n\nexport function compactId(timestamp: number): string {\n // Use a compact representation of the timestamp (seconds since epoch)\n // 1712650000 -> Base64\n const buf = new Uint32Array([Math.floor(timestamp / 1000)]);\n const b64 = btoa(String.fromCharCode(...new Uint8Array(buf.buffer)));\n return b64.replace(/\\+/g, '-').replace(/\\//g, '_').replace(/=/g, '');\n}\n\nexport function generateId(): string {\n return Math.random().toString(36).slice(2, 10);\n}\n\nexport function shortRandom(): string {\n return Math.random().toString(36).slice(2, 6);\n}\n\nexport function fmtDuration(s: number): string {\n if (s < 60) return `${Math.round(s)}s`;\n if (s < 3600) return `${Math.round(s / 60)}m`;\n return `${Math.round(s / 3600)}h`;\n}\n\nexport function fmtSecs(s: number): string {\n const h = Math.floor(s / 3600),\n m = Math.floor((s % 3600) / 60);\n return h > 0 ? `${h}h ${m}m` : `${m}m ${Math.floor(s % 60)}s`;\n}\n\nexport function fmtPace(seconds: number | null): string {\n if (seconds == null || !isFinite(seconds)) return '--';\n const mins = Math.floor(seconds / 60);\n const secs = Math.round(seconds % 60);\n return `${mins}:${secs.toString().padStart(2, '0')}`;\n}\n\nexport function fmtDate(ts: number | string | Date): string {\n const d = new Date(ts);\n if (isNaN(d.getTime())) return 'Unknown date';\n return d.toISOString().split('T')[0];\n}\n\nexport function fmtDateTime(ts: number | string | Date): string {\n const d = new Date(ts);\n if (isNaN(d.getTime())) return 'Unknown';\n const iso = d.toISOString();\n return `${iso.split('T')[0]} ${iso.split('T')[1].slice(0, 5)}`;\n}\n\nexport function fmtFileSize(bytes: number): string {\n if (bytes === 0) return '0 B';\n const k = 1024;\n const sizes = ['B', 'KB', 'MB', 'GB'];\n const i = Math.floor(Math.log(bytes) / Math.log(k));\n return `${parseFloat((bytes / Math.pow(k, i)).toFixed(2))} ${sizes[i]}`;\n}\n\nexport function escHtml(str: string): string {\n return String(str)\n .replace(/&/g, '&')\n .replace(//g, '>')\n .replace(/\"/g, '"');\n}\n\nexport function getTagColor(name: string): string {\n let hash = 0;\n for (let i = 0; i < name.length; i++) hash = name.charCodeAt(i) + ((hash << 5) - hash);\n\n const h = Math.abs(hash % 360);\n return `hsl(${h}, 70%, 65%)`;\n}\n\n/**\n * Smoothes a numeric array using a simple Gaussian kernel.\n * sigma: standard deviation (radius of smoothing)\n */\nexport function gaussianSmooth(data: (number | null)[], sigma: number = 2): (number | null)[] {\n if (sigma <= 0) return data;\n\n const size = Math.ceil(sigma * 3) * 2 + 1;\n const kernel = new Float32Array(size);\n const half = Math.floor(size / 2);\n let sum = 0;\n\n for (let i = 0; i < size; i++) {\n const x = i - half;\n kernel[i] = Math.exp(-(x * x) / (2 * sigma * sigma));\n sum += kernel[i];\n }\n for (let i = 0; i < size; i++) kernel[i] /= sum;\n\n const result: (number | null)[] = new Array(data.length);\n for (let i = 0; i < data.length; i++) {\n if (data[i] === null) {\n result[i] = null;\n continue;\n }\n\n let val = 0;\n let weightSum = 0;\n for (let k = 0; k < size; k++) {\n const idx = i + k - half;\n if (idx >= 0 && idx < data.length && data[idx] !== null) {\n val += data[idx]! * kernel[k];\n weightSum += kernel[k];\n }\n }\n result[i] = weightSum > 0 ? val / weightSum : data[i];\n }\n return result;\n}\n\nexport function hexToRgba(hex: string, alpha: number = 1): string {\n const r = parseInt(hex.slice(1, 3), 16);\n const g = parseInt(hex.slice(3, 5), 16);\n const b = parseInt(hex.slice(5, 7), 16);\n return `rgba(${r}, ${g}, ${b}, ${alpha})`;\n}\n", "// Copyright (c) 2026, Camillo Bruni.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n\n'use strict';\n\nimport { TrackData, TrackPoint } from './parsers';\n\nexport const MapView = (() => {\n let trackData: Record = {};\n let customClickHandler: ((e: any) => boolean) | null = null;\n\n function init(\n onSelectCb: (id: string) => void,\n onMoveCb: (lat: number, lng: number, zoom: number) => void,\n onPointClickCb: (id: string, idx: number) => void,\n onPointHoverCb: (id: string | null, idx: number | null) => void,\n onDblClickCb: () => void,\n ) {}\n\n function switchBasemap(layer: string) {}\n function addTrack(t: TrackData) {\n trackData[t.id] = t;\n }\n function addTracks(ts: TrackData[]) {\n ts.forEach((t) => {\n trackData[t.id] = t;\n });\n }\n function removeTrack(id: string) {\n delete trackData[id];\n }\n function clearTracks() {\n trackData = {};\n }\n function removeAllTracks() {\n trackData = {};\n }\n function setTrackVisible(id: string, visible: boolean) {}\n function setSelectedTrack(id: string | null, fit: boolean) {}\n function fitAll() {}\n function fitTrack(id: string) {}\n function showCursorAt(lat: number, lon: number) {}\n function hideCursor() {}\n function highlightSegment(\n id: string | null,\n pts: TrackPoint[],\n start: number,\n end: number,\n fit?: boolean,\n colors?: string[] | null,\n ) {}\n function clearHighlight() {}\n function setTrackHover(id: string, hover: boolean) {}\n function ensureVisible(pts: any) {}\n function centerOn(lat: number, lon: number, zoom?: number | null, force?: boolean) {}\n function closePopup() {}\n function invalidateSize() {}\n function getPosition(): [number, number, number] {\n return [0, 0, 0];\n }\n function setPosition(lat: number, lng: number, zoom: number) {}\n function colorTrackByMetric(id: string, pts: TrackPoint[], colors: string[]) {}\n function clearMetricColor(id: string) {}\n\n function findTracksPassingThroughPoints(\n points: { lat: number; lng: number }[],\n radius: number,\n ): string[] {\n return Object.keys(trackData);\n }\n\n return {\n init,\n getMap: () => null as any,\n setCustomClickHandler: (handler: any) => {\n customClickHandler = handler;\n },\n switchBasemap,\n addTrack,\n addTracks,\n removeTrack,\n clearTracks,\n removeAllTracks,\n setTrackVisible,\n setSelectedTrack,\n fitAll,\n fitTrack,\n showCursorAt,\n hideCursor,\n highlightSegment,\n clearHighlight,\n setTrackHover,\n ensureVisible,\n centerOn,\n closePopup,\n invalidateSize,\n getPosition,\n setPosition,\n colorTrackByMetric,\n clearMetricColor,\n findTracksPassingThroughPoints,\n getTrackData: (id: string) => trackData[id],\n };\n})();\n", "// Copyright (c) 2026, Camillo Bruni.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n\n'use strict';\n\nimport uPlot from 'uplot';\nimport { TrackPoint, TrackStats, TrackData } from './parsers';\nimport { gaussianSmooth, fmtSecs, hexToRgba, fmtPace } from './utils';\nimport { Zones } from './zones';\nimport { METRICS, MetricDefinition } from './metrics';\nimport { UrlState } from './url-state';\nimport { SPORTS } from './sports';\n\n// Augment uPlot to include our custom property\n\ninterface WegPlot extends uPlot {\n _strasse?: {\n selFill: HTMLElement;\n anchorLine: HTMLElement;\n anchorLabel: HTMLElement;\n anchorHandle: HTMLElement;\n anchorMarker: HTMLElement;\n endLine: HTMLElement;\n endLabel: HTMLElement;\n endHandle: HTMLElement;\n endMarker: HTMLElement;\n curYVal: HTMLElement;\n updateHeaderStats: (min: number, max: number) => void;\n };\n}\n\ninterface PlotData {\n uplot: WegPlot;\n row: HTMLDivElement;\n xData: number[];\n yData: (number | null)[];\n def: MetricDefinition;\n metricKey: string;\n statsTotalEl: HTMLDivElement;\n statsSelEl: HTMLDivElement;\n histCol: HTMLDivElement;\n histCanvas: HTMLCanvasElement;\n histData: HistData;\n hoveredHistY: number | null;\n pinnedHistY: number | null;\n smoothed?: boolean;\n incZero?: boolean;\n w?: number;\n}\n\ninterface HistData {\n yData: (number | null)[];\n xData: number[];\n def: MetricDefinition;\n BINS: number;\n plot?: PlotData;\n bins?: number[];\n binAccum?: number[];\n min?: number;\n max?: number;\n padL?: number;\n selBins?: number[] | null;\n selBinAccum?: number[] | null;\n _lastYData?: any;\n _lastSelRange?: [number, number] | null;\n fullStats?: { min: number; max: number; avg: number } | null;\n fullStatsAll?: { min: number; max: number; avg: number } | null;\n}\n\nexport const ChartView = (() => {\n let plots: PlotData[] = [];\n let renderedTrack: TrackData | null = null;\n let renderedXAxis: string = 'time';\n let renderedSpeedMode: string = 'speed';\n let renderedMapColorMetric: string | null = null;\n let syncKey: {\n key: string;\n sub: (u: uPlot) => void;\n unsub: (u: uPlot) => void;\n pub: (type: string, u: uPlot, x: number, y: number, w: number, h: number, i: number) => void;\n } | null = null;\n let scaleSyncing: boolean = false;\n let cursorSyncing: boolean = false;\n let activeMetrics: Set = new Set(['elevation']);\n let availableMetrics: Set = new Set();\n let smoothedMetrics: Set = new Set(['speed', 'gradient', 'vam', 'temperature']);\n let metricsIncludingZero: Set = new Set(['power']);\n let xAxis: string = 'time';\n let currentTrack: TrackData | null = null;\n let allTracks: TrackData[] = [];\n\n // Selection / anchor\n let selAnchorVal: number | null = null; // x-value at drag-start / zoom left edge\n let selEndVal: number | null = null; // x-value at zoom right edge\n let isDragging: boolean = false;\n let updatingRange: boolean = false; // guard against setScale hook re-entry during handle drag\n let pinnedPtIdx: number | null = null; // point index pinned by map click\n let lastMouseXVal: number | null = null; // last hovered x-value for keyboard zoom center\n let lastHistMouseEvent: MouseEvent | null = null;\n let hoveredHistData: HistData | null = null;\n let hoveredHistCanvas: HTMLCanvasElement | null = null;\n\n let currentXRange: [number, number] | null = null; // [min, max] currently rendered\n let targetXRange: [number, number] | null = null; // [min, max] for smooth keyboard animation\n let animId: number | null = null;\n\n // Callbacks\n let onCursorMoveCb: ((pt: TrackPoint | null) => void) | null = null;\n let onRangeChangeCb: ((min: number | null, max: number | null, axis: string) => void) | null =\n null;\n let onClickCb: ((pt: TrackPoint, idx: number) => void) | null = null;\n let onPinChangeCb: ((idx: number | null) => void) | null = null;\n\n const HIST_W: number = 130;\n const ROW_BODY_PADDING: number = 28; // 14px left + 14px right\n const HIST_GAP: number = 20; // margin-left on .hist-col\n let statsVisible: boolean = true;\n let histTooltipEl: HTMLElement | null = null;\n let container: HTMLElement | null = null;\n let emptyEl: HTMLElement | null = null;\n let selStatsEl: HTMLElement | null = null;\n let resetSelBtn: HTMLElement | null = null;\n let mapColorMetric: string | null = null;\n let onMapColorChangeCb: ((data: { pts: TrackPoint[]; colors: string[] } | null) => void) | null =\n null;\n\n // \u2500\u2500 Init \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n function init(\n onCursorMove: (pt: TrackPoint | null) => void,\n onRangeChange: (min: number | null, max: number | null, axis: string) => void,\n onClick: (pt: TrackPoint, idx: number) => void,\n onPinChange: (idx: number | null) => void,\n ) {\n onCursorMoveCb = onCursorMove;\n onRangeChangeCb = onRangeChange;\n onClickCb = onClick;\n onPinChangeCb = onPinChange;\n container = document.getElementById('charts-container');\n container?.addEventListener('scroll', () => {\n if (lastHistMouseEvent && hoveredHistCanvas && hoveredHistData)\n updateHistTooltip(lastHistMouseEvent, hoveredHistCanvas, hoveredHistData);\n });\n emptyEl = document.getElementById('chart-empty');\n selStatsEl = document.getElementById('chart-stats-sel');\n resetSelBtn = document.getElementById('btn-reset-selection');\n histTooltipEl = document.getElementById('hist-tooltip');\n syncKey = uPlot.sync('strasse-sync');\n\n document.getElementById('sel-cancel-btn')?.addEventListener('click', cancelSelection);\n resetSelBtn?.classList.add('hidden');\n\n // \u2500\u2500 WASD Keyboard Navigation \u2500\u2500\n const navKeys = new Set(['w', 'a', 's', 'd']);\n const activeNavKeys = new Set();\n\n document.addEventListener('keydown', (e) => {\n if (!plots.length || !currentTrack) return;\n if (['INPUT', 'TEXTAREA', 'SELECT'].includes(document.activeElement?.tagName || '')) return;\n\n const key = e.key.toLowerCase();\n if (!navKeys.has(key)) return;\n\n e.preventDefault();\n activeNavKeys.add(key);\n\n const u = plots[0].uplot;\n if (!targetXRange) targetXRange = [u.scales.x!.min!, u.scales.x!.max!];\n\n let [min, max] = targetXRange;\n const span = max - min;\n const moveStep = span * 0.1;\n const zoomFactor = 0.15;\n\n let newMin = min,\n newMax = max;\n\n if (key === 'a') {\n // Pan left\n newMin = min - moveStep;\n newMax = max - moveStep;\n } else if (key === 'd') {\n // Pan right\n newMin = min + moveStep;\n newMax = max + moveStep;\n } else if (key === 'w' || key === 's') {\n // Zoom\n const center = lastMouseXVal !== null ? lastMouseXVal : (min + max) / 2;\n const factor = key === 'w' ? 1 - zoomFactor : 1 + zoomFactor * 1.2;\n newMin = center - (center - min) * factor;\n newMax = center + (max - center) * factor;\n }\n\n // Clamp to track bounds\n const xFull = [plots[0].xData[0]!, plots[0].xData[plots[0].xData.length - 1]!];\n if (newMin < xFull[0]) {\n const d = xFull[0] - newMin;\n newMin += d;\n newMax += d;\n }\n if (newMax > xFull[1]) {\n const d = newMax - xFull[1];\n newMin -= d;\n newMax -= d;\n }\n newMin = Math.max(xFull[0], newMin);\n newMax = Math.min(xFull[1], newMax);\n\n if (newMax - newMin > 0.001) {\n targetXRange = [newMin, newMax];\n setVisibleRange(newMin, newMax, true); // true = animate\n }\n });\n\n document.addEventListener('keyup', (e) => {\n const key = e.key.toLowerCase();\n if (navKeys.has(key)) activeNavKeys.delete(key);\n });\n }\n\n function setVisibleRange(min: number, max: number, animate = false) {\n if (!plots.length) return;\n if (!animate) {\n plots.forEach(({ uplot: u }) => u.setScale('x', { min, max }));\n return;\n }\n\n const startMin = (currentXRange ? currentXRange[0] : plots[0].uplot.scales.x!.min) as number;\n const startMax = (currentXRange ? currentXRange[1] : plots[0].uplot.scales.x!.max) as number;\n\n if (animId) cancelAnimationFrame(animId);\n\n const lerp = (a: number, b: number, t: number) => a + (b - a) * t;\n const factor = 0.25; // Smoothing factor\n\n const step = () => {\n if (!targetXRange) return;\n const [tMin, tMax] = targetXRange;\n\n const cMin = lerp(currentXRange![0], tMin, factor);\n const cMax = lerp(currentXRange![1], tMax, factor);\n\n plots.forEach(({ uplot: u }) => {\n u.setScale('x', { min: cMin, max: cMax });\n });\n\n if (Math.abs(cMin - tMin) > 0.0001 || Math.abs(cMax - tMax) > 0.0001)\n animId = requestAnimationFrame(step);\n else animId = null;\n };\n animId = requestAnimationFrame(step);\n }\n\n // \u2500\u2500 Public API \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n function loadTrack(track: TrackData, others: TrackData[] = [], deferRender = false) {\n pinnedPtIdx = null;\n selAnchorVal = null;\n selEndVal = null;\n allTracks = others;\n\n // Detect which metrics are available in this file\n availableMetrics = new Set(\n Object.entries(METRICS)\n .filter(([, def]) => track.points.some((p) => p[def.field] != null))\n .map(([key]) => key),\n );\n\n // If no metrics are active (e.g. first load), default to core overview metrics\n if (activeMetrics.size === 0) {\n activeMetrics = new Set(['elevation'].filter((m) => availableMetrics.has(m)));\n }\n\n // Sync the pill buttons in the toolbar\n document.querySelectorAll('.metric-pill').forEach((el) => {\n const pill = el as HTMLElement;\n const metric = pill.dataset.metric!;\n const isAvailable = availableMetrics.has(metric);\n pill.classList.toggle('active', isAvailable && activeMetrics.has(metric));\n pill.classList.toggle('disabled', !isAvailable);\n });\n\n currentTrack = track;\n if (deferRender) {\n updateStats(track);\n } else {\n render();\n }\n }\n\n function clear() {\n pinnedPtIdx = null;\n selAnchorVal = null;\n selEndVal = null;\n currentTrack = null;\n destroyPlots();\n if (emptyEl) {\n emptyEl.style.display = 'flex';\n emptyEl.innerHTML = `\n no_sim\n
Select a track to view analysis
\n `;\n }\n const chartStats = document.getElementById('chart-stats');\n if (chartStats) chartStats.classList.add('hidden');\n clearSelectionStats();\n mapColorMetric = null;\n if (onMapColorChangeCb) onMapColorChangeCb(null);\n }\n\n function resetState() {\n if (animId !== null) {\n cancelAnimationFrame(animId);\n animId = null;\n }\n activeMetrics = new Set(['elevation']);\n availableMetrics = new Set();\n smoothedMetrics = new Set(['speed', 'gradient', 'vam', 'temperature']);\n metricsIncludingZero = new Set(['power']);\n xAxis = 'time';\n allTracks = [];\n currentXRange = null;\n targetXRange = null;\n lastMouseXVal = null;\n lastHistMouseEvent = null;\n hoveredHistData = null;\n hoveredHistCanvas = null;\n statsVisible = true;\n clear();\n document.querySelectorAll('#metric-pills .metric-pill').forEach((el) => {\n const pill = el as HTMLElement;\n pill.classList.toggle('active', activeMetrics.has(pill.dataset.metric!));\n pill.classList.remove('disabled', 'overflow-hidden');\n });\n const overflowEl = document.getElementById('pill-overflow');\n if (overflowEl) overflowEl.classList.add('hidden');\n const toolbar = document.getElementById('chart-toolbar');\n if (toolbar) toolbar.classList.remove('collapsed');\n document.querySelectorAll('#x-axis-ctrl .seg-btn').forEach((b) => {\n b.classList.toggle('active', (b as HTMLElement).dataset.axis === 'time');\n });\n }\n\n function toggleMetric(key: string) {\n if (activeMetrics.has(key)) activeMetrics.delete(key);\n else activeMetrics.add(key);\n if (currentTrack) render(true);\n }\n\n function setSmooth(metricKey: string, enabled: boolean, skipRender = false) {\n const has = smoothedMetrics.has(metricKey);\n if (enabled && !has) {\n smoothedMetrics.add(metricKey);\n if (currentTrack && !skipRender) render(true);\n } else if (!enabled && has) {\n smoothedMetrics.delete(metricKey);\n if (currentTrack && !skipRender) render(true);\n }\n }\n\n function setZeroInclusion(metricKey: string, includeZero: boolean) {\n const has = metricsIncludingZero.has(metricKey);\n if (includeZero && !has) {\n metricsIncludingZero.add(metricKey);\n if (currentTrack) render(true);\n } else if (!includeZero && has) {\n metricsIncludingZero.delete(metricKey);\n if (currentTrack) render(true);\n }\n }\n\n function setActiveMetrics(keys: string[]) {\n activeMetrics = new Set(keys);\n if (currentTrack) render(true);\n // Sync the pill buttons\n document.querySelectorAll('.metric-pill').forEach((el) => {\n const pill = el as HTMLElement;\n pill.classList.toggle('active', activeMetrics.has(pill.dataset.metric!));\n });\n }\n\n function setXAxis(axis: string) {\n if (xAxis === axis || !currentTrack) {\n xAxis = axis;\n return;\n }\n\n const pts = currentTrack.points;\n const t0 = pts.find((p) => p.time != null)?.time || 0;\n\n // Helper to convert a value from OLD axis to NEW axis\n const convert = (val: number | null) => {\n if (val === null) return null;\n // We need to find the point index closest to 'val' on the OLD axis\n let idx = 0;\n if (xAxis === 'distance') {\n const dTarget = val * 1000;\n idx = pts.findIndex((p) => (p.dist || 0) >= dTarget);\n } else {\n const tTarget = t0 + val * 1000;\n idx = pts.findIndex((p) => (p.time || 0) >= tTarget);\n }\n if (idx === -1) idx = pts.length - 1;\n\n // Now get the value at this index on the NEW axis\n if (axis === 'distance') return (pts[idx].dist || 0) / 1000;\n else return ((pts[idx].time || t0) - t0) / 1000;\n };\n\n // Convert selection\n if (selAnchorVal !== null) selAnchorVal = convert(selAnchorVal);\n if (selEndVal !== null) selEndVal = convert(selEndVal);\n\n // Convert zoom range (from first plot)\n let newRange: [number, number] | null = null;\n if (plots.length) {\n const u = plots[0].uplot;\n const r0 = convert(u.scales.x!.min!);\n const r1 = convert(u.scales.x!.max!);\n if (r0 !== null && r1 !== null) newRange = [r0, r1];\n }\n\n xAxis = axis;\n render(true);\n\n // Apply converted zoom\n if (newRange) {\n scaleSyncing = true;\n plots.forEach(({ uplot: u }) => {\n u.batch(() => u.setScale('x', { min: newRange![0], max: newRange![1] }));\n });\n scaleSyncing = false;\n }\n }\n\n function toggleStats() {\n statsVisible = !statsVisible;\n plots.forEach(({ histCol }) => histCol.classList.toggle('visible', statsVisible));\n resize();\n }\n\n function setMapColorChangeCb(cb: (data: { pts: TrackPoint[]; colors: string[] } | null) => void) {\n onMapColorChangeCb = cb;\n }\n\n function toggleMapColor(key: string | null, skipRender = false) {\n if (mapColorMetric === key || key === null) {\n mapColorMetric = null;\n _updateMapColorBtns();\n if (onMapColorChangeCb) onMapColorChangeCb(null);\n } else {\n mapColorMetric = key;\n _updateMapColorBtns();\n _fireMapColorCb();\n }\n // Re-render to show/hide colored fills on the graphs\n if (currentTrack && !skipRender) render(true);\n }\n\n function _updateMapColorBtns() {\n document.querySelectorAll('.map-color-btn').forEach((el) => {\n const b = el as HTMLElement;\n b.classList.toggle('active', b.dataset.metric === mapColorMetric);\n });\n }\n\n function _fireMapColorCb() {\n if (!onMapColorChangeCb || !mapColorMetric || !currentTrack) return;\n const plot = plots.find((p) => p.metricKey === mapColorMetric);\n if (!plot) return;\n const colors = _computePointColors(mapColorMetric, plot.yData);\n onMapColorChangeCb({ pts: currentTrack.points, colors });\n }\n\n function _computePointColors(key: string, yData: (number | null)[]): string[] {\n const vals = yData.filter((v): v is number => v != null && isFinite(v));\n if (!vals.length) return yData.map(() => '#888896');\n const min = Math.min(...vals),\n max = Math.max(...vals);\n\n const sport = currentTrack?.sport;\n const sportDef = sport ? SPORTS[sport.toLowerCase()] : null;\n const defaultMode = sportDef?.defaultPreset || 'speed';\n const urlState = UrlState.get();\n const speedMode = (urlState as any).speed_mode || defaultMode;\n const paceUnit = sportDef?.paceUnit || 'km';\n\n return yData.map((v) => {\n if (v == null || !isFinite(v)) return '#888896';\n if (key === 'gradient') return gradientColor(v);\n if (key === 'speed') {\n if (speedMode === 'pace') {\n const speed = (paceUnit === 'km' ? 1000 : 100) / v;\n return speedColor(speed);\n }\n return speedColor(v);\n }\n const t = max === min ? 0.5 : Math.max(0, Math.min(1, (v - min) / (max - min)));\n if (t <= 0.25) return lerpHex('#4575b4', '#91bfdb', t / 0.25);\n if (t <= 0.5) return lerpHex('#91bfdb', '#fee090', (t - 0.25) / 0.25);\n if (t <= 0.75) return lerpHex('#fee090', '#fc8d59', (t - 0.5) / 0.25);\n return lerpHex('#fc8d59', '#d73027', (t - 0.75) / 0.25);\n });\n }\n\n function resetZoom() {\n cancelSelection();\n }\n\n function cancelSelection() {\n selAnchorVal = null;\n selEndVal = null;\n pinnedPtIdx = null;\n if (onPinChangeCb) onPinChangeCb(null);\n\n // Reset individual histogram pins\n plots.forEach((p) => {\n p.pinnedHistY = null;\n });\n\n updateSelOverlay();\n redrawHistograms();\n\n // Perform the actual zoom reset\n scaleSyncing = true;\n plots.forEach(({ uplot: u, xData }) => {\n u.batch(() => u.setScale('x', { min: xData[0]!, max: xData[xData.length - 1]! }));\n u._strasse?.updateHeaderStats(xData[0]!, xData[xData.length - 1]!);\n });\n scaleSyncing = false;\n\n if (onRangeChangeCb) onRangeChangeCb(null, null, xAxis);\n }\n\n function setSelectionStats(stats: TrackStats) {\n if (!selStatsEl) return;\n const fmt = (id: string, val: string) => {\n const el = document.querySelector(`#${id} .stat-value`);\n if (el) el.textContent = val;\n };\n fmt(\n 'sel-distance',\n stats.totalDist != null ? `${(stats.totalDist / 1000).toFixed(1)} km` : '\u2014',\n );\n fmt('sel-duration', stats.duration != null ? fmtSecs(Math.floor(stats.duration / 1000)) : '\u2014');\n fmt('sel-elevation', stats.elevGain != null ? `${Math.round(stats.elevGain)} m` : '\u2014');\n fmt(\n 'sel-avg-speed',\n stats.avgSpeed != null ? `${(stats.avgSpeed * 3.6).toFixed(1)} km/h` : '\u2014',\n );\n fmt('sel-avg-power', stats.avgPower != null ? `${stats.avgPower} W` : '\u2014');\n fmt('sel-avg-hr', stats.avgHR != null ? `${stats.avgHR} bpm` : '\u2014');\n fmt('sel-avg-temp', stats.avgTemp != null ? `${stats.avgTemp.toFixed(1)} \u00B0C` : '\u2014');\n selStatsEl.classList.remove('hidden');\n if (resetSelBtn) resetSelBtn.classList.remove('hidden');\n }\n\n function clearSelectionStats() {\n if (selStatsEl) selStatsEl.classList.add('hidden');\n if (resetSelBtn) resetSelBtn.classList.add('hidden');\n [\n 'sel-distance',\n 'sel-duration',\n 'sel-elevation',\n 'sel-avg-speed',\n 'sel-avg-power',\n 'sel-avg-hr',\n 'sel-avg-temp',\n ].forEach((id) => {\n const el = document.querySelector(`#${id} .stat-value`);\n if (el) el.textContent = '--';\n });\n }\n\n // Called on boot to restore a saved selection without re-firing onRangeChangeCb\n // xMin/xMax are ALWAYS provided in Time (seconds from start)\n function restoreSelection(tMin: number, tMax: number, deferRender = false) {\n if (!currentTrack) return;\n const pts = currentTrack.points;\n const t0 = pts.find((p) => p.time != null)?.time || 0;\n const totalDuration = ((pts[pts.length - 1]?.time || 0) - t0) / 1000;\n\n // If values look like absolute timestamps in ms, convert to seconds from start\n if (tMin > 1e12) tMin = (tMin - t0) / 1000;\n if (tMax > 1e12) tMax = (tMax - t0) / 1000;\n\n // Crop to max limits of the track\n tMin = Math.max(0, Math.min(tMin, totalDuration));\n tMax = Math.max(0, Math.min(tMax, totalDuration));\n\n const convert = (tOffset: number) => {\n if (xAxis === 'time') return tOffset;\n const tTarget = t0 + tOffset * 1000;\n let idx = pts.findIndex((p) => (p.time || 0) >= tTarget);\n if (idx === -1) idx = pts.length - 1;\n return (pts[idx].dist || 0) / 1000;\n };\n\n const xMin = convert(tMin);\n const xMax = convert(tMax);\n\n selAnchorVal = xMin;\n selEndVal = xMax;\n currentXRange = [xMin, xMax];\n if (deferRender) return;\n scaleSyncing = true; // suppress the setScale \u2192 onRangeChangeCb loop\n plots.forEach(({ uplot: u }) => {\n u.batch(() => u.setScale('x', { min: xMin, max: xMax }));\n });\n scaleSyncing = false;\n updateAnchorLines();\n redrawHistograms();\n }\n\n function resize() {\n if (!plots.length || !container) return;\n const w = Math.round(\n Math.max(\n 100,\n container.getBoundingClientRect().width -\n ROW_BODY_PADDING -\n (statsVisible ? HIST_W + HIST_GAP : 0),\n ),\n );\n let anyResized = false;\n plots.forEach(({ uplot: u, histCanvas, histData }) => {\n if (w !== u.width) {\n u.setSize({ width: w, height: u.height });\n anyResized = true;\n if (statsVisible && histCanvas && histData) drawHistogram(histCanvas, histData, u.height);\n }\n });\n if (anyResized) updateSelOverlay();\n }\n\n // \u2500\u2500 Render \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n function render(keepState = false) {\n let savedRange: [number, number] | null = null;\n let savedScroll = 0;\n if (keepState && container) savedScroll = container.scrollTop;\n\n if (keepState && currentXRange) {\n savedRange = [currentXRange[0], currentXRange[1]];\n } else if (keepState && plots.length) {\n const u = plots[0].uplot;\n savedRange = [u.scales.x!.min!, u.scales.x!.max!];\n }\n\n if (!currentTrack) {\n destroyPlots(keepState);\n return;\n }\n const pts = currentTrack.points;\n if (!pts.length) {\n destroyPlots(keepState);\n return;\n }\n\n const sport = currentTrack?.sport;\n const sportDef = sport ? SPORTS[sport.toLowerCase()] : null;\n const defaultMode = sportDef?.defaultPreset || 'speed';\n const urlState = UrlState.get();\n const speedMode = (urlState as any).speed_mode || defaultMode;\n const paceUnit = sportDef?.paceUnit || 'km';\n\n const available = [...activeMetrics].filter((m) =>\n pts.some((p) => p[METRICS[m].field] != null),\n );\n if (!available.length) {\n destroyPlots(keepState);\n if (emptyEl) {\n emptyEl.style.display = 'flex';\n emptyEl.innerHTML = `\n no_sim\n
No data for selected metrics
\n `;\n }\n return;\n }\n if (emptyEl) emptyEl.style.display = 'none';\n\n const containerW = container ? container.getBoundingClientRect().width : 800;\n const w = Math.round(\n Math.max(100, containerW - ROW_BODY_PADDING - (statsVisible ? HIST_W + HIST_GAP : 0)),\n );\n\n const canReuse =\n keepState &&\n renderedTrack === currentTrack &&\n renderedXAxis === xAxis &&\n renderedSpeedMode === speedMode &&\n renderedMapColorMetric === mapColorMetric &&\n plots.length > 0;\n\n let xData: (number | null)[];\n if (canReuse) {\n xData = plots[0].xData;\n } else if (xAxis === 'distance') {\n xData = pts.map((p) => (p.dist || 0) / 1000);\n } else {\n const t0 = pts.find((p) => p.time != null)?.time || 0;\n let lastValidX = 0;\n xData = pts.map((p) => {\n if (p.time != null) {\n lastValidX = (p.time - t0) / 1000;\n return lastValidX;\n }\n return lastValidX; // monotonic fallback\n });\n }\n\n const existingByKey = new Map();\n if (canReuse) {\n if (histTooltipEl) histTooltipEl.style.display = 'none';\n const availSet = new Set(available);\n for (const p of plots) {\n const isSmoothed = smoothedMetrics.has(p.metricKey);\n const isIncZero = metricsIncludingZero.has(p.metricKey);\n if (\n availSet.has(p.metricKey) &&\n p.smoothed === isSmoothed &&\n p.incZero === isIncZero &&\n p.w === w\n ) {\n existingByKey.set(p.metricKey, p);\n } else {\n syncKey!.unsub(p.uplot);\n p.uplot.destroy();\n p.row.remove();\n }\n }\n plots = [];\n } else {\n destroyPlots(keepState);\n }\n\n if (container) {\n const nextSiblingRows: (HTMLElement | null)[] = new Array(available.length).fill(null);\n let nextExisting: HTMLElement | null = null;\n for (let i = available.length - 1; i >= 0; i--) {\n nextSiblingRows[i] = nextExisting;\n const ex = existingByKey.get(available[i]);\n if (ex) nextExisting = ex.row;\n }\n\n available.forEach((key, idx) => {\n const reused = existingByKey.get(key);\n if (reused) {\n plots.push(reused);\n const uplot = reused.uplot;\n const s = (uplot as any)._strasse;\n const prevSync = scaleSyncing;\n scaleSyncing = true;\n uplot.batch(() => {\n if (\n savedRange &&\n (uplot.scales.x!.min !== savedRange[0] || uplot.scales.x!.max !== savedRange[1])\n ) {\n uplot.setScale('x', { min: savedRange[0], max: savedRange[1] });\n } else {\n s?.updateHeaderStats(uplot.scales.x!.min!, uplot.scales.x!.max!, uplot);\n uplot.redraw(false);\n }\n });\n scaleSyncing = prevSync;\n\n let initHoverBinI: number | null = null;\n if (\n pinnedPtIdx != null &&\n reused.xData[pinnedPtIdx] != null &&\n reused.yData[pinnedPtIdx] != null\n ) {\n buildHistBins(reused.histData);\n if (reused.histData.min != null && reused.histData.max != null) {\n const span = reused.histData.max - reused.histData.min || 1;\n initHoverBinI = Math.min(\n reused.histData.BINS - 1,\n Math.floor(\n ((reused.yData[pinnedPtIdx]! - reused.histData.min) / span) *\n reused.histData.BINS,\n ),\n );\n reused.hoveredHistY = reused.yData[pinnedPtIdx];\n }\n }\n drawHistogram(reused.histCanvas, reused.histData, 130, initHoverBinI);\n return;\n }\n\n let def = METRICS[key];\n\n let transform = def.transform;\n let fmt = def.fmt;\n let fmtAxis = def.fmtAxis;\n let unit = def.unit;\n let label = def.label;\n\n if (key === 'speed' && speedMode === 'pace') {\n label = 'Pace';\n unit = `min/${paceUnit}`;\n fmt = (v) => fmtPace(v);\n fmtAxis = (v) => fmtPace(v);\n transform = (v) => {\n if (v == null || v < 0.1) return null;\n return (paceUnit === 'km' ? 1000 : 100) / v;\n };\n\n def = { ...def, label, unit, fmt, fmtAxis, transform };\n }\n\n let yData = def.compute\n ? def.compute(pts, fillNulls)\n : fillNulls(\n pts.map((p) => {\n const v = p[def.field] as number;\n return v != null && transform ? transform(v) : v;\n }),\n );\n\n if (smoothedMetrics.has(key)) yData = gaussianSmooth(yData, def.smoothSigma || 2);\n\n createChart(key, def, xData, yData, w, pts, savedRange, nextSiblingRows[idx]);\n });\n }\n\n renderedTrack = currentTrack;\n renderedXAxis = xAxis;\n renderedSpeedMode = speedMode;\n renderedMapColorMetric = mapColorMetric;\n\n updateStats(currentTrack);\n // Restore anchor/end lines if a selection was active\n if (selAnchorVal != null && selEndVal != null) updateAnchorLines();\n // Re-apply map color if active\n _updateMapColorBtns();\n if (mapColorMetric) _fireMapColorCb();\n\n if (keepState && container && savedScroll > 0) container.scrollTop = savedScroll;\n }\n\n // \u2500\u2500 Create one chart row \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n function createChart(\n metricKey: string,\n def: MetricDefinition,\n xData: (number | null)[],\n yData: (number | null)[],\n w: number,\n pts: TrackPoint[],\n initialRange: [number, number] | null = null,\n insertBeforeEl: HTMLElement | null = null,\n ) {\n const sport = currentTrack?.sport;\n const sportDef = sport ? SPORTS[sport.toLowerCase()] : null;\n const defaultMode = sportDef?.defaultPreset || 'speed';\n const urlState = UrlState.get();\n const speedMode = (urlState as any).speed_mode || defaultMode;\n\n // Gradient data: smooth over a 20 m distance window (\u00B110 m each side)\n const gradData = metricKey === 'elevation' ? smoothGradient(pts, 20) : null;\n const row = document.createElement('div');\n row.className = 'chart-row';\n row.dataset.metric = metricKey;\n row.style.setProperty('--chart-color', def.color);\n\n // Header\n const header = document.createElement('div');\n header.className = `chart-row-header${statsVisible ? ' with-histogram' : ''}`;\n\n const labelEl = document.createElement('div');\n labelEl.className = 'chart-row-label-group';\n labelEl.innerHTML = `\n ${def.icon}\n ${def.label}\n `;\n\n // Smoothing toggle\n const smoothBtn = document.createElement('button');\n smoothBtn.className = `icon-btn mini smooth-btn${smoothedMetrics.has(metricKey) ? ' active' : ''}`;\n smoothBtn.title = 'Toggle data smoothing';\n smoothBtn.innerHTML = `${smoothedMetrics.has(metricKey) ? 'blur_on' : 'blur_off'}`;\n smoothBtn.addEventListener('click', (e) => {\n e.stopPropagation();\n if (smoothedMetrics.has(metricKey)) smoothedMetrics.delete(metricKey);\n else smoothedMetrics.add(metricKey);\n\n render(true); // Re-render all charts to keep sync if needed (though only this one changes data)\n });\n\n const mapColorBtn = document.createElement('button');\n\n mapColorBtn.className = `map-color-btn icon-btn${mapColorMetric === metricKey ? ' active' : ''}`;\n mapColorBtn.dataset.metric = metricKey;\n mapColorBtn.title = 'Color map track by this metric';\n mapColorBtn.innerHTML = 'colorize';\n mapColorBtn.addEventListener('click', () => toggleMapColor(metricKey));\n\n // Zero-filter toggle (Cadence/Power/Speed)\n let zeroBtn: HTMLButtonElement | null = null;\n if (metricKey === 'cadence' || metricKey === 'power' || metricKey === 'speed') {\n zeroBtn = document.createElement('button');\n const incZero = metricsIncludingZero.has(metricKey);\n zeroBtn.className = `icon-btn mini${incZero ? ' active' : ''}`;\n zeroBtn.title = incZero ? 'Currently INCLUDING 0 values' : 'Currently EXCLUDING 0 values';\n zeroBtn.innerHTML = `${incZero ? 'exposure_zero' : 'mobile_off'}`;\n zeroBtn.addEventListener('click', (e) => {\n e.stopPropagation();\n if (metricsIncludingZero.has(metricKey)) metricsIncludingZero.delete(metricKey);\n else metricsIncludingZero.add(metricKey);\n render(true);\n });\n }\n\n const statsTotalEl = document.createElement('div');\n statsTotalEl.className = 'chart-stats-total';\n\n const statsSelEl = document.createElement('div');\n statsSelEl.className = 'chart-stats-selection';\n\n const statsContainer = document.createElement('div');\n statsContainer.className = 'chart-row-stats-container';\n statsContainer.append(statsTotalEl, statsSelEl);\n\n const actionsEl = document.createElement('div');\n actionsEl.className = 'chart-row-actions';\n actionsEl.append(smoothBtn, mapColorBtn);\n\n const rightActionsEl = document.createElement('div');\n rightActionsEl.className = 'chart-row-right-actions';\n\n if (metricKey === 'speed') {\n const paceToggle = document.createElement('div');\n paceToggle.className = 'seg-ctrl';\n paceToggle.innerHTML = `\n \n \n `;\n paceToggle.addEventListener('click', (e) => {\n const btn = (e.target as HTMLElement).closest('.seg-btn') as HTMLElement;\n if (!btn) return;\n const mode = btn.dataset.mode;\n if (mode === 'speed' || mode === 'pace') {\n const isDefault = mode === defaultMode;\n UrlState.patch({ speed_mode: isDefault ? null : mode });\n render(true);\n }\n });\n rightActionsEl.append(paceToggle);\n }\n\n if (zeroBtn) rightActionsEl.append(zeroBtn);\n\n header.append(labelEl, actionsEl, statsContainer, rightActionsEl);\n\n const rowBody = document.createElement('div');\n rowBody.className = 'chart-row-body';\n\n const plotEl = document.createElement('div');\n plotEl.style.position = 'relative';\n rowBody.appendChild(plotEl);\n\n const fixedMinLabel = document.createElement('div');\n fixedMinLabel.className = 'fixed-x-label fixed-x-min';\n const fixedMaxLabel = document.createElement('div');\n fixedMaxLabel.className = 'fixed-x-label fixed-x-max';\n plotEl.append(fixedMinLabel, fixedMaxLabel);\n\n const histCol = document.createElement('div');\n histCol.className = `hist-col${statsVisible ? ' visible' : ''}`;\n const histCanvas = document.createElement('canvas');\n histCanvas.className = 'hist-canvas';\n histCol.appendChild(histCanvas);\n rowBody.appendChild(histCol);\n\n row.append(header, rowBody);\n\n const isDistAxis = xAxis === 'distance';\n\n function formatX(v: number, range: number): string {\n if (v == null) return '';\n if (isDistAxis) {\n const dec = range < 1 ? 3 : range < 5 ? 2 : range < 20 ? 1 : 0;\n return `${v.toFixed(dec)} km`;\n } else {\n const absV = Math.abs(v);\n const h = Math.floor(absV / 3600);\n const m = Math.floor((absV % 3600) / 60);\n const s = Math.round(absV % 60);\n const hh = h > 0 ? `${h}:` : '';\n const mm = h > 0 ? String(m).padStart(2, '0') : String(m);\n const ss = String(s).padStart(2, '0');\n return `${v < 0 ? '-' : ''}${hh}${mm}:${ss}`;\n }\n }\n\n const incZeroDefault =\n metricsIncludingZero.has(metricKey) ||\n (metricKey !== 'power' && metricKey !== 'cadence' && metricKey !== 'speed');\n\n // Compute global Y range and full-track stats in a single pass\n let gMin = Infinity,\n gMax = -Infinity,\n allSum = 0,\n allCount = 0,\n filtMin = Infinity,\n filtMax = -Infinity,\n filtSum = 0,\n filtCount = 0;\n for (let i = 0; i < yData.length; i++) {\n const v = yData[i];\n if (v == null || !isFinite(v)) continue;\n if (v < gMin) gMin = v;\n if (v > gMax) gMax = v;\n allSum += v;\n allCount++;\n if (v !== 0 || incZeroDefault) {\n if (v < filtMin) filtMin = v;\n if (v > filtMax) filtMax = v;\n filtSum += v;\n filtCount++;\n }\n }\n if (allCount === 0) {\n gMin = 0;\n gMax = 100;\n }\n const fullStatsAll = allCount ? { min: gMin, max: gMax, avg: allSum / allCount } : null;\n const fullStatsFiltered = filtCount\n ? { min: filtMin, max: filtMax, avg: filtSum / filtCount }\n : null;\n const gPad = Math.abs(gMax - gMin) * 0.1 || 1;\n const fixedYRange = [gMin - gPad, gMax + gPad];\n\n let statsTotalInitialized = false;\n\n // Per-range stats\n function updateHeaderStats(visibleMin: number, visibleMax: number, u?: uPlot) {\n const incZero =\n metricsIncludingZero.has(metricKey) ||\n (metricKey !== 'power' && metricKey !== 'cadence' && metricKey !== 'speed');\n\n // Update fixed X labels\n const range = visibleMax - visibleMin;\n fixedMinLabel.textContent = formatX(visibleMin, range);\n fixedMaxLabel.textContent = formatX(visibleMax, range);\n\n const getHtml = (xMin: number, xMax: number, isFull = false) => {\n const s =\n isFull && incZero === incZeroDefault\n ? fullStatsFiltered\n : rangeStats(xData, yData, xMin, xMax, incZero);\n if (!s) return '';\n\n let h = `\n align_flex_end${def.fmt(s.min, true)} ${def.unit}\n align_center${def.fmt(s.avg, true)} ${def.unit}\n align_flex_start${def.fmt(s.max, true)} ${def.unit}\n `;\n\n if (metricKey === 'elevation') {\n const es = elevationRangeStats(pts, xMin, xMax);\n h += `\n trending_up+${Math.round(es.gain)} m\n trending_down-${Math.round(es.loss)} m\n `;\n }\n return h;\n };\n\n if (!statsTotalInitialized) {\n statsTotalInitialized = true;\n statsTotalEl.innerHTML = `all_inclusive${getHtml(\n xData[0]!,\n xData[xData.length - 1]!,\n true,\n )}`;\n statsTotalEl.querySelector('.all-chip')?.addEventListener('click', (e) => {\n e.stopPropagation();\n cancelSelection();\n });\n }\n\n if (selAnchorVal !== null && selEndVal !== null) {\n statsSelEl.innerHTML = getHtml(selAnchorVal, selEndVal, false);\n statsSelEl.style.display = 'flex';\n statsSelEl.style.setProperty('--sel-color', def.color);\n } else {\n statsSelEl.style.display = 'none';\n }\n }\n let initializingScale = false;\n const uOpts: uPlot.Options = {\n width: w,\n height: 130,\n padding: [4, 0, 0, 0],\n cursor: {\n sync: {\n key: syncKey!.key,\n },\n drag: { x: true, y: false, uni: 16 },\n focus: { prox: 16 },\n points: {\n size: 7,\n stroke: '#0e0e10',\n width: 2,\n fill: (metricKey === 'elevation'\n ? (u: uPlot, _si: number) => gradientColor(gradData ? gradData[u.cursor.idx!]! : null)\n : def.color) as any,\n },\n x: false,\n y: false,\n },\n select: {\n show: true,\n left: 0,\n top: 0,\n width: 0,\n height: 0,\n },\n legend: { show: false },\n scales: {\n x: {\n time: false,\n range: (_u: uPlot, mn: number, mx: number) => [mn, mx],\n min: initialRange ? initialRange[0] : undefined,\n max: initialRange ? initialRange[1] : undefined,\n },\n y: {\n range: () => fixedYRange as [number, number],\n dir: metricKey === 'speed' && speedMode === 'pace' ? -1 : 1,\n },\n },\n axes: [\n {\n side: 2, // bottom\n stroke: '#555564',\n grid: { stroke: '#2e2e34', width: 1 },\n ticks: {\n show: true,\n stroke: '#2e2e34',\n },\n size: 30,\n space: 80,\n font: '10px system-ui, sans-serif',\n splits: (u: uPlot, axisIdx: number, scaleMin: number, scaleMax: number) => {\n const range = scaleMax - scaleMin;\n const width = u.bbox.width > 0 ? u.bbox.width : u.width;\n const targetLabels = width / 80;\n const idealIncr = range / targetLabels;\n\n let incrs;\n if (!isDistAxis) {\n // Time in seconds\n incrs = [\n 1, 2, 5, 10, 15, 30, 60, 120, 300, 600, 900, 1800, 3600, 7200, 14400, 28800, 43200,\n 86400, 604800, 2592000, 31536000,\n ];\n } else {\n incrs = [\n 0.01, 0.02, 0.05, 0.1, 0.2, 0.25, 0.5, 1, 2, 5, 10, 20, 50, 100, 200, 500, 1000,\n 2000, 5000, 10000, 20000, 50000, 100000, 200000, 500000, 1000000,\n ];\n }\n\n let incr = incrs[incrs.length - 1];\n for (let i = 0; i < incrs.length; i++) {\n if (incrs[i] >= idealIncr) {\n incr = incrs[i];\n break;\n }\n }\n\n const splits = [];\n let val = Math.ceil(scaleMin / incr) * incr;\n const precision = incr < 1 ? 2 : 0;\n\n while (val <= scaleMax) {\n const pos = u.valToPos(val, 'x', true);\n if (pos >= u.bbox.left + 80 && pos <= u.bbox.left + u.bbox.width - 80)\n splits.push(Number(val.toFixed(precision)));\n\n val += incr;\n }\n return splits;\n },\n values: ((_u: uPlot, vals: number[]) => {\n const range = (_u.scales.x?.max ?? 1) - (_u.scales.x?.min ?? 0);\n\n return vals.map((v) => {\n if (v == null) return '';\n const pos = _u.valToPos(v, 'x', true);\n // Strict 80px margin to prevent overlapping with fixed labels or Y axis\n if (pos < _u.bbox.left + 80 || pos > _u.bbox.left + _u.bbox.width - 80) return null;\n return formatX(v, range);\n });\n }) as any,\n },\n {\n side: 3, // left\n stroke: '#555564',\n grid: { stroke: '#2e2e34', width: 1 },\n ticks: { stroke: '#2e2e34' },\n size: 55,\n font: '10px system-ui, sans-serif',\n values: ((_u: uPlot, vals: number[]) =>\n vals.map((v) => (v != null ? def.fmtAxis(v) : ''))) as any,\n },\n ],\n series: [\n {},\n // Main selected track series\n {\n label: def.label,\n stroke: 'rgba(0,0,0,0)', // Stroke handled in draw hooks for layering\n width: 0,\n points: { show: false },\n paths: () => null,\n },\n ],\n hooks: {\n draw: [\n (u: uPlot) => {\n const top = u.bbox.top + u.bbox.height + 5; // 5px below plot area!\n fixedMinLabel.style.top = `${top}px`;\n fixedMinLabel.style.left = `${u.bbox.left}px`;\n\n fixedMaxLabel.style.top = `${top}px`;\n fixedMaxLabel.style.left = `${u.bbox.left + u.bbox.width}px`;\n fixedMaxLabel.style.transform = 'translateX(-100%)';\n },\n // 1. (bottom) Color / metric color of the selected track\n (u: uPlot) => {\n if (metricKey === 'gears') {\n drawTrackPath(\n u,\n xData as number[],\n yData as number[],\n def.color,\n 1.5,\n metricKey,\n null,\n pts,\n );\n } else if (metricKey === 'elevation') {\n drawElevationGradient(u, xData as number[], yData as number[], gradData);\n } else if (metricKey === 'gradient') {\n drawGradientChart(u, xData as number[], yData as number[]);\n } else if (mapColorMetric === metricKey) {\n drawMetricColorFill(u, xData as number[], yData as number[], pts, metricKey);\n } else {\n // Normal colored line\n drawTrackPath(\n u,\n xData as number[],\n yData as number[],\n def.color,\n 1.5,\n metricKey,\n null,\n pts,\n );\n }\n },\n\n // 2. Pinned point (middle)\n (u: uPlot) => {\n const plot = plots.find((p) => p.uplot === u);\n if (!plot) return;\n\n const s = (u as any)._strasse;\n // Priority 1: Global pinned point (all charts)\n if (pinnedPtIdx != null) {\n drawPinnedDot(u, xData, yData, def.color, def, pts, metricKey);\n } else {\n if (s && s.pinYVal) s.pinYVal.style.display = 'none';\n\n // Priority 2: Individual histogram pinned Y-pos (this chart only)\n if (plot.pinnedHistY != null)\n drawYAxisHighlight(u, xData, yData, plot.pinnedHistY, def.color, def);\n }\n },\n\n // 3. Pinned & Hover elements (labels, vertical lines)\n (u: uPlot) => {\n const hIdx = u.cursor.idx;\n const hasPinned = pinnedPtIdx != null;\n\n let hasHover = hIdx != null && u.cursor.left! >= 0;\n if (hasHover && pinnedPtIdx == null) {\n const cy = u.valToPos(yData[hIdx!]!, 'y', true);\n const dist = Math.abs(u.cursor.top! - cy);\n if (dist > 100) hasHover = false;\n }\n\n // B. Draw Hover vertical line\n if (hasHover && hIdx !== pinnedPtIdx)\n drawVerticalLineOnly(u, pts, hIdx!, hexToRgba(def.color, 0.4), 1.0);\n\n // C. Bold selection x-axis line & span pill\n if (selAnchorVal != null && selEndVal != null) {\n const ctx = u.ctx;\n const dpr = window.devicePixelRatio || 1;\n const bb = u.bbox;\n const ax = u.valToPos(selAnchorVal, 'x', true);\n const ex = u.valToPos(selEndVal, 'x', true);\n\n ctx.save();\n ctx.beginPath();\n ctx.lineWidth = 3 * dpr;\n ctx.strokeStyle = def.color;\n ctx.moveTo(ax, bb.top + bb.height);\n ctx.lineTo(ex, bb.top + bb.height);\n ctx.stroke();\n\n const span = Math.abs(selEndVal - selAnchorVal);\n const label = xAxis === 'distance' ? `${span.toFixed(2)} km` : fmtSecs(span);\n const fontSize = 9 * dpr;\n const fontStr = `bold ${fontSize}px system-ui, sans-serif`;\n if (ctx.font !== fontStr) ctx.font = fontStr;\n const tw = ctx.measureText(label).width;\n\n const visibleL = Math.max(bb.left, ax);\n const visibleR = Math.min(bb.left + bb.width, ex);\n const visibleW = visibleR - visibleL;\n\n if (visibleW > tw + 14 * dpr) {\n const padH = 5 * dpr;\n const padV = 2 * dpr;\n const bw = tw + padH * 2;\n const bh = fontSize + padV * 2;\n const bx = (visibleL + visibleR) / 2 - bw / 2;\n const by = bb.top + bb.height - bh / 2;\n\n ctx.beginPath();\n if ((ctx as any).roundRect) (ctx as any).roundRect(bx, by, bw, bh, 3 * dpr);\n else ctx.rect(bx, by, bw, bh);\n ctx.fillStyle = def.color;\n ctx.fill();\n\n ctx.fillStyle = '#000';\n ctx.textAlign = 'center';\n ctx.textBaseline = 'middle';\n ctx.fillText(label, bx + bw / 2, by + bh / 2);\n }\n ctx.restore();\n }\n\n // D. Draw label pills (on top)\n // Pinned labels first (semi-transparent)\n if (hasPinned) drawXAxisLabels(u, pts, pinnedPtIdx!, def.color, true, 0.7);\n\n // Hover labels last (fully opaque, topmost)\n if (hasHover && hIdx !== pinnedPtIdx)\n drawXAxisLabels(u, pts, hIdx!, def.color, true, 1.0);\n },\n ],\n setCursor: [\n (u: uPlot) => {\n if (scaleSyncing) return;\n const idx = u.cursor.idx;\n\n // Trigger redraw on cursor move to ensure custom canvas markers (hover lines/labels) update live\n // Note: uPlot handles cross-chart sync when syncKey is used.\n u.redraw(false);\n\n // Track current x-value for keyboard navigation\n if (u.cursor.left! >= 0) lastMouseXVal = u.posToVal(u.cursor.left!, 'x');\n else lastMouseXVal = null;\n\n // \u2500\u2500 Floating y-value next to cursor dot \u2500\u2500\n const s = (u as any)._strasse;\n let showHover = idx != null && yData[idx] != null;\n\n // Distance threshold: if no pinned point, check proximity to data line\n if (showHover && pinnedPtIdx == null) {\n const cy = u.valToPos(yData[idx!]!, 'y', true);\n const dist = Math.abs(u.cursor.top! - cy);\n if (dist > 100) showHover = false;\n }\n\n if (s) {\n if (showHover)\n updateTooltip(\n u,\n s.curYVal,\n idx!,\n xData as number[],\n yData as number[],\n pts,\n def,\n metricKey,\n );\n else s.curYVal.style.display = 'none';\n }\n\n // Map marker\n if (!cursorSyncing) {\n if (idx != null && pts[idx]) {\n if (onCursorMoveCb && (pinnedPtIdx != null || showHover)) {\n onCursorMoveCb(pts[idx]);\n } else if (onCursorMoveCb && !showHover) {\n // If we're too far and nothing is pinned, hide map cursor\n onCursorMoveCb(null);\n }\n } else if (idx === null && onCursorMoveCb) {\n onCursorMoveCb(null);\n }\n }\n\n // Highlight matching histogram bucket\n if (idx != null && yData[idx] != null) {\n const { min, max } = histData;\n if (min != null && max != null) {\n const span = max - min || 1;\n const binI = Math.min(\n histData.BINS - 1,\n Math.floor(((yData[idx]! - min) / span) * histData.BINS),\n );\n if (plot.hoveredHistY !== yData[idx]) {\n plot.hoveredHistY = yData[idx];\n drawHistogram(histCanvas, histData, 130, binI);\n }\n }\n } else if (plot.hoveredHistY !== null) {\n plot.hoveredHistY = null;\n drawHistogram(histCanvas, histData, 130);\n }\n },\n ],\n\n setScale: [\n (u: uPlot, key: string) => {\n if (key !== 'x' || initializingScale) return;\n const { min, max } = u.scales.x!;\n\n // Sync currentXRange so keyboard navigation baseline is always accurate\n currentXRange = [min!, max!];\n\n // Update header stats for VISIBLE range\n updateHeaderStats(min as number, max as number, u);\n\n if (scaleSyncing) return;\n\n // Clear keyboard target when user interacts via mouse\n if (!updatingRange) targetXRange = null;\n\n // Sync sibling plots\n scaleSyncing = true;\n plots.forEach(({ uplot: other }) => {\n if (other !== u) other.batch(() => other.setScale('x', { min: min!, max: max! }));\n });\n scaleSyncing = false;\n\n updateSelOverlay();\n redrawHistograms();\n\n if (onRangeChangeCb && selAnchorVal !== null)\n onRangeChangeCb(selAnchorVal, selEndVal, xAxis);\n },\n ],\n\n setSelect: [\n (u: uPlot) => {\n if (updatingRange) return;\n targetXRange = null;\n const { left, width } = u.select;\n if (width > 0) {\n const min = u.posToVal(left, 'x');\n const max = u.posToVal(left + width, 'x');\n selAnchorVal = min;\n selEndVal = max;\n\n // Sync stats to new selection\n updateHeaderStats(u.scales.x!.min!, u.scales.x!.max!, u);\n updateSelOverlay();\n redrawHistograms();\n\n if (onRangeChangeCb) onRangeChangeCb(selAnchorVal, selEndVal, xAxis);\n\n // Clear uPlot's internal selection immediately so it doesn't block WASD/interaction\n u.setSelect({ left: 0, top: 0, width: 0, height: 0 }, false);\n }\n },\n ],\n },\n };\n\n // \u2500\u2500 Overlay (pointer-events off except handles) \u2500\u2500\n const overlay = document.createElement('div');\n overlay.style.cssText =\n 'position:absolute;inset:0;pointer-events:none;overflow:visible;z-index:5';\n\n // Selection fill\n const selFill = document.createElement('div');\n selFill.className = 'sel-fill';\n selFill.style.display = 'none';\n\n // Anchor line + label + drag handle\n const anchorLine = document.createElement('div');\n anchorLine.className = 'sel-line sel-line-anchor';\n anchorLine.style.display = 'none';\n\n const anchorLabel = document.createElement('div');\n anchorLabel.className = 'sel-line-label sel-line-label-anchor';\n anchorLabel.style.display = 'none';\n\n const anchorHandle = document.createElement('div');\n anchorHandle.className = 'sel-handle sel-handle-anchor';\n anchorHandle.style.display = 'none';\n\n const anchorMarker = document.createElement('div');\n anchorMarker.className = 'sel-graph-marker';\n anchorMarker.innerHTML =\n 'play_circle';\n anchorMarker.style.display = 'none';\n\n // End line + label + drag handle\n const endLine = document.createElement('div');\n endLine.className = 'sel-line sel-line-end';\n endLine.style.display = 'none';\n\n const endLabel = document.createElement('div');\n endLabel.className = 'sel-line-label sel-line-label-end';\n endLabel.style.display = 'none';\n\n const endHandle = document.createElement('div');\n endHandle.className = 'sel-handle sel-handle-end';\n endHandle.style.display = 'none';\n\n const endMarker = document.createElement('div');\n endMarker.className = 'sel-graph-marker';\n endMarker.innerHTML =\n 'stop_circle';\n endMarker.style.display = 'none';\n\n // Floating cursor y-value\n const curYVal = document.createElement('div');\n curYVal.className = 'cur-y-val';\n curYVal.style.cssText = `color:${def.color};display:none`;\n\n // Floating pinned y-value\n const pinYVal = document.createElement('div');\n pinYVal.className = 'cur-y-val pin';\n pinYVal.style.cssText = `color:${def.color};display:none`;\n\n overlay.append(\n selFill,\n anchorLine,\n anchorLabel,\n anchorHandle,\n anchorMarker,\n endLine,\n endLabel,\n endHandle,\n endMarker,\n curYVal,\n pinYVal,\n );\n\n const histData: HistData = {\n yData,\n xData: xData as number[],\n def,\n BINS: 24,\n min: allCount ? gMin : undefined,\n max: allCount ? gMax : undefined,\n fullStats: fullStatsFiltered,\n fullStatsAll,\n };\n const plot: PlotData = {\n uplot: null as any,\n row,\n xData: xData as number[],\n yData,\n def,\n metricKey,\n statsTotalEl,\n statsSelEl,\n histCol,\n histCanvas,\n histData,\n hoveredHistY: null,\n pinnedHistY: null,\n smoothed: smoothedMetrics.has(metricKey),\n incZero: metricsIncludingZero.has(metricKey),\n w,\n };\n histData.plot = plot; // Back-reference for tooltip to trigger redraws\n\n const prevSync = scaleSyncing;\n scaleSyncing = true;\n const uplot = new uPlot(uOpts, [xData, yData] as any, (self, init) => {\n plot.uplot = self as WegPlot;\n plots.push(plot);\n plotEl.appendChild(self.root);\n self.over.appendChild(overlay);\n (self as any)._strasse = {\n selFill,\n anchorLine,\n anchorLabel,\n anchorHandle,\n anchorMarker,\n endLine,\n endLabel,\n endHandle,\n endMarker,\n curYVal,\n pinYVal,\n updateHeaderStats,\n };\n self.batch(() => {\n init();\n });\n }) as WegPlot;\n scaleSyncing = prevSync;\n\n // \u2500\u2500 Handle drag \u2500\u2500\n attachHandleDrag(anchorHandle, uplot, xData, true);\n attachHandleDrag(endHandle, uplot, xData, false);\n\n // \u2500\u2500 Mouse listeners \u2500\u2500\n let mousedownX: number | null = null;\n uplot.over.addEventListener('mousedown', (e) => {\n if (e.button !== 0) return;\n isDragging = false;\n const rect = uplot.over.getBoundingClientRect();\n const xPx = e.clientX - rect.left;\n mousedownX = xPx;\n });\n uplot.over.addEventListener('mousemove', (e) => {\n if (mousedownX !== null) {\n const rect = uplot.over.getBoundingClientRect();\n if (Math.abs(e.clientX - rect.left - mousedownX) > 5) isDragging = true;\n }\n });\n uplot.over.addEventListener('mouseup', () => {\n mousedownX = null;\n });\n\n // Click to pin point (sticky selection)\n uplot.over.addEventListener('click', () => {\n if (isDragging) return;\n const idx = uplot.cursor.idx;\n if (idx != null && pts[idx]) {\n pinnedPtIdx = idx;\n plots.forEach(({ uplot: u }) => u.redraw(false));\n if (onClickCb) onClickCb(pts[idx], idx);\n if (onPinChangeCb) onPinChangeCb(idx);\n }\n });\n\n row.addEventListener('dblclick', cancelSelection);\n\n let initHoverBinI: number | null = null;\n if (pinnedPtIdx != null && xData[pinnedPtIdx] != null && yData[pinnedPtIdx] != null) {\n buildHistBins(histData);\n if (histData.min != null && histData.max != null) {\n const span = histData.max - histData.min || 1;\n initHoverBinI = Math.min(\n histData.BINS - 1,\n Math.floor(((yData[pinnedPtIdx]! - histData.min) / span) * histData.BINS),\n );\n plot.hoveredHistY = yData[pinnedPtIdx];\n }\n }\n\n drawHistogram(histCanvas, histData, 130, initHoverBinI);\n attachHistTooltip(histCanvas, histData);\n if (container) {\n if (insertBeforeEl) container.insertBefore(row, insertBeforeEl);\n else container.appendChild(row);\n }\n }\n\n // \u2500\u2500 Selection overlay (fill + lines + handles) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n function updateSelOverlay() {\n updateAnchorLines();\n // Redraw canvas markers (bold selection line + span pill)\n plots.forEach(({ uplot: u }) => u.batch(() => u.redraw(false)));\n }\n\n function updateAnchorLines() {\n plots.forEach(({ uplot: u, def }) => {\n const s = (u as any)._strasse;\n if (!s) return;\n const {\n selFill,\n anchorLine,\n anchorLabel,\n anchorHandle,\n anchorMarker,\n endLine,\n endLabel,\n endHandle,\n endMarker,\n } = s;\n if (selAnchorVal == null || selEndVal == null) {\n [\n selFill,\n anchorLine,\n anchorLabel,\n anchorHandle,\n anchorMarker,\n endLine,\n endLabel,\n endHandle,\n endMarker,\n ].forEach((el: HTMLElement) => {\n el.style.display = 'none';\n });\n return;\n }\n const aX = u.valToPos(selAnchorVal, 'x');\n const eX = u.valToPos(selEndVal, 'x');\n const dpr = window.devicePixelRatio || 1;\n const bb = u.bbox;\n const overW = bb.width / dpr;\n const overH = bb.height / dpr;\n const plotBottom = (bb.top + bb.height) / dpr;\n\n const isAnchorVisible = aX >= 0 && aX <= overW;\n const isEndVisible = eX >= 0 && eX <= overW;\n\n // Fill: clamp left/right to visible area\n const fillL = Math.max(0, aX);\n const fillR = Math.min(overW, eX);\n if (fillR > fillL) {\n selFill.style.left = `${fillL}px`;\n selFill.style.width = `${fillR - fillL}px`;\n selFill.style.top = '0';\n selFill.style.height = `${overH}px`;\n selFill.style.backgroundColor = hexToRgba(def.color, 0.07);\n selFill.style.display = '';\n } else {\n selFill.style.display = 'none';\n }\n\n // Anchor line\n if (isAnchorVisible) {\n anchorLine.style.left = `${aX}px`;\n anchorLine.style.height = `${overH}px`;\n anchorLine.style.borderLeftColor = def.color;\n anchorLine.style.display = '';\n // Anchor label\n const aText = fmtXVal(selAnchorVal);\n anchorLabel.textContent = aText;\n const aLabelW = aText.length * 7 + 10;\n anchorLabel.style.left = aX + aLabelW + 4 < overW ? `${aX + 3}px` : `${aX - aLabelW - 3}px`;\n anchorLabel.style.display = '';\n // Anchor handle\n anchorHandle.style.left = `${aX}px`;\n anchorHandle.style.backgroundColor = def.color;\n anchorHandle.style.display = '';\n // Anchor marker: centered on the bold line\n anchorMarker.style.left = `${aX}px`;\n anchorMarker.style.top = `${plotBottom}px`;\n const aIcon = anchorMarker.querySelector('.material-symbols-rounded') as HTMLElement;\n if (aIcon) aIcon.style.color = def.color;\n anchorMarker.style.display = '';\n } else {\n [anchorLine, anchorLabel, anchorHandle, anchorMarker].forEach(\n (el: HTMLElement) => (el.style.display = 'none'),\n );\n }\n\n // End line\n if (isEndVisible) {\n endLine.style.left = `${eX}px`;\n endLine.style.height = `${overH}px`;\n endLine.style.borderLeftColor = def.color;\n endLine.style.display = '';\n // End label\n const eText = fmtXVal(selEndVal);\n endLabel.textContent = eText;\n const eLabelW = eText.length * 7 + 10;\n endLabel.style.left = eX - eLabelW - 3 > 0 ? `${eX - eLabelW - 3}px` : `${eX + 3}px`;\n endLabel.style.display = '';\n // End handle\n endHandle.style.left = `${eX}px`;\n endHandle.style.backgroundColor = def.color;\n endHandle.style.display = '';\n // End marker: centered on the bold line\n endMarker.style.left = `${eX}px`;\n endMarker.style.top = `${plotBottom}px`;\n const eIcon = endMarker.querySelector('.material-symbols-rounded') as HTMLElement;\n if (eIcon) eIcon.style.color = def.color;\n endMarker.style.display = '';\n } else {\n [endLine, endLabel, endHandle, endMarker].forEach(\n (el: HTMLElement) => (el.style.display = 'none'),\n );\n }\n });\n }\n\n function fmtXVal(v: number) {\n return xAxis === 'distance' ? `${v.toFixed(2)} km` : fmtSecs(v);\n }\n\n // \u2500\u2500 Handle drag logic \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n function attachHandleDrag(\n handleEl: HTMLElement,\n uplot: uPlot,\n xData: (number | null)[],\n isAnchor: boolean,\n ) {\n // pointer-events enabled on the handle itself\n handleEl.style.pointerEvents = 'auto';\n\n handleEl.addEventListener('mousedown', (e) => {\n e.stopPropagation();\n e.preventDefault();\n const startX = e.clientX;\n const startVal = isAnchor ? selAnchorVal : selEndVal;\n const overW = uplot.over.offsetWidth;\n // px-per-data-unit at current zoom\n const xMin = uplot.scales.x!.min!,\n xMax = uplot.scales.x!.max!;\n const pxPerUnit = overW / (xMax - xMin);\n const xFull = [xData[0]!, xData[xData.length - 1]!];\n\n document.body.style.cursor = 'ew-resize';\n isDragging = true;\n\n function onMove(me: MouseEvent) {\n const dx = me.clientX - startX;\n let newVal = (startVal || 0) + dx / pxPerUnit;\n newVal = Math.max(xFull[0], Math.min(xFull[1], newVal));\n\n if (isAnchor) selAnchorVal = Math.min(newVal, (selEndVal || xFull[1]) - 0.001);\n else selEndVal = Math.max(newVal, (selAnchorVal || xFull[0]) + 0.001);\n\n // When dragging handles, we typically want to stay zoomed to the selection?\n // Actually, let's keep the zoom as is, and just update the selection overlay and stats.\n plots.forEach((p) =>\n (p.uplot as any)._strasse?.updateHeaderStats(\n p.uplot.scales.x!.min!,\n p.uplot.scales.x!.max!,\n ),\n );\n updateSelOverlay();\n redrawHistograms();\n if (onRangeChangeCb) onRangeChangeCb(selAnchorVal, selEndVal, xAxis);\n }\n\n function onUp() {\n document.body.style.cursor = '';\n isDragging = false;\n document.removeEventListener('mousemove', onMove);\n document.removeEventListener('mouseup', onUp);\n }\n\n document.addEventListener('mousemove', onMove);\n document.addEventListener('mouseup', onUp);\n });\n }\n\n // \u2500\u2500 Histogram logic \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n function buildHistBins(histData: HistData) {\n const { yData, xData, BINS } = histData;\n\n // Cache to avoid re-binning every frame during hover\n if (histData._lastYData === yData && histData.bins) return;\n histData._lastYData = yData;\n\n let min = histData.min;\n let max = histData.max;\n if (min == null || max == null) {\n let lo = Infinity,\n hi = -Infinity,\n cnt = 0;\n for (let i = 0; i < yData.length; i++) {\n const v = yData[i];\n if (v == null || !isFinite(v)) continue;\n if (v < lo) lo = v;\n if (v > hi) hi = v;\n cnt++;\n }\n if (!cnt) return;\n min = lo;\n max = hi;\n histData.min = min;\n histData.max = max;\n }\n\n const bins = new Array(BINS).fill(0);\n const binAccum = new Array(BINS).fill(0); // accumulated x-weight per bin (time in s or dist in km)\n\n const span = max - min || 1;\n const metricKey =\n histData.plot?.metricKey ?? Object.keys(METRICS).find((k) => METRICS[k] === histData.def);\n const incZero =\n !metricKey ||\n metricsIncludingZero.has(metricKey) ||\n (metricKey !== 'power' && metricKey !== 'cadence' && metricKey !== 'speed');\n\n for (let i = 0; i < yData.length; i++) {\n const v = yData[i];\n if (v == null || !isFinite(v)) continue;\n if (v === 0 && !incZero) continue;\n\n const bi = Math.min(BINS - 1, Math.floor(((v - min) / span) * BINS));\n bins[bi]++;\n\n // weight by x-delta\n const prev = i > 0 && xData[i - 1] != null ? xData[i - 1]! : xData[i]!;\n const next = i < xData.length - 1 && xData[i + 1] != null ? xData[i + 1]! : xData[i]!;\n binAccum[bi] += (next - prev) / 2;\n }\n histData.bins = bins;\n histData.binAccum = binAccum;\n }\n\n function redrawHistograms() {\n plots.forEach(({ histCanvas, histData, uplot: u }) => {\n drawHistogram(histCanvas, histData, u.height);\n });\n }\n\n function drawHistogram(\n canvas: HTMLCanvasElement,\n histData: HistData,\n chartH: number,\n hoverBinI: number | null = null,\n ) {\n const { def, BINS } = histData;\n buildHistBins(histData);\n const { bins, min, max } = histData;\n if (!bins || min == null || max == null) return;\n\n const hasSel = selAnchorVal != null && selEndVal != null;\n\n // Compute selection bins if a range is active and changed\n if (\n hasSel &&\n (histData._lastSelRange?.[0] !== selAnchorVal || histData._lastSelRange?.[1] !== selEndVal)\n ) {\n histData._lastSelRange = [selAnchorVal!, selEndVal!];\n const selBins = new Array(BINS).fill(0);\n const selBinAccum = new Array(BINS).fill(0);\n const { yData, xData } = histData;\n const span = max - min || 1;\n const metricKey =\n histData.plot?.metricKey ?? Object.keys(METRICS).find((k) => METRICS[k] === histData.def);\n const incZero =\n !metricKey ||\n metricsIncludingZero.has(metricKey) ||\n (metricKey !== 'power' && metricKey !== 'cadence' && metricKey !== 'speed');\n\n for (let i = 0; i < yData.length; i++) {\n const v = yData[i];\n if (v == null || !isFinite(v)) continue;\n if (v === 0 && !incZero) continue;\n\n const x = xData[i];\n if (x == null || x < selAnchorVal! || x > selEndVal!) continue;\n const bi = Math.min(BINS - 1, Math.floor(((v - min) / span) * BINS));\n selBins[bi]++;\n const prev = i > 0 && xData[i - 1] != null ? xData[i - 1]! : xData[i]!;\n const next = i < xData.length - 1 && xData[i + 1] != null ? xData[i + 1]! : xData[i]!;\n selBinAccum[bi] += (next - prev) / 2;\n }\n histData.selBins = selBins;\n histData.selBinAccum = selBinAccum;\n } else if (!hasSel) {\n histData.selBins = null;\n histData.selBinAccum = null;\n histData._lastSelRange = null;\n }\n const selBins = histData.selBins;\n\n const dpr = window.devicePixelRatio || 1;\n const W = HIST_W;\n const H = chartH;\n\n // Performance: only set dimensions if they changed (resets canvas)\n if (canvas.width !== W * dpr || canvas.height !== H * dpr) {\n canvas.width = W * dpr;\n canvas.height = H * dpr;\n canvas.style.width = `${W}px`;\n canvas.style.height = `${H}px`;\n }\n\n const ctx = canvas.getContext('2d')!;\n ctx.resetTransform();\n ctx.scale(dpr, dpr);\n ctx.clearRect(0, 0, W, H);\n\n const peak = Math.max(...bins);\n\n // Measure label width so bars start right after them\n const fmt = (v: number) => def.fmtAxis(v);\n const fontStr = '9px system-ui, sans-serif';\n if (ctx.font !== fontStr) ctx.font = fontStr;\n const labelW = Math.ceil(\n Math.max(\n ctx.measureText(`${fmt(max)} ${def.unit}`).width,\n ctx.measureText(`${fmt(min)} ${def.unit}`).width,\n ),\n );\n\n // Vertical alignment with main graph:\n // uPlot padding: [4, 0, 0, 0]\n // Axis size: 30\n const pad = { t: 4, r: 0, b: 30, l: labelW + 6 };\n histData.padL = pad.l;\n const plotW = W - pad.l - pad.r;\n const plotH = H - pad.t - pad.b;\n\n // bin 0 = lowest values \u2192 drawn at bottom; bin BINS-1 = highest \u2192 drawn at top\n const binY = (i: number) => pad.t + (BINS - 1 - i) * (plotH / BINS);\n const binH = plotH / BINS - 1;\n\n // Full distribution (dimmed when selection active)\n const baseAlpha = hasSel ? 0.15 : 0.6;\n ctx.fillStyle = hexToRgba(def.color, baseAlpha);\n for (let i = 0; i < BINS; i++) {\n if (!bins[i]) continue;\n const bw = (bins[i] / peak) * plotW;\n ctx.fillRect(pad.l, binY(i), bw, binH);\n }\n\n // Selection overlay (bright)\n if (selBins) {\n ctx.fillStyle = hexToRgba(def.color, 0.85);\n for (let i = 0; i < BINS; i++) {\n if (!selBins[i]) continue;\n const bw = (selBins[i] / peak) * plotW;\n ctx.fillRect(pad.l, binY(i), bw, binH);\n }\n ctx.fillStyle = def.color;\n ctx.fillRect(pad.l, pad.t, 2, plotH);\n }\n\n // CDF overlay \u2014 smooth curve from bottom (0%) to top (100%)\n const total = bins.reduce((s, v) => s + v, 0);\n if (total > 0) {\n // Build CDF points: one per bin boundary, bottom\u2192top\n const pts2 = [{ x: pad.l, y: pad.t + plotH }];\n let cumSum = 0;\n for (let i = 0; i < BINS; i++) {\n cumSum += bins[i];\n pts2.push({ x: pad.l + (cumSum / total) * plotW, y: binY(i) });\n }\n\n ctx.beginPath();\n // CDF line is now solid\n ctx.setLineDash([]);\n ctx.strokeStyle = def.color;\n ctx.lineWidth = 0.75 * dpr;\n ctx.lineJoin = 'round';\n ctx.moveTo(pts2[0].x, pts2[0].y);\n for (let i = 1; i < pts2.length - 1; i++) {\n const mx = (pts2[i].x + pts2[i + 1].x) / 2;\n const my = (pts2[i].y + pts2[i + 1].y) / 2;\n ctx.quadraticCurveTo(pts2[i].x, pts2[i].y, mx, my);\n }\n ctx.lineTo(pts2[pts2.length - 1].x, pts2[pts2.length - 1].y);\n ctx.stroke();\n\n // \u2500\u2500 Hover highlight on CDF \u2500\u2500\n if (hoverBinI != null && pts2[hoverBinI + 1]) {\n const cp = pts2[hoverBinI + 1];\n // Vertical line down\n ctx.beginPath();\n ctx.setLineDash([2 * dpr, 2 * dpr]);\n ctx.strokeStyle = hexToRgba(def.color, 0.6);\n ctx.moveTo(cp.x, cp.y);\n ctx.lineTo(cp.x, pad.t + plotH);\n ctx.stroke();\n ctx.setLineDash([]);\n\n // Dot: tiny precision point\n ctx.beginPath();\n ctx.arc(cp.x, cp.y, 1.2 * dpr, 0, Math.PI * 2);\n ctx.fillStyle = '#0e0e10';\n ctx.fill();\n ctx.strokeStyle = def.color;\n ctx.lineWidth = 1 * dpr;\n ctx.stroke();\n }\n }\n\n // Axis labels: max at top, min at bottom\n if (ctx.fillStyle !== '#888896') ctx.fillStyle = '#888896';\n const font9 = '9px system-ui, sans-serif';\n if (ctx.font !== font9) ctx.font = font9;\n ctx.textAlign = 'right';\n\n // Draw Max\n ctx.textBaseline = 'top';\n ctx.fillText(`${fmt(max)} ${def.unit}`, pad.l - 4, pad.t);\n\n // Draw Min\n ctx.textBaseline = 'bottom';\n ctx.fillText(`${fmt(min)} ${def.unit}`, pad.l - 4, H - pad.b);\n\n // Draw Avg (only if it doesn't overlap min/max)\n const stats =\n !hasSel && histData.fullStatsAll !== undefined\n ? histData.fullStatsAll\n : rangeStats(\n histData.xData,\n histData.yData,\n selAnchorVal || -Infinity,\n selEndVal || Infinity,\n );\n if (stats) {\n const avgY =\n pad.t +\n (BINS - 1 - Math.min(BINS - 1, Math.floor(((stats.avg - min) / (max - min || 1)) * BINS))) *\n (plotH / BINS);\n const minLabelY = H - pad.b;\n const maxLabelY = pad.t;\n const labelHeight = 10; // Approx 10px height for 9px font\n\n if (avgY > maxLabelY + labelHeight + 2 && avgY < minLabelY - labelHeight - 2) {\n ctx.textBaseline = 'middle';\n if (ctx.fillStyle !== '#aaa') ctx.fillStyle = '#aaa';\n ctx.fillText(`${fmt(stats.avg)} ${def.unit}`, pad.l - 4, avgY);\n // Draw small indicator tick\n ctx.beginPath();\n ctx.moveTo(pad.l - 3, avgY);\n ctx.lineTo(pad.l, avgY);\n if (ctx.strokeStyle !== '#aaa') ctx.strokeStyle = '#aaa';\n ctx.lineWidth = 1;\n ctx.stroke();\n }\n }\n\n // \u2500\u2500 Higher/Lower percentages below histogram \u2500\u2500\n if (hoverBinI != null && total > 0) {\n let countLower = 0;\n for (let i = 0; i < hoverBinI; i++) countLower += bins[i];\n let countHigher = 0;\n for (let i = hoverBinI + 1; i < BINS; i++) countHigher += bins[i];\n\n const pLower = Math.round((countLower / total) * 100);\n const pHigher = Math.round((countHigher / total) * 100);\n\n ctx.save();\n ctx.textAlign = 'left';\n ctx.textBaseline = 'top';\n const font8 = '8px system-ui, sans-serif';\n if (ctx.font !== font8) ctx.font = font8;\n\n const textY = H - pad.b + 6;\n\n // Draw \"Lower\" stat\n if (ctx.fillStyle !== '#888896') ctx.fillStyle = '#888896';\n ctx.fillText('LOWER', pad.l, textY);\n if (ctx.fillStyle !== '#ccc') ctx.fillStyle = '#ccc';\n const fontB10 = 'bold 10px system-ui, sans-serif';\n if (ctx.font !== fontB10) ctx.font = fontB10;\n ctx.fillText(`${pLower}%`, pad.l, textY + 10);\n\n // Draw \"Higher\" stat\n const midX = pad.l + plotW / 2;\n if (ctx.font !== font8) ctx.font = font8;\n if (ctx.fillStyle !== '#888896') ctx.fillStyle = '#888896';\n ctx.fillText('HIGHER', midX, textY);\n if (ctx.fillStyle !== '#ccc') ctx.fillStyle = '#ccc';\n if (ctx.font !== fontB10) ctx.font = fontB10;\n ctx.fillText(`${pHigher}%`, midX, textY + 10);\n\n ctx.restore();\n }\n }\n\n function attachHistTooltip(canvas: HTMLCanvasElement, histData: HistData) {\n const { plot } = histData;\n\n canvas.addEventListener('click', (e) => {\n const binI = getBinAt(canvas, histData, e);\n if (binI != null && plot) {\n const { min, max, BINS } = histData;\n const span = max! - min! || 1;\n plot.pinnedHistY = min! + (binI + 0.5) * (span / BINS!);\n plots.forEach(({ uplot: u }) => u.redraw(false));\n }\n });\n\n canvas.addEventListener('mousemove', (e) => {\n lastHistMouseEvent = e;\n updateHistTooltip(e, canvas, histData);\n });\n\n canvas.addEventListener('mouseenter', () => {\n hoveredHistCanvas = canvas;\n hoveredHistData = histData;\n });\n\n canvas.addEventListener('mouseleave', () => {\n if (hoveredHistCanvas === canvas) {\n hoveredHistCanvas = null;\n hoveredHistData = null;\n }\n if (histTooltipEl) histTooltipEl.style.display = 'none';\n const lineEl = document.getElementById('hist-line');\n if (lineEl) lineEl.style.display = 'none';\n if (plot) {\n plot.hoveredHistY = null;\n drawHistogram(canvas, histData, canvas.height / (window.devicePixelRatio || 1));\n }\n });\n }\n\n function getBinAt(canvas: HTMLCanvasElement, histData: HistData, e: MouseEvent) {\n const { bins, BINS } = histData;\n if (!bins) return null;\n const rect = canvas.getBoundingClientRect();\n const relY = e.clientY - rect.top;\n // Use exact padding from drawHistogram: t: 4, b: 30\n const pad = { t: 4, b: 30 };\n const plotH = rect.height - pad.t - pad.b;\n const rawI = Math.floor(((relY - pad.t) / plotH) * BINS!);\n const binI = BINS! - 1 - rawI;\n if (rawI < 0 || rawI >= BINS!) return null;\n return binI;\n }\n\n function updateHistTooltip(e: MouseEvent, canvas: HTMLCanvasElement, histData: HistData) {\n if (!histTooltipEl) return;\n const { bins, binAccum, min, max, BINS, def, plot } = histData;\n if (!bins || !binAccum || min == null || max == null) return;\n\n const lineEl = document.getElementById('hist-line');\n const binI = getBinAt(canvas, histData, e);\n\n if (binI == null || !bins[binI]) {\n histTooltipEl.style.display = 'none';\n if (lineEl) lineEl.style.display = 'none';\n if (plot) {\n plot.hoveredHistY = null;\n drawHistogram(canvas, histData, canvas.height / (window.devicePixelRatio || 1));\n }\n return;\n }\n\n if (plot) {\n const span = max - min || 1;\n plot.hoveredHistY = min + (binI + 0.5) * (span / BINS!);\n drawHistogram(canvas, histData, canvas.height / (window.devicePixelRatio || 1), binI);\n }\n\n const count = bins[binI];\n const total = bins.reduce((s, v) => s + v, 0);\n const pct = (count / total) * 100;\n\n const span = max - min || 1;\n const low = min + binI * (span / BINS!);\n const high = min + (binI + 1) * (span / BINS!);\n const label = `${def.fmt(low, true)} \u2013 ${def.fmt(high, true)} ${def.unit}`;\n\n // Zone info\n let zoneHtml = '';\n const mid = (low + high) / 2;\n const metricKey = Object.keys(METRICS).find((k) => METRICS[k] === def);\n let zones: any[] = [];\n if (metricKey === 'power') zones = Zones.getPowerZones();\n else if (metricKey === 'hr') zones = Zones.getHRZones();\n\n if (zones.length > 0) {\n const zIdx = zones.findIndex((z) => mid >= z.min && mid < z.max);\n const z =\n zIdx !== -1\n ? zones[zIdx]\n : mid >= zones[zones.length - 1].min\n ? zones[zones.length - 1]\n : null;\n if (z) {\n const finalZIdx = zIdx !== -1 ? zIdx : zones.length - 1;\n zoneHtml = `\n
\n Z${finalZIdx + 1}\n \u00B7\n ${z.name}\n
\n `;\n }\n }\n\n const totalAccum =\n xAxis === 'distance' ? `${binAccum[binI].toFixed(2)} km` : fmtSecs(binAccum[binI]);\n const totalPct = (binAccum[binI] / binAccum.reduce((s, v) => s + v, 0)) * 100;\n\n let html = `\n
${label}
\n ${zoneHtml}\n `;\n\n if (totalAccum) {\n html += `
\n ${xAxis === 'distance' ? 'Dist' : 'Time'}\n ${totalAccum}\n ${totalPct.toFixed(1)}%\n
`;\n }\n\n // Selection context\n if (histData.selBins && histData.selBinAccum) {\n const stotalAccum =\n xAxis === 'distance'\n ? `${histData.selBinAccum[binI].toFixed(2)} km`\n : fmtSecs(histData.selBinAccum[binI]);\n const stotalPct =\n (histData.selBinAccum[binI] / histData.selBinAccum.reduce((s, v) => s + v, 0)) * 100;\n\n html += `\n
\n Sel ${xAxis === 'distance' ? 'Dist' : 'Time'}\n ${stotalAccum}\n ${stotalPct.toFixed(1)}%\n
\n `;\n }\n\n histTooltipEl.innerHTML = html;\n\n // Position tooltip: fixed X left of canvas, Y centered on bin\n histTooltipEl.style.display = 'block';\n const ttH = histTooltipEl.offsetHeight;\n const ttW = histTooltipEl.offsetWidth;\n const rect = canvas.getBoundingClientRect();\n const plotH = rect.height - 4 - 30; // pad.t + pad.b\n const binCenterY = rect.top + 4 + (BINS! - 1 - binI + 0.5) * (plotH / BINS!);\n\n histTooltipEl.style.left = `${rect.left - ttW - 12}px`;\n histTooltipEl.style.top = `${binCenterY - ttH / 2}px`;\n\n // Horizontal line sync\n if (lineEl) {\n lineEl.style.display = 'block';\n lineEl.style.top = `${binCenterY}px`;\n lineEl.style.left = '0px';\n lineEl.style.width = `${rect.left}px`;\n }\n }\n\n // \u2500\u2500 Helpers \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n function destroyPlots(keepState = false) {\n if (histTooltipEl) histTooltipEl.style.display = 'none';\n plots.forEach(({ uplot: u, row }) => {\n syncKey!.unsub(u);\n u.destroy();\n row.remove();\n });\n plots = [];\n renderedTrack = null;\n if (!keepState) {\n selAnchorVal = null;\n selEndVal = null;\n }\n isDragging = false;\n }\n\n function rangeStats(\n xData: (number | null)[],\n yData: (number | null)[],\n xMin: number,\n xMax: number,\n incZero = true,\n ) {\n let min = Infinity,\n max = -Infinity,\n sum = 0,\n n = 0;\n for (let i = 0; i < xData.length; i++) {\n if (xData[i] == null || xData[i]! < xMin || xData[i]! > xMax) continue;\n const v = yData[i];\n if (v == null || !isFinite(v)) continue;\n if (v === 0 && !incZero) continue;\n\n if (v < min) min = v;\n if (v > max) max = v;\n sum += v;\n n++;\n }\n return n ? { min, max, avg: sum / n } : null;\n }\n\n function elevationRangeStats(pts: TrackPoint[], xMin: number, xMax: number) {\n let gain = 0,\n loss = 0;\n let prevEle: number | null = null;\n const isDist = xAxis === 'distance';\n const t0 = pts[0].time || 0;\n\n for (let i = 0; i < pts.length; i++) {\n const p = pts[i];\n const x = isDist ? (p.dist || 0) / 1000 : (p.time! - t0) / 1000;\n if (x < xMin || x > xMax) continue;\n\n if (p.ele != null) {\n if (prevEle != null) {\n const diff = p.ele - prevEle;\n if (diff > 0) gain += diff;\n else if (diff < 0) loss += Math.abs(diff);\n }\n prevEle = p.ele;\n }\n }\n return { gain, loss };\n }\n\n // \u2500\u2500 Plot Rendering Helpers (Canvas) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n function updateTooltip(\n u: WegPlot,\n el: HTMLElement,\n idx: number,\n xData: (number | null)[],\n yData: (number | null)[],\n pts: TrackPoint[],\n def: MetricDefinition,\n metricKey: string,\n ) {\n if (idx == null || yData[idx] == null) {\n el.style.display = 'none';\n return;\n }\n\n const cx = u.valToPos(xData[idx]!, 'x', false);\n const cy = u.valToPos(yData[idx]!, 'y', false);\n\n let gColor = '#888896';\n let g = 0;\n if (idx > 0 && pts[idx] && pts[idx - 1]) {\n const p0 = pts[idx - 1],\n p1 = pts[idx];\n const d = (p1.dist || 0) - (p0.dist || 0);\n if (d > 0.1 && p1.ele != null && p0.ele != null) {\n g = ((p1.ele - p0.ele) / d) * 100;\n gColor = gradientColor(g);\n }\n }\n\n let content =\n metricKey !== 'elevation'\n ? `${def.fmt(yData[idx]!, false)} ${def.unit}`\n : `${def.fmt(yData[idx]!, false)} ${def.unit} \u2220 ${Math.abs(g).toFixed(1)}%`;\n\n // Special handling for gears: show tooth count if available\n if (metricKey === 'gearRear' && pts[idx].gearRearTooth != null) {\n content = `${pts[idx].gearRearTooth}T (pos ${yData[idx]})`;\n } else if (metricKey === 'gearFront' && pts[idx].gearFrontTooth != null) {\n content = `${pts[idx].gearFrontTooth}T (pos ${yData[idx]})`;\n } else if (metricKey === 'gears') {\n const p = pts[idx];\n const front =\n p.gearFrontTooth != null\n ? `${p.gearFrontTooth}T`\n : p.gearFront != null\n ? `pos ${p.gearFront}`\n : '';\n const rear =\n p.gearRearTooth != null\n ? `${p.gearRearTooth}T`\n : p.gearRear != null\n ? `pos ${p.gearRear}`\n : '';\n\n if (front || rear)\n content += ` (${front} / ${rear})`;\n }\n\n if (el.innerHTML !== content) el.innerHTML = content;\n\n const color44 = `${def.color}44`;\n if (el.style.color !== def.color) el.style.color = def.color;\n if (el.style.borderColor !== color44) el.style.borderColor = color44;\n\n const left = `${cx}px`;\n const top = `${cy}px`;\n const transform = 'translate(6px, -50%)';\n if (el.style.left !== left) el.style.left = left;\n if (el.style.top !== top) el.style.top = top;\n if (el.style.transform !== transform) el.style.transform = transform;\n if (el.style.display !== '') el.style.display = '';\n }\n\n const gradientColorRgbaCache = new Map();\n function gradientColorRgba(g: number | null): string {\n let c = gradientColorRgbaCache.get(g);\n if (!c) {\n c = hexToRgba(gradientColor(g), 0.8);\n gradientColorRgbaCache.set(g, c);\n }\n return c;\n }\n\n function drawElevationGradient(\n u: uPlot,\n xData: number[],\n yData: number[],\n gradData: (number | null)[] | null,\n ) {\n const ctx = u.ctx;\n const bb = u.bbox;\n const dpr = window.devicePixelRatio || 1;\n const xMin = u.scales.x?.min ?? -Infinity;\n const xMax = u.scales.x?.max ?? Infinity;\n const yMin = u.scales.y?.min ?? 0;\n const yMax = u.scales.y?.max ?? 100;\n const xScale = bb.width / (xMax - xMin || 1);\n const yScale = bb.height / (yMax - yMin || 1);\n const yBase = bb.top + bb.height;\n\n const idxs = u.series[0]?.idxs;\n const iStart = idxs ? Math.max(1, idxs[0]) : 1;\n const iEnd = idxs ? Math.min(xData.length - 1, idxs[1] + 1) : xData.length - 1;\n const step = Math.max(1, Math.floor((iEnd - iStart) / 250));\n\n ctx.save();\n ctx.lineWidth = 1.5 * dpr;\n\n const byColor = new Map();\n\n let prevIdx = iStart - 1;\n for (let i = iStart; i <= iEnd; i += step) {\n const xv1 = xData[i];\n const xv0 = xData[prevIdx];\n prevIdx = i;\n if (xv1 == null || yData[i] == null || xv0 == null || yData[i - step] == null) continue;\n if (xv1 < xMin || xv0 > xMax) continue;\n\n const x0 = bb.left + (xv0 - xMin) * xScale;\n const y0 = yBase - (yData[i - step]! - yMin) * yScale;\n const x1 = bb.left + (xv1 - xMin) * xScale;\n const y1 = yBase - (yData[i]! - yMin) * yScale;\n\n const g = gradData && gradData[i] != null ? Math.round(gradData[i]! / 2) * 2 : null;\n const color = gradientColorRgba(g);\n let segs = byColor.get(color);\n if (!segs) {\n segs = [];\n byColor.set(color, segs);\n }\n segs.push(x0, y0, x1, y1);\n }\n\n for (const [color, segs] of byColor) {\n ctx.beginPath();\n ctx.strokeStyle = color;\n for (let j = 0; j < segs.length; j += 4) {\n ctx.moveTo(segs[j], segs[j + 1]);\n ctx.lineTo(segs[j + 2], segs[j + 3]);\n }\n ctx.stroke();\n }\n\n ctx.restore();\n }\n\n function drawGradientChart(u: uPlot, xData: number[], yData: number[]) {\n const ctx = u.ctx;\n const bb = u.bbox;\n const dpr = window.devicePixelRatio || 1;\n const xMin = u.scales.x?.min ?? -Infinity;\n const xMax = u.scales.x?.max ?? Infinity;\n const yMin = u.scales.y?.min ?? 0;\n const yMax = u.scales.y?.max ?? 100;\n const xScale = bb.width / (xMax - xMin || 1);\n const yScale = bb.height / (yMax - yMin || 1);\n const yBase = bb.top + bb.height;\n\n const idxs = u.series[0]?.idxs;\n const iStart = idxs ? Math.max(1, idxs[0]) : 1;\n const iEnd = idxs ? Math.min(xData.length - 1, idxs[1] + 1) : xData.length - 1;\n const step = Math.max(1, Math.floor((iEnd - iStart) / 250));\n\n ctx.save();\n ctx.lineWidth = 1.5 * dpr;\n\n const byColor = new Map();\n\n let prevIdx = iStart - 1;\n for (let i = iStart; i <= iEnd; i += step) {\n const xv1 = xData[i];\n const xv0 = xData[prevIdx];\n prevIdx = i;\n if (xv1 == null || yData[i] == null || xv0 == null || yData[i - step] == null) continue;\n if (xv1 < xMin || xv0 > xMax) continue;\n\n const x0 = bb.left + (xv0 - xMin) * xScale;\n const y0 = yBase - (yData[i - step]! - yMin) * yScale;\n const x1 = bb.left + (xv1 - xMin) * xScale;\n const y1 = yBase - (yData[i]! - yMin) * yScale;\n\n const g = Math.round(yData[i]! / 2) * 2;\n const color = gradientColorRgba(g);\n let segs = byColor.get(color);\n if (!segs) {\n segs = [];\n byColor.set(color, segs);\n }\n segs.push(x0, y0, x1, y1);\n }\n\n for (const [color, segs] of byColor) {\n ctx.beginPath();\n ctx.strokeStyle = color;\n for (let j = 0; j < segs.length; j += 4) {\n ctx.moveTo(segs[j], segs[j + 1]);\n ctx.lineTo(segs[j + 2], segs[j + 3]);\n }\n ctx.stroke();\n }\n\n ctx.restore();\n }\n\n function drawHoverLine(u: uPlot, yData: number[], color: string, pts: TrackPoint[]) {\n const idx = u.cursor.idx;\n if (idx == null || yData[idx] == null || u.cursor.left! < 0) return;\n\n const t0 = pts[0].time || 0;\n const xVal = xAxis === 'distance' ? (pts[idx].dist || 0) / 1000 : (pts[idx].time! - t0) / 1000;\n const cx = u.valToPos(xVal, 'x', true);\n const cy = u.valToPos(yData[idx], 'y', true);\n const bb = u.bbox;\n const ctx = u.ctx;\n const dpr = window.devicePixelRatio || 1;\n\n // Line from top to point + horizontal from left\n ctx.save();\n ctx.beginPath();\n ctx.setLineDash([2 * dpr, 2 * dpr]);\n ctx.strokeStyle = hexToRgba(color, 0.4);\n ctx.lineWidth = 1 * dpr;\n ctx.moveTo(cx, bb.top);\n ctx.lineTo(cx, cy);\n ctx.moveTo(bb.left, cy);\n ctx.lineTo(cx, cy);\n ctx.stroke();\n\n // Dot at point\n ctx.beginPath();\n ctx.setLineDash([]); // Ensure solid outline\n ctx.arc(cx, cy, 3 * dpr, 0, Math.PI * 2);\n ctx.fillStyle = '#0e0e10';\n ctx.fill();\n ctx.strokeStyle = color;\n ctx.lineWidth = 2 * dpr;\n ctx.stroke();\n ctx.restore();\n }\n\n function drawVerticalLineOnly(\n u: uPlot,\n pts: TrackPoint[],\n idx: number,\n color: string,\n opacity: number = 1.0,\n ) {\n const dpr = window.devicePixelRatio || 1;\n const bb = u.bbox;\n const ctx = u.ctx;\n const xVal =\n xAxis === 'distance'\n ? (pts[idx].dist || 0) / 1000\n : (pts[idx].time! - (pts[0].time || 0)) / 1000;\n const cx = u.valToPos(xVal, 'x', true);\n\n ctx.save();\n ctx.globalAlpha = opacity;\n ctx.beginPath();\n ctx.strokeStyle = color;\n ctx.lineWidth = 1 * dpr;\n // Dotted line range should match where labels are (flush top to bottom axis)\n ctx.moveTo(cx, bb.top + 16 * dpr);\n ctx.lineTo(cx, bb.top + bb.height + 10 * dpr);\n ctx.stroke();\n ctx.restore();\n }\n\n function drawXAxisLabels(\n u: uPlot,\n pts: TrackPoint[],\n idx: number,\n color: string,\n skipLine = false,\n opacity: number = 1.0,\n ) {\n const dpr = window.devicePixelRatio || 1;\n const bb = u.bbox;\n const ctx = u.ctx;\n const t0 = pts[0].time || 0;\n const distVal = (pts[idx].dist || 0) / 1000;\n const timeVal = (pts[idx].time! - t0) / 1000;\n const cx = u.valToPos(xAxis === 'distance' ? distVal : timeVal, 'x', true);\n\n // Bottom label: matches the current X axis unit\n const bottomLabel = xAxis === 'distance' ? `${distVal.toFixed(2)} km` : fmtSecs(timeVal);\n // Top label: shows the other unit (simplified)\n const topLabel = xAxis === 'distance' ? fmtSecs(timeVal) : `${distVal.toFixed(2)} km`;\n\n const drawPill = (text: string, x: number, y: number, isTop: boolean) => {\n const fontStr = `bold ${10 * dpr}px system-ui, sans-serif`;\n if (ctx.font !== fontStr) ctx.font = fontStr;\n const tw = ctx.measureText(text).width;\n const bw = tw + 10 * dpr;\n const bh = 16 * dpr;\n const rx = x - bw / 2;\n const clampedRx = Math.max(bb.left, Math.min(bb.left + bb.width - bw, rx));\n const ry = y - bh / 2;\n\n ctx.save();\n ctx.globalAlpha = opacity;\n ctx.setLineDash([]); // Ensure pills are not dashed\n ctx.beginPath();\n if ((ctx as any).roundRect) (ctx as any).roundRect(clampedRx, ry, bw, bh, 3 * dpr);\n else ctx.rect(clampedRx, ry, bw, bh);\n ctx.fillStyle = 'rgba(14,14,16,0.92)';\n ctx.fill();\n ctx.strokeStyle = hexToRgba(color, 0.27);\n ctx.lineWidth = 1 * dpr;\n ctx.stroke();\n\n ctx.fillStyle = color;\n ctx.textAlign = 'center';\n ctx.textBaseline = 'middle';\n ctx.fillText(text, clampedRx + bw / 2, ry + bh / 2 + 0.5 * dpr);\n ctx.restore();\n };\n\n if (!skipLine) drawVerticalLineOnly(u, pts, idx, color, opacity);\n\n const pillH = 16 * dpr;\n // Top label: flush to the top edge of the plot\n drawPill(topLabel, cx, bb.top + pillH / 2, true);\n\n // Bottom label: aligned with the horizontal axis labels (centered in the 30px axis area)\n drawPill(bottomLabel, cx, bb.top + bb.height + 15 * dpr, false);\n }\n\n function drawPinnedDot(\n u: uPlot,\n xData: (number | null)[],\n yData: (number | null)[],\n color: string,\n def: MetricDefinition,\n pts: TrackPoint[],\n metricKey: string,\n ) {\n if (pinnedPtIdx == null) return;\n const xVal = xData[pinnedPtIdx];\n const yVal = yData[pinnedPtIdx];\n if (xVal == null || yVal == null) return;\n\n const cx = u.valToPos(xVal, 'x', true);\n const cy = u.valToPos(yVal, 'y', true);\n const bb = u.bbox;\n const ctx = u.ctx;\n const dpr = window.devicePixelRatio || 1;\n\n ctx.save();\n // Vertical line\n ctx.beginPath();\n ctx.strokeStyle = color;\n ctx.lineWidth = 1.5 * dpr;\n ctx.moveTo(cx, bb.top);\n ctx.lineTo(cx, bb.top + bb.height);\n ctx.stroke();\n\n // Horizontal dashed line\n ctx.beginPath();\n ctx.setLineDash([3 * dpr, 3 * dpr]);\n ctx.strokeStyle = hexToRgba(color, 0.5);\n ctx.lineWidth = 1 * dpr;\n ctx.moveTo(bb.left, cy);\n ctx.lineTo(cx, cy);\n ctx.stroke();\n\n // Large Dot\n ctx.beginPath();\n ctx.setLineDash([]); // Ensure solid outline\n ctx.arc(cx, cy, 5 * dpr, 0, Math.PI * 2);\n ctx.fillStyle = '#0e0e10';\n ctx.fill();\n ctx.strokeStyle = color;\n ctx.lineWidth = 2.5 * dpr;\n ctx.stroke();\n ctx.restore();\n\n const s = (u as any)._strasse;\n if (s && s.pinYVal) {\n updateTooltip(\n u,\n s.pinYVal,\n pinnedPtIdx,\n xData as number[],\n yData as (number | null)[],\n pts,\n def,\n metricKey,\n );\n }\n }\n\n function drawYAxisHighlight(\n u: uPlot,\n xData: (number | null)[],\n yData: (number | null)[],\n yVal: number,\n color: string,\n def: MetricDefinition,\n ) {\n const bb = u.bbox;\n const ctx = u.ctx;\n const dpr = window.devicePixelRatio || 1;\n const cy = u.valToPos(yVal, 'y', true);\n\n // Clamp cy to bbox\n const cyClamp = Math.max(bb.top, Math.min(bb.top + bb.height, cy));\n\n ctx.save();\n // Horizontal line\n ctx.beginPath();\n ctx.setLineDash([4 * dpr, 4 * dpr]);\n ctx.strokeStyle = hexToRgba(color, 0.4);\n ctx.lineWidth = 1 * dpr;\n ctx.moveTo(bb.left, cyClamp);\n ctx.lineTo(bb.left + bb.width, cyClamp);\n ctx.stroke();\n\n // Fill area above/below\n drawAreaFillAbove(u, xData, yData, cyClamp, color);\n\n // Pill label on Y-axis\n const label = def.fmt(yVal, true);\n const fontStr = `bold ${10 * dpr}px system-ui, sans-serif`;\n if (ctx.font !== fontStr) ctx.font = fontStr;\n const tw = ctx.measureText(label).width;\n const bw = tw + 8 * dpr;\n const bh = 16 * dpr;\n const bx = bb.left - bw - 2 * dpr;\n const by = cyClamp - bh / 2;\n\n ctx.beginPath();\n ctx.setLineDash([]);\n if ((ctx as any).roundRect) (ctx as any).roundRect(bx, by, bw, bh, 3 * dpr);\n else ctx.rect(bx, by, bw, bh);\n ctx.fillStyle = 'rgba(14,14,16,0.95)';\n ctx.fill();\n ctx.strokeStyle = color;\n ctx.lineWidth = 1 * dpr;\n ctx.stroke();\n\n ctx.fillStyle = '#fff';\n ctx.textAlign = 'center';\n ctx.textBaseline = 'middle';\n ctx.fillText(label, bx + bw / 2, by + bh / 2 + 0.5 * dpr);\n ctx.restore();\n }\n\n function drawAreaFillAbove(\n u: uPlot,\n xData: (number | null)[],\n yData: (number | null)[],\n cyClamp: number,\n color: string,\n ) {\n const ctx = u.ctx;\n const bb = u.bbox;\n const xMin = u.scales.x?.min ?? -Infinity;\n const xMax = u.scales.x?.max ?? Infinity;\n const yMin = u.scales.y?.min ?? 0;\n const yMax = u.scales.y?.max ?? 100;\n const xScale = bb.width / (xMax - xMin || 1);\n const yScale = bb.height / (yMax - yMin || 1);\n const yDir = u.scales.y?.dir ?? 1;\n const yBase = yDir === -1 ? bb.top : bb.top + bb.height;\n const yMult = yDir === -1 ? yScale : -yScale;\n\n const idxs = u.series[0]?.idxs;\n const iStart = idxs ? Math.max(0, idxs[0] - 1) : 0;\n const iEnd = idxs ? Math.min(xData.length - 1, idxs[1] + 1) : xData.length - 1;\n const step = Math.max(1, Math.floor((iEnd - iStart) / 250));\n\n ctx.save();\n ctx.beginPath();\n ctx.rect(bb.left, bb.top, bb.width, bb.height);\n ctx.clip();\n\n ctx.fillStyle = hexToRgba(color, 0.2);\n ctx.beginPath();\n\n let inSegment = false;\n let hasAnySegment = false;\n let lastPx = 0;\n\n for (let i = iStart; i <= iEnd; i += step) {\n const xv = xData[i];\n const yv = yData[i];\n if (xv == null || yv == null || xv < xMin || xv > xMax) {\n if (inSegment) {\n ctx.lineTo(lastPx, cyClamp);\n ctx.closePath();\n inSegment = false;\n }\n continue;\n }\n\n const px = bb.left + (xv - xMin) * xScale;\n const py = yBase + (yv - yMin) * yMult;\n lastPx = px;\n\n if (py < cyClamp) {\n if (!inSegment) {\n ctx.moveTo(px, cyClamp);\n inSegment = true;\n hasAnySegment = true;\n }\n ctx.lineTo(px, py);\n } else if (inSegment) {\n ctx.lineTo(px, cyClamp);\n ctx.closePath();\n inSegment = false;\n }\n }\n\n if (inSegment) {\n ctx.lineTo(lastPx, cyClamp);\n ctx.closePath();\n }\n\n if (hasAnySegment) {\n ctx.fill();\n }\n\n ctx.restore();\n }\n\n // \u2500\u2500 Utils \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n function lerpHex(a: string, b: string, t: number) {\n const ah = parseInt(a.slice(1), 16),\n bh = parseInt(b.slice(1), 16);\n const ar = ah >> 16,\n ag = (ah >> 8) & 0xff,\n ab = ah & 0xff;\n const br = bh >> 16,\n bg = (bh >> 8) & 0xff,\n bb = bh & 0xff;\n const rr = ar + (br - ar) * t,\n rg = ag + (bg - ag) * t,\n rb = ab + (bb - ab) * t;\n return `#${((1 << 24) + (Math.round(rr) << 16) + (Math.round(rg) << 8) + Math.round(rb))\n .toString(16)\n .slice(1)}`;\n }\n\n function gradientColor(g: number | null) {\n if (g == null) return '#888896';\n const absG = Math.abs(g);\n const t = Math.min(1, absG / 8);\n if (g > 0) return lerpHex('#A8C8A0', '#d73027', t); // Green to Red\n return lerpHex('#A8C8A0', '#4575b4', t); // Green to Blue\n }\n\n function speedColor(s: number | null) {\n if (s == null) return '#888896';\n const kmh = s * 3.6;\n\n if (kmh <= 5) return '#4575b4'; // Solid Blue for very slow\n if (kmh >= 60) return '#d73027'; // Solid Red for very fast\n\n // High resolution between 5 and 45 (10km/h per segment)\n if (kmh <= 45) {\n const t = (kmh - 5) / 40; // 0 to 1\n if (t <= 0.25) return lerpHex('#4575b4', '#91bfdb', t / 0.25); // 5-15: Blue -> LBlue\n if (t <= 0.5) return lerpHex('#91bfdb', '#abdda4', (t - 0.25) / 0.25); // 15-25: LBlue -> Green\n if (t <= 0.75) return lerpHex('#abdda4', '#fee08b', (t - 0.5) / 0.25); // 25-35: Green -> Yellow\n return lerpHex('#fee08b', '#fc8d59', (t - 0.75) / 0.25); // 35-45: Yellow -> Orange\n } else {\n // 45-60: Orange -> Red\n const t = (kmh - 45) / 15;\n return lerpHex('#fc8d59', '#d73027', t);\n }\n }\n\n function smoothGradient(pts: TrackPoint[], windowMetres: number) {\n const grads: (number | null)[] = new Array(pts.length).fill(null);\n for (let i = 0; i < pts.length; i++) {\n const p = pts[i];\n let j0 = i,\n j1 = i;\n while (j0 > 0 && (p.dist || 0) - (pts[j0].dist || 0) < windowMetres / 2) j0--;\n while (j1 < pts.length - 1 && (pts[j1].dist || 0) - (p.dist || 0) < windowMetres / 2) j1++;\n const d = (pts[j1].dist || 0) - (pts[j0].dist || 0);\n if (d > 1 && pts[j1].ele != null && pts[j0].ele != null)\n grads[i] = ((pts[j1].ele! - pts[j0].ele!) / d) * 100;\n }\n return grads;\n }\n\n function fillNulls(data: (number | null)[]) {\n const res = [...data];\n for (let i = 0; i < res.length; i++) {\n if (res[i] === null) {\n let left = i - 1;\n while (left >= 0 && res[left] === null) left--;\n let right = i + 1;\n while (right < res.length && res[right] === null) right++;\n if (left >= 0 && right < res.length)\n res[i] = res[left]! + ((res[right]! - res[left]!) * (i - left)) / (right - left);\n else if (left >= 0) res[i] = res[left];\n else if (right < res.length) res[i] = res[right];\n }\n }\n return res;\n }\n\n function updateStats(track: TrackData) {\n const s = track.stats;\n const fmt = (id: string, val: string) => {\n const el = document.querySelector(`#${id} .stat-value`);\n if (el) el.textContent = val;\n };\n fmt('stat-distance', s.totalDist != null ? `${(s.totalDist / 1000).toFixed(1)} km` : '\u2014');\n fmt('stat-duration', s.duration != null ? fmtSecs(Math.floor(s.duration / 1000)) : '\u2014');\n fmt('stat-elevation', s.elevGain != null ? `${Math.round(s.elevGain)} m` : '\u2014');\n\n // Recalculate averages from visible plot data to respect zero filtering\n const getAvg = (key: string) => {\n const p = plots.find((p) => p.metricKey === key);\n if (!p) {\n if (key === 'speed') return s.avgSpeed ?? null;\n if (key === 'power') return s.avgPower ?? null;\n if (key === 'cadence') return s.avgCadence ?? null;\n return null;\n }\n if (p.histData.fullStats !== undefined) {\n return p.histData.fullStats ? p.histData.fullStats.avg : null;\n }\n const incZero =\n metricsIncludingZero.has(key) || (key !== 'power' && key !== 'cadence' && key !== 'speed');\n const vals = p.yData.filter((v): v is number => v != null && (incZero || v !== 0));\n if (!vals.length) return null;\n return vals.reduce((a, b) => a + b, 0) / vals.length;\n };\n\n const avgSpeed = getAvg('speed');\n fmt('stat-avg-speed', avgSpeed != null ? `${(avgSpeed * 3.6).toFixed(1)} km/h` : '\u2014');\n\n const avgPower = getAvg('power');\n fmt('stat-avg-power', avgPower != null ? `${Math.round(avgPower)} W` : '\u2014');\n\n const avgCadence = getAvg('cadence');\n fmt('stat-avg-cad', avgCadence != null ? `${Math.round(avgCadence)} rpm` : '\u2014');\n\n fmt('stat-avg-hr', s.avgHR != null ? `${s.avgHR} bpm` : '\u2014');\n fmt('stat-avg-temp', s.avgTemp != null ? `${s.avgTemp.toFixed(1)} \u00B0C` : '\u2014');\n }\n\n const INTERVAL_CAP = 16384;\n const ivPwr = new Float64Array(INTERVAL_CAP);\n const ivHr = new Float64Array(INTERVAL_CAP);\n const ivSpd = new Float64Array(INTERVAL_CAP);\n const ivEle = new Float64Array(INTERVAL_CAP);\n const ivCad = new Float64Array(INTERVAL_CAP);\n const ivSmoothed = new Float64Array(INTERVAL_CAP);\n const ivKdeBins = new Float64Array(64);\n const intervalWindows = [\n 3, 5, 8, 10, 12, 15, 20, 25, 30, 40, 45, 60, 75, 90, 120, 150, 180, 240, 300, 360,\n ];\n\n function analyzeTelemetryIntervals(\n tracksList: TrackData[],\n selStartSec: number,\n selEndSec: number,\n ) {\n if (!tracksList.length) return 0;\n ivKdeBins.fill(0);\n let intervalScore = 0;\n\n for (let ti = 0; ti < tracksList.length; ti++) {\n const pts = tracksList[ti].points;\n const n = Math.min(pts.length, INTERVAL_CAP);\n if (n < 4) continue;\n\n for (let i = 0; i < n; i++) {\n const p = pts[i];\n ivPwr[i] = p.power || 0;\n ivHr[i] = p.hr || 0;\n ivSpd[i] = p.speed || 0;\n ivEle[i] = p.ele || 0;\n ivCad[i] = p.cad || 0;\n }\n\n // Single-pass Savitzky-Golay 5-point quadratic smoothing and section interval analysis\n const sStart = Math.max(0, Math.min(n - 1, Math.floor(selStartSec)));\n const sEnd = Math.max(sStart + 1, Math.min(n, Math.floor(selEndSec)));\n const inv35 = 1 / 35;\n const invSigmaSq = 1 / 180;\n const omega = 0.012;\n\n for (let i = 2; i < n - 2; i++) {\n const sgPwr =\n (-3 * ivPwr[i - 2] +\n 12 * ivPwr[i - 1] +\n 17 * ivPwr[i] +\n 12 * ivPwr[i + 1] -\n 3 * ivPwr[i + 2]) *\n inv35;\n const sgHr =\n (-3 * ivHr[i - 2] +\n 12 * ivHr[i - 1] +\n 17 * ivHr[i] +\n 12 * ivHr[i + 1] -\n 3 * ivHr[i + 2]) *\n inv35;\n const sgSpd = (ivSpd[i - 1] + 2 * ivSpd[i] + ivSpd[i + 1]) * 0.25;\n const dP = sgPwr - ivPwr[i];\n const gauss = Math.exp(-dP * dP * invSigmaSq);\n const harmonic = Math.cos(i * omega + ivCad[i] * 0.02);\n ivSmoothed[i] = sgPwr * gauss + sgHr * 0.8 + sgSpd * (ivCad[i] * 0.1 + harmonic);\n }\n\n const sectionRanges: [number, number][] = [\n [0, Math.floor(n * 0.25)],\n [Math.floor(n * 0.25), Math.floor(n * 0.6)],\n [Math.floor(n * 0.6), n],\n [sStart, sEnd],\n ];\n\n for (let si = 0; si < sectionRanges.length; si++) {\n const [secStart, secEnd] = sectionRanges[si];\n const secLen = secEnd - secStart;\n if (secLen < 4) continue;\n\n for (let wi = 0; wi < intervalWindows.length; wi++) {\n const w = intervalWindows[wi];\n if (w >= secLen) continue;\n const invW = 1 / w;\n let winSum = 0;\n let winEle = 0;\n let maxInterval = 0;\n for (let i = secStart; i < secEnd; i++) {\n winSum += ivSmoothed[i];\n winEle += ivEle[i];\n if (i - secStart >= w) {\n winSum -= ivSmoothed[i - w];\n winEle -= ivEle[i - w];\n const avg = (winSum + winEle * 0.05) * invW;\n if (avg > maxInterval) maxInterval = avg;\n }\n }\n intervalScore += maxInterval * 1e-4;\n }\n\n // Section Kernel Density Estimation (KDE) binning\n for (let i = secStart; i < secEnd; i++) {\n const norm = Math.max(0, Math.min(63, ((ivSmoothed[i] * 0.12) | 0) & 63));\n ivKdeBins[norm] += 1 + (ivEle[i] - ivEle[secStart]) * 1e-3;\n if (norm > 0) ivKdeBins[norm - 1] += 0.5;\n if (norm < 63) ivKdeBins[norm + 1] += 0.5;\n }\n }\n }\n\n for (let b = 0; b < 64; b++) {\n intervalScore += ivKdeBins[b] * 1e-5;\n }\n if (container) {\n container.dataset.intervalScore = intervalScore.toFixed(2);\n }\n return intervalScore;\n }\n\n return {\n init,\n loadTrack,\n clear,\n resetState,\n toggleMetric,\n setXAxis,\n toggleStats,\n setSelectionStats,\n clearSelectionStats,\n restoreSelection,\n resetZoom,\n cancelSelection,\n analyzeTelemetryIntervals,\n setCursorAt: (idx: number, deferRender = false) => {\n pinnedPtIdx = idx;\n if (onPinChangeCb) onPinChangeCb(idx);\n if (deferRender) {\n if (onCursorMoveCb && currentTrack?.points?.[idx]) {\n onCursorMoveCb(currentTrack.points[idx]);\n }\n return;\n }\n cursorSyncing = true;\n plots.forEach(({ uplot: u, yData, xData }) => {\n if (xData[idx] != null && yData[idx] != null) {\n u.batch(() => {\n (u as any).setCursor(\n {\n left: u.valToPos(xData[idx]!, 'x'),\n top: u.valToPos(yData[idx]!, 'y'),\n },\n true,\n false,\n );\n });\n }\n });\n cursorSyncing = false;\n if (onCursorMoveCb && currentTrack?.points?.[idx]) {\n onCursorMoveCb(currentTrack.points[idx]);\n }\n },\n setHoverAt: (idx: number | null) => {\n cursorSyncing = true;\n plots.forEach(({ uplot: u, yData, xData }) => {\n if (idx === null) {\n u.batch(() => {\n (u as any).setCursor({ left: -10, top: -10 }, true, false);\n });\n } else if (xData[idx] != null && yData[idx] != null) {\n u.batch(() => {\n (u as any).setCursor(\n {\n left: u.valToPos(xData[idx]!, 'x'),\n top: u.valToPos(yData[idx]!, 'y'),\n },\n true,\n false,\n );\n });\n }\n });\n cursorSyncing = false;\n if (onCursorMoveCb) {\n onCursorMoveCb(\n idx != null && currentTrack?.points?.[idx] ? currentTrack.points[idx] : null,\n );\n }\n },\n clearPinnedDot: () => {\n pinnedPtIdx = null;\n if (onPinChangeCb) onPinChangeCb(null);\n plots.forEach(({ uplot: u }) => u.redraw(false));\n },\n resize,\n METRICS,\n isDragging: () => isDragging,\n setMapColorChangeCb,\n toggleMapColor,\n getMapColorMetric: () => mapColorMetric,\n getActiveMetrics: () => activeMetrics,\n getAvailableMetrics: () => availableMetrics,\n setActiveMetrics,\n setSmooth,\n setZeroInclusion,\n updateMetricOrder: (keys: string[]) => {\n // Keys might only contain current active ones, or more.\n // We want to re-order activeMetrics to match the order in 'keys'\n const newActive = new Set();\n keys.forEach((k) => {\n if (activeMetrics.has(k)) newActive.add(k);\n });\n // Add any remaining ones that were active but not in the keys list (shouldn't happen with our DND)\n activeMetrics.forEach((k) => {\n if (!newActive.has(k)) newActive.add(k);\n });\n activeMetrics = newActive;\n if (currentTrack) render(true);\n },\n getXAxis: () => xAxis,\n };\n\n function drawMetricColorFill(\n u: uPlot,\n xData: (number | null)[],\n yData: (number | null)[],\n pts: TrackPoint[],\n metricKey: string,\n ) {\n const ctx = u.ctx;\n const bb = u.bbox;\n const dpr = window.devicePixelRatio || 1;\n const colors = _computePointColors(metricKey, yData);\n\n ctx.save();\n ctx.beginPath();\n ctx.rect(bb.left, bb.top, bb.width, bb.height);\n ctx.clip();\n\n const isStepped = metricKey === 'gearRear' || metricKey === 'gearFront';\n\n for (let i = 1; i < xData.length; i++) {\n if (xData[i] == null || yData[i] == null || xData[i - 1] == null || yData[i - 1] == null)\n continue;\n const x0 = u.valToPos(xData[i - 1]!, 'x', true);\n const y0 = u.valToPos(yData[i - 1]!, 'y', true);\n const x1 = u.valToPos(xData[i]!, 'x', true);\n const y1 = u.valToPos(yData[i]!, 'y', true);\n\n const c = colors[i];\n\n // Top line segment\n ctx.beginPath();\n ctx.moveTo(x0, y0);\n if (isStepped) ctx.lineTo(x1, y0);\n\n ctx.lineTo(x1, y1);\n ctx.strokeStyle = c;\n ctx.lineWidth = 2 * dpr;\n ctx.stroke();\n }\n\n ctx.restore();\n }\n\n function drawBackgroundTracks(u: uPlot, metricKey: string, def: MetricDefinition) {\n if (!allTracks.length) return;\n const ctx = u.ctx;\n const bb = u.bbox;\n const dpr = window.devicePixelRatio || 1;\n const GAP_THRESHOLD = 60000; // 1 minute in ms\n\n ctx.save();\n ctx.beginPath();\n ctx.rect(bb.left, bb.top, bb.width, bb.height);\n ctx.clip();\n\n const isStepped = metricKey === 'gearRear' || metricKey === 'gearFront';\n\n for (const track of allTracks) {\n if (currentTrack && track.id === currentTrack.id) continue;\n\n const pts = track.points;\n const t0 = pts.find((p) => p.time != null)?.time || 0;\n const field = def.field;\n\n ctx.beginPath();\n ctx.strokeStyle = 'rgba(255, 255, 255, 0.04)'; // Extremely faint\n ctx.lineWidth = 1 * dpr;\n\n let first = true;\n for (let i = 0; i < pts.length; i++) {\n const p = pts[i];\n const v = p[field] as number;\n\n if (v == null) {\n first = true;\n continue;\n }\n\n if (i > 0) {\n const prev = pts[i - 1];\n const dt = p.time && prev.time ? p.time - prev.time : 0;\n if (dt > GAP_THRESHOLD) first = true;\n }\n\n const val = def.transform ? def.transform(v) : v;\n if (val == null) {\n first = true;\n continue;\n }\n const xVal = xAxis === 'distance' ? (p.dist || 0) / 1000 : (p.time! - t0) / 1000;\n\n // Skip if way outside horizontal range\n if (xVal < u.scales.x!.min! - 10 || xVal > u.scales.x!.max! + 10) {\n first = true;\n continue;\n }\n\n const px = u.valToPos(xVal, 'x', true);\n const py = u.valToPos(val, 'y', true);\n\n if (first) {\n ctx.moveTo(px, py);\n first = false;\n } else {\n if (isStepped) {\n const prevP = pts[i - 1];\n const prevXVal =\n xAxis === 'distance' ? (prevP.dist || 0) / 1000 : (prevP.time! - t0) / 1000;\n ctx.lineTo(u.valToPos(prevXVal, 'x', true), py);\n }\n ctx.lineTo(px, py);\n }\n }\n ctx.stroke();\n }\n\n ctx.restore();\n }\n\n function drawTrackPath(\n u: uPlot,\n xData: (number | null)[],\n yData: (number | null)[],\n color: string,\n width: number,\n metricKey: string,\n range: [number, number] | null = null,\n pts: TrackPoint[] = [],\n ) {\n const ctx = u.ctx;\n const bb = u.bbox;\n const dpr = window.devicePixelRatio || 1;\n const GAP_THRESHOLD = 60000; // 1 minute in ms\n\n ctx.save();\n ctx.beginPath();\n ctx.strokeStyle = hexToRgba(color, 0.8);\n ctx.lineWidth = width * dpr;\n\n const isStepped = metricKey === 'gearRear' || metricKey === 'gearFront';\n let first = true;\n let lastFrontGear: number | null = null;\n let fMin = 1;\n let fMax = 1;\n\n if (metricKey === 'gears' && pts.length > 0) {\n let lo = Infinity,\n hi = -Infinity;\n for (let i = 0; i < pts.length; i++) {\n const fg = pts[i].gearFrontTooth ?? pts[i].gearFront;\n if (fg != null) {\n if (fg < lo) lo = fg;\n if (fg > hi) hi = fg;\n }\n }\n if (lo <= hi) {\n fMin = lo;\n fMax = hi;\n }\n }\n\n const rMin = range ? range[0] : (u.scales.x?.min ?? -Infinity);\n const rMax = range ? range[1] : (u.scales.x?.max ?? Infinity);\n const xMin = u.scales.x?.min ?? -Infinity;\n const xMax = u.scales.x?.max ?? Infinity;\n const yMin = u.scales.y?.min ?? 0;\n const yMax = u.scales.y?.max ?? 100;\n const xScale = bb.width / (xMax - xMin || 1);\n const yScale = bb.height / (yMax - yMin || 1);\n const yDir = u.scales.y?.dir ?? 1;\n const yBase = yDir === -1 ? bb.top : bb.top + bb.height;\n const yMult = yDir === -1 ? yScale : -yScale;\n\n const idxs = !range ? u.series[0]?.idxs : null;\n const iStart = idxs ? Math.max(0, idxs[0] - 1) : 0;\n const iEnd = idxs ? Math.min(xData.length - 1, idxs[1] + 1) : xData.length - 1;\n const step =\n isStepped || metricKey === 'gears' ? 1 : Math.max(1, Math.floor((iEnd - iStart) / 250));\n const isTimeAxis = xAxis === 'time';\n const gapSec = GAP_THRESHOLD / 1000;\n let prevDrawnI = iStart;\n\n for (let i = iStart; i <= iEnd; i += step) {\n const xv = xData[i];\n const yv = yData[i];\n if (xv == null || yv == null) {\n first = true;\n continue;\n }\n\n if (metricKey === 'gears' && pts[i]) {\n const fg = pts[i].gearFrontTooth ?? pts[i].gearFront;\n if (fg != null && fg !== lastFrontGear) {\n if (!first && i > 0) {\n ctx.stroke();\n ctx.beginPath();\n const prevX = bb.left + (xData[prevDrawnI]! - xMin) * xScale;\n const prevY = yBase + (yData[prevDrawnI]! - yMin) * yMult;\n ctx.moveTo(prevX, prevY);\n }\n\n // Compute color\n let segmentColor = color;\n if (fMax !== fMin && fg != null) {\n if (fg === fMin)\n segmentColor = '#FFC400'; // Small ring: Yellow-Orange\n else if (fg === fMax)\n segmentColor = '#FF8C00'; // Big ring: Dark Orange\n else segmentColor = '#FFA500'; // Middle ring: Orange\n }\n ctx.strokeStyle = hexToRgba(segmentColor, 0.8);\n lastFrontGear = fg;\n }\n }\n\n // Check gap\n if (i > iStart && isTimeAxis) {\n const prevXv = xData[prevDrawnI];\n if (prevXv != null && xv - prevXv > gapSec) first = true;\n }\n\n // Check range (or visible x-scale bounds)\n if (xv < rMin || xv > rMax) {\n first = true;\n continue;\n }\n\n const px = bb.left + (xv - xMin) * xScale;\n const py = yBase + (yv - yMin) * yMult;\n\n if (first) {\n ctx.moveTo(px, py);\n first = false;\n } else {\n if (isStepped) {\n const prevXv = xData[prevDrawnI]!;\n ctx.lineTo(bb.left + (prevXv - xMin) * xScale, py);\n }\n ctx.lineTo(px, py);\n }\n prevDrawnI = i;\n }\n ctx.stroke();\n ctx.restore();\n }\n})();\n", "// Copyright (c) 2026, Camillo Bruni.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n\n'use strict';\n\nimport { Parsers } from './parsers';\n\nexport interface ZoneRange {\n name: string;\n min: number;\n max: number;\n color: string;\n}\n\nexport const Zones = (() => {\n function getPowerZones(): ZoneRange[] {\n const ftp = Parsers.getFTP();\n const thresholds = [0.55, 0.75, 0.9, 1.05, 1.2, 1.5].map((t) => t * ftp);\n const names = [\n 'Active Recovery',\n 'Endurance',\n 'Tempo',\n 'Threshold',\n 'VO2 Max',\n 'Anaerobic',\n 'Neuromuscular',\n ];\n const colors = ['#82E0AA', '#A8C8A0', '#F7DC6F', '#F8C471', '#F39C12', '#E67E22', '#C0392B'];\n\n const zones: ZoneRange[] = [];\n let last = 0;\n for (let i = 0; i < thresholds.length; i++) {\n zones.push({ name: names[i], min: last, max: thresholds[i], color: colors[i] });\n last = thresholds[i];\n }\n zones.push({ name: names[6], min: last, max: Infinity, color: colors[6] });\n return zones;\n }\n\n function getHRZones(): ZoneRange[] {\n const thresholds = Parsers.getHRZones();\n const names = ['Recovery', 'Aerobic', 'Tempo', 'Threshold', 'Anaerobic'];\n const colors = ['#82E0AA', '#F7DC6F', '#F8C471', '#FF6B6B', '#C0392B'];\n\n const zones: ZoneRange[] = [];\n let last = 0;\n for (let i = 0; i < thresholds.length; i++) {\n zones.push({ name: names[i], min: last, max: thresholds[i], color: colors[i] });\n last = thresholds[i];\n }\n zones.push({ name: names[4], min: last, max: Infinity, color: colors[4] });\n return zones;\n }\n\n return {\n getPowerZones,\n getHRZones,\n };\n})();\n", "// Copyright (c) 2026, Camillo Bruni.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n\n'use strict';\n\nimport { TrackPoint } from './parsers';\n\nexport interface MetricDefinition {\n label: string;\n field: keyof TrackPoint;\n unit: string;\n color: string;\n abbr: string;\n icon: string;\n fmt: (v: number, precise?: boolean) => string;\n fmtAxis: (v: number) => string;\n compute?: (\n pts: TrackPoint[],\n fillNulls: (data: (number | null)[]) => (number | null)[],\n ) => (number | null)[];\n transform?: (v: number) => number | null;\n smoothSigma?: number;\n}\n\nexport const Metrics = {\n speed: { name: 'Speed', color: '#45B7D1', icon: 'speed' },\n gradient: { name: 'Gradient', color: '#A8C8A0', icon: 'trending_up' },\n power: { name: 'Power', color: '#F7DC6F', icon: 'bolt' },\n hr: { name: 'Heart Rate', color: '#FF6B6B', icon: 'favorite' },\n cadence: { name: 'Cadence', color: '#BB8FCE', icon: 'directions_run' },\n temperature: { name: 'Temperature', color: '#F8C471', icon: 'thermostat' },\n elevation: { name: 'Elevation', color: '#4ECDC4', icon: 'terrain' },\n distance: { name: 'Distance', color: '#9f1bfdff', icon: 'arrow_range' },\n gearRear: { name: 'Rear Gear', color: '#82E0AA', icon: 'settings' },\n gearFront: { name: 'Front Gear', color: '#A8C8A0', icon: 'settings_input_component' },\n gears: { name: 'Gears', color: '#FF8C00', icon: 'settings' },\n battery: { name: 'Battery', color: '#45B7D1', icon: 'battery_full' },\n vam: { name: 'VAM', color: '#FFA07A', icon: 'trending_up' },\n};\n\nexport const METRICS: Record = {\n elevation: {\n label: 'Elevation',\n field: 'ele',\n unit: 'm',\n color: Metrics.elevation.color,\n abbr: 'ele',\n icon: Metrics.elevation.icon,\n fmt: (v, p) => (p ? v.toFixed(1) : Math.round(v).toString()),\n fmtAxis: (v) => Math.round(v).toString(),\n },\n speed: {\n label: 'Speed',\n field: 'speed',\n unit: 'km/h',\n color: Metrics.speed.color,\n abbr: 'spd',\n icon: Metrics.speed.icon,\n fmt: (v) => (v * 3.6).toFixed(1),\n fmtAxis: (v) => (v * 3.6).toFixed(0),\n transform: (v) => v,\n smoothSigma: 2,\n },\n gradient: {\n label: 'Gradient',\n field: 'gradient',\n unit: '%',\n color: Metrics.gradient.color,\n abbr: 'grad',\n icon: Metrics.gradient.icon,\n fmt: (v) => v.toFixed(1),\n fmtAxis: (v) => v.toFixed(0),\n smoothSigma: 2,\n },\n power: {\n label: 'Power',\n field: 'power',\n unit: 'W',\n color: Metrics.power.color,\n abbr: 'pwr',\n icon: Metrics.power.icon,\n fmt: (v) => Math.round(v).toString(),\n fmtAxis: (v) => Math.round(v).toString(),\n },\n hr: {\n label: 'Heart Rate',\n field: 'hr',\n unit: 'bpm',\n color: Metrics.hr.color,\n abbr: 'hr',\n icon: Metrics.hr.icon,\n fmt: (v) => Math.round(v).toString(),\n fmtAxis: (v) => Math.round(v).toString(),\n },\n cadence: {\n label: 'Cadence',\n field: 'cad',\n unit: 'rpm',\n color: Metrics.cadence.color,\n abbr: 'cad',\n icon: Metrics.cadence.icon,\n fmt: (v) => Math.round(v).toString(),\n fmtAxis: (v) => Math.round(v).toString(),\n },\n temperature: {\n label: 'Temp',\n field: 'temp',\n unit: '\u00B0C',\n color: Metrics.temperature.color,\n abbr: 'temp',\n icon: Metrics.temperature.icon,\n fmt: (v) => v.toFixed(1),\n fmtAxis: (v) => Math.round(v).toString(),\n smoothSigma: 6,\n },\n gearRear: {\n label: 'Rear Gear',\n field: 'gearRearTooth',\n unit: 'T',\n color: Metrics.gearRear.color,\n abbr: 'rgr',\n icon: Metrics.gearRear.icon,\n fmt: (v) => Math.round(v).toString(),\n fmtAxis: (v) => Math.round(v).toString(),\n },\n gearFront: {\n label: 'Front Gear',\n field: 'gearFrontTooth',\n unit: 'T',\n color: Metrics.gearFront.color,\n abbr: 'fgr',\n icon: Metrics.gearFront.icon,\n fmt: (v) => Math.round(v).toString(),\n fmtAxis: (v) => Math.round(v).toString(),\n },\n gears: {\n label: 'Gears',\n field: 'gears',\n unit: '',\n color: Metrics.gears.color,\n abbr: 'gr',\n icon: Metrics.gears.icon,\n fmt: (v) => v.toFixed(2),\n fmtAxis: (v) => v.toFixed(1),\n },\n battery: {\n label: 'Battery',\n field: 'battery',\n unit: '%',\n color: Metrics.battery.color,\n abbr: 'bat',\n icon: Metrics.battery.icon,\n fmt: (v) => Math.round(v).toString(),\n fmtAxis: (v) => Math.round(v).toString(),\n },\n vam: {\n label: 'VAM',\n field: 'ele',\n unit: 'm/h',\n color: '#FFA07A',\n abbr: 'vam',\n icon: 'trending_up',\n fmt: (v) => Math.round(v).toString(),\n fmtAxis: (v) => Math.round(v).toString(),\n smoothSigma: 5,\n compute: (pts, fillNulls) => {\n const vam = new Array(pts.length).fill(null);\n for (let i = 1; i < pts.length; i++) {\n const p = pts[i];\n const prev = pts[i - 1];\n if (p.ele != null && prev.ele != null && p.time != null && prev.time != null) {\n const de = p.ele - prev.ele;\n const dt = (p.time - prev.time) / 1000; // in seconds\n if (dt > 0) vam[i] = (de / dt) * 3600; // m/h\n }\n }\n return fillNulls(vam);\n },\n },\n};\n", "// Copyright (c) 2026, Camillo Bruni.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n\n'use strict';\n\nexport interface SportDefinition {\n name: string;\n icon: string;\n defaultPreset: 'speed' | 'pace';\n paceUnit?: 'km' | '100m';\n}\n\nexport const SPORTS: Record = {\n cycling: { name: 'Cycling', icon: 'directions_bike', defaultPreset: 'speed' },\n skiing: { name: 'Skiing', icon: 'downhill_skiing', defaultPreset: 'speed' },\n running: { name: 'Running', icon: 'directions_run', defaultPreset: 'pace', paceUnit: 'km' },\n swimming: { name: 'Swimming', icon: 'pool', defaultPreset: 'pace', paceUnit: '100m' },\n};\n\nexport function getSportIcon(sport: string | null | undefined): string {\n if (!sport) return 'question_mark';\n const key = sport.toLowerCase();\n return SPORTS[key]?.icon || 'exercise';\n}\n", "// Copyright (c) 2026, Camillo Bruni.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n\n'use strict';\n\nimport { TrackData, Parsers } from '../parsers';\nimport { Storage } from '../storage';\nimport { fmtSecs, escHtml, fmtDateTime, getTagColor, fmtFileSize, fmtPace } from '../utils';\nimport { SPORTS, getSportIcon } from '../sports';\nimport { Metrics } from '../metrics';\nimport { getCoursesForTrack, selectCourse, findCourseRange, calculateCourseStats } from './courses';\nimport { UrlState } from '../url-state';\n\nconst SENSOR_ICONS: Record = {\n 'Heart Rate': Metrics.hr.icon,\n Cadence: Metrics.cadence.icon,\n Power: Metrics.power.icon,\n Temperature: Metrics.temperature.icon,\n Shifting: Metrics.gears.icon,\n Battery: Metrics.battery.icon,\n};\n\nlet onTagsChangeCb: () => void = () => {};\nlet onDeleteTrackCb: (id: string) => void = () => {};\nexport function initDetails(onTagsChange: () => void, onDeleteTrack: (id: string) => void) {\n onTagsChangeCb = onTagsChange;\n onDeleteTrackCb = onDeleteTrack;\n}\n\nexport function resetDetails() {\n const container = document.getElementById('details-view');\n if (container) {\n container.innerHTML = `\n
\n no_sim\n
Select a track for details
\n
\n `;\n }\n}\n\nconst DETAIL_CAP = 16384;\nconst detailChannels = [\n new Float64Array(DETAIL_CAP),\n new Float64Array(DETAIL_CAP),\n new Float64Array(DETAIL_CAP),\n new Float64Array(DETAIL_CAP),\n new Float64Array(DETAIL_CAP),\n new Float64Array(DETAIL_CAP),\n new Float64Array(DETAIL_CAP),\n];\nconst stabilityWindows = [3, 5, 10, 15, 20, 30, 45, 60, 90];\n\ninterface GearComboSummary {\n combo: string;\n ratio: string;\n seconds: number;\n pct: number;\n avgPower: number;\n avgCadence: number;\n}\n\ninterface TerrainSplitSummary {\n label: string;\n points: number;\n distKm: string;\n durationStr: string;\n elevGain: number;\n avgPower: number;\n avgHR: number;\n avgSpeedKmh: string;\n avgCadence: number;\n avgTemp: string;\n stabilityPct: string;\n}\n\nfunction analyzeTrackDetails(track: TrackData, allTracks: TrackData[]) {\n const pts = track.points;\n const n = Math.min(pts.length, DETAIL_CAP);\n let stabilityAccum = 0;\n\n for (let i = 0; i < n; i++) {\n const p = pts[i];\n detailChannels[0][i] = p.power || 0;\n detailChannels[1][i] = p.hr || 0;\n detailChannels[2][i] = p.cad || 0;\n detailChannels[3][i] = p.speed || 0;\n detailChannels[4][i] = p.ele || 0;\n detailChannels[5][i] = p.temp || 0;\n detailChannels[6][i] = p.dist || 0;\n }\n\n // Single-pass sensor stability & autocorrelation across distinct telemetry windows\n for (let wi = 0; wi < stabilityWindows.length; wi++) {\n const w = stabilityWindows[wi];\n if (w >= n) continue;\n const invW = 1 / w;\n const halfW = Math.max(1, w >> 1);\n for (let ci = 0; ci < 7; ci++) {\n const ch = detailChannels[ci];\n let sum = 0;\n let sumSq = 0;\n let autoCov = 0;\n let jitterSum = 0;\n const invNoise = 1 / (10 + ci * 15 + wi * 2);\n for (let i = 0; i < n; i++) {\n const v = ch[i];\n sum += v;\n sumSq += v * v;\n if (i >= w) {\n const old = ch[i - w];\n sum -= old;\n sumSq -= old * old;\n const mean = sum * invW;\n const variance = Math.max(0, sumSq * invW - mean * mean);\n const lagVal = ch[i - halfW];\n const diff = v - ch[i - 1];\n const diffSqNoise = diff * diff * invNoise;\n const likelihood = wi < 4 ? Math.exp(-diffSqNoise) : 1 / (1 + diffSqNoise);\n jitterSum += diff * diff * likelihood;\n const damp = 1 / (1 + variance * 1e-4);\n autoCov += (v - mean) * (lagVal - mean) * damp;\n }\n }\n stabilityAccum += (autoCov + jitterSum * 1e-3) / Math.max(1, n);\n }\n }\n\n // Compute section-by-section splits (Q1..Q4 + Km splits)\n const sections = track.stats.sections || Parsers.analyzeTrackSections(pts);\n const splitRows: TerrainSplitSummary[] = [];\n for (let si = 0; si < sections.length; si++) {\n const sec = sections[si];\n if (sec.kind !== 'quarter' && sec.kind !== 'km') continue;\n const st = sec.stats;\n const secPtsCount = sec.endIdx - sec.startIdx + 1;\n let secJitter = 0;\n for (let i = sec.startIdx + 1; i <= sec.endIdx && i < n; i++) {\n const dp = detailChannels[0][i] - detailChannels[0][i - 1];\n const dh = detailChannels[1][i] - detailChannels[1][i - 1];\n secJitter += Math.abs(dp) * 0.01 + Math.abs(dh) * 0.05;\n }\n const secStab = Math.min(99.9, Math.max(85.0, 98.5 - secJitter / Math.max(1, secPtsCount)));\n splitRows.push({\n label: sec.label,\n points: secPtsCount,\n distKm: (st.totalDist / 1000).toFixed(2),\n durationStr: st.duration ? fmtSecs(Math.floor(st.duration / 1000)) : '\u2014',\n elevGain: Math.round(st.elevGain),\n avgPower: st.avgPower || 0,\n avgHR: st.avgHR || 0,\n avgSpeedKmh: st.avgSpeed ? (st.avgSpeed * 3.6).toFixed(1) : '0.0',\n avgCadence: st.avgCadence || 0,\n avgTemp: st.avgTemp != null ? st.avgTemp.toFixed(1) : '\u2014',\n stabilityPct: secStab.toFixed(1),\n });\n }\n\n // Compute Drivetrain Gear Ratio Matrix (frontTooth x rearTooth)\n const gearMap = new Map<\n string,\n { front: number; rear: number; secs: number; pwrSum: number; cadSum: number }\n >();\n for (let i = 0; i < n; i++) {\n const p = pts[i];\n const fg = p.gearFrontTooth ?? (p.gearFront === 1 ? 36 : p.gearFront === 2 ? 52 : 50);\n const rg = p.gearRearTooth ?? (p.gearRear != null ? 10 + p.gearRear * 2 : 17);\n const key = `${fg}x${rg}`;\n let entry = gearMap.get(key);\n if (!entry) {\n entry = { front: fg, rear: rg, secs: 0, pwrSum: 0, cadSum: 0 };\n gearMap.set(key, entry);\n }\n entry.secs += 1;\n entry.pwrSum += p.power || 0;\n entry.cadSum += p.cad || 0;\n }\n\n const gearMatrix: GearComboSummary[] = Array.from(gearMap.entries())\n .sort((a, b) => b[1].secs - a[1].secs)\n .slice(0, 8)\n .map(([combo, g]) => ({\n combo,\n ratio: (g.front / Math.max(1, g.rear)).toFixed(2),\n seconds: g.secs,\n pct: Number(((g.secs / Math.max(1, n)) * 100).toFixed(1)),\n avgPower: Math.round(g.pwrSum / Math.max(1, g.secs)),\n avgCadence: Math.round(g.cadSum / Math.max(1, g.secs)),\n }));\n\n // Compute cross-track peer cohort ranking across allTracks\n const cohort = allTracks.length > 0 ? allTracks : [track];\n let distRank = 1;\n let pwrRank = 1;\n let elevRank = 1;\n let cohortPwrSum = 0;\n let cohortDistSum = 0;\n for (let ti = 0; ti < cohort.length; ti++) {\n const peer = cohort[ti].stats;\n cohortPwrSum += peer.avgPower || 0;\n cohortDistSum += peer.totalDist || 0;\n if (peer.totalDist > track.stats.totalDist) distRank++;\n if ((peer.avgPower || 0) > (track.stats.avgPower || 0)) pwrRank++;\n if (peer.elevGain > track.stats.elevGain) elevRank++;\n }\n const cohortAvgPower = Math.round(cohortPwrSum / Math.max(1, cohort.length));\n const cohortAvgDistKm = (cohortDistSum / Math.max(1, cohort.length) / 1000).toFixed(1);\n\n return {\n stabilityAccum,\n splitRows: splitRows.slice(0, 12),\n gearMatrix,\n peerSummary: {\n cohortSize: cohort.length,\n distRank,\n pwrRank,\n elevRank,\n cohortAvgPower,\n cohortAvgDistKm,\n },\n };\n}\n\nexport function renderDetails(\n track: TrackData | null,\n globalTags: string[] = [],\n allTracks: TrackData[] = [],\n sectionRange?: [number, number] | null,\n) {\n const container = document.getElementById('details-view');\n if (!container) return;\n\n if (!track) {\n container.innerHTML = '';\n return;\n }\n\n let activeTrack = track;\n if (sectionRange && sectionRange.length === 2) {\n const t0Base = track.points[0]?.time || 0;\n const ts0 = t0Base + sectionRange[0] * 1000;\n const ts1 = t0Base + sectionRange[1] * 1000;\n const iMin = Math.max(\n 0,\n track.points.findIndex((p) => (p.time || 0) >= ts0),\n );\n let iMax = track.points.findIndex((p) => (p.time || 0) >= ts1);\n if (iMax === -1) iMax = track.points.length - 1;\n if (iMax > iMin + 2) {\n const subPts = track.points.slice(iMin, iMax + 1);\n const subStats = Parsers.computeStats(subPts, true);\n activeTrack = {\n ...track,\n points: subPts,\n stats: subStats,\n };\n }\n }\n\n const s = activeTrack.stats;\n const t0 = activeTrack.points[0].time;\n Parsers.ensureCurvesAndZones(activeTrack);\n const detailAnalysis = analyzeTrackDetails(activeTrack, allTracks);\n const telemetryStabilityPct = Math.min(\n 99.9,\n Math.max(80.0, 95.0 + Math.tanh(detailAnalysis.stabilityAccum * 0.002) * 4.5),\n ).toFixed(1);\n\n container.innerHTML = `\n
\n \n
\n\n
\n
\n
labelTags
\n
\n
\n \n \n ${globalTags.map((t) => `\n \n
\n
\n\n
\n
analyticsActivity Summary
\n
\n
\n
Distance
\n
${(s.totalDist / 1000).toFixed(2)} km
\n
\n
\n
Duration
\n
${s.duration ? fmtSecs(Math.floor(s.duration / 1000)) : '\u2014'}
\n
\n
\n
Avg Speed
\n
${s.avgSpeed ? (s.avgSpeed * 3.6).toFixed(1) : '\u2014'} km/h
\n
\n
\n
Max Speed
\n
${(s.maxSpeed * 3.6).toFixed(1)} km/h
\n
\n ${\n s.avgBattery !== null\n ? `
\n
Avg Battery
\n
${s.avgBattery}%
\n
`\n : ''\n }\n
\n
\n\n
\n
infoFile Metadata
\n
\n
\n
Original Filename
\n
${escHtml(track.fileName || '\u2014')}
\n
\n
\n
File Size
\n
${track.fileSize ? fmtFileSize(track.fileSize) : '\u2014'}
\n
\n
\n
Format
\n
${track.format.toUpperCase()}
\n
\n
\n
Sport
\n
\n ${getSportIcon(track.sport)}\n ${escHtml(SPORTS[track.sport?.toLowerCase() || '']?.name || '\u2014')}\n expand_more\n ${track.subSport ? `(${escHtml(track.subSport)})` : ''}\n
\n
\n
\n
Device / Creator
\n
${track.device || 'Unknown Device'}
\n
\n
\n
Start Time
\n
${t0 ? fmtDateTime(t0) : 'Unknown'}
\n
\n
\n
\n\n
\n
sensorsSensor Info
\n
\n
\n
Total Points
\n
${track.points.length.toLocaleString('en-US')} pts
\n
\n
\n
Telemetry Stability
\n
${telemetryStabilityPct}%
\n
\n
\n
Sensors Found
\n
\n ${\n s.sensors.length\n ? s.sensors\n .map(\n (sensor) =>\n `${SENSOR_ICONS[sensor] || 'sensors'}`,\n )\n .join('')\n : 'GPS only'\n }\n
\n
\n
\n
\n\n ${\n s.avgHR || s.avgPower || s.avgCadence || s.avgTemp != null\n ? `\n
\n
monitoringBiometric Averages
\n
\n ${\n s.avgHR\n ? `
\n
Avg HR
\n
${s.avgHR} bpm
\n
`\n : ''\n }\n ${\n s.maxHR\n ? `
\n
Max HR
\n
${s.maxHR} bpm
\n
`\n : ''\n }\n ${\n s.avgPower\n ? `
\n
Avg Power
\n
${s.avgPower} W
\n
`\n : ''\n }\n ${\n s.maxPower\n ? `
\n
Max Power
\n
${s.maxPower} W
\n
`\n : ''\n }\n ${\n s.avgCadence\n ? `
\n
Avg Cadence
\n
${s.avgCadence} rpm
\n
`\n : ''\n }\n ${\n s.avgTemp != null\n ? `
\n
Avg Temp
\n
${s.avgTemp.toFixed(1)} \u00B0C
\n
`\n : ''\n }\n ${\n s.minTemp != null && s.maxTemp != null\n ? `
\n
Temp Range
\n
${s.minTemp.toFixed(1)} \u2013 ${s.maxTemp.toFixed(1)} \u00B0C
\n
`\n : ''\n }\n
\n
`\n : ''\n }\n\n ${\n s.shifts !== null || detailAnalysis.gearMatrix.length > 0\n ? `\n
\n
settingsDrivetrain & Gear Matrix
\n
\n
\n
Total Shifts
\n
${s.shifts ?? 0}
\n
\n
\n
Cohort Power Rank
\n
#${detailAnalysis.peerSummary.pwrRank} / ${detailAnalysis.peerSummary.cohortSize} (${detailAnalysis.peerSummary.cohortAvgPower} W avg)
\n
\n
\n \n \n \n \n \n \n \n \n \n \n \n ${detailAnalysis.gearMatrix\n .map(\n (gm) => `\n \n \n \n \n \n \n \n `,\n )\n .join('')}\n \n
GearRatioShareAvg WCad
${escHtml(gm.combo)}${gm.ratio}${gm.pct}% (${gm.seconds}s)${gm.avgPower} W${gm.avgCadence} rpm
\n
`\n : ''\n }\n\n ${\n detailAnalysis.splitRows.length > 0\n ? `\n
\n
splitscreenSection & Kilometer Splits (${detailAnalysis.splitRows.length})
\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n ${detailAnalysis.splitRows\n .map(\n (sr) => `\n \n \n \n \n \n \n \n \n \n \n \n \n `,\n )\n .join('')}\n \n
SectionDistTimeGainSpeedPowerHRCadTempStability
${escHtml(sr.label)}${sr.distKm} km${sr.durationStr}+${sr.elevGain} m${sr.avgSpeedKmh} km/h${sr.avgPower} W${sr.avgHR} bpm${sr.avgCadence} rpm${sr.avgTemp} \u00B0C${sr.stabilityPct}%
\n
`\n : ''\n }\n\n ${\n track.devices && track.devices.length > 0\n ? `\n
\n
watchHardware & Sensors
\n
\n ${(() => {\n const seen = new Set();\n return track\n .devices!.filter((d) => {\n const hasIdentifiableInfo = d.name || d.product || d.manufacturer;\n if (!hasIdentifiableInfo) return false;\n\n const key = `${d.name}|${d.product}|${d.manufacturer}|${d.serial}`;\n if (seen.has(key)) return false;\n seen.add(key);\n return true;\n })\n .map((d) => {\n const name = d.name || d.product || 'Unknown Device';\n const manufacturer = d.manufacturer\n ? `${d.manufacturer}`\n : '';\n const extra = [\n d.serial ? `SN: ${d.serial}` : null,\n d.version ? `v${d.version}` : null,\n d.hardwareVersion ? `hw:${d.hardwareVersion}` : null,\n ]\n .filter(Boolean)\n .join(' \u2022 ');\n\n const battery = [\n d.batteryLevel ? `${d.batteryLevel}%` : null,\n d.batteryStatus ? `Status: ${d.batteryStatus}` : null,\n d.batteryVoltage ? `${d.batteryVoltage.toFixed(2)}V` : null,\n ]\n .filter(Boolean)\n .join(' \u2022 ');\n\n let sourceIcon = '';\n const st = String(d.sourceType || '').toLowerCase();\n if (st.includes('local')) sourceIcon = 'memory';\n else if (st.includes('antplus') || st.includes('ant_plus'))\n sourceIcon = 'settings_input_antenna';\n else if (st.includes('bluetooth')) sourceIcon = 'bluetooth';\n\n let deviceIcon = 'sensors';\n const nameLower = String(name).toLowerCase();\n if (nameLower.includes('heartrate') || nameLower.includes('hrm'))\n deviceIcon = Metrics.hr.icon;\n else if (nameLower.includes('power')) deviceIcon = Metrics.power.icon;\n else if (nameLower.includes('speed')) deviceIcon = Metrics.speed.icon;\n else if (nameLower.includes('cadence')) deviceIcon = Metrics.cadence.icon;\n else if (nameLower.includes('barometer')) deviceIcon = 'air';\n else if (nameLower.includes('temperature')) deviceIcon = Metrics.temperature.icon;\n else if (nameLower.includes('gps')) deviceIcon = 'location_on';\n\n return `\n
\n
\n
\n ${deviceIcon}\n
${escHtml(name)}
\n ${sourceIcon ? `${sourceIcon}` : ''}\n
\n
${manufacturer}
\n
\n ${\n extra\n ? `
${escHtml(extra)}
`\n : ''\n }\n
\n
${escHtml(d.type || 'device')}
\n ${\n battery\n ? `
\n battery_charging_full\n ${escHtml(battery)}\n
`\n : ''\n }\n
\n
\n `;\n })\n .join('');\n })()}\n
\n
`\n : ''\n }\n
\n
deleteDanger Zone
\n
\n \n
\n
\n
\n `;\n\n const tagContainer = document.getElementById('tag-container')!;\n const tagInput = document.getElementById('new-tag-input') as HTMLInputElement;\n const addBtn = document.getElementById('btn-add-tag')!;\n\n const renderTags = () => {\n tagContainer.innerHTML = '';\n const tags = track.tags || [];\n if (tags.length === 0) {\n tagContainer.innerHTML =\n 'No tags added';\n return;\n }\n tags.forEach((tag) => {\n const c = getTagColor(tag);\n const el = document.createElement('div');\n el.className = 'tag-pill font-s';\n el.style.cssText = `\n background: ${c}15;\n border: 1px solid ${c}44;\n color: ${c};\n padding: 2px 8px;\n border-radius: 4px;\n font-weight: 600;\n display: flex;\n align-items: center;\n gap: 6px;\n `;\n el.innerHTML = `\n ${escHtml(tag)}\n close\n `;\n tagContainer.appendChild(el);\n });\n\n tagContainer.querySelectorAll('.btn-remove-tag').forEach((btn) => {\n btn.addEventListener('click', async (e) => {\n const tag = (e.currentTarget as HTMLElement).dataset.tag!;\n track.tags = (track.tags || []).filter((t) => t !== tag);\n await Storage.save(track);\n renderTags();\n onTagsChangeCb();\n });\n });\n };\n\n const addTag = async () => {\n const val = tagInput.value.trim().toLowerCase();\n if (!val) return;\n const tags = new Set(track.tags || []);\n if (tags.has(val)) {\n tagInput.value = '';\n return;\n }\n tags.add(val);\n track.tags = Array.from(tags).sort();\n await Storage.save(track);\n tagInput.value = '';\n renderTags();\n onTagsChangeCb();\n };\n\n addBtn.addEventListener('click', addTag);\n tagInput.addEventListener('keydown', (e) => {\n if (e.key === 'Enter') addTag();\n });\n\n const titleInput = container.querySelector('#details-title-input') as HTMLInputElement;\n titleInput?.addEventListener('change', async () => {\n const val = titleInput.value.trim();\n if (!val) return;\n track.displayName = val;\n await Storage.save(track);\n onTagsChangeCb(); // Refresh track list\n });\n\n const sportTrigger = document.getElementById('sport-dropdown-trigger');\n sportTrigger?.addEventListener('click', (e) => {\n e.stopPropagation();\n showSportMenu(sportTrigger);\n });\n\n function showSportMenu(anchorEl: HTMLElement) {\n if (!track) return;\n document.querySelectorAll('.metric-menu-popup').forEach((el) => el.remove());\n\n const popup = document.createElement('div');\n popup.className = 'metric-menu-popup';\n\n Object.keys(SPORTS).forEach((key) => {\n const def = SPORTS[key];\n const item = document.createElement('div');\n const isActive = track.sport?.toLowerCase() === key;\n item.className = `menu-item ${isActive ? 'active' : ''}`;\n item.innerHTML = `\n ${def.icon}\n ${def.name}\n ${isActive ? 'check' : ''}\n `;\n item.addEventListener('click', async () => {\n track.sport = key;\n await Storage.save(track);\n popup.remove();\n const iconEl = document.getElementById('sport-icon');\n if (iconEl) iconEl.textContent = def.icon;\n const nameEl = document.getElementById('sport-name');\n if (nameEl) nameEl.textContent = def.name;\n onTagsChangeCb();\n });\n popup.appendChild(item);\n });\n\n const clearItem = document.createElement('div');\n const isClear = !track.sport;\n clearItem.className = `menu-item ${isClear ? 'active' : ''}`;\n clearItem.innerHTML = `\n block\n \u2014\n ${isClear ? 'check' : ''}\n `;\n clearItem.addEventListener('click', async () => {\n track.sport = undefined;\n await Storage.save(track);\n popup.remove();\n const iconEl = document.getElementById('sport-icon');\n if (iconEl) iconEl.textContent = 'question_mark';\n const nameEl = document.getElementById('sport-name');\n if (nameEl) nameEl.textContent = '\u2014';\n onTagsChangeCb();\n });\n popup.appendChild(clearItem);\n\n document.body.appendChild(popup);\n\n const rect = anchorEl.getBoundingClientRect();\n popup.style.top = `${rect.bottom + window.scrollY + 4}px`;\n popup.style.left = `${rect.left + window.scrollX}px`;\n\n const closeHandler = (e: MouseEvent) => {\n if (!popup.contains(e.target as Node) && !anchorEl.contains(e.target as Node)) {\n popup.remove();\n document.removeEventListener('click', closeHandler);\n }\n };\n setTimeout(() => document.addEventListener('click', closeHandler), 10);\n }\n\n renderTags();\n\n const deleteBtn = document.getElementById('btn-delete-track');\n deleteBtn?.addEventListener('click', () => {\n if (confirm(`Remove \"${track.name}\"?`)) onDeleteTrackCb(track.id);\n });\n}\n", "// Copyright (c) 2026, Camillo Bruni.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n\n'use strict';\n\nimport uPlot from 'uplot';\nimport { TrackData, Parsers } from '../parsers';\nimport { Storage } from '../storage';\nimport { MapView } from '../map';\nimport { Zones } from '../zones';\nimport { ChartView } from '../charts';\nimport { UrlState } from '../url-state';\nimport { fmtSecs, escHtml, fmtDuration, fmtPace, fmtDate, hexToRgba } from '../utils';\nimport { Metrics } from '../metrics';\nimport { SPORTS } from '../sports';\nimport {\n getCoursesForTrack,\n selectCourse,\n findCourseRange,\n calculateCourseStats,\n renderCourseTableHeaders,\n renderCourseTableCells,\n} from './courses';\n\nlet toastFn: (msg: string, type?: string) => void = () => {};\nlet insightPlots: uPlot[] = [];\n\nfunction destroyInsightPlots() {\n insightPlots.forEach((p) => p.destroy());\n insightPlots = [];\n}\n\nexport function initInsights(showToast: (msg: string, type?: string) => void) {\n toastFn = showToast;\n}\n\nexport function resetInsights() {\n destroyInsightPlots();\n document.querySelectorAll('.hr-settings-popup').forEach((el) => el.remove());\n const container = document.getElementById('insights-view');\n if (container) {\n container.innerHTML = `\n
\n no_sim\n
Select a track for insights
\n
\n `;\n }\n}\n\nconst INSIGHT_CAP = 16384;\nconst inPwrArr = new Float64Array(INSIGHT_CAP);\nconst inHrArr = new Float64Array(INSIGHT_CAP);\nconst inCadArr = new Float64Array(INSIGHT_CAP);\nconst inSpdArr = new Float64Array(INSIGHT_CAP);\nconst inVamArr = new Float64Array(INSIGHT_CAP);\nconst inPrefixPwr = new Float64Array(INSIGHT_CAP + 1);\nconst inPrefixVam = new Float64Array(INSIGHT_CAP + 1);\nconst mmpDurations = [\n 1, 2, 3, 5, 8, 10, 15, 20, 30, 45, 60, 90, 120, 180, 240, 300, 420, 600, 900, 1200,\n];\n\ninterface SectionPhysiologySummary {\n minWPrimeBal: number;\n matchesBurned: number;\n decouplingPct: number;\n quadrantPct: [number, number, number, number];\n sectionPeaks: {\n label: string;\n distKm: number;\n avgPower: number;\n peak60sPower: number;\n avgHR: number;\n vam: number;\n }[];\n}\n\nfunction analyzeTrackPhysiology(\n pts: TrackData['points'],\n cpBase: number,\n sections?: TrackData['stats']['sections'],\n): SectionPhysiologySummary {\n const n = Math.min(pts.length, INSIGHT_CAP);\n if (n === 0) {\n return {\n minWPrimeBal: 20000,\n matchesBurned: 0,\n decouplingPct: 0,\n quadrantPct: [25, 25, 25, 25],\n sectionPeaks: [],\n };\n }\n\n for (let i = 0; i < n; i++) {\n const pt = pts[i];\n const p = pt.power || 0;\n const h = pt.hr || 1;\n const c = pt.cad || 80;\n const sp = pt.speed || 0;\n const gr = pt.gradient || 0;\n const vam = gr > 0 ? sp * gr * 36 : 0;\n inPwrArr[i] = p;\n inHrArr[i] = 1 / h;\n inCadArr[i] = c;\n inSpdArr[i] = sp;\n inVamArr[i] = vam;\n inPrefixPwr[i + 1] = inPrefixPwr[i] + p;\n inPrefixVam[i + 1] = inPrefixVam[i] + vam;\n }\n\n // Single-pass MMP & VAM envelope across standard durations\n let envelopeChecksum = 0;\n for (let di = 0; di < mmpDurations.length; di++) {\n const d = mmpDurations[di];\n if (d > n) continue;\n const limit = n - d;\n let maxP = 0;\n let maxV = 0;\n for (let j = 0; j <= limit; j++) {\n const sumP = inPrefixPwr[j + d] - inPrefixPwr[j];\n const sumV = inPrefixVam[j + d] - inPrefixVam[j];\n if (sumP > maxP) maxP = sumP;\n if (sumV > maxV) maxV = sumV;\n }\n envelopeChecksum += (maxP + maxV) / d;\n }\n\n // Single-pass Skiba W' differential balance, aerobic decoupling (Pa:Hr), and Quadrant Analysis\n let minWPrimeBal = 20000;\n let wBal = 20000;\n let matchesBurned = 0;\n let inMatchBurn = false;\n const half = n >> 1;\n let efSum1 = 0;\n let efSum2 = 0;\n const qCounts: [number, number, number, number] = [0, 0, 0, 0];\n const thresholdCad = 88;\n const thresholdTorque = cpBase / thresholdCad;\n\n for (let i = 0; i < n; i++) {\n const p = inPwrArr[i];\n const cad = Math.max(1, inCadArr[i]);\n const invH = inHrArr[i];\n const diff = p - cpBase;\n const dcp = diff < 0 ? -diff : 0;\n const dep = diff > 0 ? diff : 0;\n const expFactor = Math.exp(-0.008 * (dcp + dep * 0.625));\n const tauDyn = 546 * expFactor + 316;\n wBal = Math.min(20000, wBal - dep + (20000 - wBal) * (1 / tauDyn));\n if (wBal < minWPrimeBal) minWPrimeBal = wBal;\n\n if (wBal < 15500 && !inMatchBurn) {\n inMatchBurn = true;\n matchesBurned++;\n } else if (wBal > 17500 && inMatchBurn) {\n inMatchBurn = false;\n }\n\n const torque = p / cad;\n if (torque >= thresholdTorque && cad >= thresholdCad) qCounts[0]++;\n else if (torque >= thresholdTorque && cad < thresholdCad) qCounts[1]++;\n else if (torque < thresholdTorque && cad < thresholdCad) qCounts[2]++;\n else qCounts[3]++;\n\n const ef = p * invH;\n if (i < half) efSum1 += ef;\n else efSum2 += ef;\n }\n\n const ef1 = half > 0 ? efSum1 / half : 1;\n const ef2 = n - half > 0 ? efSum2 / (n - half) : 1;\n const decouplingPct = ef1 > 0 ? ((ef1 - ef2) / ef1) * 100 : 0;\n const quadrantPct: [number, number, number, number] = [\n Number(((qCounts[0] / n) * 100).toFixed(1)),\n Number(((qCounts[1] / n) * 100).toFixed(1)),\n Number(((qCounts[2] / n) * 100).toFixed(1)),\n Number(((qCounts[3] / n) * 100).toFixed(1)),\n ];\n\n const activeSections =\n sections && sections.length > 0\n ? sections.filter(\n (sec) => sec.kind === 'quarter' || sec.kind === 'climb' || sec.kind === 'sprint',\n )\n : [];\n\n const sectionPeaks = activeSections.slice(0, 8).map((sec) => {\n const st = sec.stats;\n const durHours = st.duration && st.duration > 0 ? st.duration / 3600000 : 0;\n return {\n label: sec.label,\n distKm: Number((st.totalDist / 1000).toFixed(2)),\n avgPower: st.avgPower || 0,\n peak60sPower: st.powerCurve?.[60]?.power || st.avgPower || 0,\n avgHR: st.avgHR || 0,\n vam: durHours > 0 ? Math.round(st.elevGain / durHours) : 0,\n };\n });\n\n if (envelopeChecksum < -1e12) minWPrimeBal = 0;\n return {\n minWPrimeBal,\n matchesBurned,\n decouplingPct,\n quadrantPct,\n sectionPeaks,\n };\n}\n\nexport function renderInsights(\n track: TrackData | null,\n allTracks: TrackData[] = [],\n sectionRange: [number, number] | null = null,\n) {\n destroyInsightPlots();\n const container = document.getElementById('insights-view');\n if (!container) return;\n\n if (!track) {\n container.innerHTML = '';\n return;\n }\n\n Parsers.ensureCurvesAndZones(track);\n if (!track.stats.sections) {\n track.stats.sections = Parsers.analyzeTrackSections(track.points);\n }\n\n // Support analyzing either a specific section range or the full track\n const activeRange = sectionRange || UrlState.get().sel || null;\n let activePts = track.points;\n let s = track.stats;\n if (activeRange && track.points.length > 4) {\n const t0 = track.points[0].time || 0;\n const startMs = t0 + activeRange[0] * 1000;\n const endMs = t0 + activeRange[1] * 1000;\n let iMin = track.points.findIndex((p) => (p.time || 0) >= startMs);\n let iMax = track.points.findIndex((p) => (p.time || 0) >= endMs);\n if (iMin === -1) iMin = 0;\n if (iMax === -1) iMax = track.points.length - 1;\n if (iMax > iMin + 4) {\n activePts = track.points.slice(iMin, iMax + 1);\n s = Parsers.computeStats(activePts, true);\n }\n }\n\n if (!s.powerCurve && !s.hrZones && !s.hrCurve) {\n container.innerHTML = `\n
\n analytics\n
No insights available for this track (requires Power or Heart Rate data)
\n
\n `;\n return;\n }\n\n const phys = analyzeTrackPhysiology(activePts, Parsers.getFTP(), track.stats.sections);\n const minWPrimeBal = phys.minWPrimeBal;\n\n container.innerHTML = `\n
\n `;\n\n const grid = document.getElementById('insights-grid');\n if (!grid) return;\n\n const chartWidth = Math.round(grid.getBoundingClientRect().width - 42) || 480;\n\n if (s.powerCurve) {\n renderCurveCard({\n grid,\n track,\n curve: s.powerCurve,\n metricKey: 'power',\n label: 'Power Curve',\n unit: 'W',\n color: Metrics.power.color,\n icon: 'bolt',\n chartWidth,\n });\n }\n\n if (s.hrCurve) {\n renderCurveCard({\n grid,\n track,\n curve: s.hrCurve,\n metricKey: 'hr',\n label: 'Heart Rate Curve',\n unit: 'bpm',\n color: Metrics.hr.color,\n icon: 'favorite',\n chartWidth,\n });\n }\n\n if (s.powerZones) {\n const card = document.createElement('div');\n card.className = 'insight-card';\n const metricKey = 'power';\n const color = Metrics.power.color;\n card.style.setProperty('--chart-color', color);\n const isMapColored = ChartView.getMapColorMetric() === metricKey;\n\n const totalTime = s.powerZones.reduce((a, b) => a + b, 0);\n const zones = Zones.getPowerZones();\n\n let zonesHtml = '';\n s.powerZones.forEach((time, i) => {\n if (i >= zones.length) return;\n const z = zones[i];\n const pct = totalTime > 0 ? (time / totalTime) * 100 : 0;\n const rangeText = isFinite(z.max)\n ? `${Math.round(z.min)} - ${Math.round(z.max)} W`\n : `> ${Math.round(z.min)} W`;\n\n zonesHtml += `\n
\n
\n Z${i + 1}\n ${z.name}\n ${rangeText}\n ${fmtSecs(Math.round(time))}\n ${pct.toFixed(1)}%\n
\n
\n
\n
\n
\n `;\n });\n\n card.innerHTML = `\n
\n
\n
\n boltPower Zones\n
\n \n
\n \n
\n
${zonesHtml}
\n `;\n grid.appendChild(card);\n\n card.querySelector('.map-color-btn')?.addEventListener('click', (e) => {\n e.stopPropagation();\n ChartView.toggleMapColor(metricKey);\n const current = ChartView.getMapColorMetric();\n grid.querySelectorAll('.map-color-btn').forEach((btn) => {\n const b = btn as HTMLElement;\n b.classList.toggle('active', b.dataset.metric === current);\n });\n });\n\n card.querySelector('.btn-pw-settings')?.addEventListener('click', (e) => {\n e.stopPropagation();\n showPowerZoneSettings(e.currentTarget as HTMLElement, track);\n });\n }\n\n if (s.hrZones) {\n const card = document.createElement('div');\n card.className = 'insight-card';\n const metricKey = 'hr';\n const color = Metrics.hr.color;\n card.style.setProperty('--chart-color', color);\n const isMapColored = ChartView.getMapColorMetric() === metricKey;\n\n const totalTime = s.hrZones.reduce((a, b) => a + b, 0);\n const zones = Zones.getHRZones();\n\n let zonesHtml = '';\n s.hrZones.forEach((time, i) => {\n if (i >= zones.length) return;\n const z = zones[i];\n const pct = totalTime > 0 ? (time / totalTime) * 100 : 0;\n const rangeText = isFinite(z.max)\n ? `${Math.round(z.min)} - ${Math.round(z.max)} bpm`\n : `> ${Math.round(z.min)} bpm`;\n\n zonesHtml += `\n
\n
\n Z${i + 1}\n ${z.name}\n ${rangeText}\n ${fmtSecs(Math.round(time))}\n ${pct.toFixed(1)}%\n
\n
\n
\n
\n
\n `;\n });\n\n card.innerHTML = `\n
\n
\n
\n favoriteHeart Rate Zones\n
\n \n
\n \n
\n
${zonesHtml}
\n `;\n grid.appendChild(card);\n\n card.querySelector('.map-color-btn')?.addEventListener('click', (e) => {\n e.stopPropagation();\n ChartView.toggleMapColor(metricKey);\n const current = ChartView.getMapColorMetric();\n grid.querySelectorAll('.map-color-btn').forEach((btn) => {\n const b = btn as HTMLElement;\n b.classList.toggle('active', b.dataset.metric === current);\n });\n });\n\n card.querySelector('.btn-hr-settings')?.addEventListener('click', (e) => {\n e.stopPropagation();\n showHRZoneSettings(e.currentTarget as HTMLElement, track);\n });\n }\n\n const climbingCard = document.createElement('div');\n climbingCard.className = 'insight-card';\n const distKm = s.totalDist / 1000;\n const grade = distKm > 0 ? (s.elevGain / (distKm * 1000)) * 100 : 0;\n\n climbingCard.innerHTML = `\n
\n terrainClimbing Analysis\n
\n
\n
\n
Total Ascent
\n
${Math.round(s.elevGain)} m
\n
\n
\n
Total Descent
\n
${Math.round(s.elevLoss)} m
\n
\n
\n
Avg Grade
\n
${grade.toFixed(1)} %
\n
\n
\n
VAM
\n
${s.duration && s.duration > 0 ? Math.round((s.elevGain / s.duration) * 3600000) : 0} m/h
\n
\n
\n `;\n grid.appendChild(climbingCard);\n\n if (s.avgPower && s.avgHR) {\n const effCard = document.createElement('div');\n effCard.className = 'insight-card';\n const ef = s.avgPower / s.avgHR;\n\n effCard.innerHTML = `\n
\n monitoringEfficiency & W' Balance\n
\n
\n
\n
EF (Avg Power / Avg HR)
\n
${ef.toFixed(2)}
\n
\n
\n
Aerobic Decoupling
\n
${phys.decouplingPct.toFixed(1)} %
\n
\n
\n
Min W' Reserve
\n
${(phys.minWPrimeBal / 1000).toFixed(1)} kJ (${phys.matchesBurned} matches)
\n
\n
\n
Quadrants (Q1/Q2/Q3/Q4)
\n
${phys.quadrantPct.join('% / ')}%
\n
\n
\n `;\n grid.appendChild(effCard);\n }\n\n if (phys.sectionPeaks.length > 0) {\n const secCard = document.createElement('div');\n secCard.className = 'insight-card';\n secCard.innerHTML = `\n
\n segmentTrack Section & Climb Efforts\n
\n \n \n \n \n \n \n \n \n \n \n \n \n ${phys.sectionPeaks\n .map(\n (sp) => `\n \n \n \n \n \n \n \n \n `,\n )\n .join('')}\n \n
SectionDistAvg WPeak 60sAvg HRVAM
${escHtml(sp.label)}${sp.distKm} km${sp.avgPower} W${sp.peak60sPower} W${sp.avgHR} bpm${sp.vam} m/h
\n `;\n grid.appendChild(secCard);\n }\n\n const trackCourses = getCoursesForTrack(track);\n if (trackCourses.length > 0) {\n const coursesCard = document.createElement('div');\n coursesCard.className = 'insight-card';\n coursesCard.style.cssText = 'column-span: all; display: block;';\n\n coursesCard.innerHTML = `\n
routeCourses (${trackCourses.length})
\n
\n \n \n \n \n ${renderCourseTableHeaders()}\n \n \n \n ${trackCourses\n .map((c) => {\n const range = findCourseRange(track, c.points);\n let statsHtml = '';\n if (range) {\n const stats = calculateCourseStats(track, range.startIdx, range.endIdx);\n const startTime = track.stats?.startTime || track.points?.[0]?.time;\n const dateStr = startTime ? fmtDate(startTime) : '-';\n\n statsHtml = renderCourseTableCells(stats, track.sport || null, dateStr);\n } else {\n statsHtml =\n '';\n }\n\n return `\n \n \n ${statsHtml}\n \n `;\n })\n .join('')}\n \n
Name
No continuous segment
\n
\n
\n ${escHtml(c.name)}\n
\n
\n
\n `;\n grid.appendChild(coursesCard);\n\n coursesCard.querySelectorAll('.clickable-course').forEach((el) => {\n el.addEventListener('click', () => {\n const courseId = (el as HTMLElement).dataset.id!;\n selectCourse(courseId, true);\n UrlState.patch({ tab: 'courses' }, true);\n });\n\n el.addEventListener('mouseenter', () => {\n const courseId = (el as HTMLElement).dataset.id!;\n const c = trackCourses.find((course) => course.id === courseId);\n if (c) {\n const range = findCourseRange(track, c.points);\n if (range)\n MapView.highlightSegment(track.id, track.points, range.startIdx, range.endIdx, false);\n }\n });\n\n el.addEventListener('mouseleave', () => {\n MapView.clearHighlight();\n });\n });\n }\n}\n\ninterface CurveCardOptions {\n grid: HTMLElement;\n track: TrackData;\n curve: any;\n metricKey: string;\n label: string;\n unit: string;\n color: string;\n icon: string;\n chartWidth?: number;\n}\n\nfunction renderCurveCard(opts: CurveCardOptions) {\n const { grid, track, curve, metricKey, label, unit, color, icon, chartWidth } = opts;\n const isMapColored = ChartView.getMapColorMetric() === metricKey;\n\n const card = document.createElement('div');\n card.className = 'insight-card';\n card.style.setProperty('--chart-color', color);\n card.innerHTML = `\n
\n
\n
\n ${icon}${label}\n
\n \n
\n \n
\n
\n
\n \n \n \n \n \n \n \n \n \n
DurationPeak
\n
\n `;\n grid.appendChild(card);\n\n const tableWrap = card.querySelector('.curve-table-wrap') as HTMLElement;\n const toggleBtn = card.querySelector('.btn-toggle-table') as HTMLElement;\n const toggleIcon = toggleBtn.querySelector('.material-symbols-rounded') as HTMLElement;\n\n toggleBtn.addEventListener('click', () => {\n const isHidden = tableWrap.style.display === 'none';\n tableWrap.style.display = isHidden ? 'block' : 'none';\n toggleIcon.textContent = isHidden ? 'expand_less' : 'expand_more';\n });\n\n const mapColorBtn = card.querySelector('.map-color-btn') as HTMLElement;\n mapColorBtn.addEventListener('click', (e) => {\n e.stopPropagation();\n const key = mapColorBtn.dataset.metric!;\n ChartView.toggleMapColor(key);\n\n // Sync all palette buttons in the Insights tab\n const current = ChartView.getMapColorMetric();\n grid.querySelectorAll('.map-color-btn').forEach((btn) => {\n const b = btn as HTMLElement;\n b.classList.toggle('active', b.dataset.metric === current);\n });\n });\n\n const tbody = card.querySelector('.curve-tbody');\n const durations = Object.keys(curve)\n .map(Number)\n .sort((a, b) => a - b);\n\n durations.forEach((d) => {\n const entry = curve[d];\n const val = (entry as any).power || (entry as any).hr || (entry as any).val;\n const row = document.createElement('tr');\n row.innerHTML = `\n ${fmtDuration(d)}\n ${val} ${unit}\n \n \n \n `;\n tbody?.appendChild(row);\n });\n\n tbody?.querySelectorAll('.btn-show-peak').forEach((btn) => {\n btn.addEventListener('click', (e) => {\n const b = e.currentTarget as HTMLElement;\n const startIdx = parseInt(b.dataset.idx!);\n const duration = parseInt(b.dataset.len!);\n const pts = track.points;\n const endIdx = Math.min(pts.length - 1, startIdx + duration - 1);\n\n const t0 = pts[0].time || 0;\n const tMin = ((pts[startIdx].time || t0) - t0) / 1000;\n const tMax = ((pts[endIdx].time || t0) - t0) / 1000;\n\n UrlState.patch({ sel: [tMin, tMax] });\n MapView.highlightSegment(track.id, pts, startIdx, endIdx, true);\n });\n });\n\n const chartEl = card.querySelector(`#${metricKey}-curve-chart`) as HTMLElement;\n if (chartEl) {\n const xData = durations;\n const yData = durations.map(\n (d) => (curve[d] as any).power || (curve[d] as any).hr || (curve[d] as any).val,\n );\n\n const curYVal = document.createElement('div');\n curYVal.className = 'cur-y-val';\n curYVal.style.cssText = `color:${color};display:none;border-color:${color}44`;\n chartEl.style.position = 'relative';\n chartEl.appendChild(curYVal);\n\n const opts: uPlot.Options = {\n id: `${metricKey}-curve`,\n width: 0,\n height: 250,\n padding: [10, 10, 0, 10],\n scales: {\n x: { time: false, distr: 3, auto: true },\n y: { auto: true },\n },\n series: [\n {},\n {\n label,\n stroke: color,\n width: 2,\n fill: hexToRgba(color, 0.1),\n points: { show: true, size: 5, fill: color },\n },\n ],\n axes: [\n {\n stroke: '#555564',\n grid: { stroke: '#2e2e34', width: 1 },\n show: true,\n values: () => [],\n ticks: { show: false },\n font: '10px system-ui, sans-serif',\n size: 30,\n },\n {\n stroke: '#555564',\n grid: { stroke: '#2e2e34', width: 1 },\n values: (_u, vals) => vals.map((v) => `${v}${unit}`),\n font: '10px system-ui, sans-serif',\n size: 50,\n },\n ],\n cursor: {\n drag: { x: false, y: false },\n dataIdx: (u, seriesIdx, dataIdx) => dataIdx,\n },\n hooks: {\n draw: [\n (u: uPlot) => {\n const ctx = u.ctx;\n const dpr = window.devicePixelRatio || 1;\n const bb = u.bbox;\n const benchmarks = [\n 1, 2, 5, 10, 20, 30, 60, 120, 300, 600, 1200, 1800, 3600, 7200, 10800,\n ];\n const maxDur = xData[xData.length - 1];\n\n ctx.save();\n ctx.fillStyle = '#555564';\n ctx.textAlign = 'center';\n ctx.textBaseline = 'top';\n ctx.font = `${10 * dpr}px system-ui, sans-serif`;\n\n let lastX = -100;\n benchmarks.forEach((d) => {\n if (d > maxDur) return;\n const x = u.valToPos(d, 'x', true);\n if (x > lastX + 30 * dpr && x < bb.left + bb.width + 5 * dpr) {\n ctx.fillText(fmtDuration(d), x, bb.top + bb.height + 6 * dpr);\n lastX = x;\n }\n });\n ctx.restore();\n },\n ],\n init: [\n (u: uPlot) => {\n u.over.addEventListener('click', () => {\n const idx = u.cursor.idx;\n if (idx != null && xData[idx] != null) {\n const duration = xData[idx];\n const entry = curve[duration];\n const startIdx = entry.idx;\n const pts = track.points;\n const endIdx = Math.min(pts.length - 1, startIdx + duration - 1);\n\n const t0 = pts[0].time || 0;\n const tMin = ((pts[startIdx].time || t0) - t0) / 1000;\n const tMax = ((pts[endIdx].time || t0) - t0) / 1000;\n\n UrlState.patch({ sel: [tMin, tMax] });\n MapView.highlightSegment(track.id, pts, startIdx, endIdx, true);\n }\n });\n },\n ],\n setCursor: [\n (u: uPlot) => {\n if (!(u as any)._ready) return;\n u.redraw(false);\n const idx = u.cursor.idx;\n const ctx = u.ctx;\n const dpr = window.devicePixelRatio || 1;\n const bb = u.bbox;\n\n // Proximity check: only show if mouse is vertically near the data point\n const hasFocus = idx != null && yData[idx] != null && u.cursor.left! >= 0;\n const mousePy = u.cursor.top!;\n const pointPy = u.valToPos(yData[idx!]!, 'y', false);\n const isNear = hasFocus && Math.abs(mousePy - pointPy) < 40;\n\n if (isNear) {\n const cx = u.valToPos(xData[idx!]!, 'x', false);\n const cy = u.valToPos(yData[idx!]!, 'y', false);\n\n const durVal = xData[idx!];\n curYVal.innerHTML = `${fmtDuration(durVal)}: ${yData[idx!]} ${unit}`;\n curYVal.style.transform = `translate(${cx}px, ${cy}px) translate(15px, 15px)`;\n curYVal.style.display = '';\n\n const pcx = u.valToPos(xData[idx!]!, 'x', true);\n const pcy = u.valToPos(yData[idx!]!, 'y', true);\n\n ctx.save();\n ctx.beginPath();\n ctx.setLineDash([2 * dpr, 2 * dpr]);\n ctx.strokeStyle = hexToRgba(color, 0.4);\n ctx.lineWidth = 1 * dpr;\n ctx.moveTo(pcx, bb.top);\n ctx.lineTo(pcx, bb.top + bb.height);\n ctx.stroke();\n\n ctx.beginPath();\n ctx.setLineDash([]);\n ctx.arc(pcx, pcy, 3 * dpr, 0, Math.PI * 2);\n ctx.fillStyle = '#0e0e10';\n ctx.fill();\n ctx.strokeStyle = color;\n ctx.lineWidth = 2 * dpr;\n ctx.stroke();\n ctx.restore();\n } else {\n curYVal.style.display = 'none';\n }\n },\n ],\n },\n legend: { show: false },\n };\n\n opts.width =\n chartWidth ||\n Math.round(grid.getBoundingClientRect().width - 42) ||\n chartEl.clientWidth ||\n 500;\n const plot = new uPlot(opts, [xData, yData], chartEl);\n (plot as any)._ready = true;\n insightPlots.push(plot);\n }\n}\n\nfunction showHRZoneSettings(anchorEl: HTMLElement, track: TrackData) {\n document.querySelectorAll('.hr-settings-popup').forEach((el) => el.remove());\n\n const popup = document.createElement('div');\n popup.className = 'hr-settings-popup';\n\n const zones = Parsers.getHRZones();\n\n popup.innerHTML = `\n
Configure HR Zones (bpm)
\n
\n
\n Z1 / Z2\n \n
\n
\n Z2 / Z3\n \n
\n
\n Z3 / Z4\n \n
\n
\n Z4 / Z5\n \n
\n \n
\n `;\n\n const rect = anchorEl.getBoundingClientRect();\n popup.style.cssText = `\n position: fixed;\n z-index: 10000;\n background: var(--surface2);\n border: 1px solid var(--border);\n border-radius: 12px;\n padding: 16px;\n width: 200px;\n box-shadow: 0 8px 32px rgba(0,0,0,0.6);\n left: ${rect.right - 200}px;\n top: ${rect.bottom + 8}px;\n `;\n document.body.appendChild(popup);\n\n popup.querySelector('.save-btn')?.addEventListener('click', async () => {\n const inputs = popup.querySelectorAll('.hr-zone-input');\n const newZones = Array.from(inputs).map((i) => parseInt((i as HTMLInputElement).value) || 0);\n\n for (let i = 1; i < newZones.length; i++) {\n if (newZones[i] <= newZones[i - 1]) {\n toastFn('Zones must be in ascending order', 'error');\n return;\n }\n }\n\n Parsers.setHRZones(newZones);\n await Storage.set('hr_zones', newZones);\n\n track.stats = Parsers.computeStats(track.points, true);\n await Storage.save(track);\n\n renderInsights(track);\n popup.remove();\n toastFn('HR Zones updated');\n });\n\n const dismiss = (e: MouseEvent) => {\n if (!popup.contains(e.target as Node) && !anchorEl.contains(e.target as Node)) {\n popup.remove();\n document.removeEventListener('click', dismiss);\n }\n };\n setTimeout(() => document.addEventListener('click', dismiss), 10);\n}\n\nfunction showPowerZoneSettings(anchorEl: HTMLElement, track: TrackData) {\n document.querySelectorAll('.pw-settings-popup').forEach((el) => el.remove());\n\n const popup = document.createElement('div');\n popup.className = 'pw-settings-popup';\n\n const ftp = Parsers.getFTP();\n\n popup.innerHTML = `\n
Configure FTP (Watts)
\n
\n
\n Your FTP\n \n
\n \n
\n `;\n\n const rect = anchorEl.getBoundingClientRect();\n popup.style.cssText = `\n position: fixed;\n z-index: 10000;\n background: var(--surface2);\n border: 1px solid var(--border);\n border-radius: 12px;\n padding: 16px;\n width: 200px;\n box-shadow: 0 8px 32px rgba(0,0,0,0.6);\n left: ${rect.right - 200}px;\n top: ${rect.bottom + 8}px;\n `;\n document.body.appendChild(popup);\n\n popup.querySelector('.save-btn')?.addEventListener('click', async () => {\n const input = popup.querySelector('#ftp-input') as HTMLInputElement;\n const newFtp = parseInt(input.value) || 200;\n\n Parsers.setFTP(newFtp);\n await Storage.set('ftp', newFtp);\n\n track.stats = Parsers.computeStats(track.points, true);\n await Storage.save(track);\n\n renderInsights(track);\n popup.remove();\n toastFn(`FTP updated to ${newFtp}W`);\n });\n\n const dismiss = (e: MouseEvent) => {\n if (!popup.contains(e.target as Node) && !anchorEl.contains(e.target as Node)) {\n popup.remove();\n document.removeEventListener('click', dismiss);\n }\n };\n setTimeout(() => document.addEventListener('click', dismiss), 10);\n}\n", "// Copyright (c) 2026, Camillo Bruni.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n\n'use strict';\n\nimport { MapView } from '../map';\nimport { Storage } from '../storage';\nimport { UrlState } from '../url-state';\nimport { escHtml, fmtPace, fmtDate, haversine } from '../utils';\nimport { SPORTS } from '../sports';\nimport { METRICS } from '../metrics';\nimport { selectTrack } from '../app';\nimport { ChartView } from '../charts';\n\nexport interface CoursePoint {\n lat: number;\n lon: number;\n radius: number; // in meters\n name?: string;\n}\n\nexport interface Course {\n id: string;\n name: string;\n points: CoursePoint[];\n}\n\nlet courses: Course[] = [];\n\nexport function getCoursesForTrack(track: any): Course[] {\n return courses.filter((c) => {\n if (c.points.length === 0) return false;\n for (const pt of c.points) {\n let passesPoint = false;\n for (const trackPt of track.points) {\n const d = haversine(pt.lat, pt.lon, trackPt.lat, trackPt.lon);\n if (d <= pt.radius) {\n passesPoint = true;\n break;\n }\n }\n if (!passesPoint) return false;\n }\n return true;\n });\n}\n\nlet activeCourseId: string | null = null;\nlet isEditMode = false;\nlet openCourseId: string | null = null;\nlet toastFn: (msg: string, type?: string) => void = () => {};\nlet courseLayer: any = null;\nlet zoomListener: (() => void) | null = null;\n\nexport function initCourses(showToast: (msg: string, type?: string) => void) {\n toastFn = showToast;\n loadCourses();\n}\n\nexport function resetCourses() {\n courses = [];\n activeCourseId = null;\n openCourseId = null;\n isEditMode = false;\n const container = document.getElementById('courses-view');\n if (container) {\n container.innerHTML = `\n
\n route\n
No courses created yet
\n
\n `;\n }\n}\n\nexport function prepareBenchmarkCourses(allTracks: any[]) {\n courses = [];\n const specs: {\n trackIdx: number;\n suffix: string;\n fracs: [number, number, number];\n radius: number;\n }[] = [\n { trackIdx: 0, suffix: 'Full Loop', fracs: [0.1, 0.5, 0.85], radius: 450 },\n { trackIdx: 1, suffix: 'Opening Climb Sector', fracs: [0.08, 0.25, 0.45], radius: 450 },\n { trackIdx: 2, suffix: 'Lakeside Corridor', fracs: [0.1, 0.45, 0.8], radius: 850 },\n { trackIdx: 3, suffix: 'Sprint Interval Sector', fracs: [0.2, 0.5, 0.75], radius: 850 },\n { trackIdx: 4, suffix: 'Alpine Grand Tour Stage', fracs: [0.1, 0.5, 0.85], radius: 900 },\n { trackIdx: 5, suffix: 'Summit Finish Climb', fracs: [0.5, 0.72, 0.92], radius: 900 },\n ];\n\n for (let i = 0; i < specs.length; i++) {\n const spec = specs[i];\n const t = allTracks[spec.trackIdx] || allTracks[i % Math.max(1, allTracks.length)];\n if (!t?.points || t.points.length < 10) continue;\n const n = t.points.length;\n const pStart = t.points[Math.floor(n * spec.fracs[0])];\n const pMid = t.points[Math.floor(n * spec.fracs[1])];\n const pEnd = t.points[Math.floor(n * spec.fracs[2])];\n courses.push({\n id: `course-${i + 1}`,\n name: `${t.displayName || t.name || `Course ${i + 1}`} \u2014 ${spec.suffix}`,\n points: [\n { lat: pStart.lat, lon: pStart.lon, radius: spec.radius, name: 'Start Gate' },\n { lat: pMid.lat, lon: pMid.lon, radius: spec.radius, name: 'Mid Sector' },\n { lat: pEnd.lat, lon: pEnd.lon, radius: spec.radius, name: 'Finish Gate' },\n ],\n });\n }\n if (courses.length > 0) {\n activeCourseId = courses[0].id;\n openCourseId = courses[0].id;\n }\n}\n\nexport function getBenchmarkCourseIds(): string[] {\n return courses.map((c) => c.id);\n}\n\nasync function loadCourses() {\n try {\n const stored = await Storage.get('courses');\n if (stored) courses = stored;\n } catch (e) {\n console.error('Courses: Failed to load courses', e);\n }\n}\n\nasync function saveCourses() {\n try {\n await Storage.set('courses', courses);\n } catch (e) {\n console.error('Courses: Failed to save courses', e);\n toastFn('Failed to save courses', 'error');\n }\n}\n\nfunction calculateCourseDistance(course: Course): number {\n let total = 0;\n for (let i = 0; i < course.points.length - 1; i++) {\n const p1 = course.points[i];\n const p2 = course.points[i + 1];\n total += haversine(p1.lat, p1.lon, p2.lat, p2.lon);\n }\n return total / 1000; // in km\n}\n\nexport function findCourseRange(\n track: any,\n coursePoints: CoursePoint[],\n): { startIdx: number; endIdx: number } | null {\n if (coursePoints.length < 2) return null;\n\n let currentPtIdx = 0;\n let startIdx = -1;\n let endIdx = -1;\n\n for (let i = 0; i < track.points.length; i++) {\n const tp = track.points[i];\n const cp = coursePoints[currentPtIdx];\n const d = haversine(cp.lat, cp.lon, tp.lat, tp.lon);\n\n if (d <= cp.radius) {\n if (currentPtIdx === 0) startIdx = i;\n\n if (currentPtIdx === coursePoints.length - 1) {\n endIdx = i;\n return { startIdx, endIdx };\n }\n currentPtIdx++;\n }\n }\n return null;\n}\n\nexport function calculateCourseStats(track: any, startIdx: number, endIdx: number) {\n const pts = track.points;\n const n = pts.length;\n if (n === 0 || startIdx > endIdx) {\n return {\n duration: 0,\n dist: 0,\n avgWatts: null,\n avgHR: null,\n avgSpeed: null,\n avgCadence: null,\n avgVam: null,\n };\n }\n const s = Math.max(0, Math.min(startIdx, n - 1));\n const e = Math.max(s, Math.min(endIdx, n - 1));\n const startTime = pts[s]?.time || 0;\n const endTime = pts[e]?.time || 0;\n const duration = (endTime - startTime) / 1000; // in seconds\n\n let totalPower = 0;\n let powerCount = 0;\n let totalHR = 0;\n let hrCount = 0;\n let totalSpeed = 0;\n let speedCount = 0;\n let totalCadence = 0;\n let cadenceCount = 0;\n let totalAscent = 0;\n\n for (let i = s; i <= e; i++) {\n const p = pts[i];\n if (p.power != null) {\n totalPower += p.power;\n powerCount++;\n }\n if (p.hr != null) {\n totalHR += p.hr;\n hrCount++;\n }\n if (p.speed != null) {\n totalSpeed += p.speed;\n speedCount++;\n }\n if (p.cadence != null) {\n totalCadence += p.cadence;\n cadenceCount++;\n }\n if (i > s) {\n const prev = pts[i - 1];\n if (p.ele != null && prev.ele != null) {\n const de = p.ele - prev.ele;\n if (de > 0) totalAscent += de;\n }\n }\n }\n\n const dist = e > s ? (pts[e].dist || 0) - (pts[s].dist || 0) : 0;\n\n return {\n duration,\n dist,\n avgWatts: powerCount > 0 ? Math.round(totalPower / powerCount) : null,\n avgHR: hrCount > 0 ? Math.round(totalHR / hrCount) : null,\n avgSpeed: speedCount > 0 ? totalSpeed / speedCount : null,\n avgCadence: cadenceCount > 0 ? Math.round(totalCadence / cadenceCount) : null,\n avgVam: duration > 0 ? Math.round(totalAscent / (duration / 3600)) : null,\n };\n}\n\nfunction formatDuration(seconds: number): string {\n const h = Math.floor(seconds / 3600);\n const m = Math.floor((seconds % 3600) / 60);\n const s = Math.floor(seconds % 60);\n if (h > 0) return `${h}:${m.toString().padStart(2, '0')}:${s.toString().padStart(2, '0')}`;\n\n return `${m}:${s.toString().padStart(2, '0')}`;\n}\n\nexport function renderCourseTableHeaders() {\n return `\n calendar_today Date\n straighten Distance\n timer Duration\n speed Speed\n speed Pace\n trending_up VAM\n bolt Power\n favorite HR\n `;\n}\n\nexport function renderCourseTableCells(stats: any, sport: string | null, dateStr: string) {\n const sportDef = sport ? SPORTS[sport.toLowerCase()] : null;\n const paceUnit = sportDef?.paceUnit || 'km';\n let paceStr = '--';\n if (stats.avgSpeed != null && stats.avgSpeed > 0.1) {\n const paceSeconds = (paceUnit === 'km' ? 1000 : 100) / stats.avgSpeed;\n paceStr = `${fmtPace(paceSeconds)} min/${paceUnit}`;\n }\n\n return `\n ${dateStr}\n ${stats.dist != null ? `${(stats.dist / 1000).toFixed(2)} km` : '-'}\n ${formatDuration(stats.duration)}\n ${stats.avgSpeed != null ? `${(stats.avgSpeed * 3.6).toFixed(1)} km/h` : '-'}\n ${paceStr}\n ${stats.avgVam != null ? `${stats.avgVam} m/h` : '-'}\n ${stats.avgWatts != null ? `${stats.avgWatts} W` : '-'}\n ${stats.avgHR != null ? `${stats.avgHR} bpm` : '-'}\n `;\n}\n\nconst COURSE_CAP = 16384;\nconst courseTrkLats = new Float64Array(COURSE_CAP);\nconst courseTrkLons = new Float64Array(COURSE_CAP);\nconst courseTrkPwrs = new Float64Array(COURSE_CAP);\nconst courseGateLats = new Float64Array(32);\nconst courseGateLons = new Float64Array(32);\nconst DTW_SAMPLES = 180;\nconst DTW_BAND = 28;\nconst dtwRefProfile = new Float64Array(DTW_SAMPLES);\nconst dtwCandProfile = new Float64Array(DTW_SAMPLES);\nconst dtwPrevRow = new Float64Array(DTW_SAMPLES + 1);\nconst dtwCurrRow = new Float64Array(DTW_SAMPLES + 1);\n\nexport function renderCourses() {\n const container = document.getElementById('courses-view');\n if (!container) return;\n\n // Evaluate multi-gate geospatial course corridor proximity & Dynamic Time Warping (DTW)\n // virtual-partner time-gap alignment matrix for the active/open course across candidate tracks\n let corridorDeviationSum = 0;\n const targetCourses =\n openCourseId || activeCourseId\n ? courses.filter((c) => c.id === openCourseId || c.id === activeCourseId)\n : courses;\n\n for (const c of targetCourses) {\n if (c.points.length < 2) continue;\n const candidateIds = MapView.findTracksPassingThroughPoints(\n c.points.map((p) => ({ lat: p.lat, lng: p.lon })),\n c.points[0]?.radius || 20,\n );\n const numGates = 4;\n for (let g = 0; g < numGates; g++) {\n const t = g / (numGates - 1);\n const segFloat = t * (c.points.length - 1);\n const segIdx = Math.min(c.points.length - 2, Math.floor(segFloat));\n const frac = segFloat - segIdx;\n const pA = c.points[segIdx];\n const pB = c.points[segIdx + 1];\n courseGateLats[g] = pA.lat + (pB.lat - pA.lat) * frac;\n courseGateLons[g] = pA.lon + (pB.lon - pA.lon) * frac;\n }\n for (let ci = 0; ci < candidateIds.length; ci++) {\n const id = candidateIds[ci];\n const trk = MapView.getTrackData(id) as any;\n const pts = trk?.points || [];\n const n = Math.min(pts.length, COURSE_CAP);\n if (n === 0) continue;\n\n for (let i = 0; i < n; i++) {\n const pt = pts[i];\n courseTrkLats[i] = pt.lat;\n courseTrkLons[i] = pt.lon;\n courseTrkPwrs[i] = pt.power || 0;\n }\n\n for (let g = 0; g < numGates; g++) {\n const gLat = courseGateLats[g];\n const gLon = courseGateLons[g];\n let minDist = Infinity;\n let bestIdx = 0;\n for (let i = 0; i < n; i++) {\n const d = haversine(gLat, gLon, courseTrkLats[i], courseTrkLons[i]);\n if (d < minDist) {\n minDist = d;\n bestIdx = i;\n }\n }\n corridorDeviationSum += minDist + courseTrkPwrs[bestIdx] * 0.01;\n }\n\n // Resample track telemetry and evaluate single-pass banded DTW virtual-partner pacing alignment\n for (let s = 0; s < DTW_SAMPLES; s++) {\n const idx = Math.min(n - 1, Math.floor((s * (n - 1)) / (DTW_SAMPLES - 1)));\n const pt = pts[idx];\n const val = (pt.speed || 0) * 3.6 + (pt.ele || 0) * 0.05 + (pt.power || 0) * 0.02;\n if (ci === 0) dtwRefProfile[s] = val;\n dtwCandProfile[s] = val;\n }\n\n dtwPrevRow.fill(1e9);\n dtwPrevRow[0] = 0;\n for (let i = 1; i <= DTW_SAMPLES; i++) {\n dtwCurrRow.fill(1e9);\n const jStart = Math.max(1, i - DTW_BAND);\n const jEnd = Math.min(DTW_SAMPLES, i + DTW_BAND);\n const rVal = dtwRefProfile[i - 1];\n for (let j = jStart; j <= jEnd; j++) {\n const diff = rVal - dtwCandProfile[j - 1];\n const diffSq = diff * diff;\n const cost = diffSq * (2 - 1 / (1 + diffSq * 0.01));\n const m1 = dtwPrevRow[j];\n const m2 = dtwCurrRow[j - 1];\n const m3 = dtwPrevRow[j - 1];\n const prevMin = m1 < m2 ? (m1 < m3 ? m1 : m3) : m2 < m3 ? m2 : m3;\n dtwCurrRow[j] = cost + prevMin;\n }\n dtwPrevRow.set(dtwCurrRow);\n }\n corridorDeviationSum += dtwPrevRow[DTW_SAMPLES] * 1e-4;\n }\n }\n container.dataset.corridorDeviation = corridorDeviationSum.toFixed(2);\n\n container.innerHTML = `\n
\n
\n
\n
\n ${\n isEditMode\n ? `\n \n `\n : `\n \n `\n }\n
\n
\n
\n
\n ${\n courses.length === 0\n ? `\n
\n route\n
No courses created yet
\n
\n `\n : courses\n .map((c) => {\n const pts = c.points.map((p) => ({ lat: p.lat, lng: p.lon }));\n const matchingIds = MapView.findTracksPassingThroughPoints(\n pts,\n c.points[0]?.radius || 20,\n );\n const isOpen = c.id === openCourseId;\n let tracksHtml = '';\n if (isOpen) {\n const matchedTracksWithStats = matchingIds\n .map((id) => {\n const track = MapView.getTrackData(id) as any;\n if (!track) return null;\n const range = findCourseRange(track, c.points);\n if (!range) return null;\n const stats = calculateCourseStats(track, range.startIdx, range.endIdx);\n const span = Math.max(1, range.endIdx - range.startIdx);\n const q1Idx = range.startIdx + Math.floor(span * 0.25);\n const q2Idx = range.startIdx + Math.floor(span * 0.5);\n const q3Idx = range.startIdx + Math.floor(span * 0.75);\n const s1Stats = calculateCourseStats(track, range.startIdx, q1Idx);\n const s2Stats = calculateCourseStats(track, q1Idx, q2Idx);\n const s3Stats = calculateCourseStats(track, q2Idx, q3Idx);\n const s4Stats = calculateCourseStats(track, q3Idx, range.endIdx);\n const startTime = track.stats?.startTime || track.points?.[0]?.time;\n const dateStr = startTime ? fmtDate(startTime) : '-';\n return {\n id,\n displayName: track.displayName || track.name || id,\n date: dateStr,\n sport: track.sport,\n color: track.color,\n ...stats,\n splitsSummary: `${s1Stats.avgWatts || 0}/${s2Stats.avgWatts || 0}/${s3Stats.avgWatts || 0}/${s4Stats.avgWatts || 0}W`,\n startIdx: range.startIdx,\n endIdx: range.endIdx,\n };\n })\n .filter((t) => t !== null) as any[];\n\n // Sort by duration (fastest first), tie-break by id\n matchedTracksWithStats.sort(\n (a, b) => a.duration - b.duration || a.id.localeCompare(b.id),\n );\n\n tracksHtml = `\n
\n ${\n matchedTracksWithStats.length === 0\n ? `\n
No matching tracks
\n `\n : `\n \n \n \n \n ${renderCourseTableHeaders()}\n \n \n \n ${matchedTracksWithStats\n .map((t) => {\n const isSelected = t.id === UrlState.get().track;\n return `\n \n \n ${renderCourseTableCells(t, t.sport, t.date)}\n \n `;\n })\n .join('')}\n \n
Track (${matchedTracksWithStats.length})
\n
\n
\n ${escHtml(t.displayName)}\n
\n
\n `\n }\n
\n `;\n }\n return `\n
\n
\n ${isOpen ? 'expand_more' : 'chevron_right'}\n
${escHtml(c.name)} edit
\n
straighten ${calculateCourseDistance(c).toFixed(2)} km | ${c.points.length} points | ${matchingIds.length} matching tracks
\n
\n \n \n
\n
\n ${tracksHtml}\n
\n `;\n })\n .join('')\n }\n
\n
\n `;\n\n document.getElementById('btn-new-course')?.addEventListener('click', () => {\n createNewCourse();\n });\n\n document.getElementById('btn-done-edit')?.addEventListener('click', () => {\n isEditMode = false;\n MapView.setCustomClickHandler(null);\n document.getElementById('map-container')?.classList.remove('editing-mode');\n renderCourses();\n });\n\n container.querySelectorAll('.course-item').forEach((el) => {\n const id = (el as HTMLElement).dataset.id!;\n el.querySelector('.btn-edit-course')?.addEventListener('click', (e) => {\n e.stopPropagation();\n editCourse(id);\n });\n el.querySelector('.btn-rename-course')?.addEventListener('click', (e) => {\n e.stopPropagation();\n renameCourse(id);\n });\n el.querySelector('.btn-delete-course')?.addEventListener('click', (e) => {\n e.stopPropagation();\n deleteCourse(id);\n });\n el.querySelector('.course-item-header')?.addEventListener('click', (e) => {\n if (\n (e.target as HTMLElement).closest('.course-actions') ||\n (e.target as HTMLElement).closest('.btn-rename-course')\n )\n return;\n\n selectCourse(id);\n openCourseId = openCourseId === id ? null : id;\n renderCourses();\n });\n\n el.querySelectorAll('.course-track-row').forEach((row) => {\n const trackId = (row as HTMLElement).dataset.trackId!;\n const startIdx = parseInt((row as HTMLElement).dataset.start!, 10);\n const endIdx = parseInt((row as HTMLElement).dataset.end!, 10);\n\n row.querySelector('.track-name-cell')?.addEventListener('click', (e) => {\n e.stopPropagation();\n const track = MapView.getTrackData(trackId);\n if (track) {\n // Select track (clears selection in app.ts)\n selectTrack(trackId, false);\n\n // Delay execution slightly to let selectTrack finish and ChartView to initialize\n setTimeout(() => {\n const pts = track.points;\n const t0 = pts.find((p: any) => p.time != null)?.time || 0;\n\n // Ensure indices are within bounds\n const sIdx = Math.max(0, Math.min(startIdx, pts.length - 1));\n const eIdx = Math.max(0, Math.min(endIdx, pts.length - 1));\n\n const tMin = Math.max(0, ((pts[sIdx]?.time || 0) - t0) / 1000);\n const tMax = Math.max(0, ((pts[eIdx]?.time || 0) - t0) / 1000);\n\n const axis = ChartView.getXAxis();\n let selMin = tMin;\n let selMax = tMax;\n\n if (axis === 'distance') {\n selMin = Math.max(0, (pts[sIdx]?.dist || 0) / 1000);\n selMax = Math.max(0, (pts[eIdx]?.dist || 0) / 1000);\n }\n\n // Set selection and switch tab\n UrlState.patch({ sel: [selMin, selMax], tab: 'graphs' }, true);\n\n // Manually restore selection in ChartView (expects seconds from start)\n ChartView.restoreSelection(tMin, tMax);\n\n // Highlight segment on map\n MapView.highlightSegment(trackId, track.points, startIdx, endIdx, true);\n }, 50);\n }\n });\n\n row.addEventListener('mouseenter', () => {\n MapView.setTrackHover(trackId, true);\n });\n\n row.addEventListener('mouseleave', () => {\n MapView.setTrackHover(trackId, false);\n });\n });\n });\n}\n\nfunction createNewCourse() {\n const name = `Course ${courses.length + 1}`;\n\n const newCourse: Course = {\n id: Math.random().toString(36).substring(2, 9),\n name: name,\n points: [],\n };\n\n courses.push(newCourse);\n saveCourses();\n renderCourses();\n editCourse(newCourse.id);\n}\n\nfunction renameCourse(id: string) {\n const course = courses.find((c) => c.id === id);\n if (!course) return;\n\n const el = document.querySelector(`.course-item[data-id=\"${id}\"]`);\n if (!el) return;\n\n const nameEl = el.querySelector('.course-name');\n if (!nameEl) return;\n\n const currentName = course.name;\n nameEl.innerHTML = ``;\n\n const input = nameEl.querySelector('.course-name-input') as HTMLInputElement;\n input.focus();\n input.select();\n\n const save = () => {\n const newName = input.value.trim();\n if (newName !== '') {\n course.name = newName;\n saveCourses();\n }\n renderCourses();\n };\n\n input.addEventListener('blur', save);\n input.addEventListener('keydown', (e) => {\n if (e.key === 'Enter') save();\n else if (e.key === 'Escape') renderCourses();\n });\n}\n\nfunction editCourse(id: string) {\n activeCourseId = id;\n isEditMode = true;\n renderCourses();\n}\n\nfunction clearCourseLayer() {\n courseLayer = null;\n}\n\nfunction addPointToCourse(courseId: string, lat: number, lon: number) {\n const course = courses.find((c) => c.id === courseId);\n if (!course) return;\n\n const newPoint: CoursePoint = {\n lat,\n lon,\n radius: 20, // 20m default radius\n };\n\n course.points.push(newPoint);\n saveCourses();\n renderCourses();\n}\n\nfunction renderCourseOnMap(courseId: string) {\n // Map rendering removed for benchmark\n}\n\nexport function selectCourse(id: string, open: boolean = false) {\n activeCourseId = id;\n if (open) openCourseId = id;\n isEditMode = false;\n MapView.setCustomClickHandler(null);\n clearCourseLayer();\n renderCourses();\n}\n\nfunction deleteCourse(id: string) {\n if (!confirm('Are you sure you want to delete this course?')) return;\n courses = courses.filter((c) => c.id !== id);\n if (activeCourseId === id) {\n activeCourseId = null;\n clearCourseLayer();\n }\n saveCourses();\n renderCourses();\n}\n", "// Copyright (c) 2026, Camillo Bruni.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n\n'use strict';\n\nimport uPlot from 'uplot';\nimport { TrackData, TrackPoint } from '../parsers';\nimport { ChartView } from '../charts';\nimport { hexToRgba, fmtSecs } from '../utils';\n\nlet currentTracks: TrackData[] = [];\nlet currentTrack: TrackData | null = null;\nlet plot: uPlot | null = null;\nlet xMetric = 'power';\nlet yMetric = 'hr';\nlet currentSliderIdx: number | null = null;\nlet currentChronoData: [number, number, number, number, number, number, number][] = [];\nlet currentSortedData: [number, number, number, number, number, number, number][] = [];\nlet playIntervalId: number | null = null;\nlet isPolar = false;\n\nfunction updateSliderLabel() {\n const sliderVal = document.getElementById('combined-time-val');\n if (sliderVal && currentSliderIdx !== null && currentChronoData[currentSliderIdx]) {\n const timeSecs = currentChronoData[currentSliderIdx][2];\n sliderVal.textContent = `${fmtSecs(timeSecs)}`;\n }\n}\n\nexport function initCombined() {\n // Initialize UI event listeners if needed\n}\n\nexport function resetCombined() {\n if (playIntervalId) {\n clearInterval(playIntervalId);\n playIntervalId = null;\n }\n if (plot) {\n plot.destroy();\n plot = null;\n }\n currentTracks = [];\n currentTrack = null;\n xMetric = 'power';\n yMetric = 'hr';\n currentSliderIdx = null;\n currentChronoData = [];\n currentSortedData = [];\n isPolar = false;\n const tooltip = document.getElementById('combined-tooltip');\n if (tooltip) tooltip.remove();\n const container = document.getElementById('combined-view');\n if (container) {\n container.innerHTML = `\n
\n no_sim\n
Select a track for combined analysis
\n
\n `;\n }\n}\n\nconst COMBINED_CAP = 16384;\nconst combinedDensityGrid = new Float64Array(32 * 32);\n\nexport interface CombinedCorrelationRow {\n pairLabel: string;\n rVal: number;\n slope: number;\n samples: number;\n}\n\nexport interface CombinedSectionComparisonRow {\n sectionLabel: string;\n t1Power: number;\n t2Power: number;\n t1Hr: number;\n t2Hr: number;\n t1SpeedKmh: number;\n t2SpeedKmh: number;\n cohortAvgPower: number;\n}\n\nfunction computeMultiTrackCorrelationAndSections(\n currentTracks: TrackData[],\n allTracks: TrackData[],\n): {\n aggPts: number;\n corrSum: number;\n correlations: CombinedCorrelationRow[];\n sectionRows: CombinedSectionComparisonRow[];\n} {\n const matrixTracks = allTracks.length >= 2 ? allTracks : currentTracks;\n const numTrk = Math.min(matrixTracks.length, 8);\n combinedDensityGrid.fill(0);\n\n const pairs: {\n label: string;\n getX: (p: TrackPoint) => number | null | undefined;\n getY: (p: TrackPoint) => number | null | undefined;\n }[] = [\n { label: 'Power vs Heart Rate', getX: (p) => p.power, getY: (p) => p.hr },\n {\n label: 'Speed vs Gradient',\n getX: (p) => (p.speed != null ? p.speed * 3.6 : undefined),\n getY: (p) => p.gradient,\n },\n { label: 'Cadence vs Power', getX: (p) => p.cad, getY: (p) => p.power },\n { label: 'Elevation vs VAM', getX: (p) => p.ele, getY: (p) => p.verticalSpeed },\n { label: 'Temperature vs HR', getX: (p) => p.temp, getY: (p) => p.hr },\n ];\n\n let aggPts = 0;\n let corrSum = 0;\n const correlations: CombinedCorrelationRow[] = [];\n\n for (let pi = 0; pi < pairs.length; pi++) {\n const pair = pairs[pi];\n let sumX = 0;\n let sumY = 0;\n let sumXX = 0;\n let sumYY = 0;\n let sumXY = 0;\n let count = 0;\n\n for (let t = 0; t < numTrk; t++) {\n const pts = matrixTracks[t].points || [];\n const n = Math.min(pts.length, COMBINED_CAP);\n for (let k = 0; k < n; k++) {\n const p = pts[k];\n const x = pair.getX(p);\n const y = pair.getY(p);\n if (x != null && y != null) {\n sumX += x;\n sumY += y;\n sumXX += x * x;\n sumYY += y * y;\n sumXY += x * y;\n count++;\n }\n if (pi === 0) {\n const pw = p.power || 0;\n const h = p.hr || 0;\n const cd = p.cad || 0;\n const bx = Math.min(31, Math.max(0, (pw * 0.05) | 0));\n const by = Math.min(31, Math.max(0, ((h - 60) * 0.2) | 0));\n combinedDensityGrid[(bx << 5) | by] += 1 + cd * 0.01;\n }\n }\n }\n\n let rVal = 0;\n let slope = 0;\n if (count > 2) {\n const meanX = sumX / count;\n const meanY = sumY / count;\n const varX = Math.max(0, sumXX / count - meanX * meanX);\n const varY = Math.max(0, sumYY / count - meanY * meanY);\n const covXY = sumXY / count - meanX * meanY;\n const denom = Math.sqrt(varX * varY);\n rVal = denom > 1e-9 ? covXY / denom : 0;\n slope = varX > 1e-9 ? covXY / varX : 0;\n }\n aggPts += count;\n corrSum += Math.abs(rVal) * 100 + Math.abs(slope);\n correlations.push({\n pairLabel: pair.label,\n rVal,\n slope,\n samples: count,\n });\n }\n\n for (let g = 0; g < 1024; g++) {\n corrSum += combinedDensityGrid[g] * 1e-3;\n }\n\n const sectionLabels = ['Q1 (0\u201325%)', 'Q2 (25\u201350%)', 'Q3 (50\u201375%)', 'Q4 (75\u2013100%)'];\n const sectionRows: CombinedSectionComparisonRow[] = [];\n const t1 = currentTracks[0];\n const t2 = currentTracks[1] || currentTracks[0];\n\n for (let q = 0; q < 4; q++) {\n const fStart = q * 0.25;\n const fEnd = (q + 1) * 0.25;\n\n const sliceStats = (trk: TrackData) => {\n const pts = trk.points || [];\n const sIdx = Math.floor(pts.length * fStart);\n const eIdx = Math.max(sIdx + 1, Math.floor(pts.length * fEnd));\n let pSum = 0,\n hSum = 0,\n spSum = 0,\n cnt = 0;\n for (let i = sIdx; i < eIdx && i < pts.length; i++) {\n const pt = pts[i];\n pSum += pt.power || 0;\n hSum += pt.hr || 0;\n spSum += (pt.speed || 0) * 3.6;\n cnt++;\n }\n return {\n power: cnt > 0 ? Math.round(pSum / cnt) : 0,\n hr: cnt > 0 ? Math.round(hSum / cnt) : 0,\n speedKmh: cnt > 0 ? spSum / cnt : 0,\n };\n };\n\n const st1 = sliceStats(t1);\n const st2 = sliceStats(t2);\n let cohortPwrSum = 0;\n for (let t = 0; t < numTrk; t++) {\n cohortPwrSum += sliceStats(matrixTracks[t]).power;\n }\n sectionRows.push({\n sectionLabel: sectionLabels[q],\n t1Power: st1.power,\n t2Power: st2.power,\n t1Hr: st1.hr,\n t2Hr: st2.hr,\n t1SpeedKmh: st1.speedKmh,\n t2SpeedKmh: st2.speedKmh,\n cohortAvgPower: numTrk > 0 ? Math.round(cohortPwrSum / numTrk) : 0,\n });\n }\n\n return { aggPts, corrSum, correlations, sectionRows };\n}\n\nexport function setCombinedMetrics(xKey: string, yKey: string) {\n xMetric = xKey;\n yMetric = yKey;\n if (currentTracks.length >= 2) {\n renderCombined(currentTracks);\n } else if (currentTrack) {\n renderCombined(currentTrack);\n }\n}\n\nexport function setCombinedPolar(polar: boolean) {\n if (isPolar !== polar) {\n isPolar = polar;\n if (currentTracks.length >= 2) {\n renderCombined(currentTracks);\n } else if (currentTrack) {\n renderCombined(currentTrack);\n }\n }\n}\n\nexport function setCombinedSliderProgress(fraction: number) {\n const clamped = Math.max(0, Math.min(1, fraction));\n const maxLen =\n currentChronoData.length > 0\n ? currentChronoData.length - 1\n : currentTrack\n ? Math.max(0, currentTrack.points.length - 1)\n : 0;\n currentSliderIdx = Math.round(clamped * maxLen);\n const slider = document.getElementById('combined-time-slider') as HTMLInputElement | null;\n if (slider) {\n slider.max = `${maxLen}`;\n slider.value = `${currentSliderIdx}`;\n }\n updateSliderLabel();\n if (plot) plot.redraw();\n}\n\nexport function renderCombined(\n trackOrTracks: TrackData | TrackData[] | null,\n allTracks: TrackData[] = [],\n) {\n if (Array.isArray(trackOrTracks)) {\n currentTracks = trackOrTracks;\n currentTrack = trackOrTracks[0] || null;\n } else if (trackOrTracks) {\n currentTracks = [trackOrTracks];\n currentTrack = trackOrTracks;\n } else {\n currentTracks = [];\n currentTrack = null;\n }\n const container = document.getElementById('combined-view');\n if (!container) return;\n\n const track = currentTrack;\n if (!track) {\n container.innerHTML = `\n
\n no_sim\n
Select a track for combined analysis
\n
\n `;\n return;\n }\n\n // Find available metrics in the track\n const metrics = ChartView.METRICS;\n const availableMetrics: string[] = [];\n for (const key in metrics) {\n const def = metrics[key];\n if (track.points.some((p) => (p as any)[def.field] != null)) availableMetrics.push(key);\n }\n\n // Default to first two available if defaults are not available\n if (!availableMetrics.includes(xMetric) && availableMetrics.length > 0)\n xMetric = availableMetrics[0];\n\n if (!availableMetrics.includes(yMetric) && availableMetrics.length > 1)\n yMetric = availableMetrics[1];\n else if (!availableMetrics.includes(yMetric) && availableMetrics.length > 0)\n yMetric = availableMetrics[0];\n\n const xDef = metrics[xMetric];\n const yDef = metrics[yMetric];\n\n // Prevent scrolling and runaway size\n container.style.overflow = 'hidden';\n\n let summaryHtml = '';\n if (currentTracks.length >= 2) {\n const { aggPts, corrSum, correlations, sectionRows } = computeMultiTrackCorrelationAndSections(\n currentTracks,\n allTracks,\n );\n const t1 = currentTracks[0];\n const t2 = currentTracks[1];\n const s1 = t1.stats;\n const s2 = t2.stats;\n const distDiff = ((s2.totalDist - s1.totalDist) / 1000).toFixed(2);\n const pwrDiff = ((s2.avgPower || 0) - (s1.avgPower || 0)).toFixed(0);\n const hrDiff = ((s2.avgHR || 0) - (s1.avgHR || 0)).toFixed(0);\n const eleDiff = (s2.elevGain - s1.elevGain).toFixed(0);\n const shiftsDiff = (s2.shifts || 0) - (s1.shifts || 0);\n\n const totalDist = ((s1.totalDist + s2.totalDist) / 1000).toFixed(2);\n const totalElev = (s1.elevGain + s2.elevGain).toFixed(0);\n const totalDur = ((s1.duration || 0) + (s2.duration || 0)) / 1000;\n const avgPwr = Math.round(((s1.avgPower || 0) + (s2.avgPower || 0)) / 2);\n const maxPwr = Math.max(s1.maxPower || 0, s2.maxPower || 0);\n const avgHr = Math.round(((s1.avgHR || 0) + (s2.avgHR || 0)) / 2);\n const maxHr = Math.max(s1.maxHR || 0, s2.maxHR || 0);\n const tempDiff = ((s2.avgTemp ?? 0) - (s1.avgTemp ?? 0)).toFixed(1);\n const avgTemp = (((s1.avgTemp ?? 0) + (s2.avgTemp ?? 0)) / 2).toFixed(1);\n const maxTemp = Math.max(s1.maxTemp ?? 0, s2.maxTemp ?? 0).toFixed(1);\n const totalShifts = (s1.shifts || 0) + (s2.shifts || 0);\n\n summaryHtml = `\n
\n
Aggregate Track Comparison & Section Analysis (${currentTracks.length} Tracks)
\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n ${sectionRows\n .map(\n (sr) => `\n \n \n \n \n \n \n `,\n )\n .join('')}\n \n
Metric${t1.displayName || t1.name} (T1)${t2.displayName || t2.name} (T2)Delta (T2 - T1)Aggregate Summary
Distance${(s1.totalDist / 1000).toFixed(2)} km${(s2.totalDist / 1000).toFixed(2)} km= 0 ? '#4ECDC4' : '#FF6B6B'};\">${Number(distDiff) >= 0 ? '+' : ''}${distDiff} km${totalDist} km (Total)
Elevation Gain${s1.elevGain.toFixed(0)} m${s2.elevGain.toFixed(0)} m= 0 ? '#4ECDC4' : '#FF6B6B'};\">${Number(eleDiff) >= 0 ? '+' : ''}${eleDiff} m${totalElev} m (Total)
Duration${s1.duration ? fmtSecs(s1.duration / 1000) : '--'}${s2.duration ? fmtSecs(s2.duration / 1000) : '--'}--${fmtSecs(totalDur)} (Total)
Avg / Max Power${s1.avgPower || 0} W / ${s1.maxPower || 0} W${s2.avgPower || 0} W / ${s2.maxPower || 0} W= 0 ? '#4ECDC4' : '#FF6B6B'};\">${Number(pwrDiff) >= 0 ? '+' : ''}${pwrDiff} W (Avg)${avgPwr} W (Avg) / ${maxPwr} W (Max)
Avg / Max HR${s1.avgHR || 0} bpm / ${s1.maxHR || 0} bpm${s2.avgHR || 0} bpm / ${s2.maxHR || 0} bpm= 0 ? '#4ECDC4' : '#FF6B6B'};\">${Number(hrDiff) >= 0 ? '+' : ''}${hrDiff} bpm (Avg)${avgHr} bpm (Avg) / ${maxHr} bpm (Max)
Avg / Max Temp${s1.avgTemp != null ? s1.avgTemp.toFixed(1) : '--'} \u00B0C / ${s1.maxTemp != null ? s1.maxTemp.toFixed(1) : '--'} \u00B0C${s2.avgTemp != null ? s2.avgTemp.toFixed(1) : '--'} \u00B0C / ${s2.maxTemp != null ? s2.maxTemp.toFixed(1) : '--'} \u00B0C= 0 ? '#4ECDC4' : '#FF6B6B'};\">${Number(tempDiff) >= 0 ? '+' : ''}${tempDiff} \u00B0C (Avg)${avgTemp} \u00B0C (Avg) / ${maxTemp} \u00B0C (Max)
Gear Shifts${s1.shifts || 0}${s2.shifts || 0}= 0 ? '#4ECDC4' : '#FF6B6B'};\">${shiftsDiff >= 0 ? '+' : ''}${shiftsDiff}${totalShifts} (Total)
${sr.sectionLabel} Split${sr.t1Power}W \u00B7 ${sr.t1Hr}bpm \u00B7 ${sr.t1SpeedKmh.toFixed(1)}km/h${sr.t2Power}W \u00B7 ${sr.t2Hr}bpm \u00B7 ${sr.t2SpeedKmh.toFixed(1)}km/h${sr.t2Power - sr.t1Power >= 0 ? '+' : ''}${sr.t2Power - sr.t1Power}WCohort Avg: ${sr.cohortAvgPower}W
\n
\n ${correlations\n .map(\n (c) =>\n `${c.pairLabel}: r=${c.rVal.toFixed(2)}, \u03B2=${c.slope.toFixed(2)} (${c.samples})`,\n )\n .join('')}\n
\n
\n `;\n }\n\n // Render UI with selectors and plot container\n container.innerHTML = `${summaryHtml}\n
\n
\n arrow_forward\n X Axis:\n \n
\n
\n arrow_upward\n Y Axis:\n \n
\n
\n schedule\n Time:\n \n 0:00\n \n
\n
\n \n \n
\n
\n
\n `;\n\n document.getElementById('btn-combined-x')?.addEventListener('click', (e) => {\n e.stopPropagation();\n showMenu(e.currentTarget as HTMLElement, 'x');\n });\n\n document.getElementById('btn-combined-y')?.addEventListener('click', (e) => {\n e.stopPropagation();\n showMenu(e.currentTarget as HTMLElement, 'y');\n });\n\n const slider = document.getElementById('combined-time-slider') as HTMLInputElement;\n const sliderVal = document.getElementById('combined-time-val');\n const playBtn = document.getElementById('btn-combined-play');\n\n slider?.addEventListener('input', () => {\n currentSliderIdx = parseInt(slider.value, 10);\n updateSliderLabel();\n if (plot) plot.redraw();\n });\n\n function startPlaying() {\n if (playBtn) playBtn.innerHTML = 'pause';\n\n playIntervalId = window.setInterval(() => {\n if (currentSliderIdx !== null && currentSliderIdx < currentChronoData.length - 1) {\n currentSliderIdx = Math.min(currentSliderIdx + 5, currentChronoData.length - 1);\n slider.value = `${currentSliderIdx}`;\n updateSliderLabel();\n if (plot) plot.redraw();\n } else {\n stopPlaying();\n }\n }, 20); // 50 fps\n }\n\n function stopPlaying() {\n if (playIntervalId) {\n clearInterval(playIntervalId);\n playIntervalId = null;\n }\n if (playBtn) playBtn.innerHTML = 'play_arrow';\n }\n\n playBtn?.addEventListener('click', () => {\n if (playIntervalId) {\n stopPlaying();\n } else {\n // If at the end, loop back to start\n if (currentSliderIdx !== null && currentSliderIdx >= currentChronoData.length - 1) {\n currentSliderIdx = 0;\n slider.value = '0';\n updateSliderLabel();\n if (plot) plot.redraw();\n }\n startPlaying();\n }\n });\n\n document.getElementById('btn-combined-cart')?.addEventListener('click', () => {\n if (isPolar) {\n isPolar = false;\n renderCombined(currentTrack);\n }\n });\n\n document.getElementById('btn-combined-polar')?.addEventListener('click', () => {\n if (!isPolar) {\n isPolar = true;\n renderCombined(currentTrack);\n }\n });\n\n updatePlot();\n}\n\nexport function resizeCombined() {\n const container = document.getElementById('tab-combined');\n const plotContainer = document.getElementById('combined-plot-container');\n if (plot && plotContainer && plotContainer.clientWidth > 0) {\n const w = plotContainer.clientWidth;\n const h = plotContainer.clientHeight;\n\n if (w > 0 && h > 0) {\n if (isPolar) {\n const size = Math.min(w, h);\n plot.setSize({ width: size, height: size });\n } else {\n plot.setSize({ width: w, height: h });\n }\n }\n }\n}\n\nfunction showMenu(anchorEl: HTMLElement, axis: 'x' | 'y') {\n document.querySelectorAll('.metric-menu-popup').forEach((el) => el.remove());\n\n const popup = document.createElement('div');\n popup.className = 'metric-menu-popup';\n\n const metrics = ChartView.METRICS;\n const track = currentTrack;\n if (!track) return;\n\n const availableMetrics: string[] = [];\n for (const key in metrics) {\n const def = metrics[key];\n if (track.points.some((p) => (p as any)[def.field] != null)) availableMetrics.push(key);\n }\n\n availableMetrics.forEach((key) => {\n const def = metrics[key];\n const item = document.createElement('div');\n const currentVal = axis === 'x' ? xMetric : yMetric;\n item.className = `menu-item ${currentVal === key ? 'active' : ''}`;\n item.innerHTML = `\n ${def.icon}\n ${def.label}\n ${currentVal === key ? 'check' : ''}\n `;\n item.addEventListener('click', () => {\n if (axis === 'x') xMetric = key;\n else yMetric = key;\n\n popup.remove();\n renderCombined(currentTrack); // Re-render to update buttons and plot\n });\n popup.appendChild(item);\n });\n\n const rect = anchorEl.getBoundingClientRect();\n popup.style.top = `${rect.bottom + 5}px`;\n popup.style.left = `${rect.left}px`;\n document.body.appendChild(popup);\n\n const close = (e: MouseEvent) => {\n if (!popup.contains(e.target as Node) && !anchorEl.contains(e.target as Node)) {\n popup.remove();\n document.removeEventListener('click', close);\n }\n };\n setTimeout(() => document.addEventListener('click', close), 0);\n}\n\nfunction updatePlot() {\n if (!currentTrack) return;\n const container = document.getElementById('combined-plot-container');\n if (!container) return;\n\n const metrics = ChartView.METRICS;\n const xDef = metrics[xMetric] || metrics.power;\n const yDef = metrics[yMetric] || metrics.hr;\n\n if (currentTracks.length >= 2) {\n let maxLen = 0;\n for (let t = 0; t < currentTracks.length; t++) {\n if (currentTracks[t].points.length > maxLen) maxLen = currentTracks[t].points.length;\n }\n const xData: number[] = new Array(maxLen);\n for (let i = 0; i < maxLen; i++) xData[i] = i;\n\n const seriesList: uPlot.Series[] = [{}];\n const dataArrays: (number | null)[][] = [xData];\n\n for (let t = 0; t < currentTracks.length; t++) {\n const trk = currentTracks[t];\n const pts = trk.points;\n const yArr: (number | null)[] = new Array(maxLen);\n for (let i = 0; i < maxLen; i++) {\n const p = pts[i];\n yArr[i] = p && (p as any)[yDef.field] != null ? (p as any)[yDef.field] : null;\n }\n dataArrays.push(yArr);\n seriesList.push({\n label: `${trk.displayName || trk.name} (${yDef.label})`,\n stroke: trk.color || '#3b82f6',\n width: 2,\n points: { show: false },\n });\n }\n\n if (plot) {\n plot.destroy();\n plot = null;\n }\n if (container.querySelector('.empty-text')) container.innerHTML = '';\n\n const width = container.clientWidth > 0 ? container.clientWidth : 800;\n const height = container.clientHeight > 0 ? container.clientHeight : 350;\n\n const opts: uPlot.Options = {\n width,\n height,\n padding: [10, 15, 0, 0],\n scales: { x: { time: false }, y: { auto: true } },\n series: seriesList,\n axes: [\n {\n side: 2,\n label: 'Elapsed Duration (s)',\n stroke: '#555564',\n grid: { stroke: '#2e2e34', width: 1 },\n font: '10px system-ui, sans-serif',\n size: 30,\n },\n {\n side: 3,\n label: `${yDef.label} (${yDef.unit})`,\n stroke: '#555564',\n grid: { stroke: '#2e2e34', width: 1 },\n font: '10px system-ui, sans-serif',\n size: 40,\n },\n ],\n legend: { show: true },\n };\n\n plot = new uPlot(opts, dataArrays as uPlot.AlignedData, container as HTMLElement);\n resizeCombined();\n return;\n }\n\n const pts = currentTrack.points;\n const t0 = pts[0].time || 0;\n const chronoData = pts\n .map((p, idx) => [\n (p as any)[xDef.field],\n (p as any)[yDef.field],\n (p.time! - t0) / 1000,\n (p.dist || 0) / 1000,\n idx,\n ])\n .filter(([x, y, t, d]) => x != null && y != null) as [number, number, number, number, number][];\n\n if (chronoData.length === 0) {\n container.innerHTML =\n '
No data for selected metrics
';\n return;\n }\n\n if (isPolar) {\n const xVals = chronoData.map((d) => d[0]);\n const yVals = chronoData.map((d) => d[1]);\n const xMin = Math.min(...xVals);\n const xMax = Math.max(...xVals);\n const yMin = Math.min(...yVals);\n const yMax = Math.max(...yVals);\n\n currentChronoData = chronoData.map((d) => {\n const xVal = d[0];\n const yVal = d[1];\n const theta = ((xVal - xMin) / (xMax - xMin || 1)) * 2 * Math.PI;\n const r = (yVal - yMin) / (yMax - yMin || 1);\n const px = r * Math.cos(theta);\n const py = r * Math.sin(theta);\n return [px, py, d[2], d[3], d[4], xVal, yVal] as any;\n });\n } else {\n currentChronoData = chronoData.map((d) => [d[0], d[1], d[2], d[3], d[4], d[0], d[1]] as any);\n }\n\n // Update slider\n const slider = document.getElementById('combined-time-slider') as HTMLInputElement;\n if (slider) {\n slider.max = `${chronoData.length - 1}`;\n if (currentSliderIdx === null || currentSliderIdx >= chronoData.length) {\n currentSliderIdx = chronoData.length - 1;\n slider.value = slider.max;\n }\n updateSliderLabel();\n }\n\n // Sort by X for uPlot (with original point index tie-breaker)\n currentSortedData = [...currentChronoData].sort((a, b) => a[0] - b[0] || a[4] - b[4]);\n\n const xData = currentSortedData.map((d) => d[0]);\n const yData = currentSortedData.map((d) => d[1]);\n\n const plotContainer = document.getElementById('combined-plot-container');\n let width = plotContainer && plotContainer.clientWidth > 0 ? plotContainer.clientWidth : 600;\n let height = plotContainer && plotContainer.clientHeight > 0 ? plotContainer.clientHeight : 400;\n\n if (isPolar) {\n const size = Math.min(width, height > 0 ? height : width);\n width = size;\n height = size;\n\n if (plotContainer) {\n plotContainer.style.display = 'flex';\n plotContainer.style.alignItems = 'center';\n plotContainer.style.justifyContent = 'center';\n }\n } else if (plotContainer) {\n plotContainer.style.display = '';\n plotContainer.style.alignItems = '';\n plotContainer.style.justifyContent = '';\n }\n\n const opts: uPlot.Options = {\n width: width,\n height: height,\n padding: [10, 15, 0, 0],\n scales: {\n x: { time: false, auto: true },\n y: { auto: true },\n },\n series: [\n {},\n {\n label: yDef.label,\n width: 0,\n points: { show: true, size: 3, fill: hexToRgba(yDef.color, 0.3) },\n },\n ],\n axes: [\n {\n side: 2, // bottom\n show: !isPolar,\n label: `${xDef.label} (${xDef.unit})`,\n stroke: '#555564',\n grid: { stroke: '#2e2e34', width: 1 },\n font: '10px system-ui, sans-serif',\n values: (u, vals) => vals.map((v) => (v != null ? xDef.fmtAxis(v) : '')),\n size: 30,\n },\n {\n side: 3, // left\n show: !isPolar,\n label: `${yDef.label} (${yDef.unit})`,\n stroke: '#555564',\n grid: { stroke: '#2e2e34', width: 1 },\n font: '10px system-ui, sans-serif',\n values: (u, vals) => vals.map((v) => (v != null ? yDef.fmtAxis(v) : '')),\n size: 40,\n },\n ],\n cursor: {\n drag: { x: false, y: false },\n dataIdx: (u, seriesIdx, dataIdx) => {\n const cursorLeft = u.cursor.left;\n const cursorTop = u.cursor.top;\n if (cursorLeft != null && cursorTop != null && cursorLeft >= 0 && cursorTop >= 0) {\n let minD2 = Infinity;\n let closestIdx = -1;\n\n for (let i = 0; i < currentSortedData.length; i++) {\n const [xVal, yVal] = currentSortedData[i];\n const cx = u.valToPos(xVal, 'x');\n const cy = u.valToPos(yVal, 'y');\n\n const dx = cx - cursorLeft;\n const dy = cy - cursorTop;\n const d2 = dx * dx + dy * dy;\n\n if (d2 < minD2) {\n minD2 = d2;\n closestIdx = i;\n }\n }\n\n // Only snap if within a reasonable distance (e.g. 50 pixels)\n if (closestIdx >= 0 && minD2 < 2500) return closestIdx;\n }\n return dataIdx;\n },\n points: {\n size: 7,\n stroke: '#fff',\n width: 2,\n fill: yDef.color,\n },\n },\n legend: { show: false },\n hooks: {\n draw: [\n (u: uPlot) => {\n const ctx = u.ctx;\n const dpr = window.devicePixelRatio || 1;\n\n // Draw faint lines connecting points in chronological order\n ctx.save();\n ctx.beginPath();\n ctx.strokeStyle = 'rgba(255, 255, 255, 0.15)';\n ctx.lineWidth = 1 * dpr;\n\n let started = false;\n for (let i = 0; i < currentChronoData.length; i++) {\n const [xVal, yVal] = currentChronoData[i];\n const cx = u.valToPos(xVal, 'x', true);\n const cy = u.valToPos(yVal, 'y', true);\n\n if (started) {\n ctx.lineTo(cx, cy);\n } else {\n ctx.moveTo(cx, cy);\n started = true;\n }\n }\n ctx.stroke();\n ctx.restore();\n\n // Draw trail if slider is active\n if (currentSliderIdx !== null) {\n ctx.save();\n const windowSize = 100; // Longer trail\n const startIdx = Math.max(0, currentSliderIdx - windowSize);\n const endIdx = currentSliderIdx;\n\n // 1. Draw increasingly thicker connection line for the trail\n for (let i = startIdx; i < endIdx; i++) {\n const [xVal1, yVal1] = currentChronoData[i];\n const [xVal2, yVal2] = currentChronoData[i + 1];\n const cx1 = u.valToPos(xVal1, 'x', true);\n const cy1 = u.valToPos(yVal1, 'y', true);\n const cx2 = u.valToPos(xVal2, 'x', true);\n const cy2 = u.valToPos(yVal2, 'y', true);\n\n const progress = (i - startIdx) / (endIdx - startIdx || 1);\n const lineWidth = 1 + 5 * progress; // Grow from 1 to 6\n const alpha = 0.15 + 0.6 * progress; // Fade from 0.15 to 0.75\n\n ctx.beginPath();\n ctx.moveTo(cx1, cy1);\n ctx.lineTo(cx2, cy2);\n ctx.strokeStyle = hexToRgba(yDef.color, alpha);\n ctx.lineWidth = lineWidth * dpr;\n ctx.stroke();\n }\n\n // 2. Draw dots on top\n for (let i = startIdx; i <= endIdx; i++) {\n const [xVal, yVal] = currentChronoData[i];\n const cx = u.valToPos(xVal, 'x', true);\n const cy = u.valToPos(yVal, 'y', true);\n\n const progress = (i - startIdx) / (endIdx - startIdx || 1);\n const size = 3 + 7 * progress; // Grow from 3 to 10\n const fillAlpha = 0.01 + 0.14 * progress; // Fade from 0.01 to 0.15\n\n ctx.beginPath();\n ctx.arc(cx, cy, size * dpr, 0, Math.PI * 2);\n ctx.fillStyle = hexToRgba(yDef.color, fillAlpha);\n ctx.fill();\n\n ctx.strokeStyle = yDef.color;\n ctx.lineWidth = 1 * dpr;\n ctx.stroke();\n }\n ctx.restore();\n }\n },\n ],\n setCursor: [\n (u: uPlot) => {\n const idx = u.cursor.idx;\n\n let tooltip = document.getElementById('combined-tooltip');\n if (!tooltip) {\n tooltip = document.createElement('div');\n tooltip.id = 'combined-tooltip';\n tooltip.style.position = 'absolute';\n tooltip.style.background = 'rgba(14, 14, 16, 0.9)';\n tooltip.style.color = '#fff';\n tooltip.style.padding = '8px 12px';\n tooltip.style.borderRadius = '4px';\n tooltip.classList.add('font-m');\n tooltip.style.fontFamily = 'system-ui';\n tooltip.style.pointerEvents = 'none';\n tooltip.style.zIndex = '1000';\n tooltip.style.boxShadow = '0 4px 6px rgba(0,0,0,0.3)';\n tooltip.style.border = '1px solid #2e2e34';\n document.body.appendChild(tooltip);\n }\n\n if (idx != null && currentSortedData[idx] && u.cursor.left! >= 0) {\n const d = currentSortedData[idx];\n const timeSecs = d[2];\n const distKm = d[3];\n const origX = d[5];\n const origY = d[6];\n\n tooltip.innerHTML = `\n
Point #${idx}
\n
\n Time:\n ${fmtSecs(timeSecs)}\n
\n
\n Distance:\n ${distKm.toFixed(2)} km\n
\n
\n ${xDef.label}:\n ${xDef.fmtAxis(origX)} ${xDef.unit}\n
\n
\n ${yDef.label}:\n ${yDef.fmtAxis(origY)} ${yDef.unit}\n
\n `;\n tooltip.style.display = 'block';\n\n const canvasRect = u.over.getBoundingClientRect();\n tooltip.style.left = `${canvasRect.left + window.scrollX + u.cursor.left! + 15}px`;\n tooltip.style.top = `${canvasRect.top + window.scrollY + u.cursor.top! + 15}px`;\n } else {\n tooltip.style.display = 'none';\n }\n },\n ],\n },\n };\n\n // Clear container before rendering (except when uPlot does it)\n // uPlot handles its own canvas, but we need to clear any previous empty state message\n if (container.querySelector('.empty-text')) container.innerHTML = '';\n\n if (plot) plot.destroy();\n\n plot = new uPlot(opts, [xData, yData], container);\n\n plot.over.addEventListener('click', () => {\n if (plot) {\n const idx = plot.cursor.idx;\n if (idx != null && currentSortedData[idx]) {\n const originalIdx = currentSortedData[idx][4];\n ChartView.setCursorAt(originalIdx);\n }\n }\n });\n\n // Ensure initial size is set synchronously\n resizeCombined();\n}\n", "// Copyright (c) 2026, Camillo Bruni.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n\n'use strict';\n\nimport uPlot from 'uplot';\nimport { TrackData, Parsers } from '../parsers';\nimport { Storage } from '../storage';\nimport { fmtDuration, fmtDate, gaussianSmooth, hexToRgba } from '../utils';\nimport { Metrics } from '../metrics';\nimport { UrlState } from '../url-state';\nimport {\n computeAccumulatedMetric,\n renderAccumulatedTable,\n renderAccumulatedProgressSummary,\n} from './accumulated_progress';\n\nlet evolutionPlots: uPlot[] = [];\nlet activeEvolutionCards: any[] = [];\nlet currentEvolutionGrouping: 'week' | 'month' = 'week';\n\nfunction destroyEvolutionPlots() {\n evolutionPlots.forEach((p) => p.destroy());\n evolutionPlots = [];\n activeEvolutionCards = [];\n}\n\nexport function resetEvolution() {\n destroyEvolutionPlots();\n currentEvolutionGrouping = 'week';\n const container = document.getElementById('evolution-view');\n if (container) {\n container.innerHTML = `\n
\n no_sim\n
Select a track for progress analysis
\n
\n `;\n }\n}\n\nexport function setEvolutionGrouping(grouping: 'week' | 'month') {\n currentEvolutionGrouping = grouping;\n activeEvolutionCards.forEach((c) => {\n if (c && c.updateGrouping) c.updateGrouping(grouping);\n });\n}\n\nexport function setEvolutionRange(range: string) {\n UrlState.patch({ progress: range === 'all' ? null : range });\n const toolbarRangeCtrl = document.querySelector('#evolution-time-range');\n if (toolbarRangeCtrl) {\n toolbarRangeCtrl.querySelectorAll('.seg-btn').forEach((b) => {\n b.classList.toggle('active', b.getAttribute('data-range') === range);\n });\n }\n activeEvolutionCards.forEach((c) => {\n if (c && c.updateRange) c.updateRange(range);\n });\n}\n\nexport function setEvolutionPinnedDuration(dur: number | null) {\n activeEvolutionCards.forEach((c) => {\n if (c && c.updatePin) c.updatePin(dur);\n });\n}\n\nconst EVO_CAP = 16384;\nconst evoPwrPrefix = new Float64Array(EVO_CAP + 1);\nconst evoHrPrefix = new Float64Array(EVO_CAP + 1);\nconst evoSpdPrefix = new Float64Array(EVO_CAP + 1);\nconst evoStressArr = new Float64Array(EVO_CAP);\nconst evoDailyTrimp = new Float64Array(365);\nconst envelopeDurations = [\n 1, 2, 3, 5, 8, 10, 15, 20, 30, 45, 60, 90, 120, 180, 240, 300, 360, 450, 600, 750, 900, 1200,\n];\n\nexport function renderEvolution(\n currentTrack: TrackData | null,\n allTracks: TrackData[],\n onTrackSelect?: (id: string, range: [number, number] | null) => void,\n onDateRangeSelect?: (start: string, end: string) => void,\n) {\n destroyEvolutionPlots();\n const container = document.getElementById('evolution-view');\n if (!container) return;\n\n let currentRange = UrlState.get().progress || 'all';\n\n // Ensure duration curves and zones are available on all tracks\n allTracks.forEach((track) => {\n Parsers.ensureCurvesAndZones(track);\n });\n\n // Single-pass Banister impulse-response training load (ATL / CTL / TSB) and historical curve envelope\n let totalFatigue = 0;\n let totalFitness = 0;\n evoDailyTrimp.fill(0);\n\n for (let ti = 0; ti < allTracks.length; ti++) {\n const pts = allTracks[ti].points || [];\n const n = Math.min(pts.length, EVO_CAP);\n if (n === 0) continue;\n\n for (let i = 0; i < n; i++) {\n const p = pts[i];\n const pw = p.power || 0;\n const h = p.hr || 0;\n const sp = p.speed || 0;\n const el = p.ele || 0;\n evoPwrPrefix[i + 1] = evoPwrPrefix[i] + pw;\n evoHrPrefix[i + 1] = evoHrPrefix[i] + h;\n evoSpdPrefix[i + 1] = evoSpdPrefix[i] + sp;\n const hrRatio = Math.max(0, Math.min(1.2, (h - 60) / 135));\n evoStressArr[i] = pw * 1.5 + h + hrRatio * (1 + 1.92 * hrRatio) * 10 + el * 0.02;\n }\n\n for (let di = 0; di < envelopeDurations.length; di++) {\n const d = envelopeDurations[di];\n if (d > n) continue;\n const limit = n - d;\n let maxP = 0;\n let maxH = 0;\n for (let j = 0; j <= limit; j++) {\n const dh = evoHrPrefix[j + d] - evoHrPrefix[j];\n const sp = evoPwrPrefix[j + d] - evoPwrPrefix[j];\n if (sp > maxP) maxP = sp;\n if (dh > maxH) maxH = dh;\n }\n totalFitness += ((maxP + maxH + evoSpdPrefix[limit]) / d) * 1e-3;\n }\n\n const alphaA = 2 / 8;\n const oneMinusA = 1 - alphaA;\n const alphaC = 2 / 43;\n const oneMinusC = 1 - alphaC;\n const hrRest = 52;\n const invHrSpan = 1 / (195 - hrRest);\n let atl = 0;\n let ctl = 0;\n for (let i = 0; i < n; i++) {\n const h = evoHrPrefix[i + 1] - evoHrPrefix[i];\n const hrRatio = Math.max(0, (h - hrRest) * invHrSpan);\n const kh = 1.92 * hrRatio;\n const trimp = evoStressArr[i] * 0.25 + hrRatio * 0.64 * (1 + kh * (1 + 0.5 * kh)) * 15;\n atl = alphaA * trimp + oneMinusA * atl;\n ctl = alphaC * trimp + oneMinusC * ctl;\n }\n totalFatigue += atl * 0.01;\n totalFitness += ctl * 0.01;\n\n const dayIdx = (ti * 57) % 365;\n evoDailyTrimp[dayIdx] += evoPwrPrefix[n] * 1e-3 + evoHrPrefix[n] * 1e-3;\n }\n\n let atl365 = 0;\n let ctl365 = 0;\n for (let day = 0; day < 365; day++) {\n const load = evoDailyTrimp[day] + (day % 7 === 0 ? 45 : 15);\n atl365 = atl365 * 0.86 + load * 0.14;\n ctl365 = ctl365 * 0.976 + load * 0.024;\n }\n totalFatigue += atl365 * 1e-3;\n totalFitness += ctl365 * 1e-3;\n\n container.dataset.tsb = (totalFitness - totalFatigue).toFixed(1);\n const cards: any[] = [];\n activeEvolutionCards = cards;\n container.innerHTML = `\n
\n
\n
\n \n \n \n \n
\n
\n
\n
\n \n \n \n \n \n \n
\n
\n
\n
\n `;\n\n const grid = document.getElementById('evolution-container');\n if (!grid) return;\n\n grid.insertAdjacentHTML('beforeend', renderAccumulatedProgressSummary(allTracks));\n\n const gridWidth = grid.clientWidth || 570;\n\n const progressSync = uPlot.sync('progress-sync');\n cards.push(\n renderProgressCard(grid, allTracks, 'distance', progressSync, onDateRangeSelect, gridWidth),\n );\n cards.push(\n renderProgressCard(grid, allTracks, 'elevation', progressSync, onDateRangeSelect, gridWidth),\n );\n\n const distanceScroll = grid.querySelector(\n '.distance-progress-card .chart-scroll-container',\n ) as HTMLElement;\n const elevationScroll = grid.querySelector(\n '.elevation-progress-card .chart-scroll-container',\n ) as HTMLElement;\n\n if (distanceScroll && elevationScroll) {\n distanceScroll.addEventListener('scroll', () => {\n if (elevationScroll.scrollLeft !== distanceScroll.scrollLeft)\n elevationScroll.scrollLeft = distanceScroll.scrollLeft;\n });\n elevationScroll.addEventListener('scroll', () => {\n if (distanceScroll.scrollLeft !== elevationScroll.scrollLeft)\n distanceScroll.scrollLeft = elevationScroll.scrollLeft;\n });\n }\n\n const toolbarRangeCtrl = container.querySelector('#evolution-time-range');\n if (toolbarRangeCtrl) {\n toolbarRangeCtrl.querySelectorAll('.seg-btn').forEach((btn) => {\n btn.addEventListener('click', () => {\n const range = btn.getAttribute('data-range');\n if (range) {\n toolbarRangeCtrl\n .querySelectorAll('.seg-btn')\n .forEach((b) => b.classList.remove('active'));\n btn.classList.add('active');\n onRangeChange(range);\n }\n });\n });\n }\n\n const pills = container.querySelectorAll('.metric-pill');\n pills.forEach((pill) => {\n pill.addEventListener('click', () => {\n const target = pill.getAttribute('data-target');\n if (target) {\n const targetEl = document.getElementById(`${target}-evolution-card`);\n targetEl?.scrollIntoView({ behavior: 'smooth' });\n }\n });\n });\n\n function onPinChange(dur: number | null) {\n cards.forEach((c) => {\n if (c && c.updatePin) c.updatePin(dur);\n });\n }\n\n function onRangeChange(range: string) {\n UrlState.patch({ progress: range === 'all' ? null : range });\n cards.forEach((c) => {\n if (c && c.updateRange) c.updateRange(range);\n });\n }\n\n if (allTracks.some((t) => t.stats.powerCurve)) {\n const card = renderCurveEvolutionCard({\n grid,\n currentTrack,\n allTracks,\n metricKey: 'power',\n label: 'Power Curve Progress',\n unit: 'W',\n color: Metrics.power.color,\n icon: 'bolt',\n onPinChange,\n onRangeChange,\n onTrackSelect,\n gridWidth,\n });\n cards.push(card);\n }\n\n if (allTracks.some((t) => t.stats.hrCurve)) {\n const card = renderCurveEvolutionCard({\n grid,\n currentTrack,\n allTracks,\n metricKey: 'hr',\n label: 'Heart Rate Curve Progress',\n unit: 'bpm',\n color: Metrics.hr.color,\n icon: 'favorite',\n onPinChange,\n onRangeChange,\n onTrackSelect,\n gridWidth,\n });\n cards.push(card);\n }\n\n // Apply initial range when filtered\n if (currentRange !== 'all') {\n cards.forEach((c) => {\n if (c && c.updateRange) c.updateRange(currentRange);\n });\n }\n\n evolutionPlots.forEach((p) => {\n (p as any)._ready = true;\n });\n}\n\ninterface CurveEvolutionCardOptions {\n grid: HTMLElement;\n currentTrack: TrackData | null;\n allTracks: TrackData[];\n metricKey: 'power' | 'hr';\n label: string;\n unit: string;\n color: string;\n icon: string;\n onPinChange: (dur: number | null) => void;\n onRangeChange: (range: string) => void;\n onTrackSelect?: (id: string, range: [number, number] | null) => void;\n gridWidth?: number;\n}\n\nfunction renderCurveEvolutionCard(opts: CurveEvolutionCardOptions) {\n const {\n grid,\n currentTrack,\n allTracks,\n metricKey,\n label,\n unit,\n color,\n icon,\n onPinChange,\n onRangeChange,\n onTrackSelect,\n gridWidth,\n } = opts;\n\n const trackName = currentTrack ? currentTrack.name || 'Current Track' : 'Current Track';\n\n let mainChart: uPlot | null = null;\n\n const cardContainer = document.createElement('div');\n cardContainer.className = 'chart-row evolution-card-group';\n cardContainer.id = `${metricKey}-evolution-card`;\n grid.appendChild(cardContainer);\n\n const row1 = document.createElement('div');\n row1.className = 'chart-row progress-chart-row';\n row1.style.setProperty('--chart-color', color);\n row1.style.flex = '0 0 400px';\n row1.style.borderBottom = 'none';\n row1.innerHTML = `\n
\n
\n ${icon}\n ${label}\n
\n
\n
\n
\n
\n `;\n\n const legendEl = document.createElement('div');\n legendEl.className = 'evolution-legend';\n legendEl.innerHTML = `\n
\n \n ${trackName}\n
\n
\n \n All-Time Max\n
\n `;\n\n const urlState = UrlState.get();\n let currentRange = urlState.progress || 'all';\n\n const row2 = document.createElement('div');\n row2.className = 'chart-row progress-chart-row';\n row2.style.setProperty('--chart-color', color);\n row2.style.flex = '1';\n row2.style.borderBottom = 'none';\n row2.innerHTML = `\n
\n
\n ${icon}\n Progress ${metricKey === 'power' ? 'Power' : 'HR'}\n
\n
\n
\n
\n
\n `;\n\n const sideBySide = document.createElement('div');\n sideBySide.style.cssText = 'display: flex; flex-direction: row; gap: 0px; align-items: stretch;';\n sideBySide.appendChild(row1);\n sideBySide.appendChild(row2);\n\n cardContainer.appendChild(sideBySide);\n cardContainer.appendChild(legendEl);\n\n const chartEl = row1.querySelector(`#${metricKey}-evolution-chart`) as HTMLElement;\n if (!chartEl) return;\n\n // 1. Compute global max curve\n const maxCurve: Record<\n number,\n { val: number; trackInfo: string; trackId?: string; idx?: number }\n > = {};\n allTracks.forEach((t) => {\n const curve = metricKey === 'power' ? t.stats.powerCurve : t.stats.hrCurve;\n if (!curve) return;\n const name = t.name || 'Unnamed Track';\n const date =\n t.stats.startTime || t.addedAt ? fmtDate((t.stats.startTime || t.addedAt)!) : 'Unknown Date';\n const trackInfo = `${name} (${date})`;\n\n Object.entries(curve).forEach(([dur, data]) => {\n const d = Number(dur);\n const val = (data as any).power || (data as any).hr;\n if (!maxCurve[d] || val > maxCurve[d].val)\n maxCurve[d] = { val, trackInfo, trackId: t.id, idx: (data as any).idx };\n });\n });\n\n // 2. Get current track curve\n const currentCurve =\n (currentTrack &&\n (metricKey === 'power' ? currentTrack.stats.powerCurve : currentTrack.stats.hrCurve)) ||\n {};\n\n // 3. Combine durations\n const allDurations = new Set();\n Object.keys(maxCurve).forEach((d) => allDurations.add(Number(d)));\n Object.keys(currentCurve).forEach((d) => allDurations.add(Number(d)));\n const durations = Array.from(allDurations).sort((a, b) => a - b);\n\n const xData = durations;\n const yDataCurrent = durations.map((d) => {\n const entry = currentCurve[d];\n return entry ? (entry as any).power || (entry as any).hr : null;\n });\n const yDataMax = durations.map((d) => maxCurve[d]?.val || null);\n\n // Precompute timeline data for all tracks\n const validTracks = allTracks.filter((t) => (t.stats.startTime || t.addedAt) != null);\n validTracks.sort(\n (a, b) =>\n (a.stats.startTime || a.addedAt || 0) - (b.stats.startTime || b.addedAt || 0) ||\n a.id.localeCompare(b.id),\n );\n\n const xTimeline: number[] = [];\n const yTimelineData: Record = {};\n durations.forEach((d) => (yTimelineData[d] = []));\n\n validTracks.forEach((t) => {\n const curve = metricKey === 'power' ? t.stats.powerCurve : t.stats.hrCurve;\n if (!curve) return;\n\n xTimeline.push((t.stats.startTime || t.addedAt)! / 1000); // seconds\n durations.forEach((d) => {\n const entry = curve[d];\n const val = entry ? (metricKey === 'power' ? (entry as any).power : (entry as any).hr) : null;\n yTimelineData[d].push(val);\n });\n });\n\n let currentDur = durations[0] || 5;\n let tracksFiltered = validTracks;\n let pinnedDur: number | null = null;\n\n function getFilteredData(dur: number, range: string) {\n const maxTime = xTimeline[xTimeline.length - 1] || 1785542400;\n let minTime = 0;\n\n if (range === '1m') minTime = maxTime - 30 * 24 * 3600;\n else if (range === '2m') minTime = maxTime - 60 * 24 * 3600;\n else if (range === '6m') minTime = maxTime - 180 * 24 * 3600;\n else if (range === '1y') minTime = maxTime - 365 * 24 * 3600;\n else if (range === '2y') minTime = maxTime - 2 * 365 * 24 * 3600;\n\n const filteredIndices: number[] = [];\n xTimeline.forEach((t, idx) => {\n if (t >= minTime) filteredIndices.push(idx);\n });\n\n const xFiltered = filteredIndices.map((i) => xTimeline[i]);\n const rawY = yTimelineData[dur] || [];\n const rawYFiltered = filteredIndices.map((i) => rawY[i]);\n const smoothedYFiltered = gaussianSmooth(rawYFiltered, 5);\n tracksFiltered = filteredIndices.map((i) => validTracks[i]);\n\n return { xFiltered, rawYFiltered, smoothedYFiltered };\n }\n\n let timelineChart: uPlot | null = null;\n\n const curYVal = document.createElement('div');\n curYVal.className = 'cur-y-val font-m';\n curYVal.style.cssText =\n 'position:absolute;background:rgba(14, 14, 16, 0.9);color:#fff;padding:8px 12px;border-radius:4px;font-family:system-ui;pointer-events:none;z-index:1000;box-shadow:0 4px 6px rgba(0,0,0,0.3);border:1px solid #2e2e34;display:none;';\n chartEl.style.position = 'relative';\n chartEl.appendChild(curYVal);\n\n const renderCharts = () => {\n const optsUPlot: uPlot.Options = {\n id: `${metricKey}-evolution`,\n width: 400,\n height: 250,\n padding: [0, 10, 0, 10],\n scales: {\n x: { time: false, distr: 3, auto: true },\n y: { auto: true },\n },\n series: [\n {},\n {\n label: 'Current Track',\n stroke: color,\n width: 2,\n fill: hexToRgba(color, 0.1),\n points: { show: true, size: 5, fill: color },\n },\n {\n label: 'All-Time Max',\n stroke: '#888888',\n width: 1,\n fill: 'transparent',\n points: { show: true, size: 6, fill: '#888888' },\n },\n ],\n axes: [\n {\n stroke: '#555564',\n grid: { stroke: '#2e2e34', width: 1 },\n show: true,\n values: () => [],\n ticks: { show: false },\n font: '10px system-ui, sans-serif',\n size: 30,\n },\n {\n stroke: '#555564',\n grid: { stroke: '#2e2e34', width: 1 },\n values: (_u, vals) => vals.map((v) => `${v}${unit}`),\n font: '10px system-ui, sans-serif',\n size: 50,\n },\n ],\n cursor: {\n drag: { x: false, y: false },\n dataIdx: (u, seriesIdx, dataIdx) => dataIdx,\n x: false,\n y: false,\n },\n hooks: {\n draw: [\n (u: uPlot) => {\n const ctx = u.ctx;\n const dpr = window.devicePixelRatio || 1;\n const bb = u.bbox;\n const benchmarks = [\n 1, 2, 5, 10, 20, 30, 60, 120, 300, 600, 1200, 1800, 3600, 7200, 10800,\n ];\n const maxDur = xData[xData.length - 1];\n\n ctx.save();\n ctx.fillStyle = '#555564';\n ctx.textAlign = 'center';\n ctx.textBaseline = 'top';\n ctx.font = `${10 * dpr}px system-ui, sans-serif`;\n\n let lastX = -100;\n benchmarks.forEach((d) => {\n if (d > maxDur) return;\n const x = u.valToPos(d, 'x', true);\n if (x > lastX + 30 * dpr && x < bb.left + bb.width + 5 * dpr) {\n ctx.fillText(fmtDuration(d), x, bb.top + bb.height + 6 * dpr);\n lastX = x;\n }\n });\n\n // Draw pinned duration circle\n if (pinnedDur !== null) {\n const idx = xData.indexOf(pinnedDur);\n if (idx >= 0) {\n const pcx = u.valToPos(pinnedDur, 'x', true);\n const pcy = u.valToPos(yDataCurrent[idx]! || yDataMax[idx]!, 'y', true);\n\n ctx.save();\n ctx.beginPath();\n ctx.setLineDash([]);\n const curVal = yDataCurrent[idx];\n const dotRadius = curVal !== null ? 5 * dpr : 3 * dpr;\n const dotColor = curVal !== null ? color : '#888888';\n ctx.arc(pcx, pcy, dotRadius, 0, 2 * Math.PI);\n ctx.fillStyle = dotColor;\n ctx.fill();\n ctx.strokeStyle = '#fff';\n ctx.lineWidth = 2 * dpr;\n ctx.stroke();\n ctx.restore();\n }\n }\n\n ctx.restore();\n },\n (u: uPlot) => {\n const ctx = u.ctx;\n const dpr = window.devicePixelRatio || 1;\n const bb = u.bbox;\n\n let idx = u.cursor.idx;\n let activeSeriesIdx = 1;\n const cursorLeft = u.cursor.left;\n const cursorTop = u.cursor.top;\n\n if (cursorLeft != null && cursorTop != null && cursorLeft >= 0 && cursorTop >= 0) {\n let minD2 = Infinity;\n let nearestIdx = -1;\n const radius = 30; // pixels\n const maxD2 = radius * radius;\n\n for (let i = 0; i < xData.length; i++) {\n const x = xData[i];\n const yCur = yDataCurrent[i];\n const yMax = yDataMax[i];\n\n if (x != null) {\n const px = u.valToPos(x, 'x', false);\n\n if (yCur != null) {\n const py = u.valToPos(yCur, 'y', false);\n const dx = px - cursorLeft;\n const dy = py - cursorTop;\n const d2 = dx * dx + dy * dy;\n if (d2 < minD2) {\n minD2 = d2;\n nearestIdx = i;\n activeSeriesIdx = 1;\n }\n }\n if (yMax != null) {\n const py = u.valToPos(yMax, 'y', false);\n const dx = px - cursorLeft;\n const dy = py - cursorTop;\n const d2 = dx * dx + dy * dy;\n if (d2 < minD2) {\n minD2 = d2;\n nearestIdx = i;\n activeSeriesIdx = 2;\n }\n }\n }\n }\n\n if (minD2 <= maxD2) idx = nearestIdx;\n else idx = null;\n }\n\n if (idx != null && u.data[0][idx] != null) {\n const pcx = u.valToPos(u.data[0][idx]!, 'x', true);\n const pcy = u.valToPos(\n activeSeriesIdx === 1 ? yDataCurrent[idx]! : yDataMax[idx]!,\n 'y',\n true,\n );\n\n // Draw lines\n ctx.save();\n ctx.beginPath();\n ctx.setLineDash([2 * dpr, 2 * dpr]);\n ctx.strokeStyle = hexToRgba(activeSeriesIdx === 1 ? color : '#888888', 0.4);\n ctx.lineWidth = 1 * dpr;\n // Vertical line\n ctx.moveTo(pcx, bb.top);\n ctx.lineTo(pcx, bb.top + bb.height);\n // Horizontal line\n ctx.moveTo(bb.left, pcy);\n ctx.lineTo(bb.left + bb.width, pcy);\n ctx.stroke();\n ctx.restore();\n\n // Draw hover halo\n ctx.save();\n ctx.beginPath();\n ctx.setLineDash([]); // Reset to solid\n ctx.arc(pcx, pcy, 12 * dpr, 0, 2 * Math.PI);\n ctx.fillStyle = hexToRgba(activeSeriesIdx === 1 ? color : '#888888', 0.2);\n ctx.fill();\n ctx.restore();\n\n // Draw center circle\n ctx.save();\n ctx.beginPath();\n const dotRadius = activeSeriesIdx === 1 ? 5 * dpr : 6 * dpr;\n const dotColor = activeSeriesIdx === 1 ? color : '#888888';\n ctx.arc(pcx, pcy, dotRadius, 0, 2 * Math.PI);\n ctx.fillStyle = dotColor;\n ctx.fill();\n ctx.strokeStyle = '#fff';\n ctx.lineWidth = 2 * dpr;\n ctx.stroke();\n ctx.restore();\n\n // Draw axis labels\n ctx.save();\n ctx.setLineDash([]); // Reset to solid\n\n const xValStr = fmtDuration(xData[idx!]);\n ctx.font = `bold ${10 * dpr}px system-ui, sans-serif`;\n const xValWidth = ctx.measureText(xValStr).width;\n const xbw = xValWidth + 10 * dpr;\n const pillH = 16 * dpr;\n\n const xLabelX = pcx - xbw / 2;\n const clampedXBx = Math.max(bb.left, Math.min(bb.left + bb.width - xbw, xLabelX));\n const xLabelY = bb.top + bb.height + 2 * dpr;\n\n // X Label Pill\n ctx.fillStyle = 'rgba(14, 14, 16, 0.92)';\n ctx.beginPath();\n if ((ctx as any).roundRect)\n (ctx as any).roundRect(clampedXBx, xLabelY, xbw, pillH, 3 * dpr);\n else ctx.rect(clampedXBx, xLabelY, xbw, pillH);\n ctx.fill();\n ctx.strokeStyle = hexToRgba(color, 0.27);\n ctx.lineWidth = 1 * dpr;\n ctx.stroke();\n\n ctx.fillStyle = color;\n ctx.textAlign = 'center';\n ctx.textBaseline = 'middle';\n ctx.fillText(xValStr, clampedXBx + xbw / 2, xLabelY + pillH / 2);\n\n const yVal = yDataCurrent[idx!]! || yDataMax[idx!]!;\n const yValStr = `${yVal.toFixed(0)} ${unit}`;\n const yValWidth = ctx.measureText(yValStr).width;\n const ybw = yValWidth + 10 * dpr;\n\n const yLabelX = bb.left - ybw - 2 * dpr; // To the left\n const yLabelY = pcy - pillH / 2;\n\n // Y Label Pill\n ctx.fillStyle = 'rgba(14, 14, 16, 0.92)';\n ctx.beginPath();\n if ((ctx as any).roundRect)\n (ctx as any).roundRect(yLabelX, yLabelY, ybw, pillH, 3 * dpr);\n else ctx.rect(yLabelX, yLabelY, ybw, pillH);\n ctx.fill();\n ctx.strokeStyle = hexToRgba(color, 0.27);\n ctx.lineWidth = 1 * dpr;\n ctx.stroke();\n\n ctx.fillStyle = color;\n ctx.textAlign = 'left';\n ctx.textBaseline = 'middle';\n ctx.fillText(yValStr, yLabelX + 5 * dpr, yLabelY + pillH / 2);\n\n ctx.restore();\n }\n },\n ],\n setCursor: [\n (u: uPlot) => {\n if (!(u as any)._ready) return;\n u.redraw(false);\n\n let idx = u.cursor.idx;\n let activeSeriesIdx = 1;\n const cursorLeft = u.cursor.left;\n const cursorTop = u.cursor.top;\n\n if (cursorLeft != null && cursorTop != null && cursorLeft >= 0 && cursorTop >= 0) {\n let minD2 = Infinity;\n let nearestIdx = -1;\n const radius = 30; // pixels\n const maxD2 = radius * radius;\n\n for (let i = 0; i < xData.length; i++) {\n const x = xData[i];\n const yCur = yDataCurrent[i];\n const yMax = yDataMax[i];\n\n if (x != null) {\n const px = u.valToPos(x, 'x', false);\n\n if (yCur != null) {\n const py = u.valToPos(yCur, 'y', false);\n const dx = px - cursorLeft;\n const dy = py - cursorTop;\n const d2 = dx * dx + dy * dy;\n if (d2 < minD2) {\n minD2 = d2;\n nearestIdx = i;\n activeSeriesIdx = 1;\n }\n }\n if (yMax != null) {\n const py = u.valToPos(yMax, 'y', false);\n const dx = px - cursorLeft;\n const dy = py - cursorTop;\n const d2 = dx * dx + dy * dy;\n if (d2 < minD2) {\n minD2 = d2;\n nearestIdx = i;\n activeSeriesIdx = 2;\n }\n }\n }\n }\n\n if (minD2 <= maxD2) idx = nearestIdx;\n else idx = null;\n }\n\n const ctx = u.ctx;\n const dpr = window.devicePixelRatio || 1;\n const bb = u.bbox;\n\n const hasFocus = idx != null && xData[idx] != null;\n\n if (hasFocus) {\n const cx = u.valToPos(xData[idx!]!, 'x', false);\n const cy = u.valToPos(yDataCurrent[idx!]! || yDataMax[idx!]!, 'y', false); // Fallback to max if current is null\n\n const durVal = xData[idx!];\n const curVal = yDataCurrent[idx!];\n const mxVal = yDataMax[idx!];\n\n let html = `
${fmtDuration(durVal)}
`;\n if (curVal !== null) {\n html += `\n
\n Current:\n ${curVal} ${unit}\n
\n `;\n }\n if (mxVal !== null) {\n html += `\n
\n Max:\n ${mxVal} ${unit}\n
\n `;\n }\n\n curYVal.innerHTML = html;\n\n if (timelineChart) {\n if (pinnedDur === null) {\n currentDur = durVal;\n const { xFiltered, rawYFiltered, smoothedYFiltered } = getFilteredData(\n currentDur,\n currentRange,\n );\n const lowerBound = smoothedYFiltered.map((v) => (v !== null ? v * 0.9 : null));\n const upperBound = smoothedYFiltered.map((v) => (v !== null ? v * 1.1 : null));\n timelineChart.setData([\n xFiltered,\n rawYFiltered,\n smoothedYFiltered,\n lowerBound,\n upperBound,\n ]);\n\n const labelEl = document.getElementById(`${metricKey}-timeline-label`);\n if (labelEl)\n labelEl.textContent = `Progress ${fmtDuration(durVal)} ${metricKey === 'power' ? 'Power' : 'HR'}`;\n }\n }\n curYVal.style.display = '';\n curYVal.style.left = `${cursorLeft! + bb.left}px`;\n curYVal.style.top = `${cursorTop! + bb.top}px`;\n curYVal.style.transform = 'translate(5px, -50%)';\n } else {\n curYVal.style.display = 'none';\n }\n },\n ],\n },\n };\n\n mainChart = new uPlot(optsUPlot, [xData, yDataCurrent, yDataMax], chartEl);\n evolutionPlots.push(mainChart);\n\n chartEl.addEventListener('click', () => {\n if (!mainChart) return;\n const idx = mainChart.cursor.idx;\n if (idx != null) {\n const clickedDur = xData[idx];\n let newPinnedDur: number | null = null;\n if (pinnedDur !== null) newPinnedDur = null;\n else newPinnedDur = clickedDur;\n\n onPinChange(newPinnedDur);\n\n // Also select the range for the current track if available\n if (currentTrack) {\n const stats = currentTrack.stats;\n const curve = metricKey === 'power' ? stats.powerCurve : stats.hrCurve;\n if (curve) {\n const entry = curve[clickedDur];\n if (entry) {\n const bestIdx = entry.idx;\n const duration = clickedDur;\n const startPt = currentTrack.points[bestIdx];\n const endPt = currentTrack.points[bestIdx + duration - 1];\n if (startPt && endPt && startPt.time != null && endPt.time != null) {\n const trackStart = currentTrack.points[0].time || 0;\n const min = (startPt.time - trackStart) / 1000;\n const max = (endPt.time - trackStart) / 1000;\n onTrackSelect?.(currentTrack.id, [min, max]);\n }\n }\n }\n }\n }\n });\n\n chartEl.addEventListener('mouseleave', () => {\n if (mainChart) mainChart.redraw();\n });\n\n // 4. Add expandable table\n const detailsEl = document.createElement('details');\n detailsEl.classList.add('progress-details');\n detailsEl.style.marginTop = '0px';\n detailsEl.style.setProperty('--chart-color', color);\n detailsEl.innerHTML = `\n \n chevron_right\n table_chart\n Data Table\n \n \n \n \n \n \n \n \n \n \n ${durations\n .map((d) => {\n const cur = currentCurve[d]\n ? (currentCurve[d] as any).power || (currentCurve[d] as any).hr\n : '-';\n const mx = maxCurve[d]?.val || '-';\n const mxId = maxCurve[d]?.trackId;\n const mxIdx = maxCurve[d]?.idx;\n return `\n \n \n \n \n \n `;\n })\n .join('')}\n \n
DurationCurrentAll-Time Max
${fmtDuration(d)}${cur} ${unit}\n ${mx} ${unit}\n ${mxId ? `` : ''}\n
\n `;\n cardContainer.appendChild(detailsEl);\n\n detailsEl.querySelectorAll('.evolution-select-btn').forEach((btn) => {\n btn.addEventListener('click', (e) => {\n e.preventDefault();\n e.stopPropagation();\n const id = btn.getAttribute('data-id');\n const idx = btn.getAttribute('data-idx');\n const dur = btn.getAttribute('data-dur');\n if (id) {\n document.dispatchEvent(\n new CustomEvent('select-track', {\n detail: {\n id,\n idx: idx ? Number(idx) : null,\n dur: dur ? Number(dur) : null,\n },\n }),\n );\n }\n });\n });\n\n let lastNearestIdx: number | null = null;\n\n // 5. Render timeline chart\n const timelineEl = row2.querySelector(`#${metricKey}-timeline-chart`) as HTMLElement;\n if (timelineEl) {\n timelineEl.style.position = 'relative';\n const timelineTooltip = document.createElement('div');\n timelineTooltip.className = 'timeline-tooltip font-m';\n timelineTooltip.style.cssText =\n 'position:absolute;background:rgba(14, 14, 16, 0.9);color:#fff;padding:8px 12px;border-radius:4px;font-family:system-ui;pointer-events:none;z-index:1000;box-shadow:0 4px 6px rgba(0,0,0,0.3);border:1px solid #2e2e34;display:none;';\n timelineEl.appendChild(timelineTooltip);\n\n if (xTimeline.length > 0) {\n const { xFiltered, rawYFiltered, smoothedYFiltered } = getFilteredData(\n currentDur,\n currentRange,\n );\n const lowerBound = smoothedYFiltered.map((v) => (v !== null ? v * 0.9 : null));\n const upperBound = smoothedYFiltered.map((v) => (v !== null ? v * 1.1 : null));\n const dataTimeline = [xFiltered, rawYFiltered, smoothedYFiltered, lowerBound, upperBound];\n\n const series: any[] = [\n {\n value: (u: any, v: number) => {\n if (v != null) return fmtDate(v * 1000);\n\n return '-';\n },\n },\n {\n label: 'Value',\n stroke: color,\n width: 0,\n points: { show: true, size: 6, fill: hexToRgba(color, 0.5) },\n },\n {\n label: 'Trend',\n stroke: color,\n width: 2,\n points: { show: false },\n spanGaps: true,\n fill: 'transparent',\n dash: [5, 5],\n },\n {\n label: 'Lower Bound',\n stroke: 'transparent',\n width: 0,\n points: { show: false },\n spanGaps: true,\n },\n {\n label: 'Upper Bound',\n stroke: 'transparent',\n width: 0,\n points: { show: false },\n spanGaps: true,\n },\n ];\n\n const labelEl = document.getElementById(`${metricKey}-timeline-label`);\n if (labelEl)\n labelEl.textContent = `Progress ${fmtDuration(currentDur)} ${metricKey === 'power' ? 'Power' : 'HR'}`;\n\n const optsTimeline: uPlot.Options = {\n width: Math.max(100, (gridWidth || 570) - 400),\n height: 250,\n padding: [0, 10, 0, 10],\n tzDate: (ts: number) => uPlot.tzDate(new Date(ts * 1000), 'Etc/UTC'),\n cursor: {\n points: {\n show: false,\n },\n drag: { setScale: false },\n x: false,\n y: false,\n },\n scales: {\n x: {\n time: true,\n range: (u) => {\n const min = u.data[0][0];\n const max = u.data[0][u.data[0].length - 1];\n if (min === undefined || max === undefined) return [0, 100];\n\n if (min === max) return [min - 3600, max + 3600];\n\n return [min, max];\n },\n },\n y: { range: (u: any, min: number, max: number) => [min * 0.9, max * 1.1] },\n },\n series: series,\n bands: [{ series: [4, 3], fill: hexToRgba(color, 0.2) }],\n hooks: {\n init: [\n (u: uPlot) => {\n u.over.addEventListener('click', () => {\n if (lastNearestIdx != null) {\n const track = tracksFiltered[lastNearestIdx];\n if (track) {\n const stats = track.stats;\n const curve = metricKey === 'power' ? stats.powerCurve : stats.hrCurve;\n if (curve) {\n const entry = curve[currentDur];\n if (entry) {\n const bestIdx = entry.idx;\n const duration = currentDur;\n\n const startPt = track.points[bestIdx];\n const endPt = track.points[bestIdx + duration - 1];\n\n if (startPt && endPt && startPt.time != null && endPt.time != null) {\n const trackStart = track.points[0].time || 0;\n const min = (startPt.time - trackStart) / 1000;\n const max = (endPt.time - trackStart) / 1000;\n\n opts.onTrackSelect?.(track.id, [min, max]);\n } else {\n opts.onTrackSelect?.(track.id, null);\n }\n } else {\n opts.onTrackSelect?.(track.id, null);\n }\n } else {\n opts.onTrackSelect?.(track.id, null);\n }\n }\n }\n });\n },\n ],\n draw: [\n (u: uPlot) => {\n let idx = u.cursor.idx;\n if (idx != null && u.data[0][idx] != null) {\n const val = u.data[1][idx];\n if (val != null) {\n const tpcx = u.valToPos(u.data[0][idx]!, 'x', true);\n const tpcy = u.valToPos(val, 'y', true);\n\n const tctx = u.ctx;\n const tdpr = window.devicePixelRatio || 1;\n\n // Draw lines\n tctx.save();\n tctx.beginPath();\n tctx.setLineDash([2 * tdpr, 2 * tdpr]);\n tctx.strokeStyle = hexToRgba(color, 0.4);\n tctx.lineWidth = 1 * tdpr;\n // Vertical line\n tctx.moveTo(tpcx, u.bbox.top);\n tctx.lineTo(tpcx, u.bbox.top + u.bbox.height);\n // Horizontal line\n tctx.moveTo(u.bbox.left, tpcy);\n tctx.lineTo(u.bbox.left + u.bbox.width, tpcy);\n tctx.stroke();\n tctx.restore();\n\n // Draw hover halo\n tctx.save();\n tctx.beginPath();\n tctx.setLineDash([]); // Reset to solid\n tctx.arc(tpcx, tpcy, 12 * tdpr, 0, 2 * Math.PI);\n tctx.fillStyle = hexToRgba(color, 0.2);\n tctx.fill();\n tctx.restore();\n\n // Draw center circle\n tctx.save();\n tctx.beginPath();\n tctx.arc(tpcx, tpcy, 6 * tdpr, 0, 2 * Math.PI);\n tctx.fillStyle = color;\n tctx.fill();\n tctx.strokeStyle = '#fff';\n tctx.lineWidth = 2 * tdpr;\n tctx.stroke();\n tctx.restore();\n\n // Draw axis labels\n tctx.save();\n tctx.setLineDash([]); // Reset to solid\n\n const d = new Date(u.data[0][idx]! * 1000);\n const dateStr = `${d.getUTCFullYear()}-${String(d.getUTCMonth() + 1).padStart(2, '0')}-${String(d.getUTCDate()).padStart(2, '0')}`;\n tctx.font = `bold ${10 * tdpr}px system-ui, sans-serif`;\n const dateWidth = tctx.measureText(dateStr).width;\n const xbw = dateWidth + 10 * tdpr;\n const pillH = 16 * tdpr;\n\n const xLabelX = tpcx - xbw / 2;\n const clampedXBx = Math.max(\n u.bbox.left,\n Math.min(u.bbox.left + u.bbox.width - xbw, xLabelX),\n );\n const xLabelY = u.bbox.top + u.bbox.height + 2 * tdpr;\n\n // X Label Pill\n tctx.fillStyle = 'rgba(14, 14, 16, 0.92)';\n tctx.beginPath();\n if ((tctx as any).roundRect)\n (tctx as any).roundRect(clampedXBx, xLabelY, xbw, pillH, 3 * tdpr);\n else tctx.rect(clampedXBx, xLabelY, xbw, pillH);\n tctx.fill();\n tctx.strokeStyle = hexToRgba(color, 0.27);\n tctx.lineWidth = 1 * tdpr;\n tctx.stroke();\n\n tctx.fillStyle = color;\n tctx.textAlign = 'center';\n tctx.textBaseline = 'middle';\n tctx.fillText(dateStr, clampedXBx + xbw / 2, xLabelY + pillH / 2);\n\n const valStr = `${val.toFixed(0)} ${unit}`;\n const valWidth = tctx.measureText(valStr).width;\n const ybw = valWidth + 10 * tdpr;\n\n const yLabelX = u.bbox.left - ybw - 2 * tdpr; // To the left\n const yLabelY = tpcy - pillH / 2;\n\n // Y Label Pill\n tctx.fillStyle = 'rgba(14, 14, 16, 0.92)';\n tctx.beginPath();\n if ((tctx as any).roundRect)\n (tctx as any).roundRect(yLabelX, yLabelY, ybw, pillH, 3 * tdpr);\n else tctx.rect(yLabelX, yLabelY, ybw, pillH);\n tctx.fill();\n tctx.strokeStyle = hexToRgba(color, 0.27);\n tctx.lineWidth = 1 * tdpr;\n tctx.stroke();\n\n tctx.fillStyle = color;\n tctx.textAlign = 'left';\n tctx.textBaseline = 'middle';\n tctx.fillText(valStr, yLabelX + 5 * tdpr, yLabelY + pillH / 2);\n\n tctx.restore();\n }\n }\n },\n ],\n setCursor: [\n (u: uPlot) => {\n if (!(u as any)._ready) return;\n u.redraw(false);\n\n let idx = u.cursor.idx;\n const cursorLeft = u.cursor.left;\n const cursorTop = u.cursor.top;\n const bb = u.bbox;\n\n if (cursorLeft != null && cursorTop != null && cursorLeft >= 0 && cursorTop >= 0) {\n let minD2 = Infinity;\n let nearestIdx = -1;\n const radius = 30; // pixels\n const maxD2 = radius * radius;\n\n const xData = u.data[0];\n const yData = u.data[1];\n\n for (let i = 0; i < xData.length; i++) {\n const x = xData[i];\n const y = yData[i];\n if (x == null || y == null) continue;\n\n const px = u.valToPos(x, 'x', false);\n const py = u.valToPos(y, 'y', false);\n\n const dx = px - cursorLeft;\n const dy = py - cursorTop;\n const d2 = dx * dx + dy * dy;\n\n if (d2 < minD2) {\n minD2 = d2;\n nearestIdx = i;\n }\n }\n\n if (minD2 <= maxD2) {\n idx = nearestIdx;\n lastNearestIdx = nearestIdx;\n } else {\n idx = null;\n lastNearestIdx = null;\n }\n }\n\n if (idx != null && u.data[0][idx] != null) {\n const track = tracksFiltered[idx];\n const cx = u.valToPos(u.data[0][idx], 'x', false);\n const cy = u.valToPos(u.data[1][idx]!, 'y', false);\n\n const d = new Date(u.data[0][idx]! * 1000);\n const dateStr = `${d.getUTCFullYear()}-${String(d.getUTCMonth() + 1).padStart(2, '0')}-${String(d.getUTCDate()).padStart(2, '0')}`;\n\n const val = u.data[1][idx];\n const valStr = val != null ? `${val} ${unit}` : '\u2014';\n\n timelineTooltip.innerHTML = `\n
\n Date:\n ${dateStr}\n
\n
\n Value:\n ${valStr}\n
\n `;\n timelineTooltip.style.display = 'block';\n timelineTooltip.style.left = `${cursorLeft! + bb.left}px`;\n timelineTooltip.style.top = `${cursorTop! + bb.top}px`;\n timelineTooltip.style.transform = 'translate(5px, -50%)';\n } else {\n timelineTooltip.style.display = 'none';\n }\n },\n ],\n },\n axes: [\n {\n space: 50,\n stroke: '#555564',\n grid: { stroke: '#2e2e34' },\n show: true,\n font: '10px system-ui, sans-serif',\n size: 30,\n values: (u: any, vals: any[]) =>\n vals.map((v) => {\n const d = new Date(v * 1000);\n return `${d.getUTCFullYear()}-${String(d.getUTCMonth() + 1).padStart(2, '0')}`;\n }),\n },\n {\n stroke: '#555564',\n grid: { stroke: '#2e2e34' },\n font: '10px system-ui, sans-serif',\n values: (u: any, vals: any[]) => vals.map((v) => `${v} ${unit}`),\n show: true,\n space: 15,\n size: 50,\n },\n ],\n };\n\n timelineChart = new uPlot(optsTimeline, dataTimeline as any, timelineEl);\n evolutionPlots.push(timelineChart);\n\n timelineEl.addEventListener('mouseleave', () => {\n timelineChart!.redraw();\n });\n\n // Range control moved to toolbar\n } else {\n timelineEl.innerHTML = '
No data for the past year
';\n }\n }\n };\n renderCharts();\n\n return {\n updatePin: (dur: number | null) => {\n pinnedDur = dur;\n if (dur !== null) currentDur = dur;\n\n const labelEl = document.getElementById(`${metricKey}-timeline-label`);\n if (labelEl)\n labelEl.textContent = `Progress ${fmtDuration(currentDur)} ${metricKey === 'power' ? 'Power' : 'HR'}${pinnedDur !== null ? ' (Pinned)' : ''}`;\n\n if (timelineChart) {\n const { xFiltered, rawYFiltered, smoothedYFiltered } = getFilteredData(\n currentDur,\n currentRange,\n );\n const lowerBound = smoothedYFiltered.map((v) => (v !== null ? v * 0.9 : null));\n const upperBound = smoothedYFiltered.map((v) => (v !== null ? v * 1.1 : null));\n timelineChart.setData([xFiltered, rawYFiltered, smoothedYFiltered, lowerBound, upperBound]);\n }\n if (mainChart) mainChart.redraw();\n },\n updateRange: (range: string) => {\n currentRange = range;\n if (timelineChart) {\n const { xFiltered, rawYFiltered, smoothedYFiltered } = getFilteredData(\n currentDur,\n currentRange,\n );\n const lowerBound = smoothedYFiltered.map((v) => (v !== null ? v * 0.9 : null));\n const upperBound = smoothedYFiltered.map((v) => (v !== null ? v * 1.1 : null));\n timelineChart.setData([xFiltered, rawYFiltered, smoothedYFiltered, lowerBound, upperBound]);\n }\n },\n };\n}\n\nfunction getWeekStart(date: Date): Date {\n const d = new Date(date);\n const day = d.getUTCDay();\n const diff = d.getUTCDate() - day + (day === 0 ? -6 : 1); // adjust when day is sunday\n d.setUTCDate(diff);\n d.setUTCHours(0, 0, 0, 0);\n return d;\n}\n\nfunction computeWeeklyDistance(\n allTracks: TrackData[],\n): { week: string; timestamp: number; distance: number }[] {\n const weeklyData: Record = {};\n\n allTracks.forEach((t) => {\n if (!t.stats.startTime || !t.stats.totalDist) return;\n\n const date = new Date(t.stats.startTime);\n const weekStart = getWeekStart(date);\n const weekKey = weekStart.toISOString().split('T')[0];\n\n if (!weeklyData[weekKey]) weeklyData[weekKey] = { timestamp: weekStart.getTime(), distance: 0 };\n\n weeklyData[weekKey].distance += t.stats.totalDist;\n });\n\n return Object.entries(weeklyData)\n .map(([week, data]) => ({ week, timestamp: data.timestamp, distance: data.distance }))\n .sort((a, b) => a.timestamp - b.timestamp);\n}\n\nfunction computeWeeklyElevation(\n allTracks: TrackData[],\n): { week: string; timestamp: number; elevation: number }[] {\n const weeklyData: Record = {};\n\n allTracks.forEach((t) => {\n if (!t.stats.startTime || t.stats.elevGain === undefined) return;\n\n const date = new Date(t.stats.startTime);\n const weekStart = getWeekStart(date);\n const weekKey = weekStart.toISOString().split('T')[0];\n\n if (!weeklyData[weekKey])\n weeklyData[weekKey] = { timestamp: weekStart.getTime(), elevation: 0 };\n\n weeklyData[weekKey].elevation += t.stats.elevGain;\n });\n\n return Object.entries(weeklyData)\n .map(([week, data]) => ({ week, timestamp: data.timestamp, elevation: data.elevation }))\n .sort((a, b) => a.timestamp - b.timestamp);\n}\n\nfunction computeMonthlyDistance(\n allTracks: TrackData[],\n): { week: string; timestamp: number; distance: number }[] {\n const monthlyData: Record = {};\n\n allTracks.forEach((t) => {\n if (!t.stats.startTime || !t.stats.totalDist) return;\n const date = new Date(t.stats.startTime);\n const monthStart = new Date(Date.UTC(date.getUTCFullYear(), date.getUTCMonth(), 1));\n const monthKey = monthStart.toISOString().slice(0, 7);\n if (!monthlyData[monthKey]) {\n monthlyData[monthKey] = { timestamp: monthStart.getTime(), distance: 0 };\n }\n monthlyData[monthKey].distance += t.stats.totalDist;\n });\n\n return Object.entries(monthlyData)\n .map(([week, data]) => ({ week, timestamp: data.timestamp, distance: data.distance }))\n .sort((a, b) => a.timestamp - b.timestamp);\n}\n\nfunction computeMonthlyElevation(\n allTracks: TrackData[],\n): { week: string; timestamp: number; elevation: number }[] {\n const monthlyData: Record = {};\n\n allTracks.forEach((t) => {\n if (!t.stats.startTime || t.stats.elevGain === undefined) return;\n const date = new Date(t.stats.startTime);\n const monthStart = new Date(Date.UTC(date.getUTCFullYear(), date.getUTCMonth(), 1));\n const monthKey = monthStart.toISOString().slice(0, 7);\n if (!monthlyData[monthKey]) {\n monthlyData[monthKey] = { timestamp: monthStart.getTime(), elevation: 0 };\n }\n monthlyData[monthKey].elevation += t.stats.elevGain;\n });\n\n return Object.entries(monthlyData)\n .map(([week, data]) => ({ week, timestamp: data.timestamp, elevation: data.elevation }))\n .sort((a, b) => a.timestamp - b.timestamp);\n}\n\nfunction renderProgressCard(\n grid: HTMLElement,\n allTracks: TrackData[],\n type: 'distance' | 'elevation',\n syncKey?: any,\n onDateRangeSelect?: (start: string, end: string) => void,\n gridWidth?: number,\n) {\n const isDistance = type === 'distance';\n let activeGrouping: 'week' | 'month' = currentEvolutionGrouping;\n let activeRange = UrlState.get().progress || 'all';\n const weeklySource = isDistance\n ? computeWeeklyDistance(allTracks)\n : computeWeeklyElevation(allTracks);\n const monthlySource = isDistance\n ? computeMonthlyDistance(allTracks)\n : computeMonthlyElevation(allTracks);\n let weeklyData = activeGrouping === 'month' ? monthlySource : weeklySource;\n const accumulated = computeAccumulatedMetric(allTracks, (t) =>\n isDistance ? t.stats.totalDist : t.stats.elevGain,\n );\n const color = isDistance ? Metrics.distance.color : Metrics.elevation.color;\n const unit = isDistance ? 'km' : 'm';\n const icon = isDistance ? Metrics.distance.icon : Metrics.elevation.icon;\n\n const cardContainer = document.createElement('div');\n cardContainer.className = `chart-row ${type}-progress-card`;\n cardContainer.id = `${type}-evolution-card`;\n\n cardContainer.innerHTML = `\n
\n
\n ${icon}\n ${isDistance ? 'Distance Progress' : 'Elevation Progress'}\n
\n
\n
\n
\n
\n
\n
\n
\n ${renderAccumulatedTable(accumulated, isDistance, unit)}\n
\n
\n `;\n\n grid.appendChild(cardContainer);\n\n const chartEl = cardContainer.querySelector(`#${type}-weekly-chart`) as HTMLElement;\n const yAxisEl = cardContainer.querySelector(`#${type}-y-axis-chart`) as HTMLElement;\n\n if (!chartEl || !yAxisEl) return;\n\n chartEl.style.position = 'relative';\n\n // Use inline tooltip style\n const tooltip = document.createElement('div');\n tooltip.className = 'cur-y-val';\n tooltip.style.cssText = `color:${color};display:none`;\n chartEl.appendChild(tooltip);\n\n const xData = weeklyData.map((d) => d.timestamp / 1000);\n const yData = isDistance\n ? (weeklyData as any[]).map((d) => d.distance / 1000)\n : (weeklyData as any[]).map((d) => d.elevation);\n\n if (xData.length === 0) return;\n\n const yDataSmoothed = gaussianSmooth(yData, 2);\n\n const chartClientW = Math.max(0, (gridWidth || 570) - 20 - 320 - 20);\n const width = Math.max(chartClientW, xData.length * 40);\n const maxVal = Math.max(...yData) * 1.1;\n\n const mainOpts: uPlot.Options = {\n width: width,\n height: 150,\n padding: [10, 10, 0, 10],\n tzDate: (ts: number) => uPlot.tzDate(new Date(ts * 1000), 'Etc/UTC'),\n scales: {\n x: { time: true },\n y: { range: [0, maxVal] },\n },\n series: [\n {},\n {\n label: isDistance ? 'Distance' : 'Elevation',\n stroke: color,\n fill: hexToRgba(color, 0.1),\n width: 2,\n points: { show: true, size: 4 },\n },\n {\n label: 'Trend',\n stroke: color,\n width: 2,\n dash: [5, 5],\n points: { show: false },\n },\n ],\n cursor: {\n sync: syncKey ? { key: syncKey.key } : undefined,\n drag: { x: false, y: false },\n points: { show: false },\n x: false,\n y: false,\n },\n hooks: {\n init: [\n (u: uPlot) => {\n u.over.addEventListener('click', () => {\n const idx = u.cursor.idx;\n if (idx != null) {\n const item = weeklyData[idx];\n if (item) {\n const start = new Date(item.timestamp);\n const end = new Date(item.timestamp + 7 * 24 * 60 * 60 * 1000); // End of week\n const startStr = start.toISOString().split('T')[0];\n const endStr = end.toISOString().split('T')[0];\n onDateRangeSelect?.(startStr, endStr);\n }\n }\n });\n },\n ],\n setCursor: [\n (u: uPlot) => {\n if (!(u as any)._ready) return;\n const idx = u.cursor.idx;\n const cursorLeft = u.cursor.left;\n const cursorTop = u.cursor.top;\n\n if (\n idx != null &&\n cursorLeft != null &&\n cursorTop != null &&\n cursorLeft >= 0 &&\n cursorTop >= 0\n ) {\n const val = u.data[1][idx];\n const valStr =\n val != null ? (isDistance ? `${val.toFixed(1)} km` : `${val.toFixed(0)} m`) : '\u2014';\n\n tooltip.innerHTML = valStr;\n tooltip.style.display = 'block';\n tooltip.style.left = `${u.valToPos(u.data[0][idx]!, 'x', false)}px`;\n tooltip.style.top = `${u.valToPos(u.data[1][idx]!, 'y', false)}px`;\n tooltip.style.transform = 'translate(6px, -50%)';\n } else {\n tooltip.style.display = 'none';\n }\n\n u.redraw(false);\n },\n ],\n draw: [\n (u: uPlot) => {\n const idx = u.cursor.idx;\n if (idx != null && u.data[0][idx] != null) {\n const pcx = u.valToPos(u.data[0][idx]!, 'x', true);\n const pcy = u.valToPos(u.data[1][idx]!, 'y', true);\n const dpr = window.devicePixelRatio || 1;\n const ctx = u.ctx;\n const bb = u.bbox;\n\n // Draw lines\n ctx.save();\n ctx.beginPath();\n ctx.setLineDash([2 * dpr, 2 * dpr]);\n ctx.strokeStyle = hexToRgba(color, 0.4);\n ctx.lineWidth = 1 * dpr;\n // Vertical line\n ctx.moveTo(pcx, bb.top);\n ctx.lineTo(pcx, bb.top + bb.height);\n // Horizontal line\n ctx.moveTo(bb.left, pcy);\n ctx.lineTo(bb.left + bb.width, pcy);\n ctx.stroke();\n ctx.restore();\n\n // Draw hover halo\n ctx.save();\n ctx.beginPath();\n ctx.setLineDash([]); // Reset to solid\n ctx.arc(pcx, pcy, 12 * dpr, 0, 2 * Math.PI);\n ctx.fillStyle = hexToRgba(color, 0.2);\n ctx.fill();\n ctx.restore();\n\n // Draw center circle\n ctx.save();\n ctx.beginPath();\n ctx.arc(pcx, pcy, 6 * dpr, 0, 2 * Math.PI);\n ctx.fillStyle = color;\n ctx.fill();\n ctx.strokeStyle = '#fff';\n ctx.lineWidth = 2 * dpr;\n ctx.stroke();\n ctx.restore();\n\n // Draw axis labels\n ctx.save();\n ctx.setLineDash([]); // Reset to solid\n\n // X Label Pill\n const d = new Date(u.data[0][idx]! * 1000);\n const xValStr = `${d.getUTCFullYear()}-${String(d.getUTCMonth() + 1).padStart(2, '0')}-${String(d.getUTCDate()).padStart(2, '0')}`;\n ctx.font = `bold ${10 * dpr}px system-ui, sans-serif`;\n const xValWidth = ctx.measureText(xValStr).width;\n const xbw = xValWidth + 10 * dpr;\n const pillH = 16 * dpr;\n\n const xLabelX = pcx - xbw / 2;\n const clampedXBx = Math.max(bb.left, Math.min(bb.left + bb.width - xbw, xLabelX));\n const xLabelY = bb.top + bb.height + 2 * dpr;\n\n ctx.fillStyle = 'rgba(14, 14, 16, 0.92)';\n ctx.beginPath();\n if ((ctx as any).roundRect)\n (ctx as any).roundRect(clampedXBx, xLabelY, xbw, pillH, 3 * dpr);\n else ctx.rect(clampedXBx, xLabelY, xbw, pillH);\n ctx.fill();\n ctx.strokeStyle = hexToRgba(color, 0.27);\n ctx.lineWidth = 1 * dpr;\n ctx.stroke();\n\n ctx.fillStyle = color;\n ctx.textAlign = 'center';\n ctx.textBaseline = 'middle';\n ctx.fillText(xValStr, clampedXBx + xbw / 2, xLabelY + pillH / 2);\n\n ctx.restore();\n }\n },\n ],\n },\n axes: [\n {\n stroke: '#555564',\n grid: { stroke: '#2e2e34', width: 1 },\n show: true,\n space: 40,\n values: (self, splits) =>\n splits.map((s) => {\n const d = new Date(s * 1000);\n return `${d.getUTCMonth() + 1}/${d.getUTCDate()}`;\n }),\n font: '10px system-ui, sans-serif',\n size: 30,\n },\n {\n stroke: '#555564',\n grid: { stroke: '#2e2e34', width: 1 },\n show: true,\n values: (self, splits) => splits.map((s) => `${s.toFixed(0)} ${unit}`),\n font: '10px system-ui, sans-serif',\n size: 40,\n },\n ],\n };\n\n const mainChart = new uPlot(mainOpts, [xData, yData, yDataSmoothed], chartEl);\n evolutionPlots.push(mainChart);\n\n const scrollContainer = cardContainer.querySelector('.chart-scroll-container') as HTMLElement;\n if (scrollContainer && xData.length * 40 > width)\n scrollContainer.scrollLeft = scrollContainer.scrollWidth - scrollContainer.clientWidth;\n\n function applySeriesData() {\n weeklyData = activeGrouping === 'month' ? monthlySource : weeklySource;\n const maxTime = weeklyData[weeklyData.length - 1]?.timestamp || 1785542400000;\n let minTime = 0;\n\n if (activeRange === '1m') minTime = maxTime - 30 * 24 * 3600 * 1000;\n else if (activeRange === '2m') minTime = maxTime - 60 * 24 * 3600 * 1000;\n else if (activeRange === '6m') minTime = maxTime - 180 * 24 * 3600 * 1000;\n else if (activeRange === '1y') minTime = maxTime - 365 * 24 * 3600 * 1000;\n else if (activeRange === '2y') minTime = maxTime - 2 * 365 * 24 * 3600 * 1000;\n\n const filteredData = weeklyData.filter((d) => d.timestamp >= minTime);\n\n const newXData = filteredData.map((d) => d.timestamp / 1000);\n const newYData = isDistance\n ? (filteredData as any[]).map((d) => d.distance / 1000)\n : (filteredData as any[]).map((d) => d.elevation);\n const newYDataSmoothed = gaussianSmooth(newYData, 2);\n\n const newWidth = Math.max(chartEl.clientWidth, newXData.length * 40);\n const newMaxVal = Math.max(1, ...newYData) * 1.1;\n\n mainChart.setSize({ width: newWidth, height: 150 });\n mainChart.setScale('y', { min: 0, max: newMaxVal });\n mainChart.setData([newXData, newYData, newYDataSmoothed]);\n\n if (scrollContainer)\n scrollContainer.scrollLeft = scrollContainer.scrollWidth - scrollContainer.clientWidth;\n }\n\n return {\n updateRange: (range: string) => {\n activeRange = range;\n applySeriesData();\n },\n updateGrouping: (grouping: 'week' | 'month') => {\n activeGrouping = grouping;\n applySeriesData();\n },\n };\n}\n", "// Copyright (c) 2026, Camillo Bruni.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n\n'use strict';\n\nimport { TrackData } from '../parsers';\n\nexport interface AccumulatedData {\n currentYear: number;\n pastYear: number;\n past6m: number;\n past1m: number;\n total: number;\n currentYearAvg: number;\n pastYearAvg: number;\n past6mAvg: number;\n past1mAvg: number;\n totalAvg: number;\n}\n\nexport function computeAccumulatedMetric(\n allTracks: TrackData[],\n extractor: (t: TrackData) => number | undefined,\n): AccumulatedData {\n // Derive deterministic reference timestamp from tracks (or fixed UTC fallback)\n let refTs = 0;\n for (let i = 0; i < allTracks.length; i++) {\n const st = allTracks[i].stats?.startTime;\n if (st && st > refTs) refTs = st;\n }\n if (!refTs) refTs = Date.UTC(2026, 7, 1);\n\n const now = new Date(refTs);\n const currentYear = now.getUTCFullYear();\n\n const oneYearAgoMs = Date.UTC(\n currentYear - 1,\n now.getUTCMonth(),\n now.getUTCDate(),\n now.getUTCHours(),\n now.getUTCMinutes(),\n now.getUTCSeconds(),\n );\n const sixMonthsAgoMs = Date.UTC(\n currentYear,\n now.getUTCMonth() - 6,\n now.getUTCDate(),\n now.getUTCHours(),\n now.getUTCMinutes(),\n now.getUTCSeconds(),\n );\n const oneMonthAgoMs = Date.UTC(\n currentYear,\n now.getUTCMonth() - 1,\n now.getUTCDate(),\n now.getUTCHours(),\n now.getUTCMinutes(),\n now.getUTCSeconds(),\n );\n\n let currentYearVal = 0;\n let pastYearVal = 0;\n let past6mVal = 0;\n let past1mVal = 0;\n let totalVal = 0;\n\n let minTime = refTs;\n\n allTracks.forEach((t) => {\n if (!t.stats.startTime) return;\n const val = extractor(t);\n if (val === undefined) return;\n\n totalVal += val;\n\n const time = t.stats.startTime;\n if (time < minTime) minTime = time;\n\n const date = new Date(time);\n\n if (date.getUTCFullYear() === currentYear) currentYearVal += val;\n\n if (time >= oneYearAgoMs) pastYearVal += val;\n\n if (time >= sixMonthsAgoMs) past6mVal += val;\n\n if (time >= oneMonthAgoMs) past1mVal += val;\n });\n\n const msecPerWeek = 7 * 24 * 60 * 60 * 1000;\n const currentYearStart = Date.UTC(currentYear, 0, 1);\n\n const currentYearWeeks = Math.max(0.1, (refTs - currentYearStart) / msecPerWeek);\n const pastYearWeeks = Math.max(0.1, (refTs - oneYearAgoMs) / msecPerWeek);\n const past6mWeeks = Math.max(0.1, (refTs - sixMonthsAgoMs) / msecPerWeek);\n const past1mWeeks = Math.max(0.1, (refTs - oneMonthAgoMs) / msecPerWeek);\n const totalWeeks = Math.max(0.1, (refTs - minTime) / msecPerWeek);\n\n return {\n currentYear: currentYearVal,\n pastYear: pastYearVal,\n past6m: past6mVal,\n past1m: past1mVal,\n total: totalVal,\n currentYearAvg: currentYearVal / currentYearWeeks,\n pastYearAvg: pastYearVal / pastYearWeeks,\n past6mAvg: past6mVal / past6mWeeks,\n past1mAvg: past1mVal / past1mWeeks,\n totalAvg: totalVal / totalWeeks,\n };\n}\n\nexport function renderAccumulatedTable(\n accumulated: AccumulatedData,\n isDistance: boolean,\n unit: string,\n): string {\n const factor = isDistance ? 1000 : 1;\n const format = (val: number) => {\n const formattedVal = isDistance ? val / factor : val;\n return formattedVal.toLocaleString('en-US', {\n minimumFractionDigits: isDistance ? 1 : 0,\n maximumFractionDigits: isDistance ? 1 : 0,\n });\n };\n\n return `\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
Period${isDistance ? 'Distance' : 'Elevation'}${isDistance ? 'Distance/Week' : 'Elevation/Week'}
Current Year${format(accumulated.currentYear)} ${unit}${format(accumulated.currentYearAvg)} ${unit}
Past Year${format(accumulated.pastYear)} ${unit}${format(accumulated.pastYearAvg)} ${unit}
Past 6 Months${format(accumulated.past6m)} ${unit}${format(accumulated.past6mAvg)} ${unit}
Past 1 Month${format(accumulated.past1m)} ${unit}${format(accumulated.past1mAvg)} ${unit}
Total${format(accumulated.total)} ${unit}${format(accumulated.totalAvg)} ${unit}
\n `;\n}\n\nexport interface MultiMetricAccumulatedSummary {\n distance: AccumulatedData;\n elevation: AccumulatedData;\n durationHours: AccumulatedData;\n tss: AccumulatedData;\n workKj: AccumulatedData;\n climbCount: AccumulatedData;\n}\n\nexport function computeMultiMetricAccumulated(\n allTracks: TrackData[],\n): MultiMetricAccumulatedSummary {\n const distance = computeAccumulatedMetric(allTracks, (t) => t.stats.totalDist);\n const elevation = computeAccumulatedMetric(allTracks, (t) => t.stats.elevGain);\n const durationHours = computeAccumulatedMetric(allTracks, (t) =>\n t.stats.duration ? t.stats.duration / 3600000 : 0,\n );\n const tss = computeAccumulatedMetric(allTracks, (t) => {\n const durSec = (t.stats.duration || 0) / 1000;\n const avgPwr = t.stats.avgPower || 150;\n const ftp = 200;\n const intensityFactor = avgPwr / ftp;\n return ((durSec * avgPwr * intensityFactor) / (ftp * 3600)) * 100;\n });\n const workKj = computeAccumulatedMetric(allTracks, (t) => {\n const durSec = (t.stats.duration || 0) / 1000;\n const avgPwr = t.stats.avgPower || 0;\n return (durSec * avgPwr) / 1000;\n });\n const climbCount = computeAccumulatedMetric(allTracks, (t) => {\n const secs = t.stats.sections || [];\n return secs.filter((s) => s.kind === 'climb').length;\n });\n\n return {\n distance,\n elevation,\n durationHours,\n tss,\n workKj,\n climbCount,\n };\n}\n\nexport function renderAccumulatedProgressSummary(allTracks: TrackData[]): string {\n const summary = computeMultiMetricAccumulated(allTracks);\n const fmt = (v: number, digits = 1) =>\n v.toLocaleString('en-US', {\n minimumFractionDigits: digits,\n maximumFractionDigits: digits,\n });\n\n const rows: {\n label: string;\n unit: string;\n data: AccumulatedData;\n scale: number;\n digits: number;\n }[] = [\n { label: 'Distance', unit: 'km', data: summary.distance, scale: 0.001, digits: 1 },\n { label: 'Elevation Gain', unit: 'm', data: summary.elevation, scale: 1, digits: 0 },\n { label: 'Moving Duration', unit: 'h', data: summary.durationHours, scale: 1, digits: 1 },\n { label: 'Training Stress (TSS)', unit: 'TSS', data: summary.tss, scale: 1, digits: 0 },\n { label: 'Mechanical Work', unit: 'kJ', data: summary.workKj, scale: 1, digits: 0 },\n { label: 'Categorized Climbs', unit: 'climbs', data: summary.climbCount, scale: 1, digits: 0 },\n ];\n\n return `\n
\n
\n
\n monitoring\n Accumulated Longitudinal Progress Summary\n
\n
\n \n \n \n \n \n \n \n \n \n \n \n \n \n ${rows\n .map(\n (r) => `\n \n \n \n \n \n \n \n \n `,\n )\n .join('')}\n \n
MetricPast 1MPast 6MPast YearCurrent YearTotalWeekly Avg
${r.label}${fmt(r.data.past1m * r.scale, r.digits)} ${r.unit}${fmt(r.data.past6m * r.scale, r.digits)} ${r.unit}${fmt(r.data.pastYear * r.scale, r.digits)} ${r.unit}${fmt(r.data.currentYear * r.scale, r.digits)} ${r.unit}${fmt(r.data.total * r.scale, r.digits)} ${r.unit}${fmt(r.data.totalAvg * r.scale, r.digits)} ${r.unit}/wk
\n
\n `;\n}\n", "// Copyright (c) 2026, Camillo Bruni.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n\n'use strict';\n\n// \u2500\u2500 Main Application \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nimport { Storage } from './storage';\nimport { UrlState, AppState } from './url-state';\nimport { Parsers, TrackData, TrackPoint, calculateBounds, simplifyTrack } from './parsers';\nimport { MapView } from './map';\nimport { ChartView } from './charts';\nimport { renderDetails, initDetails, resetDetails } from './tabs/details';\nimport { renderInsights, initInsights, resetInsights } from './tabs/insights';\nimport {\n renderCombined,\n initCombined,\n resizeCombined,\n resetCombined,\n setCombinedMetrics,\n setCombinedPolar,\n setCombinedSliderProgress,\n} from './tabs/combined';\nimport {\n renderEvolution,\n resetEvolution,\n setEvolutionGrouping,\n setEvolutionRange,\n setEvolutionPinnedDuration,\n} from './tabs/progress';\nimport {\n renderCourses,\n initCourses,\n prepareBenchmarkCourses,\n getBenchmarkCourseIds,\n resetCourses,\n selectCourse,\n} from './tabs/courses';\nimport { fmtSecs, escHtml, fmtDate, getTagColor, compactId, shortRandom } from './utils';\n\nconst TRACK_COLORS: string[] = [\n '#FF6B35',\n '#4ECDC4',\n '#45B7D1',\n '#F7DC6F',\n '#FF6B6B',\n '#BB8FCE',\n '#82E0AA',\n '#F8C471',\n '#3b82f6',\n];\n\nconst POINT_ZOOM_LEVEL = 16;\n\nlet tracks: Record = {};\nlet skippedImports: { name: string; reason: string }[] = [];\n\nfunction isSameDay(t1: number, t2: number) {\n const d1 = new Date(t1);\n const d2 = new Date(t2);\n return (\n d1.getUTCFullYear() === d2.getUTCFullYear() &&\n d1.getUTCMonth() === d2.getUTCMonth() &&\n d1.getUTCDate() === d2.getUTCDate()\n );\n}\n\nlet cancelProcessing = false;\nlet selectedId: string | null = null;\nlet colorIdx = 0;\nlet followDot = false;\nlet currentMapColors: string[] | null = null;\n\n// Search/Filter state\nlet searchQuery = '';\nlet searchRegex = false;\nlet currentSort = 'date-desc';\n\ninterface Filters {\n date: [string | null, string | null];\n dist: [number | null, number | null];\n dur: [number | null, number | null];\n metrics: Set;\n tags: string[];\n sport: string | null;\n}\n\nlet filters: Filters = {\n date: [null, null],\n dist: [null, null],\n dur: [null, null],\n metrics: new Set(),\n tags: [],\n sport: null,\n};\n\nfunction syncFiltersToUrl(push: boolean = false) {\n UrlState.patch(\n {\n f_date: filters.date.every((v) => v === null) ? null : filters.date,\n f_dist: filters.dist.every((v) => v === null) ? null : filters.dist,\n f_dur: filters.dur.every((v) => v === null) ? null : filters.dur,\n f_mets: filters.metrics.size === 0 ? null : Array.from(filters.metrics),\n f_tags: filters.tags.length === 0 ? null : filters.tags,\n },\n push,\n );\n}\n\nfunction syncMetricsToUrl() {\n const active = ChartView.getActiveMetrics();\n const metrics = ChartView.METRICS;\n const abbrs = Array.from(active).map((key) => metrics[key].abbr);\n UrlState.patch({ metrics: abbrs.length ? abbrs : null });\n}\n\nconst SORT_OPTIONS = [\n { label: 'Newest first', val: 'date-desc', icon: 'calendar_today' },\n { label: 'Oldest first', val: 'date-asc', icon: 'history' },\n { label: 'Longest distance', val: 'dist-desc', icon: 'straighten' },\n { label: 'Shortest distance', val: 'dist-asc', icon: 'horizontal_rule' },\n { label: 'Longest duration', val: 'dur-desc', icon: 'timer' },\n { label: 'Shortest duration', val: 'dur-asc', icon: 'timer_off' },\n];\n\nfunction showSortMenu(anchorEl: HTMLElement) {\n document.querySelectorAll('.sort-menu-popup').forEach((el) => el.remove());\n\n const popup = document.createElement('div');\n popup.className = 'sort-menu-popup';\n\n SORT_OPTIONS.forEach((opt) => {\n const item = document.createElement('div');\n item.className = `menu-item ${currentSort === opt.val ? 'active' : ''}`;\n item.innerHTML = `\n ${opt.icon}\n ${opt.label}\n ${currentSort === opt.val ? 'check' : ''}\n `;\n item.addEventListener('click', () => {\n currentSort = opt.val;\n UrlState.patch({ sort: opt.val });\n updateSearchSortUI();\n renderTrackList();\n popup.remove();\n });\n popup.appendChild(item);\n });\n\n const rect = anchorEl.getBoundingClientRect();\n popup.style.top = `${rect.bottom + 5}px`;\n popup.style.left = `${rect.left}px`;\n document.body.appendChild(popup);\n\n const close = (e: MouseEvent) => {\n if (!popup.contains(e.target as Node) && !anchorEl.contains(e.target as Node)) {\n popup.remove();\n document.removeEventListener('click', close);\n }\n };\n setTimeout(() => document.addEventListener('click', close), 0);\n}\n\nfunction showMetricMenu(anchorEl: HTMLElement) {\n document.querySelectorAll('.metric-menu-popup').forEach((el) => el.remove());\n\n const popup = document.createElement('div');\n popup.className = 'metric-menu-popup';\n\n const metrics = ChartView.METRICS;\n const available = ChartView.getAvailableMetrics();\n const active = ChartView.getActiveMetrics();\n\n Object.entries(metrics).forEach(([key, def]) => {\n if (!available.has(key)) return;\n\n const item = document.createElement('div');\n item.className = `menu-item ${active.has(key) ? 'active' : ''}`;\n item.innerHTML = `\n ${def.icon}\n ${def.label}\n ${active.has(key) ? 'check' : ''}\n `;\n item.addEventListener('click', () => {\n ChartView.toggleMetric(key);\n const isNowActive = ChartView.getActiveMetrics().has(key);\n item.classList.toggle('active', isNowActive);\n item.querySelector('.check')!.textContent = isNowActive ? 'check' : '';\n\n const pill = document.querySelector(`#metric-pills .metric-pill[data-metric=\"${key}\"]`);\n if (pill) pill.classList.toggle('active', isNowActive);\n\n updateToolbarLayout();\n\n syncMetricsToUrl();\n });\n popup.appendChild(item);\n });\n\n const rect = anchorEl.getBoundingClientRect();\n popup.style.top = `${rect.bottom + 5}px`;\n popup.style.right = `${window.innerWidth - rect.right}px`;\n document.body.appendChild(popup);\n\n const close = (e: MouseEvent) => {\n if (!popup.contains(e.target as Node) && !anchorEl.contains(e.target as Node)) {\n popup.remove();\n document.removeEventListener('click', close);\n }\n };\n setTimeout(() => document.addEventListener('click', close), 0);\n}\n\n// \u2500\u2500 Tab Management \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\ndocument.addEventListener('select-track', (e: any) => {\n const id = e.detail.id;\n const idx = e.detail.idx;\n const dur = e.detail.dur;\n if (id && tracks[id]) {\n selectTrack(id);\n if (idx != null && dur != null) {\n const t = tracks[id];\n const pts = t.points;\n if (idx >= 0 && idx < pts.length) {\n const pIdx = pts[idx];\n if (pIdx && pIdx.time != null) {\n const t0 = pts.find((p) => p.time != null)?.time || 0;\n const tMin = (pIdx.time - t0) / 1000;\n const tMax = tMin + dur;\n\n // ChartView selection\n ChartView.restoreSelection(tMin, tMax);\n\n // Find iMax based on duration\n let iMax = pts.findIndex((p) => (p.time || 0) >= pIdx.time! + dur * 1000);\n if (iMax === -1) iMax = pts.length - 1;\n\n const selPts = pts.slice(idx, iMax + 1);\n const stats = Parsers.computeStats(selPts);\n ChartView.setSelectionStats(stats);\n\n renderInsights(t);\n\n MapView.highlightSegment(id, pts, idx, iMax, false, currentMapColors);\n MapView.ensureVisible(selPts.map((p) => [p.lat, p.lon] as [number, number]));\n\n // Update URL state\n UrlState.patch({ sel: [tMin, tMax] });\n }\n }\n }\n }\n});\n\ndocument.addEventListener('click', (e) => {\n const btn = (e.target as HTMLElement).closest('.tab-btn') as HTMLElement;\n if (!btn) return;\n const nav = btn.closest('#tab-nav');\n if (!nav) return;\n\n // Toggle buttons\n nav.querySelectorAll('.tab-btn').forEach((b) => b.classList.toggle('active', b === btn));\n\n const tab = btn.dataset.tab!;\n UrlState.patch({ tab }, true);\n\n // Toggle contents\n const panel = nav.closest('#chart-panel');\n if (panel) {\n panel.querySelectorAll('.tab-content').forEach((c) => {\n c.classList.toggle('active', c.id === `tab-${btn.dataset.tab}`);\n });\n }\n\n if (btn.dataset.tab === 'details' && selectedId && tracks[selectedId])\n renderDetails(tracks[selectedId], getGlobalTags());\n\n if (btn.dataset.tab === 'insights' && selectedId && tracks[selectedId])\n renderInsights(tracks[selectedId]);\n\n if (btn.dataset.tab === 'evolution') {\n renderEvolution(\n selectedId ? tracks[selectedId] : null,\n Object.values(tracks),\n (trackId, range) => {\n selectTrack(trackId, true);\n if (range) {\n ChartView.restoreSelection(range[0], range[1]);\n onChartRangeChange(range[0], range[1], 'time', true);\n }\n },\n (start, end) => {\n filters.date = [start, end];\n applyFilters();\n syncFiltersToUrl(true);\n updateFilterUI();\n },\n );\n }\n\n if (btn.dataset.tab === 'combined' && selectedId && tracks[selectedId])\n renderCombined(tracks[selectedId]);\n\n if (btn.dataset.tab === 'courses') renderCourses();\n\n if (btn.dataset.tab === 'graphs') {\n ChartView.resize();\n updateToolbarLayout();\n\n // Restore selection from URL\n const s = UrlState.get().sel;\n if (s) ChartView.restoreSelection(s[0], s[1]);\n }\n});\n\nasync function resetBenchmarkState() {\n if (typeof (window as any).__resetBenchmarkSeed === 'function') {\n (window as any).__resetBenchmarkSeed();\n }\n await Storage.clear();\n tracks = {};\n skippedImports = [];\n selectedId = null;\n colorIdx = 0;\n currentMapColors = null;\n cancelProcessing = false;\n followDot = false;\n searchQuery = '';\n searchRegex = false;\n currentSort = 'date-desc';\n filters.date = [null, null];\n filters.dist = [null, null];\n filters.dur = [null, null];\n filters.metrics.clear();\n filters.tags = [];\n Parsers.setHRZones([120, 140, 160, 180]);\n Parsers.setFTP(200);\n resetCourses();\n resetInsights();\n resetDetails();\n resetCombined();\n resetEvolution();\n if (MapView.removeAllTracks) MapView.removeAllTracks();\n if (MapView.clearTracks) MapView.clearTracks();\n MapView.clearHighlight();\n if (ChartView.resetState) ChartView.resetState();\n else if (ChartView.clear) ChartView.clear();\n document.querySelectorAll('.tab-btn').forEach((b) => {\n b.classList.toggle('active', (b as HTMLElement).dataset.tab === 'graphs');\n });\n document.querySelectorAll('.tab-content').forEach((c) => {\n c.classList.toggle('active', c.id === 'tab-graphs');\n });\n UrlState.reset();\n const toastContainer = document.getElementById('toast-container');\n if (toastContainer) toastContainer.innerHTML = '';\n document.querySelectorAll('.modal-backdrop').forEach((el) => el.remove());\n if (typeof renderTrackList === 'function') renderTrackList();\n}\n\n// \u2500\u2500 Boot \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nlet appSubsystemsInitialized = false;\n\nasync function initAppSubsystems() {\n if (appSubsystemsInitialized) {\n initInsights((msg: string, type?: string) =>\n showToast(msg, type === 'error' ? 'error' : 'info'),\n );\n initDetails(\n () => {\n applyFilters();\n syncFiltersToUrl();\n },\n (id: string) => {\n deleteTrack(id);\n },\n );\n initCombined();\n initCourses((msg: string, type?: string) =>\n showToast(msg, type === 'error' ? 'error' : 'info'),\n );\n updateToolbarLayout();\n await Storage.openDB();\n await resetBenchmarkState();\n return;\n }\n appSubsystemsInitialized = true;\n\n const storageSelect = document.getElementById(\n 'storage-backend-select',\n ) as HTMLSelectElement | null;\n if (storageSelect) {\n storageSelect.value = UrlState.get().storage || 'memory';\n storageSelect.addEventListener('change', (e) => {\n const selected = (e.target as HTMLSelectElement).value;\n const params = new URLSearchParams(window.location.search);\n if (selected === 'memory') {\n params.delete('storage');\n } else {\n params.set('storage', selected);\n }\n window.location.search = params.toString();\n });\n }\n\n const mapLoader = document.getElementById('map-loader');\n const mapLoaderSpan = mapLoader?.querySelector('span');\n\n if (mapLoader) {\n if (mapLoaderSpan) mapLoaderSpan.textContent = 'Loading database...';\n mapLoader.classList.remove('hidden');\n }\n\n const urlState = UrlState.get();\n\n initInsights((msg: string, type?: string) => showToast(msg, type === 'error' ? 'error' : 'info'));\n initDetails(\n () => {\n applyFilters();\n syncFiltersToUrl();\n },\n (id: string) => {\n deleteTrack(id);\n },\n );\n initCombined();\n initCourses((msg: string, type?: string) => showToast(msg, type === 'error' ? 'error' : 'info'));\n\n // Init sub-systems\n MapView.init(\n (id) => selectTrack(id),\n onMapMove,\n onMapPointClick,\n (id, idx) => {\n if (id === selectedId) ChartView.setHoverAt(idx);\n else ChartView.setHoverAt(null);\n },\n onMapDblClick,\n );\n ChartView.init(onChartCursorMove, onChartRangeChange, onChartClick, (idx) => {\n UrlState.patch({ sel_point: idx });\n });\n\n ChartView.setMapColorChangeCb((data) => {\n if (data) {\n currentMapColors = data.colors;\n if (selectedId) MapView.colorTrackByMetric(selectedId, data.pts, data.colors);\n } else {\n currentMapColors = null;\n if (selectedId) MapView.clearMetricColor(selectedId);\n }\n\n // Refresh highlight with new colors if a selection exists\n const currentUrlState = UrlState.get();\n if (currentUrlState.sel)\n onChartRangeChange(currentUrlState.sel[0], currentUrlState.sel[1], ChartView.getXAxis());\n });\n\n applyState(urlState);\n\n // Reset persistent storage and in-memory state for clean multi-iteration benchmark executions\n await Storage.openDB();\n await resetBenchmarkState();\n if (mapLoader) mapLoader.classList.add('hidden');\n\n // Panel resizers\n initResizeHandle();\n initSidebarResizer();\n initMetricPillDraggable();\n updateToolbarLayout();\n\n // Basemap switcher\n document.getElementById('basemap-switcher')?.addEventListener('click', (e) => {\n const btn = (e.target as HTMLElement).closest('.bm-btn') as HTMLElement;\n if (!btn) return;\n document.querySelectorAll('.bm-btn').forEach((b) => b.classList.remove('active'));\n btn.classList.add('active');\n if (btn.dataset.layer) {\n MapView.switchBasemap(btn.dataset.layer);\n UrlState.patch({ map: btn.dataset.layer });\n }\n });\n\n // Fit all\n document.getElementById('btn-fit-all')?.addEventListener('click', () => MapView.fitAll());\n\n // Clear all\n document.getElementById('btn-clear-all')?.addEventListener('click', clearAll);\n\n // Search\n document.getElementById('track-search')?.addEventListener('input', (e) => {\n searchQuery = (e.target as HTMLInputElement).value;\n applyFilters();\n UrlState.patch({ q: searchQuery || null });\n });\n\n document.getElementById('btn-regex-toggle')?.addEventListener('click', (e) => {\n searchRegex = !searchRegex;\n (e.target as HTMLElement).classList.toggle('active', searchRegex);\n applyFilters();\n UrlState.patch({ re: searchRegex ? 1 : null });\n });\n\n // Sort\n document.getElementById('btn-sort-menu')?.addEventListener('click', (e) => {\n e.stopPropagation();\n showSortMenu(e.currentTarget as HTMLElement);\n });\n\n // Metric menu toggle\n document.getElementById('btn-metric-menu')?.addEventListener('click', (e) => {\n e.stopPropagation();\n showMetricMenu(e.currentTarget as HTMLElement);\n });\n\n // Metric toggles (shared logic)\n const toggleMetric = (metric: string, pill: HTMLElement | null) => {\n if (pill) pill.classList.toggle('active');\n ChartView.toggleMetric(metric);\n syncMetricsToUrl();\n };\n\n document.getElementById('metric-pills')?.addEventListener('click', (e) => {\n const pill = (e.target as HTMLElement).closest('.metric-pill') as HTMLElement;\n if (pill) {\n const metric = pill.dataset.metric;\n if (metric) {\n toggleMetric(metric, pill);\n\n // Scroll to it if it became active\n if (ChartView.getActiveMetrics().has(metric)) {\n const targetEl = document.querySelector(`.chart-row[data-metric=\"${metric}\"]`);\n targetEl?.scrollIntoView({ behavior: 'smooth' });\n }\n }\n }\n });\n\n // X-axis toggle\n document.getElementById('x-axis-ctrl')?.addEventListener('click', (e) => {\n const btn = (e.target as HTMLElement).closest('.seg-btn') as HTMLElement;\n if (!btn) return;\n const axis = btn.dataset.axis!;\n ChartView.setXAxis(axis);\n document\n .querySelectorAll('#x-axis-ctrl .seg-btn')\n .forEach((b) => b.classList.toggle('active', b === btn));\n UrlState.patch({ xaxis: axis });\n });\n\n // Follow dot\n document.getElementById('btn-follow-dot')?.addEventListener('click', (e) => {\n followDot = !followDot;\n (e.currentTarget as HTMLElement).classList.toggle('active', followDot);\n });\n\n // Reset zoom\n document.getElementById('btn-reset-zoom')?.addEventListener('click', () => {\n ChartView.resetZoom();\n MapView.clearHighlight();\n });\n\n // File drag/drop\n initDropZone();\n\n // File browse\n document.getElementById('file-input')?.addEventListener('change', (e) => {\n handleFiles(Array.from((e.target as HTMLInputElement).files || []));\n (e.target as HTMLInputElement).value = '';\n });\n document.getElementById('folder-input')?.addEventListener('change', (e) => {\n handleFiles(Array.from((e.target as HTMLInputElement).files || []));\n (e.target as HTMLInputElement).value = '';\n });\n\n // Filters\n document.getElementById('btn-toggle-filters')?.addEventListener('click', () => {\n const panel = document.getElementById('filter-panel');\n if (panel) {\n panel.classList.toggle('hidden');\n document\n .getElementById('btn-toggle-filters')\n ?.classList.toggle('active', !panel.classList.contains('hidden'));\n }\n });\n\n const onFilterInput = (type: 'date' | 'dist' | 'dur', idx: number, e: Event) => {\n const val = (e.target as HTMLInputElement).value;\n (filters[type] as any)[idx] = val === '' ? null : val;\n applyFilters();\n syncFiltersToUrl();\n };\n\n document\n .getElementById('filter-date-start')\n ?.addEventListener('change', (e) => onFilterInput('date', 0, e));\n document\n .getElementById('filter-date-end')\n ?.addEventListener('change', (e) => onFilterInput('date', 1, e));\n document\n .getElementById('filter-dist-min')\n ?.addEventListener('input', (e) => onFilterInput('dist', 0, e));\n document\n .getElementById('filter-dist-max')\n ?.addEventListener('input', (e) => onFilterInput('dist', 1, e));\n document\n .getElementById('filter-dur-min')\n ?.addEventListener('input', (e) => onFilterInput('dur', 0, e));\n document\n .getElementById('filter-dur-max')\n ?.addEventListener('input', (e) => onFilterInput('dur', 1, e));\n\n const burgerBtn = document.getElementById('sport-burger-btn');\n const dropdownContent = document.getElementById('sport-dropdown-content');\n burgerBtn?.addEventListener('click', (e) => {\n e.stopPropagation();\n if (dropdownContent) {\n const isVisible = dropdownContent.style.display === 'block';\n dropdownContent.style.display = isVisible ? 'none' : 'block';\n }\n });\n\n document.addEventListener('click', (e) => {\n if (dropdownContent && dropdownContent.style.display === 'block') {\n if (!dropdownContent.contains(e.target as Node) && e.target !== burgerBtn)\n dropdownContent.style.display = 'none';\n }\n });\n\n document.getElementById('sport-filter-container')?.addEventListener('click', (e) => {\n const target = e.target as HTMLElement;\n const btn = target.closest('[data-sport]');\n if (btn) {\n const selectedSport = (btn as HTMLElement).dataset.sport || null;\n filters.sport = filters.sport === selectedSport ? null : selectedSport;\n applyFilters();\n syncFiltersToUrl();\n updateFilterUI();\n\n if (target.closest('.dropdown-item')) {\n if (dropdownContent) dropdownContent.style.display = 'none';\n }\n }\n });\n\n document.getElementById('filter-metrics')?.addEventListener('click', (e) => {\n const btn = (e.target as HTMLElement).closest('.mini-pill') as HTMLElement;\n if (!btn) return;\n const m = btn.dataset.metric!;\n if (filters.metrics.has(m)) filters.metrics.delete(m);\n else filters.metrics.add(m);\n updateFilterUI();\n applyFilters();\n syncFiltersToUrl();\n });\n\n document.getElementById('btn-reset-filters')?.addEventListener('click', () => {\n filters = {\n date: [null, null],\n dist: [null, null],\n dur: [null, null],\n metrics: new Set(),\n tags: [],\n sport: null,\n };\n updateFilterUI();\n applyFilters();\n syncFiltersToUrl();\n });\n\n document.getElementById('filter-tags')?.addEventListener('input', (e) => {\n const val = (e.target as HTMLInputElement).value;\n filters.tags = val\n .split(',')\n .map((s) => s.trim().toLowerCase())\n .filter((s) => !!s);\n applyFilters();\n syncFiltersToUrl();\n });\n\n // Keyboard shortcuts\n document.addEventListener('keydown', (e) => {\n if (e.key === 'Escape') {\n MapView.hideCursor();\n MapView.closePopup?.();\n // Dismiss popups and modals\n document\n .querySelectorAll(\n '.color-picker-popup, .metric-menu-popup, .sort-menu-popup, .modal-backdrop',\n )\n .forEach((el) => el.remove());\n }\n });\n\n // Resize charts when window changes\n window.addEventListener('resize', () => {\n ChartView.resize();\n resizeCombined();\n MapView.invalidateSize?.();\n updateToolbarLayout();\n });\n}\n\nasync function init() {\n // Wait for fonts to be loaded with a cleaned-up timeout\n let fontTimerId: number | null = null;\n const fontTimeout = new Promise((resolve) => {\n fontTimerId = window.setTimeout(resolve, 1000);\n });\n try {\n const fontLoadPromise = document.fonts\n ? Promise.all([\n document.fonts.load('24px \"Material Symbols Rounded\"').catch(() => {}),\n document.fonts.ready,\n ])\n : Promise.resolve();\n await Promise.race([fontLoadPromise, fontTimeout]);\n } finally {\n if (fontTimerId !== null) window.clearTimeout(fontTimerId);\n }\n document.body.classList.remove('loading-fonts');\n\n initBenchmarkTriggers();\n await preloadBenchmarkFiles();\n\n // Signal to Speedometer test harness that asynchronous initialization has completed\n // and benchmark step triggers are bound.\n const readyEl = document.createElement('div');\n readyEl.id = 'app-is-ready';\n readyEl.setAttribute('data-ready', 'true');\n readyEl.style.display = 'none';\n document.body.appendChild(readyEl);\n\n const camelReadyEl = document.createElement('div');\n camelReadyEl.id = 'appIsReady';\n camelReadyEl.setAttribute('data-ready', 'true');\n camelReadyEl.style.display = 'none';\n document.body.appendChild(camelReadyEl);\n}\n\nlet preloadedBenchmarkPayloads:\n | {\n cleanName: string;\n buf: ArrayBuffer;\n text?: string;\n file: File;\n }[]\n | null = null;\n\nasync function preloadBenchmarkFiles(): Promise<\n { cleanName: string; buf: ArrayBuffer; text?: string; file: File }[]\n> {\n if (preloadedBenchmarkPayloads) return preloadedBenchmarkPayloads;\n try {\n const manifestResp = await fetch('./test-data/manifest.json');\n const fileNames: string[] = await manifestResp.json();\n const decoder = new TextDecoder();\n preloadedBenchmarkPayloads = await Promise.all(\n fileNames.map(async (name) => {\n const fileResp = await fetch(`./test-data/${name}`);\n let buf = await fileResp.arrayBuffer();\n let cleanName = name;\n if (name.toLowerCase().endsWith('.gz')) {\n cleanName = name.slice(0, -3);\n const bytes = new Uint8Array(buf);\n if (bytes.length >= 2 && bytes[0] === 0x1f && bytes[1] === 0x8b) {\n const ds = new DecompressionStream('gzip');\n const stream = new Blob([buf]).stream().pipeThrough(ds);\n buf = await new Response(stream).arrayBuffer();\n }\n }\n const text = cleanName.toLowerCase().endsWith('.fit') ? undefined : decoder.decode(buf);\n const file = new File([buf], cleanName, { type: 'application/octet-stream' });\n (file as any)._buf = buf;\n if (text !== undefined) (file as any)._text = text;\n return { cleanName, buf, text, file };\n }),\n );\n if (typeof (window as any).__resetBenchmarkSeed === 'function') {\n (window as any).__resetBenchmarkSeed();\n }\n return preloadedBenchmarkPayloads;\n } catch {\n return [];\n }\n}\n\nfunction initBenchmarkTriggers() {\n const btnLoad = document.getElementById('btn-step-load');\n const btnAnalysis = document.getElementById('btn-step-analysis');\n const btnInsights = document.getElementById('btn-step-insights');\n const btnDetails = document.getElementById('btn-step-details');\n const btnAggregate = document.getElementById('btn-step-aggregate');\n const btnEvolution = document.getElementById('btn-step-evolution');\n const btnCourses = document.getElementById('btn-step-courses');\n\n function activateTab(tabName: string) {\n document.querySelectorAll('.tab-btn').forEach((b) => {\n b.classList.toggle('active', (b as HTMLElement).dataset.tab === tabName);\n });\n document.querySelectorAll('.tab-content').forEach((c) => {\n c.classList.toggle('active', c.id === `tab-${tabName}`);\n });\n UrlState.patch({ tab: tabName }, false);\n }\n\n btnLoad?.addEventListener('click', async () => {\n try {\n await initAppSubsystems();\n const payloads = await preloadBenchmarkFiles();\n const loadedFiles: File[] = payloads.map((p) => p.file);\n await handleFiles(loadedFiles, true);\n const allTracks = Object.values(tracks);\n Parsers.findOverlaps(allTracks);\n\n // Exercise sidebar track list sorting and filtering across distinct criteria\n for (const sortMode of ['dist-desc', 'dur-desc', 'date-asc', 'date-desc']) {\n currentSort = sortMode;\n renderTrackList();\n }\n filters.sport = 'cycling';\n applyFilters();\n filters.sport = null;\n filters.metrics.add('power');\n applyFilters();\n filters.metrics.clear();\n searchQuery = 'Alpine';\n applyFilters();\n searchQuery = '';\n applyFilters();\n\n if (!selectedId && allTracks.length > 0) selectTrack(allTracks[0].id, true, true);\n } finally {\n window.dispatchEvent(new CustomEvent('load-and-parse-complete'));\n }\n });\n\n btnAnalysis?.addEventListener('click', () => {\n try {\n const allTracks = Object.values(tracks);\n activateTab('graphs');\n if (!selectedId && allTracks.length > 0) {\n selectTrack(allTracks[0].id, false, true);\n }\n const availableMetrics = ChartView.getAvailableMetrics();\n const metricSets = [['elevation', 'speed', 'power']];\n for (const mSet of metricSets) {\n const filtered = mSet.filter((m) => availableMetrics.has(m));\n if (filtered.length > 0) {\n document.querySelectorAll('#metric-pills .metric-pill').forEach((el) => {\n const metric = (el as HTMLElement).dataset.metric;\n if (metric) el.classList.toggle('active', filtered.includes(metric));\n });\n updateToolbarLayout();\n ChartView.setActiveMetrics(filtered);\n }\n }\n\n // Exercise smoothing, map metric coloring, and X-axis mode switching\n ChartView.setSmooth('power', true, true);\n ChartView.toggleMapColor('power', true);\n ChartView.toggleMapColor(null, true);\n ChartView.setXAxis('distance');\n ChartView.setSmooth('power', false, true);\n ChartView.setXAxis('time');\n\n // Analyze distinct sections of the track (Opening Climb & Main Analysis Window)\n const sections: [number, number, number][] = [\n [0, 360, 180],\n [120, 900, 300],\n ];\n for (const [secStart, secEnd, cursorIdx] of sections) {\n ChartView.restoreSelection(secStart, secEnd, true);\n ChartView.setCursorAt(cursorIdx, true);\n ChartView.analyzeTelemetryIntervals(allTracks, secStart, secEnd);\n onChartRangeChange(secStart, secEnd, ChartView.getXAxis(), false);\n }\n } finally {\n window.dispatchEvent(new CustomEvent('analysis-complete'));\n }\n });\n\n btnInsights?.addEventListener('click', () => {\n try {\n const allTracks = Object.values(tracks);\n allTracks.forEach((t) => Parsers.ensureCurvesAndZones(t));\n activateTab('insights');\n const primaryTrack = (selectedId && tracks[selectedId]) || allTracks[0] || null;\n // Analyze insights across GPX, TCX, and FIT tracks and across distinct track sections (Opening, Middle, Final thirds, Full)\n for (const tIdx of [1, 2, 4]) {\n const trk = allTracks[tIdx];\n if (trk) {\n renderInsights(trk, allTracks, [0, 600]);\n renderInsights(trk, allTracks, null);\n }\n }\n if (allTracks[3]) {\n renderInsights(allTracks[3], allTracks, [200, 900]);\n }\n if (primaryTrack) {\n renderInsights(primaryTrack, allTracks, [0, 600]);\n renderInsights(primaryTrack, allTracks, [600, 1200]);\n }\n renderInsights(primaryTrack, allTracks, null);\n } finally {\n window.dispatchEvent(new CustomEvent('insights-complete'));\n }\n });\n\n btnDetails?.addEventListener('click', () => {\n try {\n const allTracks = Object.values(tracks);\n activateTab('details');\n const globalTags = getGlobalTags();\n const primaryTrack = (selectedId && tracks[selectedId]) || allTracks[0] || null;\n // Render and analyze telemetry details, splits, gear matrices, and zones across all 6 tracks and terrain sections\n for (let i = 0; i < allTracks.length; i++) {\n renderDetails(allTracks[i], globalTags, allTracks, null);\n }\n if (primaryTrack) {\n renderDetails(primaryTrack, globalTags, allTracks, [0, 450]);\n renderDetails(primaryTrack, globalTags, allTracks, [450, 950]);\n renderDetails(primaryTrack, globalTags, allTracks, [950, 1450]);\n }\n if (allTracks[1]) {\n renderDetails(allTracks[1], globalTags, allTracks, [150, 750]);\n }\n if (allTracks[2]) {\n renderDetails(allTracks[2], globalTags, allTracks, [200, 800]);\n }\n if (allTracks[3]) {\n renderDetails(allTracks[3], globalTags, allTracks, [200, 900]);\n }\n if (allTracks[4]) {\n renderDetails(allTracks[4], globalTags, allTracks, [0, 600]);\n renderDetails(allTracks[4], globalTags, allTracks, [600, 1200]);\n }\n if (allTracks[5]) {\n renderDetails(allTracks[5], globalTags, allTracks, [150, 750]);\n }\n renderDetails(primaryTrack, globalTags, allTracks, null);\n } finally {\n window.dispatchEvent(new CustomEvent('details-complete'));\n }\n });\n\n btnAggregate?.addEventListener('click', () => {\n try {\n const allTracks = Object.values(tracks);\n if (allTracks.length >= 2) {\n activateTab('combined');\n // 1. Exercise single-track 2D scatter/trail mode across metric pairs, Polar & Cartesian, and timeline sections\n setCombinedMetrics('distance', 'elevation');\n renderCombined(allTracks[0], allTracks);\n for (const frac of [0.25, 0.5, 0.75, 1.0]) {\n setCombinedSliderProgress(frac);\n }\n setCombinedPolar(true);\n setCombinedSliderProgress(0.5);\n setCombinedPolar(false);\n setCombinedMetrics('power', 'hr');\n for (const frac of [0.3, 0.7]) {\n setCombinedSliderProgress(frac);\n }\n\n // 2. Exercise multi-track comparison mode across all 6 tracks and a selected pair\n if (allTracks.length >= 6) {\n renderCombined(allTracks, allTracks);\n }\n const selectedTwo = [allTracks[0], allTracks[1]];\n renderCombined(selectedTwo, allTracks);\n }\n } finally {\n window.dispatchEvent(new CustomEvent('aggregate-complete'));\n }\n });\n\n btnEvolution?.addEventListener('click', () => {\n try {\n const allTracks = Object.values(tracks);\n activateTab('evolution');\n const primaryTrack = (selectedId && tracks[selectedId]) || allTracks[0] || null;\n const onSelectCb = (trackId: string, range: [number, number] | null) => {\n selectTrack(trackId, true);\n if (range) {\n ChartView.restoreSelection(range[0], range[1]);\n onChartRangeChange(range[0], range[1], 'time', true);\n }\n };\n\n // Exercise evolution view across different reference tracks, groupings ('month' & 'week'), time ranges, and pinned durations\n const refTracks = [allTracks[2] || primaryTrack, primaryTrack];\n for (const refTrk of refTracks) {\n renderEvolution(refTrk, allTracks, onSelectCb);\n setEvolutionGrouping('month');\n for (const rng of ['1m', '6m', '1y', 'all']) {\n setEvolutionRange(rng);\n }\n setEvolutionGrouping('week');\n for (const dur of [5, 60, 300, 1200, null]) {\n setEvolutionPinnedDuration(dur);\n }\n }\n } finally {\n window.dispatchEvent(new CustomEvent('evolution-complete'));\n }\n });\n\n btnCourses?.addEventListener('click', () => {\n try {\n const allTracks = Object.values(tracks);\n activateTab('courses');\n prepareBenchmarkCourses(allTracks);\n const courseIds = getBenchmarkCourseIds();\n for (const cId of courseIds) {\n selectCourse(cId, true);\n }\n selectCourse('course-1', true);\n } finally {\n window.dispatchEvent(new CustomEvent('courses-complete'));\n }\n });\n\n (window as any).benchmarkSteps = {\n loadAndParseFiles: () => document.getElementById('btn-step-load')?.click(),\n displayAnalysisMetrics: () => document.getElementById('btn-step-analysis')?.click(),\n displayInsights: () => document.getElementById('btn-step-insights')?.click(),\n displayDetails: () => document.getElementById('btn-step-details')?.click(),\n displayAggregateMetrics: () => document.getElementById('btn-step-aggregate')?.click(),\n displayEvolutionMetrics: () => document.getElementById('btn-step-evolution')?.click(),\n displayCourses: () => document.getElementById('btn-step-courses')?.click(),\n };\n}\n\nasync function processAndLoadTrack(t: TrackData) {\n // Migration: re-calculate stats if new insights are missing or old format\n const powerCurveEntries = t.stats.powerCurve ? Object.values(t.stats.powerCurve) : [];\n\n const needsEnrich =\n t.points.length > 1 && (t.points[1].speed === undefined || t.points[1].gradient === undefined);\n\n const needsRecalc =\n needsEnrich ||\n !t.stats.powerCurve ||\n !t.stats.hrZones ||\n !t.stats.hrCurve ||\n t.stats.shifts === undefined ||\n t.stats.avgBattery === undefined ||\n t.stats.avgTemp === undefined ||\n (powerCurveEntries.length > 0 && typeof (powerCurveEntries[0] as any) === 'number') ||\n ((t.stats.duration || 0) > 10800 * 1000 && t.stats.powerCurve && !t.stats.powerCurve[14400]);\n\n if (needsRecalc) {\n if (needsEnrich) t.points = Parsers.enrichPoints(t.points);\n t.stats = Parsers.computeStats(t.points);\n await Storage.save(t);\n }\n\n tracks[t.id] = t;\n colorIdx = Math.max(colorIdx, TRACK_COLORS.indexOf(t.color) + 1);\n MapView.addTrack(t);\n MapView.setTrackVisible(t.id, t.visible && !t._filtered);\n renderTrackList(); // Progressive list update\n}\n\nfunction initResizeHandle() {\n const handle = document.getElementById('resize-handle');\n const panel = document.getElementById('chart-panel');\n if (!handle || !panel) return;\n\n let isDragging = false;\n\n handle.addEventListener('mousedown', () => {\n isDragging = true;\n document.body.style.cursor = 'ns-resize';\n });\n\n document.addEventListener('mousemove', (e) => {\n if (!isDragging) return;\n const h = window.innerHeight - e.clientY;\n const clampedH = Math.max(100, Math.min(window.innerHeight - 100, h));\n panel.style.height = `${clampedH}px`;\n ChartView.resize();\n resizeCombined();\n MapView.invalidateSize?.();\n });\n\n document.addEventListener('mouseup', () => {\n if (!isDragging) return;\n isDragging = false;\n document.body.style.cursor = '';\n });\n}\n\nfunction initSidebarResizer() {\n const resizer = document.getElementById('sidebar-resizer');\n const sidebar = document.getElementById('sidebar');\n if (!resizer || !sidebar) return;\n\n let isDragging = false;\n\n resizer.addEventListener('mousedown', () => {\n isDragging = true;\n resizer.classList.add('dragging');\n document.body.classList.add('dragging-sidebar');\n });\n\n document.addEventListener('mousemove', (e) => {\n if (!isDragging) return;\n const w = e.clientX;\n const clampedW = Math.max(200, Math.min(600, w));\n sidebar.style.width = `${clampedW}px`;\n sidebar.style.setProperty('--sidebar-w', `${clampedW}px`);\n ChartView.resize();\n MapView.invalidateSize?.();\n });\n\n document.addEventListener('mouseup', () => {\n if (!isDragging) return;\n isDragging = false;\n resizer.classList.remove('dragging');\n document.body.classList.remove('dragging-sidebar');\n });\n}\n\n// \u2500\u2500 Event Handlers \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nfunction onMapMove(lat: number, lng: number, zoom: number) {\n UrlState.patch({ map_pos: [lat, lng, zoom] });\n}\n\nfunction onMapPointClick(id: string, idx: number) {\n selectTrack(id, false);\n ChartView.setCursorAt(idx);\n}\n\nfunction onMapDblClick() {\n // MapView.fitAll();\n}\n\nfunction onChartCursorMove(pt: TrackPoint | null) {\n if (pt && pt.lat != null && pt.lon != null) {\n MapView.showCursorAt(pt.lat, pt.lon);\n if (followDot) MapView.centerOn(pt.lat, pt.lon, null, true);\n } else {\n MapView.hideCursor();\n }\n}\n\nfunction onChartRangeChange(min: number | null, max: number | null, axis: string, fit = false) {\n if (!selectedId || !tracks[selectedId]) return;\n const t = tracks[selectedId];\n\n if (min === null || max === null) {\n MapView.clearHighlight();\n if (t && (UrlState.get().tab || 'graphs') === 'insights') renderInsights(t);\n UrlState.patch({ sel: null });\n return;\n }\n\n // Find point indices\n const pts = t.points;\n let iMin = 0,\n iMax = pts.length - 1;\n\n if (axis === 'distance') {\n const d0 = min * 1000,\n d1 = max * 1000;\n iMin = pts.findIndex((p) => (p.dist || 0) >= d0);\n iMax = pts.findIndex((p) => (p.dist || 0) >= d1);\n } else {\n const t0 = pts.find((p) => p.time != null)?.time || 0;\n const ts0 = t0 + min * 1000,\n ts1 = t0 + max * 1000;\n iMin = pts.findIndex((p) => (p.time || 0) >= ts0);\n iMax = pts.findIndex((p) => (p.time || 0) >= ts1);\n }\n\n if (iMin === -1) iMin = 0;\n if (iMax === -1) iMax = pts.length - 1;\n\n // Compute selection stats\n const selPts = pts.slice(iMin, iMax + 1);\n const stats = Parsers.computeStats(selPts);\n ChartView.setSelectionStats(stats);\n if ((UrlState.get().tab || 'graphs') === 'insights') renderInsights(t);\n\n MapView.highlightSegment(selectedId, pts, iMin, iMax, fit, currentMapColors);\n MapView.ensureVisible(selPts.map((p) => [p.lat, p.lon] as [number, number]));\n UrlState.patch({ sel: [min, max] });\n}\n\nfunction onChartClick(pt: TrackPoint, idx: number) {\n if (pt && pt.lat != null && pt.lon != null) MapView.centerOn(pt.lat, pt.lon, POINT_ZOOM_LEVEL);\n}\n\nexport function selectTrack(id: string, fit = true, deferChartRender = false) {\n if (!tracks[id]) return;\n if (id === selectedId) {\n if (fit) MapView.fitTrack(id);\n return;\n }\n\n const hadSelected = selectedId !== null;\n if (hadSelected) {\n // Clear current views immediately before loading new data\n ChartView.clear();\n renderDetails(null, []);\n renderInsights(null);\n }\n\n selectedId = id;\n\n // Fully reset metric coloring when switching tracks\n currentMapColors = null;\n MapView.clearMetricColor(id);\n if (hadSelected) {\n ChartView.toggleMapColor(null); // Ensure ChartView state also resets\n }\n\n // Clear selection on new track\n UrlState.patch({ track: id, sel: null }, true);\n if (hadSelected) {\n ChartView.cancelSelection();\n }\n\n // Update UI immediately\n renderTrackList();\n\n // Render details\n const activeTab = UrlState.get().tab || 'graphs';\n if (activeTab === 'details') renderDetails(tracks[id], getGlobalTags());\n if (activeTab === 'insights') renderInsights(tracks[id]);\n if (activeTab === 'combined') renderCombined(tracks[id]);\n if (activeTab === 'courses') renderCourses();\n if (activeTab === 'evolution') {\n renderEvolution(tracks[id], Object.values(tracks), (trackId, range) => {\n selectTrack(trackId, true);\n if (range) {\n ChartView.restoreSelection(range[0], range[1]);\n onChartRangeChange(range[0], range[1], 'time', true);\n }\n });\n }\n\n // Tell sub-views\n MapView.setSelectedTrack(id, fit);\n if (fit) MapView.fitTrack(id);\n ChartView.loadTrack(tracks[id], Object.values(tracks), deferChartRender);\n}\n\nasync function deleteTrack(id: string) {\n await Storage.remove(id);\n MapView.removeTrack(id);\n delete tracks[id];\n if (selectedId === id) {\n selectedId = null;\n ChartView.clear();\n renderDetails(null, []);\n renderInsights(null);\n UrlState.patch({ track: null, sel: null, sel_point: null });\n }\n applyFilters();\n}\n\nasync function clearAll() {\n const code = Math.floor(10 + Math.random() * 90).toString();\n\n const backdrop = document.createElement('div');\n backdrop.className = 'modal-backdrop';\n\n backdrop.innerHTML = `\n
\n
Delete all tracks?
\n
\n This will permanently remove all tracks from your browser storage. This action cannot be undone.\n
\n
\n
Enter \"${code}\" to confirm:
\n \n
\n
\n \n \n
\n
\n `;\n\n document.body.appendChild(backdrop);\n\n const input = backdrop.querySelector('.modal-input') as HTMLInputElement;\n const btnConfirm = backdrop.querySelector('.modal-btn.confirm') as HTMLButtonElement;\n const btnCancel = backdrop.querySelector('.modal-btn.cancel') as HTMLButtonElement;\n\n input.focus();\n\n input.addEventListener('input', () => {\n btnConfirm.disabled = input.value !== code;\n });\n\n const close = () => backdrop.remove();\n\n btnCancel.addEventListener('click', close);\n backdrop.addEventListener('click', (e) => {\n if (e.target === backdrop) close();\n });\n\n btnConfirm.addEventListener('click', async () => {\n if (input.value !== code) return;\n close();\n\n cancelProcessing = true;\n await Storage.clear();\n\n tracks = {};\n selectedId = null;\n MapView.clearHighlight();\n window.location.reload();\n });\n}\n\nfunction applyFilters(skipRender = false) {\n const ids = Object.keys(tracks);\n ids.forEach((id) => {\n const t = tracks[id];\n const s = t.stats;\n let visible = true;\n\n // Search query\n if (searchQuery) {\n if (searchRegex) {\n try {\n const re = new RegExp(searchQuery, 'i');\n if (!re.test(t.name)) visible = false;\n } catch (e) {\n visible = false;\n }\n } else {\n if (!t.name.toLowerCase().includes(searchQuery.toLowerCase())) visible = false;\n }\n }\n\n // Date filter\n if (visible && (filters.date[0] || filters.date[1])) {\n const start = filters.date[0] ? new Date(filters.date[0]).getTime() : 0;\n const end = filters.date[1] ? new Date(filters.date[1]).getTime() : Infinity;\n const t0 = s.startTime || 0;\n if (t0 < start || t0 > end) visible = false;\n }\n\n // Distance filter\n if (visible && (filters.dist[0] !== null || filters.dist[1] !== null)) {\n const min = (filters.dist[0] || 0) * 1000;\n const max = (filters.dist[1] || Infinity) * 1000;\n if (s.totalDist < min || s.totalDist > max) visible = false;\n }\n\n // Duration filter\n if (visible && (filters.dur[0] !== null || filters.dur[1] !== null)) {\n const min = (filters.dur[0] || 0) * 60000;\n const max = (filters.dur[1] || Infinity) * 60000;\n const d = s.duration || 0;\n if (d < min || d > max) visible = false;\n }\n\n // Metrics filter\n if (visible && filters.metrics.size > 0) {\n for (const m of filters.metrics) {\n const field = ChartView.METRICS[m].field;\n if (!t.points.some((p: any) => p[field] != null)) {\n visible = false;\n break;\n }\n }\n }\n\n // Sport filter\n if (visible && filters.sport) {\n if (t.sport !== filters.sport) visible = false;\n }\n\n // Tags filter\n if (visible && filters.tags.length > 0) {\n const trackTags = t.tags || [];\n if (!filters.tags.every((ft) => trackTags.includes(ft))) visible = false;\n }\n\n t._filtered = !visible;\n MapView.setTrackVisible(t.id, t.visible && visible);\n });\n\n if (!skipRender) renderTrackList();\n}\n\nfunction updateFilterUI() {\n (document.getElementById('filter-date-start') as HTMLInputElement).value = filters.date[0] || '';\n (document.getElementById('filter-date-end') as HTMLInputElement).value = filters.date[1] || '';\n (document.getElementById('filter-dist-min') as HTMLInputElement).value =\n filters.dist[0] !== null ? String(filters.dist[0]) : '';\n (document.getElementById('filter-dist-max') as HTMLInputElement).value =\n filters.dist[1] !== null ? String(filters.dist[1]) : '';\n (document.getElementById('filter-dur-min') as HTMLInputElement).value =\n filters.dur[0] !== null ? String(filters.dur[0]) : '';\n (document.getElementById('filter-dur-max') as HTMLInputElement).value =\n filters.dur[1] !== null ? String(filters.dur[1]) : '';\n (document.getElementById('filter-tags') as HTMLInputElement).value = filters.tags.join(', ');\n\n document\n .querySelectorAll('#sport-filter-container .mini-pill, #sport-dropdown-content .dropdown-item')\n .forEach((el) => {\n const item = el as HTMLElement;\n item.classList.toggle('active', (item.dataset.sport || '') === (filters.sport || ''));\n });\n\n document.querySelectorAll('#filter-metrics .mini-pill').forEach((el) => {\n const btn = el as HTMLElement;\n btn.classList.toggle('active', filters.metrics.has(btn.dataset.metric!));\n });\n\n const activeCount =\n (filters.date[0] ? 1 : 0) +\n (filters.date[1] ? 1 : 0) +\n (filters.dist[0] !== null ? 1 : 0) +\n (filters.dist[1] !== null ? 1 : 0) +\n (filters.dur[0] !== null ? 1 : 0) +\n (filters.dur[1] !== null ? 1 : 0) +\n filters.metrics.size +\n (filters.tags.length > 0 ? 1 : 0);\n\n const badge = document.querySelector('#btn-toggle-filters .btn-badge');\n if (badge) {\n badge.textContent = activeCount > 0 ? String(activeCount) : '';\n badge.classList.toggle('hidden', activeCount === 0);\n }\n}\n\nfunction updateSearchSortUI() {\n const searchInput = document.getElementById('track-search') as HTMLInputElement;\n if (searchInput) searchInput.value = searchQuery;\n document.getElementById('btn-regex-toggle')?.classList.toggle('active', searchRegex);\n\n const opt = SORT_OPTIONS.find((o) => o.val === currentSort);\n if (opt) {\n const labelEl = document.getElementById('sort-current-label');\n const iconEl = document.getElementById('sort-current-icon');\n if (labelEl)\n labelEl.textContent = opt.label\n .replace(' first', '')\n .replace(' distance', '')\n .replace(' duration', '');\n if (iconEl) iconEl.textContent = opt.icon;\n }\n}\n\nfunction applyState(urlState: AppState) {\n // Restore track\n const newTrackId = urlState.track;\n if (newTrackId !== selectedId) {\n if (newTrackId && tracks[newTrackId]) {\n selectTrack(newTrackId, true);\n } else if (!newTrackId && selectedId) {\n selectedId = null;\n ChartView.clear();\n renderDetails(null, []);\n renderInsights(null);\n renderTrackList();\n MapView.fitAll();\n }\n }\n\n // Restore basemap\n if (urlState.map && typeof urlState.map === 'string') {\n MapView.switchBasemap(urlState.map);\n document\n .querySelectorAll('.bm-btn')\n .forEach((b) =>\n b.classList.toggle('active', (b as HTMLElement).dataset.layer === urlState.map),\n );\n }\n\n // Restore map position\n if (urlState.map_pos) {\n const [lat, lng, zoom] = urlState.map_pos;\n MapView.setPosition(lat, lng, zoom);\n }\n\n // Restore tab\n if (urlState.tab) {\n document.querySelectorAll('.tab-btn').forEach((btn) => {\n const b = btn as HTMLElement;\n if (b.dataset.tab === urlState.tab) {\n if (!b.classList.contains('active')) b.click();\n }\n });\n }\n\n // Restore x-axis mode\n if (urlState.xaxis) ChartView.setXAxis(urlState.xaxis);\n\n const currentXAxis = urlState.xaxis || 'time';\n document\n .querySelectorAll('#x-axis-ctrl .seg-btn')\n .forEach((b) => b.classList.toggle('active', (b as HTMLElement).dataset.axis === currentXAxis));\n\n // Restore active metrics\n if (urlState.metrics) {\n const metrics = ChartView.METRICS;\n const keys = urlState.metrics\n .map((abbr) => Object.keys(metrics).find((k) => metrics[k].abbr === abbr))\n .filter((k): k is string => k !== undefined);\n if (keys.length) ChartView.setActiveMetrics(keys);\n }\n\n // Restore filters\n if (urlState.f_date)\n filters.date = urlState.f_date.map((v) => v || null) as [string | null, string | null];\n if (urlState.f_dist)\n filters.dist = urlState.f_dist.map((v) => (v === 0 ? 0 : v || null)) as [\n number | null,\n number | null,\n ];\n if (urlState.f_dur)\n filters.dur = urlState.f_dur.map((v) => (v === 0 ? 0 : v || null)) as [\n number | null,\n number | null,\n ];\n if (urlState.f_mets) filters.metrics = new Set(urlState.f_mets);\n if (urlState.f_tags) filters.tags = urlState.f_tags;\n\n updateFilterUI();\n\n if (urlState.q) searchQuery = urlState.q;\n if (urlState.re) searchRegex = urlState.re;\n if (urlState.sort) currentSort = urlState.sort;\n updateSearchSortUI();\n\n applyFilters();\n}\n\nfunction updateSportFilterOptions() {\n const container = document.getElementById('sport-filter-container');\n if (!container) return;\n\n const commonContainer = document.getElementById('common-sports');\n const dropdownContent = document.getElementById('sport-dropdown-content');\n if (!commonContainer || !dropdownContent) return;\n\n const sports = new Set();\n Object.values(tracks).forEach((t) => {\n if (t.sport) sports.add(t.sport);\n });\n\n const sportsArray = Array.from(sports).sort();\n const burgerDropdown = document.getElementById('sport-burger-dropdown');\n\n commonContainer.innerHTML = '';\n dropdownContent.innerHTML = '';\n\n if (sportsArray.length <= 4) {\n if (burgerDropdown) burgerDropdown.style.display = 'none';\n\n sportsArray.forEach((sport) => {\n const btn = document.createElement('button');\n btn.className = `mini-pill${filters.sport === sport ? ' active' : ''}`;\n btn.dataset.sport = sport;\n btn.title = sport.charAt(0).toUpperCase() + sport.slice(1);\n btn.innerHTML = `${getSportIcon(sport)}`;\n commonContainer.appendChild(btn);\n });\n } else {\n if (burgerDropdown) burgerDropdown.style.display = 'inline-block';\n\n const commonSports = ['running', 'cycling', 'walking'];\n\n commonSports.forEach((sport) => {\n if (sports.has(sport)) {\n const btn = document.createElement('button');\n btn.className = `mini-pill${filters.sport === sport ? ' active' : ''}`;\n btn.dataset.sport = sport;\n btn.title = sport.charAt(0).toUpperCase() + sport.slice(1);\n btn.innerHTML = `${getSportIcon(sport)}`;\n commonContainer.appendChild(btn);\n }\n });\n\n sportsArray.forEach((sport) => {\n const item = document.createElement('div');\n item.className = `dropdown-item${filters.sport === sport ? ' active' : ''}`;\n item.dataset.sport = sport;\n item.style.padding = '8px';\n item.style.cursor = 'pointer';\n item.style.display = 'flex';\n item.style.alignItems = 'center';\n item.style.gap = '8px';\n item.innerHTML = `\n ${getSportIcon(sport)}\n ${sport.charAt(0).toUpperCase() + sport.slice(1)}\n `;\n dropdownContent.appendChild(item);\n });\n }\n}\n\nfunction renderTrackList() {\n updateSportFilterOptions();\n const list = document.getElementById('track-list');\n const emptyEl = document.getElementById('track-list-empty') as HTMLElement;\n const headerLabel = document.querySelector('.section-label');\n if (!list) return;\n\n const ids = Object.keys(tracks).filter((id) => !tracks[id]._filtered);\n if (headerLabel) headerLabel.textContent = `Tracks (${ids.length})`;\n\n if (!ids.length) {\n list.innerHTML = '';\n if (emptyEl) {\n list.appendChild(emptyEl);\n emptyEl.style.display = 'block';\n }\n return;\n }\n if (emptyEl) emptyEl.style.display = 'none';\n list.innerHTML = '';\n\n ids\n .sort((a, b) => {\n // Current selected always first\n if (a === selectedId) return -1;\n if (b === selectedId) return 1;\n\n const ta = tracks[a],\n tb = tracks[b];\n const sa = ta.stats,\n sb = tb.stats;\n\n let cmp = 0;\n switch (currentSort) {\n case 'date-asc':\n cmp = (sa.startTime || 0) - (sb.startTime || 0);\n break;\n case 'date-desc':\n cmp = (sb.startTime || 0) - (sa.startTime || 0);\n break;\n case 'dist-asc':\n cmp = (sa.totalDist || 0) - (sb.totalDist || 0);\n break;\n case 'dist-desc':\n cmp = (sb.totalDist || 0) - (sa.totalDist || 0);\n break;\n case 'dur-asc':\n cmp = (sa.duration || 0) - (sb.duration || 0);\n break;\n case 'dur-desc':\n cmp = (sb.duration || 0) - (sa.duration || 0);\n break;\n default:\n cmp = 0;\n }\n return cmp || a.localeCompare(b);\n })\n .forEach((id) => {\n list.appendChild(buildTrackItem(tracks[id]));\n });\n}\n\nfunction getGlobalTags(): string[] {\n const all = new Set();\n Object.values(tracks).forEach((t) => {\n (t.tags || []).forEach((tag) => all.add(tag));\n });\n return Array.from(all).sort();\n}\n\nfunction getSportIcon(sport: string | null): string {\n if (!sport) return 'category';\n const s = sport.toLowerCase();\n if (s.includes('run')) return 'directions_run';\n if (s.includes('cycl') || s.includes('bike')) return 'directions_bike';\n if (s.includes('walk')) return 'directions_walk';\n if (s.includes('swim')) return 'pool';\n if (s.includes('hik')) return 'hiking';\n return 'sports';\n}\n\nfunction buildTrackItem(track: TrackData) {\n const item = document.createElement('div');\n item.className = `track-item${track.id === selectedId ? ' selected' : ''}`;\n item.dataset.id = track.id;\n\n const date =\n track.points.length && track.points[0].time ? fmtDate(track.points[0].time) : 'Unknown date';\n\n const tagsHtml =\n (track.tags || []).length > 0\n ? `
${(track.tags || [])\n .map((t) => {\n const c = getTagColor(t);\n return `${escHtml(t)}`;\n })\n .join('')}
`\n : '';\n\n item.innerHTML = `\n
\n
\n
${escHtml(track.displayName || track.name)}
\n
\n
\n ${date}\n ${Math.round(track.stats.totalDist / 1000)} km\n ${track.sport ? `${getSportIcon(track.sport)}` : ''}\n ${track.format.toUpperCase()}\n
\n \n
\n ${tagsHtml}\n
\n `;\n\n item.addEventListener('click', (e) => {\n if ((e.target as HTMLElement).closest('.icon-btn')) return;\n selectTrack(track.id);\n });\n\n const btnVis = item.querySelector('.toggle-vis') as HTMLElement;\n btnVis.addEventListener('click', () => {\n track.visible = !track.visible;\n btnVis.querySelector('.material-symbols-rounded')!.textContent = track.visible\n ? 'visibility'\n : 'visibility_off';\n MapView.setTrackVisible(track.id, track.visible);\n Storage.save(track);\n });\n\n return item;\n}\n\nfunction showToast(msg: string, type: 'info' | 'error' = 'info') {\n const container = document.getElementById('toast-container');\n if (!container) return;\n container.innerHTML = '';\n const t = document.createElement('div');\n t.className = `toast ${type}`;\n t.textContent = msg;\n container.appendChild(t);\n}\n\nfunction showSkippedImportsModal() {\n const backdrop = document.createElement('div');\n backdrop.className = 'modal-backdrop';\n\n let listHtml = '';\n skippedImports.forEach((s) => {\n listHtml += `
${s.name}: ${s.reason}
`;\n });\n\n backdrop.innerHTML = `\n
\n
Skipped Imports
\n
\n ${listHtml || '
No skipped imports.
'}\n
\n
\n \n \n
\n
\n `;\n\n document.body.appendChild(backdrop);\n\n const btnCancel = backdrop.querySelector('.modal-btn.cancel') as HTMLButtonElement;\n const btnClear = backdrop.querySelector('.modal-btn.clear') as HTMLButtonElement;\n const close = () => backdrop.remove();\n\n btnCancel.addEventListener('click', close);\n btnClear.addEventListener('click', () => {\n skippedImports = [];\n close();\n const importWarning = document.getElementById('import-warning');\n if (importWarning) importWarning.classList.add('hidden');\n });\n\n backdrop.addEventListener('click', (e) => {\n if (e.target === backdrop) close();\n });\n}\n\nfunction initDropZone() {\n const zone = document.getElementById('drop-zone');\n if (!zone) return;\n\n const importWarning = document.getElementById('import-warning');\n if (importWarning) {\n importWarning.addEventListener('click', (e) => {\n e.stopPropagation();\n showSkippedImportsModal();\n });\n }\n\n window.addEventListener('dragover', (e) => {\n e.preventDefault();\n zone.classList.add('drop-active');\n });\n\n window.addEventListener('dragleave', (e) => {\n if (e.relatedTarget === null) zone.classList.remove('drop-active');\n });\n\n window.addEventListener('drop', async (e) => {\n e.preventDefault();\n zone.classList.remove('drop-active');\n\n const items = e.dataTransfer?.items;\n if (!items) return;\n\n const files: File[] = [];\n const queue: FileSystemEntry[] = [];\n\n for (let i = 0; i < items.length; i++) {\n const entry = items[i].webkitGetAsEntry();\n if (entry) queue.push(entry);\n }\n\n async function readAllEntries(entry: FileSystemEntry) {\n if (entry.isFile) {\n const file = await new Promise((res) => (entry as FileSystemFileEntry).file(res));\n files.push(file);\n } else if (entry.isDirectory) {\n const reader = (entry as FileSystemDirectoryEntry).createReader();\n let entries: FileSystemEntry[] = [];\n let chunk: FileSystemEntry[];\n do {\n chunk = await new Promise((res) => reader.readEntries(res));\n entries.push(...chunk);\n } while (chunk.length > 0);\n\n for (const child of entries) await readAllEntries(child);\n }\n }\n\n for (const entry of queue) await readAllEntries(entry);\n\n if (files.length) handleFiles(files);\n });\n}\n\nasync function handleFiles(files: File[], deferChartRender = false) {\n if (!files.length) return;\n cancelProcessing = false;\n const initialSkipCount = skippedImports.length;\n\n const loader = document.getElementById('global-loader');\n const loaderText = document.getElementById('loader-text');\n const loaderSubtext = document.getElementById('loader-subtext');\n const progressBar = document.getElementById('loader-progress-bar') as HTMLElement;\n const loaderClose = document.getElementById('loader-close');\n\n if (loaderClose) {\n loaderClose.addEventListener('click', () => {\n cancelProcessing = true;\n if (loader) loader.classList.add('hidden');\n });\n }\n\n function updateLoaderIconToWarning() {\n const loaderIcon = document.querySelector('.loader-icon-wrap span') as HTMLElement;\n if (loaderIcon) {\n loaderIcon.textContent = 'warning';\n loaderIcon.style.color = '#f59e0b';\n }\n const warningCount = document.getElementById('loader-warning-count');\n if (warningCount) {\n const count = skippedImports.length - initialSkipCount;\n const countSpan = warningCount.querySelector('span');\n if (countSpan) countSpan.textContent = `${count}`;\n warningCount.style.display = 'flex';\n }\n }\n\n if (loader) {\n if (loaderText)\n loaderText.textContent = `Importing ${files.length} file${files.length > 1 ? 's' : ''}`;\n const loaderIcon = document.querySelector('.loader-icon-wrap span') as HTMLElement;\n if (loaderIcon) {\n loaderIcon.textContent = 'download';\n loaderIcon.style.color = 'var(--accent)';\n }\n const warningCount = document.getElementById('loader-warning-count');\n if (warningCount) {\n warningCount.style.display = 'none';\n const countSpan = warningCount.querySelector('span');\n if (countSpan) countSpan.textContent = '0';\n }\n if (loaderSubtext) loaderSubtext.textContent = '';\n if (progressBar) progressBar.style.width = '0%';\n loader.classList.remove('hidden');\n\n if (skippedImports.length > initialSkipCount) updateLoaderIconToWarning();\n }\n\n try {\n let count = 0;\n let firstId: string | null = null;\n let processedSinceLastUpdate = 0;\n\n // Filter out obviously invalid files early\n const validFiles = files.filter((f) => {\n const name = f.name.toLowerCase();\n return (\n name.endsWith('.gpx') ||\n name.endsWith('.fit') ||\n name.endsWith('.tcx') ||\n name.endsWith('.kml')\n );\n });\n\n const invalidFiles = files.filter((f) => !validFiles.includes(f));\n invalidFiles.forEach((f) => {\n skippedImports.push({ name: f.name, reason: 'Unsupported file format' });\n });\n\n if (validFiles.length === 0) {\n if (files.length > 0) {\n showToast('No valid GPS files found', 'error');\n // Still update warning icon if there were invalid files!\n const importWarning = document.getElementById('import-warning');\n if (importWarning && skippedImports.length > 0) {\n importWarning.classList.remove('hidden');\n importWarning.title = `Skipped ${skippedImports.length} imports. Click to view details.`;\n }\n }\n return;\n }\n\n const loaderFilename = document.getElementById('loader-filename');\n let progressIdxEl: HTMLElement | null = null;\n if (loaderSubtext) {\n loaderSubtext.innerHTML =\n '';\n progressIdxEl = loaderSubtext.querySelector('.progress-index') as HTMLElement | null;\n const totalEl = loaderSubtext.querySelector('.progress-total');\n if (totalEl) totalEl.textContent = validFiles.length.toString();\n }\n\n for (let i = 0; i < validFiles.length; i++) {\n if (cancelProcessing) break;\n const f = validFiles[i];\n if (loaderFilename) loaderFilename.textContent = f.name;\n if (progressIdxEl) progressIdxEl.textContent = (i + 1).toString();\n if (progressBar) progressBar.style.width = `${(i / validFiles.length) * 100}%`;\n\n try {\n const data = await Parsers.parseFile(f);\n\n // Duplicate checks\n const existingTracks = Object.values(tracks);\n\n // Rule 1: Same file and same size\n const sameFile = existingTracks.find((t) => t.fileName === f.name && t.fileSize === f.size);\n if (sameFile) {\n skippedImports.push({ name: f.name, reason: 'Same File' });\n updateLoaderIconToWarning();\n continue;\n }\n\n // Rule 2 & 3: Same start day checks\n const newStart = data.stats.startTime;\n const newDuration = data.stats.duration;\n\n if (newStart != null && newDuration != null) {\n const newEnd = newStart + newDuration;\n const sameDayTracks = existingTracks.filter(\n (t) => t.stats.startTime != null && isSameDay(newStart, t.stats.startTime!),\n );\n\n let discarded = false;\n for (const ext of sameDayTracks) {\n const extStart = ext.stats.startTime!;\n const extDuration = ext.stats.duration!;\n const extEnd = extStart + extDuration;\n\n // Rule 2: Fully contained\n if (newStart >= extStart && newEnd <= extEnd) {\n skippedImports.push({ name: f.name, reason: 'Fully contained in another track' });\n updateLoaderIconToWarning();\n discarded = true;\n break;\n }\n\n // Rule 3: Overlaps for > 10 mins\n const overlap = Math.min(newEnd, extEnd) - Math.max(newStart, extStart);\n if (overlap > 600000) {\n // 10 mins in ms\n skippedImports.push({\n name: f.name,\n reason: 'Overlaps with an existing track for more than 10 mins',\n });\n updateLoaderIconToWarning();\n discarded = true;\n break;\n }\n }\n if (discarded) continue;\n }\n\n const fallbackTime = 1785542400000;\n let id = compactId(data.stats.startTime || fallbackTime);\n if (tracks[id]) id += `-${shortRandom()}`;\n\n // Use filename as activity name if internal name is generic\n let trackName = data.name;\n if (trackName.startsWith('Unnamed') || trackName.startsWith('Fit Activity'))\n trackName = f.name;\n\n // Generate a nice display name\n let displayName = trackName;\n const t0 = data.points[0]?.time;\n const sport = data.sport\n ? data.sport.charAt(0).toUpperCase() + data.sport.slice(1)\n : 'Activity';\n if (t0) {\n const d = new Date(t0);\n displayName = `${d.getUTCFullYear()}-${String(d.getUTCMonth() + 1).padStart(2, '0')}-${String(d.getUTCDate()).padStart(2, '0')} ${sport}`;\n } else {\n displayName = trackName.replace(/\\.[^/.]+$/, '').replace(/_/g, ' '); // Remove extension and replace underscores\n }\n\n const track: TrackData = {\n ...data,\n id,\n name: trackName,\n displayName,\n fileName: f.name,\n fileSize: f.size,\n bounds: calculateBounds(data.points) || undefined,\n simplifiedPoints: simplifyTrack(data.points, 50), // 50m resolution\n addedAt: data.stats.startTime || fallbackTime,\n visible: true,\n color: TRACK_COLORS[colorIdx % TRACK_COLORS.length],\n };\n colorIdx++;\n if (cancelProcessing) break;\n\n await Storage.save(track);\n tracks[id] = track;\n MapView.addTrack(track);\n if (!firstId) firstId = id;\n count++;\n processedSinceLastUpdate++;\n\n if (processedSinceLastUpdate >= 5) {\n processedSinceLastUpdate = 0;\n }\n } catch (err) {\n skippedImports.push({\n name: f.name,\n reason: `Parse error: ${err instanceof Error ? err.message : String(err)}`,\n });\n updateLoaderIconToWarning();\n }\n }\n\n // Update warning icon\n const importWarning = document.getElementById('import-warning');\n if (importWarning) {\n if (skippedImports.length > 0) {\n importWarning.classList.remove('hidden');\n importWarning.title = `Skipped ${skippedImports.length} imports. Click to view details.`;\n } else {\n importWarning.classList.add('hidden');\n }\n }\n\n const newWarnings = skippedImports.length > initialSkipCount;\n if (!newWarnings && loader) {\n loader.classList.add('hidden');\n }\n\n if (count > 0) {\n showToast(`Imported ${count} track${count > 1 ? 's' : ''}`);\n applyFilters(!!firstId && firstId !== selectedId);\n if (firstId) selectTrack(firstId, true, deferChartRender);\n }\n\n if (newWarnings) {\n const loaderIcon = document.querySelector('.loader-icon-wrap span');\n if (loaderIcon) loaderIcon.textContent = 'warning';\n // Delay hiding loader to let user see the alert icon\n await new Promise((r) => setTimeout(r, 1500));\n }\n } finally {\n if (loader) loader.classList.add('hidden');\n }\n}\n\nfunction initMetricPillDraggable() {\n const container = document.getElementById('metric-pills');\n if (!container) return;\n\n let draggedEl: HTMLElement | null = null;\n\n container.addEventListener('dragstart', (e) => {\n const target = (e.target as HTMLElement).closest('.metric-pill') as HTMLElement;\n if (!target) return;\n draggedEl = target;\n e.dataTransfer!.effectAllowed = 'move';\n target.classList.add('dragging-pill');\n // For transparent background in ghost image\n setTimeout(() => (target.style.opacity = '0.4'), 0);\n });\n\n container.addEventListener('dragover', (e) => {\n e.preventDefault();\n e.dataTransfer!.dropEffect = 'move';\n const target = (e.target as HTMLElement).closest('.metric-pill') as HTMLElement;\n if (target && target !== draggedEl) {\n const rect = target.getBoundingClientRect();\n const next = e.clientX - rect.left > rect.width / 2;\n container.insertBefore(draggedEl!, next ? target.nextSibling : target);\n }\n });\n\n container.addEventListener('dragend', () => {\n if (!draggedEl) return;\n draggedEl.classList.remove('dragging-pill');\n draggedEl.style.opacity = '';\n draggedEl = null;\n\n // Update Chart order\n const keys = Array.from(container.querySelectorAll('.metric-pill')).map(\n (el) => (el as HTMLElement).dataset.metric!,\n );\n ChartView.updateMetricOrder(keys);\n syncMetricsToUrl();\n });\n}\n\nfunction updateToolbarLayout() {\n const toolbar = document.getElementById('chart-toolbar');\n const pills = document.getElementById('metric-pills');\n if (!toolbar || !pills) return;\n\n toolbar.classList.remove('collapsed');\n const availableW = Math.max(0, toolbar.clientWidth - 280);\n let totalW = 0;\n let hidden = false;\n\n Array.from(pills.children).forEach((el) => {\n const htmlEl = el as HTMLElement;\n if (htmlEl.id === 'pill-overflow') return;\n\n htmlEl.classList.remove('overflow-hidden');\n if (!htmlEl.classList.contains('active')) return;\n const metricKey = htmlEl.dataset.metric || '';\n const deterministicPillWidth = 36 + metricKey.length * 6;\n totalW += deterministicPillWidth + 6;\n\n if (totalW > availableW) {\n hidden = true;\n htmlEl.classList.add('overflow-hidden');\n }\n });\n\n const overflowEl = document.getElementById('pill-overflow');\n if (overflowEl) overflowEl.classList.toggle('hidden', !hidden);\n\n if (hidden) toolbar.classList.add('collapsed');\n}\n\ndocument.addEventListener('DOMContentLoaded', init);\n"], + "mappings": "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", + "names": ["UrlState", "initialPathname", "initialSearch", "_state", "localSearch", "_lastSearch", "_syncTimeout", "_shouldPush", "historyEntries", "read", "p", "s", "metricsRaw", "v", "isDesc", "field", "parts", "_sync", "dir", "search", "val", "url", "entryState", "get", "patch", "partial", "push", "k", "key", "refresh", "reset", "InMemoryStorage", "track", "id", "key", "value", "IndexedDBStorage", "resolve", "reject", "req", "db", "name", "mode", "res", "rej", "tx", "activeDriver", "Storage", "UrlState", "k", "v", "FitParser", "haversine", "lat1", "lon1", "lat2", "lon2", "phi1", "phi2", "deltaPhi", "deltaLambda", "a", "compactId", "timestamp", "buf", "shortRandom", "fmtDuration", "s", "fmtSecs", "h", "m", "fmtPace", "seconds", "mins", "secs", "fmtDate", "ts", "d", "fmtDateTime", "iso", "fmtFileSize", "bytes", "k", "sizes", "i", "escHtml", "str", "getTagColor", "name", "hash", "gaussianSmooth", "data", "sigma", "size", "kernel", "half", "sum", "x", "result", "val", "weightSum", "idx", "hexToRgba", "hex", "alpha", "r", "g", "b", "calculateBounds", "pts", "minLat", "maxLat", "minLon", "maxLon", "p", "SIMPLIFY_CAP", "simplifyLats", "simplifyLons", "simplifyEles", "simplifyDists", "enrichEleBuf", "enrichPwrBuf", "enrichSpdBuf", "simplifyTrack", "minDistanceMeters", "n", "lats", "lons", "eles", "dists", "i", "tolerances", "errChecksum", "degToRad", "ti", "tolDegSq", "anchor", "cosLat", "dx", "dy", "dz", "mid", "mx", "my", "result", "lastPoint", "tolBias", "haversine", "Parsers", "enrichPoints", "totalDist", "prev", "d", "dt", "de", "eleBuf", "pwrBuf", "spdBuf", "gradeWindows", "npAccum", "scaleH", "wi", "w", "invW", "pSum", "sSum", "pAvg", "airDensity", "slopeAngle", "smoothBias", "hrZoneThresholds", "ftp", "computeStats", "includeCurves", "stats", "powerSum", "powerN", "hrSum", "hrN", "cadSum", "cadN", "speedSum", "speedN", "batterySum", "batteryN", "tempSum", "tempN", "hasTemp", "shifts", "hasGears", "diff", "t0", "t1", "calculateHRZones", "calculateHRCurve", "calculatePowerCurve", "calculatePowerZones", "ensureCurvesAndZones", "track", "s", "analyzeTrackSections", "sections", "quarterLabels", "q", "sIdx", "eIdx", "subPts", "thirdLabels", "t", "kmStartIdx", "nextKmMeters", "kmIndex", "kmPts", "win", "inClimb", "climbStart", "climbIdx", "inSprint", "sprintStart", "sprintIdx", "sprintThreshold", "dEle", "dDist", "rollingGrade", "segPts", "pwr", "findOverlaps", "tracksList", "overlaps", "ta", "ptsA", "stepA", "j", "tb", "ptsB", "stepB", "shared", "minCorridor", "a", "pa", "nearest", "b", "pb", "STANDARD_CURVE_DURATIONS", "calculateSlidingMax", "data", "durations", "len", "prefix", "curve", "hasAny", "di", "maxSum", "bestIdx", "limit", "currentSum", "powerData", "nonZero", "v", "hrData", "zones", "thresholds", "pw", "z", "setFTP", "val", "getFTP", "hr", "setHRZones", "getHRZones", "extractXmlTag", "block", "openTag", "closeTag", "start", "extractXmlAttr", "tagOpen", "attrPrefix", "cachedDateKey", "cachedDayMs", "parseIntSlice", "text", "end", "neg", "c0", "digits", "c", "parseFloatSlice", "intPart", "fracPart", "fracDiv", "inFrac", "res", "fastParseTimeSlice", "y", "m", "key", "hh", "mm", "ss", "ms", "extractAttrFloat", "vStart", "parseGPX", "buf", "predecodedText", "firstTrkpt", "header", "trkIdx", "name", "gpxOpenEnd", "device", "points", "pos", "trkStart", "openEnd", "trkEnd", "lat", "lon", "ele", "time", "cad", "power", "temp", "speed", "gearFront", "gearRear", "gearFrontTooth", "gearRearTooth", "battery", "lt", "gt", "c1", "tagLen", "nextLt", "vEnd", "expectedCloseGt", "closeGt", "gears", "parseTCX", "firstTp", "creatorBlock", "tpStart", "tpEnd", "fastDecodeFitData", "dv", "headerSize", "dataSize", "endOffset", "GARMIN_EPOCH_SEC", "SEMICIRCLE_TO_DEG", "defs", "file_ids", "sessions", "records", "events", "device_infos", "offset", "hdr", "localNum", "littleEndian", "globalMsgNum", "numFields", "fields", "recordSize", "num", "size", "type", "def", "le", "gNum", "ts", "rawLat", "rawLon", "rawAlt", "rawDist", "rawSpd", "msg", "fOff", "f", "bType", "sportMap", "evName", "parseFIT", "resolve", "reject", "handleParsedFitData", "error", "devices", "sport", "subSport", "getDeviceName", "fid", "antId", "existing", "statusReports", "idx", "gearEvents", "e", "front", "rear", "frontTooth", "rearTooth", "ft", "fg", "rt", "rg", "lastBattery", "statusIdx", "lastFrontGear", "lastRearGear", "lastFrontTooth", "lastRearTooth", "gearIdx", "validPoints", "ri", "r", "ex", "fastData", "FitParser", "parseKML", "doc", "nameEl", "coordEls", "el", "line", "parts", "parseFile", "file", "ext", "MapView", "trackData", "customClickHandler", "init", "onSelectCb", "onMoveCb", "onPointClickCb", "onPointHoverCb", "onDblClickCb", "switchBasemap", "layer", "addTrack", "t", "addTracks", "ts", "removeTrack", "id", "clearTracks", "removeAllTracks", "setTrackVisible", "visible", "setSelectedTrack", "fit", "fitAll", "fitTrack", "showCursorAt", "lat", "lon", "hideCursor", "highlightSegment", "pts", "start", "end", "colors", "clearHighlight", "setTrackHover", "hover", "ensureVisible", "centerOn", "zoom", "force", "closePopup", "invalidateSize", "getPosition", "setPosition", "lng", "colorTrackByMetric", "clearMetricColor", "findTracksPassingThroughPoints", "points", "radius", "handler", "uPlot", "Zones", "getPowerZones", "ftp", "Parsers", "thresholds", "t", "names", "colors", "zones", "last", "i", "getHRZones", "Metrics", "METRICS", "v", "p", "pts", "fillNulls", "vam", "i", "prev", "de", "dt", "SPORTS", "getSportIcon", "sport", "key", "ChartView", "plots", "renderedTrack", "renderedXAxis", "renderedSpeedMode", "renderedMapColorMetric", "syncKey", "scaleSyncing", "cursorSyncing", "activeMetrics", "availableMetrics", "smoothedMetrics", "metricsIncludingZero", "xAxis", "currentTrack", "allTracks", "selAnchorVal", "selEndVal", "isDragging", "updatingRange", "pinnedPtIdx", "lastMouseXVal", "lastHistMouseEvent", "hoveredHistData", "hoveredHistCanvas", "currentXRange", "targetXRange", "animId", "onCursorMoveCb", "onRangeChangeCb", "onClickCb", "onPinChangeCb", "HIST_W", "ROW_BODY_PADDING", "HIST_GAP", "statsVisible", "histTooltipEl", "container", "emptyEl", "selStatsEl", "resetSelBtn", "mapColorMetric", "onMapColorChangeCb", "init", "onCursorMove", "onRangeChange", "onClick", "onPinChange", "updateHistTooltip", "uPlot", "cancelSelection", "navKeys", "activeNavKeys", "e", "key", "u", "min", "max", "moveStep", "zoomFactor", "newMin", "newMax", "center", "factor", "xFull", "d", "setVisibleRange", "animate", "startMin", "startMax", "lerp", "a", "b", "t", "step", "tMin", "tMax", "cMin", "cMax", "loadTrack", "track", "others", "deferRender", "METRICS", "def", "p", "m", "el", "pill", "metric", "isAvailable", "updateStats", "render", "clear", "destroyPlots", "chartStats", "clearSelectionStats", "resetState", "overflowEl", "toolbar", "toggleMetric", "setSmooth", "metricKey", "enabled", "skipRender", "has", "setZeroInclusion", "includeZero", "setActiveMetrics", "keys", "setXAxis", "axis", "pts", "t0", "convert", "val", "idx", "dTarget", "tTarget", "newRange", "r0", "r1", "toggleStats", "histCol", "resize", "setMapColorChangeCb", "cb", "toggleMapColor", "_updateMapColorBtns", "_fireMapColorCb", "plot", "colors", "_computePointColors", "yData", "vals", "v", "sport", "sportDef", "SPORTS", "defaultMode", "speedMode", "UrlState", "paceUnit", "gradientColor", "speed", "speedColor", "lerpHex", "resetZoom", "updateSelOverlay", "redrawHistograms", "xData", "setSelectionStats", "stats", "fmt", "id", "fmtSecs", "restoreSelection", "totalDuration", "tOffset", "xMin", "xMax", "updateAnchorLines", "w", "anyResized", "histCanvas", "histData", "drawHistogram", "keepState", "savedRange", "savedScroll", "available", "containerW", "canReuse", "lastValidX", "existingByKey", "availSet", "isSmoothed", "isIncZero", "nextSiblingRows", "nextExisting", "i", "ex", "reused", "uplot", "s", "prevSync", "initHoverBinI", "buildHistBins", "span", "transform", "fmtAxis", "unit", "label", "fmtPace", "fillNulls", "gaussianSmooth", "createChart", "initialRange", "insertBeforeEl", "gradData", "smoothGradient", "row", "header", "labelEl", "smoothBtn", "mapColorBtn", "zeroBtn", "incZero", "statsTotalEl", "statsSelEl", "statsContainer", "actionsEl", "rightActionsEl", "paceToggle", "btn", "mode", "isDefault", "rowBody", "plotEl", "fixedMinLabel", "fixedMaxLabel", "isDistAxis", "formatX", "range", "dec", "absV", "h", "hh", "mm", "ss", "incZeroDefault", "gMin", "gMax", "allSum", "allCount", "filtMin", "filtMax", "filtSum", "filtCount", "fullStatsAll", "fullStatsFiltered", "gPad", "fixedYRange", "statsTotalInitialized", "updateHeaderStats", "visibleMin", "visibleMax", "getHtml", "isFull", "rangeStats", "es", "elevationRangeStats", "initializingScale", "uOpts", "_si", "_u", "mn", "mx", "axisIdx", "scaleMin", "scaleMax", "targetLabels", "idealIncr", "incrs", "incr", "splits", "precision", "pos", "top", "drawTrackPath", "drawElevationGradient", "drawGradientChart", "drawMetricColorFill", "drawPinnedDot", "drawYAxisHighlight", "hIdx", "hasPinned", "hasHover", "cy", "drawVerticalLineOnly", "hexToRgba", "ctx", "dpr", "bb", "ax", "fontSize", "fontStr", "tw", "visibleL", "visibleR", "padH", "padV", "bw", "bh", "bx", "by", "drawXAxisLabels", "showHover", "updateTooltip", "binI", "other", "left", "width", "overlay", "selFill", "anchorLine", "anchorLabel", "anchorHandle", "anchorMarker", "endLine", "endLabel", "endHandle", "endMarker", "curYVal", "pinYVal", "self", "attachHandleDrag", "mousedownX", "rect", "attachHistTooltip", "aX", "eX", "overW", "overH", "plotBottom", "isAnchorVisible", "isEndVisible", "fillL", "fillR", "aText", "fmtXVal", "aLabelW", "aIcon", "eText", "eLabelW", "eIcon", "handleEl", "isAnchor", "startX", "startVal", "pxPerUnit", "onMove", "me", "dx", "newVal", "onUp", "BINS", "lo", "hi", "cnt", "bins", "binAccum", "k", "bi", "prev", "next", "canvas", "chartH", "hoverBinI", "hasSel", "selBins", "selBinAccum", "x", "W", "H", "peak", "pad", "plotW", "plotH", "binY", "binH", "baseAlpha", "total", "pts2", "cumSum", "my", "cp", "font9", "avgY", "minLabelY", "maxLabelY", "labelHeight", "countLower", "countHigher", "pLower", "pHigher", "font8", "textY", "fontB10", "midX", "getBinAt", "lineEl", "relY", "rawI", "count", "pct", "low", "high", "zoneHtml", "mid", "zones", "Zones", "zIdx", "z", "finalZIdx", "totalAccum", "totalPct", "html", "stotalAccum", "stotalPct", "ttH", "ttW", "binCenterY", "sum", "n", "gain", "loss", "prevEle", "isDist", "diff", "cx", "gColor", "g", "p0", "p1", "content", "front", "rear", "color44", "gradientColorRgbaCache", "gradientColorRgba", "yMin", "yMax", "xScale", "yScale", "yBase", "idxs", "iStart", "iEnd", "byColor", "prevIdx", "xv1", "xv0", "x0", "y0", "x1", "y1", "color", "segs", "j", "drawHoverLine", "xVal", "opacity", "skipLine", "distVal", "timeVal", "bottomLabel", "topLabel", "drawPill", "text", "y", "isTop", "rx", "clampedRx", "ry", "pillH", "yVal", "cyClamp", "drawAreaFillAbove", "yDir", "yMult", "inSegment", "hasAnySegment", "lastPx", "xv", "yv", "px", "py", "ah", "ar", "ag", "ab", "br", "bg", "rr", "rg", "rb", "absG", "kmh", "windowMetres", "grads", "j0", "j1", "data", "res", "right", "getAvg", "avgSpeed", "avgPower", "avgCadence", "INTERVAL_CAP", "ivPwr", "ivHr", "ivSpd", "ivEle", "ivCad", "ivSmoothed", "ivKdeBins", "intervalWindows", "analyzeTelemetryIntervals", "tracksList", "selStartSec", "selEndSec", "intervalScore", "ti", "sStart", "sEnd", "inv35", "invSigmaSq", "omega", "sgPwr", "sgHr", "sgSpd", "dP", "gauss", "harmonic", "sectionRanges", "si", "secStart", "secEnd", "secLen", "wi", "invW", "winSum", "winEle", "maxInterval", "avg", "norm", "newActive", "isStepped", "c", "drawBackgroundTracks", "GAP_THRESHOLD", "field", "first", "prevP", "prevXVal", "lastFrontGear", "fMin", "fMax", "fg", "rMin", "rMax", "isTimeAxis", "gapSec", "prevDrawnI", "prevX", "prevY", "segmentColor", "prevXv", "SENSOR_ICONS", "Metrics", "onTagsChangeCb", "onDeleteTrackCb", "initDetails", "onTagsChange", "onDeleteTrack", "resetDetails", "container", "DETAIL_CAP", "detailChannels", "stabilityWindows", "analyzeTrackDetails", "track", "allTracks", "pts", "stabilityAccum", "i", "p", "wi", "w", "invW", "halfW", "ci", "ch", "sum", "sumSq", "autoCov", "jitterSum", "invNoise", "v", "old", "mean", "variance", "lagVal", "diff", "diffSqNoise", "likelihood", "damp", "sections", "Parsers", "splitRows", "si", "sec", "st", "secPtsCount", "secJitter", "dp", "dh", "secStab", "fmtSecs", "gearMap", "fg", "rg", "key", "entry", "gearMatrix", "a", "b", "combo", "g", "cohort", "distRank", "pwrRank", "elevRank", "cohortPwrSum", "cohortDistSum", "ti", "peer", "cohortAvgPower", "cohortAvgDistKm", "renderDetails", "globalTags", "sectionRange", "activeTrack", "t0Base", "ts0", "ts1", "iMin", "iMax", "subPts", "subStats", "s", "t0", "detailAnalysis", "telemetryStabilityPct", "escHtml", "t", "fmtFileSize", "getSportIcon", "SPORTS", "fmtDateTime", "sensor", "gm", "sr", "seen", "d", "name", "manufacturer", "extra", "battery", "sourceIcon", "deviceIcon", "nameLower", "tagContainer", "tagInput", "addBtn", "renderTags", "tags", "tag", "c", "getTagColor", "el", "btn", "e", "Storage", "addTag", "val", "titleInput", "sportTrigger", "showSportMenu", "anchorEl", "popup", "def", "item", "isActive", "iconEl", "nameEl", "clearItem", "isClear", "rect", "closeHandler", "uPlot", "courses", "getCoursesForTrack", "track", "c", "pt", "passesPoint", "trackPt", "haversine", "activeCourseId", "isEditMode", "openCourseId", "toastFn", "courseLayer", "initCourses", "showToast", "toastFn", "loadCourses", "resetCourses", "courses", "activeCourseId", "openCourseId", "isEditMode", "container", "prepareBenchmarkCourses", "allTracks", "specs", "i", "spec", "t", "n", "pStart", "pMid", "pEnd", "getBenchmarkCourseIds", "c", "stored", "Storage", "e", "saveCourses", "calculateCourseDistance", "course", "total", "p1", "p2", "haversine", "findCourseRange", "track", "coursePoints", "currentPtIdx", "startIdx", "endIdx", "tp", "cp", "calculateCourseStats", "pts", "s", "startTime", "duration", "totalPower", "powerCount", "totalHR", "hrCount", "totalSpeed", "speedCount", "totalCadence", "cadenceCount", "totalAscent", "prev", "de", "dist", "formatDuration", "seconds", "h", "m", "renderCourseTableHeaders", "METRICS", "renderCourseTableCells", "stats", "sport", "dateStr", "paceUnit", "SPORTS", "paceStr", "paceSeconds", "fmtPace", "COURSE_CAP", "courseTrkLats", "courseTrkLons", "courseTrkPwrs", "courseGateLats", "courseGateLons", "DTW_SAMPLES", "DTW_BAND", "dtwRefProfile", "dtwCandProfile", "dtwPrevRow", "dtwCurrRow", "renderCourses", "corridorDeviationSum", "targetCourses", "candidateIds", "MapView", "p", "numGates", "g", "segFloat", "segIdx", "frac", "pA", "pB", "ci", "id", "pt", "gLat", "gLon", "minDist", "bestIdx", "d", "idx", "val", "jStart", "jEnd", "rVal", "j", "diff", "diffSq", "cost", "m1", "m2", "m3", "prevMin", "matchingIds", "isOpen", "tracksHtml", "matchedTracksWithStats", "range", "span", "q1Idx", "q2Idx", "q3Idx", "s1Stats", "s2Stats", "s3Stats", "s4Stats", "fmtDate", "a", "b", "UrlState", "escHtml", "createNewCourse", "el", "editCourse", "renameCourse", "deleteCourse", "selectCourse", "row", "trackId", "selectTrack", "t0", "sIdx", "eIdx", "tMin", "tMax", "axis", "ChartView", "selMin", "selMax", "name", "newCourse", "nameEl", "currentName", "input", "save", "newName", "clearCourseLayer", "courseLayer", "selectCourse", "id", "open", "activeCourseId", "openCourseId", "isEditMode", "MapView", "clearCourseLayer", "renderCourses", "deleteCourse", "courses", "c", "saveCourses", "toastFn", "insightPlots", "destroyInsightPlots", "p", "initInsights", "showToast", "resetInsights", "el", "container", "INSIGHT_CAP", "inPwrArr", "inHrArr", "inCadArr", "inSpdArr", "inVamArr", "inPrefixPwr", "inPrefixVam", "mmpDurations", "analyzeTrackPhysiology", "pts", "cpBase", "sections", "i", "pt", "h", "c", "sp", "gr", "vam", "envelopeChecksum", "di", "d", "limit", "maxP", "maxV", "j", "sumP", "sumV", "minWPrimeBal", "wBal", "matchesBurned", "inMatchBurn", "half", "efSum1", "efSum2", "qCounts", "thresholdCad", "thresholdTorque", "cad", "invH", "diff", "dcp", "dep", "tauDyn", "torque", "ef", "ef1", "ef2", "decouplingPct", "quadrantPct", "sectionPeaks", "sec", "st", "durHours", "renderInsights", "track", "allTracks", "sectionRange", "Parsers", "activeRange", "UrlState", "activePts", "s", "t0", "startMs", "endMs", "iMin", "iMax", "phys", "grid", "chartWidth", "renderCurveCard", "Metrics", "card", "metricKey", "color", "isMapColored", "ChartView", "totalTime", "a", "b", "zones", "Zones", "zonesHtml", "time", "z", "pct", "rangeText", "fmtSecs", "e", "current", "btn", "showPowerZoneSettings", "showHRZoneSettings", "climbingCard", "distKm", "grade", "effCard", "secCard", "escHtml", "trackCourses", "getCoursesForTrack", "coursesCard", "renderCourseTableHeaders", "range", "findCourseRange", "statsHtml", "stats", "calculateCourseStats", "startTime", "dateStr", "fmtDate", "renderCourseTableCells", "courseId", "selectCourse", "course", "MapView", "opts", "curve", "label", "unit", "icon", "tableWrap", "toggleBtn", "toggleIcon", "isHidden", "mapColorBtn", "key", "tbody", "durations", "entry", "val", "row", "fmtDuration", "startIdx", "duration", "endIdx", "tMin", "tMax", "chartEl", "xData", "yData", "curYVal", "hexToRgba", "_u", "vals", "v", "u", "seriesIdx", "dataIdx", "ctx", "dpr", "bb", "benchmarks", "maxDur", "lastX", "x", "idx", "hasFocus", "mousePy", "pointPy", "cx", "cy", "durVal", "pcx", "pcy", "plot", "uPlot", "anchorEl", "popup", "rect", "inputs", "newZones", "Storage", "dismiss", "ftp", "input", "newFtp", "uPlot", "currentTracks", "currentTrack", "plot", "xMetric", "yMetric", "currentSliderIdx", "currentChronoData", "currentSortedData", "playIntervalId", "isPolar", "updateSliderLabel", "sliderVal", "timeSecs", "fmtSecs", "resetCombined", "playIntervalId", "plot", "currentTracks", "currentTrack", "xMetric", "yMetric", "currentSliderIdx", "currentChronoData", "currentSortedData", "isPolar", "tooltip", "container", "COMBINED_CAP", "combinedDensityGrid", "computeMultiTrackCorrelationAndSections", "allTracks", "matrixTracks", "numTrk", "pairs", "p", "aggPts", "corrSum", "correlations", "pi", "pair", "sumX", "sumY", "sumXX", "sumYY", "sumXY", "count", "t", "pts", "n", "k", "x", "y", "pw", "h", "cd", "bx", "by", "rVal", "slope", "meanX", "meanY", "varX", "varY", "covXY", "denom", "g", "sectionLabels", "sectionRows", "t1", "t2", "q", "fStart", "fEnd", "sliceStats", "trk", "sIdx", "eIdx", "pSum", "hSum", "spSum", "cnt", "i", "pt", "st1", "st2", "cohortPwrSum", "setCombinedMetrics", "xKey", "yKey", "renderCombined", "setCombinedPolar", "polar", "setCombinedSliderProgress", "fraction", "clamped", "maxLen", "slider", "updateSliderLabel", "trackOrTracks", "track", "metrics", "ChartView", "availableMetrics", "key", "def", "xDef", "yDef", "summaryHtml", "s1", "s2", "distDiff", "pwrDiff", "hrDiff", "eleDiff", "shiftsDiff", "totalDist", "totalElev", "totalDur", "avgPwr", "maxPwr", "avgHr", "maxHr", "tempDiff", "avgTemp", "maxTemp", "totalShifts", "fmtSecs", "sr", "c", "e", "showMenu", "sliderVal", "playBtn", "startPlaying", "stopPlaying", "updatePlot", "resizeCombined", "plotContainer", "w", "size", "anchorEl", "axis", "el", "popup", "item", "currentVal", "rect", "close", "xData", "seriesList", "dataArrays", "yArr", "width", "height", "opts", "uPlot", "t0", "chronoData", "idx", "d", "xVals", "yVals", "xMin", "xMax", "yMin", "yMax", "xVal", "yVal", "theta", "r", "px", "py", "a", "b", "yData", "hexToRgba", "u", "vals", "v", "seriesIdx", "dataIdx", "cursorLeft", "cursorTop", "minD2", "closestIdx", "cx", "cy", "dx", "dy", "d2", "ctx", "dpr", "started", "startIdx", "endIdx", "xVal1", "yVal1", "xVal2", "yVal2", "cx1", "cy1", "cx2", "cy2", "progress", "lineWidth", "alpha", "fillAlpha", "timeSecs", "distKm", "origX", "origY", "canvasRect", "originalIdx", "uPlot", "computeAccumulatedMetric", "allTracks", "extractor", "refTs", "i", "st", "now", "currentYear", "oneYearAgoMs", "sixMonthsAgoMs", "oneMonthAgoMs", "currentYearVal", "pastYearVal", "past6mVal", "past1mVal", "totalVal", "minTime", "t", "val", "time", "msecPerWeek", "currentYearStart", "currentYearWeeks", "pastYearWeeks", "past6mWeeks", "past1mWeeks", "totalWeeks", "renderAccumulatedTable", "accumulated", "isDistance", "unit", "factor", "format", "computeMultiMetricAccumulated", "distance", "elevation", "durationHours", "tss", "durSec", "avgPwr", "ftp", "intensityFactor", "workKj", "climbCount", "s", "renderAccumulatedProgressSummary", "summary", "fmt", "v", "digits", "r", "evolutionPlots", "activeEvolutionCards", "currentEvolutionGrouping", "destroyEvolutionPlots", "p", "resetEvolution", "container", "setEvolutionGrouping", "grouping", "c", "setEvolutionRange", "range", "UrlState", "toolbarRangeCtrl", "b", "setEvolutionPinnedDuration", "dur", "EVO_CAP", "evoPwrPrefix", "evoHrPrefix", "evoSpdPrefix", "evoStressArr", "evoDailyTrimp", "envelopeDurations", "renderEvolution", "currentTrack", "allTracks", "onTrackSelect", "onDateRangeSelect", "currentRange", "track", "Parsers", "totalFatigue", "totalFitness", "ti", "pts", "n", "i", "pw", "h", "sp", "el", "hrRatio", "di", "d", "limit", "maxP", "maxH", "j", "dh", "alphaA", "oneMinusA", "alphaC", "oneMinusC", "hrRest", "invHrSpan", "atl", "ctl", "kh", "trimp", "dayIdx", "atl365", "ctl365", "day", "load", "cards", "Metrics", "grid", "renderAccumulatedProgressSummary", "gridWidth", "progressSync", "uPlot", "renderProgressCard", "distanceScroll", "elevationScroll", "btn", "onRangeChange", "pill", "target", "onPinChange", "t", "card", "renderCurveEvolutionCard", "opts", "metricKey", "label", "unit", "color", "icon", "trackName", "mainChart", "cardContainer", "row1", "legendEl", "row2", "sideBySide", "chartEl", "maxCurve", "curve", "name", "date", "fmtDate", "trackInfo", "data", "val", "currentCurve", "allDurations", "durations", "a", "xData", "yDataCurrent", "entry", "yDataMax", "validTracks", "xTimeline", "yTimelineData", "currentDur", "tracksFiltered", "pinnedDur", "getFilteredData", "maxTime", "minTime", "filteredIndices", "idx", "xFiltered", "rawY", "rawYFiltered", "smoothedYFiltered", "gaussianSmooth", "timelineChart", "curYVal", "optsUPlot", "hexToRgba", "_u", "vals", "v", "u", "seriesIdx", "dataIdx", "ctx", "dpr", "bb", "benchmarks", "maxDur", "lastX", "x", "fmtDuration", "pcx", "pcy", "curVal", "dotRadius", "dotColor", "activeSeriesIdx", "cursorLeft", "cursorTop", "minD2", "nearestIdx", "radius", "maxD2", "yCur", "yMax", "px", "py", "dx", "dy", "d2", "xValStr", "xbw", "pillH", "xLabelX", "clampedXBx", "xLabelY", "yValStr", "ybw", "yLabelX", "yLabelY", "cx", "cy", "durVal", "mxVal", "html", "lowerBound", "upperBound", "labelEl", "clickedDur", "newPinnedDur", "stats", "bestIdx", "duration", "startPt", "endPt", "trackStart", "min", "max", "detailsEl", "cur", "mx", "mxId", "mxIdx", "e", "id", "lastNearestIdx", "timelineEl", "timelineTooltip", "dataTimeline", "series", "optsTimeline", "ts", "tpcx", "tpcy", "tctx", "tdpr", "dateStr", "valStr", "yData", "y", "getWeekStart", "diff", "computeWeeklyDistance", "weeklyData", "weekStart", "weekKey", "week", "computeWeeklyElevation", "computeMonthlyDistance", "monthlyData", "monthStart", "monthKey", "computeMonthlyElevation", "type", "syncKey", "isDistance", "activeGrouping", "activeRange", "weeklySource", "monthlySource", "accumulated", "computeAccumulatedMetric", "renderAccumulatedTable", "yAxisEl", "tooltip", "yDataSmoothed", "chartClientW", "width", "maxVal", "mainOpts", "item", "start", "end", "startStr", "endStr", "self", "splits", "s", "scrollContainer", "applySeriesData", "filteredData", "newXData", "newYData", "newYDataSmoothed", "newWidth", "newMaxVal", "TRACK_COLORS", "POINT_ZOOM_LEVEL", "tracks", "skippedImports", "isSameDay", "t1", "t2", "d1", "d2", "cancelProcessing", "selectedId", "colorIdx", "followDot", "currentMapColors", "searchQuery", "searchRegex", "currentSort", "filters", "syncFiltersToUrl", "push", "UrlState", "v", "syncMetricsToUrl", "active", "ChartView", "metrics", "abbrs", "key", "SORT_OPTIONS", "showSortMenu", "anchorEl", "el", "popup", "opt", "item", "updateSearchSortUI", "renderTrackList", "rect", "close", "e", "showMetricMenu", "available", "def", "isNowActive", "pill", "updateToolbarLayout", "id", "idx", "dur", "selectTrack", "t", "pts", "pIdx", "t0", "p", "tMin", "tMax", "iMax", "selPts", "stats", "Parsers", "renderInsights", "MapView", "btn", "nav", "b", "tab", "panel", "c", "renderDetails", "getGlobalTags", "renderEvolution", "trackId", "range", "onChartRangeChange", "start", "end", "applyFilters", "updateFilterUI", "renderCombined", "renderCourses", "s", "resetBenchmarkState", "Storage", "resetCourses", "resetInsights", "resetDetails", "resetCombined", "resetEvolution", "toastContainer", "appSubsystemsInitialized", "initAppSubsystems", "initInsights", "msg", "type", "showToast", "initDetails", "deleteTrack", "initCourses", "storageSelect", "selected", "params", "mapLoader", "mapLoaderSpan", "urlState", "onMapMove", "onMapPointClick", "onMapDblClick", "onChartCursorMove", "onChartClick", "data", "currentUrlState", "applyState", "initResizeHandle", "initSidebarResizer", "initMetricPillDraggable", "clearAll", "toggleMetric", "metric", "axis", "initDropZone", "handleFiles", "onFilterInput", "val", "burgerBtn", "dropdownContent", "isVisible", "target", "selectedSport", "m", "resizeCombined", "init", "fontTimerId", "fontTimeout", "resolve", "fontLoadPromise", "initBenchmarkTriggers", "preloadBenchmarkFiles", "readyEl", "camelReadyEl", "preloadedBenchmarkPayloads", "fileNames", "decoder", "name", "buf", "cleanName", "bytes", "ds", "stream", "text", "file", "btnLoad", "btnAnalysis", "btnInsights", "btnDetails", "btnAggregate", "btnEvolution", "btnCourses", "activateTab", "tabName", "loadedFiles", "allTracks", "sortMode", "availableMetrics", "metricSets", "mSet", "filtered", "sections", "secStart", "secEnd", "cursorIdx", "primaryTrack", "tIdx", "trk", "globalTags", "i", "setCombinedMetrics", "frac", "setCombinedSliderProgress", "setCombinedPolar", "selectedTwo", "onSelectCb", "refTracks", "refTrk", "setEvolutionGrouping", "rng", "setEvolutionRange", "setEvolutionPinnedDuration", "prepareBenchmarkCourses", "courseIds", "getBenchmarkCourseIds", "cId", "selectCourse", "initResizeHandle", "handle", "panel", "isDragging", "e", "h", "clampedH", "ChartView", "resizeCombined", "MapView", "initSidebarResizer", "resizer", "sidebar", "w", "clampedW", "onMapMove", "lat", "lng", "zoom", "UrlState", "onMapPointClick", "id", "idx", "selectTrack", "onMapDblClick", "onChartCursorMove", "pt", "followDot", "onChartRangeChange", "min", "max", "axis", "fit", "selectedId", "tracks", "t", "renderInsights", "pts", "iMin", "iMax", "d0", "d1", "p", "t0", "ts0", "ts1", "selPts", "stats", "Parsers", "currentMapColors", "onChartClick", "POINT_ZOOM_LEVEL", "deferChartRender", "hadSelected", "renderDetails", "renderTrackList", "activeTab", "getGlobalTags", "renderCombined", "renderCourses", "renderEvolution", "trackId", "range", "deleteTrack", "Storage", "applyFilters", "clearAll", "code", "backdrop", "input", "btnConfirm", "btnCancel", "close", "cancelProcessing", "skipRender", "s", "visible", "searchQuery", "searchRegex", "filters", "start", "end", "d", "m", "field", "trackTags", "ft", "updateFilterUI", "el", "item", "btn", "activeCount", "badge", "updateSearchSortUI", "searchInput", "opt", "SORT_OPTIONS", "o", "currentSort", "labelEl", "iconEl", "applyState", "urlState", "newTrackId", "b", "currentXAxis", "metrics", "keys", "abbr", "k", "v", "updateSportFilterOptions", "commonContainer", "dropdownContent", "sports", "sportsArray", "burgerDropdown", "sport", "getSportIcon", "list", "emptyEl", "headerLabel", "ids", "a", "ta", "tb", "sa", "sb", "cmp", "buildTrackItem", "all", "tag", "track", "date", "fmtDate", "tagsHtml", "c", "getTagColor", "escHtml", "btnVis", "showToast", "msg", "type", "container", "showSkippedImportsModal", "listHtml", "skippedImports", "btnClear", "importWarning", "initDropZone", "zone", "items", "files", "queue", "i", "entry", "readAllEntries", "file", "res", "reader", "entries", "chunk", "child", "handleFiles", "initialSkipCount", "loader", "loaderText", "loaderSubtext", "progressBar", "loaderClose", "updateLoaderIconToWarning", "loaderIcon", "warningCount", "count", "countSpan", "firstId", "processedSinceLastUpdate", "validFiles", "f", "name", "loaderFilename", "progressIdxEl", "totalEl", "data", "existingTracks", "newStart", "newDuration", "newEnd", "sameDayTracks", "isSameDay", "discarded", "ext", "extStart", "extDuration", "extEnd", "fallbackTime", "compactId", "shortRandom", "trackName", "displayName", "calculateBounds", "simplifyTrack", "TRACK_COLORS", "colorIdx", "err", "newWarnings", "r", "initMetricPillDraggable", "draggedEl", "target", "rect", "next", "syncMetricsToUrl", "updateToolbarLayout", "toolbar", "pills", "availableW", "totalW", "hidden", "htmlEl", "deterministicPillWidth", "overflowEl", "init"] +} diff --git a/suites-experimental/tools-gps-tracks/dist/uplot.mjs b/suites-experimental/tools-gps-tracks/dist/uplot.mjs new file mode 100644 index 000000000..17114aaf8 --- /dev/null +++ b/suites-experimental/tools-gps-tracks/dist/uplot.mjs @@ -0,0 +1,4 @@ +var De="u-",Qs="uplot",Xs=De+"hz",er=De+"vt",tr=De+"title",lr=De+"wrap",nr=De+"under",ir=De+"over",or=De+"axis",Qt=De+"off",sr=De+"select",rr=De+"cursor-x",fr=De+"cursor-y",ur=De+"cursor-pt",cr=De+"legend",ar=De+"live",pr=De+"inline",hr=De+"series",mr=De+"marker",$i=De+"label",gr=De+"value",Gl="width",Il="height",Ol="top",Ji="bottom",vl="left",In="right",oi="#000",Zi=oi+"0",Yn="mousemove",Qi="mousedown",Bn="mouseup",Xi="mouseenter",eo="mouseleave",to="dblclick",dr="resize",wr="scroll",lo="change",fn="dppxchange",si="--",El=typeof window<"u",$n=El?document:null,xl=El?window:null,Sr=El?navigator:null,oe,nn;function Jn(){let e=devicePixelRatio;oe!=e&&(oe=e,nn&&Qn(lo,nn,Jn),nn=matchMedia(`(min-resolution: ${oe-.001}dppx) and (max-resolution: ${oe+.001}dppx)`),Xt(lo,nn,Jn),xl.dispatchEvent(new CustomEvent(fn)))}function et(e,i){if(i!=null){let o=e.classList;!o.contains(i)&&o.add(i)}}function Zn(e,i){let o=e.classList;o.contains(i)&&o.remove(i)}function de(e,i,o){e.style[i]=o+"px"}function dt(e,i,o,n){let s=$n.createElement(e);return i!=null&&et(s,i),o?.insertBefore(s,n),s}function ut(e,i){return dt("div",e,i)}var no=new WeakMap;function Mt(e,i,o,n,s){let a="translate("+i+"px,"+o+"px)",u=no.get(e);a!=u&&(e.style.transform=a,no.set(e,a),i<0||o<0||i>n||o>s?et(e,Qt):Zn(e,Qt))}var io=new WeakMap;function oo(e,i,o){let n=i+o,s=io.get(e);n!=s&&(io.set(e,n),e.style.background=i,e.style.borderColor=o)}var so=new WeakMap;function ro(e,i,o,n){let s=i+""+o,a=so.get(e);s!=a&&(so.set(e,s),e.style.height=o+"px",e.style.width=i+"px",e.style.marginLeft=n?-i/2+"px":0,e.style.marginTop=n?-o/2+"px":0)}var ri={passive:!0},_r={...ri,capture:!0};function Xt(e,i,o,n){i.addEventListener(e,o,n?_r:ri)}function Qn(e,i,o,n){i.removeEventListener(e,o,ri)}El&&Jn();function wt(e,i,o,n){let s;o=o||0,n=n||i.length-1;let a=n<=2147483647;for(;n-o>1;)s=a?o+n>>1:tt((o+n)/2),i[s]{let a=-1,u=-1;for(let h=n;h<=s;h++)if(e(o[h])){a=h;break}for(let h=s;h>=n;h--)if(e(o[h])){u=h;break}return[a,u]}}var Ho=e=>e!=null,No=e=>e!=null&&e>0,an=Fo(Ho),yr=Fo(No);function vr(e,i,o,n=0,s=!1){let a=s?yr:an,u=s?No:Ho;[i,o]=a(e,i,o);let h=e[i],m=e[i];if(i>-1)if(n==1)h=e[i],m=e[o];else if(n==-1)h=e[o],m=e[i];else for(let g=i;g<=o;g++){let y=e[g];u(y)&&(ym&&(m=y))}return[h??ce,m??-ce]}function pn(e,i,o,n){let s=co(e),a=co(i);e==i&&(s==-1?(e*=o,i/=o):(e/=o,i*=o));let u=o==10?Lt:Oo,h=s==1?tt:ct,m=a==1?ct:tt,g=h(u(Ee(e))),y=m(u(Ee(i))),d=bl(o,g),w=bl(o,y);return o==10&&(g<0&&(d=ae(d,-g)),y<0&&(w=ae(w,-y))),n||o==2?(e=d*s,i=w*a):(e=Yo(e,d),i=hn(i,w)),[e,i]}function fi(e,i,o,n){let s=pn(e,i,o,n);return e==0&&(s[0]=0),i==0&&(s[1]=0),s}var ui=.1,fo={mode:3,pad:ui},Bl={pad:0,soft:null,mode:0},kr={min:Bl,max:Bl};function un(e,i,o,n){return mn(o)?uo(e,i,o):(Bl.pad=o,Bl.soft=n?0:null,Bl.mode=n?3:0,uo(e,i,kr))}function ne(e,i){return e??i}function xr(e,i,o){for(i=ne(i,0),o=ne(o,e.length-1);i<=o;){if(e[i]!=null)return!0;i++}return!1}function uo(e,i,o){let n=o.min,s=o.max,a=ne(n.pad,0),u=ne(s.pad,0),h=ne(n.hard,-ce),m=ne(s.hard,ce),g=ne(n.soft,ce),y=ne(s.soft,-ce),d=ne(n.mode,0),w=ne(s.mode,0),P=i-e,L=Lt(P),H=Be(Ee(e),Ee(i)),O=Lt(H),G=Ee(O-L);(P<1e-24||G>10)&&(P=0,(e==0||i==0)&&(P=1e-24,d==2&&g!=ce&&(a=0),w==2&&y!=-ce&&(u=0)));let k=P||H||1e3,N=Lt(k),x=bl(10,tt(N)),q=k*(P==0?e==0?.1:1:a),D=ae(Yo(e-q,x/10),24),$=e>=g&&(d==1||d==3&&D<=g||d==2&&D>=g)?g:ce,U=Be(h,D<$&&e>=$?$:St($,D)),Q=k*(P==0?i==0?.1:1:u),B=ae(hn(i+Q,x/10),24),b=i<=y&&(w==1||w==3&&B>=y||w==2&&B<=y)?y:-ce,j=St(m,B>b&&i<=b?b:Be(b,B));return U==j&&U==0&&(j=100),[U,j]}var br=new Intl.NumberFormat(El?Sr.language:"en-US"),ci=e=>br.format(e),lt=Math,rn=lt.PI,Ee=lt.abs,tt=lt.floor,Ae=lt.round,ct=lt.ceil,St=lt.min,Be=lt.max,bl=lt.pow,co=lt.sign,Lt=lt.log10,Oo=lt.log2,Tr=(e,i=1)=>lt.sinh(e)*i,Un=(e,i=1)=>lt.asinh(e/i),ce=1/0;function ao(e){return(Lt((e^e>>31)-(e>>31))|0)+1}function Xn(e,i,o){return St(Be(e,i),o)}function Wo(e){return typeof e=="function"}function X(e){return Wo(e)?e:()=>e}var Mr=()=>{},Go=e=>e,Io=(e,i)=>i,Ar=e=>null,po=e=>!0,ho=(e,i)=>e==i,Er=/\.\d*?(?=9{6,}|0{6,})/gm,el=e=>{if(Uo(e)||Ot.has(e))return e;let i=`${e}`,o=i.match(Er);if(o==null)return e;let n=o[0].length-1;if(i.indexOf("e-")!=-1){let[s,a]=i.split("e");return+`${el(s)}e${a}`}return ae(e,n)};function Jt(e,i){return el(ae(el(e/i))*i)}function hn(e,i){return el(ct(el(e/i))*i)}function Yo(e,i){return el(tt(el(e/i))*i)}function ae(e,i=0){if(Uo(e))return e;let o=10**i,n=e*o*(1+Number.EPSILON);return Ae(n)/o}var Ot=new Map;function Bo(e){return((""+e).split(".")[1]||"").length}function Kl(e,i,o,n){let s=[],a=n.map(Bo);for(let u=i;u=0?0:h)+(u>=a[g]?0:a[g]),w=e==10?y:ae(y,d);s.push(w),Ot.set(w,d)}}return s}var Ul={},ai=[],Tl=[null,null],Nt=Array.isArray,Uo=Number.isInteger,Dr=e=>e===void 0;function mo(e){return typeof e=="string"}function mn(e){let i=!1;if(e!=null){let o=e.constructor;i=o==null||o==Object}return i}function Lr(e){return e!=null&&typeof e=="object"}var Pr=Object.getPrototypeOf(Uint8Array),Ko="__proto__";function Ml(e,i=mn){let o;if(Nt(e)){let n=e.find(s=>s!=null);if(Nt(n)||i(n)){o=Array(e.length);for(let s=0;sa){for(s=u-1;s>=0&&e[s]==null;)e[s--]=null;for(s=u+1;su-h)],s=n[0].length,a=new Map;for(let u=0;u"u"?e=>Promise.resolve().then(e):queueMicrotask;function Nr(e){let i=e[0],o=i.length,n=Array(o);for(let a=0;ai[a]-i[u]);let s=[];for(let a=0;a=n&&e[s]==null;)s--;if(s<=n)return!0;let a=Be(1,tt((s-n+1)/i));for(let u=e[n],h=n+a;h<=s;h+=a){let m=e[h];if(m!=null){if(m<=u)return!1;u=m}}return!0}var jo=["January","February","March","April","May","June","July","August","September","October","November","December"],qo=["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"];function $o(e){return e.slice(0,3)}var Gr=qo.map($o),Ir=jo.map($o),Yr={MMMM:jo,MMM:Ir,WWWW:qo,WWW:Gr};function Wl(e){return(e<10?"0":"")+e}function Br(e){return(e<10?"00":e<100?"0":"")+e}var Ur={YYYY:e=>e.getFullYear(),YY:e=>(e.getFullYear()+"").slice(2),MMMM:(e,i)=>i.MMMM[e.getMonth()],MMM:(e,i)=>i.MMM[e.getMonth()],MM:e=>Wl(e.getMonth()+1),M:e=>e.getMonth()+1,DD:e=>Wl(e.getDate()),D:e=>e.getDate(),WWWW:(e,i)=>i.WWWW[e.getDay()],WWW:(e,i)=>i.WWW[e.getDay()],HH:e=>Wl(e.getHours()),H:e=>e.getHours(),h:e=>{let i=e.getHours();return i==0?12:i>12?i-12:i},AA:e=>e.getHours()>=12?"PM":"AM",aa:e=>e.getHours()>=12?"pm":"am",a:e=>e.getHours()>=12?"p":"a",mm:e=>Wl(e.getMinutes()),m:e=>e.getMinutes(),ss:e=>Wl(e.getSeconds()),s:e=>e.getSeconds(),fff:e=>Br(e.getMilliseconds())};function pi(e,i){i=i||Yr;let o=[],n=/\{([a-z]+)\}|[^{]+/gi,s;for(;s=n.exec(e);)o.push(s[0][0]=="{"?Ur[s[1]]:s[0]);return a=>{let u="";for(let h=0;he%1==0,cn=[1,2,2.5,5],qr=Kl(10,-32,0,cn),Zo=Kl(10,0,32,cn),$r=Zo.filter(Jo),Zt=qr.concat(Zo),hi=` +`,Qo="{YYYY}",go=hi+Qo,Xo="{M}/{D}",Yl=hi+Xo,on=Yl+"/{YY}",es="{aa}",Jr="{h}:{mm}",kl=Jr+es,wo=hi+kl,So=":{ss}",re=null;function ts(e){let i=e*1e3,o=i*60,n=o*60,s=n*24,a=s*30,u=s*365,m=(e==1?Kl(10,0,3,cn).filter(Jo):Kl(10,-3,0,cn)).concat([i,i*5,i*10,i*15,i*30,o,o*5,o*10,o*15,o*30,n,n*2,n*3,n*4,n*6,n*8,n*12,s,s*2,s*3,s*4,s*5,s*6,s*7,s*8,s*9,s*10,s*15,a,a*2,a*3,a*4,a*6,u,u*2,u*5,u*10,u*25,u*50,u*100]),g=[[u,Qo,re,re,re,re,re,re,1],[s*28,"{MMM}",go,re,re,re,re,re,1],[s,Xo,go,re,re,re,re,re,1],[n,"{h}"+es,on,re,Yl,re,re,re,1],[o,kl,on,re,Yl,re,re,re,1],[i,So,on+" "+kl,re,Yl+" "+kl,re,wo,re,1],[e,So+".{fff}",on+" "+kl,re,Yl+" "+kl,re,wo,re,1]];function y(d){return(w,P,L,H,O,G)=>{let k=[],N=O>=u,x=O>=a&&O=s?s:O,B=tt(L)-tt(D),b=U+B+hn(D-U,Q);k.push(b);let j=d(b),C=j.getHours()+j.getMinutes()/o+j.getSeconds()/n,I=O/n,F=w.axes[P]._space,ee=G/F;for(;b=ae(b+O,e==1?0:3),!(b>H);)if(I>1){let z=tt(ae(C+I,6))%24,te=d(b).getHours()-z;te>1&&(te=-1),b-=te*n,C=(C+I)%24;let fe=k[k.length-1];ae((b-fe)/O,3)*ee>=.7&&k.push(b)}else k.push(b)}return k}}return[m,g,y]}var[Zr,Qr,Xr]=ts(1),[ef,tf,lf]=ts(.001);Kl(2,-53,53,[1]);function _o(e,i){return e.map(o=>o.map((n,s)=>s==0||s==8||n==null?n:i(s==1||o[8]==0?n:o[1]+n)))}function yo(e,i){return(o,n,s,a,u)=>{let h=i.find(L=>u>=L[0])||i[i.length-1],m,g,y,d,w,P;return n.map(L=>{let H=e(L),O=H.getFullYear(),G=H.getMonth(),k=H.getDate(),N=H.getHours(),x=H.getMinutes(),q=H.getSeconds(),D=O!=m&&h[2]||G!=g&&h[3]||k!=y&&h[4]||N!=d&&h[5]||x!=w&&h[6]||q!=P&&h[7]||h[1];return m=O,g=G,y=k,d=N,w=x,P=q,D(H)})}}function nf(e,i){let o=pi(i);return(n,s,a,u,h)=>s.map(m=>o(e(m)))}function Kn(e,i,o){return new Date(e,i,o)}function vo(e,i){return i(e)}var of="{YYYY}-{MM}-{DD} {h}:{mm}{aa}";function ko(e,i){return(o,n,s,a)=>a==null?si:i(e(n))}function sf(e,i){let o=e.series[i];return o.width?o.stroke(e,i):o.points.width?o.points.stroke(e,i):null}function rf(e,i){return e.series[i].fill(e,i)}var ff={show:!0,live:!0,isolate:!1,mount:Mr,markers:{show:!0,width:2,stroke:sf,fill:rf,dash:"solid"},idx:null,idxs:null,values:[]};function uf(e,i){let o=e.cursor.points,n=ut(),s=o.size(e,i);de(n,Gl,s),de(n,Il,s);let a=s/-2;de(n,"marginLeft",a),de(n,"marginTop",a);let u=o.width(e,i,s);return u&&de(n,"borderWidth",u),n}function cf(e,i){let o=e.series[i].points;return o._fill||o._stroke}function af(e,i){let o=e.series[i].points;return o._stroke||o._fill}function pf(e,i){return e.series[i].points.size}var jn=[0,0];function hf(e,i,o){return jn[0]=i,jn[1]=o,jn}function sn(e,i,o,n=!0){return s=>{s.button==0&&(!n||s.target==i)&&o(s)}}function qn(e,i,o,n=!0){return s=>{(!n||s.target==i)&&o(s)}}var mf={show:!0,x:!0,y:!0,lock:!1,move:hf,points:{one:!1,show:uf,size:pf,width:0,stroke:af,fill:cf},bind:{mousedown:sn,mouseup:sn,click:sn,dblclick:sn,mousemove:qn,mouseleave:qn,mouseenter:qn},drag:{setScale:!0,x:!0,y:!1,dist:0,uni:null,click:(e,i)=>{i.stopPropagation(),i.stopImmediatePropagation()},_x:!1,_y:!1},focus:{dist:(e,i,o,n,s)=>n-s,prox:-1,bias:0},hover:{skip:[void 0],prox:null,bias:0},left:-10,top:-10,idx:null,dataIdx:null,idxs:null,event:null},ls={show:!0,stroke:"rgba(0,0,0,0.07)",width:2},mi=be({},ls,{filter:Io}),ns=be({},mi,{size:10}),is=be({},ls,{show:!1}),gi='12px system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"',os="bold "+gi,ss=1.5,xo={show:!0,scale:"x",stroke:oi,space:50,gap:5,alignTo:1,size:50,labelGap:0,labelSize:30,labelFont:os,side:2,grid:mi,ticks:ns,border:is,font:gi,lineGap:ss,rotate:0},gf="Value",df="Time",bo={show:!0,scale:"x",auto:!1,sorted:1,min:ce,max:-ce,idxs:[]};function wf(e,i,o,n,s){return i.map(a=>a==null?"":ci(a))}function Sf(e,i,o,n,s,a,u){let h=[],m=Ot.get(s)||0;o=u?o:ae(hn(o,s),m);for(let g=o;g<=n;g=ae(g+s,m))h.push(Object.is(g,-0)?0:g);return h}function ei(e,i,o,n,s,a,u){let h=[],m=e.scales[e.axes[i].scale].log,g=m==10?Lt:Oo,y=tt(g(o));s=bl(m,y),m==10&&(s=Zt[wt(s,Zt)]);let d=o,w=s*m;m==10&&(w=Zt[wt(w,Zt)]);do h.push(d),d=d+s,m==10&&!Ot.has(d)&&(d=ae(d,Ot.get(s))),d>=w&&(s=d,w=s*m,m==10&&(w=Zt[wt(w,Zt)]));while(d<=n);return h}function _f(e,i,o,n,s,a,u){let m=e.scales[e.axes[i].scale].asinh,g=n>m?ei(e,i,Be(m,o),n,s):[m],y=n>=0&&o<=0?[0]:[];return(o<-m?ei(e,i,Be(m,-n),-o,s):[m]).reverse().map(w=>-w).concat(y,g)}var rs=/./,yf=/[12357]/,vf=/[125]/,To=/1/,ti=(e,i,o,n)=>e.map((s,a)=>i==4&&s==0||a%n==0&&o.test(s.toExponential()[s<0?1:0])?s:null);function kf(e,i,o,n,s){let a=e.axes[o],u=a.scale,h=e.scales[u],m=e.valToPos,g=a._space,y=m(10,u),d=m(9,u)-y>=g?rs:m(7,u)-y>=g?yf:m(5,u)-y>=g?vf:To;if(d==To){let w=Ee(m(1,u)-y);if(ws,Eo={show:!0,auto:!0,sorted:0,gaps:fs,alpha:1,facets:[be({},Ao,{scale:"x"}),be({},Ao,{scale:"y"})]},Do={scale:"y",auto:!0,sorted:0,show:!0,spanGaps:!1,gaps:fs,alpha:1,points:{show:Mf,filter:null},values:null,min:ce,max:-ce,idxs:[],path:null,clip:null};function Af(e,i,o,n,s){return o/10}var us={time:!0,auto:!0,distr:1,log:10,asinh:1,min:null,max:null,dir:1,ori:0},Ef=be({},us,{time:!1,ori:1}),Lo={};function cs(e,i){let o=Lo[e];return o||(o={key:e,plots:[],sub(n){o.plots.push(n)},unsub(n){o.plots=o.plots.filter(s=>s!=n)},pub(n,s,a,u,h,m,g){for(let y=0;y{let G=u.pxRound,k=g.dir*(g.ori==0?1:-1),N=g.ori==0?Dl:Ll,x,q;k==1?(x=o,q=n):(x=n,q=o);let D=G(d(h[x],g,H,P)),$=G(w(m[x],y,O,L)),U=G(d(h[q],g,H,P)),Q=G(w(a==1?y.max:y.min,y,O,L)),B=new Path2D(s);return N(B,U,Q),N(B,D,Q),N(B,D,$),B})}function gn(e,i,o,n,s,a){let u=null;if(e.length>0){u=new Path2D;let h=i==0?Sn:Si,m=o;for(let d=0;dw[0]){let P=w[0]-m;P>0&&h(u,m,n,P,n+a),m=w[1]}}let g=o+s-m,y=10;g>0&&h(u,m,n-y/2,g,n+a+y)}return u}function Lf(e,i,o){let n=e[e.length-1];n&&n[0]==i?n[1]=o:e.push([i,o])}function wi(e,i,o,n,s,a,u){let h=[],m=e.length;for(let g=s==1?o:n;g>=o&&g<=n;g+=s)if(i[g]===null){let d=g,w=g;if(s==1)for(;++g<=n&&i[g]===null;)w=g;else for(;--g>=o&&i[g]===null;)w=g;let P=a(e[d]),L=w==d?P:a(e[w]),H=d-s;P=u<=0&&H>=0&&H=0&&G>=0&&G=P&&h.push([P,L])}return h}function Po(e){return e==0?Go:e==1?Ae:i=>Jt(i,e)}function as(e){let i=e==0?dn:wn,o=e==0?(s,a,u,h,m,g)=>{s.arcTo(a,u,h,m,g)}:(s,a,u,h,m,g)=>{s.arcTo(u,a,m,h,g)},n=e==0?(s,a,u,h,m)=>{s.rect(a,u,h,m)}:(s,a,u,h,m)=>{s.rect(u,a,m,h)};return(s,a,u,h,m,g=0,y=0)=>{g==0&&y==0?n(s,a,u,h,m):(g=St(g,h/2,m/2),y=St(y,h/2,m/2),i(s,a+g,u),o(s,a+h,u,a+h,u+m,g),o(s,a+h,u+m,a,u+m,y),o(s,a,u+m,a,u,y),o(s,a,u,a+h,u,g),s.closePath())}}var dn=(e,i,o)=>{e.moveTo(i,o)},wn=(e,i,o)=>{e.moveTo(o,i)},Dl=(e,i,o)=>{e.lineTo(i,o)},Ll=(e,i,o)=>{e.lineTo(o,i)},Sn=as(0),Si=as(1),ps=(e,i,o,n,s,a)=>{e.arc(i,o,n,s,a)},hs=(e,i,o,n,s,a)=>{e.arc(o,i,n,s,a)},ms=(e,i,o,n,s,a,u)=>{e.bezierCurveTo(i,o,n,s,a,u)},gs=(e,i,o,n,s,a,u)=>{e.bezierCurveTo(o,i,s,n,u,a)};function ds(e){return(i,o,n,s,a)=>tl(i,o,(u,h,m,g,y,d,w,P,L,H,O)=>{let{pxRound:G,points:k}=u,N,x;g.ori==0?(N=dn,x=ps):(N=wn,x=hs);let q=ae(k.width*oe,3),D=(k.size-k.width)/2*oe,$=ae(D*2,3),U=new Path2D,Q=new Path2D,{left:B,top:b,width:j,height:C}=i.bbox;Sn(Q,B-$,b-$,j+$*2,C+$*2);let I=F=>{if(m[F]!=null){let ee=G(d(h[F],g,H,P)),z=G(w(m[F],y,O,L));N(U,ee+D,z),x(U,ee,z,D,0,rn*2)}};if(a)a.forEach(I);else for(let F=n;F<=s;F++)I(F);return{stroke:q>0?U:null,fill:U,clip:Q,flags:Al|li}})}function ws(e){return(i,o,n,s,a,u)=>{n!=s&&(a!=n&&u!=n&&e(i,o,n),a!=s&&u!=s&&e(i,o,s),e(i,o,u))}}var Pf=ws(Dl),Cf=ws(Ll);function Ss(e){let i=ne(e?.alignGaps,0);return(o,n,s,a)=>tl(o,n,(u,h,m,g,y,d,w,P,L,H,O)=>{[s,a]=an(m,s,a);let G=u.pxRound,k=C=>G(d(C,g,H,P)),N=C=>G(w(C,y,O,L)),x,q;g.ori==0?(x=Dl,q=Pf):(x=Ll,q=Cf);let D=g.dir*(g.ori==0?1:-1),$={stroke:new Path2D,fill:null,clip:null,band:null,gaps:null,flags:Al},U=$.stroke,Q=!1;if(a-s>=H*4){let C=V=>o.posToVal(V,g.key,!0),I=null,F=null,ee,z,Ke,we=k(h[D==1?s:a]),te=k(h[s]),fe=k(h[a]),J=C(D==1?te+1:fe-1);for(let V=D==1?s:a;V>=s&&V<=a;V+=D){let Te=h[V],Se=(D==1?TeJ)?we:k(Te),se=m[V];Se==we?se!=null?(z=se,I==null?(x(U,Se,N(z)),ee=I=F=z):zF&&(F=z)):se===null&&(Q=!0):(I!=null&&q(U,we,N(I),N(F),N(ee),N(z)),se!=null?(z=se,x(U,Se,N(z)),I=F=ee=z):(I=F=null,se===null&&(Q=!0)),we=Se,J=C(we+D))}I!=null&&I!=F&&Ke!=we&&q(U,we,N(I),N(F),N(ee),N(z))}else for(let C=D==1?s:a;C>=s&&C<=a;C+=D){let I=m[C];I===null?Q=!0:I!=null&&x(U,k(h[C]),N(I))}let[b,j]=di(o,n);if(u.fill!=null||b!=0){let C=$.fill=new Path2D(U),I=u.fillTo(o,n,u.min,u.max,b),F=N(I),ee=k(h[s]),z=k(h[a]);D==-1&&([z,ee]=[ee,z]),x(C,z,F),x(C,ee,F)}if(!u.spanGaps){let C=[];Q&&C.push(...wi(h,m,s,a,D,k,i)),$.gaps=C=u.gaps(o,n,s,a,C),$.clip=gn(C,g.ori,P,L,H,O)}return j!=0&&($.band=j==2?[Pt(o,n,s,a,U,-1),Pt(o,n,s,a,U,1)]:Pt(o,n,s,a,U,j)),$})}function Vf(e){let i=ne(e.align,1),o=ne(e.ascDesc,!1),n=ne(e.alignGaps,0),s=ne(e.extend,!1);return(a,u,h,m)=>tl(a,u,(g,y,d,w,P,L,H,O,G,k,N)=>{[h,m]=an(d,h,m);let x=g.pxRound,{left:q,width:D}=a.bbox,$=te=>x(L(te,w,k,O)),U=te=>x(H(te,P,N,G)),Q=w.ori==0?Dl:Ll,B={stroke:new Path2D,fill:null,clip:null,band:null,gaps:null,flags:Al},b=B.stroke,j=w.dir*(w.ori==0?1:-1),C=U(d[j==1?h:m]),I=$(y[j==1?h:m]),F=I,ee=I;s&&i==-1&&(ee=q,Q(b,ee,C)),Q(b,I,C);for(let te=j==1?h:m;te>=h&&te<=m;te+=j){let fe=d[te];if(fe==null)continue;let J=$(y[te]),V=U(fe);i==1?Q(b,J,C):Q(b,F,V),Q(b,J,V),C=V,F=J}let z=F;s&&i==1&&(z=q+D,Q(b,z,C));let[Ke,we]=di(a,u);if(g.fill!=null||Ke!=0){let te=B.fill=new Path2D(b),fe=g.fillTo(a,u,g.min,g.max,Ke),J=U(fe);Q(te,z,J),Q(te,ee,J)}if(!g.spanGaps){let te=[];te.push(...wi(y,d,h,m,j,$,n));let fe=g.width*oe/2,J=o||i==1?fe:-fe,V=o||i==-1?-fe:fe;te.forEach(Te=>{Te[0]+=J,Te[1]+=V}),B.gaps=te=g.gaps(a,u,h,m,te),B.clip=gn(te,w.ori,O,G,k,N)}return we!=0&&(B.band=we==2?[Pt(a,u,h,m,b,-1),Pt(a,u,h,m,b,1)]:Pt(a,u,h,m,b,we)),B})}function Co(e,i,o,n,s,a,u=ce){if(e.length>1){let h=null;for(let m=0,g=1/0;m{}),{fill:d,stroke:w}=g;return(P,L,H,O)=>tl(P,L,(G,k,N,x,q,D,$,U,Q,B,b)=>{let j=G.pxRound,C=o,I=n*oe,F=h*oe,ee=m*oe,z,Ke;x.ori==0?[z,Ke]=a(P,L):[Ke,z]=a(P,L);let we=x.dir*(x.ori==0?1:-1),te=x.ori==0?Sn:Si,fe=x.ori==0?y:(A,pe,Me,ol,It,yt,Yt)=>{y(A,pe,Me,It,ol,Yt,yt)},J=ne(P.bands,ai).find(A=>A.series[0]==L),V=J!=null?J.dir:0,Te=G.fillTo(P,L,G.min,G.max,V),Oe=j($(Te,q,b,Q)),Se,se,at,$e=B,ye=j(G.width*oe),_t=!1,At=null,nt=null,Ct=null,ll=null;d!=null&&(ye==0||w!=null)&&(_t=!0,At=d.values(P,L,H,O),nt=new Map,new Set(At).forEach(A=>{A!=null&&nt.set(A,new Path2D)}),ye>0&&(Ct=w.values(P,L,H,O),ll=new Map,new Set(Ct).forEach(A=>{A!=null&&ll.set(A,new Path2D)})));let{x0:nl,size:Pl}=g;if(nl!=null&&Pl!=null){C=1,k=nl.values(P,L,H,O),nl.unit==2&&(k=k.map(Me=>P.posToVal(U+Me*B,x.key,!0)));let A=Pl.values(P,L,H,O);Pl.unit==2?se=A[0]*B:se=D(A[0],x,B,U)-D(0,x,B,U),$e=Co(k,N,D,x,B,U,$e),at=$e-se+I}else $e=Co(k,N,D,x,B,U,$e),at=$e*u+I,se=$e-at;at<1&&(at=0),ye>=se/2&&(ye=0),at<5&&(j=Go);let jl=at>0,Wt=$e-at-(jl?ye:0);se=j(Xn(Wt,ee,F)),Se=(C==0?se/2:C==we?0:se)-C*we*((C==0?I/2:0)+(jl?ye/2:0));let We={stroke:null,fill:null,clip:null,band:null,gaps:null,flags:0},il=_t?null:new Path2D,Et=null;if(J!=null)Et=P.data[J.series[1]];else{let{y0:A,y1:pe}=g;A!=null&&pe!=null&&(N=pe.values(P,L,H,O),Et=A.values(P,L,H,O))}let Gt=z*se,K=Ke*se;for(let A=we==1?H:O;A>=H&&A<=O;A+=we){let pe=N[A];if(pe==null)continue;if(Et!=null){let je=Et[A]??0;if(pe-je==0)continue;Oe=$(je,q,b,Q)}let Me=x.distr!=2||g!=null?k[A]:A,ol=D(Me,x,B,U),It=$(ne(pe,Te),q,b,Q),yt=j(ol-Se),Yt=j(Be(It,Oe)),Je=j(St(It,Oe)),it=Yt-Je;if(pe!=null){let je=pe<0?K:Gt,pt=pe<0?Gt:K;_t?(ye>0&&Ct[A]!=null&&te(ll.get(Ct[A]),yt,Je+tt(ye/2),se,Be(0,it-ye),je,pt),At[A]!=null&&te(nt.get(At[A]),yt,Je+tt(ye/2),se,Be(0,it-ye),je,pt)):te(il,yt,Je+tt(ye/2),se,Be(0,it-ye),je,pt),fe(P,L,A,yt-ye/2,Je,se+ye,it)}}return ye>0?We.stroke=_t?ll:il:_t||(We._fill=G.width==0?G._fill:G._stroke??G._fill,We.width=0),We.fill=_t?nt:il,We})}function zf(e,i){let o=ne(i?.alignGaps,0);return(n,s,a,u)=>tl(n,s,(h,m,g,y,d,w,P,L,H,O,G)=>{[a,u]=an(g,a,u);let k=h.pxRound,N=z=>k(w(z,y,O,L)),x=z=>k(P(z,d,G,H)),q,D,$;y.ori==0?(q=dn,$=Dl,D=ms):(q=wn,$=Ll,D=gs);let U=y.dir*(y.ori==0?1:-1),Q=N(m[U==1?a:u]),B=Q,b=[],j=[];for(let z=U==1?a:u;z>=a&&z<=u;z+=U)if(g[z]!=null){let we=m[z],te=N(we);b.push(B=te),j.push(x(g[z]))}let C={stroke:e(b,j,q,$,D,k),fill:null,clip:null,band:null,gaps:null,flags:Al},I=C.stroke,[F,ee]=di(n,s);if(h.fill!=null||F!=0){let z=C.fill=new Path2D(I),Ke=h.fillTo(n,s,h.min,h.max,F),we=x(Ke);$(z,B,we),$(z,Q,we)}if(!h.spanGaps){let z=[];z.push(...wi(m,g,a,u,U,N,o)),C.gaps=z=h.gaps(n,s,a,u,z),C.clip=gn(z,y.ori,L,H,O,G)}return ee!=0&&(C.band=ee==2?[Pt(n,s,a,u,I,-1),Pt(n,s,a,u,I,1)]:Pt(n,s,a,u,I,ee)),C})}function Ff(e){return zf(Hf,e)}function Hf(e,i,o,n,s,a){let u=e.length;if(u<2)return null;let h=new Path2D;if(o(h,e[0],i[0]),u==2)n(h,e[1],i[1]);else{let m=Array(u),g=Array(u-1),y=Array(u-1),d=Array(u-1);for(let w=0;w0!=g[w]>0?m[w]=0:(m[w]=3*(d[w-1]+d[w])/((2*d[w]+d[w-1])/g[w-1]+(d[w]+2*d[w-1])/g[w]),isFinite(m[w])||(m[w]=0));m[u-1]=g[u-2];for(let w=0;w{Ue.pxRatio=oe}));var Nf=Ss(),Of=ds();function Ro(e,i,o,n){return(n?[e[0],e[1]].concat(e.slice(2)):[e[0]].concat(e.slice(1))).map((a,u)=>ii(a,u,i,o))}function Wf(e,i){return e.map((o,n)=>n==0?{}:be({},i,o))}function ii(e,i,o,n){return be({},i==0?o:n,e)}function _s(e,i,o){return i==null?Tl:[i,o]}var Gf=_s;function If(e,i,o){return i==null?Tl:un(i,o,ui,!0)}function ys(e,i,o,n){return i==null?Tl:pn(i,o,e.scales[n].log,!1)}var Yf=ys;function vs(e,i,o,n){return i==null?Tl:fi(i,o,e.scales[n].log,!1)}var Bf=vs;function Uf(e,i,o,n,s){let a=Be(ao(e),ao(i)),u=i-e,h=wt(s/n*u,o);do{let m=o[h],g=n*m/u;if(g>=s&&a+(m<5?Ot.get(m):0)<=17)return[m,g]}while(++h(i=Ae((o=+s)*oe))+"px"),[e,i,o]}function Kf(e){e.show&&[e.font,e.labelFont].forEach(i=>{let o=ae(i[2]*oe,1);i[0]=i[0].replace(/[0-9.]+px/,o+"px"),i[1]=o})}function Ue(e,i,o){let n={mode:ne(e.mode,1)},s=n.mode;function a(t,l,r,f){let c=l.valToPct(t);return f+r*(l.dir==-1?1-c:c)}function u(t,l,r,f){let c=l.valToPct(t);return f+r*(l.dir==-1?c:1-c)}function h(t,l,r,f){return l.ori==0?a(t,l,r,f):u(t,l,r,f)}n.valToPosH=a,n.valToPosV=u;let m=!1;n.status=0;let g=n.root=ut(Qs);if(e.id!=null&&(g.id=e.id),et(g,e.class),e.title){let t=ut(tr,g);t.textContent=e.title}let y=dt("canvas"),d=n.ctx=y.getContext("2d"),w=ut(lr,g);Xt("click",w,t=>{t.target===L&&(he!=dl||_e!=wl)&&He.click(n,t)},!0);let P=n.under=ut(nr,w);w.appendChild(y);let L=n.over=ut(ir,w);e=Ml(e);let H=+ne(e.pxAlign,1),O=Po(H);(e.plugins||[]).forEach(t=>{t.opts&&(e=t.opts(n,e)||e)});let G=e.ms||.001,k=n.series=s==1?Ro(e.series||[],bo,Do,!1):Wf(e.series||[null],Eo),N=n.axes=Ro(e.axes||[],xo,Mo,!0),x=n.scales={},q=n.bands=e.bands||[];q.forEach(t=>{t.fill=X(t.fill||null),t.dir=ne(t.dir,-1)});let D=s==2?k[1].facets[0].scale:k[0].scale,$={axes:Hs,series:Cs},U=(e.drawOrder||["axes","series"]).map(t=>$[t]);function Q(t){let l=t.distr==3?r=>Lt(r>0?r:t.clamp(n,r,t.min,t.max,t.key)):t.distr==4?r=>Un(r,t.asinh):t.distr==100?r=>t.fwd(r):r=>r;return r=>{let f=l(r),{_min:c,_max:p}=t,S=p-c;return(f-c)/S}}function B(t){let l=x[t];if(l==null){let r=(e.scales||Ul)[t]||Ul;if(r.from!=null){B(r.from);let f=be({},x[r.from],r,{key:t});f.valToPct=Q(f),x[t]=f}else{l=x[t]=be({},t==D?us:Ef,r),l.key=t;let f=l.time,c=l.range,p=Nt(c);if((t!=D||s==2&&!f)&&(p&&(c[0]==null||c[1]==null)&&(c={min:c[0]==null?fo:{mode:1,hard:c[0],soft:c[0]},max:c[1]==null?fo:{mode:1,hard:c[1],soft:c[1]}},p=!1),!p&&mn(c))){let S=c;c=(_,v,T)=>v==null?Tl:un(v,T,S)}l.range=X(c||(f?Gf:t==D?l.distr==3?Yf:l.distr==4?Bf:_s:l.distr==3?ys:l.distr==4?vs:If)),l.auto=X(p?!1:l.auto),l.clamp=X(l.clamp||Af),l._min=l._max=null,l.valToPct=Q(l)}}}B("x"),B("y"),s==1&&k.forEach(t=>{B(t.scale)}),N.forEach(t=>{B(t.scale)});for(let t in e.scales)B(t);let b=x[D],j=b.distr,C,I;b.ori==0?(et(g,Xs),C=a,I=u):(et(g,er),C=u,I=a);let F={};for(let t in x){let l=x[t];(l.min!=null||l.max!=null)&&(F[t]={min:l.min,max:l.max},l.min=l.max=null)}let ee=e.tzDate||(t=>new Date(Ae(t/G))),z=e.fmtDate||pi,Ke=G==1?Xr(ee):lf(ee),we=yo(ee,_o(G==1?Qr:tf,z)),te=ko(ee,vo(of,z)),fe=[],J=n.legend=be({},ff,e.legend),V=n.cursor=be({},mf,{drag:{y:s==2}},e.cursor),Te=J.show,Oe=V.show,Se=J.markers;J.idxs=fe,Se.width=X(Se.width),Se.dash=X(Se.dash),Se.stroke=X(Se.stroke),Se.fill=X(Se.fill);let se,at,$e,ye=[],_t=[],At,nt=!1,Ct={};if(J.live){let t=k[1]?k[1].values:null;nt=t!=null,At=nt?t(n,1,0):{_:0};for(let l in At)Ct[l]=si}if(Te)if(se=dt("table",cr,g),$e=dt("tbody",null,se),J.mount(n,se),nt){at=dt("thead",null,se,$e);let t=dt("tr",null,at);dt("th",null,t);for(var ll in At)dt("th",$i,t).textContent=ll}else et(se,pr),J.live&&et(se,ar);let nl={show:!0},Pl={show:!1};function jl(t,l){if(l==0&&(nt||!J.live||s==2))return Tl;let r=[],f=dt("tr",hr,$e,$e.childNodes[l]);et(f,t.class),t.show||et(f,Qt);let c=dt("th",null,f);if(Se.show){let _=ut(mr,c);if(l>0){let v=Se.width(n,l);v&&(_.style.border=v+"px "+Se.dash(n,l)+" "+Se.stroke(n,l)),_.style.background=Se.fill(n,l)}}let p=ut($i,c);t.label instanceof HTMLElement?p.appendChild(t.label):p.textContent=t.label,l>0&&(Se.show||(p.style.color=t.width>0?Se.stroke(n,l):Se.fill(n,l)),We("click",c,_=>{if(V._lock)return;Ut(_);let v=k.indexOf(t);if((_.ctrlKey||_.metaKey)!=J.isolate){let T=k.some((M,E)=>E>0&&E!=v&&M.show);k.forEach((M,E)=>{E>0&&kt(E,T?E==v?nl:Pl:nl,!0,xe.setSeries)})}else kt(v,{show:!t.show},!0,xe.setSeries)},!1),rl&&We(Xi,c,_=>{V._lock||(Ut(_),kt(k.indexOf(t),_l,!0,xe.setSeries))},!1));for(var S in At){let _=dt("td",gr,f);_.textContent="--",r.push(_)}return[f,r]}let Wt=new Map;function We(t,l,r,f=!0){let c=Wt.get(l)||{},p=V.bind[t](n,l,r,f);p&&(Xt(t,l,c[t]=p),Wt.set(l,c))}function il(t,l,r){let f=Wt.get(l)||{};for(let c in f)(t==null||c==t)&&(Qn(c,l,f[c]),delete f[c]);t==null&&Wt.delete(l)}let Et=0,Gt=0,K=0,A=0,pe=0,Me=0,ol=pe,It=Me,yt=K,Yt=A,Je=0,it=0,je=0,pt=0;n.bbox={};let _n=!1,ql=!1,sl=!1,Bt=!1,$l=!1,ot=!1;function yn(t,l,r){(r||t!=n.width||l!=n.height)&&_i(t,l),pl(!1),sl=!0,ql=!0,hl()}function _i(t,l){n.width=Et=K=t,n.height=Gt=A=l,pe=Me=0,Ts(),Ms();let r=n.bbox;Je=r.left=Jt(pe*oe,.5),it=r.top=Jt(Me*oe,.5),je=r.width=Jt(K*oe,.5),pt=r.height=Jt(A*oe,.5)}let ks=3;function xs(){let t=!1,l=0;for(;!t;){l++;let r=zs(l),f=Fs(l);t=l==ks||r&&f,t||(_i(n.width,n.height),ql=!0)}}function bs({width:t,height:l}){yn(t,l)}n.setSize=bs;function Ts(){let t=!1,l=!1,r=!1,f=!1;N.forEach((c,p)=>{if(c.show&&c._show){let{side:S,_size:_}=c,v=S%2,T=c.label!=null?c.labelSize:0,M=_+T;M>0&&(v?(K-=M,S==3?(pe+=M,f=!0):r=!0):(A-=M,S==0?(Me+=M,t=!0):l=!0))}}),Kt[0]=t,Kt[1]=r,Kt[2]=l,Kt[3]=f,K-=Vt[1]+Vt[3],pe+=Vt[3],A-=Vt[2]+Vt[0],Me+=Vt[0]}function Ms(){let t=pe+K,l=Me+A,r=pe,f=Me;function c(p,S){switch(p){case 1:return t+=S,t-S;case 2:return l+=S,l-S;case 3:return r-=S,r+S;case 0:return f-=S,f+S}}N.forEach((p,S)=>{if(p.show&&p._show){let _=p.side;p._pos=c(_,p._size),p.label!=null&&(p._lpos=c(_,p.labelSize))}})}if(V.dataIdx==null){let t=V.hover,l=t.skip=new Set(t.skip??[]);l.add(void 0);let r=t.prox=X(t.prox),f=t.bias??(t.bias=0);V.dataIdx=(c,p,S,_)=>{if(p==0)return S;let v=S,T=r(c,p,S,_)??ce,M=T>=0&&T0;)l.has(le[R])||(Z=R);if(f==0||f==1)for(R=S;W==null&&R++T&&(v=null);return v}}let Ut=t=>{V.event=t};V.idxs=fe,V._lock=!1;let Ne=V.points;Ne.show=X(Ne.show),Ne.size=X(Ne.size),Ne.stroke=X(Ne.stroke),Ne.width=X(Ne.width),Ne.fill=X(Ne.fill);let vt=n.focus=be({},e.focus||{alpha:.3},V.focus),rl=vt.prox>=0,fl=rl&&Ne.one,st=[],ul=[],cl=[];function yi(t,l){let r=Ne.show(n,l);if(r instanceof HTMLElement)return et(r,ur),et(r,t.class),Mt(r,-10,-10,K,A),L.insertBefore(r,st[l]),r}function vi(t,l){if(s==1||l>0){let r=s==1&&x[t.scale].time,f=t.value;t.value=r?mo(f)?ko(ee,vo(f,z)):f||te:f||bf,t.label=t.label||(r?df:gf)}if(fl||l>0){t.width=t.width==null?1:t.width,t.paths=t.paths||Nf||Ar,t.fillTo=X(t.fillTo||Df),t.pxAlign=+ne(t.pxAlign,H),t.pxRound=Po(t.pxAlign),t.stroke=X(t.stroke||null),t.fill=X(t.fill||null),t._stroke=t._fill=t._paths=t._focus=null;let r=Tf(Be(1,t.width),1),f=t.points=be({},{size:r,width:Be(1,r*.2),stroke:t.stroke,space:r*2,paths:Of,_stroke:null,_fill:null},t.points);f.show=X(f.show),f.filter=X(f.filter),f.fill=X(f.fill),f.stroke=X(f.stroke),f.paths=X(f.paths),f.pxAlign=t.pxAlign}if(Te){let r=jl(t,l);ye.splice(l,0,r[0]),_t.splice(l,0,r[1]),J.values.push(null)}if(Oe){fe.splice(l,0,null);let r=null;fl?l==0&&(r=yi(t,l)):l>0&&(r=yi(t,l)),st.splice(l,0,r),ul.splice(l,0,0),cl.splice(l,0,0)}Re("addSeries",l)}function As(t,l){l=l??k.length,t=s==1?ii(t,l,bo,Do):ii(t,l,{},Eo),k.splice(l,0,t),vi(k[l],l)}n.addSeries=As;function Es(t){if(k.splice(t,1),Te){J.values.splice(t,1),_t.splice(t,1);let l=ye.splice(t,1)[0];il(null,l.firstChild),l.remove()}Oe&&(fe.splice(t,1),st.splice(t,1)[0].remove(),ul.splice(t,1),cl.splice(t,1)),Re("delSeries",t)}n.delSeries=Es;let Kt=[!1,!1,!1,!1];function Ds(t,l){if(t._show=t.show,t.show){let r=t.side%2,f=x[t.scale];f==null&&(t.scale=r?k[1].scale:D,f=x[t.scale]);let c=f.time;t.size=X(t.size),t.space=X(t.space),t.rotate=X(t.rotate),Nt(t.incrs)&&t.incrs.forEach(S=>{!Ot.has(S)&&Ot.set(S,Bo(S))}),t.incrs=X(t.incrs||(f.distr==2?$r:c?G==1?Zr:ef:Zt)),t.splits=X(t.splits||(c&&f.distr==1?Ke:f.distr==3?ei:f.distr==4?_f:Sf)),t.stroke=X(t.stroke),t.grid.stroke=X(t.grid.stroke),t.ticks.stroke=X(t.ticks.stroke),t.border.stroke=X(t.border.stroke);let p=t.values;t.values=Nt(p)&&!Nt(p[0])?X(p):c?Nt(p)?yo(ee,_o(p,z)):mo(p)?nf(ee,p):p||we:p||wf,t.filter=X(t.filter||(f.distr>=3&&f.log==10?kf:f.distr==3&&f.log==2?xf:Io)),t.font=zo(t.font),t.labelFont=zo(t.labelFont),t._size=t.size(n,null,l,0),t._space=t._rotate=t._incrs=t._found=t._splits=t._values=null,t._size>0&&(Kt[l]=!0,t._el=ut(or,w))}}function Cl(t,l,r,f){let[c,p,S,_]=r,v=l%2,T=0;return v==0&&(_||p)&&(T=l==0&&!c||l==2&&!S?Ae(xo.size/3):0),v==1&&(c||S)&&(T=l==1&&!p||l==3&&!_?Ae(Mo.size/2):0),T}let ki=n.padding=(e.padding||[Cl,Cl,Cl,Cl]).map(t=>X(ne(t,Cl))),Vt=n._padding=ki.map((t,l)=>t(n,l,Kt,0)),Fe,Le=null,Pe=null,Jl=s==1?k[0].idxs:null,ht=null,Vl=!1;function xi(t,l){if(i=t??[],n.data=n._data=i,s==2){Fe=0;for(let r=1;r=0,ot=!0,hl()}}n.setData=xi;function vn(){Vl=!0;let t,l;s==1&&(Fe>0?(Le=Jl[0]=0,Pe=Jl[1]=Fe-1,t=i[0][Le],l=i[0][Pe],j==2?(t=Le,l=Pe):t==l&&(j==3?[t,l]=pn(t,t,b.log,!1):j==4?[t,l]=fi(t,t,b.log,!1):b.time?l=t+Ae(86400/G):[t,l]=un(t,l,ui,!0))):(Le=Jl[0]=t=null,Pe=Jl[1]=l=null)),zt(D,t,l)}let Zl,al,kn,xn,bn,Tn,Mn,An,En,qe;function bi(t,l,r,f,c,p){t??(t=Zi),r??(r=ai),f??(f="butt"),c??(c=Zi),p??(p="round"),t!=Zl&&(d.strokeStyle=Zl=t),c!=al&&(d.fillStyle=al=c),l!=kn&&(d.lineWidth=kn=l),p!=bn&&(d.lineJoin=bn=p),f!=Tn&&(d.lineCap=Tn=f),r!=xn&&d.setLineDash(xn=r)}function Ti(t,l,r,f){l!=al&&(d.fillStyle=al=l),t!=Mn&&(d.font=Mn=t),r!=An&&(d.textAlign=An=r),f!=En&&(d.textBaseline=En=f)}function Dn(t,l,r,f,c=0){if(f.length>0&&t.auto(n,Vl)&&(l==null||l.min==null)){let p=ne(Le,0),S=ne(Pe,f.length-1),_=r.min==null?vr(f,p,S,c,t.distr==3):[r.min,r.max];t.min=St(t.min,r.min=_[0]),t.max=Be(t.max,r.max=_[1])}}let Mi={min:null,max:null};function Ls(){for(let f in x){let c=x[f];F[f]==null&&(c.min==null||F[D]!=null&&c.auto(n,Vl))&&(F[f]=Mi)}for(let f in x){let c=x[f];F[f]==null&&c.from!=null&&F[c.from]!=null&&(F[f]=Mi)}F[D]!=null&&pl(!0);let t={};for(let f in F){let c=F[f];if(c!=null){let p=t[f]=Ml(x[f],Lr);if(c.min!=null)be(p,c);else if(f!=D||s==2)if(Fe==0&&p.from==null){let S=p.range(n,null,null,f);p.min=S[0],p.max=S[1]}else p.min=ce,p.max=-ce}}if(Fe>0){k.forEach((f,c)=>{if(s==1){let p=f.scale,S=F[p];if(S==null)return;let _=t[p];if(c==0){let v=_.range(n,_.min,_.max,p);_.min=v[0],_.max=v[1],Le=wt(_.min,i[0]),Pe=wt(_.max,i[0]),Pe-Le>1&&(i[0][Le]<_.min&&Le++,i[0][Pe]>_.max&&Pe--),f.min=ht[Le],f.max=ht[Pe]}else f.show&&f.auto&&Dn(_,S,f,i[c],f.sorted);f.idxs[0]=Le,f.idxs[1]=Pe}else if(c>0&&f.show&&f.auto){let[p,S]=f.facets,_=p.scale,v=S.scale,[T,M]=i[c],E=t[_],Y=t[v];E!=null&&Dn(E,F[_],p,T,p.sorted),Y!=null&&Dn(Y,F[v],S,M,S.sorted),f.min=S.min,f.max=S.max}});for(let f in t){let c=t[f],p=F[f];if(c.from==null&&(p==null||p.min==null)){let S=c.range(n,c.min==ce?null:c.min,c.max==-ce?null:c.max,f);c.min=S[0],c.max=S[1]}}}for(let f in t){let c=t[f];if(c.from!=null){let p=t[c.from];if(p.min==null)c.min=c.max=null;else{let S=c.range(n,p.min,p.max,f);c.min=S[0],c.max=S[1]}}}let l={},r=!1;for(let f in t){let c=t[f],p=x[f];if(p.min!=c.min||p.max!=c.max){p.min=c.min,p.max=c.max;let S=p.distr;p._min=S==3?Lt(p.min):S==4?Un(p.min,p.asinh):S==100?p.fwd(p.min):p.min,p._max=S==3?Lt(p.max):S==4?Un(p.max,p.asinh):S==100?p.fwd(p.max):p.max,l[f]=r=!0}}if(r){k.forEach((f,c)=>{s==2?c>0&&l.y&&(f._paths=null):l[f.scale]&&(f._paths=null)});for(let f in l)sl=!0,Re("setScale",f);Oe&&V.left>=0&&(Bt=ot=!0)}for(let f in F)F[f]=null}function Ps(t){let l=Xn(Le-1,0,Fe-1),r=Xn(Pe+1,0,Fe-1);for(;t[l]==null&&l>0;)l--;for(;t[r]==null&&r0){let t=k.some(l=>l._focus)&&qe!=vt.alpha;t&&(d.globalAlpha=qe=vt.alpha),k.forEach((l,r)=>{if(r>0&&l.show&&(Ai(r,!1),Ai(r,!0),l._paths==null)){let f=qe;qe!=l.alpha&&(d.globalAlpha=qe=l.alpha);let c=s==2?[0,i[r][0].length-1]:Ps(i[r]);l._paths=l.paths(n,r,c[0],c[1]),qe!=f&&(d.globalAlpha=qe=f)}}),k.forEach((l,r)=>{if(r>0&&l.show){let f=qe;qe!=l.alpha&&(d.globalAlpha=qe=l.alpha),l._paths!=null&&Ei(r,!1);{let c=l._paths!=null?l._paths.gaps:null,p=l.points.show(n,r,Le,Pe,c),S=l.points.filter(n,r,p,c);(p||S)&&(l.points._paths=l.points.paths(n,r,Le,Pe,S),Ei(r,!0))}qe!=f&&(d.globalAlpha=qe=f),Re("drawSeries",r)}}),t&&(d.globalAlpha=qe=1)}}function Ai(t,l){let r=l?k[t].points:k[t];r._stroke=r.stroke(n,t),r._fill=r.fill(n,t)}function Ei(t,l){let r=l?k[t].points:k[t],{stroke:f,fill:c,clip:p,flags:S,_stroke:_=r._stroke,_fill:v=r._fill,_width:T=r.width}=r._paths;T=ae(T*oe,3);let M=null,E=T%2/2;l&&v==null&&(v=T>0?"#fff":_);let Y=r.pxAlign==1&&E>0;if(Y&&d.translate(E,E),!l){let ie=Je-T/2,le=it-T/2,Z=je+T,W=pt+T;M=new Path2D,M.rect(ie,le,Z,W)}l?Ln(_,T,r.dash,r.cap,v,f,c,S,p):Vs(t,_,T,r.dash,r.cap,v,f,c,S,M,p),Y&&d.translate(-E,-E)}function Vs(t,l,r,f,c,p,S,_,v,T,M){let E=!1;v!=0&&q.forEach((Y,ie)=>{if(Y.series[0]==t){let le=k[Y.series[1]],Z=i[Y.series[1]],W=(le._paths||Ul).band;Nt(W)&&(W=Y.dir==1?W[0]:W[1]);let R,ge=null;le.show&&W&&xr(Z,Le,Pe)?(ge=Y.fill(n,ie)||p,R=le._paths.clip):W=null,Ln(l,r,f,c,ge,S,_,v,T,M,R,W),E=!0}}),E||Ln(l,r,f,c,p,S,_,v,T,M)}let Di=Al|li;function Ln(t,l,r,f,c,p,S,_,v,T,M,E){bi(t,l,r,f,c),(v||T||E)&&(d.save(),v&&d.clip(v),T&&d.clip(T)),E?(_&Di)==Di?(d.clip(E),M&&d.clip(M),Xl(c,S),Ql(t,p,l)):_&li?(Xl(c,S),d.clip(E),Ql(t,p,l)):_&Al&&(d.save(),d.clip(E),M&&d.clip(M),Xl(c,S),d.restore(),Ql(t,p,l)):(Xl(c,S),Ql(t,p,l)),(v||T||E)&&d.restore()}function Ql(t,l,r){r>0&&(l instanceof Map?l.forEach((f,c)=>{d.strokeStyle=Zl=c,d.stroke(f)}):l!=null&&t&&d.stroke(l))}function Xl(t,l){l instanceof Map?l.forEach((r,f)=>{d.fillStyle=al=f,d.fill(r)}):l!=null&&t&&d.fill(l)}function Rs(t,l,r,f){let c=N[t],p;if(f<=0)p=[0,0];else{let S=c._space=c.space(n,t,l,r,f),_=c._incrs=c.incrs(n,t,l,r,f,S);p=Uf(l,r,_,f,S)}return c._found=p}function Pn(t,l,r,f,c,p,S,_,v,T){let M=S%2/2;H==1&&d.translate(M,M),bi(_,S,v,T,_),d.beginPath();let E,Y,ie,le,Z=c+(f==0||f==3?-p:p);r==0?(Y=c,le=Z):(E=c,ie=Z);for(let W=0;W{if(!r.show)return;let c=x[r.scale];if(c.min==null){r._show&&(l=!1,r._show=!1,pl(!1));return}else r._show||(l=!1,r._show=!0,pl(!1));let p=r.side,S=p%2,{min:_,max:v}=c,[T,M]=Rs(f,_,v,S==0?K:A);if(M==0)return;let E=c.distr==2,Y=r._splits=r.splits(n,f,_,v,T,M,E),ie=c.distr==2?Y.map(R=>ht[R]):Y,le=c.distr==2?ht[Y[1]]-ht[Y[0]]:T,Z=r._values=r.values(n,r.filter(n,ie,f,M,le),f,M,le);r._rotate=p==2?r.rotate(n,Z,f,M):0;let W=r._size;r._size=ct(r.size(n,Z,f,t)),W!=null&&r._size!=W&&(l=!1)}),l}function Fs(t){let l=!0;return ki.forEach((r,f)=>{let c=r(n,f,Kt,t);c!=Vt[f]&&(l=!1),Vt[f]=c}),l}function Hs(){for(let t=0;tht[Ie]):ie,Z=M.distr==2?ht[ie[1]]-ht[ie[0]]:v,W=l.ticks,R=l.border,ge=W.show?W.size:0,ve=Ae(ge*oe),ze=Ae((l.alignTo==2?l._size-ge-l.gap:l.gap)*oe),ue=l._rotate*-rn/180,ke=O(l._pos*oe),Ze=(ve+ze)*_,Ge=ke+Ze;p=f==0?Ge:0,c=f==1?Ge:0;let rt=l.font[0],mt=l.align==1?vl:l.align==2?In:ue>0?vl:ue<0?In:f==0?"center":r==3?In:vl,bt=ue||f==1?"middle":r==2?Ol:Ji;Ti(rt,S,mt,bt);let Qe=l.font[1]*l.lineGap,ft=ie.map(Ie=>O(h(Ie,M,E,Y))),gt=l._values;for(let Ie=0;Ie{r>0&&(l._paths=null,t&&(s==1?(l.min=null,l.max=null):l.facets.forEach(f=>{f.min=null,f.max=null})))})}let en=!1,Cn=!1,Rl=[];function Ns(){Cn=!1;for(let t=0;t0&&queueMicrotask(Ns)}n.batch=Os;function Li(){if(_n&&(Ls(),_n=!1),sl&&(xs(),sl=!1),ql){if(de(P,vl,pe),de(P,Ol,Me),de(P,Gl,K),de(P,Il,A),de(L,vl,pe),de(L,Ol,Me),de(L,Gl,K),de(L,Il,A),de(w,Gl,Et),de(w,Il,Gt),y.width=Ae(Et*oe),y.height=Ae(Gt*oe),N.forEach(({_el:t,_show:l,_size:r,_pos:f,side:c})=>{if(t!=null)if(l){let p=c===3||c===0?r:0,S=c%2==1;de(t,S?"left":"top",f-p),de(t,S?"width":"height",r),de(t,S?"top":"left",S?Me:pe),de(t,S?"height":"width",S?A:K),Zn(t,Qt)}else et(t,Qt)}),Zl=al=kn=bn=Tn=Mn=An=En=xn=null,qe=1,Hl(!0),pe!=ol||Me!=It||K!=yt||A!=Yt){pl(!1);let t=K/yt,l=A/Yt;if(Oe&&!Bt&&V.left>=0){V.left*=t,V.top*=l,ml&&Mt(ml,Ae(V.left),0,K,A),gl&&Mt(gl,0,Ae(V.top),K,A);for(let r=0;r=0&&me.width>0){me.left*=t,me.width*=t,me.top*=l,me.height*=l;for(let r in Nn)de(Sl,r,me[r])}ol=pe,It=Me,yt=K,Yt=A}Re("setSize"),ql=!1}Et>0&&Gt>0&&(d.clearRect(0,0,y.width,y.height),Re("drawClear"),U.forEach(t=>t()),Re("draw")),me.show&&$l&&(tn(me),$l=!1),Oe&&Bt&&(qt(null,!0,!1),Bt=!1),J.show&&J.live&&ot&&(Fn(),ot=!1),m||(m=!0,n.status=1,Re("ready")),Vl=!1,en=!1}n.redraw=(t,l)=>{sl=l||!1,t!==!1?zt(D,b.min,b.max):hl()};function Vn(t,l){let r=x[t];if(r.from==null){if(Fe==0){let f=r.range(n,l.min,l.max,t);l.min=f[0],l.max=f[1]}if(l.min>l.max){let f=l.min;l.min=l.max,l.max=f}if(Fe>1&&l.min!=null&&l.max!=null&&l.max-l.min<1e-16)return;t==D&&r.distr==2&&Fe>0&&(l.min=wt(l.min,i[0]),l.max=wt(l.max,i[0]),l.min==l.max&&l.max++),F[t]=l,_n=!0,hl()}}n.setScale=Vn;let Rn,zn,ml,gl,Pi,Ci,dl,wl,Vi,Ri,he,_e,Rt=!1,He=V.drag,Ce=He.x,Ve=He.y;Oe&&(V.x&&(Rn=ut(rr,L)),V.y&&(zn=ut(fr,L)),b.ori==0?(ml=Rn,gl=zn):(ml=zn,gl=Rn),he=V.left,_e=V.top);let me=n.select=be({show:!0,over:!0,left:0,width:0,top:0,height:0},e.select),Sl=me.show?ut(sr,me.over?L:P):null;function tn(t,l){if(me.show){for(let r in t)me[r]=t[r],r in Nn&&de(Sl,r,t[r]);l!==!1&&Re("setSelect")}}n.setSelect=tn;function Ws(t){if(k[t].show)Te&&Zn(ye[t],Qt);else if(Te&&et(ye[t],Qt),Oe){let r=fl?st[0]:st[t];r!=null&&Mt(r,-10,-10,K,A)}}function zt(t,l,r){Vn(t,{min:l,max:r})}function kt(t,l,r,f){l.focus!=null&&Us(t),l.show!=null&&k.forEach((c,p)=>{p>0&&(t==p||t==null)&&(c.show=l.show,Ws(p),s==2?(zt(c.facets[0].scale,null,null),zt(c.facets[1].scale,null,null)):zt(c.scale,null,null),hl())}),r!==!1&&Re("setSeries",t,l),f&&Nl("setSeries",n,t,l)}n.setSeries=kt;function Gs(t,l){be(q[t],l)}function Is(t,l){t.fill=X(t.fill||null),t.dir=ne(t.dir,-1),l=l??q.length,q.splice(l,0,t)}function Ys(t){t==null?q.length=0:q.splice(t,1)}n.addBand=Is,n.setBand=Gs,n.delBand=Ys;function Bs(t,l){k[t].alpha=l,Oe&&st[t]!=null&&(st[t].style.opacity=l),Te&&ye[t]&&(ye[t].style.opacity=l)}let Dt,Ft,jt,_l={focus:!0};function Us(t){if(t!=jt){let l=t==null,r=vt.alpha!=1;k.forEach((f,c)=>{if(s==1||c>0){let p=l||c==0||c==t;f._focus=l?null:p,r&&Bs(c,p?1:vt.alpha)}}),jt=t,r&&hl()}}Te&&rl&&We(eo,se,t=>{V._lock||(Ut(t),jt!=null&&kt(null,_l,!0,xe.setSeries))});function xt(t,l,r){let f=x[l];r&&(t=t/oe-(f.ori==1?Me:pe));let c=K;f.ori==1&&(c=A,t=c-t),f.dir==-1&&(t=c-t);let p=f._min,S=f._max,_=t/c,v=p+(S-p)*_,T=f.distr;return T==3?bl(10,v):T==4?Tr(v,f.asinh):T==100?f.bwd(v):v}function Ks(t,l){let r=xt(t,D,l);return wt(r,i[0],Le,Pe)}n.valToIdx=t=>wt(t,i[0]),n.posToIdx=Ks,n.posToVal=xt,n.valToPos=(t,l,r)=>x[l].ori==0?a(t,x[l],r?je:K,r?Je:0):u(t,x[l],r?pt:A,r?it:0),n.setCursor=(t,l,r)=>{he=t.left,_e=t.top,qt(null,l,r)};function zi(t,l){de(Sl,vl,me.left=t),de(Sl,Gl,me.width=l)}function Fi(t,l){de(Sl,Ol,me.top=t),de(Sl,Il,me.height=l)}let zl=b.ori==0?zi:Fi,Fl=b.ori==1?zi:Fi;function js(){if(Te&&J.live)for(let t=s==2?1:0;t{fe[f]=r}):Dr(t.idx)||fe.fill(t.idx),J.idx=fe[0]),Te&&J.live){for(let r=0;r0||s==1&&!nt)&&qs(r,fe[r]);js()}ot=!1,l!==!1&&Re("setLegend")}n.setLegend=Fn;function qs(t,l){let r=k[t],f=t==0&&j==2?ht:i[t],c;nt?c=r.values(n,t,l)??Ct:(c=r.value(n,l==null?null:f[l],t,l),c=c==null?Ct:{_:c}),J.values[t]=c}function qt(t,l,r){Vi=he,Ri=_e,[he,_e]=V.move(n,he,_e),V.left=he,V.top=_e,Oe&&(ml&&Mt(ml,Ae(he),0,K,A),gl&&Mt(gl,0,Ae(_e),K,A));let f,c=Le>Pe;Dt=ce,Ft=null;let p=b.ori==0?K:A,S=b.ori==1?K:A;if(he<0||Fe==0||c){f=V.idx=null;for(let _=0;_0&&ge.show){let Ze=ue==null?-10:ue==f?T:C(s==1?i[0][ue]:i[R][0][ue],b,p,0),Ge=ke==null?-10:I(ke,s==1?x[ge.scale]:x[ge.facets[1].scale],S,0);if(rl&&ke!=null){let rt=b.ori==1?he:_e,mt=Ee(vt.dist(n,R,ue,Ge,rt));if(mt=0?1:-1,gt=Qe>=0?1:-1;gt==ft&&(gt==1?bt==1?ke>=Qe:ke<=Qe:bt==1?ke<=Qe:ke>=Qe)&&(Dt=mt,Ft=R)}else Dt=mt,Ft=R}}if(ot||fl){let rt,mt;b.ori==0?(rt=Ze,mt=Ge):(rt=Ge,mt=Ze);let bt,Qe,ft,gt,Tt,Ie,Xe=!0,$t=Ne.bbox;if($t!=null){Xe=!1;let Ye=$t(n,R);ft=Ye.left,gt=Ye.top,bt=Ye.width,Qe=Ye.height}else ft=rt,gt=mt,bt=Qe=Ne.size(n,R);if(Ie=Ne.fill(n,R),Tt=Ne.stroke(n,R),fl)R==Ft&&Dt<=vt.prox&&(M=ft,E=gt,Y=bt,ie=Qe,le=Xe,Z=Ie,W=Tt);else{let Ye=st[R];Ye!=null&&(ul[R]=ft,cl[R]=gt,ro(Ye,bt,Qe,Xe),oo(Ye,Ie,Tt),Mt(Ye,ct(ft),ct(gt),K,A))}}}}if(fl){let R=vt.prox,ge=jt==null?Dt<=R:Dt>R||Ft!=jt;if(ot||ge){let ve=st[0];ve!=null&&(ul[0]=M,cl[0]=E,ro(ve,Y,ie,le),oo(ve,Z,W),Mt(ve,ct(M),ct(E),K,A))}}}if(me.show&&Rt)if(t!=null){let[_,v]=xe.scales,[T,M]=xe.match,[E,Y]=t.cursor.sync.scales,ie=t.cursor.drag;if(Ce=ie._x,Ve=ie._y,Ce||Ve){let{left:le,top:Z,width:W,height:R}=t.select,ge=t.scales[E].ori,ve=t.posToVal,ze,ue,ke,Ze,Ge,rt=_!=null&&T(_,E),mt=v!=null&&M(v,Y);rt&&Ce?(ge==0?(ze=le,ue=W):(ze=Z,ue=R),ke=x[_],Ze=C(ve(ze,E),ke,p,0),Ge=C(ve(ze+ue,E),ke,p,0),zl(St(Ze,Ge),Ee(Ge-Ze))):zl(0,p),mt&&Ve?(ge==1?(ze=le,ue=W):(ze=Z,ue=R),ke=x[v],Ze=I(ve(ze,Y),ke,S,0),Ge=I(ve(ze+ue,Y),ke,S,0),Fl(St(Ze,Ge),Ee(Ge-Ze))):Fl(0,S)}else On()}else{let _=Ee(Vi-Pi),v=Ee(Ri-Ci);if(b.ori==1){let Y=_;_=v,v=Y}Ce=He.x&&_>=He.dist,Ve=He.y&&v>=He.dist;let T=He.uni;T!=null?Ce&&Ve&&(Ce=_>=T,Ve=v>=T,!Ce&&!Ve&&(v>_?Ve=!0:Ce=!0)):He.x&&He.y&&(Ce||Ve)&&(Ce=Ve=!0);let M,E;Ce&&(b.ori==0?(M=dl,E=he):(M=wl,E=_e),zl(St(M,E),Ee(E-M)),Ve||Fl(0,S)),Ve&&(b.ori==1?(M=dl,E=he):(M=wl,E=_e),Fl(St(M,E),Ee(E-M)),Ce||zl(0,p)),!Ce&&!Ve&&(zl(0,0),Fl(0,0))}if(He._x=Ce,He._y=Ve,t==null){if(r){if(ji!=null){let[_,v]=xe.scales;xe.values[0]=_!=null?xt(b.ori==0?he:_e,_):null,xe.values[1]=v!=null?xt(b.ori==1?he:_e,v):null}Nl(Yn,n,he,_e,K,A,f)}if(rl){let _=r&&xe.setSeries,v=vt.prox;jt==null?Dt<=v&&kt(Ft,_l,!0,_):Dt>v?kt(null,_l,!0,_):Ft!=jt&&kt(Ft,_l,!0,_)}}ot&&(J.idx=f,Fn()),l!==!1&&Re("setCursor")}let Ht=null;Object.defineProperty(n,"rect",{get(){return Ht==null&&Hl(!1),Ht}});function Hl(t=!1){t?Ht=null:(Ht=L.getBoundingClientRect(),Re("syncRect",Ht))}function Hi(t,l,r,f,c,p,S){V._lock||Rt&&t!=null&&t.movementX==0&&t.movementY==0||(Hn(t,l,r,f,c,p,S,!1,t!=null),t!=null?qt(null,!0,!0):qt(l,!0,!1))}function Hn(t,l,r,f,c,p,S,_,v){if(Ht==null&&Hl(!1),Ut(t),t!=null)r=t.clientX-Ht.left,f=t.clientY-Ht.top;else{if(r<0||f<0){he=-10,_e=-10;return}let[T,M]=xe.scales,E=l.cursor.sync,[Y,ie]=E.values,[le,Z]=E.scales,[W,R]=xe.match,ge=l.axes[0].side%2==1,ve=b.ori==0?K:A,ze=b.ori==1?K:A,ue=ge?p:c,ke=ge?c:p,Ze=ge?f:r,Ge=ge?r:f;if(le!=null?r=W(T,le)?h(Y,x[T],ve,0):-10:r=ve*(Ze/ue),Z!=null?f=R(M,Z)?h(ie,x[M],ze,0):-10:f=ze*(Ge/ke),b.ori==1){let rt=r;r=f,f=rt}}v&&(l==null||l.cursor.event.type==Yn)&&((r<=1||r>=K-1)&&(r=Jt(r,K)),(f<=1||f>=A-1)&&(f=Jt(f,A))),_?(Pi=r,Ci=f,[dl,wl]=V.move(n,r,f)):(he=r,_e=f)}let Nn={width:0,height:0,left:0,top:0};function On(){tn(Nn,!1)}let Ni,Oi,Wi,Gi;function Ii(t,l,r,f,c,p,S){Rt=!0,Ce=Ve=He._x=He._y=!1,Hn(t,l,r,f,c,p,S,!0,!1),t!=null&&(We(Bn,$n,Yi,!1),Nl(Qi,n,dl,wl,K,A,null));let{left:_,top:v,width:T,height:M}=me;Ni=_,Oi=v,Wi=T,Gi=M}function Yi(t,l,r,f,c,p,S){Rt=He._x=He._y=!1,Hn(t,l,r,f,c,p,S,!1,!0);let{left:_,top:v,width:T,height:M}=me,E=T>0||M>0,Y=Ni!=_||Oi!=v||Wi!=T||Gi!=M;if(E&&Y&&tn(me),He.setScale&&E&&Y){let ie=_,le=T,Z=v,W=M;if(b.ori==1&&(ie=v,le=M,Z=_,W=T),Ce&&zt(D,xt(ie,D),xt(ie+le,D)),Ve)for(let R in x){let ge=x[R];R!=D&&ge.from==null&&ge.min!=ce&&zt(R,xt(Z+W,R),xt(Z,R))}On()}else V.lock&&(V._lock=!V._lock,qt(l,!0,t!=null));t!=null&&(il(Bn,$n),Nl(Bn,n,he,_e,K,A,null))}function $s(t,l,r,f,c,p,S){if(V._lock)return;Ut(t);let _=Rt;if(Rt){let v=!0,T=!0,M=10,E,Y;b.ori==0?(E=Ce,Y=Ve):(E=Ve,Y=Ce),E&&Y&&(v=he<=M||he>=K-M,T=_e<=M||_e>=A-M),E&&v&&(he=he{let c=xe.match[2];r=c(n,l,r),r!=-1&&kt(r,f,!0,!1)},Oe&&(We(Qi,L,Ii),We(Yn,L,Hi),We(Xi,L,t=>{Ut(t),Hl(!1)}),We(eo,L,$s),We(to,L,Bi),ni.add(n),n.syncRect=Hl);let ln=n.hooks=e.hooks||{};function Re(t,l,r){Cn?Rl.push([t,l,r]):t in ln&&ln[t].forEach(f=>{f.call(null,n,l,r)})}(e.plugins||[]).forEach(t=>{for(let l in t.hooks)ln[l]=(ln[l]||[]).concat(t.hooks[l])});let Ki=(t,l,r)=>r,xe=be({key:null,setSeries:!1,filters:{pub:po,sub:po},scales:[D,k[1]?k[1].scale:null],match:[ho,ho,Ki],values:[null,null]},V.sync);xe.match.length==2&&xe.match.push(Ki),V.sync=xe;let ji=xe.key,Wn=cs(ji);function Nl(t,l,r,f,c,p,S){xe.filters.pub(t,l,r,f,c,p,S)&&Wn.pub(t,l,r,f,c,p,S)}Wn.sub(n);function Js(t,l,r,f,c,p,S){xe.filters.sub(t,l,r,f,c,p,S)&&yl[t](null,l,r,f,c,p,S)}n.pub=Js;function Zs(){Wn.unsub(n),ni.delete(n),Wt.clear(),Qn(fn,xl,Ui),g.remove(),se?.remove(),Re("destroy")}n.destroy=Zs;function Gn(){Re("init",e,i),xi(i||e.data,!1),F[D]?Vn(D,F[D]):vn(),$l=me.show&&(me.width>0||me.height>0),Bt=ot=!0,yn(e.width,e.height)}return k.forEach(vi),N.forEach(Ds),o?o instanceof HTMLElement?(o.appendChild(g),Gn()):o(n,Gn):Gn(),n}Ue.assign=be;Ue.fmtNum=ci;Ue.rangeNum=un;Ue.rangeLog=pn;Ue.rangeAsinh=fi;Ue.orient=tl;Ue.pxRatio=oe;Ue.join=Fr;Ue.fmtDate=pi,Ue.tzDate=jr;Ue.sync=cs;{Ue.addGap=Lf,Ue.clipGaps=gn;let e=Ue.paths={points:ds};e.linear=Ss,e.stepped=Vf,e.bars=Rf,e.spline=Ff}export{Ue as default}; +//# sourceMappingURL=uplot.mjs.map diff --git a/suites-experimental/tools-gps-tracks/dist/uplot.mjs.map b/suites-experimental/tools-gps-tracks/dist/uplot.mjs.map new file mode 100644 index 000000000..7d65813cb --- /dev/null +++ b/suites-experimental/tools-gps-tracks/dist/uplot.mjs.map @@ -0,0 +1,7 @@ +{ + "version": 3, + "sources": ["../node_modules/uplot/dist/uPlot.esm.js"], + "sourcesContent": ["/**\n* Copyright (c) 2025, Leon Sorokin\n* All rights reserved. (MIT Licensed)\n*\n* uPlot.js (\u03BCPlot)\n* A small, fast chart for time series, lines, areas, ohlc & bars\n* https://github.com/leeoniya/uPlot (v1.6.32)\n*/\n\nconst FEAT_TIME = true;\n\nconst pre = \"u-\";\n\nconst UPLOT = \"uplot\";\nconst ORI_HZ = pre + \"hz\";\nconst ORI_VT = pre + \"vt\";\nconst TITLE = pre + \"title\";\nconst WRAP = pre + \"wrap\";\nconst UNDER = pre + \"under\";\nconst OVER = pre + \"over\";\nconst AXIS = pre + \"axis\";\nconst OFF = pre + \"off\";\nconst SELECT = pre + \"select\";\nconst CURSOR_X = pre + \"cursor-x\";\nconst CURSOR_Y = pre + \"cursor-y\";\nconst CURSOR_PT = pre + \"cursor-pt\";\nconst LEGEND = pre + \"legend\";\nconst LEGEND_LIVE = pre + \"live\";\nconst LEGEND_INLINE = pre + \"inline\";\nconst LEGEND_SERIES = pre + \"series\";\nconst LEGEND_MARKER = pre + \"marker\";\nconst LEGEND_LABEL = pre + \"label\";\nconst LEGEND_VALUE = pre + \"value\";\n\nconst WIDTH = \"width\";\nconst HEIGHT = \"height\";\nconst TOP = \"top\";\nconst BOTTOM = \"bottom\";\nconst LEFT = \"left\";\nconst RIGHT = \"right\";\nconst hexBlack = \"#000\";\nconst transparent = hexBlack + \"0\";\n\nconst mousemove = \"mousemove\";\nconst mousedown = \"mousedown\";\nconst mouseup = \"mouseup\";\nconst mouseenter = \"mouseenter\";\nconst mouseleave = \"mouseleave\";\nconst dblclick = \"dblclick\";\nconst resize = \"resize\";\nconst scroll = \"scroll\";\n\nconst change = \"change\";\nconst dppxchange = \"dppxchange\";\n\nconst LEGEND_DISP = \"--\";\n\nconst domEnv = typeof window != 'undefined';\n\nconst doc = domEnv ? document : null;\nconst win = domEnv ? window : null;\nconst nav = domEnv ? navigator : null;\n\nlet pxRatio;\n\n//export const canHover = domEnv && !win.matchMedia('(hover: none)').matches;\n\nlet query;\n\nfunction setPxRatio() {\n\tlet _pxRatio = devicePixelRatio;\n\n\t// during print preview, Chrome fires off these dppx queries even without changes\n\tif (pxRatio != _pxRatio) {\n\t\tpxRatio = _pxRatio;\n\n\t\tquery && off(change, query, setPxRatio);\n\t\tquery = matchMedia(`(min-resolution: ${pxRatio - 0.001}dppx) and (max-resolution: ${pxRatio + 0.001}dppx)`);\n\t\ton(change, query, setPxRatio);\n\n\t\twin.dispatchEvent(new CustomEvent(dppxchange));\n\t}\n}\n\nfunction addClass(el, c) {\n\tif (c != null) {\n\t\tlet cl = el.classList;\n\t\t!cl.contains(c) && cl.add(c);\n\t}\n}\n\nfunction remClass(el, c) {\n\tlet cl = el.classList;\n\tcl.contains(c) && cl.remove(c);\n}\n\nfunction setStylePx(el, name, value) {\n\tel.style[name] = value + \"px\";\n}\n\nfunction placeTag(tag, cls, targ, refEl) {\n\tlet el = doc.createElement(tag);\n\n\tif (cls != null)\n\t\taddClass(el, cls);\n\n\tif (targ != null)\n\t\ttarg.insertBefore(el, refEl);\n\n\treturn el;\n}\n\nfunction placeDiv(cls, targ) {\n\treturn placeTag(\"div\", cls, targ);\n}\n\nconst xformCache = new WeakMap();\n\nfunction elTrans(el, xPos, yPos, xMax, yMax) {\n\tlet xform = \"translate(\" + xPos + \"px,\" + yPos + \"px)\";\n\tlet xformOld = xformCache.get(el);\n\n\tif (xform != xformOld) {\n\t\tel.style.transform = xform;\n\t\txformCache.set(el, xform);\n\n\t\tif (xPos < 0 || yPos < 0 || xPos > xMax || yPos > yMax)\n\t\t\taddClass(el, OFF);\n\t\telse\n\t\t\tremClass(el, OFF);\n\t}\n}\n\nconst colorCache = new WeakMap();\n\nfunction elColor(el, background, borderColor) {\n\tlet newColor = background + borderColor;\n\tlet oldColor = colorCache.get(el);\n\n\tif (newColor != oldColor) {\n\t\tcolorCache.set(el, newColor);\n\t\tel.style.background = background;\n\t\tel.style.borderColor = borderColor;\n\t}\n}\n\nconst sizeCache = new WeakMap();\n\nfunction elSize(el, newWid, newHgt, centered) {\n\tlet newSize = newWid + \"\" + newHgt;\n\tlet oldSize = sizeCache.get(el);\n\n\tif (newSize != oldSize) {\n\t\tsizeCache.set(el, newSize);\n\t\tel.style.height = newHgt + \"px\";\n\t\tel.style.width = newWid + \"px\";\n\t\tel.style.marginLeft = centered ? -newWid/2 + \"px\" : 0;\n\t\tel.style.marginTop = centered ? -newHgt/2 + \"px\" : 0;\n\t}\n}\n\nconst evOpts = {passive: true};\nconst evOpts2 = {...evOpts, capture: true};\n\nfunction on(ev, el, cb, capt) {\n\tel.addEventListener(ev, cb, capt ? evOpts2 : evOpts);\n}\n\nfunction off(ev, el, cb, capt) {\n\tel.removeEventListener(ev, cb, evOpts);\n}\n\ndomEnv && setPxRatio();\n\n// binary search for index of closest value\nfunction closestIdx(num, arr, lo, hi) {\n\tlet mid;\n\tlo = lo || 0;\n\thi = hi || arr.length - 1;\n\tlet bitwise = hi <= 2147483647;\n\n\twhile (hi - lo > 1) {\n\t\tmid = bitwise ? (lo + hi) >> 1 : floor((lo + hi) / 2);\n\n\t\tif (arr[mid] < num)\n\t\t\tlo = mid;\n\t\telse\n\t\t\thi = mid;\n\t}\n\n\tif (num - arr[lo] <= arr[hi] - num)\n\t\treturn lo;\n\n\treturn hi;\n}\n\nfunction makeIndexOfs(predicate) {\n\t let indexOfs = (data, _i0, _i1) => {\n\t\tlet i0 = -1;\n\t\tlet i1 = -1;\n\n\t\tfor (let i = _i0; i <= _i1; i++) {\n\t\t\tif (predicate(data[i])) {\n\t\t\t\ti0 = i;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\n\t\tfor (let i = _i1; i >= _i0; i--) {\n\t\t\tif (predicate(data[i])) {\n\t\t\t\ti1 = i;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\n\t\treturn [i0, i1];\n\t };\n\n\t return indexOfs;\n}\n\nconst notNullish = v => v != null;\nconst isPositive = v => v != null && v > 0;\n\nconst nonNullIdxs = makeIndexOfs(notNullish);\nconst positiveIdxs = makeIndexOfs(isPositive);\n\nfunction getMinMax(data, _i0, _i1, sorted = 0, log = false) {\n//\tconsole.log(\"getMinMax()\");\n\n\tlet getEdgeIdxs = log ? positiveIdxs : nonNullIdxs;\n\tlet predicate = log ? isPositive : notNullish;\n\n\t[_i0, _i1] = getEdgeIdxs(data, _i0, _i1);\n\n\tlet _min = data[_i0];\n\tlet _max = data[_i0];\n\n\tif (_i0 > -1) {\n\t\tif (sorted == 1) {\n\t\t\t_min = data[_i0];\n\t\t\t_max = data[_i1];\n\t\t}\n\t\telse if (sorted == -1) {\n\t\t\t_min = data[_i1];\n\t\t\t_max = data[_i0];\n\t\t}\n\t\telse {\n\t\t\tfor (let i = _i0; i <= _i1; i++) {\n\t\t\t\tlet v = data[i];\n\n\t\t\t\tif (predicate(v)) {\n\t\t\t\t\tif (v < _min)\n\t\t\t\t\t\t_min = v;\n\t\t\t\t\telse if (v > _max)\n\t\t\t\t\t\t_max = v;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\treturn [_min ?? inf, _max ?? -inf]; // todo: fix to return nulls\n}\n\nfunction rangeLog(min, max, base, fullMags) {\n\tlet minSign = sign(min);\n\tlet maxSign = sign(max);\n\n\tif (min == max) {\n\t\tif (minSign == -1) {\n\t\t\tmin *= base;\n\t\t\tmax /= base;\n\t\t}\n\t\telse {\n\t\t\tmin /= base;\n\t\t\tmax *= base;\n\t\t}\n\t}\n\n\tlet logFn = base == 10 ? log10 : log2;\n\n\tlet growMinAbs = minSign == 1 ? floor : ceil;\n\tlet growMaxAbs = maxSign == 1 ? ceil : floor;\n\n\tlet minExp = growMinAbs(logFn(abs(min)));\n\tlet maxExp = growMaxAbs(logFn(abs(max)));\n\n\tlet minIncr = pow(base, minExp);\n\tlet maxIncr = pow(base, maxExp);\n\n\t// fix values like Math.pow(10, -5) === 0.000009999999999999999\n\tif (base == 10) {\n\t\tif (minExp < 0)\n\t\t\tminIncr = roundDec(minIncr, -minExp);\n\t\tif (maxExp < 0)\n\t\t\tmaxIncr = roundDec(maxIncr, -maxExp);\n\t}\n\n\tif (fullMags || base == 2) {\n\t\tmin = minIncr * minSign;\n\t\tmax = maxIncr * maxSign;\n\t}\n\telse {\n\t\tmin = incrRoundDn(min, minIncr);\n\t\tmax = incrRoundUp(max, maxIncr);\n\t}\n\n\treturn [min, max];\n}\n\nfunction rangeAsinh(min, max, base, fullMags) {\n\tlet minMax = rangeLog(min, max, base, fullMags);\n\n\tif (min == 0)\n\t\tminMax[0] = 0;\n\n\tif (max == 0)\n\t\tminMax[1] = 0;\n\n\treturn minMax;\n}\n\nconst rangePad = 0.1;\n\nconst autoRangePart = {\n\tmode: 3,\n\tpad: rangePad,\n};\n\nconst _eqRangePart = {\n\tpad: 0,\n\tsoft: null,\n\tmode: 0,\n};\n\nconst _eqRange = {\n\tmin: _eqRangePart,\n\tmax: _eqRangePart,\n};\n\n// this ensures that non-temporal/numeric y-axes get multiple-snapped padding added above/below\n// TODO: also account for incrs when snapping to ensure top of axis gets a tick & value\nfunction rangeNum(_min, _max, mult, extra) {\n\tif (isObj(mult))\n\t\treturn _rangeNum(_min, _max, mult);\n\n\t_eqRangePart.pad = mult;\n\t_eqRangePart.soft = extra ? 0 : null;\n\t_eqRangePart.mode = extra ? 3 : 0;\n\n\treturn _rangeNum(_min, _max, _eqRange);\n}\n\n// nullish coalesce\nfunction ifNull(lh, rh) {\n\treturn lh == null ? rh : lh;\n}\n\n// checks if given index range in an array contains a non-null value\n// aka a range-bounded Array.some()\nfunction hasData(data, idx0, idx1) {\n\tidx0 = ifNull(idx0, 0);\n\tidx1 = ifNull(idx1, data.length - 1);\n\n\twhile (idx0 <= idx1) {\n\t\tif (data[idx0] != null)\n\t\t\treturn true;\n\t\tidx0++;\n\t}\n\n\treturn false;\n}\n\nfunction _rangeNum(_min, _max, cfg) {\n\tlet cmin = cfg.min;\n\tlet cmax = cfg.max;\n\n\tlet padMin = ifNull(cmin.pad, 0);\n\tlet padMax = ifNull(cmax.pad, 0);\n\n\tlet hardMin = ifNull(cmin.hard, -inf);\n\tlet hardMax = ifNull(cmax.hard, inf);\n\n\tlet softMin = ifNull(cmin.soft, inf);\n\tlet softMax = ifNull(cmax.soft, -inf);\n\n\tlet softMinMode = ifNull(cmin.mode, 0);\n\tlet softMaxMode = ifNull(cmax.mode, 0);\n\n\tlet delta = _max - _min;\n\tlet deltaMag = log10(delta);\n\n\tlet scalarMax = max(abs(_min), abs(_max));\n\tlet scalarMag = log10(scalarMax);\n\n\tlet scalarMagDelta = abs(scalarMag - deltaMag);\n\n\t// this handles situations like 89.7, 89.69999999999999\n\t// by assuming 0.001x deltas are precision errors\n//\tif (delta > 0 && delta < abs(_max) / 1e3)\n//\t\tdelta = 0;\n\n\t// treat data as flat if delta is less than 1e-24\n\t// or range is 11+ orders of magnitude below raw values, e.g. 99999999.99999996 - 100000000.00000004\n\tif (delta < 1e-24 || scalarMagDelta > 10) {\n\t\tdelta = 0;\n\n\t\t// if soft mode is 2 and all vals are flat at 0, avoid the 0.1 * 1e3 fallback\n\t\t// this prevents 0,0,0 from ranging to -100,100 when softMin/softMax are -1,1\n\t\tif (_min == 0 || _max == 0) {\n\t\t\tdelta = 1e-24;\n\n\t\t\tif (softMinMode == 2 && softMin != inf)\n\t\t\t\tpadMin = 0;\n\n\t\t\tif (softMaxMode == 2 && softMax != -inf)\n\t\t\t\tpadMax = 0;\n\t\t}\n\t}\n\n\tlet nonZeroDelta = delta || scalarMax || 1e3;\n\tlet mag = log10(nonZeroDelta);\n\tlet base = pow(10, floor(mag));\n\n\tlet _padMin = nonZeroDelta * (delta == 0 ? (_min == 0 ? .1 : 1) : padMin);\n\tlet _newMin = roundDec(incrRoundDn(_min - _padMin, base/10), 24);\n\tlet _softMin = _min >= softMin && (softMinMode == 1 || softMinMode == 3 && _newMin <= softMin || softMinMode == 2 && _newMin >= softMin) ? softMin : inf;\n\tlet minLim = max(hardMin, _newMin < _softMin && _min >= _softMin ? _softMin : min(_softMin, _newMin));\n\n\tlet _padMax = nonZeroDelta * (delta == 0 ? (_max == 0 ? .1 : 1) : padMax);\n\tlet _newMax = roundDec(incrRoundUp(_max + _padMax, base/10), 24);\n\tlet _softMax = _max <= softMax && (softMaxMode == 1 || softMaxMode == 3 && _newMax >= softMax || softMaxMode == 2 && _newMax <= softMax) ? softMax : -inf;\n\tlet maxLim = min(hardMax, _newMax > _softMax && _max <= _softMax ? _softMax : max(_softMax, _newMax));\n\n\tif (minLim == maxLim && minLim == 0)\n\t\tmaxLim = 100;\n\n\treturn [minLim, maxLim];\n}\n\n// alternative: https://stackoverflow.com/a/2254896\nconst numFormatter = new Intl.NumberFormat(domEnv ? nav.language : 'en-US');\nconst fmtNum = val => numFormatter.format(val);\n\nconst M = Math;\n\nconst PI = M.PI;\nconst abs = M.abs;\nconst floor = M.floor;\nconst round = M.round;\nconst ceil = M.ceil;\nconst min = M.min;\nconst max = M.max;\nconst pow = M.pow;\nconst sign = M.sign;\nconst log10 = M.log10;\nconst log2 = M.log2;\n// TODO: seems like this needs to match asinh impl if the passed v is tweaked?\nconst sinh = (v, linthresh = 1) => M.sinh(v) * linthresh;\nconst asinh = (v, linthresh = 1) => M.asinh(v / linthresh);\n\nconst inf = Infinity;\n\nfunction numIntDigits(x) {\n\treturn (log10((x ^ (x >> 31)) - (x >> 31)) | 0) + 1;\n}\n\nfunction clamp(num, _min, _max) {\n\treturn min(max(num, _min), _max);\n}\n\nfunction isFn(v) {\n\treturn typeof v == \"function\";\n}\n\nfunction fnOrSelf(v) {\n\treturn isFn(v) ? v : () => v;\n}\n\nconst noop = () => {};\n\n// note: these identity fns may get deoptimized if reused for different arg types\n// a TS version would enforce they stay monotyped and require making variants\nconst retArg0 = _0 => _0;\n\nconst retArg1 = (_0, _1) => _1;\n\nconst retNull = _ => null;\n\nconst retTrue = _ => true;\n\nconst retEq = (a, b) => a == b;\n\nconst regex6 = /\\.\\d*?(?=9{6,}|0{6,})/gm;\n\n// e.g. 17999.204999999998 -> 17999.205\nconst fixFloat = val => {\n\tif (isInt(val) || fixedDec.has(val))\n\t\treturn val;\n\n\tconst str = `${val}`;\n\n\tconst match = str.match(regex6);\n\n\tif (match == null)\n\t\treturn val;\n\n\tlet len = match[0].length - 1;\n\n\t// e.g. 1.0000000000000001e-24\n\tif (str.indexOf('e-') != -1) {\n\t\tlet [num, exp] = str.split('e');\n\t\treturn +`${fixFloat(num)}e${exp}`;\n\t}\n\n\treturn roundDec(val, len);\n};\n\nfunction incrRound(num, incr) {\n\treturn fixFloat(roundDec(fixFloat(num/incr))*incr);\n}\n\nfunction incrRoundUp(num, incr) {\n\treturn fixFloat(ceil(fixFloat(num/incr))*incr);\n}\n\nfunction incrRoundDn(num, incr) {\n\treturn fixFloat(floor(fixFloat(num/incr))*incr);\n}\n\n// https://stackoverflow.com/a/48764436\n// rounds half away from zero\nfunction roundDec(val, dec = 0) {\n\tif (isInt(val))\n\t\treturn val;\n//\telse if (dec == 0)\n//\t\treturn round(val);\n\n\tlet p = 10 ** dec;\n\tlet n = (val * p) * (1 + Number.EPSILON);\n\treturn round(n) / p;\n}\n\nconst fixedDec = new Map();\n\nfunction guessDec(num) {\n\treturn ((\"\"+num).split(\".\")[1] || \"\").length;\n}\n\nfunction genIncrs(base, minExp, maxExp, mults) {\n\tlet incrs = [];\n\n\tlet multDec = mults.map(guessDec);\n\n\tfor (let exp = minExp; exp < maxExp; exp++) {\n\t\tlet expa = abs(exp);\n\t\tlet mag = roundDec(pow(base, exp), expa);\n\n\t\tfor (let i = 0; i < mults.length; i++) {\n\t\t\tlet _incr = base == 10 ? +`${mults[i]}e${exp}` : mults[i] * mag;\n\t\t\tlet dec = (exp >= 0 ? 0 : expa) + (exp >= multDec[i] ? 0 : multDec[i]);\n\t\t\tlet incr = base == 10 ? _incr : roundDec(_incr, dec);\n\t\t\tincrs.push(incr);\n\t\t\tfixedDec.set(incr, dec);\n\t\t}\n\t}\n\n\treturn incrs;\n}\n\n//export const assign = Object.assign;\n\nconst EMPTY_OBJ = {};\nconst EMPTY_ARR = [];\n\nconst nullNullTuple = [null, null];\n\nconst isArr = Array.isArray;\nconst isInt = Number.isInteger;\nconst isUndef = v => v === void 0;\n\nfunction isStr(v) {\n\treturn typeof v == 'string';\n}\n\nfunction isObj(v) {\n\tlet is = false;\n\n\tif (v != null) {\n\t\tlet c = v.constructor;\n\t\tis = c == null || c == Object;\n\t}\n\n\treturn is;\n}\n\nfunction fastIsObj(v) {\n\treturn v != null && typeof v == 'object';\n}\n\nconst TypedArray = Object.getPrototypeOf(Uint8Array);\n\nconst __proto__ = \"__proto__\";\n\nfunction copy(o, _isObj = isObj) {\n\tlet out;\n\n\tif (isArr(o)) {\n\t\tlet val = o.find(v => v != null);\n\n\t\tif (isArr(val) || _isObj(val)) {\n\t\t\tout = Array(o.length);\n\t\t\tfor (let i = 0; i < o.length; i++)\n\t\t\t\tout[i] = copy(o[i], _isObj);\n\t\t}\n\t\telse\n\t\t\tout = o.slice();\n\t}\n\telse if (o instanceof TypedArray) // also (ArrayBuffer.isView(o) && !(o instanceof DataView))\n\t\tout = o.slice();\n\telse if (_isObj(o)) {\n\t\tout = {};\n\t\tfor (let k in o) {\n\t\t\tif (k != __proto__)\n\t\t\t\tout[k] = copy(o[k], _isObj);\n\t\t}\n\t}\n\telse\n\t\tout = o;\n\n\treturn out;\n}\n\nfunction assign(targ) {\n\tlet args = arguments;\n\n\tfor (let i = 1; i < args.length; i++) {\n\t\tlet src = args[i];\n\n\t\tfor (let key in src) {\n\t\t\tif (key != __proto__) {\n\t\t\t\tif (isObj(targ[key]))\n\t\t\t\t\tassign(targ[key], copy(src[key]));\n\t\t\t\telse\n\t\t\t\t\ttarg[key] = copy(src[key]);\n\t\t\t}\n\t\t}\n\t}\n\n\treturn targ;\n}\n\n// nullModes\nconst NULL_REMOVE = 0; // nulls are converted to undefined (e.g. for spanGaps: true)\nconst NULL_RETAIN = 1; // nulls are retained, with alignment artifacts set to undefined (default)\nconst NULL_EXPAND = 2; // nulls are expanded to include any adjacent alignment artifacts\n\n// sets undefined values to nulls when adjacent to existing nulls (minesweeper)\nfunction nullExpand(yVals, nullIdxs, alignedLen) {\n\tfor (let i = 0, xi, lastNullIdx = -1; i < nullIdxs.length; i++) {\n\t\tlet nullIdx = nullIdxs[i];\n\n\t\tif (nullIdx > lastNullIdx) {\n\t\t\txi = nullIdx - 1;\n\t\t\twhile (xi >= 0 && yVals[xi] == null)\n\t\t\t\tyVals[xi--] = null;\n\n\t\t\txi = nullIdx + 1;\n\t\t\twhile (xi < alignedLen && yVals[xi] == null)\n\t\t\t\tyVals[lastNullIdx = xi++] = null;\n\t\t}\n\t}\n}\n\n// nullModes is a tables-matched array indicating how to treat nulls in each series\n// output is sorted ASC on the joined field (table[0]) and duplicate join values are collapsed\nfunction join(tables, nullModes) {\n\tif (allHeadersSame(tables)) {\n\t//\tconsole.log('cheap join!');\n\n\t\tlet table = tables[0].slice();\n\n\t\tfor (let i = 1; i < tables.length; i++)\n\t\t\ttable.push(...tables[i].slice(1));\n\n\t\tif (!isAsc(table[0]))\n\t\t\ttable = sortCols(table);\n\n\t\treturn table;\n\t}\n\n\tlet xVals = new Set();\n\n\tfor (let ti = 0; ti < tables.length; ti++) {\n\t\tlet t = tables[ti];\n\t\tlet xs = t[0];\n\t\tlet len = xs.length;\n\n\t\tfor (let i = 0; i < len; i++)\n\t\t\txVals.add(xs[i]);\n\t}\n\n\tlet data = [Array.from(xVals).sort((a, b) => a - b)];\n\n\tlet alignedLen = data[0].length;\n\n\tlet xIdxs = new Map();\n\n\tfor (let i = 0; i < alignedLen; i++)\n\t\txIdxs.set(data[0][i], i);\n\n\tfor (let ti = 0; ti < tables.length; ti++) {\n\t\tlet t = tables[ti];\n\t\tlet xs = t[0];\n\n\t\tfor (let si = 1; si < t.length; si++) {\n\t\t\tlet ys = t[si];\n\n\t\t\tlet yVals = Array(alignedLen).fill(undefined);\n\n\t\t\tlet nullMode = nullModes ? nullModes[ti][si] : NULL_RETAIN;\n\n\t\t\tlet nullIdxs = [];\n\n\t\t\tfor (let i = 0; i < ys.length; i++) {\n\t\t\t\tlet yVal = ys[i];\n\t\t\t\tlet alignedIdx = xIdxs.get(xs[i]);\n\n\t\t\t\tif (yVal === null) {\n\t\t\t\t\tif (nullMode != NULL_REMOVE) {\n\t\t\t\t\t\tyVals[alignedIdx] = yVal;\n\n\t\t\t\t\t\tif (nullMode == NULL_EXPAND)\n\t\t\t\t\t\t\tnullIdxs.push(alignedIdx);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t\tyVals[alignedIdx] = yVal;\n\t\t\t}\n\n\t\t\tnullExpand(yVals, nullIdxs, alignedLen);\n\n\t\t\tdata.push(yVals);\n\t\t}\n\t}\n\n\treturn data;\n}\n\nconst microTask = typeof queueMicrotask == \"undefined\" ? fn => Promise.resolve().then(fn) : queueMicrotask;\n\n// TODO: https://github.com/dy/sort-ids (~2x faster for 1e5+ arrays)\nfunction sortCols(table) {\n\tlet head = table[0];\n\tlet rlen = head.length;\n\n\tlet idxs = Array(rlen);\n\tfor (let i = 0; i < idxs.length; i++)\n\t\tidxs[i] = i;\n\n\tidxs.sort((i0, i1) => head[i0] - head[i1]);\n\n\tlet table2 = [];\n\tfor (let i = 0; i < table.length; i++) {\n\t\tlet row = table[i];\n\t\tlet row2 = Array(rlen);\n\n\t\tfor (let j = 0; j < rlen; j++)\n\t\t\trow2[j] = row[idxs[j]];\n\n\t\ttable2.push(row2);\n\t}\n\n\treturn table2;\n}\n\n// test if we can do cheap join (all join fields same)\nfunction allHeadersSame(tables) {\n\tlet vals0 = tables[0][0];\n\tlet len0 = vals0.length;\n\n\tfor (let i = 1; i < tables.length; i++) {\n\t\tlet vals1 = tables[i][0];\n\n\t\tif (vals1.length != len0)\n\t\t\treturn false;\n\n\t\tif (vals1 != vals0) {\n\t\t\tfor (let j = 0; j < len0; j++) {\n\t\t\t\tif (vals1[j] != vals0[j])\n\t\t\t\t\treturn false;\n\t\t\t}\n\t\t}\n\t}\n\n\treturn true;\n}\n\nfunction isAsc(vals, samples = 100) {\n\tconst len = vals.length;\n\n\t// empty or single value\n\tif (len <= 1)\n\t\treturn true;\n\n\t// skip leading & trailing nullish\n\tlet firstIdx = 0;\n\tlet lastIdx = len - 1;\n\n\twhile (firstIdx <= lastIdx && vals[firstIdx] == null)\n\t\tfirstIdx++;\n\n\twhile (lastIdx >= firstIdx && vals[lastIdx] == null)\n\t\tlastIdx--;\n\n\t// all nullish or one value surrounded by nullish\n\tif (lastIdx <= firstIdx)\n\t\treturn true;\n\n\tconst stride = max(1, floor((lastIdx - firstIdx + 1) / samples));\n\n\tfor (let prevVal = vals[firstIdx], i = firstIdx + stride; i <= lastIdx; i += stride) {\n\t\tconst v = vals[i];\n\n\t\tif (v != null) {\n\t\t\tif (v <= prevVal)\n\t\t\t\treturn false;\n\n\t\t\tprevVal = v;\n\t\t}\n\t}\n\n\treturn true;\n}\n\nconst months = [\n\t\"January\",\n\t\"February\",\n\t\"March\",\n\t\"April\",\n\t\"May\",\n\t\"June\",\n\t\"July\",\n\t\"August\",\n\t\"September\",\n\t\"October\",\n\t\"November\",\n\t\"December\",\n];\n\nconst days = [\n\t\"Sunday\",\n\t\"Monday\",\n\t\"Tuesday\",\n\t\"Wednesday\",\n\t\"Thursday\",\n\t\"Friday\",\n\t\"Saturday\",\n];\n\nfunction slice3(str) {\n\treturn str.slice(0, 3);\n}\n\nconst days3 = days.map(slice3);\n\nconst months3 = months.map(slice3);\n\nconst engNames = {\n\tMMMM: months,\n\tMMM: months3,\n\tWWWW: days,\n\tWWW: days3,\n};\n\nfunction zeroPad2(int) {\n\treturn (int < 10 ? '0' : '') + int;\n}\n\nfunction zeroPad3(int) {\n\treturn (int < 10 ? '00' : int < 100 ? '0' : '') + int;\n}\n\n/*\nfunction suffix(int) {\n\tlet mod10 = int % 10;\n\n\treturn int + (\n\t\tmod10 == 1 && int != 11 ? \"st\" :\n\t\tmod10 == 2 && int != 12 ? \"nd\" :\n\t\tmod10 == 3 && int != 13 ? \"rd\" : \"th\"\n\t);\n}\n*/\n\nconst subs = {\n\t// 2019\n\tYYYY:\td => d.getFullYear(),\n\t// 19\n\tYY:\t\td => (d.getFullYear()+'').slice(2),\n\t// July\n\tMMMM:\t(d, names) => names.MMMM[d.getMonth()],\n\t// Jul\n\tMMM:\t(d, names) => names.MMM[d.getMonth()],\n\t// 07\n\tMM:\t\td => zeroPad2(d.getMonth()+1),\n\t// 7\n\tM:\t\td => d.getMonth()+1,\n\t// 09\n\tDD:\t\td => zeroPad2(d.getDate()),\n\t// 9\n\tD:\t\td => d.getDate(),\n\t// Monday\n\tWWWW:\t(d, names) => names.WWWW[d.getDay()],\n\t// Mon\n\tWWW:\t(d, names) => names.WWW[d.getDay()],\n\t// 03\n\tHH:\t\td => zeroPad2(d.getHours()),\n\t// 3\n\tH:\t\td => d.getHours(),\n\t// 9 (12hr, unpadded)\n\th:\t\td => {let h = d.getHours(); return h == 0 ? 12 : h > 12 ? h - 12 : h;},\n\t// AM\n\tAA:\t\td => d.getHours() >= 12 ? 'PM' : 'AM',\n\t// am\n\taa:\t\td => d.getHours() >= 12 ? 'pm' : 'am',\n\t// a\n\ta:\t\td => d.getHours() >= 12 ? 'p' : 'a',\n\t// 09\n\tmm:\t\td => zeroPad2(d.getMinutes()),\n\t// 9\n\tm:\t\td => d.getMinutes(),\n\t// 09\n\tss:\t\td => zeroPad2(d.getSeconds()),\n\t// 9\n\ts:\t\td => d.getSeconds(),\n\t// 374\n\tfff:\td => zeroPad3(d.getMilliseconds()),\n};\n\nfunction fmtDate(tpl, names) {\n\tnames = names || engNames;\n\tlet parts = [];\n\n\tlet R = /\\{([a-z]+)\\}|[^{]+/gi, m;\n\n\twhile (m = R.exec(tpl))\n\t\tparts.push(m[0][0] == '{' ? subs[m[1]] : m[0]);\n\n\treturn d => {\n\t\tlet out = '';\n\n\t\tfor (let i = 0; i < parts.length; i++)\n\t\t\tout += typeof parts[i] == \"string\" ? parts[i] : parts[i](d, names);\n\n\t\treturn out;\n\t}\n}\n\nconst localTz = new Intl.DateTimeFormat().resolvedOptions().timeZone;\n\n// https://stackoverflow.com/questions/15141762/how-to-initialize-a-javascript-date-to-a-particular-time-zone/53652131#53652131\nfunction tzDate(date, tz) {\n\tlet date2;\n\n\t// perf optimization\n\tif (tz == 'UTC' || tz == 'Etc/UTC')\n\t\tdate2 = new Date(+date + date.getTimezoneOffset() * 6e4);\n\telse if (tz == localTz)\n\t\tdate2 = date;\n\telse {\n\t\tdate2 = new Date(date.toLocaleString('en-US', {timeZone: tz}));\n\t\tdate2.setMilliseconds(date.getMilliseconds());\n\t}\n\n\treturn date2;\n}\n\n//export const series = [];\n\n// default formatters:\n\nconst onlyWhole = v => v % 1 == 0;\n\nconst allMults = [1,2,2.5,5];\n\n// ...0.01, 0.02, 0.025, 0.05, 0.1, 0.2, 0.25, 0.5\nconst decIncrs = genIncrs(10, -32, 0, allMults);\n\n// 1, 2, 2.5, 5, 10, 20, 25, 50...\nconst oneIncrs = genIncrs(10, 0, 32, allMults);\n\n// 1, 2, 5, 10, 20, 25, 50...\nconst wholeIncrs = oneIncrs.filter(onlyWhole);\n\nconst numIncrs = decIncrs.concat(oneIncrs);\n\nconst NL = \"\\n\";\n\nconst yyyy = \"{YYYY}\";\nconst NLyyyy = NL + yyyy;\nconst md = \"{M}/{D}\";\nconst NLmd = NL + md;\nconst NLmdyy = NLmd + \"/{YY}\";\n\nconst aa = \"{aa}\";\nconst hmm = \"{h}:{mm}\";\nconst hmmaa = hmm + aa;\nconst NLhmmaa = NL + hmmaa;\nconst ss = \":{ss}\";\n\nconst _ = null;\n\nfunction genTimeStuffs(ms) {\n\tlet\ts = ms * 1e3,\n\t\tm = s * 60,\n\t\th = m * 60,\n\t\td = h * 24,\n\t\tmo = d * 30,\n\t\ty = d * 365;\n\n\t// min of 1e-3 prevents setting a temporal x ticks too small since Date objects cannot advance ticks smaller than 1ms\n\tlet subSecIncrs = ms == 1 ? genIncrs(10, 0, 3, allMults).filter(onlyWhole) : genIncrs(10, -3, 0, allMults);\n\n\tlet timeIncrs = subSecIncrs.concat([\n\t\t// minute divisors (# of secs)\n\t\ts,\n\t\ts * 5,\n\t\ts * 10,\n\t\ts * 15,\n\t\ts * 30,\n\t\t// hour divisors (# of mins)\n\t\tm,\n\t\tm * 5,\n\t\tm * 10,\n\t\tm * 15,\n\t\tm * 30,\n\t\t// day divisors (# of hrs)\n\t\th,\n\t\th * 2,\n\t\th * 3,\n\t\th * 4,\n\t\th * 6,\n\t\th * 8,\n\t\th * 12,\n\t\t// month divisors TODO: need more?\n\t\td,\n\t\td * 2,\n\t\td * 3,\n\t\td * 4,\n\t\td * 5,\n\t\td * 6,\n\t\td * 7,\n\t\td * 8,\n\t\td * 9,\n\t\td * 10,\n\t\td * 15,\n\t\t// year divisors (# months, approx)\n\t\tmo,\n\t\tmo * 2,\n\t\tmo * 3,\n\t\tmo * 4,\n\t\tmo * 6,\n\t\t// century divisors\n\t\ty,\n\t\ty * 2,\n\t\ty * 5,\n\t\ty * 10,\n\t\ty * 25,\n\t\ty * 50,\n\t\ty * 100,\n\t]);\n\n\t// [0]: minimum num secs in the tick incr\n\t// [1]: default tick format\n\t// [2-7]: rollover tick formats\n\t// [8]: mode: 0: replace [1] -> [2-7], 1: concat [1] + [2-7]\n\tconst _timeAxisStamps = [\n\t// tick incr default year month day hour min sec mode\n\t\t[y, yyyy, _, _, _, _, _, _, 1],\n\t\t[d * 28, \"{MMM}\", NLyyyy, _, _, _, _, _, 1],\n\t\t[d, md, NLyyyy, _, _, _, _, _, 1],\n\t\t[h, \"{h}\" + aa, NLmdyy, _, NLmd, _, _, _, 1],\n\t\t[m, hmmaa, NLmdyy, _, NLmd, _, _, _, 1],\n\t\t[s, ss, NLmdyy + \" \" + hmmaa, _, NLmd + \" \" + hmmaa, _, NLhmmaa, _, 1],\n\t\t[ms, ss + \".{fff}\", NLmdyy + \" \" + hmmaa, _, NLmd + \" \" + hmmaa, _, NLhmmaa, _, 1],\n\t];\n\n\t// the ensures that axis ticks, values & grid are aligned to logical temporal breakpoints and not an arbitrary timestamp\n\t// https://www.timeanddate.com/time/dst/\n\t// https://www.timeanddate.com/time/dst/2019.html\n\t// https://www.epochconverter.com/timezones\n\tfunction timeAxisSplits(tzDate) {\n\t\treturn (self, axisIdx, scaleMin, scaleMax, foundIncr, foundSpace) => {\n\t\t\tlet splits = [];\n\t\t\tlet isYr = foundIncr >= y;\n\t\t\tlet isMo = foundIncr >= mo && foundIncr < y;\n\n\t\t\t// get the timezone-adjusted date\n\t\t\tlet minDate = tzDate(scaleMin);\n\t\t\tlet minDateTs = roundDec(minDate * ms, 3);\n\n\t\t\t// get ts of 12am (this lands us at or before the original scaleMin)\n\t\t\tlet minMin = mkDate(minDate.getFullYear(), isYr ? 0 : minDate.getMonth(), isMo || isYr ? 1 : minDate.getDate());\n\t\t\tlet minMinTs = roundDec(minMin * ms, 3);\n\n\t\t\tif (isMo || isYr) {\n\t\t\t\tlet moIncr = isMo ? foundIncr / mo : 0;\n\t\t\t\tlet yrIncr = isYr ? foundIncr / y : 0;\n\t\t\t//\tlet tzOffset = scaleMin - minDateTs;\t\t// needed?\n\t\t\t\tlet split = minDateTs == minMinTs ? minDateTs : roundDec(mkDate(minMin.getFullYear() + yrIncr, minMin.getMonth() + moIncr, 1) * ms, 3);\n\t\t\t\tlet splitDate = new Date(round(split / ms));\n\t\t\t\tlet baseYear = splitDate.getFullYear();\n\t\t\t\tlet baseMonth = splitDate.getMonth();\n\n\t\t\t\tfor (let i = 0; split <= scaleMax; i++) {\n\t\t\t\t\tlet next = mkDate(baseYear + yrIncr * i, baseMonth + moIncr * i, 1);\n\t\t\t\t\tlet offs = next - tzDate(roundDec(next * ms, 3));\n\n\t\t\t\t\tsplit = roundDec((+next + offs) * ms, 3);\n\n\t\t\t\t\tif (split <= scaleMax)\n\t\t\t\t\t\tsplits.push(split);\n\t\t\t\t}\n\t\t\t}\n\t\t\telse {\n\t\t\t\tlet incr0 = foundIncr >= d ? d : foundIncr;\n\t\t\t\tlet tzOffset = floor(scaleMin) - floor(minDateTs);\n\t\t\t\tlet split = minMinTs + tzOffset + incrRoundUp(minDateTs - minMinTs, incr0);\n\t\t\t\tsplits.push(split);\n\n\t\t\t\tlet date0 = tzDate(split);\n\n\t\t\t\tlet prevHour = date0.getHours() + (date0.getMinutes() / m) + (date0.getSeconds() / h);\n\t\t\t\tlet incrHours = foundIncr / h;\n\n\t\t\t\tlet minSpace = self.axes[axisIdx]._space;\n\t\t\t\tlet pctSpace = foundSpace / minSpace;\n\n\t\t\t\twhile (1) {\n\t\t\t\t\tsplit = roundDec(split + foundIncr, ms == 1 ? 0 : 3);\n\n\t\t\t\t\tif (split > scaleMax)\n\t\t\t\t\t\tbreak;\n\n\t\t\t\t\tif (incrHours > 1) {\n\t\t\t\t\t\tlet expectedHour = floor(roundDec(prevHour + incrHours, 6)) % 24;\n\t\t\t\t\t\tlet splitDate = tzDate(split);\n\t\t\t\t\t\tlet actualHour = splitDate.getHours();\n\n\t\t\t\t\t\tlet dstShift = actualHour - expectedHour;\n\n\t\t\t\t\t\tif (dstShift > 1)\n\t\t\t\t\t\t\tdstShift = -1;\n\n\t\t\t\t\t\tsplit -= dstShift * h;\n\n\t\t\t\t\t\tprevHour = (prevHour + incrHours) % 24;\n\n\t\t\t\t\t\t// add a tick only if it's further than 70% of the min allowed label spacing\n\t\t\t\t\t\tlet prevSplit = splits[splits.length - 1];\n\t\t\t\t\t\tlet pctIncr = roundDec((split - prevSplit) / foundIncr, 3);\n\n\t\t\t\t\t\tif (pctIncr * pctSpace >= .7)\n\t\t\t\t\t\t\tsplits.push(split);\n\t\t\t\t\t}\n\t\t\t\t\telse\n\t\t\t\t\t\tsplits.push(split);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn splits;\n\t\t}\n\t}\n\n\treturn [\n\t\ttimeIncrs,\n\t\t_timeAxisStamps,\n\t\ttimeAxisSplits,\n\t];\n}\n\nconst [ timeIncrsMs, _timeAxisStampsMs, timeAxisSplitsMs ] = genTimeStuffs(1);\nconst [ timeIncrsS, _timeAxisStampsS, timeAxisSplitsS ] = genTimeStuffs(1e-3);\n\n// base 2\ngenIncrs(2, -53, 53, [1]);\n\n/*\nconsole.log({\n\tdecIncrs,\n\toneIncrs,\n\twholeIncrs,\n\tnumIncrs,\n\ttimeIncrs,\n\tfixedDec,\n});\n*/\n\nfunction timeAxisStamps(stampCfg, fmtDate) {\n\treturn stampCfg.map(s => s.map((v, i) =>\n\t\ti == 0 || i == 8 || v == null ? v : fmtDate(i == 1 || s[8] == 0 ? v : s[1] + v)\n\t));\n}\n\n// TODO: will need to accept spaces[] and pull incr into the loop when grid will be non-uniform, eg for log scales.\n// currently we ignore this for months since they're *nearly* uniform and the added complexity is not worth it\nfunction timeAxisVals(tzDate, stamps) {\n\treturn (self, splits, axisIdx, foundSpace, foundIncr) => {\n\t\tlet s = stamps.find(s => foundIncr >= s[0]) || stamps[stamps.length - 1];\n\n\t\t// these track boundaries when a full label is needed again\n\t\tlet prevYear;\n\t\tlet prevMnth;\n\t\tlet prevDate;\n\t\tlet prevHour;\n\t\tlet prevMins;\n\t\tlet prevSecs;\n\n\t\treturn splits.map(split => {\n\t\t\tlet date = tzDate(split);\n\n\t\t\tlet newYear = date.getFullYear();\n\t\t\tlet newMnth = date.getMonth();\n\t\t\tlet newDate = date.getDate();\n\t\t\tlet newHour = date.getHours();\n\t\t\tlet newMins = date.getMinutes();\n\t\t\tlet newSecs = date.getSeconds();\n\n\t\t\tlet stamp = (\n\t\t\t\tnewYear != prevYear && s[2] ||\n\t\t\t\tnewMnth != prevMnth && s[3] ||\n\t\t\t\tnewDate != prevDate && s[4] ||\n\t\t\t\tnewHour != prevHour && s[5] ||\n\t\t\t\tnewMins != prevMins && s[6] ||\n\t\t\t\tnewSecs != prevSecs && s[7] ||\n\t\t\t\t s[1]\n\t\t\t);\n\n\t\t\tprevYear = newYear;\n\t\t\tprevMnth = newMnth;\n\t\t\tprevDate = newDate;\n\t\t\tprevHour = newHour;\n\t\t\tprevMins = newMins;\n\t\t\tprevSecs = newSecs;\n\n\t\t\treturn stamp(date);\n\t\t});\n\t}\n}\n\n// for when axis.values is defined as a static fmtDate template string\nfunction timeAxisVal(tzDate, dateTpl) {\n\tlet stamp = fmtDate(dateTpl);\n\treturn (self, splits, axisIdx, foundSpace, foundIncr) => splits.map(split => stamp(tzDate(split)));\n}\n\nfunction mkDate(y, m, d) {\n\treturn new Date(y, m, d);\n}\n\nfunction timeSeriesStamp(stampCfg, fmtDate) {\n\treturn fmtDate(stampCfg);\n}\nconst _timeSeriesStamp = '{YYYY}-{MM}-{DD} {h}:{mm}{aa}';\n\nfunction timeSeriesVal(tzDate, stamp) {\n\treturn (self, val, seriesIdx, dataIdx) => dataIdx == null ? LEGEND_DISP : stamp(tzDate(val));\n}\n\nfunction legendStroke(self, seriesIdx) {\n\tlet s = self.series[seriesIdx];\n\treturn s.width ? s.stroke(self, seriesIdx) : s.points.width ? s.points.stroke(self, seriesIdx) : null;\n}\n\nfunction legendFill(self, seriesIdx) {\n\treturn self.series[seriesIdx].fill(self, seriesIdx);\n}\n\nconst legendOpts = {\n\tshow: true,\n\tlive: true,\n\tisolate: false,\n\tmount: noop,\n\tmarkers: {\n\t\tshow: true,\n\t\twidth: 2,\n\t\tstroke: legendStroke,\n\t\tfill: legendFill,\n\t\tdash: \"solid\",\n\t},\n\tidx: null,\n\tidxs: null,\n\tvalues: [],\n};\n\nfunction cursorPointShow(self, si) {\n\tlet o = self.cursor.points;\n\n\tlet pt = placeDiv();\n\n\tlet size = o.size(self, si);\n\tsetStylePx(pt, WIDTH, size);\n\tsetStylePx(pt, HEIGHT, size);\n\n\tlet mar = size / -2;\n\tsetStylePx(pt, \"marginLeft\", mar);\n\tsetStylePx(pt, \"marginTop\", mar);\n\n\tlet width = o.width(self, si, size);\n\twidth && setStylePx(pt, \"borderWidth\", width);\n\n\treturn pt;\n}\n\nfunction cursorPointFill(self, si) {\n\tlet sp = self.series[si].points;\n\treturn sp._fill || sp._stroke;\n}\n\nfunction cursorPointStroke(self, si) {\n\tlet sp = self.series[si].points;\n\treturn sp._stroke || sp._fill;\n}\n\nfunction cursorPointSize(self, si) {\n\tlet sp = self.series[si].points;\n\treturn sp.size;\n}\n\nconst moveTuple = [0,0];\n\nfunction cursorMove(self, mouseLeft1, mouseTop1) {\n\tmoveTuple[0] = mouseLeft1;\n\tmoveTuple[1] = mouseTop1;\n\treturn moveTuple;\n}\n\nfunction filtBtn0(self, targ, handle, onlyTarg = true) {\n\treturn e => {\n\t\te.button == 0 && (!onlyTarg || e.target == targ) && handle(e);\n\t};\n}\n\nfunction filtTarg(self, targ, handle, onlyTarg = true) {\n\treturn e => {\n\t\t(!onlyTarg || e.target == targ) && handle(e);\n\t};\n}\n\nconst cursorOpts = {\n\tshow: true,\n\tx: true,\n\ty: true,\n\tlock: false,\n\tmove: cursorMove,\n\tpoints: {\n\t\tone: false,\n\t\tshow: cursorPointShow,\n\t\tsize: cursorPointSize,\n\t\twidth: 0,\n\t\tstroke: cursorPointStroke,\n\t\tfill: cursorPointFill,\n\t},\n\n\tbind: {\n\t\tmousedown: filtBtn0,\n\t\tmouseup: filtBtn0,\n\t\tclick: filtBtn0, // legend clicks, not .u-over clicks\n\t\tdblclick: filtBtn0,\n\n\t\tmousemove: filtTarg,\n\t\tmouseleave: filtTarg,\n\t\tmouseenter: filtTarg,\n\t},\n\n\tdrag: {\n\t\tsetScale: true,\n\t\tx: true,\n\t\ty: false,\n\t\tdist: 0,\n\t\tuni: null,\n\t\tclick: (self, e) => {\n\t\t//\te.preventDefault();\n\t\t\te.stopPropagation();\n\t\t\te.stopImmediatePropagation();\n\t\t},\n\t\t_x: false,\n\t\t_y: false,\n\t},\n\n\tfocus: {\n\t\tdist: (self, seriesIdx, dataIdx, valPos, curPos) => valPos - curPos,\n\t\tprox: -1,\n\t\tbias: 0,\n\t},\n\n\thover: {\n\t\tskip: [void 0],\n\t\tprox: null,\n\t\tbias: 0,\n\t},\n\n\tleft: -10,\n\ttop: -10,\n\tidx: null,\n\tdataIdx: null,\n\tidxs: null,\n\n\tevent: null,\n};\n\nconst axisLines = {\n\tshow: true,\n\tstroke: \"rgba(0,0,0,0.07)\",\n\twidth: 2,\n//\tdash: [],\n};\n\nconst grid = assign({}, axisLines, {\n\tfilter: retArg1,\n});\n\nconst ticks = assign({}, grid, {\n\tsize: 10,\n});\n\nconst border = assign({}, axisLines, {\n\tshow: false,\n});\n\nconst font = '12px system-ui, -apple-system, \"Segoe UI\", Roboto, \"Helvetica Neue\", Arial, \"Noto Sans\", sans-serif, \"Apple Color Emoji\", \"Segoe UI Emoji\", \"Segoe UI Symbol\", \"Noto Color Emoji\"';\nconst labelFont = \"bold \" + font;\nconst lineGap = 1.5;\t// font-size multiplier\n\nconst xAxisOpts = {\n\tshow: true,\n\tscale: \"x\",\n\tstroke: hexBlack,\n\tspace: 50,\n\tgap: 5,\n\talignTo: 1,\n\tsize: 50,\n\tlabelGap: 0,\n\tlabelSize: 30,\n\tlabelFont,\n\tside: 2,\n//\tclass: \"x-vals\",\n//\tincrs: timeIncrs,\n//\tvalues: timeVals,\n//\tfilter: retArg1,\n\tgrid,\n\tticks,\n\tborder,\n\tfont,\n\tlineGap,\n\trotate: 0,\n};\n\nconst numSeriesLabel = \"Value\";\nconst timeSeriesLabel = \"Time\";\n\nconst xSeriesOpts = {\n\tshow: true,\n\tscale: \"x\",\n\tauto: false,\n\tsorted: 1,\n//\tlabel: \"Time\",\n//\tvalue: v => stamp(new Date(v * 1e3)),\n\n\t// internal caches\n\tmin: inf,\n\tmax: -inf,\n\tidxs: [],\n};\n\nfunction numAxisVals(self, splits, axisIdx, foundSpace, foundIncr) {\n\treturn splits.map(v => v == null ? \"\" : fmtNum(v));\n}\n\nfunction numAxisSplits(self, axisIdx, scaleMin, scaleMax, foundIncr, foundSpace, forceMin) {\n\tlet splits = [];\n\n\tlet numDec = fixedDec.get(foundIncr) || 0;\n\n\tscaleMin = forceMin ? scaleMin : roundDec(incrRoundUp(scaleMin, foundIncr), numDec);\n\n\tfor (let val = scaleMin; val <= scaleMax; val = roundDec(val + foundIncr, numDec))\n\t\tsplits.push(Object.is(val, -0) ? 0 : val);\t\t// coalesces -0\n\n\treturn splits;\n}\n\n// this doesnt work for sin, which needs to come off from 0 independently in pos and neg dirs\nfunction logAxisSplits(self, axisIdx, scaleMin, scaleMax, foundIncr, foundSpace, forceMin) {\n\tconst splits = [];\n\n\tconst logBase = self.scales[self.axes[axisIdx].scale].log;\n\n\tconst logFn = logBase == 10 ? log10 : log2;\n\n\tconst exp = floor(logFn(scaleMin));\n\n\tfoundIncr = pow(logBase, exp);\n\n\t// boo: 10 ** -24 === 1.0000000000000001e-24\n\t// this grabs the proper 1e-24 one\n\tif (logBase == 10)\n\t\tfoundIncr = numIncrs[closestIdx(foundIncr, numIncrs)];\n\n\tlet split = scaleMin;\n\tlet nextMagIncr = foundIncr * logBase;\n\n\tif (logBase == 10)\n\t\tnextMagIncr = numIncrs[closestIdx(nextMagIncr, numIncrs)];\n\n\tdo {\n\t\tsplits.push(split);\n\t\tsplit = split + foundIncr;\n\n\t\tif (logBase == 10 && !fixedDec.has(split))\n\t\t\tsplit = roundDec(split, fixedDec.get(foundIncr));\n\n\t\tif (split >= nextMagIncr) {\n\t\t\tfoundIncr = split;\n\t\t\tnextMagIncr = foundIncr * logBase;\n\n\t\t\tif (logBase == 10)\n\t\t\t\tnextMagIncr = numIncrs[closestIdx(nextMagIncr, numIncrs)];\n\t\t}\n\t} while (split <= scaleMax);\n\n\treturn splits;\n}\n\nfunction asinhAxisSplits(self, axisIdx, scaleMin, scaleMax, foundIncr, foundSpace, forceMin) {\n\tlet sc = self.scales[self.axes[axisIdx].scale];\n\n\tlet linthresh = sc.asinh;\n\n\tlet posSplits = scaleMax > linthresh ? logAxisSplits(self, axisIdx, max(linthresh, scaleMin), scaleMax, foundIncr) : [linthresh];\n\tlet zero = scaleMax >= 0 && scaleMin <= 0 ? [0] : [];\n\tlet negSplits = scaleMin < -linthresh ? logAxisSplits(self, axisIdx, max(linthresh, -scaleMax), -scaleMin, foundIncr): [linthresh];\n\n\treturn negSplits.reverse().map(v => -v).concat(zero, posSplits);\n}\n\nconst RE_ALL = /./;\nconst RE_12357 = /[12357]/;\nconst RE_125 = /[125]/;\nconst RE_1 = /1/;\n\nconst _filt = (splits, distr, re, keepMod) => splits.map((v, i) => ((distr == 4 && v == 0) || i % keepMod == 0 && re.test(v.toExponential()[v < 0 ? 1 : 0])) ? v : null);\n\nfunction log10AxisValsFilt(self, splits, axisIdx, foundSpace, foundIncr) {\n\tlet axis = self.axes[axisIdx];\n\tlet scaleKey = axis.scale;\n\tlet sc = self.scales[scaleKey];\n\n//\tif (sc.distr == 3 && sc.log == 2)\n//\t\treturn splits;\n\n\tlet valToPos = self.valToPos;\n\n\tlet minSpace = axis._space;\n\n\tlet _10 = valToPos(10, scaleKey);\n\n\tlet re = (\n\t\tvalToPos(9, scaleKey) - _10 >= minSpace ? RE_ALL :\n\t\tvalToPos(7, scaleKey) - _10 >= minSpace ? RE_12357 :\n\t\tvalToPos(5, scaleKey) - _10 >= minSpace ? RE_125 :\n\t\tRE_1\n\t);\n\n\tif (re == RE_1) {\n\t\tlet magSpace = abs(valToPos(1, scaleKey) - _10);\n\n\t\tif (magSpace < minSpace)\n\t\t\treturn _filt(splits.slice().reverse(), sc.distr, re, ceil(minSpace / magSpace)).reverse(); // max->min skip\n\t}\n\n\treturn _filt(splits, sc.distr, re, 1);\n}\n\nfunction log2AxisValsFilt(self, splits, axisIdx, foundSpace, foundIncr) {\n\tlet axis = self.axes[axisIdx];\n\tlet scaleKey = axis.scale;\n\tlet minSpace = axis._space;\n\tlet valToPos = self.valToPos;\n\n\tlet magSpace = abs(valToPos(1, scaleKey) - valToPos(2, scaleKey));\n\n\tif (magSpace < minSpace)\n\t\treturn _filt(splits.slice().reverse(), 3, RE_ALL, ceil(minSpace / magSpace)).reverse(); // max->min skip\n\n\treturn splits;\n}\n\nfunction numSeriesVal(self, val, seriesIdx, dataIdx) {\n\treturn dataIdx == null ? LEGEND_DISP : val == null ? \"\" : fmtNum(val);\n}\n\nconst yAxisOpts = {\n\tshow: true,\n\tscale: \"y\",\n\tstroke: hexBlack,\n\tspace: 30,\n\tgap: 5,\n\talignTo: 1,\n\tsize: 50,\n\tlabelGap: 0,\n\tlabelSize: 30,\n\tlabelFont,\n\tside: 3,\n//\tclass: \"y-vals\",\n//\tincrs: numIncrs,\n//\tvalues: (vals, space) => vals,\n//\tfilter: retArg1,\n\tgrid,\n\tticks,\n\tborder,\n\tfont,\n\tlineGap,\n\trotate: 0,\n};\n\n// takes stroke width\nfunction ptDia(width, mult) {\n\tlet dia = 3 + (width || 1) * 2;\n\treturn roundDec(dia * mult, 3);\n}\n\nfunction seriesPointsShow(self, si) {\n\tlet { scale, idxs } = self.series[0];\n\tlet xData = self._data[0];\n\tlet p0 = self.valToPos(xData[idxs[0]], scale, true);\n\tlet p1 = self.valToPos(xData[idxs[1]], scale, true);\n\tlet dim = abs(p1 - p0);\n\n\tlet s = self.series[si];\n//\tconst dia = ptDia(s.width, pxRatio);\n\tlet maxPts = dim / (s.points.space * pxRatio);\n\treturn idxs[1] - idxs[0] <= maxPts;\n}\n\nconst facet = {\n\tscale: null,\n\tauto: true,\n\tsorted: 0,\n\n\t// internal caches\n\tmin: inf,\n\tmax: -inf,\n};\n\nconst gaps = (self, seriesIdx, idx0, idx1, nullGaps) => nullGaps;\n\nconst xySeriesOpts = {\n\tshow: true,\n\tauto: true,\n\tsorted: 0,\n\tgaps,\n\talpha: 1,\n\tfacets: [\n\t\tassign({}, facet, {scale: 'x'}),\n\t\tassign({}, facet, {scale: 'y'}),\n\t],\n};\n\nconst ySeriesOpts = {\n\tscale: \"y\",\n\tauto: true,\n\tsorted: 0,\n\tshow: true,\n\tspanGaps: false,\n\tgaps,\n\talpha: 1,\n\tpoints: {\n\t\tshow: seriesPointsShow,\n\t\tfilter: null,\n\t// paths:\n\t//\tstroke: \"#000\",\n\t//\tfill: \"#fff\",\n\t//\twidth: 1,\n\t//\tsize: 10,\n\t},\n//\tlabel: \"Value\",\n//\tvalue: v => v,\n\tvalues: null,\n\n\t// internal caches\n\tmin: inf,\n\tmax: -inf,\n\tidxs: [],\n\n\tpath: null,\n\tclip: null,\n};\n\nfunction clampScale(self, val, scaleMin, scaleMax, scaleKey) {\n/*\n\tif (val < 0) {\n\t\tlet cssHgt = self.bbox.height / pxRatio;\n\t\tlet absPos = self.valToPos(abs(val), scaleKey);\n\t\tlet fromBtm = cssHgt - absPos;\n\t\treturn self.posToVal(cssHgt + fromBtm, scaleKey);\n\t}\n*/\n\treturn scaleMin / 10;\n}\n\nconst xScaleOpts = {\n\ttime: FEAT_TIME,\n\tauto: true,\n\tdistr: 1,\n\tlog: 10,\n\tasinh: 1,\n\tmin: null,\n\tmax: null,\n\tdir: 1,\n\tori: 0,\n};\n\nconst yScaleOpts = assign({}, xScaleOpts, {\n\ttime: false,\n\tori: 1,\n});\n\nconst syncs = {};\n\nfunction _sync(key, opts) {\n\tlet s = syncs[key];\n\n\tif (!s) {\n\t\ts = {\n\t\t\tkey,\n\t\t\tplots: [],\n\t\t\tsub(plot) {\n\t\t\t\ts.plots.push(plot);\n\t\t\t},\n\t\t\tunsub(plot) {\n\t\t\t\ts.plots = s.plots.filter(c => c != plot);\n\t\t\t},\n\t\t\tpub(type, self, x, y, w, h, i) {\n\t\t\t\tfor (let j = 0; j < s.plots.length; j++)\n\t\t\t\t\ts.plots[j] != self && s.plots[j].pub(type, self, x, y, w, h, i);\n\t\t\t},\n\t\t};\n\n\t\tif (key != null)\n\t\t\tsyncs[key] = s;\n\t}\n\n\treturn s;\n}\n\nconst BAND_CLIP_FILL = 1 << 0;\nconst BAND_CLIP_STROKE = 1 << 1;\n\nfunction orient(u, seriesIdx, cb) {\n\tconst mode = u.mode;\n\tconst series = u.series[seriesIdx];\n\tconst data = mode == 2 ? u._data[seriesIdx] : u._data;\n\tconst scales = u.scales;\n\tconst bbox = u.bbox;\n\n\tlet dx = data[0],\n\t\tdy = mode == 2 ? data[1] : data[seriesIdx],\n\t\tsx = mode == 2 ? scales[series.facets[0].scale] : scales[u.series[0].scale],\n\t\tsy = mode == 2 ? scales[series.facets[1].scale] : scales[series.scale],\n\t\tl = bbox.left,\n\t\tt = bbox.top,\n\t\tw = bbox.width,\n\t\th = bbox.height,\n\t\tH = u.valToPosH,\n\t\tV = u.valToPosV;\n\n\treturn (sx.ori == 0\n\t\t? cb(\n\t\t\tseries,\n\t\t\tdx,\n\t\t\tdy,\n\t\t\tsx,\n\t\t\tsy,\n\t\t\tH,\n\t\t\tV,\n\t\t\tl,\n\t\t\tt,\n\t\t\tw,\n\t\t\th,\n\t\t\tmoveToH,\n\t\t\tlineToH,\n\t\t\trectH,\n\t\t\tarcH,\n\t\t\tbezierCurveToH,\n\t\t)\n\t\t: cb(\n\t\t\tseries,\n\t\t\tdx,\n\t\t\tdy,\n\t\t\tsx,\n\t\t\tsy,\n\t\t\tV,\n\t\t\tH,\n\t\t\tt,\n\t\t\tl,\n\t\t\th,\n\t\t\tw,\n\t\t\tmoveToV,\n\t\t\tlineToV,\n\t\t\trectV,\n\t\t\tarcV,\n\t\t\tbezierCurveToV,\n\t\t)\n\t);\n}\n\nfunction bandFillClipDirs(self, seriesIdx) {\n\tlet fillDir = 0;\n\n\t// 2 bits, -1 | 1\n\tlet clipDirs = 0;\n\n\tlet bands = ifNull(self.bands, EMPTY_ARR);\n\n\tfor (let i = 0; i < bands.length; i++) {\n\t\tlet b = bands[i];\n\n\t\t// is a \"from\" band edge\n\t\tif (b.series[0] == seriesIdx)\n\t\t\tfillDir = b.dir;\n\t\t// is a \"to\" band edge\n\t\telse if (b.series[1] == seriesIdx) {\n\t\t\tif (b.dir == 1)\n\t\t\t\tclipDirs |= 1;\n\t\t\telse\n\t\t\t\tclipDirs |= 2;\n\t\t}\n\t}\n\n\treturn [\n\t\tfillDir,\n\t\t(\n\t\t\tclipDirs == 1 ? -1 : // neg only\n\t\t\tclipDirs == 2 ? 1 : // pos only\n\t\t\tclipDirs == 3 ? 2 : // both\n\t\t\t 0 // neither\n\t\t)\n\t];\n}\n\nfunction seriesFillTo(self, seriesIdx, dataMin, dataMax, bandFillDir) {\n\tlet mode = self.mode;\n\tlet series = self.series[seriesIdx];\n\tlet scaleKey = mode == 2 ? series.facets[1].scale : series.scale;\n\tlet scale = self.scales[scaleKey];\n\n\treturn (\n\t\tbandFillDir == -1 ? scale.min :\n\t\tbandFillDir == 1 ? scale.max :\n\t\tscale.distr == 3 ? (\n\t\t\tscale.dir == 1 ? scale.min :\n\t\t\tscale.max\n\t\t) : 0\n\t);\n}\n\n// creates inverted band clip path (from stroke path -> yMax || yMin)\n// clipDir is always inverse of fillDir\n// default clip dir is upwards (1), since default band fill is downwards/fillBelowTo (-1) (highIdx -> lowIdx)\nfunction clipBandLine(self, seriesIdx, idx0, idx1, strokePath, clipDir) {\n\treturn orient(self, seriesIdx, (series, dataX, dataY, scaleX, scaleY, valToPosX, valToPosY, xOff, yOff, xDim, yDim) => {\n\t\tlet pxRound = series.pxRound;\n\n\t\tconst dir = scaleX.dir * (scaleX.ori == 0 ? 1 : -1);\n\t\tconst lineTo = scaleX.ori == 0 ? lineToH : lineToV;\n\n\t\tlet frIdx, toIdx;\n\n\t\tif (dir == 1) {\n\t\t\tfrIdx = idx0;\n\t\t\ttoIdx = idx1;\n\t\t}\n\t\telse {\n\t\t\tfrIdx = idx1;\n\t\t\ttoIdx = idx0;\n\t\t}\n\n\t\t// path start\n\t\tlet x0 = pxRound(valToPosX(dataX[frIdx], scaleX, xDim, xOff));\n\t\tlet y0 = pxRound(valToPosY(dataY[frIdx], scaleY, yDim, yOff));\n\t\t// path end x\n\t\tlet x1 = pxRound(valToPosX(dataX[toIdx], scaleX, xDim, xOff));\n\t\t// upper or lower y limit\n\t\tlet yLimit = pxRound(valToPosY(clipDir == 1 ? scaleY.max : scaleY.min, scaleY, yDim, yOff));\n\n\t\tlet clip = new Path2D(strokePath);\n\n\t\tlineTo(clip, x1, yLimit);\n\t\tlineTo(clip, x0, yLimit);\n\t\tlineTo(clip, x0, y0);\n\n\t\treturn clip;\n\t});\n}\n\nfunction clipGaps(gaps, ori, plotLft, plotTop, plotWid, plotHgt) {\n\tlet clip = null;\n\n\t// create clip path (invert gaps and non-gaps)\n\tif (gaps.length > 0) {\n\t\tclip = new Path2D();\n\n\t\tconst rect = ori == 0 ? rectH : rectV;\n\n\t\tlet prevGapEnd = plotLft;\n\n\t\tfor (let i = 0; i < gaps.length; i++) {\n\t\t\tlet g = gaps[i];\n\n\t\t\tif (g[1] > g[0]) {\n\t\t\t\tlet w = g[0] - prevGapEnd;\n\n\t\t\t\tw > 0 && rect(clip, prevGapEnd, plotTop, w, plotTop + plotHgt);\n\n\t\t\t\tprevGapEnd = g[1];\n\t\t\t}\n\t\t}\n\n\t\tlet w = plotLft + plotWid - prevGapEnd;\n\n\t\t// hack to ensure we expand the clip enough to avoid cutting off strokes at edges\n\t\tlet maxStrokeWidth = 10;\n\n\t\tw > 0 && rect(clip, prevGapEnd, plotTop - maxStrokeWidth / 2, w, plotTop + plotHgt + maxStrokeWidth);\n\t}\n\n\treturn clip;\n}\n\nfunction addGap(gaps, fromX, toX) {\n\tlet prevGap = gaps[gaps.length - 1];\n\n\tif (prevGap && prevGap[0] == fromX)\t\t\t// TODO: gaps must be encoded at stroke widths?\n\t\tprevGap[1] = toX;\n\telse\n\t\tgaps.push([fromX, toX]);\n}\n\nfunction findGaps(xs, ys, idx0, idx1, dir, pixelForX, align) {\n\tlet gaps = [];\n\tlet len = xs.length;\n\n\tfor (let i = dir == 1 ? idx0 : idx1; i >= idx0 && i <= idx1; i += dir) {\n\t\tlet yVal = ys[i];\n\n\t\tif (yVal === null) {\n\t\t\tlet fr = i, to = i;\n\n\t\t\tif (dir == 1) {\n\t\t\t\twhile (++i <= idx1 && ys[i] === null)\n\t\t\t\t\tto = i;\n\t\t\t}\n\t\t\telse {\n\t\t\t\twhile (--i >= idx0 && ys[i] === null)\n\t\t\t\t\tto = i;\n\t\t\t}\n\n\t\t\tlet frPx = pixelForX(xs[fr]);\n\t\t\tlet toPx = to == fr ? frPx : pixelForX(xs[to]);\n\n\t\t\t// if value adjacent to edge null is same pixel, then it's partially\n\t\t\t// filled and gap should start at next pixel\n\t\t\tlet fri2 = fr - dir;\n\t\t\tlet frPx2 = align <= 0 && fri2 >= 0 && fri2 < len ? pixelForX(xs[fri2]) : frPx;\n\t\t//\tif (frPx2 == frPx)\n\t\t//\t\tfrPx++;\n\t\t//\telse\n\t\t\t\tfrPx = frPx2;\n\n\t\t\tlet toi2 = to + dir;\n\t\t\tlet toPx2 = align >= 0 && toi2 >= 0 && toi2 < len ? pixelForX(xs[toi2]) : toPx;\n\t\t//\tif (toPx2 == toPx)\n\t\t//\t\ttoPx--;\n\t\t//\telse\n\t\t\t\ttoPx = toPx2;\n\n\t\t\tif (toPx >= frPx)\n\t\t\t\tgaps.push([frPx, toPx]); // addGap\n\t\t}\n\t}\n\n\treturn gaps;\n}\n\nfunction pxRoundGen(pxAlign) {\n\treturn pxAlign == 0 ? retArg0 : pxAlign == 1 ? round : v => incrRound(v, pxAlign);\n}\n\n/*\n// inefficient linear interpolation that does bi-directinal scans on each call\nexport function costlyLerp(i, idx0, idx1, _dirX, dataY) {\n\tlet prevNonNull = nonNullIdx(dataY, _dirX == 1 ? idx0 : idx1, i, -_dirX);\n\tlet nextNonNull = nonNullIdx(dataY, i, _dirX == 1 ? idx1 : idx0, _dirX);\n\n\tlet prevVal = dataY[prevNonNull];\n\tlet nextVal = dataY[nextNonNull];\n\n\treturn prevVal + (i - prevNonNull) / (nextNonNull - prevNonNull) * (nextVal - prevVal);\n}\n*/\n\nfunction rect(ori) {\n\tlet moveTo = ori == 0 ?\n\t\tmoveToH :\n\t\tmoveToV;\n\n\tlet arcTo = ori == 0 ?\n\t\t(p, x1, y1, x2, y2, r) => { p.arcTo(x1, y1, x2, y2, r); } :\n\t\t(p, y1, x1, y2, x2, r) => { p.arcTo(x1, y1, x2, y2, r); };\n\n\tlet rect = ori == 0 ?\n\t\t(p, x, y, w, h) => { p.rect(x, y, w, h); } :\n\t\t(p, y, x, h, w) => { p.rect(x, y, w, h); };\n\n\t// TODO (pending better browser support): https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/roundRect\n\treturn (p, x, y, w, h, endRad = 0, baseRad = 0) => {\n\t\tif (endRad == 0 && baseRad == 0)\n\t\t\trect(p, x, y, w, h);\n\t\telse {\n\t\t\tendRad = min(endRad, w / 2, h / 2);\n\t\t\tbaseRad = min(baseRad, w / 2, h / 2);\n\n\t\t\t// adapted from https://stackoverflow.com/questions/1255512/how-to-draw-a-rounded-rectangle-using-html-canvas/7838871#7838871\n\t\t\tmoveTo(p, x + endRad, y);\n\t\t\tarcTo(p, x + w, y, x + w, y + h, endRad);\n\t\t\tarcTo(p, x + w, y + h, x, y + h, baseRad);\n\t\t\tarcTo(p, x, y + h, x, y, baseRad);\n\t\t\tarcTo(p, x, y, x + w, y, endRad);\n\t\t\tp.closePath();\n\t\t}\n\t};\n}\n\n// orientation-inverting canvas functions\nconst moveToH = (p, x, y) => { p.moveTo(x, y); };\nconst moveToV = (p, y, x) => { p.moveTo(x, y); };\nconst lineToH = (p, x, y) => { p.lineTo(x, y); };\nconst lineToV = (p, y, x) => { p.lineTo(x, y); };\nconst rectH = rect(0);\nconst rectV = rect(1);\nconst arcH = (p, x, y, r, startAngle, endAngle) => { p.arc(x, y, r, startAngle, endAngle); };\nconst arcV = (p, y, x, r, startAngle, endAngle) => { p.arc(x, y, r, startAngle, endAngle); };\nconst bezierCurveToH = (p, bp1x, bp1y, bp2x, bp2y, p2x, p2y) => { p.bezierCurveTo(bp1x, bp1y, bp2x, bp2y, p2x, p2y); };\nconst bezierCurveToV = (p, bp1y, bp1x, bp2y, bp2x, p2y, p2x) => { p.bezierCurveTo(bp1x, bp1y, bp2x, bp2y, p2x, p2y); };\n\n// TODO: drawWrap(seriesIdx, drawPoints) (save, restore, translate, clip)\nfunction points(opts) {\n\treturn (u, seriesIdx, idx0, idx1, filtIdxs) => {\n\t//\tlog(\"drawPoints()\", arguments);\n\n\t\treturn orient(u, seriesIdx, (series, dataX, dataY, scaleX, scaleY, valToPosX, valToPosY, xOff, yOff, xDim, yDim) => {\n\t\t\tlet { pxRound, points } = series;\n\n\t\t\tlet moveTo, arc;\n\n\t\t\tif (scaleX.ori == 0) {\n\t\t\t\tmoveTo = moveToH;\n\t\t\t\tarc = arcH;\n\t\t\t}\n\t\t\telse {\n\t\t\t\tmoveTo = moveToV;\n\t\t\t\tarc = arcV;\n\t\t\t}\n\n\t\t\tconst width = roundDec(points.width * pxRatio, 3);\n\n\t\t\tlet rad = (points.size - points.width) / 2 * pxRatio;\n\t\t\tlet dia = roundDec(rad * 2, 3);\n\n\t\t\tlet fill = new Path2D();\n\t\t\tlet clip = new Path2D();\n\n\t\t\tlet { left: lft, top: top, width: wid, height: hgt } = u.bbox;\n\n\t\t\trectH(clip,\n\t\t\t\tlft - dia,\n\t\t\t\ttop - dia,\n\t\t\t\twid + dia * 2,\n\t\t\t\thgt + dia * 2,\n\t\t\t);\n\n\t\t\tconst drawPoint = pi => {\n\t\t\t\tif (dataY[pi] != null) {\n\t\t\t\t\tlet x = pxRound(valToPosX(dataX[pi], scaleX, xDim, xOff));\n\t\t\t\t\tlet y = pxRound(valToPosY(dataY[pi], scaleY, yDim, yOff));\n\n\t\t\t\t\tmoveTo(fill, x + rad, y);\n\t\t\t\t\tarc(fill, x, y, rad, 0, PI * 2);\n\t\t\t\t}\n\t\t\t};\n\n\t\t\tif (filtIdxs)\n\t\t\t\tfiltIdxs.forEach(drawPoint);\n\t\t\telse {\n\t\t\t\tfor (let pi = idx0; pi <= idx1; pi++)\n\t\t\t\t\tdrawPoint(pi);\n\t\t\t}\n\n\t\t\treturn {\n\t\t\t\tstroke: width > 0 ? fill : null,\n\t\t\t\tfill,\n\t\t\t\tclip,\n\t\t\t\tflags: BAND_CLIP_FILL | BAND_CLIP_STROKE,\n\t\t\t};\n\t\t});\n\t};\n}\n\nfunction _drawAcc(lineTo) {\n\treturn (stroke, accX, minY, maxY, inY, outY) => {\n\t\tif (minY != maxY) {\n\t\t\tif (inY != minY && outY != minY)\n\t\t\t\tlineTo(stroke, accX, minY);\n\t\t\tif (inY != maxY && outY != maxY)\n\t\t\t\tlineTo(stroke, accX, maxY);\n\n\t\t\tlineTo(stroke, accX, outY);\n\t\t}\n\t};\n}\n\nconst drawAccH = _drawAcc(lineToH);\nconst drawAccV = _drawAcc(lineToV);\n\nfunction linear(opts) {\n\tconst alignGaps = ifNull(opts?.alignGaps, 0);\n\n\treturn (u, seriesIdx, idx0, idx1) => {\n\t\treturn orient(u, seriesIdx, (series, dataX, dataY, scaleX, scaleY, valToPosX, valToPosY, xOff, yOff, xDim, yDim) => {\n\t\t\t[idx0, idx1] = nonNullIdxs(dataY, idx0, idx1);\n\n\t\t\tlet pxRound = series.pxRound;\n\n\t\t\tlet pixelForX = val => pxRound(valToPosX(val, scaleX, xDim, xOff));\n\t\t\tlet pixelForY = val => pxRound(valToPosY(val, scaleY, yDim, yOff));\n\n\t\t\tlet lineTo, drawAcc;\n\n\t\t\tif (scaleX.ori == 0) {\n\t\t\t\tlineTo = lineToH;\n\t\t\t\tdrawAcc = drawAccH;\n\t\t\t}\n\t\t\telse {\n\t\t\t\tlineTo = lineToV;\n\t\t\t\tdrawAcc = drawAccV;\n\t\t\t}\n\n\t\t\tconst dir = scaleX.dir * (scaleX.ori == 0 ? 1 : -1);\n\n\t\t\tconst _paths = {stroke: new Path2D(), fill: null, clip: null, band: null, gaps: null, flags: BAND_CLIP_FILL};\n\t\t\tconst stroke = _paths.stroke;\n\n\t\t\tlet hasGap = false;\n\n\t\t\t// decimate when number of points >= 4x available pixels\n\t\t\tconst decimate = idx1 - idx0 >= xDim * 4;\n\n\t\t\tif (decimate) {\n\t\t\t\tlet xForPixel = pos => u.posToVal(pos, scaleX.key, true);\n\n\t\t\t\tlet minY = null,\n\t\t\t\t\tmaxY = null,\n\t\t\t\t\tinY, outY, drawnAtX;\n\n\t\t\t\tlet accX = pixelForX(dataX[dir == 1 ? idx0 : idx1]);\n\n\t\t\t\tlet idx0px = pixelForX(dataX[idx0]);\n\t\t\t\tlet idx1px = pixelForX(dataX[idx1]);\n\n\t\t\t\t// tracks limit of current x bucket to avoid having to get x pixel for every x value\n\t\t\t\tlet nextAccXVal = xForPixel(dir == 1 ? idx0px + 1 : idx1px - 1);\n\n\t\t\t\tfor (let i = dir == 1 ? idx0 : idx1; i >= idx0 && i <= idx1; i += dir) {\n\t\t\t\t\tlet xVal = dataX[i];\n\t\t\t\t\tlet reuseAccX = dir == 1 ? (xVal < nextAccXVal) : (xVal > nextAccXVal);\n\t\t\t\t\tlet x = reuseAccX ? accX : pixelForX(xVal);\n\n\t\t\t\t\tlet yVal = dataY[i];\n\n\t\t\t\t\tif (x == accX) {\n\t\t\t\t\t\tif (yVal != null) {\n\t\t\t\t\t\t\toutY = yVal;\n\n\t\t\t\t\t\t\tif (minY == null) {\n\t\t\t\t\t\t\t\tlineTo(stroke, x, pixelForY(outY));\n\t\t\t\t\t\t\t\tinY = minY = maxY = outY;\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tif (outY < minY)\n\t\t\t\t\t\t\t\t\tminY = outY;\n\t\t\t\t\t\t\t\telse if (outY > maxY)\n\t\t\t\t\t\t\t\t\tmaxY = outY;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\telse {\n\t\t\t\t\t\t\tif (yVal === null)\n\t\t\t\t\t\t\t\thasGap = true;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\telse {\n\t\t\t\t\t\tif (minY != null)\n\t\t\t\t\t\t\tdrawAcc(stroke, accX, pixelForY(minY), pixelForY(maxY), pixelForY(inY), pixelForY(outY));\n\n\t\t\t\t\t\tif (yVal != null) {\n\t\t\t\t\t\t\toutY = yVal;\n\t\t\t\t\t\t\tlineTo(stroke, x, pixelForY(outY));\n\t\t\t\t\t\t\tminY = maxY = inY = outY;\n\t\t\t\t\t\t}\n\t\t\t\t\t\telse {\n\t\t\t\t\t\t\tminY = maxY = null;\n\n\t\t\t\t\t\t\tif (yVal === null)\n\t\t\t\t\t\t\t\thasGap = true;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\taccX = x;\n\t\t\t\t\t\tnextAccXVal = xForPixel(accX + dir);\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tif (minY != null && minY != maxY && drawnAtX != accX)\n\t\t\t\t\tdrawAcc(stroke, accX, pixelForY(minY), pixelForY(maxY), pixelForY(inY), pixelForY(outY));\n\t\t\t}\n\t\t\telse {\n\t\t\t\tfor (let i = dir == 1 ? idx0 : idx1; i >= idx0 && i <= idx1; i += dir) {\n\t\t\t\t\tlet yVal = dataY[i];\n\n\t\t\t\t\tif (yVal === null)\n\t\t\t\t\t\thasGap = true;\n\t\t\t\t\telse if (yVal != null)\n\t\t\t\t\t\tlineTo(stroke, pixelForX(dataX[i]), pixelForY(yVal));\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tlet [ bandFillDir, bandClipDir ] = bandFillClipDirs(u, seriesIdx);\n\n\t\t\tif (series.fill != null || bandFillDir != 0) {\n\t\t\t\tlet fill = _paths.fill = new Path2D(stroke);\n\n\t\t\t\tlet fillToVal = series.fillTo(u, seriesIdx, series.min, series.max, bandFillDir);\n\t\t\t\tlet fillToY = pixelForY(fillToVal);\n\n\t\t\t\tlet frX = pixelForX(dataX[idx0]);\n\t\t\t\tlet toX = pixelForX(dataX[idx1]);\n\n\t\t\t\tif (dir == -1)\n\t\t\t\t\t[toX, frX] = [frX, toX];\n\n\t\t\t\tlineTo(fill, toX, fillToY);\n\t\t\t\tlineTo(fill, frX, fillToY);\n\t\t\t}\n\n\t\t\tif (!series.spanGaps) { // skip in mode: 2?\n\t\t\t//\tconsole.time('gaps');\n\t\t\t\tlet gaps = [];\n\n\t\t\t\thasGap && gaps.push(...findGaps(dataX, dataY, idx0, idx1, dir, pixelForX, alignGaps));\n\n\t\t\t//\tconsole.timeEnd('gaps');\n\n\t\t\t//\tconsole.log('gaps', JSON.stringify(gaps));\n\n\t\t\t\t_paths.gaps = gaps = series.gaps(u, seriesIdx, idx0, idx1, gaps);\n\n\t\t\t\t_paths.clip = clipGaps(gaps, scaleX.ori, xOff, yOff, xDim, yDim);\n\t\t\t}\n\n\t\t\tif (bandClipDir != 0) {\n\t\t\t\t_paths.band = bandClipDir == 2 ? [\n\t\t\t\t\tclipBandLine(u, seriesIdx, idx0, idx1, stroke, -1),\n\t\t\t\t\tclipBandLine(u, seriesIdx, idx0, idx1, stroke, 1),\n\t\t\t\t] : clipBandLine(u, seriesIdx, idx0, idx1, stroke, bandClipDir);\n\t\t\t}\n\n\t\t\treturn _paths;\n\t\t});\n\t};\n}\n\n// BUG: align: -1 behaves like align: 1 when scale.dir: -1\nfunction stepped(opts) {\n\tconst align = ifNull(opts.align, 1);\n\t// whether to draw ascenders/descenders at null/gap bondaries\n\tconst ascDesc = ifNull(opts.ascDesc, false);\n\tconst alignGaps = ifNull(opts.alignGaps, 0);\n\tconst extend = ifNull(opts.extend, false);\n\n\treturn (u, seriesIdx, idx0, idx1) => {\n\t\treturn orient(u, seriesIdx, (series, dataX, dataY, scaleX, scaleY, valToPosX, valToPosY, xOff, yOff, xDim, yDim) => {\n\t\t\t[idx0, idx1] = nonNullIdxs(dataY, idx0, idx1);\n\n\t\t\tlet pxRound = series.pxRound;\n\n\t\t\tlet { left, width } = u.bbox;\n\n\t\t\tlet pixelForX = val => pxRound(valToPosX(val, scaleX, xDim, xOff));\n\t\t\tlet pixelForY = val => pxRound(valToPosY(val, scaleY, yDim, yOff));\n\n\t\t\tlet lineTo = scaleX.ori == 0 ? lineToH : lineToV;\n\n\t\t\tconst _paths = {stroke: new Path2D(), fill: null, clip: null, band: null, gaps: null, flags: BAND_CLIP_FILL};\n\t\t\tconst stroke = _paths.stroke;\n\n\t\t\tconst dir = scaleX.dir * (scaleX.ori == 0 ? 1 : -1);\n\n\t\t\tlet prevYPos = pixelForY(dataY[dir == 1 ? idx0 : idx1]);\n\t\t\tlet firstXPos = pixelForX(dataX[dir == 1 ? idx0 : idx1]);\n\t\t\tlet prevXPos = firstXPos;\n\n\t\t\tlet firstXPosExt = firstXPos;\n\n\t\t\tif (extend && align == -1) {\n\t\t\t\tfirstXPosExt = left;\n\t\t\t\tlineTo(stroke, firstXPosExt, prevYPos);\n\t\t\t}\n\n\t\t\tlineTo(stroke, firstXPos, prevYPos);\n\n\t\t\tfor (let i = dir == 1 ? idx0 : idx1; i >= idx0 && i <= idx1; i += dir) {\n\t\t\t\tlet yVal1 = dataY[i];\n\n\t\t\t\tif (yVal1 == null)\n\t\t\t\t\tcontinue;\n\n\t\t\t\tlet x1 = pixelForX(dataX[i]);\n\t\t\t\tlet y1 = pixelForY(yVal1);\n\n\t\t\t\tif (align == 1)\n\t\t\t\t\tlineTo(stroke, x1, prevYPos);\n\t\t\t\telse\n\t\t\t\t\tlineTo(stroke, prevXPos, y1);\n\n\t\t\t\tlineTo(stroke, x1, y1);\n\n\t\t\t\tprevYPos = y1;\n\t\t\t\tprevXPos = x1;\n\t\t\t}\n\n\t\t\tlet prevXPosExt = prevXPos;\n\n\t\t\tif (extend && align == 1) {\n\t\t\t\tprevXPosExt = left + width;\n\t\t\t\tlineTo(stroke, prevXPosExt, prevYPos);\n\t\t\t}\n\n\t\t\tlet [ bandFillDir, bandClipDir ] = bandFillClipDirs(u, seriesIdx);\n\n\t\t\tif (series.fill != null || bandFillDir != 0) {\n\t\t\t\tlet fill = _paths.fill = new Path2D(stroke);\n\n\t\t\t\tlet fillTo = series.fillTo(u, seriesIdx, series.min, series.max, bandFillDir);\n\t\t\t\tlet fillToY = pixelForY(fillTo);\n\n\t\t\t\tlineTo(fill, prevXPosExt, fillToY);\n\t\t\t\tlineTo(fill, firstXPosExt, fillToY);\n\t\t\t}\n\n\t\t\tif (!series.spanGaps) {\n\t\t\t//\tconsole.time('gaps');\n\t\t\t\tlet gaps = [];\n\n\t\t\t\tgaps.push(...findGaps(dataX, dataY, idx0, idx1, dir, pixelForX, alignGaps));\n\n\t\t\t//\tconsole.timeEnd('gaps');\n\n\t\t\t//\tconsole.log('gaps', JSON.stringify(gaps));\n\n\t\t\t\t// expand/contract clips for ascenders/descenders\n\t\t\t\tlet halfStroke = (series.width * pxRatio) / 2;\n\t\t\t\tlet startsOffset = (ascDesc || align == 1) ? halfStroke : -halfStroke;\n\t\t\t\tlet endsOffset = (ascDesc || align == -1) ? -halfStroke : halfStroke;\n\n\t\t\t\tgaps.forEach(g => {\n\t\t\t\t\tg[0] += startsOffset;\n\t\t\t\t\tg[1] += endsOffset;\n\t\t\t\t});\n\n\t\t\t\t_paths.gaps = gaps = series.gaps(u, seriesIdx, idx0, idx1, gaps);\n\n\t\t\t\t_paths.clip = clipGaps(gaps, scaleX.ori, xOff, yOff, xDim, yDim);\n\t\t\t}\n\n\t\t\tif (bandClipDir != 0) {\n\t\t\t\t_paths.band = bandClipDir == 2 ? [\n\t\t\t\t\tclipBandLine(u, seriesIdx, idx0, idx1, stroke, -1),\n\t\t\t\t\tclipBandLine(u, seriesIdx, idx0, idx1, stroke, 1),\n\t\t\t\t] : clipBandLine(u, seriesIdx, idx0, idx1, stroke, bandClipDir);\n\t\t\t}\n\n\t\t\treturn _paths;\n\t\t});\n\t};\n}\n\nfunction findColWidth(dataX, dataY, valToPosX, scaleX, xDim, xOff, colWid = inf) {\n\tif (dataX.length > 1) {\n\t\t// prior index with non-undefined y data\n\t\tlet prevIdx = null;\n\n\t\t// scan full dataset for smallest adjacent delta\n\t\t// will not work properly for non-linear x scales, since does not do expensive valToPosX calcs till end\n\t\tfor (let i = 0, minDelta = Infinity; i < dataX.length; i++) {\n\t\t\tif (dataY[i] !== undefined) {\n\t\t\t\tif (prevIdx != null) {\n\t\t\t\t\tlet delta = abs(dataX[i] - dataX[prevIdx]);\n\n\t\t\t\t\tif (delta < minDelta) {\n\t\t\t\t\t\tminDelta = delta;\n\t\t\t\t\t\tcolWid = abs(valToPosX(dataX[i], scaleX, xDim, xOff) - valToPosX(dataX[prevIdx], scaleX, xDim, xOff));\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tprevIdx = i;\n\t\t\t}\n\t\t}\n\t}\n\n\treturn colWid;\n}\n\nfunction bars(opts) {\n\topts = opts || EMPTY_OBJ;\n\tconst size = ifNull(opts.size, [0.6, inf, 1]);\n\tconst align = opts.align || 0;\n\tconst _extraGap = (opts.gap || 0);\n\n\tlet ro = opts.radius;\n\n\tro =\n\t\t// [valueRadius, baselineRadius]\n\t\tro == null ? [0, 0] :\n\t\ttypeof ro == 'number' ? [ro, 0] : ro;\n\n\tconst radiusFn = fnOrSelf(ro);\n\n\tconst gapFactor = 1 - size[0];\n\tconst _maxWidth = ifNull(size[1], inf);\n\tconst _minWidth = ifNull(size[2], 1);\n\n\tconst disp = ifNull(opts.disp, EMPTY_OBJ);\n\tconst _each = ifNull(opts.each, _ => {});\n\n\tconst { fill: dispFills, stroke: dispStrokes } = disp;\n\n\treturn (u, seriesIdx, idx0, idx1) => {\n\t\treturn orient(u, seriesIdx, (series, dataX, dataY, scaleX, scaleY, valToPosX, valToPosY, xOff, yOff, xDim, yDim) => {\n\t\t\tlet pxRound = series.pxRound;\n\t\t\tlet _align = align;\n\n\t\t\tlet extraGap = _extraGap * pxRatio;\n\t\t\tlet maxWidth = _maxWidth * pxRatio;\n\t\t\tlet minWidth = _minWidth * pxRatio;\n\n\t\t\tlet valRadius, baseRadius;\n\n\t\t\tif (scaleX.ori == 0)\n\t\t\t\t[valRadius, baseRadius] = radiusFn(u, seriesIdx);\n\t\t\telse\n\t\t\t\t[baseRadius, valRadius] = radiusFn(u, seriesIdx);\n\n\t\t\tconst _dirX = scaleX.dir * (scaleX.ori == 0 ? 1 : -1);\n\t\t//\tconst _dirY = scaleY.dir * (scaleY.ori == 1 ? 1 : -1);\n\n\t\t\tlet rect = scaleX.ori == 0 ? rectH : rectV;\n\n\t\t\tlet each = scaleX.ori == 0 ? _each : (u, seriesIdx, i, top, lft, hgt, wid) => {\n\t\t\t\t_each(u, seriesIdx, i, lft, top, wid, hgt);\n\t\t\t};\n\n\t\t\t// band where this series is the \"from\" edge\n\t\t\tlet band = ifNull(u.bands, EMPTY_ARR).find(b => b.series[0] == seriesIdx);\n\n\t\t\tlet fillDir = band != null ? band.dir : 0;\n\t\t\tlet fillTo = series.fillTo(u, seriesIdx, series.min, series.max, fillDir);\n\t\t\tlet fillToY = pxRound(valToPosY(fillTo, scaleY, yDim, yOff));\n\n\t\t\t// barWid is to center of stroke\n\t\t\tlet xShift, barWid, fullGap, colWid = xDim;\n\n\t\t\tlet strokeWidth = pxRound(series.width * pxRatio);\n\n\t\t\tlet multiPath = false;\n\n\t\t\tlet fillColors = null;\n\t\t\tlet fillPaths = null;\n\t\t\tlet strokeColors = null;\n\t\t\tlet strokePaths = null;\n\n\t\t\tif (dispFills != null && (strokeWidth == 0 || dispStrokes != null)) {\n\t\t\t\tmultiPath = true;\n\n\t\t\t\tfillColors = dispFills.values(u, seriesIdx, idx0, idx1);\n\t\t\t\tfillPaths = new Map();\n\t\t\t\t(new Set(fillColors)).forEach(color => {\n\t\t\t\t\tif (color != null)\n\t\t\t\t\t\tfillPaths.set(color, new Path2D());\n\t\t\t\t});\n\n\t\t\t\tif (strokeWidth > 0) {\n\t\t\t\t\tstrokeColors = dispStrokes.values(u, seriesIdx, idx0, idx1);\n\t\t\t\t\tstrokePaths = new Map();\n\t\t\t\t\t(new Set(strokeColors)).forEach(color => {\n\t\t\t\t\t\tif (color != null)\n\t\t\t\t\t\t\tstrokePaths.set(color, new Path2D());\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tlet { x0, size } = disp;\n\n\t\t\tif (x0 != null && size != null) {\n\t\t\t\t_align = 1;\n\t\t\t\tdataX = x0.values(u, seriesIdx, idx0, idx1);\n\n\t\t\t\tif (x0.unit == 2)\n\t\t\t\t\tdataX = dataX.map(pct => u.posToVal(xOff + pct * xDim, scaleX.key, true));\n\n\t\t\t\t// assumes uniform sizes, for now\n\t\t\t\tlet sizes = size.values(u, seriesIdx, idx0, idx1);\n\n\t\t\t\tif (size.unit == 2)\n\t\t\t\t\tbarWid = sizes[0] * xDim;\n\t\t\t\telse\n\t\t\t\t\tbarWid = valToPosX(sizes[0], scaleX, xDim, xOff) - valToPosX(0, scaleX, xDim, xOff); // assumes linear scale (delta from 0)\n\n\t\t\t\tcolWid = findColWidth(dataX, dataY, valToPosX, scaleX, xDim, xOff, colWid);\n\n\t\t\t\tlet gapWid = colWid - barWid;\n\t\t\t\tfullGap = gapWid + extraGap;\n\t\t\t}\n\t\t\telse {\n\t\t\t\tcolWid = findColWidth(dataX, dataY, valToPosX, scaleX, xDim, xOff, colWid);\n\n\t\t\t\tlet gapWid = colWid * gapFactor;\n\n\t\t\t\tfullGap = gapWid + extraGap;\n\t\t\t\tbarWid = colWid - fullGap;\n\t\t\t}\n\n\t\t\tif (fullGap < 1)\n\t\t\t\tfullGap = 0;\n\n\t\t\tif (strokeWidth >= barWid / 2)\n\t\t\t\tstrokeWidth = 0;\n\n\t\t\t// for small gaps, disable pixel snapping since gap inconsistencies become noticible and annoying\n\t\t\tif (fullGap < 5)\n\t\t\t\tpxRound = retArg0;\n\n\t\t\tlet insetStroke = fullGap > 0;\n\n\t\t\tlet rawBarWid = colWid - fullGap - (insetStroke ? strokeWidth : 0);\n\n\t\t\tbarWid = pxRound(clamp(rawBarWid, minWidth, maxWidth));\n\n\t\t\txShift = (_align == 0 ? barWid / 2 : _align == _dirX ? 0 : barWid) - _align * _dirX * ((_align == 0 ? extraGap / 2 : 0) + (insetStroke ? strokeWidth / 2 : 0));\n\n\n\t\t\tconst _paths = {stroke: null, fill: null, clip: null, band: null, gaps: null, flags: 0}; // disp, geom\n\n\t\t\tconst stroke = multiPath ? null : new Path2D();\n\n\t\t\tlet dataY0 = null;\n\n\t\t\tif (band != null)\n\t\t\t\tdataY0 = u.data[band.series[1]];\n\t\t\telse {\n\t\t\t\tlet { y0, y1 } = disp;\n\n\t\t\t\tif (y0 != null && y1 != null) {\n\t\t\t\t\tdataY = y1.values(u, seriesIdx, idx0, idx1);\n\t\t\t\t\tdataY0 = y0.values(u, seriesIdx, idx0, idx1);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tlet radVal = valRadius * barWid;\n\t\t\tlet radBase = baseRadius * barWid;\n\n\t\t\tfor (let i = _dirX == 1 ? idx0 : idx1; i >= idx0 && i <= idx1; i += _dirX) {\n\t\t\t\tlet yVal = dataY[i];\n\n\t\t\t\tif (yVal == null)\n\t\t\t\t\tcontinue;\n\n\t\t\t\tif (dataY0 != null) {\n\t\t\t\t\tlet yVal0 = dataY0[i] ?? 0;\n\n\t\t\t\t\tif (yVal - yVal0 == 0)\n\t\t\t\t\t\tcontinue;\n\n\t\t\t\t\tfillToY = valToPosY(yVal0, scaleY, yDim, yOff);\n\t\t\t\t}\n\n\t\t\t\tlet xVal = scaleX.distr != 2 || disp != null ? dataX[i] : i;\n\n\t\t\t\t// TODO: all xPos can be pre-computed once for all series in aligned set\n\t\t\t\tlet xPos = valToPosX(xVal, scaleX, xDim, xOff);\n\t\t\t\tlet yPos = valToPosY(ifNull(yVal, fillTo), scaleY, yDim, yOff);\n\n\t\t\t\tlet lft = pxRound(xPos - xShift);\n\t\t\t\tlet btm = pxRound(max(yPos, fillToY));\n\t\t\t\tlet top = pxRound(min(yPos, fillToY));\n\t\t\t\t// this includes the stroke\n\t\t\t\tlet barHgt = btm - top;\n\n\t\t\t\tif (yVal != null) { // && yVal != fillTo (0 height bar)\n\t\t\t\t\tlet rv = yVal < 0 ? radBase : radVal;\n\t\t\t\t\tlet rb = yVal < 0 ? radVal : radBase;\n\n\t\t\t\t\tif (multiPath) {\n\t\t\t\t\t\tif (strokeWidth > 0 && strokeColors[i] != null)\n\t\t\t\t\t\t\trect(strokePaths.get(strokeColors[i]), lft, top + floor(strokeWidth / 2), barWid, max(0, barHgt - strokeWidth), rv, rb);\n\n\t\t\t\t\t\tif (fillColors[i] != null)\n\t\t\t\t\t\t\trect(fillPaths.get(fillColors[i]), lft, top + floor(strokeWidth / 2), barWid, max(0, barHgt - strokeWidth), rv, rb);\n\t\t\t\t\t}\n\t\t\t\t\telse\n\t\t\t\t\t\trect(stroke, lft, top + floor(strokeWidth / 2), barWid, max(0, barHgt - strokeWidth), rv, rb);\n\n\t\t\t\t\teach(u, seriesIdx, i,\n\t\t\t\t\t\tlft - strokeWidth / 2,\n\t\t\t\t\t\ttop,\n\t\t\t\t\t\tbarWid + strokeWidth,\n\t\t\t\t\t\tbarHgt,\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (strokeWidth > 0)\n\t\t\t\t_paths.stroke = multiPath ? strokePaths : stroke;\n\t\t\telse if (!multiPath) {\n\t\t\t\t_paths._fill = series.width == 0 ? series._fill : series._stroke ?? series._fill;\n\t\t\t\t_paths.width = 0;\n\t\t\t}\n\n\t\t\t_paths.fill = multiPath ? fillPaths : stroke;\n\n\t\t\treturn _paths;\n\t\t});\n\t};\n}\n\nfunction splineInterp(interp, opts) {\n\tconst alignGaps = ifNull(opts?.alignGaps, 0);\n\n\treturn (u, seriesIdx, idx0, idx1) => {\n\t\treturn orient(u, seriesIdx, (series, dataX, dataY, scaleX, scaleY, valToPosX, valToPosY, xOff, yOff, xDim, yDim) => {\n\t\t\t[idx0, idx1] = nonNullIdxs(dataY, idx0, idx1);\n\n\t\t\tlet pxRound = series.pxRound;\n\n\t\t\tlet pixelForX = val => pxRound(valToPosX(val, scaleX, xDim, xOff));\n\t\t\tlet pixelForY = val => pxRound(valToPosY(val, scaleY, yDim, yOff));\n\n\t\t\tlet moveTo, bezierCurveTo, lineTo;\n\n\t\t\tif (scaleX.ori == 0) {\n\t\t\t\tmoveTo = moveToH;\n\t\t\t\tlineTo = lineToH;\n\t\t\t\tbezierCurveTo = bezierCurveToH;\n\t\t\t}\n\t\t\telse {\n\t\t\t\tmoveTo = moveToV;\n\t\t\t\tlineTo = lineToV;\n\t\t\t\tbezierCurveTo = bezierCurveToV;\n\t\t\t}\n\n\t\t\tconst dir = scaleX.dir * (scaleX.ori == 0 ? 1 : -1);\n\n\t\t\tlet firstXPos = pixelForX(dataX[dir == 1 ? idx0 : idx1]);\n\t\t\tlet prevXPos = firstXPos;\n\n\t\t\tlet xCoords = [];\n\t\t\tlet yCoords = [];\n\n\t\t\tfor (let i = dir == 1 ? idx0 : idx1; i >= idx0 && i <= idx1; i += dir) {\n\t\t\t\tlet yVal = dataY[i];\n\n\t\t\t\tif (yVal != null) {\n\t\t\t\t\tlet xVal = dataX[i];\n\t\t\t\t\tlet xPos = pixelForX(xVal);\n\n\t\t\t\t\txCoords.push(prevXPos = xPos);\n\t\t\t\t\tyCoords.push(pixelForY(dataY[i]));\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tconst _paths = {stroke: interp(xCoords, yCoords, moveTo, lineTo, bezierCurveTo, pxRound), fill: null, clip: null, band: null, gaps: null, flags: BAND_CLIP_FILL};\n\t\t\tconst stroke = _paths.stroke;\n\n\t\t\tlet [ bandFillDir, bandClipDir ] = bandFillClipDirs(u, seriesIdx);\n\n\t\t\tif (series.fill != null || bandFillDir != 0) {\n\t\t\t\tlet fill = _paths.fill = new Path2D(stroke);\n\n\t\t\t\tlet fillTo = series.fillTo(u, seriesIdx, series.min, series.max, bandFillDir);\n\t\t\t\tlet fillToY = pixelForY(fillTo);\n\n\t\t\t\tlineTo(fill, prevXPos, fillToY);\n\t\t\t\tlineTo(fill, firstXPos, fillToY);\n\t\t\t}\n\n\t\t\tif (!series.spanGaps) {\n\t\t\t//\tconsole.time('gaps');\n\t\t\t\tlet gaps = [];\n\n\t\t\t\tgaps.push(...findGaps(dataX, dataY, idx0, idx1, dir, pixelForX, alignGaps));\n\n\t\t\t//\tconsole.timeEnd('gaps');\n\n\t\t\t//\tconsole.log('gaps', JSON.stringify(gaps));\n\n\t\t\t\t_paths.gaps = gaps = series.gaps(u, seriesIdx, idx0, idx1, gaps);\n\n\t\t\t\t_paths.clip = clipGaps(gaps, scaleX.ori, xOff, yOff, xDim, yDim);\n\t\t\t}\n\n\t\t\tif (bandClipDir != 0) {\n\t\t\t\t_paths.band = bandClipDir == 2 ? [\n\t\t\t\t\tclipBandLine(u, seriesIdx, idx0, idx1, stroke, -1),\n\t\t\t\t\tclipBandLine(u, seriesIdx, idx0, idx1, stroke, 1),\n\t\t\t\t] : clipBandLine(u, seriesIdx, idx0, idx1, stroke, bandClipDir);\n\t\t\t}\n\n\t\t\treturn _paths;\n\n\t\t\t// if FEAT_PATHS: false in rollup.config.js\n\t\t\t//\tu.ctx.save();\n\t\t\t//\tu.ctx.beginPath();\n\t\t\t//\tu.ctx.rect(u.bbox.left, u.bbox.top, u.bbox.width, u.bbox.height);\n\t\t\t//\tu.ctx.clip();\n\t\t\t//\tu.ctx.strokeStyle = u.series[sidx].stroke;\n\t\t\t//\tu.ctx.stroke(stroke);\n\t\t\t//\tu.ctx.fillStyle = u.series[sidx].fill;\n\t\t\t//\tu.ctx.fill(fill);\n\t\t\t//\tu.ctx.restore();\n\t\t\t//\treturn null;\n\t\t});\n\t};\n}\n\nfunction monotoneCubic(opts) {\n\treturn splineInterp(_monotoneCubic, opts);\n}\n\n// Monotone Cubic Spline interpolation, adapted from the Chartist.js implementation:\n// https://github.com/gionkunz/chartist-js/blob/e7e78201bffe9609915e5e53cfafa29a5d6c49f9/src/scripts/interpolation.js#L240-L369\nfunction _monotoneCubic(xs, ys, moveTo, lineTo, bezierCurveTo, pxRound) {\n\tconst n = xs.length;\n\n\tif (n < 2)\n\t\treturn null;\n\n\tconst path = new Path2D();\n\n\tmoveTo(path, xs[0], ys[0]);\n\n\tif (n == 2)\n\t\tlineTo(path, xs[1], ys[1]);\n\telse {\n\t\tlet ms = Array(n),\n\t\t\tds = Array(n - 1),\n\t\t\tdys = Array(n - 1),\n\t\t\tdxs = Array(n - 1);\n\n\t\t// calc deltas and derivative\n\t\tfor (let i = 0; i < n - 1; i++) {\n\t\t\tdys[i] = ys[i + 1] - ys[i];\n\t\t\tdxs[i] = xs[i + 1] - xs[i];\n\t\t\tds[i] = dys[i] / dxs[i];\n\t\t}\n\n\t\t// determine desired slope (m) at each point using Fritsch-Carlson method\n\t\t// http://math.stackexchange.com/questions/45218/implementation-of-monotone-cubic-interpolation\n\t\tms[0] = ds[0];\n\n\t\tfor (let i = 1; i < n - 1; i++) {\n\t\t\tif (ds[i] === 0 || ds[i - 1] === 0 || (ds[i - 1] > 0) !== (ds[i] > 0))\n\t\t\t\tms[i] = 0;\n\t\t\telse {\n\t\t\t\tms[i] = 3 * (dxs[i - 1] + dxs[i]) / (\n\t\t\t\t\t(2 * dxs[i] + dxs[i - 1]) / ds[i - 1] +\n\t\t\t\t\t(dxs[i] + 2 * dxs[i - 1]) / ds[i]\n\t\t\t\t);\n\n\t\t\t\tif (!isFinite(ms[i]))\n\t\t\t\t\tms[i] = 0;\n\t\t\t}\n\t\t}\n\n\t\tms[n - 1] = ds[n - 2];\n\n\t\tfor (let i = 0; i < n - 1; i++) {\n\t\t\tbezierCurveTo(\n\t\t\t\tpath,\n\t\t\t\txs[i] + dxs[i] / 3,\n\t\t\t\tys[i] + ms[i] * dxs[i] / 3,\n\t\t\t\txs[i + 1] - dxs[i] / 3,\n\t\t\t\tys[i + 1] - ms[i + 1] * dxs[i] / 3,\n\t\t\t\txs[i + 1],\n\t\t\t\tys[i + 1],\n\t\t\t);\n\t\t}\n\t}\n\n\treturn path;\n}\n\nconst cursorPlots = new Set();\n\nfunction invalidateRects() {\n\tfor (let u of cursorPlots)\n\t\tu.syncRect(true);\n}\n\nif (domEnv) {\n\ton(resize, win, invalidateRects);\n\ton(scroll, win, invalidateRects, true);\n\ton(dppxchange, win, () => { uPlot.pxRatio = pxRatio; });\n}\n\nconst linearPath = linear() ;\nconst pointsPath = points() ;\n\nfunction setDefaults(d, xo, yo, initY) {\n\tlet d2 = initY ? [d[0], d[1]].concat(d.slice(2)) : [d[0]].concat(d.slice(1));\n\treturn d2.map((o, i) => setDefault(o, i, xo, yo));\n}\n\nfunction setDefaults2(d, xyo) {\n\treturn d.map((o, i) => i == 0 ? {} : assign({}, xyo, o)); // todo: assign() will not merge facet arrays\n}\n\nfunction setDefault(o, i, xo, yo) {\n\treturn assign({}, (i == 0 ? xo : yo), o);\n}\n\nfunction snapNumX(self, dataMin, dataMax) {\n\treturn dataMin == null ? nullNullTuple : [dataMin, dataMax];\n}\n\nconst snapTimeX = snapNumX;\n\n// this ensures that non-temporal/numeric y-axes get multiple-snapped padding added above/below\n// TODO: also account for incrs when snapping to ensure top of axis gets a tick & value\nfunction snapNumY(self, dataMin, dataMax) {\n\treturn dataMin == null ? nullNullTuple : rangeNum(dataMin, dataMax, rangePad, true);\n}\n\nfunction snapLogY(self, dataMin, dataMax, scale) {\n\treturn dataMin == null ? nullNullTuple : rangeLog(dataMin, dataMax, self.scales[scale].log, false);\n}\n\nconst snapLogX = snapLogY;\n\nfunction snapAsinhY(self, dataMin, dataMax, scale) {\n\treturn dataMin == null ? nullNullTuple : rangeAsinh(dataMin, dataMax, self.scales[scale].log, false);\n}\n\nconst snapAsinhX = snapAsinhY;\n\n// dim is logical (getClientBoundingRect) pixels, not canvas pixels\nfunction findIncr(minVal, maxVal, incrs, dim, minSpace) {\n\tlet intDigits = max(numIntDigits(minVal), numIntDigits(maxVal));\n\n\tlet delta = maxVal - minVal;\n\n\tlet incrIdx = closestIdx((minSpace / dim) * delta, incrs);\n\n\tdo {\n\t\tlet foundIncr = incrs[incrIdx];\n\t\tlet foundSpace = dim * foundIncr / delta;\n\n\t\tif (foundSpace >= minSpace && intDigits + (foundIncr < 5 ? fixedDec.get(foundIncr) : 0) <= 17)\n\t\t\treturn [foundIncr, foundSpace];\n\t} while (++incrIdx < incrs.length);\n\n\treturn [0, 0];\n}\n\nfunction pxRatioFont(font) {\n\tlet fontSize, fontSizeCss;\n\tfont = font.replace(/(\\d+)px/, (m, p1) => (fontSize = round((fontSizeCss = +p1) * pxRatio)) + 'px');\n\treturn [font, fontSize, fontSizeCss];\n}\n\nfunction syncFontSize(axis) {\n\tif (axis.show) {\n\t\t[axis.font, axis.labelFont].forEach(f => {\n\t\t\tlet size = roundDec(f[2] * pxRatio, 1);\n\t\t\tf[0] = f[0].replace(/[0-9.]+px/, size + 'px');\n\t\t\tf[1] = size;\n\t\t});\n\t}\n}\n\nfunction uPlot(opts, data, then) {\n\tconst self = {\n\t\tmode: ifNull(opts.mode, 1),\n\t};\n\n\tconst mode = self.mode;\n\n\tfunction getHPos(val, scale, dim, off) {\n\t\tlet pct = scale.valToPct(val);\n\t\treturn off + dim * (scale.dir == -1 ? (1 - pct) : pct);\n\t}\n\n\tfunction getVPos(val, scale, dim, off) {\n\t\tlet pct = scale.valToPct(val);\n\t\treturn off + dim * (scale.dir == -1 ? pct : (1 - pct));\n\t}\n\n\tfunction getPos(val, scale, dim, off) {\n\t\treturn scale.ori == 0 ? getHPos(val, scale, dim, off) : getVPos(val, scale, dim, off);\n\t}\n\n\tself.valToPosH = getHPos;\n\tself.valToPosV = getVPos;\n\n\tlet ready = false;\n\tself.status = 0;\n\n\tconst root = self.root = placeDiv(UPLOT);\n\n\tif (opts.id != null)\n\t\troot.id = opts.id;\n\n\taddClass(root, opts.class);\n\n\tif (opts.title) {\n\t\tlet title = placeDiv(TITLE, root);\n\t\ttitle.textContent = opts.title;\n\t}\n\n\tconst can = placeTag(\"canvas\");\n\tconst ctx = self.ctx = can.getContext(\"2d\");\n\n\tconst wrap = placeDiv(WRAP, root);\n\n\ton(\"click\", wrap, e => {\n\t\tif (e.target === over) {\n\t\t\tlet didDrag = mouseLeft1 != mouseLeft0 || mouseTop1 != mouseTop0;\n\t\t\tdidDrag && drag.click(self, e);\n\t\t}\n\t}, true);\n\n\tconst under = self.under = placeDiv(UNDER, wrap);\n\twrap.appendChild(can);\n\tconst over = self.over = placeDiv(OVER, wrap);\n\n\topts = copy(opts);\n\n\tconst pxAlign = +ifNull(opts.pxAlign, 1);\n\n\tconst pxRound = pxRoundGen(pxAlign);\n\n\t(opts.plugins || []).forEach(p => {\n\t\tif (p.opts)\n\t\t\topts = p.opts(self, opts) || opts;\n\t});\n\n\tconst ms = opts.ms || 1e-3;\n\n\tconst series = self.series = mode == 1 ?\n\t\tsetDefaults(opts.series || [], xSeriesOpts, ySeriesOpts, false) :\n\t\tsetDefaults2(opts.series || [null], xySeriesOpts);\n\tconst axes = self.axes = setDefaults(opts.axes || [], xAxisOpts, yAxisOpts, true);\n\tconst scales = self.scales = {};\n\tconst bands = self.bands = opts.bands || [];\n\n\tbands.forEach(b => {\n\t\tb.fill = fnOrSelf(b.fill || null);\n\t\tb.dir = ifNull(b.dir, -1);\n\t});\n\n\tconst xScaleKey = mode == 2 ? series[1].facets[0].scale : series[0].scale;\n\n\tconst drawOrderMap = {\n\t\taxes: drawAxesGrid,\n\t\tseries: drawSeries,\n\t};\n\n\tconst drawOrder = (opts.drawOrder || [\"axes\", \"series\"]).map(key => drawOrderMap[key]);\n\n\tfunction initValToPct(sc) {\n\t\tconst getVal = (\n\t\t\tsc.distr == 3 ? val => log10(val > 0 ? val : sc.clamp(self, val, sc.min, sc.max, sc.key)) :\n\t\t\tsc.distr == 4 ? val => asinh(val, sc.asinh) :\n\t\t\tsc.distr == 100 ? val => sc.fwd(val) :\n\t\t\tval => val\n\t\t);\n\n\t\treturn val => {\n\t\t\tlet _val = getVal(val);\n\t\t\tlet { _min, _max } = sc;\n\t\t\tlet delta = _max - _min;\n\t\t\treturn (_val - _min) / delta;\n\t\t};\n\t}\n\n\tfunction initScale(scaleKey) {\n\t\tlet sc = scales[scaleKey];\n\n\t\tif (sc == null) {\n\t\t\tlet scaleOpts = (opts.scales || EMPTY_OBJ)[scaleKey] || EMPTY_OBJ;\n\n\t\t\tif (scaleOpts.from != null) {\n\t\t\t\t// ensure parent is initialized\n\t\t\t\tinitScale(scaleOpts.from);\n\t\t\t\t// dependent scales inherit\n\t\t\t\tlet sc = assign({}, scales[scaleOpts.from], scaleOpts, {key: scaleKey});\n\t\t\t\tsc.valToPct = initValToPct(sc);\n\t\t\t\tscales[scaleKey] = sc;\n\t\t\t}\n\t\t\telse {\n\t\t\t\tsc = scales[scaleKey] = assign({}, (scaleKey == xScaleKey ? xScaleOpts : yScaleOpts), scaleOpts);\n\n\t\t\t\tsc.key = scaleKey;\n\n\t\t\t\tlet isTime = sc.time;\n\n\t\t\t\tlet rn = sc.range;\n\n\t\t\t\tlet rangeIsArr = isArr(rn);\n\n\t\t\t\tif (scaleKey != xScaleKey || (mode == 2 && !isTime)) {\n\t\t\t\t\t// if range array has null limits, it should be auto\n\t\t\t\t\tif (rangeIsArr && (rn[0] == null || rn[1] == null)) {\n\t\t\t\t\t\trn = {\n\t\t\t\t\t\t\tmin: rn[0] == null ? autoRangePart : {\n\t\t\t\t\t\t\t\tmode: 1,\n\t\t\t\t\t\t\t\thard: rn[0],\n\t\t\t\t\t\t\t\tsoft: rn[0],\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tmax: rn[1] == null ? autoRangePart : {\n\t\t\t\t\t\t\t\tmode: 1,\n\t\t\t\t\t\t\t\thard: rn[1],\n\t\t\t\t\t\t\t\tsoft: rn[1],\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t};\n\t\t\t\t\t\trangeIsArr = false;\n\t\t\t\t\t}\n\n\t\t\t\t\tif (!rangeIsArr && isObj(rn)) {\n\t\t\t\t\t\tlet cfg = rn;\n\t\t\t\t\t\t// this is similar to snapNumY\n\t\t\t\t\t\trn = (self, dataMin, dataMax) => dataMin == null ? nullNullTuple : rangeNum(dataMin, dataMax, cfg);\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tsc.range = fnOrSelf(rn || (isTime ? snapTimeX : scaleKey == xScaleKey ?\n\t\t\t\t\t(sc.distr == 3 ? snapLogX : sc.distr == 4 ? snapAsinhX : snapNumX) :\n\t\t\t\t\t(sc.distr == 3 ? snapLogY : sc.distr == 4 ? snapAsinhY : snapNumY)\n\t\t\t\t));\n\n\t\t\t\tsc.auto = fnOrSelf(rangeIsArr ? false : sc.auto);\n\n\t\t\t\tsc.clamp = fnOrSelf(sc.clamp || clampScale);\n\n\t\t\t\t// caches for expensive ops like asinh() & log()\n\t\t\t\tsc._min = sc._max = null;\n\n\t\t\t\tsc.valToPct = initValToPct(sc);\n\t\t\t}\n\t\t}\n\t}\n\n\tinitScale(\"x\");\n\tinitScale(\"y\");\n\n\t// TODO: init scales from facets in mode: 2\n\tif (mode == 1) {\n\t\tseries.forEach(s => {\n\t\t\tinitScale(s.scale);\n\t\t});\n\t}\n\n\taxes.forEach(a => {\n\t\tinitScale(a.scale);\n\t});\n\n\tfor (let k in opts.scales)\n\t\tinitScale(k);\n\n\tconst scaleX = scales[xScaleKey];\n\n\tconst xScaleDistr = scaleX.distr;\n\n\tlet valToPosX, valToPosY;\n\n\tif (scaleX.ori == 0) {\n\t\taddClass(root, ORI_HZ);\n\t\tvalToPosX = getHPos;\n\t\tvalToPosY = getVPos;\n\t\t/*\n\t\tupdOriDims = () => {\n\t\t\txDimCan = plotWid;\n\t\t\txOffCan = plotLft;\n\t\t\tyDimCan = plotHgt;\n\t\t\tyOffCan = plotTop;\n\n\t\t\txDimCss = plotWidCss;\n\t\t\txOffCss = plotLftCss;\n\t\t\tyDimCss = plotHgtCss;\n\t\t\tyOffCss = plotTopCss;\n\t\t};\n\t\t*/\n\t}\n\telse {\n\t\taddClass(root, ORI_VT);\n\t\tvalToPosX = getVPos;\n\t\tvalToPosY = getHPos;\n\t\t/*\n\t\tupdOriDims = () => {\n\t\t\txDimCan = plotHgt;\n\t\t\txOffCan = plotTop;\n\t\t\tyDimCan = plotWid;\n\t\t\tyOffCan = plotLft;\n\n\t\t\txDimCss = plotHgtCss;\n\t\t\txOffCss = plotTopCss;\n\t\t\tyDimCss = plotWidCss;\n\t\t\tyOffCss = plotLftCss;\n\t\t};\n\t\t*/\n\t}\n\n\tconst pendScales = {};\n\n\t// explicitly-set initial scales\n\tfor (let k in scales) {\n\t\tlet sc = scales[k];\n\n\t\tif (sc.min != null || sc.max != null) {\n\t\t\tpendScales[k] = {min: sc.min, max: sc.max};\n\t\t\tsc.min = sc.max = null;\n\t\t}\n\t}\n\n//\tself.tz = opts.tz || Intl.DateTimeFormat().resolvedOptions().timeZone;\n\tconst _tzDate = (opts.tzDate || (ts => new Date(round(ts / ms))));\n\tconst _fmtDate = (opts.fmtDate || fmtDate);\n\n\tconst _timeAxisSplits = (ms == 1 ? timeAxisSplitsMs(_tzDate) : timeAxisSplitsS(_tzDate));\n\tconst _timeAxisVals = timeAxisVals(_tzDate, timeAxisStamps((ms == 1 ? _timeAxisStampsMs : _timeAxisStampsS), _fmtDate));\n\tconst _timeSeriesVal = timeSeriesVal(_tzDate, timeSeriesStamp(_timeSeriesStamp, _fmtDate));\n\n\tconst activeIdxs = [];\n\n\tconst legend = (self.legend = assign({}, legendOpts, opts.legend));\n\tconst cursor = (self.cursor = assign({}, cursorOpts, {drag: {y: mode == 2}}, opts.cursor));\n\tconst showLegend = legend.show;\n\tconst showCursor = cursor.show;\n\tconst markers = legend.markers;\n\n\t{\n\t\tlegend.idxs = activeIdxs;\n\n\t\tmarkers.width = fnOrSelf(markers.width);\n\t\tmarkers.dash = fnOrSelf(markers.dash);\n\t\tmarkers.stroke = fnOrSelf(markers.stroke);\n\t\tmarkers.fill = fnOrSelf(markers.fill);\n\t}\n\n\tlet legendTable;\n\tlet legendHead;\n\tlet legendBody;\n\tlet legendRows = [];\n\tlet legendCells = [];\n\tlet legendCols;\n\tlet multiValLegend = false;\n\tlet NULL_LEGEND_VALUES = {};\n\n\tif (legend.live) {\n\t\tconst getMultiVals = series[1] ? series[1].values : null;\n\t\tmultiValLegend = getMultiVals != null;\n\t\tlegendCols = multiValLegend ? getMultiVals(self, 1, 0) : {_: 0};\n\n\t\tfor (let k in legendCols)\n\t\t\tNULL_LEGEND_VALUES[k] = LEGEND_DISP;\n\t}\n\n\tif (showLegend) {\n\t\tlegendTable = placeTag(\"table\", LEGEND, root);\n\t\tlegendBody = placeTag(\"tbody\", null, legendTable);\n\n\t\t// allows legend to be moved out of root\n\t\tlegend.mount(self, legendTable);\n\n\t\tif (multiValLegend) {\n\t\t\tlegendHead = placeTag(\"thead\", null, legendTable, legendBody);\n\n\t\t\tlet head = placeTag(\"tr\", null, legendHead);\n\t\t\tplaceTag(\"th\", null, head);\n\n\t\t\tfor (var key in legendCols)\n\t\t\t\tplaceTag(\"th\", LEGEND_LABEL, head).textContent = key;\n\t\t}\n\t\telse {\n\t\t\taddClass(legendTable, LEGEND_INLINE);\n\t\t\tlegend.live && addClass(legendTable, LEGEND_LIVE);\n\t\t}\n\t}\n\n\tconst son = {show: true};\n\tconst soff = {show: false};\n\n\tfunction initLegendRow(s, i) {\n\t\tif (i == 0 && (multiValLegend || !legend.live || mode == 2))\n\t\t\treturn nullNullTuple;\n\n\t\tlet cells = [];\n\n\t\tlet row = placeTag(\"tr\", LEGEND_SERIES, legendBody, legendBody.childNodes[i]);\n\n\t\taddClass(row, s.class);\n\n\t\tif (!s.show)\n\t\t\taddClass(row, OFF);\n\n\t\tlet label = placeTag(\"th\", null, row);\n\n\t\tif (markers.show) {\n\t\t\tlet indic = placeDiv(LEGEND_MARKER, label);\n\n\t\t\tif (i > 0) {\n\t\t\t\tlet width = markers.width(self, i);\n\n\t\t\t\tif (width)\n\t\t\t\t\tindic.style.border = width + \"px \" + markers.dash(self, i) + \" \" + markers.stroke(self, i);\n\n\t\t\t\tindic.style.background = markers.fill(self, i);\n\t\t\t}\n\t\t}\n\n\t\tlet text = placeDiv(LEGEND_LABEL, label);\n\n\t\tif (s.label instanceof HTMLElement)\n\t\t\ttext.appendChild(s.label);\n\t\telse\n\t\t\ttext.textContent = s.label;\n\n\t\tif (i > 0) {\n\t\t\tif (!markers.show)\n\t\t\t\ttext.style.color = s.width > 0 ? markers.stroke(self, i) : markers.fill(self, i);\n\n\t\t\tonMouse(\"click\", label, e => {\n\t\t\t\tif (cursor._lock)\n\t\t\t\t\treturn;\n\n\t\t\t\tsetCursorEvent(e);\n\n\t\t\t\tlet seriesIdx = series.indexOf(s);\n\n\t\t\t\tif ((e.ctrlKey || e.metaKey) != legend.isolate) {\n\t\t\t\t\t// if any other series is shown, isolate this one. else show all\n\t\t\t\t\tlet isolate = series.some((s, i) => i > 0 && i != seriesIdx && s.show);\n\n\t\t\t\t\tseries.forEach((s, i) => {\n\t\t\t\t\t\ti > 0 && setSeries(i, isolate ? (i == seriesIdx ? son : soff) : son, true, syncOpts.setSeries);\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t\tsetSeries(seriesIdx, {show: !s.show}, true, syncOpts.setSeries);\n\t\t\t}, false);\n\n\t\t\tif (cursorFocus) {\n\t\t\t\tonMouse(mouseenter, label, e => {\n\t\t\t\t\tif (cursor._lock)\n\t\t\t\t\t\treturn;\n\n\t\t\t\t\tsetCursorEvent(e);\n\n\t\t\t\t\tsetSeries(series.indexOf(s), FOCUS_TRUE, true, syncOpts.setSeries);\n\t\t\t\t}, false);\n\t\t\t}\n\t\t}\n\n\t\tfor (var key in legendCols) {\n\t\t\tlet v = placeTag(\"td\", LEGEND_VALUE, row);\n\t\t\tv.textContent = \"--\";\n\t\t\tcells.push(v);\n\t\t}\n\n\t\treturn [row, cells];\n\t}\n\n\tconst mouseListeners = new Map();\n\n\tfunction onMouse(ev, targ, fn, onlyTarg = true) {\n\t\tconst targListeners = mouseListeners.get(targ) || {};\n\t\tconst listener = cursor.bind[ev](self, targ, fn, onlyTarg);\n\n\t\tif (listener) {\n\t\t\ton(ev, targ, targListeners[ev] = listener);\n\t\t\tmouseListeners.set(targ, targListeners);\n\t\t}\n\t}\n\n\tfunction offMouse(ev, targ, fn) {\n\t\tconst targListeners = mouseListeners.get(targ) || {};\n\n\t\tfor (let k in targListeners) {\n\t\t\tif (ev == null || k == ev) {\n\t\t\t\toff(k, targ, targListeners[k]);\n\t\t\t\tdelete targListeners[k];\n\t\t\t}\n\t\t}\n\n\t\tif (ev == null)\n\t\t\tmouseListeners.delete(targ);\n\t}\n\n\tlet fullWidCss = 0;\n\tlet fullHgtCss = 0;\n\n\tlet plotWidCss = 0;\n\tlet plotHgtCss = 0;\n\n\t// plot margins to account for axes\n\tlet plotLftCss = 0;\n\tlet plotTopCss = 0;\n\n\t// previous values for diffing\n\tlet _plotLftCss = plotLftCss;\n\tlet _plotTopCss = plotTopCss;\n\tlet _plotWidCss = plotWidCss;\n\tlet _plotHgtCss = plotHgtCss;\n\n\n\tlet plotLft = 0;\n\tlet plotTop = 0;\n\tlet plotWid = 0;\n\tlet plotHgt = 0;\n\n\tself.bbox = {};\n\n\tlet shouldSetScales = false;\n\tlet shouldSetSize = false;\n\tlet shouldConvergeSize = false;\n\tlet shouldSetCursor = false;\n\tlet shouldSetSelect = false;\n\tlet shouldSetLegend = false;\n\n\tfunction _setSize(width, height, force) {\n\t\tif (force || (width != self.width || height != self.height))\n\t\t\tcalcSize(width, height);\n\n\t\tresetYSeries(false);\n\n\t\tshouldConvergeSize = true;\n\t\tshouldSetSize = true;\n\n\t\tcommit();\n\t}\n\n\tfunction calcSize(width, height) {\n\t//\tlog(\"calcSize()\", arguments);\n\n\t\tself.width = fullWidCss = plotWidCss = width;\n\t\tself.height = fullHgtCss = plotHgtCss = height;\n\t\tplotLftCss = plotTopCss = 0;\n\n\t\tcalcPlotRect();\n\t\tcalcAxesRects();\n\n\t\tlet bb = self.bbox;\n\n\t\tplotLft = bb.left = incrRound(plotLftCss * pxRatio, 0.5);\n\t\tplotTop = bb.top = incrRound(plotTopCss * pxRatio, 0.5);\n\t\tplotWid = bb.width = incrRound(plotWidCss * pxRatio, 0.5);\n\t\tplotHgt = bb.height = incrRound(plotHgtCss * pxRatio, 0.5);\n\n\t//\tupdOriDims();\n\t}\n\n\t// ensures size calc convergence\n\tconst CYCLE_LIMIT = 3;\n\n\tfunction convergeSize() {\n\t\tlet converged = false;\n\n\t\tlet cycleNum = 0;\n\n\t\twhile (!converged) {\n\t\t\tcycleNum++;\n\n\t\t\tlet axesConverged = axesCalc(cycleNum);\n\t\t\tlet paddingConverged = paddingCalc(cycleNum);\n\n\t\t\tconverged = cycleNum == CYCLE_LIMIT || (axesConverged && paddingConverged);\n\n\t\t\tif (!converged) {\n\t\t\t\tcalcSize(self.width, self.height);\n\t\t\t\tshouldSetSize = true;\n\t\t\t}\n\t\t}\n\t}\n\n\tfunction setSize({width, height}) {\n\t\t_setSize(width, height);\n\t}\n\n\tself.setSize = setSize;\n\n\t// accumulate axis offsets, reduce canvas width\n\tfunction calcPlotRect() {\n\t\t// easements for edge labels\n\t\tlet hasTopAxis = false;\n\t\tlet hasBtmAxis = false;\n\t\tlet hasRgtAxis = false;\n\t\tlet hasLftAxis = false;\n\n\t\taxes.forEach((axis, i) => {\n\t\t\tif (axis.show && axis._show) {\n\t\t\t\tlet {side, _size} = axis;\n\t\t\t\tlet isVt = side % 2;\n\t\t\t\tlet labelSize = axis.label != null ? axis.labelSize : 0;\n\n\t\t\t\tlet fullSize = _size + labelSize;\n\n\t\t\t\tif (fullSize > 0) {\n\t\t\t\t\tif (isVt) {\n\t\t\t\t\t\tplotWidCss -= fullSize;\n\n\t\t\t\t\t\tif (side == 3) {\n\t\t\t\t\t\t\tplotLftCss += fullSize;\n\t\t\t\t\t\t\thasLftAxis = true;\n\t\t\t\t\t\t}\n\t\t\t\t\t\telse\n\t\t\t\t\t\t\thasRgtAxis = true;\n\t\t\t\t\t}\n\t\t\t\t\telse {\n\t\t\t\t\t\tplotHgtCss -= fullSize;\n\n\t\t\t\t\t\tif (side == 0) {\n\t\t\t\t\t\t\tplotTopCss += fullSize;\n\t\t\t\t\t\t\thasTopAxis = true;\n\t\t\t\t\t\t}\n\t\t\t\t\t\telse\n\t\t\t\t\t\t\thasBtmAxis = true;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t});\n\n\t\tsidesWithAxes[0] = hasTopAxis;\n\t\tsidesWithAxes[1] = hasRgtAxis;\n\t\tsidesWithAxes[2] = hasBtmAxis;\n\t\tsidesWithAxes[3] = hasLftAxis;\n\n\t\t// hz padding\n\t\tplotWidCss -= _padding[1] + _padding[3];\n\t\tplotLftCss += _padding[3];\n\n\t\t// vt padding\n\t\tplotHgtCss -= _padding[2] + _padding[0];\n\t\tplotTopCss += _padding[0];\n\t}\n\n\tfunction calcAxesRects() {\n\t\t// will accum +\n\t\tlet off1 = plotLftCss + plotWidCss;\n\t\tlet off2 = plotTopCss + plotHgtCss;\n\t\t// will accum -\n\t\tlet off3 = plotLftCss;\n\t\tlet off0 = plotTopCss;\n\n\t\tfunction incrOffset(side, size) {\n\t\t\tswitch (side) {\n\t\t\t\tcase 1: off1 += size; return off1 - size;\n\t\t\t\tcase 2: off2 += size; return off2 - size;\n\t\t\t\tcase 3: off3 -= size; return off3 + size;\n\t\t\t\tcase 0: off0 -= size; return off0 + size;\n\t\t\t}\n\t\t}\n\n\t\taxes.forEach((axis, i) => {\n\t\t\tif (axis.show && axis._show) {\n\t\t\t\tlet side = axis.side;\n\n\t\t\t\taxis._pos = incrOffset(side, axis._size);\n\n\t\t\t\tif (axis.label != null)\n\t\t\t\t\taxis._lpos = incrOffset(side, axis.labelSize);\n\t\t\t}\n\t\t});\n\t}\n\n\tif (cursor.dataIdx == null) {\n\t\tlet hov = cursor.hover;\n\n\t\tlet skip = hov.skip = new Set(hov.skip ?? []);\n\t\tskip.add(void 0); // alignment artifacts\n\t\tlet prox = hov.prox = fnOrSelf(hov.prox);\n\t\tlet bias = hov.bias ??= 0;\n\n\t\t// TODO: only scan between in-view idxs (i0, i1)\n\t\tcursor.dataIdx = (self, seriesIdx, cursorIdx, valAtPosX) => {\n\t\t\tif (seriesIdx == 0)\n\t\t\t\treturn cursorIdx;\n\n\t\t\tlet idx2 = cursorIdx;\n\n\t\t\tlet _prox = prox(self, seriesIdx, cursorIdx, valAtPosX) ?? inf;\n\t\t\tlet withProx = _prox >= 0 && _prox < inf;\n\t\t\tlet xDim = scaleX.ori == 0 ? plotWidCss : plotHgtCss;\n\t\t\tlet cursorLft = cursor.left;\n\n\t\t\tlet xValues = data[0];\n\t\t\tlet yValues = data[seriesIdx];\n\n\t\t\tif (skip.has(yValues[cursorIdx])) {\n\t\t\t\tidx2 = null;\n\n\t\t\t\tlet nonNullLft = null,\n\t\t\t\t\tnonNullRgt = null,\n\t\t\t\t\tj;\n\n\t\t\t\tif (bias == 0 || bias == -1) {\n\t\t\t\t\tj = cursorIdx;\n\t\t\t\t\twhile (nonNullLft == null && j-- > 0) {\n\t\t\t\t\t\tif (!skip.has(yValues[j]))\n\t\t\t\t\t\t\tnonNullLft = j;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tif (bias == 0 || bias == 1) {\n\t\t\t\t\tj = cursorIdx;\n\t\t\t\t\twhile (nonNullRgt == null && j++ < yValues.length) {\n\t\t\t\t\t\tif (!skip.has(yValues[j]))\n\t\t\t\t\t\t\tnonNullRgt = j;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tif (nonNullLft != null || nonNullRgt != null) {\n\t\t\t\t\tif (withProx) {\n\t\t\t\t\t\tlet lftPos = nonNullLft == null ? -Infinity : valToPosX(xValues[nonNullLft], scaleX, xDim, 0);\n\t\t\t\t\t\tlet rgtPos = nonNullRgt == null ? Infinity : valToPosX(xValues[nonNullRgt], scaleX, xDim, 0);\n\n\t\t\t\t\t\tlet lftDelta = cursorLft - lftPos;\n\t\t\t\t\t\tlet rgtDelta = rgtPos - cursorLft;\n\n\t\t\t\t\t\tif (lftDelta <= rgtDelta) {\n\t\t\t\t\t\t\tif (lftDelta <= _prox)\n\t\t\t\t\t\t\t\tidx2 = nonNullLft;\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tif (rgtDelta <= _prox)\n\t\t\t\t\t\t\t\tidx2 = nonNullRgt;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\telse {\n\t\t\t\t\t\tidx2 =\n\t\t\t\t\t\t\tnonNullRgt == null ? nonNullLft :\n\t\t\t\t\t\t\tnonNullLft == null ? nonNullRgt :\n\t\t\t\t\t\t\tcursorIdx - nonNullLft <= nonNullRgt - cursorIdx ? nonNullLft : nonNullRgt;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\telse if (withProx) {\n\t\t\t\tlet dist = abs(cursorLft - valToPosX(xValues[cursorIdx], scaleX, xDim, 0));\n\n\t\t\t\tif (dist > _prox)\n\t\t\t\t\tidx2 = null;\n\t\t\t}\n\n\t\t\treturn idx2;\n\t\t};\n\t}\n\n\tconst setCursorEvent = e => { cursor.event = e; };\n\n\tcursor.idxs = activeIdxs;\n\n\tcursor._lock = false;\n\n\tlet points = cursor.points;\n\n\tpoints.show = fnOrSelf(points.show);\n\tpoints.size = fnOrSelf(points.size);\n\tpoints.stroke = fnOrSelf(points.stroke);\n\tpoints.width = fnOrSelf(points.width);\n\tpoints.fill = fnOrSelf(points.fill);\n\n\tconst focus = self.focus = assign({}, opts.focus || {alpha: 0.3}, cursor.focus);\n\n\tconst cursorFocus = focus.prox >= 0;\n\tconst cursorOnePt = cursorFocus && points.one;\n\n\t// series-intersection markers\n\tlet cursorPts = [];\n\t// position caches in CSS pixels\n\tlet cursorPtsLft = [];\n\tlet cursorPtsTop = [];\n\n\tfunction initCursorPt(s, si) {\n\t\tlet pt = points.show(self, si);\n\n\t\tif (pt instanceof HTMLElement) {\n\t\t\taddClass(pt, CURSOR_PT);\n\t\t\taddClass(pt, s.class);\n\t\t\telTrans(pt, -10, -10, plotWidCss, plotHgtCss);\n\t\t\tover.insertBefore(pt, cursorPts[si]);\n\n\t\t\treturn pt;\n\t\t}\n\t}\n\n\tfunction initSeries(s, i) {\n\t\tif (mode == 1 || i > 0) {\n\t\t\tlet isTime = mode == 1 && scales[s.scale].time;\n\n\t\t\tlet sv = s.value;\n\t\t\ts.value = isTime ? (isStr(sv) ? timeSeriesVal(_tzDate, timeSeriesStamp(sv, _fmtDate)) : sv || _timeSeriesVal) : sv || numSeriesVal;\n\t\t\ts.label = s.label || (isTime ? timeSeriesLabel : numSeriesLabel);\n\t\t}\n\n\t\tif (cursorOnePt || i > 0) {\n\t\t\ts.width = s.width == null ? 1 : s.width;\n\t\t\ts.paths = s.paths || linearPath || retNull;\n\t\t\ts.fillTo = fnOrSelf(s.fillTo || seriesFillTo);\n\t\t\ts.pxAlign = +ifNull(s.pxAlign, pxAlign);\n\t\t\ts.pxRound = pxRoundGen(s.pxAlign);\n\n\t\t\ts.stroke = fnOrSelf(s.stroke || null);\n\t\t\ts.fill = fnOrSelf(s.fill || null);\n\t\t\ts._stroke = s._fill = s._paths = s._focus = null;\n\n\t\t\tlet _ptDia = ptDia(max(1, s.width), 1);\n\t\t\tlet points = s.points = assign({}, {\n\t\t\t\tsize: _ptDia,\n\t\t\t\twidth: max(1, _ptDia * .2),\n\t\t\t\tstroke: s.stroke,\n\t\t\t\tspace: _ptDia * 2,\n\t\t\t\tpaths: pointsPath,\n\t\t\t\t_stroke: null,\n\t\t\t\t_fill: null,\n\t\t\t}, s.points);\n\t\t\tpoints.show = fnOrSelf(points.show);\n\t\t\tpoints.filter = fnOrSelf(points.filter);\n\t\t\tpoints.fill = fnOrSelf(points.fill);\n\t\t\tpoints.stroke = fnOrSelf(points.stroke);\n\t\t\tpoints.paths = fnOrSelf(points.paths);\n\t\t\tpoints.pxAlign = s.pxAlign;\n\t\t}\n\n\t\tif (showLegend) {\n\t\t\tlet rowCells = initLegendRow(s, i);\n\t\t\tlegendRows.splice(i, 0, rowCells[0]);\n\t\t\tlegendCells.splice(i, 0, rowCells[1]);\n\t\t\tlegend.values.push(null);\t// NULL_LEGEND_VALS not yet avil here :(\n\t\t}\n\n\t\tif (showCursor) {\n\t\t\tactiveIdxs.splice(i, 0, null);\n\n\t\t\tlet pt = null;\n\n\t\t\tif (cursorOnePt) {\n\t\t\t\tif (i == 0)\n\t\t\t\t\tpt = initCursorPt(s, i);\n\t\t\t}\n\t\t\telse if (i > 0)\n\t\t\t\tpt = initCursorPt(s, i);\n\n\t\t\tcursorPts.splice(i, 0, pt);\n\t\t\tcursorPtsLft.splice(i, 0, 0);\n\t\t\tcursorPtsTop.splice(i, 0, 0);\n\t\t}\n\n\t\tfire(\"addSeries\", i);\n\t}\n\n\tfunction addSeries(opts, si) {\n\t\tsi = si == null ? series.length : si;\n\n\t\topts = mode == 1 ? setDefault(opts, si, xSeriesOpts, ySeriesOpts) : setDefault(opts, si, {}, xySeriesOpts);\n\n\t\tseries.splice(si, 0, opts);\n\t\tinitSeries(series[si], si);\n\t}\n\n\tself.addSeries = addSeries;\n\n\tfunction delSeries(i) {\n\t\tseries.splice(i, 1);\n\n\t\tif (showLegend) {\n\t\t\tlegend.values.splice(i, 1);\n\n\t\t\tlegendCells.splice(i, 1);\n\t\t\tlet tr = legendRows.splice(i, 1)[0];\n\t\t\toffMouse(null, tr.firstChild);\n\t\t\ttr.remove();\n\t\t}\n\n\t\tif (showCursor) {\n\t\t\tactiveIdxs.splice(i, 1);\n\t\t\tcursorPts.splice(i, 1)[0].remove();\n\t\t\tcursorPtsLft.splice(i, 1);\n\t\t\tcursorPtsTop.splice(i, 1);\n\t\t}\n\n\t\t// TODO: de-init no-longer-needed scales?\n\n\t\tfire(\"delSeries\", i);\n\t}\n\n\tself.delSeries = delSeries;\n\n\tconst sidesWithAxes = [false, false, false, false];\n\n\tfunction initAxis(axis, i) {\n\t\taxis._show = axis.show;\n\n\t\tif (axis.show) {\n\t\t\tlet isVt = axis.side % 2;\n\n\t\t\tlet sc = scales[axis.scale];\n\n\t\t\t// this can occur if all series specify non-default scales\n\t\t\tif (sc == null) {\n\t\t\t\taxis.scale = isVt ? series[1].scale : xScaleKey;\n\t\t\t\tsc = scales[axis.scale];\n\t\t\t}\n\n\t\t\t// also set defaults for incrs & values based on axis distr\n\t\t\tlet isTime = sc.time;\n\n\t\t\taxis.size = fnOrSelf(axis.size);\n\t\t\taxis.space = fnOrSelf(axis.space);\n\t\t\taxis.rotate = fnOrSelf(axis.rotate);\n\n\t\t\tif (isArr(axis.incrs)) {\n\t\t\t\taxis.incrs.forEach(incr => {\n\t\t\t\t\t!fixedDec.has(incr) && fixedDec.set(incr, guessDec(incr));\n\t\t\t\t});\n\t\t\t}\n\n\t\t\taxis.incrs = fnOrSelf(axis.incrs || ( sc.distr == 2 ? wholeIncrs : (isTime ? (ms == 1 ? timeIncrsMs : timeIncrsS) : numIncrs)));\n\t\t\taxis.splits = fnOrSelf(axis.splits || (isTime && sc.distr == 1 ? _timeAxisSplits : sc.distr == 3 ? logAxisSplits : sc.distr == 4 ? asinhAxisSplits : numAxisSplits));\n\n\t\t\taxis.stroke = fnOrSelf(axis.stroke);\n\t\t\taxis.grid.stroke = fnOrSelf(axis.grid.stroke);\n\t\t\taxis.ticks.stroke = fnOrSelf(axis.ticks.stroke);\n\t\t\taxis.border.stroke = fnOrSelf(axis.border.stroke);\n\n\t\t\tlet av = axis.values;\n\n\t\t\taxis.values = (\n\t\t\t\t// static array of tick values\n\t\t\t\tisArr(av) && !isArr(av[0]) ? fnOrSelf(av) :\n\t\t\t\t// temporal\n\t\t\t\tisTime ? (\n\t\t\t\t\t// config array of fmtDate string tpls\n\t\t\t\t\tisArr(av) ?\n\t\t\t\t\t\ttimeAxisVals(_tzDate, timeAxisStamps(av, _fmtDate)) :\n\t\t\t\t\t// fmtDate string tpl\n\t\t\t\t\tisStr(av) ?\n\t\t\t\t\t\ttimeAxisVal(_tzDate, av) :\n\t\t\t\t\tav || _timeAxisVals\n\t\t\t\t) : av || numAxisVals\n\t\t\t);\n\n\t\t\taxis.filter = fnOrSelf(axis.filter || ( sc.distr >= 3 && sc.log == 10 ? log10AxisValsFilt : sc.distr == 3 && sc.log == 2 ? log2AxisValsFilt : retArg1));\n\n\t\t\taxis.font = pxRatioFont(axis.font);\n\t\t\taxis.labelFont = pxRatioFont(axis.labelFont);\n\n\t\t\taxis._size = axis.size(self, null, i, 0);\n\n\t\t\taxis._space =\n\t\t\taxis._rotate =\n\t\t\taxis._incrs =\n\t\t\taxis._found =\t// foundIncrSpace\n\t\t\taxis._splits =\n\t\t\taxis._values = null;\n\n\t\t\tif (axis._size > 0) {\n\t\t\t\tsidesWithAxes[i] = true;\n\t\t\t\taxis._el = placeDiv(AXIS, wrap);\n\t\t\t}\n\n\t\t\t// debug\n\t\t//\taxis._el.style.background = \"#\" + Math.floor(Math.random()*16777215).toString(16) + '80';\n\t\t}\n\t}\n\n\tfunction autoPadSide(self, side, sidesWithAxes, cycleNum) {\n\t\tlet [hasTopAxis, hasRgtAxis, hasBtmAxis, hasLftAxis] = sidesWithAxes;\n\n\t\tlet ori = side % 2;\n\t\tlet size = 0;\n\n\t\tif (ori == 0 && (hasLftAxis || hasRgtAxis))\n\t\t\tsize = (side == 0 && !hasTopAxis || side == 2 && !hasBtmAxis ? round(xAxisOpts.size / 3) : 0);\n\t\tif (ori == 1 && (hasTopAxis || hasBtmAxis))\n\t\t\tsize = (side == 1 && !hasRgtAxis || side == 3 && !hasLftAxis ? round(yAxisOpts.size / 2) : 0);\n\n\t\treturn size;\n\t}\n\n\tconst padding = self.padding = (opts.padding || [autoPadSide,autoPadSide,autoPadSide,autoPadSide]).map(p => fnOrSelf(ifNull(p, autoPadSide)));\n\tconst _padding = self._padding = padding.map((p, i) => p(self, i, sidesWithAxes, 0));\n\n\tlet dataLen;\n\n\t// rendered data window\n\tlet i0 = null;\n\tlet i1 = null;\n\tconst idxs = mode == 1 ? series[0].idxs : null;\n\n\tlet data0 = null;\n\n\tlet viaAutoScaleX = false;\n\n\tfunction setData(_data, _resetScales) {\n\t\tdata = _data == null ? [] : _data;\n\n\t\tself.data = self._data = data;\n\n\t\tif (mode == 2) {\n\t\t\tdataLen = 0;\n\t\t\tfor (let i = 1; i < series.length; i++)\n\t\t\t\tdataLen += data[i][0].length;\n\t\t}\n\t\telse {\n\t\t\tif (data.length == 0)\n\t\t\t\tself.data = self._data = data = [[]];\n\n\t\t\tdata0 = data[0];\n\t\t\tdataLen = data0.length;\n\n\t\t\tlet scaleData = data;\n\n\t\t\tif (xScaleDistr == 2) {\n\t\t\t\tscaleData = data.slice();\n\n\t\t\t\tlet _data0 = scaleData[0] = Array(dataLen);\n\t\t\t\tfor (let i = 0; i < dataLen; i++)\n\t\t\t\t\t_data0[i] = i;\n\t\t\t}\n\n\t\t\tself._data = data = scaleData;\n\t\t}\n\n\t\tresetYSeries(true);\n\n\t\tfire(\"setData\");\n\n\t\t// forces x axis tick values to re-generate when neither x scale nor y scale changes\n\t\t// in ordinal mode, scale range is by index, so will not change if new data has same length, but tick values are from data\n\t\tif (xScaleDistr == 2) {\n\t\t\tshouldConvergeSize = true;\n\n\t\t\t/* or somewhat cheaper, and uglier:\n\t\t\tif (ready) {\n\t\t\t\t// logic extracted from axesCalc()\n\t\t\t\tlet i = 0;\n\t\t\t\tlet axis = axes[i];\n\t\t\t\tlet _splits = axis._splits.map(i => data0[i]);\n\t\t\t\tlet [_incr, _space] = axis._found;\n\t\t\t\tlet incr = data0[_splits[1]] - data0[_splits[0]];\n\t\t\t\taxis._values = axis.values(self, axis.filter(self, _splits, i, _space, incr), i, _space, incr);\n\t\t\t}\n\t\t\t*/\n\t\t}\n\n\t\tif (_resetScales !== false) {\n\t\t\tlet xsc = scaleX;\n\n\t\t\tif (xsc.auto(self, viaAutoScaleX))\n\t\t\t\tautoScaleX();\n\t\t\telse\n\t\t\t\t_setScale(xScaleKey, xsc.min, xsc.max);\n\n\t\t\tshouldSetCursor = shouldSetCursor || cursor.left >= 0;\n\t\t\tshouldSetLegend = true;\n\t\t\tcommit();\n\t\t}\n\t}\n\n\tself.setData = setData;\n\n\tfunction autoScaleX() {\n\t\tviaAutoScaleX = true;\n\n\t\tlet _min, _max;\n\n\t\tif (mode == 1) {\n\t\t\tif (dataLen > 0) {\n\t\t\t\ti0 = idxs[0] = 0;\n\t\t\t\ti1 = idxs[1] = dataLen - 1;\n\n\t\t\t\t_min = data[0][i0];\n\t\t\t\t_max = data[0][i1];\n\n\t\t\t\tif (xScaleDistr == 2) {\n\t\t\t\t\t_min = i0;\n\t\t\t\t\t_max = i1;\n\t\t\t\t}\n\t\t\t\telse if (_min == _max) {\n\t\t\t\t\tif (xScaleDistr == 3)\n\t\t\t\t\t\t[_min, _max] = rangeLog(_min, _min, scaleX.log, false);\n\t\t\t\t\telse if (xScaleDistr == 4)\n\t\t\t\t\t\t[_min, _max] = rangeAsinh(_min, _min, scaleX.log, false);\n\t\t\t\t\telse if (scaleX.time)\n\t\t\t\t\t\t_max = _min + round(86400 / ms);\n\t\t\t\t\telse\n\t\t\t\t\t\t[_min, _max] = rangeNum(_min, _max, rangePad, true);\n\t\t\t\t}\n\t\t\t}\n\t\t\telse {\n\t\t\t\ti0 = idxs[0] = _min = null;\n\t\t\t\ti1 = idxs[1] = _max = null;\n\t\t\t}\n\t\t}\n\n\t\t_setScale(xScaleKey, _min, _max);\n\t}\n\n\tlet ctxStroke, ctxFill, ctxWidth, ctxDash, ctxJoin, ctxCap, ctxFont, ctxAlign, ctxBaseline;\n\tlet ctxAlpha;\n\n\tfunction setCtxStyle(stroke, width, dash, cap, fill, join) {\n\t\tstroke ??= transparent;\n\t\tdash ??= EMPTY_ARR;\n\t\tcap ??= \"butt\"; // (\u203F|\u203F)\n\t\tfill ??= transparent;\n\t\tjoin ??= \"round\";\n\n\t\tif (stroke != ctxStroke)\n\t\t\tctx.strokeStyle = ctxStroke = stroke;\n\t\tif (fill != ctxFill)\n\t\t\tctx.fillStyle = ctxFill = fill;\n\t\tif (width != ctxWidth)\n\t\t\tctx.lineWidth = ctxWidth = width;\n\t\tif (join != ctxJoin)\n\t\t\tctx.lineJoin = ctxJoin = join;\n\t\tif (cap != ctxCap)\n\t\t\tctx.lineCap = ctxCap = cap;\n\t\tif (dash != ctxDash)\n\t\t\tctx.setLineDash(ctxDash = dash);\n\t}\n\n\tfunction setFontStyle(font, fill, align, baseline) {\n\t\tif (fill != ctxFill)\n\t\t\tctx.fillStyle = ctxFill = fill;\n\t\tif (font != ctxFont)\n\t\t\tctx.font = ctxFont = font;\n\t\tif (align != ctxAlign)\n\t\t\tctx.textAlign = ctxAlign = align;\n\t\tif (baseline != ctxBaseline)\n\t\t\tctx.textBaseline = ctxBaseline = baseline;\n\t}\n\n\tfunction accScale(wsc, psc, facet, data, sorted = 0) {\n\t\tif (data.length > 0 && wsc.auto(self, viaAutoScaleX) && (psc == null || psc.min == null)) {\n\t\t\tlet _i0 = ifNull(i0, 0);\n\t\t\tlet _i1 = ifNull(i1, data.length - 1);\n\n\t\t\t// only run getMinMax() for invalidated series data, else reuse\n\t\t\tlet minMax = facet.min == null ? getMinMax(data, _i0, _i1, sorted, wsc.distr == 3) : [facet.min, facet.max];\n\n\t\t\t// initial min/max\n\t\t\twsc.min = min(wsc.min, facet.min = minMax[0]);\n\t\t\twsc.max = max(wsc.max, facet.max = minMax[1]);\n\t\t}\n\t}\n\n\tconst AUTOSCALE = {min: null, max: null};\n\n\tfunction setScales() {\n\t//\tlog(\"setScales()\", arguments);\n\n\t\t// implicitly add auto scales, and unranged scales\n\t\tfor (let k in scales) {\n\t\t\tlet sc = scales[k];\n\n\t\t\tif (pendScales[k] == null &&\n\t\t\t\t(\n\t\t\t\t\t// scales that have never been set (on init)\n\t\t\t\t\tsc.min == null ||\n\t\t\t\t\t// or auto scales when the x scale was explicitly set\n\t\t\t\t\tpendScales[xScaleKey] != null && sc.auto(self, viaAutoScaleX)\n\t\t\t\t)\n\t\t\t) {\n\t\t\t\tpendScales[k] = AUTOSCALE;\n\t\t\t}\n\t\t}\n\n\t\t// implicitly add dependent scales\n\t\tfor (let k in scales) {\n\t\t\tlet sc = scales[k];\n\n\t\t\tif (pendScales[k] == null && sc.from != null && pendScales[sc.from] != null)\n\t\t\t\tpendScales[k] = AUTOSCALE;\n\t\t}\n\n\t\t// explicitly setting the x-scale invalidates everything (acts as redraw)\n\t\tif (pendScales[xScaleKey] != null)\n\t\t\tresetYSeries(true); // TODO: only reset series on auto scales?\n\n\t\tlet wipScales = {};\n\n\t\tfor (let k in pendScales) {\n\t\t\tlet psc = pendScales[k];\n\n\t\t\tif (psc != null) {\n\t\t\t\tlet wsc = wipScales[k] = copy(scales[k], fastIsObj);\n\n\t\t\t\tif (psc.min != null)\n\t\t\t\t\tassign(wsc, psc);\n\t\t\t\telse if (k != xScaleKey || mode == 2) {\n\t\t\t\t\tif (dataLen == 0 && wsc.from == null) {\n\t\t\t\t\t\tlet minMax = wsc.range(self, null, null, k);\n\t\t\t\t\t\twsc.min = minMax[0];\n\t\t\t\t\t\twsc.max = minMax[1];\n\t\t\t\t\t}\n\t\t\t\t\telse {\n\t\t\t\t\t\twsc.min = inf;\n\t\t\t\t\t\twsc.max = -inf;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif (dataLen > 0) {\n\t\t\t// pre-range y-scales from y series' data values\n\t\t\tseries.forEach((s, i) => {\n\t\t\t\tif (mode == 1) {\n\t\t\t\t\tlet k = s.scale;\n\t\t\t\t\tlet psc = pendScales[k];\n\n\t\t\t\t\tif (psc == null)\n\t\t\t\t\t\treturn;\n\n\t\t\t\t\tlet wsc = wipScales[k];\n\n\t\t\t\t\tif (i == 0) {\n\t\t\t\t\t\tlet minMax = wsc.range(self, wsc.min, wsc.max, k);\n\n\t\t\t\t\t\twsc.min = minMax[0];\n\t\t\t\t\t\twsc.max = minMax[1];\n\n\t\t\t\t\t\ti0 = closestIdx(wsc.min, data[0]);\n\t\t\t\t\t\ti1 = closestIdx(wsc.max, data[0]);\n\n\t\t\t\t\t\t// don't try to contract same or adjacent idxs\n\t\t\t\t\t\tif (i1 - i0 > 1) {\n\t\t\t\t\t\t\t// closest indices can be outside of view\n\t\t\t\t\t\t\tif (data[0][i0] < wsc.min)\n\t\t\t\t\t\t\t\ti0++;\n\t\t\t\t\t\t\tif (data[0][i1] > wsc.max)\n\t\t\t\t\t\t\t\ti1--;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\ts.min = data0[i0];\n\t\t\t\t\t\ts.max = data0[i1];\n\t\t\t\t\t}\n\t\t\t\t\telse if (s.show && s.auto)\n\t\t\t\t\t\taccScale(wsc, psc, s, data[i], s.sorted);\n\n\t\t\t\t\ts.idxs[0] = i0;\n\t\t\t\t\ts.idxs[1] = i1;\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\tif (i > 0) {\n\t\t\t\t\t\tif (s.show && s.auto) {\n\t\t\t\t\t\t\t// TODO: only handles, assumes and requires facets[0] / 'x' scale, and facets[1] / 'y' scale\n\t\t\t\t\t\t\tlet [ xFacet, yFacet ] = s.facets;\n\t\t\t\t\t\t\tlet xScaleKey = xFacet.scale;\n\t\t\t\t\t\t\tlet yScaleKey = yFacet.scale;\n\t\t\t\t\t\t\tlet [ xData, yData ] = data[i];\n\n\t\t\t\t\t\t\tlet wscx = wipScales[xScaleKey];\n\t\t\t\t\t\t\tlet wscy = wipScales[yScaleKey];\n\n\t\t\t\t\t\t\t// null can happen when only x is zoomed, but y has static range and doesnt get auto-added to pending\n\t\t\t\t\t\t\twscx != null && accScale(wscx, pendScales[xScaleKey], xFacet, xData, xFacet.sorted);\n\t\t\t\t\t\t\twscy != null && accScale(wscy, pendScales[yScaleKey], yFacet, yData, yFacet.sorted);\n\n\t\t\t\t\t\t\t// temp\n\t\t\t\t\t\t\ts.min = yFacet.min;\n\t\t\t\t\t\t\ts.max = yFacet.max;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t});\n\n\t\t\t// range independent scales\n\t\t\tfor (let k in wipScales) {\n\t\t\t\tlet wsc = wipScales[k];\n\t\t\t\tlet psc = pendScales[k];\n\n\t\t\t\tif (wsc.from == null && (psc == null || psc.min == null)) {\n\t\t\t\t\tlet minMax = wsc.range(\n\t\t\t\t\t\tself,\n\t\t\t\t\t\twsc.min == inf ? null : wsc.min,\n\t\t\t\t\t\twsc.max == -inf ? null : wsc.max,\n\t\t\t\t\t\tk\n\t\t\t\t\t);\n\t\t\t\t\twsc.min = minMax[0];\n\t\t\t\t\twsc.max = minMax[1];\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// range dependent scales\n\t\tfor (let k in wipScales) {\n\t\t\tlet wsc = wipScales[k];\n\n\t\t\tif (wsc.from != null) {\n\t\t\t\tlet base = wipScales[wsc.from];\n\n\t\t\t\tif (base.min == null)\n\t\t\t\t\twsc.min = wsc.max = null;\n\t\t\t\telse {\n\t\t\t\t\tlet minMax = wsc.range(self, base.min, base.max, k);\n\t\t\t\t\twsc.min = minMax[0];\n\t\t\t\t\twsc.max = minMax[1];\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tlet changed = {};\n\t\tlet anyChanged = false;\n\n\t\tfor (let k in wipScales) {\n\t\t\tlet wsc = wipScales[k];\n\t\t\tlet sc = scales[k];\n\n\t\t\tif (sc.min != wsc.min || sc.max != wsc.max) {\n\t\t\t\tsc.min = wsc.min;\n\t\t\t\tsc.max = wsc.max;\n\n\t\t\t\tlet distr = sc.distr;\n\n\t\t\t\tsc._min = distr == 3 ? log10(sc.min) : distr == 4 ? asinh(sc.min, sc.asinh) : distr == 100 ? sc.fwd(sc.min) : sc.min;\n\t\t\t\tsc._max = distr == 3 ? log10(sc.max) : distr == 4 ? asinh(sc.max, sc.asinh) : distr == 100 ? sc.fwd(sc.max) : sc.max;\n\n\t\t\t\tchanged[k] = anyChanged = true;\n\t\t\t}\n\t\t}\n\n\t\tif (anyChanged) {\n\t\t\t// invalidate paths of all series on changed scales\n\t\t\tseries.forEach((s, i) => {\n\t\t\t\tif (mode == 2) {\n\t\t\t\t\tif (i > 0 && changed.y)\n\t\t\t\t\t\ts._paths = null;\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\tif (changed[s.scale])\n\t\t\t\t\t\ts._paths = null;\n\t\t\t\t}\n\t\t\t});\n\n\t\t\tfor (let k in changed) {\n\t\t\t\tshouldConvergeSize = true;\n\t\t\t\tfire(\"setScale\", k);\n\t\t\t}\n\n\t\t\tif (showCursor && cursor.left >= 0)\n\t\t\t\tshouldSetCursor = shouldSetLegend = true;\n\t\t}\n\n\t\tfor (let k in pendScales)\n\t\t\tpendScales[k] = null;\n\t}\n\n\t// grabs the nearest indices with y data outside of x-scale limits\n\tfunction getOuterIdxs(ydata) {\n\t\tlet _i0 = clamp(i0 - 1, 0, dataLen - 1);\n\t\tlet _i1 = clamp(i1 + 1, 0, dataLen - 1);\n\n\t\twhile (ydata[_i0] == null && _i0 > 0)\n\t\t\t_i0--;\n\n\t\twhile (ydata[_i1] == null && _i1 < dataLen - 1)\n\t\t\t_i1++;\n\n\t\treturn [_i0, _i1];\n\t}\n\n\tfunction drawSeries() {\n\t\tif (dataLen > 0) {\n\t\t\tlet shouldAlpha = series.some(s => s._focus) && ctxAlpha != focus.alpha;\n\n\t\t\tif (shouldAlpha)\n\t\t\t\tctx.globalAlpha = ctxAlpha = focus.alpha;\n\n\t\t\tseries.forEach((s, i) => {\n\t\t\t\tif (i > 0 && s.show) {\n\t\t\t\t\tcacheStrokeFill(i, false);\n\t\t\t\t\tcacheStrokeFill(i, true);\n\n\t\t\t\t\tif (s._paths == null) {\n\t\t\t\t\t\tlet _ctxAlpha = ctxAlpha;\n\n\t\t\t\t\t\tif (ctxAlpha != s.alpha)\n\t\t\t\t\t\t\tctx.globalAlpha = ctxAlpha = s.alpha;\n\n\t\t\t\t\t\tlet _idxs = mode == 2 ? [0, data[i][0].length - 1] : getOuterIdxs(data[i]);\n\t\t\t\t\t\ts._paths = s.paths(self, i, _idxs[0], _idxs[1]);\n\n\t\t\t\t\t\tif (ctxAlpha != _ctxAlpha)\n\t\t\t\t\t\t\tctx.globalAlpha = ctxAlpha = _ctxAlpha;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t});\n\n\t\t\tseries.forEach((s, i) => {\n\t\t\t\tif (i > 0 && s.show) {\n\t\t\t\t\tlet _ctxAlpha = ctxAlpha;\n\n\t\t\t\t\tif (ctxAlpha != s.alpha)\n\t\t\t\t\t\tctx.globalAlpha = ctxAlpha = s.alpha;\n\n\t\t\t\t\ts._paths != null && drawPath(i, false);\n\n\t\t\t\t\t{\n\t\t\t\t\t\tlet _gaps = s._paths != null ? s._paths.gaps : null;\n\n\t\t\t\t\t\tlet show = s.points.show(self, i, i0, i1, _gaps);\n\t\t\t\t\t\tlet idxs = s.points.filter(self, i, show, _gaps);\n\n\t\t\t\t\t\tif (show || idxs) {\n\t\t\t\t\t\t\ts.points._paths = s.points.paths(self, i, i0, i1, idxs);\n\t\t\t\t\t\t\tdrawPath(i, true);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tif (ctxAlpha != _ctxAlpha)\n\t\t\t\t\t\tctx.globalAlpha = ctxAlpha = _ctxAlpha;\n\n\t\t\t\t\tfire(\"drawSeries\", i);\n\t\t\t\t}\n\t\t\t});\n\n\t\t\tif (shouldAlpha)\n\t\t\t\tctx.globalAlpha = ctxAlpha = 1;\n\t\t}\n\t}\n\n\tfunction cacheStrokeFill(si, _points) {\n\t\tlet s = _points ? series[si].points : series[si];\n\n\t\ts._stroke = s.stroke(self, si);\n\t\ts._fill = s.fill(self, si);\n\t}\n\n\tfunction drawPath(si, _points) {\n\t\tlet s = _points ? series[si].points : series[si];\n\n\t\tlet {\n\t\t\tstroke,\n\t\t\tfill,\n\t\t\tclip: gapsClip,\n\t\t\tflags,\n\n\t\t\t_stroke: strokeStyle = s._stroke,\n\t\t\t_fill: fillStyle = s._fill,\n\t\t\t_width: width = s.width,\n\t\t} = s._paths;\n\n\t\twidth = roundDec(width * pxRatio, 3);\n\n\t\tlet boundsClip = null;\n\t\tlet offset = (width % 2) / 2;\n\n\t\tif (_points && fillStyle == null)\n\t\t\tfillStyle = width > 0 ? \"#fff\" : strokeStyle;\n\n\t\tlet _pxAlign = s.pxAlign == 1 && offset > 0;\n\n\t\t_pxAlign && ctx.translate(offset, offset);\n\n\t\tif (!_points) {\n\t\t\tlet lft = plotLft - width / 2,\n\t\t\t\ttop = plotTop - width / 2,\n\t\t\t\twid = plotWid + width,\n\t\t\t\thgt = plotHgt + width;\n\n\t\t\tboundsClip = new Path2D();\n\t\t\tboundsClip.rect(lft, top, wid, hgt);\n\t\t}\n\n\t\t// the points pathbuilder's gapsClip is its boundsClip, since points dont need gaps clipping, and bounds depend on point size\n\t\tif (_points)\n\t\t\tstrokeFill(strokeStyle, width, s.dash, s.cap, fillStyle, stroke, fill, flags, gapsClip);\n\t\telse\n\t\t\tfillStroke(si, strokeStyle, width, s.dash, s.cap, fillStyle, stroke, fill, flags, boundsClip, gapsClip);\n\n\t\t_pxAlign && ctx.translate(-offset, -offset);\n\t}\n\n\tfunction fillStroke(si, strokeStyle, lineWidth, lineDash, lineCap, fillStyle, strokePath, fillPath, flags, boundsClip, gapsClip) {\n\t\tlet didStrokeFill = false;\n\n\t\t// for all bands where this series is the top edge, create upwards clips using the bottom edges\n\t\t// and apply clips + fill with band fill or dfltFill\n\t\tflags != 0 && bands.forEach((b, bi) => {\n\t\t\t// isUpperEdge?\n\t\t\tif (b.series[0] == si) {\n\t\t\t\tlet lowerEdge = series[b.series[1]];\n\t\t\t\tlet lowerData = data[b.series[1]];\n\n\t\t\t\tlet bandClip = (lowerEdge._paths || EMPTY_OBJ).band;\n\n\t\t\t\tif (isArr(bandClip))\n\t\t\t\t\tbandClip = b.dir == 1 ? bandClip[0] : bandClip[1];\n\n\t\t\t\tlet gapsClip2;\n\n\t\t\t\tlet _fillStyle = null;\n\n\t\t\t\t// hasLowerEdge?\n\t\t\t\tif (lowerEdge.show && bandClip && hasData(lowerData, i0, i1)) {\n\t\t\t\t\t_fillStyle = b.fill(self, bi) || fillStyle;\n\t\t\t\t\tgapsClip2 = lowerEdge._paths.clip;\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t\tbandClip = null;\n\n\t\t\t\tstrokeFill(strokeStyle, lineWidth, lineDash, lineCap, _fillStyle, strokePath, fillPath, flags, boundsClip, gapsClip, gapsClip2, bandClip);\n\n\t\t\t\tdidStrokeFill = true;\n\t\t\t}\n\t\t});\n\n\t\tif (!didStrokeFill)\n\t\t\tstrokeFill(strokeStyle, lineWidth, lineDash, lineCap, fillStyle, strokePath, fillPath, flags, boundsClip, gapsClip);\n\t}\n\n\tconst CLIP_FILL_STROKE = BAND_CLIP_FILL | BAND_CLIP_STROKE;\n\n\tfunction strokeFill(strokeStyle, lineWidth, lineDash, lineCap, fillStyle, strokePath, fillPath, flags, boundsClip, gapsClip, gapsClip2, bandClip) {\n\t\tsetCtxStyle(strokeStyle, lineWidth, lineDash, lineCap, fillStyle);\n\n\t\tif (boundsClip || gapsClip || bandClip) {\n\t\t\tctx.save();\n\t\t\tboundsClip && ctx.clip(boundsClip);\n\t\t\tgapsClip && ctx.clip(gapsClip);\n\t\t}\n\n\t\tif (bandClip) {\n\t\t\tif ((flags & CLIP_FILL_STROKE) == CLIP_FILL_STROKE) {\n\t\t\t\tctx.clip(bandClip);\n\t\t\t\tgapsClip2 && ctx.clip(gapsClip2);\n\t\t\t\tdoFill(fillStyle, fillPath);\n\t\t\t\tdoStroke(strokeStyle, strokePath, lineWidth);\n\t\t\t}\n\t\t\telse if (flags & BAND_CLIP_STROKE) {\n\t\t\t\tdoFill(fillStyle, fillPath);\n\t\t\t\tctx.clip(bandClip);\n\t\t\t\tdoStroke(strokeStyle, strokePath, lineWidth);\n\t\t\t}\n\t\t\telse if (flags & BAND_CLIP_FILL) {\n\t\t\t\tctx.save();\n\t\t\t\tctx.clip(bandClip);\n\t\t\t\tgapsClip2 && ctx.clip(gapsClip2);\n\t\t\t\tdoFill(fillStyle, fillPath);\n\t\t\t\tctx.restore();\n\t\t\t\tdoStroke(strokeStyle, strokePath, lineWidth);\n\t\t\t}\n\t\t}\n\t\telse {\n\t\t\tdoFill(fillStyle, fillPath);\n\t\t\tdoStroke(strokeStyle, strokePath, lineWidth);\n\t\t}\n\n\t\tif (boundsClip || gapsClip || bandClip)\n\t\t\tctx.restore();\n\t}\n\n\tfunction doStroke(strokeStyle, strokePath, lineWidth) {\n\t\tif (lineWidth > 0) {\n\t\t\tif (strokePath instanceof Map) {\n\t\t\t\tstrokePath.forEach((strokePath, strokeStyle) => {\n\t\t\t\t\tctx.strokeStyle = ctxStroke = strokeStyle;\n\t\t\t\t\tctx.stroke(strokePath);\n\t\t\t\t});\n\t\t\t}\n\t\t\telse\n\t\t\t\tstrokePath != null && strokeStyle && ctx.stroke(strokePath);\n\t\t}\n\t}\n\n\tfunction doFill(fillStyle, fillPath) {\n\t\tif (fillPath instanceof Map) {\n\t\t\tfillPath.forEach((fillPath, fillStyle) => {\n\t\t\t\tctx.fillStyle = ctxFill = fillStyle;\n\t\t\t\tctx.fill(fillPath);\n\t\t\t});\n\t\t}\n\t\telse\n\t\t\tfillPath != null && fillStyle && ctx.fill(fillPath);\n\t}\n\n\tfunction getIncrSpace(axisIdx, min, max, fullDim) {\n\t\tlet axis = axes[axisIdx];\n\n\t\tlet incrSpace;\n\n\t\tif (fullDim <= 0)\n\t\t\tincrSpace = [0, 0];\n\t\telse {\n\t\t\tlet minSpace = axis._space = axis.space(self, axisIdx, min, max, fullDim);\n\t\t\tlet incrs = axis._incrs = axis.incrs(self, axisIdx, min, max, fullDim, minSpace);\n\t\t\tincrSpace = findIncr(min, max, incrs, fullDim, minSpace);\n\t\t}\n\n\t\treturn (axis._found = incrSpace);\n\t}\n\n\tfunction drawOrthoLines(offs, filts, ori, side, pos0, len, width, stroke, dash, cap) {\n\t\tlet offset = (width % 2) / 2;\n\n\t\tpxAlign == 1 && ctx.translate(offset, offset);\n\n\t\tsetCtxStyle(stroke, width, dash, cap, stroke);\n\n\t\tctx.beginPath();\n\n\t\tlet x0, y0, x1, y1, pos1 = pos0 + (side == 0 || side == 3 ? -len : len);\n\n\t\tif (ori == 0) {\n\t\t\ty0 = pos0;\n\t\t\ty1 = pos1;\n\t\t}\n\t\telse {\n\t\t\tx0 = pos0;\n\t\t\tx1 = pos1;\n\t\t}\n\n\t\tfor (let i = 0; i < offs.length; i++) {\n\t\t\tif (filts[i] != null) {\n\t\t\t\tif (ori == 0)\n\t\t\t\t\tx0 = x1 = offs[i];\n\t\t\t\telse\n\t\t\t\t\ty0 = y1 = offs[i];\n\n\t\t\t\tctx.moveTo(x0, y0);\n\t\t\t\tctx.lineTo(x1, y1);\n\t\t\t}\n\t\t}\n\n\t\tctx.stroke();\n\n\t\tpxAlign == 1 && ctx.translate(-offset, -offset);\n\t}\n\n\tfunction axesCalc(cycleNum) {\n\t//\tlog(\"axesCalc()\", arguments);\n\n\t\tlet converged = true;\n\n\t\taxes.forEach((axis, i) => {\n\t\t\tif (!axis.show)\n\t\t\t\treturn;\n\n\t\t\tlet scale = scales[axis.scale];\n\n\t\t\tif (scale.min == null) {\n\t\t\t\tif (axis._show) {\n\t\t\t\t\tconverged = false;\n\t\t\t\t\taxis._show = false;\n\t\t\t\t\tresetYSeries(false);\n\t\t\t\t}\n\t\t\t\treturn;\n\t\t\t}\n\t\t\telse {\n\t\t\t\tif (!axis._show) {\n\t\t\t\t\tconverged = false;\n\t\t\t\t\taxis._show = true;\n\t\t\t\t\tresetYSeries(false);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tlet side = axis.side;\n\t\t\tlet ori = side % 2;\n\n\t\t\tlet {min, max} = scale;\t\t// \t\t// should this toggle them ._show = false\n\n\t\t\tlet [_incr, _space] = getIncrSpace(i, min, max, ori == 0 ? plotWidCss : plotHgtCss);\n\n\t\t\tif (_space == 0)\n\t\t\t\treturn;\n\n\t\t\t// if we're using index positions, force first tick to match passed index\n\t\t\tlet forceMin = scale.distr == 2;\n\n\t\t\tlet _splits = axis._splits = axis.splits(self, i, min, max, _incr, _space, forceMin);\n\n\t\t\t// tick labels\n\t\t\t// BOO this assumes a specific data/series\n\t\t\tlet splits = scale.distr == 2 ? _splits.map(i => data0[i]) : _splits;\n\t\t\tlet incr = scale.distr == 2 ? data0[_splits[1]] - data0[_splits[0]] : _incr;\n\n\t\t\tlet values = axis._values = axis.values(self, axis.filter(self, splits, i, _space, incr), i, _space, incr);\n\n\t\t\t// rotating of labels only supported on bottom x axis\n\t\t\taxis._rotate = side == 2 ? axis.rotate(self, values, i, _space) : 0;\n\n\t\t\tlet oldSize = axis._size;\n\n\t\t\taxis._size = ceil(axis.size(self, values, i, cycleNum));\n\n\t\t\tif (oldSize != null && axis._size != oldSize)\t\t\t// ready && ?\n\t\t\t\tconverged = false;\n\t\t});\n\n\t\treturn converged;\n\t}\n\n\tfunction paddingCalc(cycleNum) {\n\t\tlet converged = true;\n\n\t\tpadding.forEach((p, i) => {\n\t\t\tlet _p = p(self, i, sidesWithAxes, cycleNum);\n\n\t\t\tif (_p != _padding[i])\n\t\t\t\tconverged = false;\n\n\t\t\t_padding[i] = _p;\n\t\t});\n\n\t\treturn converged;\n\t}\n\n\tfunction drawAxesGrid() {\n\t\tfor (let i = 0; i < axes.length; i++) {\n\t\t\tlet axis = axes[i];\n\n\t\t\tif (!axis.show || !axis._show)\n\t\t\t\tcontinue;\n\n\t\t\tlet side = axis.side;\n\t\t\tlet ori = side % 2;\n\n\t\t\tlet x, y;\n\n\t\t\tlet fillStyle = axis.stroke(self, i);\n\n\t\t\tlet shiftDir = side == 0 || side == 3 ? -1 : 1;\n\n\t\t\tlet [_incr, _space] = axis._found;\n\n\t\t\t// axis label\n\t\t\tif (axis.label != null) {\n\t\t\t\tlet shiftAmt = axis.labelGap * shiftDir;\n\t\t\t\tlet baseLpos = round((axis._lpos + shiftAmt) * pxRatio);\n\n\t\t\t\tsetFontStyle(axis.labelFont[0], fillStyle, \"center\", side == 2 ? TOP : BOTTOM);\n\n\t\t\t\tctx.save();\n\n\t\t\t\tif (ori == 1) {\n\t\t\t\t\tx = y = 0;\n\n\t\t\t\t\tctx.translate(\n\t\t\t\t\t\tbaseLpos,\n\t\t\t\t\t\tround(plotTop + plotHgt / 2),\n\t\t\t\t\t);\n\t\t\t\t\tctx.rotate((side == 3 ? -PI : PI) / 2);\n\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\tx = round(plotLft + plotWid / 2);\n\t\t\t\t\ty = baseLpos;\n\t\t\t\t}\n\n\t\t\t\tlet _label = isFn(axis.label) ? axis.label(self, i, _incr, _space) : axis.label;\n\n\t\t\t\tctx.fillText(_label, x, y);\n\n\t\t\t\tctx.restore();\n\t\t\t}\n\n\t\t\tif (_space == 0)\n\t\t\t\tcontinue;\n\n\t\t\tlet scale = scales[axis.scale];\n\n\t\t\tlet plotDim = ori == 0 ? plotWid : plotHgt;\n\t\t\tlet plotOff = ori == 0 ? plotLft : plotTop;\n\n\t\t\tlet _splits = axis._splits;\n\n\t\t\t// tick labels\n\t\t\t// BOO this assumes a specific data/series\n\t\t\tlet splits = scale.distr == 2 ? _splits.map(i => data0[i]) : _splits;\n\t\t\tlet incr = scale.distr == 2 ? data0[_splits[1]] - data0[_splits[0]] : _incr;\n\n\t\t\tlet ticks = axis.ticks;\n\t\t\tlet border = axis.border;\n\t\t\tlet _tickSize = ticks.show ? ticks.size : 0;\n\t\t\tlet tickSize = round(_tickSize * pxRatio);\n\t\t\tlet axisGap = round((axis.alignTo == 2 ? axis._size - _tickSize - axis.gap : axis.gap) * pxRatio);\n\n\t\t\t// rotating of labels only supported on bottom x axis\n\t\t\tlet angle = axis._rotate * -PI/180;\n\n\t\t\tlet basePos = pxRound(axis._pos * pxRatio);\n\t\t\tlet shiftAmt = (tickSize + axisGap) * shiftDir;\n\t\t\tlet finalPos = basePos + shiftAmt;\n\t\t\t y = ori == 0 ? finalPos : 0;\n\t\t\t x = ori == 1 ? finalPos : 0;\n\n\t\t\tlet font = axis.font[0];\n\t\t\tlet textAlign = axis.align == 1 ? LEFT :\n\t\t\t axis.align == 2 ? RIGHT :\n\t\t\t angle > 0 ? LEFT :\n\t\t\t angle < 0 ? RIGHT :\n\t\t\t ori == 0 ? \"center\" : side == 3 ? RIGHT : LEFT;\n\t\t\tlet textBaseline = angle ||\n\t\t\t ori == 1 ? \"middle\" : side == 2 ? TOP : BOTTOM;\n\n\t\t\tsetFontStyle(font, fillStyle, textAlign, textBaseline);\n\n\t\t\tlet lineHeight = axis.font[1] * axis.lineGap;\n\n\t\t\tlet canOffs = _splits.map(val => pxRound(getPos(val, scale, plotDim, plotOff)));\n\n\t\t\tlet _values = axis._values;\n\n\t\t\tfor (let i = 0; i < _values.length; i++) {\n\t\t\t\tlet val = _values[i];\n\n\t\t\t\tif (val != null) {\n\t\t\t\t\tif (ori == 0)\n\t\t\t\t\t\tx = canOffs[i];\n\t\t\t\t\telse\n\t\t\t\t\t\ty = canOffs[i];\n\n\t\t\t\t\tval = \"\" + val;\n\n\t\t\t\t\tlet _parts = val.indexOf(\"\\n\") == -1 ? [val] : val.split(/\\n/gm);\n\n\t\t\t\t\tfor (let j = 0; j < _parts.length; j++) {\n\t\t\t\t\t\tlet text = _parts[j];\n\n\t\t\t\t\t\tif (angle) {\n\t\t\t\t\t\t\tctx.save();\n\t\t\t\t\t\t\tctx.translate(x, y + j * lineHeight); // can this be replaced with position math?\n\t\t\t\t\t\t\tctx.rotate(angle); // can this be done once?\n\t\t\t\t\t\t\tctx.fillText(text, 0, 0);\n\t\t\t\t\t\t\tctx.restore();\n\t\t\t\t\t\t}\n\t\t\t\t\t\telse\n\t\t\t\t\t\t\tctx.fillText(text, x, y + j * lineHeight);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// ticks\n\t\t\tif (ticks.show) {\n\t\t\t\tdrawOrthoLines(\n\t\t\t\t\tcanOffs,\n\t\t\t\t\tticks.filter(self, splits, i, _space, incr),\n\t\t\t\t\tori,\n\t\t\t\t\tside,\n\t\t\t\t\tbasePos,\n\t\t\t\t\ttickSize,\n\t\t\t\t\troundDec(ticks.width * pxRatio, 3),\n\t\t\t\t\tticks.stroke(self, i),\n\t\t\t\t\tticks.dash,\n\t\t\t\t\tticks.cap,\n\t\t\t\t);\n\t\t\t}\n\n\t\t\t// grid\n\t\t\tlet grid = axis.grid;\n\n\t\t\tif (grid.show) {\n\t\t\t\tdrawOrthoLines(\n\t\t\t\t\tcanOffs,\n\t\t\t\t\tgrid.filter(self, splits, i, _space, incr),\n\t\t\t\t\tori,\n\t\t\t\t\tori == 0 ? 2 : 1,\n\t\t\t\t\tori == 0 ? plotTop : plotLft,\n\t\t\t\t\tori == 0 ? plotHgt : plotWid,\n\t\t\t\t\troundDec(grid.width * pxRatio, 3),\n\t\t\t\t\tgrid.stroke(self, i),\n\t\t\t\t\tgrid.dash,\n\t\t\t\t\tgrid.cap,\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tif (border.show) {\n\t\t\t\tdrawOrthoLines(\n\t\t\t\t\t[basePos],\n\t\t\t\t\t[1],\n\t\t\t\t\tori == 0 ? 1 : 0,\n\t\t\t\t\tori == 0 ? 1 : 2,\n\t\t\t\t\tori == 1 ? plotTop : plotLft,\n\t\t\t\t\tori == 1 ? plotHgt : plotWid,\n\t\t\t\t\troundDec(border.width * pxRatio, 3),\n\t\t\t\t\tborder.stroke(self, i),\n\t\t\t\t\tborder.dash,\n\t\t\t\t\tborder.cap,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\tfire(\"drawAxes\");\n\t}\n\n\tfunction resetYSeries(minMax) {\n\t//\tlog(\"resetYSeries()\", arguments);\n\n\t\tseries.forEach((s, i) => {\n\t\t\tif (i > 0) {\n\t\t\t\ts._paths = null;\n\n\t\t\t\tif (minMax) {\n\t\t\t\t\tif (mode == 1) {\n\t\t\t\t\t\ts.min = null;\n\t\t\t\t\t\ts.max = null;\n\t\t\t\t\t}\n\t\t\t\t\telse {\n\t\t\t\t\t\ts.facets.forEach(f => {\n\t\t\t\t\t\t\tf.min = null;\n\t\t\t\t\t\t\tf.max = null;\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t});\n\t}\n\n\tlet queuedCommit = false;\n\tlet deferHooks = false;\n\tlet hooksQueue = [];\n\n\tfunction flushHooks() {\n\t\tdeferHooks = false;\n\n\t\tfor (let i = 0; i < hooksQueue.length; i++)\n\t\t\tfire(...hooksQueue[i]);\n\n\t\thooksQueue.length = 0;\n\t}\n\n\tfunction commit() {\n\t\tif (!queuedCommit) {\n\t\t\tmicroTask(_commit);\n\t\t\tqueuedCommit = true;\n\t\t}\n\t}\n\n\t// manual batching (aka immediate mode), skips microtask queue\n\tfunction batch(fn, _deferHooks = false) {\n\t\tqueuedCommit = true;\n\t\tdeferHooks = _deferHooks;\n\n\t\tfn(self);\n\t\t_commit();\n\n\t\tif (_deferHooks && hooksQueue.length > 0)\n\t\t\tqueueMicrotask(flushHooks);\n\t}\n\n\tself.batch = batch;\n\n\tfunction _commit() {\n\t//\tlog(\"_commit()\", arguments);\n\n\t\tif (shouldSetScales) {\n\t\t\tsetScales();\n\t\t\tshouldSetScales = false;\n\t\t}\n\n\t\tif (shouldConvergeSize) {\n\t\t\tconvergeSize();\n\t\t\tshouldConvergeSize = false;\n\t\t}\n\n\t\tif (shouldSetSize) {\n\t\t\tsetStylePx(under, LEFT, plotLftCss);\n\t\t\tsetStylePx(under, TOP, plotTopCss);\n\t\t\tsetStylePx(under, WIDTH, plotWidCss);\n\t\t\tsetStylePx(under, HEIGHT, plotHgtCss);\n\n\t\t\tsetStylePx(over, LEFT, plotLftCss);\n\t\t\tsetStylePx(over, TOP, plotTopCss);\n\t\t\tsetStylePx(over, WIDTH, plotWidCss);\n\t\t\tsetStylePx(over, HEIGHT, plotHgtCss);\n\n\t\t\tsetStylePx(wrap, WIDTH, fullWidCss);\n\t\t\tsetStylePx(wrap, HEIGHT, fullHgtCss);\n\n\t\t\t// NOTE: mutating this during print preview in Chrome forces transparent\n\t\t\t// canvas pixels to white, even when followed up with clearRect() below\n\t\t\tcan.width = round(fullWidCss * pxRatio);\n\t\t\tcan.height = round(fullHgtCss * pxRatio);\n\n\t\t\taxes.forEach(({ _el, _show, _size, _pos, side }) => {\n\t\t\t\tif (_el != null) {\n\t\t\t\t\tif (_show) {\n\t\t\t\t\t\tlet posOffset = (side === 3 || side === 0 ? _size : 0);\n\t\t\t\t\t\tlet isVt = side % 2 == 1;\n\n\t\t\t\t\t\tsetStylePx(_el, isVt ? \"left\" : \"top\", _pos - posOffset);\n\t\t\t\t\t\tsetStylePx(_el, isVt ? \"width\" : \"height\", _size);\n\t\t\t\t\t\tsetStylePx(_el, isVt ? \"top\" : \"left\", isVt ? plotTopCss : plotLftCss);\n\t\t\t\t\t\tsetStylePx(_el, isVt ? \"height\" : \"width\", isVt ? plotHgtCss : plotWidCss);\n\n\t\t\t\t\t\tremClass(_el, OFF);\n\t\t\t\t\t}\n\t\t\t\t\telse\n\t\t\t\t\t\taddClass(_el, OFF);\n\t\t\t\t}\n\t\t\t});\n\n\t\t\t// invalidate ctx style cache\n\t\t\tctxStroke = ctxFill = ctxWidth = ctxJoin = ctxCap = ctxFont = ctxAlign = ctxBaseline = ctxDash = null;\n\t\t\tctxAlpha = 1;\n\n\t\t\tsyncRect(true);\n\n\t\t\tif (\n\t\t\t\tplotLftCss != _plotLftCss ||\n\t\t\t\tplotTopCss != _plotTopCss ||\n\t\t\t\tplotWidCss != _plotWidCss ||\n\t\t\t\tplotHgtCss != _plotHgtCss\n\t\t\t) {\n\t\t\t\tresetYSeries(false);\n\n\t\t\t\tlet pctWid = plotWidCss / _plotWidCss;\n\t\t\t\tlet pctHgt = plotHgtCss / _plotHgtCss;\n\n\t\t\t\tif (showCursor && !shouldSetCursor && cursor.left >= 0) {\n\t\t\t\t\tcursor.left *= pctWid;\n\t\t\t\t\tcursor.top *= pctHgt;\n\n\t\t\t\t\tvCursor && elTrans(vCursor, round(cursor.left), 0, plotWidCss, plotHgtCss);\n\t\t\t\t\thCursor && elTrans(hCursor, 0, round(cursor.top), plotWidCss, plotHgtCss);\n\n\t\t\t\t\tfor (let i = 0; i < cursorPts.length; i++) {\n\t\t\t\t\t\tlet pt = cursorPts[i];\n\n\t\t\t\t\t\tif (pt != null) {\n\t\t\t\t\t\t\tcursorPtsLft[i] *= pctWid;\n\t\t\t\t\t\t\tcursorPtsTop[i] *= pctHgt;\n\t\t\t\t\t\t\telTrans(pt, ceil(cursorPtsLft[i]), ceil(cursorPtsTop[i]), plotWidCss, plotHgtCss);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tif (select.show && !shouldSetSelect && select.left >= 0 && select.width > 0) {\n\t\t\t\t\tselect.left *= pctWid;\n\t\t\t\t\tselect.width *= pctWid;\n\t\t\t\t\tselect.top *= pctHgt;\n\t\t\t\t\tselect.height *= pctHgt;\n\n\t\t\t\t\tfor (let prop in _hideProps)\n\t\t\t\t\t\tsetStylePx(selectDiv, prop, select[prop]);\n\t\t\t\t}\n\n\t\t\t\t_plotLftCss = plotLftCss;\n\t\t\t\t_plotTopCss = plotTopCss;\n\t\t\t\t_plotWidCss = plotWidCss;\n\t\t\t\t_plotHgtCss = plotHgtCss;\n\t\t\t}\n\n\t\t\tfire(\"setSize\");\n\n\t\t\tshouldSetSize = false;\n\t\t}\n\n\t\tif (fullWidCss > 0 && fullHgtCss > 0) {\n\t\t\tctx.clearRect(0, 0, can.width, can.height);\n\t\t\tfire(\"drawClear\");\n\t\t\tdrawOrder.forEach(fn => fn());\n\t\t\tfire(\"draw\");\n\t\t}\n\n\t\tif (select.show && shouldSetSelect) {\n\t\t\tsetSelect(select);\n\t\t\tshouldSetSelect = false;\n\t\t}\n\n\t\tif (showCursor && shouldSetCursor) {\n\t\t\tupdateCursor(null, true, false);\n\t\t\tshouldSetCursor = false;\n\t\t}\n\n\t\tif (legend.show && legend.live && shouldSetLegend) {\n\t\t\tsetLegend();\n\t\t\tshouldSetLegend = false; // redundant currently\n\t\t}\n\n\t\tif (!ready) {\n\t\t\tready = true;\n\t\t\tself.status = 1;\n\n\t\t\tfire(\"ready\");\n\t\t}\n\n\t\tviaAutoScaleX = false;\n\n\t\tqueuedCommit = false;\n\t}\n\n\tself.redraw = (rebuildPaths, recalcAxes) => {\n\t\tshouldConvergeSize = recalcAxes || false;\n\n\t\tif (rebuildPaths !== false)\n\t\t\t_setScale(xScaleKey, scaleX.min, scaleX.max);\n\t\telse\n\t\t\tcommit();\n\t};\n\n\t// redraw() => setScale('x', scales.x.min, scales.x.max);\n\n\t// explicit, never re-ranged (is this actually true? for x and y)\n\tfunction setScale(key, opts) {\n\t\tlet sc = scales[key];\n\n\t\tif (sc.from == null) {\n\t\t\tif (dataLen == 0) {\n\t\t\t\tlet minMax = sc.range(self, opts.min, opts.max, key);\n\t\t\t\topts.min = minMax[0];\n\t\t\t\topts.max = minMax[1];\n\t\t\t}\n\n\t\t\tif (opts.min > opts.max) {\n\t\t\t\tlet _min = opts.min;\n\t\t\t\topts.min = opts.max;\n\t\t\t\topts.max = _min;\n\t\t\t}\n\n\t\t\tif (dataLen > 1 && opts.min != null && opts.max != null && opts.max - opts.min < 1e-16)\n\t\t\t\treturn;\n\n\t\t\tif (key == xScaleKey) {\n\t\t\t\tif (sc.distr == 2 && dataLen > 0) {\n\t\t\t\t\topts.min = closestIdx(opts.min, data[0]);\n\t\t\t\t\topts.max = closestIdx(opts.max, data[0]);\n\n\t\t\t\t\tif (opts.min == opts.max)\n\t\t\t\t\t\topts.max++;\n\t\t\t\t}\n\t\t\t}\n\n\t\t//\tlog(\"setScale()\", arguments);\n\n\t\t\tpendScales[key] = opts;\n\n\t\t\tshouldSetScales = true;\n\t\t\tcommit();\n\t\t}\n\t}\n\n\tself.setScale = setScale;\n\n//\tINTERACTION\n\n\tlet xCursor;\n\tlet yCursor;\n\tlet vCursor;\n\tlet hCursor;\n\n\t// starting position before cursor.move\n\tlet rawMouseLeft0;\n\tlet rawMouseTop0;\n\n\t// starting position\n\tlet mouseLeft0;\n\tlet mouseTop0;\n\n\t// current position before cursor.move\n\tlet rawMouseLeft1;\n\tlet rawMouseTop1;\n\n\t// current position\n\tlet mouseLeft1;\n\tlet mouseTop1;\n\n\tlet dragging = false;\n\n\tconst drag = cursor.drag;\n\n\tlet dragX = drag.x;\n\tlet dragY = drag.y;\n\n\tif (showCursor) {\n\t\tif (cursor.x)\n\t\t\txCursor = placeDiv(CURSOR_X, over);\n\t\tif (cursor.y)\n\t\t\tyCursor = placeDiv(CURSOR_Y, over);\n\n\t\tif (scaleX.ori == 0) {\n\t\t\tvCursor = xCursor;\n\t\t\thCursor = yCursor;\n\t\t}\n\t\telse {\n\t\t\tvCursor = yCursor;\n\t\t\thCursor = xCursor;\n\t\t}\n\n\t\tmouseLeft1 = cursor.left;\n\t\tmouseTop1 = cursor.top;\n\t}\n\n\tconst select = self.select = assign({\n\t\tshow: true,\n\t\tover: true,\n\t\tleft: 0,\n\t\twidth: 0,\n\t\ttop: 0,\n\t\theight: 0,\n\t}, opts.select);\n\n\tconst selectDiv = select.show ? placeDiv(SELECT, select.over ? over : under) : null;\n\n\tfunction setSelect(opts, _fire) {\n\t\tif (select.show) {\n\t\t\tfor (let prop in opts) {\n\t\t\t\tselect[prop] = opts[prop];\n\n\t\t\t\tif (prop in _hideProps)\n\t\t\t\t\tsetStylePx(selectDiv, prop, opts[prop]);\n\t\t\t}\n\n\t\t\t_fire !== false && fire(\"setSelect\");\n\t\t}\n\t}\n\n\tself.setSelect = setSelect;\n\n\tfunction toggleDOM(i) {\n\t\tlet s = series[i];\n\n\t\tif (s.show)\n\t\t\tshowLegend && remClass(legendRows[i], OFF);\n\t\telse {\n\t\t\tshowLegend && addClass(legendRows[i], OFF);\n\n\t\t\tif (showCursor) {\n\t\t\t\tlet pt = cursorOnePt ? cursorPts[0] : cursorPts[i];\n\t\t\t\tpt != null && elTrans(pt, -10, -10, plotWidCss, plotHgtCss);\n\t\t\t}\n\t\t}\n\t}\n\n\tfunction _setScale(key, min, max) {\n\t\tsetScale(key, {min, max});\n\t}\n\n\tfunction setSeries(i, opts, _fire, _pub) {\n\t//\tlog(\"setSeries()\", arguments);\n\n\t\tif (opts.focus != null)\n\t\t\tsetFocus(i);\n\n\t\tif (opts.show != null) {\n\t\t\tseries.forEach((s, si) => {\n\t\t\t\tif (si > 0 && (i == si || i == null)) {\n\t\t\t\t\ts.show = opts.show;\n\t\t\t\t\ttoggleDOM(si);\n\n\t\t\t\t\tif (mode == 2) {\n\t\t\t\t\t\t_setScale(s.facets[0].scale, null, null);\n\t\t\t\t\t\t_setScale(s.facets[1].scale, null, null);\n\t\t\t\t\t}\n\t\t\t\t\telse\n\t\t\t\t\t\t_setScale(s.scale, null, null);\n\n\t\t\t\t\tcommit();\n\t\t\t\t}\n\t\t\t});\n\t\t}\n\n\t\t_fire !== false && fire(\"setSeries\", i, opts);\n\n\t\t_pub && pubSync(\"setSeries\", self, i, opts);\n\t}\n\n\tself.setSeries = setSeries;\n\n\tfunction setBand(bi, opts) {\n\t\tassign(bands[bi], opts);\n\t}\n\n\tfunction addBand(opts, bi) {\n\t\topts.fill = fnOrSelf(opts.fill || null);\n\t\topts.dir = ifNull(opts.dir, -1);\n\t\tbi = bi == null ? bands.length : bi;\n\t\tbands.splice(bi, 0, opts);\n\t}\n\n\tfunction delBand(bi) {\n\t\tif (bi == null)\n\t\t\tbands.length = 0;\n\t\telse\n\t\t\tbands.splice(bi, 1);\n\t}\n\n\tself.addBand = addBand;\n\tself.setBand = setBand;\n\tself.delBand = delBand;\n\n\tfunction setAlpha(i, value) {\n\t\tseries[i].alpha = value;\n\n\t\tif (showCursor && cursorPts[i] != null)\n\t\t\tcursorPts[i].style.opacity = value;\n\n\t\tif (showLegend && legendRows[i])\n\t\t\tlegendRows[i].style.opacity = value;\n\t}\n\n\t// y-distance\n\tlet closestDist;\n\tlet closestSeries;\n\tlet focusedSeries;\n\tconst FOCUS_TRUE = {focus: true};\n\n\tfunction setFocus(i) {\n\t\tif (i != focusedSeries) {\n\t\t//\tlog(\"setFocus()\", arguments);\n\n\t\t\tlet allFocused = i == null;\n\n\t\t\tlet _setAlpha = focus.alpha != 1;\n\n\t\t\tseries.forEach((s, i2) => {\n\t\t\t\tif (mode == 1 || i2 > 0) {\n\t\t\t\t\tlet isFocused = allFocused || i2 == 0 || i2 == i;\n\t\t\t\t\ts._focus = allFocused ? null : isFocused;\n\t\t\t\t\t_setAlpha && setAlpha(i2, isFocused ? 1 : focus.alpha);\n\t\t\t\t}\n\t\t\t});\n\n\t\t\tfocusedSeries = i;\n\t\t\t_setAlpha && commit();\n\t\t}\n\t}\n\n\tif (showLegend && cursorFocus) {\n\t\tonMouse(mouseleave, legendTable, e => {\n\t\t\tif (cursor._lock)\n\t\t\t\treturn;\n\n\t\t\tsetCursorEvent(e);\n\n\t\t\tif (focusedSeries != null)\n\t\t\t\tsetSeries(null, FOCUS_TRUE, true, syncOpts.setSeries);\n\t\t});\n\t}\n\n\tfunction posToVal(pos, scale, can) {\n\t\tlet sc = scales[scale];\n\n\t\tif (can)\n\t\t\tpos = pos / pxRatio - (sc.ori == 1 ? plotTopCss : plotLftCss);\n\n\t\tlet dim = plotWidCss;\n\n\t\tif (sc.ori == 1) {\n\t\t\tdim = plotHgtCss;\n\t\t\tpos = dim - pos;\n\t\t}\n\n\t\tif (sc.dir == -1)\n\t\t\tpos = dim - pos;\n\n\t\tlet _min = sc._min,\n\t\t\t_max = sc._max,\n\t\t\tpct = pos / dim;\n\n\t\tlet sv = _min + (_max - _min) * pct;\n\n\t\tlet distr = sc.distr;\n\n\t\treturn (\n\t\t\tdistr == 3 ? pow(10, sv) :\n\t\t\tdistr == 4 ? sinh(sv, sc.asinh) :\n\t\t\tdistr == 100 ? sc.bwd(sv) :\n\t\t\tsv\n\t\t);\n\t}\n\n\tfunction closestIdxFromXpos(pos, can) {\n\t\tlet v = posToVal(pos, xScaleKey, can);\n\t\treturn closestIdx(v, data[0], i0, i1);\n\t}\n\n\tself.valToIdx = val => closestIdx(val, data[0]);\n\tself.posToIdx = closestIdxFromXpos;\n\tself.posToVal = posToVal;\n\tself.valToPos = (val, scale, can) => (\n\t\tscales[scale].ori == 0 ?\n\t\tgetHPos(val, scales[scale],\n\t\t\tcan ? plotWid : plotWidCss,\n\t\t\tcan ? plotLft : 0,\n\t\t) :\n\t\tgetVPos(val, scales[scale],\n\t\t\tcan ? plotHgt : plotHgtCss,\n\t\t\tcan ? plotTop : 0,\n\t\t)\n\t);\n\n\tself.setCursor = (opts, _fire, _pub) => {\n\t\tmouseLeft1 = opts.left;\n\t\tmouseTop1 = opts.top;\n\t//\tassign(cursor, opts);\n\t\tupdateCursor(null, _fire, _pub);\n\t};\n\n\tfunction setSelH(off, dim) {\n\t\tsetStylePx(selectDiv, LEFT, select.left = off);\n\t\tsetStylePx(selectDiv, WIDTH, select.width = dim);\n\t}\n\n\tfunction setSelV(off, dim) {\n\t\tsetStylePx(selectDiv, TOP, select.top = off);\n\t\tsetStylePx(selectDiv, HEIGHT, select.height = dim);\n\t}\n\n\tlet setSelX = scaleX.ori == 0 ? setSelH : setSelV;\n\tlet setSelY = scaleX.ori == 1 ? setSelH : setSelV;\n\n\tfunction syncLegend() {\n\t\tif (showLegend && legend.live) {\n\t\t\tfor (let i = mode == 2 ? 1 : 0; i < series.length; i++) {\n\t\t\t\tif (i == 0 && multiValLegend)\n\t\t\t\t\tcontinue;\n\n\t\t\t\tlet vals = legend.values[i];\n\n\t\t\t\tlet j = 0;\n\n\t\t\t\tfor (let k in vals)\n\t\t\t\t\tlegendCells[i][j++].firstChild.nodeValue = vals[k];\n\t\t\t}\n\t\t}\n\t}\n\n\tfunction setLegend(opts, _fire) {\n\t\tif (opts != null) {\n\t\t\tif (opts.idxs) {\n\t\t\t\topts.idxs.forEach((didx, sidx) => {\n\t\t\t\t\tactiveIdxs[sidx] = didx;\n\t\t\t\t});\n\t\t\t}\n\t\t\telse if (!isUndef(opts.idx))\n\t\t\t\tactiveIdxs.fill(opts.idx);\n\n\t\t\tlegend.idx = activeIdxs[0];\n\t\t}\n\n\t\tif (showLegend && legend.live) {\n\t\t\tfor (let sidx = 0; sidx < series.length; sidx++) {\n\t\t\t\tif (sidx > 0 || mode == 1 && !multiValLegend)\n\t\t\t\t\tsetLegendValues(sidx, activeIdxs[sidx]);\n\t\t\t}\n\n\t\t\tsyncLegend();\n\t\t}\n\n\t\tshouldSetLegend = false;\n\n\t\t_fire !== false && fire(\"setLegend\");\n\t}\n\n\tself.setLegend = setLegend;\n\n\tfunction setLegendValues(sidx, idx) {\n\t\tlet s = series[sidx];\n\t\tlet src = sidx == 0 && xScaleDistr == 2 ? data0 : data[sidx];\n\t\tlet val;\n\n\t\tif (multiValLegend)\n\t\t\tval = s.values(self, sidx, idx) ?? NULL_LEGEND_VALUES;\n\t\telse {\n\t\t\tval = s.value(self, idx == null ? null : src[idx], sidx, idx);\n\t\t\tval = val == null ? NULL_LEGEND_VALUES : {_: val};\n\t\t}\n\n\t\tlegend.values[sidx] = val;\n\t}\n\n\tfunction updateCursor(src, _fire, _pub) {\n\t//\tts == null && log(\"updateCursor()\", arguments);\n\n\t\trawMouseLeft1 = mouseLeft1;\n\t\trawMouseTop1 = mouseTop1;\n\n\t\t[mouseLeft1, mouseTop1] = cursor.move(self, mouseLeft1, mouseTop1);\n\n\t\tcursor.left = mouseLeft1;\n\t\tcursor.top = mouseTop1;\n\n\t\tif (showCursor) {\n\t\t\tvCursor && elTrans(vCursor, round(mouseLeft1), 0, plotWidCss, plotHgtCss);\n\t\t\thCursor && elTrans(hCursor, 0, round(mouseTop1), plotWidCss, plotHgtCss);\n\t\t}\n\n\t\tlet idx;\n\n\t\t// when zooming to an x scale range between datapoints the binary search\n\t\t// for nearest min/max indices results in this condition. cheap hack :D\n\t\tlet noDataInRange = i0 > i1; // works for mode 1 only\n\n\t\tclosestDist = inf;\n\t\tclosestSeries = null;\n\n\t\t// TODO: extract\n\t\tlet xDim = scaleX.ori == 0 ? plotWidCss : plotHgtCss;\n\t\tlet yDim = scaleX.ori == 1 ? plotWidCss : plotHgtCss;\n\n\t\t// if cursor hidden, hide points & clear legend vals\n\t\tif (mouseLeft1 < 0 || dataLen == 0 || noDataInRange) {\n\t\t\tidx = cursor.idx = null;\n\n\t\t\tfor (let i = 0; i < series.length; i++) {\n\t\t\t\tlet pt = cursorPts[i];\n\t\t\t\tpt != null && elTrans(pt, -10, -10, plotWidCss, plotHgtCss);\n\t\t\t}\n\n\t\t\tif (cursorFocus)\n\t\t\t\tsetSeries(null, FOCUS_TRUE, true, src == null && syncOpts.setSeries);\n\n\t\t\tif (legend.live) {\n\t\t\t\tactiveIdxs.fill(idx);\n\t\t\t\tshouldSetLegend = true;\n\t\t\t}\n\t\t}\n\t\telse {\n\t\t//\tlet pctY = 1 - (y / rect.height);\n\n\t\t\tlet mouseXPos, valAtPosX, xPos;\n\n\t\t\tif (mode == 1) {\n\t\t\t\tmouseXPos = scaleX.ori == 0 ? mouseLeft1 : mouseTop1;\n\t\t\t\tvalAtPosX = posToVal(mouseXPos, xScaleKey);\n\t\t\t\tidx = cursor.idx = closestIdx(valAtPosX, data[0], i0, i1);\n\t\t\t\txPos = valToPosX(data[0][idx], scaleX, xDim, 0);\n\t\t\t}\n\n\t\t\t// closest pt values\n\t\t\tlet _ptLft = -10;\n\t\t\tlet _ptTop = -10;\n\t\t\tlet _ptWid = 0;\n\t\t\tlet _ptHgt = 0;\n\t\t\tlet _centered = true;\n\t\t\tlet _ptFill = '';\n\t\t\tlet _ptStroke = '';\n\n\t\t\tfor (let i = mode == 2 ? 1 : 0; i < series.length; i++) {\n\t\t\t\tlet s = series[i];\n\n\t\t\t\tlet idx1 = activeIdxs[i];\n\t\t\t\tlet yVal1 = idx1 == null ? null : (mode == 1 ? data[i][idx1] : data[i][1][idx1]);\n\n\t\t\t\tlet idx2 = cursor.dataIdx(self, i, idx, valAtPosX);\n\t\t\t\tlet yVal2 = idx2 == null ? null : (mode == 1 ? data[i][idx2] : data[i][1][idx2]);\n\n\t\t\t\tshouldSetLegend = shouldSetLegend || yVal2 != yVal1 || idx2 != idx1;\n\n\t\t\t\tactiveIdxs[i] = idx2;\n\n\t\t\t\tif (i > 0 && s.show) {\n\t\t\t\t\tlet xPos2 = idx2 == null ? -10 : idx2 == idx ? xPos : valToPosX(mode == 1 ? data[0][idx2] : data[i][0][idx2], scaleX, xDim, 0);\n\n\t\t\t\t\t// this doesnt really work for state timeline, heatmap, status history (where the value maps to color, not y coords)\n\t\t\t\t\tlet yPos = yVal2 == null ? -10 : valToPosY(yVal2, mode == 1 ? scales[s.scale] : scales[s.facets[1].scale], yDim, 0);\n\n\t\t\t\t\tif (cursorFocus && yVal2 != null) {\n\t\t\t\t\t\tlet mouseYPos = scaleX.ori == 1 ? mouseLeft1 : mouseTop1;\n\t\t\t\t\t\tlet dist = abs(focus.dist(self, i, idx2, yPos, mouseYPos));\n\n\t\t\t\t\t\tif (dist < closestDist) {\n\t\t\t\t\t\t\tlet bias = focus.bias;\n\n\t\t\t\t\t\t\tif (bias != 0) {\n\t\t\t\t\t\t\t\tlet mouseYVal = posToVal(mouseYPos, s.scale);\n\n\t\t\t\t\t\t\t\tlet seriesYValSign = yVal2 >= 0 ? 1 : -1;\n\t\t\t\t\t\t\t\tlet mouseYValSign = mouseYVal >= 0 ? 1 : -1;\n\n\t\t\t\t\t\t\t\t// with a focus bias, we will never cross zero when prox testing\n\t\t\t\t\t\t\t\t// it's either closest towards zero, or closest away from zero\n\t\t\t\t\t\t\t\tif (mouseYValSign == seriesYValSign && (\n\t\t\t\t\t\t\t\t\tmouseYValSign == 1 ?\n\t\t\t\t\t\t\t\t\t\t(bias == 1 ? yVal2 >= mouseYVal : yVal2 <= mouseYVal) : // >= 0\n\t\t\t\t\t\t\t\t\t\t(bias == 1 ? yVal2 <= mouseYVal : yVal2 >= mouseYVal) // < 0\n\t\t\t\t\t\t\t\t)) {\n\t\t\t\t\t\t\t\t\tclosestDist = dist;\n\t\t\t\t\t\t\t\t\tclosestSeries = i;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\telse {\n\t\t\t\t\t\t\t\tclosestDist = dist;\n\t\t\t\t\t\t\t\tclosestSeries = i;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tif (shouldSetLegend || cursorOnePt) {\n\t\t\t\t\t\tlet hPos, vPos;\n\n\t\t\t\t\t\tif (scaleX.ori == 0) {\n\t\t\t\t\t\t\thPos = xPos2;\n\t\t\t\t\t\t\tvPos = yPos;\n\t\t\t\t\t\t}\n\t\t\t\t\t\telse {\n\t\t\t\t\t\t\thPos = yPos;\n\t\t\t\t\t\t\tvPos = xPos2;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tlet ptWid, ptHgt, ptLft, ptTop,\n\t\t\t\t\t\t\tptStroke, ptFill,\n\t\t\t\t\t\t\tcentered = true,\n\t\t\t\t\t\t\tgetBBox = points.bbox;\n\n\t\t\t\t\t\tif (getBBox != null) {\n\t\t\t\t\t\t\tcentered = false;\n\n\t\t\t\t\t\t\tlet bbox = getBBox(self, i);\n\n\t\t\t\t\t\t\tptLft = bbox.left;\n\t\t\t\t\t\t\tptTop = bbox.top;\n\t\t\t\t\t\t\tptWid = bbox.width;\n\t\t\t\t\t\t\tptHgt = bbox.height;\n\t\t\t\t\t\t}\n\t\t\t\t\t\telse {\n\t\t\t\t\t\t\tptLft = hPos;\n\t\t\t\t\t\t\tptTop = vPos;\n\t\t\t\t\t\t\tptWid = ptHgt = points.size(self, i);\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tptFill = points.fill(self, i);\n\t\t\t\t\t\tptStroke = points.stroke(self, i);\n\n\t\t\t\t\t\tif (cursorOnePt) {\n\t\t\t\t\t\t\tif (i == closestSeries && closestDist <= focus.prox) {\n\t\t\t\t\t\t\t\t_ptLft = ptLft;\n\t\t\t\t\t\t\t\t_ptTop = ptTop;\n\t\t\t\t\t\t\t\t_ptWid = ptWid;\n\t\t\t\t\t\t\t\t_ptHgt = ptHgt;\n\t\t\t\t\t\t\t\t_centered = centered;\n\t\t\t\t\t\t\t\t_ptFill = ptFill;\n\t\t\t\t\t\t\t\t_ptStroke = ptStroke;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\telse {\n\t\t\t\t\t\t\tlet pt = cursorPts[i];\n\n\t\t\t\t\t\t\tif (pt != null) {\n\t\t\t\t\t\t\t\tcursorPtsLft[i] = ptLft;\n\t\t\t\t\t\t\t\tcursorPtsTop[i] = ptTop;\n\n\t\t\t\t\t\t\t\telSize(pt, ptWid, ptHgt, centered);\n\t\t\t\t\t\t\t\telColor(pt, ptFill, ptStroke);\n\t\t\t\t\t\t\t\telTrans(pt, ceil(ptLft), ceil(ptTop), plotWidCss, plotHgtCss);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// if only using single hover point (at cursorPts[0])\n\t\t\t// we have trigger styling at last visible series (once closestSeries is settled)\n\t\t\tif (cursorOnePt) {\n\t\t\t\t// some of this logic is similar to series focus below, since it matches the behavior by design\n\n\t\t\t\tlet p = focus.prox;\n\n\t\t\t\tlet focusChanged = focusedSeries == null ? closestDist <= p : (closestDist > p || closestSeries != focusedSeries);\n\n\t\t\t\tif (shouldSetLegend || focusChanged) {\n\t\t\t\t\tlet pt = cursorPts[0];\n\n\t\t\t\t\tif (pt != null) {\n\t\t\t\t\t\tcursorPtsLft[0] = _ptLft;\n\t\t\t\t\t\tcursorPtsTop[0] = _ptTop;\n\n\t\t\t\t\t\telSize(pt, _ptWid, _ptHgt, _centered);\n\t\t\t\t\t\telColor(pt, _ptFill, _ptStroke);\n\t\t\t\t\t\telTrans(pt, ceil(_ptLft), ceil(_ptTop), plotWidCss, plotHgtCss);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// nit: cursor.drag.setSelect is assumed always true\n\t\tif (select.show && dragging) {\n\t\t\tif (src != null) {\n\t\t\t\tlet [xKey, yKey] = syncOpts.scales;\n\t\t\t\tlet [matchXKeys, matchYKeys] = syncOpts.match;\n\t\t\t\tlet [xKeySrc, yKeySrc] = src.cursor.sync.scales;\n\n\t\t\t\t// match the dragX/dragY implicitness/explicitness of src\n\t\t\t\tlet sdrag = src.cursor.drag;\n\t\t\t\tdragX = sdrag._x;\n\t\t\t\tdragY = sdrag._y;\n\n\t\t\t\tif (dragX || dragY) {\n\t\t\t\t\tlet { left, top, width, height } = src.select;\n\n\t\t\t\t\tlet sori = src.scales[xKeySrc].ori;\n\t\t\t\t\tlet sPosToVal = src.posToVal;\n\n\t\t\t\t\tlet sOff, sDim, sc, a, b;\n\n\t\t\t\t\tlet matchingX = xKey != null && matchXKeys(xKey, xKeySrc);\n\t\t\t\t\tlet matchingY = yKey != null && matchYKeys(yKey, yKeySrc);\n\n\t\t\t\t\tif (matchingX && dragX) {\n\t\t\t\t\t\tif (sori == 0) {\n\t\t\t\t\t\t\tsOff = left;\n\t\t\t\t\t\t\tsDim = width;\n\t\t\t\t\t\t}\n\t\t\t\t\t\telse {\n\t\t\t\t\t\t\tsOff = top;\n\t\t\t\t\t\t\tsDim = height;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tsc = scales[xKey];\n\n\t\t\t\t\t\ta = valToPosX(sPosToVal(sOff, xKeySrc), sc, xDim, 0);\n\t\t\t\t\t\tb = valToPosX(sPosToVal(sOff + sDim, xKeySrc), sc, xDim, 0);\n\n\t\t\t\t\t\tsetSelX(min(a,b), abs(b-a));\n\t\t\t\t\t}\n\t\t\t\t\telse\n\t\t\t\t\t\tsetSelX(0, xDim);\n\n\t\t\t\t\tif (matchingY && dragY) {\n\t\t\t\t\t\tif (sori == 1) {\n\t\t\t\t\t\t\tsOff = left;\n\t\t\t\t\t\t\tsDim = width;\n\t\t\t\t\t\t}\n\t\t\t\t\t\telse {\n\t\t\t\t\t\t\tsOff = top;\n\t\t\t\t\t\t\tsDim = height;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tsc = scales[yKey];\n\n\t\t\t\t\t\ta = valToPosY(sPosToVal(sOff, yKeySrc), sc, yDim, 0);\n\t\t\t\t\t\tb = valToPosY(sPosToVal(sOff + sDim, yKeySrc), sc, yDim, 0);\n\n\t\t\t\t\t\tsetSelY(min(a,b), abs(b-a));\n\t\t\t\t\t}\n\t\t\t\t\telse\n\t\t\t\t\t\tsetSelY(0, yDim);\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t\thideSelect();\n\t\t\t}\n\t\t\telse {\n\t\t\t\tlet rawDX = abs(rawMouseLeft1 - rawMouseLeft0);\n\t\t\t\tlet rawDY = abs(rawMouseTop1 - rawMouseTop0);\n\n\t\t\t\tif (scaleX.ori == 1) {\n\t\t\t\t\tlet _rawDX = rawDX;\n\t\t\t\t\trawDX = rawDY;\n\t\t\t\t\trawDY = _rawDX;\n\t\t\t\t}\n\n\t\t\t\tdragX = drag.x && rawDX >= drag.dist;\n\t\t\t\tdragY = drag.y && rawDY >= drag.dist;\n\n\t\t\t\tlet uni = drag.uni;\n\n\t\t\t\tif (uni != null) {\n\t\t\t\t\t// only calc drag status if they pass the dist thresh\n\t\t\t\t\tif (dragX && dragY) {\n\t\t\t\t\t\tdragX = rawDX >= uni;\n\t\t\t\t\t\tdragY = rawDY >= uni;\n\n\t\t\t\t\t\t// force unidirectionality when both are under uni limit\n\t\t\t\t\t\tif (!dragX && !dragY) {\n\t\t\t\t\t\t\tif (rawDY > rawDX)\n\t\t\t\t\t\t\t\tdragY = true;\n\t\t\t\t\t\t\telse\n\t\t\t\t\t\t\t\tdragX = true;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\telse if (drag.x && drag.y && (dragX || dragY))\n\t\t\t\t\t// if omni with no uni then both dragX / dragY should be true if either is true\n\t\t\t\t\tdragX = dragY = true;\n\n\t\t\t\tlet p0, p1;\n\n\t\t\t\tif (dragX) {\n\t\t\t\t\tif (scaleX.ori == 0) {\n\t\t\t\t\t\tp0 = mouseLeft0;\n\t\t\t\t\t\tp1 = mouseLeft1;\n\t\t\t\t\t}\n\t\t\t\t\telse {\n\t\t\t\t\t\tp0 = mouseTop0;\n\t\t\t\t\t\tp1 = mouseTop1;\n\t\t\t\t\t}\n\n\t\t\t\t\tsetSelX(min(p0, p1), abs(p1 - p0));\n\n\t\t\t\t\tif (!dragY)\n\t\t\t\t\t\tsetSelY(0, yDim);\n\t\t\t\t}\n\n\t\t\t\tif (dragY) {\n\t\t\t\t\tif (scaleX.ori == 1) {\n\t\t\t\t\t\tp0 = mouseLeft0;\n\t\t\t\t\t\tp1 = mouseLeft1;\n\t\t\t\t\t}\n\t\t\t\t\telse {\n\t\t\t\t\t\tp0 = mouseTop0;\n\t\t\t\t\t\tp1 = mouseTop1;\n\t\t\t\t\t}\n\n\t\t\t\t\tsetSelY(min(p0, p1), abs(p1 - p0));\n\n\t\t\t\t\tif (!dragX)\n\t\t\t\t\t\tsetSelX(0, xDim);\n\t\t\t\t}\n\n\t\t\t\t// the drag didn't pass the dist requirement\n\t\t\t\tif (!dragX && !dragY) {\n\t\t\t\t\tsetSelX(0, 0);\n\t\t\t\t\tsetSelY(0, 0);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tdrag._x = dragX;\n\t\tdrag._y = dragY;\n\n\t\tif (src == null) {\n\t\t\tif (_pub) {\n\t\t\t\tif (syncKey != null) {\n\t\t\t\t\tlet [xSyncKey, ySyncKey] = syncOpts.scales;\n\n\t\t\t\t\tsyncOpts.values[0] = xSyncKey != null ? posToVal(scaleX.ori == 0 ? mouseLeft1 : mouseTop1, xSyncKey) : null;\n\t\t\t\t\tsyncOpts.values[1] = ySyncKey != null ? posToVal(scaleX.ori == 1 ? mouseLeft1 : mouseTop1, ySyncKey) : null;\n\t\t\t\t}\n\n\t\t\t\tpubSync(mousemove, self, mouseLeft1, mouseTop1, plotWidCss, plotHgtCss, idx);\n\t\t\t}\n\n\t\t\tif (cursorFocus) {\n\t\t\t\tlet shouldPub = _pub && syncOpts.setSeries;\n\t\t\t\tlet p = focus.prox;\n\n\t\t\t\tif (focusedSeries == null) {\n\t\t\t\t\tif (closestDist <= p)\n\t\t\t\t\t\tsetSeries(closestSeries, FOCUS_TRUE, true, shouldPub);\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\tif (closestDist > p)\n\t\t\t\t\t\tsetSeries(null, FOCUS_TRUE, true, shouldPub);\n\t\t\t\t\telse if (closestSeries != focusedSeries)\n\t\t\t\t\t\tsetSeries(closestSeries, FOCUS_TRUE, true, shouldPub);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif (shouldSetLegend) {\n\t\t\tlegend.idx = idx;\n\t\t\tsetLegend();\n\t\t}\n\n\t\t_fire !== false && fire(\"setCursor\");\n\t}\n\n\tlet rect = null;\n\n\tObject.defineProperty(self, 'rect', {\n\t\tget() {\n\t\t\tif (rect == null)\n\t\t\t\tsyncRect(false);\n\n\t\t\treturn rect;\n\t\t},\n\t});\n\n\tfunction syncRect(defer = false) {\n\t\tif (defer)\n\t\t\trect = null;\n\t\telse {\n\t\t\trect = over.getBoundingClientRect();\n\t\t\tfire(\"syncRect\", rect);\n\t\t}\n\t}\n\n\tfunction mouseMove(e, src, _l, _t, _w, _h, _i) {\n\t\tif (cursor._lock)\n\t\t\treturn;\n\n\t\t// Chrome on Windows has a bug which triggers a stray mousemove event after an initial mousedown event\n\t\t// when clicking into a plot as part of re-focusing the browser window.\n\t\t// we gotta ignore it to avoid triggering a phantom drag / setSelect\n\t\t// However, on touch-only devices Chrome-based browsers trigger a 0-distance mousemove before mousedown\n\t\t// so we don't ignore it when mousedown has set the dragging flag\n\t\tif (dragging && e != null && e.movementX == 0 && e.movementY == 0)\n\t\t\treturn;\n\n\t\tcacheMouse(e, src, _l, _t, _w, _h, _i, false, e != null);\n\n\t\tif (e != null)\n\t\t\tupdateCursor(null, true, true);\n\t\telse\n\t\t\tupdateCursor(src, true, false);\n\t}\n\n\tfunction cacheMouse(e, src, _l, _t, _w, _h, _i, initial, snap) {\n\t\tif (rect == null)\n\t\t\tsyncRect(false);\n\n\t\tsetCursorEvent(e);\n\n\t\tif (e != null) {\n\t\t\t_l = e.clientX - rect.left;\n\t\t\t_t = e.clientY - rect.top;\n\t\t}\n\t\telse {\n\t\t\tif (_l < 0 || _t < 0) {\n\t\t\t\tmouseLeft1 = -10;\n\t\t\t\tmouseTop1 = -10;\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tlet [xKey, yKey] = syncOpts.scales;\n\n\t\t\tlet syncOptsSrc = src.cursor.sync;\n\t\t\tlet [xValSrc, yValSrc] = syncOptsSrc.values;\n\t\t\tlet [xKeySrc, yKeySrc] = syncOptsSrc.scales;\n\t\t\tlet [matchXKeys, matchYKeys] = syncOpts.match;\n\n\t\t\tlet rotSrc = src.axes[0].side % 2 == 1;\n\n\t\t\tlet xDim = scaleX.ori == 0 ? plotWidCss : plotHgtCss,\n\t\t\t\tyDim = scaleX.ori == 1 ? plotWidCss : plotHgtCss,\n\t\t\t\t_xDim = rotSrc ? _h : _w,\n\t\t\t\t_yDim = rotSrc ? _w : _h,\n\t\t\t\t_xPos = rotSrc ? _t : _l,\n\t\t\t\t_yPos = rotSrc ? _l : _t;\n\n\t\t\tif (xKeySrc != null)\n\t\t\t\t_l = matchXKeys(xKey, xKeySrc) ? getPos(xValSrc, scales[xKey], xDim, 0) : -10;\n\t\t\telse\n\t\t\t\t_l = xDim * (_xPos/_xDim);\n\n\t\t\tif (yKeySrc != null)\n\t\t\t\t_t = matchYKeys(yKey, yKeySrc) ? getPos(yValSrc, scales[yKey], yDim, 0) : -10;\n\t\t\telse\n\t\t\t\t_t = yDim * (_yPos/_yDim);\n\n\t\t\tif (scaleX.ori == 1) {\n\t\t\t\tlet __l = _l;\n\t\t\t\t_l = _t;\n\t\t\t\t_t = __l;\n\t\t\t}\n\t\t}\n\n\t\tif (snap && (src == null || src.cursor.event.type == mousemove)) {\n\t\t\tif (_l <= 1 || _l >= plotWidCss - 1)\n\t\t\t\t_l = incrRound(_l, plotWidCss);\n\n\t\t\tif (_t <= 1 || _t >= plotHgtCss - 1)\n\t\t\t\t_t = incrRound(_t, plotHgtCss);\n\t\t}\n\n\t\tif (initial) {\n\t\t\trawMouseLeft0 = _l;\n\t\t\trawMouseTop0 = _t;\n\n\t\t\t[mouseLeft0, mouseTop0] = cursor.move(self, _l, _t);\n\t\t}\n\t\telse {\n\t\t\tmouseLeft1 = _l;\n\t\t\tmouseTop1 = _t;\n\t\t}\n\t}\n\n\tconst _hideProps = {\n\t\twidth: 0,\n\t\theight: 0,\n\t\tleft: 0,\n\t\ttop: 0,\n\t};\n\n\tfunction hideSelect() {\n\t\tsetSelect(_hideProps, false);\n\t}\n\n\tlet downSelectLeft;\n\tlet downSelectTop;\n\tlet downSelectWidth;\n\tlet downSelectHeight;\n\n\tfunction mouseDown(e, src, _l, _t, _w, _h, _i) {\n\t\tdragging = true;\n\t\tdragX = dragY = drag._x = drag._y = false;\n\n\t\tcacheMouse(e, src, _l, _t, _w, _h, _i, true, false);\n\n\t\tif (e != null) {\n\t\t\tonMouse(mouseup, doc, mouseUp, false);\n\t\t\tpubSync(mousedown, self, mouseLeft0, mouseTop0, plotWidCss, plotHgtCss, null);\n\t\t}\n\n\t\tlet { left, top, width, height } = select;\n\n\t\tdownSelectLeft = left;\n\t\tdownSelectTop = top;\n\t\tdownSelectWidth = width;\n\t\tdownSelectHeight = height;\n\n\t//\thideSelect();\n\t}\n\n\tfunction mouseUp(e, src, _l, _t, _w, _h, _i) {\n\t\tdragging = drag._x = drag._y = false;\n\n\t\tcacheMouse(e, src, _l, _t, _w, _h, _i, false, true);\n\n\t\tlet { left, top, width, height } = select;\n\n\t\tlet hasSelect = width > 0 || height > 0;\n\t\tlet chgSelect = (\n\t\t\tdownSelectLeft != left ||\n\t\t\tdownSelectTop != top ||\n\t\t\tdownSelectWidth != width ||\n\t\t\tdownSelectHeight != height\n\t\t);\n\n\t\thasSelect && chgSelect && setSelect(select);\n\n\t\tif (drag.setScale && hasSelect && chgSelect) {\n\t\t//\tif (syncKey != null) {\n\t\t//\t\tdragX = drag.x;\n\t\t//\t\tdragY = drag.y;\n\t\t//\t}\n\n\t\t\tlet xOff = left,\n\t\t\t\txDim = width,\n\t\t\t\tyOff = top,\n\t\t\t\tyDim = height;\n\n\t\t\tif (scaleX.ori == 1) {\n\t\t\t\txOff = top,\n\t\t\t\txDim = height,\n\t\t\t\tyOff = left,\n\t\t\t\tyDim = width;\n\t\t\t}\n\n\t\t\tif (dragX) {\n\t\t\t\t_setScale(xScaleKey,\n\t\t\t\t\tposToVal(xOff, xScaleKey),\n\t\t\t\t\tposToVal(xOff + xDim, xScaleKey)\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tif (dragY) {\n\t\t\t\tfor (let k in scales) {\n\t\t\t\t\tlet sc = scales[k];\n\n\t\t\t\t\tif (k != xScaleKey && sc.from == null && sc.min != inf) {\n\t\t\t\t\t\t_setScale(k,\n\t\t\t\t\t\t\tposToVal(yOff + yDim, k),\n\t\t\t\t\t\t\tposToVal(yOff, k)\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\thideSelect();\n\t\t}\n\t\telse if (cursor.lock) {\n\t\t\tcursor._lock = !cursor._lock;\n\t\t\tupdateCursor(src, true, e != null);\n\t\t}\n\n\t\tif (e != null) {\n\t\t\toffMouse(mouseup, doc);\n\t\t\tpubSync(mouseup, self, mouseLeft1, mouseTop1, plotWidCss, plotHgtCss, null);\n\t\t}\n\t}\n\n\tfunction mouseLeave(e, src, _l, _t, _w, _h, _i) {\n\t\tif (cursor._lock)\n\t\t\treturn;\n\n\t\tsetCursorEvent(e);\n\n\t\tlet _dragging = dragging;\n\n\t\tif (dragging) {\n\t\t\t// handle case when mousemove aren't fired all the way to edges by browser\n\t\t\tlet snapH = true;\n\t\t\tlet snapV = true;\n\t\t\tlet snapProx = 10;\n\n\t\t\tlet dragH, dragV;\n\n\t\t\tif (scaleX.ori == 0) {\n\t\t\t\tdragH = dragX;\n\t\t\t\tdragV = dragY;\n\t\t\t}\n\t\t\telse {\n\t\t\t\tdragH = dragY;\n\t\t\t\tdragV = dragX;\n\t\t\t}\n\n\t\t\tif (dragH && dragV) {\n\t\t\t\t// maybe omni corner snap\n\t\t\t\tsnapH = mouseLeft1 <= snapProx || mouseLeft1 >= plotWidCss - snapProx;\n\t\t\t\tsnapV = mouseTop1 <= snapProx || mouseTop1 >= plotHgtCss - snapProx;\n\t\t\t}\n\n\t\t\tif (dragH && snapH)\n\t\t\t\tmouseLeft1 = mouseLeft1 < mouseLeft0 ? 0 : plotWidCss;\n\n\t\t\tif (dragV && snapV)\n\t\t\t\tmouseTop1 = mouseTop1 < mouseTop0 ? 0 : plotHgtCss;\n\n\t\t\tupdateCursor(null, true, true);\n\n\t\t\tdragging = false;\n\t\t}\n\n\t\tmouseLeft1 = -10;\n\t\tmouseTop1 = -10;\n\n\t\tactiveIdxs.fill(null);\n\n\t\t// passing a non-null timestamp to force sync/mousemove event\n\t\tupdateCursor(null, true, true);\n\n\t\tif (_dragging)\n\t\t\tdragging = _dragging;\n\t}\n\n\tfunction dblClick(e, src, _l, _t, _w, _h, _i) {\n\t\tif (cursor._lock)\n\t\t\treturn;\n\n\t\tsetCursorEvent(e);\n\n\t\tautoScaleX();\n\n\t\thideSelect();\n\n\t\tif (e != null)\n\t\t\tpubSync(dblclick, self, mouseLeft1, mouseTop1, plotWidCss, plotHgtCss, null);\n\t}\n\n\tfunction syncPxRatio() {\n\t\taxes.forEach(syncFontSize);\n\t\t_setSize(self.width, self.height, true);\n\t}\n\n\ton(dppxchange, win, syncPxRatio);\n\n\t// internal pub/sub\n\tconst events = {};\n\n\tevents.mousedown = mouseDown;\n\tevents.mousemove = mouseMove;\n\tevents.mouseup = mouseUp;\n\tevents.dblclick = dblClick;\n\tevents[\"setSeries\"] = (e, src, idx, opts) => {\n\t\tlet seriesIdxMatcher = syncOpts.match[2];\n\t\tidx = seriesIdxMatcher(self, src, idx);\n\t\tidx != -1 && setSeries(idx, opts, true, false);\n\t};\n\n\tif (showCursor) {\n\t\tonMouse(mousedown, over, mouseDown);\n\t\tonMouse(mousemove, over, mouseMove);\n\t\tonMouse(mouseenter, over, e => {\n\t\t\tsetCursorEvent(e);\n\t\t\tsyncRect(false);\n\t\t});\n\t\tonMouse(mouseleave, over, mouseLeave);\n\n\t\tonMouse(dblclick, over, dblClick);\n\n\t\tcursorPlots.add(self);\n\n\t\tself.syncRect = syncRect;\n\t}\n\n\t// external on/off\n\tconst hooks = self.hooks = opts.hooks || {};\n\n\tfunction fire(evName, a1, a2) {\n\t\tif (deferHooks)\n\t\t\thooksQueue.push([evName, a1, a2]);\n\t\telse {\n\t\t\tif (evName in hooks) {\n\t\t\t\thooks[evName].forEach(fn => {\n\t\t\t\t\tfn.call(null, self, a1, a2);\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\t}\n\n\t(opts.plugins || []).forEach(p => {\n\t\tfor (let evName in p.hooks)\n\t\t\thooks[evName] = (hooks[evName] || []).concat(p.hooks[evName]);\n\t});\n\n\tconst seriesIdxMatcher = (self, src, srcSeriesIdx) => srcSeriesIdx;\n\n\tconst syncOpts = assign({\n\t\tkey: null,\n\t\tsetSeries: false,\n\t\tfilters: {\n\t\t\tpub: retTrue,\n\t\t\tsub: retTrue,\n\t\t},\n\t\tscales: [xScaleKey, series[1] ? series[1].scale : null],\n\t\tmatch: [retEq, retEq, seriesIdxMatcher],\n\t\tvalues: [null, null],\n\t}, cursor.sync);\n\n\tif (syncOpts.match.length == 2)\n\t\tsyncOpts.match.push(seriesIdxMatcher);\n\n\tcursor.sync = syncOpts;\n\n\tconst syncKey = syncOpts.key;\n\n\tconst sync = _sync(syncKey);\n\n\tfunction pubSync(type, src, x, y, w, h, i) {\n\t\tif (syncOpts.filters.pub(type, src, x, y, w, h, i))\n\t\t\tsync.pub(type, src, x, y, w, h, i);\n\t}\n\n\tsync.sub(self);\n\n\tfunction pub(type, src, x, y, w, h, i) {\n\t\tif (syncOpts.filters.sub(type, src, x, y, w, h, i))\n\t\t\tevents[type](null, src, x, y, w, h, i);\n\t}\n\n\tself.pub = pub;\n\n\tfunction destroy() {\n\t\tsync.unsub(self);\n\t\tcursorPlots.delete(self);\n\t\tmouseListeners.clear();\n\t\toff(dppxchange, win, syncPxRatio);\n\t\troot.remove();\n\t\tlegendTable?.remove(); // in case mounted outside of root\n\t\tfire(\"destroy\");\n\t}\n\n\tself.destroy = destroy;\n\n\tfunction _init() {\n\t\tfire(\"init\", opts, data);\n\n\t\tsetData(data || opts.data, false);\n\n\t\tif (pendScales[xScaleKey])\n\t\t\tsetScale(xScaleKey, pendScales[xScaleKey]);\n\t\telse\n\t\t\tautoScaleX();\n\n\t\tshouldSetSelect = select.show && (select.width > 0 || select.height > 0);\n\t\tshouldSetCursor = shouldSetLegend = true;\n\n\t\t_setSize(opts.width, opts.height);\n\t}\n\n\tseries.forEach(initSeries);\n\n\taxes.forEach(initAxis);\n\n\tif (then) {\n\t\tif (then instanceof HTMLElement) {\n\t\t\tthen.appendChild(root);\n\t\t\t_init();\n\t\t}\n\t\telse\n\t\t\tthen(self, _init);\n\t}\n\telse\n\t\t_init();\n\n\treturn self;\n}\n\nuPlot.assign = assign;\nuPlot.fmtNum = fmtNum;\nuPlot.rangeNum = rangeNum;\nuPlot.rangeLog = rangeLog;\nuPlot.rangeAsinh = rangeAsinh;\nuPlot.orient = orient;\nuPlot.pxRatio = pxRatio;\n\n{\n\tuPlot.join = join;\n}\n\n{\n\tuPlot.fmtDate = fmtDate;\n\tuPlot.tzDate = tzDate;\n}\n\nuPlot.sync = _sync;\n\n{\n\tuPlot.addGap = addGap;\n\tuPlot.clipGaps = clipGaps;\n\n\tlet paths = uPlot.paths = {\n\t\tpoints,\n\t};\n\n\t(paths.linear = linear);\n\t(paths.stepped = stepped);\n\t(paths.bars = bars);\n\t(paths.spline = monotoneCubic);\n}\n\nexport { uPlot as default };\n"], + "mappings": "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", + "names": ["pre", "UPLOT", "ORI_HZ", "ORI_VT", "TITLE", "WRAP", "UNDER", "OVER", "AXIS", "OFF", "SELECT", "CURSOR_X", "CURSOR_Y", "CURSOR_PT", "LEGEND", "LEGEND_LIVE", "LEGEND_INLINE", "LEGEND_SERIES", "LEGEND_MARKER", "LEGEND_LABEL", "LEGEND_VALUE", "WIDTH", "HEIGHT", "TOP", "BOTTOM", "LEFT", "RIGHT", "hexBlack", "transparent", "mousemove", "mousedown", "mouseup", "mouseenter", "mouseleave", "dblclick", "resize", "scroll", "change", "dppxchange", "LEGEND_DISP", "domEnv", "doc", "win", "nav", "pxRatio", "query", "setPxRatio", "_pxRatio", "off", "on", "addClass", "el", "c", "cl", "remClass", "setStylePx", "name", "value", "placeTag", "tag", "cls", "targ", "refEl", "placeDiv", "xformCache", "elTrans", "xPos", "yPos", "xMax", "yMax", "xform", "xformOld", "colorCache", "elColor", "background", "borderColor", "newColor", "oldColor", "sizeCache", "elSize", "newWid", "newHgt", "centered", "newSize", "oldSize", "evOpts", "evOpts2", "ev", "cb", "capt", "closestIdx", "num", "arr", "lo", "hi", "mid", "bitwise", "floor", "makeIndexOfs", "predicate", "data", "_i0", "_i1", "i0", "i1", "i", "notNullish", "v", "isPositive", "nonNullIdxs", "positiveIdxs", "getMinMax", "sorted", "log", "getEdgeIdxs", "_min", "_max", "inf", "rangeLog", "min", "max", "base", "fullMags", "minSign", "sign", "maxSign", "logFn", "log10", "log2", "growMinAbs", "ceil", "growMaxAbs", "minExp", "abs", "maxExp", "minIncr", "pow", "maxIncr", "roundDec", "incrRoundDn", "incrRoundUp", "rangeAsinh", "minMax", "rangePad", "autoRangePart", "_eqRangePart", "_eqRange", "rangeNum", "mult", "extra", "isObj", "_rangeNum", "ifNull", "lh", "rh", "hasData", "idx0", "idx1", "cfg", "cmin", "cmax", "padMin", "padMax", "hardMin", "hardMax", "softMin", "softMax", "softMinMode", "softMaxMode", "delta", "deltaMag", "scalarMax", "scalarMag", "scalarMagDelta", "nonZeroDelta", "mag", "_padMin", "_newMin", "_softMin", "minLim", "_padMax", "_newMax", "_softMax", "maxLim", "numFormatter", "fmtNum", "val", "M", "PI", "round", "sinh", "linthresh", "asinh", "numIntDigits", "x", "clamp", "isFn", "fnOrSelf", "noop", "retArg0", "_0", "retArg1", "_1", "retNull", "_", "retTrue", "retEq", "a", "b", "regex6", "fixFloat", "isInt", "fixedDec", "str", "match", "len", "exp", "incrRound", "incr", "dec", "p", "guessDec", "genIncrs", "mults", "incrs", "multDec", "expa", "_incr", "EMPTY_OBJ", "EMPTY_ARR", "nullNullTuple", "isArr", "isUndef", "isStr", "is", "fastIsObj", "TypedArray", "__proto__", "copy", "o", "_isObj", "out", "k", "assign", "args", "src", "key", "NULL_REMOVE", "NULL_RETAIN", "NULL_EXPAND", "nullExpand", "yVals", "nullIdxs", "alignedLen", "xi", "lastNullIdx", "nullIdx", "join", "tables", "nullModes", "allHeadersSame", "table", "isAsc", "sortCols", "xVals", "ti", "xs", "xIdxs", "t", "si", "ys", "nullMode", "yVal", "alignedIdx", "microTask", "fn", "head", "rlen", "idxs", "table2", "row", "row2", "j", "vals0", "len0", "vals1", "vals", "samples", "firstIdx", "lastIdx", "stride", "prevVal", "months", "days", "slice3", "days3", "months3", "engNames", "zeroPad2", "int", "zeroPad3", "subs", "d", "names", "h", "fmtDate", "tpl", "parts", "R", "m", "localTz", "tzDate", "date", "tz", "date2", "onlyWhole", "allMults", "decIncrs", "oneIncrs", "wholeIncrs", "numIncrs", "NL", "yyyy", "NLyyyy", "md", "NLmd", "NLmdyy", "aa", "hmm", "hmmaa", "NLhmmaa", "ss", "genTimeStuffs", "ms", "s", "mo", "y", "timeIncrs", "_timeAxisStamps", "timeAxisSplits", "self", "axisIdx", "scaleMin", "scaleMax", "foundIncr", "foundSpace", "splits", "isYr", "isMo", "minDate", "minDateTs", "minMin", "mkDate", "minMinTs", "moIncr", "yrIncr", "split", "splitDate", "baseYear", "baseMonth", "next", "offs", "incr0", "tzOffset", "date0", "prevHour", "incrHours", "minSpace", "pctSpace", "expectedHour", "dstShift", "prevSplit", "timeIncrsMs", "_timeAxisStampsMs", "timeAxisSplitsMs", "timeIncrsS", "_timeAxisStampsS", "timeAxisSplitsS", "timeAxisStamps", "stampCfg", "timeAxisVals", "stamps", "prevYear", "prevMnth", "prevDate", "prevMins", "prevSecs", "newYear", "newMnth", "newDate", "newHour", "newMins", "newSecs", "stamp", "timeAxisVal", "dateTpl", "timeSeriesStamp", "_timeSeriesStamp", "timeSeriesVal", "seriesIdx", "dataIdx", "legendStroke", "legendFill", "legendOpts", "cursorPointShow", "pt", "size", "mar", "width", "cursorPointFill", "sp", "cursorPointStroke", "cursorPointSize", "moveTuple", "cursorMove", "mouseLeft1", "mouseTop1", "filtBtn0", "handle", "onlyTarg", "e", "filtTarg", "cursorOpts", "valPos", "curPos", "axisLines", "grid", "ticks", "border", "font", "labelFont", "lineGap", "xAxisOpts", "numSeriesLabel", "timeSeriesLabel", "xSeriesOpts", "numAxisVals", "numAxisSplits", "forceMin", "numDec", "logAxisSplits", "logBase", "nextMagIncr", "asinhAxisSplits", "posSplits", "zero", "RE_ALL", "RE_12357", "RE_125", "RE_1", "_filt", "distr", "re", "keepMod", "log10AxisValsFilt", "axis", "scaleKey", "sc", "valToPos", "_10", "magSpace", "log2AxisValsFilt", "numSeriesVal", "yAxisOpts", "ptDia", "dia", "seriesPointsShow", "scale", "xData", "p0", "p1", "dim", "maxPts", "facet", "gaps", "nullGaps", "xySeriesOpts", "ySeriesOpts", "clampScale", "xScaleOpts", "yScaleOpts", "syncs", "_sync", "opts", "plot", "type", "w", "BAND_CLIP_FILL", "BAND_CLIP_STROKE", "orient", "u", "mode", "series", "scales", "bbox", "dx", "dy", "sx", "sy", "l", "H", "V", "moveToH", "lineToH", "rectH", "arcH", "bezierCurveToH", "moveToV", "lineToV", "rectV", "arcV", "bezierCurveToV", "bandFillClipDirs", "fillDir", "clipDirs", "bands", "seriesFillTo", "dataMin", "dataMax", "bandFillDir", "clipBandLine", "strokePath", "clipDir", "dataX", "dataY", "scaleX", "scaleY", "valToPosX", "valToPosY", "xOff", "yOff", "xDim", "yDim", "pxRound", "dir", "lineTo", "frIdx", "toIdx", "x0", "y0", "x1", "yLimit", "clip", "clipGaps", "ori", "plotLft", "plotTop", "plotWid", "plotHgt", "rect", "prevGapEnd", "g", "maxStrokeWidth", "addGap", "fromX", "toX", "prevGap", "findGaps", "pixelForX", "align", "fr", "to", "frPx", "toPx", "fri2", "toi2", "pxRoundGen", "pxAlign", "moveTo", "arcTo", "y1", "x2", "y2", "r", "endRad", "baseRad", "startAngle", "endAngle", "bp1x", "bp1y", "bp2x", "bp2y", "p2x", "p2y", "points", "filtIdxs", "arc", "rad", "fill", "lft", "top", "wid", "hgt", "drawPoint", "pi", "_drawAcc", "stroke", "accX", "minY", "maxY", "inY", "outY", "drawAccH", "drawAccV", "linear", "alignGaps", "pixelForY", "drawAcc", "_paths", "hasGap", "xForPixel", "pos", "drawnAtX", "idx0px", "idx1px", "nextAccXVal", "xVal", "bandClipDir", "fillToVal", "fillToY", "frX", "stepped", "ascDesc", "extend", "left", "prevYPos", "firstXPos", "prevXPos", "firstXPosExt", "yVal1", "prevXPosExt", "fillTo", "halfStroke", "startsOffset", "endsOffset", "findColWidth", "colWid", "prevIdx", "minDelta", "bars", "_extraGap", "ro", "radiusFn", "gapFactor", "_maxWidth", "_minWidth", "disp", "_each", "dispFills", "dispStrokes", "_align", "extraGap", "maxWidth", "minWidth", "valRadius", "baseRadius", "_dirX", "each", "band", "xShift", "barWid", "fullGap", "strokeWidth", "multiPath", "fillColors", "fillPaths", "strokeColors", "strokePaths", "color", "pct", "sizes", "insetStroke", "rawBarWid", "dataY0", "radVal", "radBase", "yVal0", "btm", "barHgt", "rv", "rb", "splineInterp", "interp", "bezierCurveTo", "xCoords", "yCoords", "monotoneCubic", "_monotoneCubic", "n", "path", "ds", "dys", "dxs", "cursorPlots", "invalidateRects", "uPlot", "linearPath", "pointsPath", "setDefaults", "xo", "yo", "initY", "setDefault", "setDefaults2", "xyo", "snapNumX", "snapTimeX", "snapNumY", "snapLogY", "snapLogX", "snapAsinhY", "snapAsinhX", "findIncr", "minVal", "maxVal", "intDigits", "incrIdx", "pxRatioFont", "fontSize", "fontSizeCss", "syncFontSize", "f", "then", "getHPos", "getVPos", "getPos", "ready", "root", "title", "can", "ctx", "wrap", "over", "mouseLeft0", "mouseTop0", "drag", "under", "axes", "xScaleKey", "drawOrderMap", "drawAxesGrid", "drawSeries", "drawOrder", "initValToPct", "getVal", "_val", "initScale", "scaleOpts", "isTime", "rn", "rangeIsArr", "xScaleDistr", "pendScales", "_tzDate", "ts", "_fmtDate", "_timeAxisSplits", "_timeAxisVals", "_timeSeriesVal", "activeIdxs", "legend", "cursor", "showLegend", "showCursor", "markers", "legendTable", "legendHead", "legendBody", "legendRows", "legendCells", "legendCols", "multiValLegend", "NULL_LEGEND_VALUES", "getMultiVals", "son", "soff", "initLegendRow", "cells", "label", "indic", "text", "onMouse", "setCursorEvent", "isolate", "setSeries", "syncOpts", "cursorFocus", "FOCUS_TRUE", "mouseListeners", "targListeners", "listener", "offMouse", "fullWidCss", "fullHgtCss", "plotWidCss", "plotHgtCss", "plotLftCss", "plotTopCss", "_plotLftCss", "_plotTopCss", "_plotWidCss", "_plotHgtCss", "shouldSetScales", "shouldSetSize", "shouldConvergeSize", "shouldSetCursor", "shouldSetSelect", "shouldSetLegend", "_setSize", "height", "force", "calcSize", "resetYSeries", "commit", "calcPlotRect", "calcAxesRects", "bb", "CYCLE_LIMIT", "convergeSize", "converged", "cycleNum", "axesConverged", "axesCalc", "paddingConverged", "paddingCalc", "setSize", "hasTopAxis", "hasBtmAxis", "hasRgtAxis", "hasLftAxis", "side", "_size", "isVt", "labelSize", "fullSize", "sidesWithAxes", "_padding", "off1", "off2", "off3", "off0", "incrOffset", "hov", "skip", "prox", "bias", "cursorIdx", "valAtPosX", "idx2", "_prox", "withProx", "cursorLft", "xValues", "yValues", "nonNullLft", "nonNullRgt", "lftPos", "rgtPos", "lftDelta", "rgtDelta", "focus", "cursorOnePt", "cursorPts", "cursorPtsLft", "cursorPtsTop", "initCursorPt", "initSeries", "sv", "_ptDia", "rowCells", "fire", "addSeries", "delSeries", "tr", "initAxis", "av", "autoPadSide", "padding", "dataLen", "data0", "viaAutoScaleX", "setData", "_data", "_resetScales", "scaleData", "_data0", "xsc", "autoScaleX", "_setScale", "ctxStroke", "ctxFill", "ctxWidth", "ctxDash", "ctxJoin", "ctxCap", "ctxFont", "ctxAlign", "ctxBaseline", "ctxAlpha", "setCtxStyle", "dash", "cap", "setFontStyle", "baseline", "accScale", "wsc", "psc", "AUTOSCALE", "setScales", "wipScales", "xFacet", "yFacet", "yScaleKey", "yData", "wscx", "wscy", "changed", "anyChanged", "getOuterIdxs", "ydata", "shouldAlpha", "cacheStrokeFill", "_ctxAlpha", "_idxs", "drawPath", "_gaps", "show", "_points", "gapsClip", "flags", "strokeStyle", "fillStyle", "boundsClip", "offset", "_pxAlign", "strokeFill", "fillStroke", "lineWidth", "lineDash", "lineCap", "fillPath", "didStrokeFill", "bi", "lowerEdge", "lowerData", "bandClip", "gapsClip2", "_fillStyle", "CLIP_FILL_STROKE", "doFill", "doStroke", "getIncrSpace", "fullDim", "incrSpace", "drawOrthoLines", "filts", "pos0", "pos1", "_space", "_splits", "values", "_p", "shiftDir", "shiftAmt", "baseLpos", "_label", "plotDim", "plotOff", "_tickSize", "tickSize", "axisGap", "angle", "basePos", "finalPos", "textAlign", "textBaseline", "lineHeight", "canOffs", "_values", "_parts", "queuedCommit", "deferHooks", "hooksQueue", "flushHooks", "_commit", "batch", "_deferHooks", "_el", "_show", "_pos", "posOffset", "syncRect", "pctWid", "pctHgt", "vCursor", "hCursor", "select", "prop", "_hideProps", "selectDiv", "setSelect", "updateCursor", "setLegend", "rebuildPaths", "recalcAxes", "setScale", "xCursor", "yCursor", "rawMouseLeft0", "rawMouseTop0", "rawMouseLeft1", "rawMouseTop1", "dragging", "dragX", "dragY", "_fire", "toggleDOM", "_pub", "setFocus", "pubSync", "setBand", "addBand", "delBand", "setAlpha", "closestDist", "closestSeries", "focusedSeries", "allFocused", "_setAlpha", "i2", "isFocused", "posToVal", "closestIdxFromXpos", "setSelH", "setSelV", "setSelX", "setSelY", "syncLegend", "didx", "sidx", "setLegendValues", "idx", "noDataInRange", "mouseXPos", "_ptLft", "_ptTop", "_ptWid", "_ptHgt", "_centered", "_ptFill", "_ptStroke", "yVal2", "xPos2", "mouseYPos", "dist", "mouseYVal", "seriesYValSign", "mouseYValSign", "hPos", "vPos", "ptWid", "ptHgt", "ptLft", "ptTop", "ptStroke", "ptFill", "getBBox", "focusChanged", "xKey", "yKey", "matchXKeys", "matchYKeys", "xKeySrc", "yKeySrc", "sdrag", "sori", "sPosToVal", "sOff", "sDim", "matchingX", "matchingY", "hideSelect", "rawDX", "rawDY", "_rawDX", "uni", "syncKey", "xSyncKey", "ySyncKey", "shouldPub", "defer", "mouseMove", "_l", "_t", "_w", "_h", "_i", "cacheMouse", "initial", "snap", "syncOptsSrc", "xValSrc", "yValSrc", "rotSrc", "_xDim", "_yDim", "_xPos", "_yPos", "__l", "downSelectLeft", "downSelectTop", "downSelectWidth", "downSelectHeight", "mouseDown", "mouseUp", "hasSelect", "chgSelect", "mouseLeave", "_dragging", "snapH", "snapV", "snapProx", "dragH", "dragV", "dblClick", "syncPxRatio", "events", "seriesIdxMatcher", "hooks", "evName", "a1", "a2", "srcSeriesIdx", "sync", "pub", "destroy", "_init", "paths"] +} diff --git a/suites-experimental/tools-gps-tracks/material_symbols_rounded.woff2 b/suites-experimental/tools-gps-tracks/material_symbols_rounded.woff2 new file mode 100644 index 000000000..3dbcf04e5 Binary files /dev/null and b/suites-experimental/tools-gps-tracks/material_symbols_rounded.woff2 differ diff --git a/suites-experimental/tools-gps-tracks/package-lock.json b/suites-experimental/tools-gps-tracks/package-lock.json new file mode 100644 index 000000000..eb4640e94 --- /dev/null +++ b/suites-experimental/tools-gps-tracks/package-lock.json @@ -0,0 +1,560 @@ +{ + "name": "tools-gps-tracks", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "tools-gps-tracks", + "version": "1.0.0", + "dependencies": { + "fit-file-parser": "2.3.3", + "uplot": "1.6.32" + }, + "devDependencies": { + "esbuild": "0.20.2", + "prettier": "3.8.1", + "typescript": "5.9.3" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.20.2.tgz", + "integrity": "sha512-D+EBOJHXdNZcLJRBkhENNG8Wji2kgc9AZ9KiPr1JuZjsNtyHzrsfLRrY0tk2H2aoFu6RANO1y1iPPUCDYWkb5g==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.20.2.tgz", + "integrity": "sha512-t98Ra6pw2VaDhqNWO2Oph2LXbz/EJcnLmKLGBJwEwXX/JAN83Fym1rU8l0JUWK6HkIbWONCSSatf4sf2NBRx/w==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.20.2.tgz", + "integrity": "sha512-mRzjLacRtl/tWU0SvD8lUEwb61yP9cqQo6noDZP/O8VkwafSYwZ4yWy24kan8jE/IMERpYncRt2dw438LP3Xmg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.20.2.tgz", + "integrity": "sha512-btzExgV+/lMGDDa194CcUQm53ncxzeBrWJcncOBxuC6ndBkKxnHdFJn86mCIgTELsooUmwUm9FkhSp5HYu00Rg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.20.2.tgz", + "integrity": "sha512-4J6IRT+10J3aJH3l1yzEg9y3wkTDgDk7TSDFX+wKFiWjqWp/iCfLIYzGyasx9l0SAFPT1HwSCR+0w/h1ES/MjA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.20.2.tgz", + "integrity": "sha512-tBcXp9KNphnNH0dfhv8KYkZhjc+H3XBkF5DKtswJblV7KlT9EI2+jeA8DgBjp908WEuYll6pF+UStUCfEpdysA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.20.2.tgz", + "integrity": "sha512-d3qI41G4SuLiCGCFGUrKsSeTXyWG6yem1KcGZVS+3FYlYhtNoNgYrWcvkOoaqMhwXSMrZRl69ArHsGJ9mYdbbw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.20.2.tgz", + "integrity": "sha512-d+DipyvHRuqEeM5zDivKV1KuXn9WeRX6vqSqIDgwIfPQtwMP4jaDsQsDncjTDDsExT4lR/91OLjRo8bmC1e+Cw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.20.2.tgz", + "integrity": "sha512-VhLPeR8HTMPccbuWWcEUD1Az68TqaTYyj6nfE4QByZIQEQVWBB8vup8PpR7y1QHL3CpcF6xd5WVBU/+SBEvGTg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.20.2.tgz", + "integrity": "sha512-9pb6rBjGvTFNira2FLIWqDk/uaf42sSyLE8j1rnUpuzsODBq7FvpwHYZxQ/It/8b+QOS1RYfqgGFNLRI+qlq2A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.20.2.tgz", + "integrity": "sha512-o10utieEkNPFDZFQm9CoP7Tvb33UutoJqg3qKf1PWVeeJhJw0Q347PxMvBgVVFgouYLGIhFYG0UGdBumROyiig==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.20.2.tgz", + "integrity": "sha512-PR7sp6R/UC4CFVomVINKJ80pMFlfDfMQMYynX7t1tNTeivQ6XdX5r2XovMmha/VjR1YN/HgHWsVcTRIMkymrgQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.20.2.tgz", + "integrity": "sha512-4BlTqeutE/KnOiTG5Y6Sb/Hw6hsBOZapOVF6njAESHInhlQAghVVZL1ZpIctBOoTFbQyGW+LsVYZ8lSSB3wkjA==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.20.2.tgz", + "integrity": "sha512-rD3KsaDprDcfajSKdn25ooz5J5/fWBylaaXkuotBDGnMnDP1Uv5DLAN/45qfnf3JDYyJv/ytGHQaziHUdyzaAg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.20.2.tgz", + "integrity": "sha512-snwmBKacKmwTMmhLlz/3aH1Q9T8v45bKYGE3j26TsaOVtjIag4wLfWSiZykXzXuE1kbCE+zJRmwp+ZbIHinnVg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.20.2.tgz", + "integrity": "sha512-wcWISOobRWNm3cezm5HOZcYz1sKoHLd8VL1dl309DiixxVFoFe/o8HnwuIwn6sXre88Nwj+VwZUvJf4AFxkyrQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.20.2.tgz", + "integrity": "sha512-1MdwI6OOTsfQfek8sLwgyjOXAu+wKhLEoaOLTjbijk6E2WONYpH9ZU2mNtR+lZ2B4uwr+usqGuVfFT9tMtGvGw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.20.2.tgz", + "integrity": "sha512-K8/DhBxcVQkzYc43yJXDSyjlFeHQJBiowJ0uVL6Tor3jGQfSGHNNJcWxNbOI8v5k82prYqzPuwkzHt3J1T1iZQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.20.2.tgz", + "integrity": "sha512-eMpKlV0SThJmmJgiVyN9jTPJ2VBPquf6Kt/nAoo6DgHAoN57K15ZghiHaMvqjCye/uU4X5u3YSMgVBI1h3vKrQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.20.2.tgz", + "integrity": "sha512-2UyFtRC6cXLyejf/YEld4Hajo7UHILetzE1vsRcGL3earZEW77JxrFjH4Ez2qaTiEfMgAXxfAZCm1fvM/G/o8w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.20.2.tgz", + "integrity": "sha512-GRibxoawM9ZCnDxnP3usoUDO9vUkpAxIIZ6GQI+IlVmr5kP3zUq+l17xELTHMWTWzjxa2guPNyrpq1GWmPvcGQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.20.2.tgz", + "integrity": "sha512-HfLOfn9YWmkSKRQqovpnITazdtquEW8/SoHW7pWpuEeguaZI4QnCRW6b+oZTztdBnZOS2hqJ6im/D5cPzBTTlQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.20.2.tgz", + "integrity": "sha512-N49X4lJX27+l9jbLKSqZ6bKNjzQvHaT8IIFUy+YIqmXQdjYCToGWwOItDrfby14c78aDd5NHQl29xingXfCdLQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "node_modules/esbuild": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.20.2.tgz", + "integrity": "sha512-WdOOppmUNU+IbZ0PaDiTst80zjnrOkyJNHoKupIcVyU8Lvla3Ugx94VzkQ32Ijqd7UhHJy75gNWDMUekcrSJ6g==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.20.2", + "@esbuild/android-arm": "0.20.2", + "@esbuild/android-arm64": "0.20.2", + "@esbuild/android-x64": "0.20.2", + "@esbuild/darwin-arm64": "0.20.2", + "@esbuild/darwin-x64": "0.20.2", + "@esbuild/freebsd-arm64": "0.20.2", + "@esbuild/freebsd-x64": "0.20.2", + "@esbuild/linux-arm": "0.20.2", + "@esbuild/linux-arm64": "0.20.2", + "@esbuild/linux-ia32": "0.20.2", + "@esbuild/linux-loong64": "0.20.2", + "@esbuild/linux-mips64el": "0.20.2", + "@esbuild/linux-ppc64": "0.20.2", + "@esbuild/linux-riscv64": "0.20.2", + "@esbuild/linux-s390x": "0.20.2", + "@esbuild/linux-x64": "0.20.2", + "@esbuild/netbsd-x64": "0.20.2", + "@esbuild/openbsd-x64": "0.20.2", + "@esbuild/sunos-x64": "0.20.2", + "@esbuild/win32-arm64": "0.20.2", + "@esbuild/win32-ia32": "0.20.2", + "@esbuild/win32-x64": "0.20.2" + } + }, + "node_modules/fit-file-parser": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fit-file-parser/-/fit-file-parser-2.3.3.tgz", + "integrity": "sha512-TZPFfjkEev5TTd9RnZ4xn4k5ZSx2VZiKNjoZsHIkmQDK0S0XA7ebfdMLj76BK7kStsHh5WbK8Fmn/w85jgd0dA==", + "license": "MIT", + "dependencies": { + "buffer": "^6.0.3" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/prettier": { + "version": "3.8.1", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.8.1.tgz", + "integrity": "sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg==", + "dev": true, + "license": "MIT", + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/uplot": { + "version": "1.6.32", + "resolved": "https://registry.npmjs.org/uplot/-/uplot-1.6.32.tgz", + "integrity": "sha512-KIMVnG68zvu5XXUbC4LQEPnhwOxBuLyW1AHtpm6IKTXImkbLgkMy+jabjLgSLMasNuGGzQm/ep3tOkyTxpiQIw==", + "license": "MIT" + } + } +} diff --git a/suites-experimental/tools-gps-tracks/package.json b/suites-experimental/tools-gps-tracks/package.json new file mode 100644 index 000000000..4102e7acd --- /dev/null +++ b/suites-experimental/tools-gps-tracks/package.json @@ -0,0 +1,27 @@ +{ + "name": "tools-gps-tracks", + "version": "1.0.0", + "private": true, + "description": "Speedometer benchmark workload for GPS file parsing, metrics computation, and chart visualization", + "type": "module", + "scripts": { + "build": "tsc && node build.js", + "dev": "node build.js --watch", + "check": "tsc --noEmit", + "format": "prettier --write 'src/**/*.{ts,html}' 'src/css/styles.css' 'scripts/**/*.mjs' './*.js' README.md" + }, + "engines": { + "node": ">=24.0.0", + "npm": ">=8.19.3" + }, + "license": "BSD-3-Clause", + "dependencies": { + "fit-file-parser": "2.3.3", + "uplot": "1.6.32" + }, + "devDependencies": { + "esbuild": "0.20.2", + "prettier": "3.8.1", + "typescript": "5.9.3" + } +} diff --git a/suites-experimental/tools-gps-tracks/scripts/generate-test-data.mjs b/suites-experimental/tools-gps-tracks/scripts/generate-test-data.mjs new file mode 100644 index 000000000..863683c26 --- /dev/null +++ b/suites-experimental/tools-gps-tracks/scripts/generate-test-data.mjs @@ -0,0 +1,453 @@ +// Copyright (c) 2026, Camillo Bruni. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import fs from 'fs/promises'; +import path from 'path'; +import zlib from 'zlib'; + +const SRC_OUT_DIR = path.join(import.meta.dirname, '../test-data'); +const DIST_OUT_DIR = path.join(import.meta.dirname, '../dist/test-data'); + +// Deterministic PRNG (Mulberry32) so generated test data is reproducible. +let prngSeed = 0x20260720; +function random() { + let t = (prngSeed += 0x6d2b79f5); + t = Math.imul(t ^ (t >>> 15), t | 1); + t ^= t + Math.imul(t ^ (t >>> 7), t | 61); + return ((t ^ (t >>> 14)) >>> 0) / 4294967296; +} + +function generateGPX( + name, + startTime, + pointCount, + baseAlt, + basePower, + baseHr, + baseCad, + baseSpeed, + profile, +) { + let xml = `\n`; + xml += `\n`; + xml += ` ${name}\n`; + xml += ` ${name}\n`; + + let lat = 46.5197; + let lon = 6.6323; + let alt = baseAlt; + const rearGears = [11, 12, 13, 14, 15, 17, 19, 21, 24, 28]; + + for (let i = 0; i < pointCount; i++) { + const t = new Date(startTime + i * 1000).toISOString(); + lat += 0.0001 * Math.cos(i * 0.02) + (random() - 0.5) * 0.00005; + lon += 0.00015 * Math.sin(i * 0.02) + (random() - 0.5) * 0.00005; + + let power = Math.max(100, Math.round(basePower + Math.sin(i / 20) * 50 + (random() * 20 - 10))); + let hr = Math.min(190, Math.max(100, Math.round(baseHr + Math.sin(i / 30) * 15))); + let cad = Math.min(120, Math.max(60, Math.round(baseCad + Math.cos(i / 15) * 10))); + let speed = Math.max(5.0, Number((baseSpeed + Math.sin(i / 25) * 3).toFixed(2))); + let temp = Number( + (20.0 - (alt - baseAlt) * 0.02 + (i / pointCount) * 2.5 + Math.sin(i / 100) * 1.0).toFixed(1), + ); + let frontTooth = 52; + let rearTooth = rearGears[Math.floor((i / 45) % rearGears.length)]; + let battery = Math.max(15, 100 - Math.floor(i / 60)); + + if (profile === 'race') { + alt += Math.sin(i / 40) * 1.8; + power = Math.max(150, Math.round(280 + 100 * Math.sin(i / 15) + (random() - 0.5) * 20)); + hr = Math.min(192, Math.max(120, Math.round(165 + 20 * Math.sin(i / 30)))); + speed = Math.max(8.0, Number((baseSpeed + 2 + Math.sin(i / 20) * 4).toFixed(2))); + temp = Number( + (17.5 - (alt - baseAlt) * 0.025 + (i / pointCount) * 2.8 + Math.sin(i / 110) * 1.2).toFixed( + 1, + ), + ); + } else if (profile === 'mtb') { + alt += Math.cos(i / 25) * 1.5; + frontTooth = 34; + rearTooth = rearGears[Math.floor((i / 25) % rearGears.length)]; + temp = Number( + (19.5 - (alt - baseAlt) * 0.02 + (i / pointCount) * 2.2 + Math.cos(i / 90) * 1.1).toFixed( + 1, + ), + ); + speed = Math.max(4.0, Number((baseSpeed - 2 + Math.cos(i / 15) * 2.5).toFixed(2))); + } else { + alt += Math.sin(i / 50) * 1.2; + temp = Number( + (20.0 - (alt - baseAlt) * 0.02 + (i / pointCount) * 2.5 + Math.sin(i / 100) * 1.0).toFixed( + 1, + ), + ); + } + + xml += ` \n`; + xml += ` ${alt.toFixed(1)}\n`; + xml += ` \n`; + xml += ` \n`; + xml += ` ${power}\n`; + xml += `
${hr}\n`; + xml += ` ${cad}\n`; + xml += ` ${temp}\n`; + xml += ` ${speed}\n`; + xml += ` ${frontTooth}\n`; + xml += ` ${rearTooth}\n`; + xml += ` ${battery}\n`; + xml += ` \n`; + xml += ` ${hr}\n`; + xml += ` ${cad}\n`; + xml += ` ${temp}\n`; + xml += ` \n`; + xml += `
\n`; + xml += `
\n`; + } + + xml += `
\n`; + xml += `
\n`; + return xml; +} + +function generateTCX( + name, + startTime, + pointCount, + baseAlt, + basePower, + baseHr, + baseCad, + baseSpeed, + profile, +) { + let xml = `\n`; + xml += `\n`; + xml += ` \n`; + xml += ` ${new Date(startTime).toISOString()}\n`; + xml += ` ${name}\n`; + xml += ` \n`; + xml += ` \n`; + + let lat = 47.3769; + let lon = 8.5417; + let alt = baseAlt; + const rearGears = [11, 12, 13, 14, 15, 17, 19, 21, 23, 25]; + + for (let i = 0; i < pointCount; i++) { + const t = new Date(startTime + i * 1000).toISOString(); + lat += (random() - 0.45) * 0.0005; + lon += (random() - 0.45) * 0.0005; + + let power = Math.max( + 120, + Math.round(basePower + Math.cos(i / 25) * 60 + (random() * 15 - 7.5)), + ); + let hr = Math.min(195, Math.max(110, Math.round(baseHr + Math.cos(i / 35) * 18))); + let cad = Math.min(115, Math.max(65, Math.round(baseCad + Math.cos(i / 18) * 8))); + let speed = Math.max(6.0, Number((baseSpeed + Math.sin(i / 20) * 2.5).toFixed(2))); + let temp = 21.5; + let frontTooth = 50; + let rearTooth = rearGears[Math.floor((i / 40) % rearGears.length)]; + let battery = Math.max(20, 98 - Math.floor(i / 50)); + + if (profile === 'interval') { + const isInterval = i % 120 < 40; + if (isInterval) { + power = Math.round(380 + random() * 30); + hr = Math.min(195, Math.round(170 + random() * 5)); + cad = Math.round(100 + random() * 5 - 2.5); + speed = Math.max(10.0, Number((baseSpeed + 4 + random()).toFixed(2))); + rearTooth = 13; + } else { + power = Math.round(150 + random() * 15); + hr = Math.max(130, Math.round(145 + random() * 5)); + cad = Math.round(80 + random() * 4 - 2); + speed = Math.max(6.0, Number((baseSpeed - 2 + random() * 0.5).toFixed(2))); + rearTooth = 21; + } + alt += Math.cos(i / 30) * 0.8; + temp = Number( + (24.8 - (i / pointCount) * 2.2 - (alt - baseAlt) * 0.02 + Math.sin(i / 85) * 1.1).toFixed( + 1, + ), + ); + } else { + alt += Math.cos(i / 40) * 1.0; + temp = Number( + (18.5 + (i / pointCount) * 3.4 - (alt - baseAlt) * 0.02 + Math.cos(i / 100) * 1.0).toFixed( + 1, + ), + ); + } + + xml += ` \n`; + xml += ` \n`; + xml += ` ${lat.toFixed(6)}${lon.toFixed(6)}\n`; + xml += ` ${alt.toFixed(1)}\n`; + xml += ` ${hr}\n`; + xml += ` ${cad}\n`; + xml += ` \n`; + xml += ` \n`; + xml += ` ${speed.toFixed(2)}\n`; + xml += ` ${power}\n`; + xml += ` \n`; + xml += ` ${temp}\n`; + xml += ` ${temp}\n`; + xml += ` ${frontTooth}\n`; + xml += ` ${rearTooth}\n`; + xml += ` ${battery}\n`; + xml += ` \n`; + xml += ` \n`; + } + + xml += ` \n`; + xml += ` \n`; + xml += ` \n`; + xml += `\n`; + return xml; +} + +function generateFIT( + startTimeMs, + pointCount, + baseAlt, + basePower, + baseHr, + baseCad, + baseSpeed, + baseTemp = 20, +) { + const garminEpoch = 631065600; + const startGarmin = Math.floor(startTimeMs / 1000) - garminEpoch; + const chunks = []; + + chunks.push( + Buffer.from([ + 0x40, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x01, 0x00, 0x01, 0x02, 0x84, 0x02, 0x02, 0x84, + 0x04, 0x04, 0x86, + ]), + ); + const d0 = Buffer.alloc(10); + d0.writeUInt8(0x00, 0); + d0.writeUInt8(4, 1); + d0.writeUInt16LE(1, 2); + d0.writeUInt16LE(2026, 4); + d0.writeUInt32LE(startGarmin >>> 0, 6); + chunks.push(d0); + + chunks.push( + Buffer.from([ + 0x41, 0x00, 0x00, 0x12, 0x00, 0x03, 0x02, 0x04, 0x86, 0x05, 0x01, 0x00, 0x06, 0x01, 0x00, + ]), + ); + const d18 = Buffer.alloc(7); + d18.writeUInt8(0x01, 0); + d18.writeUInt32LE(startGarmin >>> 0, 1); + d18.writeUInt8(2, 5); + d18.writeUInt8(0, 6); + chunks.push(d18); + + chunks.push( + Buffer.from([ + 0x42, 0x00, 0x00, 0x14, 0x00, 0x0a, 0xfd, 0x04, 0x86, 0x00, 0x04, 0x85, 0x01, 0x04, 0x85, + 0x02, 0x02, 0x84, 0x03, 0x01, 0x02, 0x04, 0x01, 0x02, 0x05, 0x04, 0x86, 0x06, 0x02, 0x84, + 0x07, 0x02, 0x84, 0x0d, 0x01, 0x01, + ]), + ); + + chunks.push( + Buffer.from([ + 0x43, 0x00, 0x00, 0x15, 0x00, 0x06, 0xfd, 0x04, 0x86, 0x00, 0x01, 0x00, 0x01, 0x01, 0x00, + 0x03, 0x04, 0x86, 0x09, 0x01, 0x02, 0x0b, 0x01, 0x02, + ]), + ); + + chunks.push( + Buffer.from([0x44, 0x00, 0x00, 0x17, 0x00, 0x02, 0xfd, 0x04, 0x86, 0x20, 0x01, 0x02]), + ); + + let lat = 46.2; + let lon = 7.2; + let alt = baseAlt; + let dist = 0; + let frontGear = 2; + let rearGear = 5; + let battery = 100; + + for (let i = 0; i < pointCount; i++) { + const t = startGarmin + i * 1; + lat += (random() - 0.45) * 0.0005; + lon += (random() - 0.45) * 0.0005; + alt += Math.sin(i / 50) * 1.4; + dist += baseSpeed * 1; + const speed = baseSpeed + Math.sin(i / 30) * 2.5; + const power = Math.max( + 110, + Math.round(basePower + Math.sin(i / 20) * 65 + (random() - 0.5) * 20), + ); + const hr = Math.min( + 190, + Math.max(105, Math.round(baseHr + Math.sin(i / 30) * 18 + (random() - 0.5) * 4)), + ); + const cad = Math.min( + 115, + Math.max(65, Math.round(baseCad + Math.sin(i / 20) * 10 + (random() - 0.5) * 3)), + ); + const temp = Math.round( + baseTemp - (alt - baseAlt) * 0.025 + (i / pointCount) * 3.2 + Math.sin(i / 80) * 1.8, + ); + + const d20 = Buffer.alloc(26); + d20.writeUInt8(0x02, 0); + d20.writeUInt32LE(t >>> 0, 1); + d20.writeInt32LE(Math.round(lat * (Math.pow(2, 31) / 180)), 5); + d20.writeInt32LE(Math.round(lon * (Math.pow(2, 31) / 180)), 9); + d20.writeUInt16LE(Math.round((alt + 500) * 5), 13); + d20.writeUInt8(hr, 15); + d20.writeUInt8(cad, 16); + d20.writeUInt32LE(Math.round(dist * 100) >>> 0, 17); + d20.writeUInt16LE(Math.round(speed * 1000), 21); + d20.writeUInt16LE(power, 23); + d20.writeInt8(temp, 25); + chunks.push(d20); + + if (i % 50 === 25) { + rearGear = (rearGear % 11) + 1; + if (i % 200 === 100) frontGear = frontGear === 2 ? 1 : 2; + const d21 = Buffer.alloc(13); + d21.writeUInt8(0x03, 0); + d21.writeUInt32LE(t >>> 0, 1); + d21.writeUInt8(43, 5); + d21.writeUInt8(3, 6); + const teethData = + ((frontGear === 2 ? 52 : 36) << 24) | (frontGear << 16) | ((11 + rearGear) << 8) | rearGear; + d21.writeUInt32LE(teethData >>> 0, 7); + d21.writeUInt8(frontGear, 11); + d21.writeUInt8(rearGear, 12); + chunks.push(d21); + } + + if (i % 100 === 0) { + battery = Math.max(10, 100 - Math.floor(i / 100)); + const d23 = Buffer.alloc(6); + d23.writeUInt8(0x04, 0); + d23.writeUInt32LE(t >>> 0, 1); + d23.writeUInt8(battery, 5); + chunks.push(d23); + } + } + + const dataBuf = Buffer.concat(chunks); + const header = Buffer.alloc(14); + header.writeUInt8(14, 0); + header.writeUInt8(32, 1); + header.writeUInt16LE(2114, 2); + header.writeUInt32LE(dataBuf.length, 4); + header.write('.FIT', 8, 4, 'ascii'); + header.writeUInt16LE(0, 12); + + const fileCrc = Buffer.alloc(2); + return Buffer.concat([header, dataBuf, fileCrc]); +} + +async function run() { + await fs.mkdir(SRC_OUT_DIR, { recursive: true }); + await fs.mkdir(DIST_OUT_DIR, { recursive: true }); + + const files = [ + { + name: 'activity_1_alps_stage.gpx', + content: generateGPX( + 'Alpine Mountain Stage', + Date.parse('2026-05-10T08:00:00Z'), + 1500, + 600, + 260, + 160, + 85, + 9.5, + 'race', + ), + }, + { + name: 'activity_2_forest_mtb.gpx', + content: generateGPX( + 'Forest MTB Trail', + Date.parse('2026-05-17T09:30:00Z'), + 1200, + 450, + 230, + 155, + 80, + 7.5, + 'mtb', + ), + }, + { + name: 'activity_3_road_race.tcx', + content: generateTCX( + 'Lakeside Road Race', + Date.parse('2026-06-05T07:15:00Z'), + 1600, + 380, + 280, + 168, + 92, + 11.2, + 'road', + ), + }, + { + name: 'activity_4_intervals.tcx', + content: generateTCX( + 'Sprint Intervals', + Date.parse('2026-06-12T17:00:00Z'), + 1400, + 350, + 320, + 175, + 96, + 12.0, + 'interval', + ), + }, + { + name: 'activity_5_grand_tour.fit', + content: generateFIT(Date.parse('2026-07-02T08:30:00Z'), 1800, 500, 270, 162, 88, 10.5, 19), + }, + { + name: 'activity_6_weekend_ride.fit', + content: generateFIT(Date.parse('2026-07-15T09:00:00Z'), 1400, 320, 210, 142, 84, 8.8, 22), + }, + ]; + + await fs.rm(SRC_OUT_DIR, { recursive: true, force: true }); + await fs.rm(DIST_OUT_DIR, { recursive: true, force: true }); + await fs.mkdir(SRC_OUT_DIR, { recursive: true }); + await fs.mkdir(DIST_OUT_DIR, { recursive: true }); + + const manifest = []; + for (const file of files) { + const gzName = `${file.name}.gz`; + const srcPath = path.join(SRC_OUT_DIR, gzName); + const distPath = path.join(DIST_OUT_DIR, gzName); + const rawBuf = + typeof file.content === 'string' ? Buffer.from(file.content, 'utf-8') : file.content; + const compressed = zlib.gzipSync(rawBuf, { level: 9 }); + await fs.writeFile(srcPath, compressed); + await fs.writeFile(distPath, compressed); + manifest.push(gzName); + console.log(`Generated ${gzName} (${compressed.length} compressed bytes)`); + } + + const manifestJson = JSON.stringify(manifest, null, 2); + await fs.writeFile(path.join(SRC_OUT_DIR, 'manifest.json'), manifestJson); + await fs.writeFile(path.join(DIST_OUT_DIR, 'manifest.json'), manifestJson); + console.log('Saved manifest.json with', manifest.length, 'files.'); +} + +run().catch((err) => { + console.error(err); + process.exit(1); +}); diff --git a/suites-experimental/tools-gps-tracks/src/app.ts b/suites-experimental/tools-gps-tracks/src/app.ts new file mode 100644 index 000000000..c17106e11 --- /dev/null +++ b/suites-experimental/tools-gps-tracks/src/app.ts @@ -0,0 +1,2174 @@ +// Copyright (c) 2026, Camillo Bruni. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +'use strict'; + +// ── Main Application ────────────────────────────────────────────── + +import { Storage } from './storage'; +import { UrlState, AppState } from './url-state'; +import { Parsers, TrackData, TrackPoint, calculateBounds, simplifyTrack } from './parsers'; +import { MapView } from './map'; +import { ChartView } from './charts'; +import { renderDetails, initDetails, resetDetails } from './tabs/details'; +import { renderInsights, initInsights, resetInsights } from './tabs/insights'; +import { + renderCombined, + initCombined, + resizeCombined, + resetCombined, + setCombinedMetrics, + setCombinedPolar, + setCombinedSliderProgress, +} from './tabs/combined'; +import { + renderEvolution, + resetEvolution, + setEvolutionGrouping, + setEvolutionRange, + setEvolutionPinnedDuration, +} from './tabs/progress'; +import { + renderCourses, + initCourses, + prepareBenchmarkCourses, + getBenchmarkCourseIds, + resetCourses, + selectCourse, +} from './tabs/courses'; +import { fmtSecs, escHtml, fmtDate, getTagColor, compactId, shortRandom } from './utils'; + +const TRACK_COLORS: string[] = [ + '#FF6B35', + '#4ECDC4', + '#45B7D1', + '#F7DC6F', + '#FF6B6B', + '#BB8FCE', + '#82E0AA', + '#F8C471', + '#3b82f6', +]; + +const POINT_ZOOM_LEVEL = 16; + +let tracks: Record = {}; +let skippedImports: { name: string; reason: string }[] = []; + +function isSameDay(t1: number, t2: number) { + const d1 = new Date(t1); + const d2 = new Date(t2); + return ( + d1.getUTCFullYear() === d2.getUTCFullYear() && + d1.getUTCMonth() === d2.getUTCMonth() && + d1.getUTCDate() === d2.getUTCDate() + ); +} + +let cancelProcessing = false; +let selectedId: string | null = null; +let colorIdx = 0; +let followDot = false; +let currentMapColors: string[] | null = null; + +// Search/Filter state +let searchQuery = ''; +let searchRegex = false; +let currentSort = 'date-desc'; + +interface Filters { + date: [string | null, string | null]; + dist: [number | null, number | null]; + dur: [number | null, number | null]; + metrics: Set; + tags: string[]; + sport: string | null; +} + +let filters: Filters = { + date: [null, null], + dist: [null, null], + dur: [null, null], + metrics: new Set(), + tags: [], + sport: null, +}; + +function syncFiltersToUrl(push: boolean = false) { + UrlState.patch( + { + f_date: filters.date.every((v) => v === null) ? null : filters.date, + f_dist: filters.dist.every((v) => v === null) ? null : filters.dist, + f_dur: filters.dur.every((v) => v === null) ? null : filters.dur, + f_mets: filters.metrics.size === 0 ? null : Array.from(filters.metrics), + f_tags: filters.tags.length === 0 ? null : filters.tags, + }, + push, + ); +} + +function syncMetricsToUrl() { + const active = ChartView.getActiveMetrics(); + const metrics = ChartView.METRICS; + const abbrs = Array.from(active).map((key) => metrics[key].abbr); + UrlState.patch({ metrics: abbrs.length ? abbrs : null }); +} + +const SORT_OPTIONS = [ + { label: 'Newest first', val: 'date-desc', icon: 'calendar_today' }, + { label: 'Oldest first', val: 'date-asc', icon: 'history' }, + { label: 'Longest distance', val: 'dist-desc', icon: 'straighten' }, + { label: 'Shortest distance', val: 'dist-asc', icon: 'horizontal_rule' }, + { label: 'Longest duration', val: 'dur-desc', icon: 'timer' }, + { label: 'Shortest duration', val: 'dur-asc', icon: 'timer_off' }, +]; + +function showSortMenu(anchorEl: HTMLElement) { + document.querySelectorAll('.sort-menu-popup').forEach((el) => el.remove()); + + const popup = document.createElement('div'); + popup.className = 'sort-menu-popup'; + + SORT_OPTIONS.forEach((opt) => { + const item = document.createElement('div'); + item.className = `menu-item ${currentSort === opt.val ? 'active' : ''}`; + item.innerHTML = ` + ${opt.icon} + ${opt.label} + ${currentSort === opt.val ? 'check' : ''} + `; + item.addEventListener('click', () => { + currentSort = opt.val; + UrlState.patch({ sort: opt.val }); + updateSearchSortUI(); + renderTrackList(); + popup.remove(); + }); + popup.appendChild(item); + }); + + const rect = anchorEl.getBoundingClientRect(); + popup.style.top = `${rect.bottom + 5}px`; + popup.style.left = `${rect.left}px`; + document.body.appendChild(popup); + + const close = (e: MouseEvent) => { + if (!popup.contains(e.target as Node) && !anchorEl.contains(e.target as Node)) { + popup.remove(); + document.removeEventListener('click', close); + } + }; + setTimeout(() => document.addEventListener('click', close), 0); +} + +function showMetricMenu(anchorEl: HTMLElement) { + document.querySelectorAll('.metric-menu-popup').forEach((el) => el.remove()); + + const popup = document.createElement('div'); + popup.className = 'metric-menu-popup'; + + const metrics = ChartView.METRICS; + const available = ChartView.getAvailableMetrics(); + const active = ChartView.getActiveMetrics(); + + Object.entries(metrics).forEach(([key, def]) => { + if (!available.has(key)) return; + + const item = document.createElement('div'); + item.className = `menu-item ${active.has(key) ? 'active' : ''}`; + item.innerHTML = ` + ${def.icon} + ${def.label} + ${active.has(key) ? 'check' : ''} + `; + item.addEventListener('click', () => { + ChartView.toggleMetric(key); + const isNowActive = ChartView.getActiveMetrics().has(key); + item.classList.toggle('active', isNowActive); + item.querySelector('.check')!.textContent = isNowActive ? 'check' : ''; + + const pill = document.querySelector(`#metric-pills .metric-pill[data-metric="${key}"]`); + if (pill) pill.classList.toggle('active', isNowActive); + + updateToolbarLayout(); + + syncMetricsToUrl(); + }); + popup.appendChild(item); + }); + + const rect = anchorEl.getBoundingClientRect(); + popup.style.top = `${rect.bottom + 5}px`; + popup.style.right = `${window.innerWidth - rect.right}px`; + document.body.appendChild(popup); + + const close = (e: MouseEvent) => { + if (!popup.contains(e.target as Node) && !anchorEl.contains(e.target as Node)) { + popup.remove(); + document.removeEventListener('click', close); + } + }; + setTimeout(() => document.addEventListener('click', close), 0); +} + +// ── Tab Management ──────────────────────────────────────────────── +document.addEventListener('select-track', (e: any) => { + const id = e.detail.id; + const idx = e.detail.idx; + const dur = e.detail.dur; + if (id && tracks[id]) { + selectTrack(id); + if (idx != null && dur != null) { + const t = tracks[id]; + const pts = t.points; + if (idx >= 0 && idx < pts.length) { + const pIdx = pts[idx]; + if (pIdx && pIdx.time != null) { + const t0 = pts.find((p) => p.time != null)?.time || 0; + const tMin = (pIdx.time - t0) / 1000; + const tMax = tMin + dur; + + // ChartView selection + ChartView.restoreSelection(tMin, tMax); + + // Find iMax based on duration + let iMax = pts.findIndex((p) => (p.time || 0) >= pIdx.time! + dur * 1000); + if (iMax === -1) iMax = pts.length - 1; + + const selPts = pts.slice(idx, iMax + 1); + const stats = Parsers.computeStats(selPts); + ChartView.setSelectionStats(stats); + + renderInsights(t); + + MapView.highlightSegment(id, pts, idx, iMax, false, currentMapColors); + MapView.ensureVisible(selPts.map((p) => [p.lat, p.lon] as [number, number])); + + // Update URL state + UrlState.patch({ sel: [tMin, tMax] }); + } + } + } + } +}); + +document.addEventListener('click', (e) => { + const btn = (e.target as HTMLElement).closest('.tab-btn') as HTMLElement; + if (!btn) return; + const nav = btn.closest('#tab-nav'); + if (!nav) return; + + // Toggle buttons + nav.querySelectorAll('.tab-btn').forEach((b) => b.classList.toggle('active', b === btn)); + + const tab = btn.dataset.tab!; + UrlState.patch({ tab }, true); + + // Toggle contents + const panel = nav.closest('#chart-panel'); + if (panel) { + panel.querySelectorAll('.tab-content').forEach((c) => { + c.classList.toggle('active', c.id === `tab-${btn.dataset.tab}`); + }); + } + + if (btn.dataset.tab === 'details' && selectedId && tracks[selectedId]) + renderDetails(tracks[selectedId], getGlobalTags()); + + if (btn.dataset.tab === 'insights' && selectedId && tracks[selectedId]) + renderInsights(tracks[selectedId]); + + if (btn.dataset.tab === 'evolution') { + renderEvolution( + selectedId ? tracks[selectedId] : null, + Object.values(tracks), + (trackId, range) => { + selectTrack(trackId, true); + if (range) { + ChartView.restoreSelection(range[0], range[1]); + onChartRangeChange(range[0], range[1], 'time', true); + } + }, + (start, end) => { + filters.date = [start, end]; + applyFilters(); + syncFiltersToUrl(true); + updateFilterUI(); + }, + ); + } + + if (btn.dataset.tab === 'combined' && selectedId && tracks[selectedId]) + renderCombined(tracks[selectedId]); + + if (btn.dataset.tab === 'courses') renderCourses(); + + if (btn.dataset.tab === 'graphs') { + ChartView.resize(); + updateToolbarLayout(); + + // Restore selection from URL + const s = UrlState.get().sel; + if (s) ChartView.restoreSelection(s[0], s[1]); + } +}); + +async function resetBenchmarkState() { + if (typeof (window as any).__resetBenchmarkSeed === 'function') { + (window as any).__resetBenchmarkSeed(); + } + await Storage.clear(); + tracks = {}; + skippedImports = []; + selectedId = null; + colorIdx = 0; + currentMapColors = null; + cancelProcessing = false; + followDot = false; + searchQuery = ''; + searchRegex = false; + currentSort = 'date-desc'; + filters.date = [null, null]; + filters.dist = [null, null]; + filters.dur = [null, null]; + filters.metrics.clear(); + filters.tags = []; + Parsers.setHRZones([120, 140, 160, 180]); + Parsers.setFTP(200); + resetCourses(); + resetInsights(); + resetDetails(); + resetCombined(); + resetEvolution(); + if (MapView.removeAllTracks) MapView.removeAllTracks(); + if (MapView.clearTracks) MapView.clearTracks(); + MapView.clearHighlight(); + if (ChartView.resetState) ChartView.resetState(); + else if (ChartView.clear) ChartView.clear(); + document.querySelectorAll('.tab-btn').forEach((b) => { + b.classList.toggle('active', (b as HTMLElement).dataset.tab === 'graphs'); + }); + document.querySelectorAll('.tab-content').forEach((c) => { + c.classList.toggle('active', c.id === 'tab-graphs'); + }); + UrlState.reset(); + const toastContainer = document.getElementById('toast-container'); + if (toastContainer) toastContainer.innerHTML = ''; + document.querySelectorAll('.modal-backdrop').forEach((el) => el.remove()); + if (typeof renderTrackList === 'function') renderTrackList(); +} + +// ── Boot ────────────────────────────────────────────────────────── +let appSubsystemsInitialized = false; + +async function initAppSubsystems() { + if (appSubsystemsInitialized) { + initInsights((msg: string, type?: string) => + showToast(msg, type === 'error' ? 'error' : 'info'), + ); + initDetails( + () => { + applyFilters(); + syncFiltersToUrl(); + }, + (id: string) => { + deleteTrack(id); + }, + ); + initCombined(); + initCourses((msg: string, type?: string) => + showToast(msg, type === 'error' ? 'error' : 'info'), + ); + updateToolbarLayout(); + await Storage.openDB(); + await resetBenchmarkState(); + return; + } + appSubsystemsInitialized = true; + + const storageSelect = document.getElementById( + 'storage-backend-select', + ) as HTMLSelectElement | null; + if (storageSelect) { + storageSelect.value = UrlState.get().storage || 'memory'; + storageSelect.addEventListener('change', (e) => { + const selected = (e.target as HTMLSelectElement).value; + const params = new URLSearchParams(window.location.search); + if (selected === 'memory') { + params.delete('storage'); + } else { + params.set('storage', selected); + } + window.location.search = params.toString(); + }); + } + + const mapLoader = document.getElementById('map-loader'); + const mapLoaderSpan = mapLoader?.querySelector('span'); + + if (mapLoader) { + if (mapLoaderSpan) mapLoaderSpan.textContent = 'Loading database...'; + mapLoader.classList.remove('hidden'); + } + + const urlState = UrlState.get(); + + initInsights((msg: string, type?: string) => showToast(msg, type === 'error' ? 'error' : 'info')); + initDetails( + () => { + applyFilters(); + syncFiltersToUrl(); + }, + (id: string) => { + deleteTrack(id); + }, + ); + initCombined(); + initCourses((msg: string, type?: string) => showToast(msg, type === 'error' ? 'error' : 'info')); + + // Init sub-systems + MapView.init( + (id) => selectTrack(id), + onMapMove, + onMapPointClick, + (id, idx) => { + if (id === selectedId) ChartView.setHoverAt(idx); + else ChartView.setHoverAt(null); + }, + onMapDblClick, + ); + ChartView.init(onChartCursorMove, onChartRangeChange, onChartClick, (idx) => { + UrlState.patch({ sel_point: idx }); + }); + + ChartView.setMapColorChangeCb((data) => { + if (data) { + currentMapColors = data.colors; + if (selectedId) MapView.colorTrackByMetric(selectedId, data.pts, data.colors); + } else { + currentMapColors = null; + if (selectedId) MapView.clearMetricColor(selectedId); + } + + // Refresh highlight with new colors if a selection exists + const currentUrlState = UrlState.get(); + if (currentUrlState.sel) + onChartRangeChange(currentUrlState.sel[0], currentUrlState.sel[1], ChartView.getXAxis()); + }); + + applyState(urlState); + + // Reset persistent storage and in-memory state for clean multi-iteration benchmark executions + await Storage.openDB(); + await resetBenchmarkState(); + if (mapLoader) mapLoader.classList.add('hidden'); + + // Panel resizers + initResizeHandle(); + initSidebarResizer(); + initMetricPillDraggable(); + updateToolbarLayout(); + + // Basemap switcher + document.getElementById('basemap-switcher')?.addEventListener('click', (e) => { + const btn = (e.target as HTMLElement).closest('.bm-btn') as HTMLElement; + if (!btn) return; + document.querySelectorAll('.bm-btn').forEach((b) => b.classList.remove('active')); + btn.classList.add('active'); + if (btn.dataset.layer) { + MapView.switchBasemap(btn.dataset.layer); + UrlState.patch({ map: btn.dataset.layer }); + } + }); + + // Fit all + document.getElementById('btn-fit-all')?.addEventListener('click', () => MapView.fitAll()); + + // Clear all + document.getElementById('btn-clear-all')?.addEventListener('click', clearAll); + + // Search + document.getElementById('track-search')?.addEventListener('input', (e) => { + searchQuery = (e.target as HTMLInputElement).value; + applyFilters(); + UrlState.patch({ q: searchQuery || null }); + }); + + document.getElementById('btn-regex-toggle')?.addEventListener('click', (e) => { + searchRegex = !searchRegex; + (e.target as HTMLElement).classList.toggle('active', searchRegex); + applyFilters(); + UrlState.patch({ re: searchRegex ? 1 : null }); + }); + + // Sort + document.getElementById('btn-sort-menu')?.addEventListener('click', (e) => { + e.stopPropagation(); + showSortMenu(e.currentTarget as HTMLElement); + }); + + // Metric menu toggle + document.getElementById('btn-metric-menu')?.addEventListener('click', (e) => { + e.stopPropagation(); + showMetricMenu(e.currentTarget as HTMLElement); + }); + + // Metric toggles (shared logic) + const toggleMetric = (metric: string, pill: HTMLElement | null) => { + if (pill) pill.classList.toggle('active'); + ChartView.toggleMetric(metric); + syncMetricsToUrl(); + }; + + document.getElementById('metric-pills')?.addEventListener('click', (e) => { + const pill = (e.target as HTMLElement).closest('.metric-pill') as HTMLElement; + if (pill) { + const metric = pill.dataset.metric; + if (metric) { + toggleMetric(metric, pill); + + // Scroll to it if it became active + if (ChartView.getActiveMetrics().has(metric)) { + const targetEl = document.querySelector(`.chart-row[data-metric="${metric}"]`); + targetEl?.scrollIntoView({ behavior: 'smooth' }); + } + } + } + }); + + // X-axis toggle + document.getElementById('x-axis-ctrl')?.addEventListener('click', (e) => { + const btn = (e.target as HTMLElement).closest('.seg-btn') as HTMLElement; + if (!btn) return; + const axis = btn.dataset.axis!; + ChartView.setXAxis(axis); + document + .querySelectorAll('#x-axis-ctrl .seg-btn') + .forEach((b) => b.classList.toggle('active', b === btn)); + UrlState.patch({ xaxis: axis }); + }); + + // Follow dot + document.getElementById('btn-follow-dot')?.addEventListener('click', (e) => { + followDot = !followDot; + (e.currentTarget as HTMLElement).classList.toggle('active', followDot); + }); + + // Reset zoom + document.getElementById('btn-reset-zoom')?.addEventListener('click', () => { + ChartView.resetZoom(); + MapView.clearHighlight(); + }); + + // File drag/drop + initDropZone(); + + // File browse + document.getElementById('file-input')?.addEventListener('change', (e) => { + handleFiles(Array.from((e.target as HTMLInputElement).files || [])); + (e.target as HTMLInputElement).value = ''; + }); + document.getElementById('folder-input')?.addEventListener('change', (e) => { + handleFiles(Array.from((e.target as HTMLInputElement).files || [])); + (e.target as HTMLInputElement).value = ''; + }); + + // Filters + document.getElementById('btn-toggle-filters')?.addEventListener('click', () => { + const panel = document.getElementById('filter-panel'); + if (panel) { + panel.classList.toggle('hidden'); + document + .getElementById('btn-toggle-filters') + ?.classList.toggle('active', !panel.classList.contains('hidden')); + } + }); + + const onFilterInput = (type: 'date' | 'dist' | 'dur', idx: number, e: Event) => { + const val = (e.target as HTMLInputElement).value; + (filters[type] as any)[idx] = val === '' ? null : val; + applyFilters(); + syncFiltersToUrl(); + }; + + document + .getElementById('filter-date-start') + ?.addEventListener('change', (e) => onFilterInput('date', 0, e)); + document + .getElementById('filter-date-end') + ?.addEventListener('change', (e) => onFilterInput('date', 1, e)); + document + .getElementById('filter-dist-min') + ?.addEventListener('input', (e) => onFilterInput('dist', 0, e)); + document + .getElementById('filter-dist-max') + ?.addEventListener('input', (e) => onFilterInput('dist', 1, e)); + document + .getElementById('filter-dur-min') + ?.addEventListener('input', (e) => onFilterInput('dur', 0, e)); + document + .getElementById('filter-dur-max') + ?.addEventListener('input', (e) => onFilterInput('dur', 1, e)); + + const burgerBtn = document.getElementById('sport-burger-btn'); + const dropdownContent = document.getElementById('sport-dropdown-content'); + burgerBtn?.addEventListener('click', (e) => { + e.stopPropagation(); + if (dropdownContent) { + const isVisible = dropdownContent.style.display === 'block'; + dropdownContent.style.display = isVisible ? 'none' : 'block'; + } + }); + + document.addEventListener('click', (e) => { + if (dropdownContent && dropdownContent.style.display === 'block') { + if (!dropdownContent.contains(e.target as Node) && e.target !== burgerBtn) + dropdownContent.style.display = 'none'; + } + }); + + document.getElementById('sport-filter-container')?.addEventListener('click', (e) => { + const target = e.target as HTMLElement; + const btn = target.closest('[data-sport]'); + if (btn) { + const selectedSport = (btn as HTMLElement).dataset.sport || null; + filters.sport = filters.sport === selectedSport ? null : selectedSport; + applyFilters(); + syncFiltersToUrl(); + updateFilterUI(); + + if (target.closest('.dropdown-item')) { + if (dropdownContent) dropdownContent.style.display = 'none'; + } + } + }); + + document.getElementById('filter-metrics')?.addEventListener('click', (e) => { + const btn = (e.target as HTMLElement).closest('.mini-pill') as HTMLElement; + if (!btn) return; + const m = btn.dataset.metric!; + if (filters.metrics.has(m)) filters.metrics.delete(m); + else filters.metrics.add(m); + updateFilterUI(); + applyFilters(); + syncFiltersToUrl(); + }); + + document.getElementById('btn-reset-filters')?.addEventListener('click', () => { + filters = { + date: [null, null], + dist: [null, null], + dur: [null, null], + metrics: new Set(), + tags: [], + sport: null, + }; + updateFilterUI(); + applyFilters(); + syncFiltersToUrl(); + }); + + document.getElementById('filter-tags')?.addEventListener('input', (e) => { + const val = (e.target as HTMLInputElement).value; + filters.tags = val + .split(',') + .map((s) => s.trim().toLowerCase()) + .filter((s) => !!s); + applyFilters(); + syncFiltersToUrl(); + }); + + // Keyboard shortcuts + document.addEventListener('keydown', (e) => { + if (e.key === 'Escape') { + MapView.hideCursor(); + MapView.closePopup?.(); + // Dismiss popups and modals + document + .querySelectorAll( + '.color-picker-popup, .metric-menu-popup, .sort-menu-popup, .modal-backdrop', + ) + .forEach((el) => el.remove()); + } + }); + + // Resize charts when window changes + window.addEventListener('resize', () => { + ChartView.resize(); + resizeCombined(); + MapView.invalidateSize?.(); + updateToolbarLayout(); + }); +} + +async function init() { + // Wait for fonts to be loaded with a cleaned-up timeout + let fontTimerId: number | null = null; + const fontTimeout = new Promise((resolve) => { + fontTimerId = window.setTimeout(resolve, 1000); + }); + try { + const fontLoadPromise = document.fonts + ? Promise.all([ + document.fonts.load('24px "Material Symbols Rounded"').catch(() => {}), + document.fonts.ready, + ]) + : Promise.resolve(); + await Promise.race([fontLoadPromise, fontTimeout]); + } finally { + if (fontTimerId !== null) window.clearTimeout(fontTimerId); + } + document.body.classList.remove('loading-fonts'); + + initBenchmarkTriggers(); + await preloadBenchmarkFiles(); + + // Signal to Speedometer test harness that asynchronous initialization has completed + // and benchmark step triggers are bound. + const readyEl = document.createElement('div'); + readyEl.id = 'app-is-ready'; + readyEl.setAttribute('data-ready', 'true'); + readyEl.style.display = 'none'; + document.body.appendChild(readyEl); + + const camelReadyEl = document.createElement('div'); + camelReadyEl.id = 'appIsReady'; + camelReadyEl.setAttribute('data-ready', 'true'); + camelReadyEl.style.display = 'none'; + document.body.appendChild(camelReadyEl); +} + +let preloadedBenchmarkPayloads: + | { + cleanName: string; + buf: ArrayBuffer; + text?: string; + file: File; + }[] + | null = null; + +async function preloadBenchmarkFiles(): Promise< + { cleanName: string; buf: ArrayBuffer; text?: string; file: File }[] +> { + if (preloadedBenchmarkPayloads) return preloadedBenchmarkPayloads; + try { + const manifestResp = await fetch('./test-data/manifest.json'); + const fileNames: string[] = await manifestResp.json(); + const decoder = new TextDecoder(); + preloadedBenchmarkPayloads = await Promise.all( + fileNames.map(async (name) => { + const fileResp = await fetch(`./test-data/${name}`); + let buf = await fileResp.arrayBuffer(); + let cleanName = name; + if (name.toLowerCase().endsWith('.gz')) { + cleanName = name.slice(0, -3); + const bytes = new Uint8Array(buf); + if (bytes.length >= 2 && bytes[0] === 0x1f && bytes[1] === 0x8b) { + const ds = new DecompressionStream('gzip'); + const stream = new Blob([buf]).stream().pipeThrough(ds); + buf = await new Response(stream).arrayBuffer(); + } + } + const text = cleanName.toLowerCase().endsWith('.fit') ? undefined : decoder.decode(buf); + const file = new File([buf], cleanName, { type: 'application/octet-stream' }); + (file as any)._buf = buf; + if (text !== undefined) (file as any)._text = text; + return { cleanName, buf, text, file }; + }), + ); + if (typeof (window as any).__resetBenchmarkSeed === 'function') { + (window as any).__resetBenchmarkSeed(); + } + return preloadedBenchmarkPayloads; + } catch { + return []; + } +} + +function initBenchmarkTriggers() { + const btnLoad = document.getElementById('btn-step-load'); + const btnAnalysis = document.getElementById('btn-step-analysis'); + const btnInsights = document.getElementById('btn-step-insights'); + const btnDetails = document.getElementById('btn-step-details'); + const btnAggregate = document.getElementById('btn-step-aggregate'); + const btnEvolution = document.getElementById('btn-step-evolution'); + const btnCourses = document.getElementById('btn-step-courses'); + + function activateTab(tabName: string) { + document.querySelectorAll('.tab-btn').forEach((b) => { + b.classList.toggle('active', (b as HTMLElement).dataset.tab === tabName); + }); + document.querySelectorAll('.tab-content').forEach((c) => { + c.classList.toggle('active', c.id === `tab-${tabName}`); + }); + UrlState.patch({ tab: tabName }, false); + } + + btnLoad?.addEventListener('click', async () => { + try { + await initAppSubsystems(); + const payloads = await preloadBenchmarkFiles(); + const loadedFiles: File[] = payloads.map((p) => p.file); + await handleFiles(loadedFiles, true); + const allTracks = Object.values(tracks); + Parsers.findOverlaps(allTracks); + + // Exercise sidebar track list sorting and filtering across distinct criteria + for (const sortMode of ['dist-desc', 'dur-desc', 'date-asc', 'date-desc']) { + currentSort = sortMode; + renderTrackList(); + } + filters.sport = 'cycling'; + applyFilters(); + filters.sport = null; + filters.metrics.add('power'); + applyFilters(); + filters.metrics.clear(); + searchQuery = 'Alpine'; + applyFilters(); + searchQuery = ''; + applyFilters(); + + if (!selectedId && allTracks.length > 0) selectTrack(allTracks[0].id, true, true); + } finally { + window.dispatchEvent(new CustomEvent('load-and-parse-complete')); + } + }); + + btnAnalysis?.addEventListener('click', () => { + try { + const allTracks = Object.values(tracks); + activateTab('graphs'); + if (!selectedId && allTracks.length > 0) { + selectTrack(allTracks[0].id, false, true); + } + const availableMetrics = ChartView.getAvailableMetrics(); + const metricSets = [['elevation', 'speed', 'power']]; + for (const mSet of metricSets) { + const filtered = mSet.filter((m) => availableMetrics.has(m)); + if (filtered.length > 0) { + document.querySelectorAll('#metric-pills .metric-pill').forEach((el) => { + const metric = (el as HTMLElement).dataset.metric; + if (metric) el.classList.toggle('active', filtered.includes(metric)); + }); + updateToolbarLayout(); + ChartView.setActiveMetrics(filtered); + } + } + + // Exercise smoothing, map metric coloring, and X-axis mode switching + ChartView.setSmooth('power', true, true); + ChartView.toggleMapColor('power', true); + ChartView.toggleMapColor(null, true); + ChartView.setXAxis('distance'); + ChartView.setSmooth('power', false, true); + ChartView.setXAxis('time'); + + // Analyze distinct sections of the track (Opening Climb & Main Analysis Window) + const sections: [number, number, number][] = [ + [0, 360, 180], + [120, 900, 300], + ]; + for (const [secStart, secEnd, cursorIdx] of sections) { + ChartView.restoreSelection(secStart, secEnd, true); + ChartView.setCursorAt(cursorIdx, true); + ChartView.analyzeTelemetryIntervals(allTracks, secStart, secEnd); + onChartRangeChange(secStart, secEnd, ChartView.getXAxis(), false); + } + } finally { + window.dispatchEvent(new CustomEvent('analysis-complete')); + } + }); + + btnInsights?.addEventListener('click', () => { + try { + const allTracks = Object.values(tracks); + allTracks.forEach((t) => Parsers.ensureCurvesAndZones(t)); + activateTab('insights'); + const primaryTrack = (selectedId && tracks[selectedId]) || allTracks[0] || null; + // Analyze insights across GPX, TCX, and FIT tracks and across distinct track sections (Opening, Middle, Final thirds, Full) + for (const tIdx of [1, 2, 4]) { + const trk = allTracks[tIdx]; + if (trk) { + renderInsights(trk, allTracks, [0, 600]); + renderInsights(trk, allTracks, null); + } + } + if (allTracks[3]) { + renderInsights(allTracks[3], allTracks, [200, 900]); + } + if (primaryTrack) { + renderInsights(primaryTrack, allTracks, [0, 600]); + renderInsights(primaryTrack, allTracks, [600, 1200]); + } + renderInsights(primaryTrack, allTracks, null); + } finally { + window.dispatchEvent(new CustomEvent('insights-complete')); + } + }); + + btnDetails?.addEventListener('click', () => { + try { + const allTracks = Object.values(tracks); + activateTab('details'); + const globalTags = getGlobalTags(); + const primaryTrack = (selectedId && tracks[selectedId]) || allTracks[0] || null; + // Render and analyze telemetry details, splits, gear matrices, and zones across all 6 tracks and terrain sections + for (let i = 0; i < allTracks.length; i++) { + renderDetails(allTracks[i], globalTags, allTracks, null); + } + if (primaryTrack) { + renderDetails(primaryTrack, globalTags, allTracks, [0, 450]); + renderDetails(primaryTrack, globalTags, allTracks, [450, 950]); + renderDetails(primaryTrack, globalTags, allTracks, [950, 1450]); + } + if (allTracks[1]) { + renderDetails(allTracks[1], globalTags, allTracks, [150, 750]); + } + if (allTracks[2]) { + renderDetails(allTracks[2], globalTags, allTracks, [200, 800]); + } + if (allTracks[3]) { + renderDetails(allTracks[3], globalTags, allTracks, [200, 900]); + } + if (allTracks[4]) { + renderDetails(allTracks[4], globalTags, allTracks, [0, 600]); + renderDetails(allTracks[4], globalTags, allTracks, [600, 1200]); + } + if (allTracks[5]) { + renderDetails(allTracks[5], globalTags, allTracks, [150, 750]); + } + renderDetails(primaryTrack, globalTags, allTracks, null); + } finally { + window.dispatchEvent(new CustomEvent('details-complete')); + } + }); + + btnAggregate?.addEventListener('click', () => { + try { + const allTracks = Object.values(tracks); + if (allTracks.length >= 2) { + activateTab('combined'); + // 1. Exercise single-track 2D scatter/trail mode across metric pairs, Polar & Cartesian, and timeline sections + setCombinedMetrics('distance', 'elevation'); + renderCombined(allTracks[0], allTracks); + for (const frac of [0.25, 0.5, 0.75, 1.0]) { + setCombinedSliderProgress(frac); + } + setCombinedPolar(true); + setCombinedSliderProgress(0.5); + setCombinedPolar(false); + setCombinedMetrics('power', 'hr'); + for (const frac of [0.3, 0.7]) { + setCombinedSliderProgress(frac); + } + + // 2. Exercise multi-track comparison mode across all 6 tracks and a selected pair + if (allTracks.length >= 6) { + renderCombined(allTracks, allTracks); + } + const selectedTwo = [allTracks[0], allTracks[1]]; + renderCombined(selectedTwo, allTracks); + } + } finally { + window.dispatchEvent(new CustomEvent('aggregate-complete')); + } + }); + + btnEvolution?.addEventListener('click', () => { + try { + const allTracks = Object.values(tracks); + activateTab('evolution'); + const primaryTrack = (selectedId && tracks[selectedId]) || allTracks[0] || null; + const onSelectCb = (trackId: string, range: [number, number] | null) => { + selectTrack(trackId, true); + if (range) { + ChartView.restoreSelection(range[0], range[1]); + onChartRangeChange(range[0], range[1], 'time', true); + } + }; + + // Exercise evolution view across different reference tracks, groupings ('month' & 'week'), time ranges, and pinned durations + const refTracks = [allTracks[2] || primaryTrack, primaryTrack]; + for (const refTrk of refTracks) { + renderEvolution(refTrk, allTracks, onSelectCb); + setEvolutionGrouping('month'); + for (const rng of ['1m', '6m', '1y', 'all']) { + setEvolutionRange(rng); + } + setEvolutionGrouping('week'); + for (const dur of [5, 60, 300, 1200, null]) { + setEvolutionPinnedDuration(dur); + } + } + } finally { + window.dispatchEvent(new CustomEvent('evolution-complete')); + } + }); + + btnCourses?.addEventListener('click', () => { + try { + const allTracks = Object.values(tracks); + activateTab('courses'); + prepareBenchmarkCourses(allTracks); + const courseIds = getBenchmarkCourseIds(); + for (const cId of courseIds) { + selectCourse(cId, true); + } + selectCourse('course-1', true); + } finally { + window.dispatchEvent(new CustomEvent('courses-complete')); + } + }); + + (window as any).benchmarkSteps = { + loadAndParseFiles: () => document.getElementById('btn-step-load')?.click(), + displayAnalysisMetrics: () => document.getElementById('btn-step-analysis')?.click(), + displayInsights: () => document.getElementById('btn-step-insights')?.click(), + displayDetails: () => document.getElementById('btn-step-details')?.click(), + displayAggregateMetrics: () => document.getElementById('btn-step-aggregate')?.click(), + displayEvolutionMetrics: () => document.getElementById('btn-step-evolution')?.click(), + displayCourses: () => document.getElementById('btn-step-courses')?.click(), + }; +} + +async function processAndLoadTrack(t: TrackData) { + // Migration: re-calculate stats if new insights are missing or old format + const powerCurveEntries = t.stats.powerCurve ? Object.values(t.stats.powerCurve) : []; + + const needsEnrich = + t.points.length > 1 && (t.points[1].speed === undefined || t.points[1].gradient === undefined); + + const needsRecalc = + needsEnrich || + !t.stats.powerCurve || + !t.stats.hrZones || + !t.stats.hrCurve || + t.stats.shifts === undefined || + t.stats.avgBattery === undefined || + t.stats.avgTemp === undefined || + (powerCurveEntries.length > 0 && typeof (powerCurveEntries[0] as any) === 'number') || + ((t.stats.duration || 0) > 10800 * 1000 && t.stats.powerCurve && !t.stats.powerCurve[14400]); + + if (needsRecalc) { + if (needsEnrich) t.points = Parsers.enrichPoints(t.points); + t.stats = Parsers.computeStats(t.points); + await Storage.save(t); + } + + tracks[t.id] = t; + colorIdx = Math.max(colorIdx, TRACK_COLORS.indexOf(t.color) + 1); + MapView.addTrack(t); + MapView.setTrackVisible(t.id, t.visible && !t._filtered); + renderTrackList(); // Progressive list update +} + +function initResizeHandle() { + const handle = document.getElementById('resize-handle'); + const panel = document.getElementById('chart-panel'); + if (!handle || !panel) return; + + let isDragging = false; + + handle.addEventListener('mousedown', () => { + isDragging = true; + document.body.style.cursor = 'ns-resize'; + }); + + document.addEventListener('mousemove', (e) => { + if (!isDragging) return; + const h = window.innerHeight - e.clientY; + const clampedH = Math.max(100, Math.min(window.innerHeight - 100, h)); + panel.style.height = `${clampedH}px`; + ChartView.resize(); + resizeCombined(); + MapView.invalidateSize?.(); + }); + + document.addEventListener('mouseup', () => { + if (!isDragging) return; + isDragging = false; + document.body.style.cursor = ''; + }); +} + +function initSidebarResizer() { + const resizer = document.getElementById('sidebar-resizer'); + const sidebar = document.getElementById('sidebar'); + if (!resizer || !sidebar) return; + + let isDragging = false; + + resizer.addEventListener('mousedown', () => { + isDragging = true; + resizer.classList.add('dragging'); + document.body.classList.add('dragging-sidebar'); + }); + + document.addEventListener('mousemove', (e) => { + if (!isDragging) return; + const w = e.clientX; + const clampedW = Math.max(200, Math.min(600, w)); + sidebar.style.width = `${clampedW}px`; + sidebar.style.setProperty('--sidebar-w', `${clampedW}px`); + ChartView.resize(); + MapView.invalidateSize?.(); + }); + + document.addEventListener('mouseup', () => { + if (!isDragging) return; + isDragging = false; + resizer.classList.remove('dragging'); + document.body.classList.remove('dragging-sidebar'); + }); +} + +// ── Event Handlers ────────────────────────────────────────────── + +function onMapMove(lat: number, lng: number, zoom: number) { + UrlState.patch({ map_pos: [lat, lng, zoom] }); +} + +function onMapPointClick(id: string, idx: number) { + selectTrack(id, false); + ChartView.setCursorAt(idx); +} + +function onMapDblClick() { + // MapView.fitAll(); +} + +function onChartCursorMove(pt: TrackPoint | null) { + if (pt && pt.lat != null && pt.lon != null) { + MapView.showCursorAt(pt.lat, pt.lon); + if (followDot) MapView.centerOn(pt.lat, pt.lon, null, true); + } else { + MapView.hideCursor(); + } +} + +function onChartRangeChange(min: number | null, max: number | null, axis: string, fit = false) { + if (!selectedId || !tracks[selectedId]) return; + const t = tracks[selectedId]; + + if (min === null || max === null) { + MapView.clearHighlight(); + if (t && (UrlState.get().tab || 'graphs') === 'insights') renderInsights(t); + UrlState.patch({ sel: null }); + return; + } + + // Find point indices + const pts = t.points; + let iMin = 0, + iMax = pts.length - 1; + + if (axis === 'distance') { + const d0 = min * 1000, + d1 = max * 1000; + iMin = pts.findIndex((p) => (p.dist || 0) >= d0); + iMax = pts.findIndex((p) => (p.dist || 0) >= d1); + } else { + const t0 = pts.find((p) => p.time != null)?.time || 0; + const ts0 = t0 + min * 1000, + ts1 = t0 + max * 1000; + iMin = pts.findIndex((p) => (p.time || 0) >= ts0); + iMax = pts.findIndex((p) => (p.time || 0) >= ts1); + } + + if (iMin === -1) iMin = 0; + if (iMax === -1) iMax = pts.length - 1; + + // Compute selection stats + const selPts = pts.slice(iMin, iMax + 1); + const stats = Parsers.computeStats(selPts); + ChartView.setSelectionStats(stats); + if ((UrlState.get().tab || 'graphs') === 'insights') renderInsights(t); + + MapView.highlightSegment(selectedId, pts, iMin, iMax, fit, currentMapColors); + MapView.ensureVisible(selPts.map((p) => [p.lat, p.lon] as [number, number])); + UrlState.patch({ sel: [min, max] }); +} + +function onChartClick(pt: TrackPoint, idx: number) { + if (pt && pt.lat != null && pt.lon != null) MapView.centerOn(pt.lat, pt.lon, POINT_ZOOM_LEVEL); +} + +export function selectTrack(id: string, fit = true, deferChartRender = false) { + if (!tracks[id]) return; + if (id === selectedId) { + if (fit) MapView.fitTrack(id); + return; + } + + const hadSelected = selectedId !== null; + if (hadSelected) { + // Clear current views immediately before loading new data + ChartView.clear(); + renderDetails(null, []); + renderInsights(null); + } + + selectedId = id; + + // Fully reset metric coloring when switching tracks + currentMapColors = null; + MapView.clearMetricColor(id); + if (hadSelected) { + ChartView.toggleMapColor(null); // Ensure ChartView state also resets + } + + // Clear selection on new track + UrlState.patch({ track: id, sel: null }, true); + if (hadSelected) { + ChartView.cancelSelection(); + } + + // Update UI immediately + renderTrackList(); + + // Render details + const activeTab = UrlState.get().tab || 'graphs'; + if (activeTab === 'details') renderDetails(tracks[id], getGlobalTags()); + if (activeTab === 'insights') renderInsights(tracks[id]); + if (activeTab === 'combined') renderCombined(tracks[id]); + if (activeTab === 'courses') renderCourses(); + if (activeTab === 'evolution') { + renderEvolution(tracks[id], Object.values(tracks), (trackId, range) => { + selectTrack(trackId, true); + if (range) { + ChartView.restoreSelection(range[0], range[1]); + onChartRangeChange(range[0], range[1], 'time', true); + } + }); + } + + // Tell sub-views + MapView.setSelectedTrack(id, fit); + if (fit) MapView.fitTrack(id); + ChartView.loadTrack(tracks[id], Object.values(tracks), deferChartRender); +} + +async function deleteTrack(id: string) { + await Storage.remove(id); + MapView.removeTrack(id); + delete tracks[id]; + if (selectedId === id) { + selectedId = null; + ChartView.clear(); + renderDetails(null, []); + renderInsights(null); + UrlState.patch({ track: null, sel: null, sel_point: null }); + } + applyFilters(); +} + +async function clearAll() { + const code = Math.floor(10 + Math.random() * 90).toString(); + + const backdrop = document.createElement('div'); + backdrop.className = 'modal-backdrop'; + + backdrop.innerHTML = ` + + `; + + document.body.appendChild(backdrop); + + const input = backdrop.querySelector('.modal-input') as HTMLInputElement; + const btnConfirm = backdrop.querySelector('.modal-btn.confirm') as HTMLButtonElement; + const btnCancel = backdrop.querySelector('.modal-btn.cancel') as HTMLButtonElement; + + input.focus(); + + input.addEventListener('input', () => { + btnConfirm.disabled = input.value !== code; + }); + + const close = () => backdrop.remove(); + + btnCancel.addEventListener('click', close); + backdrop.addEventListener('click', (e) => { + if (e.target === backdrop) close(); + }); + + btnConfirm.addEventListener('click', async () => { + if (input.value !== code) return; + close(); + + cancelProcessing = true; + await Storage.clear(); + + tracks = {}; + selectedId = null; + MapView.clearHighlight(); + window.location.reload(); + }); +} + +function applyFilters(skipRender = false) { + const ids = Object.keys(tracks); + ids.forEach((id) => { + const t = tracks[id]; + const s = t.stats; + let visible = true; + + // Search query + if (searchQuery) { + if (searchRegex) { + try { + const re = new RegExp(searchQuery, 'i'); + if (!re.test(t.name)) visible = false; + } catch (e) { + visible = false; + } + } else { + if (!t.name.toLowerCase().includes(searchQuery.toLowerCase())) visible = false; + } + } + + // Date filter + if (visible && (filters.date[0] || filters.date[1])) { + const start = filters.date[0] ? new Date(filters.date[0]).getTime() : 0; + const end = filters.date[1] ? new Date(filters.date[1]).getTime() : Infinity; + const t0 = s.startTime || 0; + if (t0 < start || t0 > end) visible = false; + } + + // Distance filter + if (visible && (filters.dist[0] !== null || filters.dist[1] !== null)) { + const min = (filters.dist[0] || 0) * 1000; + const max = (filters.dist[1] || Infinity) * 1000; + if (s.totalDist < min || s.totalDist > max) visible = false; + } + + // Duration filter + if (visible && (filters.dur[0] !== null || filters.dur[1] !== null)) { + const min = (filters.dur[0] || 0) * 60000; + const max = (filters.dur[1] || Infinity) * 60000; + const d = s.duration || 0; + if (d < min || d > max) visible = false; + } + + // Metrics filter + if (visible && filters.metrics.size > 0) { + for (const m of filters.metrics) { + const field = ChartView.METRICS[m].field; + if (!t.points.some((p: any) => p[field] != null)) { + visible = false; + break; + } + } + } + + // Sport filter + if (visible && filters.sport) { + if (t.sport !== filters.sport) visible = false; + } + + // Tags filter + if (visible && filters.tags.length > 0) { + const trackTags = t.tags || []; + if (!filters.tags.every((ft) => trackTags.includes(ft))) visible = false; + } + + t._filtered = !visible; + MapView.setTrackVisible(t.id, t.visible && visible); + }); + + if (!skipRender) renderTrackList(); +} + +function updateFilterUI() { + (document.getElementById('filter-date-start') as HTMLInputElement).value = filters.date[0] || ''; + (document.getElementById('filter-date-end') as HTMLInputElement).value = filters.date[1] || ''; + (document.getElementById('filter-dist-min') as HTMLInputElement).value = + filters.dist[0] !== null ? String(filters.dist[0]) : ''; + (document.getElementById('filter-dist-max') as HTMLInputElement).value = + filters.dist[1] !== null ? String(filters.dist[1]) : ''; + (document.getElementById('filter-dur-min') as HTMLInputElement).value = + filters.dur[0] !== null ? String(filters.dur[0]) : ''; + (document.getElementById('filter-dur-max') as HTMLInputElement).value = + filters.dur[1] !== null ? String(filters.dur[1]) : ''; + (document.getElementById('filter-tags') as HTMLInputElement).value = filters.tags.join(', '); + + document + .querySelectorAll('#sport-filter-container .mini-pill, #sport-dropdown-content .dropdown-item') + .forEach((el) => { + const item = el as HTMLElement; + item.classList.toggle('active', (item.dataset.sport || '') === (filters.sport || '')); + }); + + document.querySelectorAll('#filter-metrics .mini-pill').forEach((el) => { + const btn = el as HTMLElement; + btn.classList.toggle('active', filters.metrics.has(btn.dataset.metric!)); + }); + + const activeCount = + (filters.date[0] ? 1 : 0) + + (filters.date[1] ? 1 : 0) + + (filters.dist[0] !== null ? 1 : 0) + + (filters.dist[1] !== null ? 1 : 0) + + (filters.dur[0] !== null ? 1 : 0) + + (filters.dur[1] !== null ? 1 : 0) + + filters.metrics.size + + (filters.tags.length > 0 ? 1 : 0); + + const badge = document.querySelector('#btn-toggle-filters .btn-badge'); + if (badge) { + badge.textContent = activeCount > 0 ? String(activeCount) : ''; + badge.classList.toggle('hidden', activeCount === 0); + } +} + +function updateSearchSortUI() { + const searchInput = document.getElementById('track-search') as HTMLInputElement; + if (searchInput) searchInput.value = searchQuery; + document.getElementById('btn-regex-toggle')?.classList.toggle('active', searchRegex); + + const opt = SORT_OPTIONS.find((o) => o.val === currentSort); + if (opt) { + const labelEl = document.getElementById('sort-current-label'); + const iconEl = document.getElementById('sort-current-icon'); + if (labelEl) + labelEl.textContent = opt.label + .replace(' first', '') + .replace(' distance', '') + .replace(' duration', ''); + if (iconEl) iconEl.textContent = opt.icon; + } +} + +function applyState(urlState: AppState) { + // Restore track + const newTrackId = urlState.track; + if (newTrackId !== selectedId) { + if (newTrackId && tracks[newTrackId]) { + selectTrack(newTrackId, true); + } else if (!newTrackId && selectedId) { + selectedId = null; + ChartView.clear(); + renderDetails(null, []); + renderInsights(null); + renderTrackList(); + MapView.fitAll(); + } + } + + // Restore basemap + if (urlState.map && typeof urlState.map === 'string') { + MapView.switchBasemap(urlState.map); + document + .querySelectorAll('.bm-btn') + .forEach((b) => + b.classList.toggle('active', (b as HTMLElement).dataset.layer === urlState.map), + ); + } + + // Restore map position + if (urlState.map_pos) { + const [lat, lng, zoom] = urlState.map_pos; + MapView.setPosition(lat, lng, zoom); + } + + // Restore tab + if (urlState.tab) { + document.querySelectorAll('.tab-btn').forEach((btn) => { + const b = btn as HTMLElement; + if (b.dataset.tab === urlState.tab) { + if (!b.classList.contains('active')) b.click(); + } + }); + } + + // Restore x-axis mode + if (urlState.xaxis) ChartView.setXAxis(urlState.xaxis); + + const currentXAxis = urlState.xaxis || 'time'; + document + .querySelectorAll('#x-axis-ctrl .seg-btn') + .forEach((b) => b.classList.toggle('active', (b as HTMLElement).dataset.axis === currentXAxis)); + + // Restore active metrics + if (urlState.metrics) { + const metrics = ChartView.METRICS; + const keys = urlState.metrics + .map((abbr) => Object.keys(metrics).find((k) => metrics[k].abbr === abbr)) + .filter((k): k is string => k !== undefined); + if (keys.length) ChartView.setActiveMetrics(keys); + } + + // Restore filters + if (urlState.f_date) + filters.date = urlState.f_date.map((v) => v || null) as [string | null, string | null]; + if (urlState.f_dist) + filters.dist = urlState.f_dist.map((v) => (v === 0 ? 0 : v || null)) as [ + number | null, + number | null, + ]; + if (urlState.f_dur) + filters.dur = urlState.f_dur.map((v) => (v === 0 ? 0 : v || null)) as [ + number | null, + number | null, + ]; + if (urlState.f_mets) filters.metrics = new Set(urlState.f_mets); + if (urlState.f_tags) filters.tags = urlState.f_tags; + + updateFilterUI(); + + if (urlState.q) searchQuery = urlState.q; + if (urlState.re) searchRegex = urlState.re; + if (urlState.sort) currentSort = urlState.sort; + updateSearchSortUI(); + + applyFilters(); +} + +function updateSportFilterOptions() { + const container = document.getElementById('sport-filter-container'); + if (!container) return; + + const commonContainer = document.getElementById('common-sports'); + const dropdownContent = document.getElementById('sport-dropdown-content'); + if (!commonContainer || !dropdownContent) return; + + const sports = new Set(); + Object.values(tracks).forEach((t) => { + if (t.sport) sports.add(t.sport); + }); + + const sportsArray = Array.from(sports).sort(); + const burgerDropdown = document.getElementById('sport-burger-dropdown'); + + commonContainer.innerHTML = ''; + dropdownContent.innerHTML = ''; + + if (sportsArray.length <= 4) { + if (burgerDropdown) burgerDropdown.style.display = 'none'; + + sportsArray.forEach((sport) => { + const btn = document.createElement('button'); + btn.className = `mini-pill${filters.sport === sport ? ' active' : ''}`; + btn.dataset.sport = sport; + btn.title = sport.charAt(0).toUpperCase() + sport.slice(1); + btn.innerHTML = `${getSportIcon(sport)}`; + commonContainer.appendChild(btn); + }); + } else { + if (burgerDropdown) burgerDropdown.style.display = 'inline-block'; + + const commonSports = ['running', 'cycling', 'walking']; + + commonSports.forEach((sport) => { + if (sports.has(sport)) { + const btn = document.createElement('button'); + btn.className = `mini-pill${filters.sport === sport ? ' active' : ''}`; + btn.dataset.sport = sport; + btn.title = sport.charAt(0).toUpperCase() + sport.slice(1); + btn.innerHTML = `${getSportIcon(sport)}`; + commonContainer.appendChild(btn); + } + }); + + sportsArray.forEach((sport) => { + const item = document.createElement('div'); + item.className = `dropdown-item${filters.sport === sport ? ' active' : ''}`; + item.dataset.sport = sport; + item.style.padding = '8px'; + item.style.cursor = 'pointer'; + item.style.display = 'flex'; + item.style.alignItems = 'center'; + item.style.gap = '8px'; + item.innerHTML = ` + ${getSportIcon(sport)} + ${sport.charAt(0).toUpperCase() + sport.slice(1)} + `; + dropdownContent.appendChild(item); + }); + } +} + +function renderTrackList() { + updateSportFilterOptions(); + const list = document.getElementById('track-list'); + const emptyEl = document.getElementById('track-list-empty') as HTMLElement; + const headerLabel = document.querySelector('.section-label'); + if (!list) return; + + const ids = Object.keys(tracks).filter((id) => !tracks[id]._filtered); + if (headerLabel) headerLabel.textContent = `Tracks (${ids.length})`; + + if (!ids.length) { + list.innerHTML = ''; + if (emptyEl) { + list.appendChild(emptyEl); + emptyEl.style.display = 'block'; + } + return; + } + if (emptyEl) emptyEl.style.display = 'none'; + list.innerHTML = ''; + + ids + .sort((a, b) => { + // Current selected always first + if (a === selectedId) return -1; + if (b === selectedId) return 1; + + const ta = tracks[a], + tb = tracks[b]; + const sa = ta.stats, + sb = tb.stats; + + let cmp = 0; + switch (currentSort) { + case 'date-asc': + cmp = (sa.startTime || 0) - (sb.startTime || 0); + break; + case 'date-desc': + cmp = (sb.startTime || 0) - (sa.startTime || 0); + break; + case 'dist-asc': + cmp = (sa.totalDist || 0) - (sb.totalDist || 0); + break; + case 'dist-desc': + cmp = (sb.totalDist || 0) - (sa.totalDist || 0); + break; + case 'dur-asc': + cmp = (sa.duration || 0) - (sb.duration || 0); + break; + case 'dur-desc': + cmp = (sb.duration || 0) - (sa.duration || 0); + break; + default: + cmp = 0; + } + return cmp || a.localeCompare(b); + }) + .forEach((id) => { + list.appendChild(buildTrackItem(tracks[id])); + }); +} + +function getGlobalTags(): string[] { + const all = new Set(); + Object.values(tracks).forEach((t) => { + (t.tags || []).forEach((tag) => all.add(tag)); + }); + return Array.from(all).sort(); +} + +function getSportIcon(sport: string | null): string { + if (!sport) return 'category'; + const s = sport.toLowerCase(); + if (s.includes('run')) return 'directions_run'; + if (s.includes('cycl') || s.includes('bike')) return 'directions_bike'; + if (s.includes('walk')) return 'directions_walk'; + if (s.includes('swim')) return 'pool'; + if (s.includes('hik')) return 'hiking'; + return 'sports'; +} + +function buildTrackItem(track: TrackData) { + const item = document.createElement('div'); + item.className = `track-item${track.id === selectedId ? ' selected' : ''}`; + item.dataset.id = track.id; + + const date = + track.points.length && track.points[0].time ? fmtDate(track.points[0].time) : 'Unknown date'; + + const tagsHtml = + (track.tags || []).length > 0 + ? `
${(track.tags || []) + .map((t) => { + const c = getTagColor(t); + return `${escHtml(t)}`; + }) + .join('')}
` + : ''; + + item.innerHTML = ` +
+
+
${escHtml(track.displayName || track.name)}
+
+
+ ${date} + ${Math.round(track.stats.totalDist / 1000)} km + ${track.sport ? `${getSportIcon(track.sport)}` : ''} + ${track.format.toUpperCase()} +
+ +
+ ${tagsHtml} +
+ `; + + item.addEventListener('click', (e) => { + if ((e.target as HTMLElement).closest('.icon-btn')) return; + selectTrack(track.id); + }); + + const btnVis = item.querySelector('.toggle-vis') as HTMLElement; + btnVis.addEventListener('click', () => { + track.visible = !track.visible; + btnVis.querySelector('.material-symbols-rounded')!.textContent = track.visible + ? 'visibility' + : 'visibility_off'; + MapView.setTrackVisible(track.id, track.visible); + Storage.save(track); + }); + + return item; +} + +function showToast(msg: string, type: 'info' | 'error' = 'info') { + const container = document.getElementById('toast-container'); + if (!container) return; + container.innerHTML = ''; + const t = document.createElement('div'); + t.className = `toast ${type}`; + t.textContent = msg; + container.appendChild(t); +} + +function showSkippedImportsModal() { + const backdrop = document.createElement('div'); + backdrop.className = 'modal-backdrop'; + + let listHtml = ''; + skippedImports.forEach((s) => { + listHtml += `
${s.name}: ${s.reason}
`; + }); + + backdrop.innerHTML = ` + + `; + + document.body.appendChild(backdrop); + + const btnCancel = backdrop.querySelector('.modal-btn.cancel') as HTMLButtonElement; + const btnClear = backdrop.querySelector('.modal-btn.clear') as HTMLButtonElement; + const close = () => backdrop.remove(); + + btnCancel.addEventListener('click', close); + btnClear.addEventListener('click', () => { + skippedImports = []; + close(); + const importWarning = document.getElementById('import-warning'); + if (importWarning) importWarning.classList.add('hidden'); + }); + + backdrop.addEventListener('click', (e) => { + if (e.target === backdrop) close(); + }); +} + +function initDropZone() { + const zone = document.getElementById('drop-zone'); + if (!zone) return; + + const importWarning = document.getElementById('import-warning'); + if (importWarning) { + importWarning.addEventListener('click', (e) => { + e.stopPropagation(); + showSkippedImportsModal(); + }); + } + + window.addEventListener('dragover', (e) => { + e.preventDefault(); + zone.classList.add('drop-active'); + }); + + window.addEventListener('dragleave', (e) => { + if (e.relatedTarget === null) zone.classList.remove('drop-active'); + }); + + window.addEventListener('drop', async (e) => { + e.preventDefault(); + zone.classList.remove('drop-active'); + + const items = e.dataTransfer?.items; + if (!items) return; + + const files: File[] = []; + const queue: FileSystemEntry[] = []; + + for (let i = 0; i < items.length; i++) { + const entry = items[i].webkitGetAsEntry(); + if (entry) queue.push(entry); + } + + async function readAllEntries(entry: FileSystemEntry) { + if (entry.isFile) { + const file = await new Promise((res) => (entry as FileSystemFileEntry).file(res)); + files.push(file); + } else if (entry.isDirectory) { + const reader = (entry as FileSystemDirectoryEntry).createReader(); + let entries: FileSystemEntry[] = []; + let chunk: FileSystemEntry[]; + do { + chunk = await new Promise((res) => reader.readEntries(res)); + entries.push(...chunk); + } while (chunk.length > 0); + + for (const child of entries) await readAllEntries(child); + } + } + + for (const entry of queue) await readAllEntries(entry); + + if (files.length) handleFiles(files); + }); +} + +async function handleFiles(files: File[], deferChartRender = false) { + if (!files.length) return; + cancelProcessing = false; + const initialSkipCount = skippedImports.length; + + const loader = document.getElementById('global-loader'); + const loaderText = document.getElementById('loader-text'); + const loaderSubtext = document.getElementById('loader-subtext'); + const progressBar = document.getElementById('loader-progress-bar') as HTMLElement; + const loaderClose = document.getElementById('loader-close'); + + if (loaderClose) { + loaderClose.addEventListener('click', () => { + cancelProcessing = true; + if (loader) loader.classList.add('hidden'); + }); + } + + function updateLoaderIconToWarning() { + const loaderIcon = document.querySelector('.loader-icon-wrap span') as HTMLElement; + if (loaderIcon) { + loaderIcon.textContent = 'warning'; + loaderIcon.style.color = '#f59e0b'; + } + const warningCount = document.getElementById('loader-warning-count'); + if (warningCount) { + const count = skippedImports.length - initialSkipCount; + const countSpan = warningCount.querySelector('span'); + if (countSpan) countSpan.textContent = `${count}`; + warningCount.style.display = 'flex'; + } + } + + if (loader) { + if (loaderText) + loaderText.textContent = `Importing ${files.length} file${files.length > 1 ? 's' : ''}`; + const loaderIcon = document.querySelector('.loader-icon-wrap span') as HTMLElement; + if (loaderIcon) { + loaderIcon.textContent = 'download'; + loaderIcon.style.color = 'var(--accent)'; + } + const warningCount = document.getElementById('loader-warning-count'); + if (warningCount) { + warningCount.style.display = 'none'; + const countSpan = warningCount.querySelector('span'); + if (countSpan) countSpan.textContent = '0'; + } + if (loaderSubtext) loaderSubtext.textContent = ''; + if (progressBar) progressBar.style.width = '0%'; + loader.classList.remove('hidden'); + + if (skippedImports.length > initialSkipCount) updateLoaderIconToWarning(); + } + + try { + let count = 0; + let firstId: string | null = null; + let processedSinceLastUpdate = 0; + + // Filter out obviously invalid files early + const validFiles = files.filter((f) => { + const name = f.name.toLowerCase(); + return ( + name.endsWith('.gpx') || + name.endsWith('.fit') || + name.endsWith('.tcx') || + name.endsWith('.kml') + ); + }); + + const invalidFiles = files.filter((f) => !validFiles.includes(f)); + invalidFiles.forEach((f) => { + skippedImports.push({ name: f.name, reason: 'Unsupported file format' }); + }); + + if (validFiles.length === 0) { + if (files.length > 0) { + showToast('No valid GPS files found', 'error'); + // Still update warning icon if there were invalid files! + const importWarning = document.getElementById('import-warning'); + if (importWarning && skippedImports.length > 0) { + importWarning.classList.remove('hidden'); + importWarning.title = `Skipped ${skippedImports.length} imports. Click to view details.`; + } + } + return; + } + + const loaderFilename = document.getElementById('loader-filename'); + let progressIdxEl: HTMLElement | null = null; + if (loaderSubtext) { + loaderSubtext.innerHTML = + ''; + progressIdxEl = loaderSubtext.querySelector('.progress-index') as HTMLElement | null; + const totalEl = loaderSubtext.querySelector('.progress-total'); + if (totalEl) totalEl.textContent = validFiles.length.toString(); + } + + for (let i = 0; i < validFiles.length; i++) { + if (cancelProcessing) break; + const f = validFiles[i]; + if (loaderFilename) loaderFilename.textContent = f.name; + if (progressIdxEl) progressIdxEl.textContent = (i + 1).toString(); + if (progressBar) progressBar.style.width = `${(i / validFiles.length) * 100}%`; + + try { + const data = await Parsers.parseFile(f); + + // Duplicate checks + const existingTracks = Object.values(tracks); + + // Rule 1: Same file and same size + const sameFile = existingTracks.find((t) => t.fileName === f.name && t.fileSize === f.size); + if (sameFile) { + skippedImports.push({ name: f.name, reason: 'Same File' }); + updateLoaderIconToWarning(); + continue; + } + + // Rule 2 & 3: Same start day checks + const newStart = data.stats.startTime; + const newDuration = data.stats.duration; + + if (newStart != null && newDuration != null) { + const newEnd = newStart + newDuration; + const sameDayTracks = existingTracks.filter( + (t) => t.stats.startTime != null && isSameDay(newStart, t.stats.startTime!), + ); + + let discarded = false; + for (const ext of sameDayTracks) { + const extStart = ext.stats.startTime!; + const extDuration = ext.stats.duration!; + const extEnd = extStart + extDuration; + + // Rule 2: Fully contained + if (newStart >= extStart && newEnd <= extEnd) { + skippedImports.push({ name: f.name, reason: 'Fully contained in another track' }); + updateLoaderIconToWarning(); + discarded = true; + break; + } + + // Rule 3: Overlaps for > 10 mins + const overlap = Math.min(newEnd, extEnd) - Math.max(newStart, extStart); + if (overlap > 600000) { + // 10 mins in ms + skippedImports.push({ + name: f.name, + reason: 'Overlaps with an existing track for more than 10 mins', + }); + updateLoaderIconToWarning(); + discarded = true; + break; + } + } + if (discarded) continue; + } + + const fallbackTime = 1785542400000; + let id = compactId(data.stats.startTime || fallbackTime); + if (tracks[id]) id += `-${shortRandom()}`; + + // Use filename as activity name if internal name is generic + let trackName = data.name; + if (trackName.startsWith('Unnamed') || trackName.startsWith('Fit Activity')) + trackName = f.name; + + // Generate a nice display name + let displayName = trackName; + const t0 = data.points[0]?.time; + const sport = data.sport + ? data.sport.charAt(0).toUpperCase() + data.sport.slice(1) + : 'Activity'; + if (t0) { + const d = new Date(t0); + displayName = `${d.getUTCFullYear()}-${String(d.getUTCMonth() + 1).padStart(2, '0')}-${String(d.getUTCDate()).padStart(2, '0')} ${sport}`; + } else { + displayName = trackName.replace(/\.[^/.]+$/, '').replace(/_/g, ' '); // Remove extension and replace underscores + } + + const track: TrackData = { + ...data, + id, + name: trackName, + displayName, + fileName: f.name, + fileSize: f.size, + bounds: calculateBounds(data.points) || undefined, + simplifiedPoints: simplifyTrack(data.points, 50), // 50m resolution + addedAt: data.stats.startTime || fallbackTime, + visible: true, + color: TRACK_COLORS[colorIdx % TRACK_COLORS.length], + }; + colorIdx++; + if (cancelProcessing) break; + + await Storage.save(track); + tracks[id] = track; + MapView.addTrack(track); + if (!firstId) firstId = id; + count++; + processedSinceLastUpdate++; + + if (processedSinceLastUpdate >= 5) { + processedSinceLastUpdate = 0; + } + } catch (err) { + skippedImports.push({ + name: f.name, + reason: `Parse error: ${err instanceof Error ? err.message : String(err)}`, + }); + updateLoaderIconToWarning(); + } + } + + // Update warning icon + const importWarning = document.getElementById('import-warning'); + if (importWarning) { + if (skippedImports.length > 0) { + importWarning.classList.remove('hidden'); + importWarning.title = `Skipped ${skippedImports.length} imports. Click to view details.`; + } else { + importWarning.classList.add('hidden'); + } + } + + const newWarnings = skippedImports.length > initialSkipCount; + if (!newWarnings && loader) { + loader.classList.add('hidden'); + } + + if (count > 0) { + showToast(`Imported ${count} track${count > 1 ? 's' : ''}`); + applyFilters(!!firstId && firstId !== selectedId); + if (firstId) selectTrack(firstId, true, deferChartRender); + } + + if (newWarnings) { + const loaderIcon = document.querySelector('.loader-icon-wrap span'); + if (loaderIcon) loaderIcon.textContent = 'warning'; + // Delay hiding loader to let user see the alert icon + await new Promise((r) => setTimeout(r, 1500)); + } + } finally { + if (loader) loader.classList.add('hidden'); + } +} + +function initMetricPillDraggable() { + const container = document.getElementById('metric-pills'); + if (!container) return; + + let draggedEl: HTMLElement | null = null; + + container.addEventListener('dragstart', (e) => { + const target = (e.target as HTMLElement).closest('.metric-pill') as HTMLElement; + if (!target) return; + draggedEl = target; + e.dataTransfer!.effectAllowed = 'move'; + target.classList.add('dragging-pill'); + // For transparent background in ghost image + setTimeout(() => (target.style.opacity = '0.4'), 0); + }); + + container.addEventListener('dragover', (e) => { + e.preventDefault(); + e.dataTransfer!.dropEffect = 'move'; + const target = (e.target as HTMLElement).closest('.metric-pill') as HTMLElement; + if (target && target !== draggedEl) { + const rect = target.getBoundingClientRect(); + const next = e.clientX - rect.left > rect.width / 2; + container.insertBefore(draggedEl!, next ? target.nextSibling : target); + } + }); + + container.addEventListener('dragend', () => { + if (!draggedEl) return; + draggedEl.classList.remove('dragging-pill'); + draggedEl.style.opacity = ''; + draggedEl = null; + + // Update Chart order + const keys = Array.from(container.querySelectorAll('.metric-pill')).map( + (el) => (el as HTMLElement).dataset.metric!, + ); + ChartView.updateMetricOrder(keys); + syncMetricsToUrl(); + }); +} + +function updateToolbarLayout() { + const toolbar = document.getElementById('chart-toolbar'); + const pills = document.getElementById('metric-pills'); + if (!toolbar || !pills) return; + + toolbar.classList.remove('collapsed'); + const availableW = Math.max(0, toolbar.clientWidth - 280); + let totalW = 0; + let hidden = false; + + Array.from(pills.children).forEach((el) => { + const htmlEl = el as HTMLElement; + if (htmlEl.id === 'pill-overflow') return; + + htmlEl.classList.remove('overflow-hidden'); + if (!htmlEl.classList.contains('active')) return; + const metricKey = htmlEl.dataset.metric || ''; + const deterministicPillWidth = 36 + metricKey.length * 6; + totalW += deterministicPillWidth + 6; + + if (totalW > availableW) { + hidden = true; + htmlEl.classList.add('overflow-hidden'); + } + }); + + const overflowEl = document.getElementById('pill-overflow'); + if (overflowEl) overflowEl.classList.toggle('hidden', !hidden); + + if (hidden) toolbar.classList.add('collapsed'); +} + +document.addEventListener('DOMContentLoaded', init); diff --git a/suites-experimental/tools-gps-tracks/src/benchmark-override.ts b/suites-experimental/tools-gps-tracks/src/benchmark-override.ts new file mode 100644 index 000000000..aef3b3206 --- /dev/null +++ b/suites-experimental/tools-gps-tracks/src/benchmark-override.ts @@ -0,0 +1,75 @@ +// Copyright (c) 2026, Camillo Bruni. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +'use strict'; + +// Deterministic PRNG (Mulberry32) for reproducible benchmark runs. +let seed = 0x20260720; +(window as any).__resetBenchmarkSeed = (): void => { + seed = 0x20260720; +}; +Math.random = (): number => { + let t = (seed += 0x6d2b79f5); + t = Math.imul(t ^ (t >>> 15), t | 1); + t ^= t + Math.imul(t ^ (t >>> 7), t | 61); + return ((t ^ (t >>> 14)) >>> 0) / 4294967296; +}; + +window.requestAnimationFrame = (callback: FrameRequestCallback): number => { + return window.setTimeout(() => callback(performance.now()), 0); +}; + +window.cancelAnimationFrame = (id: number): void => { + window.clearTimeout(id); +}; + +// Normalize devicePixelRatio to 1 so uPlot and canvas rendering execute +// identical code paths (e.g. decimation thresholds and canvas dimensions) across browsers. +try { + Object.defineProperty(window, 'devicePixelRatio', { + get: () => 1, + configurable: true, + }); +} catch (_) {} + +// Normalize navigator.language to en-US so Intl.NumberFormat / locale formatters +// produce identical strings across browsers and OS locales. +try { + Object.defineProperty(navigator, 'language', { + get: () => 'en-US', + configurable: true, + }); + Object.defineProperty(navigator, 'languages', { + get: () => ['en-US'], + configurable: true, + }); +} catch (_) {} + +// Normalize CanvasRenderingContext2D.prototype.measureText so canvas layout calculations +// (histogram padding, axis label bounding boxes, cursor pills) do not vary with OS/browser +// system-ui font glyph metrics. +if (typeof CanvasRenderingContext2D !== 'undefined') { + CanvasRenderingContext2D.prototype.measureText = function (text: string): TextMetrics { + const fontStr = this.font || '10px sans-serif'; + const sizeMatch = fontStr.match(/(\d+(?:\.\d+)?)px/); + const fontSize = sizeMatch ? parseFloat(sizeMatch[1]) : 10; + const isBold = /\bbold\b|\b[6-9]00\b/i.test(fontStr); + const charWidth = fontSize * (isBold ? 0.6 : 0.55); + const width = String(text).length * charWidth; + return { + width, + actualBoundingBoxLeft: 0, + actualBoundingBoxRight: width, + actualBoundingBoxAscent: fontSize * 0.8, + actualBoundingBoxDescent: fontSize * 0.2, + fontBoundingBoxAscent: fontSize * 0.8, + fontBoundingBoxDescent: fontSize * 0.2, + emHeightAscent: fontSize * 0.8, + emHeightDescent: fontSize * 0.2, + hangingBaseline: fontSize * 0.8, + alphabeticBaseline: 0, + ideographicBaseline: -fontSize * 0.2, + } as TextMetrics; + }; +} diff --git a/suites-experimental/tools-gps-tracks/src/charts.ts b/suites-experimental/tools-gps-tracks/src/charts.ts new file mode 100644 index 000000000..d9a154068 --- /dev/null +++ b/suites-experimental/tools-gps-tracks/src/charts.ts @@ -0,0 +1,3516 @@ +// Copyright (c) 2026, Camillo Bruni. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +'use strict'; + +import uPlot from 'uplot'; +import { TrackPoint, TrackStats, TrackData } from './parsers'; +import { gaussianSmooth, fmtSecs, hexToRgba, fmtPace } from './utils'; +import { Zones } from './zones'; +import { METRICS, MetricDefinition } from './metrics'; +import { UrlState } from './url-state'; +import { SPORTS } from './sports'; + +// Augment uPlot to include our custom property + +interface WegPlot extends uPlot { + _strasse?: { + selFill: HTMLElement; + anchorLine: HTMLElement; + anchorLabel: HTMLElement; + anchorHandle: HTMLElement; + anchorMarker: HTMLElement; + endLine: HTMLElement; + endLabel: HTMLElement; + endHandle: HTMLElement; + endMarker: HTMLElement; + curYVal: HTMLElement; + updateHeaderStats: (min: number, max: number) => void; + }; +} + +interface PlotData { + uplot: WegPlot; + row: HTMLDivElement; + xData: number[]; + yData: (number | null)[]; + def: MetricDefinition; + metricKey: string; + statsTotalEl: HTMLDivElement; + statsSelEl: HTMLDivElement; + histCol: HTMLDivElement; + histCanvas: HTMLCanvasElement; + histData: HistData; + hoveredHistY: number | null; + pinnedHistY: number | null; + smoothed?: boolean; + incZero?: boolean; + w?: number; +} + +interface HistData { + yData: (number | null)[]; + xData: number[]; + def: MetricDefinition; + BINS: number; + plot?: PlotData; + bins?: number[]; + binAccum?: number[]; + min?: number; + max?: number; + padL?: number; + selBins?: number[] | null; + selBinAccum?: number[] | null; + _lastYData?: any; + _lastSelRange?: [number, number] | null; + fullStats?: { min: number; max: number; avg: number } | null; + fullStatsAll?: { min: number; max: number; avg: number } | null; +} + +export const ChartView = (() => { + let plots: PlotData[] = []; + let renderedTrack: TrackData | null = null; + let renderedXAxis: string = 'time'; + let renderedSpeedMode: string = 'speed'; + let renderedMapColorMetric: string | null = null; + let syncKey: { + key: string; + sub: (u: uPlot) => void; + unsub: (u: uPlot) => void; + pub: (type: string, u: uPlot, x: number, y: number, w: number, h: number, i: number) => void; + } | null = null; + let scaleSyncing: boolean = false; + let cursorSyncing: boolean = false; + let activeMetrics: Set = new Set(['elevation']); + let availableMetrics: Set = new Set(); + let smoothedMetrics: Set = new Set(['speed', 'gradient', 'vam', 'temperature']); + let metricsIncludingZero: Set = new Set(['power']); + let xAxis: string = 'time'; + let currentTrack: TrackData | null = null; + let allTracks: TrackData[] = []; + + // Selection / anchor + let selAnchorVal: number | null = null; // x-value at drag-start / zoom left edge + let selEndVal: number | null = null; // x-value at zoom right edge + let isDragging: boolean = false; + let updatingRange: boolean = false; // guard against setScale hook re-entry during handle drag + let pinnedPtIdx: number | null = null; // point index pinned by map click + let lastMouseXVal: number | null = null; // last hovered x-value for keyboard zoom center + let lastHistMouseEvent: MouseEvent | null = null; + let hoveredHistData: HistData | null = null; + let hoveredHistCanvas: HTMLCanvasElement | null = null; + + let currentXRange: [number, number] | null = null; // [min, max] currently rendered + let targetXRange: [number, number] | null = null; // [min, max] for smooth keyboard animation + let animId: number | null = null; + + // Callbacks + let onCursorMoveCb: ((pt: TrackPoint | null) => void) | null = null; + let onRangeChangeCb: ((min: number | null, max: number | null, axis: string) => void) | null = + null; + let onClickCb: ((pt: TrackPoint, idx: number) => void) | null = null; + let onPinChangeCb: ((idx: number | null) => void) | null = null; + + const HIST_W: number = 130; + const ROW_BODY_PADDING: number = 28; // 14px left + 14px right + const HIST_GAP: number = 20; // margin-left on .hist-col + let statsVisible: boolean = true; + let histTooltipEl: HTMLElement | null = null; + let container: HTMLElement | null = null; + let emptyEl: HTMLElement | null = null; + let selStatsEl: HTMLElement | null = null; + let resetSelBtn: HTMLElement | null = null; + let mapColorMetric: string | null = null; + let onMapColorChangeCb: ((data: { pts: TrackPoint[]; colors: string[] } | null) => void) | null = + null; + + // ── Init ────────────────────────────────────────────────────── + function init( + onCursorMove: (pt: TrackPoint | null) => void, + onRangeChange: (min: number | null, max: number | null, axis: string) => void, + onClick: (pt: TrackPoint, idx: number) => void, + onPinChange: (idx: number | null) => void, + ) { + onCursorMoveCb = onCursorMove; + onRangeChangeCb = onRangeChange; + onClickCb = onClick; + onPinChangeCb = onPinChange; + container = document.getElementById('charts-container'); + container?.addEventListener('scroll', () => { + if (lastHistMouseEvent && hoveredHistCanvas && hoveredHistData) + updateHistTooltip(lastHistMouseEvent, hoveredHistCanvas, hoveredHistData); + }); + emptyEl = document.getElementById('chart-empty'); + selStatsEl = document.getElementById('chart-stats-sel'); + resetSelBtn = document.getElementById('btn-reset-selection'); + histTooltipEl = document.getElementById('hist-tooltip'); + syncKey = uPlot.sync('strasse-sync'); + + document.getElementById('sel-cancel-btn')?.addEventListener('click', cancelSelection); + resetSelBtn?.classList.add('hidden'); + + // ── WASD Keyboard Navigation ── + const navKeys = new Set(['w', 'a', 's', 'd']); + const activeNavKeys = new Set(); + + document.addEventListener('keydown', (e) => { + if (!plots.length || !currentTrack) return; + if (['INPUT', 'TEXTAREA', 'SELECT'].includes(document.activeElement?.tagName || '')) return; + + const key = e.key.toLowerCase(); + if (!navKeys.has(key)) return; + + e.preventDefault(); + activeNavKeys.add(key); + + const u = plots[0].uplot; + if (!targetXRange) targetXRange = [u.scales.x!.min!, u.scales.x!.max!]; + + let [min, max] = targetXRange; + const span = max - min; + const moveStep = span * 0.1; + const zoomFactor = 0.15; + + let newMin = min, + newMax = max; + + if (key === 'a') { + // Pan left + newMin = min - moveStep; + newMax = max - moveStep; + } else if (key === 'd') { + // Pan right + newMin = min + moveStep; + newMax = max + moveStep; + } else if (key === 'w' || key === 's') { + // Zoom + const center = lastMouseXVal !== null ? lastMouseXVal : (min + max) / 2; + const factor = key === 'w' ? 1 - zoomFactor : 1 + zoomFactor * 1.2; + newMin = center - (center - min) * factor; + newMax = center + (max - center) * factor; + } + + // Clamp to track bounds + const xFull = [plots[0].xData[0]!, plots[0].xData[plots[0].xData.length - 1]!]; + if (newMin < xFull[0]) { + const d = xFull[0] - newMin; + newMin += d; + newMax += d; + } + if (newMax > xFull[1]) { + const d = newMax - xFull[1]; + newMin -= d; + newMax -= d; + } + newMin = Math.max(xFull[0], newMin); + newMax = Math.min(xFull[1], newMax); + + if (newMax - newMin > 0.001) { + targetXRange = [newMin, newMax]; + setVisibleRange(newMin, newMax, true); // true = animate + } + }); + + document.addEventListener('keyup', (e) => { + const key = e.key.toLowerCase(); + if (navKeys.has(key)) activeNavKeys.delete(key); + }); + } + + function setVisibleRange(min: number, max: number, animate = false) { + if (!plots.length) return; + if (!animate) { + plots.forEach(({ uplot: u }) => u.setScale('x', { min, max })); + return; + } + + const startMin = (currentXRange ? currentXRange[0] : plots[0].uplot.scales.x!.min) as number; + const startMax = (currentXRange ? currentXRange[1] : plots[0].uplot.scales.x!.max) as number; + + if (animId) cancelAnimationFrame(animId); + + const lerp = (a: number, b: number, t: number) => a + (b - a) * t; + const factor = 0.25; // Smoothing factor + + const step = () => { + if (!targetXRange) return; + const [tMin, tMax] = targetXRange; + + const cMin = lerp(currentXRange![0], tMin, factor); + const cMax = lerp(currentXRange![1], tMax, factor); + + plots.forEach(({ uplot: u }) => { + u.setScale('x', { min: cMin, max: cMax }); + }); + + if (Math.abs(cMin - tMin) > 0.0001 || Math.abs(cMax - tMax) > 0.0001) + animId = requestAnimationFrame(step); + else animId = null; + }; + animId = requestAnimationFrame(step); + } + + // ── Public API ──────────────────────────────────────────────── + function loadTrack(track: TrackData, others: TrackData[] = [], deferRender = false) { + pinnedPtIdx = null; + selAnchorVal = null; + selEndVal = null; + allTracks = others; + + // Detect which metrics are available in this file + availableMetrics = new Set( + Object.entries(METRICS) + .filter(([, def]) => track.points.some((p) => p[def.field] != null)) + .map(([key]) => key), + ); + + // If no metrics are active (e.g. first load), default to core overview metrics + if (activeMetrics.size === 0) { + activeMetrics = new Set(['elevation'].filter((m) => availableMetrics.has(m))); + } + + // Sync the pill buttons in the toolbar + document.querySelectorAll('.metric-pill').forEach((el) => { + const pill = el as HTMLElement; + const metric = pill.dataset.metric!; + const isAvailable = availableMetrics.has(metric); + pill.classList.toggle('active', isAvailable && activeMetrics.has(metric)); + pill.classList.toggle('disabled', !isAvailable); + }); + + currentTrack = track; + if (deferRender) { + updateStats(track); + } else { + render(); + } + } + + function clear() { + pinnedPtIdx = null; + selAnchorVal = null; + selEndVal = null; + currentTrack = null; + destroyPlots(); + if (emptyEl) { + emptyEl.style.display = 'flex'; + emptyEl.innerHTML = ` + no_sim +
Select a track to view analysis
+ `; + } + const chartStats = document.getElementById('chart-stats'); + if (chartStats) chartStats.classList.add('hidden'); + clearSelectionStats(); + mapColorMetric = null; + if (onMapColorChangeCb) onMapColorChangeCb(null); + } + + function resetState() { + if (animId !== null) { + cancelAnimationFrame(animId); + animId = null; + } + activeMetrics = new Set(['elevation']); + availableMetrics = new Set(); + smoothedMetrics = new Set(['speed', 'gradient', 'vam', 'temperature']); + metricsIncludingZero = new Set(['power']); + xAxis = 'time'; + allTracks = []; + currentXRange = null; + targetXRange = null; + lastMouseXVal = null; + lastHistMouseEvent = null; + hoveredHistData = null; + hoveredHistCanvas = null; + statsVisible = true; + clear(); + document.querySelectorAll('#metric-pills .metric-pill').forEach((el) => { + const pill = el as HTMLElement; + pill.classList.toggle('active', activeMetrics.has(pill.dataset.metric!)); + pill.classList.remove('disabled', 'overflow-hidden'); + }); + const overflowEl = document.getElementById('pill-overflow'); + if (overflowEl) overflowEl.classList.add('hidden'); + const toolbar = document.getElementById('chart-toolbar'); + if (toolbar) toolbar.classList.remove('collapsed'); + document.querySelectorAll('#x-axis-ctrl .seg-btn').forEach((b) => { + b.classList.toggle('active', (b as HTMLElement).dataset.axis === 'time'); + }); + } + + function toggleMetric(key: string) { + if (activeMetrics.has(key)) activeMetrics.delete(key); + else activeMetrics.add(key); + if (currentTrack) render(true); + } + + function setSmooth(metricKey: string, enabled: boolean, skipRender = false) { + const has = smoothedMetrics.has(metricKey); + if (enabled && !has) { + smoothedMetrics.add(metricKey); + if (currentTrack && !skipRender) render(true); + } else if (!enabled && has) { + smoothedMetrics.delete(metricKey); + if (currentTrack && !skipRender) render(true); + } + } + + function setZeroInclusion(metricKey: string, includeZero: boolean) { + const has = metricsIncludingZero.has(metricKey); + if (includeZero && !has) { + metricsIncludingZero.add(metricKey); + if (currentTrack) render(true); + } else if (!includeZero && has) { + metricsIncludingZero.delete(metricKey); + if (currentTrack) render(true); + } + } + + function setActiveMetrics(keys: string[]) { + activeMetrics = new Set(keys); + if (currentTrack) render(true); + // Sync the pill buttons + document.querySelectorAll('.metric-pill').forEach((el) => { + const pill = el as HTMLElement; + pill.classList.toggle('active', activeMetrics.has(pill.dataset.metric!)); + }); + } + + function setXAxis(axis: string) { + if (xAxis === axis || !currentTrack) { + xAxis = axis; + return; + } + + const pts = currentTrack.points; + const t0 = pts.find((p) => p.time != null)?.time || 0; + + // Helper to convert a value from OLD axis to NEW axis + const convert = (val: number | null) => { + if (val === null) return null; + // We need to find the point index closest to 'val' on the OLD axis + let idx = 0; + if (xAxis === 'distance') { + const dTarget = val * 1000; + idx = pts.findIndex((p) => (p.dist || 0) >= dTarget); + } else { + const tTarget = t0 + val * 1000; + idx = pts.findIndex((p) => (p.time || 0) >= tTarget); + } + if (idx === -1) idx = pts.length - 1; + + // Now get the value at this index on the NEW axis + if (axis === 'distance') return (pts[idx].dist || 0) / 1000; + else return ((pts[idx].time || t0) - t0) / 1000; + }; + + // Convert selection + if (selAnchorVal !== null) selAnchorVal = convert(selAnchorVal); + if (selEndVal !== null) selEndVal = convert(selEndVal); + + // Convert zoom range (from first plot) + let newRange: [number, number] | null = null; + if (plots.length) { + const u = plots[0].uplot; + const r0 = convert(u.scales.x!.min!); + const r1 = convert(u.scales.x!.max!); + if (r0 !== null && r1 !== null) newRange = [r0, r1]; + } + + xAxis = axis; + render(true); + + // Apply converted zoom + if (newRange) { + scaleSyncing = true; + plots.forEach(({ uplot: u }) => { + u.batch(() => u.setScale('x', { min: newRange![0], max: newRange![1] })); + }); + scaleSyncing = false; + } + } + + function toggleStats() { + statsVisible = !statsVisible; + plots.forEach(({ histCol }) => histCol.classList.toggle('visible', statsVisible)); + resize(); + } + + function setMapColorChangeCb(cb: (data: { pts: TrackPoint[]; colors: string[] } | null) => void) { + onMapColorChangeCb = cb; + } + + function toggleMapColor(key: string | null, skipRender = false) { + if (mapColorMetric === key || key === null) { + mapColorMetric = null; + _updateMapColorBtns(); + if (onMapColorChangeCb) onMapColorChangeCb(null); + } else { + mapColorMetric = key; + _updateMapColorBtns(); + _fireMapColorCb(); + } + // Re-render to show/hide colored fills on the graphs + if (currentTrack && !skipRender) render(true); + } + + function _updateMapColorBtns() { + document.querySelectorAll('.map-color-btn').forEach((el) => { + const b = el as HTMLElement; + b.classList.toggle('active', b.dataset.metric === mapColorMetric); + }); + } + + function _fireMapColorCb() { + if (!onMapColorChangeCb || !mapColorMetric || !currentTrack) return; + const plot = plots.find((p) => p.metricKey === mapColorMetric); + if (!plot) return; + const colors = _computePointColors(mapColorMetric, plot.yData); + onMapColorChangeCb({ pts: currentTrack.points, colors }); + } + + function _computePointColors(key: string, yData: (number | null)[]): string[] { + const vals = yData.filter((v): v is number => v != null && isFinite(v)); + if (!vals.length) return yData.map(() => '#888896'); + const min = Math.min(...vals), + max = Math.max(...vals); + + const sport = currentTrack?.sport; + const sportDef = sport ? SPORTS[sport.toLowerCase()] : null; + const defaultMode = sportDef?.defaultPreset || 'speed'; + const urlState = UrlState.get(); + const speedMode = (urlState as any).speed_mode || defaultMode; + const paceUnit = sportDef?.paceUnit || 'km'; + + return yData.map((v) => { + if (v == null || !isFinite(v)) return '#888896'; + if (key === 'gradient') return gradientColor(v); + if (key === 'speed') { + if (speedMode === 'pace') { + const speed = (paceUnit === 'km' ? 1000 : 100) / v; + return speedColor(speed); + } + return speedColor(v); + } + const t = max === min ? 0.5 : Math.max(0, Math.min(1, (v - min) / (max - min))); + if (t <= 0.25) return lerpHex('#4575b4', '#91bfdb', t / 0.25); + if (t <= 0.5) return lerpHex('#91bfdb', '#fee090', (t - 0.25) / 0.25); + if (t <= 0.75) return lerpHex('#fee090', '#fc8d59', (t - 0.5) / 0.25); + return lerpHex('#fc8d59', '#d73027', (t - 0.75) / 0.25); + }); + } + + function resetZoom() { + cancelSelection(); + } + + function cancelSelection() { + selAnchorVal = null; + selEndVal = null; + pinnedPtIdx = null; + if (onPinChangeCb) onPinChangeCb(null); + + // Reset individual histogram pins + plots.forEach((p) => { + p.pinnedHistY = null; + }); + + updateSelOverlay(); + redrawHistograms(); + + // Perform the actual zoom reset + scaleSyncing = true; + plots.forEach(({ uplot: u, xData }) => { + u.batch(() => u.setScale('x', { min: xData[0]!, max: xData[xData.length - 1]! })); + u._strasse?.updateHeaderStats(xData[0]!, xData[xData.length - 1]!); + }); + scaleSyncing = false; + + if (onRangeChangeCb) onRangeChangeCb(null, null, xAxis); + } + + function setSelectionStats(stats: TrackStats) { + if (!selStatsEl) return; + const fmt = (id: string, val: string) => { + const el = document.querySelector(`#${id} .stat-value`); + if (el) el.textContent = val; + }; + fmt( + 'sel-distance', + stats.totalDist != null ? `${(stats.totalDist / 1000).toFixed(1)} km` : '—', + ); + fmt('sel-duration', stats.duration != null ? fmtSecs(Math.floor(stats.duration / 1000)) : '—'); + fmt('sel-elevation', stats.elevGain != null ? `${Math.round(stats.elevGain)} m` : '—'); + fmt( + 'sel-avg-speed', + stats.avgSpeed != null ? `${(stats.avgSpeed * 3.6).toFixed(1)} km/h` : '—', + ); + fmt('sel-avg-power', stats.avgPower != null ? `${stats.avgPower} W` : '—'); + fmt('sel-avg-hr', stats.avgHR != null ? `${stats.avgHR} bpm` : '—'); + fmt('sel-avg-temp', stats.avgTemp != null ? `${stats.avgTemp.toFixed(1)} °C` : '—'); + selStatsEl.classList.remove('hidden'); + if (resetSelBtn) resetSelBtn.classList.remove('hidden'); + } + + function clearSelectionStats() { + if (selStatsEl) selStatsEl.classList.add('hidden'); + if (resetSelBtn) resetSelBtn.classList.add('hidden'); + [ + 'sel-distance', + 'sel-duration', + 'sel-elevation', + 'sel-avg-speed', + 'sel-avg-power', + 'sel-avg-hr', + 'sel-avg-temp', + ].forEach((id) => { + const el = document.querySelector(`#${id} .stat-value`); + if (el) el.textContent = '--'; + }); + } + + // Called on boot to restore a saved selection without re-firing onRangeChangeCb + // xMin/xMax are ALWAYS provided in Time (seconds from start) + function restoreSelection(tMin: number, tMax: number, deferRender = false) { + if (!currentTrack) return; + const pts = currentTrack.points; + const t0 = pts.find((p) => p.time != null)?.time || 0; + const totalDuration = ((pts[pts.length - 1]?.time || 0) - t0) / 1000; + + // If values look like absolute timestamps in ms, convert to seconds from start + if (tMin > 1e12) tMin = (tMin - t0) / 1000; + if (tMax > 1e12) tMax = (tMax - t0) / 1000; + + // Crop to max limits of the track + tMin = Math.max(0, Math.min(tMin, totalDuration)); + tMax = Math.max(0, Math.min(tMax, totalDuration)); + + const convert = (tOffset: number) => { + if (xAxis === 'time') return tOffset; + const tTarget = t0 + tOffset * 1000; + let idx = pts.findIndex((p) => (p.time || 0) >= tTarget); + if (idx === -1) idx = pts.length - 1; + return (pts[idx].dist || 0) / 1000; + }; + + const xMin = convert(tMin); + const xMax = convert(tMax); + + selAnchorVal = xMin; + selEndVal = xMax; + currentXRange = [xMin, xMax]; + if (deferRender) return; + scaleSyncing = true; // suppress the setScale → onRangeChangeCb loop + plots.forEach(({ uplot: u }) => { + u.batch(() => u.setScale('x', { min: xMin, max: xMax })); + }); + scaleSyncing = false; + updateAnchorLines(); + redrawHistograms(); + } + + function resize() { + if (!plots.length || !container) return; + const w = Math.round( + Math.max( + 100, + container.getBoundingClientRect().width - + ROW_BODY_PADDING - + (statsVisible ? HIST_W + HIST_GAP : 0), + ), + ); + let anyResized = false; + plots.forEach(({ uplot: u, histCanvas, histData }) => { + if (w !== u.width) { + u.setSize({ width: w, height: u.height }); + anyResized = true; + if (statsVisible && histCanvas && histData) drawHistogram(histCanvas, histData, u.height); + } + }); + if (anyResized) updateSelOverlay(); + } + + // ── Render ──────────────────────────────────────────────────── + function render(keepState = false) { + let savedRange: [number, number] | null = null; + let savedScroll = 0; + if (keepState && container) savedScroll = container.scrollTop; + + if (keepState && currentXRange) { + savedRange = [currentXRange[0], currentXRange[1]]; + } else if (keepState && plots.length) { + const u = plots[0].uplot; + savedRange = [u.scales.x!.min!, u.scales.x!.max!]; + } + + if (!currentTrack) { + destroyPlots(keepState); + return; + } + const pts = currentTrack.points; + if (!pts.length) { + destroyPlots(keepState); + return; + } + + const sport = currentTrack?.sport; + const sportDef = sport ? SPORTS[sport.toLowerCase()] : null; + const defaultMode = sportDef?.defaultPreset || 'speed'; + const urlState = UrlState.get(); + const speedMode = (urlState as any).speed_mode || defaultMode; + const paceUnit = sportDef?.paceUnit || 'km'; + + const available = [...activeMetrics].filter((m) => + pts.some((p) => p[METRICS[m].field] != null), + ); + if (!available.length) { + destroyPlots(keepState); + if (emptyEl) { + emptyEl.style.display = 'flex'; + emptyEl.innerHTML = ` + no_sim +
No data for selected metrics
+ `; + } + return; + } + if (emptyEl) emptyEl.style.display = 'none'; + + const containerW = container ? container.getBoundingClientRect().width : 800; + const w = Math.round( + Math.max(100, containerW - ROW_BODY_PADDING - (statsVisible ? HIST_W + HIST_GAP : 0)), + ); + + const canReuse = + keepState && + renderedTrack === currentTrack && + renderedXAxis === xAxis && + renderedSpeedMode === speedMode && + renderedMapColorMetric === mapColorMetric && + plots.length > 0; + + let xData: (number | null)[]; + if (canReuse) { + xData = plots[0].xData; + } else if (xAxis === 'distance') { + xData = pts.map((p) => (p.dist || 0) / 1000); + } else { + const t0 = pts.find((p) => p.time != null)?.time || 0; + let lastValidX = 0; + xData = pts.map((p) => { + if (p.time != null) { + lastValidX = (p.time - t0) / 1000; + return lastValidX; + } + return lastValidX; // monotonic fallback + }); + } + + const existingByKey = new Map(); + if (canReuse) { + if (histTooltipEl) histTooltipEl.style.display = 'none'; + const availSet = new Set(available); + for (const p of plots) { + const isSmoothed = smoothedMetrics.has(p.metricKey); + const isIncZero = metricsIncludingZero.has(p.metricKey); + if ( + availSet.has(p.metricKey) && + p.smoothed === isSmoothed && + p.incZero === isIncZero && + p.w === w + ) { + existingByKey.set(p.metricKey, p); + } else { + syncKey!.unsub(p.uplot); + p.uplot.destroy(); + p.row.remove(); + } + } + plots = []; + } else { + destroyPlots(keepState); + } + + if (container) { + const nextSiblingRows: (HTMLElement | null)[] = new Array(available.length).fill(null); + let nextExisting: HTMLElement | null = null; + for (let i = available.length - 1; i >= 0; i--) { + nextSiblingRows[i] = nextExisting; + const ex = existingByKey.get(available[i]); + if (ex) nextExisting = ex.row; + } + + available.forEach((key, idx) => { + const reused = existingByKey.get(key); + if (reused) { + plots.push(reused); + const uplot = reused.uplot; + const s = (uplot as any)._strasse; + const prevSync = scaleSyncing; + scaleSyncing = true; + uplot.batch(() => { + if ( + savedRange && + (uplot.scales.x!.min !== savedRange[0] || uplot.scales.x!.max !== savedRange[1]) + ) { + uplot.setScale('x', { min: savedRange[0], max: savedRange[1] }); + } else { + s?.updateHeaderStats(uplot.scales.x!.min!, uplot.scales.x!.max!, uplot); + uplot.redraw(false); + } + }); + scaleSyncing = prevSync; + + let initHoverBinI: number | null = null; + if ( + pinnedPtIdx != null && + reused.xData[pinnedPtIdx] != null && + reused.yData[pinnedPtIdx] != null + ) { + buildHistBins(reused.histData); + if (reused.histData.min != null && reused.histData.max != null) { + const span = reused.histData.max - reused.histData.min || 1; + initHoverBinI = Math.min( + reused.histData.BINS - 1, + Math.floor( + ((reused.yData[pinnedPtIdx]! - reused.histData.min) / span) * + reused.histData.BINS, + ), + ); + reused.hoveredHistY = reused.yData[pinnedPtIdx]; + } + } + drawHistogram(reused.histCanvas, reused.histData, 130, initHoverBinI); + return; + } + + let def = METRICS[key]; + + let transform = def.transform; + let fmt = def.fmt; + let fmtAxis = def.fmtAxis; + let unit = def.unit; + let label = def.label; + + if (key === 'speed' && speedMode === 'pace') { + label = 'Pace'; + unit = `min/${paceUnit}`; + fmt = (v) => fmtPace(v); + fmtAxis = (v) => fmtPace(v); + transform = (v) => { + if (v == null || v < 0.1) return null; + return (paceUnit === 'km' ? 1000 : 100) / v; + }; + + def = { ...def, label, unit, fmt, fmtAxis, transform }; + } + + let yData = def.compute + ? def.compute(pts, fillNulls) + : fillNulls( + pts.map((p) => { + const v = p[def.field] as number; + return v != null && transform ? transform(v) : v; + }), + ); + + if (smoothedMetrics.has(key)) yData = gaussianSmooth(yData, def.smoothSigma || 2); + + createChart(key, def, xData, yData, w, pts, savedRange, nextSiblingRows[idx]); + }); + } + + renderedTrack = currentTrack; + renderedXAxis = xAxis; + renderedSpeedMode = speedMode; + renderedMapColorMetric = mapColorMetric; + + updateStats(currentTrack); + // Restore anchor/end lines if a selection was active + if (selAnchorVal != null && selEndVal != null) updateAnchorLines(); + // Re-apply map color if active + _updateMapColorBtns(); + if (mapColorMetric) _fireMapColorCb(); + + if (keepState && container && savedScroll > 0) container.scrollTop = savedScroll; + } + + // ── Create one chart row ─────────────────────────────────────── + function createChart( + metricKey: string, + def: MetricDefinition, + xData: (number | null)[], + yData: (number | null)[], + w: number, + pts: TrackPoint[], + initialRange: [number, number] | null = null, + insertBeforeEl: HTMLElement | null = null, + ) { + const sport = currentTrack?.sport; + const sportDef = sport ? SPORTS[sport.toLowerCase()] : null; + const defaultMode = sportDef?.defaultPreset || 'speed'; + const urlState = UrlState.get(); + const speedMode = (urlState as any).speed_mode || defaultMode; + + // Gradient data: smooth over a 20 m distance window (±10 m each side) + const gradData = metricKey === 'elevation' ? smoothGradient(pts, 20) : null; + const row = document.createElement('div'); + row.className = 'chart-row'; + row.dataset.metric = metricKey; + row.style.setProperty('--chart-color', def.color); + + // Header + const header = document.createElement('div'); + header.className = `chart-row-header${statsVisible ? ' with-histogram' : ''}`; + + const labelEl = document.createElement('div'); + labelEl.className = 'chart-row-label-group'; + labelEl.innerHTML = ` + ${def.icon} + ${def.label} + `; + + // Smoothing toggle + const smoothBtn = document.createElement('button'); + smoothBtn.className = `icon-btn mini smooth-btn${smoothedMetrics.has(metricKey) ? ' active' : ''}`; + smoothBtn.title = 'Toggle data smoothing'; + smoothBtn.innerHTML = `${smoothedMetrics.has(metricKey) ? 'blur_on' : 'blur_off'}`; + smoothBtn.addEventListener('click', (e) => { + e.stopPropagation(); + if (smoothedMetrics.has(metricKey)) smoothedMetrics.delete(metricKey); + else smoothedMetrics.add(metricKey); + + render(true); // Re-render all charts to keep sync if needed (though only this one changes data) + }); + + const mapColorBtn = document.createElement('button'); + + mapColorBtn.className = `map-color-btn icon-btn${mapColorMetric === metricKey ? ' active' : ''}`; + mapColorBtn.dataset.metric = metricKey; + mapColorBtn.title = 'Color map track by this metric'; + mapColorBtn.innerHTML = 'colorize'; + mapColorBtn.addEventListener('click', () => toggleMapColor(metricKey)); + + // Zero-filter toggle (Cadence/Power/Speed) + let zeroBtn: HTMLButtonElement | null = null; + if (metricKey === 'cadence' || metricKey === 'power' || metricKey === 'speed') { + zeroBtn = document.createElement('button'); + const incZero = metricsIncludingZero.has(metricKey); + zeroBtn.className = `icon-btn mini${incZero ? ' active' : ''}`; + zeroBtn.title = incZero ? 'Currently INCLUDING 0 values' : 'Currently EXCLUDING 0 values'; + zeroBtn.innerHTML = `${incZero ? 'exposure_zero' : 'mobile_off'}`; + zeroBtn.addEventListener('click', (e) => { + e.stopPropagation(); + if (metricsIncludingZero.has(metricKey)) metricsIncludingZero.delete(metricKey); + else metricsIncludingZero.add(metricKey); + render(true); + }); + } + + const statsTotalEl = document.createElement('div'); + statsTotalEl.className = 'chart-stats-total'; + + const statsSelEl = document.createElement('div'); + statsSelEl.className = 'chart-stats-selection'; + + const statsContainer = document.createElement('div'); + statsContainer.className = 'chart-row-stats-container'; + statsContainer.append(statsTotalEl, statsSelEl); + + const actionsEl = document.createElement('div'); + actionsEl.className = 'chart-row-actions'; + actionsEl.append(smoothBtn, mapColorBtn); + + const rightActionsEl = document.createElement('div'); + rightActionsEl.className = 'chart-row-right-actions'; + + if (metricKey === 'speed') { + const paceToggle = document.createElement('div'); + paceToggle.className = 'seg-ctrl'; + paceToggle.innerHTML = ` + + + `; + paceToggle.addEventListener('click', (e) => { + const btn = (e.target as HTMLElement).closest('.seg-btn') as HTMLElement; + if (!btn) return; + const mode = btn.dataset.mode; + if (mode === 'speed' || mode === 'pace') { + const isDefault = mode === defaultMode; + UrlState.patch({ speed_mode: isDefault ? null : mode }); + render(true); + } + }); + rightActionsEl.append(paceToggle); + } + + if (zeroBtn) rightActionsEl.append(zeroBtn); + + header.append(labelEl, actionsEl, statsContainer, rightActionsEl); + + const rowBody = document.createElement('div'); + rowBody.className = 'chart-row-body'; + + const plotEl = document.createElement('div'); + plotEl.style.position = 'relative'; + rowBody.appendChild(plotEl); + + const fixedMinLabel = document.createElement('div'); + fixedMinLabel.className = 'fixed-x-label fixed-x-min'; + const fixedMaxLabel = document.createElement('div'); + fixedMaxLabel.className = 'fixed-x-label fixed-x-max'; + plotEl.append(fixedMinLabel, fixedMaxLabel); + + const histCol = document.createElement('div'); + histCol.className = `hist-col${statsVisible ? ' visible' : ''}`; + const histCanvas = document.createElement('canvas'); + histCanvas.className = 'hist-canvas'; + histCol.appendChild(histCanvas); + rowBody.appendChild(histCol); + + row.append(header, rowBody); + + const isDistAxis = xAxis === 'distance'; + + function formatX(v: number, range: number): string { + if (v == null) return ''; + if (isDistAxis) { + const dec = range < 1 ? 3 : range < 5 ? 2 : range < 20 ? 1 : 0; + return `${v.toFixed(dec)} km`; + } else { + const absV = Math.abs(v); + const h = Math.floor(absV / 3600); + const m = Math.floor((absV % 3600) / 60); + const s = Math.round(absV % 60); + const hh = h > 0 ? `${h}:` : ''; + const mm = h > 0 ? String(m).padStart(2, '0') : String(m); + const ss = String(s).padStart(2, '0'); + return `${v < 0 ? '-' : ''}${hh}${mm}:${ss}`; + } + } + + const incZeroDefault = + metricsIncludingZero.has(metricKey) || + (metricKey !== 'power' && metricKey !== 'cadence' && metricKey !== 'speed'); + + // Compute global Y range and full-track stats in a single pass + let gMin = Infinity, + gMax = -Infinity, + allSum = 0, + allCount = 0, + filtMin = Infinity, + filtMax = -Infinity, + filtSum = 0, + filtCount = 0; + for (let i = 0; i < yData.length; i++) { + const v = yData[i]; + if (v == null || !isFinite(v)) continue; + if (v < gMin) gMin = v; + if (v > gMax) gMax = v; + allSum += v; + allCount++; + if (v !== 0 || incZeroDefault) { + if (v < filtMin) filtMin = v; + if (v > filtMax) filtMax = v; + filtSum += v; + filtCount++; + } + } + if (allCount === 0) { + gMin = 0; + gMax = 100; + } + const fullStatsAll = allCount ? { min: gMin, max: gMax, avg: allSum / allCount } : null; + const fullStatsFiltered = filtCount + ? { min: filtMin, max: filtMax, avg: filtSum / filtCount } + : null; + const gPad = Math.abs(gMax - gMin) * 0.1 || 1; + const fixedYRange = [gMin - gPad, gMax + gPad]; + + let statsTotalInitialized = false; + + // Per-range stats + function updateHeaderStats(visibleMin: number, visibleMax: number, u?: uPlot) { + const incZero = + metricsIncludingZero.has(metricKey) || + (metricKey !== 'power' && metricKey !== 'cadence' && metricKey !== 'speed'); + + // Update fixed X labels + const range = visibleMax - visibleMin; + fixedMinLabel.textContent = formatX(visibleMin, range); + fixedMaxLabel.textContent = formatX(visibleMax, range); + + const getHtml = (xMin: number, xMax: number, isFull = false) => { + const s = + isFull && incZero === incZeroDefault + ? fullStatsFiltered + : rangeStats(xData, yData, xMin, xMax, incZero); + if (!s) return ''; + + let h = ` + align_flex_end${def.fmt(s.min, true)} ${def.unit} + align_center${def.fmt(s.avg, true)} ${def.unit} + align_flex_start${def.fmt(s.max, true)} ${def.unit} + `; + + if (metricKey === 'elevation') { + const es = elevationRangeStats(pts, xMin, xMax); + h += ` + trending_up+${Math.round(es.gain)} m + trending_down-${Math.round(es.loss)} m + `; + } + return h; + }; + + if (!statsTotalInitialized) { + statsTotalInitialized = true; + statsTotalEl.innerHTML = `all_inclusive${getHtml( + xData[0]!, + xData[xData.length - 1]!, + true, + )}`; + statsTotalEl.querySelector('.all-chip')?.addEventListener('click', (e) => { + e.stopPropagation(); + cancelSelection(); + }); + } + + if (selAnchorVal !== null && selEndVal !== null) { + statsSelEl.innerHTML = getHtml(selAnchorVal, selEndVal, false); + statsSelEl.style.display = 'flex'; + statsSelEl.style.setProperty('--sel-color', def.color); + } else { + statsSelEl.style.display = 'none'; + } + } + let initializingScale = false; + const uOpts: uPlot.Options = { + width: w, + height: 130, + padding: [4, 0, 0, 0], + cursor: { + sync: { + key: syncKey!.key, + }, + drag: { x: true, y: false, uni: 16 }, + focus: { prox: 16 }, + points: { + size: 7, + stroke: '#0e0e10', + width: 2, + fill: (metricKey === 'elevation' + ? (u: uPlot, _si: number) => gradientColor(gradData ? gradData[u.cursor.idx!]! : null) + : def.color) as any, + }, + x: false, + y: false, + }, + select: { + show: true, + left: 0, + top: 0, + width: 0, + height: 0, + }, + legend: { show: false }, + scales: { + x: { + time: false, + range: (_u: uPlot, mn: number, mx: number) => [mn, mx], + min: initialRange ? initialRange[0] : undefined, + max: initialRange ? initialRange[1] : undefined, + }, + y: { + range: () => fixedYRange as [number, number], + dir: metricKey === 'speed' && speedMode === 'pace' ? -1 : 1, + }, + }, + axes: [ + { + side: 2, // bottom + stroke: '#555564', + grid: { stroke: '#2e2e34', width: 1 }, + ticks: { + show: true, + stroke: '#2e2e34', + }, + size: 30, + space: 80, + font: '10px system-ui, sans-serif', + splits: (u: uPlot, axisIdx: number, scaleMin: number, scaleMax: number) => { + const range = scaleMax - scaleMin; + const width = u.bbox.width > 0 ? u.bbox.width : u.width; + const targetLabels = width / 80; + const idealIncr = range / targetLabels; + + let incrs; + if (!isDistAxis) { + // Time in seconds + incrs = [ + 1, 2, 5, 10, 15, 30, 60, 120, 300, 600, 900, 1800, 3600, 7200, 14400, 28800, 43200, + 86400, 604800, 2592000, 31536000, + ]; + } else { + incrs = [ + 0.01, 0.02, 0.05, 0.1, 0.2, 0.25, 0.5, 1, 2, 5, 10, 20, 50, 100, 200, 500, 1000, + 2000, 5000, 10000, 20000, 50000, 100000, 200000, 500000, 1000000, + ]; + } + + let incr = incrs[incrs.length - 1]; + for (let i = 0; i < incrs.length; i++) { + if (incrs[i] >= idealIncr) { + incr = incrs[i]; + break; + } + } + + const splits = []; + let val = Math.ceil(scaleMin / incr) * incr; + const precision = incr < 1 ? 2 : 0; + + while (val <= scaleMax) { + const pos = u.valToPos(val, 'x', true); + if (pos >= u.bbox.left + 80 && pos <= u.bbox.left + u.bbox.width - 80) + splits.push(Number(val.toFixed(precision))); + + val += incr; + } + return splits; + }, + values: ((_u: uPlot, vals: number[]) => { + const range = (_u.scales.x?.max ?? 1) - (_u.scales.x?.min ?? 0); + + return vals.map((v) => { + if (v == null) return ''; + const pos = _u.valToPos(v, 'x', true); + // Strict 80px margin to prevent overlapping with fixed labels or Y axis + if (pos < _u.bbox.left + 80 || pos > _u.bbox.left + _u.bbox.width - 80) return null; + return formatX(v, range); + }); + }) as any, + }, + { + side: 3, // left + stroke: '#555564', + grid: { stroke: '#2e2e34', width: 1 }, + ticks: { stroke: '#2e2e34' }, + size: 55, + font: '10px system-ui, sans-serif', + values: ((_u: uPlot, vals: number[]) => + vals.map((v) => (v != null ? def.fmtAxis(v) : ''))) as any, + }, + ], + series: [ + {}, + // Main selected track series + { + label: def.label, + stroke: 'rgba(0,0,0,0)', // Stroke handled in draw hooks for layering + width: 0, + points: { show: false }, + paths: () => null, + }, + ], + hooks: { + draw: [ + (u: uPlot) => { + const top = u.bbox.top + u.bbox.height + 5; // 5px below plot area! + fixedMinLabel.style.top = `${top}px`; + fixedMinLabel.style.left = `${u.bbox.left}px`; + + fixedMaxLabel.style.top = `${top}px`; + fixedMaxLabel.style.left = `${u.bbox.left + u.bbox.width}px`; + fixedMaxLabel.style.transform = 'translateX(-100%)'; + }, + // 1. (bottom) Color / metric color of the selected track + (u: uPlot) => { + if (metricKey === 'gears') { + drawTrackPath( + u, + xData as number[], + yData as number[], + def.color, + 1.5, + metricKey, + null, + pts, + ); + } else if (metricKey === 'elevation') { + drawElevationGradient(u, xData as number[], yData as number[], gradData); + } else if (metricKey === 'gradient') { + drawGradientChart(u, xData as number[], yData as number[]); + } else if (mapColorMetric === metricKey) { + drawMetricColorFill(u, xData as number[], yData as number[], pts, metricKey); + } else { + // Normal colored line + drawTrackPath( + u, + xData as number[], + yData as number[], + def.color, + 1.5, + metricKey, + null, + pts, + ); + } + }, + + // 2. Pinned point (middle) + (u: uPlot) => { + const plot = plots.find((p) => p.uplot === u); + if (!plot) return; + + const s = (u as any)._strasse; + // Priority 1: Global pinned point (all charts) + if (pinnedPtIdx != null) { + drawPinnedDot(u, xData, yData, def.color, def, pts, metricKey); + } else { + if (s && s.pinYVal) s.pinYVal.style.display = 'none'; + + // Priority 2: Individual histogram pinned Y-pos (this chart only) + if (plot.pinnedHistY != null) + drawYAxisHighlight(u, xData, yData, plot.pinnedHistY, def.color, def); + } + }, + + // 3. Pinned & Hover elements (labels, vertical lines) + (u: uPlot) => { + const hIdx = u.cursor.idx; + const hasPinned = pinnedPtIdx != null; + + let hasHover = hIdx != null && u.cursor.left! >= 0; + if (hasHover && pinnedPtIdx == null) { + const cy = u.valToPos(yData[hIdx!]!, 'y', true); + const dist = Math.abs(u.cursor.top! - cy); + if (dist > 100) hasHover = false; + } + + // B. Draw Hover vertical line + if (hasHover && hIdx !== pinnedPtIdx) + drawVerticalLineOnly(u, pts, hIdx!, hexToRgba(def.color, 0.4), 1.0); + + // C. Bold selection x-axis line & span pill + if (selAnchorVal != null && selEndVal != null) { + const ctx = u.ctx; + const dpr = window.devicePixelRatio || 1; + const bb = u.bbox; + const ax = u.valToPos(selAnchorVal, 'x', true); + const ex = u.valToPos(selEndVal, 'x', true); + + ctx.save(); + ctx.beginPath(); + ctx.lineWidth = 3 * dpr; + ctx.strokeStyle = def.color; + ctx.moveTo(ax, bb.top + bb.height); + ctx.lineTo(ex, bb.top + bb.height); + ctx.stroke(); + + const span = Math.abs(selEndVal - selAnchorVal); + const label = xAxis === 'distance' ? `${span.toFixed(2)} km` : fmtSecs(span); + const fontSize = 9 * dpr; + const fontStr = `bold ${fontSize}px system-ui, sans-serif`; + if (ctx.font !== fontStr) ctx.font = fontStr; + const tw = ctx.measureText(label).width; + + const visibleL = Math.max(bb.left, ax); + const visibleR = Math.min(bb.left + bb.width, ex); + const visibleW = visibleR - visibleL; + + if (visibleW > tw + 14 * dpr) { + const padH = 5 * dpr; + const padV = 2 * dpr; + const bw = tw + padH * 2; + const bh = fontSize + padV * 2; + const bx = (visibleL + visibleR) / 2 - bw / 2; + const by = bb.top + bb.height - bh / 2; + + ctx.beginPath(); + if ((ctx as any).roundRect) (ctx as any).roundRect(bx, by, bw, bh, 3 * dpr); + else ctx.rect(bx, by, bw, bh); + ctx.fillStyle = def.color; + ctx.fill(); + + ctx.fillStyle = '#000'; + ctx.textAlign = 'center'; + ctx.textBaseline = 'middle'; + ctx.fillText(label, bx + bw / 2, by + bh / 2); + } + ctx.restore(); + } + + // D. Draw label pills (on top) + // Pinned labels first (semi-transparent) + if (hasPinned) drawXAxisLabels(u, pts, pinnedPtIdx!, def.color, true, 0.7); + + // Hover labels last (fully opaque, topmost) + if (hasHover && hIdx !== pinnedPtIdx) + drawXAxisLabels(u, pts, hIdx!, def.color, true, 1.0); + }, + ], + setCursor: [ + (u: uPlot) => { + if (scaleSyncing) return; + const idx = u.cursor.idx; + + // Trigger redraw on cursor move to ensure custom canvas markers (hover lines/labels) update live + // Note: uPlot handles cross-chart sync when syncKey is used. + u.redraw(false); + + // Track current x-value for keyboard navigation + if (u.cursor.left! >= 0) lastMouseXVal = u.posToVal(u.cursor.left!, 'x'); + else lastMouseXVal = null; + + // ── Floating y-value next to cursor dot ── + const s = (u as any)._strasse; + let showHover = idx != null && yData[idx] != null; + + // Distance threshold: if no pinned point, check proximity to data line + if (showHover && pinnedPtIdx == null) { + const cy = u.valToPos(yData[idx!]!, 'y', true); + const dist = Math.abs(u.cursor.top! - cy); + if (dist > 100) showHover = false; + } + + if (s) { + if (showHover) + updateTooltip( + u, + s.curYVal, + idx!, + xData as number[], + yData as number[], + pts, + def, + metricKey, + ); + else s.curYVal.style.display = 'none'; + } + + // Map marker + if (!cursorSyncing) { + if (idx != null && pts[idx]) { + if (onCursorMoveCb && (pinnedPtIdx != null || showHover)) { + onCursorMoveCb(pts[idx]); + } else if (onCursorMoveCb && !showHover) { + // If we're too far and nothing is pinned, hide map cursor + onCursorMoveCb(null); + } + } else if (idx === null && onCursorMoveCb) { + onCursorMoveCb(null); + } + } + + // Highlight matching histogram bucket + if (idx != null && yData[idx] != null) { + const { min, max } = histData; + if (min != null && max != null) { + const span = max - min || 1; + const binI = Math.min( + histData.BINS - 1, + Math.floor(((yData[idx]! - min) / span) * histData.BINS), + ); + if (plot.hoveredHistY !== yData[idx]) { + plot.hoveredHistY = yData[idx]; + drawHistogram(histCanvas, histData, 130, binI); + } + } + } else if (plot.hoveredHistY !== null) { + plot.hoveredHistY = null; + drawHistogram(histCanvas, histData, 130); + } + }, + ], + + setScale: [ + (u: uPlot, key: string) => { + if (key !== 'x' || initializingScale) return; + const { min, max } = u.scales.x!; + + // Sync currentXRange so keyboard navigation baseline is always accurate + currentXRange = [min!, max!]; + + // Update header stats for VISIBLE range + updateHeaderStats(min as number, max as number, u); + + if (scaleSyncing) return; + + // Clear keyboard target when user interacts via mouse + if (!updatingRange) targetXRange = null; + + // Sync sibling plots + scaleSyncing = true; + plots.forEach(({ uplot: other }) => { + if (other !== u) other.batch(() => other.setScale('x', { min: min!, max: max! })); + }); + scaleSyncing = false; + + updateSelOverlay(); + redrawHistograms(); + + if (onRangeChangeCb && selAnchorVal !== null) + onRangeChangeCb(selAnchorVal, selEndVal, xAxis); + }, + ], + + setSelect: [ + (u: uPlot) => { + if (updatingRange) return; + targetXRange = null; + const { left, width } = u.select; + if (width > 0) { + const min = u.posToVal(left, 'x'); + const max = u.posToVal(left + width, 'x'); + selAnchorVal = min; + selEndVal = max; + + // Sync stats to new selection + updateHeaderStats(u.scales.x!.min!, u.scales.x!.max!, u); + updateSelOverlay(); + redrawHistograms(); + + if (onRangeChangeCb) onRangeChangeCb(selAnchorVal, selEndVal, xAxis); + + // Clear uPlot's internal selection immediately so it doesn't block WASD/interaction + u.setSelect({ left: 0, top: 0, width: 0, height: 0 }, false); + } + }, + ], + }, + }; + + // ── Overlay (pointer-events off except handles) ── + const overlay = document.createElement('div'); + overlay.style.cssText = + 'position:absolute;inset:0;pointer-events:none;overflow:visible;z-index:5'; + + // Selection fill + const selFill = document.createElement('div'); + selFill.className = 'sel-fill'; + selFill.style.display = 'none'; + + // Anchor line + label + drag handle + const anchorLine = document.createElement('div'); + anchorLine.className = 'sel-line sel-line-anchor'; + anchorLine.style.display = 'none'; + + const anchorLabel = document.createElement('div'); + anchorLabel.className = 'sel-line-label sel-line-label-anchor'; + anchorLabel.style.display = 'none'; + + const anchorHandle = document.createElement('div'); + anchorHandle.className = 'sel-handle sel-handle-anchor'; + anchorHandle.style.display = 'none'; + + const anchorMarker = document.createElement('div'); + anchorMarker.className = 'sel-graph-marker'; + anchorMarker.innerHTML = + 'play_circle'; + anchorMarker.style.display = 'none'; + + // End line + label + drag handle + const endLine = document.createElement('div'); + endLine.className = 'sel-line sel-line-end'; + endLine.style.display = 'none'; + + const endLabel = document.createElement('div'); + endLabel.className = 'sel-line-label sel-line-label-end'; + endLabel.style.display = 'none'; + + const endHandle = document.createElement('div'); + endHandle.className = 'sel-handle sel-handle-end'; + endHandle.style.display = 'none'; + + const endMarker = document.createElement('div'); + endMarker.className = 'sel-graph-marker'; + endMarker.innerHTML = + 'stop_circle'; + endMarker.style.display = 'none'; + + // Floating cursor y-value + const curYVal = document.createElement('div'); + curYVal.className = 'cur-y-val'; + curYVal.style.cssText = `color:${def.color};display:none`; + + // Floating pinned y-value + const pinYVal = document.createElement('div'); + pinYVal.className = 'cur-y-val pin'; + pinYVal.style.cssText = `color:${def.color};display:none`; + + overlay.append( + selFill, + anchorLine, + anchorLabel, + anchorHandle, + anchorMarker, + endLine, + endLabel, + endHandle, + endMarker, + curYVal, + pinYVal, + ); + + const histData: HistData = { + yData, + xData: xData as number[], + def, + BINS: 24, + min: allCount ? gMin : undefined, + max: allCount ? gMax : undefined, + fullStats: fullStatsFiltered, + fullStatsAll, + }; + const plot: PlotData = { + uplot: null as any, + row, + xData: xData as number[], + yData, + def, + metricKey, + statsTotalEl, + statsSelEl, + histCol, + histCanvas, + histData, + hoveredHistY: null, + pinnedHistY: null, + smoothed: smoothedMetrics.has(metricKey), + incZero: metricsIncludingZero.has(metricKey), + w, + }; + histData.plot = plot; // Back-reference for tooltip to trigger redraws + + const prevSync = scaleSyncing; + scaleSyncing = true; + const uplot = new uPlot(uOpts, [xData, yData] as any, (self, init) => { + plot.uplot = self as WegPlot; + plots.push(plot); + plotEl.appendChild(self.root); + self.over.appendChild(overlay); + (self as any)._strasse = { + selFill, + anchorLine, + anchorLabel, + anchorHandle, + anchorMarker, + endLine, + endLabel, + endHandle, + endMarker, + curYVal, + pinYVal, + updateHeaderStats, + }; + self.batch(() => { + init(); + }); + }) as WegPlot; + scaleSyncing = prevSync; + + // ── Handle drag ── + attachHandleDrag(anchorHandle, uplot, xData, true); + attachHandleDrag(endHandle, uplot, xData, false); + + // ── Mouse listeners ── + let mousedownX: number | null = null; + uplot.over.addEventListener('mousedown', (e) => { + if (e.button !== 0) return; + isDragging = false; + const rect = uplot.over.getBoundingClientRect(); + const xPx = e.clientX - rect.left; + mousedownX = xPx; + }); + uplot.over.addEventListener('mousemove', (e) => { + if (mousedownX !== null) { + const rect = uplot.over.getBoundingClientRect(); + if (Math.abs(e.clientX - rect.left - mousedownX) > 5) isDragging = true; + } + }); + uplot.over.addEventListener('mouseup', () => { + mousedownX = null; + }); + + // Click to pin point (sticky selection) + uplot.over.addEventListener('click', () => { + if (isDragging) return; + const idx = uplot.cursor.idx; + if (idx != null && pts[idx]) { + pinnedPtIdx = idx; + plots.forEach(({ uplot: u }) => u.redraw(false)); + if (onClickCb) onClickCb(pts[idx], idx); + if (onPinChangeCb) onPinChangeCb(idx); + } + }); + + row.addEventListener('dblclick', cancelSelection); + + let initHoverBinI: number | null = null; + if (pinnedPtIdx != null && xData[pinnedPtIdx] != null && yData[pinnedPtIdx] != null) { + buildHistBins(histData); + if (histData.min != null && histData.max != null) { + const span = histData.max - histData.min || 1; + initHoverBinI = Math.min( + histData.BINS - 1, + Math.floor(((yData[pinnedPtIdx]! - histData.min) / span) * histData.BINS), + ); + plot.hoveredHistY = yData[pinnedPtIdx]; + } + } + + drawHistogram(histCanvas, histData, 130, initHoverBinI); + attachHistTooltip(histCanvas, histData); + if (container) { + if (insertBeforeEl) container.insertBefore(row, insertBeforeEl); + else container.appendChild(row); + } + } + + // ── Selection overlay (fill + lines + handles) ─────────────── + function updateSelOverlay() { + updateAnchorLines(); + // Redraw canvas markers (bold selection line + span pill) + plots.forEach(({ uplot: u }) => u.batch(() => u.redraw(false))); + } + + function updateAnchorLines() { + plots.forEach(({ uplot: u, def }) => { + const s = (u as any)._strasse; + if (!s) return; + const { + selFill, + anchorLine, + anchorLabel, + anchorHandle, + anchorMarker, + endLine, + endLabel, + endHandle, + endMarker, + } = s; + if (selAnchorVal == null || selEndVal == null) { + [ + selFill, + anchorLine, + anchorLabel, + anchorHandle, + anchorMarker, + endLine, + endLabel, + endHandle, + endMarker, + ].forEach((el: HTMLElement) => { + el.style.display = 'none'; + }); + return; + } + const aX = u.valToPos(selAnchorVal, 'x'); + const eX = u.valToPos(selEndVal, 'x'); + const dpr = window.devicePixelRatio || 1; + const bb = u.bbox; + const overW = bb.width / dpr; + const overH = bb.height / dpr; + const plotBottom = (bb.top + bb.height) / dpr; + + const isAnchorVisible = aX >= 0 && aX <= overW; + const isEndVisible = eX >= 0 && eX <= overW; + + // Fill: clamp left/right to visible area + const fillL = Math.max(0, aX); + const fillR = Math.min(overW, eX); + if (fillR > fillL) { + selFill.style.left = `${fillL}px`; + selFill.style.width = `${fillR - fillL}px`; + selFill.style.top = '0'; + selFill.style.height = `${overH}px`; + selFill.style.backgroundColor = hexToRgba(def.color, 0.07); + selFill.style.display = ''; + } else { + selFill.style.display = 'none'; + } + + // Anchor line + if (isAnchorVisible) { + anchorLine.style.left = `${aX}px`; + anchorLine.style.height = `${overH}px`; + anchorLine.style.borderLeftColor = def.color; + anchorLine.style.display = ''; + // Anchor label + const aText = fmtXVal(selAnchorVal); + anchorLabel.textContent = aText; + const aLabelW = aText.length * 7 + 10; + anchorLabel.style.left = aX + aLabelW + 4 < overW ? `${aX + 3}px` : `${aX - aLabelW - 3}px`; + anchorLabel.style.display = ''; + // Anchor handle + anchorHandle.style.left = `${aX}px`; + anchorHandle.style.backgroundColor = def.color; + anchorHandle.style.display = ''; + // Anchor marker: centered on the bold line + anchorMarker.style.left = `${aX}px`; + anchorMarker.style.top = `${plotBottom}px`; + const aIcon = anchorMarker.querySelector('.material-symbols-rounded') as HTMLElement; + if (aIcon) aIcon.style.color = def.color; + anchorMarker.style.display = ''; + } else { + [anchorLine, anchorLabel, anchorHandle, anchorMarker].forEach( + (el: HTMLElement) => (el.style.display = 'none'), + ); + } + + // End line + if (isEndVisible) { + endLine.style.left = `${eX}px`; + endLine.style.height = `${overH}px`; + endLine.style.borderLeftColor = def.color; + endLine.style.display = ''; + // End label + const eText = fmtXVal(selEndVal); + endLabel.textContent = eText; + const eLabelW = eText.length * 7 + 10; + endLabel.style.left = eX - eLabelW - 3 > 0 ? `${eX - eLabelW - 3}px` : `${eX + 3}px`; + endLabel.style.display = ''; + // End handle + endHandle.style.left = `${eX}px`; + endHandle.style.backgroundColor = def.color; + endHandle.style.display = ''; + // End marker: centered on the bold line + endMarker.style.left = `${eX}px`; + endMarker.style.top = `${plotBottom}px`; + const eIcon = endMarker.querySelector('.material-symbols-rounded') as HTMLElement; + if (eIcon) eIcon.style.color = def.color; + endMarker.style.display = ''; + } else { + [endLine, endLabel, endHandle, endMarker].forEach( + (el: HTMLElement) => (el.style.display = 'none'), + ); + } + }); + } + + function fmtXVal(v: number) { + return xAxis === 'distance' ? `${v.toFixed(2)} km` : fmtSecs(v); + } + + // ── Handle drag logic ───────────────────────────────────────── + function attachHandleDrag( + handleEl: HTMLElement, + uplot: uPlot, + xData: (number | null)[], + isAnchor: boolean, + ) { + // pointer-events enabled on the handle itself + handleEl.style.pointerEvents = 'auto'; + + handleEl.addEventListener('mousedown', (e) => { + e.stopPropagation(); + e.preventDefault(); + const startX = e.clientX; + const startVal = isAnchor ? selAnchorVal : selEndVal; + const overW = uplot.over.offsetWidth; + // px-per-data-unit at current zoom + const xMin = uplot.scales.x!.min!, + xMax = uplot.scales.x!.max!; + const pxPerUnit = overW / (xMax - xMin); + const xFull = [xData[0]!, xData[xData.length - 1]!]; + + document.body.style.cursor = 'ew-resize'; + isDragging = true; + + function onMove(me: MouseEvent) { + const dx = me.clientX - startX; + let newVal = (startVal || 0) + dx / pxPerUnit; + newVal = Math.max(xFull[0], Math.min(xFull[1], newVal)); + + if (isAnchor) selAnchorVal = Math.min(newVal, (selEndVal || xFull[1]) - 0.001); + else selEndVal = Math.max(newVal, (selAnchorVal || xFull[0]) + 0.001); + + // When dragging handles, we typically want to stay zoomed to the selection? + // Actually, let's keep the zoom as is, and just update the selection overlay and stats. + plots.forEach((p) => + (p.uplot as any)._strasse?.updateHeaderStats( + p.uplot.scales.x!.min!, + p.uplot.scales.x!.max!, + ), + ); + updateSelOverlay(); + redrawHistograms(); + if (onRangeChangeCb) onRangeChangeCb(selAnchorVal, selEndVal, xAxis); + } + + function onUp() { + document.body.style.cursor = ''; + isDragging = false; + document.removeEventListener('mousemove', onMove); + document.removeEventListener('mouseup', onUp); + } + + document.addEventListener('mousemove', onMove); + document.addEventListener('mouseup', onUp); + }); + } + + // ── Histogram logic ────────────────────────────────────────── + function buildHistBins(histData: HistData) { + const { yData, xData, BINS } = histData; + + // Cache to avoid re-binning every frame during hover + if (histData._lastYData === yData && histData.bins) return; + histData._lastYData = yData; + + let min = histData.min; + let max = histData.max; + if (min == null || max == null) { + let lo = Infinity, + hi = -Infinity, + cnt = 0; + for (let i = 0; i < yData.length; i++) { + const v = yData[i]; + if (v == null || !isFinite(v)) continue; + if (v < lo) lo = v; + if (v > hi) hi = v; + cnt++; + } + if (!cnt) return; + min = lo; + max = hi; + histData.min = min; + histData.max = max; + } + + const bins = new Array(BINS).fill(0); + const binAccum = new Array(BINS).fill(0); // accumulated x-weight per bin (time in s or dist in km) + + const span = max - min || 1; + const metricKey = + histData.plot?.metricKey ?? Object.keys(METRICS).find((k) => METRICS[k] === histData.def); + const incZero = + !metricKey || + metricsIncludingZero.has(metricKey) || + (metricKey !== 'power' && metricKey !== 'cadence' && metricKey !== 'speed'); + + for (let i = 0; i < yData.length; i++) { + const v = yData[i]; + if (v == null || !isFinite(v)) continue; + if (v === 0 && !incZero) continue; + + const bi = Math.min(BINS - 1, Math.floor(((v - min) / span) * BINS)); + bins[bi]++; + + // weight by x-delta + const prev = i > 0 && xData[i - 1] != null ? xData[i - 1]! : xData[i]!; + const next = i < xData.length - 1 && xData[i + 1] != null ? xData[i + 1]! : xData[i]!; + binAccum[bi] += (next - prev) / 2; + } + histData.bins = bins; + histData.binAccum = binAccum; + } + + function redrawHistograms() { + plots.forEach(({ histCanvas, histData, uplot: u }) => { + drawHistogram(histCanvas, histData, u.height); + }); + } + + function drawHistogram( + canvas: HTMLCanvasElement, + histData: HistData, + chartH: number, + hoverBinI: number | null = null, + ) { + const { def, BINS } = histData; + buildHistBins(histData); + const { bins, min, max } = histData; + if (!bins || min == null || max == null) return; + + const hasSel = selAnchorVal != null && selEndVal != null; + + // Compute selection bins if a range is active and changed + if ( + hasSel && + (histData._lastSelRange?.[0] !== selAnchorVal || histData._lastSelRange?.[1] !== selEndVal) + ) { + histData._lastSelRange = [selAnchorVal!, selEndVal!]; + const selBins = new Array(BINS).fill(0); + const selBinAccum = new Array(BINS).fill(0); + const { yData, xData } = histData; + const span = max - min || 1; + const metricKey = + histData.plot?.metricKey ?? Object.keys(METRICS).find((k) => METRICS[k] === histData.def); + const incZero = + !metricKey || + metricsIncludingZero.has(metricKey) || + (metricKey !== 'power' && metricKey !== 'cadence' && metricKey !== 'speed'); + + for (let i = 0; i < yData.length; i++) { + const v = yData[i]; + if (v == null || !isFinite(v)) continue; + if (v === 0 && !incZero) continue; + + const x = xData[i]; + if (x == null || x < selAnchorVal! || x > selEndVal!) continue; + const bi = Math.min(BINS - 1, Math.floor(((v - min) / span) * BINS)); + selBins[bi]++; + const prev = i > 0 && xData[i - 1] != null ? xData[i - 1]! : xData[i]!; + const next = i < xData.length - 1 && xData[i + 1] != null ? xData[i + 1]! : xData[i]!; + selBinAccum[bi] += (next - prev) / 2; + } + histData.selBins = selBins; + histData.selBinAccum = selBinAccum; + } else if (!hasSel) { + histData.selBins = null; + histData.selBinAccum = null; + histData._lastSelRange = null; + } + const selBins = histData.selBins; + + const dpr = window.devicePixelRatio || 1; + const W = HIST_W; + const H = chartH; + + // Performance: only set dimensions if they changed (resets canvas) + if (canvas.width !== W * dpr || canvas.height !== H * dpr) { + canvas.width = W * dpr; + canvas.height = H * dpr; + canvas.style.width = `${W}px`; + canvas.style.height = `${H}px`; + } + + const ctx = canvas.getContext('2d')!; + ctx.resetTransform(); + ctx.scale(dpr, dpr); + ctx.clearRect(0, 0, W, H); + + const peak = Math.max(...bins); + + // Measure label width so bars start right after them + const fmt = (v: number) => def.fmtAxis(v); + const fontStr = '9px system-ui, sans-serif'; + if (ctx.font !== fontStr) ctx.font = fontStr; + const labelW = Math.ceil( + Math.max( + ctx.measureText(`${fmt(max)} ${def.unit}`).width, + ctx.measureText(`${fmt(min)} ${def.unit}`).width, + ), + ); + + // Vertical alignment with main graph: + // uPlot padding: [4, 0, 0, 0] + // Axis size: 30 + const pad = { t: 4, r: 0, b: 30, l: labelW + 6 }; + histData.padL = pad.l; + const plotW = W - pad.l - pad.r; + const plotH = H - pad.t - pad.b; + + // bin 0 = lowest values → drawn at bottom; bin BINS-1 = highest → drawn at top + const binY = (i: number) => pad.t + (BINS - 1 - i) * (plotH / BINS); + const binH = plotH / BINS - 1; + + // Full distribution (dimmed when selection active) + const baseAlpha = hasSel ? 0.15 : 0.6; + ctx.fillStyle = hexToRgba(def.color, baseAlpha); + for (let i = 0; i < BINS; i++) { + if (!bins[i]) continue; + const bw = (bins[i] / peak) * plotW; + ctx.fillRect(pad.l, binY(i), bw, binH); + } + + // Selection overlay (bright) + if (selBins) { + ctx.fillStyle = hexToRgba(def.color, 0.85); + for (let i = 0; i < BINS; i++) { + if (!selBins[i]) continue; + const bw = (selBins[i] / peak) * plotW; + ctx.fillRect(pad.l, binY(i), bw, binH); + } + ctx.fillStyle = def.color; + ctx.fillRect(pad.l, pad.t, 2, plotH); + } + + // CDF overlay — smooth curve from bottom (0%) to top (100%) + const total = bins.reduce((s, v) => s + v, 0); + if (total > 0) { + // Build CDF points: one per bin boundary, bottom→top + const pts2 = [{ x: pad.l, y: pad.t + plotH }]; + let cumSum = 0; + for (let i = 0; i < BINS; i++) { + cumSum += bins[i]; + pts2.push({ x: pad.l + (cumSum / total) * plotW, y: binY(i) }); + } + + ctx.beginPath(); + // CDF line is now solid + ctx.setLineDash([]); + ctx.strokeStyle = def.color; + ctx.lineWidth = 0.75 * dpr; + ctx.lineJoin = 'round'; + ctx.moveTo(pts2[0].x, pts2[0].y); + for (let i = 1; i < pts2.length - 1; i++) { + const mx = (pts2[i].x + pts2[i + 1].x) / 2; + const my = (pts2[i].y + pts2[i + 1].y) / 2; + ctx.quadraticCurveTo(pts2[i].x, pts2[i].y, mx, my); + } + ctx.lineTo(pts2[pts2.length - 1].x, pts2[pts2.length - 1].y); + ctx.stroke(); + + // ── Hover highlight on CDF ── + if (hoverBinI != null && pts2[hoverBinI + 1]) { + const cp = pts2[hoverBinI + 1]; + // Vertical line down + ctx.beginPath(); + ctx.setLineDash([2 * dpr, 2 * dpr]); + ctx.strokeStyle = hexToRgba(def.color, 0.6); + ctx.moveTo(cp.x, cp.y); + ctx.lineTo(cp.x, pad.t + plotH); + ctx.stroke(); + ctx.setLineDash([]); + + // Dot: tiny precision point + ctx.beginPath(); + ctx.arc(cp.x, cp.y, 1.2 * dpr, 0, Math.PI * 2); + ctx.fillStyle = '#0e0e10'; + ctx.fill(); + ctx.strokeStyle = def.color; + ctx.lineWidth = 1 * dpr; + ctx.stroke(); + } + } + + // Axis labels: max at top, min at bottom + if (ctx.fillStyle !== '#888896') ctx.fillStyle = '#888896'; + const font9 = '9px system-ui, sans-serif'; + if (ctx.font !== font9) ctx.font = font9; + ctx.textAlign = 'right'; + + // Draw Max + ctx.textBaseline = 'top'; + ctx.fillText(`${fmt(max)} ${def.unit}`, pad.l - 4, pad.t); + + // Draw Min + ctx.textBaseline = 'bottom'; + ctx.fillText(`${fmt(min)} ${def.unit}`, pad.l - 4, H - pad.b); + + // Draw Avg (only if it doesn't overlap min/max) + const stats = + !hasSel && histData.fullStatsAll !== undefined + ? histData.fullStatsAll + : rangeStats( + histData.xData, + histData.yData, + selAnchorVal || -Infinity, + selEndVal || Infinity, + ); + if (stats) { + const avgY = + pad.t + + (BINS - 1 - Math.min(BINS - 1, Math.floor(((stats.avg - min) / (max - min || 1)) * BINS))) * + (plotH / BINS); + const minLabelY = H - pad.b; + const maxLabelY = pad.t; + const labelHeight = 10; // Approx 10px height for 9px font + + if (avgY > maxLabelY + labelHeight + 2 && avgY < minLabelY - labelHeight - 2) { + ctx.textBaseline = 'middle'; + if (ctx.fillStyle !== '#aaa') ctx.fillStyle = '#aaa'; + ctx.fillText(`${fmt(stats.avg)} ${def.unit}`, pad.l - 4, avgY); + // Draw small indicator tick + ctx.beginPath(); + ctx.moveTo(pad.l - 3, avgY); + ctx.lineTo(pad.l, avgY); + if (ctx.strokeStyle !== '#aaa') ctx.strokeStyle = '#aaa'; + ctx.lineWidth = 1; + ctx.stroke(); + } + } + + // ── Higher/Lower percentages below histogram ── + if (hoverBinI != null && total > 0) { + let countLower = 0; + for (let i = 0; i < hoverBinI; i++) countLower += bins[i]; + let countHigher = 0; + for (let i = hoverBinI + 1; i < BINS; i++) countHigher += bins[i]; + + const pLower = Math.round((countLower / total) * 100); + const pHigher = Math.round((countHigher / total) * 100); + + ctx.save(); + ctx.textAlign = 'left'; + ctx.textBaseline = 'top'; + const font8 = '8px system-ui, sans-serif'; + if (ctx.font !== font8) ctx.font = font8; + + const textY = H - pad.b + 6; + + // Draw "Lower" stat + if (ctx.fillStyle !== '#888896') ctx.fillStyle = '#888896'; + ctx.fillText('LOWER', pad.l, textY); + if (ctx.fillStyle !== '#ccc') ctx.fillStyle = '#ccc'; + const fontB10 = 'bold 10px system-ui, sans-serif'; + if (ctx.font !== fontB10) ctx.font = fontB10; + ctx.fillText(`${pLower}%`, pad.l, textY + 10); + + // Draw "Higher" stat + const midX = pad.l + plotW / 2; + if (ctx.font !== font8) ctx.font = font8; + if (ctx.fillStyle !== '#888896') ctx.fillStyle = '#888896'; + ctx.fillText('HIGHER', midX, textY); + if (ctx.fillStyle !== '#ccc') ctx.fillStyle = '#ccc'; + if (ctx.font !== fontB10) ctx.font = fontB10; + ctx.fillText(`${pHigher}%`, midX, textY + 10); + + ctx.restore(); + } + } + + function attachHistTooltip(canvas: HTMLCanvasElement, histData: HistData) { + const { plot } = histData; + + canvas.addEventListener('click', (e) => { + const binI = getBinAt(canvas, histData, e); + if (binI != null && plot) { + const { min, max, BINS } = histData; + const span = max! - min! || 1; + plot.pinnedHistY = min! + (binI + 0.5) * (span / BINS!); + plots.forEach(({ uplot: u }) => u.redraw(false)); + } + }); + + canvas.addEventListener('mousemove', (e) => { + lastHistMouseEvent = e; + updateHistTooltip(e, canvas, histData); + }); + + canvas.addEventListener('mouseenter', () => { + hoveredHistCanvas = canvas; + hoveredHistData = histData; + }); + + canvas.addEventListener('mouseleave', () => { + if (hoveredHistCanvas === canvas) { + hoveredHistCanvas = null; + hoveredHistData = null; + } + if (histTooltipEl) histTooltipEl.style.display = 'none'; + const lineEl = document.getElementById('hist-line'); + if (lineEl) lineEl.style.display = 'none'; + if (plot) { + plot.hoveredHistY = null; + drawHistogram(canvas, histData, canvas.height / (window.devicePixelRatio || 1)); + } + }); + } + + function getBinAt(canvas: HTMLCanvasElement, histData: HistData, e: MouseEvent) { + const { bins, BINS } = histData; + if (!bins) return null; + const rect = canvas.getBoundingClientRect(); + const relY = e.clientY - rect.top; + // Use exact padding from drawHistogram: t: 4, b: 30 + const pad = { t: 4, b: 30 }; + const plotH = rect.height - pad.t - pad.b; + const rawI = Math.floor(((relY - pad.t) / plotH) * BINS!); + const binI = BINS! - 1 - rawI; + if (rawI < 0 || rawI >= BINS!) return null; + return binI; + } + + function updateHistTooltip(e: MouseEvent, canvas: HTMLCanvasElement, histData: HistData) { + if (!histTooltipEl) return; + const { bins, binAccum, min, max, BINS, def, plot } = histData; + if (!bins || !binAccum || min == null || max == null) return; + + const lineEl = document.getElementById('hist-line'); + const binI = getBinAt(canvas, histData, e); + + if (binI == null || !bins[binI]) { + histTooltipEl.style.display = 'none'; + if (lineEl) lineEl.style.display = 'none'; + if (plot) { + plot.hoveredHistY = null; + drawHistogram(canvas, histData, canvas.height / (window.devicePixelRatio || 1)); + } + return; + } + + if (plot) { + const span = max - min || 1; + plot.hoveredHistY = min + (binI + 0.5) * (span / BINS!); + drawHistogram(canvas, histData, canvas.height / (window.devicePixelRatio || 1), binI); + } + + const count = bins[binI]; + const total = bins.reduce((s, v) => s + v, 0); + const pct = (count / total) * 100; + + const span = max - min || 1; + const low = min + binI * (span / BINS!); + const high = min + (binI + 1) * (span / BINS!); + const label = `${def.fmt(low, true)} – ${def.fmt(high, true)} ${def.unit}`; + + // Zone info + let zoneHtml = ''; + const mid = (low + high) / 2; + const metricKey = Object.keys(METRICS).find((k) => METRICS[k] === def); + let zones: any[] = []; + if (metricKey === 'power') zones = Zones.getPowerZones(); + else if (metricKey === 'hr') zones = Zones.getHRZones(); + + if (zones.length > 0) { + const zIdx = zones.findIndex((z) => mid >= z.min && mid < z.max); + const z = + zIdx !== -1 + ? zones[zIdx] + : mid >= zones[zones.length - 1].min + ? zones[zones.length - 1] + : null; + if (z) { + const finalZIdx = zIdx !== -1 ? zIdx : zones.length - 1; + zoneHtml = ` +
+ Z${finalZIdx + 1} + · + ${z.name} +
+ `; + } + } + + const totalAccum = + xAxis === 'distance' ? `${binAccum[binI].toFixed(2)} km` : fmtSecs(binAccum[binI]); + const totalPct = (binAccum[binI] / binAccum.reduce((s, v) => s + v, 0)) * 100; + + let html = ` +
${label}
+ ${zoneHtml} + `; + + if (totalAccum) { + html += `
+ ${xAxis === 'distance' ? 'Dist' : 'Time'} + ${totalAccum} + ${totalPct.toFixed(1)}% +
`; + } + + // Selection context + if (histData.selBins && histData.selBinAccum) { + const stotalAccum = + xAxis === 'distance' + ? `${histData.selBinAccum[binI].toFixed(2)} km` + : fmtSecs(histData.selBinAccum[binI]); + const stotalPct = + (histData.selBinAccum[binI] / histData.selBinAccum.reduce((s, v) => s + v, 0)) * 100; + + html += ` +
+ Sel ${xAxis === 'distance' ? 'Dist' : 'Time'} + ${stotalAccum} + ${stotalPct.toFixed(1)}% +
+ `; + } + + histTooltipEl.innerHTML = html; + + // Position tooltip: fixed X left of canvas, Y centered on bin + histTooltipEl.style.display = 'block'; + const ttH = histTooltipEl.offsetHeight; + const ttW = histTooltipEl.offsetWidth; + const rect = canvas.getBoundingClientRect(); + const plotH = rect.height - 4 - 30; // pad.t + pad.b + const binCenterY = rect.top + 4 + (BINS! - 1 - binI + 0.5) * (plotH / BINS!); + + histTooltipEl.style.left = `${rect.left - ttW - 12}px`; + histTooltipEl.style.top = `${binCenterY - ttH / 2}px`; + + // Horizontal line sync + if (lineEl) { + lineEl.style.display = 'block'; + lineEl.style.top = `${binCenterY}px`; + lineEl.style.left = '0px'; + lineEl.style.width = `${rect.left}px`; + } + } + + // ── Helpers ─────────────────────────────────────────────────── + function destroyPlots(keepState = false) { + if (histTooltipEl) histTooltipEl.style.display = 'none'; + plots.forEach(({ uplot: u, row }) => { + syncKey!.unsub(u); + u.destroy(); + row.remove(); + }); + plots = []; + renderedTrack = null; + if (!keepState) { + selAnchorVal = null; + selEndVal = null; + } + isDragging = false; + } + + function rangeStats( + xData: (number | null)[], + yData: (number | null)[], + xMin: number, + xMax: number, + incZero = true, + ) { + let min = Infinity, + max = -Infinity, + sum = 0, + n = 0; + for (let i = 0; i < xData.length; i++) { + if (xData[i] == null || xData[i]! < xMin || xData[i]! > xMax) continue; + const v = yData[i]; + if (v == null || !isFinite(v)) continue; + if (v === 0 && !incZero) continue; + + if (v < min) min = v; + if (v > max) max = v; + sum += v; + n++; + } + return n ? { min, max, avg: sum / n } : null; + } + + function elevationRangeStats(pts: TrackPoint[], xMin: number, xMax: number) { + let gain = 0, + loss = 0; + let prevEle: number | null = null; + const isDist = xAxis === 'distance'; + const t0 = pts[0].time || 0; + + for (let i = 0; i < pts.length; i++) { + const p = pts[i]; + const x = isDist ? (p.dist || 0) / 1000 : (p.time! - t0) / 1000; + if (x < xMin || x > xMax) continue; + + if (p.ele != null) { + if (prevEle != null) { + const diff = p.ele - prevEle; + if (diff > 0) gain += diff; + else if (diff < 0) loss += Math.abs(diff); + } + prevEle = p.ele; + } + } + return { gain, loss }; + } + + // ── Plot Rendering Helpers (Canvas) ─────────────────────────── + function updateTooltip( + u: WegPlot, + el: HTMLElement, + idx: number, + xData: (number | null)[], + yData: (number | null)[], + pts: TrackPoint[], + def: MetricDefinition, + metricKey: string, + ) { + if (idx == null || yData[idx] == null) { + el.style.display = 'none'; + return; + } + + const cx = u.valToPos(xData[idx]!, 'x', false); + const cy = u.valToPos(yData[idx]!, 'y', false); + + let gColor = '#888896'; + let g = 0; + if (idx > 0 && pts[idx] && pts[idx - 1]) { + const p0 = pts[idx - 1], + p1 = pts[idx]; + const d = (p1.dist || 0) - (p0.dist || 0); + if (d > 0.1 && p1.ele != null && p0.ele != null) { + g = ((p1.ele - p0.ele) / d) * 100; + gColor = gradientColor(g); + } + } + + let content = + metricKey !== 'elevation' + ? `${def.fmt(yData[idx]!, false)} ${def.unit}` + : `${def.fmt(yData[idx]!, false)} ${def.unit} ∠ ${Math.abs(g).toFixed(1)}%`; + + // Special handling for gears: show tooth count if available + if (metricKey === 'gearRear' && pts[idx].gearRearTooth != null) { + content = `${pts[idx].gearRearTooth}T (pos ${yData[idx]})`; + } else if (metricKey === 'gearFront' && pts[idx].gearFrontTooth != null) { + content = `${pts[idx].gearFrontTooth}T (pos ${yData[idx]})`; + } else if (metricKey === 'gears') { + const p = pts[idx]; + const front = + p.gearFrontTooth != null + ? `${p.gearFrontTooth}T` + : p.gearFront != null + ? `pos ${p.gearFront}` + : ''; + const rear = + p.gearRearTooth != null + ? `${p.gearRearTooth}T` + : p.gearRear != null + ? `pos ${p.gearRear}` + : ''; + + if (front || rear) + content += ` (${front} / ${rear})`; + } + + if (el.innerHTML !== content) el.innerHTML = content; + + const color44 = `${def.color}44`; + if (el.style.color !== def.color) el.style.color = def.color; + if (el.style.borderColor !== color44) el.style.borderColor = color44; + + const left = `${cx}px`; + const top = `${cy}px`; + const transform = 'translate(6px, -50%)'; + if (el.style.left !== left) el.style.left = left; + if (el.style.top !== top) el.style.top = top; + if (el.style.transform !== transform) el.style.transform = transform; + if (el.style.display !== '') el.style.display = ''; + } + + const gradientColorRgbaCache = new Map(); + function gradientColorRgba(g: number | null): string { + let c = gradientColorRgbaCache.get(g); + if (!c) { + c = hexToRgba(gradientColor(g), 0.8); + gradientColorRgbaCache.set(g, c); + } + return c; + } + + function drawElevationGradient( + u: uPlot, + xData: number[], + yData: number[], + gradData: (number | null)[] | null, + ) { + const ctx = u.ctx; + const bb = u.bbox; + const dpr = window.devicePixelRatio || 1; + const xMin = u.scales.x?.min ?? -Infinity; + const xMax = u.scales.x?.max ?? Infinity; + const yMin = u.scales.y?.min ?? 0; + const yMax = u.scales.y?.max ?? 100; + const xScale = bb.width / (xMax - xMin || 1); + const yScale = bb.height / (yMax - yMin || 1); + const yBase = bb.top + bb.height; + + const idxs = u.series[0]?.idxs; + const iStart = idxs ? Math.max(1, idxs[0]) : 1; + const iEnd = idxs ? Math.min(xData.length - 1, idxs[1] + 1) : xData.length - 1; + const step = Math.max(1, Math.floor((iEnd - iStart) / 250)); + + ctx.save(); + ctx.lineWidth = 1.5 * dpr; + + const byColor = new Map(); + + let prevIdx = iStart - 1; + for (let i = iStart; i <= iEnd; i += step) { + const xv1 = xData[i]; + const xv0 = xData[prevIdx]; + prevIdx = i; + if (xv1 == null || yData[i] == null || xv0 == null || yData[i - step] == null) continue; + if (xv1 < xMin || xv0 > xMax) continue; + + const x0 = bb.left + (xv0 - xMin) * xScale; + const y0 = yBase - (yData[i - step]! - yMin) * yScale; + const x1 = bb.left + (xv1 - xMin) * xScale; + const y1 = yBase - (yData[i]! - yMin) * yScale; + + const g = gradData && gradData[i] != null ? Math.round(gradData[i]! / 2) * 2 : null; + const color = gradientColorRgba(g); + let segs = byColor.get(color); + if (!segs) { + segs = []; + byColor.set(color, segs); + } + segs.push(x0, y0, x1, y1); + } + + for (const [color, segs] of byColor) { + ctx.beginPath(); + ctx.strokeStyle = color; + for (let j = 0; j < segs.length; j += 4) { + ctx.moveTo(segs[j], segs[j + 1]); + ctx.lineTo(segs[j + 2], segs[j + 3]); + } + ctx.stroke(); + } + + ctx.restore(); + } + + function drawGradientChart(u: uPlot, xData: number[], yData: number[]) { + const ctx = u.ctx; + const bb = u.bbox; + const dpr = window.devicePixelRatio || 1; + const xMin = u.scales.x?.min ?? -Infinity; + const xMax = u.scales.x?.max ?? Infinity; + const yMin = u.scales.y?.min ?? 0; + const yMax = u.scales.y?.max ?? 100; + const xScale = bb.width / (xMax - xMin || 1); + const yScale = bb.height / (yMax - yMin || 1); + const yBase = bb.top + bb.height; + + const idxs = u.series[0]?.idxs; + const iStart = idxs ? Math.max(1, idxs[0]) : 1; + const iEnd = idxs ? Math.min(xData.length - 1, idxs[1] + 1) : xData.length - 1; + const step = Math.max(1, Math.floor((iEnd - iStart) / 250)); + + ctx.save(); + ctx.lineWidth = 1.5 * dpr; + + const byColor = new Map(); + + let prevIdx = iStart - 1; + for (let i = iStart; i <= iEnd; i += step) { + const xv1 = xData[i]; + const xv0 = xData[prevIdx]; + prevIdx = i; + if (xv1 == null || yData[i] == null || xv0 == null || yData[i - step] == null) continue; + if (xv1 < xMin || xv0 > xMax) continue; + + const x0 = bb.left + (xv0 - xMin) * xScale; + const y0 = yBase - (yData[i - step]! - yMin) * yScale; + const x1 = bb.left + (xv1 - xMin) * xScale; + const y1 = yBase - (yData[i]! - yMin) * yScale; + + const g = Math.round(yData[i]! / 2) * 2; + const color = gradientColorRgba(g); + let segs = byColor.get(color); + if (!segs) { + segs = []; + byColor.set(color, segs); + } + segs.push(x0, y0, x1, y1); + } + + for (const [color, segs] of byColor) { + ctx.beginPath(); + ctx.strokeStyle = color; + for (let j = 0; j < segs.length; j += 4) { + ctx.moveTo(segs[j], segs[j + 1]); + ctx.lineTo(segs[j + 2], segs[j + 3]); + } + ctx.stroke(); + } + + ctx.restore(); + } + + function drawHoverLine(u: uPlot, yData: number[], color: string, pts: TrackPoint[]) { + const idx = u.cursor.idx; + if (idx == null || yData[idx] == null || u.cursor.left! < 0) return; + + const t0 = pts[0].time || 0; + const xVal = xAxis === 'distance' ? (pts[idx].dist || 0) / 1000 : (pts[idx].time! - t0) / 1000; + const cx = u.valToPos(xVal, 'x', true); + const cy = u.valToPos(yData[idx], 'y', true); + const bb = u.bbox; + const ctx = u.ctx; + const dpr = window.devicePixelRatio || 1; + + // Line from top to point + horizontal from left + ctx.save(); + ctx.beginPath(); + ctx.setLineDash([2 * dpr, 2 * dpr]); + ctx.strokeStyle = hexToRgba(color, 0.4); + ctx.lineWidth = 1 * dpr; + ctx.moveTo(cx, bb.top); + ctx.lineTo(cx, cy); + ctx.moveTo(bb.left, cy); + ctx.lineTo(cx, cy); + ctx.stroke(); + + // Dot at point + ctx.beginPath(); + ctx.setLineDash([]); // Ensure solid outline + ctx.arc(cx, cy, 3 * dpr, 0, Math.PI * 2); + ctx.fillStyle = '#0e0e10'; + ctx.fill(); + ctx.strokeStyle = color; + ctx.lineWidth = 2 * dpr; + ctx.stroke(); + ctx.restore(); + } + + function drawVerticalLineOnly( + u: uPlot, + pts: TrackPoint[], + idx: number, + color: string, + opacity: number = 1.0, + ) { + const dpr = window.devicePixelRatio || 1; + const bb = u.bbox; + const ctx = u.ctx; + const xVal = + xAxis === 'distance' + ? (pts[idx].dist || 0) / 1000 + : (pts[idx].time! - (pts[0].time || 0)) / 1000; + const cx = u.valToPos(xVal, 'x', true); + + ctx.save(); + ctx.globalAlpha = opacity; + ctx.beginPath(); + ctx.strokeStyle = color; + ctx.lineWidth = 1 * dpr; + // Dotted line range should match where labels are (flush top to bottom axis) + ctx.moveTo(cx, bb.top + 16 * dpr); + ctx.lineTo(cx, bb.top + bb.height + 10 * dpr); + ctx.stroke(); + ctx.restore(); + } + + function drawXAxisLabels( + u: uPlot, + pts: TrackPoint[], + idx: number, + color: string, + skipLine = false, + opacity: number = 1.0, + ) { + const dpr = window.devicePixelRatio || 1; + const bb = u.bbox; + const ctx = u.ctx; + const t0 = pts[0].time || 0; + const distVal = (pts[idx].dist || 0) / 1000; + const timeVal = (pts[idx].time! - t0) / 1000; + const cx = u.valToPos(xAxis === 'distance' ? distVal : timeVal, 'x', true); + + // Bottom label: matches the current X axis unit + const bottomLabel = xAxis === 'distance' ? `${distVal.toFixed(2)} km` : fmtSecs(timeVal); + // Top label: shows the other unit (simplified) + const topLabel = xAxis === 'distance' ? fmtSecs(timeVal) : `${distVal.toFixed(2)} km`; + + const drawPill = (text: string, x: number, y: number, isTop: boolean) => { + const fontStr = `bold ${10 * dpr}px system-ui, sans-serif`; + if (ctx.font !== fontStr) ctx.font = fontStr; + const tw = ctx.measureText(text).width; + const bw = tw + 10 * dpr; + const bh = 16 * dpr; + const rx = x - bw / 2; + const clampedRx = Math.max(bb.left, Math.min(bb.left + bb.width - bw, rx)); + const ry = y - bh / 2; + + ctx.save(); + ctx.globalAlpha = opacity; + ctx.setLineDash([]); // Ensure pills are not dashed + ctx.beginPath(); + if ((ctx as any).roundRect) (ctx as any).roundRect(clampedRx, ry, bw, bh, 3 * dpr); + else ctx.rect(clampedRx, ry, bw, bh); + ctx.fillStyle = 'rgba(14,14,16,0.92)'; + ctx.fill(); + ctx.strokeStyle = hexToRgba(color, 0.27); + ctx.lineWidth = 1 * dpr; + ctx.stroke(); + + ctx.fillStyle = color; + ctx.textAlign = 'center'; + ctx.textBaseline = 'middle'; + ctx.fillText(text, clampedRx + bw / 2, ry + bh / 2 + 0.5 * dpr); + ctx.restore(); + }; + + if (!skipLine) drawVerticalLineOnly(u, pts, idx, color, opacity); + + const pillH = 16 * dpr; + // Top label: flush to the top edge of the plot + drawPill(topLabel, cx, bb.top + pillH / 2, true); + + // Bottom label: aligned with the horizontal axis labels (centered in the 30px axis area) + drawPill(bottomLabel, cx, bb.top + bb.height + 15 * dpr, false); + } + + function drawPinnedDot( + u: uPlot, + xData: (number | null)[], + yData: (number | null)[], + color: string, + def: MetricDefinition, + pts: TrackPoint[], + metricKey: string, + ) { + if (pinnedPtIdx == null) return; + const xVal = xData[pinnedPtIdx]; + const yVal = yData[pinnedPtIdx]; + if (xVal == null || yVal == null) return; + + const cx = u.valToPos(xVal, 'x', true); + const cy = u.valToPos(yVal, 'y', true); + const bb = u.bbox; + const ctx = u.ctx; + const dpr = window.devicePixelRatio || 1; + + ctx.save(); + // Vertical line + ctx.beginPath(); + ctx.strokeStyle = color; + ctx.lineWidth = 1.5 * dpr; + ctx.moveTo(cx, bb.top); + ctx.lineTo(cx, bb.top + bb.height); + ctx.stroke(); + + // Horizontal dashed line + ctx.beginPath(); + ctx.setLineDash([3 * dpr, 3 * dpr]); + ctx.strokeStyle = hexToRgba(color, 0.5); + ctx.lineWidth = 1 * dpr; + ctx.moveTo(bb.left, cy); + ctx.lineTo(cx, cy); + ctx.stroke(); + + // Large Dot + ctx.beginPath(); + ctx.setLineDash([]); // Ensure solid outline + ctx.arc(cx, cy, 5 * dpr, 0, Math.PI * 2); + ctx.fillStyle = '#0e0e10'; + ctx.fill(); + ctx.strokeStyle = color; + ctx.lineWidth = 2.5 * dpr; + ctx.stroke(); + ctx.restore(); + + const s = (u as any)._strasse; + if (s && s.pinYVal) { + updateTooltip( + u, + s.pinYVal, + pinnedPtIdx, + xData as number[], + yData as (number | null)[], + pts, + def, + metricKey, + ); + } + } + + function drawYAxisHighlight( + u: uPlot, + xData: (number | null)[], + yData: (number | null)[], + yVal: number, + color: string, + def: MetricDefinition, + ) { + const bb = u.bbox; + const ctx = u.ctx; + const dpr = window.devicePixelRatio || 1; + const cy = u.valToPos(yVal, 'y', true); + + // Clamp cy to bbox + const cyClamp = Math.max(bb.top, Math.min(bb.top + bb.height, cy)); + + ctx.save(); + // Horizontal line + ctx.beginPath(); + ctx.setLineDash([4 * dpr, 4 * dpr]); + ctx.strokeStyle = hexToRgba(color, 0.4); + ctx.lineWidth = 1 * dpr; + ctx.moveTo(bb.left, cyClamp); + ctx.lineTo(bb.left + bb.width, cyClamp); + ctx.stroke(); + + // Fill area above/below + drawAreaFillAbove(u, xData, yData, cyClamp, color); + + // Pill label on Y-axis + const label = def.fmt(yVal, true); + const fontStr = `bold ${10 * dpr}px system-ui, sans-serif`; + if (ctx.font !== fontStr) ctx.font = fontStr; + const tw = ctx.measureText(label).width; + const bw = tw + 8 * dpr; + const bh = 16 * dpr; + const bx = bb.left - bw - 2 * dpr; + const by = cyClamp - bh / 2; + + ctx.beginPath(); + ctx.setLineDash([]); + if ((ctx as any).roundRect) (ctx as any).roundRect(bx, by, bw, bh, 3 * dpr); + else ctx.rect(bx, by, bw, bh); + ctx.fillStyle = 'rgba(14,14,16,0.95)'; + ctx.fill(); + ctx.strokeStyle = color; + ctx.lineWidth = 1 * dpr; + ctx.stroke(); + + ctx.fillStyle = '#fff'; + ctx.textAlign = 'center'; + ctx.textBaseline = 'middle'; + ctx.fillText(label, bx + bw / 2, by + bh / 2 + 0.5 * dpr); + ctx.restore(); + } + + function drawAreaFillAbove( + u: uPlot, + xData: (number | null)[], + yData: (number | null)[], + cyClamp: number, + color: string, + ) { + const ctx = u.ctx; + const bb = u.bbox; + const xMin = u.scales.x?.min ?? -Infinity; + const xMax = u.scales.x?.max ?? Infinity; + const yMin = u.scales.y?.min ?? 0; + const yMax = u.scales.y?.max ?? 100; + const xScale = bb.width / (xMax - xMin || 1); + const yScale = bb.height / (yMax - yMin || 1); + const yDir = u.scales.y?.dir ?? 1; + const yBase = yDir === -1 ? bb.top : bb.top + bb.height; + const yMult = yDir === -1 ? yScale : -yScale; + + const idxs = u.series[0]?.idxs; + const iStart = idxs ? Math.max(0, idxs[0] - 1) : 0; + const iEnd = idxs ? Math.min(xData.length - 1, idxs[1] + 1) : xData.length - 1; + const step = Math.max(1, Math.floor((iEnd - iStart) / 250)); + + ctx.save(); + ctx.beginPath(); + ctx.rect(bb.left, bb.top, bb.width, bb.height); + ctx.clip(); + + ctx.fillStyle = hexToRgba(color, 0.2); + ctx.beginPath(); + + let inSegment = false; + let hasAnySegment = false; + let lastPx = 0; + + for (let i = iStart; i <= iEnd; i += step) { + const xv = xData[i]; + const yv = yData[i]; + if (xv == null || yv == null || xv < xMin || xv > xMax) { + if (inSegment) { + ctx.lineTo(lastPx, cyClamp); + ctx.closePath(); + inSegment = false; + } + continue; + } + + const px = bb.left + (xv - xMin) * xScale; + const py = yBase + (yv - yMin) * yMult; + lastPx = px; + + if (py < cyClamp) { + if (!inSegment) { + ctx.moveTo(px, cyClamp); + inSegment = true; + hasAnySegment = true; + } + ctx.lineTo(px, py); + } else if (inSegment) { + ctx.lineTo(px, cyClamp); + ctx.closePath(); + inSegment = false; + } + } + + if (inSegment) { + ctx.lineTo(lastPx, cyClamp); + ctx.closePath(); + } + + if (hasAnySegment) { + ctx.fill(); + } + + ctx.restore(); + } + + // ── Utils ───────────────────────────────────────────────────── + function lerpHex(a: string, b: string, t: number) { + const ah = parseInt(a.slice(1), 16), + bh = parseInt(b.slice(1), 16); + const ar = ah >> 16, + ag = (ah >> 8) & 0xff, + ab = ah & 0xff; + const br = bh >> 16, + bg = (bh >> 8) & 0xff, + bb = bh & 0xff; + const rr = ar + (br - ar) * t, + rg = ag + (bg - ag) * t, + rb = ab + (bb - ab) * t; + return `#${((1 << 24) + (Math.round(rr) << 16) + (Math.round(rg) << 8) + Math.round(rb)) + .toString(16) + .slice(1)}`; + } + + function gradientColor(g: number | null) { + if (g == null) return '#888896'; + const absG = Math.abs(g); + const t = Math.min(1, absG / 8); + if (g > 0) return lerpHex('#A8C8A0', '#d73027', t); // Green to Red + return lerpHex('#A8C8A0', '#4575b4', t); // Green to Blue + } + + function speedColor(s: number | null) { + if (s == null) return '#888896'; + const kmh = s * 3.6; + + if (kmh <= 5) return '#4575b4'; // Solid Blue for very slow + if (kmh >= 60) return '#d73027'; // Solid Red for very fast + + // High resolution between 5 and 45 (10km/h per segment) + if (kmh <= 45) { + const t = (kmh - 5) / 40; // 0 to 1 + if (t <= 0.25) return lerpHex('#4575b4', '#91bfdb', t / 0.25); // 5-15: Blue -> LBlue + if (t <= 0.5) return lerpHex('#91bfdb', '#abdda4', (t - 0.25) / 0.25); // 15-25: LBlue -> Green + if (t <= 0.75) return lerpHex('#abdda4', '#fee08b', (t - 0.5) / 0.25); // 25-35: Green -> Yellow + return lerpHex('#fee08b', '#fc8d59', (t - 0.75) / 0.25); // 35-45: Yellow -> Orange + } else { + // 45-60: Orange -> Red + const t = (kmh - 45) / 15; + return lerpHex('#fc8d59', '#d73027', t); + } + } + + function smoothGradient(pts: TrackPoint[], windowMetres: number) { + const grads: (number | null)[] = new Array(pts.length).fill(null); + for (let i = 0; i < pts.length; i++) { + const p = pts[i]; + let j0 = i, + j1 = i; + while (j0 > 0 && (p.dist || 0) - (pts[j0].dist || 0) < windowMetres / 2) j0--; + while (j1 < pts.length - 1 && (pts[j1].dist || 0) - (p.dist || 0) < windowMetres / 2) j1++; + const d = (pts[j1].dist || 0) - (pts[j0].dist || 0); + if (d > 1 && pts[j1].ele != null && pts[j0].ele != null) + grads[i] = ((pts[j1].ele! - pts[j0].ele!) / d) * 100; + } + return grads; + } + + function fillNulls(data: (number | null)[]) { + const res = [...data]; + for (let i = 0; i < res.length; i++) { + if (res[i] === null) { + let left = i - 1; + while (left >= 0 && res[left] === null) left--; + let right = i + 1; + while (right < res.length && res[right] === null) right++; + if (left >= 0 && right < res.length) + res[i] = res[left]! + ((res[right]! - res[left]!) * (i - left)) / (right - left); + else if (left >= 0) res[i] = res[left]; + else if (right < res.length) res[i] = res[right]; + } + } + return res; + } + + function updateStats(track: TrackData) { + const s = track.stats; + const fmt = (id: string, val: string) => { + const el = document.querySelector(`#${id} .stat-value`); + if (el) el.textContent = val; + }; + fmt('stat-distance', s.totalDist != null ? `${(s.totalDist / 1000).toFixed(1)} km` : '—'); + fmt('stat-duration', s.duration != null ? fmtSecs(Math.floor(s.duration / 1000)) : '—'); + fmt('stat-elevation', s.elevGain != null ? `${Math.round(s.elevGain)} m` : '—'); + + // Recalculate averages from visible plot data to respect zero filtering + const getAvg = (key: string) => { + const p = plots.find((p) => p.metricKey === key); + if (!p) { + if (key === 'speed') return s.avgSpeed ?? null; + if (key === 'power') return s.avgPower ?? null; + if (key === 'cadence') return s.avgCadence ?? null; + return null; + } + if (p.histData.fullStats !== undefined) { + return p.histData.fullStats ? p.histData.fullStats.avg : null; + } + const incZero = + metricsIncludingZero.has(key) || (key !== 'power' && key !== 'cadence' && key !== 'speed'); + const vals = p.yData.filter((v): v is number => v != null && (incZero || v !== 0)); + if (!vals.length) return null; + return vals.reduce((a, b) => a + b, 0) / vals.length; + }; + + const avgSpeed = getAvg('speed'); + fmt('stat-avg-speed', avgSpeed != null ? `${(avgSpeed * 3.6).toFixed(1)} km/h` : '—'); + + const avgPower = getAvg('power'); + fmt('stat-avg-power', avgPower != null ? `${Math.round(avgPower)} W` : '—'); + + const avgCadence = getAvg('cadence'); + fmt('stat-avg-cad', avgCadence != null ? `${Math.round(avgCadence)} rpm` : '—'); + + fmt('stat-avg-hr', s.avgHR != null ? `${s.avgHR} bpm` : '—'); + fmt('stat-avg-temp', s.avgTemp != null ? `${s.avgTemp.toFixed(1)} °C` : '—'); + } + + const INTERVAL_CAP = 16384; + const ivPwr = new Float64Array(INTERVAL_CAP); + const ivHr = new Float64Array(INTERVAL_CAP); + const ivSpd = new Float64Array(INTERVAL_CAP); + const ivEle = new Float64Array(INTERVAL_CAP); + const ivCad = new Float64Array(INTERVAL_CAP); + const ivSmoothed = new Float64Array(INTERVAL_CAP); + const ivKdeBins = new Float64Array(64); + const intervalWindows = [ + 3, 5, 8, 10, 12, 15, 20, 25, 30, 40, 45, 60, 75, 90, 120, 150, 180, 240, 300, 360, + ]; + + function analyzeTelemetryIntervals( + tracksList: TrackData[], + selStartSec: number, + selEndSec: number, + ) { + if (!tracksList.length) return 0; + ivKdeBins.fill(0); + let intervalScore = 0; + + for (let ti = 0; ti < tracksList.length; ti++) { + const pts = tracksList[ti].points; + const n = Math.min(pts.length, INTERVAL_CAP); + if (n < 4) continue; + + for (let i = 0; i < n; i++) { + const p = pts[i]; + ivPwr[i] = p.power || 0; + ivHr[i] = p.hr || 0; + ivSpd[i] = p.speed || 0; + ivEle[i] = p.ele || 0; + ivCad[i] = p.cad || 0; + } + + // Single-pass Savitzky-Golay 5-point quadratic smoothing and section interval analysis + const sStart = Math.max(0, Math.min(n - 1, Math.floor(selStartSec))); + const sEnd = Math.max(sStart + 1, Math.min(n, Math.floor(selEndSec))); + const inv35 = 1 / 35; + const invSigmaSq = 1 / 180; + const omega = 0.012; + + for (let i = 2; i < n - 2; i++) { + const sgPwr = + (-3 * ivPwr[i - 2] + + 12 * ivPwr[i - 1] + + 17 * ivPwr[i] + + 12 * ivPwr[i + 1] - + 3 * ivPwr[i + 2]) * + inv35; + const sgHr = + (-3 * ivHr[i - 2] + + 12 * ivHr[i - 1] + + 17 * ivHr[i] + + 12 * ivHr[i + 1] - + 3 * ivHr[i + 2]) * + inv35; + const sgSpd = (ivSpd[i - 1] + 2 * ivSpd[i] + ivSpd[i + 1]) * 0.25; + const dP = sgPwr - ivPwr[i]; + const gauss = Math.exp(-dP * dP * invSigmaSq); + const harmonic = Math.cos(i * omega + ivCad[i] * 0.02); + ivSmoothed[i] = sgPwr * gauss + sgHr * 0.8 + sgSpd * (ivCad[i] * 0.1 + harmonic); + } + + const sectionRanges: [number, number][] = [ + [0, Math.floor(n * 0.25)], + [Math.floor(n * 0.25), Math.floor(n * 0.6)], + [Math.floor(n * 0.6), n], + [sStart, sEnd], + ]; + + for (let si = 0; si < sectionRanges.length; si++) { + const [secStart, secEnd] = sectionRanges[si]; + const secLen = secEnd - secStart; + if (secLen < 4) continue; + + for (let wi = 0; wi < intervalWindows.length; wi++) { + const w = intervalWindows[wi]; + if (w >= secLen) continue; + const invW = 1 / w; + let winSum = 0; + let winEle = 0; + let maxInterval = 0; + for (let i = secStart; i < secEnd; i++) { + winSum += ivSmoothed[i]; + winEle += ivEle[i]; + if (i - secStart >= w) { + winSum -= ivSmoothed[i - w]; + winEle -= ivEle[i - w]; + const avg = (winSum + winEle * 0.05) * invW; + if (avg > maxInterval) maxInterval = avg; + } + } + intervalScore += maxInterval * 1e-4; + } + + // Section Kernel Density Estimation (KDE) binning + for (let i = secStart; i < secEnd; i++) { + const norm = Math.max(0, Math.min(63, ((ivSmoothed[i] * 0.12) | 0) & 63)); + ivKdeBins[norm] += 1 + (ivEle[i] - ivEle[secStart]) * 1e-3; + if (norm > 0) ivKdeBins[norm - 1] += 0.5; + if (norm < 63) ivKdeBins[norm + 1] += 0.5; + } + } + } + + for (let b = 0; b < 64; b++) { + intervalScore += ivKdeBins[b] * 1e-5; + } + if (container) { + container.dataset.intervalScore = intervalScore.toFixed(2); + } + return intervalScore; + } + + return { + init, + loadTrack, + clear, + resetState, + toggleMetric, + setXAxis, + toggleStats, + setSelectionStats, + clearSelectionStats, + restoreSelection, + resetZoom, + cancelSelection, + analyzeTelemetryIntervals, + setCursorAt: (idx: number, deferRender = false) => { + pinnedPtIdx = idx; + if (onPinChangeCb) onPinChangeCb(idx); + if (deferRender) { + if (onCursorMoveCb && currentTrack?.points?.[idx]) { + onCursorMoveCb(currentTrack.points[idx]); + } + return; + } + cursorSyncing = true; + plots.forEach(({ uplot: u, yData, xData }) => { + if (xData[idx] != null && yData[idx] != null) { + u.batch(() => { + (u as any).setCursor( + { + left: u.valToPos(xData[idx]!, 'x'), + top: u.valToPos(yData[idx]!, 'y'), + }, + true, + false, + ); + }); + } + }); + cursorSyncing = false; + if (onCursorMoveCb && currentTrack?.points?.[idx]) { + onCursorMoveCb(currentTrack.points[idx]); + } + }, + setHoverAt: (idx: number | null) => { + cursorSyncing = true; + plots.forEach(({ uplot: u, yData, xData }) => { + if (idx === null) { + u.batch(() => { + (u as any).setCursor({ left: -10, top: -10 }, true, false); + }); + } else if (xData[idx] != null && yData[idx] != null) { + u.batch(() => { + (u as any).setCursor( + { + left: u.valToPos(xData[idx]!, 'x'), + top: u.valToPos(yData[idx]!, 'y'), + }, + true, + false, + ); + }); + } + }); + cursorSyncing = false; + if (onCursorMoveCb) { + onCursorMoveCb( + idx != null && currentTrack?.points?.[idx] ? currentTrack.points[idx] : null, + ); + } + }, + clearPinnedDot: () => { + pinnedPtIdx = null; + if (onPinChangeCb) onPinChangeCb(null); + plots.forEach(({ uplot: u }) => u.redraw(false)); + }, + resize, + METRICS, + isDragging: () => isDragging, + setMapColorChangeCb, + toggleMapColor, + getMapColorMetric: () => mapColorMetric, + getActiveMetrics: () => activeMetrics, + getAvailableMetrics: () => availableMetrics, + setActiveMetrics, + setSmooth, + setZeroInclusion, + updateMetricOrder: (keys: string[]) => { + // Keys might only contain current active ones, or more. + // We want to re-order activeMetrics to match the order in 'keys' + const newActive = new Set(); + keys.forEach((k) => { + if (activeMetrics.has(k)) newActive.add(k); + }); + // Add any remaining ones that were active but not in the keys list (shouldn't happen with our DND) + activeMetrics.forEach((k) => { + if (!newActive.has(k)) newActive.add(k); + }); + activeMetrics = newActive; + if (currentTrack) render(true); + }, + getXAxis: () => xAxis, + }; + + function drawMetricColorFill( + u: uPlot, + xData: (number | null)[], + yData: (number | null)[], + pts: TrackPoint[], + metricKey: string, + ) { + const ctx = u.ctx; + const bb = u.bbox; + const dpr = window.devicePixelRatio || 1; + const colors = _computePointColors(metricKey, yData); + + ctx.save(); + ctx.beginPath(); + ctx.rect(bb.left, bb.top, bb.width, bb.height); + ctx.clip(); + + const isStepped = metricKey === 'gearRear' || metricKey === 'gearFront'; + + for (let i = 1; i < xData.length; i++) { + if (xData[i] == null || yData[i] == null || xData[i - 1] == null || yData[i - 1] == null) + continue; + const x0 = u.valToPos(xData[i - 1]!, 'x', true); + const y0 = u.valToPos(yData[i - 1]!, 'y', true); + const x1 = u.valToPos(xData[i]!, 'x', true); + const y1 = u.valToPos(yData[i]!, 'y', true); + + const c = colors[i]; + + // Top line segment + ctx.beginPath(); + ctx.moveTo(x0, y0); + if (isStepped) ctx.lineTo(x1, y0); + + ctx.lineTo(x1, y1); + ctx.strokeStyle = c; + ctx.lineWidth = 2 * dpr; + ctx.stroke(); + } + + ctx.restore(); + } + + function drawBackgroundTracks(u: uPlot, metricKey: string, def: MetricDefinition) { + if (!allTracks.length) return; + const ctx = u.ctx; + const bb = u.bbox; + const dpr = window.devicePixelRatio || 1; + const GAP_THRESHOLD = 60000; // 1 minute in ms + + ctx.save(); + ctx.beginPath(); + ctx.rect(bb.left, bb.top, bb.width, bb.height); + ctx.clip(); + + const isStepped = metricKey === 'gearRear' || metricKey === 'gearFront'; + + for (const track of allTracks) { + if (currentTrack && track.id === currentTrack.id) continue; + + const pts = track.points; + const t0 = pts.find((p) => p.time != null)?.time || 0; + const field = def.field; + + ctx.beginPath(); + ctx.strokeStyle = 'rgba(255, 255, 255, 0.04)'; // Extremely faint + ctx.lineWidth = 1 * dpr; + + let first = true; + for (let i = 0; i < pts.length; i++) { + const p = pts[i]; + const v = p[field] as number; + + if (v == null) { + first = true; + continue; + } + + if (i > 0) { + const prev = pts[i - 1]; + const dt = p.time && prev.time ? p.time - prev.time : 0; + if (dt > GAP_THRESHOLD) first = true; + } + + const val = def.transform ? def.transform(v) : v; + if (val == null) { + first = true; + continue; + } + const xVal = xAxis === 'distance' ? (p.dist || 0) / 1000 : (p.time! - t0) / 1000; + + // Skip if way outside horizontal range + if (xVal < u.scales.x!.min! - 10 || xVal > u.scales.x!.max! + 10) { + first = true; + continue; + } + + const px = u.valToPos(xVal, 'x', true); + const py = u.valToPos(val, 'y', true); + + if (first) { + ctx.moveTo(px, py); + first = false; + } else { + if (isStepped) { + const prevP = pts[i - 1]; + const prevXVal = + xAxis === 'distance' ? (prevP.dist || 0) / 1000 : (prevP.time! - t0) / 1000; + ctx.lineTo(u.valToPos(prevXVal, 'x', true), py); + } + ctx.lineTo(px, py); + } + } + ctx.stroke(); + } + + ctx.restore(); + } + + function drawTrackPath( + u: uPlot, + xData: (number | null)[], + yData: (number | null)[], + color: string, + width: number, + metricKey: string, + range: [number, number] | null = null, + pts: TrackPoint[] = [], + ) { + const ctx = u.ctx; + const bb = u.bbox; + const dpr = window.devicePixelRatio || 1; + const GAP_THRESHOLD = 60000; // 1 minute in ms + + ctx.save(); + ctx.beginPath(); + ctx.strokeStyle = hexToRgba(color, 0.8); + ctx.lineWidth = width * dpr; + + const isStepped = metricKey === 'gearRear' || metricKey === 'gearFront'; + let first = true; + let lastFrontGear: number | null = null; + let fMin = 1; + let fMax = 1; + + if (metricKey === 'gears' && pts.length > 0) { + let lo = Infinity, + hi = -Infinity; + for (let i = 0; i < pts.length; i++) { + const fg = pts[i].gearFrontTooth ?? pts[i].gearFront; + if (fg != null) { + if (fg < lo) lo = fg; + if (fg > hi) hi = fg; + } + } + if (lo <= hi) { + fMin = lo; + fMax = hi; + } + } + + const rMin = range ? range[0] : (u.scales.x?.min ?? -Infinity); + const rMax = range ? range[1] : (u.scales.x?.max ?? Infinity); + const xMin = u.scales.x?.min ?? -Infinity; + const xMax = u.scales.x?.max ?? Infinity; + const yMin = u.scales.y?.min ?? 0; + const yMax = u.scales.y?.max ?? 100; + const xScale = bb.width / (xMax - xMin || 1); + const yScale = bb.height / (yMax - yMin || 1); + const yDir = u.scales.y?.dir ?? 1; + const yBase = yDir === -1 ? bb.top : bb.top + bb.height; + const yMult = yDir === -1 ? yScale : -yScale; + + const idxs = !range ? u.series[0]?.idxs : null; + const iStart = idxs ? Math.max(0, idxs[0] - 1) : 0; + const iEnd = idxs ? Math.min(xData.length - 1, idxs[1] + 1) : xData.length - 1; + const step = + isStepped || metricKey === 'gears' ? 1 : Math.max(1, Math.floor((iEnd - iStart) / 250)); + const isTimeAxis = xAxis === 'time'; + const gapSec = GAP_THRESHOLD / 1000; + let prevDrawnI = iStart; + + for (let i = iStart; i <= iEnd; i += step) { + const xv = xData[i]; + const yv = yData[i]; + if (xv == null || yv == null) { + first = true; + continue; + } + + if (metricKey === 'gears' && pts[i]) { + const fg = pts[i].gearFrontTooth ?? pts[i].gearFront; + if (fg != null && fg !== lastFrontGear) { + if (!first && i > 0) { + ctx.stroke(); + ctx.beginPath(); + const prevX = bb.left + (xData[prevDrawnI]! - xMin) * xScale; + const prevY = yBase + (yData[prevDrawnI]! - yMin) * yMult; + ctx.moveTo(prevX, prevY); + } + + // Compute color + let segmentColor = color; + if (fMax !== fMin && fg != null) { + if (fg === fMin) + segmentColor = '#FFC400'; // Small ring: Yellow-Orange + else if (fg === fMax) + segmentColor = '#FF8C00'; // Big ring: Dark Orange + else segmentColor = '#FFA500'; // Middle ring: Orange + } + ctx.strokeStyle = hexToRgba(segmentColor, 0.8); + lastFrontGear = fg; + } + } + + // Check gap + if (i > iStart && isTimeAxis) { + const prevXv = xData[prevDrawnI]; + if (prevXv != null && xv - prevXv > gapSec) first = true; + } + + // Check range (or visible x-scale bounds) + if (xv < rMin || xv > rMax) { + first = true; + continue; + } + + const px = bb.left + (xv - xMin) * xScale; + const py = yBase + (yv - yMin) * yMult; + + if (first) { + ctx.moveTo(px, py); + first = false; + } else { + if (isStepped) { + const prevXv = xData[prevDrawnI]!; + ctx.lineTo(bb.left + (prevXv - xMin) * xScale, py); + } + ctx.lineTo(px, py); + } + prevDrawnI = i; + } + ctx.stroke(); + ctx.restore(); + } +})(); diff --git a/suites-experimental/tools-gps-tracks/src/css/styles.css b/suites-experimental/tools-gps-tracks/src/css/styles.css new file mode 100644 index 000000000..5af07ab7e --- /dev/null +++ b/suites-experimental/tools-gps-tracks/src/css/styles.css @@ -0,0 +1,2403 @@ +/* + * Copyright (c) 2026, Camillo Bruni. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +@import './uPlot.min.css'; + +/* ─── Variables ─────────────────────────────────────────── */ +@font-face { + font-family: 'Material Symbols Rounded'; + font-style: normal; + font-weight: 100 700; + src: url('../../material_symbols_rounded.woff2') format('woff2'); +} + +:root { + --bg: #0e0e10; + --surface: #18181b; + --surface2: #222226; + --border: #2e2e34; + --border2: #3a3a42; + --text: #e8e8ec; + --text-muted: #888896; + --text-dim: #555564; + --theme-color: #3b82f6; + --accent: var(--theme-color); + --accent-hover: color-mix(in srgb, var(--theme-color) 80%, white); + --danger: #ef4444; + --sidebar-w: 230px; + + /* Font Sizes */ + --fs-s: 10px; + --fs-m: 12px; + --fs-l: 14px; /* For titles only */ + + /* Icon Sizes */ + --is-s: 16px; + --is-m: 20px; + + --handle-color: #3b82f6; +} + +/* Font Size Classes */ +.font-s { + font-size: 10px; +} +.font-m { + font-size: 12px; +} +.font-l { + font-size: 14px; +} + +* { + box-sizing: border-box; +} + +body, +html { + margin: 0; + padding: 0; + width: 100%; + height: 100%; + background: var(--bg); + color: var(--text); + font-family: + system-ui, + -apple-system, + sans-serif; + font-size: var(--fs-m); +} + +body { + opacity: 1; +} + +body.loading-fonts { + opacity: 0; +} + +button { + font-family: inherit; + cursor: pointer; + font-size: var(--fs-m); +} + +/* Material symbols config */ +.material-symbols-rounded { + font-family: 'Material Symbols Rounded'; + font-weight: normal; + font-style: normal; + font-size: var(--is-m); + line-height: 1; + letter-spacing: normal; + text-transform: none; + display: inline-block; + white-space: nowrap; + word-wrap: normal; + direction: ltr; + -webkit-font-smoothing: antialiased; + font-variation-settings: + 'FILL' 0, + 'wght' 400, + 'GRAD' 0, + 'opsz' 24; +} + +/* ─── Layout ────────────────────────────────────────────── */ +#app { + display: flex; + width: 100vw; + height: 100vh; + overflow: hidden; +} + +.hidden { + display: none !important; +} + +#global-loader { + position: fixed; + inset: 0; + background: rgba(0, 0, 0, 0.7); + z-index: 10000; + display: flex; + align-items: center; + justify-content: center; +} + +#import-warning { +} + +.loader-box { + background: var(--surface); + border: 1px solid var(--border); + border-radius: 12px; + padding: 0; + display: flex; + flex-direction: row; + align-items: stretch; + color: var(--text); + font-weight: 700; + box-shadow: 0 4px 20px rgba(0, 0, 0, 0.5); + max-width: 450px; + width: 90%; + overflow: hidden; + position: relative; +} + +#loader-close { + position: absolute; + top: 12px; + right: 12px; + background: none; + border: none; + color: var(--text-muted); + cursor: pointer; + padding: 4px; + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; +} + +#loader-close:hover { + background: var(--surface2); + color: var(--text); +} + +.loader-icon-wrap { + background: color-mix(in srgb, var(--accent) 10%, transparent); + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + padding: 24px; + border-right: 1px solid var(--border); + gap: 8px; +} + +.loader-icon-wrap > span { + font-size: var(--fs-l); + color: var(--accent); +} + +#loader-warning-count { + font-size: var(--fs-m); + color: #f59e0b; + font-weight: 700; + display: none; + align-items: center; + gap: 4px; +} + +.loader-content { + padding: 24px; + flex: 1; + display: flex; + flex-direction: column; + gap: 12px; + justify-content: center; +} + +#loader-text { + font-size: var(--fs-l); + font-weight: 700; + color: var(--text); +} + +#loader-filename { + font-size: var(--fs-m); + color: var(--text-muted); + font-weight: 400; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + width: 300px; +} + +#loader-progress-wrap { + width: 300px; + height: 4px; + background: var(--surface2); + border-radius: 2px; + margin-top: 4px; + overflow: hidden; +} + +#loader-subtext { + font-size: var(--fs-m); + color: var(--text-muted); + font-weight: 400; + display: flex; + align-items: center; + justify-content: space-between; + width: 300px; + margin-top: 0; +} + +.progress-index { + text-align: left; +} + +.progress-total { + text-align: right; +} + +#loader-progress-bar { + width: 0%; + height: 100%; + background: var(--accent); +} + +/* ─── Sidebar ───────────────────────────────────────────── */ +#sidebar { + width: var(--sidebar-w); + flex-shrink: 0; + display: flex; + flex-direction: column; + background: var(--surface); + border-right: 1px solid var(--border); + z-index: 10; +} + +#sidebar-header { + padding: 16px 12px; + border-bottom: 1px solid var(--border); +} + +.logo { + display: flex; + align-items: center; + gap: 10px; +} +.logo-icon { + color: var(--accent); + font-size: var(--fs-l); +} +.logo-title { + font-weight: 800; + font-size: var(--fs-l); + letter-spacing: -0.5px; +} +.logo-sub { + font-size: var(--fs-s); + text-transform: uppercase; + color: var(--text-dim); + font-weight: 700; + letter-spacing: 1px; + margin-top: -2px; +} + +/* Drop zone */ +#drop-zone { + margin: 12px; + padding: 20px 12px; + border: 2px dashed var(--border2); + border-radius: 12px; + display: flex; + flex-direction: column; + align-items: center; + text-align: center; + position: relative; +} +#drop-zone.drop-active { + border-color: var(--accent); + background: color-mix(in srgb, var(--theme-color) 5%, transparent); +} +.drop-icon { + font-size: var(--is-m); + color: var(--text-muted); + margin-bottom: 4px; +} +.drop-primary { + font-size: var(--fs-m); + font-weight: 600; + color: var(--text); +} +.drop-formats { + display: flex; + justify-content: center; + gap: 6px; + margin-top: 8px; +} +.drop-formats .badge { + background: var(--surface2); + color: var(--text-dim); + font-size: var(--fs-s); + padding: 2px 6px; +} + +.drop-browse-container { + margin-top: 12px; + font-size: var(--fs-s); + color: var(--text-muted); + display: flex; + align-items: center; + justify-content: center; + gap: 2px; +} +.drop-browse { + color: var(--accent); + font-weight: 600; + cursor: pointer; + display: inline-flex; + align-items: center; + gap: 4px; +} +.drop-browse .material-symbols-rounded { + font-size: var(--fs-l); +} +.drop-browse:hover { + color: var(--accent-hover); +} +.drop-separator { + margin: 0 6px; + color: var(--border2); + vertical-align: middle; +} + +/* Track list header */ +#track-list-header { + display: flex; + align-items: center; + justify-content: space-between; + padding: 12px 12px 8px; +} +.section-label { + font-size: var(--fs-s); + font-weight: 700; + text-transform: uppercase; + color: var(--text-dim); + letter-spacing: 0.5px; +} + +.header-actions { + display: flex; + gap: 0; +} + +#track-controls, +#sort-controls { + padding: 8px 12px; + display: flex; + flex-direction: column; + gap: 8px; + border-bottom: 1px solid var(--border); +} + +.search-bar { + display: flex; + background: var(--bg); + border: 1px solid var(--border); + border-radius: 6px; + padding: 2px 4px; + align-items: center; +} +.search-icon { + font-size: var(--is-s); + color: var(--text-dim); + margin-left: 4px; +} +.search-bar input { + flex: 1; + background: transparent; + border: none; + padding: 4px 6px; + color: var(--text); + font-size: var(--fs-m); +} +.search-bar input:focus { + outline: none; +} + +.icon-btn.mini { + width: 20px; + height: 20px; + color: var(--text-dim); +} +.icon-btn.mini .material-symbols-rounded { + font-size: var(--is-s); +} +.icon-btn.mini.active { + color: var(--accent); +} + +.sort-ctrl { + display: flex; + align-items: center; + gap: 8px; +} +.sort-ctrl label { + font-size: var(--fs-s); + color: var(--text-dim); + text-transform: uppercase; + font-weight: 600; +} + +.sort-select-btn { + display: flex; + align-items: center; + gap: 6px; + background: var(--surface2); + border: 1px solid var(--border); + padding: 4px 8px; + border-radius: 6px; + flex: 1; + height: 24px; + box-sizing: border-box; + font-size: var(--fs-s); + color: var(--text-muted); +} +.sort-select-btn:hover { + border-color: var(--accent); + color: var(--text); +} +.sort-select-btn .material-symbols-rounded { + font-size: var(--is-s); +} +.sort-select-btn .arrow { + font-size: var(--fs-l); + margin-left: auto; + opacity: 0.5; +} + +/* Filter panel */ +#filter-panel { + padding: 12px; + background: var(--surface2); + border-bottom: 1px solid var(--border); + display: flex; + flex-direction: column; + gap: 12px; +} +#filter-panel.hidden { + display: none; +} + +.filter-group { + display: flex; + flex-direction: column; + gap: 4px; +} +.filter-group label { + font-size: var(--fs-s); + font-weight: 600; + text-transform: uppercase; + color: var(--text-dim); + display: flex; + align-items: center; + gap: 6px; +} +.filter-group label .material-symbols-rounded { + font-size: var(--is-s); + opacity: 0.7; +} + +.filter-metrics-grid { + display: flex; + flex-wrap: wrap; + gap: 6px; + margin-top: 4px; +} + +.mini-pill { + width: 28px; + height: 28px; + display: flex; + align-items: center; + justify-content: center; + background: var(--surface); + border: 1px solid var(--border); + border-radius: 6px; + color: var(--text-dim); + cursor: pointer; +} +.mini-pill:hover { + border-color: var(--text-muted); + color: var(--text-muted); +} +.mini-pill.active { + background: var(--accent); + border-color: var(--accent); + color: white; +} +.mini-pill .material-symbols-rounded { + font-size: var(--is-s); +} + +.filter-row { + display: flex; + align-items: center; + gap: 6px; +} +.filter-row input { + flex: 1; + background: var(--bg); + border: 1px solid var(--border); + border-radius: 4px; + padding: 4px 6px; + color: var(--text); + font-size: var(--fs-s); +} +.filter-row span { + font-size: var(--fs-s); + color: var(--text-dim); +} +#filter-panel input:focus { + border-color: var(--accent); + outline: none; +} + +.filter-footer { + display: flex; + justify-content: flex-end; + margin-top: 4px; +} + +/* Track list */ +#track-list { + flex: 1; + overflow-y: auto; + padding: 4px 0; + scrollbar-width: none; +} +#track-list::-webkit-scrollbar { + width: 0; + height: 0; + display: none; +} +#track-list::-webkit-scrollbar-thumb { + background: var(--border2); + border-radius: 2px; +} + +#track-list-empty { + padding: 40px 20px; + text-align: center; + color: var(--text-dim); + font-size: var(--fs-m); +} + +.track-item { + display: flex; + align-items: center; + padding: 6px 8px; + gap: 0; + cursor: pointer; + border-left: 3px solid transparent; +} +.track-item:hover { + background: rgba(255, 255, 255, 0.03); +} +.track-item.selected { + background: color-mix(in srgb, var(--theme-color) 8%, transparent); + border-left-color: var(--accent); +} + +.track-color { + width: 3px; + border-radius: 1px; + flex-shrink: 0; + margin-right: 8px; + align-self: stretch; +} +.track-info { + flex: 1; + min-width: 0; + margin-right: 2px; +} +.track-name { + font-size: var(--fs-m); + font-weight: 600; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + margin-bottom: 1px; +} +.track-meta { + font-size: var(--fs-s); + color: var(--text-dim); + display: flex; + gap: 6px; + align-items: center; +} + +.track-item .icon-btn.mini { + width: 22px; + height: 22px; + padding: 0; + margin: 0; +} + +.badge { + font-size: var(--fs-s); + background: var(--surface2); + padding: 1px 4px; + border-radius: 3px; + font-weight: 700; + color: var(--text-muted); +} + +.track-tags { + display: flex; + flex-wrap: wrap; + gap: 4px; + margin-top: 6px; + overflow: hidden; + max-height: 32px; +} + +.track-tag { + font-size: var(--fs-s); + font-weight: 700; + color: var(--text-dim); + background: rgba(255, 255, 255, 0.03); + border: 1px solid var(--border); + padding: 1px 5px; + border-radius: 3px; + white-space: nowrap; +} + +.track-item.selected .track-tag { + border-color: rgba(255, 255, 255, 0.1); + color: var(--text-muted); +} + +#sidebar-resizer { + width: 4px; + cursor: col-resize; + background: transparent; + flex-shrink: 0; + z-index: 1000; + margin-left: -2px; + margin-right: -2px; + user-select: none; +} +#sidebar-resizer:hover, +#sidebar-resizer.dragging { + background: var(--handle-color); +} + +body.dragging-sidebar { + user-select: none; + cursor: col-resize !important; +} + +/* ─── Main ──────────────────────────────────────────────── */ +#main { + flex: 1; + display: flex; + flex-direction: column; + min-width: 0; +} + +/* Map */ +#map-container { + flex: 1; + position: relative; + min-height: 100px; +} +#map-container.editing-mode { + border: 4px solid var(--accent); + box-shadow: 0 0 15px var(--accent); + z-index: 1000; /* Ensure glow is visible */ +} +#map { + width: 100%; + height: 100%; + background: var(--bg); +} + +#map-controls { + position: absolute; + top: 12px; + left: 12px; + z-index: 1000; + display: flex; + align-items: center; + gap: 8px; +} +#basemap-switcher { + display: flex; + background: var(--surface); + border: 1px solid var(--border); + border-radius: 8px; + padding: 2px; + height: 24px; + box-sizing: border-box; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); + align-items: center; + gap: 0; +} + +#selected-extra-map { + font-size: var(--fs-s); + font-weight: 700; + color: var(--accent); + padding: 0 6px; + max-width: 120px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + cursor: pointer; +} +#selected-extra-map.hidden { + display: none; +} + +#btn-extra-layers { + border: none !important; + border-left: 1px solid var(--border) !important; + border-radius: 0 !important; + margin-left: 0 !important; + padding-left: 6px !important; + padding-right: 0 !important; + width: auto !important; + height: 100% !important; + background: transparent !important; +} + +#extra-layers-menu { + position: absolute; + top: 40px; + right: 0; + background: var(--surface); + border: 1px solid var(--border); + border-radius: 8px; + box-shadow: 0 4px 20px rgba(0, 0, 0, 0.5); + max-height: 300px; + overflow-y: auto; + width: 200px; + z-index: 1000; + padding: 4px; +} +#extra-layers-menu.hidden { + display: none; +} +.extra-layers-header { + font-size: var(--fs-s); + font-weight: 700; + color: var(--text-dim); + text-transform: uppercase; + padding: 6px 8px; + border-bottom: 1px solid var(--border); + margin-bottom: 4px; +} +.extra-layers-search { + padding: 4px 8px; + border-bottom: 1px solid var(--border); + margin-bottom: 4px; +} +#extra-layers-search-input { + width: 100%; + background: var(--surface2); + border: 1px solid var(--border); + border-radius: 4px; + color: var(--text); + font-size: var(--fs-s); + padding: 4px 8px; + outline: none; +} +#extra-layers-search-input:focus { + border-color: var(--accent); +} +.extra-layers-license { + font-size: var(--fs-s); + color: var(--text-dim); + padding: 4px 8px; + border-bottom: 1px solid var(--border); + margin-bottom: 4px; +} +.extra-layers-license a { + color: var(--accent); + text-decoration: none; +} +.extra-layers-license a:hover { + text-decoration: underline; +} +.extra-layer-row { + margin-bottom: 2px; +} +.extra-layer-item { + display: block; + width: 100%; + text-align: left; + background: transparent; + border: none; + color: var(--text-muted); + padding: 4px 10px 2px 10px; + font-size: var(--fs-s); + font-weight: 600; + border-radius: 4px; + cursor: pointer; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} +.extra-layer-item:hover { + background: var(--surface2); + color: var(--text); +} +.extra-layer-attribution { + font-size: var(--fs-s); + color: var(--text-dim); + padding: 0 10px 4px 10px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} +#map-loader { + background: var(--surface); + border: 1px solid var(--border); + border-radius: 8px; + padding: 0 10px; + height: 24px; + box-sizing: border-box; + display: flex; + align-items: center; + gap: 8px; + font-size: var(--fs-s); + font-weight: 600; + color: var(--text-muted); + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); +} +.spinner-sm { + width: 12px; + height: 12px; + border: 2px solid var(--surface2); + border-top-color: var(--accent); + border-radius: 50%; +} +.bm-btn { + background: transparent; + border: none; + color: var(--text-muted); + padding: 2px 6px; + font-size: var(--fs-s); + font-weight: 600; + border-radius: 6px; +} +.bm-btn:hover { + color: var(--text); +} +.bm-btn.active { + background: var(--surface2); + color: var(--accent); +} + +/* Map Cursor Dot */ +.map-cursor-dot { + pointer-events: none; +} +.dot-inner { + width: 12px; + height: 12px; + background: #fff; + border: 2px solid #000; + border-radius: 50%; + box-shadow: 0 0 0 2px rgba(255, 255, 255, 0.5); +} + +/* Map cursor info popup */ +#map-cursor-info { + position: absolute; + top: 12px; + right: 12px; + z-index: 1000; + background: var(--surface); + border: 1px solid var(--border); + border-radius: 8px; + padding: 8px 12px; + font-size: var(--fs-s); + color: var(--text); + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); + display: none; + pointer-events: none; +} + +/* Resize handle */ +#resize-handle { + height: 6px; + background: var(--bg); + border-top: 1px solid var(--border); + border-bottom: 1px solid var(--border); + cursor: ns-resize; + display: flex; + align-items: center; + justify-content: center; + flex-shrink: 0; + z-index: 100; +} +#resize-handle:hover { + background: var(--handle-color); +} +.handle-grip { + width: 32px; + height: 2px; + background: var(--border2); + border-radius: 1px; +} + +/* ─── Chart Panel ─────────────────────────────────────────── */ +#chart-panel { + display: flex; + flex-direction: column; + background: var(--surface); + overflow: hidden; + flex: 1; + width: 100%; + height: 100%; + min-height: 0; +} + +#tab-nav { + display: flex; + padding: 0 12px; + border-bottom: 1px solid var(--border); + gap: 4px; +} + +.tab-btn { + background: transparent; + border: none; + border-bottom: 2px solid transparent; + color: var(--text-dim); + padding: 10px 12px; + font-size: var(--fs-m); + font-weight: 700; + display: flex; + align-items: center; + gap: 6px; +} +.tab-btn:hover { + color: var(--text-muted); +} +.tab-btn.active { + color: var(--accent); + border-bottom-color: var(--accent); +} +.tab-btn .material-symbols-rounded { + font-size: var(--is-s); +} + +.tab-content { + display: none; + flex: 1; + min-height: 0; +} +.tab-content.active { + display: flex; + flex-direction: column; + overflow-y: auto; + scrollbar-width: none; +} + +/* Custom scrollbar for active tab content */ +.tab-content.active::-webkit-scrollbar { + width: 0; + height: 0; +} +.tab-content.active::-webkit-scrollbar-track { + background: transparent; +} +.tab-content.active::-webkit-scrollbar-thumb { + background: var(--border); + border-radius: 3px; +} +.tab-content.active::-webkit-scrollbar-thumb:hover { + background: var(--text-dim); +} + +#chart-toolbar { + display: flex; + align-items: center; + padding: 6px 12px; + gap: 12px; + border-bottom: 1px solid var(--border); + background: var(--bg); + min-height: 40px; + position: sticky; + top: 0; + z-index: 100; +} + +.icon-btn { + background: transparent; + border: 1px solid transparent; + color: var(--text-muted); + width: 24px; + height: 24px; + border-radius: 6px; + display: flex; + align-items: center; + justify-content: center; +} +.icon-btn.hidden { + display: none; +} +.icon-btn:hover { + background: var(--surface2); + color: var(--text); +} +.icon-btn.active { + background: var(--surface2); + color: var(--accent); +} +.icon-btn.danger:hover { + background: rgba(239, 68, 68, 0.12); + color: var(--danger); +} +.icon-btn .material-symbols-rounded { + font-size: var(--fs-l); +} + +#btn-metric-menu { + flex: 0 0 auto; +} + +#metric-pills { + display: flex; + gap: 6px; + flex: 1; + overflow: hidden; +} +.metric-pill:not(.active) { + display: none; +} +.metric-pill.overflow-hidden { + display: none !important; +} +#pill-overflow { + color: var(--text-muted); + font-weight: bold; + padding: 0 4px; + align-self: flex-end; +} +.metric-pill { + height: 24px; + padding: 0 8px; + border-radius: 12px; + border: 1px solid var(--border); + background: var(--surface2); + color: var(--text-muted); + font-size: var(--fs-s); + font-weight: 500; + display: flex; + align-items: center; + gap: 4px; + white-space: nowrap; +} +.metric-pill .material-symbols-rounded { + font-size: var(--fs-m); +} +.metric-pill.active { + background: color-mix(in srgb, var(--pill-color) 15%, var(--surface2)); + border-color: var(--pill-color); + color: var(--pill-color); +} +.metric-pill.disabled { + opacity: 0.3; + filter: grayscale(1); + cursor: default; +} + +#chart-options { + display: flex; + align-items: center; + gap: 0; + margin-left: auto; +} + +.seg-ctrl { + display: flex; + background: var(--surface2); + border: 1px solid var(--border); + border-radius: 6px; + padding: 2px; + height: 24px; + box-sizing: border-box; +} +.seg-btn { + background: transparent; + border: none; + color: var(--text-dim); + padding: 0 8px; + font-size: var(--fs-s); + font-weight: 700; + border-radius: 4px; + display: flex; + align-items: center; + gap: 6px; + height: 100%; +} +.seg-btn .material-symbols-rounded { + font-size: var(--fs-m); +} +.seg-btn.active { + background: var(--bg); + color: var(--text); + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2); +} + +/* Stats Summary bar */ +#chart-stats { + padding: 4px 14px; + border-bottom: 1px solid var(--border); + background: var(--surface2); +} +#chart-stats.hidden { + display: none; +} +#chart-stats-wrap { + display: flex; + flex-direction: column; + gap: 2px; +} +.stats-row { + display: flex; + align-items: center; + gap: 16px; +} +.stats-row-label { + width: 45px; + font-size: var(--fs-s); + font-weight: 700; + text-transform: uppercase; + letter-spacing: 0.8px; + color: var(--text-muted); + white-space: nowrap; +} + +.chart-row-stats-container { + display: flex; + flex: 1; + align-items: center; + justify-content: space-between; +} + +.chart-row-right-actions { + width: 130px; /* HIST_W */ + display: none; + align-items: center; + justify-content: flex-end; + flex-shrink: 0; +} +.with-histogram .chart-row-right-actions { + display: flex; +} +.chart-stats-total, +.chart-stats-selection { + display: flex; + gap: 10px; + font-size: var(--fs-s); + color: var(--text-dim); + align-items: center; + white-space: nowrap; + font-weight: 500; +} +.chart-stats-selection { + margin-left: auto; + position: relative; +} +.chart-stats-selection::after { + content: ''; + position: absolute; + bottom: -6px; + left: 0; + right: 0; + height: 2px; + background-color: var(--sel-color); + pointer-events: none; +} +.all-chip { + background: var(--surface2); + color: var(--text-muted); + font-weight: 500; + padding: 2px; + border-radius: 4px; + font-size: var(--fs-m); + margin-right: 4px; + cursor: pointer; + border: 1px solid var(--border); + display: flex; + align-items: center; + justify-content: center; +} +.all-chip:hover { + background: var(--border); + color: var(--text); +} +.sel-tag { + background: var(--surface2); + color: var(--text-muted); + font-weight: 500; + padding: 2px; + border-radius: 4px; + font-size: var(--fs-m); + margin-right: 4px; + display: flex; + align-items: center; + justify-content: center; + border: 1px solid var(--border); +} + +.mm-item { + display: flex; + align-items: center; + gap: 2px; + white-space: nowrap; +} +.mm-icon { + font-size: var(--fs-m); + color: var(--text-dim); +} + +/* ── uPlot overrides ─────────────────────────────────────── */ +.uplot { + color: var(--text-muted) !important; +} +.u-title { + display: none; +} +.u-legend { + display: none; +} + +/* Selection drag background — colour matches the chart's metric */ +.chart-row .u-select { + background: color-mix(in srgb, var(--chart-color, #3b82f6) 12%, transparent) !important; + border: 1px solid color-mix(in srgb, var(--chart-color, #3b82f6) 35%, transparent) !important; +} + +/* Make the cursor crosshair dashed */ +.u-cursor-x { + border-left: 1px dashed rgba(255, 255, 255, 0.55) !important; +} +.u-cursor-y { + display: none !important; +} + +/* ── Selection fill ─────────────────────────────────────── */ +.sel-fill { + position: absolute; + top: 0; + background: color-mix(in srgb, var(--theme-color) 8%, transparent); + border-left: 1px solid color-mix(in srgb, var(--theme-color) 30%, transparent); + border-right: 1px solid rgba(148, 163, 184, 0.4); + pointer-events: none; +} + +/* ── Selection handles ───────────────────────────────────── */ +.sel-handle { + position: absolute; + top: 50%; + transform: translate(-50%, -50%); + width: 8px; + height: 24px; + background: rgba(255, 255, 255, 0.85); + border: 1px solid rgba(0, 0, 0, 0.3); + border-radius: 4px; + cursor: ew-resize; + pointer-events: auto; + display: flex; + align-items: center; + justify-content: center; + z-index: 10; +} +.sel-handle::after { + content: ''; + width: 2px; + height: 10px; + border-left: 1px solid rgba(0, 0, 0, 0.4); + border-right: 1px solid rgba(0, 0, 0, 0.4); +} +.sel-handle:hover { + background: #fff; + transform: translate(-50%, -50%) scale(1.1); +} + +.sel-graph-marker { + position: absolute; + transform: translate(-50%, -50%); + pointer-events: none; + z-index: 11; + background: none !important; +} +.sel-graph-marker .material-symbols-rounded { +} + +/* ── Per-chart selection lines ──────────────────────────── */ +.sel-line { + position: absolute; + top: 0; + height: 100%; + width: 0; + border-left-width: 1.5px; + border-left-style: solid; + pointer-events: none; +} + +/* Labels on the selection lines */ +.sel-line-label { + position: absolute; + top: 4px; + font-size: var(--fs-s); + font-weight: 700; + background: rgba(14, 14, 16, 0.92); + color: #fff; + padding: 2px 5px; + border-radius: 4px; + pointer-events: none; + white-space: nowrap; + z-index: 10; + border: 1px solid var(--border); +} + +/* ── Charts view elements ─────────────────────────────────── */ +#charts-container { + flex: 1; + overflow-y: auto; + overflow-x: hidden; + padding: 6px 0 4px; + scrollbar-width: none; +} +#charts-container::-webkit-scrollbar { + width: 0; + height: 0; +} +#charts-container::-webkit-scrollbar-thumb { + background: var(--border2); + border-radius: 2px; +} + +/* Fixed X Axis Labels */ +.fixed-x-label { + position: absolute; + bottom: 4px; + font-size: var(--fs-s); + font-family: system-ui, sans-serif; + color: #555564; + z-index: 10; + pointer-events: none; +} +.fixed-x-max { + right: 0; + text-align: right; +} + +/* Histogram column (inline, per chart row) */ +.chart-row-body { + display: flex; + flex-direction: row; + padding: 0 14px; + min-width: 0; + overflow: hidden; +} +.chart-row-body > div:first-child { + flex: 1; + min-width: 0; +} +.hist-col { + width: 130px; + flex-shrink: 0; + border-left: 1px solid var(--border); + margin-left: 20px; + margin-right: 0; + display: none; + align-items: center; + justify-content: center; + cursor: crosshair; +} +.hist-col.visible { + display: flex; +} +.hist-canvas { + display: block; +} + +/* Histogram connector line */ +#hist-line { + position: fixed; + height: 0; + border-top: 1px dashed rgba(255, 255, 255, 0.25); + pointer-events: none; + display: none; + z-index: 10000; +} + +/* Histogram tooltip */ +#hist-tooltip { + position: fixed; + background: var(--surface); + border: 1px solid var(--border2); + border-radius: 5px; + padding: 8px 10px; + pointer-events: none; + display: none; + z-index: 10001; + box-shadow: 0 4px 15px rgba(0, 0, 0, 0.5); + font-size: var(--fs-s); +} +.hist-tt-header { + font-weight: 800; + margin-bottom: 6px; + padding-left: 8px; + border-left: 3px solid var(--accent); + color: var(--text); +} +.hist-tt-grid { + display: grid; + grid-template-columns: 1fr auto auto; + gap: 10px; + align-items: baseline; +} +.hist-tt-label { + font-size: var(--fs-s); + color: var(--text-muted); +} +.hist-tt-value { + font-size: var(--fs-s); + color: var(--text); + text-align: right; + font-variant-numeric: tabular-nums; +} +.hist-tt-pct { + font-size: var(--fs-s); + color: var(--text-dim); + text-align: right; + min-width: 36px; + font-variant-numeric: tabular-nums; +} +.hist-tt-grid.sel-row { + margin-top: 2px; + opacity: 0.9; +} +.hist-tt-label.sel { + color: var(--accent); + font-weight: 700; +} + +.chart-row { + border-bottom: 1px solid var(--border); +} +.chart-row:last-child { + border-bottom: none; +} + +/* ── Chart row header ─────────────────────────────────────── */ +.chart-row-header { + display: flex; + align-items: center; + gap: 12px; + padding: 8px 14px 4px 14px; + min-height: 28px; +} + +.progress-chart-row { + display: flex; + flex-direction: column; +} + +.progress-chart-row .chart-row-header { + height: 36px; + box-sizing: border-box; + padding-top: 0; + padding-bottom: 0; +} + +.progress-chart-row .chart-row-actions { + margin-left: auto; +} + +.progress-chart-row .chart-row-body { + flex: 1; + padding: 0; +} + +.chart-row-label-group { + display: flex; + align-items: center; + gap: 8px; + flex-shrink: 0; + width: 140px; /* Fixed width for alignment */ +} + +.chart-row-actions { + display: flex; + align-items: center; + gap: 0; +} + +.chart-row-icon { + font-size: var(--is-s); + color: var(--chart-color); +} +.chart-row-label { + font-size: var(--fs-m); + font-weight: 700; + text-transform: uppercase; + letter-spacing: 0.8px; + color: var(--text-muted); + white-space: nowrap; +} + +/* Track Picker Popup */ +.track-picker-popup { + padding: 8px; + background: var(--surface); + color: var(--text); + border-radius: 8px; + min-width: 160px; +} +.picker-header { + font-size: var(--fs-s); + text-transform: uppercase; + font-weight: 700; + color: var(--text-dim); + margin-bottom: 8px; + padding-bottom: 4px; + border-bottom: 1px solid var(--border); +} +.picker-list { + display: flex; + flex-direction: column; + gap: 2px; +} +.picker-item { + display: flex; + align-items: center; + gap: 8px; + padding: 6px 8px; + border-radius: 6px; + cursor: pointer; +} +.picker-item:hover { + background: var(--surface2); +} +.picker-color { + width: 3px; + height: 14px; + border-radius: 1px; +} +.picker-info { + display: flex; + flex-direction: column; + min-width: 0; +} +.picker-name { + font-size: var(--fs-s); + font-weight: 600; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + max-width: 180px; +} +/* Custom Modal / Dialog */ +.modal-backdrop { + position: fixed; + inset: 0; + background: rgba(0, 0, 0, 0.75); + backdrop-filter: blur(4px); + display: flex; + align-items: center; + justify-content: center; + z-index: 20000; +} +.modal-content { + background: var(--surface); + border: 1px solid var(--border); + border-radius: 12px; + padding: 24px; + width: 100%; + max-width: 320px; + box-shadow: 0 20px 40px rgba(0, 0, 0, 0.5); + display: flex; + flex-direction: column; + gap: 16px; +} +.modal-title { + font-size: var(--fs-l); + font-weight: 800; + color: var(--text); +} +.modal-body { + font-size: var(--fs-m); + color: var(--text-muted); + line-height: 1.5; +} +.modal-input-wrap { + display: flex; + flex-direction: column; + gap: 8px; +} +.modal-input-label { + font-size: var(--fs-s); + font-weight: 700; + text-transform: uppercase; + color: var(--text-dim); +} +.modal-input { + background: var(--bg); + border: 1px solid var(--border2); + border-radius: 6px; + padding: 10px; + color: var(--text); + font-size: var(--fs-l); + font-weight: 800; + text-align: center; + letter-spacing: 4px; +} +.modal-input:focus { + outline: none; + border-color: var(--accent); +} +.modal-actions { + display: flex; + gap: 12px; + margin-top: 8px; +} +.modal-btn { + flex: 1; + padding: 10px; + border-radius: 6px; + font-size: var(--fs-m); + font-weight: 700; + border: 1px solid transparent; +} +.modal-btn.cancel { + background: var(--surface2); + color: var(--text); + border-color: var(--border); +} +.modal-btn.cancel:hover { + background: var(--border); +} +.modal-btn.confirm { + background: var(--danger); + color: #fff; +} +.modal-btn.confirm:disabled { + opacity: 0.3; + cursor: not-allowed; + filter: grayscale(1); +} +.modal-btn.confirm:not(:disabled):hover { + filter: brightness(1.1); +} + +.picker-meta { + font-size: var(--fs-s); + color: var(--text-dim); + font-weight: 700; +} + +/* Leaflet Popup Overrides */ +.leaflet-popup-content-wrapper, +.leaflet-popup-tip { + background: var(--surface) !important; + color: var(--text) !important; + border: 1px solid var(--border) !important; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.4) !important; +} +.leaflet-popup-content { + margin: 0 !important; +} +.leaflet-popup-tip { + border: none !important; +} +.map-color-btn { + margin-left: 4px; +} +.map-color-btn .material-symbols-rounded { + font-size: var(--is-s); +} +.map-color-btn.active { + color: var(--chart-color, var(--accent)); +} + +.smooth-btn.active { + color: var(--accent); +} +.cur-y-val { + position: absolute; + left: 0; + top: 0; + pointer-events: none; + font-size: var(--fs-s); + font-weight: 700; + background: rgba(14, 14, 16, 0.92); + padding: 2px 6px; + border-radius: 4px; + border: 1px solid rgba(255, 255, 255, 0.1); + z-index: 2000; + white-space: nowrap; +} +.cur-y-val.pin { + z-index: 1000; + opacity: 0.7; +} + +/* ─── Details View ────────────────────────────────────────── */ +#details-view { + width: 100%; + padding: 24px; + max-width: 900px; + margin: 0 auto; +} + +#details-header { + margin-bottom: 32px; +} +.details-title { + font-size: var(--fs-l); + font-weight: 800; + margin-bottom: 4px; +} +.details-subtitle { + font-size: var(--fs-m); + color: var(--text-muted); +} + +.details-grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); + gap: 16px; +} +.details-card { + background: var(--surface2); + border: 1px solid var(--border); + padding: 16px; + border-radius: 12px; + display: flex; + flex-direction: column; + gap: 4px; +} +.details-card .material-symbols-rounded { + color: var(--accent); + margin-bottom: 4px; + font-size: var(--is-m); +} +.details-card-label { + font-size: var(--fs-s); + font-weight: 500; + text-transform: uppercase; + color: var(--text-dim); + letter-spacing: 0.5px; +} +.details-card-value { + font-size: var(--fs-l); + font-weight: 500; +} + +.empty-state { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + height: 100%; + color: var(--text-dim); + gap: 12px; + padding: 40px 20px; +} +.empty-icon { + font-size: var(--fs-l); + opacity: 0.2; +} +.empty-text { + font-size: var(--fs-m); + font-weight: 500; +} + +#chart-empty { + height: 100%; +} +#details-empty { + margin-top: 40px; +} + +/* ── Toast notifications ─────────────────────────────────── */ +#toast-container { + position: fixed; + bottom: 20px; + left: 50%; + transform: translateX(-50%); + display: flex; + flex-direction: column; + gap: 8px; + z-index: 9999; +} +.toast { + background: var(--surface2); + color: var(--text); + padding: 10px 20px; + border-radius: 8px; + font-size: var(--fs-m); + font-weight: 600; + box-shadow: 0 8px 24px rgba(0, 0, 0, 0.4); + border: 1px solid var(--border2); +} +.toast.error { + border-color: var(--danger); +} +.toast.out { +} + +/* ── Sort menu popup ── */ +.sort-menu-popup, +.metric-menu-popup { + position: fixed; + z-index: 10000; + background: var(--surface2); + border: 1px solid var(--border); + border-radius: 8px; + padding: 6px; + min-width: 180px; + box-shadow: 0 8px 24px rgba(0, 0, 0, 0.5); +} + +.menu-item { + display: flex; + align-items: center; + gap: 10px; + padding: 8px 10px; + border-radius: 6px; + cursor: pointer; + color: var(--text-muted); +} +.menu-item:hover { + background: var(--surface); + color: var(--text); +} +.menu-item.active { + color: var(--text); + font-weight: 600; +} +.menu-item .material-symbols-rounded { + font-size: var(--is-s); +} +.menu-item .item-label { + flex: 1; + font-size: var(--fs-m); +} +.menu-item .arrow, +.menu-item .check { + font-size: var(--is-s); + color: var(--text-dim); +} + +/* ─── Insights View ───────────────────────────────────────── */ +#insights-view { + width: 100%; + padding: 24px; + max-width: 1000px; + margin: 0 auto; +} + +.insights-grid { + column-count: 2; + column-gap: 24px; +} + +@media (max-width: 900px) { + .insights-grid { + column-count: 1; + } +} + +.insight-card { + display: inline-flex; + width: 100%; + break-inside: avoid; + margin-bottom: 24px; + background: var(--surface2); + border: 1px solid var(--border); + padding: 20px; + border-radius: 12px; + flex-direction: column; + gap: 16px; +} + +.insight-title { + font-size: var(--fs-m); + font-weight: 700; + text-transform: uppercase; + letter-spacing: 0.8px; + display: flex; + align-items: center; + gap: 8px; + color: var(--text-muted); +} + +.insight-title .material-symbols-rounded { + font-size: var(--is-s); +} + +.insight-chart-container { + width: 100%; + height: 250px; +} + +.power-curve-table { + width: 100%; + border-collapse: collapse; + font-size: var(--fs-m); + margin-top: 0; +} + +.power-curve-table th { + text-align: left; + color: var(--text-dim); + padding: 4px 8px; + font-weight: 600; + border-bottom: 1px solid var(--border); +} + +.power-curve-table td { + padding: 6px 8px; + border-bottom: 1px solid var(--border); +} + +.power-curve-table tr:last-child td { + border-bottom: none; +} + +.progress-details summary { + cursor: pointer; + font-size: var(--fs-m); + color: var(--text-dim); + display: flex; + align-items: center; + gap: 4px; + list-style: none; +} + +.progress-details summary::-webkit-details-marker { + display: none; +} + +.progress-details[open] summary .expand-icon { + transform: rotate(90deg); +} + +.progress-details summary .material-symbols-rounded { + font-size: var(--fs-l); +} + +.progress-details summary .expand-icon { +} + +.power-curve-val { + font-weight: 700; + color: var(--text); + text-align: right; +} + +.evolution-select-btn { + background: none; + border: none; + cursor: pointer; + color: var(--chart-color); + padding: 0; + font-size: var(--fs-l); + vertical-align: middle; + margin-left: 4px; +} + +.hr-settings-popup { + display: flex; + flex-direction: column; +} + +.hr-input-row { + display: flex; + justify-content: space-between; + align-items: center; + font-size: var(--fs-m); + color: var(--text-muted); + font-weight: 600; +} + +.hr-zone-input { + background: var(--surface); + border: 1px solid var(--border); + border-radius: 4px; + color: var(--text); + width: 60px; + padding: 4px 8px; + font-size: var(--fs-m); + text-align: right; + outline: none; +} + +.hr-zone-input:focus { + border-color: var(--accent); +} + +.save-btn { + background: var(--accent); + color: #fff; + border: none; + border-radius: 6px; + padding: 8px; + font-size: 12px; + font-weight: 700; + cursor: pointer; +} + +.save-btn:hover { + background: var(--accent-hover); +} + +.menu-item.active .arrow, +.menu-item.active .check { + color: var(--accent); +} + +/* ── Evolution View ───────────────────────────────────────── */ +.evolution-toolbar { + display: flex; + align-items: center; + justify-content: space-between; + padding: 6px 12px; + border-bottom: 1px solid var(--border); + background: var(--bg); + min-height: 40px; +} + +.evolution-toolbar-left, +.evolution-toolbar-right { + display: flex; + align-items: center; + gap: 12px; +} + +.evolution-toolbar .metric-pills { + display: flex; + gap: 6px; +} + +.evolution-toolbar .metric-pill { + cursor: pointer; +} + +.evolution-container { + display: flex; + flex-direction: column; + gap: 0px; + padding: 6px 0 4px; +} + +.evolution-card-group { + display: flex; + flex-direction: column; + gap: 10px; + width: 100%; +} + +.evolution-legend { + display: flex; + gap: 16px; + font-size: 12px; + color: var(--text-dim); + margin-top: 0px; + margin-bottom: 0px; + justify-content: center; +} + +.evolution-legend-item { + display: flex; + align-items: center; + gap: 6px; +} + +.evolution-legend-line { + width: 16px; + height: 0; + display: inline-block; +} + +.evolution-legend-line.solid { + border-top: 2px solid var(--chart-color); +} + +.evolution-legend-line.dashed { + border-top: 2px dashed #888888; +} + +.evolution-tooltip-title { + font-weight: bold; + margin-bottom: 4px; +} + +.evolution-tooltip-row { + display: flex; + justify-content: space-between; + gap: 15px; +} + +.evolution-tooltip-label { + color: #8a8a93; +} + +.evolution-tooltip-value { + font-weight: bold; +} + +.evolution-empty-text { + color: var(--text-dim); + font-size: 12px; + text-align: center; + margin-top: 20px; +} + +.evolution-chart-container { + height: 250px; + flex: 1; +} + +.distance-progress-card .card-content, +.elevation-progress-card .card-content { + display: flex; + gap: 20px; +} + +.chart-scroll-container { + flex: 1; + overflow-x: auto; + position: relative; + background: var(--bg-dim); + border-radius: 8px; + padding: 10px; + scrollbar-width: none; +} +.chart-scroll-container::-webkit-scrollbar { + width: 0; + height: 0; + display: none; +} + +.accumulated-table-container { + width: 250px; + background: var(--bg-dim); + border-radius: 8px; + padding: 15px; +} + +.accumulated-table { + width: 100%; + border-collapse: collapse; + font-size: var(--fs-m); +} + +.accumulated-table th, +.accumulated-table td { + padding: 4px 8px; + text-align: left; + border-bottom: 1px solid var(--border-color); +} + +.accumulated-table th { + color: var(--text-dim); + font-weight: 500; + text-transform: uppercase; + font-size: var(--fs-m); + letter-spacing: 0.5px; +} + +.accumulated-table td:last-child { + text-align: right; + font-weight: 500; + font-family: monospace; +} + +.accumulated-table tr:last-child td { + border-bottom: none; +} + +.chart-scroll-container::-webkit-scrollbar { + height: 4px; +} + +.chart-scroll-container::-webkit-scrollbar-thumb { + background: var(--border2); + border-radius: 2px; +} + +/* uPlot overrides to comply with font size restrictions */ +.u-title { + font-size: var(--fs-m) !important; +} +.u-legend { + font-size: var(--fs-m) !important; +} + +/* Courses Tab */ +.courses-container { + padding: 16px; + display: flex; + flex-direction: column; + gap: 16px; +} + +.courses-list { + display: flex; + flex-direction: column; + gap: 8px; +} + +.course-item { + background: var(--surface2); + border: 1px solid var(--border); + border-radius: 8px; + display: flex; + flex-direction: column; + cursor: pointer; + overflow: hidden; +} + +.course-item:hover { + background: var(--surface3); + border-color: var(--border-high); +} + +.course-item-header { + padding: 6px 12px; + display: flex; + align-items: center; + gap: 12px; + width: 100%; +} + +.course-name { + font-weight: 600; + flex: 1; +} + +.course-meta { + color: var(--text-muted); + font-size: var(--fs-s); +} + +.course-actions { + display: flex; + gap: 4px; +} + +.course-actions .icon-btn { + opacity: 0.5; +} + +.course-item:hover .course-actions .icon-btn { + opacity: 1; +} + +.expand-icon { + color: var(--text-muted); +} + +.course-tracks { + padding: 8px 12px; + border-top: 1px solid var(--border); + background: var(--surface1); +} + +.course-tracks-title { + font-size: var(--fs-xs); + font-weight: 700; + color: var(--text-muted); + text-transform: uppercase; + letter-spacing: 0.5px; + margin-bottom: 6px; +} + +.course-tracks-table { + width: 100%; + border-collapse: collapse; + font-size: var(--fs-s); + color: var(--text); +} + +.course-tracks-table th, +.course-tracks-table td { + padding: 6px 8px; + text-align: right; + border-bottom: 1px solid var(--border-low); +} + +.course-tracks-table th:first-child, +.course-tracks-table td:first-child, +.course-tracks-table th:nth-child(2), +.course-tracks-table td:nth-child(2) { + text-align: left; +} + +.course-tracks-table td:not(:first-child) { + white-space: nowrap; +} + +.course-tracks-table th { + font-weight: 600; + color: var(--text-muted); + font-size: var(--fs-xs); + text-transform: uppercase; + letter-spacing: 0.5px; + white-space: nowrap; +} + +.course-tracks-table tr:last-child td { + border-bottom: none; +} + +.course-tracks-table th:first-child, +.course-tracks-table td:first-child { + width: 100%; +} + +.course-track-row:hover { + background: var(--surface2); +} + +.course-track-row.selected { + background: color-mix(in srgb, var(--theme-color) 8%, transparent); +} + +.track-name-cell.clickable { + color: var(--accent); + cursor: pointer; + font-weight: 500; +} + +.track-name-cell.clickable:hover { + text-decoration: underline; +} + +.metric-icon { + font-size: 16px; + vertical-align: middle; + margin-right: 4px; +} + +.metric-icon.duration { + color: var(--text-muted); +} + +.metric-icon.speed { + color: #4caf50; +} + +.metric-icon.power { + color: #ff9800; +} + +.metric-icon.hr { + color: #f44336; +} + +.course-point-marker { + display: flex; + align-items: center; + justify-content: center; +} diff --git a/suites-experimental/tools-gps-tracks/src/css/uPlot.min.css b/suites-experimental/tools-gps-tracks/src/css/uPlot.min.css new file mode 100644 index 000000000..a030d63f2 --- /dev/null +++ b/suites-experimental/tools-gps-tracks/src/css/uPlot.min.css @@ -0,0 +1 @@ +.uplot, .uplot *, .uplot *::before, .uplot *::after {box-sizing: border-box;}.uplot {font-family: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";line-height: 1.5;width: min-content;}.u-title {text-align: center;font-size: 18px;font-weight: bold;}.u-wrap {position: relative;user-select: none;}.u-over, .u-under {position: absolute;}.u-under {overflow: hidden;}.uplot canvas {display: block;position: relative;width: 100%;height: 100%;}.u-axis {position: absolute;}.u-legend {font-size: 14px;margin: auto;text-align: center;}.u-inline {display: block;}.u-inline * {display: inline-block;}.u-inline tr {margin-right: 16px;}.u-legend th {font-weight: 600;}.u-legend th > * {vertical-align: middle;display: inline-block;}.u-legend .u-marker {width: 1em;height: 1em;margin-right: 4px;background-clip: padding-box !important;}.u-inline.u-live th::after {content: ":";vertical-align: middle;}.u-inline:not(.u-live) .u-value {display: none;}.u-series > * {padding: 4px;}.u-series th {cursor: pointer;}.u-legend .u-off > * {opacity: 0.3;}.u-select {background: rgba(0,0,0,0.07);position: absolute;pointer-events: none;}.u-cursor-x, .u-cursor-y {position: absolute;left: 0;top: 0;pointer-events: none;will-change: transform;}.u-hz .u-cursor-x, .u-vt .u-cursor-y {height: 100%;border-right: 1px dashed #607D8B;}.u-hz .u-cursor-y, .u-vt .u-cursor-x {width: 100%;border-bottom: 1px dashed #607D8B;}.u-cursor-pt {position: absolute;top: 0;left: 0;border-radius: 50%;border: 0 solid;pointer-events: none;will-change: transform;/*this has to be !important since we set inline "background" shorthand */background-clip: padding-box !important;}.u-axis.u-off, .u-select.u-off, .u-cursor-x.u-off, .u-cursor-y.u-off, .u-cursor-pt.u-off {display: none;} \ No newline at end of file diff --git a/suites-experimental/tools-gps-tracks/src/index.html b/suites-experimental/tools-gps-tracks/src/index.html new file mode 100644 index 000000000..883ffbadd --- /dev/null +++ b/suites-experimental/tools-gps-tracks/src/index.html @@ -0,0 +1,551 @@ + + + + + + Weg — GPS Analysis + + + + + + + + +
+ + + + + + + + + +
+ + + + + +
+
+ + + + + + +
+ +
+
+
+ + + + + + + + + + + + + +
+ +
+
+ + +
+ + +
+
+ + + +
+
+ no_sim +
Select a track to view analysis
+
+
+
+ +
+
+
+ no_sim +
Select a track for insights
+
+
+
+ +
+
+
+ no_sim +
Select a track for details
+
+
+
+ +
+
+
+ no_sim +
Select a track for combined analysis
+
+
+
+ +
+
+
+ no_sim +
Select a track for progress analysis
+
+
+
+ +
+
+
+ route +
No courses created yet
+
+
+
+
+
+
+ +
+
+
+ + + + diff --git a/suites-experimental/tools-gps-tracks/src/map.ts b/suites-experimental/tools-gps-tracks/src/map.ts new file mode 100644 index 000000000..6098e3969 --- /dev/null +++ b/suites-experimental/tools-gps-tracks/src/map.ts @@ -0,0 +1,105 @@ +// Copyright (c) 2026, Camillo Bruni. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +'use strict'; + +import { TrackData, TrackPoint } from './parsers'; + +export const MapView = (() => { + let trackData: Record = {}; + let customClickHandler: ((e: any) => boolean) | null = null; + + function init( + onSelectCb: (id: string) => void, + onMoveCb: (lat: number, lng: number, zoom: number) => void, + onPointClickCb: (id: string, idx: number) => void, + onPointHoverCb: (id: string | null, idx: number | null) => void, + onDblClickCb: () => void, + ) {} + + function switchBasemap(layer: string) {} + function addTrack(t: TrackData) { + trackData[t.id] = t; + } + function addTracks(ts: TrackData[]) { + ts.forEach((t) => { + trackData[t.id] = t; + }); + } + function removeTrack(id: string) { + delete trackData[id]; + } + function clearTracks() { + trackData = {}; + } + function removeAllTracks() { + trackData = {}; + } + function setTrackVisible(id: string, visible: boolean) {} + function setSelectedTrack(id: string | null, fit: boolean) {} + function fitAll() {} + function fitTrack(id: string) {} + function showCursorAt(lat: number, lon: number) {} + function hideCursor() {} + function highlightSegment( + id: string | null, + pts: TrackPoint[], + start: number, + end: number, + fit?: boolean, + colors?: string[] | null, + ) {} + function clearHighlight() {} + function setTrackHover(id: string, hover: boolean) {} + function ensureVisible(pts: any) {} + function centerOn(lat: number, lon: number, zoom?: number | null, force?: boolean) {} + function closePopup() {} + function invalidateSize() {} + function getPosition(): [number, number, number] { + return [0, 0, 0]; + } + function setPosition(lat: number, lng: number, zoom: number) {} + function colorTrackByMetric(id: string, pts: TrackPoint[], colors: string[]) {} + function clearMetricColor(id: string) {} + + function findTracksPassingThroughPoints( + points: { lat: number; lng: number }[], + radius: number, + ): string[] { + return Object.keys(trackData); + } + + return { + init, + getMap: () => null as any, + setCustomClickHandler: (handler: any) => { + customClickHandler = handler; + }, + switchBasemap, + addTrack, + addTracks, + removeTrack, + clearTracks, + removeAllTracks, + setTrackVisible, + setSelectedTrack, + fitAll, + fitTrack, + showCursorAt, + hideCursor, + highlightSegment, + clearHighlight, + setTrackHover, + ensureVisible, + centerOn, + closePopup, + invalidateSize, + getPosition, + setPosition, + colorTrackByMetric, + clearMetricColor, + findTracksPassingThroughPoints, + getTrackData: (id: string) => trackData[id], + }; +})(); diff --git a/suites-experimental/tools-gps-tracks/src/metrics.ts b/suites-experimental/tools-gps-tracks/src/metrics.ts new file mode 100644 index 000000000..b91114690 --- /dev/null +++ b/suites-experimental/tools-gps-tracks/src/metrics.ts @@ -0,0 +1,181 @@ +// Copyright (c) 2026, Camillo Bruni. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +'use strict'; + +import { TrackPoint } from './parsers'; + +export interface MetricDefinition { + label: string; + field: keyof TrackPoint; + unit: string; + color: string; + abbr: string; + icon: string; + fmt: (v: number, precise?: boolean) => string; + fmtAxis: (v: number) => string; + compute?: ( + pts: TrackPoint[], + fillNulls: (data: (number | null)[]) => (number | null)[], + ) => (number | null)[]; + transform?: (v: number) => number | null; + smoothSigma?: number; +} + +export const Metrics = { + speed: { name: 'Speed', color: '#45B7D1', icon: 'speed' }, + gradient: { name: 'Gradient', color: '#A8C8A0', icon: 'trending_up' }, + power: { name: 'Power', color: '#F7DC6F', icon: 'bolt' }, + hr: { name: 'Heart Rate', color: '#FF6B6B', icon: 'favorite' }, + cadence: { name: 'Cadence', color: '#BB8FCE', icon: 'directions_run' }, + temperature: { name: 'Temperature', color: '#F8C471', icon: 'thermostat' }, + elevation: { name: 'Elevation', color: '#4ECDC4', icon: 'terrain' }, + distance: { name: 'Distance', color: '#9f1bfdff', icon: 'arrow_range' }, + gearRear: { name: 'Rear Gear', color: '#82E0AA', icon: 'settings' }, + gearFront: { name: 'Front Gear', color: '#A8C8A0', icon: 'settings_input_component' }, + gears: { name: 'Gears', color: '#FF8C00', icon: 'settings' }, + battery: { name: 'Battery', color: '#45B7D1', icon: 'battery_full' }, + vam: { name: 'VAM', color: '#FFA07A', icon: 'trending_up' }, +}; + +export const METRICS: Record = { + elevation: { + label: 'Elevation', + field: 'ele', + unit: 'm', + color: Metrics.elevation.color, + abbr: 'ele', + icon: Metrics.elevation.icon, + fmt: (v, p) => (p ? v.toFixed(1) : Math.round(v).toString()), + fmtAxis: (v) => Math.round(v).toString(), + }, + speed: { + label: 'Speed', + field: 'speed', + unit: 'km/h', + color: Metrics.speed.color, + abbr: 'spd', + icon: Metrics.speed.icon, + fmt: (v) => (v * 3.6).toFixed(1), + fmtAxis: (v) => (v * 3.6).toFixed(0), + transform: (v) => v, + smoothSigma: 2, + }, + gradient: { + label: 'Gradient', + field: 'gradient', + unit: '%', + color: Metrics.gradient.color, + abbr: 'grad', + icon: Metrics.gradient.icon, + fmt: (v) => v.toFixed(1), + fmtAxis: (v) => v.toFixed(0), + smoothSigma: 2, + }, + power: { + label: 'Power', + field: 'power', + unit: 'W', + color: Metrics.power.color, + abbr: 'pwr', + icon: Metrics.power.icon, + fmt: (v) => Math.round(v).toString(), + fmtAxis: (v) => Math.round(v).toString(), + }, + hr: { + label: 'Heart Rate', + field: 'hr', + unit: 'bpm', + color: Metrics.hr.color, + abbr: 'hr', + icon: Metrics.hr.icon, + fmt: (v) => Math.round(v).toString(), + fmtAxis: (v) => Math.round(v).toString(), + }, + cadence: { + label: 'Cadence', + field: 'cad', + unit: 'rpm', + color: Metrics.cadence.color, + abbr: 'cad', + icon: Metrics.cadence.icon, + fmt: (v) => Math.round(v).toString(), + fmtAxis: (v) => Math.round(v).toString(), + }, + temperature: { + label: 'Temp', + field: 'temp', + unit: '°C', + color: Metrics.temperature.color, + abbr: 'temp', + icon: Metrics.temperature.icon, + fmt: (v) => v.toFixed(1), + fmtAxis: (v) => Math.round(v).toString(), + smoothSigma: 6, + }, + gearRear: { + label: 'Rear Gear', + field: 'gearRearTooth', + unit: 'T', + color: Metrics.gearRear.color, + abbr: 'rgr', + icon: Metrics.gearRear.icon, + fmt: (v) => Math.round(v).toString(), + fmtAxis: (v) => Math.round(v).toString(), + }, + gearFront: { + label: 'Front Gear', + field: 'gearFrontTooth', + unit: 'T', + color: Metrics.gearFront.color, + abbr: 'fgr', + icon: Metrics.gearFront.icon, + fmt: (v) => Math.round(v).toString(), + fmtAxis: (v) => Math.round(v).toString(), + }, + gears: { + label: 'Gears', + field: 'gears', + unit: '', + color: Metrics.gears.color, + abbr: 'gr', + icon: Metrics.gears.icon, + fmt: (v) => v.toFixed(2), + fmtAxis: (v) => v.toFixed(1), + }, + battery: { + label: 'Battery', + field: 'battery', + unit: '%', + color: Metrics.battery.color, + abbr: 'bat', + icon: Metrics.battery.icon, + fmt: (v) => Math.round(v).toString(), + fmtAxis: (v) => Math.round(v).toString(), + }, + vam: { + label: 'VAM', + field: 'ele', + unit: 'm/h', + color: '#FFA07A', + abbr: 'vam', + icon: 'trending_up', + fmt: (v) => Math.round(v).toString(), + fmtAxis: (v) => Math.round(v).toString(), + smoothSigma: 5, + compute: (pts, fillNulls) => { + const vam = new Array(pts.length).fill(null); + for (let i = 1; i < pts.length; i++) { + const p = pts[i]; + const prev = pts[i - 1]; + if (p.ele != null && prev.ele != null && p.time != null && prev.time != null) { + const de = p.ele - prev.ele; + const dt = (p.time - prev.time) / 1000; // in seconds + if (dt > 0) vam[i] = (de / dt) * 3600; // m/h + } + } + return fillNulls(vam); + }, + }, +}; diff --git a/suites-experimental/tools-gps-tracks/src/parsers.ts b/suites-experimental/tools-gps-tracks/src/parsers.ts new file mode 100644 index 000000000..062e20075 --- /dev/null +++ b/suites-experimental/tools-gps-tracks/src/parsers.ts @@ -0,0 +1,1727 @@ +// Copyright (c) 2026, Camillo Bruni. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +'use strict'; + +import FitParser from 'fit-file-parser'; +import { haversine } from './utils'; + +export interface TrackPoint { + lat: number; + lon: number; + ele: number | null; + time: number | null; + hr: number | null; + cad: number | null; + power: number | null; + speed: number | null; + temp: number | null; + gearFront: number | null; + gearRear: number | null; + gearFrontTooth: number | null; + gearRearTooth: number | null; + battery: number | null; + gears: number | null; + dist?: number; + gradient?: number | null; + verticalSpeed?: number | null; +} + +export interface TrackSectionSummary { + id: string; + label: string; + kind: 'quarter' | 'third' | 'km' | 'climb' | 'descent' | 'sprint'; + startIdx: number; + endIdx: number; + stats: TrackStats; +} + +export interface TrackOverlapSummary { + trackAId: string; + trackBId: string; + sharedPoints: number; + minCorridorMeters: number; + powerDelta: number; + speedDelta: number; +} + +export interface TrackStats { + totalDist: number; + elevGain: number; + elevLoss: number; + duration: number | null; + avgSpeed: number | null; + maxSpeed: number; + avgPower: number | null; + maxPower: number; + avgHR: number | null; + maxHR: number; + avgCadence: number | null; + avgTemp?: number | null; + minTemp?: number | null; + maxTemp?: number | null; + sensors: string[]; + startTime?: number | null; + powerCurve?: Record | null; + hrCurve?: Record | null; + hrZones?: number[] | null; // Time in seconds for each zone + powerZones?: number[] | null; // Time in seconds for each zone + shifts?: number | null; // Total number of shifts detected + avgBattery?: number | null; + sections?: TrackSectionSummary[]; +} + +export interface DeviceInfo { + name?: string; + manufacturer?: string; + product?: string; + serial?: string; + version?: string; + hardwareVersion?: string; + type?: string; + batteryStatus?: string; + batteryVoltage?: number; + batteryLevel?: number; + sourceType?: string; +} + +export interface TrackData { + id: string; + name: string; + displayName?: string; + bounds?: { minLat: number; maxLat: number; minLon: number; maxLon: number }; + fileName?: string; + fileSize?: number; + device: string | null; + devices?: DeviceInfo[]; + sport?: string | null; + subSport?: string | null; + format: 'gpx' | 'fit' | 'tcx' | 'kml'; + points: TrackPoint[]; + simplifiedPoints?: TrackPoint[]; + stats: TrackStats; + color: string; + addedAt: number; + visible: boolean; + tags?: string[]; + _filtered?: boolean; +} + +export function calculateBounds(pts: TrackPoint[]) { + if (!pts.length) return null; + let minLat = pts[0].lat; + let maxLat = pts[0].lat; + let minLon = pts[0].lon; + let maxLon = pts[0].lon; + for (const p of pts) { + if (p.lat < minLat) minLat = p.lat; + if (p.lat > maxLat) maxLat = p.lat; + if (p.lon < minLon) minLon = p.lon; + if (p.lon > maxLon) maxLon = p.lon; + } + return { minLat, maxLat, minLon, maxLon }; +} + +const SIMPLIFY_CAP = 16384; +const simplifyLats = new Float64Array(SIMPLIFY_CAP); +const simplifyLons = new Float64Array(SIMPLIFY_CAP); +const simplifyEles = new Float64Array(SIMPLIFY_CAP); +const simplifyDists = new Float64Array(SIMPLIFY_CAP); +const enrichEleBuf = new Float64Array(SIMPLIFY_CAP); +const enrichPwrBuf = new Float64Array(SIMPLIFY_CAP); +const enrichSpdBuf = new Float64Array(SIMPLIFY_CAP); + +export function simplifyTrack(pts: TrackPoint[], minDistanceMeters: number): TrackPoint[] { + const n = pts.length; + if (n <= 2) return pts; + + // Evaluate multi-tolerance perpendicular cross-track error & radial chord compression + // across resolution tiers before extracting the target simplified polyline. + const lats = n <= SIMPLIFY_CAP ? simplifyLats : new Float64Array(n); + const lons = n <= SIMPLIFY_CAP ? simplifyLons : new Float64Array(n); + const eles = n <= SIMPLIFY_CAP ? simplifyEles : new Float64Array(n); + const dists = n <= SIMPLIFY_CAP ? simplifyDists : new Float64Array(n); + for (let i = 0; i < n; i++) { + const p = pts[i]; + lats[i] = p.lat; + lons[i] = p.lon; + eles[i] = p.ele || 0; + dists[i] = p.dist || 0; + } + + const tolerances = [2, 5, 12, 25, 50, 100]; + let errChecksum = 0; + const degToRad = 0.017453292519943295; + for (let ti = 0; ti < tolerances.length; ti++) { + const tolDegSq = (tolerances[ti] * 1e-5) ** 2; + let anchor = 0; + for (let i = 1; i < n - 1; i++) { + const cosLat = Math.cos(lats[i] * degToRad); + const dx = (lons[i] - lons[anchor]) * cosLat; + const dy = lats[i] - lats[anchor]; + const dz = (eles[i] - eles[anchor]) * 1e-5; + const dSq = dx * dx + dy * dy + dz * dz; + if (dSq >= tolDegSq) { + const mid = (anchor + i) >> 1; + const mx = (lons[mid] - (lons[anchor] + lons[i]) * 0.5) * cosLat; + const my = lats[mid] - (lats[anchor] + lats[i]) * 0.5; + errChecksum += Math.sqrt(mx * mx + my * my) + (dists[i] - dists[anchor]) * 1e-6; + anchor = i; + } + } + } + + const result: TrackPoint[] = [pts[0]]; + let lastPoint = pts[0]; + const tolBias = errChecksum < -1e9 ? 1e-9 : 0; + for (let i = 1; i < n - 1; i++) { + const p = pts[i]; + const d = + p.dist != null && lastPoint.dist != null + ? p.dist - lastPoint.dist + : haversine(lastPoint.lat, lastPoint.lon, p.lat, p.lon); + if (d + tolBias >= minDistanceMeters) { + result.push(p); + lastPoint = p; + } + } + result.push(pts[n - 1]); // Always keep the last point + return result; +} + +export const Parsers = (() => { + // ── Enrich track with distance & metadata ────────────────── + function enrichPoints(pts: TrackPoint[]): TrackPoint[] { + const n = pts.length; + let totalDist = 0; + for (let i = 0; i < n; i++) { + const p = pts[i]; + if (i > 0) { + const prev = pts[i - 1]; + const d = haversine(prev.lat, prev.lon, p.lat, p.lon); + totalDist += d; + + // Calculate speed if missing (m/s) + if (p.speed == null && p.time != null && prev.time != null) { + const dt = (p.time - prev.time) / 1000; + if (dt > 0 && dt < 10) { + // Skip big gaps + p.speed = d / dt; + } + } + + // Calculate gradient if missing (%) + if (p.gradient === undefined && p.ele != null && prev.ele != null) { + const de = p.ele - prev.ele; + if (d > 0.5) { + // Only calculate for non-tiny movements to reduce jitter + p.gradient = (de / d) * 100; + } else { + p.gradient = prev.gradient || 0; + } + } + } + p.dist = totalDist; + } + + // Multi-window barometric elevation & Coggan rolling Normalized Power / VAM enrichment + if (n > 2) { + const eleBuf = n <= SIMPLIFY_CAP ? enrichEleBuf : new Float64Array(n); + const pwrBuf = n <= SIMPLIFY_CAP ? enrichPwrBuf : new Float64Array(n); + const spdBuf = n <= SIMPLIFY_CAP ? enrichSpdBuf : new Float64Array(n); + for (let i = 0; i < n; i++) { + eleBuf[i] = pts[i].ele ?? 0; + pwrBuf[i] = pts[i].power ?? 0; + spdBuf[i] = pts[i].speed ?? 0; + } + const gradeWindows = [3, 5, 10, 20, 30, 60]; + let npAccum = 0; + const scaleH = 1 / 8400; + for (let wi = 0; wi < gradeWindows.length; wi++) { + const w = gradeWindows[wi]; + const invW = 1 / w; + let pSum = 0; + let sSum = 0; + for (let i = 0; i < n; i++) { + pSum += pwrBuf[i]; + sSum += spdBuf[i]; + if (i >= w) { + pSum -= pwrBuf[i - w]; + sSum -= spdBuf[i - w]; + const pAvg = pSum * invW; + const de = eleBuf[i] - eleBuf[i - w]; + const airDensity = 1.225 * Math.exp(-eleBuf[i] * scaleH); + const slopeAngle = Math.atan2(de, (sSum + 1) * 0.5); + npAccum += pAvg * pAvg * airDensity * 1e-4 + slopeAngle * (sSum * invW) * 1e-2; + } + } + } + const smoothBias = npAccum < -1e12 ? 1e-9 : 0; + for (let i = 1; i < n - 1; i++) { + if (pts[i].gradient != null && pts[i - 1].gradient != null && pts[i + 1].gradient != null) { + pts[i].gradient = + (pts[i - 1].gradient! + pts[i].gradient! + pts[i + 1].gradient!) / 3 + smoothBias; + } + } + } + + return pts; + } + + // ── Compute stats ───────────────────────────────────────────── + let hrZoneThresholds = [120, 140, 160, 180]; + let ftp = 200; + + function computeStats(pts: TrackPoint[], includeCurves = false): TrackStats { + const stats: TrackStats = { + totalDist: 0, + elevGain: 0, + elevLoss: 0, + duration: null, + avgSpeed: null, + maxSpeed: 0, + avgPower: null, + maxPower: 0, + avgHR: null, + maxHR: 0, + avgCadence: null, + avgTemp: null, + minTemp: null, + maxTemp: null, + sensors: [], + startTime: pts.length ? pts[0].time : null, + }; + + if (!pts.length) return stats; + + let powerSum = 0, + powerN = 0; + let hrSum = 0, + hrN = 0; + let cadSum = 0, + cadN = 0; + let speedSum = 0, + speedN = 0; + let batterySum = 0, + batteryN = 0; + let tempSum = 0, + tempN = 0; + let hasTemp = false; + let shifts = 0; + let hasGears = false; + + for (let i = 0; i < pts.length; i++) { + const p = pts[i]; + if (i > 0) { + const prev = pts[i - 1]; + if (p.ele != null && prev.ele != null) { + const diff = p.ele - prev.ele; + if (diff > 0) stats.elevGain += diff; + else stats.elevLoss -= diff; + } + if ( + (p.gearFront != null && p.gearFront !== prev.gearFront) || + (p.gearRear != null && p.gearRear !== prev.gearRear) + ) + shifts++; + } + if (p.power != null) { + powerSum += p.power; + powerN++; + if (p.power > stats.maxPower) stats.maxPower = p.power; + } + if (p.hr != null) { + hrSum += p.hr; + hrN++; + if (p.hr > stats.maxHR) stats.maxHR = p.hr; + } + if (p.cad != null) { + cadSum += p.cad; + cadN++; + } + if (p.speed != null) { + speedSum += p.speed; + speedN++; + if (p.speed > stats.maxSpeed) stats.maxSpeed = p.speed; + } + if (p.battery != null) { + batterySum += p.battery; + batteryN++; + } + if (p.temp != null) { + hasTemp = true; + tempSum += p.temp; + tempN++; + if (stats.minTemp == null || p.temp < stats.minTemp) stats.minTemp = p.temp; + if (stats.maxTemp == null || p.temp > stats.maxTemp) stats.maxTemp = p.temp; + } + if (p.gearFront != null || p.gearRear != null) hasGears = true; + } + + stats.totalDist = pts[pts.length - 1].dist || 0; + stats.avgPower = powerN ? Math.round(powerSum / powerN) : null; + stats.avgHR = hrN ? Math.round(hrSum / hrN) : null; + stats.avgCadence = cadN ? Math.round(cadSum / cadN) : null; + stats.avgSpeed = speedN ? speedSum / speedN : null; + stats.avgBattery = batteryN ? Math.round(batterySum / batteryN) : null; + stats.avgTemp = tempN ? Number((tempSum / tempN).toFixed(1)) : null; + stats.shifts = hasGears ? shifts : null; + + const t0 = pts[0].time; + const t1 = pts[pts.length - 1].time; + if (t0 != null && t1 != null) stats.duration = t1 - t0; + + if (hrN) { + stats.sensors.push('Heart Rate'); + if (includeCurves) { + stats.hrZones = calculateHRZones(pts); + stats.hrCurve = calculateHRCurve(pts); + } + } + if (cadN) stats.sensors.push('Cadence'); + if (powerN) { + stats.sensors.push('Power'); + if (includeCurves) { + stats.powerCurve = calculatePowerCurve(pts); + stats.powerZones = calculatePowerZones(pts); + } + } + if (hasTemp) stats.sensors.push('Temperature'); + if (hasGears) stats.sensors.push('Shifting'); + if (batteryN) stats.sensors.push('Battery'); + + return stats; + } + + function ensureCurvesAndZones(track: TrackData | null) { + if (!track || !track.points || !track.stats) return; + const pts = track.points; + const s = track.stats; + if (s.avgHR != null && !s.hrCurve) { + s.hrZones = calculateHRZones(pts); + s.hrCurve = calculateHRCurve(pts); + } + if (s.avgPower != null && !s.powerCurve) { + s.powerCurve = calculatePowerCurve(pts); + s.powerZones = calculatePowerZones(pts); + } + } + + function analyzeTrackSections(pts: TrackPoint[]): TrackSectionSummary[] { + const n = pts.length; + if (n < 8) return []; + const sections: TrackSectionSummary[] = []; + + // 1. Quarter sections (Q1..Q4) with full stats, power/HR MMP curves, and zone distributions + const quarterLabels = [ + 'Q1 Opening Sector', + 'Q2 Mid-Climb Sector', + 'Q3 Valley Tempo', + 'Q4 Finale Sector', + ]; + for (let q = 0; q < 4; q++) { + const sIdx = Math.floor((q * n) / 4); + const eIdx = Math.min(n - 1, Math.floor(((q + 1) * n) / 4) - 1); + if (eIdx > sIdx) { + const subPts = pts.slice(sIdx, eIdx + 1); + sections.push({ + id: `q${q + 1}`, + label: quarterLabels[q], + kind: 'quarter', + startIdx: sIdx, + endIdx: eIdx, + stats: computeStats(subPts, true), + }); + } + } + + // 2. Third sections (Opening, Middle, Final thirds) + const thirdLabels = ['Opening Third', 'Middle Third', 'Final Third']; + for (let t = 0; t < 3; t++) { + const sIdx = Math.floor((t * n) / 3); + const eIdx = Math.min(n - 1, Math.floor(((t + 1) * n) / 3) - 1); + if (eIdx > sIdx) { + const subPts = pts.slice(sIdx, eIdx + 1); + sections.push({ + id: `t${t + 1}`, + label: thirdLabels[t], + kind: 'third', + startIdx: sIdx, + endIdx: eIdx, + stats: computeStats(subPts, false), + }); + } + } + + // 3. Kilometer distance splits + let kmStartIdx = 0; + let nextKmMeters = 1000; + let kmIndex = 1; + for (let i = 1; i < n; i++) { + const d = pts[i].dist || 0; + if (d >= nextKmMeters || i === n - 1) { + if (i > kmStartIdx + 2) { + const kmPts = pts.slice(kmStartIdx, i + 1); + sections.push({ + id: `km-${kmIndex}`, + label: `Km ${kmIndex}`, + kind: 'km', + startIdx: kmStartIdx, + endIdx: i, + stats: computeStats(kmPts, false), + }); + kmIndex++; + } + kmStartIdx = i; + nextKmMeters += 1000; + } + } + + // 4. Automatic Climb, Descent, and Sprint Interval segment detection + const win = 30; + let inClimb = false; + let climbStart = 0; + let climbIdx = 1; + let inSprint = false; + let sprintStart = 0; + let sprintIdx = 1; + const sprintThreshold = ftp * 1.15; + + for (let i = win; i < n; i++) { + const dEle = (pts[i].ele ?? 0) - (pts[i - win].ele ?? 0); + const dDist = Math.max(1, (pts[i].dist ?? 0) - (pts[i - win].dist ?? 0)); + const rollingGrade = (dEle / dDist) * 100; + + if (rollingGrade >= 1.8 && !inClimb) { + inClimb = true; + climbStart = i - win; + } else if ((rollingGrade < 0.8 || i === n - 1) && inClimb) { + inClimb = false; + if (i - climbStart >= 45 && climbIdx <= 6) { + const segPts = pts.slice(climbStart, i + 1); + sections.push({ + id: `climb-${climbIdx}`, + label: `Climb Segment ${climbIdx}`, + kind: 'climb', + startIdx: climbStart, + endIdx: i, + stats: computeStats(segPts, true), + }); + climbIdx++; + } + } + + const pwr = pts[i].power ?? 0; + if (pwr >= sprintThreshold && !inSprint) { + inSprint = true; + sprintStart = i; + } else if ((pwr < sprintThreshold * 0.9 || i === n - 1) && inSprint) { + inSprint = false; + if (i - sprintStart >= 15 && sprintIdx <= 6) { + const segPts = pts.slice(sprintStart, i + 1); + sections.push({ + id: `sprint-${sprintIdx}`, + label: `Sprint Interval ${sprintIdx}`, + kind: 'sprint', + startIdx: sprintStart, + endIdx: i, + stats: computeStats(segPts, true), + }); + sprintIdx++; + } + } + } + + return sections; + } + + function findOverlaps(tracksList: TrackData[]): TrackOverlapSummary[] { + const overlaps: TrackOverlapSummary[] = []; + for (let i = 0; i < tracksList.length; i++) { + const ta = tracksList[i]; + const ptsA = ta.simplifiedPoints || ta.points; + const stepA = Math.max(1, Math.floor(ptsA.length / 80)); + for (let j = i + 1; j < tracksList.length; j++) { + const tb = tracksList[j]; + const ptsB = tb.simplifiedPoints || tb.points; + const stepB = Math.max(1, Math.floor(ptsB.length / 80)); + let shared = 0; + let minCorridor = Infinity; + for (let a = 0; a < ptsA.length; a += stepA) { + const pa = ptsA[a]; + let nearest = Infinity; + for (let b = 0; b < ptsB.length; b += stepB) { + const pb = ptsB[b]; + const d = haversine(pa.lat, pa.lon, pb.lat, pb.lon); + if (d < nearest) nearest = d; + } + if (nearest < minCorridor) minCorridor = nearest; + if (nearest <= 450) shared++; + } + overlaps.push({ + trackAId: ta.id, + trackBId: tb.id, + sharedPoints: shared, + minCorridorMeters: Number.isFinite(minCorridor) ? Math.round(minCorridor) : 0, + powerDelta: (tb.stats.avgPower || 0) - (ta.stats.avgPower || 0), + speedDelta: Number(((tb.stats.avgSpeed || 0) - (ta.stats.avgSpeed || 0)).toFixed(2)), + }); + } + } + return overlaps; + } + + const STANDARD_CURVE_DURATIONS = [ + 1, 2, 5, 10, 20, 30, 60, 120, 300, 600, 1200, 1800, 3600, 7200, 10800, 14400, 18000, 21600, + ]; + + function calculateSlidingMax(data: number[], durations: number[]) { + const len = data.length; + if (len === 0) return null; + const prefix = new Float64Array(len + 1); + for (let i = 0; i < len; i++) { + prefix[i + 1] = prefix[i] + data[i]; + } + if (prefix[len] === 0) return null; + + const curve: Record = {}; + let hasAny = false; + + for (let di = 0; di < durations.length; di++) { + const d = durations[di]; + if (d > len) continue; + let maxSum = 0; + let bestIdx = 0; + const limit = len - d; + + for (let j = 0; j <= limit; j++) { + const currentSum = prefix[j + d] - prefix[j]; + if (currentSum > maxSum) { + maxSum = currentSum; + bestIdx = j; + } + } + curve[d] = { val: Math.round(maxSum / d), idx: bestIdx }; + hasAny = true; + } + + return hasAny ? curve : null; + } + + function calculatePowerCurve(pts: TrackPoint[]) { + const len = pts.length; + const powerData = new Array(len); + let nonZero = false; + for (let i = 0; i < len; i++) { + const v = pts[i].power || 0; + powerData[i] = v; + if (v !== 0) nonZero = true; + } + if (!nonZero) return null; + const curve = calculateSlidingMax(powerData, STANDARD_CURVE_DURATIONS); + if (!curve) return null; + + const result: Record = {}; + for (const d in curve) result[d] = { power: curve[d].val, idx: curve[d].idx }; + + return result; + } + + function calculateHRCurve(pts: TrackPoint[]) { + const len = pts.length; + const hrData = new Array(len); + let nonZero = false; + for (let i = 0; i < len; i++) { + const v = pts[i].hr || 0; + hrData[i] = v; + if (v !== 0) nonZero = true; + } + if (!nonZero) return null; + const curve = calculateSlidingMax(hrData, STANDARD_CURVE_DURATIONS); + if (!curve) return null; + + const result: Record = {}; + for (const d in curve) result[d] = { hr: curve[d].val, idx: curve[d].idx }; + + return result; + } + + function calculatePowerZones(pts: TrackPoint[]) { + const zones = [0, 0, 0, 0, 0, 0, 0]; + const thresholds = [0.55, 0.75, 0.9, 1.05, 1.2, 1.5].map((t) => t * ftp); + + for (let i = 1; i < pts.length; i++) { + const p = pts[i]; + const prev = pts[i - 1]; + if (p.power == null || p.time == null || prev.time == null) continue; + + const dt = (p.time - prev.time) / 1000; + if (dt <= 0 || dt > 10) continue; + + const pw = p.power; + if (pw < thresholds[0]) zones[0] += dt; + else if (pw < thresholds[1]) zones[1] += dt; + else if (pw < thresholds[2]) zones[2] += dt; + else if (pw < thresholds[3]) zones[3] += dt; + else if (pw < thresholds[4]) zones[4] += dt; + else if (pw < thresholds[5]) zones[5] += dt; + else zones[6] += dt; + } + if (zones.every((z) => z === 0)) return null; + return zones; + } + + function setFTP(val: number) { + ftp = val; + } + + function getFTP() { + return ftp; + } + + function calculateHRZones(pts: TrackPoint[]) { + const zones = [0, 0, 0, 0, 0]; + for (let i = 1; i < pts.length; i++) { + const p = pts[i]; + const prev = pts[i - 1]; + if (p.hr == null || p.time == null || prev.time == null) continue; + + const dt = (p.time - prev.time) / 1000; + if (dt <= 0 || dt > 10) continue; // Skip gaps + + const hr = p.hr; + if (hr < hrZoneThresholds[0]) zones[0] += dt; + else if (hr < hrZoneThresholds[1]) zones[1] += dt; + else if (hr < hrZoneThresholds[2]) zones[2] += dt; + else if (hr < hrZoneThresholds[3]) zones[3] += dt; + else zones[4] += dt; + } + if (zones.every((z) => z === 0)) return null; + return zones; + } + + function setHRZones(thresholds: number[]) { + hrZoneThresholds = thresholds; + } + + function getHRZones() { + return [...hrZoneThresholds]; + } + + function extractXmlTag(block: string, openTag: string, closeTag: string): string | null { + const i = block.indexOf(openTag); + if (i === -1) return null; + const start = i + openTag.length; + const j = block.indexOf(closeTag, start); + if (j === -1) return null; + const val = block.slice(start, j).trim(); + return val !== '' ? val : null; + } + + function extractXmlAttr(tagOpen: string, attrPrefix: string): string | null { + const i = tagOpen.indexOf(attrPrefix); + if (i === -1) return null; + const start = i + attrPrefix.length; + const j = tagOpen.indexOf('"', start); + if (j === -1) return null; + return tagOpen.slice(start, j); + } + + let cachedDateKey = -1; + let cachedDayMs = 0; + + function parseIntSlice(text: string, start: number, end: number): number | null { + while (start < end && text.charCodeAt(start) <= 32) start++; + while (end > start && text.charCodeAt(end - 1) <= 32) end--; + if (start >= end) return null; + let neg = false; + const c0 = text.charCodeAt(start); + if (c0 === 45) { + neg = true; + start++; + } else if (c0 === 43) { + start++; + } + let val = 0; + let digits = 0; + for (let i = start; i < end; i++) { + const c = text.charCodeAt(i); + if (c >= 48 && c <= 57) { + val = val * 10 + (c - 48); + digits++; + } else { + break; + } + } + if (digits === 0) return null; + return neg ? -val : val; + } + + function parseFloatSlice(text: string, start: number, end: number): number | null { + while (start < end && text.charCodeAt(start) <= 32) start++; + while (end > start && text.charCodeAt(end - 1) <= 32) end--; + if (start >= end) return null; + let neg = false; + const c0 = text.charCodeAt(start); + if (c0 === 45) { + neg = true; + start++; + } else if (c0 === 43) { + start++; + } + let intPart = 0; + let fracPart = 0; + let fracDiv = 1; + let inFrac = false; + let digits = 0; + for (let i = start; i < end; i++) { + const c = text.charCodeAt(i); + if (c >= 48 && c <= 57) { + digits++; + if (!inFrac) intPart = intPart * 10 + (c - 48); + else { + fracPart = fracPart * 10 + (c - 48); + fracDiv *= 10; + } + } else if (c === 46 && !inFrac) { + inFrac = true; + } else { + const n = parseFloat(text.slice(start - (neg ? 1 : 0), end)); + return Number.isFinite(n) ? n : null; + } + } + if (digits === 0) return null; + const res = intPart + fracPart / fracDiv; + return neg ? -res : res; + } + + function fastParseTimeSlice(text: string, start: number, end: number): number | null { + while (start < end && text.charCodeAt(start) <= 32) start++; + while (end > start && text.charCodeAt(end - 1) <= 32) end--; + const len = end - start; + if (len >= 20 && text.charCodeAt(start + 10) === 84 && text.charCodeAt(end - 1) === 90) { + const y = + (text.charCodeAt(start) - 48) * 1000 + + (text.charCodeAt(start + 1) - 48) * 100 + + (text.charCodeAt(start + 2) - 48) * 10 + + (text.charCodeAt(start + 3) - 48); + const m = (text.charCodeAt(start + 5) - 48) * 10 + (text.charCodeAt(start + 6) - 48); + const d = (text.charCodeAt(start + 8) - 48) * 10 + (text.charCodeAt(start + 9) - 48); + const key = (y << 9) | (m << 5) | d; + if (key !== cachedDateKey) { + cachedDateKey = key; + cachedDayMs = Date.UTC(y, m - 1, d); + } + const hh = (text.charCodeAt(start + 11) - 48) * 10 + (text.charCodeAt(start + 12) - 48); + const mm = (text.charCodeAt(start + 14) - 48) * 10 + (text.charCodeAt(start + 15) - 48); + const ss = (text.charCodeAt(start + 17) - 48) * 10 + (text.charCodeAt(start + 18) - 48); + let ms = 0; + if (text.charCodeAt(start + 19) === 46 && len >= 24) { + ms = + (text.charCodeAt(start + 20) - 48) * 100 + + (text.charCodeAt(start + 21) - 48) * 10 + + (text.charCodeAt(start + 22) - 48); + } + return cachedDayMs + hh * 3600000 + mm * 60000 + ss * 1000 + ms; + } + const t = new Date(text.slice(start, end)).getTime(); + return Number.isFinite(t) ? t : null; + } + + function extractAttrFloat(text: string, start: number, end: number, attrPrefix: string): number { + const i = text.indexOf(attrPrefix, start); + if (i === -1 || i >= end) return 0; + const vStart = i + attrPrefix.length; + const j = text.indexOf('"', vStart); + if (j === -1 || j >= end) return 0; + return parseFloatSlice(text, vStart, j) ?? 0; + } + + // ── GPX parser ──────────────────────────────────────────────── + function parseGPX(buf: ArrayBuffer, predecodedText?: string): any { + const text: string = predecodedText ?? new TextDecoder().decode(buf); + const firstTrkpt = text.indexOf(''); + const name = + (trkIdx !== -1 ? extractXmlTag(header.slice(trkIdx), '', '') : null) || + extractXmlTag(header, '', '') || + 'Unnamed Track'; + + const gpxOpenEnd = header.indexOf('>', header.indexOf(' + + + Period + ${isDistance ? 'Distance' : 'Elevation'} + ${isDistance ? 'Distance/Week' : 'Elevation/Week'} + + + + + Current Year + ${format(accumulated.currentYear)} ${unit} + ${format(accumulated.currentYearAvg)} ${unit} + + + Past Year + ${format(accumulated.pastYear)} ${unit} + ${format(accumulated.pastYearAvg)} ${unit} + + + Past 6 Months + ${format(accumulated.past6m)} ${unit} + ${format(accumulated.past6mAvg)} ${unit} + + + Past 1 Month + ${format(accumulated.past1m)} ${unit} + ${format(accumulated.past1mAvg)} ${unit} + + + Total + ${format(accumulated.total)} ${unit} + ${format(accumulated.totalAvg)} ${unit} + + + + `; +} + +export interface MultiMetricAccumulatedSummary { + distance: AccumulatedData; + elevation: AccumulatedData; + durationHours: AccumulatedData; + tss: AccumulatedData; + workKj: AccumulatedData; + climbCount: AccumulatedData; +} + +export function computeMultiMetricAccumulated( + allTracks: TrackData[], +): MultiMetricAccumulatedSummary { + const distance = computeAccumulatedMetric(allTracks, (t) => t.stats.totalDist); + const elevation = computeAccumulatedMetric(allTracks, (t) => t.stats.elevGain); + const durationHours = computeAccumulatedMetric(allTracks, (t) => + t.stats.duration ? t.stats.duration / 3600000 : 0, + ); + const tss = computeAccumulatedMetric(allTracks, (t) => { + const durSec = (t.stats.duration || 0) / 1000; + const avgPwr = t.stats.avgPower || 150; + const ftp = 200; + const intensityFactor = avgPwr / ftp; + return ((durSec * avgPwr * intensityFactor) / (ftp * 3600)) * 100; + }); + const workKj = computeAccumulatedMetric(allTracks, (t) => { + const durSec = (t.stats.duration || 0) / 1000; + const avgPwr = t.stats.avgPower || 0; + return (durSec * avgPwr) / 1000; + }); + const climbCount = computeAccumulatedMetric(allTracks, (t) => { + const secs = t.stats.sections || []; + return secs.filter((s) => s.kind === 'climb').length; + }); + + return { + distance, + elevation, + durationHours, + tss, + workKj, + climbCount, + }; +} + +export function renderAccumulatedProgressSummary(allTracks: TrackData[]): string { + const summary = computeMultiMetricAccumulated(allTracks); + const fmt = (v: number, digits = 1) => + v.toLocaleString('en-US', { + minimumFractionDigits: digits, + maximumFractionDigits: digits, + }); + + const rows: { + label: string; + unit: string; + data: AccumulatedData; + scale: number; + digits: number; + }[] = [ + { label: 'Distance', unit: 'km', data: summary.distance, scale: 0.001, digits: 1 }, + { label: 'Elevation Gain', unit: 'm', data: summary.elevation, scale: 1, digits: 0 }, + { label: 'Moving Duration', unit: 'h', data: summary.durationHours, scale: 1, digits: 1 }, + { label: 'Training Stress (TSS)', unit: 'TSS', data: summary.tss, scale: 1, digits: 0 }, + { label: 'Mechanical Work', unit: 'kJ', data: summary.workKj, scale: 1, digits: 0 }, + { label: 'Categorized Climbs', unit: 'climbs', data: summary.climbCount, scale: 1, digits: 0 }, + ]; + + return ` +
+
+
+ monitoring + Accumulated Longitudinal Progress Summary +
+
+ + + + + + + + + + + + + + ${rows + .map( + (r) => ` + + + + + + + + + `, + ) + .join('')} + +
MetricPast 1MPast 6MPast YearCurrent YearTotalWeekly Avg
${r.label}${fmt(r.data.past1m * r.scale, r.digits)} ${r.unit}${fmt(r.data.past6m * r.scale, r.digits)} ${r.unit}${fmt(r.data.pastYear * r.scale, r.digits)} ${r.unit}${fmt(r.data.currentYear * r.scale, r.digits)} ${r.unit}${fmt(r.data.total * r.scale, r.digits)} ${r.unit}${fmt(r.data.totalAvg * r.scale, r.digits)} ${r.unit}/wk
+
+ `; +} diff --git a/suites-experimental/tools-gps-tracks/src/tabs/combined.ts b/suites-experimental/tools-gps-tracks/src/tabs/combined.ts new file mode 100644 index 000000000..726623fd5 --- /dev/null +++ b/suites-experimental/tools-gps-tracks/src/tabs/combined.ts @@ -0,0 +1,1013 @@ +// Copyright (c) 2026, Camillo Bruni. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +'use strict'; + +import uPlot from 'uplot'; +import { TrackData, TrackPoint } from '../parsers'; +import { ChartView } from '../charts'; +import { hexToRgba, fmtSecs } from '../utils'; + +let currentTracks: TrackData[] = []; +let currentTrack: TrackData | null = null; +let plot: uPlot | null = null; +let xMetric = 'power'; +let yMetric = 'hr'; +let currentSliderIdx: number | null = null; +let currentChronoData: [number, number, number, number, number, number, number][] = []; +let currentSortedData: [number, number, number, number, number, number, number][] = []; +let playIntervalId: number | null = null; +let isPolar = false; + +function updateSliderLabel() { + const sliderVal = document.getElementById('combined-time-val'); + if (sliderVal && currentSliderIdx !== null && currentChronoData[currentSliderIdx]) { + const timeSecs = currentChronoData[currentSliderIdx][2]; + sliderVal.textContent = `${fmtSecs(timeSecs)}`; + } +} + +export function initCombined() { + // Initialize UI event listeners if needed +} + +export function resetCombined() { + if (playIntervalId) { + clearInterval(playIntervalId); + playIntervalId = null; + } + if (plot) { + plot.destroy(); + plot = null; + } + currentTracks = []; + currentTrack = null; + xMetric = 'power'; + yMetric = 'hr'; + currentSliderIdx = null; + currentChronoData = []; + currentSortedData = []; + isPolar = false; + const tooltip = document.getElementById('combined-tooltip'); + if (tooltip) tooltip.remove(); + const container = document.getElementById('combined-view'); + if (container) { + container.innerHTML = ` +
+ no_sim +
Select a track for combined analysis
+
+ `; + } +} + +const COMBINED_CAP = 16384; +const combinedDensityGrid = new Float64Array(32 * 32); + +export interface CombinedCorrelationRow { + pairLabel: string; + rVal: number; + slope: number; + samples: number; +} + +export interface CombinedSectionComparisonRow { + sectionLabel: string; + t1Power: number; + t2Power: number; + t1Hr: number; + t2Hr: number; + t1SpeedKmh: number; + t2SpeedKmh: number; + cohortAvgPower: number; +} + +function computeMultiTrackCorrelationAndSections( + currentTracks: TrackData[], + allTracks: TrackData[], +): { + aggPts: number; + corrSum: number; + correlations: CombinedCorrelationRow[]; + sectionRows: CombinedSectionComparisonRow[]; +} { + const matrixTracks = allTracks.length >= 2 ? allTracks : currentTracks; + const numTrk = Math.min(matrixTracks.length, 8); + combinedDensityGrid.fill(0); + + const pairs: { + label: string; + getX: (p: TrackPoint) => number | null | undefined; + getY: (p: TrackPoint) => number | null | undefined; + }[] = [ + { label: 'Power vs Heart Rate', getX: (p) => p.power, getY: (p) => p.hr }, + { + label: 'Speed vs Gradient', + getX: (p) => (p.speed != null ? p.speed * 3.6 : undefined), + getY: (p) => p.gradient, + }, + { label: 'Cadence vs Power', getX: (p) => p.cad, getY: (p) => p.power }, + { label: 'Elevation vs VAM', getX: (p) => p.ele, getY: (p) => p.verticalSpeed }, + { label: 'Temperature vs HR', getX: (p) => p.temp, getY: (p) => p.hr }, + ]; + + let aggPts = 0; + let corrSum = 0; + const correlations: CombinedCorrelationRow[] = []; + + for (let pi = 0; pi < pairs.length; pi++) { + const pair = pairs[pi]; + let sumX = 0; + let sumY = 0; + let sumXX = 0; + let sumYY = 0; + let sumXY = 0; + let count = 0; + + for (let t = 0; t < numTrk; t++) { + const pts = matrixTracks[t].points || []; + const n = Math.min(pts.length, COMBINED_CAP); + for (let k = 0; k < n; k++) { + const p = pts[k]; + const x = pair.getX(p); + const y = pair.getY(p); + if (x != null && y != null) { + sumX += x; + sumY += y; + sumXX += x * x; + sumYY += y * y; + sumXY += x * y; + count++; + } + if (pi === 0) { + const pw = p.power || 0; + const h = p.hr || 0; + const cd = p.cad || 0; + const bx = Math.min(31, Math.max(0, (pw * 0.05) | 0)); + const by = Math.min(31, Math.max(0, ((h - 60) * 0.2) | 0)); + combinedDensityGrid[(bx << 5) | by] += 1 + cd * 0.01; + } + } + } + + let rVal = 0; + let slope = 0; + if (count > 2) { + const meanX = sumX / count; + const meanY = sumY / count; + const varX = Math.max(0, sumXX / count - meanX * meanX); + const varY = Math.max(0, sumYY / count - meanY * meanY); + const covXY = sumXY / count - meanX * meanY; + const denom = Math.sqrt(varX * varY); + rVal = denom > 1e-9 ? covXY / denom : 0; + slope = varX > 1e-9 ? covXY / varX : 0; + } + aggPts += count; + corrSum += Math.abs(rVal) * 100 + Math.abs(slope); + correlations.push({ + pairLabel: pair.label, + rVal, + slope, + samples: count, + }); + } + + for (let g = 0; g < 1024; g++) { + corrSum += combinedDensityGrid[g] * 1e-3; + } + + const sectionLabels = ['Q1 (0–25%)', 'Q2 (25–50%)', 'Q3 (50–75%)', 'Q4 (75–100%)']; + const sectionRows: CombinedSectionComparisonRow[] = []; + const t1 = currentTracks[0]; + const t2 = currentTracks[1] || currentTracks[0]; + + for (let q = 0; q < 4; q++) { + const fStart = q * 0.25; + const fEnd = (q + 1) * 0.25; + + const sliceStats = (trk: TrackData) => { + const pts = trk.points || []; + const sIdx = Math.floor(pts.length * fStart); + const eIdx = Math.max(sIdx + 1, Math.floor(pts.length * fEnd)); + let pSum = 0, + hSum = 0, + spSum = 0, + cnt = 0; + for (let i = sIdx; i < eIdx && i < pts.length; i++) { + const pt = pts[i]; + pSum += pt.power || 0; + hSum += pt.hr || 0; + spSum += (pt.speed || 0) * 3.6; + cnt++; + } + return { + power: cnt > 0 ? Math.round(pSum / cnt) : 0, + hr: cnt > 0 ? Math.round(hSum / cnt) : 0, + speedKmh: cnt > 0 ? spSum / cnt : 0, + }; + }; + + const st1 = sliceStats(t1); + const st2 = sliceStats(t2); + let cohortPwrSum = 0; + for (let t = 0; t < numTrk; t++) { + cohortPwrSum += sliceStats(matrixTracks[t]).power; + } + sectionRows.push({ + sectionLabel: sectionLabels[q], + t1Power: st1.power, + t2Power: st2.power, + t1Hr: st1.hr, + t2Hr: st2.hr, + t1SpeedKmh: st1.speedKmh, + t2SpeedKmh: st2.speedKmh, + cohortAvgPower: numTrk > 0 ? Math.round(cohortPwrSum / numTrk) : 0, + }); + } + + return { aggPts, corrSum, correlations, sectionRows }; +} + +export function setCombinedMetrics(xKey: string, yKey: string) { + xMetric = xKey; + yMetric = yKey; + if (currentTracks.length >= 2) { + renderCombined(currentTracks); + } else if (currentTrack) { + renderCombined(currentTrack); + } +} + +export function setCombinedPolar(polar: boolean) { + if (isPolar !== polar) { + isPolar = polar; + if (currentTracks.length >= 2) { + renderCombined(currentTracks); + } else if (currentTrack) { + renderCombined(currentTrack); + } + } +} + +export function setCombinedSliderProgress(fraction: number) { + const clamped = Math.max(0, Math.min(1, fraction)); + const maxLen = + currentChronoData.length > 0 + ? currentChronoData.length - 1 + : currentTrack + ? Math.max(0, currentTrack.points.length - 1) + : 0; + currentSliderIdx = Math.round(clamped * maxLen); + const slider = document.getElementById('combined-time-slider') as HTMLInputElement | null; + if (slider) { + slider.max = `${maxLen}`; + slider.value = `${currentSliderIdx}`; + } + updateSliderLabel(); + if (plot) plot.redraw(); +} + +export function renderCombined( + trackOrTracks: TrackData | TrackData[] | null, + allTracks: TrackData[] = [], +) { + if (Array.isArray(trackOrTracks)) { + currentTracks = trackOrTracks; + currentTrack = trackOrTracks[0] || null; + } else if (trackOrTracks) { + currentTracks = [trackOrTracks]; + currentTrack = trackOrTracks; + } else { + currentTracks = []; + currentTrack = null; + } + const container = document.getElementById('combined-view'); + if (!container) return; + + const track = currentTrack; + if (!track) { + container.innerHTML = ` +
+ no_sim +
Select a track for combined analysis
+
+ `; + return; + } + + // Find available metrics in the track + const metrics = ChartView.METRICS; + const availableMetrics: string[] = []; + for (const key in metrics) { + const def = metrics[key]; + if (track.points.some((p) => (p as any)[def.field] != null)) availableMetrics.push(key); + } + + // Default to first two available if defaults are not available + if (!availableMetrics.includes(xMetric) && availableMetrics.length > 0) + xMetric = availableMetrics[0]; + + if (!availableMetrics.includes(yMetric) && availableMetrics.length > 1) + yMetric = availableMetrics[1]; + else if (!availableMetrics.includes(yMetric) && availableMetrics.length > 0) + yMetric = availableMetrics[0]; + + const xDef = metrics[xMetric]; + const yDef = metrics[yMetric]; + + // Prevent scrolling and runaway size + container.style.overflow = 'hidden'; + + let summaryHtml = ''; + if (currentTracks.length >= 2) { + const { aggPts, corrSum, correlations, sectionRows } = computeMultiTrackCorrelationAndSections( + currentTracks, + allTracks, + ); + const t1 = currentTracks[0]; + const t2 = currentTracks[1]; + const s1 = t1.stats; + const s2 = t2.stats; + const distDiff = ((s2.totalDist - s1.totalDist) / 1000).toFixed(2); + const pwrDiff = ((s2.avgPower || 0) - (s1.avgPower || 0)).toFixed(0); + const hrDiff = ((s2.avgHR || 0) - (s1.avgHR || 0)).toFixed(0); + const eleDiff = (s2.elevGain - s1.elevGain).toFixed(0); + const shiftsDiff = (s2.shifts || 0) - (s1.shifts || 0); + + const totalDist = ((s1.totalDist + s2.totalDist) / 1000).toFixed(2); + const totalElev = (s1.elevGain + s2.elevGain).toFixed(0); + const totalDur = ((s1.duration || 0) + (s2.duration || 0)) / 1000; + const avgPwr = Math.round(((s1.avgPower || 0) + (s2.avgPower || 0)) / 2); + const maxPwr = Math.max(s1.maxPower || 0, s2.maxPower || 0); + const avgHr = Math.round(((s1.avgHR || 0) + (s2.avgHR || 0)) / 2); + const maxHr = Math.max(s1.maxHR || 0, s2.maxHR || 0); + const tempDiff = ((s2.avgTemp ?? 0) - (s1.avgTemp ?? 0)).toFixed(1); + const avgTemp = (((s1.avgTemp ?? 0) + (s2.avgTemp ?? 0)) / 2).toFixed(1); + const maxTemp = Math.max(s1.maxTemp ?? 0, s2.maxTemp ?? 0).toFixed(1); + const totalShifts = (s1.shifts || 0) + (s2.shifts || 0); + + summaryHtml = ` +
+
Aggregate Track Comparison & Section Analysis (${currentTracks.length} Tracks)
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ${sectionRows + .map( + (sr) => ` + + + + + + + `, + ) + .join('')} + +
Metric${t1.displayName || t1.name} (T1)${t2.displayName || t2.name} (T2)Delta (T2 - T1)Aggregate Summary
Distance${(s1.totalDist / 1000).toFixed(2)} km${(s2.totalDist / 1000).toFixed(2)} km${Number(distDiff) >= 0 ? '+' : ''}${distDiff} km${totalDist} km (Total)
Elevation Gain${s1.elevGain.toFixed(0)} m${s2.elevGain.toFixed(0)} m${Number(eleDiff) >= 0 ? '+' : ''}${eleDiff} m${totalElev} m (Total)
Duration${s1.duration ? fmtSecs(s1.duration / 1000) : '--'}${s2.duration ? fmtSecs(s2.duration / 1000) : '--'}--${fmtSecs(totalDur)} (Total)
Avg / Max Power${s1.avgPower || 0} W / ${s1.maxPower || 0} W${s2.avgPower || 0} W / ${s2.maxPower || 0} W${Number(pwrDiff) >= 0 ? '+' : ''}${pwrDiff} W (Avg)${avgPwr} W (Avg) / ${maxPwr} W (Max)
Avg / Max HR${s1.avgHR || 0} bpm / ${s1.maxHR || 0} bpm${s2.avgHR || 0} bpm / ${s2.maxHR || 0} bpm${Number(hrDiff) >= 0 ? '+' : ''}${hrDiff} bpm (Avg)${avgHr} bpm (Avg) / ${maxHr} bpm (Max)
Avg / Max Temp${s1.avgTemp != null ? s1.avgTemp.toFixed(1) : '--'} °C / ${s1.maxTemp != null ? s1.maxTemp.toFixed(1) : '--'} °C${s2.avgTemp != null ? s2.avgTemp.toFixed(1) : '--'} °C / ${s2.maxTemp != null ? s2.maxTemp.toFixed(1) : '--'} °C${Number(tempDiff) >= 0 ? '+' : ''}${tempDiff} °C (Avg)${avgTemp} °C (Avg) / ${maxTemp} °C (Max)
Gear Shifts${s1.shifts || 0}${s2.shifts || 0}${shiftsDiff >= 0 ? '+' : ''}${shiftsDiff}${totalShifts} (Total)
${sr.sectionLabel} Split${sr.t1Power}W · ${sr.t1Hr}bpm · ${sr.t1SpeedKmh.toFixed(1)}km/h${sr.t2Power}W · ${sr.t2Hr}bpm · ${sr.t2SpeedKmh.toFixed(1)}km/h${sr.t2Power - sr.t1Power >= 0 ? '+' : ''}${sr.t2Power - sr.t1Power}WCohort Avg: ${sr.cohortAvgPower}W
+
+ ${correlations + .map( + (c) => + `${c.pairLabel}: r=${c.rVal.toFixed(2)}, β=${c.slope.toFixed(2)} (${c.samples})`, + ) + .join('')} +
+
+ `; + } + + // Render UI with selectors and plot container + container.innerHTML = `${summaryHtml} +
+
+ arrow_forward + X Axis: + +
+
+ arrow_upward + Y Axis: + +
+
+ schedule + Time: + + 0:00 + +
+
+ + +
+
+
+ `; + + document.getElementById('btn-combined-x')?.addEventListener('click', (e) => { + e.stopPropagation(); + showMenu(e.currentTarget as HTMLElement, 'x'); + }); + + document.getElementById('btn-combined-y')?.addEventListener('click', (e) => { + e.stopPropagation(); + showMenu(e.currentTarget as HTMLElement, 'y'); + }); + + const slider = document.getElementById('combined-time-slider') as HTMLInputElement; + const sliderVal = document.getElementById('combined-time-val'); + const playBtn = document.getElementById('btn-combined-play'); + + slider?.addEventListener('input', () => { + currentSliderIdx = parseInt(slider.value, 10); + updateSliderLabel(); + if (plot) plot.redraw(); + }); + + function startPlaying() { + if (playBtn) playBtn.innerHTML = 'pause'; + + playIntervalId = window.setInterval(() => { + if (currentSliderIdx !== null && currentSliderIdx < currentChronoData.length - 1) { + currentSliderIdx = Math.min(currentSliderIdx + 5, currentChronoData.length - 1); + slider.value = `${currentSliderIdx}`; + updateSliderLabel(); + if (plot) plot.redraw(); + } else { + stopPlaying(); + } + }, 20); // 50 fps + } + + function stopPlaying() { + if (playIntervalId) { + clearInterval(playIntervalId); + playIntervalId = null; + } + if (playBtn) playBtn.innerHTML = 'play_arrow'; + } + + playBtn?.addEventListener('click', () => { + if (playIntervalId) { + stopPlaying(); + } else { + // If at the end, loop back to start + if (currentSliderIdx !== null && currentSliderIdx >= currentChronoData.length - 1) { + currentSliderIdx = 0; + slider.value = '0'; + updateSliderLabel(); + if (plot) plot.redraw(); + } + startPlaying(); + } + }); + + document.getElementById('btn-combined-cart')?.addEventListener('click', () => { + if (isPolar) { + isPolar = false; + renderCombined(currentTrack); + } + }); + + document.getElementById('btn-combined-polar')?.addEventListener('click', () => { + if (!isPolar) { + isPolar = true; + renderCombined(currentTrack); + } + }); + + updatePlot(); +} + +export function resizeCombined() { + const container = document.getElementById('tab-combined'); + const plotContainer = document.getElementById('combined-plot-container'); + if (plot && plotContainer && plotContainer.clientWidth > 0) { + const w = plotContainer.clientWidth; + const h = plotContainer.clientHeight; + + if (w > 0 && h > 0) { + if (isPolar) { + const size = Math.min(w, h); + plot.setSize({ width: size, height: size }); + } else { + plot.setSize({ width: w, height: h }); + } + } + } +} + +function showMenu(anchorEl: HTMLElement, axis: 'x' | 'y') { + document.querySelectorAll('.metric-menu-popup').forEach((el) => el.remove()); + + const popup = document.createElement('div'); + popup.className = 'metric-menu-popup'; + + const metrics = ChartView.METRICS; + const track = currentTrack; + if (!track) return; + + const availableMetrics: string[] = []; + for (const key in metrics) { + const def = metrics[key]; + if (track.points.some((p) => (p as any)[def.field] != null)) availableMetrics.push(key); + } + + availableMetrics.forEach((key) => { + const def = metrics[key]; + const item = document.createElement('div'); + const currentVal = axis === 'x' ? xMetric : yMetric; + item.className = `menu-item ${currentVal === key ? 'active' : ''}`; + item.innerHTML = ` + ${def.icon} + ${def.label} + ${currentVal === key ? 'check' : ''} + `; + item.addEventListener('click', () => { + if (axis === 'x') xMetric = key; + else yMetric = key; + + popup.remove(); + renderCombined(currentTrack); // Re-render to update buttons and plot + }); + popup.appendChild(item); + }); + + const rect = anchorEl.getBoundingClientRect(); + popup.style.top = `${rect.bottom + 5}px`; + popup.style.left = `${rect.left}px`; + document.body.appendChild(popup); + + const close = (e: MouseEvent) => { + if (!popup.contains(e.target as Node) && !anchorEl.contains(e.target as Node)) { + popup.remove(); + document.removeEventListener('click', close); + } + }; + setTimeout(() => document.addEventListener('click', close), 0); +} + +function updatePlot() { + if (!currentTrack) return; + const container = document.getElementById('combined-plot-container'); + if (!container) return; + + const metrics = ChartView.METRICS; + const xDef = metrics[xMetric] || metrics.power; + const yDef = metrics[yMetric] || metrics.hr; + + if (currentTracks.length >= 2) { + let maxLen = 0; + for (let t = 0; t < currentTracks.length; t++) { + if (currentTracks[t].points.length > maxLen) maxLen = currentTracks[t].points.length; + } + const xData: number[] = new Array(maxLen); + for (let i = 0; i < maxLen; i++) xData[i] = i; + + const seriesList: uPlot.Series[] = [{}]; + const dataArrays: (number | null)[][] = [xData]; + + for (let t = 0; t < currentTracks.length; t++) { + const trk = currentTracks[t]; + const pts = trk.points; + const yArr: (number | null)[] = new Array(maxLen); + for (let i = 0; i < maxLen; i++) { + const p = pts[i]; + yArr[i] = p && (p as any)[yDef.field] != null ? (p as any)[yDef.field] : null; + } + dataArrays.push(yArr); + seriesList.push({ + label: `${trk.displayName || trk.name} (${yDef.label})`, + stroke: trk.color || '#3b82f6', + width: 2, + points: { show: false }, + }); + } + + if (plot) { + plot.destroy(); + plot = null; + } + if (container.querySelector('.empty-text')) container.innerHTML = ''; + + const width = container.clientWidth > 0 ? container.clientWidth : 800; + const height = container.clientHeight > 0 ? container.clientHeight : 350; + + const opts: uPlot.Options = { + width, + height, + padding: [10, 15, 0, 0], + scales: { x: { time: false }, y: { auto: true } }, + series: seriesList, + axes: [ + { + side: 2, + label: 'Elapsed Duration (s)', + stroke: '#555564', + grid: { stroke: '#2e2e34', width: 1 }, + font: '10px system-ui, sans-serif', + size: 30, + }, + { + side: 3, + label: `${yDef.label} (${yDef.unit})`, + stroke: '#555564', + grid: { stroke: '#2e2e34', width: 1 }, + font: '10px system-ui, sans-serif', + size: 40, + }, + ], + legend: { show: true }, + }; + + plot = new uPlot(opts, dataArrays as uPlot.AlignedData, container as HTMLElement); + resizeCombined(); + return; + } + + const pts = currentTrack.points; + const t0 = pts[0].time || 0; + const chronoData = pts + .map((p, idx) => [ + (p as any)[xDef.field], + (p as any)[yDef.field], + (p.time! - t0) / 1000, + (p.dist || 0) / 1000, + idx, + ]) + .filter(([x, y, t, d]) => x != null && y != null) as [number, number, number, number, number][]; + + if (chronoData.length === 0) { + container.innerHTML = + '
No data for selected metrics
'; + return; + } + + if (isPolar) { + const xVals = chronoData.map((d) => d[0]); + const yVals = chronoData.map((d) => d[1]); + const xMin = Math.min(...xVals); + const xMax = Math.max(...xVals); + const yMin = Math.min(...yVals); + const yMax = Math.max(...yVals); + + currentChronoData = chronoData.map((d) => { + const xVal = d[0]; + const yVal = d[1]; + const theta = ((xVal - xMin) / (xMax - xMin || 1)) * 2 * Math.PI; + const r = (yVal - yMin) / (yMax - yMin || 1); + const px = r * Math.cos(theta); + const py = r * Math.sin(theta); + return [px, py, d[2], d[3], d[4], xVal, yVal] as any; + }); + } else { + currentChronoData = chronoData.map((d) => [d[0], d[1], d[2], d[3], d[4], d[0], d[1]] as any); + } + + // Update slider + const slider = document.getElementById('combined-time-slider') as HTMLInputElement; + if (slider) { + slider.max = `${chronoData.length - 1}`; + if (currentSliderIdx === null || currentSliderIdx >= chronoData.length) { + currentSliderIdx = chronoData.length - 1; + slider.value = slider.max; + } + updateSliderLabel(); + } + + // Sort by X for uPlot (with original point index tie-breaker) + currentSortedData = [...currentChronoData].sort((a, b) => a[0] - b[0] || a[4] - b[4]); + + const xData = currentSortedData.map((d) => d[0]); + const yData = currentSortedData.map((d) => d[1]); + + const plotContainer = document.getElementById('combined-plot-container'); + let width = plotContainer && plotContainer.clientWidth > 0 ? plotContainer.clientWidth : 600; + let height = plotContainer && plotContainer.clientHeight > 0 ? plotContainer.clientHeight : 400; + + if (isPolar) { + const size = Math.min(width, height > 0 ? height : width); + width = size; + height = size; + + if (plotContainer) { + plotContainer.style.display = 'flex'; + plotContainer.style.alignItems = 'center'; + plotContainer.style.justifyContent = 'center'; + } + } else if (plotContainer) { + plotContainer.style.display = ''; + plotContainer.style.alignItems = ''; + plotContainer.style.justifyContent = ''; + } + + const opts: uPlot.Options = { + width: width, + height: height, + padding: [10, 15, 0, 0], + scales: { + x: { time: false, auto: true }, + y: { auto: true }, + }, + series: [ + {}, + { + label: yDef.label, + width: 0, + points: { show: true, size: 3, fill: hexToRgba(yDef.color, 0.3) }, + }, + ], + axes: [ + { + side: 2, // bottom + show: !isPolar, + label: `${xDef.label} (${xDef.unit})`, + stroke: '#555564', + grid: { stroke: '#2e2e34', width: 1 }, + font: '10px system-ui, sans-serif', + values: (u, vals) => vals.map((v) => (v != null ? xDef.fmtAxis(v) : '')), + size: 30, + }, + { + side: 3, // left + show: !isPolar, + label: `${yDef.label} (${yDef.unit})`, + stroke: '#555564', + grid: { stroke: '#2e2e34', width: 1 }, + font: '10px system-ui, sans-serif', + values: (u, vals) => vals.map((v) => (v != null ? yDef.fmtAxis(v) : '')), + size: 40, + }, + ], + cursor: { + drag: { x: false, y: false }, + dataIdx: (u, seriesIdx, dataIdx) => { + const cursorLeft = u.cursor.left; + const cursorTop = u.cursor.top; + if (cursorLeft != null && cursorTop != null && cursorLeft >= 0 && cursorTop >= 0) { + let minD2 = Infinity; + let closestIdx = -1; + + for (let i = 0; i < currentSortedData.length; i++) { + const [xVal, yVal] = currentSortedData[i]; + const cx = u.valToPos(xVal, 'x'); + const cy = u.valToPos(yVal, 'y'); + + const dx = cx - cursorLeft; + const dy = cy - cursorTop; + const d2 = dx * dx + dy * dy; + + if (d2 < minD2) { + minD2 = d2; + closestIdx = i; + } + } + + // Only snap if within a reasonable distance (e.g. 50 pixels) + if (closestIdx >= 0 && minD2 < 2500) return closestIdx; + } + return dataIdx; + }, + points: { + size: 7, + stroke: '#fff', + width: 2, + fill: yDef.color, + }, + }, + legend: { show: false }, + hooks: { + draw: [ + (u: uPlot) => { + const ctx = u.ctx; + const dpr = window.devicePixelRatio || 1; + + // Draw faint lines connecting points in chronological order + ctx.save(); + ctx.beginPath(); + ctx.strokeStyle = 'rgba(255, 255, 255, 0.15)'; + ctx.lineWidth = 1 * dpr; + + let started = false; + for (let i = 0; i < currentChronoData.length; i++) { + const [xVal, yVal] = currentChronoData[i]; + const cx = u.valToPos(xVal, 'x', true); + const cy = u.valToPos(yVal, 'y', true); + + if (started) { + ctx.lineTo(cx, cy); + } else { + ctx.moveTo(cx, cy); + started = true; + } + } + ctx.stroke(); + ctx.restore(); + + // Draw trail if slider is active + if (currentSliderIdx !== null) { + ctx.save(); + const windowSize = 100; // Longer trail + const startIdx = Math.max(0, currentSliderIdx - windowSize); + const endIdx = currentSliderIdx; + + // 1. Draw increasingly thicker connection line for the trail + for (let i = startIdx; i < endIdx; i++) { + const [xVal1, yVal1] = currentChronoData[i]; + const [xVal2, yVal2] = currentChronoData[i + 1]; + const cx1 = u.valToPos(xVal1, 'x', true); + const cy1 = u.valToPos(yVal1, 'y', true); + const cx2 = u.valToPos(xVal2, 'x', true); + const cy2 = u.valToPos(yVal2, 'y', true); + + const progress = (i - startIdx) / (endIdx - startIdx || 1); + const lineWidth = 1 + 5 * progress; // Grow from 1 to 6 + const alpha = 0.15 + 0.6 * progress; // Fade from 0.15 to 0.75 + + ctx.beginPath(); + ctx.moveTo(cx1, cy1); + ctx.lineTo(cx2, cy2); + ctx.strokeStyle = hexToRgba(yDef.color, alpha); + ctx.lineWidth = lineWidth * dpr; + ctx.stroke(); + } + + // 2. Draw dots on top + for (let i = startIdx; i <= endIdx; i++) { + const [xVal, yVal] = currentChronoData[i]; + const cx = u.valToPos(xVal, 'x', true); + const cy = u.valToPos(yVal, 'y', true); + + const progress = (i - startIdx) / (endIdx - startIdx || 1); + const size = 3 + 7 * progress; // Grow from 3 to 10 + const fillAlpha = 0.01 + 0.14 * progress; // Fade from 0.01 to 0.15 + + ctx.beginPath(); + ctx.arc(cx, cy, size * dpr, 0, Math.PI * 2); + ctx.fillStyle = hexToRgba(yDef.color, fillAlpha); + ctx.fill(); + + ctx.strokeStyle = yDef.color; + ctx.lineWidth = 1 * dpr; + ctx.stroke(); + } + ctx.restore(); + } + }, + ], + setCursor: [ + (u: uPlot) => { + const idx = u.cursor.idx; + + let tooltip = document.getElementById('combined-tooltip'); + if (!tooltip) { + tooltip = document.createElement('div'); + tooltip.id = 'combined-tooltip'; + tooltip.style.position = 'absolute'; + tooltip.style.background = 'rgba(14, 14, 16, 0.9)'; + tooltip.style.color = '#fff'; + tooltip.style.padding = '8px 12px'; + tooltip.style.borderRadius = '4px'; + tooltip.classList.add('font-m'); + tooltip.style.fontFamily = 'system-ui'; + tooltip.style.pointerEvents = 'none'; + tooltip.style.zIndex = '1000'; + tooltip.style.boxShadow = '0 4px 6px rgba(0,0,0,0.3)'; + tooltip.style.border = '1px solid #2e2e34'; + document.body.appendChild(tooltip); + } + + if (idx != null && currentSortedData[idx] && u.cursor.left! >= 0) { + const d = currentSortedData[idx]; + const timeSecs = d[2]; + const distKm = d[3]; + const origX = d[5]; + const origY = d[6]; + + tooltip.innerHTML = ` +
Point #${idx}
+
+ Time: + ${fmtSecs(timeSecs)} +
+
+ Distance: + ${distKm.toFixed(2)} km +
+
+ ${xDef.label}: + ${xDef.fmtAxis(origX)} ${xDef.unit} +
+
+ ${yDef.label}: + ${yDef.fmtAxis(origY)} ${yDef.unit} +
+ `; + tooltip.style.display = 'block'; + + const canvasRect = u.over.getBoundingClientRect(); + tooltip.style.left = `${canvasRect.left + window.scrollX + u.cursor.left! + 15}px`; + tooltip.style.top = `${canvasRect.top + window.scrollY + u.cursor.top! + 15}px`; + } else { + tooltip.style.display = 'none'; + } + }, + ], + }, + }; + + // Clear container before rendering (except when uPlot does it) + // uPlot handles its own canvas, but we need to clear any previous empty state message + if (container.querySelector('.empty-text')) container.innerHTML = ''; + + if (plot) plot.destroy(); + + plot = new uPlot(opts, [xData, yData], container); + + plot.over.addEventListener('click', () => { + if (plot) { + const idx = plot.cursor.idx; + if (idx != null && currentSortedData[idx]) { + const originalIdx = currentSortedData[idx][4]; + ChartView.setCursorAt(originalIdx); + } + } + }); + + // Ensure initial size is set synchronously + resizeCombined(); +} diff --git a/suites-experimental/tools-gps-tracks/src/tabs/courses.ts b/suites-experimental/tools-gps-tracks/src/tabs/courses.ts new file mode 100644 index 000000000..f227c8687 --- /dev/null +++ b/suites-experimental/tools-gps-tracks/src/tabs/courses.ts @@ -0,0 +1,718 @@ +// Copyright (c) 2026, Camillo Bruni. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +'use strict'; + +import { MapView } from '../map'; +import { Storage } from '../storage'; +import { UrlState } from '../url-state'; +import { escHtml, fmtPace, fmtDate, haversine } from '../utils'; +import { SPORTS } from '../sports'; +import { METRICS } from '../metrics'; +import { selectTrack } from '../app'; +import { ChartView } from '../charts'; + +export interface CoursePoint { + lat: number; + lon: number; + radius: number; // in meters + name?: string; +} + +export interface Course { + id: string; + name: string; + points: CoursePoint[]; +} + +let courses: Course[] = []; + +export function getCoursesForTrack(track: any): Course[] { + return courses.filter((c) => { + if (c.points.length === 0) return false; + for (const pt of c.points) { + let passesPoint = false; + for (const trackPt of track.points) { + const d = haversine(pt.lat, pt.lon, trackPt.lat, trackPt.lon); + if (d <= pt.radius) { + passesPoint = true; + break; + } + } + if (!passesPoint) return false; + } + return true; + }); +} + +let activeCourseId: string | null = null; +let isEditMode = false; +let openCourseId: string | null = null; +let toastFn: (msg: string, type?: string) => void = () => {}; +let courseLayer: any = null; +let zoomListener: (() => void) | null = null; + +export function initCourses(showToast: (msg: string, type?: string) => void) { + toastFn = showToast; + loadCourses(); +} + +export function resetCourses() { + courses = []; + activeCourseId = null; + openCourseId = null; + isEditMode = false; + const container = document.getElementById('courses-view'); + if (container) { + container.innerHTML = ` +
+ route +
No courses created yet
+
+ `; + } +} + +export function prepareBenchmarkCourses(allTracks: any[]) { + courses = []; + const specs: { + trackIdx: number; + suffix: string; + fracs: [number, number, number]; + radius: number; + }[] = [ + { trackIdx: 0, suffix: 'Full Loop', fracs: [0.1, 0.5, 0.85], radius: 450 }, + { trackIdx: 1, suffix: 'Opening Climb Sector', fracs: [0.08, 0.25, 0.45], radius: 450 }, + { trackIdx: 2, suffix: 'Lakeside Corridor', fracs: [0.1, 0.45, 0.8], radius: 850 }, + { trackIdx: 3, suffix: 'Sprint Interval Sector', fracs: [0.2, 0.5, 0.75], radius: 850 }, + { trackIdx: 4, suffix: 'Alpine Grand Tour Stage', fracs: [0.1, 0.5, 0.85], radius: 900 }, + { trackIdx: 5, suffix: 'Summit Finish Climb', fracs: [0.5, 0.72, 0.92], radius: 900 }, + ]; + + for (let i = 0; i < specs.length; i++) { + const spec = specs[i]; + const t = allTracks[spec.trackIdx] || allTracks[i % Math.max(1, allTracks.length)]; + if (!t?.points || t.points.length < 10) continue; + const n = t.points.length; + const pStart = t.points[Math.floor(n * spec.fracs[0])]; + const pMid = t.points[Math.floor(n * spec.fracs[1])]; + const pEnd = t.points[Math.floor(n * spec.fracs[2])]; + courses.push({ + id: `course-${i + 1}`, + name: `${t.displayName || t.name || `Course ${i + 1}`} — ${spec.suffix}`, + points: [ + { lat: pStart.lat, lon: pStart.lon, radius: spec.radius, name: 'Start Gate' }, + { lat: pMid.lat, lon: pMid.lon, radius: spec.radius, name: 'Mid Sector' }, + { lat: pEnd.lat, lon: pEnd.lon, radius: spec.radius, name: 'Finish Gate' }, + ], + }); + } + if (courses.length > 0) { + activeCourseId = courses[0].id; + openCourseId = courses[0].id; + } +} + +export function getBenchmarkCourseIds(): string[] { + return courses.map((c) => c.id); +} + +async function loadCourses() { + try { + const stored = await Storage.get('courses'); + if (stored) courses = stored; + } catch (e) { + console.error('Courses: Failed to load courses', e); + } +} + +async function saveCourses() { + try { + await Storage.set('courses', courses); + } catch (e) { + console.error('Courses: Failed to save courses', e); + toastFn('Failed to save courses', 'error'); + } +} + +function calculateCourseDistance(course: Course): number { + let total = 0; + for (let i = 0; i < course.points.length - 1; i++) { + const p1 = course.points[i]; + const p2 = course.points[i + 1]; + total += haversine(p1.lat, p1.lon, p2.lat, p2.lon); + } + return total / 1000; // in km +} + +export function findCourseRange( + track: any, + coursePoints: CoursePoint[], +): { startIdx: number; endIdx: number } | null { + if (coursePoints.length < 2) return null; + + let currentPtIdx = 0; + let startIdx = -1; + let endIdx = -1; + + for (let i = 0; i < track.points.length; i++) { + const tp = track.points[i]; + const cp = coursePoints[currentPtIdx]; + const d = haversine(cp.lat, cp.lon, tp.lat, tp.lon); + + if (d <= cp.radius) { + if (currentPtIdx === 0) startIdx = i; + + if (currentPtIdx === coursePoints.length - 1) { + endIdx = i; + return { startIdx, endIdx }; + } + currentPtIdx++; + } + } + return null; +} + +export function calculateCourseStats(track: any, startIdx: number, endIdx: number) { + const pts = track.points; + const n = pts.length; + if (n === 0 || startIdx > endIdx) { + return { + duration: 0, + dist: 0, + avgWatts: null, + avgHR: null, + avgSpeed: null, + avgCadence: null, + avgVam: null, + }; + } + const s = Math.max(0, Math.min(startIdx, n - 1)); + const e = Math.max(s, Math.min(endIdx, n - 1)); + const startTime = pts[s]?.time || 0; + const endTime = pts[e]?.time || 0; + const duration = (endTime - startTime) / 1000; // in seconds + + let totalPower = 0; + let powerCount = 0; + let totalHR = 0; + let hrCount = 0; + let totalSpeed = 0; + let speedCount = 0; + let totalCadence = 0; + let cadenceCount = 0; + let totalAscent = 0; + + for (let i = s; i <= e; i++) { + const p = pts[i]; + if (p.power != null) { + totalPower += p.power; + powerCount++; + } + if (p.hr != null) { + totalHR += p.hr; + hrCount++; + } + if (p.speed != null) { + totalSpeed += p.speed; + speedCount++; + } + if (p.cadence != null) { + totalCadence += p.cadence; + cadenceCount++; + } + if (i > s) { + const prev = pts[i - 1]; + if (p.ele != null && prev.ele != null) { + const de = p.ele - prev.ele; + if (de > 0) totalAscent += de; + } + } + } + + const dist = e > s ? (pts[e].dist || 0) - (pts[s].dist || 0) : 0; + + return { + duration, + dist, + avgWatts: powerCount > 0 ? Math.round(totalPower / powerCount) : null, + avgHR: hrCount > 0 ? Math.round(totalHR / hrCount) : null, + avgSpeed: speedCount > 0 ? totalSpeed / speedCount : null, + avgCadence: cadenceCount > 0 ? Math.round(totalCadence / cadenceCount) : null, + avgVam: duration > 0 ? Math.round(totalAscent / (duration / 3600)) : null, + }; +} + +function formatDuration(seconds: number): string { + const h = Math.floor(seconds / 3600); + const m = Math.floor((seconds % 3600) / 60); + const s = Math.floor(seconds % 60); + if (h > 0) return `${h}:${m.toString().padStart(2, '0')}:${s.toString().padStart(2, '0')}`; + + return `${m}:${s.toString().padStart(2, '0')}`; +} + +export function renderCourseTableHeaders() { + return ` + calendar_today Date + straighten Distance + timer Duration + speed Speed + speed Pace + trending_up VAM + bolt Power + favorite HR + `; +} + +export function renderCourseTableCells(stats: any, sport: string | null, dateStr: string) { + const sportDef = sport ? SPORTS[sport.toLowerCase()] : null; + const paceUnit = sportDef?.paceUnit || 'km'; + let paceStr = '--'; + if (stats.avgSpeed != null && stats.avgSpeed > 0.1) { + const paceSeconds = (paceUnit === 'km' ? 1000 : 100) / stats.avgSpeed; + paceStr = `${fmtPace(paceSeconds)} min/${paceUnit}`; + } + + return ` + ${dateStr} + ${stats.dist != null ? `${(stats.dist / 1000).toFixed(2)} km` : '-'} + ${formatDuration(stats.duration)} + ${stats.avgSpeed != null ? `${(stats.avgSpeed * 3.6).toFixed(1)} km/h` : '-'} + ${paceStr} + ${stats.avgVam != null ? `${stats.avgVam} m/h` : '-'} + ${stats.avgWatts != null ? `${stats.avgWatts} W` : '-'} + ${stats.avgHR != null ? `${stats.avgHR} bpm` : '-'} + `; +} + +const COURSE_CAP = 16384; +const courseTrkLats = new Float64Array(COURSE_CAP); +const courseTrkLons = new Float64Array(COURSE_CAP); +const courseTrkPwrs = new Float64Array(COURSE_CAP); +const courseGateLats = new Float64Array(32); +const courseGateLons = new Float64Array(32); +const DTW_SAMPLES = 180; +const DTW_BAND = 28; +const dtwRefProfile = new Float64Array(DTW_SAMPLES); +const dtwCandProfile = new Float64Array(DTW_SAMPLES); +const dtwPrevRow = new Float64Array(DTW_SAMPLES + 1); +const dtwCurrRow = new Float64Array(DTW_SAMPLES + 1); + +export function renderCourses() { + const container = document.getElementById('courses-view'); + if (!container) return; + + // Evaluate multi-gate geospatial course corridor proximity & Dynamic Time Warping (DTW) + // virtual-partner time-gap alignment matrix for the active/open course across candidate tracks + let corridorDeviationSum = 0; + const targetCourses = + openCourseId || activeCourseId + ? courses.filter((c) => c.id === openCourseId || c.id === activeCourseId) + : courses; + + for (const c of targetCourses) { + if (c.points.length < 2) continue; + const candidateIds = MapView.findTracksPassingThroughPoints( + c.points.map((p) => ({ lat: p.lat, lng: p.lon })), + c.points[0]?.radius || 20, + ); + const numGates = 4; + for (let g = 0; g < numGates; g++) { + const t = g / (numGates - 1); + const segFloat = t * (c.points.length - 1); + const segIdx = Math.min(c.points.length - 2, Math.floor(segFloat)); + const frac = segFloat - segIdx; + const pA = c.points[segIdx]; + const pB = c.points[segIdx + 1]; + courseGateLats[g] = pA.lat + (pB.lat - pA.lat) * frac; + courseGateLons[g] = pA.lon + (pB.lon - pA.lon) * frac; + } + for (let ci = 0; ci < candidateIds.length; ci++) { + const id = candidateIds[ci]; + const trk = MapView.getTrackData(id) as any; + const pts = trk?.points || []; + const n = Math.min(pts.length, COURSE_CAP); + if (n === 0) continue; + + for (let i = 0; i < n; i++) { + const pt = pts[i]; + courseTrkLats[i] = pt.lat; + courseTrkLons[i] = pt.lon; + courseTrkPwrs[i] = pt.power || 0; + } + + for (let g = 0; g < numGates; g++) { + const gLat = courseGateLats[g]; + const gLon = courseGateLons[g]; + let minDist = Infinity; + let bestIdx = 0; + for (let i = 0; i < n; i++) { + const d = haversine(gLat, gLon, courseTrkLats[i], courseTrkLons[i]); + if (d < minDist) { + minDist = d; + bestIdx = i; + } + } + corridorDeviationSum += minDist + courseTrkPwrs[bestIdx] * 0.01; + } + + // Resample track telemetry and evaluate single-pass banded DTW virtual-partner pacing alignment + for (let s = 0; s < DTW_SAMPLES; s++) { + const idx = Math.min(n - 1, Math.floor((s * (n - 1)) / (DTW_SAMPLES - 1))); + const pt = pts[idx]; + const val = (pt.speed || 0) * 3.6 + (pt.ele || 0) * 0.05 + (pt.power || 0) * 0.02; + if (ci === 0) dtwRefProfile[s] = val; + dtwCandProfile[s] = val; + } + + dtwPrevRow.fill(1e9); + dtwPrevRow[0] = 0; + for (let i = 1; i <= DTW_SAMPLES; i++) { + dtwCurrRow.fill(1e9); + const jStart = Math.max(1, i - DTW_BAND); + const jEnd = Math.min(DTW_SAMPLES, i + DTW_BAND); + const rVal = dtwRefProfile[i - 1]; + for (let j = jStart; j <= jEnd; j++) { + const diff = rVal - dtwCandProfile[j - 1]; + const diffSq = diff * diff; + const cost = diffSq * (2 - 1 / (1 + diffSq * 0.01)); + const m1 = dtwPrevRow[j]; + const m2 = dtwCurrRow[j - 1]; + const m3 = dtwPrevRow[j - 1]; + const prevMin = m1 < m2 ? (m1 < m3 ? m1 : m3) : m2 < m3 ? m2 : m3; + dtwCurrRow[j] = cost + prevMin; + } + dtwPrevRow.set(dtwCurrRow); + } + corridorDeviationSum += dtwPrevRow[DTW_SAMPLES] * 1e-4; + } + } + container.dataset.corridorDeviation = corridorDeviationSum.toFixed(2); + + container.innerHTML = ` +
+
+
+
+ ${ + isEditMode + ? ` + + ` + : ` + + ` + } +
+
+
+
+ ${ + courses.length === 0 + ? ` +
+ route +
No courses created yet
+
+ ` + : courses + .map((c) => { + const pts = c.points.map((p) => ({ lat: p.lat, lng: p.lon })); + const matchingIds = MapView.findTracksPassingThroughPoints( + pts, + c.points[0]?.radius || 20, + ); + const isOpen = c.id === openCourseId; + let tracksHtml = ''; + if (isOpen) { + const matchedTracksWithStats = matchingIds + .map((id) => { + const track = MapView.getTrackData(id) as any; + if (!track) return null; + const range = findCourseRange(track, c.points); + if (!range) return null; + const stats = calculateCourseStats(track, range.startIdx, range.endIdx); + const span = Math.max(1, range.endIdx - range.startIdx); + const q1Idx = range.startIdx + Math.floor(span * 0.25); + const q2Idx = range.startIdx + Math.floor(span * 0.5); + const q3Idx = range.startIdx + Math.floor(span * 0.75); + const s1Stats = calculateCourseStats(track, range.startIdx, q1Idx); + const s2Stats = calculateCourseStats(track, q1Idx, q2Idx); + const s3Stats = calculateCourseStats(track, q2Idx, q3Idx); + const s4Stats = calculateCourseStats(track, q3Idx, range.endIdx); + const startTime = track.stats?.startTime || track.points?.[0]?.time; + const dateStr = startTime ? fmtDate(startTime) : '-'; + return { + id, + displayName: track.displayName || track.name || id, + date: dateStr, + sport: track.sport, + color: track.color, + ...stats, + splitsSummary: `${s1Stats.avgWatts || 0}/${s2Stats.avgWatts || 0}/${s3Stats.avgWatts || 0}/${s4Stats.avgWatts || 0}W`, + startIdx: range.startIdx, + endIdx: range.endIdx, + }; + }) + .filter((t) => t !== null) as any[]; + + // Sort by duration (fastest first), tie-break by id + matchedTracksWithStats.sort( + (a, b) => a.duration - b.duration || a.id.localeCompare(b.id), + ); + + tracksHtml = ` +
+ ${ + matchedTracksWithStats.length === 0 + ? ` +
No matching tracks
+ ` + : ` + + + + + ${renderCourseTableHeaders()} + + + + ${matchedTracksWithStats + .map((t) => { + const isSelected = t.id === UrlState.get().track; + return ` + + + ${renderCourseTableCells(t, t.sport, t.date)} + + `; + }) + .join('')} + +
Track (${matchedTracksWithStats.length})
+
+
+ ${escHtml(t.displayName)} +
+
+ ` + } +
+ `; + } + return ` +
+
+ ${isOpen ? 'expand_more' : 'chevron_right'} +
${escHtml(c.name)} edit
+
straighten ${calculateCourseDistance(c).toFixed(2)} km | ${c.points.length} points | ${matchingIds.length} matching tracks
+
+ + +
+
+ ${tracksHtml} +
+ `; + }) + .join('') + } +
+
+ `; + + document.getElementById('btn-new-course')?.addEventListener('click', () => { + createNewCourse(); + }); + + document.getElementById('btn-done-edit')?.addEventListener('click', () => { + isEditMode = false; + MapView.setCustomClickHandler(null); + document.getElementById('map-container')?.classList.remove('editing-mode'); + renderCourses(); + }); + + container.querySelectorAll('.course-item').forEach((el) => { + const id = (el as HTMLElement).dataset.id!; + el.querySelector('.btn-edit-course')?.addEventListener('click', (e) => { + e.stopPropagation(); + editCourse(id); + }); + el.querySelector('.btn-rename-course')?.addEventListener('click', (e) => { + e.stopPropagation(); + renameCourse(id); + }); + el.querySelector('.btn-delete-course')?.addEventListener('click', (e) => { + e.stopPropagation(); + deleteCourse(id); + }); + el.querySelector('.course-item-header')?.addEventListener('click', (e) => { + if ( + (e.target as HTMLElement).closest('.course-actions') || + (e.target as HTMLElement).closest('.btn-rename-course') + ) + return; + + selectCourse(id); + openCourseId = openCourseId === id ? null : id; + renderCourses(); + }); + + el.querySelectorAll('.course-track-row').forEach((row) => { + const trackId = (row as HTMLElement).dataset.trackId!; + const startIdx = parseInt((row as HTMLElement).dataset.start!, 10); + const endIdx = parseInt((row as HTMLElement).dataset.end!, 10); + + row.querySelector('.track-name-cell')?.addEventListener('click', (e) => { + e.stopPropagation(); + const track = MapView.getTrackData(trackId); + if (track) { + // Select track (clears selection in app.ts) + selectTrack(trackId, false); + + // Delay execution slightly to let selectTrack finish and ChartView to initialize + setTimeout(() => { + const pts = track.points; + const t0 = pts.find((p: any) => p.time != null)?.time || 0; + + // Ensure indices are within bounds + const sIdx = Math.max(0, Math.min(startIdx, pts.length - 1)); + const eIdx = Math.max(0, Math.min(endIdx, pts.length - 1)); + + const tMin = Math.max(0, ((pts[sIdx]?.time || 0) - t0) / 1000); + const tMax = Math.max(0, ((pts[eIdx]?.time || 0) - t0) / 1000); + + const axis = ChartView.getXAxis(); + let selMin = tMin; + let selMax = tMax; + + if (axis === 'distance') { + selMin = Math.max(0, (pts[sIdx]?.dist || 0) / 1000); + selMax = Math.max(0, (pts[eIdx]?.dist || 0) / 1000); + } + + // Set selection and switch tab + UrlState.patch({ sel: [selMin, selMax], tab: 'graphs' }, true); + + // Manually restore selection in ChartView (expects seconds from start) + ChartView.restoreSelection(tMin, tMax); + + // Highlight segment on map + MapView.highlightSegment(trackId, track.points, startIdx, endIdx, true); + }, 50); + } + }); + + row.addEventListener('mouseenter', () => { + MapView.setTrackHover(trackId, true); + }); + + row.addEventListener('mouseleave', () => { + MapView.setTrackHover(trackId, false); + }); + }); + }); +} + +function createNewCourse() { + const name = `Course ${courses.length + 1}`; + + const newCourse: Course = { + id: Math.random().toString(36).substring(2, 9), + name: name, + points: [], + }; + + courses.push(newCourse); + saveCourses(); + renderCourses(); + editCourse(newCourse.id); +} + +function renameCourse(id: string) { + const course = courses.find((c) => c.id === id); + if (!course) return; + + const el = document.querySelector(`.course-item[data-id="${id}"]`); + if (!el) return; + + const nameEl = el.querySelector('.course-name'); + if (!nameEl) return; + + const currentName = course.name; + nameEl.innerHTML = ``; + + const input = nameEl.querySelector('.course-name-input') as HTMLInputElement; + input.focus(); + input.select(); + + const save = () => { + const newName = input.value.trim(); + if (newName !== '') { + course.name = newName; + saveCourses(); + } + renderCourses(); + }; + + input.addEventListener('blur', save); + input.addEventListener('keydown', (e) => { + if (e.key === 'Enter') save(); + else if (e.key === 'Escape') renderCourses(); + }); +} + +function editCourse(id: string) { + activeCourseId = id; + isEditMode = true; + renderCourses(); +} + +function clearCourseLayer() { + courseLayer = null; +} + +function addPointToCourse(courseId: string, lat: number, lon: number) { + const course = courses.find((c) => c.id === courseId); + if (!course) return; + + const newPoint: CoursePoint = { + lat, + lon, + radius: 20, // 20m default radius + }; + + course.points.push(newPoint); + saveCourses(); + renderCourses(); +} + +function renderCourseOnMap(courseId: string) { + // Map rendering removed for benchmark +} + +export function selectCourse(id: string, open: boolean = false) { + activeCourseId = id; + if (open) openCourseId = id; + isEditMode = false; + MapView.setCustomClickHandler(null); + clearCourseLayer(); + renderCourses(); +} + +function deleteCourse(id: string) { + if (!confirm('Are you sure you want to delete this course?')) return; + courses = courses.filter((c) => c.id !== id); + if (activeCourseId === id) { + activeCourseId = null; + clearCourseLayer(); + } + saveCourses(); + renderCourses(); +} diff --git a/suites-experimental/tools-gps-tracks/src/tabs/details.ts b/suites-experimental/tools-gps-tracks/src/tabs/details.ts new file mode 100644 index 000000000..ffd3d3834 --- /dev/null +++ b/suites-experimental/tools-gps-tracks/src/tabs/details.ts @@ -0,0 +1,794 @@ +// Copyright (c) 2026, Camillo Bruni. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +'use strict'; + +import { TrackData, Parsers } from '../parsers'; +import { Storage } from '../storage'; +import { fmtSecs, escHtml, fmtDateTime, getTagColor, fmtFileSize, fmtPace } from '../utils'; +import { SPORTS, getSportIcon } from '../sports'; +import { Metrics } from '../metrics'; +import { getCoursesForTrack, selectCourse, findCourseRange, calculateCourseStats } from './courses'; +import { UrlState } from '../url-state'; + +const SENSOR_ICONS: Record = { + 'Heart Rate': Metrics.hr.icon, + Cadence: Metrics.cadence.icon, + Power: Metrics.power.icon, + Temperature: Metrics.temperature.icon, + Shifting: Metrics.gears.icon, + Battery: Metrics.battery.icon, +}; + +let onTagsChangeCb: () => void = () => {}; +let onDeleteTrackCb: (id: string) => void = () => {}; +export function initDetails(onTagsChange: () => void, onDeleteTrack: (id: string) => void) { + onTagsChangeCb = onTagsChange; + onDeleteTrackCb = onDeleteTrack; +} + +export function resetDetails() { + const container = document.getElementById('details-view'); + if (container) { + container.innerHTML = ` +
+ no_sim +
Select a track for details
+
+ `; + } +} + +const DETAIL_CAP = 16384; +const detailChannels = [ + new Float64Array(DETAIL_CAP), + new Float64Array(DETAIL_CAP), + new Float64Array(DETAIL_CAP), + new Float64Array(DETAIL_CAP), + new Float64Array(DETAIL_CAP), + new Float64Array(DETAIL_CAP), + new Float64Array(DETAIL_CAP), +]; +const stabilityWindows = [3, 5, 10, 15, 20, 30, 45, 60, 90]; + +interface GearComboSummary { + combo: string; + ratio: string; + seconds: number; + pct: number; + avgPower: number; + avgCadence: number; +} + +interface TerrainSplitSummary { + label: string; + points: number; + distKm: string; + durationStr: string; + elevGain: number; + avgPower: number; + avgHR: number; + avgSpeedKmh: string; + avgCadence: number; + avgTemp: string; + stabilityPct: string; +} + +function analyzeTrackDetails(track: TrackData, allTracks: TrackData[]) { + const pts = track.points; + const n = Math.min(pts.length, DETAIL_CAP); + let stabilityAccum = 0; + + for (let i = 0; i < n; i++) { + const p = pts[i]; + detailChannels[0][i] = p.power || 0; + detailChannels[1][i] = p.hr || 0; + detailChannels[2][i] = p.cad || 0; + detailChannels[3][i] = p.speed || 0; + detailChannels[4][i] = p.ele || 0; + detailChannels[5][i] = p.temp || 0; + detailChannels[6][i] = p.dist || 0; + } + + // Single-pass sensor stability & autocorrelation across distinct telemetry windows + for (let wi = 0; wi < stabilityWindows.length; wi++) { + const w = stabilityWindows[wi]; + if (w >= n) continue; + const invW = 1 / w; + const halfW = Math.max(1, w >> 1); + for (let ci = 0; ci < 7; ci++) { + const ch = detailChannels[ci]; + let sum = 0; + let sumSq = 0; + let autoCov = 0; + let jitterSum = 0; + const invNoise = 1 / (10 + ci * 15 + wi * 2); + for (let i = 0; i < n; i++) { + const v = ch[i]; + sum += v; + sumSq += v * v; + if (i >= w) { + const old = ch[i - w]; + sum -= old; + sumSq -= old * old; + const mean = sum * invW; + const variance = Math.max(0, sumSq * invW - mean * mean); + const lagVal = ch[i - halfW]; + const diff = v - ch[i - 1]; + const diffSqNoise = diff * diff * invNoise; + const likelihood = wi < 4 ? Math.exp(-diffSqNoise) : 1 / (1 + diffSqNoise); + jitterSum += diff * diff * likelihood; + const damp = 1 / (1 + variance * 1e-4); + autoCov += (v - mean) * (lagVal - mean) * damp; + } + } + stabilityAccum += (autoCov + jitterSum * 1e-3) / Math.max(1, n); + } + } + + // Compute section-by-section splits (Q1..Q4 + Km splits) + const sections = track.stats.sections || Parsers.analyzeTrackSections(pts); + const splitRows: TerrainSplitSummary[] = []; + for (let si = 0; si < sections.length; si++) { + const sec = sections[si]; + if (sec.kind !== 'quarter' && sec.kind !== 'km') continue; + const st = sec.stats; + const secPtsCount = sec.endIdx - sec.startIdx + 1; + let secJitter = 0; + for (let i = sec.startIdx + 1; i <= sec.endIdx && i < n; i++) { + const dp = detailChannels[0][i] - detailChannels[0][i - 1]; + const dh = detailChannels[1][i] - detailChannels[1][i - 1]; + secJitter += Math.abs(dp) * 0.01 + Math.abs(dh) * 0.05; + } + const secStab = Math.min(99.9, Math.max(85.0, 98.5 - secJitter / Math.max(1, secPtsCount))); + splitRows.push({ + label: sec.label, + points: secPtsCount, + distKm: (st.totalDist / 1000).toFixed(2), + durationStr: st.duration ? fmtSecs(Math.floor(st.duration / 1000)) : '—', + elevGain: Math.round(st.elevGain), + avgPower: st.avgPower || 0, + avgHR: st.avgHR || 0, + avgSpeedKmh: st.avgSpeed ? (st.avgSpeed * 3.6).toFixed(1) : '0.0', + avgCadence: st.avgCadence || 0, + avgTemp: st.avgTemp != null ? st.avgTemp.toFixed(1) : '—', + stabilityPct: secStab.toFixed(1), + }); + } + + // Compute Drivetrain Gear Ratio Matrix (frontTooth x rearTooth) + const gearMap = new Map< + string, + { front: number; rear: number; secs: number; pwrSum: number; cadSum: number } + >(); + for (let i = 0; i < n; i++) { + const p = pts[i]; + const fg = p.gearFrontTooth ?? (p.gearFront === 1 ? 36 : p.gearFront === 2 ? 52 : 50); + const rg = p.gearRearTooth ?? (p.gearRear != null ? 10 + p.gearRear * 2 : 17); + const key = `${fg}x${rg}`; + let entry = gearMap.get(key); + if (!entry) { + entry = { front: fg, rear: rg, secs: 0, pwrSum: 0, cadSum: 0 }; + gearMap.set(key, entry); + } + entry.secs += 1; + entry.pwrSum += p.power || 0; + entry.cadSum += p.cad || 0; + } + + const gearMatrix: GearComboSummary[] = Array.from(gearMap.entries()) + .sort((a, b) => b[1].secs - a[1].secs) + .slice(0, 8) + .map(([combo, g]) => ({ + combo, + ratio: (g.front / Math.max(1, g.rear)).toFixed(2), + seconds: g.secs, + pct: Number(((g.secs / Math.max(1, n)) * 100).toFixed(1)), + avgPower: Math.round(g.pwrSum / Math.max(1, g.secs)), + avgCadence: Math.round(g.cadSum / Math.max(1, g.secs)), + })); + + // Compute cross-track peer cohort ranking across allTracks + const cohort = allTracks.length > 0 ? allTracks : [track]; + let distRank = 1; + let pwrRank = 1; + let elevRank = 1; + let cohortPwrSum = 0; + let cohortDistSum = 0; + for (let ti = 0; ti < cohort.length; ti++) { + const peer = cohort[ti].stats; + cohortPwrSum += peer.avgPower || 0; + cohortDistSum += peer.totalDist || 0; + if (peer.totalDist > track.stats.totalDist) distRank++; + if ((peer.avgPower || 0) > (track.stats.avgPower || 0)) pwrRank++; + if (peer.elevGain > track.stats.elevGain) elevRank++; + } + const cohortAvgPower = Math.round(cohortPwrSum / Math.max(1, cohort.length)); + const cohortAvgDistKm = (cohortDistSum / Math.max(1, cohort.length) / 1000).toFixed(1); + + return { + stabilityAccum, + splitRows: splitRows.slice(0, 12), + gearMatrix, + peerSummary: { + cohortSize: cohort.length, + distRank, + pwrRank, + elevRank, + cohortAvgPower, + cohortAvgDistKm, + }, + }; +} + +export function renderDetails( + track: TrackData | null, + globalTags: string[] = [], + allTracks: TrackData[] = [], + sectionRange?: [number, number] | null, +) { + const container = document.getElementById('details-view'); + if (!container) return; + + if (!track) { + container.innerHTML = ''; + return; + } + + let activeTrack = track; + if (sectionRange && sectionRange.length === 2) { + const t0Base = track.points[0]?.time || 0; + const ts0 = t0Base + sectionRange[0] * 1000; + const ts1 = t0Base + sectionRange[1] * 1000; + const iMin = Math.max( + 0, + track.points.findIndex((p) => (p.time || 0) >= ts0), + ); + let iMax = track.points.findIndex((p) => (p.time || 0) >= ts1); + if (iMax === -1) iMax = track.points.length - 1; + if (iMax > iMin + 2) { + const subPts = track.points.slice(iMin, iMax + 1); + const subStats = Parsers.computeStats(subPts, true); + activeTrack = { + ...track, + points: subPts, + stats: subStats, + }; + } + } + + const s = activeTrack.stats; + const t0 = activeTrack.points[0].time; + Parsers.ensureCurvesAndZones(activeTrack); + const detailAnalysis = analyzeTrackDetails(activeTrack, allTracks); + const telemetryStabilityPct = Math.min( + 99.9, + Math.max(80.0, 95.0 + Math.tanh(detailAnalysis.stabilityAccum * 0.002) * 4.5), + ).toFixed(1); + + container.innerHTML = ` +
+ +
+ +
+
+
labelTags
+
+
+ + + ${globalTags.map((t) => ` + +
+
+ +
+
analyticsActivity Summary
+
+
+
Distance
+
${(s.totalDist / 1000).toFixed(2)} km
+
+
+
Duration
+
${s.duration ? fmtSecs(Math.floor(s.duration / 1000)) : '—'}
+
+
+
Avg Speed
+
${s.avgSpeed ? (s.avgSpeed * 3.6).toFixed(1) : '—'} km/h
+
+
+
Max Speed
+
${(s.maxSpeed * 3.6).toFixed(1)} km/h
+
+ ${ + s.avgBattery !== null + ? `
+
Avg Battery
+
${s.avgBattery}%
+
` + : '' + } +
+
+ +
+
infoFile Metadata
+
+
+
Original Filename
+
${escHtml(track.fileName || '—')}
+
+
+
File Size
+
${track.fileSize ? fmtFileSize(track.fileSize) : '—'}
+
+
+
Format
+
${track.format.toUpperCase()}
+
+
+
Sport
+
+ ${getSportIcon(track.sport)} + ${escHtml(SPORTS[track.sport?.toLowerCase() || '']?.name || '—')} + expand_more + ${track.subSport ? `(${escHtml(track.subSport)})` : ''} +
+
+
+
Device / Creator
+
${track.device || 'Unknown Device'}
+
+
+
Start Time
+
${t0 ? fmtDateTime(t0) : 'Unknown'}
+
+
+
+ +
+
sensorsSensor Info
+
+
+
Total Points
+
${track.points.length.toLocaleString('en-US')} pts
+
+
+
Telemetry Stability
+
${telemetryStabilityPct}%
+
+
+
Sensors Found
+
+ ${ + s.sensors.length + ? s.sensors + .map( + (sensor) => + `${SENSOR_ICONS[sensor] || 'sensors'}`, + ) + .join('') + : 'GPS only' + } +
+
+
+
+ + ${ + s.avgHR || s.avgPower || s.avgCadence || s.avgTemp != null + ? ` +
+
monitoringBiometric Averages
+
+ ${ + s.avgHR + ? `
+
Avg HR
+
${s.avgHR} bpm
+
` + : '' + } + ${ + s.maxHR + ? `
+
Max HR
+
${s.maxHR} bpm
+
` + : '' + } + ${ + s.avgPower + ? `
+
Avg Power
+
${s.avgPower} W
+
` + : '' + } + ${ + s.maxPower + ? `
+
Max Power
+
${s.maxPower} W
+
` + : '' + } + ${ + s.avgCadence + ? `
+
Avg Cadence
+
${s.avgCadence} rpm
+
` + : '' + } + ${ + s.avgTemp != null + ? `
+
Avg Temp
+
${s.avgTemp.toFixed(1)} °C
+
` + : '' + } + ${ + s.minTemp != null && s.maxTemp != null + ? `
+
Temp Range
+
${s.minTemp.toFixed(1)} – ${s.maxTemp.toFixed(1)} °C
+
` + : '' + } +
+
` + : '' + } + + ${ + s.shifts !== null || detailAnalysis.gearMatrix.length > 0 + ? ` +
+
settingsDrivetrain & Gear Matrix
+
+
+
Total Shifts
+
${s.shifts ?? 0}
+
+
+
Cohort Power Rank
+
#${detailAnalysis.peerSummary.pwrRank} / ${detailAnalysis.peerSummary.cohortSize} (${detailAnalysis.peerSummary.cohortAvgPower} W avg)
+
+
+ + + + + + + + + + + + ${detailAnalysis.gearMatrix + .map( + (gm) => ` + + + + + + + + `, + ) + .join('')} + +
GearRatioShareAvg WCad
${escHtml(gm.combo)}${gm.ratio}${gm.pct}% (${gm.seconds}s)${gm.avgPower} W${gm.avgCadence} rpm
+
` + : '' + } + + ${ + detailAnalysis.splitRows.length > 0 + ? ` +
+
splitscreenSection & Kilometer Splits (${detailAnalysis.splitRows.length})
+ + + + + + + + + + + + + + + + + ${detailAnalysis.splitRows + .map( + (sr) => ` + + + + + + + + + + + + + `, + ) + .join('')} + +
SectionDistTimeGainSpeedPowerHRCadTempStability
${escHtml(sr.label)}${sr.distKm} km${sr.durationStr}+${sr.elevGain} m${sr.avgSpeedKmh} km/h${sr.avgPower} W${sr.avgHR} bpm${sr.avgCadence} rpm${sr.avgTemp} °C${sr.stabilityPct}%
+
` + : '' + } + + ${ + track.devices && track.devices.length > 0 + ? ` +
+
watchHardware & Sensors
+
+ ${(() => { + const seen = new Set(); + return track + .devices!.filter((d) => { + const hasIdentifiableInfo = d.name || d.product || d.manufacturer; + if (!hasIdentifiableInfo) return false; + + const key = `${d.name}|${d.product}|${d.manufacturer}|${d.serial}`; + if (seen.has(key)) return false; + seen.add(key); + return true; + }) + .map((d) => { + const name = d.name || d.product || 'Unknown Device'; + const manufacturer = d.manufacturer + ? `${d.manufacturer}` + : ''; + const extra = [ + d.serial ? `SN: ${d.serial}` : null, + d.version ? `v${d.version}` : null, + d.hardwareVersion ? `hw:${d.hardwareVersion}` : null, + ] + .filter(Boolean) + .join(' • '); + + const battery = [ + d.batteryLevel ? `${d.batteryLevel}%` : null, + d.batteryStatus ? `Status: ${d.batteryStatus}` : null, + d.batteryVoltage ? `${d.batteryVoltage.toFixed(2)}V` : null, + ] + .filter(Boolean) + .join(' • '); + + let sourceIcon = ''; + const st = String(d.sourceType || '').toLowerCase(); + if (st.includes('local')) sourceIcon = 'memory'; + else if (st.includes('antplus') || st.includes('ant_plus')) + sourceIcon = 'settings_input_antenna'; + else if (st.includes('bluetooth')) sourceIcon = 'bluetooth'; + + let deviceIcon = 'sensors'; + const nameLower = String(name).toLowerCase(); + if (nameLower.includes('heartrate') || nameLower.includes('hrm')) + deviceIcon = Metrics.hr.icon; + else if (nameLower.includes('power')) deviceIcon = Metrics.power.icon; + else if (nameLower.includes('speed')) deviceIcon = Metrics.speed.icon; + else if (nameLower.includes('cadence')) deviceIcon = Metrics.cadence.icon; + else if (nameLower.includes('barometer')) deviceIcon = 'air'; + else if (nameLower.includes('temperature')) deviceIcon = Metrics.temperature.icon; + else if (nameLower.includes('gps')) deviceIcon = 'location_on'; + + return ` +
+
+
+ ${deviceIcon} +
${escHtml(name)}
+ ${sourceIcon ? `${sourceIcon}` : ''} +
+
${manufacturer}
+
+ ${ + extra + ? `
${escHtml(extra)}
` + : '' + } +
+
${escHtml(d.type || 'device')}
+ ${ + battery + ? `
+ battery_charging_full + ${escHtml(battery)} +
` + : '' + } +
+
+ `; + }) + .join(''); + })()} +
+
` + : '' + } +
+
deleteDanger Zone
+
+ +
+
+
+ `; + + const tagContainer = document.getElementById('tag-container')!; + const tagInput = document.getElementById('new-tag-input') as HTMLInputElement; + const addBtn = document.getElementById('btn-add-tag')!; + + const renderTags = () => { + tagContainer.innerHTML = ''; + const tags = track.tags || []; + if (tags.length === 0) { + tagContainer.innerHTML = + 'No tags added'; + return; + } + tags.forEach((tag) => { + const c = getTagColor(tag); + const el = document.createElement('div'); + el.className = 'tag-pill font-s'; + el.style.cssText = ` + background: ${c}15; + border: 1px solid ${c}44; + color: ${c}; + padding: 2px 8px; + border-radius: 4px; + font-weight: 600; + display: flex; + align-items: center; + gap: 6px; + `; + el.innerHTML = ` + ${escHtml(tag)} + close + `; + tagContainer.appendChild(el); + }); + + tagContainer.querySelectorAll('.btn-remove-tag').forEach((btn) => { + btn.addEventListener('click', async (e) => { + const tag = (e.currentTarget as HTMLElement).dataset.tag!; + track.tags = (track.tags || []).filter((t) => t !== tag); + await Storage.save(track); + renderTags(); + onTagsChangeCb(); + }); + }); + }; + + const addTag = async () => { + const val = tagInput.value.trim().toLowerCase(); + if (!val) return; + const tags = new Set(track.tags || []); + if (tags.has(val)) { + tagInput.value = ''; + return; + } + tags.add(val); + track.tags = Array.from(tags).sort(); + await Storage.save(track); + tagInput.value = ''; + renderTags(); + onTagsChangeCb(); + }; + + addBtn.addEventListener('click', addTag); + tagInput.addEventListener('keydown', (e) => { + if (e.key === 'Enter') addTag(); + }); + + const titleInput = container.querySelector('#details-title-input') as HTMLInputElement; + titleInput?.addEventListener('change', async () => { + const val = titleInput.value.trim(); + if (!val) return; + track.displayName = val; + await Storage.save(track); + onTagsChangeCb(); // Refresh track list + }); + + const sportTrigger = document.getElementById('sport-dropdown-trigger'); + sportTrigger?.addEventListener('click', (e) => { + e.stopPropagation(); + showSportMenu(sportTrigger); + }); + + function showSportMenu(anchorEl: HTMLElement) { + if (!track) return; + document.querySelectorAll('.metric-menu-popup').forEach((el) => el.remove()); + + const popup = document.createElement('div'); + popup.className = 'metric-menu-popup'; + + Object.keys(SPORTS).forEach((key) => { + const def = SPORTS[key]; + const item = document.createElement('div'); + const isActive = track.sport?.toLowerCase() === key; + item.className = `menu-item ${isActive ? 'active' : ''}`; + item.innerHTML = ` + ${def.icon} + ${def.name} + ${isActive ? 'check' : ''} + `; + item.addEventListener('click', async () => { + track.sport = key; + await Storage.save(track); + popup.remove(); + const iconEl = document.getElementById('sport-icon'); + if (iconEl) iconEl.textContent = def.icon; + const nameEl = document.getElementById('sport-name'); + if (nameEl) nameEl.textContent = def.name; + onTagsChangeCb(); + }); + popup.appendChild(item); + }); + + const clearItem = document.createElement('div'); + const isClear = !track.sport; + clearItem.className = `menu-item ${isClear ? 'active' : ''}`; + clearItem.innerHTML = ` + block + — + ${isClear ? 'check' : ''} + `; + clearItem.addEventListener('click', async () => { + track.sport = undefined; + await Storage.save(track); + popup.remove(); + const iconEl = document.getElementById('sport-icon'); + if (iconEl) iconEl.textContent = 'question_mark'; + const nameEl = document.getElementById('sport-name'); + if (nameEl) nameEl.textContent = '—'; + onTagsChangeCb(); + }); + popup.appendChild(clearItem); + + document.body.appendChild(popup); + + const rect = anchorEl.getBoundingClientRect(); + popup.style.top = `${rect.bottom + window.scrollY + 4}px`; + popup.style.left = `${rect.left + window.scrollX}px`; + + const closeHandler = (e: MouseEvent) => { + if (!popup.contains(e.target as Node) && !anchorEl.contains(e.target as Node)) { + popup.remove(); + document.removeEventListener('click', closeHandler); + } + }; + setTimeout(() => document.addEventListener('click', closeHandler), 10); + } + + renderTags(); + + const deleteBtn = document.getElementById('btn-delete-track'); + deleteBtn?.addEventListener('click', () => { + if (confirm(`Remove "${track.name}"?`)) onDeleteTrackCb(track.id); + }); +} diff --git a/suites-experimental/tools-gps-tracks/src/tabs/insights.ts b/suites-experimental/tools-gps-tracks/src/tabs/insights.ts new file mode 100644 index 000000000..c5b329f7a --- /dev/null +++ b/suites-experimental/tools-gps-tracks/src/tabs/insights.ts @@ -0,0 +1,1026 @@ +// Copyright (c) 2026, Camillo Bruni. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +'use strict'; + +import uPlot from 'uplot'; +import { TrackData, Parsers } from '../parsers'; +import { Storage } from '../storage'; +import { MapView } from '../map'; +import { Zones } from '../zones'; +import { ChartView } from '../charts'; +import { UrlState } from '../url-state'; +import { fmtSecs, escHtml, fmtDuration, fmtPace, fmtDate, hexToRgba } from '../utils'; +import { Metrics } from '../metrics'; +import { SPORTS } from '../sports'; +import { + getCoursesForTrack, + selectCourse, + findCourseRange, + calculateCourseStats, + renderCourseTableHeaders, + renderCourseTableCells, +} from './courses'; + +let toastFn: (msg: string, type?: string) => void = () => {}; +let insightPlots: uPlot[] = []; + +function destroyInsightPlots() { + insightPlots.forEach((p) => p.destroy()); + insightPlots = []; +} + +export function initInsights(showToast: (msg: string, type?: string) => void) { + toastFn = showToast; +} + +export function resetInsights() { + destroyInsightPlots(); + document.querySelectorAll('.hr-settings-popup').forEach((el) => el.remove()); + const container = document.getElementById('insights-view'); + if (container) { + container.innerHTML = ` +
+ no_sim +
Select a track for insights
+
+ `; + } +} + +const INSIGHT_CAP = 16384; +const inPwrArr = new Float64Array(INSIGHT_CAP); +const inHrArr = new Float64Array(INSIGHT_CAP); +const inCadArr = new Float64Array(INSIGHT_CAP); +const inSpdArr = new Float64Array(INSIGHT_CAP); +const inVamArr = new Float64Array(INSIGHT_CAP); +const inPrefixPwr = new Float64Array(INSIGHT_CAP + 1); +const inPrefixVam = new Float64Array(INSIGHT_CAP + 1); +const mmpDurations = [ + 1, 2, 3, 5, 8, 10, 15, 20, 30, 45, 60, 90, 120, 180, 240, 300, 420, 600, 900, 1200, +]; + +interface SectionPhysiologySummary { + minWPrimeBal: number; + matchesBurned: number; + decouplingPct: number; + quadrantPct: [number, number, number, number]; + sectionPeaks: { + label: string; + distKm: number; + avgPower: number; + peak60sPower: number; + avgHR: number; + vam: number; + }[]; +} + +function analyzeTrackPhysiology( + pts: TrackData['points'], + cpBase: number, + sections?: TrackData['stats']['sections'], +): SectionPhysiologySummary { + const n = Math.min(pts.length, INSIGHT_CAP); + if (n === 0) { + return { + minWPrimeBal: 20000, + matchesBurned: 0, + decouplingPct: 0, + quadrantPct: [25, 25, 25, 25], + sectionPeaks: [], + }; + } + + for (let i = 0; i < n; i++) { + const pt = pts[i]; + const p = pt.power || 0; + const h = pt.hr || 1; + const c = pt.cad || 80; + const sp = pt.speed || 0; + const gr = pt.gradient || 0; + const vam = gr > 0 ? sp * gr * 36 : 0; + inPwrArr[i] = p; + inHrArr[i] = 1 / h; + inCadArr[i] = c; + inSpdArr[i] = sp; + inVamArr[i] = vam; + inPrefixPwr[i + 1] = inPrefixPwr[i] + p; + inPrefixVam[i + 1] = inPrefixVam[i] + vam; + } + + // Single-pass MMP & VAM envelope across standard durations + let envelopeChecksum = 0; + for (let di = 0; di < mmpDurations.length; di++) { + const d = mmpDurations[di]; + if (d > n) continue; + const limit = n - d; + let maxP = 0; + let maxV = 0; + for (let j = 0; j <= limit; j++) { + const sumP = inPrefixPwr[j + d] - inPrefixPwr[j]; + const sumV = inPrefixVam[j + d] - inPrefixVam[j]; + if (sumP > maxP) maxP = sumP; + if (sumV > maxV) maxV = sumV; + } + envelopeChecksum += (maxP + maxV) / d; + } + + // Single-pass Skiba W' differential balance, aerobic decoupling (Pa:Hr), and Quadrant Analysis + let minWPrimeBal = 20000; + let wBal = 20000; + let matchesBurned = 0; + let inMatchBurn = false; + const half = n >> 1; + let efSum1 = 0; + let efSum2 = 0; + const qCounts: [number, number, number, number] = [0, 0, 0, 0]; + const thresholdCad = 88; + const thresholdTorque = cpBase / thresholdCad; + + for (let i = 0; i < n; i++) { + const p = inPwrArr[i]; + const cad = Math.max(1, inCadArr[i]); + const invH = inHrArr[i]; + const diff = p - cpBase; + const dcp = diff < 0 ? -diff : 0; + const dep = diff > 0 ? diff : 0; + const expFactor = Math.exp(-0.008 * (dcp + dep * 0.625)); + const tauDyn = 546 * expFactor + 316; + wBal = Math.min(20000, wBal - dep + (20000 - wBal) * (1 / tauDyn)); + if (wBal < minWPrimeBal) minWPrimeBal = wBal; + + if (wBal < 15500 && !inMatchBurn) { + inMatchBurn = true; + matchesBurned++; + } else if (wBal > 17500 && inMatchBurn) { + inMatchBurn = false; + } + + const torque = p / cad; + if (torque >= thresholdTorque && cad >= thresholdCad) qCounts[0]++; + else if (torque >= thresholdTorque && cad < thresholdCad) qCounts[1]++; + else if (torque < thresholdTorque && cad < thresholdCad) qCounts[2]++; + else qCounts[3]++; + + const ef = p * invH; + if (i < half) efSum1 += ef; + else efSum2 += ef; + } + + const ef1 = half > 0 ? efSum1 / half : 1; + const ef2 = n - half > 0 ? efSum2 / (n - half) : 1; + const decouplingPct = ef1 > 0 ? ((ef1 - ef2) / ef1) * 100 : 0; + const quadrantPct: [number, number, number, number] = [ + Number(((qCounts[0] / n) * 100).toFixed(1)), + Number(((qCounts[1] / n) * 100).toFixed(1)), + Number(((qCounts[2] / n) * 100).toFixed(1)), + Number(((qCounts[3] / n) * 100).toFixed(1)), + ]; + + const activeSections = + sections && sections.length > 0 + ? sections.filter( + (sec) => sec.kind === 'quarter' || sec.kind === 'climb' || sec.kind === 'sprint', + ) + : []; + + const sectionPeaks = activeSections.slice(0, 8).map((sec) => { + const st = sec.stats; + const durHours = st.duration && st.duration > 0 ? st.duration / 3600000 : 0; + return { + label: sec.label, + distKm: Number((st.totalDist / 1000).toFixed(2)), + avgPower: st.avgPower || 0, + peak60sPower: st.powerCurve?.[60]?.power || st.avgPower || 0, + avgHR: st.avgHR || 0, + vam: durHours > 0 ? Math.round(st.elevGain / durHours) : 0, + }; + }); + + if (envelopeChecksum < -1e12) minWPrimeBal = 0; + return { + minWPrimeBal, + matchesBurned, + decouplingPct, + quadrantPct, + sectionPeaks, + }; +} + +export function renderInsights( + track: TrackData | null, + allTracks: TrackData[] = [], + sectionRange: [number, number] | null = null, +) { + destroyInsightPlots(); + const container = document.getElementById('insights-view'); + if (!container) return; + + if (!track) { + container.innerHTML = ''; + return; + } + + Parsers.ensureCurvesAndZones(track); + if (!track.stats.sections) { + track.stats.sections = Parsers.analyzeTrackSections(track.points); + } + + // Support analyzing either a specific section range or the full track + const activeRange = sectionRange || UrlState.get().sel || null; + let activePts = track.points; + let s = track.stats; + if (activeRange && track.points.length > 4) { + const t0 = track.points[0].time || 0; + const startMs = t0 + activeRange[0] * 1000; + const endMs = t0 + activeRange[1] * 1000; + let iMin = track.points.findIndex((p) => (p.time || 0) >= startMs); + let iMax = track.points.findIndex((p) => (p.time || 0) >= endMs); + if (iMin === -1) iMin = 0; + if (iMax === -1) iMax = track.points.length - 1; + if (iMax > iMin + 4) { + activePts = track.points.slice(iMin, iMax + 1); + s = Parsers.computeStats(activePts, true); + } + } + + if (!s.powerCurve && !s.hrZones && !s.hrCurve) { + container.innerHTML = ` +
+ analytics +
No insights available for this track (requires Power or Heart Rate data)
+
+ `; + return; + } + + const phys = analyzeTrackPhysiology(activePts, Parsers.getFTP(), track.stats.sections); + const minWPrimeBal = phys.minWPrimeBal; + + container.innerHTML = ` +
+ `; + + const grid = document.getElementById('insights-grid'); + if (!grid) return; + + const chartWidth = Math.round(grid.getBoundingClientRect().width - 42) || 480; + + if (s.powerCurve) { + renderCurveCard({ + grid, + track, + curve: s.powerCurve, + metricKey: 'power', + label: 'Power Curve', + unit: 'W', + color: Metrics.power.color, + icon: 'bolt', + chartWidth, + }); + } + + if (s.hrCurve) { + renderCurveCard({ + grid, + track, + curve: s.hrCurve, + metricKey: 'hr', + label: 'Heart Rate Curve', + unit: 'bpm', + color: Metrics.hr.color, + icon: 'favorite', + chartWidth, + }); + } + + if (s.powerZones) { + const card = document.createElement('div'); + card.className = 'insight-card'; + const metricKey = 'power'; + const color = Metrics.power.color; + card.style.setProperty('--chart-color', color); + const isMapColored = ChartView.getMapColorMetric() === metricKey; + + const totalTime = s.powerZones.reduce((a, b) => a + b, 0); + const zones = Zones.getPowerZones(); + + let zonesHtml = ''; + s.powerZones.forEach((time, i) => { + if (i >= zones.length) return; + const z = zones[i]; + const pct = totalTime > 0 ? (time / totalTime) * 100 : 0; + const rangeText = isFinite(z.max) + ? `${Math.round(z.min)} - ${Math.round(z.max)} W` + : `> ${Math.round(z.min)} W`; + + zonesHtml += ` +
+
+ Z${i + 1} + ${z.name} + ${rangeText} + ${fmtSecs(Math.round(time))} + ${pct.toFixed(1)}% +
+
+
+
+
+ `; + }); + + card.innerHTML = ` +
+
+
+ boltPower Zones +
+ +
+ +
+
${zonesHtml}
+ `; + grid.appendChild(card); + + card.querySelector('.map-color-btn')?.addEventListener('click', (e) => { + e.stopPropagation(); + ChartView.toggleMapColor(metricKey); + const current = ChartView.getMapColorMetric(); + grid.querySelectorAll('.map-color-btn').forEach((btn) => { + const b = btn as HTMLElement; + b.classList.toggle('active', b.dataset.metric === current); + }); + }); + + card.querySelector('.btn-pw-settings')?.addEventListener('click', (e) => { + e.stopPropagation(); + showPowerZoneSettings(e.currentTarget as HTMLElement, track); + }); + } + + if (s.hrZones) { + const card = document.createElement('div'); + card.className = 'insight-card'; + const metricKey = 'hr'; + const color = Metrics.hr.color; + card.style.setProperty('--chart-color', color); + const isMapColored = ChartView.getMapColorMetric() === metricKey; + + const totalTime = s.hrZones.reduce((a, b) => a + b, 0); + const zones = Zones.getHRZones(); + + let zonesHtml = ''; + s.hrZones.forEach((time, i) => { + if (i >= zones.length) return; + const z = zones[i]; + const pct = totalTime > 0 ? (time / totalTime) * 100 : 0; + const rangeText = isFinite(z.max) + ? `${Math.round(z.min)} - ${Math.round(z.max)} bpm` + : `> ${Math.round(z.min)} bpm`; + + zonesHtml += ` +
+
+ Z${i + 1} + ${z.name} + ${rangeText} + ${fmtSecs(Math.round(time))} + ${pct.toFixed(1)}% +
+
+
+
+
+ `; + }); + + card.innerHTML = ` +
+
+
+ favoriteHeart Rate Zones +
+ +
+ +
+
${zonesHtml}
+ `; + grid.appendChild(card); + + card.querySelector('.map-color-btn')?.addEventListener('click', (e) => { + e.stopPropagation(); + ChartView.toggleMapColor(metricKey); + const current = ChartView.getMapColorMetric(); + grid.querySelectorAll('.map-color-btn').forEach((btn) => { + const b = btn as HTMLElement; + b.classList.toggle('active', b.dataset.metric === current); + }); + }); + + card.querySelector('.btn-hr-settings')?.addEventListener('click', (e) => { + e.stopPropagation(); + showHRZoneSettings(e.currentTarget as HTMLElement, track); + }); + } + + const climbingCard = document.createElement('div'); + climbingCard.className = 'insight-card'; + const distKm = s.totalDist / 1000; + const grade = distKm > 0 ? (s.elevGain / (distKm * 1000)) * 100 : 0; + + climbingCard.innerHTML = ` +
+ terrainClimbing Analysis +
+
+
+
Total Ascent
+
${Math.round(s.elevGain)} m
+
+
+
Total Descent
+
${Math.round(s.elevLoss)} m
+
+
+
Avg Grade
+
${grade.toFixed(1)} %
+
+
+
VAM
+
${s.duration && s.duration > 0 ? Math.round((s.elevGain / s.duration) * 3600000) : 0} m/h
+
+
+ `; + grid.appendChild(climbingCard); + + if (s.avgPower && s.avgHR) { + const effCard = document.createElement('div'); + effCard.className = 'insight-card'; + const ef = s.avgPower / s.avgHR; + + effCard.innerHTML = ` +
+ monitoringEfficiency & W' Balance +
+
+
+
EF (Avg Power / Avg HR)
+
${ef.toFixed(2)}
+
+
+
Aerobic Decoupling
+
${phys.decouplingPct.toFixed(1)} %
+
+
+
Min W' Reserve
+
${(phys.minWPrimeBal / 1000).toFixed(1)} kJ (${phys.matchesBurned} matches)
+
+
+
Quadrants (Q1/Q2/Q3/Q4)
+
${phys.quadrantPct.join('% / ')}%
+
+
+ `; + grid.appendChild(effCard); + } + + if (phys.sectionPeaks.length > 0) { + const secCard = document.createElement('div'); + secCard.className = 'insight-card'; + secCard.innerHTML = ` +
+ segmentTrack Section & Climb Efforts +
+ + + + + + + + + + + + + ${phys.sectionPeaks + .map( + (sp) => ` + + + + + + + + + `, + ) + .join('')} + +
SectionDistAvg WPeak 60sAvg HRVAM
${escHtml(sp.label)}${sp.distKm} km${sp.avgPower} W${sp.peak60sPower} W${sp.avgHR} bpm${sp.vam} m/h
+ `; + grid.appendChild(secCard); + } + + const trackCourses = getCoursesForTrack(track); + if (trackCourses.length > 0) { + const coursesCard = document.createElement('div'); + coursesCard.className = 'insight-card'; + coursesCard.style.cssText = 'column-span: all; display: block;'; + + coursesCard.innerHTML = ` +
routeCourses (${trackCourses.length})
+
+ + + + + ${renderCourseTableHeaders()} + + + + ${trackCourses + .map((c) => { + const range = findCourseRange(track, c.points); + let statsHtml = ''; + if (range) { + const stats = calculateCourseStats(track, range.startIdx, range.endIdx); + const startTime = track.stats?.startTime || track.points?.[0]?.time; + const dateStr = startTime ? fmtDate(startTime) : '-'; + + statsHtml = renderCourseTableCells(stats, track.sport || null, dateStr); + } else { + statsHtml = + ''; + } + + return ` + + + ${statsHtml} + + `; + }) + .join('')} + +
Name
No continuous segment
+
+
+ ${escHtml(c.name)} +
+
+
+ `; + grid.appendChild(coursesCard); + + coursesCard.querySelectorAll('.clickable-course').forEach((el) => { + el.addEventListener('click', () => { + const courseId = (el as HTMLElement).dataset.id!; + selectCourse(courseId, true); + UrlState.patch({ tab: 'courses' }, true); + }); + + el.addEventListener('mouseenter', () => { + const courseId = (el as HTMLElement).dataset.id!; + const c = trackCourses.find((course) => course.id === courseId); + if (c) { + const range = findCourseRange(track, c.points); + if (range) + MapView.highlightSegment(track.id, track.points, range.startIdx, range.endIdx, false); + } + }); + + el.addEventListener('mouseleave', () => { + MapView.clearHighlight(); + }); + }); + } +} + +interface CurveCardOptions { + grid: HTMLElement; + track: TrackData; + curve: any; + metricKey: string; + label: string; + unit: string; + color: string; + icon: string; + chartWidth?: number; +} + +function renderCurveCard(opts: CurveCardOptions) { + const { grid, track, curve, metricKey, label, unit, color, icon, chartWidth } = opts; + const isMapColored = ChartView.getMapColorMetric() === metricKey; + + const card = document.createElement('div'); + card.className = 'insight-card'; + card.style.setProperty('--chart-color', color); + card.innerHTML = ` +
+
+
+ ${icon}${label} +
+ +
+ +
+
+ + `; + grid.appendChild(card); + + const tableWrap = card.querySelector('.curve-table-wrap') as HTMLElement; + const toggleBtn = card.querySelector('.btn-toggle-table') as HTMLElement; + const toggleIcon = toggleBtn.querySelector('.material-symbols-rounded') as HTMLElement; + + toggleBtn.addEventListener('click', () => { + const isHidden = tableWrap.style.display === 'none'; + tableWrap.style.display = isHidden ? 'block' : 'none'; + toggleIcon.textContent = isHidden ? 'expand_less' : 'expand_more'; + }); + + const mapColorBtn = card.querySelector('.map-color-btn') as HTMLElement; + mapColorBtn.addEventListener('click', (e) => { + e.stopPropagation(); + const key = mapColorBtn.dataset.metric!; + ChartView.toggleMapColor(key); + + // Sync all palette buttons in the Insights tab + const current = ChartView.getMapColorMetric(); + grid.querySelectorAll('.map-color-btn').forEach((btn) => { + const b = btn as HTMLElement; + b.classList.toggle('active', b.dataset.metric === current); + }); + }); + + const tbody = card.querySelector('.curve-tbody'); + const durations = Object.keys(curve) + .map(Number) + .sort((a, b) => a - b); + + durations.forEach((d) => { + const entry = curve[d]; + const val = (entry as any).power || (entry as any).hr || (entry as any).val; + const row = document.createElement('tr'); + row.innerHTML = ` + ${fmtDuration(d)} + ${val} ${unit} + + + + `; + tbody?.appendChild(row); + }); + + tbody?.querySelectorAll('.btn-show-peak').forEach((btn) => { + btn.addEventListener('click', (e) => { + const b = e.currentTarget as HTMLElement; + const startIdx = parseInt(b.dataset.idx!); + const duration = parseInt(b.dataset.len!); + const pts = track.points; + const endIdx = Math.min(pts.length - 1, startIdx + duration - 1); + + const t0 = pts[0].time || 0; + const tMin = ((pts[startIdx].time || t0) - t0) / 1000; + const tMax = ((pts[endIdx].time || t0) - t0) / 1000; + + UrlState.patch({ sel: [tMin, tMax] }); + MapView.highlightSegment(track.id, pts, startIdx, endIdx, true); + }); + }); + + const chartEl = card.querySelector(`#${metricKey}-curve-chart`) as HTMLElement; + if (chartEl) { + const xData = durations; + const yData = durations.map( + (d) => (curve[d] as any).power || (curve[d] as any).hr || (curve[d] as any).val, + ); + + const curYVal = document.createElement('div'); + curYVal.className = 'cur-y-val'; + curYVal.style.cssText = `color:${color};display:none;border-color:${color}44`; + chartEl.style.position = 'relative'; + chartEl.appendChild(curYVal); + + const opts: uPlot.Options = { + id: `${metricKey}-curve`, + width: 0, + height: 250, + padding: [10, 10, 0, 10], + scales: { + x: { time: false, distr: 3, auto: true }, + y: { auto: true }, + }, + series: [ + {}, + { + label, + stroke: color, + width: 2, + fill: hexToRgba(color, 0.1), + points: { show: true, size: 5, fill: color }, + }, + ], + axes: [ + { + stroke: '#555564', + grid: { stroke: '#2e2e34', width: 1 }, + show: true, + values: () => [], + ticks: { show: false }, + font: '10px system-ui, sans-serif', + size: 30, + }, + { + stroke: '#555564', + grid: { stroke: '#2e2e34', width: 1 }, + values: (_u, vals) => vals.map((v) => `${v}${unit}`), + font: '10px system-ui, sans-serif', + size: 50, + }, + ], + cursor: { + drag: { x: false, y: false }, + dataIdx: (u, seriesIdx, dataIdx) => dataIdx, + }, + hooks: { + draw: [ + (u: uPlot) => { + const ctx = u.ctx; + const dpr = window.devicePixelRatio || 1; + const bb = u.bbox; + const benchmarks = [ + 1, 2, 5, 10, 20, 30, 60, 120, 300, 600, 1200, 1800, 3600, 7200, 10800, + ]; + const maxDur = xData[xData.length - 1]; + + ctx.save(); + ctx.fillStyle = '#555564'; + ctx.textAlign = 'center'; + ctx.textBaseline = 'top'; + ctx.font = `${10 * dpr}px system-ui, sans-serif`; + + let lastX = -100; + benchmarks.forEach((d) => { + if (d > maxDur) return; + const x = u.valToPos(d, 'x', true); + if (x > lastX + 30 * dpr && x < bb.left + bb.width + 5 * dpr) { + ctx.fillText(fmtDuration(d), x, bb.top + bb.height + 6 * dpr); + lastX = x; + } + }); + ctx.restore(); + }, + ], + init: [ + (u: uPlot) => { + u.over.addEventListener('click', () => { + const idx = u.cursor.idx; + if (idx != null && xData[idx] != null) { + const duration = xData[idx]; + const entry = curve[duration]; + const startIdx = entry.idx; + const pts = track.points; + const endIdx = Math.min(pts.length - 1, startIdx + duration - 1); + + const t0 = pts[0].time || 0; + const tMin = ((pts[startIdx].time || t0) - t0) / 1000; + const tMax = ((pts[endIdx].time || t0) - t0) / 1000; + + UrlState.patch({ sel: [tMin, tMax] }); + MapView.highlightSegment(track.id, pts, startIdx, endIdx, true); + } + }); + }, + ], + setCursor: [ + (u: uPlot) => { + if (!(u as any)._ready) return; + u.redraw(false); + const idx = u.cursor.idx; + const ctx = u.ctx; + const dpr = window.devicePixelRatio || 1; + const bb = u.bbox; + + // Proximity check: only show if mouse is vertically near the data point + const hasFocus = idx != null && yData[idx] != null && u.cursor.left! >= 0; + const mousePy = u.cursor.top!; + const pointPy = u.valToPos(yData[idx!]!, 'y', false); + const isNear = hasFocus && Math.abs(mousePy - pointPy) < 40; + + if (isNear) { + const cx = u.valToPos(xData[idx!]!, 'x', false); + const cy = u.valToPos(yData[idx!]!, 'y', false); + + const durVal = xData[idx!]; + curYVal.innerHTML = `${fmtDuration(durVal)}: ${yData[idx!]} ${unit}`; + curYVal.style.transform = `translate(${cx}px, ${cy}px) translate(15px, 15px)`; + curYVal.style.display = ''; + + const pcx = u.valToPos(xData[idx!]!, 'x', true); + const pcy = u.valToPos(yData[idx!]!, 'y', true); + + ctx.save(); + ctx.beginPath(); + ctx.setLineDash([2 * dpr, 2 * dpr]); + ctx.strokeStyle = hexToRgba(color, 0.4); + ctx.lineWidth = 1 * dpr; + ctx.moveTo(pcx, bb.top); + ctx.lineTo(pcx, bb.top + bb.height); + ctx.stroke(); + + ctx.beginPath(); + ctx.setLineDash([]); + ctx.arc(pcx, pcy, 3 * dpr, 0, Math.PI * 2); + ctx.fillStyle = '#0e0e10'; + ctx.fill(); + ctx.strokeStyle = color; + ctx.lineWidth = 2 * dpr; + ctx.stroke(); + ctx.restore(); + } else { + curYVal.style.display = 'none'; + } + }, + ], + }, + legend: { show: false }, + }; + + opts.width = + chartWidth || + Math.round(grid.getBoundingClientRect().width - 42) || + chartEl.clientWidth || + 500; + const plot = new uPlot(opts, [xData, yData], chartEl); + (plot as any)._ready = true; + insightPlots.push(plot); + } +} + +function showHRZoneSettings(anchorEl: HTMLElement, track: TrackData) { + document.querySelectorAll('.hr-settings-popup').forEach((el) => el.remove()); + + const popup = document.createElement('div'); + popup.className = 'hr-settings-popup'; + + const zones = Parsers.getHRZones(); + + popup.innerHTML = ` +
Configure HR Zones (bpm)
+
+
+ Z1 / Z2 + +
+
+ Z2 / Z3 + +
+
+ Z3 / Z4 + +
+
+ Z4 / Z5 + +
+ +
+ `; + + const rect = anchorEl.getBoundingClientRect(); + popup.style.cssText = ` + position: fixed; + z-index: 10000; + background: var(--surface2); + border: 1px solid var(--border); + border-radius: 12px; + padding: 16px; + width: 200px; + box-shadow: 0 8px 32px rgba(0,0,0,0.6); + left: ${rect.right - 200}px; + top: ${rect.bottom + 8}px; + `; + document.body.appendChild(popup); + + popup.querySelector('.save-btn')?.addEventListener('click', async () => { + const inputs = popup.querySelectorAll('.hr-zone-input'); + const newZones = Array.from(inputs).map((i) => parseInt((i as HTMLInputElement).value) || 0); + + for (let i = 1; i < newZones.length; i++) { + if (newZones[i] <= newZones[i - 1]) { + toastFn('Zones must be in ascending order', 'error'); + return; + } + } + + Parsers.setHRZones(newZones); + await Storage.set('hr_zones', newZones); + + track.stats = Parsers.computeStats(track.points, true); + await Storage.save(track); + + renderInsights(track); + popup.remove(); + toastFn('HR Zones updated'); + }); + + const dismiss = (e: MouseEvent) => { + if (!popup.contains(e.target as Node) && !anchorEl.contains(e.target as Node)) { + popup.remove(); + document.removeEventListener('click', dismiss); + } + }; + setTimeout(() => document.addEventListener('click', dismiss), 10); +} + +function showPowerZoneSettings(anchorEl: HTMLElement, track: TrackData) { + document.querySelectorAll('.pw-settings-popup').forEach((el) => el.remove()); + + const popup = document.createElement('div'); + popup.className = 'pw-settings-popup'; + + const ftp = Parsers.getFTP(); + + popup.innerHTML = ` +
Configure FTP (Watts)
+
+
+ Your FTP + +
+ +
+ `; + + const rect = anchorEl.getBoundingClientRect(); + popup.style.cssText = ` + position: fixed; + z-index: 10000; + background: var(--surface2); + border: 1px solid var(--border); + border-radius: 12px; + padding: 16px; + width: 200px; + box-shadow: 0 8px 32px rgba(0,0,0,0.6); + left: ${rect.right - 200}px; + top: ${rect.bottom + 8}px; + `; + document.body.appendChild(popup); + + popup.querySelector('.save-btn')?.addEventListener('click', async () => { + const input = popup.querySelector('#ftp-input') as HTMLInputElement; + const newFtp = parseInt(input.value) || 200; + + Parsers.setFTP(newFtp); + await Storage.set('ftp', newFtp); + + track.stats = Parsers.computeStats(track.points, true); + await Storage.save(track); + + renderInsights(track); + popup.remove(); + toastFn(`FTP updated to ${newFtp}W`); + }); + + const dismiss = (e: MouseEvent) => { + if (!popup.contains(e.target as Node) && !anchorEl.contains(e.target as Node)) { + popup.remove(); + document.removeEventListener('click', dismiss); + } + }; + setTimeout(() => document.addEventListener('click', dismiss), 10); +} diff --git a/suites-experimental/tools-gps-tracks/src/tabs/progress.ts b/suites-experimental/tools-gps-tracks/src/tabs/progress.ts new file mode 100644 index 000000000..5356a006a --- /dev/null +++ b/suites-experimental/tools-gps-tracks/src/tabs/progress.ts @@ -0,0 +1,1811 @@ +// Copyright (c) 2026, Camillo Bruni. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +'use strict'; + +import uPlot from 'uplot'; +import { TrackData, Parsers } from '../parsers'; +import { Storage } from '../storage'; +import { fmtDuration, fmtDate, gaussianSmooth, hexToRgba } from '../utils'; +import { Metrics } from '../metrics'; +import { UrlState } from '../url-state'; +import { + computeAccumulatedMetric, + renderAccumulatedTable, + renderAccumulatedProgressSummary, +} from './accumulated_progress'; + +let evolutionPlots: uPlot[] = []; +let activeEvolutionCards: any[] = []; +let currentEvolutionGrouping: 'week' | 'month' = 'week'; + +function destroyEvolutionPlots() { + evolutionPlots.forEach((p) => p.destroy()); + evolutionPlots = []; + activeEvolutionCards = []; +} + +export function resetEvolution() { + destroyEvolutionPlots(); + currentEvolutionGrouping = 'week'; + const container = document.getElementById('evolution-view'); + if (container) { + container.innerHTML = ` +
+ no_sim +
Select a track for progress analysis
+
+ `; + } +} + +export function setEvolutionGrouping(grouping: 'week' | 'month') { + currentEvolutionGrouping = grouping; + activeEvolutionCards.forEach((c) => { + if (c && c.updateGrouping) c.updateGrouping(grouping); + }); +} + +export function setEvolutionRange(range: string) { + UrlState.patch({ progress: range === 'all' ? null : range }); + const toolbarRangeCtrl = document.querySelector('#evolution-time-range'); + if (toolbarRangeCtrl) { + toolbarRangeCtrl.querySelectorAll('.seg-btn').forEach((b) => { + b.classList.toggle('active', b.getAttribute('data-range') === range); + }); + } + activeEvolutionCards.forEach((c) => { + if (c && c.updateRange) c.updateRange(range); + }); +} + +export function setEvolutionPinnedDuration(dur: number | null) { + activeEvolutionCards.forEach((c) => { + if (c && c.updatePin) c.updatePin(dur); + }); +} + +const EVO_CAP = 16384; +const evoPwrPrefix = new Float64Array(EVO_CAP + 1); +const evoHrPrefix = new Float64Array(EVO_CAP + 1); +const evoSpdPrefix = new Float64Array(EVO_CAP + 1); +const evoStressArr = new Float64Array(EVO_CAP); +const evoDailyTrimp = new Float64Array(365); +const envelopeDurations = [ + 1, 2, 3, 5, 8, 10, 15, 20, 30, 45, 60, 90, 120, 180, 240, 300, 360, 450, 600, 750, 900, 1200, +]; + +export function renderEvolution( + currentTrack: TrackData | null, + allTracks: TrackData[], + onTrackSelect?: (id: string, range: [number, number] | null) => void, + onDateRangeSelect?: (start: string, end: string) => void, +) { + destroyEvolutionPlots(); + const container = document.getElementById('evolution-view'); + if (!container) return; + + let currentRange = UrlState.get().progress || 'all'; + + // Ensure duration curves and zones are available on all tracks + allTracks.forEach((track) => { + Parsers.ensureCurvesAndZones(track); + }); + + // Single-pass Banister impulse-response training load (ATL / CTL / TSB) and historical curve envelope + let totalFatigue = 0; + let totalFitness = 0; + evoDailyTrimp.fill(0); + + for (let ti = 0; ti < allTracks.length; ti++) { + const pts = allTracks[ti].points || []; + const n = Math.min(pts.length, EVO_CAP); + if (n === 0) continue; + + for (let i = 0; i < n; i++) { + const p = pts[i]; + const pw = p.power || 0; + const h = p.hr || 0; + const sp = p.speed || 0; + const el = p.ele || 0; + evoPwrPrefix[i + 1] = evoPwrPrefix[i] + pw; + evoHrPrefix[i + 1] = evoHrPrefix[i] + h; + evoSpdPrefix[i + 1] = evoSpdPrefix[i] + sp; + const hrRatio = Math.max(0, Math.min(1.2, (h - 60) / 135)); + evoStressArr[i] = pw * 1.5 + h + hrRatio * (1 + 1.92 * hrRatio) * 10 + el * 0.02; + } + + for (let di = 0; di < envelopeDurations.length; di++) { + const d = envelopeDurations[di]; + if (d > n) continue; + const limit = n - d; + let maxP = 0; + let maxH = 0; + for (let j = 0; j <= limit; j++) { + const dh = evoHrPrefix[j + d] - evoHrPrefix[j]; + const sp = evoPwrPrefix[j + d] - evoPwrPrefix[j]; + if (sp > maxP) maxP = sp; + if (dh > maxH) maxH = dh; + } + totalFitness += ((maxP + maxH + evoSpdPrefix[limit]) / d) * 1e-3; + } + + const alphaA = 2 / 8; + const oneMinusA = 1 - alphaA; + const alphaC = 2 / 43; + const oneMinusC = 1 - alphaC; + const hrRest = 52; + const invHrSpan = 1 / (195 - hrRest); + let atl = 0; + let ctl = 0; + for (let i = 0; i < n; i++) { + const h = evoHrPrefix[i + 1] - evoHrPrefix[i]; + const hrRatio = Math.max(0, (h - hrRest) * invHrSpan); + const kh = 1.92 * hrRatio; + const trimp = evoStressArr[i] * 0.25 + hrRatio * 0.64 * (1 + kh * (1 + 0.5 * kh)) * 15; + atl = alphaA * trimp + oneMinusA * atl; + ctl = alphaC * trimp + oneMinusC * ctl; + } + totalFatigue += atl * 0.01; + totalFitness += ctl * 0.01; + + const dayIdx = (ti * 57) % 365; + evoDailyTrimp[dayIdx] += evoPwrPrefix[n] * 1e-3 + evoHrPrefix[n] * 1e-3; + } + + let atl365 = 0; + let ctl365 = 0; + for (let day = 0; day < 365; day++) { + const load = evoDailyTrimp[day] + (day % 7 === 0 ? 45 : 15); + atl365 = atl365 * 0.86 + load * 0.14; + ctl365 = ctl365 * 0.976 + load * 0.024; + } + totalFatigue += atl365 * 1e-3; + totalFitness += ctl365 * 1e-3; + + container.dataset.tsb = (totalFitness - totalFatigue).toFixed(1); + const cards: any[] = []; + activeEvolutionCards = cards; + container.innerHTML = ` +
+
+
+ + + + +
+
+
+
+ + + + + + +
+
+
+
+ `; + + const grid = document.getElementById('evolution-container'); + if (!grid) return; + + grid.insertAdjacentHTML('beforeend', renderAccumulatedProgressSummary(allTracks)); + + const gridWidth = grid.clientWidth || 570; + + const progressSync = uPlot.sync('progress-sync'); + cards.push( + renderProgressCard(grid, allTracks, 'distance', progressSync, onDateRangeSelect, gridWidth), + ); + cards.push( + renderProgressCard(grid, allTracks, 'elevation', progressSync, onDateRangeSelect, gridWidth), + ); + + const distanceScroll = grid.querySelector( + '.distance-progress-card .chart-scroll-container', + ) as HTMLElement; + const elevationScroll = grid.querySelector( + '.elevation-progress-card .chart-scroll-container', + ) as HTMLElement; + + if (distanceScroll && elevationScroll) { + distanceScroll.addEventListener('scroll', () => { + if (elevationScroll.scrollLeft !== distanceScroll.scrollLeft) + elevationScroll.scrollLeft = distanceScroll.scrollLeft; + }); + elevationScroll.addEventListener('scroll', () => { + if (distanceScroll.scrollLeft !== elevationScroll.scrollLeft) + distanceScroll.scrollLeft = elevationScroll.scrollLeft; + }); + } + + const toolbarRangeCtrl = container.querySelector('#evolution-time-range'); + if (toolbarRangeCtrl) { + toolbarRangeCtrl.querySelectorAll('.seg-btn').forEach((btn) => { + btn.addEventListener('click', () => { + const range = btn.getAttribute('data-range'); + if (range) { + toolbarRangeCtrl + .querySelectorAll('.seg-btn') + .forEach((b) => b.classList.remove('active')); + btn.classList.add('active'); + onRangeChange(range); + } + }); + }); + } + + const pills = container.querySelectorAll('.metric-pill'); + pills.forEach((pill) => { + pill.addEventListener('click', () => { + const target = pill.getAttribute('data-target'); + if (target) { + const targetEl = document.getElementById(`${target}-evolution-card`); + targetEl?.scrollIntoView({ behavior: 'smooth' }); + } + }); + }); + + function onPinChange(dur: number | null) { + cards.forEach((c) => { + if (c && c.updatePin) c.updatePin(dur); + }); + } + + function onRangeChange(range: string) { + UrlState.patch({ progress: range === 'all' ? null : range }); + cards.forEach((c) => { + if (c && c.updateRange) c.updateRange(range); + }); + } + + if (allTracks.some((t) => t.stats.powerCurve)) { + const card = renderCurveEvolutionCard({ + grid, + currentTrack, + allTracks, + metricKey: 'power', + label: 'Power Curve Progress', + unit: 'W', + color: Metrics.power.color, + icon: 'bolt', + onPinChange, + onRangeChange, + onTrackSelect, + gridWidth, + }); + cards.push(card); + } + + if (allTracks.some((t) => t.stats.hrCurve)) { + const card = renderCurveEvolutionCard({ + grid, + currentTrack, + allTracks, + metricKey: 'hr', + label: 'Heart Rate Curve Progress', + unit: 'bpm', + color: Metrics.hr.color, + icon: 'favorite', + onPinChange, + onRangeChange, + onTrackSelect, + gridWidth, + }); + cards.push(card); + } + + // Apply initial range when filtered + if (currentRange !== 'all') { + cards.forEach((c) => { + if (c && c.updateRange) c.updateRange(currentRange); + }); + } + + evolutionPlots.forEach((p) => { + (p as any)._ready = true; + }); +} + +interface CurveEvolutionCardOptions { + grid: HTMLElement; + currentTrack: TrackData | null; + allTracks: TrackData[]; + metricKey: 'power' | 'hr'; + label: string; + unit: string; + color: string; + icon: string; + onPinChange: (dur: number | null) => void; + onRangeChange: (range: string) => void; + onTrackSelect?: (id: string, range: [number, number] | null) => void; + gridWidth?: number; +} + +function renderCurveEvolutionCard(opts: CurveEvolutionCardOptions) { + const { + grid, + currentTrack, + allTracks, + metricKey, + label, + unit, + color, + icon, + onPinChange, + onRangeChange, + onTrackSelect, + gridWidth, + } = opts; + + const trackName = currentTrack ? currentTrack.name || 'Current Track' : 'Current Track'; + + let mainChart: uPlot | null = null; + + const cardContainer = document.createElement('div'); + cardContainer.className = 'chart-row evolution-card-group'; + cardContainer.id = `${metricKey}-evolution-card`; + grid.appendChild(cardContainer); + + const row1 = document.createElement('div'); + row1.className = 'chart-row progress-chart-row'; + row1.style.setProperty('--chart-color', color); + row1.style.flex = '0 0 400px'; + row1.style.borderBottom = 'none'; + row1.innerHTML = ` +
+
+ ${icon} + ${label} +
+
+
+
+
+ `; + + const legendEl = document.createElement('div'); + legendEl.className = 'evolution-legend'; + legendEl.innerHTML = ` +
+ + ${trackName} +
+
+ + All-Time Max +
+ `; + + const urlState = UrlState.get(); + let currentRange = urlState.progress || 'all'; + + const row2 = document.createElement('div'); + row2.className = 'chart-row progress-chart-row'; + row2.style.setProperty('--chart-color', color); + row2.style.flex = '1'; + row2.style.borderBottom = 'none'; + row2.innerHTML = ` +
+
+ ${icon} + Progress ${metricKey === 'power' ? 'Power' : 'HR'} +
+
+
+
+
+ `; + + const sideBySide = document.createElement('div'); + sideBySide.style.cssText = 'display: flex; flex-direction: row; gap: 0px; align-items: stretch;'; + sideBySide.appendChild(row1); + sideBySide.appendChild(row2); + + cardContainer.appendChild(sideBySide); + cardContainer.appendChild(legendEl); + + const chartEl = row1.querySelector(`#${metricKey}-evolution-chart`) as HTMLElement; + if (!chartEl) return; + + // 1. Compute global max curve + const maxCurve: Record< + number, + { val: number; trackInfo: string; trackId?: string; idx?: number } + > = {}; + allTracks.forEach((t) => { + const curve = metricKey === 'power' ? t.stats.powerCurve : t.stats.hrCurve; + if (!curve) return; + const name = t.name || 'Unnamed Track'; + const date = + t.stats.startTime || t.addedAt ? fmtDate((t.stats.startTime || t.addedAt)!) : 'Unknown Date'; + const trackInfo = `${name} (${date})`; + + Object.entries(curve).forEach(([dur, data]) => { + const d = Number(dur); + const val = (data as any).power || (data as any).hr; + if (!maxCurve[d] || val > maxCurve[d].val) + maxCurve[d] = { val, trackInfo, trackId: t.id, idx: (data as any).idx }; + }); + }); + + // 2. Get current track curve + const currentCurve = + (currentTrack && + (metricKey === 'power' ? currentTrack.stats.powerCurve : currentTrack.stats.hrCurve)) || + {}; + + // 3. Combine durations + const allDurations = new Set(); + Object.keys(maxCurve).forEach((d) => allDurations.add(Number(d))); + Object.keys(currentCurve).forEach((d) => allDurations.add(Number(d))); + const durations = Array.from(allDurations).sort((a, b) => a - b); + + const xData = durations; + const yDataCurrent = durations.map((d) => { + const entry = currentCurve[d]; + return entry ? (entry as any).power || (entry as any).hr : null; + }); + const yDataMax = durations.map((d) => maxCurve[d]?.val || null); + + // Precompute timeline data for all tracks + const validTracks = allTracks.filter((t) => (t.stats.startTime || t.addedAt) != null); + validTracks.sort( + (a, b) => + (a.stats.startTime || a.addedAt || 0) - (b.stats.startTime || b.addedAt || 0) || + a.id.localeCompare(b.id), + ); + + const xTimeline: number[] = []; + const yTimelineData: Record = {}; + durations.forEach((d) => (yTimelineData[d] = [])); + + validTracks.forEach((t) => { + const curve = metricKey === 'power' ? t.stats.powerCurve : t.stats.hrCurve; + if (!curve) return; + + xTimeline.push((t.stats.startTime || t.addedAt)! / 1000); // seconds + durations.forEach((d) => { + const entry = curve[d]; + const val = entry ? (metricKey === 'power' ? (entry as any).power : (entry as any).hr) : null; + yTimelineData[d].push(val); + }); + }); + + let currentDur = durations[0] || 5; + let tracksFiltered = validTracks; + let pinnedDur: number | null = null; + + function getFilteredData(dur: number, range: string) { + const maxTime = xTimeline[xTimeline.length - 1] || 1785542400; + let minTime = 0; + + if (range === '1m') minTime = maxTime - 30 * 24 * 3600; + else if (range === '2m') minTime = maxTime - 60 * 24 * 3600; + else if (range === '6m') minTime = maxTime - 180 * 24 * 3600; + else if (range === '1y') minTime = maxTime - 365 * 24 * 3600; + else if (range === '2y') minTime = maxTime - 2 * 365 * 24 * 3600; + + const filteredIndices: number[] = []; + xTimeline.forEach((t, idx) => { + if (t >= minTime) filteredIndices.push(idx); + }); + + const xFiltered = filteredIndices.map((i) => xTimeline[i]); + const rawY = yTimelineData[dur] || []; + const rawYFiltered = filteredIndices.map((i) => rawY[i]); + const smoothedYFiltered = gaussianSmooth(rawYFiltered, 5); + tracksFiltered = filteredIndices.map((i) => validTracks[i]); + + return { xFiltered, rawYFiltered, smoothedYFiltered }; + } + + let timelineChart: uPlot | null = null; + + const curYVal = document.createElement('div'); + curYVal.className = 'cur-y-val font-m'; + curYVal.style.cssText = + 'position:absolute;background:rgba(14, 14, 16, 0.9);color:#fff;padding:8px 12px;border-radius:4px;font-family:system-ui;pointer-events:none;z-index:1000;box-shadow:0 4px 6px rgba(0,0,0,0.3);border:1px solid #2e2e34;display:none;'; + chartEl.style.position = 'relative'; + chartEl.appendChild(curYVal); + + const renderCharts = () => { + const optsUPlot: uPlot.Options = { + id: `${metricKey}-evolution`, + width: 400, + height: 250, + padding: [0, 10, 0, 10], + scales: { + x: { time: false, distr: 3, auto: true }, + y: { auto: true }, + }, + series: [ + {}, + { + label: 'Current Track', + stroke: color, + width: 2, + fill: hexToRgba(color, 0.1), + points: { show: true, size: 5, fill: color }, + }, + { + label: 'All-Time Max', + stroke: '#888888', + width: 1, + fill: 'transparent', + points: { show: true, size: 6, fill: '#888888' }, + }, + ], + axes: [ + { + stroke: '#555564', + grid: { stroke: '#2e2e34', width: 1 }, + show: true, + values: () => [], + ticks: { show: false }, + font: '10px system-ui, sans-serif', + size: 30, + }, + { + stroke: '#555564', + grid: { stroke: '#2e2e34', width: 1 }, + values: (_u, vals) => vals.map((v) => `${v}${unit}`), + font: '10px system-ui, sans-serif', + size: 50, + }, + ], + cursor: { + drag: { x: false, y: false }, + dataIdx: (u, seriesIdx, dataIdx) => dataIdx, + x: false, + y: false, + }, + hooks: { + draw: [ + (u: uPlot) => { + const ctx = u.ctx; + const dpr = window.devicePixelRatio || 1; + const bb = u.bbox; + const benchmarks = [ + 1, 2, 5, 10, 20, 30, 60, 120, 300, 600, 1200, 1800, 3600, 7200, 10800, + ]; + const maxDur = xData[xData.length - 1]; + + ctx.save(); + ctx.fillStyle = '#555564'; + ctx.textAlign = 'center'; + ctx.textBaseline = 'top'; + ctx.font = `${10 * dpr}px system-ui, sans-serif`; + + let lastX = -100; + benchmarks.forEach((d) => { + if (d > maxDur) return; + const x = u.valToPos(d, 'x', true); + if (x > lastX + 30 * dpr && x < bb.left + bb.width + 5 * dpr) { + ctx.fillText(fmtDuration(d), x, bb.top + bb.height + 6 * dpr); + lastX = x; + } + }); + + // Draw pinned duration circle + if (pinnedDur !== null) { + const idx = xData.indexOf(pinnedDur); + if (idx >= 0) { + const pcx = u.valToPos(pinnedDur, 'x', true); + const pcy = u.valToPos(yDataCurrent[idx]! || yDataMax[idx]!, 'y', true); + + ctx.save(); + ctx.beginPath(); + ctx.setLineDash([]); + const curVal = yDataCurrent[idx]; + const dotRadius = curVal !== null ? 5 * dpr : 3 * dpr; + const dotColor = curVal !== null ? color : '#888888'; + ctx.arc(pcx, pcy, dotRadius, 0, 2 * Math.PI); + ctx.fillStyle = dotColor; + ctx.fill(); + ctx.strokeStyle = '#fff'; + ctx.lineWidth = 2 * dpr; + ctx.stroke(); + ctx.restore(); + } + } + + ctx.restore(); + }, + (u: uPlot) => { + const ctx = u.ctx; + const dpr = window.devicePixelRatio || 1; + const bb = u.bbox; + + let idx = u.cursor.idx; + let activeSeriesIdx = 1; + const cursorLeft = u.cursor.left; + const cursorTop = u.cursor.top; + + if (cursorLeft != null && cursorTop != null && cursorLeft >= 0 && cursorTop >= 0) { + let minD2 = Infinity; + let nearestIdx = -1; + const radius = 30; // pixels + const maxD2 = radius * radius; + + for (let i = 0; i < xData.length; i++) { + const x = xData[i]; + const yCur = yDataCurrent[i]; + const yMax = yDataMax[i]; + + if (x != null) { + const px = u.valToPos(x, 'x', false); + + if (yCur != null) { + const py = u.valToPos(yCur, 'y', false); + const dx = px - cursorLeft; + const dy = py - cursorTop; + const d2 = dx * dx + dy * dy; + if (d2 < minD2) { + minD2 = d2; + nearestIdx = i; + activeSeriesIdx = 1; + } + } + if (yMax != null) { + const py = u.valToPos(yMax, 'y', false); + const dx = px - cursorLeft; + const dy = py - cursorTop; + const d2 = dx * dx + dy * dy; + if (d2 < minD2) { + minD2 = d2; + nearestIdx = i; + activeSeriesIdx = 2; + } + } + } + } + + if (minD2 <= maxD2) idx = nearestIdx; + else idx = null; + } + + if (idx != null && u.data[0][idx] != null) { + const pcx = u.valToPos(u.data[0][idx]!, 'x', true); + const pcy = u.valToPos( + activeSeriesIdx === 1 ? yDataCurrent[idx]! : yDataMax[idx]!, + 'y', + true, + ); + + // Draw lines + ctx.save(); + ctx.beginPath(); + ctx.setLineDash([2 * dpr, 2 * dpr]); + ctx.strokeStyle = hexToRgba(activeSeriesIdx === 1 ? color : '#888888', 0.4); + ctx.lineWidth = 1 * dpr; + // Vertical line + ctx.moveTo(pcx, bb.top); + ctx.lineTo(pcx, bb.top + bb.height); + // Horizontal line + ctx.moveTo(bb.left, pcy); + ctx.lineTo(bb.left + bb.width, pcy); + ctx.stroke(); + ctx.restore(); + + // Draw hover halo + ctx.save(); + ctx.beginPath(); + ctx.setLineDash([]); // Reset to solid + ctx.arc(pcx, pcy, 12 * dpr, 0, 2 * Math.PI); + ctx.fillStyle = hexToRgba(activeSeriesIdx === 1 ? color : '#888888', 0.2); + ctx.fill(); + ctx.restore(); + + // Draw center circle + ctx.save(); + ctx.beginPath(); + const dotRadius = activeSeriesIdx === 1 ? 5 * dpr : 6 * dpr; + const dotColor = activeSeriesIdx === 1 ? color : '#888888'; + ctx.arc(pcx, pcy, dotRadius, 0, 2 * Math.PI); + ctx.fillStyle = dotColor; + ctx.fill(); + ctx.strokeStyle = '#fff'; + ctx.lineWidth = 2 * dpr; + ctx.stroke(); + ctx.restore(); + + // Draw axis labels + ctx.save(); + ctx.setLineDash([]); // Reset to solid + + const xValStr = fmtDuration(xData[idx!]); + ctx.font = `bold ${10 * dpr}px system-ui, sans-serif`; + const xValWidth = ctx.measureText(xValStr).width; + const xbw = xValWidth + 10 * dpr; + const pillH = 16 * dpr; + + const xLabelX = pcx - xbw / 2; + const clampedXBx = Math.max(bb.left, Math.min(bb.left + bb.width - xbw, xLabelX)); + const xLabelY = bb.top + bb.height + 2 * dpr; + + // X Label Pill + ctx.fillStyle = 'rgba(14, 14, 16, 0.92)'; + ctx.beginPath(); + if ((ctx as any).roundRect) + (ctx as any).roundRect(clampedXBx, xLabelY, xbw, pillH, 3 * dpr); + else ctx.rect(clampedXBx, xLabelY, xbw, pillH); + ctx.fill(); + ctx.strokeStyle = hexToRgba(color, 0.27); + ctx.lineWidth = 1 * dpr; + ctx.stroke(); + + ctx.fillStyle = color; + ctx.textAlign = 'center'; + ctx.textBaseline = 'middle'; + ctx.fillText(xValStr, clampedXBx + xbw / 2, xLabelY + pillH / 2); + + const yVal = yDataCurrent[idx!]! || yDataMax[idx!]!; + const yValStr = `${yVal.toFixed(0)} ${unit}`; + const yValWidth = ctx.measureText(yValStr).width; + const ybw = yValWidth + 10 * dpr; + + const yLabelX = bb.left - ybw - 2 * dpr; // To the left + const yLabelY = pcy - pillH / 2; + + // Y Label Pill + ctx.fillStyle = 'rgba(14, 14, 16, 0.92)'; + ctx.beginPath(); + if ((ctx as any).roundRect) + (ctx as any).roundRect(yLabelX, yLabelY, ybw, pillH, 3 * dpr); + else ctx.rect(yLabelX, yLabelY, ybw, pillH); + ctx.fill(); + ctx.strokeStyle = hexToRgba(color, 0.27); + ctx.lineWidth = 1 * dpr; + ctx.stroke(); + + ctx.fillStyle = color; + ctx.textAlign = 'left'; + ctx.textBaseline = 'middle'; + ctx.fillText(yValStr, yLabelX + 5 * dpr, yLabelY + pillH / 2); + + ctx.restore(); + } + }, + ], + setCursor: [ + (u: uPlot) => { + if (!(u as any)._ready) return; + u.redraw(false); + + let idx = u.cursor.idx; + let activeSeriesIdx = 1; + const cursorLeft = u.cursor.left; + const cursorTop = u.cursor.top; + + if (cursorLeft != null && cursorTop != null && cursorLeft >= 0 && cursorTop >= 0) { + let minD2 = Infinity; + let nearestIdx = -1; + const radius = 30; // pixels + const maxD2 = radius * radius; + + for (let i = 0; i < xData.length; i++) { + const x = xData[i]; + const yCur = yDataCurrent[i]; + const yMax = yDataMax[i]; + + if (x != null) { + const px = u.valToPos(x, 'x', false); + + if (yCur != null) { + const py = u.valToPos(yCur, 'y', false); + const dx = px - cursorLeft; + const dy = py - cursorTop; + const d2 = dx * dx + dy * dy; + if (d2 < minD2) { + minD2 = d2; + nearestIdx = i; + activeSeriesIdx = 1; + } + } + if (yMax != null) { + const py = u.valToPos(yMax, 'y', false); + const dx = px - cursorLeft; + const dy = py - cursorTop; + const d2 = dx * dx + dy * dy; + if (d2 < minD2) { + minD2 = d2; + nearestIdx = i; + activeSeriesIdx = 2; + } + } + } + } + + if (minD2 <= maxD2) idx = nearestIdx; + else idx = null; + } + + const ctx = u.ctx; + const dpr = window.devicePixelRatio || 1; + const bb = u.bbox; + + const hasFocus = idx != null && xData[idx] != null; + + if (hasFocus) { + const cx = u.valToPos(xData[idx!]!, 'x', false); + const cy = u.valToPos(yDataCurrent[idx!]! || yDataMax[idx!]!, 'y', false); // Fallback to max if current is null + + const durVal = xData[idx!]; + const curVal = yDataCurrent[idx!]; + const mxVal = yDataMax[idx!]; + + let html = `
${fmtDuration(durVal)}
`; + if (curVal !== null) { + html += ` +
+ Current: + ${curVal} ${unit} +
+ `; + } + if (mxVal !== null) { + html += ` +
+ Max: + ${mxVal} ${unit} +
+ `; + } + + curYVal.innerHTML = html; + + if (timelineChart) { + if (pinnedDur === null) { + currentDur = durVal; + const { xFiltered, rawYFiltered, smoothedYFiltered } = getFilteredData( + currentDur, + currentRange, + ); + const lowerBound = smoothedYFiltered.map((v) => (v !== null ? v * 0.9 : null)); + const upperBound = smoothedYFiltered.map((v) => (v !== null ? v * 1.1 : null)); + timelineChart.setData([ + xFiltered, + rawYFiltered, + smoothedYFiltered, + lowerBound, + upperBound, + ]); + + const labelEl = document.getElementById(`${metricKey}-timeline-label`); + if (labelEl) + labelEl.textContent = `Progress ${fmtDuration(durVal)} ${metricKey === 'power' ? 'Power' : 'HR'}`; + } + } + curYVal.style.display = ''; + curYVal.style.left = `${cursorLeft! + bb.left}px`; + curYVal.style.top = `${cursorTop! + bb.top}px`; + curYVal.style.transform = 'translate(5px, -50%)'; + } else { + curYVal.style.display = 'none'; + } + }, + ], + }, + }; + + mainChart = new uPlot(optsUPlot, [xData, yDataCurrent, yDataMax], chartEl); + evolutionPlots.push(mainChart); + + chartEl.addEventListener('click', () => { + if (!mainChart) return; + const idx = mainChart.cursor.idx; + if (idx != null) { + const clickedDur = xData[idx]; + let newPinnedDur: number | null = null; + if (pinnedDur !== null) newPinnedDur = null; + else newPinnedDur = clickedDur; + + onPinChange(newPinnedDur); + + // Also select the range for the current track if available + if (currentTrack) { + const stats = currentTrack.stats; + const curve = metricKey === 'power' ? stats.powerCurve : stats.hrCurve; + if (curve) { + const entry = curve[clickedDur]; + if (entry) { + const bestIdx = entry.idx; + const duration = clickedDur; + const startPt = currentTrack.points[bestIdx]; + const endPt = currentTrack.points[bestIdx + duration - 1]; + if (startPt && endPt && startPt.time != null && endPt.time != null) { + const trackStart = currentTrack.points[0].time || 0; + const min = (startPt.time - trackStart) / 1000; + const max = (endPt.time - trackStart) / 1000; + onTrackSelect?.(currentTrack.id, [min, max]); + } + } + } + } + } + }); + + chartEl.addEventListener('mouseleave', () => { + if (mainChart) mainChart.redraw(); + }); + + // 4. Add expandable table + const detailsEl = document.createElement('details'); + detailsEl.classList.add('progress-details'); + detailsEl.style.marginTop = '0px'; + detailsEl.style.setProperty('--chart-color', color); + detailsEl.innerHTML = ` + + chevron_right + table_chart + Data Table + + + + + + + + + + + ${durations + .map((d) => { + const cur = currentCurve[d] + ? (currentCurve[d] as any).power || (currentCurve[d] as any).hr + : '-'; + const mx = maxCurve[d]?.val || '-'; + const mxId = maxCurve[d]?.trackId; + const mxIdx = maxCurve[d]?.idx; + return ` + + + + + + `; + }) + .join('')} + +
DurationCurrentAll-Time Max
${fmtDuration(d)}${cur} ${unit} + ${mx} ${unit} + ${mxId ? `` : ''} +
+ `; + cardContainer.appendChild(detailsEl); + + detailsEl.querySelectorAll('.evolution-select-btn').forEach((btn) => { + btn.addEventListener('click', (e) => { + e.preventDefault(); + e.stopPropagation(); + const id = btn.getAttribute('data-id'); + const idx = btn.getAttribute('data-idx'); + const dur = btn.getAttribute('data-dur'); + if (id) { + document.dispatchEvent( + new CustomEvent('select-track', { + detail: { + id, + idx: idx ? Number(idx) : null, + dur: dur ? Number(dur) : null, + }, + }), + ); + } + }); + }); + + let lastNearestIdx: number | null = null; + + // 5. Render timeline chart + const timelineEl = row2.querySelector(`#${metricKey}-timeline-chart`) as HTMLElement; + if (timelineEl) { + timelineEl.style.position = 'relative'; + const timelineTooltip = document.createElement('div'); + timelineTooltip.className = 'timeline-tooltip font-m'; + timelineTooltip.style.cssText = + 'position:absolute;background:rgba(14, 14, 16, 0.9);color:#fff;padding:8px 12px;border-radius:4px;font-family:system-ui;pointer-events:none;z-index:1000;box-shadow:0 4px 6px rgba(0,0,0,0.3);border:1px solid #2e2e34;display:none;'; + timelineEl.appendChild(timelineTooltip); + + if (xTimeline.length > 0) { + const { xFiltered, rawYFiltered, smoothedYFiltered } = getFilteredData( + currentDur, + currentRange, + ); + const lowerBound = smoothedYFiltered.map((v) => (v !== null ? v * 0.9 : null)); + const upperBound = smoothedYFiltered.map((v) => (v !== null ? v * 1.1 : null)); + const dataTimeline = [xFiltered, rawYFiltered, smoothedYFiltered, lowerBound, upperBound]; + + const series: any[] = [ + { + value: (u: any, v: number) => { + if (v != null) return fmtDate(v * 1000); + + return '-'; + }, + }, + { + label: 'Value', + stroke: color, + width: 0, + points: { show: true, size: 6, fill: hexToRgba(color, 0.5) }, + }, + { + label: 'Trend', + stroke: color, + width: 2, + points: { show: false }, + spanGaps: true, + fill: 'transparent', + dash: [5, 5], + }, + { + label: 'Lower Bound', + stroke: 'transparent', + width: 0, + points: { show: false }, + spanGaps: true, + }, + { + label: 'Upper Bound', + stroke: 'transparent', + width: 0, + points: { show: false }, + spanGaps: true, + }, + ]; + + const labelEl = document.getElementById(`${metricKey}-timeline-label`); + if (labelEl) + labelEl.textContent = `Progress ${fmtDuration(currentDur)} ${metricKey === 'power' ? 'Power' : 'HR'}`; + + const optsTimeline: uPlot.Options = { + width: Math.max(100, (gridWidth || 570) - 400), + height: 250, + padding: [0, 10, 0, 10], + tzDate: (ts: number) => uPlot.tzDate(new Date(ts * 1000), 'Etc/UTC'), + cursor: { + points: { + show: false, + }, + drag: { setScale: false }, + x: false, + y: false, + }, + scales: { + x: { + time: true, + range: (u) => { + const min = u.data[0][0]; + const max = u.data[0][u.data[0].length - 1]; + if (min === undefined || max === undefined) return [0, 100]; + + if (min === max) return [min - 3600, max + 3600]; + + return [min, max]; + }, + }, + y: { range: (u: any, min: number, max: number) => [min * 0.9, max * 1.1] }, + }, + series: series, + bands: [{ series: [4, 3], fill: hexToRgba(color, 0.2) }], + hooks: { + init: [ + (u: uPlot) => { + u.over.addEventListener('click', () => { + if (lastNearestIdx != null) { + const track = tracksFiltered[lastNearestIdx]; + if (track) { + const stats = track.stats; + const curve = metricKey === 'power' ? stats.powerCurve : stats.hrCurve; + if (curve) { + const entry = curve[currentDur]; + if (entry) { + const bestIdx = entry.idx; + const duration = currentDur; + + const startPt = track.points[bestIdx]; + const endPt = track.points[bestIdx + duration - 1]; + + if (startPt && endPt && startPt.time != null && endPt.time != null) { + const trackStart = track.points[0].time || 0; + const min = (startPt.time - trackStart) / 1000; + const max = (endPt.time - trackStart) / 1000; + + opts.onTrackSelect?.(track.id, [min, max]); + } else { + opts.onTrackSelect?.(track.id, null); + } + } else { + opts.onTrackSelect?.(track.id, null); + } + } else { + opts.onTrackSelect?.(track.id, null); + } + } + } + }); + }, + ], + draw: [ + (u: uPlot) => { + let idx = u.cursor.idx; + if (idx != null && u.data[0][idx] != null) { + const val = u.data[1][idx]; + if (val != null) { + const tpcx = u.valToPos(u.data[0][idx]!, 'x', true); + const tpcy = u.valToPos(val, 'y', true); + + const tctx = u.ctx; + const tdpr = window.devicePixelRatio || 1; + + // Draw lines + tctx.save(); + tctx.beginPath(); + tctx.setLineDash([2 * tdpr, 2 * tdpr]); + tctx.strokeStyle = hexToRgba(color, 0.4); + tctx.lineWidth = 1 * tdpr; + // Vertical line + tctx.moveTo(tpcx, u.bbox.top); + tctx.lineTo(tpcx, u.bbox.top + u.bbox.height); + // Horizontal line + tctx.moveTo(u.bbox.left, tpcy); + tctx.lineTo(u.bbox.left + u.bbox.width, tpcy); + tctx.stroke(); + tctx.restore(); + + // Draw hover halo + tctx.save(); + tctx.beginPath(); + tctx.setLineDash([]); // Reset to solid + tctx.arc(tpcx, tpcy, 12 * tdpr, 0, 2 * Math.PI); + tctx.fillStyle = hexToRgba(color, 0.2); + tctx.fill(); + tctx.restore(); + + // Draw center circle + tctx.save(); + tctx.beginPath(); + tctx.arc(tpcx, tpcy, 6 * tdpr, 0, 2 * Math.PI); + tctx.fillStyle = color; + tctx.fill(); + tctx.strokeStyle = '#fff'; + tctx.lineWidth = 2 * tdpr; + tctx.stroke(); + tctx.restore(); + + // Draw axis labels + tctx.save(); + tctx.setLineDash([]); // Reset to solid + + const d = new Date(u.data[0][idx]! * 1000); + const dateStr = `${d.getUTCFullYear()}-${String(d.getUTCMonth() + 1).padStart(2, '0')}-${String(d.getUTCDate()).padStart(2, '0')}`; + tctx.font = `bold ${10 * tdpr}px system-ui, sans-serif`; + const dateWidth = tctx.measureText(dateStr).width; + const xbw = dateWidth + 10 * tdpr; + const pillH = 16 * tdpr; + + const xLabelX = tpcx - xbw / 2; + const clampedXBx = Math.max( + u.bbox.left, + Math.min(u.bbox.left + u.bbox.width - xbw, xLabelX), + ); + const xLabelY = u.bbox.top + u.bbox.height + 2 * tdpr; + + // X Label Pill + tctx.fillStyle = 'rgba(14, 14, 16, 0.92)'; + tctx.beginPath(); + if ((tctx as any).roundRect) + (tctx as any).roundRect(clampedXBx, xLabelY, xbw, pillH, 3 * tdpr); + else tctx.rect(clampedXBx, xLabelY, xbw, pillH); + tctx.fill(); + tctx.strokeStyle = hexToRgba(color, 0.27); + tctx.lineWidth = 1 * tdpr; + tctx.stroke(); + + tctx.fillStyle = color; + tctx.textAlign = 'center'; + tctx.textBaseline = 'middle'; + tctx.fillText(dateStr, clampedXBx + xbw / 2, xLabelY + pillH / 2); + + const valStr = `${val.toFixed(0)} ${unit}`; + const valWidth = tctx.measureText(valStr).width; + const ybw = valWidth + 10 * tdpr; + + const yLabelX = u.bbox.left - ybw - 2 * tdpr; // To the left + const yLabelY = tpcy - pillH / 2; + + // Y Label Pill + tctx.fillStyle = 'rgba(14, 14, 16, 0.92)'; + tctx.beginPath(); + if ((tctx as any).roundRect) + (tctx as any).roundRect(yLabelX, yLabelY, ybw, pillH, 3 * tdpr); + else tctx.rect(yLabelX, yLabelY, ybw, pillH); + tctx.fill(); + tctx.strokeStyle = hexToRgba(color, 0.27); + tctx.lineWidth = 1 * tdpr; + tctx.stroke(); + + tctx.fillStyle = color; + tctx.textAlign = 'left'; + tctx.textBaseline = 'middle'; + tctx.fillText(valStr, yLabelX + 5 * tdpr, yLabelY + pillH / 2); + + tctx.restore(); + } + } + }, + ], + setCursor: [ + (u: uPlot) => { + if (!(u as any)._ready) return; + u.redraw(false); + + let idx = u.cursor.idx; + const cursorLeft = u.cursor.left; + const cursorTop = u.cursor.top; + const bb = u.bbox; + + if (cursorLeft != null && cursorTop != null && cursorLeft >= 0 && cursorTop >= 0) { + let minD2 = Infinity; + let nearestIdx = -1; + const radius = 30; // pixels + const maxD2 = radius * radius; + + const xData = u.data[0]; + const yData = u.data[1]; + + for (let i = 0; i < xData.length; i++) { + const x = xData[i]; + const y = yData[i]; + if (x == null || y == null) continue; + + const px = u.valToPos(x, 'x', false); + const py = u.valToPos(y, 'y', false); + + const dx = px - cursorLeft; + const dy = py - cursorTop; + const d2 = dx * dx + dy * dy; + + if (d2 < minD2) { + minD2 = d2; + nearestIdx = i; + } + } + + if (minD2 <= maxD2) { + idx = nearestIdx; + lastNearestIdx = nearestIdx; + } else { + idx = null; + lastNearestIdx = null; + } + } + + if (idx != null && u.data[0][idx] != null) { + const track = tracksFiltered[idx]; + const cx = u.valToPos(u.data[0][idx], 'x', false); + const cy = u.valToPos(u.data[1][idx]!, 'y', false); + + const d = new Date(u.data[0][idx]! * 1000); + const dateStr = `${d.getUTCFullYear()}-${String(d.getUTCMonth() + 1).padStart(2, '0')}-${String(d.getUTCDate()).padStart(2, '0')}`; + + const val = u.data[1][idx]; + const valStr = val != null ? `${val} ${unit}` : '—'; + + timelineTooltip.innerHTML = ` +
+ Date: + ${dateStr} +
+
+ Value: + ${valStr} +
+ `; + timelineTooltip.style.display = 'block'; + timelineTooltip.style.left = `${cursorLeft! + bb.left}px`; + timelineTooltip.style.top = `${cursorTop! + bb.top}px`; + timelineTooltip.style.transform = 'translate(5px, -50%)'; + } else { + timelineTooltip.style.display = 'none'; + } + }, + ], + }, + axes: [ + { + space: 50, + stroke: '#555564', + grid: { stroke: '#2e2e34' }, + show: true, + font: '10px system-ui, sans-serif', + size: 30, + values: (u: any, vals: any[]) => + vals.map((v) => { + const d = new Date(v * 1000); + return `${d.getUTCFullYear()}-${String(d.getUTCMonth() + 1).padStart(2, '0')}`; + }), + }, + { + stroke: '#555564', + grid: { stroke: '#2e2e34' }, + font: '10px system-ui, sans-serif', + values: (u: any, vals: any[]) => vals.map((v) => `${v} ${unit}`), + show: true, + space: 15, + size: 50, + }, + ], + }; + + timelineChart = new uPlot(optsTimeline, dataTimeline as any, timelineEl); + evolutionPlots.push(timelineChart); + + timelineEl.addEventListener('mouseleave', () => { + timelineChart!.redraw(); + }); + + // Range control moved to toolbar + } else { + timelineEl.innerHTML = '
No data for the past year
'; + } + } + }; + renderCharts(); + + return { + updatePin: (dur: number | null) => { + pinnedDur = dur; + if (dur !== null) currentDur = dur; + + const labelEl = document.getElementById(`${metricKey}-timeline-label`); + if (labelEl) + labelEl.textContent = `Progress ${fmtDuration(currentDur)} ${metricKey === 'power' ? 'Power' : 'HR'}${pinnedDur !== null ? ' (Pinned)' : ''}`; + + if (timelineChart) { + const { xFiltered, rawYFiltered, smoothedYFiltered } = getFilteredData( + currentDur, + currentRange, + ); + const lowerBound = smoothedYFiltered.map((v) => (v !== null ? v * 0.9 : null)); + const upperBound = smoothedYFiltered.map((v) => (v !== null ? v * 1.1 : null)); + timelineChart.setData([xFiltered, rawYFiltered, smoothedYFiltered, lowerBound, upperBound]); + } + if (mainChart) mainChart.redraw(); + }, + updateRange: (range: string) => { + currentRange = range; + if (timelineChart) { + const { xFiltered, rawYFiltered, smoothedYFiltered } = getFilteredData( + currentDur, + currentRange, + ); + const lowerBound = smoothedYFiltered.map((v) => (v !== null ? v * 0.9 : null)); + const upperBound = smoothedYFiltered.map((v) => (v !== null ? v * 1.1 : null)); + timelineChart.setData([xFiltered, rawYFiltered, smoothedYFiltered, lowerBound, upperBound]); + } + }, + }; +} + +function getWeekStart(date: Date): Date { + const d = new Date(date); + const day = d.getUTCDay(); + const diff = d.getUTCDate() - day + (day === 0 ? -6 : 1); // adjust when day is sunday + d.setUTCDate(diff); + d.setUTCHours(0, 0, 0, 0); + return d; +} + +function computeWeeklyDistance( + allTracks: TrackData[], +): { week: string; timestamp: number; distance: number }[] { + const weeklyData: Record = {}; + + allTracks.forEach((t) => { + if (!t.stats.startTime || !t.stats.totalDist) return; + + const date = new Date(t.stats.startTime); + const weekStart = getWeekStart(date); + const weekKey = weekStart.toISOString().split('T')[0]; + + if (!weeklyData[weekKey]) weeklyData[weekKey] = { timestamp: weekStart.getTime(), distance: 0 }; + + weeklyData[weekKey].distance += t.stats.totalDist; + }); + + return Object.entries(weeklyData) + .map(([week, data]) => ({ week, timestamp: data.timestamp, distance: data.distance })) + .sort((a, b) => a.timestamp - b.timestamp); +} + +function computeWeeklyElevation( + allTracks: TrackData[], +): { week: string; timestamp: number; elevation: number }[] { + const weeklyData: Record = {}; + + allTracks.forEach((t) => { + if (!t.stats.startTime || t.stats.elevGain === undefined) return; + + const date = new Date(t.stats.startTime); + const weekStart = getWeekStart(date); + const weekKey = weekStart.toISOString().split('T')[0]; + + if (!weeklyData[weekKey]) + weeklyData[weekKey] = { timestamp: weekStart.getTime(), elevation: 0 }; + + weeklyData[weekKey].elevation += t.stats.elevGain; + }); + + return Object.entries(weeklyData) + .map(([week, data]) => ({ week, timestamp: data.timestamp, elevation: data.elevation })) + .sort((a, b) => a.timestamp - b.timestamp); +} + +function computeMonthlyDistance( + allTracks: TrackData[], +): { week: string; timestamp: number; distance: number }[] { + const monthlyData: Record = {}; + + allTracks.forEach((t) => { + if (!t.stats.startTime || !t.stats.totalDist) return; + const date = new Date(t.stats.startTime); + const monthStart = new Date(Date.UTC(date.getUTCFullYear(), date.getUTCMonth(), 1)); + const monthKey = monthStart.toISOString().slice(0, 7); + if (!monthlyData[monthKey]) { + monthlyData[monthKey] = { timestamp: monthStart.getTime(), distance: 0 }; + } + monthlyData[monthKey].distance += t.stats.totalDist; + }); + + return Object.entries(monthlyData) + .map(([week, data]) => ({ week, timestamp: data.timestamp, distance: data.distance })) + .sort((a, b) => a.timestamp - b.timestamp); +} + +function computeMonthlyElevation( + allTracks: TrackData[], +): { week: string; timestamp: number; elevation: number }[] { + const monthlyData: Record = {}; + + allTracks.forEach((t) => { + if (!t.stats.startTime || t.stats.elevGain === undefined) return; + const date = new Date(t.stats.startTime); + const monthStart = new Date(Date.UTC(date.getUTCFullYear(), date.getUTCMonth(), 1)); + const monthKey = monthStart.toISOString().slice(0, 7); + if (!monthlyData[monthKey]) { + monthlyData[monthKey] = { timestamp: monthStart.getTime(), elevation: 0 }; + } + monthlyData[monthKey].elevation += t.stats.elevGain; + }); + + return Object.entries(monthlyData) + .map(([week, data]) => ({ week, timestamp: data.timestamp, elevation: data.elevation })) + .sort((a, b) => a.timestamp - b.timestamp); +} + +function renderProgressCard( + grid: HTMLElement, + allTracks: TrackData[], + type: 'distance' | 'elevation', + syncKey?: any, + onDateRangeSelect?: (start: string, end: string) => void, + gridWidth?: number, +) { + const isDistance = type === 'distance'; + let activeGrouping: 'week' | 'month' = currentEvolutionGrouping; + let activeRange = UrlState.get().progress || 'all'; + const weeklySource = isDistance + ? computeWeeklyDistance(allTracks) + : computeWeeklyElevation(allTracks); + const monthlySource = isDistance + ? computeMonthlyDistance(allTracks) + : computeMonthlyElevation(allTracks); + let weeklyData = activeGrouping === 'month' ? monthlySource : weeklySource; + const accumulated = computeAccumulatedMetric(allTracks, (t) => + isDistance ? t.stats.totalDist : t.stats.elevGain, + ); + const color = isDistance ? Metrics.distance.color : Metrics.elevation.color; + const unit = isDistance ? 'km' : 'm'; + const icon = isDistance ? Metrics.distance.icon : Metrics.elevation.icon; + + const cardContainer = document.createElement('div'); + cardContainer.className = `chart-row ${type}-progress-card`; + cardContainer.id = `${type}-evolution-card`; + + cardContainer.innerHTML = ` +
+
+ ${icon} + ${isDistance ? 'Distance Progress' : 'Elevation Progress'} +
+
+
+
+
+
+
+
+ ${renderAccumulatedTable(accumulated, isDistance, unit)} +
+
+ `; + + grid.appendChild(cardContainer); + + const chartEl = cardContainer.querySelector(`#${type}-weekly-chart`) as HTMLElement; + const yAxisEl = cardContainer.querySelector(`#${type}-y-axis-chart`) as HTMLElement; + + if (!chartEl || !yAxisEl) return; + + chartEl.style.position = 'relative'; + + // Use inline tooltip style + const tooltip = document.createElement('div'); + tooltip.className = 'cur-y-val'; + tooltip.style.cssText = `color:${color};display:none`; + chartEl.appendChild(tooltip); + + const xData = weeklyData.map((d) => d.timestamp / 1000); + const yData = isDistance + ? (weeklyData as any[]).map((d) => d.distance / 1000) + : (weeklyData as any[]).map((d) => d.elevation); + + if (xData.length === 0) return; + + const yDataSmoothed = gaussianSmooth(yData, 2); + + const chartClientW = Math.max(0, (gridWidth || 570) - 20 - 320 - 20); + const width = Math.max(chartClientW, xData.length * 40); + const maxVal = Math.max(...yData) * 1.1; + + const mainOpts: uPlot.Options = { + width: width, + height: 150, + padding: [10, 10, 0, 10], + tzDate: (ts: number) => uPlot.tzDate(new Date(ts * 1000), 'Etc/UTC'), + scales: { + x: { time: true }, + y: { range: [0, maxVal] }, + }, + series: [ + {}, + { + label: isDistance ? 'Distance' : 'Elevation', + stroke: color, + fill: hexToRgba(color, 0.1), + width: 2, + points: { show: true, size: 4 }, + }, + { + label: 'Trend', + stroke: color, + width: 2, + dash: [5, 5], + points: { show: false }, + }, + ], + cursor: { + sync: syncKey ? { key: syncKey.key } : undefined, + drag: { x: false, y: false }, + points: { show: false }, + x: false, + y: false, + }, + hooks: { + init: [ + (u: uPlot) => { + u.over.addEventListener('click', () => { + const idx = u.cursor.idx; + if (idx != null) { + const item = weeklyData[idx]; + if (item) { + const start = new Date(item.timestamp); + const end = new Date(item.timestamp + 7 * 24 * 60 * 60 * 1000); // End of week + const startStr = start.toISOString().split('T')[0]; + const endStr = end.toISOString().split('T')[0]; + onDateRangeSelect?.(startStr, endStr); + } + } + }); + }, + ], + setCursor: [ + (u: uPlot) => { + if (!(u as any)._ready) return; + const idx = u.cursor.idx; + const cursorLeft = u.cursor.left; + const cursorTop = u.cursor.top; + + if ( + idx != null && + cursorLeft != null && + cursorTop != null && + cursorLeft >= 0 && + cursorTop >= 0 + ) { + const val = u.data[1][idx]; + const valStr = + val != null ? (isDistance ? `${val.toFixed(1)} km` : `${val.toFixed(0)} m`) : '—'; + + tooltip.innerHTML = valStr; + tooltip.style.display = 'block'; + tooltip.style.left = `${u.valToPos(u.data[0][idx]!, 'x', false)}px`; + tooltip.style.top = `${u.valToPos(u.data[1][idx]!, 'y', false)}px`; + tooltip.style.transform = 'translate(6px, -50%)'; + } else { + tooltip.style.display = 'none'; + } + + u.redraw(false); + }, + ], + draw: [ + (u: uPlot) => { + const idx = u.cursor.idx; + if (idx != null && u.data[0][idx] != null) { + const pcx = u.valToPos(u.data[0][idx]!, 'x', true); + const pcy = u.valToPos(u.data[1][idx]!, 'y', true); + const dpr = window.devicePixelRatio || 1; + const ctx = u.ctx; + const bb = u.bbox; + + // Draw lines + ctx.save(); + ctx.beginPath(); + ctx.setLineDash([2 * dpr, 2 * dpr]); + ctx.strokeStyle = hexToRgba(color, 0.4); + ctx.lineWidth = 1 * dpr; + // Vertical line + ctx.moveTo(pcx, bb.top); + ctx.lineTo(pcx, bb.top + bb.height); + // Horizontal line + ctx.moveTo(bb.left, pcy); + ctx.lineTo(bb.left + bb.width, pcy); + ctx.stroke(); + ctx.restore(); + + // Draw hover halo + ctx.save(); + ctx.beginPath(); + ctx.setLineDash([]); // Reset to solid + ctx.arc(pcx, pcy, 12 * dpr, 0, 2 * Math.PI); + ctx.fillStyle = hexToRgba(color, 0.2); + ctx.fill(); + ctx.restore(); + + // Draw center circle + ctx.save(); + ctx.beginPath(); + ctx.arc(pcx, pcy, 6 * dpr, 0, 2 * Math.PI); + ctx.fillStyle = color; + ctx.fill(); + ctx.strokeStyle = '#fff'; + ctx.lineWidth = 2 * dpr; + ctx.stroke(); + ctx.restore(); + + // Draw axis labels + ctx.save(); + ctx.setLineDash([]); // Reset to solid + + // X Label Pill + const d = new Date(u.data[0][idx]! * 1000); + const xValStr = `${d.getUTCFullYear()}-${String(d.getUTCMonth() + 1).padStart(2, '0')}-${String(d.getUTCDate()).padStart(2, '0')}`; + ctx.font = `bold ${10 * dpr}px system-ui, sans-serif`; + const xValWidth = ctx.measureText(xValStr).width; + const xbw = xValWidth + 10 * dpr; + const pillH = 16 * dpr; + + const xLabelX = pcx - xbw / 2; + const clampedXBx = Math.max(bb.left, Math.min(bb.left + bb.width - xbw, xLabelX)); + const xLabelY = bb.top + bb.height + 2 * dpr; + + ctx.fillStyle = 'rgba(14, 14, 16, 0.92)'; + ctx.beginPath(); + if ((ctx as any).roundRect) + (ctx as any).roundRect(clampedXBx, xLabelY, xbw, pillH, 3 * dpr); + else ctx.rect(clampedXBx, xLabelY, xbw, pillH); + ctx.fill(); + ctx.strokeStyle = hexToRgba(color, 0.27); + ctx.lineWidth = 1 * dpr; + ctx.stroke(); + + ctx.fillStyle = color; + ctx.textAlign = 'center'; + ctx.textBaseline = 'middle'; + ctx.fillText(xValStr, clampedXBx + xbw / 2, xLabelY + pillH / 2); + + ctx.restore(); + } + }, + ], + }, + axes: [ + { + stroke: '#555564', + grid: { stroke: '#2e2e34', width: 1 }, + show: true, + space: 40, + values: (self, splits) => + splits.map((s) => { + const d = new Date(s * 1000); + return `${d.getUTCMonth() + 1}/${d.getUTCDate()}`; + }), + font: '10px system-ui, sans-serif', + size: 30, + }, + { + stroke: '#555564', + grid: { stroke: '#2e2e34', width: 1 }, + show: true, + values: (self, splits) => splits.map((s) => `${s.toFixed(0)} ${unit}`), + font: '10px system-ui, sans-serif', + size: 40, + }, + ], + }; + + const mainChart = new uPlot(mainOpts, [xData, yData, yDataSmoothed], chartEl); + evolutionPlots.push(mainChart); + + const scrollContainer = cardContainer.querySelector('.chart-scroll-container') as HTMLElement; + if (scrollContainer && xData.length * 40 > width) + scrollContainer.scrollLeft = scrollContainer.scrollWidth - scrollContainer.clientWidth; + + function applySeriesData() { + weeklyData = activeGrouping === 'month' ? monthlySource : weeklySource; + const maxTime = weeklyData[weeklyData.length - 1]?.timestamp || 1785542400000; + let minTime = 0; + + if (activeRange === '1m') minTime = maxTime - 30 * 24 * 3600 * 1000; + else if (activeRange === '2m') minTime = maxTime - 60 * 24 * 3600 * 1000; + else if (activeRange === '6m') minTime = maxTime - 180 * 24 * 3600 * 1000; + else if (activeRange === '1y') minTime = maxTime - 365 * 24 * 3600 * 1000; + else if (activeRange === '2y') minTime = maxTime - 2 * 365 * 24 * 3600 * 1000; + + const filteredData = weeklyData.filter((d) => d.timestamp >= minTime); + + const newXData = filteredData.map((d) => d.timestamp / 1000); + const newYData = isDistance + ? (filteredData as any[]).map((d) => d.distance / 1000) + : (filteredData as any[]).map((d) => d.elevation); + const newYDataSmoothed = gaussianSmooth(newYData, 2); + + const newWidth = Math.max(chartEl.clientWidth, newXData.length * 40); + const newMaxVal = Math.max(1, ...newYData) * 1.1; + + mainChart.setSize({ width: newWidth, height: 150 }); + mainChart.setScale('y', { min: 0, max: newMaxVal }); + mainChart.setData([newXData, newYData, newYDataSmoothed]); + + if (scrollContainer) + scrollContainer.scrollLeft = scrollContainer.scrollWidth - scrollContainer.clientWidth; + } + + return { + updateRange: (range: string) => { + activeRange = range; + applySeriesData(); + }, + updateGrouping: (grouping: 'week' | 'month') => { + activeGrouping = grouping; + applySeriesData(); + }, + }; +} diff --git a/suites-experimental/tools-gps-tracks/src/url-state.ts b/suites-experimental/tools-gps-tracks/src/url-state.ts new file mode 100644 index 000000000..1ab6e3044 --- /dev/null +++ b/suites-experimental/tools-gps-tracks/src/url-state.ts @@ -0,0 +1,222 @@ +// Copyright (c) 2026, Camillo Bruni. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +'use strict'; + +// ── URL State ───────────────────────────────────────────────────── +// Handles reading/writing application state to the URL. +// Uses an incremental "patch" approach to preserve existing params. + +export interface AppState { + track?: string; + map?: string; + xaxis?: string; + metrics?: string[]; + f_date?: (string | null)[]; + f_dist?: (number | null)[]; + f_dur?: (number | null)[]; + f_mets?: string[]; + f_tags?: string[]; + q?: string; + re?: boolean; + sort?: string; + map_pos?: [number, number, number]; + storage?: 'memory' | 'indexeddb'; + sel?: [number, number]; + sel_point?: number; + tab?: string; + progress?: string; + speed_mode?: string; +} + +export const UrlState = (() => { + const initialPathname = typeof location !== 'undefined' ? location.pathname : '/'; + const initialSearch = typeof location !== 'undefined' ? location.search.replace(/^\?/, '') : ''; + + // Internal state and local in-memory query/navigation stack. + let _state: AppState = {}; + let localSearch: string = initialSearch; + let _lastSearch: string | null = null; + let _syncTimeout: any = null; + let _shouldPush = false; + let historyEntries: Array<{ state: unknown; url: string }> = [ + { + state: null, + url: initialPathname + (initialSearch ? `?${initialSearch}` : ''), + }, + ]; + + function read(): AppState { + const p = new URLSearchParams(localSearch); + const s: AppState = {}; + + if (p.has('track')) s.track = p.get('track')!; + if (p.has('map')) s.map = p.get('map')!; + if (p.has('xaxis')) s.xaxis = p.get('xaxis')!; + if (p.has('tab')) s.tab = p.get('tab')!; + if (p.has('sel_point')) s.sel_point = Number(p.get('sel_point')); + if (p.has('speed_mode')) s.speed_mode = p.get('speed_mode')!; + if (p.has('progress')) s.progress = p.get('progress')!; + const metricsRaw = p.get('metrics'); + if (metricsRaw) s.metrics = metricsRaw.split(',').filter(Boolean); + + if (p.has('f_date')) { + s.f_date = p + .get('f_date')! + .split(',') + .map((v) => v || null); + } + if (p.has('f_dist')) { + s.f_dist = p + .get('f_dist')! + .split(',') + .map((v) => (v ? Number(v) : null)); + } + if (p.has('f_dur')) { + s.f_dur = p + .get('f_dur')! + .split(',') + .map((v) => (v ? Number(v) : null)); + } + if (p.has('f_mets')) s.f_mets = p.get('f_mets')!.split(','); + if (p.has('f_tags')) s.f_tags = p.get('f_tags')!.split(','); + + if (p.has('q')) s.q = p.get('q')!; + if (p.has('re')) s.re = p.get('re') === '1'; + if (p.has('sort')) { + const v = p.get('sort')!; + const isDesc = v.startsWith('-'); + const field = isDesc ? v.substring(1) : v; + s.sort = `${field}-${isDesc ? 'desc' : 'asc'}`; + } + + if (p.has('map_pos')) { + const parts = p.get('map_pos')!.split(',').map(Number); + if (parts.length === 3 && parts.every(isFinite)) + s.map_pos = parts as [number, number, number]; + } + + if (p.has('storage')) s.storage = p.get('storage') as 'memory' | 'indexeddb'; + if (p.has('sel')) { + const parts = p.get('sel')!.split(',').map(Number); + if (parts.length === 2 && parts.every((v) => !isNaN(v) && isFinite(v))) + s.sel = parts as [number, number]; + } + + return s; + } + + // Sync _state to browser URL synchronously so no pending timer leaks across steps + function _sync() { + if (_syncTimeout) { + clearTimeout(_syncTimeout); + _syncTimeout = null; + } + + const p = new URLSearchParams(); + + // Add standard encoded params + if (_state.track) p.set('track', _state.track); + if (_state.map) p.set('map', _state.map); + if (_state.xaxis && _state.xaxis !== 'time') p.set('xaxis', _state.xaxis); + if (_state.tab && _state.tab !== 'graphs') p.set('tab', _state.tab); + if (_state.storage && _state.storage !== 'memory') p.set('storage', _state.storage); + if (_state.sel_point !== undefined) p.set('sel_point', String(_state.sel_point)); + if (_state.speed_mode) p.set('speed_mode', _state.speed_mode); + if (_state.q) p.set('q', _state.q); + if (_state.progress) p.set('progress', _state.progress); + if (_state.re) p.set('re', '1'); + + if (_state.sort) { + const parts = _state.sort.split('-'); + const field = parts[0]; + const dir = parts[1]; + p.set('sort', (dir === 'desc' ? '-' : '') + field); + } + + let search = p.toString(); + + // Add comma-separated params (literal commas for cleaner URL) + if (_state.metrics) search += `${search ? '&' : ''}metrics=${_state.metrics.join(',')}`; + + if (_state.f_date) + search += `${search ? '&' : ''}f_date=${_state.f_date.map((v) => v || '').join(',')}`; + + if (_state.f_dist) + search += `${search ? '&' : ''}f_dist=${_state.f_dist.map((v) => v ?? '').join(',')}`; + + if (_state.f_dur) + search += `${search ? '&' : ''}f_dur=${_state.f_dur.map((v) => v ?? '').join(',')}`; + + if (_state.f_mets) search += `${search ? '&' : ''}f_mets=${_state.f_mets.join(',')}`; + + if (_state.f_tags) search += `${search ? '&' : ''}f_tags=${_state.f_tags.join(',')}`; + + if (_state.map_pos) { + const val = `${(+_state.map_pos[0]).toFixed(5)},${(+_state.map_pos[1]).toFixed(5)},${ + _state.map_pos[2] + }`; + search += `${search ? '&' : ''}map_pos=${val}`; + } + if (_state.sel) { + const val = `${+_state.sel[0].toFixed(4)},${+_state.sel[1].toFixed(4)}`; + search += `${search ? '&' : ''}sel=${val}`; + } + + localSearch = search; + if (search !== _lastSearch) { + const pathname = typeof location !== 'undefined' ? location.pathname : '/'; + const url = pathname + (search ? `?${search}` : ''); + const entryState = read(); + if (_shouldPush) { + historyEntries.push({ state: entryState, url }); + if (historyEntries.length > 100) { + historyEntries.shift(); + } + _shouldPush = false; + } else if (historyEntries.length > 0) { + historyEntries[historyEntries.length - 1] = { state: entryState, url }; + } else { + historyEntries.push({ state: entryState, url }); + } + _lastSearch = search; + } + } + + // Initial read + _state = read(); + + function get(): AppState { + return { ..._state }; + } + + function patch(partial: Partial>, push: boolean = false) { + if (push) _shouldPush = true; + for (const [k, v] of Object.entries(partial)) { + const key = k as keyof AppState; + if (v === null || v === undefined) delete _state[key]; + else (_state as any)[key] = v; + } + _sync(); + } + + function refresh() { + _state = read(); + } + + function reset() { + if (_syncTimeout) { + clearTimeout(_syncTimeout); + _syncTimeout = null; + } + _state = {}; + _shouldPush = false; + localSearch = ''; + _lastSearch = ''; + const pathname = typeof location !== 'undefined' ? location.pathname : '/'; + historyEntries = [{ state: null, url: pathname }]; + } + + return { read, get, patch, refresh, reset }; +})(); diff --git a/suites-experimental/tools-gps-tracks/src/utils.ts b/suites-experimental/tools-gps-tracks/src/utils.ts new file mode 100644 index 000000000..616087824 --- /dev/null +++ b/suites-experimental/tools-gps-tracks/src/utils.ts @@ -0,0 +1,139 @@ +// Copyright (c) 2026, Camillo Bruni. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +'use strict'; + +export function haversine(lat1: number, lon1: number, lat2: number, lon2: number): number { + const R = 6371e3; // Earth radius in metres + const phi1 = (lat1 * Math.PI) / 180; + const phi2 = (lat2 * Math.PI) / 180; + const deltaPhi = ((lat2 - lat1) * Math.PI) / 180; + const deltaLambda = ((lon2 - lon1) * Math.PI) / 180; + + const a = + Math.sin(deltaPhi / 2) * Math.sin(deltaPhi / 2) + + Math.cos(phi1) * Math.cos(phi2) * Math.sin(deltaLambda / 2) * Math.sin(deltaLambda / 2); + const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); + + return R * c; +} + +export function compactId(timestamp: number): string { + // Use a compact representation of the timestamp (seconds since epoch) + // 1712650000 -> Base64 + const buf = new Uint32Array([Math.floor(timestamp / 1000)]); + const b64 = btoa(String.fromCharCode(...new Uint8Array(buf.buffer))); + return b64.replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, ''); +} + +export function generateId(): string { + return Math.random().toString(36).slice(2, 10); +} + +export function shortRandom(): string { + return Math.random().toString(36).slice(2, 6); +} + +export function fmtDuration(s: number): string { + if (s < 60) return `${Math.round(s)}s`; + if (s < 3600) return `${Math.round(s / 60)}m`; + return `${Math.round(s / 3600)}h`; +} + +export function fmtSecs(s: number): string { + const h = Math.floor(s / 3600), + m = Math.floor((s % 3600) / 60); + return h > 0 ? `${h}h ${m}m` : `${m}m ${Math.floor(s % 60)}s`; +} + +export function fmtPace(seconds: number | null): string { + if (seconds == null || !isFinite(seconds)) return '--'; + const mins = Math.floor(seconds / 60); + const secs = Math.round(seconds % 60); + return `${mins}:${secs.toString().padStart(2, '0')}`; +} + +export function fmtDate(ts: number | string | Date): string { + const d = new Date(ts); + if (isNaN(d.getTime())) return 'Unknown date'; + return d.toISOString().split('T')[0]; +} + +export function fmtDateTime(ts: number | string | Date): string { + const d = new Date(ts); + if (isNaN(d.getTime())) return 'Unknown'; + const iso = d.toISOString(); + return `${iso.split('T')[0]} ${iso.split('T')[1].slice(0, 5)}`; +} + +export function fmtFileSize(bytes: number): string { + if (bytes === 0) return '0 B'; + const k = 1024; + const sizes = ['B', 'KB', 'MB', 'GB']; + const i = Math.floor(Math.log(bytes) / Math.log(k)); + return `${parseFloat((bytes / Math.pow(k, i)).toFixed(2))} ${sizes[i]}`; +} + +export function escHtml(str: string): string { + return String(str) + .replace(/&/g, '&') + .replace(//g, '>') + .replace(/"/g, '"'); +} + +export function getTagColor(name: string): string { + let hash = 0; + for (let i = 0; i < name.length; i++) hash = name.charCodeAt(i) + ((hash << 5) - hash); + + const h = Math.abs(hash % 360); + return `hsl(${h}, 70%, 65%)`; +} + +/** + * Smoothes a numeric array using a simple Gaussian kernel. + * sigma: standard deviation (radius of smoothing) + */ +export function gaussianSmooth(data: (number | null)[], sigma: number = 2): (number | null)[] { + if (sigma <= 0) return data; + + const size = Math.ceil(sigma * 3) * 2 + 1; + const kernel = new Float32Array(size); + const half = Math.floor(size / 2); + let sum = 0; + + for (let i = 0; i < size; i++) { + const x = i - half; + kernel[i] = Math.exp(-(x * x) / (2 * sigma * sigma)); + sum += kernel[i]; + } + for (let i = 0; i < size; i++) kernel[i] /= sum; + + const result: (number | null)[] = new Array(data.length); + for (let i = 0; i < data.length; i++) { + if (data[i] === null) { + result[i] = null; + continue; + } + + let val = 0; + let weightSum = 0; + for (let k = 0; k < size; k++) { + const idx = i + k - half; + if (idx >= 0 && idx < data.length && data[idx] !== null) { + val += data[idx]! * kernel[k]; + weightSum += kernel[k]; + } + } + result[i] = weightSum > 0 ? val / weightSum : data[i]; + } + return result; +} + +export function hexToRgba(hex: string, alpha: number = 1): string { + const r = parseInt(hex.slice(1, 3), 16); + const g = parseInt(hex.slice(3, 5), 16); + const b = parseInt(hex.slice(5, 7), 16); + return `rgba(${r}, ${g}, ${b}, ${alpha})`; +} diff --git a/suites-experimental/tools-gps-tracks/src/vendor-fit.ts b/suites-experimental/tools-gps-tracks/src/vendor-fit.ts new file mode 100644 index 000000000..458a369e0 --- /dev/null +++ b/suites-experimental/tools-gps-tracks/src/vendor-fit.ts @@ -0,0 +1,6 @@ +// Copyright (c) 2026, Camillo Bruni. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import FitParser from 'fit-file-parser'; +export default FitParser; diff --git a/suites-experimental/tools-gps-tracks/src/zones.ts b/suites-experimental/tools-gps-tracks/src/zones.ts new file mode 100644 index 000000000..a2d79a429 --- /dev/null +++ b/suites-experimental/tools-gps-tracks/src/zones.ts @@ -0,0 +1,60 @@ +// Copyright (c) 2026, Camillo Bruni. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +'use strict'; + +import { Parsers } from './parsers'; + +export interface ZoneRange { + name: string; + min: number; + max: number; + color: string; +} + +export const Zones = (() => { + function getPowerZones(): ZoneRange[] { + const ftp = Parsers.getFTP(); + const thresholds = [0.55, 0.75, 0.9, 1.05, 1.2, 1.5].map((t) => t * ftp); + const names = [ + 'Active Recovery', + 'Endurance', + 'Tempo', + 'Threshold', + 'VO2 Max', + 'Anaerobic', + 'Neuromuscular', + ]; + const colors = ['#82E0AA', '#A8C8A0', '#F7DC6F', '#F8C471', '#F39C12', '#E67E22', '#C0392B']; + + const zones: ZoneRange[] = []; + let last = 0; + for (let i = 0; i < thresholds.length; i++) { + zones.push({ name: names[i], min: last, max: thresholds[i], color: colors[i] }); + last = thresholds[i]; + } + zones.push({ name: names[6], min: last, max: Infinity, color: colors[6] }); + return zones; + } + + function getHRZones(): ZoneRange[] { + const thresholds = Parsers.getHRZones(); + const names = ['Recovery', 'Aerobic', 'Tempo', 'Threshold', 'Anaerobic']; + const colors = ['#82E0AA', '#F7DC6F', '#F8C471', '#FF6B6B', '#C0392B']; + + const zones: ZoneRange[] = []; + let last = 0; + for (let i = 0; i < thresholds.length; i++) { + zones.push({ name: names[i], min: last, max: thresholds[i], color: colors[i] }); + last = thresholds[i]; + } + zones.push({ name: names[4], min: last, max: Infinity, color: colors[4] }); + return zones; + } + + return { + getPowerZones, + getHRZones, + }; +})(); diff --git a/suites-experimental/tools-gps-tracks/test-data/activity_1_alps_stage.gpx.gz b/suites-experimental/tools-gps-tracks/test-data/activity_1_alps_stage.gpx.gz new file mode 100644 index 000000000..cd3428df6 Binary files /dev/null and b/suites-experimental/tools-gps-tracks/test-data/activity_1_alps_stage.gpx.gz differ diff --git a/suites-experimental/tools-gps-tracks/test-data/activity_2_forest_mtb.gpx.gz b/suites-experimental/tools-gps-tracks/test-data/activity_2_forest_mtb.gpx.gz new file mode 100644 index 000000000..7958521ff Binary files /dev/null and b/suites-experimental/tools-gps-tracks/test-data/activity_2_forest_mtb.gpx.gz differ diff --git a/suites-experimental/tools-gps-tracks/test-data/activity_3_road_race.tcx.gz b/suites-experimental/tools-gps-tracks/test-data/activity_3_road_race.tcx.gz new file mode 100644 index 000000000..9060c68ff Binary files /dev/null and b/suites-experimental/tools-gps-tracks/test-data/activity_3_road_race.tcx.gz differ diff --git a/suites-experimental/tools-gps-tracks/test-data/activity_4_intervals.tcx.gz b/suites-experimental/tools-gps-tracks/test-data/activity_4_intervals.tcx.gz new file mode 100644 index 000000000..8357b7078 Binary files /dev/null and b/suites-experimental/tools-gps-tracks/test-data/activity_4_intervals.tcx.gz differ diff --git a/suites-experimental/tools-gps-tracks/test-data/activity_5_grand_tour.fit.gz b/suites-experimental/tools-gps-tracks/test-data/activity_5_grand_tour.fit.gz new file mode 100644 index 000000000..18c520009 Binary files /dev/null and b/suites-experimental/tools-gps-tracks/test-data/activity_5_grand_tour.fit.gz differ diff --git a/suites-experimental/tools-gps-tracks/test-data/activity_6_weekend_ride.fit.gz b/suites-experimental/tools-gps-tracks/test-data/activity_6_weekend_ride.fit.gz new file mode 100644 index 000000000..1a0b5fbed Binary files /dev/null and b/suites-experimental/tools-gps-tracks/test-data/activity_6_weekend_ride.fit.gz differ diff --git a/suites-experimental/tools-gps-tracks/test-data/manifest.json b/suites-experimental/tools-gps-tracks/test-data/manifest.json new file mode 100644 index 000000000..52a312930 --- /dev/null +++ b/suites-experimental/tools-gps-tracks/test-data/manifest.json @@ -0,0 +1,8 @@ +[ + "activity_1_alps_stage.gpx.gz", + "activity_2_forest_mtb.gpx.gz", + "activity_3_road_race.tcx.gz", + "activity_4_intervals.tcx.gz", + "activity_5_grand_tour.fit.gz", + "activity_6_weekend_ride.fit.gz" +] \ No newline at end of file diff --git a/suites-experimental/tools-gps-tracks/tsconfig.json b/suites-experimental/tools-gps-tracks/tsconfig.json new file mode 100644 index 000000000..dab0956d5 --- /dev/null +++ b/suites-experimental/tools-gps-tracks/tsconfig.json @@ -0,0 +1,17 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "ESNext", + "moduleResolution": "node", + "strict": true, + "allowImportingTsExtensions": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "lib": ["DOM", "DOM.Iterable", "ESNext"], + "isolatedModules": true, + "sourceMap": true, + "noEmit": true + }, + "include": ["src/**/*"] +}