import { useEffect, useState } from "react";
import { useI18n } from "../contexts/I18nContext";
/**
* First-visit onboarding overlay (controlled).
*
* Parent owns the visibility + persistence state (see App.tsx). This component
* only renders the modal and reports the user's intent via onDismiss:
* - onDismiss(true) → "Skip" / Finish — parent should persist.
* - onDismiss(false) → backdrop click / Escape — parent should close without persisting.
*
* Force-show is handled by the parent (see `shouldShowOnboarding` in App.tsx).
*/
interface Props {
onDismiss: (remember: boolean) => void;
}
const TITLE_ID = "ua-onboarding-title";
export default function OnboardingOverlay({ onDismiss }: Props) {
const { t } = useI18n();
const STEPS = t.onboarding.steps;
const [stepIdx, setStepIdx] = useState(0);
// Capture-phase Escape handler — runs before the global keydown chain so we
// can stopPropagation() and prevent it from also firing.
useEffect(() => {
const handler = (e: KeyboardEvent) => {
if (e.key === "Escape") {
e.stopPropagation();
onDismiss(false);
}
};
document.addEventListener("keydown", handler, true);
return () => document.removeEventListener("keydown", handler, true);
}, [onDismiss]);
const isFirst = stepIdx === 0;
const isLast = stepIdx === STEPS.length - 1;
const step = STEPS[stepIdx];
return (
{
if (e.target === e.currentTarget) onDismiss(false);
}}
>
0{stepIdx + 1}
/ 0{STEPS.length}
{t.onboarding.header}
{step.title}
{step.body}
{step.hint && (
·
{step.hint}
)}
{STEPS.map((_, i) => (
))}
{!isFirst && (
)}
{!isLast ? (
) : (
)}
);
}
const KEYFRAMES = `@keyframes ua-fade-in { from { opacity: 0 } to { opacity: 1 } }`;
const overlayStyle: React.CSSProperties = {
position: "fixed",
inset: 0,
background: "rgba(0, 0, 0, 0.78)",
backdropFilter: "blur(6px)",
zIndex: 9999,
display: "flex",
alignItems: "center",
justifyContent: "center",
padding: 16,
fontFamily: "var(--font-sans)",
animation: "ua-fade-in 0.4s cubic-bezier(0.22, 1, 0.36, 1)",
};
const cardStyle: React.CSSProperties = {
background: "var(--color-elevated)",
color: "var(--color-text-primary)",
maxWidth: 580,
width: "100%",
padding: "48px 48px 36px",
border: "1px solid var(--color-border-subtle)",
borderTop: "2px solid var(--color-accent)",
position: "relative",
};
const tagStyle: React.CSSProperties = {
fontSize: "0.72rem",
letterSpacing: "0.3em",
color: "var(--color-text-muted)",
textTransform: "uppercase",
marginBottom: 24,
display: "flex",
alignItems: "center",
flexWrap: "wrap",
gap: 4,
};
const numStyle: React.CSSProperties = {
fontFamily: "var(--font-heading)",
color: "var(--color-accent)",
fontSize: "0.9rem",
letterSpacing: "0.1em",
marginRight: 4,
};
const dotStyle: React.CSSProperties = {
width: 4,
height: 4,
background: "var(--color-accent)",
borderRadius: "50%",
margin: "0 12px",
};
const titleStyle: React.CSSProperties = {
fontFamily: "var(--font-heading)",
fontSize: "1.7rem",
fontWeight: 400,
letterSpacing: "0.02em",
lineHeight: 1.3,
marginBottom: 16,
color: "var(--color-text-primary)",
};
const bodyStyle: React.CSSProperties = {
fontSize: "0.98rem",
lineHeight: 1.7,
color: "var(--color-text-secondary)",
marginBottom: 0,
};
const hintStyle: React.CSSProperties = {
margin: "20px 0 0",
padding: "12px 18px",
borderLeft: "2px solid var(--color-border-medium)",
background: "var(--color-accent-overlay-bg)",
fontSize: "0.86rem",
color: "var(--color-accent)",
fontStyle: "italic",
};
const progressTrackStyle: React.CSSProperties = {
display: "flex",
gap: 6,
marginTop: 36,
marginBottom: 28,
};
const dotProgressStyle: React.CSSProperties = {
height: 4,
borderRadius: 2,
transition: "width 0.5s cubic-bezier(0.22, 1, 0.36, 1), background 0.3s",
};
const btnRowStyle: React.CSSProperties = {
display: "flex",
alignItems: "center",
gap: 10,
};
const btnStyle: React.CSSProperties = {
padding: "10px 22px",
fontSize: "0.82rem",
letterSpacing: "0.12em",
textTransform: "uppercase",
border: "1px solid",
cursor: "pointer",
fontFamily: "inherit",
transition: "all 0.3s cubic-bezier(0.22, 1, 0.36, 1)",
fontWeight: 400,
};
const btnGhostStyle: React.CSSProperties = {
background: "transparent",
borderColor: "var(--color-border-medium)",
color: "var(--color-text-muted)",
};
const btnPrimaryStyle: React.CSSProperties = {
background: "var(--color-accent)",
borderColor: "var(--color-accent)",
color: "var(--color-root)",
fontWeight: 500,
};