kurviger.com Speed Stats

Show speed stats (Desktop only)

当前为 2025-03-04 提交的版本,查看 最新版本

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name        kurviger.com Speed Stats
// @namespace   shiftgeist
// @match       https://kurviger.com/*
// @match       https://kurviger.de/*
// @grant       none
// @version     20250304
// @author      shiftgeist
// @description Show speed stats (Desktop only)
// @license     GNU GPLv3
// @icon        https://kurviger.com/assets/favicon.ico
// ==/UserScript==

(async function () {
  'use strict'

  const debug = window.localStorage.getItem('debug-log') === 'true'
  const doc = document
  const elDropdown = '.selection-dropdown'
  const elSource = '.heightgraph-container-inner'
  let sourceObserver = null
  let stats = null
  let timeoutCounter = 0

  function log(...params) {
    if (debug) {
      console.debug('[Speed]', ...params)
    }
  }

  function getSpeed(color) {
    switch (color) {
      case 'rgb(255, 117, 117)':
        return '30'
      case 'rgb(255, 207, 110)':
        return '50'
      case 'rgb(255, 240, 110)':
        return '70'
      case 'rgb(110, 255, 141)':
        return '100'
      default:
      case 'rgb(179, 179, 179)':
        return 'unknown'
    }
  }

  function getData() {
    const source = doc.querySelector(elSource)

    const childrenEl = Array.from(source.querySelectorAll('.area'))
    const containerWidth = childrenEl.reduce(
      (acc, v) => acc + v.getBoundingClientRect().width - 4,
      0
    )

    const children = childrenEl.map(v => ({
      width: v.getBoundingClientRect().width / containerWidth,
      speed: getSpeed(v.style.fill),
    }))

    const speeds = {
      30: 0,
      50: 0,
      70: 0,
      100: 0,
      unknown: 0,
    }

    for (const s of children) {
      speeds[s.speed] += s.width
    }

    speeds.sum = speeds['30'] + speeds['50'] + speeds['70'] + speeds['100']
    speeds.average = speeds['30'] * 30 + speeds['50'] * 50 + speeds['70'] * 70 + speeds['100'] * 100

    return speeds
  }

  function isSpeedGraph() {
    return doc.querySelector(elDropdown).value === '2'
  }

  function prepareStats() {
    if (stats) {
      log('remove first', stats)
      stats.remove()
      stats = null
    }

    createListener(createStats)

    if (!isSpeedGraph()) {
      log('wrong graph type')
      return
    }

    return true
  }

  function createListener(callback) {
    if (!sourceObserver) {
      log('registering observer')

      sourceObserver = new MutationObserver(() => {
        if (prepareStats()) {
          callback()
        }
      })

      sourceObserver.observe(document.querySelector(elSource), { childList: true })
    }
  }

  function createStats() {
    if (!prepareStats()) return

    log('create stats')

    stats = doc.createElement('div')
    stats.id = 'SpeedStats'
    stats.style.zIndex = '9999'
    stats.style.position = 'absolute'
    stats.style.top = '155px'
    stats.style.left = '50%'
    stats.style.transform = 'translate(-50%)'
    doc.querySelector('.heightgraph').appendChild(stats)

    const d = getData()

    stats.textContent += `${(d['30'] * 100).toFixed()}% 30 km/h`
    stats.textContent += `, `
    stats.textContent += `${(d['50'] * 100).toFixed()}% 50 km/h`
    stats.textContent += `, `
    stats.textContent += `${(d['70'] * 100).toFixed()}% 70 km/h`
    stats.textContent += `, `
    stats.textContent += `${(d['100'] * 100).toFixed()}% 100 km/h`
    stats.textContent += `, `
    stats.textContent += `Average ${d.average.toFixed()} km/h`

    log('Stats created', d)
  }

  function waitForLoad(callback) {
    log('wait for load')

    if (!window.location.href.includes('/plan') || timeoutCounter > 20) {
      log('wrong url or too many retries')
      return
    }

    if (doc.querySelectorAll(`${elSource}, ${elDropdown}`).length) {
      log('has source')
      callback()
    } else {
      timeoutCounter += 1
      timeout = setTimeout(() => waitForLoad(callback), 20 * (timeoutCounter / 2 + 1))
    }
  }

  waitForLoad(createStats)
})()