feat: fix favicon embed, add impress and privacy pages

Include favicon.svg in go:embed directive to fix 404 in production.
Add legal notice (impress) and privacy policy pages with full EN/DE
translations, route handlers, and footer links.
This commit is contained in:
2026-02-11 01:09:56 +01:00
parent b30c0b5f36
commit a3d2867b40
7 changed files with 174 additions and 2 deletions

View File

@@ -89,6 +89,8 @@ func New(opts Options) (*Server, error) {
s.mux.HandleFunc("/", s.handleDashboard)
s.mux.HandleFunc("/setup", s.handleSetup)
s.mux.HandleFunc("/guide", s.handleGuide)
s.mux.HandleFunc("/impress", s.handleImpress)
s.mux.HandleFunc("/privacy", s.handlePrivacy)
// Health routes
s.mux.HandleFunc("/healthz", s.handleHealthz)
@@ -187,6 +189,14 @@ func (s *Server) handleGuide(w http.ResponseWriter, r *http.Request) {
s.renderPage(w, r, "guide", "guide.html")
}
func (s *Server) handleImpress(w http.ResponseWriter, r *http.Request) {
s.renderPage(w, r, "impress", "impress.html")
}
func (s *Server) handlePrivacy(w http.ResponseWriter, r *http.Request) {
s.renderPage(w, r, "privacy", "privacy.html")
}
func (s *Server) handleHealthz(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(map[string]string{"status": "ok"})

View File

@@ -2,5 +2,5 @@ package web
import "embed"
//go:embed all:templates all:js all:css all:i18n
//go:embed all:templates all:js all:css all:i18n favicon.svg
var FS embed.FS

View File

@@ -7,6 +7,8 @@
"dashboard": "Dashboard",
"setup": "Einrichtung",
"guide": "Anleitung",
"impress": "Impressum",
"privacy": "Datenschutz",
"language": "Sprache"
},
"setup": {
@@ -309,9 +311,54 @@
"tip4": "Nutzen Sie den Gaming-Schalter an Tagen, an denen Sie Ger\u00e4te unter Volllast betreiben."
}
},
"impress": {
"title": "Impressum",
"responsible": "Verantwortlich",
"country": "Deutschland",
"contact": "Kontakt",
"email": "E-Mail",
"disclaimer": {
"title": "Haftungsausschluss",
"text": "HeatGuard ist ein privates Projekt und wird ohne Gew\u00e4hr bereitgestellt. Es stellt keine professionelle Beratung dar. Hitzerisikobewertungen sind Sch\u00e4tzungen basierend auf Nutzerdaten und Wettervorhersagen \u2014 befolgen Sie bei Hitzeereignissen immer die offiziellen Hinweise."
}
},
"privacy": {
"title": "Datenschutzerkl\u00e4rung",
"intro": "HeatGuard ist darauf ausgelegt, die Datenerfassung zu minimieren. Diese Erkl\u00e4rung beschreibt, welche Daten verarbeitet und wohin sie gesendet werden.",
"localStorage": {
"title": "Lokale Datenspeicherung",
"text": "Alle Nutzerdaten (Profile, R\u00e4ume, Ger\u00e4te, Bewohner, Klimaanlagen, Vorhersage-Cache) werden ausschlie\u00dflich in der IndexedDB Ihres Browsers gespeichert. Auf dem Server werden keine Nutzerdaten gespeichert. Das L\u00f6schen Ihrer Browserdaten l\u00f6scht alle HeatGuard-Daten."
},
"cookie": {
"title": "Cookie",
"text": "HeatGuard setzt ein einziges Cookie (heatguard_lang), um Ihre Spracheinstellung zu speichern. Es l\u00e4uft nach einem Jahr ab und enth\u00e4lt nur den Sprachcode (en oder de). Weitere Cookies werden nicht verwendet."
},
"weather": {
"title": "Wettervorhersage-API",
"text": "Beim Abruf einer Vorhersage werden Ihre Standortkoordinaten (Breiten-/L\u00e4ngengrad) an Open-Meteo (open-meteo.com) oder OpenWeatherMap (openweathermap.org) gesendet, je nach Konfiguration. Diese Dienste haben eigene Datenschutzrichtlinien."
},
"warnings": {
"title": "Wetterwarnungen (DWD)",
"text": "Wetterwarnungen werden vom Deutschen Wetterdienst (DWD) \u00fcber dessen \u00f6ffentliche WFS-Schnittstelle abgerufen. Ihre Standortkoordinaten werden als Teil der Anfrage \u00fcbermittelt."
},
"llm": {
"title": "KI-Zusammenfassung (Optional)",
"text": "Wenn Sie einen KI-Anbieter konfigurieren, wird eine Zusammenfassung Ihrer Raumdaten und Wettervorhersage an den gew\u00e4hlten Anbieter (Anthropic, OpenAI, Google Gemini oder Ollama) gesendet. Ihr API-Schl\u00fcssel wird direkt aus Ihrem Browser gesendet. \u00dcber den Hitzeanalyse-Kontext hinaus werden keine pers\u00f6nlichen Daten \u00fcbermittelt."
},
"bettervent": {
"title": "Ger\u00e4tedatenbank (bettervent.me)",
"text": "Bei der Suche nach Klimager\u00e4ten wird Ihre Suchanfrage \u00fcber den HeatGuard-Server an bettervent.me weitergeleitet. In diesen Anfragen sind keine pers\u00f6nlichen Daten enthalten."
},
"noTracking": {
"title": "Keine Analyse oder Tracking",
"text": "HeatGuard verwendet keine Analyse-Tools, Tracking-Skripte, Fingerprinting oder sonstige Drittanbieter-Skripte. Es werden keine Daten an Werbetreibende oder Datenh\u00e4ndler weitergegeben."
}
},
"footer": {
"source": "Quellcode",
"license": "GPL-3.0-Lizenz",
"impress": "Impressum",
"privacy": "Datenschutz",
"betterventCredit": "W\u00e4rmepumpendaten bereitgestellt von"
},
"common": {

View File

@@ -7,6 +7,8 @@
"dashboard": "Dashboard",
"setup": "Setup",
"guide": "Guide",
"impress": "Impress",
"privacy": "Privacy",
"language": "Language"
},
"setup": {
@@ -309,9 +311,54 @@
"tip4": "Use the gaming toggle on days when you'll be running devices at full load."
}
},
"impress": {
"title": "Legal Notice",
"responsible": "Responsible Person",
"country": "Germany",
"contact": "Contact",
"email": "Email",
"disclaimer": {
"title": "Disclaimer",
"text": "HeatGuard is a personal project provided as-is, without warranty. It does not constitute professional advice. Heat risk assessments are estimates based on user-provided data and weather forecasts — always follow official guidance during heat events."
}
},
"privacy": {
"title": "Privacy Policy",
"intro": "HeatGuard is designed to minimize data collection. This policy describes what data is processed and where it is sent.",
"localStorage": {
"title": "Local Data Storage",
"text": "All user data (profiles, rooms, devices, occupants, AC units, forecast cache) is stored exclusively in your browser's IndexedDB. No user data is stored on the server. Clearing your browser data will delete all HeatGuard data."
},
"cookie": {
"title": "Cookie",
"text": "HeatGuard sets a single cookie (heatguard_lang) to remember your language preference. It expires after one year and contains only the language code (en or de). No other cookies are used."
},
"weather": {
"title": "Weather Forecast API",
"text": "When you fetch a forecast, your location coordinates (latitude/longitude) are sent to either Open-Meteo (open-meteo.com) or OpenWeatherMap (openweathermap.org), depending on your configuration. These services have their own privacy policies."
},
"warnings": {
"title": "Weather Warnings (DWD)",
"text": "Weather warnings are fetched from the German Weather Service (DWD) via their public WFS endpoint. Your location coordinates are sent as part of the request."
},
"llm": {
"title": "AI Summary (Optional)",
"text": "If you configure an AI provider, a summary of your room data and weather forecast is sent to the selected provider (Anthropic, OpenAI, Google Gemini, or Ollama). Your API key is sent directly from your browser. No personal data beyond the heat analysis context is included."
},
"bettervent": {
"title": "Device Database (bettervent.me)",
"text": "When you search for AC units, your search query is proxied through the HeatGuard server to bettervent.me. No personal data is included in these requests."
},
"noTracking": {
"title": "No Analytics or Tracking",
"text": "HeatGuard does not use analytics, tracking scripts, fingerprinting, or any third-party scripts. No data is shared with advertisers or data brokers."
}
},
"footer": {
"source": "Source Code",
"license": "GPL-3.0 License",
"impress": "Legal Notice",
"privacy": "Privacy",
"betterventCredit": "Heat pump data powered by"
},
"common": {

View File

@@ -0,0 +1,25 @@
{{define "content"}}
<div class="max-w-3xl mx-auto space-y-8">
<h1 class="text-2xl font-bold">{{t "impress.title"}}</h1>
<div class="space-y-6">
<section class="bg-white dark:bg-gray-800 rounded-xl p-5 shadow-sm space-y-2">
<h2 class="text-lg font-semibold">{{t "impress.responsible"}}</h2>
<p class="text-sm text-gray-600 dark:text-gray-400">Christian Nachtigall</p>
<p class="text-sm text-gray-600 dark:text-gray-400">Karwendelstr. 21</p>
<p class="text-sm text-gray-600 dark:text-gray-400">82061 Neuried</p>
<p class="text-sm text-gray-600 dark:text-gray-400">{{t "impress.country"}}</p>
</section>
<section class="bg-white dark:bg-gray-800 rounded-xl p-5 shadow-sm space-y-2">
<h2 class="text-lg font-semibold">{{t "impress.contact"}}</h2>
<p class="text-sm text-gray-600 dark:text-gray-400">{{t "impress.email"}}: christian@nachtigall.dev</p>
</section>
<section class="bg-white dark:bg-gray-800 rounded-xl p-5 shadow-sm space-y-2">
<h2 class="text-lg font-semibold">{{t "impress.disclaimer.title"}}</h2>
<p class="text-sm text-gray-600 dark:text-gray-400">{{t "impress.disclaimer.text"}}</p>
</section>
</div>
</div>
{{end}}

View File

@@ -39,7 +39,7 @@
<footer class="border-t border-gray-200 dark:border-gray-700 mt-12 py-4">
<div class="mx-auto px-4 sm:px-6 lg:px-8 text-center text-xs text-gray-400 dark:text-gray-500 space-y-1">
<div>{{t "app.name"}} v1.0.0 — {{t "app.tagline"}}</div>
<div><a href="https://somegit.dev/vikingowl/HeatGuard" class="hover:text-orange-600 dark:hover:text-orange-400" target="_blank" rel="noopener">{{t "footer.source"}}</a> · {{t "footer.license"}}</div>
<div><a href="https://somegit.dev/vikingowl/HeatGuard" class="hover:text-orange-600 dark:hover:text-orange-400" target="_blank" rel="noopener">{{t "footer.source"}}</a> · {{t "footer.license"}} · <a href="/impress" class="hover:text-orange-600 dark:hover:text-orange-400">{{t "footer.impress"}}</a> · <a href="/privacy" class="hover:text-orange-600 dark:hover:text-orange-400">{{t "footer.privacy"}}</a></div>
<div>{{t "footer.betterventCredit"}} <a href="https://bettervent.me" class="hover:text-orange-600 dark:hover:text-orange-400" target="_blank" rel="noopener">bettervent.me</a></div>
</div>
</footer>

View File

@@ -0,0 +1,43 @@
{{define "content"}}
<div class="max-w-3xl mx-auto space-y-8">
<h1 class="text-2xl font-bold">{{t "privacy.title"}}</h1>
<p class="text-gray-600 dark:text-gray-400">{{t "privacy.intro"}}</p>
<div class="space-y-6">
<section class="bg-white dark:bg-gray-800 rounded-xl p-5 shadow-sm space-y-2">
<h2 class="text-lg font-semibold">{{t "privacy.localStorage.title"}}</h2>
<p class="text-sm text-gray-600 dark:text-gray-400">{{t "privacy.localStorage.text"}}</p>
</section>
<section class="bg-white dark:bg-gray-800 rounded-xl p-5 shadow-sm space-y-2">
<h2 class="text-lg font-semibold">{{t "privacy.cookie.title"}}</h2>
<p class="text-sm text-gray-600 dark:text-gray-400">{{t "privacy.cookie.text"}}</p>
</section>
<section class="bg-white dark:bg-gray-800 rounded-xl p-5 shadow-sm space-y-2">
<h2 class="text-lg font-semibold">{{t "privacy.weather.title"}}</h2>
<p class="text-sm text-gray-600 dark:text-gray-400">{{t "privacy.weather.text"}}</p>
</section>
<section class="bg-white dark:bg-gray-800 rounded-xl p-5 shadow-sm space-y-2">
<h2 class="text-lg font-semibold">{{t "privacy.warnings.title"}}</h2>
<p class="text-sm text-gray-600 dark:text-gray-400">{{t "privacy.warnings.text"}}</p>
</section>
<section class="bg-white dark:bg-gray-800 rounded-xl p-5 shadow-sm space-y-2">
<h2 class="text-lg font-semibold">{{t "privacy.llm.title"}}</h2>
<p class="text-sm text-gray-600 dark:text-gray-400">{{t "privacy.llm.text"}}</p>
</section>
<section class="bg-white dark:bg-gray-800 rounded-xl p-5 shadow-sm space-y-2">
<h2 class="text-lg font-semibold">{{t "privacy.bettervent.title"}}</h2>
<p class="text-sm text-gray-600 dark:text-gray-400">{{t "privacy.bettervent.text"}}</p>
</section>
<section class="bg-white dark:bg-gray-800 rounded-xl p-5 shadow-sm space-y-2">
<h2 class="text-lg font-semibold">{{t "privacy.noTracking.title"}}</h2>
<p class="text-sm text-gray-600 dark:text-gray-400">{{t "privacy.noTracking.text"}}</p>
</section>
</div>
</div>
{{end}}