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 - - - - - - - - - - - - - - - - - - - + + + <?php echo htmlspecialchars($project_name); ?> + + + + + + + + + + + - -
-
-

Analyzing your requirements and generating your website…

-
- Loading… -
-

AI is collecting your requirements and applying the first changes.

-

This page will update automatically as the plan is implemented.

-

Runtime: PHP — UTC

+ + +
- + + +
+ +
+
+

Создавайте ИИ-контент мгновенно

+

Используйте мощные API для генерации уникальных изображений и видео по текстовому описанию.

+
+ +
+
+
+
+
+
+
+ + +
+ +
+ + +
+
+ + +
+
+ + +
+
+ +
+
+
+
+ +
+
+ +

Ваше творение появится здесь

+
+ +
+
+ Loading... +
+

Магия ИИ в процессе...

+ Генерация может занять некоторое время +
+ +
+ +
+ + +
+ +
+ + +
+ + +
+
+
+
+ + + +
+

+ + Генерация фото работает без ключа. Для видео через ИИ нужен бесплатный ключ RapidAPI. +

+
+
+
+ + +
+
+

Недавние генерации

+
+ +
+ query("SELECT * FROM media_history ORDER BY created_at DESC LIMIT 6"); + $history = $stmt->fetchAll(); + if ($history): + foreach ($history as $item): + ?> +
+
+
+ + AI result + +
+ + +
+ + + + +
+
+

+
+ +
+ + + + Открыть +
+
+
+
+
+

История пуста. Создайте что-нибудь!

'; + endif; + } catch (Exception $e) { + echo '

Ошибка загрузки истории.

'; + } + ?> + +
+
+ + + + + + + + + + + + + - + \ No newline at end of file