add welcome page and README.md

This commit is contained in:
2026-05-02 00:44:52 +03:30
parent 139b3e26da
commit 63a2aeebbc
19 changed files with 319 additions and 42 deletions

View File

@@ -12,7 +12,7 @@ bp = Blueprint("main", __name__)
def index():
if current_user.is_authenticated:
return redirect(url_for("main.dashboard"))
return redirect(url_for("auth.login"))
return render_template("welcome.html")
@bp.get("/dashboard")

View File

@@ -257,11 +257,152 @@ a {
text-align: center;
}
.welcome-page {
margin: -2rem -1.5rem -3rem;
background: #f3f6f7;
}
.welcome-hero {
min-height: 78vh;
position: relative;
display: flex;
align-items: center;
isolation: isolate;
overflow: hidden;
padding: 7rem clamp(1.5rem, 6vw, 7rem) 5rem;
background: url("../img/welcome-hero.png") center / cover no-repeat;
color: #ffffff;
}
.welcome-hero::before {
content: "";
position: absolute;
inset: 0;
z-index: -1;
background: rgba(6, 15, 28, 0.58);
}
.welcome-nav {
position: absolute;
top: 1.5rem;
left: clamp(1.5rem, 6vw, 7rem);
right: clamp(1.5rem, 6vw, 7rem);
display: flex;
align-items: center;
justify-content: space-between;
gap: 1rem;
}
.welcome-brand {
color: #ffffff;
font-size: 1.05rem;
font-weight: 700;
text-decoration: none;
}
.welcome-copy {
max-width: 760px;
}
.welcome-copy h1 {
margin: 0;
font-size: clamp(3rem, 7vw, 7rem);
font-weight: 800;
line-height: 0.95;
}
.welcome-copy p {
max-width: 660px;
margin: 1.4rem 0 0;
color: rgba(255, 255, 255, 0.86);
font-size: clamp(1.05rem, 1.5vw, 1.35rem);
}
.welcome-eyebrow {
color: rgba(255, 255, 255, 0.72);
}
.welcome-actions {
display: flex;
flex-wrap: wrap;
gap: 0.85rem;
margin-top: 2rem;
}
.welcome-section,
.workflow-band {
padding: clamp(3rem, 6vw, 5rem) clamp(1.5rem, 6vw, 7rem);
}
.welcome-section-header {
max-width: 760px;
margin-bottom: 1.5rem;
}
.welcome-section-header h2,
.workflow-band h2 {
margin: 0;
font-size: clamp(2rem, 3vw, 3rem);
}
.feature-grid {
display: grid;
grid-template-columns: repeat(4, minmax(0, 1fr));
gap: 1rem;
}
.feature-card {
min-height: 250px;
padding: 1.25rem;
border: 1px solid var(--border);
border-radius: 8px;
background: #ffffff;
}
.feature-card span {
color: var(--accent);
font-weight: 800;
}
.feature-card h3 {
margin: 2.5rem 0 0.75rem;
font-size: 1.15rem;
}
.feature-card p {
color: var(--muted);
margin: 0;
}
.workflow-band {
display: grid;
grid-template-columns: minmax(0, 0.75fr) minmax(320px, 1fr);
gap: 2rem;
align-items: start;
background: #172033;
color: #ffffff;
}
.workflow-band .eyebrow,
.workflow-list {
color: rgba(255, 255, 255, 0.72);
}
.workflow-list {
display: grid;
gap: 1rem;
margin: 0;
padding-left: 1.25rem;
font-size: 1.05rem;
}
@media (max-width: 992px) {
.stats-grid,
.upload-grid,
.detail-grid,
.kiosk-grid {
.kiosk-grid,
.feature-grid,
.workflow-band {
grid-template-columns: 1fr 1fr;
}
}
@@ -276,11 +417,22 @@ a {
.stats-grid,
.upload-grid,
.detail-grid,
.kiosk-grid {
.kiosk-grid,
.feature-grid,
.workflow-band {
grid-template-columns: 1fr;
}
.detail-list {
grid-template-columns: 1fr;
}
.welcome-hero {
min-height: 82vh;
padding-top: 6rem;
}
.welcome-actions .btn {
width: 100%;
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB

View File

@@ -82,7 +82,7 @@ function renderFaces(faces) {
const confidence = face.confidence === null ? "n/a" : `${Math.round(face.confidence * 100)}%`;
card.innerHTML = `
<h3>${escapeHtml(face.full_name)}</h3>
<p>${escapeHtml(face.employee_id)} · Confidence ${confidence}<br>${escapeHtml(face.last_presence)}</p>
<p>${escapeHtml(face.employee_id)} - Confidence ${confidence}<br>${escapeHtml(face.last_presence)}</p>
<div class="result-actions">
<button class="btn btn-success btn-sm" data-event-type="check_in" data-person-id="${face.person_id}">Check in</button>
<button class="btn btn-warning btn-sm" data-event-type="check_out" data-person-id="${face.person_id}">Check out</button>

View File

@@ -1,7 +1,7 @@
{% extends "base.html" %}
{% from "macros/forms.html" import render_field, render_select %}
{% block title %}Create account · Face Attendance{% endblock %}
{% block title %}Create account - Face Attendance{% endblock %}
{% block content %}
<div class="page-header">

View File

@@ -1,6 +1,6 @@
{% extends "base.html" %}
{% block title %}Accounts · Face Attendance{% endblock %}
{% block title %}Accounts - Face Attendance{% endblock %}
{% block content %}
<div class="page-header">

View File

@@ -1,6 +1,6 @@
{% extends "base.html" %}
{% block title %}Attendance · Face Attendance{% endblock %}
{% block title %}Attendance - Face Attendance{% endblock %}
{% block content %}
<div class="page-header">

View File

@@ -1,6 +1,6 @@
{% extends "base.html" %}
{% block title %}Kiosk · Face Attendance{% endblock %}
{% block title %}Kiosk - Face Attendance{% endblock %}
{% block content %}
<div class="page-header">

View File

@@ -1,7 +1,7 @@
{% extends "base.html" %}
{% from "macros/forms.html" import render_field %}
{% block title %}Sign in · Face Attendance{% endblock %}
{% block title %}Sign in - Face Attendance{% endblock %}
{% block content %}
<div class="auth-layout">

View File

@@ -29,7 +29,7 @@
{% endif %}
</ul>
<div class="d-flex align-items-center gap-3">
<span class="small text-secondary">{{ current_user.full_name }} · {{ current_user.role|title }}</span>
<span class="small text-secondary">{{ current_user.full_name }} - {{ current_user.role|title }}</span>
<form method="post" action="{{ url_for('auth.logout') }}">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
<button class="btn btn-outline-secondary btn-sm" type="submit">Sign out</button>

View File

@@ -1,6 +1,6 @@
{% extends "base.html" %}
{% block title %}Dashboard · Face Attendance{% endblock %}
{% block title %}Dashboard - Face Attendance{% endblock %}
{% block content %}
<div class="page-header">

View File

@@ -1,5 +1,5 @@
{% extends "base.html" %}
{% block title %}Access denied · Face Attendance{% endblock %}
{% block title %}Access denied - Face Attendance{% endblock %}
{% block content %}
<section class="error-page">
<h1>Access denied</h1>

View File

@@ -1,5 +1,5 @@
{% extends "base.html" %}
{% block title %}Not found · Face Attendance{% endblock %}
{% block title %}Not found - Face Attendance{% endblock %}
{% block content %}
<section class="error-page">
<h1>Page not found</h1>

View File

@@ -1,5 +1,5 @@
{% extends "base.html" %}
{% block title %}Server error · Face Attendance{% endblock %}
{% block title %}Server error - Face Attendance{% endblock %}
{% block content %}
<section class="error-page">
<h1>Server error</h1>

View File

@@ -1,13 +1,13 @@
{% extends "base.html" %}
{% block title %}{{ person.full_name }} · Face Attendance{% endblock %}
{% block title %}{{ person.full_name }} - Face Attendance{% endblock %}
{% block content %}
<div class="page-header">
<div>
<p class="eyebrow">Person profile</p>
<h1>{{ person.full_name }}</h1>
<p class="text-secondary mb-0">{{ person.employee_id }} · {{ "Active" if person.active else "Inactive" }}</p>
<p class="text-secondary mb-0">{{ person.employee_id }} - {{ "Active" if person.active else "Inactive" }}</p>
</div>
<a class="btn btn-outline-secondary" href="{{ url_for('people.index') }}">Back</a>
</div>

View File

@@ -1,7 +1,7 @@
{% extends "base.html" %}
{% from "macros/forms.html" import render_field, render_textarea %}
{% block title %}Enroll person · Face Attendance{% endblock %}
{% block title %}Enroll person - Face Attendance{% endblock %}
{% block content %}
<div class="page-header">

View File

@@ -1,6 +1,6 @@
{% extends "base.html" %}
{% block title %}People · Face Attendance{% endblock %}
{% block title %}People - Face Attendance{% endblock %}
{% block content %}
<div class="page-header">

View File

@@ -0,0 +1,68 @@
{% extends "base.html" %}
{% block title %}Face Attendance - Secure Recognition Attendance{% endblock %}
{% block content %}
<div class="welcome-page">
<section class="welcome-hero">
<nav class="welcome-nav" aria-label="Welcome navigation">
<a class="welcome-brand" href="{{ url_for('main.index') }}">Face Attendance</a>
<a class="btn btn-light btn-sm" href="{{ url_for('auth.login') }}">Sign in</a>
</nav>
<div class="welcome-copy">
<p class="eyebrow welcome-eyebrow">Production-ready Flask application</p>
<h1>Face Attendance</h1>
<p>
A secure attendance platform for enrolling people, identifying faces at a kiosk,
and reviewing check-in and check-out history with role-based staff access.
</p>
<div class="welcome-actions">
<a class="btn btn-primary btn-lg" href="{{ url_for('auth.login') }}">Sign in</a>
<a class="btn btn-outline-light btn-lg" href="#capabilities">View capabilities</a>
</div>
</div>
</section>
<section class="welcome-section" id="capabilities">
<div class="welcome-section-header">
<p class="eyebrow">Capabilities</p>
<h2>Built for controlled attendance workflows</h2>
</div>
<div class="feature-grid">
<article class="feature-card">
<span>01</span>
<h3>Role-based staff access</h3>
<p>Admins manage accounts, operators run attendance workflows, and viewers get read-only reports.</p>
</article>
<article class="feature-card">
<span>02</span>
<h3>Face enrollment</h3>
<p>Store multiple reference encodings per person while keeping recognition logic isolated in services.</p>
</article>
<article class="feature-card">
<span>03</span>
<h3>Attendance kiosk</h3>
<p>Capture a webcam snapshot, identify matches, and record check-in or check-out events from one screen.</p>
</article>
<article class="feature-card">
<span>04</span>
<h3>Audit-friendly records</h3>
<p>Attendance entries include the person, event type, timestamp, source, confidence, and staff user.</p>
</article>
</div>
</section>
<section class="workflow-band">
<div>
<p class="eyebrow">Architecture</p>
<h2>Clean Flask structure with production defaults</h2>
</div>
<ul class="workflow-list">
<li>Application factory, Blueprints, SQLAlchemy models, and Alembic migrations.</li>
<li>CSRF-protected forms, secure password hashing, and hardened session settings.</li>
<li>Pytest fixtures for auth, authorization, models, and attendance behavior.</li>
</ul>
</section>
</div>
{% endblock %}