Compare commits

..

23 Commits

Author SHA1 Message Date
3015418 d75916d142 Kontaktbutton korrigiert 2026-09-30 12:27:20 +02:00
Vincent Laux 673942e0fb paket empfehlungsbadge alignment angepasst 2026-09-30 12:15:12 +02:00
3015418 6ba39a9659 Kontaktformular und Pakete korrigiert
Kommentare ergänzt, Menüabstände angepasst und Paketbild aktualisiert.
2026-09-29 21:44:20 +02:00
koriboh c592bff01a Refactor navigation structure and styles for improved consistency and usability 2026-09-29 19:46:41 +02:00
Vincent Laux dc0de8df76 kontakt link fix 2026-09-29 18:24:41 +02:00
Vincent Laux d3ca135d9d git 2026-09-29 18:15:08 +02:00
Shahnam Javidnia f62c4e2e89 Update index.html 2026-09-29 18:08:21 +02:00
koriboh 662c8ddf3c Refactor code structure for improved readability and maintainability 2026-09-29 17:53:04 +02:00
3015418 7a2d0d8e26 Responsive Paketbilder korrigiert und Menüfarbe angepasst
Paketbilder proportional mit oberer Ausrichtung dargestellt und eine zweispaltige Zwischenansicht ergänzt. Gespeicherte Farbanpassung des Menüknopfs übernommen.
2026-09-29 17:42:03 +02:00
Vincent Laux 03c2bc7ab8 style changes 2026-09-29 17:10:35 +02:00
Vincent Laux a32d6f688f new font color muted 2 2026-09-29 16:52:21 +02:00
3015418 1ba8844415 US 04-05: Responsive Navigation und Startseitenlink ergänzt
Mobiles Menü mit Tastaturbedienung, Hintergrund und passenden Sprungabständen ergänzt. Logo abgerundet und mit der Startseite verlinkt. Kontakt bleibt vorerst deaktiviert; das neue Supportbild liegt im Bilderordner bereit.
2026-09-29 16:42:38 +02:00
koriboh 169e72ca35 Remove unused kontakt.html file 2026-09-29 16:19:25 +02:00
Vincent Laux 39952a0c36 navigationsbar um Übersicht, Pakete, Berater, Infos & Kontakt erweitert und placeholders für Links in Index.html 2026-09-29 16:15:08 +02:00
koriboh f5a650134a Add contact page with form and styling; implement scroll-reveal and form validation logic 2026-09-29 15:59:01 +02:00
Vincent Laux f58ee5e483 hardcoded values refactored 2026-09-29 15:58:38 +02:00
3015418 5d1272008e US 04-05: Dauerhaft sichtbare Kopfzeile für die Navigation vorbereitet
Kopfzeile aus der Produktübersicht ausgelagert und Navigationsstile in einer eigenen CSS-Datei zusammengefasst. Aktuellen Zwischenstand mit angepasstem Logo übernommen.
2026-09-29 15:43:01 +02:00
Vincent Laux 45f5b8e9b6 aligned 2026-09-29 15:40:29 +02:00
3015418 0828e3de7c Korrekturen von Shahnam und Vincent für die Navigation zusammengeführt
Produktübersicht und Pakete unverändert aus den Fehlerbehebungsbranches kombiniert. Laufende lokale Navigationsarbeiten bleiben außerhalb dieses Commits.
2026-09-29 15:31:49 +02:00
Vincent Laux abeb935792 pakete Bilder eingefügt, Borderfarbe angepasst, texte aligned und CTA angepasst 2026-09-29 15:27:55 +02:00
3015418 1da0029c9b US 04-01: Fehler in der Produktübersicht behoben
Platzhalter durch Beratungstexte ersetzt, neue Ernährungsbilder ergänzt und die Bilddarstellung sowie Textfarben angepasst. Den auskommentierten Platzhalter-Footer entfernt.
2026-09-29 14:55:47 +02:00
Vincent Laux 0338cb738d neuer branch für Pakete fehler behebung, neue bilder eingefügt 2026-09-29 14:44:36 +02:00
Ahmad Luay Eleiwy 2a4f8a089d Merge pull request 'integration/ernaehrung-review' (#2) from integration/ernaehrung-review into main
Reviewed-on: #2
2026-09-29 10:40:47 +02:00
28 changed files with 1625 additions and 174 deletions

BIN
.DS_Store vendored

Binary file not shown.

View File

@ -78,6 +78,7 @@
flex-direction: column;
}
/* ---------- Profilbild, Name und Rolle innerhalb einer Karte ---------- */
.berater-section .berater-image-wrapper {
background-color: var(--background);
border-radius: 10px;
@ -112,6 +113,7 @@
font-weight: 600;
}
/* ---------- Biografie und ergänzende Angaben aus berater.json ---------- */
.berater-section .berater-bio-box {
/* Hintergrund entfernt, um sich nahtlos in die Karte einzufügen */
background-color: transparent;

View File

@ -278,11 +278,10 @@
/* ---------- Footer ---------- */
.beratung-bereich .logo {
font-family: var(--font-display);
font-weight: 700;
font-family: var(--font-display);;
font-size: 1.35rem;
letter-spacing: -0.02em;
color: var(--foreground);
color: var(--text-muted);
text-decoration: none;
white-space: nowrap;
}

643
css/kontakt.css 100644
View File

@ -0,0 +1,643 @@
/* Dennis: Kontaktseite – Regeln sind auf .kontakt-seite begrenzt.
Tokens entsprechen css/beratung.css (tri-hub.de-Farbwelt). */
.kontakt-seite {
/* Farben – Tokens von tri-hub.de */
--background: #0714198a;
--background-rgb: 7 20 25;
--background-panel: #152027;
--text-primary: #ffffff;
--text-muted: #9ba7aa;
--accent: #4597A3;
--accent-warm: #f7c230;
--border: rgba(234, 8, 8, 0.1);
/* Typografie */
--font-display: "Sora", sans-serif;
--font-body: "DM Sans", Arial, Helvetica, sans-serif;
/* Layout */
--container: 1120px;
--radius: 1.1rem;
}
/* ---------- Basic ---------- */
.kontakt-seite,
.kontakt-seite *,
.kontakt-seite::before,
.kontakt-seite *::before,
.kontakt-seite::after,
.kontakt-seite *::after {
box-sizing: border-box;
}
.kontakt-seite {
min-height: 100vh;
margin: 0;
background-color: var(--background);
color: var(--text-primary);
font-family: var(--font-body);
line-height: 1.625;
-webkit-font-smoothing: antialiased;
position: relative;
isolation: isolate;
scroll-behavior: smooth;
}
/* Licht */
.kontakt-seite::before {
content: "";
position: absolute;
inset: 0;
z-index: -1;
background:
radial-gradient(900px 520px at 78% -10%, rgba(0, 179, 194, 0.13), transparent 65%),
radial-gradient(700px 480px at -10% 30%, rgba(35, 137, 148, 0.1), transparent 60%),
radial-gradient(600px 420px at 90% 95%, rgba(247, 194, 48, 0.05), transparent 60%);
pointer-events: none;
}
.kontakt-seite h1,
.kontakt-seite h2,
.kontakt-seite h3 {
margin-top: 0;
font-family: var(--font-display);
color: var(--text-primary);
line-height: 1.2;
letter-spacing: -0.01em;
}
.kontakt-seite p {
margin: 0 0 1em;
}
.kontakt-seite .container {
width: min(100% - 48px, var(--container));
margin-inline: auto;
}
/* ---------- Typo-Helfer ---------- */
.kontakt-seite .kicker {
display: inline-flex;
align-items: center;
gap: 10px;
font-size: 0.78rem;
font-weight: 600;
letter-spacing: 0.18em;
text-transform: uppercase;
color: var(--accent);
margin-bottom: 18px;
}
.kontakt-seite .kicker::before {
content: "";
width: 28px;
height: 2px;
border-radius: 2px;
background: var(--accent);
}
.kontakt-seite .lead {
font-size: 1.15rem;
color: var(--text-muted);
max-width: 660px;
}
/* Die Navigationsleiste oben wird von css/navigation.css gestylt (.site-navigation). */
/* ---------- Hero ---------- */
.kontakt-seite .hero {
padding: 72px 0 88px;
}
.kontakt-seite .hero__grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 48px;
align-items: center;
}
.kontakt-seite .hero h1 {
font-size: clamp(2.6rem, 6vw, 4rem);
font-weight: 500;
letter-spacing: -0.04em;
margin-bottom: 20px;
}
.kontakt-seite .hero__text {
font-size: 1.15rem;
color: var(--text-muted);
max-width: 560px;
}
.kontakt-seite .hero__image {
display: block;
width: 100%;
height: auto;
border-radius: calc(var(--radius) + 1rem);
border: 1px solid var(--border);
box-shadow: 0 30px 90px rgba(0, 0, 0, 0.35);
}
.hero--ribbon {
position: relative;
background-image: url("../images/trihub-ribbon-abstract.webp");
background-size: cover;
background-position: center;
background-repeat: no-repeat;
background-color: var(--background); /* Fallback / Grundton */
isolation: isolate; /* hält z-index sauber */
}
.hero--ribbon::before {
content: "";
position: absolute;
inset: 0;
background:
linear-gradient(0deg, rgb(var(--background-rgb) / 96%), transparent 42%),
linear-gradient(90deg, rgb(var(--background-rgb) / 94%) 0%, rgb(var(--background-rgb) / 79%) 35%, rgb(var(--background-rgb) / 38%) 72%, rgb(var(--background-rgb) / 25%) 100%);
content: ""; /* dunkler Schleier */
z-index: 0;
pointer-events: none;
}
.hero--ribbon .hero__grid {
position: relative;
z-index: 1;
}
/* ---------- Sektionen ---------- */
.kontakt-seite .section {
padding: 88px 0;
}
.kontakt-seite .section + .section {
border-top: 1px solid var(--border);
}
.kontakt-seite .section-head {
max-width: 720px;
margin-bottom: 48px;
}
.kontakt-seite .section-head h2 {
font-size: clamp(1.9rem, 4vw, 2.7rem);
margin-bottom: 16px;
}
/* ---------- Karten (Nachricht) ---------- */
.kontakt-seite .cards-3 {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: 24px;
}
.kontakt-seite .card {
background-color: var(--background-panel);
border: 1px solid var(--border);
border-radius: calc(var(--radius) + 0.4rem);
padding: 28px;
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.15);
}
.kontakt-seite .card__icon {
display: grid;
place-items: center;
width: 48px;
height: 48px;
margin-bottom: 16px;
border-radius: 12px;
font-size: 1.5rem;
background: rgba(69, 151, 163, 0.2);
}
.kontakt-seite .card--warm .card__icon {
background: rgba(247, 194, 48, 0.16);
}
.kontakt-seite .card--neutral .card__icon {
background: rgba(255, 255, 255, 0.1);
}
.kontakt-seite .card h3 {
font-size: 1.08rem;
margin-bottom: 8px;
}
.kontakt-seite .card p {
margin: 0;
color: var(--text-muted);
font-size: 0.95rem;
}
/* ---------- Formular + Kontaktinfos ---------- */
.kontakt-seite .kontakt-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 48px;
align-items: start;
}
.kontakt-seite form {
margin-top: 32px;
display: grid;
gap: 20px;
}
.kontakt-seite .field label {
display: block;
font-size: 0.9rem;
font-weight: 500;
color: var(--text-primary);
margin-bottom: 8px;
}
.kontakt-seite .field input,
.kontakt-seite .field select,
.kontakt-seite .field textarea {
width: 100%;
padding: 12px 16px;
border-radius: 10px;
border: 1px solid var(--border);
background: rgba(255, 255, 255, 0.04);
color: var(--text-primary);
font-family: var(--font-body);
font-size: 0.95rem;
transition: border-color 0.15s ease, background-color 0.15s ease;
}
.kontakt-seite .field input::placeholder,
.kontakt-seite .field textarea::placeholder {
color: rgba(255, 255, 255, 0.4);
}
.kontakt-seite .field input:focus,
.kontakt-seite .field select:focus,
.kontakt-seite .field textarea:focus {
outline: none;
border-color: var(--accent);
background: rgba(255, 255, 255, 0.06);
}
.kontakt-seite .field select option {
background: #0b1a20;
color: var(--text-primary);
}
.kontakt-seite .field textarea {
min-height: 140px;
resize: vertical;
}
/* kontakt.js setzt die Zustandsklassen für ungültige Felder und Rückmeldungen. */
.kontakt-seite .field input.invalid,
.kontakt-seite .field select.invalid,
.kontakt-seite .field textarea.invalid {
border-color: #ef4444;
}
.kontakt-seite .field input.valid,
.kontakt-seite .field textarea.valid {
border-color: #22c55e;
}
.kontakt-seite .field__error {
display: none;
margin-top: 6px;
font-size: 0.8rem;
color: #f87171;
}
.kontakt-seite .field__hint {
display: none;
margin-top: 6px;
font-size: 0.8rem;
color: #4ade80;
}
.kontakt-seite .field.has-error .field__error {
display: block;
}
.kontakt-seite .field.is-ok .field__hint {
display: block;
}
/* ---------- Button ---------- */
.kontakt-seite .btn {
display: inline-flex;
align-items: center;
justify-content: center;
gap: 0.5rem;
padding: 0.8rem 1.5rem;
border-radius: 999px;
font-family: var(--font-body);
font-weight: 600;
font-size: 0.95rem;
line-height: 1.2;
text-decoration: none;
border: 1px solid transparent;
cursor: pointer;
transition: transform 0.18s ease, box-shadow 0.18s ease,
background-color 0.18s ease, border-color 0.18s ease, color 0.18s ease;
}
.kontakt-seite .btn-accent {
background: var(--accent);
color: #03242b;
}
.kontakt-seite .btn-accent:hover:not(:disabled) {
transform: translateY(-1px);
box-shadow: 0 10px 28px rgba(0, 179, 194, 0.28);
}
.kontakt-seite .btn-block {
width: 100%;
}
.kontakt-seite .btn:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.kontakt-seite .form-status {
display: none;
font-size: 0.9rem;
}
.kontakt-seite .form-status.is-visible {
display: block;
}
.kontakt-seite .form-status--ok {
color: #4ade80;
}
.kontakt-seite .form-status--error {
color: #f87171;
}
/* ---------- Kontaktinformations-Karten ---------- */
.kontakt-seite .info-stack {
display: grid;
gap: 20px;
}
.kontakt-seite .info-card {
display: flex;
align-items: flex-start;
gap: 16px;
background-color: var(--background-panel);
border: 1px solid var(--border);
border-radius: calc(var(--radius) + 0.4rem);
padding: 22px;
transition: border-color 0.15s ease;
}
.kontakt-seite .info-card:hover {
border-color: rgba(69, 151, 163, 0.5);
}
.kontakt-seite .info-card__icon {
display: grid;
place-items: center;
flex-shrink: 0;
width: 40px;
height: 40px;
margin-top: 2px;
border-radius: 10px;
background: rgba(69, 151, 163, 0.2);
color: var(--accent);
}
.kontakt-seite .info-card__label {
font-size: 0.7rem;
letter-spacing: 0.2em;
text-transform: uppercase;
color: var(--text-muted);
margin-bottom: 4px;
}
.kontakt-seite .info-card a,
.kontakt-seite .info-card p {
color: var(--text-primary);
font-weight: 500;
text-decoration: none;
margin: 0;
}
.kontakt-seite .info-card a:hover {
color: var(--accent);
}
.kontakt-seite .info-card p small,
.kontakt-seite .info-card p.info-sub {
display: block;
font-size: 0.85rem;
font-weight: 400;
color: var(--text-muted);
}
/* ---------- Socials ---------- */
.kontakt-seite .socials {
display: flex;
gap: 12px;
}
.kontakt-seite .socials a {
display: grid;
place-items: center;
width: 44px;
height: 44px;
border-radius: 10px;
border: 1px solid var(--border);
background: rgba(255, 255, 255, 0.04);
color: var(--text-primary);
text-decoration: none;
font-weight: 600;
font-size: 0.8rem;
transition: border-color 0.15s ease, background-color 0.15s ease;
}
.kontakt-seite .socials a:hover {
border-color: var(--accent);
background: rgba(69, 151, 163, 0.12);
}
/* ---------- Standort / Karte ---------- */
.kontakt-seite .map-frame {
border-radius: calc(var(--radius) + 0.9rem);
border: 1px solid var(--border);
overflow: hidden;
height: 500px;
background: var(--background-panel);
}
.kontakt-seite .map-frame iframe {
display: block;
width: 100%;
height: 100%;
border: 0;
}
/* ---------- Footer ---------- */
.kontakt-seite .logo {
font-family: var(--font-display);
font-size: 1.35rem;
letter-spacing: -0.02em;
color: var(--text-muted);
text-decoration: none;
white-space: nowrap;
}
.kontakt-seite .logo span {
color: var(--accent);
}
.kontakt-seite .site-footer {
margin-top: 96px;
padding: 64px 0 40px;
border-top: 1px solid var(--border);
background: rgba(255, 255, 255, 0.015);
}
.kontakt-seite .footer-grid {
display: grid;
grid-template-columns: 2fr 1fr 1fr;
gap: 48px;
}
.kontakt-seite .footer-brand p {
color: var(--text-muted);
font-size: 0.95rem;
max-width: 340px;
margin-top: 14px;
}
.kontakt-seite .footer-col h3 {
font-size: 0.85rem;
letter-spacing: 0.14em;
text-transform: uppercase;
color: var(--text-muted);
margin-bottom: 16px;
}
.kontakt-seite .footer-col ul {
list-style: none;
margin: 0;
padding: 0;
}
.kontakt-seite .footer-col a {
display: block;
padding: 5px 0;
color: var(--text-muted);
text-decoration: none;
font-size: 0.92rem;
transition: color 0.15s ease;
}
.kontakt-seite .footer-col a:hover {
color: var(--text-primary);
}
.kontakt-seite .footer-legal {
display: flex;
flex-wrap: wrap;
justify-content: space-between;
gap: 12px;
margin-top: 48px;
padding-top: 24px;
border-top: 1px solid var(--border);
color: var(--text-muted);
font-size: 0.85rem;
}
.kontakt-seite .footer-legal a {
color: inherit;
text-decoration: none;
}
.kontakt-seite .footer-legal a:hover {
color: var(--text-primary);
}
/* ---------- Scroll-Reveal ---------- */
.kontakt-seite .reveal {
opacity: 0;
transform: translateY(18px);
transition: opacity 0.6s ease, transform 0.6s ease;
}
.kontakt-seite .reveal.visible {
opacity: 1;
transform: none;
}
/* ---------- Responsiv ---------- */
@media (max-width: 980px) {
.kontakt-seite .hero__grid,
.kontakt-seite .kontakt-grid {
grid-template-columns: minmax(0, 1fr);
}
.kontakt-seite .cards-3 {
grid-template-columns: minmax(0, 1fr);
}
.kontakt-seite .footer-grid {
grid-template-columns: 1fr 1fr;
}
}
@media (max-width: 760px) {
.kontakt-seite .section {
padding: 64px 0;
}
.kontakt-seite .hero {
padding: 56px 0 64px;
}
.kontakt-seite .footer-grid {
grid-template-columns: 1fr;
gap: 32px;
}
.kontakt-seite .map-frame {
height: 380px;
}
}
/* ---------- Reduzierte Bewegung ---------- */
@media (prefers-reduced-motion: reduce) {
.kontakt-seite {
scroll-behavior: auto;
}
.kontakt-seite .reveal {
opacity: 1;
transform: none;
transition: none;
}
.kontakt-seite,
.kontakt-seite * {
transition-duration: 0.01ms !important;
}
}

