Compare commits
No commits in common. "main" and "personal-page" have entirely different histories.
main
...
personal-p
8
.vscode/settings.json
vendored
@ -1,8 +0,0 @@
|
|||||||
{
|
|
||||||
"chat.tools.terminal.autoApprove": {
|
|
||||||
"git remote": true,
|
|
||||||
"git push": true,
|
|
||||||
"ssh": true,
|
|
||||||
"git add": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
BIN
assets/Burger eating together.jpg
Normal file
|
After Width: | Height: | Size: 93 KiB |
|
Before Width: | Height: | Size: 148 KiB After Width: | Height: | Size: 148 KiB |
|
Before Width: | Height: | Size: 203 KiB After Width: | Height: | Size: 203 KiB |
|
Before Width: | Height: | Size: 120 KiB After Width: | Height: | Size: 120 KiB |
|
Before Width: | Height: | Size: 53 KiB |
|
Before Width: | Height: | Size: 79 KiB After Width: | Height: | Size: 79 KiB |
|
Before Width: | Height: | Size: 128 KiB After Width: | Height: | Size: 128 KiB |
BIN
assets/Plate icon.png
Normal file
|
After Width: | Height: | Size: 24 KiB |
|
Before Width: | Height: | Size: 79 KiB After Width: | Height: | Size: 79 KiB |
|
Before Width: | Height: | Size: 112 KiB After Width: | Height: | Size: 112 KiB |
|
Before Width: | Height: | Size: 400 KiB After Width: | Height: | Size: 400 KiB |
|
Before Width: | Height: | Size: 189 KiB After Width: | Height: | Size: 189 KiB |
|
Before Width: | Height: | Size: 142 KiB After Width: | Height: | Size: 142 KiB |
|
Before Width: | Height: | Size: 282 KiB After Width: | Height: | Size: 282 KiB |
BIN
assets/add-event icon.png
Normal file
|
After Width: | Height: | Size: 24 KiB |
|
Before Width: | Height: | Size: 134 KiB After Width: | Height: | Size: 134 KiB |
|
Before Width: | Height: | Size: 283 KiB |
|
Before Width: | Height: | Size: 493 KiB |
@ -1,12 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<svg id="Ebene_1" xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="0 0 24 24">
|
|
||||||
<!-- Generator: Adobe Illustrator 30.3.0, SVG Export Plug-In . SVG Version: 2.1.3 Build 182) -->
|
|
||||||
<defs>
|
|
||||||
<style>
|
|
||||||
.st0 {
|
|
||||||
fill: #6b6b05;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</defs>
|
|
||||||
<path class="st0" d="M9,1v2h6V1h2v2h4c.55,0,1,.45,1,1v16c0,.55-.45,1-1,1H3c-.55,0-1-.45-1-1V4c0-.55.45-1,1-1h4V1h2ZM20,11H4v8h16v-8ZM7,5h-3v4h16v-4h-3v2h-2v-2h-6v2h-2v-2Z"/>
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 497 B |
@ -1,12 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<svg id="Ebene_1" xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="0 0 36 36">
|
|
||||||
<!-- Generator: Adobe Illustrator 30.3.0, SVG Export Plug-In . SVG Version: 2.1.3 Build 182) -->
|
|
||||||
<defs>
|
|
||||||
<style>
|
|
||||||
.st0 {
|
|
||||||
fill: #6b6b05;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</defs>
|
|
||||||
<path class="st0" d="M6,33c0-6.63,5.37-12,12-12s12,5.37,12,12h-3c0-4.97-4.03-9-9-9s-9,4.03-9,9h-3ZM18,19.5c-4.97,0-9-4.03-9-9S13.03,1.5,18,1.5s9,4.03,9,9-4.03,9-9,9ZM18,16.5c3.32,0,6-2.68,6-6s-2.68-6-6-6-6,2.68-6,6,2.68,6,6,6Z"/>
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 553 B |
@ -1,12 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<svg id="Ebene_1" xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="0 0 24 24">
|
|
||||||
<!-- Generator: Adobe Illustrator 30.3.0, SVG Export Plug-In . SVG Version: 2.1.3 Build 182) -->
|
|
||||||
<defs>
|
|
||||||
<style>
|
|
||||||
.st0 {
|
|
||||||
fill: #6b6b05;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</defs>
|
|
||||||
<path class="st0" d="M12,20.9l4.95-4.95c2.73-2.73,2.73-7.17,0-9.9-2.73-2.73-7.17-2.73-9.9,0-2.73,2.73-2.73,7.17,0,9.9l4.95,4.95ZM12,23.73l-6.36-6.36c-3.51-3.51-3.51-9.21,0-12.73,3.51-3.51,9.21-3.51,12.73,0,3.51,3.51,3.51,9.21,0,12.73l-6.36,6.36ZM12,13c1.1,0,2-.9,2-2s-.9-2-2-2-2,.9-2,2,.9,2,2,2ZM12,15c-2.21,0-4-1.79-4-4s1.79-4,4-4,4,1.79,4,4-1.79,4-4,4Z"/>
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 681 B |
|
Before Width: | Height: | Size: 313 KiB |
|
Before Width: | Height: | Size: 178 KiB |
|
Before Width: | Height: | Size: 145 KiB |
BIN
assets/instagram.png
Normal file
|
After Width: | Height: | Size: 18 KiB |
6
assets/location-pin.svg
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<svg preserveAspectRatio="none" width="100%" height="100%" overflow="visible" style="display: block;" viewBox="0 0 23 27.6667" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<g id="Icon">
|
||||||
|
<path d="M22 11.5C22 19.6667 11.5 26.6667 11.5 26.6667C11.5 26.6667 1 19.6667 1 11.5C1 8.71523 2.10625 6.04451 4.07538 4.07538C6.04451 2.10625 8.71523 1 11.5 1C14.2848 1 16.9555 2.10625 18.9246 4.07538C20.8938 6.04451 22 8.71523 22 11.5Z" stroke="var(--stroke-0, #D44B24)" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M11.5 15C13.433 15 15 13.433 15 11.5C15 9.567 13.433 8 11.5 8C9.567 8 8 9.567 8 11.5C8 13.433 9.567 15 11.5 15Z" stroke="var(--stroke-0, #D44B24)" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 762 B |
|
Before Width: | Height: | Size: 566 KiB |
|
Before Width: | Height: | Size: 689 KiB |
|
Before Width: | Height: | Size: 666 KiB |
|
Before Width: | Height: | Size: 604 KiB |
BIN
assets/register icon.png
Normal file
|
After Width: | Height: | Size: 11 KiB |
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
*,
|
*,
|
||||||
@ -14,9 +33,87 @@
|
|||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
html {
|
||||||
|
font-size: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
font-family: var(--font-main);
|
||||||
|
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 {
|
.event-flow-header {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: flex-start;
|
justify-content: flex-end;
|
||||||
|
margin-bottom: var(--space-4);
|
||||||
}
|
}
|
||||||
|
|
||||||
.event-form {
|
.event-form {
|
||||||
@ -27,45 +124,11 @@
|
|||||||
|
|
||||||
.step {
|
.step {
|
||||||
display: none;
|
display: none;
|
||||||
|
padding: var(--space-4) 0 var(--space-7);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
.submission-success {
|
.submission-success {
|
||||||
padding: var(--space-24) 0 var(--space-48);
|
padding: var(--space-4) 0 var(--space-7);
|
||||||
}*/
|
|
||||||
|
|
||||||
.submission-success-title-row {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: var(--space-32);
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.submission-success-title-row h2 {
|
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.submission-success-icon {
|
|
||||||
width: 2.5rem;
|
|
||||||
height: 2.5rem;
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
color: var(--brown);
|
|
||||||
font-size: 3rem;
|
|
||||||
line-height: 1;
|
|
||||||
transform: translateY(0.4rem);
|
|
||||||
}
|
|
||||||
|
|
||||||
.submission-success-image {
|
|
||||||
object-position: 42% center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.submission-success .intro-card--image {
|
|
||||||
width: min(100%, 40rem);
|
|
||||||
aspect-ratio: 4 / 5;
|
|
||||||
align-self: flex-start;
|
|
||||||
justify-self: center;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.step--active {
|
.step--active {
|
||||||
@ -73,20 +136,17 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.step-layout {
|
.step-layout {
|
||||||
gap: 80px;
|
width: min(100%, var(--content-width));
|
||||||
}
|
margin: 0 auto;
|
||||||
|
display: grid;
|
||||||
.startseite {
|
gap: var(--space-6);
|
||||||
display: grid;
|
|
||||||
grid-template-columns: 1fr 1fr;
|
|
||||||
gap: 80px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.step-layout--intro {
|
.step-layout--intro {
|
||||||
min-height: 60vh;
|
min-height: 60vh;
|
||||||
align-content: center;
|
align-content: center;
|
||||||
grid-template-columns: 1fr;
|
grid-template-columns: 1fr;
|
||||||
gap: var(--space-48);
|
gap: var(--space-7);
|
||||||
}
|
}
|
||||||
|
|
||||||
.step-copy,
|
.step-copy,
|
||||||
@ -94,15 +154,15 @@
|
|||||||
.form-field,
|
.form-field,
|
||||||
fieldset {
|
fieldset {
|
||||||
display: grid;
|
display: grid;
|
||||||
|
gap: var(--space-4);
|
||||||
}
|
}
|
||||||
|
|
||||||
.step-copy {
|
.step-copy {
|
||||||
gap: var(--space-24);
|
gap: var(--space-4);
|
||||||
align-content: start;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.step-fields {
|
.step-fields {
|
||||||
gap: var(--space-32);
|
gap: var(--space-5);
|
||||||
}
|
}
|
||||||
|
|
||||||
.form-field,
|
.form-field,
|
||||||
@ -110,39 +170,73 @@ fieldset {
|
|||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
border: 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-family: "Bagel Fat One", cursive;
|
||||||
|
font-size: clamp(2rem, 4vw, 4rem);
|
||||||
|
line-height: 1.03;
|
||||||
|
letter-spacing: -0.03em;
|
||||||
|
}
|
||||||
|
|
||||||
.step-text {
|
.step-text {
|
||||||
/* definiert Breite des Beschriebtexts der einzelnen Schritte*/
|
margin: 0;
|
||||||
max-width: 100%;
|
max-width: 42rem;
|
||||||
|
color: var(--color-text-secondary);
|
||||||
|
font-size: clamp(1rem, 1.4vw, 1.2rem);
|
||||||
}
|
}
|
||||||
|
|
||||||
.intro-card,
|
.intro-card,
|
||||||
.review-card {
|
.review-card {
|
||||||
border: 1px solid var(--color-border);
|
border: 1px solid var(--color-border);
|
||||||
background: var(--color-surface);
|
background: var(--color-surface);
|
||||||
box-shadow: var(--shadow-interaction);
|
box-shadow: var(--shadow-soft);
|
||||||
}
|
}
|
||||||
|
|
||||||
.intro-card {
|
.intro-card {
|
||||||
padding: var(--space-40);
|
max-width: 24rem;
|
||||||
|
padding: var(--space-6);
|
||||||
border-radius: 1.75rem;
|
border-radius: 1.75rem;
|
||||||
background: linear-gradient(135deg, var(--color-surface), var(--color-surface-soft));
|
background: linear-gradient(135deg, var(--color-surface), var(--color-surface-soft));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.intro-card--image {
|
||||||
|
width: 100%;
|
||||||
|
padding: 0;
|
||||||
|
border: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
background: transparent;
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
.intro-image {
|
.intro-image {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
aspect-ratio: 16 / 10;
|
||||||
display: block;
|
display: block;
|
||||||
object-fit: cover;
|
object-fit: cover;
|
||||||
border-radius: var(--radius-lg);
|
border-radius: 1.875rem;
|
||||||
|
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.12);
|
||||||
|
}
|
||||||
|
|
||||||
|
label,
|
||||||
|
legend {
|
||||||
|
font-weight: 700;
|
||||||
}
|
}
|
||||||
|
|
||||||
.field-hint {
|
.field-hint {
|
||||||
color: var(--olive);
|
margin: -0.25rem 0 0;
|
||||||
font-size: 1rem;
|
color: var(--color-muted);
|
||||||
margin-bottom: var(--space-8);
|
font-size: 0.95rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
input[type="text"],
|
input[type="text"],
|
||||||
@ -150,27 +244,15 @@ input[type="date"],
|
|||||||
input[type="time"],
|
input[type="time"],
|
||||||
input[type="number"],
|
input[type="number"],
|
||||||
textarea {
|
textarea {
|
||||||
font-family: var(--font-main);
|
width: 100%;
|
||||||
font-weight: 400;
|
min-height: var(--input-min-height);
|
||||||
font-size: 1.125rem;
|
padding: 1rem 1.1rem;
|
||||||
padding: var(--space-16) var(--space-20);
|
border: 1px solid var(--input-border-soft);
|
||||||
border: 1.5px solid var(--olive-light);
|
border-radius: 1.125rem;
|
||||||
border-radius: var(--radius-md);
|
|
||||||
background: var(--butter-light);
|
background: var(--butter-light);
|
||||||
transition: border-color 0.2s ease;
|
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;
|
||||||
|
|
||||||
/* Blendet die Standard-Buttons für number inputs aus */
|
|
||||||
input[type="number"]::-webkit-outer-spin-button,
|
|
||||||
input[type="number"]::-webkit-inner-spin-button {
|
|
||||||
-webkit-appearance: none;
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Firefox */
|
|
||||||
input[type="number"] {
|
|
||||||
-moz-appearance: textfield;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
textarea {
|
textarea {
|
||||||
@ -183,7 +265,7 @@ input[type="date"]:hover,
|
|||||||
input[type="time"]:hover,
|
input[type="time"]:hover,
|
||||||
input[type="number"]:hover,
|
input[type="number"]:hover,
|
||||||
textarea:hover {
|
textarea:hover {
|
||||||
border: 2px solid var(--olive);
|
border-color: rgba(102, 52, 13, 0.28);
|
||||||
}
|
}
|
||||||
|
|
||||||
input[type="text"]:focus,
|
input[type="text"]:focus,
|
||||||
@ -191,78 +273,47 @@ input[type="date"]:focus,
|
|||||||
input[type="time"]:focus,
|
input[type="time"]:focus,
|
||||||
input[type="number"]:focus,
|
input[type="number"]:focus,
|
||||||
textarea:focus {
|
textarea:focus {
|
||||||
border: 2px solid var(--olive);
|
border-color: var(--input-border-focus);
|
||||||
|
box-shadow: var(--input-shadow-focus);
|
||||||
|
background: var(--butter-light);
|
||||||
|
outline: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.field-invalid {
|
.field-invalid {
|
||||||
border-color: var(--error) !important;
|
border-color: var(--tomato) !important;
|
||||||
box-shadow: var(--shadow-error);
|
box-shadow: 0 0 0 2px rgba(212, 75, 36, 0.14);
|
||||||
}
|
}
|
||||||
|
|
||||||
.field-row {
|
.field-row {
|
||||||
display: grid;
|
display: grid;
|
||||||
gap: var(--space-24);
|
gap: var(--space-4);
|
||||||
}
|
}
|
||||||
|
|
||||||
.option-grid {
|
.option-grid {
|
||||||
display: grid;
|
display: grid;
|
||||||
gap: var(--space-16);
|
gap: var(--space-3);
|
||||||
}
|
}
|
||||||
|
|
||||||
.option-card {
|
.option-card {
|
||||||
position: relative;
|
position: relative;
|
||||||
display: grid;
|
display: grid;
|
||||||
padding: var(--space-20);
|
gap: 0.15rem;
|
||||||
border: 1.5px solid var(--olive-light);
|
min-height: var(--card-min-height);
|
||||||
border-radius: var(--radius-md);
|
padding: 1rem 1rem 1rem 1.05rem;
|
||||||
|
border: 1px solid var(--color-border);
|
||||||
|
border-radius: 1rem;
|
||||||
background: var(--butter-light);
|
background: var(--butter-light);
|
||||||
transition: box-shadow 0.2s ease, transform 0.2s ease, background-color 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--with-icon {
|
.option-card small {
|
||||||
justify-items: center;
|
color: var(--color-muted);
|
||||||
align-content: center;
|
|
||||||
gap: var(--space-12);
|
|
||||||
color: var(--black);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.option-card__icon {
|
|
||||||
color: var(--black);
|
|
||||||
font-size: 1.25rem;
|
|
||||||
pointer-events: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.option-card--with-icon span {
|
|
||||||
pointer-events: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
.option-card:hover {
|
.option-card:hover {
|
||||||
background: var(--olive-light);
|
background: var(--olive-light);
|
||||||
box-shadow: var(--shadow-interaction);
|
transform: translateY(-1px);
|
||||||
transform: translateY(-3px);
|
box-shadow: var(--shadow-soft);
|
||||||
}
|
|
||||||
|
|
||||||
.option-grid--tomato-choices .option-card:hover,
|
|
||||||
.option-grid--tomato-choices .option-card:has(input:focus-visible),
|
|
||||||
.option-grid--tomato-choices .option-card:has(input:checked) {
|
|
||||||
background: var(--olive-light);
|
|
||||||
}
|
|
||||||
|
|
||||||
.option-grid--icon-choices .option-card--with-icon:has(input:disabled) {
|
|
||||||
opacity: 0.6;
|
|
||||||
}
|
|
||||||
|
|
||||||
.option-grid--icon-choices .option-card--with-icon:has(input:disabled):hover {
|
|
||||||
border-color: var(--olive-light);
|
|
||||||
background: var(--butter-light);
|
|
||||||
color: var(--black);
|
|
||||||
box-shadow: none;
|
|
||||||
transform: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.option-grid--icon-choices .option-card--with-icon:has(input:disabled):hover .option-card__icon {
|
|
||||||
color: var(--black);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.option-card input {
|
.option-card input {
|
||||||
@ -273,47 +324,62 @@ textarea:focus {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.option-card:has(input:checked) {
|
.option-card:has(input:checked) {
|
||||||
border: 1.5px solid var(--olive-light);
|
border: 1px solid var(--color-primary);
|
||||||
background: var(--olive-light);
|
background: var(--color-primary);
|
||||||
|
color: var(--white);
|
||||||
|
}
|
||||||
|
|
||||||
|
.option-card:has(input:checked) small {
|
||||||
|
color: rgba(247, 246, 230, 0.88);
|
||||||
}
|
}
|
||||||
|
|
||||||
.option-card--invalid {
|
.option-card--invalid {
|
||||||
border-color: var(--error) !important;
|
border-color: var(--tomato) !important;
|
||||||
box-shadow: var(--shadow-error);
|
box-shadow: 0 0 0 2px rgba(212, 75, 36, 0.14);
|
||||||
}
|
|
||||||
|
|
||||||
.guest-count-icon {
|
|
||||||
display: block;
|
|
||||||
text-align: center;
|
|
||||||
width: 100%;
|
|
||||||
color: var(--black);
|
|
||||||
font-size: 1.5rem;
|
|
||||||
line-height: 1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.counter {
|
.counter {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: var(--space-16);
|
gap: var(--space-3);
|
||||||
}
|
|
||||||
|
|
||||||
.counter-value-group {
|
|
||||||
display: grid;
|
|
||||||
justify-items: center;
|
|
||||||
row-gap: var(--space-8);
|
|
||||||
width: 6rem;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.counter input {
|
.counter input {
|
||||||
width: 100%;
|
width: 6rem;
|
||||||
height: 3rem;
|
|
||||||
padding-block: 0.75rem;
|
|
||||||
text-align: center;
|
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-primary);
|
||||||
|
border-radius: 50%;
|
||||||
|
background: var(--color-primary);
|
||||||
|
color: var(--white);
|
||||||
|
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 {
|
||||||
display: grid;
|
display: grid;
|
||||||
gap: var(--space-24);
|
gap: var(--space-4);
|
||||||
padding: 0;
|
padding: 0;
|
||||||
border: 0;
|
border: 0;
|
||||||
border-radius: 0;
|
border-radius: 0;
|
||||||
@ -323,8 +389,8 @@ textarea:focus {
|
|||||||
|
|
||||||
.review-card--success {
|
.review-card--success {
|
||||||
display: grid;
|
display: grid;
|
||||||
gap: var(--space-32);
|
gap: var(--space-5);
|
||||||
padding: var(--space-16) 0 0;
|
padding: var(--space-3) 0 0;
|
||||||
border: 0;
|
border: 0;
|
||||||
border-radius: 0;
|
border-radius: 0;
|
||||||
background: transparent;
|
background: transparent;
|
||||||
@ -333,214 +399,187 @@ textarea:focus {
|
|||||||
|
|
||||||
.review-list {
|
.review-list {
|
||||||
display: grid;
|
display: grid;
|
||||||
gap: var(--space-12);
|
gap: var(--space-4);
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.review-item {
|
.review-item {
|
||||||
display: grid;
|
display: grid;
|
||||||
|
gap: var(--space-2);
|
||||||
padding: 1rem 1.1rem;
|
padding: 1rem 1.1rem;
|
||||||
border: 1.5px solid var(--olive-light);
|
border: 1px solid var(--input-border-soft);
|
||||||
border-radius: 1.125rem;
|
border-radius: 1.125rem;
|
||||||
background: var(--butter-light);
|
background: var(--butter-light);
|
||||||
|
box-shadow: 0 1px 2px rgba(102, 52, 13, 0.04);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: border-color 0.3s ease, box-shadow 0.3s ease, transform 0.3s ease, background-color 0.3s ease;
|
transition: border-color 0.2s ease, box-shadow 0.2s ease, transform 0.2s ease, background-color 0.2s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
.review-item:hover,
|
.review-item:last-child {
|
||||||
.review-item:focus-visible {
|
border-bottom: 1px solid var(--input-border-soft);
|
||||||
border: 2px solid var(--olive);
|
}
|
||||||
box-shadow: var(--shadow-interaction);
|
|
||||||
transform: translateY(-3px);
|
.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 {
|
||||||
font-weight: 600;
|
font-weight: 700;
|
||||||
}
|
}
|
||||||
|
|
||||||
.review-item dd {
|
.review-item dd {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
white-space: pre-wrap;
|
||||||
|
color: var(--color-text-secondary);
|
||||||
.review-gallery {
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
gap: var(--space-8);
|
|
||||||
}
|
|
||||||
|
|
||||||
.review-gallery__thumb {
|
|
||||||
width: 4.5rem;
|
|
||||||
height: 4.5rem;
|
|
||||||
border-radius: var(--radius-sm);
|
|
||||||
object-fit: cover;
|
|
||||||
box-shadow: var(--shadow-interaction);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.submission-success-actions {
|
.submission-success-actions {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: flex-start;
|
justify-content: center;
|
||||||
width: 100%;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.flow-footer {
|
.flow-footer {
|
||||||
padding-top: var(--space-80);
|
position: sticky;
|
||||||
|
bottom: 0;
|
||||||
|
z-index: 5;
|
||||||
|
margin-top: auto;
|
||||||
|
background: var(--color-bg);
|
||||||
backdrop-filter: none;
|
backdrop-filter: none;
|
||||||
|
padding-top: var(--space-4);
|
||||||
padding-bottom: env(safe-area-inset-bottom);
|
padding-bottom: env(safe-area-inset-bottom);
|
||||||
}
|
}
|
||||||
|
|
||||||
.flow-actions {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.progress-wrap {
|
.progress-wrap {
|
||||||
flex: 1;
|
|
||||||
position: relative;
|
position: relative;
|
||||||
display: flex;
|
width: min(100%, var(--content-width));
|
||||||
align-items: center;
|
margin: 0 auto;
|
||||||
align-self: center;
|
padding-top: 4.35rem;
|
||||||
min-height: 2.75rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.progress-label {
|
|
||||||
position: absolute;
|
|
||||||
top: -1.1rem;
|
|
||||||
left: 50%;
|
|
||||||
transform: translateX(-50%);
|
|
||||||
font-size: 1rem;
|
|
||||||
font-weight: 400;
|
|
||||||
color: var(--black);
|
|
||||||
white-space: nowrap;
|
|
||||||
text-align: center;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.progress {
|
.progress {
|
||||||
flex: 1;
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 0.45rem;
|
height: 0.375rem;
|
||||||
background: var(--olive-light);
|
background: var(--color-progress-bg);
|
||||||
border-radius: var(--radius-sm);
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.progress-bar {
|
.progress-bar {
|
||||||
display: block;
|
display: block;
|
||||||
width: 0;
|
width: 0;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
background: var(--olive);
|
background: var(--tomato);
|
||||||
border-radius: 999px;
|
|
||||||
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-24);
|
gap: var(--space-4);
|
||||||
width: 100%;
|
width: min(100%, var(--content-width));
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: var(--space-4) 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.flow-actions-right {
|
.flow-actions-right {
|
||||||
position: relative;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: flex-end;
|
gap: var(--space-4);
|
||||||
}
|
}
|
||||||
|
|
||||||
.error-message--callout {
|
.error-message {
|
||||||
position: absolute;
|
min-height: 1.5rem;
|
||||||
right: 0;
|
margin: 0;
|
||||||
bottom: calc(100% + 1.25rem);
|
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 {
|
.button--ghost:hover {
|
||||||
background: rgba(0, 0, 0, 0.03);
|
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 {
|
.button--intro {
|
||||||
justify-self: start;
|
justify-self: start;
|
||||||
margin-top: var(--space-12);
|
margin-top: var(--space-2);
|
||||||
}
|
}
|
||||||
|
|
||||||
.gallery-preview {
|
|
||||||
display: contents;
|
|
||||||
}
|
|
||||||
|
|
||||||
.gallery-add-button {
|
|
||||||
display: grid;
|
|
||||||
place-items: center;
|
|
||||||
align-content: center;
|
|
||||||
gap: var(--space-8);
|
|
||||||
padding: var(--space-20);
|
|
||||||
border: 1.5px solid var(--olive-light);
|
|
||||||
border-radius: var(--radius-md);
|
|
||||||
background: var(--butter-light);
|
|
||||||
font-family: var(--font-main);
|
|
||||||
cursor: pointer;
|
|
||||||
transition: background-color 0.2s ease, box-shadow 0.2s ease, transform 0.2s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.gallery-add-button__icon {
|
|
||||||
color: var(--black);
|
|
||||||
font-size: 1.5rem;
|
|
||||||
line-height: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.gallery-add-button__text {
|
|
||||||
color: var(--black);
|
|
||||||
font-size: 0.95rem;
|
|
||||||
line-height: 1.15;
|
|
||||||
text-align: center;
|
|
||||||
overflow-wrap: anywhere;
|
|
||||||
}
|
|
||||||
|
|
||||||
.gallery-add-button:hover {
|
|
||||||
background: var(--olive-light);
|
|
||||||
box-shadow: var(--shadow-interaction);
|
|
||||||
transform: translateY(-3px);
|
|
||||||
}
|
|
||||||
|
|
||||||
.gallery-thumb {
|
|
||||||
position: relative;
|
|
||||||
width: 7rem;
|
|
||||||
height: 7rem;
|
|
||||||
border-radius: var(--radius-sm, 0.5rem);
|
|
||||||
overflow: hidden;
|
|
||||||
flex-shrink: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.gallery-thumb img {
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
object-fit: cover;
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
.gallery-thumb-remove {
|
|
||||||
position: absolute;
|
|
||||||
top: 0.2rem;
|
|
||||||
right: 0.2rem;
|
|
||||||
width: 1.4rem;
|
|
||||||
height: 1.4rem;
|
|
||||||
border: 0;
|
|
||||||
border-radius: 50%;
|
|
||||||
background: rgba(0, 0, 0, 0.6);
|
|
||||||
color: #fff;
|
|
||||||
font-size: 0.85rem;
|
|
||||||
line-height: 1;
|
|
||||||
cursor: pointer;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
.site-footer {
|
.site-footer {
|
||||||
width: min(100% - 2rem, var(--max-width));
|
width: min(100% - 2rem, var(--max-width));
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
padding: var(--space-32) 0 var(--space-40);
|
padding: var(--space-5) 0 var(--space-6);
|
||||||
color: var(--color-muted);
|
color: var(--color-muted);
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
@ -556,11 +595,11 @@ textarea:focus-visible {
|
|||||||
@media (max-width: 767px) {
|
@media (max-width: 767px) {
|
||||||
.site-nav {
|
.site-nav {
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
padding: var(--space-16) 0;
|
padding: var(--space-3) 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.site-nav-links {
|
.site-nav-links {
|
||||||
gap: var(--space-16);
|
gap: var(--space-3);
|
||||||
}
|
}
|
||||||
|
|
||||||
.flow-actions,
|
.flow-actions,
|
||||||
@ -569,24 +608,12 @@ textarea:focus-visible {
|
|||||||
align-items: stretch;
|
align-items: stretch;
|
||||||
}
|
}
|
||||||
|
|
||||||
.error-message--callout {
|
.button--text {
|
||||||
position: static;
|
justify-content: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button--primary {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
max-width: 100%;
|
|
||||||
margin-bottom: var(--space-12);
|
|
||||||
}
|
|
||||||
|
|
||||||
.progress-wrap {
|
|
||||||
min-height: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.progress-label {
|
|
||||||
position: static;
|
|
||||||
transform: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.error-message--callout::after {
|
|
||||||
display: none;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.event-flow-header {
|
.event-flow-header {
|
||||||
@ -596,8 +623,9 @@ textarea:focus-visible {
|
|||||||
|
|
||||||
@media (min-width: 768px) {
|
@media (min-width: 768px) {
|
||||||
.step-layout--intro {
|
.step-layout--intro {
|
||||||
|
width: min(100%, 56rem);
|
||||||
grid-template-columns: 1fr 1fr;
|
grid-template-columns: 1fr 1fr;
|
||||||
align-items: stretch;
|
align-items: center;
|
||||||
gap: var(--space-8);
|
gap: var(--space-8);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
553
css/index.css
@ -1,553 +0,0 @@
|
|||||||
/* ===========================================
|
|
||||||
INDEX.CSS — Styles specific to index.html
|
|
||||||
Global styles (reset, variables, body, nav,
|
|
||||||
brand, typography) are in stylesheet_global.css
|
|
||||||
=========================================== */
|
|
||||||
|
|
||||||
|
|
||||||
/* --- Navigation overrides (index-specific) --- */
|
|
||||||
.nav-link {
|
|
||||||
border: 2px solid var(--olive-light);
|
|
||||||
transition: background-color 0.2s ease, color 0.2s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.nav-link:hover,
|
|
||||||
.nav-link.active,
|
|
||||||
.nav-link:focus-visible {
|
|
||||||
background: var(--olive);
|
|
||||||
color: var(--white);
|
|
||||||
border-color: var(--olive);
|
|
||||||
}
|
|
||||||
|
|
||||||
.nav-link--login {
|
|
||||||
background: var(--olive);
|
|
||||||
color: var(--white);
|
|
||||||
border-color: var(--olive);
|
|
||||||
}
|
|
||||||
|
|
||||||
.nav-link--login:hover,
|
|
||||||
.nav-link--login:focus-visible {
|
|
||||||
background: var(--white);
|
|
||||||
color: var(--olive);
|
|
||||||
border-color: var(--olive);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* --- Page layout --- */
|
|
||||||
|
|
||||||
.container {
|
|
||||||
width: min(100% - 4rem, 1200px);
|
|
||||||
margin: 0 auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* --- Hero section --- */
|
|
||||||
|
|
||||||
.hero {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: 1fr 1fr;
|
|
||||||
gap: 80px;
|
|
||||||
margin-bottom: 100px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.hero__right {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.image-card {
|
|
||||||
width: 100%;
|
|
||||||
max-width: 436px;
|
|
||||||
overflow: hidden;
|
|
||||||
border-radius: var(--radius-lg);
|
|
||||||
}
|
|
||||||
|
|
||||||
.hero-image {
|
|
||||||
width: 100%;
|
|
||||||
max-width: 436px;
|
|
||||||
max-height: 510px;
|
|
||||||
height: auto;
|
|
||||||
border-radius: var(--radius-lg);
|
|
||||||
object-fit: cover;
|
|
||||||
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.12);
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 900px) {
|
|
||||||
.hero {
|
|
||||||
grid-template-columns: 1fr;
|
|
||||||
padding: 30px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.hero__right {
|
|
||||||
order: -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.hero-image {
|
|
||||||
min-height: 320px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* --- "So funktioniert's" steps --- */
|
|
||||||
|
|
||||||
.how-it-works {
|
|
||||||
margin-bottom: 100px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.how-it-works__header {
|
|
||||||
text-align: center;
|
|
||||||
margin-bottom: 32px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.how-it-works__header h2 {
|
|
||||||
font-size: 2rem;
|
|
||||||
margin: 0;
|
|
||||||
color: var(--black);
|
|
||||||
}
|
|
||||||
|
|
||||||
.how-it-works__steps {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(3, minmax(180px, 1fr));
|
|
||||||
gap: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.how-step {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
padding: var(--space-40);
|
|
||||||
background: var(--butter-light);
|
|
||||||
border-radius: var(--radius-lg);
|
|
||||||
}
|
|
||||||
|
|
||||||
.how-step-number-numbered {
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
.how-step_corner-number {
|
|
||||||
position: absolute;
|
|
||||||
color: var(--butter-light);
|
|
||||||
background: var(--tomato);
|
|
||||||
border-radius: var(--radius-lg);
|
|
||||||
top: var(--space-20);
|
|
||||||
left: var(--space-20);
|
|
||||||
width: 44px;
|
|
||||||
height: 44px;
|
|
||||||
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
|
|
||||||
font-family: 'Bagel Fat One';
|
|
||||||
font-size: 1.5rem;
|
|
||||||
font-weight: 400;
|
|
||||||
color: var(--butter-light);
|
|
||||||
}
|
|
||||||
|
|
||||||
.how-step_corner-number--brown {
|
|
||||||
color: var(--brown);
|
|
||||||
}
|
|
||||||
|
|
||||||
.how-step_icon {
|
|
||||||
font-size: 3.5rem;
|
|
||||||
color: var(--brown);
|
|
||||||
margin: var(--space-24) 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.how-step__png {
|
|
||||||
width: 192px;
|
|
||||||
height: 192px;
|
|
||||||
object-fit: contain;
|
|
||||||
}
|
|
||||||
|
|
||||||
.how-step__png--brown {
|
|
||||||
filter: brightness(0) saturate(100%) invert(18%) sepia(56%) saturate(2800%) hue-rotate(16deg) brightness(92%) contrast(95%);
|
|
||||||
}
|
|
||||||
|
|
||||||
.how-step_text {
|
|
||||||
margin-bottom: var(--space-24);
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.text-left{
|
|
||||||
text-align: left;
|
|
||||||
}
|
|
||||||
.how-step__footer-pill {
|
|
||||||
margin-bottom: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.how-step__footer-badges {
|
|
||||||
display: flex;
|
|
||||||
gap: var(--space-16);
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.how-step__footer-banner {
|
|
||||||
width: 100%;
|
|
||||||
padding: 10px 16px;
|
|
||||||
background: var(--butter);
|
|
||||||
border-radius: var(--radius-sm);
|
|
||||||
text-align: center;
|
|
||||||
font-family: 'Bagel Fat One', sans-serif;
|
|
||||||
font-size: 0.85rem;
|
|
||||||
letter-spacing: 0.12rem;
|
|
||||||
color: var(--brown);
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 900px) {
|
|
||||||
.how-it-works__steps {
|
|
||||||
grid-template-columns: 1fr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* --- Carousel gallery --- */
|
|
||||||
|
|
||||||
.gallery {
|
|
||||||
margin-bottom: 100px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.gallery__carousel {
|
|
||||||
position: relative;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.gallery__track {
|
|
||||||
display: flex;
|
|
||||||
gap: 20px;
|
|
||||||
margin-bottom: 30px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.gallery__item {
|
|
||||||
flex: 0 0 calc((100% - 40px) / 3);
|
|
||||||
min-width: calc((100% - 40px) / 3);
|
|
||||||
border-radius: 24px;
|
|
||||||
overflow: hidden;
|
|
||||||
background: var(--white);
|
|
||||||
box-shadow: 0 12px 24px rgba(0, 0, 0, 0.06);
|
|
||||||
aspect-ratio: 2 / 3;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.gallery__item img {
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
display: block;
|
|
||||||
object-fit: cover;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* =========================================
|
|
||||||
NEW INSTAGRAM HOVER STYLES START HERE
|
|
||||||
========================================= */
|
|
||||||
|
|
||||||
.ig-post-wrapper {
|
|
||||||
position: relative;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%; /* Ensures it fills the existing gallery__item */
|
|
||||||
aspect-ratio: 1 / 1;
|
|
||||||
overflow: hidden;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ig-post-wrapper img {
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
object-fit: cover;
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ig-overlay {
|
|
||||||
position: absolute;
|
|
||||||
inset: 0;
|
|
||||||
background-color: rgba(0, 0, 0, 0.4);
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
gap: 24px;
|
|
||||||
color: #ffffff;
|
|
||||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
|
|
||||||
font-weight: 600;
|
|
||||||
font-size: 1.1rem;
|
|
||||||
opacity: 0;
|
|
||||||
transition: opacity 0.2s ease-in-out;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ig-post-wrapper:hover .ig-overlay {
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ig-overlay span {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.gallery__arrow {
|
|
||||||
position: absolute;
|
|
||||||
display: grid;
|
|
||||||
top: 45%;
|
|
||||||
width: 44px;
|
|
||||||
height: 44px;
|
|
||||||
border: 1.5px solid var(--butter-light);
|
|
||||||
background: var(--butter-light);
|
|
||||||
border-radius: var(--radius-lg);
|
|
||||||
font-family: var(--font-main);
|
|
||||||
display: none;
|
|
||||||
font-weight: 400;
|
|
||||||
font-size: 1.25rem;
|
|
||||||
place-items: center;
|
|
||||||
z-index: 2;
|
|
||||||
color: var(--olive);
|
|
||||||
cursor: pointer;
|
|
||||||
text-decoration: none;
|
|
||||||
transition: background-color 0.2s ease, border-color 0.2s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
.gallery__arrow:hover,
|
|
||||||
.gallery__arrow:focus-visible {
|
|
||||||
border: 1.5px solid var(--butter);
|
|
||||||
background: var(--butter);
|
|
||||||
}
|
|
||||||
|
|
||||||
.gallery__arrow--prev {
|
|
||||||
left: var(--space-24);
|
|
||||||
}
|
|
||||||
|
|
||||||
.gallery__arrow--next {
|
|
||||||
right: var(--space-24);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* --- Carousel dot indicators --- */
|
|
||||||
|
|
||||||
.gallery_dots {
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
gap: 10px;
|
|
||||||
padding: 12px 0 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.gallery_dot {
|
|
||||||
width: 12px;
|
|
||||||
height: 12px;
|
|
||||||
border-radius: 50%;
|
|
||||||
border: 2px solid var(--olive);
|
|
||||||
background: transparent;
|
|
||||||
cursor: pointer;
|
|
||||||
padding: 0;
|
|
||||||
transition: background 0.25s ease, transform 0.2s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.gallery_dot:hover,
|
|
||||||
.gallery_dot--active {
|
|
||||||
background: var(--olive);;
|
|
||||||
transform: scale(1.2);
|
|
||||||
}
|
|
||||||
|
|
||||||
.gallery_dot:focus-visible {
|
|
||||||
outline: 2px solid var(--olive);
|
|
||||||
outline-offset: 3px;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 900px) {
|
|
||||||
.gallery__track {
|
|
||||||
gap: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.gallery__item {
|
|
||||||
flex: 0 0 100%;
|
|
||||||
min-width: 100%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* --- Gallery info (Instagram link) --- */
|
|
||||||
|
|
||||||
.gallery__info {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.gallery__icon--instagram {
|
|
||||||
height: 32px;
|
|
||||||
width: 32px;
|
|
||||||
object-fit: contain;
|
|
||||||
border-radius: 8px;
|
|
||||||
background: none;
|
|
||||||
filter: brightness(0) saturate(100%) invert(27%) sepia(81%) saturate(749%) hue-rotate(24deg) brightness(90%) contrast(90%);
|
|
||||||
}
|
|
||||||
|
|
||||||
.gallery__icon--invite {
|
|
||||||
height: 56px;
|
|
||||||
width: 56px;
|
|
||||||
object-fit: contain;
|
|
||||||
margin-left: 0;
|
|
||||||
transform: translate(-4%, -1%);
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* --- CTA button --- */
|
|
||||||
|
|
||||||
.btn {
|
|
||||||
border: none;
|
|
||||||
background: var(--olive);
|
|
||||||
color: var(--white);
|
|
||||||
padding: 12px 22px;
|
|
||||||
font-weight: 700;
|
|
||||||
cursor: pointer;
|
|
||||||
text-decoration: none;
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
border-radius: var(--radius-pill);
|
|
||||||
font-size: 0.95rem;
|
|
||||||
transition: background-color 0.2s ease, transform 0.2s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn:hover {
|
|
||||||
background-color: var(--olive-dark);
|
|
||||||
transform: translateY(-1px);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* --- Footer --- */
|
|
||||||
|
|
||||||
.footer {
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
gap: 24px;
|
|
||||||
padding: 16px 24px;
|
|
||||||
border: none;
|
|
||||||
margin-top: 40px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.footer-link {
|
|
||||||
color: var(--black);
|
|
||||||
text-decoration: underline;
|
|
||||||
font-size: 0.8rem;
|
|
||||||
font-weight: 400;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* --- FAQ Section: Akkordion --- */
|
|
||||||
|
|
||||||
.faq-section {
|
|
||||||
margin-bottom: 0px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.faq-accordion {
|
|
||||||
width: 100%;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: var(--space-8);
|
|
||||||
}
|
|
||||||
|
|
||||||
.faq-item {
|
|
||||||
border: 1.5px solid var(--olive-light);
|
|
||||||
border-radius: var(--radius-lg);
|
|
||||||
overflow: hidden;
|
|
||||||
background: var(--butter-light);
|
|
||||||
padding: var(--space-12) var(--space-24) ;
|
|
||||||
transition: background-color 0.2s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.faq-item:hover {
|
|
||||||
background: var(--olive-light);
|
|
||||||
}
|
|
||||||
|
|
||||||
.faq-trigger {
|
|
||||||
width: 100%;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
|
||||||
background: transparent;
|
|
||||||
border: none;
|
|
||||||
cursor: pointer;
|
|
||||||
font-family: var(--font-main);
|
|
||||||
font-size: 1.25rem;
|
|
||||||
font-weight: 400;;
|
|
||||||
text-align: left;
|
|
||||||
transition: background-color 0.2s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
.faq-title {
|
|
||||||
flex: 1;
|
|
||||||
font-weight: 400;
|
|
||||||
font-size: 1.25rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.faq-icon {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
width: 24px;
|
|
||||||
height: 24px;
|
|
||||||
font-size: 1.5rem;
|
|
||||||
font-weight: 400;
|
|
||||||
color: var(--black);
|
|
||||||
transition: transform 0.3s ease;
|
|
||||||
flex-shrink: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.faq-trigger[aria-expanded="true"] .faq-icon {
|
|
||||||
transform: rotate(45deg);
|
|
||||||
}
|
|
||||||
|
|
||||||
.faq-content {
|
|
||||||
padding: 0;
|
|
||||||
max-height: 0;
|
|
||||||
overflow: hidden;
|
|
||||||
transition: max-height 0.3s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
.faq-content p {
|
|
||||||
margin: 0;
|
|
||||||
padding: var(--space-12) var(--space-40) var(--space-12) 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.faq-trigger[aria-expanded="true"] + .faq-content {
|
|
||||||
display: block;
|
|
||||||
max-height: 500px;
|
|
||||||
padding: var(--space-3) var(--space-24);
|
|
||||||
}
|
|
||||||
|
|
||||||
.faq-list {
|
|
||||||
padding-left: var(--space-24);
|
|
||||||
margin: var(--space-12) var(--space-24) var(--space-12) 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.faq-list li {
|
|
||||||
font-size: 1.125rem;
|
|
||||||
margin-bottom: var(--space-12)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* --- Responsive: FAQ Section --- */
|
|
||||||
|
|
||||||
@media (max-width: 768px) {
|
|
||||||
.faq-section {
|
|
||||||
padding: var(--space-40) var(--space-24);
|
|
||||||
margin: var(--space-40) 0 var(--space-32);
|
|
||||||
}
|
|
||||||
|
|
||||||
.faq-trigger {
|
|
||||||
padding: var(--space-2) var(--space-3);
|
|
||||||
font-size: 1.125rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.faq-content {
|
|
||||||
padding: 0 var(--space-3);
|
|
||||||
font-size: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.faq-content p {
|
|
||||||
padding: var(--space-2) 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
541
css/landingpage.css
Normal file
@ -0,0 +1,541 @@
|
|||||||
|
/* Instagram and Invite logo in gallery info area */
|
||||||
|
.gallery__icon--instagram {
|
||||||
|
height: 32px;
|
||||||
|
width: 32px;
|
||||||
|
object-fit: contain;
|
||||||
|
border-radius: 8px;
|
||||||
|
background: none;
|
||||||
|
/* Olive green filter for PNG: #6b6b05 */
|
||||||
|
filter: brightness(0) saturate(100%) invert(27%) sepia(81%) saturate(749%) hue-rotate(24deg) brightness(90%) contrast(90%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.gallery__icon--invite {
|
||||||
|
height: 56px;
|
||||||
|
width: 56px;
|
||||||
|
object-fit: contain;
|
||||||
|
margin-left: 0;
|
||||||
|
transform: translate(-4%, -1%);
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
* { box-sizing: border-box; }
|
||||||
|
|
||||||
|
:root {
|
||||||
|
--black: #22211A;
|
||||||
|
--white: #ffffff;
|
||||||
|
--button-green: var(--olive);
|
||||||
|
--button-green-dark: var(--olive-dark);
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
font-family: var(--font-main);
|
||||||
|
background: #FFFDE3; /* butter background color from stylesheet */
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-wrapper {
|
||||||
|
max-width: 1440px;
|
||||||
|
margin: 0 auto;
|
||||||
|
background: var(--white);
|
||||||
|
padding: 40px;
|
||||||
|
border: 1px solid var(--black);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- 1. Basic Layout Logic --- */
|
||||||
|
.header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding: 16px 24px;
|
||||||
|
border: 1px solid var(--black);
|
||||||
|
margin-bottom: 40px; /* Using your 40px margin from the first block */
|
||||||
|
}
|
||||||
|
|
||||||
|
.header__brand {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
font-weight: 700;
|
||||||
|
font-size: 1.2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.brand__logo {
|
||||||
|
height: 34px;
|
||||||
|
width: auto;
|
||||||
|
object-fit: contain;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Grouping container for 'Event finden' and 'Login' */
|
||||||
|
.header__actions {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.top-nav-wrap {
|
||||||
|
background: #FFFDE3;
|
||||||
|
padding: 18px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.top-nav {
|
||||||
|
background: rgba(255, 255, 255, 0.95);
|
||||||
|
border-radius: 30px;
|
||||||
|
box-shadow: 0 3px 12px rgba(102, 52, 13, 0.1);
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
min-height: 58px;
|
||||||
|
padding: 12px 24px;
|
||||||
|
width: 100%;
|
||||||
|
max-width: none;
|
||||||
|
box-sizing: border-box;
|
||||||
|
margin: 0 0 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.brand {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
height: 50px;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.brand img {
|
||||||
|
width: auto;
|
||||||
|
height: 100%;
|
||||||
|
max-width: 104px;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.top-nav-links {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-link {
|
||||||
|
color: #221c1a;
|
||||||
|
line-height: 1.3;
|
||||||
|
text-decoration: none;
|
||||||
|
padding: 8px 20px;
|
||||||
|
border: 2px solid #e5e1b7;
|
||||||
|
border-radius: 20px;
|
||||||
|
transition: background-color 0.2s ease, color 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-link:hover,
|
||||||
|
.nav-link.active,
|
||||||
|
.nav-link:focus-visible {
|
||||||
|
background: #6b6b05;
|
||||||
|
color: #ffffff;
|
||||||
|
border-color: #6b6b05;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-link--login {
|
||||||
|
background: #6b6b05;
|
||||||
|
color: #ffffff;
|
||||||
|
border-color: #6b6b05;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-link--login:hover,
|
||||||
|
.nav-link--login:focus-visible {
|
||||||
|
background: #ffffff;
|
||||||
|
color: #6b6b05;
|
||||||
|
border-color: #6b6b05;
|
||||||
|
}
|
||||||
|
|
||||||
|
.main-content {
|
||||||
|
width: min(100% - 4rem, 1120px);
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 0 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 1fr;
|
||||||
|
gap: 50px;
|
||||||
|
margin-bottom: 80px;
|
||||||
|
align-items: center;
|
||||||
|
padding: 40px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.how-it-works {
|
||||||
|
margin-bottom: 70px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.how-it-works__header {
|
||||||
|
text-align: center;
|
||||||
|
margin-bottom: 32px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.how-it-works__header h2 {
|
||||||
|
font-size: 2rem;
|
||||||
|
margin: 0;
|
||||||
|
color: #221c1a;
|
||||||
|
}
|
||||||
|
|
||||||
|
.how-it-works__steps {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(3, minmax(180px, 1fr));
|
||||||
|
gap: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.how-step {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
gap: 16px;
|
||||||
|
padding: 28px 20px;
|
||||||
|
background: var(--white) !important;
|
||||||
|
border: 2px solid var(--tomato) !important;
|
||||||
|
border-radius: 28px;
|
||||||
|
box-shadow: 0 12px 30px rgba(212, 75, 36, 0.08);
|
||||||
|
}
|
||||||
|
|
||||||
|
.how-step__icon {
|
||||||
|
width: 64px;
|
||||||
|
height: 64px;
|
||||||
|
display: grid;
|
||||||
|
place-items: center;
|
||||||
|
background: var(--button-green);
|
||||||
|
color: var(--white);
|
||||||
|
border-radius: 50%;
|
||||||
|
font-size: 1.4rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.how-step__number {
|
||||||
|
width: 36px;
|
||||||
|
height: 36px;
|
||||||
|
display: grid;
|
||||||
|
place-items: center;
|
||||||
|
background: #f4efd7;
|
||||||
|
color: #221c1a;
|
||||||
|
border-radius: 50%;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.how-step__label {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 1rem;
|
||||||
|
line-height: 1.6;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #221c1a;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.how-step__icon--brown {
|
||||||
|
background: #66340d;
|
||||||
|
}
|
||||||
|
|
||||||
|
.how-step__label--brown {
|
||||||
|
color: var(--tomato);
|
||||||
|
}
|
||||||
|
|
||||||
|
.how-step__label--big {
|
||||||
|
font-size: 1.25rem;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.how-step--numbered {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.how-step__corner-number {
|
||||||
|
position: absolute;
|
||||||
|
top: 12px;
|
||||||
|
left: 16px;
|
||||||
|
font-size: 2.2rem;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.how-step__corner-number--brown {
|
||||||
|
color: var(--tomato);
|
||||||
|
}
|
||||||
|
|
||||||
|
.how-step__png {
|
||||||
|
width: 192px;
|
||||||
|
height: 192px;
|
||||||
|
object-fit: contain;
|
||||||
|
}
|
||||||
|
|
||||||
|
.how-step__png--brown {
|
||||||
|
filter: brightness(0) saturate(100%) invert(39%) sepia(84%) saturate(1682%) hue-rotate(349deg) brightness(93%) contrast(86%);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@media (max-width: 900px) {
|
||||||
|
.how-it-works__steps {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero__left {
|
||||||
|
max-width: 520px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero__left p {
|
||||||
|
margin: 24px 0 32px;
|
||||||
|
line-height: 1.8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero__right {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.image-card {
|
||||||
|
width: 100%;
|
||||||
|
max-width: 396px;
|
||||||
|
overflow: hidden;
|
||||||
|
border-radius: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-image {
|
||||||
|
width: 100%;
|
||||||
|
max-width: 396px;
|
||||||
|
max-height: 464px;
|
||||||
|
height: auto;
|
||||||
|
border-radius: 30px;
|
||||||
|
object-fit: cover;
|
||||||
|
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.12);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 900px) {
|
||||||
|
.hero {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
padding: 30px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero__right {
|
||||||
|
order: -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-image {
|
||||||
|
min-height: 320px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Carousel gallery */
|
||||||
|
.gallery__carousel {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gallery__track {
|
||||||
|
display: flex;
|
||||||
|
gap: 20px;
|
||||||
|
overflow: hidden;
|
||||||
|
margin-bottom: 30px; /* Space between photos and dots */
|
||||||
|
scroll-behavior: smooth;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gallery__item {
|
||||||
|
flex: 0 0 calc((100% - 40px) / 3);
|
||||||
|
min-width: calc((100% - 40px) / 3);
|
||||||
|
border-radius: 24px;
|
||||||
|
overflow: hidden;
|
||||||
|
background: #fff;
|
||||||
|
box-shadow: 0 12px 24px rgba(0, 0, 0, 0.06);
|
||||||
|
aspect-ratio: 2 / 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gallery__item img {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
display: block;
|
||||||
|
object-fit: cover;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gallery__arrow {
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
width: 48px;
|
||||||
|
height: 48px;
|
||||||
|
display: grid;
|
||||||
|
place-items: center;
|
||||||
|
border-radius: 0;
|
||||||
|
background: none;
|
||||||
|
backdrop-filter: none;
|
||||||
|
border: none;
|
||||||
|
color: #ffffff;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 1.6rem;
|
||||||
|
text-shadow: 0 1px 4px rgba(0, 0, 0, 0.4);
|
||||||
|
transition: transform 0.2s ease, color 0.2s ease;
|
||||||
|
z-index: 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gallery__arrow:hover {
|
||||||
|
transform: translateY(-50%) scale(1.15);
|
||||||
|
color: #e5e1b7;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gallery__arrow--prev {
|
||||||
|
left: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gallery__arrow--next {
|
||||||
|
right: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Center arrow removed – using side arrows only */
|
||||||
|
|
||||||
|
|
||||||
|
@media (max-width: 900px) {
|
||||||
|
.gallery__track {
|
||||||
|
gap: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gallery__item {
|
||||||
|
flex: 0 0 100%;
|
||||||
|
min-width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- 2. Button & Link Styling --- */
|
||||||
|
.btn {
|
||||||
|
border: none;
|
||||||
|
background: var(--button-green);
|
||||||
|
color: var(--white);
|
||||||
|
padding: 12px 22px;
|
||||||
|
font-weight: 700;
|
||||||
|
cursor: pointer;
|
||||||
|
text-decoration: none; /* Keeps the link from having an underline */
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
border-radius: 999px;
|
||||||
|
font-size: 0.95rem;
|
||||||
|
transition: background-color 0.2s ease, transform 0.2s ease, box-shadow 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn:hover,
|
||||||
|
.btn:focus-visible {
|
||||||
|
background-color: var(--button-green-dark);
|
||||||
|
transform: translateY(-1px);
|
||||||
|
box-shadow: 0 4px 10px rgba(107, 107, 5, 0.28);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn:active {
|
||||||
|
transform: translateY(0);
|
||||||
|
box-shadow: 0 2px 6px rgba(107, 107, 5, 0.25);
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav__link {
|
||||||
|
color: var(--black); /* Fixes the purple link issue */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- 3. The "X" Box Logic --- */
|
||||||
|
.image-card__placeholder, .placeholder {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
min-height: 250px;
|
||||||
|
position: relative;
|
||||||
|
border: 1px solid var(--black);
|
||||||
|
background: var(--white);
|
||||||
|
}
|
||||||
|
|
||||||
|
.image-card__placeholder::before, .image-card__placeholder::after,
|
||||||
|
.placeholder::before, .placeholder::after {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
top: 0; left: 0; right: 0; bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Drawing the diagonal lines for the wireframe look */
|
||||||
|
.image-card__placeholder::before, .placeholder::before {
|
||||||
|
background: linear-gradient(to top left, transparent 49.5%, var(--black) 49.5%, var(--black) 50.5%, transparent 50.5%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.image-card__placeholder::after, .placeholder::after {
|
||||||
|
background: linear-gradient(to top right, transparent 49.5%, var(--black) 49.5%, var(--black) 50.5%, transparent 50.5%);
|
||||||
|
}
|
||||||
|
/* The "X" Box Logic - IMPORTANT */
|
||||||
|
.image-card__placeholder, .placeholder {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
min-height: 250px; /* Ensures the box has height */
|
||||||
|
position: relative; /* REQUIRED for the X lines to stay inside */
|
||||||
|
border: 1px solid var(--black);
|
||||||
|
background: var(--white);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Creating the diagonal lines */
|
||||||
|
.image-card__placeholder::before, .image-card__placeholder::after,
|
||||||
|
.placeholder::before, .placeholder::after {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
top: 0; left: 0; right: 0; bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.image-card__placeholder::before, .placeholder::before {
|
||||||
|
background: linear-gradient(to top left, transparent 49.5%, var(--black) 49.5%, var(--black) 50.5%, transparent 50.5%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.image-card__placeholder::after, .placeholder::after {
|
||||||
|
background: linear-gradient(to top right, transparent 49.5%, var(--black) 49.5%, var(--black) 50.5%, transparent 50.5%);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Badges and Buttons */
|
||||||
|
.social-badge {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center; /* This centers the dots */
|
||||||
|
gap: 10px;
|
||||||
|
margin-top: 20px; /* This creates space between the photos and the dots */
|
||||||
|
/* REMOVE any line that says "position: absolute" or "bottom: 16px" */
|
||||||
|
}
|
||||||
|
|
||||||
|
.social-badge__dot { width: 12px; height: 12px; background: var(--black); border-radius: 50%; }
|
||||||
|
|
||||||
|
.profile-badge {
|
||||||
|
width: 60px; height: 60px;
|
||||||
|
border-radius: 50%;
|
||||||
|
border: 1px solid var(--black);
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gallery__info {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gallery__handle {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
font-size: 1.1rem;
|
||||||
|
font-weight: 700;
|
||||||
|
color: var(--black);
|
||||||
|
gap: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gallery__icon {
|
||||||
|
font-size: 1.15rem;
|
||||||
|
color: #DD541A;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gallery__at {
|
||||||
|
font-size: 1.1rem;
|
||||||
|
color: #222;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gallery__brand {
|
||||||
|
font-family: var(--font-main);
|
||||||
|
color: #DD541A;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Footer */
|
||||||
|
.footer {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
padding: 16px 24px;
|
||||||
|
border: none;
|
||||||
|
margin-top: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer__link {
|
||||||
|
color: var(--black);
|
||||||
|
text-decoration: underline;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
font-weight: 400;
|
||||||
|
}
|
||||||
@ -1,102 +1,116 @@
|
|||||||
/* ===========================================
|
.container {
|
||||||
LOGIN_SIGNUP.CSS — Styles for login & signup
|
margin-top: 5rem;
|
||||||
Global styles (reset, variables, body, nav,
|
background-color: var(--white);
|
||||||
typography) are in stylesheet_global.css
|
|
||||||
=========================================== */
|
|
||||||
|
|
||||||
.container-login {
|
|
||||||
background-color: var(--butter-light);
|
|
||||||
border: 1.5px solid var(--olive-light);
|
|
||||||
border-radius: var(--radius-lg);
|
|
||||||
padding: var(--space-40) var(--space-80) var(--space-80) var(--space-80);
|
|
||||||
}
|
|
||||||
|
|
||||||
.container-registration {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: 1fr 1fr;
|
|
||||||
gap: var(--space-64);
|
|
||||||
|
|
||||||
background-color: var(--butter-light);
|
|
||||||
border: 1.5px solid var(--olive-light);
|
|
||||||
border-radius: var(--radius-lg);
|
border-radius: var(--radius-lg);
|
||||||
|
max-width: 1000px;
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
gap: 20px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.text-section {
|
|
||||||
padding: var(--space-40) var(--space-80) var(--space-80) var(--space-80);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* --- Image section --- */
|
|
||||||
|
|
||||||
.image-section {
|
.image-section {
|
||||||
height: 100%;
|
flex: 1;
|
||||||
|
background-color: var(--white);
|
||||||
display: flex;
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
min-height: 500px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.image-section img {
|
.image-section img {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
object-fit: cover;
|
object-fit: cover;
|
||||||
display: block;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.form-section {
|
||||||
|
flex: 1;
|
||||||
|
padding: 40px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
.form-group.has-error {
|
/* Formularelemente */
|
||||||
margin-bottom: 0;
|
.info-box {
|
||||||
|
background-color: var(--olive-light);
|
||||||
|
padding: 15px;
|
||||||
|
margin-bottom: 30px;
|
||||||
|
border-radius: 8px;
|
||||||
|
font-size: 16px;
|
||||||
|
color: var(--black);
|
||||||
|
line-height: 1.4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-group {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
label {
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 6px;
|
||||||
|
color: var(--black);
|
||||||
|
font-weight: 500;
|
||||||
|
font-size: 14px;
|
||||||
}
|
}
|
||||||
|
|
||||||
input[type="text"],
|
input[type="text"],
|
||||||
input[type="email"],
|
input[type="email"],
|
||||||
input[type="password"] {
|
input[type="password"] {
|
||||||
font-size: 1.125rem;
|
|
||||||
font-family: var(--font-main);
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
padding: var(--space-8) var(--space-16);
|
padding: 12px;
|
||||||
border: 1.5px solid var(--olive-light);
|
border: 1px solid #ddd;
|
||||||
border-radius: var(--radius-md);
|
border-radius: 4px;
|
||||||
background: transparent;
|
font-size: 14px;
|
||||||
transition: border-color 0.3s ease;
|
transition: border-color 0.3s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
input:focus {
|
input:focus {
|
||||||
outline: none;
|
outline: none;
|
||||||
border: 2px solid var(--olive);
|
border-color: var(--olive-dark);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* --- Info box --- */
|
|
||||||
.info-box {
|
/* Hilfstexte & Fehler */
|
||||||
background-color: var(--olive-light);
|
.signup-hint, .login-hint {
|
||||||
padding: var(--space-16);
|
text-align: left;
|
||||||
margin-bottom: var(--space-40);
|
margin-top: 20px;
|
||||||
border-radius: var(--radius-md);
|
|
||||||
font-size: 1rem;
|
|
||||||
color: var(--black);
|
color: var(--black);
|
||||||
line-height: 1.4;
|
font-size: 1rem;
|
||||||
|
line-height: 130%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.signup-hint a, .login-hint a {
|
||||||
|
color: var(--blue);
|
||||||
|
text-decoration: none;
|
||||||
|
font-weight: 500;
|
||||||
|
transition: color 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
/* --- Hints & errors --- */
|
.signup-hint a:hover, .login-hint a:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
text-underline-offset: 2px;
|
||||||
|
color: var(--blue);
|
||||||
|
}
|
||||||
|
|
||||||
.error-message--field-callout {
|
.error-message {
|
||||||
|
color: var(--error);
|
||||||
|
font-size: 13px;
|
||||||
|
margin-top: 5px;
|
||||||
display: none;
|
display: none;
|
||||||
margin-top: 0.65rem;
|
|
||||||
margin-left: auto;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.form-group.has-error input {
|
.form-group.has-error input {
|
||||||
border-color: var(--error);
|
border-color: var(--error);
|
||||||
box-shadow: var(--shadow-error);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.form-group.has-error .error-message--field-callout {
|
.form-group.has-error .error-message {
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
*/
|
|
||||||
|
|
||||||
/* --- Modal / Popup --- */
|
|
||||||
|
|
||||||
|
/* Modal / Popup Styles */
|
||||||
.modal {
|
.modal {
|
||||||
display: none;
|
display: none;
|
||||||
position: fixed;
|
position: fixed;
|
||||||
@ -120,19 +134,38 @@ input:focus {
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.modal-content {
|
||||||
|
background-color: white;
|
||||||
|
padding: 40px;
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
|
||||||
|
max-width: 500px;
|
||||||
|
width: 90%;
|
||||||
|
text-align: center;
|
||||||
|
animation: slideIn 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
@keyframes slideIn {
|
@keyframes slideIn {
|
||||||
from { transform: translateY(-50px); opacity: 0; }
|
from { transform: translateY(-50px); opacity: 0; }
|
||||||
to { transform: translateY(0); opacity: 1; }
|
to { transform: translateY(0); opacity: 1; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.modal-header {
|
||||||
|
position: relative;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-header h2 {
|
||||||
|
color: #0084ff;
|
||||||
|
font-size: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
.close-btn {
|
.close-btn {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
right: 0;
|
right: 0;
|
||||||
top: 0;
|
top: 0;
|
||||||
font-size: 28px;
|
font-size: 28px;
|
||||||
color: var(--black);
|
color: #999;
|
||||||
background: none;
|
background: none;
|
||||||
border: none;
|
border: none;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
@ -143,62 +176,20 @@ input:focus {
|
|||||||
justify-content: center;
|
justify-content: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-primary {
|
.modal-body {
|
||||||
padding: var(--space-2) var(--space-32);
|
color: #333;
|
||||||
background-color: var(--olive);
|
font-size: 16px;
|
||||||
color: var(--white);
|
line-height: 1.6;
|
||||||
border: none;
|
margin-bottom: 30px;
|
||||||
border-radius: var(--radius-pill);
|
|
||||||
font-size: 1rem;
|
|
||||||
font-weight: 700;
|
|
||||||
font-family: var(--font-main);
|
|
||||||
cursor: pointer;
|
|
||||||
transition: background-color 0.3s ease;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-primary:hover {
|
.modal-footer {
|
||||||
background-color: var(--olive-dark);
|
display: flex;
|
||||||
|
gap: 10px;
|
||||||
|
justify-content: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Responsive Anpassungen */
|
||||||
/* --- Footer --- */
|
|
||||||
|
|
||||||
.footer {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: 1fr auto 1fr;
|
|
||||||
align-items: center;
|
|
||||||
padding: var(--space-16) var(--space-32);
|
|
||||||
border: none;
|
|
||||||
margin-top: 40px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.footer_link {
|
|
||||||
color: var(--black);
|
|
||||||
text-decoration: underline;
|
|
||||||
font-size: 0.8rem;
|
|
||||||
font-weight: 400;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Left aligned */
|
|
||||||
.footer-left {
|
|
||||||
justify-self: start;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Center aligned */
|
|
||||||
.footer-center {
|
|
||||||
justify-self: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Right aligned */
|
|
||||||
.footer-right {
|
|
||||||
justify-self: end;
|
|
||||||
display: flex;
|
|
||||||
gap: var(--space-24);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* --- Responsive --- */
|
|
||||||
|
|
||||||
@media (max-width: 768px) {
|
@media (max-width: 768px) {
|
||||||
.container {
|
.container {
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
@ -208,13 +199,18 @@ input:focus {
|
|||||||
min-height: 300px;
|
min-height: 300px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.error-message--field-callout {
|
.header {
|
||||||
margin-top: var(--space-1);
|
flex-direction: column;
|
||||||
max-width: 100%;
|
gap: 15px;
|
||||||
white-space: normal;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.error-message--field-callout::after {
|
.header-buttons {
|
||||||
display: none;
|
width: 100%;
|
||||||
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
.header-btn {
|
||||||
|
width: 100%;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,12 +1,17 @@
|
|||||||
|
.profile-page {
|
||||||
|
/* Reserve a large safe zone below sticky nav so title/actions are never covered. */
|
||||||
|
margin-top: 0;
|
||||||
|
padding-top: 6.5rem;
|
||||||
|
margin-bottom: var(--space-8);
|
||||||
|
}
|
||||||
|
|
||||||
/* Kopfbereich mit Titel und Logout-Aktion. */
|
/* Kopfbereich mit Titel und Logout-Aktion. */
|
||||||
.profile-hero {
|
.profile-hero {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
gap: var(--space-24);
|
gap: var(--space-4);
|
||||||
margin-bottom: var(--space-32);
|
margin-bottom: var(--space-5);
|
||||||
}
|
}
|
||||||
|
|
||||||
.profile-kicker {
|
.profile-kicker {
|
||||||
@ -14,11 +19,13 @@
|
|||||||
color: var(--olive);
|
color: var(--olive);
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
letter-spacing: var(--ls-lg);
|
letter-spacing: var(--ls-label);
|
||||||
}
|
}
|
||||||
|
|
||||||
#headline {
|
#profile-headline {
|
||||||
|
margin: 0.4rem 0;
|
||||||
color: var(--brown);
|
color: var(--brown);
|
||||||
|
font-size: clamp(2rem, 4.4vw, 2.8rem);
|
||||||
}
|
}
|
||||||
|
|
||||||
.profile-subline {
|
.profile-subline {
|
||||||
@ -34,92 +41,136 @@
|
|||||||
.profile-grid {
|
.profile-grid {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 1fr;
|
grid-template-columns: 1fr;
|
||||||
gap: var(--space-24);
|
gap: var(--space-4);
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-count {
|
.profile-tabs {
|
||||||
|
display: inline-flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: var(--space-2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-tab {
|
||||||
|
border: 2px solid var(--olive);
|
||||||
|
border-radius: var(--radius-md);
|
||||||
|
background: var(--butter);
|
||||||
color: var(--black);
|
color: var(--black);
|
||||||
background: var(--tomato-light);
|
padding: 0.45rem 1rem;
|
||||||
height: 32px;
|
min-height: 2.5rem;
|
||||||
width: 32px;
|
font-family: "Jost", sans-serif;
|
||||||
margin-right: -18px;
|
font-size: 1rem;
|
||||||
display: inline-flex;
|
font-weight: 500;
|
||||||
align-items: center;
|
letter-spacing: var(--ls-ui);
|
||||||
justify-content: center;
|
cursor: pointer;
|
||||||
border-radius: var(--radius-pill);
|
transition: background-color 0.2s ease, color 0.2s ease, border-color 0.2s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Konsistentes Karten-Layout für alle Profilsektionen. */
|
.profile-tab:hover,
|
||||||
|
.profile-tab:focus-visible {
|
||||||
|
background: #faf8e8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-tab.is-active {
|
||||||
|
border-color: transparent;
|
||||||
|
background: var(--olive);
|
||||||
|
color: var(--white);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Konsistentes Karten-Layout fuer alle Profilsektionen. */
|
||||||
.profile-panel {
|
.profile-panel {
|
||||||
background: var(--butter-light);
|
background: rgba(255, 255, 255, 0.88);
|
||||||
border-radius: var(--radius-lg);
|
border-radius: var(--radius-lg);
|
||||||
padding: var(--space-32);
|
box-shadow: 0 3px 12px rgba(102, 52, 13, 0.1);
|
||||||
|
padding: var(--space-5);
|
||||||
}
|
}
|
||||||
|
|
||||||
.panel-head {
|
.panel-head {
|
||||||
display: none;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
margin-bottom: var(--space-16);
|
margin-bottom: var(--space-3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.panel-title {
|
||||||
|
margin: 0;
|
||||||
|
color: var(--brown);
|
||||||
|
font-size: 1.8rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.panel-count {
|
||||||
|
min-width: 2rem;
|
||||||
|
padding: 0.1rem 0.65rem;
|
||||||
|
border-radius: var(--radius-pill);
|
||||||
|
background: var(--olive-light);
|
||||||
|
color: var(--black);
|
||||||
|
font-size: 0.95rem;
|
||||||
|
font-weight: 600;
|
||||||
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.profile-card-list {
|
.profile-card-list {
|
||||||
display: grid;
|
display: grid;
|
||||||
gap: var(--space-16);
|
gap: var(--space-2);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Einzelne Eventkarte für "Meine Events" und "Meine Anmeldungen". */
|
/* Einzelne Eventkarte fuer "Meine Events" und "Meine Anmeldungen". */
|
||||||
.profile-event-card {
|
.profile-event-card {
|
||||||
background: var(--butter-light);
|
border: 1px solid rgba(107, 107, 5, 0.25);
|
||||||
border: 1.5px solid var(--olive-light);
|
border-radius: var(--radius-md);
|
||||||
border-radius: var(--radius-lg);
|
padding: var(--space-3);
|
||||||
box-shadow: var(--shadow-interaction);
|
|
||||||
padding: var(--space-32) var(--space-40);
|
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
gap: var(--space-40);
|
align-items: center;
|
||||||
cursor: pointer;
|
gap: var(--space-3);
|
||||||
transition: box-shadow 0.2s ease, transform 0.2s ease;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.profile-event-card-clickable {
|
.profile-event-card-clickable {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
transition: box-shadow 0.2s ease, transform 0.2s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
.profile-event-card-clickable:hover {
|
.profile-event-card-clickable:hover {
|
||||||
transform: translateY(-3px);
|
box-shadow: 0 6px 16px rgba(102, 52, 13, 0.14);
|
||||||
|
transform: translateY(-1px);
|
||||||
}
|
}
|
||||||
|
|
||||||
.profile-event-title h3{
|
.profile-event-title {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
|
color: var(--black);
|
||||||
|
font-family: "Jost", sans-serif;
|
||||||
|
font-size: 1.25rem;
|
||||||
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
.profile-event-meta {
|
.profile-event-meta {
|
||||||
|
margin: 0.3rem 0 0;
|
||||||
}*/
|
font-size: 0.95rem;
|
||||||
|
color: var(--olive);
|
||||||
|
}
|
||||||
|
|
||||||
.profile-event-address-block {
|
.profile-event-address-block {
|
||||||
margin-top: var(--space-24);
|
margin-top: 0.55rem;
|
||||||
background-color: var(--olive-light);
|
padding: 0.6rem 0.75rem;
|
||||||
padding: var(--space-16);
|
border-radius: var(--radius-sm);
|
||||||
border-radius: var(--radius-md);
|
border-left: 4px solid var(--tomato);
|
||||||
color: var(--black);
|
background: rgba(232, 237, 209, 0.65);
|
||||||
line-height: 1.4;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.profile-event-address-label {
|
.profile-event-address-label {
|
||||||
font-size: 1rem;
|
margin: 0;
|
||||||
font-weight: 400;
|
color: var(--olive);
|
||||||
color: var(--olive-dark);
|
font-size: 0.72rem;
|
||||||
|
font-weight: 700;
|
||||||
|
letter-spacing: var(--ls-label);
|
||||||
|
text-transform: uppercase;
|
||||||
}
|
}
|
||||||
|
|
||||||
.profile-event-address {
|
.profile-event-address {
|
||||||
font-size: 1.125rem;
|
margin: 0.2rem 0 0;
|
||||||
font-weight: 400;
|
font-size: 0.95rem;
|
||||||
line-height: 1.4;
|
|
||||||
color: var(--black);
|
color: var(--black);
|
||||||
|
font-weight: 600;
|
||||||
|
line-height: 1.35;
|
||||||
}
|
}
|
||||||
|
|
||||||
.profile-event-link {
|
.profile-event-link {
|
||||||
@ -138,22 +189,109 @@
|
|||||||
.profile-event-actions {
|
.profile-event-actions {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
gap: var(--space-2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-unregister-btn {
|
||||||
|
border: none;
|
||||||
|
border-radius: var(--radius-md);
|
||||||
|
background: var(--tomato);
|
||||||
|
color: var(--butter-light);
|
||||||
|
padding: 0.45rem 0.95rem;
|
||||||
|
font-family: "Jost", sans-serif;
|
||||||
|
font-size: 0.95rem;
|
||||||
|
font-weight: 500;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background-color 0.2s ease, transform 0.2s ease, box-shadow 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-cancel-btn {
|
||||||
|
border: none;
|
||||||
|
border-radius: var(--radius-md);
|
||||||
|
background: var(--tomato);
|
||||||
|
color: var(--butter-light);
|
||||||
|
padding: 0.45rem 0.95rem;
|
||||||
|
font-family: "Jost", sans-serif;
|
||||||
|
font-size: 0.95rem;
|
||||||
|
font-weight: 500;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background-color 0.2s ease, transform 0.2s ease, box-shadow 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-cancel-btn:hover,
|
||||||
|
.profile-cancel-btn:focus-visible {
|
||||||
|
background: var(--tomato-dark);
|
||||||
|
transform: translateY(-1px);
|
||||||
|
box-shadow: 0 4px 10px rgba(188, 74, 52, 0.28);
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-cancel-btn:active {
|
||||||
|
transform: translateY(0);
|
||||||
|
box-shadow: 0 2px 6px rgba(188, 74, 52, 0.25);
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-unregister-btn:hover,
|
||||||
|
.profile-unregister-btn:focus-visible {
|
||||||
|
background: var(--tomato-dark);
|
||||||
|
transform: translateY(-1px);
|
||||||
|
box-shadow: 0 4px 10px rgba(188, 74, 52, 0.28);
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-unregister-btn:active {
|
||||||
|
transform: translateY(0);
|
||||||
|
box-shadow: 0 2px 6px rgba(188, 74, 52, 0.25);
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-empty {
|
||||||
|
margin: 0;
|
||||||
|
color: var(--black);
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-empty-state {
|
||||||
|
text-align: center;
|
||||||
|
padding: 2.4rem 1.3rem;
|
||||||
|
border: 2px solid var(--olive-light);
|
||||||
|
border-radius: var(--radius-lg);
|
||||||
|
background: rgba(255, 255, 255, 0.92);
|
||||||
|
box-shadow: 0 3px 12px rgba(102, 52, 13, 0.08);
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-empty-kicker {
|
||||||
|
margin: 0 0 0.5rem;
|
||||||
|
color: var(--olive);
|
||||||
|
font-size: 0.8rem;
|
||||||
|
font-weight: 600;
|
||||||
|
letter-spacing: var(--ls-label);
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-empty-state h3 {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 1.5rem;
|
||||||
|
color: var(--brown);
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-empty-state p {
|
||||||
|
margin: 0.65rem auto 1rem;
|
||||||
|
max-width: 36rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.form-grid {
|
.form-grid {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 1fr 1fr;
|
grid-template-columns: 1fr 1fr;
|
||||||
gap: var(--space-16);
|
gap: var(--space-3);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
.form-group {
|
||||||
|
margin-bottom: var(--space-3);
|
||||||
|
}
|
||||||
|
|
||||||
.form-group label {
|
.form-group label {
|
||||||
display: block;
|
display: block;
|
||||||
margin-bottom: 0.35rem;
|
margin-bottom: 0.35rem;
|
||||||
font-size: 0.95rem;
|
font-size: 0.95rem;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
}*/
|
}
|
||||||
|
|
||||||
.form-group input {
|
.form-group input {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
@ -188,17 +326,8 @@
|
|||||||
|
|
||||||
.form-group.has-error input {
|
.form-group.has-error input {
|
||||||
border-color: var(--error);
|
border-color: var(--error);
|
||||||
box-shadow: var(--shadow-error);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.info-abmeldung {
|
|
||||||
font-size: 1rem;
|
|
||||||
color: var(--olive);
|
|
||||||
margin-bottom: 16px;
|
|
||||||
display: flex; align-items:
|
|
||||||
flex-start;
|
|
||||||
gap: 8px;
|
|
||||||
}
|
|
||||||
.profile-feedback {
|
.profile-feedback {
|
||||||
margin: 0.75rem 0 0;
|
margin: 0.75rem 0 0;
|
||||||
font-size: 0.95rem;
|
font-size: 0.95rem;
|
||||||
@ -208,8 +337,8 @@
|
|||||||
|
|
||||||
.profile-cta-row {
|
.profile-cta-row {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: var();
|
gap: var(--space-2);
|
||||||
margin-top: var(--space-16);
|
margin-top: var(--space-3);
|
||||||
}
|
}
|
||||||
|
|
||||||
.profile-button-secondary {
|
.profile-button-secondary {
|
||||||
@ -221,7 +350,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 48rem) {
|
@media (max-width: 48rem) {
|
||||||
.container {
|
.profile-page {
|
||||||
padding-top: 5.5rem;
|
padding-top: 5.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
171
data/events.json
@ -1,22 +1,21 @@
|
|||||||
[
|
[
|
||||||
{
|
{
|
||||||
"id": 1,
|
"id": 1,
|
||||||
"title": "Italienische Tavolata",
|
"title": "Italienische Tavolata",
|
||||||
"location": "Luzern",
|
"location": "Luzern",
|
||||||
"address": "Pilatusstrasse 18, 6003 Luzern",
|
"address": "Pilatusstrasse 18, 6003 Luzern",
|
||||||
"date": "17. Mai. 2026",
|
"date": "11. APR. 2026",
|
||||||
"time": "15:30 UHR",
|
"time": "3:30 UHR",
|
||||||
"category": "Dinner",
|
"category": "DINNER",
|
||||||
"diet": "Vegetarisch",
|
"diet": "VEGGIE",
|
||||||
"spots": 8,
|
"spots": 6,
|
||||||
"host": {
|
"host": {
|
||||||
"name": "Ferdinando",
|
"name": "Ferdinando",
|
||||||
"initial": "F"
|
"initial": "F"
|
||||||
},
|
},
|
||||||
"hostMessage": [
|
"hostMessage": [
|
||||||
"Ciao zusammen! Ich liebe die italienische Küche, nicht nur wegen des Essens, sondern wegen des Gefühls: Alle sitzen an einem langen Tisch, teilen sich grosse Platten und geniessen die Zeit.",
|
"Ciao zusammen! Ich liebe die italienische Küche, nicht nur wegen des Essens, sondern wegen des Gefühls: Alle sitzen an einem langen Tisch, teilen sich grosse Platten und geniessen die Zeit.",
|
||||||
"Genau das möchte ich mit euch teilen. Ich bereite dafür eine klassische Tavolata vor, bei der verschiedene Gerichte in die Mitte des Tisches kommen und sich jeder bedient.",
|
"Genau das möchte ich mit euch teilen. Ich bereite dafuer eine klassische Tavolata vor, bei der verschiedene Gerichte in die Mitte des Tisches kommen und sich jeder bedient."
|
||||||
"Wenn es das Wetter erlaubt sind wir draussen."
|
|
||||||
],
|
],
|
||||||
"menu": [
|
"menu": [
|
||||||
"Bruschetta-Variationen und Antipasti",
|
"Bruschetta-Variationen und Antipasti",
|
||||||
@ -46,10 +45,10 @@
|
|||||||
"title": "Noche Peruana",
|
"title": "Noche Peruana",
|
||||||
"location": "Chur",
|
"location": "Chur",
|
||||||
"address": "Obere Gasse 41, 7000 Chur",
|
"address": "Obere Gasse 41, 7000 Chur",
|
||||||
"date": "8. Mai 2026",
|
"date": "11. APR. 2026",
|
||||||
"time": "19:00 UHR",
|
"time": "19:00 UHR",
|
||||||
"category": "Dinner",
|
"category": "DINNER",
|
||||||
"diet": "Fleisch, Fisch",
|
"diet": "Omnivore",
|
||||||
"spots": 4,
|
"spots": 4,
|
||||||
"host": {
|
"host": {
|
||||||
"name": "Camila",
|
"name": "Camila",
|
||||||
@ -58,7 +57,7 @@
|
|||||||
"hostMessage": [
|
"hostMessage": [
|
||||||
"¡Hola a todos! Ich lade euch ein auf eine kulinarische Reise nach Peru.",
|
"¡Hola a todos! Ich lade euch ein auf eine kulinarische Reise nach Peru.",
|
||||||
"Ich koche für euch ein authentisches peruanisches Sharing-Menü, das vor Lebensfreude nur so sprüht. Freut euch auf eine Explosion aus leuchtenden Farben, fein abgestimmter Schärfe und der unverwechselbaren Frische verschiedenster Kräuter.",
|
"Ich koche für euch ein authentisches peruanisches Sharing-Menü, das vor Lebensfreude nur so sprüht. Freut euch auf eine Explosion aus leuchtenden Farben, fein abgestimmter Schärfe und der unverwechselbaren Frische verschiedenster Kräuter.",
|
||||||
"Wir geniessen den Abend gemeinsam in mehreren kleinen Gängen, ganz nach dem Sharing-Prinzip. Dabei entdecken wir die klassischen Aromen meiner Heimatstadt Lima – von traditionell bis modern interpretiert.",
|
"Wir genießen den Abend gemeinsam in mehreren kleinen Gängen, ganz nach dem Sharing-Prinzip. Dabei entdecken wir die klassischen Aromen meiner Heimatstadt Lima – von traditionell bis modern interpretiert.",
|
||||||
"Es wird gesellig, aromatisch und ein echtes Erlebnis für alle Sinne. ¡Buen provecho!"
|
"Es wird gesellig, aromatisch und ein echtes Erlebnis für alle Sinne. ¡Buen provecho!"
|
||||||
],
|
],
|
||||||
"menu": [
|
"menu": [
|
||||||
@ -86,12 +85,12 @@
|
|||||||
{
|
{
|
||||||
"id": 3,
|
"id": 3,
|
||||||
"title": "Japanese Delight",
|
"title": "Japanese Delight",
|
||||||
"location": "Zürich",
|
"location": "ZÜRICH",
|
||||||
"address": "Limmatquai 92, 8001 Zürich",
|
"address": "Limmatquai 92, 8001 Zürich",
|
||||||
"date": "12. Mai 2026",
|
"date": "02. MAI. 2026",
|
||||||
"time": "12:30 UHR",
|
"time": "12:30 UHR",
|
||||||
"category": "Lunch",
|
"category": "LUNCH",
|
||||||
"diet": "Fisch",
|
"diet": "Pescetarisch",
|
||||||
"spots": 8,
|
"spots": 8,
|
||||||
"host": {
|
"host": {
|
||||||
"name": "Akiko",
|
"name": "Akiko",
|
||||||
@ -122,145 +121,5 @@
|
|||||||
"https://i.pinimg.com/1200x/b1/fb/3a/b1fb3a7809f4046843904ac8800daacc.jpg",
|
"https://i.pinimg.com/1200x/b1/fb/3a/b1fb3a7809f4046843904ac8800daacc.jpg",
|
||||||
"https://i.pinimg.com/1200x/c6/93/42/c69342ec621333e853c35bda891d8bc6.jpg"
|
"https://i.pinimg.com/1200x/c6/93/42/c69342ec621333e853c35bda891d8bc6.jpg"
|
||||||
]
|
]
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": 4,
|
|
||||||
"title": "Cucina Brasileira",
|
|
||||||
"location": "Basel",
|
|
||||||
"address": "Fredy Kübler Weg 5, 8134 Adliswil",
|
|
||||||
"date": "15. Mai. 2026",
|
|
||||||
"time": "19:00 UHR",
|
|
||||||
"category": "Dinner",
|
|
||||||
"diet": "Fleisch",
|
|
||||||
"spots": 8,
|
|
||||||
"host": {
|
|
||||||
"name": "Mia",
|
|
||||||
"initial": "M"
|
|
||||||
},
|
|
||||||
"hostMessage": [
|
|
||||||
"Ihr seit herzlich eingeladen zu meinem Brasilianischen Abendessen! Lasst euch überraschen."
|
|
||||||
],
|
|
||||||
"menu": [
|
|
||||||
"Feijoada Brasileira com Farofa",
|
|
||||||
"Arroz",
|
|
||||||
"Vinagrette",
|
|
||||||
"Salada de Couve",
|
|
||||||
"Salada de batata",
|
|
||||||
"Bolo de Mandioca"
|
|
||||||
|
|
||||||
],
|
|
||||||
"specifications": [],
|
|
||||||
"participants": [
|
|
||||||
"Carlos",
|
|
||||||
"Vivien",
|
|
||||||
"Estelle",
|
|
||||||
"Simona",
|
|
||||||
"Ysabelle"
|
|
||||||
],
|
|
||||||
"gallery": [
|
|
||||||
"https://i.pinimg.com/736x/62/39/4b/62394bb73b986dfb89f41e809e2c8dd4.jpg",
|
|
||||||
"https://i.pinimg.com/1200x/68/fe/bd/68febdd512a00f0a345e51ebed7ddd63.jpg",
|
|
||||||
"https://i.pinimg.com/1200x/0a/8d/67/0a8d674a7923c6e9bfe3665bc63522d0.jpg"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": 5,
|
|
||||||
"title": "Mexican Fiesta",
|
|
||||||
"location": "Basel",
|
|
||||||
"address": "Münsterplatz 10, 4051 Basel",
|
|
||||||
"date": "28. April. 2026",
|
|
||||||
"time": "18:00 UHR",
|
|
||||||
"category": "Dinner",
|
|
||||||
"diet": "Omnivore",
|
|
||||||
"spots": 6,
|
|
||||||
"host": {
|
|
||||||
"name": "Carlos",
|
|
||||||
"initial": "C"
|
|
||||||
},
|
|
||||||
"hostMessage": [
|
|
||||||
"Hallo zusammen! Leider muss ich dieses Event absagen, da mir etwas Wichtiges dazwischengekommen ist.",
|
|
||||||
"Ich hoffe, wir können das bald nachholen!"
|
|
||||||
],
|
|
||||||
"menu": [
|
|
||||||
"Guacamole & Nachos",
|
|
||||||
"Tacos al Pastor",
|
|
||||||
"Churros"
|
|
||||||
],
|
|
||||||
"specifications": [],
|
|
||||||
"participants": [
|
|
||||||
"Carlos",
|
|
||||||
"Vanessa",
|
|
||||||
"Christina",
|
|
||||||
"Julian"
|
|
||||||
],
|
|
||||||
"gallery": [
|
|
||||||
"https://i.pinimg.com/736x/7d/5c/29/7d5c29117ef6f974b1a6f77b22408ae7.jpg",
|
|
||||||
"https://i.pinimg.com/1200x/4e/4e/5d/4e4e5d57576d475316f25f84e5afb38f.jpg",
|
|
||||||
"https://i.pinimg.com/webp/1200x/d6/c2/4c/d6c24c1582d944229d271d8948b53dbb.webp",
|
|
||||||
"https://i.pinimg.com/webp/1200x/24/51/8e/24518e6e7bd9a68befcd9a98bba72a23.webp"
|
|
||||||
]
|
|
||||||
}, {
|
|
||||||
"id": 6,
|
|
||||||
"title": "Schwedentorte Schlemmern",
|
|
||||||
"location": "Zürich",
|
|
||||||
"address": "Münsterplatz 10, 8009 Zürich",
|
|
||||||
"date": "9. Mai 2026",
|
|
||||||
"time": "14:00 UHR",
|
|
||||||
"category": "Kaffee + Kuchen",
|
|
||||||
"diet": "Vegan",
|
|
||||||
"spots": 5,
|
|
||||||
"host": {
|
|
||||||
"name": "Annalea",
|
|
||||||
"initial": "A"
|
|
||||||
},
|
|
||||||
"hostMessage": [
|
|
||||||
"Hallo :) Ich suche Personen die Lust haben meine Vegane Schwedentorten Kreation zu probieren. Es ist eine Schwedentorte, die ich mit einer veganen Buttercreme und frischen Früchten zubereite. Es wird ein süsser Genuss, den ihr nicht verpassen solltet!"
|
|
||||||
],
|
|
||||||
"menu": [
|
|
||||||
"Schwedentorte",
|
|
||||||
"Diverse Teesorten"
|
|
||||||
],
|
|
||||||
"specifications": [],
|
|
||||||
"participants": [
|
|
||||||
"Annalea",
|
|
||||||
"Andi",
|
|
||||||
"Leah"
|
|
||||||
],
|
|
||||||
"gallery": [
|
|
||||||
"https://i.pinimg.com/736x/0e/44/78/0e4478e4e3389c77e3e859b2663e6d47.jpg"
|
|
||||||
]
|
|
||||||
}, {
|
|
||||||
"id": 7,
|
|
||||||
"title": "Mexican Fiesta",
|
|
||||||
"location": "Basel",
|
|
||||||
"address": "Münsterplatz 10, 4051 Basel",
|
|
||||||
"date": "29. Mai. 2026",
|
|
||||||
"time": "18:00 UHR",
|
|
||||||
"category": "Dinner",
|
|
||||||
"diet": "Omnivore",
|
|
||||||
"spots": 6,
|
|
||||||
"status": "canceled",
|
|
||||||
"host": {
|
|
||||||
"name": "Carlos",
|
|
||||||
"initial": "C"
|
|
||||||
},
|
|
||||||
"hostMessage": [
|
|
||||||
"Hallo zusammen! Leider muss ich dieses Event absagen, da mir etwas Wichtiges dazwischengekommen ist.",
|
|
||||||
"Ich hoffe, wir können das bald nachholen!"
|
|
||||||
],
|
|
||||||
"menu": [
|
|
||||||
"Guacamole & Nachos",
|
|
||||||
"Tacos al Pastor",
|
|
||||||
"Churros"
|
|
||||||
],
|
|
||||||
"specifications": [],
|
|
||||||
"participants": [
|
|
||||||
"Carlos",
|
|
||||||
"Vivien",
|
|
||||||
"Test"
|
|
||||||
],
|
|
||||||
"gallery": [
|
|
||||||
"https://i.pinimg.com/1200x/e2/6a/f5/e26af5c24b805081a3f304d240818302.jpg"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
107
datenschutz.html
@ -1,107 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="de">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8" />
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
||||||
<title>Invité | Datenschutz</title>
|
|
||||||
<link rel="stylesheet" href="css/stylesheet_global.css" />
|
|
||||||
<script src="js/navigation.js" defer></script>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<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="Invité">
|
|
||||||
</a>
|
|
||||||
<nav class="nav-tab-links" aria-label="Hauptnavigation">
|
|
||||||
<a class="button-small" href="login.html" aria-label="Login">Login</a>
|
|
||||||
</nav>
|
|
||||||
</div>
|
|
||||||
</header>
|
|
||||||
|
|
||||||
<main class="layout-wide">
|
|
||||||
<h1>Datenschutzerklärung</h1>
|
|
||||||
|
|
||||||
<h3>1. Verantwortliche Stelle</h3>
|
|
||||||
<p>
|
|
||||||
Invité GmbH<br>
|
|
||||||
Musterstrasse 12<br>
|
|
||||||
7000 Chur<br>
|
|
||||||
Schweiz<br>
|
|
||||||
E-Mail: datenschutz@invite-cooking.ch
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<h3>2. Erhebung und Verarbeitung personenbezogener Daten</h3>
|
|
||||||
<p>
|
|
||||||
Beim Besuch unserer Website werden automatisch Informationen allgemeiner Natur erfasst.
|
|
||||||
Diese Informationen (Server-Logfiles) beinhalten die Art des Webbrowsers, das verwendete
|
|
||||||
Betriebssystem, den Domainnamen Ihres Internet-Service-Providers, Ihre IP-Adresse und
|
|
||||||
Ähnliches. Sie werden ausschliesslich zur technischen Bereitstellung und Verbesserung
|
|
||||||
unserer Website verwendet.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<h3>3. Registrierung und Nutzerkonto</h3>
|
|
||||||
<p>
|
|
||||||
Bei der Erstellung eines Nutzerkontos erheben wir folgende Daten: Name, E-Mail-Adresse
|
|
||||||
und Passwort. Diese Daten werden ausschliesslich zur Bereitstellung unserer Dienste
|
|
||||||
verwendet und nicht an Dritte weitergegeben.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<h3>4. Cookies</h3>
|
|
||||||
<p>
|
|
||||||
Unsere Website verwendet Cookies, um die Nutzererfahrung zu verbessern. Cookies sind
|
|
||||||
kleine Textdateien, die auf Ihrem Endgerät gespeichert werden. Sie können die Verwendung
|
|
||||||
von Cookies in Ihren Browsereinstellungen deaktivieren. Bitte beachten Sie, dass dadurch
|
|
||||||
die Funktionalität der Website eingeschränkt sein kann.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<h3>5. Datenweitergabe an Dritte</h3>
|
|
||||||
<p>
|
|
||||||
Eine Übermittlung Ihrer persönlichen Daten an Dritte findet nicht statt, es sei denn,
|
|
||||||
wir sind gesetzlich dazu verpflichtet oder Sie haben Ihre ausdrückliche Einwilligung
|
|
||||||
erteilt.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<h3>6. Datensicherheit</h3>
|
|
||||||
<p>
|
|
||||||
Wir setzen technische und organisatorische Sicherheitsmassnahmen ein, um Ihre Daten
|
|
||||||
gegen zufällige oder vorsätzliche Manipulation, Verlust, Zerstörung oder den Zugriff
|
|
||||||
unberechtigter Personen zu schützen. Unsere Sicherheitsmassnahmen werden entsprechend
|
|
||||||
der technologischen Entwicklung fortlaufend verbessert.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<h3>7. Ihre Rechte</h3>
|
|
||||||
<p>
|
|
||||||
Sie haben jederzeit das Recht auf Auskunft über die bei uns gespeicherten
|
|
||||||
personenbezogenen Daten. Ebenso haben Sie das Recht auf Berichtigung, Löschung
|
|
||||||
oder Einschränkung der Verarbeitung Ihrer Daten. Bitte wenden Sie sich dazu an:
|
|
||||||
datenschutz@invite-cooking.ch
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<h3>8. Änderungen dieser Datenschutzerklärung</h3>
|
|
||||||
<p>
|
|
||||||
Wir behalten uns vor, diese Datenschutzerklärung gelegentlich anzupassen, damit sie
|
|
||||||
stets den aktuellen rechtlichen Anforderungen entspricht oder um Änderungen unserer
|
|
||||||
Leistungen umzusetzen. Für Ihren erneuten Besuch gilt dann die neue Datenschutzerklärung.
|
|
||||||
</p>
|
|
||||||
</main>
|
|
||||||
|
|
||||||
<div class="footer">
|
|
||||||
<div class="footer-left">
|
|
||||||
<p class="p-small inline">© <img src="assets/logo_invite.svg" alt="Invité Logo" class="footer-invite_logo" /></p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="footer-center">
|
|
||||||
<a href="https://www.instagram.com" target="_blank" rel="noopener noreferrer" class="instagram-invite__link">
|
|
||||||
<img src="assets/Icon_instagram.png" alt="Instagram" class="instagram-invite_icon" />
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="footer-right footer-links">
|
|
||||||
<a href="impressum.html" class="link-text-footer">Impressum</a>
|
|
||||||
<a href="datenschutz.html" class="link-text-footer">Datenschutz</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@ -3,22 +3,16 @@
|
|||||||
<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>Invité | Event erstellen</title>
|
<title>Event erstellen | Invité</title>
|
||||||
|
|
||||||
<!-- Stylesheet für diese Seite-->
|
<!-- Stylesheet für diese Seite-->
|
||||||
<link rel="stylesheet" href="css/event_create.css" />
|
<link rel="stylesheet" href="css/event_create.css" />
|
||||||
<script src="js/navigation.js" defer></script>
|
|
||||||
|
|
||||||
<!-- Globales Stylesheet -->
|
<!-- Globales Stylesheet -->
|
||||||
<link rel="stylesheet" href="css/stylesheet_global.css">
|
<link rel="stylesheet" href="css/stylesheet_global.css">
|
||||||
|
<script src="js/navigation.js" defer></script>
|
||||||
<!-- Font Awesome Icons -->
|
|
||||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.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">
|
||||||
@ -32,7 +26,7 @@
|
|||||||
</header>
|
</header>
|
||||||
|
|
||||||
|
|
||||||
<main class="event-create-page layout-narrow">
|
<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>
|
||||||
|
|
||||||
@ -42,73 +36,69 @@
|
|||||||
data-step="0"
|
data-step="0"
|
||||||
aria-labelledby="intro-title"
|
aria-labelledby="intro-title"
|
||||||
>
|
>
|
||||||
<div class="step-layout hero startseite">
|
<div class="step-layout step-layout--intro">
|
||||||
<div>
|
<div class="step-copy">
|
||||||
<p class="badge margin-bottom-40">Event erstellen</p>
|
<p class="step-kicker">Event erstellen</p>
|
||||||
<h1 id="intro-title">Hey <span id="username">{{username}}</span>, was hast du vor?</h1>
|
<h1 id="intro-title">Hey <span id="username">{{username}}</span>, was hast du vor?</h1>
|
||||||
<p class="step-text margin-bottom-40">
|
<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 in sieben Schritten aufzubauen.
|
oder etwas ganz Eigenes – wir helfen dir dabei, dein Event in sieben Schritten aufzubauen.
|
||||||
</p>
|
</p>
|
||||||
<button type="button" class="button-primary" data-start-flow>
|
<button type="button" class="button button--primary button--intro" data-start-flow>
|
||||||
Los geht’s!
|
Los geht’s!
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="hero__right" aria-label="Stimmungsbild zur Event-Erstellung">
|
<aside class="intro-card intro-card--image" aria-label="Stimmungsbild zur Event-Erstellung">
|
||||||
<img
|
<img
|
||||||
class="intro-image"
|
class="intro-image"
|
||||||
src="assets/eventcreate_foodtable-new.jpg"
|
src="assets/eventcreate_foodtable.jpg"
|
||||||
alt="Ein gedeckter Tisch mit gemeinsamem Essen"
|
alt="Ein gedeckter Tisch mit gemeinsamem Essen"
|
||||||
/>
|
/>
|
||||||
</div>
|
</aside>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section class="step" data-step="1" aria-labelledby="step1-title">
|
<section class="step" data-step="1" aria-labelledby="step1-title">
|
||||||
<div class="step-layout">
|
<div class="step-layout">
|
||||||
<div class="step-copy">
|
<div class="step-copy">
|
||||||
<p class="badge">Schritt 1</p>
|
<p class="step-kicker">Schritt 1</p>
|
||||||
<h2 id="step1-title">Was hast du vor?</h2>
|
<h2 id="step1-title">Was hast du vor?</h2>
|
||||||
<p class="step-text margin-bottom-40">
|
<p class="step-text">
|
||||||
Erzähl uns, was für ein Event du planst. Ist es ein gemütlicher Brunch,
|
Erzähl uns, was für ein Event du planst. Ist es ein gemütlicher Brunch,
|
||||||
ein Dinner mit Wow-Effekt oder einfach ein entspanntes Mittagessen mit gutem Essen?
|
ein Dinner mit Wow-Effekt oder einfach ein entspannter Abend mit gutem Essen?
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="step-fields">
|
<div class="step-fields">
|
||||||
<fieldset class="form-field">
|
<fieldset class="form-field">
|
||||||
<label>Art des Essens / Eventtyp</label>
|
<legend>Art des Essens / Eventtyp</legend>
|
||||||
|
|
||||||
<div class="option-grid option-grid--4 option-grid--event-type option-grid--icon-choices option-grid--tomato-choices">
|
<div class="option-grid option-grid--4">
|
||||||
<label class="option-card option option-card--with-icon">
|
<label class="option-card">
|
||||||
<input type="radio" name="eventType" value="Brunch" required />
|
<input type="radio" name="eventType" value="Brunch" required />
|
||||||
<i class="fa-solid fa-bread-slice option-card__icon" aria-hidden="true"></i>
|
|
||||||
<span>Brunch</span>
|
<span>Brunch</span>
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
<label class="option-card option option-card--with-icon">
|
<label class="option-card">
|
||||||
<input type="radio" name="eventType" value="Lunch" />
|
<input type="radio" name="eventType" value="Lunch" />
|
||||||
<i class="fa-solid fa-pizza-slice option-card__icon" aria-hidden="true"></i>
|
|
||||||
<span>Lunch</span>
|
<span>Lunch</span>
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
<label class="option-card option option-card--with-icon">
|
<label class="option-card">
|
||||||
<input type="radio" name="eventType" value="Kaffee + Kuchen" />
|
<input type="radio" name="eventType" value="Kaffee + Kuchen" />
|
||||||
<i class="fa-solid fa-mug-hot option-card__icon" aria-hidden="true"></i>
|
|
||||||
<span>Kaffee + Kuchen</span>
|
<span>Kaffee + Kuchen</span>
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
<label class="option-card option option-card--with-icon">
|
<label class="option-card">
|
||||||
<input type="radio" name="eventType" value="Dinner" />
|
<input type="radio" name="eventType" value="Dinner" />
|
||||||
<i class="fa-solid fa-martini-glass option-card__icon" aria-hidden="true"></i>
|
|
||||||
<span>Dinner</span>
|
<span>Dinner</span>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
|
|
||||||
<fieldset class="form-field">
|
<fieldset class="form-field">
|
||||||
<label>Maximale Personenanzahl</label>
|
<legend>Maximale Personenanzahl</legend>
|
||||||
|
|
||||||
<div class="counter" data-counter>
|
<div class="counter" data-counter>
|
||||||
<button
|
<button
|
||||||
@ -119,18 +109,15 @@
|
|||||||
>
|
>
|
||||||
−
|
−
|
||||||
</button>
|
</button>
|
||||||
<div class="counter-value-group">
|
<input
|
||||||
<i class="fa-solid fa-user-group guest-count-icon" aria-hidden="true"></i>
|
type="number"
|
||||||
<input
|
id="maxGuests"
|
||||||
type="number"
|
name="maxGuests"
|
||||||
id="maxGuests"
|
min="1"
|
||||||
name="maxGuests"
|
step="1"
|
||||||
min="1"
|
value="4"
|
||||||
step="1"
|
required
|
||||||
value="0"
|
/>
|
||||||
required
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
class="counter-button"
|
class="counter-button"
|
||||||
@ -148,39 +135,31 @@
|
|||||||
<section class="step" data-step="2" aria-labelledby="step2-title">
|
<section class="step" data-step="2" aria-labelledby="step2-title">
|
||||||
<div class="step-layout">
|
<div class="step-layout">
|
||||||
<div class="step-copy">
|
<div class="step-copy">
|
||||||
<p class="badge">Schritt 2</p>
|
<p class="step-kicker">Schritt 2</p>
|
||||||
<h2 id="step2-title">Was kommt auf den Tisch?</h2>
|
<h2 id="step2-title">Was kommt auf den Tisch?</h2>
|
||||||
<p class="step-text margin-bottom-40">
|
<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.
|
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>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="step-fields">
|
<div class="step-fields">
|
||||||
<fieldset class="form-field">
|
<fieldset class="form-field">
|
||||||
<label>Ernährungsform</label>
|
<legend>Ernährungsform</legend>
|
||||||
|
|
||||||
<div class="option-grid option-grid--4 option-grid--icon-choices option-grid--tomato-choices">
|
<div class="option-grid option-grid--3">
|
||||||
<label class="option-card option option-card--with-icon">
|
<label class="option-card">
|
||||||
<input type="checkbox" name="dietType" value="Fleisch" />
|
<input type="radio" name="dietType" value="Omnivor" required />
|
||||||
<i class="fa-solid fa-drumstick-bite option-card__icon" aria-hidden="true"></i>
|
<span>Omnivor</span>
|
||||||
<span>Fleisch</span>
|
<small>Fleisch und/oder Fisch</small>
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
<label class="option-card option option-card--with-icon">
|
<label class="option-card">
|
||||||
<input type="checkbox" name="dietType" value="Fisch" />
|
<input type="radio" name="dietType" value="Vegetarisch" />
|
||||||
<i class="fa-solid fa-fish option-card__icon" aria-hidden="true"></i>
|
|
||||||
<span>Fisch</span>
|
|
||||||
</label>
|
|
||||||
|
|
||||||
<label class="option-card option option-card--with-icon">
|
|
||||||
<input type="checkbox" name="dietType" value="Vegetarisch" />
|
|
||||||
<i class="fa-solid fa-seedling option-card__icon" aria-hidden="true"></i>
|
|
||||||
<span>Vegetarisch</span>
|
<span>Vegetarisch</span>
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
<label class="option-card option option-card--with-icon">
|
<label class="option-card">
|
||||||
<input type="checkbox" name="dietType" value="Vegan" />
|
<input type="radio" name="dietType" value="Vegan" />
|
||||||
<i class="fa-solid fa-leaf option-card__icon" aria-hidden="true"></i>
|
|
||||||
<span>Vegan</span>
|
<span>Vegan</span>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
@ -197,39 +176,38 @@
|
|||||||
<section class="step" data-step="3" aria-labelledby="step3-title">
|
<section class="step" data-step="3" aria-labelledby="step3-title">
|
||||||
<div class="step-layout">
|
<div class="step-layout">
|
||||||
<div class="step-copy">
|
<div class="step-copy">
|
||||||
<p class="badge">Schritt 3</p>
|
<p class="step-kicker">Schritt 3</p>
|
||||||
<h2 id="step3-title">Gibt es etwas zu beachten?</h2>
|
<h2 id="step3-title">Gibt es etwas zu beachten?</h2>
|
||||||
<p class="step-text margin-bottom-40">
|
<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.
|
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>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="step-fields">
|
<div class="step-fields">
|
||||||
<fieldset class="form-field">
|
<fieldset class="form-field">
|
||||||
<label>Allergene / Unverträglichkeiten</label>
|
<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>
|
||||||
|
|
||||||
<div class="option-grid option-grid--3 option-grid--tomato-choices">
|
<div class="option-grid option-grid--3">
|
||||||
<label class="option-card option">
|
<label class="option-card option-card--checkbox">
|
||||||
<input type="checkbox" name="allergies" value="glutenfrei" />
|
<input type="checkbox" name="allergies" value="glutenfrei" />
|
||||||
<span>Glutenfrei</span>
|
<span>glutenfrei</span>
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
<label class="option-card option">
|
<label class="option-card option-card--checkbox">
|
||||||
<input type="checkbox" name="allergies" value="laktosefrei" />
|
<input type="checkbox" name="allergies" value="laktosefrei" />
|
||||||
<span>Laktosefrei</span>
|
<span>laktosefrei</span>
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
<label class="option-card option">
|
<label class="option-card option-card--checkbox">
|
||||||
<input type="checkbox" name="allergies" value="ohne Nüsse" />
|
<input type="checkbox" name="allergies" value="ohne Nüsse" />
|
||||||
<span>Ohne Nüsse</span>
|
<span>ohne Nüsse</span>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
|
|
||||||
<div class="form-field">
|
<div class="form-field">
|
||||||
<label for="allergiesOther">Weitere Unverträglichkeiten oder Hinweise</label>
|
<label for="allergiesOther">Weitere Unverträglichkeiten oder Hinweise (optional)</label>
|
||||||
<p class="field-hint">Optional – nur auswählen, wenn es für dein Event relevant ist.</p>
|
|
||||||
<textarea id="allergiesOther" name="allergiesOther" rows="3"></textarea>
|
<textarea id="allergiesOther" name="allergiesOther" rows="3"></textarea>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -239,9 +217,9 @@
|
|||||||
<section class="step" data-step="4" aria-labelledby="step4-title">
|
<section class="step" data-step="4" aria-labelledby="step4-title">
|
||||||
<div class="step-layout">
|
<div class="step-layout">
|
||||||
<div class="step-copy">
|
<div class="step-copy">
|
||||||
<p class="badge">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 margin-bottom-40">
|
<p class="step-text">
|
||||||
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.
|
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.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@ -265,9 +243,9 @@
|
|||||||
<section class="step" data-step="5" aria-labelledby="step5-title">
|
<section class="step" data-step="5" aria-labelledby="step5-title">
|
||||||
<div class="step-layout">
|
<div class="step-layout">
|
||||||
<div class="step-copy">
|
<div class="step-copy">
|
||||||
<p class="badge">Schritt 5</p>
|
<p class="step-kicker">Schritt 5</p>
|
||||||
<h2 id="step5-title">Wo findet dein Event statt?</h2>
|
<h2 id="step5-title">Wo findet dein Event statt?</h2>
|
||||||
<p class="step-text margin-bottom-40">
|
<p class="step-text">
|
||||||
Sag uns, wo dein Event stattfindet. Keine Sorge: Die genaue Adresse sehen Gäste erst nach der Buchung.
|
Sag uns, wo dein Event stattfindet. Keine Sorge: Die genaue Adresse sehen Gäste erst nach der Buchung.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@ -289,9 +267,9 @@
|
|||||||
<section class="step" data-step="6" aria-labelledby="step6-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="badge">Schritt 6</p>
|
<p class="step-kicker">Schritt 6</p>
|
||||||
<h2 id="step6-title">Gib deinem Event den letzten Schliff.</h2>
|
<h2 id="step6-title">Gib deinem Event den letzten Schliff.</h2>
|
||||||
<p class="step-text margin-bottom-40">
|
<p class="step-text">
|
||||||
Jetzt bekommt dein Event seinen Namen und die Atmosphäre, die Lust aufs Dabeisein macht.
|
Jetzt bekommt dein Event seinen Namen und die Atmosphäre, die Lust aufs Dabeisein macht.
|
||||||
Ein klarer Titel (z.B. "Italienische Tavolata") und ein guter Beschreibungstext (Ablauf etc.) machen den Unterschied.
|
Ein klarer Titel (z.B. "Italienische Tavolata") und ein guter Beschreibungstext (Ablauf etc.) machen den Unterschied.
|
||||||
</p>
|
</p>
|
||||||
@ -299,27 +277,14 @@
|
|||||||
|
|
||||||
<div class="step-fields">
|
<div class="step-fields">
|
||||||
<div class="form-field">
|
<div class="form-field">
|
||||||
<label for="eventTitle">Wie soll dein Event heissen?</label>
|
<label for="eventTitle">Wie soll dein Event heißen?</label>
|
||||||
<input type="text" id="eventTitle" name="eventTitle" required />
|
<input type="text" id="eventTitle" name="eventTitle" required />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-field">
|
<div class="form-field">
|
||||||
<label for="eventDescription">Beschreibung des Events</label>
|
<label for="eventDescription">Beschreibung des Event-Abends</label>
|
||||||
<textarea id="eventDescription" name="eventDescription" rows="6" required></textarea>
|
<textarea id="eventDescription" name="eventDescription" rows="6" required></textarea>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-field">
|
|
||||||
<label>Wie wird dein Event aussehen?</label>
|
|
||||||
<p class="field-hint">Optional – füge Bilder zu deinem Event hinzu.</p>
|
|
||||||
<div class= "option-grid option-grid--4">
|
|
||||||
<div class="gallery-preview" id="galleryPreview"></div>
|
|
||||||
<button type="button" class="gallery-add-button" id="galleryAddBtn" aria-label="Foto hinzufügen">
|
|
||||||
<i class="fa-solid fa-plus gallery-add-button__icon" aria-hidden="true"></i>
|
|
||||||
<span class="option">Foto hinzufügen</span>
|
|
||||||
</button>
|
|
||||||
<input type="file" id="galleryFileInput" accept="image/*" multiple hidden />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
@ -327,9 +292,9 @@
|
|||||||
<section class="step" data-step="7" aria-labelledby="step7-title">
|
<section class="step" data-step="7" aria-labelledby="step7-title">
|
||||||
<div class="step-layout">
|
<div class="step-layout">
|
||||||
<div class="step-copy">
|
<div class="step-copy">
|
||||||
<p class="badge">Schritt 7</p>
|
<p class="step-kicker">Schritt 7</p>
|
||||||
<h2 id="step7-title">Dein Event auf einen Blick</h2>
|
<h2 id="step7-title">Dein Event auf einen Blick.</h2>
|
||||||
<p class="step-text margin-bottom-40">
|
<p class="step-text">
|
||||||
Schau dir alle Details nochmal in Ruhe an. Wenn alles passt,
|
Schau dir alle Details nochmal in Ruhe an. Wenn alles passt,
|
||||||
kannst du dein Event jetzt veröffentlichen und Gäste einladen.
|
kannst du dein Event jetzt veröffentlichen und Gäste einladen.
|
||||||
</p>
|
</p>
|
||||||
@ -391,49 +356,30 @@
|
|||||||
<dt>Event-Abend</dt>
|
<dt>Event-Abend</dt>
|
||||||
<dd data-review="eventDescription">–</dd>
|
<dd data-review="eventDescription">–</dd>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="review-item" data-edit-step="6" data-edit-field="galleryAddBtn" role="button" tabindex="0" aria-label="Fotos bearbeiten">
|
|
||||||
<dt>Fotos</dt>
|
|
||||||
<dd>
|
|
||||||
<div class="review-gallery" data-review-gallery>
|
|
||||||
<span>Keine Fotos hinzugefügt</span>
|
|
||||||
</div>
|
|
||||||
</dd>
|
|
||||||
</div>
|
|
||||||
</dl>
|
</dl>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
<div class="flow-footer" id="flowFooter" hidden>
|
||||||
|
<div class="progress-wrap" aria-hidden="true">
|
||||||
|
<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 class="flow-footer" id="flowFooter" hidden>
|
<div class="flow-actions">
|
||||||
<div class="flow-actions">
|
<button type="button" id="backButton" class="button button--text">Zurück</button>
|
||||||
<button type="button" id="backButton" class="button-secondary">
|
|
||||||
Zurück
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<div class="progress-wrap">
|
<div class="flow-actions-right">
|
||||||
<span class="progress-label" id="progressMarkerLabel">
|
<p id="errorMessage" class="error-message" role="alert" aria-live="assertive"></p>
|
||||||
Schritt 1 von 7
|
<button type="button" id="nextButton" class="button button--primary">Weiter</button>
|
||||||
</span>
|
</div>
|
||||||
<div class="progress">
|
</div>
|
||||||
<span id="progressBar" class="progress-bar"></span>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="flow-actions-right">
|
|
||||||
<p
|
|
||||||
id="errorMessage"
|
|
||||||
class="error-message error-message--callout"
|
|
||||||
role="alert"
|
|
||||||
aria-live="assertive"
|
|
||||||
></p>
|
|
||||||
<button type="button" id="nextButton" class="button-primary">
|
|
||||||
Weiter
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<section
|
<section
|
||||||
id="submissionSuccess"
|
id="submissionSuccess"
|
||||||
@ -442,51 +388,29 @@
|
|||||||
aria-live="polite"
|
aria-live="polite"
|
||||||
hidden
|
hidden
|
||||||
>
|
>
|
||||||
<div class="step-layout hero startseite">
|
<div class="step-layout">
|
||||||
<div class="step-copy">
|
<div class="step-copy">
|
||||||
<div class="submission-success-title-row">
|
<p class="step-kicker">Event erstellt</p>
|
||||||
<h2 id="success-title">Dein Event ist ready </h2>
|
<h2 id="success-title">Dein Event ist ready.</h2>
|
||||||
<span class="submission-success-icon" aria-hidden="true">
|
<p class="step-text">
|
||||||
<!-- <i class="fa-solid fa-champagne-glasses"></i>-->
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<p class="step-text margin-bottom-40">
|
|
||||||
Sieht gut aus: Deine Idee ist jetzt live und bereit für Gäste.
|
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.
|
Im Profil kannst du dein Event anschauen, verwalten oder direkt das nächste planen.
|
||||||
</p>
|
</p>
|
||||||
<div class="submission-success-actions">
|
|
||||||
<a class="button-primary button--intro" href="my_profil.html">Weiter zu deinem Profil</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="hero__right" aria-label="Stimmungsbild zur Event-Erstellung">
|
<div class="review-card review-card--success">
|
||||||
<img
|
<div class="submission-success-actions">
|
||||||
class="intro-image"
|
<a class="button button--primary" href="event_overview.html">Weiter zu deinem Profil</a>
|
||||||
src="assets/eventcreate_foodtable with friends.jpg"
|
</div>
|
||||||
alt="Gemeinsames Essen an einem gedeckten Tisch"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
</form>
|
</form>
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
<div class="footer">
|
<footer class="site-footer">
|
||||||
<div class="footer-left">
|
<p>© Social Cooking</p>
|
||||||
<p class="p-small inline">© <img src="assets/logo_invite.svg" alt="Invité Logo" class="footer-invite_logo" /></p>
|
</footer>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="footer-center">
|
|
||||||
<a href="https://www.instagram.com" target="_blank" rel="noopener noreferrer" class="instagram-invite__link">
|
|
||||||
<img src="assets/Icon_instagram.png" alt="Instagram" class="instagram-invite_icon" />
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="footer-right footer-links">
|
|
||||||
<a href="impressum.html" class="link-text-footer">Impressum</a>
|
|
||||||
<a href="datenschutz.html" class="link-text-footer">Datenschutz</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<script src="js/event_create.js"></script>
|
<script src="js/event_create.js"></script>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
@ -3,15 +3,13 @@
|
|||||||
<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>Invité | Event-Detail</title>
|
<title>Event-Detail</title>
|
||||||
|
|
||||||
<!-- Stylesheet für diese Seite-->
|
<!-- Stylesheet für diese Seite-->
|
||||||
<link rel="stylesheet" href="css/event_overview.css">
|
<link rel="stylesheet" href="css/event_overview.css">
|
||||||
<script src="js/navigation.js" defer></script>
|
|
||||||
|
|
||||||
<!-- Globales Stylesheet -->
|
<!-- Globales Stylesheet -->
|
||||||
<link rel="stylesheet" href="css/stylesheet_global.css">
|
<link rel="stylesheet" href="css/stylesheet_global.css">
|
||||||
|
<script src="js/navigation.js" defer></script>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
@ -28,7 +26,7 @@
|
|||||||
</header>
|
</header>
|
||||||
|
|
||||||
<!-- Main content: detail page gets fully injected by JavaScript -->
|
<!-- Main content: detail page gets fully injected by JavaScript -->
|
||||||
<main class="container layout-wide">
|
<main class="container">
|
||||||
<!-- Render target: loading, error state or full detail layout -->
|
<!-- Render target: loading, error state or full detail layout -->
|
||||||
<div id="detail-view">
|
<div id="detail-view">
|
||||||
<p>Lädt Event-Details...</p>
|
<p>Lädt Event-Details...</p>
|
||||||
@ -38,58 +36,5 @@
|
|||||||
<!-- Page logic: fetch by URL id, compose detail UI, handle gallery lightbox -->
|
<!-- Page logic: fetch by URL id, compose detail UI, handle gallery lightbox -->
|
||||||
<script src="js/event_detail.js"></script>
|
<script src="js/event_detail.js"></script>
|
||||||
|
|
||||||
<div id="register-confirm-modal" class="modal">
|
|
||||||
<div class="modal-content">
|
|
||||||
<div class="modal-header">
|
|
||||||
<h2>Ein Platz für dich am Tisch!</h2>
|
|
||||||
<button type="button" class="modal-close" aria-label="Popup schließen">×</button>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
<div class="modal-body">
|
|
||||||
<p>Schön, dass du dich dazu gesellen möchtest! Da dein:e Gastgeber:in extra für dich einkauft und mit viel Liebe kocht, ist deine Anmeldung ein festes Versprechen. Bitte sag nur zu, wenn du an dem Tag wirklich Zeit hast. So zeigst du echte Wertschätzung für die Mühe und wir lassen niemanden auf vollen Töpfen sitzen.</p>
|
|
||||||
</div>
|
|
||||||
<div class="modal-footer">
|
|
||||||
<button class="button-primary button--outline" type="button" id="register-modal-cancel">Abbrechen</button>
|
|
||||||
<button class="button-primary" type="button" id="confirm-register-btn">Ja, ich bin dabei</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="unregister-confirm-modal" class="modal">
|
|
||||||
<div class="modal-content">
|
|
||||||
<div class="modal-header">
|
|
||||||
<h2>Pläne haben sich geändert?</h2>
|
|
||||||
<button type="button" class="modal-close" aria-label="Popup schließen">×</button>
|
|
||||||
</div>
|
|
||||||
<div class="modal-body">
|
|
||||||
<p>Schade, dass du nicht dabei sein kannst! Aber manchmal kommt einfach etwas dazwischen. Wenn du dich jetzt abmeldest, gibst du deinen Stuhl am Tisch für jemand anderen aus der Community frei. So hilfst du bei der Planung und ein anderer Feinschmecker freut sich über den freien Platz.</p>
|
|
||||||
</div>
|
|
||||||
<div class="modal-footer">
|
|
||||||
<button class="button-primary button--outline" type="button" id="unregister-modal-cancel">Abbrechen</button>
|
|
||||||
<button class="button-primary button-primary-abmelden" type="button" id="confirm-unregister-btn">Ja, abmelden</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Snackbar: Feedback bei An-/Abmeldung -->
|
|
||||||
<div class="snackbar" id="snackbar"></div>
|
|
||||||
|
|
||||||
<div class="footer">
|
|
||||||
<div class="footer-left">
|
|
||||||
<p class="p-small inline">© <img src="assets/logo_invite.svg" alt="Invité Logo" class="footer-invite_logo" /></p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="footer-center">
|
|
||||||
<a href="https://www.instagram.com" target="_blank" rel="noopener noreferrer" class="instagram-invite__link">
|
|
||||||
<img src="assets/Icon_instagram.png" alt="Instagram" class="instagram-invite_icon" />
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="footer-right footer-links">
|
|
||||||
<a href="impressum.html" class="link-text-footer">Impressum</a>
|
|
||||||
<a href="datenschutz.html" class="link-text-footer">Datenschutz</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
@ -3,14 +3,13 @@
|
|||||||
<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>Invité | Event-Übersicht</title>
|
<title>Event-Overview</title>
|
||||||
|
|
||||||
<!-- Stylesheet für diese Seite-->
|
<!-- Stylesheet für diese Seite-->
|
||||||
<link rel="stylesheet" href="css/event_overview.css">
|
<link rel="stylesheet" href="css/event_overview.css">
|
||||||
<script src="js/navigation.js" defer></script>
|
|
||||||
|
|
||||||
<!-- Globales Stylesheet -->
|
<!-- Globales Stylesheet -->
|
||||||
<link rel="stylesheet" href="css/stylesheet_global.css">
|
<link rel="stylesheet" href="css/stylesheet_global.css">
|
||||||
|
<script src="js/navigation.js" defer></script>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
@ -27,140 +26,45 @@
|
|||||||
</header>
|
</header>
|
||||||
|
|
||||||
<!-- Main content: page headline, filter controls and dynamic event list -->
|
<!-- Main content: page headline, filter controls and dynamic event list -->
|
||||||
<main class="container layout-wide">
|
<main class="container">
|
||||||
<!-- Page headline -->
|
<!-- Page headline -->
|
||||||
<p class="badge margin-bottom-40">Event finden</p>
|
<h1 class="overview-title">Events</h1>
|
||||||
<div class="overview-title-row">
|
|
||||||
<h1 class="overview-title">Was darf es sein?</h1>
|
|
||||||
<button type="button" id="info-button" class="btn-info" aria-label="Informationen zu kostenlosen Events">?</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Filter section: category chips + location/date filters -->
|
<!-- Filter section: category chips + location/date filters -->
|
||||||
<section class="filter-section margin-bottom-24">
|
<section class="filter-section">
|
||||||
<p class="filter-label">Art des Essens / Eventtyp</p>
|
<p class="filter-label">Was darf es sein?</p>
|
||||||
<div class="filter-row margin-bottom-24">
|
<div class="filter-row">
|
||||||
<!-- Primary category filter buttons -->
|
<!-- Primary category filter buttons -->
|
||||||
<div class="category-group">
|
<div class="category-group">
|
||||||
<button class="category-item active" type="button" data-cat="ALLE">Alle</button>
|
<button class="category-item" type="button" data-cat="BRUNCH">BRUNCH</button>
|
||||||
<button class="category-item" type="button" data-cat="Brunch">Brunch</button>
|
<button class="category-item" type="button" data-cat="LUNCH">LUNCH</button>
|
||||||
<button class="category-item" type="button" data-cat="Lunch">Lunch</button>
|
<button class="category-item" type="button" data-cat="DINNER">DINNER</button>
|
||||||
<button class="category-item" type="button" data-cat="Kaffee + Kuchen">Kaffee + Kuchen</button>
|
<button class="category-item" type="button" data-cat="COFFEE">COFFEE</button>
|
||||||
<button class="category-item" type="button" data-cat="Dinner">Dinner</button>
|
<button class="category-item active" type="button" data-cat="ALLE">ALLE</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Secondary filters populated/handled by JavaScript -->
|
<!-- Secondary filters populated/handled by JavaScript -->
|
||||||
<div class="meta-filter-group" aria-label="Weitere Filter">
|
<div class="meta-filter-group" aria-label="Weitere Filter">
|
||||||
<div class="meta-filter" for="location-filter">
|
<label class="meta-filter" for="location-filter">
|
||||||
<span>Ort</span>
|
<span>Ort</span>
|
||||||
<select id="location-filter">
|
<select id="location-filter">
|
||||||
<option value="ALLE_ORTE">Alle Orte</option>
|
<option value="ALLE_ORTE">Alle Orte</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</label>
|
||||||
|
|
||||||
<div class="meta-filter" for="date-filter">
|
<label class="meta-filter" for="date-filter">
|
||||||
<span>Datum</span>
|
<span>Datum</span>
|
||||||
<div class="date-input-wrapper">
|
<input id="date-filter" type="date">
|
||||||
<input id="date-filter" type="date">
|
</label>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<details class="filter-box">
|
</section>
|
||||||
<summary>Nach Ernährungform filtern</summary>
|
|
||||||
<div class="filter-row margin-bottom-16">
|
|
||||||
<div class="category-group">
|
|
||||||
<button class="category-item" type="button" data-diet="Fleisch">Fleisch</button>
|
|
||||||
<button class="category-item" type="button" data-diet="Fisch">Fisch</button>
|
|
||||||
<button class="category-item" type="button" data-diet="Vegetarisch">Vegetarisch</button>
|
|
||||||
<button class="category-item" type="button" data-diet="Vegan">Vegan</button>
|
|
||||||
<button class="category-item filter-delete" type="button" id="clear-all-filters">Alle Filter löschen</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</details>
|
|
||||||
|
|
||||||
<details class="filter-box margin-bottom-24">
|
|
||||||
<summary>Nach Allergenen filtern</summary>
|
|
||||||
<div class="filter-row margin-bottom-16">
|
|
||||||
<div class="category-group">
|
|
||||||
<button class="category-item" type="button" data-allergie="Glutenfrei">Glutenfrei</button>
|
|
||||||
<button class="category-item" type="button" data-allergie="Laktosefrei">Laktosefrei</button>
|
|
||||||
<button class="category-item" type="button" data-allergie="Ohne Nüsse">Ohne Nüsse</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</details>
|
|
||||||
|
|
||||||
<!-- Render target: event cards or empty state -->
|
<!-- Render target: event cards or empty state -->
|
||||||
<section id="event-grid" class="event-list"></section>
|
<section id="event-grid" class="event-list"></section>
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
<!-- Seitenlogik: Daten laden, filtern und Event-Karten rendern -->
|
<!-- Page logic: data loading, filtering and card rendering -->
|
||||||
<script src="js/event_overview.js"></script>
|
<script src="js/event_overview.js"></script>
|
||||||
|
|
||||||
<!-- Info Modal: Kostenlose Events Info -->
|
|
||||||
<div id="info-modal" class="modal">
|
|
||||||
<div class="modal-content">
|
|
||||||
<div class="modal-header">
|
|
||||||
<h2>Warum Invité kostenlos ist</h2>
|
|
||||||
<button type="button" class="modal-close" aria-label="Popup schließen">×</button>
|
|
||||||
</div>
|
|
||||||
<div class="modal-body">
|
|
||||||
<p>Alle Events bei uns sind komplett kostenlos. Invité basiert rein auf Freiwilligkeit und der Freude am Teilen. Kein Geldfluss, keine versteckten Kosten – nur die pure Absicht, die Community zu stärken und den sozialen Zusammenhalt in unserer Nachbarschaft zu fördern. Egal ob du den Kochlöffel schwingst oder dich als Gast dazu gesellst: Bei uns zählt nur die menschliche Begegnung.</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="register-confirm-modal" class="modal">
|
|
||||||
<div class="modal-content">
|
|
||||||
<div class="modal-header">
|
|
||||||
<h2>Ein Platz für dich am Tisch!</h2>
|
|
||||||
<button type="button" class="modal-close" aria-label="Popup schließen">×</button>
|
|
||||||
</div>
|
|
||||||
<div class="modal-body">
|
|
||||||
<p>Schön, dass du dich dazu gesellen möchtest! Da dein:e Gastgeber:in extra für dich einkauft und mit viel Liebe kocht, ist deine Anmeldung ein festes Versprechen. Bitte sag nur zu, wenn du an dem Tag wirklich Zeit hast. So zeigst du echte Wertschätzung für die Mühe und wir lassen niemanden auf vollen Töpfen sitzen.</p>
|
|
||||||
</div>
|
|
||||||
<div class="modal-footer">
|
|
||||||
<button class="button-primary button--outline" type="button" onclick="closeRegisterModal()">Abbrechen</button>
|
|
||||||
<button class="button-primary" type="button" id="confirm-register-btn">Ja, ich bin dabei</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="unregister-confirm-modal" class="modal">
|
|
||||||
<div class="modal-content">
|
|
||||||
<div class="modal-header">
|
|
||||||
<h2>Pläne haben sich geändert?</h2>
|
|
||||||
<button type="button" class="modal-close" aria-label="Popup schließen">×</button>
|
|
||||||
</div>
|
|
||||||
<div class="modal-body">
|
|
||||||
<p>Schade, dass du nicht dabei sein kannst! Aber manchmal kommt einfach etwas dazwischen. Wenn du dich jetzt abmeldest, gibst du deinen Stuhl am Tisch für jemand anderen aus der Community frei. So hilfst du bei der Planung und ein anderer Feinschmecker freut sich über den freien Platz.</p>
|
|
||||||
</div>
|
|
||||||
<div class="modal-footer">
|
|
||||||
<button class="button-primary button--outline" type="button" onclick="closeUnregisterModal()">Abbrechen</button>
|
|
||||||
<button class="button-primary button-primary-abmelden" type="button" id="confirm-unregister-btn">Ja, abmelden</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Snackbar: Feedback bei An-/Abmeldung -->
|
|
||||||
<div class="snackbar" id="snackbar"></div>
|
|
||||||
|
|
||||||
<div class="footer">
|
|
||||||
<div class="footer-left">
|
|
||||||
<p class="p-small inline">© <img src="assets/logo_invite.svg" alt="Invité Logo" class="footer-invite_logo" /></p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="footer-center">
|
|
||||||
<a href="https://www.instagram.com" target="_blank" rel="noopener noreferrer" class="instagram-invite__link">
|
|
||||||
<img src="assets/Icon_instagram.png" alt="Instagram" class="instagram-invite_icon" />
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="footer-right footer-links">
|
|
||||||
<a href="impressum.html" class="link-text-footer">Impressum</a>
|
|
||||||
<a href="datenschutz.html" class="link-text-footer">Datenschutz</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
@ -1,80 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="de">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8" />
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
||||||
<title>Invité | Impressum</title>
|
|
||||||
<link rel="stylesheet" href="css/stylesheet_global.css" />
|
|
||||||
<script src="js/navigation.js" defer></script>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<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="Invité">
|
|
||||||
</a>
|
|
||||||
<nav class="nav-tab-links" aria-label="Hauptnavigation">
|
|
||||||
<a class="button-small" href="login.html" aria-label="Login">Login</a>
|
|
||||||
</nav>
|
|
||||||
</div>
|
|
||||||
</header>
|
|
||||||
|
|
||||||
<main class="layout-wide">
|
|
||||||
<h1>Impressum</h1>
|
|
||||||
|
|
||||||
<h3>Angaben gemäss § 5 TMG</h3>
|
|
||||||
<p>
|
|
||||||
Invité GmbH<br>
|
|
||||||
Musterstrasse 12<br>
|
|
||||||
7000 Chur<br>
|
|
||||||
Schweiz
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<h3>Kontakt</h3>
|
|
||||||
<p>
|
|
||||||
Telefon: +41 81 123 45 67<br>
|
|
||||||
E-Mail: info@invite-cooking.ch
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<h3>Vertretungsberechtigte Person</h3>
|
|
||||||
<p>Max Mustermann, Geschäftsführer</p>
|
|
||||||
|
|
||||||
<h3>Handelsregistereintrag</h3>
|
|
||||||
<p>
|
|
||||||
Eingetragen im Handelsregister des Kantons Graubünden<br>
|
|
||||||
Firmennummer: CHE-123.456.789
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<h3>Haftungsausschluss</h3>
|
|
||||||
<p>
|
|
||||||
Die Inhalte dieser Website wurden mit grösster Sorgfalt erstellt. Für die Richtigkeit,
|
|
||||||
Vollständigkeit und Aktualität der Inhalte können wir jedoch keine Gewähr übernehmen.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<h3>Urheberrecht</h3>
|
|
||||||
<p>
|
|
||||||
Die durch die Seitenbetreiber erstellten Inhalte und Werke auf diesen Seiten unterliegen
|
|
||||||
dem schweizerischen Urheberrecht. Die Vervielfältigung, Bearbeitung, Verbreitung und jede
|
|
||||||
Art der Verwertung ausserhalb der Grenzen des Urheberrechtes bedürfen der schriftlichen
|
|
||||||
Zustimmung des jeweiligen Autors bzw. Erstellers.
|
|
||||||
</p>
|
|
||||||
</main>
|
|
||||||
|
|
||||||
<div class="footer">
|
|
||||||
<div class="footer-left">
|
|
||||||
<p class="p-small inline">© <img src="assets/logo_invite.svg" alt="Invité Logo" class="footer-invite_logo" /></p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="footer-center">
|
|
||||||
<a href="https://www.instagram.com" target="_blank" rel="noopener noreferrer" class="instagram-invite__link">
|
|
||||||
<img src="assets/Icon_instagram.png" alt="Instagram" class="instagram-invite_icon" />
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="footer-right footer-links">
|
|
||||||
<a href="impressum.html" class="link-text-footer">Impressum</a>
|
|
||||||
<a href="datenschutz.html" class="link-text-footer">Datenschutz</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
352
index.html
@ -3,17 +3,16 @@
|
|||||||
<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>Invité</title>
|
<title>Invité | Events entdecken</title>
|
||||||
|
|
||||||
<!-- Stylesheet für diese Seite-->
|
<!-- Stylesheet für diese Seite-->
|
||||||
<link rel="stylesheet" href="css/index.css">
|
<link rel="stylesheet" href="css/landingpage.css?v=2" />
|
||||||
|
|
||||||
<!-- Globales Stylesheet -->
|
<!-- Globales Stylesheet -->
|
||||||
<link rel="stylesheet" href="css/stylesheet_global.css">
|
<link rel="stylesheet" href="css/stylesheet_global.css?v=2">
|
||||||
|
|
||||||
<!-- Font Awesome Icons -->
|
|
||||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
|
|
||||||
<script src="js/navigation.js" defer></script>
|
<script src="js/navigation.js" defer></script>
|
||||||
|
|
||||||
|
<!-- 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>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
@ -21,338 +20,115 @@
|
|||||||
<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/logo_invite.svg" alt="Invité">
|
<img src="assets/logo_invite.svg" alt="Invite Logo">
|
||||||
</a>
|
</a>
|
||||||
<nav class="nav-tab-links" aria-label="Hauptnavigation">
|
<nav class="nav-tab-links" aria-label="Hauptnavigation">
|
||||||
<a class="button-small" href="login.html" aria-label="Login">Login</a>
|
<a class="button-small auth-nav-button auth-nav-button--default" href="login.html" aria-label="Login">Login</a>
|
||||||
|
<a class="button-small auth-nav-button auth-nav-button--default" href="signup.html" aria-label="Signup">Signup</a>
|
||||||
</nav>
|
</nav>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<main class="container layout-wide">
|
<main class="main-content">
|
||||||
<!-- Hero: uses .hero, .btn, .image-card, and .hero-image for a polished first impression -->
|
<!-- Hero: uses .hero, .btn, .image-card, and .hero-image for a polished first impression -->
|
||||||
<section class="hero">
|
<section class="hero">
|
||||||
<div class="hero__left">
|
<div class="hero__left">
|
||||||
<span class="badge margin-bottom-40">einfach. lecker. gemeinsam.</span>
|
<h1>Dein Platz am Tisch wartet schon.</h1>
|
||||||
<h1>Teile deine Leidenschaft, geniesse gemeinsam.</h1>
|
<p>Egal, ob du leidenschaftlich gerne den Kochlöffel schwingst oder dich einfach auf ein hausgemachtes Essen in guter Gesellschaft freust: Bei Invité bringst du Menschen zusammen. Finde Events, die zu deinem Geschmack passen, und geniesse unkomplizierte Begegnungen ohne Networking-Zwang.</p>
|
||||||
<p class="margin-bottom-40">Ob du als leidenschaftlicher Hobbykoch Gastgeber sein möchtest oder als Feinschmecker einen Platz an einem lokalen Tisch suchst Invité verbindet Menschen durch die Kraft einer gemeinsamen Mahlzeit.</p>
|
<a class="btn" href="login.html">Anmelden</a>
|
||||||
<a class="button-primary" href="signup.html">Registrieren</a>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="hero__right">
|
<div class="hero__right">
|
||||||
<div class="image-card">
|
<div class="image-card">
|
||||||
<img class="hero-image" src="assets/index_round table friends.jpeg" alt="Round table friends" />
|
<img class="hero-image" src="assets/Startpage ingredients.jpg" alt="Startpage Ingredients" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section class="how-it-works">
|
<section class="how-it-works">
|
||||||
|
<div class="how-it-works__header">
|
||||||
<h2>So funktioniert's</h2>
|
<h2>So funktioniert's</h2>
|
||||||
|
</div>
|
||||||
<div class="how-it-works__steps">
|
<div class="how-it-works__steps">
|
||||||
<article class="how-step how-step-number-numbered">
|
<article class="how-step how-step--numbered">
|
||||||
<span class="how-step_corner-number">1</span>
|
<span class="how-step__corner-number how-step__corner-number--brown">1</span>
|
||||||
<i class="fa-solid fa-id-card how-step_icon"></i>
|
<img src="assets/register icon.png" alt="Register" class="how-step__png how-step__png--brown" />
|
||||||
<h3 class="how-step_text">Anmelden und Dabeisein</h3>
|
<p class="how-step__label how-step__label--brown how-step__label--big">Anmelden</p>
|
||||||
<p class="how-step_text text-left">Erstelle kurz dein Profil und zeig uns deinen Geschmack. Bei uns zählt der Mensch am Tisch, nicht der Lebenslauf.</p>
|
|
||||||
<div class="badge margin-bottom-24">
|
|
||||||
<span>Quick Setup in 2 Min</span>
|
|
||||||
</div>
|
|
||||||
</article>
|
</article>
|
||||||
<article class="how-step how-step-number-numbered">
|
<article class="how-step how-step--numbered">
|
||||||
<span class="how-step_corner-number">2</span>
|
<span class="how-step__corner-number how-step__corner-number--brown">2</span>
|
||||||
<i class="fa-solid fa-magnifying-glass-location how-step_icon"></i>
|
<img src="assets/add-event icon.png" alt="Event erstellen" class="how-step__png how-step__png--brown" />
|
||||||
<h3 class="how-step_text">Tisch finden oder decken</h3>
|
<p class="how-step__label how-step__label--brown how-step__label--big">Event erstellen</p>
|
||||||
<p class="how-step_text text-left">Entdecke spontane Events in deiner Nähe oder öffne deine eigene Küche. Du entscheidest, ob du Gast oder Gastgeber:in bist.</p>
|
|
||||||
<div class="badge margin-bottom-24">
|
|
||||||
<span>Gast – Gastgeber:in</span>
|
|
||||||
</div>
|
|
||||||
</article>
|
</article>
|
||||||
<article class="how-step how-step-number-numbered">
|
<article class="how-step how-step--numbered">
|
||||||
<span class="how-step_corner-number">3</span>
|
<span class="how-step__corner-number how-step__corner-number--brown">3</span>
|
||||||
|
<img src="assets/Plate icon.png" alt="Gemeinsam essen" class="how-step__png how-step__png--brown" />
|
||||||
<i class="fa-solid fa-utensils how-step_icon"></i>
|
<p class="how-step__label how-step__label--brown how-step__label--big">Gemeinsam essen</p>
|
||||||
<h3 class="how-step_text">Teile den Tisch</h3>
|
|
||||||
<p class="how-step_text text-left">Triff neue Leute in entspannter Atmosphäre. Geniesse gutes Essen in Gesellschaft und mach aus einer Mahlzeit eine echte Begegnung.</p>
|
|
||||||
<div class="badge margin-bottom-24">
|
|
||||||
<span>Gemeinsam geniessen</span>
|
|
||||||
</div>
|
|
||||||
</article>
|
</article>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<!-- Main Content: uses .gallery, .gallery__carousel, .gallery__track, .gallery__item, and .gallery__info to present event carousel content -->
|
<!-- Main Content: uses .gallery, .gallery__carousel, .gallery__track, .gallery__item, and .gallery__info to present event carousel content -->
|
||||||
<section class="gallery" aria-label="Bildergalerie" aria-roledescription="Karussell">
|
<section class="gallery">
|
||||||
</div><h2>Einblick in Cooking-Erlebnisse</h2>
|
|
||||||
<p class="margin-bottom-16">#gemeinsam_invité auf Instagram</p>
|
|
||||||
</div>
|
|
||||||
<div class="gallery__carousel">
|
<div class="gallery__carousel">
|
||||||
<button type="button" class="gallery__arrow gallery__arrow--prev" aria-label="Vorheriges Bild">
|
<button type="button" class="gallery__arrow gallery__arrow--prev" aria-label="Vorheriges Bild">
|
||||||
<i class="fas fa-chevron-left"></i>
|
<i class="fas fa-chevron-left"></i>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<div class="gallery__track" aria-live="polite">
|
<div class="gallery__track">
|
||||||
|
<article class="gallery__item">
|
||||||
<article class="gallery__item" role="group" aria-roledescription="Folie" aria-label="Bild 1 von 12">
|
<img src="assets/Red checkered social eating.jpg" alt="Red checkered social eating">
|
||||||
<div class="ig-post-wrapper">
|
|
||||||
<img src="assets/index_Red checkered social eating.jpg" alt="Red checkered social eating">
|
|
||||||
<div class="ig-overlay">
|
|
||||||
<span><i class="fa-solid fa-heart"></i> 142</span>
|
|
||||||
<span><i class="fa-solid fa-comment"></i> 18</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</article>
|
</article>
|
||||||
|
<article class="gallery__item">
|
||||||
<article class="gallery__item" role="group" aria-roledescription="Folie" aria-label="Bild 2 von 12">
|
<img src="assets/Pasta and many forks.jpg" alt="Pasta and many forks">
|
||||||
<div class="ig-post-wrapper">
|
|
||||||
<img src="assets/index_Sharing food table.jpg" alt="Sharing food table">
|
|
||||||
<div class="ig-overlay">
|
|
||||||
<span><i class="fa-solid fa-heart"></i> 89</span>
|
|
||||||
<span><i class="fa-solid fa-comment"></i> 5</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</article>
|
</article>
|
||||||
|
<article class="gallery__item">
|
||||||
<article class="gallery__item" role="group" aria-roledescription="Folie" aria-label="Bild 3 von 12">
|
<img src="assets/Zoomed in asian eating.jpg" alt="Zoomed in asian eating">
|
||||||
<div class="ig-post-wrapper">
|
|
||||||
<img src="assets/index_Zoomed in asian eating.jpg" alt="Zoomed in asian eating">
|
|
||||||
<div class="ig-overlay">
|
|
||||||
<span><i class="fa-solid fa-heart"></i> 215</span>
|
|
||||||
<span><i class="fa-solid fa-comment"></i> 32</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</article>
|
</article>
|
||||||
|
<article class="gallery__item">
|
||||||
<article class="gallery__item" role="group" aria-roledescription="Folie" aria-label="Bild 4 von 12">
|
<img src="assets/Burger eating together.jpg" alt="Burger eating together">
|
||||||
<div class="ig-post-wrapper">
|
|
||||||
<img src="assets/index_Burger eating together.jpg" alt="Burger eating together">
|
|
||||||
<div class="ig-overlay">
|
|
||||||
<span><i class="fa-solid fa-heart"></i> 304</span>
|
|
||||||
<span><i class="fa-solid fa-comment"></i> 41</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</article>
|
</article>
|
||||||
|
<article class="gallery__item">
|
||||||
<article class="gallery__item" role="group" aria-roledescription="Folie" aria-label="Bild 5 von 12">
|
<img src="assets/Cake cutting figs.jpg" alt="Cake cutting figs">
|
||||||
<div class="ig-post-wrapper">
|
|
||||||
<img src="assets/index_Cake cutting figs.jpg" alt="Cake cutting figs">
|
|
||||||
<div class="ig-overlay">
|
|
||||||
<span><i class="fa-solid fa-heart"></i> 178</span>
|
|
||||||
<span><i class="fa-solid fa-comment"></i> 12</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</article>
|
</article>
|
||||||
|
<article class="gallery__item">
|
||||||
<article class="gallery__item" role="group" aria-roledescription="Folie" aria-label="Bild 6 von 12">
|
<img src="assets/Cooking woman at home.jpg" alt="Cooking woman at home">
|
||||||
<div class="ig-post-wrapper">
|
|
||||||
<img src="assets/index_Cooking woman at home.jpg" alt="Cooking woman at home">
|
|
||||||
<div class="ig-overlay">
|
|
||||||
<span><i class="fa-solid fa-heart"></i> 95</span>
|
|
||||||
<span><i class="fa-solid fa-comment"></i> 8</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</article>
|
</article>
|
||||||
|
<article class="gallery__item">
|
||||||
<article class="gallery__item" role="group" aria-roledescription="Folie" aria-label="Bild 7 von 12">
|
<img src="assets/Eating and laughing girls.jpg" alt="Eating and laughing girls">
|
||||||
<div class="ig-post-wrapper">
|
|
||||||
<img src="assets/index_Eating and laughing girls.jpg" alt="Eating and laughing girls">
|
|
||||||
<div class="ig-overlay">
|
|
||||||
<span><i class="fa-solid fa-heart"></i> 420</span>
|
|
||||||
<span><i class="fa-solid fa-comment"></i> 55</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</article>
|
</article>
|
||||||
|
<article class="gallery__item">
|
||||||
<article class="gallery__item" role="group" aria-roledescription="Folie" aria-label="Bild 8 von 12">
|
<img src="assets/Pasta in cheese.jpg" alt="Pasta in cheese">
|
||||||
<div class="ig-post-wrapper">
|
|
||||||
<img src="assets/index_Pasta in cheese.jpg" alt="Pasta in cheese">
|
|
||||||
<div class="ig-overlay">
|
|
||||||
<span><i class="fa-solid fa-heart"></i> 267</span>
|
|
||||||
<span><i class="fa-solid fa-comment"></i> 29</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</article>
|
</article>
|
||||||
|
<article class="gallery__item">
|
||||||
<article class="gallery__item" role="group" aria-roledescription="Folie" aria-label="Bild 9 von 12">
|
<img src="assets/Salad roommates.jpg" alt="Salad roommates">
|
||||||
<div class="ig-post-wrapper">
|
|
||||||
<img src="assets/index_Salad roommates.jpg" alt="Salad roommates">
|
|
||||||
<div class="ig-overlay">
|
|
||||||
<span><i class="fa-solid fa-heart"></i> 112</span>
|
|
||||||
<span><i class="fa-solid fa-comment"></i> 4</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</article>
|
</article>
|
||||||
|
<article class="gallery__item">
|
||||||
<article class="gallery__item" role="group" aria-roledescription="Folie" aria-label="Bild 10 von 12">
|
<img src="assets/Sharing food table.jpg" alt="Sharing food table">
|
||||||
<div class="ig-post-wrapper">
|
|
||||||
<img src="assets/index_Pasta and many forks.jpg" alt="Pasta and many forks">
|
|
||||||
<div class="ig-overlay">
|
|
||||||
<span><i class="fa-solid fa-heart"></i> 389</span>
|
|
||||||
<span><i class="fa-solid fa-comment"></i> 47</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</article>
|
</article>
|
||||||
|
<article class="gallery__item">
|
||||||
<article class="gallery__item" role="group" aria-roledescription="Folie" aria-label="Bild 11 von 12">
|
<img src="assets/Spicy food zoomed.jpg" alt="Spicy food zoomed">
|
||||||
<div class="ig-post-wrapper">
|
|
||||||
<img src="assets/index_Spicy food zoomed.jpg" alt="Spicy food zoomed">
|
|
||||||
<div class="ig-overlay">
|
|
||||||
<span><i class="fa-solid fa-heart"></i> 156</span>
|
|
||||||
<span><i class="fa-solid fa-comment"></i> 11</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</article>
|
</article>
|
||||||
|
|
||||||
<article class="gallery__item" role="group" aria-roledescription="Folie" aria-label="Bild 12 von 12">
|
|
||||||
<div class="ig-post-wrapper">
|
|
||||||
<img src="assets/index_cooking.jpg" alt="Cooking">
|
|
||||||
<div class="ig-overlay">
|
|
||||||
<span><i class="fa-solid fa-heart"></i> 234</span>
|
|
||||||
<span><i class="fa-solid fa-comment"></i> 21</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</article>
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button type="button" class="gallery__arrow gallery__arrow--next" aria-label="Nächstes Bild">
|
<button type="button" class="gallery__arrow gallery__arrow--next" aria-label="Nächstes Bild">
|
||||||
<i class="fas fa-chevron-right"></i>
|
<i class="fas fa-chevron-right"></i>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="gallery_dots" role="tablist" aria-label="Seite auswählen"></div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<!-- Lightbox: Bildansicht vergrössert -->
|
<div class="gallery__info">
|
||||||
<div class="lightbox" id="gallery-lightbox" aria-hidden="true">
|
<div class="gallery__handle" style="display: flex; align-items: center; gap: 16px;">
|
||||||
<div class="lightbox__backdrop" data-close-lightbox></div>
|
<img src="assets/instagram.png" alt="Instagram" class="gallery__icon--instagram" />
|
||||||
<figure class="lightbox__content" role="dialog" aria-modal="true" aria-label="Bildansicht">
|
<img src="assets/logo_invite.svg" alt="Invité Logo" class="gallery__icon--invite" />
|
||||||
<button class="lightbox__close" type="button" aria-label="Schliessen">×</button>
|
</div>
|
||||||
<img class="lightbox__image" src="" alt="Grossansicht">
|
</div>
|
||||||
</figure>
|
</section>
|
||||||
</div>
|
</main>
|
||||||
|
|
||||||
<script src="js/index-carousel.js"></script>
|
<script src="js/index-carousel.js"></script>
|
||||||
|
<footer class="footer">
|
||||||
<!-- FAQ Section: Akkordion mit häufig gestellten Fragen -->
|
<a href="#" class="footer__link">Impressum</a>
|
||||||
<section class="faq-section">
|
</footer>
|
||||||
<h2>Häufig gestellte Fragen</h2>
|
|
||||||
<div class="faq-accordion">
|
|
||||||
<div class="faq-item">
|
|
||||||
<button type="button" class="faq-trigger" aria-expanded="false">
|
|
||||||
<span class="faq-title">Wie kann ich bei Invité anfangen?</span>
|
|
||||||
<span class="faq-icon">+</span>
|
|
||||||
</button>
|
|
||||||
<div class="faq-content">
|
|
||||||
<ol class="faq-list">
|
|
||||||
<li>
|
|
||||||
<strong>Kostenloses Konto erstellen</strong><br>
|
|
||||||
Gehe auf Invité, klicke auf «Jetzt beitreten» und fülle das Anmeldeformular aus. Du benötigst nur deine E-Mail und ein Passwort.
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<strong>Finde passende Events</strong><br>
|
|
||||||
Erkunde unsere Events, filtere nach Diät- oder Allergie-Einstellungen und melde dich zu den Events an, die dich interessieren!
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<strong>Erstelle dein eigenes Event</strong><br>
|
|
||||||
Du kannst auch selbst ein Kochevent hosten! Klick auf «Event erstellen», beschreib dein Menü und lade Gäste ein.
|
|
||||||
</li>
|
|
||||||
</ol>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="faq-item">
|
|
||||||
<button type="button" class="faq-trigger" aria-expanded="false">
|
|
||||||
<span class="faq-title">Fallen bei Invité Kosten an?</span>
|
|
||||||
<span class="faq-icon">+</span>
|
|
||||||
</button>
|
|
||||||
<div class="faq-content">
|
|
||||||
<p>Nein, Invité ist komplett kostenlos. Alle Events basieren auf Freiwilligkeit und der Freude am Teilen. Es gibt keine versteckten Kosten – nur die pure Absicht, die Community zu stärken.</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="faq-item">
|
|
||||||
<button type="button" class="faq-trigger" aria-expanded="false">
|
|
||||||
<span class="faq-title">Kann ich ein eigenes Event erstellen?</span>
|
|
||||||
<span class="faq-icon">+</span>
|
|
||||||
</button>
|
|
||||||
<div class="faq-content">
|
|
||||||
<p>Ja, absolut! Du kannst dein eigenes Kochevent erstellen und Gäste einladen. Beschreibe dein Menü, die Teilnehmerzahl und weitere Details. Es ist deine Küche, dein Event, deine Regeln.</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="faq-item">
|
|
||||||
<button type="button" class="faq-trigger" aria-expanded="false">
|
|
||||||
<span class="faq-title">Wie funktioniert die An-/Abmeldung?</span>
|
|
||||||
<span class="faq-icon">+</span>
|
|
||||||
</button>
|
|
||||||
<div class="faq-content">
|
|
||||||
<p>Bei jedem Event sehen dich die verfügbaren Plätze. Du kannst dich mit einem Klick anmelden. Eine Abmeldung ist bis 24 Stunden vor dem Event möglich – so respektieren wir den Aufwand des Gastgebers.</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="faq-item">
|
|
||||||
<button type="button" class="faq-trigger" aria-expanded="false">
|
|
||||||
<span class="faq-title">Was ist mit Allergien und Diäten?</span>
|
|
||||||
<span class="faq-icon">+</span>
|
|
||||||
</button>
|
|
||||||
<div class="faq-content">
|
|
||||||
<p>Ich kann Informationen zu Allergien und Ernährungseinstellungen in der Event-Beschreibung hinzufügen oder beim Anmelden angeben. So können Gastgeber und Gäste besser zusammenkommen und Überraschungen vermeiden.</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="faq-item">
|
|
||||||
<button type="button" class="faq-trigger" aria-expanded="false">
|
|
||||||
<span class="faq-title">Ist Invité sicher und vertrauenswürdig?</span>
|
|
||||||
<span class="faq-icon">+</span>
|
|
||||||
</button>
|
|
||||||
<div class="faq-content">
|
|
||||||
<p>Ja, dein Profil hilft anderen, dich besser kennenzulernen. Wir ermutigen zu Offenheit und gegenseitigem Vertrauen. Allerdings bleibt es deine Entscheidung, wem du deine Adresse mitteilst – die erfolgt nur 12 Stunden vor dem Event.</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
</main>
|
|
||||||
|
|
||||||
<div class="footer">
|
|
||||||
<div class="footer-left">
|
|
||||||
<p class="p-small inline">© <img src="assets/logo_invite.svg" alt="Invité Logo" class="footer-invite_logo" /></p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="footer-center">
|
|
||||||
<a href="https://www.instagram.com" target="_blank" rel="noopener noreferrer" class="instagram-invite__link">
|
|
||||||
<img src="assets/Icon_instagram.png" alt="Instagram" class="instagram-invite_icon" />
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="footer-right footer-links">
|
|
||||||
<a href="impressum.html" class="link-text-footer">Impressum</a>
|
|
||||||
<a href="datenschutz.html" class="link-text-footer">Datenschutz</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- FAQ Akkordion Toggle Script -->
|
|
||||||
<script>
|
|
||||||
document.addEventListener('DOMContentLoaded', function() {
|
|
||||||
const faqTriggers = document.querySelectorAll('.faq-trigger');
|
|
||||||
|
|
||||||
faqTriggers.forEach((trigger) => {
|
|
||||||
trigger.addEventListener('click', function(e) {
|
|
||||||
e.preventDefault();
|
|
||||||
const isExpanded = this.getAttribute('aria-expanded') === 'true';
|
|
||||||
|
|
||||||
// Close all other items (optional: comment out to allow multiple open)
|
|
||||||
faqTriggers.forEach((otherTrigger) => {
|
|
||||||
if (otherTrigger !== trigger) {
|
|
||||||
otherTrigger.setAttribute('aria-expanded', 'false');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Toggle current item
|
|
||||||
this.setAttribute('aria-expanded', !isExpanded);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
@ -1,4 +1,4 @@
|
|||||||
// =============================
|
// =============================
|
||||||
// SETUP: Wichtige HTML-Elemente holen
|
// SETUP: Wichtige HTML-Elemente holen
|
||||||
// Diese Konstanten verbinden unser JavaScript mit dem HTML.
|
// Diese Konstanten verbinden unser JavaScript mit dem HTML.
|
||||||
// So können wir später Buttons, Formularfelder und Bereiche steuern.
|
// So können wir später Buttons, Formularfelder und Bereiche steuern.
|
||||||
@ -8,6 +8,7 @@ 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 progressMarkerLabel = document.getElementById("progressMarkerLabel");
|
||||||
const errorMessage = document.getElementById("errorMessage");
|
const errorMessage = document.getElementById("errorMessage");
|
||||||
const usernameElement = document.getElementById("username");
|
const usernameElement = document.getElementById("username");
|
||||||
@ -23,7 +24,6 @@ const CURRENT_USER_KEY = "socialCookingCurrentUser";
|
|||||||
// =============================
|
// =============================
|
||||||
let currentStep = 0;
|
let currentStep = 0;
|
||||||
const lastStep = steps.length - 1;
|
const lastStep = steps.length - 1;
|
||||||
let reviewReturnStep = null;
|
|
||||||
|
|
||||||
// Text für den Weiter-Button je nach Schritt
|
// Text für den Weiter-Button je nach Schritt
|
||||||
const nextLabels = {
|
const nextLabels = {
|
||||||
@ -121,7 +121,7 @@ function markRadioGroupInvalid(group) {
|
|||||||
* Zeigt den gewünschten Schritt an.
|
* Zeigt den gewünschten Schritt an.
|
||||||
* Dabei werden auch Buttons, Progress Bar und Review aktualisiert.
|
* Dabei werden auch Buttons, Progress Bar und Review aktualisiert.
|
||||||
*/
|
*/
|
||||||
function showStep(index, pushHistory = true) {
|
function showStep(index) {
|
||||||
currentStep = index;
|
currentStep = index;
|
||||||
submissionSuccess.hidden = true;
|
submissionSuccess.hidden = true;
|
||||||
clearStepInvalidState(index);
|
clearStepInvalidState(index);
|
||||||
@ -136,11 +136,6 @@ function showStep(index, pushHistory = true) {
|
|||||||
updateProgressBar(index, lastStep);
|
updateProgressBar(index, lastStep);
|
||||||
setErrorMessage("");
|
setErrorMessage("");
|
||||||
|
|
||||||
// Browser-History aktualisieren, damit Zurück-Taste funktioniert
|
|
||||||
if (pushHistory) {
|
|
||||||
history.pushState({ step: index }, "");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Für bessere UX: bei jedem Schritt wieder nach oben scrollen
|
// Für bessere UX: bei jedem Schritt wieder nach oben scrollen
|
||||||
window.scrollTo({ top: 0, behavior: "smooth" });
|
window.scrollTo({ top: 0, behavior: "smooth" });
|
||||||
}
|
}
|
||||||
@ -152,14 +147,10 @@ function showStep(index, pushHistory = true) {
|
|||||||
*/
|
*/
|
||||||
function updateFlowVisibility(stepIndex) {
|
function updateFlowVisibility(stepIndex) {
|
||||||
const isIntroStep = stepIndex === 0;
|
const isIntroStep = stepIndex === 0;
|
||||||
const isReviewReturnMode = reviewReturnStep === lastStep && stepIndex < lastStep;
|
|
||||||
|
|
||||||
flowFooter.hidden = isIntroStep;
|
flowFooter.hidden = isIntroStep;
|
||||||
backButton.hidden = isIntroStep;
|
backButton.hidden = isIntroStep;
|
||||||
backButton.textContent = "Zurück";
|
nextButton.textContent = nextLabels[stepIndex];
|
||||||
nextButton.textContent = isReviewReturnMode
|
|
||||||
? "Zurück zur Übersicht"
|
|
||||||
: nextLabels[stepIndex];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -177,17 +168,22 @@ function updateFlowVisibility(stepIndex) {
|
|||||||
* - letzter Schritt = 100%
|
* - letzter Schritt = 100%
|
||||||
*/
|
*/
|
||||||
function updateProgressBar(stepIndex, totalStepIndex) {
|
function updateProgressBar(stepIndex, totalStepIndex) {
|
||||||
const totalFormSteps = totalStepIndex;
|
|
||||||
let progress = 0;
|
let progress = 0;
|
||||||
|
let markerPosition = 0;
|
||||||
let markerStep = 1;
|
let markerStep = 1;
|
||||||
|
let markerTransform = "translateX(-50%)";
|
||||||
|
|
||||||
if (stepIndex > 0) {
|
if (stepIndex > 0) {
|
||||||
progress = ((stepIndex) / totalFormSteps) * 100;
|
progress = ((stepIndex - 1) / (totalStepIndex - 1)) * 100;
|
||||||
|
markerPosition = ((stepIndex - 1) / (totalStepIndex - 1)) * 100;
|
||||||
markerStep = stepIndex;
|
markerStep = stepIndex;
|
||||||
}
|
}
|
||||||
|
|
||||||
progressBar.style.width = `${progress}%`;
|
progressBar.style.width = `${progress}%`;
|
||||||
progressMarkerLabel.textContent = `Schritt ${markerStep} von ${totalFormSteps}`;
|
progressMarker.style.left = `${markerPosition}%`;
|
||||||
|
progressMarker.style.transform = markerTransform;
|
||||||
|
progressMarker.hidden = stepIndex === 0;
|
||||||
|
progressMarkerLabel.textContent = String(markerStep);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -270,29 +266,6 @@ function updateReviewField(fieldName, value) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Zeigt hochgeladene Fotos als kleine Vorschaubilder in der Review.
|
|
||||||
*/
|
|
||||||
function updateReviewGallery() {
|
|
||||||
const target = document.querySelector("[data-review-gallery]");
|
|
||||||
if (!target) return;
|
|
||||||
|
|
||||||
target.innerHTML = "";
|
|
||||||
|
|
||||||
if (galleryImages.length === 0) {
|
|
||||||
target.textContent = "Keine Fotos hinzugefügt";
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
galleryImages.forEach((src, index) => {
|
|
||||||
const image = document.createElement("img");
|
|
||||||
image.className = "review-gallery__thumb";
|
|
||||||
image.src = src;
|
|
||||||
image.alt = `Foto ${index + 1}`;
|
|
||||||
target.appendChild(image);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Baut den Text für Allergien / Hinweise zusammen.
|
* Baut den Text für Allergien / Hinweise zusammen.
|
||||||
* Dabei werden Checkboxen und zusätzliches Freitextfeld kombiniert.
|
* Dabei werden Checkboxen und zusätzliches Freitextfeld kombiniert.
|
||||||
@ -320,8 +293,6 @@ function updateReview() {
|
|||||||
Object.entries(reviewValues).forEach(([key, value]) => {
|
Object.entries(reviewValues).forEach(([key, value]) => {
|
||||||
updateReviewField(key, value);
|
updateReviewField(key, value);
|
||||||
});
|
});
|
||||||
|
|
||||||
updateReviewGallery();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -334,7 +305,7 @@ function getReviewValues() {
|
|||||||
menuDescription: getFieldValue("menuDescription"),
|
menuDescription: getFieldValue("menuDescription"),
|
||||||
eventDescription: getFieldValue("eventDescription"),
|
eventDescription: getFieldValue("eventDescription"),
|
||||||
maxGuests: getFieldValue("maxGuests"),
|
maxGuests: getFieldValue("maxGuests"),
|
||||||
dietType: getCheckboxValues("dietType"),
|
dietType: getFieldValue("dietType"),
|
||||||
allergies: buildAllergiesReviewValue(),
|
allergies: buildAllergiesReviewValue(),
|
||||||
eventDate: formatDate(getFieldValue("eventDate")),
|
eventDate: formatDate(getFieldValue("eventDate")),
|
||||||
eventTime: getFieldValue("eventTime"),
|
eventTime: getFieldValue("eventTime"),
|
||||||
@ -416,64 +387,26 @@ function buildMenuItems(value) {
|
|||||||
*/
|
*/
|
||||||
function mapEventTypeToCategory(value) {
|
function mapEventTypeToCategory(value) {
|
||||||
const categoryMap = {
|
const categoryMap = {
|
||||||
Brunch: "Brunch",
|
Brunch: "BRUNCH",
|
||||||
Lunch: "Lunch",
|
Lunch: "LUNCH",
|
||||||
Dinner: "Dinner",
|
Dinner: "DINNER",
|
||||||
"Kaffee + Kuchen": "Kaffee + Kuchen"
|
"Kaffee + Kuchen": "COFFEE"
|
||||||
};
|
};
|
||||||
|
|
||||||
return categoryMap[value] || value.toUpperCase();
|
return categoryMap[value] || value.toUpperCase();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Liefert je Eventtyp ein passendes Platzhalterbild.
|
|
||||||
*/
|
|
||||||
function getPlaceholderImageByEventType(eventType) {
|
|
||||||
const normalizedType = String(eventType || "")
|
|
||||||
.trim()
|
|
||||||
.toLowerCase()
|
|
||||||
.normalize("NFD")
|
|
||||||
.replace(/[\u0300-\u036f]/g, "")
|
|
||||||
.replace(/[+&/_-]/g, " ")
|
|
||||||
.replace(/\s+/g, " ");
|
|
||||||
|
|
||||||
if (normalizedType.includes("brunch")) {
|
|
||||||
return "assets/platzhalter_brunch.jpeg";
|
|
||||||
}
|
|
||||||
|
|
||||||
if (normalizedType.includes("lunch")) {
|
|
||||||
return "assets/platzhalter_lunch.jpeg";
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
normalizedType.includes("kaffee")
|
|
||||||
|| normalizedType.includes("coffee")
|
|
||||||
|| normalizedType.includes("cafe")
|
|
||||||
|| normalizedType.includes("kuchen")
|
|
||||||
) {
|
|
||||||
return "assets/platzhalter_kaffee.jpeg";
|
|
||||||
}
|
|
||||||
|
|
||||||
if (normalizedType.includes("dinner")) {
|
|
||||||
return "assets/platzhalter_dinner.jpeg";
|
|
||||||
}
|
|
||||||
|
|
||||||
return "assets/platzhalter_dinner.jpeg";
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Baut aus den Formulardaten ein lokal speicherbares Event-Objekt.
|
* Baut aus den Formulardaten ein lokal speicherbares Event-Objekt.
|
||||||
*/
|
*/
|
||||||
function buildStoredEvent() {
|
function buildStoredEvent() {
|
||||||
const eventType = getFieldValue("eventType");
|
const eventType = getFieldValue("eventType");
|
||||||
const dietType = getCheckboxValues("dietType");
|
const dietType = getFieldValue("dietType");
|
||||||
const menuDescription = form.elements.menuDescription.value.trim();
|
const menuDescription = form.elements.menuDescription.value.trim();
|
||||||
const eventDescription = form.elements.eventDescription.value.trim();
|
const eventDescription = form.elements.eventDescription.value.trim();
|
||||||
const eventDate = form.elements.eventDate.value;
|
const eventDate = form.elements.eventDate.value;
|
||||||
const eventTime = form.elements.eventTime.value;
|
const eventTime = form.elements.eventTime.value;
|
||||||
const eventCity = form.elements.eventCity.value.trim();
|
const eventCity = form.elements.eventCity.value.trim();
|
||||||
const fallbackGallery = [getPlaceholderImageByEventType(eventType)];
|
|
||||||
const resolvedGallery = galleryImages.length > 0 ? [...galleryImages] : fallbackGallery;
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id: Date.now(),
|
id: Date.now(),
|
||||||
@ -482,7 +415,6 @@ function buildStoredEvent() {
|
|||||||
address: form.elements.eventAddress.value.trim(),
|
address: form.elements.eventAddress.value.trim(),
|
||||||
date: formatDateForStorage(eventDate),
|
date: formatDateForStorage(eventDate),
|
||||||
time: formatTimeForStorage(eventTime),
|
time: formatTimeForStorage(eventTime),
|
||||||
eventType,
|
|
||||||
category: mapEventTypeToCategory(eventType),
|
category: mapEventTypeToCategory(eventType),
|
||||||
diet: dietType,
|
diet: dietType,
|
||||||
spots: Number(form.elements.maxGuests.value),
|
spots: Number(form.elements.maxGuests.value),
|
||||||
@ -497,9 +429,9 @@ function buildStoredEvent() {
|
|||||||
? []
|
? []
|
||||||
: getCheckboxValues("allergies").split(", ").filter(Boolean),
|
: getCheckboxValues("allergies").split(", ").filter(Boolean),
|
||||||
allergiesNote: form.elements.allergiesOther.value.trim(),
|
allergiesNote: form.elements.allergiesOther.value.trim(),
|
||||||
// Host wird separat geführt und nicht als angemeldeter Gast gezählt.
|
// Host wird separat gefuehrt und nicht als angemeldeter Gast gezaehlt.
|
||||||
participants: [],
|
participants: [],
|
||||||
gallery: resolvedGallery,
|
gallery: [],
|
||||||
createdAt: new Date().toISOString(),
|
createdAt: new Date().toISOString(),
|
||||||
source: "local"
|
source: "local"
|
||||||
};
|
};
|
||||||
@ -541,15 +473,6 @@ function validateCurrentStep() {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Speziell für Schritt 2: Ernährungsform prüfen
|
|
||||||
if (currentStep === 2) {
|
|
||||||
const dietCheck = validateDietType();
|
|
||||||
if (!dietCheck.isValid) {
|
|
||||||
setErrorMessage(dietCheck.message);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Danach normale Pflichtfelder prüfen
|
// Danach normale Pflichtfelder prüfen
|
||||||
const requiredCheck = validateRequiredFields(fields);
|
const requiredCheck = validateRequiredFields(fields);
|
||||||
if (!requiredCheck.isValid) {
|
if (!requiredCheck.isValid) {
|
||||||
@ -587,68 +510,6 @@ function validateRadioGroups(fields) {
|
|||||||
return { isValid: true };
|
return { isValid: true };
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Prüft die Ernährungsform-Checkboxen (mindestens eine muss ausgewählt sein).
|
|
||||||
*/
|
|
||||||
function validateDietType() {
|
|
||||||
const checked = form.querySelectorAll('input[name="dietType"]:checked');
|
|
||||||
if (checked.length === 0) {
|
|
||||||
// Mark all as invalid
|
|
||||||
form.querySelectorAll('input[name="dietType"]').forEach(input => {
|
|
||||||
const card = input.closest('.option-card');
|
|
||||||
if (card) card.classList.add('option-card--invalid');
|
|
||||||
});
|
|
||||||
return { isValid: false, message: "Bitte wähle mindestens eine Ernährungsform aus." };
|
|
||||||
}
|
|
||||||
return { isValid: true };
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Verhindert widersprüchliche Ernährungsformen:
|
|
||||||
* Fleisch/Fisch dürfen zusammen, schliessen aber vegetarisch/vegan aus.
|
|
||||||
*/
|
|
||||||
function registerDietConflictHandlers() {
|
|
||||||
const dietOptions = Array.from(form.querySelectorAll('input[name="dietType"]'));
|
|
||||||
const meatFishOptions = dietOptions.filter(input => ["Fleisch", "Fisch"].includes(input.value));
|
|
||||||
const plantOptions = dietOptions.filter(input => ["Vegetarisch", "Vegan"].includes(input.value));
|
|
||||||
|
|
||||||
const updateDietAvailability = changedInput => {
|
|
||||||
if (changedInput?.checked && changedInput.value === "Vegetarisch") {
|
|
||||||
dietOptions.forEach(input => {
|
|
||||||
if (input.value === "Vegan") input.checked = false;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (changedInput?.checked && changedInput.value === "Vegan") {
|
|
||||||
dietOptions.forEach(input => {
|
|
||||||
if (input.value === "Vegetarisch") input.checked = false;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const hasMeatOrFish = meatFishOptions.some(input => input.checked);
|
|
||||||
const selectedPlantDiet = plantOptions.find(input => input.checked);
|
|
||||||
|
|
||||||
meatFishOptions.forEach(input => {
|
|
||||||
input.disabled = Boolean(selectedPlantDiet);
|
|
||||||
});
|
|
||||||
|
|
||||||
plantOptions.forEach(input => {
|
|
||||||
input.disabled = hasMeatOrFish || Boolean(selectedPlantDiet && input !== selectedPlantDiet);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
dietOptions.forEach(input => {
|
|
||||||
input.addEventListener("change", () => {
|
|
||||||
dietOptions.forEach(option => {
|
|
||||||
option.closest(".option-card")?.classList.remove("option-card--invalid");
|
|
||||||
});
|
|
||||||
updateDietAvailability(input);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
updateDietAvailability();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Prüft alle Pflichtfelder ausser Radios und Checkboxen.
|
* Prüft alle Pflichtfelder ausser Radios und Checkboxen.
|
||||||
* Rückgabe:
|
* Rückgabe:
|
||||||
@ -682,10 +543,6 @@ function validateRequiredFields(fields) {
|
|||||||
* Wenn der User im ersten Formularschritt ist, geht es zurück zum Intro.
|
* Wenn der User im ersten Formularschritt ist, geht es zurück zum Intro.
|
||||||
*/
|
*/
|
||||||
function handleBackClick() {
|
function handleBackClick() {
|
||||||
if (reviewReturnStep === lastStep && currentStep < lastStep) {
|
|
||||||
reviewReturnStep = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (currentStep > 1) {
|
if (currentStep > 1) {
|
||||||
showStep(currentStep - 1);
|
showStep(currentStep - 1);
|
||||||
} else {
|
} else {
|
||||||
@ -701,12 +558,6 @@ function handleBackClick() {
|
|||||||
function handleNextClick() {
|
function handleNextClick() {
|
||||||
if (!validateCurrentStep()) return;
|
if (!validateCurrentStep()) return;
|
||||||
|
|
||||||
if (reviewReturnStep === lastStep && currentStep < lastStep) {
|
|
||||||
showStep(lastStep);
|
|
||||||
reviewReturnStep = null;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (currentStep < lastStep) {
|
if (currentStep < lastStep) {
|
||||||
showStep(currentStep + 1);
|
showStep(currentStep + 1);
|
||||||
} else {
|
} else {
|
||||||
@ -758,17 +609,11 @@ function registerCounterHandlers() {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Erhöht oder verringert den Wert eines Zahlenfelds.
|
* Erhöht oder verringert den Wert eines Zahlenfelds.
|
||||||
* 0 bleibt als bewusster Startwert erlaubt; gültig ist erst eine Auswahl ab min.
|
* Der Wert darf dabei nie kleiner als das definierte Minimum werden.
|
||||||
*/
|
*/
|
||||||
function updateCounterValue(input, change) {
|
function updateCounterValue(input, change) {
|
||||||
const min = Number(input.min || 1);
|
const min = Number(input.min || 1);
|
||||||
const currentValue = Number(input.value || 0);
|
const currentValue = Number(input.value || min);
|
||||||
|
|
||||||
if (currentValue < min && change < 0) {
|
|
||||||
input.value = currentValue;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
input.value = Math.max(min, currentValue + change);
|
input.value = Math.max(min, currentValue + change);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -832,8 +677,6 @@ function registerReviewEditHandlers() {
|
|||||||
const stepIndex = Number(item.dataset.editStep);
|
const stepIndex = Number(item.dataset.editStep);
|
||||||
const fieldName = item.dataset.editField;
|
const fieldName = item.dataset.editField;
|
||||||
|
|
||||||
// Öffnet einen Schritt aus der Übersicht und springt danach direkt zurück zu Schritt 7.
|
|
||||||
reviewReturnStep = lastStep;
|
|
||||||
showStep(stepIndex);
|
showStep(stepIndex);
|
||||||
focusFieldByName(fieldName);
|
focusFieldByName(fieldName);
|
||||||
};
|
};
|
||||||
@ -881,7 +724,7 @@ function registerValidationFeedbackHandlers() {
|
|||||||
* Setzt den Fokus auf ein bestimmtes Feld oder die erste Option einer Radio-Gruppe.
|
* Setzt den Fokus auf ein bestimmtes Feld oder die erste Option einer Radio-Gruppe.
|
||||||
*/
|
*/
|
||||||
function focusFieldByName(fieldName) {
|
function focusFieldByName(fieldName) {
|
||||||
const field = form.elements[fieldName] || document.getElementById(fieldName);
|
const field = form.elements[fieldName];
|
||||||
|
|
||||||
if (!field) return;
|
if (!field) return;
|
||||||
|
|
||||||
@ -895,64 +738,6 @@ function focusFieldByName(fieldName) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// =============================
|
|
||||||
// STEP 8b: Galerie-Upload (Fotos hinzufügen)
|
|
||||||
// =============================
|
|
||||||
const galleryImages = [];
|
|
||||||
|
|
||||||
function registerGalleryHandlers() {
|
|
||||||
const addBtn = document.getElementById("galleryAddBtn");
|
|
||||||
const fileInput = document.getElementById("galleryFileInput");
|
|
||||||
const preview = document.getElementById("galleryPreview");
|
|
||||||
|
|
||||||
if (!addBtn) return;
|
|
||||||
|
|
||||||
// Klick auf + öffnet direkt den Datei-Dialog
|
|
||||||
addBtn.addEventListener("click", () => {
|
|
||||||
fileInput.click();
|
|
||||||
});
|
|
||||||
|
|
||||||
// Datei(en) hochladen
|
|
||||||
fileInput.addEventListener("change", () => {
|
|
||||||
Array.from(fileInput.files).forEach(file => {
|
|
||||||
if (!file.type.startsWith("image/")) return;
|
|
||||||
const reader = new FileReader();
|
|
||||||
reader.onload = (e) => {
|
|
||||||
addGalleryImage(e.target.result);
|
|
||||||
};
|
|
||||||
reader.readAsDataURL(file);
|
|
||||||
});
|
|
||||||
fileInput.value = "";
|
|
||||||
});
|
|
||||||
|
|
||||||
function addGalleryImage(src) {
|
|
||||||
galleryImages.push(src);
|
|
||||||
renderGalleryPreview();
|
|
||||||
}
|
|
||||||
|
|
||||||
function removeGalleryImage(index) {
|
|
||||||
galleryImages.splice(index, 1);
|
|
||||||
renderGalleryPreview();
|
|
||||||
}
|
|
||||||
|
|
||||||
function renderGalleryPreview() {
|
|
||||||
preview.innerHTML = "";
|
|
||||||
galleryImages.forEach((src, i) => {
|
|
||||||
const thumb = document.createElement("div");
|
|
||||||
thumb.className = "gallery-thumb";
|
|
||||||
thumb.innerHTML = `
|
|
||||||
<img src="${src}" alt="Foto ${i + 1}">
|
|
||||||
<button type="button" class="gallery-thumb-remove" aria-label="Foto entfernen">×</button>
|
|
||||||
`;
|
|
||||||
thumb.querySelector(".gallery-thumb-remove").addEventListener("click", () => {
|
|
||||||
removeGalleryImage(i);
|
|
||||||
});
|
|
||||||
preview.appendChild(thumb);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// =============================
|
// =============================
|
||||||
// STEP 9: Alles starten
|
// STEP 9: Alles starten
|
||||||
// Hier werden alle Event Listener registriert
|
// Hier werden alle Event Listener registriert
|
||||||
@ -978,27 +763,13 @@ function initEventCreationFlow() {
|
|||||||
|
|
||||||
// Counter aktivieren
|
// Counter aktivieren
|
||||||
registerCounterHandlers();
|
registerCounterHandlers();
|
||||||
registerDietConflictHandlers();
|
|
||||||
registerMenuBulletHandler();
|
registerMenuBulletHandler();
|
||||||
registerValidationFeedbackHandlers();
|
registerValidationFeedbackHandlers();
|
||||||
registerReviewEditHandlers();
|
registerReviewEditHandlers();
|
||||||
registerGalleryHandlers();
|
|
||||||
|
|
||||||
// Browser-Zurück-Taste: vorherigen Schritt wiederherstellen
|
|
||||||
window.addEventListener("popstate", (e) => {
|
|
||||||
if (e.state && typeof e.state.step === "number") {
|
|
||||||
showStep(e.state.step, false);
|
|
||||||
} else {
|
|
||||||
showStep(0, false);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Startzustand: Intro anzeigen
|
// Startzustand: Intro anzeigen
|
||||||
submissionSuccess.hidden = true;
|
submissionSuccess.hidden = true;
|
||||||
showStep(0);
|
showStep(0);
|
||||||
|
|
||||||
// Initialen History-Eintrag ersetzen, damit Step 0 im Verlauf ist
|
|
||||||
history.replaceState({ step: 0 }, "");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Startpunkt des Skripts
|
// Startpunkt des Skripts
|
||||||
|
|||||||
@ -1,15 +1,15 @@
|
|||||||
document.addEventListener('DOMContentLoaded', async () => {
|
document.addEventListener('DOMContentLoaded', async () => {
|
||||||
const EVENTS_STORAGE_KEY = 'socialCookingEvents';
|
const EVENTS_STORAGE_KEY = 'socialCookingEvents';
|
||||||
const CURRENT_USER_KEY = 'socialCookingCurrentUser';
|
const CURRENT_USER_KEY = 'socialCookingCurrentUser';
|
||||||
const USERS_STORAGE_KEY = 'socialCookingUsers';
|
|
||||||
const REGISTRATION_STORAGE_KEY = 'socialCookingRegistrations';
|
const REGISTRATION_STORAGE_KEY = 'socialCookingRegistrations';
|
||||||
|
// -------------------------------------------------------------
|
||||||
const detailcontainer = document.getElementById('detail-view');
|
// DOM entry point and shared asset path.
|
||||||
const locationIconPath = 'assets/icon_location.svg';
|
// -------------------------------------------------------------
|
||||||
const calendarIconPath = 'assets/icon_calendar.svg';
|
const detailContainer = document.getElementById('detail-view');
|
||||||
const gastIconPath = 'assets/icon_gast.svg';
|
const locationIconPath = 'assets/location-pin.svg';
|
||||||
const currentUser = getCurrentUser();
|
const currentUser = getCurrentUser();
|
||||||
|
|
||||||
|
// Read event id from query string (detail page deep-link support).
|
||||||
const params = new URLSearchParams(window.location.search);
|
const params = new URLSearchParams(window.location.search);
|
||||||
const eventId = parseInt(params.get('id'));
|
const eventId = parseInt(params.get('id'));
|
||||||
|
|
||||||
@ -48,81 +48,20 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function getStoredUsers() {
|
|
||||||
try {
|
|
||||||
const stored = localStorage.getItem(USERS_STORAGE_KEY);
|
|
||||||
return stored ? JSON.parse(stored) : [];
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Benutzerdaten konnten nicht gelesen werden.', error);
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function getUserDisplayName(user) {
|
|
||||||
if (!user) return '';
|
|
||||||
const firstName = String(user.vorname || '').trim();
|
|
||||||
const lastName = String(user.nachname || '').trim();
|
|
||||||
const fullName = `${firstName} ${lastName}`.trim();
|
|
||||||
return (fullName || firstName || String(user.email || '').trim()).trim();
|
|
||||||
}
|
|
||||||
|
|
||||||
function getResolvedParticipants(event, registrationMap) {
|
|
||||||
const baseParticipants = Array.isArray(event.participants)
|
|
||||||
? event.participants.map(name => String(name || '').trim()).filter(Boolean)
|
|
||||||
: [];
|
|
||||||
const usersByEmail = new Map(
|
|
||||||
getStoredUsers().map(user => [String(user.email || '').trim().toLowerCase(), user])
|
|
||||||
);
|
|
||||||
const participantLookup = new Set(baseParticipants.map(name => name.toLowerCase()));
|
|
||||||
|
|
||||||
Object.entries(registrationMap || {}).forEach(([email, ids]) => {
|
|
||||||
const isRegisteredForEvent = Array.isArray(ids)
|
|
||||||
&& ids.map(id => Number(id)).includes(Number(event.id));
|
|
||||||
if (!isRegisteredForEvent) return;
|
|
||||||
|
|
||||||
const user = usersByEmail.get(String(email || '').trim().toLowerCase());
|
|
||||||
const displayName = getUserDisplayName(user) || String(email || '').trim();
|
|
||||||
const normalizedName = displayName.toLowerCase();
|
|
||||||
|
|
||||||
if (displayName && !participantLookup.has(normalizedName)) {
|
|
||||||
baseParticipants.push(displayName);
|
|
||||||
participantLookup.add(normalizedName);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return baseParticipants;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getParticipantNameForViewer(name, canSeeLastName) {
|
|
||||||
const rawName = String(name || '').trim();
|
|
||||||
if (!rawName) return '';
|
|
||||||
if (canSeeLastName) return rawName;
|
|
||||||
if (rawName.includes('@')) return rawName.split('@')[0].trim() || rawName;
|
|
||||||
return rawName.split(/\s+/)[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
function setRegistrationMap(registrationMap) {
|
function setRegistrationMap(registrationMap) {
|
||||||
localStorage.setItem(REGISTRATION_STORAGE_KEY, JSON.stringify(registrationMap));
|
localStorage.setItem(REGISTRATION_STORAGE_KEY, JSON.stringify(registrationMap));
|
||||||
}
|
}
|
||||||
|
|
||||||
function getRegistrationIdsForUser(registrationMap, user) {
|
|
||||||
const userEmail = String(user?.email || '').trim().toLowerCase();
|
|
||||||
if (!userEmail) return [];
|
|
||||||
|
|
||||||
const matchingIds = Object.entries(registrationMap || {})
|
|
||||||
.filter(([email]) => String(email || '').trim().toLowerCase() === userEmail)
|
|
||||||
.flatMap(([, ids]) => (Array.isArray(ids) ? ids : []))
|
|
||||||
.map(id => Number(id))
|
|
||||||
.filter(id => Number.isFinite(id));
|
|
||||||
|
|
||||||
return Array.from(new Set(matchingIds));
|
|
||||||
}
|
|
||||||
|
|
||||||
function parseEventDateTime(event) {
|
function parseEventDateTime(event) {
|
||||||
if (!event?.date) return null;
|
if (!event?.date) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
const dateValue = String(event.date).trim();
|
const dateValue = String(event.date).trim();
|
||||||
const isoDateMatch = dateValue.match(/^(\d{4})-(\d{2})-(\d{2})$/);
|
const isoDateMatch = dateValue.match(/^(\d{4})-(\d{2})-(\d{2})$/);
|
||||||
let year, month, day;
|
let year;
|
||||||
|
let month;
|
||||||
|
let day;
|
||||||
|
|
||||||
if (isoDateMatch) {
|
if (isoDateMatch) {
|
||||||
year = Number(isoDateMatch[1]);
|
year = Number(isoDateMatch[1]);
|
||||||
@ -130,158 +69,118 @@
|
|||||||
day = Number(isoDateMatch[3]);
|
day = Number(isoDateMatch[3]);
|
||||||
} else {
|
} else {
|
||||||
const monthMap = {
|
const monthMap = {
|
||||||
jan: 1,
|
JAN: 1,
|
||||||
januar: 1,
|
FEB: 2,
|
||||||
feb: 2,
|
'MÄR': 3,
|
||||||
februar: 2,
|
MRZ: 3,
|
||||||
'mär': 3,
|
APR: 4,
|
||||||
mrz: 3,
|
MAI: 5,
|
||||||
mar: 3,
|
JUN: 6,
|
||||||
maerz: 3,
|
JUL: 7,
|
||||||
märz: 3,
|
AUG: 8,
|
||||||
apr: 4,
|
SEP: 9,
|
||||||
april: 4,
|
OKT: 10,
|
||||||
mai: 5,
|
NOV: 11,
|
||||||
jun: 6,
|
DEZ: 12
|
||||||
juni: 6,
|
|
||||||
jul: 7,
|
|
||||||
juli: 7,
|
|
||||||
aug: 8,
|
|
||||||
august: 8,
|
|
||||||
sep: 9,
|
|
||||||
sept: 9,
|
|
||||||
september: 9,
|
|
||||||
okt: 10,
|
|
||||||
oktober: 10,
|
|
||||||
nov: 11,
|
|
||||||
november: 11,
|
|
||||||
dez: 12,
|
|
||||||
dezember: 12
|
|
||||||
};
|
};
|
||||||
const localizedMatch = dateValue.match(/^(\d{1,2})\.\s*([A-Za-zÄÖÜäöü]{3,9})\.?\s*(\d{4})$/);
|
const localizedMatch = dateValue.match(/^(\d{1,2})\.\s*([A-ZÄÖÜ]{3})\.\s*(\d{4})$/);
|
||||||
if (!localizedMatch) return null;
|
|
||||||
|
if (!localizedMatch) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
day = Number(localizedMatch[1]);
|
day = Number(localizedMatch[1]);
|
||||||
month = monthMap[String(localizedMatch[2]).toLowerCase()];
|
month = monthMap[localizedMatch[2]];
|
||||||
year = Number(localizedMatch[3]);
|
year = Number(localizedMatch[3]);
|
||||||
if (!month) return null;
|
|
||||||
|
if (!month) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const timeMatch = String(event.time || '').match(/(\d{1,2}):(\d{2})/);
|
const timeMatch = String(event.time || '').match(/(\d{1,2}):(\d{2})/);
|
||||||
const hours = timeMatch ? Number(timeMatch[1]) : 0;
|
const hours = timeMatch ? Number(timeMatch[1]) : 0;
|
||||||
const minutes = timeMatch ? Number(timeMatch[2]) : 0;
|
const minutes = timeMatch ? Number(timeMatch[2]) : 0;
|
||||||
|
|
||||||
return new Date(year, month - 1, day, hours, minutes, 0, 0);
|
return new Date(year, month - 1, day, hours, minutes, 0, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
function isRegistrationClosedForEvent(event) {
|
function isRegistrationClosedForEvent(event) {
|
||||||
const eventDateTime = parseEventDateTime(event);
|
const eventDateTime = parseEventDateTime(event);
|
||||||
if (!eventDateTime || Number.isNaN(eventDateTime.getTime())) return false;
|
if (!eventDateTime || Number.isNaN(eventDateTime.getTime())) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
const msUntilStart = eventDateTime.getTime() - Date.now();
|
const msUntilStart = eventDateTime.getTime() - Date.now();
|
||||||
return msUntilStart <= 24 * 60 * 60 * 1000;
|
const twelveHoursInMs = 12 * 60 * 60 * 1000;
|
||||||
}
|
|
||||||
|
return msUntilStart <= twelveHoursInMs;
|
||||||
function getDeregistrationInfo(event) {
|
|
||||||
const eventDateTime = parseEventDateTime(event);
|
|
||||||
if (!eventDateTime || Number.isNaN(eventDateTime.getTime())) return { daysLeft: null, isClosed: false };
|
|
||||||
const oneDayMs = 24 * 60 * 60 * 1000;
|
|
||||||
const msUntilDeadline = (eventDateTime.getTime() - oneDayMs) - Date.now();
|
|
||||||
if (msUntilDeadline <= 0) return { daysLeft: 0, isClosed: true };
|
|
||||||
return { daysLeft: Math.ceil(msUntilDeadline / oneDayMs), isClosed: false };
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Adresse ist nur im 12h-Fenster VOR Eventstart sichtbar.
|
||||||
function isAddressVisibleWindow(event) {
|
function isAddressVisibleWindow(event) {
|
||||||
const eventDateTime = parseEventDateTime(event);
|
const eventDateTime = parseEventDateTime(event);
|
||||||
if (!eventDateTime || Number.isNaN(eventDateTime.getTime())) return false;
|
if (!eventDateTime || Number.isNaN(eventDateTime.getTime())) {
|
||||||
const now = Date.now();
|
return false;
|
||||||
const start = eventDateTime.getTime();
|
}
|
||||||
const revealStart = start - (24 * 60 * 60 * 1000);
|
|
||||||
const revealEnd = start + (1 * 60 * 60 * 1000);
|
const msUntilStart = eventDateTime.getTime() - Date.now();
|
||||||
return now >= revealStart && now <= revealEnd;
|
const twelveHoursInMs = 12 * 60 * 60 * 1000;
|
||||||
|
|
||||||
|
return msUntilStart >= 0 && msUntilStart <= twelveHoursInMs;
|
||||||
}
|
}
|
||||||
|
|
||||||
function isEventPastAddressWindow(event) {
|
function countRegistrationsForEvent(registrationMap, eventId) {
|
||||||
const eventDateTime = parseEventDateTime(event);
|
return Object.values(registrationMap).reduce((count, ids) => {
|
||||||
if (!eventDateTime || Number.isNaN(eventDateTime.getTime())) return false;
|
const hasEvent = Array.isArray(ids)
|
||||||
const revealEnd = eventDateTime.getTime() + (1 * 60 * 60 * 1000);
|
&& ids.map(id => Number(id)).includes(Number(eventId));
|
||||||
return Date.now() > revealEnd;
|
|
||||||
|
return hasEvent ? count + 1 : count;
|
||||||
|
}, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Ermittelt, ob das Event vom aktuell eingeloggten Benutzer erstellt wurde.
|
||||||
function isEventOwnedByCurrentUser(event, user) {
|
function isEventOwnedByCurrentUser(event, user) {
|
||||||
if (!event || !user) return false;
|
if (!event || !user) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
const userEmail = String(user.email || '').trim().toLowerCase();
|
const userEmail = String(user.email || '').trim().toLowerCase();
|
||||||
const hostEmail = String(event.hostEmail || '').trim().toLowerCase();
|
const hostEmail = String(event.hostEmail || '').trim().toLowerCase();
|
||||||
if (userEmail && hostEmail) return userEmail === hostEmail;
|
|
||||||
|
if (userEmail && hostEmail) {
|
||||||
|
return userEmail === hostEmail;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback fuer aeltere Datensaetze ohne hostEmail.
|
||||||
const userFirstName = String(user.vorname || '').trim().toLowerCase();
|
const userFirstName = String(user.vorname || '').trim().toLowerCase();
|
||||||
const hostName = String(event.host?.name || '').trim().toLowerCase();
|
const hostName = String(event.host?.name || '').trim().toLowerCase();
|
||||||
return Boolean(userFirstName && hostName && userFirstName === hostName);
|
return Boolean(userFirstName && hostName && userFirstName === hostName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Prueft, ob der aktuelle Benutzer bereits in der Teilnehmerliste des Events steht.
|
||||||
function isUserListedInEventParticipants(event, user) {
|
function isUserListedInEventParticipants(event, user) {
|
||||||
if (!event || !user || !Array.isArray(event.participants)) return false;
|
if (!event || !user || !Array.isArray(event.participants)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
const participantSet = new Set(
|
const participantSet = new Set(
|
||||||
event.participants.map(name => String(name || '').trim().toLowerCase()).filter(Boolean)
|
event.participants
|
||||||
|
.map(name => String(name || '').trim().toLowerCase())
|
||||||
|
.filter(Boolean)
|
||||||
);
|
);
|
||||||
|
|
||||||
const userFirstName = String(user.vorname || '').trim().toLowerCase();
|
const userFirstName = String(user.vorname || '').trim().toLowerCase();
|
||||||
const userFullName = `${String(user.vorname || '').trim()} ${String(user.nachname || '').trim()}`.trim().toLowerCase();
|
const userFullName = `${String(user.vorname || '').trim()} ${String(user.nachname || '').trim()}`
|
||||||
|
.trim()
|
||||||
|
.toLowerCase();
|
||||||
|
|
||||||
return Boolean(
|
return Boolean(
|
||||||
(userFirstName && participantSet.has(userFirstName))
|
(userFirstName && participantSet.has(userFirstName))
|
||||||
|| (userFullName && participantSet.has(userFullName))
|
|| (userFullName && participantSet.has(userFullName))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
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 = { JAN:'Januar', FEB:'Februar', 'MÄR':'März', MRZ:'März', APR:'April', MAI:'Mai', JUN:'Juni', JUL:'Juli', AUG:'August', SEP:'September', OKT:'Oktober', NOV:'November', DEZ:'Dezember' };
|
|
||||||
const match = dateString.match(/^(\d{1,2})\.\s*([A-ZÄÖÜ]{3})\.\s*(\d{4})$/);
|
|
||||||
if (!match) return dateString;
|
|
||||||
const monthLabel = labels[match[2]];
|
|
||||||
return monthLabel ? `${Number(match[1])}. ${monthLabel} ${match[3]}` : dateString;
|
|
||||||
}
|
|
||||||
|
|
||||||
function formatEventTime(timeString) {
|
|
||||||
return timeString.replace('UHR', 'Uhr').trim();
|
|
||||||
}
|
|
||||||
|
|
||||||
function getDietLabel(diet) {
|
|
||||||
const labels = { FLEISCH:'Fleisch', FISCH:'Fisch', VEGGIE:'Vegetarisch', VEGAN:'Vegan' };
|
|
||||||
return labels[diet] || diet;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getPlaceholderImageByEventType(event) {
|
|
||||||
const rawType = String(event?.eventType || event?.category || '')
|
|
||||||
.trim()
|
|
||||||
.toLowerCase()
|
|
||||||
.normalize('NFD')
|
|
||||||
.replace(/[\u0300-\u036f]/g, '')
|
|
||||||
.replace(/[+&/_-]/g, ' ')
|
|
||||||
.replace(/\s+/g, ' ');
|
|
||||||
|
|
||||||
if (rawType.includes('brunch')) {
|
|
||||||
return 'assets/platzhalter_brunch.jpeg';
|
|
||||||
}
|
|
||||||
|
|
||||||
if (rawType.includes('lunch')) {
|
|
||||||
return 'assets/platzhalter_lunch.jpeg';
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
rawType.includes('kaffee')
|
|
||||||
|| rawType.includes('coffee')
|
|
||||||
|| rawType.includes('cafe')
|
|
||||||
|| rawType.includes('kuchen')
|
|
||||||
) {
|
|
||||||
return 'assets/platzhalter_kaffee.jpeg';
|
|
||||||
}
|
|
||||||
|
|
||||||
if (rawType.includes('dinner')) {
|
|
||||||
return 'assets/platzhalter_dinner.jpeg';
|
|
||||||
}
|
|
||||||
|
|
||||||
return 'assets/platzhalter_dinner.jpeg';
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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');
|
||||||
@ -292,315 +191,300 @@
|
|||||||
if (event) {
|
if (event) {
|
||||||
renderDetailPage(event);
|
renderDetailPage(event);
|
||||||
} else {
|
} else {
|
||||||
detailcontainer.innerHTML = "<h1>Event wurde nicht gefunden.</h1><a href='event_overview.html'>Zurück zur Übersicht</a>";
|
detailContainer.innerHTML = "<h1>Event wurde nicht gefunden.</h1><a href='event_overview.html'>Zurück zur Übersicht</a>";
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Fehler beim Laden der Details:", error);
|
console.error("Fehler beim Laden der Details:", error);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Format localized date token into full readable date.
|
||||||
|
function formatEventDate(dateString) {
|
||||||
|
const labels = {
|
||||||
|
JAN: 'Januar',
|
||||||
|
FEB: 'Februar',
|
||||||
|
'MÄR': 'März',
|
||||||
|
MRZ: 'März',
|
||||||
|
APR: 'April',
|
||||||
|
MAI: 'Mai',
|
||||||
|
JUN: 'Juni',
|
||||||
|
JUL: 'Juli',
|
||||||
|
AUG: 'August',
|
||||||
|
SEP: 'September',
|
||||||
|
OKT: 'Oktober',
|
||||||
|
NOV: 'November',
|
||||||
|
DEZ: 'Dezember'
|
||||||
|
};
|
||||||
|
|
||||||
|
const match = dateString.match(/^(\d{1,2})\.\s*([A-ZÄÖÜ]{3})\.\s*(\d{4})$/);
|
||||||
|
if (!match) {
|
||||||
|
return dateString;
|
||||||
|
}
|
||||||
|
|
||||||
|
const day = Number(match[1]);
|
||||||
|
const monthLabel = labels[match[2]];
|
||||||
|
const year = match[3];
|
||||||
|
|
||||||
|
return monthLabel ? `${day}. ${monthLabel} ${year}` : dateString;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Normalize time casing for UI consistency.
|
||||||
|
function formatEventTime(timeString) {
|
||||||
|
return timeString.replace('UHR', 'Uhr').trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Map diet keys to readable labels while keeping unknown values untouched.
|
||||||
|
function getDietLabel(diet) {
|
||||||
|
const labels = {
|
||||||
|
VEGGIE: 'Vegetarisch',
|
||||||
|
VEGAN: 'Vegan',
|
||||||
|
FLEISCH: 'Fleisch',
|
||||||
|
FISCH: 'Fisch'
|
||||||
|
};
|
||||||
|
|
||||||
|
return labels[diet] || diet;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compose and inject the full detail UI for a single event.
|
||||||
function renderDetailPage(event) {
|
function renderDetailPage(event) {
|
||||||
|
// Core display values and resilient fallbacks for optional data fields.
|
||||||
const displayDate = formatEventDate(event.date);
|
const displayDate = formatEventDate(event.date);
|
||||||
const displayTime = formatEventTime(event.time);
|
const displayTime = formatEventTime(event.time);
|
||||||
|
const dietLabel = getDietLabel(event.diet);
|
||||||
const eventCategory = event.category || 'EVENT';
|
const eventCategory = event.category || 'EVENT';
|
||||||
const hostName = event.host?.name || 'Host';
|
const hostName = event.host?.name || 'Host';
|
||||||
|
const hostInitial = (event.host?.initial || hostName.charAt(0) || 'H').charAt(0).toUpperCase();
|
||||||
const hostMessage = Array.isArray(event.hostMessage) && event.hostMessage.length > 0
|
const hostMessage = Array.isArray(event.hostMessage) && event.hostMessage.length > 0
|
||||||
? event.hostMessage
|
? event.hostMessage
|
||||||
: ['Der Host hat für dieses Event noch keine Nachricht hinterlegt.'];
|
: ['Der Host hat für dieses Event noch keine Nachricht hinterlegt.'];
|
||||||
const menuItems = Array.isArray(event.menu) && event.menu.length > 0
|
const menuItems = Array.isArray(event.menu) && event.menu.length > 0
|
||||||
? event.menu
|
? event.menu
|
||||||
: ['Menü wird in Kürze bekannt gegeben.'];
|
: ['Menü wird in Kuerze bekannt gegeben.'];
|
||||||
const specifications = Array.isArray(event.specifications) && event.specifications.length > 0
|
const specifications = Array.isArray(event.specifications) && event.specifications.length > 0
|
||||||
? event.specifications : [];
|
? event.specifications
|
||||||
|
: [];
|
||||||
|
const participants = Array.isArray(event.participants) ? event.participants : [];
|
||||||
|
const galleryImages = Array.isArray(event.gallery) && event.gallery.length > 0
|
||||||
|
? event.gallery
|
||||||
|
: [event.image, event.image, event.image];
|
||||||
|
const visibleParticipants = participants.slice(0, 6);
|
||||||
const registrationMap = getRegistrationMap();
|
const registrationMap = getRegistrationMap();
|
||||||
const participants = getResolvedParticipants(event, registrationMap);
|
const extraRegistrations = countRegistrationsForEvent(registrationMap, event.id);
|
||||||
const isOwnEvent = isEventOwnedByCurrentUser(event, currentUser);
|
const remainingParticipants = Math.max(0, participants.length + extraRegistrations - visibleParticipants.length);
|
||||||
const participantNamesForView = participants
|
|
||||||
.map(name => getParticipantNameForViewer(name, isOwnEvent))
|
|
||||||
.filter(Boolean);
|
|
||||||
const galleryImages = Array.isArray(event.gallery) ? event.gallery.filter(Boolean) : [];
|
|
||||||
const resolvedGalleryImages = galleryImages.length > 0
|
|
||||||
? galleryImages
|
|
||||||
: [getPlaceholderImageByEventType(event)];
|
|
||||||
const galleryLayoutClass = resolvedGalleryImages.length === 1
|
|
||||||
? 'detail-gallery detail-gallery-large detail-gallery-large--single'
|
|
||||||
: 'detail-gallery detail-gallery-large';
|
|
||||||
const galleryMarkup = resolvedGalleryImages.length > 0
|
|
||||||
? `<div class="${galleryLayoutClass}">
|
|
||||||
${resolvedGalleryImages.slice(0, 9).map((img, index) => `
|
|
||||||
<button class="detail-gallery-item" type="button" aria-label="Bild ${index + 1} gross anzeigen" data-fullsrc="${img}">
|
|
||||||
<img src="${img}" alt="${event.title} Bild ${index + 1}" class="detail-gallery-image">
|
|
||||||
</button>
|
|
||||||
`).join('')}
|
|
||||||
</div>` : '';
|
|
||||||
|
|
||||||
const visibleParticipants = participantNamesForView.slice(0, 6);
|
|
||||||
const remainingParticipants = Math.max(0, participantNamesForView.length - visibleParticipants.length);
|
|
||||||
const totalGuests = Number.isFinite(event.spots) ? event.spots : 0;
|
const totalGuests = Number.isFinite(event.spots) ? event.spots : 0;
|
||||||
const confirmedGuests = participants.length;
|
const confirmedGuests = participants.length + extraRegistrations;
|
||||||
const freePlaces = Math.max(0, totalGuests - confirmedGuests);
|
const freePlaces = Math.max(0, totalGuests - confirmedGuests);
|
||||||
const isFull = freePlaces === 0;
|
const isFull = freePlaces === 0;
|
||||||
const isRegistrationClosed = isRegistrationClosedForEvent(event);
|
const isRegistrationClosed = isRegistrationClosedForEvent(event);
|
||||||
const deregInfo = getDeregistrationInfo(event);
|
const isOwnEvent = isEventOwnedByCurrentUser(event, currentUser);
|
||||||
const userRegistrations = getRegistrationIdsForUser(registrationMap, currentUser);
|
const userRegistrations = currentUser?.email && Array.isArray(registrationMap[currentUser.email])
|
||||||
|
? registrationMap[currentUser.email].map(id => Number(id))
|
||||||
|
: [];
|
||||||
const isRegistered = userRegistrations.includes(Number(event.id));
|
const isRegistered = userRegistrations.includes(Number(event.id));
|
||||||
const isListedParticipant = isUserListedInEventParticipants(event, currentUser);
|
const isListedParticipant = isUserListedInEventParticipants(event, currentUser);
|
||||||
const hasAddressAccess = isRegistered || isListedParticipant || isOwnEvent;
|
const hasAddressAccess = isRegistered || isListedParticipant;
|
||||||
const isCanceled = event.status === 'canceled';
|
const actionButtonLabel = isOwnEvent
|
||||||
|
? 'Dein Event!'
|
||||||
const actionButtonLabel = isCanceled ? 'Abgesagt'
|
: !currentUser
|
||||||
: isOwnEvent ? 'Dein Event!'
|
? 'Einloggen'
|
||||||
: !currentUser ? 'Einloggen'
|
: isRegistered
|
||||||
: isRegistered ? (deregInfo.isClosed ? 'Abmeldung geschlossen' : 'Abmelden')
|
? 'Abmelden'
|
||||||
: isRegistrationClosed ? 'Anmeldung geschlossen'
|
: isRegistrationClosed
|
||||||
: 'Anmelden';
|
? 'Anmeldung geschlossen'
|
||||||
const actionButtonDisabled = isCanceled
|
: 'Anmelden';
|
||||||
|| isOwnEvent
|
const actionButtonDisabled = isOwnEvent || (!isRegistered && (isFull || isRegistrationClosed));
|
||||||
|| (!isRegistered && (isFull || isRegistrationClosed))
|
const actionButtonVariantClass = isOwnEvent
|
||||||
|| (isRegistered && deregInfo.isClosed);
|
? ' detail-primary-btn-own'
|
||||||
const actionButtonVariantClass = isOwnEvent ? ' button-primary-eigener-event'
|
: isRegistered
|
||||||
: (isRegistered || isRegistrationClosed) ? ' button-primary-abmelden '
|
? ' detail-primary-btn-danger'
|
||||||
: ' button-primary ';
|
: isRegistrationClosed
|
||||||
|
? ' detail-primary-btn-danger'
|
||||||
|
: ' detail-primary-btn-register';
|
||||||
const shouldRevealAddress = Boolean(event.address) && isAddressVisibleWindow(event) && hasAddressAccess;
|
const shouldRevealAddress = Boolean(event.address) && isAddressVisibleWindow(event) && hasAddressAccess;
|
||||||
let addressMessage = 'Wenn du dich anmeldest, wird die Adresse für diesen Event wird 24 Stunden vorher genau hier sichtbar sein.';
|
|
||||||
|
|
||||||
if (isCanceled) {
|
|
||||||
addressMessage = 'Dieses Event wurde leider vom Gastgeber abgesagt.';
|
|
||||||
} else if (isOwnEvent) {
|
|
||||||
addressMessage = 'Deine Adresse für diesen Event wird 24 Stunden vorher genau hier für alle Teilnehmer sichtbar sein';
|
|
||||||
} else if (hasAddressAccess) {
|
|
||||||
addressMessage = 'Vielen Dank für die Anmeldung! Die Adresse für diesen Event wird 24 Stunden vorher genau hier sichtbar sein.';
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!isCanceled && isEventPastAddressWindow(event)) {
|
|
||||||
addressMessage = 'Vielen Dank, dass du an diesem Event teilgenommen hast.';
|
|
||||||
}
|
|
||||||
|
|
||||||
const addressPanelMarkup = shouldRevealAddress
|
const addressPanelMarkup = shouldRevealAddress
|
||||||
? `<article class="detail-panel"><h2 class="detail-section-title">Adresse</h2><p>${event.address}</p></article>`
|
? `
|
||||||
: `<article class="detail-panel"><h2 class="detail-section-title">Adresse</h2><p>${addressMessage}</p></article>`;
|
<article class="detail-panel detail-panel-compact">
|
||||||
|
<h2 class="detail-section-title">Adresse</h2>
|
||||||
|
<p>${event.address}</p>
|
||||||
|
</article>
|
||||||
|
`
|
||||||
|
: '';
|
||||||
const detailChips = [
|
const detailChips = [
|
||||||
`<span class="event-tag">${eventCategory}</span>`,
|
`<span class="event-tag">${eventCategory}</span>`,
|
||||||
...event.diet.split(', ').filter(d => d.trim() && d !== 'Keine Angabe').map(d => `<span class="event-tag">${getDietLabel(d.trim())}</span>`),
|
`<span class="event-tag">${dietLabel}</span>`,
|
||||||
...specifications.map(item => `<span class="event-tag">${item}</span>`)
|
...specifications.map(item => `<span class="event-tag">${item}</span>`)
|
||||||
].join('');
|
].join('');
|
||||||
|
|
||||||
detailcontainer.innerHTML = `
|
// Render complete detail page layout including:
|
||||||
<section class="detail-hero">
|
// hero metadata, host card, menu, participants, gallery and sticky action bar.
|
||||||
<div class="detail-top-row">
|
detailContainer.innerHTML = `
|
||||||
<span class="event-location"><img src="${locationIconPath}" class="icon" alt="">${event.location}</span>
|
<div class="detail-page">
|
||||||
<span class="event-date-time"><img src="${calendarIconPath}" class="icon" alt=""> ${displayDate} | ${displayTime}</span>
|
<a class="detail-back" href="event_overview.html">
|
||||||
<span class="event-date-time"><img src="${gastIconPath}" class="icon" alt="">${confirmedGuests}/${totalGuests}</span>
|
<span aria-hidden="true">‹</span>
|
||||||
</div>
|
Alle Events
|
||||||
<h1 class="detail-title">${event.title}</h1>
|
</a>
|
||||||
<div class="event-meta-row detail-chip-row">${detailChips}</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<section class="detail-content-grid">
|
<section class="detail-hero">
|
||||||
<div class="detail-side-stack">
|
<div class="detail-top-row">
|
||||||
<article class="detail-panel">
|
<span class="event-location">
|
||||||
<header class="host-header">
|
<img src="${locationIconPath}" alt="">
|
||||||
<span class="host-role">Host</span>
|
${event.location}
|
||||||
<span class="host-name">${hostName}</span>
|
</span>
|
||||||
</header>
|
<p class="event-date-time">${displayDate} | ${displayTime} | ${confirmedGuests}/${totalGuests} Gaeste</p>
|
||||||
${hostMessage.map(paragraph => `<p>${paragraph}</p>`).join('')}
|
|
||||||
</article>
|
|
||||||
|
|
||||||
<article class="detail-panel">
|
|
||||||
<h2 class="detail-section-title">Menu</h2>
|
|
||||||
<ul class="detail-menu-list">
|
|
||||||
${menuItems.map(item => `<li>${item}</li>`).join('')}
|
|
||||||
</ul>
|
|
||||||
</article>
|
|
||||||
|
|
||||||
<article class="detail-panel">
|
|
||||||
<div class="detail-participants-head">
|
|
||||||
<h2 class="detail-section-title">Teilnehmer</h2>
|
|
||||||
<button type="button" class="detail-participants-link" data-show-all-participants>Alle ansehen</button>
|
|
||||||
</div>
|
|
||||||
<div class="detail-avatar-row" data-participants-row>
|
|
||||||
${visibleParticipants.map(name => `<span class="participant-avatar">${name.charAt(0).toUpperCase()}</span>`).join('')}
|
|
||||||
${remainingParticipants > 0 ? `<span class="participant-more">+${remainingParticipants}</span>` : ''}
|
|
||||||
</div>
|
|
||||||
<div class="detail-participants-full hidden" data-participants-full>
|
|
||||||
${participantNamesForView.map(name => `
|
|
||||||
<div class="detail-participant-item">
|
|
||||||
<span class="participant-name">${name}</span>
|
|
||||||
</div>
|
|
||||||
`).join('')}
|
|
||||||
</div>
|
|
||||||
</article>
|
|
||||||
|
|
||||||
${addressPanelMarkup}
|
|
||||||
</div>
|
|
||||||
${galleryMarkup}
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<section class="detail-action-bar">
|
|
||||||
<div class="detail-action-summary">
|
|
||||||
<small class="detail-action-meta">
|
|
||||||
<span class="event-location detail-action-location"><img src="${locationIconPath}" alt="">${event.location}</span>
|
|
||||||
<span class="event-date-time detail-action-location"><img src="${calendarIconPath}" alt=""> ${displayDate} | ${displayTime}</span>
|
|
||||||
<span class="event-gast detail-action-location"><img src="${gastIconPath}" alt="">${confirmedGuests}/${totalGuests}</span>
|
|
||||||
</small>
|
|
||||||
<strong>${event.title}</strong>
|
|
||||||
</div>
|
|
||||||
<div class="detail-action-btn-wrap">
|
|
||||||
<div class="detail-action-row" style="margin-left:auto; display:flex; gap:12px; align-items:center;">
|
|
||||||
${isFull ? `
|
|
||||||
<div class="detail-dereg-column">
|
|
||||||
<button class="button-plaetze event-spots-full" type="button" disabled>Ausgebucht</button>
|
|
||||||
<small class="detail-dereg-hint detail-dereg-hint--placeholder"> </small>
|
|
||||||
</div>
|
|
||||||
` : ''}
|
|
||||||
|
|
||||||
${(!isFull || isRegistered) ? `
|
|
||||||
<div class="detail-dereg-column">
|
|
||||||
<button class="${actionButtonVariantClass.trim()}" type="button" data-register-button ${actionButtonDisabled ? 'disabled' : ''}>
|
|
||||||
${actionButtonLabel}
|
|
||||||
</button>
|
|
||||||
${isRegistered && deregInfo.daysLeft !== null ? `
|
|
||||||
<small class="detail-dereg-hint${deregInfo.isClosed ? ' detail-dereg-hint--closed' : ''}">
|
|
||||||
${deregInfo.isClosed ? 'Abmeldefrist abgelaufen'
|
|
||||||
: deregInfo.daysLeft === 1 ? 'Noch 1 Tag zur Abmeldung'
|
|
||||||
: `Noch ${deregInfo.daysLeft} Tage zur Abmeldung`}
|
|
||||||
</small>
|
|
||||||
` : ''}
|
|
||||||
</div>
|
|
||||||
` : ''}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<h1 class="detail-title">${event.title}</h1>
|
||||||
</section>
|
<div class="event-meta-row detail-chip-row">
|
||||||
|
${detailChips}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
<div class="detail-lightbox" aria-hidden="true">
|
<section class="detail-content-grid">
|
||||||
<div class="detail-lightbox-backdrop" data-close-lightbox="true"></div>
|
<div class="detail-side-stack">
|
||||||
<figure class="detail-lightbox-content" role="dialog" aria-modal="true" aria-label="Bildansicht">
|
<article class="host-card detail-panel">
|
||||||
<button class="detail-lightbox-close" type="button" aria-label="Schliessen">×</button>
|
<header class="host-header">
|
||||||
<img class="detail-lightbox-image" src="" alt="Grossansicht Eventbild">
|
<span class="host-avatar">${hostInitial}</span>
|
||||||
</figure>
|
<span class="host-name">${hostName}</span>
|
||||||
|
<span class="host-role">Host</span>
|
||||||
|
</header>
|
||||||
|
${hostMessage.map(paragraph => `<p>${paragraph}</p>`).join('')}
|
||||||
|
</article>
|
||||||
|
|
||||||
|
<article class="detail-panel detail-panel-compact">
|
||||||
|
<h2 class="detail-section-title">Menue</h2>
|
||||||
|
<ul class="detail-menu-list">
|
||||||
|
${menuItems.map(item => `<li>${item}</li>`).join('')}
|
||||||
|
</ul>
|
||||||
|
</article>
|
||||||
|
|
||||||
|
<article class="detail-panel detail-panel-compact">
|
||||||
|
<div class="detail-participants-head">
|
||||||
|
<h2 class="detail-section-title">Teilnehmer</h2>
|
||||||
|
<a href="#" class="detail-participants-link">Alle ansehen</a>
|
||||||
|
</div>
|
||||||
|
<div class="detail-avatar-row">
|
||||||
|
${visibleParticipants.map(name => `<span class="participant-avatar">${name.charAt(0).toUpperCase()}</span>`).join('')}
|
||||||
|
${remainingParticipants > 0 ? `<span class="participant-more">+${remainingParticipants}</span>` : ''}
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
|
||||||
|
${addressPanelMarkup}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="detail-gallery detail-gallery-large">
|
||||||
|
${galleryImages.slice(0, 9).map((img, index) => `
|
||||||
|
<button class="detail-gallery-item" type="button" aria-label="Bild ${index + 1} gross anzeigen" data-fullsrc="${img}">
|
||||||
|
<img src="${img}" alt="${event.title} Bild ${index + 1}" class="detail-gallery-image">
|
||||||
|
</button>
|
||||||
|
`).join('')}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="detail-action-bar">
|
||||||
|
<div class="detail-action-summary">
|
||||||
|
<small class="detail-action-meta">
|
||||||
|
<span class="event-location detail-action-location">
|
||||||
|
<img src="${locationIconPath}" alt="">
|
||||||
|
${event.location}
|
||||||
|
</span>
|
||||||
|
<span class="detail-action-meta-text">| ${displayDate} | ${displayTime} | ${confirmedGuests}/${totalGuests} Gaeste</span>
|
||||||
|
</small>
|
||||||
|
<strong>${event.title}</strong>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="detail-action-buttons">
|
||||||
|
<span class="detail-spots-pill${isFull ? ' detail-spots-pill-full' : ''}">
|
||||||
|
${isFull ? 'AUSGEBUCHT' : `${freePlaces} Plaetze frei`}
|
||||||
|
</span>
|
||||||
|
<button class="detail-primary-btn${actionButtonVariantClass}" type="button" data-register-button ${actionButtonDisabled ? 'disabled' : ''}>
|
||||||
|
${actionButtonLabel}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<div class="detail-lightbox" aria-hidden="true">
|
||||||
|
<div class="detail-lightbox-backdrop" data-close-lightbox="true"></div>
|
||||||
|
<figure class="detail-lightbox-content" role="dialog" aria-modal="true" aria-label="Bildansicht">
|
||||||
|
<button class="detail-lightbox-close" type="button" aria-label="Schliessen">×</button>
|
||||||
|
<img class="detail-lightbox-image" src="" alt="Grossansicht Eventbild">
|
||||||
|
</figure>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
// DOM references after render
|
// ---------------------------------------------------------
|
||||||
const lightbox = detailcontainer.querySelector('.detail-lightbox');
|
// Lightbox behavior for gallery images:
|
||||||
const lightboxImage = detailcontainer.querySelector('.detail-lightbox-image');
|
// open on image click, close via backdrop, close button or ESC.
|
||||||
const lightboxClose = detailcontainer.querySelector('.detail-lightbox-close');
|
// ---------------------------------------------------------
|
||||||
const galleryButtons = detailcontainer.querySelectorAll('.detail-gallery-item');
|
const lightbox = detailContainer.querySelector('.detail-lightbox');
|
||||||
const registerButton = detailcontainer.querySelector('[data-register-button]');
|
const lightboxImage = detailContainer.querySelector('.detail-lightbox-image');
|
||||||
|
const lightboxClose = detailContainer.querySelector('.detail-lightbox-close');
|
||||||
|
const galleryButtons = detailContainer.querySelectorAll('.detail-gallery-item');
|
||||||
|
const registerButton = detailContainer.querySelector('[data-register-button]');
|
||||||
|
|
||||||
// Eigene Events immer deaktiviert
|
// Harte Absicherung: Eigene Events sind auf der Detailseite immer deaktiviert.
|
||||||
if (registerButton && isOwnEvent) {
|
if (registerButton && isOwnEvent) {
|
||||||
registerButton.disabled = true;
|
registerButton.disabled = true;
|
||||||
registerButton.textContent = 'Dein Event!';
|
registerButton.textContent = 'Dein Event!';
|
||||||
registerButton.setAttribute('aria-disabled', 'true');
|
registerButton.setAttribute('aria-disabled', 'true');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Anmeldung / Abmeldung mit Bestätigungs-Modal
|
// Anmeldung toggeln und im lokalen Registrierungs-Store persistieren.
|
||||||
if (registerButton) {
|
if (registerButton) {
|
||||||
registerButton.addEventListener('click', () => {
|
registerButton.addEventListener('click', () => {
|
||||||
if (isOwnEvent) return;
|
if (isOwnEvent) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (!currentUser || !currentUser.email) {
|
if (!currentUser || !currentUser.email) {
|
||||||
window.location.href = 'login.html';
|
window.location.href = 'login.html';
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const alreadyRegistered = (() => {
|
const nextRegistrationMap = getRegistrationMap();
|
||||||
const map = getRegistrationMap();
|
const currentList = Array.isArray(nextRegistrationMap[currentUser.email])
|
||||||
const ids = Array.isArray(map[currentUser.email])
|
? nextRegistrationMap[currentUser.email].map(id => Number(id))
|
||||||
? map[currentUser.email].map(id => Number(id)) : [];
|
: [];
|
||||||
return ids.includes(Number(event.id));
|
const registrationSet = new Set(currentList);
|
||||||
})();
|
|
||||||
|
|
||||||
if (alreadyRegistered) {
|
|
||||||
const modal = document.getElementById('unregister-confirm-modal');
|
|
||||||
if (modal) modal.classList.add('show');
|
|
||||||
|
|
||||||
document.getElementById('confirm-unregister-btn').onclick = () => {
|
|
||||||
modal.classList.remove('show');
|
|
||||||
const map = getRegistrationMap();
|
|
||||||
const ids = new Set((map[currentUser.email] || []).map(id => Number(id)));
|
|
||||||
ids.delete(Number(event.id));
|
|
||||||
map[currentUser.email] = Array.from(ids);
|
|
||||||
setRegistrationMap(map);
|
|
||||||
const snackbar = document.getElementById('snackbar');
|
|
||||||
if (snackbar) {
|
|
||||||
snackbar.textContent = 'Du wurdest erfolgreich abgemeldet.';
|
|
||||||
snackbar.classList.add('snackbar--danger', 'snackbar--visible');
|
|
||||||
setTimeout(() => {
|
|
||||||
snackbar.classList.remove('snackbar--visible');
|
|
||||||
setTimeout(() => snackbar.classList.remove('snackbar--danger'), 400);
|
|
||||||
}, 4000);
|
|
||||||
}
|
|
||||||
renderDetailPage(event);
|
|
||||||
};
|
|
||||||
|
|
||||||
const closeUnregister = () => modal.classList.remove('show');
|
|
||||||
document.getElementById('unregister-modal-close')?.addEventListener('click', closeUnregister);
|
|
||||||
document.getElementById('unregister-modal-cancel')?.addEventListener('click', closeUnregister);
|
|
||||||
modal.addEventListener('click', e => { if (e.target === modal) closeUnregister(); });
|
|
||||||
|
|
||||||
|
if (registrationSet.has(Number(event.id))) {
|
||||||
|
registrationSet.delete(Number(event.id));
|
||||||
} else if (!isFull && !isRegistrationClosed) {
|
} else if (!isFull && !isRegistrationClosed) {
|
||||||
const modal = document.getElementById('register-confirm-modal');
|
registrationSet.add(Number(event.id));
|
||||||
if (modal) modal.classList.add('show');
|
|
||||||
|
|
||||||
document.getElementById('confirm-register-btn').onclick = () => {
|
|
||||||
modal.classList.remove('show');
|
|
||||||
const map = getRegistrationMap();
|
|
||||||
const ids = new Set((map[currentUser.email] || []).map(id => Number(id)));
|
|
||||||
ids.add(Number(event.id));
|
|
||||||
map[currentUser.email] = Array.from(ids);
|
|
||||||
setRegistrationMap(map);
|
|
||||||
const snackbar = document.getElementById('snackbar');
|
|
||||||
if (snackbar) {
|
|
||||||
snackbar.textContent = 'Du wurdest erfolgreich angemeldet.';
|
|
||||||
snackbar.classList.add('snackbar--visible');
|
|
||||||
setTimeout(() => snackbar.classList.remove('snackbar--visible'), 4000);
|
|
||||||
}
|
|
||||||
renderDetailPage(event);
|
|
||||||
};
|
|
||||||
|
|
||||||
const closeRegister = () => modal.classList.remove('show');
|
|
||||||
document.getElementById('register-modal-close')?.addEventListener('click', closeRegister);
|
|
||||||
document.getElementById('register-modal-cancel')?.addEventListener('click', closeRegister);
|
|
||||||
modal.addEventListener('click', e => { if (e.target === modal) closeRegister(); });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
nextRegistrationMap[currentUser.email] = Array.from(registrationSet);
|
||||||
|
setRegistrationMap(nextRegistrationMap);
|
||||||
|
|
||||||
|
// Re-Render aktualisiert Buttonzustand und CTA ohne Seitenreload.
|
||||||
|
renderDetailPage(event);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// "Alle ansehen": Teilnehmerliste aufklappen / zuklappen
|
// Central close helper to keep all close paths consistent.
|
||||||
const showAllBtn = detailcontainer.querySelector('[data-show-all-participants]');
|
|
||||||
const avatarRow = detailcontainer.querySelector('[data-participants-row]');
|
|
||||||
const fullList = detailcontainer.querySelector('[data-participants-full]');
|
|
||||||
|
|
||||||
if (showAllBtn && avatarRow && fullList) {
|
|
||||||
showAllBtn.addEventListener('click', () => {
|
|
||||||
const isExpanded = !fullList.classList.contains('hidden');
|
|
||||||
fullList.classList.toggle('hidden');
|
|
||||||
avatarRow.classList.toggle('hidden');
|
|
||||||
showAllBtn.textContent = isExpanded ? 'Alle ansehen' : 'Weniger anzeigen';
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Lightbox
|
|
||||||
function closeLightbox() {
|
function closeLightbox() {
|
||||||
if (!lightbox) return;
|
if (!lightbox) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
lightbox.classList.remove('is-open');
|
lightbox.classList.remove('is-open');
|
||||||
lightbox.setAttribute('aria-hidden', 'true');
|
lightbox.setAttribute('aria-hidden', 'true');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (lightbox && lightboxImage) {
|
if (lightbox && lightboxImage) {
|
||||||
|
// Open with selected image source.
|
||||||
galleryButtons.forEach(button => {
|
galleryButtons.forEach(button => {
|
||||||
button.addEventListener('click', () => {
|
button.addEventListener('click', () => {
|
||||||
const imageSrc = button.getAttribute('data-fullsrc');
|
const imageSrc = button.getAttribute('data-fullsrc');
|
||||||
if (!imageSrc) return;
|
if (!imageSrc) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
lightboxImage.src = imageSrc;
|
lightboxImage.src = imageSrc;
|
||||||
lightbox.classList.add('is-open');
|
lightbox.classList.add('is-open');
|
||||||
lightbox.setAttribute('aria-hidden', 'false');
|
lightbox.setAttribute('aria-hidden', 'false');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Close when user clicks on backdrop.
|
||||||
lightbox.addEventListener('click', event => {
|
lightbox.addEventListener('click', event => {
|
||||||
const target = event.target;
|
const target = event.target;
|
||||||
if (target instanceof HTMLElement && target.hasAttribute('data-close-lightbox')) {
|
if (target instanceof HTMLElement && target.hasAttribute('data-close-lightbox')) {
|
||||||
@ -608,11 +492,15 @@
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Close via dedicated icon/button.
|
||||||
lightboxClose?.addEventListener('click', closeLightbox);
|
lightboxClose?.addEventListener('click', closeLightbox);
|
||||||
|
|
||||||
|
// Close with keyboard for accessibility.
|
||||||
document.addEventListener('keydown', event => {
|
document.addEventListener('keydown', event => {
|
||||||
if (event.key === 'Escape') closeLightbox();
|
if (event.key === 'Escape') {
|
||||||
|
closeLightbox();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@ -1,9 +1,7 @@
|
|||||||
document.addEventListener('DOMContentLoaded', () => {
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
const EVENTS_STORAGE_KEY = 'socialCookingEvents';
|
const EVENTS_STORAGE_KEY = 'socialCookingEvents';
|
||||||
const CURRENT_USER_KEY = 'socialCookingCurrentUser';
|
const CURRENT_USER_KEY = 'socialCookingCurrentUser';
|
||||||
const USERS_STORAGE_KEY = 'socialCookingUsers';
|
|
||||||
const REGISTRATION_STORAGE_KEY = 'socialCookingRegistrations';
|
const REGISTRATION_STORAGE_KEY = 'socialCookingRegistrations';
|
||||||
const INFO_MODAL_SHOWN_KEY = 'infoModalShownOnFirstLogin';
|
|
||||||
// -------------------------------------------------------------
|
// -------------------------------------------------------------
|
||||||
// DOM references used throughout the page lifecycle.
|
// DOM references used throughout the page lifecycle.
|
||||||
// -------------------------------------------------------------
|
// -------------------------------------------------------------
|
||||||
@ -11,19 +9,13 @@
|
|||||||
const filterButtons = document.querySelectorAll('.category-item');
|
const filterButtons = document.querySelectorAll('.category-item');
|
||||||
const locationFilter = document.getElementById('location-filter');
|
const locationFilter = document.getElementById('location-filter');
|
||||||
const dateFilter = document.getElementById('date-filter');
|
const dateFilter = document.getElementById('date-filter');
|
||||||
const locationIconPath = 'assets/icon_location.svg';
|
const locationIconPath = 'assets/location-pin.svg';
|
||||||
const calendarIconPath = 'assets/icon_calendar.svg';
|
|
||||||
const gastIconPath = 'assets/icon_gast.svg';
|
|
||||||
|
|
||||||
|
|
||||||
// -------------------------------------------------------------
|
// -------------------------------------------------------------
|
||||||
// In-memory state for fetched events and currently active filters.
|
// In-memory state for fetched events and currently active category.
|
||||||
// Separate state for category, diet, and allergie selections.
|
|
||||||
// -------------------------------------------------------------
|
// -------------------------------------------------------------
|
||||||
let allEvents = [];
|
let allEvents = [];
|
||||||
let activeCategory = 'ALLE';
|
let activeCategory = 'ALLE';
|
||||||
let activeDiets = new Set();
|
|
||||||
let activeAllergies = new Set();
|
|
||||||
const currentUser = getCurrentUser();
|
const currentUser = getCurrentUser();
|
||||||
|
|
||||||
function getCurrentUser() {
|
function getCurrentUser() {
|
||||||
@ -36,12 +28,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function getInfoModalShownKeyForUser(user) {
|
// Prueft, ob ein Event dem aktuellen Benutzer gehoert.
|
||||||
const email = String(user?.email || '').trim().toLowerCase();
|
|
||||||
return email ? `${INFO_MODAL_SHOWN_KEY}:${email}` : INFO_MODAL_SHOWN_KEY;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Prüft, ob ein Event dem aktuellen Benutzer gehört.
|
|
||||||
function isEventOwnedByCurrentUser(event, user) {
|
function isEventOwnedByCurrentUser(event, user) {
|
||||||
if (!event || !user) {
|
if (!event || !user) {
|
||||||
return false;
|
return false;
|
||||||
@ -79,58 +66,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function getStoredUsers() {
|
|
||||||
try {
|
|
||||||
const stored = localStorage.getItem(USERS_STORAGE_KEY);
|
|
||||||
return stored ? JSON.parse(stored) : [];
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Benutzerdaten konnten nicht gelesen werden.', error);
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function getUserDisplayName(user) {
|
|
||||||
if (!user) {
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
|
|
||||||
const firstName = String(user.vorname || '').trim();
|
|
||||||
const lastName = String(user.nachname || '').trim();
|
|
||||||
const fullName = `${firstName} ${lastName}`.trim();
|
|
||||||
|
|
||||||
return (fullName || firstName || String(user.email || '').trim()).trim();
|
|
||||||
}
|
|
||||||
|
|
||||||
function getResolvedParticipants(event, registrationMap) {
|
|
||||||
const baseParticipants = Array.isArray(event.participants)
|
|
||||||
? event.participants.map(name => String(name || '').trim()).filter(Boolean)
|
|
||||||
: [];
|
|
||||||
const usersByEmail = new Map(
|
|
||||||
getStoredUsers().map(user => [String(user.email || '').trim().toLowerCase(), user])
|
|
||||||
);
|
|
||||||
const participantLookup = new Set(baseParticipants.map(name => name.toLowerCase()));
|
|
||||||
|
|
||||||
Object.entries(registrationMap || {}).forEach(([email, ids]) => {
|
|
||||||
const isRegisteredForEvent = Array.isArray(ids)
|
|
||||||
&& ids.map(id => Number(id)).includes(Number(event.id));
|
|
||||||
|
|
||||||
if (!isRegisteredForEvent) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const user = usersByEmail.get(String(email || '').trim().toLowerCase());
|
|
||||||
const displayName = getUserDisplayName(user) || String(email || '').trim();
|
|
||||||
const normalizedName = displayName.toLowerCase();
|
|
||||||
|
|
||||||
if (displayName && !participantLookup.has(normalizedName)) {
|
|
||||||
baseParticipants.push(displayName);
|
|
||||||
participantLookup.add(normalizedName);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return baseParticipants;
|
|
||||||
}
|
|
||||||
|
|
||||||
function setRegistrationMap(registrationMap) {
|
function setRegistrationMap(registrationMap) {
|
||||||
localStorage.setItem(REGISTRATION_STORAGE_KEY, JSON.stringify(registrationMap));
|
localStorage.setItem(REGISTRATION_STORAGE_KEY, JSON.stringify(registrationMap));
|
||||||
}
|
}
|
||||||
@ -153,13 +88,8 @@
|
|||||||
const savedCategory = sessionStorage.getItem('activeFilter') || 'ALLE';
|
const savedCategory = sessionStorage.getItem('activeFilter') || 'ALLE';
|
||||||
const savedLocation = sessionStorage.getItem('activeLocation') || 'ALLE_ORTE';
|
const savedLocation = sessionStorage.getItem('activeLocation') || 'ALLE_ORTE';
|
||||||
const savedDate = sessionStorage.getItem('activeDate') || '';
|
const savedDate = sessionStorage.getItem('activeDate') || '';
|
||||||
const savedDiets = sessionStorage.getItem('activeDiets') || '';
|
|
||||||
const savedAllergies = sessionStorage.getItem('activeAllergies') || '';
|
|
||||||
|
|
||||||
activeCategory = savedCategory;
|
activeCategory = savedCategory;
|
||||||
activeDiets = new Set(savedDiets ? savedDiets.split(',') : []);
|
|
||||||
activeAllergies = new Set(savedAllergies ? savedAllergies.split(',') : []);
|
|
||||||
|
|
||||||
if (locationFilter) {
|
if (locationFilter) {
|
||||||
locationFilter.value = hasOption(locationFilter, savedLocation) ? savedLocation : 'ALLE_ORTE';
|
locationFilter.value = hasOption(locationFilter, savedLocation) ? savedLocation : 'ALLE_ORTE';
|
||||||
}
|
}
|
||||||
@ -167,7 +97,6 @@
|
|||||||
dateFilter.value = savedDate;
|
dateFilter.value = savedDate;
|
||||||
}
|
}
|
||||||
|
|
||||||
updateDietAvailability();
|
|
||||||
applyFilters();
|
applyFilters();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Fehler:', error);
|
console.error('Fehler:', error);
|
||||||
@ -269,7 +198,7 @@
|
|||||||
: `${timeString} Uhr`;
|
: `${timeString} Uhr`;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Baut aus Eventdatum/-zeit ein Date-Objekt für Fristlogik und Vergleiche.
|
// Baut aus Eventdatum/-zeit ein Date-Objekt fuer Fristlogik und Vergleiche.
|
||||||
function parseEventDateTime(event) {
|
function parseEventDateTime(event) {
|
||||||
if (!event?.date) {
|
if (!event?.date) {
|
||||||
return null;
|
return null;
|
||||||
@ -287,42 +216,28 @@
|
|||||||
day = Number(isoDateMatch[3]);
|
day = Number(isoDateMatch[3]);
|
||||||
} else {
|
} else {
|
||||||
const monthMap = {
|
const monthMap = {
|
||||||
jan: 1,
|
JAN: 1,
|
||||||
januar: 1,
|
FEB: 2,
|
||||||
feb: 2,
|
'MÄR': 3,
|
||||||
februar: 2,
|
MRZ: 3,
|
||||||
'mär': 3,
|
APR: 4,
|
||||||
mrz: 3,
|
MAI: 5,
|
||||||
mar: 3,
|
JUN: 6,
|
||||||
maerz: 3,
|
JUL: 7,
|
||||||
märz: 3,
|
AUG: 8,
|
||||||
apr: 4,
|
SEP: 9,
|
||||||
april: 4,
|
OKT: 10,
|
||||||
mai: 5,
|
NOV: 11,
|
||||||
jun: 6,
|
DEZ: 12
|
||||||
juni: 6,
|
|
||||||
jul: 7,
|
|
||||||
juli: 7,
|
|
||||||
aug: 8,
|
|
||||||
august: 8,
|
|
||||||
sep: 9,
|
|
||||||
sept: 9,
|
|
||||||
september: 9,
|
|
||||||
okt: 10,
|
|
||||||
oktober: 10,
|
|
||||||
nov: 11,
|
|
||||||
november: 11,
|
|
||||||
dez: 12,
|
|
||||||
dezember: 12
|
|
||||||
};
|
};
|
||||||
const localizedMatch = dateValue.match(/^(\d{1,2})\.\s*([A-Za-zÄÖÜäöü]{3,9})\.?\s*(\d{4})$/);
|
const localizedMatch = dateValue.match(/^(\d{1,2})\.\s*([A-ZÄÖÜ]{3})\.\s*(\d{4})$/);
|
||||||
|
|
||||||
if (!localizedMatch) {
|
if (!localizedMatch) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
day = Number(localizedMatch[1]);
|
day = Number(localizedMatch[1]);
|
||||||
month = monthMap[String(localizedMatch[2]).toLowerCase()];
|
month = monthMap[localizedMatch[2]];
|
||||||
year = Number(localizedMatch[3]);
|
year = Number(localizedMatch[3]);
|
||||||
|
|
||||||
if (!month) {
|
if (!month) {
|
||||||
@ -337,7 +252,7 @@
|
|||||||
return new Date(year, month - 1, day, hours, minutes, 0, 0);
|
return new Date(year, month - 1, day, hours, minutes, 0, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Zählt eindeutige Registrierungen eines Events über alle Benutzer.
|
// Zaehlt eindeutige Registrierungen eines Events ueber alle Benutzer.
|
||||||
function countRegistrationsForEvent(registrationMap, eventId) {
|
function countRegistrationsForEvent(registrationMap, eventId) {
|
||||||
return Object.values(registrationMap).reduce((count, ids) => {
|
return Object.values(registrationMap).reduce((count, ids) => {
|
||||||
const hasEvent = Array.isArray(ids)
|
const hasEvent = Array.isArray(ids)
|
||||||
@ -347,7 +262,7 @@
|
|||||||
}, 0);
|
}, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Schliesst neue Anmeldungen ab 24h vor Start (inkl. bereits gestarteter Events).
|
// Schliesst neue Anmeldungen ab 12h vor Start (inkl. bereits gestarteter Events).
|
||||||
function isRegistrationClosedForEvent(event) {
|
function isRegistrationClosedForEvent(event) {
|
||||||
const eventDateTime = parseEventDateTime(event);
|
const eventDateTime = parseEventDateTime(event);
|
||||||
if (!eventDateTime || Number.isNaN(eventDateTime.getTime())) {
|
if (!eventDateTime || Number.isNaN(eventDateTime.getTime())) {
|
||||||
@ -355,9 +270,9 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
const msUntilStart = eventDateTime.getTime() - Date.now();
|
const msUntilStart = eventDateTime.getTime() - Date.now();
|
||||||
const twentyfourHoursInMs = 24 * 60 * 60 * 1000;
|
const twelveHoursInMs = 12 * 60 * 60 * 1000;
|
||||||
|
|
||||||
return msUntilStart <= twentyfourHoursInMs;
|
return msUntilStart <= twelveHoursInMs;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Safely verify whether a value exists in the given select element.
|
// Safely verify whether a value exists in the given select element.
|
||||||
@ -365,64 +280,38 @@
|
|||||||
return Array.from(selectElement.options).some(option => option.value === value);
|
return Array.from(selectElement.options).some(option => option.value === value);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Apply all filters together (category, diet, allergie, location, date), update button state, render and persist.
|
// Apply all filters together (category, location, date), update button state, render and persist.
|
||||||
function applyFilters() {
|
function applyFilters() {
|
||||||
const selectedLocation = locationFilter ? locationFilter.value : 'ALLE_ORTE';
|
const selectedLocation = locationFilter ? locationFilter.value : 'ALLE_ORTE';
|
||||||
const selectedDate = dateFilter ? dateFilter.value : '';
|
const selectedDate = dateFilter ? dateFilter.value : '';
|
||||||
|
|
||||||
// Update active states for all filter types
|
|
||||||
filterButtons.forEach(btn => {
|
filterButtons.forEach(btn => {
|
||||||
const isCategoryButton = btn.getAttribute('data-cat') !== null;
|
if (btn.getAttribute('data-cat') === activeCategory) {
|
||||||
const isDietButton = btn.getAttribute('data-diet') !== null;
|
btn.classList.add('active');
|
||||||
const isAllergieButton = btn.getAttribute('data-allergie') !== null;
|
} else {
|
||||||
|
btn.classList.remove('active');
|
||||||
if (isCategoryButton) {
|
|
||||||
if (btn.getAttribute('data-cat') === activeCategory) {
|
|
||||||
btn.classList.add('active');
|
|
||||||
} else {
|
|
||||||
btn.classList.remove('active');
|
|
||||||
}
|
|
||||||
} else if (isDietButton) {
|
|
||||||
if (activeDiets.has(btn.getAttribute('data-diet'))) {
|
|
||||||
btn.classList.add('active');
|
|
||||||
} else {
|
|
||||||
btn.classList.remove('active');
|
|
||||||
}
|
|
||||||
} else if (isAllergieButton) {
|
|
||||||
if (activeAllergies.has(btn.getAttribute('data-allergie'))) {
|
|
||||||
btn.classList.add('active');
|
|
||||||
} else {
|
|
||||||
btn.classList.remove('active');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const filtered = allEvents.filter(event => {
|
const filtered = allEvents.filter(event => {
|
||||||
if (event.status === 'canceled') return false;
|
// Lokal erstellte Events werden nicht in der allgemeinen Event-Uebersicht angezeigt.
|
||||||
|
if (event.source === 'local') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
const categoryMatch = activeCategory === 'ALLE' || event.category === activeCategory;
|
const categoryMatch = activeCategory === 'ALLE' || event.category === activeCategory;
|
||||||
|
|
||||||
// Diet filter: if no diets selected, show all. Otherwise, event MUST have at least one selected diet.
|
|
||||||
const dietMatch = activeDiets.size === 0 ||
|
|
||||||
(event.diet && event.diet.split(', ').some(d => activeDiets.has(d.trim())));
|
|
||||||
|
|
||||||
// Allergie filter: if no allergies selected, show all. Otherwise, event MUST have at least one selected allergie.
|
|
||||||
const allergieMatch = activeAllergies.size === 0 ||
|
|
||||||
(event.specifications && event.specifications.some(spec => activeAllergies.has(spec)));
|
|
||||||
|
|
||||||
const locationMatch = selectedLocation === 'ALLE_ORTE' || event.location === selectedLocation;
|
const locationMatch = selectedLocation === 'ALLE_ORTE' || event.location === selectedLocation;
|
||||||
const eventDateIso = parseEventDateToIso(event.date);
|
const eventDateIso = parseEventDateToIso(event.date);
|
||||||
const dateMatch = !selectedDate || eventDateIso === selectedDate;
|
const dateMatch = !selectedDate || eventDateIso === selectedDate;
|
||||||
|
|
||||||
return categoryMatch && dietMatch && allergieMatch && locationMatch && dateMatch;
|
return categoryMatch && locationMatch && dateMatch;
|
||||||
});
|
});
|
||||||
|
|
||||||
renderEvents(filtered);
|
renderEvents(filtered);
|
||||||
|
|
||||||
sessionStorage.setItem('activeFilter', activeCategory);
|
sessionStorage.setItem('activeFilter', activeCategory);
|
||||||
sessionStorage.setItem('activeLocation', selectedLocation);
|
sessionStorage.setItem('activeLocation', selectedLocation);
|
||||||
sessionStorage.setItem('activeDate', selectedDate);
|
sessionStorage.setItem('activeDate', selectedDate);
|
||||||
sessionStorage.setItem('activeDiets', Array.from(activeDiets).join(','));
|
|
||||||
sessionStorage.setItem('activeAllergies', Array.from(activeAllergies).join(','));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Render either:
|
// Render either:
|
||||||
@ -440,9 +329,9 @@
|
|||||||
<div class="empty-state">
|
<div class="empty-state">
|
||||||
<p class="empty-state-kicker">Keine Treffer</p>
|
<p class="empty-state-kicker">Keine Treffer</p>
|
||||||
<h3>Schade, aktuell gibt es hier keine Events.</h3>
|
<h3>Schade, aktuell gibt es hier keine Events.</h3>
|
||||||
<p>Starte dein eigenes Event und bringe die Community an deinen Tisch.</p>
|
<p>Starte dein eigenes Dinner und bringe die Community an deinen Tisch.</p>
|
||||||
<a class="empty-state-link" href="event_create.html">
|
<a class="empty-state-link" href="event_create.html">
|
||||||
<button class="button-primary" type="button">Event erstellen</button>
|
<button class="empty-state-btn" type="button">Event erstellen</button>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
@ -466,78 +355,53 @@
|
|||||||
const displayTime = formatEventTime(event.time);
|
const displayTime = formatEventTime(event.time);
|
||||||
|
|
||||||
// Capacity logic:
|
// Capacity logic:
|
||||||
// spots = total capacity, resolved participants = booked seats.
|
// spots = total capacity, participants.length = booked seats.
|
||||||
const resolvedParticipants = getResolvedParticipants(event, registrationMap);
|
const baseParticipants = Array.isArray(event.participants) ? event.participants.length : 0;
|
||||||
const bookedSeats = resolvedParticipants.length;
|
const extraRegistrations = countRegistrationsForEvent(registrationMap, event.id);
|
||||||
|
const bookedSeats = baseParticipants + extraRegistrations;
|
||||||
const totalCapacity = event.spots;
|
const totalCapacity = event.spots;
|
||||||
const freePlaces = Math.max(0, totalCapacity - bookedSeats);
|
const freePlaces = Math.max(0, totalCapacity - bookedSeats);
|
||||||
const isFull = freePlaces === 0;
|
const isFull = freePlaces === 0;
|
||||||
const isOwnEvent = isEventOwnedByCurrentUser(event, currentUser);
|
const isOwnEvent = isEventOwnedByCurrentUser(event, currentUser);
|
||||||
const isRegistered = userRegistrationSet.has(Number(event.id));
|
const isRegistered = userRegistrationSet.has(Number(event.id));
|
||||||
const isRegistrationClosed = isRegistrationClosedForEvent(event);
|
const isRegistrationClosed = isRegistrationClosedForEvent(event);
|
||||||
const isCanceled = event.status === 'canceled';
|
|
||||||
|
|
||||||
if (isCanceled) {
|
|
||||||
card.style.opacity = '0.6';
|
|
||||||
}
|
|
||||||
|
|
||||||
// Build optional specification chips only when data exists.
|
// Build optional specification chips only when data exists.
|
||||||
const specsChips = event.specifications && event.specifications.length > 0
|
const specsChips = event.specifications && event.specifications.length > 0
|
||||||
? event.specifications.map(spec => `<span class="event-tag">${spec}</span>`).join('')
|
? event.specifications.map(spec => `<span class="event-tag">${spec}</span>`).join('')
|
||||||
: '';
|
: '';
|
||||||
|
|
||||||
// Build diet tags: split by comma and create individual tags
|
const actionMarkup = isOwnEvent
|
||||||
const dietTags = event.diet && event.diet !== 'Keine Angabe' && event.diet !== '–'
|
? '<button class="btn-primary btn-primary-own" type="button" data-registration-action="own" disabled>Dein Event!</button>'
|
||||||
? event.diet.split(', ').map(d => `<span class="event-tag">${d.trim()}</span>`).join('')
|
: isRegistered
|
||||||
: '';
|
? '<button class="btn-primary btn-primary-danger" type="button" data-registration-action="unregister">Abmelden</button>'
|
||||||
|
: isRegistrationClosed
|
||||||
let actionMarkup = '';
|
? '<button class="btn-primary btn-primary-danger" type="button" data-registration-action="closed" disabled>Anmeldung geschlossen</button>'
|
||||||
if (isCanceled) {
|
: isFull
|
||||||
actionMarkup = '<button class="button-primary" type="button" disabled>Abgesagt</button>';
|
? ''
|
||||||
} else if (isOwnEvent) {
|
: !currentUser
|
||||||
actionMarkup = '<button class="button-primary-eigener-event" type="button" data-registration-action="own" disabled>Dein Event!</button>';
|
? '<button class="btn-primary btn-primary-register" type="button" data-registration-action="login">Anmelden</button>'
|
||||||
} else if (isRegistered) {
|
: '<button class="btn-primary btn-primary-register" type="button" data-registration-action="register">Anmelden</button>';
|
||||||
actionMarkup = isRegistrationClosed
|
|
||||||
? '<button class="button-primary-abmelden" type="button" disabled>Abmeldung geschlossen</button>'
|
|
||||||
: '<button class="button-primary-abmelden" type="button" data-registration-action="unregister">Abmelden</button>';
|
|
||||||
} else if (isRegistrationClosed) {
|
|
||||||
actionMarkup = '<button class="button-primary-abmelden" type="button" data-registration-action="closed" disabled>Anmeldung geschlossen</button>';
|
|
||||||
} else if (!isFull) {
|
|
||||||
if (!currentUser) {
|
|
||||||
actionMarkup = '<button class="button-primary btn-primary-register" type="button" data-registration-action="login">Anmelden</button>';
|
|
||||||
} else {
|
|
||||||
actionMarkup = '<button class="button-primary btn-primary-register" type="button" data-registration-action="register">Anmelden</button>';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let sideInfoMarkup = '';
|
|
||||||
if (isCanceled) {
|
|
||||||
sideInfoMarkup = '<span class="button-plaetze">Event abgesagt</span>';
|
|
||||||
} else if (!isRegistrationClosed) {
|
|
||||||
sideInfoMarkup = `<span class="button-plaetze${isFull ? ' event-spots-full' : ''}">${isFull ? 'Ausgebucht' : `${freePlaces} Plätze frei`}</span>`;
|
|
||||||
}
|
|
||||||
|
|
||||||
card.innerHTML = `
|
card.innerHTML = `
|
||||||
<div class="event-main">
|
<div class="event-main">
|
||||||
<div class="event-top-row">
|
<div class="event-top-row">
|
||||||
<span class="event-location">
|
<span class="event-location">
|
||||||
<img src="${locationIconPath}" class="icon" alt="">
|
<img src="${locationIconPath}" alt="">
|
||||||
${event.location}
|
${event.location}
|
||||||
</span>
|
</span>
|
||||||
<span class="event-date-time"> <img src="${calendarIconPath}" class="icon" alt=""> ${displayDate} | ${displayTime}
|
<p class="event-date-time">${displayDate} | ${displayTime} | ${bookedSeats}/${totalCapacity} Gaeste</p>
|
||||||
</span>
|
|
||||||
<span class="event-gast"> <img src="${gastIconPath}" class="icon" alt="Gaeste Icon">${bookedSeats}/${totalCapacity} </span>
|
|
||||||
</div>
|
</div>
|
||||||
<h2>${event.title}</h2>
|
<h2 class="event-title">${event.title}</h2>
|
||||||
<div class="event-meta-row">
|
<div class="event-meta-row">
|
||||||
<span class="event-tag">${event.category}</span>
|
<span class="event-tag">${event.category}</span>
|
||||||
${dietTags}
|
<span class="event-tag">${event.diet}</span>
|
||||||
${specsChips}
|
${specsChips}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="event-side${isFull ? ' event-side-full' : ''}">
|
<div class="event-side${isFull ? ' event-side-full' : ''}">
|
||||||
|
<span class="event-spots${isFull ? ' event-spots-full' : ''}">${isFull ? 'AUSGEBUCHT' : `${freePlaces} Plätze FREI`}</span>
|
||||||
${actionMarkup}
|
${actionMarkup}
|
||||||
${sideInfoMarkup}
|
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
@ -571,55 +435,12 @@
|
|||||||
: [];
|
: [];
|
||||||
const idSet = new Set(currentIds);
|
const idSet = new Set(currentIds);
|
||||||
|
|
||||||
// Anmelde-Modal öffnen
|
if (action === 'unregister') {
|
||||||
if (action === 'register' && !isFull && !isRegistrationClosed) {
|
idSet.delete(Number(event.id));
|
||||||
const modal = document.getElementById('register-confirm-modal');
|
|
||||||
if (modal) {
|
|
||||||
modal.classList.add('show');
|
|
||||||
document.getElementById('confirm-register-btn').onclick = () => {
|
|
||||||
modal.classList.remove('show');
|
|
||||||
const map = getRegistrationMap();
|
|
||||||
const ids = new Set((map[currentUser.email] || []).map(id => Number(id)));
|
|
||||||
ids.add(Number(event.id));
|
|
||||||
map[currentUser.email] = Array.from(ids);
|
|
||||||
setRegistrationMap(map);
|
|
||||||
const snackbar = document.getElementById('snackbar');
|
|
||||||
if (snackbar) {
|
|
||||||
snackbar.textContent = 'Du wurdest erfolgreich angemeldet.';
|
|
||||||
snackbar.classList.add('snackbar--visible');
|
|
||||||
setTimeout(() => snackbar.classList.remove('snackbar--visible'), 3000);
|
|
||||||
}
|
|
||||||
applyFilters();
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Abmelde-Modal öffnen
|
if (action === 'register' && !isFull && !isRegistrationClosed) {
|
||||||
if (action === 'unregister') {
|
idSet.add(Number(event.id));
|
||||||
const modal = document.getElementById('unregister-confirm-modal');
|
|
||||||
if (modal) {
|
|
||||||
modal.classList.add('show');
|
|
||||||
document.getElementById('confirm-unregister-btn').onclick = () => {
|
|
||||||
modal.classList.remove('show');
|
|
||||||
const map = getRegistrationMap();
|
|
||||||
const ids = new Set((map[currentUser.email] || []).map(id => Number(id)));
|
|
||||||
ids.delete(Number(event.id));
|
|
||||||
map[currentUser.email] = Array.from(ids);
|
|
||||||
setRegistrationMap(map);
|
|
||||||
const snackbar = document.getElementById('snackbar');
|
|
||||||
if (snackbar) {
|
|
||||||
snackbar.textContent = 'Du wurdest erfolgreich abgemeldet.';
|
|
||||||
snackbar.classList.add('snackbar--danger', 'snackbar--visible');
|
|
||||||
setTimeout(() => {
|
|
||||||
snackbar.classList.remove('snackbar--visible');
|
|
||||||
setTimeout(() => snackbar.classList.remove('snackbar--danger'), 400);
|
|
||||||
}, 3000);
|
|
||||||
}
|
|
||||||
applyFilters();
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
nextRegistrationMap[currentUser.email] = Array.from(idSet);
|
nextRegistrationMap[currentUser.email] = Array.from(idSet);
|
||||||
@ -632,107 +453,10 @@
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verhindert widersprüchliche Ernährungsformen:
|
// Category filter interactions.
|
||||||
// Fleisch/Fisch schliessen Vegetarisch/Vegan aus.
|
|
||||||
// Vegan schliesst alles andere (Fleisch, Fisch, Vegetarisch) aus.
|
|
||||||
// Vegetarisch schliesst Fleisch/Fisch aus, aber nicht Vegan.
|
|
||||||
function updateDietAvailability() {
|
|
||||||
const dietButtons = Array.from(filterButtons).filter(btn => btn.getAttribute('data-diet') !== null);
|
|
||||||
const meatFishButtons = dietButtons.filter(btn => ['Fleisch', 'Fisch'].includes(btn.getAttribute('data-diet')));
|
|
||||||
const plantButtons = dietButtons.filter(btn => ['Vegetarisch', 'Vegan'].includes(btn.getAttribute('data-diet')));
|
|
||||||
const vegetarischBtn = dietButtons.find(btn => btn.getAttribute('data-diet') === 'Vegetarisch');
|
|
||||||
const veganBtn = dietButtons.find(btn => btn.getAttribute('data-diet') === 'Vegan');
|
|
||||||
|
|
||||||
const hasVegan = activeDiets.has('Vegan');
|
|
||||||
const hasVegetarisch = activeDiets.has('Vegetarisch');
|
|
||||||
const hasMeatOrFish = meatFishButtons.some(btn => activeDiets.has(btn.getAttribute('data-diet')));
|
|
||||||
|
|
||||||
// If Vegan is selected, disable everything else
|
|
||||||
if (hasVegan) {
|
|
||||||
meatFishButtons.forEach(btn => {
|
|
||||||
btn.classList.add('disabled');
|
|
||||||
});
|
|
||||||
if (vegetarischBtn) {
|
|
||||||
vegetarischBtn.classList.add('disabled');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// If Vegetarisch is selected, disable only Fleisch/Fisch
|
|
||||||
else if (hasVegetarisch) {
|
|
||||||
meatFishButtons.forEach(btn => {
|
|
||||||
btn.classList.add('disabled');
|
|
||||||
});
|
|
||||||
if (veganBtn) {
|
|
||||||
veganBtn.classList.remove('disabled');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// If Fleisch/Fisch is selected, disable both Vegetarisch and Vegan
|
|
||||||
else if (hasMeatOrFish) {
|
|
||||||
if (vegetarischBtn) {
|
|
||||||
vegetarischBtn.classList.add('disabled');
|
|
||||||
}
|
|
||||||
if (veganBtn) {
|
|
||||||
veganBtn.classList.add('disabled');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// No conflicts, enable everything
|
|
||||||
else {
|
|
||||||
plantButtons.forEach(btn => {
|
|
||||||
btn.classList.remove('disabled');
|
|
||||||
});
|
|
||||||
meatFishButtons.forEach(btn => {
|
|
||||||
btn.classList.remove('disabled');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Category filter interactions: mutually exclusive (radio button behavior).
|
|
||||||
filterButtons.forEach(button => {
|
filterButtons.forEach(button => {
|
||||||
button.addEventListener('click', () => {
|
button.addEventListener('click', () => {
|
||||||
const categoryValue = button.getAttribute('data-cat');
|
activeCategory = button.getAttribute('data-cat');
|
||||||
const dietValue = button.getAttribute('data-diet');
|
|
||||||
const allergieValue = button.getAttribute('data-allergie');
|
|
||||||
|
|
||||||
if (categoryValue !== null) {
|
|
||||||
// Category filter: exclusive selection
|
|
||||||
activeCategory = categoryValue;
|
|
||||||
} else if (dietValue !== null) {
|
|
||||||
// Diet filter: toggle selection with conflict handling
|
|
||||||
const isCurrentlySelected = activeDiets.has(dietValue);
|
|
||||||
|
|
||||||
if (!isCurrentlySelected) {
|
|
||||||
// Adding a diet - handle conflicts
|
|
||||||
if (dietValue === 'Vegetarisch') {
|
|
||||||
// Vegetarisch removes Fleisch/Fisch but not Vegan
|
|
||||||
activeDiets.delete('Fleisch');
|
|
||||||
activeDiets.delete('Fisch');
|
|
||||||
activeDiets.add('Vegetarisch');
|
|
||||||
} else if (dietValue === 'Vegan') {
|
|
||||||
// Vegan removes all other options
|
|
||||||
activeDiets.delete('Vegetarisch');
|
|
||||||
activeDiets.delete('Fleisch');
|
|
||||||
activeDiets.delete('Fisch');
|
|
||||||
activeDiets.add('Vegan');
|
|
||||||
} else if (dietValue === 'Fleisch' || dietValue === 'Fisch') {
|
|
||||||
// Fleisch/Fisch remove Vegetarisch/Vegan
|
|
||||||
activeDiets.delete('Vegetarisch');
|
|
||||||
activeDiets.delete('Vegan');
|
|
||||||
activeDiets.add(dietValue);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Removing a diet
|
|
||||||
activeDiets.delete(dietValue);
|
|
||||||
}
|
|
||||||
|
|
||||||
updateDietAvailability();
|
|
||||||
} else if (allergieValue !== null) {
|
|
||||||
// Allergie filter: toggle selection
|
|
||||||
if (activeAllergies.has(allergieValue)) {
|
|
||||||
activeAllergies.delete(allergieValue);
|
|
||||||
} else {
|
|
||||||
activeAllergies.add(allergieValue);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
applyFilters();
|
applyFilters();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -744,87 +468,8 @@
|
|||||||
|
|
||||||
if (dateFilter) {
|
if (dateFilter) {
|
||||||
dateFilter.addEventListener('change', applyFilters);
|
dateFilter.addEventListener('change', applyFilters);
|
||||||
|
|
||||||
// Make calendar icon clickable to focus the date input
|
|
||||||
const calendarIcon = document.querySelector('.calendar-icon');
|
|
||||||
if (calendarIcon) {
|
|
||||||
calendarIcon.addEventListener('click', () => {
|
|
||||||
dateFilter.focus();
|
|
||||||
dateFilter.click();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Clear all filters button
|
|
||||||
const clearAllFiltersBtn = document.getElementById('clear-all-filters');
|
|
||||||
if (clearAllFiltersBtn) {
|
|
||||||
clearAllFiltersBtn.addEventListener('click', () => {
|
|
||||||
activeCategory = 'ALLE';
|
|
||||||
activeDiets.clear();
|
|
||||||
activeAllergies.clear();
|
|
||||||
|
|
||||||
if (locationFilter) {
|
|
||||||
locationFilter.value = 'ALLE_ORTE';
|
|
||||||
}
|
|
||||||
if (dateFilter) {
|
|
||||||
dateFilter.value = '';
|
|
||||||
}
|
|
||||||
|
|
||||||
updateDietAvailability();
|
|
||||||
applyFilters();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Info button modal behavior
|
|
||||||
const infoButton = document.getElementById('info-button');
|
|
||||||
const infoModal = document.getElementById('info-modal');
|
|
||||||
const modalClose = infoModal?.querySelector('modal-close');
|
|
||||||
|
|
||||||
if (infoButton && infoModal) {
|
|
||||||
infoButton.addEventListener('click', () => {
|
|
||||||
infoModal.classList.add('show');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (modalClose && infoModal) {
|
|
||||||
modalClose.addEventListener('click', () => {
|
|
||||||
infoModal.classList.remove('show');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (infoModal) {
|
|
||||||
infoModal.addEventListener('click', (event) => {
|
|
||||||
if (event.target === infoModal) {
|
|
||||||
infoModal.classList.remove('show');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Auto-open info modal on first login
|
|
||||||
if (currentUser && infoModal) {
|
|
||||||
const userInfoModalKey = getInfoModalShownKeyForUser(currentUser);
|
|
||||||
const hasShownInfoModal = localStorage.getItem(userInfoModalKey);
|
|
||||||
if (!hasShownInfoModal) {
|
|
||||||
infoModal.classList.add('show');
|
|
||||||
localStorage.setItem(userInfoModalKey, 'true');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Kick off initial load/render cycle.
|
// Kick off initial load/render cycle.
|
||||||
fetchEvents();
|
fetchEvents();
|
||||||
});
|
});
|
||||||
|
|
||||||
// Modal closing helper functions
|
|
||||||
function closeRegisterModal() {
|
|
||||||
const modal = document.getElementById('register-confirm-modal');
|
|
||||||
if (modal) {
|
|
||||||
modal.classList.remove('show');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function closeUnregisterModal() {
|
|
||||||
const modal = document.getElementById('unregister-confirm-modal');
|
|
||||||
if (modal) {
|
|
||||||
modal.classList.remove('show');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
// =============================================
|
// =============================================
|
||||||
// Galerie-Karussell (Startseite)
|
// Galerie-Karussell (Startseite)
|
||||||
// Diese Datei steuert die Foto-Galerie mit Pfeilen.
|
// Diese Datei steuert die Foto-Galerie mit Pfeilen.
|
||||||
// =============================================
|
// =============================================
|
||||||
@ -7,9 +7,8 @@
|
|||||||
const carouselTrack = document.querySelector('.gallery__track');
|
const carouselTrack = document.querySelector('.gallery__track');
|
||||||
const prevArrow = document.querySelector('.gallery__arrow--prev');
|
const prevArrow = document.querySelector('.gallery__arrow--prev');
|
||||||
const nextArrow = document.querySelector('.gallery__arrow--next');
|
const nextArrow = document.querySelector('.gallery__arrow--next');
|
||||||
const dotscontainer = document.querySelector('.gallery_dots');
|
|
||||||
|
|
||||||
// Nur ausführen, wenn die Galerie auf der Seite vorhanden ist.
|
// Nur ausfuehren, wenn die Galerie auf der Seite vorhanden ist.
|
||||||
if (carouselTrack) {
|
if (carouselTrack) {
|
||||||
// Alle einzelnen Karten/Bilder im Track sammeln.
|
// Alle einzelnen Karten/Bilder im Track sammeln.
|
||||||
const items = Array.from(carouselTrack.querySelectorAll('.gallery__item'));
|
const items = Array.from(carouselTrack.querySelectorAll('.gallery__item'));
|
||||||
@ -17,127 +16,55 @@ if (carouselTrack) {
|
|||||||
// Auf Mobile zeigen wir 1 Bild, auf Desktop 3 Bilder pro "Seite".
|
// Auf Mobile zeigen wir 1 Bild, auf Desktop 3 Bilder pro "Seite".
|
||||||
const getItemsPerPage = () => (window.matchMedia('(max-width: 900px)').matches ? 1 : 3);
|
const getItemsPerPage = () => (window.matchMedia('(max-width: 900px)').matches ? 1 : 3);
|
||||||
let itemsPerPage = getItemsPerPage();
|
let itemsPerPage = getItemsPerPage();
|
||||||
let pageCount = Math.ceil(items.length / itemsPerPage);
|
const pageCount = Math.ceil(items.length / itemsPerPage);
|
||||||
let activePage = 0;
|
let activePage = 0;
|
||||||
var dots = [];
|
|
||||||
|
|
||||||
function buildDots() {
|
// Scrollt den Track auf eine bestimmte Seite.
|
||||||
if (!dotscontainer) return;
|
function scrollToPage(page) {
|
||||||
dotscontainer.innerHTML = '';
|
|
||||||
dots = [];
|
|
||||||
for (var i = 0; i < pageCount; i++) {
|
|
||||||
var dot = document.createElement('button');
|
|
||||||
dot.type = 'button';
|
|
||||||
dot.className = 'gallery_dot' + (i === activePage ? ' gallery_dot--active' : '');
|
|
||||||
dot.setAttribute('role', 'tab');
|
|
||||||
dot.setAttribute('aria-selected', i === activePage ? 'true' : 'false');
|
|
||||||
dot.setAttribute('aria-label', 'Seite ' + (i + 1) + ' von ' + pageCount);
|
|
||||||
dot.dataset.page = i;
|
|
||||||
dot.addEventListener('click', function() {
|
|
||||||
goToPage(parseInt(this.dataset.page));
|
|
||||||
});
|
|
||||||
dotscontainer.appendChild(dot);
|
|
||||||
dots.push(dot);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateDots() {
|
|
||||||
dots.forEach(function(dot, i) {
|
|
||||||
dot.classList.toggle('gallery_dot--active', i === activePage);
|
|
||||||
dot.setAttribute('aria-selected', i === activePage ? 'true' : 'false');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateTrack() {
|
|
||||||
var gap = parseFloat(getComputedStyle(carouselTrack).gap) || 20;
|
|
||||||
var itemWidth = items[0].getBoundingClientRect().width;
|
|
||||||
var offset = activePage * (itemWidth + gap) * itemsPerPage;
|
|
||||||
carouselTrack.style.transform = 'translateX(-' + offset + 'px)';
|
|
||||||
carouselTrack.style.transition = 'transform 0.4s ease';
|
|
||||||
updateDots();
|
|
||||||
}
|
|
||||||
|
|
||||||
function goToPage(page) {
|
|
||||||
activePage = page;
|
activePage = page;
|
||||||
updateTrack();
|
const pageWidth = carouselTrack.clientWidth;
|
||||||
|
carouselTrack.scrollTo({ left: pageWidth * page, behavior: 'smooth' });
|
||||||
}
|
}
|
||||||
|
|
||||||
// Geht zur nächsten Seite (mit Wrap-around am Ende).
|
// Geht zur naechsten Seite (mit Wrap-around am Ende).
|
||||||
function showNext() {
|
function showNext() {
|
||||||
activePage = (activePage + 1) % pageCount;
|
activePage = (activePage + 1) % pageCount;
|
||||||
updateTrack();
|
scrollToPage(activePage);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Geht zur vorherigen Seite (mit Wrap-around zum Ende).
|
// Geht zur vorherigen Seite (mit Wrap-around zum Ende).
|
||||||
function showPrev() {
|
function showPrev() {
|
||||||
activePage = (activePage - 1 + pageCount) % pageCount;
|
activePage = (activePage - 1 + pageCount) % pageCount;
|
||||||
updateTrack();
|
scrollToPage(activePage);
|
||||||
}
|
}
|
||||||
|
|
||||||
buildDots();
|
// Wenn sich bei Resize die Karten-Anzahl pro Seite aendert,
|
||||||
|
// laden wir die Seite neu, damit Layout und Seitenzahl wieder stimmen.
|
||||||
|
function refreshCarousel() {
|
||||||
|
const responsiveItemsPerPage = getItemsPerPage();
|
||||||
|
if (responsiveItemsPerPage !== itemsPerPage) {
|
||||||
|
itemsPerPage = responsiveItemsPerPage;
|
||||||
|
window.location.reload();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Klick-Steuerung der Pfeile.
|
// Klick-Steuerung der Pfeile.
|
||||||
if (nextArrow) nextArrow.addEventListener('click', showNext);
|
if (nextArrow) nextArrow.addEventListener('click', showNext);
|
||||||
if (prevArrow) prevArrow.addEventListener('click', showPrev);
|
if (prevArrow) prevArrow.addEventListener('click', showPrev);
|
||||||
|
|
||||||
// Tastatur-Support für Barrierefreiheit.
|
// Tastatur-Support fuer Barrierefreiheit.
|
||||||
document.addEventListener('keydown', function(event) {
|
document.addEventListener('keydown', (event) => {
|
||||||
if (event.key === 'ArrowRight') showNext();
|
if (event.key === 'ArrowRight') {
|
||||||
if (event.key === 'ArrowLeft') showPrev();
|
showNext();
|
||||||
});
|
|
||||||
|
|
||||||
// Reagiert auf Bildschirmgrössen-Änderungen.
|
|
||||||
window.addEventListener('resize', function() {
|
|
||||||
var newPerPage = getItemsPerPage();
|
|
||||||
if (newPerPage !== itemsPerPage) {
|
|
||||||
itemsPerPage = newPerPage;
|
|
||||||
pageCount = Math.ceil(items.length / itemsPerPage);
|
|
||||||
activePage = 0;
|
|
||||||
}
|
}
|
||||||
buildDots();
|
if (event.key === 'ArrowLeft') {
|
||||||
updateTrack();
|
showPrev();
|
||||||
});
|
|
||||||
|
|
||||||
// =============================================
|
|
||||||
// Lightbox: Bild vergrössern bei Klick
|
|
||||||
// =============================================
|
|
||||||
const lightbox = document.getElementById('gallery-lightbox');
|
|
||||||
const lightboxImage = lightbox ? lightbox.querySelector('.lightbox__image') : null;
|
|
||||||
|
|
||||||
function openLightbox(src, alt) {
|
|
||||||
if (!lightbox || !lightboxImage) return;
|
|
||||||
lightboxImage.src = src;
|
|
||||||
lightboxImage.alt = alt || 'Grossansicht';
|
|
||||||
lightbox.classList.add('is-open');
|
|
||||||
lightbox.setAttribute('aria-hidden', 'false');
|
|
||||||
}
|
|
||||||
|
|
||||||
function closeLightbox() {
|
|
||||||
if (!lightbox) return;
|
|
||||||
lightbox.classList.remove('is-open');
|
|
||||||
lightbox.setAttribute('aria-hidden', 'true');
|
|
||||||
lightboxImage.src = '';
|
|
||||||
}
|
|
||||||
|
|
||||||
// Klick auf Galerie-Bild öffnet die Lightbox.
|
|
||||||
items.forEach(function(item) {
|
|
||||||
var img = item.querySelector('img');
|
|
||||||
if (img) {
|
|
||||||
item.addEventListener('click', function() {
|
|
||||||
openLightbox(img.src, img.alt);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Lightbox schliessen: Backdrop, Close-Button oder ESC-Taste.
|
// Reagiert auf Bildschirmgroessen-Aenderungen.
|
||||||
if (lightbox) {
|
window.addEventListener('resize', () => {
|
||||||
lightbox.querySelector('.lightbox__close').addEventListener('click', closeLightbox);
|
refreshCarousel();
|
||||||
lightbox.querySelector('[data-close-lightbox]').addEventListener('click', closeLightbox);
|
scrollToPage(activePage);
|
||||||
|
});
|
||||||
document.addEventListener('keydown', function(e) {
|
|
||||||
if (e.key === 'Escape' && lightbox.classList.contains('is-open')) {
|
|
||||||
closeLightbox();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
// =============================================
|
// =============================================
|
||||||
// Mini-Galerie auf der Landingpage
|
// Mini-Galerie auf der Landingpage
|
||||||
// Diese Datei hebt immer ein Bild hervor und
|
// Diese Datei hebt immer ein Bild hervor und
|
||||||
// erlaubt Navigation mit Pfeilen/Tastatur.
|
// erlaubt Navigation mit Pfeilen/Tastatur.
|
||||||
@ -38,7 +38,7 @@ function showPrev() {
|
|||||||
if (nextBtn) nextBtn.addEventListener('click', showNext);
|
if (nextBtn) nextBtn.addEventListener('click', showNext);
|
||||||
if (prevBtn) prevBtn.addEventListener('click', showPrev);
|
if (prevBtn) prevBtn.addEventListener('click', showPrev);
|
||||||
|
|
||||||
// Tastatursteuerung für bessere Bedienbarkeit.
|
// Tastatursteuerung fuer bessere Bedienbarkeit.
|
||||||
document.addEventListener('keydown', (event) => {
|
document.addEventListener('keydown', (event) => {
|
||||||
if (event.key === 'ArrowRight') {
|
if (event.key === 'ArrowRight') {
|
||||||
showNext();
|
showNext();
|
||||||
|
|||||||
73
js/login.js
@ -1,4 +1,4 @@
|
|||||||
// =============================================
|
// =============================================
|
||||||
// Login-Logik
|
// Login-Logik
|
||||||
// Diese Datei validiert die Eingaben, sucht den
|
// Diese Datei validiert die Eingaben, sucht den
|
||||||
// Benutzer im localStorage und legt die Session an.
|
// Benutzer im localStorage und legt die Session an.
|
||||||
@ -25,12 +25,12 @@ function getStoredUsers() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Speichert den aktiven Benutzer für nachfolgende Seiten.
|
// Speichert den aktiven Benutzer fuer nachfolgende Seiten.
|
||||||
function setCurrentUser(user) {
|
function setCurrentUser(user) {
|
||||||
localStorage.setItem(CURRENT_USER_KEY, JSON.stringify(user));
|
localStorage.setItem(CURRENT_USER_KEY, JSON.stringify(user));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Erstellt einen Demo-Benutzer, falls für die E-Mail noch kein Account existiert.
|
// Erstellt einen Demo-Benutzer, falls fuer die E-Mail noch kein Account existiert.
|
||||||
function createFallbackUser(email, passwort) {
|
function createFallbackUser(email, passwort) {
|
||||||
const localPart = email.split('@')[0] || 'Gast';
|
const localPart = email.split('@')[0] || 'Gast';
|
||||||
const normalized = localPart.replace(/[._-]/g, ' ').trim();
|
const normalized = localPart.replace(/[._-]/g, ' ').trim();
|
||||||
@ -50,73 +50,60 @@ function createFallbackUser(email, passwort) {
|
|||||||
// Validierungsfunktion
|
// Validierungsfunktion
|
||||||
function validateForm(event) {
|
function validateForm(event) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
|
||||||
|
let isValid = true;
|
||||||
|
|
||||||
// Wir zeigen bewusst immer nur den ersten Fehler im Formular an.
|
// Email-Validierung
|
||||||
// So bleibt der Ablauf ruhig und führt den Nutzer Feld für Feld.
|
|
||||||
const emailValue = emailInput.value.trim();
|
const emailValue = emailInput.value.trim();
|
||||||
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
||||||
const emailGroup = emailInput.parentElement;
|
const emailGroup = emailInput.parentElement;
|
||||||
const passwortGroup = passwortInput.parentElement;
|
|
||||||
|
|
||||||
emailGroup.classList.remove('has-error');
|
|
||||||
passwortGroup.classList.remove('has-error');
|
|
||||||
|
|
||||||
if (!emailValue) {
|
if (!emailValue) {
|
||||||
emailGroup.classList.add('has-error');
|
emailGroup.classList.add('has-error');
|
||||||
emailError.textContent = 'Bitte gib deine E-Mail Adresse ein.';
|
emailError.textContent = 'Bitte gib deine E-Mail Adresse ein.';
|
||||||
emailInput.focus();
|
isValid = false;
|
||||||
return;
|
} else if (!emailRegex.test(emailValue)) {
|
||||||
}
|
|
||||||
|
|
||||||
if (!emailRegex.test(emailValue)) {
|
|
||||||
emailGroup.classList.add('has-error');
|
emailGroup.classList.add('has-error');
|
||||||
emailError.textContent = 'Bitte gib eine gültige E-Mail Adresse ein.';
|
emailError.textContent = 'Bitte gib eine gültige E-Mail Adresse ein.';
|
||||||
emailInput.focus();
|
isValid = false;
|
||||||
return;
|
} else {
|
||||||
|
emailGroup.classList.remove('has-error');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Passwort-Validierung
|
// Passwort-Validierung
|
||||||
const passwortValue = passwortInput.value;
|
const passwortValue = passwortInput.value;
|
||||||
|
const passwortGroup = passwortInput.parentElement;
|
||||||
|
|
||||||
if (!passwortValue) {
|
if (!passwortValue) {
|
||||||
passwortGroup.classList.add('has-error');
|
passwortGroup.classList.add('has-error');
|
||||||
passwortError.textContent = 'Bitte gib dein Passwort ein.';
|
passwortError.textContent = 'Bitte gib dein Passwort ein.';
|
||||||
passwortInput.focus();
|
isValid = false;
|
||||||
return;
|
} else if (passwortValue.length < 6) {
|
||||||
}
|
|
||||||
|
|
||||||
if (passwortValue.length < 6) {
|
|
||||||
passwortGroup.classList.add('has-error');
|
passwortGroup.classList.add('has-error');
|
||||||
passwortError.textContent = 'Dein Passwort ist zu kurz. Bitte überprüfe dein Passwort.';
|
passwortError.textContent = 'Dein Passwort ist zu kurz. Bitte überprüfe dein Passwort.';
|
||||||
passwortInput.focus();
|
isValid = false;
|
||||||
return;
|
} else {
|
||||||
|
passwortGroup.classList.remove('has-error');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wenn alle Validierungen bestanden, prüfen wir:
|
// Wenn alle Validierungen bestanden, pruefen wir:
|
||||||
// 1) gibt es den Benutzer schon?
|
// 1) gibt es den Benutzer schon?
|
||||||
// 2) ist das Passwort korrekt?
|
// 2) ist das Passwort korrekt?
|
||||||
// Danach speichern wir die aktive Session.
|
// Danach speichern wir die aktive Session.
|
||||||
const users = getStoredUsers();
|
if (isValid) {
|
||||||
const matchedUser = users.find(user => user.email?.toLowerCase() === emailValue.toLowerCase());
|
const users = getStoredUsers();
|
||||||
|
const matchedUser = users.find(user => user.email?.toLowerCase() === emailValue.toLowerCase());
|
||||||
|
|
||||||
if (matchedUser && matchedUser.passwort !== passwortValue) {
|
if (matchedUser && matchedUser.passwort !== passwortValue) {
|
||||||
passwortGroup.classList.add('has-error');
|
passwortGroup.classList.add('has-error');
|
||||||
passwortError.textContent = 'Das Passwort ist nicht korrekt.';
|
passwortError.textContent = 'Das Passwort ist nicht korrekt.';
|
||||||
passwortInput.focus();
|
return;
|
||||||
return;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
const userToLogin = matchedUser || createFallbackUser(emailValue, passwortValue);
|
const userToLogin = matchedUser || createFallbackUser(emailValue, passwortValue);
|
||||||
setCurrentUser(userToLogin);
|
setCurrentUser(userToLogin);
|
||||||
|
|
||||||
// Snackbar anzeigen und dann zur Event-Übersicht weiterleiten.
|
// Nach erfolgreichem Login geht es zur Event-Uebersicht.
|
||||||
var snackbar = document.getElementById('snackbar');
|
|
||||||
if (snackbar) {
|
|
||||||
snackbar.classList.add('snackbar--visible');
|
|
||||||
setTimeout(function() {
|
|
||||||
window.location.href = 'event_overview.html';
|
|
||||||
}, 2000);
|
|
||||||
} else {
|
|
||||||
window.location.href = 'event_overview.html';
|
window.location.href = 'event_overview.html';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -137,4 +124,4 @@ passwortInput.addEventListener('input', function() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Form Submit Event
|
// Form Submit Event
|
||||||
loginForm.addEventListener('submit', validateForm);
|
loginForm.addEventListener('submit', validateForm);
|
||||||
477
js/my_profil.js
@ -1,22 +1,20 @@
|
|||||||
document.addEventListener('DOMContentLoaded', () => {
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
const EVENTS_STORAGE_KEY = 'socialCookingEvents';
|
const EVENTS_STORAGE_KEY = 'socialCookingEvents';
|
||||||
const USERS_STORAGE_KEY = 'socialCookingUsers';
|
const USERS_STORAGE_KEY = 'socialCookingUsers';
|
||||||
const CURRENT_USER_KEY = 'socialCookingCurrentUser';
|
const CURRENT_USER_KEY = 'socialCookingCurrentUser';
|
||||||
const REGISTRATION_STORAGE_KEY = 'socialCookingRegistrations';
|
const REGISTRATION_STORAGE_KEY = 'socialCookingRegistrations';
|
||||||
|
|
||||||
// Zentrale DOM-Referenzen für klare, testbare Funktionen.
|
// Zentrale DOM-Referenzen fuer klare, testbare Funktionen.
|
||||||
const loggedOutState = document.getElementById('logged-out-state');
|
const loggedOutState = document.getElementById('logged-out-state');
|
||||||
const loggedInContent = document.getElementById('logged-in-content');
|
const loggedInContent = document.getElementById('logged-in-content');
|
||||||
const profileHeadline = document.getElementById('headline');
|
const profileHeadline = document.getElementById('profile-headline');
|
||||||
const profileSubline = document.getElementById('profile-subline');
|
const profileSubline = document.getElementById('profile-subline');
|
||||||
const logoutButton = document.getElementById('logout-button');
|
const logoutButton = document.getElementById('logout-button');
|
||||||
const profileTabButtons = Array.from(document.querySelectorAll('[data-category-item]'));
|
const profileTabButtons = Array.from(document.querySelectorAll('[data-profile-tab]'));
|
||||||
const profileTabPanels = Array.from(document.querySelectorAll('[data-profile-panel]'));
|
const profileTabPanels = Array.from(document.querySelectorAll('[data-profile-panel]'));
|
||||||
|
|
||||||
const myEventsCount = document.getElementById('my-events-count');
|
const myEventsCount = document.getElementById('my-events-count');
|
||||||
const myEventsBtnCount = document.getElementById('btn-my-events-count');
|
|
||||||
const myRegistrationsCount = document.getElementById('my-registrations-count');
|
const myRegistrationsCount = document.getElementById('my-registrations-count');
|
||||||
const myRegistrationsBtnCount = document.getElementById('btn-my-registrations-count');
|
|
||||||
const myEventsList = document.getElementById('my-events-list');
|
const myEventsList = document.getElementById('my-events-list');
|
||||||
const myRegistrationsList = document.getElementById('my-registrations-list');
|
const myRegistrationsList = document.getElementById('my-registrations-list');
|
||||||
|
|
||||||
@ -111,7 +109,7 @@
|
|||||||
profileSubline.textContent = 'Bitte logge dich ein, um deinen Bereich zu sehen.';
|
profileSubline.textContent = 'Bitte logge dich ein, um deinen Bereich zu sehen.';
|
||||||
}
|
}
|
||||||
|
|
||||||
// Füllt Überschriften und Formular mit den aktuellen Benutzerdaten.
|
// Fuellt Ueberschriften und Formular mit den aktuellen Benutzerdaten.
|
||||||
function renderLoggedInState(user) {
|
function renderLoggedInState(user) {
|
||||||
loggedOutState.classList.add('hidden');
|
loggedOutState.classList.add('hidden');
|
||||||
loggedInContent.classList.remove('hidden');
|
loggedInContent.classList.remove('hidden');
|
||||||
@ -133,7 +131,7 @@
|
|||||||
|
|
||||||
profileTabButtons.forEach(button => {
|
profileTabButtons.forEach(button => {
|
||||||
button.addEventListener('click', () => {
|
button.addEventListener('click', () => {
|
||||||
const tabName = button.getAttribute('data-category-item');
|
const tabName = button.getAttribute('data-profile-tab');
|
||||||
if (!tabName) {
|
if (!tabName) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -150,24 +148,11 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
logoutButton.addEventListener('click', () => {
|
logoutButton.addEventListener('click', () => {
|
||||||
const logoutModal = document.getElementById('logoutModal');
|
localStorage.removeItem(CURRENT_USER_KEY);
|
||||||
logoutModal.classList.add('show');
|
window.location.href = 'login.html';
|
||||||
document.body.style.overflow = 'hidden';
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Globale Funktionen für das Logout-Modal.
|
|
||||||
window.closeLogoutModal = function() {
|
|
||||||
const logoutModal = document.getElementById('logoutModal');
|
|
||||||
logoutModal.classList.remove('show');
|
|
||||||
document.body.style.overflow = 'auto';
|
|
||||||
};
|
|
||||||
|
|
||||||
window.confirmLogout = function() {
|
|
||||||
localStorage.removeItem(CURRENT_USER_KEY);
|
|
||||||
window.location.href = 'index.html';
|
|
||||||
};
|
|
||||||
|
|
||||||
// Reagiert auf Aktionen in der Liste "Meine Events" per Event Delegation.
|
// Reagiert auf Aktionen in der Liste "Meine Events" per Event Delegation.
|
||||||
function handleHostedListClick(event) {
|
function handleHostedListClick(event) {
|
||||||
const target = event.target;
|
const target = event.target;
|
||||||
@ -179,7 +164,7 @@
|
|||||||
if (cancelButton && currentUser?.email) {
|
if (cancelButton && currentUser?.email) {
|
||||||
const eventId = Number(cancelButton.getAttribute('data-cancel-event-id'));
|
const eventId = Number(cancelButton.getAttribute('data-cancel-event-id'));
|
||||||
if (Number.isFinite(eventId)) {
|
if (Number.isFinite(eventId)) {
|
||||||
openCancelEventModal(eventId);
|
cancelHostedEvent(eventId, currentUser.email);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -204,7 +189,7 @@
|
|||||||
// Schaltet den sichtbaren Profilbereich per Tabname um.
|
// Schaltet den sichtbaren Profilbereich per Tabname um.
|
||||||
function activateProfileTab(tabName) {
|
function activateProfileTab(tabName) {
|
||||||
profileTabButtons.forEach(button => {
|
profileTabButtons.forEach(button => {
|
||||||
const isActive = button.getAttribute('data-category-item') === tabName;
|
const isActive = button.getAttribute('data-profile-tab') === tabName;
|
||||||
button.classList.toggle('is-active', isActive);
|
button.classList.toggle('is-active', isActive);
|
||||||
button.setAttribute('aria-selected', isActive ? 'true' : 'false');
|
button.setAttribute('aria-selected', isActive ? 'true' : 'false');
|
||||||
});
|
});
|
||||||
@ -213,11 +198,6 @@
|
|||||||
const isActive = panel.getAttribute('data-profile-panel') === tabName;
|
const isActive = panel.getAttribute('data-profile-panel') === tabName;
|
||||||
panel.classList.toggle('hidden', !isActive);
|
panel.classList.toggle('hidden', !isActive);
|
||||||
});
|
});
|
||||||
|
|
||||||
if (tabName === 'teilnehmen') {
|
|
||||||
const registeredEvents = getMyRegisteredEvents(allEvents, currentUser);
|
|
||||||
markRegistrationsAsRead(registeredEvents);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reagiert auf Aktionen in der Liste "Meine Anmeldungen" per Event Delegation.
|
// Reagiert auf Aktionen in der Liste "Meine Anmeldungen" per Event Delegation.
|
||||||
@ -227,37 +207,21 @@
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const unregisterButton = target.closest('[data-unregister-id]');
|
const unregisterButton = target.closest('[data-unregister-id]');
|
||||||
if (unregisterButton) {
|
if (unregisterButton) {
|
||||||
if (!currentUser?.email) return;
|
if (!currentUser?.email) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const eventId = Number(unregisterButton.getAttribute('data-unregister-id'));
|
const eventId = Number(unregisterButton.getAttribute('data-unregister-id'));
|
||||||
if (!Number.isFinite(eventId)) return;
|
if (!Number.isFinite(eventId)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const modal = document.getElementById('unregister-confirm-modal');
|
unregisterFromEvent(eventId, currentUser.email);
|
||||||
if (modal) modal.classList.add('show');
|
|
||||||
|
|
||||||
document.getElementById('confirm-unregister-btn').onclick = () => {
|
|
||||||
modal.classList.remove('show');
|
|
||||||
unregisterFromEvent(eventId, currentUser.email);
|
|
||||||
const snackbar = document.getElementById('snackbar');
|
|
||||||
if (snackbar) {
|
|
||||||
snackbar.textContent = 'Du wurdest erfolgreich abgemeldet.';
|
|
||||||
snackbar.classList.add('snackbar--danger', 'snackbar--visible');
|
|
||||||
setTimeout(() => {
|
|
||||||
snackbar.classList.remove('snackbar--visible');
|
|
||||||
setTimeout(() => snackbar.classList.remove('snackbar--danger'), 400);
|
|
||||||
}, 3000);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
document.getElementById('unregister-modal-close').onclick = () => modal.classList.remove('show');
|
|
||||||
document.getElementById('unregister-modal-cancel').onclick = () => modal.classList.remove('show');
|
|
||||||
modal.addEventListener('click', e => { if (e.target === modal) modal.classList.remove('show'); });
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
if (target.closest('a, button')) {
|
if (target.closest('a, button')) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -277,43 +241,6 @@
|
|||||||
|
|
||||||
|
|
||||||
// Sagt ein gehostetes Event ab (aus eigener Profilansicht entfernen).
|
// Sagt ein gehostetes Event ab (aus eigener Profilansicht entfernen).
|
||||||
let pendingCancelEventId = null;
|
|
||||||
|
|
||||||
function openCancelEventModal(eventId) {
|
|
||||||
pendingCancelEventId = eventId;
|
|
||||||
const modal = document.getElementById('cancelEventModal');
|
|
||||||
modal.classList.add('show');
|
|
||||||
}
|
|
||||||
|
|
||||||
window.closeCancelEventModal = function() {
|
|
||||||
pendingCancelEventId = null;
|
|
||||||
const modal = document.getElementById('cancelEventModal');
|
|
||||||
modal.classList.remove('show');
|
|
||||||
};
|
|
||||||
|
|
||||||
document.getElementById('confirmCancelEventBtn').addEventListener('click', function() {
|
|
||||||
if (pendingCancelEventId !== null && currentUser?.email) {
|
|
||||||
cancelHostedEvent(pendingCancelEventId, currentUser.email);
|
|
||||||
}
|
|
||||||
closeCancelEventModal();
|
|
||||||
const snackbar = document.getElementById('snackbar');
|
|
||||||
if (snackbar) {
|
|
||||||
snackbar.textContent = 'Dein Event wurde erfolgreich abgesagt.';
|
|
||||||
snackbar.classList.add('snackbar--danger', 'snackbar--visible');
|
|
||||||
setTimeout(() => {
|
|
||||||
snackbar.classList.remove('snackbar--visible');
|
|
||||||
setTimeout(() => snackbar.classList.remove('snackbar--danger'), 400);
|
|
||||||
}, 3000);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Schliesst das Modal bei Klick ausserhalb des Inhalts.
|
|
||||||
document.getElementById('cancelEventModal').addEventListener('click', function(e) {
|
|
||||||
if (e.target === this) {
|
|
||||||
closeCancelEventModal();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
function cancelHostedEvent(eventId, userEmail) {
|
function cancelHostedEvent(eventId, userEmail) {
|
||||||
// Lokal erstellte, eigene Events werden direkt aus dem Storage geloescht.
|
// Lokal erstellte, eigene Events werden direkt aus dem Storage geloescht.
|
||||||
const storedEvents = getStoredEvents();
|
const storedEvents = getStoredEvents();
|
||||||
@ -326,7 +253,7 @@
|
|||||||
});
|
});
|
||||||
setStoredEvents(nextStoredEvents);
|
setStoredEvents(nextStoredEvents);
|
||||||
|
|
||||||
// Event-ID für alle Benutzer aus den Anmeldungen entfernen.
|
// Event-ID fuer alle Benutzer aus den Anmeldungen entfernen.
|
||||||
const registrationMap = getRegistrationMap();
|
const registrationMap = getRegistrationMap();
|
||||||
Object.keys(registrationMap).forEach(email => {
|
Object.keys(registrationMap).forEach(email => {
|
||||||
const ids = Array.isArray(registrationMap[email])
|
const ids = Array.isArray(registrationMap[email])
|
||||||
@ -341,10 +268,10 @@
|
|||||||
|
|
||||||
renderMyEvents(allEvents, currentUser);
|
renderMyEvents(allEvents, currentUser);
|
||||||
renderMyRegistrations(allEvents, currentUser);
|
renderMyRegistrations(allEvents, currentUser);
|
||||||
|
profileFeedback.textContent = 'Event wurde abgesagt und aus deinem Hosting entfernt.';
|
||||||
}
|
}
|
||||||
// Entfernt eine Event-ID aus der Benutzerliste und aktualisiert die UI sofort.
|
// Entfernt eine Event-ID aus der Benutzerliste und aktualisiert die UI sofort.
|
||||||
function unregisterFromEvent(eventId, userEmail) {
|
function unregisterFromEvent(eventId, userEmail) {
|
||||||
const registrationMap = getRegistrationMap();
|
const registrationMap = getRegistrationMap();
|
||||||
const currentIds = Array.isArray(registrationMap[userEmail]) ? registrationMap[userEmail] : [];
|
const currentIds = Array.isArray(registrationMap[userEmail]) ? registrationMap[userEmail] : [];
|
||||||
const nextIds = currentIds
|
const nextIds = currentIds
|
||||||
@ -355,10 +282,12 @@
|
|||||||
setRegistrationMap(registrationMap);
|
setRegistrationMap(registrationMap);
|
||||||
|
|
||||||
renderMyRegistrations(allEvents, currentUser);
|
renderMyRegistrations(allEvents, currentUser);
|
||||||
|
profileFeedback.textContent = 'Du wurdest von dem Event abgemeldet.';
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validiert Profildaten konsistent und liefert true/false zur Submit-Steuerung.
|
// Validiert Profildaten konsistent und liefert true/false zur Submit-Steuerung.
|
||||||
function validateProfileForm() {
|
function validateProfileForm() {
|
||||||
|
let isValid = true;
|
||||||
|
|
||||||
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
||||||
|
|
||||||
@ -385,12 +314,12 @@
|
|||||||
return isValid;
|
return isValid;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Speichert Profiländerungen lokal und synchronisiert auch den Benutzerkatalog.
|
// Speichert Profilaenderungen lokal und synchronisiert auch den Benutzerkatalog.
|
||||||
function handleProfileSubmit(event) {
|
function handleProfileSubmit(event) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
|
||||||
if (!validateProfileForm()) {
|
if (!validateProfileForm()) {
|
||||||
profileFeedback.textContent = 'Bitte prüfe die markierten Felder.';
|
profileFeedback.textContent = 'Bitte pruefe die markierten Felder.';
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -408,7 +337,7 @@
|
|||||||
localStorage.setItem(CURRENT_USER_KEY, JSON.stringify(nextUser));
|
localStorage.setItem(CURRENT_USER_KEY, JSON.stringify(nextUser));
|
||||||
syncUserInUserStore(previousEmail, nextUser);
|
syncUserInUserStore(previousEmail, nextUser);
|
||||||
|
|
||||||
// Falls sich die E-Mail geändert hat, verschieben wir bestehende Anmeldungen auf die neue E-Mail.
|
// Falls sich die E-Mail geaendert hat, verschieben wir bestehende Anmeldungen auf die neue E-Mail.
|
||||||
migrateRegistrationEmail(previousEmail, nextUser.email);
|
migrateRegistrationEmail(previousEmail, nextUser.email);
|
||||||
|
|
||||||
passwortInput.value = '';
|
passwortInput.value = '';
|
||||||
@ -469,138 +398,51 @@
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ermittelt angemeldete Events über die Registration-Map und participants-Liste.
|
// Ermittelt angemeldete Events ueber die Registration-Map.
|
||||||
function getMyRegisteredEvents(events, user) {
|
function getMyRegisteredEvents(events, user) {
|
||||||
const registrationMap = getRegistrationMap();
|
const registrationMap = getRegistrationMap();
|
||||||
const registeredIds = Array.isArray(registrationMap[user.email]) ? registrationMap[user.email] : [];
|
const registeredIds = Array.isArray(registrationMap[user.email]) ? registrationMap[user.email] : [];
|
||||||
const idSet = new Set(registeredIds.map(id => Number(id)));
|
const idSet = new Set(registeredIds.map(id => Number(id)));
|
||||||
|
|
||||||
const userFirstName = String(user.vorname || '').trim().toLowerCase();
|
return events.filter(event => idSet.has(Number(event.id)));
|
||||||
const userFullName = `${String(user.vorname || '').trim()} ${String(user.nachname || '').trim()}`.trim().toLowerCase();
|
|
||||||
|
|
||||||
return events.filter(event => {
|
|
||||||
if (idSet.has(Number(event.id))) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (Array.isArray(event.participants)) {
|
|
||||||
const participantSet = new Set(event.participants.map(name => String(name || '').trim().toLowerCase()).filter(Boolean));
|
|
||||||
if ((userFirstName && participantSet.has(userFirstName)) || (userFullName && participantSet.has(userFullName))) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Rendert angemeldete Events inkl. Zähler.
|
// Rendert gehostete Events inkl. Zaehler.
|
||||||
function renderMyEvents(events, user) {
|
function renderMyEvents(events, user) {
|
||||||
const hostedEvents = getMyHostedEvents(events, user);
|
const hostedEvents = getMyHostedEvents(events, user);
|
||||||
const count = hostedEvents.length;
|
myEventsCount.textContent = String(hostedEvents.length);
|
||||||
|
renderEventCards(myEventsList, hostedEvents, {
|
||||||
myEventsCount.textContent = String(count);
|
title: 'Noch kein eigenes Event',
|
||||||
if (myEventsBtnCount) myEventsBtnCount.textContent = String(count);
|
text: 'Starte dein erstes Dinner und lade die Community an deinen Tisch ein.',
|
||||||
|
buttonLabel: 'Event erstellen',
|
||||||
renderEventCards(myEventsList, hostedEvents, {
|
href: 'event_create.html'
|
||||||
title: 'Noch kein eigenes Event',
|
}, 'hosting');
|
||||||
text: 'Starte dein erstes Dinner und lade die Community an deinen Tisch ein.',
|
|
||||||
buttonLabel: 'Event erstellen',
|
|
||||||
href: 'event_create.html'
|
|
||||||
}, 'hosting');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function getSeenAddresses() {
|
// Rendert angemeldete Events inkl. Zaehler.
|
||||||
try {
|
|
||||||
const raw = localStorage.getItem('socialCookingSeenAddresses');
|
|
||||||
return raw ? JSON.parse(raw) : [];
|
|
||||||
} catch (err) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function markRegistrationsAsRead(events) {
|
|
||||||
const seen = getSeenAddresses();
|
|
||||||
let changed = false;
|
|
||||||
events.forEach(event => {
|
|
||||||
if (isAddressVisibleWindow(event) && !seen.includes(Number(event.id))) {
|
|
||||||
seen.push(Number(event.id));
|
|
||||||
changed = true;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
if (changed) {
|
|
||||||
localStorage.setItem('socialCookingSeenAddresses', JSON.stringify(seen));
|
|
||||||
// Remove dots from UI
|
|
||||||
const tabDot = document.querySelector('[data-category-item="teilnehmen"] .notification-dot');
|
|
||||||
if (tabDot) tabDot.remove();
|
|
||||||
|
|
||||||
const navDot = document.querySelector('.profile-pill .notification-dot');
|
|
||||||
if (navDot) navDot.remove();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Rendert angemeldete Events inkl. Zähler.
|
|
||||||
function renderMyRegistrations(events, user) {
|
function renderMyRegistrations(events, user) {
|
||||||
const registeredEvents = getMyRegisteredEvents(events, user);
|
const registeredEvents = getMyRegisteredEvents(events, user);
|
||||||
|
myRegistrationsCount.textContent = String(registeredEvents.length);
|
||||||
const count = registeredEvents.length;
|
renderEventCards(myRegistrationsList, registeredEvents, {
|
||||||
|
title: 'Noch keine Anmeldungen',
|
||||||
myRegistrationsCount.textContent = String(count);
|
text: 'Entdecke spannende Dinner in deiner Naehe und melde dich direkt an.',
|
||||||
if (myRegistrationsBtnCount) myRegistrationsBtnCount.textContent = String(count);
|
buttonLabel: 'Events entdecken',
|
||||||
|
href: 'event_overview.html'
|
||||||
const seenAddresses = getSeenAddresses();
|
}, 'registrations');
|
||||||
const unreadEvents = registeredEvents.filter(e => isAddressVisibleWindow(e) && !seenAddresses.includes(Number(e.id)));
|
|
||||||
const hasNotifications = unreadEvents.length > 0;
|
|
||||||
|
|
||||||
const tabButton = document.querySelector('[data-category-item="teilnehmen"]');
|
|
||||||
if (tabButton) {
|
|
||||||
let dot = tabButton.querySelector('.notification-dot');
|
|
||||||
if (hasNotifications) {
|
|
||||||
if (!dot) {
|
|
||||||
dot = document.createElement('span');
|
|
||||||
dot.className = 'notification-dot';
|
|
||||||
tabButton.appendChild(dot);
|
|
||||||
}
|
|
||||||
} else if (dot) {
|
|
||||||
dot.remove();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
renderEventCards(myRegistrationsList, registeredEvents, {
|
// Baut die Eventkarten fuer beide Listen in einheitlichem Markup.
|
||||||
title: 'Noch keine Anmeldungen',
|
function renderEventCards(container, events, emptyStateConfig, mode) {
|
||||||
text: 'Entdecke spannende Dinner in deiner Naehe und melde dich direkt an.',
|
|
||||||
buttonLabel: 'Events entdecken',
|
|
||||||
href: 'event_overview.html'
|
|
||||||
}, 'registrations', seenAddresses);
|
|
||||||
|
|
||||||
// Falls wir bereits auf dem Tab sind, direkt als gelesen markieren
|
|
||||||
const activeTab = document.querySelector('[data-category-item="teilnehmen"].is-active');
|
|
||||||
if (activeTab && hasNotifications) {
|
|
||||||
// Kurze Verzögerung, damit UI sich erst aufbaut
|
|
||||||
setTimeout(() => markRegistrationsAsRead(registeredEvents), 500);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Gibt true zurück, wenn die Abmeldung gesperrt ist (innerhalb von 24h oder in der Vergangenheit).
|
|
||||||
function isDeregistrationClosed(event) {
|
|
||||||
const eventDateTime = parseEventDateTime(event);
|
|
||||||
if (!eventDateTime || Number.isNaN(eventDateTime.getTime())) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
const msUntilStart = eventDateTime.getTime() - Date.now();
|
|
||||||
return msUntilStart <= 24 * 60 * 60 * 1000;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Baut die Eventkarten für beide Listen in einheitlichem Markup.
|
|
||||||
function renderEventCards(container, events, emptyStateConfig, mode, seenAddresses = []) {
|
|
||||||
container.innerHTML = '';
|
container.innerHTML = '';
|
||||||
|
|
||||||
if (events.length === 0) {
|
if (events.length === 0) {
|
||||||
const emptyElement = document.createElement('div');
|
const emptyElement = document.createElement('div');
|
||||||
emptyElement.className = 'empty-state';
|
emptyElement.className = 'profile-empty-state';
|
||||||
emptyElement.innerHTML = `
|
emptyElement.innerHTML = `
|
||||||
<div class="empty-state-kicker">Keine Treffer</div>
|
<p class="profile-empty-kicker">Keine Treffer</p>
|
||||||
<h3>${emptyStateConfig.title}</h3>
|
<h3>${emptyStateConfig.title}</h3>
|
||||||
<p>${emptyStateConfig.text}</p>
|
<p>${emptyStateConfig.text}</p>
|
||||||
<a class="empty-state-link button-primary" href="${emptyStateConfig.href}">${emptyStateConfig.buttonLabel}</a>
|
<a class="button" href="${emptyStateConfig.href}">${emptyStateConfig.buttonLabel}</a>
|
||||||
`;
|
`;
|
||||||
container.appendChild(emptyElement);
|
container.appendChild(emptyElement);
|
||||||
return;
|
return;
|
||||||
@ -610,81 +452,26 @@
|
|||||||
const card = document.createElement('article');
|
const card = document.createElement('article');
|
||||||
card.className = 'profile-event-card profile-event-card-clickable';
|
card.className = 'profile-event-card profile-event-card-clickable';
|
||||||
card.setAttribute('data-event-id', String(event.id));
|
card.setAttribute('data-event-id', String(event.id));
|
||||||
|
const addressMarkup = mode === 'registrations' && event.address && isAddressVisibleWindow(event)
|
||||||
const isCanceled = event.status === 'canceled';
|
? `
|
||||||
if (isCanceled) {
|
<div class="profile-event-address-block" aria-label="Event Adresse">
|
||||||
card.style.opacity = '0.6';
|
<p class="profile-event-address-label">Adresse</p>
|
||||||
}
|
<p class="profile-event-address">${event.address}</p>
|
||||||
|
|
||||||
let addressMessage = 'Vielen Dank für die Anmeldung! Die Adresse für diesen Event wird 24 Stunden vorher genau hier sichtbar sein.';
|
|
||||||
if (isEventPastAddressWindow(event)) {
|
|
||||||
addressMessage = 'Vielen Dank, dass du an diesem Event teilgenommen hast.';
|
|
||||||
}
|
|
||||||
|
|
||||||
let addressMarkup = '';
|
|
||||||
if (mode === 'registrations' && event.address) {
|
|
||||||
if (isCanceled) {
|
|
||||||
addressMarkup = `
|
|
||||||
<div class="profile-event-address-block" aria-label="Hinweis zur Adresse">
|
|
||||||
<p class="profile-event-address-label">Adresse</p>
|
|
||||||
<p class="profile-event-address">Dieses Event wurde leider vom Gastgeber abgesagt.</p>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
} else if (isAddressVisibleWindow(event)) {
|
|
||||||
addressMarkup = `
|
|
||||||
<div class="profile-event-address-block" aria-label="Event Adresse">
|
|
||||||
<p class="profile-event-address-label">Adresse</p>
|
|
||||||
<p class="profile-event-address">${event.address}</p>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
} else {
|
|
||||||
addressMarkup = `
|
|
||||||
<div class="profile-event-address-block" aria-label="Hinweis zur Adresse">
|
|
||||||
<p class="profile-event-address-label">Adresse</p>
|
|
||||||
<p class="profile-event-address">${addressMessage}</p>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const isDeregClosed = isDeregistrationClosed(event);
|
|
||||||
|
|
||||||
let actionMarkup = '';
|
|
||||||
if (mode === 'registrations') {
|
|
||||||
if (isCanceled) {
|
|
||||||
actionMarkup = `
|
|
||||||
<div class="event-side">
|
|
||||||
<button class="button-primary-abmelden" type="button" disabled>Abgesagt</button>
|
|
||||||
</div>
|
</div>
|
||||||
`;
|
`
|
||||||
} else if (isDeregClosed) {
|
: '';
|
||||||
actionMarkup = `
|
|
||||||
<div class="event-side">
|
const actionMarkup = mode === 'registrations'
|
||||||
<button class="button-primary-abmelden" type="button" disabled>Abmeldung geschlossen</button>
|
? `
|
||||||
|
<div class="profile-event-actions">
|
||||||
|
<button class="profile-unregister-btn" type="button" data-unregister-id="${event.id}">Abmelden</button>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`
|
||||||
} else {
|
: `
|
||||||
actionMarkup = `
|
<div class="profile-event-actions">
|
||||||
<div class="event-side">
|
<button class="profile-cancel-btn" type="button" data-cancel-event-id="${event.id}">Event absagen</button>
|
||||||
<button class="button-primary-abmelden" type="button" data-unregister-id="${event.id}">Abmelden</button>
|
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (isCanceled) {
|
|
||||||
actionMarkup = `
|
|
||||||
<div class="event-side">
|
|
||||||
<button class="button-primary-eigener-event" type="button" disabled>Abgesagt</button>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
} else {
|
|
||||||
actionMarkup = `
|
|
||||||
<div class="event-side">
|
|
||||||
<button class="button-primary-eigener-event" type="button" data-cancel-event-id="${event.id}">Event absagen</button>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
card.innerHTML = `
|
card.innerHTML = `
|
||||||
<div>
|
<div>
|
||||||
@ -699,27 +486,20 @@
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Gibt true zurück, wenn die Adresse sichtbar sein soll (24h vor bis 1h nach Start).
|
// Gibt true zurueck, wenn ein Event innerhalb der naechsten 12 Stunden startet.
|
||||||
function isAddressVisibleWindow(event) {
|
function isAddressVisibleWindow(event) {
|
||||||
if (event.status === 'canceled') return false;
|
|
||||||
const eventDateTime = parseEventDateTime(event);
|
const eventDateTime = parseEventDateTime(event);
|
||||||
if (!eventDateTime || Number.isNaN(eventDateTime.getTime())) return false;
|
if (!eventDateTime || Number.isNaN(eventDateTime.getTime())) {
|
||||||
const now = Date.now();
|
return false;
|
||||||
const start = eventDateTime.getTime();
|
}
|
||||||
const revealStart = start - (24 * 60 * 60 * 1000);
|
|
||||||
const revealEnd = start + (1 * 60 * 60 * 1000);
|
const msUntilStart = eventDateTime.getTime() - Date.now();
|
||||||
return now >= revealStart && now <= revealEnd;
|
const twelveHoursInMs = 12 * 60 * 60 * 1000;
|
||||||
|
|
||||||
|
return msUntilStart >= 0 && msUntilStart <= twelveHoursInMs;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Gibt true zurück, wenn ein Event bereits vorbei ist (1h nach Start).
|
// Parse fuer ISO- und lokalisierte Datumsformate aus den Eventdaten.
|
||||||
function isEventPastAddressWindow(event) {
|
|
||||||
const eventDateTime = parseEventDateTime(event);
|
|
||||||
if (!eventDateTime || Number.isNaN(eventDateTime.getTime())) return false;
|
|
||||||
const revealEnd = eventDateTime.getTime() + (1 * 60 * 60 * 1000);
|
|
||||||
return Date.now() > revealEnd;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse für ISO- und lokalisierte Datumsformate aus den Eventdaten.
|
|
||||||
function parseEventDateTime(event) {
|
function parseEventDateTime(event) {
|
||||||
if (!event?.date) {
|
if (!event?.date) {
|
||||||
return null;
|
return null;
|
||||||
@ -737,42 +517,28 @@
|
|||||||
day = Number(isoDateMatch[3]);
|
day = Number(isoDateMatch[3]);
|
||||||
} else {
|
} else {
|
||||||
const monthMap = {
|
const monthMap = {
|
||||||
jan: 1,
|
JAN: 1,
|
||||||
januar: 1,
|
FEB: 2,
|
||||||
feb: 2,
|
'MÄR': 3,
|
||||||
februar: 2,
|
MRZ: 3,
|
||||||
'mär': 3,
|
APR: 4,
|
||||||
mrz: 3,
|
MAI: 5,
|
||||||
mar: 3,
|
JUN: 6,
|
||||||
maerz: 3,
|
JUL: 7,
|
||||||
märz: 3,
|
AUG: 8,
|
||||||
apr: 4,
|
SEP: 9,
|
||||||
april: 4,
|
OKT: 10,
|
||||||
mai: 5,
|
NOV: 11,
|
||||||
jun: 6,
|
DEZ: 12
|
||||||
juni: 6,
|
|
||||||
jul: 7,
|
|
||||||
juli: 7,
|
|
||||||
aug: 8,
|
|
||||||
august: 8,
|
|
||||||
sep: 9,
|
|
||||||
sept: 9,
|
|
||||||
september: 9,
|
|
||||||
okt: 10,
|
|
||||||
oktober: 10,
|
|
||||||
nov: 11,
|
|
||||||
november: 11,
|
|
||||||
dez: 12,
|
|
||||||
dezember: 12
|
|
||||||
};
|
};
|
||||||
const localizedMatch = dateValue.match(/^(\d{1,2})\.\s*([A-Za-zÄÖÜäöü]{3,9})\.?\s*(\d{4})$/);
|
const localizedMatch = dateValue.match(/^(\d{1,2})\.\s*([A-ZÄÖÜ]{3})\.\s*(\d{4})$/);
|
||||||
|
|
||||||
if (!localizedMatch) {
|
if (!localizedMatch) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
day = Number(localizedMatch[1]);
|
day = Number(localizedMatch[1]);
|
||||||
month = monthMap[String(localizedMatch[2]).toLowerCase()];
|
month = monthMap[localizedMatch[2]];
|
||||||
year = Number(localizedMatch[3]);
|
year = Number(localizedMatch[3]);
|
||||||
|
|
||||||
if (!month) {
|
if (!month) {
|
||||||
@ -787,51 +553,22 @@
|
|||||||
return new Date(year, month - 1, day, hours, minutes, 0, 0);
|
return new Date(year, month - 1, day, hours, minutes, 0, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Formatiert ein Eventdatum konsistent für die Profilkarten.
|
// Formatiert ein Eventdatum konsistent fuer die Profilkarten.
|
||||||
function formatEventDate(dateString) {
|
function formatEventDate(dateString) {
|
||||||
if (!dateString) {
|
if (!dateString) {
|
||||||
return 'Kein Datum';
|
return 'Kein Datum';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (/^\d{4}-\d{2}-\d{2}$/.test(dateString)) {
|
||||||
|
const [year, month, day] = dateString.split('-');
|
||||||
|
const monthLabel = ['Januar', 'Februar', 'Maerz', 'April', 'Mai', 'Juni', 'Juli', 'August', 'September', 'Oktober', 'November', 'Dezember'][Number(month) - 1];
|
||||||
|
return `${Number(day)}. ${monthLabel} ${year}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return dateString;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ISO Format: 2026-02-12
|
// Vereinheitlicht die Zeitanzeige fuer die Profilseite.
|
||||||
if (/^\d{4}-\d{2}-\d{2}$/.test(dateString)) {
|
|
||||||
const [year, month, day] = dateString.split('-');
|
|
||||||
|
|
||||||
const monthLabel = ['Jan', 'Feb', 'Mär', 'Apr', 'Mai', 'Jun', 'Jul', 'Aug', 'Sep', 'Okt', 'Nov', 'Dez'][Number(month) - 1];
|
|
||||||
|
|
||||||
return `${Number(day)}. ${monthLabel} ${year}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Format: 12. FEB. 2026
|
|
||||||
const match = dateString.match(/^(\d{1,2})\.\s*([A-ZÄÖÜ]{3})\.\s*(\d{4})$/);
|
|
||||||
|
|
||||||
if (match) {
|
|
||||||
const day = match[1];
|
|
||||||
const month = match[2];
|
|
||||||
|
|
||||||
const shortMonthMap = {
|
|
||||||
JAN: 'Januar',
|
|
||||||
FEB: 'Februar',
|
|
||||||
MÄR: 'März',
|
|
||||||
MRZ: 'März',
|
|
||||||
APR: 'April',
|
|
||||||
MAI: 'Mai',
|
|
||||||
JUN: 'Juni',
|
|
||||||
JUL: 'Juli',
|
|
||||||
AUG: 'August',
|
|
||||||
SEP: 'September',
|
|
||||||
OKT: 'Oktober',
|
|
||||||
NOV: 'November',
|
|
||||||
DEZ: 'Dezember'
|
|
||||||
};
|
|
||||||
|
|
||||||
return `${day}. ${shortMonthMap[month] || month} ${match[3]}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
return dateString;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Vereinheitlicht die Zeitanzeige für die Profilseite.
|
|
||||||
function formatEventTime(timeString) {
|
function formatEventTime(timeString) {
|
||||||
if (!timeString) {
|
if (!timeString) {
|
||||||
return 'Keine Uhrzeit';
|
return 'Keine Uhrzeit';
|
||||||
@ -840,7 +577,7 @@
|
|||||||
return timeString.includes('UHR') ? timeString.replace('UHR', 'Uhr').trim() : timeString;
|
return timeString.includes('UHR') ? timeString.replace('UHR', 'Uhr').trim() : timeString;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Normalisiert Vergleichswerte für robuste String-Matches.
|
// Normalisiert Vergleichswerte fuer robuste String-Matches.
|
||||||
function normalizeText(value) {
|
function normalizeText(value) {
|
||||||
return String(value || '').trim().toLowerCase();
|
return String(value || '').trim().toLowerCase();
|
||||||
}
|
}
|
||||||
|
|||||||
187
js/navigation.js
@ -1,18 +1,16 @@
|
|||||||
// =============================================
|
// =============================================
|
||||||
// Dynamische Navigation
|
// Dynamische Navigation
|
||||||
// Je nach Login-Status wird die Kopfzeile für
|
// Je nach Login-Status wird die Kopfzeile fuer
|
||||||
// alle Seiten mit passendem Markup aufgebaut.
|
// alle Seiten mit passendem Markup aufgebaut.
|
||||||
// =============================================
|
// =============================================
|
||||||
|
|
||||||
document.addEventListener('DOMContentLoaded', () => {
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
const CURRENT_USER_KEY = 'socialCookingCurrentUser';
|
const CURRENT_USER_KEY = 'socialCookingCurrentUser';
|
||||||
const REGISTRATION_STORAGE_KEY = 'socialCookingRegistrations';
|
const navContainers = document.querySelectorAll('.nav-tab-links');
|
||||||
const EVENTS_STORAGE_KEY = 'socialCookingEvents';
|
|
||||||
const navcontainers = document.querySelectorAll('.nav-tab-links');
|
|
||||||
const currentPage = (window.location.pathname.split('/').pop() || 'index.html').toLowerCase();
|
const currentPage = (window.location.pathname.split('/').pop() || 'index.html').toLowerCase();
|
||||||
|
|
||||||
// Beendet früh, falls auf einer Seite keine Hauptnavigation vorhanden ist.
|
// Beendet frueh, falls auf einer Seite keine Hauptnavigation vorhanden ist.
|
||||||
if (!navcontainers.length) {
|
if (!navContainers.length) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -27,118 +25,10 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Logout-Funktion
|
// Baut die Navigation fuer ausgeloggte Besucher.
|
||||||
window.logout = function() {
|
|
||||||
localStorage.removeItem(CURRENT_USER_KEY);
|
|
||||||
window.location.href = 'index.html';
|
|
||||||
};
|
|
||||||
|
|
||||||
// Hilfsfunktionen für Datumsberechnungen
|
|
||||||
function parseEventDateTime(event) {
|
|
||||||
if (!event?.date) return null;
|
|
||||||
const dateValue = String(event.date).trim();
|
|
||||||
const isoDateMatch = dateValue.match(/^(\d{4})-(\d{2})-(\d{2})$/);
|
|
||||||
let year, month, day;
|
|
||||||
|
|
||||||
if (isoDateMatch) {
|
|
||||||
year = Number(isoDateMatch[1]);
|
|
||||||
month = Number(isoDateMatch[2]);
|
|
||||||
day = Number(isoDateMatch[3]);
|
|
||||||
} else {
|
|
||||||
const monthMap = {
|
|
||||||
jan: 1, januar: 1,
|
|
||||||
feb: 2, februar: 2,
|
|
||||||
'mär': 3, mrz: 3, mar: 3, maerz: 3, märz: 3,
|
|
||||||
apr: 4, april: 4,
|
|
||||||
mai: 5,
|
|
||||||
jun: 6, juni: 6,
|
|
||||||
jul: 7, juli: 7,
|
|
||||||
aug: 8, august: 8,
|
|
||||||
sep: 9, sept: 9, september: 9,
|
|
||||||
okt: 10, oktober: 10,
|
|
||||||
nov: 11, november: 11,
|
|
||||||
dez: 12, dezember: 12
|
|
||||||
};
|
|
||||||
const localizedMatch = dateValue.match(/^(\d{1,2})\.\s*([A-Za-zÄÖÜäöü]{3,9})\.?\s*(\d{4})$/);
|
|
||||||
if (!localizedMatch) return null;
|
|
||||||
day = Number(localizedMatch[1]);
|
|
||||||
month = monthMap[String(localizedMatch[2]).toLowerCase()];
|
|
||||||
year = Number(localizedMatch[3]);
|
|
||||||
if (!month) return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const timeMatch = String(event.time || '').match(/(\d{1,2}):(\d{2})/);
|
|
||||||
const hours = timeMatch ? Number(timeMatch[1]) : 0;
|
|
||||||
const minutes = timeMatch ? Number(timeMatch[2]) : 0;
|
|
||||||
return new Date(year, month - 1, day, hours, minutes, 0, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
function isAddressVisibleWindow(event) {
|
|
||||||
if (event.status === 'canceled') return false;
|
|
||||||
const eventDateTime = parseEventDateTime(event);
|
|
||||||
if (!eventDateTime || Number.isNaN(eventDateTime.getTime())) return false;
|
|
||||||
const now = Date.now();
|
|
||||||
const start = eventDateTime.getTime();
|
|
||||||
const revealStart = start - (24 * 60 * 60 * 1000);
|
|
||||||
const revealEnd = start + (1 * 60 * 60 * 1000);
|
|
||||||
return now >= revealStart && now <= revealEnd;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function hasUnreadNotifications(user) {
|
|
||||||
if (!user || !user.email) return false;
|
|
||||||
|
|
||||||
let events = [];
|
|
||||||
try {
|
|
||||||
const rawStored = localStorage.getItem(EVENTS_STORAGE_KEY);
|
|
||||||
const storedEvents = rawStored ? JSON.parse(rawStored) : [];
|
|
||||||
|
|
||||||
const response = await fetch('data/events.json');
|
|
||||||
const apiEvents = await response.json();
|
|
||||||
events = [...storedEvents, ...apiEvents];
|
|
||||||
} catch (err) {
|
|
||||||
console.error('Fehler beim Laden der Events für Benachrichtigungen', err);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
let map = {};
|
|
||||||
try {
|
|
||||||
const rawReg = localStorage.getItem(REGISTRATION_STORAGE_KEY);
|
|
||||||
map = rawReg ? JSON.parse(rawReg) : {};
|
|
||||||
} catch (err) {}
|
|
||||||
|
|
||||||
let seenAddresses = [];
|
|
||||||
try {
|
|
||||||
const rawSeen = localStorage.getItem('socialCookingSeenAddresses');
|
|
||||||
seenAddresses = rawSeen ? JSON.parse(rawSeen) : [];
|
|
||||||
} catch (err) {}
|
|
||||||
|
|
||||||
const registeredIds = Array.isArray(map[user.email]) ? map[user.email] : [];
|
|
||||||
const idSet = new Set(registeredIds.map(id => Number(id)));
|
|
||||||
|
|
||||||
const myRegisteredEvents = events.filter(e => idSet.has(Number(e.id)));
|
|
||||||
|
|
||||||
// Unread = address visible AND NOT marked as seen
|
|
||||||
return myRegisteredEvents.some(e => isAddressVisibleWindow(e) && !seenAddresses.includes(Number(e.id)));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Baut die Navigation für ausgeloggte Besucher.
|
|
||||||
function buildLoggedOutNavigation() {
|
function buildLoggedOutNavigation() {
|
||||||
const loginIsActive = currentPage === 'login.html';
|
const loginIsActive = currentPage === 'login.html';
|
||||||
const signupIsActive = currentPage === 'signup.html';
|
const signupIsActive = currentPage === 'signup.html';
|
||||||
const isIndex = currentPage === 'index.html' || currentPage === '';
|
|
||||||
|
|
||||||
// Auf der Startseite, Login und Signup nur Login anzeigen.
|
|
||||||
if (isIndex || loginIsActive || signupIsActive) {
|
|
||||||
return `
|
|
||||||
<a
|
|
||||||
class="button-small"
|
|
||||||
href="login.html"
|
|
||||||
aria-label="Login"
|
|
||||||
>
|
|
||||||
Login
|
|
||||||
</a>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
|
|
||||||
return `
|
return `
|
||||||
<a
|
<a
|
||||||
@ -160,63 +50,20 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Baut die Navigation für eingeloggte Benutzer.
|
// Baut die Navigation fuer eingeloggte Benutzer.
|
||||||
function buildLoggedInNavigation(user, hasNotifications) {
|
function buildLoggedInNavigation() {
|
||||||
const initial = (user.vorname || 'U').charAt(0).toUpperCase();
|
|
||||||
const isEventOverview = currentPage === 'event_overview.html';
|
|
||||||
const isEventCreate = currentPage === 'event_create.html';
|
|
||||||
const notificationMarkup = hasNotifications ? '<span class="notification-dot"></span>' : '';
|
|
||||||
|
|
||||||
return `
|
return `
|
||||||
<a
|
<a class="nav-tab" href="event_overview.html">Event finden</a>
|
||||||
class="nav-tab${isEventCreate ? ' nav-tab--active' : ''}"
|
<a class="nav-tab" href="event_create.html">Event erstellen</a>
|
||||||
href="event_create.html"
|
<a class="button-small" href="my_profil.html" aria-label="Mein Profil">Mein Profil</a>
|
||||||
${isEventCreate ? 'aria-current="page"' : ''}
|
|
||||||
>
|
|
||||||
Event erstellen
|
|
||||||
</a>
|
|
||||||
<a
|
|
||||||
class="nav-tab${isEventOverview ? ' nav-tab--active' : ''}"
|
|
||||||
href="event_overview.html"
|
|
||||||
${isEventOverview ? 'aria-current="page"' : ''}
|
|
||||||
>
|
|
||||||
Event finden
|
|
||||||
</a>
|
|
||||||
<button
|
|
||||||
class="button-small logout-button"
|
|
||||||
onclick="logout()"
|
|
||||||
aria-label="Logout"
|
|
||||||
>
|
|
||||||
Logout
|
|
||||||
</button>
|
|
||||||
<a
|
|
||||||
class="profile-pill"
|
|
||||||
href="my_profil.html"
|
|
||||||
aria-label="Mein Profil"
|
|
||||||
title="${user.vorname || 'Profil'}"
|
|
||||||
>
|
|
||||||
${initial}
|
|
||||||
${notificationMarkup}
|
|
||||||
</a>
|
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function initNavigation() {
|
const currentUser = getCurrentUser();
|
||||||
const currentUser = getCurrentUser();
|
const nextMarkup = currentUser ? buildLoggedInNavigation() : buildLoggedOutNavigation();
|
||||||
let nextMarkup;
|
|
||||||
|
|
||||||
if (currentUser) {
|
// Wendet das passende Markup auf alle vorhandenen Kopf-Navigationen an.
|
||||||
const hasNotifications = await hasUnreadNotifications(currentUser);
|
navContainers.forEach(container => {
|
||||||
nextMarkup = buildLoggedInNavigation(currentUser, hasNotifications);
|
container.innerHTML = nextMarkup;
|
||||||
} else {
|
});
|
||||||
nextMarkup = buildLoggedOutNavigation();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wendet das passende Markup auf alle vorhandenen Kopf-Navigationen an.
|
|
||||||
navcontainers.forEach(container => {
|
|
||||||
container.innerHTML = nextMarkup;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
initNavigation();
|
|
||||||
});
|
});
|
||||||
|
|||||||
119
js/signup.js
@ -1,4 +1,4 @@
|
|||||||
// =============================================
|
// =============================================
|
||||||
// Signup-Logik
|
// Signup-Logik
|
||||||
// Diese Datei validiert das Formular, speichert
|
// Diese Datei validiert das Formular, speichert
|
||||||
// neue Benutzer lokal und startet direkt die Session.
|
// neue Benutzer lokal und startet direkt die Session.
|
||||||
@ -31,7 +31,7 @@ function setStoredUsers(users) {
|
|||||||
localStorage.setItem(USERS_STORAGE_KEY, JSON.stringify(users));
|
localStorage.setItem(USERS_STORAGE_KEY, JSON.stringify(users));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Speichert den aktiven Benutzer für nachfolgende Seiten.
|
// Speichert den aktiven Benutzer fuer nachfolgende Seiten.
|
||||||
function setCurrentUser(user) {
|
function setCurrentUser(user) {
|
||||||
localStorage.setItem(CURRENT_USER_KEY, JSON.stringify(user));
|
localStorage.setItem(CURRENT_USER_KEY, JSON.stringify(user));
|
||||||
}
|
}
|
||||||
@ -42,110 +42,107 @@ function openWelcomeModal() {
|
|||||||
document.body.style.overflow = 'hidden';
|
document.body.style.overflow = 'hidden';
|
||||||
}
|
}
|
||||||
|
|
||||||
// Funktion zum Schliessen des Welcome Modals
|
// Funktion zum Schließen des Welcome Modals
|
||||||
function closeWelcomeModal() {
|
function closeWelcomeModal() {
|
||||||
welcomeModal.classList.remove('show');
|
welcomeModal.classList.remove('show');
|
||||||
document.body.style.overflow = 'auto';
|
document.body.style.overflow = 'auto';
|
||||||
window.location.href = 'event_overview.html';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Hauptfunktion für Formularvalidierung und Speicherung.
|
// Hauptfunktion fuer Formularvalidierung und Speicherung.
|
||||||
function validateForm(event) {
|
function validateForm(event) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
|
||||||
|
let isValid = true;
|
||||||
|
|
||||||
// Wir zeigen pro Submit nur den ersten Fehler an.
|
// Vorname-Validierung
|
||||||
// So bleibt der Formularfluss klar und ruhig.
|
|
||||||
const vornameValue = vornameInput.value.trim();
|
const vornameValue = vornameInput.value.trim();
|
||||||
const vornameGroup = vornameInput.parentElement;
|
const vornameGroup = vornameInput.parentElement;
|
||||||
const nachnameGroup = nachnameInput.parentElement;
|
|
||||||
const emailGroup = emailInput.parentElement;
|
|
||||||
const passwortGroup = passwortInput.parentElement;
|
|
||||||
|
|
||||||
vornameGroup.classList.remove('has-error');
|
|
||||||
nachnameGroup.classList.remove('has-error');
|
|
||||||
emailGroup.classList.remove('has-error');
|
|
||||||
passwortGroup.classList.remove('has-error');
|
|
||||||
|
|
||||||
if (!vornameValue) {
|
if (!vornameValue) {
|
||||||
vornameGroup.classList.add('has-error');
|
vornameGroup.classList.add('has-error');
|
||||||
vornameInput.focus();
|
isValid = false;
|
||||||
return;
|
} else {
|
||||||
|
vornameGroup.classList.remove('has-error');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Nachname-Validierung
|
// Nachname-Validierung
|
||||||
const nachnameValue = nachnameInput.value.trim();
|
const nachnameValue = nachnameInput.value.trim();
|
||||||
|
const nachnameGroup = nachnameInput.parentElement;
|
||||||
|
|
||||||
if (!nachnameValue) {
|
if (!nachnameValue) {
|
||||||
nachnameGroup.classList.add('has-error');
|
nachnameGroup.classList.add('has-error');
|
||||||
nachnameInput.focus();
|
isValid = false;
|
||||||
return;
|
} else {
|
||||||
|
nachnameGroup.classList.remove('has-error');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Email-Validierung
|
// Email-Validierung
|
||||||
const emailValue = emailInput.value.trim();
|
const emailValue = emailInput.value.trim();
|
||||||
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
||||||
|
const emailGroup = emailInput.parentElement;
|
||||||
|
|
||||||
if (!emailValue) {
|
if (!emailValue) {
|
||||||
emailGroup.classList.add('has-error');
|
emailGroup.classList.add('has-error');
|
||||||
document.getElementById('emailError').textContent = 'Bitte gib deine E-Mail Adresse ein.';
|
document.getElementById('emailError').textContent = 'Bitte gib deine E-Mail Adresse ein.';
|
||||||
emailInput.focus();
|
isValid = false;
|
||||||
return;
|
} else if (!emailRegex.test(emailValue)) {
|
||||||
}
|
|
||||||
|
|
||||||
if (!emailRegex.test(emailValue)) {
|
|
||||||
emailGroup.classList.add('has-error');
|
emailGroup.classList.add('has-error');
|
||||||
document.getElementById('emailError').textContent = 'Bitte gib eine gültige E-Mail Adresse ein.';
|
document.getElementById('emailError').textContent = 'Bitte gib eine gültige E-Mail Adresse ein.';
|
||||||
emailInput.focus();
|
isValid = false;
|
||||||
return;
|
} else {
|
||||||
|
emailGroup.classList.remove('has-error');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Passwort-Validierung
|
// Passwort-Validierung
|
||||||
const passwortValue = passwortInput.value;
|
const passwortValue = passwortInput.value;
|
||||||
|
const passwortGroup = passwortInput.parentElement;
|
||||||
|
|
||||||
if (!passwortValue) {
|
if (!passwortValue) {
|
||||||
passwortGroup.classList.add('has-error');
|
passwortGroup.classList.add('has-error');
|
||||||
document.getElementById('passwortError').textContent = 'Bitte gib ein Passwort ein.';
|
document.getElementById('passwortError').textContent = 'Bitte gib ein Passwort ein.';
|
||||||
passwortInput.focus();
|
isValid = false;
|
||||||
return;
|
} else if (passwortValue.length < 8) {
|
||||||
}
|
|
||||||
|
|
||||||
if (passwortValue.length < 8) {
|
|
||||||
passwortGroup.classList.add('has-error');
|
passwortGroup.classList.add('has-error');
|
||||||
document.getElementById('passwortError').textContent = 'Dein Passwort muss mindestens 8 Zeichen lang sein.';
|
document.getElementById('passwortError').textContent = 'Dein Passwort muss mindestens 8 Zeichen lang sein.';
|
||||||
passwortInput.focus();
|
isValid = false;
|
||||||
return;
|
} else {
|
||||||
|
passwortGroup.classList.remove('has-error');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wenn alles gültig ist:
|
// Wenn alles gueltig ist:
|
||||||
// 1) auf doppelte E-Mail prüfen
|
// 1) auf doppelte E-Mail pruefen
|
||||||
// 2) neuen Benutzer speichern
|
// 2) neuen Benutzer speichern
|
||||||
// 3) als aktuellen Benutzer einloggen
|
// 3) als aktuellen Benutzer einloggen
|
||||||
const existingUsers = getStoredUsers();
|
if (isValid) {
|
||||||
const emailLower = emailValue.toLowerCase();
|
const existingUsers = getStoredUsers();
|
||||||
const emailAlreadyUsed = existingUsers.some(user => user.email?.toLowerCase() === emailLower);
|
const emailLower = emailValue.toLowerCase();
|
||||||
|
const emailAlreadyUsed = existingUsers.some(user => user.email?.toLowerCase() === emailLower);
|
||||||
|
|
||||||
if (emailAlreadyUsed) {
|
if (emailAlreadyUsed) {
|
||||||
emailGroup.classList.add('has-error');
|
emailGroup.classList.add('has-error');
|
||||||
document.getElementById('emailError').textContent = 'Diese E-Mail ist bereits registriert. Bitte nutze den Login.';
|
document.getElementById('emailError').textContent = 'Diese E-Mail ist bereits registriert. Bitte nutze den Login.';
|
||||||
emailInput.focus();
|
return;
|
||||||
return;
|
}
|
||||||
|
|
||||||
|
const newUser = {
|
||||||
|
id: Date.now(),
|
||||||
|
vorname: vornameValue,
|
||||||
|
nachname: nachnameValue,
|
||||||
|
email: emailValue,
|
||||||
|
passwort: passwortValue,
|
||||||
|
createdAt: new Date().toISOString(),
|
||||||
|
source: 'signup'
|
||||||
|
};
|
||||||
|
|
||||||
|
setStoredUsers([newUser, ...existingUsers]);
|
||||||
|
setCurrentUser(newUser);
|
||||||
|
|
||||||
|
openWelcomeModal();
|
||||||
|
// Hier koennte spaeter ein echter API-Call zum Backend stehen.
|
||||||
|
|
||||||
|
// Weiterleitung zur Event-Overview-Seite.
|
||||||
|
window.location.href = 'event_overview.html';
|
||||||
}
|
}
|
||||||
|
|
||||||
const newUser = {
|
|
||||||
id: Date.now(),
|
|
||||||
vorname: vornameValue,
|
|
||||||
nachname: nachnameValue,
|
|
||||||
email: emailValue,
|
|
||||||
passwort: passwortValue,
|
|
||||||
createdAt: new Date().toISOString(),
|
|
||||||
source: 'signup'
|
|
||||||
};
|
|
||||||
|
|
||||||
setStoredUsers([newUser, ...existingUsers]);
|
|
||||||
setCurrentUser(newUser);
|
|
||||||
|
|
||||||
openWelcomeModal();
|
|
||||||
// Weiterleitung erfolgt beim Klick auf "Weiter zu den Events".
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fehlerbehandlung bei Input-Änderung (entfernt Fehler wenn Benutzer korrigiert)
|
// Fehlerbehandlung bei Input-Änderung (entfernt Fehler wenn Benutzer korrigiert)
|
||||||
@ -177,7 +174,7 @@ passwortInput.addEventListener('input', function() {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Modal schliessen wenn ausserhalb geklickt wird
|
// Modal schließen wenn außerhalb geklickt wird
|
||||||
welcomeModal.addEventListener('click', function(event) {
|
welcomeModal.addEventListener('click', function(event) {
|
||||||
if (event.target === welcomeModal) {
|
if (event.target === welcomeModal) {
|
||||||
closeWelcomeModal();
|
closeWelcomeModal();
|
||||||
@ -185,4 +182,4 @@ welcomeModal.addEventListener('click', function(event) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Form Submit Event
|
// Form Submit Event
|
||||||
signupForm.addEventListener('submit', validateForm);
|
signupForm.addEventListener('submit', validateForm);
|
||||||
66
login.html
@ -3,76 +3,62 @@
|
|||||||
<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>Invité | Login</title>
|
<title>Login - Social Cooking</title>
|
||||||
|
|
||||||
<!-- Stylesheet für diese Seite -->
|
<!-- Stylesheet für diese Seite-->
|
||||||
<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">
|
||||||
<script src="js/navigation.js" defer></script>
|
<script src="js/navigation.js" defer></script>
|
||||||
|
|
||||||
|
|
||||||
</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/logo_invite.svg" alt="Invité">
|
<img src="assets/logo_invite.svg" alt="Invite Logo">
|
||||||
</a>
|
</a>
|
||||||
<nav class="nav-tab-links" aria-label="Hauptnavigation">
|
<nav class="nav-tab-links" aria-label="Hauptnavigation">
|
||||||
<a class="button-small" href="login.html" aria-label="Login">Login</a>
|
<a class="button-small auth-nav-button auth-nav-button--active" href="login.html" aria-label="Login" aria-current="page">Login</a>
|
||||||
|
<a class="button-small auth-nav-button auth-nav-button--default" href="signup.html" aria-label="Signup">Signup</a>
|
||||||
</nav>
|
</nav>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<!-- Main Content -->
|
<!-- Main Content -->
|
||||||
<div class="container-login layout-narrow">
|
<div class="main-content">
|
||||||
|
<div class="container">
|
||||||
|
<div class="image-section">
|
||||||
|
<img src="assets/cooking.jpg" alt="Social Cooking">
|
||||||
|
</div>
|
||||||
|
|
||||||
<div>
|
<div class="form-section">
|
||||||
<h1>Login</h1>
|
<h1>Login</h1>
|
||||||
|
|
||||||
<form id="loginForm" novalidate >
|
<form id="loginForm">
|
||||||
<div class="form-group margin-bottom-16">
|
<div class="form-group">
|
||||||
<p class= "label-input-field" for="email">E-Mail</p>
|
<label for="email">E-Mail</label>
|
||||||
<input type="email" id="email" name="email" required placeholder="Deine E-mail-Adresse">
|
<input type="email" id="email" name="email" required placeholder="deine.email@example.com">
|
||||||
<div class="error-message error-message--field-callout" id="emailError">Bitte gib eine gültige E-Mail-Adresse ein.</div>
|
<div class="error-message" id="emailError">Bitte gib eine gültige E-Mail Adresse ein.</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-group margin-bottom-40">
|
<div class="form-group">
|
||||||
<p class= "label-input-field" for="passwort">Passwort</p>
|
<label for="passwort">Passwort</label>
|
||||||
<input type="password" id="passwort" name="passwort" required placeholder="Dein Passwort">
|
<input type="password" id="passwort" name="passwort" required placeholder="Gib dein Passwort ein">
|
||||||
<div class="error-message error-message--field-callout" id="passwortError">Bitte gib dein Passwort ein.</div>
|
<div class="error-message" id="passwortError">Bitte gib dein Passwort ein.</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button class="button-primary margin-bottom-24">Login</button>
|
<button type="submit" class="button">Login</button>
|
||||||
|
|
||||||
<div class="link-text">
|
<div class="signup-hint">
|
||||||
Du hast noch keinen Account? <a href="signup.html">Hier geht es zur Registration.</a>
|
Du hast noch keinen Account? <a href="signup.html">Hier geht es zur Anmeldung.</a>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div> <!-- Schliesst main-content -->
|
||||||
</div> <!-- Schliesst container -->
|
|
||||||
<div class="snackbar" id="snackbar">Willkommen zurück! Du wirst weitergeleitet...</div>
|
|
||||||
<script src="js/login.js"></script>
|
<script src="js/login.js"></script>
|
||||||
<div class="footer">
|
|
||||||
<div class="footer-left">
|
|
||||||
<p class="p-small inline">© <img src="assets/logo_invite.svg" alt="Invité Logo" class="footer-invite_logo" /></p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="footer-center">
|
|
||||||
<a href="https://www.instagram.com" target="_blank" rel="noopener noreferrer" class="instagram-invite__link">
|
|
||||||
<img src="assets/Icon_instagram.png" alt="Instagram" class="instagram-invite_icon" />
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="footer-right footer-links">
|
|
||||||
<a href="impressum.html" class="link-text-footer">Impressum</a>
|
|
||||||
<a href="datenschutz.html" class="link-text-footer">Datenschutz</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
132
my_profil.html
@ -19,65 +19,50 @@
|
|||||||
<img src="assets/logo_invite.svg" alt="Invite Logo">
|
<img src="assets/logo_invite.svg" alt="Invite Logo">
|
||||||
</a>
|
</a>
|
||||||
<nav class="nav-tab-links" aria-label="Hauptnavigation">
|
<nav class="nav-tab-links" aria-label="Hauptnavigation">
|
||||||
<button id="logout-button" class="button-small profile-logout" type="button">
|
<a class="button-small" href="login.html" aria-label="Login">Login</a>
|
||||||
Logout
|
|
||||||
</button>
|
|
||||||
<a class="button-small" href="login.html" aria-label="Login">
|
|
||||||
Login
|
|
||||||
</a>
|
|
||||||
</nav>
|
</nav>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<main class="layout-wide">
|
<main class="container profile-page">
|
||||||
<section class="profile-hero" aria-label="Profilübersicht">
|
<section class="profile-hero" aria-label="Profilübersicht">
|
||||||
<div>
|
<div>
|
||||||
<p class="badge margin-bottom-40">Mein Bereich</p>
|
<p class="profile-kicker">Mein Bereich</p>
|
||||||
<h1 id="headline">Mein Profil</h1>
|
<h1 id="profile-headline">Mein Profil</h1>
|
||||||
<p id="profile-subline" class="profile-subline">Hier findest du deine Events, deine Anmeldungen und kannst deine Profildaten verwalten.</p>
|
<p id="profile-subline" class="profile-subline">Hier findest du deine Events, deine Anmeldungen und kannst deine Profildaten verwalten.</p>
|
||||||
</div>
|
</div>
|
||||||
|
<button id="logout-button" class="button-small profile-logout" type="button">Logout</button>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section id="logged-out-state" class="profile-panel hidden" aria-live="polite">
|
<section id="logged-out-state" class="profile-panel hidden" aria-live="polite">
|
||||||
<h2 class="panel-title">Du bist noch nicht eingeloggt</h2>
|
<h2 class="panel-title">Du bist noch nicht eingeloggt</h2>
|
||||||
<p>Melde dich an, damit wir deine Events und Anmeldungen anzeigen können.</p>
|
<p>Melde dich an, damit wir deine Events und Anmeldungen anzeigen können.</p>
|
||||||
<div class="profile-cta-row">
|
<div class="profile-cta-row">
|
||||||
<a class="button-primary" href="login.html">Zum Login</a>
|
<a class="button" href="login.html">Zum Login</a>
|
||||||
<a class="button-primary profile-button-secondary" href="signup.html">Konto erstellen</a>
|
<a class="button profile-button-secondary" href="signup.html">Konto erstellen</a>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section id="logged-in-content" class="profile-grid">
|
<section id="logged-in-content" class="profile-grid">
|
||||||
<nav class="category-items" aria-label="Profilbereiche">
|
<nav class="profile-tabs" aria-label="Profilbereiche">
|
||||||
<button type="button" class="category-item is-active category-item-profile" data-category-item="hosting">
|
<button type="button" class="profile-tab is-active" data-profile-tab="hosting">Hosting</button>
|
||||||
Meine Events <span class="btn-count" id="btn-my-events-count">0</span>
|
<button type="button" class="profile-tab" data-profile-tab="teilnehmen">Teilnehmen</button>
|
||||||
</button>
|
<button type="button" class="profile-tab" data-profile-tab="einstellungen">Einstellungen</button>
|
||||||
<button type="button"
|
|
||||||
class="category-item category-item-profile" data-category-item="teilnehmen">
|
|
||||||
Meine Anmeldungen <span class="btn-count" id="btn-my-registrations-count">0</span>
|
|
||||||
</button>
|
|
||||||
<button type="button" class="category-item category-item-profile" data-category-item="einstellungen">Profil-Einstellungen</button>
|
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
<article data-profile-panel="hosting">
|
<article class="profile-panel" data-profile-panel="hosting">
|
||||||
<div class="panel-head">
|
<div class="panel-head">
|
||||||
|
<h2 class="panel-title">Meine Events</h2>
|
||||||
<span id="my-events-count" class="panel-count">0</span>
|
<span id="my-events-count" class="panel-count">0</span>
|
||||||
</div>
|
</div>
|
||||||
<div id="my-events-list" class="profile-card-list"></div>
|
<div id="my-events-list" class="profile-card-list"></div>
|
||||||
</article>
|
</article>
|
||||||
|
|
||||||
<article data-profile-panel="teilnehmen">
|
<article class="profile-panel hidden" data-profile-panel="teilnehmen">
|
||||||
<div class="panel-head">
|
<div class="panel-head">
|
||||||
|
<h2 class="panel-title">Meine Anmeldungen</h2>
|
||||||
<span id="my-registrations-count" class="panel-count">0</span>
|
<span id="my-registrations-count" class="panel-count">0</span>
|
||||||
</div>
|
</div>
|
||||||
<p class="info-abmeldung">
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="var(--olive)" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="flex-shrink: 0; margin-top: 1px;">
|
|
||||||
<circle cx="12" cy="12" r="10"/>
|
|
||||||
<line x1="12" y1="8" x2="12" y2="8"/>
|
|
||||||
<line x1="12" y1="12" x2="12" y2="16"/>
|
|
||||||
</svg>
|
|
||||||
Eine Abmeldung ist bis 24 Stunden vor Eventbeginn möglich. Bitte respektiere den Host und melde dich rechtzeitig ab, wenn du nicht kommen kannst.
|
|
||||||
</p>
|
|
||||||
<div id="my-registrations-list" class="profile-card-list"></div>
|
<div id="my-registrations-list" class="profile-card-list"></div>
|
||||||
</article>
|
</article>
|
||||||
|
|
||||||
@ -85,108 +70,39 @@
|
|||||||
<h2 class="panel-title">Profil verwalten</h2>
|
<h2 class="panel-title">Profil verwalten</h2>
|
||||||
<form id="profile-form" novalidate>
|
<form id="profile-form" novalidate>
|
||||||
<div class="form-grid">
|
<div class="form-grid">
|
||||||
<div class="margin-bottom-16">
|
<div class="form-group">
|
||||||
<label class="label-input-field" for="vorname">Vorname</label>
|
<label for="vorname">Vorname</label>
|
||||||
<input type="text" id="vorname" name="vorname" required>
|
<input type="text" id="vorname" name="vorname" required>
|
||||||
<p class="input-error" id="vorname-error">Bitte gib deinen Vornamen ein.</p>
|
<p class="input-error" id="vorname-error">Bitte gib deinen Vornamen ein.</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="margin-bottom-16">
|
<div class="form-group">
|
||||||
<label class="label-input-field" for="nachname">Nachname</label>
|
<label for="nachname">Nachname</label>
|
||||||
<input type="text" id="nachname" name="nachname" required>
|
<input type="text" id="nachname" name="nachname" required>
|
||||||
<p class="input-error" id="nachname-error">Bitte gib deinen Nachnamen ein.</p>
|
<p class="input-error" id="nachname-error">Bitte gib deinen Nachnamen ein.</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="margin-bottom-16">
|
<div class="form-group">
|
||||||
<label class="label-input-field" for="email">E-Mail</label>
|
<label for="email">E-Mail</label>
|
||||||
<input type="email" id="email" name="email" required>
|
<input type="email" id="email" name="email" required>
|
||||||
<p class="input-error" id="email-error">Bitte gib eine gültige E-Mail-Adresse ein.</p>
|
<p class="input-error" id="email-error">Bitte gib eine gültige E-Mail-Adresse ein.</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="margin-bottom-40">
|
<div class="form-group">
|
||||||
<label class="label-input-field" for="passwort">Passwort</label>
|
<label for="passwort">Passwort</label>
|
||||||
<input type="password" id="passwort" name="passwort" minlength="6" placeholder="Mindestens 6 Zeichen">
|
<input type="password" id="passwort" name="passwort" minlength="6" placeholder="Mindestens 6 Zeichen">
|
||||||
<p class="input-hint">Nur ausfüllen, wenn du dein Passwort ändern möchtest.</p>
|
<p class="input-hint">Nur ausfüllen, wenn du dein Passwort ändern möchtest.</p>
|
||||||
<p class="input-error" id="passwort-error">Das Passwort muss mindestens 6 Zeichen lang sein.</p>
|
<p class="input-error" id="passwort-error">Das Passwort muss mindestens 6 Zeichen lang sein.</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button class="button-primary" type="submit">Profil speichern</button>
|
<button class="button" type="submit">Profil speichern</button>
|
||||||
<p id="profile-feedback" class="profile-feedback" aria-live="polite"></p>
|
<p id="profile-feedback" class="profile-feedback" aria-live="polite"></p>
|
||||||
</form>
|
</form>
|
||||||
</article>
|
</article>
|
||||||
</section>
|
</section>
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
<!-- Logout Confirmation Modal -->
|
|
||||||
<div id="logoutModal" class="modal">
|
|
||||||
<div class="modal-content">
|
|
||||||
<div class="modal-header">
|
|
||||||
<h2>Abmelden?</h2>
|
|
||||||
</div>
|
|
||||||
<p class="modal-body">
|
|
||||||
Bist du sicher, dass du dich abmelden möchtest?
|
|
||||||
<button type="button" class="modal-close" aria-label="Popup schließen">×</button>
|
|
||||||
</p>
|
|
||||||
<div class="modal-footer">
|
|
||||||
<button class="button-primary button--outline" type="button" onclick="closeLogoutModal()">Abbrechen</button>
|
|
||||||
<button class="button-primary" type="button" onclick="confirmLogout()">Abmelden</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="unregister-confirm-modal" class="modal">
|
|
||||||
<div class="modal-content">
|
|
||||||
<div class="modal-header">
|
|
||||||
<h2>Pläne haben sich geändert?</h2>
|
|
||||||
<button type="button" class="modal-close" id="unregister-modal-close" aria-label="Popup schließen">×</button>
|
|
||||||
</div>
|
|
||||||
<div class="modal-body">
|
|
||||||
<p>Schade, dass du nicht dabei sein kannst! Aber manchmal kommt einfach etwas dazwischen. Wenn du dich jetzt abmeldest, gibst du deinen Stuhl am Tisch für jemand anderen aus der Community frei. So hilfst du bei der Planung und ein anderer Feinschmecker freut sich über den freien Platz.</p>
|
|
||||||
</div>
|
|
||||||
<div class="modal-footer" style="display: flex; justify-content: flex-end; gap: 16px; margin-top: 24px;">
|
|
||||||
<button class="button-primary button--outline" type="button" id="unregister-modal-cancel">Abbrechen</button>
|
|
||||||
<button class="button-primary button-primary-abmelden" type="button" id="confirm-unregister-btn">Ja, abmelden</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<!-- Snackbar: Feedback bei Abmeldung von Events -->
|
|
||||||
<div class="snackbar" id="snackbar"></div>
|
|
||||||
|
|
||||||
<!-- Event-Absage Confirmation Modal -->
|
|
||||||
<div id="cancelEventModal" class="modal">
|
|
||||||
<div class="modal-content">
|
|
||||||
<div class="modal-header">
|
|
||||||
<h2>Event absagen?</h2>
|
|
||||||
</div>
|
|
||||||
<p class="modal-body">
|
|
||||||
Bist du sicher, dass du dieses Event absagen möchtest? Diese Aktion kann nicht rückgängig gemacht werden.
|
|
||||||
<button type="button" class="modal-close" aria-label="Popup schließen">×</button>
|
|
||||||
</p>
|
|
||||||
<div class="modal-footer">
|
|
||||||
<button class="button-secondary" type="button" onclick="closeCancelEventModal()">Abbrechen</button>
|
|
||||||
<button class="button-primary-abmelden" type="button" id="confirmCancelEventBtn";>Event absagen</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="footer">
|
|
||||||
<div class="footer-left">
|
|
||||||
<p class="p-small inline">© <img src="assets/logo_invite.svg" alt="Invité Logo" class="footer-invite_logo" /></p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="footer-center">
|
|
||||||
<a href="https://www.instagram.com" target="_blank" rel="noopener noreferrer" class="instagram-invite__link">
|
|
||||||
<img src="assets/Icon_instagram.png" alt="Instagram" class="instagram-invite_icon" />
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="footer-right footer-links">
|
|
||||||
<a href="impressum.html" class="link-text-footer">Impressum</a>
|
|
||||||
<a href="datenschutz.html" class="link-text-footer">Datenschutz</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<script src="js/my_profil.js"></script>
|
<script src="js/my_profil.js"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
82
signup.html
@ -3,12 +3,12 @@
|
|||||||
<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>Invité | Registration</title>
|
<title>Kontaktseite - Invité</title>
|
||||||
|
|
||||||
<!-- Globales Stylesheet -->
|
|
||||||
<link rel="stylesheet" href="css/stylesheet_global.css">
|
|
||||||
<!-- Stylesheet für diese Seite-->
|
<!-- Stylesheet für diese Seite-->
|
||||||
<link rel="stylesheet" href="css/login_signup.css">
|
<link rel="stylesheet" href="css/login_signup.css">
|
||||||
|
<!-- Globales Stylesheet -->
|
||||||
|
<link rel="stylesheet" href="css/stylesheet_global.css">
|
||||||
<script src="js/navigation.js" defer></script>
|
<script src="js/navigation.js" defer></script>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
@ -20,14 +20,19 @@
|
|||||||
<img src="assets/logo_invite.svg" alt="Invite Logo">
|
<img src="assets/logo_invite.svg" alt="Invite Logo">
|
||||||
</a>
|
</a>
|
||||||
<nav class="nav-tab-links" aria-label="Hauptnavigation">
|
<nav class="nav-tab-links" aria-label="Hauptnavigation">
|
||||||
<a class="button-small" href="login.html" aria-label="Login">Login</a>
|
<a class="button-small auth-nav-button auth-nav-button--default" href="login.html" aria-label="Login">Login</a>
|
||||||
|
<a class="button-small auth-nav-button auth-nav-button--active" href="signup.html" aria-label="Signup" aria-current="page">Signup</a>
|
||||||
</nav>
|
</nav>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<!-- Main Content -->
|
<!-- Main Content -->
|
||||||
<div class="container-registration layout-wide">
|
<div class="main-content">
|
||||||
<div class="text-section">
|
<div class="container">
|
||||||
|
<div class="image-section">
|
||||||
|
<img src="assets/cooking.jpg" alt="Social Cooking">
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="form-section">
|
<div class="form-section">
|
||||||
<h1>Erstelle deinen Account</h1>
|
<h1>Erstelle deinen Account</h1>
|
||||||
|
|
||||||
@ -35,80 +40,57 @@
|
|||||||
<strong>Hinweis:</strong> Sichtbar auf der Plattform ist nur dein Vorname. Erst einer Anmeldung zum Event ist der Nachname für die Teilnehmenden sichtbar.
|
<strong>Hinweis:</strong> Sichtbar auf der Plattform ist nur dein Vorname. Erst einer Anmeldung zum Event ist der Nachname für die Teilnehmenden sichtbar.
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<form id="signupForm" novalidate>
|
<form id="signupForm">
|
||||||
<div class="form-group margin-bottom-16">
|
<div class="form-group">
|
||||||
<p class= "label-input-field" for="vorname">Vorname*</p>
|
<label for="vorname">Vorname *</label>
|
||||||
<input type="text" id="vorname" name="vorname" required placeholder="Dein Vorname">
|
<input type="text" id="vorname" name="vorname" required placeholder="Dein Vorname">
|
||||||
<div class="error-message error-message--field-callout" id="vornameError">Bitte gib deinen Vornamen ein.</div>
|
<div class="error-message" id="vornameError">Bitte gib deinen Vornamen ein.</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-group margin-bottom-16">
|
<div class="form-group">
|
||||||
<p class= "label-input-field" for="nachname">Nachname*</p>
|
<label for="nachname">Nachname *</label>
|
||||||
<input type="text" id="nachname" name="nachname" required placeholder="Dein Nachname">
|
<input type="text" id="nachname" name="nachname" required placeholder="Dein Nachname">
|
||||||
<div class="error-message error-message--field-callout" id="nachnameError">Bitte gib deinen Nachnamen ein.</div>
|
<div class="error-message" id="nachnameError">Bitte gib deinen Nachnamen ein.</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-group margin-bottom-16">
|
<div class="form-group">
|
||||||
<p class= "label-input-field" for="email">E-Mail*</p>
|
<label for="email">E-Mail *</label>
|
||||||
<input type="email" id="email" name="email" required placeholder="Deine E-mail-Adresse">
|
<input type="email" id="email" name="email" required placeholder="deine.email@example.com">
|
||||||
<div class="error-message error-message--field-callout" id="emailError">Bitte gib eine gültige E-Mail-Adresse ein.</div>
|
<div class="error-message" id="emailError">Bitte gib eine gültige E-Mail Adresse ein.</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-group margin-bottom-40">
|
<div class="form-group">
|
||||||
<p class= "label-input-field" for="passwort">Passwort*</p>
|
<label for="passwort">Passwort *</label>
|
||||||
<input type="password" id="passwort" name="passwort" required placeholder="Mindestens 8 Zeichen">
|
<input type="password" id="passwort" name="passwort" required placeholder="Mindestens 8 Zeichen">
|
||||||
<div class="error-message error-message--field-callout" id="passwortError">Dein Passwort muss mindestens 8 Zeichen lang sein.</div>
|
<div class="error-message" id="passwortError">Dein Passwort muss mindestens 8 Zeichen lang sein.</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button type="submit" class="button-primary margin-bottom-24">Konto erstellen</button>
|
<button type="submit" class="button">Konto erstellen</button>
|
||||||
|
|
||||||
|
|
||||||
<div class="link-text">
|
<div class="login-hint">
|
||||||
Du hast bereits einen Account? <a href="login.html">Hier geht es zum Login.</a>
|
Du hast bereits einen Account? <a href="login.html">Hier geht es zum Login.</a>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
<div class="image-section">
|
|
||||||
<img src="assets/index_cooking.jpg" alt="Social Cooking">
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div> <!-- Schliesst container -->
|
</div> <!-- Schließt main-content -->
|
||||||
|
|
||||||
<!-- Welcome Modal -->
|
<!-- Welcome Modal -->
|
||||||
<div id="welcomeModal" class="modal">
|
<div id="welcomeModal" class="modal">
|
||||||
<div class="modal-content">
|
<div class="modal-content">
|
||||||
<div class="modal-header">
|
<div class="modal-header">
|
||||||
<h2>Konto erfolgreich erstellt!</h2>
|
|
||||||
<button class="close-btn" onclick="closeWelcomeModal()">×</button>
|
<button class="close-btn" onclick="closeWelcomeModal()">×</button>
|
||||||
|
<h2>🎉 Willkommen bei Invité!</h2>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
Willkommen bei Invité! Dein Account wurde erfolgreich erstellt. Entdecke jetzt die neuesten Events in deiner Nähe.
|
Hier findest du die Übersicht zu den aktuellsten Events.
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
<button class="button-primary" onclick="closeWelcomeModal()">Weiter zu den Events</button>
|
<button class="btn-primary" onclick="closeWelcomeModal()">Weiter zu den Events</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<script src="js/signup.js"></script>
|
<script src="js/signup.js"></script>
|
||||||
<div class="footer">
|
|
||||||
<div class="footer-left">
|
|
||||||
<p class="p-small inline">© <img src="assets/logo_invite.svg" alt="Invité Logo" class="footer-invite_logo" /></p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="footer-center">
|
|
||||||
<a href="https://www.instagram.com" target="_blank" rel="noopener noreferrer" class="instagram-invite__link">
|
|
||||||
<img src="assets/Icon_instagram.png" alt="Instagram" class="instagram-invite_icon" />
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="footer-right footer-links">
|
|
||||||
<a href="impressum.html" class="link-text-footer">Impressum</a>
|
|
||||||
<a href="datenschutz.html" class="link-text-footer">Datenschutz</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||