This commit is contained in:
relikd
2024-06-27 21:13:47 +02:00
commit d44d74eb2c
72 changed files with 2444 additions and 0 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 696 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 618 B

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;
}
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,63 @@
.leaflet-control-locate a {
cursor: pointer;
}
.leaflet-control-locate a .leaflet-control-locate-location-arrow {
display: inline-block;
width: 16px;
height: 16px;
margin: 7px;
background-image: url('data:image/svg+xml;charset=UTF-8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="black" d="M445 4 29 195c-48 23-32 93 19 93h176v176c0 51 70 67 93 19L508 67c16-38-25-79-63-63z"/></svg>');
}
.leaflet-control-locate a .leaflet-control-locate-spinner {
display: inline-block;
width: 16px;
height: 16px;
margin: 7px;
background-image: url('data:image/svg+xml;charset=UTF-8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="black" d="M304 48a48 48 0 1 1-96 0 48 48 0 0 1 96 0zm-48 368a48 48 0 1 0 0 96 48 48 0 0 0 0-96zm208-208a48 48 0 1 0 0 96 48 48 0 0 0 0-96zM96 256a48 48 0 1 0-96 0 48 48 0 0 0 96 0zm13 99a48 48 0 1 0 0 96 48 48 0 0 0 0-96zm294 0a48 48 0 1 0 0 96 48 48 0 0 0 0-96zM109 61a48 48 0 1 0 0 96 48 48 0 0 0 0-96z"/></svg>');
animation: leaflet-control-locate-spin 2s linear infinite;
}
.leaflet-control-locate.active a .leaflet-control-locate-location-arrow {
background-image: url('data:image/svg+xml;charset=UTF-8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="rgb(32, 116, 182)" d="M445 4 29 195c-48 23-32 93 19 93h176v176c0 51 70 67 93 19L508 67c16-38-25-79-63-63z"/></svg>');
}
.leaflet-control-locate.following a .leaflet-control-locate-location-arrow {
background-image: url('data:image/svg+xml;charset=UTF-8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="rgb(252, 132, 40)" d="M445 4 29 195c-48 23-32 93 19 93h176v176c0 51 70 67 93 19L508 67c16-38-25-79-63-63z"/></svg>');
}
.leaflet-touch .leaflet-bar .leaflet-locate-text-active {
width: 100%;
max-width: 200px;
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
padding: 0 10px;
}
.leaflet-touch .leaflet-bar .leaflet-locate-text-active .leaflet-locate-icon {
padding: 0 5px 0 0;
}
.leaflet-control-locate-location circle {
animation: leaflet-control-locate-throb 4s ease infinite;
}
@keyframes leaflet-control-locate-throb {
0% {
stroke-width: 1;
}
50% {
stroke-width: 3;
transform: scale(0.8, 0.8);
}
100% {
stroke-width: 1;
}
}
@keyframes leaflet-control-locate-spin {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
/*# sourceMappingURL=L.Control.Locate.css.map */

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

15
frontend/3p/lozad.min.js vendored Normal file
View File

@@ -0,0 +1,15 @@
/*! lozad.js - v1.16.0 - 2020-09-10
* https://github.com/ApoorvSaxena/lozad.js
* Copyright (c) 2020 Apoorv Saxena; Licensed MIT */
!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define(e):t.lozad=e()}(this,function(){"use strict";
/**
* Detect IE browser
* @const {boolean}
* @private
*/var g="undefined"!=typeof document&&document.documentMode,v=function(t){return window&&window[t]},h=["data-iesrc","data-alt","data-src","data-srcset","data-background-image","data-toggle-class"],p={rootMargin:"0px",threshold:0,enableAutoReload:!1,load:function(t){if("picture"===t.nodeName.toLowerCase()){var e=t.querySelector("img"),r=!1;null===e&&(e=document.createElement("img"),r=!0),g&&t.getAttribute("data-iesrc")&&(e.src=t.getAttribute("data-iesrc")),t.getAttribute("data-alt")&&(e.alt=t.getAttribute("data-alt")),r&&t.append(e)}if("video"===t.nodeName.toLowerCase()&&!t.getAttribute("data-src")&&t.children){for(var a=t.children,o=void 0,i=0;i<=a.length-1;i++)(o=a[i].getAttribute("data-src"))&&(a[i].src=o);t.load()}t.getAttribute("data-poster")&&(t.poster=t.getAttribute("data-poster")),t.getAttribute("data-src")&&(t.src=t.getAttribute("data-src")),t.getAttribute("data-srcset")&&t.setAttribute("srcset",t.getAttribute("data-srcset"));var n=",";if(t.getAttribute("data-background-delimiter")&&(n=t.getAttribute("data-background-delimiter")),t.getAttribute("data-background-image"))t.style.backgroundImage="url('"+t.getAttribute("data-background-image").split(n).join("'),url('")+"')";else if(t.getAttribute("data-background-image-set")){var d=t.getAttribute("data-background-image-set").split(n),u=d[0].substr(0,d[0].indexOf(" "))||d[0];// Substring before ... 1x
u=-1===u.indexOf("url(")?"url("+u+")":u,1===d.length?t.style.backgroundImage=u:t.setAttribute("style",(t.getAttribute("style")||"")+"background-image: "+u+"; background-image: -webkit-image-set("+d+"); background-image: image-set("+d+")")}t.getAttribute("data-toggle-class")&&t.classList.toggle(t.getAttribute("data-toggle-class"))},loaded:function(){}};
/**
*
* @param {string} type
*
*/function k(t){t.setAttribute("data-loaded",!0)}var y=function(t){return"true"===t.getAttribute("data-loaded")},w=function(t){var e=1<arguments.length&&void 0!==arguments[1]?arguments[1]:document;return t instanceof Element?[t]:t instanceof NodeList?t:e.querySelectorAll(t)};return function(){var r,a,e,o=0<arguments.length&&void 0!==arguments[0]?arguments[0]:".lozad",t=1<arguments.length&&void 0!==arguments[1]?arguments[1]:{},i=Object.assign({},p,t),n=i.root,d=i.rootMargin,u=i.threshold,g=i.enableAutoReload,s=i.load,c=i.loaded,l=void 0,b=void 0;v("IntersectionObserver")&&(l=new IntersectionObserver((r=s,a=c,function(t,e){t.forEach(function(t){(0<t.intersectionRatio||t.isIntersecting)&&(e.unobserve(t.target),y(t.target)||(r(t.target),k(t.target),a(t.target)))})}),{root:n,rootMargin:d,threshold:u})),v("MutationObserver")&&g&&(b=new MutationObserver((e=s,function(t){t.forEach(function(t){y(t.target)&&"attributes"===t.type&&-1<h.indexOf(t.attributeName)&&e(t.target)})})));for(var f,m=w(o,n),A=0;A<m.length;A++)(f=m[A]).getAttribute("data-placeholder-background")&&(f.style.background=f.getAttribute("data-placeholder-background"));return{observe:function(){for(var t=w(o,n),e=0;e<t.length;e++)y(t[e])||(l?(b&&g&&b.observe(t[e],{subtree:!0,attributes:!0,attributeFilter:h}),l.observe(t[e])):(s(t[e]),k(t[e]),c(t[e])))},triggerLoad:function(t){y(t)||(s(t),k(t),c(t))},observer:l,mutationObserver:b}}});

