Compare commits

...

4 Commits

Author SHA1 Message Date
Flatlogic Bot
866e0c05b7 step 2025-11-19 15:16:54 +00:00
Flatlogic Bot
390ee703a3 simulator 2025-11-19 15:06:22 +00:00
Flatlogic Bot
f2fd1d30f2 second 2025-11-19 14:11:32 +00:00
Flatlogic Bot
e0d7dd4af9 first 2025-11-19 14:03:01 +00:00
18 changed files with 2066 additions and 146 deletions

67
admin/add.php Normal file
View File

@ -0,0 +1,67 @@
<?php
session_start();
if (!isset($_SESSION['loggedin']) || $_SESSION['loggedin'] !== true) {
header('Location: login.php');
exit;
}
require_once __DIR__ . '/../db/config.php';
if ($_SERVER['REQUEST_METHOD'] == 'POST') {
$title = $_POST['title'] ?? '';
$description = $_POST['description'] ?? '';
$image_url = $_POST['image_url'] ?? '';
if ($title) {
try {
$pdo = db();
if ($pdo) {
$sql = "INSERT INTO projects (title, description, image_url) VALUES (:title, :description, :image_url)";
$stmt = $pdo->prepare($sql);
$stmt->execute(['title' => $title, 'description' => $description, 'image_url' => $image_url]);
header('Location: index.php');
exit;
}
} catch (PDOException $e) {
error_log("Database error: " . $e->getMessage());
}
}
}
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Add Project</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="../assets/css/custom.css">
</head>
<body class="dark-theme">
<div class="container mt-5">
<h1>Add New Project</h1>
<div class="card portfolio-item">
<div class="card-body">
<form action="add.php" method="POST">
<div class="mb-3">
<label for="title" class="form-label">Title</label>
<input type="text" class="form-control" id="title" name="title" required>
</div>
<div class="mb-3">
<label for="description" class="form-label">Description</label>
<textarea class="form-control" id="description" name="description" rows="5"></textarea>
</div>
<div class="mb-3">
<label for="image_url" class="form-label">Image URL</label>
<input type="text" class="form-control" id="image_url" name="image_url">
</div>
<button type="submit" class="btn btn-accent">Add Project</button>
<a href="index.php" class="btn btn-secondary">Cancel</a>
</form>
</div>
</div>
</div>
</body>
</html>

26
admin/delete.php Normal file
View File

@ -0,0 +1,26 @@
<?php
session_start();
if (!isset($_SESSION['loggedin']) || $_SESSION['loggedin'] !== true) {
header('Location: login.php');
exit;
}
require_once __DIR__ . '/../db/config.php';
$id = $_GET['id'] ?? null;
if ($id) {
try {
$pdo = db();
if ($pdo) {
$sql = "DELETE FROM projects WHERE id = :id";
$stmt = $pdo->prepare($sql);
$stmt->execute(['id' => $id]);
}
} catch (PDOException $e) {
error_log("Database error: " . $e->getMessage());
}
}
header('Location: index.php');
exit;

90
admin/edit.php Normal file
View File

@ -0,0 +1,90 @@
<?php
session_start();
if (!isset($_SESSION['loggedin']) || $_SESSION['loggedin'] !== true) {
header('Location: login.php');
exit;
}
require_once __DIR__ . '/../db/config.php';
$id = $_GET['id'] ?? null;
if (!$id) {
header('Location: index.php');
exit;
}
$project = null;
try {
$pdo = db();
if ($pdo) {
$stmt = $pdo->prepare("SELECT * FROM projects WHERE id = :id");
$stmt->execute(['id' => $id]);
$project = $stmt->fetch(PDO::FETCH_ASSOC);
}
} catch (PDOException $e) {
error_log("Database error: " . $e->getMessage());
}
if (!$project) {
header('Location: index.php');
exit;
}
if ($_SERVER['REQUEST_METHOD'] == 'POST') {
$title = $_POST['title'] ?? '';
$description = $_POST['description'] ?? '';
$image_url = $_POST['image_url'] ?? '';
if ($title) {
try {
$pdo = db();
if ($pdo) {
$sql = "UPDATE projects SET title = :title, description = :description, image_url = :image_url WHERE id = :id";
$stmt = $pdo->prepare($sql);
$stmt->execute(['title' => $title, 'description' => $description, 'image_url' => $image_url, 'id' => $id]);
header('Location: index.php');
exit;
}
} catch (PDOException $e) {
error_log("Database error: " . $e->getMessage());
}
}
}
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Edit Project</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="../assets/css/custom.css">
</head>
<body class="dark-theme">
<div class="container mt-5">
<h1>Edit Project</h1>
<div class="card portfolio-item">
<div class="card-body">
<form action="edit.php?id=<?php echo $id; ?>" method="POST">
<div class="mb-3">
<label for="title" class="form-label">Title</label>
<input type="text" class="form-control" id="title" name="title" value="<?php echo htmlspecialchars($project['title']); ?>" required>
</div>
<div class="mb-3">
<label for="description" class="form-label">Description</label>
<textarea class="form-control" id="description" name="description" rows="5"><?php echo htmlspecialchars($project['description']); ?></textarea>
</div>
<div class="mb-3">
<label for="image_url" class="form-label">Image URL</label>
<input type="text" class="form-control" id="image_url" name="image_url" value="<?php echo htmlspecialchars($project['image_url']); ?>">
</div>
<button type="submit" class="btn btn-accent">Update Project</button>
<a href="index.php" class="btn btn-secondary">Cancel</a>
</form>
</div>
</div>
</div>
</body>
</html>

77
admin/index.php Normal file
View File

@ -0,0 +1,77 @@
<?php
session_start();
// If not logged in, redirect to login page
if (!isset($_SESSION['loggedin']) || $_SESSION['loggedin'] !== true) {
header('Location: login.php');
exit;
}
require_once __DIR__ . '/../db/config.php';
$projects = [];
try {
$pdo = db();
if ($pdo) {
$stmt = $pdo->query("SELECT * FROM projects ORDER BY created_at DESC");
if ($stmt) {
$projects = $stmt->fetchAll(PDO::FETCH_ASSOC);
}
}
} catch (PDOException $e) {
error_log("Database error: " . $e->getMessage());
}
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Admin Dashboard</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="../assets/css/custom.css">
</head>
<body class="dark-theme">
<div class="container mt-5">
<div class="d-flex justify-content-between align-items-center mb-4">
<h1>Admin Dashboard</h1>
<a href="logout.php" class="btn btn-danger">Logout</a>
</div>
<div class="card portfolio-item">
<div class="card-body">
<h2 class="card-title">Projects</h2>
<a href="add.php" class="btn btn-accent mb-3">Add New Project</a>
<table class="table table-dark table-striped">
<thead>
<tr>
<th>Title</th>
<th>Description</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
<?php if (empty($projects)): ?>
<tr>
<td colspan="3" class="text-center">No projects found.</td>
</tr>
<?php else: ?>
<?php foreach ($projects as $project): ?>
<tr>
<td><?php echo htmlspecialchars($project['title']); ?></td>
<td><?php echo htmlspecialchars(substr($project['description'], 0, 100)); ?>...</td>
<td>
<a href="edit.php?id=<?php echo $project['id']; ?>" class="btn btn-sm btn-primary">Edit</a>
<a href="delete.php?id=<?php echo $project['id']; ?>" class="btn btn-sm btn-danger" onclick="return confirm('Are you sure?')">Delete</a>
</td>
</tr>
<?php endforeach; ?>
<?php endif; ?>
</tbody>
</table>
</div>
</div>
</div>
</body>
</html>

58
admin/login.php Normal file
View File

