adjustments based on the coaching feedback

This commit is contained in:
Alisa Cantillo-Olsson 2026-05-02 16:43:37 +02:00
parent ed0d261056
commit 28803b266a
16 changed files with 421 additions and 278 deletions

View File

@ -0,0 +1 @@
/* /index.html 200

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 24 24"><path fill="currentColor" d="m13.966 22.624l-1.69-4.281H8.122l3.892-9.144l5.662 13.425zM8.884 1.376H0v21.248zm15.116 0h-8.884L24 22.624Z"/></svg>

After

Width:  |  Height:  |  Size: 230 B

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 24 24"><path fill="currentColor" d="M15.852 8.981h-4.588V0h4.588c2.476 0 4.49 2.014 4.49 4.49s-2.014 4.491-4.49 4.491M12.735 7.51h3.117c1.665 0 3.019-1.355 3.019-3.019s-1.355-3.019-3.019-3.019h-3.117zm0 1.471H8.148c-2.476 0-4.49-2.014-4.49-4.49S5.672 0 8.148 0h4.588v8.981zm-4.587-7.51c-1.665 0-3.019 1.355-3.019 3.019s1.354 3.02 3.019 3.02h3.117V1.471zm4.587 15.019H8.148c-2.476 0-4.49-2.014-4.49-4.49s2.014-4.49 4.49-4.49h4.588v8.98zM8.148 8.981c-1.665 0-3.019 1.355-3.019 3.019s1.355 3.019 3.019 3.019h3.117V8.981zM8.172 24c-2.489 0-4.515-2.014-4.515-4.49s2.014-4.49 4.49-4.49h4.588v4.441c0 2.503-2.047 4.539-4.563 4.539m-.024-7.51a3.023 3.023 0 0 0-3.019 3.019c0 1.665 1.365 3.019 3.044 3.019c1.705 0 3.093-1.376 3.093-3.068v-2.97zm7.704 0h-.098c-2.476 0-4.49-2.014-4.49-4.49s2.014-4.49 4.49-4.49h.098c2.476 0 4.49 2.014 4.49 4.49s-2.014 4.49-4.49 4.49m-.097-7.509c-1.665 0-3.019 1.355-3.019 3.019s1.355 3.019 3.019 3.019h.098c1.665 0 3.019-1.355 3.019-3.019s-1.355-3.019-3.019-3.019z"/></svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 24 24"><path fill="currentColor" d="M12.48 10.92v3.28h7.84c-.24 1.84-.853 3.187-1.787 4.133c-1.147 1.147-2.933 2.4-6.053 2.4c-4.827 0-8.6-3.893-8.6-8.72s3.773-8.72 8.6-8.72c2.6 0 4.507 1.027 5.907 2.347l2.307-2.307C18.747 1.44 16.133 0 12.48 0C5.867 0 .307 5.387.307 12s5.56 12 12.173 12c3.573 0 6.267-1.173 8.373-3.36c2.16-2.16 2.84-5.213 2.84-7.667c0-.76-.053-1.467-.173-2.053z"/></svg>

After

Width:  |  Height:  |  Size: 466 B

View File

@ -1,13 +1,11 @@
import React from "react";
type ImageProps = {
src: string;
alt?: string;
};
import logoFigma from "../assets/logo-figma.svg";
import logoAdobe from "../assets/logo-adobe.svg";
import logoGoogle from "../assets/logo-google.svg";
type Section = {
title: string;
image: ImageProps;
image?: { src: string; alt: string };
};
type Props = {
@ -17,12 +15,9 @@ type Props = {
export type Banner15Props = React.ComponentPropsWithoutRef<"section"> & Partial<Props>;
export const Banner15 = (props: Banner15Props) => {
const { sections } = {
...Banner15Defaults,
...props,
};
const { sections } = { ...Banner15Defaults, ...props };
return (
<section id="relume" className="flex w-screen max-w-full justify-end overflow-hidden bg-cloud-white">
<section className="flex w-screen max-w-full justify-end overflow-hidden bg-cloud-white">
<div className="flex justify-end">
{Array(2)
.fill(0)
@ -31,18 +26,20 @@ export const Banner15 = (props: Banner15Props) => {
key={index}
className="grid animate-marquee-right auto-cols-max grid-flow-col grid-cols-[max-content] items-center justify-around py-4"
>
{sections.map((section, index) => (
<React.Fragment key={index}>
{sections.map((section, i) => (
<React.Fragment key={i}>
<div className="flex items-center justify-center whitespace-nowrap px-8 text-center lg:text-left">
<h1 className="text-6xl font-bold md:text-9xl lg:text-10xl">{section.title}</h1>
</div>
<div className="relative aspect-[3/2] w-full overflow-hidden object-cover">
<img
src={section.image.src}
alt={section.image.alt}
className="aspect-[3/2] size-full h-16 max-h-24 object-cover md:h-auto"
/>
</div>
{section.image && (
<div className="relative flex items-center justify-center px-4">
<img
src={section.image.src}
alt={section.image.alt}
className="h-16 max-h-24 w-auto object-contain md:h-20"
/>
</div>
)}
</React.Fragment>
))}
</div>
@ -55,32 +52,21 @@ export const Banner15 = (props: Banner15Props) => {
export const Banner15Defaults: Props = {
sections: [
{
title: "UX Schweiz",
image: {
src: "https://relume-assets.s3.us-east-1.amazonaws.com/placeholder-image.svg",
alt: "UX Schweiz",
},
title: "Figma",
image: { src: logoFigma, alt: "Figma" },
},
{
title: "Adobe",
image: {
src: "https://relume-assets.s3.us-east-1.amazonaws.com/placeholder-image.svg",
alt: "Adobe",
},
image: { src: logoAdobe, alt: "Adobe" },
},
{
title: "Google",
image: {
src: "https://relume-assets.s3.us-east-1.amazonaws.com/placeholder-image.svg",
alt: "Google",
},
image: { src: logoGoogle, alt: "Google" },
},
{
title: "Figma",
image: {
src: "https://relume-assets.s3.us-east-1.amazonaws.com/placeholder-image.svg",
alt: "Figma",
},
title: "UX Schweiz",
},
],
};
};
export default Banner15;

View File

@ -29,8 +29,11 @@ const levelStyles: Record<EventLevel, string> = {
};
const EventRow = ({ event }: { event: ProgramEvent }) => (
<div className="grid grid-cols-1 items-center gap-4 border-t border-gray-200 py-6 last-of-type:border-b md:grid-cols-[5.5rem_5.5rem_1fr_max-content_max-content] md:gap-6 md:py-7">
<span className="text-sm font-semibold text-tech-navy">{event.time}</span>
<div className="grid grid-cols-1 items-center gap-4 border-t border-gray-200 py-6 last-of-type:border-b md:grid-cols-[8rem_5.5rem_1fr_max-content] md:gap-6 md:py-7">
<div className="flex flex-col gap-0.5">
<span className="text-xs font-bold uppercase tracking-wide text-electric-violet">{event.day}</span>
<span className="text-sm font-semibold text-tech-navy">{event.time}</span>
</div>
<div className="w-14 h-14 shrink-0 md:w-[5.5rem] md:h-[5.5rem]">
<img src={event.image.src} alt={event.image.alt} className="size-full object-cover" />
@ -52,13 +55,6 @@ const EventRow = ({ event }: { event: ProgramEvent }) => (
>
Über {event.speaker.split(" ")[0]} {event.speaker.split(" ").slice(-1)[0]}
</a>
<a
href="/tickets"
className="shrink-0 rounded-full bg-electric-violet px-4 py-1.5 text-sm font-semibold text-cloud-white hover:opacity-90 transition-opacity"
>
Tickets kaufen
</a>
</div>
);
@ -75,6 +71,14 @@ export const Event33 = ({ events }: Props) => {
))}
</div>
)}
<div className="mt-10 flex justify-center">
<a
href="/tickets"
className="rounded-2xl bg-electric-violet px-8 py-3 text-base font-normal text-cloud-white hover:opacity-90 transition-opacity"
>
Jetzt Ticket sichern
</a>
</div>
</div>
</section>
);

View File

@ -1,31 +1,39 @@
import { useState } from "react";
import { BiFilter, BiSearch } from "react-icons/bi";
import { IoCloseOutline } from "react-icons/io5";
import { RxChevronDown } from "react-icons/rx";
import { useState } from "react";
export type FilterTab = { value: string; label: string };
type ActiveTag = { label: string; onRemove: () => void };
type Props = {
// Speaker page (single-select)
tabs?: FilterTab[];
activeTab?: string;
onTabChange?: (tab: string) => void;
// Programm page (multi-select)
activeTabs?: string[];
onTabsChange?: (tabs: string[]) => void;
// Shared
searchQuery?: string;
onSearchChange?: (q: string) => void;
searchLabel?: string;
resetLabel?: string;
activeTags?: ActiveTag[];
onReset?: () => void;
// Programm-only
useFilterToggle?: boolean;
showDayFilter?: boolean;
showSortButton?: boolean;
sortOrder?: "asc" | "desc";
onSortChange?: (order: "asc" | "desc") => void;
// Legacy day props (kept for backwards compat)
activeDay?: string;
onDayChange?: (day: string) => void;
};
const PROGRAMM_TABS: FilterTab[] = [
{ value: "all", label: "Alle Anzeigen" },
{ value: "Donnerstag", label: "Donnerstag" },
{ value: "Freitag", label: "Freitag" },
{ value: "Talk", label: "Talk" },
{ value: "Workshop", label: "Workshop" },
{ value: "Interaction", label: "Interaction" },
@ -35,16 +43,12 @@ const PROGRAMM_TABS: FilterTab[] = [
{ value: "Junior", label: "Junior-Level" },
];
const DAYS = [
{ value: "all", label: "Alle Tage" },
{ value: "Donnerstag", label: "Donnerstag" },
{ value: "Freitag", label: "Freitag" },
];
export const Filters5 = ({
tabs = PROGRAMM_TABS,
tabs,
activeTab = "all",
onTabChange = () => {},
activeTabs = [],
onTabsChange = () => {},
searchQuery = "",
onSearchChange = () => {},
searchLabel = "Stichwortsuche",
@ -52,40 +56,51 @@ export const Filters5 = ({
activeTags = [],
onReset = () => {},
useFilterToggle = true,
showDayFilter = true,
showSortButton = true,
activeDay = "all",
onDayChange = () => {},
sortOrder = "asc",
onSortChange = () => {},
}: Props) => {
const [sortOpen, setSortOpen] = useState(false);
const [dayOpen, setDayOpen] = useState(false);
const selectedDayLabel = DAYS.find((d) => d.value === activeDay)?.label ?? "Alle Tage";
const toggleTab = (value: string) => {
if (activeTabs.includes(value)) {
onTabsChange(activeTabs.filter((t) => t !== value));
} else {
onTabsChange([...activeTabs, value]);
}
};
const TabPills = () => (
<div className="flex items-center gap-1 overflow-x-auto no-scrollbar">
{tabs.map((tab) => (
<button
key={tab.value}
onClick={() => onTabChange(tab.value)}
className={`shrink-0 rounded-2xl px-4 py-2 text-base font-semibold transition-colors ${
activeTab === tab.value
? "bg-electric-violet text-cloud-white"
: "text-tech-navy hover:text-electric-violet"
}`}
>
{tab.label}
</button>
))}
</div>
);
// Speaker page: just tabs + search, no day filter or sort
// Speaker page: multi-select tabs + search
if (!useFilterToggle) {
const speakerTabs = tabs ?? [];
return (
<section className="bg-cloud-white font-barlow border-b border-gray-200 px-[5%] py-8">
<div className="container flex flex-col gap-6">
<TabPills />
<div className="flex items-center gap-1 overflow-x-auto no-scrollbar">
<button
onClick={() => onTabsChange([])}
className={`shrink-0 rounded-2xl px-4 py-2 text-base font-semibold transition-colors ${
activeTabs.length === 0
? "bg-electric-violet text-cloud-white"
: "text-tech-navy hover:text-electric-violet"
}`}
>
Alle ansehen
</button>
{speakerTabs.map((tab) => (
<button
key={tab.value}
onClick={() => toggleTab(tab.value)}
className={`shrink-0 rounded-2xl px-4 py-2 text-base font-semibold transition-colors ${
activeTabs.includes(tab.value)
? "bg-electric-violet text-cloud-white"
: "text-tech-navy hover:text-electric-violet"
}`}
>
{tab.label}
</button>
))}
</div>
<div className="flex flex-col gap-2">
<div className="flex items-center justify-between">
<span className="text-base font-medium text-tech-navy">{searchLabel}</span>
@ -93,17 +108,17 @@ export const Filters5 = ({
{resetLabel}
</button>
</div>
<div className="flex h-12 items-center gap-3 rounded-2xl border border-tech-navy px-3">
<BiSearch className="size-6 shrink-0 text-tech-navy" />
<div className="flex h-12 items-center gap-3 rounded-2xl border border-electric-violet px-3">
<BiSearch className="size-6 shrink-0 text-electric-violet" />
<input
className="flex-1 bg-transparent text-base text-tech-navy outline-none placeholder:text-tech-navy/40"
className="flex-1 bg-transparent text-base text-electric-violet outline-none placeholder:text-electric-violet/50"
placeholder="Suchen..."
value={searchQuery}
onChange={(e) => onSearchChange(e.target.value)}
/>
{searchQuery && (
<button onClick={() => onSearchChange("")}>
<IoCloseOutline className="size-5 text-tech-navy" />
<IoCloseOutline className="size-5 text-electric-violet" />
</button>
)}
</div>
@ -128,20 +143,43 @@ export const Filters5 = ({
);
}
// Programm page: full filter bar, always visible
// Programm page: multi-select filter bar
return (
<section className="bg-cloud-white font-barlow px-[5%] py-8">
<div className="container flex flex-col gap-8">
{/* Row 1: Filter label + category pills + sort */}
{/* Row 1: Filter label + multi-select pills + sort */}
<div className="flex items-center gap-4">
<div className="flex shrink-0 items-center gap-3 rounded-2xl bg-electric-violet px-6 py-3 text-base font-semibold text-cloud-white">
<div className="flex shrink-0 items-center gap-2 text-base font-semibold text-tech-navy">
<BiFilter className="size-6" />
Filter
</div>
<div className="flex flex-1 items-center overflow-x-auto no-scrollbar">
<TabPills />
<div className="flex flex-1 items-center gap-1 overflow-x-auto no-scrollbar">
{/* "Alle" pill — active only when nothing selected */}
<button
onClick={() => onTabsChange([])}
className={`shrink-0 rounded-2xl px-4 py-2 text-base font-semibold transition-colors ${
activeTabs.length === 0
? "bg-electric-violet text-cloud-white"
: "text-tech-navy hover:text-electric-violet"
}`}
>
Alle
</button>
{PROGRAMM_TABS.map((tab) => (
<button
key={tab.value}
onClick={() => toggleTab(tab.value)}
className={`shrink-0 rounded-2xl px-4 py-2 text-base font-semibold transition-colors ${
activeTabs.includes(tab.value)
? "bg-electric-violet text-cloud-white"
: "text-tech-navy hover:text-electric-violet"
}`}
>
{tab.label}
</button>
))}
</div>
{showSortButton && (
@ -157,85 +195,48 @@ export const Filters5 = ({
</button>
{sortOpen && (
<div className="absolute right-0 top-9 z-50 w-48 overflow-hidden rounded-xl border border-gray-200 bg-white shadow-md">
{["Zeit aufsteigend", "Zeit absteigend"].map((opt) => (
<button
key={opt}
className="block w-full px-4 py-2.5 text-left text-sm text-tech-navy hover:bg-electric-violet/10"
onClick={() => setSortOpen(false)}
>
{opt}
</button>
))}
{(["asc", "desc"] as const).map((order) => {
const label = order === "asc" ? "Zeit aufsteigend" : "Zeit absteigend";
return (
<button
key={order}
className={`block w-full px-4 py-2.5 text-left text-sm transition-colors hover:bg-electric-violet/10 ${
sortOrder === order ? "font-semibold text-electric-violet" : "text-tech-navy"
}`}
onClick={() => { onSortChange(order); setSortOpen(false); }}
>
{label}
</button>
);
})}
</div>
)}
</div>
)}
</div>
{/* Row 2: Search + Day filter — always visible */}
<div className="grid grid-cols-1 gap-8 md:grid-cols-2">
{/* Search */}
<div className="flex flex-col gap-2">
<div className="flex items-center justify-between">
<span className="text-base font-medium text-tech-navy">{searchLabel}</span>
<button onClick={onReset} className="text-base font-medium text-tech-navy">
{resetLabel}
</button>
</div>
<div className="flex h-12 items-center gap-3 rounded-2xl border border-electric-violet px-3">
<BiSearch className="size-6 shrink-0 text-electric-violet" />
<input
className="flex-1 bg-transparent text-base text-electric-violet outline-none placeholder:text-electric-violet/50"
placeholder="Titel oder Speaker..."
value={searchQuery}
onChange={(e) => onSearchChange(e.target.value)}
/>
{searchQuery && (
<button onClick={() => onSearchChange("")}>
<IoCloseOutline className="size-5 text-electric-violet" />
</button>
)}
</div>
{/* Row 2: Full-width search */}
<div className="flex flex-col gap-2">
<div className="flex items-center justify-between">
<span className="text-base font-medium text-tech-navy">{searchLabel}</span>
<button onClick={onReset} className="text-base font-medium text-tech-navy">
{resetLabel}
</button>
</div>
<div className="flex h-12 items-center gap-3 rounded-2xl border border-electric-violet px-3">
<BiSearch className="size-6 shrink-0 text-electric-violet" />
<input
className="flex-1 bg-transparent text-base text-electric-violet outline-none placeholder:text-electric-violet/50"
placeholder="Titel oder Speaker..."
value={searchQuery}
onChange={(e) => onSearchChange(e.target.value)}
/>
{searchQuery && (
<button onClick={() => onSearchChange("")}>
<IoCloseOutline className="size-5 text-electric-violet" />
</button>
)}
</div>
{/* Day dropdown */}
{showDayFilter && (
<div className="flex flex-col gap-2">
<span className="text-base font-medium text-tech-navy">Filtern nach Eventtage</span>
<div className="relative">
<button
onClick={() => setDayOpen((v) => !v)}
className="flex h-12 w-full items-center justify-between gap-4 rounded-2xl border border-electric-violet px-4 text-base text-electric-violet"
>
<span>{selectedDayLabel}</span>
<RxChevronDown
className={`size-6 shrink-0 transition-transform ${dayOpen ? "rotate-180" : ""}`}
/>
</button>
{dayOpen && (
<div className="absolute left-0 top-[calc(100%+4px)] z-50 w-full overflow-hidden rounded-2xl border border-electric-violet bg-white shadow-md">
{DAYS.map((d) => (
<button
key={d.value}
className={`block w-full px-4 py-2.5 text-left text-base transition-colors hover:bg-electric-violet/10 ${
activeDay === d.value
? "font-semibold text-electric-violet"
: "text-tech-navy"
}`}
onClick={() => {
onDayChange(d.value);
setDayOpen(false);
}}
>
{d.label}
</button>
))}
</div>
)}
</div>
</div>
)}
</div>
{/* Active tags */}

View File

@ -1,43 +1,114 @@
import headerImage from "../assets/Header.png";
import { Button } from "@relume_io/relume-ui";
import type { ButtonProps } from "@relume_io/relume-ui";
import logoAdobe from "../assets/logo-adobe.svg";
import logoFigma from "../assets/logo-figma.svg";
import logoGoogle from "../assets/logo-google.svg";
type ImageProps = {
src: string;
alt?: string;
};
type Props = {
heading: string;
description: string;
buttons: ButtonProps[];
image: ImageProps;
};
export type Header1Props = React.ComponentPropsWithoutRef<"section"> & Partial<Props>;
export type Header1Props = React.ComponentPropsWithoutRef<"section">;
export const Header1 = (props: Header1Props) => {
const { heading, description, buttons, image } = {
...Header1Defaults,
...props,
};
return (
<section id="relume" className="px-[5%] py-16 md:py-24 lg:py-28 bg-tech-navy font-barlow">
<div className="container">
<div className="grid grid-cols-1 gap-x-20 gap-y-12 md:gap-y-16 lg:grid-cols-2 lg:items-center">
<div>
<h1 className="mb-5 text-6xl font-bold md:mb-6 md:text-9xl lg:text-10xl text-acid-lime whitespace-pre-wrap">{heading}</h1>
<p className="text-[48px] font-bold leading-[1.2] text-cloud-white whitespace-pre-wrap">{description}</p>
<div className="mt-6 flex flex-wrap gap-4 md:mt-8">
{buttons.map((button, index) => (
<Button key={index} {...button}>
{button.title}
</Button>
<section
className="relative flex min-h-[92vh] flex-col overflow-hidden bg-tech-navy font-barlow"
{...props}
>
{/* Mobile-only faint background image */}
<div className="absolute inset-0 lg:hidden pointer-events-none">
<img src={headerImage} alt="" className="h-full w-full object-cover opacity-10" />
<div className="absolute inset-0 bg-gradient-to-b from-tech-navy/60 to-tech-navy" />
</div>
{/* Desktop right-side image panel */}
<div className="absolute right-0 top-0 hidden h-full w-[52%] lg:block pointer-events-none">
<img
src={headerImage}
alt=""
className="h-full w-full object-cover opacity-90"
/>
{/* Blend edge left */}
<div className="absolute inset-y-0 left-0 w-56 bg-gradient-to-r from-tech-navy to-transparent" />
{/* Top vignette */}
<div className="absolute inset-x-0 top-0 h-40 bg-gradient-to-b from-tech-navy to-transparent" />
{/* Bottom vignette */}
<div className="absolute inset-x-0 bottom-0 h-28 bg-gradient-to-t from-tech-navy to-transparent" />
</div>
{/* Glow blobs */}
<div className="absolute -left-24 top-1/4 h-[480px] w-[480px] rounded-full bg-electric-violet/20 blur-3xl pointer-events-none" />
<div className="absolute left-1/3 top-0 h-[280px] w-[280px] rounded-full bg-electric-violet/10 blur-3xl pointer-events-none" />
<div className="absolute bottom-0 left-0 h-[220px] w-[460px] rounded-full bg-acid-lime/5 blur-3xl pointer-events-none" />
{/* Main content */}
<div className="relative z-10 flex flex-1 items-center px-[5%] py-24 md:py-32">
<div className="container">
<div className="mx-auto max-w-xl text-center lg:mx-0 lg:text-left">
{/* Date badge */}
<div className="mb-8 flex items-center justify-center gap-3 lg:justify-start">
<div className="h-px w-10 bg-acid-lime/60" />
<span className="text-xs font-semibold uppercase tracking-[0.2em] text-acid-lime">
25. 26. Juni 2026 &nbsp;·&nbsp; Zürich
</span>
<div className="h-px w-10 bg-acid-lime/60" />
</div>
{/* Heading */}
<h1 className="mb-3 text-[80px] font-bold leading-none tracking-tight text-acid-lime md:text-[110px] lg:text-[130px]">
UXplore
</h1>
<p className="mb-6 text-3xl font-bold text-cloud-white md:text-4xl">
Konferenz 2026
</p>
{/* Tagline */}
<p className="mb-10 max-w-md text-base leading-relaxed text-cloud-white/60 md:text-lg lg:mx-0 mx-auto">
Zwei Tage, zwölf Sessions, ein Ziel: UX, das wirklich wirkt. Die führende Schweizer Konferenz für Designer, Produktteams und alle, die nutzerzentriertes Denken in die Praxis bringen.
</p>
{/* CTAs */}
<div className="flex flex-wrap items-center justify-center gap-4 lg:justify-start">
<a
href="/tickets"
className="rounded-2xl bg-acid-lime px-8 py-3 text-base font-semibold text-tech-navy transition-opacity hover:opacity-90"
>
Jetzt Ticket sichern
</a>
<a
href="/programm"
className="rounded-2xl border border-cloud-white/25 px-8 py-3 text-base font-semibold text-cloud-white transition-colors hover:border-cloud-white/50 hover:bg-white/5"
>
Programm ansehen
</a>
</div>
{/* Stats */}
<div className="mt-14 flex flex-wrap items-center justify-center gap-8 lg:justify-start md:gap-12">
{[
{ value: "2", label: "Tage" },
{ value: "12+", label: "Sessions" },
{ value: "6", label: "Workshops" },
{ value: "500+", label: "Teilnehmer" },
].map((stat, i) => (
<div key={i} className="text-center lg:text-left">
<div className="text-2xl font-bold text-acid-lime md:text-3xl">{stat.value}</div>
<div className="mt-0.5 text-[11px] uppercase tracking-widest text-cloud-white/40">{stat.label}</div>
</div>
))}
</div>
</div>
<div>
<img src={image.src} className="w-full object-cover" alt={image.alt} />
</div>
</div>
{/* Speaker companies bar */}
<div className="relative z-10 border-t border-white/10 px-[5%] py-5">
<div className="container">
<div className="flex flex-wrap items-center justify-center gap-6 md:gap-10 lg:justify-start">
<span className="text-[10px] font-semibold uppercase tracking-[0.18em] text-cloud-white/30">
Speaker von
</span>
<img src={logoAdobe} alt="Adobe" className="h-4 brightness-0 invert opacity-35" />
<img src={logoFigma} alt="Figma" className="h-4 brightness-0 invert opacity-35" />
<img src={logoGoogle} alt="Google" className="h-4 brightness-0 invert opacity-35" />
</div>
</div>
</div>
@ -45,13 +116,6 @@ export const Header1 = (props: Header1Props) => {
);
};
export const Header1Defaults: Props = {
heading: "UXplore Konferenz 2026",
description:
"25-26 Juni Zürich",
buttons: [],
image: {
src: headerImage,
alt: "UXplore Konferenz 2026",
},
};
export const Header1Defaults = {};
export default Header1;

View File

@ -1,17 +1,22 @@
import headerBg from "../assets/program-header.png";
import { Button } from "@relume_io/relume-ui";
import type { ButtonProps } from "@relume_io/relume-ui";
type LinkButton = {
title: string;
href?: string;
variant?: string;
className?: string;
};
type Props = {
heading: string;
description: string;
buttons: ButtonProps[];
buttons: LinkButton[];
showTicketButton?: boolean;
};
export type Header23Props = React.ComponentPropsWithoutRef<"section"> & Partial<Props>;
export const Header23 = (props: Header23Props) => {
const { heading, description, buttons, children } = {
const { heading, description, buttons, showTicketButton = true, children } = {
...Header23Defaults,
...props,
};
@ -29,18 +34,38 @@ export const Header23 = (props: Header23Props) => {
{/* Content */}
<div className="container relative z-10">
<div className="mx-auto w-full max-w-lg text-center">
<div className="mb-5 flex items-center justify-center gap-3 md:mb-6">
<span className="rounded-full bg-white/15 px-4 py-1.5 text-sm font-semibold text-cloud-white">
2-Tage-Konferenz
</span>
<span className="rounded-full border border-acid-lime bg-acid-lime/10 px-4 py-1.5 text-sm font-semibold text-acid-lime">
Workshops inklusive
</span>
</div>
<h1 className="mb-5 text-6xl font-bold text-cloud-white md:mb-6 md:text-9xl lg:text-10xl">
{heading}
</h1>
<p className="md:text-md text-cloud-white text-[18px] leading-[1.5]">
{description}
</p>
{showTicketButton && (
<a
href="/tickets"
className="mt-8 inline-block rounded-2xl bg-acid-lime px-8 py-3 text-base font-normal text-tech-navy hover:opacity-90 transition-opacity"
>
Jetzt Ticket sichern
</a>
)}
{buttons.length > 0 && (
<div className="mt-6 flex items-center justify-center gap-x-4 md:mt-8">
<div className="mt-4 flex items-center justify-center gap-x-4">
{buttons.map((button, index) => (
<Button key={index} {...button}>
<a
key={index}
href={button.href ?? "#"}
className={`inline-flex items-center justify-center rounded-2xl border px-6 py-3 text-sm font-semibold transition-colors ${button.className ?? ""}`}
>
{button.title}
</Button>
</a>
))}
</div>
)}
@ -53,7 +78,7 @@ export const Header23 = (props: Header23Props) => {
export const Header23Defaults: Props = {
heading: "UXplore 2026 Programm",
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.",
description: "Erlebe zwei Tage voller Insights von den klügsten Köpfen der Branche. Jedes Ticket beinhaltet den Zugang zu allen Sessions und Workshops nach dem Kauf kannst du dich für deine Wunsch-Workshops anmelden.",
buttons: [],
};

View File

@ -95,10 +95,10 @@ export const Navbar3 = (props: Navbar3Props) => {
{buttons.map(({ url, ...button }, index) =>
url ? (
<Link key={index} to={url}>
<Button {...button} className="w-full">{button.title}</Button>
<Button {...button} className="w-full rounded-2xl bg-electric-violet border-electric-violet text-cloud-white font-barlow font-semibold">{button.title}</Button>
</Link>
) : (
<Button key={index} {...button} className="w-full">{button.title}</Button>
<Button key={index} {...button} className="w-full rounded-2xl bg-electric-violet border-electric-violet text-cloud-white font-barlow font-semibold">{button.title}</Button>
)
)}
</div>

View File

@ -2,8 +2,10 @@ import React, { useState } from "react";
type TicketPlan = {
day: string;
price: number;
date: string;
earlyBirdPrice: number;
regularPrice: number;
earlyBirdDeadline: string;
};
type Props = {
@ -13,15 +15,28 @@ type Props = {
export type Pricing39TicketProps = React.ComponentPropsWithoutRef<"section"> & Partial<Props>;
const TicketCardLime = ({ plan }: { plan: TicketPlan }) => {
const TicketCard = ({ plan }: { plan: TicketPlan }) => {
const [qty, setQty] = useState(0);
const total = qty * plan.price;
const total = qty * plan.earlyBirdPrice;
return (
<div className="bg-acid-lime text-tech-navy p-6 md:p-8 flex flex-col gap-5 font-barlow border border-electric-violet">
<p className="text-sm font-semibold">{plan.day}</p>
<h3 className="text-6xl font-bold md:text-8xl">CHF {plan.price}</h3>
<p className="text-sm">{plan.date}</p>
<div className="bg-electric-violet/10 text-tech-navy p-6 md:p-8 flex flex-col gap-5 font-barlow border border-electric-violet/30">
<p className="text-sm font-semibold uppercase tracking-wide">{plan.day}</p>
{/* Earlybird price block */}
<div className="flex flex-col gap-2">
<div className="flex items-end gap-3">
<h3 className="text-6xl font-bold md:text-8xl leading-none">CHF {plan.earlyBirdPrice}</h3>
<span className="mb-1 text-sm text-tech-navy/50 line-through">CHF {plan.regularPrice}</span>
</div>
<span className="inline-flex w-fit items-center gap-1.5 rounded-full bg-acid-lime px-3 py-1 text-xs font-semibold text-tech-navy">
<span className="h-1.5 w-1.5 rounded-full bg-tech-navy" />
Earlybird bis {plan.earlyBirdDeadline}
</span>
</div>
<p className="text-sm text-tech-navy/50">{plan.date}</p>
<div className="flex items-center justify-between">
<div className="flex items-center gap-4">
<button
@ -39,10 +54,11 @@ const TicketCardLime = ({ plan }: { plan: TicketPlan }) => {
</button>
</div>
<div className="text-right text-sm font-semibold">
<div>CHF {plan.price},-</div>
<div>{total},-</div>
<div className="text-tech-navy/50">CHF {plan.earlyBirdPrice},-</div>
<div>Total CHF {total},-</div>
</div>
</div>
<button className="w-full rounded-2xl bg-electric-violet text-cloud-white font-semibold py-3 text-sm">
Ticket sichern
</button>
@ -69,7 +85,7 @@ export const Pricing39Ticket = (props: Pricing39TicketProps) => {
}`}
>
{plans.map((plan, index) => (
<TicketCardLime key={index} plan={plan} />
<TicketCard key={index} plan={plan} />
))}
</div>
</div>
@ -80,8 +96,8 @@ export const Pricing39Ticket = (props: Pricing39TicketProps) => {
export const Pricing39TicketDefaults: Props = {
heading: "1-Tagespässe",
plans: [
{ day: "Donnerstag", price: 131, date: "25. Juni 2026" },
{ day: "Freitag", price: 131, date: "26. Juni 2026" },
{ day: "Donnerstag", date: "25. Juni 2026", earlyBirdPrice: 99, regularPrice: 131, earlyBirdDeadline: "31. Mai 2026" },
{ day: "Freitag", date: "26. Juni 2026", earlyBirdPrice: 99, regularPrice: 131, earlyBirdDeadline: "31. Mai 2026" },
],
};

View File

@ -81,17 +81,7 @@ const MemberCard = ({ member }: { member: TeamMember }) => (
<p className="text-lg text-tech-navy">{member.company}</p>
)}
</div>
<p className="mb-4 text-base leading-relaxed text-tech-navy">{member.description}</p>
<div className="mb-4 flex flex-wrap gap-2">
{member.talks.map((talk, i) => (
<span
key={i}
className={`rounded-lg px-2 py-1 text-sm font-semibold whitespace-nowrap ${talkBadgeClass(talk.type)}`}
>
{talk.title}
</span>
))}
</div>
<p className="text-base leading-relaxed text-tech-navy">{member.description}</p>
</div>
);

View File

@ -6,45 +6,61 @@ import { Event33 } from "../components/Event33";
import { Banner15 } from "../components/Banner15";
import { Footer3 } from "../components/Footer";
import { ALL_EVENTS } from "../data/events";
import type { EventDay } from "../components/Event33";
const TYPE_TABS = ["Talk", "Workshop", "Interaction", "AI and Future"];
const LEVEL_TABS: Record<string, string> = {
"Senior": "Senior",
"Mid-Level": "Mid-Level",
"Junior": "Junior",
const DAY_VALUES = ["Donnerstag", "Freitag"];
const TYPE_VALUES = ["Talk", "Workshop", "Interaction", "AI and Future"];
const LEVEL_VALUES = ["Senior", "Mid-Level", "Junior"];
const DAY_ORDER: Record<EventDay, number> = { Donnerstag: 0, Freitag: 1 };
const parseStartMinutes = (time: string) => {
const match = time.match(/(\d{2}):(\d{2})/);
return match ? parseInt(match[1]) * 60 + parseInt(match[2]) : 0;
};
const Programm = () => {
const [activeTab, setActiveTab] = useState("all");
const [activeTabs, setActiveTabs] = useState<string[]>([]);
const [searchQuery, setSearchQuery] = useState("");
const [activeDay, setActiveDay] = useState("all");
const [sortOrder, setSortOrder] = useState<"asc" | "desc">("asc");
const filteredEvents = useMemo(() => {
return ALL_EVENTS.filter((e) => {
if (activeTab !== "all") {
const matchesType = TYPE_TABS.includes(activeTab) && e.type === activeTab;
const matchesLevel = LEVEL_TABS[activeTab] && e.level === LEVEL_TABS[activeTab];
if (!matchesType && !matchesLevel) return false;
const filtered = ALL_EVENTS.filter((e) => {
if (activeTabs.length > 0) {
const activeDays = activeTabs.filter((t) => DAY_VALUES.includes(t));
const activeTypes = activeTabs.filter((t) => TYPE_VALUES.includes(t));
const activeLevels = activeTabs.filter((t) => LEVEL_VALUES.includes(t));
if (activeDays.length > 0 && !activeDays.includes(e.day)) return false;
if (activeTypes.length > 0 && !activeTypes.includes(e.type)) return false;
if (activeLevels.length > 0 && !activeLevels.includes(e.level)) return false;
}
if (searchQuery) {
const q = searchQuery.toLowerCase();
if (!e.title.toLowerCase().includes(q) && !e.speaker.toLowerCase().includes(q)) return false;
}
if (activeDay !== "all" && e.day !== activeDay) return false;
return true;
});
}, [activeTab, searchQuery, activeDay]);
return [...filtered].sort((a, b) => {
const dayDiff = DAY_ORDER[a.day] - DAY_ORDER[b.day];
if (dayDiff !== 0) return sortOrder === "asc" ? dayDiff : -dayDiff;
const timeDiff = parseStartMinutes(a.time) - parseStartMinutes(b.time);
return sortOrder === "asc" ? timeDiff : -timeDiff;
});
}, [activeTabs, searchQuery, sortOrder]);
const activeTags = [
searchQuery ? { label: searchQuery, onRemove: () => setSearchQuery("") } : null,
activeDay !== "all" ? { label: activeDay, onRemove: () => setActiveDay("all") } : null,
activeTab !== "all" ? { label: activeTab, onRemove: () => setActiveTab("all") } : null,
...activeTabs.map((tab) => ({
label: tab,
onRemove: () => setActiveTabs((prev) => prev.filter((t) => t !== tab)),
})),
].filter(Boolean) as { label: string; onRemove: () => void }[];
const handleReset = () => {
setActiveTab("all");
setActiveTabs([]);
setSearchQuery("");
setActiveDay("all");
setSortOrder("asc");
};
return (
@ -52,14 +68,14 @@ const Programm = () => {
<Navbar3 />
<Header23 />
<Filters5
activeTab={activeTab}
onTabChange={setActiveTab}
activeTabs={activeTabs}
onTabsChange={setActiveTabs}
searchQuery={searchQuery}
onSearchChange={setSearchQuery}
activeDay={activeDay}
onDayChange={setActiveDay}
activeTags={activeTags}
onReset={handleReset}
sortOrder={sortOrder}
onSortChange={setSortOrder}
/>
<Event33 events={filteredEvents} />
<Banner15 />

View File

@ -7,7 +7,6 @@ import { Team4Defaults } from "../components/Team4";
import { Footer3 } from "../components/Footer";
const SPEAKER_TABS = [
{ value: "all", label: "Alle ansehen" },
{ value: "Strategie", label: "Strategie" },
{ value: "Research", label: "Research" },
{ value: "Design / UX", label: "Design / UX" },
@ -18,12 +17,12 @@ const SPEAKER_TABS = [
const ALL_SPEAKERS = Team4Defaults.teamMembers;
const Speaker = () => {
const [activeTab, setActiveTab] = useState("all");
const [activeTabs, setActiveTabs] = useState<string[]>([]);
const [searchQuery, setSearchQuery] = useState("");
const filteredSpeakers = useMemo(() => {
return ALL_SPEAKERS.filter((s) => {
if (activeTab !== "all" && s.category !== activeTab) return false;
if (activeTabs.length > 0 && !activeTabs.includes(s.category)) return false;
if (searchQuery) {
const q = searchQuery.toLowerCase();
if (
@ -35,11 +34,14 @@ const Speaker = () => {
}
return true;
});
}, [activeTab, searchQuery]);
}, [activeTabs, searchQuery]);
const activeTags = [
searchQuery ? { label: searchQuery, onRemove: () => setSearchQuery("") } : null,
activeTab !== "all" ? { label: activeTab, onRemove: () => setActiveTab("all") } : null,
...activeTabs.map((tab) => ({
label: tab,
onRemove: () => setActiveTabs((prev) => prev.filter((t) => t !== tab)),
})),
].filter(Boolean) as { label: string; onRemove: () => void }[];
return (
@ -49,20 +51,19 @@ const Speaker = () => {
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."
buttons={[
{ title: "Zu den Speakern", variant: "primary", className: "bg-acid-lime text-neutral-dark border-acid-lime hover:bg-acid-lime/90 hover:border-acid-lime/90 rounded-2xl" },
{ title: "Zum Programm", variant: "secondary", className: "bg-transparent border-acid-lime text-acid-lime hover:bg-acid-lime/10 hover:border-acid-lime hover:text-acid-lime rounded-2xl" },
{ title: "Zum Programm", href: "/programm", className: "bg-transparent border-acid-lime text-acid-lime hover:bg-acid-lime/10" },
]}
/>
<Filters5
tabs={SPEAKER_TABS}
activeTab={activeTab}
onTabChange={setActiveTab}
activeTabs={activeTabs}
onTabsChange={setActiveTabs}
searchQuery={searchQuery}
onSearchChange={setSearchQuery}
searchLabel="Stichwortsuche"
resetLabel="Löschen"
activeTags={activeTags}
onReset={() => { setSearchQuery(""); setActiveTab("all"); }}
onReset={() => { setSearchQuery(""); setActiveTabs([]); }}
useFilterToggle={false}
showDayFilter={false}
showSortButton={false}

View File

@ -2,6 +2,7 @@ import { useParams, Navigate } from "react-router-dom";
import { Navbar3 } from "../components/Navbar";
import { Team4, Team4Defaults } from "../components/Team4";
import { Footer3 } from "../components/Footer";
import { ALL_EVENTS } from "../data/events";
import webflowLogo from "../assets/webflow.png";
import relumeLogo from "../assets/relume.png";
@ -38,6 +39,7 @@ const SpeakerDetail = () => {
if (!speaker) return <Navigate to="/speaker" replace />;
const speakerEvents = ALL_EVENTS.filter((e) => e.speaker === speaker.name);
const otherSpeakers = ALL_SPEAKERS.filter((s) => s.slug !== slug);
return (
@ -95,6 +97,41 @@ const SpeakerDetail = () => {
</div>
</section>
{/* Sessions */}
{speakerEvents.length > 0 && (
<section className="bg-cloud-white px-[5%] py-16 font-barlow">
<div className="container">
<h2 className="mb-8 text-3xl font-bold text-tech-navy md:text-4xl">Sessions</h2>
<div className="flex flex-col gap-4">
{speakerEvents.map((event) => (
<a
key={event.id}
href={`/programm/${event.id}`}
className="group flex flex-col gap-3 rounded-2xl border border-gray-200 p-6 transition-colors hover:border-electric-violet sm:flex-row sm:items-center sm:justify-between"
>
<div className="flex flex-col gap-2">
<div className="flex flex-wrap items-center gap-2">
<span className="rounded-md bg-electric-violet/10 px-3 py-1 text-xs font-semibold text-electric-violet">
{event.type}
</span>
<span className="text-sm text-gray-400">
{event.day} · {event.time}
</span>
</div>
<p className="text-base font-semibold text-tech-navy group-hover:text-electric-violet transition-colors">
{event.title}
</p>
</div>
<span className="shrink-0 text-sm font-semibold text-electric-violet">
Zur Session
</span>
</a>
))}
</div>
</div>
</section>
)}
{/* Other speakers */}
<div className="bg-cloud-white">
<div className="px-[5%] pt-16 font-barlow">

View File

@ -3,7 +3,6 @@
import React, { useState, useEffect } from "react";
import { Navbar3 } from "../components/Navbar";
import { Header23 } from "../components/Header23";
import { Pricing1Ticket } from "../components/Pricing1Ticket";
import { Pricing39Ticket } from "../components/Pricing39Ticket";
import { Footer3 } from "../components/Footer";
import { Input } from "@relume_io/relume-ui";
@ -82,6 +81,7 @@ const Tickets = () => {
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."
buttons={[]}
showTicketButton={false}
>
<div className="mt-5 font-semibold text-cloud-white md:mt-6 md:text-md">
DO FR, 25. 26. Juni 2026
@ -120,17 +120,16 @@ const Tickets = () => {
</p>
</div>
</Header23>
<Pricing1Ticket />
<Pricing39Ticket
<Pricing39Ticket
heading="1-Tagespässe"
plans={[
{ day: "Donnerstag", price: 131, date: "25. Juni 2026" },
{ day: "Freitag", price: 131, date: "26. Juni 2026" },
{ day: "Donnerstag", date: "25. Juni 2026", earlyBirdPrice: 99, regularPrice: 131, earlyBirdDeadline: "31. Mai 2026" },
{ day: "Freitag", date: "26. Juni 2026", earlyBirdPrice: 99, regularPrice: 131, earlyBirdDeadline: "31. Mai 2026" },
]}
/>
<Pricing39Ticket
heading="2-Tagespässe"
plans={[{ day: "Donnerstag Freitag", price: 236, date: "25. 26. Juni 2026" }]}
heading="2-Tagespass"
plans={[{ day: "Donnerstag Freitag", date: "25. 26. Juni 2026", earlyBirdPrice: 179, regularPrice: 236, earlyBirdDeadline: "31. Mai 2026" }]}
/>
<Footer3 />
</div>