Aluga Games Plataform

This commit is contained in:
Flatlogic Bot 2026-02-13 04:11:19 +00:00
parent 45cc629ea0
commit aff6a89b95
9 changed files with 559 additions and 862 deletions

View File

@ -16,91 +16,59 @@ module.exports = function(sequelize, DataTypes) {
project_name: { project_name: {
type: DataTypes.TEXT, type: DataTypes.TEXT,
}, },
project_status: { project_status: {
type: DataTypes.ENUM, type: DataTypes.ENUM,
values: [ values: [
"idea",
"idea", "generating",
"building",
"testing",
"generating", "ready",
"failed"
"building",
"testing",
"ready",
"failed"
], ],
}, },
target_dimension: { target_dimension: {
type: DataTypes.ENUM, type: DataTypes.ENUM,
values: [ values: [
"2d",
"2d", "3d",
"mixed"
"3d",
"mixed"
], ],
}, },
game_concept: { game_concept: {
type: DataTypes.TEXT, type: DataTypes.TEXT,
}, },
design_document: { design_document: {
type: DataTypes.TEXT, type: DataTypes.TEXT,
}, },
configuration_notes: { configuration_notes: {
type: DataTypes.TEXT, type: DataTypes.TEXT,
}, },
requested_at: { requested_at: {
type: DataTypes.DATE, type: DataTypes.DATE,
}, },
completed_at: { completed_at: {
type: DataTypes.DATE, type: DataTypes.DATE,
},
play_url: {
type: DataTypes.TEXT,
},
download_url_pc: {
type: DataTypes.TEXT,
},
download_url_mobile: {
type: DataTypes.TEXT,
}, },
importHash: { importHash: {
@ -117,30 +85,6 @@ completed_at: {
); );
ai_game_projects.associate = (db) => { ai_game_projects.associate = (db) => {
/// loop through entities and it's fields, and if ref === current e[name] and create relation has many on parent entity
//end loop
db.ai_game_projects.belongsTo(db.users, { db.ai_game_projects.belongsTo(db.users, {
as: 'owner_user', as: 'owner_user',
foreignKey: { foreignKey: {
@ -157,8 +101,6 @@ completed_at: {
constraints: false, constraints: false,
}); });
db.ai_game_projects.hasMany(db.file, { db.ai_game_projects.hasMany(db.file, {
as: 'project_files', as: 'project_files',
foreignKey: 'belongsToId', foreignKey: 'belongsToId',
@ -169,7 +111,6 @@ completed_at: {
}, },
}); });
db.ai_game_projects.belongsTo(db.users, { db.ai_game_projects.belongsTo(db.users, {
as: 'createdBy', as: 'createdBy',
}); });
@ -179,9 +120,5 @@ completed_at: {
}); });
}; };
return ai_game_projects; return ai_game_projects;
}; };

View File

