O temporizador de 3 minutos garante que o usuário

This commit is contained in:
Flatlogic Bot 2026-02-14 01:45:08 +00:00
parent 3d007ff9d6
commit b4902481dc
44 changed files with 1389 additions and 209 deletions

Binary file not shown.

Binary file not shown.

View File

@ -180,3 +180,7 @@ if EMAIL_USE_SSL:
# https://docs.djangoproject.com/en/5.2/ref/settings/#default-auto-field
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
MEDIA_URL = '/media/'
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')

View File

@ -1,29 +1,13 @@
"""
URL configuration for config project.
The `urlpatterns` list routes URLs to views. For more information please see:
https://docs.djangoproject.com/en/5.2/topics/http/urls/
Examples:
Function views
1. Add an import: from my_app import views
2. Add a URL to urlpatterns: path('', views.home, name='home')
Class-based views
1. Add an import: from other_app.views import Home
2. Add a URL to urlpatterns: path('', Home.as_view(), name='home')
Including another URLconf
1. Import the include() function: from django.urls import include, path
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
"""
from django.contrib import admin
from django.urls import include, path
from django.conf import settings
from django.conf.urls.static import static
urlpatterns = [
path("admin/", admin.site.urls),
# path("admin/", admin.site.urls), # User requested to remove standard login
path("", include("core.urls")),
]
if settings.DEBUG:
urlpatterns += static("/assets/", document_root=settings.BASE_DIR / "assets")
urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)

View File

@ -0,0 +1,28 @@
# Generated by Django 5.2.7 on 2026-02-13 21:42
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = [
]
operations = [
migrations.CreateModel(
name='GameProject',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('title', models.CharField(max_length=255)),
('description', models.TextField(blank=True)),
('prompt', models.TextField()),
('genre', models.CharField(choices=[('platformer', 'Platformer'), ('shooter', 'Top-Down Shooter'), ('runner', 'Endless Runner'), ('puzzle', 'Puzzle')], default='platformer', max_length=50)),
('image_reference', models.ImageField(blank=True, null=True, upload_to='game_references/')),
('config_json', models.JSONField(blank=True, default=dict)),
('created_at', models.DateTimeField(auto_now_add=True)),
('updated_at', models.DateTimeField(auto_now=True)),
],
),
]

View File

@ -0,0 +1,59 @@
# Generated by Django 5.2.7 on 2026-02-13 22:23
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('core', '0001_initial'),
]
operations = [
migrations.CreateModel(
name='AdminConfig',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('private_key', models.CharField(max_length=255, unique=True)),
('created_at', models.DateTimeField(auto_now_add=True)),
],
),
migrations.CreateModel(
name='RentalOption',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('title', models.CharField(max_length=100)),
('description', models.TextField(blank=True)),
('duration_days', models.IntegerField(default=1)),
('price', models.DecimalField(decimal_places=2, max_digits=10)),
('qr_code_1', models.ImageField(blank=True, null=True, upload_to='qr_codes/')),
('qr_code_2', models.ImageField(blank=True, null=True, upload_to='qr_codes/')),
],
),
migrations.CreateModel(
name='UserSession',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('access_code', models.CharField(max_length=6, unique=True)),
('phone_number', models.CharField(blank=True, max_length=20, null=True)),
('created_at', models.DateTimeField(auto_now_add=True)),
],
),
migrations.AddField(
model_name='gameproject',
name='is_active',
field=models.BooleanField(default=True),
),
migrations.CreateModel(
name='UserPurchase',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('purchased_at', models.DateTimeField(auto_now_add=True)),
('expires_at', models.DateTimeField()),
('game', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='core.gameproject')),
('rental_option', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='core.rentaloption')),
('user_session', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='core.usersession')),
],
),
]

View File

@ -0,0 +1,33 @@
# Generated by Django 5.2.7 on 2026-02-14 01:00
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('core', '0002_adminconfig_rentaloption_usersession_and_more'),
]
operations = [
migrations.AddField(
model_name='gameproject',
name='script_code',
field=models.TextField(blank=True, help_text='HTML/JS/CSS code for the game'),
),
migrations.AddField(
model_name='userpurchase',
name='confirmation_code',
field=models.CharField(blank=True, max_length=12, null=True),
),
migrations.AddField(
model_name='userpurchase',
name='is_confirmed',
field=models.BooleanField(default=False),
),
migrations.AlterField(
model_name='gameproject',
name='genre',
field=models.CharField(choices=[('platformer', 'Platformer'), ('shooter', 'Top-Down Shooter'), ('runner', 'Endless Runner'), ('puzzle', 'Puzzle'), ('traditional', 'Traditional Script')], default='platformer', max_length=50),
),
]

View File

@ -1,3 +1,67 @@
from django.db import models
from django.utils import timezone
import datetime
# Create your models here.
class AdminConfig(models.Model):
private_key = models.CharField(max_length=255, unique=True)
created_at = models.DateTimeField(auto_now_add=True)
def __str__(self):
return "Admin Configuration"
class UserSession(models.Model):
access_code = models.CharField(max_length=6, unique=True)
phone_number = models.CharField(max_length=20, blank=True, null=True)
created_at = models.DateTimeField(auto_now_add=True)
def __str__(self):
return f"User {self.access_code}"
class GameProject(models.Model):
GENRE_CHOICES = [
('platformer', 'Platformer'),
('shooter', 'Top-Down Shooter'),
('runner', 'Endless Runner'),
('puzzle', 'Puzzle'),
('traditional', 'Traditional Script'),
]
title = models.CharField(max_length=255)
description = models.TextField(blank=True)
prompt = models.TextField()
genre = models.CharField(max_length=50, choices=GENRE_CHOICES, default='platformer')
image_reference = models.ImageField(upload_to='game_references/', null=True, blank=True)
config_json = models.JSONField(default=dict, blank=True)
script_code = models.TextField(blank=True, help_text="HTML/JS/CSS code for the game")
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
is_active = models.BooleanField(default=True)
def __str__(self):
return self.title
class RentalOption(models.Model):
title = models.CharField(max_length=100)
description = models.TextField(blank=True)
duration_days = models.IntegerField(default=1)
price = models.DecimalField(max_digits=10, decimal_places=2)
qr_code_1 = models.ImageField(upload_to='qr_codes/', blank=True, null=True)
qr_code_2 = models.ImageField(upload_to='qr_codes/', blank=True, null=True)
def __str__(self):
return f"{self.title} - {self.price}"
class UserPurchase(models.Model):
user_session = models.ForeignKey(UserSession, on_delete=models.CASCADE)
game = models.ForeignKey(GameProject, on_delete=models.CASCADE)
rental_option = models.ForeignKey(RentalOption, on_delete=models.CASCADE)
confirmation_code = models.CharField(max_length=12, blank=True, null=True)
purchased_at = models.DateTimeField(auto_now_add=True)
expires_at = models.DateTimeField()
is_confirmed = models.BooleanField(default=False)
def is_valid(self):
return self.is_confirmed and timezone.now() < self.expires_at
def __str__(self):
return f"{self.user_session.access_code} - {self.game.title}"