@ -0,0 +1,58 @@
<?php
session_start();
// If already logged in, redirect to admin dashboard
if (isset($_SESSION['loggedin']) && $_SESSION['loggedin'] === true) {
header('Location: index.php');
exit;
}
$login_error = '';
$password = 'password123'; // Hardcoded password - NOT FOR PRODUCTION
if ($_SERVER['REQUEST_METHOD'] == 'POST') {
if (isset($_POST['password']) && $_POST['password'] == $password) {
$_SESSION['loggedin'] = true;
header('Location: index.php');
exit;
} else {
$login_error = 'Invalid password.';
}
}
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Admin Login</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="../assets/css/custom.css">
</head>
<body class="dark-theme">
<div class="container">
<div class="row justify-content-center align-items-center" style="height: 100vh;">
<div class="col-md-4">
<div class="card portfolio-item">
<div class="card-body">
<h3 class="card-title text-center">Admin Login</h3>
<form action="login.php" method="POST">
<div class="mb-3">
<label for="password" class="form-label">Password</label>
<input type="password" class="form-control" id="password" name="password" required>
</div>
<?php if ($login_error): ?>
<div class="alert alert-danger"><?php echo $login_error; ?></div>
<?php endif; ?>
<div class="d-grid">
<button type="submit" class="btn btn-accent">Login</button>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
</body>
</html>

6
admin/logout.php Normal file
View File

@ -0,0 +1,6 @@
<?php
session_start();
session_unset();
session_destroy();
header('Location: login.php');
exit;

252
assets/css/custom.css Normal file
View File