1
frontend/data Symbolic link
View File

@@ -0,0 +1 @@
../backend/data

BIN
frontend/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 905 B

BIN
frontend/ico/150.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 946 B

BIN
frontend/ico/16.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 171 B

BIN
frontend/ico/192.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

BIN
frontend/ico/310.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

BIN
frontend/ico/32.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 293 B

BIN
frontend/ico/48.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 387 B

BIN
frontend/ico/512.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

BIN
frontend/ico/70.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 506 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<browserconfig>
<msapplication>
<tile>
<square70x70logo src="/ico/70.png"/>
<square150x150logo src="/ico/150.png"/>
<square310x310logo src="/ico/310.png"/>
<TileImage src="/ico/150.png"/>
<TileColor>#d1e7dd</TileColor>
</tile>
</msapplication>
</browserconfig>

1
frontend/ico/favicon.svg Normal file
View File

@@ -0,0 +1 @@
<svg enable-background="new 0 0 1792 1792" viewBox="0 0 1792 1792" xmlns="http://www.w3.org/2000/svg"><path d="m1767.5 121.6c16.3 11.7 24.5 27.1 24.5 46.4v1232c0 11.7-3.2 22.2-9.6 31.5s-14.9 16-25.4 20.1l-560 224c-14 6.4-28 6.4-42 0l-539-215.2-539 215.2c-5.8 2.9-12.8 4.4-21 4.4-11.1 0-21.6-3.2-31.5-9.6-16.3-11.7-24.5-27.2-24.5-46.4v-1232c0-11.7 3.2-22.2 9.6-31.5s14.9-16 25.4-20.1l560-224c14-6.4 28-6.4 42 0l539 215.2 539-215.2c18.7-7.6 36.2-5.9 52.5 5.2z"/><g fill="#fff"><path d="m588 1351-476 189.9v-1111.3l476-189.8z"/><path d="m1680 1362.4-476 189.8v-1111.2l476-189.9z"/><path d="m1148 1552.2-504-201.2v-1111.2l504 201.2z"/></g></svg>

