diff --git a/ai/__pycache__/__init__.cpython-311.pyc b/ai/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000..9beeae7 Binary files /dev/null and b/ai/__pycache__/__init__.cpython-311.pyc differ diff --git a/ai/__pycache__/local_ai_api.cpython-311.pyc b/ai/__pycache__/local_ai_api.cpython-311.pyc new file mode 100644 index 0000000..ae12bda Binary files /dev/null and b/ai/__pycache__/local_ai_api.cpython-311.pyc differ diff --git a/config/__pycache__/settings.cpython-311.pyc b/config/__pycache__/settings.cpython-311.pyc index 96bce55..77800c6 100644 Binary files a/config/__pycache__/settings.cpython-311.pyc and b/config/__pycache__/settings.cpython-311.pyc differ diff --git a/config/__pycache__/urls.cpython-311.pyc b/config/__pycache__/urls.cpython-311.pyc index 0b85e94..c56ff4a 100644 Binary files a/config/__pycache__/urls.cpython-311.pyc and b/config/__pycache__/urls.cpython-311.pyc differ diff --git a/config/settings.py b/config/settings.py index 291d043..87bab3e 100644 --- a/config/settings.py +++ b/config/settings.py @@ -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') + diff --git a/config/urls.py b/config/urls.py index bcfc074..b5bd462 100644 --- a/config/urls.py +++ b/config/urls.py @@ -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) \ No newline at end of file diff --git a/core/__pycache__/models.cpython-311.pyc b/core/__pycache__/models.cpython-311.pyc index e061640..505bc48 100644 Binary files a/core/__pycache__/models.cpython-311.pyc and b/core/__pycache__/models.cpython-311.pyc differ diff --git a/core/__pycache__/urls.cpython-311.pyc b/core/__pycache__/urls.cpython-311.pyc index 5a69659..e4054c9 100644 Binary files a/core/__pycache__/urls.cpython-311.pyc and b/core/__pycache__/urls.cpython-311.pyc differ diff --git a/core/__pycache__/views.cpython-311.pyc b/core/__pycache__/views.cpython-311.pyc index 2a36fd6..c371fb2 100644 Binary files a/core/__pycache__/views.cpython-311.pyc and b/core/__pycache__/views.cpython-311.pyc differ diff --git a/core/migrations/0001_initial.py b/core/migrations/0001_initial.py new file mode 100644 index 0000000..a44c770 --- /dev/null +++ b/core/migrations/0001_initial.py @@ -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)), + ], + ), + ] diff --git a/core/migrations/0002_adminconfig_rentaloption_usersession_and_more.py b/core/migrations/0002_adminconfig_rentaloption_usersession_and_more.py new file mode 100644 index 0000000..8ebe9ae --- /dev/null +++ b/core/migrations/0002_adminconfig_rentaloption_usersession_and_more.py @@ -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')), + ], + ), + ] diff --git a/core/migrations/0003_gameproject_script_code_and_more.py b/core/migrations/0003_gameproject_script_code_and_more.py new file mode 100644 index 0000000..983eabd --- /dev/null +++ b/core/migrations/0003_gameproject_script_code_and_more.py @@ -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), + ), + ] diff --git a/core/migrations/__pycache__/0001_initial.cpython-311.pyc b/core/migrations/__pycache__/0001_initial.cpython-311.pyc new file mode 100644 index 0000000..6208d0b Binary files /dev/null and b/core/migrations/__pycache__/0001_initial.cpython-311.pyc differ diff --git a/core/migrations/__pycache__/0002_adminconfig_rentaloption_usersession_and_more.cpython-311.pyc b/core/migrations/__pycache__/0002_adminconfig_rentaloption_usersession_and_more.cpython-311.pyc new file mode 100644 index 0000000..bd7c8ab Binary files /dev/null and b/core/migrations/__pycache__/0002_adminconfig_rentaloption_usersession_and_more.cpython-311.pyc differ diff --git a/core/migrations/__pycache__/0003_gameproject_script_code_and_more.cpython-311.pyc b/core/migrations/__pycache__/0003_gameproject_script_code_and_more.cpython-311.pyc new file mode 100644 index 0000000..f3c5b84 Binary files /dev/null and b/core/migrations/__pycache__/0003_gameproject_script_code_and_more.cpython-311.pyc differ diff --git a/core/models.py b/core/models.py index 71a8362..60d6bbd 100644 --- a/core/models.py +++ b/core/models.py @@ -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}" \ No newline at end of file diff --git a/core/templates/base.html b/core/templates/base.html index 1e7e5fb..4ab3a6b 100644 --- a/core/templates/base.html +++ b/core/templates/base.html @@ -1,25 +1,97 @@ +{% load static %} - - + - - {% block title %}Knowledge Base{% endblock %} - {% if project_description %} - - - - {% endif %} - {% if project_image_url %} - - - {% endif %} - {% load static %} - - {% block head %}{% endblock %} + + + {{ title|default:"AI Game Forge" }} + + + + + + + {% block extra_css %}{% endblock %} - - {% block content %}{% endblock %} - + +
+
+ {% if messages %} + {% for message in messages %} +
+ {{ message }} + +
+ {% endfor %} + {% endif %} + + {% block content %}{% endblock %} +
+
+ + + + + {% block extra_js %}{% endblock %} + diff --git a/core/templates/core/admin_dashboard.html b/core/templates/core/admin_dashboard.html new file mode 100644 index 0000000..bea0ecd --- /dev/null +++ b/core/templates/core/admin_dashboard.html @@ -0,0 +1,66 @@ +{% extends 'base.html' %} + +{% block content %} +
+

