refactor: Remove tasks.json and update documentation in store functions
All checks were successful
Build / Build (push) Successful in 28s

This commit removes the `tasks.json` file from the `.vscode` directory. Additionally, it enhances the documentation in `fnc_buyItem.sqf` and `fnc_buyVehicle.sqf` by providing clearer descriptions of item and vehicle types. The `fnc_handlePurchase.sqf` has also been updated to improve variable scoping for better code clarity.
This commit is contained in:
Jacob Schmidt 2025-04-19 11:12:53 -05:00
parent e2a8517268
commit 69f8f037df
43 changed files with 3346 additions and 30 deletions

24
.vscode/tasks.json vendored
View File

@ -1,24 +0,0 @@
{
"version": "2.0.0",
"tasks": [
{
"label": "Start Arma 3 Server",
"type": "process",
"command": "wscript.exe",
"args": [
"D:\\SteamLibrary\\steamapps\\common\\Arma 3\\start_serverhub_hidden.vbs"
],
"presentation": {
"reveal": "silent",
"panel": "shared",
"showReuseMessage": false,
"clear": true
},
"problemMatcher": [],
"group": {
"kind": "build",
"isDefault": true
}
}
]
}

View File

@ -0,0 +1,107 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Forge Admin Panel</title>
<link rel="stylesheet" href="styles.css">
<script src="script.js" defer></script>
</head>
<body>
<header>
<div class="container">
<div class="header-content">
<h1>Admin Panel</h1>
<div class="admin-stats">
<div class="stat-item">
<span>👥</span>
<div>
<div>Online Players</div>
<div id="playerCount">0</div>
</div>
</div>
<div class="stat-item">
<span>👑</span>
<div>
<div>Staff Online</div>
<div id="staffCount">0</div>
</div>
</div>
</div>
</div>
</div>
</header> <main class="container">
<div class="sections-grid">
<div class="action-sections">
<!-- Global Actions Section -->
<div class="admin-section">
<h2>Global Actions</h2>
<div class="form-group">
<label for="paydayAmount">Payday Amount</label>
<input type="number" id="paydayAmount" min="0" value="1000">
</div>
<button class="submit-btn" onclick="triggerPayday()">Trigger Payday</button>
</div>
<!-- Message System Section -->
<div class="admin-section">
<h2>Broadcast Message</h2>
<div class="form-group">
<label for="broadcastMessage">Message</label>
<input type="text" id="broadcastMessage" placeholder="Enter message to broadcast...">
</div>
<button class="submit-btn" onclick="broadcastMessage()">Send to All</button>
</div>
</div>
<!-- Player List Section -->
<div class="admin-section player-list-section">
<div class="search-bar">
<input type="text" id="playerSearch" class="search-input" placeholder="Search players...">
<div class="filter-bar">
<button class="filter-btn active" data-filter="all">All</button>
<button class="filter-btn" data-filter="online">Online</button>
<button class="filter-btn" data-filter="staff">Staff</button>
</div>
</div>
<div class="player-list" id="playerList">
<!-- Players will be populated dynamically -->
</div>
</div>
</div>
</main>
<!-- Message Modal -->
<div id="messageModal" class="modal">
<div class="modal-content">
<div class="modal-header">
<h2>Send Message</h2>
<button class="close-modal" onclick="closeMessageModal()">&times;</button>
</div>
<div class="form-group">
<label for="messageInput">Message to <span id="messagePlayerName"></span></label>
<input type="text" id="messageInput" placeholder="Enter your message...">
</div>
<button class="submit-btn" onclick="sendPlayerMessage()">Send Message</button>
</div>
</div>
<!-- Money Modal -->
<div id="moneyModal" class="modal">
<div class="modal-content">
<div class="modal-header">
<h2>Modify Money</h2>
<button class="close-modal" onclick="closeMoneyModal()">&times;</button>
</div>
<div class="form-group">
<label for="moneyAmount">Amount</label>
<input type="number" id="moneyAmount" placeholder="Enter amount...">
</div>
<div class="player-actions">
<button class="action-btn promote-btn" onclick="giveMoney()">Give Money</button>
<button class="action-btn demote-btn" onclick="takeMoney()">Take Money</button>
</div>
</div>
</div>
</body>
</html>

View File

@ -0,0 +1,228 @@
// Simulated admin data - this would be replaced with actual game data
let adminData = {
players: [
{
id: 1,
name: "John_Doe",
role: "admin",
money: 50000,
status: "online"
},
{
id: 2,
name: "Jane_Smith",
role: "mod",
money: 25000,
status: "online"
},
{
id: 3,
name: "Mike_Johnson",
role: "player",
money: 10000,
status: "offline"
}
],
paydayAmount: 1000
};
let selectedPlayerId = null;
// Initialize the admin panel
function initializeAdmin() {
updateStats();
setupFilterListeners();
updatePlayerList();
}
// Update header statistics
function updateStats() {
const onlinePlayers = adminData.players.filter(p => p.status === "online").length;
const onlineStaff = adminData.players.filter(p => p.status === "online" && (p.role === "admin" || p.role === "mod")).length;
document.getElementById('playerCount').textContent = onlinePlayers;
document.getElementById('staffCount').textContent = onlineStaff;
}
// Set up filter button listeners
function setupFilterListeners() {
const filterButtons = document.querySelectorAll('.filter-btn');
filterButtons.forEach(button => {
button.addEventListener('click', () => {
filterButtons.forEach(btn => btn.classList.remove('active'));
button.classList.add('active');
filterPlayers(button.dataset.filter);
});
});
// Set up search functionality
const searchInput = document.getElementById('playerSearch');
searchInput.addEventListener('input', () => {
const activeFilter = document.querySelector('.filter-btn.active').dataset.filter;
filterPlayers(activeFilter, searchInput.value);
});
}
// Filter players based on category and search term
function filterPlayers(filter, searchTerm = '') {
let filteredPlayers = adminData.players;
// Apply category filter
if (filter === 'online') {
filteredPlayers = filteredPlayers.filter(p => p.status === 'online');
} else if (filter === 'staff') {
filteredPlayers = filteredPlayers.filter(p => p.role === 'admin' || p.role === 'moderator');
}
// Apply search filter
if (searchTerm) {
const term = searchTerm.toLowerCase();
filteredPlayers = filteredPlayers.filter(p =>
p.name.toLowerCase().includes(term)
);
}
updatePlayerList(filteredPlayers);
}
// Update the player list display
function updatePlayerList(players = adminData.players) {
const playerList = document.getElementById('playerList');
playerList.innerHTML = players.map(player => `
<div class="player-item" data-id="${player.id}">
<div class="player-info">
<span class="player-name">${player.name}</span>
<span class="player-role role-${player.role}">${player.role}</span>
<span class="badge badge-${player.status}">${player.status}</span>
<span class="player-money">$${player.money.toLocaleString()}</span>
</div>
<div class="player-actions">
${player.role !== 'admin' ? `
<button class="action-btn promote-btn" onclick="promotePlayer(${player.id})">
${player.role === 'player' ? 'Promote to Mod' : 'Promote to Admin'}
</button>
` : ''}
${player.role !== 'player' ? `
<button class="action-btn demote-btn" onclick="demotePlayer(${player.id})">
${player.role === 'admin' ? 'Demote to Mod' : 'Demote to Player'}
</button>
` : ''}
<button class="action-btn message-btn" onclick="openMessageModal(${player.id})">Message</button>
<button class="action-btn" onclick="openMoneyModal(${player.id})">Modify Money</button>
</div>
</div>
`).join('');
}
// Role management functions
function promotePlayer(playerId) {
const player = adminData.players.find(p => p.id === playerId);
if (player) {
if (player.role === 'player') {
player.role = 'moderator';
} else if (player.role === 'moderator') {
player.role = 'admin';
}
updatePlayerList();
}
}
function demotePlayer(playerId) {
const player = adminData.players.find(p => p.id === playerId);
if (player) {
if (player.role === 'admin') {
player.role = 'moderator';
} else if (player.role === 'moderator') {
player.role = 'player';
}
updatePlayerList();
}
}
// Money management functions
function openMoneyModal(playerId) {
selectedPlayerId = playerId;
const modal = document.getElementById('moneyModal');
modal.style.display = 'block';
}
function closeMoneyModal() {
const modal = document.getElementById('moneyModal');
modal.style.display = 'none';
document.getElementById('moneyAmount').value = '';
selectedPlayerId = null;
}
function giveMoney() {
const amount = parseInt(document.getElementById('moneyAmount').value);
if (amount && selectedPlayerId) {
const player = adminData.players.find(p => p.id === selectedPlayerId);
if (player) {
player.money += amount;
updatePlayerList();
closeMoneyModal();
}
}
}
function takeMoney() {
const amount = parseInt(document.getElementById('moneyAmount').value);
if (amount && selectedPlayerId) {
const player = adminData.players.find(p => p.id === selectedPlayerId);
if (player) {
player.money = Math.max(0, player.money - amount);
updatePlayerList();
closeMoneyModal();
}
}
}
// Message system functions
function openMessageModal(playerId) {
selectedPlayerId = playerId;
const player = adminData.players.find(p => p.id === playerId);
const modal = document.getElementById('messageModal');
document.getElementById('messagePlayerName').textContent = player.name;
modal.style.display = 'block';
}
function closeMessageModal() {
const modal = document.getElementById('messageModal');
modal.style.display = 'none';
document.getElementById('messageInput').value = '';
selectedPlayerId = null;
}
function sendPlayerMessage() {
const message = document.getElementById('messageInput').value;
if (message && selectedPlayerId) {
const player = adminData.players.find(p => p.id === selectedPlayerId);
if (player) {
console.log(`Message sent to ${player.name}: ${message}`);
closeMessageModal();
}
}
}
function broadcastMessage() {
const message = document.getElementById('broadcastMessage').value;
if (message) {
console.log(`Broadcasting message to all players: ${message}`);
document.getElementById('broadcastMessage').value = '';
}
}
// Global actions
function triggerPayday() {
const amount = parseInt(document.getElementById('paydayAmount').value);
if (amount) {
adminData.players.forEach(player => {
player.money += amount;
});
updatePlayerList();
}
}
// Initialize when DOM is loaded
document.addEventListener('DOMContentLoaded', initializeAdmin);