After

Width:  |  Height:  |  Size: 641 B

View File

@@ -0,0 +1,18 @@
{
"name": "Klangkarte",
"short_name": "Klangkarte",
"background_color": "#d1e7dd",
"display": "fullscreen",
"icons": [
{
"src": "/ico/192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "/ico/512.png",
"sizes": "512x512",
"type": "image/png"
}
]
}

11
frontend/icons.svg Normal file
View File

@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<svg version="1.1" xmlns="http://www.w3.org/2000/svg">
<symbol id="mark" viewBox="0 0 69 110">
<path stroke="#000" stroke-width="3" d="m34.5 1.5c-18.2 0-33 14.8-33 33 0 5.3 1.3 10.4 3.5 14.8 5.4 10.8 29.5 57.7 29.5 57.7s24.1-47 29.5-57.7c2.2-4.5 3.5-9.5 3.5-14.8 0-18.2-14.8-33-33-33zm0 49.5c-9.1 0-16.5-7.4-16.5-16.5s7.4-16.5 16.5-16.5 16.5 7.4 16.5 16.5-7.4 16.5-16.5 16.5z"/>
</symbol>
<symbol id="pin" viewBox="0 0 60 100">
<path fill="#000" d="m25 58v42h10v-42z"/>
<circle stroke="#000" stroke-width="2" cx="30" cy="30" r="27.5"/>
<path fill="#fff" d="m30 14.5c.5 0 1.8-.5 1.8-1.8s-1.3-1.8-1.8-1.8c-5.4 0-10.1 1.9-13.9 5.7s-5.7 8.5-5.7 13.9c0 .5.6 1.8 1.8 1.8s1.8-1.3 1.8-1.8c0-4.4 1.6-8.2 4.7-11.4s6.9-4.6 11.3-4.6z"/>
</symbol>
</svg>

After

Width:  |  Height:  |  Size: 780 B

110
frontend/index.html Normal file
View File

