Files
yggdrasil/server/templates/login.html
T
2026-10-03 20:24:37 +02:00

82 lines
3.6 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Login — Yggdrasil</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link href="https://fonts.googleapis.com/css2?family=VT323&display=swap" rel="stylesheet">
<link rel="stylesheet" href="{{ url_for('static', filename='retro.css') }}">
</head>
<body>
<div class="dialog">
<div class="dialog-title">[ Login ]</div>
<h1>Yggdrasil</h1>
{% if error %}
<p class="error">! {{ error }}</p>
{% endif %}
<form method="post" action="{{ url_for('login', next=next) if next else url_for('login') }}">
<input type="hidden" name="next" value="{{ next }}">
<div>
<label for="username">Username:</label><br>
<div class="term-field">
<input type="text" id="username" name="username" required autocomplete="username" autofocus>
<span class="caret-block" aria-hidden="true"></span>
</div>
</div>
<div>
<label for="password">Password:</label><br>
<div class="term-field">
<input type="password" id="password" name="password" required autocomplete="current-password">
<span class="caret-block" aria-hidden="true"></span>
</div>
</div>
<button type="submit">[ OK ] Log in</button>
</form>
</div>
<script>
// Block caret, Midnight Commander style.
// The native caret is hidden (only when this script runs) and a
// blinking block is drawn exactly where the text cursor is.
// Measurement is exact because the font is monospace.
// Without JS the fields keep their normal native caret.
function attachBlockCaret(input) {
const field = input.closest('.term-field');
const block = field.querySelector('.caret-block');
input.classList.add('js-caret');
const ctx = document.createElement('canvas').getContext('2d');
function place() {
const cs = getComputedStyle(input);
ctx.font = cs.font;
const pos = (input.selectionStart !== null && input.selectionStart !== undefined)
? input.selectionStart
: input.value.length;
let text = input.value.slice(0, pos);
if (input.type === 'password') text = '\u2022'.repeat(text.length);
const padLeft = parseFloat(cs.paddingLeft) || 0;
const borderLeft = parseFloat(cs.borderLeftWidth) || 0;
const x = borderLeft + padLeft + ctx.measureText(text).width - (input.scrollLeft || 0);
block.style.left = Math.max(borderLeft + padLeft, x) + 'px';
}
function show() {
field.classList.add('active');
requestAnimationFrame(place);
}
function hide() { field.classList.remove('active'); }
['input', 'change', 'keyup', 'click', 'select', 'focus'].forEach((ev) => {
input.addEventListener(ev, show);
});
input.addEventListener('blur', hide);
window.addEventListener('load', () => setTimeout(place, 300)); // late autofill
if (document.fonts && document.fonts.ready) document.fonts.ready.then(place);
if (document.activeElement === input) show();
}
document.querySelectorAll('.term-field input').forEach(attachBlockCaret);
</script>
</body>
</html>