Autosave: 20260125-175638
90
api/edit.php
Normal file
@ -0,0 +1,90 @@
|
||||
<?php
|
||||
header('Content-Type: application/json');
|
||||
require_once __DIR__ . '/../db/config.php';
|
||||
require_once __DIR__ . '/../ai/LocalAIApi.php';
|
||||
|
||||
$data = json_decode(file_get_contents('php://input'), true);
|
||||
|
||||
if (!$data) {
|
||||
echo json_encode(['success' => 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()]);
|
||||
}
|
||||
184
api/generate.php
Normal file
@ -0,0 +1,184 @@
|
||||
<?php
|
||||
header('Content-Type: application/json');
|
||||
require_once __DIR__ . '/../db/config.php';
|
||||
require_once __DIR__ . '/../includes/pexels.php';
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
||||
echo json_encode(['success' => 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()]);
|
||||
}
|
||||
@ -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%; }
|
||||
}
|
||||
}
|
||||
BIN
assets/images/pexels/2416483.jpg
Normal file
|
After Width: | Height: | Size: 108 KiB |
BIN
assets/images/pexels/gen_2766c2b5e438b7584be2a2b709ce59fc.jpg
Normal file
|
After Width: | Height: | Size: 86 KiB |
BIN
assets/images/pexels/gen_2db11fa6a0c517d176327af35602fc3f.jpg
Normal file
|
After Width: | Height: | Size: 111 KiB |
BIN
assets/images/pexels/gen_2e4b0855eec62a17f61bb46cdce9ffb1.jpg
Normal file
|
After Width: | Height: | Size: 105 KiB |
BIN
assets/images/pexels/gen_68d5114c20a78dadeeed57878c123376.jpg
Normal file
|
After Width: | Height: | Size: 74 KiB |
BIN
assets/images/pexels/gen_c61ca490ae21d80fd26565e96bff8734.jpg
Normal file
|
After Width: | Height: | Size: 82 KiB |
BIN
assets/images/pexels/gen_db78761d52f98962dc88bfa8c6c3e519.jpg
Normal file
|
After Width: | Height: | Size: 70 KiB |
BIN
assets/images/pexels/gen_e718695e9cf74bedd73931b397f91dfa.jpg
Normal file
|
After Width: | Height: | Size: 70 KiB |
@ -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);
|
||||
}
|
||||
});
|
||||
});
|
||||
7
db/migrations/001_init_history.sql
Normal file
@ -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
|
||||
);
|
||||
11
healthz.php
Normal file
@ -0,0 +1,11 @@
|
||||
<?php
|
||||
header('Content-Type: application/json');
|
||||
require_once 'db/config.php';
|
||||
|
||||
try {
|
||||
db()->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()]);
|
||||
}
|
||||
27
includes/pexels.php
Normal file
@ -0,0 +1,27 @@
|
||||
<?php
|
||||
function pexels_key() {
|
||||
$k = getenv('PEXELS_KEY');
|
||||
return $k && strlen($k) > 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;
|
||||
}
|
||||
606
index.php
@ -1,150 +1,468 @@
|
||||
<?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');
|
||||
require_once 'db/config.php';
|
||||
$project_name = $_SERVER['PROJECT_NAME'] ?? 'AI Media Forge';
|
||||
$project_description = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Generate unique AI photos and videos for free.';
|
||||
?>
|
||||
<!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">
|
||||
<title><?php echo htmlspecialchars($project_name); ?></title>
|
||||
<meta name="description" content="<?php echo htmlspecialchars($project_description); ?>">
|
||||
|
||||
<!-- Bootstrap 5 CSS -->
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
<!-- Google Fonts: Inter -->
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
|
||||
<!-- Font Awesome -->
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
||||
<!-- Custom CSS -->
|
||||
<link rel="stylesheet" href="assets/css/custom.css?v=<?php echo time(); ?>">
|
||||
<style>
|
||||
.badge-ai { background: linear-gradient(45deg, #007bff, #6610f2); }
|
||||
.badge-stock { background: #6c757d; }
|
||||
.form-select, .form-control { border-radius: 8px; }
|
||||
.btn-primary { border-radius: 8px; background: #000; border: none; }
|
||||
.btn-primary:hover { background: #333; }
|
||||
.card { border-radius: 16px; }
|
||||
.editor-preview-container {
|
||||
background: #1a1a1a;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-height: 400px;
|
||||
position: relative;
|
||||
}
|
||||
#editor-canvas {
|
||||
max-width: 100%;
|
||||
max-height: 60vh;
|
||||
object-fit: contain;
|
||||
}
|
||||
.filter-control label {
|
||||
font-size: 0.8rem;
|
||||
font-weight: 600;
|
||||
color: #444;
|
||||
display: block;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
.nav-pills .nav-link {
|
||||
font-size: 0.85rem;
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
color: #666;
|
||||
border-radius: 8px;
|
||||
padding: 8px 16px;
|
||||
}
|
||||
.nav-pills .nav-link.active {
|
||||
background-color: #000;
|
||||
color: #fff;
|
||||
}
|
||||
.ai-magic-box {
|
||||
background: #f8f9fa;
|
||||
border-radius: 12px;
|
||||
padding: 15px;
|
||||
border: 1px solid #eee;
|
||||
}
|
||||
#editor-loading {
|
||||
background: rgba(0,0,0,0.7);
|
||||
z-index: 10;
|
||||
}
|
||||
</style>
|
||||
</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>
|
||||
<body class="bg-light">
|
||||
|
||||
<nav class="navbar navbar-expand-lg navbar-dark bg-dark sticky-top">
|
||||
<div class="container">
|
||||
<a class="navbar-brand fw-bold" href="/">
|
||||
<i class="fas fa-robot me-2"></i><?php echo htmlspecialchars($project_name); ?>
|
||||
</a>
|
||||
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
<div class="collapse navbar-collapse" id="navbarNav">
|
||||
<ul class="navbar-nav ms-auto align-items-center">
|
||||
<li class="nav-item"><a class="nav-link active" href="#">Генерация</a></li>
|
||||
<li class="nav-item"><a class="nav-link" href="#history">История</a></li>
|
||||
<li class="nav-item">
|
||||
<button class="btn btn-outline-light btn-sm ms-lg-3" data-bs-toggle="modal" data-bs-target="#settingsModal">
|
||||
<i class="fas fa-cog"></i> Настройки
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
<footer>
|
||||
Page updated: <?= htmlspecialchars($now) ?> (UTC)
|
||||
</footer>
|
||||
</nav>
|
||||
|
||||
<main class="container py-5">
|
||||
<!-- Hero & Generator Section -->
|
||||
<section class="row justify-content-center mb-5">
|
||||
<div class="col-lg-10 text-center mb-4">
|
||||
<h1 class="display-5 fw-bold text-dark mb-2">Создавайте ИИ-контент мгновенно</h1>
|
||||
<p class="lead text-muted">Используйте мощные API для генерации уникальных изображений и видео по текстовому описанию.</p>
|
||||
</div>
|
||||
|
||||
<div class="col-lg-12">
|
||||
<div class="card border-0 shadow-lg overflow-hidden">
|
||||
<div class="card-body p-0">
|
||||
<div class="bg-white p-4 p-md-4 border-bottom">
|
||||
<form id="generation-form">
|
||||
<div class="row g-3">
|
||||
<div class="col-md-3">
|
||||
<label class="form-label small fw-bold text-uppercase">Тип контента</label>
|
||||
<select class="form-select border-2" id="media-type" name="type">
|
||||
<option value="photo">Изображение (Real AI)</option>
|
||||
<option value="video">Видео</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-3" id="video-provider-container" style="display: none;">
|
||||
<label class="form-label small fw-bold text-uppercase">Провайдер</label>
|
||||
<select class="form-select border-2" id="video-provider" name="video_provider">
|
||||
<option value="pexels">Сток (Всегда Бесплатно)</option>
|
||||
<option value="ai">ИИ (Требуется Ключ)</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<label class="form-label small fw-bold text-uppercase">Стиль</label>
|
||||
<select class="form-select border-2" id="style" name="style">
|
||||
<option value="">Без стиля</option>
|
||||
<option value="anime">Аниме</option>
|
||||
<option value="realism">Реализм</option>
|
||||
<option value="cyberpunk">Киберпанк</option>
|
||||
<option value="vaporwave">Вейпорвейв</option>
|
||||
<option value="pixel-art">Пиксель-арт</option>
|
||||
<option value="fantasy">Фэнтези</option>
|
||||
<option value="3d-render">3D Рендер</option>
|
||||
<option value="steampunk">Стимпанк</option>
|
||||
<option value="oil-painting">Масляная живопись</option>
|
||||
<option value="sketch">Скетч</option>
|
||||
<option value="pop-art">Поп-арт</option>
|
||||
<option value="minimalism">Минимализм</option>
|
||||
<option value="cinematic">Кинематографичный</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
<label class="form-label small fw-bold text-uppercase">Формат</label>
|
||||
<select class="form-select border-2" id="aspect_ratio" name="aspect_ratio">
|
||||
<option value="1:1">1:1 Квадрат</option>
|
||||
<option value="16:9">16:9 Широкий</option>
|
||||
<option value="9:16">9:16 Вертикаль</option>
|
||||
<option value="4:3">4:3 Классик</option>
|
||||
<option value="3:2">3:2 Фото</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<label class="form-label small fw-bold text-uppercase">Ваш запрос</label>
|
||||
<input type="text" class="form-control border-2" id="prompt" name="prompt" placeholder="Например: 'Космонавт на лошади'..." required>
|
||||
</div>
|
||||
<div class="col-md-2 d-flex align-items-end">
|
||||
<button type="submit" class="btn btn-primary w-100 fw-bold py-2" id="generate-btn">
|
||||
Создать
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div id="result-preview" class="bg-dark position-relative" style="min-height: 500px; display: flex; align-items: center; justify-content: center;">
|
||||
<div class="text-center text-secondary" id="placeholder-text">
|
||||
<i class="fas fa-wand-magic-sparkles fa-3x mb-3 d-block"></i>
|
||||
<p>Ваше творение появится здесь</p>
|
||||
</div>
|
||||
|
||||
<div class="loading-spinner text-center d-none" id="loading-state">
|
||||
<div class="spinner-grow text-primary" role="status" style="width: 3rem; height: 3rem;">
|
||||
<span class="visually-hidden">Loading...</span>
|
||||
</div>
|
||||
<p class="mt-3 text-light fw-medium">Магия ИИ в процессе...</p>
|
||||
<small class="text-muted d-block mt-2">Генерация может занять некоторое время</small>
|
||||
</div>
|
||||
|
||||
<div id="content-container" class="d-none w-100 h-100 p-3 text-center">
|
||||
<!-- Generated content will be injected here -->
|
||||
</div>
|
||||
|
||||
<!-- Info Overlay -->
|
||||
<div id="info-overlay" class="position-absolute top-0 start-0 p-3 d-none">
|
||||
<span class="badge badge-ai shadow-sm" id="provider-badge"></span>
|
||||
</div>
|
||||
|
||||
<!-- Action Overlay -->
|
||||
<div id="action-buttons" class="position-absolute bottom-0 end-0 p-3 d-none d-flex gap-2">
|
||||
<button class="btn btn-warning btn-sm shadow-sm d-none" id="edit-btn">
|
||||
<i class="fas fa-magic me-1"></i> Редактировать
|
||||
</button>
|
||||
<button class="btn btn-light btn-sm shadow-sm" id="download-btn">
|
||||
<i class="fas fa-download me-1"></i> Скачать
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="status-message" class="mt-3 alert alert-info d-none" role="alert">
|
||||
<!-- Status/Warning messages go here -->
|
||||
</div>
|
||||
|
||||
<div class="mt-4 text-center">
|
||||
<p class="small text-muted">
|
||||
<i class="fas fa-info-circle me-1"></i>
|
||||
Генерация <strong>фото</strong> работает без ключа. Для <strong>видео через ИИ</strong> нужен бесплатный ключ <a href="#" data-bs-toggle="modal" data-bs-target="#settingsModal">RapidAPI</a>.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- History & Gallery Section -->
|
||||
<section id="history" class="py-5">
|
||||
<div class="d-flex align-items-center justify-content-between mb-4">
|
||||
<h2 class="h3 fw-bold mb-0">Недавние генерации</h2>
|
||||
</div>
|
||||
|
||||
<div class="row g-4" id="history-grid">
|
||||
<?php
|
||||
try {
|
||||
$stmt = db()->query("SELECT * FROM media_history ORDER BY created_at DESC LIMIT 6");
|
||||
$history = $stmt->fetchAll();
|
||||
if ($history):
|
||||
foreach ($history as $item):
|
||||
?>
|
||||
<div class="col-md-4 col-sm-6">
|
||||
<div class="card h-100 border-0 shadow-sm history-card">
|
||||
<div class="position-relative">
|
||||
<?php if ($item['type'] === 'photo'): ?>
|
||||
<img src="<?php echo htmlspecialchars($item['result_url']); ?>" class="card-img-top history-img" data-url="<?php echo htmlspecialchars($item['result_url']); ?>" alt="AI result" style="height: 250px; object-fit: contain; background: #222;">
|
||||
<?php else: ?>
|
||||
<div class="bg-dark d-flex align-items-center justify-content-center" style="height: 250px;">
|
||||
<video class="w-100 h-100" style="object-fit: contain;" muted onmouseover="this.play()" onmouseout="this.pause();this.currentTime=0;">
|
||||
<source src="<?php echo htmlspecialchars($item['result_url']); ?>" type="video/mp4">
|
||||
</video>
|
||||
<i class="fas fa-play position-absolute text-white opacity-50"></i>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<span class="badge bg-dark position-absolute top-0 end-0 m-2 opacity-75">
|
||||
<?php echo $item['type'] === 'photo' ? 'PHOTO' : 'VIDEO'; ?>
|
||||
</span>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<p class="card-text small text-truncate mb-2 fw-medium"><?php echo htmlspecialchars($item['prompt']); ?></p>
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<small class="text-muted"><?php echo date('d.m, H:i', strtotime($item['created_at'])); ?></small>
|
||||
<div>
|
||||
<?php if ($item['type'] === 'photo'): ?>
|
||||
<button class="btn btn-outline-warning btn-sm border-0 history-edit-btn" data-url="<?php echo htmlspecialchars($item['result_url']); ?>">
|
||||
<i class="fas fa-magic"></i>
|
||||
</button>
|
||||
<?php endif; ?>
|
||||
<a href="<?php echo htmlspecialchars($item['result_url']); ?>" target="_blank" class="btn btn-link btn-sm p-0 text-decoration-none ms-1">Открыть <i class="fas fa-external-link-alt ms-1 small"></i></a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php
|
||||
endforeach;
|
||||
else:
|
||||
echo '<div class="col-12 text-center py-5 text-muted"><p>История пуста. Создайте что-нибудь!</p></div>';
|
||||
endif;
|
||||
} catch (Exception $e) {
|
||||
echo '<div class="col-12 text-center py-5 text-danger"><p>Ошибка загрузки истории.</p></div>';
|
||||
}
|
||||
?>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<!-- Photo Editor Modal -->
|
||||
<div class="modal fade" id="editorModal" tabindex="-1" aria-hidden="true">
|
||||
<div class="modal-dialog modal-xl modal-dialog-centered">
|
||||
<div class="modal-content border-0 shadow">
|
||||
<div class="modal-header bg-dark text-white border-0">
|
||||
<h5 class="modal-title fw-bold"><i class="fas fa-magic me-2"></i> Nano Editor AI</h5>
|
||||
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
|
||||
</div>
|
||||
<div class="modal-body p-0">
|
||||
<div class="row g-0">
|
||||
<!-- Preview Area -->
|
||||
<div class="col-lg-8">
|
||||
<div class="editor-preview-container p-3 h-100">
|
||||
<canvas id="editor-canvas"></canvas>
|
||||
<!-- Loading Overlay for AI edits -->
|
||||
<div id="editor-loading" class="position-absolute top-0 start-0 w-100 h-100 d-none flex-column align-items-center justify-content-center text-white">
|
||||
<div class="spinner-border text-primary mb-3" role="status"></div>
|
||||
<span class="fw-bold">Применяем ИИ магию...</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Controls Area -->
|
||||
<div class="col-lg-4 border-start">
|
||||
<div class="p-4 overflow-auto" style="max-height: 80vh;">
|
||||
|
||||
<ul class="nav nav-pills mb-4 justify-content-center" id="editor-tabs" role="tablist">
|
||||
<li class="nav-item" role="presentation">
|
||||
<button class="nav-link active" id="filters-tab" data-bs-toggle="pill" data-bs-target="#filters-panel" type="button" role="tab">Фильтры</button>
|
||||
</li>
|
||||
<li class="nav-item" role="presentation">
|
||||
<button class="nav-link" id="ai-magic-tab" data-bs-toggle="pill" data-bs-target="#ai-magic-panel" type="button" role="tab">ИИ Магия</button>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<div class="tab-content" id="editor-tab-content">
|
||||
<!-- Filters Panel -->
|
||||
<div class="tab-pane fade show active" id="filters-panel" role="tabpanel">
|
||||
<h6 class="fw-bold mb-4 text-uppercase small letter-spacing-1">Настройки фильтров</h6>
|
||||
|
||||
<div class="filter-control mb-4">
|
||||
<label>Яркость</label>
|
||||
<input type="range" class="form-range filter-range" data-filter="brightness" min="0" max="200" value="100">
|
||||
</div>
|
||||
|
||||
<div class="filter-control mb-4">
|
||||
<label>Контраст</label>
|
||||
<input type="range" class="form-range filter-range" data-filter="contrast" min="0" max="200" value="100">
|
||||
</div>
|
||||
|
||||
<div class="filter-control mb-4">
|
||||
<label>Насыщенность</label>
|
||||
<input type="range" class="form-range filter-range" data-filter="saturate" min="0" max="200" value="100">
|
||||
</div>
|
||||
|
||||
<div class="filter-control mb-4">
|
||||
<label>Чёрно-белое</label>
|
||||
<input type="range" class="form-range filter-range" data-filter="grayscale" min="0" max="100" value="0">
|
||||
</div>
|
||||
|
||||
<div class="filter-control mb-4">
|
||||
<label>Сепия</label>
|
||||
<input type="range" class="form-range filter-range" data-filter="sepia" min="0" max="100" value="0">
|
||||
</div>
|
||||
|
||||
<div class="filter-control mb-4">
|
||||
<label>Инверсия</label>
|
||||
<input type="range" class="form-range filter-range" data-filter="invert" min="0" max="100" value="0">
|
||||
</div>
|
||||
|
||||
<div class="filter-control mb-4">
|
||||
<label>Размытие</label>
|
||||
<input type="range" class="form-range filter-range" data-filter="blur" min="0" max="20" value="0">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- AI Magic Panel -->
|
||||
<div class="tab-pane fade" id="ai-magic-panel" role="tabpanel">
|
||||
<h6 class="fw-bold mb-3 text-uppercase small letter-spacing-1">Умное редактирование</h6>
|
||||
|
||||
<div class="ai-magic-box mb-4">
|
||||
<label class="form-label small fw-bold mb-2">Что изменить на фото?</label>
|
||||
<textarea class="form-control mb-2" id="ai-edit-prompt" rows="3" placeholder="Например: 'Добавь коту корону' или 'Сделай фон в стиле киберпанк'"></textarea>
|
||||
<button class="btn btn-primary btn-sm w-100 fw-bold" id="apply-ai-magic">
|
||||
<i class="fas fa-sparkles me-1"></i> Применить магию
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="d-grid gap-2">
|
||||
<button class="btn btn-outline-dark btn-sm" id="remove-bg-btn">
|
||||
<i class="fas fa-eraser me-1"></i> Удалить фон (ИИ)
|
||||
</button>
|
||||
<button class="btn btn-outline-dark btn-sm" id="upscale-btn">
|
||||
<i class="fas fa-expand me-1"></i> Улучшить качество
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="alert alert-info mt-4 small border-0 py-2">
|
||||
<i class="fas fa-lightbulb me-2"></i> ИИ анализирует фото и перерисовывает его согласно вашим пожеланиям.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr class="my-4">
|
||||
|
||||
<div class="d-grid gap-2">
|
||||
<button class="btn btn-outline-danger btn-sm mb-2" id="reset-filters">
|
||||
<i class="fas fa-undo me-1"></i> Сбросить всё
|
||||
</button>
|
||||
<button class="btn btn-dark fw-bold" id="save-edited-btn">
|
||||
<i class="fas fa-download me-1"></i> Скачать результат
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Settings Modal -->
|
||||
<div class="modal fade" id="settingsModal" tabindex="-1">
|
||||
<div class="modal-dialog modal-dialog-centered modal-lg">
|
||||
<div class="modal-content border-0 shadow">
|
||||
<div class="modal-header border-0">
|
||||
<h5 class="modal-title fw-bold">Настройки API</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||
</div>
|
||||
<div class="modal-body p-4">
|
||||
<div class="mb-4">
|
||||
<label class="form-label small fw-bold">Ваш X-RapidAPI-Key</label>
|
||||
<div class="input-group">
|
||||
<span class="input-group-text border-2"><i class="fas fa-key"></i></span>
|
||||
<input type="password" class="form-control border-2" id="rapidapi-key-input" placeholder="Введите ваш ключ...">
|
||||
</div>
|
||||
<div class="form-text mt-2 text-muted">
|
||||
Этот ключ хранится только в вашем браузере.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="alert alert-light border">
|
||||
<h6 class="fw-bold mb-3">Где получить бесплатный ключ для видео?</h6>
|
||||
<p class="small mb-3">Зарегистрируйтесь на <a href="https://rapidapi.com/" target="_blank">RapidAPI</a> и подпишитесь на один из этих бесплатных планов (Free Tier):</p>
|
||||
<div class="list-group list-group-flush small">
|
||||
<a href="https://rapidapi.com/novita-ai-novita-ai-default/api/novita-ai" target="_blank" class="list-group-item list-group-item-action py-3 px-0 border-0">
|
||||
<div class="d-flex w-100 justify-content-between">
|
||||
<h6 class="mb-1 fw-bold">1. Novita AI</h6>
|
||||
<span class="badge bg-success">Рекомендуется</span>
|
||||
</div>
|
||||
<p class="mb-1">Мощная генерация видео и фото.</p>
|
||||
</a>
|
||||
<a href="https://rapidapi.com/moat-io-moat-io-default/api/text-to-video2" target="_blank" class="list-group-item list-group-item-action py-3 px-0 border-0">
|
||||
<div class="d-flex w-100 justify-content-between">
|
||||
<h6 class="mb-1 fw-bold">2. Moat Text-to-Video</h6>
|
||||
</div>
|
||||
<p class="mb-1">Хорошая база бесплатных генераций в день.</p>
|
||||
</a>
|
||||
<a href="https://rapidapi.com/pictory-pictory-default/api/pictory" target="_blank" class="list-group-item list-group-item-action py-3 px-0 border-0">
|
||||
<div class="d-flex w-100 justify-content-between">
|
||||
<h6 class="mb-1 fw-bold">3. Pictory AI</h6>
|
||||
</div>
|
||||
<p class="mb-1">Генерация видео из текста (более длинные ролики).</p>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer border-0">
|
||||
<button type="button" class="btn btn-dark w-100 py-2 fw-bold" data-bs-dismiss="modal">Сохранить настройки</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<footer class="bg-white py-5 border-top mt-5">
|
||||
<div class="container text-center">
|
||||
<p class="text-muted mb-0">© <?php echo date('Y'); ?> <?php echo htmlspecialchars($project_name); ?></p>
|
||||
<div class="mt-2">
|
||||
<span class="badge bg-light text-dark border">PHP <?php echo PHP_VERSION; ?></span>
|
||||
<span class="badge bg-light text-dark border">Flux AI</span>
|
||||
<span class="badge bg-light text-dark border">RapidAPI Ready</span>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<!-- Bootstrap 5 JS -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
|
||||
<!-- Main JS -->
|
||||
<script src="assets/js/main.js?v=<?php echo time(); ?>"></script>
|
||||
</body>
|
||||
</html>
|
||||
</html>
|
||||