/* global React */ // Yomee — Onboarding Shared // Reusable header + form fields used by every onboarding screen. // Note: PrimaryButton lives in Shared.jsx (rounded-square, font-mono) — DO NOT redefine here. const { useState: useStateOS, useRef: useRefOS } = React; function OnboardingHeader({ onBack, onHelp, screenKey, progress = 0, hideBack = false, noHelp = false }) { const handleHelp = onHelp || (() => { if (window.YomeeHelp && screenKey) window.YomeeHelp.toggle(screenKey); }); const pct = Math.max(0, Math.min(1, progress)) * 100; return ( <>
{!hideBack && } {!noHelp && }
); } function TextField({ label, value, onChange, placeholder, placeholderColor, error, type = "text", inputMode, maxLength, autoFocus, prefix }) { const [focused, setFocused] = useStateOS(false); const phColor = placeholderColor || "rgba(20,32,28,0.42)"; const phClassRef = useRefOS( "yo-ph-" + Math.random().toString(36).slice(2, 8) ); const phClass = phClassRef.current; const borderColor = error ? "var(--yo-coral)" : focused ? "var(--yo-ink)" : "var(--yo-ink-200, rgba(20,32,28,0.18))"; return (
{label && }
{prefix && {prefix} } onChange(e.target.value)} onFocus={() => setFocused(true)} onBlur={() => setFocused(false)} placeholder={placeholder} maxLength={maxLength} autoFocus={autoFocus} className={phClass} style={{ width: "100%", height: "100%", border: "none", outline: "none", background: "transparent", fontFamily: "var(--font-mono)", fontSize: 15, color: "var(--yo-ink)", letterSpacing: "0.005em" }} />
{error &&
{error}
}
); } function SegmentedSelect({ label, value, onChange, options, error }) { return (
{label && }
{options.map((opt) => { const active = opt.value === value; return ( ); })}
{error &&
{error}
}
); } // ──────────────────────────────────────────────── // Typography helpers — drop-in DS primitives. // H1 / H1Bold = Geist Mono Medium / Bold @ 28px (the spec's "subtitle" / // "archetype" pair). Body = Onest @ 16px, body tracking 0.04em. // All colors must come from DS tokens; never hardcode hex. // ──────────────────────────────────────────────── function H1({ children, color = "var(--yo-ink)", style }) { return (
{children}
); } function H1Bold({ children, color = "var(--yo-ink)", style }) { return (
{children}
); } function Body({ children, color = "var(--yo-ink)", style }) { return (

{children}

); } // ──────────────────────────────────────────────── // Archetype reveal typography primitives. // Backed by --type-archetype-* tokens — used ONLY by ArchetypeScreen + // DiagnosticScreen (the 4 archetype reveal screens). Do not use elsewhere. // ──────────────────────────────────────────────── function ArchetypeExclamation({ children, style }) { return (
{children}
); } function ArchetypeLabel({ children, color = "var(--yo-ink)", style }) { return (
{children}
); } function ArchetypeHeadline({ children, style }) { return (

{children}

); } function ArchetypeBody({ children, style }) { return (

{children}

); } // ──────────────────────────────────────────────── // FrameHeader — back arrow + help (?) + optional close (×) + 6px progress bar. // Composed on top of OnboardingHeader to keep the existing progress chrome. // The help icon mirrors OnboardingHeader's behavior (delegates to // window.YomeeHelp.toggle(screenKey) when no explicit onHelp is provided). // Used by ArchetypeScreen and DiagnosticScreen. // ──────────────────────────────────────────────── function FrameHeader({ onBack, onClose, onHelp, screenKey, pct = 0, hideBack = false, noHelp = false }) { const handleHelp = onHelp || (() => { if (window.YomeeHelp && screenKey) window.YomeeHelp.toggle(screenKey); }); return ( <>
{!hideBack && } {!noHelp && } {onClose && }
); } // ──────────────────────────────────────────────── // MascotSlot — single, codified slot for the per-archetype Pocopin mascot. // // Used by ArchetypeScreen and DiagnosticScreen. ALL 4 archetype reveal // screens (totalero, optimizador, escalador, reiniciador) must render the // mascot through this slot so its bounding box is pixel-aligned across // frames — same width, same vertical center, same overlap depth into the // content card directly below it. // // Specs (derived from the totalero reference frame, 390 × 844): // width / height : 220 × 220 (≈56% of frame width) // object-fit : contain // horizontal : centered // overlap : the mascot's bottom third overlaps the top edge of the // content card below it. Implemented via negative // margin-bottom on the slot. Vertical overlap target: // ~70px (≈32% of mascot height). // z-index : 2 (above the content card, which sits at z-index 1) // background : transparent — no tile, no frame // source : profileAsset prop, REQUIRED. Must be one of the 4 DS // references under assets/profiles/. No fallback — // missing prop fails loudly so silent placeholders can // never re-introduce drift. // // Companion: MASCOT_OVERLAP_PX is the matching negative top-margin the // content card directly below the slot must absorb so the slot's overlap // lands consistently regardless of card padding. // ──────────────────────────────────────────────── const MASCOT_SLOT_PX = 220; const MASCOT_OVERLAP_PX = 70; function MascotSlot({ profileAsset, style }) { if (!profileAsset) { // Fail loudly. Silent placeholders are how the totalero drift survived. throw new Error( "MascotSlot: `profileAsset` prop is required. Pass one of " + "assets/profiles/{totalero,optimizador,escalador,reiniciador}.png — " + "no fallback is provided." ); } return (
); } // ──────────────────────────────────────────────── // ArchetypeScreen — used by totalero & reiniciador. // // Render order top-down inside the frame: // 1. FrameHeader (with pct) // 2. subtitle (Geist Mono Medium 28px, ink, centered) // 3. archetype (Geist Mono Bold 28px, archetypeColor, centered) // 4. mascot (220×220 centered) // 5. body (Onest 16px, centered, max-width ~294px) // 6. PrimaryButton // ──────────────────────────────────────────────── function ArchetypeScreen({ pct = 0.95, onBack, onClose, onHelp, screenKey = "profile-result", subtitle, archetype, archetypeColor = "var(--yo-ink)", mascot, body, variant, // "mascot-on-card" → totalero-style layout headline, // bold, 2-line, ink — only used by variant question, // muted question line — only used by variant cardBody, // text inside the card — only used by variant ctaLabel, onCta }) { const isMascotOnCard = variant === "mascot-on-card"; return (
{subtitle && {subtitle} } {archetype} {isMascotOnCard ? <> {headline &&
{headline}
} {question &&
{question}
} {/* Mascot + card. The mascot is rendered through MascotSlot so its bounding box is identical across all 4 archetype frames. The slot owns the overlap (negative margin-bottom); the card below absorbs that with extra top padding so its body copy clears the mascot. */} {mascot && }
{cardBody &&

{cardBody}

}
: <> {mascot && } {body && {body} } }
{ctaLabel}
); } // ──────────────────────────────────────────────── // DiagnosticScreen — used by optimizador & escalador. // // Render order top-down inside the frame: // 1. FrameHeader // 2. subtitle (Geist Mono Medium 28px, ink, centered) // 3. archetype (Geist Mono Bold 28px, archetypeColor, centered) // 4. headline (Body, centered, max-width 294) // 5. mascot (220×220 centered) // 6. metric card (white, 2px ink stroke, 28px radius, no shadow) // - metric: Geist Mono Bold 28px, --yo-coral // - footline: Body, with bolded coral interest amount // - emphasis link: Onest Bold 14px, --yo-mint-700 // 7. PrimaryButton // // Edge case (unpayable): pass `unpayable: true` to render the single-line // coral message instead of the metric block. // ──────────────────────────────────────────────── function DiagnosticScreen({ pct = 0.95, onBack, onClose, onHelp, screenKey = "profile-result", subtitle, archetype, archetypeColor = "var(--yo-ink)", headline, mascot, metric, // string e.g. "8 meses" / "3 años y 4 meses" interesTotal, // string e.g. "$14,400" emphasis = "Ahórratelos con Yomee", unpayable = false, ctaLabel, onCta }) { return (
{subtitle && {subtitle} } {archetype} {headline && {headline} } {mascot && } {/* Metric card. Top padding absorbs the MascotSlot's negative margin-bottom so the metric clears the mascot, while keeping the mascot's bounding box pixel-aligned with the totalero reference frame. */}
{unpayable ?
Tu pago mensual no cubre los intereses. Yomee encuentra la salida.
: <>
{metric}
Pagarás{" "} {interesTotal} {" "} en intereses. }
{ctaLabel}
); } window.OnboardingHeader = OnboardingHeader; window.TextField = TextField; window.SegmentedSelect = SegmentedSelect; window.H1 = H1; window.H1Bold = H1Bold; window.Body = Body; window.ArchetypeExclamation = ArchetypeExclamation; window.ArchetypeLabel = ArchetypeLabel; window.ArchetypeHeadline = ArchetypeHeadline; window.ArchetypeBody = ArchetypeBody; window.FrameHeader = FrameHeader; window.ArchetypeScreen = ArchetypeScreen; window.DiagnosticScreen = DiagnosticScreen; window.MascotSlot = MascotSlot;