Compare commits

..

9 Commits

Author SHA1 Message Date
46074df578 Merge pull request 'Anpassungen event erstellung Sprint 1' (#10) from event_create into main
Reviewed-on: #10
2026-04-09 17:24:38 +02:00
Ysabelle Moser
ad6b6c2c8c Anpassungen event erstellung Sprint 1 2026-04-09 17:01:37 +02:00
Estelle Köhler
481f3e3934 Fix how-it-works card styling 2026-04-09 12:14:35 +02:00
Estelle Köhler
6b01e178b9 Update how-it-works button styling 2026-04-09 12:07:55 +02:00
«schmona»
c6d8df790e Typographie ergänzt H3-H6 2026-03-30 22:25:59 +02:00
«schmona»
cba0ab45ba Globales stylesheet verknüpft, Navigation auf allen Seiten angepasst 2026-03-30 22:12:06 +02:00
«schmona»
ddd294dd79 Austausch Logo 2026-03-30 01:36:48 +02:00
«schmona»
5c0806dc67 Logo ersetzen 2026-03-30 01:16:04 +02:00
«schmona»
601d416339 Anpassungen stylesheet global 2026-03-30 00:45:56 +02:00
15 changed files with 884 additions and 219 deletions

15
.vscode/launch.json vendored
View File

@ -1,15 +0,0 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"type": "chrome",
"request": "launch",
"name": "Launch Chrome against localhost",
"url": "http://localhost:8080",
"webRoot": "${workspaceFolder}"
}
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 156 KiB

View File

Before

Width:  |  Height:  |  Size: 8.4 KiB

After

Width:  |  Height:  |  Size: 8.4 KiB

View File