View File

@ -0,0 +1,313 @@
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: Arial, sans-serif;
line-height: 1.6;
background-color: #f4f4f4;
}
.container {
max-width: 1200px;
margin: 0 auto;
padding: 20px;
}
header {
background-color: #333;
color: white;
padding: 1rem 0;
margin-bottom: 2rem;
}
.header-content {
display: flex;
justify-content: space-between;
align-items: center;
}
.admin-stats {
display: flex;
gap: 2rem;
}
.stat-item {
display: flex;
align-items: center;
gap: 0.5rem;
background: rgba(255, 255, 255, 0.1);
padding: 0.5rem 1rem;
border-radius: 4px;
}
.sections-grid {
display: grid;
grid-template-columns: 1fr;
gap: 2rem;
padding: 1rem;
}
.action-sections {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 2rem;
}
.admin-section {
background-color: white;
border-radius: 8px;
overflow: hidden;
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
padding: 1.5rem;
display: flex;
flex-direction: column;
gap: 1rem;
}
.player-list-section {
grid-column: span 2;
grid-row: span 2;
}
.player-list {
list-style: none;
overflow-y: auto;
flex-grow: 1;
height: 0;
min-height: 400px;
}
.player-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 1rem;
border-bottom: 1px solid #eee;
transition: background-color 0.3s;
}
.player-item:hover {
background-color: #f8f9fa;
}
.player-info {
display: flex;
align-items: center;
gap: 1rem;
flex: 1;
}
.player-name {
font-weight: 500;
}
.player-role {
font-size: 0.875rem;
padding: 0.25rem 0.5rem;
border-radius: 4px;
font-weight: bold;
}
.role-admin {
background-color: #dc3545;
color: white;
}
.role-mod {
background-color: #ffc107;
color: black;
}
.role-player {
background-color: #28a745;
color: white;
}
.player-money {
font-size: 0.875rem;
color: #28a745;
}
.player-actions {
display: flex;
gap: 0.5rem;
}
.action-btn {
padding: 0.5rem 1rem;
border: none;
border-radius: 4px;
cursor: pointer;
font-weight: 500;
transition: background-color 0.3s;
}
.action-btn:disabled {
background-color: #6c757d;
cursor: not-allowed;
opacity: 0.65;
}
.promote-btn {
background-color: #28a745;
color: white;
}
.promote-btn:hover:not(:disabled) {
background-color: #218838;
}
.demote-btn {
background-color: #dc3545;
color: white;
}
.demote-btn:hover:not(:disabled) {
background-color: #c82333;
}
.message-btn {
background-color: #007bff;
color: white;
}
.message-btn:hover:not(:disabled) {
background-color: #0056b3;
}
.search-bar {
display: flex;
gap: 1rem;
margin-bottom: 1rem;
}
.search-input {
flex: 1;
padding: 0.5rem;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 1rem;
}
.form-group {
display: flex;
flex-direction: column;
gap: 0.5rem;
margin-bottom: 1rem;
}
.form-group label {
font-weight: 500;
color: #333;
}
.form-group input {
padding: 0.5rem;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 1rem;
}
.submit-btn {
background-color: #007bff;
color: white;
padding: 0.75rem 1rem;
border: none;
border-radius: 4px;
cursor: pointer;
font-weight: bold;
transition: background-color 0.3s;
}
.submit-btn:hover {
background-color: #0056b3;
}
.modal {
display: none;
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.5);
z-index: 1000;
}
.modal-content {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background-color: white;
padding: 2rem;
border-radius: 8px;
min-width: 400px;
}
.modal-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 1rem;
}
.modal-header h2 {
margin: 0;
}
.close-modal {
background: none;
border: none;
font-size: 1.5rem;
cursor: pointer;
color: #666;
}
.close-modal:hover {
color: #333;
}
.badge {
padding: 0.25rem 0.5rem;
border-radius: 4px;
font-size: 0.875rem;
font-weight: bold;
}
.badge-online {
background-color: #28a745;
color: white;
}
.badge-offline {
background-color: #dc3545;
color: white;
}
.filter-bar {
display: flex;
gap: 1rem;
margin-bottom: 1rem;
}
.filter-btn {
padding: 0.5rem 1rem;
border: none;
border-radius: 4px;
cursor: pointer;
background-color: #444;
color: white;
transition: background-color 0.3s;
}
.filter-btn:hover {
background-color: #666;
}
.filter-btn.active {
background-color: #007bff;
}

View File

@ -0,0 +1,99 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>FORGE - FDIC</title>
<link rel="stylesheet" href="styles.css">
<script src="script.js" defer></script>
</head>
<body>
<header>
<div class="container">
<div class="header-content">
<h1>Federal Deposit Insurance Corporation</h1>
<div class="balance-display">
<div class="balance-item">
<span>💰</span>
<div>
<div>Wallet</div>
<div id="walletBalance">$0</div>
</div>
</div>
<div class="balance-item">
<span>🏦</span>
<div>
<div>Account</div>
<div id="accountBalance">$0</div>
</div>
</div>
</div>
</div>
</div>
</header>
<main class="container">
<div class="actions-grid"> <!-- Transfer Between Accounts -->
<div class="action-tile">
<h2>Transfer Money</h2>
<form id="transferForm">
<div class="form-group">
<label for="transferType">Transfer From</label>
<select id="transferType" required>
<option value="to_wallet">Account to Wallet</option>
<option value="to_account">Wallet to Account</option>
</select>
</div>
<div class="form-group">
<label for="transferAmount">Amount</label>
<input type="number" id="transferAmount" min="1" step="1" required>
</div>
<button type="submit" class="submit-btn">Transfer</button>
</form>
</div>
<!-- Transfer to Player -->
<div class="action-tile">
<h2>Transfer to Player</h2>
<form id="transferPlayerForm">
<div class="form-group">
<label for="playerSelect">Player</label>
<select id="playerSelect" required>
<option value="" disabled selected>Select Player</option>
</select>
</div>
<div class="form-group">
<label for="playerTransferAmount">Amount</label>
<input type="number" id="playerTransferAmount" min="1" step="1" required>
</div>
<button type="submit" class="submit-btn">Send Money</button>
</form>
</div>
<!-- Submit Timesheet -->
<div class="action-tile">
<h2>Submit Timesheet</h2>
<form id="timesheetForm">
<div class="form-group">
<label for="hoursWorked">Hours Worked</label>
<input type="number" id="hoursWorked" min="1" step="0.5" required>
</div>
<div class="form-group">
<label for="hourlyRate">Hourly Rate</label>
<input type="number" id="hourlyRate" min="1" step="1" required>
</div>
<button type="submit" class="submit-btn">Submit Timesheet</button>
</form>
</div>
</div>
<!-- Transaction History -->
<section class="history-section">
<h2>Transaction History</h2>
<ul class="history-list" id="transactionHistory">
<!-- Transactions will be added here dynamically -->
</ul>
</section>
</main>
</body>
</html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 92 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 109 KiB

Binary file not shown.

View File

