new feat übersetzer und faq

This commit is contained in:
2026-07-24 15:16:10 +00:00
parent 11b0d20c42
commit 45b37597a9
14 changed files with 1205 additions and 51 deletions

View File

@@ -7,62 +7,82 @@
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link href="https://fonts.googleapis.com/css2?family=Playfair+Display:wght@600;700&family=Source+Sans+3:wght@400;500;600&display=swap" rel="stylesheet" />
<link rel="stylesheet" href="{{ url_for('static', filename='styles.css', v='20260321a') }}" />
<link rel="stylesheet" href="{{ url_for('static', filename='styles.css', v='20260724v') }}" />
</head>
<body>
<header class="topbar">
<div>
<a class="brand" href="{{ url_for('welcome') if guest_name else url_for('landing') }}">{{ t('brand') }}</a>
</div>
<div class="toolbar">
<form method="post" action="{{ url_for('set_lang', code='de') }}">
<button class="btn btn-ghost" type="submit">DE</button>
</form>
<form method="post" action="{{ url_for('set_lang', code='en') }}">
<button class="btn btn-ghost" type="submit">EN</button>
</form>
{% if guest_name %}
<div
class="toolbar-timer"
data-countdown-target="{{ wedding_countdown_iso }}"
data-countdown-started="{{ t('countdown_started') }}"
>
<div class="toolbar {{ 'toolbar-authenticated' if guest_name else 'toolbar-login' }}">
<div class="toolbar-row toolbar-row-top">
<form method="post" action="{{ url_for('set_lang', code='de') }}">
<button class="btn btn-ghost" type="submit">DE</button>
</form>
<form method="post" action="{{ url_for('set_lang', code='en') }}">
<button class="btn btn-ghost" type="submit">EN</button>
</form>
{% if guest_name %}
<button
class="btn btn-ghost toolbar-nav-btn toolbar-timer-btn"
class="btn btn-ghost toolbar-nav-btn toolbar-translate-btn"
type="button"
aria-label="{{ t('countdown_button_label') }}"
title="{{ t('countdown_button_label') }}"
data-countdown-toggle
aria-expanded="false"
aria-label="{{ t('translator') }}"
title="{{ t('translator') }}"
data-translator-open
>
<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false">
<path d="M15 2H9v2h6V2zm-3 4a8 8 0 1 0 8 8 8 8 0 0 0-8-8zm3.4 11.4L11 13V8h2v4.2l3.8 3.8z" />
<path d="M12 5.5A8.4 8.4 0 0 0 6.5 3.4 8.6 8.6 0 0 0 2 4.7v14.1a.75.75 0 0 0 1.02.7A8.1 8.1 0 0 1 6.5 18.6a7.1 7.1 0 0 1 4.98 2 .75.75 0 0 0 1.04 0 7.1 7.1 0 0 1 4.98-2 8.1 8.1 0 0 1 3.48.9.75.75 0 0 0 1.02-.7V4.7a8.6 8.6 0 0 0-4.5-1.3A8.4 8.4 0 0 0 12 5.5zm-1 12.9a8.7 8.7 0 0 0-4.5-1.3 9.6 9.6 0 0 0-3 .5V5.7a7.4 7.4 0 0 1 3-.8A6.9 6.9 0 0 1 11 6.7zm9.5-.8a9.6 9.6 0 0 0-3-.5 8.7 8.7 0 0 0-4.5 1.3V6.7a6.9 6.9 0 0 1 4.5-1.8 7.4 7.4 0 0 1 3 .8z" />
</svg>
</button>
<div class="toolbar-timer-popover" data-countdown-popover hidden>
<p class="toolbar-timer-label">{{ t('countdown_until') }}</p>
<div class="toolbar-timer-grid" data-countdown-grid>
<div class="toolbar-timer-unit">
<span class="toolbar-timer-value" data-countdown-days>0</span>
<span class="toolbar-timer-unit-label">{{ t('countdown_days') }}</span>
</div>
<div class="toolbar-timer-unit">
<span class="toolbar-timer-value" data-countdown-hours>00</span>
<span class="toolbar-timer-unit-label">{{ t('countdown_hours') }}</span>
</div>
<div class="toolbar-timer-unit">
<span class="toolbar-timer-value" data-countdown-minutes>00</span>
<span class="toolbar-timer-unit-label">{{ t('countdown_minutes') }}</span>
</div>
<div class="toolbar-timer-unit">
<span class="toolbar-timer-value" data-countdown-seconds>00</span>
<span class="toolbar-timer-unit-label">{{ t('countdown_seconds') }}</span>
</div>
</div>
<p class="toolbar-timer-subline" data-countdown-subline>{{ t('countdown_subline') }}</p>
</div>
<a class="btn btn-ghost toolbar-nav-btn toolbar-faq-btn" href="{{ url_for('faq') }}" aria-label="{{ t('faq') }}" title="{{ t('faq') }}">
<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false">
<path d="M12 2a10 10 0 1 0 10 10A10.01 10.01 0 0 0 12 2zm.15 16.25a1.25 1.25 0 1 1 1.25-1.25 1.25 1.25 0 0 1-1.25 1.25zm1.75-7.05-.75.48a1.36 1.36 0 0 0-.65 1.17v.4h-2v-.4a3.34 3.34 0 0 1 1.58-2.85l.75-.48a1.75 1.75 0 1 0-2.68-1.48h-2a3.75 3.75 0 1 1 5.75 3.16z" />
</svg>
</a>
{% endif %}
</div>
<div class="toolbar-session-actions">
{% if guest_name %}
<div class="toolbar-row toolbar-row-bottom">
<div
class="toolbar-timer"
data-countdown-target="{{ wedding_countdown_iso }}"
data-countdown-started="{{ t('countdown_started') }}"
>
<button
class="btn btn-ghost toolbar-nav-btn toolbar-timer-btn"
type="button"
aria-label="{{ t('countdown_button_label') }}"
title="{{ t('countdown_button_label') }}"
data-countdown-toggle
aria-expanded="false"
>
<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false">
<path d="M15 2H9v2h6V2zm-3 4a8 8 0 1 0 8 8 8 8 0 0 0-8-8zm3.4 11.4L11 13V8h2v4.2l3.8 3.8z" />
</svg>
</button>
<div class="toolbar-timer-popover" data-countdown-popover hidden>
<p class="toolbar-timer-label">{{ t('countdown_until') }}</p>
<div class="toolbar-timer-grid" data-countdown-grid>
<div class="toolbar-timer-unit">
<span class="toolbar-timer-value" data-countdown-days>0</span>
<span class="toolbar-timer-unit-label">{{ t('countdown_days') }}</span>
</div>
<div class="toolbar-timer-unit">
<span class="toolbar-timer-value" data-countdown-hours>00</span>
<span class="toolbar-timer-unit-label">{{ t('countdown_hours') }}</span>
</div>
<div class="toolbar-timer-unit">
<span class="toolbar-timer-value" data-countdown-minutes>00</span>
<span class="toolbar-timer-unit-label">{{ t('countdown_minutes') }}</span>
</div>
<div class="toolbar-timer-unit">
<span class="toolbar-timer-value" data-countdown-seconds>00</span>
<span class="toolbar-timer-unit-label">{{ t('countdown_seconds') }}</span>
</div>
</div>
<p class="toolbar-timer-subline" data-countdown-subline>{{ t('countdown_subline') }}</p>
</div>
</div>
<a class="btn btn-ghost toolbar-nav-btn" href="{{ url_for('guest_area') }}" aria-label="{{ t('dashboard') }}" title="{{ t('dashboard') }}">
<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false">
<path d="M3 3h8v8H3V3zm10 0h8v5h-8V3zM3 13h5v8H3v-8zm7 0h11v8H10v-8z" />
@@ -76,6 +96,50 @@
</div>
</header>
{% if guest_name %}
<dialog
class="translator-dialog"
data-translator-dialog
data-source-lang="{{ 'de' if lang == 'de' else 'en' }}"
aria-labelledby="translator-title"
>
<div class="translator-sheet">
<div class="translator-head">
<div>
<h2 id="translator-title">{{ t('translator') }}</h2>
</div>
</div>
<div class="translator-direction" aria-label="{{ t('translator_direction') }}">
<span data-translator-source>{{ t('language_german') if lang == 'de' else t('language_english') }}</span>
<button class="translator-swap" type="button" data-translator-swap aria-label="{{ t('translator_swap') }}"></button>
<span data-translator-target>{{ t('language_english') if lang == 'de' else t('language_german') }}</span>
</div>
<label class="translator-label" for="translator-text">{{ t('translator_input_label') }}</label>
<textarea
id="translator-text"
class="translator-input"
rows="4"
maxlength="500"
placeholder="{{ t('translator_placeholder') }}"
data-translator-input
></textarea>
<div class="translator-quick-list" aria-label="{{ t('translator_quick_phrases') }}">
<button type="button" data-translator-phrase data-de="Schön, dich kennenzulernen." data-en="Nice to meet you.">{{ t('translator_phrase_meet') }}</button>
<button type="button" data-translator-phrase data-de="Möchtest du etwas trinken?" data-en="Would you like something to drink?">{{ t('translator_phrase_drink') }}</button>
<button type="button" data-translator-phrase data-de="Wo ist die Toilette?" data-en="Where is the restroom?">{{ t('translator_phrase_restroom') }}</button>
</div>
<p class="translator-privacy">{{ t('translator_privacy') }}</p>
<button class="btn translator-submit" type="button" data-translator-submit disabled>
{{ t('translator_submit') }} <span aria-hidden="true"></span>
</button>
</div>
</dialog>
{% endif %}
<main class="container">
{% with messages = get_flashed_messages() %}
@@ -95,5 +159,6 @@
</footer>
<script src="{{ url_for('static', filename='dashboard-transition.js', v='20260303a') }}" defer></script>
<script src="{{ url_for('static', filename='countdown.js', v='20260303c') }}" defer></script>
<script src="{{ url_for('static', filename='translator.js', v='20260724b') }}" defer></script>
</body>
</html>

