Initial commit: SREBOT website (Express/EJS + i18n) - extracted from SREBOT monorepo

This commit is contained in:
clxud
2026-07-02 02:35:56 +00:00
commit 7f2ab08adc
145 changed files with 148257 additions and 0 deletions
+18
View File
@@ -0,0 +1,18 @@
# Environment
NODE_ENV=production
# Server Configuration
PORT=3000
# Domain Configuration (CORS)
PRODUCTION_DOMAIN=https://sre.pawjob.us
# External API Configuration
EXTERNAL_API_URL=http://localhost:6000
# Logging Configuration
LOG_LEVEL=info
# Rate Limiting
RATE_LIMIT_WINDOW_MS=60000
RATE_LIMIT_MAX_REQUESTS=100
+2
View File
@@ -0,0 +1,2 @@
# Auto detect text files and perform LF normalization
* text=auto
+56
View File
@@ -0,0 +1,56 @@
# Dependencies
node_modules/
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# Environment variables
.env
.env.local
.env.development.local
.env.test.local
.env.production.local
# Logs
logs
*.log
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Coverage directory used by tools like istanbul
coverage/
*.lcov
# Operating System Files
.DS_Store
.DS_Store?
._*
.Spotlight-V100
.Trashes
ehthumbs.db
Thumbs.db
# Editor directories and files
.vscode/
.idea/
*.swp
*.swo
*~
.claude/
# Temporary files
*.tmp
*.temp
temp/
# Debug and test files
debug-*.js
test-*.js
# Build output (obfuscated files and generated CSS)
public/js/dist/
public/css/output.css.map
Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 692 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 593 B

+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2025 Sop
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 MiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 627 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 460 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 574 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 574 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 819 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 MiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 MiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 786 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1012 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 773 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 MiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 1006 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 777 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 818 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 MiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 927 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 948 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 849 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 829 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 929 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 858 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 885 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 477 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 921 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 818 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 MiB

