82 lines
3.6 KiB
HTML
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>
|