View File

@ -67,7 +67,7 @@
/* Kästchen nebeneinander, jede Karte für sich */
.pakete .pakete__grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: 1.5rem;
align-items: stretch;
}
@ -75,15 +75,18 @@
.pakete .paket-karte {
display: flex;
flex-direction: column;
min-width: 0;
background: var(--background-panel);
border: 1px solid rgba(248, 250, 252, 0.08);
border-radius: 14px;
padding: 2rem 1.75rem;
padding: 0; /* Entfernt, damit das Bild oben bündig anliegt */
overflow: hidden;
position: relative;
}
/* Empfehlung kommt aus der JSON-Datei; die Auswahlklasse setzt leistungen.js beim Klick. */
.pakete .paket-karte--empfohlen {
border-color: var(--accent);
border-color: 1px solid rgba(248, 250, 252, 0.08);
box-shadow: 0 0 0 1px var(--accent-soft);
}
@ -97,13 +100,46 @@
background: var(--accent-soft);
color: var(--accent);
border: 1px solid var(--accent);
font-size: 0.75rem;
font-size: 0.7rem;
font-weight: 600;
letter-spacing: 0.06em;
text-transform: uppercase;
padding: 0.35rem 0.9rem;
padding: 0.25rem 0.65rem;
border-radius: 999px;
margin: 0 0 1rem;
margin: 0; /* Kein Abstand nach unten, da er jetzt im Header neben der Überschrift steht */
line-height: 1;
}
.pakete .paket-karte__image-wrapper {
width: 100%;
/* Proportional skalieren, damit beim Spaltenwechsel keine extremen Bildausschnitte entstehen. */
aspect-ratio: 3 / 2;
flex-shrink: 0;
overflow: hidden;
position: relative;
}
.pakete .paket-karte__image {
width: 100%;
height: 100%;
object-fit: cover;
object-position: center top;
display: block;
}
/* ---------- Karteninhalt: Paketname, Preis, Beschreibung und Leistungen ---------- */
.pakete .paket-karte__body {
padding: 1.75rem;
display: flex;
flex-direction: column;
flex-grow: 1;
}
.pakete .paket-karte__header {
display: flex;
align-items: center;
gap: 0.75rem;
margin-bottom: 0.5rem;
flex-wrap: nowrap;
}
.pakete .paket-karte__name {
@ -166,13 +202,14 @@
background: var(--accent);
}
/* ---------- Paketauswahl und Tastaturfokus ---------- */
.pakete .paket-karte__cta {
font-family: var(--font-body);
font-weight: 600;
font-size: 0.95rem;
color: var(--text-primary);
background: var(--accent);
border: none;
background: var(--background);
border: 1px solid rgba(248, 250, 252, 0.08);
border-radius: 8px;
padding: 0.85rem 1rem;
cursor: pointer;
@ -189,12 +226,20 @@
outline-offset: 2px;
}
/* ---------- Spaltenwechsel: drei, zwei, dann eine; Empfehlung bleibt mobil zuerst ---------- */
@media (max-width: 860px) {
.pakete .pakete__grid {
grid-template-columns: 1fr;
grid-template-columns: repeat(2, minmax(0, 1fr));
}
.pakete .paket-karte--empfohlen {
order: -1;
}
}
@media (max-width: 620px) {
.pakete .pakete__grid {
grid-template-columns: minmax(0, 480px);
justify-content: center;
}
}

