<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>文字雨</title>
<style>
@import url('https://fonts.googleapis.com/css2?family=Orienta&family=Noto+Sans+KR&family=Noto+Sans+Bamum&family=Noto+Emoji&display=swap');
body {
margin: 0;
padding: 0;
background-color: #000;
overflow: hidden;
font-family: 'Courier New', monospace;
font-size: 0.8em;
}
canvas {
display: block;
}
#toolbar {
position: absolute;
top: 50px;
left: 10px;
color: #0f0;
background-color: rgba(0, 0, 0, 0.7);
padding: 10px;
border-radius: 5px;
transition: transform 0.3s ease-in-out;
}
#toolbar.hidden {
transform: translateX(-100%);
}
button,
select {
background-color: #0f0;
color: #000;
border: none;
padding: 5px 10px;
margin: 5px;
cursor: pointer;
font-family: 'Courier New', monospace;
}
input[type='range'] {
width: 80%;
vertical-align: middle;
}
#toggleToolbar {
position: absolute;
top: 10px;
left: 10px;
z-index: 1000;
transition: background-color 0.3s ease;
}
label {
display: block;
margin-top: 10px;
}
#resetButton {
background-color: #ff4500;
color: white;
font-weight: bold;
margin-top: 15px;
}
.slider-value {
display: inline-block;
width: 40px;
text-align: right;
margin-left: 10px;
}
</style>
</head>
<body>
<canvas id="matrix"></canvas>
<button id="toggleToolbar" onclick="toggleToolbar()">☰</button>
<div id="toolbar" class="hidden">
<button onclick="togglePause()">Pause/Resume</button>
<button onclick="randomizeColor()">Random Color</button>
<button onclick="toggleCycleColor()">Cycle Color</button>
<input type="color" id="colorPicker" onchange="updateColor(this.value)" />
<label
>Delay: <span id="speedValue" class="slider-value">50</span>
<input
type="range"
id="speedSlider"
min="10"
max="200"
value="50"
oninput="updateSpeed(this.value)"
/>
</label>
<label
>Font Size: <span id="fontSizeValue" class="slider-value">16</span>
<input
type="range"
id="fontSizeSlider"
min="8"
max="32"
value="16"
oninput="updateFontSize(this.value)"
/>
</label>
<label
>Fade Speed: <span id="fadeSpeedValue" class="slider-value">0.05</span>
<input
type="range"
id="fadeSpeedSlider"
min="0.01"
max="0.2"
step="0.01"
value="0.05"
oninput="updateFadeSpeed(this.value)"
/>
</label>
<label
>Horizontal Movement:
<span id="horizontalValue" class="slider-value">3</span>
<input
type="range"
id="horizontalSlider"
min="-10"
max="10"
value="3"
step="0.1"
oninput="updateHorizontalMovement(this.value)"
/>
</label>
<label
>Character Set:
<select id="charSetSelect" onchange="updateCharacterSet(this.value)">
<option value="bamum" selected>Bamum</option>
<option value="ascii">ASCII</option>
<option value="binary">Binary</option>
<option value="korean">Korean (Hangul)</option>
<option value="matrix">Matrix (Katakana + Latin + Nums)</option>
</select>
</label>
<button onclick="toggleFullscreen()">Toggle Fullscreen</button>
<button id="resetButton" onclick="resetToDefaults()">
Reset to Defaults
</button>
</div>
<script>
const canvas = document.getElementById('matrix');
const ctx = canvas.getContext('2d');
canvas.height = window.innerHeight;
canvas.width = window.innerWidth;
let katakana =
'アイウエオカキクケコサシスセソタチツテトナニヌネノハヒフヘホマミムメモヤユヨラリルレロワヰヱヲン';
let latin = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
let nums = '0123456789';
let korean = 'ㄱㄴㄷㄹㅁㅂㅅㅇㅈㅊㅋㅌㅍㅎㅏㅑㅓㅕㅗㅛㅜㅠㅡㅣ';
let bamum =
'ꚠꚡꚢꚣꚤꚥꚦꚧꚨꚩꚪꚫꚬꚭꚮꚯꚰꚱꚲꚳꚴꚵꚶꚷꚸꚹꚺꚻꚼꚽꚾꚿꛀꛁꛂꛃꛄꛅꛆꛇꛈꛉꛊꛋꛌꛍꛎꛏꛐꛑꛒꛓꛔꛕꛖꛗꛘꛙꛚꛛꛜꛝꛞꛟꛠꛡꛢꛣꛤꛥꛦꛧꛨꛩꛪꛫꛬꛭꛮꛯ';
let emoji =
'😀😃😄😁😆😅😂🤣😊😇🙂🙃😉😌😍🥰😘😗😙😚😋😛😝😜🤪🤨🧐🤓😎🤩🥳😏😒😞😔😟😕🙁☹️😣😖😫😩🥺😢😭😤😠😡🤬🤯😳🥵🥶😱😨😰😥😓🤗🤔🤭🤫🤥😶😐😑😬🙄😯😦😧😮😲🥱😴🤤😪😵🤐🥴🤢🤮🤧😷🤒🤕🤑🤠😈👿👹👺🤡💩👻💀☠️👽👾🤖🎃😺😸😹😻😼😽🙀😿😾🥰💀✌️🌴🐢🐐🍄⚽🍻👑📸😬👀🚨🏡🐦🔥🍋🟩🍄🟫🙂↕️🕊️🏆😻🌟🧿🍀🎨🍜😀😁😂🤣😃😄😅😆😉😊😋😎😍😘🥰😗😙😚☺🙂🤗🤩🤔🤨😐😑😶🙄😏😣😥😮🤐😯😪😫🥱😴😌😛😜😝🤤😒😓😔😕🙃🤑😲☹🙁😖😞😟😤😢😭😦😧😨😩🤯😬🥴😵🤪😳🥶🥵😱😰😠😡🤬😷🤒🤕🤢🤮🤫🤥🤡🤠🥺🥳😇🤧';
let alphabet = bamum;
let fontSize = 16;
let columns = canvas.width / fontSize;
let rainDrops = [];
for (let x = 0; x < columns; x++) {
rainDrops[x] = 1;
}
let color = '#0f0';
let speed = 50;
let paused = false;
let fadeSpeed = 0.05;
let horizontalMovement = 3;
let cycleColorActive = true;
let hue = 0;
function draw() {
if (paused) return;
ctx.fillStyle = `rgba(0, 0, 0, ${fadeSpeed})`;
ctx.fillRect(0, 0, canvas.width, canvas.height);
if (cycleColorActive) {
hue = (hue + 1) % 360;
color = `hsl(${hue}, 100%, 50%)`;
updateMenuButtonColor(color);
document.getElementById('colorPicker').value = hslToHex(hue, 100, 50);
}
ctx.fillStyle = color;
let fontFamily = 'Noto Sans Bamum';
ctx.font = fontSize + 'px ' + fontFamily;
for (let i = 0; i < rainDrops.length; i++) {
const text = alphabet.charAt(
Math.floor(Math.random() * alphabet.length)
);
ctx.fillText(
text,
i * fontSize + rainDrops[i] * horizontalMovement,
rainDrops[i] * fontSize
);
if (
rainDrops[i] * fontSize > canvas.height &&
Math.random() > 0.975
) {
rainDrops[i] = 0;
}
rainDrops[i]++;
}
}
let interval = setInterval(draw, speed);
window.addEventListener('resize', () => {
canvas.height = window.innerHeight;
canvas.width = window.innerWidth;
columns = canvas.width / fontSize;
rainDrops = Array(Math.ceil(columns)).fill(1);
});
function togglePause() {
paused = !paused;
}
function randomizeColor() {
color = `#${Math.floor(Math.random() * 16777215).toString(16)}`;
document.getElementById('colorPicker').value = color;
updateMenuButtonColor(color);
}
function updateColor(newColor) {
color = newColor;
updateMenuButtonColor(newColor);
}
function updateMenuButtonColor(newColor) {
document.getElementById('toggleToolbar').style.backgroundColor =
newColor;
document.getElementById('toggleToolbar').style.color =
getContrastColor(newColor);
}
function getContrastColor(hexcolor) {
let r = parseInt(hexcolor.substr(1, 2), 16);
let g = parseInt(hexcolor.substr(3, 2), 16);
let b = parseInt(hexcolor.substr(5, 2), 16);
let yiq = (r * 299 + g * 587 + b * 114) / 1000;
return yiq >= 128 ? 'black' : 'white';
}
function updateSpeed(newSpeed) {
clearInterval(interval);
speed = newSpeed;
interval = setInterval(draw, speed);
document.getElementById('speedValue').textContent = newSpeed;
}
function updateFontSize(newSize) {
fontSize = parseInt(newSize);
columns = canvas.width / fontSize;
rainDrops = Array(Math.ceil(columns)).fill(1);
document.getElementById('fontSizeValue').textContent = newSize;
}
function updateFadeSpeed(newSpeed) {
fadeSpeed = parseFloat(newSpeed);
document.getElementById('fadeSpeedValue').textContent = newSpeed;
}
function updateHorizontalMovement(newMovement) {
horizontalMovement = parseFloat(newMovement);
document.getElementById('horizontalValue').textContent = newMovement;
}
function updateCharacterSet(set) {
switch (set) {
case 'binary':
alphabet = '01';
break;
case 'matrix':
alphabet = katakana + latin + nums;
break;
case 'korean':
alphabet = korean;
break;
case 'bamum':
alphabet = bamum;
break;
case 'emoji':
alphabet = emoji;
break;
default:
alphabet = '';
for (let i = 33; i <= 126; i++) {
alphabet += String.fromCharCode(i);
}
}
}
function toggleFullscreen() {
if (!document.fullscreenElement) {
document.documentElement.requestFullscreen();
} else {
if (document.exitFullscreen) {
document.exitFullscreen();
}
}
}
function toggleToolbar() {
document.getElementById('toolbar').classList.toggle('hidden');
}
function toggleCycleColor() {
cycleColorActive = !cycleColorActive;
}
function resetToDefaults() {
color = '#0f0';
speed = 50;
fontSize = 16;
fadeSpeed = 0.05;
horizontalMovement = 0;
cycleColorActive = true;
alphabet = bamum;
document.getElementById('colorPicker').value = color;
document.getElementById('speedSlider').value = speed;
document.getElementById('fontSizeSlider').value = fontSize;
document.getElementById('fadeSpeedSlider').value = fadeSpeed;
document.getElementById('horizontalSlider').value = horizontalMovement;
document.getElementById('charSetSelect').value = 'bamum';
updateColor(color);
updateSpeed(speed);
updateFontSize(fontSize);
updateFadeSpeed(fadeSpeed);
updateHorizontalMovement(horizontalMovement);
columns = canvas.width / fontSize;
rainDrops = Array(Math.ceil(columns)).fill(1);
}
function hslToHex(h, s, l) {
l /= 100;
const a = (s * Math.min(l, 1 - l)) / 100;
const f = (n) => {
const k = (n + h / 30) % 12;
const color = l - a * Math.max(Math.min(k - 3, 9 - k, 1), -1);
return Math.round(255 * color)
.toString(16)
.padStart(2, '0');
};
return `#${f(0)}${f(8)}${f(4)}`;
}
updateMenuButtonColor(color);
document.getElementById('speedValue').textContent = speed;
document.getElementById('fontSizeValue').textContent = fontSize;
document.getElementById('fadeSpeedValue').textContent = fadeSpeed;
document.getElementById('horizontalValue').textContent =
horizontalMovement;
let link = document.createElement('link');
link.href =
'https://fonts.googleapis.com/css2?family=Noto+Sans+KR&family=Noto+Sans+Bamum&family=Noto+Emoji&display=swap';
link.rel = 'stylesheet';
document.head.appendChild(link);
updateCharacterSet('bamum');
cycleColorActive = true;
</script>
</body>
</html>