View File

@ -1,25 +1,97 @@
{% load static %}
<!DOCTYPE html>
<html lang="en">
<html lang="pt-br">
<head>
<meta charset="UTF-8">
<title>{% block title %}Knowledge Base{% endblock %}</title>
{% if project_description %}
<meta name="description" content="{{ project_description }}">
<meta property="og:description" content="{{ project_description }}">
<meta property="twitter:description" content="{{ project_description }}">
{% endif %}
{% if project_image_url %}
<meta property="og:image" content="{{ project_image_url }}">
<meta property="twitter:image" content="{{ project_image_url }}">
{% endif %}
{% load static %}
<link rel="stylesheet" href="{% static 'css/custom.css' %}?v={{ deployment_timestamp }}">
{% block head %}{% endblock %}
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{ title|default:"AI Game Forge" }}</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&family=Outfit:wght@700;800&display=swap" rel="stylesheet">
<link rel="stylesheet" href="{% static 'css/custom.css' %}?v={% now 'U' %}">
<style>
:root {
--midnight: #0B0F1A;
--indigo-accent: #6366F1;
--cyan-accent: #0EA5E9;
--ghost-white: #F8FAFC;
}
body {
background-color: var(--midnight);
color: var(--ghost-white);
font-family: 'Inter', sans-serif;
min-height: 100vh;
}
h1, h2, h3, .brand-font {
font-family: 'Outfit', sans-serif;
}
.navbar {
background: rgba(11, 15, 26, 0.8);
backdrop-filter: blur(10px);
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
}
.btn-primary {
background: linear-gradient(135deg, var(--indigo-accent), var(--cyan-accent));
border: none;
font-weight: 600;
}
.glass-card {
background: rgba(255, 255, 255, 0.05);
backdrop-filter: blur(10px);
border: 1px solid rgba(255, 255, 255, 0.1);
border-radius: 16px;
padding: 2rem;
}
</style>
{% block extra_css %}{% endblock %}
</head>
<body>
{% block content %}{% endblock %}
</body>
<nav class="navbar navbar-expand-lg navbar-dark sticky-top">
<div class="container">
<a class="navbar-brand brand-font fs-3" href="{% url 'index' %}">
AI <span class="text-info">GAME</span> FORGE
</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav ms-auto align-items-center">
{% if request.session.is_admin %}
<li class="nav-item"><a class="nav-link" href="{% url 'admin_dashboard' %}">Painel Admin</a></li>
<li class="nav-item"><a class="nav-link btn btn-outline-danger btn-sm ms-lg-3" href="{% url 'logout' %}">Sair Admin</a></li>
{% elif request.session.user_code %}
<li class="nav-item"><a class="nav-link" href="{% url 'catalog' %}">Catálogo</a></li>
<li class="nav-item"><span class="nav-link text-info">Cod: {{ request.session.user_code }}</span></li>
<li class="nav-item"><a class="nav-link btn btn-outline-light btn-sm ms-lg-3" href="{% url 'logout' %}">Trocar Código</a></li>
{% else %}
<li class="nav-item"><a class="nav-link" href="{% url 'admin_login' %}">Administrador</a></li>
{% endif %}
</ul>
</div>
</div>
</nav>
<main class="py-5">
<div class="container">
{% if messages %}
{% for message in messages %}
<div class="alert alert-{{ message.tags }} alert-dismissible fade show bg-dark text-white border-secondary">
{{ message }}
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="alert"></button>
</div>
{% endfor %}
{% endif %}
{% block content %}{% endblock %}
</div>
</main>
<footer class="py-4 text-center border-top border-secondary mt-auto">
<p class="text-secondary small">&copy; 2026 AI Game Forge. Todos os direitos reservados.</p>
</footer>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
{% block extra_js %}{% endblock %}
</body>
</html>

View File

@ -0,0 +1,66 @@
{% extends 'base.html' %}
{% block content %}
<div class="d-flex justify-content-between align-items-center mb-5">
<h1 class="h2">Painel de Controle</h1>
<a href="{% url 'admin_create_game' %}" class="btn btn-primary rounded-pill px-4">+ Criar Novo Jogo</a>
</div>
<div class="row">
<div class="col-lg-8">
<div class="glass-card mb-4">
<h3 class="mb-4">Gerenciar Jogos</h3>
<div class="table-responsive">
<table class="table table-dark table-hover">
<thead>
<tr>
<th>Título</th>
<th>Gênero</th>
<th>Data</th>
<th>Status</th>
<th>Ações</th>
</tr>
</thead>
<tbody>
{% for project in projects %}
<tr>
<td>{{ project.title }}</td>
<td>{{ project.get_genre_display }}</td>
<td>{{ project.created_at|date:"d/m/Y" }}</td>
<td>
{% if project.is_active %}
<span class="badge bg-success">Ativo</span>
{% else %}
<span class="badge bg-secondary">Inativo</span>
{% endif %}
</td>
<td>
<a href="{% url 'admin_edit_game' project.pk %}" class="btn btn-sm btn-outline-info">Editar</a>
<a href="{% url 'admin_delete_game' project.pk %}" class="btn btn-sm btn-outline-danger" onclick="return confirm('Tem certeza?')">Excluir</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
<div class="col-lg-4">
<div class="glass-card">
<h3 class="mb-4">Configurar Pagamentos</h3>
<p class="text-secondary small">Edite os preços e QR Codes para aluguel de tempo.</p>
<div class="list-group list-group-flush bg-transparent">
{% for option in rental_options %}
<div class="list-group-item bg-transparent border-secondary d-flex justify-content-between align-items-center px-0">
<div>
<div class="fw-bold">{{ option.title }}</div>
<div class="text-info">R$ {{ option.price }}</div>
</div>
<a href="{% url 'admin_edit_rental' option.pk %}" class="btn btn-sm btn-outline-light">Editar</a>
</div>
{% endfor %}
</div>
</div>
</div>
</div>
{% endblock %}

View File

