mirror of
https://github.com/aljazceru/satshkd-vercel.git
synced 2025-12-17 05:04:24 +01:00
The background pattern image was not scaling appropriately on mobile screens, causing only a portion of the banknote to be visible in the pattern tiles. Changes: - Added responsive scaling logic for background pattern image - Mobile devices (<768px) now use proportional scaling based on viewport width - Tablet devices (768-1200px) use 70% scaling - Desktop devices maintain full-size image - Uses temporary canvas to create scaled pattern for optimal display This ensures the entire banknote is visible in pattern tiles across all device sizes.
426 lines
26 KiB
Handlebars
426 lines
26 KiB
Handlebars
<head>
|
||
<title>EURSAT</title>
|
||
<meta charset="utf-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||
<!-- Preconnect to external resources for faster loading -->
|
||
<link rel="preconnect" href="https://cdn.jsdelivr.net" crossorigin>
|
||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||
<link rel="preconnect" href="https://api-pub.bitfinex.com">
|
||
<link rel="preconnect" href="https://analytics.cypherpunk.cloud">
|
||
<!-- 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 defer src="/static/chart.min.js"></script>
|
||
<script defer src="/static/chartjs-adapter-date-fns.min.js"></script>
|
||
<script defer 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 defer 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 class="mt-5">
|
||
<h4 class="mb-4">The Most Spectacularly Wrong Bitcoin Predictions by ECB Officials</h4>
|
||
|
||
<div class="mb-4">
|
||
<h6><strong>November 15, 2018</strong> - Benoît Cœuré, ECB Executive Board Member</h6>
|
||
<p><em>"In more ways than one, Bitcoin is the evil spawn of the financial crisis. Lightning may strike me for saying this in the Tower of Basel – but Bitcoin was an extremely clever idea. Sadly, not every clever idea is a good idea. I believe that Agustín Carstens summed its manifold problems up well when he said that Bitcoin is 'a combination of a bubble, a Ponzi scheme and an environmental disaster.'"</em></p>
|
||
<p><small>Sources: <a href="https://www.ccn.com/bitcoin-is-the-evil-spawn-of-the-financial-crisis-european-central-bank-board-member/" target="_blank">CCN</a>, <a href="https://bitcoinist.com/the-ecb-is-right-bitcoin-was-spawned-by-the-financial-crisis-created-by-the-ecb/" target="_blank">Bitcoinist</a></small></p>
|
||
</div>
|
||
|
||
<div class="mb-4">
|
||
<h6><strong>May 9, 2019</strong> - Mario Draghi, ECB President</h6>
|
||
<p><em>"Cryptocurrencies or bitcoins, or anything like that, are not really currencies — they are assets. A euro is a euro — today, tomorrow, in a month — it's always a euro. And the ECB is behind the euro. Who is behind the cryptocurrencies? So they are very, very risky assets."</em></p>
|
||
<p><small>Sources: <a href="https://cointelegraph.com/news/ecb-president-mario-draghi-cryptos-are-not-currencies-they-are-very-risky-assets" target="_blank">Cointelegraph</a>, <a href="https://finance.yahoo.com/news/draghi-comments-ecb-press-conference-133645028.html" target="_blank">Yahoo Finance</a></small></p>
|
||
</div>
|
||
|
||
<div class="mb-4">
|
||
<h6><strong>May 29, 2019</strong> - Yves Mersch, ECB Executive Board Member</h6>
|
||
<p><em>"Bitcoin and other crypto-assets claim to need neither trust nor the backing of a sovereign. These self-proclaimed currencies, more accurately described as crypto-assets, have proved to be unfit for purpose, demonstrating that well-executed central bank policies are still the only sound basis for stability."</em></p>
|
||
<p><small>Source: <a href="https://www.bis.org/review/r190529a.htm" target="_blank">BIS</a></small></p>
|
||
</div>
|
||
|
||
<div class="mb-4">
|
||
<h6><strong>January 13, 2021</strong> - Christine Lagarde, ECB President</h6>
|
||
<p><em>"For those who had assumed that it might turn into a currency -- terribly sorry, but this is an asset and it's a highly speculative asset which has conducted some funny business and some interesting and totally reprehensible money-laundering activity."</em></p>
|
||
<p><small>Sources: <a href="https://www.bloomberg.com/news/articles/2021-01-13/lagarde-blasts-bitcoin-s-role-in-facilitating-money-laundering" target="_blank">Bloomberg</a>, <a href="https://www.theglobeandmail.com/business/international-business/european-business/article-ecbs-lagarde-calls-for-global-regulation-of-bitcoin-to-prevent-use-in/" target="_blank">The Globe and Mail</a>, <a href="https://www.coindesk.com/policy/2021/01/13/ecbs-christine-lagarde-says-speculative-bitcoin-needs-global-regulation" target="_blank">CoinDesk</a></small></p>
|
||
</div>
|
||
|
||
<div class="mb-4">
|
||
<h6><strong>April 9, 2021</strong> - Isabel Schnabel, ECB Executive Board Member</h6>
|
||
<p><em>"In our view it is wrong to describe bitcoin as a currency, because it does not fulfil the basic properties of money. It is a speculative asset without any recognisable fundamental value and is subject to massive price swings."</em></p>
|
||
<p><small>Source: <a href="https://www.ecb.europa.eu/press/inter/date/2021/html/ecb.in210409~c8c348a12c.en.html" target="_blank">ECB</a></small></p>
|
||
</div>
|
||
|
||
<div class="mb-4">
|
||
<h6><strong>May 19, 2021</strong> - Luis de Guindos, ECB Vice President</h6>
|
||
<p><em>"When you have difficulties to find out what are the real fundamentals of an investment, then what you're doing is not a real investment. This is an asset with very weak fundamentals, and that is going to be subject to a lot of volatility."</em></p>
|
||
<p><small>Sources: <a href="https://www.coindesk.com/markets/2021/05/19/crypto-assets-arent-a-real-investment-ecb-vice-president-says/" target="_blank">CoinDesk</a>, <a href="https://cointelegraph.com/news/ecb-veep-says-crypto-has-weak-fundamentals-is-not-a-real-asset-amid-30-dip" target="_blank">Cointelegraph</a></small></p>
|
||
</div>
|
||
|
||
<div class="mb-4">
|
||
<h6><strong>April 25, 2022</strong> - Fabio Panetta, ECB Executive Board Member</h6>
|
||
<p><em>"Crypto-assets are speculative assets that can cause major damage to society. They derive their value mainly from greed, they rely on the greed of others and the hope that the scheme continues unhindered. In fact, they are a gamble disguised as an investment asset."</em></p>
|
||
<p><small>Sources: <a href="https://www.bis.org/review/r220425j.htm" target="_blank">BIS</a>, <a href="https://www.paymentscardsandmobile.com/fabio-panetta-why-we-must-reign-in-the-wild-west-of-crypto/" target="_blank">Payments Cards & Mobile</a>, <a href="https://www.ledgerinsights.com/european-central-bank-ecb-panetta-likens-crypto-to-wild-west-ponzi-scheme/" target="_blank">Ledger Insights</a></small></p>
|
||
</div>
|
||
|
||
<div class="mb-4">
|
||
<h6><strong>May 22, 2022</strong> - Christine Lagarde, ECB President</h6>
|
||
<p><em>"My very humble assessment is that it is worth nothing. It is based on nothing, there is no underlying assets to act as an anchor of safety."</em></p>
|
||
<p><small>Sources: <a href="https://www.cnbc.com/amp/2022/05/23/ecb-chief-christine-lagarde-crypto-is-worth-nothing.html" target="_blank">CNBC</a>, <a href="https://forkast.news/cryptocurrency-ecb-christine-lagarde/" target="_blank">Forkast</a>, <a href="https://www.techspot.com/news/94679-european-central-bank-president-calls-crypto-worthless-based.html" target="_blank">TechSpot</a></small></p>
|
||
</div>
|
||
|
||
<div class="mb-4">
|
||
<h6><strong>December 7, 2022</strong> - Fabio Panetta, ECB Executive Board Member</h6>
|
||
<p><em>"Crypto assets should be banned if they are too energy intensive. Investors have been caught in the textbook definition of a bubble, lured by the promise of ever-rising prices. Many cryptocurrencies are just a new way of gambling."</em></p>
|
||
<p><small>Sources: <a href="https://www.coindesk.com/policy/2022/12/07/ban-energy-intensive-crypto-says-ecb-official" target="_blank">CoinDesk</a>, <a href="https://uk.finance.yahoo.com/news/ecb-ban-crypto-bitcoin-ethereum-123751593.html" target="_blank">Yahoo Finance UK</a></small></p>
|
||
</div>
|
||
|
||
<div class="mb-4">
|
||
<h6><strong>January 30, 2025</strong> - Christine Lagarde, ECB President</h6>
|
||
<p><em>"I am confident that bitcoin will not enter the reserves of any of the central banks of the General Council... reserves have to be liquid, that reserves have to be secure, that they have to be safe, that they should not be plagued by the suspicion of money laundering."</em></p>
|
||
<p><small>Sources: <a href="https://www.bloomberg.com/news/articles/2025-01-30/lagarde-is-confident-eu-central-banks-will-shun-bitcoin-reserves" target="_blank">Bloomberg</a>, <a href="https://cointelegraph.com/news/ecb-president-christine-lagarde-bitcoin-central-bank-reserves" target="_blank">Cointelegraph</a></small></p>
|
||
</div>
|
||
|
||
</div>
|
||
|
||
</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() {
|
||
// Scale the pattern image based on viewport size for better mobile display
|
||
const scaleFactor = window.innerWidth < 768
|
||
? Math.max(0.3, window.innerWidth / 1500) // Mobile: scale down proportionally
|
||
: window.innerWidth < 1200
|
||
? 0.7 // Tablet: moderate scaling
|
||
: 1.0; // Desktop: full size
|
||
|
||
// Create a temporary canvas to scale the image
|
||
const tempCanvas = document.createElement('canvas');
|
||
const tempCtx = tempCanvas.getContext('2d');
|
||
tempCanvas.width = patternImage.width * scaleFactor;
|
||
tempCanvas.height = patternImage.height * scaleFactor;
|
||
|
||
// Draw scaled image to temporary canvas
|
||
tempCtx.drawImage(patternImage, 0, 0, tempCanvas.width, tempCanvas.height);
|
||
|
||
// Create pattern from scaled image
|
||
const pattern = ctx.createPattern(tempCanvas, '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>
|