diff --git a/GUESTS.md b/GUESTS.md index a93f79b..e5b85af 100644 --- a/GUESTS.md +++ b/GUESTS.md @@ -16,7 +16,7 @@ Anzahl Gruppen: **41** | Marie & Kai | guest | `Mari&Kai24!#` | Marie, Kai | | Familie Olsem | guest | `Olse!Fam24#?` | Laura, Sven, Lena, Finn | | Maxime | guest | `Maxi#Love24!` | Maxime | -| Familie Löster | guest | `Loes@Ring24#` | Claudia, Mario, Mélodie, Julian | +| Familie Löster | guest | `Loes@Ring24#` | Claudia, Mario, Mélodie | | Familie Thiels | guest | `Thie$Fest24!` | Matthias, Opa Bernd, Oma Heidi | | Familie Gollor | guest | `Goll%Herz24!` | Michael, Christin, Bruno | | Monika | guest | `Moni!Rose24#` | Monika | diff --git a/README.md b/README.md index 8871cd8..05171ae 100644 --- a/README.md +++ b/README.md @@ -542,7 +542,7 @@ Maxime Mitglieder: Maxime, Freund Familie Löster -Mitglieder: Claudia, Mario, Mélodie, Julian +Mitglieder: Claudia, Mario, Mélodie Familie Thiels Mitglieder: Matthias, Opa Bernd, Oma Heidi diff --git a/backend/.dockerignore b/backend/.dockerignore new file mode 100644 index 0000000..7f68881 --- /dev/null +++ b/backend/.dockerignore @@ -0,0 +1,10 @@ +.venv/ +__pycache__/ +*.py[cod] +*.sqlite3 +uploads/ +.pytest_cache/ +.ruff_cache/ +.git/ +.gitignore + diff --git a/backend/Dockerfile b/backend/Dockerfile index 4381c8a..6b6718b 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -10,4 +10,4 @@ RUN uv sync --frozen --no-dev COPY . . EXPOSE 8000 -CMD ["uv", "run", "gunicorn", "-b", "0.0.0.0:8000", "--workers", "1", "--timeout", "300", "--graceful-timeout", "30", "--keep-alive", "5", "--error-logfile", "-", "--access-logfile", "-", "app:app"] +CMD ["uv", "run", "gunicorn", "-b", "0.0.0.0:8000", "--workers", "2", "--timeout", "120", "--graceful-timeout", "30", "--keep-alive", "5", "--error-logfile", "-", "--access-logfile", "-", "app:app"] diff --git a/backend/app.py b/backend/app.py index 1426622..5148b52 100644 --- a/backend/app.py +++ b/backend/app.py @@ -105,7 +105,7 @@ DEFAULT_INVITATION_GROUPS = [ "name": "Familie Löster", "password": "Loes@Ring24#", "role": "guest", - "members": ["Claudia", "Mario", "Mélodie", "Julian"], + "members": ["Claudia", "Mario", "Mélodie"], }, { "name": "Familie Thiels", @@ -372,6 +372,80 @@ TEXTS = { "hotel_weinhaus_engel_desc": "Gemütliches Weinhaus-Hotel in Rauenthal.", "hotel_unico_boutique_desc": "Modernes Boutique-Hotel direkt in Hochheim mit stilvollen Zimmern und kurzer Entfernung zur Location.", "taxi_text": "Coming soon", + "culinary_eyebrow": "Genuss", + "culinary_title": "Gemeinsam genießen", + "culinary_intro": "Freut euch auf einen Abend voller besonderer Aromen, guter Getränke und gemeinsamer Genussmomente.", + "culinary_hero_image_alt": "Festlich gedeckte Hochzeitstafel in warmer Abendstimmung", + "culinary_navigation": "Navigation durch die Genuss-Seite", + "culinary_menu": "Menü", + "culinary_drinks": "Getränkekarte", + "culinary_reception_title": "Kulinarischer Auftakt zum Sektempfang", + "culinary_reception_text": "Verschiedene Flammkuchenvariationen als kleiner herzhafter Genuss zum Ankommen und gemeinsamen Anstoßen.", + "culinary_starter": "Vorspeise", + "culinary_starter_title": "Burrata mit Wassermelone und Cherrytomaten", + "culinary_starter_text": "Cremige Burrata mit Wassermelone, Cherrytomaten und Mango-Basilikum-Pesto, begleitet von hausgemachtem Nussbrot.", + "culinary_starter_image_alt": "Burrata mit Wassermelone, Cherrytomaten und Mango-Basilikum-Pesto", + "culinary_image_placeholder": "Bild folgt", + "culinary_main": "Hauptgang", + "culinary_main_title": "Buffet mit Frontcooking", + "culinary_main_image_alt": "Frontcooking mit gebratenen Steaks und Linguine aus dem Parmesanlaib", + "culinary_meat_fish": "Vom Fleisch & Fisch", + "culinary_rosemary_chicken": "Rosmarinhähnchenbrust", + "culinary_roastbeef": "Roastbeef am Stück, frisch vom Brett tranchiert", + "culinary_seabream": "Auf der Haut gebratenes Doradenfilet mit frischen Kräutern", + "culinary_parmesan_wheel": "Aus dem Parmesanlaib", + "culinary_linguine": "Linguine, frisch im Parmesanlaib geschwenkt", + "culinary_sauces": "Saucen", + "culinary_bbq": "Hausgemachte Barbecuesauce", + "culinary_sesame_mayo": "Sesammayonnaise", + "culinary_red_wine_jus": "Rotweinjus", + "culinary_sides": "Beilagen", + "culinary_rosemary_potatoes": "Rosmarinkartoffeln", + "culinary_herb_rice": "Kräuterreis", + "culinary_grilled_vegetables": "Mediterranes Grillgemüse", + "culinary_salad": "Salatvariation mit verschiedenen Dressings", + "culinary_dessert": "Süßer Abschluss", + "culinary_dessert_title": "Dessert & Hochzeitstorte", + "culinary_dessert_image_alt": "Panna cotta mit Fruchtsauce und frischen Erdbeeren", + "culinary_cake": "Unsere Hochzeitstorte", + "culinary_cake_text": "Vanillebiskuittorte mit Erdbeercreme – kreiert von Meisterkonditorin Lana.", + "culinary_dessert_variations": "Dessertvariationen", + "culinary_panna_cotta": "Panna cotta mit Fruchtsauce", + "culinary_creme_brulee": "Crème brûlée", + "culinary_pineapple": "Gegrillte Ananas mit schwarzem Pfeffer", + "culinary_drinks_title": "Für jeden Geschmack", + "drinks_image_alt": "Zwei Weingläser vor einem sonnigen Weinberg", + "drinks_during_celebration": "Getränke während der Feier", + "drinks_intro": "Vom Sektempfang bis zur Partynacht findet ihr hier unsere Getränkeauswahl.", + "drinks_refreshments": "Erfrischungen", + "drinks_refreshments_image_alt": "Alkoholfreie Erfrischungen mit Zitrone, Minze und Eis", + "drinks_aperitif_spirits": "Aperitif & Spirituosen", + "drinks_bar_classics": "Klassiker an der Bar", + "drinks_bar_image_alt": "Aperitifgläser mit Beeren und Zitrusfrüchten", + "drinks_evening_bar": "Bar am Abend", + "drinks_longdrinks_cocktails": "Longdrinks & Cocktails", + "drinks_evening_image_alt": "Tropischer Cocktail mit Ananas auf Eis", + "drinks_non_alcoholic": "Alkoholfreie Getränke", + "drinks_water": "Wasser", + "drinks_soft": "Softdrinks", + "drinks_juices": "Säfte", + "drinks_wine_beer": "Wein & Bier", + "drinks_wine_selection": "Ausgewählte Begleiter", + "drinks_white_wine": "Weißwein", + "drinks_rose_wine": "Roséwein", + "drinks_red_wine": "Rotwein", + "drinks_off_dry": "feinherb", + "drinks_dry": "trocken", + "drinks_beer": "Bier", + "drinks_beer_selection": "Eine Auswahl an Bieren", + "drinks_aperitif": "Aperitif", + "drinks_sparkling_wine": "Sekt", + "drinks_longdrinks": "Longdrinks", + "drinks_spirits": "Spirituosen", + "drinks_spirits_text": "Eine Auswahl an Spirituosen – unter anderem Jägermeister und verschiedene Obstbrände.", + "drinks_from_22": "Ab 22 Uhr", + "drinks_cocktails": "Cocktails", + "drinks_cocktails_text": "Unsere Cocktailauswahl wird noch bekannt gegeben.", "gifts_teaser": "Wir würden uns über diese Geschenke sehr freuen...", "gifts_reveal_button": "Wunsch aufdecken", "gifts_image_alt": "Ein großer Haufen Geldscheine", @@ -571,6 +645,80 @@ TEXTS = { "hotel_weinhaus_engel_desc": "Cozy winehouse hotel in Rauenthal.", "hotel_unico_boutique_desc": "Modern boutique hotel in Hochheim with stylish rooms and a short journey to the venue.", "taxi_text": "Coming soon", + "culinary_eyebrow": "Culinary delights", + "culinary_title": "Enjoying together", + "culinary_intro": "Look forward to an evening of special flavours, fine drinks, and shared culinary moments.", + "culinary_hero_image_alt": "Festively set wedding table in a warm evening atmosphere", + "culinary_navigation": "Culinary page navigation", + "culinary_menu": "Menu", + "culinary_drinks": "Drinks menu", + "culinary_reception_title": "A culinary prelude to the sparkling reception", + "culinary_reception_text": "A selection of tarte flambée offers a small savoury treat while everyone arrives and raises a glass together.", + "culinary_starter": "Starter", + "culinary_starter_title": "Burrata with watermelon and cherry tomatoes", + "culinary_starter_text": "Creamy burrata with watermelon, cherry tomatoes, and mango-basil pesto, served with homemade nut bread.", + "culinary_starter_image_alt": "Burrata with watermelon, cherry tomatoes, and mango-basil pesto", + "culinary_image_placeholder": "Photo coming soon", + "culinary_main": "Main course", + "culinary_main_title": "Buffet with live cooking", + "culinary_main_image_alt": "Live cooking with grilled steaks and linguine from a Parmesan wheel", + "culinary_meat_fish": "Meat & fish", + "culinary_rosemary_chicken": "Rosemary chicken breast", + "culinary_roastbeef": "Whole roast beef, freshly carved at the board", + "culinary_seabream": "Crispy skin sea bream fillet with fresh herbs", + "culinary_parmesan_wheel": "From the Parmesan wheel", + "culinary_linguine": "Linguine, freshly tossed in a Parmesan wheel", + "culinary_sauces": "Sauces", + "culinary_bbq": "Homemade barbecue sauce", + "culinary_sesame_mayo": "Sesame mayonnaise", + "culinary_red_wine_jus": "Red wine jus", + "culinary_sides": "Side dishes", + "culinary_rosemary_potatoes": "Rosemary potatoes", + "culinary_herb_rice": "Herb rice", + "culinary_grilled_vegetables": "Mediterranean grilled vegetables", + "culinary_salad": "Mixed salads with a selection of dressings", + "culinary_dessert": "Sweet finale", + "culinary_dessert_title": "Desserts & wedding cake", + "culinary_dessert_image_alt": "Panna cotta with fruit sauce and fresh strawberries", + "culinary_cake": "Our wedding cake", + "culinary_cake_text": "Vanilla sponge cake with strawberry cream – created by master pastry chef Lana.", + "culinary_dessert_variations": "Dessert selection", + "culinary_panna_cotta": "Panna cotta with fruit sauce", + "culinary_creme_brulee": "Crème brûlée", + "culinary_pineapple": "Grilled pineapple with black pepper", + "culinary_drinks_title": "Something for every taste", + "drinks_image_alt": "Two wine glasses in front of a sunlit vineyard", + "drinks_during_celebration": "Drinks throughout the celebration", + "drinks_intro": "From the sparkling reception to the party, here you will find our selection of drinks.", + "drinks_refreshments": "Refreshments", + "drinks_refreshments_image_alt": "Non-alcoholic refreshments with lemon, mint, and ice", + "drinks_aperitif_spirits": "Aperitifs & spirits", + "drinks_bar_classics": "Bar classics", + "drinks_bar_image_alt": "Aperitif glasses with berries and citrus fruit", + "drinks_evening_bar": "Evening bar", + "drinks_longdrinks_cocktails": "Long drinks & cocktails", + "drinks_evening_image_alt": "Tropical pineapple cocktail served on ice", + "drinks_non_alcoholic": "Non-alcoholic drinks", + "drinks_water": "Water", + "drinks_soft": "Soft drinks", + "drinks_juices": "Juices", + "drinks_wine_beer": "Wine & beer", + "drinks_wine_selection": "Selected accompaniments", + "drinks_white_wine": "White wine", + "drinks_rose_wine": "Rosé wine", + "drinks_red_wine": "Red wine", + "drinks_off_dry": "off-dry", + "drinks_dry": "dry", + "drinks_beer": "Beer", + "drinks_beer_selection": "A selection of beers", + "drinks_aperitif": "Aperitif", + "drinks_sparkling_wine": "Sparkling wine", + "drinks_longdrinks": "Long drinks", + "drinks_spirits": "Spirits", + "drinks_spirits_text": "A selection of spirits – including Jägermeister and various fruit brandies.", + "drinks_from_22": "From 10 PM", + "drinks_cocktails": "Cocktails", + "drinks_cocktails_text": "Our cocktail selection will be announced soon.", "gifts_teaser": "We would be very happy to receive these gifts...", "gifts_reveal_button": "Reveal wish", "gifts_image_alt": "A big pile of cash", diff --git a/backend/app.sqlite3 b/backend/app.sqlite3 index c97e861..0c5c553 100644 Binary files a/backend/app.sqlite3 and b/backend/app.sqlite3 differ diff --git a/backend/static/assets/ankunft.webp b/backend/static/assets/ankunft.webp new file mode 100644 index 0000000..3941305 Binary files /dev/null and b/backend/static/assets/ankunft.webp differ diff --git a/backend/static/assets/buffet.webp b/backend/static/assets/buffet.webp new file mode 100644 index 0000000..ba84c2f Binary files /dev/null and b/backend/static/assets/buffet.webp differ diff --git a/backend/static/assets/culinary/Burrata.png b/backend/static/assets/culinary/Burrata.png new file mode 100644 index 0000000..75042af Binary files /dev/null and b/backend/static/assets/culinary/Burrata.png differ diff --git a/backend/static/assets/culinary/Cocktails.png b/backend/static/assets/culinary/Cocktails.png new file mode 100644 index 0000000..355383b Binary files /dev/null and b/backend/static/assets/culinary/Cocktails.png differ diff --git a/backend/static/assets/culinary/Genusseinleitungsbild.png b/backend/static/assets/culinary/Genusseinleitungsbild.png new file mode 100644 index 0000000..79924ca Binary files /dev/null and b/backend/static/assets/culinary/Genusseinleitungsbild.png differ diff --git a/backend/static/assets/culinary/Getränkekarte titel.png b/backend/static/assets/culinary/Getränkekarte titel.png new file mode 100644 index 0000000..9a4afaa Binary files /dev/null and b/backend/static/assets/culinary/Getränkekarte titel.png differ diff --git a/backend/static/assets/culinary/Panacotta.png b/backend/static/assets/culinary/Panacotta.png new file mode 100644 index 0000000..1464933 Binary files /dev/null and b/backend/static/assets/culinary/Panacotta.png differ diff --git a/backend/static/assets/culinary/alkoholfrei.png b/backend/static/assets/culinary/alkoholfrei.png new file mode 100644 index 0000000..38cff74 Binary files /dev/null and b/backend/static/assets/culinary/alkoholfrei.png differ diff --git a/backend/static/assets/culinary/barclassics.png b/backend/static/assets/culinary/barclassics.png new file mode 100644 index 0000000..5bbb9c0 Binary files /dev/null and b/backend/static/assets/culinary/barclassics.png differ diff --git a/backend/static/assets/culinary/main-course-alternative-v2.png b/backend/static/assets/culinary/main-course-alternative-v2.png new file mode 100644 index 0000000..552a9e1 Binary files /dev/null and b/backend/static/assets/culinary/main-course-alternative-v2.png differ diff --git a/backend/static/assets/culinary/main-course-alternative.png b/backend/static/assets/culinary/main-course-alternative.png new file mode 100644 index 0000000..0ad1f09 Binary files /dev/null and b/backend/static/assets/culinary/main-course-alternative.png differ diff --git a/backend/static/assets/culinary/main-course-placeholder.png b/backend/static/assets/culinary/main-course-placeholder.png new file mode 100644 index 0000000..7d8388a Binary files /dev/null and b/backend/static/assets/culinary/main-course-placeholder.png differ diff --git a/backend/static/assets/kuchen.webp b/backend/static/assets/kuchen.webp new file mode 100644 index 0000000..0fec702 Binary files /dev/null and b/backend/static/assets/kuchen.webp differ diff --git a/backend/static/assets/party.webp b/backend/static/assets/party.webp new file mode 100644 index 0000000..7e0033a Binary files /dev/null and b/backend/static/assets/party.webp differ diff --git a/backend/static/assets/sektempfang.webp b/backend/static/assets/sektempfang.webp new file mode 100644 index 0000000..f1204e1 Binary files /dev/null and b/backend/static/assets/sektempfang.webp differ diff --git a/backend/static/assets/trauzermonie.webp b/backend/static/assets/trauzermonie.webp new file mode 100644 index 0000000..73317da Binary files /dev/null and b/backend/static/assets/trauzermonie.webp differ diff --git a/backend/static/culinary.js b/backend/static/culinary.js new file mode 100644 index 0000000..51a7202 --- /dev/null +++ b/backend/static/culinary.js @@ -0,0 +1,51 @@ +(() => { + const switcher = document.querySelector("[data-culinary-switch]"); + if (!switcher) return; + + const links = [...switcher.querySelectorAll("[data-culinary-target]")]; + const sections = links + .map((link) => document.getElementById(link.dataset.culinaryTarget)) + .filter(Boolean); + const reduceMotion = window.matchMedia("(prefers-reduced-motion: reduce)"); + + const activate = (target) => { + switcher.dataset.active = target; + links.forEach((link) => { + const selected = link.dataset.culinaryTarget === target; + link.classList.toggle("is-active", selected); + if (selected) link.setAttribute("aria-current", "true"); + else link.removeAttribute("aria-current"); + }); + }; + + links.forEach((link) => { + link.addEventListener("click", (event) => { + const section = document.getElementById(link.dataset.culinaryTarget); + if (!section) return; + event.preventDefault(); + activate(link.dataset.culinaryTarget); + section.scrollIntoView({ + behavior: reduceMotion.matches ? "auto" : "smooth", + block: "start", + }); + window.history.replaceState(null, "", link.hash); + }); + }); + + const observer = new IntersectionObserver( + (entries) => { + const visible = entries + .filter((entry) => entry.isIntersecting) + .sort((a, b) => b.intersectionRatio - a.intersectionRatio)[0]; + if (visible) activate(visible.target.id); + }, + { rootMargin: "-22% 0px -58%", threshold: [0, 0.15, 0.4] } + ); + + sections.forEach((section) => observer.observe(section)); + + const initialTarget = window.location.hash.slice(1); + if (sections.some((section) => section.id === initialTarget)) { + activate(initialTarget); + } +})(); diff --git a/backend/static/styles.css b/backend/static/styles.css index ede7feb..2636e05 100644 --- a/backend/static/styles.css +++ b/backend/static/styles.css @@ -1520,10 +1520,15 @@ input[type="file"]:focus { overflow: hidden; border-radius: 14px; padding: 1rem; + min-height: 145px; + display: flex; + flex-direction: column; + justify-content: center; + text-align: center; border: 1px solid rgba(31, 58, 47, 0.12); background-color: #f5ece0; background-image: - linear-gradient(180deg, rgba(22, 34, 28, 0.5), rgba(22, 34, 28, 0.28)), + linear-gradient(180deg, rgba(22, 34, 28, 0.3), rgba(22, 34, 28, 0.16)), var(--schedule-card-image); background-size: cover; background-position: var(--schedule-card-position, center); @@ -1540,6 +1545,7 @@ input[type="file"]:focus { letter-spacing: 0.08em; text-transform: uppercase; color: rgba(255, 247, 230, 0.95); + text-shadow: 0 1px 8px rgba(10, 20, 14, 0.72); } .schedule-item h2 { @@ -1547,12 +1553,19 @@ input[type="file"]:focus { font-size: 1.28rem; line-height: 1.15; color: #fff; + text-shadow: 0 2px 12px rgba(10, 20, 14, 0.72); } .schedule-item p { margin: 0.45rem 0 0; max-width: 42ch; color: rgba(255, 255, 255, 0.95); + margin-inline: auto; + text-shadow: 0 1px 9px rgba(10, 20, 14, 0.72); +} + +.schedule-item .schedule-time { + margin: 0 auto 0.2rem; } .hotel-list { @@ -2221,6 +2234,667 @@ body.has-route-modal { margin-top: 1.2rem; } +/* Genuss */ +.culinary-page { + display: grid; + gap: clamp(1.1rem, 3vw, 2rem); +} + +.culinary-hero, +.drinks-hero { + position: relative; + overflow: hidden; + border-radius: 22px; + color: #fff; + box-shadow: 0 16px 38px rgba(31, 58, 47, 0.18); +} + +.culinary-hero { + min-height: 0; + height: clamp(14rem, 38vw, 30rem); + background: var(--culinary-hero-image) center / cover; +} + +.culinary-intro-copy { + max-width: 44rem; + margin: 0.25rem auto 0; + text-align: center; +} + +.culinary-intro-copy h2 { + margin-bottom: 0.65rem; + color: var(--forest); + font-size: clamp(1.55rem, 3vw, 2.1rem); +} + +.culinary-intro-copy p { + margin: 0 auto; + max-width: 40rem; + line-height: 1.6; +} + +.culinary-hero-copy { + max-width: 42rem; + text-align: center; +} + +.culinary-hero h2, +.drinks-hero h2 { + margin-bottom: 0.55rem; + font-size: clamp(2.25rem, 7vw, 4.75rem); + line-height: 1; + text-shadow: 0 2px 16px rgba(10, 20, 14, 0.62); +} + +.culinary-hero p:last-child { + max-width: 38rem; + margin: 0 auto; + font-size: clamp(1rem, 2vw, 1.2rem); + line-height: 1.55; + text-shadow: 0 1px 10px rgba(10, 20, 14, 0.7); +} + +.culinary-eyebrow, +.culinary-course-label, +.cocktail-time { + margin: 0 0 0.5rem; + color: var(--gold); + font-weight: 700; + letter-spacing: 0.14em; + text-transform: uppercase; + font-size: 0.78rem; +} + +.culinary-hero .culinary-eyebrow, +.drinks-hero .culinary-eyebrow { + color: #f3d8a6; +} + +.culinary-jump-nav { + position: sticky; + top: 0.7rem; + z-index: 12; + display: grid; + grid-template-columns: repeat(2, minmax(8.5rem, 1fr)); + width: min(25rem, 100%); + margin: 0.15rem auto 0; + padding: 0.3rem; + border: 1px solid rgba(178, 137, 70, 0.3); + border-radius: 999px; + background: rgba(255, 251, 245, 0.94); + box-shadow: 0 8px 26px rgba(31, 58, 47, 0.14); + backdrop-filter: blur(12px); + isolation: isolate; +} + +.culinary-switch-slider { + position: absolute; + z-index: -1; + top: 0.3rem; + bottom: 0.3rem; + left: 0.3rem; + width: calc((100% - 0.6rem) / 2); + border-radius: 999px; + background: var(--forest); + box-shadow: 0 5px 15px rgba(31, 58, 47, 0.22); + transform: translateX(0); + transition: transform 420ms cubic-bezier(0.22, 1, 0.36, 1); +} + +.culinary-jump-nav[data-active="drinks"] .culinary-switch-slider { + transform: translateX(100%); +} + +.culinary-jump-nav a { + position: relative; + z-index: 1; + padding: 0.65rem 1rem; + border-radius: 999px; + color: var(--burgundy); + font-weight: 700; + text-align: center; + text-decoration: none; + transition: color 280ms ease; +} + +.culinary-jump-nav a:hover, +.culinary-jump-nav a:focus-visible { + color: var(--burgundy); +} + +.culinary-jump-nav a.is-active, +.culinary-jump-nav a.is-active:hover, +.culinary-jump-nav a.is-active:focus-visible { + color: #fff; +} + +.culinary-section { + scroll-margin-top: 5rem; +} + +.culinary-section-heading { + max-width: 48rem; + margin: 1.8rem auto 1.2rem; + text-align: center; +} + +.culinary-section-heading h2 { + margin-bottom: 0.7rem; + color: var(--forest); + font-size: clamp(1.8rem, 4vw, 2.8rem); +} + +.culinary-section-heading p:last-child { + margin: 0 auto; + max-width: 43rem; + line-height: 1.6; +} + +.culinary-course { + position: relative; + overflow: hidden; + margin-bottom: 1rem; + border: 1px solid rgba(31, 58, 47, 0.11); + border-radius: 22px; + background: rgba(255, 251, 245, 0.94); + box-shadow: 0 12px 30px rgba(31, 58, 47, 0.09); +} + +.culinary-course-body { + display: grid; + grid-template-columns: 1fr; + gap: 1rem; + padding: clamp(1.25rem, 4vw, 2.4rem); +} + +.culinary-course-main > img, +.culinary-course-image, +.culinary-image-placeholder { + display: block; + width: 100%; + height: clamp(14rem, 38vw, 30rem); + object-fit: cover; + object-position: center; +} + +.culinary-image-placeholder { + position: relative; + display: grid; + place-items: center; + overflow: hidden; + color: rgba(255, 255, 255, 0.92); + background: + linear-gradient(135deg, rgba(31, 58, 47, 0.86), rgba(109, 47, 61, 0.62)), + radial-gradient(circle at 70% 30%, rgba(242, 216, 166, 0.36), transparent 34%), + #344d42; +} + +.culinary-dessert-placeholder { + background: + linear-gradient(135deg, rgba(54, 25, 31, 0.88), rgba(109, 47, 61, 0.66)), + radial-gradient(circle at 72% 24%, rgba(242, 216, 166, 0.38), transparent 32%), + #522b35; +} + +.culinary-image-placeholder::before, +.culinary-image-placeholder::after { + content: ""; + position: absolute; + border: 1px solid rgba(242, 216, 166, 0.3); + border-radius: 50%; +} + +.culinary-image-placeholder::before { + width: 10rem; + height: 10rem; + transform: translate(-4rem, 3rem); +} + +.culinary-image-placeholder::after { + width: 15rem; + height: 15rem; + transform: translate(7rem, -5rem); +} + +.culinary-image-placeholder span { + position: relative; + z-index: 1; + padding: 0.55rem 0.9rem; + border: 1px solid rgba(255, 255, 255, 0.34); + border-radius: 999px; + background: rgba(18, 31, 24, 0.18); + font-size: 0.78rem; + font-weight: 700; + letter-spacing: 0.12em; + text-transform: uppercase; + backdrop-filter: blur(4px); +} + +.culinary-course-number { + color: rgba(178, 137, 70, 0.42); + font-family: "Playfair Display", serif; + font-size: clamp(2.2rem, 5vw, 4rem); + line-height: 0.9; + text-align: center; +} + +.culinary-course-icon { + display: flex; + justify-content: center; + color: rgba(178, 137, 70, 0.66); +} + +.culinary-course-icon svg { + width: clamp(2.8rem, 5vw, 4rem); + height: clamp(2.8rem, 5vw, 4rem); + fill: none; + stroke: currentColor; + stroke-width: 1.7; + stroke-linecap: round; + stroke-linejoin: round; +} + +.culinary-course-copy h3 { + margin-bottom: 0.6rem; + color: var(--forest); + font-size: clamp(1.45rem, 3vw, 2.25rem); +} + +.culinary-course-copy, +.culinary-menu-grid > div, +.culinary-dessert-grid > div, +.drink-card, +.wine-grid > div { + text-align: center; +} + +.culinary-course-copy > p:last-child { + margin-bottom: 0; + line-height: 1.6; +} + +.culinary-menu-grid, +.culinary-dessert-grid, +.wine-grid { + display: grid; + gap: 1rem; +} + +.culinary-menu-grid { + grid-template-columns: repeat(2, minmax(0, 1fr)); + margin-top: 1.2rem; +} + +.culinary-menu-grid > div, +.culinary-dessert-grid > div { + padding-top: 0.9rem; + border-top: 1px solid rgba(178, 137, 70, 0.3); +} + +.culinary-menu-grid h4, +.culinary-dessert-grid h4, +.wine-grid h4 { + margin: 0 0 0.45rem; + color: var(--burgundy); + font-family: "Playfair Display", serif; + font-size: 1.06rem; +} + +.culinary-menu-grid ul, +.culinary-dessert-grid ul, +.drink-card ul { + margin: 0; + padding: 0; + list-style: none; +} + +.culinary-menu-grid li, +.culinary-dessert-grid li, +.drink-card li { + padding: 0.18rem 0; + line-height: 1.45; +} + +.culinary-menu-grid li + li, +.culinary-dessert-grid li + li, +.drink-card li + li { + margin-top: 0.25rem; +} + +.culinary-dessert-grid { + grid-template-columns: repeat(2, minmax(0, 1fr)); + margin-top: 1.1rem; +} + +.culinary-dessert-grid p { + margin: 0; + line-height: 1.55; +} + +.drinks-section { + padding-top: 0.5rem; +} + +.drinks-hero { + min-height: 18rem; + display: flex; + align-items: flex-end; + justify-content: center; + padding: clamp(1.4rem, 4vw, 2.6rem); + background: var(--drinks-hero-image) center 58% / cover; +} + +.drinks-menu-card .drinks-hero { + min-height: 0; + height: clamp(14rem, 38vw, 30rem); + border: 0; + border-radius: 0; + box-shadow: none; +} + +.drinks-menu-card { + overflow: hidden; + margin-top: 0.5rem; + border: 1px solid rgba(31, 58, 47, 0.11); + border-radius: 22px; + background: rgba(255, 251, 245, 0.94); + box-shadow: 0 12px 30px rgba(31, 58, 47, 0.1); +} + +.drinks-menu-section { + padding: clamp(1.35rem, 4vw, 2.5rem); + text-align: center; +} + +.drinks-section-heading { + margin-bottom: 1rem; +} + +.drinks-course-list { + display: grid; + gap: 1rem; +} + +.drinks-course-list .culinary-course { + margin-bottom: 0; +} + +.drink-course ul { + margin: 0; + padding: 0; + list-style: none; +} + +.drink-course li { + line-height: 1.55; +} + +.drink-course li + li { + margin-top: 0.25rem; +} + +.drink-placeholder-refreshments { + background: + linear-gradient(135deg, rgba(45, 83, 67, 0.8), rgba(122, 154, 121, 0.58)), + radial-gradient(circle at 72% 24%, rgba(244, 226, 184, 0.4), transparent 34%), + #416653; +} + +.drink-placeholder-bar { + background: + linear-gradient(135deg, rgba(119, 75, 39, 0.8), rgba(178, 137, 70, 0.58)), + radial-gradient(circle at 70% 24%, rgba(255, 230, 179, 0.42), transparent 32%), + #79512f; +} + +.drink-placeholder-evening { + background: + linear-gradient(135deg, rgba(31, 39, 34, 0.94), rgba(109, 47, 61, 0.72)), + radial-gradient(circle at 72% 24%, rgba(224, 181, 105, 0.32), transparent 32%), + #252d29; +} + +.wine-selection-grid { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 1rem; + margin-top: 1.25rem; +} + +.wine-family, +.drink-pair-grid > section, +.evening-drinks-grid > section { + padding: 1rem; + border-top: 1px solid rgba(178, 137, 70, 0.3); + text-align: center; +} + +.wine-family h4, +.drink-pair-grid h4, +.evening-drinks-grid h4 { + margin: 0 0 0.75rem; + color: var(--burgundy); + font-family: "Playfair Display", serif; + font-size: 1.1rem; +} + +.wine-entry + .wine-entry { + margin-top: 1rem; + padding-top: 1rem; + border-top: 1px solid rgba(178, 137, 70, 0.18); +} + +.wine-estate { + margin: 0 0 0.15rem; + color: rgba(31, 26, 23, 0.68); + font-size: 0.88rem; + font-weight: 600; + letter-spacing: 0.06em; + text-transform: uppercase; +} + +.wine-entry h5 { + margin: 0; + color: var(--forest); + font-family: "Playfair Display", serif; + font-size: 1.18rem; +} + +.wine-style { + display: inline-block; + margin-top: 0.45rem; + padding: 0.18rem 0.55rem; + border: 1px solid rgba(178, 137, 70, 0.42); + border-radius: 999px; + color: var(--burgundy); + font-size: 0.78rem; + font-weight: 600; +} + +.drink-pair-grid, +.evening-drinks-grid { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 1rem; + margin-top: 1.2rem; +} + +.evening-drinks-grid p { + margin: 0; + line-height: 1.55; +} + +.cocktail-panel { + border-radius: 14px; + color: #fff; + background: linear-gradient(145deg, #273e34, #152820); +} + +.evening-drinks-grid .cocktail-panel h4, +.cocktail-panel .cocktail-time { + color: #f0d6a4; +} + +.cocktail-panel strong { + display: inline-block; + margin-bottom: 0.35rem; + font-family: "Playfair Display", serif; + font-size: 1.25rem; +} + +.drinks-menu-section + .drinks-menu-section, +.drinks-menu-columns, +.spirits-section { + border-top: 1px solid rgba(178, 137, 70, 0.3); +} + +.drinks-menu-section h3 { + margin-bottom: 0.75rem; + color: var(--forest); + font-size: clamp(1.45rem, 3vw, 2rem); +} + +.drinks-menu-section p { + margin: 0; + line-height: 1.5; +} + +.drinks-menu-section ul { + margin: 0; + padding: 0; + list-style: none; +} + +.drinks-menu-section li { + line-height: 1.55; +} + +.drinks-menu-section li + li { + margin-top: 0.25rem; +} + +.drinks-inline-list { + display: flex; + flex-wrap: wrap; + justify-content: center; + gap: 0.15rem 0; +} + +.drinks-inline-list li + li { + margin-top: 0; +} + +.drinks-inline-list li:not(:last-child)::after { + content: "·"; + display: inline-block; + padding: 0 0.65rem; + color: var(--gold); +} + +.wine-section { + background: + radial-gradient(circle at 50% 0%, rgba(178, 137, 70, 0.13), transparent 46%), + rgba(250, 244, 233, 0.55); +} + +.wine-section > h3 { + margin-bottom: 1.4rem; +} + +.wine-grid { + grid-template-columns: repeat(4, minmax(0, 1fr)); + gap: 0; +} + +.wine-grid > div { + padding: 0.3rem clamp(0.7rem, 2vw, 1.4rem); +} + +.wine-grid > div + div { + border-left: 1px solid rgba(178, 137, 70, 0.28); +} + +.wine-grid p + p { + margin-top: 0.4rem; +} + +.drinks-menu-columns { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); +} + +.drinks-menu-columns .drinks-menu-section + .drinks-menu-section { + border-top: 0; + border-left: 1px solid rgba(178, 137, 70, 0.3); +} + +.spirits-section p { + max-width: 42rem; + margin-inline: auto; +} + +.cocktail-card { + color: #fff; + border-top: 0; + background: linear-gradient(145deg, #273e34, #152820); +} + +.drinks-menu-section.cocktail-card h3, +.cocktail-card .cocktail-time { + color: #f0d6a4; +} + +.cocktail-card strong { + display: inline-block; + margin-bottom: 0.35rem; + font-family: "Playfair Display", serif; + font-size: 1.3rem; +} + +@media (max-width: 640px) { + .culinary-course-body { + grid-template-columns: 1fr; + } + + .culinary-course-number { + font-size: 2.4rem; + } + + .culinary-menu-grid, + .culinary-dessert-grid, + .wine-grid, + .wine-selection-grid, + .drink-pair-grid, + .evening-drinks-grid { + grid-template-columns: 1fr; + } + + .wine-grid > div { + padding: 0.85rem 0; + } + + .wine-grid > div + div { + border-top: 1px solid rgba(178, 137, 70, 0.24); + border-left: 0; + } + + .drinks-menu-columns { + grid-template-columns: 1fr; + } + + .drinks-menu-columns .drinks-menu-section + .drinks-menu-section { + border-top: 1px solid rgba(178, 137, 70, 0.3); + border-left: 0; + } +} + +@media (prefers-reduced-motion: reduce) { + .culinary-switch-slider, + .culinary-jump-nav a { + transition: none; + } +} + @media (min-width: 768px) { .hero-overlay { max-width: 54ch; @@ -2256,7 +2930,7 @@ body.has-route-modal { max-width: 1040px; margin-left: auto; margin-right: auto; - min-height: 68vh; + min-height: clamp(520px, 62vh, 700px); padding: 2.5rem; align-items: flex-end; background-image: @@ -2268,8 +2942,8 @@ body.has-route-modal { } .hero-overlay { - max-width: 33rem; - margin: 0 0 1rem 1rem; + max-width: 31rem; + margin: 0 0 1.15rem 1rem; padding: 0; text-align: left; background: none; @@ -2278,16 +2952,17 @@ body.has-route-modal { } .hero-overlay h1 { - font-size: clamp(3rem, 4vw, 4.2rem); - line-height: 1; + font-size: clamp(2.45rem, 3.1vw, 3.45rem); + line-height: 1.04; margin-bottom: 1.05rem; - max-width: 11ch; + max-width: 14ch; + text-wrap: balance; } .hero-overlay p { - font-size: 1.2rem; + font-size: 1.1rem; line-height: 1.55; - max-width: 30ch; + max-width: 31ch; color: rgba(255, 255, 255, 0.94); } diff --git a/backend/templates/base.html b/backend/templates/base.html index eab7f78..e838569 100644 --- a/backend/templates/base.html +++ b/backend/templates/base.html @@ -7,7 +7,7 @@ - +
diff --git a/backend/templates/info.html b/backend/templates/info.html index 1280b90..033d5b3 100644 --- a/backend/templates/info.html +++ b/backend/templates/info.html @@ -12,32 +12,32 @@ {% if page == 'schedule' %}

{{ t('schedule_intro') }}

-
+

16:00

{{ t('schedule_arrival_title') }}

{{ t('schedule_arrival_text') }}

-
+

16:30

{{ t('schedule_ceremony_title') }}

{{ t('schedule_ceremony_text') }}

-
+

{{ t('schedule_reception_time') }}

{{ t('schedule_reception_title') }}

{{ t('schedule_reception_text') }}

-
+

19:00

{{ t('schedule_buffet_title') }}

{{ t('schedule_buffet_text') }}

-
+

{{ t('schedule_cake_time') }}

{{ t('schedule_cake_title') }}

{{ t('schedule_cake_text') }}

-
+

{{ t('schedule_afterwards_time') }}

{{ t('schedule_party_title') }}

{{ t('schedule_party_text') }}

@@ -186,7 +186,269 @@ })(); {% elif page == 'taxi' %} -

{{ t('taxi_text') }}

+
+ + +
+

{{ t('culinary_title') }}

+

{{ t('culinary_intro') }}

+
+ + + + + +
+
+

{{ t('culinary_drinks') }}

+

{{ t('drinks_during_celebration') }}

+

{{ t('drinks_intro') }}

+
+
+
+ {{ t('drinks_refreshments_image_alt') }} +
+ +
+

{{ t('drinks_refreshments') }}

+

{{ t('drinks_non_alcoholic') }}

+
    +
  • {{ t('drinks_water') }}
  • {{ t('drinks_soft') }}
  • {{ t('drinks_juices') }}
  • +
+
+
+
+ +
+ {{ t('drinks_image_alt') }} +
+ +
+

{{ t('drinks_wine_beer') }}

+

{{ t('drinks_wine_selection') }}

+
+
+

{{ t('drinks_white_wine') }}

+

Jung Dahlen

Einklang Riesling
{{ t('drinks_off_dry') }}
+

Domdechant Werner

Riesling
{{ t('drinks_dry') }}
+
+
+

{{ t('drinks_rose_wine') }}

+

Jung Dahlen

Rosé Sinfonie
+
+
+

{{ t('drinks_red_wine') }}

+

Jung Dahlen

Pinot Noir
+
+
+

{{ t('drinks_beer') }}

+

{{ t('drinks_beer_selection') }}

+
+
+
+
+
+ +
+ {{ t('drinks_bar_image_alt') }} +
+ +
+

{{ t('drinks_aperitif_spirits') }}

+

{{ t('drinks_bar_classics') }}

+
+

{{ t('drinks_aperitif') }}

  • Aperol Spritz
  • Lillet Wildberry
  • Hugo
  • {{ t('drinks_sparkling_wine') }}
+

{{ t('drinks_spirits') }}

{{ t('drinks_spirits_text') }}

+
+
+
+
+ +
+ {{ t('drinks_evening_image_alt') }} +
+ +
+

{{ t('drinks_evening_bar') }}

+

{{ t('drinks_from_22') }}

+

{{ t('drinks_longdrinks_cocktails') }}

+
+

{{ t('drinks_longdrinks') }}

  • Gin & Tonic
  • Rum & Cola
  • Vodka Orange
  • Whisky & Cola
+

{{ t('drinks_cocktails') }}

  • Mojito Razz
  • Sex on the Beach
  • Bahama Mama
+
+
+
+
+
+
+
+ {% elif page == 'gifts' %}

{{ t('gifts_teaser') }}

diff --git a/data/db/app.sqlite3 b/data/db/app.sqlite3 index a682f8d..2e4e476 100644 Binary files a/data/db/app.sqlite3 and b/data/db/app.sqlite3 differ diff --git a/docker-compose.yml b/docker-compose.yml index 181ca2e..423390d 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -8,7 +8,8 @@ services: volumes: - ./frontend/nginx/default.conf:/etc/nginx/conf.d/default.conf:ro depends_on: - - backend + backend: + condition: service_healthy backend: build: ./backend @@ -33,3 +34,9 @@ services: - LOCATION_ADDRESS=${LOCATION_ADDRESS:-Mainzer Str. 22, 65239 Hochheim am Main} - LOCATION_WEBSITE_URL=${LOCATION_WEBSITE_URL:-https://hochheimerterrasse.de/} - GOOGLE_MAPS_EMBED_URL=${GOOGLE_MAPS_EMBED_URL:-https://www.google.com/maps?q=Hochheimer+Terrasse+Mainzer+Str.+22+65239+Hochheim+am+Main&output=embed} + healthcheck: + test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://127.0.0.1:8000/health', timeout=2)"] + interval: 10s + timeout: 3s + retries: 5 + start_period: 15s diff --git a/frontend/nginx/default.conf b/frontend/nginx/default.conf index 30f3058..8751da6 100644 --- a/frontend/nginx/default.conf +++ b/frontend/nginx/default.conf @@ -4,18 +4,18 @@ server { client_max_body_size 256M; client_body_timeout 300s; - send_timeout 300s; + send_timeout 120s; location / { proxy_pass http://backend:8000; proxy_http_version 1.1; - proxy_request_buffering off; + proxy_request_buffering on; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; - proxy_read_timeout 300s; - proxy_send_timeout 300s; - proxy_connect_timeout 60s; + proxy_read_timeout 120s; + proxy_send_timeout 120s; + proxy_connect_timeout 10s; } }