US01-02: 3 Pakete mit Detaildialog und Verweis auf Coaching-Rabatte

feature_US01-02
Kevin Kopp 2026-09-24 17:23:24 +02:00
parent b41b3cbc3e
commit 07bf86f818
7 changed files with 595 additions and 15 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 157 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 118 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 131 KiB

View File

@ -0,0 +1,376 @@
/* =========================================================
US01-02: Angebote und Pakete (+ Verweis auf Coaching-Rabatte)
Branch: feature_US01-02
Nur Selektoren unter #angebote verwenden.
Farben/Schriften über var(--th-...) aus base.css.
Vorlage: Prototyp (Angebotsbereich + Detaildialog)
========================================================= */
#angebote {
--ang-mint: #56d9b1;
--ang-line: rgb(255 255 255 / 0.11);
background: linear-gradient(135deg, #161625, #11111e);
border-block: 1px solid var(--ang-line);
}
/* ---------- Überschrift ---------- */
#angebote .angebote-heading {
display: flex;
align-items: flex-end;
justify-content: space-between;
gap: 3.5rem;
margin-bottom: 1.5rem;
}
#angebote .angebote-eyebrow {
margin: 0;
font-size: 0.875rem;
font-weight: 650;
letter-spacing: 0.12em;
text-transform: uppercase;
color: var(--ang-mint);
}
#angebote h2 {
margin: 0.9rem 0 0;
font-size: clamp(2.2rem, 3.4vw, 2.8rem);
font-weight: 400;
line-height: 1.12;
letter-spacing: -0.03em;
}
#angebote .angebote-heading > p {
max-width: 24rem;
margin: 0;
color: var(--th-text-muted);
}
#angebote .concept-note {
display: flex;
gap: 0.5rem;
margin: 0 0 2rem;
font-size: 1rem;
color: var(--th-text-muted);
}
/* ---------- Paketkarten ---------- */
#angebote .package-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 1.5rem;
}
#angebote .package {
display: flex;
flex-direction: column;
padding: 0.75rem;
border: 1px solid var(--ang-line);
border-radius: 18px;
background: linear-gradient(140deg, #202032, #181827);
transition: border-color 0.2s;
}
#angebote .package:hover,
#angebote .package.featured {
border-color: rgb(16 185 129 / 0.45);
}
#angebote .package-image {
position: relative;
overflow: hidden;
border-radius: 10px;
}
#angebote .package-image img {
width: 100%;
aspect-ratio: 1.65;
object-fit: cover;
transition: transform 0.4s;
}
#angebote .package:hover img {
transform: scale(1.03);
}
#angebote .package-badge {
position: absolute;
left: 0.75rem;
bottom: 0.75rem;
padding: 0.45rem 0.75rem;
border-radius: 5px;
background: var(--th-accent);
color: #06261a;
font-size: 0.75rem;
font-weight: 750;
letter-spacing: 0.08em;
text-transform: uppercase;
}
#angebote .package-body {
display: flex;
flex-direction: column;
flex: 1;
padding: 1.5rem 0.9rem 0.9rem;
}
#angebote .package-label {
margin: 0;
font-size: 0.875rem;
font-weight: 650;
letter-spacing: 0.12em;
text-transform: uppercase;
color: var(--ang-mint);
}
#angebote .package-body h3 {
margin: 0.8rem 0 1rem;
font-size: 1.9rem;
font-weight: 400;
line-height: 1.15;
}
#angebote .package-text {
margin: 0;
color: var(--th-text-muted);
}
#angebote .package-body ul {
flex: 1;
margin: 1.25rem 0 0;
padding: 1.25rem 0 1.5rem;
list-style: none;
border-top: 1px solid var(--ang-line);
}
#angebote .package-body li {
position: relative;
padding: 0.3rem 0 0.3rem 1.5rem;
}
#angebote .package-body li::before {
content: "✓";
position: absolute;
left: 0;
color: var(--ang-mint);
}
/* ---------- Buttons ---------- */
#angebote .package-button,
#angebote .dialog-done {
display: inline-flex;
align-items: center;
justify-content: space-between;
width: 100%;
min-height: 3.25rem;
padding: 0.9rem 1rem;
border: 1px solid rgb(255 255 255 / 0.15);
border-radius: 7px;
background: transparent;
color: var(--th-text-soft);
font-size: 1.0625rem;
font-weight: 650;
cursor: pointer;
transition: background 0.2s, border-color 0.2s;
}
#angebote .package-button:hover {
border-color: var(--th-accent);
background: rgb(16 185 129 / 0.08);
}
#angebote .package-button.is-primary,
#angebote .dialog-done {
border-color: transparent;
background: var(--th-accent);
color: #071b16;
}
#angebote .package-button.is-primary:hover,
#angebote .dialog-done:hover {
background: #37c99b;
}
/* ---------- Coaching-Rabatt ---------- */
#angebote .coaching-banner {
display: flex;
align-items: center;
gap: 1.5rem;
margin-top: 1.75rem;
padding: 1.6rem 1.9rem;
border: 1px solid rgb(16 185 129 / 0.19);
border-radius: 12px;
background: rgb(16 185 129 / 0.04);
}
#angebote .bonus-symbol {
padding-right: 1.5rem;
border-right: 1px solid rgb(16 185 129 / 0.25);
font-family: var(--th-font-display);
font-size: 2.2rem;
color: var(--ang-mint);
}
#angebote .coaching-banner h3 {
margin: 0 0 0.3rem;
font-family: var(--th-font-body);
font-size: 1.2rem;
font-weight: 600;
}
#angebote .coaching-banner p {
margin: 0;
color: var(--th-text-muted);
}
#angebote .bonus-button {
margin-left: auto;
min-height: 2.75rem;
border: 0;
background: none;
color: var(--ang-mint);
font-size: 1.0625rem;
font-weight: 600;
white-space: nowrap;
cursor: pointer;
}
/* ---------- Dialog ---------- */
#angebote dialog {
margin: auto; /* Kunden-CSS setzt margin:0 → Dialog wieder zentrieren */
width: min(680px, calc(100% - 2rem));
max-height: 90dvh;
padding: 0;
border: 1px solid rgb(255 255 255 / 0.15);
border-radius: 20px;
background: #171726;
color: var(--th-text-soft);
box-shadow: 0 30px 100px rgb(0 0 0 / 0.55);
}
#angebote dialog::backdrop {
background: rgb(5 5 11 / 0.8);
backdrop-filter: blur(6px);
}
#angebote .dialog-inner {
position: relative;
padding: 2.5rem;
}
#angebote .close-dialog {
position: absolute;
top: 0.75rem;
right: 1rem;
width: 2.75rem;
height: 2.75rem;
border: 0;
background: transparent;
color: var(--th-text-muted);
font-size: 1.9rem;
cursor: pointer;
}
#angebote .dialog-inner h2 {
margin: 0.8rem 2.2rem 1.1rem 0;
font-size: 2.2rem;
}
#angebote #dialog-description {
margin: 0;
color: var(--th-text-muted);
}
#angebote .dialog-inner h3 {
margin: 1.6rem 0 0.75rem;
font-family: var(--th-font-body);
font-size: 1.2rem;
font-weight: 600;
}
#angebote #dialog-features {
margin: 0;
padding-left: 1.25rem;
}
#angebote #dialog-features li {
padding: 0.25rem 0;
}
#angebote .dialog-hint {
margin: 0.75rem 0 0;
font-size: 1rem;
color: var(--th-text-muted);
}
#angebote .discount-box {
margin: 1.6rem 0;
padding: 1.4rem;
border: 1px solid rgb(16 185 129 / 0.27);
border-radius: 12px;
background: rgb(16 185 129 / 0.035);
}
#angebote .discount-box h3 {
margin-top: 0.4rem;
}
#angebote .discount-box label {
display: block;
margin-bottom: 0.5rem;
}
#angebote .discount-box select {
width: 100%;
padding: 0.75rem;
border: 1px solid rgb(255 255 255 / 0.25);
border-radius: 7px;
background: #11111f;
color: var(--th-text-soft);
font-size: 1.0625rem;
}
#angebote #discount-result {
margin: 0;
padding: 1rem 0 0;
color: var(--ang-mint);
}
#angebote #discount-result strong {
font-size: 1.6rem;
}
#angebote .discount-box a {
display: inline-block;
margin-top: 0.9rem;
color: var(--ang-mint);
}
#angebote select:focus-visible {
outline: 3px solid var(--th-accent);
outline-offset: 3px;
}
/* ---------- Tablet ---------- */
@media (max-width: 1100px) {
#angebote .angebote-heading { gap: 2rem; }
#angebote .package-body h3 { font-size: 1.7rem; }
#angebote .coaching-banner { flex-wrap: wrap; }
#angebote .bonus-button { margin-left: 0; }
}
/* ---------- Mobil ---------- */
@media (max-width: 760px) {
#angebote .angebote-heading { display: block; }
#angebote .angebote-heading > p { max-width: none; margin-top: 1.25rem; }
#angebote .package-grid { grid-template-columns: 1fr; }
#angebote .package-image img { aspect-ratio: 1.8; }
#angebote .coaching-banner { padding: 1.4rem; gap: 1rem; }
#angebote .coaching-banner > div:nth-child(2) { flex: 1; min-width: 12rem; }
#angebote .bonus-symbol { display: none; }
#angebote .dialog-inner { padding: 2rem 1.4rem; }
#angebote .dialog-inner h2 { font-size: 1.9rem; }
}
@media (prefers-reduced-motion: reduce) {
#angebote .package img { transition: none; }
}

