Erstellung CSS und JS File für event-create inkl. Anpassung HTML von Event-create

This commit is contained in:
Ysabelle Moser 2026-03-22 23:48:59 +01:00
parent 7e788dafd7
commit b0e7b68417
3 changed files with 974 additions and 5 deletions

450
event-create.css Normal file
View File

@ -0,0 +1,450 @@
:root {
--color-bg: #f7f7f2;
--color-surface: #ffffff;
--color-text: #1f1f1f;
--color-muted: #6b6b6b;
--color-border: #d8d8d2;
--color-border-strong: #202020;
--color-primary: #222222;
--color-primary-hover: #111111;
--color-focus: #2f6fed;
--color-error: #b42318;
--shadow-soft: 0 10px 30px rgba(0, 0, 0, 0.06);
--radius-sm: 0.875rem;
--radius-md: 1.25rem;
--radius-lg: 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;
--header-height: 4.5rem;
}
*,
*::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%, 760px);
margin: 0 auto;
display: grid;
gap: var(--space-6);
}
.step-layout--intro {
min-height: 60vh;
align-content: center;
grid-template-columns: 1fr;
}
.step-copy {
display: grid;
gap: var(--space-4);
}
.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: #303030;
font-size: clamp(1rem, 1.4vw, 1.2rem);
}
.intro-card {
max-width: 24rem;
padding: var(--space-6);
border: 1px solid var(--color-border);
border-radius: 1.75rem;
background: linear-gradient(135deg, #ffffff, #f0eee7);
box-shadow: var(--shadow-soft);
}
.intro-card-emoji {
font-size: 2rem;
margin-bottom: var(--space-3);
}
.step-fields {
display: grid;
gap: var(--space-5);
}
.form-field,
fieldset {
margin: 0;
padding: 0;
border: 0;
display: grid;
gap: 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: 3.5rem;
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: 6rem;
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 {
width: 3rem;
height: 3rem;
border: 1px solid var(--color-border);
border-radius: 50%;
background: var(--color-surface);
font-size: 1.5rem;
}
.review-card {
padding: var(--space-5);
border: 1px solid var(--color-border);
border-radius: 1.5rem;
background: var(--color-surface);
box-shadow: var(--shadow-soft);
}
.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 #ecece7;
}
.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: #303030;
}
.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: #ddddda;
}
.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;
min-height: 3rem;
padding: 0.9rem 1.35rem;
border-radius: var(--radius-lg);
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 (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));
}
}

View File

@ -1,12 +1,329 @@
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<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 gehts!
</button>
</div>
<aside class="intro-card" aria-hidden="true">
<div class="intro-card-emoji">🍽️</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</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>
</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"></p>
<button type="button" id="nextButton" class="button button--primary">Weiter</button>
</div>
</div>
</div>
</form>
</main>
<footer class="site-footer">
<p>&copy; Invité</p>
</footer>
<script src="event-create.js"></script>
</body>
</html>

202
event-create.js Normal file
View File

@ -0,0 +1,202 @@
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");
let currentStep = 0;
const lastStep = steps.length - 1;
const nextLabels = {
0: "Weiter",
1: "Weiter",
2: "Weiter",
3: "Weiter",
4: "Weiter",
5: "Event veröffentlichen"
};
// Demo-Platzhalter; später aus Nutzerprofil setzen
usernameElement.textContent = "Mia";
const flowFooter = document.getElementById("flowFooter");
function showStep(index) {
currentStep = index;
steps.forEach((step, stepIndex) => {
step.classList.toggle("step--active", stepIndex === index);
});
const isIntroStep = index === 0;
flowFooter.hidden = isIntroStep;
backButton.hidden = isIntroStep;
nextButton.textContent = nextLabels[index];
errorMessage.textContent = "";
if (index === lastStep) {
updateReview();
}
function updateProgress(index) {
if (index === 0) {
progressBar.style.width = "0%";
return;
}
const totalSteps = lastStep;
const progress = ((index - 1) / (totalSteps - 1)) * 100;
progressBar.style.width = `${progress}%`;
}
window.scrollTo({ top: 0, behavior: "smooth" });
updateProgress(index);
}
function stepFields(stepIndex) {
return Array.from(steps[stepIndex].querySelectorAll("input, textarea, select"));
}
function validateCurrentStep() {
if (currentStep === 0 || currentStep === lastStep) {
return true;
}
const fields = stepFields(currentStep);
for (const field of fields) {
if (field.type === "radio") {
const group = Array.from(form.querySelectorAll(`input[name="${field.name}"]`));
const isRequiredGroup = group.some((item) => item.required);
const hasSelection = group.some((item) => item.checked);
if (isRequiredGroup && !hasSelection) {
errorMessage.textContent = "Bitte wähle eine Option aus, bevor du weitergehst.";
group[0].focus();
return false;
}
continue;
}
if (field.type === "checkbox") {
continue;
}
if (!field.checkValidity()) {
errorMessage.textContent = "Bitte fülle alle Pflichtfelder dieses Schritts aus.";
field.reportValidity();
return false;
}
}
errorMessage.textContent = "";
return true;
}
function getFieldValue(name) {
const field = form.elements[name];
if (!field) return "";
if (field instanceof RadioNodeList) {
return field.value || "";
}
return field.value.trim() || "";
}
function getCheckboxValues(name) {
const checked = Array.from(form.querySelectorAll(`input[name="${name}"]:checked`));
return checked.length ? checked.map((item) => item.value).join(", ") : "Keine Angabe";
}
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);
}
function updateReview() {
const values = {
eventTitle: getFieldValue("eventTitle"),
eventType: getFieldValue("eventType"),
menuDescription: getFieldValue("menuDescription"),
eventDescription: getFieldValue("eventDescription"),
maxGuests: getFieldValue("maxGuests"),
dietType: getFieldValue("dietType"),
allergies: getCheckboxValues("allergies"),
eventDate: formatDate(getFieldValue("eventDate")),
eventTime: getFieldValue("eventTime"),
eventAddress: getFieldValue("eventAddress"),
eventCity: getFieldValue("eventCity")
};
Object.entries(values).forEach(([key, value]) => {
const target = document.querySelector(`[data-review="${key}"]`);
if (target) {
target.textContent = value;
}
});
}
document.querySelectorAll("[data-start-flow]").forEach((button) => {
button.addEventListener("click", () => showStep(1));
});
backButton.addEventListener("click", () => {
if (currentStep > 1) {
showStep(currentStep - 1);
} else {
showStep(0);
}
});
nextButton.addEventListener("click", () => {
if (!validateCurrentStep()) return;
if (currentStep < lastStep) {
showStep(currentStep + 1);
return;
}
form.requestSubmit();
});
form.addEventListener("submit", (event) => {
event.preventDefault();
alert("Dein Event wurde vorbereitet und würde jetzt veröffentlicht werden.");
});
document.querySelectorAll("[data-counter]").forEach((counter) => {
const input = counter.querySelector("input[type='number']");
const decreaseButton = counter.querySelector("[data-counter-action='decrease']");
const increaseButton = counter.querySelector("[data-counter-action='increase']");
decreaseButton.addEventListener("click", () => {
const min = Number(input.min || 1);
const currentValue = Number(input.value || min);
input.value = Math.max(min, currentValue - 1);
});
increaseButton.addEventListener("click", () => {
const currentValue = Number(input.value || 0);
input.value = currentValue + 1;
});
});
showStep(0);