From 45b37597a9ed3f065d627285430a4f527dc6fc8a Mon Sep 17 00:00:00 2001 From: Dominic Date: Fri, 24 Jul 2026 15:16:10 +0000 Subject: [PATCH] =?UTF-8?q?new=20feat=20=C3=BCbersetzer=20und=20faq?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/__pycache__/app.cpython-312.pyc | Bin 57062 -> 67643 bytes backend/app.py | 155 ++++++- backend/app.sqlite3 | Bin 49152 -> 49152 bytes backend/static/assets/money-pile.svg | 1 - backend/static/faq.js | 53 +++ backend/static/onboarding.js | 81 ++++ backend/static/styles.css | 550 +++++++++++++++++++++++- backend/static/translator.js | 117 +++++ backend/templates/base.html | 151 +++++-- backend/templates/datenschutz.html | 36 ++ backend/templates/faq.html | 24 ++ backend/templates/info.html | 5 +- backend/templates/welcome.html | 83 ++++ data/db/app.sqlite3 | Bin 53248 -> 53248 bytes 14 files changed, 1205 insertions(+), 51 deletions(-) create mode 100644 backend/static/faq.js create mode 100644 backend/static/onboarding.js create mode 100644 backend/static/translator.js create mode 100644 backend/templates/faq.html diff --git a/backend/__pycache__/app.cpython-312.pyc b/backend/__pycache__/app.cpython-312.pyc index 952a63ad504cdd398138de37f1c8d5f8f32a05cd..5c333349741f14296a2a913ec812278a175621fa 100644 GIT binary patch delta 24333 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 delta 14125 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 Y_T*`+a#)GIG$GoPVfr$|;>n5ne> None: name TEXT NOT NULL UNIQUE, password_hash TEXT NOT NULL, role TEXT NOT NULL DEFAULT 'guest', + onboarding_completed INTEGER NOT NULL DEFAULT 0, created_at TEXT NOT NULL ) """ @@ -811,6 +917,8 @@ def init_db() -> None: group_cols = table_columns(conn, "groups") if "role" not in group_cols: conn.execute("ALTER TABLE groups ADD COLUMN role TEXT NOT NULL DEFAULT 'guest'") + if "onboarding_completed" not in group_cols: + conn.execute("ALTER TABLE groups ADD COLUMN onboarding_completed INTEGER NOT NULL DEFAULT 0") conn.commit() @@ -947,14 +1055,38 @@ def welcome(): is_single_guest = member_count <= 1 welcome_headline = t("hero_headline_with_group").format(name=session.get("group_name", "")) welcome_text = t("hero_text_single") if is_single_guest else t("hero_text_group") + onboarding_row = db.execute( + "SELECT onboarding_completed FROM groups WHERE id = ?", + (int(session["group_id"]),), + ).fetchone() + completed_onboarding_version = ( + int(onboarding_row["onboarding_completed"] or 0) if onboarding_row else 0 + ) + show_onboarding = ( + request.args.get("tour") == "1" + or completed_onboarding_version < ONBOARDING_VERSION + ) return render_template( "welcome.html", welcome_headline=welcome_headline, welcome_text=welcome_text, welcome_hint=t("hero_app_hint_single") if is_single_guest else t("hero_app_hint"), + show_onboarding=show_onboarding, ) +@app.post("/onboarding/complete") +@login_required +def complete_onboarding(): + db = get_db() + db.execute( + "UPDATE groups SET onboarding_completed = ? WHERE id = ?", + (ONBOARDING_VERSION, int(session["group_id"])), + ) + db.commit() + return ("", 204) + + @app.get("/gaestebereich") @app.get("/gästebereich") @login_required @@ -1002,6 +1134,21 @@ def dashboard(): return redirect(url_for("guest_area")) +@app.get("/faq") +@login_required +def faq(): + faq_items = [ + ("faq_deadline_question", "faq_deadline_answer"), + ("faq_change_question", "faq_change_answer"), + ("faq_when_where_question", "faq_when_where_answer"), + ("faq_parking_question", "faq_parking_answer"), + ("faq_hotel_question", "faq_hotel_answer"), + ("faq_upload_question", "faq_upload_answer"), + ("faq_visibility_question", "faq_visibility_answer"), + ] + return render_template("faq.html", faq_items=faq_items) + + @app.route("/rsvp", methods=["GET", "POST"]) @login_required def rsvp(): diff --git a/backend/app.sqlite3 b/backend/app.sqlite3 index 0214a384f9a744d6b5a67963f66e99e5c04b1dbe..93b14bb5e335d294470849d6b544faa51d104aba 100644 GIT binary patch delta 156 zcmZo@U~Xt&o**qKz`(%31H>@EI#I`1R)9gTtdp1fBm)PBF9Y8y{?mLByb2t?JfAsN zaQAI&yvWYg6v4_aE-A^_%DwqAw>+bfjzWH3Qhs7lN@iYqd~$wnK~8E(YKnrVUx=%_ zYmkDUe~5x#sE?0=i>sSss85K3f#&8FoQg~={684AHa4m;GHJ6-KFcq=S%~-DCIAv@ BDDVIP delta 98 zcmZo@U~Xt&o**sA&%nUI1H>@EGEv7^mY+edtdp1j2LlJ^3I@Ja{HOUMcon!$@_go8 v!QHo6P(X)s^9oKyCaxxRR(5eoNyZk<&EXvKjLe$aY?IIO%Wf9peYXh!yeAe7 diff --git a/backend/static/assets/money-pile.svg b/backend/static/assets/money-pile.svg index 7848c48..7836b2c 100644 --- a/backend/static/assets/money-pile.svg +++ b/backend/static/assets/money-pile.svg @@ -36,5 +36,4 @@ $ - Gift Fund diff --git a/backend/static/faq.js b/backend/static/faq.js new file mode 100644 index 0000000..13d1e11 --- /dev/null +++ b/backend/static/faq.js @@ -0,0 +1,53 @@ +(() => { + function initFaqItem(item) { + const summary = item.querySelector("summary"); + const answer = item.querySelector(".faq-answer"); + if (!summary || !answer) { + return; + } + + let isAnimating = false; + + summary.addEventListener("click", (event) => { + event.preventDefault(); + if (isAnimating) { + return; + } + + if (!item.open) { + item.open = true; + item.classList.remove("is-closing"); + item.classList.add("is-opening"); + const finishOpen = () => { + item.classList.remove("is-opening"); + isAnimating = false; + }; + isAnimating = true; + answer.addEventListener("animationend", finishOpen, { once: true }); + window.setTimeout( + finishOpen, + window.matchMedia("(prefers-reduced-motion: reduce)").matches ? 0 : 360, + ); + return; + } + + isAnimating = true; + item.classList.remove("is-opening"); + item.classList.add("is-closing"); + const finishClose = () => { + item.classList.remove("is-closing"); + item.open = false; + isAnimating = false; + }; + answer.addEventListener("animationend", finishClose, { once: true }); + window.setTimeout( + finishClose, + window.matchMedia("(prefers-reduced-motion: reduce)").matches ? 0 : 360, + ); + }); + } + + document.addEventListener("DOMContentLoaded", () => { + document.querySelectorAll(".faq-item").forEach(initFaqItem); + }); +})(); diff --git a/backend/static/onboarding.js b/backend/static/onboarding.js new file mode 100644 index 0000000..4ce307a --- /dev/null +++ b/backend/static/onboarding.js @@ -0,0 +1,81 @@ +(() => { + function initOnboarding(dialog) { + const slides = Array.from(dialog.querySelectorAll("[data-onboarding-slide]")); + const dots = Array.from(dialog.querySelectorAll("[data-onboarding-dot]")); + const progress = dialog.querySelector("[data-onboarding-progress]"); + const backButton = dialog.querySelector("[data-onboarding-back]"); + const nextButton = dialog.querySelector("[data-onboarding-next]"); + const completeUrl = dialog.dataset.completeUrl; + const progressTemplate = dialog.dataset.progressTemplate || "Step {current} of {total}"; + + if (!slides.length || !progress || !backButton || !nextButton || !completeUrl) { + return; + } + + let currentIndex = 0; + let completionSent = false; + + const markComplete = () => { + if (completionSent) { + return; + } + completionSent = true; + window.fetch(completeUrl, { + method: "POST", + credentials: "same-origin", + headers: { "X-Requested-With": "XMLHttpRequest" }, + keepalive: true, + }).catch(() => { + completionSent = false; + }); + }; + + const finish = () => { + markComplete(); + dialog.close(); + }; + + const render = () => { + slides.forEach((slide, index) => { + slide.hidden = index !== currentIndex; + }); + dots.forEach((dot, index) => { + dot.classList.toggle("is-active", index <= currentIndex); + }); + progress.textContent = progressTemplate + .replace("{current}", String(currentIndex + 1)) + .replace("{total}", String(slides.length)); + backButton.hidden = currentIndex === 0; + const isLast = currentIndex === slides.length - 1; + nextButton.textContent = isLast ? nextButton.dataset.finishLabel : nextButton.dataset.nextLabel; + }; + + backButton.addEventListener("click", () => { + currentIndex = Math.max(0, currentIndex - 1); + render(); + }); + + nextButton.addEventListener("click", () => { + if (currentIndex === slides.length - 1) { + finish(); + return; + } + currentIndex += 1; + render(); + }); + + dialog.addEventListener("cancel", (event) => { + event.preventDefault(); + }); + + render(); + dialog.showModal(); + } + + document.addEventListener("DOMContentLoaded", () => { + const dialog = document.querySelector("[data-onboarding-dialog]"); + if (dialog) { + initOnboarding(dialog); + } + }); +})(); diff --git a/backend/static/styles.css b/backend/static/styles.css index de7cd6f..47cee9f 100644 --- a/backend/static/styles.css +++ b/backend/static/styles.css @@ -697,7 +697,17 @@ input[type="file"]:focus { fill: currentColor; } -.toolbar-session-actions { +.toolbar-faq-btn svg { + width: 1rem; + height: 1rem; +} + +.toolbar-translate-btn svg { + width: 1.02rem; + height: 1.02rem; +} + +.toolbar-row { display: inline-flex; align-items: center; gap: 0.38rem; @@ -816,6 +826,437 @@ input[type="file"]:focus { 0 1px 0 rgba(255, 255, 255, 0.5) inset; } +.faq-card { + width: min(760px, 100%); + margin-inline: auto; + padding: clamp(1.2rem, 3vw, 2rem); +} + +.faq-card h1 { + margin-bottom: 0.55rem; +} + +.faq-intro { + width: min(56ch, 100%); + margin: 0 auto 1.4rem; + color: rgba(31, 26, 23, 0.76); + line-height: 1.5; +} + +.faq-list { + display: grid; + gap: 0.7rem; + text-align: left; +} + +.faq-item { + overflow: hidden; + border: 1px solid rgba(31, 58, 47, 0.14); + border-radius: 14px; + background: linear-gradient(180deg, #fffdf9 0%, #fbf3e6 100%); + box-shadow: 0 5px 14px rgba(31, 58, 47, 0.07); +} + +.faq-item summary { + display: flex; + align-items: center; + justify-content: space-between; + gap: 1rem; + padding: 1rem; + color: var(--forest); + font-weight: 650; + cursor: pointer; + list-style: none; +} + +.faq-item summary::-webkit-details-marker { + display: none; +} + +.faq-item summary:focus-visible { + outline: 2px solid var(--gold); + outline-offset: -3px; +} + +.faq-toggle { + position: relative; + width: 1rem; + height: 1rem; + flex: 0 0 1rem; +} + +.faq-toggle::before, +.faq-toggle::after { + content: ""; + position: absolute; + top: 50%; + left: 50%; + width: 0.85rem; + height: 2px; + border-radius: 2px; + background: var(--gold); + transform: translate(-50%, -50%); + transition: transform 0.2s ease; +} + +.faq-toggle::after { + transform: translate(-50%, -50%) rotate(90deg); +} + +.faq-item[open] .faq-toggle::after { + transform: translate(-50%, -50%) rotate(0deg); +} + +.faq-answer { + padding: 0 1rem 1rem; + color: rgba(31, 26, 23, 0.8); + line-height: 1.55; +} + +.faq-answer p { + margin: 0; +} + +.faq-item.is-opening .faq-answer { + animation: faq-slide-in 300ms cubic-bezier(0.22, 0.61, 0.36, 1) both; +} + +.faq-item.is-closing .faq-answer { + animation: faq-slide-out 260ms cubic-bezier(0.55, 0.06, 0.68, 0.19) both; +} + +@keyframes faq-slide-in { + from { + opacity: 0; + transform: translateX(2.5rem); + } + to { + opacity: 1; + transform: translateX(0); + } +} + +@keyframes faq-slide-out { + from { + opacity: 1; + transform: translateX(0); + } + to { + opacity: 0; + transform: translateX(2.5rem); + } +} + +.translator-dialog { + width: min(92vw, 520px); + max-height: min(88vh, 720px); + padding: 0; + overflow: hidden; + color: var(--ink); + border: 1px solid rgba(31, 58, 47, 0.16); + border-radius: 22px; + background: var(--card); + box-shadow: 0 24px 70px rgba(18, 31, 24, 0.28); +} + +.translator-dialog::backdrop { + background: rgba(18, 31, 24, 0); +} + +.translator-dialog.is-opening { + animation: translator-slide-up 320ms cubic-bezier(0.22, 0.61, 0.36, 1) both; +} + +.translator-dialog.is-opening::backdrop { + animation: translator-backdrop-in 320ms ease-out both; +} + +.translator-dialog.is-closing { + animation: translator-slide-down 280ms cubic-bezier(0.55, 0.06, 0.68, 0.19) both; +} + +.translator-dialog.is-closing::backdrop { + animation: translator-backdrop-out 280ms ease-in both; +} + +@keyframes translator-slide-up { + from { + opacity: 0; + transform: translateY(3.5rem); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +@keyframes translator-slide-down { + from { + opacity: 1; + transform: translateY(0); + } + to { + opacity: 0; + transform: translateY(3.5rem); + } +} + +@keyframes translator-backdrop-in { + from { + background: rgba(18, 31, 24, 0); + } + to { + background: rgba(18, 31, 24, 0.22); + } +} + +@keyframes translator-backdrop-out { + from { + background: rgba(18, 31, 24, 0.22); + } + to { + background: rgba(18, 31, 24, 0); + } +} + +.translator-sheet { + max-height: min(88vh, 720px); + padding: clamp(1.1rem, 4vw, 1.6rem); + overflow-y: auto; + text-align: center; +} + +.translator-head { + display: flex; + align-items: flex-start; + justify-content: center; + gap: 1rem; +} + +.translator-head h2 { + margin-bottom: 0.8rem; +} + +.translator-direction { + display: grid; + grid-template-columns: 1fr auto 1fr; + align-items: center; + gap: 0.55rem; + margin-bottom: 1rem; + color: var(--forest); + font-weight: 650; + text-align: center; +} + +.translator-direction > span { + padding: 0.55rem 0.7rem; + border: 1px solid rgba(31, 58, 47, 0.14); + border-radius: 999px; + background: rgba(178, 137, 70, 0.09); +} + +.translator-swap { + width: 2.35rem; + height: 2.35rem; + border: 0; + border-radius: 50%; + color: #fff; + background: var(--forest); + font-size: 1.1rem; + cursor: pointer; +} + +.translator-label { + display: block; + margin-bottom: 0.35rem; + font-weight: 650; +} + +.translator-input { + width: 100%; + min-height: 7.5rem; + resize: vertical; + padding: 0.85rem; + border: 1px solid rgba(31, 58, 47, 0.22); + border-radius: 14px; + color: var(--ink); + background: #fff; + font: inherit; + line-height: 1.45; + text-align: center; +} + +.translator-input:focus { + outline: 2px solid rgba(178, 137, 70, 0.55); + border-color: var(--gold); +} + +.translator-quick-list { + display: flex; + flex-wrap: wrap; + justify-content: center; + gap: 0.45rem; + margin-top: 0.7rem; +} + +.translator-quick-list button { + padding: 0.38rem 0.65rem; + border: 1px solid rgba(31, 58, 47, 0.16); + border-radius: 999px; + color: var(--forest); + background: rgba(255, 253, 248, 0.92); + font: inherit; + font-size: 0.86rem; + cursor: pointer; +} + +.translator-privacy { + margin: 1rem 0 0.8rem; + color: rgba(31, 26, 23, 0.7); + font-size: 0.85rem; + line-height: 1.4; +} + +.translator-submit { + width: 100%; + border-radius: 999px; +} + +.onboarding-dialog { + width: min(92vw, 560px); + max-height: min(88vh, 720px); + padding: 0; + overflow: hidden; + color: var(--ink); + border: 1px solid rgba(31, 58, 47, 0.16); + border-radius: 24px; + background: var(--card); + box-shadow: 0 26px 72px rgba(18, 31, 24, 0.3); +} + +.onboarding-dialog::backdrop { + background: rgba(18, 31, 24, 0.5); + backdrop-filter: blur(4px); +} + +.onboarding-shell { + max-height: min(88vh, 720px); + padding: clamp(1.25rem, 4vw, 1.8rem); + overflow-y: auto; + text-align: center; +} + +.onboarding-progress { + display: flex; + justify-content: center; + gap: 0.38rem; + margin-bottom: 0.55rem; +} + +.onboarding-progress span { + width: 1.4rem; + height: 0.28rem; + border-radius: 999px; + background: rgba(31, 58, 47, 0.14); +} + +.onboarding-progress span.is-active { + background: var(--gold); +} + +.onboarding-step-label { + margin: 0 0 0.3rem; + color: var(--gold-text); + font-size: 0.82rem; + font-weight: 650; +} + +.onboarding-shell > h2 { + margin-bottom: 1rem; + color: var(--forest); +} + +.onboarding-slide { + min-height: 14rem; + padding: 1.15rem; + border: 1px solid rgba(31, 58, 47, 0.12); + border-radius: 18px; + background: linear-gradient(180deg, #fffdf8 0%, #fdf5e8 100%); + box-shadow: 0 8px 20px rgba(31, 58, 47, 0.08); +} + +.onboarding-slide[hidden] { + display: none; +} + +.onboarding-icon { + display: grid; + place-items: center; + width: 3.4rem; + height: 3.4rem; + margin: 0 auto 0.8rem; + border-radius: 50%; + color: #fff; + background: linear-gradient(180deg, #2a4a3b 0%, var(--forest) 100%); + box-shadow: 0 7px 16px rgba(31, 58, 47, 0.2); + font-size: 1.35rem; + font-weight: 700; +} + +.onboarding-icon svg { + width: 1.5rem; + height: 1.5rem; + fill: currentColor; +} + +.onboarding-icon-route svg { + width: 1.65rem; + height: 1.65rem; + fill: none; + stroke: currentColor; + stroke-width: 1.8; + stroke-linecap: round; + stroke-linejoin: round; +} + +.onboarding-slide h3 { + margin-bottom: 0.55rem; + color: var(--forest); + font-size: 1.35rem; +} + +.onboarding-slide p { + max-width: 44ch; + margin: 0 auto; + color: var(--gold-text); + line-height: 1.55; +} + +.onboarding-actions { + display: flex; + align-items: center; + justify-content: space-between; + gap: 0.65rem; + margin-top: 1rem; +} + +.onboarding-actions > div { + display: flex; + gap: 0.5rem; + margin-left: auto; +} + +.onboarding-actions .btn { + border-radius: 999px; +} + +.faq-tour-restart { + display: flex; + width: fit-content; + margin: 1.2rem auto 0; + border-radius: 999px; +} + .gallery-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); @@ -1172,6 +1613,20 @@ input[type="file"]:focus { padding: 0.74rem 1.2rem; } +.route-location-btn.btn-ghost { + color: #174ea6; + border-color: #a8c7fa; + background: linear-gradient(180deg, #f2f7ff 0%, #e8f0fe 100%); + box-shadow: 0 5px 12px rgba(66, 133, 244, 0.12); +} + +.route-location-btn.btn-ghost:hover { + color: #123d82; + border-color: #7eaff8; + background: linear-gradient(180deg, #edf5ff 0%, #dce9fd 100%); + box-shadow: 0 7px 16px rgba(66, 133, 244, 0.2); +} + body.has-route-modal { overflow: hidden; } @@ -1296,6 +1751,7 @@ body.has-route-modal { } .gift-image-wrap { + position: relative; max-width: min(640px, 100%); margin: 0 auto; } @@ -1309,6 +1765,18 @@ body.has-route-modal { box-shadow: 0 12px 28px rgba(31, 58, 47, 0.18); } +.gift-money-label { + position: absolute; + top: 7%; + left: 50%; + transform: translateX(-50%); + color: #f0db8e; + font-family: Arial, sans-serif; + font-size: clamp(1.35rem, 5.6vw, 2.25rem); + line-height: 1; + white-space: nowrap; +} + .gift-caption { margin: 0.8rem 0 0; font-weight: 700; @@ -1528,6 +1996,33 @@ body.has-route-modal { } @media (max-width: 640px) { + .toolbar { + display: flex; + flex-direction: column; + align-items: stretch; + gap: 0.28rem; + width: 13.2rem; + } + + .toolbar-row { + display: flex; + width: 100%; + align-items: center; + justify-content: space-between; + gap: 0; + } + + .toolbar-login { + width: auto; + align-items: flex-end; + } + + .toolbar-login .toolbar-row { + width: auto; + justify-content: flex-end; + gap: 0.38rem; + } + .container { margin: 1.15rem auto 1.25rem; } @@ -1609,9 +2104,62 @@ body.has-route-modal { .route-open-btn { width: 100%; } + + .translator-dialog { + width: 100%; + max-width: none; + max-height: 88vh; + margin: auto 0 0; + border-radius: 22px 22px 0 0; + } + + .translator-sheet { + max-height: 88vh; + padding-bottom: max(1.2rem, env(safe-area-inset-bottom)); + } + + .onboarding-dialog { + width: 100%; + max-width: none; + max-height: 90vh; + margin: auto 0 0; + border-radius: 24px 24px 0 0; + } + + .onboarding-shell { + max-height: 90vh; + padding-bottom: max(1.25rem, env(safe-area-inset-bottom)); + } + + .onboarding-slide { + min-height: 12.5rem; + } + + .onboarding-actions { + align-items: stretch; + flex-direction: column-reverse; + } + + .onboarding-actions > div, + .onboarding-actions .btn { + width: 100%; + } } @media (prefers-reduced-motion: reduce) { + .translator-dialog.is-opening, + .translator-dialog.is-closing, + .faq-item.is-opening .faq-answer, + .faq-item.is-closing .faq-answer { + animation: none; + } + + .translator-dialog.is-opening::backdrop, + .translator-dialog.is-closing::backdrop { + animation: none; + background: rgba(18, 31, 24, 0.14); + } + .gift-reveal.is-active { animation: none; } diff --git a/backend/static/translator.js b/backend/static/translator.js new file mode 100644 index 0000000..fd907fd --- /dev/null +++ b/backend/static/translator.js @@ -0,0 +1,117 @@ +(() => { + function initTranslator(dialog) { + const openButton = document.querySelector("[data-translator-open]"); + const swapButton = dialog.querySelector("[data-translator-swap]"); + const submitButton = dialog.querySelector("[data-translator-submit]"); + const input = dialog.querySelector("[data-translator-input]"); + const sourceLabel = dialog.querySelector("[data-translator-source]"); + const targetLabel = dialog.querySelector("[data-translator-target]"); + const phraseButtons = dialog.querySelectorAll("[data-translator-phrase]"); + + if (!openButton || !swapButton || !submitButton || !input || !sourceLabel || !targetLabel) { + return; + } + + let sourceLang = dialog.dataset.sourceLang === "en" ? "en" : "de"; + const labels = { + de: sourceLang === "de" ? sourceLabel.textContent : targetLabel.textContent, + en: sourceLang === "en" ? sourceLabel.textContent : targetLabel.textContent, + }; + + const renderDirection = () => { + const targetLang = sourceLang === "de" ? "en" : "de"; + sourceLabel.textContent = labels[sourceLang]; + targetLabel.textContent = labels[targetLang]; + }; + + const updateSubmit = () => { + submitButton.disabled = input.value.trim().length === 0; + }; + + let isClosing = false; + + const close = () => { + if (!dialog.open || isClosing) { + return; + } + isClosing = true; + dialog.classList.remove("is-opening"); + dialog.classList.add("is-closing"); + + const finishClose = () => { + if (!dialog.open) { + return; + } + dialog.classList.remove("is-closing"); + dialog.close(); + isClosing = false; + openButton.focus(); + }; + + dialog.addEventListener("animationend", finishClose, { once: true }); + window.setTimeout( + finishClose, + window.matchMedia("(prefers-reduced-motion: reduce)").matches ? 0 : 360, + ); + }; + + openButton.addEventListener("click", () => { + dialog.showModal(); + dialog.classList.remove("is-closing"); + dialog.classList.add("is-opening"); + window.requestAnimationFrame(() => input.focus()); + }); + + dialog.addEventListener("click", (event) => { + if (event.target === dialog) { + close(); + } + }); + dialog.addEventListener("cancel", (event) => { + event.preventDefault(); + close(); + }); + + swapButton.addEventListener("click", () => { + sourceLang = sourceLang === "de" ? "en" : "de"; + renderDirection(); + phraseButtons.forEach((button) => { + input.value = input.value === button.dataset.de || input.value === button.dataset.en + ? button.dataset[sourceLang] + : input.value; + }); + updateSubmit(); + input.focus(); + }); + + phraseButtons.forEach((button) => { + button.addEventListener("click", () => { + input.value = button.dataset[sourceLang] || ""; + updateSubmit(); + input.focus(); + }); + }); + + input.addEventListener("input", updateSubmit); + submitButton.addEventListener("click", () => { + const text = input.value.trim(); + if (!text) { + return; + } + const targetLang = sourceLang === "de" ? "en" : "de"; + const url = `https://www.deepl.com/translator#${sourceLang}/${targetLang}/${encodeURIComponent(text)}`; + window.open(url, "_blank", "noopener,noreferrer"); + close(); + }); + + renderDirection(); + updateSubmit(); + } + + document.addEventListener("DOMContentLoaded", () => { + const dialog = document.querySelector("[data-translator-dialog]"); + if (dialog) { + initTranslator(dialog); + } + }); +})(); diff --git a/backend/templates/base.html b/backend/templates/base.html index e21fd93..d234857 100644 --- a/backend/templates/base.html +++ b/backend/templates/base.html @@ -7,62 +7,82 @@ - +
-
-
- -
-
- -
- {% if guest_name %} -
+
+
+
+ +
+
+ +
+ {% if guest_name %} - + + + + {% endif %}
-
+ {% if guest_name %} +
+
+ + +
+ {% if guest_name %} + +
+
+
+

{{ t('translator') }}

+
+
+ +
+ {{ t('language_german') if lang == 'de' else t('language_english') }} + + {{ t('language_english') if lang == 'de' else t('language_german') }} +
+ + + + +
+ + + +
+ +

{{ t('translator_privacy') }}

+ +
+
+ {% endif %} +
{% with messages = get_flashed_messages() %} @@ -95,5 +159,6 @@ + diff --git a/backend/templates/datenschutz.html b/backend/templates/datenschutz.html index d2e9074..bc93ce5 100644 --- a/backend/templates/datenschutz.html +++ b/backend/templates/datenschutz.html @@ -169,6 +169,23 @@

+
+ + +