@ -0,0 +1,252 @@
:root {
--bg-color: #121212;
--surface-color: #1E1E1E;
--primary-text-color: #EAEAEA;
--secondary-text-color: #B3B3B3;
--accent-color: #3E8BFF;
--accent-color-hover: #5A9BFF;
--border-color: rgba(255, 255, 255, 0.1);
}
body.dark-theme {
background-color: var(--bg-color);
color: var(--primary-text-color);
font-family: 'Poppins', sans-serif;
}
/* Particles.js container */
#particles-js {
position: fixed;
width: 100%;
height: 100%;
top: 0;
left: 0;
z-index: -1; /* Place it behind all content */
}
/* --- Header & Nav --- */
header.sticky-top .navbar {
background-color: rgba(30, 30, 30, 0.5); /* More transparency */
backdrop-filter: blur(12px);
-webkit-backdrop-filter: blur(12px);
border-bottom: 1px solid rgba(255, 255, 255, 0.1); /* Subtle border */
}
.navbar-brand, .nav-link {
color: var(--primary-text-color) !important;
font-weight: 400;
transition: color 0.3s ease;
}
.nav-link:hover, .navbar-brand:hover {
color: var(--accent-color) !important;
}
.navbar-toggler {
border-color: var(--border-color);
}
.navbar-toggler-icon {
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='rgba(234, 234, 234, 0.8)' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e");
}
/* --- Hero Section --- */
.hero {
padding: 8rem 0;
background: linear-gradient(135deg, var(--bg-color) 0%, #1a1a1a 100%);
border-bottom: 1px solid var(--border-color);
}
.hero h1 {
font-size: 3.5rem;
font-weight: 700;
color: #fff;
}
.hero p.lead {
color: var(--secondary-text-color);
font-size: 1.25rem;
}
/* --- General Section --- */
.section {
padding: 5rem 0;
}
.section-title {
font-weight: 700;
margin-bottom: 3rem;
color: #fff;
}
/* --- Buttons --- */
.btn-accent {
background-color: var(--accent-color);
color: #fff;
border: none;
padding: 0.75rem 1.5rem;
font-weight: 600;
transition: background-color 0.3s ease, transform 0.2s ease;
}
.btn-accent:hover {
background-color: var(--accent-color-hover);
color: #fff;
transform: translateY(-2px);
}
.btn-outline-accent {
color: var(--accent-color);
border-color: var(--accent-color);
font-weight: 400;
transition: background-color 0.3s ease, color 0.3s ease;
}
.btn-outline-accent:hover {
background-color: var(--accent-color);
color: #fff;
}
/* --- About Section --- */
.profile-pic {
border: 5px solid var(--surface-color);
box-shadow: 0 0 20px rgba(0,0,0,0.5);
}
/* --- Portfolio Section --- */
.portfolio-item.card {
background-color: rgba(30, 30, 30, 0.5);
backdrop-filter: blur(12px);
-webkit-backdrop-filter: blur(12px);
border: 1px solid var(--border-color);
border-radius: 0.5rem;
transition: transform 0.3s ease, box-shadow 0.3s ease;
overflow: hidden;
}
.portfolio-item.card:hover {
transform: translateY(-5px);
box-shadow: 0 10px 20px rgba(0, 0, 0, 0.2);
}
.card-title {
color: var(--primary-text-color);
}
.card-text {
color: var(--secondary-text-color);
}
.card-footer {
background-color: transparent;
border-top: 1px solid var(--border-color);
}
/* --- Contact Form --- */
.form-control {
background-color: var(--surface-color);
border: 1px solid var(--border-color);
color: var(--primary-text-color);
padding: 0.75rem 1rem;
}
.form-control:focus {
background-color: var(--surface-color);
border-color: var(--accent-color);
color: var(--primary-text-color);
box-shadow: 0 0 0 0.25rem rgba(62, 139, 255, 0.25);
}
.form-label {
color: var(--secondary-text-color);
}
.form-control::placeholder {
color: #6c757d;
}
/* --- Footer --- */
footer {
background-color: var(--surface-color);
color: var(--secondary-text-color);
border-top: 1px solid var(--border-color);
}
/* --- AI Chat --- */
.ai-response {
background-color: var(--surface-color);
border: 1px solid var(--border-color);
border-radius: 0.25rem;
padding: 1rem;
margin-top: 1rem;
}
.ai-response pre {
white-space: pre-wrap;
word-wrap: break-word;
color: var(--primary-text-color);
margin: 0;
}
/* Tools Page */
.tool-card {
background-color: rgba(30, 30, 30, 0.5);
backdrop-filter: blur(12px);
-webkit-backdrop-filter: blur(12px);
border: 1px solid var(--border-color);
border-radius: 0.75rem;
padding: 2rem;
color: var(--primary-text-color);
margin-bottom: 2rem;
transition: transform 0.3s ease, box-shadow 0.3s ease;
}
.tool-card:hover {
transform: translateY(-5px);
box-shadow: 0 10px 20px rgba(0, 0, 0, 0.2);
}
.tool-card h2, .tool-card h3, .tool-card h4, .tool-card h5 {
color: var(--primary-text-color);
font-weight: 600;
}
.tool-card p {
color: var(--secondary-text-color);
}
.tool-card .form-label,
.tool-card label {
color: var(--secondary-text-color);
}
.tool-card .form-control {
background-color: var(--surface-color);
border-color: var(--border-color);
color: var(--primary-text-color);
}
.tool-card .form-control:focus {
background-color: #2a2a2a;
border-color: var(--accent-color);
box-shadow: none;
color: #fff;
}
.user-prompt,
.ai-response {
background: rgba(0, 0, 0, 0.2);
padding: 1rem;
border-radius: 8px;
margin-top: 1.5rem;
border: 1px solid var(--border-color);
}
.user-prompt strong,
.ai-response strong {
color: var(--accent-color);
display: block;
margin-bottom: 0.5rem;
}

47
assets/js/main.js Normal file
View File

@ -0,0 +1,47 @@
// Bootstrap form validation
(() => {
'use strict'
const forms = document.querySelectorAll('.needs-validation')
Array.from(forms).forEach(form => {
form.addEventListener('submit', event => {
if (!form.checkValidity()) {
event.preventDefault()
event.stopPropagation()
}
form.classList.add('was-validated')
}, false)
})
})();
// Show toast notification for form submission status
document.addEventListener('DOMContentLoaded', () => {
const urlParams = new URLSearchParams(window.location.search);
const status = urlParams.get('status');
const message = urlParams.get('message');
if (status) {
const toastContainer = document.getElementById('toast-container');
const toastHTML = `
<div class="toast align-items-center text-white ${status === 'success' ? 'bg-success' : 'bg-danger'} border-0" role="alert" aria-live="assertive" aria-atomic="true">
<div class="d-flex">
<div class="toast-body">
${message || (status === 'success' ? 'Your message has been sent successfully!' : 'An error occurred. Please try again.')}
</div>
<button type="button" class="btn-close btn-close-white me-2 m-auto" data-bs-dismiss="toast" aria-label="Close"></button>
</div>
</div>
`;
toastContainer.innerHTML = toastHTML;
const toastEl = toastContainer.querySelector('.toast');
const toast = new bootstrap.Toast(toastEl, { delay: 5000 });
toast.show();
}
});
// Initialize Particles.js
particlesJS.load('particles-js', 'assets/particles.json', function() {
console.log('particles.js config loaded');
});

184
assets/js/simulator.js Normal file
View File

@ -0,0 +1,184 @@
document.addEventListener('DOMContentLoaded', () => {
const canvas = document.getElementById('simulationCanvas');
if (!canvas) return;
const ctx = canvas.getContext('2d');
const launchButton = document.getElementById('launchButton');
const accelerationInput = document.getElementById('acceleration');
const burnTimeInput = document.getElementById('burnTime');
const angleInput = document.getElementById('angle');
const resultMessage = document.getElementById('result-message');
// --- Параметры симуляции ---
const GRAVITY = 9.8;
const PIXELS_PER_METER = 0.01;
const TIME_STEP = 1.0;
let rocket = {};
let simulationState = {};
function initializeState() {
return {
animationFrameId: null,
elapsedTime: 0,
acceleration: 0,
burnTime: 0,
angleRad: 0,
};
}
function initializeRocket() {
return {
x: 30,
y: canvas.height - 45,
width: 10,
height: 25,
x_meters: 0,
y_meters: 0,
vx: 0, // Скорость по X в м/с
vy: 0, // Скорость по Y в м/с
};
}
function drawEarth() {
ctx.fillStyle = '#2c5c23';
ctx.beginPath();
ctx.arc(canvas.width / 2, canvas.height + canvas.height - 50, canvas.height, 0, Math.PI, true);
ctx.fill();
}
function drawRocket() {
ctx.save();
ctx.translate(rocket.x, rocket.y);
// Угол поворота ракеты зависит от вектора скорости
// Но до старта (скорость 0) она стоит вертикально
const angle = (rocket.vx === 0 && rocket.vy === 0)
? -Math.PI / 2
: Math.atan2(rocket.vy, rocket.vx);
// Поворачиваем ракету. Наша модель "смотрит" вправо, поэтому доп. вращение не нужно.
ctx.rotate(angle);
// Рисуем пламя, если двигатель работает
if (simulationState.elapsedTime > 0 && simulationState.elapsedTime <= simulationState.burnTime) {
ctx.fillStyle = `rgba(255, ${Math.random() * 150 + 100}, 0, 0.8)`;
ctx.beginPath();
const flameLength = rocket.height * (1.5 + Math.random() * 0.5);
ctx.moveTo(-rocket.width / 2, 0);
ctx.lineTo(-rocket.width / 2 - flameLength, rocket.width / 2);
ctx.lineTo(-rocket.width / 2 - flameLength, -rocket.width / 2);
ctx.closePath();
ctx.fill();
}
// Корпус ракеты (перерисовываем, чтобы был поверх пламени)
// Модель ракеты теперь "смотрит" вправо (по оси X)
ctx.fillStyle = '#d0d0d0';
ctx.beginPath();
ctx.moveTo(rocket.height / 2, 0);
ctx.lineTo(-rocket.height / 2, -rocket.width / 2);
ctx.lineTo(-rocket.height / 2, rocket.width / 2);
ctx.closePath();
ctx.fill();
ctx.restore();
}
function resetSimulation() {
if (simulationState.animationFrameId) {
cancelAnimationFrame(simulationState.animationFrameId);
}
simulationState = initializeState();
rocket = initializeRocket();
resultMessage.innerHTML = '&nbsp;';
resultMessage.className = 'alert alert-secondary';
clearCanvas();
drawEarth();
drawRocket();
}
function clearCanvas() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
}
function animate() {
simulationState.elapsedTime += TIME_STEP;
// 1. Обновляем физику
// Если двигатель работает, применяем ускорение
if (simulationState.elapsedTime <= simulationState.burnTime) {
const currentAccelerationX = simulationState.acceleration * Math.cos(simulationState.angleRad);
const currentAccelerationY = simulationState.acceleration * Math.sin(simulationState.angleRad);
rocket.vx += currentAccelerationX * TIME_STEP;
rocket.vy += currentAccelerationY * TIME_STEP;
}
// Всегда применяем гравитацию (она действует на ось Y)
rocket.vy -= GRAVITY * TIME_STEP;
// Обновляем позицию в метрах
rocket.x_meters += rocket.vx * TIME_STEP;
rocket.y_meters += rocket.vy * TIME_STEP;
// 2. Обновляем позицию для отрисовки в пикселях
const groundY = canvas.height - 45;
rocket.x = 30 + (rocket.x_meters * PIXELS_PER_METER);
rocket.y = groundY - (rocket.y_meters * PIXELS_PER_METER);
// 3. Отрисовываем сцену
clearCanvas();
drawEarth();
drawRocket();
// 4. Проверяем условия завершения
const hasCrashed = rocket.y_meters < 0 && simulationState.elapsedTime > 1;
const outOfBounds = rocket.x > canvas.width + rocket.height;
if (hasCrashed || outOfBounds) {
rocket.y_meters = 0;
rocket.y = groundY;
clearCanvas();
drawEarth();
drawRocket();
resultMessage.innerHTML = outOfBounds
? "<strong>Неудача.</strong> Ракета улетела за пределы видимости."
: "<strong>Неудача.</strong> Ракета упала обратно на Землю.";
resultMessage.className = 'alert alert-danger';
cancelAnimationFrame(simulationState.animationFrameId);
simulationState.animationFrameId = null;
return;
}
simulationState.animationFrameId = requestAnimationFrame(animate);
}
launchButton.addEventListener('click', () => {
const accelValue = parseFloat(accelerationInput.value);
const burnTimeValue = parseFloat(burnTimeInput.value);
const angleValue = parseFloat(angleInput.value);
if (isNaN(accelValue) || isNaN(burnTimeValue) || isNaN(angleValue) || accelValue <= 0 || burnTimeValue <= 0 || angleValue < 0 || angleValue > 90) {
resultMessage.innerHTML = 'Пожалуйста, введите корректные значения (ускорение > 0, время > 0, угол 0-90).';
resultMessage.className = 'alert alert-warning';
return;
}
resetSimulation();
simulationState.acceleration = accelValue;
simulationState.burnTime = burnTimeValue;
simulationState.angleRad = angleValue * (Math.PI / 180);
resultMessage.innerHTML = "Запуск...";
resultMessage.className = 'alert alert-info';
if (!simulationState.animationFrameId) {
simulationState.animationFrameId = requestAnimationFrame(animate);
}
});
resetSimulation();
});

110
assets/particles.json Normal file
View File

