Compare commits
10 Commits
7c3e2a5c89
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 226cff721c | |||
| e4ef99e19d | |||
| 6ffc2e9780 | |||
| ddabcb0a73 | |||
| 5ffda01a3e | |||
| c08b4cc281 | |||
| 3da72b1fb3 | |||
| 44e29f5bca | |||
| ab4ca23351 | |||
| b9a0948b2c |
@@ -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
@@ -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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
BIN
public/pwa/apple-icon-180.png
Normal file
|
After Width: | Height: | Size: 2.2 KiB |
BIN
public/pwa/apple-splash-1125-2436.jpg
Normal file
|
After Width: | Height: | Size: 56 KiB |
BIN
public/pwa/apple-splash-1136-640.jpg
Normal file
|
After Width: | Height: | Size: 24 KiB |
BIN
public/pwa/apple-splash-1170-2532.jpg
Normal file
|
After Width: | Height: | Size: 58 KiB |
BIN
public/pwa/apple-splash-1179-2556.jpg
Normal file
|
After Width: | Height: | Size: 59 KiB |
BIN
public/pwa/apple-splash-1242-2208.jpg
Normal file
|
After Width: | Height: | Size: 60 KiB |
BIN
public/pwa/apple-splash-1242-2688.jpg
Normal file
|
After Width: | Height: | Size: 64 KiB |
BIN
public/pwa/apple-splash-1284-2778.jpg
Normal file
|
After Width: | Height: | Size: 64 KiB |
BIN
public/pwa/apple-splash-1290-2796.jpg
Normal file
|
After Width: | Height: | Size: 66 KiB |
BIN
public/pwa/apple-splash-1334-750.jpg
Normal file
|
After Width: | Height: | Size: 29 KiB |
BIN
public/pwa/apple-splash-1536-2048.jpg
Normal file
|
After Width: | Height: | Size: 71 KiB |
BIN
public/pwa/apple-splash-1620-2160.jpg
Normal file
|
After Width: | Height: | Size: 76 KiB |
BIN
public/pwa/apple-splash-1668-2224.jpg
Normal file
|
After Width: | Height: | Size: 78 KiB |
BIN
public/pwa/apple-splash-1668-2388.jpg
Normal file
|
After Width: | Height: | Size: 81 KiB |
BIN
public/pwa/apple-splash-1792-828.jpg
Normal file
|
After Width: | Height: | Size: 32 KiB |
BIN
public/pwa/apple-splash-2048-1536.jpg
Normal file
|
After Width: | Height: | Size: 67 KiB |
BIN
public/pwa/apple-splash-2048-2732.jpg
Normal file
|
After Width: | Height: | Size: 102 KiB |
BIN
public/pwa/apple-splash-2160-1620.jpg
Normal file
|
After Width: | Height: | Size: 72 KiB |
BIN
public/pwa/apple-splash-2208-1242.jpg
Normal file
|
After Width: | Height: | Size: 51 KiB |
BIN
public/pwa/apple-splash-2224-1668.jpg
Normal file
|
After Width: | Height: | Size: 75 KiB |
BIN
public/pwa/apple-splash-2388-1668.jpg
Normal file
|
After Width: | Height: | Size: 75 KiB |
BIN
public/pwa/apple-splash-2436-1125.jpg
Normal file
|
After Width: | Height: | Size: 45 KiB |
BIN
public/pwa/apple-splash-2532-1170.jpg
Normal file
|
After Width: | Height: | Size: 48 KiB |
BIN
public/pwa/apple-splash-2556-1179.jpg
Normal file
|
After Width: | Height: | Size: 48 KiB |
BIN
public/pwa/apple-splash-2688-1242.jpg
Normal file
|
After Width: | Height: | Size: 51 KiB |
BIN
public/pwa/apple-splash-2732-2048.jpg
Normal file
|
After Width: | Height: | Size: 97 KiB |
BIN
public/pwa/apple-splash-2778-1284.jpg
Normal file
|
After Width: | Height: | Size: 53 KiB |
BIN
public/pwa/apple-splash-2796-1290.jpg
Normal file
|
After Width: | Height: | Size: 54 KiB |
BIN
public/pwa/apple-splash-640-1136.jpg
Normal file
|
After Width: | Height: | Size: 28 KiB |
BIN
public/pwa/apple-splash-750-1334.jpg
Normal file
|
After Width: | Height: | Size: 34 KiB |
BIN
public/pwa/apple-splash-828-1792.jpg
Normal file
|
After Width: | Height: | Size: 40 KiB |
BIN
public/pwa/manifest-icon-192.maskable.png
Normal file
|
After Width: | Height: | Size: 2.4 KiB |
BIN
public/pwa/manifest-icon-512.maskable.png
Normal file
|
After Width: | Height: | Size: 8.8 KiB |
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
|
|||||||
@@ -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'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|||||||