@ -0,0 +1,147 @@
// Simulated data - this would be replaced with actual game data
let bankState = {
wallet: 1000,
account: 5000,
players: [
{ id: 1, name: "Player 1" },
{ id: 2, name: "Player 2" },
{ id: 3, name: "Player 3" }
],
transactions: []
};
// Initialize the interface
function initializeBank() {
updateBalanceDisplays();
populatePlayerList();
setupEventListeners();
loadTransactionHistory();
}
// Update balance displays in the header
function updateBalanceDisplays() {
document.getElementById('walletBalance').textContent = `$${bankState.wallet.toLocaleString()}`;
document.getElementById('accountBalance').textContent = `$${bankState.account.toLocaleString()}`;
}
// Populate the player selection dropdown
function populatePlayerList() {
const playerSelect = document.getElementById('playerSelect');
playerSelect.innerHTML = '<option value="" disabled selected>Select Player</option>';
bankState.players.forEach(player => {
const option = document.createElement('option');
option.value = player.id;
option.textContent = player.name;
playerSelect.appendChild(option);
});
}
// Add a new transaction to history
function addTransaction(type, amount, details = '') {
const transaction = {
type,
amount,
details,
timestamp: new Date().toISOString()
};
bankState.transactions.unshift(transaction);
updateTransactionHistory();
}
// Update the transaction history display
function updateTransactionHistory() {
const historyList = document.getElementById('transactionHistory');
historyList.innerHTML = '';
bankState.transactions.forEach(transaction => {
const li = document.createElement('li');
li.className = 'history-item';
const isNegative = ['transfer_out', 'to_wallet'].includes(transaction.type);
const amountClass = isNegative ? 'amount-negative' : 'amount-positive';
const amountPrefix = isNegative ? '-' : '+';
li.innerHTML = `
<span class="transaction-type">${formatTransactionType(transaction.type)}</span>
<span class="transaction-details">${transaction.details}</span>
<span class="amount ${amountClass}">${amountPrefix}$${Math.abs(transaction.amount).toLocaleString()}</span>
`;
historyList.appendChild(li);
});
}
// Format transaction type for display
function formatTransactionType(type) {
const types = {
'to_wallet': 'To Wallet',
'to_account': 'To Account',
'transfer_out': 'Transfer Out',
'transfer_in': 'Transfer In',
'timesheet': 'Timesheet'
};
return types[type] || type;
}
// Set up all form event listeners
function setupEventListeners() { // Handle transfers between wallet and account
document.getElementById('transferForm').addEventListener('submit', (e) => {
e.preventDefault();
const amount = parseInt(document.getElementById('transferAmount').value);
const transferType = document.getElementById('transferType').value;
if (transferType === 'to_wallet') {
if (amount > bankState.account) {
alert('Insufficient funds in account');
return;
}
bankState.account -= amount;
bankState.wallet += amount;
} else {
if (amount > bankState.wallet) {
alert('Insufficient funds in wallet');
return;
}
bankState.wallet -= amount;
bankState.account += amount;
}
addTransaction(transferType, amount);
updateBalanceDisplays();
e.target.reset();
}); // Transfer to Player
document.getElementById('transferPlayerForm').addEventListener('submit', (e) => {
e.preventDefault();
const amount = parseInt(document.getElementById('playerTransferAmount').value);
const playerId = document.getElementById('playerSelect').value;
const playerName = bankState.players.find(p => p.id.toString() === playerId)?.name;
if (amount > bankState.account) {
alert('Insufficient funds in account');
return;
}
bankState.account -= amount;
addTransaction('transfer_out', amount, `To ${playerName}`);
updateBalanceDisplays();
e.target.reset();
});
// Submit Timesheet
document.getElementById('timesheetForm').addEventListener('submit', (e) => {
e.preventDefault();
const hours = parseFloat(document.getElementById('hoursWorked').value);
const rate = parseInt(document.getElementById('hourlyRate').value);
const amount = Math.floor(hours * rate);
bankState.account += amount;
addTransaction('timesheet', amount, `${hours} hours @ $${rate}/hr`);
updateBalanceDisplays();
e.target.reset();
});
}
// Initialize when DOM is loaded
document.addEventListener('DOMContentLoaded', initializeBank);

View File

@ -0,0 +1,163 @@
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: Arial, sans-serif;
line-height: 1.6;
background-color: #f4f4f4;
}
.container {
max-width: 1200px;
margin: 0 auto;
padding: 20px;
}
header {
background-color: #333;
color: white;
padding: 1rem 0;
margin-bottom: 2rem;
}
.header-content {
display: flex;
justify-content: space-between;
align-items: center;
}
.balance-display {
display: flex;
gap: 2rem;
margin-left: auto;
}
.balance-item {
display: flex;
align-items: center;
gap: 0.5rem;
background: rgba(255, 255, 255, 0.1);
padding: 0.5rem 1rem;
border-radius: 4px;
}
.balance-item i {
font-size: 1.2rem;
}
.actions-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
gap: 2rem;
padding: 1rem;
}
.action-tile {
background-color: white;
border-radius: 8px;
overflow: hidden;
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
transition: transform 0.3s;
aspect-ratio: 1;
padding: 2rem;
display: flex;
flex-direction: column;
gap: 1rem;
}
.action-tile:hover {
transform: translateY(-5px);
}
.action-tile h2 {
font-size: 1.5rem;
color: #333;
margin-bottom: 1rem;
}
.form-group {
display: flex;
flex-direction: column;
gap: 0.5rem;
margin-bottom: 1rem;
}
.form-group label {
font-weight: bold;
color: #555;
}
.form-group input, .form-group select {
padding: 0.5rem;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 1rem;
}
.submit-btn {
background-color: #007bff;
color: white;
border: none;
border-radius: 4px;
padding: 0.75rem 1rem;
font-size: 1rem;
cursor: pointer;
transition: background-color 0.3s;
margin-top: auto;
}
.submit-btn:hover {
background-color: #0056b3;
}
.history-section {
background: white;
border-radius: 8px;
padding: 2rem;
margin-top: 2rem;
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
}
.history-list {
list-style: none;
margin-top: 1rem;
}
.history-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 1rem;
border-bottom: 1px solid #eee;
}
.history-item:last-child {
border-bottom: none;
}
.transaction-type {
font-weight: bold;
}
.amount-positive {
color: #28a745;
}
.amount-negative {
color: #dc3545;
}
.error-message {
color: #dc3545;
font-size: 0.875rem;
margin-top: 0.25rem;
}
.success-message {
color: #28a745;
font-size: 0.875rem;
margin-top: 0.25rem;
}

View File

@ -0,0 +1,45 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>FORGE - Garage</title>
<link rel="stylesheet" href="styles.css">
<script src="script.js" defer></script>
</head>
<body>
<header>
<div class="container">
<div class="header-content">
<h1>Garage</h1>
<div class="garage-stats">
<div class="stat-item">
<i>🚗</i>
<div>
<div>Vehicles</div>
<div id="vehicleCount">0</div>
</div>
</div>
<div class="stat-item">
<i>🔧</i>
<div>
<div>In Maintenance</div>
<div id="maintenanceCount">0</div>
</div>
</div>
</div>
</div>
</div>
</header>
<main class="container">
<div class="category-filters" id="categoryFilters">
<!-- Categories will be populated dynamically -->
</div>
<div class="vehicles-grid" id="vehiclesGrid">
<!-- Vehicles will be populated dynamically -->
</div>
</main>
</body>
</html>

View File

@ -0,0 +1,184 @@
// Simulated garage data - this would be replaced with actual game data
let garageData = {
vehicles: [
{
id: 1,
name: "Offroad Vehicle",
category: "Land",
type: "Transport",
status: "available",
fuel: 100,
damage: 0,
maintenance: 95,
lastUsed: "2025-04-10T15:30:00",
image: "placeholder.jpg"
},
{
id: 2,
name: "Transport Helicopter",
category: "Air",
type: "Transport",
status: "in-use",
fuel: 75,
damage: 15,
maintenance: 80,
lastUsed: "2025-04-12T09:15:00",
image: "placeholder.jpg"
},
{
id: 3,
name: "Patrol Boat",
category: "Sea",
type: "Patrol",
status: "maintenance",
fuel: 50,
damage: 35,
maintenance: 45,
lastUsed: "2025-04-11T18:45:00",
image: "placeholder.jpg"
},
{
id: 4,
name: "Armed SUV",
category: "Land",
type: "Combat",
status: "available",
fuel: 90,
damage: 5,
maintenance: 88,
lastUsed: "2025-04-12T11:20:00",
image: "placeholder.jpg"
}
]
};
// Initialize the garage interface
function initializeGarage() {
updateStats();
setupCategoryFilters();
displayVehicles();
}
// Update garage statistics
function updateStats() {
const totalVehicles = garageData.vehicles.length;
const inMaintenance = garageData.vehicles.filter(v => v.status === 'maintenance').length;
document.getElementById('vehicleCount').textContent = totalVehicles;
document.getElementById('maintenanceCount').textContent = inMaintenance;
}
// Set up category filters
function setupCategoryFilters() {
const categories = ['All', ...new Set(garageData.vehicles.map(v => v.category))];
const filtersContainer = document.getElementById('categoryFilters');
categories.forEach(category => {
const button = document.createElement('button');
button.className = 'filter-btn' + (category === 'All' ? ' active' : '');
button.textContent = category;
button.addEventListener('click', () => filterVehicles(category));
filtersContainer.appendChild(button);
});
}
// Filter vehicles by category
function filterVehicles(category) {
// Update active filter button
document.querySelectorAll('.filter-btn').forEach(btn => {
btn.classList.toggle('active', btn.textContent === category);
});
// Filter and display vehicles
const filteredVehicles = category === 'All'
? garageData.vehicles
: garageData.vehicles.filter(v => v.category === category);
displayVehicles(filteredVehicles);
}
// Display vehicles in the grid
function displayVehicles(vehicles = garageData.vehicles) {
const grid = document.getElementById('vehiclesGrid');
grid.innerHTML = '';
vehicles.forEach(vehicle => {
const card = document.createElement('div');
card.className = 'vehicle-card';
const statusText = {
'available': 'Available',
'in-use': 'In Use',
'maintenance': 'Maintenance'
}[vehicle.status];
card.innerHTML = `
<div class="vehicle-image" style="background-image: url(${vehicle.image})"></div>
<div class="vehicle-info">
<div class="vehicle-header">
<div class="vehicle-name">${vehicle.name}</div>
<span class="vehicle-status status-${vehicle.status}">${statusText}</span>
</div>
<div class="vehicle-details">
<div class="detail-item">
<span class="detail-label">Type</span>
<span class="detail-value">${vehicle.type}</span>
</div>
<div class="detail-item">
<span class="detail-label">Category</span>
<span class="detail-value">${vehicle.category}</span>
</div>
<div class="detail-item">
<span class="detail-label">Fuel</span>
<div class="vehicle-stats">
<span class="stat-dot ${getFuelStatusColor(vehicle.fuel)}"></span>
<span>${vehicle.fuel}%</span>
</div>
</div>
<div class="detail-item">
<span class="detail-label">Condition</span>
<div class="vehicle-stats">
<span class="stat-dot ${getConditionStatusColor(vehicle.maintenance)}"></span>
<span>${vehicle.maintenance}%</span>
</div>
</div>
</div>
<div class="vehicle-actions">
<button class="action-btn spawn-btn"
onclick="spawnVehicle(${vehicle.id})"
${vehicle.status !== 'available' ? 'disabled' : ''}>
Spawn Vehicle
</button>
</div>
</div>
`;
grid.appendChild(card);
});
}
// Get color class for fuel status
function getFuelStatusColor(fuelLevel) {
if (fuelLevel > 66) return 'stat-green';
if (fuelLevel > 33) return 'stat-yellow';
return 'stat-red';
}
// Get color class for vehicle condition
function getConditionStatusColor(condition) {
if (condition > 66) return 'stat-green';
if (condition > 33) return 'stat-yellow';
return 'stat-red';
}
// Handle vehicle spawn
function spawnVehicle(vehicleId) {
const vehicle = garageData.vehicles.find(v => v.id === vehicleId);
if (vehicle && vehicle.status === 'available') {
alert(`Spawning vehicle: ${vehicle.name}`);
// Here you would typically integrate with your game's vehicle spawning system
}
}
// Initialize when DOM is loaded
document.addEventListener('DOMContentLoaded', initializeGarage);

