Добавил звук печатающей клавиатуры

This commit is contained in:
2026-04-21 12:57:35 +03:00
parent a20f80453b
commit 4ce5faed59
3 changed files with 291 additions and 155 deletions

View File

@@ -216,14 +216,14 @@
<div class="term-line" id="welcomeLine"></div> <div class="term-line" id="welcomeLine"></div>
<div class="term-line hidden" id="loginLine"> <div class="term-line hidden" id="loginLine">
<span class="prompt">login:</span> <span id="loginPrompt" class="prompt"></span>
<span id="usernameText" class="typed-value"></span> <span id="usernameText" class="typed-value"></span>
<span id="usernameCaret" class="caret"></span> <span id="usernameCaret" class="caret"></span>
<input id="usernameInput" class="fake-input" type="text" autocomplete="username" spellcheck="false"> <input id="usernameInput" class="fake-input" type="text" autocomplete="username" spellcheck="false">
</div> </div>
<div class="term-line hidden" id="passwordLine"> <div class="term-line hidden" id="passwordLine">
<span class="prompt">password:</span> <span id="passwordPrompt" class="prompt"></span>
<span id="passwordText" class="typed-value"></span> <span id="passwordText" class="typed-value"></span>
<span id="passwordCaret" class="caret hidden"></span> <span id="passwordCaret" class="caret hidden"></span>
<input id="passwordInput" class="fake-input" type="password" autocomplete="current-password" spellcheck="false"> <input id="passwordInput" class="fake-input" type="password" autocomplete="current-password" spellcheck="false">
@@ -249,187 +249,322 @@
<input type="hidden" name="password" id="realPassword"> <input type="hidden" name="password" id="realPassword">
</form> </form>
<script> <script>
(function () { (function () {
const canvas = document.getElementById("matrixCanvas"); const canvas = document.getElementById("matrixCanvas");
const ctx = canvas.getContext("2d"); const ctx = canvas ? canvas.getContext("2d") : null;
let matrixAnimationId = null; let matrixAnimationId = null;
let drops = []; let drops = [];
const fontSize = 18; const fontSize = 18;
const chars = "アイウエオカキクケコサシスセソ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZЖФЛ<>+*#"; const chars = "アイウエオカキクケコサシスセソ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZЖФЛ<>+*#";
let columns = 0; let columns = 0;
let lastTime = 0; let lastTime = 0;
const fps = 18; const fps = 18;
const interval = 1000 / fps; const interval = 1000 / fps;
function resizeCanvas() { function resizeCanvas() {
canvas.width = window.innerWidth; if (!canvas || !ctx) return;
canvas.height = window.innerHeight; canvas.width = window.innerWidth;
columns = Math.floor(canvas.width / fontSize); canvas.height = window.innerHeight;
drops = Array(columns).fill(1); columns = Math.floor(canvas.width / fontSize);
drops = Array(columns).fill(1);
}
function drawMatrix(time = 0) {
if (!canvas || !ctx) return;
if (time - lastTime < interval) {
matrixAnimationId = requestAnimationFrame(drawMatrix);
return;
}
lastTime = time;
ctx.fillStyle = "rgba(0, 0, 0, 0.12)";
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = "#00ff66";
ctx.font = fontSize + "px monospace";
for (let i = 0; i < drops.length; i++) {
const text = chars[Math.floor(Math.random() * chars.length)];
ctx.fillText(text, i * fontSize, drops[i] * fontSize);
if (drops[i] * fontSize > canvas.height && Math.random() > 0.985) {
drops[i] = 0;
} }
function drawMatrix(time = 0) { drops[i] += 0.65;
if (time - lastTime < interval) { }
matrixAnimationId = requestAnimationFrame(drawMatrix);
return;
}
lastTime = time; matrixAnimationId = requestAnimationFrame(drawMatrix);
}
ctx.fillStyle = "rgba(0, 0, 0, 0.12)"; resizeCanvas();
ctx.fillRect(0, 0, canvas.width, canvas.height); drawMatrix();
window.addEventListener("resize", resizeCanvas);
ctx.fillStyle = "#00ff66"; const welcomeText = "добро пожаловать в ЖФЛ";
ctx.font = fontSize + "px monospace";
for (let i = 0; i < drops.length; i++) { const welcomeLine = document.getElementById("welcomeLine");
const text = chars[Math.floor(Math.random() * chars.length)]; const loginLine = document.getElementById("loginLine");
ctx.fillText(text, i * fontSize, drops[i] * fontSize); const passwordLine = document.getElementById("passwordLine");
const submitLine = document.getElementById("submitLine");
if (drops[i] * fontSize > canvas.height && Math.random() > 0.985) { const loginPrompt = document.getElementById("loginPrompt");
drops[i] = 0; const passwordPrompt = document.getElementById("passwordPrompt");
}
drops[i] += 0.65; const usernameInput = document.getElementById("usernameInput");
} const passwordInput = document.getElementById("passwordInput");
matrixAnimationId = requestAnimationFrame(drawMatrix); const usernameText = document.getElementById("usernameText");
const passwordText = document.getElementById("passwordText");
const usernameCaret = document.getElementById("usernameCaret");
const passwordCaret = document.getElementById("passwordCaret");
const realLoginForm = document.getElementById("realLoginForm");
const realUsername = document.getElementById("realUsername");
const realPassword = document.getElementById("realPassword");
const hasServerError = !!document.querySelector(".sys-error");
let phase = "waiting_start";
let started = false;
let audioCtx = null;
function ensureAudioContext() {
if (!audioCtx) {
audioCtx = new (window.AudioContext || window.webkitAudioContext)();
}
if (audioCtx.state === "suspended") {
audioCtx.resume();
}
return audioCtx;
}
function playKeySound() {
const ctx = ensureAudioContext();
const oscillator = ctx.createOscillator();
const gainNode = ctx.createGain();
oscillator.type = "square";
oscillator.frequency.setValueAtTime(900, ctx.currentTime);
gainNode.gain.setValueAtTime(0.012, ctx.currentTime);
gainNode.gain.exponentialRampToValueAtTime(0.0001, ctx.currentTime + 0.035);
oscillator.connect(gainNode);
gainNode.connect(ctx.destination);
oscillator.start();
oscillator.stop(ctx.currentTime + 0.035);
}
function typeText(target, text, speed = 60, callback, withSound = true) {
let i = 0;
function typing() {
if (i < text.length) {
target.textContent += text[i];
if (withSound) playKeySound();
i++;
setTimeout(typing, speed);
} else if (callback) {
callback();
}
}
typing();
}
function focusCurrentInput() {
if (phase === "login") {
usernameInput.focus();
} else if (phase === "password") {
passwordInput.focus();
}
}
function showLoginStep(prefill = "") {
phase = "typing_login";
loginLine.classList.remove("hidden");
usernameCaret.classList.add("hidden");
passwordCaret.classList.add("hidden");
loginPrompt.textContent = "";
usernameText.textContent = "";
usernameInput.value = "";
typeText(loginPrompt, "login:", 60, () => {
if (prefill) {
usernameInput.value = prefill;
usernameText.textContent = prefill;
} }
resizeCanvas(); phase = "login";
drawMatrix(); usernameCaret.classList.remove("hidden");
window.addEventListener("resize", resizeCanvas); usernameInput.focus();
});
}
const welcomeText = "добро пожаловать в ЖФЛ"; function showPasswordStep(prefill = "") {
const typingSpeed = 75; phase = "typing_password";
passwordLine.classList.remove("hidden");
const welcomeLine = document.getElementById("welcomeLine"); usernameCaret.classList.add("hidden");
const loginLine = document.getElementById("loginLine"); passwordCaret.classList.add("hidden");
const passwordLine = document.getElementById("passwordLine");
const submitLine = document.getElementById("submitLine");
const usernameInput = document.getElementById("usernameInput"); passwordPrompt.textContent = "";
const passwordInput = document.getElementById("passwordInput"); passwordText.textContent = "";
passwordInput.value = "";
const usernameText = document.getElementById("usernameText"); typeText(passwordPrompt, "password:", 60, () => {
const passwordText = document.getElementById("passwordText"); if (prefill) {
passwordInput.value = prefill;
const usernameCaret = document.getElementById("usernameCaret"); passwordText.textContent = "*".repeat(prefill.length);
const passwordCaret = document.getElementById("passwordCaret");
const realLoginForm = document.getElementById("realLoginForm");
const realUsername = document.getElementById("realUsername");
const realPassword = document.getElementById("realPassword");
let phase = "typing_welcome";
function focusCurrentInput() {
if (phase === "login") {
usernameInput.focus();
} else if (phase === "password") {
passwordInput.focus();
}
} }
function showLoginStep() { phase = "password";
phase = "login"; passwordCaret.classList.remove("hidden");
loginLine.classList.remove("hidden"); passwordInput.focus();
usernameCaret.classList.remove("hidden"); });
passwordCaret.classList.add("hidden"); }
setTimeout(() => usernameInput.focus(), 30);
function submitLogin() {
phase = "submitting";
usernameCaret.classList.add("hidden");
passwordCaret.classList.add("hidden");
submitLine.classList.remove("hidden");
realUsername.value = usernameInput.value.trim();
realPassword.value = passwordInput.value;
setTimeout(() => {
realLoginForm.submit();
}, 350);
}
function startSequence(options = {}) {
if (started) return;
started = true;
const skipWelcome = !!options.skipWelcome;
const usernamePrefill = options.usernamePrefill || "";
ensureAudioContext();
phase = "typing_welcome";
welcomeLine.textContent = "";
loginPrompt.textContent = "";
passwordPrompt.textContent = "";
usernameText.textContent = "";
passwordText.textContent = "";
usernameInput.value = "";
passwordInput.value = "";
submitLine.classList.add("hidden");
loginLine.classList.add("hidden");
passwordLine.classList.add("hidden");
usernameCaret.classList.add("hidden");
passwordCaret.classList.add("hidden");
if (skipWelcome) {
welcomeLine.textContent = welcomeText;
setTimeout(() => showLoginStep(usernamePrefill), 120);
return;
}
typeText(welcomeLine, welcomeText, 75, () => {
setTimeout(() => showLoginStep(usernamePrefill), 220);
});
}
usernameInput.addEventListener("input", function () {
usernameText.textContent = usernameInput.value;
playKeySound();
});
passwordInput.addEventListener("input", function () {
passwordText.textContent = "*".repeat(passwordInput.value.length);
playKeySound();
});
usernameInput.addEventListener("keydown", function (event) {
if (event.key === "Enter") {
playKeySound();
event.preventDefault();
if (!usernameInput.value.trim()) {
return;
} }
function showPasswordStep() { usernameCaret.classList.add("hidden");
phase = "password"; showPasswordStep();
passwordLine.classList.remove("hidden"); }
usernameCaret.classList.add("hidden"); });
passwordCaret.classList.remove("hidden");
setTimeout(() => passwordInput.focus(), 30); passwordInput.addEventListener("keydown", function (event) {
if (event.key === "Enter") {
playKeySound();
event.preventDefault();
if (!usernameInput.value.trim() || !passwordInput.value) {
return;
} }
function submitLogin() { submitLogin();
phase = "submitting"; }
passwordCaret.classList.add("hidden"); });
submitLine.classList.remove("hidden");
realUsername.value = usernameInput.value.trim(); document.addEventListener("click", function () {
realPassword.value = passwordInput.value; if (!started) {
startSequence();
return;
}
focusCurrentInput();
});
setTimeout(() => { document.addEventListener("keydown", function (event) {
realLoginForm.submit(); if (!started) {
}, 350); event.preventDefault();
} startSequence();
return;
}
function typeWelcome(index = 0) { if (
if (index < welcomeText.length) { phase === "typing_welcome" ||
welcomeLine.textContent += welcomeText[index]; phase === "typing_login" ||
setTimeout(() => typeWelcome(index + 1), typingSpeed); phase === "typing_password" ||
return; phase === "submitting"
} ) {
return;
}
setTimeout(showLoginStep, 220); if (document.activeElement === usernameInput || document.activeElement === passwordInput) {
} return;
}
usernameInput.addEventListener("input", function () { if (event.key === "Tab") {
usernameText.textContent = usernameInput.value; event.preventDefault();
}); focusCurrentInput();
return;
}
passwordInput.addEventListener("input", function () { if (event.key.length === 1 || event.key === "Backspace") {
passwordText.textContent = "*".repeat(passwordInput.value.length); focusCurrentInput();
}); }
});
usernameInput.addEventListener("keydown", function (event) { if (hasServerError) {
if (event.key === "Enter") { startSequence({ skipWelcome: true });
event.preventDefault(); }
})();
</script>
if (!usernameInput.value.trim()) {
return;
}
showPasswordStep();
}
});
passwordInput.addEventListener("keydown", function (event) {
if (event.key === "Enter") {
event.preventDefault();
if (!usernameInput.value.trim() || !passwordInput.value) {
return;
}
submitLogin();
}
});
document.addEventListener("click", function () {
focusCurrentInput();
});
document.addEventListener("keydown", function (event) {
if (phase === "typing_welcome" || phase === "submitting") {
return;
}
if (document.activeElement === usernameInput || document.activeElement === passwordInput) {
return;
}
if (event.key === "Tab") {
event.preventDefault();
focusCurrentInput();
return;
}
if (event.key.length === 1 || event.key === "Backspace") {
focusCurrentInput();
}
});
typeWelcome();
})();
</script>
</body> </body>
</html> </html>

View File

@@ -1234,7 +1234,8 @@
drops[i] = 0; drops[i] = 0;
} }
drops[i] += 0.65; // drops[i] += 0.65;
drops[i] ++;
} }
matrixAnimationId = requestAnimationFrame(drawMatrix); matrixAnimationId = requestAnimationFrame(drawMatrix);

View File

@@ -534,7 +534,7 @@
drops[i] = 0; drops[i] = 0;
} }
drops[i] += 0.65; drops[i] ++;
} }
matrixAnimationId = requestAnimationFrame(drawMatrix); matrixAnimationId = requestAnimationFrame(drawMatrix);