diff --git a/css/berater.css b/css/berater.css index c83959a..f18cc00 100644 --- a/css/berater.css +++ b/css/berater.css @@ -1,199 +1,199 @@ -/* Ahmad: Styles bleiben in .berater-section; Karten passen auch auf Mobilgeraete. */ -/* ========================================================== - Globale Design-Variablen - Angepasst an die Design-DNA von Tri-hub - ========================================================== */ -.berater-section { - /* Farben und variabel-namen wurden standarttisiert siehe Kommentare */ - --background: ##0714198a; /* früher "--bg-dark-1" */ - --background-rgb: 7 20 25; /* gleiche Basisfarbe wie --background, - aber ohne fixes Alpha – für Gradient-Stops - wird benutzt um Bildern einen gradient zu verpassen */ - --background-panel: #152027; /* früher "--bg-dark-2" */ - --accent: #4597A3; /* früher "accent-teal" */ - --text-primary: #ffffff; /* früher "text-white" */ - --text-muted: #94A3B8; - - --font-display: 'Inter', system-ui, sans-serif; - --font-body: 'Inter', system-ui, sans-serif; -} - -.berater-section { - margin: 0; - /* Padding vereinheitlicht, um dem Pakete-Bereich zu entsprechen */ - padding: 5rem 1.5rem; - font-family: var(--font-body); - background-color: var(--background); - color: var(--text-white); - display: flex; - flex-direction: column; - align-items: center; - /* Gesamten Inhalt vertikal zentrieren */ -} - -/* ========================================= - Styling für Header (Titel und Untertitel) passend zu den Paketen - ========================================= */ -.berater-section .section-header { - text-align: center; - max-width: 42rem; - margin: 0 auto 3rem; -} - -.berater-section .section-title { - font-family: var(--font-display); - font-weight: 700; - font-size: clamp(1.8rem, 3vw, 2.4rem); - color: var(--text-white); - margin: 0 0 0.75rem; -} - -.berater-section .section-subtitle { - color: var(--text-muted); - line-height: 1.6; - margin: 0; -} - -/* ========================================= - Styling für den Container und die Berater-Karten - ========================================= */ -.berater-section .berater-container { - display: flex; - gap: 30px; - width: 100%; - max-width: 1000px; - flex-wrap: wrap; - justify-content: center; -} - -.berater-section .berater-card { - background-color: var(--background-panel); - /* Leichte Umrandung hinzugefügt, ähnlich wie bei den Pakete-Karten */ - border: 1px solid rgba(248, 250, 252, 0.08); - /* Border-Radius angepasst, passend zu den Paketen */ - border-radius: 14px; - padding: 24px; - width: min(380px, 100%); - box-sizing: border-box; - display: flex; - flex-direction: column; -} - -/* ---------- Profilbild, Name und Rolle innerhalb einer Karte ---------- */ -.berater-section .berater-image-wrapper { - background-color: var(--background); - border-radius: 10px; - padding: 10px; - margin-bottom: 24px; -} - -.berater-section .berater-image-wrapper img { - width: 100%; - height: 350px; - object-fit: cover; - border-radius: 8px; - display: block; -} - -.berater-section .berater-name { - margin: 0 0 8px 0; - font-family: var(--font-display); - font-size: 1.4rem; - /* Schriftgröße des Namens harmonisiert */ - font-weight: 600; - color: var(--text-white); -} - -.berater-section .berater-rolle { - margin: 0 0 24px 0; - font-family: var(--font-body); - font-size: 14px; - color: var(--accent); - text-transform: uppercase; - letter-spacing: 1.5px; - 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; - border-radius: 16px; - /* Überschüssiges Padding entfernt */ - padding: 0; - flex-grow: 1; -} - -.berater-section .bio-title { - margin: 0 0 16px 0; - font-size: 16px; - font-weight: 600; - color: var(--text-primary); -} - -.berater-section .bio-text { - margin: 0; - font-size: 15px; - line-height: 1.6; - color: var(--text-muted); -} - -/* ========================================= - Styling für dynamische Listen (z. B. Zertifikate) - ========================================= */ -.berater-section .berater-liste { - margin: 8px 0 0 0; - padding-left: 1.4rem; - color: var(--text-muted); - font-size: 14px; - line-height: 1.6; - list-style: none; - /* Standard-Listenpunkte entfernt für benutzerdefiniertes Styling */ -} - -.berater-section .berater-liste li { - position: relative; - margin-bottom: 0.7rem; - color: var(--text-primary); -} - -/* Grünes quadratisches Aufzählungszeichen hinzugefügt, exakt wie im Pakete-Bereich */ -.berater-section .berater-liste li::before { - content: ""; - position: absolute; - left: -1.4rem; - top: 0.4em; - width: 8px; - height: 8px; - border-radius: 2px; - background: var(--accent); -} - -/* ========================================= - Styling für dynamische Links (Buttons) - ========================================= */ -.berater-section .berater-links { - margin-top: 20px; - display: flex; - flex-wrap: wrap; - gap: 10px; -} - -.berater-section .berater-link-btn { - display: inline-block; - font-family: var(--font-body); - font-weight: 600; - font-size: 0.95rem; - color: var(--background); - background: var(--accent); - border: none; - border-radius: 8px; - padding: 0.85rem 1rem; - cursor: pointer; - text-decoration: none; - transition: filter 0.15s ease; -} - -.berater-section .berater-link-btn:hover, -.berater-section .berater-link-btn:focus-visible { - filter: brightness(1.1); -} +/* Ahmad: Styles bleiben in .berater-section; Karten passen auch auf Mobilgeraete. */ +/* ========================================================== + Globale Design-Variablen - Angepasst an die Design-DNA von Tri-hub + ========================================================== */ +.berater-section { + /* Farben und variabel-namen wurden standarttisiert siehe Kommentare */ + --background: ##0714198a; /* früher "--bg-dark-1" */ + --background-rgb: 7 20 25; /* gleiche Basisfarbe wie --background, + aber ohne fixes Alpha – für Gradient-Stops + wird benutzt um Bildern einen gradient zu verpassen */ + --background-panel: #152027; /* früher "--bg-dark-2" */ + --accent: #4597A3; /* früher "accent-teal" */ + --text-primary: #ffffff; /* früher "text-white" */ + --text-muted: #94A3B8; + + --font-display: 'Inter', system-ui, sans-serif; + --font-body: 'Inter', system-ui, sans-serif; +} + +.berater-section { + margin: 0; + /* Padding vereinheitlicht, um dem Pakete-Bereich zu entsprechen */ + padding: 5rem 1.5rem; + font-family: var(--font-body); + background-color: var(--background); + color: var(--text-white); + display: flex; + flex-direction: column; + align-items: center; + /* Gesamten Inhalt vertikal zentrieren */ +} + +/* ========================================= + Styling für Header (Titel und Untertitel) passend zu den Paketen + ========================================= */ +.berater-section .section-header { + text-align: center; + max-width: 42rem; + margin: 0 auto 3rem; +} + +.berater-section .section-title { + font-family: var(--font-display); + font-weight: 700; + font-size: clamp(1.8rem, 3vw, 2.4rem); + color: var(--text-white); + margin: 0 0 0.75rem; +} + +.berater-section .section-subtitle { + color: var(--text-muted); + line-height: 1.6; + margin: 0; +} + +/* ========================================= + Styling für den Container und die Berater-Karten + ========================================= */ +.berater-section .berater-container { + display: flex; + gap: 30px; + width: 100%; + max-width: 1000px; + flex-wrap: wrap; + justify-content: center; +} + +.berater-section .berater-card { + background-color: var(--background-panel); + /* Leichte Umrandung hinzugefügt, ähnlich wie bei den Pakete-Karten */ + border: 1px solid rgba(248, 250, 252, 0.08); + /* Border-Radius angepasst, passend zu den Paketen */ + border-radius: 14px; + padding: 24px; + width: min(380px, 100%); + box-sizing: border-box; + display: flex; + flex-direction: column; +} + +/* ---------- Profilbild, Name und Rolle innerhalb einer Karte ---------- */ +.berater-section .berater-image-wrapper { + background-color: var(--background); + border-radius: 10px; + padding: 10px; + margin-bottom: 24px; +} + +.berater-section .berater-image-wrapper img { + width: 100%; + height: 350px; + object-fit: cover; + border-radius: 8px; + display: block; +} + +.berater-section .berater-name { + margin: 0 0 8px 0; + font-family: var(--font-display); + font-size: 1.4rem; + /* Schriftgröße des Namens harmonisiert */ + font-weight: 600; + color: var(--text-white); +} + +.berater-section .berater-rolle { + margin: 0 0 24px 0; + font-family: var(--font-body); + font-size: 14px; + color: var(--accent); + text-transform: uppercase; + letter-spacing: 1.5px; + 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; + border-radius: 16px; + /* Überschüssiges Padding entfernt */ + padding: 0; + flex-grow: 1; +} + +.berater-section .bio-title { + margin: 0 0 16px 0; + font-size: 16px; + font-weight: 600; + color: var(--text-primary); +} + +.berater-section .bio-text { + margin: 0; + font-size: 15px; + line-height: 1.6; + color: var(--text-muted); +} + +/* ========================================= + Styling für dynamische Listen (z. B. Zertifikate) + ========================================= */ +.berater-section .berater-liste { + margin: 8px 0 0 0; + padding-left: 1.4rem; + color: var(--text-muted); + font-size: 14px; + line-height: 1.6; + list-style: none; + /* Standard-Listenpunkte entfernt für benutzerdefiniertes Styling */ +} + +.berater-section .berater-liste li { + position: relative; + margin-bottom: 0.7rem; + color: var(--text-primary); +} + +/* Grünes quadratisches Aufzählungszeichen hinzugefügt, exakt wie im Pakete-Bereich */ +.berater-section .berater-liste li::before { + content: ""; + position: absolute; + left: -1.4rem; + top: 0.4em; + width: 8px; + height: 8px; + border-radius: 2px; + background: var(--accent); +} + +/* ========================================= + Styling für dynamische Links (Buttons) + ========================================= */ +.berater-section .berater-links { + margin-top: 20px; + display: flex; + flex-wrap: wrap; + gap: 10px; +} + +.berater-section .berater-link-btn { + display: inline-block; + font-family: var(--font-body); + font-weight: 600; + font-size: 0.95rem; + color: var(--background); + background: var(--accent); + border: none; + border-radius: 8px; + padding: 0.85rem 1rem; + cursor: pointer; + text-decoration: none; + transition: filter 0.15s ease; +} + +.berater-section .berater-link-btn:hover, +.berater-section .berater-link-btn:focus-visible { + filter: brightness(1.1); +} diff --git a/css/kontakt.css b/css/kontakt.css index af6e4d3..3f1bb0f 100644 --- a/css/kontakt.css +++ b/css/kontakt.css @@ -1,644 +1,644 @@ -/* 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; - } -} +/* 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; + } +} diff --git a/css/terminbuchung.css b/css/terminbuchung.css index 44cdc9f..739f884 100644 --- a/css/terminbuchung.css +++ b/css/terminbuchung.css @@ -1,933 +1,1148 @@ -/* Online-Terminbuchung – zwei Bereiche: - 1. .tb-overlay & Co.: Kalender-Pop-up auf der Startseite (index.html). - 2. .terminbuchung-seite: Buchungsformular-Seite (html/terminbuchung.html), - Tokens und Formular-Styles entsprechen css/kontakt.css. */ - -/* ========================================================= - 1. Terminbuchungs-Pop-up (Kalender) auf der Startseite - ========================================================= */ - -.tb-overlay { - --background-panel: #152027; - --text-primary: #ffffff; - --text-muted: #9ba7aa; - --accent: #4597A3; - --border: rgba(234, 8, 8, 0.1); - --font-display: "Sora", sans-serif; - --font-body: "DM Sans", Arial, Helvetica, sans-serif; - --radius: 1.1rem; - - position: fixed; - inset: 0; - z-index: 1000; - display: flex; - align-items: center; - justify-content: center; - padding: 24px; - background: rgb(7 20 25 / 72%); - backdrop-filter: blur(6px); - -webkit-backdrop-filter: blur(6px); -} - -.tb-overlay[hidden] { - display: none; -} - -.tb-dialog { - position: relative; - width: min(560px, 100%); - max-height: calc(100vh - 48px); - overflow-y: auto; - background-color: var(--background-panel); - border: 1px solid rgba(255, 255, 255, 0.08); - border-radius: calc(var(--radius) + 0.4rem); - box-shadow: 0 30px 90px rgb(0 0 0 / 45%); - padding: 28px; - color: var(--text-primary); - font-family: var(--font-body); - line-height: 1.6; -} - -.tb-close { - position: absolute; - top: 14px; - right: 14px; - display: grid; - place-items: center; - width: 36px; - height: 36px; - border: 1px solid var(--border); - border-radius: 10px; - background: rgb(255 255 255 / 4%); - color: var(--text-primary); - font-size: 1.1rem; - line-height: 1; - cursor: pointer; - transition: border-color 0.15s ease, background-color 0.15s ease; -} - -.tb-close:hover, -.tb-close:focus-visible { - border-color: var(--accent); - background: rgb(69 151 163 / 14%); -} - -.tb-kicker { - display: inline-flex; - align-items: center; - gap: 10px; - font-size: 0.72rem; - font-weight: 600; - letter-spacing: 0.18em; - text-transform: uppercase; - color: var(--accent); - margin-bottom: 10px; -} - -.tb-kicker::before { - content: ""; - width: 24px; - height: 2px; - border-radius: 2px; - background: var(--accent); -} - -.tb-title { - margin: 0 0 6px; - font-family: var(--font-display); - font-size: 1.5rem; - font-weight: 600; - letter-spacing: -0.02em; -} - -.tb-subtitle { - margin: 0 0 20px; - font-size: 0.9rem; - color: var(--text-muted); -} - -/* ---------- Trainer-Auswahl ---------- */ - -.tb-trainer-label { - display: block; - font-size: 0.85rem; - font-weight: 600; - margin-bottom: 10px; -} - -.tb-trainer { - display: flex; - flex-wrap: wrap; - gap: 10px; - margin-bottom: 22px; -} - -.tb-trainer-btn { - padding: 9px 16px; - border-radius: 999px; - border: 1px solid rgba(255, 255, 255, 0.14); - background: rgb(255 255 255 / 4%); - color: var(--text-primary); - font-family: var(--font-body); - font-size: 0.9rem; - font-weight: 600; - cursor: pointer; - transition: border-color 0.15s ease, background-color 0.15s ease; -} - -.tb-trainer-btn:hover { - border-color: var(--accent); -} - -.tb-trainer-btn.is-active { - border-color: var(--accent); - background: rgb(69 151 163 / 18%); -} - -/* ---------- Kalender ---------- */ - -.tb-cal-head { - display: flex; - align-items: center; - justify-content: space-between; - gap: 12px; - margin-bottom: 12px; -} - -.tb-cal-month { - font-family: var(--font-display); - font-size: 1rem; - font-weight: 600; -} - -.tb-cal-nav { - display: grid; - place-items: center; - width: 34px; - height: 34px; - border-radius: 9px; - border: 1px solid rgba(255, 255, 255, 0.14); - background: rgb(255 255 255 / 4%); - color: var(--text-primary); - font-size: 1.05rem; - cursor: pointer; - transition: border-color 0.15s ease, background-color 0.15s ease; -} - -.tb-cal-nav:hover:not(:disabled) { - border-color: var(--accent); - background: rgb(69 151 163 / 14%); -} - -.tb-cal-nav:disabled { - opacity: 0.35; - cursor: not-allowed; -} - -.tb-cal-weekdays, -.tb-cal-grid { - display: grid; - grid-template-columns: repeat(7, minmax(0, 1fr)); - gap: 6px; -} - -.tb-cal-weekdays span { - text-align: center; - font-size: 0.72rem; - font-weight: 600; - letter-spacing: 0.08em; - text-transform: uppercase; - color: var(--text-muted); - padding-bottom: 4px; -} - -.tb-day { - position: relative; - display: grid; - place-items: center; - min-height: 44px; - border-radius: 10px; - border: 1px solid transparent; - background: transparent; - font-family: var(--font-body); - font-size: 0.88rem; - color: var(--text-primary); - cursor: pointer; - transition: background-color 0.15s ease, border-color 0.15s ease, color 0.15s ease; -} - -/* Leere Zellen vor dem ersten bzw. nach dem letzten Tag des Monats */ -.tb-day--leer { - visibility: hidden; - pointer-events: none; -} - -/* Verfügbarkeitskategorien */ -.tb-day--verfuegbar { - background: rgb(34 197 94 / 12%); - border-color: rgb(34 197 94 / 40%); -} - -.tb-day--verfuegbar:hover, -.tb-day--verfuegbar:focus-visible { - background: rgb(34 197 94 / 22%); - border-color: rgb(34 197 94 / 70%); -} - -.tb-day--nicht { - color: rgb(255 255 255 / 26%); - background: rgb(255 255 255 / 3%); - cursor: default; -} - -/* Heute: dezent markiert, ohne die Kategorie zu verdecken */ -.tb-day--heute { - font-weight: 700; - box-shadow: inset 0 -2px 0 0 var(--accent); -} - -/* Ausgewählter Tag */ -.tb-day--ausgewaehlt { - background: var(--accent); - border-color: var(--accent); - color: #03242b; - font-weight: 700; -} - -/* ---------- Legende ---------- */ - -.tb-legend { - display: flex; - flex-wrap: wrap; - gap: 18px; - list-style: none; - margin: 14px 0 0; - padding: 0; - font-size: 0.82rem; - color: var(--text-muted); -} - -.tb-legend li { - display: inline-flex; - align-items: center; - gap: 7px; -} - -.tb-dot { - width: 10px; - height: 10px; - border-radius: 50%; -} - -.tb-dot--verfuegbar { - background: #22c55e; -} - -.tb-dot--nicht { - background: rgb(255 255 255 / 22%); -} - -/* ---------- Uhrzeiten des gewählten Tages ---------- */ - -.tb-times { - margin-top: 20px; - padding-top: 18px; - border-top: 1px solid var(--border); -} - -.tb-times-label { - margin: 0 0 12px; - font-size: 0.9rem; - color: var(--text-muted); -} - -.tb-times-label strong { - color: var(--text-primary); -} - -.tb-time-chips { - display: flex; - flex-wrap: wrap; - gap: 10px; -} - -.tb-time-chip { - padding: 8px 16px; - border-radius: 999px; - border: 1px solid rgba(255, 255, 255, 0.16); - background: rgb(255 255 255 / 4%); - color: var(--text-primary); - font-family: var(--font-body); - font-size: 0.9rem; - font-weight: 600; - cursor: pointer; - transition: border-color 0.15s ease, background-color 0.15s ease, color 0.15s ease; -} - -.tb-time-chip:hover { - border-color: var(--accent); -} - -.tb-time-chip.is-active { - background: var(--accent); - border-color: var(--accent); - color: #03242b; -} - -/* ---------- Buchen-Button ---------- */ - -.tb-book { - display: block; - width: 100%; - margin-top: 22px; - padding: 0.85rem 1.5rem; - border-radius: 999px; - border: none; - background: var(--accent); - color: #03242b; - font-family: var(--font-body); - font-size: 0.98rem; - font-weight: 600; - cursor: pointer; - transition: transform 0.18s ease, box-shadow 0.18s ease, opacity 0.18s ease; -} - -.tb-book:hover:not(:disabled) { - transform: translateY(-1px); - box-shadow: 0 10px 28px rgb(0 179 194 / 28%); -} - -.tb-book:disabled { - opacity: 0.5; - cursor: not-allowed; -} - -.tb-hinweis { - margin: 12px 0 0; - font-size: 0.78rem; - color: var(--text-muted); -} - -/* ========================================================= - 2. Buchungsformular-Seite (html/terminbuchung.html) - ========================================================= */ - -.terminbuchung-seite { - /* Farben – Tokens von tri-hub.de (wie css/kontakt.css) */ - --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 ---------- */ - -.terminbuchung-seite, -.terminbuchung-seite *, -.terminbuchung-seite::before, -.terminbuchung-seite *::before, -.terminbuchung-seite::after, -.terminbuchung-seite *::after { - box-sizing: border-box; -} - -.terminbuchung-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 */ -.terminbuchung-seite::before { - content: ""; - position: absolute; - inset: 0; - z-index: -1; - background: - radial-gradient(900px 520px at 78% -10%, rgb(0 179 194 / 13%), transparent 65%), - radial-gradient(700px 480px at -10% 30%, rgb(35 137 148 / 10%), transparent 60%), - radial-gradient(600px 420px at 90% 95%, rgb(247 194 48 / 5%), transparent 60%); - pointer-events: none; -} - -.terminbuchung-seite h1, -.terminbuchung-seite h2, -.terminbuchung-seite h3 { - margin-top: 0; - font-family: var(--font-display); - color: var(--text-primary); - line-height: 1.2; - letter-spacing: -0.01em; -} - -.terminbuchung-seite p { - margin: 0 0 1em; -} - -.terminbuchung-seite .container { - width: min(100% - 48px, var(--container)); - margin-inline: auto; -} - -/* ---------- Typo-Helfer ---------- */ - -.terminbuchung-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; -} - -.terminbuchung-seite .kicker::before { - content: ""; - width: 28px; - height: 2px; - border-radius: 2px; - background: var(--accent); -} - -.terminbuchung-seite .lead { - font-size: 1.15rem; - color: var(--text-muted); - max-width: 660px; -} - -/* Die Navigationsleiste oben wird von css/navigation.css gestylt (.site-navigation). */ - -/* ---------- Kopfzeile / Hero ---------- */ - -.terminbuchung-seite .hero { - padding: 72px 0 56px; -} - -.terminbuchung-seite .hero h1 { - font-size: clamp(2.2rem, 5vw, 3.2rem); - font-weight: 500; - letter-spacing: -0.04em; - margin-bottom: 16px; -} - -.terminbuchung-seite .hero__text { - font-size: 1.1rem; - color: var(--text-muted); - max-width: 640px; -} - -/* ---------- Sektionen ---------- */ - -.terminbuchung-seite .section { - padding: 56px 0 88px; -} - -.terminbuchung-seite .section + .section { - border-top: 1px solid var(--border); -} - -.terminbuchung-seite .section h2 { - font-size: clamp(1.7rem, 3.5vw, 2.4rem); - margin-bottom: 16px; -} - -/* ---------- Termin-Zusammenfassung ---------- */ - -.tb-summary { - display: grid; - grid-template-columns: repeat(3, minmax(0, 1fr)); - gap: 20px; - margin-top: 32px; -} - -.tb-summary__item { - background-color: var(--background-panel); - border: 1px solid var(--border); - border-radius: calc(var(--radius) + 0.4rem); - padding: 22px; -} - -.tb-summary__label { - display: block; - font-size: 0.7rem; - letter-spacing: 0.2em; - text-transform: uppercase; - color: var(--text-muted); - margin-bottom: 8px; -} - -.tb-summary__value { - font-family: var(--font-display); - font-size: 1.05rem; - font-weight: 600; - color: var(--text-primary); - margin: 0; -} - -/* Hinweis, wenn die Seite ohne Termin-Parameter geöffnet wurde */ -.tb-summary--fehlend { - grid-template-columns: minmax(0, 1fr); -} - -.tb-summary--fehlend .tb-summary__item { - border-color: rgb(247 194 48 / 45%); - background: rgb(247 194 48 / 6%); -} - -.tb-summary--fehlend a { - color: var(--accent); -} - -/* ---------- Formular ---------- */ - -.terminbuchung-seite form { - margin-top: 32px; - display: grid; - gap: 20px; -} - -.terminbuchung-seite .field label { - display: block; - font-size: 0.9rem; - font-weight: 500; - color: var(--text-primary); - margin-bottom: 8px; -} - -.terminbuchung-seite .field input, -.terminbuchung-seite .field select, -.terminbuchung-seite .field textarea { - width: 100%; - padding: 12px 16px; - border-radius: 10px; - border: 1px solid var(--border); - background: rgb(255 255 255 / 4%); - color: var(--text-primary); - font-family: var(--font-body); - font-size: 0.95rem; - transition: border-color 0.15s ease, background-color 0.15s ease; -} - -.terminbuchung-seite .field input::placeholder, -.terminbuchung-seite .field textarea::placeholder { - color: rgb(255 255 255 / 40%); -} - -.terminbuchung-seite .field input:focus, -.terminbuchung-seite .field select:focus, -.terminbuchung-seite .field textarea:focus { - outline: none; - border-color: var(--accent); - background: rgb(255 255 255 / 6%); -} - -.terminbuchung-seite .field select option { - background: #0b1a20; - color: var(--text-primary); -} - -.terminbuchung-seite .field textarea { - min-height: 110px; - resize: vertical; -} - -/* terminbuchung-form.js setzt die Zustandsklassen für ungültige Felder und Rückmeldungen. */ -.terminbuchung-seite .field input.invalid, -.terminbuchung-seite .field select.invalid, -.terminbuchung-seite .field textarea.invalid { - border-color: #ef4444; -} - -.terminbuchung-seite .field input.valid, -.terminbuchung-seite .field textarea.valid { - border-color: #22c55e; -} - -.terminbuchung-seite .field__error { - display: none; - margin-top: 6px; - font-size: 0.8rem; - color: #f87171; -} - -.terminbuchung-seite .field__hint { - margin-top: 6px; - font-size: 0.8rem; - color: var(--text-muted); -} - -.terminbuchung-seite .field.has-error .field__error { - display: block; -} - -/* ---------- Anamnese-Tags (Allergien & Unverträglichkeiten) ---------- */ - -.tb-tag-row { - display: flex; - gap: 10px; -} - -.tb-tag-row input { - flex: 1; -} - -.tb-tag-add { - flex-shrink: 0; - padding: 0 18px; - border-radius: 10px; - border: 1px solid var(--accent); - background: rgb(69 151 163 / 14%); - color: var(--text-primary); - font-family: var(--font-body); - font-size: 0.9rem; - font-weight: 600; - cursor: pointer; - transition: background-color 0.15s ease, border-color 0.15s ease; -} - -.tb-tag-add:hover { - background: rgb(69 151 163 / 28%); -} - -.tb-tags { - display: flex; - flex-wrap: wrap; - gap: 8px; - list-style: none; - margin: 10px 0 0; - padding: 0; -} - -.tb-tags:empty { - display: none; -} - -.tb-tag { - display: inline-flex; - align-items: center; - gap: 8px; - padding: 5px 12px; - border-radius: 999px; - background: rgb(69 151 163 / 16%); - border: 1px solid rgb(69 151 163 / 45%); - font-size: 0.85rem; - color: var(--text-primary); -} - -.tb-tag button { - display: grid; - place-items: center; - width: 18px; - height: 18px; - border: none; - border-radius: 50%; - background: rgb(255 255 255 / 12%); - color: var(--text-primary); - font-size: 0.7rem; - line-height: 1; - cursor: pointer; - transition: background-color 0.15s ease; -} - -.tb-tag button:hover { - background: rgb(239 68 68 / 60%); -} - -/* ---------- Button ---------- */ - -.terminbuchung-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; -} - -.terminbuchung-seite .btn-accent { - background: var(--accent); - color: #03242b; -} - -.terminbuchung-seite .btn-accent:hover:not(:disabled) { - transform: translateY(-1px); - box-shadow: 0 10px 28px rgb(0 179 194 / 28%); -} - -.terminbuchung-seite .btn-block { - width: 100%; -} - -.terminbuchung-seite .btn:disabled { - opacity: 0.5; - cursor: not-allowed; -} - -.terminbuchung-seite .form-status { - display: none; - font-size: 0.9rem; -} - -.terminbuchung-seite .form-status.is-visible { - display: block; -} - -.terminbuchung-seite .form-status--ok { - color: #4ade80; -} - -.terminbuchung-seite .form-status--error { - color: #f87171; -} - -/* ---------- Footer ---------- */ - -.terminbuchung-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; -} - -.terminbuchung-seite .logo span { - color: var(--accent); -} - -.terminbuchung-seite .site-footer { - margin-top: 0; - padding: 64px 0 40px; - border-top: 1px solid var(--border); - background: rgb(255 255 255 / 1.5%); -} - -.terminbuchung-seite .footer-grid { - display: grid; - grid-template-columns: 2fr 1fr 1fr; - gap: 48px; -} - -.terminbuchung-seite .footer-brand p { - color: var(--text-muted); - font-size: 0.95rem; - max-width: 340px; - margin-top: 14px; -} - -.terminbuchung-seite .footer-col h3 { - font-size: 0.85rem; - letter-spacing: 0.14em; - text-transform: uppercase; - color: var(--text-muted); - margin-bottom: 16px; -} - -.terminbuchung-seite .footer-col ul { - list-style: none; - margin: 0; - padding: 0; -} - -.terminbuchung-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; -} - -.terminbuchung-seite .footer-col a:hover { - color: var(--text-primary); -} - -.terminbuchung-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; -} - -.terminbuchung-seite .footer-legal a { - color: inherit; - text-decoration: none; -} - -.terminbuchung-seite .footer-legal a:hover { - color: var(--text-primary); -} - -/* ---------- Scroll-Reveal ---------- */ - -.terminbuchung-seite .reveal { - opacity: 0; - transform: translateY(18px); - transition: opacity 0.6s ease, transform 0.6s ease; -} - -.terminbuchung-seite .reveal.visible { - opacity: 1; - transform: none; -} - -/* ---------- Responsiv ---------- */ - -@media (max-width: 980px) { - .terminbuchung-seite .tb-summary { - grid-template-columns: minmax(0, 1fr); - } - - .terminbuchung-seite .footer-grid { - grid-template-columns: 1fr 1fr; - } -} - -@media (max-width: 760px) { - .terminbuchung-seite .section { - padding: 48px 0 64px; - } - - .terminbuchung-seite .hero { - padding: 56px 0 48px; - } - - .terminbuchung-seite .footer-grid { - grid-template-columns: 1fr; - gap: 32px; - } -} - -/* Pop-up auf schmalen Bildschirmen */ -@media (max-width: 560px) { - .tb-overlay { - padding: 12px; - align-items: stretch; - } - - .tb-dialog { - max-height: calc(100vh - 24px); - padding: 22px 18px; - } - - .tb-day { - min-height: 38px; - font-size: 0.8rem; - } -} - -/* ---------- Reduzierte Bewegung ---------- */ - -@media (prefers-reduced-motion: reduce) { - .terminbuchung-seite { - scroll-behavior: auto; - } - - .terminbuchung-seite .reveal { - opacity: 1; - transform: none; - transition: none; - } - - .terminbuchung-seite, - .terminbuchung-seite *, - .tb-overlay, - .tb-overlay * { - transition-duration: 0.01ms !important; - } -} +/* Online-Terminbuchung – zwei Bereiche: + 1. .tb-overlay & Co.: Kalender-Pop-up auf der Startseite (index.html). + 2. .terminbuchung-seite: Buchungsformular-Seite (html/terminbuchung.html), + Tokens und Formular-Styles entsprechen css/kontakt.css. */ + +/* ========================================================= + 1. Terminbuchungs-Pop-up (Kalender) auf der Startseite + ========================================================= */ + +.tb-overlay { + --background-panel: #152027; + --text-primary: #ffffff; + --text-muted: #9ba7aa; + --accent: #4597A3; + --border: rgb(255 255 255 / 14%); + --font-display: "Sora", sans-serif; + --font-body: "DM Sans", Arial, Helvetica, sans-serif; + --radius: 1.1rem; + + position: fixed; + inset: 0; + z-index: 1000; + display: flex; + align-items: center; + justify-content: center; + padding: 24px; + background: rgb(7 20 25 / 72%); + backdrop-filter: blur(6px); + -webkit-backdrop-filter: blur(6px); +} + +.tb-overlay[hidden] { + display: none; +} + +.tb-dialog { + position: relative; + box-sizing: border-box; + width: min(560px, 100%); + background-color: #111a1e; + border: 2px solid rgb(255 255 255 / 9%); + border-radius: 16px; + box-shadow: 0 30px 90px rgb(0 0 0 / 45%); + padding: 26px 28px; + color: var(--text-primary); + font-family: var(--font-body); + line-height: 1.6; +} + +.tb-close { + position: absolute; + top: 14px; + right: 14px; + display: grid; + place-items: center; + width: 36px; + height: 36px; + border: 1px solid var(--border); + border-radius: 10px; + background: rgb(255 255 255 / 4%); + color: var(--text-primary); + font-size: 1.1rem; + line-height: 1; + cursor: pointer; + transition: border-color 0.15s ease, background-color 0.15s ease; +} + +.tb-close:hover, +.tb-close:focus-visible { + border-color: var(--accent); + background: rgb(69 151 163 / 14%); +} + +.tb-kicker { + display: inline-flex; + align-items: center; + gap: 10px; + font-size: 0.72rem; + font-weight: 600; + letter-spacing: 0.18em; + text-transform: uppercase; + color: var(--accent); + margin-bottom: 10px; +} + +.tb-kicker::before { + content: ""; + width: 24px; + height: 2px; + border-radius: 2px; + background: var(--accent); +} + +.tb-title { + margin: 0 0 6px; + font-family: var(--font-display); + font-size: 1.5rem; + font-weight: 600; + letter-spacing: -0.02em; +} + +.tb-subtitle { + margin: 0 0 20px; + font-size: 0.9rem; + color: var(--text-muted); +} + +/* ---------- Trainer-Auswahl ---------- */ + +.tb-trainer-label { + display: block; + font-size: 0.85rem; + font-weight: 600; + margin-bottom: 10px; +} + +.tb-trainer { + display: flex; + flex-wrap: wrap; + gap: 10px; + margin-bottom: 22px; +} + +.tb-trainer-btn { + padding: 9px 16px; + border-radius: 999px; + border: 1px solid rgba(255, 255, 255, 0.14); + background: rgb(255 255 255 / 4%); + color: var(--text-primary); + font-family: var(--font-body); + font-size: 0.9rem; + font-weight: 600; + cursor: pointer; + transition: border-color 0.15s ease, background-color 0.15s ease; +} + +.tb-trainer-btn:hover { + border-color: var(--accent); +} + +.tb-trainer-btn.is-active { + border-color: var(--accent); + background: rgb(69 151 163 / 18%); +} + +/* ---------- Kalender ---------- */ + +.tb-cal-head { + display: grid; + grid-template-columns: 52px minmax(0, 1fr) 52px; + align-items: center; + gap: 12px; + margin-bottom: 16px; +} + +.tb-cal-month { + text-align: center; + font-family: var(--font-display); + font-size: 1.55rem; + font-weight: 650; +} + +.tb-cal-nav { + display: grid; + place-items: center; + width: 52px; + height: 52px; + border-radius: 13px; + border: 2px solid var(--border); + background: transparent; + color: #6fa6b2; + font-size: 1.55rem; + cursor: pointer; + transition: border-color 0.15s ease, background-color 0.15s ease; +} + +.tb-cal-nav:hover:not(:disabled) { + border-color: var(--accent); + background: rgb(69 151 163 / 10%); +} + +.tb-cal-nav:disabled { + opacity: 0.35; + cursor: not-allowed; +} + +.tb-cal-weekdays, +.tb-cal-grid { + display: grid; + grid-template-columns: repeat(7, minmax(0, 1fr)); + gap: 6px 8px; +} + +.tb-cal-weekdays span { + text-align: center; + font-size: 1rem; + font-weight: 500; + color: #a1b0bb; + padding-bottom: 6px; +} + +.tb-day { + position: relative; + display: grid; + place-items: center; + width: min(100%, 54px); + aspect-ratio: 1; + min-height: 0; + justify-self: center; + border-radius: 50%; + border: 2px solid transparent; + background: transparent; + font-family: var(--font-body); + font-size: 1.05rem; + color: #7b8991; + cursor: pointer; + transition: background-color 0.15s ease, border-color 0.15s ease, color 0.15s ease; +} + +/* Leere Zellen vor dem ersten bzw. nach dem letzten Tag des Monats */ +.tb-day--leer { + visibility: hidden; + pointer-events: none; +} + +/* Verfügbarkeitskategorien */ +.tb-cal-grid .tb-day--verfuegbar { + background: transparent; + border-color: var(--accent); + color: var(--text-primary); +} + +.tb-cal-grid .tb-day--verfuegbar:hover, +.tb-cal-grid .tb-day--verfuegbar:focus-visible { + background: rgb(69 151 163 / 14%); + border-color: var(--accent); +} + +.tb-cal-grid .tb-day--neutral { + color: #7b8991; + background: transparent; + border-color: transparent; + cursor: default; +} + +/* Heute: dezent markiert, ohne die Kategorie zu verdecken */ +.tb-day--heute { + font-weight: 700; + box-shadow: inset 0 -2px 0 0 var(--accent); +} + +/* Ausgewählter Tag */ +.tb-cal-grid .tb-day--ausgewaehlt { + background: var(--accent); + border-color: var(--accent); + color: #071419; + font-weight: 700; +} + +/* ---------- Legende ---------- */ + +.tb-legend { + display: flex; + flex-wrap: wrap; + gap: 14px; + list-style: none; + margin: 12px 0 0; + padding: 0; + font-size: 0.9rem; + color: var(--text-muted); +} + +.tb-legend li { + display: inline-flex; + align-items: center; + gap: 7px; +} + +.tb-dot { + width: 10px; + height: 10px; + border-radius: 50%; +} + +.tb-dot--verfuegbar { + background: #22c55e; +} + +.tb-dot--nicht { + background: #ef4444; +} + +/* ---------- Uhrzeiten des gewählten Tages ---------- */ + +.tb-times { + margin-top: 18px; + padding-top: 14px; + border-top: 1px solid rgb(255 255 255 / 10%); +} + +.tb-times-label { + margin: 0 0 12px; + font-size: 1rem; + color: #a1b0bb; +} + +.tb-times-label strong { + color: var(--text-primary); +} + +.tb-time-chips { + display: flex; + flex-wrap: wrap; + gap: 10px; +} + +.tb-time-chip { + padding: 9px 14px; + border-radius: 11px; + border: 2px solid var(--border); + background: transparent; + color: var(--text-primary); + font-family: var(--font-body); + font-size: 1rem; + font-weight: 600; + cursor: pointer; + transition: border-color 0.15s ease, background-color 0.15s ease, color 0.15s ease; +} + +.tb-time-chip:hover { + border-color: var(--accent); +} + +.tb-time-chip.is-active { + background: #6fa6b2; + border-color: #6fa6b2; + color: #ffffff; +} + +/* ---------- Buchen-Button ---------- */ + +.tb-book { + display: block; + width: 100%; + margin-top: 22px; + padding: 0.85rem 1.5rem; + border-radius: 12px; + border: none; + background: var(--accent); + color: #03242b; + font-family: var(--font-body); + font-size: 0.98rem; + font-weight: 600; + cursor: pointer; + transition: transform 0.18s ease, box-shadow 0.18s ease, opacity 0.18s ease; +} + +.tb-book:hover:not(:disabled) { + transform: translateY(-1px); + box-shadow: 0 10px 28px rgb(0 179 194 / 28%); +} + +.tb-book:disabled { + opacity: 0.5; + cursor: not-allowed; +} + +.tb-hinweis { + margin: 12px 0 0; + font-size: 0.78rem; + color: var(--text-muted); +} + +@media (max-height: 760px) { + .tb-dialog { + padding-block: 18px; + line-height: 1.4; + } + + .tb-subtitle { + margin-bottom: 12px; + } + + .tb-trainer { + margin-bottom: 14px; + } + + .tb-cal-head { + grid-template-columns: 44px minmax(0, 1fr) 44px; + margin-bottom: 10px; + } + + .tb-cal-nav { + width: 44px; + height: 44px; + } + + .tb-cal-weekdays, + .tb-cal-grid { + gap: 3px 5px; + } + + .tb-day { + width: min(100%, 40px); + } + + .tb-times { + margin-top: 12px; + padding-top: 10px; + } + + .tb-book { + margin-top: 14px; + padding-block: 0.65rem; + } + + .tb-hinweis { + margin-top: 8px; + } +} + +/* ========================================================= + 2. Buchungsformular-Seite (html/terminbuchung.html) + ========================================================= */ + +.terminbuchung-seite { + /* Farben – Tokens von tri-hub.de (wie css/kontakt.css) */ + --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 ---------- */ + +.terminbuchung-seite, +.terminbuchung-seite *, +.terminbuchung-seite::before, +.terminbuchung-seite *::before, +.terminbuchung-seite::after, +.terminbuchung-seite *::after { + box-sizing: border-box; +} + +.terminbuchung-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 */ +.terminbuchung-seite::before { + content: ""; + position: absolute; + inset: 0; + z-index: -1; + background: + radial-gradient(900px 520px at 78% -10%, rgb(0 179 194 / 13%), transparent 65%), + radial-gradient(700px 480px at -10% 30%, rgb(35 137 148 / 10%), transparent 60%), + radial-gradient(600px 420px at 90% 95%, rgb(247 194 48 / 5%), transparent 60%); + pointer-events: none; +} + +.terminbuchung-seite h1, +.terminbuchung-seite h2, +.terminbuchung-seite h3 { + margin-top: 0; + font-family: var(--font-display); + color: var(--text-primary); + line-height: 1.2; + letter-spacing: -0.01em; +} + +.terminbuchung-seite p { + margin: 0 0 1em; +} + +.terminbuchung-seite .container { + width: min(100% - 48px, var(--container)); + margin-inline: auto; +} + +/* ---------- Typo-Helfer ---------- */ + +.terminbuchung-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; +} + +.terminbuchung-seite .kicker::before { + content: ""; + width: 28px; + height: 2px; + border-radius: 2px; + background: var(--accent); +} + +.terminbuchung-seite .lead { + font-size: 1.15rem; + color: var(--text-muted); + max-width: 660px; +} + +/* Die Navigationsleiste oben wird von css/navigation.css gestylt (.site-navigation). */ + +/* ---------- Kopfzeile / Hero ---------- */ + +.terminbuchung-seite .hero { + padding: 72px 0 56px; +} + +.terminbuchung-seite .hero h1 { + font-size: clamp(2.2rem, 5vw, 3.2rem); + font-weight: 500; + letter-spacing: -0.04em; + margin-bottom: 16px; +} + +.terminbuchung-seite .hero__text { + font-size: 1.1rem; + color: var(--text-muted); + max-width: 640px; +} + +/* ---------- Sektionen ---------- */ + +.terminbuchung-seite .section { + padding: 56px 0 88px; +} + +.terminbuchung-seite .section+.section { + border-top: 1px solid var(--border); +} + +.terminbuchung-seite .section h2 { + font-size: clamp(1.7rem, 3.5vw, 2.4rem); + margin-bottom: 16px; +} + +/* ---------- Termin-Zusammenfassung ---------- */ + +.tb-summary { + display: grid; + grid-template-columns: repeat(3, minmax(0, 1fr)); + gap: 20px; + margin-top: 32px; +} + +.tb-summary__item { + background-color: var(--background-panel); + border: 1px solid var(--border); + border-radius: calc(var(--radius) + 0.4rem); + padding: 22px; +} + +.tb-summary__label { + display: block; + font-size: 0.7rem; + letter-spacing: 0.2em; + text-transform: uppercase; + color: var(--text-muted); + margin-bottom: 8px; +} + +.tb-summary__value { + font-family: var(--font-display); + font-size: 1.05rem; + font-weight: 600; + color: var(--text-primary); + margin: 0; +} + +/* Hinweis, wenn die Seite ohne Termin-Parameter geöffnet wurde */ +.tb-summary--fehlend { + grid-template-columns: minmax(0, 1fr); +} + +.tb-summary--fehlend .tb-summary__item { + border-color: rgb(247 194 48 / 45%); + background: rgb(247 194 48 / 6%); +} + +.tb-summary--fehlend a { + color: var(--accent); +} + +.terminbuchung-seite #anfrage-pruefen[hidden] { + display: none; +} + +.tb-review-list { + display: grid; + grid-template-columns: minmax(150px, 0.7fr) minmax(0, 1.3fr); + gap: 0; + margin: 28px 0 0; + padding: 0 24px; + background: var(--background-panel); + border: 1px solid var(--border); + border-radius: var(--radius); +} + +.tb-review-list dt, +.tb-review-list dd { + margin: 0; + padding: 14px 0; + border-bottom: 1px solid var(--border); +} + +.tb-review-list dt { + padding-right: 18px; + color: var(--text-muted); +} + +.tb-review-list dd { + color: var(--text-primary); + font-weight: 600; + overflow-wrap: anywhere; +} + +.tb-review-list dt:last-of-type, +.tb-review-list dd:last-of-type { + border-bottom: 0; +} + +.tb-review-actions { + display: flex; + flex-wrap: wrap; + gap: 12px; + margin-top: 24px; +} + +.tb-review-button { + display: inline-flex; + min-height: 46px; + align-items: center; + justify-content: center; + padding: 0.7rem 1.1rem; + border: 1px solid var(--accent); + border-radius: 8px; + font: inherit; + font-weight: 600; + text-decoration: none; + cursor: pointer; +} + +.tb-review-button--primary { + color: #071419; + background: var(--accent); +} + +.tb-review-button--secondary { + color: var(--text-primary); + background: transparent; +} + +.tb-review-button:hover, +.tb-review-button:focus-visible { + filter: brightness(1.12); +} + +.terminbuchung-seite .tb-confirm-dialog { + width: min(480px, calc(100% - 32px)); + padding: 28px; + color: var(--text-primary); + background: var(--background-panel); + border: 1px solid rgb(255 255 255 / 14%); + border-radius: 12px; + box-shadow: 0 24px 72px rgb(0 0 0 / 45%); +} + +.terminbuchung-seite .tb-confirm-dialog::backdrop { + background: rgb(7 20 25 / 78%); + backdrop-filter: blur(5px); +} + +.terminbuchung-seite .tb-confirm-dialog h2 { + margin: 0 0 12px; + font-family: var(--font-display); + font-size: 1.5rem; +} + +.terminbuchung-seite .tb-confirm-dialog p { + margin: 0 0 22px; + color: var(--text-muted); + line-height: 1.6; +} + +@media (max-width: 560px) { + .terminbuchung-seite .tb-confirm-dialog { + padding: 22px; + } +} + +/* ---------- Formular ---------- */ + +.terminbuchung-seite form { + margin-top: 32px; + display: grid; + gap: 20px; +} + +.terminbuchung-seite .field label { + display: block; + font-size: 0.9rem; + font-weight: 500; + color: var(--text-primary); + margin-bottom: 8px; +} + +.terminbuchung-seite .field input, +.terminbuchung-seite .field select, +.terminbuchung-seite .field textarea { + width: 100%; + padding: 12px 16px; + border-radius: 10px; + border: 1px solid var(--border); + background: rgb(255 255 255 / 4%); + color: var(--text-primary); + font-family: var(--font-body); + font-size: 0.95rem; + transition: border-color 0.15s ease, background-color 0.15s ease; +} + +.terminbuchung-seite .field input::placeholder, +.terminbuchung-seite .field textarea::placeholder { + color: rgb(255 255 255 / 40%); +} + +.terminbuchung-seite .field input:focus, +.terminbuchung-seite .field select:focus, +.terminbuchung-seite .field textarea:focus { + outline: none; + border-color: var(--accent); + background: rgb(255 255 255 / 6%); +} + +.terminbuchung-seite .field select option { + background: #0b1a20; + color: var(--text-primary); +} + +.terminbuchung-seite .field textarea { + min-height: 110px; + resize: vertical; +} + +/* terminbuchung-form.js setzt die Zustandsklassen für ungültige Felder und Rückmeldungen. */ +.terminbuchung-seite .field input.invalid, +.terminbuchung-seite .field select.invalid, +.terminbuchung-seite .field textarea.invalid { + border-color: #ef4444; +} + +.terminbuchung-seite .field input.valid, +.terminbuchung-seite .field textarea.valid { + border-color: #22c55e; +} + +.terminbuchung-seite .field__error { + display: none; + margin-top: 6px; + font-size: 0.8rem; + color: #f87171; +} + +.terminbuchung-seite .field__hint { + margin-top: 6px; + font-size: 0.8rem; + color: var(--text-muted); +} + +.terminbuchung-seite .field.has-error .field__error { + display: block; +} + +/* ---------- Anamnese-Tags (Allergien & Unverträglichkeiten) ---------- */ + +.tb-tag-row { + display: flex; + gap: 10px; +} + +.tb-tag-row input { + flex: 1; +} + +.tb-tag-add { + flex-shrink: 0; + padding: 0 18px; + border-radius: 10px; + border: 1px solid var(--accent); + background: rgb(69 151 163 / 14%); + color: var(--text-primary); + font-family: var(--font-body); + font-size: 0.9rem; + font-weight: 600; + cursor: pointer; + transition: background-color 0.15s ease, border-color 0.15s ease; +} + +.tb-tag-add:hover { + background: rgb(69 151 163 / 28%); +} + +.tb-tags { + display: flex; + flex-wrap: wrap; + gap: 8px; + list-style: none; + margin: 10px 0 0; + padding: 0; +} + +.tb-tags:empty { + display: none; +} + +.tb-tag { + display: inline-flex; + align-items: center; + gap: 8px; + padding: 5px 12px; + border-radius: 999px; + background: rgb(69 151 163 / 16%); + border: 1px solid rgb(69 151 163 / 45%); + font-size: 0.85rem; + color: var(--text-primary); +} + +.tb-tag button { + display: grid; + place-items: center; + width: 18px; + height: 18px; + border: none; + border-radius: 50%; + background: rgb(255 255 255 / 12%); + color: var(--text-primary); + font-size: 0.7rem; + line-height: 1; + cursor: pointer; + transition: background-color 0.15s ease; +} + +.tb-tag button:hover { + background: rgb(239 68 68 / 60%); +} + +/* ---------- Button ---------- */ + +.terminbuchung-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; +} + +.terminbuchung-seite .btn-accent { + background: var(--accent); + color: #03242b; +} + +.terminbuchung-seite .btn-accent:hover:not(:disabled) { + transform: translateY(-1px); + box-shadow: 0 10px 28px rgb(0 179 194 / 28%); +} + +.terminbuchung-seite .btn-block { + width: 100%; +} + +.terminbuchung-seite .btn:disabled { + opacity: 0.5; + cursor: not-allowed; +} + +.terminbuchung-seite .form-status { + display: none; + font-size: 0.9rem; +} + +.terminbuchung-seite .form-status.is-visible { + display: block; +} + +.terminbuchung-seite .form-status--ok { + color: #4ade80; +} + +.terminbuchung-seite .form-status--error { + color: #f87171; +} + +/* ---------- Footer ---------- */ + +.terminbuchung-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; +} + +.terminbuchung-seite .logo span { + color: var(--accent); +} + +.terminbuchung-seite .site-footer { + margin-top: 0; + padding: 64px 0 40px; + border-top: 1px solid var(--border); + background: rgb(255 255 255 / 1.5%); +} + +.terminbuchung-seite .footer-grid { + display: grid; + grid-template-columns: 2fr 1fr 1fr; + gap: 48px; +} + +.terminbuchung-seite .footer-brand p { + color: var(--text-muted); + font-size: 0.95rem; + max-width: 340px; + margin-top: 14px; +} + +.terminbuchung-seite .footer-col h3 { + font-size: 0.85rem; + letter-spacing: 0.14em; + text-transform: uppercase; + color: var(--text-muted); + margin-bottom: 16px; +} + +.terminbuchung-seite .footer-col ul { + list-style: none; + margin: 0; + padding: 0; +} + +.terminbuchung-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; +} + +.terminbuchung-seite .footer-col a:hover { + color: var(--text-primary); +} + +.terminbuchung-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; +} + +.terminbuchung-seite .footer-legal a { + color: inherit; + text-decoration: none; +} + +.terminbuchung-seite .footer-legal a:hover { + color: var(--text-primary); +} + +/* ---------- Scroll-Reveal ---------- */ + +.terminbuchung-seite .reveal { + opacity: 0; + transform: translateY(18px); + transition: opacity 0.6s ease, transform 0.6s ease; +} + +.terminbuchung-seite .reveal.visible { + opacity: 1; + transform: none; +} + +/* ---------- Responsiv ---------- */ + +@media (max-width: 980px) { + .terminbuchung-seite .tb-summary { + grid-template-columns: minmax(0, 1fr); + } + + .terminbuchung-seite .footer-grid { + grid-template-columns: 1fr 1fr; + } +} + +@media (max-width: 760px) { + .terminbuchung-seite .section { + padding: 48px 0 64px; + } + + .terminbuchung-seite .hero { + padding: 56px 0 48px; + } + + .terminbuchung-seite .footer-grid { + grid-template-columns: 1fr; + gap: 32px; + } +} + +/* Pop-up auf schmalen Bildschirmen */ +@media (max-width: 560px) { + .tb-overlay { + padding: 10px; + } + + .tb-dialog { + width: 100%; + max-height: calc(100dvh - 20px); + padding: 20px 14px; + } + + .tb-cal-head { + grid-template-columns: 44px minmax(0, 1fr) 44px; + gap: 8px; + margin-bottom: 12px; + } + + .tb-cal-month { + font-size: 1.2rem; + } + + .tb-cal-nav { + width: 44px; + height: 44px; + border-radius: 11px; + font-size: 1.35rem; + } + + .tb-cal-weekdays, + .tb-cal-grid { + gap: 4px; + } + + .tb-cal-weekdays span { + font-size: 0.82rem; + padding-bottom: 4px; + } + + .tb-day { + width: min(100%, 42px); + min-height: 0; + font-size: 0.9rem; + } + + .tb-times-label { + font-size: 0.9rem; + } + + .tb-time-chip { + padding: 8px 11px; + border-radius: 9px; + font-size: 0.9rem; + } + + .tb-review-list { + grid-template-columns: minmax(0, 1fr); + padding-inline: 18px; + } + + .tb-review-list dt { + padding: 12px 0 2px; + border-bottom: 0; + } + + .tb-review-list dd { + padding: 0 0 12px; + } + + .tb-review-list dt:last-of-type, + .tb-review-list dd:last-of-type { + border-bottom: 0; + } +} + +/* ---------- Reduzierte Bewegung ---------- */ + +@media (prefers-reduced-motion: reduce) { + .terminbuchung-seite { + scroll-behavior: auto; + } + + .terminbuchung-seite .reveal { + opacity: 1; + transform: none; + transition: none; + } + + .terminbuchung-seite, + .terminbuchung-seite *, + .tb-overlay, + .tb-overlay * { + transition-duration: 0.01ms !important; + } +} \ No newline at end of file diff --git a/html/terminbuchung.html b/html/terminbuchung.html index 461fb77..ed134bc 100644 --- a/html/terminbuchung.html +++ b/html/terminbuchung.html @@ -1,210 +1,240 @@ - - - - - - - Terminbuchung | Ernährungsberatung für Triathleten | tri-hub - - - - - - - - - - - - - -
- - - -
- -
-
-
- Online-Terminbuchung -

Dein Termin ist fast gebucht

-

- Du hast dir einen Termin im Kalender ausgesucht. Bestätige ihn hier – und gib noch ein paar - Details zur Beratung sowie deine Kontaktdaten an. -

- - -
-
- Berater/in -

–

-
-
- Datum -

–

-
-
- Uhrzeit -

–

-
-
-
-
-
- - -
-
-
- Deine Angaben -

Noch ein paar Details zur Beratung

-

Damit dein Gegenüber den Termin gut vorbereiten kann: Wähle die Art der - Ernährungsberatung und das Format, beschreibe kurz dein Anliegen und nimm Allergien oder - Unverträglichkeiten als Tags auf.

- - -
-
- - -

Bitte wähle eine Art der Ernährungsberatung.

-
- -
- - -

Bitte wähle ein Beratungsformat.

-
- -
- - -

Bitte wähle aus, ob es ein Erst- oder Folgegespräch ist.

-
- -
- - -
- -
- -
- - -
-
    -

    Enter oder der Button fügt den Eintrag als Tag hinzu – so behält dein Gegenüber die Anamnese im Blick.

    -
    - -
    - - -

    Bitte gib deinen Namen an.

    -
    - -
    - - -

    Bitte gib eine gültige E-Mail-Adresse an.

    -
    - -
    - - -
    - - -

    -
    -
    -
    -
    - - - -
    -
    - - - + + + + + + + Terminbuchung | Ernährungsberatung für Triathleten | tri-hub + + + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    + Online-Terminbuchung +

    Dein Termin ist fast gebucht

    +

    + Du hast dir einen Termin im Kalender ausgesucht. Bestätige ihn hier – und gib noch ein paar + Details zur Beratung sowie deine Kontaktdaten an. +

    + + +
    +
    + Berater/in +

    –

    +
    +
    + Datum +

    –

    +
    +
    + Uhrzeit +

    –

    +
    +
    +
    +
    +
    + + +
    +
    +
    + Deine Angaben +

    Noch ein paar Details zur Beratung

    +

    Damit dein Gegenüber den Termin gut vorbereiten kann: Wähle die Art der + Ernährungsberatung und das Format, beschreibe kurz dein Anliegen und nimm Allergien oder + Unverträglichkeiten als Tags auf.

    + + +
    +
    + + +

    Bitte wähle eine Art der Ernährungsberatung.

    +
    + +
    + + +

    Bitte wähle ein Beratungsformat.

    +
    + +
    + + +

    Bitte wähle aus, ob es ein Erst- oder Folgegespräch ist.

    +
    + +
    + + +
    + +
    + +
    + + +
    +
      +

      Enter oder der Button fügt den Eintrag als Tag hinzu – so behält + dein Gegenüber die Anamnese im Blick.

      +
      + +
      + + +

      Bitte gib deinen Namen an.

      +
      + +
      + + +

      Bitte gib eine gültige E-Mail-Adresse an.

      +
      + +
      + + +
      + + +

      +
      +
      +
      +
      + + + + + +
      + + + Anfrage vorbereitet +

      Terminanfrage

      +

      Deine Terminanfrage wurde an dein E-Mail-Programm übergeben. Sende sie dort + bitte ab. Wir prüfen deinen Wunschtermin und schicken dir anschließend eine Bestätigung per E-Mail.

      + OK, zur Übersicht +
      +
      + + + \ No newline at end of file diff --git a/index.html b/index.html index 7820763..afa8159 100644 --- a/index.html +++ b/index.html @@ -1,265 +1,265 @@ - - - - - - - Ernährungsberatung für Triathleten | tri-hub - - - - - - - - - - - - - - - - - - - - - - - - -
      - -
      -
      -
      - KI-generierte Bildidee: Ernährungsberatung für einen älteren Radsportler, mit Müsli, Obst und Wasser am Tisch. - -
      -
      -

      Ernährungsberatung für Triathleten ab 50

      -

      - Dein Training hat einen Plan. - Deine Ernährung auch? -

      -

      - 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. -

      -
      - - -
      -
        -
      • - -

        Persönlich beraten Deine Ziele und Gewohnheiten im Blick

        -
      • -
      • - -

        Passend zum Training Für Training, Wettkampf und Erholung

        -
      • -
      • - -

        Machbar im Alltag Konkrete Schritte, die zu dir passen

        -
      • -
      - -
      -

      Ernährung für deinen Sport

      -

      Gut versorgt. In deinem Rhythmus.

      -
      -
      -
      -
      -
      -
      - - - -
      -
      -

      -

      -
      - -
      -
      - - -
      -
      -

      -

      -
      - -
      -
      - - -
      -
      -
      -
      - Info über die Beratung -

      Gut zu wissen, bevor du startest

      -

      Transparenz von Anfang an: So arbeitet die Beratung, für wen sie sich eignet und - was dich erwartet.

      -
      - - -
      -
      -

      Was dich bei der Beratung erwartet

      - -

      Gemeinsam schauen wir uns deine individuellen Ziele, Gewohnheiten und Bedürfnisse an und - entwickeln daraus passende Strategien für deinen Alltag.

      - -
        -
      • Individuelle Ernährungsberatung
      • -
      • Erstellung von Ernährungsplänen
      • -
      • Unterstützung bei Gewichtsmanagement
      • -
      • Beratung bei speziellen Ernährungsbedürfnissen
      • -
      • Analyse deiner aktuellen Ernährungsgewohnheiten
      • -
      • Praktische Tipps für die Umsetzung im Alltag
      • -
      -
      - -
      -

      So läuft die Beratung ab

      - -
        -
      1. - Erstgespräch -

        Wir besprechen deine Ziele, Wünsche und bisherigen Erfahrungen.

        -
      2. - -
      3. - Analyse -

        Deine aktuelle Ernährung und deine individuellen Bedürfnisse werden betrachtet. -

        -
      4. - -
      5. - Planung -

        Gemeinsam entwickeln wir konkrete und realistische Maßnahmen.

        -
      6. - -
      7. - Begleitung -

        Bei Bedarf wird der Fortschritt regelmäßig überprüft und der Plan angepasst.

        -
      8. -
      -
      - -
      -

      Für wen ist die Beratung geeignet?

      - -
        -
      • Menschen, die ihre Ernährung verbessern möchten
      • -
      • Sportler mit individuellen Ernährungszielen
      • -
      • Menschen mit speziellen Ernährungsbedürfnissen
      • -
      • Personen, die Unterstützung beim Gewichtsmanagement suchen
      • -
      -
      - -
      -

      Weitere Informationen

      - -

      - Platzhalter für zukünftige Erweiterungen, beispielsweise Informationen zu Preisen, - Terminen, Kontaktmöglichkeiten oder zusätzlichen Beratungsangeboten. -

      -
      -
      -
      - - -
      - -
      -

      Bereit für deinen nächsten Schritt?

      -

      Vereinbare ein unverbindliches Kennenlerngespräch – wir schauen gemeinsam, ob die Beratung zu - dir - passt.

      - Jetzt unverbindlich anfragen -
      -
      - - -
      - - -
      -
      - - - + + + + + + + Ernährungsberatung für Triathleten | tri-hub + + + + + + + + + + + + + + + + + + + + + + + + +
      + +
      +
      +
      + KI-generierte Bildidee: Ernährungsberatung für einen älteren Radsportler, mit Müsli, Obst und Wasser am Tisch. + +
      +
      +

      Ernährungsberatung für Triathleten ab 50

      +

      + Dein Training hat einen Plan. + Deine Ernährung auch? +

      +

      + 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. +

      +
      + + +
      +
        +
      • + +

        Persönlich beraten Deine Ziele und Gewohnheiten im Blick

        +
      • +
      • + +

        Passend zum Training Für Training, Wettkampf und Erholung

        +
      • +
      • + +

        Machbar im Alltag Konkrete Schritte, die zu dir passen

        +
      • +
      + +
      +

      Ernährung für deinen Sport

      +

      Gut versorgt. In deinem Rhythmus.

      +
      +
      +
      +
      +
      +
      + + + +
      +
      +

      +

      +
      + +
      +
      + + +
      +
      +

      +

      +
      + +
      +
      + + +
      +
      +
      +
      + Info über die Beratung +

      Gut zu wissen, bevor du startest

      +

      Transparenz von Anfang an: So arbeitet die Beratung, für wen sie sich eignet und + was dich erwartet.

      +
      + + +
      +
      +

      Was dich bei der Beratung erwartet

      + +

      Gemeinsam schauen wir uns deine individuellen Ziele, Gewohnheiten und Bedürfnisse an und + entwickeln daraus passende Strategien für deinen Alltag.

      + +
        +
      • Individuelle Ernährungsberatung
      • +
      • Erstellung von Ernährungsplänen
      • +
      • Unterstützung bei Gewichtsmanagement
      • +
      • Beratung bei speziellen Ernährungsbedürfnissen
      • +
      • Analyse deiner aktuellen Ernährungsgewohnheiten
      • +
      • Praktische Tipps für die Umsetzung im Alltag
      • +
      +
      + +
      +

      So läuft die Beratung ab

      + +
        +
      1. + Erstgespräch +

        Wir besprechen deine Ziele, Wünsche und bisherigen Erfahrungen.

        +
      2. + +
      3. + Analyse +

        Deine aktuelle Ernährung und deine individuellen Bedürfnisse werden betrachtet. +

        +
      4. + +
      5. + Planung +

        Gemeinsam entwickeln wir konkrete und realistische Maßnahmen.

        +
      6. + +
      7. + Begleitung +

        Bei Bedarf wird der Fortschritt regelmäßig überprüft und der Plan angepasst.

        +
      8. +
      +
      + +
      +

      Für wen ist die Beratung geeignet?

      + +
        +
      • Menschen, die ihre Ernährung verbessern möchten
      • +
      • Sportler mit individuellen Ernährungszielen
      • +
      • Menschen mit speziellen Ernährungsbedürfnissen
      • +
      • Personen, die Unterstützung beim Gewichtsmanagement suchen
      • +
      +
      + +
      +

      Weitere Informationen

      + +

      + Platzhalter für zukünftige Erweiterungen, beispielsweise Informationen zu Preisen, + Terminen, Kontaktmöglichkeiten oder zusätzlichen Beratungsangeboten. +

      +
      +
      +
      + + +
      + +
      +

      Bereit für deinen nächsten Schritt?

      +

      Vereinbare ein unverbindliches Kennenlerngespräch – wir schauen gemeinsam, ob die Beratung zu + dir + passt.

      + Jetzt unverbindlich anfragen +
      +
      + + +
      + + +
      +
      + + + diff --git a/js/berater.js b/js/berater.js index a783aaf..8143ee0 100644 --- a/js/berater.js +++ b/js/berater.js @@ -1,124 +1,124 @@ -document.addEventListener('DOMContentLoaded', async () => { - // Container für die Berater-Karten auswählen - const container = document.getElementById('berater-container'); - - // Titel- und Untertitel-Elemente aus dem HTML abrufen - const titleEl = document.querySelector('.section-title'); - const subtitleEl = document.querySelector('.section-subtitle'); - - let daten; - try { - // Daten aus der JSON-Datei asynchron laden - const response = await fetch('json/berater.json'); - if (!response.ok) throw new Error(`HTTP Fehler: ${response.status}`); - daten = await response.json(); - } catch (error) { - console.error('Fehler beim Laden der JSON-Datei:', error); - // Fehlermeldung auf der Webseite anzeigen, falls das Laden fehlschlägt - if (container) { - container.innerHTML = '

      Daten konnten nicht geladen werden.

      '; - } - return; - } - - // 1. Titel und Einleitungstext (Intro) aus der JSON-Datei einfügen - if (titleEl && daten.intro) { - titleEl.textContent = daten.intro.titel; - } - if (subtitleEl && daten.intro) { - subtitleEl.textContent = daten.intro.text; - } - - // 2. Berater-Karten verarbeiten und erstellen - // Wir iterieren über das Array "berater" innerhalb der JSON-Daten - daten.berater.forEach(berater => { - // Ein neues div-Element für die Karte erstellen - const card = document.createElement('div'); - card.className = 'berater-card'; - - // Zertifikate verarbeiten (falls vorhanden) - let zertifikateHtml = ''; - if (berater.zertifikate && berater.zertifikate.length > 0) { - zertifikateHtml = `

      Zertifikate

      `; - } - - // Links verarbeiten (falls vorhanden) - let linksHtml = ''; - if (berater.links && berater.links.length > 0) { - linksHtml = ``; - } - - // Zusatzinformationen verarbeiten (falls vorhanden) - let zusatzHtml = ''; - if (berater.zusatzInfo) { - zusatzHtml = `

      ${berater.zusatzInfo}

      `; - } - - // Eigene/unbekannte dynamische Felder verarbeiten - // Zuerst definieren wir, welche Felder Standard sind und ignoriert werden sollen - const standardFelder = ['id', 'name', 'role', 'image', 'bioTitle', 'bioText', 'zertifikate', 'links', 'zusatzInfo']; - let eigeneFelderHtml = ''; - - // Wir durchlaufen jeden Eintrag in der JSON für diesen spezifischen Berater - for (const [schluessel, wert] of Object.entries(berater)) { - // Wenn das Feld kein Standardfeld ist, generieren wir daraus eine Überschrift und den zugehörigen Text - if (!standardFelder.includes(schluessel)) { - eigeneFelderHtml += ` -

      ${schluessel}

      -

      ${wert}

      - `; - } - } - - // Die Karte mit allen gesammelten HTML-Bausteinen zusammenbauen - card.innerHTML = ` -
      - ${berater.name} Profilbild -
      -

      ${berater.name}

      -

      ${berater.role}

      - -
      -

      ${berater.bioTitle}

      -

      ${berater.bioText}

      - - ${eigeneFelderHtml} - ${zusatzHtml} - ${zertifikateHtml} - ${linksHtml} -
      - `; - - // Die fertige Karte zum Container auf der Seite hinzufügen - container.appendChild(card); - }); - - // "Trainingsplan buchen"-Buttons öffnen das Kalender-Pop-up (js/terminbuchung.js) - // mit der jeweiligen Berater-ID, statt eine externe Seite zu laden. - if (container) { - container.addEventListener('click', (event) => { - const button = event.target.closest('[data-terminbuchung]'); - if (!button) return; - if (window.Terminbuchung && typeof window.Terminbuchung.oeffnen === 'function') { - window.Terminbuchung.oeffnen(Number(button.dataset.terminbuchung)); - } else { - // Fallback, falls terminbuchung.js nicht geladen wurde: ursprüngliches Linkziel. - window.location.href = button.dataset.url || '#'; - } - }); - } +document.addEventListener('DOMContentLoaded', async () => { + // Container für die Berater-Karten auswählen + const container = document.getElementById('berater-container'); + + // Titel- und Untertitel-Elemente aus dem HTML abrufen + const titleEl = document.querySelector('.section-title'); + const subtitleEl = document.querySelector('.section-subtitle'); + + let daten; + try { + // Daten aus der JSON-Datei asynchron laden + const response = await fetch('json/berater.json'); + if (!response.ok) throw new Error(`HTTP Fehler: ${response.status}`); + daten = await response.json(); + } catch (error) { + console.error('Fehler beim Laden der JSON-Datei:', error); + // Fehlermeldung auf der Webseite anzeigen, falls das Laden fehlschlägt + if (container) { + container.innerHTML = '

      Daten konnten nicht geladen werden.

      '; + } + return; + } + + // 1. Titel und Einleitungstext (Intro) aus der JSON-Datei einfügen + if (titleEl && daten.intro) { + titleEl.textContent = daten.intro.titel; + } + if (subtitleEl && daten.intro) { + subtitleEl.textContent = daten.intro.text; + } + + // 2. Berater-Karten verarbeiten und erstellen + // Wir iterieren über das Array "berater" innerhalb der JSON-Daten + daten.berater.forEach(berater => { + // Ein neues div-Element für die Karte erstellen + const card = document.createElement('div'); + card.className = 'berater-card'; + + // Zertifikate verarbeiten (falls vorhanden) + let zertifikateHtml = ''; + if (berater.zertifikate && berater.zertifikate.length > 0) { + zertifikateHtml = `

      Zertifikate

      `; + } + + // Links verarbeiten (falls vorhanden) + let linksHtml = ''; + if (berater.links && berater.links.length > 0) { + linksHtml = ``; + } + + // Zusatzinformationen verarbeiten (falls vorhanden) + let zusatzHtml = ''; + if (berater.zusatzInfo) { + zusatzHtml = `

      ${berater.zusatzInfo}

      `; + } + + // Eigene/unbekannte dynamische Felder verarbeiten + // Zuerst definieren wir, welche Felder Standard sind und ignoriert werden sollen + const standardFelder = ['id', 'name', 'role', 'image', 'bioTitle', 'bioText', 'zertifikate', 'links', 'zusatzInfo']; + let eigeneFelderHtml = ''; + + // Wir durchlaufen jeden Eintrag in der JSON für diesen spezifischen Berater + for (const [schluessel, wert] of Object.entries(berater)) { + // Wenn das Feld kein Standardfeld ist, generieren wir daraus eine Überschrift und den zugehörigen Text + if (!standardFelder.includes(schluessel)) { + eigeneFelderHtml += ` +

      ${schluessel}

      +

      ${wert}

      + `; + } + } + + // Die Karte mit allen gesammelten HTML-Bausteinen zusammenbauen + card.innerHTML = ` +
      + ${berater.name} Profilbild +
      +

      ${berater.name}

      +

      ${berater.role}

      + +
      +

      ${berater.bioTitle}

      +

      ${berater.bioText}

      + + ${eigeneFelderHtml} + ${zusatzHtml} + ${zertifikateHtml} + ${linksHtml} +
      + `; + + // Die fertige Karte zum Container auf der Seite hinzufügen + container.appendChild(card); + }); + + // "Trainingsplan buchen"-Buttons öffnen das Kalender-Pop-up (js/terminbuchung.js) + // mit der jeweiligen Berater-ID, statt eine externe Seite zu laden. + if (container) { + container.addEventListener('click', (event) => { + const button = event.target.closest('[data-terminbuchung]'); + if (!button) return; + if (window.Terminbuchung && typeof window.Terminbuchung.oeffnen === 'function') { + window.Terminbuchung.oeffnen(Number(button.dataset.terminbuchung)); + } else { + // Fallback, falls terminbuchung.js nicht geladen wurde: ursprüngliches Linkziel. + window.location.href = button.dataset.url || '#'; + } + }); + } }); \ No newline at end of file diff --git a/js/terminbuchung-form.js b/js/terminbuchung-form.js index 1a2eab9..71499c3 100644 --- a/js/terminbuchung-form.js +++ b/js/terminbuchung-form.js @@ -1,350 +1,382 @@ -// Online-Terminbuchung – Buchungsseite (html/terminbuchung.html). -// Liest den gewählten Termin aus der URL (berater, datum, zeit), füllt die -// Zusammenfassung, verwaltet die Anamnese-Tags und prüft das Formular. -// Zum Absenden wird wie auf der Kontaktseite das E-Mail-Programm geöffnet. - -// ----- Scroll-Reveal ----- -(function () { - const elements = document.querySelectorAll(".terminbuchung-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); - }); -})(); - -// ----- Termin-Zusammenfassung aus der URL ----- -(function () { - const params = new URLSearchParams(window.location.search); - const beraterId = params.get("berater"); - const datum = params.get("datum"); - const zeit = params.get("zeit"); - - const elBerater = document.getElementById("tb-berater"); - const elDatum = document.getElementById("tb-datum"); - const elZeit = document.getElementById("tb-zeit"); - const zusammenfassung = document.getElementById("termin-zusammenfassung"); - if (!zusammenfassung) return; - - // Ohne Termin-Parameter (z. B. direkter Seitenaufruf): Hinweis statt Zusammenfassung. - if (!beraterId || !datum || !zeit) { - zusammenfassung.classList.add("tb-summary--fehlend"); - zusammenfassung.innerHTML = ` -
      - Kein Termin ausgewählt -

      - Du hast noch keinen Termin im Kalender gewählt. - Gehe bitte zurück zur Startseite und öffne dort über einen - Berater die Terminbuchung. -

      -
      - `; - return; - } - - // Datum lesbar formatieren, z. B. "Montag, 05.10.2026" - const [jahr, monat, tag] = datum.split("-").map(Number); - if (!jahr || !monat || !tag) return; - const datumObjekt = new Date(jahr, monat - 1, tag); - const formatiert = new Intl.DateTimeFormat("de-DE", { - weekday: "long", - day: "2-digit", - month: "2-digit", - year: "numeric" - }).format(datumObjekt); - - elDatum.textContent = formatiert; - elZeit.textContent = zeit.replace(":", ".") + " Uhr"; - - // Berater-Namen aus json/berater.json auflösen (ID stammt aus dem Kalender). - fetch("../json/berater.json") - .then(function (response) { - if (!response.ok) throw new Error("HTTP " + response.status); - return response.json(); - }) - .then(function (daten) { - const treffer = (daten.berater || []).find(function (b) { - return String(b.id) === String(beraterId); - }); - elBerater.textContent = treffer ? String(treffer.name).trim() : "Unbekannter Berater"; - }) - .catch(function () { - elBerater.textContent = "Unbekannter Berater"; - }); -})(); - -// ----- Anamnese-Tags (Allergien & Unverträglichkeiten) ----- -(function () { - const input = document.getElementById("tag-input"); - const addButton = document.getElementById("tag-add"); - const liste = document.getElementById("tag-list"); - if (!input || !addButton || !liste) return; - - const tags = []; - const MAX_TAGS = 20; - - function tagHinzufuegen() { - let wert = input.value.trim().replace(/^#+/, "").trim(); - if (!wert) return; - if (tags.length >= MAX_TAGS) { - input.value = ""; - return; - } - // Doppelte Einträge (groß/kleinschreibungsgleich) werden nicht aufgenommen. - const existiert = tags.some(function (t) { - return t.toLowerCase() === wert.toLowerCase(); - }); - if (!existiert) { - tags.push(wert); - } - input.value = ""; - zeichnen(); - } - - function tagEntfernen(index) { - tags.splice(index, 1); - zeichnen(); - } - - function zeichnen() { - liste.innerHTML = ""; - tags.forEach(function (wert, index) { - const li = document.createElement("li"); - li.className = "tb-tag"; - - const text = document.createElement("span"); - text.textContent = wert; - - const entfernen = document.createElement("button"); - entfernen.type = "button"; - entfernen.setAttribute("aria-label", "Tag entfernen: " + wert); - entfernen.innerHTML = "×"; - entfernen.addEventListener("click", function () { - tagEntfernen(index); - }); - - li.appendChild(text); - li.appendChild(entfernen); - liste.appendChild(li); - }); - } - - addButton.addEventListener("click", tagHinzufuegen); - input.addEventListener("keydown", function (event) { - if (event.key === "Enter") { - event.preventDefault(); - tagHinzufuegen(); - } - }); - - // Für das E-Mail-Body: aktuelle Tag-Liste als Text. - window.TerminbuchungTags = { - holen: function () { - return tags.slice(); - }, - leeren: function () { - tags.length = 0; - zeichnen(); - } - }; -})(); - -// ----- Formularprüfung und Absenden ----- -(function () { - const form = document.getElementById("terminbuchung-formular"); - if (!form) return; - - const EMAIL_TARGET = "info@trihub.de"; - - const fields = { - art: form.querySelector("[name='art']"), - format: form.querySelector("[name='format']"), - gespraech: form.querySelector("[name='gespraech']"), - beschreibung: form.querySelector("[name='beschreibung']"), - name: form.querySelector("[name='name']"), - email: form.querySelector("[name='email']"), - phone: form.querySelector("[name='phone']") - }; - - 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 validateSelect(key) { - const ok = fields[key].value !== ""; - setFieldState(fields[key], ok ? null : "error"); - return ok; - } - - function allValid() { - return ( - fields.art.value !== "" && - fields.format.value !== "" && - fields.gespraech.value !== "" && - fields.name.value.trim().length > 0 && - /^[^\s@]+@[^\s@]+\.[^\s@]{2,}$/.test(fields.email.value.trim()) - ); - } - - function updateSubmitState() { - const submit = form.querySelector("[type='submit']"); - if (submit) { - submit.disabled = !allValid(); - } - } - - form.addEventListener("input", updateSubmitState); - form.addEventListener("change", updateSubmitState); - - ["name", "email"].forEach(function (key) { - fields[key].addEventListener("blur", function () { - if (fields[key].value.trim() !== "" || form.dataset.touched === "true") { - ({ name: validateName, email: validateEmail })[key](); - } - }); - }); - - ["art", "format", "gespraech"].forEach(function (key) { - fields[key].addEventListener("change", function () { - validateSelect(key); - }); - }); - - // Termin-Angaben aus der URL für das E-Mail übernehmen. - const params = new URLSearchParams(window.location.search); - const datumRoh = params.get("datum") || ""; - const zeitRoh = params.get("zeit") || ""; - let terminText = "–"; - if (datumRoh && zeitRoh) { - const [jahr, monat, tag] = datumRoh.split("-").map(Number); - if (jahr && monat && tag) { - const formatiert = new Intl.DateTimeFormat("de-DE", { - weekday: "long", - day: "2-digit", - month: "2-digit", - year: "numeric" - }).format(new Date(jahr, monat - 1, tag)); - terminText = formatiert + " um " + zeitRoh.replace(":", ".") + " Uhr"; - } - } - - const elBerater = document.getElementById("tb-berater"); - - form.addEventListener("submit", function (event) { - event.preventDefault(); - form.dataset.touched = "true"; - - const okArt = validateSelect("art"); - const okFormat = validateSelect("format"); - const okGespraech = validateSelect("gespraech"); - const okName = validateName(); - const okEmail = validateEmail(); - - if (!(okArt && okFormat && okGespraech && okName && okEmail)) { - showStatus("error", "Bitte überprüfe die markierten Felder und versuche es erneut."); - return; - } - - function labelVon(key) { - const select = fields[key]; - return select.options[select.selectedIndex].text; - } - - const tagListe = window.TerminbuchungTags ? window.TerminbuchungTags.holen() : []; - - const body = [ - "Terminanfrage – Trainingsplan & Beratung", - "", - "Termin: " + terminText, - "Berater/in: " + (elBerater ? elBerater.textContent.trim() : "–"), - "Art der Ernährungsberatung: " + labelVon("art"), - "Beratungsformat: " + labelVon("format"), - "Gesprächsart: " + labelVon("gespraech"), - "", - "Kurzbeschreibung: " + (fields.beschreibung.value.trim() || "–"), - "Anamnese (Allergien & Unverträglichkeiten): " + (tagListe.length ? tagListe.join(", ") : "–"), - "", - "Name: " + fields.name.value.trim(), - "E-Mail: " + fields.email.value.trim(), - "Telefon: " + (fields.phone.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("Terminanfrage: " + terminText) + - "&body=" + encodeURIComponent(body); - - showStatus( - "ok", - "Danke! Dein E-Mail-Programm wurde geöffnet. Sende die Nachricht ab – wir bestätigen den Termin kurzfristig." - ); - form.reset(); - if (window.TerminbuchungTags) { - window.TerminbuchungTags.leeren(); - } - 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(); -})(); +// Online-Terminbuchung – Buchungsseite (html/terminbuchung.html). +// Liest den gewählten Termin aus der URL (berater, datum, zeit), füllt die +// Zusammenfassung, verwaltet die Anamnese-Tags und prüft das Formular. +// Zum Absenden wird wie auf der Kontaktseite das E-Mail-Programm geöffnet. + +// ----- Scroll-Reveal ----- +(function () { + const elements = document.querySelectorAll(".terminbuchung-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); + }); +})(); + +// ----- Termin-Zusammenfassung aus der URL ----- +(function () { + const params = new URLSearchParams(window.location.search); + const beraterId = params.get("berater"); + const datum = params.get("datum"); + const zeit = params.get("zeit"); + + const elBerater = document.getElementById("tb-berater"); + const elDatum = document.getElementById("tb-datum"); + const elZeit = document.getElementById("tb-zeit"); + const zusammenfassung = document.getElementById("termin-zusammenfassung"); + if (!zusammenfassung) return; + + // Ohne Termin-Parameter (z. B. direkter Seitenaufruf): Hinweis statt Zusammenfassung. + if (!beraterId || !datum || !zeit) { + zusammenfassung.classList.add("tb-summary--fehlend"); + zusammenfassung.innerHTML = ` +
      + Kein Termin ausgewählt +

      + Du hast noch keinen Termin im Kalender gewählt. + Gehe bitte zurück zur Startseite und öffne dort über einen + Berater die Terminbuchung. +

      +
      + `; + return; + } + + // Datum lesbar formatieren, z. B. "Montag, 05.10.2026" + const [jahr, monat, tag] = datum.split("-").map(Number); + if (!jahr || !monat || !tag) return; + const datumObjekt = new Date(jahr, monat - 1, tag); + const formatiert = new Intl.DateTimeFormat("de-DE", { + weekday: "long", + day: "2-digit", + month: "2-digit", + year: "numeric" + }).format(datumObjekt); + + elDatum.textContent = formatiert; + elZeit.textContent = zeit.replace(":", ".") + " Uhr"; + + // Berater-Namen aus json/berater.json auflösen (ID stammt aus dem Kalender). + fetch("../json/berater.json") + .then(function (response) { + if (!response.ok) throw new Error("HTTP " + response.status); + return response.json(); + }) + .then(function (daten) { + const treffer = (daten.berater || []).find(function (b) { + return String(b.id) === String(beraterId); + }); + elBerater.textContent = treffer ? String(treffer.name).trim() : "Unbekannter Berater"; + }) + .catch(function () { + elBerater.textContent = "Unbekannter Berater"; + }); +})(); + +// ----- Anamnese-Tags (Allergien & Unverträglichkeiten) ----- +(function () { + const input = document.getElementById("tag-input"); + const addButton = document.getElementById("tag-add"); + const liste = document.getElementById("tag-list"); + if (!input || !addButton || !liste) return; + + const tags = []; + const MAX_TAGS = 20; + + function tagHinzufuegen() { + let wert = input.value.trim().replace(/^#+/, "").trim(); + if (!wert) return; + if (tags.length >= MAX_TAGS) { + input.value = ""; + return; + } + // Doppelte Einträge (groß/kleinschreibungsgleich) werden nicht aufgenommen. + const existiert = tags.some(function (t) { + return t.toLowerCase() === wert.toLowerCase(); + }); + if (!existiert) { + tags.push(wert); + } + input.value = ""; + zeichnen(); + } + + function tagEntfernen(index) { + tags.splice(index, 1); + zeichnen(); + } + + function zeichnen() { + liste.innerHTML = ""; + tags.forEach(function (wert, index) { + const li = document.createElement("li"); + li.className = "tb-tag"; + + const text = document.createElement("span"); + text.textContent = wert; + + const entfernen = document.createElement("button"); + entfernen.type = "button"; + entfernen.setAttribute("aria-label", "Tag entfernen: " + wert); + entfernen.innerHTML = "×"; + entfernen.addEventListener("click", function () { + tagEntfernen(index); + }); + + li.appendChild(text); + li.appendChild(entfernen); + liste.appendChild(li); + }); + } + + addButton.addEventListener("click", tagHinzufuegen); + input.addEventListener("keydown", function (event) { + if (event.key === "Enter") { + event.preventDefault(); + tagHinzufuegen(); + } + }); + + // Für das E-Mail-Body: aktuelle Tag-Liste als Text. + window.TerminbuchungTags = { + holen: function () { + return tags.slice(); + }, + leeren: function () { + tags.length = 0; + zeichnen(); + } + }; +})(); + +// ----- Formularprüfung und Absenden ----- +(function () { + const form = document.getElementById("terminbuchung-formular"); + if (!form) return; + + const EMAIL_TARGET = "info@trihub.de"; + const bestaetigungDialog = document.getElementById("anfrage-bestaetigung"); + + const fields = { + art: form.querySelector("[name='art']"), + format: form.querySelector("[name='format']"), + gespraech: form.querySelector("[name='gespraech']"), + beschreibung: form.querySelector("[name='beschreibung']"), + name: form.querySelector("[name='name']"), + email: form.querySelector("[name='email']"), + phone: form.querySelector("[name='phone']") + }; + + const statusEl = document.getElementById("form-status"); + const formularBereich = document.getElementById("formular"); + const pruefBereich = document.getElementById("anfrage-pruefen"); + const pruefDetails = document.getElementById("anfrage-pruefen-details"); + const bearbeitenButton = document.getElementById("anfrage-bearbeiten"); + const sendenLink = document.getElementById("anfrage-senden"); + + 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 validateSelect(key) { + const ok = fields[key].value !== ""; + setFieldState(fields[key], ok ? null : "error"); + return ok; + } + + function allValid() { + return ( + fields.art.value !== "" && + fields.format.value !== "" && + fields.gespraech.value !== "" && + fields.name.value.trim().length > 0 && + /^[^\s@]+@[^\s@]+\.[^\s@]{2,}$/.test(fields.email.value.trim()) + ); + } + + function updateSubmitState() { + const submit = form.querySelector("[type='submit']"); + if (submit) { + submit.disabled = !allValid(); + } + } + + form.addEventListener("input", updateSubmitState); + form.addEventListener("change", updateSubmitState); + + ["name", "email"].forEach(function (key) { + fields[key].addEventListener("blur", function () { + if (fields[key].value.trim() !== "" || form.dataset.touched === "true") { + ({ name: validateName, email: validateEmail })[key](); + } + }); + }); + + ["art", "format", "gespraech"].forEach(function (key) { + fields[key].addEventListener("change", function () { + validateSelect(key); + }); + }); + + // Termin-Angaben aus der URL für das E-Mail übernehmen. + const params = new URLSearchParams(window.location.search); + const datumRoh = params.get("datum") || ""; + const zeitRoh = params.get("zeit") || ""; + let terminText = "–"; + if (datumRoh && zeitRoh) { + const [jahr, monat, tag] = datumRoh.split("-").map(Number); + if (jahr && monat && tag) { + const formatiert = new Intl.DateTimeFormat("de-DE", { + weekday: "long", + day: "2-digit", + month: "2-digit", + year: "numeric" + }).format(new Date(jahr, monat - 1, tag)); + terminText = formatiert + " um " + zeitRoh.replace(":", ".") + " Uhr"; + } + } + + const elBerater = document.getElementById("tb-berater"); + + function labelVon(key) { + const select = fields[key]; + return select.options[select.selectedIndex].text; + } + + function werteAnzeigen(werte) { + pruefDetails.replaceChildren(); + werte.forEach(function (eintrag) { + const label = document.createElement("dt"); + label.textContent = eintrag[0]; + const wert = document.createElement("dd"); + wert.textContent = eintrag[1] || "–"; + pruefDetails.append(label, wert); + }); + } + + bearbeitenButton.addEventListener("click", function () { + pruefBereich.hidden = true; + formularBereich.hidden = false; + formularBereich.scrollIntoView({ behavior: "smooth", block: "start" }); + }); + + sendenLink.addEventListener("click", function (event) { + event.preventDefault(); + bestaetigungDialog.showModal(); + window.location.href = sendenLink.href; + }); + + form.addEventListener("submit", function (event) { + event.preventDefault(); + form.dataset.touched = "true"; + + const okArt = validateSelect("art"); + const okFormat = validateSelect("format"); + const okGespraech = validateSelect("gespraech"); + const okName = validateName(); + const okEmail = validateEmail(); + + if (!(okArt && okFormat && okGespraech && okName && okEmail)) { + showStatus("error", "Bitte überprüfe die markierten Felder und versuche es erneut."); + return; + } + + const tagListe = window.TerminbuchungTags ? window.TerminbuchungTags.holen() : []; + const beraterText = elBerater ? elBerater.textContent.trim() : "–"; + const anfrageDaten = [ + ["Berater/in", beraterText], + ["Termin", terminText], + ["Art der Ernährungsberatung", labelVon("art")], + ["Beratungsformat", labelVon("format")], + ["Gesprächsart", labelVon("gespraech")], + ["Kurzbeschreibung", fields.beschreibung.value.trim() || "–"], + ["Anamnese (Allergien & Unverträglichkeiten)", tagListe.length ? tagListe.join(", ") : "–"], + ["Name", fields.name.value.trim()], + ["E-Mail", fields.email.value.trim()], + ["Telefon", fields.phone.value.trim() || "–"] + ]; + + const body = [ + "Terminanfrage – Trainingsplan & Beratung", + "", + "Termin: " + terminText, + "Berater/in: " + beraterText, + "Art der Ernährungsberatung: " + labelVon("art"), + "Beratungsformat: " + labelVon("format"), + "Gesprächsart: " + labelVon("gespraech"), + "", + "Kurzbeschreibung: " + (fields.beschreibung.value.trim() || "–"), + "Anamnese (Allergien & Unverträglichkeiten): " + (tagListe.length ? tagListe.join(", ") : "–"), + "", + "Name: " + fields.name.value.trim(), + "E-Mail: " + fields.email.value.trim(), + "Telefon: " + (fields.phone.value.trim() || "–") + ].join("\n"); + + werteAnzeigen(anfrageDaten); + sendenLink.href = + "mailto:" + EMAIL_TARGET + + "?subject=" + encodeURIComponent("Terminanfrage: " + terminText) + + "&body=" + encodeURIComponent(body); + formularBereich.hidden = true; + pruefBereich.hidden = false; + pruefBereich.scrollIntoView({ behavior: "smooth", block: "start" }); + }); + + 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(); +})(); diff --git a/js/terminbuchung.js b/js/terminbuchung.js index dd369cf..5f35e54 100644 --- a/js/terminbuchung.js +++ b/js/terminbuchung.js @@ -1,379 +1,400 @@ -// Online-Terminbuchung – Kalender-Pop-up auf der Startseite (index.html). -// Daten: json/berater.json (Trainer mit ID) und json/terminbuchung.json -// (Verfügbarkeit pro Trainer-ID: Datum JJJJ-MM-TT → Array von Uhrzeiten HH:MM). - -(function () { - "use strict"; - - const BERATER_URL = "json/berater.json"; - const TERMINE_URL = "json/terminbuchung.json"; - const ZIELSEITE = "html/terminbuchung.html"; - - const MONATE = [ - "Januar", "Februar", "März", "April", "Mai", "Juni", - "Juli", "August", "September", "Oktober", "November", "Dezember" - ]; - const WOCHE = ["Mo", "Di", "Mi", "Do", "Fr", "Sa", "So"]; - - let beraterDaten = []; // [{id, name, ...}] - let terminDaten = {}; // { "1": { "2026-10-05": ["09:00", ...] }, ... } - let geladen = null; // Promise, die auf das Laden beider JSON-Dateien wartet - - let overlay = null; // DOM: Pop-up (wird einmalig erzeugt) - let istGeoeffnet = false; - let fokusVorher = null; // Element, das vor dem Öffnen Fokus hatte - - const zustand = { - beraterId: null, - jahr: 0, - monat: 0, // 0-basiert - datum: null, // "JJJJ-MM-TT" - zeit: null // "HH:MM" - }; - - // ----- Daten laden ----- - - function ladeDaten() { - if (geladen) return geladen; - geladen = Promise.all([ - fetch(BERATER_URL).then((r) => (r.ok ? r.json() : Promise.reject(new Error("berater.json: HTTP " + r.status)))), - fetch(TERMINE_URL).then((r) => (r.ok ? r.json() : Promise.reject(new Error("terminbuchung.json: HTTP " + r.status)))) - ]) - .then(([berater, termine]) => { - beraterDaten = Array.isArray(berater.berater) ? berater.berater : []; - terminDaten = termine && typeof termine === "object" ? termine : {}; - }) - .catch((error) => { - console.error("Terminbuchung: Daten konnten nicht geladen werden.", error); - }); - return geladen; - } - - // ----- Hilfsfunktionen ----- - - function pad(n) { - return String(n).padStart(2, "0"); - } - - function datumSchluessel(jahr, monat, tag) { - return `${jahr}-${pad(monat + 1)}-${pad(tag)}`; - } - - function heutigerTag() { - const jetzt = new Date(); - return { jahr: jetzt.getFullYear(), monat: jetzt.getMonth(), tag: jetzt.getDate() }; - } - - function istVergangen(jahr, monat, tag) { - const heute = heutigerTag(); - return ( - jahr < heute.jahr || - (jahr === heute.jahr && monat < heute.monat) || - (jahr === heute.jahr && monat === heute.monat && tag < heute.tag) - ); - } - - function zeitenFuer(beraterId, schluessel) { - const tabelle = terminDaten[String(beraterId)]; - if (!tabelle || typeof tabelle !== "object") return []; - const zeiten = tabelle[schluessel]; - return Array.isArray(zeiten) ? zeiten : []; - } - - function istVerfuegbar(jahr, monat, tag) { - if (istVergangen(jahr, monat, tag)) return false; - return zeitenFuer(zustand.beraterId, datumSchluessel(jahr, monat, tag)).length > 0; - } - - function deutscherTagText(schluessel) { - const [jahr, monat, tag] = schluessel.split("-").map(Number); - const datum = new Date(jahr, monat - 1, tag); - return ( - WOCHE[(datum.getDay() + 6) % 7] + ", " + - pad(tag) + "." + pad(monat) + "." + jahr - ); - } - - function beraterName(beraterId) { - const treffer = beraterDaten.find((b) => Number(b.id) === Number(beraterId)); - return treffer ? String(treffer.name).trim() : "Berater/in"; - } - - // ----- Pop-up aufbauen (einmalig) ----- - - function bauPopUp() { - if (overlay) return overlay; - - const div = document.createElement("div"); - div.innerHTML = ` - - `; - - overlay = div.firstElementChild; - document.body.appendChild(overlay); - - // Trainer-Buttons füllen - const trainerBox = overlay.querySelector(".tb-trainer"); - beraterDaten.forEach((berater) => { - const btn = document.createElement("button"); - btn.type = "button"; - btn.className = "tb-trainer-btn"; - btn.textContent = String(berater.name).trim(); - btn.dataset.beraterId = berater.id; - btn.addEventListener("click", () => trainerWaehlen(Number(berater.id))); - trainerBox.appendChild(btn); - }); - - // Monatsnavigation - overlay.querySelectorAll(".tb-cal-nav").forEach((btn) => { - btn.addEventListener("click", () => { - verschiebeMonat(Number(btn.dataset.richtung)); - }); - }); - - // Schließen: Knopf, Klick auf den Schleier, Escape - overlay.querySelector(".tb-close").addEventListener("click", schliessen); - overlay.addEventListener("click", (event) => { - if (event.target === overlay) schliessen(); - }); - document.addEventListener("keydown", (event) => { - if (istGeoeffnet && event.key === "Escape") schliessen(); - }); - - // Buchen - overlay.querySelector(".tb-book").addEventListener("click", buchen); - - return overlay; - } - - // ----- Öffnen / Schließen ----- - - async function oeffnen(beraterId) { - await ladeDaten(); - - // Ohne Berater-Daten gibt es nichts auszuwählen – Pop-up bleibt geschlossen. - if (!beraterDaten.length) { - console.error("Terminbuchung: Keine Berater geladen – Pop-up wird nicht geöffnet."); - return; - } - - bauPopUp(); - - const heute = heutigerTag(); - zustand.jahr = heute.jahr; - zustand.monat = heute.monat; - trainerWaehlen(beraterId != null ? Number(beraterId) : beraterDaten[0].id); - - overlay.hidden = false; - istGeoeffnet = true; - fokusVorher = document.activeElement; - document.body.style.overflow = "hidden"; - overlay.querySelector(".tb-close").focus(); - } - - function schliessen() { - if (!istGeoeffnet) return; - overlay.hidden = true; - istGeoeffnet = false; - document.body.style.overflow = ""; - if (fokusVorher && typeof fokusVorher.focus === "function") { - fokusVorher.focus(); - } - } - - // ----- Auswahl-Logik ----- - - function trainerWaehlen(beraterId) { - zustand.beraterId = beraterId; - zustand.datum = null; - zustand.zeit = null; - - overlay.querySelectorAll(".tb-trainer-btn").forEach((btn) => { - const aktiv = Number(btn.dataset.beraterId) === Number(beraterId); - btn.classList.toggle("is-active", aktiv); - btn.setAttribute("aria-pressed", aktiv ? "true" : "false"); - }); - - zeitenBereichZuruecksetzen(); - kalenderZeichnen(); - buchButtonAktualisieren(); - } - - function verschiebeMonat(richtung) { - const heute = heutigerTag(); - let ziel = zustand.jahr * 12 + zustand.monat + richtung; - if (ziel < heute.jahr * 12 + heute.monat) return; // nicht vor dem aktuellen Monat - - zustand.jahr = Math.floor(ziel / 12); - zustand.monat = ziel % 12; - zustand.datum = null; - zustand.zeit = null; - zeitenBereichZuruecksetzen(); - kalenderZeichnen(); - buchButtonAktualisieren(); - } - - function tagKlicken(jahr, monat, tag) { - if (!istVerfuegbar(jahr, monat, tag)) return; - zustand.datum = datumSchluessel(jahr, monat, tag); - zustand.zeit = null; - kalenderZeichnen(); - zeitenZeichnen(); - buchButtonAktualisieren(); - } - - function zeitKlicken(zeit) { - zustand.zeit = zeit; - overlay.querySelectorAll(".tb-time-chip").forEach((chip) => { - chip.classList.toggle("is-active", chip.dataset.zeit === zeit); - }); - buchButtonAktualisieren(); - } - - function buchen() { - if (!zustand.datum || !zustand.zeit || zustand.beraterId == null) return; - const ziel = - ZIELSEITE + - "?berater=" + encodeURIComponent(zustand.beraterId) + - "&datum=" + encodeURIComponent(zustand.datum) + - "&zeit=" + encodeURIComponent(zustand.zeit); - window.location.href = ziel; - } - - // ----- Zeichnen ----- - - function kalenderZeichnen() { - const grid = overlay.querySelector(".tb-cal-grid"); - const monatsLabel = overlay.querySelector(".tb-cal-month"); - const navVorher = overlay.querySelector('.tb-cal-nav[data-richtung="-1"]'); - - monatsLabel.textContent = `${MONATE[zustand.monat]} ${zustand.jahr}`; - - // Vor dem aktuellen Monat kann nicht zurückgeblättert werden. - const heute = heutigerTag(); - navVorher.disabled = zustand.jahr * 12 + zustand.monat <= heute.jahr * 12 + heute.monat; - - const ersterTag = new Date(zustand.jahr, zustand.monat, 1); - const versatz = (ersterTag.getDay() + 6) % 7; // Montag als erste Spalte - const tageImMonat = new Date(zustand.jahr, zustand.monat + 1, 0).getDate(); - const heuteInfo = heutigerTag(); - - grid.innerHTML = ""; - for (let i = 0; i < versatz; i++) { - const leer = document.createElement("span"); - leer.className = "tb-day tb-day--leer"; - grid.appendChild(leer); - } - - for (let tag = 1; tag <= tageImMonat; tag++) { - const verfuegbar = istVerfuegbar(zustand.jahr, zustand.monat, tag); - const schluessel = datumSchluessel(zustand.jahr, zustand.monat, tag); - - const btn = document.createElement("button"); - btn.type = "button"; - let klassen = "tb-day "; - if (verfuegbar) { - klassen += "tb-day--verfuegbar"; - } else { - klassen += "tb-day--nicht"; - } - if ( - zustand.jahr === heuteInfo.jahr && - zustand.monat === heuteInfo.monat && - tag === heuteInfo.tag - ) { - klassen += " tb-day--heute"; - } - if (zustand.datum === schluessel) { - klassen += " tb-day--ausgewaehlt"; - } - btn.className = klassen; - btn.textContent = tag; - btn.disabled = !verfuegbar; - btn.setAttribute("aria-label", `${deutscherTagText(schluessel)} – ${verfuegbar ? "verfügbar" : "nicht verfügbar"}`); - if (verfuegbar) { - btn.addEventListener("click", () => tagKlicken(zustand.jahr, zustand.monat, tag)); - } - grid.appendChild(btn); - } - } - - function zeitenBereichZuruecksetzen() { - const bereich = overlay.querySelector(".tb-times"); - bereich.hidden = true; - overlay.querySelector(".tb-time-chips").innerHTML = ""; - overlay.querySelector(".tb-times-label").textContent = ""; - } - - function zeitenZeichnen() { - const bereich = overlay.querySelector(".tb-times"); - const label = overlay.querySelector(".tb-times-label"); - const chips = overlay.querySelector(".tb-time-chips"); - const zeiten = zeitenFuer(zustand.beraterId, zustand.datum); - - if (!zeiten.length) { - bereich.hidden = true; - return; - } - - label.innerHTML = `Wähle eine Uhrzeit für ${deutscherTagText(zustand.datum)} bei ${beraterName(zustand.beraterId)}:`; - chips.innerHTML = ""; - zeiten.forEach((zeit) => { - const chip = document.createElement("button"); - chip.type = "button"; - chip.className = "tb-time-chip" + (zustand.zeit === zeit ? " is-active" : ""); - chip.dataset.zeit = zeit; - chip.textContent = zeit.replace(":", ".") + " Uhr"; - chip.addEventListener("click", () => zeitKlicken(zeit)); - chips.appendChild(chip); - }); - bereich.hidden = false; - } - - function buchButtonAktualisieren() { - const btn = overlay.querySelector(".tb-book"); - btn.disabled = !(zustand.datum && zustand.zeit && zustand.beraterId != null); - } - - // ----- Öffentliche Schnittstelle (wird von berater.js genutzt) ----- - - window.Terminbuchung = { - oeffnen, - schliessen, - istGeoeffnet: () => istGeoeffnet - }; -})(); +// Online-Terminbuchung – Kalender-Pop-up auf der Startseite (index.html). +// Daten: json/berater.json (Trainer mit ID) und json/terminbuchung.json +// (Verfügbarkeit pro Trainer-ID: Datum JJJJ-MM-TT → Array von Uhrzeiten HH:MM). + +(function () { + "use strict"; + + const BERATER_URL = "json/berater.json"; + const TERMINE_URL = "json/terminbuchung.json"; + const ZIELSEITE = "html/terminbuchung.html"; + + const MONATE = [ + "Januar", "Februar", "März", "April", "Mai", "Juni", + "Juli", "August", "September", "Oktober", "November", "Dezember" + ]; + const WOCHE = ["Mo", "Di", "Mi", "Do", "Fr", "Sa", "So"]; + + let beraterDaten = []; // [{id, name, ...}] + let terminDaten = {}; // { "1": { "2026-10-05": ["09:00", ...] }, ... } + let geladen = null; // Promise, die auf das Laden beider JSON-Dateien wartet + + let overlay = null; // DOM: Pop-up (wird einmalig erzeugt) + let istGeoeffnet = false; + let fokusVorher = null; // Element, das vor dem Öffnen Fokus hatte + + const zustand = { + beraterId: null, + jahr: 0, + monat: 0, // 0-basiert + datum: null, // "JJJJ-MM-TT" + zeit: null // "HH:MM" + }; + + // ----- Daten laden ----- + + function ladeDaten() { + if (geladen) return geladen; + geladen = Promise.all([ + fetch(BERATER_URL).then((r) => (r.ok ? r.json() : Promise.reject(new Error("berater.json: HTTP " + r.status)))), + fetch(TERMINE_URL).then((r) => (r.ok ? r.json() : Promise.reject(new Error("terminbuchung.json: HTTP " + r.status)))) + ]) + .then(([berater, termine]) => { + beraterDaten = Array.isArray(berater.berater) ? berater.berater : []; + terminDaten = termine && typeof termine === "object" ? termine : {}; + }) + .catch((error) => { + console.error("Terminbuchung: Daten konnten nicht geladen werden.", error); + }); + return geladen; + } + + // ----- Hilfsfunktionen ----- + + function pad(n) { + return String(n).padStart(2, "0"); + } + + function datumSchluessel(jahr, monat, tag) { + return `${jahr}-${pad(monat + 1)}-${pad(tag)}`; + } + + function heutigerTag() { + const jetzt = new Date(); + return { jahr: jetzt.getFullYear(), monat: jetzt.getMonth(), tag: jetzt.getDate() }; + } + + function istVergangen(jahr, monat, tag) { + const heute = heutigerTag(); + return ( + jahr < heute.jahr || + (jahr === heute.jahr && monat < heute.monat) || + (jahr === heute.jahr && monat === heute.monat && tag < heute.tag) + ); + } + + function zeitenFuer(beraterId, schluessel) { + const tabelle = terminDaten[String(beraterId)]; + if (!tabelle || typeof tabelle !== "object") return []; + const zeiten = tabelle[schluessel]; + return Array.isArray(zeiten) ? zeiten : []; + } + + function hatFreieTermine(beraterId) { + const tabelle = terminDaten[String(beraterId)]; + if (!tabelle || typeof tabelle !== "object") return false; + + return Object.entries(tabelle).some(([schluessel, zeiten]) => { + const datum = /^(\d{4})-(\d{2})-(\d{2})$/.exec(schluessel); + if (!datum || !Array.isArray(zeiten) || zeiten.length === 0) return false; + return !istVergangen(Number(datum[1]), Number(datum[2]) - 1, Number(datum[3])); + }); + } + + function istVerfuegbar(jahr, monat, tag) { + if (istVergangen(jahr, monat, tag)) return false; + return zeitenFuer(zustand.beraterId, datumSchluessel(jahr, monat, tag)).length > 0; + } + + function deutscherTagText(schluessel) { + const [jahr, monat, tag] = schluessel.split("-").map(Number); + const datum = new Date(jahr, monat - 1, tag); + return ( + WOCHE[(datum.getDay() + 6) % 7] + ", " + + pad(tag) + "." + pad(monat) + "." + jahr + ); + } + + function beraterName(beraterId) { + const treffer = beraterDaten.find((b) => Number(b.id) === Number(beraterId)); + return treffer ? String(treffer.name).trim() : "Berater/in"; + } + + // ----- Pop-up aufbauen (einmalig) ----- + + function bauPopUp() { + if (overlay) return overlay; + + const div = document.createElement("div"); + div.innerHTML = ` + + `; + + overlay = div.firstElementChild; + document.body.appendChild(overlay); + + // Trainer-Buttons füllen + const trainerBox = overlay.querySelector(".tb-trainer"); + beraterDaten.forEach((berater) => { + const btn = document.createElement("button"); + btn.type = "button"; + btn.className = "tb-trainer-btn"; + btn.textContent = String(berater.name).trim(); + btn.dataset.beraterId = berater.id; + btn.addEventListener("click", () => trainerWaehlen(Number(berater.id))); + trainerBox.appendChild(btn); + }); + + // Monatsnavigation + overlay.querySelectorAll(".tb-cal-nav").forEach((btn) => { + btn.addEventListener("click", () => { + verschiebeMonat(Number(btn.dataset.richtung)); + }); + }); + + // Schließen: Knopf, Klick auf den Schleier, Escape + overlay.querySelector(".tb-close").addEventListener("click", schliessen); + overlay.addEventListener("click", (event) => { + if (event.target === overlay) schliessen(); + }); + document.addEventListener("keydown", (event) => { + if (istGeoeffnet && event.key === "Escape") schliessen(); + }); + + // Buchen + overlay.querySelector(".tb-book").addEventListener("click", buchen); + + return overlay; + } + + // ----- Öffnen / Schließen ----- + + async function oeffnen(beraterId) { + await ladeDaten(); + + // Ohne Berater-Daten gibt es nichts auszuwählen – Pop-up bleibt geschlossen. + if (!beraterDaten.length) { + console.error("Terminbuchung: Keine Berater geladen – Pop-up wird nicht geöffnet."); + return; + } + + bauPopUp(); + + const heute = heutigerTag(); + zustand.jahr = heute.jahr; + zustand.monat = heute.monat; + trainerWaehlen(beraterId != null ? Number(beraterId) : beraterDaten[0].id); + + overlay.hidden = false; + istGeoeffnet = true; + fokusVorher = document.activeElement; + document.body.style.overflow = "hidden"; + overlay.querySelector(".tb-close").focus(); + } + + function schliessen() { + if (!istGeoeffnet) return; + overlay.hidden = true; + istGeoeffnet = false; + document.body.style.overflow = ""; + if (fokusVorher && typeof fokusVorher.focus === "function") { + fokusVorher.focus(); + } + } + + // ----- Auswahl-Logik ----- + + function trainerWaehlen(beraterId) { + zustand.beraterId = beraterId; + zustand.datum = null; + zustand.zeit = null; + + overlay.querySelectorAll(".tb-trainer-btn").forEach((btn) => { + const aktiv = Number(btn.dataset.beraterId) === Number(beraterId); + btn.classList.toggle("is-active", aktiv); + btn.setAttribute("aria-pressed", aktiv ? "true" : "false"); + }); + + legendeAktualisieren(); + zeitenBereichZuruecksetzen(); + kalenderZeichnen(); + buchButtonAktualisieren(); + } + + function legendeAktualisieren() { + const dot = overlay.querySelector(".tb-legend .tb-dot"); + const label = overlay.querySelector(".tb-legend-label"); + const verfuegbar = hatFreieTermine(zustand.beraterId); + dot.className = "tb-dot " + (verfuegbar ? "tb-dot--verfuegbar" : "tb-dot--nicht"); + label.textContent = verfuegbar ? "Verfügbar" : "Nicht verfügbar"; + } + + function verschiebeMonat(richtung) { + const heute = heutigerTag(); + let ziel = zustand.jahr * 12 + zustand.monat + richtung; + if (ziel < heute.jahr * 12 + heute.monat) return; // nicht vor dem aktuellen Monat + + zustand.jahr = Math.floor(ziel / 12); + zustand.monat = ziel % 12; + zustand.datum = null; + zustand.zeit = null; + zeitenBereichZuruecksetzen(); + kalenderZeichnen(); + buchButtonAktualisieren(); + } + + function tagKlicken(jahr, monat, tag) { + if (!istVerfuegbar(jahr, monat, tag)) return; + zustand.datum = datumSchluessel(jahr, monat, tag); + zustand.zeit = null; + kalenderZeichnen(); + zeitenZeichnen(); + buchButtonAktualisieren(); + } + + function zeitKlicken(zeit) { + zustand.zeit = zeit; + overlay.querySelectorAll(".tb-time-chip").forEach((chip) => { + chip.classList.toggle("is-active", chip.dataset.zeit === zeit); + }); + buchButtonAktualisieren(); + } + + function buchen() { + if (!zustand.datum || !zustand.zeit || zustand.beraterId == null) return; + const ziel = + ZIELSEITE + + "?berater=" + encodeURIComponent(zustand.beraterId) + + "&datum=" + encodeURIComponent(zustand.datum) + + "&zeit=" + encodeURIComponent(zustand.zeit); + window.location.href = ziel; + } + + // ----- Zeichnen ----- + + function kalenderZeichnen() { + const grid = overlay.querySelector(".tb-cal-grid"); + const monatsLabel = overlay.querySelector(".tb-cal-month"); + const navVorher = overlay.querySelector('.tb-cal-nav[data-richtung="-1"]'); + + monatsLabel.textContent = `${MONATE[zustand.monat]} ${zustand.jahr}`; + + // Vor dem aktuellen Monat kann nicht zurückgeblättert werden. + const heute = heutigerTag(); + navVorher.disabled = zustand.jahr * 12 + zustand.monat <= heute.jahr * 12 + heute.monat; + + const ersterTag = new Date(zustand.jahr, zustand.monat, 1); + const versatz = (ersterTag.getDay() + 6) % 7; // Montag als erste Spalte + const tageImMonat = new Date(zustand.jahr, zustand.monat + 1, 0).getDate(); + const heuteInfo = heutigerTag(); + + grid.innerHTML = ""; + for (let i = 0; i < versatz; i++) { + const leer = document.createElement("span"); + leer.className = "tb-day tb-day--leer"; + grid.appendChild(leer); + } + + for (let tag = 1; tag <= tageImMonat; tag++) { + const verfuegbar = istVerfuegbar(zustand.jahr, zustand.monat, tag); + const schluessel = datumSchluessel(zustand.jahr, zustand.monat, tag); + const vergangen = istVergangen(zustand.jahr, zustand.monat, tag); + + const btn = document.createElement("button"); + btn.type = "button"; + let klassen = "tb-day "; + if (verfuegbar) { + klassen += "tb-day--verfuegbar"; + } else { + klassen += "tb-day--neutral"; + } + if ( + zustand.jahr === heuteInfo.jahr && + zustand.monat === heuteInfo.monat && + tag === heuteInfo.tag + ) { + klassen += " tb-day--heute"; + } + if (zustand.datum === schluessel) { + klassen += " tb-day--ausgewaehlt"; + } + btn.className = klassen; + btn.textContent = tag; + btn.disabled = !verfuegbar; + const status = verfuegbar ? "verfügbar" : vergangen ? "vergangen" : "keine Termine eingetragen"; + btn.setAttribute("aria-label", `${deutscherTagText(schluessel)} – ${status}`); + if (verfuegbar) { + btn.addEventListener("click", () => tagKlicken(zustand.jahr, zustand.monat, tag)); + } + grid.appendChild(btn); + } + } + + function zeitenBereichZuruecksetzen() { + const bereich = overlay.querySelector(".tb-times"); + bereich.hidden = true; + overlay.querySelector(".tb-time-chips").innerHTML = ""; + overlay.querySelector(".tb-times-label").textContent = ""; + } + + function zeitenZeichnen() { + const bereich = overlay.querySelector(".tb-times"); + const label = overlay.querySelector(".tb-times-label"); + const chips = overlay.querySelector(".tb-time-chips"); + const zeiten = zeitenFuer(zustand.beraterId, zustand.datum); + + if (!zeiten.length) { + bereich.hidden = true; + return; + } + + label.innerHTML = `Wähle eine Uhrzeit für ${deutscherTagText(zustand.datum)} bei ${beraterName(zustand.beraterId)}:`; + chips.innerHTML = ""; + zeiten.forEach((zeit) => { + const chip = document.createElement("button"); + chip.type = "button"; + chip.className = "tb-time-chip" + (zustand.zeit === zeit ? " is-active" : ""); + chip.dataset.zeit = zeit; + chip.textContent = zeit.replace(":", ".") + " Uhr"; + chip.addEventListener("click", () => zeitKlicken(zeit)); + chips.appendChild(chip); + }); + bereich.hidden = false; + } + + function buchButtonAktualisieren() { + const btn = overlay.querySelector(".tb-book"); + btn.disabled = !(zustand.datum && zustand.zeit && zustand.beraterId != null); + } + + // ----- Öffentliche Schnittstelle (wird von berater.js genutzt) ----- + + window.Terminbuchung = { + oeffnen, + schliessen, + istGeoeffnet: () => istGeoeffnet + }; +})(); diff --git a/json/berater.json b/json/berater.json index 417f55d..896526b 100644 --- a/json/berater.json +++ b/json/berater.json @@ -1,54 +1,54 @@ -{ - "intro": { - "titel": "Deine Ernährungsberater und Coaches", - "text": "Erfahrene Triathleten und zertifizierte Coaches, die dich auf dem Weg zu deinen Zielen begleiten und dir helfen, deine Leistung zu optimieren." - }, - "berater": [ - { - "id": 1, - "name": "Relindis Agethen ", - "role": "TRIATHLET UND ERNÄHRUNGSBERATERIN", - "image": "images/relindis_agethen.png", - "bioTitle": "Kurzbiografie", - "bioText": "Relindis Agethen, genannt Lilli, ist seit 16 Jahren im Triathlon aktiv und bringt starke sportliche Erfolge mit: Vize-Europameisterin, Deutsche Meisterin und mehrfache Baden-Württembergische Meisterin. Als zertifizierte Ernährungsberaterin für Sportler B-Lizenz ergänzt sie das Team um Leistungsentwicklung und Ernährungswissen.", - "zertifikate": [ - "Zertifizierter Triathlon-Trainer", - "Ernährungscoach B-Lizenz" - ], - "links": [ - { - "text": "Instagram Profil", - "url": "https://instagram.com" - }, - { - "text": "Trainingsplan buchen", - "url": "https://tri-hub.de/booking", - "aktion": "terminbuchung" - } - ] - }, - { - "id": 2, - "name": "Relindis Agethen ", - "role": "TRIATHLET UND ERNÄHRUNGSBERATERIN", - "image": "images/relindis_agethen_1.jpg", - "bioTitle": "Kurzbiografie", - "bioText": "Relindis Agethen, genannt Lilli, ist seit 16 Jahren im Triathlon aktiv und bringt starke sportliche Erfolge mit: Vize-Europameisterin, Deutsche Meisterin und mehrfache Baden-Württembergische Meisterin. Als zertifizierte Ernährungsberaterin für Sportler B-Lizenz ergänzt sie das Team um Leistungsentwicklung und Ernährungswissen.", - "zertifikate": [ - "Zertifizierter Triathlon-Trainer", - "Ernährungscoach B-Lizenz" - ], - "links": [ - { - "text": "Instagram Profil", - "url": "https://instagram.com" - }, - { - "text": "Trainingsplan buchen", - "url": "https://tri-hub.de/booking", - "aktion": "terminbuchung" - } - ] - } - ] +{ + "intro": { + "titel": "Deine Ernährungsberater und Coaches", + "text": "Erfahrene Triathleten und zertifizierte Coaches, die dich auf dem Weg zu deinen Zielen begleiten und dir helfen, deine Leistung zu optimieren." + }, + "berater": [ + { + "id": 1, + "name": "Relindis Agethen ", + "role": "TRIATHLET UND ERNÄHRUNGSBERATERIN", + "image": "images/relindis_agethen.png", + "bioTitle": "Kurzbiografie", + "bioText": "Relindis Agethen, genannt Lilli, ist seit 16 Jahren im Triathlon aktiv und bringt starke sportliche Erfolge mit: Vize-Europameisterin, Deutsche Meisterin und mehrfache Baden-Württembergische Meisterin. Als zertifizierte Ernährungsberaterin für Sportler B-Lizenz ergänzt sie das Team um Leistungsentwicklung und Ernährungswissen.", + "zertifikate": [ + "Zertifizierter Triathlon-Trainer", + "Ernährungscoach B-Lizenz" + ], + "links": [ + { + "text": "Instagram Profil", + "url": "https://instagram.com" + }, + { + "text": "Trainingsplan buchen", + "url": "https://tri-hub.de/booking", + "aktion": "terminbuchung" + } + ] + }, + { + "id": 2, + "name": "Relindis Agethen ", + "role": "TRIATHLET UND ERNÄHRUNGSBERATERIN", + "image": "images/relindis_agethen_1.jpg", + "bioTitle": "Kurzbiografie", + "bioText": "Relindis Agethen, genannt Lilli, ist seit 16 Jahren im Triathlon aktiv und bringt starke sportliche Erfolge mit: Vize-Europameisterin, Deutsche Meisterin und mehrfache Baden-Württembergische Meisterin. Als zertifizierte Ernährungsberaterin für Sportler B-Lizenz ergänzt sie das Team um Leistungsentwicklung und Ernährungswissen.", + "zertifikate": [ + "Zertifizierter Triathlon-Trainer", + "Ernährungscoach B-Lizenz" + ], + "links": [ + { + "text": "Instagram Profil", + "url": "https://instagram.com" + }, + { + "text": "Trainingsplan buchen", + "url": "https://tri-hub.de/booking", + "aktion": "terminbuchung" + } + ] + } + ] } \ No newline at end of file diff --git a/json/terminbuchung.json b/json/terminbuchung.json index 33eedb1..7d00d4e 100644 --- a/json/terminbuchung.json +++ b/json/terminbuchung.json @@ -1,27 +1,93 @@ -{ - "_hinweis": "Verfügbare Termine pro Trainer. Schlüssel = Berater-ID aus json/berater.json (z. B. \"1\"). Wert = Objekt: Datum im Format JJJJ-MM-TT als Schlüssel, Array mit Uhrzeiten (HH:MM) als Wert. Tage ohne Eintrag werden im Kalender als 'Nicht verfügbar' angezeigt. Schüssel, die mit '_' beginnen, werden von terminbuchung.js ignoriert.", - "1": { - "2026-10-05": ["09:00", "11:00", "14:30"], - "2026-10-07": ["10:00", "15:00"], - "2026-10-08": ["09:30", "11:30", "16:00"], - "2026-10-12": ["10:00", "14:00"], - "2026-10-14": ["09:00", "10:30", "15:30"], - "2026-10-15": ["11:00", "16:00"], - "2026-10-19": ["09:30", "14:30"], - "2026-10-21": ["10:00", "11:30"], - "2026-10-22": ["15:00", "16:30"], - "2026-10-26": ["09:00", "10:30", "14:00"], - "2026-10-28": ["11:00", "15:00"], - "2026-10-29": ["09:30", "16:00"] - }, - "2": { - "2026-10-06": ["10:00", "14:00", "16:00"], - "2026-10-09": ["09:00", "11:00"], - "2026-10-13": ["10:30", "15:00"], - "2026-10-16": ["09:30", "14:30"], - "2026-10-20": ["10:00", "11:30", "16:00"], - "2026-10-23": ["09:00", "15:30"], - "2026-10-27": ["10:30", "14:00"], - "2026-10-30": ["11:00", "16:00"] - } -} +{ + "_hinweis": "Verfügbare Termine pro Trainer. Schlüssel = Berater-ID aus json/berater.json (z. B. \"1\"). Wert = Objekt: Datum im Format JJJJ-MM-TT als Schlüssel, Array mit Uhrzeiten (HH:MM) als Wert. Tage ohne Eintrag werden im Kalender als 'Nicht verfügbar' angezeigt. Schüssel, die mit '_' beginnen, werden von terminbuchung.js ignoriert.", + "1": { + "2026-10-05": [ + "09:00", + "11:00", + "14:30" + ], + "2026-10-07": [ + "10:00", + "15:00" + ], + "2026-10-08": [ + "09:30", + "11:30", + "16:00" + ], + "2026-10-12": [ + "10:00", + "14:00" + ], + "2026-10-14": [ + "09:00", + "10:30", + "15:30" + ], + "2026-10-15": [ + "11:00", + "16:00" + ], + "2026-10-19": [ + "09:30", + "14:30" + ], + "2026-10-21": [ + "10:00", + "11:30" + ], + "2026-10-22": [ + "15:00", + "16:30" + ], + "2026-10-26": [ + "09:00", + "10:30", + "14:00" + ], + "2026-10-28": [ + "11:00", + "15:00" + ], + "2026-10-29": [ + "09:30", + "16:00" + ] + }, + "2": { + "2026-10-06": [ + "10:00", + "14:00", + "16:00" + ], + "2026-10-09": [ + "09:00", + "11:00" + ], + "2026-10-13": [ + "10:30", + "15:00" + ], + "2026-10-16": [ + "09:30", + "14:30" + ], + "2026-10-20": [ + "10:00", + "11:30", + "16:00" + ], + "2026-10-23": [ + "09:00", + "15:30" + ], + "2026-10-27": [ + "10:30", + "14:00" + ], + "2026-10-30": [ + "11:00", + "16:00" + ] + } +} \ No newline at end of file