O temporizador de 3 minutos garante que o usuário
BIN
ai/__pycache__/__init__.cpython-311.pyc
Normal file
BIN
ai/__pycache__/local_ai_api.cpython-311.pyc
Normal 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')
|
||||
|
||||
|
||||
@ -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)
|
||||
28
core/migrations/0001_initial.py
Normal 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)),
|
||||
],
|
||||
),
|
||||
]
|
||||
@ -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')),
|
||||
],
|
||||
),
|
||||
]
|
||||
33
core/migrations/0003_gameproject_script_code_and_more.py
Normal 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),
|
||||
),
|
||||
]
|
||||
BIN
core/migrations/__pycache__/0001_initial.cpython-311.pyc
Normal 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}"
|
||||
@ -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">© 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>
|
||||
|
||||
66
core/templates/core/admin_dashboard.html
Normal 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 %}
|
||||
62
core/templates/core/admin_game_form.html
Normal 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 %}
|
||||
19
core/templates/core/admin_login.html
Normal 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 %}
|
||||
46
core/templates/core/admin_rental_form.html
Normal 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 %}
|
||||
34
core/templates/core/catalog.html
Normal 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 %}
|
||||
@ -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 %}
|
||||
|
||||
78
core/templates/core/play.html
Normal 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 %}
|
||||
183
core/templates/core/purchase.html
Normal 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 %}
|
||||
144
core/templates/core/studio.html
Normal 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 %}
|
||||
24
core/urls.py
@ -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'),
|
||||
]
|
||||
310
core/views.py
@ -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}'})
|
||||
BIN
media/game_references/Screenshot_20250517-233850.jpg
Normal file
|
After Width: | Height: | Size: 87 KiB |
BIN
media/game_references/Screenshot_20250517-233850_0adtLXv.jpg
Normal file
|
After Width: | Height: | Size: 87 KiB |
BIN
media/game_references/Screenshot_20250517-233850_gCfOipa.jpg
Normal file
|
After Width: | Height: | Size: 87 KiB |
BIN
media/game_references/Screenshot_20250608-133058.jpg
Normal file
|
After Width: | Height: | Size: 27 KiB |
BIN
media/game_references/Screenshot_20250608-133058_tHzVFw5.jpg
Normal file
|
After Width: | Height: | Size: 27 KiB |
BIN
media/qr_codes/Screenshot_20260209_151849_Chrome2.jpg
Normal file
|
After Width: | Height: | Size: 86 KiB |
BIN
media/qr_codes/Screenshot_20260209_151849_Chrome2_01nHXgw.jpg
Normal file
|
After Width: | Height: | Size: 86 KiB |
BIN
media/qr_codes/Screenshot_20260209_151849_Chrome2_Bcrszzw.jpg
Normal file
|
After Width: | Height: | Size: 86 KiB |
BIN
media/qr_codes/Screenshot_20260209_151849_Chrome2_H9df2Q9.jpg
Normal file
|
After Width: | Height: | Size: 86 KiB |
BIN
media/qr_codes/Screenshot_20260209_151849_Chrome2_SCoAxg5.jpg
Normal file
|
After Width: | Height: | Size: 86 KiB |
BIN
media/qr_codes/pp_p2p_my_qrcode_1770415916858.jpg
Normal file
|
After Width: | Height: | Size: 60 KiB |
BIN
media/qr_codes/pp_p2p_my_qrcode_1770415916858_KX7DOVY.jpg
Normal file
|
After Width: | Height: | Size: 60 KiB |
BIN
media/qr_codes/pp_p2p_my_qrcode_1770415916858_TJsiNFV.jpg
Normal file
|
After Width: | Height: | Size: 60 KiB |
BIN
media/qr_codes/pp_p2p_my_qrcode_1770415916858_WjhvGBE.jpg
Normal file
|
After Width: | Height: | Size: 60 KiB |
BIN
media/qr_codes/pp_p2p_my_qrcode_1770415916858_v0cAbpF.jpg
Normal file
|
After Width: | Height: | Size: 60 KiB |
@ -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;
|
||||
}
|
||||