View File

@ -0,0 +1,233 @@
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: Arial, sans-serif;
line-height: 1.6;
background-color: #f4f4f4;
}
.container {
max-width: 1200px;
margin: 0 auto;
padding: 20px;
}
header {
background-color: #333;
color: white;
padding: 1rem 0;
margin-bottom: 2rem;
}
.header-content {
display: flex;
justify-content: space-between;
align-items: center;
}
.garage-stats {
display: flex;
gap: 2rem;
}
.stat-item {
display: flex;
align-items: center;
gap: 0.5rem;
background: rgba(255, 255, 255, 0.1);
padding: 0.5rem 1rem;
border-radius: 4px;
}
.vehicles-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
gap: 2rem;
padding: 1rem;
}
.vehicle-card {
background-color: white;
border-radius: 8px;
overflow: hidden;
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
transition: transform 0.3s;
display: flex;
flex-direction: column;
}
.vehicle-card:hover {
transform: translateY(-5px);
}
.vehicle-image {
width: 100%;
height: 200px;
background-color: #eee;
background-size: cover;
background-position: center;
}
.vehicle-info {
padding: 1.5rem;
display: flex;
flex-direction: column;
gap: 1rem;
}
.vehicle-header {
display: flex;
justify-content: space-between;
align-items: flex-start;
}
.vehicle-name {
font-size: 1.2rem;
font-weight: bold;
color: #333;
}
.vehicle-status {
font-size: 0.875rem;
padding: 0.25rem 0.5rem;
border-radius: 4px;
font-weight: bold;
}
.status-available {
background-color: #28a745;
color: white;
}
.status-in-use {
background-color: #dc3545;
color: white;
}
.status-maintenance {
background-color: #ffc107;
color: black;
}
.vehicle-details {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 1rem;
}
.detail-item {
display: flex;
flex-direction: column;
gap: 0.25rem;
}
.detail-label {
font-size: 0.875rem;
color: #666;
}
.detail-value {
font-weight: bold;
color: #333;
}
.vehicle-actions {
display: flex;
gap: 1rem;
margin-top: auto;
padding-top: 1rem;
border-top: 1px solid #eee;
}
.action-btn {
flex: 1;
padding: 0.75rem;
border: none;
border-radius: 4px;
cursor: pointer;
font-weight: bold;
transition: background-color 0.3s;
}
.spawn-btn {
background-color: #007bff;
color: white;
}
.spawn-btn:hover {
background-color: #0056b3;
}
.spawn-btn:disabled {
background-color: #6c757d;
cursor: not-allowed;
opacity: 0.65;
}
.spawn-btn:disabled:hover {
background-color: #6c757d;
}
.maintain-btn {
background-color: #ffc107;
color: black;
}
.maintain-btn:hover {
background-color: #d39e00;
}
.category-filters {
display: flex;
gap: 1rem;
margin-bottom: 2rem;
flex-wrap: wrap;
}
.filter-btn {
padding: 0.5rem 1rem;
border: none;
border-radius: 4px;
cursor: pointer;
background-color: #444;
color: white;
transition: background-color 0.3s;
}
.filter-btn:hover {
background-color: #666;
}
.filter-btn.active {
background-color: #007bff;
}
.vehicle-stats {
display: flex;
gap: 0.5rem;
align-items: center;
color: #666;
font-size: 0.875rem;
}
.stat-dot {
width: 8px;
height: 8px;
border-radius: 50%;
}
.stat-green {
background-color: #28a745;
}
.stat-yellow {
background-color: #ffc107;
}
.stat-red {
background-color: #dc3545;
}

View File

@ -0,0 +1,74 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>FORGE - Locker</title>
<link rel="stylesheet" href="styles.css">
<script src="script.js" defer></script>
</head>
<body>
<header>
<div class="container">
<div class="header-content">
<h1>Locker</h1>
<div class="storage-stats">
<div class="stat-item">
<span>📦</span>
<div>
<div>Storage Space</div>
<div id="storageSpace">0/100</div>
</div>
</div>
<div class="stat-item">
<span>🎒</span>
<div>
<div>Items Stored</div>
<div id="itemCount">0</div>
</div>
</div>
</div>
</div>
</div>
</header>
<main class="container">
<div class="category-filters" id="categoryFilters">
<button class="filter-btn active" data-category="all">All Items</button>
<button class="filter-btn" data-category="weapons">Weapons</button>
<button class="filter-btn" data-category="clothing">Clothing</button>
<button class="filter-btn" data-category="equipment">Equipment</button>
<button class="filter-btn" data-category="magazines">Magazines</button>
</div>
<div class="sections-grid">
<!-- Current Equipment Section -->
<div class="equipment-section player-equipment">
<h2>Current Equipment</h2>
<div class="equipment-list" id="playerEquipment">
<!-- Player equipment will be populated dynamically -->
</div>
</div>
<!-- Storage Section -->
<div class="equipment-section stored-equipment">
<h2>Stored Equipment</h2>
<div class="equipment-list" id="storedEquipment">
<!-- Stored equipment will be populated dynamically -->
</div>
</div>
<!-- Equipment Details Section -->
<div class="equipment-section">
<h2>Equipment Details</h2>
<div id="equipmentDetails">
<!-- Equipment details will be populated when an item is selected -->
<div class="detail-placeholder">
Select an item to view its details
</div>
</div>
</div>
</div>
</main>
</body>
</html>

View File