261
css/navigation.css 100644
View File

@ -0,0 +1,261 @@
/* Shahnam und Vincent: Gemeinsame Navigation für US 04-05. */
:root {
/* navigation.js ergänzt den tatsächlich gemessenen Abstand für Anker-Sprünge. */
--navigation-height: 99px;
scroll-padding-top: calc(var(--navigation-offset, var(--navigation-height)) + 16px);
}
.site-navigation {
color-scheme: dark;
--background: #071419;
--background-panel: #152027;
--accent: #4597a3;
--accent-soft: rgb(69 151 163 / 14%);
--text-primary: #fff;
--text-muted: #7e858a;
--text-muted2: #b7bec2;
--border: rgb(255 255 255 / 16%);
/* Bleibt beim Scrollen sichtbar und behält ihren Platz im Layout. */
position: sticky;
top: 0;
z-index: 100;
border-top: 3px solid var(--background);
background: var(--background);
color: var(--text-primary);
font-family: system-ui, -apple-system, "Segoe UI", Arial, sans-serif;
line-height: 1.6;
box-shadow: 0 4px 16px rgb(0 0 0 / 16%);
}
.site-navigation,
.site-navigation * {
box-sizing: border-box;
}
/* ---------- Kopfzeile: Logo, Begleittext und Menü ---------- */
.site-navigation__bar {
display: flex;
align-items: center;
justify-content: space-between;
gap: 24px;
width: min(1680px, calc(100% - clamp(40px, 10vw, 192px)));
margin-inline: auto;
}
.site-navigation .brand {
display: flex;
align-items: center;
gap: 20px;
min-width: 0;
min-height: calc(var(--navigation-height) - 3px);
padding-block: 8px;
}
/* ---------- Navigationslinks und ihre Klickflächen ---------- */
.site-navigation__links ul {
display: flex;
align-items: center;
gap: clamp(60px, 2.5vw, 32px);
margin: 0;
padding: 0;
list-style: none;
}
.site-navigation__links a,
.site-navigation .navigation-disabled {
display: block;
padding-block: 10px;
padding-inline: 14px;
color: var(--text-muted);
font-size: 19px;
font-weight: 250;
text-decoration: none;
white-space: nowrap;
}
/* Hover-Highlight, übernommen aus der ursprünglichen Navigationsleiste in kontakt.css. */
.site-navigation__links a {
border-radius: 8px;
transition: background-color 0.15s ease, color 0.15s ease;
}
.site-navigation__links a:hover,
.site-navigation__links a:focus-visible {
background: rgb(255 255 255 / 6%);
color: var(--text-primary);
}
.site-navigation__links a:focus-visible,
.site-navigation .menu-toggle:focus-visible {
outline: 2px solid var(--accent);
outline-offset: 4px;
border-radius: 4px;
}
.site-navigation .navigation-disabled {
opacity: 0.5;
cursor: default;
}
/* ---------- Logo und Markenname ---------- */
.site-navigation .brand__logo {
display: grid;
place-items: center;
flex-shrink: 0;
width: 60px;
height: 60px;
border-radius: 22px;
overflow: hidden;
background: var(--text-primary);
}
.site-navigation .brand__logo img {
display: block;
width: 60px;
height: 60px;
object-fit: contain;
border-radius: inherit;
}
.site-navigation .brand__logo:focus-visible {
outline: 2px solid var(--accent);
outline-offset: 4px;
}
.site-navigation .brand__text {
display: flex;
flex-direction: column;
gap: 4px;
min-width: 0;
}
.site-navigation .brand__name {
color: var(--text-muted);
font-size: 14px;
font-weight: 200;
line-height: 1.4;
letter-spacing: 0.25em;
}
.site-navigation .brand__label {
color: var(--text-muted2);
font-size: 17px;
font-weight: 300;
line-height: 1.4;
}
/* ---------- Menüknopf und Hintergrund für die kompakte Ansicht ---------- */
.site-navigation .menu-toggle {
display: none;
flex-shrink: 0;
align-items: center;
justify-content: center;
width: 44px;
height: 44px;
padding: 0;
border: 1px solid var(--border);
border-radius: 10px;
background: transparent;
color: var(--text-muted);
font: inherit;
font-size: 26px;
line-height: 1;
cursor: pointer;
}
.site-navigation .menu-toggle:hover {
background: var(--accent-soft);
}
.site-navigation__backdrop {
display: none;
}
/* Der gleiche Grenzwert wird in navigation.js verwendet. */
@media (max-width: 1100px) {
.site-navigation .menu-toggle {
display: inline-flex;
}
.site-navigation__links {
display: none;
position: absolute;
z-index: 2;
top: 100%;
right: 0;
width: min(360px, 100%);
max-height: calc(100vh - var(--navigation-offset, var(--navigation-height)));
max-height: calc(100dvh - var(--navigation-offset, var(--navigation-height)));
overflow-y: auto;
overscroll-behavior: contain;
padding: 16px 24px 24px;
border: 1px solid var(--border);
border-top: 0;
border-radius: 0 0 0 16px;
background: var(--background);
box-shadow: -8px 12px 24px rgb(0 0 0 / 20%);
}
.site-navigation__links ul {
align-items: stretch;
flex-direction: column;
gap: 4px;
}
.site-navigation__links a,
.site-navigation .navigation-disabled {
padding: 12px 16px;
border-radius: 8px;
}
.site-navigation__links a:hover {
background: var(--accent-soft);
}
.site-navigation.is-open .site-navigation__links,
.site-navigation.is-open .site-navigation__backdrop {
display: block;
}
.site-navigation__backdrop {
position: fixed;
z-index: 1;
inset: var(--navigation-offset, var(--navigation-height)) 0 0;
background: rgb(0 0 0 / 45%);
}
}
/* Kleinere Kopfzeile und Schriftgrößen auf schmalen Bildschirmen. */
@media (max-width: 700px) {
:root {
--navigation-height: 81px;
}
.site-navigation__bar {
gap: 12px;
}
.site-navigation .brand {
gap: 12px;
}
.site-navigation .brand__logo {
width: 66px;
height: 62px;
border-radius: 17px;
}
.site-navigation .brand__logo img {
width: 46px;
height: 46px;
}
.site-navigation .brand__name {
font-size: 12px;
}
.site-navigation .brand__label {
font-size: 13px;
}
}

