mirror of
https://github.com/tzuwei-huang/random-number-generator.git
synced 2026-08-24 14:10:29 +09:00
305 lines
15 KiB
HTML
305 lines
15 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="en">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>Fair Random Number Generator</title>
|
||
<style>
|
||
body { font-family: sans-serif; text-align: center; margin: 20px 0; background-color: #f4f7f6; font-size: 1.2em; }
|
||
.container { width: 98%; max-width: 98%; margin: 0 auto; padding: 40px 20px; background: white; border: 1px solid #eee; border-radius: 15px; box-shadow: 0 10px 25px rgba(0,0,0,0.05); position: relative; box-sizing: border-box; }
|
||
.lang-switch { position: absolute; top: 20px; right: 20px; }
|
||
.lang-link { text-decoration: none; color: #3498db; font-size: 1.2em; font-weight: bold; cursor: pointer; }
|
||
h1 { color: #2c3e50; margin-bottom: 30px; font-size: 3em; }
|
||
.result { font-size: 16em; font-weight: bold; color: #e74c3c; margin: 30px 0; text-shadow: 2px 2px 4px rgba(0,0,0,0.1); }
|
||
.info { font-size: 2em; color: #7f8c8d; margin-bottom: 30px; }
|
||
button { padding: 20px 40px; font-size: 2em; cursor: pointer; transition: all 0.2s; }
|
||
button:hover { transform: translateY(-2px); box-shadow: 0 5px 15px rgba(0,0,0,0.1); }
|
||
button:active { transform: translateY(0); }
|
||
.reset-btn { background-color: #f8f9fa; border: 1px solid #dee2e6; color: #6c757d; text-decoration: none; padding: 15px 30px; border-radius: 6px; display: inline-block; margin-top: 20px; font-size: 1.3em; cursor: pointer; }
|
||
.reset-btn:hover { background-color: #e9ecef; }
|
||
.generate-btn { background-color: #2ecc71; border: none; color: white; border-radius: 6px; font-weight: bold; }
|
||
.generate-btn:hover { background-color: #27ae60; }
|
||
input[type="number"] { padding: 15px; border: 2px solid #edeff2; border-radius: 8px; width: 150px; outline: none; font-size: 2em; text-align: center; }
|
||
input[type="number"]:focus { border-color: #3498db; }
|
||
label { font-size: 2em; color: #34495e; margin-right: 10px; }
|
||
.drawn-list { margin-top: 40px; text-align: left; background: #fdfdfd; padding: 25px; border: 1px dashed #dcdde1; border-radius: 10px; }
|
||
.drawn-numbers { font-size: 3em; font-weight: bold; color: #34495e; word-wrap: break-word; line-height: 1.2; margin-top: 10px; }
|
||
.mode-container { margin: 10px 0; font-size: 1.2em; color: #7f8c8d; }
|
||
.mode-badge { padding: 5px 15px; border-radius: 20px; font-weight: bold; background-color: #ecf0f1; color: #34495e; margin: 0 10px; }
|
||
.mode-toggle-btn { background-color: #3498db; border: none; color: white; padding: 8px 16px; border-radius: 6px; cursor: pointer; font-size: 1em; text-decoration: none; display: inline-block; transition: background 0.2s; }
|
||
.mode-toggle-btn:hover { background-color: #2980b9; }
|
||
.repeat-badge { background-color: #ff9f43; color: white; padding: 5px 15px; border-radius: 50px; font-size: 0.15em; vertical-align: middle; margin-left: 10px; box-shadow: 0 4px 10px rgba(230, 126, 34, 0.3); animation: bounce 0.5s ease infinite alternate; }
|
||
@keyframes bounce { from { transform: translateY(0); } to { transform: translateY(-5px); } }
|
||
.hidden { display: none; }
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<div class="container">
|
||
<a href="https://github.com/tzuwei-huang/random-number-generator" target="_blank" style="position: absolute; top: 15px; left: 20px;">
|
||
<svg width="36" height="36" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||
<path d="M12 0C5.37 0 0 5.37 0 12c0 5.3 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61-.546-1.387-1.333-1.756-1.333-1.756-1.09-.745.083-.73.083-.73 1.205.085 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 21.795 24 17.295 24 12c0-6.63-5.37-12-12-12z"/>
|
||
</svg>
|
||
</a>
|
||
<div class="lang-switch">
|
||
<a id="lang-toggle" class="lang-link">繁體中文</a>
|
||
</div>
|
||
<h1 id="title">🎲 Random Picker</h1>
|
||
|
||
<div id="setup-view">
|
||
<div class="mode-container">
|
||
<span id="mode-text">Mode:</span>
|
||
<span id="mode-badge" class="mode-badge">Fair (No Repeat)</span>
|
||
<button id="toggle-mode-setup" class="mode-toggle-btn">Switch Mode</button>
|
||
</div>
|
||
<p id="enter-range" style="font-size: 2em; color: #34495e;">Enter the range:</p>
|
||
<label id="label-min">Min:</label>
|
||
<input type="number" id="min-val" value="1" min="0">
|
||
<label id="label-max">Max:</label>
|
||
<input type="number" id="max-val" value="100" min="0">
|
||
<br><br>
|
||
<button id="start-btn" class="generate-btn">Start</button>
|
||
</div>
|
||
|
||
<div id="active-view" class="hidden">
|
||
<div class="mode-container">
|
||
<span id="mode-text-active">Mode:</span>
|
||
<span id="mode-badge-active" class="mode-badge">Fair (No Repeat)</span>
|
||
<button id="toggle-mode-active" class="mode-toggle-btn">Switch Mode</button>
|
||
</div>
|
||
<div class="info">
|
||
<span id="range-text">Range:</span> <span id="display-range"></span><br>
|
||
<span id="remaining-text">Remaining:</span> <span id="remaining-count"></span>
|
||
</div>
|
||
|
||
<div class="result">
|
||
<span id="result-display">?</span>
|
||
<span id="repeat-badge" class="repeat-badge hidden">Repeat!</span>
|
||
</div>
|
||
|
||
<div id="action-buttons">
|
||
<button id="pick-btn" class="generate-btn">Pick One</button>
|
||
<div id="all-picked-msg" class="hidden">
|
||
<p style="font-size: 2em; color: #2ecc71; font-weight: bold;" id="all-picked-text">🎉 All numbers picked!</p>
|
||
<button id="restart-btn" class="generate-btn">Restart</button>
|
||
</div>
|
||
</div>
|
||
|
||
<a id="reset-btn" class="reset-btn">Reset</a>
|
||
|
||
<div id="drawn-container" class="drawn-list hidden">
|
||
<strong id="drawn-title" style="font-size: 2em;">Drawn Numbers:</strong>
|
||
<div id="drawn-numbers" class="drawn-numbers"></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<script>
|
||
const TRANSLATIONS = {
|
||
"en": {
|
||
"title": "🎲 Random Picker",
|
||
"enter_range": "Enter the range:",
|
||
"min": "Min:",
|
||
"max": "Max:",
|
||
"start": "Start",
|
||
"range": "Range:",
|
||
"remaining": "Remaining:",
|
||
"pick_one": "Pick One",
|
||
"all_picked": "🎉 All numbers picked!",
|
||
"restart": "Restart",
|
||
"reset": "Reset",
|
||
"reset_range": "Reset Range",
|
||
"drawn_numbers": "Drawn Numbers:",
|
||
"lang_toggle": "繁體中文",
|
||
"mode": "Mode:",
|
||
"mode_fair": "Fair (No Repeat)",
|
||
"mode_random": "Pure Random",
|
||
"toggle_mode": "Switch Mode",
|
||
"repeat": "Repeat!"
|
||
},
|
||
"zh": {
|
||
"title": "🎲 隨機抽號器",
|
||
"enter_range": "輸入範圍:",
|
||
"min": "最小值:",
|
||
"max": "最大值:",
|
||
"start": "開始",
|
||
"range": "範圍:",
|
||
"remaining": "剩餘數量:",
|
||
"pick_one": "抽一個",
|
||
"all_picked": "🎉 全部抽完啦!",
|
||
"restart": "重新開始",
|
||
"reset": "重置",
|
||
"reset_range": "重新設定範圍",
|
||
"drawn_numbers": "已抽出的號碼:",
|
||
"lang_toggle": "English",
|
||
"mode": "模式:",
|
||
"mode_fair": "公平模式 (不重複)",
|
||
"mode_random": "純隨機模式",
|
||
"toggle_mode": "切換模式",
|
||
"repeat": "重複!"
|
||
}
|
||
};
|
||
|
||
let currentLang = 'zh';
|
||
let pool = [];
|
||
let drawn = [];
|
||
let currentRange = [1, 100];
|
||
let isActive = false;
|
||
let mode = 'fair';
|
||
let isRepeat = false;
|
||
|
||
function updateUI() {
|
||
const t = TRANSLATIONS[currentLang];
|
||
document.getElementById('lang-toggle').innerText = t.lang_toggle;
|
||
document.getElementById('title').innerText = t.title;
|
||
document.getElementById('enter-range').innerText = t.enter_range;
|
||
document.getElementById('label-min').innerText = t.min;
|
||
document.getElementById('label-max').innerText = t.max;
|
||
document.getElementById('start-btn').innerText = t.start;
|
||
document.getElementById('range-text').innerText = t.range;
|
||
document.getElementById('remaining-text').innerText = t.remaining;
|
||
document.getElementById('pick-btn').innerText = t.pick_one;
|
||
document.getElementById('all-picked-text').innerText = t.all_picked;
|
||
document.getElementById('restart-btn').innerText = t.restart;
|
||
document.getElementById('drawn-title').innerText = t.drawn_numbers;
|
||
|
||
const modeLabel = mode === 'fair' ? t.mode_fair : t.mode_random;
|
||
document.getElementById('mode-text').innerText = t.mode;
|
||
document.getElementById('mode-badge').innerText = modeLabel;
|
||
document.getElementById('toggle-mode-setup').innerText = t.toggle_mode;
|
||
document.getElementById('mode-text-active').innerText = t.mode;
|
||
document.getElementById('mode-badge-active').innerText = modeLabel;
|
||
document.getElementById('toggle-mode-active').innerText = t.toggle_mode;
|
||
document.getElementById('repeat-badge').innerText = t.repeat;
|
||
|
||
if (isActive) {
|
||
document.getElementById('setup-view').classList.add('hidden');
|
||
document.getElementById('active-view').classList.remove('hidden');
|
||
document.getElementById('display-range').innerText = `${currentRange[0]} ~ ${currentRange[1]}`;
|
||
document.getElementById('remaining-count').innerText = pool.length;
|
||
|
||
if (mode === 'fair') {
|
||
if (pool.length > 0) {
|
||
document.getElementById('pick-btn').classList.remove('hidden');
|
||
document.getElementById('all-picked-msg').classList.add('hidden');
|
||
document.getElementById('reset-btn').innerText = t.reset;
|
||
} else {
|
||
document.getElementById('pick-btn').classList.add('hidden');
|
||
document.getElementById('all-picked-msg').classList.remove('hidden');
|
||
document.getElementById('reset-btn').innerText = t.reset_range;
|
||
}
|
||
} else {
|
||
document.getElementById('pick-btn').classList.remove('hidden');
|
||
document.getElementById('all-picked-msg').classList.add('hidden');
|
||
document.getElementById('reset-btn').innerText = t.reset;
|
||
}
|
||
|
||
const padding = String(currentRange[1]).length;
|
||
const formattedDrawn = drawn.map(n => String(n).padStart(padding, '0'));
|
||
document.getElementById('drawn-numbers').innerText = formattedDrawn.join(', ');
|
||
|
||
if (drawn.length > 0) {
|
||
document.getElementById('drawn-container').classList.remove('hidden');
|
||
const lastNumber = drawn[0];
|
||
document.getElementById('result-display').innerText = String(lastNumber).padStart(padding, '0');
|
||
if (isRepeat) {
|
||
document.getElementById('repeat-badge').classList.remove('hidden');
|
||
} else {
|
||
document.getElementById('repeat-badge').classList.add('hidden');
|
||
}
|
||
} else {
|
||
document.getElementById('drawn-container').classList.add('hidden');
|
||
document.getElementById('result-display').innerText = '?';
|
||
document.getElementById('repeat-badge').classList.add('hidden');
|
||
}
|
||
} else {
|
||
document.getElementById('setup-view').classList.remove('hidden');
|
||
document.getElementById('active-view').classList.add('hidden');
|
||
}
|
||
}
|
||
|
||
function getRandomInt(min, max) {
|
||
const range = max - min + 1;
|
||
const array = new Uint32Array(1);
|
||
window.crypto.getRandomValues(array);
|
||
return (array[0] % range) + min;
|
||
}
|
||
|
||
function toggleMode() {
|
||
mode = mode === 'fair' ? 'random' : 'fair';
|
||
updateUI();
|
||
}
|
||
|
||
document.getElementById('toggle-mode-setup').onclick = toggleMode;
|
||
document.getElementById('toggle-mode-active').onclick = toggleMode;
|
||
|
||
document.getElementById('lang-toggle').onclick = () => {
|
||
currentLang = currentLang === 'en' ? 'zh' : 'en';
|
||
updateUI();
|
||
};
|
||
|
||
document.getElementById('start-btn').onclick = () => {
|
||
const min = parseInt(document.getElementById('min-val').value);
|
||
const max = parseInt(document.getElementById('max-val').value);
|
||
if (isNaN(min) || isNaN(max) || min > max) {
|
||
alert(currentLang === 'en' ? "Invalid range!" : "無效的範圍!");
|
||
return;
|
||
}
|
||
if (max - min + 1 > 10000) {
|
||
alert(currentLang === 'en' ? "Range too large (max 10000)!" : "範圍太大 (最大 10000)!");
|
||
return;
|
||
}
|
||
currentRange = [min, max];
|
||
initPool(min, max);
|
||
isActive = true;
|
||
pickOne();
|
||
updateUI();
|
||
};
|
||
|
||
function initPool(min, max) {
|
||
pool = [];
|
||
for (let i = min; i <= max; i++) {
|
||
pool.push(i);
|
||
}
|
||
drawn = [];
|
||
isRepeat = false;
|
||
}
|
||
|
||
function pickOne() {
|
||
let picked;
|
||
if (mode === 'fair') {
|
||
if (pool.length === 0) return;
|
||
const idx = getRandomInt(0, pool.length - 1);
|
||
picked = pool.splice(idx, 1)[0];
|
||
isRepeat = false;
|
||
} else {
|
||
picked = getRandomInt(currentRange[0], currentRange[1]);
|
||
isRepeat = drawn.includes(picked);
|
||
const poolIdx = pool.indexOf(picked);
|
||
if (poolIdx !== -1) {
|
||
pool.splice(poolIdx, 1);
|
||
}
|
||
}
|
||
drawn.unshift(picked);
|
||
}
|
||
|
||
document.getElementById('pick-btn').onclick = () => {
|
||
pickOne();
|
||
updateUI();
|
||
};
|
||
|
||
document.getElementById('restart-btn').onclick = () => {
|
||
initPool(currentRange[0], currentRange[1]);
|
||
pickOne();
|
||
updateUI();
|
||
};
|
||
|
||
document.getElementById('reset-btn').onclick = () => {
|
||
isActive = false;
|
||
updateUI();
|
||
};
|
||
|
||
updateUI();
|
||
</script>
|
||
</body>
|
||
</html>
|