719 lines
30 KiB
PHP
719 lines
30 KiB
PHP
<?php
|
||
declare(strict_types=1);
|
||
|
||
require __DIR__ . '/includes/bootstrap.php';
|
||
|
||
// unify CSRF token for forms
|
||
$csrfToken = csrf_token();
|
||
$_SESSION['csrf'] = $csrfToken;
|
||
$_SESSION['_csrf'] = $csrfToken;
|
||
|
||
$stack = [];
|
||
$frameworks = [];
|
||
$languageLabels = [];
|
||
$projects = [];
|
||
$projectTags = [];
|
||
|
||
function project_excerpt(string $text, int $max = 160): string {
|
||
$text = trim($text);
|
||
if ($text === '') return '';
|
||
if (function_exists('mb_strlen') && function_exists('mb_substr')) {
|
||
if (mb_strlen($text) <= $max) return $text;
|
||
return rtrim(mb_substr($text, 0, $max - 1)) . '…';
|
||
}
|
||
if (strlen($text) <= $max) return $text;
|
||
return rtrim(substr($text, 0, $max - 1)) . '…';
|
||
}
|
||
|
||
try {
|
||
if (function_exists('pdo')) {
|
||
// skills table -> homepage stack
|
||
$skillLinks = [];
|
||
try {
|
||
$skillLinks = pdo()->query("SELECT framework_id, language_id FROM skill_links")->fetchAll(PDO::FETCH_ASSOC);
|
||
} catch (Throwable $e) {}
|
||
|
||
$rows = pdo()->query("SELECT id, label, level, icon, category, parent_id FROM skills ORDER BY sort_order ASC, id ASC")->fetchAll();
|
||
if ($rows && is_array($rows)) {
|
||
$languages = [];
|
||
$frameworks = [];
|
||
$frameworkLanguageMap = [];
|
||
foreach ($skillLinks as $lnk) {
|
||
$fw = (int)($lnk['framework_id'] ?? 0);
|
||
$lang = (int)($lnk['language_id'] ?? 0);
|
||
if ($fw > 0 && $lang > 0) {
|
||
if (!isset($frameworkLanguageMap[$fw])) $frameworkLanguageMap[$fw] = [];
|
||
$frameworkLanguageMap[$fw][$lang] = $lang;
|
||
}
|
||
}
|
||
|
||
foreach ($rows as $r) {
|
||
$cat = strtolower((string)($r['category'] ?? 'language'));
|
||
$item = [
|
||
'id' => (int)($r['id'] ?? 0),
|
||
'label' => (string)($r['label'] ?? ''),
|
||
'level' => (int)($r['level'] ?? 0),
|
||
'icon' => (string)($r['icon'] ?? 'ri-code-s-slash-line'),
|
||
'parent_id' => (int)($r['parent_id'] ?? 0),
|
||
];
|
||
|
||
if ($cat === 'framework') {
|
||
$frameworks[(int)$item['id']] = $item;
|
||
} else {
|
||
$item['frameworks'] = [];
|
||
$languageLabels[(int)$item['id']] = $item['label'];
|
||
$languages[(int)$item['id']] = $item;
|
||
}
|
||
}
|
||
|
||
// attach frameworks to languages via pivot map (fallback to legacy parent_id)
|
||
foreach ($frameworks as $fwId => $fw) {
|
||
$langIds = array_values($frameworkLanguageMap[$fwId] ?? []);
|
||
if (!$langIds && $fw['parent_id'] > 0) $langIds = [$fw['parent_id']];
|
||
|
||
$frameworks[$fwId]['language_ids'] = $langIds;
|
||
$frameworks[$fwId]['language_labels'] = [];
|
||
foreach ($langIds as $lid) {
|
||
if (isset($languageLabels[$lid])) {
|
||
$frameworks[$fwId]['language_labels'][] = $languageLabels[$lid];
|
||
$fwItem = $fw;
|
||
$fwItem['parent_id'] = $lid;
|
||
$languages[$lid]['frameworks'][] = $fwItem;
|
||
}
|
||
}
|
||
}
|
||
|
||
if ($languages) {
|
||
foreach ($languages as $id => $lang) {
|
||
$lang['frameworks'] = array_values($lang['frameworks'] ?? []);
|
||
$stack[] = $lang;
|
||
}
|
||
}
|
||
$frameworks = array_values($frameworks);
|
||
}
|
||
|
||
// projects table -> homepage projects
|
||
$rows = [];
|
||
try {
|
||
$rows = pdo()->query("SELECT id, slug, title, tag, year, summary, short_summary, tech_json, links_json FROM projects ORDER BY sort_order ASC, id DESC")->fetchAll();
|
||
} catch (Throwable $e) {
|
||
$rows = pdo()->query("SELECT id, slug, title, tag, year, summary, tech_json, links_json FROM projects ORDER BY sort_order ASC, id DESC")->fetchAll();
|
||
if (is_array($rows)) {
|
||
foreach ($rows as &$r) { $r['short_summary'] = null; }
|
||
unset($r);
|
||
}
|
||
}
|
||
if ($rows && is_array($rows)) {
|
||
$tmp = [];
|
||
$tagSet = [];
|
||
foreach ($rows as $r) {
|
||
$tech = [];
|
||
$links = [];
|
||
$slugVal = project_slugify((string)($r['slug'] ?? ''));
|
||
if ($slugVal === '') {
|
||
$slugVal = project_slugify((string)($r['title'] ?? '')) ?: ('p' . (int)$r['id']);
|
||
}
|
||
|
||
$techJson = (string)($r['tech_json'] ?? '');
|
||
$linksJson = (string)($r['links_json'] ?? '');
|
||
|
||
if ($techJson !== '') {
|
||
$decoded = json_decode($techJson, true);
|
||
if (is_array($decoded)) $tech = $decoded;
|
||
}
|
||
if ($linksJson !== '') {
|
||
$decoded = json_decode($linksJson, true);
|
||
if (is_array($decoded)) $links = $decoded;
|
||
}
|
||
|
||
$projectForMedia = [
|
||
'id' => (int)($r['id'] ?? 0),
|
||
'slug' => $slugVal,
|
||
'title' => (string)($r['title'] ?? ''),
|
||
];
|
||
|
||
$shortCard = trim((string)($r['short_summary'] ?? ''));
|
||
if ($shortCard === '') {
|
||
$shortCard = project_excerpt((string)($r['summary'] ?? ''), 180);
|
||
}
|
||
|
||
$tmp[] = [
|
||
'id' => (string)$slugVal,
|
||
'title' => (string)($r['title'] ?? ''),
|
||
'tag' => (string)($r['tag'] ?? ''),
|
||
'year' => (string)($r['year'] ?? ''),
|
||
'summary' => (string)($r['summary'] ?? ''),
|
||
'summary_short' => $shortCard,
|
||
'tech' => is_array($tech) ? $tech : [],
|
||
'links' => is_array($links) ? $links : [],
|
||
'images' => project_media_files($projectForMedia),
|
||
];
|
||
|
||
$tag = trim((string)($r['tag'] ?? ''));
|
||
if ($tag !== '') $tagSet[$tag] = true;
|
||
}
|
||
if ($tmp) $projects = $tmp;
|
||
if ($tagSet) $projectTags = array_keys($tagSet);
|
||
}
|
||
}
|
||
} catch (Throwable $e) {
|
||
// keep fallback arrays
|
||
}
|
||
|
||
$profile = [
|
||
'name' => 'Georgi Mushatov',
|
||
'role' => 'Full Stack Developer & Software Engineer',
|
||
'location' => 'Varna,BG',
|
||
'github' => 'https://github.com/AJOffishal',
|
||
'instagram' => 'https://instagram.com/1_3_aj_official_3_7/',
|
||
'tiktok' => 'https://tiktok.com/@ajbtgd',
|
||
];
|
||
|
||
$projectsJson = json_encode($projects, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
|
||
$initial = function_exists('mb_substr') ? mb_substr($profile['name'], 0, 1) : substr($profile['name'], 0, 1);
|
||
$pfpUrl = url_path('/img/' . rawurlencode('ajpfp.png'));
|
||
?>
|
||
<!DOCTYPE html>
|
||
<html lang="en">
|
||
<head>
|
||
<meta charset="utf-8" />
|
||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||
<title><?= htmlspecialchars($profile['name']) ?> • Portfolio</title>
|
||
|
||
|
||
<link rel="icon" href="/favicon.ico?v=3" sizes="any">
|
||
|
||
<link rel="apple-touch-icon" href="<?= htmlspecialchars($pfpUrl) ?>">
|
||
|
||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
|
||
<script src="https://cdn.tailwindcss.com"></script>
|
||
<script>
|
||
tailwind.config = {
|
||
theme: {
|
||
extend: {
|
||
colors: {
|
||
ink: "#07060a",
|
||
night: "#0b0a12",
|
||
violet: "#8b5cf6",
|
||
magenta: "#d946ef",
|
||
neon: "#a78bfa"
|
||
}
|
||
}
|
||
}
|
||
}
|
||
</script>
|
||
|
||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.0/css/all.min.css">
|
||
<link href="https://cdn.jsdelivr.net/npm/remixicon@4.3.0/fonts/remixicon.css" rel="stylesheet">
|
||
|
||
<link rel="stylesheet" href="<?= htmlspecialchars(url_path('/public/css/app.css')) ?>" />
|
||
|
||
<!-- minimal CSS for active-section animation -->
|
||
<style>
|
||
.navlink { position: relative; border-radius: 999px; padding: .55rem .85rem; transition: transform .18s ease, background .18s ease, box-shadow .18s ease; }
|
||
.navlink:hover { transform: translateY(-1px); }
|
||
.navlink.is-active {
|
||
background: rgba(139, 92, 246, .18);
|
||
box-shadow: 0 0 0 1px rgba(167, 139, 250, .25) inset, 0 10px 30px rgba(139, 92, 246, .18);
|
||
}
|
||
.navlink.is-active::after{
|
||
content:"";
|
||
position:absolute;
|
||
left:12px; right:12px;
|
||
bottom:6px;
|
||
height:2px;
|
||
border-radius:999px;
|
||
background: linear-gradient(90deg, rgba(139,92,246,.0), rgba(139,92,246,.9), rgba(217,70,239,.9), rgba(139,92,246,.0));
|
||
filter: drop-shadow(0 0 8px rgba(167,139,250,.4));
|
||
animation: glowline .9s ease;
|
||
}
|
||
@keyframes glowline { from { transform: scaleX(.2); opacity: .2;} to { transform: scaleX(1); opacity:1; } }
|
||
</style>
|
||
</head>
|
||
|
||
<body class="bg-ink text-white selection:bg-violet/40">
|
||
<div class="bg-aurora" aria-hidden="true"></div>
|
||
<div class="bg-grid" aria-hidden="true"></div>
|
||
|
||
<header class="fixed top-0 left-0 right-0 z-50">
|
||
<div class="mx-auto max-w-6xl px-4 py-3">
|
||
<div class="topbar-glass flex items-center justify-between gap-3 rounded-2xl px-4 py-3">
|
||
<a href="#home" class="flex items-center gap-3 no-underline" data-scroll>
|
||
<span class="brand-badge"><?= htmlspecialchars($initial) ?></span>
|
||
<span class="font-semibold tracking-tight"><?= htmlspecialchars($profile['name']) ?></span>
|
||
</a>
|
||
|
||
<nav class="hidden md:flex items-center gap-1" aria-label="Primary">
|
||
<a class="navlink" href="#about" data-scroll><i class="ri-user-3-fill"></i><span>About</span></a>
|
||
<a class="navlink" href="#projects" data-scroll><i class="ri-grid-fill"></i><span>Projects</span></a>
|
||
<a class="navlink" href="#stack" data-scroll><i class="ri-stack-fill"></i><span>Stack</span></a>
|
||
<a class="navlink" href="#gaming" data-scroll><i class="ri-gamepad-fill"></i><span>Gaming</span></a>
|
||
<a class="navlink" href="#contact" data-scroll><i class="ri-mail-fill"></i><span>Contact</span></a>
|
||
</nav>
|
||
|
||
<div class="hidden md:flex items-center gap-2">
|
||
<a href="<?= htmlspecialchars(url_path('/public/admin/login.php')) ?>" class="btn btn-outline-light btn-sm">
|
||
<i class="ri-login-circle-line me-1"></i>Login
|
||
</a>
|
||
<a href="<?= htmlspecialchars(url_path('/public/admin/signup.php')) ?>" class="btn btn-light btn-sm">
|
||
<i class="ri-user-add-line me-1"></i>Create
|
||
</a>
|
||
</div>
|
||
|
||
<button class="md:hidden btn btn-sm btn-outline-light" type="button"
|
||
data-bs-toggle="offcanvas" data-bs-target="#mobileNav" aria-controls="mobileNav">
|
||
<i class="fa-solid fa-bars"></i>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</header>
|
||
|
||
<div class="offcanvas offcanvas-end text-bg-dark" tabindex="-1" id="mobileNav" aria-labelledby="mobileNavLabel">
|
||
<div class="offcanvas-header">
|
||
<h5 class="offcanvas-title" id="mobileNavLabel">Menu</h5>
|
||
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="offcanvas"></button>
|
||
</div>
|
||
<div class="offcanvas-body">
|
||
<a class="navlink block mb-2" href="#about" data-scroll><i class="ri-user-3-fill"></i><span>About</span></a>
|
||
<a class="navlink block mb-2" href="#projects" data-scroll><i class="ri-grid-fill"></i><span>Projects</span></a>
|
||
<a class="navlink block mb-2" href="#stack" data-scroll><i class="ri-stack-fill"></i><span>Stack</span></a>
|
||
<a class="navlink block mb-2" href="#gaming" data-scroll><i class="ri-gamepad-fill"></i><span>Gaming</span></a>
|
||
<a class="navlink block mb-2" href="#contact" data-scroll><i class="ri-mail-fill"></i><span>Contact</span></a>
|
||
|
||
<hr class="border-white/10 my-3">
|
||
|
||
<a href="<?= htmlspecialchars(url_path('/public/admin/login.php')) ?>" class="btn btn-outline-light w-100 mb-2">
|
||
<i class="ri-login-circle-line me-1"></i>Login
|
||
</a>
|
||
<a href="<?= htmlspecialchars(url_path('/public/admin/signup.php')) ?>" class="btn btn-light w-100">
|
||
<i class="ri-user-add-line me-1"></i>Create Account
|
||
</a>
|
||
</div>
|
||
</div>
|
||
|
||
<main id="home" class="pt-28">
|
||
|
||
<!-- HERO -->
|
||
<section class="mx-auto max-w-6xl px-4 py-10" id="hero">
|
||
|
||
<!-- CENTERED HERO (desktop + mobile) -->
|
||
<div class="mx-auto max-w-3xl text-center space-y-5">
|
||
<div class="flex flex-col items-center gap-3">
|
||
<div class="pfp-wrap">
|
||
<div class="pfp-ring"></div>
|
||
<img
|
||
class="pfp-img"
|
||
src="<?= htmlspecialchars($pfpUrl) ?>"
|
||
alt="Profile picture"
|
||
loading="eager"
|
||
decoding="async"
|
||
>
|
||
</div>
|
||
|
||
<div class="pill inline-flex items-center gap-2">
|
||
<span class="dot"></span> Not Avalaibe
|
||
</div>
|
||
</div>
|
||
|
||
<h1 class="text-center font-extrabold tracking-tight">
|
||
<?php
|
||
$name = trim((string)($profile['name'] ?? ''));
|
||
$parts = preg_split('/\s+/', $name, 2);
|
||
$first = $parts[0] ?? '';
|
||
$last = $parts[1] ?? '';
|
||
?>
|
||
|
||
<span class="block leading-none text-5xl sm:text-6xl md:text-7xl whitespace-nowrap">
|
||
<span class="text-white/90 drop-shadow-md"><?= htmlspecialchars($first) ?></span>
|
||
<span class="hero-gradient drop-shadow-md"> <?= $last !== '' ? ' ' . htmlspecialchars($last) : '' ?></span>
|
||
</span>
|
||
|
||
|
||
<span class="block mt-4 text-white/85 font-black leading-tight drop-shadow-md text-2xl sm:text-3xl md:text-4xl">
|
||
<?= htmlspecialchars($profile['role']) ?>
|
||
</span>
|
||
</h1>
|
||
|
||
|
||
|
||
<p class="text-white/70 text-lg">
|
||
<?= htmlspecialchars($profile['location']) ?>.
|
||
</p>
|
||
|
||
<!-- Spotify widget -->
|
||
<div id="spotifyCard" class="card-glass p-4 rounded-2xl mx-auto" style="max-width: 560px;">
|
||
<div class="flex items-center justify-between">
|
||
<div class="flex items-center justify-center gap-2 text-sm text-white/70 w-100">
|
||
<i class="fa-brands fa-spotify text-green-400"></i>
|
||
<span id="spStatus">Loading Spotify…</span>
|
||
</div>
|
||
<div class="pulse-bars" aria-hidden="true">
|
||
<span></span><span></span><span></span><span></span>
|
||
</div>
|
||
</div>
|
||
|
||
<a id="spLink" href="#" target="_blank" class="mt-3 flex gap-3 items-center justify-center text-center no-underline">
|
||
<img id="spArt" class="w-14 h-14 rounded-xl object-cover bg-white/5" alt="Album art" />
|
||
<div class="min-w-0 text-center">
|
||
<div id="spTitle" class="font-semibold truncate">—</div>
|
||
<div id="spArtist" class="text-white/60 truncate text-sm">—</div>
|
||
</div>
|
||
</a>
|
||
|
||
</div>
|
||
|
||
<div class="flex gap-3 flex-wrap justify-center">
|
||
<a href="#projects" class="btn btn-light btn-lg" data-scroll>
|
||
<i class="ri-rocket-2-fill me-2"></i>Explore Projects
|
||
</a>
|
||
<a href="#contact" class="btn btn-outline-light btn-lg" data-scroll>
|
||
<i class="ri-chat-3-fill me-2"></i>Contact
|
||
</a>
|
||
</div>
|
||
|
||
<div class="flex gap-3 text-white/70 justify-center">
|
||
<a class="iconlink" href="<?= htmlspecialchars($profile['github']) ?>" target="_blank" rel="noreferrer">
|
||
<i class="fa-brands fa-github"></i>
|
||
</a>
|
||
<a class="iconlink" href="<?= htmlspecialchars($profile['instagram']) ?>" target="_blank" rel="noreferrer">
|
||
<i class="fa-brands fa-instagram"></i>
|
||
</a>
|
||
<a class="iconlink" href="<?= htmlspecialchars($profile['tiktok']) ?>" target="_blank" rel="noreferrer">
|
||
<i class="fa-brands fa-tiktok"></i>
|
||
</a>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- HERO CARDS -->
|
||
<div class="mt-10 grid gap-4 lg:grid-cols-3 mx-auto" style="max-width: 980px;">
|
||
<div class="card-glass rounded-2xl p-5">
|
||
<div class="flex items-center justify-between">
|
||
<div class="text-white/70 text-sm">What I like building</div>
|
||
<i class="ri-sparkling-2-fill text-violet text-xl"></i>
|
||
</div>
|
||
<ul class="mt-3 space-y-2 text-white/80">
|
||
<li><i class="ri-check-line text-neon me-2"></i>Video Games & Websites.</li>
|
||
<li><i class="ri-check-line text-neon me-2"></i>Discord Bots using discord.js.</li>
|
||
<li><i class="ri-check-line text-neon me-2"></i>Tools & launchers.</li>
|
||
</ul>
|
||
</div>
|
||
|
||
<div class="card-glass rounded-2xl p-5">
|
||
<div class="text-white/60 text-sm">Focus</div>
|
||
<div class="text-2xl font-bold mt-1">Full Stack</div>
|
||
<div class="text-white/60 text-sm mt-2">Lua • PHP • AJAX • jQuery • MariaDB • C# • C++</div>
|
||
<div class="mt-4 text-white/70 text-sm">
|
||
Build fast → harden security → ship clean UI.
|
||
</div>
|
||
</div>
|
||
|
||
<div class="card-glass rounded-2xl p-5">
|
||
<div class="text-white/60 text-sm">Workflow</div>
|
||
<div class="text-2xl font-bold mt-1">Git + Gitea</div>
|
||
<div class="text-white/60 text-sm mt-2">Clean commits, tags</div>
|
||
<div class="mt-4 text-white/70 text-sm">
|
||
Stable releases + readable history.
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
</section>
|
||
|
||
|
||
<!-- ABOUT ME -->
|
||
<section id="about" class="mx-auto max-w-6xl px-4 py-14">
|
||
<div class="flex items-end justify-between gap-3 flex-wrap">
|
||
<div>
|
||
<h2 class="text-3xl font-bold">About Me</h2>
|
||
</div>
|
||
<div class="text-white/60 text-sm">
|
||
<i class="ri-map-pin-2-fill text-neon"></i> <?= htmlspecialchars($profile['location']) ?>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="mt-6 grid gap-4 lg:grid-cols-3">
|
||
<div class="card-glass rounded-2xl p-5 lg:col-span-2">
|
||
<div class="text-white/80 leading-relaxed space-y-3">
|
||
<p>
|
||
I’m Georgi Mushatov, a 15-year-old high schooler from Varna, Bulgaria.
|
||
I’m into video games and building websites (front-end + back-end). I like making
|
||
community sites, dashboards and tools that connect real data using APIs.
|
||
My focus is clean UI and solid functionality - making things work smoothly end-to-end.
|
||
</p>
|
||
</div>
|
||
|
||
<div class="mt-4 flex flex-wrap gap-2">
|
||
<span class="tag">Video Games</span>
|
||
<span class="tag">APIs</span>
|
||
<span class="tag">Automation</span>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="card-glass rounded-2xl p-5">
|
||
<div class="font-semibold mb-2">Quick facts</div>
|
||
<ul class="space-y-2 text-white/70">
|
||
<li><i class="ri-flashlight-fill text-neon me-2"></i>Fast prototyping → clean refactor</li>
|
||
<li><i class="ri-shield-check-fill text-neon me-2"></i>Secure auth + CSRF patterns</li>
|
||
<li><i class="ri-code-box-fill text-neon me-2"></i>Git workflow daily</li>
|
||
</ul>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- PROJECTS -->
|
||
<section id="projects" class="mx-auto max-w-6xl px-4 py-14">
|
||
<div class="flex items-end justify-between gap-3 flex-wrap">
|
||
<div>
|
||
<h2 class="text-3xl font-bold">Project Library</h2>
|
||
<p class="text-white/60">Click a card for details.</p>
|
||
</div>
|
||
<div class="flex gap-2">
|
||
<button class="chip active" data-filter="all">All</button>
|
||
<?php foreach ($projectTags as $tag): ?>
|
||
<button class="chip" data-filter="<?= htmlspecialchars(strtolower((string)$tag)) ?>">
|
||
<?= htmlspecialchars((string)$tag) ?>
|
||
</button>
|
||
<?php endforeach; ?>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="mt-6 grid gap-4 md:grid-cols-2 lg:grid-cols-3 project-grid">
|
||
<?php foreach ($projects as $p): ?>
|
||
<button type="button"
|
||
class="project-card card-glass rounded-2xl p-5 text-start"
|
||
data-project-id="<?= htmlspecialchars((string)$p['id']) ?>"
|
||
data-project-tag="<?= htmlspecialchars(strtolower((string)$p['tag'])) ?>">
|
||
<div class="flex items-center justify-between">
|
||
<div class="text-xs text-white/60"><?= htmlspecialchars((string)$p['year']) ?></div>
|
||
<span class="badge rounded-pill text-bg-light"><?= htmlspecialchars((string)$p['tag']) ?></span>
|
||
</div>
|
||
<div class="mt-3 text-xl font-bold"><?= htmlspecialchars((string)$p['title']) ?></div>
|
||
<?php $cardSummary = (string)($p['summary_short'] ?? $p['summary'] ?? ''); ?>
|
||
<div class="mt-2 text-white/70 summary-clamp"><?= htmlspecialchars($cardSummary) ?></div>
|
||
|
||
<div class="mt-4 flex flex-wrap gap-2 tech-row">
|
||
<?php foreach (($p['tech'] ?? []) as $t): ?>
|
||
<span class="tag"><?= htmlspecialchars((string)$t) ?></span>
|
||
<?php endforeach; ?>
|
||
</div>
|
||
|
||
<div class="mt-5 text-white/70 flex items-center gap-2 project-footer">
|
||
<i class="ri-information-fill"></i><span>View details</span>
|
||
</div>
|
||
</button>
|
||
<?php endforeach; ?>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- STACK -->
|
||
<section id="stack" class="mx-auto max-w-6xl px-4 py-14">
|
||
<h2 class="text-3xl font-bold">Tech Stack</h2>
|
||
|
||
<div class="mt-6 grid gap-4 lg:grid-cols-2">
|
||
<?php foreach ($stack as $lang): ?>
|
||
<div class="card-glass rounded-2xl p-5">
|
||
<div class="flex items-center justify-between">
|
||
<div class="flex items-center gap-3">
|
||
<i class="<?= htmlspecialchars((string)$lang['icon']) ?> text-2xl text-neon"></i>
|
||
<div class="font-semibold"><?= htmlspecialchars((string)$lang['label']) ?></div>
|
||
</div>
|
||
<div class="text-white/60 text-sm"><?= (int)$lang['level'] ?>%</div>
|
||
</div>
|
||
<div class="bar mt-4" data-level="<?= (int)$lang['level'] ?>">
|
||
<div class="bar-fill" style="width:0%"></div>
|
||
</div>
|
||
|
||
<?php if (!empty($lang['frameworks'])): ?>
|
||
<div class="mt-4 text-white/60 text-sm">Frameworks / Librarys / Game Engines</div>
|
||
<div class="mt-2 flex flex-wrap gap-2">
|
||
<?php foreach ($lang['frameworks'] as $fw): ?>
|
||
<span class="tag">
|
||
<i class="<?= htmlspecialchars((string)$fw['icon']) ?> me-1"></i>
|
||
<?= htmlspecialchars((string)$fw['label']) ?>
|
||
</span>
|
||
<?php endforeach; ?>
|
||
</div>
|
||
<?php else: ?>
|
||
<div class="mt-3 text-white/50 text-sm">No frameworks added yet.</div>
|
||
<?php endif; ?>
|
||
</div>
|
||
<?php endforeach; ?>
|
||
|
||
<?php if (!$stack): ?>
|
||
<div class="text-white/60">No skills added yet.</div>
|
||
<?php endif; ?>
|
||
</div>
|
||
|
||
<div class="mt-10">
|
||
<h3 class="text-2xl font-bold">Frameworks / Librarys / Game Engines</h3>
|
||
|
||
<div class="mt-4 grid gap-4 lg:grid-cols-2">
|
||
<?php foreach ($frameworks as $fw): ?>
|
||
<div class="card-glass rounded-2xl p-5">
|
||
<div class="flex items-center justify-between">
|
||
<div class="flex items-center gap-3">
|
||
<i class="<?= htmlspecialchars((string)$fw['icon'] ?? 'ri-code-s-slash-line') ?> text-2xl text-neon"></i>
|
||
<div>
|
||
<div class="font-semibold"><?= htmlspecialchars((string)$fw['label']) ?></div>
|
||
<?php
|
||
$langs = (array)($fw['language_labels'] ?? []);
|
||
$langLine = implode(', ', array_filter($langs, 'strlen'));
|
||
?>
|
||
<?php if ($langLine !== ''): ?>
|
||
<div class="text-white/50 text-xs">Languages: <?= htmlspecialchars($langLine) ?></div>
|
||
<?php endif; ?>
|
||
</div>
|
||
</div>
|
||
<div class="text-white/60 text-sm"><?= (int)($fw['level'] ?? 0) ?>%</div>
|
||
</div>
|
||
<div class="bar mt-4" data-level="<?= (int)($fw['level'] ?? 0) ?>">
|
||
<div class="bar-fill" style="width:0%"></div>
|
||
</div>
|
||
</div>
|
||
<?php endforeach; ?>
|
||
|
||
<?php if (!$frameworks): ?>
|
||
<div class="text-white/60">No frameworks added yet.</div>
|
||
<?php endif; ?>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<section id="gaming" class="mx-auto max-w-6xl px-4 py-14">
|
||
<h2 class="text-3xl font-bold">Gaming + Web Focus</h2>
|
||
|
||
<div class="mt-6 grid gap-4 lg:grid-cols-3">
|
||
<div class="card-glass rounded-2xl p-5">
|
||
<div class="flex items-center justify-between">
|
||
<div class="font-bold">1) Community Websites</div>
|
||
<i class="ri-group-fill text-violet text-xl"></i>
|
||
</div>
|
||
<p class="text-white/70 mt-2">
|
||
Landing pages, team/clean sites, game community hubs - fast, clean and easy to use.
|
||
</p>
|
||
</div>
|
||
|
||
<div class="card-glass rounded-2xl p-5">
|
||
<div class="flex items-center justify-between">
|
||
<div class="font-bold">2) Dashboards</div>
|
||
<i class="ri-dashboard-2-fill text-violet text-xl"></i>
|
||
</div>
|
||
<p class="text-white/70 mt-2">
|
||
Simple dashboards for data, versions, stats and pages that keep things organized.
|
||
</p>
|
||
</div>
|
||
|
||
<div class="card-glass rounded-2xl p-5">
|
||
<div class="flex items-center justify-between">
|
||
<div class="font-bold">3) APIs + Logic</div>
|
||
<i class="ri-code-box-fill text-violet text-xl"></i>
|
||
</div>
|
||
<p class="text-white/70 mt-2">
|
||
PHP + SQL + AJAX/jQuery to connect APIs and build real functionality behind the UI.
|
||
</p>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
|
||
<!-- CONTACT -->
|
||
<section id="contact" class="mx-auto max-w-6xl px-4 py-14 pb-24">
|
||
<h2 class="text-3xl font-bold">Get in Touch</h2>
|
||
|
||
<div class="mt-6 grid gap-4 lg:grid-cols-2">
|
||
<div class="card-glass rounded-2xl p-5">
|
||
<form id="contactForm" class="space-y-3" autocomplete="off">
|
||
<input type="hidden" name="csrf" value="<?= htmlspecialchars($csrfToken) ?>">
|
||
<input type="text" name="website" class="hidden" tabindex="-1" autocomplete="off">
|
||
|
||
<div>
|
||
<label class="text-sm text-white/70">Name</label>
|
||
<input name="name" class="field" required maxlength="80" placeholder="Your name">
|
||
</div>
|
||
<div>
|
||
<label class="text-sm text-white/70">Email</label>
|
||
<input name="email" type="email" class="field" required maxlength="120" placeholder="you@domain.com">
|
||
</div>
|
||
<div>
|
||
<label class="text-sm text-white/70">Message</label>
|
||
<textarea name="message" class="field h-32" required maxlength="2000"
|
||
placeholder="Tell me about your project…"></textarea>
|
||
</div>
|
||
|
||
<button type="submit" class="btn btn-light btn-lg w-100">
|
||
<i class="ri-send-plane-2-fill me-2"></i>Send Message
|
||
</button>
|
||
</form>
|
||
</div>
|
||
|
||
<div class="card-glass rounded-2xl p-5">
|
||
<div class="grid grid-cols-2 gap-3">
|
||
<a class="social" href="<?= htmlspecialchars($profile['github']) ?>" target="_blank" rel="noreferrer">
|
||
<i class="fa-brands fa-github"></i><span>GitHub</span>
|
||
</a>
|
||
<a class="social" href="<?= htmlspecialchars($profile['instagram']) ?>" target="_blank" rel="noreferrer">
|
||
<i class="fa-brands fa-instagram"></i><span>Instagram</span>
|
||
</a>
|
||
<a class="social" href="<?= htmlspecialchars($profile['tiktok']) ?>" target="_blank" rel="noreferrer">
|
||
<i class="fa-brands fa-tiktok"></i><span>TikTok</span>
|
||
</a>
|
||
</div>
|
||
|
||
<div class="mt-5 text-white/70">
|
||
<div class="font-semibold">What to include</div>
|
||
<ul class="mt-2 space-y-1">
|
||
<li><i class="ri-arrow-right-s-line text-neon"></i>Goal (website, dashboard, tool)</li>
|
||
<li><i class="ri-arrow-right-s-line text-neon"></i>Deadline + must-have features</li>
|
||
<li><i class="ri-arrow-right-s-line text-neon"></i>Any game/community context</li>
|
||
</ul>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<footer class="py-10 text-center text-white/50">
|
||
<div>© <?= date('Y') ?> <?= htmlspecialchars($profile['name']) ?> • Built with PHP • Tailwind • Bootstrap • AJAX • jQuery</div>
|
||
</footer>
|
||
</main>
|
||
|
||
<div class="modal fade" id="projectModal" tabindex="-1" aria-hidden="true">
|
||
<div class="modal-dialog modal-dialog-centered modal-lg">
|
||
<div class="modal-content text-bg-dark border border-white/10">
|
||
<div class="modal-header border-white/10">
|
||
<h5 class="modal-title" id="pmTitle">Project</h5>
|
||
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
|
||
</div>
|
||
<div class="modal-body">
|
||
<div class="text-white/70" id="pmSummary"></div>
|
||
<div class="mt-3 flex flex-wrap gap-2" id="pmTech"></div>
|
||
<div class="mt-4 flex gap-2 flex-wrap" id="pmLinks"></div>
|
||
<div class="mt-4" id="pmGalleryWrap">
|
||
<div class="text-white/60 text-sm mb-2">Gallery</div>
|
||
<div class="pm-gallery" id="pmGallery"></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="toast-container position-fixed bottom-0 end-0 p-3">
|
||
<div id="appToast" class="toast text-bg-dark border border-white/10" role="alert" aria-live="polite">
|
||
<div class="toast-body" id="toastMsg">…</div>
|
||
</div>
|
||
</div>
|
||
|
||
<script>
|
||
window.PROJECTS = <?= $projectsJson ?>;
|
||
</script>
|
||
|
||
<script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>
|
||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
|
||
<script src="<?= htmlspecialchars(url_path('/public/js/app.js')) ?>"></script>
|
||
<script>
|
||
window.CONTACT_ENDPOINT = <?= json_encode(url_path('/api/contact.php'), JSON_UNESCAPED_SLASHES) ?>;
|
||
</script>
|
||
</body>
|
||
</html>
|
||
|
||
|