Добавил звук печатающей клавиатуры
This commit is contained in:
@@ -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>
|
||||||
@@ -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);
|
||||||
|
|||||||
@@ -534,7 +534,7 @@
|
|||||||
drops[i] = 0;
|
drops[i] = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
drops[i] += 0.65;
|
drops[i] ++;
|
||||||
}
|
}
|
||||||
|
|
||||||
matrixAnimationId = requestAnimationFrame(drawMatrix);
|
matrixAnimationId = requestAnimationFrame(drawMatrix);
|
||||||
|
|||||||
Reference in New Issue
Block a user