Files
SatsFaucet/frontend/bitcoin-faucet_3.html
Michaël 3734365463 first commit
Made-with: Cursor
2026-02-26 18:33:00 -03:00

375 lines
9.2 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Free Bitcoins</title>
<script src="https://challenges.cloudflare.com/turnstile/v0/api.js" async defer></script>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: Arial, sans-serif;
background: #fff;
color: #333;
}
.topbar {
background: #1a1a1a;
height: 6px;
width: 100%;
}
.container {
max-width: 900px;
margin: 0 auto;
padding: 20px;
display: flex;
gap: 30px;
}
.sidebar {
width: 140px;
flex-shrink: 0;
padding-top: 10px;
}
.sidebar p {
font-size: 13px;
margin-bottom: 8px;
color: #555;
}
.sidebar .label {
font-size: 13px;
color: #555;
margin-top: 20px;
margin-bottom: 4px;
}
.sidebar a {
display: block;
color: #0645ad;
font-size: 14px;
font-weight: bold;
text-decoration: none;
margin-bottom: 4px;
}
.sidebar a:hover { text-decoration: underline; }
.main { flex: 1; }
.header {
display: flex;
align-items: center;
margin-bottom: 30px;
border-bottom: 2px solid #eee;
padding-bottom: 16px;
}
.faucet-svg {
width: 110px;
height: 150px;
}
h1 {
font-size: 2.8rem;
font-weight: normal;
color: #bbb;
letter-spacing: 1px;
}
.content h2 {
font-size: 1.2rem;
font-weight: bold;
margin-bottom: 12px;
color: #222;
}
.content p {
font-size: 14px;
margin-bottom: 16px;
color: #444;
line-height: 1.5;
}
.turnstile-wrap {
margin-bottom: 16px;
}
/* ── Entropy Generator ── */
.entropy-box {
display: inline-flex;
align-items: center;
gap: 14px;
background: #f7f7f7;
border: 1px solid #ddd;
border-radius: 6px;
padding: 10px 16px;
margin-bottom: 16px;
}
.entropy-label {
font-size: 12px;
color: #888;
text-transform: uppercase;
letter-spacing: 0.5px;
}
.entropy-display {
font-size: 2rem;
font-weight: bold;
color: #f39c12;
min-width: 32px;
text-align: center;
font-family: monospace;
transition: color 0.15s;
}
.entropy-display.rolling {
color: #ccc;
}
.entropy-unit {
font-size: 12px;
color: #888;
}
.entropy-bar {
display: flex;
gap: 4px;
align-items: center;
}
.entropy-pip {
width: 10px;
height: 10px;
border-radius: 50%;
background: #ddd;
transition: background 0.2s;
}
.entropy-pip.active {
background: #f39c12;
}
.entropy-btn {
font-size: 11px;
background: #fff;
border: 1px solid #bbb;
border-radius: 3px;
padding: 3px 8px;
cursor: pointer;
color: #555;
}
.entropy-btn:hover { background: #f0f0f0; }
.entropy-source {
font-size: 10px;
color: #bbb;
font-style: italic;
}
/* Address row */
.address-row {
display: flex;
align-items: center;
gap: 10px;
margin-top: 8px;
}
.address-row label {
font-size: 14px;
color: #444;
}
.address-row input[type="text"] {
width: 220px;
height: 26px;
border: 1px solid #aaa;
padding: 2px 6px;
font-size: 13px;
}
.address-row button {
height: 26px;
padding: 0 14px;
font-size: 13px;
background: #f0f0f0;
border: 1px solid #aaa;
cursor: pointer;
border-radius: 2px;
}
.address-row button:hover { background: #e0e0e0; }
</style>
</head>
<body>
<div class="topbar"></div>
<div class="container">
<div class="sidebar">
<p>Sats available</p>
<p class="label">Other Sites:</p>
<a href="https://bitcoin.org/en/">Bitcoin.org</a>
<a href="https://bitcoin.org/en/buy">Bitcoin Market</a>
</div>
<div class="main">
<div class="header">
<svg class="faucet-svg" viewBox="0 0 100 140" xmlns="http://www.w3.org/2000/svg">
<ellipse cx="52" cy="14" rx="10" ry="6" fill="#c0392b"/>
<rect x="49" y="14" width="6" height="16" fill="#c0392b"/>
<rect x="30" y="28" width="44" height="30" rx="6" fill="#c0392b"/>
<rect x="72" y="36" width="24" height="14" rx="4" fill="#c0392b"/>
<rect x="85" y="50" width="12" height="32" rx="4" fill="#c0392b"/>
<ellipse cx="89" cy="91" rx="3" ry="5" fill="#5dade2" opacity="0.85"/>
<ellipse cx="92" cy="102" rx="2" ry="4" fill="#5dade2" opacity="0.65"/>
<ellipse cx="87" cy="108" rx="2" ry="3.5" fill="#5dade2" opacity="0.5"/>
<ellipse cx="91" cy="116" rx="1.5" ry="3" fill="#5dade2" opacity="0.35"/>
<rect x="10" y="33" width="22" height="18" rx="3" fill="#c0392b"/>
<rect x="6" y="36" width="8" height="12" rx="2" fill="#922b21"/>
</svg>
<h1>Free Bitcoins</h1>
</div>
<div class="content">
<h2>Get Bitcoins from the Bitcoin Faucet</h2>
<p>I'm giving away 1 to 5 satoshis per visitor; just solve the "captcha" then enter your Lightning address and press Get Some:</p>
<!-- Entropy Generator -->
<div class="entropy-box">
<div>
<div class="entropy-label">You will receive</div>
<div style="display:flex; align-items:baseline; gap:6px;">
<div class="entropy-display" id="entropy-display">?</div>
<div class="entropy-unit">sats</div>
</div>
<div class="entropy-source" id="entropy-source">pending roll…</div>
</div>
<div>
<div class="entropy-bar" id="entropy-bar">
<div class="entropy-pip" id="pip-1"></div>
<div class="entropy-pip" id="pip-2"></div>
<div class="entropy-pip" id="pip-3"></div>
<div class="entropy-pip" id="pip-4"></div>
<div class="entropy-pip" id="pip-5"></div>
</div>
<div style="margin-top:8px;">
<button class="entropy-btn" onclick="rollEntropy()">&#x21bb; Re-roll</button>
</div>
</div>
</div>
<!-- Cloudflare Turnstile CAPTCHA -->
<div class="turnstile-wrap">
<div class="cf-turnstile"
data-sitekey="0x4AAAAAAChmQ1hiZcL5Tf1s"
data-callback="onTurnstileSuccess">
</div>
</div>
<!-- Lightning Address -->
<div class="address-row">
<label>Your Lightning Address:</label>
<input type="text" id="lightning-address" placeholder="you@wallet.com">
<button onclick="handleGetSome()">Get Some!</button>
</div>
</div>
</div>
</div>
<script>
let turnstileToken = null;
let satAmount = null;
function onTurnstileSuccess(token) {
turnstileToken = token;
}
// Uses crypto.getRandomValues for cryptographic-quality entropy
function cryptoRandInt(min, max) {
const range = max - min + 1;
const buf = new Uint32Array(1);
let result;
// Rejection sampling to avoid modulo bias
do {
crypto.getRandomValues(buf);
result = buf[0] % range;
} while (buf[0] > Math.floor(0xFFFFFFFF / range) * range);
return min + result;
}
function rollEntropy() {
const display = document.getElementById('entropy-display');
const source = document.getElementById('entropy-source');
satAmount = null;
// Animate a short roll
let ticks = 0;
const totalTicks = 14;
display.classList.add('rolling');
const interval = setInterval(() => {
const temp = cryptoRandInt(1, 5);
display.textContent = temp;
updatePips(temp);
ticks++;
if (ticks >= totalTicks) {
clearInterval(interval);
// Final authoritative roll
satAmount = cryptoRandInt(1, 5);
// Collect extra entropy from timing jitter
const jitterBuf = new Uint32Array(4);
crypto.getRandomValues(jitterBuf);
const entropyHex = Array.from(jitterBuf).map(n => n.toString(16).padStart(8,'0')).join('').slice(0, 12);
display.textContent = satAmount;
display.classList.remove('rolling');
updatePips(satAmount);
source.textContent = 'entropy: 0x' + entropyHex + '…';
}
}, 60);
}
function updatePips(n) {
for (let i = 1; i <= 5; i++) {
const pip = document.getElementById('pip-' + i);
pip.classList.toggle('active', i <= n);
}
}
// Roll on page load
window.addEventListener('DOMContentLoaded', rollEntropy);
function handleGetSome() {
const address = document.getElementById('lightning-address').value.trim();
if (satAmount === null) {
alert('Please wait for the entropy roll to complete.');
return;
}
if (!turnstileToken) {
alert('Please complete the CAPTCHA first.');
return;
}
if (!address) {
alert('Please enter your Lightning address.');
return;
}
console.log('Sats to send:', satAmount);
console.log('Turnstile token:', turnstileToken);
console.log('Lightning address:', address);
alert('Request submitted! Sending ' + satAmount + ' sat(s) to ' + address);
}
</script>
</body>
</html>