View File

@@ -169,6 +169,23 @@
</p>
</article>
<article class="legal-section" id="deepl-translation">
<h2>Translation with DeepL</h2>
<p>
The protected guest area provides an optional link to the external translation service DeepL, operated by DeepL
SE, Maarweg 165, 50825 Cologne, Germany. No connection to DeepL is established merely by opening this website.
</p>
<p>
Only after you click “Translate with DeepL” will the text you entered and technical connection data, such as your
IP address, be transmitted directly from your browser to DeepL. The text is not sent to or stored by our web
server. Please do not enter names, contact details, confidential information, or other personal data.
</p>
<p>
After the link is opened, DeepL's privacy policy and terms apply:
<a href="https://www.deepl.com/privacy" target="_blank" rel="noopener noreferrer">DeepL privacy policy</a>.
</p>
</article>
<article class="legal-section" id="photo-upload">
<h2>Photo Uploads</h2>
<p>
@@ -384,6 +401,25 @@
</p>
</article>
<article class="legal-section" id="deepl-uebersetzung">
<h2>Übersetzung mit DeepL</h2>
<p>
Im geschützten Gästebereich wird eine optionale Weiterleitung zum externen Übersetzungsdienst DeepL angeboten.
Anbieter ist die DeepL SE, Maarweg 165, 50825 Köln. Allein durch das Öffnen dieser Website wird noch keine
Verbindung zu DeepL hergestellt.
</p>
<p>
Erst wenn Sie „Mit DeepL übersetzen“ anklicken, werden der von Ihnen eingegebene Text und technische
Verbindungsdaten, beispielsweise Ihre IP-Adresse, direkt durch Ihren Browser an DeepL übermittelt. Der Text wird
nicht an unseren Webserver gesendet oder dort gespeichert. Bitte geben Sie keine Namen, Kontaktdaten,
vertraulichen Informationen oder sonstigen personenbezogenen Daten ein.
</p>
<p>
Nach dem Öffnen des Links gelten die Datenschutzbestimmungen und Nutzungsbedingungen von DeepL:
<a href="https://www.deepl.com/privacy" target="_blank" rel="noopener noreferrer">Datenschutz bei DeepL</a>.
</p>
</article>
<article class="legal-section" id="foto-upload">
<h2>Upload von Fotos durch Nutzer</h2>
<p>

