public/login.html
Rama main · réplica de lectura del repositorio original.
<!DOCTYPE html>
<html lang="id">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Login — VRCloud IDE</title>
<style>
:root { color-scheme: dark; --bg:#0b1219; --panel:rgba(20,31,42,.78); --line:rgba(255,255,255,.09);
--text:#e6edf3; --dim:#8b9bab; --accent:#4da3ff; --accent2:#7cc4ff; --danger:#ff7b8a; }
* { box-sizing:border-box; }
html, body { height:100%; }
body { margin:0; display:grid; place-items:center; overflow:hidden; color:var(--text);
font-family:"Segoe UI",Inter,Ubuntu,Arial,sans-serif; background:var(--bg); }
/* latar: gradien lembut yang bergerak pelan + grid halus */
.bg { position:fixed; inset:0; overflow:hidden; z-index:0; }
.bg i { position:absolute; border-radius:50%; filter:blur(70px); opacity:.55; animation:drift 18s ease-in-out infinite alternate; }
.bg i:nth-child(1) { width:520px; height:520px; left:-140px; top:-160px; background:#1d4ed8; }
.bg i:nth-child(2) { width:460px; height:460px; right:-120px; top:10%; background:#0e7490; animation-delay:-6s; }
.bg i:nth-child(3) { width:420px; height:420px; left:30%; bottom:-200px; background:#6d28d9; animation-delay:-12s; opacity:.4; }
.bg::after { content:""; position:absolute; inset:0; opacity:.14;
background-image:linear-gradient(rgba(255,255,255,.06) 1px,transparent 1px),linear-gradient(90deg,rgba(255,255,255,.06) 1px,transparent 1px);
background-size:32px 32px; mask-image:radial-gradient(circle at 50% 40%, #000 30%, transparent 80%); }
@keyframes drift { from { transform:translate(0,0) scale(1); } to { transform:translate(40px,30px) scale(1.08); } }
.card { position:relative; z-index:1; width:min(400px,calc(100vw - 32px)); padding:36px 34px 28px;
background:var(--panel); border:1px solid var(--line); border-radius:18px;
box-shadow:0 30px 80px rgba(0,0,0,.55), inset 0 1px 0 rgba(255,255,255,.06);
backdrop-filter:blur(18px); -webkit-backdrop-filter:blur(18px);
animation:rise .45s cubic-bezier(.2,.8,.2,1) both; }
@keyframes rise { from { opacity:0; transform:translateY(14px) scale(.98); } to { opacity:1; transform:none; } }
.brand { display:flex; flex-direction:column; align-items:center; gap:10px; margin-bottom:6px; }
.logo { width:56px; height:56px; border-radius:16px; display:grid; place-items:center; color:#fff;
background:linear-gradient(135deg,#4da3ff,#2f6feb 60%,#6d5dfc); box-shadow:0 10px 30px rgba(47,111,235,.45); }
.logo svg { width:32px; height:32px; }
h1 { margin:4px 0 0; font-size:22px; font-weight:650; letter-spacing:.2px; }
.subtitle { margin:0 0 24px; text-align:center; color:var(--dim); font-size:13px; }
label { display:block; margin:14px 0 6px; color:#b9c8d3; font-size:11.5px; font-weight:600; letter-spacing:.4px; text-transform:uppercase; }
.field { position:relative; }
.field svg { position:absolute; left:12px; top:50%; transform:translateY(-50%); width:16px; height:16px; color:var(--dim); pointer-events:none; }
input { width:100%; border:1px solid var(--line); border-radius:10px; padding:12px 12px 12px 38px;
background:rgba(0,0,0,.28); color:var(--text); outline:none; font-size:14px; transition:border-color .15s, box-shadow .15s, background .15s; }
input:focus { border-color:var(--accent); box-shadow:0 0 0 3px rgba(77,163,255,.18); background:rgba(0,0,0,.36); }
input:focus + svg, .field:focus-within svg { color:var(--accent2); }
.eye { position:absolute; right:8px; top:50%; transform:translateY(-50%); width:28px; height:28px; border:0; border-radius:7px;
background:transparent; color:var(--dim); cursor:pointer; display:grid; place-items:center; padding:0; margin:0; box-shadow:none; }
.eye:hover { background:rgba(255,255,255,.07); color:var(--text); }
.eye svg { position:static; transform:none; width:16px; height:16px; pointer-events:none; }
button.go { width:100%; margin-top:22px; padding:12px; border:0; border-radius:10px; color:#fff; font-size:14px; font-weight:650; cursor:pointer;
background:linear-gradient(135deg,#4da3ff,#2f6feb); box-shadow:0 8px 24px rgba(47,111,235,.35); transition:transform .12s, filter .12s, box-shadow .12s;
display:flex; align-items:center; justify-content:center; gap:8px; }
button.go:hover { filter:brightness(1.06); transform:translateY(-1px); box-shadow:0 12px 28px rgba(47,111,235,.42); }
button.go:active { transform:translateY(0); }
button.go.busy { pointer-events:none; opacity:.85; }
button.go .sp { display:none; width:14px; height:14px; border:2px solid rgba(255,255,255,.45); border-top-color:#fff; border-radius:50%; animation:spin .8s linear infinite; }
button.go.busy .sp { display:inline-block; }
@keyframes spin { to { transform:rotate(360deg); } }
.error { display:none; margin:0 0 14px; padding:10px 12px; border-radius:9px; color:#ffd6db; font-size:12.5px;
background:rgba(255,80,100,.12); border:1px solid rgba(255,123,138,.35); align-items:center; gap:8px; }
.error.show { display:flex; animation:shake .35s; }
.error svg { width:15px; height:15px; flex:none; }
@keyframes shake { 0%,100% { transform:translateX(0); } 25% { transform:translateX(-4px); } 75% { transform:translateX(4px); } }
.foot { margin-top:22px; display:flex; justify-content:center; gap:14px; color:var(--dim); font-size:11px; }
.foot span { display:inline-flex; align-items:center; gap:5px; }
.foot svg { width:12px; height:12px; }
@media (max-width:420px) { .card { padding:28px 22px 22px; } }
</style>
</head>
<body>
<div class="bg" aria-hidden="true"><i></i><i></i><i></i></div>
<main class="card">
<div class="brand">
<div class="logo"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M17.5 19a4.5 4.5 0 0 0 .4-9A6.5 6.5 0 0 0 5.4 8.6 4 4 0 0 0 6 16.5"/><path d="M8 19h9.5"/></svg></div>
<h1>VRCloud IDE</h1>
</div>
<p class="subtitle">Masuk untuk mengakses workspace development</p>
<div id="error" class="error" role="alert">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><circle cx="12" cy="12" r="9"/><path d="M12 8v4M12 16h.01"/></svg>
<span id="error-text">Username atau password salah.</span>
</div>
<form method="post" action="/login" autocomplete="on" id="form">
<label for="username">Username</label>
<div class="field">
<input id="username" name="username" type="text" autocomplete="username" required autofocus placeholder="admin" />
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="8" r="4"/><path d="M4 20a8 8 0 0 1 16 0"/></svg>
</div>
<label for="password">Password</label>
<div class="field">
<input id="password" name="password" type="password" autocomplete="current-password" required placeholder="••••••••" />
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="11" width="18" height="11" rx="2"/><path d="M7 11V7a5 5 0 0 1 10 0v4"/></svg>
<button type="button" class="eye" id="eye" title="Tampilkan password" aria-label="Tampilkan password">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M2 12s3.5-7 10-7 10 7 10 7-3.5 7-10 7S2 12 2 12z"/><circle cx="12" cy="12" r="3"/></svg>
</button>
</div>
<button type="submit" class="go" id="go"><span class="sp"></span><span>Masuk</span></button>
</form>
<div class="foot">
<span><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="11" width="18" height="11" rx="2"/><path d="M7 11V7a5 5 0 0 1 10 0v4"/></svg>Koneksi terenkripsi</span>
<span><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="9"/><path d="M12 7v5l3 2"/></svg>Sesi tersimpan</span>
</div>
</main>
<script>
(function () {
// Language follows the IDE setting (localStorage vrcloud_lang); with no saved preference, an English browser gets English.
var EN = (function () {
try { var v = localStorage.getItem("vrcloud_lang"); if (v) return v === "en"; } catch (e) {}
return /^en\b/i.test(navigator.language || "");
})();
var S = EN ? {
subtitle: "Sign in to access your development workspace", wrong: "Wrong username or password.", locked: "Too many attempts. Try again in 5 minutes.",
show: "Show password", hide: "Hide password", go: "Sign in", tls: "Encrypted connection", session: "Session is remembered",
} : {
subtitle: "Masuk untuk mengakses workspace development", wrong: "Username atau password salah.", locked: "Terlalu banyak percobaan. Coba lagi dalam 5 menit.",
show: "Tampilkan password", hide: "Sembunyikan password", go: "Masuk", tls: "Koneksi terenkripsi", session: "Sesi tersimpan",
};
if (EN) {
document.documentElement.lang = "en"; document.title = "Sign in \u2014 VRCloud IDE";
document.querySelector(".subtitle").textContent = S.subtitle;
document.getElementById("error-text").textContent = S.wrong;
document.querySelector("#go span:last-child").textContent = S.go;
var foot = document.querySelectorAll(".foot span");
if (foot[0]) foot[0].lastChild.textContent = S.tls;
if (foot[1]) foot[1].lastChild.textContent = S.session;
var eyeBtn = document.getElementById("eye"); eyeBtn.title = S.show; eyeBtn.setAttribute("aria-label", S.show);
}
var q = new URLSearchParams(location.search), err = document.getElementById("error"), txt = document.getElementById("error-text");
if (q.has("error")) {
if (q.get("error") === "locked") txt.textContent = S.locked;
err.classList.add("show");
}
var pw = document.getElementById("password"), eye = document.getElementById("eye");
eye.addEventListener("click", function () {
var show = pw.type === "password"; pw.type = show ? "text" : "password";
var lbl = show ? S.hide : S.show;
eye.title = lbl; eye.setAttribute("aria-label", lbl);
pw.focus();
});
document.getElementById("form").addEventListener("submit", function () { document.getElementById("go").classList.add("busy"); });
if (location.protocol !== "https:") document.querySelector(".foot span").style.display = "none";
})();
</script>
</body>
</html>