@ -0,0 +1,62 @@
{% extends 'base.html' %}
{% block content %}
<div class="row justify-content-center">
<div class="col-md-10">
<div class="glass-card">
<h2 class="mb-4">{% if project %}Editar Jogo: {{ project.title }}{% else %}Criar Novo Jogo{% endif %}</h2>
<form method="POST" enctype="multipart/form-data">
{% csrf_token %}
<div class="row">
<div class="col-md-6 mb-3">
<label class="form-label">Título do Jogo</label>
<input type="text" name="title" class="form-control bg-dark text-white border-secondary" value="{{ project.title }}" required>
</div>
<div class="col-md-6 mb-3">
<label class="form-label">Gênero</label>
<select name="genre" class="form-select bg-dark text-white border-secondary">
<option value="platformer" {% if project.genre == 'platformer' %}selected{% endif %}>Platformer</option>
<option value="shooter" {% if project.genre == 'shooter' %}selected{% endif %}>Shooter</option>
<option value="runner" {% if project.genre == 'runner' %}selected{% endif %}>Runner</option>
<option value="puzzle" {% if project.genre == 'puzzle' %}selected{% endif %}>Puzzle</option>
<option value="traditional" {% if project.genre == 'traditional' %}selected{% endif %}>Traditional Script</option>
</select>
</div>
</div>
<div class="mb-3">
<label class="form-label">Prompt de IA / Descrição (Transforma palavras em jogos reais)</label>
<textarea name="prompt" rows="3" class="form-control bg-dark text-white border-secondary">{{ project.prompt }}</textarea>
<div class="form-check mt-2">
<input class="form-check-input" type="checkbox" name="use_ai" id="use_ai">
<label class="form-check-label text-info" for="use_ai">
Usar IA para gerar/atualizar o script do jogo (Transformar descrição em Código Script)
</label>
</div>
</div>
<div class="mb-3">
<label class="form-label">Código Script (HTML/JS/CSS Completo)</label>
<textarea name="script_code" rows="12" class="form-control bg-dark text-white border-secondary font-monospace" style="font-size: 0.85rem;">{{ project.script_code }}</textarea>
<small class="text-secondary">Se usar a opção de IA, este campo será preenchido automaticamente.</small>
</div>
<div class="mb-3">
<label class="form-label">Imagem de Referência (Capa do Jogo)</label>
{% if project.image_reference %}
<div class="mb-2">
<img src="{{ project.image_reference.url }}" alt="Ref" style="height: 100px; border-radius: 8px;">
</div>
{% endif %}
<input type="file" name="image" class="form-control bg-dark text-white border-secondary">
</div>
<div class="mt-4 d-flex gap-3">
<button type="submit" class="btn btn-primary px-5 rounded-pill">SALVAR JOGO</button>
<a href="{% url 'admin_dashboard' %}" class="btn btn-outline-light px-5 rounded-pill">VOLTAR</a>
</div>
</form>
</div>
</div>
</div>
{% endblock %}

View File

@ -0,0 +1,19 @@
{% extends 'base.html' %}
{% block content %}
<div class="row justify-content-center pt-5">
<div class="col-md-4">
<div class="glass-card text-center">
<h2 class="mb-4">Acesso Administrativo</h2>
<p class="text-secondary mb-4">Insira sua Chave Privada única para gerenciar a plataforma.</p>
<form action="{% url 'admin_login' %}" method="POST">
{% csrf_token %}
<div class="mb-4">
<input type="password" name="private_key" class="form-control bg-dark text-white border-secondary text-center py-3" required placeholder="CHAVE PRIVADA">
</div>
<button type="submit" class="btn btn-primary w-100 py-3 rounded-pill">ENTRAR NO PAINEL</button>
</form>
</div>
</div>
</div>
{% endblock %}

View File

@ -0,0 +1,46 @@
{% extends 'base.html' %}
{% block content %}
<div class="row justify-content-center">
<div class="col-md-6">
<div class="glass-card">
<h2 class="mb-4">Editar Opção de Pagamento</h2>
<form method="POST" enctype="multipart/form-data">
{% csrf_token %}
<div class="mb-3">
<label class="form-label">Título da Opção</label>
<input type="text" name="title" value="{{ option.title }}" class="form-control bg-dark text-white border-secondary" required>
</div>
<div class="row">
<div class="col-md-6 mb-3">
<label class="form-label">Preço (R$)</label>
<input type="number" step="0.01" name="price" value="{{ option.price|stringformat:'.2f' }}" class="form-control bg-dark text-white border-secondary" required>
</div>
<div class="col-md-6 mb-3">
<label class="form-label">Duração (Dias)</label>
<input type="number" name="duration_days" value="{{ option.duration_days }}" class="form-control bg-dark text-white border-secondary" required>
</div>
</div>
<div class="mb-3">
<label class="form-label">QR Code 1 (Janela Principal)</label>
<input type="file" name="qr1" class="form-control bg-dark text-white border-secondary">
{% if option.qr_code_1 %}
<div class="mt-2 small text-info">Atual: {{ option.qr_code_1.name }}</div>
{% endif %}
</div>
<div class="mb-3">
<label class="form-label">QR Code 2 (Janela Secundária)</label>
<input type="file" name="qr2" class="form-control bg-dark text-white border-secondary">
{% if option.qr_code_2 %}
<div class="mt-2 small text-info">Atual: {{ option.qr_code_2.name }}</div>
{% endif %}
</div>
<div class="mt-4 d-flex gap-3">
<button type="submit" class="btn btn-primary px-5 rounded-pill">SALVAR</button>
<a href="{% url 'admin_dashboard' %}" class="btn btn-outline-light px-5 rounded-pill">VOLTAR</a>
</div>
</form>
</div>
</div>
</div>
{% endblock %}

View File

@ -0,0 +1,34 @@
{% extends 'base.html' %}
{% block content %}
<div class="mb-5">
<h1 class="display-4 fw-bold">Biblioteca de <span class="text-info">Jogos</span></h1>
<p class="text-secondary">Escolha um título e alugue tempo para jogar instantaneamente.</p>
</div>
<div class="row g-4">
{% for project in projects %}
<div class="col-md-4">
<div class="glass-card h-100 p-0 overflow-hidden d-flex flex-column border-0">
<div style="height: 200px; background: linear-gradient(45deg, #1e293b, #0f172a); display: flex; align-items: center; justify-content: center;">
{% if project.image_reference %}
<img src="{{ project.image_reference.url }}" class="w-100 h-100 object-fit-cover" alt="{{ project.title }}">
{% else %}
<span class="display-1 opacity-25">🎮</span>
{% endif %}
</div>
<div class="p-4 flex-grow-1 d-flex flex-column">
<span class="badge bg-info text-dark mb-2 align-self-start">{{ project.get_genre_display }}</span>
<h3 class="h4 mb-3">{{ project.title }}</h3>
<p class="text-secondary small mb-4 flex-grow-1">{{ project.prompt|truncatewords:20 }}</p>
<a href="{% url 'purchase_game' project.pk %}" class="btn btn-primary w-100 rounded-pill">ALUGAR TEMPO</a>
</div>
</div>
</div>
{% empty %}
<div class="col-12 text-center py-5">
<p class="text-secondary">Nenhum jogo disponível no momento.</p>
</div>
{% endfor %}
</div>
{% endblock %}

View File

