Add a generic tour-map shortcode and a data format to supply the necessary info
All checks were successful
/ build (pull_request) Successful in 27s

This commit is contained in:
Stefan Bethke 2026-10-16 20:21:21 +02:00
commit 14aed5e649
15 changed files with 1433 additions and 2 deletions

View file

@ -45,4 +45,93 @@ Wide screens get one column per day on an hour grid, with tours that start at th
For a different event than the page's section, pass its name: `{{< schedule 39c3 >}}`.
Without a data file, or without any tours in it, the shortcode renders nothing and the build logs a warning. To silence it, for example for an upcoming event, add `warning-schedule-empty` to `ignoreLogs` in `hugo.yaml`.
Without a data file, or without any tours in it, the shortcode renders nothing and the build logs a warning. To silence it, for example for an upcoming event, add `warning-schedule-empty` to `ignoreLogs` in `hugo.yaml`.
## Shortcode: Tour map
`{{< tour-map >}}` on an event's overview page shows a map of the tour locations, with the routes from the Congress, on OpenStreetMap. For a different event than the page's section, pass its name: `{{< tour-map 39c3 >}}`. It uses [Leaflet](https://leafletjs.com), which is kept in [assets/vendor/leaflet/](assets/vendor/leaflet/).
- Each tour gets a marker in its colour, with a symbol for its `icon`, and its route as a line; walking legs are dotted. Clicking a marker or a route shows the tour title, linked to the tour page, and the directions.
- The map shows all tours at once. Scroll-wheel zoom only starts after clicking into the map, so the page still scrolls normally.
- Without JavaScript, and for screen readers, it shows a list of the tours with their directions and a link to each location on openstreetmap.org.
- If a text is missing in the page's language, the other language is used.
- Without any tours with `geo`, the shortcode renders nothing and the build logs a warning with the ID `warning-tour-map-empty`.
### Map data in the schedule file
A map of an event shows where each tour takes place and how to get there from the Congress. The data for it goes into the same file as the schedule, `data/schedule/<event>.yaml`, next to `days`. Like the schedule, it is shared by both languages; anything that people read comes with a `de` and an `en` version.
```yaml
map:
origin: # where all routes start, shown with its own marker
name: { de: "Congress (CCH)", en: "Congress (CCH)" }
geo: { lat: 53.561450, lon: 9.986360 }
icon: computer
tours:
miniatur-wunderland: # tour slug, as in days[].tours[].tour
geo: { lat: 53.543843, lon: 9.988765 }
color: "#32cd32"
icon: tram-stop
directions:
de: |
- Ab S Dammtor mit dem Bus 4 Richtung Osakaallee.
- Am Rathausmarkt in die U3 Richtung Schlump umsteigen.
- An der U Baumwall aussteigen, über den Kanal, links am Kanal entlang bis zum Eingang.
en: |
- From S Dammtor take bus 4 towards Osakaallee.
- At Rathausmarkt change to the U3 towards Schlump.
- Exit at U Baumwall, cross the canal and walk left alongside it to the entrance.
route: # from map.origin to geo, one entry per leg
- { mode: walk, polyline: "…" }
- { mode: bus, line: "4", polyline: "…" }
- { mode: subway, line: "U3", polyline: "…" }
- { mode: walk, polyline: "…" }
days:
# … as described under "Shortcode: Tour schedule"
```
`tours` has one entry per tour slug that should appear on the map. A slug can be in `tours` without being scheduled, and the other way round; scheduled tours without an entry simply don't appear on the map.
| Field | Required | Content |
|---|---|---|
| `geo` | yes | Meeting point or venue: `lat` and `lon` in decimal degrees (WGS 84), six decimals are plenty. Write it even though the tour page has the same value in `params.geo`; the map only reads this file. |
| `color` | no | Colour of the marker and the route, as a CSS colour (`"#32cd32"` or `LimeGreen`). Pick colours that differ between tours. |
| `icon` | no | Name of a symbol for the marker. The map knows uMap's pictogram names `computer`, `ferry`, `tram-stop`, `railway-halt`, `fire-station`, `garden`, `mast-communications`, `ford` and `viewpoint` (see `ICONS` in [assets/js/tour-map.js](assets/js/tour-map.js)); others give a plain coloured circle. |
| `directions` | no | How to get there from the origin, per language (`de`, `en`) as Markdown, usually a short list of steps. |
| `route` | no | The way from `map.origin` to `geo` as a list of legs in travel order. Without it the tour only gets a marker. |
Each leg in `route` has:
- `mode`: `walk`, `bus`, `subway` (U-Bahn), `train` (S-Bahn, regional train), `ferry` or `other`. The map can draw walking legs differently from rides.
- `line` (optional): the line name as signposted, for example `"4"`, `"U3"` or `"S2"`.
- The geometry, in one of two forms:
- `polyline`: an [encoded polyline](https://developers.google.com/maps/documentation/utilities/polylinealgorithm) as returned by most routing services. Add `precision: 6` if it was encoded with six decimals instead of the default five.
- `coordinates`: a list of `[lon, lat]` pairs (GeoJSON order), handy for hand-drawn routes exported from uMap or geojson.io.
Legs should join up: each leg starts where the previous one ends, and the first leg starts at `map.origin`.
`map.origin` is usually the same for all tours of an event, so it is written once. It takes `name` (per language), `geo` and an optional `icon`.
### The 39C3 tour map (uMap)
The 39C3 overview pages embed a map with the `map-39c3` shortcode ([layouts/_shortcodes/map-39c3.html](layouts/_shortcodes/map-39c3.html)). It is an iframe of the uMap map [39c3 Hackertours](https://umap.openstreetmap.fr/en/map/39c3-hackertours_1329184), which was created by the uMap user WinstonS in December 2025. The map has no other editors, so only that account can change it. Nothing about the map is stored in this repository apart from the embed code.
How the map is built:
- The background is the "Jawg Dark" tile layer.
- Layer 1 holds a single marker for the Congress (CCH).
- Each tour has its own layer, named after the tour (`Alsterdampfer`, `MiWuLa`, `XFEL`, …), with two features of the same colour:
- A marker at the venue, with a pictogram icon. Its name is an HTML link to the English tour page, and its description lists the directions from the Congress (English only).
- A line named "Way to …" from the Congress area (mostly S Dammtor) to the venue, with the same directions as its description.
- The lines were most likely drawn by hand with uMap's line tool rather than calculated by a routing service: most of them start near S Dammtor, but each at a slightly different point, the vertices are spaced irregularly (30 to 100 m on average, up to several hundred metres), and the XFEL route consists of two separately drawn pieces. The routes combine walking, bus, U-Bahn and S-Bahn.
To change the map, edit it on uMap (as the owner, or after the owner adds you as an editor); the website picks up changes immediately because the iframe loads the live map. When adding a layer, its ID has to be added to the `datalayers` parameter in the embed URL, which uMap's "Share" panel generates.
The uMap data has been converted into this format in [data/schedule/39c3.yaml](data/schedule/39c3.yaml), with English directions only and each route as one leg of mode `other`. To get the data out again, for example after changes on uMap:
- A single layer as GeoJSON: `https://umap.openstreetmap.fr/en/datalayer/1329184/<layer-id>/`. The layer IDs are listed in the `datalayers` parameter of the embed URL.
- The whole map: the "Download" option in uMap's share panel exports all layers as one GeoJSON file.
In the GeoJSON, `coordinates` are already in `[lon, lat]` order, the colour and icon are in each feature's `properties._umap_options`, and the directions are in `properties.description`.

View file

@ -1,4 +1,5 @@
@import './_schedule.css';
@import './_tour-map.css';
.header__intro_headline {
font: bold var(--fs-xxl) "Arial";

91
assets/css/_tour-map.css Normal file
View file

@ -0,0 +1,91 @@
/* Tour map shortcode: Leaflet map of the tour locations, a plain list without JavaScript. */
.tour-map {
margin-block: var(--spacing-l);
}
/* Without JavaScript only the list is shown. */
.tour-map__canvas {
display: none;
}
.tour-map--ready {
.tour-map__canvas {
display: block;
height: clamp(20rem, 60vh, 36rem);
border: 1px solid color-mix(in oklch, var(--color-text) 20%, transparent);
border-radius: var(--radius-m);
}
/* Kept for screen readers, which can't use the map itself. */
.tour-map__list {
position: absolute;
width: 1px;
height: 1px;
overflow: hidden;
clip-path: inset(50%);
white-space: nowrap;
}
}
.tour-map__marker span {
display: grid;
place-items: center;
width: 2rem;
height: 2rem;
/* Leaflet positions the icon's top left corner; centre it on the location. */
margin: -1rem 0 0 -1rem;
border: 2px solid #fff;
border-radius: 50%;
background: var(--tour-map-color, #555);
box-shadow: 0 1px 4px rgb(0 0 0 / 0.4);
font-size: 1rem;
line-height: 1;
}
.tour-map__marker--origin span {
width: 2.75rem;
height: 2.75rem;
margin: -1.375rem 0 0 -1.375rem;
background: #111;
font-size: 1.4rem;
}
.tour-map {
.leaflet-popup-content-wrapper,
.leaflet-popup-tip {
background: var(--color-body-bg);
color: var(--color-text);
}
.leaflet-popup-content {
margin: 0.75rem 1rem;
font-family: var(--ff-alt);
font-size: var(--fs-s);
line-height: 1.4;
}
}
.tour-map__popup-title {
font-family: var(--ff-headings);
font-size: var(--fs-m);
font-weight: 600;
}
.tour-map__directions {
ul {
margin: 0.5rem 0 0;
padding-inline-start: 1.1rem;
}
p {
margin: 0.5rem 0 0;
}
}
/* Dark map tiles to match the dark colour scheme. */
@media (prefers-color-scheme: dark) {
.tour-map .leaflet-tile-pane {
filter: invert(1) hue-rotate(180deg) brightness(0.9) contrast(0.9);
}
}

125
assets/js/tour-map.js Normal file
View file

@ -0,0 +1,125 @@
// Tour map shortcode: draws the tour locations and their routes from the Congress on
// OpenStreetMap tiles with Leaflet. The data comes from the data-tour-map attribute.
// Marker symbols for the icon names in the data files (uMap pictogram names).
const ICONS = {
computer: '💻',
ferry: '⛴️',
'tram-stop': '🚊',
'railway-halt': '🚂',
'fire-station': '🚒',
garden: '🌳',
'mast-communications': '📡',
ford: '🚇',
viewpoint: '🔭',
};
// Google's encoded polyline algorithm, returning [lat, lon] pairs.
const POPUP = { minWidth: 220, maxWidth: 320 };
const decodePolyline = (encoded, precision = 5) => {
const factor = 10 ** precision;
const points = [];
let index = 0;
let lat = 0;
let lon = 0;
const next = () => {
let result = 0;
let shift = 0;
let byte;
do {
byte = encoded.charCodeAt(index++) - 63;
result |= (byte & 0x1f) << shift;
shift += 5;
} while (byte >= 0x20);
return result & 1 ? ~(result >> 1) : result >> 1;
};
while (index < encoded.length) {
lat += next();
lon += next();
points.push([lat / factor, lon / factor]);
}
return points;
};
const legPoints = (leg) =>
leg.coordinates
? leg.coordinates.map(([lon, lat]) => [lat, lon])
: decodePolyline(leg.polyline ?? '', leg.precision ?? 5);
const markerIcon = (color, icon, modifier) => {
const symbol = document.createElement('span');
symbol.textContent = ICONS[icon] ?? '';
if (color) symbol.style.setProperty('--tour-map-color', color);
return L.divIcon({
className: `tour-map__marker${modifier ? ` tour-map__marker--${modifier}` : ''}`,
html: symbol,
iconSize: null,
});
};
const popup = (tour) => {
const content = document.createElement('div');
const title = document.createElement(tour.url ? 'a' : 'strong');
title.className = 'tour-map__popup-title';
title.textContent = tour.title;
if (tour.url) title.href = tour.url;
content.append(title);
if (tour.directions) {
// Rendered from the site's own data files by Hugo.
const directions = document.createElement('div');
directions.className = 'tour-map__directions';
directions.innerHTML = tour.directions;
content.append(directions);
}
return content;
};
for (const element of document.querySelectorAll('[data-tour-map]')) {
const { origin, tours } = JSON.parse(element.dataset.tourMap);
// Show the map container first: Leaflet needs its size.
element.classList.add('tour-map--ready');
const map = L.map(element.querySelector('.tour-map__canvas'), { scrollWheelZoom: false });
L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png', {
maxZoom: 19,
attribution: '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a>',
}).addTo(map);
// Zooming with the scroll wheel only once the map is in use, so it doesn't catch page scrolling.
map.on('click focus', () => map.scrollWheelZoom.enable());
map.on('mouseout blur', () => map.scrollWheelZoom.disable());
const bounds = L.latLngBounds([]);
for (const tour of tours) {
for (const leg of tour.route) {
const points = legPoints(leg);
L.polyline(points, {
color: tour.color || '#555',
weight: 4,
opacity: 0.8,
dashArray: leg.mode === 'walk' ? '2 8' : null,
})
.bindPopup(popup(tour), POPUP)
.addTo(map);
points.forEach((point) => bounds.extend(point));
}
L.marker([tour.lat, tour.lon], { icon: markerIcon(tour.color, tour.icon), title: tour.title })
.bindPopup(popup(tour), POPUP)
.addTo(map);
bounds.extend([tour.lat, tour.lon]);
}
if (origin.lat !== undefined) {
L.marker([origin.lat, origin.lon], {
icon: markerIcon('', origin.icon, 'origin'),
title: origin.name,
zIndexOffset: 1000,
})
.bindPopup(popup({ title: origin.name }), POPUP)
.addTo(map);
bounds.extend([origin.lat, origin.lon]);
}
map.fitBounds(bounds, { padding: [24, 24], maxZoom: 16 });
}

26
assets/vendor/leaflet/LICENSE vendored Normal file
View file

@ -0,0 +1,26 @@
BSD 2-Clause License
Copyright (c) 2010-2023, Volodymyr Agafonkin
Copyright (c) 2010-2011, CloudMade
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

3
assets/vendor/leaflet/README vendored Normal file
View file

@ -0,0 +1,3 @@
Leaflet 1.9.4 (https://leafletjs.com), from the npm package leaflet-1.9.4.tgz
(sha512-nxS1ynzJOmOlHp+iL3FyWqK89GtNL8U8rvlMOsQdTTssxZwCXh8N2NB3GDQOL+YR3XnWyZAxwQixURb+FA74PA==).
Only dist/leaflet.js, dist/leaflet.css and LICENSE are kept; the tour map uses no Leaflet images.

661
assets/vendor/leaflet/leaflet.css vendored Normal file
View file

@ -0,0 +1,661 @@
/* required styles */
.leaflet-pane,
.leaflet-tile,
.leaflet-marker-icon,
.leaflet-marker-shadow,
.leaflet-tile-container,
.leaflet-pane > svg,
.leaflet-pane > canvas,
.leaflet-zoom-box,
.leaflet-image-layer,
.leaflet-layer {
position: absolute;
left: 0;
top: 0;
}
.leaflet-container {
overflow: hidden;
}
.leaflet-tile,
.leaflet-marker-icon,
.leaflet-marker-shadow {
-webkit-user-select: none;
-moz-user-select: none;
user-select: none;
-webkit-user-drag: none;
}
/* Prevents IE11 from highlighting tiles in blue */
.leaflet-tile::selection {
background: transparent;
}
/* Safari renders non-retina tile on retina better with this, but Chrome is worse */
.leaflet-safari .leaflet-tile {
image-rendering: -webkit-optimize-contrast;
}
/* hack that prevents hw layers "stretching" when loading new tiles */
.leaflet-safari .leaflet-tile-container {
width: 1600px;
height: 1600px;
-webkit-transform-origin: 0 0;
}
.leaflet-marker-icon,
.leaflet-marker-shadow {
display: block;
}
/* .leaflet-container svg: reset svg max-width decleration shipped in Joomla! (joomla.org) 3.x */
/* .leaflet-container img: map is broken in FF if you have max-width: 100% on tiles */
.leaflet-container .leaflet-overlay-pane svg {
max-width: none !important;
max-height: none !important;
}
.leaflet-container .leaflet-marker-pane img,
.leaflet-container .leaflet-shadow-pane img,
.leaflet-container .leaflet-tile-pane img,
.leaflet-container img.leaflet-image-layer,
.leaflet-container .leaflet-tile {
max-width: none !important;
max-height: none !important;
width: auto;
padding: 0;
}
.leaflet-container img.leaflet-tile {
/* See: https://bugs.chromium.org/p/chromium/issues/detail?id=600120 */
mix-blend-mode: plus-lighter;
}
.leaflet-container.leaflet-touch-zoom {
-ms-touch-action: pan-x pan-y;
touch-action: pan-x pan-y;
}
.leaflet-container.leaflet-touch-drag {
-ms-touch-action: pinch-zoom;
/* Fallback for FF which doesn't support pinch-zoom */
touch-action: none;
touch-action: pinch-zoom;
}
.leaflet-container.leaflet-touch-drag.leaflet-touch-zoom {
-ms-touch-action: none;
touch-action: none;
}
.leaflet-container {
-webkit-tap-highlight-color: transparent;
}
.leaflet-container a {
-webkit-tap-highlight-color: rgba(51, 181, 229, 0.4);
}
.leaflet-tile {
filter: inherit;
visibility: hidden;
}
.leaflet-tile-loaded {
visibility: inherit;
}
.leaflet-zoom-box {
width: 0;
height: 0;
-moz-box-sizing: border-box;
box-sizing: border-box;
z-index: 800;
}
/* workaround for https://bugzilla.mozilla.org/show_bug.cgi?id=888319 */
.leaflet-overlay-pane svg {
-moz-user-select: none;
}
.leaflet-pane { z-index: 400; }
.leaflet-tile-pane { z-index: 200; }
.leaflet-overlay-pane { z-index: 400; }
.leaflet-shadow-pane { z-index: 500; }
.leaflet-marker-pane { z-index: 600; }
.leaflet-tooltip-pane { z-index: 650; }
.leaflet-popup-pane { z-index: 700; }
.leaflet-map-pane canvas { z-index: 100; }
.leaflet-map-pane svg { z-index: 200; }
.leaflet-vml-shape {
width: 1px;
height: 1px;
}
.lvml {
behavior: url(#default#VML);
display: inline-block;
position: absolute;
}
/* control positioning */
.leaflet-control {
position: relative;
z-index: 800;
pointer-events: visiblePainted; /* IE 9-10 doesn't have auto */
pointer-events: auto;
}
.leaflet-top,
.leaflet-bottom {
position: absolute;
z-index: 1000;
pointer-events: none;
}
.leaflet-top {
top: 0;
}
.leaflet-right {
right: 0;
}
.leaflet-bottom {
bottom: 0;
}
.leaflet-left {
left: 0;
}
.leaflet-control {
float: left;
clear: both;
}
.leaflet-right .leaflet-control {
float: right;
}
.leaflet-top .leaflet-control {
margin-top: 10px;
}
.leaflet-bottom .leaflet-control {
margin-bottom: 10px;
}
.leaflet-left .leaflet-control {
margin-left: 10px;
}
.leaflet-right .leaflet-control {
margin-right: 10px;
}
/* zoom and fade animations */
.leaflet-fade-anim .leaflet-popup {
opacity: 0;
-webkit-transition: opacity 0.2s linear;
-moz-transition: opacity 0.2s linear;
transition: opacity 0.2s linear;
}
.leaflet-fade-anim .leaflet-map-pane .leaflet-popup {
opacity: 1;
}
.leaflet-zoom-animated {
-webkit-transform-origin: 0 0;
-ms-transform-origin: 0 0;
transform-origin: 0 0;
}
svg.leaflet-zoom-animated {
will-change: transform;
}
.leaflet-zoom-anim .leaflet-zoom-animated {
-webkit-transition: -webkit-transform 0.25s cubic-bezier(0,0,0.25,1);
-moz-transition: -moz-transform 0.25s cubic-bezier(0,0,0.25,1);
transition: transform 0.25s cubic-bezier(0,0,0.25,1);
}
.leaflet-zoom-anim .leaflet-tile,
.leaflet-pan-anim .leaflet-tile {
-webkit-transition: none;
-moz-transition: none;
transition: none;
}
.leaflet-zoom-anim .leaflet-zoom-hide {
visibility: hidden;
}
/* cursors */
.leaflet-interactive {
cursor: pointer;
}
.leaflet-grab {
cursor: -webkit-grab;
cursor: -moz-grab;
cursor: grab;
}
.leaflet-crosshair,
.leaflet-crosshair .leaflet-interactive {
cursor: crosshair;
}
.leaflet-popup-pane,
.leaflet-control {
cursor: auto;
}
.leaflet-dragging .leaflet-grab,
.leaflet-dragging .leaflet-grab .leaflet-interactive,
.leaflet-dragging .leaflet-marker-draggable {
cursor: move;
cursor: -webkit-grabbing;
cursor: -moz-grabbing;
cursor: grabbing;
}
/* marker & overlays interactivity */
.leaflet-marker-icon,
.leaflet-marker-shadow,
.leaflet-image-layer,
.leaflet-pane > svg path,
.leaflet-tile-container {
pointer-events: none;
}
.leaflet-marker-icon.leaflet-interactive,
.leaflet-image-layer.leaflet-interactive,
.leaflet-pane > svg path.leaflet-interactive,
svg.leaflet-image-layer.leaflet-interactive path {
pointer-events: visiblePainted; /* IE 9-10 doesn't have auto */
pointer-events: auto;
}
/* visual tweaks */
.leaflet-container {
background: #ddd;
outline-offset: 1px;
}
.leaflet-container a {
color: #0078A8;
}
.leaflet-zoom-box {
border: 2px dotted #38f;
background: rgba(255,255,255,0.5);
}
/* general typography */
.leaflet-container {
font-family: "Helvetica Neue", Arial, Helvetica, sans-serif;
font-size: 12px;
font-size: 0.75rem;
line-height: 1.5;
}
/* general toolbar styles */
.leaflet-bar {
box-shadow: 0 1px 5px rgba(0,0,0,0.65);
border-radius: 4px;
}
.leaflet-bar a {
background-color: #fff;
border-bottom: 1px solid #ccc;
width: 26px;
height: 26px;
line-height: 26px;
display: block;
text-align: center;
text-decoration: none;
color: black;
}
.leaflet-bar a,
.leaflet-control-layers-toggle {
background-position: 50% 50%;
background-repeat: no-repeat;
display: block;
}
.leaflet-bar a:hover,
.leaflet-bar a:focus {
background-color: #f4f4f4;
}
.leaflet-bar a:first-child {
border-top-left-radius: 4px;
border-top-right-radius: 4px;
}
.leaflet-bar a:last-child {
border-bottom-left-radius: 4px;
border-bottom-right-radius: 4px;
border-bottom: none;
}
.leaflet-bar a.leaflet-disabled {
cursor: default;
background-color: #f4f4f4;
color: #bbb;
}
.leaflet-touch .leaflet-bar a {
width: 30px;
height: 30px;
line-height: 30px;
}
.leaflet-touch .leaflet-bar a:first-child {
border-top-left-radius: 2px;
border-top-right-radius: 2px;
}
.leaflet-touch .leaflet-bar a:last-child {
border-bottom-left-radius: 2px;
border-bottom-right-radius: 2px;
}
/* zoom control */
.leaflet-control-zoom-in,
.leaflet-control-zoom-out {
font: bold 18px 'Lucida Console', Monaco, monospace;
text-indent: 1px;
}
.leaflet-touch .leaflet-control-zoom-in, .leaflet-touch .leaflet-control-zoom-out {
font-size: 22px;
}
/* layers control */
.leaflet-control-layers {
box-shadow: 0 1px 5px rgba(0,0,0,0.4);
background: #fff;
border-radius: 5px;
}
.leaflet-control-layers-toggle {
background-image: url(images/layers.png);
width: 36px;
height: 36px;
}
.leaflet-retina .leaflet-control-layers-toggle {
background-image: url(images/layers-2x.png);
background-size: 26px 26px;
}
.leaflet-touch .leaflet-control-layers-toggle {
width: 44px;
height: 44px;
}
.leaflet-control-layers .leaflet-control-layers-list,
.leaflet-control-layers-expanded .leaflet-control-layers-toggle {
display: none;
}
.leaflet-control-layers-expanded .leaflet-control-layers-list {
display: block;
position: relative;
}
.leaflet-control-layers-expanded {
padding: 6px 10px 6px 6px;
color: #333;
background: #fff;
}
.leaflet-control-layers-scrollbar {
overflow-y: scroll;
overflow-x: hidden;
padding-right: 5px;
}
.leaflet-control-layers-selector {
margin-top: 2px;
position: relative;
top: 1px;
}
.leaflet-control-layers label {
display: block;
font-size: 13px;
font-size: 1.08333em;
}
.leaflet-control-layers-separator {
height: 0;
border-top: 1px solid #ddd;
margin: 5px -10px 5px -6px;
}
/* Default icon URLs */
.leaflet-default-icon-path { /* used only in path-guessing heuristic, see L.Icon.Default */
background-image: url(images/marker-icon.png);
}
/* attribution and scale controls */
.leaflet-container .leaflet-control-attribution {
background: #fff;
background: rgba(255, 255, 255, 0.8);
margin: 0;
}
.leaflet-control-attribution,
.leaflet-control-scale-line {
padding: 0 5px;
color: #333;
line-height: 1.4;
}
.leaflet-control-attribution a {
text-decoration: none;
}
.leaflet-control-attribution a:hover,
.leaflet-control-attribution a:focus {
text-decoration: underline;
}
.leaflet-attribution-flag {
display: inline !important;
vertical-align: baseline !important;
width: 1em;
height: 0.6669em;
}
.leaflet-left .leaflet-control-scale {
margin-left: 5px;
}
.leaflet-bottom .leaflet-control-scale {
margin-bottom: 5px;
}
.leaflet-control-scale-line {
border: 2px solid #777;
border-top: none;
line-height: 1.1;
padding: 2px 5px 1px;
white-space: nowrap;
-moz-box-sizing: border-box;
box-sizing: border-box;
background: rgba(255, 255, 255, 0.8);
text-shadow: 1px 1px #fff;
}
.leaflet-control-scale-line:not(:first-child) {
border-top: 2px solid #777;
border-bottom: none;
margin-top: -2px;
}
.leaflet-control-scale-line:not(:first-child):not(:last-child) {
border-bottom: 2px solid #777;
}
.leaflet-touch .leaflet-control-attribution,
.leaflet-touch .leaflet-control-layers,
.leaflet-touch .leaflet-bar {
box-shadow: none;
}
.leaflet-touch .leaflet-control-layers,
.leaflet-touch .leaflet-bar {
border: 2px solid rgba(0,0,0,0.2);
background-clip: padding-box;
}
/* popup */
.leaflet-popup {
position: absolute;
text-align: center;
margin-bottom: 20px;
}
.leaflet-popup-content-wrapper {
padding: 1px;
text-align: left;
border-radius: 12px;
}
.leaflet-popup-content {
margin: 13px 24px 13px 20px;
line-height: 1.3;
font-size: 13px;
font-size: 1.08333em;
min-height: 1px;
}
.leaflet-popup-content p {
margin: 17px 0;
margin: 1.3em 0;
}
.leaflet-popup-tip-container {
width: 40px;
height: 20px;
position: absolute;
left: 50%;
margin-top: -1px;
margin-left: -20px;
overflow: hidden;
pointer-events: none;
}
.leaflet-popup-tip {
width: 17px;
height: 17px;
padding: 1px;
margin: -10px auto 0;
pointer-events: auto;
-webkit-transform: rotate(45deg);
-moz-transform: rotate(45deg);
-ms-transform: rotate(45deg);
transform: rotate(45deg);
}
.leaflet-popup-content-wrapper,
.leaflet-popup-tip {
background: white;
color: #333;
box-shadow: 0 3px 14px rgba(0,0,0,0.4);
}
.leaflet-container a.leaflet-popup-close-button {
position: absolute;
top: 0;
right: 0;
border: none;
text-align: center;
width: 24px;
height: 24px;
font: 16px/24px Tahoma, Verdana, sans-serif;
color: #757575;
text-decoration: none;
background: transparent;
}
.leaflet-container a.leaflet-popup-close-button:hover,
.leaflet-container a.leaflet-popup-close-button:focus {
color: #585858;
}
.leaflet-popup-scrolled {
overflow: auto;
}
.leaflet-oldie .leaflet-popup-content-wrapper {
-ms-zoom: 1;
}
.leaflet-oldie .leaflet-popup-tip {
width: 24px;
margin: 0 auto;
-ms-filter: "progid:DXImageTransform.Microsoft.Matrix(M11=0.70710678, M12=0.70710678, M21=-0.70710678, M22=0.70710678)";
filter: progid:DXImageTransform.Microsoft.Matrix(M11=0.70710678, M12=0.70710678, M21=-0.70710678, M22=0.70710678);
}
.leaflet-oldie .leaflet-control-zoom,
.leaflet-oldie .leaflet-control-layers,
.leaflet-oldie .leaflet-popup-content-wrapper,
.leaflet-oldie .leaflet-popup-tip {
border: 1px solid #999;
}
/* div icon */
.leaflet-div-icon {
background: #fff;
border: 1px solid #666;
}
/* Tooltip */
/* Base styles for the element that has a tooltip */
.leaflet-tooltip {
position: absolute;
padding: 6px;
background-color: #fff;
border: 1px solid #fff;
border-radius: 3px;
color: #222;
white-space: nowrap;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
pointer-events: none;
box-shadow: 0 1px 3px rgba(0,0,0,0.4);
}
.leaflet-tooltip.leaflet-interactive {
cursor: pointer;
pointer-events: auto;
}
.leaflet-tooltip-top:before,
.leaflet-tooltip-bottom:before,
.leaflet-tooltip-left:before,
.leaflet-tooltip-right:before {
position: absolute;
pointer-events: none;
border: 6px solid transparent;
background: transparent;
content: "";
}
/* Directions */
.leaflet-tooltip-bottom {
margin-top: 6px;
}
.leaflet-tooltip-top {
margin-top: -6px;
}
.leaflet-tooltip-bottom:before,
.leaflet-tooltip-top:before {
left: 50%;
margin-left: -6px;
}
.leaflet-tooltip-top:before {
bottom: 0;
margin-bottom: -12px;
border-top-color: #fff;
}
.leaflet-tooltip-bottom:before {
top: 0;
margin-top: -12px;
margin-left: -6px;
border-bottom-color: #fff;
}
.leaflet-tooltip-left {
margin-left: -6px;
}
.leaflet-tooltip-right {
margin-left: 6px;
}
.leaflet-tooltip-left:before,
.leaflet-tooltip-right:before {
top: 50%;
margin-top: -6px;
}
.leaflet-tooltip-left:before {
right: 0;
margin-right: -12px;
border-left-color: #fff;
}
.leaflet-tooltip-right:before {
left: 0;
margin-left: -12px;
border-right-color: #fff;
}
/* Printing */
@media print {
/* Prevent printers from removing background-images of controls. */
.leaflet-control {
-webkit-print-color-adjust: exact;
print-color-adjust: exact;
}
}

6
assets/vendor/leaflet/leaflet.js vendored Normal file

File diff suppressed because one or more lines are too long

View file

@ -1,5 +1,348 @@
# Tour schedule for 39C3, rendered by the schedule shortcode.
# Tour schedule for 39C3, rendered by the schedule shortcode.
# tour: slug of the tour page in this section; lang: de, en or deen.
# Map data, converted from the uMap map "39c3 Hackertours"; see README.md.
map:
origin:
name: { de: "Congress (CCH)", en: "Congress (CCH)" }
geo: { lat: 53.561450, lon: 9.986358 }
icon: computer
tours:
alsterdampfer:
geo: { lat: 53.553312, lon: 9.993353 }
color: Black
icon: ferry
directions:
en: |
- S Dammtor take Bus 4 to Osakaallee.
- Exit on U/S Jungfernstieg and walk over to the boat.
route:
- mode: other
coordinates: [
[9.989201, 53.560686], [9.990166, 53.561097], [9.990413, 53.560874], [9.990478, 53.560899],
[9.990622, 53.560682], [9.990638, 53.560609], [9.990553, 53.560485], [9.99044, 53.560415],
[9.989791, 53.559405], [9.989072, 53.558356], [9.988825, 53.558028], [9.988493, 53.557257],
[9.988455, 53.557066], [9.987972, 53.556444], [9.987801, 53.556043], [9.987726, 53.555772],
[9.987806, 53.555616], [9.988128, 53.555457], [9.988772, 53.555288], [9.989399, 53.5551],
[9.990306, 53.554593], [9.990456, 53.554449], [9.990832, 53.554297], [9.991845, 53.553646],
[9.992768, 53.553035], [9.992704, 53.552996], [9.992774, 53.552948], [9.993364, 53.553226]
]
miwula:
geo: { lat: 53.543836, lon: 9.988772 }
color: LimeGreen
icon: tram-stop
directions:
en: |
- S Dammtor take Bus 4 towards Oasakaallee.
- Rathausmarkt change to U3 from U Rathaus towards Schlump.
- Exit on U Baumwall, cross the canal, walk left alongside it to the entrance of the MiWuLa.
route:
- mode: other
coordinates: [
[9.989254, 53.560663], [9.990118, 53.561039], [9.990317, 53.560851], [9.990413, 53.560867],
[9.990548, 53.560689], [9.990526, 53.560615], [9.990467, 53.560523], [9.990338, 53.560431],
[9.989561, 53.559217], [9.988724, 53.557999], [9.988391, 53.557247], [9.988359, 53.557053],
[9.987887, 53.55647], [9.987688, 53.556001], [9.987635, 53.555772], [9.987726, 53.5556],
[9.988069, 53.555425], [9.989346, 53.555071], [9.990247, 53.554551], [9.990381, 53.554427],
[9.99073, 53.554277], [9.993021, 53.552808], [9.993879, 53.55227], [9.994051, 53.552085],
[9.993975, 53.551954], [9.993198, 53.551604], [9.993037, 53.551482], [9.993139, 53.551323],
[9.99389, 53.550855], [9.993836, 53.550772], [9.994206, 53.550507], [9.994067, 53.550437],
[9.993203, 53.550087], [9.992302, 53.549854], [9.99073, 53.549471], [9.989791, 53.549076],
[9.989319, 53.548936], [9.988858, 53.548895], [9.987973, 53.548926], [9.987603, 53.548885],
[9.987281, 53.548767], [9.98698, 53.548487], [9.986492, 53.547776], [9.985988, 53.547288],
[9.985237, 53.546482], [9.984502, 53.545532], [9.983998, 53.545028], [9.983268, 53.544649],
[9.982485, 53.544343], [9.981862, 53.544279], [9.981873, 53.544216], [9.9822, 53.544247],
[9.982662, 53.544321], [9.983456, 53.543584], [9.988611, 53.543913], [9.988648, 53.543859]
]
eisenbahnmuseum:
geo: { lat: 53.530651, lon: 10.321487 }
color: DarkGreen
icon: railway-halt
directions:
en: |
- S Dammtor take S2 towards Aumühle
- S Aumühle, go right, across the street, and go to the right on the forest path, staying on the right side
route:
- mode: other
coordinates: [
[9.98941, 53.560568], [9.989812, 53.560746], [9.990681, 53.56], [9.991277, 53.55943],
[9.993476, 53.558302], [9.99449, 53.557869], [9.997376, 53.557101], [9.998471, 53.556859],
[9.999812, 53.556741], [10.000901, 53.556658], [10.002086, 53.556467], [10.003159, 53.556174],
[10.003915, 53.555884], [10.004548, 53.555571], [10.005541, 53.554899], [10.006233, 53.554389],
[10.006775, 53.55393], [10.00693, 53.553701], [10.00788, 53.551722], [10.008228, 53.55125],
[10.00877, 53.550794], [10.009645, 53.550326], [10.010691, 53.549985], [10.011619, 53.549812],
[10.012552, 53.549675], [10.013255, 53.549631], [10.014548, 53.549679], [10.015626, 53.549841],
[10.016876, 53.550138], [10.018099, 53.550472], [10.019177, 53.550734], [10.020534, 53.551106],
[10.021564, 53.551435], [10.022374, 53.551753], [10.02349, 53.552165], [10.02459, 53.55241],
[10.025255, 53.552474], [10.026076, 53.552474], [10.02673, 53.552429], [10.027599, 53.552286],
[10.028726, 53.551874], [10.029321, 53.551549], [10.029933, 53.551075], [10.030335, 53.550523],
[10.03122, 53.549293], [10.032304, 53.547916], [10.033629, 53.546201], [10.034664, 53.544856],
[10.036005, 53.543103], [10.0371, 53.541704], [10.037851, 53.540986], [10.039117, 53.540116],
[10.040431, 53.539514], [10.042341, 53.538889], [10.045828, 53.537862], [10.048773, 53.537021],
[10.052463, 53.535978], [10.055854, 53.535025], [10.05734, 53.534607], [10.059974, 53.533852],
[10.060821, 53.533517], [10.062178, 53.532869], [10.063471, 53.532203], [10.064533, 53.53169],
[10.066046, 53.530972], [10.067269, 53.530382], [10.068712, 53.529643], [10.070359, 53.528842],
[10.072548, 53.527752], [10.074071, 53.527076], [10.076276, 53.525944], [10.079425, 53.524362],
[10.081871, 53.523121], [10.084226, 53.521916], [10.086753, 53.520647], [10.089403, 53.519301],
[10.092074, 53.517983], [10.095556, 53.516255], [10.097562, 53.515231], [10.09945, 53.514293],
[10.101264, 53.513374], [10.103361, 53.512293], [10.105265, 53.51131], [10.10718, 53.51035],
[10.110603, 53.508576], [10.11306, 53.507342], [10.116681, 53.505507], [10.1202, 53.503724],
[10.123333, 53.502132], [10.126433, 53.500485], [10.128622, 53.499333], [10.131154, 53.498019],
[10.133584, 53.496816], [10.135408, 53.495944], [10.137441, 53.495067], [10.139523, 53.494196],
[10.141438, 53.493532], [10.145284, 53.492469], [10.147199, 53.492038], [10.149881, 53.491473],
[10.152703, 53.491036], [10.156066, 53.490608], [10.158609, 53.490324], [10.162724, 53.489894],
[10.166093, 53.489539], [10.171001, 53.489009], [10.174547, 53.488633], [10.180437, 53.487982],
[10.18504, 53.487449], [10.188639, 53.487066], [10.191236, 53.486788], [10.193177, 53.486734],
[10.195345, 53.486823], [10.197195, 53.487002], [10.198762, 53.48727], [10.200747, 53.487707],
[10.202453, 53.488212], [10.205162, 53.489287], [10.205977, 53.489667], [10.207763, 53.490561],
[10.208498, 53.49095], [10.210022, 53.4917], [10.210977, 53.492147], [10.211969, 53.492638],
[10.213251, 53.493197], [10.215198, 53.493956], [10.216084, 53.494279], [10.218127, 53.494891],
[10.219018, 53.495131], [10.220219, 53.495396], [10.221453, 53.495622], [10.223073, 53.495865],
[10.226131, 53.496187], [10.227976, 53.496423], [10.229237, 53.496611], [10.230503, 53.49686],
[10.231683, 53.497141], [10.232788, 53.497444], [10.233668, 53.497709], [10.234795, 53.498114],
[10.235658, 53.498465], [10.23686, 53.498992], [10.237729, 53.499423], [10.238737, 53.499984],
[10.239639, 53.500562], [10.240186, 53.500961], [10.240717, 53.501337], [10.241066, 53.501621],
[10.241457, 53.501947], [10.241967, 53.502448], [10.242466, 53.502859], [10.242879, 53.503223],
[10.243801, 53.503963], [10.244193, 53.504241], [10.244874, 53.504722], [10.245459, 53.505093],
[10.246097, 53.505479], [10.247337, 53.506133], [10.248157, 53.506563], [10.248753, 53.506825],
[10.249702, 53.507185], [10.250829, 53.507591], [10.252009, 53.507989], [10.253516, 53.508385],
[10.255437, 53.508867], [10.257003, 53.50917], [10.258731, 53.509521], [10.260608, 53.509907],
[10.262282, 53.510331], [10.263929, 53.510832], [10.265023, 53.51117], [10.26572, 53.511422],
[10.26638, 53.511696], [10.267115, 53.512047], [10.268392, 53.512692], [10.269443, 53.513244],
[10.270801, 53.514086], [10.271664, 53.514717], [10.27227, 53.515212], [10.273156, 53.515999],
[10.274035, 53.516921], [10.275478, 53.518411], [10.276685, 53.519505], [10.277823, 53.520401],
[10.278504, 53.520857], [10.279695, 53.521623], [10.280768, 53.52226], [10.282999, 53.523284],
[10.284566, 53.523893], [10.286572, 53.524572], [10.288476, 53.52507], [10.29927, 53.527698],
[10.302209, 53.528354], [10.30399, 53.528702], [10.30723, 53.529228], [10.310749, 53.529624],
[10.314285, 53.529882], [10.315132, 53.529917], [10.315111, 53.529968], [10.314564, 53.529949],
[10.31451, 53.53015], [10.315009, 53.530395], [10.31496, 53.530628], [10.314794, 53.530864],
[10.315738, 53.531001], [10.316913, 53.531078], [10.31782, 53.531125], [10.318576, 53.531333],
[10.319032, 53.531489], [10.319365, 53.531521], [10.319933, 53.531457], [10.320448, 53.531342],
[10.320894, 53.531192], [10.321151, 53.531074], [10.321242, 53.530877], [10.321398, 53.530698]
]
alternative-harbor-cruise:
geo: { lat: 53.543584, lon: 9.979658 }
color: MediumBlue
icon: ferry
directions:
en: |
- Dammtor take Bus 4 towards Oasakaallee.
- Rathausmarkt change to U3 from U Rathaus towards Schlump.
- Exit on U Baumwall, go to the docks, towards the big white "Cap Sandiego" ship.
route:
- mode: other
coordinates: [
[9.989297, 53.560644], [9.990183, 53.56102], [9.990365, 53.560851], [9.990461, 53.560867],
[9.990596, 53.560689], [9.990574, 53.560615], [9.990515, 53.560523], [9.990386, 53.560431],
[9.989609, 53.559217], [9.988772, 53.557999], [9.988439, 53.557247], [9.988407, 53.557053],
[9.987935, 53.55647], [9.987736, 53.556001], [9.987683, 53.555772], [9.987774, 53.5556],
[9.988117, 53.555425], [9.989394, 53.555071], [9.990295, 53.554551], [9.990429, 53.554427],
[9.990778, 53.554277], [9.993069, 53.552808], [9.993927, 53.55227], [9.994099, 53.552085],
[9.994023, 53.551954], [9.993246, 53.551604], [9.993085, 53.551482], [9.993187, 53.551323],
[9.993938, 53.550855], [9.993884, 53.550772], [9.994254, 53.550507], [9.994115, 53.550437],
[9.993251, 53.550087], [9.99235, 53.549854], [9.990778, 53.549471], [9.989839, 53.549076],
[9.989367, 53.548936], [9.988906, 53.548895], [9.988021, 53.548926], [9.987651, 53.548885],
[9.987329, 53.548767], [9.987028, 53.548487], [9.98654, 53.547776], [9.986036, 53.547288],
[9.985285, 53.546482], [9.98455, 53.545532], [9.984046, 53.545028], [9.983316, 53.544649],
[9.982522, 53.54433], [9.981648, 53.544241], [9.981658, 53.54419], [9.981278, 53.544184],
[9.981272, 53.544117], [9.980553, 53.543884], [9.979244, 53.543823], [9.979239, 53.543578],
[9.979545, 53.543578]
]
retro-games-museum:
geo: { lat: 53.607995, lon: 10.039364 }
color: DeepPink
icon: computer
directions:
en: |
- S Dammtor take S2/5 towards Hauptbahnhof.
- Hauptbahnhof switch to S1 towards Airport/Poppenbüttel.
- Exit S Rübenkamp, leave station to the right and walk along to street, crossing the next big crossroad.
route:
- mode: other
coordinates: [
[9.989335, 53.560622], [9.989812, 53.560823], [9.990612, 53.560118], [9.990976, 53.559774],
[9.991336, 53.559433], [9.99258, 53.558802], [9.993643, 53.55828], [9.99456, 53.557885],
[9.996834, 53.557282], [9.998487, 53.556897], [9.999849, 53.556769], [10.000917, 53.556693],
[10.002167, 53.556486], [10.003245, 53.556202], [10.004296, 53.555797], [10.004838, 53.555463],
[10.005326, 53.555106], [10.006276, 53.554446], [10.006855, 53.553949], [10.006984, 53.553736],
[10.007172, 53.553344], [10.007424, 53.552831], [10.007654, 53.552859], [10.007982, 53.552037],
[10.008148, 53.551642], [10.008529, 53.551119], [10.00899, 53.550762], [10.009956, 53.550287],
[10.01083, 53.55001], [10.011855, 53.549838], [10.013121, 53.54972], [10.01392, 53.549714],
[10.014719, 53.549768], [10.015637, 53.549908], [10.016897, 53.550227], [10.019188, 53.550855],
[10.020706, 53.551243], [10.021892, 53.551632], [10.022948, 53.552056], [10.023855, 53.552477],
[10.024627, 53.552936], [10.025078, 53.553337], [10.025437, 53.55386], [10.025615, 53.55429],
[10.025818, 53.555058], [10.026033, 53.555648], [10.02628, 53.555992], [10.026805, 53.55655],
[10.027251, 53.556852], [10.027825, 53.557129], [10.028495, 53.557416], [10.030963, 53.558407],
[10.03224, 53.558908], [10.036376, 53.560526], [10.040125, 53.561998], [10.041772, 53.562578],
[10.043478, 53.562999], [10.045704, 53.563372], [10.047115, 53.563505], [10.048537, 53.563598],
[10.0516, 53.563789], [10.053053, 53.564018], [10.055602, 53.564611], [10.057533, 53.56506],
[10.058498, 53.56536], [10.059078, 53.565624], [10.059625, 53.565949], [10.06007, 53.566258],
[10.060365, 53.566554], [10.060639, 53.566937], [10.060794, 53.567354], [10.060886, 53.56787],
[10.060896, 53.568166], [10.060671, 53.568753], [10.060344, 53.569297], [10.059507, 53.570689],
[10.059008, 53.571706], [10.057839, 53.574101], [10.057651, 53.574579], [10.057554, 53.575072],
[10.057511, 53.576092], [10.057501, 53.577184], [10.057393, 53.577751], [10.057265, 53.578324],
[10.056868, 53.579073], [10.056369, 53.579799], [10.055113, 53.581634], [10.054475, 53.582309],
[10.053542, 53.583114], [10.052544, 53.583777], [10.051723, 53.584299], [10.049905, 53.585452],
[10.04837, 53.586423], [10.047748, 53.586729], [10.047297, 53.586894], [10.046879, 53.587022],
[10.046209, 53.587203], [10.045243, 53.587413], [10.043301, 53.587732], [10.041381, 53.588085],
[10.040007, 53.588467], [10.038784, 53.589092], [10.03791, 53.589795], [10.037363, 53.59062],
[10.037019, 53.591594], [10.036933, 53.59297], [10.03673, 53.59395], [10.036091, 53.59565],
[10.035828, 53.596513], [10.035539, 53.597834], [10.035222, 53.598863], [10.034713, 53.600254],
[10.033838, 53.603354], [10.033522, 53.604354], [10.033361, 53.605388], [10.033119, 53.606295],
[10.033007, 53.606273], [10.032899, 53.606614], [10.03305, 53.606668], [10.032894, 53.607323],
[10.035002, 53.607708], [10.037202, 53.608094], [10.038355, 53.608135], [10.039235, 53.60803]
]
flora:
geo: { lat: 53.562068, lon: 9.961601 }
color: Red
icon: garden
directions:
en: |
- S Dammtor take S5 towards Elbgaustraße.
- Exit next station (S Sternschanze).
- 10 minutes on foot to "Schulterblatt 71".
route:
- mode: other
coordinates: [
[9.989094, 53.560759], [9.989598, 53.560972], [9.988756, 53.561641], [9.985676, 53.563305],
[9.984952, 53.56361], [9.984185, 53.563837], [9.983439, 53.563935], [9.982951, 53.563974],
[9.978536, 53.563971], [9.974856, 53.563958], [9.97219, 53.563865], [9.971042, 53.563821],
[9.969964, 53.563824], [9.968634, 53.563875], [9.965968, 53.563789], [9.965973, 53.563747],
[9.965726, 53.563744], [9.96571, 53.563955], [9.965361, 53.563964], [9.965383, 53.563094],
[9.961955, 53.561769], [9.961687, 53.561998]
]
firestation:
geo: { lat: 53.583290, lon: 10.044385 }
color: Orange
icon: fire-station
directions:
en: |
- S Dammtor take S2/5 towards Hauptbahnhof.
- Hauptbahnhof switch to S1 towards Airport/Poppenbüttel.
- Exit U/S Barmbek, leave station to the south, walk across the canal.
route:
- mode: other
coordinates: [
[9.989383, 53.560596], [9.989812, 53.560778], [9.99059, 53.560109], [9.991304, 53.559427],
[9.992559, 53.558799], [9.9936, 53.558267], [9.994544, 53.557878], [9.996808, 53.557273],
[9.998465, 53.556887], [9.999817, 53.55676], [10.00089, 53.55668], [10.002151, 53.556476],
[10.003218, 53.556189], [10.004264, 53.555782], [10.004801, 53.555457], [10.005294, 53.555103],
[10.006244, 53.554434], [10.006828, 53.553943], [10.006957, 53.553729], [10.007145, 53.553334],
[10.007392, 53.552805], [10.007638, 53.552834], [10.007955, 53.552043], [10.008121, 53.551632],
[10.008486, 53.551113], [10.008958, 53.550753], [10.009929, 53.550281], [10.010819, 53.549988],
[10.011839, 53.549822], [10.013099, 53.549695], [10.013915, 53.549698], [10.014725, 53.549742],
[10.015669, 53.549883], [10.016913, 53.550208], [10.019199, 53.550845], [10.020744, 53.551231],
[10.021935, 53.551623], [10.022964, 53.552043], [10.023887, 53.552464], [10.024665, 53.552926],
[10.025105, 53.553331], [10.02547, 53.553857], [10.025615, 53.55429], [10.025818, 53.555058],
[10.026033, 53.555648], [10.02628, 53.555992], [10.026805, 53.55655], [10.027251, 53.556852],
[10.027825, 53.557129], [10.028495, 53.557416], [10.030963, 53.558407], [10.03224, 53.558908],
[10.036376, 53.560526], [10.040125, 53.561998], [10.041772, 53.562578], [10.043478, 53.562999],
[10.045704, 53.563372], [10.047115, 53.563505], [10.048537, 53.563598], [10.0516, 53.563789],
[10.053053, 53.564018], [10.055602, 53.564611], [10.057533, 53.56506], [10.058498, 53.56536],
[10.059078, 53.565624], [10.059625, 53.565949], [10.06007, 53.566258], [10.060365, 53.566554],
[10.060639, 53.566937], [10.060794, 53.567354], [10.060886, 53.56787], [10.060896, 53.568166],
[10.060671, 53.568753], [10.060344, 53.569297], [10.059507, 53.570689], [10.059008, 53.571706],
[10.057839, 53.574101], [10.057651, 53.574579], [10.057554, 53.575072], [10.057511, 53.576092],
[10.057452, 53.577203], [10.057324, 53.577815], [10.05713, 53.578312], [10.05676, 53.579044],
[10.056251, 53.579774], [10.054985, 53.581621], [10.054346, 53.58228], [10.053397, 53.583083],
[10.052404, 53.583748], [10.051573, 53.584296], [10.049792, 53.585426], [10.048209, 53.58642],
[10.047566, 53.586751], [10.046772, 53.587006], [10.046123, 53.587181], [10.045152, 53.587356],
[10.0452, 53.587391], [10.045624, 53.587337], [10.04528, 53.586614], [10.046112, 53.586496],
[10.046348, 53.586404], [10.046546, 53.585987], [10.046273, 53.585812], [10.045393, 53.584452],
[10.045184, 53.584315], [10.044739, 53.583755], [10.044444, 53.58336]
]
fsk:
geo: { lat: 53.555240, lon: 9.983429 }
color: MediumOrchid
icon: mast-communications
directions:
en: |
15 minutes on foot from the CCH to "Valentinskamp 34a", easier to reach by going towards "Speckstraße 83".
route:
- mode: other
coordinates: [
[9.986165, 53.561077], [9.986068, 53.560976], [9.985843, 53.560874], [9.986127, 53.560533],
[9.986363, 53.56045], [9.986352, 53.5603], [9.986722, 53.559956], [9.986266, 53.559615],
[9.985784, 53.558946], [9.98551, 53.558962], [9.985242, 53.558822], [9.984684, 53.558226],
[9.98418, 53.558044], [9.983971, 53.557942], [9.984158, 53.557649], [9.983751, 53.557518],
[9.984089, 53.556986], [9.984067, 53.555578], [9.984148, 53.555045], [9.983488, 53.555014],
[9.983477, 53.555173]
]
alter-elbtunnel:
geo: { lat: 53.545924, lon: 9.966584 }
color: Maroon
icon: ford
directions:
en: |
- U Stephansplatz take U1 towards Farmsen
- U/S Jungfernstieg switch to S1/3 towards Wedel/Pinneberg
- Exit at U/S Landungsbrücken, walking outside towards the right to the building with the stone dome
route:
- mode: other
coordinates: [
[9.988015, 53.561001], [9.988772, 53.560415], [9.988648, 53.559589], [9.988933, 53.559061],
[9.988863, 53.558649], [9.989163, 53.558602], [9.989807, 53.557923], [9.990252, 53.556693],
[9.990757, 53.554688], [9.991647, 53.553535], [9.99346, 53.552126], [9.993616, 53.552171],
[9.993514, 53.552231], [9.989394, 53.550526], [9.987291, 53.549742], [9.986272, 53.5495],
[9.985156, 53.549385], [9.983118, 53.549392], [9.981358, 53.549175], [9.980017, 53.548665],
[9.97933, 53.548162], [9.977313, 53.54695], [9.975109, 53.546437], [9.973719, 53.546198],
[9.972598, 53.546112], [9.971268, 53.546153], [9.970484, 53.54622], [9.970517, 53.546297],
[9.970779, 53.546259], [9.970366, 53.545599], [9.969696, 53.545797], [9.966702, 53.545908]
]
xfel:
geo: { lat: 53.590107, lon: 9.828815 }
color: Aqua
icon: viewpoint
directions:
en: |
- S Dammtor take S5 to Elbgaustraße.
- S Holstenstraße transfer to Bus X3 to Schenefeld.
- Stadionstraße transfer to Bus 2 to Aneken.
- In Aneken, on foot taking a right at the next crossroads.
route:
- mode: other
coordinates: [
[9.989153, 53.560724], [9.989673, 53.560953], [9.988761, 53.56167], [9.987296, 53.562457],
[9.985768, 53.563282], [9.984963, 53.563639], [9.98419, 53.563853], [9.983423, 53.563961],
[9.982919, 53.563993], [9.974813, 53.56398], [9.971091, 53.563846], [9.970044, 53.563843],
[9.968591, 53.563897], [9.965909, 53.563818], [9.964058, 53.563652], [9.962566, 53.563595],
[9.96188, 53.563512], [9.961456, 53.563407], [9.960093, 53.562967], [9.957299, 53.561852],
[9.956918, 53.561708], [9.95644, 53.561597], [9.955405, 53.561425], [9.954166, 53.561243],
[9.953404, 53.561202], [9.952766, 53.561221], [9.952101, 53.561307], [9.94929, 53.561922],
[9.94922, 53.561858], [9.948791, 53.561966], [9.949016, 53.562381], [9.949107, 53.562352],
[9.949091, 53.562285], [9.948131, 53.56246], [9.94746, 53.562575], [9.945374, 53.562989],
[9.942348, 53.563416], [9.941495, 53.563646], [9.940782, 53.563661], [9.939462, 53.563633],
[9.935203, 53.563773], [9.933406, 53.563684], [9.932402, 53.563512], [9.931818, 53.563451],
[9.9292, 53.563486], [9.920456, 53.563983], [9.920091, 53.56405], [9.918466, 53.564104],
[9.917414, 53.564292], [9.916947, 53.564397], [9.915912, 53.564582], [9.913133, 53.565213],
[9.912549, 53.565385], [9.912302, 53.565513], [9.910832, 53.565895], [9.910435, 53.565959],
[9.907565, 53.567], [9.906626, 53.567411], [9.903569, 53.568682], [9.903354, 53.568874],
[9.902517, 53.569208], [9.902265, 53.569412], [9.901021, 53.569848], [9.89962, 53.570403],
[9.898687, 53.571078], [9.898274, 53.571311], [9.894052, 53.573072], [9.892974, 53.573853],
[9.891794, 53.5744], [9.891166, 53.574592], [9.89027, 53.575203], [9.889449, 53.575866],
[9.888629, 53.57684], [9.887497, 53.578156], [9.886601, 53.579006], [9.885823, 53.579637],
[9.885195, 53.580086], [9.884267, 53.580831], [9.884407, 53.580869], [9.884289, 53.580933],
[9.884171, 53.580876], [9.882642, 53.581933], [9.882052, 53.582455], [9.880421, 53.584474],
[9.879305, 53.585582], [9.878619, 53.586086], [9.876784, 53.587856], [9.876215, 53.588518],
[9.8754, 53.589066], [9.874789, 53.58976], [9.873276, 53.591155], [9.87113, 53.592562],
[9.86922, 53.594441], [9.867439, 53.596001], [9.866366, 53.596599], [9.86495, 53.597128],
[9.861946, 53.597809], [9.860305, 53.597974], [9.855219, 53.598204], [9.853964, 53.598331],
[9.853653, 53.598331], [9.849876, 53.598719], [9.847215, 53.599216], [9.844426, 53.599623],
[9.843439, 53.59963], [9.841594, 53.599744], [9.839072, 53.600235], [9.835591, 53.60147]
]
- mode: other
coordinates: [
[9.828885, 53.590177], [9.828708, 53.591489], [9.82667, 53.591521], [9.826021, 53.591505],
[9.825162, 53.5914], [9.825125, 53.59148], [9.823848, 53.591355], [9.823231, 53.592919],
[9.822421, 53.594364], [9.822298, 53.594925], [9.82211, 53.598325], [9.82218, 53.599006],
[9.822416, 53.599846], [9.822485, 53.60055], [9.822609, 53.600839], [9.823092, 53.600668],
[9.823971, 53.600512], [9.824958, 53.60047], [9.825538, 53.600352], [9.826589, 53.599881],
[9.828038, 53.59955], [9.828644, 53.599499], [9.829341, 53.5997], [9.829904, 53.600056],
[9.829872, 53.600209], [9.830157, 53.600286], [9.83101, 53.601432], [9.831439, 53.60188],
[9.831766, 53.602094], [9.833472, 53.602756], [9.834416, 53.602994], [9.834593, 53.602702],
[9.834856, 53.60239], [9.835011, 53.601887], [9.835435, 53.601537], [9.835585, 53.601467]
]
days:
- day: 0
date: "2025-12-26"

View file

@ -1,2 +1,4 @@
schedule_day: "Tag {{ .Day }}"
schedule_pick_day: "Tag auswählen"
tour_map_label: "Karte der Tourorte"
tour_map_osm: "auf OpenStreetMap"

View file

@ -1,2 +1,4 @@
schedule_day: "Day {{ .Day }}"
schedule_pick_day: "Choose a day"
tour_map_label: "Map of the tour locations"
tour_map_osm: "on OpenStreetMap"

View file

@ -0,0 +1,10 @@
{{- /* Leaflet and the script for the tour-map shortcode, included by the base templates on pages that use it. */ -}}
{{- with resources.Get "vendor/leaflet/leaflet.css" | fingerprint }}
<link rel="stylesheet" href="{{ .RelPermalink }}" integrity="{{ .Data.Integrity }}" crossorigin="anonymous">
{{- end }}
{{- with resources.Get "vendor/leaflet/leaflet.js" | fingerprint }}
<script src="{{ .RelPermalink }}" integrity="{{ .Data.Integrity }}" crossorigin="anonymous" defer></script>
{{- end }}
{{- with resources.Get "js/tour-map.js" | minify | fingerprint }}
<script src="{{ .RelPermalink }}" integrity="{{ .Data.Integrity }}" crossorigin="anonymous" defer></script>
{{- end }}

View file

@ -0,0 +1,70 @@
{{- /*
Map of an event's tour locations with routes from the Congress, on OpenStreetMap tiles.
Reads the map and tours sections of data/schedule/<event>.yaml (format in README.md).
Without JavaScript it shows a list of the locations with links to OpenStreetMap.
Usage: {{< tour-map >}} on a section page (the event is the section name),
or {{< tour-map 39c3 >}}.
*/ -}}
{{- $event := .Get 0 | default .Page.Section -}}
{{- $lang := .Page.Language.Name -}}
{{- $other := cond (eq $lang "de") "en" "de" -}}
{{- $data := dict -}}
{{- with hugo.Data.schedule }}{{ with index . $event }}{{ $data = . }}{{ end }}{{ end -}}
{{- /* Tour page titles start with the event name ("39C3 Rote Flora"); the map doesn't need it. */ -}}
{{- $prefix := printf `(?i)^%s\s+` $event -}}
{{- $tours := slice -}}
{{- range $slug, $tour := $data.tours -}}
{{- with $tour.geo -}}
{{- $title := $slug -}}
{{- $url := "" -}}
{{- with $.Page.GetPage $slug -}}
{{- $title = .LinkTitle | replaceRE $prefix "" -}}
{{- $url = .RelPermalink -}}
{{- else -}}
{{- warnf "tour-map: %s: no page for tour %q" $event $slug -}}
{{- end -}}
{{- $directions := "" -}}
{{- with $tour.directions -}}
{{- with index . $lang | default (index . $other) -}}
{{- $directions = $.Page.RenderString (dict "display" "block") . -}}
{{- end -}}
{{- end -}}
{{- $tours = $tours | append (dict
"title" $title
"url" $url
"lat" .lat
"lon" .lon
"color" ($tour.color | default "")
"icon" ($tour.icon | default "")
"directions" $directions
"route" ($tour.route | default slice)
) -}}
{{- end -}}
{{- end -}}
{{- $origin := dict -}}
{{- with $data.map }}{{ with .origin }}{{ with .geo -}}
{{- $name := "" -}}
{{- with $data.map.origin.name }}{{ $name = index . $lang | default (index . $other) }}{{ end -}}
{{- $origin = dict "name" $name "lat" .lat "lon" .lon "icon" ($data.map.origin.icon | default "") -}}
{{- end }}{{ end }}{{ end -}}
{{- if not $tours -}}
{{- warnidf "warning-tour-map-empty" "tour-map: no tours with geo in data/schedule/%s.yaml: %s" $event .Position -}}
{{- else -}}
{{- $tours = sort $tours "title" -}}
<div class="tour-map" data-tour-map="{{ jsonify (dict "origin" $origin "tours" $tours) }}">
<div class="tour-map__canvas" role="region" aria-label="{{ i18n "tour_map_label" }}"></div>
<ul class="tour-map__list">
{{- range $tours }}
<li>
{{- if .url }}<a href="{{ .url }}">{{ .title }}</a>{{ else }}{{ .title }}{{ end }}
(<a href="https://www.openstreetmap.org/?mlat={{ .lat }}&amp;mlon={{ .lon }}#map=17/{{ .lat }}/{{ .lon }}">{{ i18n "tour_map_osm" }}</a>)
{{- with .directions }}{{ . }}{{ end }}
</li>
{{- end }}
</ul>
</div>
{{- end -}}

View file

@ -14,6 +14,7 @@
{{ partial "head/extra.html" . }}
{{ block "head" . }}{{ end }}
{{ if .HasShortcode "schedule" }}{{ partial "schedule-script.html" . }}{{ end }}
{{ if .HasShortcode "tour-map" }}{{ partial "tour-map-script.html" . }}{{ end }}
</head>
<body class="{{ if .IsPage }}single-page{{ else }}list-page{{ end }}{{ if .IsHome }} front{{ end }}{{ with .Section }} section-page--{{ . | urlize }}{{ end }}{{ with $.Param "bodyclass" }} {{ . }}{{ end }}">

View file

@ -14,6 +14,7 @@
{{ partial "head/extra.html" . }}
{{ block "head" . }}{{ end }}
{{ if .HasShortcode "schedule" }}{{ partial "schedule-script.html" . }}{{ end }}
{{ if .HasShortcode "tour-map" }}{{ partial "tour-map-script.html" . }}{{ end }}
</head>
<body class="{{ if .IsPage }}single-page{{ else }}list-page{{ end }}{{ if .IsHome }} front{{ end }}{{ with .Section }} section-page--{{ . | urlize }}{{ end }}{{ with $.Param "bodyclass" }} {{ . }}{{ end }}">