View File

@@ -0,0 +1,24 @@
{% extends "base.html" %}
{% block content %}
<section class="card faq-card">
<h1>{{ t('faq') }}</h1>
<p class="faq-intro">{{ t('faq_intro') }}</p>
<div class="faq-list">
{% for question_key, answer_key in faq_items %}
<details class="faq-item">
<summary>
<span>{{ t(question_key) }}</span>
<span class="faq-toggle" aria-hidden="true"></span>
</summary>
<div class="faq-answer">
<p>{{ t(answer_key) }}</p>
</div>
</details>
{% endfor %}
</div>
<a class="btn btn-ghost faq-tour-restart" href="{{ url_for('welcome', tour=1) }}">{{ t('tour_restart') }}</a>
</section>
<script src="{{ url_for('static', filename='faq.js', v='20260724a') }}" defer></script>
{% endblock %}

View File

@@ -63,7 +63,7 @@
<div class="hotel-actions">
<a class="btn" href="{{ hotel.website_url }}" target="_blank" rel="noopener">{{ t('hotel_visit_website') }}</a>
<a
class="btn btn-ghost"
class="btn btn-ghost route-location-btn"
href="{{ hotel.current_route_url }}"
target="_blank"
rel="noopener"
@@ -199,6 +199,7 @@
loading="lazy"
decoding="async"
>
<span class="gift-money-label">{{ t('gifts_money_label') }}</span>
</div>
<p class="gift-caption">{{ t('gifts_caption') }}</p>
<p>{{ t('gifts_text') }}</p>
@@ -318,7 +319,7 @@
</button>
<div class="map-embed-target" data-map-embed-target></div>
<div class="location-actions">
<a class="btn btn-ghost" href="{{ location_route_url }}" target="_blank" rel="noopener" data-location-route-live>{{ t('route_from_current') }}</a>
<a class="btn btn-ghost route-location-btn" href="{{ location_route_url }}" target="_blank" rel="noopener" data-location-route-live>{{ t('route_from_current') }}</a>
<a class="btn" href="{{ location_website_url }}" target="_blank" rel="noopener">{{ t('visit_location') }}</a>
</div>
</div>