View File

@ -1,9 +0,0 @@
/* =========================================================
US01-02: Vorteile auf einen Blick
Branch: feature/US01-02-vorteile
Nur Selektoren unter #vorteile verwenden.
Farben/Schriften über var(--th-...) aus base.css.
========================================================= */
#vorteile {
}

View File

@ -19,7 +19,7 @@
<!-- Ein Stylesheet pro User Story – jede Datei gehört genau einem Feature-Branch --> <!-- Ein Stylesheet pro User Story – jede Datei gehört genau einem Feature-Branch -->
<link rel="stylesheet" href="css/sections/hero.css"> <!-- US01-01 --> <link rel="stylesheet" href="css/sections/hero.css"> <!-- US01-01 -->
<link rel="stylesheet" href="css/sections/vorteile.css"> <!-- US01-02 --> <link rel="stylesheet" href="css/sections/angebote.css"> <!-- US01-02 -->
<link rel="stylesheet" href="css/sections/ablauf.css"> <!-- US01-03 --> <link rel="stylesheet" href="css/sections/ablauf.css"> <!-- US01-03 -->
</head> </head>
<body> <body>
@ -52,7 +52,7 @@
</p> </p>
<div class="hero-actions"> <div class="hero-actions">
<a class="hero-button" href="#vorteile">Mehr erfahren <span aria-hidden="true">↓</span></a> <a class="hero-button" href="#angebote">Mehr erfahren <span aria-hidden="true">↓</span></a>
<a class="hero-link" href="#ablauf">So funktioniert’s <span aria-hidden="true">→</span></a> <a class="hero-link" href="#ablauf">So funktioniert’s <span aria-hidden="true">→</span></a>
</div> </div>
@ -100,11 +100,118 @@
<!-- ========== /US01-01 ========== --> <!-- ========== /US01-01 ========== -->
<!-- ========== US01-02: Vorteile auf einen Blick ========== <!-- ========== US01-02: Angebote und Pakete ==========
Branch: feature/US01-02-vorteile · Styles: css/sections/vorteile.css Branch: feature_US01-02 · Styles: css/sections/angebote.css · Script: js/angebote.js
Nur innerhalb dieser <section> arbeiten. --> Nur innerhalb dieser <section> arbeiten. -->
<section id="vorteile" aria-labelledby="vorteile-title"> <section id="angebote" aria-labelledby="angebote-title">
<!-- US01-02: Inhalt hier einfügen --> <div class="container">
<div class="angebote-heading">
<div>
<p class="angebote-eyebrow">Deine Ernährung. Deine Begleitung.</p>
<h2 id="angebote-title">So viel Unterstützung,<br>wie du brauchst.</h2>
</div>
<p>Ein klarer Einstieg oder eine enge Begleitung: Wähle den Rahmen, der sich für dich richtig anfühlt.</p>
</div>
<p class="concept-note"><span aria-hidden="true">ⓘ</span> Angebotskonzept · Leistungen zur Abstimmung. Preise und Laufzeiten sind noch offen.</p>
<div class="package-grid">
<article class="package">
<div class="package-image">
<img src="assets/images/angebote/starter.webp" width="700" height="420" alt="Gemeinsame Planung am See für den sportlichen Einstieg" loading="lazy">
</div>
<div class="package-body">
<p class="package-label">Starter</p>
<h3>Dein erster<br>guter Schritt.</h3>
<p class="package-text">Für Einsteiger und Wiedereinsteiger, die ihre Ernährung verstehen und gezielt verändern möchten.</p>
<ul>
<li>Persönliche Bestandsaufnahme</li>
<li>Analyse deiner Essgewohnheiten</li>
<li>Konkrete Empfehlungen für den Alltag</li>
</ul>
<button type="button" class="package-button" data-package="starter">Starter entdecken <span aria-hidden="true">↗</span></button>
</div>
</article>
<article class="package featured">
<div class="package-image">
<img src="assets/images/angebote/standard.webp" width="700" height="420" alt="Sportler und Coach besprechen gemeinsam einen Plan am Fahrrad" loading="lazy">
<span class="package-badge">Mit individuellem Plan</span>
</div>
<div class="package-body">
<p class="package-label">Standard</p>
<h3>Aus guten Vorsätzen<br>wird dein Plan.</h3>
<p class="package-text">Für alle, die Ernährung und Training aufeinander abstimmen und neue Routinen festigen möchten.</p>
<ul>
<li>Alles aus Starter</li>
<li>Individueller Ernährungsrahmen</li>
<li>Feedback und gemeinsame Anpassung</li>
</ul>
<button type="button" class="package-button is-primary" data-package="standard">Standard entdecken <span aria-hidden="true">↗</span></button>
</div>
</article>
<article class="package">
<div class="package-image">
<img src="assets/images/angebote/premium.webp" width="700" height="420" alt="Persönliches Coachinggespräch auf einer Bank neben einem Triathlonrad" loading="lazy">
</div>
<div class="package-body">
<p class="package-label">Premium</p>
<h3>Dein Ziel.<br>Ganz persönlich.</h3>
<p class="package-text">Für ambitionierte Ziele und alle, die sich eine besonders enge, individuelle Begleitung wünschen.</p>
<ul>
<li>Alles aus Standard</li>
<li>Verpflegungsstrategie für Wettkämpfe</li>
<li>Engmaschige Begleitung und Anpassung</li>
</ul>
<button type="button" class="package-button" data-package="premium">Premium entdecken <span aria-hidden="true">↗</span></button>
</div>
</article>
</div>
<!-- Verweis auf Rabatte aus dem Coaching 1zu1 -->
<div class="coaching-banner">
<div class="bonus-symbol" aria-hidden="true">%</div>
<div>
<h3>Schon im Coaching 1zu1? Dein Vorteil kommt mit.</h3>
<p>Mit deinem Coaching-Paket sparst du bei der Ernährungsberatung: Starter 3&nbsp;%, Standard 5&nbsp;%, Premium 10&nbsp;%.</p>
</div>
<button type="button" class="bonus-button" data-package="bonus">Rabatte ansehen <span aria-hidden="true">↗</span></button>
</div>
</div>
<!-- Detaildialog (öffnet nur per Klick, kein Pop-up beim Laden) -->
<dialog id="package-dialog" aria-labelledby="dialog-title">
<div class="dialog-inner">
<button type="button" class="close-dialog" aria-label="Details schließen">×</button>
<p id="dialog-eyebrow" class="angebote-eyebrow"></p>
<h2 id="dialog-title"></h2>
<p id="dialog-description"></p>
<div id="detail-section">
<h3>Deine Begleitung im Überblick</h3>
<ul id="dialog-features"></ul>
<p class="dialog-hint">Konzeptvorschlag: Leistungsumfang, Kontaktfrequenz, Laufzeit und Preis werden noch abgestimmt.</p>
</div>
<div class="discount-box">
<p class="angebote-eyebrow">Dein Coaching-Vorteil</p>
<h3>Du bist schon im Coaching 1zu1?</h3>
<label for="coaching-select">Dein bestehendes Coaching-Paket</label>
<select id="coaching-select">
<option value="0">Noch kein Coaching 1zu1</option>
<option value="3">Starter-Coaching</option>
<option value="5">Standard-Coaching</option>
<option value="10">Premium-Coaching (24 oder 52 Wochen)</option>
</select>
<p id="discount-result" aria-live="polite"></p>
<p class="dialog-hint">Der Rabatt richtet sich nach deinem bestehenden Coaching 1zu1, unabhängig vom gewählten Ernährungsangebot.</p>
<a href="https://tri-hub.de/coaching-1zu1" target="_blank" rel="noopener">Coaching 1zu1 bei TriHub ansehen ↗</a>
</div>
<button type="button" class="dialog-done">Zurück zu den Angeboten</button>
</div>
</dialog>
</section> </section>
<!-- ========== /US01-02 ========== --> <!-- ========== /US01-02 ========== -->
@ -120,5 +227,6 @@
</main> </main>
<script src="js/main.js" defer></script> <script src="js/main.js" defer></script>
<script src="js/angebote.js" defer></script> <!-- US01-02 -->
</body> </body>
</html> </html>