@@ -0,0 +1,110 @@
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="utf-8">
<title>Klangkarte</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="shortcut icon" href="/favicon.ico">
<link rel="icon" type="image/png" sizes="32x32" href="/ico/32.png">
<link rel="icon" type="image/png" sizes="16x16" href="/ico/16.png">
<link rel="apple-touch-icon" sizes="180x180" href="/ico/apple-touch-icon.png">
<link rel="manifest" href="/ico/manifest.json">
<meta name="msapplication-config" content="/ico/browserconfig.xml">
<script src="/3p/bootstrap/bootstrap.min.js"></script>
<script src="/3p/leaflet/leaflet.js"></script>
<script src="/3p/leaflet/locate/L.Control.Locate.min.js"></script>
<script src="/3p/lozad.min.js"></script>
<script src="/script.js"></script>
<link rel="stylesheet" href="/3p/bootstrap/bootstrap.min.css" />
<link rel="stylesheet" href="/3p/leaflet/leaflet.css" />
<link rel="stylesheet" href="/3p/leaflet/locate/L.Control.Locate.css" />
<link rel="stylesheet" href="/style.css" />
<style id="colors"></style>
</head>
<body>
<div id="spin" class="position-absolute w-100 h-100 bg-dark bg-opacity-25">
<div class="position-absolute top-50 start-50">
<div class="spinner-border text-white" role="status">
<span class="visually-hidden">Loading...</span>
</div>
</div>
</div>
<div id="detail" class="modal fade" tabindex="-1" aria-labelledby="detail-title" aria-hidden="true">
<div class="modal-dialog modal-lg modal-fullscreen-lg-down">
<div class="modal-content">
<div class="modal-header">
<h2 class="modal-title fs-5" id="detail-title">Title</h2>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<audio controls preload="none">
<source src="" type="audio/mpeg">
Audio vom Browser nicht unterstüzt
</audio>
<small class="text-body-secondary">Typ:</small>
<span class="badge border">__CAT__</span>
<div id="detail-desc" class="py-2 mt-2">Description</div>
<small class="text-body-secondary">Foto:</small>
<div class="d-flex justify-content-center">
<img src="" alt="Kein Bild vorhanden" class="rounded">
</div>
<div id="detail-map-container">
<small class="text-body-secondary">Karte:</small>
<div id="detail-map"></div>
<div class="hstack gap-2 small">
<a id="osm-link" class="small link-secondary">OpenStreetMap</a>
<a id="g-maps" class="small link-secondary">Google Maps</a>
</div>
</div>
</div>
</div>
</div>
</div>
<div id="notice" class="modal fade" tabindex="-1" aria-hidden="true">
<div class="modal-dialog modal-md modal-fullscreen-md-down">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Title</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">Description</div>
</div>
</div>
</div>
<div id="map" class="d-none d-md-block"></div>
<div id="card-container" class="shadow-lg bg-success-subtle">
<h1 class="fs-4 text-center m-2 mb-4">Klangkarte
<a class="small text-decoration-none" title="Info" href="" onclick="return showNotice('info')">?⃝</a>
</h1>
<div id="cards">
<div id="card-template" class="card shadow-sm">
<img class="card-img-top lozad" alt="Kein Bild">
<div class="card-footer">
<h3 class="fs-5 card-title">__NAME__</h3>
<small class="card-text text-body-secondary">Typ:</small>
<span class="badge border">__CAT__</span>
</div>
<a class="stretched-link"></a>
</div>
</div>
<div class="text-center m-2 mt-4">
<a class="small" href="" onclick="return showNotice('imprint')">Impressum</a>
</div>
</div>
<script defer>start()</script>
</body>
</html>

305
frontend/script.js Normal file
View File