@ -0,0 +1,236 @@
// Simulated locker data - this would be replaced with actual game data
let lockerData = {
storageSpace: {
used: 45,
total: 100
},
playerEquipment: [
{
id: 1,
name: "Combat Uniform",
type: "clothing",
category: "Uniform",
condition: 95
},
{
id: 2,
name: "Tactical Vest",
type: "clothing",
category: "Vest",
condition: 88
},
{
id: 3,
name: "Combat Backpack",
type: "clothing",
category: "Backpack",
condition: 90
},
{
id: 4,
name: "Assault Rifle",
type: "weapons",
category: "Primary",
condition: 92,
attachments: ["Scope", "Grip"]
}
],
storedItems: [
{
id: 5,
name: "Pistol",
type: "weapons",
category: "Secondary",
condition: 100
},
{
id: 6,
name: "Medical Kit",
type: "equipment",
category: "Medical",
quantity: 3
},
{
id: 7,
name: "Rifle Magazines",
type: "magazines",
category: "Magazine",
quantity: 5
},
{
id: 8,
name: "Combat Helmet",
type: "clothing",
category: "Headgear",
condition: 85
}
]
};
// Initialize the locker interface
function initializeLocker() {
updateStats();
setupFilterListeners();
updateEquipmentLists();
}
// Update storage statistics
function updateStats() {
document.getElementById('storageSpace').textContent =
`${lockerData.storageSpace.used}/${lockerData.storageSpace.total}`;
document.getElementById('itemCount').textContent =
lockerData.storedItems.length;
}
// Set up category filter listeners
function setupFilterListeners() {
const filterButtons = document.querySelectorAll('.filter-btn');
filterButtons.forEach(button => {
button.addEventListener('click', (e) => {
// Update active state
filterButtons.forEach(btn => btn.classList.remove('active'));
button.classList.add('active');
// Filter items
const category = button.dataset.category;
filterEquipment(category);
});
});
}
// Filter equipment by category
function filterEquipment(category) {
const playerItems = category === 'all'
? lockerData.playerEquipment
: lockerData.playerEquipment.filter(item => item.type === category);
const storedItems = category === 'all'
? lockerData.storedItems
: lockerData.storedItems.filter(item => item.type === category);
updateEquipmentLists(playerItems, storedItems);
}
// Update equipment lists display
function updateEquipmentLists(playerItems = lockerData.playerEquipment, storedItems = lockerData.storedItems) {
const playerList = document.getElementById('playerEquipment');
const storedList = document.getElementById('storedEquipment');
// Update player equipment
playerList.innerHTML = playerItems.map(item => `
<div class="equipment-item" data-id="${item.id}">
<div class="item-info">
<span class="item-name">${item.name}</span>
<span class="item-type">${item.category}</span>
${item.condition ? `
<span class="item-quantity">
${item.condition}%
</span>
` : ''}
</div>
<button class="action-btn store-btn" onclick="storeItem(${item.id})">
Store
</button>
</div>
`).join('');
// Update stored equipment
storedList.innerHTML = storedItems.map(item => `
<div class="equipment-item" data-id="${item.id}">
<div class="item-info">
<span class="item-name">${item.name}</span>
<span class="item-type">${item.category}</span>
${item.quantity ? `
<span class="item-quantity">
x${item.quantity}
</span>
` : item.condition ? `
<span class="item-quantity">
${item.condition}%
</span>
` : ''}
</div>
<button class="action-btn equip-btn" onclick="equipItem(${item.id})">
Equip
</button>
</div>
`).join('');
// Add click listeners for showing details
document.querySelectorAll('.equipment-item').forEach(item => {
item.addEventListener('click', () => showItemDetails(item.dataset.id));
});
}
// Show item details
function showItemDetails(itemId) {
const item = lockerData.playerEquipment.find(i => i.id === parseInt(itemId)) ||
lockerData.storedItems.find(i => i.id === parseInt(itemId));
if (!item) return;
const detailsDiv = document.getElementById('equipmentDetails');
detailsDiv.innerHTML = `
<h3>${item.name}</h3>
<div class="detail-grid">
<div class="detail-item">
<span class="detail-label">Category:</span>
<span class="detail-value">${item.category}</span>
</div>
<div class="detail-item">
<span class="detail-label">Type:</span>
<span class="detail-value">${item.type}</span>
</div>
${item.condition ? `
<div class="detail-item">
<span class="detail-label">Condition:</span>
<span class="detail-value">${item.condition}%</span>
</div>
` : ''}
${item.quantity ? `
<div class="detail-item">
<span class="detail-label">Quantity:</span>
<span class="detail-value">x${item.quantity}</span>
</div>
` : ''}
${item.attachments ? `
<div class="detail-item">
<span class="detail-label">Attachments:</span>
<span class="detail-value">${item.attachments.join(', ')}</span>
</div>
` : ''}
</div>
`;
}
// Store an item in the locker
function storeItem(itemId) {
const itemIndex = lockerData.playerEquipment.findIndex(item => item.id === itemId);
if (itemIndex === -1) return;
const item = lockerData.playerEquipment[itemIndex];
lockerData.playerEquipment.splice(itemIndex, 1);
lockerData.storedItems.push(item);
lockerData.storageSpace.used += 1;
updateStats();
updateEquipmentLists();
}
// Equip an item from the locker
function equipItem(itemId) {
const itemIndex = lockerData.storedItems.findIndex(item => item.id === itemId);
if (itemIndex === -1) return;
const item = lockerData.storedItems[itemIndex];
lockerData.storedItems.splice(itemIndex, 1);
lockerData.playerEquipment.push(item);
lockerData.storageSpace.used -= 1;
updateStats();
updateEquipmentLists();
}
// Initialize when DOM is loaded
document.addEventListener('DOMContentLoaded', initializeLocker);

View File

@ -0,0 +1,194 @@
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: Arial, sans-serif;
line-height: 1.6;
background-color: #f4f4f4;
}
.container {
max-width: 1200px;
margin: 0 auto;
padding: 20px;
}
header {
background-color: #333;
color: white;
padding: 1rem 0;
margin-bottom: 2rem;
}
.header-content {
display: flex;
justify-content: space-between;
align-items: center;
}
.storage-stats {
display: flex;
gap: 2rem;
}
.stat-item {
display: flex;
align-items: center;
gap: 0.5rem;
background: rgba(255, 255, 255, 0.1);
padding: 0.5rem 1rem;
border-radius: 4px;
}
.sections-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
grid-template-rows: auto auto;
gap: 2rem;
padding: 1rem;
min-height: calc(100vh - 200px); /* Account for header and padding */
}
.equipment-section {
background-color: white;
border-radius: 8px;
overflow: hidden;
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
transition: transform 0.3s;
padding: 1.5rem;
display: flex;
flex-direction: column;
gap: 1rem;
max-height: 100%;
}
.equipment-section h2 {
display: flex;
align-items: center;
gap: 0.5rem;
font-size: 1.2rem;
color: #333;
}
.equipment-list {
list-style: none;
overflow-y: auto;
flex-grow: 1;
height: 0; /* This forces the flex-grow to work properly */
min-height: 400px;
}
.equipment-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 0.75rem;
border-bottom: 1px solid #eee;
transition: background-color 0.3s;
}
.equipment-item:hover {
background-color: #f8f9fa;
}
.equipment-item:last-child {
border-bottom: none;
}
.item-info {
display: flex;
align-items: center;
gap: 1rem;
}
.item-name {
font-weight: 500;
}
.item-type {
font-size: 0.875rem;
color: #666;
}
.item-quantity {
font-size: 0.875rem;
color: #28a745;
background: rgba(40, 167, 69, 0.1);
padding: 0.2rem 0.5rem;
border-radius: 4px;
}
.item-actions {
display: flex;
gap: 0.5rem;
}
.action-btn {
padding: 0.5rem 1rem;
border: none;
border-radius: 4px;
cursor: pointer;
font-weight: bold;
transition: background-color 0.3s;
}
.store-btn {
background-color: #007bff;
color: white;
}
.store-btn:hover {
background-color: #0056b3;
}
.equip-btn {
background-color: #28a745;
color: white;
}
.equip-btn:hover {
background-color: #218838;
}
.equip-btn:disabled {
background-color: #6c757d;
cursor: not-allowed;
opacity: 0.65;
}
.category-filters {
display: flex;
gap: 1rem;
margin-bottom: 2rem;
flex-wrap: wrap;
}
.filter-btn {
padding: 0.5rem 1rem;
border: none;
border-radius: 4px;
cursor: pointer;
background-color: #444;
color: white;
transition: background-color 0.3s;
}
.filter-btn:hover {
background-color: #666;
}
.filter-btn.active {
background-color: #007bff;
}
.equipment-section.player-equipment {
grid-column: span 2;
}
.equipment-section.stored-equipment {
grid-column: span 1;
grid-row: span 2;
}

View File

View File

@ -0,0 +1,90 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>FORGE - ORG</title>
<link rel="stylesheet" href="styles.css">
<script src="script.js" defer></script>
</head>
<body>
<header>
<div class="container">
<div class="header-content">
<h1 id="orgName">Organization Name</h1>
<div class="org-stats">
<div class="stat-item">
<span></span>
<div>
<div>Reputation</div>
<div id="orgReputation">0</div>
</div>
</div>
<div class="stat-item">
<span>💰</span>
<div>
<div>Funds</div>
<div id="orgFunds">$0</div>
</div>
</div>
</div>
</div>
</div>
</header>
<main class="container">
<div class="sections-grid">
<!-- Members Section -->
<div class="section-tile">
<h2>Members</h2>
<ul class="member-list" id="membersList">
<!-- Members will be populated dynamically -->
</ul>
</div>
<!-- Vehicles Section -->
<div class="section-tile">
<h2>Vehicles</h2>
<ul class="asset-list" id="vehiclesList">
<!-- Vehicles will be populated dynamically -->
</ul>
</div>
<!-- Equipment Section -->
<div class="section-tile">
<h2>Equipment</h2>
<ul class="asset-list" id="equipmentList">
<!-- Equipment will be populated dynamically -->
</ul>
</div> <!-- Properties Section -->
<div class="section-tile">
<h2>Properties</h2>
<ul class="asset-list" id="propertiesList">
<!-- Properties will be populated dynamically -->
</ul>
</div> <!-- Supplies Section -->
<div class="section-tile">
<h2>Supplies</h2>
<ul class="asset-list" id="suppliesList">
<!-- Supplies will be populated dynamically -->
</ul>
</div> <!-- Transactions Section -->
<div class="section-tile">
<h2>Transactions</h2>
<ul class="transaction-list" id="transactionsList">
<!-- Transactions will be populated dynamically -->
</ul>
</div> <!-- Memos Section -->
<div class="section-tile memo-section">
<h2>Memos & Logs</h2>
<div class="memo-controls">
<button id="addMemoBtn" class="add-btn">Add Memo</button>
</div>
<ul class="memo-list" id="memosList">
<!-- Memos will be populated dynamically -->
</ul>
</div>
</div>
</main>
</body>
</html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 92 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 109 KiB

Binary file not shown.

View File

