diff --git a/api/edit.php b/api/edit.php
new file mode 100644
index 0000000..dccc9ff
--- /dev/null
+++ b/api/edit.php
@@ -0,0 +1,90 @@
+ false, 'error' => 'Invalid request data']);
+ exit;
+}
+
+$action = $data['action'] ?? 'magic';
+$original_prompt = $data['original_prompt'] ?? '';
+$edit_prompt = $data['edit_prompt'] ?? '';
+$image_url = $data['image_url'] ?? '';
+
+if (empty($original_prompt) && empty($edit_prompt)) {
+ echo json_encode(['success' => false, 'error' => 'Prompt is required']);
+ exit;
+}
+
+try {
+ $new_prompt = '';
+
+ // Step 1: Use AI to merge prompts or handle actions
+ if ($action === 'magic') {
+ $ai_payload = [
+ 'input' => [
+ ['role' => 'system', 'content' => 'You are an AI Image Prompt Engineer. Your task is to combine an original image description with a new edit instruction into a single, highly detailed, and cohesive new image prompt. Maintain the core subject and style of the original but incorporate the changes naturally. Output ONLY the new prompt, no explanation.'],
+ ['role' => 'user', 'content' => "Original description: {$original_prompt}\nEdit instruction: {$edit_prompt}\nNew Prompt:"]
+ ]
+ ];
+
+ $ai_response = LocalAIApi::createResponse($ai_payload);
+ if (!empty($ai_response['success'])) {
+ $new_prompt = LocalAIApi::extractText($ai_response);
+ } else {
+ // Fallback: simple concatenation
+ $new_prompt = $original_prompt . ", " . $edit_prompt;
+ }
+ } else if ($action === 'remove_bg') {
+ $new_prompt = "{$original_prompt}, isolated on a pure white background, studio lighting, professional product photography";
+ } else if ($action === 'upscale') {
+ $new_prompt = "{$original_prompt}, extremely detailed, 8k resolution, masterpiece, sharp focus, hyperrealistic";
+ }
+
+ if (empty($new_prompt)) {
+ $new_prompt = $original_prompt . " " . $edit_prompt;
+ }
+
+ // Step 2: Generate new image using Pollinations (Flux)
+ $seed = rand(1000, 99999);
+ $encoded_prompt = urlencode($new_prompt);
+ $api_url = "https://image.pollinations.ai/prompt/{$encoded_prompt}?width=1024&height=1024&seed={$seed}&model=flux&nologo=true";
+
+ $filename = 'assets/images/pexels/gen_edit_' . md5($new_prompt . $seed) . '.jpg';
+ $target = __DIR__ . '/../' . $filename;
+
+ // Download the image
+ $ch = curl_init($api_url);
+ curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
+ curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
+ curl_setopt($ch, CURLOPT_TIMEOUT, 60);
+ $image_data = curl_exec($ch);
+ $http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
+ curl_close($ch);
+
+ if ($http_code === 200 && $image_data) {
+ if (!is_dir(__DIR__ . '/../assets/images/pexels/')) {
+ mkdir(__DIR__ . '/../assets/images/pexels/', 0775, true);
+ }
+ file_put_contents($target, $image_data);
+
+ // Save to history
+ $stmt = db()->prepare("INSERT INTO media_history (type, prompt, result_url) VALUES (?, ?, ?)");
+ $stmt->execute(['photo', "[Edit] " . $new_prompt, $filename]);
+
+ echo json_encode([
+ 'success' => true,
+ 'url' => $filename,
+ 'new_prompt' => $new_prompt
+ ]);
+ } else {
+ echo json_encode(['success' => false, 'error' => 'Failed to generate image from AI: HTTP ' . $http_code]);
+ }
+
+} catch (Exception $e) {
+ echo json_encode(['success' => false, 'error' => $e->getMessage()]);
+}
\ No newline at end of file
diff --git a/api/generate.php b/api/generate.php
new file mode 100644
index 0000000..e99983c
--- /dev/null
+++ b/api/generate.php
@@ -0,0 +1,184 @@
+ false, 'error' => 'Invalid request method']);
+ exit;
+}
+
+$type = $_POST['type'] ?? 'photo';
+$prompt = $_POST['prompt'] ?? '';
+$style = $_POST['style'] ?? '';
+$aspect_ratio = $_POST['aspect_ratio'] ?? '1:1';
+$rapidapi_key = $_POST['rapidapi_key'] ?? '';
+$video_provider = $_POST['video_provider'] ?? 'pexels'; // 'pexels' or 'ai'
+
+if (empty($prompt)) {
+ echo json_encode(['success' => false, 'error' => 'Запрос не может быть пустым']);
+ exit;
+}
+
+// Map aspect ratios to dimensions
+$dimensions = [
+ '1:1' => [1024, 1024],
+ '16:9' => [1280, 720],
+ '9:16' => [720, 1280],
+ '4:3' => [1024, 768],
+ '3:2' => [1080, 720]
+];
+
+$width = $dimensions[$aspect_ratio][0] ?? 1024;
+$height = $dimensions[$aspect_ratio][1] ?? 1024;
+
+// Enhance prompt with style
+$enhanced_prompt = $prompt;
+if (!empty($style)) {
+ $style_map = [
+ 'anime' => 'in anime style, high quality, vibrant colors',
+ 'realism' => 'photorealistic, highly detailed, 8k, masterpiece',
+ 'cyberpunk' => 'cyberpunk style, neon lights, futuristic, highly detailed',
+ 'vaporwave' => 'vaporwave aesthetic, pink and blue colors, retro-futuristic',
+ 'pixel-art' => 'pixel art style, 8-bit, retro game aesthetic',
+ 'fantasy' => 'fantasy art, magical atmosphere, epic, highly detailed',
+ '3d-render' => '3d render, octane render, cinematic lighting, unreal engine 5',
+ 'steampunk' => 'steampunk style, gears, brass, Victorian aesthetic',
+ 'oil-painting' => 'oil painting, brush strokes, artistic, classic masterpiece',
+ 'sketch' => 'pencil sketch, hand drawn, artistic, charcoal',
+ 'pop-art' => 'pop art style, Andy Warhol aesthetic, vibrant, bold colors',
+ 'minimalism' => 'minimalist style, clean lines, simple, elegant',
+ 'cinematic' => 'cinematic shot, dramatic lighting, movie still, high contrast'
+ ];
+
+ if (isset($style_map[$style])) {
+ $enhanced_prompt .= ", " . $style_map[$style];
+ }
+}
+
+try {
+ $result_url = '';
+ $is_ai = false;
+ $provider_name = '';
+ $message = '';
+
+ if ($type === 'photo') {
+ $is_ai = true;
+ $provider_name = 'Pollinations AI (Flux)';
+ // Real AI Generation (Truly free)
+ $seed = rand(1000, 99999);
+ $encoded_prompt = urlencode($enhanced_prompt);
+ $api_url = "https://image.pollinations.ai/prompt/{$encoded_prompt}?width={$width}&height={$height}&seed={$seed}&model=flux&nologo=true";
+
+ $filename = 'assets/images/pexels/gen_' . md5($enhanced_prompt . $seed) . '.jpg';
+ $target = __DIR__ . '/../' . $filename;
+
+ $ch = curl_init($api_url);
+ curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
+ curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
+ curl_setopt($ch, CURLOPT_TIMEOUT, 30);
+ $image_data = curl_exec($ch);
+ $content_type = curl_getinfo($ch, CURLINFO_CONTENT_TYPE);
+ $http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
+ curl_close($ch);
+
+ if ($http_code === 200 && strpos($content_type, 'image') !== false && $image_data) {
+ file_put_contents($target, $image_data);
+ $result_url = $filename;
+ } else {
+ // Fallback to direct URL if saving failed
+ $result_url = $api_url;
+ }
+ } else {
+ // Video Logic
+ if ($video_provider === 'ai') {
+ if (!empty($rapidapi_key)) {
+ $is_ai = true;
+ $provider_name = 'RapidAPI AI';
+ // Attempt real AI Video generation
+ // Note: Different APIs have different formats, this is a generic attempt
+ $ch = curl_init();
+ curl_setopt_array($ch, [
+ CURLOPT_URL => "https://text-to-video2.p.rapidapi.com/generate",
+ CURLOPT_RETURNTRANSFER => true,
+ CURLOPT_POST => true,
+ CURLOPT_POSTFIELDS => json_encode(["prompt" => $enhanced_prompt]),
+ CURLOPT_HTTPHEADER => [
+ "Content-Type: application/json",
+ "X-RapidAPI-Host: text-to-video2.p.rapidapi.com",
+ "X-RapidAPI-Key: " . $rapidapi_key
+ ],
+ ]);
+ $response = curl_exec($ch);
+ $err = curl_error($ch);
+ curl_close($ch);
+
+ if (!$err) {
+ $res_data = json_decode($response, true);
+ if (isset($res_data['video_url'])) {
+ $result_url = $res_data['video_url'];
+ } else if (isset($res_data['url'])) {
+ $result_url = $res_data['url'];
+ }
+ }
+ }
+
+ if (empty($result_url)) {
+ // Fallback to Pexels search if no key or API failed
+ $is_ai = false;
+ $provider_name = 'Pexels Stock (Fallback)';
+ if (empty($rapidapi_key)) {
+ $message = 'Ключ RapidAPI не найден. Используется качественный стоковый поиск вместо ИИ-генерации.';
+ } else {
+ $message = 'Ошибка API генерации. Используется стоковый поиск.';
+ }
+
+ $url = 'https://api.pexels.com/videos/search?query=' . urlencode($prompt) . '&per_page=1&page=1';
+ $data = pexels_get($url);
+ if ($data && !empty($data['videos'])) {
+ foreach ($data['videos'][0]['video_files'] as $file) {
+ if ($file['quality'] === 'hd' || $file['quality'] === 'sd') {
+ $result_url = $file['link'];
+ break;
+ }
+ }
+ }
+ }
+ } else {
+ // Standard Pexels Stock
+ $is_ai = false;
+ $provider_name = 'Pexels Stock';
+ $url = 'https://api.pexels.com/videos/search?query=' . urlencode($prompt) . '&per_page=1&page=1';
+ $data = pexels_get($url);
+ if ($data && !empty($data['videos'])) {
+ foreach ($data['videos'][0]['video_files'] as $file) {
+ if ($file['quality'] === 'hd' || $file['quality'] === 'sd') {
+ $result_url = $file['link'];
+ break;
+ }
+ }
+ }
+ }
+
+ if (empty($result_url)) {
+ $result_url = 'https://interactive-examples.mdn.mozilla.net/media/cc0-videos/flower.mp4';
+ }
+ }
+
+ // Save to history
+ $stmt = db()->prepare("INSERT INTO media_history (type, prompt, result_url) VALUES (?, ?, ?)");
+ $stmt->execute([$type, $prompt . ($style ? " ($style)" : ""), $result_url]);
+
+ echo json_encode([
+ 'success' => true,
+ 'type' => $type,
+ 'url' => $result_url,
+ 'prompt' => $prompt,
+ 'is_ai' => $is_ai,
+ 'provider' => $provider_name,
+ 'message' => $message
+ ]);
+
+} catch (Exception $e) {
+ echo json_encode(['success' => false, 'error' => $e->getMessage()]);
+}
\ No newline at end of file
diff --git a/assets/css/custom.css b/assets/css/custom.css
index 65a1626..0b7e4cf 100644
--- a/assets/css/custom.css
+++ b/assets/css/custom.css
@@ -1,346 +1,120 @@
+/* Custom Styles for AI Media Forge */
+
:root {
- --color-bg: #ffffff;
- --color-text: #1a1a1a;
- --color-primary: #2563EB; /* Vibrant Blue */
- --color-secondary: #000000;
- --color-accent: #A3E635; /* Lime Green */
- --color-surface: #f8f9fa;
- --font-heading: 'Space Grotesk', sans-serif;
- --font-body: 'Inter', sans-serif;
- --border-width: 2px;
- --shadow-hard: 5px 5px 0px #000;
- --shadow-hover: 8px 8px 0px #000;
- --radius-pill: 50rem;
- --radius-card: 1rem;
+ --primary-color: #000000;
+ --accent-color: #007bff;
+ --bg-light: #f8f9fa;
+ --card-radius: 16px;
}
body {
- font-family: var(--font-body);
- background-color: var(--color-bg);
- color: var(--color-text);
- overflow-x: hidden;
+ font-family: 'Inter', sans-serif;
+ color: #333;
}
-h1, h2, h3, h4, h5, h6, .navbar-brand {
- font-family: var(--font-heading);
- letter-spacing: -0.03em;
-}
-
-/* Utilities */
-.text-primary { color: var(--color-primary) !important; }
-.bg-black { background-color: #000 !important; }
-.text-white { color: #fff !important; }
-.shadow-hard { box-shadow: var(--shadow-hard); }
-.border-2-black { border: var(--border-width) solid #000; }
-.py-section { padding-top: 5rem; padding-bottom: 5rem; }
-
/* Navbar */
.navbar {
- background: rgba(255, 255, 255, 0.9);
backdrop-filter: blur(10px);
- border-bottom: var(--border-width) solid transparent;
- transition: all 0.3s;
- padding-top: 1rem;
- padding-bottom: 1rem;
+ background-color: rgba(33, 37, 41, 0.95) !important;
}
-.navbar.scrolled {
- border-bottom-color: #000;
- padding-top: 0.5rem;
- padding-bottom: 0.5rem;
+/* Cards */
+.card {
+ border: none;
+ transition: transform 0.2s ease, box-shadow 0.2s ease;
}
-.brand-text {
- font-size: 1.5rem;
- font-weight: 800;
-}
-
-.nav-link {
- font-weight: 500;
- color: var(--color-text);
- margin-left: 1rem;
- position: relative;
-}
-
-.nav-link:hover, .nav-link.active {
- color: var(--color-primary);
+.history-card:hover {
+ transform: translateY(-5px);
+ box-shadow: 0 10px 20px rgba(0,0,0,0.1) !important;
}
/* Buttons */
-.btn {
- font-weight: 700;
- font-family: var(--font-heading);
- padding: 0.8rem 2rem;
- border-radius: var(--radius-pill);
- border: var(--border-width) solid #000;
- transition: all 0.2s cubic-bezier(0.25, 1, 0.5, 1);
- box-shadow: var(--shadow-hard);
-}
-
-.btn:hover {
- transform: translate(-2px, -2px);
- box-shadow: var(--shadow-hover);
-}
-
-.btn:active {
- transform: translate(2px, 2px);
- box-shadow: 0 0 0 #000;
-}
-
.btn-primary {
- background-color: var(--color-primary);
- border-color: #000;
- color: #fff;
+ background-color: var(--primary-color);
+ padding: 10px 24px;
+ transition: all 0.3s ease;
}
.btn-primary:hover {
- background-color: #1d4ed8;
- border-color: #000;
- color: #fff;
+ background-color: #333;
+ transform: translateY(-1px);
}
-.btn-outline-dark {
- background-color: #fff;
- color: #000;
+/* Editor */
+.editor-preview-container {
+ box-shadow: inset 0 0 20px rgba(0,0,0,0.5);
}
-.btn-cta {
- background-color: var(--color-accent);
- color: #000;
+.filter-range::-webkit-slider-runnable-track {
+ background: #ddd;
+ height: 4px;
+ border-radius: 2px;
}
-.btn-cta:hover {
- background-color: #8cc629;
- color: #000;
+.filter-range::-webkit-slider-thumb {
+ background: var(--primary-color);
+ margin-top: -6px;
}
-/* Hero Section */
-.hero-section {
- min-height: 100vh;
- padding-top: 80px;
+/* AI Magic Box */
+.ai-magic-box {
+ border: 1px solid #e0e0e0;
+ transition: border-color 0.3s ease;
}
-.background-blob {
- position: absolute;
- border-radius: 50%;
- filter: blur(80px);
- opacity: 0.6;
- z-index: 1;
+.ai-magic-box:focus-within {
+ border-color: var(--accent-color);
}
-.blob-1 {
- top: -10%;
- right: -10%;
- width: 600px;
- height: 600px;
- background: radial-gradient(circle, var(--color-accent), transparent);
+/* Badges */
+.badge-ai {
+ font-weight: 600;
+ letter-spacing: 0.5px;
+ padding: 6px 12px;
}
-.blob-2 {
- bottom: 10%;
- left: -10%;
- width: 500px;
- height: 500px;
- background: radial-gradient(circle, var(--color-primary), transparent);
+/* Transitions */
+.fade-in {
+ animation: fadeIn 0.5s ease-in;
}
-.highlight-text {
- background: linear-gradient(120deg, transparent 0%, transparent 40%, var(--color-accent) 40%, var(--color-accent) 100%);
- background-repeat: no-repeat;
- background-size: 100% 40%;
- background-position: 0 88%;
- padding: 0 5px;
+@keyframes fadeIn {
+ from { opacity: 0; }
+ to { opacity: 1; }
}
-.dot { color: var(--color-primary); }
+/* Responsiveness */
+@media (max-width: 768px) {
+ .display-5 {
+ font-size: 2rem;
+ }
+}
-.badge-pill {
- display: inline-block;
- padding: 0.5rem 1rem;
- border: 2px solid #000;
- border-radius: 50px;
- font-weight: 700;
- background: #fff;
- box-shadow: 4px 4px 0 #000;
- font-family: var(--font-heading);
+/* Nano Editor Specifics */
+.letter-spacing-1 {
+ letter-spacing: 1px;
+}
+
+.nav-pills .nav-link {
+ border: 1px solid transparent;
+}
+
+.nav-pills .nav-link:not(.active):hover {
+ background-color: #f0f0f0;
+}
+
+#editor-loading {
+ border-radius: 8px;
+}
+
+textarea#ai-edit-prompt {
+ resize: none;
+ border-radius: 8px;
font-size: 0.9rem;
}
-/* Marquee */
-.marquee-container {
- overflow: hidden;
- white-space: nowrap;
- border-top: 2px solid #000;
- border-bottom: 2px solid #000;
-}
-
-.rotate-divider {
- transform: rotate(-2deg) scale(1.05);
- z-index: 10;
- position: relative;
- margin-top: -50px;
- margin-bottom: 30px;
-}
-
-.marquee-content {
- display: inline-block;
- animation: marquee 20s linear infinite;
- font-family: var(--font-heading);
- font-weight: 700;
- font-size: 1.5rem;
- letter-spacing: 2px;
-}
-
-@keyframes marquee {
- 0% { transform: translateX(0); }
- 100% { transform: translateX(-50%); }
-}
-
-/* Portfolio Cards */
-.project-card {
- border: 2px solid #000;
- border-radius: var(--radius-card);
- overflow: hidden;
- background: #fff;
- transition: transform 0.3s ease;
- box-shadow: var(--shadow-hard);
- height: 100%;
- display: flex;
- flex-direction: column;
-}
-
-.project-card:hover {
- transform: translateY(-10px);
- box-shadow: 8px 8px 0 #000;
-}
-
-.card-img-holder {
- height: 250px;
- display: flex;
- align-items: center;
- justify-content: center;
- border-bottom: 2px solid #000;
- position: relative;
- font-size: 4rem;
-}
-
-.placeholder-art {
- transition: transform 0.3s ease;
-}
-
-.project-card:hover .placeholder-art {
- transform: scale(1.2) rotate(10deg);
-}
-
-.bg-soft-blue { background-color: #e0f2fe; }
-.bg-soft-green { background-color: #dcfce7; }
-.bg-soft-purple { background-color: #f3e8ff; }
-.bg-soft-yellow { background-color: #fef9c3; }
-
-.category-tag {
- position: absolute;
- top: 15px;
- right: 15px;
- background: #000;
- color: #fff;
- padding: 5px 12px;
- border-radius: 20px;
- font-size: 0.75rem;
- font-weight: 700;
-}
-
-.card-body { padding: 1.5rem; }
-
-.link-arrow {
- text-decoration: none;
- color: #000;
- font-weight: 700;
- display: inline-flex;
- align-items: center;
- margin-top: auto;
-}
-
-.link-arrow i { transition: transform 0.2s; margin-left: 5px; }
-.link-arrow:hover i { transform: translateX(5px); }
-
-/* About */
-.about-image-stack {
- position: relative;
- height: 400px;
- width: 100%;
-}
-
-.stack-card {
- position: absolute;
- width: 80%;
- height: 100%;
- border-radius: var(--radius-card);
- border: 2px solid #000;
- box-shadow: var(--shadow-hard);
- left: 10%;
- transform: rotate(-3deg);
- background-size: cover;
-}
-
-/* Forms */
-.form-control {
- border: 2px solid #000;
- border-radius: 0.5rem;
- padding: 1rem;
+.btn-outline-dark {
+ border-radius: 8px;
font-weight: 500;
- background: #f8f9fa;
-}
-
-.form-control:focus {
- box-shadow: 4px 4px 0 var(--color-primary);
- border-color: #000;
- background: #fff;
-}
-
-/* Animations */
-.animate-up {
- opacity: 0;
- transform: translateY(30px);
- animation: fadeUp 0.8s ease forwards;
-}
-
-.delay-100 { animation-delay: 0.1s; }
-.delay-200 { animation-delay: 0.2s; }
-
-@keyframes fadeUp {
- to {
- opacity: 1;
- transform: translateY(0);
- }
-}
-
-/* Social */
-.social-links a {
- transition: transform 0.2s;
- display: inline-block;
-}
-.social-links a:hover {
- transform: scale(1.2) rotate(10deg);
- color: var(--color-accent) !important;
-}
-
-/* Responsive */
-@media (max-width: 991px) {
- .rotate-divider {
- transform: rotate(0);
- margin-top: 0;
- margin-bottom: 2rem;
- }
-
- .hero-section {
- padding-top: 120px;
- text-align: center;
- min-height: auto;
- padding-bottom: 100px;
- }
-
- .display-1 { font-size: 3.5rem; }
-
- .blob-1 { width: 300px; height: 300px; right: -20%; }
- .blob-2 { width: 300px; height: 300px; left: -20%; }
-}
+}
\ No newline at end of file
diff --git a/assets/images/pexels/2416483.jpg b/assets/images/pexels/2416483.jpg
new file mode 100644
index 0000000..d42acfb
Binary files /dev/null and b/assets/images/pexels/2416483.jpg differ
diff --git a/assets/images/pexels/gen_2766c2b5e438b7584be2a2b709ce59fc.jpg b/assets/images/pexels/gen_2766c2b5e438b7584be2a2b709ce59fc.jpg
new file mode 100644
index 0000000..0015f61
Binary files /dev/null and b/assets/images/pexels/gen_2766c2b5e438b7584be2a2b709ce59fc.jpg differ
diff --git a/assets/images/pexels/gen_2db11fa6a0c517d176327af35602fc3f.jpg b/assets/images/pexels/gen_2db11fa6a0c517d176327af35602fc3f.jpg
new file mode 100644
index 0000000..ed35479
Binary files /dev/null and b/assets/images/pexels/gen_2db11fa6a0c517d176327af35602fc3f.jpg differ
diff --git a/assets/images/pexels/gen_2e4b0855eec62a17f61bb46cdce9ffb1.jpg b/assets/images/pexels/gen_2e4b0855eec62a17f61bb46cdce9ffb1.jpg
new file mode 100644
index 0000000..cffe348
Binary files /dev/null and b/assets/images/pexels/gen_2e4b0855eec62a17f61bb46cdce9ffb1.jpg differ
diff --git a/assets/images/pexels/gen_68d5114c20a78dadeeed57878c123376.jpg b/assets/images/pexels/gen_68d5114c20a78dadeeed57878c123376.jpg
new file mode 100644
index 0000000..9c9c87c
Binary files /dev/null and b/assets/images/pexels/gen_68d5114c20a78dadeeed57878c123376.jpg differ
diff --git a/assets/images/pexels/gen_c61ca490ae21d80fd26565e96bff8734.jpg b/assets/images/pexels/gen_c61ca490ae21d80fd26565e96bff8734.jpg
new file mode 100644
index 0000000..bb8e36c
Binary files /dev/null and b/assets/images/pexels/gen_c61ca490ae21d80fd26565e96bff8734.jpg differ
diff --git a/assets/images/pexels/gen_db78761d52f98962dc88bfa8c6c3e519.jpg b/assets/images/pexels/gen_db78761d52f98962dc88bfa8c6c3e519.jpg
new file mode 100644
index 0000000..521f984
Binary files /dev/null and b/assets/images/pexels/gen_db78761d52f98962dc88bfa8c6c3e519.jpg differ
diff --git a/assets/images/pexels/gen_e718695e9cf74bedd73931b397f91dfa.jpg b/assets/images/pexels/gen_e718695e9cf74bedd73931b397f91dfa.jpg
new file mode 100644
index 0000000..6e92abb
Binary files /dev/null and b/assets/images/pexels/gen_e718695e9cf74bedd73931b397f91dfa.jpg differ
diff --git a/assets/js/main.js b/assets/js/main.js
index fdf2cfd..03a23f7 100644
--- a/assets/js/main.js
+++ b/assets/js/main.js
@@ -1,73 +1,286 @@
document.addEventListener('DOMContentLoaded', () => {
+ const form = document.getElementById('generation-form');
+ const mediaType = document.getElementById('media-type');
+ const videoProviderContainer = document.getElementById('video-provider-container');
+ const generateBtn = document.getElementById('generate-btn');
+ const placeholderText = document.getElementById('placeholder-text');
+ const loadingState = document.getElementById('loading-state');
+ const contentContainer = document.getElementById('content-container');
+ const actionButtons = document.getElementById('action-buttons');
+ const downloadBtn = document.getElementById('download-btn');
+ const editBtn = document.getElementById('edit-btn');
+ const providerBadge = document.getElementById('provider-badge');
+ const infoOverlay = document.getElementById('info-overlay');
+ const statusMessage = document.getElementById('status-message');
+
+ // Editor Elements
+ const editorModal = new bootstrap.Modal(document.getElementById('editorModal'));
+ const editorCanvas = document.getElementById('editor-canvas');
+ const ctx = editorCanvas.getContext('2d');
+ const filterRanges = document.querySelectorAll('.filter-range');
+ const resetFiltersBtn = document.getElementById('reset-filters');
+ const saveEditedBtn = document.getElementById('save-edited-btn');
+ const editorLoading = document.getElementById('editor-loading');
- // Smooth scrolling for navigation links
- document.querySelectorAll('a[href^="#"]').forEach(anchor => {
- anchor.addEventListener('click', function (e) {
- e.preventDefault();
- const targetId = this.getAttribute('href');
- if (targetId === '#') return;
-
- const targetElement = document.querySelector(targetId);
- if (targetElement) {
- // Close mobile menu if open
- const navbarToggler = document.querySelector('.navbar-toggler');
- const navbarCollapse = document.querySelector('.navbar-collapse');
- if (navbarCollapse.classList.contains('show')) {
- navbarToggler.click();
- }
+ // AI Magic Elements
+ const aiEditPrompt = document.getElementById('ai-edit-prompt');
+ const applyAiMagicBtn = document.getElementById('apply-ai-magic');
+ const removeBgBtn = document.getElementById('remove-bg-btn');
+ const upscaleBtn = document.getElementById('upscale-btn');
+
+ let currentImage = null;
+ let originalPrompt = '';
- // Scroll with offset
- const offset = 80;
- const elementPosition = targetElement.getBoundingClientRect().top;
- const offsetPosition = elementPosition + window.pageYOffset - offset;
+ // Local storage for settings
+ const getRapidKey = () => localStorage.getItem('rapidapi_key') || '';
+ const setRapidKey = (key) => localStorage.setItem('rapidapi_key', key);
- window.scrollTo({
- top: offsetPosition,
- behavior: "smooth"
- });
- }
- });
- });
+ // Sync input with local storage
+ const keyInput = document.getElementById('rapidapi-key-input');
+ if (keyInput) {
+ keyInput.value = getRapidKey();
+ keyInput.addEventListener('change', (e) => setRapidKey(e.target.value));
+ }
- // Navbar scroll effect
- const navbar = document.querySelector('.navbar');
- window.addEventListener('scroll', () => {
- if (window.scrollY > 50) {
- navbar.classList.add('scrolled', 'shadow-sm', 'bg-white');
- navbar.classList.remove('bg-transparent');
+ // Toggle video provider field
+ mediaType.addEventListener('change', () => {
+ if (mediaType.value === 'video') {
+ videoProviderContainer.style.display = 'block';
} else {
- navbar.classList.remove('scrolled', 'shadow-sm', 'bg-white');
- navbar.classList.add('bg-transparent');
+ videoProviderContainer.style.display = 'none';
}
});
- // Intersection Observer for fade-up animations
- const observerOptions = {
- threshold: 0.1,
- rootMargin: "0px 0px -50px 0px"
- };
+ form.addEventListener('submit', async (e) => {
+ e.preventDefault();
+
+ const formData = new FormData(form);
+ formData.append('rapidapi_key', getRapidKey());
+ originalPrompt = formData.get('prompt');
- const observer = new IntersectionObserver((entries) => {
- entries.forEach(entry => {
- if (entry.isIntersecting) {
- entry.target.classList.add('animate-up');
- entry.target.style.opacity = "1";
- observer.unobserve(entry.target); // Only animate once
+ // UI State: Loading
+ placeholderText.classList.add('d-none');
+ contentContainer.classList.add('d-none');
+ actionButtons.classList.add('d-none');
+ infoOverlay.classList.add('d-none');
+ statusMessage.classList.add('d-none');
+ loadingState.classList.remove('d-none');
+ generateBtn.disabled = true;
+
+ try {
+ const response = await fetch('api/generate.php', {
+ method: 'POST',
+ body: formData
+ });
+
+ const result = await response.json();
+
+ if (result.success) {
+ renderResult(result);
+ } else {
+ alert('Ошибка: ' + (result.error || 'Что-то пошло не так'));
+ resetUI();
}
- });
- }, observerOptions);
-
- // Select elements to animate (add a class 'reveal' to them in HTML if not already handled by CSS animation)
- // For now, let's just make sure the hero animations run.
- // If we want scroll animations, we'd add opacity: 0 to elements in CSS and reveal them here.
- // Given the request, the CSS animation I added runs on load for Hero.
- // Let's make the project cards animate in.
-
- const projectCards = document.querySelectorAll('.project-card');
- projectCards.forEach((card, index) => {
- card.style.opacity = "0";
- card.style.animationDelay = `${index * 0.1}s`;
- observer.observe(card);
+ } catch (error) {
+ console.error('Generation error:', error);
+ alert('Сетевая ошибка при генерации');
+ resetUI();
+ } finally {
+ loadingState.classList.add('d-none');
+ generateBtn.disabled = false;
+ }
});
+ function renderResult(result) {
+ contentContainer.innerHTML = '';
+ contentContainer.classList.remove('d-none');
+ actionButtons.classList.remove('d-none');
+ infoOverlay.classList.remove('d-none');
+
+ providerBadge.textContent = result.provider;
+ if (result.is_ai) {
+ providerBadge.className = 'badge badge-ai shadow-sm';
+ } else {
+ providerBadge.className = 'badge badge-stock shadow-sm';
+ }
+
+ if (result.message) {
+ statusMessage.textContent = result.message;
+ statusMessage.classList.remove('d-none');
+ }
+
+ if (result.type === 'photo') {
+ const img = document.createElement('img');
+ img.src = result.url;
+ img.className = 'img-fluid shadow-sm rounded mx-auto d-block';
+ img.style.maxHeight = '480px';
+ img.style.objectFit = 'contain';
+ img.id = 'active-result-img';
+ contentContainer.appendChild(img);
+
+ editBtn.classList.remove('d-none');
+ editBtn.onclick = () => {
+ originalPrompt = result.prompt || originalPrompt;
+ openEditor(result.url);
+ };
+ } else {
+ const video = document.createElement('video');
+ video.src = result.url;
+ video.controls = true;
+ video.autoplay = true;
+ video.className = 'rounded mx-auto d-block shadow-sm';
+ video.style.maxWidth = '100%';
+ video.style.maxHeight = '480px';
+ contentContainer.appendChild(video);
+ editBtn.classList.add('d-none');
+ }
+
+ downloadBtn.onclick = () => {
+ const a = document.createElement('a');
+ a.href = result.url;
+ a.download = `generation_${Date.now()}.${result.type === 'photo' ? 'jpg' : 'mp4'}`;
+ document.body.appendChild(a);
+ a.click();
+ document.body.removeChild(a);
+ };
+ }
+
+ function resetUI() {
+ placeholderText.classList.remove('d-none');
+ contentContainer.classList.add('d-none');
+ actionButtons.classList.add('d-none');
+ infoOverlay.classList.add('d-none');
+ }
+
+ // Editor Logic
+ function openEditor(url) {
+ currentImage = new Image();
+ currentImage.crossOrigin = "Anonymous";
+ currentImage.src = url;
+ currentImage.onload = () => {
+ editorCanvas.width = currentImage.width;
+ editorCanvas.height = currentImage.height;
+ applyFilters();
+ editorModal.show();
+ };
+ }
+
+ function applyFilters() {
+ if (!currentImage) return;
+
+ let filters = '';
+ filterRanges.forEach(range => {
+ const filter = range.dataset.filter;
+ const value = range.value;
+
+ if (filter === 'brightness' || filter === 'contrast' || filter === 'saturate') {
+ filters += `${filter}(${value}%) `;
+ } else if (filter === 'blur') {
+ filters += `${filter}(${value}px) `;
+ } else {
+ filters += `${filter}(${value}%) `;
+ }
+ });
+
+ ctx.filter = filters;
+ ctx.clearRect(0, 0, editorCanvas.width, editorCanvas.height);
+ ctx.drawImage(currentImage, 0, 0);
+ }
+
+ filterRanges.forEach(range => {
+ range.addEventListener('input', applyFilters);
+ });
+
+ resetFiltersBtn.addEventListener('click', () => {
+ filterRanges.forEach(range => {
+ if (range.dataset.filter === 'brightness' || range.dataset.filter === 'contrast' || range.dataset.filter === 'saturate') {
+ range.value = 100;
+ } else {
+ range.value = 0;
+ }
+ });
+ applyFilters();
+ });
+
+ saveEditedBtn.addEventListener('click', () => {
+ const link = document.createElement('a');
+ link.download = `edited_${Date.now()}.png`;
+ link.href = editorCanvas.toDataURL('image/png');
+ link.click();
+ });
+
+ // AI Magic Handlers
+ async function performAiEdit(action, customPrompt = '') {
+ if (!currentImage) return;
+
+ editorLoading.classList.remove('d-none');
+ editorLoading.classList.add('d-flex');
+
+ try {
+ const response = await fetch('api/edit.php', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({
+ action: action,
+ original_prompt: originalPrompt,
+ edit_prompt: customPrompt,
+ image_url: currentImage.src
+ })
+ });
+
+ const result = await response.json();
+
+ if (result.success) {
+ // Load new image into canvas
+ const newImg = new Image();
+ newImg.crossOrigin = "Anonymous";
+ newImg.src = result.url + '?t=' + Date.now();
+ newImg.onload = () => {
+ currentImage = newImg;
+ editorCanvas.width = newImg.width;
+ editorCanvas.height = newImg.height;
+ applyFilters();
+ editorLoading.classList.add('d-none');
+ editorLoading.classList.remove('d-flex');
+ };
+ } else {
+ alert('Ошибка ИИ: ' + (result.error || 'Не удалось применить изменения'));
+ editorLoading.classList.add('d-none');
+ editorLoading.classList.remove('d-flex');
+ }
+ } catch (error) {
+ console.error('AI Edit error:', error);
+ alert('Сетевая ошибка при работе с ИИ');
+ editorLoading.classList.add('d-none');
+ editorLoading.classList.remove('d-flex');
+ }
+ }
+
+ applyAiMagicBtn.addEventListener('click', () => {
+ const prompt = aiEditPrompt.value.trim();
+ if (!prompt) {
+ alert('Введите описание изменений');
+ return;
+ }
+ performAiEdit('magic', prompt);
+ });
+
+ removeBgBtn.addEventListener('click', () => {
+ performAiEdit('remove_bg');
+ });
+
+ upscaleBtn.addEventListener('click', () => {
+ performAiEdit('upscale');
+ });
+
+ // History Edit Buttons
+ document.addEventListener('click', (e) => {
+ if (e.target.closest('.history-edit-btn')) {
+ const btn = e.target.closest('.history-edit-btn');
+ const url = btn.dataset.url;
+ const card = btn.closest('.card');
+ originalPrompt = card.querySelector('.card-text').textContent;
+ openEditor(url);
+ }
+ });
});
\ No newline at end of file
diff --git a/db/migrations/001_init_history.sql b/db/migrations/001_init_history.sql
new file mode 100644
index 0000000..033c718
--- /dev/null
+++ b/db/migrations/001_init_history.sql
@@ -0,0 +1,7 @@
+CREATE TABLE IF NOT EXISTS media_history (
+ id INT AUTO_INCREMENT PRIMARY KEY,
+ type VARCHAR(10) NOT NULL,
+ prompt TEXT NOT NULL,
+ result_url TEXT,
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
+);
diff --git a/healthz.php b/healthz.php
new file mode 100644
index 0000000..40fe687
--- /dev/null
+++ b/healthz.php
@@ -0,0 +1,11 @@
+query('SELECT 1');
+ echo json_encode(['status' => 'ok', 'database' => 'connected', 'php_version' => PHP_VERSION, 'time' => date('c')]);
+} catch (Exception $e) {
+ http_response_code(500);
+ echo json_encode(['status' => 'error', 'message' => $e->getMessage()]);
+}
diff --git a/includes/pexels.php b/includes/pexels.php
new file mode 100644
index 0000000..bd44d74
--- /dev/null
+++ b/includes/pexels.php
@@ -0,0 +1,27 @@
+ 0 ? $k : 'Vc99rnmOhHhJAbgGQoKLZtsaIVfkeownoQNbTj78VemUjKh08ZYRbf18';
+}
+
+function pexels_get($url) {
+ $ch = curl_init();
+ curl_setopt_array($ch, [
+ CURLOPT_URL => $url,
+ CURLOPT_RETURNTRANSFER => true,
+ CURLOPT_HTTPHEADER => [ 'Authorization: '. pexels_key() ],
+ CURLOPT_TIMEOUT => 15,
+ ]);
+ $resp = curl_exec($ch);
+ $code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
+ curl_close($ch);
+ if ($code >= 200 && $code < 300 && $resp) return json_decode($resp, true);
+ return null;
+}
+
+function download_to($srcUrl, $destPath) {
+ $data = file_get_contents($srcUrl);
+ if ($data === false) return false;
+ if (!is_dir(dirname($destPath))) mkdir(dirname($destPath), 0775, true);
+ return file_put_contents($destPath, $data) !== false;
+}
diff --git a/index.php b/index.php
index 7205f3d..20140fa 100644
--- a/index.php
+++ b/index.php
@@ -1,150 +1,468 @@
-
-
+
+
-
-
- New Style
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
Analyzing your requirements and generating your website…
-
- Loading…
-
-
= ($_SERVER['HTTP_HOST'] ?? '') === 'appwizzy.com' ? 'AppWizzy' : 'Flatlogic' ?> AI is collecting your requirements and applying the first changes.
-
This page will update automatically as the plan is implemented.
-
Runtime: PHP = htmlspecialchars($phpVersion) ?> — UTC = htmlspecialchars($now) ?>
+
+
+
+
+
+
+
+
+
Создавайте ИИ-контент мгновенно
+
Используйте мощные API для генерации уникальных изображений и видео по текстовому описанию.
+
+
+
+
+
+
+
+
+
+
+
Ваше творение появится здесь
+
+
+
+
+ Loading...
+
+
Магия ИИ в процессе...
+
Генерация может занять некоторое время
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Генерация фото работает без ключа. Для видео через ИИ нужен бесплатный ключ RapidAPI.
+
+
+
+
+
+
+
+
+
Недавние генерации
+
+
+
+ query("SELECT * FROM media_history ORDER BY created_at DESC LIMIT 6");
+ $history = $stmt->fetchAll();
+ if ($history):
+ foreach ($history as $item):
+ ?>
+
+
+
+
+
; ?>)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
История пуста. Создайте что-нибудь!
';
+ endif;
+ } catch (Exception $e) {
+ echo '';
+ }
+ ?>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Применяем ИИ магию...
+
+
+
+
+
+
+
+
+ -
+
+
+ -
+
+
+
+
+
+
+
+
+
+
+
Умное редактирование
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ИИ анализирует фото и перерисовывает его согласно вашим пожеланиям.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Этот ключ хранится только в вашем браузере.
+
+
+
+
+
Где получить бесплатный ключ для видео?
+
Зарегистрируйтесь на RapidAPI и подпишитесь на один из этих бесплатных планов (Free Tier):
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
\ No newline at end of file