Files
ani-web/static/login.html
T
jozamudi 8f6d4dcf77 Mobile audit fixes: iOS input auto-zoom + tap delay
Audited on iPhone SE (320px), iPhone 13, Pixel 5 emulations:
- FOUND: login/search inputs at 13.3px (< 16px) -> iOS Safari
  auto-zooms the page on focus. Fixed: 16px in style.css AND
  login.html (separate page with own CSS).
- FOUND: no touch-action anywhere -> double-tap-zoom tap delay on
  buttons. Fixed: touch-action: manipulation on interactive elements
  in both stylesheets.
- FOUND: .pl-spin missing from prefers-reduced-motion. Added.
- VERIFIED CLEAN: no horizontal overflow at 320/390/393px, controls
  row fits, chip+gauge in viewport, episode grid 66x31 ok,
  Tap-to-play gesture flow works on BOTH hls.js and native paths
  (simulated iOS autoplay block), toast fits, zero JS errors,
  desktop playback regression OK, 18/18 API tests.
assets v=7; login.html is no-store so updates without a bump.
2026-08-31 19:40:07 +00:00

63 lines
2.7 KiB
HTML

<!doctype html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>ani-web — log in</title>
<style>
:root { --bg:#0e0f13; --card:#171821; --bd:#262833; --fg:#e8e9f0; --acc:#f97316; }
body { margin:0; height:100vh; display:grid; place-items:center; background:var(--bg);
color:var(--fg); font:15px/1.5 system-ui,sans-serif; }
form { background:var(--card); border:1px solid var(--bd); border-radius:10px;
padding:2rem; width:280px; }
h1 { font-size:1.1rem; margin:0 0 1rem; color:var(--acc); }
input { width:100%; box-sizing:border-box; padding:.55rem .8rem; border-radius:8px;
border:1px solid var(--bd); background:var(--bg); color:var(--fg); margin-bottom:.6rem;
font-size:16px; } /* >=16px stops iOS Safari auto-zoom on focus */
button { width:100%; margin-top:.4rem; padding:.55rem; border:0; border-radius:8px;
background:var(--acc); color:#000; font-weight:600; cursor:pointer;
touch-action:manipulation; }
.err { color:#f87171; font-size:.85rem; margin:.6rem 0 0; min-height:1.2em; }
.banner { position:fixed; top:1rem; left:50%; transform:translateX(-50%); background:var(--card);
border:1px solid var(--bd); border-radius:8px; padding:.4rem .9rem; color:var(--muted);
font-size:.85rem; }
</style>
</head>
<body>
<p class="banner" id="banner" hidden></p>
<form onsubmit="event.preventDefault(); login()">
<h1>ani-web</h1>
<input id="user" placeholder="username" autocomplete="username">
<input type="password" id="pw" placeholder="password" autocomplete="current-password">
<button id="go">log in</button>
<p class="err" id="err"></p>
</form>
<script>
// base = this page's directory, so the app works at / or behind a
// prefixing reverse proxy (e.g. https://host/ani/)
const BASE = (() => {
const p = location.pathname;
return p.endsWith("/") ? p : p.slice(0, p.lastIndexOf("/") + 1);
})();
const $ = id => document.getElementById(id);
const next = new URLSearchParams(location.search).get("next");
(() => { // one-shot confirmations for logout / account delete
const s = new URLSearchParams(location.search);
const note = s.get("signedout") ? "Signed out." : s.get("deleted") ? "Account deleted." : null;
if (note) { const b = $("banner"); b.textContent = note; b.hidden = false; }
})();
async function login() {
$("err").textContent = "";
const r = await fetch(BASE + "login", {method: "POST",
headers: {"Content-Type": "application/json"},
body: JSON.stringify({username: $("user").value, password: $("pw").value})});
if (r.ok) {
location.href = next ? BASE + next.replace(/^\/+/, "") : BASE;
} else {
$("err").textContent = (await r.json().catch(() => ({}))).detail || "login failed";
}
}
</script>
</body>
</html>