diff --git a/api/chat.php b/api/chat.php
index 63c7705..6637e05 100644
--- a/api/chat.php
+++ b/api/chat.php
@@ -122,30 +122,44 @@ $systemPrompt .= "4. Do not make up facts or external projects not listed in the
$systemPrompt .= "5. Format your output using simple markdown (like bolding or bullet points) to make it readable.\n";
// Map chat history
+// Map chat history and prepend system instructions to the first user message
$contents = [];
-if (isset($inputData['history']) && is_array($inputData['history'])) {
- foreach ($inputData['history'] as $msg) {
+$firstMsgPrepended = false;
+
+if (isset($inputData['history']) && is_array($inputData['history']) && !empty($inputData['history'])) {
+ foreach ($inputData['history'] as $index => $msg) {
if (isset($msg['role']) && isset($msg['text'])) {
$role = ($msg['role'] === 'user') ? 'user' : 'model';
+ $text = $msg['text'];
+
+ // Prepend system prompt to the first user message in history
+ if (!$firstMsgPrepended && $role === 'user') {
+ $text = "System Context & Instructions:\n" . $systemPrompt . "\n\nUser Message: " . $text;
+ $firstMsgPrepended = true;
+ }
+
$contents[] = [
'role' => $role,
- 'parts' => [['text' => $msg['text']]]
+ 'parts' => [['text' => $text]]
];
}
}
}
-// Add the current user message
+
+// Add the current user message (prepend system instructions if history was empty)
+$currentUserText = $userMessage;
+if (!$firstMsgPrepended) {
+ $currentUserText = "System Context & Instructions:\n" . $systemPrompt . "\n\nUser Message: " . $userMessage;
+}
+
$contents[] = [
'role' => 'user',
- 'parts' => [['text' => $userMessage]]
+ 'parts' => [['text' => $currentUserText]]
];
-// Setup request body
+// Setup request body without systemInstruction key
$requestPayload = [
'contents' => $contents,
- 'systemInstruction' => [
- 'parts' => [['text' => $systemPrompt]]
- ],
'generationConfig' => [
'temperature' => 0.7,
'maxOutputTokens' => 800
@@ -153,7 +167,7 @@ $requestPayload = [
];
// Call Gemini API via cURL
-$url = "https://generativelanguage.googleapis.com/v1beta/models/gemini-1.5-flash:generateContent?key=" . urlencode($apiKey);
+$url = "https://generativelanguage.googleapis.com/v1/models/gemini-3.5-flash:generateContent?key=" . urlencode($apiKey);
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
@@ -161,6 +175,7 @@ curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($requestPayload));
curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json']);
curl_setopt($ch, CURLOPT_TIMEOUT, 15);
+curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
diff --git a/api/test_models.php b/api/test_models.php
new file mode 100644
index 0000000..caf424d
--- /dev/null
+++ b/api/test_models.php
@@ -0,0 +1,23 @@
+ p {
+
+.contact-container>p {
color: var(--text-muted);
margin-bottom: 40px;
font-size: 1.05rem;
@@ -858,7 +1026,12 @@ a.project-card:hover .arrow-icon {
transition: all 0.25s var(--ease);
text-decoration: none;
}
-.social-btn svg { width: 20px; height: 20px; }
+
+.social-btn svg {
+ width: 20px;
+ height: 20px;
+}
+
.social-btn:hover {
border-color: var(--border-hover);
color: var(--accent-2);
@@ -874,7 +1047,8 @@ a.project-card:hover .arrow-icon {
text-align: left;
}
-input, textarea {
+input,
+textarea {
width: 100%;
padding: 14px 18px;
background: var(--card-bg);
@@ -887,15 +1061,24 @@ input, textarea {
backdrop-filter: blur(10px);
outline: none;
}
-input::placeholder, textarea::placeholder { color: var(--text-subtle); }
-input:focus, textarea:focus {
+
+input::placeholder,
+textarea::placeholder {
+ color: var(--text-subtle);
+}
+
+input:focus,
+textarea:focus {
border-color: var(--border-hover);
box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.15);
background: rgba(37, 99, 235, 0.03);
}
/* reCAPTCHA centering */
-.g-recaptcha { display: flex; justify-content: center; }
+.g-recaptcha {
+ display: flex;
+ justify-content: center;
+}
/* =====================================================
FOOTER
@@ -915,7 +1098,8 @@ footer {
#back-to-top {
position: fixed;
bottom: 30px;
- right: 30px;
+ left: 50%;
+ transform: translateX(-50%) translateY(20px);
width: 48px;
height: 48px;
background: linear-gradient(135deg, var(--grad-1), var(--grad-2));
@@ -926,19 +1110,20 @@ footer {
align-items: center;
text-decoration: none;
box-shadow: 0 4px 12px rgba(37, 99, 235, 0.25);
- z-index: 999;
+ z-index: 998;
opacity: 0;
visibility: hidden;
- transform: translateY(20px);
transition: all 0.3s var(--ease);
}
+
#back-to-top.show {
opacity: 1;
visibility: visible;
- transform: translateY(0);
+ transform: translateX(-50%) translateY(0);
}
+
#back-to-top:hover {
- transform: translateY(-4px);
+ transform: translateX(-50%) translateY(-4px);
box-shadow: 0 8px 20px rgba(37, 99, 235, 0.4);
}
@@ -952,8 +1137,15 @@ footer {
width: 100%;
box-sizing: border-box;
}
-.page-hero h1 { font-size: clamp(1.8rem, 5vw, 3rem); margin-bottom: 14px; }
-.page-hero .subtitle { max-width: 500px; }
+
+.page-hero h1 {
+ font-size: clamp(1.8rem, 5vw, 3rem);
+ margin-bottom: 14px;
+}
+
+.page-hero .subtitle {
+ max-width: 500px;
+}
/* =====================================================
DIVIDER
@@ -968,54 +1160,116 @@ footer {
RESPONSIVE — TABLET (≤ 900px)
===================================================== */
@media (max-width: 900px) {
- .hero { gap: 40px; }
- .hero-img-container { width: 220px; height: 220px; }
- .about-content { grid-template-columns: 1fr; gap: 40px; }
- .stats-row { grid-template-columns: repeat(3, 1fr); }
+ .hero {
+ gap: 40px;
+ }
+
+ .hero-img-container {
+ width: 220px;
+ height: 220px;
+ }
+
+ .about-content {
+ grid-template-columns: 1fr;
+ gap: 40px;
+ }
+
+ .stats-row {
+ grid-template-columns: repeat(3, 1fr);
+ }
}
/* =====================================================
RESPONSIVE — MOBILE (≤ 768px)
===================================================== */
@media (max-width: 768px) {
+
/* Hamburger visible */
- .hamburger { display: flex; }
+ .hamburger {
+ display: flex;
+ }
/* Nav desktop links hidden by default on mobile */
.nav-links {
- /* Nav is full viewport width (no container offset), so left: 0 = screen edge */
position: fixed;
top: 0;
- left: -100vw; /* hide completely off-screen */
- width: 100vw;
+ right: -280px; /* slide from right */
+ left: auto;
+ width: 280px;
height: 100vh;
flex-direction: column;
- align-items: center;
- justify-content: center;
- text-align: center;
- padding: 40px 20px;
- gap: 8px;
+ align-items: flex-start;
+ justify-content: flex-start;
+ text-align: left;
+ padding: 80px 28px 40px;
+ gap: 4px;
background: var(--glass-bg);
- backdrop-filter: blur(24px);
- -webkit-backdrop-filter: blur(24px);
- border-right: 1px solid var(--border);
- transition: left 0.35s var(--ease);
- z-index: 999;
- box-shadow: 4px 0 40px rgba(0, 0, 0, 0.3);
+ backdrop-filter: blur(28px);
+ -webkit-backdrop-filter: blur(28px);
+ border-left: 1px solid var(--border);
+ transition: right 0.35s var(--ease);
+ z-index: 1001;
+ box-shadow: -4px 0 40px rgba(0, 0, 0, 0.3);
+ }
+
+ /* Slide in from right */
+ .nav-links.active {
+ right: 0;
}
- /* Slide in from left */
- .nav-links.active { left: 0; }
.nav-links a {
- font-size: 1.2rem;
+ font-size: 1.1rem;
font-weight: 600;
display: block;
- padding: 14px 30px;
+ padding: 12px 16px;
width: 100%;
border-radius: 12px;
}
- /* Close button (mode-btn) repositioned */
+ /* Nav close button */
+ .nav-close-btn {
+ display: flex;
+ position: absolute;
+ top: 22px;
+ right: 20px;
+ background: var(--card-bg);
+ border: 1px solid var(--border);
+ color: var(--text-main);
+ width: 36px;
+ height: 36px;
+ border-radius: 9px;
+ align-items: center;
+ justify-content: center;
+ font-size: 1.1rem;
+ cursor: pointer;
+ transition: background 0.2s, border-color 0.2s, transform 0.2s;
+ z-index: 1002;
+ }
+
+ .nav-close-btn:hover {
+ background: rgba(37, 99, 235, 0.1);
+ border-color: var(--border-hover);
+ transform: rotate(90deg);
+ }
+
+ /* Nav overlay backdrop */
+ .nav-overlay {
+ display: block;
+ position: fixed;
+ inset: 0;
+ background: rgba(0, 0, 0, 0.45);
+ z-index: 1000;
+ opacity: 0;
+ pointer-events: none;
+ transition: opacity 0.35s var(--ease);
+ }
+
+ .nav-overlay.active {
+ opacity: 1;
+ pointer-events: auto;
+ }
+
+ /* Theme toggle repositioned inside mobile nav */
.mode-btn {
margin-top: 20px;
margin-left: 0;
@@ -1027,18 +1281,44 @@ footer {
text-align: center;
gap: 30px;
}
- .hero-btns { justify-content: center; flex-wrap: wrap; }
- .subtitle { margin: 0 auto 24px; }
- .hero-badge { margin: 0 auto 16px; }
- .hero-img-container { width: 180px; height: 180px; }
+
+ .hero-btns {
+ justify-content: center;
+ flex-wrap: wrap;
+ }
+
+ .subtitle {
+ margin: 0 auto 24px;
+ }
+
+ .hero-badge {
+ margin: 0 auto 16px;
+ }
+
+ .hero-img-container {
+ width: 180px;
+ height: 180px;
+ }
/* Stats */
- .stats-row { grid-template-columns: repeat(3, 1fr); gap: 8px; }
- .stat-number { font-size: 1.6rem; }
- .stat-label { font-size: 0.7rem; }
+ .stats-row {
+ grid-template-columns: repeat(3, 1fr);
+ gap: 8px;
+ }
+
+ .stat-number {
+ font-size: 1.6rem;
+ }
+
+ .stat-label {
+ font-size: 0.7rem;
+ }
/* Certs */
- .cert-card { flex-direction: column; text-align: center; }
+ .cert-card {
+ flex-direction: column;
+ text-align: center;
+ }
/* Project grid single column on very narrow */
.project-grid,
@@ -1046,24 +1326,55 @@ footer {
grid-template-columns: 1fr;
}
- /* Back to top smaller */
- #back-to-top { bottom: 16px; right: 16px; width: 42px; height: 42px; }
+ /* Back to top smaller — centered */
+ #back-to-top {
+ bottom: 16px;
+ left: 50%;
+ right: auto;
+ width: 42px;
+ height: 42px;
+ }
/* Section divider */
- .section-divider { margin: 0 4%; }
+ .section-divider {
+ margin: 0 4%;
+ }
}
/* =====================================================
RESPONSIVE — SMALL MOBILE (≤ 480px)
===================================================== */
@media (max-width: 480px) {
- h1 { letter-spacing: -1px; }
- .stats-row { grid-template-columns: repeat(3, 1fr); }
- .cert-grid { grid-template-columns: 1fr; }
- .btn { padding: 11px 18px; font-size: 0.9rem; }
- .hero-img-container { width: 150px; height: 150px; }
- .social-links { gap: 10px; }
- .social-btn { width: 44px; height: 44px; }
+ h1 {
+ letter-spacing: -1px;
+ }
+
+ .stats-row {
+ grid-template-columns: repeat(3, 1fr);
+ }
+
+ .cert-grid {
+ grid-template-columns: 1fr;
+ }
+
+ .btn {
+ padding: 11px 18px;
+ font-size: 0.9rem;
+ }
+
+ .hero-img-container {
+ width: 150px;
+ height: 150px;
+ }
+
+ .social-links {
+ gap: 10px;
+ }
+
+ .social-btn {
+ width: 44px;
+ height: 44px;
+ }
}
/* =====================================================
@@ -1092,10 +1403,12 @@ footer {
justify-content: center;
transition: transform 0.25s var(--ease), box-shadow 0.25s var(--ease);
}
+
#chatbot-toggle-btn:hover {
transform: scale(1.08) translateY(-2px);
box-shadow: 0 8px 25px rgba(37, 99, 235, 0.45);
}
+
#chatbot-toggle-btn svg {
width: 24px;
height: 24px;
@@ -1133,11 +1446,13 @@ footer {
opacity: 0;
pointer-events: none;
}
+
#chatbot-window.active {
transform: translateY(0);
opacity: 1;
pointer-events: auto;
}
+
#chatbot-window.hidden {
display: none !important;
}
@@ -1150,16 +1465,19 @@ footer {
justify-content: space-between;
align-items: center;
}
+
.header-avatar-info {
display: flex;
align-items: center;
gap: 10px;
}
+
.avatar-container {
position: relative;
width: 36px;
height: 36px;
}
+
.chat-avatar {
width: 100%;
height: 100%;
@@ -1167,6 +1485,7 @@ footer {
object-fit: cover;
border: 1.5px solid var(--accent);
}
+
.status-indicator {
position: absolute;
bottom: 0;
@@ -1176,9 +1495,11 @@ footer {
border-radius: 50%;
border: 1.5px solid var(--bg);
}
+
.status-indicator.online {
background: #22c55e;
}
+
.header-text-info h4 {
font-family: 'Outfit', sans-serif;
font-size: 0.95rem;
@@ -1186,10 +1507,12 @@ footer {
color: var(--text-main);
line-height: 1.2;
}
+
.status-text {
font-size: 0.72rem;
color: var(--text-muted);
}
+
#chatbot-close-btn {
background: transparent;
border: none;
@@ -1202,6 +1525,7 @@ footer {
transition: color 0.2s;
border-radius: 6px;
}
+
#chatbot-close-btn:hover {
color: var(--text-main);
background: var(--card-bg);
@@ -1218,8 +1542,14 @@ footer {
}
/* Custom Scrollbar for Chat */
-#chatbot-messages::-webkit-scrollbar { width: 4px; }
-#chatbot-messages::-webkit-scrollbar-track { background: transparent; }
+#chatbot-messages::-webkit-scrollbar {
+ width: 4px;
+}
+
+#chatbot-messages::-webkit-scrollbar-track {
+ background: transparent;
+}
+
#chatbot-messages::-webkit-scrollbar-thumb {
background: var(--border);
border-radius: 2px;
@@ -1229,12 +1559,15 @@ footer {
display: flex;
width: 100%;
}
+
.chat-message.user-msg {
justify-content: flex-end;
}
+
.chat-message.bot-msg {
justify-content: flex-start;
}
+
.msg-bubble {
max-width: 80%;
padding: 10px 14px;
@@ -1243,18 +1576,21 @@ footer {
line-height: 1.5;
word-break: break-word;
}
+
.user-msg .msg-bubble {
background: var(--accent);
color: #fff;
border-bottom-right-radius: 2px;
box-shadow: 0 4px 10px rgba(37, 99, 235, 0.2);
}
+
.bot-msg .msg-bubble {
background: var(--card-bg);
color: var(--text-main);
border: 1px solid var(--border);
border-bottom-left-radius: 2px;
}
+
.bot-msg.error-msg .msg-bubble {
border-color: rgba(239, 68, 68, 0.3);
background: rgba(239, 68, 68, 0.05);
@@ -1270,6 +1606,7 @@ footer {
gap: 8px;
align-items: center;
}
+
#chatbot-input-field {
flex-grow: 1;
background: var(--card-bg);
@@ -1281,9 +1618,11 @@ footer {
outline: none;
transition: border-color 0.2s;
}
+
#chatbot-input-field:focus {
border-color: var(--border-hover);
}
+
#chatbot-send-btn {
background: var(--accent);
color: #fff;
@@ -1298,10 +1637,12 @@ footer {
transition: background 0.2s, transform 0.2s;
flex-shrink: 0;
}
+
#chatbot-send-btn:hover {
background: var(--accent-2);
transform: scale(1.05);
}
+
#chatbot-send-btn svg {
width: 16px;
height: 16px;
@@ -1314,6 +1655,7 @@ footer {
gap: 4px;
padding: 12px 16px !important;
}
+
.dot-bounce {
width: 6px;
height: 6px;
@@ -1322,12 +1664,26 @@ footer {
display: inline-block;
animation: bounce 1.4s infinite ease-in-out both;
}
-.dot-bounce:nth-child(1) { animation-delay: -0.32s; }
-.dot-bounce:nth-child(2) { animation-delay: -0.16s; }
+
+.dot-bounce:nth-child(1) {
+ animation-delay: -0.32s;
+}
+
+.dot-bounce:nth-child(2) {
+ animation-delay: -0.16s;
+}
@keyframes bounce {
- 0%, 80%, 100% { transform: scale(0); }
- 40% { transform: scale(1.0); }
+
+ 0%,
+ 80%,
+ 100% {
+ transform: scale(0);
+ }
+
+ 40% {
+ transform: scale(1.0);
+ }
}
/* Markdown spacing inside bubbles */
@@ -1335,6 +1691,7 @@ footer {
padding-left: 20px;
margin: 6px 0;
}
+
.msg-bubble li {
margin-bottom: 4px;
}
@@ -1345,11 +1702,11 @@ footer {
bottom: 16px;
right: 16px;
}
+
#chatbot-window {
width: calc(100vw - 32px);
right: 16px;
bottom: 80px;
height: 60vh;
}
-}
-
+}
\ No newline at end of file
diff --git a/assets/js/chat.js b/assets/js/chat.js
index a245ce8..9333721 100644
--- a/assets/js/chat.js
+++ b/assets/js/chat.js
@@ -38,7 +38,7 @@ document.addEventListener("DOMContentLoaded", () => {
chatHistory.push({ role: "user", text: message });
inputField.value = "";
- // Cap history to last 10 messages to save context limits
+ // Cap history to last 10 messages
if (chatHistory.length > 10) {
chatHistory = chatHistory.slice(-10);
}
@@ -47,6 +47,9 @@ document.addEventListener("DOMContentLoaded", () => {
const typingIndicator = showTypingIndicator();
isWaiting = true;
+ // Try backend proxy first, fallback to client-side if it fails
+ let replySuccess = false;
+
try {
const response = await fetch("api/chat.php", {
method: "POST",
@@ -55,28 +58,85 @@ document.addEventListener("DOMContentLoaded", () => {
},
body: JSON.stringify({
message: message,
- history: chatHistory.slice(0, -1) // pass history excluding the current user message (which is passed in "message" parameter)
+ history: chatHistory.slice(0, -1)
})
});
const data = await response.json();
- removeTypingIndicator(typingIndicator);
if (response.ok && data.reply) {
+ removeTypingIndicator(typingIndicator);
const botReply = data.reply;
addMessage(botReply, "bot-msg");
chatHistory.push({ role: "model", text: botReply });
- } else {
- const errMsg = data.error || "Sorry, I encountered an error. Please try again.";
- addMessage(errMsg, "bot-msg error-msg");
+ replySuccess = true;
}
} catch (error) {
- removeTypingIndicator(typingIndicator);
- addMessage("Unable to connect to assistant. Please check your internet connection.", "bot-msg error-msg");
- console.error("Chatbot Error:", error);
- } finally {
- isWaiting = false;
+ console.warn("Backend API call failed, attempting client-side fallback. Details:", error);
}
+
+ // CLIENT-SIDE FALLBACK (e.g. for InfinityFree or local cert errors)
+ if (!replySuccess) {
+ const config = window.chatbotConfig;
+ if (config && config.apiKey && config.systemPrompt) {
+ try {
+ // Structure payload for Gemini client-side and inject systemInstructions in first user message
+ let firstMsgPrepended = false;
+ const contents = chatHistory.map(msg => {
+ let text = msg.text;
+ const role = msg.role === 'user' ? 'user' : 'model';
+ if (!firstMsgPrepended && role === 'user') {
+ text = `System Context & Instructions:\n${config.systemPrompt}\n\nUser Message: ${text}`;
+ firstMsgPrepended = true;
+ }
+ return {
+ role: role,
+ parts: [{ text: text }]
+ };
+ });
+
+ const requestPayload = {
+ contents: contents,
+ generationConfig: {
+ temperature: 0.7,
+ maxOutputTokens: 800
+ }
+ };
+
+ const url = `https://generativelanguage.googleapis.com/v1/models/gemini-3.5-flash:generateContent?key=${encodeURIComponent(config.apiKey)}`;
+
+ const response = await fetch(url, {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/json"
+ },
+ body: JSON.stringify(requestPayload)
+ });
+
+ const data = await response.json();
+ removeTypingIndicator(typingIndicator);
+
+ if (response.ok && data.candidates && data.candidates[0].content.parts[0].text) {
+ const botReply = data.candidates[0].content.parts[0].text;
+ addMessage(botReply, "bot-msg");
+ chatHistory.push({ role: "model", text: botReply });
+ replySuccess = true;
+ } else {
+ const errMsg = data.error?.message || "Sorry, I encountered an error. Please try again.";
+ addMessage(errMsg, "bot-msg error-msg");
+ }
+ } catch (fallbackError) {
+ removeTypingIndicator(typingIndicator);
+ addMessage("Unable to connect to assistant. Please check your internet connection.", "bot-msg error-msg");
+ console.error("Direct API Fallback Error:", fallbackError);
+ }
+ } else {
+ removeTypingIndicator(typingIndicator);
+ addMessage("API request blocked by host firewall and fallback config is missing.", "bot-msg error-msg");
+ }
+ }
+
+ isWaiting = false;
});
// Helper: Add message bubble to logs
@@ -87,7 +147,6 @@ document.addEventListener("DOMContentLoaded", () => {
const bubbleDiv = document.createElement("div");
bubbleDiv.className = "msg-bubble";
- // Format simple Markdown conversions (bold, bullet points, line breaks)
bubbleDiv.innerHTML = formatMessageText(text);
messageDiv.appendChild(bubbleDiv);
@@ -125,16 +184,13 @@ document.addEventListener("DOMContentLoaded", () => {
// Helper: Simple Markdown Formatter
function formatMessageText(text) {
- // Safe escaping (minimal to allow HTML format helper if needed, but safe)
let escaped = text
.replace(/&/g, "&")
.replace(//g, ">");
- // Bold (**bold**)
escaped = escaped.replace(/\*\*(.*?)\*\*/g, "$1");
- // Bullet points starting with "- " or "* "
const lines = escaped.split("\n");
let inList = false;
let formatted = "";
@@ -160,7 +216,6 @@ document.addEventListener("DOMContentLoaded", () => {
formatted += "";
}
- // Clean up double linebreaks
return formatted.replace(/(
\s*){3,}/g, "
");
}
});
diff --git a/views/chatbot.php b/views/chatbot.php
index ee78b93..33bbb7f 100644
--- a/views/chatbot.php
+++ b/views/chatbot.php
@@ -1,3 +1,96 @@
+connect_error) {
+ // 1. Settings / About
+ $settingsQuery = $mysqli->query("SELECT * FROM settings WHERE id = 1");
+ if ($settingsQuery) {
+ $settings = $settingsQuery->fetch_assoc();
+ }
+
+ // 2. Projects
+ $projectsQuery = $mysqli->query("SELECT title, des, techstack, url FROM projects");
+ if ($projectsQuery) {
+ while ($row = $projectsQuery->fetch_assoc()) {
+ $projects[] = $row;
+ }
+ }
+
+ // 3. Certifications
+ $certsQuery = $mysqli->query("SELECT title, issuer, date_issued FROM certifications");
+ if ($certsQuery) {
+ while ($row = $certsQuery->fetch_assoc()) {
+ $certs[] = $row;
+ }
+ }
+
+ // 4. Articles
+ $articlesQuery = $mysqli->query("SELECT title, excerpt, date_published, tag, url FROM articles");
+ if ($articlesQuery) {
+ while ($row = $articlesQuery->fetch_assoc()) {
+ $articles[] = $row;
+ }
+ }
+}
+
+// Build system instructions
+$systemPrompt = "You are a friendly, professional, and intelligent AI assistant built into the personal portfolio website of Cris Charles Garcia.\n";
+$systemPrompt .= "Your role is to represent Cris, assist recruiters, potential clients, and visitors, and answer questions about his background, experience, skills, and projects.\n\n";
+
+$systemPrompt .= "Cris's Profile & Biography:\n";
+if ($settings) {
+ $systemPrompt .= "- Bio Paragraph 1: " . $settings['about_text_1'] . "\n";
+ $systemPrompt .= "- Bio Paragraph 2: " . $settings['about_text_2'] . "\n";
+ $systemPrompt .= "- Years coding: " . $settings['years_coding'] . "\n";
+ $systemPrompt .= "- Projects completed: " . $settings['projects_count'] . "\n";
+ $systemPrompt .= "- Certifications: " . $settings['certifications_count'] . "\n";
+ $systemPrompt .= "- Github: " . $settings['github_url'] . "\n";
+ $systemPrompt .= "- LinkedIn: " . $settings['linkedin_url'] . "\n";
+ $systemPrompt .= "- Instagram: " . $settings['instagram_url'] . "\n";
+ $systemPrompt .= "- Facebook: " . $settings['facebook_url'] . "\n";
+ $systemPrompt .= "- TikTok: " . $settings['tiktok_url'] . "\n";
+} else {
+ $systemPrompt .= "- Cris is a senior IT student majoring in Software Development based in Laguna, Philippines.\n";
+}
+
+$systemPrompt .= "\nCris's Projects:\n";
+foreach ($projects as $p) {
+ $systemPrompt .= "- **" . $p['title'] . "**: " . $p['des'] . " (Tech: " . $p['techstack'] . "). Link: " . $p['url'] . "\n";
+}
+
+$systemPrompt .= "\nCris's Certifications:\n";
+foreach ($certs as $c) {
+ $systemPrompt .= "- " . $c['title'] . " by " . $c['issuer'] . " (Issued: " . $c['date_issued'] . ")\n";
+}
+
+$systemPrompt .= "\nCris's Latest Articles/Blog Posts:\n";
+foreach ($articles as $a) {
+ $systemPrompt .= "- \"" . $a['title'] . "\": " . $a['excerpt'] . " [Tag: " . $a['tag'] . ", Date: " . $a['date_published'] . ", Link: " . $a['url'] . "]\n";
+}
+
+$systemPrompt .= "\nGuidelines for your responses:\n";
+$systemPrompt .= "1. Respond in first-person (e.g., \"I am a senior IT student...\" or \"In my project UniMarket...\") or as a dedicated assistant of Cris. Represent Cris accurately, warmly, and professionally.\n";
+$systemPrompt .= "2. Keep responses brief, engaging, and directly related to the question. Recruiters appreciate concise answers.\n";
+$systemPrompt .= "3. If asked about contact options or hiring, point them to his contact form at the bottom of the page or his professional links (Github, LinkedIn).\n";
+$systemPrompt .= "4. Do not make up facts or external projects not listed in the context. If you don't know the answer, politely invite the user to contact Cris directly.\n";
+$systemPrompt .= "5. Format your output using simple markdown (like bolding or bullet points) to make it readable.\n";
+
+$apiKey = getenv('GEMINI_API_KEY') ?: ($_ENV['GEMINI_API_KEY'] ?? '');
+?>
+
+
+
+