diff --git a/messages/de.json b/messages/de.json
index 277b88ac3..62fa4aec1 100644
--- a/messages/de.json
+++ b/messages/de.json
@@ -34,7 +34,8 @@
"retry": "Erneut versuchen",
"reportIssue": "Fehler melden",
"showPassword": "Passwort anzeigen",
- "hidePassword": "Passwort verbergen"
+ "hidePassword": "Passwort verbergen",
+ "learnMore": "Mehr erfahren"
},
"doctorReport": {
"title": "Gesundheitsbericht",
diff --git a/messages/en.json b/messages/en.json
index bf05239fc..30a6934e1 100644
--- a/messages/en.json
+++ b/messages/en.json
@@ -34,7 +34,8 @@
"retry": "Retry",
"reportIssue": "Report issue",
"showPassword": "Show password",
- "hidePassword": "Hide password"
+ "hidePassword": "Hide password",
+ "learnMore": "Learn more"
},
"doctorReport": {
"title": "Health Report",
diff --git a/messages/es.json b/messages/es.json
index bb1b6992f..9ab22a6a3 100644
--- a/messages/es.json
+++ b/messages/es.json
@@ -34,7 +34,8 @@
"retry": "Reintentar",
"reportIssue": "Reportar problema",
"showPassword": "Mostrar contraseña",
- "hidePassword": "Ocultar contraseña"
+ "hidePassword": "Ocultar contraseña",
+ "learnMore": "Más información"
},
"doctorReport": {
"title": "Informe de salud",
diff --git a/messages/fr.json b/messages/fr.json
index fb09bcfa2..d2df8eb8d 100644
--- a/messages/fr.json
+++ b/messages/fr.json
@@ -34,7 +34,8 @@
"retry": "Réessayer",
"reportIssue": "Signaler un problème",
"showPassword": "Afficher le mot de passe",
- "hidePassword": "Masquer le mot de passe"
+ "hidePassword": "Masquer le mot de passe",
+ "learnMore": "En savoir plus"
},
"doctorReport": {
"title": "Rapport de santé",
diff --git a/messages/it.json b/messages/it.json
index 9c2a5496c..46e4e9c83 100644
--- a/messages/it.json
+++ b/messages/it.json
@@ -34,7 +34,8 @@
"retry": "Riprova",
"reportIssue": "Segnala un problema",
"showPassword": "Mostra password",
- "hidePassword": "Nascondi password"
+ "hidePassword": "Nascondi password",
+ "learnMore": "Scopri di più"
},
"doctorReport": {
"title": "Rapporto sanitario",
diff --git a/messages/pl.json b/messages/pl.json
index e4c29338e..6df51477c 100644
--- a/messages/pl.json
+++ b/messages/pl.json
@@ -34,7 +34,8 @@
"retry": "Spróbuj ponownie",
"reportIssue": "Zgłoś problem",
"showPassword": "Pokaż hasło",
- "hidePassword": "Ukryj hasło"
+ "hidePassword": "Ukryj hasło",
+ "learnMore": "Dowiedz się więcej"
},
"doctorReport": {
"title": "Raport zdrowotny",
diff --git a/src/components/ui/learn-more-link.tsx b/src/components/ui/learn-more-link.tsx
new file mode 100644
index 000000000..df5c47b2c
--- /dev/null
+++ b/src/components/ui/learn-more-link.tsx
@@ -0,0 +1,57 @@
+"use client";
+
+import { ExternalLink } from "lucide-react";
+
+import { learnLinkForMetric } from "@/lib/learn-links";
+import { useTranslations } from "@/lib/i18n/context";
+import { cn } from "@/lib/utils";
+
+/**
+ * v1.21.0 — discreet "Learn more" pointer to a public /learn guide.
+ *
+ * One small, calm anchor (no new colour, no card chrome) that links a concept
+ * out to its plain-language guide. Fail-closed by construction:
+ *
+ * - the href is resolved through `learnLinkForMetric` — a closed-set,
+ * registry-backed URL, never user/model content — so there is no XSS
+ * surface and no invented link;
+ * - an unmapped concept resolves to `null` and the component renders
+ * nothing, so a surface that wires a concept with no guide simply shows
+ * no pointer rather than a broken one.
+ *
+ * It is a literal `` (no markdown library): `target="_blank"` with
+ * `rel="noopener noreferrer"`, the guide title as the accessible label.
+ */
+export interface LearnMoreLinkProps {
+ /**
+ * A `LearnConcept` key (or any metric id) to resolve. Anything unmapped
+ * renders nothing.
+ */
+ concept: string;
+ className?: string;
+}
+
+export function LearnMoreLink({ concept, className }: LearnMoreLinkProps) {
+ const { t } = useTranslations();
+ const guide = learnLinkForMetric(concept);
+ if (guide == null) return null;
+
+ const label = t("common.learnMore");
+
+ return (
+
+ {label}
+
+
+ );
+}