Painel de Controle

+ + Criar Novo Jogo +
+ +
+
+
+

Gerenciar Jogos

+
+ + + + + + + + + + + + {% for project in projects %} + + + + + + + + {% endfor %} + +
TítuloGêneroDataStatusAções
{{ project.title }}{{ project.get_genre_display }}{{ project.created_at|date:"d/m/Y" }} + {% if project.is_active %} + Ativo + {% else %} + Inativo + {% endif %} + + Editar + Excluir +
+
+
+
+
+
+

Configurar Pagamentos

+

Edite os preços e QR Codes para aluguel de tempo.

+
+ {% for option in rental_options %} +
+
+
{{ option.title }}
+
R$ {{ option.price }}
+
+ Editar +
+ {% endfor %} +
+
+
+
+{% endblock %} \ No newline at end of file diff --git a/core/templates/core/admin_game_form.html b/core/templates/core/admin_game_form.html new file mode 100644 index 0000000..9dde2b1 --- /dev/null +++ b/core/templates/core/admin_game_form.html @@ -0,0 +1,62 @@ +{% extends 'base.html' %} + +{% block content %} +
+
+
+

{% if project %}Editar Jogo: {{ project.title }}{% else %}Criar Novo Jogo{% endif %}

+
+ {% csrf_token %} +
+
+ + +
+
+ + +
+
+ +
+ + +
+ + +
+
+ +
+ + + Se usar a opção de IA, este campo será preenchido automaticamente. +
+ +
+ + {% if project.image_reference %} +
+ Ref +
+ {% endif %} + +
+ +
+ + VOLTAR +
+
+
+
+
+{% endblock %} \ No newline at end of file diff --git a/core/templates/core/admin_login.html b/core/templates/core/admin_login.html new file mode 100644 index 0000000..907d29e --- /dev/null +++ b/core/templates/core/admin_login.html @@ -0,0 +1,19 @@ +{% extends 'base.html' %} + +{% block content %} +
+
+
+

Acesso Administrativo

+

Insira sua Chave Privada única para gerenciar a plataforma.

+
+ {% csrf_token %} +
+ +
+ +
+
+
+
+{% endblock %} diff --git a/core/templates/core/admin_rental_form.html b/core/templates/core/admin_rental_form.html new file mode 100644 index 0000000..fd42ba0 --- /dev/null +++ b/core/templates/core/admin_rental_form.html @@ -0,0 +1,46 @@ +{% extends 'base.html' %} + +{% block content %} +
+
+
+

Editar Opção de Pagamento

