kurviger.com Speed Stats

Show speed stats (Desktop only)

Dovrai installare un'estensione come Tampermonkey, Greasemonkey o Violentmonkey per installare questo script.

You will need to install an extension such as Tampermonkey to install this script.

Dovrai installare un'estensione come Tampermonkey o Violentmonkey per installare questo script.

Dovrai installare un'estensione come Tampermonkey o Userscripts per installare questo script.

Dovrai installare un'estensione come ad esempio Tampermonkey per installare questo script.

Dovrai installare un gestore di script utente per installare questo script.

(Ho già un gestore di script utente, lasciamelo installare!)

Advertisement:

Dovrai installare un'estensione come ad esempio Stylus per installare questo stile.

Dovrai installare un'estensione come ad esempio Stylus per installare questo stile.

Dovrai installare un'estensione come ad esempio Stylus per installare questo stile.

Dovrai installare un'estensione per la gestione degli stili utente per installare questo stile.

Dovrai installare un'estensione per la gestione degli stili utente per installare questo stile.

Dovrai installare un'estensione per la gestione degli stili utente per installare questo stile.

(Ho già un gestore di stile utente, lasciamelo installare!)

Advertisement:

// ==UserScript==
// @name        kurviger.com Speed Stats
// @namespace   shiftgeist
// @match       https://kurviger.com/*
// @match       https://kurviger.de/*
// @grant       GM_addStyle
// @version     20250417
// @author      shiftgeist
// @description Show speed stats (Desktop only)
// @license     GNU GPLv3
// @icon        https://www.google.com/s2/favicons?sz=64&domain=kurviger.com
// ==/UserScript==

GM_addStyle(`
  .leaflet-marker-icon.rounded-full {
    display: none !important;
  }

  .leaflet-zoom-animated [stroke="#ff7575"] /* 30 */ {
    stroke-width: 10px;
  }

  /* .leaflet-zoom-animated [stroke="#ffcf6e"], 50 */
  .leaflet-zoom-animated [stroke="#fff06e"], /* 70 */
  .leaflet-zoom-animated [stroke="#6eff8d"] /* 100 */ {
    stroke: #477BD6;
  }
`)

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
    setTimeout(() => waitForLoad(callback), 20 * (timeoutCounter / 2 + 1))
  }
}

waitForLoad(createStats)