Compare commits
No commits in common. "main" and "Hinweise-Text-einfügen" have entirely different histories.
main
...
Hinweise-T
|
Before Width: | Height: | Size: 53 KiB After Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 283 KiB |
|
Before Width: | Height: | Size: 493 KiB |
40
assets/icon_eating-svgrepo-com.svg
Normal file
@ -0,0 +1,40 @@
|
||||
<?xml version="1.0" encoding="iso-8859-1"?>
|
||||
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
|
||||
<svg fill="#000000" height="800px" width="800px" version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
viewBox="0 0 298.748 298.748" xml:space="preserve">
|
||||
<g id="XMLID_1429_">
|
||||
<g>
|
||||
<g>
|
||||
<path d="M89.486,99.966l5.118,2.999l10.451-6.427c1.463-0.9,2.999-1.573,4.57-2.038l-12.606-7.387
|
||||
c-3.552-2.081-8.113-0.889-10.193,2.661C84.746,93.323,85.937,97.887,89.486,99.966z"/>
|
||||
<path d="M194.008,187.239c5.392,0,9.764-4.372,9.764-9.764c0-5.393-4.372-9.764-9.764-9.764H99.344
|
||||
c-5.393,0-9.764,4.372-9.764,9.764c0,5.393,4.372,9.764,9.764,9.764h39.885v46.522h-7.948c-4.113,0-7.447,3.334-7.447,7.447
|
||||
c0,4.113,3.334,7.447,7.447,7.447h30.791c4.113,0,7.447-3.334,7.447-7.447c0-4.113-3.334-7.447-7.447-7.447h-7.949v-46.522
|
||||
H194.008z"/>
|
||||
<path d="M80.72,177.226c-1.169-3.279-3.799-5.825-7.112-6.888l-10.951-3.51l7.194-25.11l-18.93-16.393l30.517,14.102
|
||||
c2.778,1.283,6.078,1.142,8.782-0.521l29.67-18.248c4.379-2.694,5.746-8.427,3.052-12.806c-2.694-4.379-8.427-5.746-12.806-3.052
|
||||
l-25.418,15.634l-25.987-12.009l13.749,2.106c-1.581-1.776-3.658-3.147-6.112-3.851l-21.263-6.092
|
||||
c-3.485-0.998-7.227-0.497-10.326,1.384c-3.099,1.881-5.271,4.97-5.993,8.522l-12.821,63.063
|
||||
c-0.664,3.266,0.174,6.657,2.283,9.237c0.327,0.4,0.686,0.764,1.06,1.11H7.621c-0.541,0-1.067,0.061-1.576,0.17
|
||||
C2.603,184.73,0,187.751,0,191.384v49.815c0,4.113,3.334,7.447,7.447,7.447s7.447-3.334,7.447-7.447v-42.401H49.29v42.401
|
||||
c0,4.113,3.334,7.447,7.447,7.447s7.447-3.334,7.447-7.447v-43.818l15.626,43.848c2.072,5.815,8.466,8.843,14.272,6.772
|
||||
c5.812-2.071,8.844-8.461,6.773-14.273L80.72,177.226z"/>
|
||||
<circle cx="66.103" cy="76.449" r="19.243"/>
|
||||
<circle cx="252.446" cy="69.335" r="19.243"/>
|
||||
<path d="M291.129,183.905h-14.077c0.415-0.256,0.819-0.532,1.201-0.844c2.579-2.111,4.073-5.268,4.07-8.6l-0.054-64.353
|
||||
c-0.006-7.573-6.471-13.548-14.029-12.947l-22.049,1.752c-7.156,0.569-12.498,6.831-11.929,13.988l0.922,11.603l1.781-0.025
|
||||
l16.473-14.381l-15.502,24.067l-29.838,0.426c-5.14,0.073-9.248,4.3-9.175,9.441c0.073,5.095,4.226,9.176,9.305,9.176
|
||||
c0.045,0,0.091,0,0.136-0.001l34.828-0.498c3.117-0.044,6.005-1.646,7.693-4.267l18.205-28.263l-10.052,33.515
|
||||
c-1.559,5.197-6.287,8.576-11.439,8.655c-5.26,0.071-3.13,0.042-9.398,0.131l0.362,4.555l-12.661,3.092
|
||||
c-3.652,0.892-6.611,3.561-7.872,7.102l-20.136,56.505c-2.072,5.812,0.961,12.201,6.773,14.273
|
||||
c5.812,2.072,12.202-0.963,14.272-6.772l15.626-43.85v43.82c0,4.113,3.335,7.447,7.447,7.447c4.113,0,7.447-3.334,7.447-7.447
|
||||
v-42.401h34.395v42.401c0,4.113,3.335,7.447,7.447,7.447s7.447-3.334,7.447-7.447v-49.815
|
||||
C298.748,187.265,295.406,183.905,291.129,183.905z"/>
|
||||
<path d="M104.088,144.815c-0.057,0.25-0.09,0.502-0.09,0.757c0,4.239,6.881,7.896,16.812,9.586v1.397
|
||||
c0,2.202,1.786,3.988,3.988,3.988h16.795c2.203,0,3.988-1.786,3.988-3.988v-1.396c9.931-1.69,16.812-5.346,16.812-9.586
|
||||
c0-0.255-0.032-0.506-0.088-0.755c-0.11-0.487-0.933-0.854-1.915-0.854h-54.387C105.024,143.963,104.2,144.33,104.088,144.815z"
|
||||
/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.1 KiB |
|
Before Width: | Height: | Size: 313 KiB After Width: | Height: | Size: 93 KiB |
|
Before Width: | Height: | Size: 178 KiB |
|
Before Width: | Height: | Size: 145 KiB |
|
Before Width: | Height: | Size: 566 KiB |
|
Before Width: | Height: | Size: 689 KiB |
|
Before Width: | Height: | Size: 666 KiB |
|
Before Width: | Height: | Size: 604 KiB |
@ -14,9 +14,16 @@
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.event-create-page {
|
||||
width: min(100% - 2rem, var(--max-width));
|
||||
margin: 0 auto;
|
||||
padding: var(--space-5) 0 0;
|
||||
}
|
||||
|
||||
.event-flow-header {
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
justify-content: flex-end;
|
||||
margin-bottom: var(--space-4);
|
||||
}
|
||||
|
||||
.event-form {
|
||||
@ -27,45 +34,11 @@
|
||||
|
||||
.step {
|
||||
display: none;
|
||||
padding: var(--space-4) 0 var(--space-4);
|
||||
}
|
||||
|
||||
/*
|
||||
.submission-success {
|
||||
padding: var(--space-24) 0 var(--space-48);
|
||||
}*/
|
||||
|
||||
.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;
|
||||
padding: var(--space-4) 0 var(--space-7);
|
||||
}
|
||||
|
||||
.step--active {
|
||||
@ -73,20 +46,17 @@
|
||||
}
|
||||
|
||||
.step-layout {
|
||||
gap: 80px;
|
||||
}
|
||||
|
||||
.startseite {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 80px;
|
||||
width: min(100%, var(--content-width));
|
||||
margin: 0 auto;
|
||||
display: grid;
|
||||
gap: var(--space-40);
|
||||
}
|
||||
|
||||
.step-layout--intro {
|
||||
min-height: 60vh;
|
||||
align-content: center;
|
||||
grid-template-columns: 1fr;
|
||||
gap: var(--space-48);
|
||||
gap: var(--space-7);
|
||||
}
|
||||
|
||||
.step-copy,
|
||||
@ -94,15 +64,15 @@
|
||||
.form-field,
|
||||
fieldset {
|
||||
display: grid;
|
||||
gap: var(--space-4);
|
||||
}
|
||||
|
||||
.step-copy {
|
||||
gap: var(--space-24);
|
||||
align-content: start;
|
||||
gap: var(--space-4);
|
||||
}
|
||||
|
||||
.step-fields {
|
||||
gap: var(--space-32);
|
||||
gap: var(--space-5);
|
||||
}
|
||||
|
||||
.form-field,
|
||||
@ -110,12 +80,13 @@ fieldset {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
gap: var(--space-3);
|
||||
}
|
||||
|
||||
|
||||
.step-text {
|
||||
/* definiert Breite des Beschriebtexts der einzelnen Schritte*/
|
||||
max-width: 100%;
|
||||
max-width: 600px;
|
||||
}
|
||||
|
||||
.intro-card,
|
||||
@ -131,18 +102,44 @@ fieldset {
|
||||
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 {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: block;
|
||||
object-fit: cover;
|
||||
border-radius: var(--radius-lg);
|
||||
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.12);
|
||||
}
|
||||
|
||||
|
||||
label {
|
||||
font-family: var(--font-main);
|
||||
font-weight: 400;
|
||||
font-size: 1.25rem;
|
||||
}
|
||||
|
||||
|
||||
legend {
|
||||
font-size: 1rem;
|
||||
font-weight: 600;
|
||||
letter-spacing: var(--ls-sm);
|
||||
line-height: 1;
|
||||
margin-bottom: var(--space-3);
|
||||
}
|
||||
|
||||
.field-hint {
|
||||
color: var(--olive);
|
||||
font-size: 1rem;
|
||||
margin-bottom: var(--space-8);
|
||||
margin: -0.25rem 0 0;
|
||||
color: var(--color-muted);
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
|
||||
input[type="text"],
|
||||
@ -152,8 +149,8 @@ input[type="number"],
|
||||
textarea {
|
||||
font-family: var(--font-main);
|
||||
font-weight: 400;
|
||||
font-size: 1.125rem;
|
||||
padding: var(--space-16) var(--space-20);
|
||||
font-size: 1.25rem;;
|
||||
padding: 1rem 1.25rem;
|
||||
border: 1.5px solid var(--olive-light);
|
||||
border-radius: var(--radius-md);
|
||||
background: var(--butter-light);
|
||||
@ -183,7 +180,7 @@ input[type="date"]:hover,
|
||||
input[type="time"]:hover,
|
||||
input[type="number"]:hover,
|
||||
textarea:hover {
|
||||
border: 2px solid var(--olive);
|
||||
border-color: rgba(102, 52, 13, 0.28);
|
||||
}
|
||||
|
||||
input[type="text"]:focus,
|
||||
@ -191,7 +188,7 @@ input[type="date"]:focus,
|
||||
input[type="time"]:focus,
|
||||
input[type="number"]:focus,
|
||||
textarea:focus {
|
||||
border: 2px solid var(--olive);
|
||||
border-color: var(--olive-dark);
|
||||
}
|
||||
|
||||
.field-invalid {
|
||||
@ -201,12 +198,12 @@ textarea:focus {
|
||||
|
||||
.field-row {
|
||||
display: grid;
|
||||
gap: var(--space-24);
|
||||
gap: var(--space-4);
|
||||
}
|
||||
|
||||
.option-grid {
|
||||
display: grid;
|
||||
gap: var(--space-16);
|
||||
gap: var(--space-3);
|
||||
}
|
||||
|
||||
.option-card {
|
||||
@ -219,23 +216,6 @@ textarea:focus {
|
||||
transition: box-shadow 0.2s ease, transform 0.2s ease, background-color 0.2s ease;
|
||||
}
|
||||
|
||||
.option-card--with-icon {
|
||||
justify-items: center;
|
||||
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 {
|
||||
background: var(--olive-light);
|
||||
@ -243,28 +223,6 @@ textarea:focus {
|
||||
transform: translateY(-3px);
|
||||
}
|
||||
|
||||
.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 {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
@ -279,41 +237,23 @@ textarea:focus {
|
||||
|
||||
.option-card--invalid {
|
||||
border-color: var(--error) !important;
|
||||
box-shadow: var(--shadow-error);
|
||||
}
|
||||
|
||||
.guest-count-icon {
|
||||
display: block;
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
color: var(--black);
|
||||
font-size: 1.5rem;
|
||||
line-height: 1;
|
||||
box-shadow: 0 0 0 2px rgba(212, 75, 36, 0.14);
|
||||
}
|
||||
|
||||
.counter {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: var(--space-16);
|
||||
}
|
||||
|
||||
.counter-value-group {
|
||||
display: grid;
|
||||
justify-items: center;
|
||||
row-gap: var(--space-8);
|
||||
width: 6rem;
|
||||
gap: var(--space-3);
|
||||
}
|
||||
|
||||
.counter input {
|
||||
width: 100%;
|
||||
height: 3rem;
|
||||
padding-block: 0.75rem;
|
||||
width: 6rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.review-card {
|
||||
display: grid;
|
||||
gap: var(--space-24);
|
||||
gap: var(--space-4);
|
||||
padding: 0;
|
||||
border: 0;
|
||||
border-radius: 0;
|
||||
@ -323,8 +263,8 @@ textarea:focus {
|
||||
|
||||
.review-card--success {
|
||||
display: grid;
|
||||
gap: var(--space-32);
|
||||
padding: var(--space-16) 0 0;
|
||||
gap: var(--space-5);
|
||||
padding: var(--space-3) 0 0;
|
||||
border: 0;
|
||||
border-radius: 0;
|
||||
background: transparent;
|
||||
@ -333,58 +273,56 @@ textarea:focus {
|
||||
|
||||
.review-list {
|
||||
display: grid;
|
||||
gap: var(--space-12);
|
||||
gap: var(--space-4);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.review-item {
|
||||
display: grid;
|
||||
gap: var(--space-2);
|
||||
padding: 1rem 1.1rem;
|
||||
border: 1.5px solid var(--olive-light);
|
||||
border: 1px solid var(--input-border-soft);
|
||||
border-radius: 1.125rem;
|
||||
background: var(--butter-light);
|
||||
box-shadow: 0 1px 2px rgba(102, 52, 13, 0.04);
|
||||
cursor: pointer;
|
||||
transition: border-color 0.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:focus-visible {
|
||||
border: 2px solid var(--olive);
|
||||
box-shadow: var(--shadow-interaction);
|
||||
transform: translateY(-3px);
|
||||
.review-item:last-child {
|
||||
border-bottom: 1px solid var(--input-border-soft);
|
||||
}
|
||||
|
||||
.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 {
|
||||
font-weight: 600;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.review-item dd {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.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);
|
||||
white-space: pre-wrap;
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
.submission-success-actions {
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
width: 100%;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.flow-footer {
|
||||
padding-top: var(--space-80);
|
||||
bottom: 0;
|
||||
backdrop-filter: none;
|
||||
padding-top: var(--space-4);
|
||||
padding-bottom: env(safe-area-inset-bottom);
|
||||
}
|
||||
|
||||
@ -394,31 +332,32 @@ textarea:focus {
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.error-message {
|
||||
margin: 0;
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
|
||||
}
|
||||
|
||||
.progress-wrap {
|
||||
flex: 1;
|
||||
position: relative;
|
||||
width: min(100%, var(--content-width));
|
||||
margin: 0 auto;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
align-self: center;
|
||||
min-height: 2.75rem;
|
||||
gap: 0.75rem;
|
||||
}
|
||||
|
||||
.progress-label {
|
||||
position: absolute;
|
||||
top: -1.1rem;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
font-size: 1rem;
|
||||
font-weight: 400;
|
||||
color: var(--black);
|
||||
flex-shrink: 0;
|
||||
font-size: 0.8rem;
|
||||
font-weight: 600;
|
||||
color: var(--color-muted);
|
||||
white-space: nowrap;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.progress {
|
||||
flex: 1;
|
||||
width: 100%;
|
||||
height: 0.45rem;
|
||||
height: 0.3rem;
|
||||
background: var(--olive-light);
|
||||
border-radius: var(--radius-sm);
|
||||
overflow: hidden;
|
||||
@ -437,21 +376,16 @@ textarea:focus {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: var(--space-24);
|
||||
width: 100%;
|
||||
gap: var(--space-4);
|
||||
width: min(100%, var(--content-width));
|
||||
margin: 0 auto;
|
||||
padding: var(--space-4) 0;
|
||||
}
|
||||
|
||||
.flow-actions-right {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.error-message--callout {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
bottom: calc(100% + 1.25rem);
|
||||
gap: var(--space-4);
|
||||
}
|
||||
|
||||
.button--ghost:hover {
|
||||
@ -461,51 +395,25 @@ textarea:focus {
|
||||
|
||||
.button--intro {
|
||||
justify-self: start;
|
||||
margin-top: var(--space-12);
|
||||
margin-top: var(--space-2);
|
||||
}
|
||||
|
||||
/* Gallery Upload */
|
||||
.gallery-upload {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
gap: 0.75rem;
|
||||
}
|
||||
|
||||
.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;
|
||||
width: 5.5rem;
|
||||
height: 5.5rem;
|
||||
border-radius: var(--radius-sm, 0.5rem);
|
||||
overflow: hidden;
|
||||
flex-shrink: 0;
|
||||
@ -540,7 +448,7 @@ textarea:focus {
|
||||
.site-footer {
|
||||
width: min(100% - 2rem, var(--max-width));
|
||||
margin: 0 auto;
|
||||
padding: var(--space-32) 0 var(--space-40);
|
||||
padding: var(--space-5) 0 var(--space-40);
|
||||
color: var(--color-muted);
|
||||
text-align: center;
|
||||
}
|
||||
@ -556,11 +464,11 @@ textarea:focus-visible {
|
||||
@media (max-width: 767px) {
|
||||
.site-nav {
|
||||
flex-wrap: wrap;
|
||||
padding: var(--space-16) 0;
|
||||
padding: var(--space-3) 0;
|
||||
}
|
||||
|
||||
.site-nav-links {
|
||||
gap: var(--space-16);
|
||||
gap: var(--space-3);
|
||||
}
|
||||
|
||||
.flow-actions,
|
||||
@ -569,26 +477,6 @@ textarea:focus-visible {
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.error-message--callout {
|
||||
position: static;
|
||||
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 {
|
||||
justify-content: flex-start;
|
||||
}
|
||||
@ -596,6 +484,7 @@ textarea:focus-visible {
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.step-layout--intro {
|
||||
width: min(100%, 56rem);
|
||||
grid-template-columns: 1fr 1fr;
|
||||
align-items: stretch;
|
||||
gap: var(--space-8);
|
||||
@ -612,4 +501,4 @@ textarea:focus-visible {
|
||||
.option-grid--4 {
|
||||
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,7 +1,11 @@
|
||||
.container { max-width: 1100px; margin: 0 auto; padding: 20px; }
|
||||
|
||||
/* ---------------------------------------------------------
|
||||
Shared Typography Tokens
|
||||
Reuse common text styles across nav, controls and buttons
|
||||
--------------------------------------------------------- */
|
||||
|
||||
|
||||
.meta-filter select,
|
||||
.meta-filter input[type="date"],
|
||||
.detail-primary-btn {
|
||||
@ -12,19 +16,17 @@
|
||||
}
|
||||
|
||||
/* Heading hierarchy: page title > detail title > card title > section title */
|
||||
.overview-title-row {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
justify-content: space-between;
|
||||
gap: var(--space-24);
|
||||
}
|
||||
|
||||
.overview-title-row .overview-title {
|
||||
margin-bottom: var(--space-24);
|
||||
.overview-title {
|
||||
margin: 0 0 var(--space-4);
|
||||
color: var(--olive);
|
||||
font-family: "Bagel Fat One", cursive;
|
||||
font-size: clamp(38px, 5vw, 44px);
|
||||
font-weight: 400;
|
||||
line-height: 1.15;
|
||||
}
|
||||
|
||||
.detail-title {
|
||||
margin-bottom: var(--space-24);
|
||||
margin-bottom: var(--space-4);
|
||||
}
|
||||
|
||||
.detail-section-title {
|
||||
@ -39,15 +41,22 @@
|
||||
/* ---------------------------------------------------------
|
||||
Overview Header + Filters
|
||||
--------------------------------------------------------- */
|
||||
.filter-label {
|
||||
margin: 0 0 var(--space-1);
|
||||
letter-spacing: var(--ls-la);
|
||||
}
|
||||
|
||||
.filter-row {
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: var(--space-3);
|
||||
margin-bottom: var(--space-2);
|
||||
}
|
||||
|
||||
.category-group {
|
||||
display: flex;
|
||||
gap: var(--space-6);
|
||||
display: flex;
|
||||
gap: var(--space-1);
|
||||
margin-bottom: 0;
|
||||
flex-wrap: wrap;
|
||||
flex: 1;
|
||||
@ -61,13 +70,13 @@
|
||||
font-weight: 400;
|
||||
font-size: 1rem;
|
||||
line-height: 1;
|
||||
padding: var(--space-8) var(--space-20);
|
||||
padding: var(--space-1) var(--space-20);
|
||||
}
|
||||
|
||||
.meta-filter-group {
|
||||
display: flex;
|
||||
flex-wrap: nowrap;
|
||||
gap: var(--space-12);
|
||||
gap: var(--space-2);
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
@ -85,7 +94,7 @@
|
||||
border-radius: var(--radius-sm);
|
||||
background: var(--butter-light);
|
||||
height: 37px;
|
||||
padding: 0 var(--space-24);
|
||||
padding: 0 var(--space-4);
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
@ -107,18 +116,17 @@
|
||||
transition: background-color 0.2s ease;
|
||||
}
|
||||
|
||||
.meta-filter input[type="date"]:hover::-webkit-calendar-picker-indicator {
|
||||
filter: brightness(0.8);
|
||||
.meta-filter input[type="date"]::-webkit-calendar-picker-indicator:hover {
|
||||
filter: brightness(0.8);
|
||||
}
|
||||
|
||||
.meta-filter select {
|
||||
cursor: pointer;
|
||||
appearance: none;
|
||||
-webkit-appearance: none;
|
||||
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 12 8'%3E%3Cpath d='M1 1l5 5 5-5' stroke='currentColor' stroke-width='1.5' fill='none' stroke-linecap='butt' stroke-linejoin='miter'/%3E%3C/svg%3E");
|
||||
background-repeat: no-repeat;
|
||||
color: var(--black);
|
||||
background-position: right var(--space-24) center;
|
||||
background-position: right var(--space-4) center;
|
||||
}
|
||||
|
||||
/* ---------------------------------------------------------
|
||||
@ -127,7 +135,7 @@
|
||||
.event-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-16);
|
||||
gap: var(--space-3);
|
||||
}
|
||||
|
||||
.event-card {
|
||||
@ -135,7 +143,7 @@
|
||||
background: var(--butter-light);
|
||||
border: 1.5px solid var(--olive-light);
|
||||
border-radius: var(--radius-lg);
|
||||
padding: var(--space-32) var(--space-40);
|
||||
padding: var(--space-5) var(--space-40);
|
||||
display: flex;
|
||||
gap: var(--space-40);
|
||||
cursor: pointer;
|
||||
@ -163,7 +171,7 @@
|
||||
/* Primary metadata line: location + date/time/guest counters. */
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-24);
|
||||
gap: var(--space-4);
|
||||
margin-bottom: var(--space-0);
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
@ -183,7 +191,7 @@
|
||||
.event-meta-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-8);
|
||||
gap: var(--space-1);
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
@ -195,7 +203,7 @@
|
||||
font-weight: 400;
|
||||
font-size: 1rem;
|
||||
line-height: 1;
|
||||
padding: var(--space-8) var(--space-20);
|
||||
padding: var(--space-1) var(--space-20);
|
||||
}
|
||||
|
||||
.event-spec-chip {
|
||||
@ -220,14 +228,14 @@
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
gap: var(--space-16);
|
||||
gap: var(--space-3);
|
||||
padding-top: 36px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.button-plaetze {
|
||||
border: none;
|
||||
padding: var(--space-8) var(--space-24);
|
||||
padding: var(--space-01) var(--space-4);
|
||||
font-family: var(--font-main);
|
||||
font-weight: 400;
|
||||
font-size: 1.125rem;
|
||||
@ -242,14 +250,15 @@
|
||||
|
||||
.event-spots-full, .detail-spots-pill-full {
|
||||
/* Sold-out visual state, intentionally high-contrast and always filled. */
|
||||
border: 1.5px solid var(--tomato-light);
|
||||
padding: var(--space-8) var(--space-24);
|
||||
border: 1.5px solid var(--tomato);
|
||||
padding: var(--space-01) var(--space-4);
|
||||
border-radius: var(--radius-pill);
|
||||
color: var(--butter-light);
|
||||
background: var(--tomato-light);
|
||||
background: var(--tomato);
|
||||
font-family: var(--font-main);
|
||||
font-weight: 400;
|
||||
font-size: 1.25rem;
|
||||
opacity: 0.6;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
@ -265,6 +274,47 @@
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
/* ---------------------------------------------------------
|
||||
Overview Empty State
|
||||
--------------------------------------------------------- */
|
||||
.empty-state {
|
||||
text-align: center;
|
||||
padding: 42px var(--space-5);
|
||||
border: 2px solid var(--olive-light);
|
||||
border-radius: var(--radius-lg);
|
||||
background: var(--butter-light);
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.empty-state-kicker {
|
||||
margin: 0 0 8px;
|
||||
color: var(--olive);
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
letter-spacing: var(--ls-la);
|
||||
}
|
||||
|
||||
.empty-state h3 {
|
||||
margin: 0;
|
||||
font-family: "Jost", sans-serif;
|
||||
font-size: 28px;
|
||||
font-weight: 600;
|
||||
color: var(--brown);
|
||||
line-height: 1.25;
|
||||
}
|
||||
|
||||
.empty-state p {
|
||||
margin: 12px 0 0;
|
||||
font-size: 18px;
|
||||
color: var(--black);
|
||||
}
|
||||
|
||||
.empty-state-link {
|
||||
display: inline-block;
|
||||
margin-top: 20px;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
|
||||
/* ---------------------------------------------------------
|
||||
Detail Page
|
||||
@ -282,7 +332,7 @@
|
||||
.detail-top-row {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: var(--space-32);
|
||||
gap: var(--space-5);
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
@ -308,7 +358,7 @@
|
||||
.detail-side-stack {
|
||||
grid-area: side;
|
||||
display: grid;
|
||||
gap: var(--space-16);
|
||||
gap: var(--space-3);
|
||||
align-content: start;
|
||||
}
|
||||
|
||||
@ -337,8 +387,8 @@
|
||||
font-weight: 400;
|
||||
font-size: 1rem;
|
||||
line-height: 1;
|
||||
letter-spacing: var(--ls-lg);
|
||||
padding: var(--space-8) var(--space-20);
|
||||
letter-spacing: var(--ls-la);
|
||||
padding: var(--space-1) var(--space-20);
|
||||
}*/
|
||||
|
||||
.detail-gallery {
|
||||
@ -350,7 +400,7 @@
|
||||
/* Editorial mosaic: first image spans two rows, side images stack vertically. */
|
||||
grid-template-columns: 1fr 1fr;
|
||||
grid-template-rows: repeat(2, minmax(220px, 1fr));
|
||||
gap: var(--space-16);
|
||||
gap: var(--space-3);
|
||||
min-height: 520px;
|
||||
}
|
||||
|
||||
@ -384,30 +434,6 @@
|
||||
grid-row: 1 / 3;
|
||||
}
|
||||
|
||||
.detail-gallery-large--single {
|
||||
grid-template-columns: 1fr 1fr;
|
||||
grid-template-rows: auto;
|
||||
align-items: start;
|
||||
}
|
||||
|
||||
.detail-gallery-large--single .detail-gallery-item {
|
||||
grid-column: 1 / -1;
|
||||
grid-row: auto;
|
||||
align-self: start;
|
||||
}
|
||||
|
||||
.detail-gallery-large--single .detail-gallery-image {
|
||||
height: auto;
|
||||
min-height: 0;
|
||||
object-fit: contain;
|
||||
object-position: top center;
|
||||
background: var(--butter-light);
|
||||
}
|
||||
|
||||
.detail-gallery-large--single img:first-child {
|
||||
grid-row: auto;
|
||||
}
|
||||
|
||||
.detail-lightbox {
|
||||
/* Full-screen overlay for enlarged gallery image view. */
|
||||
position: fixed;
|
||||
@ -442,16 +468,16 @@
|
||||
.detail-lightbox-image {
|
||||
display: block;
|
||||
width: 100%;
|
||||
max-height: 80vh;
|
||||
max-height: 90vh;
|
||||
object-fit: contain;
|
||||
border-radius: var(--radius-md);
|
||||
background: var(--black);
|
||||
background: #111;
|
||||
}
|
||||
|
||||
.detail-lightbox-close {
|
||||
position: absolute;
|
||||
top: -40px;
|
||||
right: -40px;
|
||||
top: -42px;
|
||||
right: 0;
|
||||
border: 0;
|
||||
background: transparent;
|
||||
color: var(--butter-light);
|
||||
@ -463,7 +489,7 @@
|
||||
.detail-panel {
|
||||
background: var(--butter-light);
|
||||
border-radius: var(--radius-md);
|
||||
padding: var(--space-24) var(--space-32);
|
||||
padding: var(--space-4) var(--space-5);
|
||||
}
|
||||
|
||||
.detail-panel-compact {
|
||||
@ -474,7 +500,7 @@
|
||||
margin: 20px;
|
||||
list-style: disc;
|
||||
font-size: 1.125rem;
|
||||
line-height: 1.45;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.detail-participants-head {
|
||||
@ -484,6 +510,14 @@
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.detail-participants-link {
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
color: var(--tomato);
|
||||
letter-spacing: var(--ls-la);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.detail-avatar-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@ -511,26 +545,27 @@
|
||||
.detail-participants-full {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.detail-participant-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.participant-name {
|
||||
font-size: 1.125rem;
|
||||
font-weight: 400;
|
||||
font-size: 0.95rem;
|
||||
font-weight: 500;
|
||||
color: var(--black);
|
||||
}
|
||||
|
||||
.detail-participants-link {
|
||||
background: none;
|
||||
border: none;
|
||||
font-family: var(--font-main);
|
||||
color: var(--olive);
|
||||
font-size: 1rem;
|
||||
font-weight: 400;
|
||||
font-size: 0.85rem;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
padding: 0;
|
||||
text-decoration: underline;
|
||||
@ -547,13 +582,13 @@
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
gap: var(--space-16);
|
||||
gap: var(--space-2);
|
||||
background: var(--white);
|
||||
border: 1.5px solid var(--olive-light);
|
||||
border-radius: var(--radius-md);
|
||||
box-shadow: var(--shadow-interaction);
|
||||
|
||||
padding: var(--space-24) var(--space-32);
|
||||
padding: var(--space-4) var(--space-5);
|
||||
margin-top: var(--space-40);
|
||||
position: sticky;
|
||||
bottom: var(--space-40);
|
||||
@ -601,75 +636,46 @@
|
||||
.detail-action-buttons {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-8);
|
||||
gap: var(--space-1);
|
||||
}
|
||||
|
||||
|
||||
.detail-spots-pill {
|
||||
border: none;
|
||||
padding: var(--space-8) var(--space-24);
|
||||
padding: var(--space-01) var(--space-4);
|
||||
font-family: var(--font-main);
|
||||
font-weight: 400;
|
||||
font-size: 1rem;
|
||||
color: var(--olive);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
.detail-spots-pill-full {
|
||||
border: 1.5px solid var(--tomato-light);
|
||||
color: var(--butter-light);
|
||||
background: var(--tomato-light);
|
||||
font-family: var(--font-main);
|
||||
font-weight: 400;
|
||||
font-size: 1.25rem;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
border-color: var(--tomato);
|
||||
color: var(--tomato);
|
||||
opacity: 1;
|
||||
font-weight: 600;
|
||||
}*/
|
||||
|
||||
.detail-action-btn-wrap {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.detail-action-row {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.detail-dereg-column {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-end;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.detail-action-btn-wrap .button-plaetze,
|
||||
.detail-action-btn-wrap .event-spots-full,
|
||||
.detail-action-btn-wrap .button-primary,
|
||||
.detail-action-btn-wrap .button-primary-abmelden,
|
||||
.detail-action-btn-wrap .button-primary-eigener-event {
|
||||
/* Force identical sizing and vertical alignment for action buttons in detail bar */
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 52px;
|
||||
padding: 0 22px;
|
||||
font-size: 1.25rem;
|
||||
border-radius: var(--radius-pill);
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.detail-dereg-hint {
|
||||
display: block;
|
||||
font-size: 14px;
|
||||
font-size: 11px;
|
||||
font-weight: 400;
|
||||
color: var(--olive);
|
||||
opacity: 0.75;
|
||||
}
|
||||
|
||||
.detail-dereg-hint--placeholder {
|
||||
visibility: hidden;
|
||||
.detail-dereg-hint--closed {
|
||||
color: var(--tomato);
|
||||
font-weight: 500;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.detail-primary-btn {
|
||||
@ -711,6 +717,11 @@
|
||||
box-shadow: 0 2px 6px rgba(102, 52, 13, 0.22);
|
||||
}
|
||||
|
||||
.detail-primary-btn:disabled {
|
||||
opacity: 0.45;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.detail-primary-btn-own,
|
||||
.detail-primary-btn-own:disabled {
|
||||
border-color: var(--olive-light);
|
||||
@ -744,13 +755,6 @@
|
||||
font-size: 34px;
|
||||
}
|
||||
|
||||
.overview-info-button {
|
||||
width: 44px;
|
||||
height: 44px;
|
||||
flex-basis: 44px;
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
.top-nav-links {
|
||||
gap: 8px;
|
||||
}
|
||||
@ -827,15 +831,15 @@
|
||||
grid-row: auto;
|
||||
}
|
||||
|
||||
.detail-gallery-large--single {
|
||||
grid-template-columns: 1fr;
|
||||
grid-template-rows: auto;
|
||||
}
|
||||
|
||||
.detail-lightbox {
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
.detail-lightbox-close {
|
||||
top: -36px;
|
||||
font-size: 34px;
|
||||
}
|
||||
|
||||
.detail-section-title {
|
||||
font-size: 24px;
|
||||
}
|
||||
@ -877,19 +881,6 @@
|
||||
font-size: 30px;
|
||||
}
|
||||
|
||||
.overview-title-row {
|
||||
align-items: flex-start;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.overview-info-button {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
flex-basis: 40px;
|
||||
font-size: 1.35rem;
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
.top-nav-links {
|
||||
width: auto;
|
||||
justify-content: flex-end;
|
||||
@ -918,21 +909,11 @@
|
||||
.meta-filter-group {
|
||||
width: auto;
|
||||
flex-wrap: wrap;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.meta-filter {
|
||||
flex: 1 1 160px;
|
||||
min-width: 140px;
|
||||
}
|
||||
}
|
||||
|
||||
.filter-section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
|
||||
.filter-box summary {
|
||||
cursor: pointer;
|
||||
margin-bottom: var(--space-8);
|
||||
}
|
||||
153
css/index.css
@ -6,6 +6,7 @@
|
||||
|
||||
|
||||
/* --- Navigation overrides (index-specific) --- */
|
||||
|
||||
.nav-link {
|
||||
border: 2px solid var(--olive-light);
|
||||
transition: background-color 0.2s ease, color 0.2s ease;
|
||||
@ -35,9 +36,10 @@
|
||||
|
||||
/* --- Page layout --- */
|
||||
|
||||
.container {
|
||||
width: min(100% - 4rem, 1200px);
|
||||
.main-content {
|
||||
width: min(100% - 4rem, 1120px);
|
||||
margin: 0 auto;
|
||||
padding: 0 20px;
|
||||
}
|
||||
|
||||
|
||||
@ -46,10 +48,19 @@
|
||||
.hero {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 80px;
|
||||
margin-bottom: 100px;
|
||||
gap: 55px;
|
||||
margin-bottom: 88px;
|
||||
align-items: center;
|
||||
padding: 44px 0;
|
||||
}
|
||||
|
||||
/*
|
||||
.hero__buttons {
|
||||
display: flex;
|
||||
gap: var(--space-3);
|
||||
flex-wrap: wrap;
|
||||
}*/
|
||||
|
||||
.hero__right {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@ -92,7 +103,7 @@
|
||||
/* --- "So funktioniert's" steps --- */
|
||||
|
||||
.how-it-works {
|
||||
margin-bottom: 100px;
|
||||
margin-bottom: 70px;
|
||||
}
|
||||
|
||||
.how-it-works__header {
|
||||
@ -152,7 +163,7 @@
|
||||
.how-step_icon {
|
||||
font-size: 3.5rem;
|
||||
color: var(--brown);
|
||||
margin: var(--space-24) 0;
|
||||
margin: var(--space-4) 0;
|
||||
}
|
||||
|
||||
.how-step__png {
|
||||
@ -166,7 +177,7 @@
|
||||
}
|
||||
|
||||
.how-step_text {
|
||||
margin-bottom: var(--space-24);
|
||||
margin-bottom: var(--space-4);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
@ -179,7 +190,7 @@
|
||||
|
||||
.how-step__footer-badges {
|
||||
display: flex;
|
||||
gap: var(--space-16);
|
||||
gap: var(--space-2);
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
@ -204,10 +215,6 @@
|
||||
|
||||
/* --- Carousel gallery --- */
|
||||
|
||||
.gallery {
|
||||
margin-bottom: 100px;
|
||||
}
|
||||
|
||||
.gallery__carousel {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
@ -237,52 +244,6 @@
|
||||
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;
|
||||
@ -312,11 +273,11 @@
|
||||
}
|
||||
|
||||
.gallery__arrow--prev {
|
||||
left: var(--space-24);
|
||||
left: var(--space-4);
|
||||
}
|
||||
|
||||
.gallery__arrow--next {
|
||||
right: var(--space-24);
|
||||
right: var(--space-4);
|
||||
}
|
||||
|
||||
|
||||
@ -437,27 +398,35 @@
|
||||
/* --- FAQ Section: Akkordion --- */
|
||||
|
||||
.faq-section {
|
||||
margin-bottom: 0px;
|
||||
padding: var(--space-8) var(--space-4);
|
||||
margin: var(--space-8) 0 var(--space-5);
|
||||
}
|
||||
|
||||
.faq-section h2 {
|
||||
text-align: center;
|
||||
margin-bottom: var(--space-5);
|
||||
color: var(--brown);
|
||||
}
|
||||
|
||||
.faq-accordion {
|
||||
width: 100%;
|
||||
max-width: 56rem;
|
||||
margin: 0 auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-8);
|
||||
gap: var(--space-2);
|
||||
}
|
||||
|
||||
.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;
|
||||
background: var(--white);
|
||||
transition: background-color 0.2s ease, box-shadow var(--shadow-interaction);
|
||||
}
|
||||
|
||||
.faq-item:hover {
|
||||
background: var(--olive-light);
|
||||
box-shadow: var(--shadow-interaction);
|
||||
}
|
||||
|
||||
.faq-trigger {
|
||||
@ -465,32 +434,41 @@
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: var(--space-3) var(--space-4);
|
||||
background: transparent;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
font-family: var(--font-main);
|
||||
font-size: 1.25rem;
|
||||
font-weight: 400;;
|
||||
font-weight: 400;
|
||||
color: var(--olive);
|
||||
text-align: left;
|
||||
transition: background-color 0.2s ease;
|
||||
font-family: var(--font-main);
|
||||
}
|
||||
|
||||
.faq-trigger:hover {
|
||||
background-color: var(--butter-light);
|
||||
}
|
||||
|
||||
.faq-trigger:focus-visible {
|
||||
outline: 2px solid var(--olive);
|
||||
outline-offset: -2px;
|
||||
}
|
||||
|
||||
.faq-title {
|
||||
flex: 1;
|
||||
font-weight: 400;
|
||||
font-size: 1.25rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.faq-icon {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
font-size: 1.5rem;
|
||||
font-weight: 400;
|
||||
color: var(--black);
|
||||
font-weight: 300;
|
||||
color: var(--olive);
|
||||
transition: transform 0.3s ease;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
@ -500,32 +478,25 @@
|
||||
}
|
||||
|
||||
.faq-content {
|
||||
padding: 0;
|
||||
padding: 0 var(--space-4);
|
||||
max-height: 0;
|
||||
overflow: hidden;
|
||||
transition: max-height 0.3s ease;
|
||||
transition: max-height 0.3s ease, padding 0.3s ease;
|
||||
font-size: 1.125rem;
|
||||
line-height: 1.7;
|
||||
color: var(--black);
|
||||
font-family: var(--font-main);
|
||||
}
|
||||
|
||||
|
||||
.faq-content p {
|
||||
margin: 0;
|
||||
padding: var(--space-12) var(--space-40) var(--space-12) 0;
|
||||
padding: var(--space-3) 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)
|
||||
padding: var(--space-3) var(--space-4);
|
||||
}
|
||||
|
||||
|
||||
@ -533,8 +504,8 @@
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.faq-section {
|
||||
padding: var(--space-40) var(--space-24);
|
||||
margin: var(--space-40) 0 var(--space-32);
|
||||
padding: var(--space-40) var(--space-4);
|
||||
margin: var(--space-40) 0 var(--space-5);
|
||||
}
|
||||
|
||||
.faq-trigger {
|
||||
|
||||
@ -4,96 +4,154 @@
|
||||
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);
|
||||
|
||||
/* --- Page layout --- */
|
||||
|
||||
.main-content {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
flex: 1;
|
||||
padding: var(--space-4);
|
||||
}
|
||||
|
||||
.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);
|
||||
.container {
|
||||
background-color: var(--white);
|
||||
border-radius: var(--radius-lg);
|
||||
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
|
||||
max-width: 1000px;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
gap: var(--space-4);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.text-section {
|
||||
padding: var(--space-40) var(--space-80) var(--space-80) var(--space-80);
|
||||
}
|
||||
|
||||
|
||||
/* --- Image section --- */
|
||||
|
||||
.image-section {
|
||||
height: 100%;
|
||||
flex: 1;
|
||||
background-color: var(--butter-light);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-height: 500px;
|
||||
}
|
||||
|
||||
.image-section img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
display: block;
|
||||
}
|
||||
|
||||
|
||||
.form-group.has-error {
|
||||
margin-bottom: 0;
|
||||
/* --- Form section --- */
|
||||
|
||||
.form-section {
|
||||
flex: 1;
|
||||
padding: 40px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
h1 {
|
||||
color: var(--black);
|
||||
margin-bottom: var(--space-4);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
|
||||
/* --- Form elements --- */
|
||||
|
||||
.form-group {
|
||||
margin-bottom: var(--space-4);
|
||||
}
|
||||
|
||||
label {
|
||||
display: block;
|
||||
margin-bottom: var(--space-1);
|
||||
color: var(--black);
|
||||
font-weight: 600;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
input[type="text"],
|
||||
input[type="email"],
|
||||
input[type="password"] {
|
||||
font-size: 1.125rem;
|
||||
font-family: var(--font-main);
|
||||
width: 100%;
|
||||
padding: var(--space-8) var(--space-16);
|
||||
border: 1.5px solid var(--olive-light);
|
||||
padding: var(--space-2);
|
||||
border: 2px solid var(--olive-light);
|
||||
border-radius: var(--radius-md);
|
||||
background: transparent;
|
||||
font-size: 0.9rem;
|
||||
font-family: var(--font-main);
|
||||
transition: border-color 0.3s ease;
|
||||
background: var(--white);
|
||||
color: var(--black);
|
||||
}
|
||||
|
||||
input:focus {
|
||||
outline: none;
|
||||
border: 2px solid var(--olive);
|
||||
border-color: var(--olive);
|
||||
box-shadow: 0 0 5px rgba(107, 107, 5, 0.25);
|
||||
}
|
||||
|
||||
button[type="submit"] {
|
||||
width: 100%;
|
||||
padding: var(--space-2);
|
||||
background-color: var(--olive);
|
||||
color: var(--white);
|
||||
border: none;
|
||||
border-radius: var(--radius-pill);
|
||||
font-size: 1rem;
|
||||
font-weight: 700;
|
||||
font-family: var(--font-main);
|
||||
cursor: pointer;
|
||||
transition: background-color 0.3s ease, transform 0.2s ease;
|
||||
margin-top: var(--space-2);
|
||||
}
|
||||
|
||||
button[type="submit"]:hover {
|
||||
background-color: var(--olive-dark);
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
button[type="submit"]:active {
|
||||
background-color: var(--olive-dark);
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
|
||||
/* --- Info box --- */
|
||||
|
||||
.info-box {
|
||||
background-color: var(--olive-light);
|
||||
padding: var(--space-16);
|
||||
background-color: var(--butter-light);
|
||||
border-left: 4px solid var(--olive);
|
||||
padding: var(--space-3);
|
||||
margin-bottom: var(--space-40);
|
||||
border-radius: var(--radius-md);
|
||||
font-size: 1rem;
|
||||
border-radius: 4px;
|
||||
font-size: 0.9rem;
|
||||
color: var(--black);
|
||||
line-height: 1.4;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
|
||||
/* --- Hints & errors --- */
|
||||
|
||||
.error-message--field-callout {
|
||||
.error-message {
|
||||
margin-top: 5px;
|
||||
display: none;
|
||||
margin-top: 0.65rem;
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
.form-group.has-error input {
|
||||
border-color: var(--error);
|
||||
box-shadow: var(--shadow-error);
|
||||
box-shadow: 0 0 5px rgba(212, 75, 36, 0.3);
|
||||
}
|
||||
|
||||
.form-group.has-error .error-message--field-callout {
|
||||
.form-group.has-error .error-message {
|
||||
display: block;
|
||||
}
|
||||
*/
|
||||
|
||||
|
||||
/* --- Modal / Popup --- */
|
||||
|
||||
@ -143,8 +201,14 @@ input:focus {
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.modal-footer {
|
||||
display: flex;
|
||||
gap: var(--space-2);
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
padding: var(--space-2) var(--space-32);
|
||||
padding: var(--space-2) var(--space-5);
|
||||
background-color: var(--olive);
|
||||
color: var(--white);
|
||||
border: none;
|
||||
@ -163,11 +227,12 @@ input:focus {
|
||||
|
||||
/* --- Footer --- */
|
||||
|
||||
|
||||
.footer {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr auto 1fr;
|
||||
align-items: center;
|
||||
padding: var(--space-16) var(--space-32);
|
||||
padding: var(--space-3) var(--space-5);
|
||||
border: none;
|
||||
margin-top: 40px;
|
||||
}
|
||||
@ -193,7 +258,7 @@ input:focus {
|
||||
.footer-right {
|
||||
justify-self: end;
|
||||
display: flex;
|
||||
gap: var(--space-24);
|
||||
gap: var(--space-4);
|
||||
}
|
||||
|
||||
|
||||
@ -207,14 +272,31 @@ input:focus {
|
||||
.image-section {
|
||||
min-height: 300px;
|
||||
}
|
||||
|
||||
.error-message--field-callout {
|
||||
margin-top: var(--space-1);
|
||||
max-width: 100%;
|
||||
white-space: normal;
|
||||
}
|
||||
|
||||
.error-message--field-callout::after {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* --- Snackbar --- */
|
||||
|
||||
.snackbar {
|
||||
position: fixed;
|
||||
bottom: 30px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%) translateY(100px);
|
||||
background: var(--olive);
|
||||
color: var(--white);
|
||||
padding: var(--space-3) var(--space-40);
|
||||
border-radius: var(--radius-pill);
|
||||
font-size: 1rem;
|
||||
font-weight: 600;
|
||||
font-family: var(--font-main);
|
||||
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.2);
|
||||
opacity: 0;
|
||||
transition: transform 0.4s ease, opacity 0.4s ease;
|
||||
z-index: 9999;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.snackbar--visible {
|
||||
transform: translateX(-50%) translateY(0);
|
||||
opacity: 1;
|
||||
}
|
||||
@ -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. */
|
||||
.profile-hero {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
gap: var(--space-24);
|
||||
margin-bottom: var(--space-32);
|
||||
gap: var(--space-4);
|
||||
margin-bottom: var(--space-5);
|
||||
}
|
||||
|
||||
.profile-kicker {
|
||||
@ -14,11 +19,13 @@
|
||||
color: var(--olive);
|
||||
font-size: 1rem;
|
||||
font-weight: 500;
|
||||
letter-spacing: var(--ls-lg);
|
||||
letter-spacing: var(--ls-la);
|
||||
}
|
||||
|
||||
#headline {
|
||||
margin: 0.4rem 0;
|
||||
color: var(--brown);
|
||||
font-size: clamp(2rem, 4.4vw, 2.8rem);
|
||||
}
|
||||
|
||||
.profile-subline {
|
||||
@ -34,38 +41,44 @@
|
||||
.profile-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr;
|
||||
gap: var(--space-24);
|
||||
gap: var(--space-4);
|
||||
}
|
||||
|
||||
.btn-count {
|
||||
color: var(--black);
|
||||
background: var(--tomato-light);
|
||||
height: 32px;
|
||||
width: 32px;
|
||||
margin-right: -18px;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: var(--radius-pill);
|
||||
}
|
||||
|
||||
/* Konsistentes Karten-Layout für alle Profilsektionen. */
|
||||
.profile-panel {
|
||||
background: var(--butter-light);
|
||||
border-radius: var(--radius-lg);
|
||||
padding: var(--space-32);
|
||||
padding: var(--space-5);
|
||||
}
|
||||
|
||||
.panel-head {
|
||||
display: none;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
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 {
|
||||
display: grid;
|
||||
gap: var(--space-16);
|
||||
gap: var(--space-2);
|
||||
}
|
||||
|
||||
/* Einzelne Eventkarte für "Meine Events" und "Meine Anmeldungen". */
|
||||
@ -74,7 +87,7 @@
|
||||
border: 1.5px solid var(--olive-light);
|
||||
border-radius: var(--radius-lg);
|
||||
box-shadow: var(--shadow-interaction);
|
||||
padding: var(--space-32) var(--space-40);
|
||||
padding: var(--space-5) var(--space-40);
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
gap: var(--space-40);
|
||||
@ -87,7 +100,7 @@
|
||||
}
|
||||
|
||||
.profile-event-card-clickable:hover {
|
||||
transform: translateY(-3px);
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
.profile-event-title h3{
|
||||
@ -100,26 +113,27 @@
|
||||
}*/
|
||||
|
||||
.profile-event-address-block {
|
||||
margin-top: var(--space-24);
|
||||
background-color: var(--olive-light);
|
||||
padding: var(--space-16);
|
||||
border-radius: var(--radius-md);
|
||||
color: var(--black);
|
||||
line-height: 1.4;
|
||||
margin-top: 0.55rem;
|
||||
padding: 0.6rem 0.75rem;
|
||||
border-radius: var(--radius-sm);
|
||||
border-left: 4px solid var(--tomato);
|
||||
background: rgba(232, 237, 209, 0.65);
|
||||
}
|
||||
|
||||
|
||||
.profile-event-address-label {
|
||||
font-size: 1rem;
|
||||
font-weight: 400;
|
||||
color: var(--olive-dark);
|
||||
margin: 0;
|
||||
color: var(--olive);
|
||||
font-size: 0.72rem;
|
||||
font-weight: 700;
|
||||
letter-spacing: var(--ls-la);
|
||||
}
|
||||
|
||||
.profile-event-address {
|
||||
font-size: 1.125rem;
|
||||
font-weight: 400;
|
||||
line-height: 1.4;
|
||||
margin: 0.2rem 0 0;
|
||||
font-size: 0.95rem;
|
||||
color: var(--black);
|
||||
font-weight: 600;
|
||||
line-height: 1.35;
|
||||
}
|
||||
|
||||
.profile-event-link {
|
||||
@ -138,22 +152,58 @@
|
||||
.profile-event-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-2);
|
||||
}
|
||||
|
||||
.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-la);
|
||||
}
|
||||
|
||||
.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 {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: var(--space-16);
|
||||
gap: var(--space-3);
|
||||
}
|
||||
|
||||
/*
|
||||
.form-group {
|
||||
margin-bottom: var(--space-3);
|
||||
}
|
||||
|
||||
.form-group label {
|
||||
display: block;
|
||||
margin-bottom: 0.35rem;
|
||||
font-size: 0.95rem;
|
||||
font-weight: 500;
|
||||
}*/
|
||||
}
|
||||
|
||||
.form-group input {
|
||||
width: 100%;
|
||||
@ -188,17 +238,8 @@
|
||||
|
||||
.form-group.has-error input {
|
||||
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 {
|
||||
margin: 0.75rem 0 0;
|
||||
font-size: 0.95rem;
|
||||
@ -208,8 +249,8 @@
|
||||
|
||||
.profile-cta-row {
|
||||
display: flex;
|
||||
gap: var();
|
||||
margin-top: var(--space-16);
|
||||
gap: var(--space-2);
|
||||
margin-top: var(--space-3);
|
||||
}
|
||||
|
||||
.profile-button-secondary {
|
||||
@ -221,7 +262,7 @@
|
||||
}
|
||||
|
||||
@media (max-width: 48rem) {
|
||||
.container {
|
||||
.profile-page {
|
||||
padding-top: 5.5rem;
|
||||
}
|
||||
|
||||
|
||||
@ -18,8 +18,7 @@
|
||||
|
||||
|
||||
--tomato: #D44B24;
|
||||
--tomato-dark: #B53A18;
|
||||
--tomato-light: #E5937C;
|
||||
--tomato-dark: #D44B24;
|
||||
--olive: #6B6B05;
|
||||
--olive-dark: #545404;
|
||||
--olive-light: #C8CC7A;
|
||||
@ -42,17 +41,16 @@
|
||||
|
||||
/* Spacing Scale > 1rem = 16px*/
|
||||
--space-0: 0.25rem; /* 4px */
|
||||
--space-6: 0.375rem; /* 6px */
|
||||
--space-8: 0.5rem; /* 8px */
|
||||
--space-12: 0.75rem; /* 12px */
|
||||
--space-16: 1rem; /* 16px */
|
||||
--space-01: 0.375rem; /* 6px */
|
||||
--space-1: 0.5rem; /* 8px */
|
||||
--space-2: 0.75rem; /* 12px */
|
||||
--space-3: 1rem; /* 16px */
|
||||
--space-20: 1.25rem; /* 20px */
|
||||
--space-24: 1.5rem; /* 24px */
|
||||
--space-32: 2rem; /* 32px */
|
||||
--space-4: 1.5rem; /* 24px */
|
||||
--space-5: 2rem; /* 32px */
|
||||
--space-40: 2.5rem; /* 40px */
|
||||
--space-48: 3rem; /* 48px */
|
||||
--space-64: 4rem; /* 64px */
|
||||
--space-80: 5rem; /* 80px */
|
||||
--space-7: 3rem; /* 48px */
|
||||
--space-8: 4rem; /* 64px */
|
||||
|
||||
|
||||
/* Radius Scale */
|
||||
@ -64,7 +62,7 @@
|
||||
/* Letter Spacing */
|
||||
--ls-none: 0;
|
||||
--ls-sm: 2.5%;
|
||||
--ls-lg: 5%;
|
||||
--ls-la: 5%;
|
||||
}
|
||||
|
||||
/* Base Styles */
|
||||
@ -82,73 +80,53 @@ img {
|
||||
|
||||
h1, h2, h3, h4, h5, h6 {
|
||||
font-family: 'Bagel Fat One';
|
||||
font-weight: 500;
|
||||
letter-spacing: var(--ls-sm);
|
||||
line-height: 120%;
|
||||
color: var(--brown);
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: clamp(2.25rem, 5vw, 3rem);
|
||||
font-weight: 600;
|
||||
line-height: 120%;
|
||||
letter-spacing: var(--ls-sm);
|
||||
margin-bottom: var(--space-32);
|
||||
color: var(--brown);
|
||||
margin-bottom: var(--space-5);
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: 2.5rem;
|
||||
margin-bottom: var(--space-24);
|
||||
font-weight: 600;
|
||||
line-height: 120%;
|
||||
letter-spacing: var(--ls-sm);
|
||||
color: var(--brown);
|
||||
margin-bottom: var(--space-4);
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: 2rem;
|
||||
font-weight: 600;
|
||||
line-height: 120%;
|
||||
letter-spacing: var(--ls-sm);
|
||||
color: var(--brown);
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
p {
|
||||
font-family: var(--font-main);
|
||||
font-size: 1.125rem;
|
||||
line-height: 1.45;
|
||||
line-height: 1.5;
|
||||
color: var(--black);
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.p-small {
|
||||
font-family: var(--font-main);
|
||||
font-size: 1rem;
|
||||
font-weight: 400;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.label-input-field,
|
||||
.filter-label {
|
||||
font-family: var(--font-main);
|
||||
font-size: 1rem;
|
||||
font-weight: 400;
|
||||
line-height: 1;
|
||||
margin-bottom: var(--space-8);
|
||||
}
|
||||
|
||||
|
||||
label {
|
||||
font-family: var(--font-main);
|
||||
font-size: 1.125rem;
|
||||
font-weight: 600;
|
||||
letter-spacing: var(--ls-lg);
|
||||
line-height: 1;
|
||||
margin-bottom: var(--space-16);
|
||||
}
|
||||
|
||||
.option {
|
||||
font-family: var(--font-main);
|
||||
font-size: 1rem;
|
||||
font-weight: 600;
|
||||
letter-spacing: var(--ls-sm);
|
||||
line-height: 1;
|
||||
margin-bottom: var(--space-8);
|
||||
line-height: 1.5;
|
||||
margin-bottom: 0rem;
|
||||
}
|
||||
|
||||
.link-text a{
|
||||
color: var(--blue);
|
||||
margin-top: var(--space-24);
|
||||
margin-top: var(--space-4);
|
||||
text-decoration: none;
|
||||
transition: color 0.3s ease;
|
||||
}
|
||||
@ -169,80 +147,14 @@ label {
|
||||
}
|
||||
|
||||
.error-message {
|
||||
font-family: var(--font-main);
|
||||
font-size: 1rem;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.error-message--inline {
|
||||
color: var(--error-text);
|
||||
}
|
||||
|
||||
.error-message--field-callout {
|
||||
display: none;
|
||||
width: fit-content;
|
||||
max-width: min(100%, 20rem);
|
||||
padding: var(--space-6) var(--space-16);
|
||||
/*margin-bottom: -32px;*/
|
||||
border-radius: var(--radius-md);
|
||||
|
||||
background: var(--error);
|
||||
color: var(--butter-light);
|
||||
text-align: center;
|
||||
overflow-wrap: anywhere;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.error-message--field-callout::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: -0.35rem;
|
||||
right: 1.6rem;
|
||||
width: 0.8rem;
|
||||
height: 0.8rem;
|
||||
background: inherit;
|
||||
border-top-left-radius: 0.2rem;
|
||||
transform: rotate(45deg);
|
||||
}
|
||||
|
||||
.error-message--callout {
|
||||
display: none;
|
||||
width: max-content;
|
||||
max-width: 15rem;
|
||||
padding: var(--space-6) var(--space-16);
|
||||
border-radius: var(--radius-md);
|
||||
background: var(--error);
|
||||
color: var(--butter-light);
|
||||
text-align: center;
|
||||
overflow-wrap: anywhere;
|
||||
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.error-message--callout:not(:empty) {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.error-message--callout::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
right: 2.1rem;
|
||||
bottom: -0.55rem;
|
||||
width: 1.1rem;
|
||||
height: 1.1rem;
|
||||
background: inherit;
|
||||
border-bottom-right-radius: 0.25rem;
|
||||
transform: rotate(45deg);
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
/* Margins */
|
||||
|
||||
.margin-bottom-16 {
|
||||
margin-bottom: var(--space-16);
|
||||
}
|
||||
|
||||
.margin-bottom-24 {
|
||||
margin-bottom: var(--space-24);
|
||||
margin-bottom: var(--space-4);
|
||||
}
|
||||
|
||||
.margin-bottom-40 {
|
||||
@ -255,7 +167,7 @@ label {
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
gap: var(--space-16);
|
||||
gap: var(--space-3);
|
||||
padding-top: 36px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
@ -276,37 +188,28 @@ label {
|
||||
}
|
||||
|
||||
/* Layout */
|
||||
.main-content {
|
||||
margin-top: var(--space-8);
|
||||
|
||||
.layout-wide {
|
||||
width: 75%;
|
||||
max-width: 1200px;
|
||||
margin: 82px auto 0 auto;
|
||||
}
|
||||
|
||||
.layout-narrow {
|
||||
width: 55%;
|
||||
max-width: 900px;
|
||||
margin: 82px auto 0 auto;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
.container {
|
||||
width: 80%;
|
||||
width: 90%;
|
||||
max-width: 75rem;
|
||||
margin: 0 auto;
|
||||
}
|
||||
*/
|
||||
|
||||
/* Media Queries (Responsive) */
|
||||
@media (max-width: 48rem) {
|
||||
.container {
|
||||
width: 95%;
|
||||
}
|
||||
/*
|
||||
Content pages with sticky nav require top padding to avoid overlap.
|
||||
Used on event_overview, event_detail, and similar pages.
|
||||
*/
|
||||
.container.page-content-safe {
|
||||
padding-top: 6.5rem;
|
||||
}
|
||||
|
||||
.nav {
|
||||
flex-direction: column;
|
||||
}
|
||||
/* Detail pages with back button need less top padding. */
|
||||
.container.page-content-safe.detail-page {
|
||||
padding-top: 3.5rem;
|
||||
}
|
||||
|
||||
.icon {
|
||||
@ -337,7 +240,7 @@ label {
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
min-height: 3rem;
|
||||
padding: 0.1875rem 0.75rem 0.1875rem var(--space-32);
|
||||
padding: 0.1875rem 0.75rem 0.1875rem var(--space-5);
|
||||
max-width: none;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
@ -383,7 +286,7 @@ label {
|
||||
.nav-tab-links {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-32);
|
||||
gap: var(--space-5);
|
||||
}
|
||||
|
||||
|
||||
@ -397,7 +300,7 @@ label {
|
||||
font-family: var(--font-main);
|
||||
font-size: 1rem;
|
||||
font-weight: 600;
|
||||
letter-spacing: var(--ls-lg);
|
||||
letter-spacing: var(--ls-la);
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
@ -422,7 +325,7 @@ label {
|
||||
/* Buttons */
|
||||
.button-primary {
|
||||
display: inline-block;
|
||||
padding: var(--space-6) var(--space-24);
|
||||
padding: var(--space-01) var(--space-4);
|
||||
background-color: var(--olive);
|
||||
border: 1.5px solid var(--olive);
|
||||
border-radius: var(--radius-lg);
|
||||
@ -445,7 +348,7 @@ label {
|
||||
background: transparent;
|
||||
color: var(--olive-dark);
|
||||
opacity: 0.6;
|
||||
padding: var(--space-6) 0 var(--space-6) var(--space-24);
|
||||
padding: var(--space-01) 0 var(--space-01) var(--space-4);
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
@ -465,20 +368,12 @@ label {
|
||||
transition: background-color 0.2s ease, border-color 0.2s ease;
|
||||
}
|
||||
|
||||
.button-primary-abmelden:hover:not(:disabled),
|
||||
.button-primary-abmelden:focus-visible:not(:disabled) {
|
||||
.button-primary-abmelden:hover,
|
||||
.button-primary-abmelden:focus-visible {
|
||||
background: var(--blue-dark);
|
||||
border-color: var(--blue-dark);
|
||||
}
|
||||
|
||||
.button-primary-abmelden:disabled {
|
||||
background-color: var(--blue);
|
||||
border-color: var(--blue);
|
||||
color: var(--butter-light);
|
||||
opacity: 0.6;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.button-primary-eigener-event{
|
||||
display: inline-block;
|
||||
padding: 0.375rem 1.5rem;
|
||||
@ -527,6 +422,29 @@ label {
|
||||
border-color: var(--olive-dark);
|
||||
}
|
||||
|
||||
/* Butter-colored back button for detail pages. */
|
||||
.btn-back-to-overview {
|
||||
display: inline-block;
|
||||
padding: 0.5rem 1.5rem;
|
||||
background-color: var(--butter);
|
||||
border: 1.5px solid var(--olive-light);
|
||||
border-radius: var(--radius-lg);
|
||||
font-family: var(--font-main);
|
||||
font-weight: 400;
|
||||
font-size: 1.125rem;
|
||||
color: var(--olive);
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
transition: background-color 0.2s ease, border-color 0.2s ease;
|
||||
margin-bottom: var(--space-4);
|
||||
}
|
||||
|
||||
.btn-back-to-overview:hover,
|
||||
.btn-back-to-overview:focus-visible {
|
||||
background-color: var(--butter-light);
|
||||
border-color: var(--olive);
|
||||
}
|
||||
|
||||
|
||||
.button--outline {
|
||||
background-color: transparent;
|
||||
@ -536,8 +454,7 @@ label {
|
||||
|
||||
.button--outline:hover {
|
||||
background-color: var(--olive-light);
|
||||
color: var(--olive-dark);
|
||||
border: 1.5px solid var(--olive-dark);
|
||||
color: var(--black);
|
||||
}
|
||||
|
||||
.counter-button {
|
||||
@ -555,8 +472,7 @@ label {
|
||||
transition: background-color 0.2s ease, border-color 0.2s ease;
|
||||
}
|
||||
|
||||
.counter-button:hover,
|
||||
.counter-button:focus-visible {
|
||||
.counter-button:hover, .counter-button:focus-visible {
|
||||
background-color: var(--olive-dark);
|
||||
border-color: var(--olive-dark);
|
||||
}
|
||||
@ -571,56 +487,33 @@ label {
|
||||
font-weight: 400;
|
||||
font-size: 1.125rem;
|
||||
line-height: 1;
|
||||
padding: var(--space-8) var(--space-20);
|
||||
padding: var(--space-1) var(--space-20);
|
||||
cursor: pointer;
|
||||
transition: background-color 0.2s ease, color 0.2s ease;
|
||||
}
|
||||
|
||||
.category-item:hover,
|
||||
.category-item:focus-visible,
|
||||
.category-item.is-active,
|
||||
.category-item.active {
|
||||
.category-item:hover, .category-item:focus-visible {
|
||||
background: var(--tomato);
|
||||
color: var(--butter-light);
|
||||
}
|
||||
|
||||
.category-item.disabled {
|
||||
opacity: 0.6;
|
||||
pointer-events: none;
|
||||
cursor: not-allowed;
|
||||
.category-item.is-active, .category-item.active {
|
||||
background: var(--tomato);
|
||||
color: var(--butter-light);
|
||||
}
|
||||
|
||||
.category-item-profile {
|
||||
font-size: 1.25rem;
|
||||
font-weight: 400;
|
||||
line-height: 1;
|
||||
padding: var(--space-1) var(--space-4);
|
||||
|
||||
}
|
||||
|
||||
.category-items {
|
||||
display: inline-flex;
|
||||
flex-wrap: wrap;
|
||||
gap: var(--space-8);
|
||||
}
|
||||
|
||||
.category-item-profile {
|
||||
position: relative;
|
||||
font-size: 1.25rem;
|
||||
font-weight: 400;
|
||||
line-height: 1;
|
||||
height: 44px;
|
||||
padding: 0 var(--space-24);
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: var(--space-16);
|
||||
}
|
||||
|
||||
.filter-delete {
|
||||
border: 1.5px solid var(--olive);
|
||||
color: var(--olive);
|
||||
background: transparent;
|
||||
|
||||
}
|
||||
|
||||
.filter-delete:hover {
|
||||
border: 1.5px solid var(--olive-dark);
|
||||
color: var(--olive-dark);
|
||||
background: var(--olive-light);
|
||||
|
||||
gap: var(--space-1);
|
||||
}
|
||||
|
||||
.button-small {
|
||||
@ -633,7 +526,7 @@ label {
|
||||
font-weight: 400;
|
||||
line-height: 1;
|
||||
border-radius: var(--radius-pill);
|
||||
padding: 10px var(--space-16);
|
||||
padding: 10px var(--space-3);
|
||||
text-decoration: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
@ -667,7 +560,6 @@ label {
|
||||
}
|
||||
|
||||
.profile-pill {
|
||||
position: relative;
|
||||
width: 2.375rem;
|
||||
height: 2.375rem;
|
||||
border-radius: 1.1875rem;
|
||||
@ -683,18 +575,6 @@ label {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.notification-dot {
|
||||
position: absolute;
|
||||
top: -2px;
|
||||
right: -2px;
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
background-color: var(--error);
|
||||
border-radius: 50%;
|
||||
border: 2px solid var(--butter-light);
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
/* Utilities */
|
||||
.text-center {
|
||||
text-align: center;
|
||||
@ -719,7 +599,7 @@ label {
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 1em; /* keeps it aligned with text size */
|
||||
margin-top: var(--space-48);
|
||||
margin-top: var(--space-7);
|
||||
}
|
||||
|
||||
.instagram-invite__link {
|
||||
@ -734,6 +614,7 @@ label {
|
||||
width: 32px;
|
||||
object-fit: contain;
|
||||
border-radius: 8px;
|
||||
filter: brightness(0) saturate(100%) invert(27%) sepia(81%) saturate(749%) hue-rotate(24deg) brightness(90%) contrast(90%);
|
||||
}
|
||||
|
||||
.footer-invite_logo {
|
||||
@ -749,6 +630,32 @@ label {
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
/* Info button for event overview page */
|
||||
.btn-info {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 2.5rem;
|
||||
height: 2.5rem;
|
||||
border: 1.5px solid var(--olive-light);
|
||||
border-radius: 999px;
|
||||
background-color: var(--butter);
|
||||
color: var(--olive);
|
||||
font-family: var(--font-main);
|
||||
font-size: 1.5rem;
|
||||
font-weight: 600;
|
||||
line-height: 1;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.2s ease, border-color 0.2s ease;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.btn-info:hover,
|
||||
.btn-info:focus-visible {
|
||||
background-color: var(--butter-light);
|
||||
border-color: var(--olive);
|
||||
}
|
||||
|
||||
/* Modal / Popup */
|
||||
.modal {
|
||||
display: none;
|
||||
@ -775,34 +682,13 @@ label {
|
||||
|
||||
.modal-content {
|
||||
background-color: var(--white);
|
||||
padding: var(--space-40);
|
||||
padding: var(--space-20) var(--space-20) var(--space-40) var(--space-20);
|
||||
border-radius: var(--radius-lg);
|
||||
box-shadow: var(--shadow-interaction);
|
||||
max-width: 460px;
|
||||
max-width: 500px;
|
||||
width: 90%;
|
||||
text-align: center;
|
||||
animation: modalSlideIn 0.5s ease;
|
||||
}
|
||||
|
||||
.btn-info {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
flex: 0 0 48px;
|
||||
border: 1.5px solid var(--olive-light);
|
||||
border-radius: var(--radius-pill);
|
||||
color: var(--black);
|
||||
background: var(--butter-light);
|
||||
font-family: "Bagel Fat One";
|
||||
font-size: 1.75rem;
|
||||
font-weight: 500;
|
||||
line-height: 1;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.2s ease;
|
||||
}
|
||||
|
||||
.btn-info:hover,
|
||||
.btn-info:focus-visible {
|
||||
background: var(--olive-light);
|
||||
animation: modalSlideIn 0.3s ease;
|
||||
}
|
||||
|
||||
@keyframes modalSlideIn {
|
||||
@ -811,43 +697,33 @@ label {
|
||||
}
|
||||
|
||||
.modal-header {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
justify-content: space-between;
|
||||
gap: var(--space-24);
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.modal-header h2 {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
text-align: left;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.modal-close {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
flex: 0 0 32px;
|
||||
margin-left: auto;
|
||||
border: none;
|
||||
background: transparent;
|
||||
color: transparent;
|
||||
font-size: 2rem;
|
||||
line-height: 1;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 0;
|
||||
transition: transform 0.2s ease;
|
||||
}
|
||||
|
||||
.modal-close:hover,
|
||||
.modal-close:focus-visible {
|
||||
transform: translateY(-1px);
|
||||
padding: var(--space-20)var(--space-20) 0 var(--space-20);
|
||||
}
|
||||
|
||||
.close-btn {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
font-size: 28px;
|
||||
color: var(--black);
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.modal-close {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
font-size: 28px;
|
||||
color: var(--black);
|
||||
background: none;
|
||||
@ -861,36 +737,31 @@ label {
|
||||
}
|
||||
|
||||
.modal-body {
|
||||
font-family: var(--font-main);
|
||||
font-size: 1.125rem;
|
||||
padding: var(--space-24) 0 var(--space-40) 0;
|
||||
text-align: left;
|
||||
line-height: 1.45;
|
||||
padding: var(--space-20) var(--space-20) var(--space-4) var(--space-20);
|
||||
}
|
||||
|
||||
.modal-footer {
|
||||
display: flex;
|
||||
gap: var(--space-12);
|
||||
gap: var(--space-2);
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
/* Snackbar */
|
||||
.snackbar,
|
||||
.snackbar--danger {
|
||||
.snackbar {
|
||||
position: fixed;
|
||||
bottom: 40px;
|
||||
bottom: 30px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%) translateY(100px);
|
||||
color: var(--butter-light);
|
||||
background: var(--tomato);
|
||||
padding: var(--space-16) var(--space-40);
|
||||
background: var(--olive);
|
||||
color: var(--white);
|
||||
padding: var(--space-3) var(--space-40);
|
||||
border-radius: var(--radius-pill);
|
||||
font-size: 1.125rem;
|
||||
font-weight: 400;
|
||||
font-size: 1rem;
|
||||
font-weight: 600;
|
||||
font-family: var(--font-main);
|
||||
box-shadow: var(--shadow-interaction);
|
||||
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.2);
|
||||
opacity: 0;
|
||||
transition: transform 0.5s ease, opacity 0.4s ease;
|
||||
transition: transform 0.4s ease, opacity 0.4s ease;
|
||||
z-index: 9999;
|
||||
pointer-events: none;
|
||||
}
|
||||
@ -900,6 +771,10 @@ label {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.snackbar--danger {
|
||||
background: var(--tomato);
|
||||
}
|
||||
|
||||
/* Lightbox */
|
||||
.lightbox {
|
||||
position: fixed;
|
||||
@ -940,59 +815,16 @@ label {
|
||||
|
||||
.lightbox__close {
|
||||
position: absolute;
|
||||
top: -40px;
|
||||
right: -40px;
|
||||
top: -42px;
|
||||
right: 0;
|
||||
border: 0;
|
||||
background: transparent;
|
||||
color: var(--butter-light);
|
||||
color: var(--white);
|
||||
font-size: 40px;
|
||||
line-height: 1;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* Formulare */
|
||||
|
||||
input[type="text"],
|
||||
input[type="email"],
|
||||
input[type="password"] {
|
||||
font-size: 1.125rem;
|
||||
font-family: var(--font-main);
|
||||
width: 100%;
|
||||
padding: var(--space-12) var(--space-16);
|
||||
border: 1.5px solid var(--olive-light);
|
||||
border-radius: var(--radius-md);
|
||||
background: var(--butter-light);
|
||||
transition: border-color 0.3s ease;
|
||||
}
|
||||
|
||||
input:focus {
|
||||
outline: none;
|
||||
border: 2px solid var(--olive);
|
||||
}
|
||||
|
||||
/* ---------------------------------------------------------
|
||||
Overview Empty State
|
||||
--------------------------------------------------------- */
|
||||
.empty-state {
|
||||
text-align: center;
|
||||
padding: var(--space-32) var(--space-40);
|
||||
border-radius: var(--radius-lg);
|
||||
background: var(--butter-light);
|
||||
}
|
||||
|
||||
.empty-state-kicker {
|
||||
color: var(--olive);
|
||||
font-size: 1rem;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.empty-state-link {
|
||||
display: inline-block;
|
||||
margin-top: 20px;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
|
||||
/* Footer */
|
||||
|
||||
.footer {
|
||||
@ -1000,12 +832,12 @@ input:focus {
|
||||
grid-template-columns: 1fr auto 1fr;
|
||||
align-items: center;
|
||||
margin-top: 120px;
|
||||
padding: var(--space-16) var(--space-48);
|
||||
padding: var(--space-3) var(--space-7);
|
||||
}
|
||||
|
||||
.footer-links {
|
||||
display: flex;
|
||||
gap: var(--space-24);
|
||||
gap: var(--space-4);
|
||||
}
|
||||
|
||||
/* Left aligned */
|
||||
@ -1022,6 +854,16 @@ input:focus {
|
||||
.footer-right {
|
||||
justify-self: end;
|
||||
display: flex;
|
||||
gap: var(--space-24);
|
||||
gap: var(--space-4);
|
||||
}
|
||||
|
||||
/* Media Queries (Responsive) */
|
||||
@media (max-width: 48rem) {
|
||||
.container {
|
||||
width: 95%;
|
||||
}
|
||||
|
||||
.nav {
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
151
data/events.json
@ -4,7 +4,7 @@
|
||||
"title": "Italienische Tavolata",
|
||||
"location": "Luzern",
|
||||
"address": "Pilatusstrasse 18, 6003 Luzern",
|
||||
"date": "17. Mai. 2026",
|
||||
"date": "22. APR. 2026",
|
||||
"time": "15:30 UHR",
|
||||
"category": "Dinner",
|
||||
"diet": "Vegetarisch",
|
||||
@ -15,8 +15,7 @@
|
||||
},
|
||||
"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.",
|
||||
"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.",
|
||||
"Wenn es das Wetter erlaubt sind wir draussen."
|
||||
"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."
|
||||
],
|
||||
"menu": [
|
||||
"Bruschetta-Variationen und Antipasti",
|
||||
@ -46,10 +45,10 @@
|
||||
"title": "Noche Peruana",
|
||||
"location": "Chur",
|
||||
"address": "Obere Gasse 41, 7000 Chur",
|
||||
"date": "8. Mai 2026",
|
||||
"date": "12. April 2026",
|
||||
"time": "19:00 UHR",
|
||||
"category": "Dinner",
|
||||
"diet": "Fleisch, Fisch",
|
||||
"diet": "Omnivore",
|
||||
"spots": 4,
|
||||
"host": {
|
||||
"name": "Camila",
|
||||
@ -88,7 +87,7 @@
|
||||
"title": "Japanese Delight",
|
||||
"location": "Zürich",
|
||||
"address": "Limmatquai 92, 8001 Zürich",
|
||||
"date": "12. Mai 2026",
|
||||
"date": "02. MAI. 2026",
|
||||
"time": "12:30 UHR",
|
||||
"category": "Lunch",
|
||||
"diet": "Fisch",
|
||||
@ -122,145 +121,5 @@
|
||||
"https://i.pinimg.com/1200x/b1/fb/3a/b1fb3a7809f4046843904ac8800daacc.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"
|
||||
]
|
||||
}
|
||||
]
|
||||
@ -19,7 +19,7 @@
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main class="layout-wide">
|
||||
<main class="main-content" style="padding: 40px 20px; max-width: 800px; margin: 0 auto;">
|
||||
<h1>Datenschutzerklärung</h1>
|
||||
|
||||
<h3>1. Verantwortliche Stelle</h3>
|
||||
|
||||
@ -9,12 +9,9 @@
|
||||
<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">
|
||||
|
||||
<!-- Font Awesome Icons -->
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
|
||||
|
||||
</head>
|
||||
|
||||
<body>
|
||||
@ -32,7 +29,7 @@
|
||||
</header>
|
||||
|
||||
|
||||
<main class="event-create-page layout-narrow">
|
||||
<main class="event-create-page">
|
||||
<section class="event-flow-header" aria-label="Event erstellen Aktionen">
|
||||
</section>
|
||||
|
||||
@ -42,26 +39,26 @@
|
||||
data-step="0"
|
||||
aria-labelledby="intro-title"
|
||||
>
|
||||
<div class="step-layout hero startseite">
|
||||
<div>
|
||||
<p class="badge margin-bottom-40">Event erstellen</p>
|
||||
<div class="step-layout step-layout--intro">
|
||||
<div class="step-copy">
|
||||
<p class="badge">Event erstellen</p>
|
||||
<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
|
||||
oder etwas ganz Eigenes wir helfen dir dabei, dein Event in sieben Schritten aufzubauen.
|
||||
</p>
|
||||
<button type="button" class="button-primary" data-start-flow>
|
||||
<button type="button" class="button-primary button--intro" data-start-flow>
|
||||
Los geht’s!
|
||||
</button>
|
||||
</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
|
||||
class="intro-image"
|
||||
src="assets/eventcreate_foodtable-new.jpg"
|
||||
src="assets/eventcreate_foodtable.jpg"
|
||||
alt="Ein gedeckter Tisch mit gemeinsamem Essen"
|
||||
/>
|
||||
</div>
|
||||
</aside>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@ -70,7 +67,7 @@
|
||||
<div class="step-copy">
|
||||
<p class="badge">Schritt 1</p>
|
||||
<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,
|
||||
ein Dinner mit Wow-Effekt oder einfach ein entspanntes Mittagessen mit gutem Essen?
|
||||
</p>
|
||||
@ -78,37 +75,33 @@
|
||||
|
||||
<div class="step-fields">
|
||||
<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">
|
||||
<label class="option-card option option-card--with-icon">
|
||||
<div class="option-grid option-grid--4">
|
||||
<label class="option-card">
|
||||
<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>
|
||||
</label>
|
||||
|
||||
<label class="option-card option option-card--with-icon">
|
||||
<label class="option-card">
|
||||
<input type="radio" name="eventType" value="Lunch" />
|
||||
<i class="fa-solid fa-pizza-slice option-card__icon" aria-hidden="true"></i>
|
||||
<span>Lunch</span>
|
||||
</label>
|
||||
|
||||
<label class="option-card option option-card--with-icon">
|
||||
<label class="option-card">
|
||||
<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>
|
||||
</label>
|
||||
|
||||
<label class="option-card option option-card--with-icon">
|
||||
<label class="option-card">
|
||||
<input type="radio" name="eventType" value="Dinner" />
|
||||
<i class="fa-solid fa-martini-glass option-card__icon" aria-hidden="true"></i>
|
||||
<span>Dinner</span>
|
||||
</label>
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
<fieldset class="form-field">
|
||||
<label>Maximale Personenanzahl</label>
|
||||
<legend>Maximale Personenanzahl</legend>
|
||||
|
||||
<div class="counter" data-counter>
|
||||
<button
|
||||
@ -119,18 +112,15 @@
|
||||
>
|
||||
−
|
||||
</button>
|
||||
<div class="counter-value-group">
|
||||
<i class="fa-solid fa-user-group guest-count-icon" aria-hidden="true"></i>
|
||||
<input
|
||||
type="number"
|
||||
id="maxGuests"
|
||||
name="maxGuests"
|
||||
min="1"
|
||||
step="1"
|
||||
value="0"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<input
|
||||
type="number"
|
||||
id="maxGuests"
|
||||
name="maxGuests"
|
||||
min="1"
|
||||
step="1"
|
||||
value="4"
|
||||
required
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
class="counter-button"
|
||||
@ -150,37 +140,33 @@
|
||||
<div class="step-copy">
|
||||
<p class="badge">Schritt 2</p>
|
||||
<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.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="step-fields">
|
||||
<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">
|
||||
<label class="option-card option option-card--with-icon">
|
||||
<div class="option-grid option-grid--4">
|
||||
<label class="option-card">
|
||||
<input type="checkbox" name="dietType" value="Fleisch" />
|
||||
<i class="fa-solid fa-drumstick-bite option-card__icon" aria-hidden="true"></i>
|
||||
<span>Fleisch</span>
|
||||
</label>
|
||||
|
||||
<label class="option-card option option-card--with-icon">
|
||||
<label class="option-card">
|
||||
<input type="checkbox" name="dietType" value="Fisch" />
|
||||
<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">
|
||||
<label class="option-card">
|
||||
<input type="checkbox" name="dietType" value="Vegetarisch" />
|
||||
<i class="fa-solid fa-seedling option-card__icon" aria-hidden="true"></i>
|
||||
<span>Vegetarisch</span>
|
||||
</label>
|
||||
|
||||
<label class="option-card option option-card--with-icon">
|
||||
<label class="option-card">
|
||||
<input type="checkbox" name="dietType" value="Vegan" />
|
||||
<i class="fa-solid fa-leaf option-card__icon" aria-hidden="true"></i>
|
||||
<span>Vegan</span>
|
||||
</label>
|
||||
</div>
|
||||
@ -199,37 +185,36 @@
|
||||
<div class="step-copy">
|
||||
<p class="badge">Schritt 3</p>
|
||||
<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.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="step-fields">
|
||||
<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>
|
||||
|
||||
<div class="option-grid option-grid--3 option-grid--tomato-choices">
|
||||
<label class="option-card option">
|
||||
<div class="option-grid option-grid--3">
|
||||
<label class="option-card option-card--checkbox">
|
||||
<input type="checkbox" name="allergies" value="glutenfrei" />
|
||||
<span>Glutenfrei</span>
|
||||
<span>glutenfrei</span>
|
||||
</label>
|
||||
|
||||
<label class="option-card option">
|
||||
<label class="option-card option-card--checkbox">
|
||||
<input type="checkbox" name="allergies" value="laktosefrei" />
|
||||
<span>Laktosefrei</span>
|
||||
<span>laktosefrei</span>
|
||||
</label>
|
||||
|
||||
<label class="option-card option">
|
||||
<label class="option-card option-card--checkbox">
|
||||
<input type="checkbox" name="allergies" value="ohne Nüsse" />
|
||||
<span>Ohne Nüsse</span>
|
||||
<span>ohne Nüsse</span>
|
||||
</label>
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
<div class="form-field">
|
||||
<label for="allergiesOther">Weitere Unverträglichkeiten oder Hinweise</label>
|
||||
<p class="field-hint">Optional – nur auswählen, wenn es für dein Event relevant ist.</p>
|
||||
<label for="allergiesOther">Weitere Unverträglichkeiten oder Hinweise (optional)</label>
|
||||
<textarea id="allergiesOther" name="allergiesOther" rows="3"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
@ -241,7 +226,7 @@
|
||||
<div class="step-copy">
|
||||
<p class="badge">Schritt 4</p>
|
||||
<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.
|
||||
</p>
|
||||
</div>
|
||||
@ -267,7 +252,7 @@
|
||||
<div class="step-copy">
|
||||
<p class="badge">Schritt 5</p>
|
||||
<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.
|
||||
</p>
|
||||
</div>
|
||||
@ -291,7 +276,7 @@
|
||||
<div class="step-copy">
|
||||
<p class="badge">Schritt 6</p>
|
||||
<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.
|
||||
Ein klarer Titel (z.B. "Italienische Tavolata") und ein guter Beschreibungstext (Ablauf etc.) machen den Unterschied.
|
||||
</p>
|
||||
@ -309,14 +294,10 @@
|
||||
</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">
|
||||
<label>Fotos hinzufügen <span class="field-hint-inline">(optional)</span></label>
|
||||
<div class="gallery-upload">
|
||||
<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>
|
||||
<button type="button" class="button-primary" id="galleryAddBtn" aria-label="Foto hinzufügen">Foto hinzufügen</button>
|
||||
<input type="file" id="galleryFileInput" accept="image/*" multiple hidden />
|
||||
</div>
|
||||
</div>
|
||||
@ -328,8 +309,8 @@
|
||||
<div class="step-layout">
|
||||
<div class="step-copy">
|
||||
<p class="badge">Schritt 7</p>
|
||||
<h2 id="step7-title">Dein Event auf einen Blick</h2>
|
||||
<p class="step-text margin-bottom-40">
|
||||
<h2 id="step7-title">Dein Event auf einen Blick.</h2>
|
||||
<p class="step-text">
|
||||
Schau dir alle Details nochmal in Ruhe an. Wenn alles passt,
|
||||
kannst du dein Event jetzt veröffentlichen und Gäste einladen.
|
||||
</p>
|
||||
@ -391,15 +372,6 @@
|
||||
<dt>Event-Abend</dt>
|
||||
<dd data-review="eventDescription">–</dd>
|
||||
</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>
|
||||
</div>
|
||||
</div>
|
||||
@ -407,6 +379,9 @@
|
||||
|
||||
|
||||
<div class="flow-footer" id="flowFooter" hidden>
|
||||
|
||||
<p id="errorMessage" class="error-message" role="alert" aria-live="assertive"></p>
|
||||
|
||||
<div class="flow-actions">
|
||||
<button type="button" id="backButton" class="button-secondary">
|
||||
Zurück
|
||||
@ -421,17 +396,14 @@
|
||||
</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 class="flow-actions-right">
|
||||
<p id="errorMessage" class="error-message" role="alert" aria-live="assertive"></p>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -442,29 +414,20 @@
|
||||
aria-live="polite"
|
||||
hidden
|
||||
>
|
||||
<div class="step-layout hero startseite">
|
||||
<div class="step-layout">
|
||||
<div class="step-copy">
|
||||
<div class="submission-success-title-row">
|
||||
<h2 id="success-title">Dein Event ist ready </h2>
|
||||
<span class="submission-success-icon" aria-hidden="true">
|
||||
<!-- <i class="fa-solid fa-champagne-glasses"></i>-->
|
||||
</span>
|
||||
</div>
|
||||
<p class="step-text margin-bottom-40">
|
||||
<p class="badge">Event erstellt</p>
|
||||
<h2 id="success-title">Dein Event ist ready.</h2>
|
||||
<p class="step-text">
|
||||
Sieht gut aus: Deine Idee ist jetzt live und bereit für Gäste.
|
||||
Im Profil kannst du dein Event anschauen, verwalten oder direkt das nächste planen.
|
||||
</p>
|
||||
<div class="submission-success-actions">
|
||||
<a class="button-primary button--intro" href="my_profil.html">Weiter zu deinem Profil</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="hero__right" aria-label="Stimmungsbild zur Event-Erstellung">
|
||||
<img
|
||||
class="intro-image"
|
||||
src="assets/eventcreate_foodtable with friends.jpg"
|
||||
alt="Gemeinsames Essen an einem gedeckten Tisch"
|
||||
/>
|
||||
<div class="review-card review-card--success">
|
||||
<div class="submission-success-actions">
|
||||
<a class="button-primary" href="my_profil.html">Weiter zu deinem Profil</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Invité | Event-Detail</title>
|
||||
<title>Invité | Event-Detail</title>
|
||||
|
||||
<!-- Stylesheet für diese Seite-->
|
||||
<link rel="stylesheet" href="css/event_overview.css">
|
||||
@ -28,7 +28,7 @@
|
||||
</header>
|
||||
|
||||
<!-- Main content: detail page gets fully injected by JavaScript -->
|
||||
<main class="container layout-wide">
|
||||
<main class="container page-content-safe detail-page">
|
||||
<!-- Render target: loading, error state or full detail layout -->
|
||||
<div id="detail-view">
|
||||
<p>Lädt Event-Details...</p>
|
||||
@ -38,39 +38,6 @@
|
||||
<!-- Page logic: fetch by URL id, compose detail UI, handle gallery lightbox -->
|
||||
<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>
|
||||
|
||||
|
||||
@ -11,6 +11,7 @@
|
||||
|
||||
<!-- Globales Stylesheet -->
|
||||
<link rel="stylesheet" href="css/stylesheet_global.css">
|
||||
|
||||
</head>
|
||||
|
||||
<body>
|
||||
@ -27,18 +28,17 @@
|
||||
</header>
|
||||
|
||||
<!-- Main content: page headline, filter controls and dynamic event list -->
|
||||
<main class="container layout-wide">
|
||||
<!-- Page headline -->
|
||||
<p class="badge margin-bottom-40">Event finden</p>
|
||||
<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>
|
||||
<main class="container page-content-safe">
|
||||
<!-- Page headline with info button -->
|
||||
<div style="display: flex; align-items: center; justify-content: space-between; margin-bottom: 2rem;">
|
||||
<h1 style="margin-bottom: 0;">Events</h1>
|
||||
<button type="button" class="btn-info" id="info-button" aria-label="Information über kostenlose Events">?</button>
|
||||
</div>
|
||||
|
||||
<!-- Filter section: category chips + location/date filters -->
|
||||
<section class="filter-section margin-bottom-24">
|
||||
<p class="filter-label">Art des Essens / Eventtyp</p>
|
||||
<div class="filter-row margin-bottom-24">
|
||||
<section class="filter-section">
|
||||
<p class="filter-label">Was darf es sein?</p>
|
||||
<div class="filter-row">
|
||||
<!-- Primary category filter buttons -->
|
||||
<div class="category-group">
|
||||
<button class="category-item active" type="button" data-cat="ALLE">Alle</button>
|
||||
@ -50,45 +50,43 @@
|
||||
|
||||
<!-- Secondary filters populated/handled by JavaScript -->
|
||||
<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>
|
||||
<select id="location-filter">
|
||||
<option value="ALLE_ORTE">Alle Orte</option>
|
||||
</select>
|
||||
</div>
|
||||
</label>
|
||||
|
||||
<div class="meta-filter" for="date-filter">
|
||||
<label class="meta-filter" for="date-filter">
|
||||
<span>Datum</span>
|
||||
<div class="date-input-wrapper">
|
||||
<input id="date-filter" type="date">
|
||||
</div>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<details class="filter-box">
|
||||
<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 class="filter-row">
|
||||
<!-- Diet filter buttons -->
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</details>
|
||||
<div class="filter-row">
|
||||
<!-- Allergen filter buttons -->
|
||||
<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>
|
||||
|
||||
|
||||
</section>
|
||||
|
||||
<!-- Render target: event cards or empty state -->
|
||||
<section id="event-grid" class="event-list"></section>
|
||||
@ -110,38 +108,6 @@
|
||||
</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>
|
||||
|
||||
|
||||
@ -19,7 +19,7 @@
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main class="layout-wide">
|
||||
<main class="main-content" style="padding: 40px 20px; max-width: 800px; margin: 0 auto;">
|
||||
<h1>Impressum</h1>
|
||||
|
||||
<h3>Angaben gemäss § 5 TMG</h3>
|
||||
|
||||
143
index.html
@ -29,19 +29,19 @@
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main class="container layout-wide">
|
||||
<main class="main-content">
|
||||
<!-- Hero: uses .hero, .btn, .image-card, and .hero-image for a polished first impression -->
|
||||
<section class="hero">
|
||||
<div class="hero__left">
|
||||
<span class="badge margin-bottom-40">einfach. lecker. gemeinsam.</span>
|
||||
<h1>Teile deine Leidenschaft, geniesse gemeinsam.</h1>
|
||||
<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>
|
||||
<p>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="button-primary" href="signup.html">Registrieren</a>
|
||||
</div>
|
||||
|
||||
<div class="hero__right">
|
||||
<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/index_ingredients.jpg" alt="Startpage Ingredients" />
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
@ -82,8 +82,8 @@
|
||||
|
||||
<!-- 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">
|
||||
</div><h2>Einblick in Cooking-Erlebnisse</h2>
|
||||
<p class="margin-bottom-16">#gemeinsam_invité auf Instagram</p>
|
||||
<h2>Einblick in Cooking-Erlebnisse</h2>
|
||||
<p>#gemeinsam_invité auf Instagram</p>
|
||||
</div>
|
||||
<div class="gallery__carousel">
|
||||
<button type="button" class="gallery__arrow gallery__arrow--prev" aria-label="Vorheriges Bild">
|
||||
@ -91,129 +91,44 @@
|
||||
</button>
|
||||
|
||||
<div class="gallery__track" aria-live="polite">
|
||||
|
||||
<article class="gallery__item" role="group" aria-roledescription="Folie" aria-label="Bild 1 von 12">
|
||||
<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>
|
||||
<img src="assets/index_Red checkered social eating.jpg" alt="Red checkered social eating">
|
||||
</article>
|
||||
|
||||
<article class="gallery__item" role="group" aria-roledescription="Folie" aria-label="Bild 2 von 12">
|
||||
<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>
|
||||
<img src="assets/index_Pasta and many forks.jpg" alt="Pasta and many forks">
|
||||
</article>
|
||||
|
||||
<article class="gallery__item" role="group" aria-roledescription="Folie" aria-label="Bild 3 von 12">
|
||||
<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>
|
||||
<img src="assets/index_Zoomed in asian eating.jpg" alt="Zoomed in asian eating">
|
||||
</article>
|
||||
|
||||
<article class="gallery__item" role="group" aria-roledescription="Folie" aria-label="Bild 4 von 12">
|
||||
<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>
|
||||
<img src="assets/index_Burger eating together.jpg" alt="Burger eating together">
|
||||
</article>
|
||||
|
||||
<article class="gallery__item" role="group" aria-roledescription="Folie" aria-label="Bild 5 von 12">
|
||||
<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>
|
||||
<img src="assets/index_Cake cutting figs.jpg" alt="Cake cutting figs">
|
||||
</article>
|
||||
|
||||
<article class="gallery__item" role="group" aria-roledescription="Folie" aria-label="Bild 6 von 12">
|
||||
<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>
|
||||
<img src="assets/index_Cooking woman at home.jpg" alt="Cooking woman at home">
|
||||
</article>
|
||||
|
||||
<article class="gallery__item" role="group" aria-roledescription="Folie" aria-label="Bild 7 von 12">
|
||||
<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>
|
||||
<img src="assets/index_Eating and laughing girls.jpg" alt="Eating and laughing girls">
|
||||
</article>
|
||||
|
||||
<article class="gallery__item" role="group" aria-roledescription="Folie" aria-label="Bild 8 von 12">
|
||||
<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>
|
||||
<img src="assets/index_Pasta in cheese.jpg" alt="Pasta in cheese">
|
||||
</article>
|
||||
|
||||
<article class="gallery__item" role="group" aria-roledescription="Folie" aria-label="Bild 9 von 12">
|
||||
<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>
|
||||
<img src="assets/index_Salad roommates.jpg" alt="Salad roommates">
|
||||
</article>
|
||||
|
||||
<article class="gallery__item" role="group" aria-roledescription="Folie" aria-label="Bild 10 von 12">
|
||||
<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>
|
||||
<img src="assets/index_Sharing food table.jpg" alt="Sharing food table">
|
||||
</article>
|
||||
|
||||
<article class="gallery__item" role="group" aria-roledescription="Folie" aria-label="Bild 11 von 12">
|
||||
<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>
|
||||
<img src="assets/index_Spicy food zoomed.jpg" alt="Spicy food zoomed">
|
||||
</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>
|
||||
<img src="assets/index_cooking.jpg" alt="Cooking">
|
||||
</article>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
<button type="button" class="gallery__arrow gallery__arrow--next" aria-label="Nächstes Bild">
|
||||
<i class="fas fa-chevron-right"></i>
|
||||
</button>
|
||||
@ -221,6 +136,7 @@
|
||||
|
||||
<div class="gallery_dots" role="tablist" aria-label="Seite auswählen"></div>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<!-- Lightbox: Bildansicht vergrössert -->
|
||||
<div class="lightbox" id="gallery-lightbox" aria-hidden="true">
|
||||
@ -235,6 +151,7 @@
|
||||
|
||||
<!-- FAQ Section: Akkordion mit häufig gestellten Fragen -->
|
||||
<section class="faq-section">
|
||||
<div class="container">
|
||||
<h2>Häufig gestellte Fragen</h2>
|
||||
<div class="faq-accordion">
|
||||
<div class="faq-item">
|
||||
@ -243,20 +160,10 @@
|
||||
<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>
|
||||
<p><strong>Schritt 1: 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.</p>
|
||||
<p><strong>Schritt 2: Dein Profil ausfüllen</strong><br>Lade ein Profilfoto hoch, schreib ein bisschen über dich und gib deine Allergien/Ernährungspräferenzen an. Das hilft anderen, dich besser kennenzulernen.</p>
|
||||
<p><strong>Schritt 3: Erkunde Events</strong><br>Browsing durch unsere Events, filtere nach Diät oder Allergie-Einstellungen, und melde dich zu den Events an, die dich interessieren!</p>
|
||||
<p><strong>Schritt 4: 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.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -309,9 +216,9 @@
|
||||
<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>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<div class="footer">
|
||||
<div class="footer-left">
|
||||
|
||||
@ -23,7 +23,6 @@ const CURRENT_USER_KEY = "socialCookingCurrentUser";
|
||||
// =============================
|
||||
let currentStep = 0;
|
||||
const lastStep = steps.length - 1;
|
||||
let reviewReturnStep = null;
|
||||
|
||||
// Text für den Weiter-Button je nach Schritt
|
||||
const nextLabels = {
|
||||
@ -152,14 +151,10 @@ function showStep(index, pushHistory = true) {
|
||||
*/
|
||||
function updateFlowVisibility(stepIndex) {
|
||||
const isIntroStep = stepIndex === 0;
|
||||
const isReviewReturnMode = reviewReturnStep === lastStep && stepIndex < lastStep;
|
||||
|
||||
flowFooter.hidden = isIntroStep;
|
||||
backButton.hidden = isIntroStep;
|
||||
backButton.textContent = "Zurück";
|
||||
nextButton.textContent = isReviewReturnMode
|
||||
? "Zurück zur Übersicht"
|
||||
: nextLabels[stepIndex];
|
||||
nextButton.textContent = nextLabels[stepIndex];
|
||||
}
|
||||
|
||||
|
||||
@ -270,29 +265,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.
|
||||
* Dabei werden Checkboxen und zusätzliches Freitextfeld kombiniert.
|
||||
@ -320,8 +292,6 @@ function updateReview() {
|
||||
Object.entries(reviewValues).forEach(([key, value]) => {
|
||||
updateReviewField(key, value);
|
||||
});
|
||||
|
||||
updateReviewGallery();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -425,42 +395,6 @@ function mapEventTypeToCategory(value) {
|
||||
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.
|
||||
*/
|
||||
@ -472,8 +406,6 @@ function buildStoredEvent() {
|
||||
const eventDate = form.elements.eventDate.value;
|
||||
const eventTime = form.elements.eventTime.value;
|
||||
const eventCity = form.elements.eventCity.value.trim();
|
||||
const fallbackGallery = [getPlaceholderImageByEventType(eventType)];
|
||||
const resolvedGallery = galleryImages.length > 0 ? [...galleryImages] : fallbackGallery;
|
||||
|
||||
return {
|
||||
id: Date.now(),
|
||||
@ -482,7 +414,6 @@ function buildStoredEvent() {
|
||||
address: form.elements.eventAddress.value.trim(),
|
||||
date: formatDateForStorage(eventDate),
|
||||
time: formatTimeForStorage(eventTime),
|
||||
eventType,
|
||||
category: mapEventTypeToCategory(eventType),
|
||||
diet: dietType,
|
||||
spots: Number(form.elements.maxGuests.value),
|
||||
@ -499,7 +430,7 @@ function buildStoredEvent() {
|
||||
allergiesNote: form.elements.allergiesOther.value.trim(),
|
||||
// Host wird separat geführt und nicht als angemeldeter Gast gezählt.
|
||||
participants: [],
|
||||
gallery: resolvedGallery,
|
||||
gallery: [...galleryImages],
|
||||
createdAt: new Date().toISOString(),
|
||||
source: "local"
|
||||
};
|
||||
@ -603,52 +534,6 @@ function validateDietType() {
|
||||
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.
|
||||
* Rückgabe:
|
||||
@ -682,10 +567,6 @@ function validateRequiredFields(fields) {
|
||||
* Wenn der User im ersten Formularschritt ist, geht es zurück zum Intro.
|
||||
*/
|
||||
function handleBackClick() {
|
||||
if (reviewReturnStep === lastStep && currentStep < lastStep) {
|
||||
reviewReturnStep = null;
|
||||
}
|
||||
|
||||
if (currentStep > 1) {
|
||||
showStep(currentStep - 1);
|
||||
} else {
|
||||
@ -701,12 +582,6 @@ function handleBackClick() {
|
||||
function handleNextClick() {
|
||||
if (!validateCurrentStep()) return;
|
||||
|
||||
if (reviewReturnStep === lastStep && currentStep < lastStep) {
|
||||
showStep(lastStep);
|
||||
reviewReturnStep = null;
|
||||
return;
|
||||
}
|
||||
|
||||
if (currentStep < lastStep) {
|
||||
showStep(currentStep + 1);
|
||||
} else {
|
||||
@ -758,17 +633,11 @@ function registerCounterHandlers() {
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
const min = Number(input.min || 1);
|
||||
const currentValue = Number(input.value || 0);
|
||||
|
||||
if (currentValue < min && change < 0) {
|
||||
input.value = currentValue;
|
||||
return;
|
||||
}
|
||||
|
||||
const currentValue = Number(input.value || min);
|
||||
input.value = Math.max(min, currentValue + change);
|
||||
}
|
||||
|
||||
@ -832,8 +701,6 @@ function registerReviewEditHandlers() {
|
||||
const stepIndex = Number(item.dataset.editStep);
|
||||
const fieldName = item.dataset.editField;
|
||||
|
||||
// Öffnet einen Schritt aus der Übersicht und springt danach direkt zurück zu Schritt 7.
|
||||
reviewReturnStep = lastStep;
|
||||
showStep(stepIndex);
|
||||
focusFieldByName(fieldName);
|
||||
};
|
||||
@ -881,7 +748,7 @@ function registerValidationFeedbackHandlers() {
|
||||
* Setzt den Fokus auf ein bestimmtes Feld oder die erste Option einer Radio-Gruppe.
|
||||
*/
|
||||
function focusFieldByName(fieldName) {
|
||||
const field = form.elements[fieldName] || document.getElementById(fieldName);
|
||||
const field = form.elements[fieldName];
|
||||
|
||||
if (!field) return;
|
||||
|
||||
@ -978,7 +845,6 @@ function initEventCreationFlow() {
|
||||
|
||||
// Counter aktivieren
|
||||
registerCounterHandlers();
|
||||
registerDietConflictHandlers();
|
||||
registerMenuBulletHandler();
|
||||
registerValidationFeedbackHandlers();
|
||||
registerReviewEditHandlers();
|
||||
|
||||
@ -3,13 +3,16 @@
|
||||
const CURRENT_USER_KEY = 'socialCookingCurrentUser';
|
||||
const USERS_STORAGE_KEY = 'socialCookingUsers';
|
||||
const REGISTRATION_STORAGE_KEY = 'socialCookingRegistrations';
|
||||
|
||||
const detailcontainer = document.getElementById('detail-view');
|
||||
// -------------------------------------------------------------
|
||||
// DOM entry point and shared asset path.
|
||||
// -------------------------------------------------------------
|
||||
const detailContainer = document.getElementById('detail-view');
|
||||
const locationIconPath = 'assets/icon_location.svg';
|
||||
const calendarIconPath = 'assets/icon_calendar.svg';
|
||||
const gastIconPath = 'assets/icon_gast.svg';
|
||||
const currentUser = getCurrentUser();
|
||||
|
||||
|
||||
// Read event id from query string (detail page deep-link support).
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
const eventId = parseInt(params.get('id'));
|
||||
|
||||
@ -59,11 +62,14 @@
|
||||
}
|
||||
|
||||
function getUserDisplayName(user) {
|
||||
if (!user) return '';
|
||||
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();
|
||||
|
||||
return (firstName || `${firstName} ${lastName}`.trim() || String(user.email || '').trim()).trim();
|
||||
}
|
||||
|
||||
function getResolvedParticipants(event, registrationMap) {
|
||||
@ -78,7 +84,10 @@
|
||||
Object.entries(registrationMap || {}).forEach(([email, ids]) => {
|
||||
const isRegisteredForEvent = Array.isArray(ids)
|
||||
&& ids.map(id => Number(id)).includes(Number(event.id));
|
||||
if (!isRegisteredForEvent) return;
|
||||
|
||||
if (!isRegisteredForEvent) {
|
||||
return;
|
||||
}
|
||||
|
||||
const user = usersByEmail.get(String(email || '').trim().toLowerCase());
|
||||
const displayName = getUserDisplayName(user) || String(email || '').trim();
|
||||
@ -93,36 +102,20 @@
|
||||
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) {
|
||||
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) {
|
||||
if (!event?.date) return null;
|
||||
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;
|
||||
let year;
|
||||
let month;
|
||||
let day;
|
||||
|
||||
if (isoDateMatch) {
|
||||
year = Number(isoDateMatch[1]);
|
||||
@ -130,158 +123,137 @@
|
||||
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
|
||||
JAN: 1,
|
||||
FEB: 2,
|
||||
'MÄR': 3,
|
||||
MRZ: 3,
|
||||
APR: 4,
|
||||
MAI: 5,
|
||||
JUN: 6,
|
||||
JUL: 7,
|
||||
AUG: 8,
|
||||
SEP: 9,
|
||||
OKT: 10,
|
||||
NOV: 11,
|
||||
DEZ: 12
|
||||
};
|
||||
const localizedMatch = dateValue.match(/^(\d{1,2})\.\s*([A-Za-zÄÖÜäöü]{3,9})\.?\s*(\d{4})$/);
|
||||
if (!localizedMatch) return null;
|
||||
const localizedMatch = dateValue.match(/^(\d{1,2})\.\s*([A-ZÄÖÜ]{3})\.\s*(\d{4})$/);
|
||||
|
||||
if (!localizedMatch) {
|
||||
return null;
|
||||
}
|
||||
|
||||
day = Number(localizedMatch[1]);
|
||||
month = monthMap[String(localizedMatch[2]).toLowerCase()];
|
||||
month = monthMap[localizedMatch[2]];
|
||||
year = Number(localizedMatch[3]);
|
||||
if (!month) return null;
|
||||
|
||||
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 isRegistrationClosedForEvent(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();
|
||||
return msUntilStart <= 24 * 60 * 60 * 1000;
|
||||
const twentyfourHoursInMs = 24 * 60 * 60 * 1000;
|
||||
|
||||
return msUntilStart <= twentyfourHoursInMs;
|
||||
}
|
||||
|
||||
// Abmeldefrist: 1 Tag (24 h) vor Eventstart.
|
||||
function getDeregistrationInfo(event) {
|
||||
const eventDateTime = parseEventDateTime(event);
|
||||
if (!eventDateTime || Number.isNaN(eventDateTime.getTime())) return { daysLeft: null, isClosed: false };
|
||||
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 };
|
||||
const deadlineMs = eventDateTime.getTime() - oneDayMs;
|
||||
const msUntilDeadline = deadlineMs - Date.now();
|
||||
|
||||
if (msUntilDeadline <= 0) {
|
||||
return { daysLeft: 0, isClosed: true };
|
||||
}
|
||||
|
||||
const daysLeft = Math.ceil(msUntilDeadline / oneDayMs);
|
||||
return { daysLeft, isClosed: false };
|
||||
}
|
||||
|
||||
// Adresse ist nur im 24h-Fenster VOR Eventstart sichtbar.
|
||||
function isAddressVisibleWindow(event) {
|
||||
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;
|
||||
if (!eventDateTime || Number.isNaN(eventDateTime.getTime())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const msUntilStart = eventDateTime.getTime() - Date.now();
|
||||
const twentyfourHoursInMs = 24 * 60 * 60 * 1000;
|
||||
|
||||
return msUntilStart >= 0 && msUntilStart <= twentyfourHoursInMs;
|
||||
}
|
||||
|
||||
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;
|
||||
function countRegistrationsForEvent(registrationMap, eventId) {
|
||||
return Object.values(registrationMap).reduce((count, ids) => {
|
||||
const hasEvent = Array.isArray(ids)
|
||||
&& ids.map(id => Number(id)).includes(Number(eventId));
|
||||
|
||||
return hasEvent ? count + 1 : count;
|
||||
}, 0);
|
||||
}
|
||||
|
||||
// Ermittelt, ob das Event vom aktuell eingeloggten Benutzer erstellt wurde.
|
||||
function isEventOwnedByCurrentUser(event, user) {
|
||||
if (!event || !user) return false;
|
||||
if (!event || !user) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const userEmail = String(user.email || '').trim().toLowerCase();
|
||||
const hostEmail = String(event.hostEmail || '').trim().toLowerCase();
|
||||
if (userEmail && hostEmail) return userEmail === hostEmail;
|
||||
|
||||
if (userEmail && hostEmail) {
|
||||
return userEmail === hostEmail;
|
||||
}
|
||||
|
||||
// Fallback für ältere Datensätze ohne hostEmail.
|
||||
const userFirstName = String(user.vorname || '').trim().toLowerCase();
|
||||
const hostName = String(event.host?.name || '').trim().toLowerCase();
|
||||
return Boolean(userFirstName && hostName && userFirstName === hostName);
|
||||
}
|
||||
|
||||
// Prüft, ob der aktuelle Benutzer bereits in der Teilnehmerliste des Events steht.
|
||||
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(
|
||||
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 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(
|
||||
(userFirstName && participantSet.has(userFirstName))
|
||||
|| (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.
|
||||
try {
|
||||
const response = await fetch('data/events.json');
|
||||
@ -292,17 +264,68 @@
|
||||
if (event) {
|
||||
renderDetailPage(event);
|
||||
} 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) {
|
||||
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 = {
|
||||
FLEISCH: 'Fleisch',
|
||||
FISCH: 'Fisch',
|
||||
VEGGIE: 'Vegetarisch',
|
||||
VEGAN: 'Vegan'
|
||||
};
|
||||
|
||||
return labels[diet] || diet;
|
||||
}
|
||||
|
||||
// Compose and inject the full detail UI for a single event.
|
||||
function renderDetailPage(event) {
|
||||
// Core display values and resilient fallbacks for optional data fields.
|
||||
const displayDate = formatEventDate(event.date);
|
||||
const displayTime = formatEventTime(event.time);
|
||||
const dietLabel = getDietLabel(event.diet);
|
||||
const eventCategory = event.category || 'EVENT';
|
||||
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
|
||||
? event.hostMessage
|
||||
: ['Der Host hat für dieses Event noch keine Nachricht hinterlegt.'];
|
||||
@ -310,269 +333,269 @@
|
||||
? event.menu
|
||||
: ['Menü wird in Kürze bekannt gegeben.'];
|
||||
const specifications = Array.isArray(event.specifications) && event.specifications.length > 0
|
||||
? event.specifications : [];
|
||||
? event.specifications
|
||||
: [];
|
||||
const registrationMap = getRegistrationMap();
|
||||
const participants = getResolvedParticipants(event, registrationMap);
|
||||
const isOwnEvent = isEventOwnedByCurrentUser(event, currentUser);
|
||||
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 galleryImages = Array.isArray(event.gallery)
|
||||
? event.gallery.filter(Boolean)
|
||||
: [];
|
||||
const galleryMarkup = galleryImages.length > 0
|
||||
? `
|
||||
<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>
|
||||
`
|
||||
: '';
|
||||
const visibleParticipants = participants.slice(0, 6);
|
||||
const remainingParticipants = Math.max(0, participants.length - visibleParticipants.length);
|
||||
const totalGuests = Number.isFinite(event.spots) ? event.spots : 0;
|
||||
const confirmedGuests = participants.length;
|
||||
const freePlaces = Math.max(0, totalGuests - confirmedGuests);
|
||||
const isFull = freePlaces === 0;
|
||||
const isRegistrationClosed = isRegistrationClosedForEvent(event);
|
||||
const isOwnEvent = isEventOwnedByCurrentUser(event, currentUser);
|
||||
const deregInfo = getDeregistrationInfo(event);
|
||||
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 isListedParticipant = isUserListedInEventParticipants(event, currentUser);
|
||||
const hasAddressAccess = isRegistered || isListedParticipant || isOwnEvent;
|
||||
const isCanceled = event.status === 'canceled';
|
||||
|
||||
const actionButtonLabel = isCanceled ? 'Abgesagt'
|
||||
: isOwnEvent ? 'Dein Event!'
|
||||
: !currentUser ? 'Einloggen'
|
||||
: isRegistered ? (deregInfo.isClosed ? 'Abmeldung geschlossen' : 'Abmelden')
|
||||
: isRegistrationClosed ? 'Anmeldung geschlossen'
|
||||
: 'Anmelden';
|
||||
const actionButtonDisabled = isCanceled
|
||||
|| isOwnEvent
|
||||
const hasAddressAccess = isRegistered || isListedParticipant;
|
||||
const actionButtonLabel = isOwnEvent
|
||||
? 'Dein Event!'
|
||||
: !currentUser
|
||||
? 'Einloggen'
|
||||
: isRegistered
|
||||
? (deregInfo.isClosed ? 'Abmeldung geschlossen' : 'Abmelden')
|
||||
: isRegistrationClosed
|
||||
? 'Anmeldung geschlossen'
|
||||
: 'Anmelden';
|
||||
const actionButtonDisabled = isOwnEvent
|
||||
|| (!isRegistered && (isFull || isRegistrationClosed))
|
||||
|| (isRegistered && deregInfo.isClosed);
|
||||
const actionButtonVariantClass = isOwnEvent ? ' button-primary-eigener-event'
|
||||
: (isRegistered || isRegistrationClosed) ? ' button-primary-abmelden '
|
||||
: ' button-primary ';
|
||||
|
||||
const actionButtonVariantClass = isOwnEvent
|
||||
? ' button-primary'
|
||||
: isRegistered
|
||||
? ' button-primary-abmelden '
|
||||
: isRegistrationClosed
|
||||
? ' button-primary-abmelden '
|
||||
: ' button-primary ';
|
||||
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
|
||||
? `<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">
|
||||
<h2 class="detail-section-title">Adresse</h2>
|
||||
<p>${event.address}</p>
|
||||
</article>
|
||||
`
|
||||
: `
|
||||
<article class="detail-panel">
|
||||
<h2 class="detail-section-title">Adresse</h2>
|
||||
<p>Vielen Dank für die Anmeldung! Die Adresse für diesen Event wird 24 Stunden vorher genau hier sichtbar sein.</p>
|
||||
</article>
|
||||
`;
|
||||
const detailChips = [
|
||||
`<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>`),
|
||||
...specifications.map(item => `<span class="event-tag">${item}</span>`)
|
||||
].join('');
|
||||
|
||||
detailcontainer.innerHTML = `
|
||||
<section class="detail-hero">
|
||||
<div class="detail-top-row">
|
||||
<span class="event-location"><img src="${locationIconPath}" class="icon" alt="">${event.location}</span>
|
||||
<span class="event-date-time"><img src="${calendarIconPath}" class="icon" alt=""> ${displayDate} | ${displayTime}</span>
|
||||
<span class="event-date-time"><img src="${gastIconPath}" class="icon" alt="">${confirmedGuests}/${totalGuests}</span>
|
||||
</div>
|
||||
<h1 class="detail-title">${event.title}</h1>
|
||||
<div class="event-meta-row detail-chip-row">${detailChips}</div>
|
||||
</section>
|
||||
// Render complete detail page layout including:
|
||||
// hero metadata, host card, menu, participants, gallery and sticky action bar.
|
||||
detailContainer.innerHTML = `
|
||||
<button type="button" class="btn-back-to-overview" data-navigate-back>Zurück</button>
|
||||
|
||||
<section class="detail-content-grid">
|
||||
<div class="detail-side-stack">
|
||||
<article class="detail-panel">
|
||||
<header class="host-header">
|
||||
<span class="host-role">Host</span>
|
||||
<span class="host-name">${hostName}</span>
|
||||
</header>
|
||||
${hostMessage.map(paragraph => `<p>${paragraph}</p>`).join('')}
|
||||
</article>
|
||||
<section class="detail-hero">
|
||||
<div class="detail-top-row">
|
||||
<span class="event-location">
|
||||
<img src="${locationIconPath}" class="icon" alt="">${event.location}
|
||||
</span>
|
||||
|
||||
<span class="event-date-time">
|
||||
<img src="${calendarIconPath}" class="icon" alt=""> ${displayDate} | ${displayTime}
|
||||
</span>
|
||||
|
||||
<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>
|
||||
` : ''}
|
||||
<span class="event-date-time">
|
||||
<img src="${gastIconPath}" class="icon" alt="">${confirmedGuests}/${totalGuests}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<h1 class="detail-title">${event.title}</h1>
|
||||
<div class="event-meta-row detail-chip-row">
|
||||
${detailChips}
|
||||
</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>
|
||||
<section class="detail-content-grid">
|
||||
<div class="detail-side-stack">
|
||||
<article class="detail-panel">
|
||||
<header class="host-header">
|
||||
<span class="host-role">Host</span>
|
||||
<span class="host-name">${hostName}</span>
|
||||
</header>
|
||||
${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>
|
||||
${participants.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-buttons">
|
||||
<span class="detail-spots-pill${isFull ? ' detail-spots-pill-full' : ''}">
|
||||
${isFull ? 'AUSGEBUCHT' : `${freePlaces} Plätze frei`}
|
||||
</span>
|
||||
<div class="detail-action-btn-wrap">
|
||||
<button class="detail-primary-btn${actionButtonVariantClass}" 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>
|
||||
</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>
|
||||
`;
|
||||
|
||||
// DOM references after render
|
||||
const lightbox = detailcontainer.querySelector('.detail-lightbox');
|
||||
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]');
|
||||
// ---------------------------------------------------------
|
||||
// Lightbox behavior for gallery images:
|
||||
// open on image click, close via backdrop, close button or ESC.
|
||||
// ---------------------------------------------------------
|
||||
const backButton = detailContainer.querySelector('[data-navigate-back]');
|
||||
const lightbox = detailContainer.querySelector('.detail-lightbox');
|
||||
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) {
|
||||
registerButton.disabled = true;
|
||||
registerButton.textContent = 'Dein Event!';
|
||||
registerButton.setAttribute('aria-disabled', 'true');
|
||||
}
|
||||
|
||||
// Anmeldung / Abmeldung mit Bestätigungs-Modal
|
||||
// Anmeldung toggeln und im lokalen Registrierungs-Store persistieren.
|
||||
if (registerButton) {
|
||||
registerButton.addEventListener('click', () => {
|
||||
if (isOwnEvent) return;
|
||||
if (isOwnEvent) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!currentUser || !currentUser.email) {
|
||||
window.location.href = 'login.html';
|
||||
return;
|
||||
}
|
||||
|
||||
const alreadyRegistered = (() => {
|
||||
const map = getRegistrationMap();
|
||||
const ids = Array.isArray(map[currentUser.email])
|
||||
? map[currentUser.email].map(id => Number(id)) : [];
|
||||
return ids.includes(Number(event.id));
|
||||
})();
|
||||
const nextRegistrationMap = getRegistrationMap();
|
||||
const currentList = Array.isArray(nextRegistrationMap[currentUser.email])
|
||||
? nextRegistrationMap[currentUser.email].map(id => Number(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));
|
||||
|
||||
// Snackbar: Feedback bei Abmeldung.
|
||||
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);
|
||||
}
|
||||
} else if (!isFull && !isRegistrationClosed) {
|
||||
const modal = document.getElementById('register-confirm-modal');
|
||||
if (modal) modal.classList.add('show');
|
||||
registrationSet.add(Number(event.id));
|
||||
|
||||
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(); });
|
||||
// Snackbar: Feedback bei Anmeldung.
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
nextRegistrationMap[currentUser.email] = Array.from(registrationSet);
|
||||
setRegistrationMap(nextRegistrationMap);
|
||||
|
||||
// Re-Render aktualisiert Buttonzustand und CTA ohne Seitenreload.
|
||||
renderDetailPage(event);
|
||||
});
|
||||
}
|
||||
|
||||
// "Alle ansehen": Teilnehmerliste aufklappen / zuklappen
|
||||
const showAllBtn = detailcontainer.querySelector('[data-show-all-participants]');
|
||||
const avatarRow = detailcontainer.querySelector('[data-participants-row]');
|
||||
const fullList = detailcontainer.querySelector('[data-participants-full]');
|
||||
// "Alle ansehen": Teilnehmerliste aufklappen / zuklappen.
|
||||
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', () => {
|
||||
@ -583,24 +606,32 @@
|
||||
});
|
||||
}
|
||||
|
||||
// Lightbox
|
||||
// Central close helper to keep all close paths consistent.
|
||||
function closeLightbox() {
|
||||
if (!lightbox) return;
|
||||
if (!lightbox) {
|
||||
return;
|
||||
}
|
||||
|
||||
lightbox.classList.remove('is-open');
|
||||
lightbox.setAttribute('aria-hidden', 'true');
|
||||
}
|
||||
|
||||
if (lightbox && lightboxImage) {
|
||||
// Open with selected image source.
|
||||
galleryButtons.forEach(button => {
|
||||
button.addEventListener('click', () => {
|
||||
const imageSrc = button.getAttribute('data-fullsrc');
|
||||
if (!imageSrc) return;
|
||||
if (!imageSrc) {
|
||||
return;
|
||||
}
|
||||
|
||||
lightboxImage.src = imageSrc;
|
||||
lightbox.classList.add('is-open');
|
||||
lightbox.setAttribute('aria-hidden', 'false');
|
||||
});
|
||||
});
|
||||
|
||||
// Close when user clicks on backdrop.
|
||||
lightbox.addEventListener('click', event => {
|
||||
const target = event.target;
|
||||
if (target instanceof HTMLElement && target.hasAttribute('data-close-lightbox')) {
|
||||
@ -608,11 +639,22 @@
|
||||
}
|
||||
});
|
||||
|
||||
// Close via dedicated icon/button.
|
||||
lightboxClose?.addEventListener('click', closeLightbox);
|
||||
|
||||
// Close with keyboard for accessibility.
|
||||
document.addEventListener('keydown', event => {
|
||||
if (event.key === 'Escape') closeLightbox();
|
||||
if (event.key === 'Escape') {
|
||||
closeLightbox();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Back button navigation: returns to event overview page.
|
||||
if (backButton) {
|
||||
backButton.addEventListener('click', () => {
|
||||
window.location.href = 'event_overview.html';
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@ -36,11 +36,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
function getInfoModalShownKeyForUser(user) {
|
||||
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) {
|
||||
if (!event || !user) {
|
||||
@ -96,9 +91,8 @@
|
||||
|
||||
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();
|
||||
return (firstName || `${firstName} ${lastName}`.trim() || String(user.email || '').trim()).trim();
|
||||
}
|
||||
|
||||
function getResolvedParticipants(event, registrationMap) {
|
||||
@ -167,7 +161,6 @@
|
||||
dateFilter.value = savedDate;
|
||||
}
|
||||
|
||||
updateDietAvailability();
|
||||
applyFilters();
|
||||
} catch (error) {
|
||||
console.error('Fehler:', error);
|
||||
@ -287,42 +280,28 @@
|
||||
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
|
||||
JAN: 1,
|
||||
FEB: 2,
|
||||
'MÄR': 3,
|
||||
MRZ: 3,
|
||||
APR: 4,
|
||||
MAI: 5,
|
||||
JUN: 6,
|
||||
JUL: 7,
|
||||
AUG: 8,
|
||||
SEP: 9,
|
||||
OKT: 10,
|
||||
NOV: 11,
|
||||
DEZ: 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;
|
||||
}
|
||||
|
||||
day = Number(localizedMatch[1]);
|
||||
month = monthMap[String(localizedMatch[2]).toLowerCase()];
|
||||
month = monthMap[localizedMatch[2]];
|
||||
year = Number(localizedMatch[3]);
|
||||
|
||||
if (!month) {
|
||||
@ -398,24 +377,23 @@
|
||||
});
|
||||
|
||||
const filtered = allEvents.filter(event => {
|
||||
if (event.status === 'canceled') return false;
|
||||
|
||||
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 ||
|
||||
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 ||
|
||||
const allergieMatch = activeAllergies.size === 0 ||
|
||||
(event.specifications && event.specifications.some(spec => activeAllergies.has(spec)));
|
||||
|
||||
|
||||
const locationMatch = selectedLocation === 'ALLE_ORTE' || event.location === selectedLocation;
|
||||
const eventDateIso = parseEventDateToIso(event.date);
|
||||
const dateMatch = !selectedDate || eventDateIso === selectedDate;
|
||||
|
||||
return categoryMatch && dietMatch && allergieMatch && locationMatch && dateMatch;
|
||||
});
|
||||
|
||||
renderEvents(filtered);
|
||||
|
||||
sessionStorage.setItem('activeFilter', activeCategory);
|
||||
@ -475,11 +453,6 @@
|
||||
const isOwnEvent = isEventOwnedByCurrentUser(event, currentUser);
|
||||
const isRegistered = userRegistrationSet.has(Number(event.id));
|
||||
const isRegistrationClosed = isRegistrationClosedForEvent(event);
|
||||
const isCanceled = event.status === 'canceled';
|
||||
|
||||
if (isCanceled) {
|
||||
card.style.opacity = '0.6';
|
||||
}
|
||||
|
||||
// Build optional specification chips only when data exists.
|
||||
const specsChips = event.specifications && event.specifications.length > 0
|
||||
@ -491,31 +464,17 @@
|
||||
? event.diet.split(', ').map(d => `<span class="event-tag">${d.trim()}</span>`).join('')
|
||||
: '';
|
||||
|
||||
let actionMarkup = '';
|
||||
if (isCanceled) {
|
||||
actionMarkup = '<button class="button-primary" type="button" disabled>Abgesagt</button>';
|
||||
} else if (isOwnEvent) {
|
||||
actionMarkup = '<button class="button-primary-eigener-event" type="button" data-registration-action="own" disabled>Dein Event!</button>';
|
||||
} else if (isRegistered) {
|
||||
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>`;
|
||||
}
|
||||
const actionMarkup = isOwnEvent
|
||||
? '<button class="button-primary-eigener-event" type="button" data-registration-action="own" disabled>Dein Event!</button>'
|
||||
: isRegistered
|
||||
? '<button class="button-primary button-primary-abmelden" type="button" data-registration-action="unregister">Abmelden</button>'
|
||||
: isRegistrationClosed
|
||||
? '<button class="button-primary" button-plaetze" type="button" data-registration-action="closed" disabled>Anmeldung geschlossen</button>'
|
||||
: isFull
|
||||
? ''
|
||||
: !currentUser
|
||||
? '<button class="button-primary btn-primary-register" type="button" data-registration-action="login">Anmelden</button>'
|
||||
: '<button class="button-primary btn-primary-register" type="button" data-registration-action="register">Anmelden</button>';
|
||||
|
||||
card.innerHTML = `
|
||||
<div class="event-main">
|
||||
@ -536,8 +495,8 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="event-side${isFull ? ' event-side-full' : ''}">
|
||||
${isRegistrationClosed ? '' : `<span class="button-plaetze${isFull ? ' event-spots-full' : ''}">${isFull ? 'Ausgebucht' : `${freePlaces} Plätze frei`}</span>`}
|
||||
${actionMarkup}
|
||||
${sideInfoMarkup}
|
||||
</div>
|
||||
`;
|
||||
|
||||
@ -571,55 +530,29 @@
|
||||
: [];
|
||||
const idSet = new Set(currentIds);
|
||||
|
||||
// Anmelde-Modal öffnen
|
||||
if (action === 'register' && !isFull && !isRegistrationClosed) {
|
||||
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();
|
||||
};
|
||||
// Abmeldung: Benutzer vom Event entfernen und Snackbar anzeigen.
|
||||
if (action === 'unregister') {
|
||||
idSet.delete(Number(event.id));
|
||||
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);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Abmelde-Modal öffnen
|
||||
if (action === 'unregister') {
|
||||
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();
|
||||
};
|
||||
// Anmeldung: Benutzer zum Event hinzufügen und Snackbar anzeigen.
|
||||
if (action === 'register' && !isFull && !isRegistrationClosed) {
|
||||
idSet.add(Number(event.id));
|
||||
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);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
nextRegistrationMap[currentUser.email] = Array.from(idSet);
|
||||
@ -632,59 +565,6 @@
|
||||
});
|
||||
}
|
||||
|
||||
// Verhindert widersprüchliche Ernährungsformen:
|
||||
// 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 => {
|
||||
button.addEventListener('click', () => {
|
||||
@ -696,34 +576,12 @@
|
||||
// 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
|
||||
// Diet filter: toggle selection
|
||||
if (activeDiets.has(dietValue)) {
|
||||
activeDiets.delete(dietValue);
|
||||
} else {
|
||||
activeDiets.add(dietValue);
|
||||
}
|
||||
|
||||
updateDietAvailability();
|
||||
} else if (allergieValue !== null) {
|
||||
// Allergie filter: toggle selection
|
||||
if (activeAllergies.has(allergieValue)) {
|
||||
@ -755,30 +613,10 @@
|
||||
}
|
||||
}
|
||||
|
||||
// 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');
|
||||
const modalClose = infoModal?.querySelector('.modal-close');
|
||||
|
||||
if (infoButton && infoModal) {
|
||||
infoButton.addEventListener('click', () => {
|
||||
@ -802,29 +640,13 @@
|
||||
|
||||
// Auto-open info modal on first login
|
||||
if (currentUser && infoModal) {
|
||||
const userInfoModalKey = getInfoModalShownKeyForUser(currentUser);
|
||||
const hasShownInfoModal = localStorage.getItem(userInfoModalKey);
|
||||
const hasShownInfoModal = localStorage.getItem(INFO_MODAL_SHOWN_KEY);
|
||||
if (!hasShownInfoModal) {
|
||||
infoModal.classList.add('show');
|
||||
localStorage.setItem(userInfoModalKey, 'true');
|
||||
localStorage.setItem(INFO_MODAL_SHOWN_KEY, 'true');
|
||||
}
|
||||
}
|
||||
|
||||
// Kick off initial load/render cycle.
|
||||
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');
|
||||
}
|
||||
}
|
||||
|
||||
@ -7,7 +7,7 @@
|
||||
const carouselTrack = document.querySelector('.gallery__track');
|
||||
const prevArrow = document.querySelector('.gallery__arrow--prev');
|
||||
const nextArrow = document.querySelector('.gallery__arrow--next');
|
||||
const dotscontainer = document.querySelector('.gallery_dots');
|
||||
const dotsContainer = document.querySelector('.gallery_dots');
|
||||
|
||||
// Nur ausführen, wenn die Galerie auf der Seite vorhanden ist.
|
||||
if (carouselTrack) {
|
||||
@ -22,8 +22,8 @@ if (carouselTrack) {
|
||||
var dots = [];
|
||||
|
||||
function buildDots() {
|
||||
if (!dotscontainer) return;
|
||||
dotscontainer.innerHTML = '';
|
||||
if (!dotsContainer) return;
|
||||
dotsContainer.innerHTML = '';
|
||||
dots = [];
|
||||
for (var i = 0; i < pageCount; i++) {
|
||||
var dot = document.createElement('button');
|
||||
@ -36,7 +36,7 @@ if (carouselTrack) {
|
||||
dot.addEventListener('click', function() {
|
||||
goToPage(parseInt(this.dataset.page));
|
||||
});
|
||||
dotscontainer.appendChild(dot);
|
||||
dotsContainer.appendChild(dot);
|
||||
dots.push(dot);
|
||||
}
|
||||
}
|
||||
|
||||
73
js/login.js
@ -50,74 +50,69 @@ function createFallbackUser(email, passwort) {
|
||||
// Validierungsfunktion
|
||||
function validateForm(event) {
|
||||
event.preventDefault();
|
||||
|
||||
let isValid = true;
|
||||
|
||||
// Wir zeigen bewusst immer nur den ersten Fehler im Formular an.
|
||||
// So bleibt der Ablauf ruhig und führt den Nutzer Feld für Feld.
|
||||
// Email-Validierung
|
||||
const emailValue = emailInput.value.trim();
|
||||
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
||||
const emailGroup = emailInput.parentElement;
|
||||
const passwortGroup = passwortInput.parentElement;
|
||||
|
||||
emailGroup.classList.remove('has-error');
|
||||
passwortGroup.classList.remove('has-error');
|
||||
|
||||
if (!emailValue) {
|
||||
emailGroup.classList.add('has-error');
|
||||
emailError.textContent = 'Bitte gib deine E-Mail Adresse ein.';
|
||||
emailInput.focus();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!emailRegex.test(emailValue)) {
|
||||
isValid = false;
|
||||
} else if (!emailRegex.test(emailValue)) {
|
||||
emailGroup.classList.add('has-error');
|
||||
emailError.textContent = 'Bitte gib eine gültige E-Mail Adresse ein.';
|
||||
emailInput.focus();
|
||||
return;
|
||||
isValid = false;
|
||||
} else {
|
||||
emailGroup.classList.remove('has-error');
|
||||
}
|
||||
|
||||
// Passwort-Validierung
|
||||
const passwortValue = passwortInput.value;
|
||||
const passwortGroup = passwortInput.parentElement;
|
||||
|
||||
if (!passwortValue) {
|
||||
passwortGroup.classList.add('has-error');
|
||||
passwortError.textContent = 'Bitte gib dein Passwort ein.';
|
||||
passwortInput.focus();
|
||||
return;
|
||||
}
|
||||
|
||||
if (passwortValue.length < 6) {
|
||||
isValid = false;
|
||||
} else if (passwortValue.length < 6) {
|
||||
passwortGroup.classList.add('has-error');
|
||||
passwortError.textContent = 'Dein Passwort ist zu kurz. Bitte überprüfe dein Passwort.';
|
||||
passwortInput.focus();
|
||||
return;
|
||||
isValid = false;
|
||||
} else {
|
||||
passwortGroup.classList.remove('has-error');
|
||||
}
|
||||
|
||||
// Wenn alle Validierungen bestanden, prüfen wir:
|
||||
// 1) gibt es den Benutzer schon?
|
||||
// 2) ist das Passwort korrekt?
|
||||
// Danach speichern wir die aktive Session.
|
||||
const users = getStoredUsers();
|
||||
const matchedUser = users.find(user => user.email?.toLowerCase() === emailValue.toLowerCase());
|
||||
if (isValid) {
|
||||
const users = getStoredUsers();
|
||||
const matchedUser = users.find(user => user.email?.toLowerCase() === emailValue.toLowerCase());
|
||||
|
||||
if (matchedUser && matchedUser.passwort !== passwortValue) {
|
||||
passwortGroup.classList.add('has-error');
|
||||
passwortError.textContent = 'Das Passwort ist nicht korrekt.';
|
||||
passwortInput.focus();
|
||||
return;
|
||||
}
|
||||
if (matchedUser && matchedUser.passwort !== passwortValue) {
|
||||
passwortGroup.classList.add('has-error');
|
||||
passwortError.textContent = 'Das Passwort ist nicht korrekt.';
|
||||
return;
|
||||
}
|
||||
|
||||
const userToLogin = matchedUser || createFallbackUser(emailValue, passwortValue);
|
||||
setCurrentUser(userToLogin);
|
||||
const userToLogin = matchedUser || createFallbackUser(emailValue, passwortValue);
|
||||
setCurrentUser(userToLogin);
|
||||
|
||||
// Snackbar anzeigen und dann zur Event-Übersicht weiterleiten.
|
||||
var snackbar = document.getElementById('snackbar');
|
||||
if (snackbar) {
|
||||
snackbar.classList.add('snackbar--visible');
|
||||
setTimeout(function() {
|
||||
// Snackbar anzeigen und dann zur Event-Übersicht weiterleiten.
|
||||
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';
|
||||
}, 2000);
|
||||
} else {
|
||||
window.location.href = 'event_overview.html';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -137,4 +132,4 @@ passwortInput.addEventListener('input', function() {
|
||||
});
|
||||
|
||||
// Form Submit Event
|
||||
loginForm.addEventListener('submit', validateForm);
|
||||
loginForm.addEventListener('submit', validateForm);
|
||||
346
js/my_profil.js
@ -14,9 +14,7 @@
|
||||
const profileTabPanels = Array.from(document.querySelectorAll('[data-profile-panel]'));
|
||||
|
||||
const myEventsCount = document.getElementById('my-events-count');
|
||||
const myEventsBtnCount = document.getElementById('btn-my-events-count');
|
||||
const myRegistrationsCount = document.getElementById('my-registrations-count');
|
||||
const myRegistrationsBtnCount = document.getElementById('btn-my-registrations-count');
|
||||
const myEventsList = document.getElementById('my-events-list');
|
||||
const myRegistrationsList = document.getElementById('my-registrations-list');
|
||||
|
||||
@ -213,11 +211,6 @@
|
||||
const isActive = panel.getAttribute('data-profile-panel') === tabName;
|
||||
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.
|
||||
@ -227,37 +220,32 @@
|
||||
return;
|
||||
}
|
||||
|
||||
const unregisterButton = target.closest('[data-unregister-id]');
|
||||
const unregisterButton = target.closest('[data-unregister-id]');
|
||||
if (unregisterButton) {
|
||||
if (!currentUser?.email) return;
|
||||
if (!currentUser?.email) {
|
||||
return;
|
||||
}
|
||||
|
||||
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');
|
||||
if (modal) modal.classList.add('show');
|
||||
unregisterFromEvent(eventId, currentUser.email);
|
||||
|
||||
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'); });
|
||||
// Snackbar: Feedback bei erfolgreicher Abmeldung.
|
||||
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);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
if (target.closest('a, button')) {
|
||||
return;
|
||||
}
|
||||
@ -291,20 +279,11 @@
|
||||
modal.classList.remove('show');
|
||||
};
|
||||
|
||||
document.getElementById('confirmCancelEventBtn').addEventListener('click', function() {
|
||||
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.
|
||||
@ -341,10 +320,10 @@
|
||||
|
||||
renderMyEvents(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.
|
||||
function unregisterFromEvent(eventId, userEmail) {
|
||||
function unregisterFromEvent(eventId, userEmail) {
|
||||
const registrationMap = getRegistrationMap();
|
||||
const currentIds = Array.isArray(registrationMap[userEmail]) ? registrationMap[userEmail] : [];
|
||||
const nextIds = currentIds
|
||||
@ -355,10 +334,12 @@
|
||||
setRegistrationMap(registrationMap);
|
||||
|
||||
renderMyRegistrations(allEvents, currentUser);
|
||||
profileFeedback.textContent = 'Du wurdest von dem Event abgemeldet.';
|
||||
}
|
||||
|
||||
// Validiert Profildaten konsistent und liefert true/false zur Submit-Steuerung.
|
||||
function validateProfileForm() {
|
||||
let isValid = true;
|
||||
|
||||
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
||||
|
||||
@ -469,138 +450,51 @@
|
||||
});
|
||||
}
|
||||
|
||||
// Ermittelt angemeldete Events über die Registration-Map und participants-Liste.
|
||||
// Ermittelt angemeldete Events über die Registration-Map.
|
||||
function getMyRegisteredEvents(events, user) {
|
||||
const registrationMap = getRegistrationMap();
|
||||
const registeredIds = Array.isArray(registrationMap[user.email]) ? registrationMap[user.email] : [];
|
||||
const idSet = new Set(registeredIds.map(id => Number(id)));
|
||||
|
||||
const userFirstName = String(user.vorname || '').trim().toLowerCase();
|
||||
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;
|
||||
});
|
||||
return events.filter(event => idSet.has(Number(event.id)));
|
||||
}
|
||||
|
||||
// Rendert angemeldete Events inkl. Zähler.
|
||||
// Rendert gehostete Events inkl. Zähler.
|
||||
function renderMyEvents(events, user) {
|
||||
const hostedEvents = getMyHostedEvents(events, user);
|
||||
const count = hostedEvents.length;
|
||||
|
||||
myEventsCount.textContent = String(count);
|
||||
if (myEventsBtnCount) myEventsBtnCount.textContent = String(count);
|
||||
|
||||
renderEventCards(myEventsList, hostedEvents, {
|
||||
title: 'Noch kein eigenes Event',
|
||||
text: 'Starte dein erstes Dinner und lade die Community an deinen Tisch ein.',
|
||||
buttonLabel: 'Event erstellen',
|
||||
href: 'event_create.html'
|
||||
}, 'hosting');
|
||||
}
|
||||
|
||||
function getSeenAddresses() {
|
||||
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();
|
||||
}
|
||||
const hostedEvents = getMyHostedEvents(events, user);
|
||||
myEventsCount.textContent = String(hostedEvents.length);
|
||||
renderEventCards(myEventsList, hostedEvents, {
|
||||
title: 'Noch kein eigenes Event',
|
||||
text: 'Starte dein erstes Dinner und lade die Community an deinen Tisch ein.',
|
||||
buttonLabel: 'Event erstellen',
|
||||
href: 'event_create.html'
|
||||
}, 'hosting');
|
||||
}
|
||||
|
||||
// Rendert angemeldete Events inkl. Zähler.
|
||||
function renderMyRegistrations(events, user) {
|
||||
const registeredEvents = getMyRegisteredEvents(events, user);
|
||||
|
||||
const count = registeredEvents.length;
|
||||
|
||||
myRegistrationsCount.textContent = String(count);
|
||||
if (myRegistrationsBtnCount) myRegistrationsBtnCount.textContent = String(count);
|
||||
|
||||
const seenAddresses = getSeenAddresses();
|
||||
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, {
|
||||
title: 'Noch keine Anmeldungen',
|
||||
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;
|
||||
const registeredEvents = getMyRegisteredEvents(events, user);
|
||||
myRegistrationsCount.textContent = String(registeredEvents.length);
|
||||
renderEventCards(myRegistrationsList, registeredEvents, {
|
||||
title: 'Noch keine Anmeldungen',
|
||||
text: 'Entdecke spannende Dinner in deiner Naehe und melde dich direkt an.',
|
||||
buttonLabel: 'Events entdecken',
|
||||
href: 'event_overview.html'
|
||||
}, 'registrations');
|
||||
}
|
||||
|
||||
// Baut die Eventkarten für beide Listen in einheitlichem Markup.
|
||||
function renderEventCards(container, events, emptyStateConfig, mode, seenAddresses = []) {
|
||||
function renderEventCards(container, events, emptyStateConfig, mode) {
|
||||
container.innerHTML = '';
|
||||
|
||||
if (events.length === 0) {
|
||||
const emptyElement = document.createElement('div');
|
||||
emptyElement.className = 'empty-state';
|
||||
emptyElement.className = 'profile-empty-state';
|
||||
emptyElement.innerHTML = `
|
||||
<div class="empty-state-kicker">Keine Treffer</div>
|
||||
<p class="profile-empty-kicker">Keine Treffer</p>
|
||||
<h3>${emptyStateConfig.title}</h3>
|
||||
<p>${emptyStateConfig.text}</p>
|
||||
<a class="empty-state-link button-primary" href="${emptyStateConfig.href}">${emptyStateConfig.buttonLabel}</a>
|
||||
<a class="button-primary" href="${emptyStateConfig.href}">${emptyStateConfig.buttonLabel}</a>
|
||||
`;
|
||||
container.appendChild(emptyElement);
|
||||
return;
|
||||
@ -610,81 +504,26 @@
|
||||
const card = document.createElement('article');
|
||||
card.className = 'profile-event-card profile-event-card-clickable';
|
||||
card.setAttribute('data-event-id', String(event.id));
|
||||
|
||||
const isCanceled = event.status === 'canceled';
|
||||
if (isCanceled) {
|
||||
card.style.opacity = '0.6';
|
||||
}
|
||||
|
||||
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>
|
||||
const addressMarkup = mode === 'registrations' && event.address && isAddressVisibleWindow(event)
|
||||
? `
|
||||
<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 if (isDeregClosed) {
|
||||
actionMarkup = `
|
||||
<div class="event-side">
|
||||
<button class="button-primary-abmelden" type="button" disabled>Abmeldung geschlossen</button>
|
||||
</div>
|
||||
`;
|
||||
} else {
|
||||
actionMarkup = `
|
||||
`
|
||||
: '';
|
||||
|
||||
const actionMarkup = mode === 'registrations'
|
||||
? `
|
||||
<div class="event-side">
|
||||
<button class="button-primary-abmelden" type="button" data-unregister-id="${event.id}">Abmelden</button>
|
||||
</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 = `
|
||||
<div>
|
||||
@ -699,24 +538,17 @@
|
||||
});
|
||||
}
|
||||
|
||||
// Gibt true zurück, wenn die Adresse sichtbar sein soll (24h vor bis 1h nach Start).
|
||||
// Gibt true zurück, wenn ein Event innerhalb der nächsten 24 Stunden startet.
|
||||
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;
|
||||
}
|
||||
if (!eventDateTime || Number.isNaN(eventDateTime.getTime())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Gibt true zurück, wenn ein Event bereits vorbei ist (1h nach Start).
|
||||
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;
|
||||
const msUntilStart = eventDateTime.getTime() - Date.now();
|
||||
const twentyfourHoursInMs = 24 * 60 * 60 * 1000;
|
||||
|
||||
return msUntilStart >= 0 && msUntilStart <= twentyfourHoursInMs;
|
||||
}
|
||||
|
||||
// Parse für ISO- und lokalisierte Datumsformate aus den Eventdaten.
|
||||
@ -737,42 +569,28 @@
|
||||
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
|
||||
JAN: 1,
|
||||
FEB: 2,
|
||||
'MÄR': 3,
|
||||
MRZ: 3,
|
||||
APR: 4,
|
||||
MAI: 5,
|
||||
JUN: 6,
|
||||
JUL: 7,
|
||||
AUG: 8,
|
||||
SEP: 9,
|
||||
OKT: 10,
|
||||
NOV: 11,
|
||||
DEZ: 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;
|
||||
}
|
||||
|
||||
day = Number(localizedMatch[1]);
|
||||
month = monthMap[String(localizedMatch[2]).toLowerCase()];
|
||||
month = monthMap[localizedMatch[2]];
|
||||
year = Number(localizedMatch[3]);
|
||||
|
||||
if (!month) {
|
||||
|
||||
121
js/navigation.js
@ -6,13 +6,11 @@
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const CURRENT_USER_KEY = 'socialCookingCurrentUser';
|
||||
const REGISTRATION_STORAGE_KEY = 'socialCookingRegistrations';
|
||||
const EVENTS_STORAGE_KEY = 'socialCookingEvents';
|
||||
const navcontainers = document.querySelectorAll('.nav-tab-links');
|
||||
const navContainers = document.querySelectorAll('.nav-tab-links');
|
||||
const currentPage = (window.location.pathname.split('/').pop() || 'index.html').toLowerCase();
|
||||
|
||||
// Beendet früh, falls auf einer Seite keine Hauptnavigation vorhanden ist.
|
||||
if (!navcontainers.length) {
|
||||
if (!navContainers.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -33,94 +31,6 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
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() {
|
||||
const loginIsActive = currentPage === 'login.html';
|
||||
@ -161,11 +71,10 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
}
|
||||
|
||||
// Baut die Navigation für eingeloggte Benutzer.
|
||||
function buildLoggedInNavigation(user, hasNotifications) {
|
||||
function buildLoggedInNavigation(user) {
|
||||
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 `
|
||||
<a
|
||||
@ -196,27 +105,15 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
title="${user.vorname || 'Profil'}"
|
||||
>
|
||||
${initial}
|
||||
${notificationMarkup}
|
||||
</a>
|
||||
`;
|
||||
}
|
||||
|
||||
async function initNavigation() {
|
||||
const currentUser = getCurrentUser();
|
||||
let nextMarkup;
|
||||
const currentUser = getCurrentUser();
|
||||
const nextMarkup = currentUser ? buildLoggedInNavigation(currentUser) : buildLoggedOutNavigation();
|
||||
|
||||
if (currentUser) {
|
||||
const hasNotifications = await hasUnreadNotifications(currentUser);
|
||||
nextMarkup = buildLoggedInNavigation(currentUser, hasNotifications);
|
||||
} else {
|
||||
nextMarkup = buildLoggedOutNavigation();
|
||||
}
|
||||
|
||||
// Wendet das passende Markup auf alle vorhandenen Kopf-Navigationen an.
|
||||
navcontainers.forEach(container => {
|
||||
container.innerHTML = nextMarkup;
|
||||
});
|
||||
}
|
||||
|
||||
initNavigation();
|
||||
// Wendet das passende Markup auf alle vorhandenen Kopf-Navigationen an.
|
||||
navContainers.forEach(container => {
|
||||
container.innerHTML = nextMarkup;
|
||||
});
|
||||
});
|
||||
|
||||
101
js/signup.js
@ -52,100 +52,95 @@ function closeWelcomeModal() {
|
||||
// Hauptfunktion für Formularvalidierung und Speicherung.
|
||||
function validateForm(event) {
|
||||
event.preventDefault();
|
||||
|
||||
let isValid = true;
|
||||
|
||||
// Wir zeigen pro Submit nur den ersten Fehler an.
|
||||
// So bleibt der Formularfluss klar und ruhig.
|
||||
// Vorname-Validierung
|
||||
const vornameValue = vornameInput.value.trim();
|
||||
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) {
|
||||
vornameGroup.classList.add('has-error');
|
||||
vornameInput.focus();
|
||||
return;
|
||||
isValid = false;
|
||||
} else {
|
||||
vornameGroup.classList.remove('has-error');
|
||||
}
|
||||
|
||||
// Nachname-Validierung
|
||||
const nachnameValue = nachnameInput.value.trim();
|
||||
const nachnameGroup = nachnameInput.parentElement;
|
||||
|
||||
if (!nachnameValue) {
|
||||
nachnameGroup.classList.add('has-error');
|
||||
nachnameInput.focus();
|
||||
return;
|
||||
isValid = false;
|
||||
} else {
|
||||
nachnameGroup.classList.remove('has-error');
|
||||
}
|
||||
|
||||
// Email-Validierung
|
||||
const emailValue = emailInput.value.trim();
|
||||
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
||||
const emailGroup = emailInput.parentElement;
|
||||
|
||||
if (!emailValue) {
|
||||
emailGroup.classList.add('has-error');
|
||||
document.getElementById('emailError').textContent = 'Bitte gib deine E-Mail Adresse ein.';
|
||||
emailInput.focus();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!emailRegex.test(emailValue)) {
|
||||
isValid = false;
|
||||
} else if (!emailRegex.test(emailValue)) {
|
||||
emailGroup.classList.add('has-error');
|
||||
document.getElementById('emailError').textContent = 'Bitte gib eine gültige E-Mail Adresse ein.';
|
||||
emailInput.focus();
|
||||
return;
|
||||
isValid = false;
|
||||
} else {
|
||||
emailGroup.classList.remove('has-error');
|
||||
}
|
||||
|
||||
// Passwort-Validierung
|
||||
const passwortValue = passwortInput.value;
|
||||
const passwortGroup = passwortInput.parentElement;
|
||||
|
||||
if (!passwortValue) {
|
||||
passwortGroup.classList.add('has-error');
|
||||
document.getElementById('passwortError').textContent = 'Bitte gib ein Passwort ein.';
|
||||
passwortInput.focus();
|
||||
return;
|
||||
}
|
||||
|
||||
if (passwortValue.length < 8) {
|
||||
isValid = false;
|
||||
} else if (passwortValue.length < 8) {
|
||||
passwortGroup.classList.add('has-error');
|
||||
document.getElementById('passwortError').textContent = 'Dein Passwort muss mindestens 8 Zeichen lang sein.';
|
||||
passwortInput.focus();
|
||||
return;
|
||||
isValid = false;
|
||||
} else {
|
||||
passwortGroup.classList.remove('has-error');
|
||||
}
|
||||
|
||||
// Wenn alles gültig ist:
|
||||
// 1) auf doppelte E-Mail prüfen
|
||||
// 2) neuen Benutzer speichern
|
||||
// 3) als aktuellen Benutzer einloggen
|
||||
const existingUsers = getStoredUsers();
|
||||
const emailLower = emailValue.toLowerCase();
|
||||
const emailAlreadyUsed = existingUsers.some(user => user.email?.toLowerCase() === emailLower);
|
||||
if (isValid) {
|
||||
const existingUsers = getStoredUsers();
|
||||
const emailLower = emailValue.toLowerCase();
|
||||
const emailAlreadyUsed = existingUsers.some(user => user.email?.toLowerCase() === emailLower);
|
||||
|
||||
if (emailAlreadyUsed) {
|
||||
emailGroup.classList.add('has-error');
|
||||
document.getElementById('emailError').textContent = 'Diese E-Mail ist bereits registriert. Bitte nutze den Login.';
|
||||
emailInput.focus();
|
||||
return;
|
||||
if (emailAlreadyUsed) {
|
||||
emailGroup.classList.add('has-error');
|
||||
document.getElementById('emailError').textContent = 'Diese E-Mail ist bereits registriert. Bitte nutze den Login.';
|
||||
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();
|
||||
// Weiterleitung erfolgt beim Klick auf "Weiter zu den Events".
|
||||
}
|
||||
|
||||
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)
|
||||
@ -185,4 +180,4 @@ welcomeModal.addEventListener('click', function(event) {
|
||||
});
|
||||
|
||||
// Form Submit Event
|
||||
signupForm.addEventListener('submit', validateForm);
|
||||
signupForm.addEventListener('submit', validateForm);
|
||||
41
login.html
@ -5,11 +5,10 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Invité | Login</title>
|
||||
|
||||
<!-- Stylesheet für diese Seite -->
|
||||
<link rel="stylesheet" href="css/login_signup.css">
|
||||
|
||||
<!-- Globales Stylesheet -->
|
||||
<link rel="stylesheet" href="css/stylesheet_global.css">
|
||||
<!-- Stylesheet für diese Seite -->
|
||||
<link rel="stylesheet" href="css/login_signup.css">
|
||||
<script src="js/navigation.js" defer></script>
|
||||
|
||||
</head>
|
||||
@ -27,35 +26,37 @@
|
||||
</header>
|
||||
|
||||
<!-- Main Content -->
|
||||
<div class="container-login layout-narrow">
|
||||
<div class="main-content">
|
||||
<div class="container">
|
||||
<div class="image-section">
|
||||
<img src="assets/index_cooking.jpg" alt="Social Cooking">
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div class="form-section">
|
||||
<h1>Login</h1>
|
||||
|
||||
<form id="loginForm" novalidate >
|
||||
<div class="form-group margin-bottom-16">
|
||||
<p class= "label-input-field" for="email">E-Mail</p>
|
||||
<input type="email" id="email" name="email" required placeholder="Deine E-mail-Adresse">
|
||||
<div class="error-message error-message--field-callout" id="emailError">Bitte gib eine gültige E-Mail-Adresse ein.</div>
|
||||
<form id="loginForm">
|
||||
<div class="form-group">
|
||||
<label for="email">E-Mail</label>
|
||||
<input type="email" id="email" name="email" required placeholder="deine.email@example.com">
|
||||
<div class="error-message" id="emailError">Bitte gib eine gültige E-Mail Adresse ein.</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group margin-bottom-40">
|
||||
<p class= "label-input-field" for="passwort">Passwort</p>
|
||||
<input type="password" id="passwort" name="passwort" required placeholder="Dein Passwort">
|
||||
<div class="error-message error-message--field-callout" id="passwortError">Bitte gib dein Passwort ein.</div>
|
||||
<div class="form-group">
|
||||
<label for="passwort">Passwort</label>
|
||||
<input type="password" id="passwort" name="passwort" required placeholder="Gib dein Passwort ein">
|
||||
<div class="error-message" id="passwortError">Bitte gib dein Passwort ein.</div>
|
||||
</div>
|
||||
|
||||
<button class="button-primary margin-bottom-24">Login</button>
|
||||
<button type="submit" class="button-primary">Login</button>
|
||||
|
||||
<div class="link-text">
|
||||
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>
|
||||
</form>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div> <!-- Schliesst container -->
|
||||
</div> <!-- Schliesst main-content -->
|
||||
<div class="snackbar" id="snackbar">Willkommen zurück! Du wirst weitergeleitet...</div>
|
||||
<script src="js/login.js"></script>
|
||||
<div class="footer">
|
||||
@ -75,4 +76,4 @@
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
</html>
|
||||
@ -29,10 +29,10 @@
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main class="layout-wide">
|
||||
<main class="container profile-page">
|
||||
<section class="profile-hero" aria-label="Profilübersicht">
|
||||
<div>
|
||||
<p class="badge margin-bottom-40">Mein Bereich</p>
|
||||
<p class="badge">Mein Bereich</p>
|
||||
<h1 id="headline">Mein Profil</h1>
|
||||
<p id="profile-subline" class="profile-subline">Hier findest du deine Events, deine Anmeldungen und kannst deine Profildaten verwalten.</p>
|
||||
</div>
|
||||
@ -49,13 +49,8 @@
|
||||
|
||||
<section id="logged-in-content" class="profile-grid">
|
||||
<nav class="category-items" aria-label="Profilbereiche">
|
||||
<button type="button" class="category-item is-active category-item-profile" data-category-item="hosting">
|
||||
Meine Events <span class="btn-count" id="btn-my-events-count">0</span>
|
||||
</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 is-active category-item-profile" data-category-item="hosting">Meine Events</button>
|
||||
<button type="button" class="category-item category-item-profile" data-category-item="teilnehmen">Meine Anmeldungen</button>
|
||||
<button type="button" class="category-item category-item-profile" data-category-item="einstellungen">Profil-Einstellungen</button>
|
||||
</nav>
|
||||
|
||||
@ -70,14 +65,6 @@
|
||||
<div class="panel-head">
|
||||
<span id="my-registrations-count" class="panel-count">0</span>
|
||||
</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>
|
||||
</article>
|
||||
|
||||
@ -85,27 +72,27 @@
|
||||
<h2 class="panel-title">Profil verwalten</h2>
|
||||
<form id="profile-form" novalidate>
|
||||
<div class="form-grid">
|
||||
<div class="margin-bottom-16">
|
||||
<label class="label-input-field" for="vorname">Vorname</label>
|
||||
<div class="form-group">
|
||||
<label for="vorname">Vorname</label>
|
||||
<input type="text" id="vorname" name="vorname" required>
|
||||
<p class="input-error" id="vorname-error">Bitte gib deinen Vornamen ein.</p>
|
||||
</div>
|
||||
|
||||
<div class="margin-bottom-16">
|
||||
<label class="label-input-field" for="nachname">Nachname</label>
|
||||
<div class="form-group">
|
||||
<label for="nachname">Nachname</label>
|
||||
<input type="text" id="nachname" name="nachname" required>
|
||||
<p class="input-error" id="nachname-error">Bitte gib deinen Nachnamen ein.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="margin-bottom-16">
|
||||
<label class="label-input-field" for="email">E-Mail</label>
|
||||
<div class="form-group">
|
||||
<label for="email">E-Mail</label>
|
||||
<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>
|
||||
</div>
|
||||
|
||||
<div class="margin-bottom-40">
|
||||
<label class="label-input-field" for="passwort">Passwort</label>
|
||||
<div class="form-group">
|
||||
<label for="passwort">Passwort</label>
|
||||
<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-error" id="passwort-error">Das Passwort muss mindestens 6 Zeichen lang sein.</p>
|
||||
@ -122,11 +109,11 @@
|
||||
<div id="logoutModal" class="modal">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button class="close-btn" onclick="closeLogoutModal()">×</button>
|
||||
<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>
|
||||
@ -135,21 +122,6 @@
|
||||
</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>
|
||||
|
||||
@ -157,11 +129,11 @@
|
||||
<div id="cancelEventModal" class="modal">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button class="close-btn" onclick="closeCancelEventModal()">×</button>
|
||||
<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>
|
||||
|
||||
53
signup.html
@ -3,7 +3,7 @@
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<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">
|
||||
@ -26,8 +26,12 @@
|
||||
</header>
|
||||
|
||||
<!-- Main Content -->
|
||||
<div class="container-registration layout-wide">
|
||||
<div class="text-section">
|
||||
<div class="main-content">
|
||||
<div class="container">
|
||||
<div class="image-section">
|
||||
<img src="assets/index_cooking.jpg" alt="Social Cooking">
|
||||
</div>
|
||||
|
||||
<div class="form-section">
|
||||
<h1>Erstelle deinen Account</h1>
|
||||
|
||||
@ -35,32 +39,32 @@
|
||||
<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>
|
||||
|
||||
<form id="signupForm" novalidate>
|
||||
<div class="form-group margin-bottom-16">
|
||||
<p class= "label-input-field" for="vorname">Vorname*</p>
|
||||
<form id="signupForm">
|
||||
<div class="form-group">
|
||||
<label for="vorname">Vorname *</label>
|
||||
<input type="text" id="vorname" name="vorname" required placeholder="Dein Vorname">
|
||||
<div class="error-message 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 class="form-group margin-bottom-16">
|
||||
<p class= "label-input-field" for="nachname">Nachname*</p>
|
||||
<div class="form-group">
|
||||
<label for="nachname">Nachname *</label>
|
||||
<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 class="form-group margin-bottom-16">
|
||||
<p class= "label-input-field" for="email">E-Mail*</p>
|
||||
<input type="email" id="email" name="email" required placeholder="Deine E-mail-Adresse">
|
||||
<div class="error-message error-message--field-callout" id="emailError">Bitte gib eine gültige E-Mail-Adresse ein.</div>
|
||||
<div class="form-group">
|
||||
<label for="email">E-Mail *</label>
|
||||
<input type="email" id="email" name="email" required placeholder="deine.email@example.com">
|
||||
<div class="error-message" id="emailError">Bitte gib eine gültige E-Mail Adresse ein.</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group margin-bottom-40">
|
||||
<p class= "label-input-field" for="passwort">Passwort*</p>
|
||||
<div class="form-group">
|
||||
<label for="passwort">Passwort *</label>
|
||||
<input type="password" id="passwort" name="passwort" required placeholder="Mindestens 8 Zeichen">
|
||||
<div class="error-message 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>
|
||||
|
||||
<button type="submit" class="button-primary margin-bottom-24">Konto erstellen</button>
|
||||
<button type="submit" class="button-primary">Konto erstellen</button>
|
||||
|
||||
|
||||
<div class="link-text">
|
||||
@ -68,22 +72,15 @@
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="image-section">
|
||||
<img src="assets/index_cooking.jpg" alt="Social Cooking">
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div> <!-- Schliesst container -->
|
||||
</div> <!-- Schliesst main-content -->
|
||||
|
||||
<!-- Welcome Modal -->
|
||||
<div id="welcomeModal" class="modal">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h2>Konto erfolgreich erstellt!</h2>
|
||||
<button class="close-btn" onclick="closeWelcomeModal()">×</button>
|
||||
<h2>Konto erfolgreich erstellt!</h2>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
Willkommen bei Invité! Dein Account wurde erfolgreich erstellt. Entdecke jetzt die neuesten Events in deiner Nähe.
|
||||
@ -111,4 +108,4 @@
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
</html>
|
||||