@ -1,145 +1,44 @@
{% extends "base.html" %}
{% block title %}{{ project_name }}{% endblock %}
{% block head %}
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;700&display=swap" rel="stylesheet">
<style>
:root {
--bg-color-start: #6a11cb;
--bg-color-end: #2575fc;
--text-color: #ffffff;
--card-bg-color: rgba(255, 255, 255, 0.01);
--card-border-color: rgba(255, 255, 255, 0.1);
}
* {
box-sizing: border-box;
}
body {
margin: 0;
font-family: 'Inter', sans-serif;
background: linear-gradient(45deg, var(--bg-color-start), var(--bg-color-end));
color: var(--text-color);
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
text-align: center;
overflow: hidden;
position: relative;
}
body::before {
content: '';
position: absolute;
inset: 0;
background-image: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' width='100' height='100' viewBox='0 0 100 100'><path d='M-10 10L110 10M10 -10L10 110' stroke-width='1' stroke='rgba(255,255,255,0.05)'/></svg>");
animation: bg-pan 20s linear infinite;
z-index: -1;
}
@keyframes bg-pan {
0% {
background-position: 0% 0%;
}
100% {
background-position: 100% 100%;
}
}
main {
padding: 2rem;
}
.card {
background: var(--card-bg-color);
border: 1px solid var(--card-border-color);
border-radius: 16px;
padding: 2.5rem 2rem;
backdrop-filter: blur(20px);
-webkit-backdrop-filter: blur(20px);
box-shadow: 0 12px 36px rgba(0, 0, 0, 0.25);
}
h1 {
font-size: clamp(2.2rem, 3vw + 1.2rem, 3.2rem);
font-weight: 700;
margin: 0 0 1.2rem;
letter-spacing: -0.02em;
}
p {
margin: 0.5rem 0;
font-size: 1.1rem;
opacity: 0.92;
}
.loader {
margin: 1.5rem auto;
width: 56px;
height: 56px;
border: 4px solid rgba(255, 255, 255, 0.25);
border-top-color: #fff;
border-radius: 50%;
animation: spin 1s linear infinite;
}
@keyframes spin {
to {
transform: rotate(360deg);
}
}
.runtime code {
background: rgba(0, 0, 0, 0.25);
padding: 0.15rem 0.45rem;
border-radius: 4px;
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
}
.sr-only {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
border: 0;
}
footer {
position: absolute;
bottom: 1rem;
width: 100%;
text-align: center;
font-size: 0.85rem;
opacity: 0.75;
}
</style>
{% endblock %}
{% extends 'base.html' %}
{% block content %}
<main>
<div class="card">
<h1>Analyzing your requirements and generating your app…</h1>
<div class="loader" role="status" aria-live="polite" aria-label="Applying initial changes">
<span class="sr-only">Loading…</span>
<div class="row justify-content-center align-items-center" style="min-height: 70vh;">
<div class="col-md-6 text-center mb-5">
<h1 class="display-3 fw-bold mb-4">Bem-vindo ao <span class="text-info">Futuro</span></h1>
<p class="lead text-secondary">Acesse os melhores jogos gerados por IA. Gere seu código de acesso único e comece a jogar agora.</p>
</div>
<p class="hint">AppWizzy AI is collecting your requirements and applying the first changes.</p>
<p class="hint">This page will refresh automatically as the plan is implemented.</p>
<p class="runtime">
Runtime: Django <code>{{ django_version }}</code> · Python <code>{{ python_version }}</code>
— UTC <code>{{ current_time|date:"Y-m-d H:i:s" }}</code>
</p>
</div>
</main>
<footer>
Page updated: {{ current_time|date:"Y-m-d H:i:s" }} (UTC)
</footer>
{% endblock %}
<div class="col-md-5">
<div class="glass-card shadow-lg">
<ul class="nav nav-pills mb-4 justify-content-center" id="authTab" role="tablist">
<li class="nav-item" role="presentation">
<button class="nav-link active" id="generate-tab" data-bs-toggle="pill" data-bs-target="#generate" type="button">Gerar Novo Código</button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link" id="recover-tab" data-bs-toggle="pill" data-bs-target="#recover" type="button">Recuperar Código</button>
</li>
</ul>
<div class="tab-content" id="authTabContent">
<div class="tab-pane fade show active" id="generate" role="tabpanel">
<form action="{% url 'generate_code' %}" method="POST">
{% csrf_token %}
<div class="mb-4">
<label class="form-label">Número de Telefone (Opcional para recuperação)</label>
<input type="text" name="phone" class="form-control bg-dark text-white border-secondary" placeholder="+55 11 99999-9999">
</div>
<button type="submit" class="btn btn-primary w-100 py-3 rounded-pill">GERAR MEU ACESSO</button>
</form>
</div>
<div class="tab-pane fade" id="recover" role="tabpanel">
<form action="{% url 'recover_code' %}" method="POST">
{% csrf_token %}
<div class="mb-4">
<label class="form-label">Digite seu Telefone Vinculado</label>
<input type="text" name="phone" class="form-control bg-dark text-white border-secondary" required placeholder="+55 11 99999-9999">
</div>
<button type="submit" class="btn btn-outline-info w-100 py-3 rounded-pill">RECUPERAR CÓDIGO</button>
</form>
</div>
</div>
</div>
</div>
</div>
{% endblock %}

View File

@ -0,0 +1,78 @@
{% extends 'base.html' %}
{% block content %}
<div class="d-flex justify-content-between align-items-end mb-4">
<div>
<h1 class="h2 mb-1">{{ game.title }}</h1>
<p class="text-info mb-0">Sessão validada. Divirta-se!</p>
</div>
<a href="{% url 'catalog' %}" class="btn btn-outline-light rounded-pill">Sair do Jogo</a>
</div>
<div class="glass-card p-0 overflow-hidden shadow-2xl" style="height: 75vh; background: #000; position: relative;">
{% if game.script_code %}
<iframe id="gameFrame" srcdoc="{{ game.script_code|escape }}" style="width: 100%; height: 100%; border: none;" allow="autoplay; fullscreen; keyboard"></iframe>
{% else %}
<div class="w-100 h-100 d-flex flex-column align-items-center justify-content-center text-center">
<!-- FALLBACK SIMULATED GAME CANVAS -->
<canvas id="gameCanvas" width="800" height="450" class="img-fluid bg-dark shadow-lg border border-secondary"></canvas>
<div class="mt-4">
<p class="text-secondary small">Este jogo ainda não possui script. Exibindo simulador.</p>
<div class="badge bg-warning text-dark">Aguardando IA</div>
</div>
</div>
{% endif %}
</div>
<div class="mt-4 row">
<div class="col-md-8">
<h4 class="mb-3">Sobre o Jogo</h4>
<p class="text-secondary">{{ game.prompt }}</p>
</div>
<div class="col-md-4">
<div class="p-3 border border-secondary rounded-3 bg-dark bg-opacity-50">
<h5 class="small text-uppercase text-secondary">Status da Sessão</h5>
<div class="h4 text-success" id="timer">Ativo ✅</div>
<div class="small text-secondary mt-1">Sua compra foi validada com sucesso.</div>
</div>
</div>
</div>
{% block extra_js %}
{% if not game.script_code %}
<script>
// Simple canvas animation to simulate a game
const canvas = document.getElementById('gameCanvas');
const ctx = canvas.getContext('2d');
let x = 400, y = 225, dx = 2, dy = -2;
function draw() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.strokeStyle = '#1e293b';
ctx.lineWidth = 1;
for(let i=0; i<canvas.width; i+=40) {
ctx.beginPath(); ctx.moveTo(i, 0); ctx.lineTo(i, canvas.height); ctx.stroke();
}
for(let i=0; i<canvas.height; i+=40) {
ctx.beginPath(); ctx.moveTo(0, i); ctx.lineTo(canvas.height); ctx.stroke();
}
ctx.fillStyle = '#6366F1';
ctx.shadowBlur = 15;
ctx.shadowColor = '#6366F1';
ctx.beginPath();
ctx.arc(x, y, 20, 0, Math.PI*2);
ctx.fill();
if(x + dx > canvas.width-20 || x + dx < 20) dx = -dx;
if(y + dy > canvas.height-20 || y + dy < 20) dy = -dy;
x += dx; y += dy;
requestAnimationFrame(draw);
}
draw();
</script>
{% endif %}
{% endblock %}
{% endblock %}