@ -0,0 +1,302 @@
// Simulated organization data - this would be replaced with actual game data
let orgData = {
name: "Black Rifle Company",
reputation: 1250,
funds: 75000,
transactions: [
{ id: 1, type: "deposit", amount: 5000, description: "Weekly income", date: "2025-04-12T10:30:00" },
{ id: 2, type: "withdrawal", amount: -2000, description: "Vehicle maintenance", date: "2025-04-11T15:45:00" },
{ id: 3, type: "deposit", amount: 3000, description: "Property rent", date: "2025-04-10T09:15:00" },
{ id: 4, type: "withdrawal", amount: -1500, description: "Equipment purchase", date: "2025-04-09T14:20:00" }
],
members: [
{ id: 1, name: "John Doe", role: "owner", status: "online" },
{ id: 2, name: "Jane Smith", role: "admin", status: "online" },
{ id: 3, name: "Mike Johnson", role: "member", status: "offline" },
{ id: 4, name: "Sarah Wilson", role: "member", status: "online" }
],
vehicles: [
{ id: 1, name: "Transport Truck", type: "Vehicle", value: 25000 },
{ id: 2, name: "Patrol Car", type: "Vehicle", value: 15000 }
],
equipment: [
{ id: 1, name: "Combat Gear Set", type: "Equipment", value: 5000 },
{ id: 3, name: "Radio Equipment", type: "Equipment", value: 3000 }
],
properties: [
{ id: 1, name: "Main Base", type: "Property", value: 100000 },
{ id: 2, name: "Storage Facility", type: "Property", value: 50000 }
],
supplies: [
{ id: 1, name: "Medical Supplies", type: "Supply", value: 2000, quantity: 50 },
{ id: 2, name: "Ammunition", type: "Supply", value: 5000, quantity: 1000 },
{ id: 3, name: "Food Rations", type: "Supply", value: 1000, quantity: 100 },
{ id: 4, name: "Repair Kits", type: "Supply", value: 3000, quantity: 25 }
],
memos: [
{
id: 1,
title: "Weekly Mission Update",
content: "New assignments available in the northern sector. All teams please check your mission boards.",
author: "John Doe",
date: "2025-04-12T08:30:00",
priority: "high"
},
{
id: 2,
title: "Equipment Maintenance",
content: "Regular maintenance check required for all vehicles by end of week.",
author: "Jane Smith",
date: "2025-04-11T14:15:00",
priority: "medium"
}
]
};
// Initialize the organization interface
function initializeOrg() {
updateOrgInfo();
updateMembers();
updateAssets();
updateTransactions();
updateMemos();
setupMemoControls();
}
// Update organization info in the header
function updateOrgInfo() {
document.getElementById('orgName').textContent = orgData.name;
document.getElementById('orgReputation').textContent = orgData.reputation.toLocaleString();
document.getElementById('orgFunds').textContent = `$${orgData.funds.toLocaleString()}`;
}
// Update members list
function updateMembers() {
const membersList = document.getElementById('membersList');
membersList.innerHTML = '';
orgData.members.forEach(member => {
const li = document.createElement('li');
li.className = 'member-item';
li.innerHTML = `
<div class="member-info">
<span class="member-status status-${member.status}"></span>
<span>${member.name}</span>
<span class="member-role role-${member.role}">${member.role}</span>
</div>
`;
membersList.appendChild(li);
});
}
// Update assets lists (vehicles, equipment, properties)
function updateAssets() {
// Update vehicles
const vehiclesList = document.getElementById('vehiclesList');
vehiclesList.innerHTML = '';
orgData.vehicles.forEach(vehicle => {
const li = document.createElement('li');
li.className = 'asset-item';
li.innerHTML = `
<div class="asset-info">
<span>${vehicle.name}</span>
<span class="asset-type">${vehicle.type}</span>
</div>
<span class="asset-value">$${vehicle.value.toLocaleString()}</span>
`;
vehiclesList.appendChild(li);
});
// Update equipment
const equipmentList = document.getElementById('equipmentList');
equipmentList.innerHTML = '';
orgData.equipment.forEach(item => {
const li = document.createElement('li');
li.className = 'asset-item';
li.innerHTML = `
<div class="asset-info">
<span>${item.name}</span>
<span class="asset-type">${item.type}</span>
</div>
<span class="asset-value">$${item.value.toLocaleString()}</span>
`;
equipmentList.appendChild(li);
}); // Update properties
const propertiesList = document.getElementById('propertiesList');
propertiesList.innerHTML = '';
orgData.properties.forEach(property => {
const li = document.createElement('li');
li.className = 'asset-item';
li.innerHTML = `
<div class="asset-info">
<span>${property.name}</span>
<span class="asset-type">${property.type}</span>
</div>
<span class="asset-value">$${property.value.toLocaleString()}</span>
`;
propertiesList.appendChild(li);
});
// Update supplies
const suppliesList = document.getElementById('suppliesList');
suppliesList.innerHTML = '';
orgData.supplies.forEach(supply => {
const li = document.createElement('li');
li.className = 'asset-item';
li.innerHTML = `
<div class="asset-info">
<span>${supply.name}</span>
<div class="supply-details">
<span class="asset-type">${supply.type}</span>
<span class="supply-quantity">Qty: ${supply.quantity}</span>
</div>
</div>
<span class="asset-value">$${supply.value.toLocaleString()}</span>
`;
suppliesList.appendChild(li);
});
}
// Update transactions list
function updateTransactions() {
const transactionsList = document.getElementById('transactionsList');
transactionsList.innerHTML = '';
orgData.transactions.forEach(transaction => {
const li = document.createElement('li');
li.className = 'transaction-item';
const date = new Date(transaction.date);
const formattedDate = date.toLocaleDateString('en-US', {
year: 'numeric',
month: 'short',
day: 'numeric',
hour: '2-digit',
minute: '2-digit'
});
const amountClass = transaction.amount >= 0 ? 'amount-positive' : 'amount-negative';
const prefix = transaction.amount >= 0 ? '+' : '';
li.innerHTML = `
<div class="transaction-info">
<div>${transaction.description}</div>
<div class="transaction-date">${formattedDate}</div>
</div>
<span class="transaction-amount ${amountClass}">${prefix}$${Math.abs(transaction.amount).toLocaleString()}</span>
`;
transactionsList.appendChild(li);
});
}
// Update memos list
function updateMemos() {
const memosList = document.getElementById('memosList');
memosList.innerHTML = '';
orgData.memos.forEach(memo => {
const li = document.createElement('li');
li.className = 'memo-item';
const date = new Date(memo.date);
const formattedDate = date.toLocaleDateString('en-US', {
year: 'numeric',
month: 'short',
day: 'numeric',
hour: '2-digit',
minute: '2-digit'
});
li.innerHTML = `
<div class="memo-header">
<div class="memo-title">${memo.title}</div>
<div class="memo-metadata">
<span class="memo-author">by ${memo.author}</span>
<span class="memo-date">${formattedDate}</span>
</div>
</div>
<div class="memo-content">${memo.content}</div>
`;
memosList.appendChild(li);
});
}
// Set up memo controls and dialog
function setupMemoControls() {
const addMemoBtn = document.getElementById('addMemoBtn');
addMemoBtn.addEventListener('click', () => {
showMemoDialog();
});
}
// Show memo creation dialog
function showMemoDialog() {
const dialog = document.createElement('div');
dialog.className = 'memo-dialog';
dialog.innerHTML = `
<form class="memo-form" id="memoForm">
<div class="form-group">
<label for="memoTitle">Title</label><br/>
<input type="text" id="memoTitle" required>
</div>
<div class="form-group">
<label for="memoContent">Content</label><br/>
<textarea id="memoContent" required></textarea>
</div>
<div class="memo-form-buttons">
<button type="button" class="cancel-btn" id="cancelMemo">Cancel</button>
<button type="submit" class="submit-btn">Add Memo</button>
</div>
</form>
`;
const overlay = document.createElement('div');
overlay.className = 'memo-dialog-overlay';
document.body.appendChild(overlay);
document.body.appendChild(dialog);
const form = dialog.querySelector('#memoForm');
const cancelBtn = dialog.querySelector('#cancelMemo');
form.addEventListener('submit', (e) => {
e.preventDefault();
const newMemo = {
id: orgData.memos.length + 1,
title: document.getElementById('memoTitle').value,
content: document.getElementById('memoContent').value,
author: orgData.members.find(m => m.role === 'owner').name,
date: new Date().toISOString(),
priority: "normal"
};
orgData.memos.unshift(newMemo);
updateMemos();
closeMemoDialog(dialog, overlay);
});
cancelBtn.addEventListener('click', () => {
closeMemoDialog(dialog, overlay);
});
overlay.addEventListener('click', () => {
closeMemoDialog(dialog, overlay);
});
}
// Close memo dialog
function closeMemoDialog(dialog, overlay) {
document.body.removeChild(dialog);
document.body.removeChild(overlay);
}
// Initialize when DOM is loaded
document.addEventListener('DOMContentLoaded', initializeOrg);

View File

