This commit is contained in:
2026-07-12 18:30:45 +00:00
parent 430a730cf0
commit 20ba71465f
13 changed files with 231 additions and 83 deletions

View File

@@ -28,12 +28,12 @@ app.config["SECRET_KEY"] = os.environ.get("SECRET_KEY", "change-me-in-production
app.config["DB_PATH"] = os.environ.get("DB_PATH", str(base_dir / "app.sqlite3"))
app.config["UPLOAD_FOLDER"] = os.environ.get("UPLOAD_FOLDER", str(base_dir / "uploads"))
app.config["MAX_CONTENT_LENGTH"] = int(os.environ.get("MAX_UPLOAD_BYTES", str(64 * 1024 * 1024)))
app.config["LOCATION_NAME"] = os.environ.get("LOCATION_NAME", "Schlossgarten Venue")
app.config["LOCATION_ADDRESS"] = os.environ.get("LOCATION_ADDRESS", "Musterstraße 12, 12345 Musterstadt")
app.config["LOCATION_WEBSITE_URL"] = os.environ.get("LOCATION_WEBSITE_URL", "https://example.com/location")
app.config["LOCATION_NAME"] = os.environ.get("LOCATION_NAME", "Hochheimer Terrasse")
app.config["LOCATION_ADDRESS"] = os.environ.get("LOCATION_ADDRESS", "Mainzer Str. 22, 65239 Hochheim am Main")
app.config["LOCATION_WEBSITE_URL"] = os.environ.get("LOCATION_WEBSITE_URL", "https://hochheimerterrasse.de/")
app.config["GOOGLE_MAPS_EMBED_URL"] = os.environ.get(
"GOOGLE_MAPS_EMBED_URL",
"https://www.google.com/maps/embed?pb=!1m18!1m12!1m3!1d0",
"https://www.google.com/maps?q=Hochheimer+Terrasse+Mainzer+Str.+22+65239+Hochheim+am+Main&output=embed",
)
app.config["WEDDING_DATE"] = os.environ.get("WEDDING_DATE", "")
app.config["WEDDING_COUNTDOWN_ISO"] = os.environ.get("WEDDING_COUNTDOWN_ISO", "")
@@ -52,6 +52,8 @@ ALLOWED_MIME_TYPES = {
"image/heif-sequence",
}
LOCATION_VIDEO_EXTENSIONS = {".mp4", ".webm", ".mov", ".m4v"}
DEFAULT_INVITATION_GROUPS = [
{
"name": "Bubus",
@@ -218,48 +220,48 @@ HOTEL_RECOMMENDATIONS = [
"name": "Parkhotel Sonnenberg",
"address": "Friedrichstraße 65, 65343 Eltville am Rhein",
"website_url": "https://parkhotel-sonnenberg.com/",
"drive_minutes": "5",
"walk_minutes": "27",
"drive_minutes": "25",
"walk_minutes": None,
},
{
"id": "hotel_frankenbach",
"name": "Hotel Frankenbach",
"address": "Wilhelmstraße 13, 65343 Eltville am Rhein",
"website_url": "https://www.hotel-frankenbach.de/",
"drive_minutes": "5",
"walk_minutes": "19",
"drive_minutes": "25",
"walk_minutes": None,
},
{
"id": "spritzenhaus",
"name": "Das Spritzenhaus",
"address": "Platz von Montrichard 1, 65343 Eltville am Rhein",
"website_url": "https://www.das-spritzenhaus.de/",
"drive_minutes": "5",
"walk_minutes": "22",
"drive_minutes": "25",
"walk_minutes": None,
},
{
"id": "kronenschloesschen",
"name": "Hotel Kronenschlösschen",
"address": "Rheinallee, 65347 Eltville-Hattenheim",
"website_url": "https://www.kronenschloesschen.de/",
"drive_minutes": "6",
"walk_minutes": "63",
"drive_minutes": "30",
"walk_minutes": None,
},
{
"id": "kloster_eberbach",
"name": "Kloster Eberbach",
"address": "Kloster-Eberbach-Straße 1, 65346 Eltville am Rhein",
"website_url": "https://www.kloster-eberbach.de/",
"drive_minutes": "10",
"walk_minutes": "69",
"drive_minutes": "30",
"walk_minutes": None,
},
{
"id": "weinhaus_engel",
"name": "Weinhaus & Hotel Engel",
"address": "Hauptstraße 10, 65345 Eltville-Rauenthal",
"website_url": "https://weinhaus-engel.com/rauenthal/",
"drive_minutes": "10",
"walk_minutes": "49",
"drive_minutes": "25",
"walk_minutes": None,
},
]
@@ -306,12 +308,12 @@ TEXTS = {
"location": "Location",
"gifts": "Geschenke",
"visit_location": "Zur Location-Webseite",
"location_story_title": "Klostermühle in den Weinbergen",
"location_story_text": "Die Klostermühle liegt mitten in den Weinbergen und verbindet ein besonderes Weingut-Ambiente mit saisonaler Küche. Euch erwartet eine entspannte Atmosphäre zwischen Natur, gutem Essen und einem wunderschönen Rahmen für unseren Hochzeitstag.",
"location_story_title": "Rheingauer Flair auf der Hochheimer Terrasse",
"location_story_text": "Die Hochheimer Terrasse liegt in der historischen Altstadt von Hochheim und verbindet Rheingauer Atmosphäre mit einem eleganten Rahmen für unseren Hochzeitstag. Euch erwartet ein besonderer Ort zwischen Mainz, Wiesbaden und den Weinbergen, an dem wir gemeinsam feiern möchten.",
"location_map_intro": "Damit ihr problemlos zur Location findet, könnt ihr direkt von hier zu Google Maps wechseln oder eine Route von eurem aktuellen Standort aus starten.",
"route_from_current": "Route ab aktuellem Standort",
"route_location_denied": "Standortfreigabe abgelehnt. Es wurde keine Route mit deinem Standort erstellt.",
"route_location_unavailable": "Standort ist in diesem Browser derzeit nicht verfügbar.",
"maps_privacy_notice": "Zur Anzeige der Karte werden Daten an Google übertragen.",
"maps_load_button": "Google Maps anzeigen",
"privacy": "Datenschutz",
"imprint": "Impressum",
@@ -328,16 +330,17 @@ TEXTS = {
"schedule_arrival_title": "Ankunft",
"schedule_arrival_text": "Wir freuen uns auf eure Ankunft.",
"schedule_ceremony_title": "Trauzeremonie",
"schedule_ceremony_text": "Trauzeremonie im Rosengarten und Fotos.",
"schedule_ceremony_text": "Trauzeremonie im Hof und Fotos.",
"schedule_reception_title": "Sektempfang & Häppchen",
"schedule_reception_text": "Sektempfang im Hof der Klostermühle.",
"schedule_reception_text": "Sektempfang im Hof der Hochheimer Terrasse.",
"schedule_buffet_title": "Buffet-Eröffnung",
"schedule_buffet_text": "Das Buffet wird eröffnet, danach folgen die Brautpaarspiele. Später gibt es zusätzlich Käseplatten.",
"schedule_cake_title": "Tortenanschnitt",
"schedule_cake_time": "Am Abend",
"schedule_cake_text": "Gemeinsamer Tortenmoment am Abend.",
"schedule_afterwards_time": "Anschließend",
"schedule_party_title": "Party",
"schedule_party_text": "Wir feiern gemeinsam bis 03:00 Uhr.",
"schedule_party_text": "Wir feiern gemeinsam bis 03:30 Uhr.",
"hotels_text": "Empfehlungen folgen. Bitte frühzeitig buchen.",
"hotels_intro": "Hier findet ihr Hotels in der Nähe. Bitte bucht frühzeitig und schaut euch die aktuellen Preise direkt auf den Hotel-Webseiten an.",
"hotel_visit_website": "Zur Hotel-Webseite",
@@ -349,6 +352,7 @@ TEXTS = {
"hotel_route_open_maps": "In Google Maps öffnen",
"hotel_drive_badge": "Auto ca. {minutes} Min",
"hotel_walk_badge": "Zu Fuß ca. {minutes} Min",
"hotel_walk_not_recommended": "Zu Fuß nicht empfohlen",
"hotel_parkhotel_sonnenberg_desc": "Ruhiges Hotel mit Blick über Eltville.",
"hotel_hotel_frankenbach_desc": "Zentral in Eltville, gute Anbindung zur Location.",
"hotel_spritzenhaus_desc": "Historisches Haus am Rhein mit modernen Zimmern.",
@@ -447,12 +451,12 @@ TEXTS = {
"location": "Location",
"gifts": "Gifts",
"visit_location": "Visit location website",
"location_story_title": "Klostermuehle in the vineyards",
"location_story_text": "Klostermuehle is surrounded by vineyards and combines a unique winery atmosphere with seasonal cuisine. Expect a relaxed setting with nature, great food, and a beautiful backdrop for our wedding day.",
"location_story_title": "Rheingau flair at Hochheimer Terrasse",
"location_story_text": "Hochheimer Terrasse is located in Hochheim's historic old town and combines Rheingau atmosphere with an elegant setting for our wedding day. It is a special place between Mainz, Wiesbaden, and the vineyards where we cannot wait to celebrate with you.",
"location_map_intro": "To help you find the location easily, you can open Google Maps directly from here or start a route from your current location.",
"route_from_current": "Route from current location",
"route_location_denied": "Location access was denied. No route with your location was created.",
"route_location_unavailable": "Location is currently unavailable in this browser.",
"maps_privacy_notice": "To display the map, data will be transferred to Google.",
"maps_load_button": "Show Google Maps",
"privacy": "Privacy",
"imprint": "Imprint",
@@ -469,16 +473,17 @@ TEXTS = {
"schedule_arrival_title": "Arrival",
"schedule_arrival_text": "We look forward to welcoming you.",
"schedule_ceremony_title": "Wedding Ceremony",
"schedule_ceremony_text": "Wedding ceremony in the rose garden and photos.",
"schedule_ceremony_text": "Wedding ceremony in the courtyard and photos.",
"schedule_reception_title": "Sparkling Reception & Snacks",
"schedule_reception_text": "Sparkling reception in the courtyard of the Klostermuehle.",
"schedule_reception_text": "Sparkling reception in the courtyard of Hochheimer Terrasse.",
"schedule_buffet_title": "Buffet Opening",
"schedule_buffet_text": "The buffet opens, followed by wedding games. Cheese platters will be served later.",
"schedule_cake_title": "Cake Cutting",
"schedule_cake_time": "In the evening",
"schedule_cake_text": "Cake cutting together in the evening.",
"schedule_afterwards_time": "Afterwards",
"schedule_party_title": "Party",
"schedule_party_text": "We celebrate together until 3:00 AM.",
"schedule_party_text": "We celebrate together until 3:30 AM.",
"hotels_text": "Recommendations will follow. Please book early.",
"hotels_intro": "Here are hotel options nearby. Please book early and check current rates directly on each hotel website.",
"hotel_visit_website": "Visit hotel website",
@@ -490,6 +495,7 @@ TEXTS = {
"hotel_route_open_maps": "Open in Google Maps",
"hotel_drive_badge": "By car about {minutes} min",
"hotel_walk_badge": "On foot about {minutes} min",
"hotel_walk_not_recommended": "Walking not recommended",
"hotel_parkhotel_sonnenberg_desc": "Quiet hotel with a view over Eltville.",
"hotel_hotel_frankenbach_desc": "Central in Eltville with good access to the venue.",
"hotel_spritzenhaus_desc": "Historic house by the Rhine with modern rooms.",
@@ -663,7 +669,11 @@ def get_hotels_for_page() -> list[dict]:
"website_url": entry["website_url"],
"description": t(f"hotel_{entry['id']}_desc"),
"drive_badge": t("hotel_drive_badge").format(minutes=entry["drive_minutes"]),
"walk_badge": t("hotel_walk_badge").format(minutes=entry["walk_minutes"]),
"walk_badge": (
t("hotel_walk_badge").format(minutes=entry["walk_minutes"])
if entry["walk_minutes"]
else t("hotel_walk_not_recommended")
),
"drive_route_url": build_google_maps_directions(entry["address"], "driving"),
"walk_route_url": build_google_maps_directions(entry["address"], "walking"),
"drive_route_embed_url": build_google_maps_route_embed(entry["address"], "driving"),
@@ -673,10 +683,22 @@ def get_hotels_for_page() -> list[dict]:
return hotels
def get_location_video_asset() -> str | None:
location_dir = base_dir / "static" / "assets" / "location"
if not location_dir.exists():
return None
for asset in sorted(location_dir.iterdir()):
if asset.is_file() and asset.suffix.lower() in LOCATION_VIDEO_EXTENSIONS:
return f"assets/location/{asset.name}"
return None
@app.context_processor
def inject_common() -> dict:
role = session.get("role", "guest")
group_id = session.get("group_id")
location_video_asset = get_location_video_asset()
return {
"t": t,
"lang": get_lang(),
@@ -691,6 +713,7 @@ def inject_common() -> dict:
"location_website_url": app.config["LOCATION_WEBSITE_URL"],
"location_route_url": build_google_maps_destination(app.config["LOCATION_ADDRESS"], "driving"),
"google_maps_embed_url": app.config["GOOGLE_MAPS_EMBED_URL"],
"location_video_url": url_for("static", filename=location_video_asset) if location_video_asset else None,
"wedding_date": app.config["WEDDING_DATE"],
"wedding_countdown_iso": get_wedding_countdown_iso(),
"hero_image_url": url_for("static", filename=get_hero_image_asset()),

Binary file not shown.

After

Width:  |  Height:  |  Size: 96 KiB

View File

@@ -1,42 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1600 700" role="img" aria-label="Kartenvorschau">
<defs>
<linearGradient id="land" x1="0" x2="1" y1="0" y2="1">
<stop offset="0" stop-color="#d8e6de" />
<stop offset="1" stop-color="#cadcd2" />
</linearGradient>
<filter id="shadow" x="-10%" y="-10%" width="120%" height="120%">
<feDropShadow dx="0" dy="4" stdDeviation="6" flood-color="#1f2a24" flood-opacity="0.16"/>
</filter>
</defs>
<rect width="1600" height="700" fill="url(#land)"/>
<path d="M0 420 C120 380, 200 420, 300 390 C420 360, 530 440, 670 410 C770 390, 860 350, 980 380 C1110 420, 1210 370, 1330 410 C1430 440, 1500 410, 1600 430 L1600 700 L0 700 Z" fill="#b8d2c2" opacity="0.8"/>
<path d="M40 80 L520 560" stroke="#f5f5f2" stroke-width="28" stroke-linecap="round"/>
<path d="M580 50 L980 650" stroke="#f7f7f4" stroke-width="26" stroke-linecap="round"/>
<path d="M1050 70 L1540 520" stroke="#f4f5f2" stroke-width="24" stroke-linecap="round"/>
<path d="M160 640 L620 170" stroke="#f2f4ef" stroke-width="20" stroke-linecap="round"/>
<path d="M30 120 L500 590" stroke="#9eb8aa" stroke-width="5" stroke-dasharray="12 14"/>
<path d="M600 90 L1000 670" stroke="#9fb9ab" stroke-width="5" stroke-dasharray="12 14"/>
<path d="M1070 100 L1555 540" stroke="#9db6a8" stroke-width="5" stroke-dasharray="12 14"/>
<path d="M180 655 L640 185" stroke="#9db7a9" stroke-width="5" stroke-dasharray="12 14"/>
<circle cx="1080" cy="240" r="55" fill="#b0cbba" opacity="0.9"/>
<circle cx="1200" cy="500" r="48" fill="#abc6b5" opacity="0.85"/>
<circle cx="860" cy="540" r="45" fill="#aec8b8" opacity="0.88"/>
<g filter="url(#shadow)">
<rect x="105" y="78" width="560" height="168" rx="16" fill="#ffffff" opacity="0.96"/>
<text x="135" y="125" font-size="34" font-family="Arial, Helvetica, sans-serif" fill="#20372d" font-weight="700">Klostermühle Kiedrich</text>
<text x="135" y="168" font-size="30" font-family="Arial, Helvetica, sans-serif" fill="#385246">An d. Klostermühle 3, 65399 Kiedrich</text>
<text x="135" y="210" font-size="28" font-family="Arial, Helvetica, sans-serif" fill="#4f6a5d">Kartenvorschau</text>
</g>
<g transform="translate(980 320)">
<circle cx="0" cy="0" r="34" fill="#c83c38"/>
<circle cx="0" cy="0" r="14" fill="#f8d9d7"/>
<path d="M0 34 L-17 98 L17 98 Z" fill="#c83c38"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 666 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 751 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 960 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 766 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 964 KiB

View File

@@ -1395,9 +1395,94 @@ body.has-route-modal {
}
}
.location-video-card {
width: min(100%, 920px);
margin: 0 auto 1.1rem;
overflow: hidden;
border: 1px solid rgba(178, 137, 70, 0.24);
border-radius: 16px;
background: #1f3a2f;
box-shadow:
0 14px 30px rgba(31, 58, 47, 0.16),
0 1px 0 rgba(255, 255, 255, 0.22) inset;
}
.location-video {
display: block;
width: 100%;
aspect-ratio: 16 / 9;
background: #1f3a2f;
object-fit: cover;
}
.location-slideshow {
position: relative;
isolation: isolate;
width: min(100%, 920px);
aspect-ratio: 16 / 9;
margin: 1rem auto 1.1rem;
overflow: hidden;
border: 1px solid rgba(178, 137, 70, 0.24);
border-radius: 16px;
background: #eadfce;
box-shadow:
0 14px 30px rgba(31, 58, 47, 0.14),
0 1px 0 rgba(255, 255, 255, 0.64) inset;
}
.location-slideshow::after {
content: "";
position: absolute;
inset: 0;
z-index: 2;
pointer-events: none;
background:
linear-gradient(180deg, rgba(22, 34, 28, 0.06), transparent 44%),
linear-gradient(0deg, rgba(22, 34, 28, 0.12), transparent 34%);
}
.location-slide {
position: absolute;
inset: 0;
z-index: 1;
width: 100%;
height: 100%;
object-fit: cover;
opacity: 0;
transform: scale(1.03);
animation: location-slide-fade 36s ease-in-out infinite;
animation-delay: calc(var(--slide-index) * 6s);
}
@keyframes location-slide-fade {
0% {
opacity: 0;
transform: scale(1.03);
}
4%,
16% {
opacity: 1;
}
22%,
100% {
opacity: 0;
transform: scale(1.08);
}
}
.location-map-intro {
max-width: 58ch;
margin: 1rem auto 0.75rem;
color: rgba(31, 26, 23, 0.82);
line-height: 1.5;
text-align: center;
}
.map-consent {
display: grid;
gap: 0.7rem;
width: min(100%, 920px);
margin: 0 auto;
padding: 0.9rem;
border: 1px solid rgba(31, 58, 47, 0.12);
border-radius: 12px;
@@ -1413,17 +1498,18 @@ body.has-route-modal {
.map-preview {
width: 100%;
aspect-ratio: 16 / 7;
aspect-ratio: 4 / 3;
min-height: 0;
overflow: hidden;
border: 1px solid rgba(39, 66, 53, 0.16);
border-radius: 12px;
cursor: pointer;
background-color: rgba(221, 230, 225, 0.95);
background-image:
linear-gradient(180deg, rgba(28, 45, 37, 0.22), rgba(28, 45, 37, 0.22)),
linear-gradient(180deg, rgba(22, 34, 28, 0.12), rgba(22, 34, 28, 0.2)),
var(--map-preview-image);
background-size: contain;
background-position: top left;
background-size: cover;
background-position: center;
background-repeat: no-repeat;
display: grid;
place-items: center;
@@ -1440,9 +1526,11 @@ body.has-route-modal {
.map-preview-overlay {
font-weight: 700;
color: #fff;
background: rgba(39, 66, 53, 0.9);
background: rgba(31, 58, 47, 0.9);
border: 1px solid rgba(255, 255, 255, 0.26);
border-radius: 999px;
padding: 0.48rem 0.82rem;
box-shadow: 0 8px 18px rgba(18, 31, 24, 0.2);
padding: 0.58rem 0.95rem;
}
.map-embed-target:not(:empty) {
@@ -1497,6 +1585,11 @@ body.has-route-modal {
justify-content: center;
}
.location-slideshow {
aspect-ratio: 4 / 3;
border-radius: 13px;
}
.dashboard-grid {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
@@ -1555,6 +1648,15 @@ body.has-route-modal {
animation: none;
opacity: 0;
}
.location-slide {
animation: none;
}
.location-slide:first-child {
opacity: 1;
transform: none;
}
}
@media (max-width: 380px) {

View File

@@ -33,7 +33,7 @@
<p>{{ t('schedule_buffet_text') }}</p>
</article>
<article class="schedule-item" style="--schedule-card-image: url('{{ url_for('static', filename='assets/kuchen.png') }}'); --schedule-card-position: center 48%;">
<p class="schedule-time">22:00</p>
<p class="schedule-time">{{ t('schedule_cake_time') }}</p>
<h2>{{ t('schedule_cake_title') }}</h2>
<p>{{ t('schedule_cake_text') }}</p>
</article>
@@ -190,8 +190,72 @@
})();
</script>
{% elif page == 'location' %}
{% if location_video_url %}
<div class="location-video-card">
<video
class="location-video"
controls
preload="metadata"
playsinline
poster="{{ url_for('static', filename='assets/location/hochheim.png') }}"
>
<source src="{{ location_video_url }}">
</video>
</div>
{% endif %}
<h2>{{ t('location_story_title') }}</h2>
<p>{{ t('location_story_text') }}</p>
<div class="location-slideshow" aria-label="{{ location_name }}">
<img
class="location-slide"
src="{{ url_for('static', filename='assets/location/hochheim.png') }}"
alt="{{ location_name }}"
loading="eager"
decoding="async"
style="--slide-index: 0;"
>
<img
class="location-slide"
src="{{ url_for('static', filename='assets/location/draußen.png') }}"
alt="{{ location_name }}"
loading="lazy"
decoding="async"
style="--slide-index: 1;"
>
<img
class="location-slide"
src="{{ url_for('static', filename='assets/location/trausaal.png') }}"
alt="{{ location_name }}"
loading="lazy"
decoding="async"
style="--slide-index: 2;"
>
<img
class="location-slide"
src="{{ url_for('static', filename='assets/location/gewölbe.png') }}"
alt="{{ location_name }}"
loading="lazy"
decoding="async"
style="--slide-index: 3;"
>
<img
class="location-slide"
src="{{ url_for('static', filename='assets/location/thresen.png') }}"
alt="{{ location_name }}"
loading="lazy"
decoding="async"
style="--slide-index: 4;"
>
<img
class="location-slide"
src="{{ url_for('static', filename='assets/location/essen1.png') }}"
alt="{{ location_name }}"
loading="lazy"
decoding="async"
style="--slide-index: 5;"
>
</div>
<p class="location-map-intro">{{ t('location_map_intro') }}</p>
<div
class="map-wrap map-consent"
data-map-consent
@@ -204,7 +268,7 @@
data-map-load
aria-label="{{ t('maps_load_button') }}"
title="{{ t('maps_load_button') }}"
style="--map-preview-image: url('{{ url_for('static', filename='assets/location-map-preview.svg') }}');"
style="--map-preview-image: url('{{ url_for('static', filename='assets/location-map-preview.jpeg') }}');"
>
<span class="map-preview-overlay">{{ t('maps_load_button') }}</span>
</button>
@@ -233,6 +297,7 @@
if (loaded) return;
const iframe = document.createElement("iframe");
iframe.src = src;
iframe.title = {{ location_name|tojson }};
iframe.loading = "lazy";
iframe.referrerPolicy = "no-referrer-when-downgrade";
iframe.allowFullscreen = true;

Binary file not shown.

View File

@@ -29,7 +29,7 @@ services:
- WEDDING_TIMEZONE=${WEDDING_TIMEZONE:-Europe/Berlin}
- SECRET_KEY=${SECRET_KEY:-change-me-in-production}
- MAX_UPLOAD_BYTES=${MAX_UPLOAD_BYTES:-268435456}
- LOCATION_NAME=${LOCATION_NAME:-Klostermühle}
- LOCATION_ADDRESS=${LOCATION_ADDRESS:-An d. Klostermühle 3, 65399 Kiedrich}
- LOCATION_WEBSITE_URL=${LOCATION_WEBSITE_URL:-https://www.klostermuehle.de/}
- GOOGLE_MAPS_EMBED_URL=${GOOGLE_MAPS_EMBED_URL:-https://www.google.com/maps?q=Klostermuehle+Kiedrich+Eltville&output=embed}
- LOCATION_NAME=${LOCATION_NAME:-Hochheimer Terrasse}
- 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}