// =============================================================
// PACK OPENING — dramatic reveal sequence
// 1. Pack on table → click → tear animation
// 2. Cards fly out one by one, flip from back to front
// 3. Reveal summary with NEW badges
// =============================================================
const { useState: usePackState, useEffect: usePackEffect, useMemo: usePackMemo } = React;
function PackOpening({ state, setState, onDone }) {
// phase: idle → tearing → reveal (one card at a time) → summary
const [phase, setPhase] = usePackState("idle");
const [pack, setPack] = usePackState(null); // generated cards
const [results, setResults] = usePackState([]); // [{pokemon, isNew}]
const [revealIdx, setRevealIdx] = usePackState(0);
function startOpen() {
if (state.packsAvailable <= 0) return;
const cards = GameEngine.generatePack();
setPack(cards);
setPhase("tearing");
// After tear animation, commit to state and begin reveal
setTimeout(() => {
setState(prev => {
const next = { ...prev, packsAvailable: prev.packsAvailable - 1, packsOpenedTotal: prev.packsOpenedTotal + 1 };
const res = GameEngine.addCardsToCollection(next, cards);
// Give coins for new dex entries
const newCount = res.filter(r => r.isNew).length;
next.coins = (next.coins || 0) + newCount * 5 + cards.length;
setResults(res);
return next;
});
setPhase("reveal");
setRevealIdx(0);
}, 900);
}
function nextCard() {
if (revealIdx < (results.length - 1)) {
setRevealIdx(revealIdx + 1);
} else {
setPhase("summary");
}
}
function openAnother() {
setPack(null); setResults([]); setRevealIdx(0); setPhase("idle");
}
return (
{/* Top bar */}
SECUENCIA DE APERTURA
SOBRE BÁSICO · 5 CARTAS
SOBRES RESTANTES: {state.packsAvailable}
{/* Background grid */}
{phase === "idle" &&
0}/>}
{phase === "tearing" && }
{phase === "reveal" && results[revealIdx] && (
)}
{phase === "summary" && (
0}/>
)}
);
}
// ---------- Idle pack ready to be opened ----------
function IdlePack({ onOpen, canOpen }) {
const [hover, setHover] = usePackState(false);
return (
Toca el sobre para abrirlo
setHover(true)}
onMouseLeave={()=>setHover(false)}
style={{
position:"relative", width:240, height:340,
cursor: canOpen ? "pointer" : "not-allowed",
transform: `perspective(1200px) rotateY(${hover ? -12 : -4}deg) rotateX(${hover ? 6 : 2}deg) translateY(${hover ? -8 : 0}px)`,
transition: "transform 400ms cubic-bezier(0.16,1,0.3,1)",
filter: canOpen ? "none" : "grayscale(1) brightness(0.5)",
}}>
3 COMUNES · 1 INUSUAL · 1 RARA
★ CHANCE DE LEGENDARIA ★
);
}
function BigPack({ hover }) {
return (

{/* Holo shine sweep */}
{/* Subtle scanlines */}
);
}
// ---------- Tearing animation ----------
function TearingPack() {
return (
);
}
// ---------- Reveal one card ----------
function RevealCard({ result, idx, total, onNext }) {
const [flipped, setFlipped] = usePackState(false);
const { pokemon, isNew } = result;
const isShiny = pokemon.rarity === "holo" || pokemon.rarity === "legendary";
usePackEffect(() => {
const t = setTimeout(() => setFlipped(true), 380);
return () => clearTimeout(t);
}, []);
return (
CARTA {idx + 1} / {total}
{/* Legendary radial burst */}
{pokemon.rarity === "legendary" && flipped && (
)}
{/* Flip card */}
{/* Status under card */}
{flipped && (
<>
{isNew && (
★ NUEVA ★
)}
{RARITY[pokemon.rarity].symbol} {RARITY[pokemon.rarity].name.toUpperCase()}
o presiona ESPACIO
>
)}
flipped && onNext()}/>
);
}
function KeyHandler({ onSpace }) {
usePackEffect(() => {
const h = (e) => { if (e.code === "Space" || e.code === "Enter") { e.preventDefault(); onSpace(); } };
window.addEventListener("keydown", h);
return () => window.removeEventListener("keydown", h);
}, [onSpace]);
return null;
}
// ---------- Summary ----------
function Summary({ results, onAnother, onDone, canOpenAnother }) {
const newCount = results.filter(r => r.isNew).length;
return (
RESULTADO DEL SOBRE
{newCount === 0 ? "Sin nuevas, pero sumas duplicados" :
newCount === 5 ? "¡SOBRE PERFECTO! Todas nuevas." :
`${newCount} ${newCount === 1 ? "carta nueva" : "cartas nuevas"} a la Pokédex`}
+{results.length} cartas obtenidas · +{newCount * 5 + results.length} Pokécoins
{results.map((r, i) => (
))}
{canOpenAnother && (
)}
);
}
// ---------- Button helpers ----------
function btnGhost() {
return {
fontFamily:"var(--font-sans)", fontWeight:600,
fontSize:13, letterSpacing:"0.15em", textTransform:"uppercase",
color:"var(--fg-2)",
background:"transparent",
border:"1px solid var(--border-solid)",
borderRadius: 2,
padding:"12px 22px",
cursor:"pointer",
transition:"all 180ms",
};
}
function btnGhostCyan() {
return {
...btnGhost(),
color:"var(--accent-cyan)",
border:"1px solid var(--accent-cyan)",
boxShadow:"0 0 12px var(--accent-cyan-glow)",
};
}
window.PackOpening = PackOpening;
window.btnGhost = btnGhost;
window.btnGhostCyan = btnGhostCyan;