@ -0,0 +1,359 @@
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: Arial, sans-serif;
line-height: 1.6;
background-color: #f4f4f4;
}
.container {
max-width: 1200px;
margin: 0 auto;
padding: 20px;
}
header {
background-color: #333;
color: white;
padding: 1rem 0;
margin-bottom: 2rem;
}
.header-content {
display: flex;
justify-content: space-between;
align-items: center;
}
.org-stats {
display: flex;
gap: 2rem;
margin-left: auto;
}
.stat-item {
display: flex;
align-items: center;
gap: 0.5rem;
background: rgba(255, 255, 255, 0.1);
padding: 0.5rem 1rem;
border-radius: 4px;
}
.stat-item i {
font-size: 1.2rem;
}
.sections-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
gap: 2rem;
padding: 1rem;
}
/* Make memo section span full width */
.section-tile.memo-section {
grid-column: 1 / -1;
aspect-ratio: unset;
min-height: 400px;
}
.section-tile {
background-color: white;
border-radius: 8px;
overflow: hidden;
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
transition: transform 0.3s;
aspect-ratio: 1;
padding: 2rem;
display: flex;
flex-direction: column;
}
.section-tile:hover {
transform: translateY(-5px);
}
.section-tile h2 {
font-size: 1.5rem;
color: #333;
margin-bottom: 1rem;
display: flex;
align-items: center;
gap: 0.5rem;
}
.member-list, .asset-list {
list-style: none;
overflow-y: auto;
flex-grow: 1;
}
.member-item, .asset-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 0.75rem;
border-bottom: 1px solid #eee;
}
.member-item:last-child, .asset-item:last-child {
border-bottom: none;
}
.member-info, .asset-info {
display: flex;
align-items: center;
gap: 1rem;
}
.member-role {
font-size: 0.875rem;
padding: 0.25rem 0.5rem;
border-radius: 4px;
background-color: #e9ecef;
}
.role-owner {
background-color: #ffd700;
color: #000;
}
.role-admin {
background-color: #dc3545;
color: white;
}
.role-member {
background-color: #28a745;
color: white;
}
.asset-type {
font-size: 0.875rem;
color: #666;
}
.supply-details {
display: flex;
gap: 1rem;
align-items: center;
}
.supply-quantity {
font-size: 0.875rem;
color: #28a745;
background: rgba(40, 167, 69, 0.1);
padding: 0.2rem 0.5rem;
border-radius: 4px;
}
.asset-value {
color: #007bff;
font-weight: bold;
}
.transaction-list {
list-style: none;
overflow-y: auto;
flex-grow: 1;
}
.transaction-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 0.75rem;
border-bottom: 1px solid #eee;
}
.transaction-item:last-child {
border-bottom: none;
}
.transaction-info {
display: flex;
flex-direction: column;
gap: 0.25rem;
}
.transaction-description {
font-size: 0.9rem;
color: #666;
}
.transaction-date {
font-size: 0.8rem;
color: #888;
}
.transaction-amount {
font-weight: bold;
}
.amount-positive {
color: #28a745;
}
.amount-negative {
color: #dc3545;
}
.memo-controls {
margin-bottom: 1rem;
display: flex;
justify-content: flex-end;
}
.memo-list {
list-style: none;
overflow-y: auto;
flex-grow: 1;
}
.memo-item {
padding: 1rem;
border-bottom: 1px solid #eee;
display: flex;
flex-direction: column;
gap: 0.5rem;
}
.memo-item:last-child {
border-bottom: none;
}
.memo-header {
display: flex;
justify-content: space-between;
align-items: flex-start;
}
.memo-title {
font-weight: bold;
color: #333;
}
.memo-metadata {
display: flex;
gap: 1rem;
font-size: 0.8rem;
color: #666;
}
.memo-author {
color: #007bff;
}
.memo-content {
color: #444;
line-height: 1.4;
}
.memo-dialog {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background: white;
padding: 2rem;
border-radius: 8px;
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
z-index: 1000;
width: 90%;
max-width: 500px;
}
.memo-dialog-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0,0,0,0.5);
z-index: 999;
}
.memo-form {
display: flex;
flex-direction: column;
gap: 1rem;
}
.memo-form input {
width: 100%;
padding: 0.5rem;
border: 1px solid #ddd;
border-radius: 4px;
}
.memo-form textarea {
width: 100%;
min-height: 100px;
padding: 0.5rem;
border: 1px solid #ddd;
border-radius: 4px;
resize: vertical;
}
.memo-form-buttons {
display: flex;
justify-content: flex-end;
gap: 1rem;
}
.cancel-btn {
width: 100%;
background-color: #6c757d;
color: white;
border: none;
border-radius: 4px;
padding: 0.75rem 1rem;
font-size: 1rem;
cursor: pointer;
transition: background-color 0.3s;
}
.add-btn {
border: none;
border-radius: 4px;
padding: 0.75rem 1rem;
font-size: 1rem;
cursor: pointer;
transition: background-color 0.3s;
}
.submit-btn {
width: 100%;
background-color: #007bff;
color: white;
border: none;
border-radius: 4px;
padding: 0.75rem 1rem;
font-size: 1rem;
cursor: pointer;
transition: background-color 0.3s;
}
.cancel-btn:hover {
background-color: #5a6268;
}
.member-status {
width: 8px;
height: 8px;
border-radius: 50%;
margin-right: 0.5rem;
}
.status-online {
background-color: #28a745;
}
.status-offline {
background-color: #dc3545;
}

View File

@ -11,13 +11,13 @@
* 0: Class Name <STRING> - The classname of the item to purchase
* 1: Price <NUMBER> - The price of the item
* 2: Config Type <STRING> - The type of config ("item", "weapon", "magazine", "backpack")
* 3: Item Type <STRING> - The type of item for locker storage
* 3: Item Type <STRING> - The type of item for locker storage ("backpack", "facewear", "headgear", "hmd", "item", "magazine", "uniform", "vest", "weapon")
*
* Return Value:
* None
*
* Example:
* ["arifle_MX_F", 1000, "weapon", "primary"] call forge_store_fnc_buyItem
* ["arifle_MX_F", 1000, "weapon", "weapon"] call forge_store_fnc_buyItem
*
* Public: No
*/

View File

@ -10,7 +10,7 @@
* Arguments:
* 0: Class Name <STRING> - The classname of the vehicle to purchase
* 1: Price <NUMBER> - The price of the vehicle
* 2: Vehicle Type <STRING> - The type of vehicle ("car", "air", "ship", "tank")
* 2: Vehicle Type <STRING> - The type of vehicle ("car", "armor", "heli", "plane", "naval", "static")
*
* Return Value:
* None

View File