+
+ {% csrf_token %} +
+ + +
+
+
+ + +
+
+ + +
+
+
+ + + {% if option.qr_code_1 %} +
Atual: {{ option.qr_code_1.name }}
+ {% endif %} +
+
+ + + {% if option.qr_code_2 %} +
Atual: {{ option.qr_code_2.name }}
+ {% endif %} +
+
+ + VOLTAR +
+
+
+
+
+{% endblock %} diff --git a/core/templates/core/catalog.html b/core/templates/core/catalog.html new file mode 100644 index 0000000..a96989c --- /dev/null +++ b/core/templates/core/catalog.html @@ -0,0 +1,34 @@ +{% extends 'base.html' %} + +{% block content %} +
+

Biblioteca de Jogos

+

Escolha um título e alugue tempo para jogar instantaneamente.

+
+ +
+ {% for project in projects %} +
+
+
+ {% if project.image_reference %} + {{ project.title }} + {% else %} + 🎮 + {% endif %} +
+
+ {{ project.get_genre_display }} +

{{ project.title }}

+

{{ project.prompt|truncatewords:20 }}

+ ALUGAR TEMPO +
+
+
+ {% empty %} +
+

Nenhum jogo disponível no momento.

+
+ {% endfor %} +
+{% endblock %} diff --git a/core/templates/core/index.html b/core/templates/core/index.html index faec813..eacf0f5 100644 --- a/core/templates/core/index.html +++ b/core/templates/core/index.html @@ -1,145 +1,44 @@ -{% extends "base.html" %} - -{% block title %}{{ project_name }}{% endblock %} - -{% block head %} - - - - -{% endblock %} +{% extends 'base.html' %} {% block content %} -
-
-

Analyzing your requirements and generating your app…

-
- Loading… +
+
+

Bem-vindo ao Futuro

+

Acesse os melhores jogos gerados por IA. Gere seu código de acesso único e comece a jogar agora.

-

AppWizzy AI is collecting your requirements and applying the first changes.

-

This page will refresh automatically as the plan is implemented.

-

- Runtime: Django {{ django_version }} · Python {{ python_version }} - — UTC {{ current_time|date:"Y-m-d H:i:s" }} -

-
-
- -{% endblock %} \ No newline at end of file +
+
+ +
+
+
+ {% csrf_token %} +
+ + +
+ +
+
+
+
+ {% csrf_token %} +
+ + +
+ +
+
+
+
+
+ +{% endblock %} diff --git a/core/templates/core/play.html b/core/templates/core/play.html new file mode 100644 index 0000000..d8bee65 --- /dev/null +++ b/core/templates/core/play.html @@ -0,0 +1,78 @@ +{% extends 'base.html' %} + +{% block content %} +
+
+

{{ game.title }}

+

Sessão validada. Divirta-se!

+
+ Sair do Jogo +
+ +
+ {% if game.script_code %} + + {% else %} +
+ + +
+

Este jogo ainda não possui script. Exibindo simulador.

+
Aguardando IA
+
+
+ {% endif %} +
+ +
+
+

Sobre o Jogo

+

{{ game.prompt }}

+
+
+
+
Status da Sessão
+
Ativo ✅
+
Sua compra foi validada com sucesso.
+
+
+
+ +{% block extra_js %} +{% if not game.script_code %} + +{% endif %} +{% endblock %} +{% endblock %} \ No newline at end of file diff --git a/core/templates/core/purchase.html b/core/templates/core/purchase.html new file mode 100644 index 0000000..4225c69 --- /dev/null +++ b/core/templates/core/purchase.html @@ -0,0 +1,183 @@ +{% extends 'base.html' %} + +{% block content %} +
+
+
+

{{ game.title }}

+

{{ game.prompt }}

+
+
💳
+
+
Instruções de Aluguel
+
Selecione um plano, clique em "CLICK NO QR", escaneie o código e aguarde 3 minutos para receber seu código de validação.
+
+
+ +

Escolha o tempo de acesso:

+
+ {% for option in options %} +
+
+
{{ option.title }}
+
R$ {{ option.price }}
+
+
+ {% endfor %} +
+
+
+ +
+
+
+
🛒
+