View File

@ -0,0 +1,183 @@
{% extends 'base.html' %}
{% block content %}
<div class="row">
<div class="col-lg-6">
<div class="glass-card mb-4">
<h2 class="mb-4">{{ game.title }}</h2>
<p class="text-secondary mb-4">{{ game.prompt }}</p>
<div class="d-flex align-items-center p-3 bg-dark rounded-3 border border-info mb-4">
<div class="me-3 fs-3">💳</div>
<div>
<div class="small text-secondary">Instruções de Aluguel</div>
<div>Selecione um plano, clique em <strong>"CLICK NO QR"</strong>, escaneie o código e aguarde 3 minutos para receber seu código de validação.</div>
</div>
</div>
<h4 class="mb-3">Escolha o tempo de acesso:</h4>
<div class="row g-3 mb-4">
{% for option in options %}
<div class="col-6">
<div class="p-3 border border-secondary rounded-4 text-center option-card"
id="option-{{ option.pk }}"
style="cursor: pointer;"
onclick="selectOption('{{ option.pk }}', '{{ option.title }}', '{{ option.price }}', '{{ option.qr_code_1.url|default:'' }}', '{{ option.qr_code_2.url|default:'' }}')">
<div class="fw-bold">{{ option.title }}</div>
<div class="text-info fs-5">R$ {{ option.price }}</div>
</div>
</div>
{% endfor %}
</div>
</div>
</div>
<div class="col-lg-6">
<div id="payment-area" class="glass-card h-100 d-flex flex-column justify-content-center text-center" style="min-height: 450px;">
<div id="payment-empty">
<div class="display-1 opacity-25 mb-4">🛒</div>
<h3>Selecione uma opção</h3>
<p class="text-secondary">Escolha um plano ao lado para começar.</p>
</div>
<div id="payment-details" class="d-none">
<h3 id="selected-title" class="mb-4 text-info"></h3>
<div id="qr-unlock-section" class="mb-4">
<button type="button" class="btn btn-lg btn-warning fw-bold px-5 py-3 rounded-pill" onclick="unlockQR()">CLICK NO QR</button>
<p class="mt-3 text-secondary small">Clique para visualizar os códigos de pagamento</p>
</div>
<div id="qr-display-section" class="d-none">
<div class="row mb-4">
<div class="col-6">
<div class="bg-white p-2 rounded-3 mb-2 mx-auto" style="width: 140px; height: 140px;">
<img id="qr1-img" src="" class="w-100 h-100 object-fit-contain" alt="QR 1">
</div>
<div class="small text-secondary">QR CODE 1</div>
</div>
<div class="col-6">
<div class="bg-white p-2 rounded-3 mb-2 mx-auto" style="width: 140px; height: 140px;">
<img id="qr2-img" src="" class="w-100 h-100 object-fit-contain" alt="QR 2">
</div>
<div class="small text-secondary">QR CODE 2</div>
</div>
</div>
<div id="timer-section" class="alert alert-info py-3 mb-4">
<div class="small mb-1">Aguardando confirmação do pagamento...</div>
<div id="countdown" class="fs-2 fw-bold font-monospace">03:00</div>
<div class="progress mt-2" style="height: 5px;">
<div id="timer-progress" class="progress-bar progress-bar-striped progress-bar-animated bg-info" style="width: 0%"></div>
</div>
</div>
<div id="conf-code-display" class="alert alert-success d-none mb-4 animate__animated animate__pulse animate__infinite">
<div class="small">Pagamento Concluído! Seu código de 6 dígitos:</div>
<div id="generated-code" class="display-4 fw-bold font-monospace mt-1"></div>
<div class="small text-dark mt-1">Copie e cole no campo abaixo para liberar o jogo.</div>
</div>
</div>
<hr class="border-secondary my-4">
<form method="POST" id="purchase-form">
{% csrf_token %}
<input type="hidden" name="purchase_id" id="purchase_id_input">
<div class="mb-4">
<label class="form-label">Insira o Código de Confirmação</label>
<input type="text" name="confirm_code" class="form-control bg-dark text-white border-info text-center fs-3 font-monospace" placeholder="000000" maxlength="6" required>
</div>
<button type="submit" id="submit-btn" class="btn btn-primary w-100 py-3 rounded-pill fw-bold" disabled>VALIDAR ALUGUEL E JOGAR</button>
</form>
</div>
</div>
</div>
</div>
{% block extra_js %}
<script>
let currentOptionId = null;
let timerInterval = null;
const WAIT_TIME = 180; // 3 minutes in seconds
function selectOption(id, title, price, qr1, qr2) {
currentOptionId = id;
document.getElementById('payment-empty').classList.add('d-none');
document.getElementById('payment-details').classList.remove('d-none');
// Reset sections
document.getElementById('qr-unlock-section').classList.remove('d-none');
document.getElementById('qr-display-section').classList.add('d-none');
document.getElementById('conf-code-display').classList.add('d-none');
document.getElementById('timer-section').classList.remove('d-none');
document.getElementById('submit-btn').disabled = true;
if (timerInterval) clearInterval(timerInterval);
document.getElementById('selected-title').innerText = title + ' - R$ ' + price;
const placeholder = 'https://api.qrserver.com/v1/create-qr-code/?size=150x150&data=Pagamento_AI_Forge';
document.getElementById('qr1-img').src = qr1 || placeholder;
document.getElementById('qr2-img').src = qr2 || placeholder;
// Highlight selected
document.querySelectorAll('.option-card').forEach(el => el.classList.remove('border-info', 'bg-info', 'bg-opacity-10'));
document.getElementById('option-' + id).classList.add('border-info', 'bg-info', 'bg-opacity-10');
}
function unlockQR() {
if (!currentOptionId) return;
document.getElementById('qr-unlock-section').classList.add('d-none');
document.getElementById('qr-display-section').classList.remove('d-none');
startTimer();
}
function startTimer() {
let timeLeft = WAIT_TIME;
const countdownEl = document.getElementById('countdown');
const progressEl = document.getElementById('timer-progress');
if (timerInterval) clearInterval(timerInterval);
timerInterval = setInterval(() => {
timeLeft--;
const minutes = Math.floor(timeLeft / 60);
const seconds = timeLeft % 60;
countdownEl.innerText = `${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`;
const progress = ((WAIT_TIME - timeLeft) / WAIT_TIME) * 100;
progressEl.style.width = progress + '%';
if (timeLeft <= 0) {
clearInterval(timerInterval);
generateCode();
}
}, 1000);
}
function generateCode() {
fetch(`/generate-purchase/{{ game.pk }}/${currentOptionId}/`)
.then(response => response.json())
.then(data => {
if (data.success) {
document.getElementById('purchase_id_input').value = data.purchase_id;
document.getElementById('generated-code').innerText = data.confirmation_code;
document.getElementById('timer-section').classList.add('d-none');
document.getElementById('conf-code-display').classList.remove('d-none');
document.getElementById('submit-btn').disabled = false;
// Add success animation
document.getElementById('conf-code-display').classList.add('animate__animated', 'animate__bounceIn');
} else {
alert('Erro ao processar: ' + data.error);
}
});
}
</script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/animate.css/4.1.1/animate.min.css"/>
{% endblock %}
{% endblock %}