+55
View File
@@ -0,0 +1,55 @@
# SREBOT-web
Toothless SQB Bot Website — Express.js + EJS frontend with Tailwind CSS and i18n support (10 languages).
## Features
- Squadron leaderboard, profiles, and stats
- Player profiles with vehicle stats
- Game detail pages with replay visualization
- Season timeline and comparison tools
- Tournament bracket viewer
- Analytics dashboard
- Multi-language support (en, ru, fr, it, uk, de, es, pl, cs, zh-CN)
## Development
```bash
npm install
cp env.example .env # edit with your values
npm run dev
```
## Build
```bash
npm run build # builds CSS + obfuscated JS
```
## Production
```bash
npm run build
node server.js # or: npm run start
```
## PM2
```bash
npm run pm2:start # cluster mode (3 instances)
npm run pm2:logs
```
## Environment Variables
See `env.example` for all available variables. Key ones:
| Variable | Description |
|---|---|
| `PORT` | Server port (default: 3001) |
| `EXTERNAL_API_URL` | SREBOT API URL (default: http://localhost:6000) |
| `STORAGE_VOL_PATH` | Storage volume path (required) |
| `PYTHON_BIN` | Python binary for replay rendering |
| `RECAP_SCRIPT` | Path to render_recap.py (optional) |
| `SHARED_ICONS_DIR` | Path to vehicle icons directory |
| `SHARED_MINIMAPS_DIR` | Path to minimaps directory |
+56
View File
@@ -0,0 +1,56 @@
const fs = require('fs');
const postcss = require('postcss');
const tailwindcss = require('tailwindcss');
const autoprefixer = require('autoprefixer');
const cssnano = require('cssnano');
const inputPath = './public/css/tailwind.css';
const outputPath = './public/css/output.css';
const isProduction = process.env.NODE_ENV === 'production';
async function buildCSS() {
console.log('[CSS Build] Reading input file...');
const css = fs.readFileSync(inputPath, 'utf8');
console.log(`[CSS Build] Processing with PostCSS and Tailwind... (${isProduction ? 'production' : 'development'} mode)`);
const plugins = [
tailwindcss(),
autoprefixer()
];
// Only minify in production
if (isProduction) {
plugins.push(cssnano({
preset: ['default', {
discardComments: {
removeAll: true,
},
normalizeWhitespace: true,
}]
}));
}
const result = await postcss(plugins).process(css, {
from: inputPath,
to: outputPath,
map: !isProduction ? { inline: false } : false
});
console.log('[CSS Build] Writing output file...');
fs.writeFileSync(outputPath, result.css);
if (result.map && !isProduction) {
fs.writeFileSync(outputPath + '.map', result.map.toString());
}
const sizeKB = (Buffer.byteLength(result.css, 'utf8') / 1024).toFixed(2);
console.log('[CSS Build] ✓ CSS build complete!');
console.log(`[CSS Build] Output: ${outputPath} (${sizeKB} KB)`);
}
buildCSS().catch(err => {
console.error('[CSS Build] Error:', err);
process.exit(1);
});
+81
View File
@@ -0,0 +1,81 @@
const JavaScriptObfuscator = require('javascript-obfuscator');
const fs = require('fs');
const path = require('path');
const PUBLIC_JS_DIR = path.join(__dirname, 'public', 'js');
const OUTPUT_DIR = path.join(__dirname, 'public', 'js', 'dist');
// Obfuscation options - balanced between security and performance
const obfuscationOptions = {
compact: true,
controlFlowFlattening: true,
controlFlowFlatteningThreshold: 0.75,
deadCodeInjection: true,
deadCodeInjectionThreshold: 0.4,
debugProtection: false,
debugProtectionInterval: 0,
disableConsoleOutput: true,
identifierNamesGenerator: 'hexadecimal',
log: false,
numbersToExpressions: true,
renameGlobals: false,
selfDefending: true,
simplify: true,
splitStrings: true,
splitStringsChunkLength: 10,
stringArray: true,
stringArrayCallsTransform: true,
stringArrayEncoding: ['base64'],
stringArrayIndexShift: true,
stringArrayRotate: true,
stringArrayShuffle: true,
stringArrayWrappersCount: 2,
stringArrayWrappersChainedCalls: true,
stringArrayWrappersParametersMaxCount: 4,
stringArrayWrappersType: 'function',
stringArrayThreshold: 0.75,
transformObjectKeys: true,
unicodeEscapeSequence: false
};
// Create output directory if it doesn't exist
if (!fs.existsSync(OUTPUT_DIR)) {
fs.mkdirSync(OUTPUT_DIR, { recursive: true });
}
console.log('[BUILD] Starting JavaScript obfuscation...');
// Get all JS files in the public/js directory
const SKIP_FILES = ['replay-canvas.js', 'replay-canvas-3d.js'];
const jsFiles = fs.readdirSync(PUBLIC_JS_DIR).filter(file =>
file.endsWith('.js') && !file.startsWith('.') && !SKIP_FILES.includes(file)
);
let obfuscatedCount = 0;
jsFiles.forEach(file => {
const inputPath = path.join(PUBLIC_JS_DIR, file);
const outputPath = path.join(OUTPUT_DIR, file);
try {
console.log(`[BUILD] Obfuscating ${file}...`);
const sourceCode = fs.readFileSync(inputPath, 'utf8');
const obfuscationResult = JavaScriptObfuscator.obfuscate(sourceCode, obfuscationOptions);
fs.writeFileSync(outputPath, obfuscationResult.getObfuscatedCode());
const originalSize = (fs.statSync(inputPath).size / 1024).toFixed(2);
const obfuscatedSize = (fs.statSync(outputPath).size / 1024).toFixed(2);
console.log(`[BUILD] ✓ ${file} (${originalSize}KB → ${obfuscatedSize}KB)`);
obfuscatedCount++;
} catch (error) {
console.error(`[BUILD] ✗ Failed to obfuscate ${file}:`, error.message);
}
});
console.log(`\n[BUILD] Obfuscation complete! ${obfuscatedCount}/${jsFiles.length} files processed.`);
console.log(`[BUILD] Obfuscated files saved to: ${OUTPUT_DIR}`);
console.log('[BUILD] To use obfuscated files in production, set NODE_ENV=production');
+47
View File
@@ -0,0 +1,47 @@
2026-I
week 1 (01.01 — 07.01) <t:1767225600:R> max BR 14.3
week 2 (08.01 — 14.01) <t:1767830400:R> max BR 12.0
week 3 (15.01 — 21.01) <t:1768435200:R> max BR 11.0
week 4 (22.01 — 28.01) <t:1769040000:R> max BR 10.0
week 5 (29.01 — 04.02) <t:1769644800:R> max BR 9.0
week 6 (05.02 — 11.02) <t:1770249600:R> max BR 8.0
week 7 (12.02 — 18.02) <t:1770854400:R> max BR 7.0
week 8 (19.02 — 23.02) <t:1771459200:R> max BR 6.0
until eos (24.02 — 28.02) <t:1771891200:R> max BR 5.0
2026-II
week 1 (01.03 — 08.03) <t:1772348400:R> max BR 14.3
week 2 (09.03 — 15.03) <t:1773039600:R> max BR 12.0
week 3 (16.03 — 22.03) <t:1773644400:R> max BR 10.7
week 4 (23.03 — 29.03) <t:1774249200:R> max BR 9.7
week 5 (30.03 — 05.04) <t:1774854000:R> max BR 8.7
week 6 (06.04 — 12.04) <t:1775458800:R> max BR 7.3
week 7 (13.04 — 19.04) <t:1776063600:R> max BR 6.3
week 8 (20.04 — 26.04) <t:1776668400:R> max BR 5.7
until eos (27.04 — 30.04) <t:1777273200:R> max BR 4.7
2026-III
week 1 (01.05 — 07.05) <t:1777618800:R> max BR 14.3
week 2 (08.05 — 14.05) <t:1778223600:R> max BR 12.0
week 3 (15.05 — 21.05) <t:1778828400:R> max BR 11.0
week 4 (22.05 — 28.05) <t:1779433200:R> max BR 10.0
week 5 (29.05 — 04.06) <t:1780038000:R> max BR 9.0
week 6 (05.06 — 11.06) <t:1780642800:R> max BR 8.0
week 7 (12.06 — 18.06) <t:1781247600:R> max BR 7.0
week 8 (19.06 — 25.06) <t:1781852400:R> max BR 6.0
until eos (26.06 — 30.06) <t:1782457200:R> max BR 5.0
2026-IV
week 1 (01.07 — 07.07) <t:1782889200:R> max BR 14.7
week 2 (08.07 — 14.07) <t:1783494000:R> max BR 12.0
week 3 (15.07 — 21.07) <t:1784098800:R> max BR 10.7
week 4 (22.07 — 28.07) <t:1784703600:R> max BR 9.7
week 5 (29.07 — 04.08) <t:1785308400:R> max BR 8.7
week 6 (05.08 — 11.08) <t:1785913200:R> max BR 7.3
week 7 (12.08 — 18.08) <t:1786518000:R> max BR 6.3
week 8 (19.08 — 25.08) <t:1787122800:R> max BR 5.7
until eos (26.08 — 31.08) <t:1787727600:R> max BR 4.7
+65
View File
@@ -0,0 +1,65 @@
#!/bin/bash
echo "[DEPLOY] Starting deployment process..."
echo "[DEPLOY] Deployment started at: $(date)"
cd "$(dirname "$0")"
echo ""
echo "[DEPLOY] Pulling latest changes from Git..."
git pull origin main
if [ $? -ne 0 ]; then
echo "[ERROR] Git pull failed!"
exit 1
fi
echo ""
echo "[DEPLOY] Checking for package.json changes..."
PACKAGE_CHANGED=$(git diff HEAD@{1} HEAD --name-only | grep -q "package.json" && echo "yes" || echo "no")
if [ "$PACKAGE_CHANGED" = "yes" ]; then
echo "[DEPLOY] package.json has changed, running npm install..."
npm install
if [ $? -ne 0 ]; then
echo "[ERROR] npm install failed!"
exit 1
fi
echo ""
echo "[DEPLOY] Running npm audit fix..."
npm audit fix
else
echo "[DEPLOY] No package.json changes detected, skipping npm install"
fi
echo ""
echo "[DEPLOY] Building CSS with Tailwind..."
npm run build:css
if [ $? -ne 0 ]; then
echo "[ERROR] CSS build failed!"
exit 1
fi
echo ""
echo "[DEPLOY] Building obfuscated JavaScript files..."
npm run build
if [ $? -ne 0 ]; then
echo "[WARN] JS build failed, but continuing deployment..."
fi
echo ""
echo "[DEPLOY] Restarting PM2 process 3..."
pm2 restart 3
if [ $? -ne 0 ]; then
echo "[ERROR] PM2 restart failed!"
exit 1
fi
echo ""
echo "[DEPLOY] Deployment completed successfully!"
echo "[DEPLOY] Deployment finished at: $(date)"
File diff suppressed because it is too large Load Diff
+66
View File
@@ -0,0 +1,66 @@
# ============================================
# Environment Configuration Example
# Copy this file to .env and fill in your actual values
# ============================================
# Server Configuration
NODE_ENV=production
PORT=3001
# External API Configuration (srebot-api)
EXTERNAL_API_URL=http://localhost:6000
# Domain Config (used for CORS)
PRODUCTION_DOMAIN=https://sre.pawjob.us
# Path to .env file (defaults to ./env in this directory)
# DOTENV_PATH=/path/to/.env
# Storage volume (required - holds replays, squadrons, entitlements data)
STORAGE_VOL_PATH=/mnt/HC_Volume_105581488/STORAGE
# Legacy replays directory (optional, for backwards compatibility)
# LEGACY_REPLAYS_ROOT=/path/to/replays
# Python binary for replay rendering and recap generation
# PYTHON_BIN=/path/to/python3
# Path to render_recap.py script (optional, enables recap generation)
# RECAP_SCRIPT=/path/to/BOT/render_recap.py
# Root of the SREBOT repo (used as cwd for Python scripts)
# BOT_REPO_ROOT=/path/to/SREBOT
# SHARED icon and minimap directories
# SHARED_ICONS_DIR=/path/to/SHARED/ICONS
# SHARED_MINIMAPS_DIR=/path/to/SHARED/MAPS/MINIMAPS
# API Security (optional - auto-generates if not set)
# Generate with: openssl rand -hex 32
API_SECRET=
# IP Whitelist (optional - comma-separated IPs for production)
ALLOWED_IPS=
# Webhook Configuration (optional - for GitHub auto-deployment)
# Generate a secure random string for this
WEBHOOK_SECRET=
# ============================================
# PM2 Commands:
# ============================================
# Start with PM2:
# npm run pm2:start
#
# Other PM2 commands:
# npm run pm2:stop - Stop the app
# npm run pm2:restart - Restart the app
# npm run pm2:reload - Zero-downtime reload
# npm run pm2:logs - View logs
# npm run pm2:monit - Monitor dashboard
# npm run pm2:delete - Remove from PM2
#
# Auto-start on reboot:
# pm2 startup
# pm2 save
# ============================================
+1005
View File
File diff suppressed because it is too large Load Diff
+1005
View File
File diff suppressed because it is too large Load Diff
+1008
View File
File diff suppressed because it is too large Load Diff
+1005
View File
File diff suppressed because it is too large Load Diff
+1005
View File
File diff suppressed because it is too large Load Diff
+1005
View File
File diff suppressed because it is too large Load Diff
+1005
View File
File diff suppressed because it is too large Load Diff
+1005
View File
File diff suppressed because it is too large Load Diff
+1005
View File
File diff suppressed because it is too large Load Diff
+1060
View File
File diff suppressed because it is too large Load Diff
+5912
View File
File diff suppressed because it is too large Load Diff
+53
View File
@@ -0,0 +1,53 @@
{
"name": "toothless-sqb-bot-web",
"version": "1.0.0",
"description": "Website for Toothless SQB Discord Bot",
"main": "server.js",
"scripts": {
"start": "cross-env NODE_ENV=production node server.js",
"dev": "npm run build:css && concurrently \"npm run watch:css\" \"cross-env NODE_ENV=development nodemon server.js\"",
"build": "npm run build:css && node build.js",
"build:css": "node build-css.js",
"watch:css": "nodemon --watch public/css/tailwind.css --watch tailwind.config.js --exec \"node build-css.js\"",
"build:prod": "npm run build:css && node build.js && cross-env NODE_ENV=production node server.js",
"pm2:start": "npm run build && pm2 start ecosystem.config.js",
"pm2:stop": "pm2 stop toothless-sqb-web",
"pm2:restart": "pm2 restart toothless-sqb-web",
"pm2:reload": "pm2 reload toothless-sqb-web",
"pm2:delete": "pm2 delete toothless-sqb-web",
"pm2:logs": "pm2 logs toothless-sqb-web",
"pm2:monit": "pm2 monit",
"test": "echo \"No tests specified yet. Please run 'npm run dev' to start the development server.\""
},
"keywords": [
"discord",
"bot",
"website",
"express",
"node"
],
"author": "Sophie :3",
"license": "MIT",
"dependencies": {
"compression": "^1.8.1",
"cors": "^2.8.5",
"dotenv": "^16.3.1",
"ejs": "^3.1.9",
"express": "^4.18.2",
"node-fetch": "^2.7.0",
"sqlite3": "^5.1.7"
},
"devDependencies": {
"autoprefixer": "^10.4.22",
"concurrently": "^9.2.1",
"cross-env": "^10.1.0",
"cssnano": "^7.1.2",
"javascript-obfuscator": "^4.1.0",
"nodemon": "^3.0.1",
"postcss": "^8.5.6",
"tailwindcss": "^3.4.18"
},
"engines": {
"node": ">=16.0.0"
}
}
+7
View File
@@ -0,0 +1,7 @@
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
}
File diff suppressed because it is too large Load Diff
+2451
View File
File diff suppressed because it is too large Load Diff
+325
View File
@@ -0,0 +1,325 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
/* Custom Fonts */
@font-face {
font-family: 'skyquakesymbols';
src: url('/Fonts/symbols_skyquake.ttf');
font-display: block;
}
@layer base {
* {
@apply m-0 p-0 box-border;
}
body {
@apply font-sans leading-relaxed text-white overflow-x-hidden min-h-screen antialiased;
background: linear-gradient(rgba(0, 0, 0, 0.7), rgba(0, 0, 0, 0.7)), url('/images/toothless_face.webp');
background-size: cover;
background-position: center;
background-attachment: fixed;
background-repeat: no-repeat;
-webkit-overflow-scrolling: touch;
}
html {
@apply scroll-smooth;
}
}
@layer components {
/* ========================================
COLOR SCHEME:
- Background: Dark earth green (#1C1E1D) to graphite (#0A0B0A)
- Accent/Primary text: Cream (#F5F5DC)
- Secondary/Muted text: Mint green (#90EE90)
======================================== */
/* Vehicle, Squadron, and Player names use custom font */
.vehicle-name,
.squadron-name,
.squadron-tag,
.player-name,
.player-nick {
@apply font-skyquake;
}
/* Primary Button - Cream gradient */
.btn-primary {
@apply inline-flex items-center justify-center px-6 py-3 font-bold rounded-lg
transition-all duration-300 relative overflow-hidden;
background: linear-gradient(135deg, #F5F5DC 0%, #E8E8D0 100%);
color: #1E1E1E;
box-shadow: 0 4px 20px rgba(245, 245, 220, 0.15), inset 0 1px 0 rgba(255, 255, 255, 0.4);
}
.btn-primary:hover {
box-shadow: 0 8px 25px rgba(245, 245, 220, 0.3), inset 0 1px 0 rgba(255, 255, 255, 0.4);
transform: translateY(-2px);
}
.btn-primary:active {
transform: translateY(0);
}
/* Navigation callouts */
.nav-premium {
color: #f4d35e;
}
.nav-premium:hover {
color: #ffe08a;
}
.nav-rainbow {
color: #ff9b8a;
}
.nav-rainbow:hover {
color: #ffc0b4;
}
.nav-donate {
background: linear-gradient(90deg, #ff7a7a 0%, #ffd166 25%, #90ee90 50%, #8fd3ff 75%, #c79bff 100%);
-webkit-background-clip: text;
background-clip: text;
-webkit-text-fill-color: transparent;
color: transparent;
}
.nav-donate:hover {
filter: brightness(1.12);
}
.nav-donate i {
-webkit-text-fill-color: initial;
color: #ffd166;
}
/* Secondary Button */
.btn-secondary {
@apply inline-flex items-center justify-center px-6 py-3
bg-white/10 text-white font-semibold rounded-lg border-2 border-primary-400/50
transition-all duration-300 backdrop-blur-sm
hover:-translate-y-0.5 hover:bg-primary-400/20 hover:border-primary-400
active:translate-y-0;
}
/* Feature Card - used on homepage */
.feature-card {
background: linear-gradient(135deg, rgba(62, 78, 62, 0.2) 0%, rgba(44, 44, 44, 0.2) 100%);
border: 1px solid rgba(245, 245, 220, 0.08);
backdrop-filter: blur(12px);
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}
.card-hover:hover {
transform: translateY(-3px);
background: linear-gradient(135deg, rgba(62, 78, 62, 0.3) 0%, rgba(44, 44, 44, 0.3) 100%);
border-color: rgba(144, 238, 144, 0.3);
box-shadow: 0 8px 30px rgba(0, 0, 0, 0.3);
}
.card-hover:hover i {
color: #F5F5DC;
transform: scale(1.1);
transition: transform 0.3s ease;
}
/* Search Input - Glass effect */
.search-input-glass {
background: rgba(30, 30, 30, 0.6);
border: 1px solid rgba(245, 245, 220, 0.1);
backdrop-filter: blur(10px);
transition: all 0.3s ease;
box-shadow: inset 0 2px 4px rgba(0,0,0,0.2);
}
.search-input-glass:focus {
background: rgba(40, 40, 40, 0.8);
border-color: rgba(144, 238, 144, 0.4);
box-shadow: 0 0 0 2px rgba(144, 238, 144, 0.1), inset 0 2px 4px rgba(0,0,0,0.2);
}
/* Card Component */
.card {
@apply bg-dark-100/80 backdrop-blur-md rounded-2xl border border-primary-400/20
p-6 transition-all duration-300
hover:border-primary-400/40 hover:shadow-lg hover:shadow-primary-400/10;
}
/* Glass Card (more transparent) */
.glass-card {
@apply bg-dark-100/60 backdrop-blur-xl rounded-2xl border border-primary-400/20
p-6 transition-all duration-300;
}
/* Stat Card */
.stat-card {
@apply card text-center relative overflow-hidden
hover:-translate-y-1 hover:border-primary-400;
}
/* Input Field */
.input-field {
@apply w-full px-4 py-3 bg-dark-200/80 border-2 border-primary-400/30
rounded-lg text-white placeholder-white/50
transition-all duration-300 backdrop-blur-sm
focus:outline-none focus:border-primary-400 focus:shadow-lg focus:shadow-primary-400/20;
}
/* Select Dropdown */
.select-field {
@apply input-field cursor-pointer appearance-none pr-10;
background-image: url('data:image/svg+xml;charset=UTF-8,%3csvg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 24 24%22 fill=%22none%22 stroke=%22%2339ff14%22 stroke-width=%222%22 stroke-linecap=%22round%22 stroke-linejoin=%22round%22%3e%3cpolyline points=%226 9 12 15 18 9%22%3e%3c/polyline%3e%3c/svg%3e');
background-position: right 0.75rem center;
background-size: 1.25rem;
background-repeat: no-repeat;
}
/* Table Styles */
.data-table {
@apply w-full border-collapse bg-dark-200/80 rounded-xl overflow-hidden
shadow-xl border border-primary-400/10;
}
.data-table thead {
@apply bg-gradient-to-r from-primary-400/20 to-primary-500/20;
}
.data-table th {
@apply px-4 py-3 text-white font-semibold text-center border-b-2 border-primary-400/30;
}
.data-table td {
@apply px-4 py-3 text-center text-white/90 border-b border-primary-400/10;
}
.data-table tbody tr {
@apply transition-all duration-300 hover:bg-gradient-to-r hover:from-primary-400/10
hover:to-primary-500/10 hover:scale-[1.01];
}
/* Loading Spinner */
.spinner {
@apply border-4 border-primary-400/30 border-t-primary-400 rounded-full
w-10 h-10 animate-spin mx-auto;
}
/* Badge */
.badge {
@apply inline-flex items-center px-3 py-1 rounded-full text-sm font-semibold
bg-primary-400/10 text-primary-400 border border-primary-400/30;
}
/* Navbar */
.navbar {
@apply fixed top-0 w-full bg-dark-300/95 backdrop-blur-md z-50
border-b border-primary-400/20 transition-all duration-300;
}
/* Container */
.container-custom {
@apply max-w-7xl mx-auto px-4 sm:px-6 lg:px-8;
}
/* Section Title */
.section-title {
@apply text-4xl font-bold text-center mb-4 bg-gradient-to-r from-primary-400 via-primary-500 to-primary-400
bg-clip-text text-transparent bg-[length:200%_auto] animate-gradient-shift;
}
/* Link Hover Effect */
.link-hover {
@apply relative text-white transition-colors duration-300
hover:text-primary-400
after:content-[''] after:absolute after:bottom-0 after:left-0
after:w-0 after:h-0.5 after:bg-gradient-to-r after:from-primary-400 after:to-primary-500
after:transition-all after:duration-300
hover:after:w-full;
}
/* Date Filter Styles */
.date-filter-container {
@apply glass-card space-y-4;
}
.filter-button {
@apply px-4 py-2 rounded-lg border border-primary-400/30 bg-dark-100/50
text-white font-medium transition-all duration-300
hover:border-primary-400 hover:bg-primary-400/10
focus:outline-none focus:ring-2 focus:ring-primary-400/50;
}
.filter-button-active {
@apply filter-button border-primary-400 bg-primary-400/20 text-primary-400 shadow-lg shadow-primary-400/20;
}
}
@layer utilities {
/* Text Gradient */
.text-gradient {
@apply bg-gradient-to-r from-primary-400 via-primary-500 to-primary-600
bg-clip-text text-transparent;
}
/* Glass Effect */
.glass {
@apply bg-white/5 backdrop-blur-md;
}
/* Glow Effect */
.glow {
@apply shadow-lg shadow-primary-400/30;
}
/* Scrollbar Styles */
.custom-scrollbar::-webkit-scrollbar {
@apply w-2 h-2;
}
.custom-scrollbar::-webkit-scrollbar-track {
@apply bg-dark-200 rounded;
}
.custom-scrollbar::-webkit-scrollbar-thumb {
@apply bg-primary-400/30 rounded hover:bg-primary-400/50;
}
}
/* Additional custom animations */
@keyframes gradientShift {
0%, 100% { background-position: 0% 50%; }
50% { background-position: 100% 50%; }
}
@keyframes scrollRight {
0% { transform: translateX(0); }
100% { transform: translateX(-50%); }
}
/* Row link overlay - makes entire table row clickable with native right-click support */
tr.row-link {
position: relative;
cursor: pointer;
}
tr.row-link a.row-link-overlay::before {
content: '';
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 0;
}
tr.row-link td a:not(.row-link-overlay),
tr.row-link td button,
tr.row-link td [tabindex],
tr.row-link td input,
tr.row-link td select,
tr.row-link td textarea {
position: relative;
z-index: 1;
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 258 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 8.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 MiB

Some files were not shown because too many files have changed in this diff Show More