diff --git a/messages/de.json b/messages/de.json index 2ca08a6d2..18c580a18 100644 --- a/messages/de.json +++ b/messages/de.json @@ -901,6 +901,7 @@ "navMood": "Stimmung", "navMedication": "Medikamente", "navBmi": "BMI", + "navAriaLabel": "Zu Abschnitt springen", "measurementsOverDays": "{count} Messungen über {days} Tage", "healthStatus": "Gesundheitsstatus", "noWeightData": "Keine Gewichtsdaten oder Größe nicht hinterlegt.", diff --git a/messages/en.json b/messages/en.json index 635d9b946..d6456bcc5 100644 --- a/messages/en.json +++ b/messages/en.json @@ -901,6 +901,7 @@ "navMood": "Mood", "navMedication": "Medication", "navBmi": "BMI", + "navAriaLabel": "Skip to section", "measurementsOverDays": "{count} measurements over {days} days", "healthStatus": "Health Status", "noWeightData": "No weight data or height not set.", diff --git a/src/app/insights/page.tsx b/src/app/insights/page.tsx index 38e716291..a59f2b19f 100644 --- a/src/app/insights/page.tsx +++ b/src/app/insights/page.tsx @@ -1017,7 +1017,7 @@ export default function InsightsPage() { legacyPayload={advisor.payload?.legacyPayload ?? false} /> -
+

{t("insights.generalStatusTitle")} @@ -1041,7 +1041,7 @@ export default function InsightsPage() {

{/* Section 3: Blood pressure */} -
+

{t("insights.bloodPressureSectionTitle")} @@ -1255,7 +1255,7 @@ export default function InsightsPage() {

{/* Section 4: Weight */} -
+

{t("insights.weightSectionTitle")} @@ -1425,7 +1425,7 @@ export default function InsightsPage() {

{/* Section 5: Pulse */} -
+

{t("insights.pulseSectionTitle")} @@ -1460,7 +1460,7 @@ export default function InsightsPage() { {/* Section: Mood */} {showMoodSection && ( -
+

{t("insights.moodSectionTitle")} @@ -1488,7 +1488,7 @@ export default function InsightsPage() { )} {/* Section 6: Medication Compliance */} -
+

{t("insights.medicationCompliance")} @@ -1589,7 +1589,7 @@ export default function InsightsPage() {

{/* Section 7: BMI */} -
+

{t("insights.bmiSectionTitle")} @@ -1729,10 +1729,19 @@ function InsightsSectionNav() { useEffect(() => { observerRef.current = new IntersectionObserver( (entries) => { - for (const entry of entries) { - if (entry.isIntersecting) { - setActiveId(entry.target.id); - } + // v1.4.22 W5 reconcile (Code-MED-4) — pick the entry with the + // highest intersectionRatio in the band rather than the + // observer-supplied last entry. Three sections briefly + // visible during a fast scroll otherwise made the active + // pill jump to whichever one came last in the batch. + const visible = entries + .filter((e) => e.isIntersecting) + .sort((a, b) => b.intersectionRatio - a.intersectionRatio); + const top = visible[0]; + if (top) { + setActiveId((current) => + current === top.target.id ? current : top.target.id, + ); } }, { rootMargin: "-30% 0px -60% 0px" }, @@ -1748,26 +1757,56 @@ function InsightsSectionNav() { function scrollTo(id: string) { const el = document.getElementById(id); - if (el) el.scrollIntoView({ behavior: "smooth" }); + if (!el) return; + // v1.4.22 W5 reconcile (Design-H1) — gate smooth scrolling behind + // `prefers-reduced-motion`; honour the user's OS-level pref. + const prefersReducedMotion = + typeof window !== "undefined" && + window.matchMedia?.("(prefers-reduced-motion: reduce)").matches; + el.scrollIntoView({ + behavior: prefersReducedMotion ? "auto" : "smooth", + }); } return ( -