Add user authentication and registration features

- Implement login and registration HTML templates for user authentication.
- Create user management functions in user.py for handling user data, including adding, deleting, and updating users.
- Introduce password hashing and strength checking for secure user credentials.
- Add terms of service and privacy policy pages to the website.
- Enhance main page with dynamic content based on user session status.
This commit is contained in:
Maximilian G.
2026-03-23 17:39:26 +00:00
committed by GitHub
parent 316581dd8f
commit 283a37638b
20 changed files with 4293 additions and 5 deletions
+206
View File
@@ -0,0 +1,206 @@
{% extends "base.html" %}
{% block title %}Blog verwalten{% endblock %}
{% block head %}
{{ super() }}
<style>
.blog-admin-grid {
display: grid;
grid-template-columns: 0.7fr 1.3fr;
gap: 1.2rem;
}
.new-post-form {
background: #ffffff;
border: 1px solid #d4e0e9;
border-radius: 16px;
padding: 1.4rem;
box-shadow: 0 14px 32px rgba(20, 39, 55, 0.06);
height: fit-content;
position: sticky;
top: 100px;
}
.new-post-form h2 {
font-size: 1.2rem;
color: #0f354d;
margin: 0 0 0.9rem;
}
.field {
margin-bottom: 0.9rem;
}
.field label {
display: block;
margin-bottom: 0.3rem;
font-weight: 700;
color: #1f4b65;
font-size: 0.9rem;
}
.field input,
.field textarea {
width: 100%;
border: 1px solid #c4d4e0;
border-radius: 10px;
padding: 0.65rem 0.72rem;
font: inherit;
color: #143f5d;
background: #ffffff;
}
.field textarea {
min-height: 120px;
resize: vertical;
}
.field input:focus,
.field textarea:focus {
outline: none;
border-color: #2f79a7;
box-shadow: 0 0 0 3px rgba(58, 133, 180, 0.2);
}
.submit-btn {
width: 100%;
border: none;
border-radius: 999px;
padding: 0.72rem;
font-size: 0.95rem;
font-weight: 700;
color: #ffffff;
background: linear-gradient(120deg, #0a5f8f 0%, #0b4567 100%);
cursor: pointer;
}
.posts-list {
display: grid;
gap: 0.8rem;
}
.post-card {
background: #ffffff;
border: 1px solid #d4e0e9;
border-radius: 14px;
padding: 1.1rem;
box-shadow: 0 10px 28px rgba(20, 40, 55, 0.05);
display: flex;
justify-content: space-between;
align-items: flex-start;
gap: 1rem;
}
.post-info h3 {
font-size: 1.05rem;
color: #113d59;
margin: 0 0 0.25rem;
}
.post-info p {
margin: 0.2rem 0;
font-size: 0.9rem;
color: #5a7a8f;
}
.post-meta {
font-size: 0.8rem;
color: #7a8fa2;
margin-top: 0.3rem;
}
.delete-form {
display: flex;
}
.delete-btn {
border: none;
border-radius: 6px;
padding: 0.4rem 0.7rem;
font-size: 0.8rem;
font-weight: 700;
color: #ffffff;
background: linear-gradient(120deg, #a74444 0%, #8a3535 100%);
cursor: pointer;
}
@media (max-width: 980px) {
.blog-admin-grid {
grid-template-columns: 1fr;
}
.new-post-form {
position: static;
}
}
</style>
{% endblock %}
{% block content %}
<h1 style="font-size: 2rem; color: #0f354d; margin-bottom: 1.2rem;">Blog verwalten</h1>
<section class="blog-admin-grid">
<aside class="new-post-form">
<h2>Neuer Beitrag</h2>
<form method="POST" action="{{ url_for('admin_blog') }}">
<div class="field">
<label for="title">Titel</label>
<input
id="title"
name="title"
type="text"
placeholder="Beitragstitel"
required
/>
</div>
<div class="field">
<label for="excerpt">Zusammenfassung</label>
<textarea
id="excerpt"
name="excerpt"
placeholder="Optionale Kurzbeschreibung"
></textarea>
</div>
<div class="field">
<label for="content">Inhalt</label>
<textarea
id="content"
name="content"
placeholder="Beitrag schreiben..."
required
></textarea>
</div>
<input type="hidden" name="action" value="create" />
<button type="submit" class="submit-btn">Veroeffentlichen</button>
</form>
</aside>
<article>
<h2 style="font-size: 1.2rem; color: #0f354d; margin: 0 0 0.8rem;">Veroeffentlichte Beitraege</h2>
<section class="posts-list">
{% if posts %}
{% for post in posts %}
<article class="post-card">
<div class="post-info">
<h3>{{ post.title }}</h3>
<p>{{ post.excerpt }}</p>
<div class="post-meta">
<strong>Autor:</strong> {{ post.author }}<br />
<strong>Veroeffentlicht:</strong> {{ post.created_at[:10] }}
</div>
</div>
<form method="POST" action="{{ url_for('admin_blog') }}" class="delete-form">
<input type="hidden" name="action" value="delete" />
<input type="hidden" name="post_id" value="{{ post.id }}" />
<button type="submit" class="delete-btn" onclick="return confirm('Sicher?')">Loeschen</button>
</form>
</article>
{% endfor %}
{% else %}
<p>Noch keine Beitraege veroeffentlicht.</p>
{% endif %}
</section>
</article>
</section>
{% endblock %}
+214
View File
@@ -0,0 +1,214 @@
{% extends "base.html" %}
{% block title %}Admin Dashboard{% endblock %}
{% block head %}
{{ super() }}
<style>
.admin-grid {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: 1rem;
margin-bottom: 1.2rem;
}
.stat-card {
background: #ffffff;
border: 1px solid #d4e0e9;
border-radius: 14px;
padding: 1.2rem;
box-shadow: 0 10px 28px rgba(20, 40, 55, 0.05);
text-align: center;
}
.stat-value {
font-size: 2.2rem;
font-weight: 700;
color: #0a5c88;
margin-bottom: 0.3rem;
}
.stat-label {
font-size: 0.9rem;
color: #5a7a8f;
font-weight: 600;
}
.appointments-section {
background: #ffffff;
border: 1px solid #d4e0e9;
border-radius: 16px;
padding: 1.4rem;
box-shadow: 0 14px 32px rgba(20, 39, 55, 0.06);
}
.appointments-section h2 {
font-size: 1.3rem;
color: #0f354d;
margin-bottom: 0.8rem;
}
.appointment-card {
border: 1px solid #d8e3ec;
border-radius: 12px;
padding: 1rem;
margin-bottom: 0.8rem;
background: #f9fbfd;
display: flex;
justify-content: space-between;
align-items: flex-start;
gap: 1rem;
}
.appointment-info > strong {
display: block;
color: #113d59;
margin-bottom: 0.25rem;
}
.appointment-info p {
margin: 0.15rem 0;
font-size: 0.9rem;
}
.appointment-status {
display: inline-block;
border-radius: 999px;
padding: 0.25rem 0.6rem;
font-size: 0.8rem;
font-weight: 700;
margin-top: 0.35rem;
}
.status-pending {
border: 1px solid #fba316;
background: #fffbf0;
color: #b8730f;
}
.status-confirmed {
border: 1px solid #0e9f6e;
background: #f0fdf4;
color: #0b5c47;
}
.status-rejected {
border: 1px solid #d3413e;
background: #fdf5f5;
color: #8b2a27;
}
.appointment-actions {
display: flex;
gap: 0.4rem;
}
.action-form {
display: grid;
gap: 0.6rem;
}
.response-input {
width: 100%;
min-height: 60px;
border: 1px solid #c4d4e0;
border-radius: 8px;
padding: 0.5rem;
font-family: inherit;
font-size: 0.85rem;
resize: vertical;
}
.action-btn {
border: none;
border-radius: 6px;
padding: 0.4rem 0.7rem;
font-size: 0.85rem;
font-weight: 700;
cursor: pointer;
color: #ffffff;
}
.confirm-btn {
background: linear-gradient(120deg, #0a7c4e 0%, #065f3f 100%);
}
.reject-btn {
background: linear-gradient(120deg, #a74444 0%, #8a3535 100%);
}
@media (max-width: 980px) {
.admin-grid {
grid-template-columns: 1fr 1fr;
}
}
@media (max-width: 600px) {
.admin-grid {
grid-template-columns: 1fr;
}
.appointment-card {
flex-direction: column;
}
}
</style>
{% endblock %}
{% block content %}
<h1 style="font-size: 2rem; color: #0f354d; margin-bottom: 1rem;">Admin Dashboard</h1>
<section class="admin-grid">
<article class="stat-card">
<div class="stat-value">{{ status_counts.Angefragt }}</div>
<div class="stat-label">Neue Anfragen</div>
</article>
<article class="stat-card">
<div class="stat-value">{{ status_counts.Bestaetigt }}</div>
<div class="stat-label">Bestaetigt</div>
</article>
<article class="stat-card">
<div class="stat-value">{{ total_posts }}</div>
<div class="stat-label">Blog-Beitraege</div>
</article>
</section>
<section class="appointments-section">
<h2>Terminanfragen verwalten</h2>
{% if appointments %}
{% for appointment in appointments %}
<article class="appointment-card">
<div class="appointment-info">
<strong>{{ appointment.display_name }} - {{ appointment.subject }}</strong>
<p><strong>Datum:</strong> {{ appointment.date }} um {{ appointment.time }}</p>
<p><strong>Angefragt:</strong> {{ appointment.created_at[:10] }}</p>
{% if appointment.note %}
<p><strong>Notiz:</strong> {{ appointment.note }}</p>
{% endif %}
{% if appointment.response %}
<p><strong>Antwort:</strong> {{ appointment.response }}</p>
{% endif %}
<span class="appointment-status status-{{ appointment.status|lower|replace('Ä', 'ae')|replace('ö', 'oe')|replace('ü', 'ue') if appointment.status == 'Angefragt' else 'confirmed' if appointment.status == 'Bestaetigt' else 'rejected' }}">{{ appointment.status }}</span>
</div>
{% if appointment.status == 'Angefragt' %}
<div class="appointment-actions">
<form method="POST" action="{{ url_for('update_appointment', appointment_id=appointment.id) }}" class="action-form" style="min-width: 280px;">
<textarea class="response-input" name="response" placeholder="Antwort (optional)"></textarea>
<div style="display: flex; gap: 0.4rem;">
<button type="submit" name="action" value="confirm" class="confirm-btn action-btn">Bestaetigen</button>
<button type="submit" name="action" value="reject" class="reject-btn action-btn">Ablehnen</button>
</div>
</form>
</div>
{% endif %}
</article>
{% endfor %}
{% else %}
<p>Keine Terminanfragen vorhanden.</p>
{% endif %}
</section>
<section style="margin-top: 1.5rem;">
<a href="{{ url_for('admin_blog') }}" style="color: #ffffff; background: linear-gradient(120deg, #0b5b89 0%, #0b4262 100%); padding: 0.68rem 1.15rem; border-radius: 999px; font-weight: 700; display: inline-block; text-decoration: none;">Blog verwalten</a>
</section>
{% endblock %}
+302
View File
@@ -0,0 +1,302 @@
{% extends "base.html" %}
{% block title %}Termin anfragen{% endblock %}
{% block head %}
{{ super() }}
<style>
.appointments-grid {
display: grid;
grid-template-columns: 1.2fr 0.8fr;
gap: 1rem;
align-items: start;
}
.panel {
background: #ffffff;
border: 1px solid #d4e0e9;
border-radius: 16px;
padding: 1.2rem;
box-shadow: 0 14px 32px rgba(20, 39, 55, 0.06);
}
.calendar-header {
display: flex;
align-items: center;
justify-content: space-between;
gap: 0.8rem;
margin-bottom: 0.9rem;
}
.calendar-header h1 {
font-size: 1.5rem;
color: #133f5d;
}
.month-nav {
display: inline-flex;
gap: 0.4rem;
}
.month-nav a {
border-radius: 999px;
border: 1px solid #bfd1de;
padding: 0.38rem 0.7rem;
color: #185170;
font-weight: 700;
background: #ffffff;
}
.weekday-row,
.day-grid {
display: grid;
grid-template-columns: repeat(7, minmax(0, 1fr));
gap: 0.3rem;
}
.weekday {
text-align: center;
font-size: 0.82rem;
font-weight: 700;
color: #587689;
padding-bottom: 0.2rem;
}
.day {
min-height: 74px;
border: 1px solid #d8e3ec;
border-radius: 10px;
background: #f9fcff;
color: #0f3f5d;
cursor: pointer;
font-weight: 700;
transition: all 0.15s ease;
}
.day:hover {
border-color: #9ec0d6;
transform: translateY(-1px);
}
.day.empty {
background: #f2f6f9;
border-style: dashed;
cursor: default;
}
.day.today {
border-color: #2d7fab;
}
.day.selected {
background: linear-gradient(140deg, #0d5c88 0%, #0a4464 100%);
border-color: #0d5c88;
color: #ffffff;
}
.booking-form h2,
.appointment-list h2 {
font-size: 1.2rem;
color: #143f5e;
margin: 0 0 0.8rem;
}
.selected-date-badge {
display: inline-block;
margin-bottom: 0.8rem;
border-radius: 999px;
padding: 0.28rem 0.6rem;
border: 1px solid #bfd2df;
color: #23526c;
font-weight: 700;
font-size: 0.9rem;
}
.field {
margin-bottom: 0.8rem;
}
.field label {
display: block;
margin-bottom: 0.3rem;
font-weight: 700;
color: #1f4b65;
}
.field input,
.field textarea {
width: 100%;
border: 1px solid #c4d4e0;
border-radius: 10px;
padding: 0.65rem 0.72rem;
font: inherit;
color: #143f5d;
background: #ffffff;
}
.field textarea {
min-height: 86px;
resize: vertical;
}
.submit-btn {
width: 100%;
border: none;
border-radius: 999px;
padding: 0.74rem;
font-size: 1rem;
font-weight: 700;
color: #ffffff;
background: linear-gradient(120deg, #0a5f8f 0%, #0b4567 100%);
cursor: pointer;
}
.entry {
border: 1px solid #d7e2ea;
border-radius: 12px;
padding: 0.72rem;
margin-bottom: 0.55rem;
background: #f9fbfd;
}
.entry strong {
color: #113d59;
}
.entry p {
margin-top: 0.18rem;
font-size: 0.92rem;
}
.status {
display: inline-block;
margin-top: 0.45rem;
border-radius: 999px;
border: 1px solid #c9d7e2;
padding: 0.15rem 0.55rem;
font-size: 0.8rem;
color: #2f5f79;
font-weight: 700;
background: #eef4f8;
}
@media (max-width: 960px) {
.appointments-grid {
grid-template-columns: 1fr;
}
}
</style>
{% endblock %}
{% block content %}
<section class="appointments-grid">
<article class="panel">
<div class="calendar-header">
<h1>{{ month_name }} {{ year }}</h1>
<div class="month-nav">
<a href="{{ url_for('appointments', month=previous_month, year=previous_year) }}">Zurueck</a>
<a href="{{ url_for('appointments', month=next_month, year=next_year) }}">Weiter</a>
</div>
</div>
<div class="weekday-row" aria-hidden="true">
<div class="weekday">Mo</div>
<div class="weekday">Di</div>
<div class="weekday">Mi</div>
<div class="weekday">Do</div>
<div class="weekday">Fr</div>
<div class="weekday">Sa</div>
<div class="weekday">So</div>
</div>
<div class="day-grid" id="dayGrid">
{% for week in month_grid %}
{% for day in week %}
{% if day == 0 %}
<button type="button" class="day empty" disabled></button>
{% else %}
{% set day_iso = "%04d-%02d-%02d"|format(year, month, day) %}
<button
type="button"
class="day {% if day_iso == today_iso %}today{% endif %}"
data-date="{{ day_iso }}"
>
{{ day }}
</button>
{% endif %}
{% endfor %}
{% endfor %}
</div>
</article>
<aside class="panel booking-form">
<h2>Termin anfragen</h2>
<span class="selected-date-badge" id="selectedDateBadge">Kein Datum gewaehlt</span>
<form method="POST" action="{{ url_for('appointments', month=month, year=year) }}">
<input type="hidden" id="selectedDateInput" name="selected_date" required>
<div class="field">
<label for="appointment_time">Uhrzeit</label>
<input id="appointment_time" name="appointment_time" type="time" required>
</div>
<div class="field">
<label for="subject">Betreff</label>
<input id="subject" name="subject" type="text" placeholder="z. B. Projekt Kickoff" required>
</div>
<div class="field">
<label for="note">Notiz (optional)</label>
<textarea id="note" name="note" placeholder="Kurzbeschreibung der Anfrage"></textarea>
</div>
<button class="submit-btn" type="submit">Termin anfragen</button>
</form>
</aside>
</section>
<section class="panel appointment-list" style="margin-top: 1rem;">
<h2>Meine Terminanfragen</h2>
{% if user_appointments %}
{% for item in user_appointments %}
<article class="entry">
<strong>{{ item.date }} um {{ item.time }} - {{ item.subject }}</strong>
{% if item.note %}
<p>{{ item.note }}</p>
{% endif %}
{% if item.response %}
<p><strong>Admin-Antwort:</strong> {{ item.response }}</p>
{% endif %}
<span class="status">{{ item.status }}</span>
</article>
{% endfor %}
{% else %}
<p>Noch keine Anfragen vorhanden.</p>
{% endif %}
</section>
<script>
(function () {
const dayGrid = document.getElementById("dayGrid");
const selectedDateInput = document.getElementById("selectedDateInput");
const selectedDateBadge = document.getElementById("selectedDateBadge");
function setSelection(dateValue, button) {
selectedDateInput.value = dateValue;
selectedDateBadge.textContent = "Ausgewaehlt: " + dateValue;
dayGrid.querySelectorAll(".day").forEach(function (node) {
node.classList.remove("selected");
});
if (button) {
button.classList.add("selected");
}
}
dayGrid.addEventListener("click", function (event) {
const target = event.target.closest(".day[data-date]");
if (!target) {
return;
}
setSelection(target.dataset.date, target);
});
})();
</script>
{% endblock %}
+325
View File
@@ -0,0 +1,325 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=5.0, user-scalable=yes">
<meta name="mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
<title>{% block title %}IT Loesungen{% endblock %}</title>
{% block head %}
<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=Space+Grotesk:wght@400;500;600;700&family=Source+Sans+3:wght@400;500;600&display=swap" rel="stylesheet">
<style>
:root {
--bg-main: #f5f8fa;
--bg-soft: #e8eef2;
--surface: #ffffff;
--text-main: #112332;
--text-soft: #466176;
--line: #d8e1e8;
--brand: #0a4c74;
--brand-strong: #073754;
--accent: #f28c28;
--radius-md: 14px;
--shadow-sm: 0 10px 30px rgba(17, 35, 50, 0.08);
}
* { box-sizing: border-box; }
html,
body {
margin: 0;
padding: 0;
min-height: 100%;
font-family: "Source Sans 3", system-ui, sans-serif;
color: var(--text-main);
background: radial-gradient(circle at 85% -10%, #c5d8e5 0%, transparent 35%),
radial-gradient(circle at -10% 50%, #dce8ef 0%, transparent 42%),
var(--bg-main);
}
h1, h2, h3, h4 {
margin: 0;
font-family: "Space Grotesk", system-ui, sans-serif;
line-height: 1.08;
}
p {
margin: 0;
line-height: 1.55;
color: var(--text-soft);
}
a {
color: inherit;
text-decoration: none;
}
img {
max-width: 100%;
display: block;
}
.site-shell {
width: min(1240px, calc(100% - 2rem));
margin: 0 auto;
}
.site-nav {
position: sticky;
top: 0;
z-index: 30;
backdrop-filter: blur(10px);
background: rgba(245, 248, 250, 0.86);
border-bottom: 1px solid var(--line);
}
.site-nav-inner {
min-height: 78px;
display: flex;
align-items: center;
justify-content: space-between;
gap: 1rem;
}
.brand {
font-family: "Space Grotesk", system-ui, sans-serif;
font-size: 1.1rem;
font-weight: 700;
letter-spacing: 0.08em;
text-transform: uppercase;
color: var(--brand-strong);
}
.nav-links {
display: flex;
align-items: center;
gap: 1rem;
font-weight: 600;
color: var(--text-soft);
}
.nav-links a:hover {
color: var(--brand);
}
.nav-actions {
display: flex;
align-items: center;
gap: 0.6rem;
}
.nav-btn {
display: inline-flex;
align-items: center;
justify-content: center;
border-radius: 999px;
border: 1px solid #bcd0de;
padding: 0.48rem 0.95rem;
font-weight: 700;
color: #0e425f;
background: #ffffff;
transition: transform 0.15s ease;
}
.nav-btn:hover {
transform: translateY(-1px);
}
.nav-btn.primary {
color: #ffffff;
border-color: #0a4c74;
background: linear-gradient(120deg, #0b5b89 0%, #0b4262 100%);
}
.main-wrap {
padding: 2rem 0 3.4rem;
}
.flashes {
margin-bottom: 1rem;
display: grid;
gap: 0.75rem;
}
.flash {
padding: 0.8rem 1rem;
border-radius: 10px;
border: 1px solid var(--line);
background: var(--surface);
box-shadow: var(--shadow-sm);
font-weight: 600;
}
.flash.success { border-left: 5px solid #0e9f6e; }
.flash.error { border-left: 5px solid #d3413e; }
.flash.info { border-left: 5px solid var(--brand); }
@media (max-width: 820px) {
.site-nav-inner {
min-height: 64px;
}
.nav-links {
display: none;
}
.nav-actions {
gap: 0.4rem;
}
.nav-btn {
padding: 0.42rem 0.72rem;
font-size: 0.85rem;
}
}
</style>
{% endblock %}
</head>
<body>
<nav class="site-nav">
<div class="site-shell site-nav-inner">
<a class="brand" href="{{ url_for('default') }}">Invario</a>
<div class="nav-links" aria-label="Main Navigation">
<a href="{{ url_for('default') }}#leistungen">Dienstleistungen</a>
<a href="{{ url_for('default') }}#projekte">Projekte</a>
<a href="{{ url_for('default') }}#team">Team</a>
<a href="{{ url_for('default') }}#kontakt">Kontakt</a>
</div>
<div class="nav-actions">
{% if 'username' in session %}
<a class="nav-btn" href="{{ url_for('blog') }}">Blog</a>
<a class="nav-btn" href="{{ url_for('appointments') }}">Termin anfragen</a>
{% if session.get('is_admin') %}
<a class="nav-btn" href="{{ url_for('admin_dashboard') }}">Admin</a>
{% endif %}
<a class="nav-btn" href="{{ url_for('logout') }}">Logout</a>
{% else %}
<a class="nav-btn" href="{{ url_for('blog') }}">Blog</a>
<a class="nav-btn" href="{{ url_for('login') }}">Login</a>
<a class="nav-btn primary" href="{{ url_for('register') }}">Registrieren</a>
{% endif %}
</div>
</div>
</nav>
<main class="site-shell main-wrap">
{% with messages = get_flashed_messages(with_categories=true) %}
{% if messages %}
<div class="flashes">
{% for category, message in messages %}
<div class="flash {{ category }}">{{ message }}</div>
{% endfor %}
</div>
{% endif %}
{% endwith %}
{% block content %}{% endblock %}
</main>
<footer class="site-footer">
<div class="site-shell footer-inner">
<div class="footer-content">
<div class="footer-section">
<h4>Über uns</h4>
<p>Wir bieten professionelle IT-Lösungen und Dienstleistungen für Ihr Unternehmen.</p>
</div>
<div class="footer-section">
<h4>Schnelllinks</h4>
<ul class="footer-links">
<li><a href="{{ url_for('default') }}">Startseite</a></li>
<li><a href="{{ url_for('blog') }}">Blog</a></li>
{% if 'username' in session %}
<li><a href="{{ url_for('appointments') }}">Termine</a></li>
{% endif %}
</ul>
</div>
<div class="footer-section">
<h4>Rechtliches</h4>
<ul class="footer-links">
<li><a href="{{ url_for('datenschutz') }}">Datenschutz</a></li>
<li><a href="{{ url_for('impressum') }}">Impressum</a></li>
<li><a href="{{ url_for('nutzungsbedingungen') }}">Nutzungsbedingungen</a></li>
</ul>
</div>
</div>
<div class="footer-bottom">
<p>&copy; 2026 Invario. Alle Rechte vorbehalten.</p>
</div>
</div>
<style>
.site-footer {
background: linear-gradient(135deg, var(--brand-strong) 0%, #0a4c74 100%);
color: #ffffff;
margin-top: 4rem;
padding: 3rem 0 1.5rem;
border-top: 1px solid var(--line);
}
.footer-inner {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 2rem;
margin-bottom: 2rem;
}
.footer-section {
display: flex;
flex-direction: column;
gap: 0.75rem;
}
.footer-section h4 {
font-size: 1rem;
font-weight: 700;
color: #ffffff;
}
.footer-section p {
color: rgba(255, 255, 255, 0.8);
font-size: 0.95rem;
}
.footer-links {
list-style: none;
margin: 0;
padding: 0;
display: flex;
flex-direction: column;
gap: 0.5rem;
}
.footer-links a {
color: rgba(255, 255, 255, 0.8);
font-size: 0.95rem;
transition: color 0.2s ease;
}
.footer-links a:hover {
color: #ffffff;
}
.footer-bottom {
text-align: center;
padding-top: 1.5rem;
border-top: 1px solid rgba(255, 255, 255, 0.1);
}
.footer-bottom p {
color: rgba(255, 255, 255, 0.7);
font-size: 0.9rem;
margin: 0;
}
@media (max-width: 700px) {
.footer-inner {
grid-template-columns: 1fr;
gap: 1.5rem;
}
}
</style>
</footer>
<!-- Mobile compatibility scripts -->
<script src="{{ url_for('static', filename='js/mobile_compatibility.js') }}"></script>
<script src="{{ url_for('static', filename='js/ios_fixes.js') }}"></script>
</body>
</html>
+117
View File
@@ -0,0 +1,117 @@
{% extends "base.html" %}
{% block title %}Blog{% endblock %}
{% block head %}
{{ super() }}
<style>
.blog-header {
text-align: center;
margin-bottom: 2rem;
}
.blog-header h1 {
font-size: 2.4rem;
color: #0f354d;
margin-bottom: 0.5rem;
}
.blog-header p {
font-size: 1.05rem;
color: #5a7a8f;
}
.posts-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
gap: 1.2rem;
margin-bottom: 1.5rem;
}
.post-link {
text-decoration: none;
color: inherit;
}
.post-item {
background: #ffffff;
border: 1px solid #d4e0e9;
border-radius: 14px;
padding: 1.2rem;
box-shadow: 0 10px 28px rgba(20, 40, 55, 0.05);
transition: transform 0.2s ease, box-shadow 0.2s ease;
display: flex;
flex-direction: column;
}
.post-link:hover .post-item {
transform: translateY(-3px);
box-shadow: 0 16px 40px rgba(20, 40, 55, 0.12);
}
.post-item h2 {
font-size: 1.2rem;
color: #113d59;
margin: 0 0 0.5rem;
}
.post-item p {
margin: 0;
color: #5a7a8f;
font-size: 0.95rem;
flex: 1;
}
.post-meta {
display: flex;
justify-content: space-between;
align-items: center;
margin-top: 0.8rem;
padding-top: 0.8rem;
border-top: 1px solid #e8f0f6;
font-size: 0.8rem;
color: #7a8fa2;
}
.read-more {
color: #0a5c88;
font-weight: 700;
}
@media (max-width: 768px) {
.posts-grid {
grid-template-columns: 1fr;
}
.blog-header h1 {
font-size: 1.8rem;
}
}
</style>
{% endblock %}
{% block content %}
<section class="blog-header">
<h1>Blog</h1>
<p>Neuigkeiten und Insights rund um IT-Loesungen und Projekte</p>
</section>
{% if posts %}
<section class="posts-grid">
{% for post in posts %}
<a href="{{ url_for('blog_post', post_id=post.id) }}" class="post-link">
<article class="post-item">
<h2>{{ post.title }}</h2>
<p>{{ post.excerpt }}</p>
<div class="post-meta">
<span>{{ post.created_at[:10] }}</span>
<span class="read-more">Zum Beitrag →</span>
</div>
</article>
</a>
{% endfor %}
</section>
{% else %}
<p style="text-align: center; color: #5a7a8f; padding: 2rem 0;">Noch keine Beitraege veroeffentlicht.</p>
{% endif %}
{% endblock %}
+127
View File
@@ -0,0 +1,127 @@
{% extends "base.html" %}
{% block title %}{{ post.title }}{% endblock %}
{% block head %}
{{ super() }}
<style>
.post-container {
max-width: 760px;
margin: 0 auto;
background: #ffffff;
border: 1px solid #d4e0e9;
border-radius: 16px;
padding: 2rem;
box-shadow: 0 14px 32px rgba(20, 39, 55, 0.06);
}
.post-header {
margin-bottom: 1.5rem;
padding-bottom: 1.2rem;
border-bottom: 1px solid #e8f0f6;
}
.post-header h1 {
font-size: 2.2rem;
color: #0f354d;
margin: 0 0 0.6rem;
}
.post-header-meta {
display: flex;
align-items: center;
gap: 1rem;
color: #7a8fa2;
font-size: 0.9rem;
}
.author-badge {
display: inline-block;
border-radius: 999px;
padding: 0.25rem 0.6rem;
background: #eef4f8;
border: 1px solid #bcd0de;
color: #185170;
font-weight: 700;
}
.post-content {
line-height: 1.8;
color: #2f4556;
font-size: 1.05rem;
}
.post-content h2 {
font-size: 1.5rem;
color: #113d59;
margin: 1.5rem 0 0.8rem;
}
.post-content h3 {
font-size: 1.2rem;
color: #113d59;
margin: 1.2rem 0 0.6rem;
}
.post-content p {
margin: 0 0 1rem;
}
.post-content ul,
.post-content ol {
margin: 0 0 1rem 1.5rem;
}
.post-content li {
margin-bottom: 0.5rem;
}
.back-link {
display: inline-block;
margin-top: 1rem;
padding: 0.6rem 1rem;
border-radius: 999px;
border: 1px solid #bcd0de;
color: #0a5c88;
font-weight: 700;
text-decoration: none;
transition: transform 0.15s ease;
}
.back-link:hover {
transform: translateY(-1px);
}
@media (max-width: 768px) {
.post-container {
padding: 1.4rem;
}
.post-header h1 {
font-size: 1.6rem;
}
.post-content {
font-size: 1rem;
}
}
</style>
{% endblock %}
{% block content %}
<article class="post-container">
<header class="post-header">
<h1>{{ post.title }}</h1>
<div class="post-header-meta">
<span class="author-badge">{{ post.author }}</span>
<span>{{ post.created_at[:10] }}</span>
</div>
</header>
<section class="post-content">
{{ post.content }}
</section>
<a href="{{ url_for('blog') }}" class="back-link">← Zurueck zum Blog</a>
</article>
{% endblock %}
+238
View File
@@ -0,0 +1,238 @@
{% extends "base.html" %}
{% block title %}Datenschutz | Invario{% endblock %}
{% block content %}
<div class="legal-page">
<div class="legal-header">
<h1>Datenschutzerklärung</h1>
<p>Gültig ab: 1. Januar 2026</p>
</div>
<div class="legal-content">
<section>
<h2>1. Verantwortlicher</h2>
<p>Verantwortlich für die Datenverarbeitung ist:</p>
<p>
Invario GmbH<br>
Musterstraße 123<br>
12345 Musterstadt<br>
Deutschland<br>
E-Mail: info@invario.de
</p>
</section>
<section>
<h2>2. Datenschutzbeauftragter</h2>
<p>Unseren Datenschutzbeauftragten erreichen Sie unter:</p>
<p>
datenschutz@invario.de
</p>
</section>
<section>
<h2>3. Erhobene Daten</h2>
<p>Wir erheben und verarbeiten folgende Kategorien von personenbezogenen Daten:</p>
<ul>
<li><strong>Registrierungsdaten:</strong> Benutzername, Display-Name, E-Mail (falls vorhanden), Passwort-Hash</li>
<li><strong>Termin- und Buchungsdaten:</strong> Datum, Uhrzeit, Betreff, Notizen, Status der Termine</li>
<li><strong>Blog-Daten:</strong> Veröffentlichte Artikel, Autoreninformationen</li>
<li><strong>Technische Daten:</strong> IP-Adresse, Browser-Informationen, Cookie-Daten, Zeitstempel</li>
</ul>
</section>
<section>
<h2>4. Zweck der Datenverarbeitung</h2>
<p>Wir verarbeiten Ihre Daten zu folgenden Zwecken:</p>
<ul>
<li>Bereitstellung und Verwaltung Ihres Benutzerkontos</li>
<li>Abwicklung von Terminbuchungen und Anfragen</li>
<li>Kommunikation mit Ihnen zu Ihren Anfragen</li>
<li>Bereitstellung personalisierter Inhalte</li>
<li>Sicherheit und Schutz vor Missbrauch</li>
<li>Verbesserung unserer Dienstleistungen</li>
<li>Erfüllung gesetzlicher Verpflichtungen</li>
</ul>
</section>
<section>
<h2>5. Rechtsgrundlagen</h2>
<p>Die Verarbeitung personenbezogener Daten erfolgt auf Grundlage von:</p>
<ul>
<li><strong>Artikel 6 Abs. 1 lit. b) DSGVO:</strong> Abwicklung von Verträgen und Anfragen</li>
<li><strong>Artikel 6 Abs. 1 lit. a) DSGVO:</strong> Ihre ausdrückliche Einwilligung</li>
<li><strong>Artikel 6 Abs. 1 lit. c) DSGVO:</strong> Erfüllung rechtlicher Verpflichtungen</li>
<li><strong>Artikel 6 Abs. 1 lit. f) DSGVO:</strong> Berechtigte Interessen zur Sicherheit und Verbesserung</li>
</ul>
</section>
<section>
<h2>6. Speicherdauer</h2>
<p>Wir speichern Ihre Daten so lange, wie dies erforderlich ist:</p>
<ul>
<li>Kontodaten: Solange Ihr Konto aktiv ist, danach 12 Monate</li>
<li>Terminbuchungen: Bis 3 Jahre nach Abwicklung</li>
<li>Blog-Kommentare und Inhalte: Bis zur Löschung des Inhalts</li>
<li>Technische Logs: Maximal 90 Tage</li>
</ul>
</section>
<section>
<h2>7. Empfänger von Daten</h2>
<p>Wir geben Ihre Daten nicht an Dritte weiter, außer:</p>
<ul>
<li>Unsere technischen Dienstanbieter (Webhoting, Cloud-Services)</li>
<li>Behörden und Gerichte bei gesetzlicher Verpflichtung</li>
<li>Mit Ihrer ausdrücklichen Einwilligung</li>
</ul>
</section>
<section>
<h2>8. Ihre Rechte</h2>
<p>Sie haben folgende Rechte bezügliche Ihrer personenbezogenen Daten:</p>
<ul>
<li><strong>Auskunftsrecht:</strong> Recht auf Auskunft über Ihre Daten (Artikel 15 DSGVO)</li>
<li><strong>Berichtigungsrecht:</strong> Recht auf Korrektur unrichtiger Daten (Artikel 16 DSGVO)</li>
<li><strong>Löschungsrecht:</strong> Recht auf Löschung Ihrer Daten (Artikel 17 DSGVO)</li>
<li><strong>Einschränkungsrecht:</strong> Recht auf Einschränkung der Verarbeitung (Artikel 18 DSGVO)</li>
<li><strong>Recht auf Datenportabilität:</strong> Recht auf Übertragung Ihrer Daten (Artikel 20 DSGVO)</li>
<li><strong>Widerspruchsrecht:</strong> Recht auf Widerspruch gegen Verarbeitung (Artikel 21 DSGVO)</li>
<li><strong>Recht auf Beschwerde:</strong> Beschwerde bei einer Aufsichtsbehörde</li>
</ul>
<p>Um Ihre Rechte auszuüben, kontaktieren Sie uns unter: datenschutz@invario.de</p>
</section>
<section>
<h2>9. Cookies</h2>
<p>Wir verwenden Cookies zur Verwaltung von Sitzungen und Authentifizierung:</p>
<ul>
<li><strong>Session-Cookies:</strong> Erforderlich für die Anmeldung und Funktionalität</li>
<li><strong>Sicherheits-Cookies:</strong> Schutz vor CSRF und XSS-Angriffen</li>
<li><strong>Analytik-Cookies:</strong> Optional, zur Verbesserung unserer Dienste</li>
</ul>
<p>Sie können Cookies in den Einstellungen Ihres Browsers deaktivieren. Dies kann jedoch die Funktionalität beeinträchtigen.</p>
</section>
<section>
<h2>10. Sicherheit</h2>
<p>Wir schützen Ihre Daten durch:</p>
<ul>
<li>Verschlüsselte Übertragung (SSL/TLS)</li>
<li>Sichere Passwortspeicherung (Hashing)</li>
<li>Firewalls und Zugriffskontrollen</li>
<li>Regelmäßige Sicherheitsupdates</li>
</ul>
</section>
<section>
<h2>11. Externe Links</h2>
<p>Diese Website enthält möglicherweise Links zu externen Websites. Wir sind nicht verantwortlich für deren Datenschutzpraktiken. Bitte lesen Sie deren Datenschutzerklärungen.</p>
</section>
<section>
<h2>12. Änderungen dieser Datenschutzerklärung</h2>
<p>Wir können diese Datenschutzerklärung jederzeit aktualisieren. Die aktuelle Version ist immer auf unserer Website verfügbar. Bei wesentlichen Änderungen werden Sie benachrichtigt.</p>
</section>
<section>
<h2>13. Kontakt</h2>
<p>Bei Fragen zur Datenschutzerklärung kontaktieren Sie uns unter:</p>
<p>
E-Mail: datenschutz@invario.de<br>
Telefon: +49 (0) 123 456789<br>
Adresse: Musterstraße 123, 12345 Musterstadt
</p>
</section>
</div>
</div>
<style>
.legal-page {
max-width: 900px;
margin: 0 auto;
}
.legal-header {
margin-bottom: 3rem;
padding-bottom: 2rem;
border-bottom: 2px solid var(--line);
}
.legal-header h1 {
font-size: 2.2rem;
margin-bottom: 0.5rem;
color: var(--brand);
}
.legal-header p {
color: var(--text-soft);
font-size: 0.95rem;
}
.legal-content {
display: flex;
flex-direction: column;
gap: 2.5rem;
}
section {
display: flex;
flex-direction: column;
gap: 1rem;
}
section h2 {
font-size: 1.3rem;
color: var(--brand);
margin-bottom: 0.5rem;
}
section p {
line-height: 1.7;
color: var(--text-soft);
}
section ul {
list-style: none;
padding: 0;
margin: 0;
display: flex;
flex-direction: column;
gap: 0.75rem;
}
section ul li {
padding-left: 1.5rem;
position: relative;
color: var(--text-soft);
line-height: 1.6;
}
section ul li:before {
content: "→";
position: absolute;
left: 0;
color: var(--accent);
font-weight: 700;
}
section strong {
color: var(--text-main);
font-weight: 600;
}
@media (max-width: 700px) {
.legal-header h1 {
font-size: 1.6rem;
}
section h2 {
font-size: 1.1rem;
}
.legal-content {
gap: 1.8rem;
}
}
</style>
{% endblock %}
+279
View File
@@ -0,0 +1,279 @@
{% extends "base.html" %}
{% block title %}Impressum | Invario{% endblock %}
{% block content %}
<div class="legal-page">
<div class="legal-header">
<h1>Impressum</h1>
<p>Angaben gemäß TMG (Telemediengesetz) und MDStV (Mediendienstestaatsvertrag)</p>
</div>
<div class="legal-content">
<section>
<h2>1. Betreiber der Website</h2>
<p>
<strong>Invario GmbH</strong><br>
Musterstraße 123<br>
12345 Musterstadt<br>
Deutschland
</p>
</section>
<section>
<h2>2. Registergericht und Registernummer</h2>
<p>
<strong>Registergericht:</strong> Amtsgericht Musterstadt<br>
<strong>Registernummer:</strong> HRB 123456<br>
<strong>USt-ID:</strong> DE 123 456 789
</p>
</section>
<section>
<h2>3. Geschäftsführung</h2>
<p>
<strong>Geschäftsführer:</strong> Max Mustermann und Erika Musterfrau
</p>
</section>
<section>
<h2>4. Kontaktinformationen</h2>
<p>
<strong>Telefon:</strong> +49 (0) 123 / 456789<br>
<strong>E-Mail:</strong> info@invario.de<br>
<strong>Website:</strong> www.invario.de
</p>
</section>
<section>
<h2>5. Verantwortliche Redaktion</h2>
<p>
<strong>Verantwortlich nach § 55 Abs. 2 RStV:</strong><br>
Max Mustermann<br>
Invario GmbH<br>
Musterstraße 123<br>
12345 Musterstadt
</p>
</section>
<section>
<h2>6. Haftungsausschluss</h2>
<p>
<strong>Haftung für Inhalte:</strong><br>
Trotz sorgfältiger Überprüfung übernehmen wir keine Haftung für die Inhalte externer Links.
Die auf dieser Website verlinkten Seiten sind ausschließlich Verantwortung des jeweiligen Betreibers.
Zum Zeitpunkt der Link-Erstellung waren keine illegalen Inhalte erkennbar.
</p>
<p>
<strong>Haftung für Links:</strong><br>
Sollte eine Seite, auf die wir verlinken, in Zukunft illegale oder sittenwidrige Inhalte enthalten,
werden wir den Link entfernen, sobald wir davon Kenntnis erlangen. Die Funktionsfähigkeit der Website
wird trotz gründlicher Prüfung nicht garantiert.
</p>
<p>
<strong>Haftung für Dienste und Angebote:</strong><br>
Alle unterbreiteten Informationen sind freibleibend und unverbindlich. Wir behalten uns ausdrücklich vor,
Teile der Website oder das gesamte Angebot ohne separate Ankündigung zu verändern, zu ergänzen, zu löschen oder
die Veröffentlichung zeitweise oder endgültig einzustellen.
</p>
</section>
<section>
<h2>7. Urheberrecht und Nutzungsrechte</h2>
<p>
Die auf dieser Website veröffentlichten Inhalte und Werke unterliegen dem deutschen Urheberrecht.
Eine Vervielfältigung, Bearbeitung, Verbreitung und jede Art der Verwertung außerhalb der Grenzen des Urheberrechts
bedürfen der schriftlichen Zustimmung des Urhebers oder Erstellers. Downloads und Kopien dieser
Seite sind nur für den privaten, nicht kommerziellen Gebrauch gestattet.
</p>
<p>
<strong>© 2026 Invario GmbH. Alle Rechte vorbehalten.</strong>
</p>
</section>
<section>
<h2>8. Besonderheiten in Bezug auf Inhalte</h2>
<p>
<strong>Blog und Benutzergenerierte Inhalte:</strong><br>
Für Inhalte, die von Benutzern oder Administratoren veröffentlicht werden, trägt der jeweilige Autor/Verfasser
die Verantwortung. Wir überprüfen regelmäßig alle Inhalte auf der Website. Sollten Sie der Meinung sein,
dass gegen Ihr Urheberrecht verstoßen wird oder illegale Inhalte vorliegen, teilen Sie uns dies bitte
umgehend mit.
</p>
</section>
<section>
<h2>9. Zahlungsarten und Geschäftsbedingungen</h2>
<p>
Die Nutzung unserer Dienstleistungen unterliegt den auf dieser Website veröffentlichten Nutzungsbedingungen
und unseren allgemeinen Geschäftsbedingungen. Diese finden Sie unter
<a href="{{ url_for('nutzungsbedingungen') }}">Nutzungsbedingungen</a>.
</p>
</section>
<section>
<h2>10. Datenschutz</h2>
<p>
Weitere Informationen zur Verarbeitung Ihrer personenbezogenen Daten finden Sie in unserer
<a href="{{ url_for('datenschutz') }}">Datenschutzerklärung</a>.
</p>
</section>
<section>
<h2>11. Außergerichtliche Streitbeilegung</h2>
<p>
Der Europäischen Kommission steht ein Online-Portal zur Beilegung von Streitigkeiten
(Plattform zur Online-Streitbeilegung - OS) zur Verfügung:
<a href="https://ec.europa.eu/consumers/odr/" target="_blank">https://ec.europa.eu/consumers/odr/</a>
</p>
<p>
Wir sind nicht bereit oder verpflichtet, an Streitbeilegungsverfahren vor einer Verbraucherschlichtungsstelle teilzunehmen.
</p>
</section>
<section>
<h2>12. Geltendes Recht</h2>
<p>
Für alle Rechtsbeziehungen zwischen dem Nutzer und uns gilt ausschließlich deutsches Recht.
Es gilt das TMG und das MDStV in ihrer jeweils aktuellen Fassung. Erfüllungsort ist Musterstadt.
</p>
</section>
<section>
<h2>13. Kontakt zum Datenschutzbeauftragten</h2>
<p>
<strong>Datenschutzbeauftragter:</strong><br>
E-Mail: datenschutz@invario.de<br>
Telefon: +49 (0) 123 / 456789
</p>
</section>
<section>
<h2>14. Zu beachtende Gebote in Bezug auf Konten</h2>
<ul>
<li>Die Anmeldung erfordert einen gültigen Benutzernamen und ein Passwort</li>
<li>Sie sind verantwortlich für die Geheimhaltung des Passworts</li>
<li>Sie haften für alle Aktivitäten in Ihrem Konto</li>
<li>Missbrauch und Sicherheitslücken müssen uns sofort mitgeteilt werden</li>
<li>Eine Konto-Weitergabe ist nicht gestattet</li>
</ul>
</section>
<div class="last-updated">
<p><strong>Zuletzt aktualisiert:</strong> 1. Januar 2026</p>
</div>
</div>
</div>
<style>
.legal-page {
max-width: 900px;
margin: 0 auto;
}
.legal-header {
margin-bottom: 3rem;
padding-bottom: 2rem;
border-bottom: 2px solid var(--line);
}
.legal-header h1 {
font-size: 2.2rem;
margin-bottom: 0.5rem;
color: var(--brand);
}
.legal-header p {
color: var(--text-soft);
font-size: 0.95rem;
}
.legal-content {
display: flex;
flex-direction: column;
gap: 2.5rem;
}
section {
display: flex;
flex-direction: column;
gap: 1rem;
}
section h2 {
font-size: 1.3rem;
color: var(--brand);
margin-bottom: 0.5rem;
}
section p {
line-height: 1.7;
color: var(--text-soft);
}
section p a {
color: var(--brand);
text-decoration: underline;
}
section p a:hover {
color: var(--accent);
}
section ul {
list-style: none;
padding: 0;
margin: 0;
display: flex;
flex-direction: column;
gap: 0.75rem;
}
section ul li {
padding-left: 1.5rem;
position: relative;
color: var(--text-soft);
line-height: 1.6;
}
section ul li:before {
content: "→";
position: absolute;
left: 0;
color: var(--accent);
font-weight: 700;
}
section strong {
color: var(--text-main);
font-weight: 600;
}
.last-updated {
background: var(--bg-soft);
padding: 1.5rem;
border-radius: var(--radius-md);
margin-top: 2rem;
}
.last-updated p {
margin: 0;
text-align: center;
color: var(--text-soft);
}
@media (max-width: 700px) {
.legal-header h1 {
font-size: 1.6rem;
}
section h2 {
font-size: 1.1rem;
}
.legal-content {
gap: 1.8rem;
}
}
</style>
{% endblock %}
+110
View File
@@ -0,0 +1,110 @@
<!--
Copyright 2025 Maximilian Gründinger
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
{% extends "base.html" %}
{% block title %}Login{% endblock %}
{% block head %}
{{ super() }}
<style>
.auth-wrap {
max-width: 520px;
margin: 2rem auto;
padding: 2rem;
border-radius: 18px;
border: 1px solid #d4e0e9;
background: #ffffff;
box-shadow: 0 20px 44px rgba(14, 32, 48, 0.08);
}
.auth-wrap h1 {
font-size: 2rem;
color: #0f344d;
margin-bottom: 0.4rem;
}
.auth-wrap p {
margin-bottom: 1.3rem;
}
.field {
margin-bottom: 0.9rem;
}
.field label {
display: block;
margin-bottom: 0.35rem;
font-weight: 700;
color: #19445f;
}
.field input {
width: 100%;
padding: 0.68rem 0.78rem;
border-radius: 10px;
border: 1px solid #c4d5e2;
font-size: 1rem;
color: #12384f;
}
.field input:focus {
outline: none;
border-color: #2f79a7;
box-shadow: 0 0 0 3px rgba(58, 133, 180, 0.2);
}
.auth-btn {
width: 100%;
border: none;
border-radius: 999px;
padding: 0.72rem;
font-size: 1rem;
font-weight: 700;
color: #ffffff;
background: linear-gradient(120deg, #0a5f8f 0%, #0b4567 100%);
cursor: pointer;
}
.auth-meta {
margin-top: 1rem;
color: #547184;
}
.auth-meta a {
color: #0a5f8f;
font-weight: 700;
}
</style>
{% endblock %}
{% block content %}
<section class="auth-wrap">
<h1>Login</h1>
<p>Bitte melden Sie sich mit Ihrem Konto an, um Termine anzufragen.</p>
<form method="POST" action="{{ url_for('login') }}">
<div class="field">
<label for="username">Benutzername</label>
<input id="username" name="username" type="text" autocomplete="username" required>
</div>
<div class="field">
<label for="password">Passwort</label>
<input id="password" name="password" type="password" autocomplete="current-password" required>
</div>
<button class="auth-btn" type="submit">Login</button>
</form>
<p class="auth-meta">Noch kein Konto? <a href="{{ url_for('register') }}">Jetzt registrieren</a></p>
</section>
{% endblock %}
+450
View File
@@ -0,0 +1,450 @@
{% extends "base.html" %}
{% block title %}IT Loesungen | Modern{% endblock %}
{% block head %}
{{ super() }}
<style>
.hero {
margin-top: 1.2rem;
padding: 3.8rem;
border-radius: 24px;
background: linear-gradient(135deg, #f8fcff 0%, #e1ecf3 70%, #d4e3ed 100%);
border: 1px solid #cfdbe4;
display: grid;
grid-template-columns: 1.2fr 1fr;
gap: 2rem;
position: relative;
overflow: hidden;
box-shadow: 0 25px 55px rgba(17, 35, 50, 0.1);
}
.hero::after {
content: "";
position: absolute;
right: -80px;
bottom: -90px;
width: 280px;
height: 280px;
border-radius: 999px;
background: radial-gradient(circle at center, #0b5b89 0%, #0b5b89 40%, transparent 70%);
opacity: 0.12;
pointer-events: none;
}
.eyebrow {
display: inline-block;
margin-bottom: 0.85rem;
padding: 0.3rem 0.65rem;
border-radius: 999px;
font-size: 0.75rem;
font-weight: 700;
letter-spacing: 0.08em;
text-transform: uppercase;
color: #0c4f78;
background: #d9ebf6;
}
.hero h1 {
font-size: clamp(2rem, 5.1vw, 3.4rem);
color: #06293e;
text-wrap: balance;
}
.hero p {
max-width: 56ch;
margin-top: 1.1rem;
font-size: 1.06rem;
}
.cta-row {
margin-top: 1.8rem;
display: flex;
flex-wrap: wrap;
gap: 0.75rem;
}
.btn {
display: inline-flex;
align-items: center;
justify-content: center;
border-radius: 999px;
border: 1px solid transparent;
padding: 0.68rem 1.15rem;
font-weight: 700;
transition: transform 0.2s ease, box-shadow 0.2s ease;
}
.btn:hover {
transform: translateY(-1px);
}
.btn-primary {
color: #ffffff;
background: linear-gradient(120deg, #0c5a86 0%, #08486c 100%);
box-shadow: 0 8px 20px rgba(11, 87, 128, 0.26);
}
.btn-secondary {
color: #0d4567;
background: #ffffff;
border-color: #b8cddc;
}
.hero-card {
align-self: end;
background: #0e2f44;
color: #dcf0ff;
border-radius: 18px;
padding: 1.2rem;
border: 1px solid #194b68;
}
.hero-card h3 {
font-size: 1.15rem;
margin-bottom: 0.6rem;
}
.hero-card p {
color: #b6d6ea;
font-size: 0.96rem;
margin: 0;
}
.stats {
margin-top: 1rem;
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: 0.55rem;
}
.stats div {
padding: 0.55rem 0.65rem;
border-radius: 12px;
background: #153a52;
border: 1px solid #2a5673;
}
.stats strong {
display: block;
font-family: "Space Grotesk", system-ui, sans-serif;
color: #ffffff;
font-size: 1.08rem;
}
.stats span {
color: #9ec5dc;
font-size: 0.8rem;
}
.section {
margin-top: 2.2rem;
padding: 2rem;
border-radius: 18px;
border: 1px solid #d4dde5;
background: #ffffff;
box-shadow: 0 15px 35px rgba(20, 40, 55, 0.05);
animation: rise 700ms ease both;
}
.section h2 {
font-size: clamp(1.4rem, 2.5vw, 2rem);
margin-bottom: 0.5rem;
color: #0f334b;
}
.section-grid {
margin-top: 1.1rem;
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: 0.9rem;
}
.card {
padding: 1rem;
border-radius: 14px;
border: 1px solid #d9e2ea;
background: linear-gradient(160deg, #fbfdff 0%, #f4f8fb 100%);
}
.card h3 {
font-size: 1.07rem;
color: #113c58;
margin-bottom: 0.45rem;
}
.card p {
font-size: 0.96rem;
}
.project-list {
margin-top: 1.1rem;
display: grid;
gap: 0.8rem;
}
.project {
padding: 0.95rem 1rem;
border-radius: 14px;
border: 1px solid #cfdae4;
background: #f8fbfd;
display: flex;
gap: 1rem;
justify-content: space-between;
align-items: center;
}
.project b {
display: block;
color: #0b3753;
margin-bottom: 0.2rem;
}
.project span {
font-size: 0.88rem;
color: #4a6579;
border: 1px solid #bfd0dd;
padding: 0.2rem 0.55rem;
border-radius: 999px;
white-space: nowrap;
}
.process {
margin-top: 1.2rem;
display: grid;
grid-template-columns: repeat(6, minmax(0, 1fr));
gap: 0.6rem;
}
.step {
padding: 0.8rem 0.65rem;
text-align: center;
border-radius: 12px;
border: 1px solid #ccdae5;
background: #f7fafc;
}
.step strong {
display: block;
margin-bottom: 0.2rem;
font-size: 0.98rem;
color: #0f3650;
}
.step small {
color: #5f7b8f;
}
.team-note {
margin-top: 0.85rem;
padding: 0.9rem 1rem;
border-radius: 12px;
border: 1px solid #f0c18b;
background: linear-gradient(120deg, #fffaf3 0%, #ffeed8 100%);
color: #7d4f14;
font-weight: 600;
}
.contact {
margin-top: 2rem;
border-radius: 20px;
padding: 2.3rem;
border: 1px solid #0f496f;
background: linear-gradient(120deg, #114e75 0%, #0a3d5c 100%);
color: #d2ebfa;
display: flex;
align-items: center;
justify-content: space-between;
gap: 1rem;
}
.contact h2 {
color: #ffffff;
font-size: clamp(1.25rem, 2.7vw, 2rem);
margin-bottom: 0.4rem;
}
.contact p {
color: #b9d8ec;
}
.contact .btn-secondary {
border-color: rgba(255, 255, 255, 0.35);
background: rgba(255, 255, 255, 0.14);
color: #ffffff;
}
.site-footer {
margin-top: 2rem;
padding: 1rem 0 0.3rem;
color: #6a8192;
display: flex;
justify-content: space-between;
gap: 1rem;
font-size: 0.95rem;
}
@keyframes rise {
from {
opacity: 0;
transform: translateY(16px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
@media (max-width: 980px) {
.hero {
grid-template-columns: 1fr;
padding: 2rem;
}
.section-grid {
grid-template-columns: 1fr 1fr;
}
.process {
grid-template-columns: repeat(3, minmax(0, 1fr));
}
}
@media (max-width: 700px) {
.section-grid {
grid-template-columns: 1fr;
}
.project {
flex-direction: column;
align-items: flex-start;
}
.process {
grid-template-columns: 1fr 1fr;
}
.contact {
flex-direction: column;
align-items: flex-start;
padding: 1.55rem;
}
.site-footer {
flex-direction: column;
padding-bottom: 1.2rem;
}
}
</style>
{% endblock %}
{% block content %}
<section class="hero" id="top">
<div>
<span class="eyebrow">IT Loesungen mit Haltung</span>
<h1>Zuhören. Verstehen. Planen. Realisieren.</h1>
<p>
Wir unterstuetzen Unternehmen ganzheitlich bei Enterprise-Architektur,
Projektmanagement und IT-Betrieb. Die Anforderung bleibt im Mittelpunkt,
die Umsetzung wird klar, messbar und belastbar.
</p>
<div class="cta-row">
{% if 'username' in session %}
<a class="btn btn-primary" href="{{ url_for('appointments') }}">Termin anfragen</a>
{% else %}
<a class="btn btn-primary" href="{{ url_for('login') }}">Termin anfragen</a>
{% endif %}
<a class="btn btn-primary" href="#kontakt">Kontakt aufnehmen</a>
<a class="btn btn-secondary" href="#leistungen">Dienstleistungen ansehen</a>
</div>
</div>
<aside class="hero-card" aria-label="Kurzfakten">
<h3>IT Projekte mit Substanz</h3>
<p>
Von Infrastruktur bis Governance: Wir verbinden konzeptionelle Tiefe mit
pragmatischer Umsetzung im Tagesgeschaeft.
</p>
<div class="stats">
<div><strong>25+</strong><span>aktive Mandate</span></div>
<div><strong>98%</strong><span>Zieltermine erreicht</span></div>
<div><strong>24/7</strong><span>Servicebereit</span></div>
</div>
</aside>
</section>
<section class="section" id="leistungen" style="animation-delay: 100ms;">
<h2>Dienstleistungen</h2>
<p>Modulare Leistungen, individuell kombiniert fuer Ihre IT-Roadmap.</p>
<div class="section-grid">
<article class="card">
<h3>Support und Service</h3>
<p>Betriebskritische Systeme stabil halten, proaktiv ueberwachen und schnell reagieren.</p>
</article>
<article class="card">
<h3>Optimierung und Tuning</h3>
<p>Strukturen, Kosten und Performance datenbasiert verbessern statt nur Symptome behandeln.</p>
</article>
<article class="card">
<h3>Health Check und Verify</h3>
<p>Technische Due-Diligence, klare Handlungsplaene und belastbare Priorisierung.</p>
</article>
</div>
</section>
<section class="section" id="projekte" style="animation-delay: 180ms;">
<h2>Projektfelder</h2>
<p>Ausgewählte Schwerpunkte aus laufenden und abgeschlossenen Kundenprojekten.</p>
<div class="project-list">
<div class="project">
<div>
<b>Virtualisierung und Hybrid-Cloud</b>
Migration in wartbare Plattformen mit planbaren Betriebskosten.
</div>
<span>Cloud Computing</span>
</div>
<div class="project">
<div>
<b>Netzwerk und Sicherheit</b>
Segmentierung, Monitoring und Security-Standards auf Enterprise-Niveau.
</div>
<span>Security</span>
</div>
<div class="project">
<div>
<b>Projektmanagement und EAM</b>
Governance, Architekturprinzipien und Umsetzung in einer Linie.
</div>
<span>Transformation</span>
</div>
</div>
</section>
<section class="section" id="team" style="animation-delay: 260ms;">
<h2>Unsere Arbeitsweise</h2>
<p>Klassische Beratungsdisziplin trifft auf moderne Delivery-Kultur.</p>
<div class="process" aria-label="Prozessschritte">
<div class="step"><strong>Zuhoeren</strong><small>Anforderungen erfassen</small></div>
<div class="step"><strong>Verstehen</strong><small>Kontext analysieren</small></div>
<div class="step"><strong>Besprechen</strong><small>Optionen priorisieren</small></div>
<div class="step"><strong>Planen</strong><small>Roadmap festlegen</small></div>
<div class="step"><strong>Realisieren</strong><small>Loesung liefern</small></div>
<div class="step"><strong>Unterstuetzen</strong><small>Betrieb absichern</small></div>
</div>
<p class="team-note">
Aktuell verfuegbar: Ein IT-Infrastruktur-Architekt und ein IT-Governance-Experte
mit technischem Hintergrund fuer neue Aufgaben.
</p>
</section>
<section class="contact" id="kontakt">
<div>
<h2>Bereit fuer das naechste IT-Projekt?</h2>
<p>In einem kurzen Erstgespraech klaeren wir Zielbild, Risiken und realistische Umsetzungsschritte.</p>
</div>
{% if 'username' in session %}
<a class="btn btn-secondary" href="{{ url_for('appointments') }}">Termin anfragen</a>
{% else %}
<a class="btn btn-secondary" href="{{ url_for('login') }}">Termin anfragen</a>
{% endif %}
</section>
{% endblock %}
+354
View File
@@ -0,0 +1,354 @@
{% extends "base.html" %}
{% block title %}Nutzungsbedingungen | Invario{% endblock %}
{% block content %}
<div class="legal-page">
<div class="legal-header">
<h1>Nutzungsbedingungen</h1>
<p>Allgemeine Geschäftsbedingungen (AGB) für die Nutzung von Invario</p>
</div>
<div class="legal-content">
<section>
<h2>1. Geltungsbereich und Vertragsparteien</h2>
<p>
Diese Nutzungsbedingungen regeln die Beziehung zwischen Ihnen als Nutzer und der Invario GmbH
bei der Nutzung unserer Website und Dienstleistungen. Mit der Nutzung unserer Website und Dienste
akzeptieren Sie diese Bedingungen vollständig und verbindlich.
</p>
<p>
<strong>Vertragsparteien sind:</strong>
</p>
<ul>
<li>Invario GmbH (Anbieter)</li>
<li>Der Nutzer (Sie)</li>
</ul>
</section>
<section>
<h2>2. Registrierung und Konto</h2>
<p>
<strong>Anforderungen zur Registrierung:</strong><br>
Für die Nutzung bestimmter Funktionen müssen Sie sich registrieren. Sie verpflichten sich,
wahrheitsgemäß und vollständig folgende Informationen bereitzustellen:
</p>
<ul>
<li>Einen eindeutigen Benutzernamen</li>
<li>Einen Anzeigenamen (Display Name)</li>
<li>Ein sicheres Passwort (mindestens 8 Zeichen)</li>
</ul>
<p>
<strong>Ihre Verantwortung:</strong><br>
Sie sind verantwortlich für den Schutz Ihres Passworts und für alle Aktivitäten in Ihrem Konto.
Sie müssen uns sofort benachrichtigen, wenn Sie unbefugten Zugriff vermuten.
</p>
</section>
<section>
<h2>3. Zulässige Nutzung</h2>
<p>
<strong>Ihre Nutzung unserer Dienste ist auf folgende Weise begrenzt:</strong>
</p>
<ul>
<li>Sie werden die Website nicht für illegale oder schädliche Zwecke nutzen</li>
<li>Sie werden keine Malware, Viren oder zerstörerischen Code hochladen</li>
<li>Sie werden nicht versuchen, unbefugten Zugang zu erhalten</li>
<li>Sie werden die Website oder deren Dienste nicht missbrauchen</li>
<li>Sie werden keine personenbezogenen Daten anderer Nutzer sammeln</li>
<li>Sie werden die Rechte anderer Nutzer respektieren</li>
<li>Sie werden keine rassistischen, rassistischen oder fremdenfeindlichen Inhalte teilen</li>
<li>Sie werden keine Belästigung oder Bedrohung durchführen</li>
</ul>
</section>
<section>
<h2>4. Terminbuchungen und Anfragen</h2>
<p>
<strong>Buchungsprozess:</strong><br>
Sie können über unsere Plattform Termine anfordern. Ein Termin ist nicht automatisch bestätigt,
sondern muss von unserem Team überprüft und genehmigt werden.
</p>
<p>
<strong>Termine inkl. Folgende Rechte und Pflichten:</strong>
</p>
<ul>
<li>Sie können einen Termin anfordern, wir sind jedoch nicht verpflichtet, diesen zu bestätigen</li>
<li>Ein Termin wird erst vermittelt durch eine Bestätigung durch unser Team</li>
<li>Bei Ablehnung erhalten Sie eine Begründung</li>
<li>Sie sind verantwortlich für die Richtigkeit Ihrer Angaben</li>
<li>Im Falle von Stornierungen beachten Sie bitte unsere Stornierungsrichtlinien</li>
</ul>
</section>
<section>
<h2>5. Blog und benutzergenerierte Inhalte</h2>
<p>
<strong>Veröffentlichung von Inhalten:</strong><br>
Admins können Blog-Beiträge veröffentlichen. Diese Beiträge müssen folgende Anforderungen erfüllen:
</p>
<ul>
<li>Keine illegalen oder extremistischen Inhalte</li>
<li>Keine rassistischen oder rassistischen Aussagen</li>
<li>Keine Verletzung von Urheberrechten dritter Parteien</li>
<li>Keine persönlichen Daten von anderen Personen ohne ihre Zustimmung</li>
<li>Keine Spam oder kommerzielle Werbung (außer für autorisierte Partner)</li>
</ul>
<p>
<strong>Recht zur Modernis und Lösch:</strong><br>
Wir behalten uns das Recht vor, Inhalte zu moderieren, zu bearbeiten oder zu löschen,
die gegen diese Bedingungen verstoßen. Dies geschieht ohne Ankündigung und ohne Haftung.
</p>
</section>
<section>
<h2>6. Geistige Eigentum</h2>
<p>
<strong>Alle Inhalte und Materialien auf dieser Website, einschließlich, aber nicht begrenzt auf:</strong>
</p>
<ul>
<li>Texte, Grafiken, Logos, Bilder, Audio- und Video-Dateien</li>
<li>Software, Quellcode und Datenbanken</li>
<li>Die Auswahl, Koordination und Anordnung solcher Materialien</li>
</ul>
<p>
...sind Eigentum der Invario GmbH oder der lizenzierten Parteien und durch Urheberrechts-
und sonstige intellektuelle Eigentumsgesetze geschützt.
</p>
</section>
<section>
<h2>7. Haftungsbeschränkung</h2>
<p>
<strong>Haftungsausschluss:</strong><br>
Soweit von Gesetzes wegen zulässig, erklären wir uns nicht verantwortlich für:
</p>
<ul>
<li>Indirekte, zufällige oder Folgeschäden</li>
<li>Entgangene Gewinne oder Geschäftsmöglichkeiten</li>
<li>Verlust von Daten oder Informationen</li>
<li>Unterbrechungen oder Ausfallzeiten</li>
<li>Schäden durch unbefugten Zugriff</li>
<li>Schäden durch Dritte oder externe Faktoren</li>
</ul>
<p>
<strong>Haftungsbegrenzung:</strong><br>
Falls wir haftbar sind, ist unsere Haftung auf den Betrag begrenzt, den Sie an uns bezahlt haben
oder den geringeren Betrag von 100 EUR.
</p>
</section>
<section>
<h2>8. Sicherheit und Datenschutz</h2>
<p>
Wir nehmen die Sicherheit Ihrer Daten ernst. Weitere Details finden Sie in unserer
<a href="{{ url_for('datenschutz') }}">Datenschutzerklärung</a>.
</p>
<p>
<strong>Sie verpflichten sich:</strong>
</p>
<ul>
<li>Ihr Passwort geheim zu halten</li>
<li>Sich abzumelden, wenn Sie andere das System nutzen</li>
<li>Uns über Sicherheitsverletzungen zu benachrichtigen</li>
</ul>
</section>
<section>
<h2>9. Änderungen der Bedingungen und Dienste</h2>
<p>
<strong>Recht zur Änderung:</strong><br>
Wir behalten uns das Recht vor, diese Nutzungsbedingungen jederzeit zu ändern.
Sie werden benachrichtigt, wenn wesentliche Änderungen vorgenommen werden.
Die weitere Nutzung der Website bedeutet Ihre Annahme der neuen Bedingungen.
</p>
<p>
<strong>Änderungen an Diensten:</strong><br>
Wir behalten uns das Recht vor, Dienste jederzeit zu ändern, zu erweitern oder zu entfernen,
ohne davor verpflichtet zu sein, Sie zu informieren.
</p>
</section>
<section>
<h2>10. Beendigung des Kontos</h2>
<p>
<strong>Beendigung durch den Nutzer:</strong><br>
Sie können Ihr Konto jederzeit beenden, indem Sie uns eine E-Mail senden.
</p>
<p>
<strong>Beendigung durch uns:</strong><br>
Wir können Ihr Konto sofort beenden, wenn Sie diese Nutzungsbedingungen verletzen oder
verdächtige Aktivitäten aufweisen. Dies geschieht ohne Vorankündigung.
</p>
</section>
<section>
<h2>11. Webhooks und automatische Integration</h2>
<p>
<strong>Funktionalität:</strong><br>
Unsere Plattform ermöglicht die Verwaltung von Terminen und Inhalten.
Diese Daten sind nur für Sie und bei Berechtigungen für unseren Admin-Team zugänglich.
</p>
</section>
<section>
<h2>12. Benachrichtigungen und Kommunikation</h2>
<p>
<strong>Kommunikationsmittel:</strong><br>
Wir können Sie per E-Mail, Website-Benachrichtigungen oder anderen Mitteln benachrichtigen.
Sie erklären sich damit einverstanden, solche Benachrichtigungen zu erhalten.
</p>
<p>
<strong>Häufigkeit:</strong><br>
Wir verpflichten uns, Sie nicht übermäßig zu bombardieren, aber wir können Ihnen relevante Aktualisierungen zusenden.
</p>
</section>
<section>
<h2>13. Verfügbarkeit und Support</h2>
<p>
Wir bieten Support während normaler Geschäftszeiten. Während Wartungsarbeiten oder ungeplanten Ausfallzeiten
ist die Website möglicherweise nicht verfügbar. Wir entschuldigen uns für jegliche Unannehmlichkeiten.
</p>
</section>
<section>
<h2>14. Anwendbares Recht und Gerichtsstand</h2>
<p>
Diese Nutzungsbedingungen unterliegen deutschem Recht. Die Gerichtsstände für alle Streitigkeiten
sind in Musterstadt. Sie erklären sich damit einverstanden, dass alle Ansprüche in den deutschen Gerichten
eingereicht werden müssen.
</p>
</section>
<section>
<h2>15. Kontakt</h2>
<p>
Bei Fragen zu diesen Nutzungsbedingungen kontaktieren Sie uns unter:
</p>
<p>
Invario GmbH<br>
E-Mail: info@invario.de<br>
Telefon: +49 (0) 123 / 456789<br>
Adresse: Musterstraße 123, 12345 Musterstadt
</p>
</section>
<div class="last-updated">
<p><strong>Zuletzt aktualisiert:</strong> 1. Januar 2026</p>
</div>
</div>
</div>
<style>
.legal-page {
max-width: 900px;
margin: 0 auto;
}
.legal-header {
margin-bottom: 3rem;
padding-bottom: 2rem;
border-bottom: 2px solid var(--line);
}
.legal-header h1 {
font-size: 2.2rem;
margin-bottom: 0.5rem;
color: var(--brand);
}
.legal-header p {
color: var(--text-soft);
font-size: 0.95rem;
}
.legal-content {
display: flex;
flex-direction: column;
gap: 2.5rem;
}
section {
display: flex;
flex-direction: column;
gap: 1rem;
}
section h2 {
font-size: 1.3rem;
color: var(--brand);
margin-bottom: 0.5rem;
}
section p {
line-height: 1.7;
color: var(--text-soft);
}
section p a {
color: var(--brand);
text-decoration: underline;
}
section p a:hover {
color: var(--accent);
}
section ul {
list-style: none;
padding: 0;
margin: 0;
display: flex;
flex-direction: column;
gap: 0.75rem;
}
section ul li {
padding-left: 1.5rem;
position: relative;
color: var(--text-soft);
line-height: 1.6;
}
section ul li:before {
content: "→";
position: absolute;
left: 0;
color: var(--accent);
font-weight: 700;
}
section strong {
color: var(--text-main);
font-weight: 600;
}
.last-updated {
background: var(--bg-soft);
padding: 1.5rem;
border-radius: var(--radius-md);
margin-top: 2rem;
}
.last-updated p {
margin: 0;
text-align: center;
color: var(--text-soft);
}
@media (max-width: 700px) {
.legal-header h1 {
font-size: 1.6rem;
}
section h2 {
font-size: 1.1rem;
}
.legal-content {
gap: 1.8rem;
}
}
</style>
{% endblock %}
+103
View File
@@ -0,0 +1,103 @@
{% extends "base.html" %}
{% block title %}Registrieren{% endblock %}
{% block head %}
{{ super() }}
<style>
.auth-wrap {
max-width: 560px;
margin: 2rem auto;
padding: 2rem;
border-radius: 18px;
border: 1px solid #d4e0e9;
background: #ffffff;
box-shadow: 0 20px 44px rgba(14, 32, 48, 0.08);
}
.auth-wrap h1 {
font-size: 2rem;
color: #0f344d;
margin-bottom: 0.4rem;
}
.auth-wrap p {
margin-bottom: 1.3rem;
}
.field {
margin-bottom: 0.9rem;
}
.field label {
display: block;
margin-bottom: 0.35rem;
font-weight: 700;
color: #19445f;
}
.field input {
width: 100%;
padding: 0.68rem 0.78rem;
border-radius: 10px;
border: 1px solid #c4d5e2;
font-size: 1rem;
color: #12384f;
}
.field input:focus {
outline: none;
border-color: #2f79a7;
box-shadow: 0 0 0 3px rgba(58, 133, 180, 0.2);
}
.auth-btn {
width: 100%;
border: none;
border-radius: 999px;
padding: 0.72rem;
font-size: 1rem;
font-weight: 700;
color: #ffffff;
background: linear-gradient(120deg, #0a5f8f 0%, #0b4567 100%);
cursor: pointer;
}
.auth-meta {
margin-top: 1rem;
color: #547184;
}
.auth-meta a {
color: #0a5f8f;
font-weight: 700;
}
</style>
{% endblock %}
{% block content %}
<section class="auth-wrap">
<h1>Registrierung</h1>
<p>Legen Sie ein Konto an, um den Kalender fuer Terminanfragen zu nutzen.</p>
<form method="POST" action="{{ url_for('register') }}">
<div class="field">
<label for="display_name">Name</label>
<input id="display_name" name="display_name" type="text" autocomplete="name" required>
</div>
<div class="field">
<label for="username">Benutzername</label>
<input id="username" name="username" type="text" autocomplete="username" required>
</div>
<div class="field">
<label for="password">Passwort (mind. 8 Zeichen)</label>
<input id="password" name="password" type="password" autocomplete="new-password" required>
</div>
<div class="field">
<label for="password_repeat">Passwort wiederholen</label>
<input id="password_repeat" name="password_repeat" type="password" autocomplete="new-password" required>
</div>
<button class="auth-btn" type="submit">Konto erstellen</button>
</form>
<p class="auth-meta">Bereits registriert? <a href="{{ url_for('login') }}">Zum Login</a></p>
</section>
{% endblock %}