@ -6,6 +6,25 @@
--control-min-height: 3rem; --control-min-height: 3rem;
--input-min-height: 3.5rem; --input-min-height: 3.5rem;
--card-min-height: 6rem; --card-min-height: 6rem;
--color-bg: var(--butter);
--color-surface: var(--white);
--color-surface-soft: var(--butter-light);
--color-text: var(--black);
--color-text-secondary: rgba(34, 33, 26, 0.8);
--color-muted: rgba(34, 33, 26, 0.68);
--color-border: rgba(102, 52, 13, 0.16);
--color-border-strong: var(--brown);
--color-divider: rgba(102, 52, 13, 0.14);
--color-primary: var(--olive);
--color-primary-hover: var(--olive-dark);
--color-progress-bg: rgba(212, 75, 36, 0.18);
--color-focus: var(--blue);
--color-error: var(--error);
--shadow-soft: 0 12px 30px rgba(102, 52, 13, 0.1);
--input-border-soft: rgba(102, 52, 13, 0.2);
--input-border-focus: rgba(107, 107, 5, 0.45);
--input-shadow-focus: 0 0 0 4px rgba(107, 107, 5, 0.12);
} }
*, *,
@ -18,12 +37,72 @@ html {
font-size: 100%; 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 { .site-header {
background: var(--color-bg); background: var(--color-bg);
border-top: 2px solid #232323; border-top: 2px solid #232323;
border-bottom: 1px solid var(--color-border); 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 { .event-create-page {
width: min(100% - 2rem, var(--max-width)); width: min(100% - 2rem, var(--max-width));
@ -48,6 +127,10 @@ html {
padding: var(--space-4) 0 var(--space-7); padding: var(--space-4) 0 var(--space-7);
} }
.submission-success {
padding: var(--space-4) 0 var(--space-7);
}
.step--active { .step--active {
display: block; display: block;
} }
@ -63,6 +146,7 @@ html {
min-height: 60vh; min-height: 60vh;
align-content: center; align-content: center;
grid-template-columns: 1fr; grid-template-columns: 1fr;
gap: var(--space-7);
} }
.step-copy, .step-copy,
@ -96,6 +180,13 @@ fieldset {
letter-spacing: 0.02em; letter-spacing: 0.02em;
} }
h1,
h2 {
margin: 0;
font-size: clamp(2rem, 4vw, 4rem);
line-height: 1.03;
letter-spacing: -0.03em;
}
.step-text { .step-text {
margin: 0; margin: 0;
@ -118,9 +209,22 @@ fieldset {
background: linear-gradient(135deg, var(--color-surface), var(--color-surface-soft)); background: linear-gradient(135deg, var(--color-surface), var(--color-surface-soft));
} }
.intro-card-emoji { .intro-card--image {
font-size: 2rem; width: 100%;
margin-bottom: var(--space-3); padding: 0;
border: 0;
overflow: hidden;
background: transparent;
box-shadow: none;
}
.intro-image {
width: 100%;
aspect-ratio: 16 / 10;
display: block;
object-fit: cover;
border-radius: 1.875rem;
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.12);
} }
label, label,
@ -141,11 +245,13 @@ input[type="number"],
textarea { textarea {
width: 100%; width: 100%;
min-height: var(--input-min-height); min-height: var(--input-min-height);
padding: 0.95rem 1rem; padding: 1rem 1.1rem;
border: 1px solid var(--color-border); border: 1px solid var(--input-border-soft);
border-radius: 1rem; border-radius: 1.125rem;
background: var(--color-surface); background: var(--butter-light);
color: var(--color-text); color: var(--color-text);
box-shadow: 0 1px 2px rgba(102, 52, 13, 0.04);
transition: border-color 0.2s ease, box-shadow 0.2s ease, background-color 0.2s ease;
} }
textarea { textarea {
@ -153,6 +259,30 @@ textarea {
resize: vertical; resize: vertical;
} }
input[type="text"]:hover,
input[type="date"]:hover,
input[type="time"]:hover,
input[type="number"]:hover,
textarea:hover {
border-color: rgba(102, 52, 13, 0.28);
}
input[type="text"]:focus,
input[type="date"]:focus,
input[type="time"]:focus,
input[type="number"]:focus,
textarea:focus {
border-color: var(--input-border-focus);
box-shadow: var(--input-shadow-focus);
background: var(--butter-light);
outline: none;
}
.field-invalid {
border-color: var(--tomato) !important;
box-shadow: 0 0 0 2px rgba(212, 75, 36, 0.14);
}
.field-row { .field-row {
display: grid; display: grid;
gap: var(--space-4); gap: var(--space-4);
@ -171,8 +301,8 @@ textarea {
padding: 1rem 1rem 1rem 1.05rem; padding: 1rem 1rem 1rem 1.05rem;
border: 1px solid var(--color-border); border: 1px solid var(--color-border);
border-radius: 1rem; border-radius: 1rem;
background: var(--color-surface); background: var(--butter-light);
transition: border-color 0.2s ease, box-shadow 0.2s ease, transform 0.2s ease; transition: border-color 0.2s ease, box-shadow 0.2s ease, transform 0.2s ease, background-color 0.2s ease, color 0.2s ease;
} }
.option-card small { .option-card small {
@ -180,6 +310,7 @@ textarea {
} }
.option-card:hover { .option-card:hover {
background: var(--olive-light);
transform: translateY(-1px); transform: translateY(-1px);
box-shadow: var(--shadow-soft); box-shadow: var(--shadow-soft);
} }
@ -192,7 +323,18 @@ textarea {
} }
.option-card:has(input:checked) { .option-card:has(input:checked) {
border: 2px solid var(--color-border-strong); border: 1px solid var(--color-primary);
background: var(--color-primary);
color: var(--white);
}
.option-card:has(input:checked) small {
color: rgba(247, 246, 230, 0.88);
}
.option-card--invalid {
border-color: var(--tomato) !important;
box-shadow: 0 0 0 2px rgba(212, 75, 36, 0.14);
} }
.counter { .counter {
@ -214,15 +356,44 @@ textarea {
.counter-button { .counter-button {
width: var(--control-min-height); width: var(--control-min-height);
height: var(--control-min-height); height: var(--control-min-height);
border: 1px solid var(--color-border); border: 1px solid var(--color-primary);
border-radius: 50%; border-radius: 50%;
background: var(--color-surface); background: var(--color-primary);
color: var(--white);
font-size: 1.5rem; font-size: 1.5rem;
line-height: 1;
box-shadow: 0 6px 16px rgba(107, 107, 5, 0.18);
transition: background-color 0.2s ease, transform 0.2s ease, box-shadow 0.2s ease;
}
.counter-button:hover {
background: var(--color-primary-hover);
transform: translateY(-1px);
}
.counter-button:focus-visible {
outline: 3px solid rgba(107, 107, 5, 0.22);
outline-offset: 3px;
} }
.review-card { .review-card {
padding: var(--space-5); display: grid;
border-radius: var(--radius-lg); gap: var(--space-4);
padding: 0;
border: 0;
border-radius: 0;
background: transparent;
box-shadow: none;
}
.review-card--success {
display: grid;
gap: var(--space-5);
padding: var(--space-3) 0 0;
border: 0;
border-radius: 0;
background: transparent;
box-shadow: none;
} }
.review-list { .review-list {
@ -233,14 +404,29 @@ textarea {
.review-item { .review-item {
display: grid; display: grid;
gap: var(--space-1); gap: var(--space-2);
padding-bottom: var(--space-4); padding: 1rem 1.1rem;
border-bottom: 1px solid var(--color-divider); border: 1px solid var(--input-border-soft);
border-radius: 1.125rem;
background: var(--butter-light);
box-shadow: 0 1px 2px rgba(102, 52, 13, 0.04);
cursor: pointer;
transition: border-color 0.2s ease, box-shadow 0.2s ease, transform 0.2s ease, background-color 0.2s ease;
} }
.review-item:last-child { .review-item:last-child {
border-bottom: 0; border-bottom: 1px solid var(--input-border-soft);
padding-bottom: 0; }
.review-item:hover {
border-color: rgba(102, 52, 13, 0.28);
background: rgba(247, 246, 230, 0.92);
transform: translateY(-1px);
}
.review-item:focus-visible {
outline: 3px solid rgba(107, 107, 5, 0.2);
outline-offset: 3px;
} }
.review-item dt { .review-item dt {
@ -253,16 +439,29 @@ textarea {
color: var(--color-text-secondary); color: var(--color-text-secondary);
} }
.submission-success-actions {
display: flex;
justify-content: center;
}
.flow-footer { .flow-footer {
position: sticky; position: sticky;
bottom: 0; bottom: 0;
z-index: 5; z-index: 5;
margin-top: auto; margin-top: auto;
background: rgba(247, 247, 242, 0.96); background: var(--color-bg);
backdrop-filter: blur(8px); backdrop-filter: none;
padding-top: var(--space-4);
padding-bottom: env(safe-area-inset-bottom); padding-bottom: env(safe-area-inset-bottom);
} }
.progress-wrap {
position: relative;
width: min(100%, var(--content-width));
margin: 0 auto;
padding-top: 4.35rem;
}
.progress { .progress {
width: 100%; width: 100%;
height: 0.375rem; height: 0.375rem;
@ -273,15 +472,50 @@ textarea {
display: block; display: block;
width: 0; width: 0;
height: 100%; height: 100%;
background: var(--color-primary); background: var(--tomato);
transition: width 0.25s ease; transition: width 0.25s ease;
} }
.progress-marker {
position: absolute;
top: 0;
transform: translateX(-50%);
display: grid;
justify-items: center;
gap: 0.2rem;
pointer-events: none;
}
.progress-marker::after {
content: "";
width: 0.125rem;
height: 1rem;
background: var(--tomato);
border-radius: 999px;
}
.progress-marker__circle {
width: 2.9rem;
height: 2.9rem;
display: inline-flex;
align-items: center;
justify-content: center;
border-radius: 50%;
background: var(--tomato);
color: var(--butter-light);
font-size: 1.35rem;
font-weight: 600;
line-height: 1;
box-shadow: 0 10px 24px rgba(212, 75, 36, 0.18);
}
.flow-actions { .flow-actions {
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: space-between; justify-content: space-between;
gap: var(--space-4); gap: var(--space-4);
width: min(100%, var(--content-width));
margin: 0 auto;
padding: var(--space-4) 0; padding: var(--space-4) 0;
} }
@ -388,8 +622,10 @@ textarea:focus-visible {
@media (min-width: 768px) { @media (min-width: 768px) {
.step-layout--intro { .step-layout--intro {
grid-template-columns: 1.25fr 0.8fr; width: min(100%, 56rem);
grid-template-columns: 1fr 1fr;
align-items: center; align-items: center;
gap: var(--space-8);
} }
.field-row { .field-row {

View File

@ -26,6 +26,11 @@
--button-green-dark: #514c04; --button-green-dark: #514c04;
} }
body {
margin: 0;
font-family: 'Inter', sans-serif;
background: #FFFDE3; /* butter background color from stylesheet */
}
.page-wrapper { .page-wrapper {
max-width: 1440px; max-width: 1440px;
@ -179,9 +184,10 @@
align-items: center; align-items: center;
gap: 16px; gap: 16px;
padding: 28px 20px; padding: 28px 20px;
background: #6B6B05; background: var(--white) !important;
border: 2px solid var(--tomato) !important;
border-radius: 28px; border-radius: 28px;
box-shadow: 0 12px 30px rgba(0, 0, 0, 0.06); box-shadow: 0 12px 30px rgba(212, 75, 36, 0.08);
} }
.how-step__icon { .how-step__icon {
@ -220,7 +226,7 @@
} }
.how-step__label--brown { .how-step__label--brown {
color: #FFFDE3; color: var(--tomato);
} }
.how-step__label--big { .how-step__label--big {
@ -241,7 +247,7 @@
} }
.how-step__corner-number--brown { .how-step__corner-number--brown {
color: #FFFDE3; color: var(--tomato);
} }
.how-step__png { .how-step__png {
@ -251,9 +257,9 @@
} }
.how-step__png--brown { .how-step__png--brown {
filter: brightness(0) saturate(100%) invert(99%) sepia(6%) saturate(1200%) hue-rotate(10deg) brightness(104%) contrast(97%); filter: brightness(0) saturate(100%) invert(39%) sepia(84%) saturate(1682%) hue-rotate(349deg) brightness(93%) contrast(86%);
}
} }
@media (max-width: 900px) { @media (max-width: 900px) {
.how-it-works__steps { .how-it-works__steps {
@ -372,7 +378,7 @@
} }
/* Center arrow removed using side arrows only */ /* Center arrow removed using side arrows only */
}
@media (max-width: 900px) { @media (max-width: 900px) {
.gallery__track { .gallery__track {

View File

@ -73,7 +73,7 @@ img {
/* Typography */ /* Typography */
h1, h2 { h1, h2, h3, h4, h5, h6 {
font-family: 'Bagel Fat One'; font-family: 'Bagel Fat One';
} }
@ -123,8 +123,8 @@ p {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
min-height: 3.625rem; min-height: 3rem;
padding: 0.1875rem 0.5625rem 0.1875rem var(--space-5); padding: 0.1875rem 0.75rem 0.1875rem var(--space-5);
max-width: none; max-width: none;
width: 100%; width: 100%;
box-sizing: border-box; box-sizing: border-box;
@ -144,10 +144,31 @@ p {
display: block; display: block;
} }
.button-small-links { .nav-tab {
color: var(--black);
font-size: 1.125rem;
font-weight: 500;
letter-spacing: var(--ls-sm);
line-height: 1;
text-decoration: none;
}
.nav-tab:hover, .nav-tab:active,
.nav-tab:focus-visible {
text-decoration: underline;
text-underline-offset: 4px;
}
.button-small:hover, .button-small:active,
.button-small:focus-visible {
background: var(--olive-dark);
color: var(--black);
}
.nav-tab-links {
display: flex; display: flex;
align-items: center; align-items: center;
gap: var(--space-1); gap: var(--space-5);
} }
@ -169,8 +190,10 @@ p {
background-color: var(--olive-dark); background-color: var(--olive-dark);
} }
.button-small { .button-small {
color: var(--black); background: var(--olive);
color: var(--butter-light);
font-size: 1.125rem; font-size: 1.125rem;
font-weight: 500; font-weight: 500;
letter-spacing: var(--ls-sm); letter-spacing: var(--ls-sm);
@ -183,22 +206,8 @@ p {
.button-small:hover, .button-small:active, .button-small:hover, .button-small:active,
.button-small:focus-visible { .button-small:focus-visible {
background: var(--olive-light); background: var(--olive-dark);
color: var(--black);
}
.button-login {
background: var(--olive);
color: var(--butter-light); color: var(--butter-light);
font-size: 1.125rem;
font-weight: 500;
letter-spacing: var(--ls-sm);
line-height: 1;
text-decoration: none;
padding: var(--space-1) var(--space-4);
border-radius: var(--radius-md);
} }
.profile-pill { .profile-pill {

View File

@ -9,23 +9,24 @@
<link rel="stylesheet" href="css/event_create.css" /> <link rel="stylesheet" href="css/event_create.css" />
<!-- Globales Stylesheet --> <!-- Globales Stylesheet -->
<link rel="stylesheet" href="css/stylesheet_global.css"> <link rel="stylesheet" href="css/stylesheet_global.css">
</head> </head>
<body> <body>
<!-- Top Navigation mit Seitenlinks --> <!-- Top Navigation mit Seitenlinks -->
<header class="top-nav-wrap"> <header class="top-nav-wrap">
<div class="top-nav"> <div class="top-nav">
<a class="brand" href="index.html" aria-label="Zur Startseite"> <a class="brand" href="index.html" aria-label="Zur Startseite">
<img src="assets/invite-logo.svg" alt="Invite Logo"> <img src="assets/logo_invite.svg" alt="Invite Logo">
</a> </a>
<nav class="top-nav-links" aria-label="Hauptnavigation"> <nav class="nav-tab-links" aria-label="Hauptnavigation">
<a class="button-small" href="event_overview.html" aria-current="page">Event finden</a> <a class="nav-tab" href="event_overview.html">Event finden</a>
<a class="button-small:active" href="event_create.html">Event erstellen</a> <a class="nav-tab" href="event_create.html">Event erstellen</a>
<a class="button-small" href="login.html" aria-label="Profil">M</a> <a class="button-small" href="login.html" aria-label="Profil">Login</a>
</nav> </nav>
</div> </div>
</header> </header>
<main class="event-create-page"> <main class="event-create-page">
<section class="event-flow-header" aria-label="Event erstellen Aktionen"> <section class="event-flow-header" aria-label="Event erstellen Aktionen">
</section> </section>
@ -38,16 +39,24 @@
> >
<div class="step-layout step-layout--intro"> <div class="step-layout step-layout--intro">
<div class="step-copy"> <div class="step-copy">
<h1 class="step-kicker">Event erstellen</h1> <p class="step-kicker">Event erstellen</p>
<h2 id="intro-title">Hey <span id="username">{{username}}</span>, was hast du vor?</h2> <h1 id="intro-title">Hey <span id="username">{{username}}</span>, was hast du vor?</h1>
<p class="step-text"> <p class="step-text">
Erzähl uns von deiner Idee vom Essen bis zur Stimmung. Ob Dinner, Brunch 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. oder etwas ganz Eigenes wir helfen dir dabei, dein Event in sieben Schritten aufzubauen.
</p> </p>
<button type="button" class="button button--primary button--intro" data-start-flow> <button type="button" class="button button--primary button--intro" data-start-flow>
Los gehts! Los gehts!
</button> </button>
</div> </div>
<aside class="intro-card intro-card--image" aria-label="Stimmungsbild zur Event-Erstellung">
<img
class="intro-image"
src="assets/eventcreate_foodtable.jpg"
alt="Ein gedeckter Tisch mit gemeinsamem Essen"
/>
</aside>
</div> </div>
</section> </section>
@ -63,11 +72,6 @@
</div> </div>
<div class="step-fields"> <div class="step-fields">
<div class="form-field">
<label for="eventTitle">Wie soll dein Event heissen?</label>
<input type="text" id="eventTitle" name="eventTitle" required />
</div>
<fieldset class="form-field"> <fieldset class="form-field">
<legend>Art des Essens / Eventtyp</legend> <legend>Art des Essens / Eventtyp</legend>
@ -93,47 +97,7 @@
</label> </label>
</div> </div>
</fieldset> </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"> <fieldset class="form-field">
<legend>Maximale Personenanzahl</legend> <legend>Maximale Personenanzahl</legend>
@ -165,7 +129,21 @@
</button> </button>
</div> </div>
</fieldset> </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 gibt es zu essen? Gibt es eine bestimmte Ernährungsform oder ein Motto? Je mehr du verrätst, desto besser können sich deine Gäste auf dein Event freuen.
</p>
</div>
<div class="step-fields">
<fieldset class="form-field"> <fieldset class="form-field">
<legend>Ernährungsform</legend> <legend>Ernährungsform</legend>
@ -188,6 +166,25 @@
</div> </div>
</fieldset> </fieldset>
<div class="form-field">
<label for="menuDescription">Was ist das Menü?</label>
<textarea id="menuDescription" name="menuDescription" rows="5" 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">Gibt es etwas zu beachten?</h2>
<p class="step-text">
Gibt es Allergien, Unverträglichkeiten oder andere Hinweise, die für dein Event wichtig sind? So wissen deine Gäste gleich, worauf sie sich einstellen können.
</p>
</div>
<div class="step-fields">
<fieldset class="form-field"> <fieldset class="form-field">
<legend>Allergene / Unverträglichkeiten</legend> <legend>Allergene / Unverträglichkeiten</legend>
<p class="field-hint">Optional nur auswählen, wenn es für dein Event relevant ist.</p> <p class="field-hint">Optional nur auswählen, wenn es für dein Event relevant ist.</p>
@ -208,12 +205,12 @@
<span>ohne Nüsse</span> <span>ohne Nüsse</span>
</label> </label>
</div> </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> </fieldset>
<div class="form-field">
<label for="allergiesOther">Weitere Unverträglichkeiten oder Hinweise (optional)</label>
<textarea id="allergiesOther" name="allergiesOther" rows="3"></textarea>
</div>
</div> </div>
</div> </div>
</section> </section>
@ -224,8 +221,7 @@
<p class="step-kicker">Schritt 4</p> <p class="step-kicker">Schritt 4</p>
<h2 id="step4-title">Wann findet dein Event statt?</h2> <h2 id="step4-title">Wann findet dein Event statt?</h2>
<p class="step-text"> <p class="step-text">
Wähle Datum und Uhrzeit und sag uns, wo dein Event stattfindet. Wähle Datum und Uhrzeit für dein Event. So können deine Gäste direkt einschätzen, ob der Termin für sie passt.
Keine Sorge: Die genaue Adresse sehen Gäste erst nach der Buchung.
</p> </p>
</div> </div>
@ -241,7 +237,21 @@
<input type="time" id="eventTime" name="eventTime" required /> <input type="time" id="eventTime" name="eventTime" required />
</div> </div>
</div> </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">Wo findet dein Event statt?</h2>
<p class="step-text">
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="form-field"> <div class="form-field">
<label for="eventAddress">Adresse</label> <label for="eventAddress">Adresse</label>
<input type="text" id="eventAddress" name="eventAddress" autocomplete="street-address" required /> <input type="text" id="eventAddress" name="eventAddress" autocomplete="street-address" required />
@ -255,81 +265,111 @@
</div> </div>
</section> </section>
<section class="step" data-step="5" aria-labelledby="step5-title"> <section class="step" data-step="6" aria-labelledby="step6-title">
<div class="step-layout"> <div class="step-layout">
<div class="step-copy"> <div class="step-copy">
<p class="step-kicker">Schritt 5</p> <p class="step-kicker">Schritt 6</p>
<h2 id="step5-title">Alles bereit für deine Gäste?</h2> <h2 id="step6-title">Gib deinem Event den letzten Schliff.</h2>
<p class="step-text"> <p class="step-text">
Schau dir dein Event nochmal in Ruhe an. Passt alles? Jetzt bekommt dein Event seinen Namen und die Atmosphäre, die Lust aufs Dabeisein macht.
Dann kannst du es jetzt veröffentlichen und Gäste einladen. Ein klarer Titel (z.B. "Italienische Tavolata") und ein guter Beschreibungstext (Ablauf etc.) machen den Unterschied.
</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>
<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="7" aria-labelledby="step7-title">
<div class="step-layout">
<div class="step-copy">
<p class="step-kicker">Schritt 7</p>
<h2 id="step7-title">Dein Event auf einen Blick.</h2>
<p class="step-text">
Schau dir alle Details nochmal in Ruhe an. Wenn alles passt,
kannst du dein Event jetzt veröffentlichen und Gäste einladen.
</p> </p>
</div> </div>
<div class="review-card" aria-live="polite"> <div class="review-card" aria-live="polite">
<dl class="review-list"> <dl class="review-list">
<div class="review-item"> <div class="review-item" data-edit-step="1" data-edit-field="eventType" role="button" tabindex="0" aria-label="Eventtyp bearbeiten">
<dt>Eventtitel</dt>
<dd data-review="eventTitle"></dd>
</div>
<div class="review-item">
<dt>Eventtyp</dt> <dt>Eventtyp</dt>
<dd data-review="eventType"></dd> <dd data-review="eventType"></dd>
</div> </div>
<div class="review-item"> <div class="review-item" data-edit-step="1" data-edit-field="maxGuests" role="button" tabindex="0" aria-label="Maximale Personenanzahl bearbeiten">
<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> <dt>Maximale Personenanzahl</dt>
<dd data-review="maxGuests"></dd> <dd data-review="maxGuests"></dd>
</div> </div>
<div class="review-item"> <div class="review-item" data-edit-step="2" data-edit-field="dietType" role="button" tabindex="0" aria-label="Ernährungsform bearbeiten">
<dt>Ernährungsform</dt> <dt>Ernährungsform</dt>
<dd data-review="dietType"></dd> <dd data-review="dietType"></dd>
</div> </div>
<div class="review-item"> <div class="review-item" data-edit-step="2" data-edit-field="menuDescription" role="button" tabindex="0" aria-label="Menü bearbeiten">
<dt>Menü</dt>
<dd data-review="menuDescription"></dd>
</div>
<div class="review-item" data-edit-step="3" data-edit-field="allergiesOther" role="button" tabindex="0" aria-label="Allergene und Unverträglichkeiten bearbeiten">
<dt>Allergene / Unverträglichkeiten</dt> <dt>Allergene / Unverträglichkeiten</dt>
<dd data-review="allergies">Keine Angabe</dd> <dd data-review="allergies">Keine Angabe</dd>
</div> </div>
<div class="review-item"> <div class="review-item" data-edit-step="4" data-edit-field="eventDate" role="button" tabindex="0" aria-label="Datum bearbeiten">
<dt>Datum</dt> <dt>Datum</dt>
<dd data-review="eventDate"></dd> <dd data-review="eventDate"></dd>
</div> </div>
<div class="review-item"> <div class="review-item" data-edit-step="4" data-edit-field="eventTime" role="button" tabindex="0" aria-label="Uhrzeit bearbeiten">
<dt>Uhrzeit</dt> <dt>Uhrzeit</dt>
<dd data-review="eventTime"></dd> <dd data-review="eventTime"></dd>
</div> </div>
<div class="review-item"> <div class="review-item" data-edit-step="5" data-edit-field="eventAddress" role="button" tabindex="0" aria-label="Adresse bearbeiten">
<dt>Adresse</dt> <dt>Adresse</dt>
<dd data-review="eventAddress"></dd> <dd data-review="eventAddress"></dd>
</div> </div>
<div class="review-item"> <div class="review-item" data-edit-step="5" data-edit-field="eventCity" role="button" tabindex="0" aria-label="Ort bearbeiten">
<dt>Ort</dt> <dt>Ort</dt>
<dd data-review="eventCity"></dd> <dd data-review="eventCity"></dd>
</div> </div>
<div class="review-item" data-edit-step="6" data-edit-field="eventTitle" role="button" tabindex="0" aria-label="Eventtitel bearbeiten">
<dt>Eventtitel</dt>
<dd data-review="eventTitle"></dd>
</div>
<div class="review-item" data-edit-step="6" data-edit-field="eventDescription" role="button" tabindex="0" aria-label="Event-Abend bearbeiten">
<dt>Event-Abend</dt>
<dd data-review="eventDescription"></dd>
</div>
</dl> </dl>
</div> </div>
</div> </div>
</section> </section>
<div class="flow-footer" id="flowFooter" hidden> <div class="flow-footer" id="flowFooter" hidden>
<div class="progress" aria-hidden="true"> <div class="progress-wrap" aria-hidden="true">
<span id="progressBar" class="progress-bar"></span> <div class="progress-marker" id="progressMarker">
<span class="progress-marker__circle" id="progressMarkerLabel">1</span>
</div>
<div class="progress">
<span id="progressBar" class="progress-bar"></span>
</div>
</div> </div>
<div class="flow-actions"> <div class="flow-actions">
@ -341,6 +381,31 @@
</div> </div>
</div> </div>
</div> </div>
<section
id="submissionSuccess"
class="submission-success"
aria-labelledby="success-title"
aria-live="polite"
hidden
>
<div class="step-layout">
<div class="step-copy">
<p class="step-kicker">Event erstellt</p>
<h2 id="success-title">Dein Event ist ready.</h2>
<p class="step-text">
Sieht gut aus: Deine Idee ist jetzt live und bereit für Gäste.
Im Profil kannst du dein Event anschauen, verwalten oder direkt das nächste planen.
</p>
</div>
<div class="review-card review-card--success">
<div class="submission-success-actions">
<a class="button button--primary" href="event_overview.html">Weiter zu deinem Profil</a>
</div>
</div>
</div>
</section>
</form> </form>
</main> </main>

View File

@ -4,23 +4,24 @@
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Event-Detail</title> <title>Event-Detail</title>
<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=Bagel+Fat+One&family=Jost:wght@400;500;600;700&display=swap" rel="stylesheet">
<link rel="stylesheet" href="css/event_overview.css">
</head>
<body>
<!-- Stylesheet für diese Seite-->
<link rel="stylesheet" href="css/event_overview.css">
<!-- Globales Stylesheet -->
<link rel="stylesheet" href="css/stylesheet_global.css">
</head>
<body>
<!-- Top Navigation mit Seitenlinks --> <!-- Top Navigation mit Seitenlinks -->
<header class="top-nav-wrap"> <header class="top-nav-wrap">
<div class="top-nav"> <div class="top-nav">
<a class="brand" href="index.html" aria-label="Zur Startseite"> <a class="brand" href="index.html" aria-label="Zur Startseite">
<img src="assets/invite-logo.svg" alt="Invite Logo"> <img src="assets/logo_invite.svg" alt="Invite Logo">
</a> </a>
<nav class="top-nav-links" aria-label="Hauptnavigation"> <nav class="nav-tab-links" aria-label="Hauptnavigation">
<a class="nav-link active" href="event_overview.html" aria-current="page">Event finden</a> <a class="nav-tab" href="event_overview.html">Event finden</a>
<a class="nav-link" href="event_create.html">Event erstellen</a> <a class="nav-tab" href="event_create.html">Event erstellen</a>
<a class="profile-pill" href="login.html" aria-label="Profil">M</a> <a class="button-small" href="login.html" aria-label="Profil">Login</a>
</nav> </nav>
</div> </div>
</header> </header>

View File

@ -6,23 +6,22 @@
<title>Event-Overview</title> <title>Event-Overview</title>
<!-- Stylesheet für diese Seite--> <!-- Stylesheet für diese Seite-->
<link rel="stylesheet" href="css/stylesheet_global.css"> <link rel="stylesheet" href="css/event_overview.css">
<!-- Globales Stylesheet --> <!-- Globales Stylesheet -->
<link rel="stylesheet" href="stylesheet.css"> <link rel="stylesheet" href="css/stylesheet_global.css">
</head> </head>
<body> <body>
<!-- Top Navigation mit Seitenlinks --> <!-- Top Navigation mit Seitenlinks -->
<header class="top-nav-wrap"> <header class="top-nav-wrap">
<div class="top-nav"> <div class="top-nav">
<a class="brand" href="index.html" aria-label="Zur Startseite"> <a class="brand" href="index.html" aria-label="Zur Startseite">
<img src="assets/invite-logo.svg" alt="Invite Logo"> <img src="assets/logo_invite.svg" alt="Invite Logo">
</a> </a>
<nav class="top-nav-links" aria-label="Hauptnavigation"> <nav class="nav-tab-links" aria-label="Hauptnavigation">
<a class="nav-link active" href="event_overview.html" aria-current="page">Event finden</a> <a class="nav-tab" href="event_overview.html">Event finden</a>
<a class="nav-link" href="event_create.html">Event erstellen</a> <a class="nav-tab" href="event_create.html">Event erstellen</a>
<a class="profile-pill" href="login.html" aria-label="Profil">M</a> <a class="button-small" href="login.html" aria-label="Profil">Login</a>
</nav> </nav>
</div> </div>
</header> </header>

View File

@ -3,20 +3,30 @@
<head> <head>
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Social Cooking Wireframe</title> <title>Invité | Events entdecken</title>
<link rel="stylesheet" href="css/landingpage.css" />
</head>
<body>
<div class="page-wrapper">
<header class="header">
<div class="header__brand">LOGO</div>
<div class="header__actions"> <!-- Stylesheet für diese Seite-->
<nav class="nav"> <link rel="stylesheet" href="css/landingpage.css?v=2" />
<a class="nav__link btn btn--outline" href="event_overview.html">Event finden</a> <!-- Globales Stylesheet -->
</nav> <link rel="stylesheet" href="css/stylesheet_global.css?v=2">
<a class="btn btn--outline" href="login.html">Login</a>
</div> <!-- Font Awesome
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.0/css/all.min.css" integrity="sha512-dU7ZrF1pFq5kVnPzlV9+04YhARzNjCX5Q5P1shgMpuN4s5I8mI8QD4981h7kYtV7sSgNldR0z5pZW5bS2ZpY3Q==" crossorigin="anonymous" referrerpolicy="no-referrer" /> -->
</head>
<body>
<!-- Top Navigation mit Seitenlinks -->
<header class="top-nav-wrap">
<div class="top-nav">
<a class="brand" href="index.html" aria-label="Zur Startseite">
<img src="assets/logo_invite.svg" alt="Invite Logo">
</a>
<nav class="nav-tab-links" aria-label="Hauptnavigation">
<a class="nav-tab" href="event_overview.html">Event finden</a>
<a class="nav-tab" href="event_create.html">Event erstellen</a>
<a class="button-small" href="login.html" aria-label="Profil">Login</a>
</nav>
</div>
</header> </header>
<main class="main-content"> <main class="main-content">
@ -110,7 +120,7 @@
<div class="gallery__info"> <div class="gallery__info">
<div class="gallery__handle" style="display: flex; align-items: center; gap: 16px;"> <div class="gallery__handle" style="display: flex; align-items: center; gap: 16px;">
<img src="assets/instagram.png" alt="Instagram" class="gallery__icon--instagram" /> <img src="assets/instagram.png" alt="Instagram" class="gallery__icon--instagram" />
<img src="assets/invite-logo.svg" alt="Invité Logo" class="gallery__icon--invite" /> <img src="assets/logo_invite.svg" alt="Invité Logo" class="gallery__icon--invite" />
</div> </div>
</div> </div>
</section> </section>

View File

@ -8,9 +8,13 @@ const steps = Array.from(document.querySelectorAll(".step"));
const backButton = document.getElementById("backButton"); const backButton = document.getElementById("backButton");
const nextButton = document.getElementById("nextButton"); const nextButton = document.getElementById("nextButton");
const progressBar = document.getElementById("progressBar"); const progressBar = document.getElementById("progressBar");
const progressMarker = document.getElementById("progressMarker");
const progressMarkerLabel = document.getElementById("progressMarkerLabel");
const errorMessage = document.getElementById("errorMessage"); const errorMessage = document.getElementById("errorMessage");
const usernameElement = document.getElementById("username"); const usernameElement = document.getElementById("username");
const flowFooter = document.getElementById("flowFooter"); const flowFooter = document.getElementById("flowFooter");
const submissionSuccess = document.getElementById("submissionSuccess");
const EVENTS_STORAGE_KEY = "socialCookingEvents";
// ============================= // =============================
// STATE: aktueller Schritt im Flow // STATE: aktueller Schritt im Flow
@ -27,7 +31,9 @@ const nextLabels = {
2: "Weiter", 2: "Weiter",
3: "Weiter", 3: "Weiter",
4: "Weiter", 4: "Weiter",
5: "Event veröffentlichen" 5: "Weiter",
6: "Weiter",
7: "Event veröffentlichen"
}; };
// Demo-Wert: Später könnte der Name z. B. aus einem User-Profil kommen // Demo-Wert: Später könnte der Name z. B. aus einem User-Profil kommen
@ -57,6 +63,39 @@ function setErrorMessage(message = "") {
errorMessage.textContent = message; errorMessage.textContent = message;
} }
/**
* Entfernt alle Fehlermarkierungen innerhalb eines Schritts.
*/
function clearStepInvalidState(stepIndex) {
if (!steps[stepIndex]) return;
steps[stepIndex]
.querySelectorAll(".field-invalid, .option-card--invalid")
.forEach(element => {
element.classList.remove("field-invalid", "option-card--invalid");
});
}
/**
* Markiert ein einzelnes Feld visuell als ungültig.
*/
function markFieldInvalid(field) {
field.classList.add("field-invalid");
}
/**
* Markiert eine ganze Radio-Gruppe visuell als ungültig.
*/
function markRadioGroupInvalid(group) {
group.forEach(field => {
const card = field.closest(".option-card");
if (card) {
card.classList.add("option-card--invalid");
}
});
}
// ============================= // =============================
// STEP 2: Schritt anzeigen & Oberfläche aktualisieren // STEP 2: Schritt anzeigen & Oberfläche aktualisieren
@ -71,6 +110,8 @@ function setErrorMessage(message = "") {
*/ */
function showStep(index) { function showStep(index) {
currentStep = index; currentStep = index;
submissionSuccess.hidden = true;
clearStepInvalidState(index);
// Nur der aktuelle Schritt soll sichtbar sein // Nur der aktuelle Schritt soll sichtbar sein
steps.forEach((step, stepIndex) => { steps.forEach((step, stepIndex) => {
@ -115,12 +156,21 @@ function updateFlowVisibility(stepIndex) {
*/ */
function updateProgressBar(stepIndex, totalStepIndex) { function updateProgressBar(stepIndex, totalStepIndex) {
let progress = 0; let progress = 0;
let markerPosition = 0;
let markerStep = 1;
let markerTransform = "translateX(-50%)";
if (stepIndex > 0) { if (stepIndex > 0) {
progress = ((stepIndex - 1) / (totalStepIndex - 1)) * 100; progress = ((stepIndex - 1) / (totalStepIndex - 1)) * 100;
markerPosition = ((stepIndex - 1) / (totalStepIndex - 1)) * 100;
markerStep = stepIndex;
} }
progressBar.style.width = `${progress}%`; progressBar.style.width = `${progress}%`;
progressMarker.style.left = `${markerPosition}%`;
progressMarker.style.transform = markerTransform;
progressMarker.hidden = stepIndex === 0;
progressMarkerLabel.textContent = String(markerStep);
} }
@ -225,7 +275,18 @@ function buildAllergiesReviewValue() {
* und schreibt sie gesammelt in die Review-Ansicht. * und schreibt sie gesammelt in die Review-Ansicht.
*/ */
function updateReview() { function updateReview() {
const reviewValues = { const reviewValues = getReviewValues();
Object.entries(reviewValues).forEach(([key, value]) => {
updateReviewField(key, value);
});
}
/**
* Liest alle wichtigen Formularwerte gesammelt aus.
*/
function getReviewValues() {
return {
eventTitle: getFieldValue("eventTitle"), eventTitle: getFieldValue("eventTitle"),
eventType: getFieldValue("eventType"), eventType: getFieldValue("eventType"),
menuDescription: getFieldValue("menuDescription"), menuDescription: getFieldValue("menuDescription"),
@ -238,12 +299,137 @@ function updateReview() {
eventAddress: getFieldValue("eventAddress"), eventAddress: getFieldValue("eventAddress"),
eventCity: getFieldValue("eventCity") eventCity: getFieldValue("eventCity")
}; };
Object.entries(reviewValues).forEach(([key, value]) => {
updateReviewField(key, value);
});
} }
/**
* Liest lokal gespeicherte Events robust aus dem Browser-Storage.
*/
function getStoredEvents() {
try {
const stored = localStorage.getItem(EVENTS_STORAGE_KEY);
return stored ? JSON.parse(stored) : [];
} catch (error) {
console.error("Lokale Events konnten nicht gelesen werden:", error);
return [];
}
}
/**
* Speichert die komplette Eventliste zurück in den Browser-Storage.
*/
function setStoredEvents(events) {
localStorage.setItem(EVENTS_STORAGE_KEY, JSON.stringify(events));
}
/**
* Formatiert ein ISO-Datum in das bestehende Eventformat der Demo-Daten.
*/
function formatDateForStorage(value) {
if (!value) return "";
const date = new Date(value);
if (Number.isNaN(date.getTime())) return value;
const monthMap = {
0: "JAN",
1: "FEB",
2: "MRZ",
3: "APR",
4: "MAI",
5: "JUN",
6: "JUL",
7: "AUG",
8: "SEP",
9: "OKT",
10: "NOV",
11: "DEZ"
};
const day = String(date.getDate()).padStart(2, "0");
const month = monthMap[date.getMonth()];
const year = date.getFullYear();
return `${day}. ${month}. ${year}`;
}
/**
* Formatiert die Zeit in das bestehende Eventformat der Demo-Daten.
*/
function formatTimeForStorage(value) {
return value ? `${value} UHR` : "";
}
/**
* Zerlegt das Menü-Textarea in saubere Listenpunkte.
*/
function buildMenuItems(value) {
return value
.split("\n")
.map(item => item.replace(/^[•-]\s*/, "").trim())
.filter(Boolean);
}
/**
* Leitet den gewählten Eventtyp in die Kategorien der Übersicht über.
*/
function mapEventTypeToCategory(value) {
const categoryMap = {
Brunch: "BRUNCH",
Lunch: "LUNCH",
Dinner: "DINNER",
"Kaffee + Kuchen": "COFFEE"
};
return categoryMap[value] || value.toUpperCase();
}
/**
* Baut aus den Formulardaten ein lokal speicherbares Event-Objekt.
*/
function buildStoredEvent() {
const eventType = getFieldValue("eventType");
const dietType = getFieldValue("dietType");
const menuDescription = form.elements.menuDescription.value.trim();
const eventDescription = form.elements.eventDescription.value.trim();
const eventDate = form.elements.eventDate.value;
const eventTime = form.elements.eventTime.value;
const eventCity = form.elements.eventCity.value.trim();
return {
id: Date.now(),
title: form.elements.eventTitle.value.trim(),
location: eventCity,
address: form.elements.eventAddress.value.trim(),
date: formatDateForStorage(eventDate),
time: formatTimeForStorage(eventTime),
category: mapEventTypeToCategory(eventType),
diet: dietType,
spots: Number(form.elements.maxGuests.value),
host: {
name: usernameElement.textContent.trim() || "Host",
initial: (usernameElement.textContent.trim().charAt(0) || "H").toUpperCase()
},
hostMessage: [eventDescription],
menu: buildMenuItems(menuDescription),
specifications: getCheckboxValues("allergies") === "Keine Angabe"
? []
: getCheckboxValues("allergies").split(", ").filter(Boolean),
allergiesNote: form.elements.allergiesOther.value.trim(),
participants: [usernameElement.textContent.trim() || "Host"],
gallery: [],
createdAt: new Date().toISOString(),
source: "local"
};
}
/**
* Speichert das aktuell erstellte Event lokal im Browser.
*/
function saveCurrentEvent() {
const storedEvents = getStoredEvents();
const nextEvents = [buildStoredEvent(), ...storedEvents];
setStoredEvents(nextEvents);
}
// ============================= // =============================
// STEP 5: Validierung // STEP 5: Validierung
@ -263,6 +449,7 @@ function validateCurrentStep() {
if (currentStep === 0 || currentStep === lastStep) return true; if (currentStep === 0 || currentStep === lastStep) return true;
const fields = getStepFields(currentStep); const fields = getStepFields(currentStep);
clearStepInvalidState(currentStep);
// Zuerst Radio-Gruppen prüfen // Zuerst Radio-Gruppen prüfen
const radioCheck = validateRadioGroups(fields); const radioCheck = validateRadioGroups(fields);
@ -297,6 +484,7 @@ function validateRadioGroups(fields) {
const selected = group.some(f => f.checked); const selected = group.some(f => f.checked);
if (required && !selected) { if (required && !selected) {
markRadioGroupInvalid(group);
return { return {
isValid: false, isValid: false,
message: "Bitte wähle eine Option aus." message: "Bitte wähle eine Option aus."
@ -318,6 +506,7 @@ function validateRequiredFields(fields) {
if (field.type === "radio" || field.type === "checkbox") continue; if (field.type === "radio" || field.type === "checkbox") continue;
if (!field.checkValidity()) { if (!field.checkValidity()) {
markFieldInvalid(field);
return { return {
isValid: false, isValid: false,
message: "Bitte fülle alle Pflichtfelder aus." message: "Bitte fülle alle Pflichtfelder aus."
@ -374,11 +563,12 @@ function handleNextClick() {
*/ */
function handleFormSubmit(event) { function handleFormSubmit(event) {
event.preventDefault(); event.preventDefault();
// 1. Feedback geben saveCurrentEvent();
alert("Dein Event wurde erfolgreich veröffentlicht!"); steps.forEach(step => step.classList.remove("step--active"));
flowFooter.hidden = true;
// 2. Weiterleiten (z. B. zur Event-Übersicht) submissionSuccess.hidden = false;
window.location.href = "event_overview.html"; setErrorMessage("");
window.scrollTo({ top: 0, behavior: "smooth" });
} }
@ -412,6 +602,126 @@ function updateCounterValue(input, change) {
input.value = Math.max(min, currentValue + change); input.value = Math.max(min, currentValue + change);
} }
/**
* Macht aus "-"+Enter im Menüfeld eine einfache Bullet-Liste.
*/
function registerMenuBulletHandler() {
const menuField = document.getElementById("menuDescription");
if (!menuField) return;
menuField.addEventListener("keydown", event => {
if (event.key !== "Enter") return;
const { selectionStart, selectionEnd, value } = menuField;
const lineStart = value.lastIndexOf("\n", selectionStart - 1) + 1;
const lineEnd = value.indexOf("\n", selectionStart);
const currentLineEnd = lineEnd === -1 ? value.length : lineEnd;
const currentLine = value.slice(lineStart, currentLineEnd);
const trimmedLine = currentLine.trim();
if (trimmedLine !== "-" && !currentLine.startsWith("• ")) return;
event.preventDefault();
const isEmptyBullet = currentLine.trim() === "•";
if (isEmptyBullet) {
const beforeLine = value.slice(0, lineStart);
const afterLine = value.slice(currentLineEnd);
const separator = beforeLine.endsWith("\n") || afterLine.startsWith("\n") ? "" : "\n";
const nextValue = `${beforeLine}${separator}${afterLine}`.replace(/\n{3,}/g, "\n\n");
menuField.value = nextValue;
const caretPosition = Math.min(lineStart, nextValue.length);
menuField.setSelectionRange(caretPosition, caretPosition);
menuField.dispatchEvent(new Event("input", { bubbles: true }));
return;
}
const bulletLine = currentLine.startsWith("• ") ? currentLine : currentLine.replace("-", "•");
const updatedLine = bulletLine.startsWith("• ") ? bulletLine : `${trimmedLine.slice(1).trimStart()}`;
const beforeLine = value.slice(0, lineStart);
const afterLine = value.slice(currentLineEnd);
const nextValue = `${beforeLine}${updatedLine}\n${afterLine}`;
const caretPosition = beforeLine.length + updatedLine.length + 3;
menuField.value = nextValue;
menuField.setSelectionRange(caretPosition, caretPosition);
menuField.dispatchEvent(new Event("input", { bubbles: true }));
});
}
/**
* Springt aus der Review zurück zum passenden Schritt
* und fokussiert das gewünschte Feld für direktes Weiterbearbeiten.
*/
function registerReviewEditHandlers() {
document.querySelectorAll(".review-item[data-edit-step]").forEach(item => {
const activateEdit = () => {
const stepIndex = Number(item.dataset.editStep);
const fieldName = item.dataset.editField;
showStep(stepIndex);
focusFieldByName(fieldName);
};
item.addEventListener("click", activateEdit);
item.addEventListener("keydown", event => {
if (event.key === "Enter" || event.key === " ") {
event.preventDefault();
activateEdit();
}
});
});
}
/**
* Entfernt Fehlermarkierungen, sobald der User ein Feld korrigiert.
*/
function registerValidationFeedbackHandlers() {
form.querySelectorAll("input, textarea, select").forEach(field => {
const clearInvalidState = () => {
field.classList.remove("field-invalid");
if (field.type === "radio") {
const group = Array.from(form.querySelectorAll(`input[name="${field.name}"]`));
const hasSelection = group.some(item => item.checked);
if (hasSelection) {
group.forEach(item => {
const card = item.closest(".option-card");
if (card) {
card.classList.remove("option-card--invalid");
}
});
}
}
};
field.addEventListener("input", clearInvalidState);
field.addEventListener("change", clearInvalidState);
});
}
/**
* Setzt den Fokus auf ein bestimmtes Feld oder die erste Option einer Radio-Gruppe.
*/
function focusFieldByName(fieldName) {
const field = form.elements[fieldName];
if (!field) return;
const focusTarget = field instanceof RadioNodeList ? field[0] : field;
if (focusTarget && typeof focusTarget.focus === "function") {
window.setTimeout(() => {
focusTarget.focus();
}, 150);
}
}
// ============================= // =============================
// STEP 9: Alles starten // STEP 9: Alles starten
@ -438,8 +748,12 @@ function initEventCreationFlow() {
// Counter aktivieren // Counter aktivieren
registerCounterHandlers(); registerCounterHandlers();
registerMenuBulletHandler();
registerValidationFeedbackHandlers();
registerReviewEditHandlers();
// Startzustand: Intro anzeigen // Startzustand: Intro anzeigen
submissionSuccess.hidden = true;
showStep(0); showStep(0);
} }

View File

@ -1,4 +1,5 @@
document.addEventListener('DOMContentLoaded', async () => { document.addEventListener('DOMContentLoaded', async () => {
const EVENTS_STORAGE_KEY = 'socialCookingEvents';
// ------------------------------------------------------------- // -------------------------------------------------------------
// DOM entry point and shared asset path. // DOM entry point and shared asset path.
// ------------------------------------------------------------- // -------------------------------------------------------------
@ -14,10 +15,21 @@ document.addEventListener('DOMContentLoaded', async () => {
return; return;
} }
function getStoredEvents() {
try {
const stored = localStorage.getItem(EVENTS_STORAGE_KEY);
return stored ? JSON.parse(stored) : [];
} catch (error) {
console.error('Lokale Events konnten nicht gelesen werden.', error);
return [];
}
}
// Fetch data source and resolve the matching event record. // Fetch data source and resolve the matching event record.
try { try {
const response = await fetch('data/events.json'); const response = await fetch('data/events.json');
const allEvents = await response.json(); const apiEvents = await response.json();
const allEvents = [...getStoredEvents(), ...apiEvents];
const event = allEvents.find(e => e.id === eventId); const event = allEvents.find(e => e.id === eventId);
if (event) { if (event) {

View File

@ -1,4 +1,5 @@
document.addEventListener('DOMContentLoaded', () => { document.addEventListener('DOMContentLoaded', () => {
const EVENTS_STORAGE_KEY = 'socialCookingEvents';
// ------------------------------------------------------------- // -------------------------------------------------------------
// DOM references used throughout the page lifecycle. // DOM references used throughout the page lifecycle.
// ------------------------------------------------------------- // -------------------------------------------------------------
@ -14,6 +15,16 @@ document.addEventListener('DOMContentLoaded', () => {
let allEvents = []; let allEvents = [];
let activeCategory = 'ALLE'; let activeCategory = 'ALLE';
function getStoredEvents() {
try {
const stored = localStorage.getItem(EVENTS_STORAGE_KEY);
return stored ? JSON.parse(stored) : [];
} catch (error) {
console.error('Lokale Events konnten nicht gelesen werden.', error);
return [];
}
}
// ------------------------------------------------------------- // -------------------------------------------------------------
// Initial data bootstrap: // Initial data bootstrap:
// 1) fetch JSON, // 1) fetch JSON,
@ -24,7 +35,9 @@ document.addEventListener('DOMContentLoaded', () => {
async function fetchEvents() { async function fetchEvents() {
try { try {
const response = await fetch('data/events.json'); const response = await fetch('data/events.json');
allEvents = await response.json(); const apiEvents = await response.json();
const localEvents = getStoredEvents();
allEvents = [...localEvents, ...apiEvents];
populateMetaFilters(); populateMetaFilters();
const savedCategory = sessionStorage.getItem('activeFilter') || 'ALLE'; const savedCategory = sessionStorage.getItem('activeFilter') || 'ALLE';
@ -62,6 +75,10 @@ document.addEventListener('DOMContentLoaded', () => {
// Convert localized event date (e.g. 19. MÄR. 2026) into ISO format for date input comparison. // Convert localized event date (e.g. 19. MÄR. 2026) into ISO format for date input comparison.
function parseEventDateToIso(dateString) { function parseEventDateToIso(dateString) {
if (/^\d{4}-\d{2}-\d{2}$/.test(dateString)) {
return dateString;
}
const months = { const months = {
JAN: '01', JAN: '01',
FEB: '02', FEB: '02',
@ -92,6 +109,11 @@ document.addEventListener('DOMContentLoaded', () => {
// Convert short month notation into full German month label for UI display. // Convert short month notation into full German month label for UI display.
function formatEventDate(dateString) { function formatEventDate(dateString) {
if (/^\d{4}-\d{2}-\d{2}$/.test(dateString)) {
const [year, month, day] = dateString.split('-');
return `${Number(day)}. ${['Januar', 'Februar', 'März', 'April', 'Mai', 'Juni', 'Juli', 'August', 'September', 'Oktober', 'November', 'Dezember'][Number(month) - 1]} ${year}`;
}
const labels = { const labels = {
JAN: 'Januar', JAN: 'Januar',
FEB: 'Februar', FEB: 'Februar',
@ -122,7 +144,13 @@ document.addEventListener('DOMContentLoaded', () => {
// Normalize time label from UHR to Uhr for consistent typography. // Normalize time label from UHR to Uhr for consistent typography.
function formatEventTime(timeString) { function formatEventTime(timeString) {
return timeString.replace('UHR', 'Uhr').trim(); if (!timeString) {
return '';
}
return timeString.includes('UHR')
? timeString.replace('UHR', 'Uhr').trim()
: `${timeString} Uhr`;
} }
// Safely verify whether a value exists in the given select element. // Safely verify whether a value exists in the given select element.

View File

@ -17,12 +17,12 @@
<header class="top-nav-wrap"> <header class="top-nav-wrap">
<div class="top-nav"> <div class="top-nav">
<a class="brand" href="index.html" aria-label="Zur Startseite"> <a class="brand" href="index.html" aria-label="Zur Startseite">
<img src="assets/invite-logo.svg" alt="Invite Logo"> <img src="assets/logo_invite.svg" alt="Invite Logo">
</a> </a>
<nav class="button-small-links" aria-label="Hauptnavigation"> <nav class="nav-tab-links" aria-label="Hauptnavigation">
<a class="button-small" href="event_overview.html">Event finden</a> <a class="nav-tab" href="event_overview.html">Event finden</a>
<a class="button-small" href="event_create.html">Event erstellen</a> <a class="nav-tab" href="event_create.html">Event erstellen</a>
<a class="button-login" href="login.html" aria-label="Profil">Login</a> <a class="button-small" href="login.html" aria-label="Profil">Login</a>
</nav> </nav>
</div> </div>
</header> </header>

View File

@ -9,19 +9,19 @@
<link rel="stylesheet" href="css/login_signup.css"> <link rel="stylesheet" href="css/login_signup.css">
<!-- Globales Stylesheet --> <!-- Globales Stylesheet -->
<link rel="stylesheet" href="css/stylesheet_global.css"> <link rel="stylesheet" href="css/stylesheet_global.css">
</head> </head>
<body> <body>
<!-- Top Navigation mit Seitenlinks --> <!-- Top Navigation mit Seitenlinks -->
<header class="top-nav-wrap"> <header class="top-nav-wrap">
<div class="top-nav"> <div class="top-nav">
<a class="brand" href="index.html" aria-label="Zur Startseite"> <a class="brand" href="index.html" aria-label="Zur Startseite">
<img src="assets/invite-logo.svg" alt="Invite Logo"> <img src="assets/logo_invite.svg" alt="Invite Logo">
</a> </a>
<nav class="button-small-links" aria-label="Hauptnavigation"> <nav class="nav-tab-links" aria-label="Hauptnavigation">
<a class="button-small" href="event_overview.html">Event finden</a> <a class="nav-tab" href="event_overview.html">Event finden</a>
<a class="button-small" href="event_create.html">Event erstellen</a> <a class="nav-tab" href="event_create.html">Event erstellen</a>
<a class="button-login" href="login.html" aria-label="Profil">Login</a> <a class="button-small" href="login.html" aria-label="Profil">Login</a>
</nav> </nav>
</div> </div>
</header> </header>