);
}
// ────────────────────────────────────────────────
// 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 (
}
{/* 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.
>
}