v0.30.2: Report geo-restriction + logo/favicon update + Hub geo auth

- Add GeoRestrictionReport to report types and builder, so Hub can
  display geo-blocking status on customer detail pages
- Update all 5 BuildReport() call sites with new geoRestriction param
- Add /api/geo/ to selfUpdateAuthMiddleware (Hub Bearer token auth)
- Replace embedded logo SVG with updated logo.svg (white text variant)
- Add FelhomFaviconSVG constant + /static/favicon.svg route
- Update layout.html and catchall.html favicon links

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-25 12:42:51 +01:00
parent e61e164cf7
commit 2ad743b66f
9 changed files with 449 additions and 41 deletions
+11
View File
@@ -1,5 +1,16 @@
## Changelog ## Changelog
### v0.30.2 — Report geo-restriction + logo/favicon update (2026-02-25)
#### Added
- **Geo-restriction in reports** (`internal/report/`) — New `GeoRestrictionReport` struct and `geo_restriction` field in the Report JSON. Hub can now display current geo-blocking status (enabled, allowed countries, per-app overrides, sync state) on customer detail pages.
- **Favicon route** (`/static/favicon.svg`) — Separate favicon SVG served from synced assets or embedded fallback. Uses the cloud icon from `logo_favicon_2.svg`.
- **Hub Bearer auth for geo API** — `/api/geo/` routes now accept `selfUpdateAuthMiddleware` (session auth OR Hub API key), allowing the Hub to send geo-disable commands to controllers.
#### Changed
- **Logo SVG updated** (`internal/web/templates.go`) — Replaced embedded logo with the latest `logo.svg` from the website (white text variant).
- **Favicon link** — Layout and catch-all templates now reference `/static/favicon.svg` instead of the full logo.
### v0.30.1 — Geo-Restriction fix (2026-02-25) ### v0.30.1 — Geo-Restriction fix (2026-02-25)
#### Fixed #### Fixed
+2 -2
View File
@@ -4,7 +4,7 @@
A single, lightweight Go container that replaces Portainer + scattered systemd scripts with a unified, Hungarian-language web dashboard for managing Docker Compose stacks, backups, storage, monitoring, and notifications on customer hardware. A single, lightweight Go container that replaces Portainer + scattered systemd scripts with a unified, Hungarian-language web dashboard for managing Docker Compose stacks, backups, storage, monitoring, and notifications on customer hardware.
**Current version: v0.30.1** **Current version: v0.30.2**
--- ---
@@ -1170,7 +1170,7 @@ Thread-safe access via `GetGeoRestriction()`, `SetGeoRestriction()`, `SetGeoAppO
| POST | `/api/stacks/{name}/geo/override` | Set per-app country override | | POST | `/api/stacks/{name}/geo/override` | Set per-app country override |
| DELETE | `/api/stacks/{name}/geo/override` | Remove per-app override | | DELETE | `/api/stacks/{name}/geo/override` | Remove per-app override |
All mutating endpoints trigger an async Cloudflare sync. All mutating endpoints trigger an async Cloudflare sync. The `/api/geo/` path accepts both session auth and Hub Bearer token auth (via `selfUpdateAuthMiddleware`), enabling Hub-side geo-disable for lockout recovery.
#### Sync Triggers #### Sync Triggers
+7 -5
View File
@@ -368,7 +368,7 @@ func main() {
pushInterval = 15 * time.Minute pushInterval = 15 * time.Minute
} }
sched.Every("hub-report", pushInterval, func(ctx context.Context) error { sched.Every("hub-report", pushInterval, func(ctx context.Context) error {
r := report.BuildReport(cfg, *configPath, stackMgr, backupMgr, cpuCollector, metricsStore, Version, sett.GetStoragePaths(), logger) r := report.BuildReport(cfg, *configPath, stackMgr, backupMgr, cpuCollector, metricsStore, Version, sett.GetStoragePaths(), sett.GetGeoRestriction(), logger)
if err := hubPusher.Push(r); err != nil { if err := hubPusher.Push(r); err != nil {
return err return err
} }
@@ -434,7 +434,7 @@ func main() {
}) })
if hubPusher != nil { if hubPusher != nil {
storageWatchdog.SetHubReportPusher(func() { storageWatchdog.SetHubReportPusher(func() {
r := report.BuildReport(cfg, *configPath, stackMgr, backupMgr, cpuCollector, metricsStore, Version, sett.GetStoragePaths(), logger) r := report.BuildReport(cfg, *configPath, stackMgr, backupMgr, cpuCollector, metricsStore, Version, sett.GetStoragePaths(), sett.GetGeoRestriction(), logger)
hubPusher.Push(r) hubPusher.Push(r)
}) })
} }
@@ -515,7 +515,7 @@ func main() {
// Hub report // Hub report
if hubPusher != nil { if hubPusher != nil {
if cfg.Hub.Enabled { if cfg.Hub.Enabled {
r := report.BuildReport(cfg, *configPath, stackMgr, backupMgr, cpuCollector, metricsStore, Version, sett.GetStoragePaths(), logger) r := report.BuildReport(cfg, *configPath, stackMgr, backupMgr, cpuCollector, metricsStore, Version, sett.GetStoragePaths(), sett.GetGeoRestriction(), logger)
var pushErr error var pushErr error
for attempt := 1; attempt <= 3; attempt++ { for attempt := 1; attempt <= 3; attempt++ {
pushErr = hubPusher.Push(r) pushErr = hubPusher.Push(r)
@@ -674,7 +674,7 @@ func main() {
dc := &web.DebugCallbacks{} dc := &web.DebugCallbacks{}
if hubPusher != nil { if hubPusher != nil {
dc.TriggerHubReportPush = func() error { dc.TriggerHubReportPush = func() error {
r := report.BuildReport(cfg, *configPath, stackMgr, backupMgr, cpuCollector, metricsStore, Version, sett.GetStoragePaths(), logger) r := report.BuildReport(cfg, *configPath, stackMgr, backupMgr, cpuCollector, metricsStore, Version, sett.GetStoragePaths(), sett.GetGeoRestriction(), logger)
return hubPusher.Push(r) return hubPusher.Push(r)
} }
dc.TriggerHubInfraPush = func() error { dc.TriggerHubInfraPush = func() error {
@@ -740,7 +740,7 @@ func main() {
} }
if hubPusher != nil { if hubPusher != nil {
driveMigrator.PushHubReport = func() { driveMigrator.PushHubReport = func() {
r := report.BuildReport(cfg, *configPath, stackMgr, backupMgr, cpuCollector, metricsStore, Version, sett.GetStoragePaths(), logger) r := report.BuildReport(cfg, *configPath, stackMgr, backupMgr, cpuCollector, metricsStore, Version, sett.GetStoragePaths(), sett.GetGeoRestriction(), logger)
hubPusher.Push(r) hubPusher.Push(r)
} }
driveMigrator.PushInfraBackup = func() { driveMigrator.PushInfraBackup = func() {
@@ -771,6 +771,8 @@ func main() {
mux.Handle("/api/selfupdate/", selfUpdateAuthMiddleware(cfg, webServer, webServer.CsrfProtect(http.HandlerFunc(apiRouter.ServeHTTP)))) mux.Handle("/api/selfupdate/", selfUpdateAuthMiddleware(cfg, webServer, webServer.CsrfProtect(http.HandlerFunc(apiRouter.ServeHTTP))))
// Config API — accepts session auth OR hub API key (for Hub config push) // Config API — accepts session auth OR hub API key (for Hub config push)
mux.Handle("/api/config/", selfUpdateAuthMiddleware(cfg, webServer, webServer.CsrfProtect(http.HandlerFunc(apiRouter.ServeHTTP)))) mux.Handle("/api/config/", selfUpdateAuthMiddleware(cfg, webServer, webServer.CsrfProtect(http.HandlerFunc(apiRouter.ServeHTTP))))
// Geo API — accepts session auth OR hub API key (for Hub geo-disable)
mux.Handle("/api/geo/", selfUpdateAuthMiddleware(cfg, webServer, webServer.CsrfProtect(http.HandlerFunc(apiRouter.ServeHTTP))))
mux.Handle("/api/", webServer.RequireAuth(webServer.CsrfProtect(http.HandlerFunc(apiRouter.ServeHTTP)))) mux.Handle("/api/", webServer.RequireAuth(webServer.CsrfProtect(http.HandlerFunc(apiRouter.ServeHTTP))))
// Web UI routes (auth required) // Web UI routes (auth required)
+18
View File
@@ -30,6 +30,7 @@ func BuildReport(
metricsStore *metrics.MetricsStore, metricsStore *metrics.MetricsStore,
version string, version string,
storagePaths []settings.StoragePath, storagePaths []settings.StoragePath,
geoRestriction *settings.GeoRestriction,
logger *log.Logger, logger *log.Logger,
) *Report { ) *Report {
debug := cfg.Logging.Level == "debug" debug := cfg.Logging.Level == "debug"
@@ -153,6 +154,23 @@ func BuildReport(
// App telemetry (metrics + log scan) // App telemetry (metrics + log scan)
r.AppTelemetry = buildAppTelemetrySection(stackMgr, metricsStore, logger) r.AppTelemetry = buildAppTelemetrySection(stackMgr, metricsStore, logger)
// Geo-restriction status
if geoRestriction != nil {
gr := &GeoRestrictionReport{
Enabled: geoRestriction.Enabled,
AllowedCountries: geoRestriction.AllowedCountries,
LastSync: geoRestriction.LastSync,
LastSyncError: geoRestriction.LastSyncError,
}
if len(geoRestriction.AppOverrides) > 0 {
gr.AppOverrides = make(map[string]GeoAppOverrideReport, len(geoRestriction.AppOverrides))
for k, v := range geoRestriction.AppOverrides {
gr.AppOverrides[k] = GeoAppOverrideReport{AllowedCountries: v.AllowedCountries}
}
}
r.GeoRestriction = gr
}
if debug && logger != nil { if debug && logger != nil {
logger.Printf("[DEBUG] BuildReport: complete — containers=%d, health=%s, deployed=%d, available=%d, app_telemetry=%d", logger.Printf("[DEBUG] BuildReport: complete — containers=%d, health=%s, deployed=%d, available=%d, app_telemetry=%d",
r.Containers.Total, r.Health.Status, len(r.Stacks.Deployed), len(r.Stacks.Available), len(r.AppTelemetry)) r.Containers.Total, r.Health.Status, len(r.Stacks.Deployed), len(r.Stacks.Available), len(r.AppTelemetry))
+16 -1
View File
@@ -22,7 +22,8 @@ type Report struct {
Backup BackupReport `json:"backup"` Backup BackupReport `json:"backup"`
Health HealthReport `json:"health"` Health HealthReport `json:"health"`
Stacks StacksReport `json:"stacks"` Stacks StacksReport `json:"stacks"`
AppTelemetry []AppTelemetry `json:"app_telemetry,omitempty"` AppTelemetry []AppTelemetry `json:"app_telemetry,omitempty"`
GeoRestriction *GeoRestrictionReport `json:"geo_restriction,omitempty"`
} }
// SystemReport holds host-level system info. // SystemReport holds host-level system info.
@@ -97,6 +98,20 @@ type StacksReport struct {
Available []string `json:"available"` Available []string `json:"available"`
} }
// GeoRestrictionReport holds geo-restriction status for hub display.
type GeoRestrictionReport struct {
Enabled bool `json:"enabled"`
AllowedCountries []string `json:"allowed_countries"`
AppOverrides map[string]GeoAppOverrideReport `json:"app_overrides,omitempty"`
LastSync string `json:"last_sync,omitempty"`
LastSyncError string `json:"last_sync_error,omitempty"`
}
// GeoAppOverrideReport holds per-app country override.
type GeoAppOverrideReport struct {
AllowedCountries []string `json:"allowed_countries"`
}
// AppTelemetry holds per-app (per-stack) resource and log telemetry. // AppTelemetry holds per-app (per-stack) resource and log telemetry.
type AppTelemetry struct { type AppTelemetry struct {
AppName string `json:"app_name"` AppName string `json:"app_name"`
+18
View File
@@ -275,6 +275,8 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
s.serveChartJSHandler(w, r) s.serveChartJSHandler(w, r)
case path == "/static/felhom-logo.svg": case path == "/static/felhom-logo.svg":
s.serveLogoHandler(w, r) s.serveLogoHandler(w, r)
case path == "/static/favicon.svg":
s.serveFaviconHandler(w, r)
case strings.HasPrefix(path, "/static/assets/"): case strings.HasPrefix(path, "/static/assets/"):
s.serveAsset(w, r, strings.TrimPrefix(path, "/static/assets/")) s.serveAsset(w, r, strings.TrimPrefix(path, "/static/assets/"))
case strings.HasPrefix(path, "/apps/"): case strings.HasPrefix(path, "/apps/"):
@@ -446,6 +448,22 @@ func (s *Server) serveLogoHandler(w http.ResponseWriter, r *http.Request) {
fmt.Fprint(w, FelhomLogoSVG) fmt.Fprint(w, FelhomLogoSVG)
} }
func (s *Server) serveFaviconHandler(w http.ResponseWriter, r *http.Request) {
// Try synced asset first
if s.assetsSyncer != nil {
path := s.assetsSyncer.Resolve("felhom-favicon.svg")
if _, err := os.Stat(path); err == nil {
w.Header().Set("Cache-Control", "public, max-age=86400")
http.ServeFile(w, r, path)
return
}
}
// Fallback to embedded favicon
w.Header().Set("Content-Type", "image/svg+xml")
w.Header().Set("Cache-Control", "public, max-age=86400")
fmt.Fprint(w, FelhomFaviconSVG)
}
// serveAsset serves baked-in app assets (logos, screenshots) from /usr/share/felhom/assets/ // serveAsset serves baked-in app assets (logos, screenshots) from /usr/share/felhom/assets/
const assetsDir = "/usr/share/felhom/assets" const assetsDir = "/usr/share/felhom/assets"
+375 -31
View File
@@ -1,35 +1,379 @@
package web package web
// FelhomLogoSVG is the felhom.eu logo, served at /static/felhom-logo.svg. // FelhomLogoSVG is the felhom.eu logo, served at /static/felhom-logo.svg.
// Cleaned from the original Inkscape SVG, removing editor metadata. // Updated from website/assets/logo.svg (white text variant).
const FelhomLogoSVG = `<?xml version="1.0" encoding="UTF-8"?> const FelhomLogoSVG = `<?xml version="1.0" encoding="UTF-8"?>
<svg viewBox="0 0 645.30703 408.36403" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> <svg
<defs> viewBox="0 0 645.30703 408.36403"
<linearGradient id="lg9"><stop offset="0"/><stop offset="0.99875164" style="stop-color:rgb(4,114,187)"/></linearGradient> version="1.1"
<linearGradient id="g1"><stop offset="0" style="stop-color:rgb(0,64,141)"/><stop offset="1" style="stop-color:rgb(0,141,223)"/></linearGradient> id="svg14"
<linearGradient id="g1-0" xlink:href="#g1" gradientUnits="userSpaceOnUse" x1="30.771" y1="2283.52" x2="30.771" y2="2416.4089" spreadMethod="pad" gradientTransform="matrix(0.999122,0,0,0.848244,1717.8096,192.633)"/> width="645.30701"
<linearGradient id="g1-1" xlink:href="#g1" gradientUnits="userSpaceOnUse" x1="30.849001" y1="2446.3101" x2="30.849001" y2="2588.6721" gradientTransform="matrix(0.996573,0,0,0.791798,1717.8096,192.63306)"/> height="408.36404"
<linearGradient id="g2"><stop offset="0.002"/><stop offset="1" style="stop-color:rgb(4,114,187)"/></linearGradient> xmlns:xlink="http://www.w3.org/1999/xlink"
<linearGradient id="lg14" xlink:href="#g1" gradientUnits="userSpaceOnUse" gradientTransform="matrix(0.999122,0,0,0.848244,-306.27885,-1772.6719)" x1="30.771" y1="2283.52" x2="30.771" y2="2416.4089" spreadMethod="pad"/> xmlns="http://www.w3.org/2000/svg"><defs
<linearGradient id="lg15" xlink:href="#g1" gradientUnits="userSpaceOnUse" gradientTransform="matrix(0.996573,0,0,0.791798,-306.27895,-1772.6718)" x1="30.849001" y1="2446.3101" x2="30.849001" y2="2588.6721"/> id="defs4"><linearGradient
<linearGradient id="lg17" xlink:href="#lg9" x1="160.76199" y1="268.35672" x2="284.18887" y2="268.80402" gradientUnits="userSpaceOnUse"/> id="linearGradient9"><stop
<linearGradient id="lg1" xlink:href="#lg9" gradientUnits="userSpaceOnUse" x1="160.76199" y1="268.35672" x2="284.18887" y2="268.80402" gradientTransform="translate(-9.2828853,15.718777)"/> offset="0"
</defs> style=""
<rect x="1740.2544" y="2129.6233" width="16.597" height="112.721" style="fill:url(#g1-0);stroke:url(#g1-1);paint-order:fill" transform="rotate(-89.99513)"/> id="stop8" /><stop
<g transform="translate(43.276659,-1.4142135)"> offset="0.99875164"
<text style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:140.132px;font-family:'Vremena Grotesk',Arial,sans-serif;text-align:center;text-anchor:middle;fill:#00408d;stroke:#051343;stroke-width:2.33554" x="189.29001" y="402.45694"><tspan x="189.29001" y="402.45694">f<tspan style="font-family:'M+ 2c',Arial,sans-serif">e</tspan>lhom</tspan></text> style="stop-color: rgb(4, 114, 187);"
<text style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:140.132px;font-family:'M+ 2c',Arial,sans-serif;text-align:center;text-anchor:middle;fill:#008ddf;stroke:#0472bb;stroke-width:2.33554" x="520.41119" y="401.63116"><tspan x="520.41119" y="401.63116">e<tspan style="font-family:'Vremena Grotesk',Arial,sans-serif">u</tspan></tspan></text> id="stop9" /></linearGradient><linearGradient
<circle style="fill:#008ddf;stroke:#0472bb;stroke-width:1.85226" cx="426.38022" cy="392.96091" r="10.150504"/> id="gradient-1"><stop
</g> offset="0"
<g> style="stop-color: rgb(0, 64, 141);"
<path style="fill:#00408d;stroke:#051343;stroke-width:2px" d="m 153.81527,161.41782 v 28.90235 h 105.70117 v -10.96094 h -24.28711 v -17.94141 z m 20.66992,8.46485 a 5.556,5.556 0 0 1 5.55664,5.55664 5.556,5.556 0 0 1 -5.55664,5.55468 5.556,5.556 0 0 1 -5.55664,-5.55468 5.556,5.556 0 0 1 5.55664,-5.55664 z m 20.47852,0.0215 a 5.556,5.556 0 0 1 5.55664,5.55664 5.556,5.556 0 0 1 -5.55664,5.55664 5.556,5.556 0 0 1 -5.55664,-5.55664 5.556,5.556 0 0 1 5.55664,-5.55664 z m 20.25586,0.14258 a 5.556,5.556 0 0 1 5.55664,5.55664 5.556,5.556 0 0 1 -5.55664,5.55469 5.556,5.556 0 0 1 -5.55664,-5.55469 5.556,5.556 0 0 1 5.55664,-5.55664 z"/> id="stop1" /><stop
<path style="fill:#00408d;stroke:#051343;stroke-width:2px" d="m 153.72738,200.94907 v 28.90039 h 105.70117 v -28.90039 z m 20.67188,9.46289 a 5.556,5.556 0 0 1 5.55468,5.55664 5.556,5.556 0 0 1 -5.55468,5.55664 5.556,5.556 0 0 1 -5.55665,-5.55664 5.556,5.556 0 0 1 5.55665,-5.55664 z m 20.47656,0.0234 a 5.556,5.556 0 0 1 5.55664,5.55469 5.556,5.556 0 0 1 -5.55664,5.55664 5.556,5.556 0 0 1 -5.55469,-5.55664 5.556,5.556 0 0 1 5.55469,-5.55469 z"/> offset="1"
<path style="fill:#00408d;stroke:#051343;stroke-width:1.9" d="m 197.77426,121.21274 v 28.90039 h 53.76953 l 38.35351,-28.90039 z m 20.67187,9.46289 a 5.556,5.556 0 0 1 5.55469,5.55664 5.556,5.556 0 0 1 -5.55469,5.55469 5.556,5.556 0 0 1 -5.55664,-5.55469 5.556,5.556 0 0 1 5.55664,-5.55664 z"/> style="stop-color: rgb(0, 141, 223);"
<path d="m 257.94649,264.90079 c -53.034,0 -99.796,-10.762 -127.437,-27.136 19.291,8.807 47.768,14.377 79.534,14.377 23.242,0 44.724,-2.982 62.135,-8.032 v -75.023 l -28.665,-0.001 115.772,-87.280004 48.409,36.495004 v -12.853 h 25.034 v 31.726 l 42.329,31.912 h -28.353 v 77.268 l -78.822,0.005 c -27.916,11.441 -66.856,18.542 -109.935,18.542 z m 102.245,-115.364 c -12.363,0 -22.385,10.022 -22.385,22.385 0,8.343 4.565,15.621 11.334,19.471 l -7.782,41.652 h 37.666 l -7.782,-41.652 c 6.769,-3.85 11.334,-11.127 11.334,-19.471 0,-12.363 -10.022,-22.385 -22.385,-22.385 z" style="fill:#00408d;stroke:#051343;stroke-width:2px"/> id="stop2" /></linearGradient><linearGradient
<path style="fill:#008ddf;stroke:#0472bb" d="m 522.38281,159.26953 c -16.13,0.388 -18.85575,5.44224 -48.34375,27.74024 -31.401,25.206 -62.71092,49.374 -117.91992,67 -12.11688,3.8684 -22.84543,6.83673 -36.66992,9.16601 -3.52327,0.53538 -7.07021,1.0572 -10.49219,1.52734 -11.11981,1.52774 -21.588,2.81477 -31.93359,3.76563 l -0.37205,20.49219 c 13.36471,0.34187 35.32192,0.38638 61.20023,-0.23523 25.87831,-0.62162 55.67773,-1.90936 84.72687,-4.23198 29.04914,-2.32263 73.441,-8.14839 90.28521,-12.88581 11.83463,-3.32848 28.09255,-8.72249 37.92989,-19.04615 9.46724,-10.59763 17.11931,-21.57912 18.56188,-36.64771 0.76581,-7.99943 -1.41558,-15.75159 -4.01758,-22.93359 -7.941,-19.793 -24.84508,-34.14694 -42.95508,-33.71094 z"/> id="gradient-1-0"
<path d="m 297.34961,5.4492188 c -65.399,0 -119.66288,43.7706692 -129.92188,101.1386712 -0.925,-0.029 -1.85411,-0.043 -2.78711,-0.043 -48.98199,0 -88.689448,39.70841 -88.689448,88.69141 0,48.968 50.024968,85.48542 92.356838,89.13334 l -3.4892,-16.75561 c -32.82055,-3.42533 -71.546884,-33.52411 -71.546884,-73.16211 0,-40.362 33.061744,-73.31914 73.491744,-73.31914 1.843,0 3.67147,0.0672 5.48047,0.20117 v -0.2246 c 4,0.257 7.90183,0.8211 11.67383,1.6621 2.252,-55.916995 52.66917,-100.619136 114.53515,-100.619136 53.04001,0 97.66433,32.85775 110.73633,77.46875 0.309,-0.159 0.61964,-0.315703 0.93164,-0.470703 0.006,0.02 0.0116,0.04055 0.0176,0.06055 11.026,-5.378001 23.57472,-8.419922 36.88672,-8.419922 37.486,0 68.91847,24.124511 77.35547,56.603511 C 520.38986,108.44553 485.73998,78 443.58398,78 c -8.339,0 -16.38403,1.191298 -23.95703,3.404297 -19.419,-44.513 -66.84934,-75.9550782 -122.27734,-75.9550782 z" style="fill:#00408d;stroke:#051343;stroke-width:2px"/> href="#gradient-1"
<path style="fill:url(#lg14);stroke:url(#lg15);paint-order:fill" d="m -267.63252,164.31767 -15.70118,-0.12404 -3.54906,37.20629 -1.95109,76.01487 20.15622,-0.37475 c -4.12353,-45.07233 -3.02437,-82.7491 1.04511,-112.72275 z" transform="rotate(-89.99513)"/> gradientUnits="userSpaceOnUse"
<path style="fill:none;stroke:url(#lg17);stroke-width:1" d="m 161.24828,267.14668 c 40.90805,5.43949 88.8177,6.68378 159.17408,-4.13383"/> x1="30.771"
<path style="fill:none;stroke:url(#lg1);stroke-width:1" d="m 151.96539,281.42915 c 19.27385,6.64292 99.62693,7.03138 134.89483,7.72"/> y1="2283.52"
</g> x2="30.771"
</svg>` y2="2416.4089"
spreadMethod="pad"
gradientTransform="matrix(0.999122,0,0,0.848244,1717.8096,192.633)" /><linearGradient
id="gradient-1-1"
href="#gradient-1"
gradientUnits="userSpaceOnUse"
x1="30.849001"
y1="2446.3101"
x2="30.849001"
y2="2588.6721"
gradientTransform="matrix(0.996573,0,0,0.791798,1717.8096,192.63306)" /><linearGradient
id="gradient-2"><stop
offset="0.002"
style=""
id="stop3" /><stop
offset="1"
style="stop-color: rgb(4, 114, 187);"
id="stop4" /></linearGradient><linearGradient
id="gradient-2-0"
href="#gradient-2"
gradientUnits="userSpaceOnUse"
x1="255.14799"
y1="321.186"
x2="255.14799"
y2="321.71799"
gradientTransform="matrix(1.47e-4,6.198697,270.23999,-0.112229,-86674.622,-1260.2124)" /><linearGradient
id="gradient-2-1"
href="#gradient-2"
gradientUnits="userSpaceOnUse"
x1="255.08755"
y1="321.37799"
x2="255.09705"
y2="321.90982"
gradientTransform="matrix(1.17e-4,21.428825,213.85692,-0.389689,-68565.13,-5073.7432)" /><linearGradient
id="gradient-2-1-0"
href="#linearGradient9"
gradientUnits="userSpaceOnUse"
x1="255.87483"
y1="321.37949"
x2="255.87479"
y2="321.90854"
gradientTransform="matrix(1.17e-4,21.428825,213.85692,-0.389689,-68565.13,-5073.7432)" /><linearGradient
xlink:href="#linearGradient16"
id="linearGradient14"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(8.4922907e-5,-0.999122,0.848244,7.2098649e-5,-1738.1491,381.17896)"
x1="30.771"
y1="2283.52"
x2="30.771"
y2="2416.4089"
spreadMethod="pad" /><linearGradient
id="linearGradient16"><stop
offset="0"
style="stop-color:#051343;stop-opacity:1;"
id="stop13" /><stop
offset="1"
style="stop-color: rgb(0, 141, 223);"
id="stop14" /></linearGradient><linearGradient
xlink:href="#linearGradient16"
id="linearGradient15"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(8.4706248e-5,-0.996573,0.791798,6.7300878e-5,-1738.149,381.17906)"
x1="30.849001"
y1="2446.3101"
x2="30.849001"
y2="2588.6721" /><linearGradient
xlink:href="#linearGradient10"
id="linearGradient17"
x1="160.76199"
y1="268.35672"
x2="284.18887"
y2="268.80402"
gradientUnits="userSpaceOnUse"
gradientTransform="translate(34.548823,75.050781)" /><linearGradient
id="linearGradient10"><stop
offset="0"
style="stop-color:#051343;stop-opacity:1;"
id="stop7" /><stop
offset="0.99875164"
style="stop-color: rgb(4, 114, 187);"
id="stop10" /></linearGradient><linearGradient
xlink:href="#linearGradient10"
id="linearGradient1"
gradientUnits="userSpaceOnUse"
x1="160.76199"
y1="268.35672"
x2="284.18887"
y2="268.80402"
gradientTransform="translate(25.265938,90.769558)" /></defs><rect
x="1740.2544"
y="2129.6233"
width="16.597"
height="112.721"
style="fill:url(#gradient-1-0);stroke:url(#gradient-1-1);paint-order:fill"
transform="rotate(-89.99513)"
id="rect13" /><text
style="font-size:28px;font-family:Arial, sans-serif;white-space:pre;fill:#333333"
x="813.82861"
y="302.39877"
id="text14" /><text
style="font-size:106.667px;text-align:center;writing-mode:lr-tb;direction:ltr;text-anchor:middle;fill:#00408d;fill-opacity:1;stroke:#000000;stroke-width:2;stroke-dasharray:none;stroke-opacity:1"
x="324.56198"
y="377.59503"
id="text1"><tspan
id="tspan1"
x="324.56198"
y="377.59503"
style="font-style:normal;font-variant:normal;font-weight:900;font-stretch:normal;font-size:106.667px;font-family:'M+ 2c';fill:#00408d;fill-opacity:1;stroke:#000000;stroke-width:2;stroke-dasharray:none;stroke-opacity:1" /></text><text
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:140.132px;font-family:'Vremena Grotesk';text-align:center;writing-mode:lr-tb;direction:ltr;text-anchor:middle;fill:#ffffff;fill-opacity:1;stroke:#051343;stroke-width:2.33554;stroke-dasharray:none;stroke-opacity:1"
x="232.56667"
y="401.04272"
id="text2"><tspan
id="tspan2"
x="232.56667"
y="401.04272"
style="font-size:140.132px;stroke-width:2.33554;fill:#ffffff;fill-opacity:1">f<tspan
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:'M+ 2c';stroke-width:2.33554;fill:#ffffff;fill-opacity:1"
id="tspan4">e</tspan>lhom</tspan></text><text
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:140.132px;font-family:'M+ 2c';text-align:center;writing-mode:lr-tb;direction:ltr;text-anchor:middle;fill:#008ddf;fill-opacity:1;stroke:#051343;stroke-width:2.33554;stroke-dasharray:none;stroke-opacity:1"
x="563.68787"
y="400.21695"
id="text5"><tspan
id="tspan5"
x="563.68787"
y="400.21695"
style="stroke:#051343;stroke-width:2.33554;stroke-opacity:1">e<tspan
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:'Vremena Grotesk';stroke:#051343;stroke-width:2.33554;stroke-opacity:1"
id="tspan6">u</tspan></tspan></text><circle
style="fill:#008ddf;fill-opacity:1;stroke:#051343;stroke-width:1.85226;stroke-dasharray:none;stroke-opacity:1"
id="path6"
cx="469.65689"
cy="391.54669"
r="10.150504" /><g
id="g2"
transform="translate(-34.626437,-75)"><path
style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.745956;stroke-dasharray:none;stroke-opacity:1"
d="m 184.76065,350.14894 c -15.98877,-4.50438 -28.82636,-12.40521 -41.07759,-25.28104 -19.29776,-20.28158 -25.9034,-45.05671 -19.24091,-72.16505 5.44415,-22.15119 21.9613,-42.47639 42.05694,-51.75321 10.46476,-4.83088 15.41718,-5.83792 29.10384,-5.91808 6.75769,-0.0394 13.78198,0.2012 15.60955,0.53505 l 3.32286,0.60701 0.46604,-6.7663 c 1.11599,-16.20306 7.59666,-35.0318 17.25505,-50.13235 5.22044,-8.16196 20.3749,-24.12314 28.39374,-29.90526 15.86366,-11.438698 32.8931,-15.404258 52.541,-18.490808 11.05595,-1.73682 26.69069,0.35712 37.13887,2.71288 40.8437,9.209108 78.44375,33.380298 91.01513,71.148868 1.09236,3.28174 2.14751,5.96681 2.34482,5.96681 0.1973,0 3.1737,-1.19976 6.6142,-2.6661 9.80615,-4.1794 17.28246,-5.85315 28.33643,-6.34387 11.05001,-0.49053 18.45598,0.39016 27.96449,3.32542 28.80326,8.89154 53.06677,47.54197 52.24723,61.73235 l -0.80326,13.90855 -4.28191,0.0455 c -2.64695,0.0281 -9.43186,2.02792 -11.29568,2.61754 -5.78846,1.83115 -1.84497,-4.47147 -48.23835,33.83846 -32.22413,26.60949 -59.34963,42.82359 -93.44795,55.85791 -15.09262,5.76925 -36.3971,12.02181 -46.1779,13.55253 -1.59004,0.24885 -34.79244,4.83384 -73.35101,5.86764 -38.55855,1.03382 -64.73196,-0.41814 -80.59412,-1.26238 -9.06729,-0.48259 -11.66601,0.16115 -15.90151,-1.03209 z"
id="path15-1" /><path
id="path4"
style="fill:#051343;fill-opacity:1;stroke:#051343;stroke-width:2px;stroke-opacity:1"
d="m 188.36409,236.4686 v 28.90235 H 294.06526 V 254.41001 H 269.77815 V 236.4686 Z m 20.66992,8.46485 a 5.5560002,5.5560002 0 0 1 5.55664,5.55664 5.5560002,5.5560002 0 0 1 -5.55664,5.55468 5.5560002,5.5560002 0 0 1 -5.55664,-5.55468 5.5560002,5.5560002 0 0 1 5.55664,-5.55664 z m 20.47852,0.0215 a 5.5560002,5.5560002 0 0 1 5.55664,5.55664 5.5560002,5.5560002 0 0 1 -5.55664,5.55664 5.5560002,5.5560002 0 0 1 -5.55664,-5.55664 5.5560002,5.5560002 0 0 1 5.55664,-5.55664 z m 20.25586,0.14258 a 5.5560002,5.5560002 0 0 1 5.55664,5.55664 5.5560002,5.5560002 0 0 1 -5.55664,5.55469 5.5560002,5.5560002 0 0 1 -5.55664,-5.55469 5.5560002,5.5560002 0 0 1 5.55664,-5.55664 z" /><path
id="rect6"
style="fill:#051343;fill-opacity:1;stroke:#051343;stroke-width:2px;stroke-opacity:1"
d="m 188.2762,275.99985 v 28.90039 h 105.70117 v -28.90039 z m 20.67188,9.46289 a 5.5560002,5.5560002 0 0 1 5.55468,5.55664 5.5560002,5.5560002 0 0 1 -5.55468,5.55664 5.5560002,5.5560002 0 0 1 -5.55665,-5.55664 5.5560002,5.5560002 0 0 1 5.55665,-5.55664 z m 20.47656,0.0234 a 5.5560002,5.5560002 0 0 1 5.55664,5.55469 5.5560002,5.5560002 0 0 1 -5.55664,5.55664 5.5560002,5.5560002 0 0 1 -5.55469,-5.55664 5.5560002,5.5560002 0 0 1 5.55469,-5.55469 z" /><path
id="path10"
style="fill:#051343;fill-opacity:1;stroke:#051343;stroke-width:1.9;stroke-dasharray:none;stroke-opacity:1"
d="m 232.32308,196.26352 v 28.90039 h 53.76953 l 38.35351,-28.90039 z m 20.67187,9.46289 a 5.5560002,5.5560002 0 0 1 5.55469,5.55664 5.5560002,5.5560002 0 0 1 -5.55469,5.55469 5.5560002,5.5560002 0 0 1 -5.55664,-5.55469 5.5560002,5.5560002 0 0 1 5.55664,-5.55664 z" /><path
d="m 292.49531,339.95157 c -53.034,0 -99.796,-10.762 -127.437,-27.136 19.291,8.807 47.768,14.377 79.534,14.377 23.242,0 44.724,-2.982 62.135,-8.032 v -75.023 l -28.665,-0.001 115.772,-87.28 48.409,36.495 v -12.853 h 25.034 v 31.726 l 42.329,31.912 h -28.353 v 77.268 l -78.822,0.005 c -27.916,11.441 -66.856,18.542 -109.935,18.542 z m 102.245,-115.364 c -12.363,0 -22.385,10.022 -22.385,22.385 0,8.343 4.565,15.621 11.334,19.471 l -7.782,41.652 h 37.666 l -7.782,-41.652 c 6.769,-3.85 11.334,-11.127 11.334,-19.471 0,-12.363 -10.022,-22.385 -22.385,-22.385 z"
style="fill:#051343;fill-opacity:1;stroke:#051343;stroke-width:2px;stroke-opacity:1"
id="path11" /><path
id="path12"
style="fill:#051343;fill-opacity:1;stroke:#051343;stroke-opacity:1"
d="m 331.89843,80.5 c -65.399,0 -119.66288,43.77067 -129.92188,101.13867 -0.925,-0.029 -1.85411,-0.043 -2.78711,-0.043 -48.98199,0 -88.68944,39.70841 -88.68944,88.69141 0,48.968 50.02496,85.48542 92.35683,89.13334 l -3.4892,-16.75561 c -32.82055,-3.42533 -71.54688,-33.52411 -71.54688,-73.16211 0,-40.362 33.06174,-73.31914 73.49174,-73.31914 1.843,0 3.67147,0.0672 5.48047,0.20117 v -0.2246 c 4,0.257 7.90183,0.8211 11.67383,1.6621 2.252,-55.91699 52.66917,-100.619135 114.53515,-100.619135 53.04001,0 97.66433,32.857755 110.73633,77.468755 0.309,-0.159 0.61964,-0.31571 0.93164,-0.47071 0.006,0.02 0.0116,0.0405 0.0176,0.0605 11.026,-5.378 23.57472,-8.41992 36.88672,-8.41992 37.486,0 68.91847,24.12451 77.35547,56.60351 -3.99102,-38.94897 -38.6409,-69.3945 -80.7969,-69.3945 -8.339,0 -16.38403,1.1913 -23.95703,3.4043 C 434.75677,111.94208 387.32643,80.5 331.89843,80.5 Z" /><path
style="fill:none;fill-opacity:1;stroke:#051343;stroke-width:1;stroke-dasharray:none;stroke-opacity:1"
d="m 558.54392,219.94889 c 0.55822,2.06578 1.0625,10.5625 0.375,14.46875"
id="path1-2" /><path
id="path2"
style="fill:#008ddf;stroke:#0472bb"
d="m 556.93163,234.32031 c -16.13,0.388 -18.85575,5.44224 -48.34375,27.74024 -31.401,25.206 -62.71092,49.374 -117.91992,67 -12.11688,3.8684 -22.84543,6.83673 -36.66992,9.16601 -3.52327,0.53538 -7.07021,1.0572 -10.49219,1.52734 -11.11981,1.52774 -21.588,2.81477 -31.93359,3.76563 l -0.37205,20.49219 c 13.36471,0.34187 35.32192,0.38638 61.20023,-0.23523 25.87831,-0.62162 55.67773,-1.90936 84.72687,-4.23198 29.04914,-2.32263 73.441,-8.14839 90.28521,-12.88581 11.83463,-3.32848 28.09255,-8.72249 37.92989,-19.04615 9.46724,-10.59763 17.11931,-21.57912 18.56188,-36.64771 0.76581,-7.99943 -1.41558,-15.75159 -4.01758,-22.93359 -7.941,-19.793 -24.84508,-34.14694 -42.95508,-33.71094 z" /><path
id="rect2"
style="fill:url(#linearGradient14);stroke:url(#linearGradient15);paint-order:fill"
d="m 198.84375,342.69727 -0.12538,15.70117 37.20599,3.55222 76.0147,1.95755 -0.37303,-20.15625 c -45.07268,4.1197 -82.74936,3.01733 -112.72267,-1.05469 z" /><path
style="fill:none;fill-opacity:1;stroke:url(#linearGradient17);stroke-width:1;stroke-dasharray:none"
d="m 195.7971,342.19746 c 40.90805,5.43949 88.8177,6.68378 159.17408,-4.13383"
id="path14" /><path
style="fill:none;fill-opacity:1;stroke:url(#linearGradient1);stroke-width:1;stroke-dasharray:none"
d="m 186.51421,356.47993 c 19.27385,6.64292 99.62693,7.03138 134.89483,7.72"
id="path14-8" /></g></svg>
`
// FelhomFaviconSVG is the felhom.eu favicon (cloud icon only), served at /static/favicon.svg.
const FelhomFaviconSVG = `<?xml version="1.0" encoding="UTF-8"?>
<svg
viewBox="0 0 437.307 296.36403"
version="1.1"
id="svg14"
width="437.30698"
height="296.36404"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns="http://www.w3.org/2000/svg"><defs
id="defs4"><linearGradient
id="linearGradient9"><stop
offset="0"
style=""
id="stop8" /><stop
offset="0.99875164"
style="stop-color: rgb(4, 114, 187);"
id="stop9" /></linearGradient><linearGradient
id="gradient-1"><stop
offset="0"
style="stop-color: rgb(0, 64, 141);"
id="stop1" /><stop
offset="1"
style="stop-color: rgb(0, 141, 223);"
id="stop2" /></linearGradient><linearGradient
id="gradient-1-0"
href="#gradient-1"
gradientUnits="userSpaceOnUse"
x1="30.771"
y1="2283.52"
x2="30.771"
y2="2416.4089"
spreadMethod="pad"
gradientTransform="matrix(0.999122,0,0,0.848244,1719.8035,120.63283)" /><linearGradient
id="gradient-1-1"
href="#gradient-1"
gradientUnits="userSpaceOnUse"
x1="30.849001"
y1="2446.3101"
x2="30.849001"
y2="2588.6721"
gradientTransform="matrix(0.996573,0,0,0.791798,1719.8035,120.63289)" /><linearGradient
id="gradient-2"><stop
offset="0.002"
style=""
id="stop3" /><stop
offset="1"
style="stop-color: rgb(4, 114, 187);"
id="stop4" /></linearGradient><linearGradient
id="gradient-2-0"
href="#gradient-2"
gradientUnits="userSpaceOnUse"
x1="255.14799"
y1="321.186"
x2="255.14799"
y2="321.71799"
gradientTransform="matrix(1.47e-4,6.198697,270.23999,-0.112229,-86674.622,-1260.2124)" /><linearGradient
id="gradient-2-1"
href="#gradient-2"
gradientUnits="userSpaceOnUse"
x1="255.08755"
y1="321.37799"
x2="255.09705"
y2="321.90982"
gradientTransform="matrix(1.17e-4,21.428825,213.85692,-0.389689,-68565.13,-5073.7432)" /><linearGradient
id="gradient-2-1-0"
href="#linearGradient9"
gradientUnits="userSpaceOnUse"
x1="255.87483"
y1="321.37949"
x2="255.87479"
y2="321.90854"
gradientTransform="matrix(1.17e-4,21.428825,213.85692,-0.389689,-68565.13,-5073.7432)" /><linearGradient
xlink:href="#linearGradient16"
id="linearGradient14"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(8.4922907e-5,-0.999122,0.848244,7.2098649e-5,-1738.1491,381.17896)"
x1="30.771"
y1="2283.52"
x2="30.771"
y2="2416.4089"
spreadMethod="pad" /><linearGradient
id="linearGradient16"><stop
offset="0"
style="stop-color:#051343;stop-opacity:1;"
id="stop13" /><stop
offset="1"
style="stop-color: rgb(0, 141, 223);"
id="stop14" /></linearGradient><linearGradient
xlink:href="#linearGradient16"
id="linearGradient15"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(8.4706248e-5,-0.996573,0.791798,6.7300878e-5,-1738.149,381.17906)"
x1="30.849001"
y1="2446.3101"
x2="30.849001"
y2="2588.6721" /><linearGradient
xlink:href="#linearGradient10"
id="linearGradient17"
x1="160.76199"
y1="268.35672"
x2="284.18887"
y2="268.80402"
gradientUnits="userSpaceOnUse"
gradientTransform="translate(34.548823,75.050781)" /><linearGradient
id="linearGradient10"><stop
offset="0"
style="stop-color:#051343;stop-opacity:1;"
id="stop7" /><stop
offset="0.99875164"
style="stop-color: rgb(4, 114, 187);"
id="stop10" /></linearGradient><linearGradient
xlink:href="#linearGradient10"
id="linearGradient1"
gradientUnits="userSpaceOnUse"
x1="160.76199"
y1="268.35672"
x2="284.18887"
y2="268.80402"
gradientTransform="translate(25.265938,90.769558)" /></defs><rect
x="1742.2483"
y="2057.6233"
width="16.597"
height="112.721"
style="fill:url(#gradient-1-0);stroke:url(#gradient-1-1);paint-order:fill"
transform="rotate(-89.99513)"
id="rect13" /><text
style="font-size:28px;font-family:Arial, sans-serif;white-space:pre;fill:#333333"
x="741.82861"
y="300.39877"
id="text14" /><text
style="font-size:106.667px;text-align:center;writing-mode:lr-tb;direction:ltr;text-anchor:middle;fill:#00408d;fill-opacity:1;stroke:#000000;stroke-width:2;stroke-dasharray:none;stroke-opacity:1"
x="252.56198"
y="375.59503"
id="text1"><tspan
id="tspan1"
x="252.56198"
y="375.59503"
style="font-style:normal;font-variant:normal;font-weight:900;font-stretch:normal;font-size:106.667px;font-family:'M+ 2c';fill:#00408d;fill-opacity:1;stroke:#000000;stroke-width:2;stroke-dasharray:none;stroke-opacity:1" /></text><g
id="g2"
transform="matrix(0.86843482,0,0,1.0072763,-92.120831,-77.61811)"><path
style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.745956;stroke-dasharray:none;stroke-opacity:1"
d="m 184.76065,350.14894 c -15.98877,-4.50438 -28.82636,-12.40521 -41.07759,-25.28104 -19.29776,-20.28158 -25.9034,-45.05671 -19.24091,-72.16505 5.44415,-22.15119 21.9613,-42.47639 42.05694,-51.75321 10.46476,-4.83088 15.41718,-5.83792 29.10384,-5.91808 6.75769,-0.0394 13.78198,0.2012 15.60955,0.53505 l 3.32286,0.60701 0.46604,-6.7663 c 1.11599,-16.20306 7.59666,-35.0318 17.25505,-50.13235 5.22044,-8.16196 20.3749,-24.12314 28.39374,-29.90526 15.86366,-11.438698 32.8931,-15.404258 52.541,-18.490808 11.05595,-1.73682 26.69069,0.35712 37.13887,2.71288 40.8437,9.209108 78.44375,33.380298 91.01513,71.148868 1.09236,3.28174 2.14751,5.96681 2.34482,5.96681 0.1973,0 3.1737,-1.19976 6.6142,-2.6661 9.80615,-4.1794 17.28246,-5.85315 28.33643,-6.34387 11.05001,-0.49053 18.45598,0.39016 27.96449,3.32542 28.80326,8.89154 53.06677,47.54197 52.24723,61.73235 l -0.80326,13.90855 -4.28191,0.0455 c -2.64695,0.0281 -9.43186,2.02792 -11.29568,2.61754 -5.78846,1.83115 -1.84497,-4.47147 -48.23835,33.83846 -32.22413,26.60949 -59.34963,42.82359 -93.44795,55.85791 -15.09262,5.76925 -36.3971,12.02181 -46.1779,13.55253 -1.59004,0.24885 -34.79244,4.83384 -73.35101,5.86764 -38.55855,1.03382 -64.73196,-0.41814 -80.59412,-1.26238 -9.06729,-0.48259 -11.66601,0.16115 -15.90151,-1.03209 z"
id="path15-1" /><path
id="path4"
style="fill:#051343;fill-opacity:1;stroke:#051343;stroke-width:2px;stroke-opacity:1"
d="m 188.36409,236.4686 v 28.90235 H 294.06526 V 254.41001 H 269.77815 V 236.4686 Z m 20.66992,8.46485 a 5.5560002,5.5560002 0 0 1 5.55664,5.55664 5.5560002,5.5560002 0 0 1 -5.55664,5.55468 5.5560002,5.5560002 0 0 1 -5.55664,-5.55468 5.5560002,5.5560002 0 0 1 5.55664,-5.55664 z m 20.47852,0.0215 a 5.5560002,5.5560002 0 0 1 5.55664,5.55664 5.5560002,5.5560002 0 0 1 -5.55664,5.55664 5.5560002,5.5560002 0 0 1 -5.55664,-5.55664 5.5560002,5.5560002 0 0 1 5.55664,-5.55664 z m 20.25586,0.14258 a 5.5560002,5.5560002 0 0 1 5.55664,5.55664 5.5560002,5.5560002 0 0 1 -5.55664,5.55469 5.5560002,5.5560002 0 0 1 -5.55664,-5.55469 5.5560002,5.5560002 0 0 1 5.55664,-5.55664 z" /><path
id="rect6"
style="fill:#051343;fill-opacity:1;stroke:#051343;stroke-width:2px;stroke-opacity:1"
d="m 188.2762,275.99985 v 28.90039 h 105.70117 v -28.90039 z m 20.67188,9.46289 a 5.5560002,5.5560002 0 0 1 5.55468,5.55664 5.5560002,5.5560002 0 0 1 -5.55468,5.55664 5.5560002,5.5560002 0 0 1 -5.55665,-5.55664 5.5560002,5.5560002 0 0 1 5.55665,-5.55664 z m 20.47656,0.0234 a 5.5560002,5.5560002 0 0 1 5.55664,5.55469 5.5560002,5.5560002 0 0 1 -5.55664,5.55664 5.5560002,5.5560002 0 0 1 -5.55469,-5.55664 5.5560002,5.5560002 0 0 1 5.55469,-5.55469 z" /><path
id="path10"
style="fill:#051343;fill-opacity:1;stroke:#051343;stroke-width:1.9;stroke-dasharray:none;stroke-opacity:1"
d="m 232.32308,196.26352 v 28.90039 h 53.76953 l 38.35351,-28.90039 z m 20.67187,9.46289 a 5.5560002,5.5560002 0 0 1 5.55469,5.55664 5.5560002,5.5560002 0 0 1 -5.55469,5.55469 5.5560002,5.5560002 0 0 1 -5.55664,-5.55469 5.5560002,5.5560002 0 0 1 5.55664,-5.55664 z" /><path
d="m 292.49531,339.95157 c -53.034,0 -99.796,-10.762 -127.437,-27.136 19.291,8.807 47.768,14.377 79.534,14.377 23.242,0 44.724,-2.982 62.135,-8.032 v -75.023 l -28.665,-0.001 115.772,-87.28 48.409,36.495 v -12.853 h 25.034 v 31.726 l 42.329,31.912 h -28.353 v 77.268 l -78.822,0.005 c -27.916,11.441 -66.856,18.542 -109.935,18.542 z m 102.245,-115.364 c -12.363,0 -22.385,10.022 -22.385,22.385 0,8.343 4.565,15.621 11.334,19.471 l -7.782,41.652 h 37.666 l -7.782,-41.652 c 6.769,-3.85 11.334,-11.127 11.334,-19.471 0,-12.363 -10.022,-22.385 -22.385,-22.385 z"
style="fill:#051343;fill-opacity:1;stroke:#051343;stroke-width:2px;stroke-opacity:1"
id="path11" /><path
id="path12"
style="fill:#051343;fill-opacity:1;stroke:#051343;stroke-opacity:1"
d="m 331.89843,80.5 c -65.399,0 -119.66288,43.77067 -129.92188,101.13867 -0.925,-0.029 -1.85411,-0.043 -2.78711,-0.043 -48.98199,0 -88.68944,39.70841 -88.68944,88.69141 0,48.968 50.02496,85.48542 92.35683,89.13334 l -3.4892,-16.75561 c -32.82055,-3.42533 -71.54688,-33.52411 -71.54688,-73.16211 0,-40.362 33.06174,-73.31914 73.49174,-73.31914 1.843,0 3.67147,0.0672 5.48047,0.20117 v -0.2246 c 4,0.257 7.90183,0.8211 11.67383,1.6621 2.252,-55.91699 52.66917,-100.619135 114.53515,-100.619135 53.04001,0 97.66433,32.857755 110.73633,77.468755 0.309,-0.159 0.61964,-0.31571 0.93164,-0.47071 0.006,0.02 0.0116,0.0405 0.0176,0.0605 11.026,-5.378 23.57472,-8.41992 36.88672,-8.41992 37.486,0 68.91847,24.12451 77.35547,56.60351 -3.99102,-38.94897 -38.6409,-69.3945 -80.7969,-69.3945 -8.339,0 -16.38403,1.1913 -23.95703,3.4043 C 434.75677,111.94208 387.32643,80.5 331.89843,80.5 Z" /><path
style="fill:none;fill-opacity:1;stroke:#051343;stroke-width:1;stroke-dasharray:none;stroke-opacity:1"
d="m 558.54392,219.94889 c 0.55822,2.06578 1.0625,10.5625 0.375,14.46875"
id="path1-2" /><path
id="path2"
style="fill:#008ddf;stroke:#0472bb"
d="m 556.93163,234.32031 c -16.13,0.388 -18.85575,5.44224 -48.34375,27.74024 -31.401,25.206 -62.71092,49.374 -117.91992,67 -12.11688,3.8684 -22.84543,6.83673 -36.66992,9.16601 -3.52327,0.53538 -7.07021,1.0572 -10.49219,1.52734 -11.11981,1.52774 -21.588,2.81477 -31.93359,3.76563 l -0.37205,20.49219 c 13.36471,0.34187 35.32192,0.38638 61.20023,-0.23523 25.87831,-0.62162 55.67773,-1.90936 84.72687,-4.23198 29.04914,-2.32263 73.441,-8.14839 90.28521,-12.88581 11.83463,-3.32848 28.09255,-8.72249 37.92989,-19.04615 9.46724,-10.59763 17.11931,-21.57912 18.56188,-36.64771 0.76581,-7.99943 -1.41558,-15.75159 -4.01758,-22.93359 -7.941,-19.793 -24.84508,-34.14694 -42.95508,-33.71094 z" /><path
id="rect2"
style="fill:url(#linearGradient14);stroke:url(#linearGradient15);paint-order:fill"
d="m 198.84375,342.69727 -0.12538,15.70117 37.20599,3.55222 76.0147,1.95755 -0.37303,-20.15625 c -45.07268,4.1197 -82.74936,3.01733 -112.72267,-1.05469 z" /><path
style="fill:none;fill-opacity:1;stroke:url(#linearGradient17);stroke-width:1;stroke-dasharray:none"
d="m 195.7971,342.19746 c 40.90805,5.43949 88.8177,6.68378 159.17408,-4.13383"
id="path14" /><path
style="fill:none;fill-opacity:1;stroke:url(#linearGradient1);stroke-width:1;stroke-dasharray:none"
d="m 186.51421,356.47993 c 19.27385,6.64292 99.62693,7.03138 134.89483,7.72"
id="path14-8" /></g></svg>
`
@@ -5,7 +5,7 @@
<meta charset="utf-8"> <meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<title>{{if .AppName}}{{.AppName}} — {{end}}felhom.eu</title> <title>{{if .AppName}}{{.AppName}} — {{end}}felhom.eu</title>
<link rel="icon" type="image/svg+xml" href="{{.ControllerURL}}/static/felhom-logo.svg"> <link rel="icon" type="image/svg+xml" href="{{.ControllerURL}}/static/favicon.svg">
<style> <style>
*{margin:0;padding:0;box-sizing:border-box} *{margin:0;padding:0;box-sizing:border-box}
body{background:#0d1117;color:#e6edf3;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif;min-height:100vh;display:flex;align-items:center;justify-content:center} body{background:#0d1117;color:#e6edf3;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif;min-height:100vh;display:flex;align-items:center;justify-content:center}
@@ -5,7 +5,7 @@
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{.Title}} — Felhom.eu</title> <title>{{.Title}} — Felhom.eu</title>
<link rel="icon" type="image/svg+xml" href="/static/felhom-logo.svg"> <link rel="icon" type="image/svg+xml" href="/static/favicon.svg">
<link rel="stylesheet" href="/static/style.css"> <link rel="stylesheet" href="/static/style.css">
<meta name="csrf-token" content="{{.CSRFToken}}"> <meta name="csrf-token" content="{{.CSRFToken}}">
<script> <script>