Selecione uma opção

+

Escolha um plano ao lado para começar.

+
+ +
+

+ +
+ +

Clique para visualizar os códigos de pagamento

+
+ +
+
+
+
+ QR 1 +
+
QR CODE 1
+
+
+
+ QR 2 +
+
QR CODE 2
+
+
+ +
+
Aguardando confirmação do pagamento...
+
03:00
+
+
+
+
+ +
+
Pagamento Concluído! Seu código de 6 dígitos:
+
+
Copie e cole no campo abaixo para liberar o jogo.
+
+
+ +
+ +
+ {% csrf_token %} + +
+ + +
+ +
+
+
+
+
+ +{% block extra_js %} + + +{% endblock %} +{% endblock %} \ No newline at end of file diff --git a/core/templates/core/studio.html b/core/templates/core/studio.html new file mode 100644 index 0000000..6478f0c --- /dev/null +++ b/core/templates/core/studio.html @@ -0,0 +1,144 @@ +{% extends 'base.html' %} +{% load static %} + +{% block content %} +
+
+ +
+
+
+
+

{{ project.title }}

+ {{ project.get_genre_display }} +
+
+ +
+ +
+ +

{{ project.config_json.ai_summary }}

+
+ +
+ +
+ {{ config_json }} +
+
+ +
+ + + +
+
+
+ + +
+
+
+
Live Preview (Web HTML5)
+
+ + +
+
+ +
+ +
+

AI Engine Initialized

+

Press 'Run Build' to compile and play

+
+
+ +
+
+
+

0

+ Bugs Detected +
+
+
+
+

142

+ Assets Generated +
+
+
+
+

60

