feat: Initial commit - Ruleta con PIN (credenciales sanitizadas)

This commit is contained in:
Isaac_Aracena
2026-05-09 11:56:04 -04:00
commit 19f01c4c54
18 changed files with 1008 additions and 0 deletions
+27
View File
@@ -0,0 +1,27 @@
# ─── Credenciales y configuración local ───
config.php
.env
# ─── Sistema operativo ───
Thumbs.db
.DS_Store
desktop.ini
# ─── IDEs y editores ───
.vscode/
.idea/
*.swp
*.swo
*~
# ─── PHP ───
vendor/
composer.lock
# ─── Archivos temporales ───
*.log
*.tmp
*.bak
# ─── ZIP original del proyecto ───
*.zip
+232
View File
@@ -0,0 +1,232 @@
# 🎰 Ruleta con PIN
Aplicación web de **ruleta de premios** protegida por PIN, con registro de resultados vía backend PHP y base de datos MySQL/MariaDB.
Desarrollada para campañas promocionales de **Gomez Lee Marketing (GLM)**.
---
## 📋 Tabla de Contenidos
- [Características](#-características)
- [Requisitos](#-requisitos)
- [Instalación](#-instalación)
- [Configuración de Base de Datos](#-configuración-de-base-de-datos)
- [Configuración de PINs](#-configuración-de-pins)
- [Estructura del Proyecto](#-estructura-del-proyecto)
- [Flujo del Sistema](#-flujo-del-sistema)
- [Operación Diaria](#-operación-diaria)
- [Solución de Problemas](#-solución-de-problemas)
---
## ✨ Características
- **Autenticación por PIN** antes de girar la ruleta
- **Selección aleatoria ponderada** de premios según probabilidades configurables
- **Control de stock** por premio y cupo global de giros
- **Registro de auditoría** con IP, user agent y timestamp
- **Animación fluida** de ruleta con CSS transitions
- **Efecto confeti** al ganar (canvas-confetti)
- **Diseño responsive** con soporte de pantalla completa
- **Redirección a formulario JotForm** para reclamar el premio
---
## 📦 Requisitos
| Componente | Versión mínima |
|---|---|
| PHP | 8.0+ con extensión `mysqli` |
| MySQL / MariaDB | 5.7+ / 10.3+ |
| Servidor Web | Apache (XAMPP) o Nginx |
| Navegador | Cualquier navegador moderno con JS habilitado |
> La aplicación requiere acceso HTTP saliente a `https://api.ipify.org` para obtener la IP pública del usuario.
---
## 🚀 Instalación
### 1. Clonar el repositorio
```bash
git clone https://git.digitalcompass.agency/Isaac_Aracena/ruleta-con-pin.git
```
### 2. Colocar en el servidor web
- **Windows (XAMPP):** Copiar a `C:\xampp\htdocs\ruleta\`
- **Linux (Apache/Nginx):** Copiar a `/var/www/ruleta/`
### 3. Configurar credenciales de BD
```bash
cp config.example.php config.php
```
Editar `config.php` con los datos reales:
```php
$servername = "localhost";
$username = "tu_usuario";
$password = "tu_contraseña";
$dbname = "ruleta_db";
```
> ⚠️ **`config.php` está en `.gitignore`** y nunca se versiona. Las credenciales quedan solo en tu servidor.
### 4. Crear la base de datos
Ejecutar el script SQL incluido:
```bash
mysql -u root -p < database/schema.sql
```
### 5. Cargar premios y PINs
Ver sección [Configuración de PINs](#-configuración-de-pins).
### 6. Probar
Abrir `http://localhost/ruleta/index.html` e ingresar un PIN válido.
---
## 🗄️ Configuración de Base de Datos
### Tablas
| Tabla | Descripción |
|---|---|
| `girospermitidos` | Control global de cupo de giros (1 registro, id=1) |
| `premiosnew` | Premios con stock, probabilidad y PIN asignado |
| `premiosentregados` | Auditoría de premios entregados |
### Ajustar cupo de giros
```sql
UPDATE girospermitidos SET cantidad = 50000 WHERE id = 1;
```
### Cargar un premio
```sql
INSERT INTO premiosnew (nombre, probabilidad, cantidad, inicio, final, nombre_usuario)
VALUES ('Gorra', 1.0, 10, 0, 45, '1989');
```
- `inicio` / `final` = ángulos en la imagen de la ruleta (0360°)
- `nombre_usuario` = el PIN que podrá ganar este premio
- `cantidad` = stock disponible
---
## 🔑 Configuración de PINs
Los PINs deben estar sincronizados en **dos lugares**:
1. **Base de datos:** columna `premiosnew.nombre_usuario`
2. **Frontend:** array `validPins` en `script.js` (línea 35)
```javascript
const validPins = ["1989", "4708", "6823", "7591", ...];
```
> Si un PIN está en la BD pero no en `validPins`, el modal del frontend no dejará pasar.
> Si un PIN está en `validPins` pero no en la BD, el backend responderá "No hay premios disponibles".
---
## 📁 Estructura del Proyecto
```
ruleta-con-pin/
├── index.html # Página principal (modal PIN + ruleta)
├── script.js # Lógica del juego, validación, animación
├── style.css # Estilos, responsive, modales
├── seleccionar_premio.php # Backend: selección aleatoria + registro BD
├── config.example.php # Plantilla de configuración de BD
├── config.php # ⛔ Credenciales reales (no versionado)
├── database/
│ └── schema.sql # Script de creación de BD y tablas
└── img/
├── background.png # Fondo de la página
├── ruleta.png # Imagen de la ruleta
├── puntero.png # Flecha/puntero de la ruleta
├── fullscreen-icon.png
├── BANNER-1.png # Logo GLM
├── BANNER-2.png # Banner lateral
└── BANNER-4.png # Banner inferior
```
---
## 🔄 Flujo del Sistema
```
Usuario → Ingresa PIN → Validación frontend (validPins)
→ Muestra ruleta → Click en puntero → Obtiene IP (ipify)
→ POST a seleccionar_premio.php
→ Verifica cupo global (girospermitidos)
→ Selecciona premio aleatorio ponderado (premiosnew)
→ Descuenta stock + registra en premiosentregados
→ Retorna JSON {id, nombre, inicio, final}
→ Anima ruleta al ángulo del premio
→ Confeti 🎊 + Modal "¡Ganaste!"
→ Botón "Ir al formulario" → JotForm
```
---
## 📊 Operación Diaria
### Agregar PINs/premios
1. Insertar en `premiosnew` con el PIN deseado
2. Agregar el PIN al array `validPins` en `script.js`
### Consultar premios entregados
```sql
SELECT * FROM premiosentregados ORDER BY created_at DESC;
```
### Consultar stock restante
```sql
SELECT nombre, nombre_usuario, cantidad FROM premiosnew;
```
### Aumentar cupo de giros
```sql
UPDATE girospermitidos SET cantidad = cantidad + 1000 WHERE id = 1;
```
---
## 🔧 Solución de Problemas
| Problema | Causa |
|---|---|
| "Conexión fallida…" | Credenciales en `config.php` incorrectas o BD inaccesible |
| "No hay premios disponibles" | No hay fila en `premiosnew` con ese PIN y `cantidad > 0` |
| No pasa del modal de PIN | El PIN no está en `validPins` en `script.js` |
| No registra IP | Navegador no puede acceder a `https://api.ipify.org` |
| "Se ha alcanzado el límite de giros" | `girospermitidos.cantidad` llegó a 0 |
---
## 🛡️ Notas de Seguridad
- Las credenciales de BD se cargan desde `config.php` (no versionado)
- Los `INSERT` usan **prepared statements** para prevenir SQL injection
- La validación de PINs en el frontend es solo UX; la validación real ocurre en el backend vía `nombre_usuario` en la consulta SQL
---
## 📄 Licencia
Proyecto interno de **Gomez Lee Marketing**. Uso privado.
+9
View File
@@ -0,0 +1,9 @@
<?php
// ─── Configuración de Base de Datos ───
// Copia este archivo como "config.php" y completa los valores reales.
// config.php está excluido de git (.gitignore) para proteger credenciales.
$servername = "localhost"; // Host de la BD (IP o dominio)
$username = "tu_usuario"; // Usuario de MySQL
$password = "tu_contraseña"; // Contraseña de MySQL
$dbname = "ruleta_db"; // Nombre de la base de datos
+54
View File
@@ -0,0 +1,54 @@
-- =========================================================
-- RuletaFullConPIN — Esquema de Base de Datos
-- =========================================================
-- Ejecutar este script en MySQL/MariaDB para crear la
-- estructura necesaria antes de usar la aplicación.
-- =========================================================
CREATE DATABASE IF NOT EXISTS ruleta_db
CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
USE ruleta_db;
-- ---------------------------------------------------------
-- 1. Control de giros global (un único registro, id = 1)
-- ---------------------------------------------------------
CREATE TABLE IF NOT EXISTS girospermitidos (
id INT PRIMARY KEY,
cantidad INT NOT NULL
);
INSERT INTO girospermitidos (id, cantidad)
VALUES (1, 100000) -- Ajusta el cupo según la campaña
ON DUPLICATE KEY UPDATE cantidad = VALUES(cantidad);
-- ---------------------------------------------------------
-- 2. Premios disponibles (vinculados a un PIN / usuario)
-- ---------------------------------------------------------
CREATE TABLE IF NOT EXISTS premiosnew (
id INT AUTO_INCREMENT PRIMARY KEY,
nombre VARCHAR(190) NOT NULL, -- Nombre del premio a mostrar
probabilidad DECIMAL(10,6) NOT NULL, -- Peso relativo (0..1 o cualquier peso)
cantidad INT NOT NULL, -- Stock disponible
inicio INT NOT NULL, -- Ángulo inicial en la ruleta (0-360)
final INT NOT NULL, -- Ángulo final en la ruleta (0-360)
nombre_usuario VARCHAR(64) NOT NULL -- PIN que puede ganar este premio
);
-- ---------------------------------------------------------
-- 3. Registro de premios entregados (auditoría)
-- ---------------------------------------------------------
CREATE TABLE IF NOT EXISTS premiosentregados (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
nombre_usuario VARCHAR(64) NOT NULL, -- PIN usado
premio VARCHAR(190) NOT NULL, -- Nombre del premio ganado
ip_address VARCHAR(64) NULL,
user_agent VARCHAR(255) NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- ---------------------------------------------------------
-- Ejemplo: Cargar un premio para el PIN "1989"
-- ---------------------------------------------------------
-- INSERT INTO premiosnew (nombre, probabilidad, cantidad, inicio, final, nombre_usuario)
-- VALUES ('Gorra', 1.0, 10, 0, 45, '1989');
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 61 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 102 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.2 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 99 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 106 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 135 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

+60
View File
@@ -0,0 +1,60 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>GLM - Ruleta</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<!-- Banners en las posiciones 1, 2, 3 y 4 -->
<a href="https://promo.gomezleemarketing.com/" target="_blank" class="banner banner-1"></a>
<div class="banner banner-2"></div>
<div class="banner banner-3"></div>
<div class="banner banner-4"></div>
<!-- Contenido principal -->
<div class="content">
<h1></h1>
<div class="game">
<img id="ruleta" src="img/ruleta.png"/>
<img id="puntero" src="img/puntero.png"/>
</div>
</div>
<!-- Botón de pantalla completa -->
<div class="fullscreen-btn" id="fullscreen-btn"></div>
<!-- Mensaje de orientaci¨®n vertical -->
<div class="landscape-message">
Por favor, gira tu dispositivo para ver el contenido en horizontal.
</div>
<!-- Pop-up modal -->
<div id="popup-modal" class="popup-modal">
<div class="popup-content">
<h2>Favor de ingresar su PIN</h2>
<form id="user-form">
<label for="user-name">PIN:</label>
<input type="text" id="user-name" name="user-name" required>
<button type="submit">Continuar</button>
</form>
</div>
</div>
<!-- Nuevo popup modal de agradecimiento -->
<div id="thank-you-modal" class="popup-modal" style="display: none;">
<div class="popup-content">
<h2 id="thank-you-message"></h2>
<p id="thank-you-details"></p>
<button id="redirect-button">Ir al formulario</button>
</div>
</div>
<!-- Script -->
<script src="https://cdn.jsdelivr.net/npm/canvas-confetti@1.5.1/dist/confetti.browser.min.js"></script>
<script src="script.js"></script>
</body>
</html>
+216
View File
@@ -0,0 +1,216 @@
var ruleta = document.getElementById("ruleta");
var puntero = document.getElementById("puntero");
let gira = 0;
let giraDos = 0;
const rangos = [
{ articulo: "Freidora de aire", inicio: 330, final: 16 },
{ articulo: "Articulo plastico", inicio: 15, final: 16 },
{ articulo: "Sandwichera", inicio: 150, final: 196 },
{ articulo: "Kits Protex", inicio: 170, final: 16 },
{ articulo: "Cubeta", inicio: 20, final: 52 },
{ articulo: "Lonchera", inicio: 42, final: 88 },
{ articulo: "Recipiente plastico", inicio: 130, final: 160 },
{ articulo: "Kits Cuidado Oral", inicio: 220, final: 232 },
{ articulo: "Plancha", inicio: 150, final: 232 },
{ articulo: "Caldero", inicio: 30, final: 194 },
{ articulo: "Abanico de piso", inicio: 296, final: 328 },
];
let articuloGanador = "";
// Variables para el modal y formulario
const modal = document.getElementById("popup-modal");
const form = document.getElementById("user-form");
const userNameInput = document.getElementById("user-name");
let nombreUsuario = "";
// Mostrar el modal al cargar la página
window.onload = function () {
modal.style.display = "flex";
};
// Lista de PINs válidos
const validPins = ["1989", "4708", "6823", "7591", "3664", "3520", "4528", "9033", "3688", "6159", "2258"];
// Manejar el envío del formulario
form.addEventListener("submit", function (e) {
e.preventDefault();
nombreUsuario = userNameInput.value.trim(); // Eliminar espacios en blanco
if (!nombreUsuario) {
alert("Por favor, completa todos los campos.");
return;
}
// Validar si el PIN es válido
if (!validPins.includes(nombreUsuario)) {
alert("PIN inválido. Por favor, inténtelo de nuevo.");
return;
}
// Si el PIN es válido, ocultar el modal y continuar
modal.style.display = "none";
document.querySelector(".content").style.display = "block"; // Mostrar el contenido principal
});
// Calcular el ángulo intermedio de un rango
function calcularAngulo(rango) {
if (rango.inicio > rango.final) {
return (rango.inicio + (360 - rango.inicio + rango.final) / 2) % 360;
}
return rango.inicio + (rango.final - rango.inicio) / 2;
}
// Girar la ruleta a un artículo específico
function girarRuletaAArticulo(nombreArticulo) {
let rangoSeleccionado = rangos.find(
(rango) => rango.articulo === nombreArticulo
);
if (rangoSeleccionado) {
let anguloFinal = calcularAngulo(rangoSeleccionado);
gira = 1440 + anguloFinal; // 1440 asegura varias vueltas antes de detenerse en el artículo ganador
ruleta.style.transition = "all 5s ease-out";
ruleta.style.transform = `rotate(${gira}deg)`;
} else {
console.log("Artículo no encontrado.");
}
}
// Fin de la rodada
ruleta.addEventListener("transitionend", function () {
puntero.style.pointerEvents = "auto"; // Reactivar clics
ruleta.style.transition = "none";
giraDos = gira % 360;
ruleta.style.transform = `rotate(${giraDos}deg)`;
mostrarConfetiConTexto(articuloGanador);
});
// Obtener dirección IP
async function obtenerIP() {
const response = await fetch("https://api.ipify.org?format=json");
const data = await response.json();
return data.ip;
}
// Obtener artículo ganador
async function obtenerArticuloGanador() {
const userAgent = navigator.userAgent;
const ipAddress = await obtenerIP();
fetch("seleccionar_premio.php", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
user_agent: userAgent,
ip_address: ipAddress,
nombre_usuario: nombreUsuario,
}),
})
.then((response) => response.json())
.then((data) => {
articuloGanador = data.nombre;
girarRuletaAArticulo(articuloGanador);
})
.catch((error) =>
console.error("Error al obtener el artículo ganador:", error)
);
}
// Bloquear interacción con la ruleta mientras gira
puntero.addEventListener("mousedown", function () {
if (!nombreUsuario) {
alert("Por favor, completa el formulario antes de continuar.");
return;
}
puntero.style.pointerEvents = "none"; // Bloquear clics
obtenerArticuloGanador();
});
// Mostrar confeti con texto
function mostrarConfetiConTexto(texto) {
const duration = 5 * 1000;
const end = Date.now() + duration;
(function frame() {
confetti({
particleCount: 7,
angle: 60,
spread: 55,
origin: { x: 0 },
scalar: 1.2,
});
confetti({
particleCount: 7,
angle: 120,
spread: 55,
origin: { x: 1 },
scalar: 1.2,
});
if (Date.now() < end) {
requestAnimationFrame(frame);
}
})();
console.log("¡Felicidades! Ganaste: " + texto);
}
const fullscreenBtn = document.getElementById("fullscreen-btn");
fullscreenBtn.addEventListener("click", () => {
if (!document.fullscreenElement) {
document.documentElement.requestFullscreen().catch((err) => {
console.error(
`Error al intentar entrar en pantalla completa: ${err.message}`
);
});
} else {
document.exitFullscreen().catch((err) => {
console.error(
`Error al intentar salir de pantalla completa: ${err.message}`
);
});
}
});
// Mostrar el popup de agradecimiento
function mostrarMensajeAgradecimiento() {
const modal = document.getElementById("thank-you-modal");
const mensaje = document.getElementById("thank-you-message");
const detalles = document.getElementById("thank-you-details");
const botonRedirigir = document.getElementById("redirect-button");
// Personalizar contenido del mensaje
mensaje.textContent = `🎊🎉¡Felicidades!🎉🎊`;
detalles.textContent = `🎁 Has ganado "${articuloGanador}" 🎁`;
modal.style.display = "flex"; // Mostrar el modal
botonRedirigir.addEventListener("click", () => {
// Abre el enlace en una nueva pestaña
window.open("https://form.jotform.com/250134957677669", "_blank");
// Actualiza la pestaña actual
window.location.href = window.location.href; // Redirige a la misma URL actual
});
}
// Fin de la rodada
ruleta.addEventListener("transitionend", function () {
puntero.style.pointerEvents = "auto"; // Reactivar clics
ruleta.style.transition = "none";
giraDos = gira % 360;
ruleta.style.transform = `rotate(${giraDos}deg)`;
mostrarConfetiConTexto(articuloGanador); // Mostrar confeti
mostrarMensajeAgradecimiento(); // Mostrar mensaje de agradecimiento
});
+128
View File
@@ -0,0 +1,128 @@
<?php
// ─── Configuración de Base de Datos ───
// Carga las credenciales desde config.php (no versionado en git).
// Si no existe, usa valores por defecto para desarrollo local.
if (file_exists(__DIR__ . '/config.php')) {
require_once __DIR__ . '/config.php';
} else {
$servername = "localhost";
$username = "root";
$password = "";
$dbname = "ruleta_db";
}
// Crear la conexión
$conn = new mysqli($servername, $username, $password, $dbname);
// Verificar la conexión
if ($conn->connect_error) {
die(json_encode(["error" => "Conexión fallida: " . $conn->connect_error]));
}
// Obtener la IP del cliente en el servidor
$ip_address = $_SERVER['REMOTE_ADDR'];
// Recibir los datos enviados desde el frontend
$data = json_decode(file_get_contents("php://input"), true);
$user_agent = $data['user_agent'] ?? null;
$nombre_usuario = $data['nombre_usuario'] ?? null;
// Consultar la cantidad de giros disponibles en la tabla limite_giros
$sqlLimite = "SELECT cantidad FROM girospermitidos WHERE id = 1"; // Se asume un único registro de control
$resultLimite = $conn->query($sqlLimite);
if ($resultLimite->num_rows > 0) {
$rowLimite = $resultLimite->fetch_assoc();
$cantidadGirosDisponibles = $rowLimite['cantidad'];
if ($cantidadGirosDisponibles <= 0) {
echo json_encode(["error" => "Se ha alcanzado el límite de giros"]);
$conn->close();
exit();
}
// Restar un giro al contador global
$stmtGiros = $conn->prepare("UPDATE girospermitidos SET cantidad = cantidad - 1 WHERE id = 1");
if ($stmtGiros) {
$stmtGiros->execute();
$stmtGiros->close();
} else {
echo json_encode(["error" => "Error al actualizar el límite de giros"]);
$conn->close();
exit();
}
} else {
echo json_encode(["error" => "No se encontró el registro de límite de giros"]);
$conn->close();
exit();
}
// Consultar los premios con cantidad mayor que 0
$sql = "SELECT id, nombre, probabilidad, cantidad, inicio, final FROM premiosnew WHERE cantidad > 0 AND nombre_usuario = '$nombre_usuario'";
$result = $conn->query($sql);
$premios = array();
$totalProbabilidad = 0;
if ($result->num_rows > 0) {
// Calcular el total de probabilidades
while ($row = $result->fetch_assoc()) {
$premios[] = $row;
$totalProbabilidad += $row['probabilidad'];
}
// Generar un número aleatorio entre 0 y el total de probabilidades
$random = mt_rand(0, $totalProbabilidad * 1000) / 1000; // Se multiplica por 1000 para trabajar con 3 decimales
$suma = 0;
// Seleccionar el premio basado en la probabilidad acumulada
foreach ($premios as $premio) {
$suma += $premio['probabilidad'];
if ($random <= $suma) {
// Actualizar la cantidad del premio seleccionado
$stmt = $conn->prepare("UPDATE premiosnew SET cantidad = cantidad - 1 WHERE id = ?");
if ($stmt) {
$stmt->bind_param("i", $premio['id']);
$stmt->execute();
$stmt->close();
// Agregar el premio y nombre a la tabla premios
$stmtInsert = $conn->prepare(
"INSERT INTO premiosentregados (nombre_usuario, premio, ip_address, user_agent)
VALUES (?, ?, ?, ?)"
);
if ($stmtInsert) {
$stmtInsert->bind_param(
"ssss",
$nombre_usuario,
$premio['nombre'],
$ip_address,
$user_agent
);
$stmtInsert->execute();
$stmtInsert->close();
} else {
echo json_encode(["error" => "Error al guardar el participante"]);
exit();
}
// Devolver el premio seleccionado con sus rangos de ángulo
echo json_encode([
'id' => $premio['id'],
'nombre' => $premio['nombre'],
'inicio' => $premio['inicio'],
'final' => $premio['final']
]);
exit(); // Salir inmediatamente después de devolver el premio seleccionado
} else {
echo json_encode(["error" => "Error en la preparación de la actualización"]);
}
break;
}
}
} else {
echo json_encode(["error" => "No hay premios disponibles"]);
}
$conn->close();
?>
+282
View File
@@ -0,0 +1,282 @@
/* Estilos generales existentes */
body, html {
margin: 0;
padding: 0;
width: 100%;
height: 100%;
background: #C10F1A url(img/background.png) repeat center center; /* Imagen centrada y no repetida */
background-size: cover; /* Hace que la imagen se ajuste al contenedor sin recortarse */
display: flex;
justify-content: center;
align-items: center;
font-family: 'Montserrat', sans-serif;
overflow: hidden; /* Evitar desplazamiento */
}
.content {
text-align: center;
font-weight: bold;
font-size: 48px;
color: #ffffff;
text-shadow:
4px 4px 0 #C10F1A;
}
.game {
position: relative;
display: inline-block;
margin-left: -400px;
}
#ruleta {
max-width: 100%;
height: auto;
}
#puntero {
position: absolute;
top: 47%;
left: 50%;
width: 300px;
transform: translate(-50%, -50%);
}
/* Estilos para dispositivos con pantallas más pequeñas */
@media (max-width: 1024px) {
#ruleta {
width: 70%;
}
#puntero {
width: 200px;
}
}
@media (max-width: 768px) {
#ruleta, #puntero {
width: 50%;
}
}
@media (max-width: 480px) {
#ruleta, #puntero {
width: 80%;
}
#puntero {
width: 100px;
}
}
/* Nueva clase para manejar orientación vertical */
.landscape-message {
display: none;
text-align: center;
font-size: 24px;
color: red;
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background-color: #ffffff;
padding: 20px;
border: 2px solid red;
z-index: 9999;
}
@media (orientation: portrait) {
.landscape-message {
display: block !important;
z-index: 100000;
}
#ruleta, #puntero, .content, .game {
display: none !important;
}
}
.fullscreen-btn {
position: absolute;
top: 5%;
right: 3%;
width: 40px;
height: 40px;
background: url('img/fullscreen-icon.png') no-repeat center center;
background-size: contain;
cursor: pointer;
z-index: 1000;
}
@media (max-width: 1024px) {
.fullscreen-btn {
top: 3%;
right: 5%;
width: 35px;
height: 35px;
}
}
@media (max-width: 768px) {
.fullscreen-btn {
top: 3%;
right: 5%;
width: 30px;
height: 30px;
}
}
@media (max-width: 480px) {
.fullscreen-btn {
top: 2%;
right: 8%;
width: 25px;
height: 25px;
}
}
canvas {
position: fixed !important;
top: 0;
left: 0;
width: 100% !important;
height: 100% !important;
z-index: 99999 !important; /* Mayor que cualquier otro elemento */
pointer-events: none; /* No bloquear interacciones */
}
/* Estilos de los banners */
.banner {
position: fixed;
background-size: contain;
background-repeat: no-repeat;
z-index: 1000;
}
.banner-1 {
top: 0%;
left: 5%;
width: 150px;
height: 150px;
background-image: url('img/BANNER-1.png');
}
.banner-4 {
bottom: 0%;
right: 5%;
width: 400px;
height: 100px;
background-image: url('img/BANNER-4.png');
}
.banner-2 {
top: 10%;
right: 5%;
width: 400px;
height: 400px;
background-image: url('img/BANNER-2.png');
}
.popup-modal {
display: none;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.8);
justify-content: center;
align-items: center;
z-index: 1000;
}
.popup-content {
background: #C10F1A;
padding: 20px;
border-radius: 8px;
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2);
max-width: 400px;
width: 90%;
text-align: center;
animation: fadeIn 0.3s ease-out;
}
.popup-content h2 {
margin-bottom: 15px;
font-size: 24px;
color: #ccc;
}
#user-form label {
display: block;
margin-bottom: 8px;
font-weight: bold;
color: #ccc;
}
#user-form input, #user-form select, #user-form button {
width: 50%;
padding: 10px;
margin-bottom: 15px;
border: 1px solid #ccc;
border-radius: 4px;
font-size: 16px;
font-family: 'Montserrat', sans-serif;
}
#user-form button {
background-color: #007BFF;
color: white;
font-weight: bold;
cursor: pointer;
}
#user-form button:hover {
background-color: #0056b3;
}
@keyframes fadeIn {
from {
opacity: 0;
transform: scale(0.9);
}
to {
opacity: 1;
transform: scale(1);
}
}
/*PopUp Agradecimiento*/
#thank-you-modal {
display: none;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.8);
justify-content: center;
align-items: center;
z-index: 1000;
color: #ffffff;
}
#thank-you-modal .popup-content {
background: #C10F1A;
padding: 20px;
border-radius: 8px;
text-align: center;
}
#redirect-button {
padding: 10px 20px;
background-color: #007bff;
color: white;
border: none;
border-radius: 5px;
cursor: pointer;
}
#redirect-button:hover {
background-color: #0056b3;
}