feat: enhance accessibility with skip to main content button and improve navigation handling

🔧 fix: update navigation href resolution to include localized paths

🆕 feat: add new FeatureIcon component for feature boxes

🎨 style: improve styling for prose elements in richtext blocks

🛠️ refactor: streamline medialib image loading and caching logic

📦 chore: update mock data handling to support new medialib entries

🔄 chore: synchronize i18n initialization and locale management

📝 docs: update video tour descriptions to reflect recent changes
This commit is contained in:
2026-05-12 13:55:32 +00:00
parent 8fb26fdeba
commit e84b87ed16
41 changed files with 1523 additions and 338 deletions
+99
View File
@@ -0,0 +1,99 @@
<script lang="ts">
let { icon = "lightning", className = "" }: { icon?: ContentFeatureBoxEntry["icon"]; className?: string } = $props()
</script>
{#if icon === "palette"}
<svg
class={className}
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="1.5"
stroke-linecap="round"
stroke-linejoin="round"
aria-hidden="true"
>
<path
d="M12 2C6.5 2 2 6.5 2 12s4.5 10 10 10c.93 0 1.5-.67 1.5-1.5 0-.39-.15-.74-.39-1.04-.23-.29-.38-.63-.38-1.01 0-.83.67-1.5 1.5-1.5H16c3.31 0 6-2.69 6-6 0-5.17-4.49-9-10-9z"
></path>
<circle cx="7.5" cy="11.5" r="1.5"></circle>
<circle cx="10.5" cy="7.5" r="1.5"></circle>
<circle cx="14.5" cy="7.5" r="1.5"></circle>
<circle cx="17.5" cy="11.5" r="1.5"></circle>
</svg>
{:else if icon === "database"}
<svg
class={className}
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="1.5"
stroke-linecap="round"
stroke-linejoin="round"
aria-hidden="true"
>
<path d="M12 22v-5"></path>
<path d="M9 8V2"></path>
<path d="M15 8V2"></path>
<path d="M18 8v5a6 6 0 0 1-12 0V8h12z"></path>
</svg>
{:else if icon === "globe"}
<svg
class={className}
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="1.5"
stroke-linecap="round"
stroke-linejoin="round"
aria-hidden="true"
>
<circle cx="12" cy="12" r="10"></circle>
<path d="M2 12h20"></path>
<path d="M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z"></path>
</svg>
{:else if icon === "monitor"}
<svg
class={className}
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="1.5"
stroke-linecap="round"
stroke-linejoin="round"
aria-hidden="true"
>
<rect x="2" y="3" width="20" height="14" rx="2"></rect>
<path d="M8 21h8"></path>
<path d="M12 17v4"></path>
</svg>
{:else if icon === "flask"}
<svg
class={className}
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="1.5"
stroke-linecap="round"
stroke-linejoin="round"
aria-hidden="true"
>
<path d="M9 2h6"></path>
<path
d="M10 2v7.527a2 2 0 0 1-.211.896L4.72 20.578A1 1 0 0 0 5.598 22h12.804a1 1 0 0 0 .878-1.422l-5.069-10.155A2 2 0 0 1 14 9.527V2"
></path>
</svg>
{:else}
<svg
class={className}
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="1.5"
stroke-linecap="round"
stroke-linejoin="round"
aria-hidden="true"
>
<path d="M13 2L3 14h9l-1 8 10-12h-9l1-8z"></path>
</svg>
{/if}
+17 -4
View File
@@ -1,5 +1,6 @@
<script lang="ts">
import { reveal } from "../lib/actions/reveal"
import FeatureIcon from "./FeatureIcon.svelte"
let { block }: { block: ContentBlockEntry } = $props()
@@ -41,10 +42,22 @@
</div>
{/if}
{#if block.text}
<div class="prose max-w-none" use:reveal={{ delay: 200 }}>
{@html block.text}
{#if block.featureBoxes?.length}
<div class="grid gap-8 sm:grid-cols-2 lg:grid-cols-3" use:reveal={{ delay: 200 }}>
{#each block.featureBoxes as item}
<article class="feature-card">
<div class="feature-icon">
<FeatureIcon icon={item.icon} className="w-10 h-10" />
</div>
{#if item.title}
<h3>{item.title}</h3>
{/if}
{#if item.text}
<p>{item.text}</p>
{/if}
</article>
{/each}
</div>
{/if}
</div>
</section>
</section>
+13 -4
View File
@@ -1,10 +1,16 @@
<script lang="ts">
import { reveal } from "../lib/actions/reveal"
import { spaLink } from "../lib/navigation"
import MedialibImage from "../widgets/MedialibImage.svelte"
let { block }: { block: ContentBlockEntry } = $props()
const hasImage = $derived(block.heroImage?.externalUrl || block.heroImage?.image)
const resolvedHeroImage = $derived(
block.heroImage?._lookup?.image ||
(block._lookup?.["heroImage.image"] as MedialibEntry | null | undefined) ||
null
)
const hasImage = $derived(!!resolvedHeroImage?.file?.src)
const isAnchorLink = $derived(block.callToAction?.buttonLink?.startsWith("#"))
</script>
@@ -17,9 +23,12 @@
<!-- Background image -->
{#if hasImage}
<div class="absolute inset-0 z-0">
{#if block.heroImage?.externalUrl}
<img src={block.heroImage.externalUrl} alt="" class="w-full h-full object-cover" loading="lazy" />
{/if}
<MedialibImage
id={block.heroImage?.image || resolvedHeroImage?.id || resolvedHeroImage?._id || ""}
entry={resolvedHeroImage}
noPlaceholder
style="width:100%;height:100%;object-fit:cover;"
/>
<div class="absolute inset-0 bg-linear-to-b from-brand-950/80 via-brand-900/70 to-brand-950/90"></div>
</div>
{:else}
+13 -8
View File
@@ -1,8 +1,11 @@
<script lang="ts">
import { reveal } from "../lib/actions/reveal"
import MedialibImage from "../widgets/MedialibImage.svelte"
let { block }: { block: ContentBlockEntry } = $props()
const resolvedImage = $derived(block._lookup?.image || null)
const paddingTop = $derived(
block.padding?.top === "lg"
? "pt-20"
@@ -22,10 +25,10 @@
: "pb-4"
)
const hasImage = $derived(block.externalImageUrl || block.image)
const hasImage = $derived(block.image || resolvedImage)
const imageOnRight = $derived(block.imagePosition === "right")
const imageOnLeft = $derived(block.imagePosition === "left")
const showImage = $derived(hasImage && (imageOnRight || imageOnLeft))
const showImage = $derived(hasImage && !!resolvedImage?.file?.src && (imageOnRight || imageOnLeft))
</script>
<section data-block="richtext" class="richtext-section {paddingTop} {paddingBottom}" id={block.anchorId || undefined}>
@@ -54,12 +57,14 @@
</div>
<div class:order-1={imageOnLeft} class="relative">
<div class="rounded-2xl overflow-hidden shadow-xl shadow-brand-900/10">
<img
src={block.externalImageUrl || ""}
alt={block.headline || ""}
class="w-full h-auto object-cover aspect-4/3"
loading="lazy"
/>
{#if block.image || resolvedImage}
<MedialibImage
id={block.image || resolvedImage?.id || resolvedImage?._id || ""}
entry={resolvedImage}
noPlaceholder
style="width:100%;height:auto;aspect-ratio:4/3;object-fit:cover;"
/>
{/if}
</div>
<!-- Decorative gradient behind image -->
<div