mirror of
https://github.com/aljazceru/satshkd-vercel.git
synced 2025-12-17 05:04:24 +01:00
- Created mobile.css with responsive styles for all screen sizes - Updated table headers to use responsive font sizes (removed 2rem inline styles) - Fixed text-align from non-standard -webkit-left to standard left/right values - Made chart configuration mobile-aware with smaller fonts and fewer ticks on mobile - Optimized chart annotations and labels for mobile screens - Added responsive breakpoints for tablets (768px) and phones (480px) - Improved touch targets for mobile devices - Added landscape orientation optimizations - Enhanced dark mode toggle positioning for small screens The website now properly fits mobile screens and provides an optimal viewing experience across all device sizes.
339 lines
17 KiB
Handlebars
339 lines
17 KiB
Handlebars
<head>
|
|
<title>EURSAT</title>
|
|
<meta charset="utf-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
|
<!-- CSS only -->
|
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-wEmeIV1mKuiNpC+IOBjI7aAzPcEZeedi5yW5f2yOq55WWLwNGmvvx4Um1vskeMj0" crossorigin="anonymous">
|
|
|
|
<link rel="stylesheet" type="text/css" href="/static/skeleton.css">
|
|
<link rel="stylesheet" type="text/css" href="/static/normalize.css">
|
|
<link rel="stylesheet" type="text/css" href="/static/mobile.css">
|
|
<link rel="shortcut icon" type="image/png" href="/static/favicon.png" />
|
|
<link href="https://fonts.googleapis.com/css?family=Open+Sans|Raleway&display=swap" rel="stylesheet">
|
|
<script src="/static/chart.min.js"></script>
|
|
<script src="/static/chartjs-adapter-date-fns.min.js"></script>
|
|
<script src="/static/chartjs-plugin-annotation.min.js"></script>
|
|
<script>
|
|
// Auto-detect domain for Plausible Analytics
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
const domain = window.location.hostname;
|
|
const script = document.createElement('script');
|
|
script.async = true;
|
|
script.defer = true;
|
|
script.setAttribute('data-domain', domain);
|
|
script.src = 'https://analytics.cypherpunk.cloud/js/script.js';
|
|
document.head.appendChild(script);
|
|
});
|
|
</script>
|
|
<!-- JavaScript Bundle with Popper -->
|
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0/dist/js/bootstrap.bundle.min.js" integrity="sha384-p34f1UUtsS3wqzfto5wAAmdvj+osOnFyQFpp4Ua3gs/ZVWx6oOypYoCJhGGScy+8" crossorigin="anonymous"></script>
|
|
<style>
|
|
#historical {
|
|
position: relative;
|
|
width: 100%;
|
|
}
|
|
</style>
|
|
</head>
|
|
|
|
<body>
|
|
|
|
<div class="container" style="margin-top: 20px;">
|
|
<div class="row">
|
|
<center>
|
|
<h3> {{ Title }} <span id="current"></span> sats</h3>
|
|
</center>
|
|
<script>
|
|
var currentPrice = 0;
|
|
var ws_ticker_id, t;
|
|
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
startWebSocket();
|
|
}, false);
|
|
|
|
function startWebSocket() {
|
|
t = new WebSocket("wss://api-pub.bitfinex.com/ws/2");
|
|
|
|
t.onmessage = function(e) {
|
|
var msg = JSON.parse(e.data);
|
|
if (msg.event === 'subscribed' && msg.channel == 'ticker') {
|
|
ws_ticker_id = msg.chanId;
|
|
}
|
|
if (Array.isArray(msg) && msg[0] === ws_ticker_id && Array.isArray(msg[1])) {
|
|
updatePrice(msg[1]);
|
|
}
|
|
}
|
|
|
|
t.onopen = function(e) {
|
|
console.log('Web socket open.');
|
|
t.send(JSON.stringify({
|
|
"event": "subscribe",
|
|
"channel": "ticker",
|
|
"symbol": "tBTCEUR"
|
|
}));
|
|
setPingTimer();
|
|
}
|
|
|
|
t.onclose = function(e) {
|
|
console.log('Web socket closed. Attempting to reopen.');
|
|
setTimeout(function() {
|
|
startWebSocket();
|
|
}, 2000);
|
|
}
|
|
|
|
t.onerror = function(e) {
|
|
console.log(e);
|
|
}
|
|
}
|
|
|
|
function setPingTimer() {
|
|
setInterval(function() {
|
|
t.send("ping");
|
|
}, 10000);
|
|
}
|
|
|
|
function updatePrice(tickerArray) {
|
|
// Bitfinex ticker format: [ BID, BID_SIZE, ASK, ASK_SIZE, DAILY_CHANGE, DAILY_CHANGE_PERC, LAST_PRICE, VOLUME, HIGH, LOW ]
|
|
var lastPriceEur = tickerArray[6];
|
|
currentPrice = Math.round(100000000 / lastPriceEur); // sats per EUR
|
|
document.title = currentPrice.toLocaleString() + " sats";
|
|
document.querySelector('#current').textContent = currentPrice.toLocaleString();
|
|
}
|
|
</script>
|
|
|
|
<div id='historical'>
|
|
<canvas id="chart"></canvas>
|
|
</div>
|
|
|
|
<div class="container">
|
|
|
|
<table class="u-full-width">
|
|
<thead>
|
|
<tr>
|
|
<th style="text-align:left;">{{ date }} </th>
|
|
<th style="text-align:right;">{{ price }} </th>
|
|
<th style="text-align:right;white-space: pre;"> {{ percentchange }} </th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{{#each yeardata}}
|
|
<tr>
|
|
<td style="text-align:left">{{ this.year }}</td>
|
|
<td style="text-align:right">{{ this.sats }} sats </td>
|
|
<td style="text-align:right">{{ this.percent }} % </td>
|
|
</tr>
|
|
{{/each}}
|
|
</tbody>
|
|
</table>
|
|
|
|
|
|
</div>
|
|
<script>
|
|
// Load data and initialize chart
|
|
fetch('/{{ data_file }}')
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
// Convert date strings to Date objects
|
|
data = data.map(d => ({
|
|
...d,
|
|
date: new Date(d.date)
|
|
}));
|
|
|
|
// Calculate min and max values from data for proper axis scaling
|
|
const rateField = '{{ rate_field }}';
|
|
const values = data.map(d => d[rateField]).filter(v => v != null && v > 0);
|
|
const minValue = Math.min(...values);
|
|
const maxValue = Math.max(...values);
|
|
|
|
// Add more padding for better visualization, especially at bottom
|
|
const yAxisMin = Math.floor(minValue / 50); // Much more padding at bottom
|
|
const yAxisMax = Math.ceil(maxValue * 2.5); // Less padding at top for flatter look
|
|
|
|
// Calculate responsive height
|
|
const windowWidth = window.innerWidth;
|
|
let graphHeight;
|
|
if (windowWidth < 550) {
|
|
graphHeight = windowWidth / 1.9047;
|
|
} else if (windowWidth < 1200) {
|
|
graphHeight = windowWidth * 0.8 / 1.9047;
|
|
} else {
|
|
graphHeight = 500;
|
|
}
|
|
console.log("graph height: ", graphHeight);
|
|
|
|
// Event markers configuration
|
|
const qhLink = () => window.open('https://en.bitcoin.it/wiki/Controlled_supply', '_blank');
|
|
const qeLink = () => window.open('https://en.wikipedia.org/wiki/Quantitative_easing', '_blank');
|
|
|
|
const markers = [{
|
|
date: new Date('2010-11-03T00:00:00.000Z'),
|
|
label: 'QE2',
|
|
click: qeLink,
|
|
}, {
|
|
date: new Date('2012-09-13T00:00:00.000Z'),
|
|
label: 'QE3',
|
|
click: qeLink,
|
|
}, {
|
|
date: new Date('2012-11-28T00:00:00.000Z'),
|
|
label: 'QH1',
|
|
click: qhLink,
|
|
}, {
|
|
date: new Date('2016-07-09T00:00:00.000Z'),
|
|
label: 'QH2',
|
|
click: qhLink,
|
|
}, {
|
|
date: new Date('2019-10-11T00:00:00.000Z'),
|
|
label: 'QE4',
|
|
click: qeLink,
|
|
}, {
|
|
date: new Date('2020-05-11T00:00:00.000Z'),
|
|
label: 'QH3',
|
|
click: qhLink,
|
|
}];
|
|
|
|
// Create pattern fill
|
|
const canvas = document.getElementById('chart');
|
|
canvas.height = graphHeight;
|
|
const ctx = canvas.getContext('2d');
|
|
|
|
// Load the 100 EUR image for pattern fill
|
|
const patternImage = new Image();
|
|
patternImage.src = '/static/assets/100eur.jpg';
|
|
|
|
patternImage.onload = function() {
|
|
const pattern = ctx.createPattern(patternImage, 'repeat');
|
|
|
|
// Initialize Chart.js
|
|
const chart = new Chart(ctx, {
|
|
type: 'line',
|
|
data: {
|
|
labels: data.map(d => d.date),
|
|
datasets: [{
|
|
label: '{{ subtitle }}',
|
|
data: data.map(d => d['{{ rate_field }}']),
|
|
borderColor: 'green',
|
|
backgroundColor: pattern,
|
|
fill: true,
|
|
tension: 0.1,
|
|
borderWidth: 2,
|
|
pointRadius: 0,
|
|
pointHoverRadius: 4,
|
|
}]
|
|
},
|
|
options: {
|
|
responsive: true,
|
|
maintainAspectRatio: false,
|
|
plugins: {
|
|
title: {
|
|
display: true,
|
|
text: '{{ subtitle }}',
|
|
font: {
|
|
size: window.innerWidth < 768 ? 12 : 16
|
|
}
|
|
},
|
|
legend: {
|
|
display: false
|
|
},
|
|
tooltip: {
|
|
mode: 'index',
|
|
intersect: false,
|
|
callbacks: {
|
|
label: function(context) {
|
|
return context.parsed.y.toLocaleString() + ' sats';
|
|
}
|
|
}
|
|
},
|
|
annotation: {
|
|
annotations: markers.reduce((acc, marker, idx) => {
|
|
acc['line' + idx] = {
|
|
type: 'line',
|
|
xMin: marker.date,
|
|
xMax: marker.date,
|
|
borderColor: 'rgba(255, 99, 132, 0.5)',
|
|
borderWidth: 2,
|
|
borderDash: [5, 5],
|
|
label: {
|
|
display: true,
|
|
content: marker.label,
|
|
position: 'start',
|
|
backgroundColor: 'rgba(255, 99, 132, 0.8)',
|
|
color: 'white',
|
|
font: {
|
|
size: window.innerWidth < 768 ? 8 : 10,
|
|
weight: 'bold'
|
|
},
|
|
padding: window.innerWidth < 768 ? 2 : 4
|
|
},
|
|
click: function() {
|
|
marker.click();
|
|
}
|
|
};
|
|
return acc;
|
|
}, {})
|
|
}
|
|
},
|
|
scales: {
|
|
x: {
|
|
type: 'time',
|
|
time: {
|
|
unit: 'year',
|
|
displayFormats: {
|
|
year: 'yyyy'
|
|
}
|
|
},
|
|
ticks: {
|
|
maxTicksLimit: window.innerWidth < 768 ? 8 : 16,
|
|
font: {
|
|
size: window.innerWidth < 768 ? 10 : 12
|
|
}
|
|
},
|
|
grid: {
|
|
display: true
|
|
}
|
|
},
|
|
y: {
|
|
type: 'logarithmic',
|
|
min: yAxisMin,
|
|
max: yAxisMax,
|
|
ticks: {
|
|
callback: function(value) {
|
|
return value.toLocaleString() + ' sats';
|
|
},
|
|
maxTicksLimit: window.innerWidth < 768 ? 8 : 12,
|
|
font: {
|
|
size: window.innerWidth < 768 ? 10 : 12
|
|
}
|
|
},
|
|
grid: {
|
|
display: true
|
|
}
|
|
}
|
|
},
|
|
interaction: {
|
|
mode: 'nearest',
|
|
axis: 'x',
|
|
intersect: false
|
|
},
|
|
onClick: function(event, elements, chart) {
|
|
// Handle marker clicks
|
|
const canvasPosition = Chart.helpers.getRelativePosition(event, chart);
|
|
const dataX = chart.scales.x.getValueForPixel(canvasPosition.x);
|
|
|
|
// Check if click is near any marker
|
|
markers.forEach(marker => {
|
|
const markerX = marker.date.getTime();
|
|
const tolerance = 365 * 24 * 60 * 60 * 1000; // 1 year tolerance
|
|
|
|
if (Math.abs(dataX - markerX) < tolerance) {
|
|
marker.click();
|
|
}
|
|
});
|
|
}
|
|
}
|
|
});
|
|
};
|
|
})
|
|
.catch(error => console.error('Error loading chart data:', error));
|
|
</script>
|
|
|
|
</body>
|