Files
LT_Janitorial_Quality_Control/app/templates/auth/login.html
T
2026-06-10 13:27:47 -04:00

334 lines
14 KiB
HTML

{% extends "base.html" %}
{% block title %}Login - Janitorial QC{% endblock %}
{% block extra_css %}
<style>
/* ════════════════════════════════════════════════════════════════════
Login page — split-screen layout
Theme colors preserved: brand #1a56db / #1a4ab5 / #1551c7,
quality-green #16a34a, amber #d97706 / #f59e0b, light-blue surfaces.
════════════════════════════════════════════════════════════════════ */
body.login-page { min-height: 100vh; background: #fff; }
/* Let the split fill the viewport: neutralize base.html content wrapper */
body.login-page .container-fluid.mt-4 {
margin-top: 0 !important;
padding: 0;
max-width: none;
}
/* Flash messages float as top-center toasts instead of pushing layout */
body.login-page .container-fluid.mt-4 > .alert {
position: fixed;
top: 1rem; left: 50%;
transform: translateX(-50%);
z-index: 1080;
min-width: 320px; max-width: 92vw;
box-shadow: 0 8px 28px rgba(0,0,0,.18);
}
.login-split { display: flex; min-height: 100vh; }
/* ── LEFT: brand / QC showcase ──────────────────────────────────────── */
.login-brand {
flex: 1 1 55%;
position: relative;
overflow: hidden;
background: linear-gradient(135deg, #1a56db 0%, #1a4ab5 55%, #1551c7 100%);
color: #fff;
padding: 3rem 3.5rem;
display: flex;
flex-direction: column;
justify-content: center;
gap: 1.75rem;
}
.login-brand::before { /* dotted texture */
content: "";
position: absolute; inset: 0;
background-image: radial-gradient(rgba(255,255,255,.12) 1.5px, transparent 1.5px);
background-size: 26px 26px;
opacity: .55; pointer-events: none;
}
.login-brand::after { /* soft glow */
content: "";
position: absolute; width: 460px; height: 460px;
right: -140px; top: -120px;
background: radial-gradient(circle, rgba(255,255,255,.14), transparent 70%);
border-radius: 50%; pointer-events: none;
}
.login-brand > * { position: relative; z-index: 2; max-width: 500px; }
.brand-logo { display: flex; align-items: center; gap: .8rem; }
.brand-logo .logo-badge {
width: 50px; height: 50px; border-radius: 13px;
background: rgba(255,255,255,.16);
border: 1px solid rgba(255,255,255,.22);
display: flex; align-items: center; justify-content: center;
font-size: 1.6rem;
}
.brand-logo .name { font-weight: 700; font-size: 1.3rem; line-height: 1.1; }
.brand-logo .sub { font-size: .8rem; opacity: .82; }
.brand-headline { font-size: 2.05rem; font-weight: 700; line-height: 1.22; }
.brand-sub { opacity: .86; font-size: 1.02rem; line-height: 1.55; }
.feature-list { list-style: none; padding: 0; margin: 0; display: flex; flex-direction: column; gap: 1.05rem; }
.feature-list li { display: flex; align-items: flex-start; gap: .85rem; }
.feature-list .fi {
flex: 0 0 auto; width: 32px; height: 32px; border-radius: 50%;
background: #16a34a; color: #fff;
display: flex; align-items: center; justify-content: center;
font-size: .95rem; box-shadow: 0 0 0 4px rgba(22,163,74,.22);
}
.feature-list .ft { font-weight: 600; line-height: 1.2; }
.feature-list .fd { font-size: .85rem; opacity: .82; }
.stat-row { display: flex; gap: .75rem; flex-wrap: wrap; }
.stat-chip {
background: rgba(255,255,255,.12);
border: 1px solid rgba(255,255,255,.20);
border-radius: 12px; padding: .6rem 1.05rem; min-width: 102px;
}
.stat-chip .v { font-size: 1.4rem; font-weight: 700; line-height: 1; }
.stat-chip .l { font-size: .7rem; opacity: .82; text-transform: uppercase; letter-spacing: .6px; margin-top: .25rem; }
.stat-chip.g .v { color: #86efac; }
.stat-chip.a .v { color: #fcd34d; }
/* Decorative janitorial / QC illustration anchored bottom-right */
.brand-illustration {
position: absolute; right: -8px; bottom: -10px;
width: 340px; max-width: 42%; height: auto;
z-index: 1; opacity: .95; pointer-events: none;
}
/* ── RIGHT: sign-in form ────────────────────────────────────────────── */
.login-form-side {
flex: 1 1 45%;
display: flex; align-items: center; justify-content: center;
padding: 2.5rem 1.5rem; background: #fff;
}
.login-form-inner { width: 100%; max-width: 400px; }
.form-mobile-brand { display: none; } /* shown only on small screens */
.form-mobile-brand .logo-badge {
width: 56px; height: 56px; border-radius: 15px;
background: #1a56db; color: #fff;
display: flex; align-items: center; justify-content: center; font-size: 1.8rem;
}
.form-mobile-brand .name { font-weight: 700; color: #1a56db; font-size: 1.2rem; margin-top: .55rem; }
.form-mobile-brand .sub { color: #6b7280; font-size: .8rem; }
.login-form-inner h2 { font-weight: 700; color: #16264a; margin-bottom: .25rem; }
.login-form-inner .lead-sub { color: #6b7280; margin-bottom: 1.75rem; }
.login-form-side .input-group-text {
background: #f0f7ff; border-color: #d4e1f4; color: #1a56db;
}
.login-form-side .form-control:focus {
border-color: #1a56db; box-shadow: 0 0 0 .2rem rgba(26,86,219,.18);
}
.login-form-side .pw-toggle {
border-color: #d4e1f4; color: #6b8fc7; background: #fff;
}
.login-form-side .pw-toggle:hover { color: #1a56db; }
.btn-login {
background: #1a56db; border-color: #1a56db; color: #fff; font-weight: 600;
}
.btn-login:hover, .btn-login:focus { background: #1648b8; border-color: #1648b8; color: #fff; }
.login-foot { margin-top: 2rem; text-align: center; color: #9aa3af; font-size: .8rem; }
/* ── Responsive: collapse to single column ──────────────────────────── */
@media (max-width: 991.98px) {
.login-brand { display: none; }
.form-mobile-brand {
display: flex; flex-direction: column; align-items: center;
text-align: center; margin-bottom: 1.75rem;
}
.login-form-inner h2, .login-form-inner .lead-sub { text-align: center; }
}
</style>
{% endblock %}
{% block content %}
<script>document.body.classList.add('login-page');</script>
<div class="login-split">
{# ══ LEFT: brand / quality-control showcase ══ #}
<section class="login-brand">
<div class="brand-logo">
<span class="logo-badge"><i class="bi bi-clipboard-check-fill"></i></span>
<span>
<span class="name d-block">Janitorial QC</span>
<span class="sub">Quality Control System</span>
</span>
</div>
<div>
<h1 class="brand-headline mb-2">Quality control for spotless facilities.</h1>
<p class="brand-sub mb-0">Standardized inspections, real-time issue tracking, and
clear performance reporting &mdash; keeping every facility audit-ready.</p>
</div>
<ul class="feature-list">
<li>
<span class="fi"><i class="bi bi-clipboard-check"></i></span>
<span><span class="ft d-block">Standardized facility inspections</span>
<span class="fd">Consistent checklists across every site.</span></span>
</li>
<li>
<span class="fi"><i class="bi bi-search"></i></span>
<span><span class="ft d-block">Real-time issue tracking</span>
<span class="fd">Log, assign, and resolve quality issues fast.</span></span>
</li>
<li>
<span class="fi"><i class="bi bi-bar-chart-line"></i></span>
<span><span class="ft d-block">Automated quality reporting</span>
<span class="fd">Scores and trends, ready to share.</span></span>
</li>
</ul>
<div class="stat-row">
<div class="stat-chip g"><div class="v">98%</div><div class="l">Pass rate</div></div>
<div class="stat-chip"> <div class="v">247</div><div class="l">Inspections</div></div>
<div class="stat-chip a"><div class="v">4.8</div><div class="l">Avg score</div></div>
</div>
{# ── Decorative inspection / janitorial illustration ── #}
<svg class="brand-illustration" viewBox="0 0 360 340" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
<!-- clipboard card -->
<rect x="48" y="36" width="210" height="252" rx="16" fill="#ffffff" opacity="0.97"/>
<rect x="48" y="36" width="210" height="52" rx="16" fill="#1a56db"/>
<rect x="48" y="64" width="210" height="24" fill="#1a56db"/>
<rect x="118" y="26" width="70" height="24" rx="10" fill="#dceeff" stroke="#93c5fd" stroke-width="2"/>
<text x="153" y="70" font-family="Arial,sans-serif" font-size="17" font-weight="700"
fill="#ffffff" text-anchor="middle">Inspection</text>
<!-- row 1 (pass) -->
<circle cx="78" cy="124" r="11" fill="#dcfce7" stroke="#86efac" stroke-width="2"/>
<polyline points="73,124 78,130 85,118" fill="none" stroke="#16a34a" stroke-width="3"
stroke-linecap="round" stroke-linejoin="round"/>
<line x1="98" y1="124" x2="236" y2="124" stroke="#cfe0f5" stroke-width="5" stroke-linecap="round"/>
<!-- row 2 (pass) -->
<circle cx="78" cy="160" r="11" fill="#dcfce7" stroke="#86efac" stroke-width="2"/>
<polyline points="73,160 78,166 85,154" fill="none" stroke="#16a34a" stroke-width="3"
stroke-linecap="round" stroke-linejoin="round"/>
<line x1="98" y1="160" x2="236" y2="160" stroke="#cfe0f5" stroke-width="5" stroke-linecap="round"/>
<!-- row 3 (flag / amber) -->
<circle cx="78" cy="196" r="11" fill="#fef9c3" stroke="#fde047" stroke-width="2"/>
<line x1="78" y1="190" x2="78" y2="197" stroke="#d97706" stroke-width="3" stroke-linecap="round"/>
<circle cx="78" cy="201" r="1.6" fill="#d97706"/>
<line x1="98" y1="196" x2="206" y2="196" stroke="#cfe0f5" stroke-width="5" stroke-linecap="round"/>
<!-- row 4 -->
<circle cx="78" cy="232" r="11" fill="#eef4fc" stroke="#c2d9f8" stroke-width="2"/>
<line x1="98" y1="232" x2="236" y2="232" stroke="#e4edf9" stroke-width="5" stroke-linecap="round"/>
<!-- shield-check badge -->
<g transform="translate(214,210)">
<path d="M30 0 L58 10 L58 38 Q58 62 30 74 Q2 62 2 38 L2 10 Z"
fill="#16a34a"/>
<polyline points="16,38 26,49 46,25" fill="none" stroke="#ffffff" stroke-width="5"
stroke-linecap="round" stroke-linejoin="round"/>
</g>
<!-- magnifying glass -->
<circle cx="118" cy="206" r="56" fill="none" stroke="#1a56db" stroke-width="0" />
<circle cx="118" cy="206" r="46" fill="#dbeafe" fill-opacity="0.55" stroke="#1a56db" stroke-width="7"/>
<line x1="151" y1="239" x2="182" y2="270" stroke="#1a56db" stroke-width="11" stroke-linecap="round"/>
<text x="118" y="214" font-family="Arial,sans-serif" font-size="26" font-weight="800"
fill="#1a56db" text-anchor="middle">QC</text>
</svg>
</section>
{# ══ RIGHT: sign-in form ══ #}
<section class="login-form-side">
<div class="login-form-inner">
<div class="form-mobile-brand">
<span class="logo-badge"><i class="bi bi-clipboard-check-fill"></i></span>
<span class="name">Janitorial Quality Control System</span>
<span class="sub">By L.T Services, Inc</span>
</div>
<h2>Welcome</h2>
<p class="lead-sub">Sign in to access your inspections and reports.</p>
<form method="POST" action="{{ url_for('auth.login') }}">
{{ form.hidden_tag() }}
<div class="mb-3">
{{ form.username.label(class="form-label fw-semibold") }}
<div class="input-group input-group-lg">
<span class="input-group-text"><i class="bi bi-person"></i></span>
{{ form.username(class="form-control", placeholder="Enter username", autofocus=true) }}
</div>
{% if form.username.errors %}
<div class="text-danger small mt-1">
{% for error in form.username.errors %}{{ error }}{% endfor %}
</div>
{% endif %}
</div>
<div class="mb-4">
<div class="d-flex justify-content-between align-items-baseline">
{{ form.password.label(class="form-label fw-semibold mb-0") }}
<a href="{{ url_for('auth.forgot_password') }}"
class="small text-muted text-decoration-none">Forgot password?</a>
</div>
<div class="input-group input-group-lg mt-1">
<span class="input-group-text"><i class="bi bi-lock"></i></span>
{{ form.password(class="form-control", placeholder="Enter password") }}
<button class="btn pw-toggle" type="button" id="pw-toggle" tabindex="-1"
aria-label="Show password">
<i class="bi bi-eye" id="pw-toggle-icon"></i>
</button>
</div>
{% if form.password.errors %}
<div class="text-danger small mt-1">
{% for error in form.password.errors %}{{ error }}{% endfor %}
</div>
{% endif %}
</div>
<div class="mb-4 form-check">
{{ form.remember_me(class="form-check-input") }}
{{ form.remember_me.label(class="form-check-label text-muted") }}
</div>
<button type="submit" class="btn btn-login btn-lg w-100">
<i class="bi bi-box-arrow-in-right me-1"></i> Log In
</button>
</form>
<div class="login-foot">
&copy; <span id="copy-year"></span> Janitorial QC System
</div>
</div>
</section>
</div>
<script>
document.getElementById('copy-year').textContent = new Date().getFullYear();
// Password visibility toggle
(function () {
var btn = document.getElementById('pw-toggle');
var icon = document.getElementById('pw-toggle-icon');
var input = document.getElementById('password');
if (btn && input) {
btn.addEventListener('click', function () {
var show = input.type === 'password';
input.type = show ? 'text' : 'password';
icon.classList.toggle('bi-eye', !show);
icon.classList.toggle('bi-eye-slash', show);
btn.setAttribute('aria-label', show ? 'Hide password' : 'Show password');
});
}
})();
// Remove .login-page when navigating away (SPA-style guard)
window.addEventListener('pagehide', function () {
document.body.classList.remove('login-page');
});
</script>
{% endblock %}