Initial commit
This commit is contained in:
718
index.php
Normal file
718
index.php
Normal file
@@ -0,0 +1,718 @@
|
||||
<?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>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user