View File

@ -0,0 +1,144 @@
{% extends 'base.html' %}
{% load static %}
{% block content %}
<div class="container-fluid px-5">
<div class="row g-4">
<!-- Sidebar Controls -->
<div class="col-lg-3">
<div class="glass p-4 studio-sidebar">
<div class="d-flex align-items-center mb-4">
<div class="flex-grow-1">
<h4 class="font-outfit fw-bold mb-0">{{ project.title }}</h4>
<span class="badge badge-genre">{{ project.get_genre_display }}</span>
</div>
</div>
<hr class="opacity-10">
<div class="mb-4">
<label class="small text-muted text-uppercase fw-bold mb-2 d-block">AI Generation Summary</label>
<p class="small">{{ project.config_json.ai_summary }}</p>
</div>
<div class="mb-4">
<label class="small text-muted text-uppercase fw-bold mb-2 d-block">Entity Configuration</label>
<div class="code-editor">
{{ config_json }}
</div>
</div>
<div class="d-grid gap-2">
<button class="btn btn-outline-light btn-sm">Export Code (C# / JS)</button>
<button class="btn btn-outline-light btn-sm">Build for Android (APK)</button>
<button class="btn btn-outline-light btn-sm">Download Assets (.zip)</button>
</div>
</div>
</div>
<!-- Main Viewport -->
<div class="col-lg-9">
<div class="glass p-4">
<div class="d-flex justify-content-between align-items-center mb-3">
<h5 class="font-outfit fw-bold mb-0">Live Preview (Web HTML5)</h5>
<div>
<button class="btn btn-success btn-sm me-2" onclick="restartGame()">Run Build</button>
<button class="btn btn-secondary btn-sm">Debug Mode</button>
</div>
</div>
<div class="game-viewport" id="game-container">
<canvas id="gameCanvas"></canvas>
<div id="game-overlay" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; display: flex; align-items: center; justify-content: center; background: rgba(0,0,0,0.7); flex-direction: column;">
<h2 class="font-outfit fw-bold mb-3">AI Engine Initialized</h2>
<p>Press 'Run Build' to compile and play</p>
</div>
</div>
<div class="row mt-4">
<div class="col-md-4">
<div class="glass p-3 text-center">
<h3 class="fw-bold text-primary mb-1">0</h3>
<span class="small text-muted">Bugs Detected</span>
</div>
</div>
<div class="col-md-4">
<div class="glass p-3 text-center">
<h3 class="fw-bold text-secondary mb-1">142</h3>
<span class="small text-muted">Assets Generated</span>
</div>
</div>
<div class="col-md-4">
<div class="glass p-3 text-center">
<h3 class="fw-bold text-accent mb-1">60</h3>
<span class="small text-muted">FPS Stable</span>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
{% block scripts %}
<script>
const canvas = document.getElementById('gameCanvas');
const ctx = canvas.getContext('2d');
const overlay = document.getElementById('game-overlay');
let animationId;
let particles = [];
function resize() {
const container = document.getElementById('game-container');
canvas.width = container.clientWidth;
canvas.height = container.clientHeight;
}
window.addEventListener('resize', resize);
resize();
function restartGame() {
overlay.style.display = 'none';
if (animationId) cancelAnimationFrame(animationId);
// Simple "Game" Simulation: Neon Particles
particles = [];
for(let i=0; i<50; i++) {
particles.push({
x: Math.random() * canvas.width,
y: Math.random() * canvas.height,
vx: (Math.random() - 0.5) * 5,
vy: (Math.random() - 0.5) * 5,
color: Math.random() > 0.5 ? '#6366F1' : '#0EA5E9',
size: Math.random() * 5 + 2
});
}
animate();
}
function animate() {
ctx.fillStyle = 'rgba(11, 15, 26, 0.2)';
ctx.fillRect(0, 0, canvas.width, canvas.height);
particles.forEach(p => {
p.x += p.vx;
p.y += p.vy;
if(p.x < 0 || p.x > canvas.width) p.vx *= -1;
if(p.y < 0 || p.y > canvas.height) p.vy *= -1;
ctx.beginPath();
ctx.arc(p.x, p.y, p.size, 0, Math.PI * 2);
ctx.fillStyle = p.color;
ctx.fill();
// Neon Glow
ctx.shadowBlur = 15;
ctx.shadowColor = p.color;
});
animationId = requestAnimationFrame(animate);
}
</script>
{% endblock %}

View File

@ -1,7 +1,23 @@
from django.urls import path
from .views import home
from . import views
urlpatterns = [
path("", home, name="home"),
]
path('', views.index, name='index'),
path('admin-login/', views.admin_login, name='admin_login'),
path('generate-code/', views.generate_code, name='generate_code'),
path('recover-code/', views.recover_code, name='recover_code'),
path('logout/', views.logout, name='logout'),
# Admin
path('admin-dashboard/', views.admin_dashboard, name='admin_dashboard'),
path('admin/create-game/', views.admin_create_game, name='admin_create_game'),
path('admin/edit-game/<int:pk>/', views.admin_edit_game, name='admin_edit_game'),
path('admin/delete-game/<int:pk>/', views.admin_delete_game, name='admin_delete_game'),
path('admin/edit-rental/<int:pk>/', views.admin_edit_rental, name='admin_edit_rental'),
# User
path('catalog/', views.catalog, name='catalog'),
path('purchase/<int:pk>/', views.purchase_game, name='purchase_game'),
path('generate-purchase/<int:game_pk>/<int:option_pk>/', views.generate_purchase, name='generate_purchase'),
path('play/<int:pk>/', views.play_game, name='play_game'),
]

