Compare commits

..
15 Commits
Author SHA1 Message Date
maikandClaude Opus 4.8 e26ba3712a docs: README um Kiosk-Modus und Externe Quellen ergaenzt
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-18 18:03:39 +02:00
maikandClaude Opus 4.8 45876be61e feat: Externe Quellen mit klickbaren Links (neuer Tab) + Kiosk-Modus
- Label "Externe Quelle (Links)" -> "Externe Quellen" (de/en)
- Quelle-URLs werden via linkify-Partial anklickbar, oeffnen in neuem Tab
- Neuer Kiosk-Modus: ?kiosk=true macht externe Links unklickbar
  (bleibt fuer die Session aktiv, verlassen mit ?kiosk=false)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-18 17:58:53 +02:00
maikandClaude Opus 4.8 fad62315df chore: Todo.md nie pushen (aus Tracking entfernt, gitignored)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-18 17:38:40 +02:00
maik 7a9acf3f08 hugo.toml aktualisiert 2026-08-18 17:25:21 +02:00
maikandClaude Opus 4.8 3ea323f1ef deploy: Host-nginx-Config für Webroot /var/www/sites/website (Backend :8080 hinter Proxy Manager)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-18 17:17:53 +02:00
maik 46ddf45679 ci: trigger deploy 2026-08-18 17:15:10 +02:00
maikandClaude Opus 4.8 e5d9f07bb6 ci: statisches Deploy nach /var/www/sites/website (Hugo build + rsync)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-18 17:13:54 +02:00
maikandClaude Opus 4.8 15e23700ac ci: Deploy-Workflow auf Hugo/Docker korrigiert (war Node-Template); LFS im Checkout
Build & Deploy Website / deploy (push) Failing after 5m14s
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-18 17:06:47 +02:00
maik 9ce855314f ci: trigger deploy
Deploy / deploy (push) Failing after 6m18s
2026-08-18 17:05:43 +02:00
maik e6c02c80b2 ci: trigger deploy
Deploy / deploy (push) Canceled after 10m6s
2026-08-18 16:55:37 +02:00
maik 6d83c879c5 ci: trigger deploy
Deploy / deploy (push) Failing after 42s
2026-08-18 16:49:24 +02:00
maik e426107700 ci: trigger deploy
Deploy / deploy (push) Failing after 40s
2026-08-18 16:45:11 +02:00
maikandClaude Opus 4.8 4ede931fce ci: Gitea-Actions Deploy-Workflow (docker compose, LFS-aware)
Deploy / deploy (push) Failing after 51s
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-18 16:43:43 +02:00
maikandClaude Opus 4.8 ce4091169a chore: .gitattributes – Zeilenenden-Normalisierung & Binär-Markierung
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-18 16:04:42 +02:00
maikandClaude Opus 4.8 936b710edf erster brauchbarer stand.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-18 15:32:41 +02:00
9 changed files with 21 additions and 191 deletions
-5
View File
@@ -9,8 +9,3 @@ Thumbs.db
# Persönliche Notizen nie pushen
/Todo.md
# Große Vektor-Basemap (6,7 GB) außerhalb des Repos, lokal per Symlink
# nach static/tiles/ eingehängt (ln -s /home/maik/op-pmtiles/germany.pmtiles).
# Nie ins Git, auf dem Server separat unter /tiles/ bereitstellen.
/static/tiles/
+6 -18
View File
@@ -2,9 +2,8 @@
Hugo-Neubau von [map.offener-prozess.de](https://map.offener-prozess.de/)
(Ablösung des alten WordPress/Listable-Setups). Interaktive Karte der
Schauplätze des NSU-Komplexes mit **Leaflet** und einer **lokalen Deutschland-Karte**
(Bundesländer-GeoJSON im Repo, keine externen Kacheln/CDN DSGVO-freundlich),
im Layout ans Original angelehnt (Split-View:
Schauplätze des NSU-Komplexes mit **Leaflet + OpenStreetMap** (statt der alten
Google-Maps-/Mapbox-Einbindung), im Layout ans Original angelehnt (Split-View:
Kartenliste links, Karte rechts), umgesetzt mit dem Theme
[`themes/op-dz/`](themes/op-dz/) (UIkit 3, Schrift *ABC Favorit*, alle Assets
lokal/DSGVO-konform).
@@ -72,22 +71,11 @@ Der Punkt erscheint automatisch auf allen Karten (Start, Kategorie) und bekommt
eine Detailseite mit Mini-Karte + „Anfahrt per OpenStreetMap". Ein Bild
`cover.jpg` im selben Ordner wird als Card-/Kopfbild genutzt.
## Karte (komplett lokal, keine externen CDN)
## Karte / Kacheln
Die Basemap ist **keine** externe Kachel (kein OpenStreetMap/CDN, DSGVO-freundlich),
sondern eine lokale **Bundesländer-GeoJSON** unter
[`static/geo/deutschland-bundeslaender.geojson`](static/geo/deutschland-bundeslaender.geojson)
(amtliche BKG-Geometrie, vereinfacht, ~40 KB). `map.js` lädt sie same-origin per
`fetch` und rendert sie vektoriell mit Leaflet (Bundesländer-Grenzen blau, Land
cremefarben). 118 Marker werden per Leaflet.markercluster gebündelt; Farbe =
Kategorie (`data/kategorien.yaml`). Attribution steht als **Text** in `hugo.toml`
unter `[params.map]` (kein Request).
Tradeoff: keine Straßen-Detailtiefe beim Reinzoomen die Karte ist eine
Übersichts-/Verortungskarte. Die Detailseiten zeigen den Ort als Pin auf der
ganzen Deutschland-Silhouette (Locator). Neue amtliche Geometrie:
[isellsoap/deutschlandGeoJSON](https://github.com/isellsoap/deutschlandGeoJSON)
(`2_bundeslaender/3_mittel.geo.json` für mehr Detail).
Standard: OpenStreetMap. Eigener Tile-Server (OSM-Usage-Policy!) in `hugo.toml`
unter `[params.map]` (`tileURL`, `attribution`). 118 Marker werden per
Leaflet.markercluster gebündelt; Farbe = Kategorie (`data/kategorien.yaml`).
## Externe Quellen
+2 -16
View File
@@ -22,12 +22,8 @@ removePathAccents = true # ä/ö/ü → a/o/u in URLs
email = "info@offener-prozess.de"
[params.map]
# Kein externes CDN: Basemap = selbst gehostete Vektor-Tiles (Protomaps/OSM)
# als .pmtiles auf dem eigenen Server unter /tiles/, gerendert im Browser via
# protomaps-leaflet. Fallback ohne pmtiles = lokaler Umriss (static/geo/).
# pmtilesURL leer lassen → Default /tiles/germany.pmtiles (relativ zur baseURL).
flavor = "light"
attribution = "© OpenStreetMap-Mitwirkende"
tileURL = "https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
attribution = '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a>-Mitwirkende'
# ============================================================================
# SPRACHEN
@@ -51,11 +47,6 @@ removePathAccents = true # ä/ö/ü → a/o/u in URLs
name = "Schauplätze"
url = "/schauplaetze/"
weight = 20
[[languages.de.menus.main]]
name = "Alle Schauplätze"
parent = "Schauplätze"
url = "/schauplaetze/"
weight = -1
[[languages.de.menus.main]]
name = "Aufenthaltsorte"
parent = "Schauplätze"
@@ -122,11 +113,6 @@ removePathAccents = true # ä/ö/ü → a/o/u in URLs
name = "Locations"
url = "/schauplaetze/"
weight = 20
[[languages.en.menus.main]]
name = "All locations"
parent = "Locations"
url = "/schauplaetze/"
weight = -1
[[languages.en.menus.main]]
name = "Whereabouts"
parent = "Locations"
File diff suppressed because one or more lines are too long
+2 -23
View File
@@ -241,25 +241,6 @@ a:hover { color: var(--op-blau-dunkel); }
}
.op-navbar .op-logo__img { height: 44px; }
/* Mobiles Offcanvas-Menü im Hausstil (Blau statt UIkit-Default-Grau) */
.uk-offcanvas-bar { background: var(--op-blau); color: #fff; }
.uk-offcanvas-bar .uk-nav-primary > li > a {
color: #fff; font-weight: 700; text-transform: none;
}
.uk-offcanvas-bar .uk-nav-primary > li > a:hover,
.uk-offcanvas-bar .uk-nav-primary > li > a:focus,
.uk-offcanvas-bar .uk-nav-primary > li.uk-active > a,
.uk-offcanvas-bar .uk-nav-primary > li > a[aria-current="page"] {
color: var(--op-gelb);
}
.uk-offcanvas-bar .uk-nav-sub a { color: rgba(255,255,255,.75); font-weight: 600; }
.uk-offcanvas-bar .uk-nav-sub a:hover,
.uk-offcanvas-bar .uk-nav-sub a:focus { color: var(--op-gelb); }
.uk-offcanvas-bar .uk-nav-parent-icon > .uk-parent > a .uk-nav-parent-icon,
.uk-offcanvas-bar .uk-close,
.uk-offcanvas-bar .uk-offcanvas-close { color: #fff; }
.uk-offcanvas-bar .uk-offcanvas-close:hover { color: var(--op-gelb); }
/* =========================================================================
6. TOPBAR
========================================================================= */
@@ -474,11 +455,9 @@ a:hover { color: var(--op-blau-dunkel); }
width: 100%;
height: 560px;
border: 2px solid var(--op-blau);
background: #d9e1ea; /* neutrale Fläche „ausserhalb DE" (kein Tile-Grau) */
background: var(--op-papier);
z-index: 0; /* unter der Sticky-Navbar halten */
}
/* Leaflet-eigener Container-Hintergrund (ohne Tiles) an den Hausstil angleichen */
.op-map .leaflet-container { background: #d9e1ea; }
.op-map--klein { height: 320px; }
/* Leaflet-Popup im Hausstil: Bild oben, gelber Body (wie im Original) */
@@ -527,7 +506,7 @@ a:hover { color: var(--op-blau-dunkel); }
/* --- 15a. Split-View (Karte + Liste), Vorbild map.offener-prozess.de --- */
.op-split { display: flex; align-items: flex-start; }
.op-split__list { box-sizing: border-box; width: 50%; padding: 2rem clamp(1rem, 3vw, 2.5rem); }
.op-split__list { width: 50%; padding: 2rem clamp(1rem, 3vw, 2.5rem); }
.op-split__map {
width: 50%;
position: sticky;
+9 -98
View File
@@ -1,7 +1,6 @@
/* =========================================================================
OP·Karte Leaflet mit LOKALER Deutschland-Karte (keine externen Tiles/CDN)
- Basemap = Bundesländer-GeoJSON aus dem Repo (static/geo/…), vektoriell
- farbige Pin-Marker je Kategorie, Clustering (markercluster)
OP·Karte Leaflet + OpenStreetMap
- farbige Kreis-Marker je Kategorie, Clustering (markercluster)
- Kategorie-Filter (Checkboxen) + Textsuche, synchron mit der Card-Liste
- Klick auf Card öffnet den zugehörigen Marker
Daten kommen aus <script type="application/json" id="op-map-data">.
@@ -19,98 +18,15 @@
var places = payload.places || [];
var colors = payload.colors || {};
var defaultColor = payload.defaultColor || "#1e2758";
var klein = !!payload.klein;
var geojsonUrl = payload.geojsonUrl || "";
var pmtilesUrl = payload.pmtilesUrl || "";
var flavor = payload.flavor || "light";
var attribution = payload.attribution || "© OpenStreetMap-Mitwirkende";
// Statischer Deutschland-Ausschnitt (für Locator/leere Filterung ohne Marker)
var germanyBounds = L.latLngBounds([[47.2, 5.8], [55.1, 15.1]]);
var tileUrl = payload.tileUrl || "https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png";
var attribution = payload.attribution ||
'&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a>-Mitwirkende';
var map = L.map(el, {
scrollWheelZoom: false,
minZoom: 5, maxZoom: 18,
attributionControl: true
});
map.attributionControl.setPrefix(false).addAttribution(attribution);
var map = L.map(el, { scrollWheelZoom: false });
L.tileLayer(tileUrl, { maxZoom: 19, attribution: attribution }).addTo(map);
map.on("focus", function () { map.scrollWheelZoom.enable(); });
map.on("blur", function () { map.scrollWheelZoom.disable(); });
// Ausschnitt fest auf Deutschland begrenzen: kein Wegscrollen ins Ausland.
map.setMaxBounds(germanyBounds.pad(0.08));
// Basemap: selbst gehostete Vektor-Tiles (Protomaps/OSM, .pmtiles auf dem
// eigenen Server kein externes CDN). Der Browser rendert Strassen/Haeuser.
var tilesActive = false;
if (pmtilesUrl && typeof protomapsL !== "undefined" && protomapsL.leafletLayer) {
try {
protomapsL.leafletLayer({ url: pmtilesUrl, flavor: flavor, lang: "de" }).addTo(map);
tilesActive = true;
} catch (e) { tilesActive = false; }
}
// Grenz-GeoJSON (Bundeslaender) immer laden:
// - ohne Tiles: gefuellter Umriss als Ersatz-Hintergrund
// - IMMER: Invers-Maske, die alles ausserhalb Deutschlands abdeckt, damit
// nur Deutschland sichtbar ist (Nachbarlaender/Meer ausgeblendet)
if (geojsonUrl) {
fetch(geojsonUrl)
.then(function (r) { return r.ok ? r.json() : null; })
.then(function (geo) {
if (!geo) { fitGermanyIfNeeded(); return; }
if (!tilesActive) {
L.geoJSON(geo, {
style: { color: "#1e2758", weight: 1, opacity: .85, fillColor: "#f4f1e6", fillOpacity: 1 },
interactive: false
}).addTo(map).bringToBack();
}
addGermanyMask(geo);
fitGermanyIfNeeded();
})
.catch(function () { fitGermanyIfNeeded(); });
}
function fitGermanyIfNeeded() {
// Nur bei leerem Ergebnis ganz Deutschland zeigen. Die Detail-Minikarte
// (klein) bleibt auf dem Strassenzoom aus apply() (maxZoom 17), damit man
// Strasse/Haus des Schauplatzes sieht.
if (!records.length) {
try { map.fitBounds(germanyBounds.pad(0.02)); } catch (e) {}
}
}
// Invers-Maske: weltgrosses Rechteck mit Deutschland als "Loechern".
// Fuellung (Papierfarbe) deckt alles ausserhalb der Landesgrenze ab.
// Even-Odd-Fuellregel: es MUESSEN alle Ringe rein (auch innere), sonst
// werden Enklaven wie Berlin/Bremen faelschlich ueberdeckt.
// Liegt im overlayPane ueber den Tiles, aber unter den Markern.
function addGermanyMask(geo) {
var world = [[-89, -180], [-89, 180], [89, 180], [89, -180]];
var holes = [];
function toLatLng(ring) {
return ring.map(function (c) { return [c[1], c[0]]; });
}
function collect(geom) {
if (!geom) return;
if (geom.type === "Polygon") {
geom.coordinates.forEach(function (ring) { holes.push(toLatLng(ring)); });
} else if (geom.type === "MultiPolygon") {
geom.coordinates.forEach(function (poly) {
poly.forEach(function (ring) { holes.push(toLatLng(ring)); });
});
}
}
(geo.features || []).forEach(function (f) { collect(f.geometry); });
if (geo.type === "Polygon" || geo.type === "MultiPolygon") collect(geo);
if (!holes.length) return;
L.polygon([world].concat(holes), {
stroke: false,
fill: true, fillColor: "#f6f6f1", fillOpacity: 1,
fillRule: "evenodd",
interactive: false
}).addTo(map);
}
var useCluster = places.length > 1 && typeof L.markerClusterGroup === "function";
var group = useCluster
? L.markerClusterGroup({ maxClusterRadius: 45, showCoverageOnHover: false })
@@ -177,13 +93,8 @@
});
var countCards = cards.filter(function (c) { return !c.classList.contains("is--hidden"); }).length;
if (countEl) countEl.textContent = countCards;
if (bounds.isValid()) {
// Detail-Minikarte: nah an Strasse/Haus heran (Zoom 17)
if (klein) map.fitBounds(bounds.pad(0.15), { maxZoom: 17 });
else map.fitBounds(bounds.pad(0.15));
}
else if (allBounds) map.fitBounds(allBounds.pad(0.15), klein ? { maxZoom: 17 } : undefined);
else if (germanyBounds) map.fitBounds(germanyBounds.pad(0.02));
if (bounds.isValid()) map.fitBounds(bounds.pad(0.15));
else if (allBounds) map.fitBounds(allBounds.pad(0.15));
}
// --- DOM-Hooks ---
+2 -5
View File
@@ -35,11 +35,8 @@
"places" $places
"colors" $colorMap
"defaultColor" $default
"klein" ($klein | default false)
"pmtilesUrl" (site.Params.map.pmtilesURL | default ("tiles/germany.pmtiles" | relURL))
"flavor" (site.Params.map.flavor | default "light")
"geojsonUrl" ("geo/deutschland-bundeslaender.geojson" | relURL)
"attribution" (site.Params.map.attribution | default "© OpenStreetMap-Mitwirkende")
"tileUrl" (site.Params.map.tileURL | default "https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png")
"attribution" (site.Params.map.attribution | default `&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a>-Mitwirkende`)
}}
<div id="op-map" class="op-map{{ if $klein }} op-map--klein{{ end }}"
@@ -7,8 +7,6 @@
{{ if or .IsHome (eq .Type "schauplaetze") (eq .Type "kategorien") (eq .Type "erinnerungsorte") }}
<script src="{{ "vendor/leaflet/leaflet.js" | relURL }}"></script>
<script src="{{ "vendor/markercluster/leaflet.markercluster.js" | relURL }}"></script>
{{/* Selbst gehostete Vektor-Basemap (Protomaps/OSM, .pmtiles) kein externes CDN */}}
<script src="{{ "vendor/protomaps/protomaps-leaflet.js" | relURL }}"></script>
{{ $map := resources.Get "js/map.js" }}
{{ if hugo.IsProduction }}{{ $map = $map | minify | fingerprint }}{{ end }}
<script src="{{ $map.RelPermalink }}"{{ with $map.Data.Integrity }} integrity="{{ . }}"{{ end }}></script>
File diff suppressed because one or more lines are too long