Merge branch main into feature/landing-page and resolve file name conflicts
This commit is contained in:
commit
14ba71eb64
BIN
cooking.jpg
Normal file
BIN
cooking.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 134 KiB |
54
css/event_overview_stylesheet.css
Normal file
54
css/event_overview_stylesheet.css
Normal file
@ -0,0 +1,54 @@
|
||||
:root {
|
||||
--black: #000;
|
||||
--white: #fff;
|
||||
--gray-bg: #f4f4f4;
|
||||
--border: 1px solid #000;
|
||||
}
|
||||
|
||||
body { font-family: Arial, sans-serif; margin: 0; padding: 0; }
|
||||
.container { max-width: 1100px; margin: 0 auto; padding: 20px; }
|
||||
|
||||
/* Navbar & Filter */
|
||||
.navbar { display: flex; justify-content: space-between; padding: 20px 5%; border-bottom: var(--border); }
|
||||
.category-group { display: flex; gap: 20px; margin-bottom: 40px; flex-wrap: wrap; }
|
||||
.category-item { cursor: pointer; text-align: center; font-size: 11px; font-weight: bold; }
|
||||
.square { width: 50px; height: 50px; border: var(--border); margin-bottom: 5px; }
|
||||
.category-item.active .square { background: var(--black); }
|
||||
|
||||
/* Event Cards */
|
||||
.event-list { display: flex; flex-direction: column; gap: 20px; }
|
||||
.event-card {
|
||||
display: grid;
|
||||
grid-template-columns: 200px 1fr 150px 150px 50px;
|
||||
border: var(--border);
|
||||
align-items: center;
|
||||
}
|
||||
.event-image { height: 150px; background-size: cover; background-position: center; border-right: var(--border); }
|
||||
.event-content, .event-info, .event-cta { padding: 15px; }
|
||||
.tag { border: var(--border); padding: 3px 10px; border-radius: 15px; font-size: 10px; margin-right: 5px; }
|
||||
.btn-primary { background: var(--black); color: var(--white); border: none; padding: 10px; width: 100%; cursor: pointer; font-weight: bold; }
|
||||
|
||||
/* Empty State Style */
|
||||
.empty-state {
|
||||
text-align: center;
|
||||
padding: 50px 20px;
|
||||
border: 2px dashed #ccc;
|
||||
background: var(--gray-bg);
|
||||
margin-top: 20px;
|
||||
}
|
||||
.btn-outline {
|
||||
background: transparent;
|
||||
border: 2px solid var(--black);
|
||||
padding: 12px 25px;
|
||||
font-weight: bold;
|
||||
cursor: pointer;
|
||||
margin-top: 15px;
|
||||
transition: 0.3s;
|
||||
}
|
||||
.btn-outline:hover { background: var(--black); color: var(--white); }
|
||||
|
||||
/* Responsive */
|
||||
@media (max-width: 850px) {
|
||||
.event-card { grid-template-columns: 1fr; }
|
||||
.event-image { border-right: none; border-bottom: var(--border); width: 100%; }
|
||||
}
|
||||
0
css/stylesheet.css
Normal file
0
css/stylesheet.css
Normal file
38
data/events.json
Normal file
38
data/events.json
Normal file
@ -0,0 +1,38 @@
|
||||
[
|
||||
{
|
||||
"id": 1,
|
||||
"title": "Italienische Tavolata",
|
||||
"location": "LUZERN",
|
||||
"date": "19. MÄR. 2026",
|
||||
"time": "18:30 UHR",
|
||||
"category": "DINNER",
|
||||
"cuisine": "ITALIENISCH",
|
||||
"diet": "VEGGIE",
|
||||
"spots": 4,
|
||||
"image": "https://via.placeholder.com/300x200"
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"title": "Noche Peruana",
|
||||
"location": "LUZERN",
|
||||
"date": "11. APR. 2026",
|
||||
"time": "19:00 UHR",
|
||||
"category": "DINNER",
|
||||
"cuisine": "PERUANISCH",
|
||||
"diet": "FLEISCH",
|
||||
"spots": 2,
|
||||
"image": "https://via.placeholder.com/300x200"
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"title": "Japanese Delight",
|
||||
"location": "ZÜRICH",
|
||||
"date": "02. MAI. 2026",
|
||||
"time": "12:30 UHR",
|
||||
"category": "LUNCH",
|
||||
"cuisine": "JAPANISCH",
|
||||
"diet": "FISCH",
|
||||
"spots": 8,
|
||||
"image": "https://via.placeholder.com/300x200"
|
||||
}
|
||||
]
|
||||
504
event-create.css
Normal file
504
event-create.css
Normal file
@ -0,0 +1,504 @@
|
||||
:root {
|
||||
--color-bg: #f7f7f2;
|
||||
--color-surface: #ffffff;
|
||||
--color-surface-soft: #f0eee7;
|
||||
--color-text: #1f1f1f;
|
||||
--color-text-secondary: #303030;
|
||||
--color-muted: #6b6b6b;
|
||||
--color-border: #d8d8d2;
|
||||
--color-border-strong: #202020;
|
||||
--color-primary: #222222;
|
||||
--color-primary-hover: #111111;
|
||||
--color-focus: #2f6fed;
|
||||
--color-error: #b42318;
|
||||
--color-progress-bg: #ddddda;
|
||||
--color-divider: #ecece7;
|
||||
|
||||
--shadow-soft: 0 10px 30px rgba(0, 0, 0, 0.06);
|
||||
|
||||
--radius-sm: 0.875rem;
|
||||
--radius-md: 1.25rem;
|
||||
--radius-lg: 1.5rem;
|
||||
--radius-pill: 999px;
|
||||
|
||||
--space-1: 0.25rem;
|
||||
--space-2: 0.5rem;
|
||||
--space-3: 0.75rem;
|
||||
--space-4: 1rem;
|
||||
--space-5: 1.5rem;
|
||||
--space-6: 2rem;
|
||||
--space-7: 3rem;
|
||||
--space-8: 4rem;
|
||||
|
||||
--max-width: 1120px;
|
||||
--content-width: 760px;
|
||||
--header-height: 4.5rem;
|
||||
|
||||
--control-min-height: 3rem;
|
||||
--input-min-height: 3.5rem;
|
||||
--card-min-height: 6rem;
|
||||
}
|
||||
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
html {
|
||||
font-size: 100%;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
||||
background: var(--color-bg);
|
||||
color: var(--color-text);
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
a,
|
||||
button,
|
||||
input,
|
||||
textarea {
|
||||
font: inherit;
|
||||
}
|
||||
|
||||
a {
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.site-header {
|
||||
background: var(--color-bg);
|
||||
border-top: 2px solid #232323;
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
}
|
||||
|
||||
.site-nav {
|
||||
width: min(100% - 2rem, var(--max-width));
|
||||
margin: 0 auto;
|
||||
min-height: var(--header-height);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: var(--space-4);
|
||||
}
|
||||
|
||||
.site-logo {
|
||||
font-size: 2rem;
|
||||
font-weight: 800;
|
||||
letter-spacing: 0.02em;
|
||||
}
|
||||
|
||||
.site-nav-links {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-5);
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.site-nav-links a {
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.site-nav-links li:last-child a {
|
||||
width: 2.25rem;
|
||||
height: 2.25rem;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 50%;
|
||||
background: #231f20;
|
||||
color: #ffffff;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.event-create-page {
|
||||
width: min(100% - 2rem, var(--max-width));
|
||||
margin: 0 auto;
|
||||
padding: var(--space-5) 0 0;
|
||||
}
|
||||
|
||||
.event-flow-header {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
margin-bottom: var(--space-4);
|
||||
}
|
||||
|
||||
.event-form {
|
||||
min-height: calc(100vh - var(--header-height) - 9rem);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.step {
|
||||
display: none;
|
||||
padding: var(--space-4) 0 var(--space-7);
|
||||
}
|
||||
|
||||
.step--active {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.step-layout {
|
||||
width: min(100%, var(--content-width));
|
||||
margin: 0 auto;
|
||||
display: grid;
|
||||
gap: var(--space-6);
|
||||
}
|
||||
|
||||
.step-layout--intro {
|
||||
min-height: 60vh;
|
||||
align-content: center;
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.step-copy,
|
||||
.step-fields,
|
||||
.form-field,
|
||||
fieldset {
|
||||
display: grid;
|
||||
gap: var(--space-4);
|
||||
}
|
||||
|
||||
.step-copy {
|
||||
gap: var(--space-4);
|
||||
}
|
||||
|
||||
.step-fields {
|
||||
gap: var(--space-5);
|
||||
}
|
||||
|
||||
.form-field,
|
||||
fieldset {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
gap: var(--space-3);
|
||||
}
|
||||
|
||||
.step-kicker {
|
||||
margin: 0;
|
||||
font-weight: 700;
|
||||
color: var(--color-muted);
|
||||
letter-spacing: 0.02em;
|
||||
}
|
||||
|
||||
h1,
|
||||
h2 {
|
||||
margin: 0;
|
||||
font-size: clamp(2rem, 4vw, 4rem);
|
||||
line-height: 1.03;
|
||||
letter-spacing: -0.03em;
|
||||
}
|
||||
|
||||
.step-text {
|
||||
margin: 0;
|
||||
max-width: 42rem;
|
||||
color: var(--color-text-secondary);
|
||||
font-size: clamp(1rem, 1.4vw, 1.2rem);
|
||||
}
|
||||
|
||||
.intro-card,
|
||||
.review-card {
|
||||
border: 1px solid var(--color-border);
|
||||
background: var(--color-surface);
|
||||
box-shadow: var(--shadow-soft);
|
||||
}
|
||||
|
||||
.intro-card {
|
||||
max-width: 24rem;
|
||||
padding: var(--space-6);
|
||||
border-radius: 1.75rem;
|
||||
background: linear-gradient(135deg, var(--color-surface), var(--color-surface-soft));
|
||||
}
|
||||
|
||||
.intro-card-emoji {
|
||||
font-size: 2rem;
|
||||
margin-bottom: var(--space-3);
|
||||
}
|
||||
|
||||
label,
|
||||
legend {
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.field-hint {
|
||||
margin: -0.25rem 0 0;
|
||||
color: var(--color-muted);
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
|
||||
input[type="text"],
|
||||
input[type="date"],
|
||||
input[type="time"],
|
||||
input[type="number"],
|
||||
textarea {
|
||||
width: 100%;
|
||||
min-height: var(--input-min-height);
|
||||
padding: 0.95rem 1rem;
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: 1rem;
|
||||
background: var(--color-surface);
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
textarea {
|
||||
min-height: 9rem;
|
||||
resize: vertical;
|
||||
}
|
||||
|
||||
.field-row {
|
||||
display: grid;
|
||||
gap: var(--space-4);
|
||||
}
|
||||
|
||||
.option-grid {
|
||||
display: grid;
|
||||
gap: var(--space-3);
|
||||
}
|
||||
|
||||
.option-card {
|
||||
position: relative;
|
||||
display: grid;
|
||||
gap: 0.15rem;
|
||||
min-height: var(--card-min-height);
|
||||
padding: 1rem 1rem 1rem 1.05rem;
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: 1rem;
|
||||
background: var(--color-surface);
|
||||
transition: border-color 0.2s ease, box-shadow 0.2s ease, transform 0.2s ease;
|
||||
}
|
||||
|
||||
.option-card small {
|
||||
color: var(--color-muted);
|
||||
}
|
||||
|
||||
.option-card:hover {
|
||||
transform: translateY(-1px);
|
||||
box-shadow: var(--shadow-soft);
|
||||
}
|
||||
|
||||
.option-card input {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
opacity: 0;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.option-card:has(input:checked) {
|
||||
border: 2px solid var(--color-border-strong);
|
||||
}
|
||||
|
||||
.counter {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: var(--space-3);
|
||||
}
|
||||
|
||||
.counter input {
|
||||
width: 6rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.counter-button,
|
||||
.button {
|
||||
min-height: var(--control-min-height);
|
||||
}
|
||||
|
||||
.counter-button {
|
||||
width: var(--control-min-height);
|
||||
height: var(--control-min-height);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: 50%;
|
||||
background: var(--color-surface);
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
.review-card {
|
||||
padding: var(--space-5);
|
||||
border-radius: var(--radius-lg);
|
||||
}
|
||||
|
||||
.review-list {
|
||||
display: grid;
|
||||
gap: var(--space-4);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.review-item {
|
||||
display: grid;
|
||||
gap: var(--space-1);
|
||||
padding-bottom: var(--space-4);
|
||||
border-bottom: 1px solid var(--color-divider);
|
||||
}
|
||||
|
||||
.review-item:last-child {
|
||||
border-bottom: 0;
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
.review-item dt {
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.review-item dd {
|
||||
margin: 0;
|
||||
white-space: pre-wrap;
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
.flow-footer {
|
||||
position: sticky;
|
||||
bottom: 0;
|
||||
z-index: 5;
|
||||
margin-top: auto;
|
||||
background: rgba(247, 247, 242, 0.96);
|
||||
backdrop-filter: blur(8px);
|
||||
padding-bottom: env(safe-area-inset-bottom);
|
||||
}
|
||||
|
||||
.progress {
|
||||
width: 100%;
|
||||
height: 0.375rem;
|
||||
background: var(--color-progress-bg);
|
||||
}
|
||||
|
||||
.progress-bar {
|
||||
display: block;
|
||||
width: 0;
|
||||
height: 100%;
|
||||
background: var(--color-primary);
|
||||
transition: width 0.25s ease;
|
||||
}
|
||||
|
||||
.flow-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: var(--space-4);
|
||||
padding: var(--space-4) 0;
|
||||
}
|
||||
|
||||
.flow-actions-right {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-4);
|
||||
}
|
||||
|
||||
.error-message {
|
||||
min-height: 1.5rem;
|
||||
margin: 0;
|
||||
color: var(--color-error);
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
|
||||
.button {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 0.9rem 1.35rem;
|
||||
border-radius: var(--radius-pill);
|
||||
border: 1px solid var(--color-border);
|
||||
background: transparent;
|
||||
color: var(--color-text);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.button--ghost:hover {
|
||||
background: rgba(0, 0, 0, 0.03);
|
||||
}
|
||||
|
||||
.button--text {
|
||||
border: 0;
|
||||
padding-left: 0;
|
||||
}
|
||||
|
||||
.button--primary {
|
||||
min-width: 10rem;
|
||||
border-color: var(--color-primary);
|
||||
background: var(--color-primary);
|
||||
color: #ffffff;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.button--primary:hover {
|
||||
background: var(--color-primary-hover);
|
||||
}
|
||||
|
||||
.button--primary:disabled {
|
||||
opacity: 0.45;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.button--intro {
|
||||
justify-self: start;
|
||||
margin-top: var(--space-2);
|
||||
}
|
||||
|
||||
.site-footer {
|
||||
width: min(100% - 2rem, var(--max-width));
|
||||
margin: 0 auto;
|
||||
padding: var(--space-5) 0 var(--space-6);
|
||||
color: var(--color-muted);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
a:focus-visible,
|
||||
button:focus-visible,
|
||||
input:focus-visible,
|
||||
textarea:focus-visible {
|
||||
outline: 3px solid var(--color-focus);
|
||||
outline-offset: 3px;
|
||||
}
|
||||
|
||||
@media (max-width: 767px) {
|
||||
.site-nav {
|
||||
flex-wrap: wrap;
|
||||
padding: var(--space-3) 0;
|
||||
}
|
||||
|
||||
.site-nav-links {
|
||||
gap: var(--space-3);
|
||||
}
|
||||
|
||||
.flow-actions,
|
||||
.flow-actions-right {
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.button--text {
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
.button--primary {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.event-flow-header {
|
||||
justify-content: flex-start;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.step-layout--intro {
|
||||
grid-template-columns: 1.25fr 0.8fr;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.field-row {
|
||||
grid-template-columns: 1fr 1fr;
|
||||
}
|
||||
|
||||
.option-grid--3 {
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
}
|
||||
|
||||
.option-grid--4 {
|
||||
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||||
}
|
||||
}
|
||||
352
event-create.html
Normal file
352
event-create.html
Normal file
@ -0,0 +1,352 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Event erstellen | Invité</title>
|
||||
<link rel="stylesheet" href="stylesheet.css">
|
||||
<link rel="stylesheet" href="event-create.css" />
|
||||
</head>
|
||||
<body>
|
||||
<header class="site-header">
|
||||
<nav class="site-nav" aria-label="Hauptnavigation">
|
||||
<a href="" class="site-logo">INVITÉ</a>
|
||||
|
||||
<ul class="site-nav-links">
|
||||
<li><a href="">Events</a></li>
|
||||
<li><a href="" aria-label="Zum Profil">M</a></li>
|
||||
</ul>
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
<main class="event-create-page">
|
||||
<section class="event-flow-header" aria-label="Event erstellen Aktionen">
|
||||
<a href="" class="button button--ghost">Abbrechen</a>
|
||||
</section>
|
||||
|
||||
<form id="eventForm" class="event-form" novalidate>
|
||||
<section
|
||||
class="step step--active step--intro"
|
||||
data-step="0"
|
||||
aria-labelledby="intro-title"
|
||||
>
|
||||
<div class="step-layout step-layout--intro">
|
||||
<div class="step-copy">
|
||||
<p class="step-kicker">Event erstellen</p>
|
||||
<h1 id="intro-title">Hey <span id="username">{{username}}</span>, was hast du vor?</h1>
|
||||
<p class="step-text">
|
||||
Erzähl uns von deiner Idee – vom Essen bis zur Stimmung. Ob Dinner, Brunch
|
||||
oder etwas ganz Eigenes – wir helfen dir dabei, dein Event Schritt für Schritt aufzubauen.
|
||||
</p>
|
||||
<button type="button" class="button button--primary button--intro" data-start-flow>
|
||||
Los geht’s!
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<aside class="intro-card" aria-label="Hinweis zur Event-Erstellung">
|
||||
<div class="intro-card-emoji" aria-hidden="true">🍽️</div>
|
||||
<p>Aus einer Idee wird Schritt für Schritt dein Event.</p>
|
||||
</aside>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="step" data-step="1" aria-labelledby="step1-title">
|
||||
<div class="step-layout">
|
||||
<div class="step-copy">
|
||||
<p class="step-kicker">Schritt 1</p>
|
||||
<h2 id="step1-title">Was hast du vor?</h2>
|
||||
<p class="step-text">
|
||||
Erzähl uns, was für ein Event du planst. Ist es ein gemütlicher Brunch,
|
||||
ein Dinner mit Wow-Effekt oder einfach ein entspannter Abend mit gutem Essen?
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="step-fields">
|
||||
<div class="form-field">
|
||||
<label for="eventTitle">Wie soll dein Event heißen?</label>
|
||||
<input type="text" id="eventTitle" name="eventTitle" required />
|
||||
</div>
|
||||
|
||||
<fieldset class="form-field">
|
||||
<legend>Art des Essens / Eventtyp</legend>
|
||||
|
||||
<div class="option-grid option-grid--4">
|
||||
<label class="option-card">
|
||||
<input type="radio" name="eventType" value="Brunch" required />
|
||||
<span>Brunch</span>
|
||||
</label>
|
||||
|
||||
<label class="option-card">
|
||||
<input type="radio" name="eventType" value="Lunch" />
|
||||
<span>Lunch</span>
|
||||
</label>
|
||||
|
||||
<label class="option-card">
|
||||
<input type="radio" name="eventType" value="Kaffee + Kuchen" />
|
||||
<span>Kaffee + Kuchen</span>
|
||||
</label>
|
||||
|
||||
<label class="option-card">
|
||||
<input type="radio" name="eventType" value="Dinner" />
|
||||
<span>Dinner</span>
|
||||
</label>
|
||||
</div>
|
||||
</fieldset>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="step" data-step="2" aria-labelledby="step2-title">
|
||||
<div class="step-layout">
|
||||
<div class="step-copy">
|
||||
<p class="step-kicker">Schritt 2</p>
|
||||
<h2 id="step2-title">Was kommt auf den Tisch?</h2>
|
||||
<p class="step-text">
|
||||
Mach uns neugierig. Was kochst du – und wie fühlt sich dein Abend an?
|
||||
Hier entsteht die Geschichte, auf die sich deine Gäste freuen.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="step-fields">
|
||||
<div class="form-field">
|
||||
<label for="menuDescription">Was ist das Menü?</label>
|
||||
<textarea id="menuDescription" name="menuDescription" rows="5" required></textarea>
|
||||
</div>
|
||||
|
||||
<div class="form-field">
|
||||
<label for="eventDescription">Beschreibung des Event-Abends</label>
|
||||
<textarea id="eventDescription" name="eventDescription" rows="6" required></textarea>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="step" data-step="3" aria-labelledby="step3-title">
|
||||
<div class="step-layout">
|
||||
<div class="step-copy">
|
||||
<p class="step-kicker">Schritt 3</p>
|
||||
<h2 id="step3-title">Wen lädst du ein?</h2>
|
||||
<p class="step-text">
|
||||
Wie viele Gäste passen zu deinem Event? Und gibt es etwas, das du bei
|
||||
Ernährung oder Unverträglichkeiten beachten möchtest?
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="step-fields">
|
||||
<fieldset class="form-field">
|
||||
<legend>Maximale Personenanzahl</legend>
|
||||
|
||||
<div class="counter" data-counter>
|
||||
<button
|
||||
type="button"
|
||||
class="counter-button"
|
||||
data-counter-action="decrease"
|
||||
aria-label="Personenzahl verringern"
|
||||
>
|
||||
−
|
||||
</button>
|
||||
<input
|
||||
type="number"
|
||||
id="maxGuests"
|
||||
name="maxGuests"
|
||||
min="1"
|
||||
step="1"
|
||||
value="4"
|
||||
required
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
class="counter-button"
|
||||
data-counter-action="increase"
|
||||
aria-label="Personenzahl erhöhen"
|
||||
>
|
||||
+
|
||||
</button>
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
<fieldset class="form-field">
|
||||
<legend>Ernährungsform</legend>
|
||||
|
||||
<div class="option-grid option-grid--3">
|
||||
<label class="option-card">
|
||||
<input type="radio" name="dietType" value="Omnivor" required />
|
||||
<span>Omnivor</span>
|
||||
<small>Fleisch und/oder Fisch</small>
|
||||
</label>
|
||||
|
||||
<label class="option-card">
|
||||
<input type="radio" name="dietType" value="Vegetarisch" />
|
||||
<span>Vegetarisch</span>
|
||||
</label>
|
||||
|
||||
<label class="option-card">
|
||||
<input type="radio" name="dietType" value="Vegan" />
|
||||
<span>Vegan</span>
|
||||
</label>
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
<fieldset class="form-field">
|
||||
<legend>Allergene / Unverträglichkeiten</legend>
|
||||
<p class="field-hint">Optional – nur auswählen, wenn es für dein Event relevant ist.</p>
|
||||
|
||||
<div class="option-grid option-grid--3">
|
||||
<label class="option-card option-card--checkbox">
|
||||
<input type="checkbox" name="allergies" value="glutenfrei" />
|
||||
<span>glutenfrei</span>
|
||||
</label>
|
||||
|
||||
<label class="option-card option-card--checkbox">
|
||||
<input type="checkbox" name="allergies" value="laktosefrei" />
|
||||
<span>laktosefrei</span>
|
||||
</label>
|
||||
|
||||
<label class="option-card option-card--checkbox">
|
||||
<input type="checkbox" name="allergies" value="ohne Nüsse" />
|
||||
<span>ohne Nüsse</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="form-field">
|
||||
<label for="allergiesOther">Weitere Unverträglichkeiten oder Hinweise (optional)</label>
|
||||
<textarea id="allergiesOther" name="allergiesOther" rows="3"></textarea>
|
||||
</div>
|
||||
</fieldset>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="step" data-step="4" aria-labelledby="step4-title">
|
||||
<div class="step-layout">
|
||||
<div class="step-copy">
|
||||
<p class="step-kicker">Schritt 4</p>
|
||||
<h2 id="step4-title">Wann findet dein Event statt?</h2>
|
||||
<p class="step-text">
|
||||
Wähle Datum und Uhrzeit – und sag uns, wo dein Event stattfindet.
|
||||
Keine Sorge: Die genaue Adresse sehen Gäste erst nach der Buchung.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="step-fields">
|
||||
<div class="field-row">
|
||||
<div class="form-field">
|
||||
<label for="eventDate">Datum</label>
|
||||
<input type="date" id="eventDate" name="eventDate" required />
|
||||
</div>
|
||||
|
||||
<div class="form-field">
|
||||
<label for="eventTime">Uhrzeit</label>
|
||||
<input type="time" id="eventTime" name="eventTime" required />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-field">
|
||||
<label for="eventAddress">Adresse</label>
|
||||
<input type="text" id="eventAddress" name="eventAddress" autocomplete="street-address" required />
|
||||
</div>
|
||||
|
||||
<div class="form-field">
|
||||
<label for="eventCity">Ort</label>
|
||||
<input type="text" id="eventCity" name="eventCity" autocomplete="address-level2" required />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="step" data-step="5" aria-labelledby="step5-title">
|
||||
<div class="step-layout">
|
||||
<div class="step-copy">
|
||||
<p class="step-kicker">Schritt 5</p>
|
||||
<h2 id="step5-title">Alles bereit für deine Gäste?</h2>
|
||||
<p class="step-text">
|
||||
Schau dir dein Event nochmal in Ruhe an. Passt alles?
|
||||
Dann kannst du es jetzt veröffentlichen und Gäste einladen.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="review-card" aria-live="polite">
|
||||
<dl class="review-list">
|
||||
<div class="review-item">
|
||||
<dt>Eventtitel</dt>
|
||||
<dd data-review="eventTitle">–</dd>
|
||||
</div>
|
||||
|
||||
<div class="review-item">
|
||||
<dt>Eventtyp</dt>
|
||||
<dd data-review="eventType">–</dd>
|
||||
</div>
|
||||
|
||||
<div class="review-item">
|
||||
<dt>Menü</dt>
|
||||
<dd data-review="menuDescription">–</dd>
|
||||
</div>
|
||||
|
||||
<div class="review-item">
|
||||
<dt>Event-Abend</dt>
|
||||
<dd data-review="eventDescription">–</dd>
|
||||
</div>
|
||||
|
||||
<div class="review-item">
|
||||
<dt>Maximale Personenanzahl</dt>
|
||||
<dd data-review="maxGuests">–</dd>
|
||||
</div>
|
||||
|
||||
<div class="review-item">
|
||||
<dt>Ernährungsform</dt>
|
||||
<dd data-review="dietType">–</dd>
|
||||
</div>
|
||||
|
||||
<div class="review-item">
|
||||
<dt>Allergene / Unverträglichkeiten</dt>
|
||||
<dd data-review="allergies">Keine Angabe</dd>
|
||||
</div>
|
||||
|
||||
<div class="review-item">
|
||||
<dt>Datum</dt>
|
||||
<dd data-review="eventDate">–</dd>
|
||||
</div>
|
||||
|
||||
<div class="review-item">
|
||||
<dt>Uhrzeit</dt>
|
||||
<dd data-review="eventTime">–</dd>
|
||||
</div>
|
||||
|
||||
<div class="review-item">
|
||||
<dt>Adresse</dt>
|
||||
<dd data-review="eventAddress">–</dd>
|
||||
</div>
|
||||
|
||||
<div class="review-item">
|
||||
<dt>Ort</dt>
|
||||
<dd data-review="eventCity">–</dd>
|
||||
</div>
|
||||
</dl>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div class="flow-footer" id="flowFooter" hidden>
|
||||
<div class="progress" aria-hidden="true">
|
||||
<span id="progressBar" class="progress-bar"></span>
|
||||
</div>
|
||||
|
||||
<div class="flow-actions">
|
||||
<button type="button" id="backButton" class="button button--text">Zurück</button>
|
||||
|
||||
<div class="flow-actions-right">
|
||||
<p id="errorMessage" class="error-message" role="alert" aria-live="assertive"></p>
|
||||
<button type="button" id="nextButton" class="button button--primary">Weiter</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</main>
|
||||
|
||||
<footer class="site-footer">
|
||||
<p>© Social Cooking</p>
|
||||
</footer>
|
||||
|
||||
<script src="event-create.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
443
event-create.js
Normal file
443
event-create.js
Normal file
@ -0,0 +1,443 @@
|
||||
// =============================
|
||||
// SETUP: Wichtige HTML-Elemente holen
|
||||
// Diese Konstanten verbinden unser JavaScript mit dem HTML.
|
||||
// So können wir später Buttons, Formularfelder und Bereiche steuern.
|
||||
// =============================
|
||||
const form = document.getElementById("eventForm");
|
||||
const steps = Array.from(document.querySelectorAll(".step"));
|
||||
const backButton = document.getElementById("backButton");
|
||||
const nextButton = document.getElementById("nextButton");
|
||||
const progressBar = document.getElementById("progressBar");
|
||||
const errorMessage = document.getElementById("errorMessage");
|
||||
const usernameElement = document.getElementById("username");
|
||||
const flowFooter = document.getElementById("flowFooter");
|
||||
|
||||
// =============================
|
||||
// STATE: aktueller Schritt im Flow
|
||||
// currentStep merkt sich, auf welchem Schritt der User gerade ist.
|
||||
// lastStep ist der letzte Schritt im Formular.
|
||||
// =============================
|
||||
let currentStep = 0;
|
||||
const lastStep = steps.length - 1;
|
||||
|
||||
// Text für den Weiter-Button je nach Schritt
|
||||
const nextLabels = {
|
||||
0: "Weiter",
|
||||
1: "Weiter",
|
||||
2: "Weiter",
|
||||
3: "Weiter",
|
||||
4: "Weiter",
|
||||
5: "Event veröffentlichen"
|
||||
};
|
||||
|
||||
// Demo-Wert: Später könnte der Name z. B. aus einem User-Profil kommen
|
||||
usernameElement.textContent = "Mia";
|
||||
|
||||
|
||||
// =============================
|
||||
// STEP 1: Kleine Hilfsfunktionen
|
||||
// Diese Funktionen helfen uns später an mehreren Stellen im Code.
|
||||
// =============================
|
||||
|
||||
/**
|
||||
* Gibt alle Eingabefelder eines bestimmten Schritts zurück.
|
||||
* Rückgabe: Array mit input-, textarea- und select-Feldern.
|
||||
*/
|
||||
function getStepFields(stepIndex) {
|
||||
return Array.from(
|
||||
steps[stepIndex].querySelectorAll("input, textarea, select")
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Zeigt eine Fehlermeldung im Formular an.
|
||||
* Wenn keine Nachricht übergeben wird, wird die Meldung geleert.
|
||||
*/
|
||||
function setErrorMessage(message = "") {
|
||||
errorMessage.textContent = message;
|
||||
}
|
||||
|
||||
|
||||
// =============================
|
||||
// STEP 2: Schritt anzeigen & Oberfläche aktualisieren
|
||||
// showStep() ist eine der wichtigsten Funktionen:
|
||||
// Sie bestimmt, welcher Schritt sichtbar ist,
|
||||
// und aktualisiert gleichzeitig die restliche Oberfläche.
|
||||
// =============================
|
||||
|
||||
/**
|
||||
* Zeigt den gewünschten Schritt an.
|
||||
* Dabei werden auch Buttons, Progress Bar und Review aktualisiert.
|
||||
*/
|
||||
function showStep(index) {
|
||||
currentStep = index;
|
||||
|
||||
// Nur der aktuelle Schritt soll sichtbar sein
|
||||
steps.forEach((step, stepIndex) => {
|
||||
step.classList.toggle("step--active", stepIndex === index);
|
||||
});
|
||||
|
||||
updateFlowVisibility(index);
|
||||
updateReviewIfNeeded(index);
|
||||
updateProgressBar(index, lastStep);
|
||||
setErrorMessage("");
|
||||
|
||||
// Für bessere UX: bei jedem Schritt wieder nach oben scrollen
|
||||
window.scrollTo({ top: 0, behavior: "smooth" });
|
||||
}
|
||||
|
||||
/**
|
||||
* Steuert Sichtbarkeit von Footer und Buttons.
|
||||
* Im Intro-Schritt werden Footer und Zurück-Button versteckt.
|
||||
* Zusätzlich bekommt der Weiter-Button je nach Schritt ein anderes Label.
|
||||
*/
|
||||
function updateFlowVisibility(stepIndex) {
|
||||
const isIntroStep = stepIndex === 0;
|
||||
|
||||
flowFooter.hidden = isIntroStep;
|
||||
backButton.hidden = isIntroStep;
|
||||
nextButton.textContent = nextLabels[stepIndex];
|
||||
}
|
||||
|
||||
|
||||
// =============================
|
||||
// STEP 3: Fortschrittsanzeige
|
||||
// Die Progress Bar zeigt, wie weit der User im Flow ist.
|
||||
// Das Intro zählt noch nicht als eigentlicher Formularschritt.
|
||||
// =============================
|
||||
|
||||
/**
|
||||
* Berechnet den Fortschritt in Prozent und setzt die Breite der Progress Bar.
|
||||
* Beispiel:
|
||||
* - Intro = 0%
|
||||
* - erster echter Formularschritt = 0%
|
||||
* - letzter Schritt = 100%
|
||||
*/
|
||||
function updateProgressBar(stepIndex, totalStepIndex) {
|
||||
let progress = 0;
|
||||
|
||||
if (stepIndex > 0) {
|
||||
progress = ((stepIndex - 1) / (totalStepIndex - 1)) * 100;
|
||||
}
|
||||
|
||||
progressBar.style.width = `${progress}%`;
|
||||
}
|
||||
|
||||
|
||||
// =============================
|
||||
// STEP 4: Review-Daten vorbereiten
|
||||
// Im letzten Schritt werden alle eingegebenen Daten nochmals angezeigt.
|
||||
// Dafür brauchen wir Funktionen, die Feldwerte sauber auslesen und formatieren.
|
||||
// =============================
|
||||
|
||||
/**
|
||||
* Führt das Update der Review nur aus,
|
||||
* wenn wirklich der letzte Schritt geöffnet ist.
|
||||
*/
|
||||
function updateReviewIfNeeded(stepIndex) {
|
||||
if (stepIndex === lastStep) {
|
||||
updateReview();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gibt den Wert eines Formularfeldes zurück.
|
||||
* Rückgabe:
|
||||
* - eingegebener Text / ausgewählte Option
|
||||
* - oder "–", falls nichts vorhanden ist
|
||||
*/
|
||||
function getFieldValue(name) {
|
||||
const field = form.elements[name];
|
||||
|
||||
if (!field) return "–";
|
||||
|
||||
// Spezialfall: Radio-Gruppen verhalten sich anders als normale Inputs
|
||||
if (field instanceof RadioNodeList) {
|
||||
return field.value || "–";
|
||||
}
|
||||
|
||||
return field.value.trim() || "–";
|
||||
}
|
||||
|
||||
/**
|
||||
* Gibt alle ausgewählten Checkbox-Werte als Text zurück.
|
||||
* Beispiel: "Vegetarisch, Vegan"
|
||||
* Falls nichts ausgewählt wurde: "Keine Angabe"
|
||||
*/
|
||||
function getCheckboxValues(name) {
|
||||
const checked = Array.from(
|
||||
form.querySelectorAll(`input[name="${name}"]:checked`)
|
||||
);
|
||||
|
||||
return checked.length
|
||||
? checked.map(item => item.value).join(", ")
|
||||
: "Keine Angabe";
|
||||
}
|
||||
|
||||
/**
|
||||
* Formatiert ein Datum für die Review-Anzeige.
|
||||
* Beispiel: aus "2026-03-26" wird "26.03.2026"
|
||||
*/
|
||||
function formatDate(value) {
|
||||
if (!value || value === "–") return "–";
|
||||
|
||||
const date = new Date(value);
|
||||
if (Number.isNaN(date.getTime())) return value;
|
||||
|
||||
return new Intl.DateTimeFormat("de-CH", {
|
||||
day: "2-digit",
|
||||
month: "2-digit",
|
||||
year: "numeric"
|
||||
}).format(date);
|
||||
}
|
||||
|
||||
/**
|
||||
* Schreibt einen Wert in das passende Feld der Review-Ansicht.
|
||||
* Gesucht wird ein HTML-Element mit data-review="..."
|
||||
*/
|
||||
function updateReviewField(fieldName, value) {
|
||||
const target = document.querySelector(`[data-review="${fieldName}"]`);
|
||||
|
||||
if (target) {
|
||||
target.textContent = value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Baut den Text für Allergien / Hinweise zusammen.
|
||||
* Dabei werden Checkboxen und zusätzliches Freitextfeld kombiniert.
|
||||
*/
|
||||
function buildAllergiesReviewValue() {
|
||||
const selected = getCheckboxValues("allergies");
|
||||
const notes = getFieldValue("allergiesOther");
|
||||
|
||||
if (selected === "Keine Angabe" && notes === "–") return "Keine Angabe";
|
||||
|
||||
if (selected !== "Keine Angabe" && notes !== "–") {
|
||||
return `${selected}, ${notes}`;
|
||||
}
|
||||
|
||||
return selected !== "Keine Angabe" ? selected : notes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Liest alle wichtigen Formularwerte aus
|
||||
* und schreibt sie gesammelt in die Review-Ansicht.
|
||||
*/
|
||||
function updateReview() {
|
||||
const reviewValues = {
|
||||
eventTitle: getFieldValue("eventTitle"),
|
||||
eventType: getFieldValue("eventType"),
|
||||
menuDescription: getFieldValue("menuDescription"),
|
||||
eventDescription: getFieldValue("eventDescription"),
|
||||
maxGuests: getFieldValue("maxGuests"),
|
||||
dietType: getFieldValue("dietType"),
|
||||
allergies: buildAllergiesReviewValue(),
|
||||
eventDate: formatDate(getFieldValue("eventDate")),
|
||||
eventTime: getFieldValue("eventTime"),
|
||||
eventAddress: getFieldValue("eventAddress"),
|
||||
eventCity: getFieldValue("eventCity")
|
||||
};
|
||||
|
||||
Object.entries(reviewValues).forEach(([key, value]) => {
|
||||
updateReviewField(key, value);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// =============================
|
||||
// STEP 5: Validierung
|
||||
// Bevor der User weitergehen darf, prüfen wir:
|
||||
// 1. Sind Pflichtfelder ausgefüllt?
|
||||
// 2. Wurde bei Pflicht-Radios etwas ausgewählt?
|
||||
// =============================
|
||||
|
||||
/**
|
||||
* Prüft, ob der aktuelle Schritt gültig ist.
|
||||
* Rückgabe:
|
||||
* - true = alles okay
|
||||
* - false = es gibt einen Fehler
|
||||
*/
|
||||
function validateCurrentStep() {
|
||||
// Intro und Review müssen nicht validiert werden
|
||||
if (currentStep === 0 || currentStep === lastStep) return true;
|
||||
|
||||
const fields = getStepFields(currentStep);
|
||||
|
||||
// Zuerst Radio-Gruppen prüfen
|
||||
const radioCheck = validateRadioGroups(fields);
|
||||
if (!radioCheck.isValid) {
|
||||
setErrorMessage(radioCheck.message);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Danach normale Pflichtfelder prüfen
|
||||
const requiredCheck = validateRequiredFields(fields);
|
||||
if (!requiredCheck.isValid) {
|
||||
setErrorMessage(requiredCheck.message);
|
||||
return false;
|
||||
}
|
||||
|
||||
setErrorMessage("");
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prüft alle Radio-Gruppen eines Schritts.
|
||||
* Rückgabe:
|
||||
* - Objekt mit isValid: true/false
|
||||
* - bei Fehler zusätzlich eine passende Meldung
|
||||
*/
|
||||
function validateRadioGroups(fields) {
|
||||
const names = [...new Set(fields.filter(f => f.type === "radio").map(f => f.name))];
|
||||
|
||||
for (const name of names) {
|
||||
const group = Array.from(form.querySelectorAll(`input[name="${name}"]`));
|
||||
const required = group.some(f => f.required);
|
||||
const selected = group.some(f => f.checked);
|
||||
|
||||
if (required && !selected) {
|
||||
return {
|
||||
isValid: false,
|
||||
message: "Bitte wähle eine Option aus."
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return { isValid: true };
|
||||
}
|
||||
|
||||
/**
|
||||
* Prüft alle Pflichtfelder ausser Radios und Checkboxen.
|
||||
* Rückgabe:
|
||||
* - Objekt mit isValid: true/false
|
||||
* - bei Fehler zusätzlich eine passende Meldung
|
||||
*/
|
||||
function validateRequiredFields(fields) {
|
||||
for (const field of fields) {
|
||||
if (field.type === "radio" || field.type === "checkbox") continue;
|
||||
|
||||
if (!field.checkValidity()) {
|
||||
return {
|
||||
isValid: false,
|
||||
message: "Bitte fülle alle Pflichtfelder aus."
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return { isValid: true };
|
||||
}
|
||||
|
||||
|
||||
// =============================
|
||||
// STEP 6: Navigation mit Zurück / Weiter
|
||||
// Diese Funktionen bestimmen, was beim Klicken auf die Buttons passiert.
|
||||
// =============================
|
||||
|
||||
/**
|
||||
* Einen Schritt zurückgehen.
|
||||
* Wenn der User im ersten Formularschritt ist, geht es zurück zum Intro.
|
||||
*/
|
||||
function handleBackClick() {
|
||||
if (currentStep > 1) {
|
||||
showStep(currentStep - 1);
|
||||
} else {
|
||||
showStep(0);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Einen Schritt weitergehen.
|
||||
* Vorher wird geprüft, ob der aktuelle Schritt gültig ist.
|
||||
* Im letzten Schritt wird stattdessen das Formular abgeschickt.
|
||||
*/
|
||||
function handleNextClick() {
|
||||
if (!validateCurrentStep()) return;
|
||||
|
||||
if (currentStep < lastStep) {
|
||||
showStep(currentStep + 1);
|
||||
} else {
|
||||
form.requestSubmit();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// =============================
|
||||
// STEP 7: Submit
|
||||
// Aktuell ist das nur eine Demo.
|
||||
// Später könnte hier ein API-Call oder Speichern in einer Datenbank passieren.
|
||||
// =============================
|
||||
|
||||
/**
|
||||
* Reagiert auf das Absenden des Formulars.
|
||||
* preventDefault verhindert, dass die Seite neu lädt.
|
||||
*/
|
||||
function handleFormSubmit(event) {
|
||||
event.preventDefault();
|
||||
alert("Event würde jetzt veröffentlicht werden.");
|
||||
}
|
||||
|
||||
|
||||
// =============================
|
||||
// STEP 8: Counter-Felder (+ / -)
|
||||
// Für Zahlenfelder wie z. B. Anzahl Gäste.
|
||||
// =============================
|
||||
|
||||
/**
|
||||
* Sucht alle Counter-Komponenten
|
||||
* und verbindet die Plus-/Minus-Buttons mit den passenden Funktionen.
|
||||
*/
|
||||
function registerCounterHandlers() {
|
||||
document.querySelectorAll("[data-counter]").forEach(counter => {
|
||||
const input = counter.querySelector("input[type='number']");
|
||||
const dec = counter.querySelector("[data-counter-action='decrease']");
|
||||
const inc = counter.querySelector("[data-counter-action='increase']");
|
||||
|
||||
dec.addEventListener("click", () => updateCounterValue(input, -1));
|
||||
inc.addEventListener("click", () => updateCounterValue(input, 1));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Erhöht oder verringert den Wert eines Zahlenfelds.
|
||||
* Der Wert darf dabei nie kleiner als das definierte Minimum werden.
|
||||
*/
|
||||
function updateCounterValue(input, change) {
|
||||
const min = Number(input.min || 1);
|
||||
const currentValue = Number(input.value || min);
|
||||
input.value = Math.max(min, currentValue + change);
|
||||
}
|
||||
|
||||
|
||||
// =============================
|
||||
// STEP 9: Alles starten
|
||||
// Hier werden alle Event Listener registriert
|
||||
// und der Flow startet mit dem Intro-Schritt.
|
||||
// =============================
|
||||
|
||||
/**
|
||||
* Initialisiert den kompletten Event-Erstellungs-Flow.
|
||||
* Diese Funktion wird einmal beim Laden der Seite aufgerufen.
|
||||
*/
|
||||
function initEventCreationFlow() {
|
||||
// Buttons, die den Flow starten
|
||||
document.querySelectorAll("[data-start-flow]").forEach(btn => {
|
||||
btn.addEventListener("click", () => showStep(1));
|
||||
});
|
||||
|
||||
// Navigation
|
||||
backButton.addEventListener("click", handleBackClick);
|
||||
nextButton.addEventListener("click", handleNextClick);
|
||||
|
||||
// Formular absenden
|
||||
form.addEventListener("submit", handleFormSubmit);
|
||||
|
||||
// Counter aktivieren
|
||||
registerCounterHandlers();
|
||||
|
||||
// Startzustand: Intro anzeigen
|
||||
showStep(0);
|
||||
}
|
||||
|
||||
// Startpunkt des Skripts
|
||||
initEventCreationFlow();
|
||||
29
event_detail.html
Normal file
29
event_detail.html
Normal file
@ -0,0 +1,29 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Event-Detail</title>
|
||||
<link rel="stylesheet" href="css/event_overview_stylesheet.css">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<header class="navbar">
|
||||
<div class="logo">SOCIAL COOKING</div>
|
||||
<nav>
|
||||
<a href="index.html">Events</a>
|
||||
<div class="user-profile">M</div>
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
<main class="container">
|
||||
<div id="detail-view">
|
||||
<p>Lädt Event-Details...</p>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<script src="js/event_overview_script.js"></script>
|
||||
<script src="js/event_detail.js"></script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
36
event_overview.html
Normal file
36
event_overview.html
Normal file
@ -0,0 +1,36 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Event-Overview</title>
|
||||
<link rel="stylesheet" href="css/event_overview_stylesheet.css">
|
||||
</head>
|
||||
<body>
|
||||
<header class="navbar">
|
||||
<div class="logo">SOCIAL COOKING</div>
|
||||
<nav>
|
||||
<span>Events</span>
|
||||
<div class="user-profile">M</div>
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
<main class="container">
|
||||
<h1>Invité Events</h1>
|
||||
|
||||
<section class="filter-section">
|
||||
<p class="filter-label">WORAUF HAST DU LUST</p>
|
||||
<div class="category-group">
|
||||
<div class="category-item" data-cat="BRUNCH"><div class="square"></div><span>BRUNCH</span></div>
|
||||
<div class="category-item" data-cat="LUNCH"><div class="square"></div><span>LUNCH</span></div>
|
||||
<div class="category-item" data-cat="DINNER"><div class="square"></div><span>DINNER</span></div>
|
||||
<div class="category-item" data-cat="COFFEE"><div class="square"></div><span>COFFEE</span></div>
|
||||
<div class="category-item active" data-cat="ALLE"><div class="square"></div><span>ALLE</span></div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="event-grid" class="event-list"></section>
|
||||
</main>
|
||||
<script src="js/event_overview_script.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
47
js/event_detail.js
Normal file
47
js/event_detail.js
Normal file
@ -0,0 +1,47 @@
|
||||
document.addEventListener('DOMContentLoaded', async () => {
|
||||
const detailContainer = document.getElementById('detail-view');
|
||||
|
||||
// 1. ID aus der URL lesen (z.B. detail.html?id=1)
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
const eventId = parseInt(params.get('id'));
|
||||
|
||||
if (!eventId) {
|
||||
window.location.href = 'event_overview.html';
|
||||
return;
|
||||
}
|
||||
|
||||
// 2. Daten laden und das richtige Event suchen
|
||||
try {
|
||||
const response = await fetch('data/events.json');
|
||||
const allEvents = await response.json();
|
||||
const event = allEvents.find(e => e.id === eventId);
|
||||
|
||||
if (event) {
|
||||
renderDetailPage(event);
|
||||
} else {
|
||||
detailContainer.innerHTML = "<h1>Event wurde nicht gefunden.</h1><a href='event_overview.html'>Zurück zur Übersicht</a>";
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Fehler beim Laden der Details:", error);
|
||||
}
|
||||
|
||||
function renderDetailPage(event) {
|
||||
//Layout Deatilseite der Events mit Rücklink zur Übersicht, Eventtitel, Infos und Bild
|
||||
detailContainer.innerHTML = `
|
||||
<div class="detail-header">
|
||||
<a href="event_overview.html" style="text-decoration:none; color:black; font-size:24px;">←</a>
|
||||
<h1>${event.title}</h1>
|
||||
</div>
|
||||
<div class="detail-grid">
|
||||
<div class="info-section">
|
||||
<p>📍 ${event.location} | 📅 ${event.date} | 👤 Max. ${event.spots} Personen</p>
|
||||
<hr>
|
||||
<p>Hier kommen die detaillierten Infos zu ${event.title} hin...</p>
|
||||
</div>
|
||||
<div class="image-section">
|
||||
<img src="${event.image}" alt="${event.title}" style="width:100%;">
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
});
|
||||
109
js/event_overview_script.js
Normal file
109
js/event_overview_script.js
Normal file
@ -0,0 +1,109 @@
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const eventGrid = document.getElementById('event-grid');
|
||||
const filterButtons = document.querySelectorAll('.category-item');
|
||||
let allEvents = [];
|
||||
|
||||
// 1. Daten laden aus JSOn file
|
||||
async function fetchEvents() {
|
||||
try {
|
||||
// Pfad zu JSON File angepasst an lokale Ordnerstruktur
|
||||
const response = await fetch('data/events.json');
|
||||
allEvents = await response.json();
|
||||
renderEvents(allEvents);
|
||||
|
||||
// Beim Laden prüfen, ob ein Filter gespeichert war
|
||||
const savedFilter = sessionStorage.getItem('activeFilter') || 'ALLE';
|
||||
applyFilter(savedFilter);
|
||||
|
||||
//checked ob Fehler beim Laden oder Parsen der Daten auftreten
|
||||
} catch (error) {
|
||||
console.error("Fehler:", error);
|
||||
eventGrid.innerHTML = "<p>Events konnten nicht geladen werden.</p>";
|
||||
}
|
||||
}
|
||||
// Funktion um Filter anzuwenden und gleichzeitig UI zu aktualisieren
|
||||
function applyFilter(category) {
|
||||
|
||||
// UI: Aktiven Button stylen
|
||||
filterButtons.forEach(btn => {
|
||||
if (btn.getAttribute('data-cat') === category) {
|
||||
btn.classList.add('active');
|
||||
} else {
|
||||
btn.classList.remove('active');
|
||||
}
|
||||
});
|
||||
|
||||
// Daten filtern
|
||||
const filtered = category === 'ALLE'
|
||||
? allEvents
|
||||
: allEvents.filter(e => e.category === category);
|
||||
|
||||
renderEvents(filtered);
|
||||
|
||||
//Filter im Browser merken
|
||||
sessionStorage.setItem('activeFilter', category);
|
||||
|
||||
}
|
||||
// 2. Events rendern + "Empty State" Logik
|
||||
function renderEvents(events) {
|
||||
eventGrid.innerHTML = '';
|
||||
|
||||
// PRÜFUNG: Wenn keine Events vorhanden sind zeigt folgende Nachricht
|
||||
if (events.length === 0) {
|
||||
eventGrid.innerHTML = `
|
||||
<div class="empty-state">
|
||||
<h3>Schade! Aktuell gibt es hier keine Events.</h3>
|
||||
<p>Möchtest du vielleicht selbst Gastgeber sein?</p>
|
||||
<button class="btn-outline" onclick="alert('Hier gehts zum Formular!')">Eigenes Event erstellen</button>
|
||||
</div>
|
||||
`;
|
||||
return;
|
||||
}
|
||||
|
||||
// Wenn Events da sind, Karten bauen
|
||||
events.forEach(event => {
|
||||
const card = document.createElement('article');
|
||||
card.className = 'event-card';
|
||||
|
||||
//Klick auf die gesamte Karte leitet zur Detailseite weiter
|
||||
card.style.cursor = "pointer";
|
||||
card.onclick = () => {
|
||||
window.location.href = `event_detail.html?id=${event.id}`;
|
||||
};
|
||||
|
||||
//internes HTML im Js zur Styling der Event-Karte (HIER CHECKEN OB SO OK NACH CLEAN CODE)
|
||||
card.innerHTML = `
|
||||
<div class="event-image" style="background-image: url('${event.image}')"></div>
|
||||
<div class="event-content">
|
||||
<small>📍 ${event.location}</small>
|
||||
<h2 style="margin: 5px 0;">${event.title}</h2>
|
||||
<div>
|
||||
<span class="tag">${event.cuisine}</span>
|
||||
<span class="tag">${event.category}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="event-info">
|
||||
<strong>${event.date}</strong><br>
|
||||
<span>🕒 ${event.time}</span>
|
||||
</div>
|
||||
<div class="event-cta">
|
||||
<div style="font-size: 12px; margin-bottom: 8px;">🥗 ${event.diet}</div>
|
||||
<button class="btn-primary">Anmelden</button>
|
||||
<p style="font-size: 10px; margin-top: 8px;">${event.spots} PLÄTZE FREI</p>
|
||||
</div>
|
||||
<div style="text-align: center;">❤️</div>
|
||||
`;
|
||||
eventGrid.appendChild(card);
|
||||
});
|
||||
}
|
||||
|
||||
// 3. Filter-Logik basic anhand der Kategorien im JSON File
|
||||
filterButtons.forEach(button => {
|
||||
button.addEventListener('click', () => {
|
||||
const selectedCat = button.getAttribute('data-cat');
|
||||
applyFilter(selectedCat);
|
||||
});
|
||||
});
|
||||
|
||||
fetchEvents();
|
||||
});
|
||||
0
js/javascript.js
Normal file
0
js/javascript.js
Normal file
573
kontakt.html
Normal file
573
kontakt.html
Normal file
@ -0,0 +1,573 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Kontaktseite - Invité</title>
|
||||
<link rel="stylesheet" href="stylesheet.css">
|
||||
<style>
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
background-color: #f5f5f5;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 100vh;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/* Header Styles */
|
||||
.header {
|
||||
background-color: white;
|
||||
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
|
||||
padding: 15px 30px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.header-logo {
|
||||
font-size: 24px;
|
||||
font-weight: bold;
|
||||
color: #0084ff;
|
||||
text-decoration: none;
|
||||
letter-spacing: 1px;
|
||||
}
|
||||
|
||||
.header-buttons {
|
||||
display: flex;
|
||||
gap: 15px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.header-btn {
|
||||
padding: 10px 20px;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
text-decoration: none;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.header-btn-secondary {
|
||||
background-color: transparent;
|
||||
color: #0084ff;
|
||||
border: 2px solid #0084ff;
|
||||
}
|
||||
|
||||
.header-btn-secondary:hover {
|
||||
background-color: #0084ff;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.header-btn-primary {
|
||||
background-color: #0084ff;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.header-btn-primary:hover {
|
||||
background-color: #0073e6;
|
||||
}
|
||||
|
||||
/* Main content wrapper */
|
||||
.main-content {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
flex: 1;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.container {
|
||||
background-color: white;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
|
||||
max-width: 1000px;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
gap: 20px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.image-section {
|
||||
flex: 1;
|
||||
background-color: #e8f4f8;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-height: 500px;
|
||||
}
|
||||
|
||||
.image-section img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.form-section {
|
||||
flex: 1;
|
||||
padding: 40px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.container {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.image-section {
|
||||
min-height: 300px;
|
||||
}
|
||||
|
||||
.header {
|
||||
flex-direction: column;
|
||||
gap: 15px;
|
||||
}
|
||||
|
||||
.header-buttons {
|
||||
width: 100%;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.header-btn {
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
h1 {
|
||||
color: #333;
|
||||
margin-bottom: 10px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.info-box {
|
||||
background-color: #e8f4f8;
|
||||
border-left: 4px solid #0084ff;
|
||||
padding: 15px;
|
||||
margin-bottom: 30px;
|
||||
border-radius: 4px;
|
||||
font-size: 14px;
|
||||
color: #333;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.form-group {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
label {
|
||||
display: block;
|
||||
margin-bottom: 6px;
|
||||
color: #333;
|
||||
font-weight: bold;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
input[type="text"],
|
||||
input[type="email"],
|
||||
input[type="password"] {
|
||||
width: 100%;
|
||||
padding: 12px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
font-size: 14px;
|
||||
transition: border-color 0.3s ease;
|
||||
font-family: Arial, sans-serif;
|
||||
}
|
||||
|
||||
input[type="text"]:focus,
|
||||
input[type="email"]:focus,
|
||||
input[type="password"]:focus {
|
||||
outline: none;
|
||||
border-color: #0084ff;
|
||||
box-shadow: 0 0 5px rgba(0, 132, 255, 0.3);
|
||||
}
|
||||
|
||||
button {
|
||||
width: 100%;
|
||||
padding: 12px;
|
||||
background-color: #0084ff;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.3s ease;
|
||||
}
|
||||
|
||||
button:hover {
|
||||
background-color: #0073e6;
|
||||
}
|
||||
|
||||
button:active {
|
||||
background-color: #0063cc;
|
||||
}
|
||||
|
||||
.login-hint {
|
||||
text-align: center;
|
||||
margin-top: 20px;
|
||||
color: #666;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.login-hint a {
|
||||
color: #0084ff;
|
||||
text-decoration: none;
|
||||
font-weight: bold;
|
||||
transition: color 0.3s ease;
|
||||
}
|
||||
|
||||
.login-hint a:hover {
|
||||
color: #0073e6;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.error-message {
|
||||
color: #d32f2f;
|
||||
font-size: 13px;
|
||||
margin-top: 5px;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.form-group.has-error input {
|
||||
border-color: #d32f2f;
|
||||
box-shadow: 0 0 5px rgba(211, 47, 47, 0.3);
|
||||
}
|
||||
|
||||
.form-group.has-error .error-message {
|
||||
display: block;
|
||||
}
|
||||
|
||||
/* Modal Styles */
|
||||
.modal {
|
||||
display: none;
|
||||
position: fixed;
|
||||
z-index: 1000;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: rgba(0, 0, 0, 0.4);
|
||||
animation: fadeIn 0.3s ease;
|
||||
}
|
||||
|
||||
@keyframes fadeIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.modal.show {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
background-color: white;
|
||||
padding: 40px;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
|
||||
max-width: 500px;
|
||||
width: 90%;
|
||||
text-align: center;
|
||||
animation: slideIn 0.3s ease;
|
||||
}
|
||||
|
||||
@keyframes slideIn {
|
||||
from {
|
||||
transform: translateY(-50px);
|
||||
opacity: 0;
|
||||
}
|
||||
to {
|
||||
transform: translateY(0);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.modal-header {
|
||||
position: relative;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.modal-header h2 {
|
||||
color: #0084ff;
|
||||
font-size: 24px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.close-btn {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
font-size: 28px;
|
||||
font-weight: bold;
|
||||
color: #999;
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
padding: 0;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition: color 0.3s ease;
|
||||
}
|
||||
|
||||
.close-btn:hover {
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.modal-body {
|
||||
color: #333;
|
||||
font-size: 16px;
|
||||
line-height: 1.6;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.modal-footer {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.modal-footer button {
|
||||
padding: 10px 30px;
|
||||
font-size: 14px;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.3s ease;
|
||||
}
|
||||
|
||||
.modal-footer .btn-primary {
|
||||
background-color: #0084ff;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.modal-footer .btn-primary:hover {
|
||||
background-color: #0073e6;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<!-- Header -->
|
||||
<header class="header">
|
||||
<div class="header-logo">Invité</div>
|
||||
<div class="header-buttons">
|
||||
<button class="header-btn header-btn-secondary">Event finden</button>
|
||||
<a href="login.html" class="header-btn header-btn-primary">Login</a>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- Main Content -->
|
||||
<div class="main-content">
|
||||
<div class="container">
|
||||
<div class="image-section">
|
||||
<img src="cooking.jpg" alt="Social Cooking">
|
||||
</div>
|
||||
|
||||
<div class="form-section">
|
||||
<h1>Erstelle deinen Account</h1>
|
||||
|
||||
<div class="info-box">
|
||||
<strong>Hinweis:</strong> Sichtbar auf der Plattform ist nur der Vorname, erst einer Anmeldung zum Event ist der Nachname für die Teilnehmenden sichtbar.
|
||||
</div>
|
||||
|
||||
<form id="signupForm">
|
||||
<div class="form-group">
|
||||
<label for="vorname">Vorname *</label>
|
||||
<input type="text" id="vorname" name="vorname" required placeholder="Dein Vorname">
|
||||
<div class="error-message" id="vornameError">Bitte gib deinen Vornamen ein.</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="nachname">Nachname *</label>
|
||||
<input type="text" id="nachname" name="nachname" required placeholder="Dein Nachname">
|
||||
<div class="error-message" id="nachnameError">Bitte gib deinen Nachnamen ein.</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="email">E-Mail Adresse *</label>
|
||||
<input type="email" id="email" name="email" required placeholder="deine.email@example.com">
|
||||
<div class="error-message" id="emailError">Bitte gib eine gültige E-Mail Adresse ein.</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="passwort">Passwort *</label>
|
||||
<input type="password" id="passwort" name="passwort" required placeholder="Mindestens 8 Zeichen">
|
||||
<div class="error-message" id="passwortError">Dein Passwort muss mindestens 8 Zeichen lang sein.</div>
|
||||
</div>
|
||||
|
||||
<button type="submit">Konto erstellen</button>
|
||||
|
||||
<div class="login-hint">
|
||||
Du hast bereits einen Account? <a href="login.html">Hier geht's zum Log-in</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div> <!-- Schließt main-content -->
|
||||
|
||||
<!-- Welcome Modal -->
|
||||
<div id="welcomeModal" class="modal">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button class="close-btn" onclick="closeWelcomeModal()">×</button>
|
||||
<h2>🎉 Willkommen bei Invité!</h2>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
Hier findest du die Übersicht zu den aktuellsten Events.
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button class="btn-primary" onclick="closeWelcomeModal()">Weiter zu den Events</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const signupForm = document.getElementById('signupForm');
|
||||
const vornameInput = document.getElementById('vorname');
|
||||
const nachnameInput = document.getElementById('nachname');
|
||||
const emailInput = document.getElementById('email');
|
||||
const passwortInput = document.getElementById('passwort');
|
||||
const welcomeModal = document.getElementById('welcomeModal');
|
||||
|
||||
// Funktion zum Öffnen des Welcome Modals
|
||||
function openWelcomeModal() {
|
||||
welcomeModal.classList.add('show');
|
||||
document.body.style.overflow = 'hidden';
|
||||
}
|
||||
|
||||
// Funktion zum Schließen des Welcome Modals
|
||||
function closeWelcomeModal() {
|
||||
welcomeModal.classList.remove('show');
|
||||
document.body.style.overflow = 'auto';
|
||||
}
|
||||
|
||||
// Validierungsfunktion
|
||||
function validateForm(event) {
|
||||
event.preventDefault();
|
||||
|
||||
let isValid = true;
|
||||
|
||||
// Vorname-Validierung
|
||||
const vornameValue = vornameInput.value.trim();
|
||||
const vornameGroup = vornameInput.parentElement;
|
||||
|
||||
if (!vornameValue) {
|
||||
vornameGroup.classList.add('has-error');
|
||||
isValid = false;
|
||||
} else {
|
||||
vornameGroup.classList.remove('has-error');
|
||||
}
|
||||
|
||||
// Nachname-Validierung
|
||||
const nachnameValue = nachnameInput.value.trim();
|
||||
const nachnameGroup = nachnameInput.parentElement;
|
||||
|
||||
if (!nachnameValue) {
|
||||
nachnameGroup.classList.add('has-error');
|
||||
isValid = false;
|
||||
} else {
|
||||
nachnameGroup.classList.remove('has-error');
|
||||
}
|
||||
|
||||
// Email-Validierung
|
||||
const emailValue = emailInput.value.trim();
|
||||
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
||||
const emailGroup = emailInput.parentElement;
|
||||
|
||||
if (!emailValue) {
|
||||
emailGroup.classList.add('has-error');
|
||||
document.getElementById('emailError').textContent = 'Bitte gib deine E-Mail Adresse ein.';
|
||||
isValid = false;
|
||||
} else if (!emailRegex.test(emailValue)) {
|
||||
emailGroup.classList.add('has-error');
|
||||
document.getElementById('emailError').textContent = 'Bitte gib eine gültige E-Mail Adresse ein.';
|
||||
isValid = false;
|
||||
} else {
|
||||
emailGroup.classList.remove('has-error');
|
||||
}
|
||||
|
||||
// Passwort-Validierung
|
||||
const passwortValue = passwortInput.value;
|
||||
const passwortGroup = passwortInput.parentElement;
|
||||
|
||||
if (!passwortValue) {
|
||||
passwortGroup.classList.add('has-error');
|
||||
document.getElementById('passwortError').textContent = 'Bitte gib ein Passwort ein.';
|
||||
isValid = false;
|
||||
} else if (passwortValue.length < 8) {
|
||||
passwortGroup.classList.add('has-error');
|
||||
document.getElementById('passwortError').textContent = 'Dein Passwort muss mindestens 8 Zeichen lang sein.';
|
||||
isValid = false;
|
||||
} else {
|
||||
passwortGroup.classList.remove('has-error');
|
||||
}
|
||||
|
||||
// Wenn alle Validierungen bestanden, Modal anzeigen
|
||||
if (isValid) {
|
||||
openWelcomeModal();
|
||||
// Hier würde später die Registrierung zum Backend gesendet
|
||||
}
|
||||
}
|
||||
|
||||
// Fehlerbehandlung bei Input-Änderung (entfernt Fehler wenn Benutzer korrigiert)
|
||||
vornameInput.addEventListener('input', function() {
|
||||
const vornameGroup = this.parentElement;
|
||||
if (this.value.trim()) {
|
||||
vornameGroup.classList.remove('has-error');
|
||||
}
|
||||
});
|
||||
|
||||
nachnameInput.addEventListener('input', function() {
|
||||
const nachnameGroup = this.parentElement;
|
||||
if (this.value.trim()) {
|
||||
nachnameGroup.classList.remove('has-error');
|
||||
}
|
||||
});
|
||||
|
||||
emailInput.addEventListener('input', function() {
|
||||
const emailGroup = this.parentElement;
|
||||
if (this.value.trim()) {
|
||||
emailGroup.classList.remove('has-error');
|
||||
}
|
||||
});
|
||||
|
||||
passwortInput.addEventListener('input', function() {
|
||||
const passwortGroup = this.parentElement;
|
||||
if (this.value) {
|
||||
passwortGroup.classList.remove('has-error');
|
||||
}
|
||||
});
|
||||
|
||||
// Modal schließen wenn außerhalb geklickt wird
|
||||
welcomeModal.addEventListener('click', function(event) {
|
||||
if (event.target === welcomeModal) {
|
||||
closeWelcomeModal();
|
||||
}
|
||||
});
|
||||
|
||||
// Form Submit Event
|
||||
signupForm.addEventListener('submit', validateForm);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
358
login.html
Normal file
358
login.html
Normal file
@ -0,0 +1,358 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Login - Social Cooking</title>
|
||||
<link rel="stylesheet" href="stylesheet.css">
|
||||
<style>
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
background-color: #f5f5f5;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 100vh;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/* Header Styles */
|
||||
.header {
|
||||
background-color: white;
|
||||
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
|
||||
padding: 15px 30px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.header-logo {
|
||||
font-size: 24px;
|
||||
font-weight: bold;
|
||||
color: #0084ff;
|
||||
text-decoration: none;
|
||||
letter-spacing: 1px;
|
||||
}
|
||||
|
||||
.header-buttons {
|
||||
display: flex;
|
||||
gap: 15px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.header-btn {
|
||||
padding: 10px 20px;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
text-decoration: none;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.header-btn-secondary {
|
||||
background-color: transparent;
|
||||
color: #0084ff;
|
||||
border: 2px solid #0084ff;
|
||||
}
|
||||
|
||||
.header-btn-secondary:hover {
|
||||
background-color: #0084ff;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.header-btn-primary {
|
||||
background-color: #0084ff;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.header-btn-primary:hover {
|
||||
background-color: #0073e6;
|
||||
}
|
||||
|
||||
/* Main content wrapper */
|
||||
.main-content {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
flex: 1;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.container {
|
||||
background-color: white;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
|
||||
max-width: 1000px;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
gap: 20px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.image-section {
|
||||
flex: 1;
|
||||
background-color: #e8f4f8;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-height: 500px;
|
||||
}
|
||||
|
||||
.image-section img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.form-section {
|
||||
flex: 1;
|
||||
padding: 40px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.container {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.image-section {
|
||||
min-height: 300px;
|
||||
}
|
||||
|
||||
.header {
|
||||
flex-direction: column;
|
||||
gap: 15px;
|
||||
}
|
||||
|
||||
.header-buttons {
|
||||
width: 100%;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.header-btn {
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
h1 {
|
||||
color: #333;
|
||||
margin-bottom: 30px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.form-group {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
label {
|
||||
display: block;
|
||||
margin-bottom: 6px;
|
||||
color: #333;
|
||||
font-weight: bold;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
input[type="email"],
|
||||
input[type="password"] {
|
||||
width: 100%;
|
||||
padding: 12px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
font-size: 14px;
|
||||
transition: border-color 0.3s ease;
|
||||
font-family: Arial, sans-serif;
|
||||
}
|
||||
|
||||
input[type="email"]:focus,
|
||||
input[type="password"]:focus {
|
||||
outline: none;
|
||||
border-color: #0084ff;
|
||||
box-shadow: 0 0 5px rgba(0, 132, 255, 0.3);
|
||||
}
|
||||
|
||||
button {
|
||||
width: 100%;
|
||||
padding: 12px;
|
||||
background-color: #0084ff;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.3s ease;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
button:hover {
|
||||
background-color: #0073e6;
|
||||
}
|
||||
|
||||
button:active {
|
||||
background-color: #0063cc;
|
||||
}
|
||||
|
||||
.signup-hint {
|
||||
text-align: center;
|
||||
margin-top: 20px;
|
||||
color: #666;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.signup-hint a {
|
||||
color: #0084ff;
|
||||
text-decoration: none;
|
||||
font-weight: bold;
|
||||
transition: color 0.3s ease;
|
||||
}
|
||||
|
||||
.signup-hint a:hover {
|
||||
color: #0073e6;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.error-message {
|
||||
color: #d32f2f;
|
||||
font-size: 13px;
|
||||
margin-top: 5px;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.form-group.has-error input {
|
||||
border-color: #d32f2f;
|
||||
box-shadow: 0 0 5px rgba(211, 47, 47, 0.3);
|
||||
}
|
||||
|
||||
.form-group.has-error .error-message {
|
||||
display: block;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<!-- Header -->
|
||||
<header class="header">
|
||||
<div class="header-logo">Invité</div>
|
||||
<div class="header-buttons">
|
||||
<button class="header-btn header-btn-secondary">Event finden</button>
|
||||
<a href="kontakt.html" class="header-btn header-btn-primary">Anmeldung</a>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- Main Content -->
|
||||
<div class="main-content">
|
||||
<div class="container">
|
||||
<div class="image-section">
|
||||
<img src="cooking.jpg" alt="Social Cooking">
|
||||
</div>
|
||||
|
||||
<div class="form-section">
|
||||
<h1>Willkommen zurück</h1>
|
||||
|
||||
<form id="loginForm">
|
||||
<div class="form-group">
|
||||
<label for="email">Deine E-Mail Adresse *</label>
|
||||
<input type="email" id="email" name="email" required placeholder="deine.email@example.com">
|
||||
<div class="error-message" id="emailError">Bitte gib eine gültige E-Mail Adresse ein.</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="passwort">Dein Passwort *</label>
|
||||
<input type="password" id="passwort" name="passwort" required placeholder="Gib dein Passwort ein">
|
||||
<div class="error-message" id="passwortError">Bitte gib dein Passwort ein.</div>
|
||||
</div>
|
||||
|
||||
<button type="submit">Login</button>
|
||||
|
||||
<div class="signup-hint">
|
||||
Du hast noch keinen Account? <a href="kontakt.html">Hier geht es zur Anmeldung</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div> <!-- Schließt main-content -->
|
||||
|
||||
<script>
|
||||
const loginForm = document.getElementById('loginForm');
|
||||
const emailInput = document.getElementById('email');
|
||||
const passwortInput = document.getElementById('passwort');
|
||||
const emailError = document.getElementById('emailError');
|
||||
const passwortError = document.getElementById('passwortError');
|
||||
|
||||
// Validierungsfunktion
|
||||
function validateForm(event) {
|
||||
event.preventDefault();
|
||||
|
||||
let isValid = true;
|
||||
|
||||
// Email-Validierung
|
||||
const emailValue = emailInput.value.trim();
|
||||
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
||||
const emailGroup = emailInput.parentElement;
|
||||
|
||||
if (!emailValue) {
|
||||
emailGroup.classList.add('has-error');
|
||||
emailError.textContent = 'Bitte gib deine E-Mail Adresse ein.';
|
||||
isValid = false;
|
||||
} else if (!emailRegex.test(emailValue)) {
|
||||
emailGroup.classList.add('has-error');
|
||||
emailError.textContent = 'Bitte gib eine gültige E-Mail Adresse ein.';
|
||||
isValid = false;
|
||||
} else {
|
||||
emailGroup.classList.remove('has-error');
|
||||
}
|
||||
|
||||
// Passwort-Validierung
|
||||
const passwortValue = passwortInput.value;
|
||||
const passwortGroup = passwortInput.parentElement;
|
||||
|
||||
if (!passwortValue) {
|
||||
passwortGroup.classList.add('has-error');
|
||||
passwortError.textContent = 'Bitte gib dein Passwort ein.';
|
||||
isValid = false;
|
||||
} else if (passwortValue.length < 6) {
|
||||
passwortGroup.classList.add('has-error');
|
||||
passwortError.textContent = 'Dein Passwort ist zu kurz. Bitte überprüfe dein Passwort.';
|
||||
isValid = false;
|
||||
} else {
|
||||
passwortGroup.classList.remove('has-error');
|
||||
}
|
||||
|
||||
// Wenn alle Validierungen bestanden, Form absenden (hier kannst du später Backend Integration vornehmen)
|
||||
if (isValid) {
|
||||
alert('Login erfolgreich! (Dies ist eine Demo)');
|
||||
// Hier würde später die Anmeldung zum Backend gesendet
|
||||
}
|
||||
}
|
||||
|
||||
// Fehlerbehandlung bei Input-Änderung (entfernt Fehler wenn Benutzer korrigiert)
|
||||
emailInput.addEventListener('input', function() {
|
||||
const emailGroup = this.parentElement;
|
||||
if (this.value.trim()) {
|
||||
emailGroup.classList.remove('has-error');
|
||||
}
|
||||
});
|
||||
|
||||
passwortInput.addEventListener('input', function() {
|
||||
const passwortGroup = this.parentElement;
|
||||
if (this.value) {
|
||||
passwortGroup.classList.remove('has-error');
|
||||
}
|
||||
});
|
||||
|
||||
// Form Submit Event
|
||||
loginForm.addEventListener('submit', validateForm);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Loading…
x
Reference in New Issue
Block a user