diff --git a/cooking.jpg b/cooking.jpg new file mode 100644 index 0000000..8b807bb Binary files /dev/null and b/cooking.jpg differ diff --git a/css/event_overview_stylesheet.css b/css/event_overview_stylesheet.css new file mode 100644 index 0000000..91d6cce --- /dev/null +++ b/css/event_overview_stylesheet.css @@ -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%; } +} \ No newline at end of file diff --git a/css/stylesheet.css b/css/stylesheet.css new file mode 100644 index 0000000..e69de29 diff --git a/data/events.json b/data/events.json new file mode 100644 index 0000000..5ea7e98 --- /dev/null +++ b/data/events.json @@ -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" + } +] \ No newline at end of file diff --git a/event-create.css b/event-create.css new file mode 100644 index 0000000..e118a3e --- /dev/null +++ b/event-create.css @@ -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)); + } +} \ No newline at end of file diff --git a/event-create.html b/event-create.html new file mode 100644 index 0000000..da5d6b0 --- /dev/null +++ b/event-create.html @@ -0,0 +1,352 @@ + + + + + + Event erstellen | Invité + + + + + + +
+
+ Abbrechen +
+ +
+
+
+
+

Event erstellen

+

Hey {{username}}, was hast du vor?

+

+ 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. +

+ +
+ + +
+
+ +
+
+
+

Schritt 1

+

Was hast du vor?

+

+ 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? +

+
+ +
+
+ + +
+ +
+ Art des Essens / Eventtyp + +
+ + + + + + + +
+
+
+
+
+ +
+
+
+

Schritt 2

+

Was kommt auf den Tisch?

+

+ 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. +

+
+ +
+
+ + +
+ +
+ + +
+
+
+
+ +
+
+
+

Schritt 3

+

Wen lädst du ein?

+

+ Wie viele Gäste passen zu deinem Event? Und gibt es etwas, das du bei + Ernährung oder Unverträglichkeiten beachten möchtest? +

+
+ +
+
+ Maximale Personenanzahl + +
+ + + +
+
+ +
+ Ernährungsform + +
+ + + + + +
+
+ +
+ Allergene / Unverträglichkeiten +

Optional – nur auswählen, wenn es für dein Event relevant ist.

+ +
+ + + + + +
+ +
+ + +
+
+
+
+
+ +
+
+
+

Schritt 4

+

Wann findet dein Event statt?

+

+ Wähle Datum und Uhrzeit – und sag uns, wo dein Event stattfindet. + Keine Sorge: Die genaue Adresse sehen Gäste erst nach der Buchung. +

+
+ +
+
+
+ + +
+ +
+ + +
+
+ +
+ + +
+ +
+ + +
+
+
+
+ +
+
+
+

Schritt 5

+

Alles bereit für deine Gäste?

+

+ Schau dir dein Event nochmal in Ruhe an. Passt alles? + Dann kannst du es jetzt veröffentlichen und Gäste einladen. +

+
+ +
+
+
+
Eventtitel
+
+
+ +
+
Eventtyp
+
+
+ +
+
Menü
+
+
+ +
+
Event-Abend
+
+
+ +
+
Maximale Personenanzahl
+
+
+ +
+
Ernährungsform
+
+
+ +
+
Allergene / Unverträglichkeiten
+
Keine Angabe
+
+ +
+
Datum
+
+
+ +
+
Uhrzeit
+
+
+ +
+
Adresse
+
+
+ +
+
Ort
+
+
+
+
+
+
+ + +
+
+ + + + + + \ No newline at end of file diff --git a/event-create.js b/event-create.js new file mode 100644 index 0000000..5735a3f --- /dev/null +++ b/event-create.js @@ -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(); \ No newline at end of file diff --git a/event_detail.html b/event_detail.html new file mode 100644 index 0000000..22044dd --- /dev/null +++ b/event_detail.html @@ -0,0 +1,29 @@ + + + + + + Event-Detail + + + + + + +
+
+

Lädt Event-Details...

+
+
+ + + + + + \ No newline at end of file diff --git a/event_overview.html b/event_overview.html new file mode 100644 index 0000000..0fdc248 --- /dev/null +++ b/event_overview.html @@ -0,0 +1,36 @@ + + + + + + Event-Overview + + + + + +
+

Invité Events

+ +
+

WORAUF HAST DU LUST

+
+
BRUNCH
+
LUNCH
+
DINNER
+
COFFEE
+
ALLE
+
+
+ +
+
+ + + \ No newline at end of file diff --git a/js/event_detail.js b/js/event_detail.js new file mode 100644 index 0000000..e34ebc6 --- /dev/null +++ b/js/event_detail.js @@ -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 = "

Event wurde nicht gefunden.

Zurück zur Übersicht"; + } + } 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 = ` +
+ +

${event.title}

+
+
+
+

📍 ${event.location} | 📅 ${event.date} | 👤 Max. ${event.spots} Personen

+
+

Hier kommen die detaillierten Infos zu ${event.title} hin...

+
+
+ ${event.title} +
+
+ `; + } +}); \ No newline at end of file diff --git a/js/event_overview_script.js b/js/event_overview_script.js new file mode 100644 index 0000000..4aa3c77 --- /dev/null +++ b/js/event_overview_script.js @@ -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 = "

Events konnten nicht geladen werden.

"; + } + } + // 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 = ` +
+

Schade! Aktuell gibt es hier keine Events.

+

Möchtest du vielleicht selbst Gastgeber sein?

+ +
+ `; + 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 = ` +
+
+ 📍 ${event.location} +

${event.title}

+
+ ${event.cuisine} + ${event.category} +
+
+
+ ${event.date}
+ 🕒 ${event.time} +
+
+
🥗 ${event.diet}
+ +

${event.spots} PLÄTZE FREI

+
+
❤️
+ `; + 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(); +}); \ No newline at end of file diff --git a/js/javascript.js b/js/javascript.js new file mode 100644 index 0000000..e69de29 diff --git a/kontakt.html b/kontakt.html new file mode 100644 index 0000000..87907ad --- /dev/null +++ b/kontakt.html @@ -0,0 +1,573 @@ + + + + + + Kontaktseite - Invité + + + + + +
+ +
+ + Login +
+
+ + +
+
+
+ Social Cooking +
+ +
+

Erstelle deinen Account

+ +
+ Hinweis: Sichtbar auf der Plattform ist nur der Vorname, erst einer Anmeldung zum Event ist der Nachname für die Teilnehmenden sichtbar. +
+ +
+
+ + +
Bitte gib deinen Vornamen ein.
+
+ +
+ + +
Bitte gib deinen Nachnamen ein.
+
+ +
+ + +
Bitte gib eine gültige E-Mail Adresse ein.
+
+ +
+ + +
Dein Passwort muss mindestens 8 Zeichen lang sein.
+
+ + + + +
+
+
+
+ + + + + + + diff --git a/login.html b/login.html new file mode 100644 index 0000000..4cdbcdd --- /dev/null +++ b/login.html @@ -0,0 +1,358 @@ + + + + + + Login - Social Cooking + + + + + +
+ +
+ + Anmeldung +
+
+ + +
+
+
+ Social Cooking +
+ +
+

Willkommen zurück

+ +
+
+ + +
Bitte gib eine gültige E-Mail Adresse ein.
+
+ +
+ + +
Bitte gib dein Passwort ein.
+
+ + + + +
+
+
+
+ + + +