Service Worker lieferte alte Stilvorlagen aus - Korrekturen blieben unsichtbar

Gemeldet 22.08.2026 mit Bildschirmfoto: "immer noch so" -- das Logo im
Marken-Intro stand weiterhin hochkant gestreckt da, obwohl die Korrektur
(height:auto gegen die height-Attribute) laengst live war.

Ursache war NICHT die Korrektur, sondern mein eigener Service Worker. Er
lief fuer /assets/ als "stale-while-revalidate": die gespeicherte Fassung
geht sofort raus, im Hintergrund wird eine frische geholt. Das ist schnell
-- bedeutet aber, dass jede Aenderung an CSS oder JavaScript beim ersten
Aufruf unsichtbar bleibt und erst beim zweiten wirkt. Ein behobener Fehler
sieht dadurch aus wie ein nicht behobener. Die unangenehmste Sorte.

Nachgemessen ist das Logo jetzt 160x161px bei einem natuerlichen
Verhaeltnis von 472x476 -- also unverzerrt. Vorher erzwang das Attribut
height="476" bei 160px Breite ein Verhaeltnis von 0,34 statt 0,99, genau
das lange Gesicht auf dem Bildschirmfoto.

Behoben:
- Stilvorlagen und Skripte laufen jetzt "Netz zuerst", Zwischenspeicher
  nur als Notfallnetz. Richtigkeit vor Millisekunden.
- Bilder und Symbole bleiben beim schnellen Weg -- die aendern sich
  praktisch nie und bekaemen sonst einen neuen Dateinamen.
- CACHE_NAME auf v2 gesetzt, damit der alte Speicher beim naechsten Start
  vollstaendig weggeworfen wird.
- Versionsnummer an allen CSS/JS-Verweisen hochgesetzt, damit es sich auch
  ohne Service Worker sofort selbst heilt.