@ -23,6 +23,7 @@ params ["_price"];
private _paymentData = GVAR(activePayment);
private _payment = call compile _paymentData;
private _store = nil;
scopeName "main";
@ -36,7 +37,7 @@ if (count _payment > 3) then {
};
if (_payment select 0 == "Organization") then {
private _store = call EFUNC(org,verifyOrgStore);
_store = call EFUNC(org,verifyOrgStore);
private _org = _store call ["getOrg", []];
private _ownerUID = _org get "owner";
@ -49,7 +50,6 @@ if (_payment select 0 == "Organization") then {
private _varType = _payment select 2;
private _balance = switch (_varType) do {
case "organization": {
private _store = call EFUNC(org,verifyOrgStore);
_store call ["getFunds", []];
};
case "player": { player getVariable [_payment select 1, 0] };
@ -64,7 +64,6 @@ if (_balance < _price) exitWith {
switch (_varType) do {
case "organization": {
private _store = call EFUNC(org,verifyOrgStore);
_store call ["updateFunds", -_price];
};
case "player": {

View File

@ -0,0 +1,150 @@
{
"paymentMethods": [
{
"id": "cash",
"name": "Cash",
"icon": "💵"
},
{
"id": "bank",
"name": "Bank",
"icon": "🏦"
}
],
"Weapons": {
"icon": "",
"subcategories": {
"Rifles": {
"icon": "",
"products": [
{
"id": 1,
"name": "Combat Rifle",
"price": 1500,
"image": "placeholder.jpg"
},
{
"id": 2,
"name": "Sniper Rifle",
"price": 2500,
"image": "placeholder.jpg"
}
]
},
"Pistols": {
"icon": "",
"products": [
{
"id": 3,
"name": "Tactical Pistol",
"price": 800,
"image": "placeholder.jpg"
}
]
}
}
},
"Equipment": {
"icon": "",
"subcategories": {
"Vests": {
"icon": "",
"products": [
{
"id": 4,
"name": "Tactical Vest",
"price": 800,
"image": "placeholder.jpg"
}
]
},
"Headwear": {
"icon": "",
"products": [
{
"id": 5,
"name": "Combat Helmet",
"price": 1200,
"image": "placeholder.jpg"
}
]
},
"Facewear": {
"icon": "",
"products": [
{
"id": 6,
"name": "Balaclava",
"price": 500,
"image": "placeholder.jpg"
}
]
},
"Hmd": {
"icon": "",
"products": [
{
"id": 7,
"name": "Night Vision Goggles",
"price": 2000,
"image": "placeholder.jpg"
}
]
},
"Backpacks": {
"icon": "",
"products": [
{
"id": 8,
"name": "Combat Backpack",
"price": 450,
"image": "placeholder.jpg"
}
]
},
"Uniforms": {
"icon": "",
"products": [
{
"id": 9,
"name": "Combat Fatigues",
"price": 1000,
"image": "placeholder.jpg"
}
]
}
}
},
"Vehicles": {
"icon": "",
"subcategories": {
"Wheeled": {
"icon": "",
"products": [
{
"id": 10,
"name": "Transport Vehicle",
"price": 25000,
"image": "placeholder.jpg"
}
]
}
}
},
"Supplies": {
"icon": "",
"subcategories": {
"Medical": {
"icon": "",
"products": [
{
"id": 11,
"name": "First Aid Kit",
"price": 300,
"image": "placeholder.jpg"
}
]
}
}
}
}

View File

@ -0,0 +1,27 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>FORGE - GMS</title>
<link rel="stylesheet" href="styles.css">
<script src="script.js" defer></script>
</head>
<body>
<header>
<div class="container">
<div class="header-content">
<h1>General Military Surplus</h1>
<div class="payment-method">
<select id="paymentMethod" class="payment-select">
<option value="" disabled selected>Select Payment Method</option>
</select>
</div>
</div>
</div>
</header>
<main class="container">
<!-- Content will be dynamically populated -->
</main>
</body>
</html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 92 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 109 KiB

Binary file not shown.

View File

@ -0,0 +1,174 @@
// Store data will be loaded from JSON
let categories = null;
let currentCategory = null;
let currentSubcategory = null;
let selectedPaymentMethod = null;
function initializePaymentMethods() {
const paymentSelect = document.getElementById('paymentMethod');
paymentSelect.innerHTML = `
<option value="" disabled ${!selectedPaymentMethod ? 'selected' : ''}>Select Payment Method</option>
${categories.paymentMethods.map(method => `
<option value="${method.id}" ${method.id === selectedPaymentMethod ? 'selected' : ''}>
${method.icon} ${method.name}
</option>
`).join('')}
`;
paymentSelect.addEventListener('change', (e) => {
selectedPaymentMethod = e.target.value;
updateBuyButtons();
});
}
function updateBuyButtons() {
const buyButtons = document.querySelectorAll('.buy-btn');
buyButtons.forEach(button => {
button.disabled = !selectedPaymentMethod;
});
}
function handleQuantityChange(productId, change, quantityDisplay) {
const currentQty = parseInt(quantityDisplay.textContent);
const newQty = Math.max(1, currentQty + change);
quantityDisplay.textContent = newQty;
}
function handlePurchase(product, quantity) {
if (!selectedPaymentMethod) {
alert('Please select a payment method first');
return;
}
const total = product.price * quantity;
alert(`Purchase Summary:
Product: ${product.name}
Quantity: ${quantity}
Total: $${total.toLocaleString()}
Payment Method: ${categories.paymentMethods.find(m => m.id === selectedPaymentMethod).name}`);
// Here you would typically integrate with your game's purchasing system
}
function showMainCategories() {
const mainContent = document.querySelector('main');
currentCategory = null;
currentSubcategory = null;
mainContent.innerHTML = `
<div class="categories-grid"></div>
`;
const categoriesGrid = document.querySelector('.categories-grid');
Object.entries(categories)
.filter(([key]) => key !== 'paymentMethods')
.forEach(([categoryName, categoryData]) => {
const categoryTile = document.createElement('button');
categoryTile.className = 'category-tile';
categoryTile.innerHTML = `
<span class="icon">${categoryData.icon}</span>
<span class="name">${categoryName}</span>
`;
categoryTile.addEventListener('click', () => showSubcategories(categoryName));
categoriesGrid.appendChild(categoryTile);
});
}
function showSubcategories(categoryName) {
const mainContent = document.querySelector('main');
currentCategory = categoryName;
currentSubcategory = null;
mainContent.innerHTML = `
<button class="back-button"> Back to Categories</button>
<h2>${categoryName}</h2>
<div class="subcategories-grid"></div>
`;
const subcategoriesGrid = document.querySelector('.subcategories-grid');
const backButton = document.querySelector('.back-button');
backButton.addEventListener('click', showMainCategories);
Object.entries(categories[categoryName].subcategories).forEach(([subCategoryName, subCategoryData]) => {
const subCategoryTile = document.createElement('button');
subCategoryTile.className = 'subcategory-tile';
subCategoryTile.innerHTML = `
<span class="icon">${subCategoryData.icon}</span>
<span class="name">${subCategoryName}</span>
`;
subCategoryTile.addEventListener('click', () => showProducts(categoryName, subCategoryName));
subcategoriesGrid.appendChild(subCategoryTile);
});
}
function showProducts(categoryName, subCategoryName) {
const mainContent = document.querySelector('main');
currentSubcategory = subCategoryName;
mainContent.innerHTML = `
<button class="back-button"> Back to ${categoryName}</button>
<h2>${categoryName} > ${subCategoryName}</h2>
<div class="products-grid"></div>
`;
const productsGrid = document.querySelector('.products-grid');
const backButton = document.querySelector('.back-button');
backButton.addEventListener('click', () => showSubcategories(categoryName));
const products = categories[categoryName].subcategories[subCategoryName].products;
products.forEach(product => {
const productCard = document.createElement('div');
productCard.className = 'product-card';
productCard.innerHTML = `
<div class="product-image"></div>
<div class="product-info">
<h3 class="product-name">${product.name}</h3>
<p class="product-price">$${product.price.toLocaleString()}</p>
<div class="product-controls">
<div class="quantity-controls">
<button class="quantity-btn minus">-</button>
<span class="quantity-display">1</span>
<button class="quantity-btn plus">+</button>
</div>
<button class="buy-btn" ${!selectedPaymentMethod ? 'disabled' : ''}>
Buy Now
</button>
</div>
</div>
`;
const quantityDisplay = productCard.querySelector('.quantity-display');
const minusBtn = productCard.querySelector('.quantity-btn.minus');
const plusBtn = productCard.querySelector('.quantity-btn.plus');
const buyBtn = productCard.querySelector('.buy-btn');
minusBtn.addEventListener('click', () => handleQuantityChange(product.id, -1, quantityDisplay));
plusBtn.addEventListener('click', () => handleQuantityChange(product.id, 1, quantityDisplay));
buyBtn.addEventListener('click', () => handlePurchase(product, parseInt(quantityDisplay.textContent)));
productsGrid.appendChild(productCard);
});
}
// Initialize the store
async function initializeStore() {
try {
const response = await fetch('http://localhost:8000/data/categories.json');
if (!response.ok) {
throw new Error('Failed to load store data');
}
categories = await response.json();
initializePaymentMethods();
showMainCategories();
} catch (error) {
console.error('Error loading store data:', error);
document.querySelector('main').innerHTML = `
<div class="error-message">
<h2>Error Loading Store Data</h2>
<p>Please try again later.</p>
</div>
`;
}
}
document.addEventListener('DOMContentLoaded', () => {
initializeStore();
});

View File

@ -0,0 +1,16 @@
import http.server
import socketserver
PORT = 8000
DIRECTORY = '.'
class Handler(http.server.SimpleHTTPRequestHandler):
def __init__(self, *args, **kwargs):
super().__init__(*args, directory=DIRECTORY, **kwargs)
with socketserver.TCPServer(("", PORT), Handler) as httpd:
print(f"Serving at http://localhost:{PORT}")
try:
httpd.serve_forever()
except KeyboardInterrupt:
print("\nServer stopped.")

View File

@ -0,0 +1,200 @@
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: Arial, sans-serif;
line-height: 1.6;
background-color: #f4f4f4;
}
.container {
max-width: 1200px;
margin: 0 auto;
padding: 20px;
}
header {
background-color: #333;
color: white;
padding: 1rem 0;
margin-bottom: 2rem;
}
header h1 {
text-align: center;
}
.categories-grid, .subcategories-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
gap: 2rem;
padding: 1rem;
margin-bottom: 2rem;
}
.category-tile, .subcategory-tile {
background-color: #444;
color: white;
border: none;
border-radius: 8px;
cursor: pointer;
transition: all 0.3s;
aspect-ratio: 1;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
text-align: center;
padding: 1rem;
font-size: 1.3rem;
}
.category-tile:hover, .subcategory-tile:hover {
background-color: #666;
transform: translateY(-5px);
}
.category-tile.active, .subcategory-tile.active {
background-color: #007bff;
}
.category-tile i, .subcategory-tile i {
font-size: 2rem;
margin-bottom: 1rem;
}
.back-button {
background-color: #444;
color: white;
border: none;
border-radius: 4px;
padding: 0.5rem 1rem;
cursor: pointer;
margin-bottom: 1rem;
display: inline-flex;
align-items: center;
gap: 0.5rem;
}
.back-button:hover {
background-color: #666;
}
.products-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
gap: 2rem;
padding: 1rem;
}
.product-card {
background-color: white;
border-radius: 8px;
overflow: hidden;
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
transition: transform 0.3s;
}
.product-card:hover {
transform: translateY(-5px);
}
.product-image {
width: 100%;
height: 256px;
object-fit: cover;
background-color: #eee;
}
.product-info {
padding: 1rem;
}
.product-name {
font-size: 1.1rem;
margin-bottom: 0.5rem;
color: #333;
}
.product-price {
font-size: 1.2rem;
color: #007bff;
font-weight: bold;
}
.header-content {
display: flex;
justify-content: space-between;
align-items: center;
}
.payment-select {
padding: 0.5rem;
border-radius: 4px;
background-color: white;
border: 1px solid #ddd;
font-size: 1rem;
}
.product-controls {
display: flex;
align-items: center;
gap: 1rem;
margin-top: 1rem;
padding-top: 1rem;
border-top: 1px solid #eee;
}
.quantity-controls {
display: flex;
align-items: center;
gap: 0.5rem;
}
.quantity-btn {
background-color: #007bff;
color: white;
border: none;
border-radius: 4px;
width: 30px;
height: 30px;
font-size: 1.2rem;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
}
.quantity-btn:hover {
background-color: #0056b3;
}
.quantity-display {
font-size: 1.1rem;
min-width: 40px;
text-align: center;
}
.buy-btn {
flex: 1;
background-color: #28a745;
color: white;
border: none;
border-radius: 4px;
padding: 0.5rem 1rem;
font-size: 1rem;
cursor: pointer;
transition: background-color 0.3s;
}
.buy-btn:hover {
background-color: #218838;
}
.buy-btn:disabled {
background-color: #6c757d;
cursor: not-allowed;
}