105
js/angebote.js 100644
View File

@ -0,0 +1,105 @@
// US01-02: Paketdetails und Coaching-Rabatt im Dialog (Vorlage: Prototyp)
'use strict';
(() => {
const section = document.querySelector('#angebote');
if (!section) return;
const packages = {
starter: {
name: 'Starter',
title: 'Dein erster guter Schritt.',
description: 'Du möchtest wissen, wo du stehst und was du konkret verändern kannst? Starter schafft eine persönliche Grundlage für deinen Einstieg.',
features: [
'Gemeinsames Erstgespräch zu Zielen, Training, Alltag und Vorlieben.',
'Auswertung eines Ernährungsprotokolls mit Blick auf deine bisherigen Gewohnheiten.',
'Persönliche Handlungsempfehlungen für Essen und Trinken im Alltag und rund um dein Training.',
'Abschlussgespräch zur Einordnung deiner nächsten Schritte.'
]
},
standard: {
name: 'Standard',
title: 'Aus guten Vorsätzen wird dein Plan.',
description: 'Du möchtest aus einzelnen Empfehlungen eine verlässliche Routine machen? Standard verbindet die Bestandsaufnahme mit einem individuellen Plan und persönlichem Feedback.',
features: [
'Alle vorgeschlagenen Leistungen aus Starter.',
'Ein individueller Ernährungsrahmen für Trainings- und Ruhetage.',
'Praktische Mahlzeitenideen, abgestimmt auf deine Vorlieben und deinen Alltag.',
'Feedbackgespräche, um Erfahrungen zu besprechen und den Plan gemeinsam anzupassen.'
]
},
premium: {
name: 'Premium',
title: 'Dein Ziel. Ganz persönlich.',
description: 'Du möchtest deine Ernährung gezielt auf sportliche Herausforderungen abstimmen? Premium sieht eine besonders enge Begleitung bis in die Wettkampfvorbereitung vor.',
features: [
'Alle vorgeschlagenen Leistungen aus Standard.',
'Eine individuelle Strategie für Essen und Trinken vor, während und nach Wettkämpfen.',
'Erprobung der Verpflegung im Training und Anpassung an deine Erfahrungen.',
'Engmaschiger persönlicher Austausch und laufende Anpassungen an deine Trainingsphasen.'
]
}
};
const dialog = section.querySelector('#package-dialog');
const select = section.querySelector('#coaching-select');
const result = section.querySelector('#discount-result');
let trigger = null;
function updateDiscount() {
const rate = Number(select.value);
result.replaceChildren();
if (rate) {
const strong = document.createElement('strong');
strong.textContent = `${rate} % Rabatt`;
result.append(strong, document.createTextNode(' auf deine Ernährungsberatung.'));
} else {
result.textContent = 'Mit Coaching 1zu1: Starter 3 %, Standard 5 %, Premium 10 % Rabatt.';
}
}
function openDetails(key, button) {
trigger = button;
const data = packages[key];
section.querySelector('#dialog-eyebrow').textContent =
data ? `Ernährungsberatung · ${data.name}` : 'Coaching 1zu1 · Bonusregelung';
section.querySelector('#dialog-title').textContent =
data ? data.title : 'Dein Coaching zahlt sich aus.';
section.querySelector('#dialog-description').textContent = data
? data.description
: 'Dein bestehendes Coaching-Paket bringt dir einen Preisvorteil bei der Ernährungsberatung. Wähle es aus und sieh deinen Rabatt.';
section.querySelector('#detail-section').hidden = !data;
const list = section.querySelector('#dialog-features');
list.replaceChildren();
if (data) {
data.features.forEach((text) => {
const li = document.createElement('li');
li.textContent = text;
list.append(li);
});
}
updateDiscount();
dialog.showModal();
}
section.querySelectorAll('[data-package]').forEach((button) => {
button.addEventListener('click', () => openDetails(button.dataset.package, button));
});
select.addEventListener('change', updateDiscount);
section.querySelectorAll('.close-dialog, .dialog-done').forEach((button) => {
button.addEventListener('click', () => dialog.close());
});
// Klick auf den abgedunkelten Hintergrund schließt den Dialog
dialog.addEventListener('click', (event) => {
if (event.target === dialog) dialog.close();
});
// Fokus zurück auf den auslösenden Button (Barrierefreiheit)
dialog.addEventListener('close', () => trigger?.focus());
})();