/* eslint-disable */
/* global React, window, PAIN_LANDINGS */
const { useEffect, useMemo, useRef, useState } = React;
const PAIN_KEYS = Object.keys(PAIN_LANDINGS);
const GATEWAY_ROUTE_VERSION = '20260519-gateway-stagevideos';
const TRANSITION_MS = 520;
const ACTIVATION_TAIL_MS = 1000;
const BASE_IMAGE = '01_inicial_meditacion_fondo_negro';
const DIAGNOSTIC_VIDEOS = ['activation-01', 'activation-02', 'activation-03'];
const STAGE_ACTIVATION_VIDEOS = ['activation-01', 'activation-02', 'activation-03'];
const VISUAL_STATES = {
neutral: {
label: 'Calibración inicial',
short: 'Sistema Cortex',
video: 'activation-01',
poster: BASE_IMAGE,
},
sueno: {
label: 'Ruido nocturno',
short: 'Sueño',
video: 'activation-02',
poster: 'activation-02-poster',
},
ruido: {
label: 'Interferencia mental',
short: 'Ruido mental',
video: 'activation-02',
poster: 'activation-02-poster',
},
decisiones: {
label: 'Opciones abiertas',
short: 'Decisión',
video: 'activation-02',
poster: 'activation-02-poster',
},
burnout: {
label: 'Energía drenada',
short: 'Energía',
video: 'activation-01',
poster: 'activation-01-poster',
},
foco: {
label: 'Atención dispersa',
short: 'Foco',
video: 'activation-03',
poster: 'activation-03-poster',
},
};
const PRELOAD_ASSETS = [
{ id: BASE_IMAGE, type: 'image', url: 'assets/diagnostico/01_inicial_meditacion_fondo_negro.webp' },
{ id: 'activation-01-poster', type: 'image', url: 'assets/diagnostico/activation-01-poster.jpg' },
{ id: 'activation-02-poster', type: 'image', url: 'assets/diagnostico/activation-02-poster.jpg' },
{ id: 'activation-03-poster', type: 'image', url: 'assets/diagnostico/activation-03-poster.jpg' },
...DIAGNOSTIC_VIDEOS.map((id) => ({ id, type: 'video', url: `assets/diagnostico/${id}.mp4` })),
];
const QUESTIONS = [
{
label: '01 / Lo que más pesa',
title: <>¿Qué te está afectando más hoy?>,
help: 'Elige la frase que más se parece a tu día real.',
options: [
{ text: 'No puedo dormir o me despierto de madrugada', score: { sueno: 5 } },
{ text: 'Mi cabeza no para y no logro cortar', score: { ruido: 5 } },
{ text: 'Tengo información, pero sigo sin decidir', score: { decisiones: 5 } },
{ text: 'Estoy agotado aunque sigo funcionando', score: { burnout: 5 } },
{ text: 'Me disperso y no termino lo que empiezo', score: { foco: 5 } },
],
},
{
label: '02 / Cuándo aparece',
title: <>¿En qué momento se vuelve más evidente?>,
help: 'Esto afina la ruta sin hacerte contestar un formulario largo.',
options: [
{ text: 'De noche, cuando debería apagar', score: { sueno: 3, ruido: 2 } },
{ text: 'Al despertar, cuando ya arranco sin energía', score: { burnout: 3, sueno: 1 } },
{ text: 'Frente a una decisión importante', score: { decisiones: 4, ruido: 1 } },
{ text: 'Cuando trabajo con pantalla, pestañas o redes', score: { foco: 4, burnout: 1 } },
{ text: 'Después de conversaciones o estrés', score: { ruido: 3, burnout: 2 } },
],
},
{
label: '03 / Primer cambio',
title: <>Si esto mejora, ¿qué notarías primero?>,
help: 'La landing siguiente va a hablarte desde esa prioridad.',
options: [
{ text: 'Dormir de corrido y levantarme más liviano', score: { sueno: 4 } },
{ text: 'Tener silencio mental aunque el día esté intenso', score: { ruido: 4 } },
{ text: 'Decidir sin postergar tres meses', score: { decisiones: 4 } },
{ text: 'Recuperar energía sin apilar más hábitos', score: { burnout: 4 } },
{ text: 'Sostener foco real durante la mañana', score: { foco: 4 } },
],
},
];
function gwAsset(path) {
const prefix = window.CORTEX_ASSET_PREFIX || '../';
return `${prefix}${path}`;
}
function scoreToPain(scores) {
const ranked = [...PAIN_KEYS].sort((a, b) => scores[b] - scores[a]);
return ranked[0] || 'ruido';
}
function visualPainFromScores(scores) {
const hasScore = Object.values(scores).some((score) => score > 0);
return hasScore ? scoreToPain(scores) : 'neutral';
}
function optionTarget(option) {
return Object.entries(option.score)
.sort((a, b) => b[1] - a[1])[0]?.[0] || 'neutral';
}
function emptyScores() {
return PAIN_KEYS.reduce((acc, key) => ({ ...acc, [key]: 0 }), {});
}
function scoresFromSelections(selections) {
const nextScores = emptyScores();
Object.values(selections).forEach((option) => {
Object.entries(option.score).forEach(([key, value]) => {
nextScores[key] += value;
});
});
return nextScores;
}
function answersFromSelections(selections) {
return Object.keys(selections)
.map(Number)
.sort((a, b) => a - b)
.map((index) => selections[index].text);
}
function safeStoreResult(pain, answers) {
try {
window.sessionStorage.setItem('cortex-gateway-result', JSON.stringify({
pain: pain.slug,
answers,
createdAt: new Date().toISOString(),
}));
} catch (_) {
// Some embedded browsers block storage. The click must still navigate.
}
}
function GatewayChrome() {
return (
);
}
function GatewaySlideIndicator({ activeStep, onSelect }) {
return (
);
}
function GatewayLoading({ progress }) {
return (
Academia Cortex
{progress}%
Cargando diagnóstico
);
}
function GatewayMedia({ visualPain, activeVideoId, isHolding, videoSources, videoRefs }) {
const visual = VISUAL_STATES[visualPain] || VISUAL_STATES.neutral;
return (
{DIAGNOSTIC_VIDEOS.map((id) => (
))}
{visual.label}
{visual.short}
);
}
function HoldCue({ isHolding, hasHeld }) {
return (
{isHolding ? 'Soltá para volver' : 'Mantené presionado para activar'}
);
}
function GatewayQuestion({ question, questionIndex, onPick, getHref, disabled, selectedText }) {
return (
{question.label}
{question.title}
{question.help}
);
}
function Gateway() {
const videoRefs = useRef({});
const holdTimerRef = useRef(null);
const stageRef = useRef(null);
const slideRefs = useRef([]);
const [activeStep, setActiveStep] = useState(0);
const [selectedOptions, setSelectedOptions] = useState({});
const [activePulse, setActivePulse] = useState(null);
const [hasHeld, setHasHeld] = useState(false);
const [isHolding, setIsHolding] = useState(false);
const [isTransitioning, setIsTransitioning] = useState(false);
const [isExiting, setIsExiting] = useState(false);
const [loadProgress, setLoadProgress] = useState(0);
const [isReady, setIsReady] = useState(false);
const [videoSources, setVideoSources] = useState({});
const transitionRef = useRef(null);
const scores = useMemo(() => scoresFromSelections(selectedOptions), [selectedOptions]);
const visualPain = activePulse || visualPainFromScores(scores);
const visual = VISUAL_STATES[visualPain] || VISUAL_STATES.neutral;
const activeVideoId = STAGE_ACTIVATION_VIDEOS[activeStep] || visual.video;
useEffect(() => {
let cancelled = false;
const objectUrls = [];
async function preload() {
const sources = {};
let completed = 0;
await Promise.all(PRELOAD_ASSETS.map(async (asset) => {
try {
const response = await fetch(gwAsset(asset.url), { cache: 'force-cache' });
if (!response.ok) throw new Error(`Could not load ${asset.url}`);
const blob = await response.blob();
if (asset.type === 'video') {
const objectUrl = URL.createObjectURL(blob);
objectUrls.push(objectUrl);
sources[asset.id] = objectUrl;
}
} catch (_) {
if (asset.type === 'video') sources[asset.id] = gwAsset(asset.url);
} finally {
completed += 1;
if (!cancelled) {
setLoadProgress(Math.round((completed / PRELOAD_ASSETS.length) * 100));
}
}
}));
if (!cancelled) {
setVideoSources(sources);
setLoadProgress(100);
window.setTimeout(() => {
if (!cancelled) setIsReady(true);
}, 160);
}
}
preload();
return () => {
cancelled = true;
objectUrls.forEach((url) => URL.revokeObjectURL(url));
};
}, []);
useEffect(() => {
if (!isReady) return;
Object.values(videoRefs.current).forEach((video) => {
video.muted = true;
video.playsInline = true;
video.loop = true;
video.currentTime = 0.01;
const promise = video.play();
if (promise?.catch) promise.catch(() => {});
});
}, [isReady, videoSources]);
useEffect(() => {
if (!isReady) return undefined;
const stop = () => stopHold();
window.addEventListener('pointerup', stop);
window.addEventListener('pointercancel', stop);
window.addEventListener('touchend', stop, { passive: true });
window.addEventListener('touchcancel', stop, { passive: true });
window.addEventListener('mouseup', stop);
window.addEventListener('blur', stop);
return () => {
window.removeEventListener('pointerup', stop);
window.removeEventListener('pointercancel', stop);
window.removeEventListener('touchend', stop);
window.removeEventListener('touchcancel', stop);
window.removeEventListener('mouseup', stop);
window.removeEventListener('blur', stop);
window.clearTimeout(holdTimerRef.current);
};
}, [isReady, visualPain]);
useEffect(() => {
if (!isReady) return undefined;
const stage = stageRef.current;
if (!stage) return undefined;
let frame = 0;
const updateActiveStep = () => {
frame = 0;
const slideHeight = stage.clientHeight || window.innerHeight || 1;
const nextStep = Math.max(0, Math.min(QUESTIONS.length - 1, Math.round(stage.scrollTop / slideHeight)));
setActiveStep(nextStep);
};
const handleScroll = () => {
if (frame) return;
frame = window.requestAnimationFrame(updateActiveStep);
};
updateActiveStep();
stage.addEventListener('scroll', handleScroll, { passive: true });
return () => {
stage.removeEventListener('scroll', handleScroll);
if (frame) window.cancelAnimationFrame(frame);
};
}, [isReady]);
function clearTransition() {
if (transitionRef.current) window.clearTimeout(transitionRef.current);
}
function scrollToStep(index, behavior = 'smooth') {
const safeIndex = Math.max(0, Math.min(QUESTIONS.length - 1, index));
const stage = stageRef.current;
const slide = slideRefs.current[safeIndex];
setActiveStep(safeIndex);
if (stage && slide) {
stage.scrollTo({ top: slide.offsetTop, behavior });
}
}
function startHold(event) {
if (!isReady) return;
if (event?.pointerType === 'mouse' && event.button !== 0) return;
window.clearTimeout(holdTimerRef.current);
setHasHeld(true);
setIsHolding(true);
const activeVideo = videoRefs.current[activeVideoId];
if (activeVideo) {
const promise = activeVideo.play();
if (promise?.catch) promise.catch(() => {});
}
}
function stopHold() {
window.clearTimeout(holdTimerRef.current);
holdTimerRef.current = window.setTimeout(() => {
setIsHolding(false);
}, ACTIVATION_TAIL_MS);
}
function pick(option, questionIndex, event) {
if (isTransitioning) {
event?.preventDefault?.();
return;
}
const nextSelections = { ...selectedOptions, [questionIndex]: option };
const nextScores = scoresFromSelections(nextSelections);
const nextAnswers = answersFromSelections(nextSelections);
const isFinalStep = questionIndex + 1 >= QUESTIONS.length;
const painKey = isFinalStep ? optionTarget(option) : scoreToPain(nextScores);
const pain = PAIN_LANDINGS[painKey];
event?.currentTarget?.blur?.();
setSelectedOptions(nextSelections);
setActivePulse(painKey);
setIsTransitioning(true);
stopHold();
if (isFinalStep) {
event?.preventDefault?.();
safeStoreResult(pain, nextAnswers);
setIsExiting(true);
clearTransition();
transitionRef.current = window.setTimeout(() => {
window.location.href = `${pain.slug}/?v=${GATEWAY_ROUTE_VERSION}`;
}, TRANSITION_MS + 180);
return;
}
clearTransition();
window.setTimeout(() => scrollToStep(questionIndex + 1), 40);
transitionRef.current = window.setTimeout(() => {
setActivePulse(null);
setIsTransitioning(false);
setIsHolding(false);
}, TRANSITION_MS);
}
const routePreview = useMemo(() => {
const pain = PAIN_LANDINGS[visualPain];
return pain?.short || visual.short;
}, [visualPain, visual.short]);
return (
event.preventDefault()}
>
{!isReady && }
{QUESTIONS.map((question, index) => (
{
if (node) slideRefs.current[index] = node;
}}
>
{VISUAL_STATES[index === 0 ? 'neutral' : visualPain].label}
{routePreview}
= QUESTIONS.length
? (option) => `${PAIN_LANDINGS[optionTarget(option)].slug}/?v=${GATEWAY_ROUTE_VERSION}`
: null}
/>
))}
{isExiting && (
Abriendo tu VSL personalizada
)}
);
}
window.Gateway = Gateway;