@@ -0,0 +1,305 @@
const DATA_ROOT = '/data/';
let CATEGORIES = {};
let PLACES = {};
let TEXT = {};
let DETAIL_MAP;
let DETAIL_MAP_LAYER;
// ---------------------
// Helper
// ---------------------
async function loadJson(url) {
const res = await fetch(url);
return await res.json();
}
function highlight(div) {
const prev = div.style;
div.style.transition = 'background-color .7s';
div.style.backgroundColor = 'yellow';
setTimeout(() => div.style = prev, 800);
}
// ---------------------
// HTML utils
// ---------------------
function makeMarker(place) {
return L.marker(L.latLng(place.loc), {
pid: place.id,
title: place.later ? 'Noch nicht verfügbar' : place.name,
icon: L.divIcon({
className: 'pin ff-c' + place.cat + (place.later ? ' later' : ''),
html: '<svg id="pin-' + place.id + '"><use href="/icons.svg#mark"></use></svg>',
iconSize: [34, 55],
iconAnchor: [17, 55],
popupAnchor: [0, -20],
tooltipAnchor: [0, -20],
offset: [10, 20],
}),
});
}
function setBadge(div, category) {
const badge = div.querySelector('.badge');
// clear previous color
badge.classList.remove('inv');
for (const cls of badge.classList) {
if (cls.startsWith('bg-c')) {
badge.classList.remove(cls);
}
}
// set new values
badge.innerText = category.name || '';
badge.classList.add('bg-c' + category.id);
if (category.inv) {
badge.classList.add('inv');
}
}
function loadAudio(detailDiv, srcUrl) {
const x = detailDiv.querySelector('audio');
x.hidden = !srcUrl;
x.querySelector('source').src = srcUrl || '';
x.load(); // stops playing and reloads source
}
function comeBackLater() {
showNotice('come-back-later');
}
// ---------------------
// Interactive
// ---------------------
function selectPin(e) {
document.getElementById('pin-' + e.target.dataset.pk)
.parentNode.classList.add('selected');
}
function unselectPin(e) {
document.getElementById('pin-' + e.target.dataset.pk)
.parentNode.classList.remove('selected');
}
function openDetails(placeId, password) {
initDetails(placeId);
new bootstrap.Modal('#detail').show(); // trigger modal
}
function showNotice(id) {
const txt = TEXT[id];
if (txt) {
const div = document.getElementById('notice');
const sz = txt.wide ? 'lg' : 'md';
div.firstElementChild.className = `modal-dialog modal-${sz} modal-fullscreen-${sz}-down`;
div.querySelector('.modal-title').innerText = txt.title;
div.querySelector('.modal-title').innerText = txt.title;
div.querySelector('.modal-body').innerHTML = txt.body;
new bootstrap.Modal(div).show();
} else {
console.error(`Missing text for "${id}"`)
}
return false;
}
// ---------------------
// Initializer
// ---------------------
function initColors() {
let rv = '';
for (const cat of Object.values(CATEGORIES)) {
rv += `.ff-c${cat.id} { fill: ${cat.color} }\n`;
rv += `.bg-c${cat.id} { background: ${cat.color} }\n`;
}
document.getElementById('colors').innerHTML = rv;
}
function initCard(placeId) {
const place = PLACES[placeId];
const category = CATEGORIES[place.cat];
const x = document.getElementById('card-template').cloneNode(true);
document.getElementById('cards').append(x);
x.id = 'card-' + placeId;
x.dataset.pk = placeId;
if (place.loc) {
x.onmouseenter = selectPin;
x.onmouseleave = unselectPin;
}
x.querySelector('a').href = '#' + placeId;
x.querySelector('img').dataset.src = place.cov || '';
x.querySelector('.card-title').innerText = place.name || '';
setBadge(x, category);
}
function initLoadingCard() {
const x = document.getElementById('card-template').cloneNode(true);
x.id = 'card-loading';
x.classList.add('placeholder-glow');
document.getElementById('cards').append(x);
x.querySelectorAll('img,h3,span').forEach(elem => {
elem.classList.add('placeholder');
elem.alt = '';
if (elem.tagName === 'H3') {
elem.classList.add('w-100');
}
});
return x;
}
function initDetails(placeId) {
const place = PLACES[placeId];
const category = CATEGORIES[place.cat];
const x = document.getElementById('detail');
x.querySelector('img').src = place.img || '';
x.querySelector('.modal-title').innerText = place.name || '';
x.querySelector('#detail-desc').innerHTML = place.desc || '';
setBadge(x, category);
loadAudio(x, place.audio);
// external map links
x.querySelector('#detail-map-container').hidden = !place.loc;
if (place.loc) {
const [lat, long] = place.loc;
x.querySelector('#osm-link').href = `https://www.openstreetmap.org/?mlat=${lat}&mlon=${long}&zoom=18`;
x.querySelector('#g-maps').href = `https://www.google.com/maps/search/?api=1&query=${lat}%2C${long}&zoom=18`;
}
setDetailMarker(place);
document.title = place.name + ' ' + document.title;
}
function clearDetails() {
const x = document.getElementById('detail');
loadAudio(x, '');
// in case of youtube videos or other media: stops everything else
x.querySelector('#detail-desc').innerHTML = '';
setDetailMarker(null);
document.title = document.title.split(' ').pop();
}
// ---------------------
// Map stuff
// ---------------------
function initGPS(map) {
L.control.locate({
returnToPrevBounds: true,
showPopup: false,
}).addTo(map);
}
function initDetailMap() {
const map = L.map('detail-map');
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
attribution: '© <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a>',
}).addTo(map);
initGPS(map);
DETAIL_MAP_LAYER = L.layerGroup([]).addTo(map);
DETAIL_MAP = map;
}
function setDetailMarker(place) {
if (place && place.loc) {
const pos = makeMarker(place).addTo(DETAIL_MAP_LAYER).getLatLng();
DETAIL_MAP.setView(pos, 17);
DETAIL_MAP.setMaxBounds(pos.toBounds(0));
setTimeout(() => DETAIL_MAP.invalidateSize(), 300);
setTimeout(() => DETAIL_MAP.invalidateSize(), 1000);
} else {
DETAIL_MAP_LAYER.clearLayers();
}
}
async function initMainMap() {
const osm = L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
attribution: '© <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a>',
});
const map = L.map('map', {
layers: [osm],
center: [49.894413, 10.880028],
zoom: 14,
// minZoom: 11,
});
initGPS(map);
// load data
const layers = {};
var bounds = L.latLngBounds();
var layerControl = L.control.layers(null, null, {
position: 'bottomright',
// sortLayers: true,
}).addTo(map);
// init checkbox to toggle groups
for (const cat of await loadJson(DATA_ROOT + 'categories.json')) {
CATEGORIES[cat.id] = cat;
layers[cat.id] = L.layerGroup([]).addTo(map);
layerControl.addOverlay(layers[cat.id],
'<i class="group-dot" style="background:' + cat.color + '"></i> '
+ cat.name);
}
// init places
for (const place of await loadJson(DATA_ROOT + 'places.json')) {
PLACES[place.id] = place;
const group = layers[place.cat];
if (place.loc) {
const marker = makeMarker(place).addTo(group);
marker.on('click', place.later ? comeBackLater : onMarkerClick);
bounds.extend(marker.getLatLng());
}
if (!place.later) {
initCard(place.id);
}
}
// adjust bounds & zoom
if (bounds.isValid()) {
map.fitBounds(bounds, { padding: [100, 100] });
}
// tooltip
function onTooltip(pin) {
const div = document.getElementById("card-" + pin.options.pid);
div.scrollIntoView({ behavior: 'smooth' });
highlight(div);
return '';
// const place = PLACES[pin.options.pid];
// return place.name;
}
// click events
function onMarkerClick(e) {
location.hash = e.target.options.pid; // triggers openDetails()
}
}
async function start() {
const temp = initLoadingCard();
await initMainMap();
initDetailMap();
initColors();
document.getElementById('spin').remove();
onHashChange();
loadJson(DATA_ROOT + 'text.json').then(x => TEXT = x)
temp.remove();
document.getElementById('detail').addEventListener('hidden.bs.modal', e => {
location.hash = '';
});
const observer = lozad();
observer.observe();
}
// event listener
function onHashChange() {
if (location.hash.length > 1) {
const [id, pw] = location.hash.slice(1).split(':', 1);
openDetails(id, pw);
} else {
clearDetails();
}
}
addEventListener('hashchange', onHashChange);

