add components refinements, accessibility quick wins

This commit is contained in:
Alisa Cantillo-Olsson 2026-05-06 18:44:50 +02:00
parent 28803b266a
commit 92cdd44af3
15 changed files with 342 additions and 310 deletions

View File

@ -1,5 +1,5 @@
<!doctype html>
<html lang="en">
<html lang="de">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />

View File

@ -5,9 +5,12 @@ import ProgrammDetail from "./pages/ProgrammDetail";
import Speaker from "./pages/Speaker";
import SpeakerDetail from "./pages/SpeakerDetail";
import Tickets from "./pages/Tickets";
import { ScrollToTop } from "./components/ScrollToTop";
function App() {
return (
<>
<ScrollToTop />
<Routes>
<Route path="/" element={<Home />} />
<Route path="/programm" element={<Programm />} />
@ -16,6 +19,7 @@ function App() {
<Route path="/speaker/:slug" element={<SpeakerDetail />} />
<Route path="/tickets" element={<Tickets />} />
</Routes>
</>
);
}

View File

@ -39,8 +39,9 @@ export const Cta30 = (props: Cta30Props) => {
className="rb-4 mb-4 grid max-w-sm grid-cols-1 gap-y-3 sm:grid-cols-[1fr_max-content] sm:gap-4"
onSubmit={handleSubmit}
>
<label htmlFor="cta-email" className="sr-only">E-Mail-Adresse</label>
<Input
id="email"
id="cta-email"
type="email"
placeholder={inputPlaceholder}
value={emailInput}

View File

@ -4,6 +4,7 @@ import speaker3 from "../assets/speaker3.png";
import * as React from "react";
import type { ButtonProps } from "@relume_io/relume-ui";
import { Button } from "@relume_io/relume-ui";
import { Link } from "react-router-dom";
//import { RxChevronRight } from "react-icons/rx";
//import { BiMap, BiCalendarAlt } from "react-icons/bi";
@ -34,7 +35,7 @@ type Props = {
tagline: string;
heading: string;
description: string;
button: ButtonProps;
button: ButtonProps & { url?: string };
featuredEvents: FeaturedEvent[];
};
@ -49,8 +50,8 @@ export const Event17 = (props: Event17Props) => {
<section id="relume" className="px-[5%] py-16 md:py-24 lg:py-28 bg-tech-navy font-barlow">
<div className="container">
<div className="mx-auto mb-12 max-w-lg text-center md:mb-18 lg:mb-20">
<h4 className="font-semibold">{tagline}</h4>
<h1 className="mt-3 text-5xl font-bold md:mt-4 md:text-7xl lg:text-8xl text-acid-lime">{heading}</h1>
{tagline && <p className="font-semibold">{tagline}</p>}
<h2 className="mt-3 text-5xl font-bold md:mt-4 md:text-7xl lg:text-8xl text-acid-lime">{heading}</h2>
<p className="mt-5 text-base md:mt-6 md:text-md text-cloud-white whitespace-pre-wrap">{description}</p>
</div>
<div className="grid auto-cols-fr grid-cols-1 gap-x-8 gap-y-12 md:grid-cols-2 md:gap-y-16 lg:grid-cols-3">
@ -59,7 +60,13 @@ export const Event17 = (props: Event17Props) => {
))}
</div>
<div className="mt-12 flex justify-center md:mt-18 lg:mt-20">
{button.url ? (
<Link to={button.url}>
<Button {...button} className="bg-acid-lime text-tech-navy font-semibold px-6 py-3 rounded-2xl border-acid-lime">{button.title}</Button>
</Link>
) : (
<Button {...button} className="bg-acid-lime text-tech-navy font-semibold px-6 py-3 rounded-2xl border-acid-lime">{button.title}</Button>
)}
</div>
</div>
</section>
@ -83,7 +90,7 @@ export const Event17Defaults: Props = {
tagline: "",
heading: "Speaker:innen",
description: "Überwältigt von unserem Programm?\nLerne unsere diesjährigen Speakerinnen und Speaker kennen.",
button: { variant: "primary", size: "primary", title: "Zu den Speakern" },
button: { variant: "primary", size: "primary", title: "Zu den Speakern", url: "/speaker" },
featuredEvents: [
{
url: "/speaker", image: { src: speaker1, alt: "Dr. Elena Rossi" },

View File

@ -51,7 +51,7 @@ export const Event20 = (props: Event20Props) => {
<div className="mb-12 grid auto-cols-fr grid-cols-1 items-end gap-12 md:mb-18 md:grid-cols-[1fr_max-content] lg:mb-20 lg:gap-20">
<div className="max-w-lg">
<p className="mb-3 font-semibold md:mb-4">{tagline}</p>
<h1 className="mb-3 text-5xl font-bold md:mb-4 md:text-7xl lg:text-8xl text-cloud-white">{heading}</h1>
<h2 className="mb-3 text-5xl font-bold md:mb-4 md:text-7xl lg:text-8xl text-cloud-white">{heading}</h2>
<p className="md:text-md text-cloud-white">{description}</p>
</div>
<Button {...button} className="hidden md:flex bg-acid-lime text-tech-navy font-semibold px-6 py-3 rounded-2xl border-acid-lime">

View File

@ -21,6 +21,7 @@ type Links = {
type SocialMediaLinks = {
url: string;
icon: React.ReactNode;
label: string;
};
type ColumnLinks = {
@ -80,7 +81,7 @@ export const Footer3 = (props: Footer3Props) => {
</div>
<div className="grid grid-flow-col grid-cols-[max-content] items-start justify-start gap-x-3">
{socialMediaLinks.map((link, index) => (
<a key={index} href={link.url}>
<a key={index} href={link.url} aria-label={link.label}>
{link.icon}
</a>
))}
@ -134,24 +135,24 @@ export const Footer3Defaults: Props = {
links: [
{ title: "Programm", url: "/programm" },
{ title: "Speaker", url: "/speaker" },
{ title: "Location", url: "#" },
{ title: "Tickets", url: "#tickets" },
{ title: "FAQ", url: "#faq" },
{ title: "Location", url: "/location" },
{ title: "Tickets", url: "/tickets" },
{ title: "FAQ", url: "/programm#faq" },
],
},
{ links: [] },
],
socialMediaLinks: [
{ url: "#", icon: <BiLogoFacebookCircle className="size-6 text-neutral-dark" /> },
{ url: "#", icon: <BiLogoInstagram className="size-6 text-neutral-dark" /> },
{ url: "#", icon: <FaXTwitter className="size-6 p-0.5 text-neutral-dark" /> },
{ url: "#", icon: <BiLogoLinkedinSquare className="size-6 text-neutral-dark" /> },
{ url: "#", icon: <BiLogoYoutube className="size-6 text-neutral-dark" /> },
{ url: "#", label: "UXplore auf Facebook", icon: <BiLogoFacebookCircle className="size-6 text-neutral-dark" /> },
{ url: "#", label: "UXplore auf Instagram", icon: <BiLogoInstagram className="size-6 text-neutral-dark" /> },
{ url: "#", label: "UXplore auf X (Twitter)", icon: <FaXTwitter className="size-6 p-0.5 text-neutral-dark" /> },
{ url: "#", label: "UXplore auf LinkedIn", icon: <BiLogoLinkedinSquare className="size-6 text-neutral-dark" /> },
{ url: "#", label: "UXplore auf YouTube", icon: <BiLogoYoutube className="size-6 text-neutral-dark" /> },
],
footerText: "© 2026 UXplore",
footerLinks: [
{ title: "AGB", url: "#" },
{ title: "Datenschutz", url: "#" },
{ title: "Cookie Einstellungen", url: "#" },
{ title: "AGB", url: "/agb" },
{ title: "Datenschutz", url: "/datenschutz" },
{ title: "Cookie Einstellungen", url: "/cookie-einstellungen" },
],
};

View File

@ -37,12 +37,15 @@ export const Navbar3 = (props: Navbar3Props) => {
const isMobile = useMediaQuery("(max-width: 991px)");
return (
<section
<nav
id="relume"
className="z-[999] grid w-full grid-cols-[1fr_max-content_1fr] items-center justify-between border-b border-neutral-dark bg-tech-navy px-[5%] md:min-h-18 font-barlow"
aria-label="Hauptnavigation"
className="sticky top-0 z-[999] grid w-full grid-cols-[1fr_max-content_1fr] items-center justify-between border-b border-neutral-dark bg-tech-navy px-[5%] md:min-h-18 font-barlow"
>
<button
className="flex size-12 flex-col justify-center lg:hidden"
aria-label={isMobileMenuOpen ? "Menü schließen" : "Menü öffnen"}
aria-expanded={isMobileMenuOpen}
onClick={() => setIsMobileMenuOpen((prev) => !prev)}
>
{Array(3)
@ -134,7 +137,7 @@ export const Navbar3 = (props: Navbar3Props) => {
)}
</div>
</div>
</section>
</nav>
);
};
@ -209,7 +212,7 @@ export const Navbar3Defaults: Props = {
navLinks: [
{ title: "Programm", url: "/programm" },
{ title: "Speaker", url: "/speaker" },
{ title: "Location", url: "#" },
{ title: "Location", url: "/location" },
],
buttons: [
{

View File

@ -0,0 +1,10 @@
import { useEffect } from "react";
import { useLocation } from "react-router-dom";
export const ScrollToTop = () => {
const { pathname } = useLocation();
useEffect(() => {
window.scrollTo(0, 0);
}, [pathname]);
return null;
};

View File

@ -16,6 +16,7 @@ type ImageProps = {
type SocialLink = {
href: string;
icon: React.ReactNode;
label: string;
};
type Talk = {
@ -67,7 +68,7 @@ const MemberCard = ({ member }: { member: TeamMember }) => (
{member.socialLinks.length > 0 && (
<div className="flex gap-2 shrink-0">
{member.socialLinks.map((link, i) => (
<a key={i} href={link.href}>{link.icon}</a>
<a key={i} href={link.href} aria-label={link.label}>{link.icon}</a>
))}
</div>
)}
@ -95,8 +96,8 @@ export const Team4 = (props: Team4Props) => {
<p className="py-16 text-center text-gray-400">Keine Speaker gefunden.</p>
) : (
<div className="grid grid-cols-1 gap-x-8 gap-y-12 sm:grid-cols-2 lg:grid-cols-4">
{teamMembers.map((member, i) => (
<MemberCard key={i} member={member} />
{teamMembers.map((member) => (
<MemberCard key={member.slug} member={member} />
))}
</div>
)}
@ -124,7 +125,7 @@ export const Team4Defaults: Props = {
{ title: "Talk: Scaling Research", type: "Talk" },
{ title: "Workshop: Skalierbare Research-Infrastruktur", type: "Workshop" },
],
socialLinks: [{ href: "#", icon: <BiLogoLinkedinSquare className="size-6" /> }],
socialLinks: [{ href: "#", label: "LinkedIn-Profil", icon: <BiLogoLinkedinSquare className="size-6" /> }],
},
{
image: { src: speaker2, alt: "Marcus J. Low" },
@ -139,7 +140,7 @@ export const Team4Defaults: Props = {
"Marcus J. Low ist Principal Designer bei Airbnb und war massgeblich an der Entwicklung des ersten Airbnb Design Systems beteiligt. Er spricht über die Balance zwischen Markenästhetik und funktionaler Logik und teilt seine Erfahrungen aus über 10 Jahren in der Produktgestaltung für globale Plattformen.",
category: "Design / UX",
talks: [{ title: "Workshop: The Future of Tokens", type: "Workshop" }],
socialLinks: [{ href: "#", icon: <BiLogoLinkedinSquare className="size-6" /> }],
socialLinks: [{ href: "#", label: "LinkedIn-Profil", icon: <BiLogoLinkedinSquare className="size-6" /> }],
},
{
image: { src: speaker3, alt: "Reto Gwerder" },
@ -154,7 +155,7 @@ export const Team4Defaults: Props = {
"Reto Gwerder ist Head of Product bei Ginetta und steht für konsequente Schweizer Design-Qualität. Er analysiert, warum Simplizität oft die größte technische Herausforderung ist und wie man in einem schnellen Produktumfeld trotzdem Qualität bewahrt.",
category: "Strategie",
talks: [{ title: "Talk: Simplify or Die", type: "Talk" }],
socialLinks: [{ href: "#", icon: <BiLogoLinkedinSquare className="size-6" /> }],
socialLinks: [{ href: "#", label: "LinkedIn-Profil", icon: <BiLogoLinkedinSquare className="size-6" /> }],
},
{
image: { src: speaker4, alt: "Dr. Elena Rossi" },
@ -169,7 +170,7 @@ export const Team4Defaults: Props = {
"Dr. Elena Rossi ist Kognitionspsychologin an der Universität Mailand und verbindet Wissenschaft mit Design. Sie erklärt, wie unser Gehirn auf Micro-Interactions reagiert und wann wir uns manipuliert fühlen. Elena forscht seit über einem Jahrzehnt zur Schnittstelle von Psychologie und digitaler Produktgestaltung.",
category: "Psychologie",
talks: [{ title: "Deep Dive: Psychological UX", type: "Talk" }],
socialLinks: [{ href: "#", icon: <BiLogoLinkedinSquare className="size-6" /> }],
socialLinks: [{ href: "#", label: "LinkedIn-Profil", icon: <BiLogoLinkedinSquare className="size-6" /> }],
},
{
image: { src: speaker5, alt: "Liam O'Connor" },
@ -184,7 +185,7 @@ export const Team4Defaults: Props = {
"Liam O'Connor ist Lead Product Designer bei Spotify und gibt Einblicke, wie das Unternehmen Personalisierung nutzt, ohne die Privatsphäre der Nutzer zu verletzen. Er arbeitet an der Schnittstelle von Daten, Design und Ethik.",
category: "AI & Future",
talks: [{ title: "Talk: Trust in AI UX", type: "Talk" }],
socialLinks: [{ href: "#", icon: <BiLogoLinkedinSquare className="size-6" /> }],
socialLinks: [{ href: "#", label: "LinkedIn-Profil", icon: <BiLogoLinkedinSquare className="size-6" /> }],
},
{
image: { src: speaker6, alt: "Fabienne Keller" },
@ -199,7 +200,7 @@ export const Team4Defaults: Props = {
"Fabienne Keller ist Gründerin der User-First Agency und Expertin für User Research in komplexen B2B-Umfeldern. Sie zeigt, wie man Stakeholder von Test-Ergebnissen überzeugt und eine nachhaltige Research-Kultur in Unternehmen aufbaut.",
category: "Research",
talks: [{ title: "Workshop: Research Repositories", type: "Workshop" }],
socialLinks: [{ href: "#", icon: <BiLogoLinkedinSquare className="size-6" /> }],
socialLinks: [{ href: "#", label: "LinkedIn-Profil", icon: <BiLogoLinkedinSquare className="size-6" /> }],
},
],
};

View File

@ -10,11 +10,13 @@ export const Home = () => {
return (
<div>
<Navbar3 />
<main>
<Header1 />
<Content11 />
<Event17 />
<Event20 />
<Cta30 />
</main>
<Footer3 />
</div>
);

View File

@ -66,6 +66,7 @@ const Programm = () => {
return (
<div>
<Navbar3 />
<main>
<Header23 />
<Filters5
activeTabs={activeTabs}
@ -79,6 +80,7 @@ const Programm = () => {
/>
<Event33 events={filteredEvents} />
<Banner15 />
</main>
<Footer3 />
</div>
);

View File

@ -50,7 +50,7 @@ const ProgrammDetail = () => {
return (
<div>
<Navbar3 />
<main>
{/* Hero section */}
<section className="bg-tech-navy px-[5%] py-16 md:py-24 font-barlow">
<div className="container">
@ -71,7 +71,7 @@ const ProgrammDetail = () => {
<BiCalendarAlt className="size-5 shrink-0 text-acid-lime" />
<span className="text-sm">{dateLabel}, {event.time}</span>
</div>
<div className="flex items-center gap-3 text-cloud-white/80">
<div className="flex items-center gap-3 text-cloud-white/80">
<BiUser className="size-5 shrink-0 text-acid-lime" />
<span className="text-sm">{event.speaker}</span>
</div>
@ -112,13 +112,13 @@ const ProgrammDetail = () => {
<p className="mb-1 text-sm text-cloud-white/60">{speaker.role}</p>
<p className="mb-4 text-sm text-cloud-white/60">{speaker.company}</p>
<div className="flex gap-3 mb-6">
<a href="#" className="text-cloud-white/60 hover:text-acid-lime transition-colors">
<a href="#" aria-label={`${speaker.name} auf LinkedIn`} className="text-cloud-white/60 hover:text-acid-lime transition-colors">
<BiLogoLinkedinSquare className="size-6" />
</a>
<a href="#" className="text-cloud-white/60 hover:text-acid-lime transition-colors">
<a href="#" aria-label={`${speaker.name} auf Xing`} className="text-cloud-white/60 hover:text-acid-lime transition-colors">
<SiXing className="size-5" />
</a>
<a href="#" className="text-cloud-white/60 hover:text-acid-lime transition-colors">
<a href="#" aria-label={`${speaker.name} auf Dribbble`} className="text-cloud-white/60 hover:text-acid-lime transition-colors">
<BiLogoDribbble className="size-6" />
</a>
</div>
@ -134,7 +134,7 @@ const ProgrammDetail = () => {
)}
</div>
{/* Right: das erwartete dich */}
{/* Right: das erwartet dich */}
<div>
<RxCube className="mb-4 size-8 text-electric-violet" />
<h2 className="mb-6 text-2xl font-bold text-cloud-white md:text-3xl">
@ -148,12 +148,9 @@ const ProgrammDetail = () => {
</div>
</section>
{/* Stay tuned */}
<Cta30 />
{/* Sponsors */}
<SponsorsSection />
</main>
<Footer3 />
</div>
);

View File

@ -47,6 +47,7 @@ const Speaker = () => {
return (
<div>
<Navbar3 />
<main>
<Header23
heading="Unsere Speaker"
description="Erfahre von den klügsten Köpfen der Branche, wie sie komplexe Design-Herausforderungen meistern. Von Schweizer Präzision im Interface bis hin zu globalen Strategien führender Tech-Giganten – unsere Speaker bringen handfeste Insights statt nur Buzzwords."
@ -69,6 +70,7 @@ const Speaker = () => {
showSortButton={false}
/>
<Team4 teamMembers={filteredSpeakers} />
</main>
<Footer3 />
</div>
);

View File

@ -46,20 +46,12 @@ const SpeakerDetail = () => {
<div>
<Navbar3 />
{/* Hero image */}
<div className="w-full aspect-[16/7] overflow-hidden">
<img
src={(speaker.detailImage ?? speaker.image).src}
alt={(speaker.detailImage ?? speaker.image).alt}
className="size-full object-cover object-top"
/>
</div>
<main>
{/* Speaker info */}
<section className="bg-tech-navy px-[5%] py-16 md:py-24 font-barlow">
<div className="container">
<div className="grid grid-cols-1 gap-12 lg:grid-cols-2 lg:gap-20">
{/* Left: badge, name, location */}
<div className="grid grid-cols-1 items-center gap-12 lg:grid-cols-2 lg:gap-20">
{/* Left: badge, name, bio, social */}
<div>
<span className="mb-6 inline-block rounded-full bg-acid-lime px-4 py-1.5 text-sm font-bold text-tech-navy">
{speaker.category}
@ -70,21 +62,18 @@ const SpeakerDetail = () => {
<p className="text-base font-semibold text-cloud-white/80">{speaker.role}</p>
<p className="mb-2 text-base text-cloud-white/60">{speaker.company}</p>
{speaker.location && (
<p className="text-base text-cloud-white/60">{speaker.location}</p>
<p className="mb-6 text-base text-cloud-white/60">{speaker.location}</p>
)}
</div>
{/* Right: bio + social */}
<div className="flex flex-col justify-center gap-6">
<p className="text-base leading-relaxed text-cloud-white/80 md:text-lg">
{speaker.fullBio ?? speaker.description}
</p>
{speaker.socialLinks.length > 0 && (
<div className="flex gap-4">
<div className="mt-6 flex gap-4">
{speaker.socialLinks.map((link, i) => (
<a
key={i}
href={link.href}
aria-label={link.label}
className="text-cloud-white/60 hover:text-acid-lime transition-colors"
>
{link.icon}
@ -93,6 +82,15 @@ const SpeakerDetail = () => {
</div>
)}
</div>
{/* Right: speaker image */}
<div className="aspect-square w-full max-w-md overflow-hidden lg:ml-auto">
<img
src={(speaker.detailImage ?? speaker.image).src}
alt={(speaker.detailImage ?? speaker.image).alt}
className="size-full object-cover object-top"
/>
</div>
</div>
</div>
</section>
@ -143,6 +141,7 @@ const SpeakerDetail = () => {
</div>
<SponsorsSection />
</main>
<Footer3 />
</div>
);

View File

@ -77,6 +77,7 @@ const Tickets = () => {
return (
<div>
<Navbar3 />
<main>
<Header23
heading="Ticketshop"
description="Erfahre von den klügsten Köpfen der Branche, wie sie komplexe Design-Herausforderungen meistern. Von Schweizer Präzision im Interface bis hin zu globalen Strategien führender Tech-Giganten – unsere Speaker bringen handfeste Insights statt nur Buzzwords."
@ -99,8 +100,9 @@ const Tickets = () => {
className="mb-4 grid max-w-sm grid-cols-1 gap-y-3 sm:grid-cols-[1fr_max-content] sm:gap-4"
onSubmit={handleSubmit}
>
<label htmlFor="tickets-email" className="sr-only">E-Mail-Adresse</label>
<Input
id="email"
id="tickets-email"
type="email"
placeholder="Enter your email"
value={emailInput}
@ -116,11 +118,11 @@ const Tickets = () => {
</form>
<p className="text-xs text-cloud-white">
By clicking Get Updates you&apos;re confirming that you agree with our{" "}
<a href="#" className="underline">Terms and Conditions</a>.
<a href="/agb" className="underline">Terms and Conditions</a>.
</p>
</div>
</Header23>
<Pricing39Ticket
<Pricing39Ticket
heading="1-Tagespässe"
plans={[
{ day: "Donnerstag", date: "25. Juni 2026", earlyBirdPrice: 99, regularPrice: 131, earlyBirdDeadline: "31. Mai 2026" },
@ -131,6 +133,7 @@ const Tickets = () => {
heading="2-Tagespass"
plans={[{ day: "Donnerstag – Freitag", date: "25. – 26. Juni 2026", earlyBirdPrice: 179, regularPrice: 236, earlyBirdDeadline: "31. Mai 2026" }]}
/>
</main>
<Footer3 />
</div>
);