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

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