Unmined Bitcoin
Aus quickguide.bitcointoolz.com
<!--
// Handlungsanweisung: Füge diese Sanduhr-Komponente in Deinen Artikel ein.
// Sie ruft die aktuelle Umlaufmenge von Bitcoin per Coingecko-API ab
// und zeichnet eine animierte Sanduhr, in der die obere Kammer den noch verbleibenden Bitcoin-Anteil darstellt.
-->
<div id="bitcoin-hourglass-container" style="width:220px; margin:20px auto; text-align:center;">
<h3 style="margin-bottom:10px;">Verbleibende Bitcoin bis 21 000 000</h3>
<canvas id="bitcoinHourglass" width="200" height="400" style="border:1px solid #ccc; background:#f9f9f9;"></canvas>
<p id="btc-remaining-text" style="margin-top:10px; font-weight:bold;">Lade Daten…</p>
</div>
<script>
(function() {
const canvas = document.getElementById('bitcoinHourglass');
const ctx = canvas.getContext('2d');
const totalSupply = 21000000;
let circulating = 0;
let remaining = 0;
let animationParticles = [];
// Abschnitt 1: Hilfsfunktion, um Daten von Coingecko zu holen
async function fetchCirculatingSupply() {
try {
const response = await fetch('https://api.coingecko.com/api/v3/coins/bitcoin');
if (!response.ok) throw new Error('Netzwerkfehler');
const data = await response.json();
// Feld circulating_supply liefert die im Umlauf befindlichen BTC (ungefähr gleich minierten BTC)
circulating = data.market_data.circulating_supply;
remaining = Math.max(totalSupply - circulating, 0);
document.getElementById('btc-remaining-text').textContent =
'Noch ' + remaining.toLocaleString('de-DE') + ' BTC';
} catch (err) {
document.getElementById('btc-remaining-text').textContent = 'Fehler beim Laden der Daten';
console.error(err);
}
}
// Abschnitt 2: Zeichnet die statische Umrandung der Sanduhr
function drawHourglassFrame() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.strokeStyle = '#333';
ctx.lineWidth = 2;
// Obere Kammer (Dreieck nach unten)
ctx.beginPath();
ctx.moveTo(20, 20);
ctx.lineTo(canvas.width - 20, 20);
ctx.lineTo(canvas.width/2 + 20, canvas.height/2 - 20);
ctx.lineTo(canvas.width/2 - 20, canvas.height/2 - 20);
ctx.closePath();
ctx.stroke();
// Untere Kammer (Dreieck nach oben)
ctx.beginPath();
ctx.moveTo(20, canvas.height - 20);
ctx.lineTo(canvas.width - 20, canvas.height - 20);
ctx.lineTo(canvas.width/2 + 20, canvas.height/2 + 20);
ctx.lineTo(canvas.width/2 - 20, canvas.height/2 + 20);
ctx.closePath();
ctx.stroke();
// Verbindungslinien
ctx.beginPath();
ctx.moveTo(canvas.width/2 - 20, canvas.height/2 - 20);
ctx.lineTo(canvas.width/2 - 20, canvas.height/2 + 20);
ctx.moveTo(canvas.width/2 + 20, canvas.height/2 - 20);
ctx.lineTo(canvas.width/2 + 20, canvas.height/2 + 20);
ctx.stroke();
}
// Abschnitt 3: Zeichnet den Sand in der oberen und unteren Kammer anhand des verbleibenden Anteils
function drawSandLevels() {
const ratio = remaining / totalSupply; // Anteil der oberen Kammer
const maxHeight = (canvas.height/2 - 30); // maximal nutzbare Höhe in einer Kammer
const upperHeight = maxHeight * ratio;
const lowerHeight = maxHeight * (1 - ratio);
// Obere Kammer füllen
if (upperHeight > 0) {
ctx.fillStyle = '#f2c94c';
ctx.beginPath();
// Wir zeichnen ein kleines Trapez, das von der Mitte nach oben geht
const topY = 20;
const baseY = (canvas.height/2 - 20);
const currentY = baseY - upperHeight;
const interpolation = (currentY - topY) / (baseY - topY);
const leftX = 20 + (canvas.width/2 - 20 - 20) * (1 - interpolation);
const rightX = canvas.width - 20 - (canvas.width/2 - 20 - 20) * (1 - interpolation);
ctx.moveTo(leftX, currentY);
ctx.lineTo(rightX, currentY);
ctx.lineTo(canvas.width/2 + 20, baseY);
ctx.lineTo(canvas.width/2 - 20, baseY);
ctx.closePath();
ctx.fill();
}
// Untere Kammer füllen
if (lowerHeight > 0) {
ctx.fillStyle = '#f2c94c';
ctx.beginPath();
const bottomY = canvas.height - 20;
const baseY2 = (canvas.height/2 + 20);
const currentY2 = baseY2 + lowerHeight;
const interpolation2 = (currentY2 - baseY2) / (bottomY - baseY2);
const leftX2 = 20 + (canvas.width/2 - 20 - 20) * interpolation2;
const rightX2 = canvas.width - 20 - (canvas.width/2 - 20 - 20) * interpolation2;
ctx.moveTo(canvas.width/2 - 20, baseY2);
ctx.lineTo(canvas.width/2 + 20, baseY2);
ctx.lineTo(rightX2, currentY2);
ctx.lineTo(leftX2, currentY2);
ctx.closePath();
ctx.fill();
}
}
// Abschnitt 4: Animation von Sandkörnern, die durch die Engstelle fallen
class Particle {
constructor() {
this.reset();
}
reset() {
this.x = canvas.width/2 + (Math.random() * 20 - 10);
this.y = canvas.height/2 - 20;
this.size = Math.random() * 3 + 2;
this.vy = Math.random() * 2 + 1;
this.alpha = 1;
}
update() {
this.y += this.vy;
if (this.y > canvas.height/2 + 20) {
this.alpha -= 0.05;
}
if (this.alpha <= 0) {
this.reset();
}
}
draw() {
ctx.fillStyle = 'rgba(242,201,76,' + this.alpha + ')';
ctx.beginPath();
ctx.arc(this.x, this.y, this.size, 0, Math.PI * 2);
ctx.fill();
}
}
function initParticles(count) {
for (let i = 0; i < count; i++) {
animationParticles.push(new Particle());
}
}
function animateSand() {
drawHourglassFrame();
drawSandLevels();
animationParticles.forEach(p => {
p.update();
p.draw();
});
requestAnimationFrame(animateSand);
}
// Abschnitt 5: Initialisierung
async function init() {
await fetchCirculatingSupply();
initParticles(50);
animateSand();
// Alle 5 Minuten die Daten neu laden
setInterval(async () => {
await fetchCirculatingSupply();
}, 5 * 60 * 1000);
}
init();
})();
</script>
oder
Hilf mit, Wissen frei zu halten.
Wenn Dir dieser Artikel geholfen hat, gib 21 000 sats oder 5 € zurück – damit finanzierst Du Quellenarbeit, Aktualisierungen und den Server.
Werbefrei & unabhängig – Danke!
Zurück zur → Hauptseite