Compare commits
No commits in common. "ai-dev" and "master" have entirely different histories.
@ -1,43 +0,0 @@
|
||||
<?php
|
||||
header('Content-Type: application/json');
|
||||
require_once __DIR__ . '/../ai/LocalAIApi.php';
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
||||
echo json_encode(['success' => false, 'error' => 'Invalid method']);
|
||||
exit;
|
||||
}
|
||||
|
||||
$input = json_decode(file_get_contents('php://input'), true);
|
||||
$title = $input['title'] ?? '';
|
||||
|
||||
if (empty(trim($title))) {
|
||||
echo json_encode(['success' => false, 'error' => 'Please enter a Title first to generate content.']);
|
||||
exit;
|
||||
}
|
||||
|
||||
$prompt = "Write a comprehensive, engaging, and well-structured blog post article based on the following title: \"$title\".
|
||||
Format the text strictly using HTML tags suitable for a WYSIWYG editor (like <h2>, <h3>, <p>, <ul>, <li>, <strong>).
|
||||
Do not use markdown formatting. Output only raw HTML code for the article body without any markdown blocks or explanations.";
|
||||
|
||||
$response = LocalAIApi::createResponse([
|
||||
'input' => [
|
||||
['role' => 'system', 'content' => 'You are an expert copywriter and professional blog author. You write high-quality, engaging content formatted as HTML.'],
|
||||
['role' => 'user', 'content' => $prompt]
|
||||
]
|
||||
], ['poll_interval' => 3, 'poll_timeout' => 180]);
|
||||
|
||||
if (!empty($response['success'])) {
|
||||
$text = LocalAIApi::extractText($response);
|
||||
if ($text === '') {
|
||||
$decoded = LocalAIApi::decodeJsonFromResponse($response);
|
||||
$text = $decoded ? json_encode($decoded, JSON_UNESCAPED_UNICODE) : (string)($response['data'] ?? '');
|
||||
}
|
||||
|
||||
// Clean up markdown block if the model returned it anyway
|
||||
$text = preg_replace('/^```(?:html)?\s*|\s*```$/i', '', trim($text));
|
||||
|
||||
echo json_encode(['success' => true, 'content' => trim($text)]);
|
||||
} else {
|
||||
echo json_encode(['success' => false, 'error' => $response['error'] ?? 'AI generation failed']);
|
||||
}
|
||||
|
||||
@ -1,10 +0,0 @@
|
||||
<?php
|
||||
require_once '../db/config.php';
|
||||
$id = $_GET['id'] ?? null;
|
||||
if ($id) {
|
||||
$db = db();
|
||||
$stmt = $db->prepare("DELETE FROM posts WHERE id = ?");
|
||||
$stmt->execute([$id]);
|
||||
}
|
||||
header("Location: index.php");
|
||||
exit;
|
||||
232
admin/edit.php
232
admin/edit.php
@ -1,232 +0,0 @@
|
||||
<?php
|
||||
require_once '../db/config.php';
|
||||
$id = $_GET['id'] ?? null;
|
||||
$db = db();
|
||||
$post = null;
|
||||
|
||||
if ($id) {
|
||||
$stmt = $db->prepare("SELECT * FROM posts WHERE id = ?");
|
||||
$stmt->execute([$id]);
|
||||
$post = $stmt->fetch();
|
||||
}
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$title = $_POST['title'];
|
||||
$slug = $_POST['slug'];
|
||||
$summary = $_POST['summary'];
|
||||
$content = $_POST['content'];
|
||||
$image_url = $_POST['image_url'];
|
||||
|
||||
if ($id) {
|
||||
$stmt = $db->prepare("UPDATE posts SET title = ?, slug = ?, summary = ?, content = ?, image_url = ? WHERE id = ?");
|
||||
$stmt->execute([$title, $slug, $summary, $content, $image_url, $id]);
|
||||
} else {
|
||||
$stmt = $db->prepare("INSERT INTO posts (title, slug, summary, content, image_url) VALUES (?, ?, ?, ?, ?)");
|
||||
$stmt->execute([$title, $slug, $summary, $content, $image_url]);
|
||||
}
|
||||
header("Location: index.php");
|
||||
exit;
|
||||
}
|
||||
|
||||
$projectName = $_SERVER['PROJECT_NAME'] ?? 'Our Blog';
|
||||
?>
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title><?= $id ? 'Edit Post' : 'New Post' ?> | Admin Panel</title>
|
||||
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=Outfit:wght@500;700;800&display=swap" rel="stylesheet">
|
||||
|
||||
<!-- Quill Editor CSS -->
|
||||
<link href="https://cdn.quilljs.com/1.3.7/quill.snow.css" rel="stylesheet">
|
||||
|
||||
<link rel="stylesheet" href="../assets/css/custom.css?v=<?= time() ?>">
|
||||
</head>
|
||||
<body>
|
||||
<div class="bg-shapes">
|
||||
<canvas id="bg-canvas"></canvas>
|
||||
<div class="shape shape-1" data-speed="2"></div>
|
||||
<div class="shape shape-2" data-speed="-1.5" style="opacity: 0.15; top: -10%; right: -10%; background: #3b82f6;"></div>
|
||||
<div class="shape shape-mouse"></div>
|
||||
</div>
|
||||
|
||||
<header>
|
||||
<div class="container nav">
|
||||
<a href="../index.php" class="logo">Admin Panel</a>
|
||||
<div class="nav-links">
|
||||
<a href="index.php">Back to Dashboard</a>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main class="container">
|
||||
<div class="admin-header">
|
||||
<h1><?= $id ? 'Edit Post' : 'Create New Post' ?></h1>
|
||||
</div>
|
||||
|
||||
<form method="POST" class="form-container" id="post-form">
|
||||
<div class="form-group">
|
||||
<label for="title">Title</label>
|
||||
<input type="text" name="title" id="title" class="form-control" value="<?= htmlspecialchars($post['title'] ?? '') ?>" placeholder="e.g. 10 Tips for Modern Web Design" required>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="slug">URL Slug</label>
|
||||
<input type="text" name="slug" id="slug" class="form-control" value="<?= htmlspecialchars($post['slug'] ?? '') ?>" placeholder="e.g. 10-tips-for-modern-web-design" required>
|
||||
<small style="color: var(--secondary); margin-top: 0.25rem; display: block; font-size: 0.85rem;">This will be used in the URL: /post.php?slug=<b>your-slug</b></small>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="summary">Summary</label>
|
||||
<textarea name="summary" id="summary" class="form-control" style="min-height: 100px;" placeholder="A short description of your article." required><?= htmlspecialchars($post['summary'] ?? '') ?></textarea>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="image_url">Cover Image URL</label>
|
||||
<input type="url" name="image_url" id="image_url" class="form-control" value="<?= htmlspecialchars($post['image_url'] ?? '') ?>" placeholder="https://example.com/image.jpg">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 0.5rem;">
|
||||
<label for="content" style="margin-bottom: 0;">Content</label>
|
||||
<button type="button" id="btn-generate-ai" class="btn" style="background: linear-gradient(135deg, #a855f7, #ec4899); color: white; border: none; padding: 0.4rem 0.8rem; font-size: 0.85rem; border-radius: 8px; cursor: pointer; display: flex; align-items: center; gap: 0.4rem; box-shadow: 0 4px 12px rgba(236, 72, 153, 0.3); transition: transform 0.2s, box-shadow 0.2s;">
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z"></path><polyline points="3.27 6.96 12 12.01 20.73 6.96"></polyline><line x1="12" y1="22.08" x2="12" y2="12"></line></svg>
|
||||
<span>Generate with AI</span>
|
||||
</button>
|
||||
</div>
|
||||
<!-- Editor Container -->
|
||||
<div id="editor-container"></div>
|
||||
<!-- Hidden textarea to store the HTML for form submission -->
|
||||
<textarea name="content" id="hidden-content" style="display: none;"></textarea>
|
||||
<!-- Hidden textarea to safely load initial content -->
|
||||
<textarea id="initial-content" style="display: none;"><?= htmlspecialchars($post['content'] ?? '') ?></textarea>
|
||||
</div>
|
||||
|
||||
<div class="actions" style="margin-top: 2.5rem; padding-top: 2rem; border-top: 1px solid var(--border);">
|
||||
<button type="submit" class="btn btn-primary" style="padding-left: 2rem; padding-right: 2rem;"><?= $id ? 'Update Post' : 'Publish Post' ?></button>
|
||||
<a href="index.php" class="btn btn-outline" style="padding-left: 1.5rem; padding-right: 1.5rem;">Cancel</a>
|
||||
</div>
|
||||
</form>
|
||||
</main>
|
||||
|
||||
<footer style="background: transparent; border-top: none; margin-top: 0;">
|
||||
<div class="container">
|
||||
<p>© <?= date('Y') ?> <?= htmlspecialchars($projectName) ?> Admin.</p>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<!-- Background scripts and Quill logic -->
|
||||
<script src="../assets/js/main.js?v=<?= time() ?>"></script>
|
||||
<script src="https://cdn.quilljs.com/1.3.7/quill.min.js"></script>
|
||||
|
||||
<script>
|
||||
const titleInput = document.getElementById('title');
|
||||
const slugInput = document.getElementById('slug');
|
||||
const btnGenerateAI = document.getElementById('btn-generate-ai');
|
||||
|
||||
titleInput.addEventListener('input', () => {
|
||||
if (!<?= $id ? 'true' : 'false' ?> || slugInput.value === '') {
|
||||
slugInput.value = titleInput.value
|
||||
.toLowerCase()
|
||||
.replace(/[^\w\s-]/g, '')
|
||||
.replace(/[\s_]+/g, '-')
|
||||
.replace(/^-+|-+$/g, '');
|
||||
}
|
||||
});
|
||||
|
||||
// Initialize Quill Editor
|
||||
const quill = new Quill('#editor-container', {
|
||||
theme: 'snow',
|
||||
placeholder: 'Write your amazing article here... You can format text, add lists, and embed images!',
|
||||
modules: {
|
||||
toolbar: [
|
||||
[{ 'header': [2, 3, 4, false] }],
|
||||
['bold', 'italic', 'underline', 'strike'],
|
||||
['blockquote', 'code-block'],
|
||||
[{ 'list': 'ordered'}, { 'list': 'bullet' }],
|
||||
['link', 'image', 'video'],
|
||||
['clean']
|
||||
]
|
||||
}
|
||||
});
|
||||
|
||||
// Load initial content if editing
|
||||
const initialContent = document.getElementById('initial-content').value;
|
||||
if (initialContent) {
|
||||
quill.clipboard.dangerouslyPasteHTML(initialContent);
|
||||
}
|
||||
|
||||
// Sync Quill content to hidden textarea on submit
|
||||
const form = document.getElementById('post-form');
|
||||
form.addEventListener('submit', function(e) {
|
||||
const hiddenContent = document.getElementById('hidden-content');
|
||||
hiddenContent.value = quill.root.innerHTML;
|
||||
|
||||
// Basic validation to prevent empty submissions
|
||||
if (quill.getText().trim().length === 0 && !quill.root.innerHTML.includes('<img') && !quill.root.innerHTML.includes('<video')) {
|
||||
e.preventDefault();
|
||||
alert('Please write some content before publishing.');
|
||||
}
|
||||
});
|
||||
|
||||
// AI Generation Logic
|
||||
btnGenerateAI.addEventListener('click', async () => {
|
||||
const title = titleInput.value.trim();
|
||||
if (!title) {
|
||||
alert('Пожалуйста, введите заголовок (Title) статьи перед генерацией текста.');
|
||||
titleInput.focus();
|
||||
return;
|
||||
}
|
||||
|
||||
const originalBtnHTML = btnGenerateAI.innerHTML;
|
||||
btnGenerateAI.innerHTML = '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" class="spin"><path d="M12 2v4M12 18v4M4.93 4.93l2.83 2.83M16.24 16.24l2.83 2.83M2 12h4M18 12h4M4.93 19.07l2.83-2.83M16.24 7.76l2.83-2.83"></path></svg> <span>Generating...</span>';
|
||||
btnGenerateAI.disabled = true;
|
||||
btnGenerateAI.style.opacity = '0.7';
|
||||
|
||||
try {
|
||||
const response = await fetch('api_generate.php', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ title: title })
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (data.success && data.content) {
|
||||
// Insert into Quill
|
||||
// If editor is empty, replace. Otherwise append? Let's just set it or append.
|
||||
const currentText = quill.getText().trim();
|
||||
if (currentText.length > 0) {
|
||||
if (confirm('Редактор уже содержит текст. Заменить его сгенерированным (ОК) или добавить в конец (Отмена)?')) {
|
||||
quill.clipboard.dangerouslyPasteHTML(data.content);
|
||||
} else {
|
||||
const html = quill.root.innerHTML + '<br>' + data.content;
|
||||
quill.clipboard.dangerouslyPasteHTML(html);
|
||||
}
|
||||
} else {
|
||||
quill.clipboard.dangerouslyPasteHTML(data.content);
|
||||
}
|
||||
} else {
|
||||
alert('Ошибка генерации: ' + (data.error || 'Неизвестная ошибка'));
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('AI Request failed:', error);
|
||||
alert('Произошла ошибка при обращении к серверу ИИ. Попробуйте еще раз.');
|
||||
} finally {
|
||||
btnGenerateAI.innerHTML = originalBtnHTML;
|
||||
btnGenerateAI.disabled = false;
|
||||
btnGenerateAI.style.opacity = '1';
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style>
|
||||
@keyframes spin { 100% { transform: rotate(360deg); } }
|
||||
.spin { animation: spin 1s linear infinite; }
|
||||
</style>
|
||||
</body>
|
||||
</html>
|
||||
@ -1,87 +0,0 @@
|
||||
<?php
|
||||
require_once '../db/config.php';
|
||||
$db = db();
|
||||
$stmt = $db->query("SELECT * FROM posts ORDER BY created_at DESC");
|
||||
$posts = $stmt->fetchAll();
|
||||
|
||||
$projectName = $_SERVER['PROJECT_NAME'] ?? 'Our Blog';
|
||||
?>
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>Admin Dashboard | <?= htmlspecialchars($projectName) ?></title>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=Outfit:wght@500;700;800&display=swap" rel="stylesheet">
|
||||
<link rel="stylesheet" href="../assets/css/custom.css?v=<?= time() ?>">
|
||||
</head>
|
||||
<body>
|
||||
<div class="bg-shapes">
|
||||
<canvas id="bg-canvas"></canvas>
|
||||
<div class="shape shape-1" data-speed="2" style="opacity: 0.15; top: -20%; background: #06b6d4;"></div>
|
||||
<div class="shape shape-2" data-speed="-1.5"></div>
|
||||
<div class="shape shape-mouse"></div>
|
||||
</div>
|
||||
|
||||
<header>
|
||||
<div class="container nav">
|
||||
<a href="../index.php" class="logo">Admin Panel</a>
|
||||
<div class="nav-links">
|
||||
<a href="../index.php">View Site</a>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main class="container">
|
||||
<div class="admin-header">
|
||||
<h1>Manage Posts</h1>
|
||||
<a href="edit.php" class="btn btn-primary">Create New Post</a>
|
||||
</div>
|
||||
|
||||
<div style="background: var(--surface-solid); border-radius: var(--radius-lg); box-shadow: var(--shadow-sm); border: 1px solid var(--border); overflow: hidden;">
|
||||
<table class="admin-table" style="margin-top: 0; border: none; box-shadow: none; border-radius: 0;">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Article details</th>
|
||||
<th>Date</th>
|
||||
<th style="text-align: right;">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($posts as $post): ?>
|
||||
<tr>
|
||||
<td>
|
||||
<strong style="font-size: 1.1rem; color: var(--primary); font-family: 'Outfit', sans-serif;"><?= htmlspecialchars($post['title']) ?></strong>
|
||||
<br>
|
||||
<small style="color: var(--secondary); font-family: monospace; background: #f1f5f9; padding: 2px 6px; border-radius: 4px;"><?= htmlspecialchars($post['slug']) ?></small>
|
||||
</td>
|
||||
<td style="color: var(--secondary);"><?= date('M j, Y', strtotime($post['created_at'])) ?></td>
|
||||
<td class="actions" style="justify-content: flex-end;">
|
||||
<a href="edit.php?id=<?= $post['id'] ?>" class="btn btn-outline" style="padding: 0.4rem 1rem;">Edit</a>
|
||||
<a href="delete.php?id=<?= $post['id'] ?>" class="btn btn-outline" style="padding: 0.4rem 1rem; color: #ef4444; border-color: rgba(239,68,68,0.3);" onclick="return confirm('Are you sure you want to delete this post?')">Delete</a>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
<?php if (empty($posts)): ?>
|
||||
<tr>
|
||||
<td colspan="3" style="text-align: center; padding: 3rem;">
|
||||
<div style="color: var(--secondary); margin-bottom: 1rem;">Your blog is empty.</div>
|
||||
<a href="edit.php" class="btn btn-primary">Write your first post</a>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endif; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<footer style="background: transparent; border-top: none; margin-top: 2rem;">
|
||||
<div class="container">
|
||||
<p>© <?= date('Y') ?> <?= htmlspecialchars($projectName) ?> Admin.</p>
|
||||
</div>
|
||||
</footer>
|
||||
<script src="../assets/js/main.js?v=<?= time() ?>"></script>
|
||||
</body>
|
||||
</html>
|
||||
@ -1,524 +1,302 @@
|
||||
:root {
|
||||
--primary: #0f172a;
|
||||
--secondary: #475569;
|
||||
--accent: #2563eb;
|
||||
--accent-hover: #1d4ed8;
|
||||
--accent-gradient: linear-gradient(135deg, #2563eb 0%, #06b6d4 100%);
|
||||
--bg: #f8fafc;
|
||||
--surface: rgba(255, 255, 255, 0.85);
|
||||
--surface-solid: #ffffff;
|
||||
--border: rgba(15, 23, 42, 0.08);
|
||||
--text-main: #0f172a;
|
||||
--text-muted: #64748b;
|
||||
--radius-lg: 24px;
|
||||
--radius-md: 16px;
|
||||
--radius-sm: 8px;
|
||||
--shadow-sm: 0 4px 6px -1px rgba(0, 0, 0, 0.05);
|
||||
--shadow-lg: 0 20px 25px -5px rgba(0, 0, 0, 0.05), 0 8px 10px -6px rgba(0, 0, 0, 0.01);
|
||||
--shadow-hover: 0 25px 30px -5px rgba(0, 0, 0, 0.08), 0 10px 15px -6px rgba(0, 0, 0, 0.04);
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: 'Inter', system-ui, -apple-system, sans-serif;
|
||||
background-color: var(--bg);
|
||||
color: var(--text-main);
|
||||
line-height: 1.6;
|
||||
margin: 0;
|
||||
overflow-x: hidden;
|
||||
background: linear-gradient(-45deg, #ee7752, #e73c7e, #23a6d5, #23d5ab);
|
||||
background-size: 400% 400%;
|
||||
animation: gradient 15s ease infinite;
|
||||
color: #212529;
|
||||
font-family: 'Inter', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
|
||||
font-size: 14px;
|
||||
margin: 0;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
h1, h2, h3, h4, h5, h6, .logo {
|
||||
font-family: 'Outfit', 'Inter', system-ui, -apple-system, sans-serif;
|
||||
color: var(--primary);
|
||||
.main-wrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-height: 100vh;
|
||||
width: 100%;
|
||||
padding: 20px;
|
||||
box-sizing: border-box;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
/* Decorative Background Elements */
|
||||
.bg-shapes {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
overflow: hidden;
|
||||
z-index: -1;
|
||||
pointer-events: none;
|
||||
@keyframes gradient {
|
||||
0% {
|
||||
background-position: 0% 50%;
|
||||
}
|
||||
50% {
|
||||
background-position: 100% 50%;
|
||||
}
|
||||
100% {
|
||||
background-position: 0% 50%;
|
||||
}
|
||||
}
|
||||
|
||||
#bg-canvas {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
opacity: 0.6;
|
||||
.chat-container {
|
||||
width: 100%;
|
||||
max-width: 600px;
|
||||
background: rgba(255, 255, 255, 0.85);
|
||||
border: 1px solid rgba(255, 255, 255, 0.3);
|
||||
border-radius: 20px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 85vh;
|
||||
box-shadow: 0 20px 40px rgba(0,0,0,0.2);
|
||||
backdrop-filter: blur(15px);
|
||||
-webkit-backdrop-filter: blur(15px);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.shape {
|
||||
position: absolute;
|
||||
border-radius: 50%;
|
||||
filter: blur(90px);
|
||||
opacity: 0.45;
|
||||
transition: transform 0.4s cubic-bezier(0.2, 0.8, 0.2, 1);
|
||||
will-change: transform;
|
||||
.chat-header {
|
||||
padding: 1.5rem;
|
||||
border-bottom: 1px solid rgba(0, 0, 0, 0.05);
|
||||
background: rgba(255, 255, 255, 0.5);
|
||||
font-weight: 700;
|
||||
font-size: 1.1rem;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.shape-1 {
|
||||
top: -10%;
|
||||
left: -10%;
|
||||
width: 500px;
|
||||
height: 500px;
|
||||
background: var(--accent);
|
||||
animation: float 15s ease-in-out infinite;
|
||||
.chat-messages {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
padding: 1.5rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1.25rem;
|
||||
}
|
||||
|
||||
.shape-2 {
|
||||
top: 40%;
|
||||
right: -5%;
|
||||
width: 450px;
|
||||
height: 450px;
|
||||
background: #06b6d4;
|
||||
animation: float 18s ease-in-out infinite reverse;
|
||||
/* Custom Scrollbar */
|
||||
::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
}
|
||||
|
||||
.shape-mouse {
|
||||
width: 300px;
|
||||
height: 300px;
|
||||
background: radial-gradient(circle, rgba(139, 92, 246, 0.6) 0%, rgba(37, 99, 235, 0.1) 60%, transparent 100%);
|
||||
filter: blur(60px);
|
||||
top: -150px;
|
||||
left: -150px;
|
||||
opacity: 0;
|
||||
transition: opacity 0.5s ease;
|
||||
animation: pulse 4s ease-in-out infinite alternate;
|
||||
::-webkit-scrollbar-track {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
@keyframes float {
|
||||
0% { transform: translateY(0) scale(1) translateX(0); }
|
||||
33% { transform: translateY(40px) scale(1.05) translateX(20px); }
|
||||
66% { transform: translateY(-20px) scale(0.95) translateX(-20px); }
|
||||
100% { transform: translateY(0) scale(1) translateX(0); }
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: rgba(255, 255, 255, 0.3);
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
0% { opacity: 0.3; transform: scale(0.9); }
|
||||
100% { opacity: 0.6; transform: scale(1.1); }
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
background: rgba(255, 255, 255, 0.5);
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 900px;
|
||||
margin: 0 auto;
|
||||
padding: 0 1.5rem;
|
||||
.message {
|
||||
max-width: 85%;
|
||||
padding: 0.85rem 1.1rem;
|
||||
border-radius: 16px;
|
||||
line-height: 1.5;
|
||||
font-size: 0.95rem;
|
||||
box-shadow: 0 4px 15px rgba(0,0,0,0.05);
|
||||
animation: fadeIn 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275);
|
||||
}
|
||||
|
||||
header {
|
||||
background: var(--surface);
|
||||
backdrop-filter: blur(12px);
|
||||
-webkit-backdrop-filter: blur(12px);
|
||||
border-bottom: 1px solid var(--border);
|
||||
padding: 1.2rem 0;
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 50;
|
||||
transition: all 0.3s ease;
|
||||
@keyframes fadeIn {
|
||||
from { opacity: 0; transform: translateY(20px) scale(0.95); }
|
||||
to { opacity: 1; transform: translateY(0) scale(1); }
|
||||
}
|
||||
|
||||
nav {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
.message.visitor {
|
||||
align-self: flex-end;
|
||||
background: linear-gradient(135deg, #212529 0%, #343a40 100%);
|
||||
color: #fff;
|
||||
border-bottom-right-radius: 4px;
|
||||
}
|
||||
|
||||
.nav-links {
|
||||
display: flex;
|
||||
gap: 2rem;
|
||||
align-items: center;
|
||||
.message.bot {
|
||||
align-self: flex-start;
|
||||
background: #ffffff;
|
||||
color: #212529;
|
||||
border-bottom-left-radius: 4px;
|
||||
}
|
||||
|
||||
.nav-links a {
|
||||
text-decoration: none;
|
||||
color: var(--secondary);
|
||||
font-weight: 500;
|
||||
font-size: 0.95rem;
|
||||
transition: color 0.2s, transform 0.2s;
|
||||
.chat-input-area {
|
||||
padding: 1.25rem;
|
||||
background: rgba(255, 255, 255, 0.5);
|
||||
border-top: 1px solid rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.nav-links a:hover {
|
||||
color: var(--accent);
|
||||
transform: translateY(-1px);
|
||||
.chat-input-area form {
|
||||
display: flex;
|
||||
gap: 0.75rem;
|
||||
}
|
||||
|
||||
.logo {
|
||||
font-weight: 800;
|
||||
font-size: 1.5rem;
|
||||
background: var(--accent-gradient);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
text-decoration: none;
|
||||
letter-spacing: -0.5px;
|
||||
.chat-input-area input {
|
||||
flex: 1;
|
||||
border: 1px solid rgba(0, 0, 0, 0.1);
|
||||
border-radius: 12px;
|
||||
padding: 0.75rem 1rem;
|
||||
outline: none;
|
||||
background: rgba(255, 255, 255, 0.9);
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.hero {
|
||||
padding: 6rem 0 4rem;
|
||||
text-align: center;
|
||||
position: relative;
|
||||
.chat-input-area input:focus {
|
||||
border-color: #23a6d5;
|
||||
box-shadow: 0 0 0 3px rgba(35, 166, 213, 0.2);
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 3.5rem;
|
||||
font-weight: 800;
|
||||
letter-spacing: -0.03em;
|
||||
margin-bottom: 1.5rem;
|
||||
line-height: 1.1;
|
||||
.chat-input-area button {
|
||||
background: #212529;
|
||||
color: #fff;
|
||||
border: none;
|
||||
padding: 0.75rem 1.5rem;
|
||||
border-radius: 12px;
|
||||
cursor: pointer;
|
||||
font-weight: 600;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
color: var(--secondary);
|
||||
font-size: 1.2rem;
|
||||
max-width: 600px;
|
||||
margin: 0 auto;
|
||||
font-weight: 400;
|
||||
.chat-input-area button:hover {
|
||||
background: #000;
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 5px 15px rgba(0,0,0,0.2);
|
||||
}
|
||||
|
||||
.post-grid {
|
||||
display: grid;
|
||||
gap: 2.5rem;
|
||||
margin-bottom: 6rem;
|
||||
/* Background Animations */
|
||||
.bg-animations {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: 0;
|
||||
overflow: hidden;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.post-card {
|
||||
background: var(--surface-solid);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius-lg);
|
||||
overflow: hidden;
|
||||
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
text-decoration: none;
|
||||
color: inherit;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
box-shadow: var(--shadow-sm);
|
||||
.blob {
|
||||
position: absolute;
|
||||
width: 500px;
|
||||
height: 500px;
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
border-radius: 50%;
|
||||
filter: blur(80px);
|
||||
animation: move 20s infinite alternate cubic-bezier(0.45, 0, 0.55, 1);
|
||||
}
|
||||
|
||||
.post-card:hover {
|
||||
transform: translateY(-8px);
|
||||
box-shadow: var(--shadow-hover);
|
||||
border-color: rgba(37, 99, 235, 0.2);
|
||||
.blob-1 {
|
||||
top: -10%;
|
||||
left: -10%;
|
||||
background: rgba(238, 119, 82, 0.4);
|
||||
}
|
||||
|
||||
.post-card-image {
|
||||
height: 240px;
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
border-bottom: 1px solid var(--border);
|
||||
transition: transform 0.5s ease;
|
||||
.blob-2 {
|
||||
bottom: -10%;
|
||||
right: -10%;
|
||||
background: rgba(35, 166, 213, 0.4);
|
||||
animation-delay: -7s;
|
||||
width: 600px;
|
||||
height: 600px;
|
||||
}
|
||||
|
||||
.post-card:hover .post-card-image {
|
||||
transform: scale(1.02);
|
||||
.blob-3 {
|
||||
top: 40%;
|
||||
left: 30%;
|
||||
background: rgba(231, 60, 126, 0.3);
|
||||
animation-delay: -14s;
|
||||
width: 450px;
|
||||
height: 450px;
|
||||
}
|
||||
|
||||
.post-card-content {
|
||||
padding: 2rem;
|
||||
background: var(--surface-solid);
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
@keyframes move {
|
||||
0% { transform: translate(0, 0) rotate(0deg) scale(1); }
|
||||
33% { transform: translate(150px, 100px) rotate(120deg) scale(1.1); }
|
||||
66% { transform: translate(-50px, 200px) rotate(240deg) scale(0.9); }
|
||||
100% { transform: translate(0, 0) rotate(360deg) scale(1); }
|
||||
}
|
||||
|
||||
.post-card-date {
|
||||
font-size: 0.85rem;
|
||||
font-weight: 600;
|
||||
color: var(--accent);
|
||||
margin-bottom: 0.75rem;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em;
|
||||
.admin-link {
|
||||
font-size: 14px;
|
||||
color: #fff;
|
||||
text-decoration: none;
|
||||
background: rgba(0, 0, 0, 0.2);
|
||||
padding: 0.5rem 1rem;
|
||||
border-radius: 8px;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.post-card-title {
|
||||
font-size: 1.5rem;
|
||||
font-weight: 700;
|
||||
margin-bottom: 1rem;
|
||||
line-height: 1.3;
|
||||
.admin-link:hover {
|
||||
background: rgba(0, 0, 0, 0.4);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.post-card-summary {
|
||||
color: var(--secondary);
|
||||
font-size: 1rem;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 3;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
/* Admin Styles */
|
||||
.admin-container {
|
||||
max-width: 900px;
|
||||
margin: 3rem auto;
|
||||
padding: 2.5rem;
|
||||
background: rgba(255, 255, 255, 0.85);
|
||||
backdrop-filter: blur(20px);
|
||||
-webkit-backdrop-filter: blur(20px);
|
||||
border-radius: 24px;
|
||||
box-shadow: 0 20px 50px rgba(0,0,0,0.15);
|
||||
border: 1px solid rgba(255, 255, 255, 0.4);
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 0.75rem 1.5rem;
|
||||
border-radius: var(--radius-md);
|
||||
font-weight: 600;
|
||||
font-size: 0.95rem;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
border: 1px solid transparent;
|
||||
text-decoration: none;
|
||||
font-family: 'Outfit', sans-serif;
|
||||
letter-spacing: 0.02em;
|
||||
.admin-container h1 {
|
||||
margin-top: 0;
|
||||
color: #212529;
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background: var(--primary);
|
||||
color: #fff;
|
||||
box-shadow: 0 4px 12px rgba(15, 23, 42, 0.15);
|
||||
.table {
|
||||
width: 100%;
|
||||
border-collapse: separate;
|
||||
border-spacing: 0 8px;
|
||||
margin-top: 1.5rem;
|
||||
}
|
||||
|
||||
.btn-primary:hover {
|
||||
background: var(--accent);
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 6px 16px rgba(37, 99, 235, 0.25);
|
||||
color: #fff;
|
||||
.table th {
|
||||
background: transparent;
|
||||
border: none;
|
||||
padding: 1rem;
|
||||
color: #6c757d;
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
font-size: 0.75rem;
|
||||
letter-spacing: 1px;
|
||||
}
|
||||
|
||||
.btn-outline {
|
||||
border-color: var(--border);
|
||||
background: var(--surface-solid);
|
||||
color: var(--text-main);
|
||||
.table td {
|
||||
background: #fff;
|
||||
padding: 1rem;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.btn-outline:hover {
|
||||
background: var(--bg);
|
||||
border-color: var(--secondary);
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
footer {
|
||||
border-top: 1px solid var(--border);
|
||||
padding: 4rem 0;
|
||||
text-align: center;
|
||||
color: var(--text-muted);
|
||||
font-size: 0.95rem;
|
||||
background: var(--surface-solid);
|
||||
}
|
||||
|
||||
/* Post Detail */
|
||||
.post-detail {
|
||||
background-color: var(--surface-solid);
|
||||
}
|
||||
|
||||
.post-detail header {
|
||||
border: none;
|
||||
background: transparent;
|
||||
backdrop-filter: none;
|
||||
-webkit-backdrop-filter: none;
|
||||
padding: 2rem 0;
|
||||
position: static;
|
||||
}
|
||||
|
||||
.post-detail .hero-image {
|
||||
width: 100%;
|
||||
height: 40vh;
|
||||
min-height: 300px;
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
border-radius: var(--radius-lg);
|
||||
margin-bottom: 3rem;
|
||||
box-shadow: var(--shadow-lg);
|
||||
}
|
||||
|
||||
.post-detail h1 {
|
||||
font-size: 3rem;
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.post-content {
|
||||
font-size: 1.15rem;
|
||||
color: var(--secondary);
|
||||
line-height: 1.8;
|
||||
padding-bottom: 4rem;
|
||||
}
|
||||
|
||||
.post-content p {
|
||||
margin-bottom: 1.8rem;
|
||||
}
|
||||
|
||||
.post-content h2 {
|
||||
font-size: 2rem;
|
||||
margin-top: 3rem;
|
||||
margin-bottom: 1.2rem;
|
||||
color: var(--primary);
|
||||
}
|
||||
|
||||
.post-content img {
|
||||
border-radius: var(--radius-md);
|
||||
box-shadow: var(--shadow-sm);
|
||||
margin: 2rem 0;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
/* Admin */
|
||||
.admin-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-top: 3rem;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.admin-table {
|
||||
width: 100%;
|
||||
border-collapse: separate;
|
||||
border-spacing: 0;
|
||||
margin-top: 1rem;
|
||||
background: var(--surface-solid);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius-lg);
|
||||
overflow: hidden;
|
||||
box-shadow: var(--shadow-sm);
|
||||
}
|
||||
|
||||
.admin-table th, .admin-table td {
|
||||
padding: 1.25rem 1.5rem;
|
||||
text-align: left;
|
||||
border-bottom: 1px solid var(--border);
|
||||
}
|
||||
|
||||
.admin-table th {
|
||||
background: #f1f5f9;
|
||||
font-weight: 600;
|
||||
font-size: 0.85rem;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em;
|
||||
color: var(--secondary);
|
||||
}
|
||||
|
||||
.admin-table tr:last-child td {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.admin-table tbody tr {
|
||||
transition: background-color 0.2s;
|
||||
}
|
||||
|
||||
.admin-table tbody tr:hover {
|
||||
background-color: #f8fafc;
|
||||
}
|
||||
|
||||
.form-container {
|
||||
background: var(--surface-solid);
|
||||
padding: 2.5rem;
|
||||
border-radius: var(--radius-lg);
|
||||
box-shadow: var(--shadow-sm);
|
||||
border: 1px solid var(--border);
|
||||
margin-bottom: 4rem;
|
||||
}
|
||||
.table tr td:first-child { border-radius: 12px 0 0 12px; }
|
||||
.table tr td:last-child { border-radius: 0 12px 12px 0; }
|
||||
|
||||
.form-group {
|
||||
margin-bottom: 1.5rem;
|
||||
margin-bottom: 1.25rem;
|
||||
}
|
||||
|
||||
.form-group label {
|
||||
display: block;
|
||||
font-weight: 600;
|
||||
margin-bottom: 0.5rem;
|
||||
font-size: 0.95rem;
|
||||
color: var(--primary);
|
||||
display: block;
|
||||
margin-bottom: 0.5rem;
|
||||
font-weight: 600;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.form-control {
|
||||
width: 100%;
|
||||
padding: 0.8rem 1rem;
|
||||
border: 2px solid var(--border);
|
||||
border-radius: var(--radius-md);
|
||||
font-size: 1rem;
|
||||
font-family: inherit;
|
||||
box-sizing: border-box;
|
||||
transition: all 0.2s;
|
||||
background: #f8fafc;
|
||||
color: var(--text-main);
|
||||
width: 100%;
|
||||
padding: 0.75rem 1rem;
|
||||
border: 1px solid rgba(0, 0, 0, 0.1);
|
||||
border-radius: 12px;
|
||||
background: #fff;
|
||||
transition: all 0.3s ease;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.form-control:focus {
|
||||
outline: none;
|
||||
border-color: var(--accent);
|
||||
background: var(--surface-solid);
|
||||
box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.1);
|
||||
}
|
||||
|
||||
textarea.form-control {
|
||||
min-height: 250px;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.actions {
|
||||
display: flex;
|
||||
gap: 0.75rem;
|
||||
}
|
||||
|
||||
.toast {
|
||||
position: fixed;
|
||||
bottom: 2rem;
|
||||
right: 2rem;
|
||||
background: #10b981;
|
||||
color: #fff;
|
||||
padding: 1rem 1.5rem;
|
||||
border-radius: var(--radius-md);
|
||||
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1);
|
||||
display: none;
|
||||
z-index: 100;
|
||||
font-weight: 500;
|
||||
animation: slideUp 0.3s ease-out forwards;
|
||||
}
|
||||
|
||||
@keyframes slideUp {
|
||||
from { transform: translateY(100%); opacity: 0; }
|
||||
to { transform: translateY(0); opacity: 1; }
|
||||
}
|
||||
|
||||
@media (max-width: 640px) {
|
||||
h1 { font-size: 2.5rem; }
|
||||
.post-detail h1 { font-size: 2.2rem; }
|
||||
.nav-links { gap: 1rem; }
|
||||
.hero { padding: 4rem 0 3rem; }
|
||||
}
|
||||
|
||||
/* Quill Editor Overrides */
|
||||
.ql-toolbar.ql-snow {
|
||||
border: 2px solid var(--border);
|
||||
border-bottom: none;
|
||||
border-top-left-radius: var(--radius-md);
|
||||
border-top-right-radius: var(--radius-md);
|
||||
background: #f8fafc;
|
||||
font-family: 'Inter', sans-serif;
|
||||
padding: 12px;
|
||||
}
|
||||
.ql-container.ql-snow {
|
||||
border: 2px solid var(--border);
|
||||
border-bottom-left-radius: var(--radius-md);
|
||||
border-bottom-right-radius: var(--radius-md);
|
||||
background: var(--surface-solid);
|
||||
font-family: 'Inter', sans-serif;
|
||||
font-size: 1.1rem;
|
||||
transition: all 0.2s;
|
||||
min-height: 400px;
|
||||
}
|
||||
.ql-container.ql-snow:focus-within {
|
||||
border-color: var(--accent);
|
||||
box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.1);
|
||||
}
|
||||
.ql-editor {
|
||||
min-height: 400px;
|
||||
line-height: 1.8;
|
||||
color: var(--text-main);
|
||||
padding: 1.5rem;
|
||||
}
|
||||
.ql-editor p {
|
||||
margin-bottom: 1.2rem;
|
||||
}
|
||||
.ql-editor h2, .ql-editor h3, .ql-editor h4 {
|
||||
color: var(--primary);
|
||||
margin-top: 1.5rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
.ql-editor img {
|
||||
border-radius: var(--radius-md);
|
||||
box-shadow: var(--shadow-sm);
|
||||
max-width: 100%;
|
||||
}
|
||||
outline: none;
|
||||
border-color: #23a6d5;
|
||||
box-shadow: 0 0 0 3px rgba(35, 166, 213, 0.1);
|
||||
}
|
||||
@ -1,179 +1,39 @@
|
||||
// Main JS file for blog interactivity
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
// Show toast message from URL parameter if present
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
const msg = urlParams.get('msg');
|
||||
|
||||
if (msg) {
|
||||
showToast(msg);
|
||||
}
|
||||
});
|
||||
const chatForm = document.getElementById('chat-form');
|
||||
const chatInput = document.getElementById('chat-input');
|
||||
const chatMessages = document.getElementById('chat-messages');
|
||||
|
||||
function showToast(message) {
|
||||
const toast = document.createElement('div');
|
||||
toast.className = 'toast';
|
||||
toast.textContent = message;
|
||||
toast.style.display = 'block';
|
||||
document.body.appendChild(toast);
|
||||
|
||||
setTimeout(() => {
|
||||
toast.style.opacity = '0';
|
||||
toast.style.transition = 'opacity 0.5s';
|
||||
setTimeout(() => toast.remove(), 500);
|
||||
}, 3000);
|
||||
}
|
||||
const appendMessage = (text, sender) => {
|
||||
const msgDiv = document.createElement('div');
|
||||
msgDiv.classList.add('message', sender);
|
||||
msgDiv.textContent = text;
|
||||
chatMessages.appendChild(msgDiv);
|
||||
chatMessages.scrollTop = chatMessages.scrollHeight;
|
||||
};
|
||||
|
||||
// --- Interactive Background ---
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
initInteractiveBackground();
|
||||
});
|
||||
chatForm.addEventListener('submit', async (e) => {
|
||||
e.preventDefault();
|
||||
const message = chatInput.value.trim();
|
||||
if (!message) return;
|
||||
|
||||
function initInteractiveBackground() {
|
||||
// 1. Mouse Glow & Parallax shapes
|
||||
const mouseShape = document.querySelector('.shape-mouse');
|
||||
const bgShapes = document.querySelectorAll('.bg-shapes .shape:not(.shape-mouse)');
|
||||
|
||||
let mouseX = window.innerWidth / 2;
|
||||
let mouseY = window.innerHeight / 2;
|
||||
let currentX = mouseX;
|
||||
let currentY = mouseY;
|
||||
|
||||
// Parallax variables
|
||||
const windowCenterX = window.innerWidth / 2;
|
||||
const windowCenterY = window.innerHeight / 2;
|
||||
appendMessage(message, 'visitor');
|
||||
chatInput.value = '';
|
||||
|
||||
document.addEventListener('mousemove', (e) => {
|
||||
mouseX = e.clientX;
|
||||
mouseY = e.clientY;
|
||||
|
||||
// Make the mouse glow visible once mouse moves
|
||||
if (mouseShape && mouseShape.style.opacity === '0') {
|
||||
mouseShape.style.opacity = '1';
|
||||
}
|
||||
|
||||
// Simple parallax for the background blobs
|
||||
bgShapes.forEach(shape => {
|
||||
const speed = parseFloat(shape.getAttribute('data-speed') || 1);
|
||||
const x = (windowCenterX - mouseX) * speed * 0.05;
|
||||
const y = (windowCenterY - mouseY) * speed * 0.05;
|
||||
// Use translation combined with the existing animation (in CSS, we'll just layer it)
|
||||
// Note: Since css animation overrides transform, we can apply parallax translation
|
||||
// by updating margin or a nested div. Alternatively, since shape-1 and shape-2
|
||||
// have keyframes using transform, we shouldn't overwrite transform directly.
|
||||
// Let's use margin instead for a simple offset!
|
||||
shape.style.marginLeft = `${x}px`;
|
||||
shape.style.marginTop = `${y}px`;
|
||||
});
|
||||
});
|
||||
|
||||
// Smooth follow for the glowing orb
|
||||
function animateGlow() {
|
||||
if (mouseShape) {
|
||||
currentX += (mouseX - currentX) * 0.1;
|
||||
currentY += (mouseY - currentY) * 0.1;
|
||||
// Center the 300x300 shape on the cursor (so subtract half width/height)
|
||||
mouseShape.style.transform = `translate(${currentX}px, ${currentY}px)`;
|
||||
}
|
||||
requestAnimationFrame(animateGlow);
|
||||
}
|
||||
animateGlow();
|
||||
|
||||
// 2. Interactive Canvas Particles
|
||||
const canvas = document.getElementById('bg-canvas');
|
||||
if (!canvas) return;
|
||||
|
||||
const ctx = canvas.getContext('2d');
|
||||
let particles = [];
|
||||
|
||||
function resizeCanvas() {
|
||||
canvas.width = window.innerWidth;
|
||||
canvas.height = window.innerHeight;
|
||||
initParticles();
|
||||
}
|
||||
|
||||
class Particle {
|
||||
constructor() {
|
||||
this.x = Math.random() * canvas.width;
|
||||
this.y = Math.random() * canvas.height;
|
||||
this.size = Math.random() * 2 + 0.5;
|
||||
this.baseX = this.x;
|
||||
this.baseY = this.y;
|
||||
this.density = (Math.random() * 30) + 1;
|
||||
this.speedX = (Math.random() - 0.5) * 0.5;
|
||||
this.speedY = (Math.random() - 0.5) * 0.5;
|
||||
}
|
||||
|
||||
update() {
|
||||
this.x += this.speedX;
|
||||
this.y += this.speedY;
|
||||
|
||||
// Bounce off edges
|
||||
if (this.x > canvas.width || this.x < 0) this.speedX = -this.speedX;
|
||||
if (this.y > canvas.height || this.y < 0) this.speedY = -this.speedY;
|
||||
|
||||
// Mouse interaction (repel)
|
||||
let dx = mouseX - this.x;
|
||||
let dy = mouseY - this.y;
|
||||
let distance = Math.sqrt(dx * dx + dy * dy);
|
||||
let forceDirectionX = dx / distance;
|
||||
let forceDirectionY = dy / distance;
|
||||
let maxDistance = 150;
|
||||
let force = (maxDistance - distance) / maxDistance;
|
||||
let directionX = forceDirectionX * force * this.density;
|
||||
let directionY = forceDirectionY * force * this.density;
|
||||
|
||||
if (distance < maxDistance) {
|
||||
this.x -= directionX;
|
||||
this.y -= directionY;
|
||||
}
|
||||
}
|
||||
|
||||
draw() {
|
||||
ctx.fillStyle = 'rgba(37, 99, 235, 0.4)';
|
||||
ctx.beginPath();
|
||||
ctx.arc(this.x, this.y, this.size, 0, Math.PI * 2);
|
||||
ctx.closePath();
|
||||
ctx.fill();
|
||||
}
|
||||
}
|
||||
|
||||
function initParticles() {
|
||||
particles = [];
|
||||
let numParticles = Math.min((canvas.width * canvas.height) / 10000, 100); // cap at 100 for perf
|
||||
for (let i = 0; i < numParticles; i++) {
|
||||
particles.push(new Particle());
|
||||
}
|
||||
}
|
||||
|
||||
function animateParticles() {
|
||||
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||||
|
||||
for (let i = 0; i < particles.length; i++) {
|
||||
particles[i].update();
|
||||
particles[i].draw();
|
||||
try {
|
||||
const response = await fetch('api/chat.php', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ message })
|
||||
});
|
||||
const data = await response.json();
|
||||
|
||||
// Connect particles
|
||||
for (let j = i; j < particles.length; j++) {
|
||||
let dx = particles[i].x - particles[j].x;
|
||||
let dy = particles[i].y - particles[j].y;
|
||||
let distance = Math.sqrt(dx * dx + dy * dy);
|
||||
|
||||
if (distance < 120) {
|
||||
ctx.beginPath();
|
||||
ctx.strokeStyle = `rgba(37, 99, 235, ${0.1 * (1 - distance/120)})`;
|
||||
ctx.lineWidth = 1;
|
||||
ctx.moveTo(particles[i].x, particles[i].y);
|
||||
ctx.lineTo(particles[j].x, particles[j].y);
|
||||
ctx.stroke();
|
||||
ctx.closePath();
|
||||
}
|
||||
}
|
||||
// Artificial delay for realism
|
||||
setTimeout(() => {
|
||||
appendMessage(data.reply, 'bot');
|
||||
}, 500);
|
||||
} catch (error) {
|
||||
console.error('Error:', error);
|
||||
appendMessage("Sorry, something went wrong. Please try again.", 'bot');
|
||||
}
|
||||
requestAnimationFrame(animateParticles);
|
||||
}
|
||||
|
||||
window.addEventListener('resize', resizeCanvas);
|
||||
resizeCanvas();
|
||||
animateParticles();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
201
index.php
201
index.php
@ -1,91 +1,150 @@
|
||||
<?php
|
||||
require_once 'db/config.php';
|
||||
$db = db();
|
||||
$stmt = $db->query("SELECT * FROM posts ORDER BY created_at DESC");
|
||||
$posts = $stmt->fetchAll();
|
||||
declare(strict_types=1);
|
||||
@ini_set('display_errors', '1');
|
||||
@error_reporting(E_ALL);
|
||||
@date_default_timezone_set('UTC');
|
||||
|
||||
$projectName = $_SERVER['PROJECT_NAME'] ?? 'Our Blog';
|
||||
$projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Sharing updates and insights on design and technology.';
|
||||
$projectImageUrl = $_SERVER['PROJECT_IMAGE_URL'] ?? 'https://images.unsplash.com/photo-1499750310107-5fef28a66643?auto=format&fit=crop&q=80&w=1000';
|
||||
$phpVersion = PHP_VERSION;
|
||||
$now = date('Y-m-d H:i:s');
|
||||
?>
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title><?= htmlspecialchars($projectName) ?></title>
|
||||
<meta name="description" content="<?= htmlspecialchars($projectDescription) ?>" />
|
||||
<meta property="og:title" content="<?= htmlspecialchars($projectName) ?>" />
|
||||
<title>New Style</title>
|
||||
<?php
|
||||
// Read project preview data from environment
|
||||
$projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? '';
|
||||
$projectImageUrl = $_SERVER['PROJECT_IMAGE_URL'] ?? '';
|
||||
?>
|
||||
<?php if ($projectDescription): ?>
|
||||
<!-- Meta description -->
|
||||
<meta name="description" content='<?= htmlspecialchars($projectDescription) ?>' />
|
||||
<!-- Open Graph meta tags -->
|
||||
<meta property="og:description" content="<?= htmlspecialchars($projectDescription) ?>" />
|
||||
<meta property="og:image" content="<?= htmlspecialchars($projectImageUrl) ?>" />
|
||||
<meta property="twitter:card" content="summary_large_image" />
|
||||
<meta property="twitter:title" content="<?= htmlspecialchars($projectName) ?>" />
|
||||
<!-- Twitter meta tags -->
|
||||
<meta property="twitter:description" content="<?= htmlspecialchars($projectDescription) ?>" />
|
||||
<?php endif; ?>
|
||||
<?php if ($projectImageUrl): ?>
|
||||
<!-- Open Graph image -->
|
||||
<meta property="og:image" content="<?= htmlspecialchars($projectImageUrl) ?>" />
|
||||
<!-- Twitter image -->
|
||||
<meta property="twitter:image" content="<?= htmlspecialchars($projectImageUrl) ?>" />
|
||||
|
||||
<?php endif; ?>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=Outfit:wght@500;700;800&display=swap" rel="stylesheet">
|
||||
<link rel="stylesheet" href="assets/css/custom.css?v=<?= time() ?>">
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;700&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
:root {
|
||||
--bg-color-start: #6a11cb;
|
||||
--bg-color-end: #2575fc;
|
||||
--text-color: #ffffff;
|
||||
--card-bg-color: rgba(255, 255, 255, 0.01);
|
||||
--card-border-color: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: 'Inter', sans-serif;
|
||||
background: linear-gradient(45deg, var(--bg-color-start), var(--bg-color-end));
|
||||
color: var(--text-color);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
min-height: 100vh;
|
||||
text-align: center;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
}
|
||||
body::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-image: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" width="100" height="100" viewBox="0 0 100 100"><path d="M-10 10L110 10M10 -10L10 110" stroke-width="1" stroke="rgba(255,255,255,0.05)"/></svg>');
|
||||
animation: bg-pan 20s linear infinite;
|
||||
z-index: -1;
|
||||
}
|
||||
@keyframes bg-pan {
|
||||
0% { background-position: 0% 0%; }
|
||||
100% { background-position: 100% 100%; }
|
||||
}
|
||||
main {
|
||||
padding: 2rem;
|
||||
}
|
||||
.card {
|
||||
background: var(--card-bg-color);
|
||||
border: 1px solid var(--card-border-color);
|
||||
border-radius: 16px;
|
||||
padding: 2rem;
|
||||
backdrop-filter: blur(20px);
|
||||
-webkit-backdrop-filter: blur(20px);
|
||||
box-shadow: 0 8px 32px 0 rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
.loader {
|
||||
margin: 1.25rem auto 1.25rem;
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
border: 3px solid rgba(255, 255, 255, 0.25);
|
||||
border-top-color: #fff;
|
||||
border-radius: 50%;
|
||||
animation: spin 1s linear infinite;
|
||||
}
|
||||
@keyframes spin {
|
||||
from { transform: rotate(0deg); }
|
||||
to { transform: rotate(360deg); }
|
||||
}
|
||||
.hint {
|
||||
opacity: 0.9;
|
||||
}
|
||||
.sr-only {
|
||||
position: absolute;
|
||||
width: 1px; height: 1px;
|
||||
padding: 0; margin: -1px;
|
||||
overflow: hidden;
|
||||
clip: rect(0, 0, 0, 0);
|
||||
white-space: nowrap; border: 0;
|
||||
}
|
||||
h1 {
|
||||
font-size: 3rem;
|
||||
font-weight: 700;
|
||||
margin: 0 0 1rem;
|
||||
letter-spacing: -1px;
|
||||
}
|
||||
p {
|
||||
margin: 0.5rem 0;
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
code {
|
||||
background: rgba(0,0,0,0.2);
|
||||
padding: 2px 6px;
|
||||
border-radius: 4px;
|
||||
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
|
||||
}
|
||||
footer {
|
||||
position: absolute;
|
||||
bottom: 1rem;
|
||||
font-size: 0.8rem;
|
||||
opacity: 0.7;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="bg-shapes">
|
||||
<canvas id="bg-canvas"></canvas>
|
||||
<div class="shape shape-1" data-speed="2"></div>
|
||||
<div class="shape shape-2" data-speed="-1.5"></div>
|
||||
<div class="shape shape-mouse"></div>
|
||||
</div>
|
||||
|
||||
<header>
|
||||
<div class="container nav">
|
||||
<a href="index.php" class="logo"><?= htmlspecialchars($projectName) ?></a>
|
||||
<div class="nav-links">
|
||||
<a href="index.php">Home</a>
|
||||
<a href="admin/index.php">Admin</a>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main>
|
||||
<section class="hero">
|
||||
<div class="container">
|
||||
<h1>Welcome to <span style="background: var(--accent-gradient); -webkit-background-clip: text; -webkit-text-fill-color: transparent;"><?= htmlspecialchars($projectName) ?></span></h1>
|
||||
<p class="subtitle"><?= htmlspecialchars($projectDescription) ?></p>
|
||||
<div class="card">
|
||||
<h1>Analyzing your requirements and generating your website…</h1>
|
||||
<div class="loader" role="status" aria-live="polite" aria-label="Applying initial changes">
|
||||
<span class="sr-only">Loading…</span>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="container">
|
||||
<div class="post-grid">
|
||||
<?php foreach ($posts as $post): ?>
|
||||
<a href="post.php?slug=<?= htmlspecialchars($post['slug']) ?>" class="post-card">
|
||||
<?php if ($post['image_url']): ?>
|
||||
<div class="post-card-image" style="background-image: url('<?= htmlspecialchars($post['image_url']) ?>')"></div>
|
||||
<?php else: ?>
|
||||
<div class="post-card-image" style="background-image: url('https://images.unsplash.com/photo-1555066931-4365d14bab8c?auto=format&fit=crop&q=80&w=800')"></div>
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="post-card-content">
|
||||
<div class="post-card-date"><?= date('M j, Y', strtotime($post['created_at'])) ?></div>
|
||||
<h2 class="post-card-title"><?= htmlspecialchars($post['title']) ?></h2>
|
||||
<p class="post-card-summary"><?= htmlspecialchars($post['summary']) ?></p>
|
||||
</div>
|
||||
</a>
|
||||
<?php endforeach; ?>
|
||||
<?php if (empty($posts)): ?>
|
||||
<div style="text-align: center; padding: 4rem; background: var(--surface-solid); border-radius: var(--radius-lg); border: 1px dashed var(--border);">
|
||||
<h3>No posts yet.</h3>
|
||||
<p class="subtitle">Head to the Admin panel to write your first article.</p>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<footer>
|
||||
<div class="container">
|
||||
<p>© <?= date('Y') ?> <?= htmlspecialchars($projectName) ?>. Beautifully crafted with Flatlogic AI.</p>
|
||||
<p class="hint"><?= ($_SERVER['HTTP_HOST'] ?? '') === 'appwizzy.com' ? 'AppWizzy' : 'Flatlogic' ?> AI is collecting your requirements and applying the first changes.</p>
|
||||
<p class="hint">This page will update automatically as the plan is implemented.</p>
|
||||
<p>Runtime: PHP <code><?= htmlspecialchars($phpVersion) ?></code> — UTC <code><?= htmlspecialchars($now) ?></code></p>
|
||||
</div>
|
||||
</main>
|
||||
<footer>
|
||||
Page updated: <?= htmlspecialchars($now) ?> (UTC)
|
||||
</footer>
|
||||
<script src="assets/js/main.js?v=<?= time() ?>"></script>
|
||||
</body>
|
||||
</html>
|
||||
</html>
|
||||
|
||||
78
post.php
78
post.php
@ -1,78 +0,0 @@
|
||||
<?php
|
||||
require_once 'db/config.php';
|
||||
$slug = $_GET['slug'] ?? '';
|
||||
$db = db();
|
||||
$stmt = $db->prepare("SELECT * FROM posts WHERE slug = ?");
|
||||
$stmt->execute([$slug]);
|
||||
$post = $stmt->fetch();
|
||||
|
||||
if (!$post) {
|
||||
header("Location: index.php");
|
||||
exit;
|
||||
}
|
||||
|
||||
$projectName = $_SERVER['PROJECT_NAME'] ?? 'Our Blog';
|
||||
?>
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title><?= htmlspecialchars($post['title']) ?> | <?= htmlspecialchars($projectName) ?></title>
|
||||
<meta name="description" content="<?= htmlspecialchars($post['summary']) ?>" />
|
||||
<meta property="og:title" content="<?= htmlspecialchars($post['title']) ?> | <?= htmlspecialchars($projectName) ?>" />
|
||||
<meta property="og:description" content="<?= htmlspecialchars($post['summary']) ?>" />
|
||||
<meta property="og:image" content="<?= htmlspecialchars($post['image_url']) ?>" />
|
||||
<meta property="twitter:card" content="summary_large_image" />
|
||||
<meta property="twitter:title" content="<?= htmlspecialchars($post['title']) ?> | <?= htmlspecialchars($projectName) ?>" />
|
||||
<meta property="twitter:description" content="<?= htmlspecialchars($post['summary']) ?>" />
|
||||
<meta property="twitter:image" content="<?= htmlspecialchars($post['image_url']) ?>" />
|
||||
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=Outfit:wght@500;700;800&display=swap" rel="stylesheet">
|
||||
<link rel="stylesheet" href="assets/css/custom.css?v=<?= time() ?>">
|
||||
</head>
|
||||
<body class="post-detail">
|
||||
<div class="bg-shapes">
|
||||
<canvas id="bg-canvas"></canvas>
|
||||
<div class="shape shape-1" data-speed="2"></div>
|
||||
<div class="shape shape-2" data-speed="-1.5"></div>
|
||||
<div class="shape shape-mouse"></div>
|
||||
</div>
|
||||
<header>
|
||||
<div class="container nav">
|
||||
<a href="index.php" class="logo"><?= htmlspecialchars($projectName) ?></a>
|
||||
<div class="nav-links">
|
||||
<a href="index.php">Home</a>
|
||||
<a href="admin/index.php">Admin</a>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main>
|
||||
<article class="container">
|
||||
<div style="text-align: center; margin-bottom: 3rem; margin-top: 1rem;">
|
||||
<div class="post-card-date" style="font-size: 1rem; margin-bottom: 1rem;"><?= date('F j, Y', strtotime($post['created_at'])) ?></div>
|
||||
<h1><?= htmlspecialchars($post['title']) ?></h1>
|
||||
<p class="subtitle"><?= htmlspecialchars($post['summary']) ?></p>
|
||||
</div>
|
||||
|
||||
<?php if ($post['image_url']): ?>
|
||||
<div class="hero-image" style="background-image: url('<?= htmlspecialchars($post['image_url']) ?>');"></div>
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="post-content">
|
||||
<?= $post['content'] ?>
|
||||
</div>
|
||||
</article>
|
||||
</main>
|
||||
|
||||
<footer>
|
||||
<div class="container">
|
||||
<p>© <?= date('Y') ?> <?= htmlspecialchars($projectName) ?>. Built with Flatlogic AI.</p>
|
||||
</div>
|
||||
</footer>
|
||||
<script src="assets/js/main.js?v=<?= time() ?>"></script>
|
||||
</body>
|
||||
</html>
|
||||
Loading…
x
Reference in New Issue
Block a user