feat: add new contact form, hero, features, and richtext blocks; implement scroll-reveal action and update styles

- Introduced ContactFormBlock, FeaturesBlock, HeroBlock, and RichtextBlock components.
- Implemented a scroll-reveal action for animations on element visibility.
- Enhanced CSS styles for better theming and prose formatting.
- Added localization support for new components and updated existing translations.
- Created e2e tests for demo pages including contact form validation and navigation.
- Added a video tour showcasing the demo pages and interactions.
This commit is contained in:
2026-02-26 03:54:07 +00:00
parent e8fd38e98a
commit 40ffa8207e
27 changed files with 2009 additions and 98 deletions
+58
View File
@@ -0,0 +1,58 @@
/**
* Scroll-reveal action — fades elements in when they enter the viewport.
*
* Usage: <div use:reveal>...</div>
* <div use:reveal={{ delay: 200, threshold: 0.2 }}>...</div>
*
* The element gets the `.reveal` CSS class on mount and `.revealed` when
* it enters the viewport. Animation is defined in style.css.
*
* SSR-safe: no-ops when IntersectionObserver is unavailable.
*/
export interface RevealOptions {
/** Delay in ms before the animation starts (default: 0) */
delay?: number
/** IntersectionObserver threshold 01 (default: 0.15) */
threshold?: number
/** Only animate once, then disconnect (default: true) */
once?: boolean
}
export function reveal(node: HTMLElement, options: RevealOptions = {}) {
if (typeof IntersectionObserver === "undefined") return
const { delay = 0, threshold = 0.15, once = true } = options
node.classList.add("reveal")
const observer = new IntersectionObserver(
(entries) => {
for (const entry of entries) {
if (entry.isIntersecting) {
if (delay > 0) {
setTimeout(() => node.classList.add("revealed"), delay)
} else {
node.classList.add("revealed")
}
if (once) observer.unobserve(node)
} else if (!once) {
node.classList.remove("revealed")
}
}
},
{ threshold }
)
observer.observe(node)
return {
update(newOptions: RevealOptions) {
// Options are static for simplicity — recreate if needed
},
destroy() {
observer.unobserve(node)
observer.disconnect()
},
}
}
+2 -2
View File
@@ -21,8 +21,8 @@ export const LANGUAGE_LABELS: Record<SupportedLanguage, string> = {
* Example: { about: { de: "ueber-uns", en: "about" } }
*/
export const ROUTE_TRANSLATIONS: Record<string, Record<SupportedLanguage, string>> = {
// Add your route translations here:
// about: { de: "ueber-uns", en: "about" },
about: { de: "ueber-uns", en: "about" },
contact: { de: "kontakt", en: "contact" },
}
export const getLocalizedRoute = (canonicalRoute: string, lang: SupportedLanguage): string => {
+33 -1
View File
@@ -16,8 +16,40 @@
"contact": {
"title": "Kontakt",
"text": "Hier kannst du ein Kontaktformular oder Kontaktdaten anzeigen."
},
"notFound": {
"title": "Seite nicht gefunden",
"text": "Die Seite, die du suchst, existiert leider nicht.",
"backHome": "Zur Startseite"
}
},
"form": {
"name": "Name",
"email": "E-Mail",
"subject": "Betreff",
"message": "Nachricht",
"send": "Nachricht senden",
"success": "Vielen Dank! Deine Nachricht wurde gesendet.",
"error": "Leider ist ein Fehler aufgetreten. Bitte versuche es erneut.",
"selectSubject": "Bitte wählen…",
"subjects": {
"general": "Allgemeine Anfrage",
"project": "Projektanfrage",
"support": "Technischer Support",
"feedback": "Feedback"
},
"validation": {
"required": "Dieses Feld ist erforderlich.",
"email": "Bitte gib eine gültige E-Mail-Adresse ein."
}
},
"footer": {
"builtWith": "Gebaut mit",
"rights": "Alle Rechte vorbehalten.",
"madeWith": "Gemacht mit ♥ und Svelte"
},
"welcome": "Willkommen",
"language": "Sprache"
"language": "Sprache",
"scrollToTop": "Nach oben",
"loading": "Laden…"
}
+33 -1
View File
@@ -16,8 +16,40 @@
"contact": {
"title": "Contact",
"text": "Use this page to display a contact form or contact details."
},
"notFound": {
"title": "Page not found",
"text": "The page you're looking for doesn't exist.",
"backHome": "Back to Home"
}
},
"form": {
"name": "Name",
"email": "Email",
"subject": "Subject",
"message": "Message",
"send": "Send message",
"success": "Thank you! Your message has been sent.",
"error": "Sorry, something went wrong. Please try again.",
"selectSubject": "Please select…",
"subjects": {
"general": "General inquiry",
"project": "Project inquiry",
"support": "Technical support",
"feedback": "Feedback"
},
"validation": {
"required": "This field is required.",
"email": "Please enter a valid email address."
}
},
"footer": {
"builtWith": "Built with",
"rights": "All rights reserved.",
"madeWith": "Made with ♥ and Svelte"
},
"welcome": "Welcome",
"language": "Language"
"language": "Language",
"scrollToTop": "Scroll to top",
"loading": "Loading…"
}