375 lines
9.2 KiB
HTML
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()">↻ 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>
|