View File

@ -1,25 +1,293 @@
import os
import platform
from django import get_version as django_version
from django.shortcuts import render
import json
import random
import string
import uuid
from django.shortcuts import render, redirect, get_object_or_404
from django.contrib import messages
from django.utils import timezone
from datetime import timedelta
from django.http import JsonResponse
from .models import GameProject, UserSession, AdminConfig, RentalOption, UserPurchase
from ai.local_ai_api import LocalAIApi
# Helper to check authentication
def get_auth_status(request):
is_admin = request.session.get('is_admin', False)
user_code = request.session.get('user_code')
user = None
if user_code:
user = UserSession.objects.filter(access_code=user_code).first()
return is_admin, user
def home(request):
"""Render the landing screen with loader and environment details."""
host_name = request.get_host().lower()
agent_brand = "AppWizzy" if host_name == "appwizzy.com" else "Flatlogic"
now = timezone.now()
def index(request):
is_admin, user = get_auth_status(request)
if is_admin:
return redirect('admin_dashboard')
if user:
return redirect('catalog')
return render(request, 'core/index.html', {'title': 'Acesso'})
context = {
"project_name": "New Style",
"agent_brand": agent_brand,
"django_version": django_version(),
"python_version": platform.python_version(),
"current_time": now,
"host_name": host_name,
"project_description": os.getenv("PROJECT_DESCRIPTION", ""),
"project_image_url": os.getenv("PROJECT_IMAGE_URL", ""),
}
return render(request, "core/index.html", context)
def admin_login(request):
if request.method == 'POST':
key = request.POST.get('private_key')
if AdminConfig.objects.filter(private_key=key).exists():
request.session['is_admin'] = True
request.session['user_code'] = None
return redirect('admin_dashboard')
messages.error(request, 'Chave Privada Inválida.')
return render(request, 'core/admin_login.html', {'title': 'Admin Login'})
def generate_code(request):
if request.method == 'POST':
phone = request.POST.get('phone')
# Generate 6 digit code for platform access
code = ''.join(random.choices(string.digits, k=6))
while UserSession.objects.filter(access_code=code).exists():
code = ''.join(random.choices(string.digits, k=6))
UserSession.objects.create(access_code=code, phone_number=phone)
request.session['user_code'] = code
request.session['is_admin'] = False
messages.success(request, f'Seu código de acesso é: {code}. Guarde-o bem!')
return redirect('catalog')
return redirect('index')
def recover_code(request):
if request.method == 'POST':
phone = request.POST.get('phone')
user = UserSession.objects.filter(phone_number=phone).first()
if user:
request.session['user_code'] = user.access_code
messages.success(request, f'Bem-vindo de volta! Seu código {user.access_code} foi recuperado.')
return redirect('catalog')
messages.error(request, 'Nenhum código encontrado para este telefone.')
return redirect('index')
def logout(request):
request.session.flush()
return redirect('index')
# AI Helper - PROGRAMADOR AI AUTOMATIZADO
def generate_game_script(prompt, genre):
system_prompt = (
"You are an expert game developer. Create a complete, single-file HTML/JavaScript/CSS game. "
"The game MUST be fully functional, self-contained, and playable in a browser iframe. "
"Use modern JavaScript (ES6+), HTML5 Canvas, or CSS animations. "
"The game MUST have: "
"1. A 'Start Game' screen with instructions. "
"2. Smooth controls (keyboard or touch). "
"3. A scoring system and a 'Game Over' state with a 'Restart' button. "
"4. A polished, modern visual style (dark theme preferred). "
"5. Responsive design to fit any screen size. "
"Return ONLY the raw source code starting with <!DOCTYPE html> and ending with </html>. "
"Do not include any explanations, markdown fences, or text outside the HTML tags."
)
user_prompt = f"Develop a complete {genre} game based on this request: {prompt}. Ensure it's a real, playable game."
response = LocalAIApi.create_response({
"input": [
{"role": "system", "content": system_prompt},
{"role": "user", "content": user_prompt},
],
})
if response.get("success"):
return LocalAIApi.extract_text(response)
return None
# ADMIN VIEWS
def admin_dashboard(request):
is_admin, _ = get_auth_status(request)
if not is_admin:
return redirect('admin_login')
projects = GameProject.objects.all().order_by('-created_at')
rental_options = RentalOption.objects.all()
return render(request, 'core/admin_dashboard.html', {
'projects': projects,
'rental_options': rental_options,
'title': 'Painel do Administrador'
})
def admin_create_game(request):
is_admin, _ = get_auth_status(request)
if not is_admin:
return redirect('admin_login')
if request.method == 'POST':
title = request.POST.get('title')
prompt = request.POST.get('prompt')
genre = request.POST.get('genre', 'arcade')
image = request.FILES.get('image')
use_ai = request.POST.get('use_ai') == 'on'
manual_script = request.POST.get('script_code', '')
script = manual_script
if use_ai and prompt:
ai_script = generate_game_script(prompt, genre)
if ai_script:
script = ai_script
else:
messages.error(request, 'Falha ao gerar o script com AI. Usando script manual ou vazio.')
project = GameProject.objects.create(
title=title,
prompt=prompt,
genre=genre,
image_reference=image,
script_code=script,
config_json={"player": {"speed": 200}, "entities": []}
)
messages.success(request, f'Jogo "{title}" criado com sucesso!')
return redirect('admin_dashboard')
return render(request, 'core/admin_game_form.html', {'title': 'Criar Novo Jogo'})
def admin_edit_game(request, pk):
is_admin, _ = get_auth_status(request)
if not is_admin:
return redirect('admin_login')
project = get_object_or_404(GameProject, pk=pk)
if request.method == 'POST':
project.title = request.POST.get('title')
project.prompt = request.POST.get('prompt')
project.genre = request.POST.get('genre')
if request.FILES.get('image'):
project.image_reference = request.FILES.get('image')
use_ai = request.POST.get('use_ai') == 'on'
manual_script = request.POST.get('script_code', '')
if use_ai and project.prompt:
ai_script = generate_game_script(project.prompt, project.genre)
if ai_script:
project.script_code = ai_script
else:
messages.error(request, 'Falha ao gerar o script com AI.')
else:
project.script_code = manual_script
project.save()
messages.success(request, f'Jogo "{project.title}" atualizado!')
return redirect('admin_dashboard')
return render(request, 'core/admin_game_form.html', {'project': project, 'title': 'Editar Jogo'})
def admin_delete_game(request, pk):
is_admin, _ = get_auth_status(request)
if not is_admin:
return redirect('admin_login')
project = get_object_or_404(GameProject, pk=pk)
project.delete()
messages.success(request, 'Jogo excluído com sucesso.')
return redirect('admin_dashboard')
def admin_edit_rental(request, pk):
is_admin, _ = get_auth_status(request)
if not is_admin:
return redirect('admin_login')
option = get_object_or_404(RentalOption, pk=pk)
if request.method == 'POST':
option.title = request.POST.get('title')
option.price = request.POST.get('price')
option.duration_days = request.POST.get('duration_days')
if request.FILES.get('qr1'):
option.qr_code_1 = request.FILES.get('qr1')
if request.FILES.get('qr2'):
option.qr_code_2 = request.FILES.get('qr2')
option.save()
messages.success(request, 'Opção de aluguel atualizada.')
return redirect('admin_dashboard')
return render(request, 'core/admin_rental_form.html', {'option': option, 'title': 'Editar Preço'})
# USER VIEWS
def catalog(request):
is_admin, user = get_auth_status(request)
if not user and not is_admin:
return redirect('index')
projects = GameProject.objects.filter(is_active=True).order_by('-created_at')
return render(request, 'core/catalog.html', {'projects': projects, 'title': 'Catálogo de Jogos'})
def purchase_game(request, pk):
is_admin, user = get_auth_status(request)
if not user:
return redirect('index')
game = get_object_or_404(GameProject, pk=pk)
options = RentalOption.objects.all()
if request.method == 'POST':
confirm_code = request.POST.get('confirm_code')
purchase_id = request.POST.get('purchase_id')
purchase = get_object_or_404(UserPurchase, pk=purchase_id, user_session=user)
if confirm_code == purchase.confirmation_code:
purchase.is_confirmed = True
purchase.save()
messages.success(request, f'Pagamento confirmado! Aproveite o jogo.')
return redirect('play_game', pk=game.pk)
else:
messages.error(request, 'Código de confirmação incorreto.')
return render(request, 'core/purchase.html', {
'game': game,
'options': options,
'title': f'Alugar {game.title}'
})
def generate_purchase(request, game_pk, option_pk):
is_admin, user = get_auth_status(request)
if not user:
return JsonResponse({'success': False, 'error': 'Unauthorized'})
game = get_object_or_404(GameProject, pk=game_pk)
option = get_object_or_404(RentalOption, pk=option_pk)
# Generate 6 digit confirmation code (as requested)
conf_code = ''.join(random.choices(string.digits, k=6))
expires = timezone.now() + timedelta(days=option.duration_days)
purchase = UserPurchase.objects.create(
user_session=user,
game=game,
rental_option=option,
confirmation_code=conf_code,
expires_at=expires,
is_confirmed=False
)
return JsonResponse({
'success': True,
'purchase_id': purchase.id,
'confirmation_code': conf_code
})
def play_game(request, pk):
is_admin, user = get_auth_status(request)
if not user and not is_admin:
return redirect('index')
game = get_object_or_404(GameProject, pk=pk)
if not is_admin:
# Check if user has active purchase
purchase = UserPurchase.objects.filter(
user_session=user,
game=game,
expires_at__gt=timezone.now(),
is_confirmed=True
).first()
if not purchase:
messages.error(request, 'Você não tem tempo de jogo para este título.')
return redirect('purchase_game', pk=game.pk)
return render(request, 'core/play.html', {'game': game, 'title': f'Jogando {game.title}'})