@ -0,0 +1,110 @@
{
"particles": {
"number": {
"value": 80,
"density": {
"enable": true,
"value_area": 800
}
},
"color": {
"value": "#ffffff"
},
"shape": {
"type": "circle",
"stroke": {
"width": 0,
"color": "#000000"
},
"polygon": {
"nb_sides": 5
},
"image": {
"src": "img/github.svg",
"width": 100,
"height": 100
}
},
"opacity": {
"value": 0.5,
"random": false,
"anim": {
"enable": false,
"speed": 1,
"opacity_min": 0.1,
"sync": false
}
},
"size": {
"value": 3,
"random": true,
"anim": {
"enable": false,
"speed": 40,
"size_min": 0.1,
"sync": false
}
},
"line_linked": {
"enable": true,
"distance": 150,
"color": "#ffffff",
"opacity": 0.4,
"width": 1
},
"move": {
"enable": true,
"speed": 6,
"direction": "none",
"random": false,
"straight": false,
"out_mode": "out",
"bounce": false,
"attract": {
"enable": false,
"rotateX": 600,
"rotateY": 1200
}
}
},
"interactivity": {
"detect_on": "canvas",
"events": {
"onhover": {
"enable": true,
"mode": "repulse"
},
"onclick": {
"enable": true,
"mode": "push"
},
"resize": true
},
"modes": {
"grab": {
"distance": 400,
"line_linked": {
"opacity": 1
}
},
"bubble": {
"distance": 400,
"size": 40,
"duration": 2,
"opacity": 8,
"speed": 3
},
"repulse": {
"distance": 200,
"duration": 0.4
},
"push": {
"particles_nb": 4
},
"remove": {
"particles_nb": 2
}
}
},
"retina_detect": true
}

38
contact.php Normal file
View File

@ -0,0 +1,38 @@
<?php
// Basic security: check if it's a POST request
if ($_SERVER["REQUEST_METHOD"] !== "POST") {
header("Location: index.php?status=error&message=Invalid request method.");
exit;
}
require_once __DIR__ . '/mail/MailService.php';
// Sanitize and validate inputs
$name = filter_var(trim($_POST['name'] ?? ''), FILTER_SANITIZE_STRING);
$email = filter_var(trim($_POST['email'] ?? ''), FILTER_SANITIZE_EMAIL);
$message = filter_var(trim($_POST['message'] ?? ''), FILTER_SANITIZE_STRING);
if (empty($name) || !filter_var($email, FILTER_VALIDATE_EMAIL) || empty($message)) {
header("Location: index.php?status=error&message=Please fill out all fields correctly.");
exit;
}
// Use MailService to send the email
// The recipient is determined by the MAIL_TO environment variable in .env
// The user's email is set as the Reply-To address.
$res = MailService::sendContactMessage(
$name,
$email,
$message,
null, // Use default recipient from config
'New Contact Form Submission'
);
if (!empty($res['success'])) {
header("Location: index.php?status=success&message=Your message has been sent successfully!");
exit;
} else {
// In a real app, you would log the detailed error: error_log('MailService Error: ' . $res['error']);
header("Location: index.php?status=error&message=Sorry, there was an error sending your message.");
exit;
}

View File

@ -0,0 +1,7 @@
CREATE TABLE IF NOT EXISTS projects (
id INT AUTO_INCREMENT PRIMARY KEY,
title VARCHAR(255) NOT NULL,
description TEXT,
image_url VARCHAR(255),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

328
index.php
View File

@ -1,150 +1,186 @@
<?php
declare(strict_types=1);
@ini_set('display_errors', '1');
@error_reporting(E_ALL);
@date_default_timezone_set('UTC');
$phpVersion = PHP_VERSION;
$now = date('Y-m-d H:i:s');
?>
<!doctype html>
<html lang="en">
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<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) ?>" />
<!-- 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;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>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<!-- SEO and Meta Tags -->
<title>Имя Фамилия | Личное Портфолио</title>
<meta name="description" content="Личный сайт-портфолио для демонстрации работ и получения контактных запросов.">
<meta name="keywords" content="личное портфолио, веб-разработчик, дизайнер, форма обратной связи, проекты, резюме, flatlogic, php, lamp">
<!-- Open Graph / Facebook -->
<meta property="og:type" content="website">
<meta property="og:title" content="Имя Фамилия | Личное Портфолио">
<meta property="og:description" content="Личный сайт-портфолио для демонстрации работ и получения контактных запросов.">
<meta property="og:image" content="<?php echo htmlspecialchars($_SERVER['PROJECT_IMAGE_URL'] ?? ''); ?>">
<!-- Twitter -->
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:title" content="Имя Фамилия | Личное Портфолио">
<meta name="twitter:description" content="Личный сайт-портфолио для демонстрации работ и получения контактных запросов.">
<meta name="twitter:image" content="<?php echo htmlspecialchars($_SERVER['PROJECT_IMAGE_URL'] ?? ''); ?>">
<!-- Google Fonts: Poppins -->
<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=Poppins:wght@300;400;600;700&display=swap" rel="stylesheet">
<!-- Bootstrap CSS -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
<!-- Bootstrap Icons -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css">
<!-- Custom CSS -->
<link rel="stylesheet" href="assets/css/custom.css?v=<?php echo time(); ?>">
</head>
<body>
<main>
<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>
<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>
<body class="dark-theme">
<!-- Particles.js container -->
<div id="particles-js"></div>
<!-- Toast container for notifications -->
<div id="toast-container" class="position-fixed top-0 end-0 p-3" style="z-index: 1100"></div>
<!-- Header -->
<header class="sticky-top">
<nav class="navbar navbar-expand-lg">
<div class="container">
<a class="navbar-brand fw-bold" href="#">Имя Фамилия</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav ms-auto">
<li class="nav-item"><a class="nav-link" href="#about">Обо мне</a></li>
<li class="nav-item"><a class="nav-link" href="projects.php">Портфолио</a></li>
<li class="nav-item"><a class="nav-link" href="#contact">Контакт</a></li>
<li class="nav-item"><a class="nav-link" href="/admin">Админка</a></li>
<li class="nav-item"><a class="nav-link" href="tools.php">Инструменты</a></li>
<li class="nav-item"><a class="nav-link" href="simulator.php">Симулятор</a></li>
</ul>
</div>
</div>
</nav>
</header>
<main>
<!-- Hero Section -->
<section class="hero text-center">
<div class="container">
<h1 class="display-4">Креативный разработчик и дизайнер</h1>
<p class="lead">Я создаю красивые и функциональные сайты. Добро пожаловать в мое личное пространство в сети.</p>
<a href="#contact" class="btn btn-accent btn-lg mt-3">Связаться</a>
</div>
</section>
<!-- About Section -->
<section id="about" class="section">
<div class="container">
<h2 class="text-center section-title">Обо мне</h2>
<div class="row align-items-center">
<div class="col-md-4 text-center">
<img src="https://i.pravatar.cc/300?u=a042581f4e29026704d" class="img-fluid rounded-circle mb-4 mb-md-0 profile-pic" alt="Портрет">
</div>
<div class="col-md-8">
<p>Привет! Я увлеченный разработчик со склонностью к созданию элегантных решений в кратчайшие сроки. Я люблю чистый код, хороший дизайн и чашку кофе. Мой опыт охватывает различные технологии, что позволяет мне решать разнообразные задачи.</p>
<p>Этот сайт демонстрация моего пути и навыков. Он построен на классическом стеке LAMP, демонстрируя мощность и гибкость этой надежной технологии. Не стесняйтесь осмотреться и написать мне, если хотите сотрудничать!</p>
</div>
</div>
</div>
</section>
<?php
require_once __DIR__ . '/db/config.php';
$projects = [];
try {
$pdo = db();
if ($pdo) {
$stmt = $pdo->query("SELECT * FROM projects ORDER BY created_at DESC");
if ($stmt) {
$projects = $stmt->fetchAll(PDO::FETCH_ASSOC);
}
}
} catch (PDOException $e) {
// Log error or handle it gracefully
error_log("Database error: " . $e->getMessage());
}
?>
<!-- Portfolio Section -->
<section id="portfolio" class="section">
<div class="container">
<h2 class="text-center section-title">Мои работы</h2>
<div class="row g-4">
<?php if (empty($projects)): ?>
<div class="col">
<p class="text-center">Пока нет проектов для отображения. Зайдите позже!</p>
</div>
<?php else: ?>
<?php foreach ($projects as $project): ?>
<div class="col-md-6 col-lg-4">
<div class="card portfolio-item">
<img src="<?php echo htmlspecialchars($project['image_url'] ?: 'https://picsum.photos/seed/'.htmlspecialchars($project['id']).'/400/300'); ?>" class="card-img-top" alt="<?php echo htmlspecialchars($project['title']); ?>">
<div class="card-body">
<h5 class="card-title"><?php echo htmlspecialchars($project['title']); ?></h5>
<p class="card-text"><?php echo htmlspecialchars($project['description']); ?></p>
</div>
<div class="card-footer">
<a href="project.php?id=<?php echo $project['id']; ?>" class="btn btn-sm btn-outline-accent">Подробнее</a>
</div>
</div>
</div>
<?php endforeach; ?>
<?php endif; ?>
</div>
</div>
</section>
<!-- Contact Section -->
<section id="contact" class="section">
<div class="container">
<h2 class="text-center section-title">Свяжитесь со мной</h2>
<div class="row justify-content-center">
<div class="col-lg-8">
<form action="contact.php" method="POST" class="needs-validation" novalidate>
<div class="mb-3">
<label for="name" class="form-label">Имя</label>
<input type="text" class="form-control" id="name" name="name" required>
<div class="invalid-feedback">Пожалуйста, введите ваше имя.</div>
</div>
<div class="mb-3">
<label for="email" class="form-label">Email</label>
<input type="email" class="form-control" id="email" name="email" required>
<div class="invalid-feedback">Пожалуйста, введите корректный email.</div>
</div>
<div class="mb-3">
<label for="message" class="form-label">Сообщение</label>
<textarea class="form-control" id="message" name="message" rows="5" required></textarea>
<div class="invalid-feedback">Пожалуйста, введите ваше сообщение.</div>
</div>
<div class="text-center">
<button type="submit" class="btn btn-accent btn-lg">Отправить</button>
</div>
</form>
</div>
</div>
</div>
</section>
</main>
<!-- Footer -->
<footer class="text-center p-4">
<div class="container">
<p class="mb-0">&copy; <?php echo date("Y"); ?> Имя Фамилия. Все права защищены.</p>
</div>
</footer>
<!-- Bootstrap JS Bundle -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
<!-- Particles.js -->
<script src="https://cdn.jsdelivr.net/particles.js/2.0.0/particles.min.js"></script>
<!-- Custom JS -->
<script src="assets/js/main.js?v=<?php echo time(); ?>"></script>
</body>
</html>

