@@ 1,14 @@
+image: alpine/latest
+packages:
+ - npm
+secrets:
+ - 3ccf561f-8f40-49dd-9d3f-e2a9e69b5219
+environment:
+ site: bpm.app.5ls.de
+tasks:
+ - install: |
+ sudo npm install --loglevel=error --global surge
+
+ - publish: |
+ . ~/.surgesecrets
+ surge $site/ $(echo https://$site | sed -e "s/\./-/g" -e "1 s/$/\.surge\.sh/")
@@ 1,134 @@
+<!doctype html>
+<html lang="en">
+ <head>
+ <meta charset="utf-8" />
+ <meta
+ name="viewport"
+ content="width=device-width, initial-scale=1, shrink-to-fit=no"
+ />
+ <meta name="apple-mobile-web-app-capable" content="yes" />
+ <meta name="apple-mobile-web-app-status-bar-style" content="black" />
+ <meta name="apple-mobile-web-app-title" content="BPM" />
+ <meta name="msapplication-TileColor" content="#000" />
+ <meta name="theme-color" content="#000" />
+ <meta name="Description" content="BPM finder" />
+ <title>BPM</title>
+ <style>
+ html,
+ body {
+ height: 100%;
+ margin: 0px;
+ padding: 0px;
+ touch-action: manipulation;
+ -webkit-touch-callout: none;
+ /* iOS Safari */
+ -webkit-user-select: none;
+ /* Safari */
+ -khtml-user-select: none;
+ /* Konqueror HTML */
+ -moz-user-select: none;
+ /* Old versions of Firefox */
+ -ms-user-select: none;
+ /* Internet Explorer/Edge */
+ user-select: none;
+ /* Non-prefixed version, currently supported by Chrome, Edge, Opera and Firefox */
+ }
+
+ body {
+ background-color: #000;
+ position: relative;
+ color: #fff;
+ fill: #fff;
+ text-align: center;
+ font-family: monospace, monospace;
+ }
+
+ .main {
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+ }
+
+ .main > div {
+ display: flex;
+ flex-direction: column;
+ }
+
+ span#bpm {
+ font-size: 3.5rem;
+ font-weight: 500;
+ line-height: 1.2;
+ }
+ </style>
+ </head>
+
+ <body>
+ <div class="main">
+ <div>
+ <h1><span id="bpm">-</span> <span>BPM</span></h1>
+ </div>
+ </div>
+
+ <script>
+ const bpm_div = document.getElementById("bpm");
+ const diffs = [];
+
+ function filterOutliers(array) {
+ // https://stackoverflow.com/a/20811670
+ const values = array.concat();
+ values.sort();
+
+ const q1 = values[Math.floor(values.length / 4)];
+ const q3 = values[Math.ceil(values.length * (3 / 4))];
+ const iqr = q3 - q1; // inter-quartile range
+
+ const maxValue = q3 + iqr * 1.5;
+ const minValue = q1 - iqr * 1.5;
+ return array.filter((x) => x <= maxValue && x >= minValue);
+ }
+
+ function calcBPM() {
+ // limit to last 10 clicks
+ const lastDiffs = diffs.slice(-10);
+ const filteredDiffs = filterOutliers(lastDiffs);
+ if (!filteredDiffs) {
+ return;
+ }
+
+ const avg =
+ filteredDiffs.reduce((a, b) => a + b, 0) / filteredDiffs.length;
+ const bpm = 60 / avg;
+
+ return Math.round(bpm);
+ }
+
+ function displayBPM() {
+ const bpm = calcBPM();
+ if (!bpm || Number.isNaN(bpm) || bpm == Infinity || bpm == 0) {
+ bpm_div.innerText = "-";
+ } else {
+ bpm_div.innerText = bpm;
+ }
+ }
+
+ let lastClick;
+ document.addEventListener("click", function (e) {
+ const now = new Date().getTime() / 1000;
+
+ if (now - lastClick > 10) {
+ diffs.length = 0;
+ lastClick = null;
+ }
+
+ if (lastClick) {
+ const diff = now - lastClick;
+ diffs.push(diff);
+ }
+
+ displayBPM();
+ lastClick = now;
+ });
+ </script>
+ </body>
+</html>