Initial commit
This commit is contained in:
111
frontend/src/lib/components/Footer.svelte
Normal file
111
frontend/src/lib/components/Footer.svelte
Normal file
@@ -0,0 +1,111 @@
|
||||
<script lang="ts">
|
||||
import { navigate } from "svelte-routing"
|
||||
import { serviceNavigation, sites } from "../stores"
|
||||
</script>
|
||||
|
||||
<div class="footer">
|
||||
<div class="infos">
|
||||
<h3>Wasserski Erfurt</h3>
|
||||
<div class="infos-inner">
|
||||
<div class="upper">
|
||||
<p>Inh. Michael Sadlon</p>
|
||||
<p>Zum Nordstrand 4</p>
|
||||
<p>99085 Erfurt</p>
|
||||
</div>
|
||||
<div class="lower">
|
||||
<p>Tel.: 0361 - 796 876 4</p>
|
||||
<p>Fax.: 0361 - 796 876 8</p>
|
||||
<p>Email: info@wasserski-erfurt.de</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="social">
|
||||
<a href="https://www.instagram.com/wasserski_erfurt/">
|
||||
<svg width="36" height="36" viewBox="0 0 36 36" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
d="M11.7 3h12.6c4.8 0 8.7 3.9 8.7 8.7v12.6a8.7 8.7 0 0 1-8.7 8.7H11.7C6.9 33 3 29.1 3 24.3V11.7A8.7 8.7 0 0 1 11.7 3zm-.3 3A5.4 5.4 0 0 0 6 11.4v13.2c0 2.985 2.415 5.4 5.4 5.4h13.2a5.4 5.4 0 0 0 5.4-5.4V11.4C30 8.415 27.585 6 24.6 6H11.4zm14.475 2.25a1.875 1.875 0 1 1 0 3.75 1.875 1.875 0 0 1 0-3.75zM18 10.5a7.5 7.5 0 1 1 0 15 7.5 7.5 0 0 1 0-15zm0 3a4.5 4.5 0 1 0 0 9 4.5 4.5 0 0 0 0-9z"
|
||||
fill="#333333"></path>
|
||||
</svg>
|
||||
</a>
|
||||
<a href="https://www.facebook.com/wasserskierfurt/">
|
||||
<svg width="36" height="36" viewBox="0 0 36 36" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
d="M18 3.06c-8.25 0-15 6.735-15 15.03 0 7.5 5.49 13.725 12.66 14.85v-10.5h-3.81v-4.35h3.81v-3.315c0-3.765 2.235-5.835 5.67-5.835 1.635 0 3.345.285 3.345.285v3.705h-1.89c-1.86 0-2.445 1.155-2.445 2.34v2.82h4.17l-.675 4.35H20.34v10.5A15 15 0 0 0 33 18.09c0-8.295-6.75-15.03-15-15.03z"
|
||||
fill="#333333"></path>
|
||||
</svg>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="services">
|
||||
{#each $serviceNavigation?.elemente || [] as service, i (i)}
|
||||
<button
|
||||
on:click="{() => {
|
||||
navigate(`${$sites[service.seite]?.path}`)
|
||||
}}">{service?.name}</button
|
||||
>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style lang="less">
|
||||
@import "../assets/css/variables.less";
|
||||
.social {
|
||||
margin-top: 20px;
|
||||
}
|
||||
.footer {
|
||||
margin-top: 60px;
|
||||
width: 100vw;
|
||||
font-size: 16px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
max-width: calc(@body-maxwidth - min(9vw, 200px));
|
||||
background-color: @hover-color;
|
||||
padding: min(4.5vw, 100px) min(4.5vw, 100px) 0px min(4.5vw, 100px);
|
||||
}
|
||||
.infos {
|
||||
display: flex;
|
||||
height: 70%;
|
||||
.infos-inner {
|
||||
gap: 10px;
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
|
||||
.services {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
button {
|
||||
font-size: inherit;
|
||||
padding: 6px 0px;
|
||||
text-align: start;
|
||||
}
|
||||
}
|
||||
|
||||
@media @mobile {
|
||||
.infos {
|
||||
flex-direction: column;
|
||||
justify-content: start;
|
||||
|
||||
@media @mobile {
|
||||
gap: 20px;
|
||||
}
|
||||
@media @tablet {
|
||||
gap: 40px;
|
||||
}
|
||||
height: 350px;
|
||||
.infos-inner {
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
.footer {
|
||||
}
|
||||
}
|
||||
@media @desktop {
|
||||
.footer {
|
||||
height: 350px;
|
||||
.infos-inner {
|
||||
gap: 5vw;
|
||||
flex-direction: row;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
60
frontend/src/lib/components/cookieSet.svelte
Normal file
60
frontend/src/lib/components/cookieSet.svelte
Normal file
@@ -0,0 +1,60 @@
|
||||
<script lang="ts">
|
||||
export let cookieName
|
||||
export let backgroundUrl = ""
|
||||
export let textPosition = "center"
|
||||
export let background = ""
|
||||
let contentShown = false
|
||||
const positions = {
|
||||
oben: "flex-start",
|
||||
mitte: "center",
|
||||
unten: "flex-end",
|
||||
}
|
||||
|
||||
window.addEventListener("ccAccept", (e) => {
|
||||
if (e.detail[1] == cookieName) contentShown = true
|
||||
})
|
||||
//isCookieSet isnt really precise
|
||||
function checkCookie(name) {
|
||||
// Get all cookies
|
||||
var allCookies = decodeURIComponent(document.cookie)
|
||||
// Split into individual cookies
|
||||
var cookies = allCookies.split(";")
|
||||
var ccTagCookies = []
|
||||
cookies.forEach((e) => {
|
||||
e.includes("ccTags") ? (ccTagCookies = e.split(",")) : void 0
|
||||
})
|
||||
for (var i = 0; i < ccTagCookies.length; i++) {
|
||||
var c = ccTagCookies[i]
|
||||
// Trim whitespace
|
||||
while (c.charAt(0) == " ") c = c.substring(1)
|
||||
// If the cookie's name matches the given name
|
||||
if (c == cookieName) return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Verwendung
|
||||
if (checkCookie(cookieName)) contentShown = true
|
||||
</script>
|
||||
|
||||
{#if contentShown}
|
||||
<slot />
|
||||
{:else}
|
||||
<div
|
||||
style="display: flex;
|
||||
justify-content: center;
|
||||
align-items: {positions[textPosition]};
|
||||
background-image: url({backgroundUrl});
|
||||
background-size: cover;
|
||||
width: 100%;
|
||||
align-self: stretch;
|
||||
flex-grow: 1;
|
||||
"
|
||||
>
|
||||
<div style="background-color: rgba(255,255,255,0.7); padding: 20px; background: {background}; width: 100%;">
|
||||
<p>Cookie ist nicht aktiviert. Bitte aktivieren Sie ihn.</p>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<style lang="less"></style>
|
||||
28
frontend/src/lib/components/googleMaps.svelte
Normal file
28
frontend/src/lib/components/googleMaps.svelte
Normal file
@@ -0,0 +1,28 @@
|
||||
<script lang="ts">
|
||||
import { onMount } from "svelte"
|
||||
import CookieSet from "./cookieSet.svelte"
|
||||
$: iframeTitle = "testrest"
|
||||
|
||||
let setHeight = (element) => {
|
||||
element.style.height = (element.offsetWidth / 16) * 9 + "px"
|
||||
}
|
||||
</script>
|
||||
|
||||
<CookieSet cookieName="{'googleMaps'}" textPosition="{'unten'}" background="{'rgba(44, 44, 44, 0.4)'}">
|
||||
<iframe
|
||||
use:setHeight
|
||||
id="iframe"
|
||||
src="https://www.google.com/maps/embed?pb=!1m18!1m12!1m3!1d2510.8843160281504!2d11.046507612986247!3d50.99980984753832!2m3!1f0!2f0!3f0!3m2!1i1024!2i768!4f13.1!3m3!1m2!1s0x47a46d575a841fe1%3A0xd6703e9ebe1ca279!2sErfurt%20Wasserski-%20und%20Wakeboardanlage!5e0!3m2!1sde!2sde!4v1682691189533!5m2!1sde!2sde"
|
||||
style="border:0;"
|
||||
allowfullscreen=""
|
||||
loading="lazy"
|
||||
referrerpolicy="no-referrer-when-downgrade"></iframe>
|
||||
</CookieSet>
|
||||
|
||||
<style>
|
||||
iframe {
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
border: 0;
|
||||
}
|
||||
</style>
|
||||
71
frontend/src/lib/components/header/Header.svelte
Normal file
71
frontend/src/lib/components/header/Header.svelte
Normal file
@@ -0,0 +1,71 @@
|
||||
<script lang="ts">
|
||||
import DesktopHeader from "./desktop.svelte"
|
||||
import MobileHeader from "./mobile.svelte"
|
||||
import { navigation } from "../../stores"
|
||||
</script>
|
||||
|
||||
<main class="headercontainer">
|
||||
<nav>
|
||||
<div class="mobile-header">
|
||||
<MobileHeader />
|
||||
</div>
|
||||
<div class="desktop-header">
|
||||
<DesktopHeader />
|
||||
</div>
|
||||
</nav>
|
||||
</main>
|
||||
<div class="placeholder"></div>
|
||||
|
||||
<style global lang="less">
|
||||
@import "../../assets/css/main.less";
|
||||
@desktop: ~"only screen and (min-width: 1440px)";
|
||||
|
||||
.ignore {
|
||||
display: none !important;
|
||||
visibility: hidden;
|
||||
}
|
||||
nav {
|
||||
position: relative;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
.placeholder {
|
||||
height: 105px;
|
||||
}
|
||||
.headercontainer {
|
||||
display: flex;
|
||||
position: fixed;
|
||||
z-index: 5500;
|
||||
top: 0px;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
color: #333;
|
||||
height: 105px;
|
||||
}
|
||||
@media @desktop {
|
||||
.headercontainer,
|
||||
.placeholder {
|
||||
height: 120px;
|
||||
}
|
||||
nav {
|
||||
position: initial;
|
||||
}
|
||||
}
|
||||
|
||||
@media @mobile {
|
||||
.desktop-header {
|
||||
display: none;
|
||||
}
|
||||
.mobile-header {
|
||||
display: inherit;
|
||||
}
|
||||
}
|
||||
@media @desktop {
|
||||
.mobile-header {
|
||||
display: none;
|
||||
}
|
||||
.desktop-header {
|
||||
display: inherit;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
305
frontend/src/lib/components/header/desktop.svelte
Normal file
305
frontend/src/lib/components/header/desktop.svelte
Normal file
@@ -0,0 +1,305 @@
|
||||
<script lang="ts">
|
||||
import { navigation, sites } from "../../stores"
|
||||
|
||||
import { apiBaseURL } from "../../../config"
|
||||
import { navigate } from "svelte-routing"
|
||||
|
||||
function changeStateOfSite(menuOn: boolean) {
|
||||
let siteContainer = document.body
|
||||
if (menuOn) siteContainer.style.overflow = "hidden"
|
||||
else siteContainer.style.overflow = "initial"
|
||||
}
|
||||
|
||||
async function changeSubmenu(e, i) {
|
||||
changeStateOfSite(true)
|
||||
let submenu = document.getElementById("submenu-container")
|
||||
submenu.style.height = "calc(100vh - 120px)"
|
||||
submenu.classList.add("show-submenu")
|
||||
|
||||
let elements = document.getElementsByClassName("innersubmenu-container")
|
||||
Array.from(elements).forEach((element) => {
|
||||
element.classList.remove("shown")
|
||||
})
|
||||
let shownMenu = document.getElementById("submenu-" + i)
|
||||
shownMenu.classList.add("shown")
|
||||
}
|
||||
|
||||
let hoverTimeout
|
||||
</script>
|
||||
|
||||
<ul
|
||||
class="menu"
|
||||
on:mouseover="{(e) => {
|
||||
clearTimeout(hoverTimeout)
|
||||
hoverTimeout = setTimeout(() => {
|
||||
let element = document.getElementById('submenu-container')
|
||||
element.style.height = '0vh'
|
||||
changeStateOfSite(false)
|
||||
element.classList.remove('show-submenu')
|
||||
let elements = document.getElementsByClassName('select-menu')
|
||||
Array.from(elements).forEach((e) => e.classList.remove('select-menu'))
|
||||
}, 500)
|
||||
}}"
|
||||
on:mouseleave="{() => {
|
||||
clearTimeout(hoverTimeout)
|
||||
}}"
|
||||
>
|
||||
<button
|
||||
class="logo-container"
|
||||
on:click="{() => {
|
||||
let element = document.getElementById('submenu-container')
|
||||
element.style.height = '0vh'
|
||||
element.classList.remove('show-submenu')
|
||||
changeStateOfSite(false)
|
||||
navigate('/')
|
||||
}}"
|
||||
>
|
||||
<button class="img-logo-container"><img src="media/logo.png" alt="logo" /></button>
|
||||
<p class="logo-text">Wasserski-Erfurt</p>
|
||||
</button>
|
||||
<ul class="menuitem-container">
|
||||
{#if $navigation?.elemente}
|
||||
{#each $navigation.elemente as site, i (i)}
|
||||
<li
|
||||
class="menu-item"
|
||||
on:mousedown="{() => {
|
||||
if (site.endpoint) {
|
||||
let element = document.getElementById('submenu-container')
|
||||
element.style.height = '0vh'
|
||||
element.classList.remove('show-submenu')
|
||||
changeStateOfSite(false)
|
||||
navigate(`${$sites[site.seite].path}`)
|
||||
}
|
||||
}}"
|
||||
on:mouseenter|stopPropagation="{(e) => {
|
||||
clearTimeout(hoverTimeout)
|
||||
if (!site.endpoint) {
|
||||
let target = e.currentTarget
|
||||
hoverTimeout = setTimeout(() => {
|
||||
changeSubmenu(target, i)
|
||||
let elements = document.getElementsByClassName('select-menu')
|
||||
Array.from(elements).forEach((e) => e.classList.remove('select-menu'))
|
||||
target.classList.add('select-menu')
|
||||
}, 500)
|
||||
} else {
|
||||
hoverTimeout = setTimeout(() => {
|
||||
let elements = document.getElementsByClassName('select-menu')
|
||||
Array.from(elements).forEach((e) => e.classList.remove('select-menu'))
|
||||
let element = document.getElementById('submenu-container')
|
||||
element.style.height = '0vh'
|
||||
element.classList.remove('show-submenu')
|
||||
changeStateOfSite(false)
|
||||
}, 500)
|
||||
}
|
||||
}}"
|
||||
on:mouseleave|stopPropagation="{(e) => {
|
||||
clearTimeout(hoverTimeout)
|
||||
}}"
|
||||
>
|
||||
{site.name}
|
||||
</li>
|
||||
{/each}
|
||||
{/if}
|
||||
</ul>
|
||||
</ul>
|
||||
|
||||
<button
|
||||
class="submenu-container"
|
||||
id="submenu-container"
|
||||
on:mouseover="{(e) => {
|
||||
let element = e.currentTarget
|
||||
element.style.height = '0vh'
|
||||
changeStateOfSite(false)
|
||||
element.classList.remove('show-submenu')
|
||||
}}"
|
||||
>
|
||||
{#if $navigation?.elemente}
|
||||
<div class="inner-container">
|
||||
{#each $navigation?.elemente as submenu, i (i * 10)}
|
||||
{#if !submenu?.endpoint}
|
||||
<button
|
||||
on:mouseover|stopPropagation
|
||||
class="innersubmenu-container"
|
||||
id="{`submenu-${i}`}"
|
||||
on:click|stopPropagation
|
||||
>
|
||||
<div class="submenu-most-inner-container">
|
||||
<div class="submenu-img">
|
||||
<img
|
||||
src="{`${apiBaseURL}navigation/${$navigation?.id}/${submenu.image?.src}?filter=${
|
||||
window.innerWidth > 500 ? 'xl' : 'm'
|
||||
}`}"
|
||||
alt="img"
|
||||
/>
|
||||
</div>
|
||||
<ul class="sub-menu">
|
||||
{#each submenu?.elemente as submenu_point, i (i)}
|
||||
<li>
|
||||
<button
|
||||
class="submenu-item"
|
||||
on:click="{() => {
|
||||
let element = document.getElementById('submenu-container')
|
||||
element.style.height = '0vh'
|
||||
element.classList.remove('show-submenu')
|
||||
changeStateOfSite(false)
|
||||
navigate(`${$sites[submenu_point.seite]?.path}`)
|
||||
}}">{submenu_point?.name}</button
|
||||
>
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
</div>
|
||||
</button>
|
||||
{/if}
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
</button>
|
||||
|
||||
<style lang="less" global>
|
||||
@import "../../assets/css/variables.less";
|
||||
@desktop: ~"only screen and (min-width: 1440px)";
|
||||
@media @desktop {
|
||||
nav {
|
||||
flex-grow: 3;
|
||||
.desktop-header {
|
||||
margin: 0px auto 0px auto;
|
||||
max-width: @body-maxwidth;
|
||||
width: 100%;
|
||||
}
|
||||
.menu {
|
||||
padding: 0px min(4.5vw, 100px);
|
||||
max-width: 1800px;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
height: 120px;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
.logo-container {
|
||||
height: 60px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
.logo-text {
|
||||
font-weight: bold;
|
||||
font-size: 1.2rem;
|
||||
padding-left: 10px;
|
||||
}
|
||||
.img-logo-container {
|
||||
height: 100%;
|
||||
img {
|
||||
height: 100%;
|
||||
width: auto;
|
||||
object-fit: contain;
|
||||
}
|
||||
}
|
||||
}
|
||||
div {
|
||||
margin-right: 10%;
|
||||
}
|
||||
.menuitem-container {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
align-items: center;
|
||||
width: fit-content;
|
||||
height: 100%;
|
||||
max-width: 1400px;
|
||||
margin-left: 15px;
|
||||
.menu-item {
|
||||
font-size: 24px;
|
||||
border-radius: 60px;
|
||||
padding: 3px 14px;
|
||||
margin: 0px 2px;
|
||||
height: fit-content;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
background-color: @hover-color;
|
||||
}
|
||||
}
|
||||
.select-menu {
|
||||
background-color: @hover-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
.submenu-container {
|
||||
position: absolute;
|
||||
left: 0px;
|
||||
height: 0vh;
|
||||
overflow: hidden;
|
||||
width: 100vw;
|
||||
z-index: 2000;
|
||||
background-color: rgba(0, 0, 0, 0.854);
|
||||
opacity: 0;
|
||||
.inner-container {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: relative;
|
||||
.innersubmenu-container {
|
||||
width: 100%;
|
||||
height: 60vh;
|
||||
max-height: 700px;
|
||||
opacity: 0;
|
||||
position: absolute;
|
||||
visibility: hidden;
|
||||
background-color: rgb(255, 255, 255);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
.submenu-most-inner-container {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
max-width: 1800px;
|
||||
padding: 0px min(4.5vw, 100px);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
.sub-menu {
|
||||
display: flex;
|
||||
max-width: calc(@body-maxwidth / 2 - min(3vw, 80px));
|
||||
align-items: start;
|
||||
padding-left: 20px;
|
||||
flex-direction: column;
|
||||
width: 50%;
|
||||
li {
|
||||
border-bottom: 1px solid #dee2e6;
|
||||
|
||||
width: 100%;
|
||||
text-align: start;
|
||||
&:hover {
|
||||
border-bottom: 1px solid rgba(24, 24, 24, 0.795);
|
||||
}
|
||||
button {
|
||||
padding: 10px 0px;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
text-align: start;
|
||||
}
|
||||
}
|
||||
}
|
||||
.submenu-img {
|
||||
max-width: calc(@body-maxwidth / 2 - min(3vw, 80px));
|
||||
height: 100%;
|
||||
width: 50%;
|
||||
padding: 10px;
|
||||
padding-right: 20px;
|
||||
|
||||
img {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
}
|
||||
}
|
||||
.shown {
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
}
|
||||
}
|
||||
}
|
||||
.show-submenu {
|
||||
height: 100vh;
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
355
frontend/src/lib/components/header/mobile.svelte
Normal file
355
frontend/src/lib/components/header/mobile.svelte
Normal file
@@ -0,0 +1,355 @@
|
||||
<script lang="ts">
|
||||
import { navigation, sites } from "../../stores"
|
||||
import { onMount } from "svelte"
|
||||
import { apiBaseURL } from "../../../config"
|
||||
import { navigate } from "svelte-routing"
|
||||
|
||||
let images: HTMLImageElement[] = []
|
||||
function changeStateOfSite(menuOn: boolean) {
|
||||
let element = document.getElementById("menu")
|
||||
element.classList.toggle("show-menu")
|
||||
let body = document.body
|
||||
if (menuOn) {
|
||||
body.style.overflow = "initial"
|
||||
} else {
|
||||
body.style.overflow = "hidden"
|
||||
}
|
||||
|
||||
let button = document.getElementsByClassName("button-three")[0]
|
||||
const currentState = button.getAttribute("data-state")
|
||||
if (!currentState || currentState === "closed") {
|
||||
button.setAttribute("data-state", "opened")
|
||||
button.setAttribute("aria-expanded", "true")
|
||||
} else {
|
||||
button.setAttribute("data-state", "closed")
|
||||
button.setAttribute("aria-expanded", "false")
|
||||
}
|
||||
}
|
||||
|
||||
function imageSlide(images) {
|
||||
console.log(images)
|
||||
let currentImage = 0
|
||||
images[0].classList.add("show-img")
|
||||
let interval = setInterval(() => {
|
||||
images[currentImage].classList.remove("show-img")
|
||||
currentImage += 1
|
||||
if (images.length == currentImage) currentImage = 0
|
||||
images[currentImage].classList.add("show-img")
|
||||
}, 4000)
|
||||
return () => clearInterval(interval)
|
||||
}
|
||||
|
||||
function pushImages(node) {
|
||||
images[0] = node
|
||||
}
|
||||
|
||||
$: {
|
||||
if (images.length != 0) imageSlide(document.getElementsByClassName("img-menu"))
|
||||
}
|
||||
</script>
|
||||
|
||||
<ul>
|
||||
<div class="header">
|
||||
<button
|
||||
class="logo-container"
|
||||
on:click="{() => {
|
||||
navigate('/')
|
||||
let element = document.getElementById('menu')
|
||||
changeStateOfSite(element.classList.contains('show-menu'))
|
||||
}}"
|
||||
>
|
||||
<button class="img-logo-container"><img src="media/logo.png" alt="logo" /></button>
|
||||
<p class="logo-text">Wasserski-Erfurt</p>
|
||||
</button>
|
||||
<button
|
||||
class="button-three"
|
||||
on:click="{(e) => {
|
||||
let element = document.getElementById('menu')
|
||||
changeStateOfSite(element.classList.contains('show-menu'))
|
||||
}}"
|
||||
aria-controls="primary-navigation"
|
||||
aria-expanded="false"
|
||||
>
|
||||
<svg stroke="var(--button-color)" fill="none" class="hamburger" viewBox="-10 -10 120 120" width="45">
|
||||
<path
|
||||
class="line"
|
||||
stroke-width="10"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
d="m 20 40 h 60 a 1 1 0 0 1 0 20 h -60 a 1 1 0 0 1 0 -40 h 30 v 70"
|
||||
>
|
||||
</path>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<div class="menu-container" id="menu">
|
||||
{#if $navigation?.elemente}
|
||||
<div class="inner-container">
|
||||
<div class="higher-absolute">
|
||||
{#each $navigation.elemente as link, i (i)}
|
||||
<li
|
||||
class="menu"
|
||||
on:mousedown|stopPropagation="{(e) => {
|
||||
if (link.endpoint) {
|
||||
navigate(`${$sites[link.seite]?.path}`)
|
||||
let element = document.getElementById('menu')
|
||||
changeStateOfSite(element.classList.contains('show-menu'))
|
||||
return
|
||||
}
|
||||
let element = e.currentTarget
|
||||
element.classList.toggle('active')
|
||||
let chevronContainer = document.getElementById('chevron-' + i)
|
||||
chevronContainer.src =
|
||||
chevronContainer.src.split('/')?.pop() == 'chevron-down.png'
|
||||
? 'media/chevron-up.png'
|
||||
: 'media/chevron-down.png'
|
||||
}}"
|
||||
>
|
||||
<div class="menu-point">
|
||||
<div>{link?.name}</div>
|
||||
|
||||
<div class:hidden="{link?.endpoint}">
|
||||
<img id="{`chevron-${i}`}" src="media/chevron-down.png" alt="chev" />
|
||||
</div>
|
||||
</div>
|
||||
{#if !link?.endpoint}
|
||||
<ul class="submenu">
|
||||
{#each link.elemente as submenu, i (i)}
|
||||
<li>
|
||||
<button
|
||||
on:mousedown="{(e) => {
|
||||
navigate(`${$sites[submenu.seite]?.path}`)
|
||||
let element = document.getElementById('menu')
|
||||
changeStateOfSite(element.classList.contains('show-menu'))
|
||||
}}">{submenu.name}</button
|
||||
>
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
{/if}
|
||||
</li>
|
||||
{/each}
|
||||
|
||||
<button class="socials">
|
||||
<a target="_blank" href="https://www.instagram.com/wasserski_erfurt/">
|
||||
<svg
|
||||
width="36"
|
||||
height="36"
|
||||
viewBox="0 0 36 36"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M11.7 3h12.6c4.8 0 8.7 3.9 8.7 8.7v12.6a8.7 8.7 0 0 1-8.7 8.7H11.7C6.9 33 3 29.1 3 24.3V11.7A8.7 8.7 0 0 1 11.7 3zm-.3 3A5.4 5.4 0 0 0 6 11.4v13.2c0 2.985 2.415 5.4 5.4 5.4h13.2a5.4 5.4 0 0 0 5.4-5.4V11.4C30 8.415 27.585 6 24.6 6H11.4zm14.475 2.25a1.875 1.875 0 1 1 0 3.75 1.875 1.875 0 0 1 0-3.75zM18 10.5a7.5 7.5 0 1 1 0 15 7.5 7.5 0 0 1 0-15zm0 3a4.5 4.5 0 1 0 0 9 4.5 4.5 0 0 0 0-9z"
|
||||
fill="#fff"></path>
|
||||
</svg>
|
||||
</a>
|
||||
<a target="_blank" href="https://www.facebook.com/wasserskierfurt/">
|
||||
<svg
|
||||
width="36"
|
||||
height="36"
|
||||
viewBox="0 0 36 36"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M18 3.06c-8.25 0-15 6.735-15 15.03 0 7.5 5.49 13.725 12.66 14.85v-10.5h-3.81v-4.35h3.81v-3.315c0-3.765 2.235-5.835 5.67-5.835 1.635 0 3.345.285 3.345.285v3.705h-1.89c-1.86 0-2.445 1.155-2.445 2.34v2.82h4.17l-.675 4.35H20.34v10.5A15 15 0 0 0 33 18.09c0-8.295-6.75-15.03-15-15.03z"
|
||||
fill="#fff"></path>
|
||||
</svg>
|
||||
</a>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="img-container">
|
||||
<div class="img-relative">
|
||||
{#each $navigation.elemente?.map((e) => e.image?.src) as imgSrc, i (i)}
|
||||
{#if imgSrc != undefined}
|
||||
<div>
|
||||
<img
|
||||
use:pushImages
|
||||
src="{`${apiBaseURL}navigation/${$navigation.id}/${imgSrc}?filter=${
|
||||
window.innerWidth > 500 ? 'xl' : 'm'
|
||||
}`}"
|
||||
alt="img"
|
||||
class="img img-menu"
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</ul>
|
||||
|
||||
<style lang="less" global>
|
||||
@import "../../assets/css/variables.less";
|
||||
@desktop: ~"only screen and (min-width: 1440px)";
|
||||
|
||||
@media @tablet {
|
||||
.socials {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
@media @mobile {
|
||||
nav {
|
||||
.hidden {
|
||||
visibility: hidden;
|
||||
}
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100vh;
|
||||
background-color: white;
|
||||
ul {
|
||||
.header {
|
||||
padding: 0px min(4.5vw, 100px);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
height: 105px;
|
||||
.button-three {
|
||||
--button-color: #333;
|
||||
overflow: hidden;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.button-three .hamburger {
|
||||
transition: translate 400ms, rotate 400ms;
|
||||
}
|
||||
|
||||
.button-three[aria-expanded="true"] .hamburger {
|
||||
translate: 1px -1px;
|
||||
rotate: 0.125turn;
|
||||
}
|
||||
|
||||
.button-three .line {
|
||||
transition: 1s;
|
||||
stroke-dasharray: 60 31 60 300;
|
||||
}
|
||||
|
||||
.button-three[aria-expanded="true"] .line {
|
||||
stroke-dasharray: 60 105 60 300;
|
||||
stroke-dashoffset: -90;
|
||||
}
|
||||
|
||||
.logo-container {
|
||||
height: 50px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
.logo-text {
|
||||
font-weight: bold;
|
||||
font-size: 1.2rem;
|
||||
padding-left: 10px;
|
||||
color: #333;
|
||||
}
|
||||
.img-logo-container {
|
||||
height: 100%;
|
||||
img {
|
||||
height: 100%;
|
||||
width: auto;
|
||||
object-fit: contain;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.img-container {
|
||||
position: absolute;
|
||||
bottom: 5px;
|
||||
width: 100%;
|
||||
left: 0px;
|
||||
height: calc((105vw / 16) * 9);
|
||||
padding: 0px min(4.5vw, 100px);
|
||||
.img-relative {
|
||||
position: relative;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
div {
|
||||
position: absolute;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
.show-img {
|
||||
visibility: visible !important;
|
||||
opacity: 1;
|
||||
}
|
||||
img {
|
||||
opacity: 0;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.menu-container {
|
||||
background-color: white;
|
||||
position: absolute;
|
||||
z-index: 2000;
|
||||
opacity: 0;
|
||||
top: 105px;
|
||||
height: calc(100vh - 105px);
|
||||
width: 100%;
|
||||
left: -100vw;
|
||||
overflow: scroll;
|
||||
.inner-container {
|
||||
position: relative;
|
||||
height: 100%;
|
||||
.menu {
|
||||
padding: 10px;
|
||||
border-radius: 10px;
|
||||
}
|
||||
.higher-absolute {
|
||||
position: absolute;
|
||||
z-index: 2000;
|
||||
width: 100%;
|
||||
left: 0px;
|
||||
padding: 0px min(4.5vw, 100px);
|
||||
background-color: white;
|
||||
border-radius: 0px 0px 10px 10px;
|
||||
}
|
||||
.socials {
|
||||
width: 100%;
|
||||
background-color: @link-font-color;
|
||||
height: 50px;
|
||||
border-radius: 20px;
|
||||
color: white;
|
||||
}
|
||||
.active {
|
||||
background-color: rgba(128, 128, 128, 0.153) !important;
|
||||
.submenu {
|
||||
visibility: visible;
|
||||
max-height: 250px;
|
||||
}
|
||||
}
|
||||
li {
|
||||
padding: 6px 0px;
|
||||
margin: 2px 0px;
|
||||
margin-left: 5px !important;
|
||||
button {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
text-align: start;
|
||||
}
|
||||
.menu-point {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
div {
|
||||
font-weight: bold;
|
||||
}
|
||||
.submenu {
|
||||
visibility: hidden;
|
||||
max-height: 0px;
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.show-menu {
|
||||
left: 0vw;
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
148
frontend/src/lib/components/pagebuilder/banner.svelte
Normal file
148
frontend/src/lib/components/pagebuilder/banner.svelte
Normal file
@@ -0,0 +1,148 @@
|
||||
<script lang="ts">
|
||||
import { onMount, onDestroy } from "svelte"
|
||||
import { banner } from "../../stores"
|
||||
|
||||
let isExpanded = false
|
||||
let currentIndex = 0
|
||||
let interval
|
||||
let img: HTMLImageElement
|
||||
|
||||
function toggleBanner() {
|
||||
isExpanded = !isExpanded
|
||||
img.src = img.src.includes("information") ? "media/close-circle.png" : "media/information.svg"
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
isExpanded = true
|
||||
interval = setInterval(() => {
|
||||
currentIndex = (currentIndex + 1) % $banner.length
|
||||
}, 7000)
|
||||
|
||||
return () => clearInterval(interval)
|
||||
})
|
||||
</script>
|
||||
|
||||
{#if $banner[0] != undefined}
|
||||
<div class="banner" class:expanded="{isExpanded}">
|
||||
<div class="banner-container">
|
||||
<div class="banner-text" class:visible="{isExpanded}">
|
||||
{$banner[currentIndex]}
|
||||
</div>
|
||||
<button class="toggle" on:click="{() => toggleBanner()}">
|
||||
<img src="media/close-circle.png" alt="info" bind:this="{img}" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<style lang="less">
|
||||
@import "../../assets/css/variables.less";
|
||||
* {
|
||||
transition: all 0.9s;
|
||||
}
|
||||
|
||||
@keyframes opacityText {
|
||||
0%,
|
||||
100% {
|
||||
opacity: 0.3;
|
||||
}
|
||||
20%,
|
||||
50%,
|
||||
80% {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
.banner-text {
|
||||
animation: opacityText;
|
||||
animation-duration: 7000ms;
|
||||
animation-iteration-count: infinite;
|
||||
visibility: hidden;
|
||||
}
|
||||
@media @mobile {
|
||||
.banner {
|
||||
width: 100vw;
|
||||
height: 0px;
|
||||
.banner-text {
|
||||
line-height: 90%;
|
||||
width: 100%;
|
||||
}
|
||||
&.expanded {
|
||||
height: 80px;
|
||||
}
|
||||
}
|
||||
|
||||
.toggle {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
background-color: @banner-color;
|
||||
transform: translate(-50%, -100%);
|
||||
border-radius: 10px;
|
||||
}
|
||||
}
|
||||
@media @tablet {
|
||||
.banner {
|
||||
height: 60px;
|
||||
width: 60px;
|
||||
.banner-text {
|
||||
width: 0px;
|
||||
}
|
||||
&.expanded {
|
||||
height: 60px;
|
||||
width: 80vw;
|
||||
}
|
||||
}
|
||||
|
||||
.toggle {
|
||||
position: relative;
|
||||
transform: translate(0%, 0%);
|
||||
left: 0px;
|
||||
border-radius: 0px;
|
||||
}
|
||||
}
|
||||
|
||||
.banner {
|
||||
position: fixed;
|
||||
border-radius: 0px 5px 0px 0px;
|
||||
z-index: 4000;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
background-color: @banner-color;
|
||||
cursor: pointer;
|
||||
transition: width 0.5s ease-in-out, height 0.5s ease-in-out;
|
||||
img {
|
||||
transform: scale(0.9);
|
||||
}
|
||||
}
|
||||
|
||||
.banner.expanded {
|
||||
}
|
||||
.banner-container {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
padding: 10px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
position: relative;
|
||||
color: white;
|
||||
.toggle {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.banner-text {
|
||||
color: white;
|
||||
transition: opacity 0.3s ease-in-out;
|
||||
pointer-events: none;
|
||||
opacity: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.banner-text.visible {
|
||||
flex: 1;
|
||||
padding: 10px;
|
||||
visibility: visible;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
160
frontend/src/lib/components/pagebuilder/form/form.svelte
Normal file
160
frontend/src/lib/components/pagebuilder/form/form.svelte
Normal file
@@ -0,0 +1,160 @@
|
||||
<script lang="ts">
|
||||
import FormLabelNumberBlock from "./formLabelNumberBlock.svelte"
|
||||
|
||||
export let formRow: FormRow
|
||||
export let index: number
|
||||
export let formValues
|
||||
function getSortedFields(column: FormColumn) {
|
||||
const fields = [
|
||||
...(column.text.length ? [{ text: column.text, type: "text", order: column.textfieldOrder ?? 3 }] : []),
|
||||
...(column.showTimes ? [{ type: "times", order: column.timesfieldOrder ?? 3, times: column.times }] : []),
|
||||
...(column.showDate
|
||||
? [{ type: "date", order: column.datefieldOrder ?? 3, datePlaceholder: column.datePlaceholder }]
|
||||
: []),
|
||||
]
|
||||
return fields.sort((a, b) => a.order - b.order)
|
||||
}
|
||||
//formRow.columns = formRow.columns.map((e) => getSortedFields(e))
|
||||
function removeInvalid(e) {
|
||||
let element = e.currentTarget
|
||||
element.classList.remove("invalid")
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="form-row">
|
||||
<div class="form-cols">
|
||||
{#each formRow.columns as column, columnIndex}
|
||||
<div class="form-column">
|
||||
<h3>{column?.title ?? ""}</h3>
|
||||
{#if column?.annotation}
|
||||
<p>{column?.annotation}</p>
|
||||
{/if}
|
||||
{#if column.showLabelNumber}
|
||||
<FormLabelNumberBlock column="{column}" formValues="{formValues}" rowIndex="{index}" />
|
||||
{/if}
|
||||
|
||||
{#if column?.showTimes}
|
||||
<label bind:this="{$formValues[`times_${column?.title ? column.title + '_' : ''}label`]}">
|
||||
<select
|
||||
id="time-select"
|
||||
bind:this="{$formValues[
|
||||
`times_${column?.title ?? 'Zeiten'}_${column.timesfieldOrder}_${index}_${
|
||||
column.emailNameTimes || columnIndex + 'invalidtimes'
|
||||
}`
|
||||
]}"
|
||||
required="{column?.timeNotRequired !== true}"
|
||||
on:change="{removeInvalid}"
|
||||
>
|
||||
<option value="" disabled selected>Bitte Uhrzeit wählen</option>
|
||||
{#each column?.times as time}
|
||||
<option value="{time?.timeFrom}-{time?.timeTo}">
|
||||
{time?.timeFrom} - {time?.timeTo}
|
||||
</option>
|
||||
{/each}
|
||||
</select>
|
||||
</label>
|
||||
{/if}
|
||||
|
||||
{#if column?.showSelect}
|
||||
<label bind:this="{$formValues[`select_${column?.title ? column.title + '_' : ''}label`]}">
|
||||
<select
|
||||
required="{column?.dateSelectNotRequired !== true}"
|
||||
bind:this="{$formValues[
|
||||
`select_${column?.title ?? 'Auswahl'}_${column.datefieldOrder}_${index}_${
|
||||
column?.emailNameTime || columnIndex + 'invalidtime'
|
||||
}`
|
||||
]}"
|
||||
on:change="{removeInvalid}"
|
||||
>
|
||||
<option value="" disabled selected>{column.selectTitle}</option>
|
||||
{#each column?.selectEntries as entry}
|
||||
<option value="{entry?.leftSide}-{entry?.rightSide}">
|
||||
{entry?.leftSide}-{entry?.rightSide}
|
||||
</option>
|
||||
{/each}
|
||||
</select>
|
||||
</label>
|
||||
{/if}
|
||||
|
||||
{#if column.showDate}
|
||||
<label bind:this="{$formValues[`date_${column?.title ? column.title + '_' : ''}label`]}">
|
||||
<input
|
||||
class="date"
|
||||
type="date"
|
||||
required="{column?.dateNotRequired !== true}"
|
||||
on:change="{removeInvalid}"
|
||||
bind:this="{$formValues[
|
||||
`date_${column?.title ?? 'Datum'}_${column.datefieldOrder}_${index}_${
|
||||
column?.emailNameDate || columnIndex + 'invaliddate'
|
||||
}`
|
||||
]}"
|
||||
/>
|
||||
</label>
|
||||
{/if}
|
||||
{#if column.showNumber}
|
||||
<label
|
||||
bind:this="{$formValues[
|
||||
`input_${column.title ? column.title + '_' : ''}${column?.numberPlaceholder}_label`
|
||||
]}"
|
||||
>
|
||||
<input
|
||||
type="number"
|
||||
step="any"
|
||||
required="{column?.numberNotRequired !== true}"
|
||||
on:change="{removeInvalid}"
|
||||
placeholder="{column?.numberPlaceholder}"
|
||||
bind:this="{$formValues[
|
||||
`input_${column.title ? column.title + '_' : ''}${column?.numberPlaceholder}_${
|
||||
column.numberfieldOrder
|
||||
}_${index}_${column?.emailNameNumber || columnIndex + 'invalidnumber'}`
|
||||
]}"
|
||||
/>
|
||||
</label>
|
||||
{/if}
|
||||
|
||||
{#each column.text as textField, textFieldIndex}
|
||||
{#if textField?.textArea}
|
||||
<label bind:this="{$formValues[`textarea_Nachricht_label`]}">
|
||||
<textarea
|
||||
placeholder="{textField?.textPlaceholder}"
|
||||
required="{textField?.notRequired !== true}"
|
||||
on:change="{removeInvalid}"
|
||||
bind:this="{$formValues[
|
||||
`textarea_Nachricht_${textField.textfieldOrder}_${index}_${
|
||||
textField.emailName || columnIndex + 'invalidtext' + textFieldIndex
|
||||
}`
|
||||
]}"></textarea>
|
||||
</label>
|
||||
{:else}
|
||||
<label
|
||||
bind:this="{$formValues[
|
||||
`input_${column.title ? column.title + '_' : ''}${textField?.textPlaceholder}_label`
|
||||
]}"
|
||||
>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="{textField?.textPlaceholder}"
|
||||
on:change="{removeInvalid}"
|
||||
required="{textField?.notRequired !== true}"
|
||||
bind:this="{$formValues[
|
||||
`${
|
||||
textField?.telValidation
|
||||
? 'Telefon'
|
||||
: textField?.emailValidation
|
||||
? 'Email'
|
||||
: 'input'
|
||||
}_${column.title ? column.title + '_' : ''}${textField?.textPlaceholder}_${
|
||||
textField.textfieldOrder
|
||||
}_${index}_${textField.emailName || columnIndex + 'invalidtext' + textFieldIndex}`
|
||||
]}"
|
||||
/>
|
||||
</label>
|
||||
{/if}
|
||||
{/each}
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style lang="less">
|
||||
</style>
|
||||
@@ -0,0 +1,63 @@
|
||||
<script lang="ts">
|
||||
export let column
|
||||
export let formValues
|
||||
export let rowIndex
|
||||
let blockContainer
|
||||
$formValues["blockGroups"] = new Set(column.labelNumber.map((e) => e.group))
|
||||
console.log($formValues["blockGroups"])
|
||||
</script>
|
||||
|
||||
<div class="blockContainer" bind:this="{blockContainer}">
|
||||
{#each column.labelNumber as outerblock, i}
|
||||
<div class="{`block`}" bind:this="{$formValues['blockGroups'][i]}">
|
||||
<h3>{outerblock.title}</h3>
|
||||
<div class="innterBlockContainer">
|
||||
{#each outerblock.block as innerBlock}
|
||||
<div class="innerBlock">
|
||||
<div class="label">{innerBlock.label}</div>
|
||||
<input
|
||||
placeholder="0"
|
||||
on:change="{(e) => {
|
||||
let element = e.currentTarget
|
||||
element.classList.remove('border-red')
|
||||
blockContainer.classList.remove('invalidBlocks')
|
||||
}}"
|
||||
type="number"
|
||||
name="{outerblock.group}"
|
||||
class="{`group-${outerblock.group}`}"
|
||||
bind:this="{$formValues[
|
||||
`numberLabel_${outerblock.title ?? ''}_${innerBlock.label}_${rowIndex}_${
|
||||
innerBlock.emailName
|
||||
}`
|
||||
]}"
|
||||
/>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
<style lang="less">
|
||||
.block {
|
||||
width: 100%;
|
||||
padding: 25px 0px;
|
||||
h3 {
|
||||
margin-bottom: 0px;
|
||||
}
|
||||
.innerBlock {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
.label {
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
}
|
||||
input {
|
||||
text-align: center;
|
||||
width: 80px;
|
||||
font-size: 1rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
311
frontend/src/lib/components/pagebuilder/form/mobileForm.svelte
Normal file
311
frontend/src/lib/components/pagebuilder/form/mobileForm.svelte
Normal file
@@ -0,0 +1,311 @@
|
||||
<script lang="ts">
|
||||
import FormLabelNumberBlock from "./formLabelNumberBlock.svelte"
|
||||
|
||||
export let formRow: FormRow
|
||||
export let formValues
|
||||
export let index
|
||||
function removeInvalid(e) {
|
||||
let element = e.currentTarget
|
||||
element.classList.remove("invalid")
|
||||
}
|
||||
|
||||
function getPosition(column, pos, i = 0) {
|
||||
let position = 0
|
||||
if (pos == 0) return
|
||||
if (column.showLabelNumber) position++
|
||||
if (pos == 1) return position
|
||||
if (column.showTimes) position++
|
||||
if (pos == 2) return position
|
||||
|
||||
if (column.showSelect) position++
|
||||
if (pos == 3) return position
|
||||
if (column.showDate) position++
|
||||
if (pos == 4) return position
|
||||
if (column.showNumber) position++
|
||||
return position + i
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="form-row">
|
||||
<div class="form-cols mobile-fields">
|
||||
{#each formRow.columns as column, columnIndex}
|
||||
<h3>{column?.title ?? ""}</h3>
|
||||
{#if column?.annotation}
|
||||
<p>{column?.annotation}</p>
|
||||
{/if}
|
||||
{#if column.showLabelNumber}
|
||||
<div class="{`column-${columnIndex} position-${getPosition(column, 0)}`}">
|
||||
<FormLabelNumberBlock column="{column}" formValues="{formValues}" rowIndex="{index}" />
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if column?.showTimes}
|
||||
<div class="column-{columnIndex} position-{getPosition(column, 1)}">
|
||||
<label bind:this="{$formValues[`times_${column?.title ? column.title + '_' : ''}label`]}">
|
||||
<select
|
||||
id="time-select"
|
||||
bind:this="{$formValues[
|
||||
`times_${column?.title ?? 'Zeiten'}_${column.timesfieldOrder}_${index}_${
|
||||
column.emailNameTimes || columnIndex + 'invalidtimes'
|
||||
}`
|
||||
]}"
|
||||
required="{column?.timeNotRequired !== true}"
|
||||
on:change="{removeInvalid}"
|
||||
>
|
||||
<option value="" disabled selected>Bitte Uhrzeit wählen</option>
|
||||
{#each column?.times as time}
|
||||
<option value="{time?.timeFrom}-{time?.timeTo}">
|
||||
{time?.timeFrom} - {time?.timeTo}
|
||||
</option>
|
||||
{/each}
|
||||
</select>
|
||||
</label>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if column?.showSelect}
|
||||
<div class="column-{columnIndex} position-{getPosition(column, 2)}">
|
||||
<label bind:this="{$formValues[`select_${column?.title ? column.title + '_' : ''}label`]}">
|
||||
<select
|
||||
required="{column?.dateNotRequired !== true}"
|
||||
bind:this="{$formValues[
|
||||
`select_${column?.title ?? 'Auswahl'}_${column.datefieldOrder}_${index}_${
|
||||
column?.emailNameTime || columnIndex + 'invalidtime'
|
||||
}`
|
||||
]}"
|
||||
on:change="{removeInvalid}"
|
||||
>
|
||||
<option value="" disabled selected>{column.selectTitle}</option>
|
||||
{#each column?.selectEntries as entry}
|
||||
<option value="{entry?.leftSide}-{entry?.rightSide}">
|
||||
{entry?.leftSide}-{entry?.rightSide}
|
||||
</option>
|
||||
{/each}
|
||||
</select>
|
||||
</label>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if column.showDate}
|
||||
<div class="column-{columnIndex} position-{getPosition(column, 3)}">
|
||||
<label bind:this="{$formValues[`date_${column?.title ? column.title + '_' : ''}label`]}">
|
||||
<input
|
||||
class="date"
|
||||
type="date"
|
||||
required="{column?.dateNotRequired !== true}"
|
||||
on:change="{removeInvalid}"
|
||||
bind:this="{$formValues[
|
||||
`date_${column?.title ?? 'Datum'}_${column.datefieldOrder}_${index}_${
|
||||
column?.emailNameDate || columnIndex + 'invaliddate'
|
||||
}`
|
||||
]}"
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
{/if}
|
||||
{#if column.showNumber}
|
||||
<div class=" column-{columnIndex} position-{getPosition(column, 4)}">
|
||||
<label
|
||||
bind:this="{$formValues[
|
||||
`input_${column.title ? column.title + '_' : ''}${column?.numberPlaceholder}_label`
|
||||
]}"
|
||||
>
|
||||
<input
|
||||
type="number"
|
||||
step="any"
|
||||
required="{column?.numberNotRequired !== true}"
|
||||
on:change="{removeInvalid}"
|
||||
placeholder="{column?.numberPlaceholder}"
|
||||
bind:this="{$formValues[
|
||||
`input_${column.title ? column.title + '_' : ''}${column?.numberPlaceholder}_${
|
||||
column.numberfieldOrder
|
||||
}_${index}_${column?.emailNameNumber || columnIndex + 'invalidnumber'}`
|
||||
]}"
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#each column.text as textField, textFieldIndex}
|
||||
<div class="column-{columnIndex} position-{getPosition(column, 5 + textFieldIndex, textFieldIndex)}">
|
||||
{#if textField?.textArea}
|
||||
<label bind:this="{$formValues[`textarea_Nachricht_label`]}">
|
||||
<textarea
|
||||
placeholder="{textField?.textPlaceholder}"
|
||||
required="{textField?.notRequired !== true}"
|
||||
on:change="{removeInvalid}"
|
||||
bind:this="{$formValues[
|
||||
`textarea_Nachricht_${textField.textfieldOrder}_${index}_${
|
||||
textField.emailName || columnIndex + 'invalidtext' + textFieldIndex
|
||||
}`
|
||||
]}"></textarea>
|
||||
</label>
|
||||
{:else}
|
||||
<label
|
||||
bind:this="{$formValues[
|
||||
`input_${column.title ? column.title + '_' : ''}${textField?.textPlaceholder}_label`
|
||||
]}"
|
||||
>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="{textField?.textPlaceholder}"
|
||||
on:change="{removeInvalid}"
|
||||
required="{textField?.notRequired !== true}"
|
||||
bind:this="{$formValues[
|
||||
`${
|
||||
textField?.telValidation
|
||||
? 'Telefon'
|
||||
: textField?.emailValidation
|
||||
? 'Email'
|
||||
: 'input'
|
||||
}_${column.title ? column.title + '_' : ''}${textField?.textPlaceholder}_${
|
||||
textField.textfieldOrder
|
||||
}_${index}_${textField.emailName || columnIndex + 'invalidtext' + textFieldIndex}`
|
||||
]}"
|
||||
/>
|
||||
</label>
|
||||
{/if}
|
||||
</div>
|
||||
{/each}
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style lang="less">
|
||||
.mobile-fields {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.form-field {
|
||||
order: 1;
|
||||
}
|
||||
|
||||
/* Order for first column */
|
||||
.column-0.position-0 {
|
||||
order: 1;
|
||||
}
|
||||
.column-0.position-1 {
|
||||
order: 5;
|
||||
}
|
||||
.column-0.position-2 {
|
||||
order: 9;
|
||||
}
|
||||
.column-0.position-3 {
|
||||
order: 13;
|
||||
}
|
||||
.column-0.position-4 {
|
||||
order: 17;
|
||||
}
|
||||
.column-0.position-5 {
|
||||
order: 21;
|
||||
}
|
||||
.column-0.position-6 {
|
||||
order: 25;
|
||||
}
|
||||
.column-0.position-7 {
|
||||
order: 29;
|
||||
}
|
||||
.column-0.position-8 {
|
||||
order: 33;
|
||||
}
|
||||
.column-0.position-9 {
|
||||
order: 37;
|
||||
}
|
||||
|
||||
/* Order for second column */
|
||||
.column-1.position-0 {
|
||||
order: 2;
|
||||
}
|
||||
.column-1.position-1 {
|
||||
order: 6;
|
||||
}
|
||||
.column-1.position-2 {
|
||||
order: 10;
|
||||
}
|
||||
.column-1.position-3 {
|
||||
order: 14;
|
||||
}
|
||||
.column-1.position-4 {
|
||||
order: 18;
|
||||
}
|
||||
.column-1.position-5 {
|
||||
order: 22;
|
||||
}
|
||||
.column-1.position-6 {
|
||||
order: 26;
|
||||
}
|
||||
.column-1.position-7 {
|
||||
order: 30;
|
||||
}
|
||||
.column-1.position-8 {
|
||||
order: 34;
|
||||
}
|
||||
.column-1.position-9 {
|
||||
order: 38;
|
||||
}
|
||||
|
||||
/* Order for third column */
|
||||
.column-2.position-0 {
|
||||
order: 3;
|
||||
}
|
||||
.column-2.position-1 {
|
||||
order: 7;
|
||||
}
|
||||
.column-2.position-2 {
|
||||
order: 11;
|
||||
}
|
||||
.column-2.position-3 {
|
||||
order: 15;
|
||||
}
|
||||
.column-2.position-4 {
|
||||
order: 19;
|
||||
}
|
||||
.column-2.position-5 {
|
||||
order: 23;
|
||||
}
|
||||
.column-2.position-6 {
|
||||
order: 27;
|
||||
}
|
||||
.column-2.position-7 {
|
||||
order: 31;
|
||||
}
|
||||
.column-2.position-8 {
|
||||
order: 35;
|
||||
}
|
||||
.column-2.position-9 {
|
||||
order: 39;
|
||||
}
|
||||
|
||||
/* Order for fourth column */
|
||||
.column-3.position-0 {
|
||||
order: 4;
|
||||
}
|
||||
.column-3.position-1 {
|
||||
order: 8;
|
||||
}
|
||||
.column-3.position-2 {
|
||||
order: 12;
|
||||
}
|
||||
.column-3.position-3 {
|
||||
order: 16;
|
||||
}
|
||||
.column-3.position-4 {
|
||||
order: 20;
|
||||
}
|
||||
.column-3.position-5 {
|
||||
order: 24;
|
||||
}
|
||||
.column-3.position-6 {
|
||||
order: 28;
|
||||
}
|
||||
.column-3.position-7 {
|
||||
order: 32;
|
||||
}
|
||||
.column-3.position-8 {
|
||||
order: 36;
|
||||
}
|
||||
.column-3.position-9 {
|
||||
order: 40;
|
||||
}
|
||||
</style>
|
||||
93
frontend/src/lib/components/pagebuilder/iconBoard.svelte
Normal file
93
frontend/src/lib/components/pagebuilder/iconBoard.svelte
Normal file
@@ -0,0 +1,93 @@
|
||||
<script lang="ts">
|
||||
import { navigate } from "svelte-routing"
|
||||
import { fly } from "svelte/transition"
|
||||
export let col: Column
|
||||
export let siteId
|
||||
import { apiBaseURL } from "../../../config"
|
||||
import inView from "../../functions/observer"
|
||||
let visible = false
|
||||
let node
|
||||
</script>
|
||||
|
||||
<main>
|
||||
<div
|
||||
class="wrapper"
|
||||
bind:this="{node}"
|
||||
use:inView="{{ threshold: 0 }}"
|
||||
on:enter="{() => {
|
||||
visible = true
|
||||
}}"
|
||||
>
|
||||
{#key visible}
|
||||
{#each col.iconBoard as icon, i (i)}
|
||||
<div
|
||||
class="img-container"
|
||||
id="{'img-container' + i}"
|
||||
in:fly="{{ duration: 1000, delay: i * 150, y: 130, opacity: 0 }}"
|
||||
>
|
||||
<img alt="icon" src="{`${apiBaseURL}content/${siteId}/${icon.icon?.src}`}" class="icon" />
|
||||
|
||||
<div class="subText">{icon?.subText}</div>
|
||||
</div>
|
||||
{/each}
|
||||
{/key}
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<style lang="less">
|
||||
@import "../../assets/css/variables.less";
|
||||
main {
|
||||
display: flex;
|
||||
max-width: @body-small-maxwidth;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
width: 100%;
|
||||
padding: 15px;
|
||||
.wrapper {
|
||||
width: 100%;
|
||||
min-height: 400px;
|
||||
margin-top: 75px;
|
||||
display: flex;
|
||||
justify-content: start;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
@media @mobile {
|
||||
margin-top: 10px;
|
||||
}
|
||||
@media @tablet {
|
||||
margin-top: 40px;
|
||||
}
|
||||
|
||||
.img-container {
|
||||
display: flex;
|
||||
margin: 15px 0px;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
min-width: 25%;
|
||||
.subText {
|
||||
text-align: center;
|
||||
color: @heading-font-color;
|
||||
}
|
||||
@media @mobile {
|
||||
width: 50%;
|
||||
}
|
||||
@media @tablet {
|
||||
width: auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media @tablet {
|
||||
main {
|
||||
margin-top: 300px !important;
|
||||
}
|
||||
.background-container {
|
||||
margin-top: -180px !important;
|
||||
}
|
||||
.title {
|
||||
font-size: 3rem !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
104
frontend/src/lib/components/pagebuilder/image.svelte
Normal file
104
frontend/src/lib/components/pagebuilder/image.svelte
Normal file
@@ -0,0 +1,104 @@
|
||||
<script lang="ts">
|
||||
import { onMount } from "svelte"
|
||||
import { register } from "swiper/element/bundle"
|
||||
import Pagebuilder from "./Pagebuilder.svelte"
|
||||
import { apiBaseURL } from "../../../config"
|
||||
|
||||
export let siteId
|
||||
export let siteImages
|
||||
|
||||
register(false)
|
||||
let swiper
|
||||
|
||||
onMount(async () => {
|
||||
if (swiper !== undefined) {
|
||||
const response = await fetch("/dist/index.css")
|
||||
const cssText = await response.text()
|
||||
|
||||
const params = {
|
||||
injectStyles: [cssText],
|
||||
}
|
||||
|
||||
Object.assign(swiper, params)
|
||||
swiper.initialize()
|
||||
}
|
||||
})
|
||||
let image = siteImages[0]
|
||||
</script>
|
||||
|
||||
{#if siteImages.length > 1}
|
||||
<div class="flex">
|
||||
<swiper-container
|
||||
bind:this="{swiper}"
|
||||
slides-per-view="1"
|
||||
loop="{true}"
|
||||
direction="horizontal"
|
||||
effect="slide"
|
||||
autoplay-delay="2500"
|
||||
mousewheel="{true}"
|
||||
navigation="{true}"
|
||||
init="{false}"
|
||||
speed="600"
|
||||
class="relative"
|
||||
>
|
||||
{#each siteImages as image, i (i)}
|
||||
<swiper-slide class="relative">
|
||||
<div class="image-container">
|
||||
<img
|
||||
src="{`${apiBaseURL}content/${siteId}/${image.image?.src}?filter=${
|
||||
window.innerWidth > 500 ? 'xl' : 'm'
|
||||
}`}"
|
||||
alt="Bild"
|
||||
/>
|
||||
</div>
|
||||
</swiper-slide>
|
||||
{/each}
|
||||
</swiper-container>
|
||||
</div>
|
||||
{:else if image}
|
||||
<div class="image-container single flex">
|
||||
<img
|
||||
src="{`${apiBaseURL}content/${siteId}/${image.image?.src}?filter=${window.innerWidth > 500 ? 'xl' : 'm'}`}"
|
||||
alt="Bild"
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<style lang="less" global>
|
||||
@import "swiper/swiper.less";
|
||||
@import "swiper/modules/effect-fade/effect-fade.less";
|
||||
@import "swiper/modules/navigation/navigation.less";
|
||||
@import "swiper/modules/pagination/pagination.less";
|
||||
@import "../../assets/css/variables.less";
|
||||
@import "../../assets/css/swiperStyles.less";
|
||||
swiper-container {
|
||||
height: auto;
|
||||
width: 100%;
|
||||
}
|
||||
swiper-slide {
|
||||
width: 100% !important;
|
||||
}
|
||||
.flex {
|
||||
flex: 2 !important;
|
||||
}
|
||||
|
||||
.image-container {
|
||||
max-width: 100%;
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
max-width: @body-maxwidth;
|
||||
&:hover img {
|
||||
transform: scale(1.05);
|
||||
transform-origin: center;
|
||||
}
|
||||
img {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
object-fit: cover;
|
||||
}
|
||||
}
|
||||
|
||||
.single {
|
||||
max-width: @body-small-maxwidth;
|
||||
}
|
||||
</style>
|
||||
90
frontend/src/lib/components/pagebuilder/infoBoard.svelte
Normal file
90
frontend/src/lib/components/pagebuilder/infoBoard.svelte
Normal file
@@ -0,0 +1,90 @@
|
||||
<script lang="ts">
|
||||
import { navigate } from "svelte-routing"
|
||||
import { fly } from "svelte/transition"
|
||||
export let col: Column
|
||||
export let siteId
|
||||
export let i
|
||||
import { apiBaseURL } from "../../../config"
|
||||
import inView from "../../functions/observer"
|
||||
let visible
|
||||
let node
|
||||
</script>
|
||||
|
||||
{#key visible}
|
||||
<main
|
||||
bind:this="{node}"
|
||||
use:inView="{{ threshold: 0 }}"
|
||||
on:enter="{() => {
|
||||
visible = true
|
||||
}}"
|
||||
in:fly="{{ duration: 600, delay: 100 + 100 * (i + 1), x: -300 - i * 200, opacity: 0 }}"
|
||||
>
|
||||
<div class="img-container">
|
||||
<img
|
||||
src="{`${apiBaseURL}content/${siteId}/${col.image?.src}?filter=${
|
||||
window.innerWidth > 500 ? 'xl' : 'm'
|
||||
}`}"
|
||||
alt="img"
|
||||
/>
|
||||
</div>
|
||||
<div class="title-container">
|
||||
{@html col?.title}
|
||||
</div>
|
||||
<div class="description-container">
|
||||
{@html col?.text}
|
||||
</div>
|
||||
<div class="links">
|
||||
{#if col?.links}
|
||||
{#each col?.links as link}
|
||||
<button on:click="{() => navigate(link?.site)}">
|
||||
<img src="media/arrow-right.svg" alt="arrow" />{link?.name}
|
||||
</button>
|
||||
{/each}
|
||||
{/if}
|
||||
</div>
|
||||
</main>
|
||||
{/key}
|
||||
|
||||
<style lang="less">
|
||||
@import "../../assets/css/variables.less";
|
||||
main {
|
||||
flex: 1;
|
||||
max-width: @body-small-maxwidth;
|
||||
.img-container {
|
||||
width: 100%;
|
||||
height: 270px;
|
||||
@media @tablet {
|
||||
height: 65vw;
|
||||
}
|
||||
@media @desktop {
|
||||
height: 270px;
|
||||
}
|
||||
img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
padding: 1rem 0px;
|
||||
}
|
||||
}
|
||||
.title-container {
|
||||
font-weight: 700;
|
||||
padding-bottom: 1.2rem;
|
||||
}
|
||||
.description-container {
|
||||
}
|
||||
.links {
|
||||
margin-top: 25px;
|
||||
img {
|
||||
transform: scale(1.4);
|
||||
margin: 0.5rem 0.9rem 0.5rem 0.4rem;
|
||||
}
|
||||
button {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
color: @link-font-color;
|
||||
font-weight: 600;
|
||||
margin: 10px 0px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
57
frontend/src/lib/components/pagebuilder/mainPicture.svelte
Normal file
57
frontend/src/lib/components/pagebuilder/mainPicture.svelte
Normal file
@@ -0,0 +1,57 @@
|
||||
<script lang="ts">
|
||||
import { fly } from "svelte/transition"
|
||||
export let col: Column
|
||||
export let siteId
|
||||
import { apiBaseURL } from "../../../config"
|
||||
</script>
|
||||
|
||||
<div class="mainPicture">
|
||||
<div class="imgContainer">
|
||||
<img
|
||||
src="{`${apiBaseURL}content/${siteId}/${col.mainPicture?.src}?filter=${
|
||||
window.innerWidth > 500 ? 'xl' : 'm'
|
||||
}`}"
|
||||
alt="img"
|
||||
/>
|
||||
</div>
|
||||
<div class="inscription" in:fly="{{ duration: 1000, delay: 500, y: 50, opacity: 0 }}">
|
||||
{col.inscription}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style lang="less">
|
||||
@import "../../assets/css/variables.less";
|
||||
.mainPicture {
|
||||
max-width: @body-maxwidth;
|
||||
position: relative;
|
||||
height: 100%;
|
||||
line-height: normal;
|
||||
|
||||
flex-grow: 2;
|
||||
width: 100%;
|
||||
.imgContainer {
|
||||
width: 100%;
|
||||
img {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
object-fit: contain;
|
||||
}
|
||||
}
|
||||
.inscription {
|
||||
position: absolute;
|
||||
bottom: 35px;
|
||||
left: 35px;
|
||||
margin-right: 35px;
|
||||
font-size: 1.7rem;
|
||||
font-weight: 700;
|
||||
color: white;
|
||||
}
|
||||
@media @tablet {
|
||||
.inscription {
|
||||
bottom: 35px;
|
||||
left: 35px;
|
||||
font-size: 2.7rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
619
frontend/src/lib/components/pagebuilder/pagebuilder.svelte
Normal file
619
frontend/src/lib/components/pagebuilder/pagebuilder.svelte
Normal file
@@ -0,0 +1,619 @@
|
||||
<script lang="ts">
|
||||
export let row: Row
|
||||
export let rows: Row[]
|
||||
import { apiBaseURL } from "../../../config"
|
||||
import IconBoard from "./iconBoard.svelte"
|
||||
import Image from "./image.svelte"
|
||||
import InfoBoard from "./infoBoard.svelte"
|
||||
import MainPicture from "./mainPicture.svelte"
|
||||
import TextContent from "./textContent.svelte"
|
||||
import Table from "./table.svelte"
|
||||
import Form from "./form/form.svelte"
|
||||
import MobileForm from "./form/mobileForm.svelte"
|
||||
import { writable } from "svelte/store"
|
||||
import { validateFields } from "../../functions/validateFields"
|
||||
import { sendForm } from "../../functions/sendForm"
|
||||
import VideoSwitch from "../videoSwitch.svelte"
|
||||
import GoogleMaps from "../googleMaps.svelte"
|
||||
import Video from "./video.svelte"
|
||||
import { navigate } from "svelte-routing"
|
||||
import { onMount } from "svelte"
|
||||
export let siteId
|
||||
export let rowNr
|
||||
let formSend = false
|
||||
|
||||
let formValues = writable({})
|
||||
function getRowClass(row) {
|
||||
if (row.maxWidth || row.column.some((col) => col.iconBackgroundImage)) return "max-width"
|
||||
return row.column.some((col) => col.contentType === "mainPicture" || col.contentType == "video")
|
||||
? "max-width"
|
||||
: "small-max-width"
|
||||
}
|
||||
|
||||
function submitForm(e) {
|
||||
const values = Object.entries($formValues).map((entry) => {
|
||||
return [
|
||||
entry[0],
|
||||
!entry[0].includes("numberLabel")
|
||||
? [entry[1].checked || entry[1].value, entry[1].required]
|
||||
: [entry[1].value, entry[1], entry[1].getAttribute("name"), entry[1].required],
|
||||
]
|
||||
})
|
||||
|
||||
const fields = values.filter((entry) => !entry[0].includes("label"))
|
||||
console.log(fields)
|
||||
const validation = validateFields([...fields])
|
||||
if (validation.length) {
|
||||
validation.forEach((error) => {
|
||||
if (error[0].includes("block")) {
|
||||
error[1]()
|
||||
} else {
|
||||
$formValues[error[1]].classList.add("invalid")
|
||||
const label = $formValues[`${error[1]}_label`]
|
||||
const errorElement = document.createElement("div")
|
||||
errorElement.className = "error-message"
|
||||
errorElement.textContent = error[0]
|
||||
label?.appendChild(errorElement)
|
||||
}
|
||||
})
|
||||
} else {
|
||||
const formObj = {}
|
||||
fields.forEach((entry) => {
|
||||
if (Array.isArray(entry[1]) && entry[1].length == 4) {
|
||||
if (entry[1][0]) formObj[entry[0]] = entry[1]
|
||||
} else {
|
||||
if (!entry[1][0] && !entry[1][1]) return
|
||||
formObj[entry[0]] = entry[1][0]
|
||||
}
|
||||
})
|
||||
console.log(fields, formObj)
|
||||
let form
|
||||
row.column.forEach((col) => {
|
||||
if (col.contentType == "form") form = col
|
||||
})
|
||||
formObj["formRows"] = form.formRows.map((r) => r.rowName)
|
||||
formObj["formTitle"] = form.formEmailTitle
|
||||
formSend = true
|
||||
const hny = document.getElementById("hny")
|
||||
formObj["honey"] = hny.checked
|
||||
sendForm(formObj)
|
||||
}
|
||||
}
|
||||
let videoElement
|
||||
|
||||
onMount(() => {
|
||||
if (videoElement) {
|
||||
var source = document.createElement("source")
|
||||
if (window.innerWidth <= 767) {
|
||||
source.src = "media/wasser_mobile.webm"
|
||||
} else {
|
||||
source.src = "media/wasser_desktop.webm"
|
||||
}
|
||||
videoElement.appendChild(source)
|
||||
videoElement.load()
|
||||
}
|
||||
|
||||
const handleResize = () => {
|
||||
innerWidth = window.innerWidth
|
||||
if (
|
||||
(window.innerWidth >= 768 && innerWidthStore < 768) ||
|
||||
(window.innerWidth < 768 && innerWidthStore >= 768)
|
||||
) {
|
||||
innerWidthStore = window.innerWidth
|
||||
window.location.reload()
|
||||
}
|
||||
}
|
||||
|
||||
window.addEventListener("resize", handleResize)
|
||||
|
||||
// Cleanup function
|
||||
return () => {
|
||||
window.removeEventListener("resize", handleResize)
|
||||
}
|
||||
})
|
||||
|
||||
let innerWidth = window.innerWidth
|
||||
let innerWidthStore = window.innerWidth
|
||||
</script>
|
||||
|
||||
<main class="row" name="{row.title}" class:max-width="{getRowClass(row) === 'max-width'}" id="{`RowNr` + rowNr}">
|
||||
{#if row?.iconBackgroundImage}
|
||||
<div class="virtual-container">
|
||||
<div class="wavebackground-container">
|
||||
{#if row?.iconBackgroundTitle} <div class="title">Highlights des Sees</div>{/if}
|
||||
<video class="video-background" autoplay loop muted playsinline bind:this="{videoElement}"> </video>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
{#if row.iconBackgroundImage}
|
||||
<div class="wave-placeholder"></div>
|
||||
{/if}
|
||||
{#if row.title || row.iconBackgroundImage}
|
||||
<h3 class="row-title" class:no-margin="{rowNr == 0 || row.iconBackgroundImage}">{row?.title || ""}</h3>
|
||||
{/if}
|
||||
<div class:no-gap="{row.noGap}">
|
||||
{#each row.column as col, i}
|
||||
{#if col.contentType == "mainPicture"}
|
||||
<MainPicture col="{col}" siteId="{siteId}" />
|
||||
{:else if col.contentType == "text"}
|
||||
<TextContent rows="{rows}" col="{col}" />
|
||||
{:else if col.contentType == "infoBoard"}
|
||||
<InfoBoard siteId="{siteId}" col="{col}" i="{i}" />
|
||||
{:else if col.contentType == "iconBoard"}
|
||||
<IconBoard col="{col}" siteId="{siteId}" i="{i}" />
|
||||
{:else if col.contentType == "image"}
|
||||
<Image siteImages="{col.imageSlider}" siteId="{siteId}" />
|
||||
{:else if col.contentType == "table"}
|
||||
<Table col="{col}" rows="{rows}" />
|
||||
{:else if col.contentType == "videos"}
|
||||
<VideoSwitch col="{col}" siteId="{siteId}" />
|
||||
{:else if col.contentType == "video"}
|
||||
<Video col="{col}" siteId="{siteId}" />
|
||||
{:else if col.contentType == "googleMaps"}{#if col.showGoogleMaps}
|
||||
<GoogleMaps />
|
||||
{/if}
|
||||
{:else if col.contentType == "form"}
|
||||
{#if formSend}
|
||||
<div class="success-message">
|
||||
<h1>Formular erfolgreich gesendet!</h1>
|
||||
<p>Vielen Dank für Ihre Anfrage. Wir werden uns in Kürze bei Ihnen melden.</p>
|
||||
</div>
|
||||
{:else}
|
||||
<form
|
||||
class="form-rows"
|
||||
on:submit="{(e) => {
|
||||
e.preventDefault()
|
||||
submitForm(e)
|
||||
}}"
|
||||
>
|
||||
{#each col.formRows as formRow, i}
|
||||
{#if innerWidth < 768}
|
||||
<MobileForm formRow="{formRow}" formValues="{formValues}" index="{i}" />
|
||||
{:else}
|
||||
<Form formRow="{formRow}" formValues="{formValues}" index="{i}" />
|
||||
{/if}
|
||||
{/each}
|
||||
<div class="row additional">
|
||||
<div class="data-protection">
|
||||
<label bind:this="{$formValues[`agreement_label`]}">
|
||||
<input
|
||||
required="{true}"
|
||||
class="checkit"
|
||||
type="checkbox"
|
||||
on:change="{(e) => {
|
||||
let element = e.currentTarget
|
||||
element.classList.remove('invalid')
|
||||
}}"
|
||||
bind:this="{$formValues['agreement']}"
|
||||
/>
|
||||
<span class="checkit-span"></span>
|
||||
</label>
|
||||
|
||||
<div class="datasec">
|
||||
<button on:click|preventDefault="{() => navigate('/datenschutz')}" class="link">
|
||||
Datenschutz
|
||||
</button>
|
||||
akzeptieren
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<input
|
||||
type="checkbox"
|
||||
name="contact_me_by_fax_only"
|
||||
id="hny"
|
||||
value="1"
|
||||
style="display:none !important"
|
||||
tabindex="-1"
|
||||
autocomplete="off"
|
||||
/>
|
||||
<button class="submit-request" type="submit">Anfrage absenden</button>
|
||||
</div>
|
||||
</form>
|
||||
{/if}
|
||||
{/if}
|
||||
{/each}
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<style lang="less" global>
|
||||
@import "../../assets/css/variables.less";
|
||||
.wave-placeholder {
|
||||
height: 10px;
|
||||
}
|
||||
|
||||
input,
|
||||
select,
|
||||
textarea,
|
||||
.data-protection {
|
||||
margin: 5px 0px;
|
||||
box-shadow: 0 0 25px 10px rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
.virtual-container {
|
||||
display: flex;
|
||||
padding-top: 90px;
|
||||
max-width: @body-small-maxwidth;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
width: 100%;
|
||||
.wavebackground-container {
|
||||
position: absolute;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
width: 100vw;
|
||||
left: 0px;
|
||||
height: 450px;
|
||||
z-index: -1;
|
||||
.video-background {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
-webkit-mask-image: url("../../../../media/welle.svg");
|
||||
mask-image: url("../../../../media/welle.svg");
|
||||
-webkit-mask-size: 30px;
|
||||
mask-size: 30px;
|
||||
-webkit-mask-repeat: repeat-x;
|
||||
mask-repeat: repeat-x;
|
||||
z-index: -1;
|
||||
}
|
||||
@media @mobile {
|
||||
.title {
|
||||
margin-top: 40px;
|
||||
}
|
||||
}
|
||||
@media @tablet {
|
||||
.title {
|
||||
margin-top: 75px;
|
||||
}
|
||||
}
|
||||
.title {
|
||||
font-size: 2rem;
|
||||
color: white;
|
||||
font-weight: bold;
|
||||
}
|
||||
&::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: linear-gradient(to top, rgb(255, 255, 255), rgba(255, 255, 255, 0.144));
|
||||
z-index: 10;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.success-message {
|
||||
h1 {
|
||||
color: @banner-color;
|
||||
font-size: 36px;
|
||||
margin-top: 50px;
|
||||
}
|
||||
|
||||
p {
|
||||
font-size: 20px;
|
||||
margin-top: 20px;
|
||||
}
|
||||
}
|
||||
.invalidBlocks {
|
||||
border: 2px solid rgb(255, 0, 0) !important;
|
||||
position: relative;
|
||||
&::after {
|
||||
font-size: 0.9rem !important;
|
||||
color: red !important;
|
||||
position: absolute;
|
||||
bottom: 2px;
|
||||
content: "Bitte wähle entweder eine Kartenanzahl oder einen Wunschbetrag aus.";
|
||||
}
|
||||
}
|
||||
|
||||
.border-red {
|
||||
border-color: red !important;
|
||||
}
|
||||
|
||||
li {
|
||||
}
|
||||
|
||||
.row-title {
|
||||
font-size: 1.6rem;
|
||||
font-weight: bold;
|
||||
line-height: normal;
|
||||
width: 100%;
|
||||
color: @heading-font-color;
|
||||
@media @mobile {
|
||||
margin: 3.2rem 1.5vw 1.6rem;
|
||||
}
|
||||
|
||||
@media @desktop {
|
||||
margin: 6rem 0vw 1.6rem 0vw;
|
||||
}
|
||||
}
|
||||
.no-margin {
|
||||
margin-top: 15px !important;
|
||||
}
|
||||
|
||||
.invalid {
|
||||
border-bottom: 2px solid red !important;
|
||||
}
|
||||
.error-message {
|
||||
font-size: 0.9rem !important;
|
||||
color: red !important;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.row {
|
||||
width: 100%;
|
||||
padding: 0px 0px 15px 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
max-width: @body-small-maxwidth;
|
||||
|
||||
input[type="number"]::-webkit-inner-spin-button,
|
||||
input[type="number"]::-webkit-outer-spin-button {
|
||||
-webkit-appearance: none;
|
||||
margin: 0;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
input[type="number"] {
|
||||
-moz-appearance: textfield;
|
||||
}
|
||||
|
||||
ul {
|
||||
list-style-type: none; /* Remove default bullet points */
|
||||
padding: 0;
|
||||
|
||||
li {
|
||||
padding-left: 1.5em; /* Add padding to the left of the list items */
|
||||
position: relative;
|
||||
}
|
||||
|
||||
li::before {
|
||||
content: ""; /* Add an empty content */
|
||||
background-image: url("../../../../media/arrow-right.svg"); /* Replace this with the path to your SVG file */
|
||||
background-size: contain;
|
||||
background-repeat: no-repeat;
|
||||
position: absolute;
|
||||
left: 0; /* Position the SVG at the beginning of the list item */
|
||||
width: 1em; /* Adjust the width of the SVG */
|
||||
height: 1em; /* Adjust the height of the SVG */
|
||||
top: 0.2em; /* Adjust the vertical alignment of the SVG */
|
||||
}
|
||||
}
|
||||
& > div {
|
||||
width: 100%;
|
||||
}
|
||||
& > h3 {
|
||||
width: 100%;
|
||||
}
|
||||
.checkit {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.checkit-span {
|
||||
height: 20px;
|
||||
width: 20px;
|
||||
border: 1px solid grey;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
[type="checkbox"]:checked + span:before {
|
||||
content: "\2714";
|
||||
position: absolute;
|
||||
transform-origin: bottom;
|
||||
}
|
||||
&.additional {
|
||||
display: flex;
|
||||
|
||||
@media @mobile {
|
||||
flex-direction: column;
|
||||
}
|
||||
@media @tablet {
|
||||
flex-direction: row;
|
||||
}
|
||||
gap: 2.5rem;
|
||||
div {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: start;
|
||||
}
|
||||
.submit-request {
|
||||
flex: 1;
|
||||
box-shadow: 0 0 25px 10px rgba(0, 0, 0, 0.05);
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: start;
|
||||
background-color: @heading-font-color;
|
||||
color: white;
|
||||
padding: 10px 20px;
|
||||
}
|
||||
}
|
||||
.form-rows {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
gap: 1.5rem;
|
||||
width: 100%;
|
||||
h3 {
|
||||
font-weight: bold !important;
|
||||
}
|
||||
|
||||
.form-row {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
gap: 1.5rem;
|
||||
|
||||
.date {
|
||||
max-width: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
label {
|
||||
width: 100% !important;
|
||||
font-size: inherit;
|
||||
input,
|
||||
select,
|
||||
textarea {
|
||||
width: 100%;
|
||||
font-size: inherit;
|
||||
}
|
||||
}
|
||||
.form-cols {
|
||||
display: flex;
|
||||
gap: 1.5rem;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.form-column {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex-direction: column;
|
||||
justify-content: flex-end;
|
||||
gap: 1rem;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
h3 {
|
||||
margin-bottom: 0.5rem;
|
||||
font-size: 1.2rem;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
p {
|
||||
margin-bottom: 0.5rem;
|
||||
color: #777;
|
||||
}
|
||||
|
||||
input,
|
||||
select,
|
||||
textarea,
|
||||
.data-protection {
|
||||
padding: 10px 20px;
|
||||
border: 0px solid black;
|
||||
border-bottom: 3px solid @heading-font-color;
|
||||
outline: 0px solid black;
|
||||
|
||||
background-color: white;
|
||||
resize: none;
|
||||
}
|
||||
|
||||
.data-protection {
|
||||
display: flex;
|
||||
|
||||
flex-wrap: nowrap;
|
||||
gap: 5px !important;
|
||||
justify-content: start;
|
||||
flex-direction: row !important;
|
||||
}
|
||||
|
||||
input[type="date"] {
|
||||
font-family: inherit;
|
||||
width: 100% !important;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
select {
|
||||
padding: 10px 20px;
|
||||
border: 0;
|
||||
border-bottom: 3px solid @heading-font-color;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
select:focus {
|
||||
border-bottom-color: @heading-font-color;
|
||||
}
|
||||
|
||||
#time-select {
|
||||
appearance: none;
|
||||
background-image: url("../../../../media/clock.svg");
|
||||
background-repeat: no-repeat;
|
||||
background-position: right 20px center;
|
||||
background-size: 18px;
|
||||
|
||||
option {
|
||||
padding: 10px 20px;
|
||||
background-color: white;
|
||||
}
|
||||
}
|
||||
@media @mobile {
|
||||
.date {
|
||||
width: 100vw !important;
|
||||
}
|
||||
.form-cols {
|
||||
}
|
||||
}
|
||||
@media @tablet {
|
||||
.date {
|
||||
width: 100% !important;
|
||||
}
|
||||
.form-cols {
|
||||
flex-direction: row;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media @mobile {
|
||||
.no-gap {
|
||||
gap: 0px !important;
|
||||
}
|
||||
|
||||
& > div {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: start;
|
||||
|
||||
gap: 20px;
|
||||
|
||||
& > div {
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media @tablet {
|
||||
& > div {
|
||||
gap: 40px;
|
||||
}
|
||||
}
|
||||
@media @desktop {
|
||||
.no-gap {
|
||||
gap: 40px !important;
|
||||
}
|
||||
.row-title {
|
||||
font-size: 2.5rem !important;
|
||||
}
|
||||
& > div {
|
||||
flex-direction: row;
|
||||
align-items: start;
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
.max-width {
|
||||
max-width: @body-maxwidth !important;
|
||||
}
|
||||
|
||||
.datasec {
|
||||
display: flex;
|
||||
align-items: center !important;
|
||||
.link {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: flex-end !important;
|
||||
text-decoration: underline;
|
||||
margin-right: 3px;
|
||||
color: rgb(14, 91, 146);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
52
frontend/src/lib/components/pagebuilder/siteRefs.svelte
Normal file
52
frontend/src/lib/components/pagebuilder/siteRefs.svelte
Normal file
@@ -0,0 +1,52 @@
|
||||
<script lang="ts">
|
||||
import inView from "../../functions/observer"
|
||||
import { fly } from "svelte/transition"
|
||||
export let rows: Row[]
|
||||
function scrollToRow(name) {
|
||||
const element = document.getElementsByName(name)[0]
|
||||
if (element) {
|
||||
element.scrollIntoView({ behavior: "smooth" })
|
||||
}
|
||||
}
|
||||
let visible = false
|
||||
let node
|
||||
</script>
|
||||
|
||||
{#key visible}
|
||||
<div
|
||||
class="siteRefs"
|
||||
bind:this="{node}"
|
||||
use:inView="{{ threshold: 0 }}"
|
||||
on:enter="{() => {
|
||||
visible = true
|
||||
}}"
|
||||
>
|
||||
{#each rows as row, i}
|
||||
{#if row.title && i != 0}
|
||||
<button
|
||||
class="siteRef"
|
||||
on:click="{() => scrollToRow(row.title)}"
|
||||
in:fly="{{ duration: 1000, delay: 40 * i, opacity: 0, y: 150 }}"
|
||||
><img src="media/arrow-bottom-right.svg" alt="arrow" />{row?.title}</button
|
||||
>
|
||||
{/if}
|
||||
{/each}
|
||||
</div>
|
||||
{/key}
|
||||
|
||||
<style lang="less">
|
||||
@import "../../assets/css/variables.less";
|
||||
.siteRefs {
|
||||
margin-top: 25px;
|
||||
button {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
img {
|
||||
transform: scale(1.4);
|
||||
margin: 0.5rem 0.9rem 0.5rem 0rem;
|
||||
}
|
||||
color: @link-font-color;
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
128
frontend/src/lib/components/pagebuilder/table.svelte
Normal file
128
frontend/src/lib/components/pagebuilder/table.svelte
Normal file
@@ -0,0 +1,128 @@
|
||||
<script lang="ts">
|
||||
export let col: Column
|
||||
import { apiBaseURL } from "../../../config"
|
||||
import { fly } from "svelte/transition"
|
||||
import SiteRefs from "./siteRefs.svelte"
|
||||
import inView from "../../functions/observer"
|
||||
let visible = false
|
||||
let node
|
||||
export let rows: Row[]
|
||||
</script>
|
||||
|
||||
<div
|
||||
class="tableContainer"
|
||||
bind:this="{node}"
|
||||
use:inView="{{ threshold: 0 }}"
|
||||
on:enter="{() => {
|
||||
visible = true
|
||||
}}"
|
||||
>
|
||||
<h3>{col?.tableFieldHeading ?? ""}</h3>
|
||||
{#key visible}
|
||||
{#each col?.table as table, i (table?.title)}
|
||||
<div class="table" in:fly="{{ duration: 1000, delay: i * 150, y: 130, opacity: 0 }}">
|
||||
<div class="tableTitle">{table?.title ?? ""}</div>
|
||||
|
||||
<table>
|
||||
{#each table?.tableRow as row}
|
||||
<tr class:font-bold="{row.bold}">
|
||||
{#if row?.left}
|
||||
<td>{row?.left}</td>
|
||||
{/if}
|
||||
{#if row?.center}
|
||||
<td>{row.center}</td>
|
||||
{/if}
|
||||
{#if row.right}
|
||||
<td>{row?.right}</td>
|
||||
{/if}
|
||||
</tr>
|
||||
{/each}
|
||||
</table>
|
||||
{#if table.hintsTable}
|
||||
<div class="hints-table">
|
||||
<h5>Hinweise:</h5>
|
||||
<p>{@html table?.hintsTable}</p>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{/each}
|
||||
{/key}
|
||||
{#if col.hintsTable}
|
||||
<div in:fly="{{ duration: 1000, y: 130, opacity: 0 }}">
|
||||
<h5>Hinweise:</h5>
|
||||
<p>{@html col?.hintsTable}</p>
|
||||
</div>
|
||||
{/if}
|
||||
{#if col.siteRefs}
|
||||
<SiteRefs rows="{rows}" />
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<style lang="less">
|
||||
@import "../../assets/css/variables.less";
|
||||
* {
|
||||
border: 0px solid black !important;
|
||||
transition: font-size 0ms;
|
||||
}
|
||||
|
||||
.hints-table {
|
||||
padding: 0.3rem 0px;
|
||||
h5 {
|
||||
font-size: 0.85rem;
|
||||
font-weight: 700;
|
||||
}
|
||||
p {
|
||||
font-size: 0.85rem;
|
||||
padding: 0.3rem 0px;
|
||||
}
|
||||
}
|
||||
|
||||
.tableContainer {
|
||||
max-width: @body-maxwidth;
|
||||
position: relative;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
line-height: normal;
|
||||
|
||||
h3 {
|
||||
width: 100%;
|
||||
font-size: 2rem;
|
||||
font-weight: bold;
|
||||
margin: 0rem 0vw 1.6rem 0vw;
|
||||
color: @heading-font-color;
|
||||
}
|
||||
|
||||
h5 {
|
||||
font-size: 0.85rem;
|
||||
font-weight: 700;
|
||||
}
|
||||
p {
|
||||
font-size: 0.85rem;
|
||||
padding: 0.3rem 0px;
|
||||
}
|
||||
.font-bold {
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
|
||||
.tableTitle {
|
||||
font-weight: bold;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
table {
|
||||
width: 100%;
|
||||
tr {
|
||||
border: 0px solid black;
|
||||
display: flex;
|
||||
}
|
||||
td {
|
||||
padding: 0.3rem 0px;
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
55
frontend/src/lib/components/pagebuilder/teaser.svelte
Normal file
55
frontend/src/lib/components/pagebuilder/teaser.svelte
Normal file
@@ -0,0 +1,55 @@
|
||||
<script lang="ts">
|
||||
import { navigate } from "svelte-routing"
|
||||
export let site: Site
|
||||
import Image from "./image.svelte"
|
||||
export let index
|
||||
</script>
|
||||
|
||||
<main class="teaser">
|
||||
{#if index % 2 == 0 || window.innerWidth < 1023}
|
||||
<Image siteId="{site?.id}" siteImages="{site?.teaserImages}" />
|
||||
{/if}
|
||||
<div class="content">
|
||||
<h3>{site?.teaserTitle}</h3>
|
||||
<p>{site?.teaserDescription}</p>
|
||||
<button on:click="{() => navigate(site.path)}">MEHR</button>
|
||||
</div>
|
||||
{#if index % 2 == 1 && window.innerWidth > 1023}
|
||||
<Image siteId="{site?.id}" siteImages="{site?.teaserImages}" />
|
||||
{/if}
|
||||
</main>
|
||||
|
||||
<style lang="less">
|
||||
@import "../../assets/css/variables.less";
|
||||
.teaser {
|
||||
max-width: @body-small-maxwidth;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2vw;
|
||||
width: 100%;
|
||||
@media @desktop {
|
||||
flex-direction: row;
|
||||
}
|
||||
div {
|
||||
flex: 1;
|
||||
}
|
||||
.content {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
h3 {
|
||||
font-size: 2rem;
|
||||
font-weight: bold;
|
||||
margin: 0rem 0 1.6rem 0;
|
||||
color: @heading-font-color;
|
||||
}
|
||||
button {
|
||||
font-weight: bold;
|
||||
color: @link-font-color;
|
||||
width: fit-content;
|
||||
border-top: 3px solid @link-font-color;
|
||||
margin-top: 3rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
48
frontend/src/lib/components/pagebuilder/textContent.svelte
Normal file
48
frontend/src/lib/components/pagebuilder/textContent.svelte
Normal file
@@ -0,0 +1,48 @@
|
||||
<script lang="ts">
|
||||
import SiteRefs from "./siteRefs.svelte"
|
||||
|
||||
export let col: Column
|
||||
export let rows: Row[]
|
||||
</script>
|
||||
|
||||
<div class="text-container">
|
||||
<h3 class:noMargin={!col.textFieldHeading}>{col.textFieldHeading ?? ""}</h3>
|
||||
{#if col?.textContent}
|
||||
<div>
|
||||
{@html col?.textContent}
|
||||
</div>
|
||||
{/if}
|
||||
{#if col.siteReference}
|
||||
<SiteRefs rows="{rows}" />
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<style lang="less">
|
||||
@import "../../assets/css/variables.less";
|
||||
.noMargin{
|
||||
margin: 0px !important;
|
||||
}
|
||||
.text-container {
|
||||
text-align: left;
|
||||
width: 100%;
|
||||
h3 {
|
||||
width: 100%;
|
||||
font-size: 2rem;
|
||||
font-weight: bold;
|
||||
margin: 0rem 0vw 1.6rem 0vw;
|
||||
color: @heading-font-color;
|
||||
}
|
||||
}
|
||||
@media @mobile {
|
||||
.text-container {
|
||||
li {
|
||||
padding-left: 16px !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
@media @desktop {
|
||||
.text-container {
|
||||
margin: 0px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
163
frontend/src/lib/components/pagebuilder/video.svelte
Normal file
163
frontend/src/lib/components/pagebuilder/video.svelte
Normal file
@@ -0,0 +1,163 @@
|
||||
<script lang="ts">
|
||||
import { apiBaseURL } from "../../../config"
|
||||
import { navigate } from "svelte-routing"
|
||||
import { fly } from "svelte/transition"
|
||||
let container
|
||||
let videoElement
|
||||
let videoText
|
||||
let videoWrapper
|
||||
let cooldown = false
|
||||
export let col
|
||||
export let siteId
|
||||
</script>
|
||||
|
||||
<div id="wrapper" class="video-container" bind:this="{container}">
|
||||
<div class="video-wrapper video-wrapper-big" bind:this="{videoWrapper}">
|
||||
<div class="text shown" bind:this="{videoText}">
|
||||
<h2 in:fly="{{ duration: 1000, delay: 500, y: 50, opacity: 0 }}">{col.titleVideo}</h2>
|
||||
<p in:fly="{{ duration: 1000, delay: 500, y: 50, opacity: 0 }}">
|
||||
{col.descriptionVideo}
|
||||
</p>
|
||||
</div>
|
||||
<video
|
||||
autoplay
|
||||
class="video"
|
||||
playsinline
|
||||
src="{`${apiBaseURL}content/${siteId}/${col.video?.src}`}"
|
||||
loop
|
||||
alt="video1"
|
||||
muted
|
||||
bind:this="{videoElement}"><track kind="captions" /></video
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style lang="less">
|
||||
@import "../../assets/css/variables.less";
|
||||
|
||||
#wrapper {
|
||||
width: 100%;
|
||||
flex: 2;
|
||||
}
|
||||
|
||||
.video-wrapper {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
max-height: min(700px, 55vh);
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
&::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: rgba(0, 0, 0, 0.3);
|
||||
z-index: 10;
|
||||
}
|
||||
@media @mobile {
|
||||
.hidden {
|
||||
p,
|
||||
button {
|
||||
visibility: hidden !important;
|
||||
opacity: 0;
|
||||
height: 0px;
|
||||
margin-top: 0px !important;
|
||||
transition: opacity 0s;
|
||||
}
|
||||
h2 {
|
||||
margin-bottom: 0px !important;
|
||||
transform: translateY(50%);
|
||||
}
|
||||
}
|
||||
|
||||
.shown {
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
}
|
||||
}
|
||||
|
||||
@media @desktop {
|
||||
.hidden {
|
||||
h2 {
|
||||
visibility: hidden;
|
||||
transition: opacity 0s;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.text {
|
||||
position: absolute;
|
||||
z-index: 20;
|
||||
max-width: 85%;
|
||||
color: rgb(255, 255, 255);
|
||||
animation-delay: 500ms;
|
||||
bottom: 35px;
|
||||
left: 35px;
|
||||
|
||||
@media @mobile {
|
||||
h2 {
|
||||
font-size: 2.8rem;
|
||||
font-weight: bold;
|
||||
line-height: normal;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
p {
|
||||
line-height: normal;
|
||||
}
|
||||
|
||||
button {
|
||||
border-top: 3px solid white;
|
||||
font-size: 1.4rem;
|
||||
font-weight: bold;
|
||||
color: white;
|
||||
margin-top: 15px;
|
||||
}
|
||||
}
|
||||
@media @desktop {
|
||||
max-width: 70%;
|
||||
h2 {
|
||||
font-size: 2.7rem;
|
||||
}
|
||||
p {
|
||||
}
|
||||
|
||||
button {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.video {
|
||||
width: 100%;
|
||||
min-height: max(35vh, 400px);
|
||||
height: 100% !important;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
@media (max-width: 1023px) {
|
||||
.video-container {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.text {
|
||||
max-width: 90%;
|
||||
}
|
||||
|
||||
video {
|
||||
height: 100% !important;
|
||||
min-height: max(45vh, 60vw) !important;
|
||||
width: auto !important;
|
||||
min-width: 100%;
|
||||
object-fit: cover;
|
||||
object-position: center;
|
||||
}
|
||||
|
||||
.video-wrapper-small,
|
||||
.video-wrapper-big {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
315
frontend/src/lib/components/videoSwitch.svelte
Normal file
315
frontend/src/lib/components/videoSwitch.svelte
Normal file
@@ -0,0 +1,315 @@
|
||||
<script lang="ts">
|
||||
import { onMount } from "svelte"
|
||||
import { apiBaseURL } from "../../config"
|
||||
import { navigate } from "svelte-routing"
|
||||
import { temperature } from "../stores"
|
||||
export let siteId
|
||||
let container
|
||||
let videoElements = []
|
||||
let videoTexts = []
|
||||
let videoWrappers = []
|
||||
let cooldown = false
|
||||
export let col
|
||||
|
||||
onMount(() => {
|
||||
container.style.height = videoElements[0].offsetHeight + "px"
|
||||
Array.from(videoElements).forEach((e) => {
|
||||
e.style.height = "100%"
|
||||
})
|
||||
videoElements[0].play()
|
||||
videoElements[1].play()
|
||||
setTimeout(() => {
|
||||
videoElements[1].pause()
|
||||
}, 0)
|
||||
videoWrappers.forEach((videoWrapper) => {
|
||||
const video = videoWrapper.querySelector(".video")
|
||||
let eventFunc = (e) => {
|
||||
if (cooldown) return
|
||||
let selected = e.currentTarget
|
||||
cooldown = true
|
||||
setTimeout(() => {
|
||||
cooldown = false
|
||||
}, 502)
|
||||
let text = selected.querySelector(".text")
|
||||
videoWrappers.forEach((vw) => {
|
||||
if (selected == vw) {
|
||||
vw.classList.add("video-wrapper-big")
|
||||
vw.classList.remove("video-wrapper-small")
|
||||
} else {
|
||||
vw.classList.add("video-wrapper-small")
|
||||
vw.classList.remove("video-wrapper-big")
|
||||
}
|
||||
vw.querySelector(".video").pause()
|
||||
})
|
||||
videoTexts.forEach((vt) => {
|
||||
if (text == vt) {
|
||||
setTimeout(() => {
|
||||
vt.classList.add("shown")
|
||||
vt.classList.remove("hidden")
|
||||
}, 500)
|
||||
} else {
|
||||
vt.classList.add("hidden")
|
||||
vt.classList.remove("shown")
|
||||
}
|
||||
})
|
||||
video.play()
|
||||
}
|
||||
|
||||
videoWrapper.addEventListener("mousemove", (e) => eventFunc(e))
|
||||
videoWrapper.addEventListener("click", (e) => eventFunc(e))
|
||||
})
|
||||
})
|
||||
let innerWidth = window.innerWidth
|
||||
</script>
|
||||
|
||||
<div class="video-top-lvl-container">
|
||||
<div id="wrapper" class="video-container" bind:this="{container}">
|
||||
<div class="video-wrapper video-wrapper-big" bind:this="{videoWrappers[0]}">
|
||||
<div class="text shown" bind:this="{videoTexts[0]}">
|
||||
<h2>{col.videoSwitch[0]?.title}</h2>
|
||||
<p>
|
||||
{col.videoSwitch[0]?.description}
|
||||
</p>
|
||||
<button
|
||||
class="link"
|
||||
on:click="{() => {
|
||||
navigate(col.videoSwitch[0]?.link)
|
||||
}}">MEHR</button
|
||||
>
|
||||
</div>
|
||||
<video
|
||||
class="video"
|
||||
playsinline
|
||||
src="{`${apiBaseURL}content/${siteId}/${col.videoSwitch[0].video?.src}`}"
|
||||
loop
|
||||
alt="video1"
|
||||
muted
|
||||
bind:this="{videoElements[0]}"><track kind="captions" /></video
|
||||
>
|
||||
</div>
|
||||
<div class="video-wrapper video-wrapper-small" bind:this="{videoWrappers[1]}">
|
||||
<div class="text hidden" bind:this="{videoTexts[1]}">
|
||||
<h2>{col.videoSwitch[1]?.title}</h2>
|
||||
<p>
|
||||
{col.videoSwitch[1]?.description}
|
||||
</p>
|
||||
<button
|
||||
class="link"
|
||||
on:click="{() => {
|
||||
navigate(col.videoSwitch[1]?.link)
|
||||
}}">MEHR</button
|
||||
>
|
||||
</div>
|
||||
<video
|
||||
class="video"
|
||||
loop
|
||||
playsinline
|
||||
src="{`${apiBaseURL}content/${siteId}/${col.videoSwitch[1].video?.src}`}"
|
||||
muted
|
||||
bind:this="{videoElements[1]}"><track kind="captions" /></video
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div class="icons">
|
||||
<button class="thermo">
|
||||
<img src="/media/thermometer.svg" alt="temp" />
|
||||
{Number($temperature).toFixed(1) || "-"} °C
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style lang="less">
|
||||
@import "../assets/css/variables.less";
|
||||
|
||||
.video-top-lvl-container {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 40px;
|
||||
}
|
||||
|
||||
.video-container {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
@media @mobile {
|
||||
gap: 20px;
|
||||
}
|
||||
@media @tablet {
|
||||
gap: 40px;
|
||||
}
|
||||
}
|
||||
.icons {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
@media @mobile {
|
||||
justify-content: end;
|
||||
}
|
||||
@media @tablet {
|
||||
justify-content: end;
|
||||
button {
|
||||
margin: 28px 2rem !important;
|
||||
}
|
||||
}
|
||||
margin-bottom: 60px;
|
||||
|
||||
img {
|
||||
transform: scale(1.5);
|
||||
margin: 0px 0.4rem;
|
||||
}
|
||||
button {
|
||||
display: flex;
|
||||
font-weight: bold;
|
||||
justify-content: center;
|
||||
font-size: 1rem;
|
||||
position: relative;
|
||||
margin: 0 1rem;
|
||||
}
|
||||
button::before {
|
||||
font-weight: 400;
|
||||
position: absolute;
|
||||
transform: translateY(-150%);
|
||||
}
|
||||
.camera::before {
|
||||
content: "Webcam";
|
||||
}
|
||||
.thermo::before {
|
||||
content: "Lufttemperatur";
|
||||
}
|
||||
.waves::before {
|
||||
content: "Wassertemperatur";
|
||||
}
|
||||
}
|
||||
|
||||
.video-wrapper {
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
&::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: rgba(0, 0, 0, 0.3);
|
||||
z-index: 10;
|
||||
}
|
||||
@media @mobile {
|
||||
.hidden {
|
||||
p,
|
||||
button {
|
||||
visibility: hidden !important;
|
||||
opacity: 0;
|
||||
height: 0px;
|
||||
margin-top: 0px !important;
|
||||
transition: opacity 0s;
|
||||
}
|
||||
h2 {
|
||||
margin-bottom: 0px !important;
|
||||
transform: translateY(50%);
|
||||
}
|
||||
}
|
||||
|
||||
.shown {
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
}
|
||||
}
|
||||
@media @desktop {
|
||||
.hidden {
|
||||
h2 {
|
||||
visibility: hidden;
|
||||
transition: opacity 0s;
|
||||
}
|
||||
}
|
||||
}
|
||||
.text {
|
||||
position: absolute;
|
||||
z-index: 20;
|
||||
max-width: 85%;
|
||||
color: rgb(255, 255, 255);
|
||||
animation-delay: 500ms;
|
||||
bottom: 35px;
|
||||
left: 35px;
|
||||
|
||||
@media @mobile {
|
||||
h2 {
|
||||
font-size: 2.8rem;
|
||||
font-weight: bold;
|
||||
line-height: normal;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
p {
|
||||
line-height: normal;
|
||||
}
|
||||
|
||||
button {
|
||||
border-top: 3px solid white;
|
||||
font-weight: bold;
|
||||
padding-top: 2px;
|
||||
color: white;
|
||||
font-size: 16px;
|
||||
margin-top: 15px;
|
||||
}
|
||||
}
|
||||
@media @desktop {
|
||||
max-width: 70%;
|
||||
h2 {
|
||||
font-size: 2.7rem;
|
||||
}
|
||||
p {
|
||||
}
|
||||
|
||||
button {
|
||||
font-size: 24px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.video {
|
||||
width: 100%;
|
||||
min-height: max(35vh, 400px);
|
||||
height: auto;
|
||||
object-fit: cover;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
.video-wrapper-big {
|
||||
flex: 3;
|
||||
}
|
||||
|
||||
.video-wrapper-small {
|
||||
flex: 1 !important;
|
||||
}
|
||||
|
||||
.video-wrapper-big:hover,
|
||||
.video-wrapper-small:hover {
|
||||
flex: 3;
|
||||
}
|
||||
|
||||
@media (max-width: 1023px) {
|
||||
.video-container {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.text {
|
||||
max-width: 90%;
|
||||
}
|
||||
|
||||
video {
|
||||
height: 100% !important;
|
||||
min-height: 75vh !important;
|
||||
width: auto !important;
|
||||
min-width: 100%;
|
||||
object-fit: cover;
|
||||
object-position: center;
|
||||
}
|
||||
|
||||
.video-wrapper-small,
|
||||
.video-wrapper-big {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user