259
invoice.php Normal file
View File

@ -0,0 +1,259 @@
<?php
// Sanitize POST data
$from_name = htmlspecialchars($_POST['from_name'] ?? 'N/A');
$from_address = nl2br(htmlspecialchars($_POST['from_address'] ?? 'N/A'));
$to_name = htmlspecialchars($_POST['to_name'] ?? 'N/A');
$to_address = nl2br(htmlspecialchars($_POST['to_address'] ?? 'N/A'));
$invoice_number = htmlspecialchars($_POST['invoice_number'] ?? 'N/A');
$invoice_date = htmlspecialchars($_POST['invoice_date'] ?? date('Y-m-d'));
$invoice_due_date = htmlspecialchars($_POST['invoice_due_date'] ?? 'N/A');
$items = $_POST['items'] ?? [];
$notes = nl2br(htmlspecialchars($_POST['notes'] ?? ''));
$currency = htmlspecialchars($_POST['currency'] ?? '$');
$tax_rate = floatval($_POST['tax_rate'] ?? 0);
// Server-side calculation
$subtotal = 0;
foreach ($items as $item) {
$qty = floatval($item['qty'] ?? 0);
$price = floatval($item['price'] ?? 0);
$subtotal += $qty * $price;
}
$tax_amount = $subtotal * ($tax_rate / 100);
$total = $subtotal + $tax_amount;
?>
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Инвойс <?= $invoice_number ?></title>
<style>
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
line-height: 1.6;
color: #333;
background-color: #f9f9f9;
margin: 0;
padding: 0;
}
.invoice-container {
max-width: 800px;
margin: 20px auto;
padding: 40px;
background-color: #fff;
border: 1px solid #ddd;
box-shadow: 0 0 15px rgba(0,0,0,0.05);
}
.invoice-header {
display: flex;
justify-content: space-between;
align-items: flex-start;
margin-bottom: 40px;
padding-bottom: 20px;
border-bottom: 2px solid #eee;
}
.invoice-header .from-details,
.invoice-header .invoice-meta {
width: 48%;
}
.invoice-header h1 {
margin: 0;
font-size: 2.5em;
color: #000;
}
.invoice-meta table {
width: 100%;
text-align: right;
}
.invoice-meta th, .invoice-meta td {
padding: 4px 0;
}
.invoice-meta th {
color: #777;
font-weight: normal;
}
.client-details {
margin-bottom: 40px;
}
.client-details h5 {
font-size: 1.1em;
color: #777;
margin-bottom: 5px;
}
.items-table {
width: 100%;
border-collapse: collapse;
margin-bottom: 30px;
}
.items-table th, .items-table td {
border-bottom: 1px solid #eee;
padding: 12px;
text-align: left;
}
.items-table th {
background-color: #f9f9f9;
font-weight: 600;
}
.items-table .text-right {
text-align: right;
}
.totals {
float: right;
width: 100%;
max-width: 300px;
}
.totals table {
width: 100%;
}
.totals th, .totals td {
padding: 8px;
}
.totals th {
text-align: left;
font-weight: normal;
color: #555;
}
.totals td {
text-align: right;
}
.totals .grand-total th, .totals .grand-total td {
font-size: 1.4em;
font-weight: bold;
padding-top: 15px;
border-top: 2px solid #333;
}
.invoice-notes {
margin-top: 40px;
padding-top: 20px;
border-top: 1px solid #eee;
}
.invoice-notes h5 {
margin-bottom: 5px;
color: #777;
}
.print-button-container {
text-align: center;
margin-top: 30px;
}
.print-button {
padding: 12px 25px;
font-size: 1em;
background-color: #0d6efd;
color: #fff;
border: none;
border-radius: 5px;
cursor: pointer;
transition: background-color 0.3s;
}
.print-button:hover {
background-color: #0b5ed7;
}
@media print {
body {
background-color: #fff;
}
.invoice-container {
box-shadow: none;
border: none;
margin: 0;
padding: 0;
max-width: 100%;
}
.print-button-container {
display: none;
}
}
</style>
</head>
<body>
<div class="invoice-container">
<div class="invoice-header">
<div class="from-details">
<h1>Инвойс</h1>
<strong><?= $from_name ?></strong><br>
<?= $from_address ?>
</div>
<div class="invoice-meta">
<table>
<tr>
<th>Номер #</th>
<td><?= $invoice_number ?></td>
</tr>
<tr>
<th>Дата</th>
<td><?= $invoice_date ?></td>
</tr>
<tr>
<th>Срок оплаты</th>
<td><?= $invoice_due_date ?></td>
</tr>
</table>
</div>
</div>
<div class="client-details">
<h5>Кому:</h5>
<strong><?= $to_name ?></strong><br>
<?= $to_address ?>
</div>
<table class="items-table">
<thead>
<tr>
<th>Описание</th>
<th class="text-right">Кол-во</th>
<th class="text-right">Цена</th>
<th class="text-right">Сумма</th>
</tr>
</thead>
<tbody>
<?php foreach ($items as $item): ?>
<tr>
<td><?= htmlspecialchars($item['description'] ?? '') ?></td>
<td class="text-right"><?= htmlspecialchars($item['qty'] ?? 0) ?></td>
<td class="text-right"><?= $currency ?> <?= number_format(floatval($item['price'] ?? 0), 2) ?></td>
<td class="text-right"><?= $currency ?> <?= number_format(floatval($item['qty'] ?? 0) * floatval($item['price'] ?? 0), 2) ?></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<div class="totals">
<table>
<tr>
<th>Подытог:</th>
<td><?= $currency ?> <?= number_format($subtotal, 2) ?></td>
</tr>
<tr>
<th>Налог (<?= $tax_rate ?>%):</th>
<td><?= $currency ?> <?= number_format($tax_amount, 2) ?></td>
</tr>
<tr class="grand-total">
<th>Итого:</th>
<td><?= $currency ?> <?= number_format($total, 2) ?></td>
</tr>
</table>
</div>
<div style="clear: both;"></div>
<?php if (!empty($notes)): ?>
<div class="invoice-notes">
<h5>Примечания:</h5>
<p><?= $notes ?></p>
</div>
<?php endif; ?>
<div class="print-button-container">
<button class="print-button" onclick="window.print()">Печать / Сохранить в PDF</button>
</div>
</div>
</body>
</html>

