first commit
Made-with: Cursor
This commit is contained in:
374
frontend/bitcoin-faucet_3.html
Normal file
374
frontend/bitcoin-faucet_3.html
Normal file
@@ -0,0 +1,374 @@
|
||||
<!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>
|
||||
Reference in New Issue
Block a user