99
frontend/style.css Normal file
View File

@@ -0,0 +1,99 @@
html, body {
height: 100%;
margin: 0;
}
#map {
width: calc(100% - 316px);
height: 100%;
}
#detail-map {
width: 100%;
height: 200px;
}
#card-container {
position: absolute;
right: 0;
top: 0;
z-index: 810;
height: 100%;
width: 316px;
overflow: auto;
/* filter: drop-shadow(0 0 2px #000); */
}
.card {
margin: 8px;
}
.card img {
height: 200px;
object-fit: cover;
/* no-img fallback */
line-height: 200px;
background: #666;
color: white;
text-align: center;
font-weight: 100;
font-size: 24px;
}
.card:hover {
border-color: green;
}
.pin svg {
filter: drop-shadow(0 0 5px #888);
width: 100%;
height: 100%;
}
.pin.selected {
z-index: 800 !important;
}
.pin.selected svg {
filter: drop-shadow(0 0 5px yellow);
}
.pin.later {
fill-opacity: 0;
}
/* badge */
.badge {
color: black;
}
.badge.inv {
color: white;
}
.group-dot {
display: inline-block;
border-radius: 50%;
border: .5px solid black;
width: 1em;
height: 1em;
}
#spin {
z-index: 820;
}
#card-template {
display: none;
}
#detail img {
/* max-width: 100%; */
object-fit: cover;
width: 100%;
max-height: 600px;
}
#detail audio {
width: 100%;
}
@media(max-width: 768px) {
#card-container {
width: 100%;
}
.card img {
height: calc(100vw * 2/3);
line-height: calc((100vw - 20px) * 2/3);
}
}