120
project.php Normal file
View File

@ -0,0 +1,120 @@
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<?php
require_once __DIR__ . '/db/config.php';
$project = null;
$project_id = filter_input(INPUT_GET, 'id', FILTER_VALIDATE_INT);
if ($project_id) {
try {
$pdo = db();
if ($pdo) {
$stmt = $pdo->prepare("SELECT * FROM projects WHERE id = ?");
$stmt->execute([$project_id]);
$project = $stmt->fetch(PDO::FETCH_ASSOC);
}
} catch (PDOException $e) {
error_log("Database error: " . $e->getMessage());
}
}
$page_title = $project ? htmlspecialchars($project['title']) . ' | Проект' : 'Проект не найден';
$page_description = $project ? htmlspecialchars($project['description']) : 'Запрошенный проект не найден.';
?>
<title><?php echo $page_title; ?></title>
<meta name="description" content="<?php echo $page_description; ?>">
<!-- Open Graph / Facebook -->
<meta property="og:title" content="<?php echo $page_title; ?>">
<meta property="og:description" content="<?php echo $page_description; ?>">
<meta property="og:image" content="<?php echo $project ? htmlspecialchars($project['image_url']) : htmlspecialchars($_SERVER['PROJECT_IMAGE_URL'] ?? ''); ?>">
<!-- Twitter -->
<meta name="twitter:title" content="<?php echo $page_title; ?>">
<meta name="twitter:description" content="<?php echo $page_description; ?>">
<meta name="twitter:image" content="<?php echo $project ? htmlspecialchars($project['image_url']) : htmlspecialchars($_SERVER['PROJECT_IMAGE_URL'] ?? ''); ?>">
<!-- Google Fonts: Poppins -->
<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=Poppins:wght@300;400;600;700&display=swap" rel="stylesheet">
<!-- Bootstrap CSS -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
<!-- Custom CSS -->
<link rel="stylesheet" href="assets/css/custom.css?v=<?php echo time(); ?>">
</head>
<body class="dark-theme">
<!-- Particles.js container -->
<div id="particles-js"></div>
<!-- Header -->
<header class="sticky-top">
<nav class="navbar navbar-expand-lg">
<div class="container">
<a class="navbar-brand fw-bold" href="index.php">Имя Фамилия</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav ms-auto">
<li class="nav-item"><a class="nav-link" href="index.php#about">Обо мне</a></li>
<li class="nav-item"><a class="nav-link" href="projects.php">Портфолио</a></li>
<li class="nav-item"><a class="nav-link" href="index.php#contact">Контакт</a></li>
<li class="nav-item"><a class="nav-link" href="/admin">Админка</a></li>
<li class="nav-item"><a class="nav-link" href="tools.php">Инструменты</a></li>
<li class="nav-item"><a class="nav-link" href="simulator.php">Симулятор</a></li>
</ul>
</div>
</div>
</nav>
</header>
<main class="container py-5">
<div class="row justify-content-center">
<div class="col-lg-10">
<?php if ($project): ?>
<div class="project-details">
<h1 class="project-title text-center mb-4"><?php echo htmlspecialchars($project['title']); ?></h1>
<img src="<?php echo htmlspecialchars($project['image_url'] ?: 'https://picsum.photos/seed/'.htmlspecialchars($project['id']).'/1200/800'); ?>" class="img-fluid rounded-3 mb-4 project-image" alt="<?php echo htmlspecialchars($project['title']); ?>">
<div class="project-description">
<p><?php echo nl2br(htmlspecialchars($project['description'])); ?></p>
</div>
<div class="text-center mt-4">
<a href="projects.php" class="btn btn-outline-accent">Назад к проектам</a>
</div>
</div>
<?php else: ?>
<div class="alert alert-danger text-center" role="alert">
<h4 class="alert-heading">Ошибка 404</h4>
<p>К сожалению, проект с таким ID не найден.</p>
<hr>
<a href="projects.php" class="btn btn-primary">Вернуться к проектам</a>
</div>
<?php endif; ?>
</div>
</div>
</main>
<!-- Footer -->
<footer class="text-center p-4 mt-auto">
<div class="container">
<p class="mb-0">&copy; <?php echo date("Y"); ?> Имя Фамилия. Все права защищены.</p>
</div>
</footer>
<!-- Bootstrap JS Bundle -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
<!-- Particles.js -->
<script src="https://cdn.jsdelivr.net/particles.js/2.0.0/particles.min.js"></script>
<!-- Custom JS -->
<script src="assets/js/main.js?v=<?php echo time(); ?>"></script>
</body>
</html>

118
projects.php Normal file
View File

@ -0,0 +1,118 @@
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Проекты | Личное Портфолио</title>
<meta name="description" content="Галерея проектов в личном портфолио.">
<!-- Open Graph / Facebook -->
<meta property="og:title" content="Проекты | Личное Портфолио">
<meta property="og:description" content="Галерея проектов в личном портфолио.">
<meta property="og:image" content="<?php echo htmlspecialchars($_SERVER['PROJECT_IMAGE_URL'] ?? ''); ?>">
<!-- Twitter -->
<meta name="twitter:title" content="Проекты | Личное Портфолио">
<meta name="twitter:description" content="Галерея проектов в личном портфолио.">
<meta name="twitter:image" content="<?php echo htmlspecialchars($_SERVER['PROJECT_IMAGE_URL'] ?? ''); ?>">
<!-- Google Fonts: Poppins -->
<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=Poppins:wght@300;400;600;700&display=swap" rel="stylesheet">
<!-- Bootstrap CSS -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
<!-- Bootstrap Icons -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css">
<!-- Custom CSS -->
<link rel="stylesheet" href="assets/css/custom.css?v=<?php echo time(); ?>">
</head>
<body class="dark-theme">
<!-- Particles.js container -->
<div id="particles-js"></div>
<!-- Header -->
<header class="sticky-top">
<nav class="navbar navbar-expand-lg">
<div class="container">
<a class="navbar-brand fw-bold" href="index.php">Имя Фамилия</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav ms-auto">
<li class="nav-item"><a class="nav-link" href="index.php#about">Обо мне</a></li>
<li class="nav-item"><a class="nav-link active" href="projects.php">Портфолио</a></li>
<li class="nav-item"><a class="nav-link" href="index.php#contact">Контакт</a></li>
<li class="nav-item"><a class="nav-link" href="/admin">Админка</a></li>
<li class="nav-item"><a class="nav-link" href="tools.php">Инструменты</a></li>
<li class="nav-item"><a class="nav-link" href="simulator.php">Симулятор</a></li>
</ul>
</div>
</div>
</nav>
</header>
<main class="container py-5">
<?php
require_once __DIR__ . '/db/config.php';
$projects = [];
try {
$pdo = db();
if ($pdo) {
$stmt = $pdo->query("SELECT * FROM projects ORDER BY created_at DESC");
if ($stmt) {
$projects = $stmt->fetchAll(PDO::FETCH_ASSOC);
}
}
} catch (PDOException $e) {
error_log("Database error: " . $e->getMessage());
}
?>
<!-- Portfolio Section -->
<section id="portfolio" class="section">
<div class="container">
<h2 class="text-center section-title mb-5">Мои работы</h2>
<div class="row g-4">
<?php if (empty($projects)): ?>
<div class="col">
<p class="text-center">Пока нет проектов для отображения. Зайдите позже!</p>
</div>
<?php else: ?>
<?php foreach ($projects as $project): ?>
<div class="col-md-6 col-lg-4">
<div class="card portfolio-item h-100">
<img src="<?php echo htmlspecialchars($project['image_url'] ?: 'https://picsum.photos/seed/'.htmlspecialchars($project['id']).'/400/300'); ?>" class="card-img-top" alt="<?php echo htmlspecialchars($project['title']); ?>">
<div class="card-body d-flex flex-column">
<h5 class="card-title"><?php echo htmlspecialchars($project['title']); ?></h5>
<p class="card-text flex-grow-1"><?php echo htmlspecialchars($project['description']); ?></p>
<a href="project.php?id=<?php echo $project['id']; ?>" class="btn btn-sm btn-outline-accent mt-auto">Подробнее</a>
</div>
</div>
</div>
<?php endforeach; ?>
<?php endif; ?>
</div>
</div>
</section>
</main>
<!-- Footer -->
<footer class="text-center p-4 mt-auto">
<div class="container">
<p class="mb-0">&copy; <?php echo date("Y"); ?> Имя Фамилия. Все права защищены.</p>
</div>
</footer>
<!-- Bootstrap JS Bundle -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
<!-- Particles.js -->
<script src="https://cdn.jsdelivr.net/particles.js/2.0.0/particles.min.js"></script>
<!-- Custom JS -->
<script src="assets/js/main.js?v=<?php echo time(); ?>"></script>
</body>
</html>

