Adds the BMG-branded "AMPP Safe" hardhat photo as the visual identity for the auth + first-load surfaces. * services/web-ui/public/img/ampp-safe.jpg (52 KB, 1200w optimized JPEG) * services/web-ui/public/login.html: full redesign as a two-column hero + sign-in panel. Hero shows the hardhat photo full-bleed with a subtle AMPP Safe pill badge and broadcast-safe caption. Login + first-run admin setup forms unchanged functionally. * services/web-ui/public/index.html: brief first-visit splash overlay (~1.4s) using the same image. Dismisses to the library and uses sessionStorage so it only shows once per session.
152 lines
9.2 KiB
HTML
152 lines
9.2 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>Sign in - Z-AMPP</title>
|
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&display=swap" rel="stylesheet">
|
|
<style>
|
|
*,*::before,*::after{box-sizing:border-box;margin:0;padding:0}
|
|
:root{
|
|
--bg:#08090d;--surface:#11141b;--surface-2:#171a23;--border:#1f2330;
|
|
--accent:#e8a020;--accent-strong:#f0b740;--text:#e8eaf0;--text-dim:#7a8195;
|
|
--error:#e05555;--success:#3ecf6a;--radius:8px;--input-h:42px;
|
|
}
|
|
html,body{height:100%;background:var(--bg);color:var(--text);font-family:'Inter',-apple-system,sans-serif;font-size:14px;line-height:1.4;-webkit-font-smoothing:antialiased}
|
|
body{display:grid;grid-template-columns:1fr 460px;min-height:100vh}
|
|
@media (max-width: 900px){ body{grid-template-columns:1fr;grid-template-rows:40vh 1fr} }
|
|
|
|
/* === Hero === */
|
|
.hero{position:relative;overflow:hidden;background:radial-gradient(ellipse at 30% 40%,#1a1d28 0%,#0a0b10 70%);display:flex;align-items:flex-end;padding:48px 56px}
|
|
.hero-img{position:absolute;inset:0;background-image:url(img/ampp-safe.jpg);background-size:contain;background-position:center;background-repeat:no-repeat}
|
|
.hero-grad-bot{position:absolute;inset:auto 0 0 0;height:40%;background:linear-gradient(to top,rgba(8,9,13,.85),transparent);pointer-events:none}
|
|
.hero-stamp{position:absolute;top:32px;left:32px;display:flex;align-items:center;gap:10px;z-index:2;background:rgba(8,9,13,.55);backdrop-filter:blur(6px);padding:8px 14px 8px 12px;border:1px solid rgba(232,160,32,.25);border-radius:999px}
|
|
.hero-stamp-dot{width:8px;height:8px;background:var(--accent);border-radius:50%;box-shadow:0 0 12px var(--accent)}
|
|
.hero-stamp-text{font-size:11px;font-weight:600;letter-spacing:.14em;text-transform:uppercase;color:var(--accent-strong)}
|
|
.hero-caption{position:relative;z-index:2;max-width:520px}
|
|
.hero-caption h2{font-size:28px;font-weight:600;letter-spacing:-.01em;line-height:1.15;margin-bottom:10px}
|
|
.hero-caption p{color:var(--text-dim);font-size:14px;line-height:1.55}
|
|
.hero-caption .accent{color:var(--accent-strong)}
|
|
|
|
/* === Panel === */
|
|
.panel{display:flex;flex-direction:column;justify-content:center;padding:48px 56px;background:var(--surface);border-left:1px solid var(--border)}
|
|
@media (max-width: 900px){ .panel{padding:32px 24px;border-left:none;border-top:1px solid var(--border)} }
|
|
.brand{display:flex;align-items:center;gap:10px;margin-bottom:36px}
|
|
.brand-icon{width:32px;height:32px;background:var(--accent);color:#0a0b10;border-radius:7px;display:flex;align-items:center;justify-content:center;font-weight:700}
|
|
.brand-name{font-weight:600;font-size:15px;letter-spacing:-.01em}
|
|
.brand-sub{font-size:10px;color:var(--text-dim);letter-spacing:.14em;text-transform:uppercase;margin-top:1px}
|
|
h1{font-size:22px;font-weight:600;letter-spacing:-.01em;margin-bottom:6px}
|
|
.subtitle{color:var(--text-dim);font-size:13px;margin-bottom:28px}
|
|
.field{margin-bottom:16px}
|
|
label{display:block;font-size:11px;font-weight:500;color:var(--text-dim);text-transform:uppercase;letter-spacing:.08em;margin-bottom:6px}
|
|
input{width:100%;height:var(--input-h);background:var(--surface-2);border:1px solid var(--border);border-radius:var(--radius);color:var(--text);font-size:14px;font-family:inherit;padding:0 14px;outline:none;transition:border-color .15s, background .15s}
|
|
input:focus{border-color:var(--accent);background:var(--bg)}
|
|
.btn{width:100%;height:42px;background:var(--accent);border:none;border-radius:var(--radius);color:#0a0b10;font-weight:600;font-size:14px;font-family:inherit;cursor:pointer;margin-top:10px;transition:background .15s}
|
|
.btn:hover{background:var(--accent-strong)}
|
|
.btn:disabled{opacity:.5;cursor:not-allowed}
|
|
.flash{border-radius:var(--radius);padding:10px 12px;font-size:13px;margin-bottom:18px;display:none}
|
|
.flash.error{background:rgba(224,85,85,.1);border:1px solid rgba(224,85,85,.4);color:var(--error);display:block}
|
|
.flash.success{background:rgba(62,207,106,.1);border:1px solid rgba(62,207,106,.4);color:var(--success);display:block}
|
|
.setup-link{text-align:center;margin-top:24px;font-size:12px;color:var(--text-dim)}
|
|
.setup-link a{color:var(--accent-strong);text-decoration:none}
|
|
.setup-link a:hover{color:var(--accent);text-decoration:underline}
|
|
#setup-panel{display:none}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
|
|
<section class="hero" aria-hidden="true">
|
|
<div class="hero-img"></div>
|
|
<div class="hero-grad-bot"></div>
|
|
<div class="hero-stamp">
|
|
<span class="hero-stamp-dot"></span>
|
|
<span class="hero-stamp-text">AMPP Safe</span>
|
|
</div>
|
|
<div class="hero-caption">
|
|
<h2>Broadcast-safe media,<br><span class="accent">end to end.</span></h2>
|
|
<p>Z-AMPP is the Wild Dragon media-asset hub. Ingest live SRT & RTMP feeds, upload masters, and hand proxies to your editors in seconds.</p>
|
|
</div>
|
|
</section>
|
|
|
|
<section class="panel">
|
|
<div class="brand">
|
|
<div class="brand-icon">Z</div>
|
|
<div>
|
|
<div class="brand-name">Z-AMPP</div>
|
|
<div class="brand-sub">Media Asset Management</div>
|
|
</div>
|
|
</div>
|
|
<div id="flash" class="flash"></div>
|
|
|
|
<div id="login-panel">
|
|
<h1>Sign in</h1>
|
|
<p class="subtitle">Enter your credentials to continue.</p>
|
|
<form id="login-form" autocomplete="on">
|
|
<div class="field">
|
|
<label for="username">Username</label>
|
|
<input id="username" name="username" type="text" autocomplete="username" required placeholder="username">
|
|
</div>
|
|
<div class="field">
|
|
<label for="password">Password</label>
|
|
<input id="password" name="password" type="password" autocomplete="current-password" required placeholder="••••••••">
|
|
</div>
|
|
<button type="submit" class="btn" id="login-btn">Sign in</button>
|
|
</form>
|
|
<div class="setup-link">First time? <a href="#" id="show-setup">Create admin account</a></div>
|
|
</div>
|
|
|
|
<div id="setup-panel">
|
|
<h1>Create admin</h1>
|
|
<p class="subtitle">No accounts exist yet - create the first admin.</p>
|
|
<form id="setup-form" autocomplete="off">
|
|
<div class="field"><label for="su-username">Username</label><input id="su-username" name="username" type="text" required placeholder="admin"></div>
|
|
<div class="field"><label for="su-password">Password (min 8 chars)</label><input id="su-password" name="password" type="password" required minlength="8" placeholder="••••••••"></div>
|
|
<div class="field"><label for="su-display">Display name (optional)</label><input id="su-display" name="display_name" type="text" placeholder="Admin"></div>
|
|
<button type="submit" class="btn" id="setup-btn">Create account</button>
|
|
</form>
|
|
<div class="setup-link"><a href="#" id="show-login">Back to login</a></div>
|
|
</div>
|
|
</section>
|
|
|
|
<script>
|
|
const API = '/api/v1/auth';
|
|
const $ = id => document.getElementById(id);
|
|
const flash = $('flash');
|
|
function showFlash(m,t){ flash.textContent=m; flash.className='flash '+t; }
|
|
function clearFlash(){ flash.className='flash'; flash.textContent=''; }
|
|
|
|
$('show-setup').onclick = e => { e.preventDefault(); clearFlash(); $('login-panel').style.display='none'; $('setup-panel').style.display='block'; };
|
|
$('show-login').onclick = e => { e.preventDefault(); clearFlash(); $('setup-panel').style.display='none'; $('login-panel').style.display='block'; };
|
|
|
|
$('login-form').onsubmit = async (e) => {
|
|
e.preventDefault(); clearFlash();
|
|
const btn=$('login-btn'); btn.disabled=true; btn.textContent='Signing in...';
|
|
try{
|
|
const res = await fetch(API + '/login', {method:'POST',headers:{'Content-Type':'application/json'},credentials:'same-origin',
|
|
body: JSON.stringify({username:$('username').value.trim(),password:$('password').value})});
|
|
if(res.ok){ showFlash('Signed in - redirecting...','success'); setTimeout(()=>{location.href='/'},600); }
|
|
else{ const d=await res.json().catch(()=>({})); showFlash(d.error||'Login failed','error'); }
|
|
} catch(err){ showFlash('Network error: '+err.message,'error'); }
|
|
finally{ btn.disabled=false; btn.textContent='Sign in'; }
|
|
};
|
|
|
|
$('setup-form').onsubmit = async (e) => {
|
|
e.preventDefault(); clearFlash();
|
|
const btn=$('setup-btn'); btn.disabled=true; btn.textContent='Creating...';
|
|
try{
|
|
const res = await fetch(API + '/setup', {method:'POST',headers:{'Content-Type':'application/json'},credentials:'same-origin',
|
|
body: JSON.stringify({username:$('su-username').value.trim(),password:$('su-password').value,display_name:$('su-display').value.trim()})});
|
|
if(res.ok){
|
|
showFlash('Admin account created - you can now log in','success');
|
|
setTimeout(()=>{ $('setup-panel').style.display='none'; $('login-panel').style.display='block'; },1200);
|
|
} else{
|
|
const d=await res.json().catch(()=>({}));
|
|
showFlash(d.error||'Setup failed','error');
|
|
}
|
|
} catch(err){ showFlash('Network error: '+err.message,'error'); }
|
|
finally{ btn.disabled=false; btn.textContent='Create account'; }
|
|
};
|
|
</script>
|
|
</body></html>
|