Compare commits
No commits in common. "main" and "explore/relume-react" have entirely different histories.
main
...
explore/re
477
index.html
Normal file
@ -0,0 +1,477 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Klangwald Festival 2026</title>
|
||||
<meta name="description" content="Klangwald Festival – Das ultimative Musikerlebnis im Herzen der Schweizer Alpen. 18.–20. Juli 2026.">
|
||||
|
||||
<!-- Stylesheets -->
|
||||
<link rel="stylesheet" href="css/style.css">
|
||||
|
||||
<!-- Google Fonts -->
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;500;600;700&family=Inter:wght@300;400;500;600&display=swap" rel="stylesheet">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<!-- ============================================
|
||||
NAVIGATION
|
||||
============================================ -->
|
||||
<nav class="navbar" id="navbar">
|
||||
<div class="container navbar__inner">
|
||||
<a href="#" class="navbar__logo">
|
||||
<!-- TODO: Logo hier einfügen (z.B. <img src="img/logo.svg" alt="Klangwald">) -->
|
||||
KLANGWALD
|
||||
</a>
|
||||
|
||||
<!-- Hamburger-Menü für Mobile -->
|
||||
<button class="navbar__toggle" id="nav-toggle" aria-label="Menü öffnen">
|
||||
<span class="navbar__toggle-bar"></span>
|
||||
<span class="navbar__toggle-bar"></span>
|
||||
<span class="navbar__toggle-bar"></span>
|
||||
</button>
|
||||
|
||||
<ul class="navbar__menu" id="nav-menu">
|
||||
<li><a href="#lineup" class="navbar__link">Line-up</a></li>
|
||||
<li><a href="#programm" class="navbar__link">Programm</a></li>
|
||||
<li><a href="#tickets" class="navbar__link">Tickets</a></li>
|
||||
<li><a href="#info" class="navbar__link">Info</a></li>
|
||||
<li><a href="#tickets" class="navbar__link navbar__link--cta">Tickets kaufen</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<!-- ============================================
|
||||
HERO SECTION
|
||||
Grosser Einstiegsbereich mit Festival-Name,
|
||||
Datum und Call-to-Action.
|
||||
============================================ -->
|
||||
<header class="hero" id="hero">
|
||||
<div class="hero__overlay"></div>
|
||||
<div class="container hero__content">
|
||||
<p class="hero__date">18. – 20. Juli 2026</p>
|
||||
<h1 class="hero__title">Klangwald<br>Festival</h1>
|
||||
<p class="hero__subtitle">Drei Tage Musik, Natur & Gemeinschaft<br>im Herzen der Schweizer Alpen</p>
|
||||
<div class="hero__actions">
|
||||
<a href="#tickets" class="btn btn--primary">Tickets sichern</a>
|
||||
<a href="#lineup" class="btn btn--outline">Line-up entdecken</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Countdown -->
|
||||
<div class="hero__countdown" id="countdown">
|
||||
<div class="countdown__item">
|
||||
<span class="countdown__number" id="countdown-days">--</span>
|
||||
<span class="countdown__label">Tage</span>
|
||||
</div>
|
||||
<div class="countdown__item">
|
||||
<span class="countdown__number" id="countdown-hours">--</span>
|
||||
<span class="countdown__label">Stunden</span>
|
||||
</div>
|
||||
<div class="countdown__item">
|
||||
<span class="countdown__number" id="countdown-minutes">--</span>
|
||||
<span class="countdown__label">Minuten</span>
|
||||
</div>
|
||||
<div class="countdown__item">
|
||||
<span class="countdown__number" id="countdown-seconds">--</span>
|
||||
<span class="countdown__label">Sekunden</span>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- ============================================
|
||||
LINE-UP SECTION
|
||||
Übersicht der Künstler/Bands.
|
||||
============================================ -->
|
||||
<section class="section lineup" id="lineup">
|
||||
<div class="container">
|
||||
<h2 class="section__title">Line-up</h2>
|
||||
<p class="section__subtitle">Diese Acts erwarten dich am Klangwald Festival 2026</p>
|
||||
|
||||
<!-- Headliner -->
|
||||
<div class="lineup__tier lineup__tier--headliner">
|
||||
<h3 class="lineup__tier-label">Headliner</h3>
|
||||
<div class="lineup__grid lineup__grid--headliner">
|
||||
<!-- TODO: Echte Künstlerdaten einfügen -->
|
||||
<article class="artist-card artist-card--large">
|
||||
<div class="artist-card__image">
|
||||
<!-- TODO: Bild einfügen -->
|
||||
<div class="artist-card__placeholder">Bild</div>
|
||||
</div>
|
||||
<div class="artist-card__info">
|
||||
<h4 class="artist-card__name">Künstler*in 1</h4>
|
||||
<p class="artist-card__genre">Electronic / Ambient</p>
|
||||
</div>
|
||||
</article>
|
||||
<article class="artist-card artist-card--large">
|
||||
<div class="artist-card__image">
|
||||
<div class="artist-card__placeholder">Bild</div>
|
||||
</div>
|
||||
<div class="artist-card__info">
|
||||
<h4 class="artist-card__name">Künstler*in 2</h4>
|
||||
<p class="artist-card__genre">Indie Rock</p>
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Weitere Acts -->
|
||||
<div class="lineup__tier">
|
||||
<h3 class="lineup__tier-label">Weitere Acts</h3>
|
||||
<div class="lineup__grid">
|
||||
<article class="artist-card">
|
||||
<div class="artist-card__image">
|
||||
<div class="artist-card__placeholder">Bild</div>
|
||||
</div>
|
||||
<div class="artist-card__info">
|
||||
<h4 class="artist-card__name">Band A</h4>
|
||||
<p class="artist-card__genre">Pop</p>
|
||||
</div>
|
||||
</article>
|
||||
<article class="artist-card">
|
||||
<div class="artist-card__image">
|
||||
<div class="artist-card__placeholder">Bild</div>
|
||||
</div>
|
||||
<div class="artist-card__info">
|
||||
<h4 class="artist-card__name">Band B</h4>
|
||||
<p class="artist-card__genre">Hip-Hop</p>
|
||||
</div>
|
||||
</article>
|
||||
<article class="artist-card">
|
||||
<div class="artist-card__image">
|
||||
<div class="artist-card__placeholder">Bild</div>
|
||||
</div>
|
||||
<div class="artist-card__info">
|
||||
<h4 class="artist-card__name">DJ C</h4>
|
||||
<p class="artist-card__genre">Techno</p>
|
||||
</div>
|
||||
</article>
|
||||
<article class="artist-card">
|
||||
<div class="artist-card__image">
|
||||
<div class="artist-card__placeholder">Bild</div>
|
||||
</div>
|
||||
<div class="artist-card__info">
|
||||
<h4 class="artist-card__name">Duo D</h4>
|
||||
<p class="artist-card__genre">Jazz / Funk</p>
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p class="lineup__more">Weitere Acts werden laufend bekannt gegeben.</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ============================================
|
||||
PROGRAMM SECTION
|
||||
Tagesübersicht / Zeitplan.
|
||||
============================================ -->
|
||||
<section class="section programm" id="programm">
|
||||
<div class="container">
|
||||
<h2 class="section__title">Programm</h2>
|
||||
<p class="section__subtitle">Drei Tage voller unvergesslicher Erlebnisse</p>
|
||||
|
||||
<!-- Tag-Auswahl -->
|
||||
<div class="programm__tabs">
|
||||
<button class="programm__tab programm__tab--active" data-day="1">Freitag, 18. Juli</button>
|
||||
<button class="programm__tab" data-day="2">Samstag, 19. Juli</button>
|
||||
<button class="programm__tab" data-day="3">Sonntag, 20. Juli</button>
|
||||
</div>
|
||||
|
||||
<!-- Programm Tag 1 -->
|
||||
<div class="programm__day programm__day--active" id="day-1">
|
||||
<div class="programm__event">
|
||||
<div class="programm__time">16:00</div>
|
||||
<div class="programm__details">
|
||||
<h4 class="programm__act">Doors Open</h4>
|
||||
<p class="programm__stage">Einlass & Willkommen</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="programm__event">
|
||||
<div class="programm__time">17:30</div>
|
||||
<div class="programm__details">
|
||||
<h4 class="programm__act">Band A</h4>
|
||||
<p class="programm__stage">Waldbühne</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="programm__event">
|
||||
<div class="programm__time">19:00</div>
|
||||
<div class="programm__details">
|
||||
<h4 class="programm__act">DJ C</h4>
|
||||
<p class="programm__stage">Lichtung Stage</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="programm__event programm__event--highlight">
|
||||
<div class="programm__time">21:00</div>
|
||||
<div class="programm__details">
|
||||
<h4 class="programm__act">Künstler*in 1</h4>
|
||||
<p class="programm__stage">Hauptbühne</p>
|
||||
<span class="programm__badge">Headliner</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="programm__event">
|
||||
<div class="programm__time">23:00</div>
|
||||
<div class="programm__details">
|
||||
<h4 class="programm__act">Late Night Session</h4>
|
||||
<p class="programm__stage">Lichtung Stage</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Programm Tag 2 -->
|
||||
<div class="programm__day" id="day-2">
|
||||
<div class="programm__event">
|
||||
<div class="programm__time">14:00</div>
|
||||
<div class="programm__details">
|
||||
<h4 class="programm__act">Yoga & Sounds</h4>
|
||||
<p class="programm__stage">Waldlichtung</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="programm__event">
|
||||
<div class="programm__time">16:00</div>
|
||||
<div class="programm__details">
|
||||
<h4 class="programm__act">Duo D</h4>
|
||||
<p class="programm__stage">Waldbühne</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="programm__event">
|
||||
<div class="programm__time">18:00</div>
|
||||
<div class="programm__details">
|
||||
<h4 class="programm__act">Band B</h4>
|
||||
<p class="programm__stage">Hauptbühne</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="programm__event programm__event--highlight">
|
||||
<div class="programm__time">21:00</div>
|
||||
<div class="programm__details">
|
||||
<h4 class="programm__act">Künstler*in 2</h4>
|
||||
<p class="programm__stage">Hauptbühne</p>
|
||||
<span class="programm__badge">Headliner</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Programm Tag 3 -->
|
||||
<div class="programm__day" id="day-3">
|
||||
<div class="programm__event">
|
||||
<div class="programm__time">12:00</div>
|
||||
<div class="programm__details">
|
||||
<h4 class="programm__act">Brunch & Akustik</h4>
|
||||
<p class="programm__stage">Waldlichtung</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="programm__event">
|
||||
<div class="programm__time">15:00</div>
|
||||
<div class="programm__details">
|
||||
<h4 class="programm__act">Offene Jam Session</h4>
|
||||
<p class="programm__stage">Waldbühne</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="programm__event">
|
||||
<div class="programm__time">18:00</div>
|
||||
<div class="programm__details">
|
||||
<h4 class="programm__act">Abschlusskonzert</h4>
|
||||
<p class="programm__stage">Hauptbühne</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ============================================
|
||||
TICKETS SECTION
|
||||
Ticket-Kategorien und Preise.
|
||||
============================================ -->
|
||||
<section class="section tickets" id="tickets">
|
||||
<div class="container">
|
||||
<h2 class="section__title">Tickets</h2>
|
||||
<p class="section__subtitle">Sichere dir jetzt deinen Platz am Klangwald Festival</p>
|
||||
|
||||
<div class="tickets__grid">
|
||||
<!-- Tagespass -->
|
||||
<article class="ticket-card">
|
||||
<h3 class="ticket-card__title">Tagespass</h3>
|
||||
<div class="ticket-card__price">
|
||||
<span class="ticket-card__amount">CHF 69</span>
|
||||
<span class="ticket-card__period">pro Tag</span>
|
||||
</div>
|
||||
<ul class="ticket-card__features">
|
||||
<li>Zugang zu allen Bühnen</li>
|
||||
<li>1 Tag gültig</li>
|
||||
<li>Food-Court Zugang</li>
|
||||
</ul>
|
||||
<a href="#" class="btn btn--outline ticket-card__btn">Auswählen</a>
|
||||
</article>
|
||||
|
||||
<!-- 3-Tages-Pass (Featured) -->
|
||||
<article class="ticket-card ticket-card--featured">
|
||||
<div class="ticket-card__badge">Beliebt</div>
|
||||
<h3 class="ticket-card__title">3-Tages-Pass</h3>
|
||||
<div class="ticket-card__price">
|
||||
<span class="ticket-card__amount">CHF 149</span>
|
||||
<span class="ticket-card__period">alle 3 Tage</span>
|
||||
</div>
|
||||
<ul class="ticket-card__features">
|
||||
<li>Zugang zu allen Bühnen</li>
|
||||
<li>Alle 3 Tage gültig</li>
|
||||
<li>Food-Court Zugang</li>
|
||||
<li>Camping inklusive</li>
|
||||
</ul>
|
||||
<a href="#" class="btn btn--primary ticket-card__btn">Auswählen</a>
|
||||
</article>
|
||||
|
||||
<!-- VIP -->
|
||||
<article class="ticket-card">
|
||||
<h3 class="ticket-card__title">VIP Pass</h3>
|
||||
<div class="ticket-card__price">
|
||||
<span class="ticket-card__amount">CHF 299</span>
|
||||
<span class="ticket-card__period">alle 3 Tage</span>
|
||||
</div>
|
||||
<ul class="ticket-card__features">
|
||||
<li>Zugang zu allen Bühnen</li>
|
||||
<li>Alle 3 Tage gültig</li>
|
||||
<li>VIP-Lounge & Catering</li>
|
||||
<li>Glamping inklusive</li>
|
||||
<li>Meet & Greet</li>
|
||||
</ul>
|
||||
<a href="#" class="btn btn--outline ticket-card__btn">Auswählen</a>
|
||||
</article>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ============================================
|
||||
INFO SECTION
|
||||
Anreise, FAQ und praktische Infos.
|
||||
============================================ -->
|
||||
<section class="section info" id="info">
|
||||
<div class="container">
|
||||
<h2 class="section__title">Info</h2>
|
||||
<p class="section__subtitle">Alles was du für dein Festival-Erlebnis wissen musst</p>
|
||||
|
||||
<div class="info__grid">
|
||||
<!-- Anreise -->
|
||||
<div class="info__card">
|
||||
<div class="info__icon">
|
||||
<!-- TODO: Icon einfügen (z.B. SVG oder Icon-Font) -->
|
||||
<span class="info__icon-placeholder">📍</span>
|
||||
</div>
|
||||
<h3 class="info__card-title">Anreise</h3>
|
||||
<p class="info__card-text">
|
||||
Das Festival findet auf der Waldlichtung oberhalb von Chur statt.
|
||||
Shuttle-Busse verkehren ab Bahnhof Chur alle 30 Minuten.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Camping -->
|
||||
<div class="info__card">
|
||||
<div class="info__icon">
|
||||
<span class="info__icon-placeholder">⛺</span>
|
||||
</div>
|
||||
<h3 class="info__card-title">Camping</h3>
|
||||
<p class="info__card-text">
|
||||
Der Campingplatz öffnet am Freitag um 10:00 Uhr.
|
||||
Duschen und Sanitäranlagen sind vorhanden. Glamping-Zelte können dazu gebucht werden.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Nachhaltigkeit -->
|
||||
<div class="info__card">
|
||||
<div class="info__icon">
|
||||
<span class="info__icon-placeholder">🌱</span>
|
||||
</div>
|
||||
<h3 class="info__card-title">Nachhaltigkeit</h3>
|
||||
<p class="info__card-text">
|
||||
Klangwald ist ein klimaneutrales Festival. Wir setzen auf
|
||||
Mehrwegbecher, regionale Verpflegung und erneuerbare Energien.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- FAQ -->
|
||||
<div class="info__card">
|
||||
<div class="info__icon">
|
||||
<span class="info__icon-placeholder">❓</span>
|
||||
</div>
|
||||
<h3 class="info__card-title">FAQ</h3>
|
||||
<p class="info__card-text">
|
||||
Häufig gestellte Fragen zu Tickets, Anreise,
|
||||
Barrierefreiheit und mehr findest du in unseren FAQ.
|
||||
</p>
|
||||
<a href="#" class="info__link">Zu den FAQ →</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ============================================
|
||||
NEWSLETTER SECTION
|
||||
E-Mail Anmeldung.
|
||||
============================================ -->
|
||||
<section class="section newsletter" id="newsletter">
|
||||
<div class="container newsletter__inner">
|
||||
<h2 class="newsletter__title">Bleib auf dem Laufenden</h2>
|
||||
<p class="newsletter__text">Erhalte News zu Line-up, Tickets und Specials direkt in dein Postfach.</p>
|
||||
<form class="newsletter__form" id="newsletter-form">
|
||||
<input
|
||||
type="email"
|
||||
class="newsletter__input"
|
||||
placeholder="Deine E-Mail-Adresse"
|
||||
aria-label="E-Mail-Adresse"
|
||||
required
|
||||
>
|
||||
<button type="submit" class="btn btn--primary newsletter__btn">Anmelden</button>
|
||||
</form>
|
||||
<p class="newsletter__hint">Kein Spam. Jederzeit abmeldbar.</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ============================================
|
||||
FOOTER
|
||||
============================================ -->
|
||||
<footer class="footer">
|
||||
<div class="container footer__inner">
|
||||
<div class="footer__brand">
|
||||
<a href="#" class="footer__logo">KLANGWALD</a>
|
||||
<p class="footer__tagline">Musik. Natur. Gemeinschaft.</p>
|
||||
</div>
|
||||
|
||||
<div class="footer__links">
|
||||
<div class="footer__column">
|
||||
<h4 class="footer__heading">Festival</h4>
|
||||
<ul>
|
||||
<li><a href="#lineup">Line-up</a></li>
|
||||
<li><a href="#programm">Programm</a></li>
|
||||
<li><a href="#tickets">Tickets</a></li>
|
||||
<li><a href="#info">Info</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="footer__column">
|
||||
<h4 class="footer__heading">Rechtliches</h4>
|
||||
<ul>
|
||||
<li><a href="#">Impressum</a></li>
|
||||
<li><a href="#">Datenschutz</a></li>
|
||||
<li><a href="#">AGB</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="footer__column">
|
||||
<h4 class="footer__heading">Social Media</h4>
|
||||
<ul>
|
||||
<li><a href="#" target="_blank" rel="noopener">Instagram</a></li>
|
||||
<li><a href="#" target="_blank" rel="noopener">TikTok</a></li>
|
||||
<li><a href="#" target="_blank" rel="noopener">YouTube</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="footer__bottom">
|
||||
<p>© 2026 Klangwald Festival. Ein Projekt der FHGR.</p>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<!-- JavaScript -->
|
||||
<script src="js/main.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
@ -4,8 +4,7 @@
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>UXplore</title>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Barlow:wght@400;500;600;700&display=swap" rel="stylesheet">
|
||||
<title>Vite + React</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
|
||||
60
relume-test/package-lock.json
generated
@ -14,8 +14,7 @@
|
||||
"framer-motion": "^12.38.0",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
"react-icons": "^5.6.0",
|
||||
"react-router-dom": "^7.14.0"
|
||||
"react-icons": "^5.6.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.13.0",
|
||||
@ -5633,19 +5632,6 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/cookie": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/cookie/-/cookie-1.1.1.tgz",
|
||||
"integrity": "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/express"
|
||||
}
|
||||
},
|
||||
"node_modules/cross-spawn": {
|
||||
"version": "7.0.6",
|
||||
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
|
||||
@ -10063,44 +10049,6 @@
|
||||
"react-dom": "^16.14.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc"
|
||||
}
|
||||
},
|
||||
"node_modules/react-router": {
|
||||
"version": "7.14.0",
|
||||
"resolved": "https://registry.npmjs.org/react-router/-/react-router-7.14.0.tgz",
|
||||
"integrity": "sha512-m/xR9N4LQLmAS0ZhkY2nkPA1N7gQ5TUVa5n8TgANuDTARbn1gt+zLPXEm7W0XDTbrQ2AJSJKhoa6yx1D8BcpxQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"cookie": "^1.0.1",
|
||||
"set-cookie-parser": "^2.6.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": ">=18",
|
||||
"react-dom": ">=18"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"react-dom": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/react-router-dom": {
|
||||
"version": "7.14.0",
|
||||
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.14.0.tgz",
|
||||
"integrity": "sha512-2G3ajSVSZMEtmTjIklRWlNvo8wICEpLihfD/0YMDxbWK2UyP5EGfnoIn9AIQGnF3G/FX0MRbHXdFcD+rL1ZreQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"react-router": "7.14.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": ">=18",
|
||||
"react-dom": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/react-style-singleton": {
|
||||
"version": "2.2.3",
|
||||
"resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.3.tgz",
|
||||
@ -10374,12 +10322,6 @@
|
||||
"semver": "bin/semver.js"
|
||||
}
|
||||
},
|
||||
"node_modules/set-cookie-parser": {
|
||||
"version": "2.7.2",
|
||||
"resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.2.tgz",
|
||||
"integrity": "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/set-function-length": {
|
||||
"version": "1.2.2",
|
||||
"resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz",
|
||||
|
||||
@ -16,8 +16,7 @@
|
||||
"framer-motion": "^12.38.0",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
"react-icons": "^5.6.0",
|
||||
"react-router-dom": "^7.14.0"
|
||||
"react-icons": "^5.6.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.13.0",
|
||||
|
||||
@ -1,22 +1,7 @@
|
||||
import { Routes, Route } from "react-router-dom";
|
||||
import Home from "./pages/Home";
|
||||
import Programm from "./pages/Programm";
|
||||
import ProgrammDetail from "./pages/ProgrammDetail";
|
||||
import Speaker from "./pages/Speaker";
|
||||
import SpeakerDetail from "./pages/SpeakerDetail";
|
||||
import Tickets from "./pages/Tickets";
|
||||
|
||||
function App() {
|
||||
return (
|
||||
<Routes>
|
||||
<Route path="/" element={<Home />} />
|
||||
<Route path="/programm" element={<Programm />} />
|
||||
<Route path="/programm/:id" element={<ProgrammDetail />} />
|
||||
<Route path="/speaker" element={<Speaker />} />
|
||||
<Route path="/speaker/:slug" element={<SpeakerDetail />} />
|
||||
<Route path="/tickets" element={<Tickets />} />
|
||||
</Routes>
|
||||
);
|
||||
return <Home />;
|
||||
}
|
||||
|
||||
export default App;
|
||||
export default App;
|
||||
|
Before Width: | Height: | Size: 1.3 MiB |
|
Before Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 6.6 MiB |
|
Before Width: | Height: | Size: 1.5 KiB |
|
Before Width: | Height: | Size: 6.6 MiB |
|
Before Width: | Height: | Size: 3.7 KiB |
|
Before Width: | Height: | Size: 1.1 MiB |
|
Before Width: | Height: | Size: 153 KiB |
|
Before Width: | Height: | Size: 168 KiB |
|
Before Width: | Height: | Size: 105 KiB |
|
Before Width: | Height: | Size: 137 KiB |
|
Before Width: | Height: | Size: 104 KiB |
|
Before Width: | Height: | Size: 60 KiB |
|
Before Width: | Height: | Size: 84 KiB |
|
Before Width: | Height: | Size: 95 KiB |
|
Before Width: | Height: | Size: 100 KiB |
|
Before Width: | Height: | Size: 3.9 KiB |
|
Before Width: | Height: | Size: 173 KiB |
|
Before Width: | Height: | Size: 173 KiB |
|
Before Width: | Height: | Size: 181 KiB |
@ -1,86 +0,0 @@
|
||||
import React from "react";
|
||||
|
||||
type ImageProps = {
|
||||
src: string;
|
||||
alt?: string;
|
||||
};
|
||||
|
||||
type Section = {
|
||||
title: string;
|
||||
image: ImageProps;
|
||||
};
|
||||
|
||||
type Props = {
|
||||
sections: Section[];
|
||||
};
|
||||
|
||||
export type Banner15Props = React.ComponentPropsWithoutRef<"section"> & Partial<Props>;
|
||||
|
||||
export const Banner15 = (props: Banner15Props) => {
|
||||
const { sections } = {
|
||||
...Banner15Defaults,
|
||||
...props,
|
||||
};
|
||||
return (
|
||||
<section id="relume" className="flex w-screen max-w-full justify-end overflow-hidden bg-cloud-white">
|
||||
<div className="flex justify-end">
|
||||
{Array(2)
|
||||
.fill(0)
|
||||
.map((_, index) => (
|
||||
<div
|
||||
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}>
|
||||
<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>
|
||||
</React.Fragment>
|
||||
))}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
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: "Adobe",
|
||||
image: {
|
||||
src: "https://relume-assets.s3.us-east-1.amazonaws.com/placeholder-image.svg",
|
||||
alt: "Adobe",
|
||||
},
|
||||
},
|
||||
{
|
||||
title: "Google",
|
||||
image: {
|
||||
src: "https://relume-assets.s3.us-east-1.amazonaws.com/placeholder-image.svg",
|
||||
alt: "Google",
|
||||
},
|
||||
},
|
||||
{
|
||||
title: "Figma",
|
||||
image: {
|
||||
src: "https://relume-assets.s3.us-east-1.amazonaws.com/placeholder-image.svg",
|
||||
alt: "Figma",
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
@ -1,99 +0,0 @@
|
||||
import contentImage from "../assets/content-header.png";
|
||||
import { Dialog, DialogTrigger, DialogContent } from "@relume_io/relume-ui";
|
||||
import clsx from "clsx";
|
||||
import React from "react";
|
||||
import { useState } from "react";
|
||||
import { CgSpinner } from "react-icons/cg";
|
||||
import { FaCirclePlay } from "react-icons/fa6";
|
||||
|
||||
type ImageProps = {
|
||||
src: string;
|
||||
alt?: string;
|
||||
};
|
||||
|
||||
type Props = {
|
||||
heading: string;
|
||||
children: React.ReactNode;
|
||||
image: ImageProps;
|
||||
video: string;
|
||||
};
|
||||
|
||||
export type Content11Props = React.ComponentPropsWithoutRef<"section"> & Partial<Props>;
|
||||
|
||||
export const Content11 = (props: Content11Props) => {
|
||||
const { heading, children, image, video } = {
|
||||
...Content11Defaults,
|
||||
...props,
|
||||
};
|
||||
|
||||
const [isIframeLoaded, setIsIframeLoaded] = useState<boolean>(false);
|
||||
|
||||
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="mb-12 md:mb-18 lg:mb-20">
|
||||
<Dialog>
|
||||
<DialogTrigger asChild>
|
||||
<button className="relative flex w-full items-center justify-center">
|
||||
<img
|
||||
src={image.src}
|
||||
alt={image.alt}
|
||||
className="aspect-video size-full object-cover"
|
||||
/>
|
||||
<span className="absolute inset-0 z-10 bg-black/50" />
|
||||
<FaCirclePlay className="absolute z-20 size-16 text-white" />
|
||||
</button>
|
||||
</DialogTrigger>
|
||||
<DialogContent>
|
||||
{!isIframeLoaded && <CgSpinner className="mx-auto size-16 animate-spin text-white" />}
|
||||
<iframe
|
||||
className={clsx(
|
||||
"z-0 mx-auto aspect-video h-full w-full md:w-[738px] lg:w-[940px]",
|
||||
{
|
||||
visible: isIframeLoaded,
|
||||
hidden: !isIframeLoaded,
|
||||
},
|
||||
)}
|
||||
src={video}
|
||||
allow="autoplay; encrypted-media; picture-in-picture"
|
||||
allowFullScreen
|
||||
onLoad={() => setIsIframeLoaded(true)}
|
||||
></iframe>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</div>
|
||||
<div className="mx-auto max-w-lg">
|
||||
<h2 className="mb-5 text-5xl font-bold text-acid-lime md:mb-6 md:text-7xl lg:text-8xl">{heading}</h2>
|
||||
<div className="prose">{children}</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
export const Content11Defaults: Props = {
|
||||
heading: "UXPLORE 2026: Gestalte, was die Welt bewegt.",
|
||||
children: (
|
||||
<React.Fragment>
|
||||
<p className="text-cloud-white text-[18px] leading-[1.5] mb-4">
|
||||
UXPLORE bringt Designer, Researcher und Produktstrategen in Zürich zusammen. Wir konzentrieren uns auf die Realität unserer täglichen Arbeit: fundierte Methoden, ehrliche Einblicke in komplexe Projekte und der direkte Austausch unter Fachleuten.
|
||||
</p>
|
||||
<p className="text-cloud-white text-[18px] leading-[1.5] mb-4">
|
||||
Es geht um die Themen, die uns heute fordern – von effektiven Design-Systemen bis hin zu strategischem Research und nachhaltiger Produktentwicklung.
|
||||
</p>
|
||||
<ul className="list-disc pl-6 text-cloud-white text-[18px] leading-[1.5] mb-4 flex flex-col gap-2">
|
||||
<li><span className="font-semibold">Insights: </span>Erfahre, wie führende Teams Herausforderungen in Design und Strategie lösen.</li>
|
||||
<li><span className="font-semibold">Community: </span>Vernetze dich mit Menschen, die vor den gleichen Aufgaben stehen wie du.</li>
|
||||
<li><span className="font-semibold">Relevanz: </span>Nimm Ansätze mit, die dein Handwerk und deine Prozesse stärken.</li>
|
||||
</ul>
|
||||
<p className="text-cloud-white text-[18px] leading-[1.5]">
|
||||
Komm nach Zürich. Lass uns gemeinsam besser gestalten.
|
||||
</p>
|
||||
</React.Fragment>
|
||||
),
|
||||
video: "https://www.youtube.com/embed/8DKLYsikxTs?si=Ch9W0KrDWWUiCMMW",
|
||||
image: {
|
||||
src: contentImage,
|
||||
alt: "UXplore venue",
|
||||
},
|
||||
};
|
||||
@ -28,12 +28,12 @@ export const Cta30 = (props: Cta30Props) => {
|
||||
};
|
||||
|
||||
return (
|
||||
<section id="relume" className="px-[5%] py-16 md:py-24 lg:py-28 bg-cloud-white font-barlow">
|
||||
<section id="relume" className="relative px-[5%] py-16 md:py-24 lg:py-28">
|
||||
<div className="container relative z-10 max-w-lg text-center">
|
||||
<h2 className="rb-5 mb-5 text-5xl font-bold text-neutral-dark md:mb-6 md:text-7xl lg:text-8xl">
|
||||
<h2 className="rb-5 mb-5 text-5xl font-bold text-text-alternative md:mb-6 md:text-7xl lg:text-8xl">
|
||||
{heading}
|
||||
</h2>
|
||||
<p className="text-neutral-dark md:text-md">{description}</p>
|
||||
<p className="text-text-alternative md:text-md">{description}</p>
|
||||
<div className="mx-auto mt-6 w-full max-w-sm md:mt-8">
|
||||
<form
|
||||
className="rb-4 mb-4 grid max-w-sm grid-cols-1 gap-y-3 sm:grid-cols-[1fr_max-content] sm:gap-4"
|
||||
@ -46,28 +46,34 @@ export const Cta30 = (props: Cta30Props) => {
|
||||
value={emailInput}
|
||||
onChange={(e) => setEmailInput(e.target.value)}
|
||||
/>
|
||||
<Button {...button} className="items-center justify-center px-6 py-3 bg-electric-green text-cloud-white rounded-2xl border-acid-lime font-semibold">
|
||||
<Button {...button} className="items-center justify-center px-6 py-3">
|
||||
{button.title}
|
||||
</Button>
|
||||
</form>
|
||||
<div dangerouslySetInnerHTML={{ __html: termsAndConditions }} />
|
||||
</div>
|
||||
</div>
|
||||
<div className="absolute inset-0 z-0">
|
||||
<video className="absolute inset-0 aspect-video size-full object-cover" autoPlay loop muted>
|
||||
<source src={video} type={videoType} />
|
||||
</video>
|
||||
<div className="absolute inset-0 bg-black/50" />
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
export const Cta30Defaults: Props = {
|
||||
heading: "Stay tuned!",
|
||||
description: "Bleib' auf dem Laufenden und erfahre alles rund um die UXplore sowie weitere spannende Themen.",
|
||||
heading: "Medium length heading goes here",
|
||||
description: "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse varius enim in eros elementum tristique.",
|
||||
inputPlaceholder: "Enter your email",
|
||||
button: { title: "Sign Up", variant: "primary", size: "sm" },
|
||||
button: { title: "Sign up", variant: "primary", size: "sm" },
|
||||
termsAndConditions: `
|
||||
<p class='text-xs text-neutral-dark text-center'>
|
||||
<p class='text-xs text-text-alternative'>
|
||||
By clicking Sign Up you're confirming that you agree with our
|
||||
<a href='#' class='underline'>Terms and Conditions</a>.
|
||||
</p>
|
||||
`,
|
||||
video: "",
|
||||
videoType: "",
|
||||
video: "https://d22po4pjz3o32e.cloudfront.net/placeholder-video.mp4",
|
||||
videoType: "video/mp4",
|
||||
};
|
||||
@ -1,11 +1,8 @@
|
||||
import speaker1 from "../assets/speaker1.png";
|
||||
import speaker2 from "../assets/speaker2.png";
|
||||
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 { RxChevronRight } from "react-icons/rx";
|
||||
//import { BiMap, BiCalendarAlt } from "react-icons/bi";
|
||||
import { RxChevronRight } from "react-icons/rx";
|
||||
import { BiMap, BiCalendarAlt } from "react-icons/bi";
|
||||
|
||||
type ImageProps = {
|
||||
src: string;
|
||||
@ -46,12 +43,12 @@ export const Event17 = (props: Event17Props) => {
|
||||
...props,
|
||||
};
|
||||
return (
|
||||
<section id="relume" className="px-[5%] py-16 md:py-24 lg:py-28 bg-tech-navy font-barlow">
|
||||
<section id="relume" className="px-[5%] py-16 md:py-24 lg:py-28">
|
||||
<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>
|
||||
<p className="mt-5 text-base md:mt-6 md:text-md text-cloud-white whitespace-pre-wrap">{description}</p>
|
||||
<h1 className="mt-3 text-5xl font-bold md:mt-4 md:text-7xl lg:text-8xl">{heading}</h1>
|
||||
<p className="mt-5 text-base md:mt-6 md:text-md">{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">
|
||||
{featuredEvents.map((event, index) => (
|
||||
@ -59,7 +56,7 @@ export const Event17 = (props: Event17Props) => {
|
||||
))}
|
||||
</div>
|
||||
<div className="mt-12 flex justify-center md:mt-18 lg:mt-20">
|
||||
<Button {...button} className="bg-acid-lime text-tech-navy font-semibold px-6 py-3 rounded-2xl border-acid-lime">{button.title}</Button>
|
||||
<Button {...button}>{button.title}</Button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
@ -67,41 +64,83 @@ export const Event17 = (props: Event17Props) => {
|
||||
};
|
||||
|
||||
const FeaturedEvent: React.FC<FeaturedEvent> = ({
|
||||
url, image, title
|
||||
url, image, date, category, title, location, description, button,
|
||||
}) => {
|
||||
return (
|
||||
<div className="flex flex-col items-start gap-4">
|
||||
<div className="flex flex-col items-start">
|
||||
<a href={url} className="relative block aspect-[3/2] w-full">
|
||||
<img src={image.src} alt={image.alt} className="absolute size-full object-cover" />
|
||||
<span className="absolute right-4 top-4 bg-background-secondary px-2 py-1 text-sm font-semibold">
|
||||
{category}
|
||||
</span>
|
||||
</a>
|
||||
<h2 className="text-xl font-bold text-cloud-white">{title}</h2>
|
||||
<div className="mt-5 flex flex-col items-start md:mt-6">
|
||||
<div className="mb-3 flex flex-wrap gap-4 text-sm md:mb-4">
|
||||
<div className="flex items-center gap-2">
|
||||
<BiCalendarAlt className="size-6 flex-none" />
|
||||
{date.weekday} {date.day} {date.month} {date.year}
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<BiMap className="size-6 flex-none" />
|
||||
<span>{location}</span>
|
||||
</div>
|
||||
</div>
|
||||
<a href={url} className="mb-2">
|
||||
<h2 className="text-xl font-bold md:text-2xl">{title}</h2>
|
||||
</a>
|
||||
<p>{description}</p>
|
||||
<Button {...button} className="mt-5 md:mt-6">
|
||||
{button.title}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
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" },
|
||||
tagline: "Tagline",
|
||||
heading: "Events",
|
||||
description: "Lorem ipsum dolor sit amet, consectetur adipiscing elit.",
|
||||
button: { variant: "secondary", size: "primary", title: "View all" },
|
||||
featuredEvents: [
|
||||
{
|
||||
url: "/speaker", image: { src: speaker1, alt: "Dr. Elena Rossi" },
|
||||
date: { weekday: "", day: "", month: "", year: "" },
|
||||
category: "", title: "Dr. Elena Rossi", location: "", description: "",
|
||||
button: { title: "", variant: "link", size: "link" },
|
||||
url: "#",
|
||||
image: {
|
||||
src: "https://d22po4pjz3o32e.cloudfront.net/placeholder-image-landscape.svg",
|
||||
alt: "Relume placeholder image 1",
|
||||
},
|
||||
date: { weekday: "Fri", day: "09", month: "Feb", year: "2024" },
|
||||
category: "Category",
|
||||
title: "Event title heading",
|
||||
location: "Location",
|
||||
description: "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse varius enim in eros.",
|
||||
button: { title: "View event", variant: "link", size: "link", iconRight: <RxChevronRight /> },
|
||||
},
|
||||
{
|
||||
url: "/speaker", image: { src: speaker2, alt: "Jens Riegelberger" },
|
||||
date: { weekday: "", day: "", month: "", year: "" },
|
||||
category: "", title: "Jens Riegelberger", location: "", description: "",
|
||||
button: { title: "", variant: "link", size: "link" },
|
||||
url: "#",
|
||||
image: {
|
||||
src: "https://d22po4pjz3o32e.cloudfront.net/placeholder-image-landscape.svg",
|
||||
alt: "Relume placeholder image 2",
|
||||
},
|
||||
date: { weekday: "Sat", day: "10", month: "Feb", year: "2024" },
|
||||
category: "Category",
|
||||
title: "Event title heading",
|
||||
location: "Location",
|
||||
description: "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse varius enim in eros.",
|
||||
button: { title: "View event", variant: "link", size: "link", iconRight: <RxChevronRight /> },
|
||||
},
|
||||
{
|
||||
url: "/speaker", image: { src: speaker3, alt: "Reto Gwerder" },
|
||||
date: { weekday: "", day: "", month: "", year: "" },
|
||||
category: "", title: "Reto Gwerder", location: "", description: "",
|
||||
button: { title: "", variant: "link", size: "link" },
|
||||
url: "#",
|
||||
image: {
|
||||
src: "https://d22po4pjz3o32e.cloudfront.net/placeholder-image-landscape.svg",
|
||||
alt: "Relume placeholder image 3",
|
||||
},
|
||||
date: { weekday: "Sun", day: "11", month: "Feb", year: "2024" },
|
||||
category: "Category",
|
||||
title: "Event title heading",
|
||||
location: "Location",
|
||||
description: "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse varius enim in eros.",
|
||||
button: { title: "View event", variant: "link", size: "link", iconRight: <RxChevronRight /> },
|
||||
},
|
||||
],
|
||||
};
|
||||
@ -1,6 +1,3 @@
|
||||
import workshop1 from "../assets/workshop1.png";
|
||||
import workshop2 from "../assets/workshop2.png";
|
||||
import workshop3 from "../assets/workshop3.png";
|
||||
import * as React from "react";
|
||||
import type { ButtonProps } from "@relume_io/relume-ui";
|
||||
import { Button } from "@relume_io/relume-ui";
|
||||
@ -46,15 +43,15 @@ export const Event20 = (props: Event20Props) => {
|
||||
...props,
|
||||
};
|
||||
return (
|
||||
<section id="relume" className="px-[5%] py-16 md:py-24 lg:py-28 bg-tech-navy font-barlow">
|
||||
<section id="relume" className="px-[5%] py-16 md:py-24 lg:py-28">
|
||||
<div className="container">
|
||||
<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>
|
||||
<p className="md:text-md text-cloud-white">{description}</p>
|
||||
<h1 className="mb-3 text-5xl font-bold md:mb-4 md:text-7xl lg:text-8xl">{heading}</h1>
|
||||
<p className="md:text-md">{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">
|
||||
<Button {...button} className="hidden md:flex">
|
||||
{button.title}
|
||||
</Button>
|
||||
</div>
|
||||
@ -75,11 +72,11 @@ const FeaturedEvent: React.FC<FeaturedEvent> = ({
|
||||
url, image, date, category, title, location, description, button,
|
||||
}) => {
|
||||
return (
|
||||
<div className="flex flex-col items-start border border-electric-violet bg-cloud-white">
|
||||
<div className="flex flex-col items-start border border-border-primary">
|
||||
<a href={url} className="relative block aspect-[3/2] w-full">
|
||||
<img src={image.src} alt={image.alt} className="absolute size-full object-cover" />
|
||||
<span className="absolute right-4 top-4 bg-electric-violet text-cloud-white px-2 py-1 text-sm font-semibold rounded-lg">
|
||||
{category}
|
||||
<span className="absolute right-4 top-4 bg-background-secondary px-2 py-1 text-sm font-semibold">
|
||||
{category}
|
||||
</span>
|
||||
</a>
|
||||
<div className="flex flex-col items-start p-6">
|
||||
@ -94,44 +91,59 @@ const FeaturedEvent: React.FC<FeaturedEvent> = ({
|
||||
</div>
|
||||
</div>
|
||||
<a href={url} className="mb-2">
|
||||
<h2 className="text-xl font-bold md:text-2xl text-neutral-dark">{title}</h2>
|
||||
<h2 className="text-xl font-bold md:text-2xl">{title}</h2>
|
||||
</a>
|
||||
<p>{description}</p>
|
||||
<a href={url} className="mt-5 md:mt-6 flex items-center gap-2 text-electric-violet font-semibold text-base">
|
||||
{button.title} <RxChevronRight />
|
||||
</a>
|
||||
<Button {...button} className="mt-5 md:mt-6">
|
||||
{button.title}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const Event20Defaults: Props = {
|
||||
tagline: "",
|
||||
heading: "Workshops",
|
||||
description: "Hands-on, Skills statt Theorie. Lerne Methoden und Workflows direkt von Experten aus der Industrie.",
|
||||
button: { variant: "primary", size: "primary", title: "View all" },
|
||||
tagline: "Tagline",
|
||||
heading: "Events",
|
||||
description: "Lorem ipsum dolor sit amet, consectetur adipiscing elit.",
|
||||
button: { variant: "secondary", size: "primary", title: "View all" },
|
||||
featuredEvents: [
|
||||
{
|
||||
url: "/programm",
|
||||
image: { src: workshop1, alt: "Research Repositories" },
|
||||
date: { weekday: "Sat", day: "25", month: "Jun", year: "2026" },
|
||||
category: "Research", title: "Research Repositories", location: "Location",
|
||||
url: "#",
|
||||
image: {
|
||||
src: "https://d22po4pjz3o32e.cloudfront.net/placeholder-image-landscape.svg",
|
||||
alt: "Relume placeholder image 1",
|
||||
},
|
||||
date: { weekday: "Fri", day: "09", month: "Feb", year: "2024" },
|
||||
category: "Category",
|
||||
title: "Event title heading",
|
||||
location: "Location",
|
||||
description: "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse varius enim in eros.",
|
||||
button: { title: "View event", variant: "link", size: "link", iconRight: <RxChevronRight /> },
|
||||
},
|
||||
{
|
||||
url: "/programm",
|
||||
image: { src: workshop2, alt: "The future of Tokens" },
|
||||
date: { weekday: "Sat", day: "25", month: "Jun", year: "2026" },
|
||||
category: "Design / UX", title: "The future of Tokens", location: "Location",
|
||||
url: "#",
|
||||
image: {
|
||||
src: "https://d22po4pjz3o32e.cloudfront.net/placeholder-image-landscape.svg",
|
||||
alt: "Relume placeholder image 2",
|
||||
},
|
||||
date: { weekday: "Sat", day: "10", month: "Feb", year: "2024" },
|
||||
category: "Category",
|
||||
title: "Event title heading",
|
||||
location: "Location",
|
||||
description: "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse varius enim in eros.",
|
||||
button: { title: "View event", variant: "link", size: "link", iconRight: <RxChevronRight /> },
|
||||
},
|
||||
{
|
||||
url: "/programm",
|
||||
image: { src: workshop3, alt: "Trust in AI" },
|
||||
date: { weekday: "Sun", day: "26", month: "Jun", year: "2026" },
|
||||
category: "AI & Future", title: "Trust in AI", location: "Hall 101",
|
||||
url: "#",
|
||||
image: {
|
||||
src: "https://d22po4pjz3o32e.cloudfront.net/placeholder-image-landscape.svg",
|
||||
alt: "Relume placeholder image 3",
|
||||
},
|
||||
date: { weekday: "Sun", day: "11", month: "Feb", year: "2024" },
|
||||
category: "Category",
|
||||
title: "Event title heading",
|
||||
location: "Location",
|
||||
description: "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse varius enim in eros.",
|
||||
button: { title: "View event", variant: "link", size: "link", iconRight: <RxChevronRight /> },
|
||||
},
|
||||
|
||||
@ -1,83 +0,0 @@
|
||||
|
||||
export type EventLevel = "Senior" | "Mid-Level" | "Junior";
|
||||
export type EventType = "Talk" | "Workshop" | "Interaction" | "AI and Future";
|
||||
export type EventDay = "Donnerstag" | "Freitag";
|
||||
|
||||
export type ProgramEvent = {
|
||||
id: string;
|
||||
time: string;
|
||||
image: { src: string; alt: string };
|
||||
title: string;
|
||||
subtitle?: string;
|
||||
speaker: string;
|
||||
speakerSlug?: string;
|
||||
type: EventType;
|
||||
level: EventLevel;
|
||||
day: EventDay;
|
||||
speakerUrl: string;
|
||||
description?: string;
|
||||
};
|
||||
|
||||
type Props = {
|
||||
events: ProgramEvent[];
|
||||
};
|
||||
|
||||
const levelStyles: Record<EventLevel, string> = {
|
||||
Senior: "bg-electric-violet/20 text-electric-violet",
|
||||
"Mid-Level": "bg-electric-violet/20 text-electric-violet",
|
||||
Junior: "bg-electric-violet/20 text-electric-violet",
|
||||
};
|
||||
|
||||
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="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" />
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-1.5">
|
||||
<a href={`/programm/${event.id}`} className="hover:underline">
|
||||
<h5 className="text-lg font-bold text-tech-navy">{event.title}</h5>
|
||||
</a>
|
||||
<p className="text-sm text-gray-500">{event.speaker}</p>
|
||||
<span className={`w-fit rounded-md px-3 py-1 text-xs font-semibold ${levelStyles[event.level]}`}>
|
||||
{event.level}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<a
|
||||
href={event.speakerUrl}
|
||||
className="shrink-0 rounded-full border border-electric-violet px-4 py-1.5 text-sm font-semibold text-electric-violet hover:bg-electric-violet hover:text-cloud-white transition-colors"
|
||||
>
|
||||
Ü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>
|
||||
);
|
||||
|
||||
export const Event33 = ({ events }: Props) => {
|
||||
return (
|
||||
<section className="px-[5%] py-10 bg-cloud-white font-barlow">
|
||||
<div className="container">
|
||||
{events.length === 0 ? (
|
||||
<p className="py-16 text-center text-gray-400">Keine Veranstaltungen gefunden.</p>
|
||||
) : (
|
||||
<div>
|
||||
{events.map((event) => (
|
||||
<EventRow key={event.id} event={event} />
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
export default Event33;
|
||||
@ -1,263 +0,0 @@
|
||||
import { useState } from "react";
|
||||
import { BiFilter, BiSearch } from "react-icons/bi";
|
||||
import { IoCloseOutline } from "react-icons/io5";
|
||||
import { RxChevronDown } from "react-icons/rx";
|
||||
|
||||
export type FilterTab = { value: string; label: string };
|
||||
type ActiveTag = { label: string; onRemove: () => void };
|
||||
|
||||
type Props = {
|
||||
tabs?: FilterTab[];
|
||||
activeTab?: string;
|
||||
onTabChange?: (tab: string) => void;
|
||||
searchQuery?: string;
|
||||
onSearchChange?: (q: string) => void;
|
||||
searchLabel?: string;
|
||||
resetLabel?: string;
|
||||
activeTags?: ActiveTag[];
|
||||
onReset?: () => void;
|
||||
// Programm-only
|
||||
useFilterToggle?: boolean;
|
||||
showDayFilter?: boolean;
|
||||
showSortButton?: boolean;
|
||||
activeDay?: string;
|
||||
onDayChange?: (day: string) => void;
|
||||
};
|
||||
|
||||
const PROGRAMM_TABS: FilterTab[] = [
|
||||
{ value: "all", label: "Alle Anzeigen" },
|
||||
{ value: "Talk", label: "Talk" },
|
||||
{ value: "Workshop", label: "Workshop" },
|
||||
{ value: "Interaction", label: "Interaction" },
|
||||
{ value: "AI and Future", label: "AI and Future" },
|
||||
{ value: "Senior", label: "Senior Level" },
|
||||
{ value: "Mid-Level", label: "Mid-Level" },
|
||||
{ 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,
|
||||
activeTab = "all",
|
||||
onTabChange = () => {},
|
||||
searchQuery = "",
|
||||
onSearchChange = () => {},
|
||||
searchLabel = "Stichwortsuche",
|
||||
resetLabel = "Zurücksetzen",
|
||||
activeTags = [],
|
||||
onReset = () => {},
|
||||
useFilterToggle = true,
|
||||
showDayFilter = true,
|
||||
showSortButton = true,
|
||||
activeDay = "all",
|
||||
onDayChange = () => {},
|
||||
}: Props) => {
|
||||
const [sortOpen, setSortOpen] = useState(false);
|
||||
const [dayOpen, setDayOpen] = useState(false);
|
||||
|
||||
const selectedDayLabel = DAYS.find((d) => d.value === activeDay)?.label ?? "Alle Tage";
|
||||
|
||||
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
|
||||
if (!useFilterToggle) {
|
||||
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 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-tech-navy px-3">
|
||||
<BiSearch className="size-6 shrink-0 text-tech-navy" />
|
||||
<input
|
||||
className="flex-1 bg-transparent text-base text-tech-navy outline-none placeholder:text-tech-navy/40"
|
||||
placeholder="Suchen..."
|
||||
value={searchQuery}
|
||||
onChange={(e) => onSearchChange(e.target.value)}
|
||||
/>
|
||||
{searchQuery && (
|
||||
<button onClick={() => onSearchChange("")}>
|
||||
<IoCloseOutline className="size-5 text-tech-navy" />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
{activeTags.length > 0 && (
|
||||
<div className="flex flex-wrap gap-3">
|
||||
{activeTags.map((tag, i) => (
|
||||
<span
|
||||
key={i}
|
||||
className="flex items-center gap-2 rounded-lg bg-neutral-dark pl-4 pr-3 py-2 text-base text-cloud-white"
|
||||
>
|
||||
{tag.label}
|
||||
<button onClick={tag.onRemove}>
|
||||
<IoCloseOutline className="size-[18px]" />
|
||||
</button>
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
// Programm page: full filter bar, always visible
|
||||
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 */}
|
||||
<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">
|
||||
<BiFilter className="size-6" />
|
||||
Filter
|
||||
</div>
|
||||
|
||||
<div className="flex flex-1 items-center overflow-x-auto no-scrollbar">
|
||||
<TabPills />
|
||||
</div>
|
||||
|
||||
{showSortButton && (
|
||||
<div className="relative shrink-0">
|
||||
<button
|
||||
onClick={() => setSortOpen((v) => !v)}
|
||||
className="flex items-center gap-2 text-base font-semibold text-electric-violet"
|
||||
>
|
||||
Sortieren
|
||||
<RxChevronDown
|
||||
className={`size-6 transition-transform ${sortOpen ? "rotate-180" : ""}`}
|
||||
/>
|
||||
</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>
|
||||
))}
|
||||
</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>
|
||||
</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 */}
|
||||
{activeTags.length > 0 && (
|
||||
<div className="flex flex-wrap gap-3">
|
||||
{activeTags.map((tag, i) => (
|
||||
<span
|
||||
key={i}
|
||||
className="flex items-center gap-2 rounded-lg bg-neutral-dark pl-4 pr-3 py-2 text-base text-cloud-white"
|
||||
>
|
||||
{tag.label}
|
||||
<button onClick={tag.onRemove}>
|
||||
<IoCloseOutline className="size-[18px]" />
|
||||
</button>
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
export default Filters5;
|
||||
@ -1,4 +1,3 @@
|
||||
import LogoFooter from "../assets/logo-footer.png";
|
||||
import { FaXTwitter } from "react-icons/fa6";
|
||||
import {
|
||||
BiLogoFacebookCircle,
|
||||
@ -15,7 +14,7 @@ type ImageProps = {
|
||||
|
||||
type Links = {
|
||||
title: string;
|
||||
url?: string;
|
||||
url: string;
|
||||
};
|
||||
|
||||
type SocialMediaLinks = {
|
||||
@ -56,7 +55,7 @@ export const Footer3 = (props: Footer3Props) => {
|
||||
...props,
|
||||
};
|
||||
return (
|
||||
<footer id="relume" className="px-[5%] py-12 md:py-18 lg:py-20 bg-cloud-white font-barlow">
|
||||
<footer id="relume" className="px-[5%] py-12 md:py-18 lg:py-20">
|
||||
<div className="container">
|
||||
<div className="grid grid-cols-1 gap-x-[4vw] gap-y-12 pb-12 md:gap-y-16 md:pb-18 lg:grid-cols-[1fr_0.5fr] lg:gap-y-4 lg:pb-20">
|
||||
<div>
|
||||
@ -67,12 +66,12 @@ export const Footer3 = (props: Footer3Props) => {
|
||||
</div>
|
||||
<div className="rb-6 mb-6 md:mb-8">
|
||||
<div>
|
||||
<p className="mb-1 text-sm font-semibold text-neutral-dark">{address.label}</p>
|
||||
<p className="mb-5 text-sm md:mb-6 text-neutral-dark">{address.value}</p>
|
||||
<p className="mb-1 text-sm font-semibold">{address.label}</p>
|
||||
<p className="mb-5 text-sm md:mb-6">{address.value}</p>
|
||||
</div>
|
||||
<div>
|
||||
<p className="mb-1 text-sm font-semibold text-neutral-dark">{contact.label}</p>
|
||||
<p className="flex flex-col text-sm underline decoration-black underline-offset-1 md:mb-6 text-neutral-dark">
|
||||
<p className="mb-1 text-sm font-semibold">{contact.label}</p>
|
||||
<p className="flex flex-col text-sm underline decoration-black underline-offset-1 md:mb-6">
|
||||
<a href={`tel:${contact.phone}`}>{contact.phone}</a>
|
||||
<a href={`mailto:${contact.email}`}>{contact.email}</a>
|
||||
</p>
|
||||
@ -90,7 +89,7 @@ export const Footer3 = (props: Footer3Props) => {
|
||||
{columnLinks.map((column, index) => (
|
||||
<ul key={index}>
|
||||
{column.links.map((link, linkIndex) => (
|
||||
<li key={linkIndex} className="py-2 text-sm font-semibold text-neutral-dark">
|
||||
<li key={linkIndex} className="py-2 text-sm font-semibold">
|
||||
<a href={link.url}>{link.title}</a>
|
||||
</li>
|
||||
))}
|
||||
@ -98,7 +97,7 @@ export const Footer3 = (props: Footer3Props) => {
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<div className="h-px w-full bg-electric-green" />
|
||||
<div className="h-px w-full bg-black" />
|
||||
<div className="flex flex-col-reverse items-start justify-between pb-4 pt-6 text-sm md:flex-row md:items-center md:pb-0 md:pt-8">
|
||||
<p className="mt-8 md:mt-0">{footerText}</p>
|
||||
<ul className="grid grid-flow-row grid-cols-[max-content] justify-center gap-y-4 text-sm md:grid-flow-col md:gap-x-6 md:gap-y-0">
|
||||
@ -116,42 +115,50 @@ export const Footer3 = (props: Footer3Props) => {
|
||||
|
||||
export const Footer3Defaults: Props = {
|
||||
logo: {
|
||||
url: "/",
|
||||
src: LogoFooter,
|
||||
alt: "UXplore Logo",
|
||||
url: "#",
|
||||
src: "https://d22po4pjz3o32e.cloudfront.net/logo-image.svg",
|
||||
alt: "Logo image",
|
||||
},
|
||||
address: {
|
||||
label: "Address:",
|
||||
value: "Strasse Nr.\nPLZ Ort",
|
||||
value: "Level 1, 12 Sample St, Sydney NSW 2000",
|
||||
},
|
||||
contact: {
|
||||
label: "Kontakt:",
|
||||
phone: "079 123 45 67",
|
||||
email: "kontakt@uxplore.com",
|
||||
label: "Contact:",
|
||||
phone: "1800 123 4567",
|
||||
email: "info@relume.io",
|
||||
},
|
||||
columnLinks: [
|
||||
{
|
||||
links: [
|
||||
{ title: "Programm", url: "/programm" },
|
||||
{ title: "Speaker", url: "/speaker" },
|
||||
{ title: "Location", url: "#" },
|
||||
{ title: "Tickets", url: "#tickets" },
|
||||
{ title: "FAQ", url: "#faq" },
|
||||
{ title: "Link One", url: "#" },
|
||||
{ title: "Link Two", url: "#" },
|
||||
{ title: "Link Three", url: "#" },
|
||||
{ title: "Link Four", url: "#" },
|
||||
{ title: "Link Five", url: "#" },
|
||||
],
|
||||
},
|
||||
{
|
||||
links: [
|
||||
{ title: "Link Six", url: "#" },
|
||||
{ title: "Link Seven", url: "#" },
|
||||
{ title: "Link Eight", url: "#" },
|
||||
{ title: "Link Nine", url: "#" },
|
||||
{ title: "Link Ten", url: "#" },
|
||||
],
|
||||
},
|
||||
{ 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: "#", icon: <BiLogoFacebookCircle className="size-6" /> },
|
||||
{ url: "#", icon: <BiLogoInstagram className="size-6" /> },
|
||||
{ url: "#", icon: <FaXTwitter className="size-6 p-0.5" /> },
|
||||
{ url: "#", icon: <BiLogoLinkedinSquare className="size-6" /> },
|
||||
{ url: "#", icon: <BiLogoYoutube className="size-6" /> },
|
||||
],
|
||||
footerText: "© 2026 UXplore",
|
||||
footerText: "© 2024 Relume. All rights reserved.",
|
||||
footerLinks: [
|
||||
{ title: "AGB", url: "#" },
|
||||
{ title: "Datenschutz", url: "#" },
|
||||
{ title: "Cookie Einstellungen", url: "#" },
|
||||
{ title: "Privacy Policy", url: "#" },
|
||||
{ title: "Terms of Service", url: "#" },
|
||||
{ title: "Cookies Settings", url: "#" },
|
||||
],
|
||||
};
|
||||
};
|
||||
|
||||
@ -1,4 +1,3 @@
|
||||
import headerImage from "../assets/Header.png";
|
||||
import { Button } from "@relume_io/relume-ui";
|
||||
import type { ButtonProps } from "@relume_io/relume-ui";
|
||||
|
||||
@ -22,12 +21,12 @@ export const Header1 = (props: Header1Props) => {
|
||||
...props,
|
||||
};
|
||||
return (
|
||||
<section id="relume" className="px-[5%] py-16 md:py-24 lg:py-28 bg-tech-navy font-barlow">
|
||||
<section id="relume" className="px-[5%] py-16 md:py-24 lg:py-28">
|
||||
<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>
|
||||
<h1 className="mb-5 text-6xl font-bold md:mb-6 md:text-9xl lg:text-10xl">{heading}</h1>
|
||||
<p className="md:text-md">{description}</p>
|
||||
<div className="mt-6 flex flex-wrap gap-4 md:mt-8">
|
||||
{buttons.map((button, index) => (
|
||||
<Button key={index} {...button}>
|
||||
@ -46,12 +45,12 @@ export const Header1 = (props: Header1Props) => {
|
||||
};
|
||||
|
||||
export const Header1Defaults: Props = {
|
||||
heading: "UXplore Konferenz 2026",
|
||||
heading: "Medium length hero heading goes here",
|
||||
description:
|
||||
"25-26 Juni Zürich",
|
||||
buttons: [],
|
||||
"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse varius enim in eros elementum tristique. Duis cursus, mi quis viverra ornare, eros dolor interdum nulla, ut commodo diam libero vitae erat.",
|
||||
buttons: [{ title: "Button" }, { title: "Button", variant: "secondary" }],
|
||||
image: {
|
||||
src: headerImage,
|
||||
alt: "UXplore Konferenz 2026",
|
||||
src: "https://d22po4pjz3o32e.cloudfront.net/placeholder-image.svg",
|
||||
alt: "Relume placeholder image",
|
||||
},
|
||||
};
|
||||
|
||||
@ -1,60 +0,0 @@
|
||||
import headerBg from "../assets/program-header.png";
|
||||
import { Button } from "@relume_io/relume-ui";
|
||||
import type { ButtonProps } from "@relume_io/relume-ui";
|
||||
|
||||
type Props = {
|
||||
heading: string;
|
||||
description: string;
|
||||
buttons: ButtonProps[];
|
||||
};
|
||||
|
||||
export type Header23Props = React.ComponentPropsWithoutRef<"section"> & Partial<Props>;
|
||||
|
||||
export const Header23 = (props: Header23Props) => {
|
||||
const { heading, description, buttons, children } = {
|
||||
...Header23Defaults,
|
||||
...props,
|
||||
};
|
||||
return (
|
||||
<section className="px-[5%] py-16 md:py-24 lg:py-28 relative font-barlow overflow-hidden">
|
||||
{/* Background image with overlay */}
|
||||
<div className="absolute inset-0 pointer-events-none">
|
||||
<div className="absolute inset-0 bg-neutral-dark opacity-90" />
|
||||
<img
|
||||
src={headerBg}
|
||||
alt=""
|
||||
className="absolute inset-0 w-full h-full object-cover opacity-40"
|
||||
/>
|
||||
</div>
|
||||
{/* Content */}
|
||||
<div className="container relative z-10">
|
||||
<div className="mx-auto w-full max-w-lg text-center">
|
||||
<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>
|
||||
{buttons.length > 0 && (
|
||||
<div className="mt-6 flex items-center justify-center gap-x-4 md:mt-8">
|
||||
{buttons.map((button, index) => (
|
||||
<Button key={index} {...button}>
|
||||
{button.title}
|
||||
</Button>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
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.",
|
||||
buttons: [],
|
||||
};
|
||||
|
||||
export default Header23;
|
||||
@ -1,12 +1,8 @@
|
||||
import logo from "../assets/Logo.png";
|
||||
import { useState } from "react";
|
||||
import { Button, useMediaQuery } from "@relume_io/relume-ui";
|
||||
import type { ButtonProps } from "@relume_io/relume-ui";
|
||||
import { AnimatePresence, motion } from "framer-motion";
|
||||
import { RxChevronDown } from "react-icons/rx";
|
||||
import { Link } from "react-router-dom";
|
||||
|
||||
type NavButtonProps = ButtonProps & { url?: string };
|
||||
|
||||
type ImageProps = {
|
||||
url?: string;
|
||||
@ -15,7 +11,7 @@ type ImageProps = {
|
||||
};
|
||||
|
||||
type NavLink = {
|
||||
url?: string;
|
||||
url: string;
|
||||
title: string;
|
||||
subMenuLinks?: NavLink[];
|
||||
};
|
||||
@ -23,7 +19,7 @@ type NavLink = {
|
||||
type Props = {
|
||||
logo: ImageProps;
|
||||
navLinks: NavLink[];
|
||||
buttons: NavButtonProps[];
|
||||
buttons: ButtonProps[];
|
||||
};
|
||||
|
||||
export type Navbar3Props = React.ComponentPropsWithoutRef<"section"> & Partial<Props>;
|
||||
@ -39,7 +35,7 @@ export const Navbar3 = (props: Navbar3Props) => {
|
||||
return (
|
||||
<section
|
||||
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"
|
||||
className="z-[999] grid w-full grid-cols-[1fr_max-content_1fr] items-center justify-between border-b border-border-primary bg-background-primary px-[5%] md:min-h-18"
|
||||
>
|
||||
<button
|
||||
className="flex size-12 flex-col justify-center lg:hidden"
|
||||
@ -48,7 +44,7 @@ export const Navbar3 = (props: Navbar3Props) => {
|
||||
{Array(3)
|
||||
.fill(null)
|
||||
.map((_, index) => (
|
||||
<span key={index} className="my-[3px] h-0.5 w-6 bg-cloud-white lg:hidden" />
|
||||
<span key={index} className="my-[3px] h-0.5 w-6 bg-black lg:hidden" />
|
||||
))}
|
||||
</button>
|
||||
<AnimatePresence>
|
||||
@ -72,7 +68,7 @@ export const Navbar3 = (props: Navbar3Props) => {
|
||||
transition: { type: "spring", duration: 0.4, bounce: 0 },
|
||||
},
|
||||
}}
|
||||
className="absolute left-0 top-0 z-50 flex h-dvh w-[90%] flex-col border-r border-border-primary bg-tech-navy px-[5%] pb-4 md:w-[80%] lg:visible lg:static lg:-ml-4 lg:flex lg:h-auto lg:w-auto lg:flex-row lg:border-none lg:px-0 lg:pb-0 lg:[--opacity-closed:100%] lg:[--x-closed:0%]"
|
||||
className="absolute left-0 top-0 z-50 flex h-dvh w-[90%] flex-col border-r border-border-primary bg-white px-[5%] pb-4 md:w-[80%] lg:visible lg:static lg:-ml-4 lg:flex lg:h-auto lg:w-auto lg:flex-row lg:border-none lg:px-0 lg:pb-0 lg:[--opacity-closed:100%] lg:[--x-closed:0%]"
|
||||
>
|
||||
<a href={logo.url} className="mb-8 mt-10 flex flex-shrink-0 lg:hidden">
|
||||
<img src={logo.src} alt={logo.alt} />
|
||||
@ -84,7 +80,7 @@ export const Navbar3 = (props: Navbar3Props) => {
|
||||
) : (
|
||||
<a
|
||||
href={navLink.url}
|
||||
className="relative block py-3 text-md text-cloud-white lg:px-4 lg:py-2 lg:text-base"
|
||||
className="relative block py-3 text-md lg:px-4 lg:py-2 lg:text-base"
|
||||
>
|
||||
{navLink.title}
|
||||
</a>
|
||||
@ -92,15 +88,11 @@ export const Navbar3 = (props: Navbar3Props) => {
|
||||
</div>
|
||||
))}
|
||||
<div className="mt-6 lg:hidden">
|
||||
{buttons.map(({ url, ...button }, index) =>
|
||||
url ? (
|
||||
<Link key={index} to={url}>
|
||||
<Button {...button} className="w-full">{button.title}</Button>
|
||||
</Link>
|
||||
) : (
|
||||
<Button key={index} {...button} className="w-full">{button.title}</Button>
|
||||
)
|
||||
)}
|
||||
{buttons.map((button, index) => (
|
||||
<Button key={index} {...button} className="w-full">
|
||||
{button.title}
|
||||
</Button>
|
||||
))}
|
||||
</div>
|
||||
</motion.div>
|
||||
{isMobileMenuOpen && (
|
||||
@ -119,19 +111,11 @@ export const Navbar3 = (props: Navbar3Props) => {
|
||||
</a>
|
||||
<div className="flex min-h-16 items-center justify-end gap-x-4">
|
||||
<div>
|
||||
{buttons.map(({ url, ...button }, index) =>
|
||||
url ? (
|
||||
<Link key={index} to={url}>
|
||||
<Button {...button} className="px-5 py-2 bg-electric-violet text-cloud-white rounded-2xl border-electric-violet font-barlow font-semibold">
|
||||
{button.title}
|
||||
</Button>
|
||||
</Link>
|
||||
) : (
|
||||
<Button key={index} {...button} className="px-5 py-2 bg-electric-violet text-cloud-white rounded-2xl border-electric-violet font-barlow font-semibold">
|
||||
{button.title}
|
||||
</Button>
|
||||
)
|
||||
)}
|
||||
{buttons.map((button, index) => (
|
||||
<Button key={index} {...button} className="px-4 py-1 md:px-6 md:py-2">
|
||||
{button.title}
|
||||
</Button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
@ -202,21 +186,27 @@ const SubMenu = ({ navLink, isMobile }: { navLink: NavLink; isMobile: boolean })
|
||||
|
||||
export const Navbar3Defaults: Props = {
|
||||
logo: {
|
||||
url: "/",
|
||||
src: logo,
|
||||
alt: "UXplore Logo",
|
||||
url: "#",
|
||||
src: "https://d22po4pjz3o32e.cloudfront.net/logo-image.svg",
|
||||
alt: "Logo image",
|
||||
},
|
||||
navLinks: [
|
||||
{ title: "Programm", url: "/programm" },
|
||||
{ title: "Speaker", url: "/speaker" },
|
||||
{ title: "Location", url: "#" },
|
||||
{ title: "Link One", url: "#" },
|
||||
{ title: "Link Two", url: "#" },
|
||||
{
|
||||
title: "Link Three",
|
||||
url: "#",
|
||||
subMenuLinks: [
|
||||
{ title: "Link Four", url: "#" },
|
||||
{ title: "Link Five", url: "#" },
|
||||
{ title: "Link Six", url: "#" },
|
||||
],
|
||||
},
|
||||
],
|
||||
buttons: [
|
||||
{
|
||||
title: "Tickets",
|
||||
title: "Button",
|
||||
size: "sm",
|
||||
variant: "primary",
|
||||
url: "/tickets",
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
@ -1,79 +0,0 @@
|
||||
import React, { useState } from "react";
|
||||
|
||||
type TicketPlan = {
|
||||
period: string;
|
||||
price: number;
|
||||
date: string;
|
||||
};
|
||||
|
||||
type Props = {
|
||||
heading: string;
|
||||
plan: TicketPlan;
|
||||
};
|
||||
|
||||
export type Pricing1TicketProps = React.ComponentPropsWithoutRef<"section"> & Partial<Props>;
|
||||
|
||||
const TicketCardViolet = ({ plan }: { plan: TicketPlan }) => {
|
||||
const [qty, setQty] = useState(0);
|
||||
const total = qty * plan.price;
|
||||
|
||||
return (
|
||||
<div className="bg-electric-violet text-cloud-white p-6 md:p-8 flex flex-col gap-5 font-barlow border border-white/20">
|
||||
<p className="text-sm font-semibold">{plan.period}</p>
|
||||
<h3 className="text-6xl font-bold md:text-8xl">CHF {plan.price}</h3>
|
||||
<p className="text-sm">{plan.date}</p>
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-4">
|
||||
<button
|
||||
onClick={() => setQty((q) => Math.max(0, q - 1))}
|
||||
className="w-8 h-8 rounded-lg bg-acid-lime text-tech-navy flex items-center justify-center font-bold text-lg leading-none"
|
||||
>
|
||||
−
|
||||
</button>
|
||||
<span className="w-6 text-center font-semibold">{qty}</span>
|
||||
<button
|
||||
onClick={() => setQty((q) => q + 1)}
|
||||
className="w-8 h-8 rounded-lg bg-acid-lime text-tech-navy flex items-center justify-center font-bold text-lg leading-none"
|
||||
>
|
||||
+
|
||||
</button>
|
||||
</div>
|
||||
<div className="text-right text-sm font-semibold">
|
||||
<div>CHF {plan.price},-</div>
|
||||
<div>{total},-</div>
|
||||
</div>
|
||||
</div>
|
||||
<button className="w-full rounded-2xl bg-acid-lime text-tech-navy font-semibold py-3 text-sm">
|
||||
Ticket sichern
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const Pricing1Ticket = (props: Pricing1TicketProps) => {
|
||||
const { heading, plan } = { ...Pricing1TicketDefaults, ...props };
|
||||
|
||||
return (
|
||||
<section className="px-[5%] py-16 md:py-24 bg-neutral-dark font-barlow">
|
||||
<div className="container">
|
||||
<h2 className="mb-10 text-center text-4xl font-bold text-cloud-white md:mb-14 md:text-5xl">
|
||||
{heading}
|
||||
</h2>
|
||||
<div className="mx-auto w-full max-w-sm">
|
||||
<TicketCardViolet plan={plan} />
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
export const Pricing1TicketDefaults: Props = {
|
||||
heading: "Earlybird",
|
||||
plan: {
|
||||
period: "DO – FR",
|
||||
price: 250,
|
||||
date: "25. – 26. Juni 2026",
|
||||
},
|
||||
};
|
||||
|
||||
export default Pricing1Ticket;
|
||||
@ -1,88 +0,0 @@
|
||||
import React, { useState } from "react";
|
||||
|
||||
type TicketPlan = {
|
||||
day: string;
|
||||
price: number;
|
||||
date: string;
|
||||
};
|
||||
|
||||
type Props = {
|
||||
heading: string;
|
||||
plans: TicketPlan[];
|
||||
};
|
||||
|
||||
export type Pricing39TicketProps = React.ComponentPropsWithoutRef<"section"> & Partial<Props>;
|
||||
|
||||
const TicketCardLime = ({ plan }: { plan: TicketPlan }) => {
|
||||
const [qty, setQty] = useState(0);
|
||||
const total = qty * plan.price;
|
||||
|
||||
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="flex items-center justify-between">
|
||||
<div className="flex items-center gap-4">
|
||||
<button
|
||||
onClick={() => setQty((q) => Math.max(0, q - 1))}
|
||||
className="w-8 h-8 rounded-lg bg-electric-violet text-cloud-white flex items-center justify-center font-bold text-lg leading-none"
|
||||
>
|
||||
−
|
||||
</button>
|
||||
<span className="w-6 text-center font-semibold">{qty}</span>
|
||||
<button
|
||||
onClick={() => setQty((q) => q + 1)}
|
||||
className="w-8 h-8 rounded-lg bg-electric-violet text-cloud-white flex items-center justify-center font-bold text-lg leading-none"
|
||||
>
|
||||
+
|
||||
</button>
|
||||
</div>
|
||||
<div className="text-right text-sm font-semibold">
|
||||
<div>CHF {plan.price},-</div>
|
||||
<div>{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>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const Pricing39Ticket = (props: Pricing39TicketProps) => {
|
||||
const { heading, plans } = { ...Pricing39TicketDefaults, ...props };
|
||||
|
||||
return (
|
||||
<section className="px-[5%] py-16 md:py-24 bg-white font-barlow">
|
||||
<div className="container">
|
||||
<h2 className="mb-10 text-4xl font-bold text-tech-navy md:mb-14 md:text-5xl">
|
||||
{heading}
|
||||
</h2>
|
||||
<div
|
||||
className={`grid gap-6 ${
|
||||
plans.length === 1
|
||||
? "grid-cols-1 max-w-sm"
|
||||
: plans.length === 2
|
||||
? "grid-cols-1 sm:grid-cols-2 max-w-2xl"
|
||||
: "grid-cols-1 sm:grid-cols-2 lg:grid-cols-3"
|
||||
}`}
|
||||
>
|
||||
{plans.map((plan, index) => (
|
||||
<TicketCardLime key={index} plan={plan} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
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" },
|
||||
],
|
||||
};
|
||||
|
||||
export default Pricing39Ticket;
|
||||
@ -1,217 +0,0 @@
|
||||
import speaker1 from "../assets/speakers/speaker1.jpg";
|
||||
import jensDetail from "../assets/speaker-detail/jens.jpg";
|
||||
import speaker2 from "../assets/speakers/speaker2.jpg";
|
||||
import speaker3 from "../assets/speakers/speaker3.jpg";
|
||||
import speaker4 from "../assets/speakers/speaker4.jpg";
|
||||
import speaker5 from "../assets/speakers/speaker5.jpg";
|
||||
import speaker6 from "../assets/speakers/speaker6.jpg";
|
||||
import { BiLogoLinkedinSquare } from "react-icons/bi";
|
||||
import { Link } from "react-router-dom";
|
||||
|
||||
type ImageProps = {
|
||||
src: string;
|
||||
alt?: string;
|
||||
};
|
||||
|
||||
type SocialLink = {
|
||||
href: string;
|
||||
icon: React.ReactNode;
|
||||
};
|
||||
|
||||
type Talk = {
|
||||
title: string;
|
||||
type: "Talk" | "Workshop" | "Networking" | "AI and Future" | "Interaction";
|
||||
};
|
||||
|
||||
export type TeamMember = {
|
||||
image: ImageProps;
|
||||
detailImage?: ImageProps;
|
||||
name: string;
|
||||
role: string;
|
||||
company: string;
|
||||
companyUrl?: string;
|
||||
description: string;
|
||||
fullBio?: string;
|
||||
category: string;
|
||||
location?: string;
|
||||
slug: string;
|
||||
talks: Talk[];
|
||||
socialLinks: SocialLink[];
|
||||
};
|
||||
|
||||
type Props = {
|
||||
teamMembers: TeamMember[];
|
||||
};
|
||||
|
||||
export type Team4Props = React.ComponentPropsWithoutRef<"section"> & Partial<Props>;
|
||||
|
||||
const talkBadgeClass = (type: Talk["type"]) => {
|
||||
if (type === "Talk") return "bg-neutral-dark text-cloud-white";
|
||||
return "bg-electric-violet text-cloud-white";
|
||||
};
|
||||
|
||||
const MemberCard = ({ member }: { member: TeamMember }) => (
|
||||
<div className="flex flex-col font-barlow">
|
||||
<Link to={`/speaker/${member.slug}`} className="relative mb-6 aspect-square w-full overflow-hidden block">
|
||||
<img
|
||||
src={member.image.src}
|
||||
alt={member.image.alt}
|
||||
className="absolute inset-0 size-full object-cover hover:scale-105 transition-transform duration-300"
|
||||
/>
|
||||
</Link>
|
||||
<div className="flex flex-col gap-1 mb-4">
|
||||
<div className="flex items-center justify-between gap-2">
|
||||
<Link to={`/speaker/${member.slug}`} className="hover:underline">
|
||||
<h5 className="text-xl font-semibold text-tech-navy leading-snug">{member.name}</h5>
|
||||
</Link>
|
||||
{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>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<p className="text-lg text-tech-navy">{member.role}</p>
|
||||
{member.companyUrl ? (
|
||||
<a href={member.companyUrl} className="text-lg underline underline-offset-2 text-tech-navy">
|
||||
{member.company}
|
||||
</a>
|
||||
) : (
|
||||
<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>
|
||||
</div>
|
||||
);
|
||||
|
||||
export const Team4 = (props: Team4Props) => {
|
||||
const { teamMembers } = { ...Team4Defaults, ...props };
|
||||
|
||||
return (
|
||||
<section className="px-[5%] py-16 md:py-24 bg-cloud-white font-barlow">
|
||||
<div className="container">
|
||||
{teamMembers.length === 0 ? (
|
||||
<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} />
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
export const Team4Defaults: Props = {
|
||||
teamMembers: [
|
||||
{
|
||||
image: { src: speaker1, alt: "Jens Riegelsberger" },
|
||||
detailImage: { src: jensDetail, alt: "Jens Riegelsberger" },
|
||||
name: "Jens Riegelsberger",
|
||||
role: "UX Director",
|
||||
company: "Google",
|
||||
slug: "jens-riegelsberger",
|
||||
location: "London, Vereinigtes Königreich",
|
||||
description:
|
||||
"Jens leitet die UX-Teams für Search und Maps sowie die globale UXR-Infrastruktur. Der HCI-Experte lehrte als Gastprofessor an der UdK Berlin und bringt Erfahrung von Stationen bei Microsoft Research, Amazon und Apple mit.",
|
||||
fullBio:
|
||||
"Jens Riegelsberger ist UX Director bei Google und leitet die UX-Teams für die Bereiche Search, Maps und Nutzerkonten sowie das UXR-Infrastruktur-team von Google. Er promovierte an der University College London im Bereich Mensch-Computer-Interaktion und war stellvertretender Herausgeber des IJHCS. Bevor er zu Google kam, arbeitete Jens bei der UX-Beratungsfirma LBi und lehrte als Gastprofessor an der Universität der Künste Berlin; zu seinen früheren beruflichen Stationen zählen Tätigkeiten bei Microsoft Research, Amazon und Apple.",
|
||||
category: "Research",
|
||||
talks: [
|
||||
{ title: "Talk: Scaling Research", type: "Talk" },
|
||||
{ title: "Workshop: Skalierbare Research-Infrastruktur", type: "Workshop" },
|
||||
],
|
||||
socialLinks: [{ href: "#", icon: <BiLogoLinkedinSquare className="size-6" /> }],
|
||||
},
|
||||
{
|
||||
image: { src: speaker2, alt: "Marcus J. Low" },
|
||||
name: "Marcus J. Low",
|
||||
role: "Principal Designer",
|
||||
company: "Airbnb",
|
||||
slug: "marcus-j-low",
|
||||
location: "San Francisco, USA",
|
||||
description:
|
||||
"Als Mitentwickler des ersten Airbnb Design Systems spricht Marcus über die Balance zwischen Markenästhetik und funktionaler Logik.",
|
||||
fullBio:
|
||||
"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" /> }],
|
||||
},
|
||||
{
|
||||
image: { src: speaker3, alt: "Reto Gwerder" },
|
||||
name: "Reto Gwerder",
|
||||
role: "Head of Product",
|
||||
company: "Ginetta",
|
||||
slug: "reto-gwerder",
|
||||
location: "Zürich, Schweiz",
|
||||
description:
|
||||
"Reto steht für Schweizer Design-Qualität. Er analysiert, warum Simplizität oft die größte technische Herausforderung ist.",
|
||||
fullBio:
|
||||
"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" /> }],
|
||||
},
|
||||
{
|
||||
image: { src: speaker4, alt: "Dr. Elena Rossi" },
|
||||
name: "Dr. Elena Rossi",
|
||||
role: "Cognitive Psychologist",
|
||||
company: "University of Milan",
|
||||
slug: "dr-elena-rossi",
|
||||
location: "Mailand, Italien",
|
||||
description:
|
||||
"Elena verbindet Wissenschaft mit Design. Sie erklärt, wie unser Gehirn auf Micro-Interactions reagiert und wann wir uns manipuliert fühlen.",
|
||||
fullBio:
|
||||
"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" /> }],
|
||||
},
|
||||
{
|
||||
image: { src: speaker5, alt: "Liam O'Connor" },
|
||||
name: "Liam O'Connor",
|
||||
role: "Lead Product Designer",
|
||||
company: "Spotify",
|
||||
slug: "liam-oconnor",
|
||||
location: "Stockholm, Schweden",
|
||||
description:
|
||||
"Liam gibt Einblicke, wie Spotify Personalisierung nutzt, ohne die Privatsphäre der Nutzer zu verletzen.",
|
||||
fullBio:
|
||||
"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" /> }],
|
||||
},
|
||||
{
|
||||
image: { src: speaker6, alt: "Fabienne Keller" },
|
||||
name: "Fabienne Keller",
|
||||
role: "Gründerin",
|
||||
company: "User-First Agency",
|
||||
slug: "fabienne-keller",
|
||||
location: "Basel, Schweiz",
|
||||
description:
|
||||
"Fabienne ist Expertin für User Research in komplexen B2B-Umfeldern und wie man Stakeholder von Test-Ergebnissen überzeugt.",
|
||||
fullBio:
|
||||
"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" /> }],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export default Team4;
|
||||
@ -1,177 +0,0 @@
|
||||
import spk1 from "../assets/speakers/speaker1.jpg";
|
||||
import spk2 from "../assets/speakers/speaker2.jpg";
|
||||
import spk3 from "../assets/speakers/speaker3.jpg";
|
||||
import spk4 from "../assets/speakers/speaker4.jpg";
|
||||
import spk5 from "../assets/speakers/speaker5.jpg";
|
||||
import spk6 from "../assets/speakers/speaker6.jpg";
|
||||
import type { ProgramEvent } from "../components/Event33";
|
||||
|
||||
export const ALL_EVENTS: ProgramEvent[] = [
|
||||
// Donnerstag
|
||||
{
|
||||
id: "do-1",
|
||||
time: "10:00 – 11:30 Uhr",
|
||||
image: { src: spk1, alt: "Jens Riegelsberger" },
|
||||
title: "Opening Keynote: The Future of UX",
|
||||
subtitle: "Wie KI, Ethik und Nutzerzentriertheit die nächste UX-Ära prägen",
|
||||
speaker: "Jens Riegelsberger",
|
||||
speakerSlug: "jens-riegelsberger",
|
||||
type: "Talk",
|
||||
level: "Senior",
|
||||
day: "Donnerstag",
|
||||
speakerUrl: "/speaker/jens-riegelsberger",
|
||||
description:
|
||||
"In dieser Keynote zeigt Jens Riegelsberger, wie Google seine UX-Teams für Search und Maps aufgebaut hat und welche Rolle Nutzerzentriertheit, Ethik und KI in der nächsten Designgeneration spielen werden. Ein Muss für alle, die verstehen wollen, wohin sich UX in den nächsten fünf Jahren bewegt.",
|
||||
},
|
||||
{
|
||||
id: "do-2",
|
||||
time: "12:00 – 13:00 Uhr",
|
||||
image: { src: spk2, alt: "Marcus J. Low" },
|
||||
title: "Workshop: Design Systems at Scale",
|
||||
subtitle: "Wie Airbnb seine Designsprache global skaliert",
|
||||
speaker: "Marcus J. Low",
|
||||
speakerSlug: "marcus-j-low",
|
||||
type: "Workshop",
|
||||
level: "Mid-Level",
|
||||
day: "Donnerstag",
|
||||
speakerUrl: "/speaker/marcus-j-low",
|
||||
description:
|
||||
"Als Mitentwickler des ersten Airbnb Design Systems spricht Marcus über die Balance zwischen Markenästhetik und funktionaler Logik. In diesem Workshop lernst du, wie du ein Design System aufbaust, das wächst ohne zu brechen.",
|
||||
},
|
||||
{
|
||||
id: "do-3",
|
||||
time: "14:00 – 15:30 Uhr",
|
||||
image: { src: spk3, alt: "Reto Gwerder" },
|
||||
title: "Interaction: Prototyping Fast",
|
||||
subtitle: "Schnell iterieren ohne Qualitätsverlust",
|
||||
speaker: "Reto Gwerder",
|
||||
speakerSlug: "reto-gwerder",
|
||||
type: "Interaction",
|
||||
level: "Junior",
|
||||
day: "Donnerstag",
|
||||
speakerUrl: "/speaker/reto-gwerder",
|
||||
description:
|
||||
"Reto zeigt, wie du mit minimalem Aufwand maximale Erkenntnisse aus Prototypen ziehst. Praktische Methoden für schnelles Iterieren, ohne die Qualität zu opfern.",
|
||||
},
|
||||
{
|
||||
id: "do-4",
|
||||
time: "16:00 – 17:30 Uhr",
|
||||
image: { src: spk4, alt: "Dr. Elena Rossi" },
|
||||
title: "AI and Future: Intelligence in Design",
|
||||
subtitle: "Wenn Algorithmen mitgestalten – Chancen und Risiken",
|
||||
speaker: "Dr. Elena Rossi",
|
||||
speakerSlug: "dr-elena-rossi",
|
||||
type: "AI and Future",
|
||||
level: "Senior",
|
||||
day: "Donnerstag",
|
||||
speakerUrl: "/speaker/dr-elena-rossi",
|
||||
description:
|
||||
"Dr. Elena Rossi untersucht die psychologischen Auswirkungen von KI-gestütztem Design auf Nutzer. Wann erleben wir KI als hilfreich – und wann als manipulativ? Ein tiefes Tauchen in die Kognitionspsychologie des digitalen Zeitalters.",
|
||||
},
|
||||
{
|
||||
id: "do-5",
|
||||
time: "18:00 – 19:30 Uhr",
|
||||
image: { src: spk5, alt: "Liam O'Connor" },
|
||||
title: "Talk: Ethical AI",
|
||||
subtitle: "Verantwortungsvolles Design in der KI-Ära",
|
||||
speaker: "Liam O'Connor",
|
||||
speakerSlug: "liam-oconnor",
|
||||
type: "Talk",
|
||||
level: "Mid-Level",
|
||||
day: "Donnerstag",
|
||||
speakerUrl: "/speaker/liam-oconnor",
|
||||
description:
|
||||
"Liam O'Connor gibt Einblicke, wie Spotify mit ethischen Fragen rund um personalisierte Empfehlungen umgeht. Was bedeutet es, verantwortungsvoll zu gestalten, wenn Algorithmen Entscheidungen treffen?",
|
||||
},
|
||||
// Freitag
|
||||
{
|
||||
id: "fr-1",
|
||||
time: "12:00 – 13:30 Uhr",
|
||||
image: { src: spk1, alt: "Jens Riegelsberger" },
|
||||
title: "Scaling Research",
|
||||
subtitle: "Vom Engpass zum Motor: Scaling Research ohne Qualitätsverlust",
|
||||
speaker: "Jens Riegelsberger",
|
||||
speakerSlug: "jens-riegelsberger",
|
||||
type: "Talk",
|
||||
level: "Senior",
|
||||
day: "Freitag",
|
||||
speakerUrl: "/speaker/jens-riegelsberger",
|
||||
description:
|
||||
"Jeder will Insights, aber dein Research-Team wächst nicht so schnell wie der Backlog? Willkommen im Club. Wenn die Nachfrage nach Nutzererkenntnissen explodiert, wird das Research-Team oft zum Flaschenhals. Aber \"Scaling Research\" bedeutet nicht einfach, mehr Überstunden zu machen oder wahllos Tools zu kaufen. Es geht darum, Systeme zu schaffen, die Wissen demokratisieren, ohne die methodische Integrität zu opfern. In diesem interaktiven Workshop räumen wir mit dem Mythos auf, dass Research nur von Researchern gemacht werden kann. Wir zeigen dir, wie du Research Ops sinnvoll aufbaust, Nicht-Researcher befähigst und eine Kultur schaffst, in der Insights fließen statt zu verstauben.",
|
||||
},
|
||||
{
|
||||
id: "fr-2",
|
||||
time: "12:00 – 13:00 Uhr",
|
||||
image: { src: spk2, alt: "Marcus J. Low" },
|
||||
title: "Workshop: The Future of Tokens",
|
||||
subtitle: "Design Tokens als Brücke zwischen Design und Entwicklung",
|
||||
speaker: "Marcus J. Low",
|
||||
speakerSlug: "marcus-j-low",
|
||||
type: "Workshop",
|
||||
level: "Mid-Level",
|
||||
day: "Freitag",
|
||||
speakerUrl: "/speaker/marcus-j-low",
|
||||
description:
|
||||
"Design Tokens sind mehr als nur Variablen. Marcus zeigt, wie du ein Token-System aufbaust, das Design und Entwicklung wirklich verbindet und auf alle Plattformen skaliert.",
|
||||
},
|
||||
{
|
||||
id: "fr-3",
|
||||
time: "14:00 – 15:30 Uhr",
|
||||
image: { src: spk3, alt: "Reto Gwerder" },
|
||||
title: "Talk: Simplify or Die",
|
||||
subtitle: "Warum Simplizität die härteste Designentscheidung ist",
|
||||
speaker: "Reto Gwerder",
|
||||
speakerSlug: "reto-gwerder",
|
||||
type: "Talk",
|
||||
level: "Junior",
|
||||
day: "Freitag",
|
||||
speakerUrl: "/speaker/reto-gwerder",
|
||||
description:
|
||||
"Reto Gwerder analysiert, warum Simplizität oft die größte technische und gestalterische Herausforderung ist. Weniger ist mehr – aber wie schafft man es, wirklich weniger zu machen?",
|
||||
},
|
||||
{
|
||||
id: "fr-4",
|
||||
time: "16:00 – 17:30 Uhr",
|
||||
image: { src: spk4, alt: "Dr. Elena Rossi" },
|
||||
title: "Deep Dive: Psychological UX",
|
||||
subtitle: "Wie unser Gehirn auf Interfaces reagiert",
|
||||
speaker: "Dr. Elena Rossi",
|
||||
speakerSlug: "dr-elena-rossi",
|
||||
type: "Talk",
|
||||
level: "Senior",
|
||||
day: "Freitag",
|
||||
speakerUrl: "/speaker/dr-elena-rossi",
|
||||
description:
|
||||
"Elena verbindet Wissenschaft mit Design und erklärt, wie unser Gehirn auf Micro-Interactions reagiert. Wann fühlen wir uns von einem Interface gut geführt – und wann manipuliert? Ein wissenschaftlich fundierter Blick auf die Psychologie hinter UX-Entscheidungen.",
|
||||
},
|
||||
{
|
||||
id: "fr-5",
|
||||
time: "18:00 – 19:30 Uhr",
|
||||
image: { src: spk5, alt: "Liam O'Connor" },
|
||||
title: "Talk: Trust in AI",
|
||||
subtitle: "Wie wir Vertrauen in algorithmische Systeme aufbauen",
|
||||
speaker: "Liam O'Connor",
|
||||
speakerSlug: "liam-oconnor",
|
||||
type: "Talk",
|
||||
level: "Mid-Level",
|
||||
day: "Freitag",
|
||||
speakerUrl: "/speaker/liam-oconnor",
|
||||
description:
|
||||
"Vertrauen ist die Währung der Zukunft. Liam erklärt, wie Spotify transparente und ethische KI-Systeme gestaltet, die Nutzer verstehen und akzeptieren können.",
|
||||
},
|
||||
{
|
||||
id: "fr-6",
|
||||
time: "20:00 – 21:30 Uhr",
|
||||
image: { src: spk6, alt: "Fabienne Keller" },
|
||||
title: "Workshop: Research Repositories",
|
||||
subtitle: "Wissen organisieren, das wirklich genutzt wird",
|
||||
speaker: "Fabienne Keller",
|
||||
speakerSlug: "fabienne-keller",
|
||||
type: "Workshop",
|
||||
level: "Senior",
|
||||
day: "Freitag",
|
||||
speakerUrl: "/speaker/fabienne-keller",
|
||||
description:
|
||||
"Fabienne zeigt, wie du ein Research Repository aufbaust, das nicht im Regal verstaubt. Praktische Methoden für die Organisation, Pflege und Aktivierung von Research-Wissen in deinem Team.",
|
||||
},
|
||||
];
|
||||
6
relume-test/src/declarations.d.ts
vendored
@ -1,6 +0,0 @@
|
||||
declare module "*.png";
|
||||
declare module "*.jpg";
|
||||
declare module "*.jpeg";
|
||||
declare module "*.svg";
|
||||
declare module "*.webp";
|
||||
declare module "*.css";
|
||||
@ -1,23 +1,3 @@
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
@layer base {
|
||||
:root {
|
||||
--color-scheme-1-background: #f8fafc;
|
||||
--color-scheme-1-text: #f8fafc;
|
||||
--color-scheme-1-accent: #895af6;
|
||||
--color-scheme-1-border: #895af6;
|
||||
--color-tech-navy: #1d283a;
|
||||
--color-acid-lime: #cfff24;
|
||||
--color-electric-violet: #895af6;
|
||||
--color-electric-green: #00d949;
|
||||
--color-cloud-white: #f8fafc;
|
||||
--color-neutral-dark: #283c5e;
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: #1d283a;
|
||||
font-family: "Barlow", sans-serif;
|
||||
}
|
||||
}
|
||||
@tailwind utilities;
|
||||
@ -1,13 +1,10 @@
|
||||
import { StrictMode } from 'react'
|
||||
import { createRoot } from 'react-dom/client'
|
||||
import { BrowserRouter } from 'react-router-dom'
|
||||
import './index.css'
|
||||
import App from './App'
|
||||
|
||||
createRoot(document.getElementById('root')!).render(
|
||||
<StrictMode>
|
||||
<BrowserRouter>
|
||||
<App />
|
||||
</BrowserRouter>
|
||||
<App />
|
||||
</StrictMode>,
|
||||
)
|
||||
@ -1,9 +1,11 @@
|
||||
import { Navbar3 } from "../components/Navbar";
|
||||
import { Header1 } from "../components/Header";
|
||||
import { Content11 } from "../components/Content11";
|
||||
import { Event17 } from "../components/Event17";
|
||||
import { Event20 } from "../components/Event20";
|
||||
import { Gallery19 } from "../components/Gallery19";
|
||||
import { Cta30 } from "../components/Cta30";
|
||||
import { Event20 } from "../components/Event20";
|
||||
import { Pricing18 } from "../components/Pricing18";
|
||||
import { Faq10 } from "../components/Faq10";
|
||||
import { Footer3 } from "../components/Footer";
|
||||
|
||||
export const Home = () => {
|
||||
@ -11,10 +13,12 @@ export const Home = () => {
|
||||
<div>
|
||||
<Navbar3 />
|
||||
<Header1 />
|
||||
<Content11 />
|
||||
<Event17 />
|
||||
<Event20 />
|
||||
<Gallery19 />
|
||||
<Cta30 />
|
||||
<Event20 />
|
||||
<Pricing18 />
|
||||
<Faq10 />
|
||||
<Footer3 />
|
||||
</div>
|
||||
);
|
||||
|
||||
@ -1,71 +0,0 @@
|
||||
import { useMemo, useState } from "react";
|
||||
import { Navbar3 } from "../components/Navbar";
|
||||
import { Header23 } from "../components/Header23";
|
||||
import { Filters5 } from "../components/Filters5";
|
||||
import { Event33 } from "../components/Event33";
|
||||
import { Banner15 } from "../components/Banner15";
|
||||
import { Footer3 } from "../components/Footer";
|
||||
import { ALL_EVENTS } from "../data/events";
|
||||
|
||||
const TYPE_TABS = ["Talk", "Workshop", "Interaction", "AI and Future"];
|
||||
const LEVEL_TABS: Record<string, string> = {
|
||||
"Senior": "Senior",
|
||||
"Mid-Level": "Mid-Level",
|
||||
"Junior": "Junior",
|
||||
};
|
||||
|
||||
const Programm = () => {
|
||||
const [activeTab, setActiveTab] = useState("all");
|
||||
const [searchQuery, setSearchQuery] = useState("");
|
||||
const [activeDay, setActiveDay] = useState("all");
|
||||
|
||||
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;
|
||||
}
|
||||
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]);
|
||||
|
||||
const activeTags = [
|
||||
searchQuery ? { label: searchQuery, onRemove: () => setSearchQuery("") } : null,
|
||||
activeDay !== "all" ? { label: activeDay, onRemove: () => setActiveDay("all") } : null,
|
||||
activeTab !== "all" ? { label: activeTab, onRemove: () => setActiveTab("all") } : null,
|
||||
].filter(Boolean) as { label: string; onRemove: () => void }[];
|
||||
|
||||
const handleReset = () => {
|
||||
setActiveTab("all");
|
||||
setSearchQuery("");
|
||||
setActiveDay("all");
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Navbar3 />
|
||||
<Header23 />
|
||||
<Filters5
|
||||
activeTab={activeTab}
|
||||
onTabChange={setActiveTab}
|
||||
searchQuery={searchQuery}
|
||||
onSearchChange={setSearchQuery}
|
||||
activeDay={activeDay}
|
||||
onDayChange={setActiveDay}
|
||||
activeTags={activeTags}
|
||||
onReset={handleReset}
|
||||
/>
|
||||
<Event33 events={filteredEvents} />
|
||||
<Banner15 />
|
||||
<Footer3 />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Programm;
|
||||
@ -1,162 +0,0 @@
|
||||
import { useParams, Navigate, Link } from "react-router-dom";
|
||||
import { Navbar3 } from "../components/Navbar";
|
||||
import { Cta30 } from "../components/Cta30";
|
||||
import { Footer3 } from "../components/Footer";
|
||||
import { Team4Defaults } from "../components/Team4";
|
||||
import { ALL_EVENTS } from "../data/events";
|
||||
import { BiCalendarAlt, BiUser, BiLogoLinkedinSquare, BiLogoDribbble } from "react-icons/bi";
|
||||
import { SiXing } from "react-icons/si";
|
||||
import { RxCube } from "react-icons/rx";
|
||||
import webflowLogo from "../assets/webflow.png";
|
||||
import relumeLogo from "../assets/relume.png";
|
||||
|
||||
const SPONSORS = [
|
||||
{ src: webflowLogo, alt: "Webflow" },
|
||||
{ src: relumeLogo, alt: "Relume" },
|
||||
];
|
||||
|
||||
const SponsorsSection = () => (
|
||||
<section className="bg-tech-navy px-[5%] py-16 md:py-24 font-barlow">
|
||||
<div className="container">
|
||||
<h2 className="mb-12 text-center text-4xl font-bold text-cloud-white md:text-5xl">
|
||||
Unsere Sponsoren & Partner
|
||||
</h2>
|
||||
<div className="flex flex-wrap items-center justify-center gap-8">
|
||||
{SPONSORS.map((sponsor) => (
|
||||
<div
|
||||
key={sponsor.alt}
|
||||
className="flex items-center justify-center rounded-lg border border-white/10 bg-white/5 px-8 py-6"
|
||||
>
|
||||
<img src={sponsor.src} alt={sponsor.alt} className="h-10 w-auto object-contain" />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
|
||||
const ProgrammDetail = () => {
|
||||
const { id } = useParams<{ id: string }>();
|
||||
const event = ALL_EVENTS.find((e) => e.id === id);
|
||||
|
||||
if (!event) return <Navigate to="/programm" replace />;
|
||||
|
||||
const speaker = Team4Defaults.teamMembers.find((m) => m.slug === event.speakerSlug);
|
||||
|
||||
const dateLabel = event.day === "Donnerstag"
|
||||
? "Donnerstag, 25. Juni 2026"
|
||||
: "Freitag, 26. Juni 2026";
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Navbar3 />
|
||||
|
||||
{/* Hero section */}
|
||||
<section className="bg-tech-navy px-[5%] py-16 md:py-24 font-barlow">
|
||||
<div className="container">
|
||||
<div className="grid grid-cols-1 items-center gap-12 lg:grid-cols-2 lg:gap-20">
|
||||
{/* Left: event info */}
|
||||
<div>
|
||||
<span className="mb-4 inline-block rounded-full border border-electric-violet px-3 py-1 text-xs font-semibold text-electric-violet">
|
||||
{event.type}
|
||||
</span>
|
||||
<h1 className="mb-4 text-5xl font-bold text-cloud-white md:text-6xl lg:text-7xl">
|
||||
{event.title}
|
||||
</h1>
|
||||
{event.subtitle && (
|
||||
<p className="mb-8 text-lg text-cloud-white/70">{event.subtitle}</p>
|
||||
)}
|
||||
<div className="mb-8 flex flex-col gap-3">
|
||||
<div className="flex items-center gap-3 text-cloud-white/80">
|
||||
<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">
|
||||
<BiUser className="size-5 shrink-0 text-acid-lime" />
|
||||
<span className="text-sm">{event.speaker}</span>
|
||||
</div>
|
||||
</div>
|
||||
<Link
|
||||
to="/tickets"
|
||||
className="inline-block rounded-2xl bg-acid-lime px-8 py-3 text-base font-bold text-tech-navy hover:opacity-90 transition-opacity"
|
||||
>
|
||||
Ticket kaufen
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
{/* Right: speaker image */}
|
||||
<div className="aspect-square w-full max-w-md overflow-hidden lg:ml-auto">
|
||||
<img
|
||||
src={event.image.src}
|
||||
alt={event.image.alt}
|
||||
className="size-full object-cover object-top"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Speaker bio + event description */}
|
||||
<section className="bg-tech-navy px-[5%] pb-16 md:pb-24 font-barlow">
|
||||
<div className="container border-t border-white/10 pt-16">
|
||||
<div className="grid grid-cols-1 gap-12 lg:grid-cols-2 lg:gap-20">
|
||||
{/* Left: über den speaker */}
|
||||
<div>
|
||||
<RxCube className="mb-4 size-8 text-electric-violet" />
|
||||
<h2 className="mb-6 text-2xl font-bold text-cloud-white md:text-3xl">
|
||||
Über den Speaker
|
||||
</h2>
|
||||
{speaker ? (
|
||||
<>
|
||||
<p className="mb-1 text-lg font-bold text-cloud-white">{speaker.name}</p>
|
||||
<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">
|
||||
<BiLogoLinkedinSquare className="size-6" />
|
||||
</a>
|
||||
<a href="#" 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">
|
||||
<BiLogoDribbble className="size-6" />
|
||||
</a>
|
||||
</div>
|
||||
<Link
|
||||
to={`/speaker/${speaker.slug}`}
|
||||
className="inline-block rounded-2xl border border-acid-lime px-6 py-2.5 text-sm font-semibold text-acid-lime hover:bg-acid-lime hover:text-tech-navy transition-colors"
|
||||
>
|
||||
Speaker Details
|
||||
</Link>
|
||||
</>
|
||||
) : (
|
||||
<p className="text-cloud-white/60">{event.speaker}</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Right: das erwartete 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">
|
||||
Das erwartet dich
|
||||
</h2>
|
||||
<p className="text-base leading-relaxed text-cloud-white/80">
|
||||
{event.description ?? "Weitere Informationen folgen in Kürze."}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Stay tuned */}
|
||||
<Cta30 />
|
||||
|
||||
{/* Sponsors */}
|
||||
<SponsorsSection />
|
||||
|
||||
<Footer3 />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ProgrammDetail;
|
||||
@ -1,76 +0,0 @@
|
||||
import { useMemo, useState } from "react";
|
||||
import { Navbar3 } from "../components/Navbar";
|
||||
import { Header23 } from "../components/Header23";
|
||||
import { Filters5 } from "../components/Filters5";
|
||||
import { Team4 } from "../components/Team4";
|
||||
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" },
|
||||
{ value: "AI & Future", label: "AI & Future" },
|
||||
{ value: "Psychologie", label: "Psychologie" },
|
||||
];
|
||||
|
||||
const ALL_SPEAKERS = Team4Defaults.teamMembers;
|
||||
|
||||
const Speaker = () => {
|
||||
const [activeTab, setActiveTab] = useState("all");
|
||||
const [searchQuery, setSearchQuery] = useState("");
|
||||
|
||||
const filteredSpeakers = useMemo(() => {
|
||||
return ALL_SPEAKERS.filter((s) => {
|
||||
if (activeTab !== "all" && s.category !== activeTab) return false;
|
||||
if (searchQuery) {
|
||||
const q = searchQuery.toLowerCase();
|
||||
if (
|
||||
!s.name.toLowerCase().includes(q) &&
|
||||
!s.role.toLowerCase().includes(q) &&
|
||||
!s.company.toLowerCase().includes(q)
|
||||
)
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}, [activeTab, searchQuery]);
|
||||
|
||||
const activeTags = [
|
||||
searchQuery ? { label: searchQuery, onRemove: () => setSearchQuery("") } : null,
|
||||
activeTab !== "all" ? { label: activeTab, onRemove: () => setActiveTab("all") } : null,
|
||||
].filter(Boolean) as { label: string; onRemove: () => void }[];
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Navbar3 />
|
||||
<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."
|
||||
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" },
|
||||
]}
|
||||
/>
|
||||
<Filters5
|
||||
tabs={SPEAKER_TABS}
|
||||
activeTab={activeTab}
|
||||
onTabChange={setActiveTab}
|
||||
searchQuery={searchQuery}
|
||||
onSearchChange={setSearchQuery}
|
||||
searchLabel="Stichwortsuche"
|
||||
resetLabel="Löschen"
|
||||
activeTags={activeTags}
|
||||
onReset={() => { setSearchQuery(""); setActiveTab("all"); }}
|
||||
useFilterToggle={false}
|
||||
showDayFilter={false}
|
||||
showSortButton={false}
|
||||
/>
|
||||
<Team4 teamMembers={filteredSpeakers} />
|
||||
<Footer3 />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Speaker;
|
||||
@ -1,114 +0,0 @@
|
||||
import { useParams, Navigate } from "react-router-dom";
|
||||
import { Navbar3 } from "../components/Navbar";
|
||||
import { Team4, Team4Defaults } from "../components/Team4";
|
||||
import { Footer3 } from "../components/Footer";
|
||||
import webflowLogo from "../assets/webflow.png";
|
||||
import relumeLogo from "../assets/relume.png";
|
||||
|
||||
const ALL_SPEAKERS = Team4Defaults.teamMembers;
|
||||
|
||||
const SPONSORS = [
|
||||
{ src: webflowLogo, alt: "Webflow" },
|
||||
{ src: relumeLogo, alt: "Relume" },
|
||||
];
|
||||
|
||||
const SponsorsSection = () => (
|
||||
<section className="bg-tech-navy px-[5%] py-16 md:py-24 font-barlow">
|
||||
<div className="container">
|
||||
<h2 className="mb-12 text-center text-4xl font-bold text-cloud-white md:text-5xl">
|
||||
Unsere Sponsoren & Partner
|
||||
</h2>
|
||||
<div className="flex flex-wrap items-center justify-center gap-8">
|
||||
{SPONSORS.map((sponsor) => (
|
||||
<div
|
||||
key={sponsor.alt}
|
||||
className="flex items-center justify-center rounded-lg border border-white/10 bg-white/5 px-8 py-6"
|
||||
>
|
||||
<img src={sponsor.src} alt={sponsor.alt} className="h-10 w-auto object-contain" />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
|
||||
const SpeakerDetail = () => {
|
||||
const { slug } = useParams<{ slug: string }>();
|
||||
const speaker = ALL_SPEAKERS.find((s) => s.slug === slug);
|
||||
|
||||
if (!speaker) return <Navigate to="/speaker" replace />;
|
||||
|
||||
const otherSpeakers = ALL_SPEAKERS.filter((s) => s.slug !== slug);
|
||||
|
||||
return (
|
||||
<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>
|
||||
|
||||
{/* 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>
|
||||
<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}
|
||||
</span>
|
||||
<h1 className="mb-4 text-5xl font-bold text-cloud-white md:text-7xl">
|
||||
{speaker.name}
|
||||
</h1>
|
||||
<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>
|
||||
)}
|
||||
</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">
|
||||
{speaker.socialLinks.map((link, i) => (
|
||||
<a
|
||||
key={i}
|
||||
href={link.href}
|
||||
className="text-cloud-white/60 hover:text-acid-lime transition-colors"
|
||||
>
|
||||
{link.icon}
|
||||
</a>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Other speakers */}
|
||||
<div className="bg-cloud-white">
|
||||
<div className="px-[5%] pt-16 font-barlow">
|
||||
<div className="container mb-10">
|
||||
<h2 className="text-3xl font-bold text-tech-navy md:text-4xl">Weitere Speaker</h2>
|
||||
</div>
|
||||
</div>
|
||||
<Team4 teamMembers={otherSpeakers} />
|
||||
</div>
|
||||
|
||||
<SponsorsSection />
|
||||
<Footer3 />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default SpeakerDetail;
|
||||
@ -1,140 +0,0 @@
|
||||
"use client";
|
||||
|
||||
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";
|
||||
|
||||
const COUNTDOWN_TARGET = "2026-06-25T09:00:00.000+02:00";
|
||||
|
||||
type CountdownValues = { days: string; hours: string; minutes: string; seconds: string };
|
||||
|
||||
const Countdown = ({ targetDate }: { targetDate: string }) => {
|
||||
const [time, setTime] = useState<CountdownValues>({
|
||||
days: "00",
|
||||
hours: "00",
|
||||
minutes: "00",
|
||||
seconds: "00",
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
const target = new Date(targetDate).getTime();
|
||||
const pad = (n: number) => (n < 10 ? `0${n}` : `${n}`);
|
||||
|
||||
const tick = () => {
|
||||
const diff = target - Date.now();
|
||||
if (diff <= 0) {
|
||||
setTime({ days: "00", hours: "00", minutes: "00", seconds: "00" });
|
||||
return;
|
||||
}
|
||||
setTime({
|
||||
days: pad(Math.floor(diff / 86400000)),
|
||||
hours: pad(Math.floor((diff % 86400000) / 3600000)),
|
||||
minutes: pad(Math.floor((diff % 3600000) / 60000)),
|
||||
seconds: pad(Math.floor((diff % 60000) / 1000)),
|
||||
});
|
||||
};
|
||||
|
||||
tick();
|
||||
const id = setInterval(tick, 1000);
|
||||
return () => clearInterval(id);
|
||||
}, [targetDate]);
|
||||
|
||||
const Cell = ({ value, label }: { value: string; label: string }) => (
|
||||
<div className="flex min-w-[4.5rem] flex-col items-center">
|
||||
<span className="text-4xl font-bold leading-tight text-cloud-white md:text-5xl lg:text-6xl">
|
||||
{value}
|
||||
</span>
|
||||
<span className="text-sm text-cloud-white">{label}</span>
|
||||
</div>
|
||||
);
|
||||
|
||||
const Divider = () => <div className="hidden w-px self-stretch bg-white/30 sm:block" />;
|
||||
|
||||
return (
|
||||
<div className="flex flex-wrap justify-center gap-4 border border-white/30 px-4 py-4 sm:flex-nowrap sm:px-6">
|
||||
<Cell value={time.days} label="Days" />
|
||||
<Divider />
|
||||
<Cell value={time.hours} label="Hours" />
|
||||
<Divider />
|
||||
<Cell value={time.minutes} label="Mins" />
|
||||
<Divider />
|
||||
<Cell value={time.seconds} label="Secs" />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const Tickets = () => {
|
||||
const [emailInput, setEmailInput] = useState("");
|
||||
|
||||
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
|
||||
e.preventDefault();
|
||||
console.log({ emailInput });
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Navbar3 />
|
||||
<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."
|
||||
buttons={[]}
|
||||
>
|
||||
<div className="mt-5 font-semibold text-cloud-white md:mt-6 md:text-md">
|
||||
DO – FR, 25. – 26. Juni 2026
|
||||
</div>
|
||||
<p className="mt-4 inline-block bg-electric-violet px-2 py-1 text-sm font-semibold text-cloud-white">
|
||||
10 Spots left!
|
||||
</p>
|
||||
<div className="mt-8 flex justify-center">
|
||||
<div className="w-fit">
|
||||
<Countdown targetDate={COUNTDOWN_TARGET} />
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-6 w-full max-w-sm md:mt-8 mx-auto">
|
||||
<form
|
||||
className="mb-4 grid max-w-sm grid-cols-1 gap-y-3 sm:grid-cols-[1fr_max-content] sm:gap-4"
|
||||
onSubmit={handleSubmit}
|
||||
>
|
||||
<Input
|
||||
id="email"
|
||||
type="email"
|
||||
placeholder="Enter your email"
|
||||
value={emailInput}
|
||||
onChange={(e) => setEmailInput(e.target.value)}
|
||||
className="rounded-lg border-electric-violet text-electric-violet placeholder:text-electric-violet/50"
|
||||
/>
|
||||
<button
|
||||
type="submit"
|
||||
className="rounded-2xl bg-acid-lime px-5 py-2 text-sm font-normal uppercase text-tech-navy hover:opacity-90 transition-opacity"
|
||||
>
|
||||
Get Updates
|
||||
</button>
|
||||
</form>
|
||||
<p className="text-xs text-cloud-white">
|
||||
By clicking Get Updates you're confirming that you agree with our{" "}
|
||||
<a href="#" className="underline">Terms and Conditions</a>.
|
||||
</p>
|
||||
</div>
|
||||
</Header23>
|
||||
<Pricing1Ticket />
|
||||
<Pricing39Ticket
|
||||
heading="1-Tagespässe"
|
||||
plans={[
|
||||
{ day: "Donnerstag", price: 131, date: "25. Juni 2026" },
|
||||
{ day: "Freitag", price: 131, date: "26. Juni 2026" },
|
||||
]}
|
||||
/>
|
||||
<Pricing39Ticket
|
||||
heading="2-Tagespässe"
|
||||
plans={[{ day: "Donnerstag – Freitag", price: 236, date: "25. – 26. Juni 2026" }]}
|
||||
/>
|
||||
<Footer3 />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Tickets;
|
||||
1242
relume-test/src/reactcomponents_home.jsx
Normal file
@ -9,19 +9,7 @@ export default {
|
||||
],
|
||||
presets: [relumetailwind],
|
||||
theme: {
|
||||
extend: {
|
||||
colors: {
|
||||
"tech-navy": "#1d283a",
|
||||
"acid-lime": "#cfff24",
|
||||
"electric-violet": "#895af6",
|
||||
"electric-green": "#00d949",
|
||||
"cloud-white": "#f8fafc",
|
||||
"neutral-dark": "#283c5e",
|
||||
},
|
||||
fontFamily: {
|
||||
barlow: ["Barlow", "sans-serif"],
|
||||
},
|
||||
},
|
||||
extend: {},
|
||||
},
|
||||
plugins: [],
|
||||
}
|
||||
989
style.css
Normal file
@ -0,0 +1,989 @@
|
||||
/* ===========================================
|
||||
KLANGWALD FESTIVAL – Stylesheet
|
||||
===========================================
|
||||
Inhaltsverzeichnis:
|
||||
1. CSS Custom Properties (Variablen)
|
||||
2. Reset & Basis-Styles
|
||||
3. Typografie
|
||||
4. Layout-Utilities
|
||||
5. Buttons
|
||||
6. Navigation
|
||||
7. Hero Section
|
||||
8. Line-up Section
|
||||
9. Programm Section
|
||||
10. Tickets Section
|
||||
11. Info Section
|
||||
12. Newsletter Section
|
||||
13. Footer
|
||||
14. Animationen
|
||||
15. Media Queries (Responsive)
|
||||
=========================================== */
|
||||
|
||||
/* ===========================================
|
||||
1. CSS CUSTOM PROPERTIES
|
||||
Hier können Farben, Schriftarten und Abstände
|
||||
zentral angepasst werden.
|
||||
=========================================== */
|
||||
:root {
|
||||
/* Farben */
|
||||
--color-primary: #6C3CE1; /* Violett – Hauptfarbe */
|
||||
--color-primary-light: #8B5CF6;
|
||||
--color-primary-dark: #4C1D95;
|
||||
--color-accent: #F59E0B; /* Gold – Akzentfarbe */
|
||||
--color-accent-light: #FBBF24;
|
||||
|
||||
--color-bg: #0F0F14; /* Dunkler Hintergrund */
|
||||
--color-bg-light: #1A1A24; /* Etwas hellerer Hintergrund */
|
||||
--color-bg-card: #222233; /* Karten-Hintergrund */
|
||||
|
||||
--color-text: #E8E8F0; /* Haupttext */
|
||||
--color-text-muted: #9999AA; /* Gedämpfter Text */
|
||||
--color-text-heading: #FFFFFF; /* Überschriften */
|
||||
|
||||
--color-border: #333344; /* Rahmenfarbe */
|
||||
|
||||
/* Schriftarten */
|
||||
--font-heading: 'Space Grotesk', sans-serif;
|
||||
--font-body: 'Inter', sans-serif;
|
||||
|
||||
/* Abstände */
|
||||
--spacing-xs: 0.5rem; /* 8px */
|
||||
--spacing-sm: 1rem; /* 16px */
|
||||
--spacing-md: 1.5rem; /* 24px */
|
||||
--spacing-lg: 2.5rem; /* 40px */
|
||||
--spacing-xl: 4rem; /* 64px */
|
||||
--spacing-2xl: 6rem; /* 96px */
|
||||
|
||||
/* Rundungen */
|
||||
--radius-sm: 6px;
|
||||
--radius-md: 12px;
|
||||
--radius-lg: 20px;
|
||||
--radius-full: 9999px;
|
||||
|
||||
/* Schatten */
|
||||
--shadow-sm: 0 2px 8px rgba(0, 0, 0, 0.2);
|
||||
--shadow-md: 0 4px 20px rgba(0, 0, 0, 0.3);
|
||||
--shadow-lg: 0 8px 40px rgba(0, 0, 0, 0.4);
|
||||
--shadow-glow: 0 0 30px rgba(108, 60, 225, 0.3);
|
||||
|
||||
/* Übergänge */
|
||||
--transition-fast: 150ms ease;
|
||||
--transition-base: 300ms ease;
|
||||
--transition-slow: 500ms ease;
|
||||
|
||||
/* Container */
|
||||
--container-max: 1200px;
|
||||
--container-padding: 1.5rem;
|
||||
}
|
||||
|
||||
/* ===========================================
|
||||
2. RESET & BASIS-STYLES
|
||||
=========================================== */
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
html {
|
||||
scroll-behavior: smooth;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: var(--font-body);
|
||||
background-color: var(--color-bg);
|
||||
color: var(--color-text);
|
||||
line-height: 1.6;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
img {
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
display: block;
|
||||
}
|
||||
|
||||
a {
|
||||
color: var(--color-primary-light);
|
||||
text-decoration: none;
|
||||
transition: color var(--transition-fast);
|
||||
}
|
||||
|
||||
a:hover {
|
||||
color: var(--color-accent);
|
||||
}
|
||||
|
||||
ul {
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
/* ===========================================
|
||||
3. TYPOGRAFIE
|
||||
=========================================== */
|
||||
h1, h2, h3, h4, h5, h6 {
|
||||
font-family: var(--font-heading);
|
||||
color: var(--color-text-heading);
|
||||
font-weight: 700;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
.section__title {
|
||||
font-size: clamp(2rem, 5vw, 3.5rem);
|
||||
text-align: center;
|
||||
margin-bottom: var(--spacing-xs);
|
||||
}
|
||||
|
||||
.section__subtitle {
|
||||
text-align: center;
|
||||
color: var(--color-text-muted);
|
||||
font-size: 1.1rem;
|
||||
margin-bottom: var(--spacing-xl);
|
||||
max-width: 600px;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
/* ===========================================
|
||||
4. LAYOUT-UTILITIES
|
||||
=========================================== */
|
||||
.container {
|
||||
width: 100%;
|
||||
max-width: var(--container-max);
|
||||
margin: 0 auto;
|
||||
padding: 0 var(--container-padding);
|
||||
}
|
||||
|
||||
.section {
|
||||
padding: var(--spacing-2xl) 0;
|
||||
}
|
||||
|
||||
/* ===========================================
|
||||
5. BUTTONS
|
||||
=========================================== */
|
||||
.btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 0.85rem 2rem;
|
||||
font-family: var(--font-heading);
|
||||
font-size: 1rem;
|
||||
font-weight: 600;
|
||||
border-radius: var(--radius-full);
|
||||
border: 2px solid transparent;
|
||||
cursor: pointer;
|
||||
transition: all var(--transition-base);
|
||||
text-decoration: none;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.btn--primary {
|
||||
background: linear-gradient(135deg, var(--color-primary), var(--color-primary-light));
|
||||
color: #fff;
|
||||
border-color: var(--color-primary);
|
||||
}
|
||||
|
||||
.btn--primary:hover {
|
||||
background: linear-gradient(135deg, var(--color-primary-light), var(--color-primary));
|
||||
box-shadow: var(--shadow-glow);
|
||||
color: #fff;
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.btn--outline {
|
||||
background: transparent;
|
||||
color: var(--color-text-heading);
|
||||
border-color: var(--color-border);
|
||||
}
|
||||
|
||||
.btn--outline:hover {
|
||||
border-color: var(--color-primary-light);
|
||||
color: var(--color-primary-light);
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
/* ===========================================
|
||||
6. NAVIGATION
|
||||
=========================================== */
|
||||
.navbar {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
z-index: 1000;
|
||||
padding: 1rem 0;
|
||||
transition: background-color var(--transition-base), padding var(--transition-base);
|
||||
}
|
||||
|
||||
.navbar--scrolled {
|
||||
background-color: rgba(15, 15, 20, 0.95);
|
||||
backdrop-filter: blur(10px);
|
||||
padding: 0.6rem 0;
|
||||
box-shadow: var(--shadow-sm);
|
||||
}
|
||||
|
||||
.navbar__inner {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.navbar__logo {
|
||||
font-family: var(--font-heading);
|
||||
font-size: 1.5rem;
|
||||
font-weight: 700;
|
||||
color: var(--color-text-heading);
|
||||
letter-spacing: 0.1em;
|
||||
}
|
||||
|
||||
.navbar__logo:hover {
|
||||
color: var(--color-accent);
|
||||
}
|
||||
|
||||
.navbar__menu {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--spacing-md);
|
||||
}
|
||||
|
||||
.navbar__link {
|
||||
font-family: var(--font-heading);
|
||||
font-size: 0.95rem;
|
||||
font-weight: 500;
|
||||
color: var(--color-text);
|
||||
transition: color var(--transition-fast);
|
||||
}
|
||||
|
||||
.navbar__link:hover {
|
||||
color: var(--color-text-heading);
|
||||
}
|
||||
|
||||
.navbar__link--cta {
|
||||
background: var(--color-primary);
|
||||
color: #fff;
|
||||
padding: 0.5rem 1.2rem;
|
||||
border-radius: var(--radius-full);
|
||||
}
|
||||
|
||||
.navbar__link--cta:hover {
|
||||
background: var(--color-primary-light);
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
/* Hamburger Toggle */
|
||||
.navbar__toggle {
|
||||
display: none;
|
||||
flex-direction: column;
|
||||
gap: 5px;
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
padding: 0.5rem;
|
||||
}
|
||||
|
||||
.navbar__toggle-bar {
|
||||
display: block;
|
||||
width: 24px;
|
||||
height: 2px;
|
||||
background-color: var(--color-text-heading);
|
||||
border-radius: 2px;
|
||||
transition: all var(--transition-base);
|
||||
}
|
||||
|
||||
/* ===========================================
|
||||
7. HERO SECTION
|
||||
=========================================== */
|
||||
.hero {
|
||||
position: relative;
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
text-align: center;
|
||||
overflow: hidden;
|
||||
|
||||
/* TODO: Hintergrundbild hier einfügen */
|
||||
background: linear-gradient(
|
||||
135deg,
|
||||
var(--color-bg) 0%,
|
||||
var(--color-primary-dark) 50%,
|
||||
var(--color-bg) 100%
|
||||
);
|
||||
/* Falls ein Bild verwendet wird:
|
||||
background-image: url('../img/hero-bg.jpg');
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
*/
|
||||
}
|
||||
|
||||
.hero__overlay {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background: radial-gradient(
|
||||
ellipse at center,
|
||||
rgba(108, 60, 225, 0.15) 0%,
|
||||
rgba(15, 15, 20, 0.8) 70%
|
||||
);
|
||||
}
|
||||
|
||||
.hero__content {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.hero__date {
|
||||
font-family: var(--font-heading);
|
||||
font-size: 1.1rem;
|
||||
font-weight: 500;
|
||||
color: var(--color-accent);
|
||||
letter-spacing: 0.15em;
|
||||
text-transform: uppercase;
|
||||
margin-bottom: var(--spacing-sm);
|
||||
}
|
||||
|
||||
.hero__title {
|
||||
font-size: clamp(3rem, 10vw, 8rem);
|
||||
font-weight: 700;
|
||||
line-height: 0.95;
|
||||
letter-spacing: -0.02em;
|
||||
margin-bottom: var(--spacing-md);
|
||||
background: linear-gradient(180deg, #fff 40%, var(--color-primary-light) 100%);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
background-clip: text;
|
||||
}
|
||||
|
||||
.hero__subtitle {
|
||||
font-size: clamp(1rem, 2.5vw, 1.3rem);
|
||||
color: var(--color-text-muted);
|
||||
max-width: 500px;
|
||||
margin: 0 auto var(--spacing-lg);
|
||||
line-height: 1.7;
|
||||
}
|
||||
|
||||
.hero__actions {
|
||||
display: flex;
|
||||
gap: var(--spacing-sm);
|
||||
justify-content: center;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
/* Countdown */
|
||||
.hero__countdown {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
display: flex;
|
||||
gap: var(--spacing-lg);
|
||||
margin-top: var(--spacing-xl);
|
||||
}
|
||||
|
||||
.countdown__item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.countdown__number {
|
||||
font-family: var(--font-heading);
|
||||
font-size: clamp(2rem, 5vw, 3rem);
|
||||
font-weight: 700;
|
||||
color: var(--color-text-heading);
|
||||
}
|
||||
|
||||
.countdown__label {
|
||||
font-size: 0.85rem;
|
||||
color: var(--color-text-muted);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.1em;
|
||||
}
|
||||
|
||||
/* ===========================================
|
||||
8. LINE-UP SECTION
|
||||
=========================================== */
|
||||
.lineup {
|
||||
background-color: var(--color-bg);
|
||||
}
|
||||
|
||||
.lineup__tier {
|
||||
margin-bottom: var(--spacing-xl);
|
||||
}
|
||||
|
||||
.lineup__tier-label {
|
||||
font-size: 1.2rem;
|
||||
color: var(--color-accent);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.15em;
|
||||
margin-bottom: var(--spacing-md);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.lineup__grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
|
||||
gap: var(--spacing-md);
|
||||
}
|
||||
|
||||
.lineup__grid--headliner {
|
||||
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
|
||||
}
|
||||
|
||||
.artist-card {
|
||||
background: var(--color-bg-card);
|
||||
border-radius: var(--radius-md);
|
||||
overflow: hidden;
|
||||
border: 1px solid var(--color-border);
|
||||
transition: transform var(--transition-base), box-shadow var(--transition-base);
|
||||
}
|
||||
|
||||
.artist-card:hover {
|
||||
transform: translateY(-4px);
|
||||
box-shadow: var(--shadow-md);
|
||||
}
|
||||
|
||||
.artist-card__image {
|
||||
aspect-ratio: 1 / 1;
|
||||
overflow: hidden;
|
||||
background: var(--color-bg-light);
|
||||
}
|
||||
|
||||
.artist-card--large .artist-card__image {
|
||||
aspect-ratio: 16 / 10;
|
||||
}
|
||||
|
||||
.artist-card__placeholder {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: var(--color-text-muted);
|
||||
font-size: 0.9rem;
|
||||
background: linear-gradient(135deg, var(--color-bg-light), var(--color-bg-card));
|
||||
}
|
||||
|
||||
.artist-card__info {
|
||||
padding: var(--spacing-sm);
|
||||
}
|
||||
|
||||
.artist-card__name {
|
||||
font-size: 1.1rem;
|
||||
margin-bottom: 0.25rem;
|
||||
}
|
||||
|
||||
.artist-card__genre {
|
||||
font-size: 0.85rem;
|
||||
color: var(--color-text-muted);
|
||||
}
|
||||
|
||||
.lineup__more {
|
||||
text-align: center;
|
||||
color: var(--color-text-muted);
|
||||
font-style: italic;
|
||||
margin-top: var(--spacing-md);
|
||||
}
|
||||
|
||||
/* ===========================================
|
||||
9. PROGRAMM SECTION
|
||||
=========================================== */
|
||||
.programm {
|
||||
background-color: var(--color-bg-light);
|
||||
}
|
||||
|
||||
.programm__tabs {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: var(--spacing-xs);
|
||||
margin-bottom: var(--spacing-lg);
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.programm__tab {
|
||||
font-family: var(--font-heading);
|
||||
font-size: 0.95rem;
|
||||
font-weight: 600;
|
||||
padding: 0.7rem 1.5rem;
|
||||
background: transparent;
|
||||
border: 2px solid var(--color-border);
|
||||
border-radius: var(--radius-full);
|
||||
color: var(--color-text-muted);
|
||||
cursor: pointer;
|
||||
transition: all var(--transition-base);
|
||||
}
|
||||
|
||||
.programm__tab:hover {
|
||||
border-color: var(--color-primary-light);
|
||||
color: var(--color-text-heading);
|
||||
}
|
||||
|
||||
.programm__tab--active {
|
||||
background: var(--color-primary);
|
||||
border-color: var(--color-primary);
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
/* Tages-Container */
|
||||
.programm__day {
|
||||
display: none;
|
||||
max-width: 700px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.programm__day--active {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.programm__event {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: var(--spacing-md);
|
||||
padding: var(--spacing-sm) 0;
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
}
|
||||
|
||||
.programm__event:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.programm__event--highlight {
|
||||
background: rgba(108, 60, 225, 0.1);
|
||||
margin: 0 calc(var(--spacing-sm) * -1);
|
||||
padding: var(--spacing-sm);
|
||||
border-radius: var(--radius-md);
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.programm__time {
|
||||
font-family: var(--font-heading);
|
||||
font-size: 1.1rem;
|
||||
font-weight: 600;
|
||||
color: var(--color-accent);
|
||||
min-width: 70px;
|
||||
}
|
||||
|
||||
.programm__act {
|
||||
font-size: 1.05rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.programm__stage {
|
||||
font-size: 0.9rem;
|
||||
color: var(--color-text-muted);
|
||||
}
|
||||
|
||||
.programm__badge {
|
||||
display: inline-block;
|
||||
margin-top: 0.3rem;
|
||||
padding: 0.2rem 0.7rem;
|
||||
font-size: 0.75rem;
|
||||
font-weight: 600;
|
||||
background: var(--color-primary);
|
||||
color: #fff;
|
||||
border-radius: var(--radius-full);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em;
|
||||
}
|
||||
|
||||
/* ===========================================
|
||||
10. TICKETS SECTION
|
||||
=========================================== */
|
||||
.tickets {
|
||||
background-color: var(--color-bg);
|
||||
}
|
||||
|
||||
.tickets__grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
|
||||
gap: var(--spacing-md);
|
||||
align-items: start;
|
||||
}
|
||||
|
||||
.ticket-card {
|
||||
position: relative;
|
||||
background: var(--color-bg-card);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius-lg);
|
||||
padding: var(--spacing-lg);
|
||||
text-align: center;
|
||||
transition: transform var(--transition-base), box-shadow var(--transition-base);
|
||||
}
|
||||
|
||||
.ticket-card:hover {
|
||||
transform: translateY(-4px);
|
||||
box-shadow: var(--shadow-md);
|
||||
}
|
||||
|
||||
.ticket-card--featured {
|
||||
border-color: var(--color-primary);
|
||||
box-shadow: var(--shadow-glow);
|
||||
transform: scale(1.03);
|
||||
}
|
||||
|
||||
.ticket-card--featured:hover {
|
||||
transform: scale(1.03) translateY(-4px);
|
||||
}
|
||||
|
||||
.ticket-card__badge {
|
||||
position: absolute;
|
||||
top: -12px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
background: var(--color-accent);
|
||||
color: var(--color-bg);
|
||||
font-family: var(--font-heading);
|
||||
font-size: 0.8rem;
|
||||
font-weight: 700;
|
||||
padding: 0.3rem 1rem;
|
||||
border-radius: var(--radius-full);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em;
|
||||
}
|
||||
|
||||
.ticket-card__title {
|
||||
font-size: 1.3rem;
|
||||
margin-bottom: var(--spacing-sm);
|
||||
}
|
||||
|
||||
.ticket-card__price {
|
||||
margin-bottom: var(--spacing-md);
|
||||
}
|
||||
|
||||
.ticket-card__amount {
|
||||
display: block;
|
||||
font-family: var(--font-heading);
|
||||
font-size: 2.5rem;
|
||||
font-weight: 700;
|
||||
color: var(--color-text-heading);
|
||||
}
|
||||
|
||||
.ticket-card__period {
|
||||
font-size: 0.9rem;
|
||||
color: var(--color-text-muted);
|
||||
}
|
||||
|
||||
.ticket-card__features {
|
||||
text-align: left;
|
||||
margin-bottom: var(--spacing-lg);
|
||||
}
|
||||
|
||||
.ticket-card__features li {
|
||||
padding: 0.5rem 0;
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
font-size: 0.95rem;
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
.ticket-card__features li::before {
|
||||
content: '✓ ';
|
||||
color: var(--color-primary-light);
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.ticket-card__features li:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.ticket-card__btn {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* ===========================================
|
||||
11. INFO SECTION
|
||||
=========================================== */
|
||||
.info {
|
||||
background-color: var(--color-bg-light);
|
||||
}
|
||||
|
||||
.info__grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
||||
gap: var(--spacing-md);
|
||||
}
|
||||
|
||||
.info__card {
|
||||
background: var(--color-bg-card);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius-md);
|
||||
padding: var(--spacing-lg);
|
||||
transition: transform var(--transition-base);
|
||||
}
|
||||
|
||||
.info__card:hover {
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.info__icon {
|
||||
margin-bottom: var(--spacing-sm);
|
||||
}
|
||||
|
||||
.info__icon-placeholder {
|
||||
font-size: 2rem;
|
||||
}
|
||||
|
||||
.info__card-title {
|
||||
font-size: 1.2rem;
|
||||
margin-bottom: var(--spacing-xs);
|
||||
}
|
||||
|
||||
.info__card-text {
|
||||
color: var(--color-text-muted);
|
||||
font-size: 0.95rem;
|
||||
line-height: 1.7;
|
||||
}
|
||||
|
||||
.info__link {
|
||||
display: inline-block;
|
||||
margin-top: var(--spacing-sm);
|
||||
font-weight: 600;
|
||||
color: var(--color-primary-light);
|
||||
}
|
||||
|
||||
/* ===========================================
|
||||
12. NEWSLETTER SECTION
|
||||
=========================================== */
|
||||
.newsletter {
|
||||
background: linear-gradient(135deg, var(--color-primary-dark), var(--color-primary));
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.newsletter__inner {
|
||||
max-width: 600px;
|
||||
}
|
||||
|
||||
.newsletter__title {
|
||||
font-size: clamp(1.5rem, 4vw, 2.5rem);
|
||||
color: #fff;
|
||||
margin-bottom: var(--spacing-xs);
|
||||
}
|
||||
|
||||
.newsletter__text {
|
||||
color: rgba(255, 255, 255, 0.8);
|
||||
margin-bottom: var(--spacing-md);
|
||||
}
|
||||
|
||||
.newsletter__form {
|
||||
display: flex;
|
||||
gap: var(--spacing-xs);
|
||||
margin-bottom: var(--spacing-xs);
|
||||
}
|
||||
|
||||
.newsletter__input {
|
||||
flex: 1;
|
||||
padding: 0.85rem 1.2rem;
|
||||
font-size: 1rem;
|
||||
font-family: var(--font-body);
|
||||
background: rgba(255, 255, 255, 0.15);
|
||||
border: 2px solid rgba(255, 255, 255, 0.3);
|
||||
border-radius: var(--radius-full);
|
||||
color: #fff;
|
||||
outline: none;
|
||||
transition: border-color var(--transition-fast);
|
||||
}
|
||||
|
||||
.newsletter__input::placeholder {
|
||||
color: rgba(255, 255, 255, 0.5);
|
||||
}
|
||||
|
||||
.newsletter__input:focus {
|
||||
border-color: var(--color-accent);
|
||||
}
|
||||
|
||||
.newsletter__btn {
|
||||
white-space: nowrap;
|
||||
background: var(--color-accent);
|
||||
border-color: var(--color-accent);
|
||||
color: var(--color-bg);
|
||||
}
|
||||
|
||||
.newsletter__btn:hover {
|
||||
background: var(--color-accent-light);
|
||||
border-color: var(--color-accent-light);
|
||||
}
|
||||
|
||||
.newsletter__hint {
|
||||
font-size: 0.8rem;
|
||||
color: rgba(255, 255, 255, 0.5);
|
||||
}
|
||||
|
||||
/* ===========================================
|
||||
13. FOOTER
|
||||
=========================================== */
|
||||
.footer {
|
||||
background-color: var(--color-bg);
|
||||
border-top: 1px solid var(--color-border);
|
||||
padding: var(--spacing-xl) 0 var(--spacing-md);
|
||||
}
|
||||
|
||||
.footer__inner {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: var(--spacing-xl);
|
||||
}
|
||||
|
||||
.footer__brand {
|
||||
flex: 1;
|
||||
min-width: 200px;
|
||||
}
|
||||
|
||||
.footer__logo {
|
||||
font-family: var(--font-heading);
|
||||
font-size: 1.3rem;
|
||||
font-weight: 700;
|
||||
color: var(--color-text-heading);
|
||||
letter-spacing: 0.1em;
|
||||
}
|
||||
|
||||
.footer__tagline {
|
||||
color: var(--color-text-muted);
|
||||
margin-top: var(--spacing-xs);
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.footer__links {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: var(--spacing-xl);
|
||||
}
|
||||
|
||||
.footer__heading {
|
||||
font-size: 0.9rem;
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.1em;
|
||||
color: var(--color-text-heading);
|
||||
margin-bottom: var(--spacing-sm);
|
||||
}
|
||||
|
||||
.footer__column a {
|
||||
display: block;
|
||||
padding: 0.3rem 0;
|
||||
color: var(--color-text-muted);
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.footer__column a:hover {
|
||||
color: var(--color-text-heading);
|
||||
}
|
||||
|
||||
.footer__bottom {
|
||||
width: 100%;
|
||||
padding-top: var(--spacing-lg);
|
||||
margin-top: var(--spacing-lg);
|
||||
border-top: 1px solid var(--color-border);
|
||||
text-align: center;
|
||||
color: var(--color-text-muted);
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
|
||||
/* ===========================================
|
||||
14. ANIMATIONEN
|
||||
=========================================== */
|
||||
|
||||
/* Scroll-Animation: Elemente einblenden */
|
||||
.fade-in {
|
||||
opacity: 0;
|
||||
transform: translateY(20px);
|
||||
transition: opacity var(--transition-slow), transform var(--transition-slow);
|
||||
}
|
||||
|
||||
.fade-in--visible {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
/* ===========================================
|
||||
15. MEDIA QUERIES (Responsive)
|
||||
=========================================== */
|
||||
|
||||
/* Tablet */
|
||||
@media (max-width: 768px) {
|
||||
/* Navigation: Hamburger-Menü */
|
||||
.navbar__toggle {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.navbar__menu {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
right: -100%;
|
||||
width: 280px;
|
||||
height: 100vh;
|
||||
background: var(--color-bg);
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
padding: 5rem var(--spacing-lg) var(--spacing-lg);
|
||||
gap: var(--spacing-sm);
|
||||
transition: right var(--transition-base);
|
||||
box-shadow: var(--shadow-lg);
|
||||
}
|
||||
|
||||
.navbar__menu--open {
|
||||
right: 0;
|
||||
}
|
||||
|
||||
.navbar__link--cta {
|
||||
margin-top: var(--spacing-sm);
|
||||
}
|
||||
|
||||
/* Hero */
|
||||
.hero__countdown {
|
||||
gap: var(--spacing-md);
|
||||
}
|
||||
|
||||
/* Tickets */
|
||||
.ticket-card--featured {
|
||||
transform: none;
|
||||
}
|
||||
|
||||
.ticket-card--featured:hover {
|
||||
transform: translateY(-4px);
|
||||
}
|
||||
|
||||
/* Newsletter */
|
||||
.newsletter__form {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
/* Footer */
|
||||
.footer__inner {
|
||||
flex-direction: column;
|
||||
gap: var(--spacing-lg);
|
||||
}
|
||||
}
|
||||
|
||||
/* Mobile */
|
||||
@media (max-width: 480px) {
|
||||
:root {
|
||||
--container-padding: 1rem;
|
||||
}
|
||||
|
||||
.section {
|
||||
padding: var(--spacing-xl) 0;
|
||||
}
|
||||
|
||||
.hero__actions {
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.hero__countdown {
|
||||
gap: var(--spacing-sm);
|
||||
}
|
||||
|
||||
.lineup__grid--headliner {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.programm__tabs {
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.programm__tab {
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||