118
simulator.php Normal file
View File

@ -0,0 +1,118 @@
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<!-- SEO and Meta Tags -->
<title>Симулятор | Личное Портфолио</title>
<meta name="description" content="Страница симулятора.">
<!-- Google Fonts: Poppins -->
<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=Poppins:wght@300;400;600;700&display=swap" rel="stylesheet">
<!-- Bootstrap CSS -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
<!-- Bootstrap Icons -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css">
<!-- Custom CSS -->
<link rel="stylesheet" href="assets/css/custom.css?v=<?php echo time(); ?>">
</head>
<body class="dark-theme">
<!-- Particles.js container -->
<div id="particles-js"></div>
<!-- Header -->
<header class="sticky-top">
<nav class="navbar navbar-expand-lg">
<div class="container">
<a class="navbar-brand fw-bold" href="index.php">Имя Фамилия</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav ms-auto">
<li class="nav-item"><a class="nav-link" href="index.php#about">Обо мне</a></li>
<li class="nav-item"><a class="nav-link" href="projects.php">Портфолио</a></li>
<li class="nav-item"><a class="nav-link" href="index.php#contact">Контакт</a></li>
<li class="nav-item"><a class="nav-link" href="/admin">Админка</a></li>
<li class="nav-item"><a class="nav-link" href="tools.php">Инструменты</a></li>
<li class="nav-item"><a class="nav-link active" href="simulator.php">Симулятор</a></li>
</ul>
</div>
</div>
</nav>
</header>
<main>
<section class="section">
<div class="container">
<h1 class="text-center section-title">Симулятор Запуска Ракеты</h1>
<p class="text-center mb-4">Задайте параметры запуска: ускорение, время работы двигателя и угол.<br>Наблюдайте, как ракета набирает скорость и летит по траектории.</p>
<div class="row justify-content-center">
<div class="col-md-6 col-lg-5">
<div class="card bg-dark-2 text-white">
<div class="card-body">
<!-- Форма теперь без перезагрузки страницы -->
<form onsubmit="return false;">
<div class="mb-3">
<label for="acceleration" class="form-label">Ускорение (м/с²):</label>
<input type="number" class="form-control" id="acceleration" name="acceleration" placeholder="Например, 100" value="100" required>
</div>
<div class="mb-3">
<label for="burnTime" class="form-label">Время работы двигателя (сек):</label>
<input type="number" class="form-control" id="burnTime" name="burnTime" placeholder="Например, 60" value="60" required>
</div>
<div class="mb-3">
<label for="angle" class="form-label">Угол запуска (°):</label>
<input type="number" class="form-control" id="angle" name="angle" placeholder="от 0 до 90" value="90" min="0" max="90" required>
</div>
<div class="d-grid">
<!-- Кнопка теперь запускает JS-функцию -->
<button type="button" id="launchButton" class="btn btn-primary btn-lg">Запуск</button>
</div>
</form>
</div>
</div>
</div>
</div>
<!-- Область для анимации -->
<div class="row justify-content-center mt-4">
<div class="col-md-10 col-lg-8">
<div class="card bg-dark-2 text-white">
<div class="card-body text-center">
<h5 class="card-title mb-3">Траектория полёта</h5>
<canvas id="simulationCanvas" width="600" height="400" style="background-color: #0c1021; border-radius: 5px;"></canvas>
<div id="result-message" class="mt-3">&nbsp;</div>
</div>
</div>
</div>
</div>
</div>
</section>
</main>
<!-- Footer -->
<footer class="text-center p-4">
<div class="container">
<p class="mb-0">&copy; <?php echo date("Y"); ?> Имя Фамилия. Все права защищены.</p>
</div>
</footer>
<!-- Bootstrap JS Bundle -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
<!-- Particles.js -->
<script src="https://cdn.jsdelivr.net/particles.js/2.0.0/particles.min.js"></script>
<!-- Custom JS -->
<script src="assets/js/main.js?v=<?php echo time(); ?>"></script>
<script src="assets/js/simulator.js?v=<?php echo time(); ?>"></script>
</body>
</html>

307
tools.php Normal file
View File

