feat: Ajuste de ejecucion de ruleta, aplicacion de CC
This commit is contained in:
Binary file not shown.
|
Before Width: | Height: | Size: 318 KiB After Width: | Height: | Size: 459 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 110 KiB After Width: | Height: | Size: 665 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 3.9 MiB After Width: | Height: | Size: 1.8 MiB |
@@ -77,8 +77,8 @@ export default function FeedbackModal({
|
||||
<div
|
||||
className="flex flex-col justify-center transition-all duration-300"
|
||||
style={{
|
||||
width: '1700px',
|
||||
maxWidth: '1824px',
|
||||
width: '1100px',
|
||||
maxWidth: '1100px',
|
||||
height: '594px',
|
||||
background: 'linear-gradient(180deg, #ffffff 0%, #d8dcec 100%)',
|
||||
borderRadius: '18px',
|
||||
@@ -128,7 +128,7 @@ export default function FeedbackModal({
|
||||
<div className="px-12 pt-10 pb-6">
|
||||
<p
|
||||
className="leading-relaxed"
|
||||
style={{ color: '#333333', fontSize: '22px', lineHeight: '1.6' }}
|
||||
style={{ color: '#333333', fontSize: '26px', lineHeight: '1.6' }}
|
||||
>
|
||||
<strong>Reflexión:</strong> {reflexion}
|
||||
</p>
|
||||
@@ -144,7 +144,7 @@ export default function FeedbackModal({
|
||||
>
|
||||
<span
|
||||
className="font-bold text-white whitespace-nowrap"
|
||||
style={{ fontSize: '28px' }}
|
||||
style={{ fontSize: '32px' }}
|
||||
>
|
||||
¿Sabías qué?
|
||||
</span>
|
||||
@@ -152,7 +152,7 @@ export default function FeedbackModal({
|
||||
<div className="flex-1 px-6 py-5">
|
||||
<p
|
||||
className="leading-relaxed"
|
||||
style={{ color: '#444444', fontSize: '22px', lineHeight: '1.6' }}
|
||||
style={{ color: '#444444', fontSize: '26px', lineHeight: '1.6' }}
|
||||
>
|
||||
{datoCurioso}
|
||||
</p>
|
||||
|
||||
@@ -71,8 +71,8 @@ export default function RuletaSVG({ categorias, rotacion, isSpinning, onClick }:
|
||||
return (
|
||||
<div className="relative flex flex-col items-center justify-center w-full select-none">
|
||||
|
||||
{/* ─── Puntero (fuera del SVG, fijo arriba) ─── */}
|
||||
<div className="absolute top-0 left-1/2 -translate-x-1/2 z-20" style={{ marginTop: '35px' }}>
|
||||
{/* ─── Puntero ─── */}
|
||||
<div className="absolute top-0 left-1/2 -translate-x-1/2 z-20" style={{ marginTop: '0px' }}>
|
||||
<img
|
||||
src="./img/indicador.png"
|
||||
alt="Indicador"
|
||||
@@ -81,7 +81,7 @@ export default function RuletaSVG({ categorias, rotacion, isSpinning, onClick }:
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* ─── SVG principal (ruleta + anillo + soporte) ─── */}
|
||||
{/* ─── SVG principal ─── */}
|
||||
<svg
|
||||
viewBox={`0 0 ${VIEWBOX_W} ${VIEWBOX_H}`}
|
||||
onClick={!isSpinning ? onClick : undefined}
|
||||
@@ -101,7 +101,8 @@ export default function RuletaSVG({ categorias, rotacion, isSpinning, onClick }:
|
||||
{/* ══════ GRUPO GIRATORIO: Segmentos + centro ══════ */}
|
||||
<g
|
||||
style={{
|
||||
transformOrigin: `${CX}px ${CY}px`,
|
||||
transformBox: 'fill-box',
|
||||
transformOrigin: 'center center',
|
||||
transform: `rotate(${rotacion}deg)`,
|
||||
transition: 'transform 4s cubic-bezier(0.17, 0.67, 0.12, 0.99)',
|
||||
}}
|
||||
@@ -120,7 +121,8 @@ export default function RuletaSVG({ categorias, rotacion, isSpinning, onClick }:
|
||||
const textOffsetX = i === 0 ? 0 : i === 1 ? -40 : i === 2 ? 30 : i === 3 ? 38 : 0
|
||||
const textOffsetY = i === 0 ? -45 : i === 1 ? 10 : i === 3 ? -12 : 0
|
||||
const textPos = { x: textPosRaw.x + textOffsetX, y: textPosRaw.y + textOffsetY }
|
||||
const imgSize = 60
|
||||
const imgSize = i === 1 ? 58 : i === 2 ? 95 : i === 3 ? 88 : 70
|
||||
const imgOffsetY = i === 0 ? 12 : i === 3 ? 15 : 0
|
||||
|
||||
/* Imágenes por segmento */
|
||||
const imgs = ['agua.png', 'residuos.png', 'energia.png', 'movilidad.png']
|
||||
@@ -142,7 +144,7 @@ export default function RuletaSVG({ categorias, rotacion, isSpinning, onClick }:
|
||||
<image
|
||||
href={`./img/${imgs[i]}`}
|
||||
x={imgCenter.x - imgSize / 2}
|
||||
y={imgCenter.y - imgSize / 2}
|
||||
y={imgCenter.y - imgSize / 2 + imgOffsetY}
|
||||
width={imgSize}
|
||||
height={imgSize}
|
||||
preserveAspectRatio="xMidYMid meet"
|
||||
|
||||
@@ -3,12 +3,12 @@ export default function Header() {
|
||||
<header
|
||||
className="w-full bg-transparent flex items-center shrink-0"
|
||||
style={{
|
||||
height: '90px',
|
||||
height: '130px',
|
||||
}}
|
||||
>
|
||||
{/* Logo izquierdo */}
|
||||
<div style={{ marginLeft: '75px', display: 'flex', alignItems: 'center' }}>
|
||||
<img src="./img/logo.png" alt="TIP" style={{ height: '50px', width: 'auto' }} />
|
||||
<img src="./img/logo.png" alt="TIP" style={{ height: '110px', width: 'auto' }} />
|
||||
</div>
|
||||
</header>
|
||||
)
|
||||
|
||||
@@ -32,15 +32,17 @@ export default function Cover({ onStart }: CoverProps) {
|
||||
className="rounded-3xl p-8 shadow-lg"
|
||||
style={{ background: 'linear-gradient(180deg, #ffffff 0%, #d8dcec 100%)' }}
|
||||
>
|
||||
<p className="text-gray-700 leading-relaxed" style={{ fontSize: '22px' }}>
|
||||
<p className="text-gray-700 leading-relaxed" style={{ fontSize: '26px' }}>
|
||||
Las reglas son muy sencillas: la ruleta girará, la suerte elegirá una categoría
|
||||
y te tocará responder una pregunta. Contarás con 30 segundos para responder
|
||||
cada una.
|
||||
</p>
|
||||
<p className="text-gray-700 leading-relaxed mt-4" style={{ fontSize: '22px' }}>
|
||||
<p className="text-gray-700 leading-relaxed mt-4" style={{ fontSize: '26px' }}>
|
||||
No se trata de encontrar la respuesta perfecta, sino de reflexionar y responder
|
||||
desde tu experiencia y perspectiva. ¡Prepárate para poner a prueba tus
|
||||
conocimientos y disfrutar de la dinámica!
|
||||
desde tu experiencia y perspectiva.
|
||||
</p>
|
||||
<p className="text-gray-700 leading-relaxed mt-4" style={{ fontSize: '26px' }}>
|
||||
¡Prepárate para poner a prueba tus conocimientos y disfrutar de la dinámica!
|
||||
</p>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -13,9 +13,9 @@ const LETRAS = ['A', 'B', 'C', 'D']
|
||||
/** Mapeo de nombre de categoría → imagen PNG */
|
||||
const CAT_IMG: Record<string, string> = {
|
||||
'Agua': 'agua.png',
|
||||
'Residuos': 'residuos.png',
|
||||
'Residuos y Circularidad': 'residuos.png',
|
||||
'Energía': 'energia.png',
|
||||
'Electromovilidad': 'movilidad.png',
|
||||
'Electromovilidad y Transporte Sostenible': 'movilidad.png',
|
||||
}
|
||||
|
||||
export default function Game({ onEnd }: GameProps) {
|
||||
@@ -44,7 +44,7 @@ export default function Game({ onEnd }: GameProps) {
|
||||
backgroundColor: '#ed1f24',
|
||||
borderRadius: '0px 0px 0px 25px',
|
||||
boxShadow: '0 4px 12px rgba(0,0,0,0.3)',
|
||||
maxWidth: '520px',
|
||||
maxWidth: '700px',
|
||||
}}
|
||||
>
|
||||
{/* Círculo blanco con imagen de categoría */}
|
||||
@@ -52,29 +52,45 @@ export default function Game({ onEnd }: GameProps) {
|
||||
<div
|
||||
className="rounded-full flex items-center justify-center shrink-0"
|
||||
style={{
|
||||
width: '52px',
|
||||
height: '52px',
|
||||
width: '80px',
|
||||
height: '80px',
|
||||
backgroundColor: '#FFFFFF',
|
||||
boxShadow: '0 2px 8px rgba(0,0,0,0.2)',
|
||||
boxShadow: '0 4px 12px rgba(0,0,0,0.3)',
|
||||
marginTop: '-19px',
|
||||
marginBottom: '-55px',
|
||||
position: 'relative',
|
||||
zIndex: 30,
|
||||
}}
|
||||
>
|
||||
<img
|
||||
src={`./img/${CAT_IMG[categoria.nombre]}`}
|
||||
alt={categoria.nombre}
|
||||
className="object-contain"
|
||||
style={{ width: '36px', height: '36px' }}
|
||||
style={{ width: '50px', height: '50px' }}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<span
|
||||
className="text-white font-extrabold tracking-wide py-3 whitespace-nowrap"
|
||||
style={{ fontSize: '22px' }}
|
||||
style={{ fontSize: categoria.nombre.length > 20 ? '26px' : '30px' }}
|
||||
>
|
||||
{categoria.nombre}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* ─── Contador de preguntas (bajo la barra roja) ─── */}
|
||||
<div className="absolute top-0 right-0 z-10" style={{ marginTop: '72px' }}>
|
||||
<div
|
||||
className="px-4 py-1"
|
||||
style={{ backgroundColor: '#000000', borderRadius: '10px 0px 0px 10px' }}
|
||||
>
|
||||
<span className="text-white font-bold" style={{ fontSize: '26px' }}>
|
||||
{Math.min(estado.preguntasRespondidas + 1, MAX_PREGUNTAS)}/{MAX_PREGUNTAS}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* ─── Contenido principal: Ruleta + Panel de pregunta ─── */}
|
||||
<div className="flex-1 flex items-center justify-center min-h-0">
|
||||
|
||||
@@ -148,10 +164,10 @@ export default function Game({ onEnd }: GameProps) {
|
||||
background: 'linear-gradient(180deg, #ffffff 0%, #d8dcec 100%)',
|
||||
}}
|
||||
>
|
||||
<p className="text-gray-700 font-medium" style={{ fontSize: '22px' }}>
|
||||
<p className="text-gray-700 font-medium" style={{ fontSize: '26px' }}>
|
||||
Gira la ruleta para obtener una pregunta
|
||||
</p>
|
||||
<p className="text-gray-500 mt-2" style={{ fontSize: '22px' }}>
|
||||
<p className="text-gray-500 mt-2" style={{ fontSize: '26px' }}>
|
||||
Cada respuesta correcta te acerca al objetivo de {MAX_PREGUNTAS} preguntas
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -139,11 +139,14 @@ export default function Intro() {
|
||||
|
||||
{/* ─── Columna derecha (40%): texto ─────────── */}
|
||||
<div className="flex flex-col justify-center" style={{ width: '35%', paddingRight: '20px' }}>
|
||||
<div className="relative z-10 w-full bg-transparent rounded-3xl p-10" style={{top: '-10%'}}>
|
||||
<div className="relative z-10 w-full bg-transparent rounded-3xl p-10" style={{ top: 'calc(-10% - 65px)' }}>
|
||||
{/* Texto introductorio */}
|
||||
<p className="text-gray-700 leading-relaxed mb-8" style={{ fontSize: '22px' }}>
|
||||
<p className="text-gray-700 leading-relaxed mb-6" style={{ fontSize: '26px' }}>
|
||||
Hoy vamos a poner a prueba nuestros conocimientos y a reflexionar juntos sobre
|
||||
cómo nuestras acciones impactan el planeta. En esta ruleta tenemos{' '}
|
||||
cómo nuestras acciones impactan el planeta.
|
||||
</p>
|
||||
<p className="text-gray-700 leading-relaxed mb-8" style={{ fontSize: '26px' }}>
|
||||
En esta ruleta tenemos{' '}
|
||||
<strong>4 ejes fundamentales para el futuro:</strong>
|
||||
</p>
|
||||
|
||||
@@ -157,10 +160,13 @@ export default function Intro() {
|
||||
src={`./img/${imagenes[i]}`}
|
||||
alt={cat.nombre}
|
||||
className="shrink-0 object-contain"
|
||||
style={{ width: '75px', height: '78px' }}
|
||||
style={{
|
||||
width: i === 1 ? '48px' : '60px',
|
||||
height: i === 1 ? '50px' : '62px',
|
||||
}}
|
||||
/>
|
||||
<div>
|
||||
<strong className="text-gray-800" style={{ fontSize: '22px' }}>
|
||||
<strong className="text-gray-800" style={{ fontSize: '24px' }}>
|
||||
{cat.nombre}
|
||||
{cat.nombre2 ? ` ${cat.nombre2}` : ''}
|
||||
{cat.nombre3 ? ` ${cat.nombre3}` : ''}
|
||||
|
||||
@@ -45,13 +45,13 @@ export default function Result(_props: ResultProps) {
|
||||
>
|
||||
<p
|
||||
className="text-gray-700 mb-6"
|
||||
style={{ fontSize: '22px', lineHeight: '1.6' }}
|
||||
style={{ fontSize: '28px', lineHeight: '1.6' }}
|
||||
>
|
||||
Recorrimos desde la gestión del agua y los residuos, hasta la transición hacia energías más limpias y la movilidad del futuro. Más allá de las respuestas correctas o los puntos, lo verdaderamente importante es que la sostenibilidad no se queda en esta pantalla: se construye con las decisiones que tomamos todos los días.
|
||||
</p>
|
||||
<p
|
||||
className="text-gray-700"
|
||||
style={{ fontSize: '22px', lineHeight: '1.6', fontWeight: '600' }}
|
||||
style={{ fontSize: '28px', lineHeight: '1.6', fontWeight: '600' }}
|
||||
>
|
||||
Muchísimas gracias a todos por su participación, su energía y sus aportaciones.
|
||||
</p>
|
||||
@@ -77,7 +77,7 @@ export default function Result(_props: ResultProps) {
|
||||
>
|
||||
<p
|
||||
className="font-bold text-white"
|
||||
style={{ fontSize: '22px', lineHeight: '1.5' }}
|
||||
style={{ fontSize: '28px', lineHeight: '1.5' }}
|
||||
>
|
||||
¡Nos llevamos grandes aprendizajes para seguir cuidando el planeta juntos!
|
||||
</p>
|
||||
|
||||
+51
-46
@@ -67,6 +67,17 @@ export function useRuleta() {
|
||||
})
|
||||
|
||||
const rotacionRef = useRef(0)
|
||||
const isSpinningRef = useRef(false)
|
||||
const completadoRef = useRef(false)
|
||||
const usadasRef = useRef<Record<number, number>>({})
|
||||
const poolRef = useRef(poolInicial)
|
||||
const girarRef = useRef<() => void>(() => {})
|
||||
|
||||
useEffect(() => {
|
||||
usadasRef.current = estado.preguntasUsadasPorCategoria
|
||||
poolRef.current = estado.preguntasPool
|
||||
completadoRef.current = estado.completado
|
||||
}, [estado.preguntasUsadasPorCategoria, estado.preguntasPool, estado.completado])
|
||||
|
||||
// ─── Temporizador por pregunta ───────────────────────────────────────────
|
||||
useEffect(() => {
|
||||
@@ -89,6 +100,12 @@ export function useRuleta() {
|
||||
feedbackCorrecto: false,
|
||||
completado,
|
||||
aprobado,
|
||||
preguntasUsadasPorCategoria: {
|
||||
...prev.preguntasUsadasPorCategoria,
|
||||
...(prev.categoriaActual
|
||||
? { [prev.categoriaActual.id]: (prev.preguntasUsadasPorCategoria[prev.categoriaActual.id] ?? 0) + 1 }
|
||||
: {}),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -99,38 +116,29 @@ export function useRuleta() {
|
||||
return () => clearInterval(interval)
|
||||
}, [estado.preguntaActual?.id, estado.mostrarFeedback, estado.completado])
|
||||
|
||||
/**
|
||||
* Busca la siguiente pregunta disponible.
|
||||
* 1. Intenta usar la categoría donde cayó la ruleta (si le quedan preguntas).
|
||||
* 2. Si no, recorre las demás categorías en orden circular.
|
||||
* 3. Retorna null si ya se usaron las 12 preguntas.
|
||||
*/
|
||||
function obtenerSiguientePregunta(
|
||||
catIndice: number,
|
||||
usadasPorCat: Record<number, number>,
|
||||
pool: Record<number, Pregunta[]>,
|
||||
): { pregunta: Pregunta; categoria: Categoria } | null {
|
||||
// Recorrer desde catIndice en círculo
|
||||
for (let vuelta = 0; vuelta < SECCIONES; vuelta++) {
|
||||
const idx = (catIndice + vuelta) % SECCIONES
|
||||
const cat = categorias[idx]
|
||||
const usadas = usadasPorCat[cat.id] ?? 0
|
||||
const preguntasCat = pool[cat.id] ?? []
|
||||
if (usadas < preguntasCat.length) {
|
||||
return { pregunta: preguntasCat[usadas], categoria: cat }
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
function girar() {
|
||||
if (estado.isSpinning || estado.completado) return
|
||||
if (isSpinningRef.current || completadoRef.current) return
|
||||
|
||||
const hayDisponibles = categorias.some((cat) => {
|
||||
const usadas = usadasRef.current[cat.id] ?? 0
|
||||
return usadas < (poolRef.current[cat.id]?.length ?? 0)
|
||||
})
|
||||
if (!hayDisponibles) {
|
||||
setEstado((prev) => ({
|
||||
...prev,
|
||||
completado: true,
|
||||
aprobado: prev.correctas >= MIN_CORRECTAS,
|
||||
}))
|
||||
return
|
||||
}
|
||||
|
||||
isSpinningRef.current = true
|
||||
sounds.wheelSpin?.play()
|
||||
|
||||
const vueltas = 3 + Math.random() * 2
|
||||
const anguloFinal = Math.random() * 360
|
||||
const rotacionTotal = rotacionRef.current + vueltas * 360 + anguloFinal
|
||||
const anguloExtra = Math.random() * 360
|
||||
const delta = vueltas * 360 + anguloExtra
|
||||
const rotacionTotal = rotacionRef.current + delta
|
||||
rotacionRef.current = rotacionTotal
|
||||
|
||||
setEstado((prev) => ({ ...prev, isSpinning: true, rotacion: rotacionTotal }))
|
||||
@@ -138,33 +146,29 @@ export function useRuleta() {
|
||||
setTimeout(() => {
|
||||
const anguloNormalizado = ((360 - (rotacionRef.current % 360)) + 360) % 360
|
||||
const indiceCategoria = Math.floor(anguloNormalizado / ANGULO_SECCION) % SECCIONES
|
||||
const cat = categorias[indiceCategoria]
|
||||
const usadas = usadasRef.current[cat.id] ?? 0
|
||||
const preguntasCat = poolRef.current[cat.id] ?? []
|
||||
|
||||
setEstado((prev) => {
|
||||
const resultado = obtenerSiguientePregunta(
|
||||
indiceCategoria,
|
||||
prev.preguntasUsadasPorCategoria,
|
||||
prev.preguntasPool,
|
||||
)
|
||||
|
||||
if (!resultado) {
|
||||
// Ya no hay preguntas — marcar como completado
|
||||
return {
|
||||
...prev,
|
||||
completado: true,
|
||||
aprobado: prev.correctas >= MIN_CORRECTAS,
|
||||
}
|
||||
// Categoría sin preguntas → re-girar
|
||||
if (usadas >= preguntasCat.length) {
|
||||
isSpinningRef.current = false
|
||||
setEstado((prev) => ({ ...prev, isSpinning: false }))
|
||||
setTimeout(() => girarRef.current(), 600)
|
||||
return
|
||||
}
|
||||
|
||||
return {
|
||||
setEstado((prev) => ({
|
||||
...prev,
|
||||
preguntaActual: resultado.pregunta,
|
||||
categoriaActual: resultado.categoria,
|
||||
preguntaActual: preguntasCat[usadas],
|
||||
categoriaActual: cat,
|
||||
tiempoRestante: TIEMPO_LIMITE,
|
||||
}
|
||||
})
|
||||
}))
|
||||
}, 4000)
|
||||
}
|
||||
|
||||
girarRef.current = girar
|
||||
|
||||
function responder(indiceOpcion: number) {
|
||||
if (!estado.preguntaActual || !estado.categoriaActual) return
|
||||
const esCorrecta = indiceOpcion === estado.preguntaActual.correcta
|
||||
@@ -194,6 +198,7 @@ export function useRuleta() {
|
||||
}
|
||||
|
||||
function cerrarFeedback() {
|
||||
isSpinningRef.current = false
|
||||
setEstado((prev) => ({
|
||||
...prev,
|
||||
preguntaActual: null,
|
||||
|
||||
Reference in New Issue
Block a user