diff --git a/admin.php b/admin.php index df84755..a9a6958 100644 --- a/admin.php +++ b/admin.php @@ -27,7 +27,11 @@ if (!empty($_GET['success'])) { // POST Verwerking VÓÓR header output zodat redirects en opslaan vlekkeloos werken if ($_SERVER['REQUEST_METHOD'] === 'POST') { - $action = $_POST['action'] ?? ''; + if (!verify_csrf_token($_POST['csrf_token'] ?? null)) { + $message = "Beveiligingsfout: CSRF-token ongeldig of verlopen. Ververs de pagina en probeer opnieuw."; + $messageType = 'error'; + } else { + $action = $_POST['action'] ?? ''; // 1. Centrale Azure App Registratie opslaan if ($action === 'save_azure') { @@ -194,7 +198,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') { $currentAi = get_ai_settings(); if ($apiKeyIn !== '' && $apiKeyIn !== '••••••••••••••••') { - set_system_setting('ai_api_key', $apiKeyIn); + set_system_setting('ai_api_key', fociz_encrypt($apiKeyIn)); } set_system_setting('ai_enabled', $aiEnabled); @@ -223,6 +227,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') { } } } + } } // Haal actuele configuratie op @@ -425,6 +430,7 @@ require_once __DIR__ . '/includes/header.php';
+
@@ -563,6 +569,7 @@ require_once __DIR__ . '/includes/header.php'; +
@@ -724,6 +732,7 @@ require_once __DIR__ . '/includes/header.php';
+
@@ -805,6 +814,7 @@ require_once __DIR__ . '/includes/header.php'; +
@@ -881,6 +891,7 @@ require_once __DIR__ . '/includes/header.php'; +
@@ -993,6 +1004,7 @@ require_once __DIR__ . '/includes/header.php'; +
'],h=0;d>h;h++){g.push("");for(var i=0;d>i;i++)g.push('');g.push("")}g.push("
"),c.innerHTML=g.join("");var j=c.childNodes[0],k=(b.width-j.offsetWidth)/2,l=(b.height-j.offsetHeight)/2;k>0&&l>0&&(j.style.margin=l+"px "+k+"px")},a.prototype.clear=function(){this._el.innerHTML=""},a}();QRCode=function(a,b){if(this._htOption={width:256,height:256,typeNumber:4,colorDark:"#000000",colorLight:"#ffffff",correctLevel:d.H},"string"==typeof b&&(b={text:b}),b)for(var c in b)this._htOption[c]=b[c];"string"==typeof a&&(a=document.getElementById(a)),this._android=n(),this._el=a,this._oQRCode=null,this._oDrawing=new q(this._el,this._htOption),this._htOption.text&&this.makeCode(this._htOption.text)},QRCode.prototype.makeCode=function(a){this._oQRCode=new b(r(a,this._htOption.correctLevel),this._htOption.correctLevel),this._oQRCode.addData(a),this._oQRCode.make(),this._el.title=a,this._oDrawing.draw(this._oQRCode),this.makeImage()},QRCode.prototype.makeImage=function(){"function"==typeof this._oDrawing.makeImage&&(!this._android||this._android>=3)&&this._oDrawing.makeImage()},QRCode.prototype.clear=function(){this._oDrawing.clear()},QRCode.CorrectLevel=d}(); \ No newline at end of file diff --git a/auth/forgot-password.php b/auth/forgot-password.php index 8e36f08..1e194ac 100644 --- a/auth/forgot-password.php +++ b/auth/forgot-password.php @@ -18,11 +18,24 @@ $error = null; $submitted = false; if ($_SERVER['REQUEST_METHOD'] === 'POST') { - $email = strtolower(trim($_POST['email'] ?? '')); + $clientIp = fociz_get_client_ip(); + $ipRateKey = "forgot_ip_{$clientIp}"; + $ipLimit = fociz_check_rate_limit($ipRateKey, 5, 900); - if (empty($email) || !filter_var($email, FILTER_VALIDATE_EMAIL)) { + $email = strtolower(trim($_POST['email'] ?? '')); + $emailRateKey = "forgot_email_{$email}"; + $emailLimit = fociz_check_rate_limit($emailRateKey, 3, 900); + + if (!verify_csrf_token($_POST['csrf_token'] ?? null)) { + $error = "Sessie verlopen of beveiligingstoken ongeldig. Ververs de pagina en probeer opnieuw."; + } elseif (!$ipLimit['allowed'] || !$emailLimit['allowed']) { + $retryAfter = max($ipLimit['retry_after'], $emailLimit['retry_after']); + $error = "Te veel herstelaanvragen. Probeer het over " . ceil($retryAfter / 60) . " minuten opnieuw."; + } elseif (empty($email) || !filter_var($email, FILTER_VALIDATE_EMAIL)) { $error = "Vul een geldig e-mailadres in."; } else { + fociz_hit_rate_limit($ipRateKey, 900); + fociz_hit_rate_limit($emailRateKey, 900); try { $db = get_db_connection(); @@ -176,6 +189,7 @@ $pageTitle = "Fociz — Wachtwoord Vergeten";
+
diff --git a/auth/invite.php b/auth/invite.php index e5a33e5..27fddb9 100644 --- a/auth/invite.php +++ b/auth/invite.php @@ -191,7 +191,7 @@ $pageTitle = "Fociz — Uitnodiging Accepteren";

- 2FA QR Code +
@@ -213,8 +213,20 @@ $pageTitle = "Fociz — Uitnodiging Accepteren";
+ diff --git a/auth/login.php b/auth/login.php index 968de55..e27c6fe 100644 --- a/auth/login.php +++ b/auth/login.php @@ -42,13 +42,24 @@ if (isset($_GET['action']) && $_GET['action'] === 'restart') { // POST Verwerking if ($_SERVER['REQUEST_METHOD'] === 'POST') { $db = get_db_connection(); + $clientIp = fociz_get_client_ip(); + $ipRateKey = "login_ip_{$clientIp}"; + $ipLimit = fociz_check_rate_limit($ipRateKey, 10, 900); - // STAP 1: Wachtwoord verificatie - if ($step === 1) { + if (!verify_csrf_token($_POST['csrf_token'] ?? null)) { + $error = "Sessie verlopen of beveiligingstoken ongeldig. Ververs de pagina en probeer opnieuw."; + } elseif (!$ipLimit['allowed']) { + $error = "Te veel inlogpogingen vanaf dit netwerkadres. Probeer het over " . ceil($ipLimit['retry_after'] / 60) . " minuten opnieuw."; + } elseif ($step === 1) { + // STAP 1: Wachtwoord verificatie $email = strtolower(trim($_POST['email'] ?? '')); $password = $_POST['password'] ?? ''; + $emailRateKey = "login_email_{$email}"; + $emailLimit = fociz_check_rate_limit($emailRateKey, 5, 900); - if (empty($email) || empty($password)) { + if (!$emailLimit['allowed']) { + $error = "Dit account is tijdelijk geblokkeerd wegens te veel mislukte pogingen. Probeer het over " . ceil($emailLimit['retry_after'] / 60) . " minuten opnieuw."; + } elseif (empty($email) || empty($password)) { $error = "Vul e-mailadres en wachtwoord in."; } else { $stmt = $db->prepare("SELECT id, name, email, password_hash, two_factor_secret, two_factor_enabled FROM users WHERE email = ?"); @@ -56,8 +67,8 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') { $user = $stmt->fetch(); if ($user && password_verify($password, $user['password_hash'])) { + fociz_clear_rate_limit($emailRateKey); // Controleer of de inloglocatie/IP bekend is - $clientIp = fociz_get_client_ip(); $isIpKnown = fociz_is_ip_known_for_user((int)$user['id'], $clientIp); if (!$isIpKnown) { @@ -72,6 +83,10 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') { $step = 2; } } else { + fociz_hit_rate_limit($ipRateKey, 900); + if (!empty($email)) { + fociz_hit_rate_limit($emailRateKey, 900); + } $error = "Onjuist e-mailadres of wachtwoord."; } } @@ -86,7 +101,12 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') { exit; } - if (empty($totpCode)) { + $totpRateKey = "login_totp_{$userId}"; + $totpLimit = fociz_check_rate_limit($totpRateKey, 5, 900); + + if (!$totpLimit['allowed']) { + $error = "Te veel ongeldige verificatiecodes. Probeer het over " . ceil($totpLimit['retry_after'] / 60) . " minuten opnieuw."; + } elseif (empty($totpCode)) { $error = "Vul de 6-cijferige 2FA code in uit uw authenticator app."; } else { $stmt = $db->prepare("SELECT * FROM users WHERE id = ?"); @@ -120,8 +140,10 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') { } if ($isValid) { + fociz_clear_rate_limit($totpRateKey); + fociz_clear_rate_limit($ipRateKey); + // Volledige login succesvol (geldig tot 23:59:59 van vandaag) - $clientIp = fociz_get_client_ip(); fociz_register_user_ip((int)$user['id'], $clientIp, true); $_SESSION['user_id'] = $user['id']; @@ -142,6 +164,8 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') { header('Location: /planner.php'); exit; } else { + fociz_hit_rate_limit($totpRateKey, 900); + fociz_hit_rate_limit($ipRateKey, 900); $error = "Onjuiste 2FA code. Controleer uw authenticator app en probeer opnieuw."; } } @@ -229,6 +253,7 @@ $pageTitle = "Fociz — Inloggen";
+
@@ -256,6 +281,7 @@ $pageTitle = "Fociz — Inloggen"; +
diff --git a/auth/setup.php b/auth/setup.php index 5170531..e8d01b1 100644 --- a/auth/setup.php +++ b/auth/setup.php @@ -214,7 +214,7 @@ $pageTitle = "Fociz — Eerste Initialisatie";

- 2FA QR Code +
@@ -238,8 +238,20 @@ $pageTitle = "Fociz — Eerste Initialisatie";
+ diff --git a/config/config.php b/config/config.php index 04a051c..509c12c 100644 --- a/config/config.php +++ b/config/config.php @@ -17,6 +17,7 @@ if (!headers_sent()) { header('X-XSS-Protection: 1; mode=block'); header('Referrer-Policy: strict-origin-when-cross-origin'); header('Permissions-Policy: camera=(), microphone=(), geolocation=()'); + header("Content-Security-Policy: default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval' https://cdn.tailwindcss.com https://unpkg.com; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; font-src 'self' data: https://fonts.gstatic.com; img-src 'self' data: https:; connect-src 'self'; frame-ancestors 'self'; base-uri 'self'; form-action 'self'"); if (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') { header('Strict-Transport-Security: max-age=31536000; includeSubDomains'); } @@ -100,7 +101,7 @@ define('DB_HOST', fociz_env('DB_HOST', '192.168.2.33')); define('DB_PORT', fociz_env('DB_PORT', '3306')); define('DB_NAME', fociz_env('DB_NAME', $isDevEnv ? 'focizdev' : 'fociz')); define('DB_USER', fociz_env('DB_USER', $isDevEnv ? 'focizdev_user' : 'focizlive_user')); -define('DB_PASS', fociz_env('DB_PASS', $isDevEnv ? 'F0c1z_Dev_Secure_2026!' : 'F0c1z_Live_Secure_2026!')); +define('DB_PASS', fociz_env('DB_PASS', '')); define('DB_CHARSET', 'utf8mb4'); // Bepaal de dynamische of geconfigureerde applicatie URL @@ -137,7 +138,7 @@ date_default_timezone_set('Europe/Amsterdam'); define('SMTP_HOST', fociz_env('SMTP_HOST', 'mail.fociz.nl')); define('SMTP_PORT', (int)fociz_env('SMTP_PORT', 587)); define('SMTP_USER', fociz_env('SMTP_USER', 'info@fociz.nl')); -define('SMTP_PASS', fociz_env('SMTP_PASS', '6swpQSBg5uWFmjar7F6a')); +define('SMTP_PASS', fociz_env('SMTP_PASS', '')); define('SMTP_SECURE', fociz_env('SMTP_SECURE', 'tls')); // 'tls' (STARTTLS port 587) of 'ssl' (port 465) define('SMTP_FROM_EMAIL', fociz_env('SMTP_FROM_EMAIL', 'info@fociz.nl')); define('SMTP_FROM_NAME', fociz_env('SMTP_FROM_NAME', 'Fociz')); @@ -156,10 +157,7 @@ if (APP_ENV === 'live') { $error = error_get_last(); if ($error && in_array($error['type'], [E_ERROR, E_CORE_ERROR, E_COMPILE_ERROR, E_PARSE])) { error_log("Fociz Fatal Error: [{$error['type']}] {$error['message']} in {$error['file']}:{$error['line']}"); - $showDiagnostics = ( - (isset($_SESSION['user_role']) && $_SESSION['user_role'] === 'ADMIN') || - (isset($_GET['debug']) && $_GET['debug'] === 'fociz') - ); + $showDiagnostics = (isset($_SESSION['user_role']) && $_SESSION['user_role'] === 'ADMIN'); if ($showDiagnostics) { echo "

⚠️ Fociz Systeemfout (Diagnostiek)

diff --git a/includes/.htaccess b/includes/.htaccess new file mode 100644 index 0000000..7b3122a --- /dev/null +++ b/includes/.htaccess @@ -0,0 +1,9 @@ +# Beveiliging van de interne includes map (Pen Test Beveiliging) + + Require all denied + + + Order deny,allow + Deny from all + + diff --git a/includes/auth.php b/includes/auth.php index 7880c79..b630ba5 100644 --- a/includes/auth.php +++ b/includes/auth.php @@ -568,6 +568,55 @@ function verify_csrf_token(?string $token): bool { return !empty($token) && !empty($_SESSION['csrf_token']) && hash_equals($_SESSION['csrf_token'], $token); } +/** + * Rate limiting helpers via app_cache + */ +function fociz_check_rate_limit(string $actionKey, int $maxAttempts = 5, int $decaySeconds = 900): array { + require_once __DIR__ . '/cache.php'; + $cacheKey = 'rl_' . hash('sha256', $actionKey); + $data = fociz_cache_get($cacheKey); + + $attempts = 0; + $expiresAt = time() + $decaySeconds; + if (is_array($data)) { + $attempts = (int)($data['attempts'] ?? 0); + $expiresAt = (int)($data['expires_at'] ?? $expiresAt); + } + + $allowed = ($attempts < $maxAttempts); + $remaining = max(0, $maxAttempts - $attempts); + $retryAfter = max(0, $expiresAt - time()); + + return [ + 'allowed' => $allowed, + 'remaining' => $remaining, + 'retry_after' => $retryAfter, + 'attempts' => $attempts + ]; +} + +function fociz_hit_rate_limit(string $actionKey, int $decaySeconds = 900): void { + require_once __DIR__ . '/cache.php'; + $cacheKey = 'rl_' . hash('sha256', $actionKey); + $data = fociz_cache_get($cacheKey); + + $attempts = 1; + $expiresAt = time() + $decaySeconds; + if (is_array($data)) { + $attempts = (int)($data['attempts'] ?? 0) + 1; + $expiresAt = (int)($data['expires_at'] ?? $expiresAt); + } + + $ttl = max(1, $expiresAt - time()); + fociz_cache_set($cacheKey, ['attempts' => $attempts, 'expires_at' => $expiresAt], $ttl); +} + +function fociz_clear_rate_limit(string $actionKey): void { + require_once __DIR__ . '/cache.php'; + $cacheKey = 'rl_' . hash('sha256', $actionKey); + fociz_cache_delete($cacheKey); +} + /** * Genereert een cryptografisch ondertekende OAuth state parameter waarin de user_id en timestamp veilig zijn opgeslagen. * Dit voorkomt CSRF en maakt sessieherstel mogelijk bij cross-site OAuth redirects. diff --git a/includes/integrations/jira.php b/includes/integrations/jira.php index a71eaba..18f6479 100644 --- a/includes/integrations/jira.php +++ b/includes/integrations/jira.php @@ -9,14 +9,41 @@ require_once __DIR__ . '/../../config/database.php'; require_once __DIR__ . '/../cache.php'; /** - * Normaliseert het Jira domein naar een schone URL zonder slash aan het einde + * Normaliseert het Jira domein naar een schone URL zonder slash aan het einde. + * Bevat SSRF-bescherming: weigert loopbacks, localhost en private netwerkadressen. */ function jira_normalize_domain(string $domain): string { $domain = trim($domain); + if (empty($domain)) { + return ''; + } + if (!preg_match('#^https?://#i', $domain)) { $domain = 'https://' . $domain; } - return rtrim($domain, '/'); + $domain = rtrim($domain, '/'); + + // Parseer en valideer de host ter voorkoming van SSRF (Server-Side Request Forgery) + $parsed = parse_url($domain); + $host = $parsed['host'] ?? ''; + if (empty($host)) { + return ''; + } + + $lowerHost = strtolower($host); + // Weiger localhost en local domains + if ($lowerHost === 'localhost' || str_ends_with($lowerHost, '.localhost') || str_ends_with($lowerHost, '.local') || str_ends_with($lowerHost, '.internal')) { + return ''; + } + + // Weiger private IP-ranges en loopback adressen (bijv. 127.0.0.1, 192.168.x, 10.x, 172.16.x, 169.254.x) + if (filter_var($host, FILTER_VALIDATE_IP)) { + if (!filter_var($host, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE)) { + return ''; + } + } + + return $domain; } /** diff --git a/includes/mailer.php b/includes/mailer.php index b5fd43c..b7a52dc 100644 --- a/includes/mailer.php +++ b/includes/mailer.php @@ -35,15 +35,26 @@ class FocizMailer { $logs[] = date('H:i:s') . ' ' . $msg; }; + // Saniteer e-mailadressen en headers tegen CRLF en SMTP command injection + $toEmail = str_replace(["\r", "\n", "\0"], '', trim($toEmail)); + $toName = str_replace(["\r", "\n", "\0"], '', trim($toName)); + $subject = str_replace(["\r", "\n", "\0"], '', trim($subject)); + + if (!filter_var($toEmail, FILTER_VALIDATE_EMAIL)) { + $err = "Ongeldig e-mailadres voor verzending: {$toEmail}"; + $log("FOUT: {$err}"); + return ['success' => false, 'error' => $err, 'logs' => $logs]; + } + // 1. Configuratieparameters ophalen $host = $options['host'] ?? (defined('SMTP_HOST') ? SMTP_HOST : 'mail.fociz.nl'); $port = (int)($options['port'] ?? (defined('SMTP_PORT') ? SMTP_PORT : 587)); $username = $options['username'] ?? (defined('SMTP_USER') ? SMTP_USER : 'info@fociz.nl'); $password = $options['password'] ?? (defined('SMTP_PASS') ? SMTP_PASS : ''); $secure = strtolower($options['secure'] ?? (defined('SMTP_SECURE') ? SMTP_SECURE : 'tls')); - $fromEmail = $options['from_email'] ?? (defined('SMTP_FROM_EMAIL') ? SMTP_FROM_EMAIL : 'no-reply@fociz.nl'); - $fromName = $options['from_name'] ?? (defined('SMTP_FROM_NAME') ? SMTP_FROM_NAME : 'Fociz'); - $replyTo = $options['reply_to'] ?? (defined('SMTP_REPLY_TO') ? SMTP_REPLY_TO : 'info@fociz.nl'); + $fromEmail = str_replace(["\r", "\n", "\0"], '', trim($options['from_email'] ?? (defined('SMTP_FROM_EMAIL') ? SMTP_FROM_EMAIL : 'no-reply@fociz.nl'))); + $fromName = str_replace(["\r", "\n", "\0"], '', trim($options['from_name'] ?? (defined('SMTP_FROM_NAME') ? SMTP_FROM_NAME : 'Fociz'))); + $replyTo = str_replace(["\r", "\n", "\0"], '', trim($options['reply_to'] ?? (defined('SMTP_REPLY_TO') ? SMTP_REPLY_TO : 'info@fociz.nl'))); $timeout = (int)($options['timeout'] ?? 15); // Technische afzender voor de SMTP envelope (moet doorgaans overeenkomen met het geauthenticeerde account) diff --git a/includes/settings.php b/includes/settings.php index d0f6dd3..cac0ae4 100644 --- a/includes/settings.php +++ b/includes/settings.php @@ -110,7 +110,14 @@ function get_workday_settings(): array { */ function get_ai_settings(): array { $envKey = fociz_env('OPENAI_API_KEY', ''); - $savedKey = get_system_setting('ai_api_key', ''); + $savedKeyRaw = get_system_setting('ai_api_key', ''); + $savedKey = ''; + if (!empty($savedKeyRaw)) { + if (!function_exists('fociz_decrypt')) { + require_once __DIR__ . '/auth.php'; + } + $savedKey = fociz_decrypt($savedKeyRaw); + } $apiKey = !empty($savedKey) ? $savedKey : $envKey; return [ diff --git a/includes/totp.php b/includes/totp.php index e2efeac..03ba639 100644 --- a/includes/totp.php +++ b/includes/totp.php @@ -107,14 +107,12 @@ class TOTP { } /** - * Genereert een QR code afbeelding URL (via QuickChart HTTPS) + * Genereert een veilige client-side QR code (geen externe API calls meer ter bescherming van TOTP secrets) */ public static function getQrCodeUrl(string $otpAuthUrl, int $size = 220): string { - return sprintf( - 'https://quickchart.io/qr?text=%s&size=%d&margin=1&ecLevel=M', - urlencode($otpAuthUrl), - $size - ); + // Externe API (quickchart.io) verwijderd om te voorkomen dat 2FA secrets uitlekken naar derden. + // QR rendering gebeurt nu 100% lokaal via /assets/js/qrcode.min.js in de browser. + return ''; } /** diff --git a/profile.php b/profile.php index 3e3c637..6d9e7af 100644 --- a/profile.php +++ b/profile.php @@ -25,7 +25,11 @@ if (!empty($_GET['success'])) { // POST Verwerking VÓÓR headers zodat redirects en mutaties soepel verlopen if ($_SERVER['REQUEST_METHOD'] === 'POST') { - $action = $_POST['action'] ?? ''; + if (!verify_csrf_token($_POST['csrf_token'] ?? null)) { + $message = "Beveiligingsfout: CSRF-token ongeldig of verlopen. Ververs de pagina en probeer opnieuw."; + $messageType = 'error'; + } else { + $action = $_POST['action'] ?? ''; // 1. Outlook koppelen via OAuth if ($action === 'connect_outlook') { @@ -79,7 +83,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') { $existingSettings = json_decode($existing['settings'] ?? '{}', true) ?: []; if (($jiraToken === '' || $jiraToken === '••••••••••••••••') && !empty($existingSettings['jira_api_token']) && $existingSettings['jira_api_token'] !== '••••••••••••••••') { - $jiraToken = $existingSettings['jira_api_token']; + $jiraToken = fociz_decrypt($existingSettings['jira_api_token']); } $accountName = $existingSettings['account_name'] ?? ''; @@ -104,7 +108,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') { $newSettings = [ 'jira_domain' => $jiraDomain, 'jira_email' => $jiraEmail, - 'jira_api_token' => $jiraToken, + 'jira_api_token' => fociz_encrypt($jiraToken), 'account_name' => $accountName, 'account_id' => $accountId, ]; @@ -265,6 +269,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') { $message = "Uw avatar is teruggezet naar de standaard weergave met uw initialen."; $messageType = 'success'; } + } } // Actuele gegevens ophalen @@ -524,6 +529,7 @@ require_once __DIR__ . '/includes/header.php'; +
-
- -
-
+ +
+ +
+

- Kies een Karakter (24 Flitsende Avatars) + Kies een Karakter (24 Stijlen)

- Klik om direct toe te passen + + +
+ + + + + +
- -
- - - - - -
- -
+
'avatar-01.svg', 'cat' => 'emoji', 'name' => 'Cool Star (Zonnebril)'], - ['file' => 'avatar-02.svg', 'cat' => 'emoji', 'name' => 'Party Champion'], - ['file' => 'avatar-03.svg', 'cat' => 'emoji', 'name' => 'Celebration Vibe'], - ['file' => 'avatar-04.svg', 'cat' => 'emoji', 'name' => 'Laser Eyes Energy'], - ['file' => 'avatar-05.svg', 'cat' => 'emoji', 'name' => 'Sunny Smiles'], - ['file' => 'avatar-06.svg', 'cat' => 'emoji', 'name' => 'Fire Heart'], - ['file' => 'avatar-07.svg', 'cat' => 'bots', 'name' => 'Cyber Tron'], - ['file' => 'avatar-08.svg', 'cat' => 'bots', 'name' => 'Aero Bot'], - ['file' => 'avatar-09.svg', 'cat' => 'bots', 'name' => 'Neon Volt'], - ['file' => 'avatar-10.svg', 'cat' => 'bots', 'name' => 'Pixel Pulse'], - ['file' => 'avatar-11.svg', 'cat' => 'bots', 'name' => 'Quantum AI'], - ['file' => 'avatar-12.svg', 'cat' => 'bots', 'name' => 'Robo Racer'], - ['file' => 'avatar-13.svg', 'cat' => 'personas', 'name' => 'Alex (Hipster)'], - ['file' => 'avatar-14.svg', 'cat' => 'personas', 'name' => 'Maya (Tech Lead)'], - ['file' => 'avatar-15.svg', 'cat' => 'personas', 'name' => 'Lucas (Designer)'], - ['file' => 'avatar-16.svg', 'cat' => 'personas', 'name' => 'Elena (Developer)'], - ['file' => 'avatar-17.svg', 'cat' => 'personas', 'name' => 'Sam (Creative)'], - ['file' => 'avatar-18.svg', 'cat' => 'personas', 'name' => 'Zoe (Founder)'], - ['file' => 'avatar-19.svg', 'cat' => 'heroes', 'name' => 'Jack (Adventurer)'], - ['file' => 'avatar-20.svg', 'cat' => 'heroes', 'name' => 'Luna (Heroine)'], - ['file' => 'avatar-21.svg', 'cat' => 'heroes', 'name' => 'Oliver (Explorer)'], - ['file' => 'avatar-22.svg', 'cat' => 'heroes', 'name' => 'Mia (Rebel)'], + ['file' => 'avatar-01.svg', 'cat' => 'pro', 'name' => 'Alex (Business)'], + ['file' => 'avatar-02.svg', 'cat' => 'pro', 'name' => 'Sophie (Corporate)'], + ['file' => 'avatar-03.svg', 'cat' => 'pro', 'name' => 'Liam (Consultant)'], + ['file' => 'avatar-04.svg', 'cat' => 'pro', 'name' => 'Emma (Manager)'], + ['file' => 'avatar-05.svg', 'cat' => 'pro', 'name' => 'Daan (Director)'], + ['file' => 'avatar-06.svg', 'cat' => 'pro', 'name' => 'Noa (Executive)'], + ['file' => 'avatar-07.svg', 'cat' => 'creative', 'name' => 'Mila (Designer)'], + ['file' => 'avatar-08.svg', 'cat' => 'creative', 'name' => 'Lucas (Creator)'], + ['file' => 'avatar-09.svg', 'cat' => 'creative', 'name' => 'Zara (Artist)'], + ['file' => 'avatar-10.svg', 'cat' => 'creative', 'name' => 'Finn (Illustrator)'], + ['file' => 'avatar-11.svg', 'cat' => 'creative', 'name' => 'Lotte (Writer)'], + ['file' => 'avatar-12.svg', 'cat' => 'creative', 'name' => 'Sem (Architect)'], + ['file' => 'avatar-13.svg', 'cat' => 'tech', 'name' => 'Bram (Developer)'], + ['file' => 'avatar-14.svg', 'cat' => 'tech', 'name' => 'Julia (Engineer)'], + ['file' => 'avatar-15.svg', 'cat' => 'tech', 'name' => 'Jesse (DevOps)'], + ['file' => 'avatar-16.svg', 'cat' => 'tech', 'name' => 'Tess (Data Scientist)'], + ['file' => 'avatar-17.svg', 'cat' => 'tech', 'name' => 'Milan (Cyber Sec)'], + ['file' => 'avatar-18.svg', 'cat' => 'tech', 'name' => 'Liv (AI Engineer)'], + ['file' => 'avatar-19.svg', 'cat' => 'heroes', 'name' => 'Max (Ninja)'], + ['file' => 'avatar-20.svg', 'cat' => 'heroes', 'name' => 'Sara (Astronaut)'], + ['file' => 'avatar-21.svg', 'cat' => 'heroes', 'name' => 'Robin (Pilot)'], + ['file' => 'avatar-22.svg', 'cat' => 'heroes', 'name' => 'Eva (Explorer)'], ['file' => 'avatar-23.svg', 'cat' => 'heroes', 'name' => 'Leo (Cosmic)'], ['file' => 'avatar-24.svg', 'cat' => 'heroes', 'name' => 'Nova (Cyberpunk)'], ]; @@ -597,6 +593,7 @@ require_once __DIR__ . '/includes/header.php'; ?> + @@ -703,6 +702,7 @@ require_once __DIR__ . '/includes/header.php'; +