diff --git a/README.md b/README.md
index 970ba8e..0767ad9 100644
--- a/README.md
+++ b/README.md
@@ -241,7 +241,4 @@ The **docker folder** contains a couple of helper scripts:
Afterwards, continue to start your project in the backend directory by running:
- `yarn start`
- admin commands tools for admin@flatlogic.com
- admin_pass for admin@flatlogic.com
- password for admin@flatlogic.com PASSWORD fac6a658
\ No newline at end of file
+ `yarn start`
\ No newline at end of file
diff --git a/frontend/public/assets/vm-shot-2026-08-07T19-27-16-851Z.jpg b/frontend/public/assets/vm-shot-2026-08-07T19-27-16-851Z.jpg
deleted file mode 100644
index 3edc0d3..0000000
Binary files a/frontend/public/assets/vm-shot-2026-08-07T19-27-16-851Z.jpg and /dev/null differ
diff --git a/frontend/public/assets/vm-shot-2026-08-07T19-27-26-780Z.jpg b/frontend/public/assets/vm-shot-2026-08-07T19-27-26-780Z.jpg
deleted file mode 100644
index 3edc0d3..0000000
Binary files a/frontend/public/assets/vm-shot-2026-08-07T19-27-26-780Z.jpg and /dev/null differ
diff --git a/frontend/public/assets/vm-shot-2026-08-07T19-28-03-715Z.jpg b/frontend/public/assets/vm-shot-2026-08-07T19-28-03-715Z.jpg
deleted file mode 100644
index 3edc0d3..0000000
Binary files a/frontend/public/assets/vm-shot-2026-08-07T19-28-03-715Z.jpg and /dev/null differ
diff --git a/frontend/src/components/NavBarItem.tsx b/frontend/src/components/NavBarItem.tsx
index 2ef67e9..6548433 100644
--- a/frontend/src/components/NavBarItem.tsx
+++ b/frontend/src/components/NavBarItem.tsx
@@ -1,5 +1,6 @@
-import React, { useEffect, useRef, useState } from 'react'
+import React, {useEffect, useRef} from 'react'
import Link from 'next/link'
+import { useState } from 'react'
import { mdiChevronUp, mdiChevronDown } from '@mdi/js'
import BaseDivider from './BaseDivider'
import BaseIcon from './BaseIcon'
diff --git a/frontend/src/layouts/Authenticated.tsx b/frontend/src/layouts/Authenticated.tsx
index 73d8391..1b9907d 100644
--- a/frontend/src/layouts/Authenticated.tsx
+++ b/frontend/src/layouts/Authenticated.tsx
@@ -1,4 +1,5 @@
-import React, { ReactNode, useEffect, useState } from 'react'
+import React, { ReactNode, useEffect } from 'react'
+import { useState } from 'react'
import jwt from 'jsonwebtoken';
import { mdiForwardburger, mdiBackburger, mdiMenu } from '@mdi/js'
import menuAside from '../menuAside'
diff --git a/frontend/src/menuAside.ts b/frontend/src/menuAside.ts
index cdc330c..5cb10e8 100644
--- a/frontend/src/menuAside.ts
+++ b/frontend/src/menuAside.ts
@@ -7,13 +7,6 @@ const menuAside: MenuAsideItem[] = [
icon: icon.mdiViewDashboardOutline,
label: 'Dashboard',
},
- {
- href: '/play',
- label: 'Play Game',
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
- // @ts-ignore
- icon: 'mdiGamepadVariant' in icon ? icon['mdiGamepadVariant' as keyof typeof icon] : icon.mdiTable,
- },
{
href: '/users/users-list',
diff --git a/frontend/src/pages/index.tsx b/frontend/src/pages/index.tsx
index af5d53e..ae1bc95 100644
--- a/frontend/src/pages/index.tsx
+++ b/frontend/src/pages/index.tsx
@@ -1,166 +1,166 @@
-import React from 'react';
+
+import React, { useEffect, useState } from 'react';
import type { ReactElement } from 'react';
import Head from 'next/head';
import Link from 'next/link';
+import BaseButton from '../components/BaseButton';
+import CardBox from '../components/CardBox';
+import SectionFullScreen from '../components/SectionFullScreen';
import LayoutGuest from '../layouts/Guest';
+import BaseDivider from '../components/BaseDivider';
+import BaseButtons from '../components/BaseButtons';
import { getPageTitle } from '../config';
+import { useAppSelector } from '../stores/hooks';
+import CardBoxComponentTitle from "../components/CardBoxComponentTitle";
+import { getPexelsImage, getPexelsVideo } from '../helpers/pexels';
-const colorBursts = [
- 'bg-[#35F2C1]',
- 'bg-[#FF4FD8]',
- 'bg-[#FFD166]',
- 'bg-[#7C5CFF]',
- 'bg-[#38BDF8]',
- 'bg-[#FF7A59]',
-];
-
-const arenaStats = [
- ['49', 'color blocks'],
- ['4', 'rivals'],
- ['45s', 'rounds'],
-];
-
-const floatingBlocks = [
- 'left-[6%] top-[18%] h-14 w-14 rotate-12 bg-[#FF4FD8] shadow-[#B8228D]',
- 'right-[8%] top-[17%] h-16 w-16 -rotate-12 bg-[#FFD166] shadow-[#B88711]',
- 'left-[12%] bottom-[14%] h-12 w-12 -rotate-6 bg-[#38BDF8] shadow-[#117DAA]',
- 'right-[18%] bottom-[18%] h-10 w-10 rotate-6 bg-[#7C5CFF] shadow-[#4931B8]',
-];
export default function Starter() {
+ const [illustrationImage, setIllustrationImage] = useState({
+ src: undefined,
+ photographer: undefined,
+ photographer_url: undefined,
+ })
+ const [illustrationVideo, setIllustrationVideo] = useState({video_files: []})
+ const [contentType, setContentType] = useState('image');
+ const [contentPosition, setContentPosition] = useState('left');
+ const textColor = useAppSelector((state) => state.style.linkColor);
+
+ const title = 'Steal a Color Game'
+
+ // Fetch Pexels image/video
+ useEffect(() => {
+ async function fetchData() {
+ const image = await getPexelsImage();
+ const video = await getPexelsVideo();
+ setIllustrationImage(image);
+ setIllustrationVideo(video);
+ }
+ fetchData();
+ }, []);
+
+ const imageBlock = (image) => (
+
+ );
+
+ const videoBlock = (video) => {
+ if (video?.video_files?.length > 0) {
+ return (
+
+
+
+ Your browser does not support the video tag.
+
+
+
)
+ }
+ };
+
return (
- <>
+
-
{getPageTitle('Steal a Color')}
-
+
{getPageTitle('Starter Page')}
-
-
-
-
-
-
-
-
- {floatingBlocks.map((block) => (
-
- ))}
-
-
-
-
- {colorBursts.slice(0, 4).map((color) => (
-
- ))}
-
-
- Roblox-like
- Steal a Color
-
-
-
-
-
- Play
-
-
- Admin Login
-
-
-
-
-
-
-
-
- Color stealing block arena
-
-
-
- Jump in, slap a color block, and steal the whole map.
-
-
- A chunkier, Roblox-inspired look for quick territory rounds: toy-bright tiles, simple block avatars, chunky buttons, and an arena that feels like a colorful playground.
-
-
-
-
- Start a Match
-
-
- Open Admin
-
-
-
-
- {arenaStats.map(([value, label]) => (
-
-
{value}
- {label}
-
- ))}
-
+
+
+ {contentType === 'image' && contentPosition !== 'background'
+ ? imageBlock(illustrationImage)
+ : null}
+ {contentType === 'video' && contentPosition !== 'background'
+ ? videoBlock(illustrationVideo)
+ : null}
+
+
+
+
+
+
+
+
-
-
-
-
-
-
Live board
-
Preview Arena
-
-
00:45
-
+
+
+
+
+
+
+
© 2026 {title} . All rights reserved
+
+ Privacy Policy
+
+
-
-
-
-
-
-
- {Array.from({ length: 49 }).map((_, index) => {
- const color = colorBursts[(index * 5 + Math.floor(index / 7)) % colorBursts.length];
- return (
-
- );
- })}
-
-
-
-
- {[
- ['You', '31%', '#35F2C1', '#159875'],
- ['Pixel', '26%', '#FF4FD8', '#B8228D'],
- ['Volt', '23%', '#FFD166', '#B88711'],
- ['Nova', '20%', '#7C5CFF', '#4931B8'],
- ].map(([name, score, color, shade]) => (
-
-
-
{name}
-
{score} control
-
- ))}
-
-
-
-
-
-
- >
+
);
}
Starter.getLayout = function getLayout(page: ReactElement) {
return {page} ;
};
+
diff --git a/frontend/src/pages/play.tsx b/frontend/src/pages/play.tsx
deleted file mode 100644
index 9e86147..0000000
--- a/frontend/src/pages/play.tsx
+++ /dev/null
@@ -1,471 +0,0 @@
-import React, { useCallback, useEffect, useMemo, useState } from 'react';
-import type { ReactElement } from 'react';
-import Head from 'next/head';
-import Link from 'next/link';
-import LayoutGuest from '../layouts/Guest';
-import { getPageTitle } from '../config';
-
-type Owner = 0 | 1 | 2 | 3;
-type Phase = 'setup' | 'playing' | 'finished';
-
-type Player = {
- id: Owner;
- name: string;
- color: string;
- textColor: string;
- glow: string;
- shade: string;
-};
-
-type MatchRecord = {
- id: string;
- playerName: string;
- score: number;
- winner: string;
- won: boolean;
- captured: number;
- createdAt: string;
- board: Owner[];
-};
-
-const boardSize = 49;
-const matchSeconds = 45;
-const historyKey = 'steal-a-color-history';
-
-const players: Player[] = [
- { id: 0, name: 'You', color: '#35F2C1', textColor: 'text-[#087560]', glow: 'shadow-[#159875]/35', shade: '#159875' },
- { id: 1, name: 'Pixel', color: '#FF4FD8', textColor: 'text-[#B8228D]', glow: 'shadow-[#B8228D]/30', shade: '#B8228D' },
- { id: 2, name: 'Volt', color: '#FFD166', textColor: 'text-[#9B6A00]', glow: 'shadow-[#B88711]/30', shade: '#B88711' },
- { id: 3, name: 'Nova', color: '#7C5CFF', textColor: 'text-[#4931B8]', glow: 'shadow-[#4931B8]/30', shade: '#4931B8' },
-];
-
-const createBoard = () =>
- Array.from({ length: boardSize }, (_, index) => {
- if (index === 0 || index === 1 || index === 7) {
- return 0;
- }
-
- if (index === 5 || index === 6 || index === 13) {
- return 1;
- }
-
- if (index === 35 || index === 42 || index === 43) {
- return 2;
- }
-
- if (index === 41 || index === 47 || index === 48) {
- return 3;
- }
-
- return ((index + Math.floor(index / 7)) % 4) as Owner;
- });
-
-const getCounts = (board: Owner[]) =>
- players.map((player) => ({
- ...player,
- tiles: board.filter((owner) => owner === player.id).length,
- }));
-
-const loadHistory = (): MatchRecord[] => {
- if (typeof window === 'undefined') {
- return [];
- }
-
- try {
- const stored = window.localStorage.getItem(historyKey);
- if (!stored) {
- return [];
- }
-
- const parsed = JSON.parse(stored);
- return Array.isArray(parsed) ? parsed : [];
- } catch (error) {
- console.error('Failed to load Steal a Color match history', error);
- return [];
- }
-};
-
-const saveHistory = (records: MatchRecord[]) => {
- try {
- window.localStorage.setItem(historyKey, JSON.stringify(records));
- } catch (error) {
- console.error('Failed to save Steal a Color match history', error);
- }
-};
-
-const formatDate = (value: string) =>
- new Intl.DateTimeFormat('en', {
- month: 'short',
- day: 'numeric',
- hour: '2-digit',
- minute: '2-digit',
- }).format(new Date(value));
-
-export default function PlayStealAColor() {
- const [phase, setPhase] = useState('setup');
- const [playerName, setPlayerName] = useState('Player One');
- const [board, setBoard] = useState(createBoard);
- const [secondsLeft, setSecondsLeft] = useState(matchSeconds);
- const [captured, setCaptured] = useState(0);
- const [history, setHistory] = useState([]);
- const [selectedMatchId, setSelectedMatchId] = useState(null);
- const [message, setMessage] = useState('Name your blocky avatar, start a match, then smack rival color blocks to steal them.');
-
- useEffect(() => {
- const records = loadHistory();
- setHistory(records);
- setSelectedMatchId(records[0]?.id ?? null);
- }, []);
-
- const counts = useMemo(() => getCounts(board), [board]);
- const leader = useMemo(() => [...counts].sort((a, b) => b.tiles - a.tiles)[0], [counts]);
- const playerScore = counts[0]?.tiles ?? 0;
- const selectedMatch = history.find((record) => record.id === selectedMatchId) ?? history[0];
-
- const finishMatch = useCallback(
- (finalBoard: Owner[]) => {
- const finalCounts = getCounts(finalBoard);
- const winner = [...finalCounts].sort((a, b) => b.tiles - a.tiles)[0];
- const record: MatchRecord = {
- id: `${Date.now()}`,
- playerName: playerName.trim() || 'Player One',
- score: finalCounts[0].tiles,
- winner: winner.name,
- won: winner.id === 0,
- captured,
- createdAt: new Date().toISOString(),
- board: finalBoard,
- };
- const nextHistory = [record, ...history].slice(0, 6);
- setPhase('finished');
- setMessage(winner.id === 0 ? 'Victory! Your color owns the arena.' : `${winner.name} stole the crown. Hit rematch and take it back.`);
- setHistory(nextHistory);
- setSelectedMatchId(record.id);
- saveHistory(nextHistory);
- },
- [captured, history, playerName]
- );
-
- useEffect(() => {
- if (phase !== 'playing') {
- return undefined;
- }
-
- if (secondsLeft <= 0) {
- finishMatch(board);
- return undefined;
- }
-
- const timer = window.setTimeout(() => setSecondsLeft((current) => current - 1), 1000);
- return () => window.clearTimeout(timer);
- }, [board, finishMatch, phase, secondsLeft]);
-
- useEffect(() => {
- if (phase !== 'playing') {
- return undefined;
- }
-
- const aiTimer = window.setInterval(() => {
- setBoard((currentBoard) => {
- const nextBoard = [...currentBoard];
- const ai = (1 + Math.floor(Math.random() * 3)) as Owner;
- const candidates = nextBoard
- .map((owner, index) => ({ owner, index }))
- .filter((tile) => tile.owner !== ai);
-
- if (candidates.length === 0) {
- return currentBoard;
- }
-
- const target = candidates[Math.floor(Math.random() * candidates.length)];
- nextBoard[target.index] = ai;
- return nextBoard;
- });
- }, 1050);
-
- return () => window.clearInterval(aiTimer);
- }, [phase]);
-
- const startMatch = () => {
- const safeName = playerName.trim();
- if (!safeName) {
- setMessage('Please add a player name before starting.');
- return;
- }
-
- setBoard(createBoard());
- setSecondsLeft(matchSeconds);
- setCaptured(0);
- setPhase('playing');
- setMessage(`Go ${safeName}! Tap any rival block to flip it mint.`);
- };
-
- const stealTile = (index: number) => {
- if (phase !== 'playing') {
- setMessage('Press Start first to unlock the arena.');
- return;
- }
-
- setBoard((currentBoard) => {
- if (currentBoard[index] === 0) {
- setMessage('That block already belongs to you. Find a rival color.');
- return currentBoard;
- }
-
- const nextBoard = [...currentBoard];
- nextBoard[index] = 0;
- setCaptured((current) => current + 1);
- setMessage('Steal confirmed! Keep the mint wave rolling.');
- return nextBoard;
- });
- };
-
- const clearHistory = () => {
- setHistory([]);
- setSelectedMatchId(null);
- saveHistory([]);
- setMessage('Local match history cleared. The arena is reset.');
- };
-
- return (
- <>
-
- {getPageTitle('Play Steal a Color')}
-
-
-
-
-
-
-
-
-
-
-
-
- {players.map((player) => (
-
- ))}
-
-
- Steal a Color
- Block Arena
-
-
-
-
-
- Landing
-
-
- Admin Login
-
-
-
-
-
-
-
-
-
-
-
Arena
-
Tap-to-steal blocks
-
-
-
- Time
- 0:{String(secondsLeft).padStart(2, '0')}
-
-
- Your blocks
- {playerScore}
-
-
-
-
-
- {counts.map((player) => (
-
-
-
{player.name === 'You' ? playerName || 'You' : player.name}
-
{player.tiles}
-
- ))}
-
-
-
- {board.map((owner, index) => {
- const tileOwner = players[owner];
- return (
- stealTile(index)}
- className={`relative aspect-square overflow-hidden rounded-xl border-2 border-white/50 shadow-lg ${tileOwner.glow} transition duration-150 hover:-translate-y-1 hover:scale-[1.02] focus:outline-none focus:ring-4 focus:ring-white ${owner === 0 ? 'ring-2 ring-[#087560]' : ''} ${phase === 'playing' ? 'cursor-pointer' : 'cursor-default'}`}
- style={{
- backgroundColor: tileOwner.color,
- boxShadow: `inset 0 6px 0 rgba(255,255,255,0.42), inset 0 -8px 0 rgba(0,0,0,0.2), 0 8px 0 ${tileOwner.shade}, 0 14px 18px rgba(0,0,0,0.22)`,
- }}
- >
-
- {tileOwner.name}
-
- );
- })}
-
-
- {phase === 'finished' ? (
-
-
Results
-
- {leader.id === 0 ? 'You won the color crown!' : `${leader.name} wins this round.`}
-
-
- {playerName.trim() || 'Player One'} finished with {playerScore} blocks and made {captured} manual steals .
-
-
- ) : null}
-
-
-
-
-
-
-
Live leaderboard
-
{leader.name} leads
-
-
- {Math.round((leader.tiles / boardSize) * 100)}%
-
-
-
-
- {counts.map((player) => {
- const width = `${Math.max(8, Math.round((player.tiles / boardSize) * 100))}%`;
- return (
-
-
- {player.name === 'You' ? playerName || 'You' : player.name}
- {player.tiles} blocks
-
-
-
- );
- })}
-
-
-
-
-
-
-
Recent rounds
-
Local history
-
-
- Clear
-
-
-
- {history.length === 0 ? (
-
- No completed rounds yet. Finish a match to save your first color steal result here.
-
- ) : (
-
- {history.map((record) => (
-
setSelectedMatchId(record.id)}
- className={`w-full rounded-2xl border-4 p-3 text-left font-bold transition hover:-translate-y-0.5 ${selectedMatch?.id === record.id ? 'border-[#159875] bg-[#D9FFF6] shadow-[0_7px_0_#A5DCD0]' : 'border-[#172033] bg-white shadow-[0_7px_0_#9AA7B8]'}`}
- >
-
- {record.won ? 'Win' : 'Loss'} • {record.score} blocks
- {formatDate(record.createdAt)}
-
- Winner: {record.winner} • Player: {record.playerName}
-
- ))}
-
- )}
-
-
- {selectedMatch ? (
-
- Match detail
- {formatDate(selectedMatch.createdAt)}
-
- {selectedMatch.board.map((owner, index) => (
-
- ))}
-
-
- {selectedMatch.playerName} scored {selectedMatch.score} blocks. Winner: {selectedMatch.winner} .
-
-
- ) : null}
-
-
-
-
-
- >
- );
-}
-
-PlayStealAColor.getLayout = function getLayout(page: ReactElement) {
- return {page} ;
-};