first row
This commit is contained in:
@@ -4,30 +4,15 @@
|
||||
import Footer from "./lib/components/Footer.svelte"
|
||||
import Notifications from "./lib/components/widgets/Notifications.svelte"
|
||||
import SSRSkip from "./lib/components/SSRSkip.svelte"
|
||||
import { baseURL } from "./config"
|
||||
import { isMobile, location, openModal } from "./lib/store"
|
||||
import StaticHomepage from "./routes/StaticHomepage.svelte"
|
||||
|
||||
export let url = ""
|
||||
let innerWidth = $state(0)
|
||||
$effect(() => {
|
||||
$isMobile = innerWidth <= 1024
|
||||
})
|
||||
|
||||
if (url) {
|
||||
const [rawPath, rawQuery = ""] = url.split("?")
|
||||
const normalizedPath = rawPath.startsWith("/") ? rawPath : `/${rawPath}`
|
||||
const query = rawQuery ? decodeURIComponent(`?${rawQuery}`) : ""
|
||||
$location = {
|
||||
path: normalizedPath,
|
||||
search: query,
|
||||
hash: "",
|
||||
push: false,
|
||||
pop: false,
|
||||
url: `${baseURL}${normalizedPath}${rawQuery ? `?${rawQuery}` : ""}`,
|
||||
}
|
||||
}
|
||||
|
||||
let innerWidth = 0
|
||||
$: $isMobile = innerWidth <= 1024
|
||||
|
||||
let googleCookiesAllowed = false
|
||||
let googleCookiesAllowed = $state(false)
|
||||
const googleCookieName = "googleAnalytics"
|
||||
|
||||
const syncModalState = () => {
|
||||
@@ -77,7 +62,7 @@
|
||||
})
|
||||
</script>
|
||||
|
||||
<svelte:window bind:innerWidth={innerWidth} />
|
||||
<svelte:window bind:innerWidth />
|
||||
|
||||
<svelte:head>
|
||||
{#if googleCookiesAllowed}
|
||||
@@ -107,7 +92,10 @@
|
||||
<Notifications />
|
||||
<SSRSkip />
|
||||
|
||||
<style lang="less" global>
|
||||
<style
|
||||
lang="less"
|
||||
global
|
||||
>
|
||||
@import "./lib/assets/css/variables.less";
|
||||
@import "./lib/assets/css/main.less";
|
||||
@import "../assets/fonts/fonts.css";
|
||||
@@ -130,20 +118,18 @@
|
||||
@media @min-tablet {
|
||||
font-size: 18px;
|
||||
}
|
||||
}
|
||||
.app-shell {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.app-shell {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 100vh;
|
||||
background-color: var(--neutral-white);
|
||||
}
|
||||
|
||||
main {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
main {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -89,10 +89,10 @@ export async function api<T>(endpoint: string, options: ApiOptions = {}, body?:
|
||||
type EntryTypeSwitch<T> = T extends "medialib"
|
||||
? MedialibEntry
|
||||
: T extends "content"
|
||||
? ContentEntry
|
||||
: T extends "navigation"
|
||||
? NavigationEntry
|
||||
: any
|
||||
? ContentEntry
|
||||
: T extends "navigation"
|
||||
? NavigationEntry
|
||||
: any
|
||||
|
||||
export async function getDBEntries<T extends CollectionName>(
|
||||
collectionName: T,
|
||||
|
||||
13
frontend/src/i18n.ts
Normal file
13
frontend/src/i18n.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import { writable } from "svelte/store"
|
||||
import { baseURL } from "./config"
|
||||
|
||||
const initLoc = {
|
||||
path: (typeof window !== "undefined" && window.location?.pathname) || "/",
|
||||
search: (typeof window !== "undefined" && window.location?.search) || "",
|
||||
hash: (typeof window !== "undefined" && window.location?.hash) || "",
|
||||
push: false,
|
||||
pop: false,
|
||||
url: `${baseURL}${(typeof window !== "undefined" && window.location?.pathname) || "/"}`,
|
||||
}
|
||||
|
||||
export const location = writable<LocationStore>(initLoc)
|
||||
@@ -1,11 +1,82 @@
|
||||
import "./i18n"
|
||||
import App from "./App.svelte"
|
||||
|
||||
import { baseURL } from "./config"
|
||||
import { hydrate } from "svelte"
|
||||
import { location } from "./i18n"
|
||||
|
||||
const publishLocation = (_p?: string) => {
|
||||
let _s: string
|
||||
let _h: string
|
||||
if (_p) {
|
||||
const parts = _p.split("#")
|
||||
_p = parts.shift()
|
||||
_h = parts.join()
|
||||
if (_h) _h = "#" + _h
|
||||
|
||||
const parts2 = _p.split("?")
|
||||
_p = parts2.shift()
|
||||
_s = parts2.join()
|
||||
if (_s) _s = "?" + _s
|
||||
}
|
||||
// make sure the path does not include the baseURL
|
||||
if (_p && _p.startsWith(baseURL)) {
|
||||
_p = _p.slice(baseURL.length)
|
||||
} else if (!_p) {
|
||||
_p = typeof window !== "undefined" && window.location?.pathname
|
||||
}
|
||||
|
||||
const newLocation = {
|
||||
path: _p,
|
||||
search: _p ? _s : typeof window !== "undefined" && window.location?.search,
|
||||
hash: _p ? _h : typeof window !== "undefined" && window.location?.hash,
|
||||
push: !!_p,
|
||||
pop: !_p,
|
||||
url: "",
|
||||
}
|
||||
|
||||
newLocation.url = `${baseURL}${newLocation.path}${newLocation.search ? "?" + newLocation.search : ""}`
|
||||
|
||||
location.set(newLocation)
|
||||
}
|
||||
|
||||
if (typeof history !== "undefined") {
|
||||
if (typeof Proxy !== "undefined") {
|
||||
// modern browser
|
||||
const historyApply = (target: any, thisArg: any, argumentsList: any[]) => {
|
||||
publishLocation(argumentsList && argumentsList.length >= 2 && argumentsList[2])
|
||||
Reflect.apply(target, thisArg, argumentsList)
|
||||
}
|
||||
|
||||
history.pushState = new Proxy(history.pushState, {
|
||||
apply: historyApply,
|
||||
})
|
||||
|
||||
history.replaceState = new Proxy(history.replaceState, {
|
||||
apply: historyApply,
|
||||
})
|
||||
} else {
|
||||
// ie11
|
||||
const pushStateFn = history.pushState
|
||||
const replaceStateFn = history.replaceState
|
||||
|
||||
history.pushState = function (data: any, title: string, url?: string) {
|
||||
publishLocation(url)
|
||||
return pushStateFn.apply(history, [...arguments] as [any, string, string?])
|
||||
}
|
||||
history.replaceState = function (data: any, title: string, url?: string) {
|
||||
publishLocation(url)
|
||||
return replaceStateFn.apply(history, [...arguments] as [any, string, string?])
|
||||
}
|
||||
}
|
||||
} // else ssr -> no history handling
|
||||
|
||||
typeof window !== "undefined" &&
|
||||
window.addEventListener("popstate", (event) => {
|
||||
publishLocation()
|
||||
})
|
||||
|
||||
let appContainer = document?.getElementById("appContainer")
|
||||
const hydrate = true //import.meta?.env?.MODE !== "development"
|
||||
const app = new App({
|
||||
target: appContainer,
|
||||
props: {},
|
||||
hydrate,
|
||||
})
|
||||
const app = hydrate(App, { target: appContainer })
|
||||
|
||||
export default app
|
||||
|
||||
@@ -40,8 +40,18 @@ ul {
|
||||
}
|
||||
|
||||
* {
|
||||
transition: background-color 0.5s ease, max-height 0.5s, height 0.5s ease, width 0.5s ease, flex 0.5s ease,
|
||||
opacity 0.5s ease, top 0.5s ease, bottom 0.5s ease, left 0.5s ease, right 0.5s ease, transform 0.5s ease;
|
||||
transition:
|
||||
background-color 0.5s ease,
|
||||
max-height 0.5s,
|
||||
height 0.5s ease,
|
||||
width 0.5s ease,
|
||||
flex 0.5s ease,
|
||||
opacity 0.5s ease,
|
||||
top 0.5s ease,
|
||||
bottom 0.5s ease,
|
||||
left 0.5s ease,
|
||||
right 0.5s ease,
|
||||
transform 0.5s ease;
|
||||
}
|
||||
/* General scrollbar styles for all webkit browsers */
|
||||
*::-webkit-scrollbar {
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
/* Figma Styles of your File */
|
||||
:root {
|
||||
/* Colors */
|
||||
--wire: linear-gradient(-77.29deg, rgba(0, 0, 0, 0) 0%, rgba(13, 12, 12, 1) 44.86416280269623%),
|
||||
--wire:
|
||||
linear-gradient(-77.29deg, rgba(0, 0, 0, 0) 0%, rgba(13, 12, 12, 1) 44.86416280269623%),
|
||||
linear-gradient(98.68deg, rgba(0, 0, 0, 0) 0%, rgba(13, 12, 12, 1) 44.86416280269623%),
|
||||
linear-gradient(180deg, rgba(51, 45, 44, 1) 0%, rgba(51, 45, 44, 0) 100%);
|
||||
--bg-grey-cultured: linear-gradient(to left, #0d0c0c, #0d0c0c);
|
||||
@@ -37,10 +38,6 @@
|
||||
--text-300: #625755;
|
||||
--bg-300: #eceaea;
|
||||
--text-invers-150: #6d97b0;
|
||||
--krass-kraft-primary: #eb5757;
|
||||
--crazy-crave-control-primary: #c0f256;
|
||||
--krass-kreativ-primary: #56f2f2;
|
||||
--crazy-calm-primary: #56f2b0;
|
||||
|
||||
--vertical-default-margin: 3rem;
|
||||
--small-max-width: 1392px;
|
||||
|
||||
@@ -1,219 +1,209 @@
|
||||
<script lang="ts">
|
||||
import { onMount } from "svelte"
|
||||
import { getDBEntries } from "../../api"
|
||||
import { socialIcons, companyName, email, streetAddress, zipCode, localityAddress } from "../../config"
|
||||
import { spaLink } from "../actions"
|
||||
import { navigationCache } from "../store"
|
||||
import CrinkledSection from "./CrinkledSection.svelte"
|
||||
|
||||
const NAVIGATION_TYPE = {
|
||||
Main: 0,
|
||||
Service: 1,
|
||||
Legal: 2,
|
||||
} as const
|
||||
let navigationEntries: NavigationEntry[] = []
|
||||
|
||||
let legalLinks: NavigationElement[] = []
|
||||
let serviceLinks: NavigationElement[] = []
|
||||
let loadingNavigation = true
|
||||
|
||||
const resolveHref = (link: NavigationElement) => {
|
||||
const base = link.page || "/"
|
||||
const hash = link.hash ? (link.hash.startsWith("#") ? link.hash : `#${link.hash}`) : ""
|
||||
return `${base}${hash}`
|
||||
function elementsToCache(elements: NavigationElement[]) {
|
||||
elements.forEach((el) => {
|
||||
if (!el.external) {
|
||||
if (!$navigationCache[el.page]) $navigationCache[el.page] = el
|
||||
if (el.elements?.length > 0) elementsToCache(el.elements)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
onMount(async () => {
|
||||
try {
|
||||
const entries = await getDBEntries("navigation")
|
||||
legalLinks = entries.find((entry) => Number(entry.type) === NAVIGATION_TYPE.Legal)?.elements ?? []
|
||||
serviceLinks = entries.find((entry) => Number(entry.type) === NAVIGATION_TYPE.Service)?.elements ?? []
|
||||
} catch (error) {
|
||||
console.error("Unable to load footer navigation", error)
|
||||
} finally {
|
||||
loadingNavigation = false
|
||||
}
|
||||
getDBEntries("navigation").then((navs) => {
|
||||
navigationEntries = navs
|
||||
navigationEntries.filter((nav) => "elements" in nav).forEach((nav) => elementsToCache(nav.elements))
|
||||
})
|
||||
|
||||
const currentYear = new Date().getFullYear()
|
||||
</script>
|
||||
|
||||
<footer class="site-footer">
|
||||
<div class="footer-inner">
|
||||
<section class="footer-column">
|
||||
<h2>{companyName}</h2>
|
||||
<p>{streetAddress}, {zipCode} {localityAddress}</p>
|
||||
<a
|
||||
class="footer-link"
|
||||
href={`mailto:${email}`}
|
||||
>
|
||||
{email}
|
||||
</a>
|
||||
<ul class="footer-social">
|
||||
{#each Object.entries(socialIcons) as [name, url]}
|
||||
<li>
|
||||
<a
|
||||
href={url}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
aria-label={`Öffne ${name} in einem neuen Tab`}
|
||||
>
|
||||
{name}
|
||||
</a>
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
</section>
|
||||
<section class="footer-column">
|
||||
<h3>Service</h3>
|
||||
{#if loadingNavigation}
|
||||
<span class="footer-placeholder">Links werden geladen …</span>
|
||||
{:else if serviceLinks.length}
|
||||
<ul>
|
||||
{#each serviceLinks as link (link.name)}
|
||||
<li>
|
||||
{#if link.external && link.externalUrl}
|
||||
<CrinkledSection>
|
||||
<footer class="footer">
|
||||
<section id="legal-section">
|
||||
<div class="wrapper">
|
||||
<small class="">© 2025 | KontextWerk | Alle Rechte vorbehalten.</small>
|
||||
<nav class="nav-points">
|
||||
<ul>
|
||||
{#each navigationEntries.length ? navigationEntries[0].elements : [] as link}
|
||||
<li>
|
||||
<a
|
||||
href={link.externalUrl}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
{link.name}
|
||||
</a>
|
||||
{:else}
|
||||
<a
|
||||
href={resolveHref(link)}
|
||||
class="footer-nav-point-bottom"
|
||||
use:spaLink
|
||||
href={link.page}
|
||||
>
|
||||
{link.name}
|
||||
<small>{link.name}</small>
|
||||
</a>
|
||||
{/if}
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
{:else}
|
||||
<span class="footer-placeholder">Aktuell keine Service-Links</span>
|
||||
{/if}
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
</nav>
|
||||
</div>
|
||||
</section>
|
||||
<section class="footer-column">
|
||||
<h3>Rechtliches</h3>
|
||||
{#if loadingNavigation}
|
||||
<span class="footer-placeholder">Links werden geladen …</span>
|
||||
{:else if legalLinks.length}
|
||||
<ul>
|
||||
{#each legalLinks as link (link.name)}
|
||||
<li>
|
||||
{#if link.external && link.externalUrl}
|
||||
<a
|
||||
href={link.externalUrl}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
{link.name}
|
||||
</a>
|
||||
{:else}
|
||||
<a
|
||||
href={resolveHref(link)}
|
||||
use:spaLink
|
||||
>
|
||||
{link.name}
|
||||
</a>
|
||||
{/if}
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
{:else}
|
||||
<span class="footer-placeholder">Aktuell keine rechtlichen Hinweise</span>
|
||||
{/if}
|
||||
</section>
|
||||
</div>
|
||||
<div class="footer-meta">© {currentYear} {companyName}. Alle Rechte vorbehalten.</div>
|
||||
</footer>
|
||||
</footer>
|
||||
</CrinkledSection>
|
||||
|
||||
<style lang="less">
|
||||
@import "../../lib/assets/css/variables.less";
|
||||
|
||||
.site-footer {
|
||||
background-color: var(--bg-200, #0d0d0d);
|
||||
color: var(--neutral-white);
|
||||
padding: 3rem 1.5rem 1.5rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2rem;
|
||||
}
|
||||
|
||||
.footer-inner {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
|
||||
gap: 2rem;
|
||||
width: 100%;
|
||||
max-width: var(--body-maxwidth);
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.footer-column {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.75rem;
|
||||
|
||||
h2,
|
||||
h3 {
|
||||
margin: 0;
|
||||
font-size: 1.2rem;
|
||||
font-weight: 600;
|
||||
.footer {
|
||||
&,
|
||||
& * {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
ul {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
background: var(--neutral-white);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
gap: 1.5rem;
|
||||
#content-section {
|
||||
padding: 0px var(--horizontal-default-margin);
|
||||
max-width: var(--small-max-width);
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 1.5rem;
|
||||
align-items: flex-start;
|
||||
justify-content: flex-start;
|
||||
position: relative;
|
||||
#content-link-section {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 1.5rem;
|
||||
align-items: flex-start;
|
||||
justify-content: flex-start;
|
||||
flex: 1;
|
||||
position: relative;
|
||||
.content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1.125rem;
|
||||
align-items: flex-start;
|
||||
justify-content: flex-start;
|
||||
width: 8rem;
|
||||
position: relative;
|
||||
}
|
||||
}
|
||||
@media @mobile {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
#content-link-section {
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
}
|
||||
#newsletter-section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
gap: 18px;
|
||||
min-width: 300px;
|
||||
form {
|
||||
button {
|
||||
width: fit-content;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
li {
|
||||
#icons-section {
|
||||
padding: 0px var(--horizontal-default-margin);
|
||||
max-width: var(--small-max-width);
|
||||
width: 100%;
|
||||
margin: 0 0 0 -0.0625rem;
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
|
||||
.footer-link,
|
||||
.footer-column a {
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
transition: opacity 0.2s ease;
|
||||
|
||||
&:hover {
|
||||
opacity: 0.75;
|
||||
}
|
||||
}
|
||||
|
||||
.footer-social {
|
||||
flex-direction: row;
|
||||
gap: 0.75rem;
|
||||
|
||||
a {
|
||||
text-transform: capitalize;
|
||||
font-size: 0.9rem;
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
|
||||
.footer-placeholder {
|
||||
font-size: 0.85rem;
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.footer-meta {
|
||||
text-align: center;
|
||||
font-size: 0.85rem;
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
@media (max-width: 640px) {
|
||||
.site-footer {
|
||||
padding: 2.4rem 1.2rem 1.2rem;
|
||||
gap: 0.625rem;
|
||||
align-items: flex-start;
|
||||
justify-content: flex-start;
|
||||
flex: 1;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
.payments,
|
||||
.social {
|
||||
display: flex;
|
||||
gap: 0.75rem;
|
||||
figure {
|
||||
height: 1.2rem;
|
||||
img {
|
||||
height: 100%;
|
||||
width: auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
.line {
|
||||
padding: 0rem 1.5rem 0rem 1.5rem;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 1.5rem;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
flex: 1;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
.line-1,
|
||||
.line-2 {
|
||||
border-style: solid;
|
||||
border-color: var(--text-invers-100);
|
||||
border-width: 0.0625rem 0 0 0;
|
||||
flex: 1;
|
||||
height: 0rem;
|
||||
position: relative;
|
||||
}
|
||||
}
|
||||
@media @mobile {
|
||||
flex-direction: column-reverse;
|
||||
align-items: center;
|
||||
gap: 2rem;
|
||||
.line {
|
||||
width: 100%;
|
||||
padding: 0px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.footer-social {
|
||||
flex-wrap: wrap;
|
||||
#legal-section {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
background: var(--bg-100);
|
||||
gap: 1.2rem;
|
||||
.wrapper {
|
||||
max-width: var(--small-max-width);
|
||||
|
||||
width: 100%;
|
||||
a,
|
||||
small {
|
||||
color: var(--text-100);
|
||||
}
|
||||
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
flex-shrink: 0;
|
||||
position: relative;
|
||||
gap: 1.2rem;
|
||||
padding: 1.5rem var(--horizontal-default-margin);
|
||||
@media @mobile {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.nav-points {
|
||||
ul {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.6rem;
|
||||
align-items: center;
|
||||
a {
|
||||
font-weight: 400;
|
||||
font-family: Outfit;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,108 +1,49 @@
|
||||
<script lang="ts">
|
||||
import { onMount } from "svelte"
|
||||
import { getDBEntries } from "../../../api"
|
||||
import { spaLink } from "../../actions"
|
||||
import { location } from "../../store"
|
||||
|
||||
const NAVIGATION_TYPE = {
|
||||
Main: 0,
|
||||
Service: 1,
|
||||
Legal: 2,
|
||||
} as const
|
||||
let scrolled: boolean = false,
|
||||
isHomepage: boolean = true
|
||||
|
||||
let navigationEntries: NavigationEntry[] = []
|
||||
let isMenuOpen = false
|
||||
let loadingNavigation = true
|
||||
|
||||
const resolveHref = (item: NavigationElement) => {
|
||||
const base = item.page || "/"
|
||||
const hash = item.hash ? (item.hash.startsWith("#") ? item.hash : `#${item.hash}`) : ""
|
||||
return `${base}${hash}`
|
||||
function checkScroll() {
|
||||
scrolled = window.scrollY >= 100
|
||||
}
|
||||
|
||||
const isActive = (item: NavigationElement) => {
|
||||
const target = resolveHref(item)
|
||||
const [path] = target.split("#")
|
||||
return path === $location.path
|
||||
}
|
||||
|
||||
const closeMenu = () => {
|
||||
isMenuOpen = false
|
||||
}
|
||||
|
||||
const toggleMenu = () => {
|
||||
isMenuOpen = !isMenuOpen
|
||||
}
|
||||
|
||||
onMount(async () => {
|
||||
try {
|
||||
const entries = await getDBEntries("navigation")
|
||||
navigationEntries = entries ?? []
|
||||
} catch (error) {
|
||||
console.error("Unable to load navigation", error)
|
||||
} finally {
|
||||
loadingNavigation = false
|
||||
onMount(() => {
|
||||
if (typeof window !== "undefined") {
|
||||
checkScroll()
|
||||
window.addEventListener("scroll", checkScroll)
|
||||
return () => {
|
||||
window.removeEventListener("scroll", checkScroll)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
$: mainNavigation = navigationEntries.find((entry) => Number(entry.type) === NAVIGATION_TYPE.Main)
|
||||
$: checkScroll()
|
||||
$: darkBG = scrolled
|
||||
</script>
|
||||
|
||||
<header class="site-header" aria-label="Primäre Navigation">
|
||||
<div class="header-inner">
|
||||
<a
|
||||
class="brand"
|
||||
href="/"
|
||||
use:spaLink
|
||||
on:click={closeMenu}
|
||||
>
|
||||
Kontextwerk
|
||||
</a>
|
||||
<button
|
||||
class="menu-toggle"
|
||||
aria-expanded={isMenuOpen}
|
||||
aria-controls="primary-navigation"
|
||||
on:click={toggleMenu}
|
||||
>
|
||||
<span class="sr-only">Navigation umschalten</span>
|
||||
<span class="bar"></span>
|
||||
<span class="bar"></span>
|
||||
<span class="bar"></span>
|
||||
</button>
|
||||
<nav
|
||||
id="primary-navigation"
|
||||
class:open={isMenuOpen}
|
||||
>
|
||||
{#if loadingNavigation}
|
||||
<span class="nav-placeholder">Navigation wird geladen …</span>
|
||||
{:else if mainNavigation?.elements?.length}
|
||||
<ul>
|
||||
{#each mainNavigation.elements as item (item.name)}
|
||||
<li class:active={isActive(item)}>
|
||||
{#if item.external && item.externalUrl}
|
||||
<a
|
||||
href={item.externalUrl}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
on:click={closeMenu}
|
||||
>
|
||||
{item.name}
|
||||
</a>
|
||||
{:else}
|
||||
<a
|
||||
href={resolveHref(item)}
|
||||
use:spaLink
|
||||
on:click={closeMenu}
|
||||
>
|
||||
{item.name}
|
||||
</a>
|
||||
{/if}
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
{:else}
|
||||
<span class="nav-placeholder">Keine Navigationspunkte verfügbar</span>
|
||||
{/if}
|
||||
<header
|
||||
class="headercontainer"
|
||||
id={"header-container"}
|
||||
class:scrolled={darkBG}
|
||||
class:homepageHeader={isHomepage}
|
||||
role="dialog"
|
||||
aria-label="Hauptnavigation"
|
||||
>
|
||||
<div class="padding">
|
||||
<nav class="menu">
|
||||
<a
|
||||
href="/"
|
||||
use:spaLink
|
||||
class="logo-container"
|
||||
aria-label="Go to homepage"
|
||||
>
|
||||
<img
|
||||
src="../../../../logo/KontextWerk.svg"
|
||||
alt="logo"
|
||||
/>
|
||||
</a>
|
||||
</nav>
|
||||
</div>
|
||||
</header>
|
||||
@@ -110,138 +51,55 @@
|
||||
<style lang="less">
|
||||
@import "../../assets/css/variables.less";
|
||||
|
||||
.site-header {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 1000;
|
||||
background-color: var(--neutral-white);
|
||||
border-bottom: 1px solid rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
@desktop: ~"only screen and (min-width: 1440px)";
|
||||
|
||||
.header-inner {
|
||||
max-width: var(--body-maxwidth);
|
||||
margin: 0 auto;
|
||||
padding: 1.2rem 1.6rem;
|
||||
.headercontainer {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 1.2rem;
|
||||
}
|
||||
|
||||
.brand {
|
||||
font-size: 1.4rem;
|
||||
font-weight: 700;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.08em;
|
||||
color: var(--text-invers-100);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.menu-toggle {
|
||||
display: none;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
@media @mobile {
|
||||
overflow: hidden;
|
||||
}
|
||||
position: sticky;
|
||||
z-index: 5500;
|
||||
top: 0px;
|
||||
|
||||
justify-content: space-between;
|
||||
background-color: #0d0c0c;
|
||||
|
||||
align-items: center;
|
||||
gap: 0.3rem;
|
||||
padding: 0.6rem;
|
||||
border: none;
|
||||
background: none;
|
||||
cursor: pointer;
|
||||
|
||||
.bar {
|
||||
width: 1.6rem;
|
||||
height: 2px;
|
||||
background-color: var(--text-invers-100);
|
||||
transition: transform 0.2s ease;
|
||||
}
|
||||
}
|
||||
|
||||
nav {
|
||||
ul {
|
||||
list-style: none;
|
||||
width: 100%;
|
||||
.padding {
|
||||
width: 100%;
|
||||
padding: 0px var(--horizontal-default-margin);
|
||||
height: 100%;
|
||||
display: flex;
|
||||
gap: 1.2rem;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
li {
|
||||
position: relative;
|
||||
|
||||
&.active a::after {
|
||||
transform: scaleX(1);
|
||||
}
|
||||
}
|
||||
|
||||
a {
|
||||
text-decoration: none;
|
||||
font-weight: 500;
|
||||
color: var(--text-100);
|
||||
padding: 0.3rem 0;
|
||||
position: relative;
|
||||
display: inline-flex;
|
||||
|
||||
&::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
left: 0;
|
||||
bottom: -0.3rem;
|
||||
align-items: flex-end;
|
||||
justify-content: center;
|
||||
.menu {
|
||||
max-width: var(--normal-max-width);
|
||||
width: 100%;
|
||||
height: 2px;
|
||||
background-color: var(--accent-100, #c4102d);
|
||||
transform: scaleX(0);
|
||||
transform-origin: left;
|
||||
transition: transform 0.2s ease;
|
||||
}
|
||||
|
||||
&:hover::after {
|
||||
transform: scaleX(1);
|
||||
display: flex;
|
||||
height: 86px;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
.logo-container {
|
||||
height: 64px;
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
img {
|
||||
height: 60px;
|
||||
width: auto;
|
||||
object-fit: contain;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.nav-placeholder {
|
||||
font-size: 0.875rem;
|
||||
color: var(--text-60);
|
||||
&.homepageHeader {
|
||||
background-color: transparent;
|
||||
}
|
||||
}
|
||||
|
||||
.sr-only {
|
||||
position: absolute;
|
||||
width: 1px;
|
||||
height: 1px;
|
||||
padding: 0;
|
||||
margin: -1px;
|
||||
overflow: hidden;
|
||||
clip: rect(0, 0, 0, 0);
|
||||
white-space: nowrap;
|
||||
border: 0;
|
||||
}
|
||||
|
||||
@media (max-width: 960px) {
|
||||
.menu-toggle {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
nav {
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
left: 0;
|
||||
right: 0;
|
||||
background-color: var(--neutral-white);
|
||||
border-bottom: 1px solid rgba(0, 0, 0, 0.05);
|
||||
transform: translateY(-110%);
|
||||
transition: transform 0.2s ease;
|
||||
padding: 1.2rem 1.6rem;
|
||||
|
||||
&.open {
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
ul {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 0.8rem;
|
||||
}
|
||||
&.scrolled&.homepageHeader {
|
||||
box-shadow: 0px 10px 20px 0px rgba(0, 0, 0, 0.2);
|
||||
background-color: var(--bg-100);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -0,0 +1,244 @@
|
||||
<script lang="ts">
|
||||
import { onMount } from "svelte"
|
||||
|
||||
let interval: NodeJS.Timeout
|
||||
let selectedChapter = -1
|
||||
let currentColor = "#741e20" // Initial color
|
||||
|
||||
setTimeout(() => {
|
||||
// set width and height of placeholder to elements width and height
|
||||
const placeholder = document.querySelector(".placeholder")
|
||||
const elements = document.querySelector(".elements")
|
||||
if (placeholder && elements) {
|
||||
const { width, height } = elements.getBoundingClientRect()
|
||||
placeholder.style.width = `${width}px`
|
||||
placeholder.style.height = `${height}px`
|
||||
}
|
||||
}, 10)
|
||||
function startInterval() {
|
||||
interval = setInterval(() => {
|
||||
selectedChapter = (selectedChapter + 1) % chapters.length
|
||||
updateShadowColor(chapters[selectedChapter]?.color || "#000")
|
||||
}, 3000)
|
||||
}
|
||||
|
||||
function stopInterval() {
|
||||
clearInterval(interval)
|
||||
}
|
||||
|
||||
function updateShadowColor(newColor: string) {
|
||||
const filter = document.getElementById("redShadow")
|
||||
const feDropShadow = filter?.querySelector("feDropShadow")
|
||||
if (feDropShadow) {
|
||||
feDropShadow.style.transition = "flood-color 1s ease"
|
||||
feDropShadow.setAttribute("flood-color", newColor)
|
||||
currentColor = newColor
|
||||
}
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
startInterval()
|
||||
return () => clearInterval(interval)
|
||||
})
|
||||
|
||||
const chapters = [
|
||||
{
|
||||
title: "Schneller",
|
||||
alias: "Schneller",
|
||||
shortDescription:
|
||||
"Unser internes System sorgt für eine schnelle und effiziente Umsetzung Ihres Projekts. Dadurch ermöglichen wir, ihr Projekt in Wochen, statt Monaten zu realisieren!",
|
||||
color: "#ffffff",
|
||||
},
|
||||
{
|
||||
title: "Qualitativer",
|
||||
alias: "Qualitativer",
|
||||
shortDescription:
|
||||
"Höhere Qualität durch spezialisierte Experten. Wir setzen auf ein Netzwerk aus erfahrenen Fachleuten, um Ihnen die bestmöglichen Lösungen mit State-of-the-Art-Technologien zu bieten.",
|
||||
color: "#741e20",
|
||||
},
|
||||
{
|
||||
title: "Entspannter",
|
||||
alias: "Entspannter",
|
||||
shortDescription:
|
||||
"Wir bieten Ihnen einen Rundum-sorglos-Service. Von der Konzeption über die Umsetzung bis hin zur Nachbetreuung. Alles aus einer Hand.",
|
||||
color: "#ffffff",
|
||||
},
|
||||
{
|
||||
title: "Autonomer",
|
||||
alias: "Autonomer",
|
||||
shortDescription:
|
||||
"Sie entscheiden wo die Software gehostet wird. Ob bei uns in unseren deutschen Rechenzentren oder bei Ihnen, wir bieten beides an.",
|
||||
color: "#741e20",
|
||||
},
|
||||
]
|
||||
</script>
|
||||
|
||||
<section
|
||||
class="splittedHomepage"
|
||||
style="--color: {currentColor}"
|
||||
>
|
||||
<div class="placeholder"></div>
|
||||
<ul class="elements">
|
||||
{#each chapters as chapter, i}
|
||||
<li
|
||||
class:selected={i == selectedChapter}
|
||||
on:mouseenter={() => {
|
||||
stopInterval()
|
||||
selectedChapter = i
|
||||
updateShadowColor(chapter.color)
|
||||
}}
|
||||
on:mouseleave={startInterval}
|
||||
>
|
||||
{#if i == selectedChapter}
|
||||
<h2
|
||||
class={i % 2 ? "" : "transparent-heading"}
|
||||
style={i % 2 ? `color: ${chapter.color}` : `-webkit-text-stroke: 1px ${chapter.color}`}
|
||||
>
|
||||
{chapter.title}
|
||||
</h2>
|
||||
{:else}
|
||||
<h2 class={i % 2 ? "white-heading" : "transparent-heading"}>{chapter.alias}</h2>
|
||||
{/if}
|
||||
|
||||
<p style="color: {chapter.color} !important;">
|
||||
{@html chapter.shortDescription}
|
||||
</p>
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
<div class="media">
|
||||
<svg
|
||||
viewBox="0 0 51 57"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<defs>
|
||||
<filter
|
||||
id="redShadow"
|
||||
x="-20%"
|
||||
y="-20%"
|
||||
width="200%"
|
||||
height="200%"
|
||||
primitiveUnits="objectBoundingBox"
|
||||
>
|
||||
<feDropShadow
|
||||
dx="-0.01"
|
||||
dy="0"
|
||||
stdDeviation="0.01"
|
||||
flood-color="currentColor"
|
||||
></feDropShadow>
|
||||
</filter>
|
||||
<clipPath id="bd9e6d0ed5"
|
||||
><path
|
||||
d="M 165 159.082031 L 215.691406 159.082031 L 215.691406 216 L 165 216 Z M 165 159.082031 "
|
||||
clip-rule="nonzero"
|
||||
></path></clipPath
|
||||
><clipPath id="8bf626c89e"
|
||||
><path
|
||||
d="M 0.601562 0.0820312 L 50.519531 0.0820312 L 50.519531 56.761719 L 0.601562 56.761719 Z M 0.601562 0.0820312 "
|
||||
clip-rule="nonzero"
|
||||
></path></clipPath
|
||||
><clipPath id="e48e3f235c"
|
||||
><rect
|
||||
x="0"
|
||||
width="51"
|
||||
y="0"
|
||||
height="57"
|
||||
></rect></clipPath
|
||||
>
|
||||
</defs>
|
||||
<path
|
||||
fill="black"
|
||||
d="M 32.6875 31.042969 C 40.058594 28.636719 47.421875 35.292969 45.523438 42.96875 C 43.078125 52.433594 32.925781 56.882812 22.929688 56.535156 L 22.929688 49.703125 C 39.636719 49.835938 45.316406 32.035156 32.6875 31.042969 M 29.917969 36.039062 C 30.808594 36.039062 31.53125 36.761719 31.53125 37.652344 C 31.53125 38.542969 30.808594 39.265625 29.917969 39.265625 C 29.027344 39.265625 28.304688 38.542969 28.304688 37.652344 C 28.304688 36.761719 29.027344 36.039062 29.917969 36.039062 Z M 24.203125 36.039062 C 25.09375 36.039062 25.816406 36.761719 25.816406 37.652344 C 25.816406 38.542969 25.09375 39.265625 24.203125 39.265625 C 23.3125 39.265625 22.589844 38.542969 22.589844 37.652344 C 22.589844 36.761719 23.3125 36.039062 24.203125 36.039062 Z M 18.484375 36.039062 C 19.378906 36.039062 20.097656 36.761719 20.097656 37.652344 C 20.097656 38.542969 19.378906 39.265625 18.484375 39.265625 C 17.59375 39.265625 16.871094 38.542969 16.871094 37.652344 C 16.871094 36.761719 17.59375 36.039062 18.484375 36.039062 Z M 28.578125 16.488281 C 36.304688 14.222656 46.632812 9.421875 49.421875 1.210938 C 50.289062 3.949219 50.550781 6.75 50.269531 9.457031 C 50.066406 11.445312 49.726562 13.175781 49.273438 14.691406 C 47.90625 18.25 45.480469 21.414062 42.160156 23.6875 C 42.496094 21.765625 42.253906 19.792969 41.460938 18.011719 C 36.949219 23.855469 21.6875 25.710938 14.578125 30.480469 C 11.671875 32.429688 9.898438 35.710938 9.898438 39.257812 C 9.898438 45.679688 18.644531 51.957031 22.769531 56.53125 C 13.492188 56.164062 4.390625 51.664062 1.753906 43.210938 C -5.558594 19.773438 30.507812 16.988281 42.691406 0.109375 C 42.144531 8.601562 36.023438 13.347656 28.578125 16.488281 Z M 28.578125 16.488281 "
|
||||
fill-opacity="1"
|
||||
fill-rule="evenodd"
|
||||
filter="url(#redShadow)"
|
||||
></path>
|
||||
</svg>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<style lang="less">
|
||||
@import "../../assets/css/variables.less";
|
||||
|
||||
.splittedHomepage {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
position: relative;
|
||||
height: 100%;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
transition: --color 1s ease;
|
||||
max-width: var(--normal-max-width);
|
||||
.elements {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1.5rem;
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
@media @mobile {
|
||||
gap: 0.5rem;
|
||||
}
|
||||
li {
|
||||
transition: max-height 1.5s ease;
|
||||
|
||||
max-height: 5rem;
|
||||
max-width: 900px;
|
||||
h2 {
|
||||
font-size: 4.5rem;
|
||||
text-transform: uppercase;
|
||||
font-weight: 700;
|
||||
line-height: 4.5rem;
|
||||
font-family: sans-serif;
|
||||
@media @mobile {
|
||||
font-size: 3rem;
|
||||
line-height: 3rem;
|
||||
}
|
||||
&.transparent-heading {
|
||||
@media @mobile {
|
||||
font-size: 3rem;
|
||||
}
|
||||
font-weight: 700;
|
||||
color: transparent;
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
-webkit-text-stroke: 1px white;
|
||||
}
|
||||
}
|
||||
height: fit-content;
|
||||
max-height: 10rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
p {
|
||||
opacity: 0;
|
||||
transition: opacity 1s;
|
||||
}
|
||||
&.selected {
|
||||
p {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.media {
|
||||
max-width: 600px;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
height: 100%;
|
||||
align-items: center;
|
||||
flex-grow: 1;
|
||||
@media @mobile {
|
||||
width: 100%;
|
||||
}
|
||||
overflow: visible;
|
||||
svg {
|
||||
width: 100%;
|
||||
transition: fill 1s ease;
|
||||
overflow: visible;
|
||||
}
|
||||
}
|
||||
@media @mobile {
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,5 @@
|
||||
<script lang="ts">
|
||||
import CoreSellingPoints from "../lib/components/staticPageRows/CoreSellingPoints.svelte"
|
||||
</script>
|
||||
|
||||
<CoreSellingPoints />
|
||||
|
||||
Reference in New Issue
Block a user