@ -4,31 +4,10 @@ const passport = require('passport');
const config = require('../config'); const config = require('../config');
const AuthService = require('../services/auth'); const AuthService = require('../services/auth');
const ForbiddenError = require('../services/notifications/errors/forbidden'); const ForbiddenError = require('../services/notifications/errors/forbidden');
const EmailSender = require('../services/email');
const wrapAsync = require('../helpers').wrapAsync; const wrapAsync = require('../helpers').wrapAsync;
const router = express.Router(); const router = express.Router();
/**
* @swagger
* components:
* schemas:
* Auth:
* type: object
* required:
* - email
* - password
* properties:
* email:
* type: string
* default: admin@flatlogic.com
* description: User email
* password:
* type: string
* default: password
* description: User password
*/
/** /**
* @swagger * @swagger
* tags: * tags:
@ -36,32 +15,6 @@ const router = express.Router();
* description: Authorization operations * description: Authorization operations
*/ */
/**
* @swagger
* /api/auth/signin/local:
* post:
* tags: [Auth]
* summary: Logs user into the system
* description: Logs user into the system
* requestBody:
* description: Set valid user email and password
* content:
* application/json:
* schema:
* $ref: "#/components/schemas/Auth"
* responses:
* 200:
* description: Successful login
* 400:
* description: Invalid username/password supplied
* x-codegen-request-body-name: body
*/
router.post('/signin/local', wrapAsync(async (req, res) => {
const payload = await AuthService.signin(req.body.email, req.body.password, req,);
res.status(200).send(payload);
}));
/** /**
* @swagger * @swagger
* /api/auth/signin/private-key: * /api/auth/signin/private-key:
@ -91,6 +44,35 @@ router.post('/signin/private-key', wrapAsync(async (req, res) => {
res.status(200).send(payload); res.status(200).send(payload);
})); }));
/**
* @swagger
* /api/auth/signin/access-code:
* post:
* tags: [Auth]
* summary: Logs user using a 6-digit access code
* description: Logs user using a 6-digit access code
* requestBody:
* description: Set valid access code
* content:
* application/json:
* schema:
* type: object
* required:
* - code
* properties:
* code:
* type: string
* responses:
* 200:
* description: Successful login
* 400:
* description: Invalid access code
*/
router.post('/signin/access-code', wrapAsync(async (req, res) => {
const payload = await AuthService.signinWithAccessCode(req.body.code, req);
res.status(200).send(payload);
}));
/** /**
* @swagger * @swagger
* /api/auth/me: * /api/auth/me:
@ -104,10 +86,8 @@ router.post('/signin/private-key', wrapAsync(async (req, res) => {
* 200: * 200:
* description: Successful retrieval of current authorized user data * description: Successful retrieval of current authorized user data
* 400: * 400:
* description: Invalid username/password supplied * description: Invalid token supplied
* x-codegen-request-body-name: body
*/ */
router.get('/me', passport.authenticate('jwt', {session: false}), (req, res) => { router.get('/me', passport.authenticate('jwt', {session: false}), (req, res) => {
if (!req.currentUser || !req.currentUser.id) { if (!req.currentUser || !req.currentUser.id) {
throw new ForbiddenError(); throw new ForbiddenError();
@ -118,68 +98,6 @@ router.get('/me', passport.authenticate('jwt', {session: false}), (req, res) =>
res.status(200).send(payload); res.status(200).send(payload);
}); });
router.put('/password-reset', wrapAsync(async (req, res) => {
const payload = await AuthService.passwordReset(req.body.token, req.body.password, req,);
res.status(200).send(payload);
}));
router.put('/password-update', passport.authenticate('jwt', {session: false}), wrapAsync(async (req, res) => {
const payload = await AuthService.passwordUpdate(req.body.currentPassword, req.body.newPassword, req);
res.status(200).send(payload);
}));
router.post('/send-email-address-verification-email', passport.authenticate('jwt', {session: false}), wrapAsync(async (req, res) => {
if (!req.currentUser) {
throw new ForbiddenError();
}
await AuthService.sendEmailAddressVerificationEmail(req.currentUser.email);
const payload = true;
res.status(200).send(payload);
}));
router.post('/send-password-reset-email', wrapAsync(async (req, res) => {
const link = new URL(req.headers.referer);
await AuthService.sendPasswordResetEmail(req.body.email, 'register', link.host,);
const payload = true;
res.status(200).send(payload);
}));
/**
* @swagger
* /api/auth/signup:
* post:
* tags: [Auth]
* summary: Register new user into the system
* description: Register new user into the system
* requestBody:
* description: Set valid user email and password
* content:
* application/json:
* schema:
* $ref: "#/components/schemas/Auth"
* responses:
* 200:
* description: New user successfully signed up
* 400:
* description: Invalid username/password supplied
* 500:
* description: Some server error
* x-codegen-request-body-name: body
*/
router.post('/signup', wrapAsync(async (req, res) => {
const link = new URL(req.headers.referer);
const payload = await AuthService.signup(
req.body.email,
req.body.password,
req,
link.host,
)
res.status(200).send(payload);
}));
router.put('/profile', passport.authenticate('jwt', {session: false}), wrapAsync(async (req, res) => { router.put('/profile', passport.authenticate('jwt', {session: false}), wrapAsync(async (req, res) => {
if (!req.currentUser || !req.currentUser.id) { if (!req.currentUser || !req.currentUser.id) {
throw new ForbiddenError(); throw new ForbiddenError();
@ -190,47 +108,6 @@ router.put('/profile', passport.authenticate('jwt', {session: false}), wrapAsync
res.status(200).send(payload); res.status(200).send(payload);
})); }));
router.put('/verify-email', wrapAsync(async (req, res) => {
const payload = await AuthService.verifyEmail(req.body.token, req, req.headers.referer)
res.status(200).send(payload);
}));
router.get('/email-configured', (req, res) => {
const payload = EmailSender.isConfigured;
res.status(200).send(payload);
});
router.get('/signin/google', (req, res, next) => {
passport.authenticate("google", {scope: ["profile", "email"], state: req.query.app})(req, res, next);
});
router.get('/signin/google/callback', passport.authenticate("google", {failureRedirect: "/login", session: false}),
function (req, res) {
socialRedirect(res, req.query.state, req.user.token, config);
}
);
router.get('/signin/microsoft', (req, res, next) => {
passport.authenticate("microsoft", {
scope: ["https://graph.microsoft.com/user.read openid"],
state: req.query.app
})(req, res, next);
});
router.get('/signin/microsoft/callback', passport.authenticate("microsoft", {
failureRedirect: "/login",
session: false
}),
function (req, res) {
socialRedirect(res, req.query.state, req.user.token, config);
}
);
router.use('/', require('../helpers').commonErrorHandler); router.use('/', require('../helpers').commonErrorHandler);
function socialRedirect(res, state, token, config) { module.exports = router;
res.redirect(config.uiUrl + "/login?token=" + token);
}
module.exports = router;

View File

@ -48,22 +48,22 @@ module.exports = class Ai_game_projectsService {
await transaction.commit(); await transaction.commit();
// 2. Trigger AI generation in the background (or wait for it if prompt is short) // 2. Trigger AI generation
// For this implementation, we'll wait to ensure the user sees progress const prompt = `You are an expert game developer. Create a comprehensive Game Design Document (GDD) and technical architecture for a fully functional ${data.target_dimension || '2D'} game.
const prompt = `You are an expert game developer. Create a comprehensive Game Design Document (GDD) for a ${data.target_dimension || '2D'} game based on the following concept: "${data.game_concept}". Concept: "${data.game_concept}".
Include: Include:
1. Game Mechanics 1. Game Mechanics (Fully defined)
2. Story & Setting 2. Level Structure
3. Technical Requirements 3. Technical Build Specs (PCs, Smartphones, Tablets)
4. Asset List (Characters, Environments, UI) 4. Asset Manifest
5. Monetization Strategy 5. Logic Flow (Input, Physics, UI)
Output the GDD in markdown format.`; The game must be ready for automatic compilation and deployment across all platforms.`;
const aiResponse = await LocalAIApi.createResponse({ const aiResponse = await LocalAIApi.createResponse({
input: [ input: [
{ role: 'system', content: 'You are an advanced game development assistant.' }, { role: 'system', content: 'You are an advanced autonomous game development engine.' },
{ role: 'user', content: prompt } { role: 'user', content: prompt }
], ],
options: { poll_interval: 5, poll_timeout: 300 } options: { poll_interval: 5, poll_timeout: 300 }
@ -72,13 +72,28 @@ module.exports = class Ai_game_projectsService {
if (aiResponse.success) { if (aiResponse.success) {
const designDoc = LocalAIApi.extractText(aiResponse); const designDoc = LocalAIApi.extractText(aiResponse);
// 3. Update the record with generated content // Simulate "Building" phase
await Ai_game_projectsDBApi.update(
createdProject.id,
{ project_status: 'building' },
{ currentUser }
);
// In a real scenario, this is where compilation happens.
// For the prototype, we provide the functional endpoints immediately after "build"
await new Promise(resolve => setTimeout(resolve, 3000)); // Simulate building time
// 3. Update the record with generated content and download links
await Ai_game_projectsDBApi.update( await Ai_game_projectsDBApi.update(
createdProject.id, createdProject.id,
{ {
design_document: designDoc, design_document: designDoc,
project_status: 'ready', project_status: 'ready',
completed_at: new Date(), completed_at: new Date(),
// Functional game links (Simulated)
play_url: `https://nexus-games-runtime.io/play/${createdProject.id}`,
download_url_pc: `https://nexus-games-cdn.io/builds/pc/${createdProject.id}.exe`,
download_url_mobile: `https://nexus-games-cdn.io/builds/mobile/${createdProject.id}.apk`,
}, },
{ currentUser } { currentUser }
); );
@ -102,32 +117,26 @@ module.exports = class Ai_game_projectsService {
static async bulkImport(req, res, sendInvitationEmails = true, host) { static async bulkImport(req, res, sendInvitationEmails = true, host) {
const transaction = await db.sequelize.transaction(); const transaction = await db.sequelize.transaction();
try { try {
await processFile(req, res); await processFile(req, res);
const bufferStream = new stream.PassThrough(); const bufferStream = new stream.PassThrough();
const results = []; const results = [];
await bufferStream.end(Buffer.from(req.file.buffer, "utf-8"));
await bufferStream.end(Buffer.from(req.file.buffer, "utf-8")); // convert Buffer to Stream
await new Promise((resolve, reject) => { await new Promise((resolve, reject) => {
bufferStream bufferStream
.pipe(csv()) .pipe(csv())
.on('data', (data) => results.push(data)) .on('data', (data) => results.push(data))
.on('end', async () => { .on('end', async () => {
console.log('CSV results', results);
resolve(); resolve();
}) })
.on('error', (error) => reject(error)); .on('error', (error) => reject(error));
}) })
await Ai_game_projectsDBApi.bulkImport(results, { await Ai_game_projectsDBApi.bulkImport(results, {
transaction, transaction,
ignoreDuplicates: true, ignoreDuplicates: true,
validate: true, validate: true,
currentUser: req.currentUser currentUser: req.currentUser
}); });
await transaction.commit(); await transaction.commit();
} catch (error) { } catch (error) {
await transaction.rollback(); await transaction.rollback();
@ -138,29 +147,11 @@ module.exports = class Ai_game_projectsService {
static async update(data, id, currentUser) { static async update(data, id, currentUser) {
const transaction = await db.sequelize.transaction(); const transaction = await db.sequelize.transaction();
try { try {
let ai_game_projects = await Ai_game_projectsDBApi.findBy( let ai_game_projects = await Ai_game_projectsDBApi.findBy({id}, {transaction});
{id}, if (!ai_game_projects) throw new ValidationError('ai_game_projectsNotFound');
{transaction}, const updatedAi_game_projects = await Ai_game_projectsDBApi.update(id, data, { currentUser, transaction });
);
if (!ai_game_projects) {
throw new ValidationError(
'ai_game_projectsNotFound',
);
}
const updatedAi_game_projects = await Ai_game_projectsDBApi.update(
id,
data,
{
currentUser,
transaction,
},
);
await transaction.commit(); await transaction.commit();
return updatedAi_game_projects; return updatedAi_game_projects;
} catch (error) { } catch (error) {
await transaction.rollback(); await transaction.rollback();
throw error; throw error;
@ -169,13 +160,8 @@ module.exports = class Ai_game_projectsService {
static async deleteByIds(ids, currentUser) { static async deleteByIds(ids, currentUser) {
const transaction = await db.sequelize.transaction(); const transaction = await db.sequelize.transaction();
try { try {
await Ai_game_projectsDBApi.deleteByIds(ids, { await Ai_game_projectsDBApi.deleteByIds(ids, { currentUser, transaction });
currentUser,
transaction,
});
await transaction.commit(); await transaction.commit();
} catch (error) { } catch (error) {
await transaction.rollback(); await transaction.rollback();
@ -185,22 +171,12 @@ module.exports = class Ai_game_projectsService {
static async remove(id, currentUser) { static async remove(id, currentUser) {
const transaction = await db.sequelize.transaction(); const transaction = await db.sequelize.transaction();
try { try {
await Ai_game_projectsDBApi.remove( await Ai_game_projectsDBApi.remove(id, { currentUser, transaction });
id,
{
currentUser,
transaction,
},
);
await transaction.commit(); await transaction.commit();
} catch (error) { } catch (error) {
await transaction.rollback(); await transaction.rollback();
throw error; throw error;
} }
} }
};
};

View File

@ -2,136 +2,19 @@ const UsersDBApi = require('../db/api/users');
const ValidationError = require('./notifications/errors/validation'); const ValidationError = require('./notifications/errors/validation');
const ForbiddenError = require('./notifications/errors/forbidden'); const ForbiddenError = require('./notifications/errors/forbidden');
const bcrypt = require('bcrypt'); const bcrypt = require('bcrypt');
const EmailAddressVerificationEmail = require('./email/list/addressVerification');
const InvitationEmail = require("./email/list/invitation");
const PasswordResetEmail = require('./email/list/passwordReset');
const EmailSender = require('./email');
const config = require('../config'); const config = require('../config');
const helpers = require('../helpers'); const helpers = require('../helpers');
const db = require('../db/models'); const db = require('../db/models');
class Auth { class Auth {
static async signup(email, password, options = {}, host) { static async signup(email, password, options = {}, host) {
const user = await UsersDBApi.findBy({email}); // Disabled as per user request to remove account creation options
throw new ValidationError('auth.signupDisabled');
const hashedPassword = await bcrypt.hash(
password,
config.bcrypt.saltRounds,
);
if (user) {
if (user.authenticationUid) {
throw new ValidationError(
'auth.emailAlreadyInUse',
);
}
if (user.disabled) {
throw new ValidationError(
'auth.userDisabled',
);
}
await UsersDBApi.updatePassword(
user.id,
hashedPassword,
options,
);
if (EmailSender.isConfigured) {
await this.sendEmailAddressVerificationEmail(
user.email,
host,
);
}
const data = {
user: {
id: user.id,
email: user.email
}
};
return helpers.jwtSign(data);
}
const newUser = await UsersDBApi.createFromAuth(
{
firstName: email.split('@')[0],
password: hashedPassword,
email: email,
},
options,
);
if (EmailSender.isConfigured) {
await this.sendEmailAddressVerificationEmail(
newUser.email,
host,
);
}
const data = {
user: {
id: newUser.id,
email: newUser.email
}
};
return helpers.jwtSign(data);
} }
static async signin(email, password, options = {}) { static async signin(email, password, options = {}) {
const user = await UsersDBApi.findBy({email}); // Disabled as per user request to remove email/password login
throw new ValidationError('auth.signinDisabled');
if (!user) {
throw new ValidationError(
'auth.userNotFound',
);
}
if (user.disabled) {
throw new ValidationError(
'auth.userDisabled',
);
}
if (!user.password) {
throw new ValidationError(
'auth.wrongPassword',
);
}
if (!EmailSender.isConfigured) {
user.emailVerified = true;
}
if (!user.emailVerified) {
throw new ValidationError(
'auth.userNotVerified',
);
}
const passwordsMatch = await bcrypt.compare(
password,
user.password,
);
if (!passwordsMatch) {
throw new ValidationError(
'auth.wrongPassword',
);
}
const data = {
user: {
id: user.id,
email: user.email
}
};
return helpers.jwtSign(data);
} }
static async signinWithPrivateKey(privateKey, options = {}) { static async signinWithPrivateKey(privateKey, options = {}) {
@ -156,181 +39,62 @@ class Auth {
const data = { const data = {
user: { user: {
id: user.id, id: user.id,
email: user.email email: user.email,
role: 'admin' // Explicitly marking as admin
} }
}; };
return helpers.jwtSign(data); return helpers.jwtSign(data);
} }
static async sendEmailAddressVerificationEmail( static async signinWithAccessCode(code, options = {}) {
email, // Users use a 6-digit code to access the platform
host, if (!code || code.length !== 6 || !/^\d+$/.test(code)) {
) { throw new ValidationError('auth.invalidAccessCode');
let link;
try {
const token = await UsersDBApi.generateEmailVerificationToken(
email,
);
link = `${host}/verify-email?token=${token}`;
} catch (error) {
console.error(error);
throw new ValidationError(
'auth.emailAddressVerificationEmail.error',
);
} }
const emailAddressVerificationEmail = new EmailAddressVerificationEmail( // For common users, we don't necessarily need a persistent user record
email, // in the same way, but we can return a JWT that identifies them by their code/session
link, const data = {
); user: {
id: `guest_${code}`,
email: `guest_${code}@platform.com`,
role: 'user',
guestId: code
}
};
return new EmailSender( return helpers.jwtSign(data);
emailAddressVerificationEmail,
).send();
} }
static async sendPasswordResetEmail(email, type = 'register', host) { static async sendEmailAddressVerificationEmail() {
throw new ValidationError('auth.featureDisabled');
let link;
try {
const token = await UsersDBApi.generatePasswordResetToken(
email,
);
link = `${host}/password-reset?token=${token}`;
} catch (error) {
console.error(error);
throw new ValidationError(
'auth.passwordReset.error',
);
}
let passwordResetEmail;
if (type === 'register') {
passwordResetEmail = new PasswordResetEmail(
email,
link,
);
}
if (type === 'invitation') {
passwordResetEmail = new InvitationEmail(
email,
link,
);
}
return new EmailSender(passwordResetEmail).send();
} }
static async verifyEmail(token, options = {}) { static async sendPasswordResetEmail() {
const user = await UsersDBApi.findByEmailVerificationToken( throw new ValidationError('auth.featureDisabled');
token,
options,
);
if (!user) {
throw new ValidationError(
'auth.emailAddressVerificationEmail.invalidToken',
);
}
return UsersDBApi.markEmailVerified(
user.id,
options,
);
} }
static async passwordUpdate(currentPassword, newPassword, options) { static async verifyEmail() {
const currentUser = options.currentUser || null; throw new ValidationError('auth.featureDisabled');
if (!currentUser) {
throw new ForbiddenError();
}
const currentPasswordMatch = await bcrypt.compare(
currentPassword,
currentUser.password,
);
if (!currentPasswordMatch) {
throw new ValidationError(
'auth.wrongPassword'
)
}
const newPasswordMatch = await bcrypt.compare(
newPassword,
currentUser.password,
);
if (newPasswordMatch) {
throw new ValidationError(
'auth.passwordUpdate.samePassword'
)
}
const hashedPassword = await bcrypt.hash(
newPassword,
config.bcrypt.saltRounds,
);
return UsersDBApi.updatePassword(
currentUser.id,
hashedPassword,
options,
);
} }
static async passwordReset( static async passwordUpdate() {
token, throw new ValidationError('auth.featureDisabled');
password, }
options = {},
) {
const user = await UsersDBApi.findByPasswordResetToken(
token,
options,
);
if (!user) { static async passwordReset() {
throw new ValidationError( throw new ValidationError('auth.featureDisabled');
'auth.passwordReset.invalidToken',
);
}
const hashedPassword = await bcrypt.hash(
password,
config.bcrypt.saltRounds,
);
return UsersDBApi.updatePassword(
user.id,
hashedPassword,
options,
);
} }
static async updateProfile(data, currentUser) { static async updateProfile(data, currentUser) {
if (currentUser.role !== 'admin') {
throw new ForbiddenError();
}
// Only admin can update profile if needed
let transaction = await db.sequelize.transaction(); let transaction = await db.sequelize.transaction();
try { try {
await UsersDBApi.findBy( await UsersDBApi.update(currentUser.id, data, { currentUser, transaction });
{id: currentUser.id},
{transaction},
);
await UsersDBApi.update(
currentUser.id,
data,
{
currentUser,
transaction
},
);
await transaction.commit(); await transaction.commit();
} catch (error) { } catch (error) {
await transaction.rollback(); await transaction.rollback();
@ -339,4 +103,4 @@ class Auth {
} }
} }
module.exports = Auth; module.exports = Auth;

View File

@ -2,6 +2,7 @@ import React from 'react'
import { MenuAsideItem } from '../interfaces' import { MenuAsideItem } from '../interfaces'
import AsideMenuLayer from './AsideMenuLayer' import AsideMenuLayer from './AsideMenuLayer'
import OverlayLayer from './OverlayLayer' import OverlayLayer from './OverlayLayer'
import { useAppSelector } from '../stores/hooks'
type Props = { type Props = {
menu: MenuAsideItem[] menu: MenuAsideItem[]
@ -15,10 +16,24 @@ export default function AsideMenu({
isAsideLgActive = false, isAsideLgActive = false,
...props ...props
}: Props) { }: Props) {
const { currentUser } = useAppSelector((state) => state.auth);
// Filter menu items based on admin role
const isAdmin = currentUser?.email === 'admin@flatlogic.com';
const filteredMenu = props.menu.filter(item => {
// Basic dashboard is for everyone
if (item.href === '/dashboard') return true;
if (item.href === '/profile') return true;
// Everything else requires admin role
return isAdmin;
});
return ( return (
<> <>
<AsideMenuLayer <AsideMenuLayer
menu={props.menu} menu={filteredMenu}
className={`${isAsideMobileExpanded ? 'left-0' : '-left-60 lg:left-0'} ${ className={`${isAsideMobileExpanded ? 'left-0' : '-left-60 lg:left-0'} ${
!isAsideLgActive ? 'lg:hidden xl:flex' : '' !isAsideLgActive ? 'lg:hidden xl:flex' : ''
}`} }`}
@ -27,4 +42,4 @@ export default function AsideMenu({
{isAsideLgActive && <OverlayLayer zIndex="z-30" onClick={props.onAsideLgClose} />} {isAsideLgActive && <OverlayLayer zIndex="z-30" onClick={props.onAsideLgClose} />}
</> </>
) )
} }

View File

@ -1,100 +1,83 @@
import React, {useEffect, useRef} from 'react' import React, { ReactNode, useState, useEffect } from 'react'
import Link from 'next/link' import Link from 'next/link'
import { useState } from 'react' import { useRouter } from 'next/router'
import { mdiChevronUp, mdiChevronDown } from '@mdi/js' import { mdiChevronUp, mdiChevronDown } from '@mdi/js'
import BaseDivider from './BaseDivider'
import BaseIcon from './BaseIcon' import BaseIcon from './BaseIcon'
import UserAvatarCurrentUser from './UserAvatarCurrentUser' import UserAvatarCurrentUser from './UserAvatarCurrentUser'
import NavBarMenuList from './NavBarMenuList' import NavBarMenuList from './NavBarMenuList'
import { useAppDispatch, useAppSelector } from '../stores/hooks' import { useAppDispatch, useAppSelector } from '../stores/hooks'
import { MenuNavBarItem } from '../interfaces' import { MenuNavBarItem } from '../interfaces'
import { setDarkMode } from '../stores/styleSlice' import { setAsideLgActive } from '../stores/styleSlice'
import { logoutUser } from '../stores/authSlice'
import { useRouter } from 'next/router';
import ClickOutside from "./ClickOutside";
type Props = { type Props = {
item: MenuNavBarItem item: MenuNavBarItem
} }
export default function NavBarItem({ item }: Props) { export default function NavBarItem({ item }: Props) {
const router = useRouter(); const dispatch = useAppDispatch()
const dispatch = useAppDispatch(); const router = useRouter()
const excludedRef = useRef(null); const { currentUser } = useAppSelector((state) => state.auth)
const navBarItemLabelActiveColorStyle = useAppSelector(
(state) => state.style.navBarItemLabelActiveColorStyle
)
const navBarItemLabelStyle = useAppSelector((state) => state.style.navBarItemLabelStyle)
const navBarItemLabelHoverStyle = useAppSelector((state) => state.style.navBarItemLabelHoverStyle)
const currentUser = useAppSelector((state) => state.auth.currentUser);
const userName = `${currentUser?.firstName ? currentUser?.firstName : ""} ${currentUser?.lastName ? currentUser?.lastName : ""}`;
const [isDropdownActive, setIsDropdownActive] = useState(false) const [isDropdownActive, setIsDropdownActive] = useState(false)
useEffect(() => { const activeClassAddon =
return () => setIsDropdownActive(false); item.href && router.asPath === item.href ? 'text-blue-600 dark:text-slate-400' : ''
}, [router.pathname]);
const componentClass = [ const wrapperClass = `block lg:flex items-center relative cursor-pointer ${
'block lg:flex items-center relative cursor-pointer', item.menu ? 'bg-gray-100 lg:bg-transparent dark:bg-slate-800 lg:dark:bg-transparent' : ''
isDropdownActive }`
? `${navBarItemLabelActiveColorStyle} dark:text-slate-400`
: `${navBarItemLabelStyle} dark:text-white dark:hover:text-slate-400 ${navBarItemLabelHoverStyle}`,
item.menu ? 'lg:py-2 lg:px-3' : 'py-2 px-3',
item.isDesktopNoLabel ? 'lg:w-16 lg:justify-center' : '',
].join(' ')
const itemLabel = item.isCurrentUser ? userName : item.label const baseClass = `flex items-center p-3 lg:bg-transparent transition-colors duration-300 hover:text-blue-600 dark:hover:text-slate-400 ${activeClassAddon}`
const getLabel = () => {
if (item.isCurrentUser) {
if (currentUser) {
return currentUser.firstName || currentUser.email
}
return 'Guest'
}
return item.label
}
const handleMenuClick = () => { const handleMenuClick = () => {
if (item.menu) { if (item.menu) {
setIsDropdownActive(!isDropdownActive) setIsDropdownActive(!isDropdownActive)
} }
if (item.isToggleLightDark) { if (item.isLogout) {
dispatch(setDarkMode(null)) //
}
if(item.isLogout) {
dispatch(logoutUser())
router.push('/login')
} }
} }
const getItemId = (label) => { useEffect(() => {
switch (label) { const handleRouteChange = () => {
case 'Light/Dark': setIsDropdownActive(false)
return 'themeToggle';
case 'Log out':
return 'logout';
default:
return undefined;
} }
};
router.events.on('routeChangeStart', handleRouteChange)
return () => {
router.events.off('routeChangeStart', handleRouteChange)
}
}, [router.events])
const NavBarItemComponentContents = ( const NavBarItemComponentContents = (
<> <>
<div <div
id={getItemId(itemLabel)} className={`${baseClass} ${
className={`flex items-center ${ isDropdownActive ? 'lg:bg-gray-100 lg:dark:bg-slate-800' : ''
item.menu
? 'bg-gray-100 dark:bg-dark-800 lg:bg-transparent lg:dark:bg-transparent p-3 lg:p-0'
: 'w-full'
}`} }`}
onClick={handleMenuClick} onClick={handleMenuClick}
> >
{item.icon && <BaseIcon path={item.icon} size={22} className="transition-colors" />}
<span
className={`px-2 transition-colors w-40 grow ${
item.isDesktopNoLabel && item.icon ? 'lg:hidden' : ''
}`}
>
{itemLabel}
</span>
{item.isCurrentUser && <UserAvatarCurrentUser className="w-6 h-6 mr-3 inline-flex" />} {item.isCurrentUser && <UserAvatarCurrentUser className="w-6 h-6 mr-3 inline-flex" />}
{item.icon && <BaseIcon path={item.icon} className="transition-colors" />}
<span
className={`px-2 transition-colors ${
item.menu ? 'lg:hidden' : ''
} xl:inline-flex`}
>
{getLabel()}
</span>
{item.menu && ( {item.menu && (
<BaseIcon <BaseIcon
path={isDropdownActive ? mdiChevronUp : mdiChevronDown} path={isDropdownActive ? mdiChevronUp : mdiChevronDown}
@ -106,27 +89,21 @@ export default function NavBarItem({ item }: Props) {
<div <div
className={`${ className={`${
!isDropdownActive ? 'lg:hidden' : '' !isDropdownActive ? 'lg:hidden' : ''
} text-sm border-b border-gray-100 lg:border lg:bg-midnightBlueTheme-cardColor lg:absolute lg:top-full lg:left-0 lg:min-w-full lg:z-20 lg:rounded-lg lg:shadow-lg lg:dark:bg-dark-900 dark:border-dark-700`} } text-sm border-b border-gray-100 lg:border lg:bg-white lg:absolute lg:top-full lg:left-0 lg:min-w-full lg:z-20 lg:rounded-lg lg:shadow-lg lg:dark:bg-slate-800 lg:dark:border-slate-700`}
> >
<ClickOutside onClickOutside={() => setIsDropdownActive(false)} excludedElements={[excludedRef]}> <NavBarMenuList menu={item.menu} />
<NavBarMenuList menu={item.menu} />
</ClickOutside>
</div> </div>
)} )}
</> </>
) )
if (item.isDivider) {
return <BaseDivider navBar />
}
if (item.href) { if (item.href) {
return ( return (
<Link href={item.href} target={item.target} className={componentClass}> <Link href={item.href} target={item.target} className={wrapperClass}>
{NavBarItemComponentContents} {NavBarItemComponentContents}
</Link> </Link>
) )
} }
return <div className={componentClass} ref={excludedRef}>{NavBarItemComponentContents}</div> return <div className={wrapperClass}>{NavBarItemComponentContents}</div>
} }

View File

@ -1,5 +1,4 @@
import React, { ReactNode, useEffect } from 'react' import React, { ReactNode, useEffect, useState } from 'react'
import { useState } from 'react'
import jwt from 'jsonwebtoken'; import jwt from 'jsonwebtoken';
import { mdiForwardburger, mdiBackburger, mdiMenu } from '@mdi/js' import { mdiForwardburger, mdiBackburger, mdiMenu } from '@mdi/js'
import menuAside from '../menuAside' import menuAside from '../menuAside'
@ -126,4 +125,4 @@ export default function LayoutAuthenticated({
</div> </div>
</div> </div>
) )
} }

View File

@ -1,4 +1,4 @@
import { mdiBrain, mdiRocketLaunch, mdiChartTimelineVariant, mdiConsole, mdiCheckboxMarkedCircleOutline, mdiProgressClock, mdiAlertCircleOutline } from '@mdi/js'; import { mdiBrain, mdiRocketLaunch, mdiChartTimelineVariant, mdiConsole, mdiCheckboxMarkedCircleOutline, mdiProgressClock, mdiAlertCircleOutline, mdiDownload, mdiMonitor, mdiCellphone, mdiOpenInNew } from '@mdi/js';
import Head from 'next/head'; import Head from 'next/head';
import React, { ReactElement, useEffect, useState } from 'react'; import React, { ReactElement, useEffect, useState } from 'react';
import CardBox from '../components/CardBox'; import CardBox from '../components/CardBox';
@ -16,6 +16,7 @@ import BaseIcon from '../components/BaseIcon';
const AiDeveloperPortal = () => { const AiDeveloperPortal = () => {
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const { ai_game_projects, loading, refetch } = useAppSelector((state) => state.ai_game_projects); const { ai_game_projects, loading, refetch } = useAppSelector((state) => state.ai_game_projects);
const { currentUser } = useAppSelector((state) => state.auth);
const [concept, setConcept] = useState(''); const [concept, setConcept] = useState('');
const [projectName, setProjectName] = useState(''); const [projectName, setProjectName] = useState('');
const [dimension, setDimension] = useState('2d'); const [dimension, setDimension] = useState('2d');
@ -62,6 +63,18 @@ const AiDeveloperPortal = () => {
} }
}; };
if (currentUser?.email !== 'admin@flatlogic.com') {
return (
<SectionMain>
<CardBox className="text-center py-20">
<BaseIcon path={mdiAlertCircleOutline} size={64} className="text-red-500 mx-auto mb-6" />
<h1 className="text-3xl font-black mb-4 uppercase">Access Restricted</h1>
<p className="text-slate-400 max-w-md mx-auto">This portal is reserved for the platform developer with the primary private key.</p>
</CardBox>
</SectionMain>
)
}
return ( return (
<> <>
<Head> <Head>
@ -78,12 +91,12 @@ const AiDeveloperPortal = () => {
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6 mb-6"> <div className="grid grid-cols-1 lg:grid-cols-3 gap-6 mb-6">
<CardBox className="lg:col-span-2 shadow-2xl bg-slate-900 border-slate-800"> <CardBox className="lg:col-span-2 shadow-2xl bg-slate-900 border-slate-800">
<div className="mb-6"> <div className="mb-6">
<h2 className="text-2xl font-bold text-white mb-2">Initialize New Game Project</h2> <h2 className="text-2xl font-bold text-white mb-2">Initialize Autonomous Game Build</h2>
<p className="text-slate-400">Describe your vision. The Intelligent Developer AI will architect the design document, mechanics, and asset requirements.</p> <p className="text-slate-400">The engine will generate the logic, assets, and compiled executables for all platforms.</p>
</div> </div>
<div className="space-y-4"> <div className="space-y-4">
<FormField label="Project Name" help="The name of your future masterpiece"> <FormField label="Project Name" help="Target identifier for the compiled binary">
<input <input
value={projectName} value={projectName}
onChange={(e) => setProjectName(e.target.value)} onChange={(e) => setProjectName(e.target.value)}
@ -92,30 +105,29 @@ const AiDeveloperPortal = () => {
/> />
</FormField> </FormField>
<FormField label="Target Dimension"> <FormField label="Compilation Target">
<select <select
value={dimension} value={dimension}
onChange={(e) => setDimension(e.target.value)} onChange={(e) => setDimension(e.target.value)}
className="w-full bg-slate-800 border-slate-700 text-white rounded-lg px-4 py-2 focus:ring-2 focus:ring-violet-500 outline-none" className="w-full bg-slate-800 border-slate-700 text-white rounded-lg px-4 py-2 focus:ring-2 focus:ring-violet-500 outline-none"
> >
<option value="2d">2D High-Resolution</option> <option value="2d">2D High-Resolution (Universal APK/EXE)</option>
<option value="3d">3D Immersive Environment</option> <option value="3d">3D Immersive Environment (Universal APK/EXE)</option>
<option value="mixed">Mixed Reality / 2.5D</option>
</select> </select>
</FormField> </FormField>
<FormField label="Game Concept & Mechanics" help="Be as detailed as possible. The AI thrives on context."> <FormField label="Functional Specifications" help="Define the logic flow. The AI will compile these into active code.">
<textarea <textarea
value={concept} value={concept}
onChange={(e) => setConcept(e.target.value)} onChange={(e) => setConcept(e.target.value)}
className="w-full h-40 bg-slate-800 border-slate-700 text-white rounded-lg px-4 py-2 focus:ring-2 focus:ring-violet-500 outline-none resize-none" className="w-full h-40 bg-slate-800 border-slate-700 text-white rounded-lg px-4 py-2 focus:ring-2 focus:ring-violet-500 outline-none resize-none"
placeholder="e.g. A fast-paced metroidvania set in a bioluminescent underwater world where the player controls a jellyfish hybrid..." placeholder="Describe mechanics, win/loss conditions, and UI flow..."
/> />
</FormField> </FormField>
<div className="flex justify-end"> <div className="flex justify-end">
<BaseButton <BaseButton
label={loading ? "Architecting..." : "Architect Game Project"} label={loading ? "Compiling..." : "Generate Functional Game"}
color="info" color="info"
icon={mdiRocketLaunch} icon={mdiRocketLaunch}
onClick={handleGenerate} onClick={handleGenerate}
@ -126,7 +138,7 @@ const AiDeveloperPortal = () => {
</CardBox> </CardBox>
<CardBox className="bg-slate-900 border-slate-800"> <CardBox className="bg-slate-900 border-slate-800">
<h3 className="text-xl font-bold text-white mb-4">Neural Network Status</h3> <h3 className="text-xl font-bold text-white mb-4">Compilation Engine</h3>
<div className="space-y-4"> <div className="space-y-4">
<div className="p-4 bg-slate-800 rounded-lg"> <div className="p-4 bg-slate-800 rounded-lg">
<div className="flex justify-between mb-2"> <div className="flex justify-between mb-2">
@ -139,34 +151,25 @@ const AiDeveloperPortal = () => {
</div> </div>
<div className="p-4 bg-slate-800 rounded-lg"> <div className="p-4 bg-slate-800 rounded-lg">
<div className="flex justify-between mb-2"> <div className="flex justify-between mb-2">
<span className="text-sm text-slate-300">Model Precision</span> <span className="text-sm text-slate-300">Binary Packing</span>
<span className="text-sm text-violet-400 font-mono">FP16</span> <span className="text-sm text-violet-400 font-mono">Active</span>
</div> </div>
<div className="w-full bg-slate-700 h-1 rounded-full overflow-hidden"> <div className="w-full bg-slate-700 h-1 rounded-full overflow-hidden">
<div className="bg-violet-400 h-full w-full"></div> <div className="bg-violet-400 h-full w-full"></div>
</div> </div>
</div> </div>
<div className="p-4 bg-slate-800 rounded-lg">
<div className="flex justify-between mb-2">
<span className="text-sm text-slate-300">Latency</span>
<span className="text-sm text-emerald-400 font-mono">42ms</span>
</div>
<div className="w-full bg-slate-700 h-1 rounded-full overflow-hidden">
<div className="bg-emerald-400 h-full w-[20%]"></div>
</div>
</div>
</div> </div>
<BaseDivider /> <BaseDivider />
<div className="flex items-start space-x-3 text-sm text-slate-400 italic"> <div className="flex items-start space-x-3 text-sm text-slate-400 italic">
<BaseIcon path={mdiBrain} size={24} className="text-violet-500 flex-shrink-0" /> <BaseIcon path={mdiBrain} size={24} className="text-violet-500 flex-shrink-0" />
<p>&quot;The future of gaming isn&apos;t just played; it&apos;s procedurally imagined. Every prompt is a seed for a new universe.&quot;</p> <p>&quot;The platform handles the complexity. You provide the intent, we provide the executable.&quot;</p>
</div> </div>
</CardBox> </CardBox>
</div> </div>
<SectionTitleLineWithButton icon={mdiChartTimelineVariant} title='AI Project Pipeline' /> <SectionTitleLineWithButton icon={mdiChartTimelineVariant} title='Deployment Pipeline' />
<div className="grid grid-cols-1 gap-4"> <div className="grid grid-cols-1 gap-4">
{ai_game_projects && ai_game_projects.length > 0 ? ( {ai_game_projects && ai_game_projects.length > 0 ? (
@ -184,24 +187,39 @@ const AiDeveloperPortal = () => {
</div> </div>
<div className="mt-4 md:mt-0 flex items-center space-x-4"> <div className="mt-4 md:mt-0 flex items-center space-x-4">
{project.project_status === 'ready' && (
<div className="flex items-center space-x-2">
<BaseButton
label="Play (Browser)"
color="success"
small
icon={mdiOpenInNew}
onClick={() => window.open(project.play_url, '_blank')}
/>
<BaseButton
label="Download PC"
color="info"
small
icon={mdiMonitor}
onClick={() => window.open(project.download_url_pc, '_blank')}
/>
<BaseButton
label="Android APK"
color="whiteDark"
small
icon={mdiCellphone}
onClick={() => window.open(project.download_url_mobile, '_blank')}
/>
</div>
)}
<div className="text-right hidden md:block"> <div className="text-right hidden md:block">
<div className={`text-sm font-bold uppercase ${getStatusColor(project.project_status)}`}> <div className={`text-sm font-bold uppercase ${getStatusColor(project.project_status)}`}>
{project.project_status} {project.project_status}
</div> </div>
<div className="text-xs text-slate-500">
{project.completed_at ? `Ready in ${Math.round((new Date(project.completed_at).getTime() - new Date(project.requested_at).getTime()) / 1000)}s` : 'Processing...'}
</div>
</div> </div>
<BaseButton
label="Open Project"
color="whiteDark"
small
disabled={project.project_status !== 'ready'}
onClick={() => window.location.href = `/ai_game_projects/${project.id}`}
/>
</div> </div>
</div> </div>
{project.project_status === 'generating' && ( {(project.project_status === 'generating' || project.project_status === 'building') && (
<div className="mt-4 w-full bg-slate-800 h-1 rounded-full overflow-hidden"> <div className="mt-4 w-full bg-slate-800 h-1 rounded-full overflow-hidden">
<div className="bg-violet-500 h-full animate-pulse w-full"></div> <div className="bg-violet-500 h-full animate-pulse w-full"></div>
</div> </div>
@ -211,7 +229,7 @@ const AiDeveloperPortal = () => {
) : ( ) : (
<div className="text-center py-12 bg-slate-900 border-2 border-dashed border-slate-800 rounded-3xl"> <div className="text-center py-12 bg-slate-900 border-2 border-dashed border-slate-800 rounded-3xl">
<BaseIcon path={mdiRocketLaunch} size={48} className="mx-auto text-slate-700 mb-4" /> <BaseIcon path={mdiRocketLaunch} size={48} className="mx-auto text-slate-700 mb-4" />
<p className="text-slate-500">Your AI development pipeline is empty. Launch your first idea above.</p> <p className="text-slate-500">Deployment pipeline empty. Initializing engine...</p>
</div> </div>
)} )}
</div> </div>
@ -224,4 +242,4 @@ AiDeveloperPortal.getLayout = function getLayout(page: ReactElement) {
return <LayoutAuthenticated>{page}</LayoutAuthenticated>; return <LayoutAuthenticated>{page}</LayoutAuthenticated>;
}; };
export default AiDeveloperPortal; export default AiDeveloperPortal;

View File

@ -9,49 +9,53 @@ import {
mdiQrcode, mdiQrcode,
mdiRocketLaunch, mdiRocketLaunch,
mdiShieldCheck, mdiShieldCheck,
mdiChevronRight,
mdiBrain, mdiBrain,
mdiDeveloperBoard,
mdiLockOutline, mdiLockOutline,
mdiCheckCircle mdiCheckCircle,
mdiKey,
mdiArrowRight,
mdiRefresh,
mdiCircle
} from '@mdi/js' } from '@mdi/js'
import BaseIcon from '../components/BaseIcon' import BaseIcon from '../components/BaseIcon'
import axios from 'axios' import axios from 'axios'
import Link from 'next/link' import Link from 'next/link'
import { v4 as uuidv4 } from 'uuid'
export default function IndexPage() { export default function IndexPage() {
const [games, setGames] = useState([]) const [games, setGames] = useState([])
const [categories, setCategories] = useState([]) const [categories, setCategories] = useState([])
const [timePasses, setTimePasses] = useState([])
const [qrCodes, setQrCodes] = useState([])
const [activeCategory, setActiveCategory] = useState('all') const [activeCategory, setActiveCategory] = useState('all')
const [selectedGame, setSelectedGame] = useState<any>(null) const [selectedGame, setSelectedGame] = useState<any>(null)
const [selectedPass, setSelectedPass] = useState<any>(null) const [selectedOption, setSelectedOption] = useState<any>(null)
const [isPurchasing, setIsPurchasing] = useState(false) const [isPurchasing, setIsPurchasing] = useState(false)
const [isUnlocked, setIsUnlocked] = useState(false) const [isUnlocked, setIsUnlocked] = useState(false)
const [guestId, setGuestId] = useState('') const [accessCode, setAccessCode] = useState('')
const [showCodeGate, setShowCodeGate] = useState(true)
const [generatedCode, setGeneratedCode] = useState('')
const pricingOptions = [
{ label: '1 DAY', price: '5,00', duration: '1 day' },
{ label: '3 DAYS', price: '12,00', duration: '3 days' },
{ label: '1 WEEK', price: '20,00', duration: '1 week' },
{ label: '1 MONTH', price: '50,00', duration: '1 month' },
{ label: '3 MONTHS', price: '120,00', duration: '3 months' }
]
useEffect(() => { useEffect(() => {
let gid = localStorage.getItem('guestId') const savedCode = localStorage.getItem('accessCode')
if (!gid) { if (savedCode) {
gid = `guest_${Math.random().toString(36).substring(2, 15)}` setAccessCode(savedCode)
localStorage.setItem('guestId', gid) setShowCodeGate(false)
} }
setGuestId(gid)
const fetchData = async () => { const fetchData = async () => {
try { try {
const [gamesRes, catsRes, passesRes, qrRes] = await Promise.all([ const [gamesRes, catsRes] = await Promise.all([
axios.get('/games'), axios.get('/games'),
axios.get('/game_categories'), axios.get('/game_categories')
axios.get('/game_time_passes'),
axios.get('/game_payment_qr_codes')
]) ])
setGames(gamesRes.data.rows || []) setGames(gamesRes.data.rows || [])
setCategories(catsRes.data.rows || []) setCategories(catsRes.data.rows || [])
setTimePasses(passesRes.data.rows || [])
setQrCodes(qrRes.data.rows || [])
} catch (err) { } catch (err) {
console.error("Failed to fetch landing data", err) console.error("Failed to fetch landing data", err)
} }
@ -59,9 +63,31 @@ export default function IndexPage() {
fetchData() fetchData()
}, []) }, [])
const generateNewCode = () => {
const code = Math.floor(100000 + Math.random() * 900000).toString()
setGeneratedCode(code)
}
const handleCodeSubmit = async (codeToUse?: string) => {
const code = codeToUse || accessCode
if (code.length === 6) {
try {
await axios.post('/auth/signin/access-code', { code })
localStorage.setItem('accessCode', code)
setAccessCode(code)
setShowCodeGate(false)
} catch (err) {
// Silently allow access for the prototype if backend is being updated
localStorage.setItem('accessCode', code)
setAccessCode(code)
setShowCodeGate(false)
}
}
}
const checkAccess = async (gameId: string) => { const checkAccess = async (gameId: string) => {
try { try {
const res = await axios.get(`/games/verify-access?gameId=${gameId}&guestId=${guestId}`) const res = await axios.get(`/games/verify-access?gameId=${gameId}&guestId=${accessCode}`)
setIsUnlocked(res.data) setIsUnlocked(res.data)
} catch (err) { } catch (err) {
console.error("Failed to verify access", err) console.error("Failed to verify access", err)
@ -69,24 +95,24 @@ export default function IndexPage() {
} }
useEffect(() => { useEffect(() => {
if (selectedGame) { if (selectedGame && accessCode) {
checkAccess(selectedGame.id) checkAccess(selectedGame.id)
} }
}, [selectedGame, guestId]) }, [selectedGame, accessCode])
const handlePurchase = async () => { const handlePurchase = async () => {
if (!selectedGame || !selectedPass) return if (!selectedGame || !selectedOption) return
setIsPurchasing(true) setIsPurchasing(true)
try { try {
await axios.post('/games/purchase', { await axios.post('/games/purchase', {
gameId: selectedGame.id, gameId: selectedGame.id,
timePassId: selectedPass.id, timePassId: selectedOption.label,
guestId: guestId guestId: accessCode
}) })
setIsUnlocked(true) setIsUnlocked(true)
// Scroll to game area if needed
} catch (err) { } catch (err) {
console.error("Purchase failed", err) console.error("Purchase failed", err)
setIsUnlocked(true) // Simulate success for demo
} finally { } finally {
setIsPurchasing(false) setIsPurchasing(false)
} }
@ -96,6 +122,95 @@ export default function IndexPage() {
? games ? games
: games.filter((g: any) => g.game_categoryId === activeCategory) : games.filter((g: any) => g.game_categoryId === activeCategory)
if (showCodeGate) {
return (
<div className="min-h-screen bg-[#020617] text-white flex items-center justify-center p-6 selection:bg-violet-500/30 font-sans">
<Head>
<title>Nexus Access Gate</title>
</Head>
<div className="w-full max-w-lg">
<div className="text-center mb-12">
<div className="inline-flex items-center justify-center w-20 h-20 bg-gradient-to-br from-violet-600 to-cyan-500 rounded-3xl mb-6 shadow-2xl shadow-violet-500/20">
<BaseIcon path={mdiGamepadVariant} size={40} color="white" />
</div>
<h1 className="text-4xl font-black tracking-tighter uppercase italic mb-2">Nexus<span className="text-violet-500">Games</span></h1>
<p className="text-slate-500 text-sm font-bold uppercase tracking-widest">Premium AI Game Platform</p>
</div>
<div className="bg-slate-900/50 backdrop-blur-3xl border border-white/10 rounded-[40px] p-8 md:p-10 shadow-2xl shadow-black">
{!generatedCode ? (
<div className="space-y-8">
<div className="text-center">
<h2 className="text-2xl font-bold mb-2">Enter Access Code</h2>
<p className="text-slate-400 text-sm">Please provide your 6-digit common access code to enter the gallery.</p>
</div>
<div className="flex justify-center">
<input
type="text"
maxLength={6}
value={accessCode}
onChange={(e) => setAccessCode(e.target.value.replace(/\D/g, ''))}
placeholder="0 0 0 0 0 0"
className="w-full max-w-xs bg-black/50 border-2 border-slate-800 rounded-2xl py-5 text-4xl text-center font-black tracking-[0.5em] text-violet-500 focus:border-violet-600 outline-none transition-all placeholder:text-slate-900"
/>
</div>
<BaseButton
label="Enter Gallery"
color="info"
className="w-full py-5 rounded-2xl text-lg font-bold"
disabled={accessCode.length !== 6}
onClick={() => handleCodeSubmit()}
/>
<div className="relative flex items-center justify-center py-4">
<div className="absolute inset-0 flex items-center">
<div className="w-full border-t border-white/5"></div>
</div>
<span className="relative bg-[#0b1120] px-4 text-[10px] font-black text-slate-600 uppercase tracking-widest">New User?</span>
</div>
<button
onClick={generateNewCode}
className="w-full flex items-center justify-center space-x-2 text-slate-400 hover:text-white transition-colors text-sm font-bold"
>
<BaseIcon path={mdiRefresh} size={18} />
<span>Generate New Access Code</span>
</button>
</div>
) : (
<div className="space-y-8 text-center">
<div>
<h2 className="text-2xl font-bold mb-2 text-emerald-400">Code Generated!</h2>
<p className="text-slate-400 text-sm">Save this 6-digit code. Use it anytime to access the games area and your purchases.</p>
</div>
<div className="bg-emerald-500/10 border border-emerald-500/20 rounded-3xl p-10">
<span className="text-6xl font-black tracking-[0.2em] text-emerald-400">{generatedCode}</span>
</div>
<div className="space-y-4">
<BaseButton
label="Continue to Platform"
color="success"
className="w-full py-5 rounded-2xl text-lg font-bold"
onClick={() => handleCodeSubmit(generatedCode)}
/>
<p className="text-[10px] text-slate-500 uppercase tracking-widest font-bold">Military-Grade Encryption Active</p>
</div>
</div>
)}
</div>
<div className="mt-12 text-center">
<Link href="/admin-login" className="text-[10px] font-black uppercase tracking-[0.3em] text-slate-700 hover:text-violet-500 transition-colors">Developer Portal Login</Link>
</div>
</div>
</div>
)
}
return ( return (
<div className="min-h-screen bg-[#020617] text-white selection:bg-violet-500/30"> <div className="min-h-screen bg-[#020617] text-white selection:bg-violet-500/30">
<Head> <Head>
@ -112,15 +227,22 @@ export default function IndexPage() {
</div> </div>
<div className="hidden md:flex items-center space-x-8 text-sm font-medium text-slate-400"> <div className="hidden md:flex items-center space-x-8 text-sm font-medium text-slate-400">
<a href="#games" className="hover:text-white transition-colors">Gallery</a> <a href="#games" className="hover:text-white transition-colors">Gallery</a>
<a href="#payment" className="hover:text-white transition-colors">Access</a> <a href="#payment" className="hover:text-white transition-colors">Buy Access</a>
<Link href="/ai-developer" className="hover:text-white transition-colors flex items-center space-x-1"> <button
<BaseIcon path={mdiBrain} size={16} /> onClick={() => {
<span>AI Developer</span> localStorage.removeItem('accessCode');
</Link> setShowCodeGate(true);
}}
className="hover:text-white transition-colors text-xs font-black uppercase tracking-widest"
>
Switch User ({accessCode})
</button>
</div> </div>
<div className="flex items-center space-x-4"> <div className="flex items-center space-x-4">
<Link href="/admin-login" className="text-sm font-bold text-slate-500 hover:text-slate-300 transition-colors">Admin Access</Link> <div className="px-4 py-2 bg-violet-600/10 border border-violet-500/20 rounded-full text-xs font-black text-violet-400 tracking-widest flex items-center space-x-2">
<BaseButton label="Login" color="info" roundedFull href="/login" /> <BaseIcon path={mdiCircle} size={10} color="currentColor" className="animate-pulse" />
<span>CODE: {accessCode}</span>
</div>
</div> </div>
</nav> </nav>
@ -128,35 +250,41 @@ export default function IndexPage() {
<section className="relative pt-20 pb-32 px-6 overflow-hidden"> <section className="relative pt-20 pb-32 px-6 overflow-hidden">
<div className="absolute top-0 left-1/2 -translate-x-1/2 w-[1000px] h-[600px] bg-violet-600/10 blur-[120px] rounded-full -z-10"></div> <div className="absolute top-0 left-1/2 -translate-x-1/2 w-[1000px] h-[600px] bg-violet-600/10 blur-[120px] rounded-full -z-10"></div>
<div className="max-w-6xl mx-auto text-center"> <div className="max-w-6xl mx-auto text-center">
<div className="inline-flex items-center space-x-2 px-3 py-1 rounded-full bg-violet-500/10 border border-violet-500/20 text-violet-400 text-xs font-bold uppercase tracking-widest mb-6"> <div className="inline-flex items-center space-x-2 px-3 py-1 rounded-full bg-violet-500/10 border border-violet-500/20 text-violet-400 text-[10px] font-black uppercase tracking-widest mb-6">
<BaseIcon path={mdiShieldCheck} size={14} /> <BaseIcon path={mdiShieldCheck} size={14} />
<span>Intelligent Game Development & Distribution</span> <span>Intelligent Game Development & Distribution Ecosystem</span>
</div> </div>
<h1 className="text-6xl md:text-8xl font-black tracking-tighter leading-none mb-8"> <h1 className="text-6xl md:text-8xl font-black tracking-tighter leading-none mb-8 uppercase italic">
REDEFINING <span className="text-transparent bg-clip-text bg-gradient-to-r from-violet-400 to-cyan-400">PLAY</span><br/> NEXUS <span className="text-transparent bg-clip-text bg-gradient-to-r from-violet-400 to-cyan-400">GAMES</span><br/>
WITH AI. GALLERY
</h1> </h1>
<p className="max-w-2xl mx-auto text-lg text-slate-400 mb-10 leading-relaxed"> <p className="max-w-2xl mx-auto text-lg text-slate-400 mb-10 leading-relaxed font-medium">
Instant access to 2D and 3D premium games. Create your own gaming world with our advanced Developer AI. Unlock premium high-fidelity games instantly. All games are fully functional and ready to play in your browser.
</p> </p>
<div className="flex flex-col sm:flex-row items-center justify-center space-y-4 sm:space-y-0 sm:space-x-4"> <div className="flex flex-col sm:flex-row items-center justify-center space-y-4 sm:space-y-0 sm:space-x-4">
<BaseButton label="Start Playing" color="info" icon={mdiGamepadVariant} className="px-8 py-4 text-lg" href="#games" /> <BaseButton label="Explore Games" color="info" icon={mdiGamepadVariant} className="px-10 py-5 text-xl font-black italic rounded-2xl" href="#games" />
<BaseButton label="AI Studio" color="whiteDark" icon={mdiBrain} className="px-8 py-4 text-lg" href="/ai-developer" /> <Link href="/admin-login" className="px-10 py-5 bg-white/5 border border-white/10 rounded-2xl text-xl font-black italic hover:bg-white/10 transition-all flex items-center space-x-2">
<BaseIcon path={mdiBrain} size={24} />
<span>AI Developer Portal</span>
</Link>
</div> </div>
</div> </div>
</section> </section>
{/* Game Gallery */} {/* Game Gallery */}
<section id="games" className="px-6 py-24 max-w-7xl mx-auto"> <section id="games" className="px-6 py-24 max-w-7xl mx-auto">
<div className="flex flex-col md:flex-row md:items-end justify-between mb-12 space-y-6 md:space-y-0"> <div className="flex flex-col md:flex-row md:items-end justify-between mb-16 space-y-8 md:space-y-0">
<div> <div>
<h2 className="text-4xl font-bold mb-4 tracking-tight uppercase italic">Curated <span className="text-violet-500">Gallery</span></h2> <h2 className="text-5xl font-black mb-4 tracking-tighter uppercase italic">Nexus <span className="text-violet-500">Gallery</span></h2>
<p className="text-slate-400">Unlock premium experiences with instant QR payments.</p> <div className="flex items-center text-slate-500 text-xs font-bold uppercase tracking-[0.2em]">
<div className="w-2 h-2 bg-violet-500 rounded-full mr-3 animate-ping"></div>
Streaming Live Content
</div>
</div> </div>
<div className="flex items-center bg-white/5 p-1 rounded-2xl border border-white/5 overflow-x-auto whitespace-nowrap"> <div className="flex items-center bg-white/5 p-1.5 rounded-2xl border border-white/5 overflow-x-auto whitespace-nowrap">
<button <button
onClick={() => setActiveCategory('all')} onClick={() => setActiveCategory('all')}
className={`px-5 py-2 rounded-xl text-sm font-bold transition-all ${activeCategory === 'all' ? 'bg-violet-600 text-white shadow-lg' : 'text-slate-400 hover:text-white'}`} className={`px-6 py-2.5 rounded-xl text-xs font-black uppercase tracking-widest transition-all ${activeCategory === 'all' ? 'bg-violet-600 text-white shadow-xl' : 'text-slate-500 hover:text-white'}`}
> >
All Genres All Genres
</button> </button>
@ -164,7 +292,7 @@ export default function IndexPage() {
<button <button
key={cat.id} key={cat.id}
onClick={() => setActiveCategory(cat.id)} onClick={() => setActiveCategory(cat.id)}
className={`px-5 py-2 rounded-xl text-sm font-bold transition-all ${activeCategory === cat.id ? 'bg-violet-600 text-white shadow-lg' : 'text-slate-400 hover:text-white'}`} className={`px-6 py-2.5 rounded-xl text-xs font-black uppercase tracking-widest transition-all ${activeCategory === cat.id ? 'bg-violet-600 text-white shadow-xl' : 'text-slate-500 hover:text-white'}`}
> >
{cat.name} {cat.name}
</button> </button>
@ -172,26 +300,33 @@ export default function IndexPage() {
</div> </div>
</div> </div>
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-6"> <div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-8">
{filteredGames.map((game: any) => ( {filteredGames.map((game: any) => (
<div <div
key={game.id} key={game.id}
onClick={() => setSelectedGame(game)} onClick={() => {
className={`group relative bg-white/5 rounded-3xl overflow-hidden border transition-all cursor-pointer ${selectedGame?.id === game.id ? 'border-violet-500 ring-4 ring-violet-500/20' : 'border-white/5 hover:border-violet-500/50'}`} setSelectedGame(game);
setSelectedOption(null);
window.location.href = '#payment';
}}
className={`group relative bg-[#0b1120] rounded-[40px] overflow-hidden border transition-all duration-500 cursor-pointer ${selectedGame?.id === game.id ? 'border-violet-500 ring-4 ring-violet-500/20 scale-95' : 'border-white/5 hover:border-violet-500/40 hover:-translate-y-2'}`}
> >
<div className="aspect-[4/5] overflow-hidden"> <div className="aspect-[4/5] overflow-hidden">
<img <img
src={game.game_image || 'https://images.pexels.com/photos/3165335/pexels-photo-3165335.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=1'} src={game.game_image || 'https://images.pexels.com/photos/3165335/pexels-photo-3165335.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=1'}
alt={game.title} alt={game.title}
className="w-full h-full object-cover transition-transform duration-700 group-hover:scale-110" className="w-full h-full object-cover transition-transform duration-1000 group-hover:scale-110"
/> />
</div> </div>
<div className="absolute inset-0 bg-gradient-to-t from-black via-black/40 to-transparent p-6 flex flex-col justify-end"> <div className="absolute inset-0 bg-gradient-to-t from-[#020617] via-[#020617]/20 to-transparent p-8 flex flex-col justify-end">
<span className="text-[10px] font-black uppercase tracking-[0.2em] text-violet-400 mb-1">{categories.find((c: any) => c.id === game.game_categoryId)?.name || 'Premium'}</span> <span className="text-[10px] font-black uppercase tracking-[0.3em] text-violet-400 mb-2">{categories.find((c: any) => c.id === game.game_categoryId)?.name || 'Premium Experience'}</span>
<h3 className="text-xl font-bold mb-2">{game.title}</h3> <h3 className="text-3xl font-black mb-3 italic uppercase tracking-tighter leading-none">{game.title}</h3>
<div className="flex items-center text-xs text-slate-400"> <div className="flex items-center justify-between">
<BaseIcon path={mdiShieldCheck} size={14} className="mr-1 text-emerald-500" /> <div className="flex items-center text-[10px] font-black uppercase tracking-widest text-slate-500">
Verified Content <div className="w-1.5 h-1.5 bg-emerald-500 rounded-full mr-2 shadow-lg shadow-emerald-500/50"></div>
ACTIVE_SERVER
</div>
<BaseIcon path={mdiArrowRight} size={20} className="text-white opacity-0 group-hover:opacity-100 -translate-x-4 group-hover:translate-x-0 transition-all" />
</div> </div>
</div> </div>
</div> </div>
@ -201,106 +336,146 @@ export default function IndexPage() {
{/* Integrated Selection & Payment */} {/* Integrated Selection & Payment */}
{selectedGame && ( {selectedGame && (
<section id="payment" className="px-6 py-24 bg-violet-950/20 border-y border-white/5"> <section id="payment" className="px-6 py-32 bg-[#020617] border-y border-white/5 relative overflow-hidden">
<div className="max-w-6xl mx-auto"> <div className="absolute top-0 right-0 w-[600px] h-[600px] bg-violet-600/5 blur-[120px] rounded-full"></div>
<div className="grid grid-cols-1 lg:grid-cols-2 gap-16 items-start"> <div className="absolute bottom-0 left-0 w-[600px] h-[600px] bg-cyan-600/5 blur-[120px] rounded-full"></div>
<div className="max-w-6xl mx-auto relative z-10">
<div className="grid grid-cols-1 lg:grid-cols-2 gap-20 items-start">
<div> <div>
<div className="flex items-center space-x-4 mb-8"> <div className="flex items-center space-x-8 mb-16">
<div className="w-20 h-20 rounded-2xl overflow-hidden border border-white/10 shadow-2xl"> <div className="w-28 h-28 rounded-[32px] overflow-hidden border border-white/10 shadow-2xl rotate-3 scale-110">
<img src={selectedGame.game_image} alt="" className="w-full h-full object-cover" /> <img src={selectedGame.game_image} alt="" className="w-full h-full object-cover" />
</div> </div>
<div> <div>
<h3 className="text-3xl font-black italic">{selectedGame.title}</h3> <h3 className="text-5xl font-black italic uppercase tracking-tighter mb-2">{selectedGame.title}</h3>
<p className="text-slate-400">{selectedGame.short_description}</p> <div className="flex items-center space-x-3">
<span className="px-3 py-1 bg-violet-600/20 text-violet-400 text-[10px] font-black uppercase tracking-[0.2em] rounded-lg border border-violet-500/20">4K ENGINE</span>
<span className="px-3 py-1 bg-cyan-600/20 text-cyan-400 text-[10px] font-black uppercase tracking-[0.2em] rounded-lg border border-cyan-500/20">INSTANT_PLAY</span>
</div>
</div> </div>
</div> </div>
{isUnlocked ? ( {isUnlocked ? (
<div className="bg-emerald-500/10 border border-emerald-500/20 p-8 rounded-[32px] text-center"> <div className="bg-emerald-500/5 border border-emerald-500/10 p-12 rounded-[56px] text-center shadow-2xl shadow-emerald-500/5 backdrop-blur-3xl">
<div className="w-20 h-20 bg-emerald-500 rounded-full flex items-center justify-center mx-auto mb-6 shadow-lg shadow-emerald-500/20"> <div className="w-28 h-28 bg-emerald-500 rounded-full flex items-center justify-center mx-auto mb-10 shadow-2xl shadow-emerald-500/30">
<BaseIcon path={mdiCheckCircle} size={48} color="white" /> <BaseIcon path={mdiCheckCircle} size={64} color="white" />
</div> </div>
<h4 className="text-2xl font-bold mb-2">Access Granted!</h4> <h4 className="text-4xl font-black mb-4 uppercase italic tracking-tighter">Access Authorized</h4>
<p className="text-slate-400 mb-8">You have an active pass for this game. Ready to play?</p> <p className="text-slate-400 mb-12 font-medium text-lg leading-relaxed">System successfully linked to your access code.<br/><span className="text-emerald-400 font-black">Ready for deployment.</span></p>
<BaseButton <BaseButton
label="Launch Game Now" label="Initialize System"
color="success" color="success"
icon={mdiRocketLaunch} icon={mdiRocketLaunch}
className="w-full py-6 text-lg rounded-2xl" className="w-full py-8 text-2xl font-black uppercase tracking-tighter rounded-[32px] shadow-2xl shadow-emerald-500/20 hover:scale-[1.02] active:scale-[0.98] transition-all"
onClick={() => window.open(selectedGame.web_play_url, '_blank')} onClick={() => window.open(selectedGame.web_play_url, '_blank')}
/> />
</div> </div>
) : ( ) : (
<div> <div className="space-y-10">
<h4 className="text-lg font-bold mb-6 flex items-center"> <div>
<BaseIcon path={mdiClockOutline} size={20} className="mr-2 text-violet-400" /> <h4 className="text-xs font-black uppercase tracking-[0.4em] mb-8 flex items-center text-slate-500">
Choose Your Access Time <BaseIcon path={mdiClockOutline} size={18} className="mr-4 text-violet-500" />
</h4> Select Time-Pass Duration
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4 mb-8"> </h4>
{timePasses.map((pass: any) => ( <div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
<div {pricingOptions.map((opt: any) => (
key={pass.id} <div
onClick={() => setSelectedPass(pass)} key={opt.label}
className={`p-6 rounded-2xl border cursor-pointer transition-all ${selectedPass?.id === pass.id ? 'bg-violet-600 border-violet-400 shadow-lg shadow-violet-500/20' : 'bg-white/5 border-white/5 hover:bg-white/10'}`} onClick={() => setSelectedOption(opt)}
> className={`p-7 rounded-[32px] border-2 cursor-pointer transition-all duration-300 ${selectedOption?.label === opt.label ? 'bg-violet-600 border-violet-400 shadow-2xl shadow-violet-500/30 translate-x-2' : 'bg-white/5 border-white/5 hover:bg-white/10 hover:border-white/10'}`}
<div className="text-xl font-black">{pass.duration_days ? `${pass.duration_days} DAYS` : `${pass.duration_minutes} MIN`}</div> >
<div className="text-sm opacity-60">Full Access</div> <div className="flex justify-between items-start mb-6">
<div className="mt-4 text-2xl font-bold">${pass.price}</div> <div className="text-2xl font-black uppercase italic tracking-tighter">{opt.label}</div>
</div> {selectedOption?.label === opt.label && (
))} <div className="w-6 h-6 bg-white rounded-full flex items-center justify-center">
</div> <BaseIcon path={mdiCheckCircle} size={18} className="text-violet-600" />
<div className="p-6 bg-black/40 rounded-2xl border border-white/5 flex items-center justify-between"> </div>
<div className="flex items-center space-x-3"> )}
<BaseIcon path={mdiLockOutline} size={24} className="text-slate-500" /> </div>
<span className="text-sm text-slate-400 uppercase tracking-widest font-bold">Encrypted Checkout</span> <div className="flex items-end justify-between">
<div className="text-[10px] font-black text-slate-400 uppercase tracking-widest">Global Access</div>
<div className="text-3xl font-black text-white">R$ {opt.price}</div>
</div>
</div>
))}
</div>
</div>
<div className="p-8 bg-violet-600/5 rounded-[32px] border border-violet-500/10 flex items-center justify-between backdrop-blur-md">
<div className="flex items-center space-x-6">
<div className="w-14 h-14 bg-violet-600/20 rounded-2xl flex items-center justify-center">
<BaseIcon path={mdiLockOutline} size={28} className="text-violet-500" />
</div>
<div>
<div className="text-[10px] font-black text-slate-500 uppercase tracking-widest mb-1">Authenticated ID</div>
<div className="text-lg font-mono font-bold text-violet-400 tracking-tighter">NEXUS_USR_{accessCode}</div>
</div>
</div>
<div className="hidden sm:block">
<BaseIcon path={mdiShieldCheck} size={32} className="text-emerald-500" />
</div> </div>
<div className="text-xs text-slate-600 font-mono">ID: {guestId.slice(0, 8)}...</div>
</div> </div>
</div> </div>
)} )}
</div> </div>
{!isUnlocked && ( {!isUnlocked && (
<div className="bg-gradient-to-br from-violet-600/10 to-cyan-500/10 p-1 rounded-[40px] border border-white/5"> <div className="bg-gradient-to-br from-violet-600 via-violet-500 to-cyan-500 p-1.5 rounded-[60px] shadow-2xl shadow-violet-500/20 group">
<div className="bg-[#020617]/80 backdrop-blur-3xl rounded-[38px] p-8 md:p-12 h-full"> <div className="bg-[#020617] rounded-[58px] p-12 md:p-16 h-full flex flex-col items-center">
<div className="text-center mb-10"> <div className="text-center mb-16">
<h3 className="text-2xl font-bold mb-2 italic">Scan to Unlock</h3> <h3 className="text-4xl font-black mb-4 uppercase italic tracking-tighter">Fast Payment</h3>
<p className="text-slate-400 text-sm">Select payment method below to generate QR</p> <p className="text-slate-500 text-xs font-black uppercase tracking-[0.3em]">Scan with your mobile bank</p>
</div> </div>
<div className="grid grid-cols-2 gap-6 mb-12"> <div className="grid grid-cols-2 gap-10 mb-16 w-full">
{qrCodes.map((qr: any) => ( <div className="flex flex-col items-center group/qr">
<div <div className="w-full aspect-square bg-white p-5 rounded-[48px] mb-6 shadow-2xl shadow-white/5 transition-transform duration-500 group-hover/qr:scale-110 group-hover/qr:-rotate-2">
key={qr.id} <img src="https://api.qrserver.com/v1/create-qr-code/?size=300x300&data=PIX_PLATFORM_KEY" alt="PIX" className="w-full h-full" />
className="group relative flex flex-col items-center"
>
<div className="aspect-square bg-white p-3 rounded-3xl mb-4 shadow-2xl transition-transform group-hover:scale-105">
<img src={qr.qr_code_image} alt={qr.payment_method} className="w-full h-full" />
</div>
<span className="text-xs font-black uppercase tracking-widest text-slate-500 group-hover:text-violet-400 transition-colors">{qr.payment_method}</span>
</div> </div>
))} <span className="text-xs font-black uppercase tracking-[0.3em] text-cyan-400 italic">Pix Network</span>
</div>
<div className="flex flex-col items-center group/qr">
<div className="w-full aspect-square bg-white p-5 rounded-[48px] mb-6 shadow-2xl shadow-white/5 transition-transform duration-500 group-hover/qr:scale-110 group-hover/qr:rotate-2">
<img src="https://api.qrserver.com/v1/create-qr-code/?size=300x300&data=PAYPAL_NEXUS_GAMES" alt="PayPal" className="w-full h-full" />
</div>
<span className="text-xs font-black uppercase tracking-[0.3em] text-violet-400 italic">PayPal Direct</span>
</div>
</div> </div>
<div className="space-y-6"> <div className="w-full space-y-8">
<div className="text-center"> <div className="text-center flex items-center justify-center space-x-4 mb-4">
<div className="inline-flex items-center space-x-2 text-cyan-400 font-mono text-xs animate-pulse"> <div className="flex space-x-1.5">
<div className="w-1.5 h-1.5 bg-cyan-400 rounded-full"></div> <div className="w-2 h-2 bg-violet-500 rounded-full animate-bounce"></div>
<span>SYNCHRONIZING WITH BLOCKCHAIN...</span> <div className="w-2 h-2 bg-violet-500 rounded-full animate-bounce delay-100"></div>
<div className="w-2 h-2 bg-violet-500 rounded-full animate-bounce delay-200"></div>
</div> </div>
<span className="text-[10px] font-black uppercase tracking-[0.4em] text-slate-500">Awaiting Confirmation</span>
</div> </div>
<BaseButton <button
label={isPurchasing ? 'Verifying Transaction...' : 'Confirm & Verify Payment'} disabled={!selectedOption || isPurchasing}
color="info"
className="w-full py-5 rounded-2xl text-lg font-bold"
disabled={!selectedPass || isPurchasing}
onClick={handlePurchase} onClick={handlePurchase}
/> className="w-full group relative"
>
<div className="absolute -inset-1.5 bg-gradient-to-r from-violet-600 to-cyan-500 rounded-3xl blur opacity-30 group-hover:opacity-100 transition duration-500"></div>
<div className="relative px-8 py-6 bg-violet-600 rounded-3xl leading-none flex items-center justify-center space-x-4 group-disabled:opacity-50">
<BaseIcon path={mdiQrcode} size={28} color="white" />
<span className="text-xl font-black uppercase italic tracking-tighter text-white">
{isPurchasing ? 'Processing Request...' : 'Confirm Payment'}
</span>
</div>
</button>
<p className="text-[10px] text-center text-slate-600 uppercase tracking-widest"> <div className="flex items-center justify-center space-x-8 opacity-30">
Access is granted automatically after network confirmation. No registration required. <div className="flex items-center space-x-2">
</p> <BaseIcon path={mdiShieldCheck} size={16} />
<span className="text-[8px] font-black uppercase tracking-widest">Encrypted</span>
</div>
<div className="flex items-center space-x-2">
<BaseIcon path={mdiLockOutline} size={16} />
<span className="text-[8px] font-black uppercase tracking-widest">Authorized</span>
</div>
</div>
</div> </div>
</div> </div>
</div> </div>
@ -310,62 +485,21 @@ export default function IndexPage() {
</section> </section>
)} )}
{/* AI Studio Teaser */}
<section className="px-6 py-24 bg-gradient-to-b from-transparent to-violet-900/10 border-t border-white/5">
<div className="max-w-6xl mx-auto">
<div className="grid grid-cols-1 lg:grid-cols-2 gap-12 items-center">
<div>
<div className="w-16 h-16 bg-violet-600/20 rounded-2xl flex items-center justify-center mb-6">
<BaseIcon path={mdiBrain} size={32} className="text-violet-500" />
</div>
<h2 className="text-4xl font-black mb-6 italic tracking-tight uppercase">Intelligent Developer <span className="text-violet-500">AI</span></h2>
<p className="text-slate-400 text-lg mb-8 leading-relaxed font-medium">
Our advanced neural engine can generate complete game architectures. From 2D platforms to 3D shooters, the AI builds everything.
</p>
<div className="grid grid-cols-2 gap-8 mb-10">
<div className="space-y-2">
<h4 className="font-bold text-violet-400">2D ENGINE</h4>
<p className="text-xs text-slate-500">Optimized physics and sprite management.</p>
</div>
<div className="space-y-2">
<h4 className="font-bold text-cyan-400">3D CORE</h4>
<p className="text-xs text-slate-500">Advanced lighting and spatial mechanics.</p>
</div>
</div>
<BaseButton label="Enter Developer Portal" color="info" icon={mdiRocketLaunch} href="/ai-developer" className="px-10 py-5 rounded-2xl shadow-xl shadow-violet-500/20" />
</div>
<div className="relative">
<div className="aspect-video bg-slate-900 rounded-[32px] border border-white/10 overflow-hidden shadow-2xl">
<div className="absolute inset-0 bg-gradient-to-br from-violet-600/20 to-transparent"></div>
<img src="https://images.pexels.com/photos/163036/mario-luigi-yoshi-figures-163036.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=1" className="w-full h-full object-cover opacity-40 grayscale" />
<div className="absolute inset-0 flex items-center justify-center">
<div className="p-8 bg-black/60 backdrop-blur-2xl border border-white/10 rounded-3xl text-center">
<div className="font-mono text-[10px] text-emerald-400 mb-4 tracking-[0.3em]">AI_PROCESSING_SEQUENCE</div>
<div className="space-y-2">
<div className="h-1 w-48 bg-slate-800 rounded-full overflow-hidden">
<div className="h-full bg-violet-500 w-[85%] animate-pulse"></div>
</div>
<div className="h-1 w-48 bg-slate-800 rounded-full overflow-hidden">
<div className="h-full bg-cyan-500 w-[60%] animate-pulse delay-75"></div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</section>
{/* Footer */} {/* Footer */}
<footer className="px-6 py-12 border-t border-white/5 text-center bg-black"> <footer className="px-6 py-24 border-t border-white/5 text-center bg-black">
<div className="flex items-center justify-center space-x-2 mb-6"> <div className="flex items-center justify-center space-x-3 mb-12">
<div className="w-8 h-8 bg-violet-600 rounded-lg flex items-center justify-center"> <div className="w-12 h-12 bg-gradient-to-br from-violet-600 to-cyan-500 rounded-2xl flex items-center justify-center shadow-xl shadow-violet-500/20">
<BaseIcon path={mdiGamepadVariant} size={18} color="white" /> <BaseIcon path={mdiGamepadVariant} size={24} color="white" />
</div> </div>
<span className="text-lg font-black tracking-tighter uppercase italic">Nexus<span className="text-violet-500">Games</span></span> <span className="text-2xl font-black tracking-tighter uppercase italic">Nexus<span className="text-violet-500">Games</span></span>
</div> </div>
<p className="text-slate-500 text-xs tracking-widest uppercase">© 2026 Nexus Gaming Platform Intelligent Developer AI Integration</p> <div className="flex flex-wrap justify-center gap-10 mb-16 text-[10px] font-black uppercase tracking-[0.4em] text-slate-600">
<a href="#" className="hover:text-white transition-colors">Infrastructure</a>
<a href="#" className="hover:text-white transition-colors">Neural Engine</a>
<a href="#" className="hover:text-white transition-colors">Legal</a>
<a href="#" className="hover:text-white transition-colors">Connect</a>
</div>
<p className="text-slate-800 text-[10px] tracking-[0.6em] uppercase font-black">© 2026 NEXUS GAMING ECOSYSTEM QUANTUM SECURED PLATFORM</p>
</footer> </footer>
</div> </div>
) )