View File

@ -15,6 +15,7 @@
background: var(--background);
}
/* ---------- Grundlayout: Der Hero nutzt die Fensterhöhe unter der Navigation ---------- */
.produktuebersicht,
.produktuebersicht *,
.produktuebersicht::before,
@ -27,8 +28,8 @@
.produktuebersicht {
display: flex;
flex-direction: column;
min-height: 100vh;
min-height: 100svh;
min-height: calc(100vh - var(--navigation-height));
min-height: calc(100svh - var(--navigation-height));
margin: 0;
line-height: 1.6;
}
@ -43,62 +44,12 @@
margin-inline: auto;
}
.produktuebersicht .site-header {
border-top: 3px solid var(--accent);
}
.produktuebersicht .brand {
display: flex;
align-items: center;
gap: 20px;
min-height: 96px;
padding-block: 8px;
}
.produktuebersicht .brand__logo {
display: grid;
place-items: center;
flex-shrink: 0;
width: 90px;
height: 80px;
border-radius: 22px;
background: #fff;
}
.produktuebersicht .brand__logo img {
display: block;
width: 60px;
height: 60px;
object-fit: contain;
}
.produktuebersicht .brand__text {
display: flex;
flex-direction: column;
gap: 4px;
min-width: 0;
}
.produktuebersicht .brand__name {
color: #8a9093;
font-size: 14px;
font-weight: 650;
line-height: 1.4;
letter-spacing: 0.25em;
}
.produktuebersicht .brand__label {
color: var(--text-muted);
font-size: 17px;
font-weight: 600;
line-height: 1.4;
}
.produktuebersicht .produktuebersicht__main {
display: flex;
flex: 1;
}
/* ---------- Hero-Bild und dunkle Verläufe für lesbaren Text ---------- */
.produktuebersicht .hero {
position: relative;
isolation: isolate;
@ -118,8 +69,15 @@
}
.produktuebersicht .hero__image {
object-fit: cover;
object-position: center 42%;
inset: 0 0 auto auto;
width: auto;
max-width: 94%;
height: 94%;
object-fit: contain;
object-position: right top;
mask-image: linear-gradient(to right, transparent, #000 18%),
linear-gradient(to bottom, #000 82%, transparent);
mask-composite: intersect;
}
.produktuebersicht .hero::after {
@ -133,6 +91,7 @@
/* ---------- Überschrift, Einleitung und Ausrichtung im Hero ---------- */
.produktuebersicht .hero__inner {
display: flex;
flex-direction: column;
@ -150,7 +109,7 @@
align-items: center;
gap: 12px;
margin-bottom: 26px;
color: #4597A3;
color: var(--text-muted);
font-size: 11px;
font-weight: 650;
letter-spacing: 0.16em;
@ -166,6 +125,7 @@
}
.produktuebersicht h1 {
color: var(--text-primary);
max-width: 100%;
font-size: clamp(42px, 4.9vw, 88px);
font-weight: 550;
@ -178,7 +138,7 @@
.produktuebersicht h1 span {
display: block;
margin-top: 8px;
color: var(--accent);
color: var(--text-primary);
}
.produktuebersicht .hero__description {
@ -189,6 +149,7 @@
line-height: 1.85;
}
/* ---------- Vorteile und ergänzender Slogan unter dem Haupttext ---------- */
.produktuebersicht .hero__details {
display: grid;
grid-template-columns: minmax(0, 1fr) 250px;
@ -252,6 +213,7 @@
letter-spacing: -0.025em;
}
/* Reservierte Styles für den derzeit nicht eingebundenen Produkt-Footer. */
.produktuebersicht .site-footer__inner {
display: flex;
align-items: center;
@ -282,11 +244,8 @@
background: var(--accent);
}
/* ---------- Anpassungen für Tablet und Mobilgeräte ---------- */
@media (max-width: 1100px) {
.produktuebersicht .hero__image {
object-position: 80% center;
}
.produktuebersicht .hero::after {
background:
linear-gradient(0deg, rgb(var(--background-rgb) / 96%), transparent 42%),
@ -309,32 +268,14 @@
}
@media (max-width: 700px) {
.produktuebersicht .brand {
min-height: 78px;
gap: 12px;
}
.produktuebersicht .brand__logo {
width: 66px;
height: 62px;
border-radius: 17px;
}
.produktuebersicht .brand__logo img {
width: 46px;
height: 46px;
}
.produktuebersicht .brand__name {
font-size: 12px;
}
.produktuebersicht .brand__label {
font-size: 13px;
}
.produktuebersicht .hero__image {
object-position: 85% center;
inset: 0;
width: 100%;
max-width: 100%;
height: 100%;
object-fit: cover;
object-position: 60% bottom;
mask-image: none;
}
.produktuebersicht .hero::after {

View File

@ -1,3 +1,4 @@
/* Gemeinsame Grundwerte für Übersichts- und Kontaktseite; Bereichsstile liegen in eigenen Dateien. */
:root {
color-scheme: dark;
font-family: system-ui, -apple-system, "Segoe UI", Arial, sans-serif;

254
html/kontakt.html 100644
View File

@ -0,0 +1,254 @@
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Kontakt | Ernährungsberatung für Triathleten | tri-hub</title>
<!-- Gemeinsame Schriften und Navigation; kontakt.css ergänzt die Styles dieser Seite. -->
<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=DM+Sans:wght@400;500;600;700&amp;family=Inter:wght@400;500;600;700&amp;family=Sora:wght@400;600;700&amp;display=swap"
rel="stylesheet">
<link rel="stylesheet" href="../css/site.css">
<link rel="stylesheet" href="../css/navigation.css">
<link rel="stylesheet" href="../css/kontakt.css">
<!-- Gemeinsames Menü sowie Formularprüfung und Scroll-Reveal für die Kontaktseite. -->
<script src="../js/navigation.js" defer></script>
<script src="../js/kontakt.js" defer></script>
</head>
<body>
<div class="kontakt-seite">
<!-- Gemeinsame Navigation (Styles: css/navigation.css) -->
<header class="site-navigation">
<div class="site-navigation__bar">
<div class="brand">
<a class="brand__logo" href="../index.html" aria-label="Zurück zur Übersicht">
<img src="../images/trihub-logo.jpg" alt="Trihub – fit und vital ab 50!" width="1024" height="1024">
</a>
<div class="brand__text">
<span class="brand__name">TRI-HUB</span>
<span class="brand__label">Kontakt &amp; Unterstützung</span>
</div>
</div>
<button class="menu-toggle" type="button" aria-controls="navigation-links" aria-expanded="false"
aria-label="Menü öffnen">☰</button>
<nav class="site-navigation__links" id="navigation-links" aria-label="Seitennavigation">
<ul>
<li><a href="#nachricht">Nachricht</a></li>
<li><a href="#kontaktinfos">Kontaktinfos</a></li>
<li><a href="#standort">Standort</a></li>
<li><a href="../index.html">Zurück zur Übersicht</a></li>
</ul>
</nav>
</div>
<div class="site-navigation__backdrop" aria-hidden="true"></div>
</header>
<main id="main-content">
<!-- Hero -->
<section class="hero hero--ribbon">
<div class="container hero__grid">
<div class="reveal">
<span class="kicker">Kontakt</span>
<h1>Von der Frage zur richtigen Strategie</h1>
<p class="hero__text">
Du möchtest mehr über unsere Ernährungsberatung erfahren oder hast konkrete Fragen?
Nimm Kontakt mit uns auf - gemeinsam finden wir heraus, was für dich und deine Ziele am besten passt. Wir freuen uns auf deine Nachricht!
</p>
</div>
<div class="reveal">
<img class="hero__image" src="../images/contact-support-trihub.png"
alt="Gruppe beim Radfahren und Laufen in einer Berglandschaft im Abendlicht" width="1920"
height="1080">
</div>
</div>
</section>
<!-- Nachricht: drei Karten -->
<section id="nachricht" class="section">
<div class="container">
<div class="cards-3">
<div class="card reveal">
<div class="card__icon" aria-hidden="true">💬</div>
<h3>Persönlicher Austausch</h3>
<p>Wir nehmen uns Zeit für deine Fragen und Anliegen – schnell und unkompliziert.</p>
</div>
<div class="card card--warm reveal">
<div class="card__icon" aria-hidden="true">⚡</div>
<h3>Schnelle Antworten</h3>
<p>Deine Fragen beantworten wir zeitnah – per Mail, Telefon oder persönlich.</p>
</div>
<div class="card card--neutral reveal">
<div class="card__icon" aria-hidden="true">🤝</div>
<h3>Echte Beziehung</h3>
<p>Für uns zählt nicht nur die Antwort – sondern der Mensch hinter der Frage.</p>
</div>
</div>
</div>
</section>
<!-- Kontaktinfos: Formular + Kontaktdaten -->
<section id="kontaktinfos" class="section">
<div class="container kontakt-grid">
<div class="reveal">
<span class="kicker">Nachricht</span>
<h2>Schreib uns eine Nachricht</h2>
<!-- kontakt.js prüft die Eingaben und öffnet zum Absenden das E-Mail-Programm. -->
<form id="kontakt-formular" novalidate>
<div class="field">
<label for="kf-name">Name *</label>
<input type="text" id="kf-name" name="name" placeholder="Dein Name" required>
<p class="field__error">Bitte gib deinen Namen an.</p>
</div>
<div class="field">
<label for="kf-email">Email *</label>
<input type="email" id="kf-email" name="email" placeholder="deine@email.de" required>
<p class="field__error">Bitte gib eine gültige E-Mail-Adresse an.</p>
</div>
<div class="field">
<label for="kf-phone">Telefon</label>
<input type="tel" id="kf-phone" name="phone" placeholder="06203/1764877">
</div>
<div class="field">
<label for="kf-type">Anfrage-Typ *</label>
<select id="kf-type" name="anfragetyp" required>
<option value="" selected disabled>Bitte wählen …</option>
<option value="ernaehrungsberatung">Ernährungsberatung anfragen</option>
<option value="kennenlerngespraech">Kennenlerngespräch vereinbaren</option>
<option value="allgemein">Allgemeine Fragen</option>
</select>
<p class="field__error">Bitte wähle einen Anfrage-Typ.</p>
</div>
<div class="field">
<label for="kf-subject">Betreff *</label>
<input type="text" id="kf-subject" name="betreff" placeholder="Worum geht es?" required>
<p class="field__error">Bitte gib einen kurzen Betreff an.</p>
</div>
<div class="field">
<label for="kf-message">Nachricht * (mind. 10 Zeichen)</label>
<textarea id="kf-message" name="nachricht" rows="5" placeholder="Deine Nachricht…" required></textarea>
<p class="field__error">Bitte beschreibe dein Anliegen in wenigen Sätzen.</p>
<p class="field__hint">✓ Nachricht ist lang genug</p>
</div>
<button type="submit" class="btn btn-accent btn-block" disabled>Nachricht senden</button>
<p id="form-status" class="form-status" role="status" aria-live="polite"></p>
</form>
</div>
<div class="reveal">
<h3 style="font-size: 1.25rem; margin-bottom: 24px;">Kontaktinformationen</h3>
<div class="info-stack">
<div class="info-card">
<div class="info-card__icon" aria-hidden="true">✉</div>
<div>
<p class="info-card__label">Allgemeine Fragen &amp; Beratung</p>
<a href="mailto:info@trihub.de">info@trihub.de</a>
</div>
</div>
<div class="info-card">
<div class="info-card__icon" aria-hidden="true">✆</div>
<div>
<p class="info-card__label">Telefon</p>
<a href="tel:+4962031764877">06203 / 1764877</a>
</div>
</div>
<div class="info-card">
<div class="info-card__icon" aria-hidden="true">⚲</div>
<div>
<p class="info-card__label">Adresse</p>
<p>Schloßstr. 11
<span class="info-sub">68535 Edingen-Neckarhausen</span>
</p>
</div>
</div>
<div class="info-card">
<div>
<p class="info-card__label" style="margin-bottom: 12px;">Folge uns auf</p>
<div class="socials">
<a href="https://www.facebook.com/profile.php?id=61569002603339" target="_blank"
rel="noopener noreferrer" aria-label="Tri-hub auf Facebook">FB</a>
<a href="https://www.linkedin.com/company/tri-hub-gmbh" target="_blank"
rel="noopener noreferrer" aria-label="Tri-hub auf LinkedIn">IN</a>
</div>
</div>
</div>
</div>
</div>
</div>
</section>
<!-- Standort -->
<section id="standort" class="section">
<div class="container">
<div class="reveal">
<span class="kicker">Standort</span>
<h2 style="margin-bottom: 32px;">Unser Standort</h2>
<div class="map-frame">
<iframe
title="Karte: Tri-hub, Schloßstr. 11, 68535 Edingen-Neckarhausen"
src="https://www.openstreetmap.org/export/embed.html?bbox=8.5958662%2C49.4613437%2C8.6038662%2C49.4673437&amp;layer=mapnik&amp;marker=49.4643437%2C8.5998662"
loading="lazy"></iframe>
</div>
</div>
</div>
</section>
<!-- Footer -->
<footer class="site-footer">
<div class="container">
<div class="footer-grid">
<div class="footer-brand">
<a class="logo" href="https://tri-hub.de/">TRI-HUB</a>
<p>Triathlon ab 50 – Coaching, Wissen und Begleitung für Best Ager. Hier: der neue Bereich
Ernährungsberatung.</p>
</div>
<div class="footer-col">
<h3>Dieser Bereich</h3>
<ul>
<li><a href="../index.html#beratung">Ablauf der Beratung</a></li>
<li><a href="#kontaktinfos">Kontakt</a></li>
</ul>
</div>
<div class="footer-col">
<h3>Tri-hub</h3>
<ul>
<li><a href="https://tri-hub.de/">Start</a></li>
<li><a href="https://tri-hub.de/coaching-1zu1">1:1 Coaching</a></li>
<li><a href="https://tri-hub.de/camps-schwarzwald">Camps Schwarzwald</a></li>
<li><a href="https://tri-hub.de/webinare-workshops">Webinare &amp; Workshops</a></li>
<li><a href="https://tri-hub.de/wissen">Wissen</a></li>
<li><a href="https://tri-hub.de/kontakt">Kontakt</a></li>
</ul>
</div>
</div>
<div class="footer-legal">
<span>© 2026 Tri-hub GmbH</span>
<span>
<a href="https://tri-hub.de/impressum">Impressum</a> ·
<a href="https://tri-hub.de/datenschutz">Datenschutz</a> ·
<a href="https://tri-hub.de/agb">AGB</a>
</span>
</div>
</div>
</footer>
</main>
</div>
</body>
</html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 187 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 682 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 751 KiB

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: 93 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 131 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

View File

@ -5,93 +5,104 @@
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Ernährungsberatung für Triathleten | tri-hub</title>
<!-- Schriften und Styles: gemeinsame Grundlage zuerst, danach die einzelnen Bereiche. -->
<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=DM+Sans:wght@400;500;600;700&amp;family=Inter:wght@400;500;600;700&amp;family=Sora:wght@400;600;700&amp;display=swap"
rel="stylesheet">
<link rel="stylesheet" href="css/site.css">
<link rel="stylesheet" href="css/navigation.css">
<link rel="stylesheet" href="css/produktuebersicht.css">
<link rel="stylesheet" href="css/leistungen.css">
<link rel="stylesheet" href="css/berater.css">
<link rel="stylesheet" href="css/beratung.css">
<!-- Navigation und dynamische Inhalte werden nach dem Einlesen des HTML initialisiert. -->
<script src="js/navigation.js" defer></script>
<script src="js/leistungen.js" defer></script>
<script src="js/berater.js" defer></script>
<script src="js/beratung.js" defer></script>
</head>
<body>
<!-- Shahnam und Vincent: Gemeinsame Navigation außerhalb der einzelnen Bereiche. -->
<header class="site-navigation">
<div class="site-navigation__bar">
<div class="brand">
<a class="brand__logo" href="https://tri-hub.de/" aria-label="Zur Trihub-Startseite">
<img src="images/trihub-logo.jpg" alt="Trihub – fit und vital ab 50!" width="500" height="500">
</a>
<div class="brand__text">
<span class="brand__name">TRI-HUB</span>
<span class="brand__label">Triathlon als sportliches Zuhause ab 50</span>
</div>
</div>
<!-- Ein gemeinsamer Linksatz für Desktop und Mobil; navigation.js steuert den Menüknopf. -->
<button class="menu-toggle" type="button" aria-controls="navigation-links"
aria-expanded="false" aria-label="Menü öffnen">☰</button>
<nav class="site-navigation__links" id="navigation-links" aria-label="Hauptnavigation">
<ul>
<li><a href="#produktuebersicht">Übersicht</a></li>
<li><a href="#leistungen">Pakete</a></li>
<li><a href="#berater">Berater</a></li>
<li><a href="#beratung">Infos</a></li>
<li><a href="html/kontakt.html">Kontakt</a></li>
</ul>
</nav>
</div>
<div class="site-navigation__backdrop" aria-hidden="true"></div>
</header>
<main id="main-content">
<!-- Shahnam: Produktübersicht -->
<section class="produktuebersicht" id="produktuebersicht" aria-labelledby="hero-title">
<header class="site-header">
<div class="container brand">
<div class="brand__logo">
<img src="images/trihub-logo.jpg" alt="Trihub – fit und vital ab 50!" width="1024" height="1024">
</div>
<div class="brand__text">
<span class="brand__name">TRI-HUB</span>
<span class="brand__label">Triathlon als sportliches Zuhause ab 50</span>
</div>
</div>
</header>
<div class="produktuebersicht__main">
<div class="hero">
<img class="hero__image" src="images/hero-triathlon.webp"
alt="Gruppe beim Radfahren und Laufen in einer Berglandschaft im Abendlicht" width="1920"
height="1080" fetchpriority="high">
<img class="hero__image" src="images/hero-nutrition.png"
alt="KI-generierte Bildidee: Ernährungsberatung für einen älteren Radsportler, mit Müsli, Obst und Wasser am Tisch." width="1536"
height="1024" fetchpriority="high">
<div class="container hero__inner">
<div class="hero__content">
<p class="hero__eyebrow">Der Text wärmt sich noch auf</p>
<p class="hero__eyebrow">Ernährungsberatung für Triathleten ab 50</p>
<h1 id="hero-title">
Überschrift. Weitere Zeile.
<span>Akzentzeile.</span>
Dein Training hat einen Plan.
<span>Deine Ernährung auch?</span>
</h1>
<p class="hero__description">
Hier folgt eine kurze Beschreibung der Ernährungsberatung.
Dieser Platz ist für den finalen Text vorgesehen und wird ergänzt,
sobald die Inhalte vorliegen.
Individuelle Ernährungsberatung für Triathletinnen und Triathleten ab 50. Gemeinsam finden wir heraus, was vor, während und nach dem Training zu dir passt – und wie du es im Alltag umsetzt.
</p>
</div>
<!-- Vorteile der Beratung und ergänzender Slogan. -->
<div class="hero__details">
<ul class="benefits" aria-label="Platzhalter für die Vorteile der Beratung">
<ul class="benefits" aria-label="Vorteile der Ernährungsberatung">
<li>
<span class="benefits__number" aria-hidden="true">01</span>
<p>Erster Vorteil <span>Text folgt</span></p>
<p>Persönlich beraten <span>Deine Ziele und Gewohnheiten im Blick</span></p>
</li>
<li>
<span class="benefits__number" aria-hidden="true">02</span>
<p>Zweiter Vorteil <span>Text folgt</span></p>
<p>Passend zum Training <span>Für Training, Wettkampf und Erholung</span></p>
</li>
<li>
<span class="benefits__number" aria-hidden="true">03</span>
<p>Dritter Vorteil <span>Text folgt</span></p>
<p>Machbar im Alltag <span>Konkrete Schritte, die zu dir passen</span></p>
</li>
</ul>
<div class="hero__caption">
<p class="hero__caption-label">Bildunterschrift folgt</p>
<p class="hero__slogan">Slogan folgt. Weitere Zeile.</p>
<p class="hero__caption-label">Ernährung für deinen Sport</p>
<p class="hero__slogan">Gut versorgt. In deinem Rhythmus.</p>
</div>
</div>
</div>
</div>
</div>
<!-- Vorläufig ausgeblendet, bis die endgültigen Texte vorliegen.
<footer>
<div class="container site-footer__inner">
<p>Abschlusstext folgt. <span>Ergänzender Text folgt.</span></p>
<p class="site-footer__note"><span aria-hidden="true"></span>Kurzhinweis folgt.</p>
</div>
</footer>
-->
</section>
<!-- Vincent: Leistungsübersicht -->
<!-- Titel, Einleitung und Karten werden durch leistungen.js aus json/pakete.json erzeugt. -->
<section id="leistungen" class="pakete">
<div class="pakete__intro">
<h2 class="pakete__titel"></h2>
@ -122,6 +133,7 @@
was dich erwartet.</p>
</div>
<!-- Beratung im Überblick: Inhalte, Ablauf, Zielgruppe und weitere Informationen. -->
<div class="grid-2">
<div class="box reveal">
<h2>Was dich bei der Beratung erwartet</h2>
@ -188,26 +200,26 @@
</div>
</div>
<div class="container" id="kontakt">
<!-- Einstieg zur separaten Kontaktseite mit Formular. -->
<div class="cta-band reveal">
<h2>Bereit für deinen nächsten Schritt?</h2>
<p>Vereinbare ein unverbindliches Kennenlerngespräch – wir schauen gemeinsam, ob die Beratung zu
dir
passt.</p>
<a class="btn btn-primary"
href="mailto:info@trihub.de?subject=Ernaehrungsberatung%20-%20Kennenlerngespraech">Jetzt
unverbindlich anfragen</a>
<a class="btn btn-accent" href="html/kontakt.html">Jetzt unverbindlich anfragen</a>
</div>
</div>
</section>
<!-- Seitenabschluss mit internen Links, Trihub-Angeboten und rechtlichen Informationen. -->
<footer class="site-footer">
<div class="container">
<div class="footer-grid">
<div class="footer-brand">
<a class="logo" href="https://tri-hub.de/">tri<span>-hub</span></a>
<a class="logo" href="https://tri-hub.de/">TRI-HUB</a>
<p>Triathlon ab 50 – Coaching, Wissen und Begleitung für Best Ager. Hier: der neue Bereich
Ernährungsberatung.</p>
</div>
@ -221,7 +233,7 @@
</div>
<div class="footer-col">
<h3>Tri-hub</h3>
<h3>TRI-HUB</h3>
<ul>
<li><a href="https://tri-hub.de/">Start</a></li>
<li><a href="https://tri-hub.de/coaching-1zu1">1:1 Coaching</a></li>

198
js/kontakt.js 100644
View File

@ -0,0 +1,198 @@
// Dennis: Kontaktseite – Scroll-Reveal und Formular-Logik.
// ----- Scroll-Reveal -----
(function () {
const elements = document.querySelectorAll(".kontakt-seite .reveal");
if (!elements.length) return;
const reduceMotion = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
if (reduceMotion || !("IntersectionObserver" in window)) {
elements.forEach(function (el) {
el.classList.add("visible");
});
return;
}
const observer = new IntersectionObserver(
function (entries) {
entries.forEach(function (entry) {
if (entry.isIntersecting) {
entry.target.classList.add("visible");
observer.unobserve(entry.target);
}
});
},
{ threshold: 0.12, rootMargin: "0px 0px -40px 0px" }
);
elements.forEach(function (el) {
observer.observe(el);
});
})();
// ----- Kontaktformular -----
(function () {
const form = document.getElementById("kontakt-formular");
if (!form) return;
const MIN_MESSAGE = 10;
const EMAIL_TARGET = "info@trihub.de";
const fields = {
name: form.querySelector("[name='name']"),
email: form.querySelector("[name='email']"),
phone: form.querySelector("[name='phone']"),
type: form.querySelector("[name='anfragetyp']"),
subject: form.querySelector("[name='betreff']"),
message: form.querySelector("[name='nachricht']")
};
const statusEl = document.getElementById("form-status");
function wrap(el) {
return el.closest(".field");
}
function setFieldState(el, state) {
const box = wrap(el);
if (!box) return;
box.classList.remove("has-error", "is-ok");
el.classList.remove("invalid", "valid");
if (state === "error") {
box.classList.add("has-error");
el.classList.add("invalid");
} else if (state === "ok") {
box.classList.add("is-ok");
el.classList.add("valid");
}
}
function validateName() {
const ok = fields.name.value.trim().length > 0;
setFieldState(fields.name, ok ? null : "error");
return ok;
}
function validateEmail() {
const value = fields.email.value.trim();
const ok = /^[^\s@]+@[^\s@]+\.[^\s@]{2,}$/.test(value);
setFieldState(fields.email, ok ? null : "error");
return ok;
}
function validateType() {
const ok = fields.type.value !== "";
setFieldState(fields.type, ok ? null : "error");
return ok;
}
function validateSubject() {
const ok = fields.subject.value.trim().length > 0;
setFieldState(fields.subject, ok ? null : "error");
return ok;
}
function validateMessage() {
const len = fields.message.value.trim().length;
if (len === 0) {
setFieldState(fields.message, "error");
return false;
}
if (len >= MIN_MESSAGE) {
setFieldState(fields.message, "ok");
return true;
}
// Zu kurz: erst bei Submit als Fehler markieren, live nur grün bei erfüllt.
setFieldState(fields.message, null);
return false;
}
function allValid() {
return (
fields.name.value.trim().length > 0 &&
/^[^\s@]+@[^\s@]+\.[^\s@]{2,}$/.test(fields.email.value.trim()) &&
fields.type.value !== "" &&
fields.subject.value.trim().length > 0 &&
fields.message.value.trim().length >= MIN_MESSAGE
);
}
function updateSubmitState() {
const submit = form.querySelector("[type='submit']");
if (submit) {
submit.disabled = !allValid();
}
}
// Der Button bleibt unabhängig von der Eingabereihenfolge mit allen Feldern synchron.
form.addEventListener("input", updateSubmitState);
form.addEventListener("change", updateSubmitState);
// Live-Feedback: Nachricht wird bei 10+ Zeichen grün markiert.
fields.message.addEventListener("input", function () {
validateMessage();
});
["name", "email", "subject"].forEach(function (key) {
fields[key].addEventListener("blur", function () {
if (fields[key].value.trim() !== "" || form.dataset.touched === "true") {
({ name: validateName, email: validateEmail, subject: validateSubject })[key]();
}
});
});
fields.type.addEventListener("change", validateType);
form.addEventListener("submit", function (event) {
event.preventDefault();
form.dataset.touched = "true";
const okName = validateName();
const okEmail = validateEmail();
const okType = validateType();
const okSubject = validateSubject();
const okMessage = validateMessage();
if (!(okName && okEmail && okType && okSubject && okMessage)) {
showStatus("error", "Bitte überprüfe die markierten Felder und versuche es erneut.");
return;
}
const subject = fields.subject.value.trim();
const typeLabel = fields.type.options[fields.type.selectedIndex].text;
const body = [
"Name: " + fields.name.value.trim(),
"E-Mail: " + fields.email.value.trim(),
"Telefon: " + (fields.phone.value.trim() || "–"),
"Anfrage-Typ: " + typeLabel,
"",
fields.message.value.trim()
].join("\n");
// Statische Seite ohne Backend: E-Mail-Programm wird mit allen Angaben geöffnet.
window.location.href =
"mailto:" + EMAIL_TARGET +
"?subject=" + encodeURIComponent("Ernährungsberatung – " + subject) +
"&body=" + encodeURIComponent(body);
showStatus(
"ok",
"Danke! Dein E-Mail-Programm wurde geöffnet. Sende die Nachricht ab – wir melden uns zeitnah bei dir."
);
form.reset();
Object.values(fields).forEach(function (el) {
setFieldState(el, null);
});
updateSubmitState();
});
function showStatus(kind, text) {
if (!statusEl) return;
statusEl.textContent = kind === "ok" ? "✓ " + text : "✗ " + text;
statusEl.className =
"form-status is-visible " + (kind === "ok" ? "form-status--ok" : "form-status--error");
}
updateSubmitState();
})();

View File

@ -2,11 +2,11 @@
// Markiert das gewählte Paket visuell und stellt die Auswahl bereit,
// damit US-01-03 (Pakete online buchen) später darauf aufbauen kann.
// US-01-02 Leistungsübersicht
document.addEventListener("DOMContentLoaded", async () => {
const section = document.getElementById("leistungen");
if (!section) return;
// Integration: Der Datenpfad muss zum vorhandenen JSON-Ordner passen.
let daten;
try {
const response = await fetch("json/pakete.json");
@ -18,45 +18,65 @@ document.addEventListener("DOMContentLoaded", async () => {
return;
}
// Elemente aus dem HTML holen
const titel = section.querySelector(".pakete__titel");
const text = section.querySelector(".pakete__text");
const grid = section.querySelector(".pakete__grid");
// Intro mit Daten aus JSON füllen
titel.textContent = daten.intro.titel;
text.textContent = daten.intro.text;
// Vorhandenen Inhalt entfernen
grid.innerHTML = "";
// Für jedes Paket eine Karte erstellen
daten.pakete.forEach(paket => {
// Platzhalter-Pfade für die drei generierten Bilder
const bilderMap = {
"STARTER": "images/paket-starter.jpg",
"STANDARD": "images/paket-standard.jpg",
"PREMIUM": "images/paket-premium.jpg"
};
// Artikel erstellen
daten.pakete.forEach(paket => {
const karte = document.createElement("article");
karte.classList.add("paket-karte");
// Empfohlenes Paket bekommt zusätzliche Klasse
if (paket.empfohlen) {
karte.classList.add("paket-karte--empfohlen");
}
// Badge nur erstellen, wenn eines vorhanden ist
if (paket.badge) {
const badge = document.createElement("p");
badge.classList.add("paket-karte__badge");
badge.textContent = paket.badge;
karte.appendChild(badge);
}
// 1. Bild oben (Platzhalter)
const imgWrapper = document.createElement("div");
imgWrapper.classList.add("paket-karte__image-wrapper");
const img = document.createElement("img");
img.src = bilderMap[paket.name] || "images/paket-placeholder.jpg";
img.alt = `Vorschau ${paket.name}`;
img.classList.add("paket-karte__image");
imgWrapper.appendChild(img);
karte.appendChild(imgWrapper);
// Content-Container für die Texte & Abstände
const body = document.createElement("div");
body.classList.add("paket-karte__body");
// 2. Header: Paketname + Badge nebeneinander
const header = document.createElement("div");
header.classList.add("paket-karte__header");
// Name
const name = document.createElement("h3");
name.classList.add("paket-karte__name");
name.textContent = paket.name;
karte.appendChild(name);
header.appendChild(name);
// Preis
if (paket.badge) {
const badge = document.createElement("span");
badge.classList.add("paket-karte__badge");
badge.textContent = paket.badge;
header.appendChild(badge);
}
body.appendChild(header);
// 3. Preis
const preis = document.createElement("p");
preis.classList.add("paket-karte__preis");
@ -70,15 +90,15 @@ document.addEventListener("DOMContentLoaded", async () => {
preis.appendChild(betrag);
preis.appendChild(zeitraum);
karte.appendChild(preis);
body.appendChild(preis);
// Beschreibung
// 4. Beschreibung
const beschreibung = document.createElement("p");
beschreibung.classList.add("paket-karte__beschreibung");
beschreibung.textContent = paket.beschreibung;
karte.appendChild(beschreibung);
body.appendChild(beschreibung);
// Leistungen
// 5. Leistungen
const liste = document.createElement("ul");
liste.classList.add("paket-karte__leistungen");
@ -88,9 +108,9 @@ document.addEventListener("DOMContentLoaded", async () => {
liste.appendChild(punkt);
});
karte.appendChild(liste);
body.appendChild(liste);
// Button
// 6. Button
const button = document.createElement("button");
button.classList.add("paket-karte__cta");
button.type = "button";
@ -98,7 +118,6 @@ document.addEventListener("DOMContentLoaded", async () => {
button.dataset.paket = paket.dataPaket;
button.setAttribute("aria-pressed", "false");
// Die Paketauswahl aus der ersten Version auch nach dem JSON-Rendering erhalten.
button.addEventListener("click", () => {
grid.querySelectorAll(".paket-karte").forEach(andereKarte => {
andereKarte.classList.remove("paket-karte--ausgewaehlt");
@ -111,9 +130,9 @@ document.addEventListener("DOMContentLoaded", async () => {
}));
});
karte.appendChild(button);
body.appendChild(button);
karte.appendChild(body);
// Karte ins Grid einfügen
grid.appendChild(karte);
});
});

76
js/navigation.js 100644
View File

@ -0,0 +1,76 @@
(() => {
const navigation = document.querySelector('.site-navigation');
if (!navigation) return;
const button = navigation.querySelector('.menu-toggle');
const links = navigation.querySelector('#navigation-links');
const backdrop = navigation.querySelector('.site-navigation__backdrop');
if (!button || !links || !backdrop) return;
// Muss zum Grenzwert in navigation.css passen.
const mobileView = window.matchMedia('(max-width: 1100px)');
function setMenuOpen(isOpen) {
isOpen = isOpen && mobileView.matches;
navigation.classList.toggle('is-open', isOpen);
button.setAttribute('aria-expanded', String(isOpen));
button.setAttribute('aria-label', isOpen ? 'Menü schließen' : 'Menü öffnen');
button.textContent = isOpen ? '✕' : '☰';
}
button.addEventListener('click', () => {
setMenuOpen(button.getAttribute('aria-expanded') !== 'true');
});
// Nach einem Sprung bleibt der Tastaturfokus im gewählten Bereich.
links.addEventListener('click', (event) => {
const link = event.target.closest('a');
if (!link || !mobileView.matches) return;
setMenuOpen(false);
// Nur Anker-Sprünge innerhalb der Seite; Links zu anderen Seiten übernimmt der Browser.
const href = link.getAttribute('href');
if (!href || !href.startsWith('#')) return;
const target = document.querySelector(href);
if (target) {
const hadTabindex = target.hasAttribute('tabindex');
if (!hadTabindex) target.setAttribute('tabindex', '-1');
requestAnimationFrame(() => target.focus({ preventScroll: true }));
if (!hadTabindex) {
target.addEventListener('blur', () => target.removeAttribute('tabindex'), { once: true });
}
}
});
document.addEventListener('keydown', (event) => {
if (event.key === 'Escape' && button.getAttribute('aria-expanded') === 'true') {
setMenuOpen(false);
button.focus();
}
});
backdrop.addEventListener('click', () => {
setMenuOpen(false);
button.focus();
});
// Das Menü bleibt beim Weitergehen mit Tab nicht über dem Seiteninhalt liegen.
navigation.addEventListener('focusout', (event) => {
if (!navigation.contains(event.relatedTarget)) setMenuOpen(false);
});
mobileView.addEventListener('change', () => {
const focusedElement = document.activeElement;
setMenuOpen(false);
if (mobileView.matches && links.contains(focusedElement)) button.focus();
if (!mobileView.matches && focusedElement === button) links.querySelector('a')?.focus();
});
// Berücksichtigt auch umgebrochene Texte und vergrößerte Schrift.
const headerObserver = new ResizeObserver(() => {
document.documentElement.style.setProperty(
'--navigation-offset', `${navigation.getBoundingClientRect().height}px`
);
});
headerObserver.observe(navigation);
setMenuOpen(false);
})();

View File

@ -5,7 +5,7 @@
},
"pakete": [
{
"name": "Starter",
"name": "STARTER",
"preis": 79,
"zeitraum": "Monat",
"beschreibung": "Für den Einstieg in eine bewusste Ernährung neben dem Training.",
@ -20,7 +20,7 @@
"dataPaket": "Starter"
},
{
"name": "STANDART",
"name": "STANDARD",
"preis": 149,
"zeitraum": "Monat",
"beschreibung": "Laufende Begleitung für alle, die regelmäßig für Wettkämpfe trainieren.",
@ -33,15 +33,15 @@
],
"badge": "Beliebteste Wahl",
"empfohlen": true,
"dataPaket": "Ausdauer"
"dataPaket": "STANDARD"
},
{
"name": "Rundum Sorglos Paket",
"name": "PREMIUM",
"preis": 249,
"zeitraum": "Monat",
"beschreibung": "Volle Betreuung für ambitionierte Athletinnen und Athleten.",
"leistungen": [
"Alles aus Ausdauer",
"Alles aus STANDARD",
"Wöchentliche Beratungsgespräche",
"Individuelle Supplement-Empfehlung",
"Betreuung am Wettkampftag",
@ -49,7 +49,7 @@
],
"badge": null,
"empfohlen": false,
"dataPaket": "Wettkampf"
"dataPaket": "PREMIUM"
}
]
}