+ FPS Stable +
+
+
+
+
+
+
+{% endblock %} + +{% block scripts %} + +{% endblock %} diff --git a/core/urls.py b/core/urls.py index 6299e3d..b7cb8c5 100644 --- a/core/urls.py +++ b/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//', views.admin_edit_game, name='admin_edit_game'), + path('admin/delete-game//', views.admin_delete_game, name='admin_delete_game'), + path('admin/edit-rental//', views.admin_edit_rental, name='admin_edit_rental'), + + # User + path('catalog/', views.catalog, name='catalog'), + path('purchase//', views.purchase_game, name='purchase_game'), + path('generate-purchase///', views.generate_purchase, name='generate_purchase'), + path('play//', views.play_game, name='play_game'), +] \ No newline at end of file diff --git a/core/views.py b/core/views.py index c9aed12..c7b1bdf 100644 --- a/core/views.py +++ b/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 and ending with . " + "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}'}) \ No newline at end of file diff --git a/media/game_references/Screenshot_20250517-233850.jpg b/media/game_references/Screenshot_20250517-233850.jpg new file mode 100644 index 0000000..ab71189 Binary files /dev/null and b/media/game_references/Screenshot_20250517-233850.jpg differ diff --git a/media/game_references/Screenshot_20250517-233850_0adtLXv.jpg b/media/game_references/Screenshot_20250517-233850_0adtLXv.jpg new file mode 100644 index 0000000..ab71189 Binary files /dev/null and b/media/game_references/Screenshot_20250517-233850_0adtLXv.jpg differ diff --git a/media/game_references/Screenshot_20250517-233850_gCfOipa.jpg b/media/game_references/Screenshot_20250517-233850_gCfOipa.jpg new file mode 100644 index 0000000..ab71189 Binary files /dev/null and b/media/game_references/Screenshot_20250517-233850_gCfOipa.jpg differ diff --git a/media/game_references/Screenshot_20250608-133058.jpg b/media/game_references/Screenshot_20250608-133058.jpg new file mode 100644 index 0000000..2919c67 Binary files /dev/null and b/media/game_references/Screenshot_20250608-133058.jpg differ diff --git a/media/game_references/Screenshot_20250608-133058_tHzVFw5.jpg b/media/game_references/Screenshot_20250608-133058_tHzVFw5.jpg new file mode 100644 index 0000000..2919c67 Binary files /dev/null and b/media/game_references/Screenshot_20250608-133058_tHzVFw5.jpg differ diff --git a/media/qr_codes/Screenshot_20260209_151849_Chrome2.jpg b/media/qr_codes/Screenshot_20260209_151849_Chrome2.jpg new file mode 100644 index 0000000..8d86a24 Binary files /dev/null and b/media/qr_codes/Screenshot_20260209_151849_Chrome2.jpg differ diff --git a/media/qr_codes/Screenshot_20260209_151849_Chrome2_01nHXgw.jpg b/media/qr_codes/Screenshot_20260209_151849_Chrome2_01nHXgw.jpg new file mode 100644 index 0000000..8d86a24 Binary files /dev/null and b/media/qr_codes/Screenshot_20260209_151849_Chrome2_01nHXgw.jpg differ diff --git a/media/qr_codes/Screenshot_20260209_151849_Chrome2_Bcrszzw.jpg b/media/qr_codes/Screenshot_20260209_151849_Chrome2_Bcrszzw.jpg new file mode 100644 index 0000000..8d86a24 Binary files /dev/null and b/media/qr_codes/Screenshot_20260209_151849_Chrome2_Bcrszzw.jpg differ diff --git a/media/qr_codes/Screenshot_20260209_151849_Chrome2_H9df2Q9.jpg b/media/qr_codes/Screenshot_20260209_151849_Chrome2_H9df2Q9.jpg new file mode 100644 index 0000000..8d86a24 Binary files /dev/null and b/media/qr_codes/Screenshot_20260209_151849_Chrome2_H9df2Q9.jpg differ diff --git a/media/qr_codes/Screenshot_20260209_151849_Chrome2_SCoAxg5.jpg b/media/qr_codes/Screenshot_20260209_151849_Chrome2_SCoAxg5.jpg new file mode 100644 index 0000000..8d86a24 Binary files /dev/null and b/media/qr_codes/Screenshot_20260209_151849_Chrome2_SCoAxg5.jpg differ diff --git a/media/qr_codes/pp_p2p_my_qrcode_1770415916858.jpg b/media/qr_codes/pp_p2p_my_qrcode_1770415916858.jpg new file mode 100644 index 0000000..a14b617 Binary files /dev/null and b/media/qr_codes/pp_p2p_my_qrcode_1770415916858.jpg differ diff --git a/media/qr_codes/pp_p2p_my_qrcode_1770415916858_KX7DOVY.jpg b/media/qr_codes/pp_p2p_my_qrcode_1770415916858_KX7DOVY.jpg new file mode 100644 index 0000000..a14b617 Binary files /dev/null and b/media/qr_codes/pp_p2p_my_qrcode_1770415916858_KX7DOVY.jpg differ diff --git a/media/qr_codes/pp_p2p_my_qrcode_1770415916858_TJsiNFV.jpg b/media/qr_codes/pp_p2p_my_qrcode_1770415916858_TJsiNFV.jpg new file mode 100644 index 0000000..a14b617 Binary files /dev/null and b/media/qr_codes/pp_p2p_my_qrcode_1770415916858_TJsiNFV.jpg differ diff --git a/media/qr_codes/pp_p2p_my_qrcode_1770415916858_WjhvGBE.jpg b/media/qr_codes/pp_p2p_my_qrcode_1770415916858_WjhvGBE.jpg new file mode 100644 index 0000000..a14b617 Binary files /dev/null and b/media/qr_codes/pp_p2p_my_qrcode_1770415916858_WjhvGBE.jpg differ diff --git a/media/qr_codes/pp_p2p_my_qrcode_1770415916858_v0cAbpF.jpg b/media/qr_codes/pp_p2p_my_qrcode_1770415916858_v0cAbpF.jpg new file mode 100644 index 0000000..a14b617 Binary files /dev/null and b/media/qr_codes/pp_p2p_my_qrcode_1770415916858_v0cAbpF.jpg differ diff --git a/static/css/custom.css b/static/css/custom.css index 925f6ed..ccc4921 100644 --- a/static/css/custom.css +++ b/static/css/custom.css @@ -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; +} \ No newline at end of file