@ -0,0 +1,307 @@
<?php
require_once __DIR__ . '/ai/LocalAIApi.php';
$ai_response = '';
$user_prompt = '';
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['user_prompt'])) {
$user_prompt = htmlspecialchars($_POST['user_prompt']);
if (!empty($user_prompt)) {
$response = LocalAIApi::createResponse([
'input' => [
['role' => 'system', 'content' => 'You are a helpful assistant.'],
['role' => 'user', 'content' => $user_prompt],
],
]);
if (!empty($response['success'])) {
$ai_response = LocalAIApi::extractText($response);
} else {
$ai_response = 'Ошибка: Не удалось получить ответ от AI.';
}
}
}
?>
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Инструменты - Ваше Имя</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<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=Poppins:wght@300;400;600;700&display=swap" rel="stylesheet">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css">
<link rel="stylesheet" href="assets/css/custom.css">
</head>
<body class="dark-theme">
<div id="particles-js"></div>
<nav class="navbar navbar-expand-lg sticky-top">
<div class="container">
<a class="navbar-brand fw-bold" href="index.php">Имя Фамилия</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav ms-auto">
<li class="nav-item">
<a class="nav-link" href="index.php#about">Обо мне</a>
</li>
<li class="nav-item">
<a class="nav-link" href="projects.php">Портфолио</a>
</li>
<li class="nav-item">
<a class="nav-link" href="index.php#contact">Контакт</a>
</li>
<li class="nav-item"><a class="nav-link" href="/admin">Админка</a></li>
<li class="nav-item">
<a class="nav-link active" href="tools.php">Инструменты</a>
</li>
<li class="nav-item"><a class="nav-link" href="simulator.php">Симулятор</a></li>
</ul>
</div>
</div>
</nav>
<main class="container mt-5 pt-5">
<div class="text-center mb-5">
<h1 class="display-4">Инструменты</h1>
<p class="lead">Полезные инструменты для автоматизации ваших задач.</p>
</div>
<div class="row g-4">
<div class="col-12">
<div class="card tool-card">
<div class="card-body">
<h3 class="card-title">AI-чат</h3>
<p>Задайте любой вопрос и получите ответ от искусственного интеллекта.</p>
<form action="tools.php" method="post">
<div class="form-group mb-3">
<label for="user_prompt">Ваш вопрос:</label>
<input type="text" id="user_prompt" name="user_prompt" class="form-control" required value="<?php echo $user_prompt; ?>">
</div>
<button type="submit" class="btn btn-primary">Отправить</button>
</form>
<?php if ($_SERVER['REQUEST_METHOD'] === 'POST' && !empty($user_prompt)): ?>
<div class="mt-4">
<div class="user-prompt">
<strong>Вы:</strong>
<p><?php echo $user_prompt; ?></p>
</div>
<div class="ai-response">
<strong>AI:</strong>
<p><?php echo nl2br($ai_response); ?></p>
</div>
</div>
<?php endif; ?>
</div>
</div>
</div>
<div class="col-12">
<div class="card tool-card">
<div class="card-body">
<h2 class="card-title">Генератор инвойсов</h2>
<p>Создайте профессиональный инвойс и сохраните его в PDF.</p>
<form action="invoice.php" method="POST" target="_blank">
<div class="row mb-4">
<div class="col-md-6">
<h5 class="mb-3">От кого</h5>
<div class="mb-2">
<input type="text" class="form-control" name="from_name" placeholder="Ваше имя / Название компании" required>
</div>
<div class="mb-2">
<textarea class="form-control" name="from_address" placeholder="Ваш адрес" required></textarea>
</div>
</div>
<div class="col-md-6">
<h5 class="mb-3">Кому</h5>
<div class="mb-2">
<input type="text" class="form-control" name="to_name" placeholder="Имя клиента / Название компании" required>
</div>
<div class="mb-2">
<textarea class="form-control" name="to_address" placeholder="Адрес клиента" required></textarea>
</div>
</div>
</div>
<div class="row mb-4">
<div class="col-md-4">
<label for="invoice_number" class="form-label">Номер инвойса</label>
<input type="text" class="form-control" id="invoice_number" name="invoice_number" value="INV-001">
</div>
<div class="col-md-4">
<label for="invoice_date" class="form-label">Дата</label>
<input type="date" class="form-control" id="invoice_date" name="invoice_date">
</div>
<div class="col-md-4">
<label for="invoice_due_date" class="form-label">Срок оплаты</label>
<input type="date" class="form-control" id="invoice_due_date" name="invoice_due_date">
</div>
</div>
<table class="table table-bordered" id="invoice-items">
<thead>
<tr>
<th>Описание</th>
<th>Кол-во</th>
<th>Цена за ед.</th>
<th>Сумма</th>
<th></th>
</tr>
</thead>
<tbody>
<tr>
<td><input type="text" class="form-control" name="items[0][description]" placeholder="Название услуги или товара" required></td>
<td><input type="number" class="form-control item-qty" name="items[0][qty]" value="1" min="1" required></td>
<td><input type="number" class="form-control item-price" name="items[0][price]" value="0.00" step="0.01" min="0" required></td>
<td><input type="text" class="form-control item-total" readonly></td>
<td><button type="button" class="btn btn-danger btn-sm remove-item">&times;</button></td>
</tr>
</tbody>
</table>
<button type="button" class="btn btn-secondary mb-3" id="add-item">Добавить позицию</button>
<div class="row justify-content-end">
<div class="col-md-5">
<div class="row mb-2">
<div class="col">Валюта:</div>
<div class="col-auto">
<input type="text" class="form-control" name="currency" value="$">
</div>
</div>
<div class="row mb-2">
<div class="col">Подытог:</div>
<div class="col-auto" id="subtotal">0.00</div>
</div>
<div class="row mb-2 align-items-center">
<div class="col">Налог (%):</div>
<div class="col-auto">
<input type="number" class="form-control" id="tax-rate" name="tax_rate" value="0" min="0" step="1">
</div>
</div>
<hr>
<div class="row fw-bold fs-5">
<div class="col">Итого:</div>
<div class="col-auto" id="total">0.00</div>
</div>
</div>
</div>
<div class="mt-4">
<label for="notes" class="form-label">Примечания</label>
<textarea class="form-control" id="notes" name="notes" rows="3" placeholder="Например: Спасибо за ваш бизнес!"></textarea>
</div>
<div class="text-center mt-4">
<button type="submit" class="btn btn-primary btn-lg">Сгенерировать инвойс</button>
</div>
</form>
</div>
</div>
</div>
</div>
</main>
<footer class="text-center p-4">
<div class="container">
<p class="mb-0">&copy; <?php echo date("Y"); ?> Имя Фамилия. Все права защищены.</p>
</div>
</footer>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
<script src="https://cdn.jsdelivr.net/particles.js/2.0.0/particles.min.js"></script>
<script>
particlesJS.load('particles-js', 'assets/particles.json', function() {
console.log('callback - particles.js config loaded');
});
</script>
<script src="assets/js/main.js"></script>
<script>
document.addEventListener('DOMContentLoaded', function () {
// Set default dates
const today = new Date().toISOString().split('T')[0];
const invoiceDate = document.getElementById('invoice_date');
const dueDate = document.getElementById('invoice_due_date');
if (invoiceDate) {
invoiceDate.value = today;
}
if (dueDate) {
// Set due date to 30 days from now
const thirtyDaysFromNow = new Date();
thirtyDaysFromNow.setDate(thirtyDaysFromNow.getDate() + 30);
dueDate.value = thirtyDaysFromNow.toISOString().split('T')[0];
}
const itemsTable = document.getElementById('invoice-items');
if (!itemsTable) return;
let itemIndex = 1;
// Function to update totals
function updateTotals() {
let subtotal = 0;
document.querySelectorAll('#invoice-items tbody tr').forEach(row => {
const qty = parseFloat(row.querySelector('.item-qty').value) || 0;
const price = parseFloat(row.querySelector('.item-price').value) || 0;
const total = qty * price;
row.querySelector('.item-total').value = total.toFixed(2);
subtotal += total;
});
const taxRate = parseFloat(document.getElementById('tax-rate').value) || 0;
const taxAmount = subtotal * (taxRate / 100);
const total = subtotal + taxAmount;
const currency = document.querySelector('input[name="currency"]').value || '$';
document.getElementById('subtotal').textContent = currency + ' ' + subtotal.toFixed(2);
document.getElementById('total').textContent = currency + ' ' + total.toFixed(2);
}
// Add new item row
document.getElementById('add-item').addEventListener('click', function () {
const newRow = document.createElement('tr');
newRow.innerHTML = `
<td><input type="text" class="form-control" name="items[${itemIndex}][description]" placeholder="Название услуги или товара" required></td>
<td><input type="number" class="form-control item-qty" name="items[${itemIndex}][qty]" value="1" min="1" required></td>
<td><input type="number" class="form-control item-price" name="items[${itemIndex}][price]" value="0.00" step="0.01" min="0" required></td>
<td><input type="text" class="form-control item-total" readonly></td>
<td><button type="button" class="btn btn-danger btn-sm remove-item">&times;</button></td>
`;
itemsTable.querySelector('tbody').appendChild(newRow);
itemIndex++;
updateTotals();
});
// Remove item row and update totals on click
itemsTable.addEventListener('click', function (e) {
if (e.target.classList.contains('remove-item')) {
e.target.closest('tr').remove();
updateTotals();
}
});
// Update totals on input change
itemsTable.addEventListener('input', function (e) {
if (e.target.classList.contains('item-qty') || e.target.classList.contains('item-price')) {
updateTotals();
}
});
document.getElementById('tax-rate').addEventListener('input', updateTotals);
document.querySelector('input[name="currency"]').addEventListener('input', updateTotals);
// Initial calculation
updateTotals();
});
</script>
</body>
</html>