first row

This commit is contained in:
2025-10-02 17:27:06 +00:00
parent 9409de9103
commit eefa562cb1
29 changed files with 779 additions and 739 deletions

View File

@@ -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>

View File

@@ -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
View 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)

View File

@@ -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

View File

@@ -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 {

View File

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

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -0,0 +1,5 @@
<script lang="ts">
import CoreSellingPoints from "../lib/components/staticPageRows/CoreSellingPoints.svelte"
</script>
<CoreSellingPoints />