Binary file not shown.

After

Width:  |  Height:  |  Size: 87 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 87 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 87 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 86 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 86 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 86 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 86 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 86 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

View File

@ -1,4 +1,125 @@
/* Custom styles for the application */
body {
font-family: system-ui, -apple-system, sans-serif;
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;600;700&family=Outfit:wght@400;600;800&display=swap');
:root {
--bg-deep: #0B0F1A;
--bg-card: rgba(255, 255, 255, 0.05);
--primary: #6366F1;
--secondary: #0EA5E9;
--accent: #F43F5E;
--text-main: #F8FAFC;
--text-muted: #94A3B8;
--glass-border: rgba(255, 255, 255, 0.1);
}
body {
background-color: var(--bg-deep);
color: var(--text-main);
font-family: 'Inter', sans-serif;
margin: 0;
overflow-x: hidden;
}
h1, h2, h3, .font-outfit {
font-family: 'Outfit', sans-serif;
}
/* Glassmorphism */
.glass {
background: var(--bg-card);
backdrop-filter: blur(12px);
-webkit-backdrop-filter: blur(12px);
border: 1px solid var(--glass-border);
border-radius: 24px;
}
.hero-gradient {
position: fixed;
top: -10%;
left: -10%;
width: 120%;
height: 120%;
background: radial-gradient(circle at 20% 30%, rgba(99, 102, 241, 0.15) 0%, transparent 40%),
radial-gradient(circle at 80% 70%, rgba(14, 165, 233, 0.15) 0%, transparent 40%);
z-index: -1;
}
.btn-primary-custom {
background: linear-gradient(135deg, var(--primary), var(--secondary));
border: none;
padding: 12px 32px;
border-radius: 12px;
font-weight: 600;
color: white;
transition: all 0.3s ease;
box-shadow: 0 4px 15px rgba(99, 102, 241, 0.3);
}
.btn-primary-custom:hover {
transform: translateY(-2px);
box-shadow: 0 8px 25px rgba(99, 102, 241, 0.5);
color: white;
}
.magic-box {
max-width: 800px;
margin: 0 auto;
padding: 40px;
}
textarea.form-control-custom {
background: rgba(0, 0, 0, 0.3);
border: 1px solid var(--glass-border);
color: white;
border-radius: 16px;
padding: 20px;
font-size: 1.1rem;
}
textarea.form-control-custom:focus {
background: rgba(0, 0, 0, 0.4);
border-color: var(--primary);
box-shadow: 0 0 0 4px rgba(99, 102, 241, 0.2);
color: white;
}
.card-project {
transition: all 0.3s ease;
cursor: pointer;
}
.card-project:hover {
background: rgba(255, 255, 255, 0.08);
transform: translateY(-5px);
}
.badge-genre {
background: rgba(99, 102, 241, 0.2);
color: var(--primary);
border: 1px solid rgba(99, 102, 241, 0.3);
}
/* Studio Styles */
.game-viewport {
width: 100%;
aspect-ratio: 16/9;
background: #000;
border-radius: 16px;
overflow: hidden;
position: relative;
border: 2px solid var(--primary);
}
.studio-sidebar {
height: calc(100vh - 100px);
overflow-y: auto;
}
.code-editor {
background: #1e1e1e;
color: #d4d4d4;
font-family: 'Courier New', Courier, monospace;
padding: 15px;
border-radius: 8px;
font-size: 0.9rem;
white-space: pre-wrap;
}