View File

@@ -11,4 +11,87 @@
</div>
</section>
<p class="hero-hint-below">{{ welcome_hint }}</p>
{% if show_onboarding %}
<dialog
class="onboarding-dialog"
data-onboarding-dialog
data-complete-url="{{ url_for('complete_onboarding') }}"
data-progress-template="{{ t('tour_progress') }}"
aria-labelledby="onboarding-title"
>
<div class="onboarding-shell">
<div class="onboarding-progress" aria-hidden="true">
{% for _ in range(6) %}<span data-onboarding-dot></span>{% endfor %}
</div>
<p class="onboarding-step-label" data-onboarding-progress></p>
<h2 id="onboarding-title">{{ t('tour_title') }}</h2>
<div class="onboarding-slides">
<section class="onboarding-slide" data-onboarding-slide>
<span class="onboarding-icon" aria-hidden="true">
<svg viewBox="0 0 24 24" focusable="false">
<path d="M3 3h8v8H3V3zm10 0h8v5h-8V3zM3 13h5v8H3v-8zm7 0h11v8H10v-8z" />
</svg>
</span>
<h3>{{ t('tour_dashboard_title') }}</h3>
<p>{{ t('tour_dashboard_text') }}</p>
</section>
<section class="onboarding-slide" data-onboarding-slide hidden>
<span class="onboarding-icon" aria-hidden="true"></span>
<h3>{{ t('tour_rsvp_title') }}</h3>
<p>{{ t('tour_rsvp_text') }}</p>
</section>
<section class="onboarding-slide" data-onboarding-slide hidden>
<span class="onboarding-icon" aria-hidden="true">
<svg viewBox="0 0 24 24" focusable="false">
<path d="M9 4 7.6 6H4a2 2 0 0 0-2 2v10a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-3.6L15 4H9zm3 4.2A4.8 4.8 0 1 1 7.2 13 4.8 4.8 0 0 1 12 8.2zm0 2A2.8 2.8 0 1 0 14.8 13 2.8 2.8 0 0 0 12 10.2z" />
</svg>
</span>
<h3>{{ t('tour_photos_title') }}</h3>
<p>{{ t('tour_photos_text') }}</p>
</section>
<section class="onboarding-slide" data-onboarding-slide hidden>
<span class="onboarding-icon onboarding-icon-route" aria-hidden="true">
<svg viewBox="0 0 24 24" focusable="false">
<circle cx="11" cy="13" r="7.5" />
<circle cx="11" cy="13" r="3.5" />
<path d="M20.5 3.5 11 13M17 7l.2-3.6M17 7l3.6-.2" />
</svg>
</span>
<h3>{{ t('tour_routes_title') }}</h3>
<p>{{ t('tour_routes_text') }}</p>
</section>
<section class="onboarding-slide" data-onboarding-slide hidden>
<span class="onboarding-icon" aria-hidden="true">
<svg viewBox="0 0 24 24" focusable="false">
<path d="M12 5.5A8.4 8.4 0 0 0 6.5 3.4 8.6 8.6 0 0 0 2 4.7v14.1a.75.75 0 0 0 1.02.7A8.1 8.1 0 0 1 6.5 18.6a7.1 7.1 0 0 1 4.98 2 .75.75 0 0 0 1.04 0 7.1 7.1 0 0 1 4.98-2 8.1 8.1 0 0 1 3.48.9.75.75 0 0 0 1.02-.7V4.7a8.6 8.6 0 0 0-4.5-1.3A8.4 8.4 0 0 0 12 5.5zm-1 12.9a8.7 8.7 0 0 0-4.5-1.3 9.6 9.6 0 0 0-3 .5V5.7a7.4 7.4 0 0 1 3-.8A6.9 6.9 0 0 1 11 6.7zm9.5-.8a9.6 9.6 0 0 0-3-.5 8.7 8.7 0 0 0-4.5 1.3V6.7a6.9 6.9 0 0 1 4.5-1.8 7.4 7.4 0 0 1 3 .8z" />
</svg>
</span>
<h3>{{ t('tour_translator_title') }}</h3>
<p>{{ t('tour_translator_text') }}</p>
</section>
<section class="onboarding-slide" data-onboarding-slide hidden>
<span class="onboarding-icon" aria-hidden="true">?</span>
<h3>{{ t('tour_faq_title') }}</h3>
<p>{{ t('tour_faq_text') }}</p>
</section>
</div>
<div class="onboarding-actions">
<div>
<button class="btn btn-ghost" type="button" data-onboarding-back hidden>{{ t('tour_back') }}</button>
<button
class="btn"
type="button"
data-onboarding-next
data-next-label="{{ t('tour_next') }}"
data-finish-label="{{ t('tour_finish') }}"
>{{ t('tour_next') }}</button>
</div>
</div>
</div>
</dialog>
{% endif %}
<script src="{{ url_for('static', filename='onboarding.js', v='20260724a') }}" defer></script>
{% endblock %}