First commit
This commit is contained in:
@@ -0,0 +1,334 @@
|
||||
{% 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 Quality Control System</span>
|
||||
<span class="sub">By L.T Services, Inc</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 — 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">
|
||||
© <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 %}
|
||||
Reference in New Issue
Block a user