Ausserdem: Woerterbuch fuer das Anfrageformular, 101 Textbausteine in fuenf
Sprachen (vier Schritte, Zusammenfassung, Fehlermeldungen).

Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
2026-08-22 18:34:15 +02:00
co-authored by Claude Opus 5
parent 29a054c359
commit 8ace955740
10 changed files with 390 additions and 37 deletions
+317
View File
@@ -0,0 +1,317 @@
/* =====================================================================
i18n-wd-anfrage.js — Projektanfrage (Masterplan S.10)
Vier Schritte, fünf Sprachen.
Grundhaltung der Texte: Es wird nur gefragt, was für ein ehrliches
Angebot wirklich nötig ist. Jede Frage, die man auch später klären
kann, ist eine Frage zu viel — abgebrochene Formulare sind teurer als
eine fehlende Angabe.
===================================================================== */
window.I18N_WD = {
seiten_titel: {
de: "Projekt anfragen — Dogfather Webdesign",
"de-CH": "Projekt aafroge — Dogfather Webdesign",
en: "Start a project — Dogfather Web Design",
fr: "Demander un projet — Dogfather Web Design",
pt: "Pedir projeto — Dogfather Web Design",
},
af_eyebrow: { de: "Projektanfrage", "de-CH": "Projektaafrog", en: "Project enquiry", fr: "Demande de projet", pt: "Pedido de projeto" },
af_h1: {
de: "Erzähl mir von deinem Vorhaben",
"de-CH": "Verzell mer vo dim Vorhabe",
en: "Tell me about your project",
fr: "Parle-moi de ton projet",
pt: "Conta-me o teu projeto",
},
af_lead: {
de: "Vier kurze Schritte, etwa fünf Minuten. Unverbindlich, kostenlos — und du siehst am Ende alles noch einmal, bevor du absendest.",
"de-CH": "Vier churzi Schritt, öppe füf Minute. Unverbindlich, gratis — und du gsehsch am Schluss alles nomol, bevor du abschicksch.",
en: "Four short steps, about five minutes. Non-binding, free — and you see everything again before you send it.",
fr: "Quatre étapes courtes, environ cinq minutes. Sans engagement, gratuit — et tu revois tout avant d'envoyer.",
pt: "Quatro passos curtos, cerca de cinco minutos. Sem compromisso, gratuito — e vês tudo outra vez antes de enviar.",
},
/* --- Schrittleiste --- */
af_schritt: { de: "Schritt", "de-CH": "Schritt", en: "Step", fr: "Étape", pt: "Passo" },
af_von: { de: "von", "de-CH": "vo", en: "of", fr: "sur", pt: "de" },
af_s1: { de: "Vorhaben", "de-CH": "Vorhabe", en: "Your project", fr: "Ton projet", pt: "O teu projeto" },
af_s2: { de: "Umfang", "de-CH": "Umfang", en: "Scope", fr: "Étendue", pt: "Âmbito" },
af_s3: { de: "Inhalte &amp; Rahmen", "de-CH": "Inhalt &amp; Rahme", en: "Content &amp; budget", fr: "Contenus &amp; budget", pt: "Conteúdos &amp; orçamento" },
af_s4: { de: "Kontakt", "de-CH": "Kontakt", en: "Contact", fr: "Contact", pt: "Contacto" },
af_weiter: { de: "Weiter", "de-CH": "Wiiter", en: "Next", fr: "Suivant", pt: "Seguinte" },
af_zurueck: { de: "Zurück", "de-CH": "Zrugg", en: "Back", fr: "Retour", pt: "Voltar" },
af_pruefen: { de: "Angaben prüfen", "de-CH": "Aagabe prüefe", en: "Review your answers", fr: "Vérifier mes réponses", pt: "Rever as respostas" },
af_senden: { de: "Anfrage absenden", "de-CH": "Aafrog abschicke", en: "Send enquiry", fr: "Envoyer la demande", pt: "Enviar pedido" },
af_sendet: { de: "Wird gesendet…", "de-CH": "Wird gschickt…", en: "Sending…", fr: "Envoi…", pt: "A enviar…" },
af_pflicht: { de: "Pflichtfeld", "de-CH": "Pflichtfäld", en: "Required", fr: "Obligatoire", pt: "Obrigatório" },
af_optional: { de: "optional", "de-CH": "optional", en: "optional", fr: "facultatif", pt: "opcional" },
/* --- Zwischenspeicherung --- */
af_gespeichert: {
de: "Deine Eingaben werden automatisch in diesem Browser gespeichert — du kannst jederzeit später weitermachen.",
"de-CH": "Dini Iigabe wärded automatisch i dem Browser gspeicheret — du chasch jederziit spöter wiitermache.",
en: "Your answers are saved automatically in this browser — you can continue later at any time.",
fr: "Tes réponses sont enregistrées automatiquement dans ce navigateur — tu peux continuer plus tard.",
pt: "As tuas respostas são guardadas automaticamente neste navegador — podes continuar mais tarde.",
},
af_wiederhergestellt: {
de: "Wir haben deine letzte Eingabe wiederhergestellt.",
"de-CH": "Mir hend dini letschti Iigab wiederhergstellt.",
en: "We restored your previous answers.",
fr: "Nous avons restauré tes réponses précédentes.",
pt: "Restaurámos as tuas respostas anteriores.",
},
af_verwerfen: { de: "Neu anfangen", "de-CH": "Neu aafange", en: "Start over", fr: "Recommencer", pt: "Começar de novo" },
/* ---------------- Schritt 1 ---------------- */
af1_h2: {
de: "Worum geht es ungefähr?",
"de-CH": "Um was gohts öppe?",
en: "What is this roughly about?",
fr: "De quoi s'agit-il en gros ?",
pt: "De que se trata, mais ou menos?",
},
af1_paket_label: { de: "Art des Projekts", "de-CH": "Art vom Projekt", en: "Type of project", fr: "Type de projet", pt: "Tipo de projeto" },
af1_paket_hilfe: {
de: "Nur eine grobe Richtung — wenn du unsicher bist, wähle das Letzte. Das ist völlig in Ordnung und kostet nichts extra.",
"de-CH": "Nur e grobi Richtig — wenn du unsicher bisch, wähl s letschte. Das isch völlig i Ornig und choschtet nüt extra.",
en: "Just a rough direction — if you are unsure, pick the last option. That is completely fine and costs nothing extra.",
fr: "Juste une direction générale — si tu hésites, choisis la dernière option. C'est parfaitement normal et sans surcoût.",
pt: "Apenas uma direção aproximada — se tiveres dúvidas, escolhe a última opção. Não há problema nenhum nem custo extra.",
},
af1_unklar: { de: "Ich weiß es noch nicht", "de-CH": "Ich weiss es no nöd", en: "I don't know yet", fr: "Je ne sais pas encore", pt: "Ainda não sei" },
af1_unklar_text: {
de: "Wir finden im Gespräch heraus, was passt.",
"de-CH": "Mir finded im Gspröch use, was passt.",
en: "We will work out what fits in a conversation.",
fr: "Nous déterminerons ensemble ce qui convient.",
pt: "Descobrimos numa conversa o que encaixa.",
},
af1_ziel_label: { de: "Was soll die Seite erreichen?", "de-CH": "Was söll d Site erreiche?", en: "What should the site achieve?", fr: "Que doit accomplir le site ?", pt: "O que deve o site alcançar?" },
af1_ziel_hilfe: {
de: "Ein, zwei Sätze reichen. Beispiel: „Kunden sollen mich finden und direkt einen Termin anfragen können.“",
"de-CH": "Ei, zwei Sätz länged. Bispiel: „Chunde söled mich finde und grad en Termin aafroge chönne.“",
en: "One or two sentences are enough. Example: “Customers should find me and be able to request an appointment directly.”",
fr: "Une ou deux phrases suffisent. Exemple : « Les clients doivent me trouver et pouvoir demander un rendez-vous. »",
pt: "Uma ou duas frases chegam. Exemplo: “Os clientes devem encontrar-me e poder pedir uma marcação.”",
},
af1_ziel_platzhalter: {
de: "z. B. Kunden sollen mich finden und direkt anfragen können",
"de-CH": "z. B. Chunde söled mich finde und grad aafroge chönne",
en: "e.g. customers should find me and be able to enquire directly",
fr: "p. ex. les clients doivent me trouver et pouvoir me contacter",
pt: "p. ex. os clientes devem encontrar-me e poder contactar-me",
},
af1_zielgruppe_label: { de: "Wer sind deine Kunden?", "de-CH": "Wär sind dini Chunde?", en: "Who are your customers?", fr: "Qui sont tes clients ?", pt: "Quem são os teus clientes?" },
af1_zielgruppe_platzhalter: {
de: "z. B. Privatkunden aus der Region, 30–60 Jahre",
"de-CH": "z. B. Privatchunde us dr Region, 30–60 Jahr",
en: "e.g. private customers in the region, aged 30–60",
fr: "p. ex. particuliers de la région, 30 à 60 ans",
pt: "p. ex. clientes particulares da região, 30–60 anos",
},
/* ---------------- Schritt 2 ---------------- */
af2_h2: {
de: "Wie groß soll es werden?",
"de-CH": "Wie gross söll s wärde?",
en: "How big should it be?",
fr: "Quelle ampleur ?",
pt: "Que dimensão deve ter?",
},
af2_seiten_label: { de: "Welche Bereiche brauchst du?", "de-CH": "Weli Bereich bruuchsch?", en: "Which sections do you need?", fr: "Quelles sections te faut-il ?", pt: "De que secções precisas?" },
af2_seiten_platzhalter: {
de: "z. B. Start, Leistungen, Über uns, Kontakt",
"de-CH": "z. B. Start, Leischtige, Über üs, Kontakt",
en: "e.g. home, services, about, contact",
fr: "p. ex. accueil, prestations, à propos, contact",
pt: "p. ex. início, serviços, sobre, contacto",
},
af2_funktionen_label: { de: "Was soll die Seite können?", "de-CH": "Was söll d Site chöne?", en: "What should the site be able to do?", fr: "Que doit pouvoir faire le site ?", pt: "O que deve o site conseguir fazer?" },
af2_funktionen_hilfe: {
de: "Alles, was du hier ankreuzt, wird im Angebot berücksichtigt. Nichts davon ist Pflicht.",
"de-CH": "Alles, wo du do aachrüzisch, wird im Aagebot berücksichtigt. Nüt devo isch Pflicht.",
en: "Everything you tick here is accounted for in the quote. None of it is mandatory.",
fr: "Tout ce que tu coches ici est pris en compte dans le devis. Rien n'est obligatoire.",
pt: "Tudo o que marcares aqui entra no orçamento. Nada disto é obrigatório.",
},
af2_f_kontakt: { de: "Kontaktformular", "de-CH": "Kontaktformular", en: "Contact form", fr: "Formulaire de contact", pt: "Formulário de contacto" },
af2_f_galerie: { de: "Bildergalerie", "de-CH": "Bildergalerie", en: "Image gallery", fr: "Galerie d'images", pt: "Galeria de imagens" },
af2_f_termin: { de: "Terminbuchung", "de-CH": "Terminbuechig", en: "Appointment booking", fr: "Prise de rendez-vous", pt: "Marcação de horários" },
af2_f_shop: { de: "Produkte verkaufen", "de-CH": "Produkt verchaufe", en: "Selling products", fr: "Vente de produits", pt: "Venda de produtos" },
af2_f_login: { de: "Geschützter Kundenbereich", "de-CH": "Gschützte Chundebereich", en: "Protected customer area", fr: "Espace client protégé", pt: "Área de cliente protegida" },
af2_f_blog: { de: "Blog oder Neuigkeiten", "de-CH": "Blog oder Neuigkeite", en: "Blog or news", fr: "Blog ou actualités", pt: "Blogue ou novidades" },
af2_f_sprachen: { de: "Mehrere Sprachen", "de-CH": "Mehreri Sprooche", en: "Multiple languages", fr: "Plusieurs langues", pt: "Vários idiomas" },
af2_f_karte: { de: "Karte / Anfahrt", "de-CH": "Charte / Aafahrt", en: "Map / directions", fr: "Carte / itinéraire", pt: "Mapa / como chegar" },
af2_f_newsletter: { de: "Newsletter", "de-CH": "Newsletter", en: "Newsletter", fr: "Newsletter", pt: "Newsletter" },
af2_f_app: { de: "Als App installierbar", "de-CH": "Als App installierbar", en: "Installable as an app", fr: "Installable comme application", pt: "Instalável como aplicação" },
/* Kategoriespezifische Folgefragen (Masterplan S.10 "Intelligente Felder") */
af2_shop_h3: { de: "Zum Verkauf", "de-CH": "Zum Verchauf", en: "About selling", fr: "À propos de la vente", pt: "Sobre a venda" },
af2_shop_anzahl: { de: "Wie viele Produkte ungefähr?", "de-CH": "Wie viili Produkt öppe?", en: "Roughly how many products?", fr: "Combien de produits environ ?", pt: "Quantos produtos, mais ou menos?" },
af2_shop_versand: { de: "Verschickst du selbst?", "de-CH": "Verschicksch sälber?", en: "Do you ship yourself?", fr: "Expédies-tu toi-même ?", pt: "Envias tu próprio?" },
af2_shop_zahlung: { de: "Gewünschte Zahlungsarten", "de-CH": "Gwünschti Zahligsarte", en: "Preferred payment methods", fr: "Moyens de paiement souhaités", pt: "Métodos de pagamento pretendidos" },
af2_vw_h3: { de: "Zur Verwaltung", "de-CH": "Zur Verwaltig", en: "About the admin area", fr: "À propos de l'administration", pt: "Sobre a administração" },
af2_vw_rollen: { de: "Wie viele Personen arbeiten damit?", "de-CH": "Wie viili Persone schaffed demit?", en: "How many people will work with it?", fr: "Combien de personnes l'utiliseront ?", pt: "Quantas pessoas vão trabalhar com isso?" },
af2_vw_dokumente: { de: "Welche Unterlagen sollen hinein?", "de-CH": "Weli Unterlage söled ine?", en: "Which documents should go in?", fr: "Quels documents doivent y figurer ?", pt: "Que documentos devem entrar?" },
/* ---------------- Schritt 3 ---------------- */
af3_h2: {
de: "Was hast du schon, und was ist der Rahmen?",
"de-CH": "Was hesch scho, und was isch dr Rahme?",
en: "What do you already have, and what is the frame?",
fr: "Qu'as-tu déjà, et quel est le cadre ?",
pt: "O que já tens e qual é o enquadramento?",
},
af3_hilfe: {
de: "Bitte ehrlich antworten — fehlende Texte und Bilder sind der häufigste Grund, warum Projekte länger dauern. Wenn ich es vorher weiß, planen wir es ein, statt später überrascht zu werden.",
"de-CH": "Bitte ehrlich antworte — fählendi Täxt und Bilder sind dr hüüfigscht Grund, wieso Projekt länger duured. Wenn i s vorher weiss, plane mers ii, statt spöter überrascht z wärde.",
en: "Please answer honestly — missing texts and images are the most common reason projects take longer. If I know beforehand, we plan for it instead of being surprised later.",
fr: "Réponds honnêtement — l'absence de textes et d'images est la première cause de retard. Si je le sais à l'avance, nous l'intégrons au planning.",
pt: "Responde honestamente — a falta de textos e imagens é a causa mais comum de atrasos. Se eu souber antes, planeamos isso em vez de sermos surpreendidos.",
},
af3_texte: { de: "Texte", "de-CH": "Täxt", en: "Texts", fr: "Textes", pt: "Textos" },
af3_bilder: { de: "Bilder / Fotos", "de-CH": "Bilder / Fotene", en: "Images / photos", fr: "Images / photos", pt: "Imagens / fotos" },
af3_logo: { de: "Logo", "de-CH": "Logo", en: "Logo", fr: "Logo", pt: "Logótipo" },
af3_domain: { de: "Domain (Internetadresse)", "de-CH": "Domain (Internetadress)", en: "Domain (web address)", fr: "Domaine (adresse web)", pt: "Domínio (endereço web)" },
af3_ja: { de: "Habe ich", "de-CH": "Han i", en: "I have it", fr: "Je l'ai", pt: "Tenho" },
af3_teilweise: { de: "Teilweise", "de-CH": "Teilwiis", en: "Partly", fr: "En partie", pt: "Em parte" },
af3_nein: { de: "Noch nicht", "de-CH": "No nöd", en: "Not yet", fr: "Pas encore", pt: "Ainda não" },
af3_budget_label: { de: "Welcher Rahmen ist realistisch?", "de-CH": "Wele Rahme isch realistisch?", en: "What budget is realistic?", fr: "Quel budget est réaliste ?", pt: "Que orçamento é realista?" },
af3_budget_hilfe: {
de: "Keine Bindung — die Angabe hilft nur, dir kein Angebot zu schreiben, das an deinem Rahmen vorbeigeht.",
"de-CH": "Kei Bindig — d Aagab hilft nur, dir kes Aagebot z schriibe, wo a dim Rahme verbii goht.",
en: "Not binding — it only helps to avoid writing you a quote that misses your budget entirely.",
fr: "Sans engagement — cela évite simplement de te proposer un devis hors de ton budget.",
pt: "Sem compromisso — serve apenas para não te enviar um orçamento completamente fora do teu alcance.",
},
af3_budget_wahl: { de: "Bitte auswählen", "de-CH": "Bitte uswähle", en: "Please choose", fr: "Choisis", pt: "Escolhe" },
af3_budget_1: { de: "bis 500 €", "de-CH": "bis 500 €", en: "up to €500", fr: "jusqu'à 500 €", pt: "até 500 €" },
af3_budget_2: { de: "500 – 1.000 €", "de-CH": "500 – 1.000 €", en: "€500 – €1,000", fr: "500 – 1 000 €", pt: "500 – 1.000 €" },
af3_budget_3: { de: "1.000 – 2.500 €", "de-CH": "1.000 – 2.500 €", en: "€1,000 – €2,500", fr: "1 000 – 2 500 €", pt: "1.000 – 2.500 €" },
af3_budget_4: { de: "über 2.500 €", "de-CH": "über 2.500 €", en: "over €2,500", fr: "plus de 2 500 €", pt: "mais de 2.500 €" },
af3_budget_5: { de: "Weiß ich noch nicht", "de-CH": "Weiss i no nöd", en: "I don't know yet", fr: "Je ne sais pas encore", pt: "Ainda não sei" },
af3_termin_label: { de: "Bis wann sollte es fertig sein?", "de-CH": "Bis wenn sött s fertig si?", en: "By when should it be ready?", fr: "Pour quand ?", pt: "Para quando deve estar pronto?" },
af3_termin_hilfe: {
de: "Die Zeitrahmen auf der Preisseite gelten ab Eingang der Anzahlung, nicht ab heute.",
"de-CH": "D Ziitrahme uf dr Priissite gälted ab em Iigang vo dr Aazahlig, nöd ab hüt.",
en: "The timeframes on the pricing page start when the deposit arrives, not today.",
fr: "Les délais indiqués courent à partir de la réception de l'acompte, pas d'aujourd'hui.",
pt: "Os prazos indicados contam a partir da entrada do sinal, não a partir de hoje.",
},
/* ---------------- Schritt 4 ---------------- */
af4_h2: { de: "Wie erreiche ich dich?", "de-CH": "Wie erreiche ich dich?", en: "How do I reach you?", fr: "Comment te joindre ?", pt: "Como te contacto?" },
af4_name: { de: "Name", "de-CH": "Name", en: "Name", fr: "Nom", pt: "Nome" },
af4_name_platzhalter: { de: "Vor- und Nachname oder Firma", "de-CH": "Vor- und Nachname oder Firma", en: "First and last name, or company", fr: "Prénom et nom, ou société", pt: "Nome próprio e apelido, ou empresa" },
af4_email: { de: "E-Mail", "de-CH": "E-Mail", en: "E-mail", fr: "E-mail", pt: "E-mail" },
af4_telefon: { de: "Telefon", "de-CH": "Telefon", en: "Phone", fr: "Téléphone", pt: "Telefone" },
af4_weg: { de: "Wie darf ich mich melden?", "de-CH": "Wie dörf i mich mälde?", en: "How may I get back to you?", fr: "Comment puis-je te répondre ?", pt: "Como posso responder-te?" },
af4_weg_email: { de: "Per E-Mail", "de-CH": "Per E-Mail", en: "By e-mail", fr: "Par e-mail", pt: "Por e-mail" },
af4_weg_telefon: { de: "Per Telefon", "de-CH": "Per Telefon", en: "By phone", fr: "Par téléphone", pt: "Por telefone" },
af4_bemerkung: { de: "Noch etwas, das ich wissen sollte?", "de-CH": "No öppis, wo i wüsse sött?", en: "Anything else I should know?", fr: "Autre chose à savoir ?", pt: "Mais alguma coisa que eu deva saber?" },
af4_bemerkung_hilfe: {
de: "Auch Fragen an mich gehören hier hinein.",
"de-CH": "Au Froge a mich ghöred do ine.",
en: "Questions for me belong here too.",
fr: "Tes questions pour moi ont aussi leur place ici.",
pt: "Perguntas para mim também cabem aqui.",
},
af4_datenschutz: {
de: "Ich bin damit einverstanden, dass meine Angaben zur Bearbeitung dieser Anfrage gespeichert werden. Sie werden nicht weitergegeben und können jederzeit gelöscht werden.",
"de-CH": "Ich bi demit iiverstande, dass mini Aagabe zur Bearbeitig vo dere Aafrog gspeicheret wärded. Si wärded nöd wiitergäh und chönd jederziit glöscht wärde.",
en: "I agree that my details may be stored to process this enquiry. They will not be passed on and can be deleted at any time.",
fr: "J'accepte que mes données soient conservées pour traiter cette demande. Elles ne seront pas transmises et peuvent être supprimées à tout moment.",
pt: "Concordo que os meus dados sejam guardados para tratar este pedido. Não serão partilhados e podem ser apagados a qualquer momento.",
},
af4_datenschutz_link: { de: "Datenschutz nachlesen", "de-CH": "Dateschutz nacheläse", en: "Read the privacy notice", fr: "Lire la politique de confidentialité", pt: "Ler a política de privacidade" },
/* --- Zusammenfassung --- */
af_zus_h2: {
de: "Passt alles so?",
"de-CH": "Passt alles so?",
en: "Does everything look right?",
fr: "Tout est correct ?",
pt: "Está tudo certo?",
},
af_zus_hilfe: {
de: "Das schickst du ab. Du kannst jeden Punkt noch ändern.",
"de-CH": "Das schicksch ab. Du chasch jede Punkt no ändere.",
en: "This is what you are sending. You can still change any item.",
fr: "Voici ce que tu envoies. Tu peux encore modifier chaque point.",
pt: "É isto que vais enviar. Ainda podes alterar qualquer ponto.",
},
af_zus_aendern: { de: "Ändern", "de-CH": "Ändere", en: "Change", fr: "Modifier", pt: "Alterar" },
af_zus_leer: { de: "keine Angabe", "de-CH": "kei Aagab", en: "not specified", fr: "non renseigné", pt: "sem indicação" },
af_kein_vertrag_titel: {
de: "Das ist noch kein Auftrag",
"de-CH": "Das isch no kein Uftrag",
en: "This is not an order yet",
fr: "Ceci n'est pas encore une commande",
pt: "Isto ainda não é uma encomenda",
},
af_kein_vertrag_text: {
de: "Mit dem Absenden entsteht kein Vertrag und keine Zahlungspflicht. Du bekommst zuerst ein schriftliches Angebot und entscheidest dann in Ruhe.",
"de-CH": "Mit em Abschicke entstoht kein Vertrag und kei Zahligspflicht. Du überchunnsch zerscht es schriftlichs Aagebot und entscheidsch denn i Rueh.",
en: "Sending this creates no contract and no obligation to pay. You first receive a written quote and then decide at your own pace.",
fr: "L'envoi ne crée ni contrat ni obligation de paiement. Tu reçois d'abord un devis écrit et décides ensuite tranquillement.",
pt: "Enviar isto não cria contrato nem obrigação de pagamento. Recebes primeiro um orçamento escrito e decides com calma.",
},
/* --- Nach dem Absenden --- */
af_danke_h2: {
de: "Angekommen!",
"de-CH": "Aacho!",
en: "Received!",
fr: "Bien reçu !",
pt: "Recebido!",
},
af_danke_text: {
de: "Deine Anfrage liegt bei mir. Ich melde mich innerhalb weniger Tage — entweder mit Rückfragen oder direkt mit einem Angebot.",
"de-CH": "Dini Aafrog ligt bi mir. Ich mälde mich innert weniger Täg — entweder mit Rückfroge oder grad mit eme Aagebot.",
en: "Your enquiry has reached me. I will get back to you within a few days — either with questions or directly with a quote.",
fr: "Ta demande m'est parvenue. Je reviens vers toi sous quelques jours — avec des questions ou directement un devis.",
pt: "O teu pedido chegou-me. Volto a contactar-te dentro de poucos dias — com perguntas ou diretamente com um orçamento.",
},
af_danke_nummer: {
de: "Deine Anfragenummer",
"de-CH": "Dini Aafrognummere",
en: "Your enquiry number",
fr: "Ton numéro de demande",
pt: "O teu número de pedido",
},
af_danke_notieren: {
de: "Notier sie dir — mit dieser Nummer findest du deine Anfrage sofort wieder, wenn du nachfragst.",
"de-CH": "Schriib sie dir uf — mit dere Nummere findsch dini Aafrog sofort wieder, wenn du nachefrogsch.",
en: "Note it down — with this number your enquiry is found immediately if you follow up.",
fr: "Note-le — avec ce numéro, ta demande est retrouvée immédiatement si tu nous relances.",
pt: "Aponta-o — com este número o teu pedido é encontrado de imediato se voltares a contactar.",
},
af_danke_zurueck: { de: "Zurück zur Startseite", "de-CH": "Zrugg zur Startsite", en: "Back to the home page", fr: "Retour à l'accueil", pt: "Voltar ao início" },
/* --- Fehlermeldungen --- */
af_fehler_name: { de: "Bitte gib deinen Namen an.", "de-CH": "Bitte gib din Name aa.", en: "Please enter your name.", fr: "Merci d'indiquer ton nom.", pt: "Indica o teu nome." },
af_fehler_email: { de: "Bitte gib eine gültige E-Mail-Adresse an.", "de-CH": "Bitte gib e gültigi E-Mail-Adress aa.", en: "Please enter a valid e-mail address.", fr: "Merci d'indiquer une adresse e-mail valide.", pt: "Indica um endereço de e-mail válido." },
af_fehler_paket: { de: "Bitte wähle aus, worum es ungefähr geht.", "de-CH": "Bitte wähl us, um was es öppe goht.", en: "Please choose what this is roughly about.", fr: "Merci de choisir de quoi il s'agit.", pt: "Escolhe de que se trata, aproximadamente." },
af_fehler_ziel: { de: "Bitte beschreib kurz, was die Seite erreichen soll.", "de-CH": "Bitte beschriib churz, was d Site erreiche söll.", en: "Please describe briefly what the site should achieve.", fr: "Décris brièvement l'objectif du site.", pt: "Descreve brevemente o que o site deve alcançar." },
af_fehler_datenschutz: { de: "Ohne diese Zustimmung dürfen wir deine Angaben nicht speichern.", "de-CH": "Ohni die Zuestimmig dörfed mir dini Aagabe nöd speichere.", en: "Without this consent we may not store your details.", fr: "Sans ce consentement, nous ne pouvons pas conserver tes données.", pt: "Sem este consentimento não podemos guardar os teus dados." },
af_fehler_telefon: {
de: "Du möchtest telefonisch kontaktiert werden — bitte gib dafür eine Nummer an.",
"de-CH": "Du wottsch telefonisch kontaktiert wärde — bitte gib defür e Nummere aa.",
en: "You asked to be contacted by phone — please provide a number.",
fr: "Tu souhaites être contacté par téléphone — merci d'indiquer un numéro.",
pt: "Pediste contacto por telefone — indica um número.",
},
af_fehler_schritt: {
de: "Bitte prüfe die markierten Felder.",
"de-CH": "Bitte prüef die markierte Fälder.",
en: "Please check the highlighted fields.",
fr: "Merci de vérifier les champs signalés.",
pt: "Verifica os campos assinalados.",
},
};
+4 -4
View File
@@ -13,8 +13,8 @@
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
<meta name="apple-mobile-web-app-title" content="Dogfather Webdesign" />
<link rel="stylesheet" href="/assets/css/main.css?v=20260822wd" />
<link rel="stylesheet" href="/assets/css/webdesign.css?v=20260822wd" />
<link rel="stylesheet" href="/assets/css/main.css?v=20260822wd2" />
<link rel="stylesheet" href="/assets/css/webdesign.css?v=20260822wd2" />
<style>
/* Zeitstrahl: die Linie verbindet die Nummernkreise. Sie liegt hinter
den Kreisen (z-index) und endet beim letzten Schritt, damit sie nicht
@@ -183,7 +183,7 @@
<div id="wd-fuss"></div>
<script src="/assets/js/i18n-wd-ablauf.js?v=20260822wd"></script>
<script src="/assets/js/wd-core.js?v=20260822wd"></script>
<script src="/assets/js/i18n-wd-ablauf.js?v=20260822wd2"></script>
<script src="/assets/js/wd-core.js?v=20260822wd2"></script>
</body>
</html>
+4 -4
View File
@@ -13,8 +13,8 @@
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
<meta name="apple-mobile-web-app-title" content="Dogfather Webdesign" />
<link rel="stylesheet" href="/assets/css/main.css?v=20260822wd" />
<link rel="stylesheet" href="/assets/css/webdesign.css?v=20260822wd" />
<link rel="stylesheet" href="/assets/css/main.css?v=20260822wd2" />
<link rel="stylesheet" href="/assets/css/webdesign.css?v=20260822wd2" />
<style>
.fq-thema { scroll-margin-top: 90px; }
.fq-thema + .fq-thema { margin-top: clamp(2rem, 4vw, 3rem); }
@@ -160,7 +160,7 @@
<div id="wd-fuss"></div>
<script src="/assets/js/i18n-wd-faq.js?v=20260822wd"></script>
<script src="/assets/js/wd-core.js?v=20260822wd"></script>
<script src="/assets/js/i18n-wd-faq.js?v=20260822wd2"></script>
<script src="/assets/js/wd-core.js?v=20260822wd2"></script>
</body>
</html>
+4 -4
View File
@@ -16,8 +16,8 @@
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
<meta name="apple-mobile-web-app-title" content="Dogfather Webdesign" />
<link rel="stylesheet" href="/assets/css/main.css?v=20260822wd" />
<link rel="stylesheet" href="/assets/css/webdesign.css?v=20260822wd" />
<link rel="stylesheet" href="/assets/css/main.css?v=20260822wd2" />
<link rel="stylesheet" href="/assets/css/webdesign.css?v=20260822wd2" />
<style>
/* ---------- Nur auf der Startseite gebraucht ---------- */
@@ -407,8 +407,8 @@
<div id="wd-fuss"></div>
<script src="/assets/js/i18n-wd-index.js?v=20260822wd"></script>
<script src="/assets/js/wd-core.js?v=20260822wd"></script>
<script src="/assets/js/i18n-wd-index.js?v=20260822wd2"></script>
<script src="/assets/js/wd-core.js?v=20260822wd2"></script>
<script>
/* ---------------------------------------------------------------------
Marken-Intro (Masterplan S.3, Schritt 02: "Nach erfolgreichem Code
+4 -4
View File
@@ -13,8 +13,8 @@
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
<meta name="apple-mobile-web-app-title" content="Dogfather Webdesign" />
<link rel="stylesheet" href="/assets/css/main.css?v=20260822wd" />
<link rel="stylesheet" href="/assets/css/webdesign.css?v=20260822wd" />
<link rel="stylesheet" href="/assets/css/main.css?v=20260822wd2" />
<link rel="stylesheet" href="/assets/css/webdesign.css?v=20260822wd2" />
<style>
/* Sprungmarken-Leiste: auf schmalen Bildschirmen waagerecht wischbar
statt umbrechend — vier lange deutsche Paketnamen ergeben sonst vier
@@ -301,7 +301,7 @@
<div id="wd-fuss"></div>
<script src="/assets/js/i18n-wd-leistungen.js?v=20260822wd"></script>
<script src="/assets/js/wd-core.js?v=20260822wd"></script>
<script src="/assets/js/i18n-wd-leistungen.js?v=20260822wd2"></script>
<script src="/assets/js/wd-core.js?v=20260822wd2"></script>
</body>
</html>
+4 -4
View File
@@ -13,8 +13,8 @@
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
<meta name="apple-mobile-web-app-title" content="Dogfather Webdesign" />
<link rel="stylesheet" href="/assets/css/main.css?v=20260822wd" />
<link rel="stylesheet" href="/assets/css/webdesign.css?v=20260822wd" />
<link rel="stylesheet" href="/assets/css/main.css?v=20260822wd2" />
<link rel="stylesheet" href="/assets/css/webdesign.css?v=20260822wd2" />
<style>
.pfo-projekt { overflow: hidden; padding: 0; margin-bottom: clamp(1.4rem, 3vw, 2.2rem); }
.pfo-bild {
@@ -192,7 +192,7 @@
<div id="wd-fuss"></div>
<script src="/assets/js/i18n-wd-portfolio.js?v=20260822wd"></script>
<script src="/assets/js/wd-core.js?v=20260822wd"></script>
<script src="/assets/js/i18n-wd-portfolio.js?v=20260822wd2"></script>
<script src="/assets/js/wd-core.js?v=20260822wd2"></script>
</body>
</html>
+4 -4
View File
@@ -13,8 +13,8 @@
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
<meta name="apple-mobile-web-app-title" content="Dogfather Webdesign" />
<link rel="stylesheet" href="/assets/css/main.css?v=20260822wd" />
<link rel="stylesheet" href="/assets/css/webdesign.css?v=20260822wd" />
<link rel="stylesheet" href="/assets/css/main.css?v=20260822wd2" />
<link rel="stylesheet" href="/assets/css/webdesign.css?v=20260822wd2" />
<style>
.pr-preis-gross { font-family: var(--font-head); font-weight: 800; color: var(--wd-blau); white-space: nowrap; }
@@ -196,7 +196,7 @@
<div id="wd-fuss"></div>
<script src="/assets/js/i18n-wd-preise.js?v=20260822wd"></script>
<script src="/assets/js/wd-core.js?v=20260822wd"></script>
<script src="/assets/js/i18n-wd-preise.js?v=20260822wd2"></script>
<script src="/assets/js/wd-core.js?v=20260822wd2"></script>
</body>
</html>
+41 -5
View File
@@ -34,7 +34,7 @@
Start alle alten Zwischenspeicher weg. Muss bei jeder Änderung an den
Dateien unten hochgezählt werden, sonst hängen Nutzer auf einem alten
Stand fest. */
const CACHE_NAME = "dogfather-webdesign-v1";
const CACHE_NAME = "dogfather-webdesign-v2";
/* Bausteine, die die Oberfläche zum Anzeigen braucht. Bewusst KEINE
HTML-Datei in dieser Liste. */
@@ -126,11 +126,45 @@ self.addEventListener("fetch", (ereignis) => {
/* --- Zugangs-Endpunkte niemals zwischenspeichern --- */
if (url.pathname.startsWith("/webdesign/zugang-")) return;
/* --- Neutrale Bausteine: erst Zwischenspeicher, dann Netz ---
"stale-while-revalidate": die gespeicherte Fassung geht sofort raus
(die Seite baut sich ohne Wartezeit auf), im Hintergrund wird eine
frische geholt und für das nächste Mal abgelegt. */
/* --- Neutrale Bausteine unter /assets/ ---
⚠️ HIER WAR EIN ECHTER FEHLER (gemeldet 22.08.2026: "immer noch so").
Vorher lief das als "stale-while-revalidate": die gespeicherte Fassung
ging sofort raus, im Hintergrund wurde eine frische geholt. Das ist
schnell — bedeutet aber, dass jede Korrektur an CSS oder JavaScript
beim ersten Aufruf UNSICHTBAR bleibt und erst beim zweiten wirkt.
Genau das ist passiert: die Korrektur am gestreckten Logo war auf dem
Server längst live, der Browser hat aber die alte Stilvorlage aus
meinem eigenen Zwischenspeicher angezeigt. Ein Fehler, der wie ein
nicht behobener Fehler aussieht — die unangenehmste Sorte.
Jetzt: erst Netz, Zwischenspeicher nur als Notfallnetz.
Für Stilvorlagen und Skripte ist Richtigkeit wichtiger als die
eingesparten Millisekunden — eine falsche Darstellung kostet mehr
als ein kurzer Ladevorgang. Bilder und Symbole bleiben beim schnellen
Weg: die ändern sich praktisch nie, und wenn doch, bekommen sie einen
neuen Dateinamen. */
if (url.pathname.startsWith("/assets/")) {
const istCodeOderStil = /\.(css|js|mjs)$/i.test(url.pathname);
if (istCodeOderStil) {
// Netz zuerst
ereignis.respondWith(
fetch(anfrage)
.then((antwort) => {
if (antwort && antwort.status === 200 && antwort.type === "basic") {
const kopie = antwort.clone();
caches.open(CACHE_NAME).then((speicher) => speicher.put(anfrage, kopie));
}
return antwort;
})
.catch(() => caches.match(anfrage).then((g) => g || Response.error()))
);
return;
}
// Bilder, Symbole, Schriften: Zwischenspeicher zuerst
ereignis.respondWith(
caches.open(CACHE_NAME).then(async (speicher) => {
const gespeichert = await speicher.match(anfrage);
@@ -149,7 +183,9 @@ self.addEventListener("fetch", (ereignis) => {
return gespeichert || (await ausDemNetz) || Response.error();
})
);
return;
}
/* Alles Übrige: kein respondWith -> der Browser macht es ganz normal
selbst. Das ist die sicherste Voreinstellung. */
});
+4 -4
View File
@@ -13,8 +13,8 @@
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
<meta name="apple-mobile-web-app-title" content="Dogfather Webdesign" />
<link rel="stylesheet" href="/assets/css/main.css?v=20260822wd" />
<link rel="stylesheet" href="/assets/css/webdesign.css?v=20260822wd" />
<link rel="stylesheet" href="/assets/css/main.css?v=20260822wd2" />
<link rel="stylesheet" href="/assets/css/webdesign.css?v=20260822wd2" />
<style>
.ue-kopf { display: grid; grid-template-columns: minmax(0, .85fr) minmax(0, 1.15fr); gap: clamp(1.5rem, 4vw, 3rem); align-items: center; }
/* Auf dem Handy zuerst der Text, dann das Bild: sonst schiebt ein
@@ -127,7 +127,7 @@
<div id="wd-fuss"></div>
<script src="/assets/js/i18n-wd-ueber.js?v=20260822wd"></script>
<script src="/assets/js/wd-core.js?v=20260822wd"></script>
<script src="/assets/js/i18n-wd-ueber.js?v=20260822wd2"></script>
<script src="/assets/js/wd-core.js?v=20260822wd2"></script>
</body>
</html>
+4 -4
View File
@@ -21,8 +21,8 @@
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
<meta name="apple-mobile-web-app-title" content="Dogfather Webdesign" />
<link rel="stylesheet" href="/assets/css/main.css?v=20260822wd" />
<link rel="stylesheet" href="/assets/css/webdesign.css?v=20260822wd" />
<link rel="stylesheet" href="/assets/css/main.css?v=20260822wd2" />
<link rel="stylesheet" href="/assets/css/webdesign.css?v=20260822wd2" />
<style>
/* =====================================================================
Zugangswand — nur hier gebraucht, deshalb bewusst NICHT in
@@ -177,8 +177,8 @@
</div>
</main>
<script src="/assets/js/i18n-wd-zugang.js?v=20260822wd"></script>
<script src="/assets/js/wd-core.js?v=20260822wd"></script>
<script src="/assets/js/i18n-wd-zugang.js?v=20260822wd2"></script>
<script src="/assets/js/wd-core.js?v=20260822wd2"></script>
<script>
(function () {
"use strict";