Compare commits

...

10 Commits

40 changed files with 4508 additions and 27 deletions

View File

@@ -4,10 +4,19 @@
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/bitcoin.svg" /> <link rel="icon" type="image/svg+xml" href="/bitcoin.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="google-site-verification" content="ouaN13NvEutq1CGQ2iALjnFFP3naSQH9u2zElneZgk0" />
<title>Bitcoin Average</title> <title>Bitcoin Average</title>
</head> </head>
<body> <body>
<div id="app"></div> <div id="app"></div>
<script type="module" src="/src/main.js"></script> <script type="module" src="/src/main.js"></script>
<!-- Google tag (gtag.js) -->
<script async src="https://www.googletagmanager.com/gtag/js?id=G-TW05X9PC25"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', 'G-TW05X9PC25');
</script>
</body> </body>
</html> </html>

4380
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -15,6 +15,7 @@
}, },
"devDependencies": { "devDependencies": {
"@vitejs/plugin-vue": "^4.5.0", "@vitejs/plugin-vue": "^4.5.0",
"vite": "^5.0.0" "vite": "^5.0.0",
"vite-plugin-pwa": "^0.17.4"
} }
} }

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 71 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 81 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 102 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 97 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.8 KiB

View File

@@ -24,7 +24,6 @@ onMounted(updateBackground);
window.addEventListener('resize', event => { window.addEventListener('resize', event => {
const newWidth = round(event.target.innerWidth); const newWidth = round(event.target.innerWidth);
const newHeight = round(event.target.innerHeight); const newHeight = round(event.target.innerHeight);
console.log(newWidth, width.value);
if (newWidth === width.value && newHeight === height.value) return; if (newWidth === width.value && newHeight === height.value) return;
width.value = newWidth; width.value = newWidth;
height.value = newHeight; height.value = newHeight;
@@ -40,11 +39,14 @@ window.addEventListener('resize', event => {
<style scoped> <style scoped>
.background { .background {
overflow: hidden;
height: 100vh;
width: 100vw;
}
.background svg {
position: absolute; position: absolute;
top: 0; top: 0;
left: 0; left: 0;
height: 100%;
width: 100%;
z-index: -1; z-index: -1;
} }
</style> </style>

View File

@@ -1,5 +1,5 @@
<script setup> <script setup>
import { ref } from 'vue'; import { ref, onMounted } from 'vue';
const TIMEOUT = 15e3; const TIMEOUT = 15e3;
@@ -34,12 +34,18 @@ const dataSources = [
pick: res => +res.result.XXBTZUSD.a[0], pick: res => +res.result.XXBTZUSD.a[0],
asset: 'USD', asset: 'USD',
}, },
// { {
// name: 'bitstamp', name: 'bitstamp',
// url: 'https://www.bitstamp.net/api/v2/ticker/btcusd', url: 'https://www.bitstamp.net/api/v2/ticker/btcusd',
// pick: res => +res.last, pick: res => +res.last,
// asset: 'USD', asset: 'USD',
// }, },
{
name: 'kucoin',
url: 'https://api.kucoin.com/api/v1/market/orderbook/level1?symbol=BTC-USDT',
pick: res => +res.data.price,
asset: 'USDT',
},
{ {
name: 'coingecko', name: 'coingecko',
url: 'https://api.coingecko.com/api/v3/simple/price?ids=bitcoin&vs_currencies=usd', url: 'https://api.coingecko.com/api/v3/simple/price?ids=bitcoin&vs_currencies=usd',
@@ -77,16 +83,28 @@ const recalc = () => {
calcStdev(); calcStdev();
}; };
const format = (price, precision = 2) => { const vibrate = () => {
const num = (price ?? 0); navigator?.vibrate([30,20,20]);
return (Number.isNaN(num) ? 0 : num).toFixed(precision);
}; };
const format = (price, precision = 2, split = false) => {
const num = (price ?? 0);
const str = (Number.isNaN(num) ? 0 : num).toFixed(precision);
if (!split) return str;
return (str.match(/(^\d{1,2}(?=(\d{3})*$)|\d{3})/g) || []).join(' ');
};
const dataSourcesEl = ref(null);
const dataSourcesHeight = ref('100%');
const average = ref(); const average = ref();
const prices = ref([]); const prices = ref([]);
const stdev = ref(); const stdev = ref();
const showSources = ref(true); const showSources = ref(true);
onMounted(() => {
dataSourcesHeight.value = `${dataSourcesEl.value.offsetHeight}px`;
});
(() => { (() => {
let idx = -1; let idx = -1;
const loop = async () => { const loop = async () => {
@@ -111,17 +129,18 @@ const showSources = ref(true);
</script> </script>
<template> <template>
<div class="box" @click="showSources = !showSources"> <div class="box" @click="showSources = !showSources; vibrate()">
<p class="average">1 BTC = <span class="nobr">{{ format(average, 0) }} USD</span></p> <p class="average"><span class="nobr">1 BTC</span> = <span class="nobr">{{ format(average, 0, true) }} USD</span></p>
<Transition> <Transition>
<div v-show="showSources"> <div v-show="showSources" ref="dataSourcesEl">
<p>Data Sources:</p> <p class="vert-margin">Data Sources:</p>
<ul> <ul>
<li v-for="(val, i) in dataSources"> <li v-for="(val, i) in dataSources">
{{ val.name }}: {{ format(prices[i]) }} <div class="li-name">{{ val.name }}:</div>
<div class="li-price">{{ format(prices[i]) }}</div>
</li> </li>
</ul> </ul>
<p>Standard Deviation: {{ format(stdev) }} ({{ format(stdev/average*100, 4) }}%)</p> <p class="vert-margin">St. Deviation: {{ format(stdev) }} ({{ format(stdev/average*100, 4) }}%)</p>
</div> </div>
</Transition> </Transition>
</div> </div>
@@ -130,16 +149,19 @@ const showSources = ref(true);
<style scoped> <style scoped>
.v-enter-active, .v-enter-active,
.v-leave-active { .v-leave-active {
transition: opacity 0.2s ease; overflow: hidden;
transition: all 80ms ease-in-out;
height: v-bind(dataSourcesHeight);
} }
.v-enter-from, .v-enter-from,
.v-leave-to { .v-leave-to {
opacity: 0; opacity: 0;
height: 0;
} }
.box { .box {
cursor: pointer; /* cursor: pointer;*/
position: absolute; position: absolute;
top: 50%; top: 50%;
@@ -163,4 +185,26 @@ const showSources = ref(true);
white-space: nowrap; white-space: nowrap;
} }
.box ul li {
display: flex;
flex-basis: 0;
overflow: hidden;
}
.box ul li div {
flex-basis: 0;
padding: 0 4px;
}
.li-name {
color: rgba(0, 0, 0, 0.5);
text-align: right;
flex-grow: 7;
}
.li-price {
text-align: left;
flex-grow: 5;
}
.vert-margin {
margin: 8px 0;
}
</style> </style>

View File

@@ -1 +1,9 @@
@import 'reset-css'; @import 'reset-css';
body {
user-select: none;
-moz-user-select: none;
-khtml-user-select: none;
-webkit-user-select: none;
-o-user-select: none;
}

View File

@@ -1,7 +1,46 @@
import { defineConfig } from 'vite' import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue' import vue from '@vitejs/plugin-vue';
import { VitePWA } from 'vite-plugin-pwa';
// https://vitejs.dev/config/ // https://vitejs.dev/config/
export default defineConfig({ export default defineConfig({
plugins: [vue()], plugins: [
}) vue(),
VitePWA({
registerType: 'autoUpdate',
injectRegister: 'auto',
manifest: {
name: 'Bitcoin Average',
short_name: 'BtcAvg',
description: 'Display bitcoin average price',
theme_color: '#f7931d',
icons: [
{
src: 'pwa/manifest-icon-192.maskable.png',
sizes: '192x192',
type: 'image/png',
purpose: 'any'
},
{
src: 'pwa/manifest-icon-192.maskable.png',
sizes: '192x192',
type: 'image/png',
purpose: 'maskable'
},
{
src: 'pwa/manifest-icon-512.maskable.png',
sizes: '512x512',
type: 'image/png',
purpose: 'any'
},
{
src: 'pwa/manifest-icon-512.maskable.png',
sizes: '512x512',
type: 'image/png',
purpose: 'maskable'
}
],
},
}),
],
});