soc-io / index.html
nkjoy's picture
한국어로 전체 번역 - Follow Up Deployment
cd88df4 verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>soc.io | AI 기반 소셜 콘텐츠 생성기</title>
<script src="https://cdn.tailwindcss.com"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<style>
@import url('https://fonts.googleapis.com/css2?family=Orbitron:wght@400;500;600;700&family=Inter:wght@300;400;500;600&display=swap');
:root {
--primary: #6d28d9;
--primary-light: #8b5cf6;
--secondary: #10b981;
--dark: #0f172a;
--light: #f8fafc;
--accent: #f59e0b;
}
body {
font-family: 'Inter', sans-serif;
background-color: var(--dark);
color: var(--light);
overflow-x: hidden;
}
.tech-font {
font-family: 'Orbitron', sans-serif;
}
.gradient-text {
background: linear-gradient(90deg, var(--primary-light), var(--secondary));
-webkit-background-clip: text;
background-clip: text;
color: transparent;
}
.glow-box {
box-shadow: 0 0 15px rgba(139, 92, 246, 0.5);
}
.glow-box:hover {
box-shadow: 0 0 25px rgba(139, 92, 246, 0.7);
}
.neon-border {
border: 1px solid rgba(139, 92, 246, 0.3);
position: relative;
}
.neon-border::before {
content: '';
position: absolute;
top: -2px;
left: -2px;
right: -2px;
bottom: -2px;
border: 2px solid transparent;
background: linear-gradient(135deg, var(--primary-light), var(--secondary)) border-box;
-webkit-mask: linear-gradient(#fff 0 0) padding-box, linear-gradient(#fff 0 0);
-webkit-mask-composite: destination-out;
mask-composite: exclude;
border-radius: inherit;
pointer-events: none;
}
.grid-pattern {
background-image:
linear-gradient(rgba(139, 92, 246, 0.1) 1px, transparent 1px),
linear-gradient(90deg, rgba(139, 92, 246, 0.1) 1px, transparent 1px);
background-size: 30px 30px;
}
.pulse-animation {
animation: pulse 2s infinite;
}
@keyframes pulse {
0% { opacity: 0.7; }
50% { opacity: 1; }
100% { opacity: 0.7; }
}
.slide-in {
animation: slideIn 0.5s ease-out forwards;
}
@keyframes slideIn {
from { transform: translateY(20px); opacity: 0; }
to { transform: translateY(0); opacity: 1; }
}
.terminal-text {
font-family: 'Courier New', monospace;
color: #50fa7b;
}
.loading-spinner {
width: 24px;
height: 24px;
border: 3px solid rgba(255,255,255,0.3);
border-radius: 50%;
border-top-color: var(--primary-light);
animation: spin 1s ease-in-out infinite;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
.api-key-modal {
display: none;
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0,0,0,0.8);
z-index: 100;
justify-content: center;
align-items: center;
}
.api-key-modal.active {
display: flex;
}
.generated-image {
max-width: 100%;
max-height: 400px;
border-radius: 0.5rem;
object-fit: contain;
}
</style>
</head>
<body class="min-h-screen grid-pattern">
<!-- Navigation -->
<nav class="bg-black bg-opacity-80 backdrop-blur-md border-b border-purple-900 fixed w-full z-50">
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div class="flex items-center justify-between h-16">
<div class="flex items-center">
<div class="flex-shrink-0 flex items-center">
<i class="fas fa-robot text-purple-500 text-2xl mr-2"></i>
<span class="tech-font text-xl font-bold gradient-text">soc.io</span>
</div>
<div class="hidden md:block">
<div class="ml-10 flex items-baseline space-x-4">
<a href="#" class="text-purple-300 hover:text-white px-3 py-2 rounded-md text-sm font-medium">Dashboard</a>
<a href="#" class="text-gray-300 hover:text-white px-3 py-2 rounded-md text-sm font-medium">Templates</a>
<a href="#" class="text-gray-300 hover:text-white px-3 py-2 rounded-md text-sm font-medium">Gallery</a>
<a href="#" class="text-gray-300 hover:text-white px-3 py-2 rounded-md text-sm font-medium">Analytics</a>
</div>
</div>
</div>
<div class="hidden md:block">
<div class="ml-4 flex items-center md:ml-6">
<button id="generate-btn" class="bg-gradient-to-r from-purple-600 to-indigo-600 hover:from-purple-700 hover:to-indigo-700 text-white px-4 py-2 rounded-md text-sm font-medium mr-3">
<i class="fas fa-bolt mr-1"></i> Generate
</button>
<div class="ml-3 relative">
<div>
<button class="max-w-xs flex items-center text-sm rounded-full focus:outline-none" id="user-menu">
<img class="h-8 w-8 rounded-full" src="https://images.unsplash.com/photo-1472099645785-5658abf4ff4e?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80" alt="">
</button>
</div>
</div>
</div>
</div>
<div class="-mr-2 flex md:hidden">
<button class="inline-flex items-center justify-center p-2 rounded-md text-gray-400 hover:text-white hover:bg-gray-700 focus:outline-none">
<span class="sr-only">Open main menu</span>
<i class="fas fa-bars"></i>
</button>
</div>
</div>
</div>
</nav>
<!-- API Key Modal -->
<div id="api-key-modal" class="api-key-modal">
<div class="bg-gray-900 rounded-xl p-8 max-w-md w-full neon-border glow-box">
<div class="flex justify-between items-center mb-6">
<h3 class="tech-font text-2xl gradient-text">Hugging Face API 키</h3>
<button id="close-modal" class="text-gray-400 hover:text-white">
<i class="fas fa-times"></i>
</button>
</div>
<p class="text-gray-300 mb-4">
이미지를 생성하려면 Stable Diffusion 모델에 액세스할 수 있는 Hugging Face API 키가 필요합니다.
</p>
<div class="mb-6">
<label class="block text-sm font-medium text-gray-300 mb-2">당신의 API 키</label>
<input
type="password"
id="api-key-input"
class="w-full bg-gray-800 border border-gray-700 rounded-lg px-4 py-3 text-white focus:outline-none focus:ring-2 focus:ring-purple-500 focus:border-transparent"
placeholder="hf_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx">
<p class="text-xs text-gray-500 mt-2">
API 키가 없으신가요? <a href="https://huggingface.co/settings/tokens" target="_blank" class="text-purple-400 hover:underline">여기에서 받으세요</a>
</p>
</div>
<div class="flex justify-end gap-3">
<button id="cancel-api-key" class="px-4 py-2 rounded-lg border border-gray-700 text-gray-300 hover:bg-gray-800">
취소
</button>
<button id="save-api-key" class="bg-gradient-to-r from-purple-600 to-indigo-600 hover:from-purple-700 hover:to-indigo-700 text-white px-6 py-2 rounded-lg">
키 저장
</button>
</div>
</div>
</div>
<!-- Main Content -->
<main class="pt-20 pb-12 px-4 sm:px-6 lg:px-8 max-w-7xl mx-auto">
<!-- Hero Section -->
<div class="text-center py-12 slide-in">
<h1 class="tech-font text-4xl md:text-6xl font-bold mb-6">
<span class="gradient-text">AI 기반</span> 소셜 콘텐츠
</h1>
<p class="text-xl text-gray-300 max-w-3xl mx-auto">
<span class="text-purple-300 font-medium">ControlNet</span><span class="text-green-300 font-medium">SDXL</span>의 힘으로 소셜 미디어를 위한 놀라운 시각 자료를 만드세요. 원하는 것을 설명하거나 템플릿을 사용하세요.
</p>
<div class="mt-10 flex flex-col sm:flex-row justify-center gap-4">
<button id="start-creating" class="glow-box bg-gradient-to-r from-purple-600 to-indigo-600 hover:from-purple-700 hover:to-indigo-700 text-white px-8 py-4 rounded-lg text-lg font-medium flex items-center justify-center">
<i class="fas fa-magic mr-3"></i> 만들기 시작
</button>
<button class="neon-border bg-gray-900 hover:bg-gray-800 text-white px-8 py-4 rounded-lg text-lg font-medium flex items-center justify-center">
<i class="fas fa-play-circle mr-3"></i> 데모 보기
</button>
</div>
</div>
<!-- Generator Section -->
<div class="mt-16 neon-border rounded-xl bg-gray-900 bg-opacity-70 p-6 glow-box">
<div class="flex flex-col lg:flex-row gap-8">
<!-- Input Panel -->
<div class="lg:w-1/2">
<h2 class="tech-font text-2xl font-bold mb-6 gradient-text">콘텐츠 생성기</h2>
<div class="mb-6">
<label class="block text-sm font-medium text-gray-300 mb-2">프롬프트</label>
<div class="relative">
<textarea
id="prompt-input"
class="w-full bg-gray-800 border border-gray-700 rounded-lg px-4 py-3 text-white focus:outline-none focus:ring-2 focus:ring-purple-500 focus:border-transparent"
rows="4"
placeholder="생성하고자 하는 이미지를 설명하세요..."></textarea>
<div class="absolute bottom-3 right-3 flex gap-2">
<button class="text-gray-400 hover:text-purple-400">
<i class="fas fa-microphone"></i>
</button>
<button id="prompt-suggestions" class="text-gray-400 hover:text-purple-400">
<i class="fas fa-lightbulb"></i>
</button>
</div>
</div>
</div>
<div class="grid grid-cols-2 gap-4 mb-6">
<div>
<label class="block text-sm font-medium text-gray-300 mb-2">스타일</label>
<select id="style-select" class="w-full bg-gray-800 border border-gray-700 rounded-lg px-4 py-2 text-white focus:outline-none focus:ring-2 focus:ring-purple-500 focus:border-transparent">
<option value="photorealistic">사실적</option>
<option value="digital_art">디지털 아트</option>
<option value="3d_render">3D 렌더</option>
<option value="anime">애니메</option>
<option value="watercolor">수채화</option>
<option value="cyberpunk">사이버펑크</option>
</select>
</div>
<div>
<label class="block text-sm font-medium text-gray-300 mb-2">종횡비</label>
<select id="aspect-r번호 -select" class="w-full bg-gray-800 border border-gray-700 rounded-lg px-4 py-2 text-white focus:outline-none focus:ring-2 focus:ring-purple-500 focus:border-transparent">
<option value="1:1">정사각형 (1:1)</option>
<option value="4:5">인물 (4:5)</option>
<option value="16:9">풍경 (16:9)</option>
<option value="9:16">스토리 (9:16)</option>
</select>
</div>
</div>
<div class="mb-6">
<label class="block text-sm font-medium text-gray-300 mb-2">참조 이미지 업로드 (ControlNet)</label>
<div id="upload-area" class="border-2 border-dashed border-gray-700 rounded-lg p-8 text-center cursor-pointer hover:border-purple-500 transition">
<i class="fas fa-cloud-upload-alt text-4xl text-gray-500 mb-3"></i>
<p class="text-gray-400">이미지를 드래그 앤 드롭하거나 클릭하여 탐색</p>
<p class="text-xs text-gray-500 mt-2">JPG, PNG 지원 (최대 10MB)</p>
<input type="file" id="file-input" class="hidden" accept="image/jpeg,image/png">
</div>
<div id="preview-container" class="hidden mt-4">
<div class="relative">
<img id="preview-image" src="#" alt="미리보기" class="max-h-40 rounded-lg">
<button id="remove-image" class="absolute top-2 right-2 bg-gray-900 bg-opacity-70 text-white p-1 rounded-full hover:bg-gray-800">
<i class="fas fa-times"></i>
</button>
</div>
</div>
</div>
<div class="flex justify-between items-center">
<div class="flex items-center">
<input id="advanced-toggle" type="checkbox" class="h-4 w-4 text-purple-600 focus:ring-purple-500 border-gray-700 rounded">
<label for="advanced-toggle" class="ml-2 block text-sm text-gray-300">고급 설정</label>
</div>
<button id="generate-now-btn" class="bg-gradient-to-r from-purple-600 to-indigo-600 hover:from-purple-700 hover:to-indigo-700 text-white px-6 py-3 rounded-lg font-medium flex items-center">
<i class="fas fa-bolt mr-2"></i> 지금 생성
</button>
</div>
<!-- Advanced Controls (Hidden by default) -->
<div id="advanced-controls" class="hidden mt-6 space-y-4">
<div>
<label class="block text-sm font-medium text-gray-300 mb-2">가이던스 스케일</label>
<input id="guidance-scale" type="range" min="1" max="20" value="7.5" step="0.5" class="w-full h-2 bg-gray-700 rounded-lg appearance-none cursor-pointer">
<div class="flex justify-between text-xs text-gray-400">
<span>더 창의적</span>
<span>더 정확</span>
</div>
</div>
<div>
<label class="block text-sm font-medium text-gray-300 mb-2">스텝</label>
<input id="steps-input" type="range" min="10" max="50" value="25" class="w-full h-2 bg-gray-700 rounded-lg appearance-none cursor-pointer">
</div>
<div>
<label class="block text-sm font-medium text-gray-300 mb-2">시드</label>
<input id="seed-input" type="number" class="w-full bg-gray-800 border border-gray-700 rounded-lg px-4 py-2 text-white focus:outline-none focus:ring-2 focus:ring-purple-500 focus:border-transparent" placeholder="랜덤">
</div>
</div>
</div>
<!-- Output Preview -->
<div class="lg:w-1/2">
<div class="flex justify-between items-center mb-6">
<h2 class="tech-font text-2xl font-bold gradient-text">미리보기</h2>
<div class="flex gap-2">
<button id="undo-btn" class="bg-gray-800 hover:bg-gray-700 text-gray-300 p-2 rounded-lg">
<i class="fas fa-undo"></i>
</button>
<button id="randomize-btn" class="bg-gray-800 hover:bg-gray-700 text-gray-300 p-2 rounded-lg">
<i class="fas fa-random"></i>
</button>
<button id="download-btn" class="bg-gray-800 hover:bg-gray-700 text-gray-300 p-2 rounded-lg">
<i class="fas fa-download"></i>
</button>
</div>
</div>
<div id="output-container" class="bg-gray-800 rounded-xl p-4 h-full min-h-[400px] flex items-center justify-center">
<div id="placeholder-content" class="text-center">
<i class="fas fa-image text-6xl text-gray-600 mb-4"></i>
<p class="text-gray-400">생성된 콘텐츠가 여기에 표시됩니다</p>
<p id="status-text" class="terminal-text text-xs mt-2">SDXL 1.0 모델 로드됨 | ControlNet 준비 완료</p>
</div>
<div id="loading-indicator" class="hidden flex-col items-center">
<div class="loading-spinner mb-4"></div>
<p class="text-purple-300">이미지 생성 중...</p>
<p id="generation-status" class="terminal-text text-xs mt-2">모델 초기화 중</p>
</div>
<img id="generated-image" src="#" alt="생성된 이미지" class="hidden generated-image">
</div>
<div id="variations-container" class="mt-4 grid grid-cols-4 gap-2">
<div class="bg-gray-800 rounded-lg h-20 flex items-center justify-center cursor-pointer hover:border-2 hover:border-purple-500">
<i class="fas fa-plus text-gray-500"></i>
</div>
</div>
</div>
</div>
</div>
<!-- Features Section -->
<div class="mt-20">
<h2 class="tech-font text-3xl font-bold text-center mb-12 gradient-text">강력한 기능</h2>
<div class="grid md:grid-cols-3 gap-8">
<div class="neon-border bg-gray-900 rounded-xl p-6 hover:bg-opacity-100 transition">
<div class="text-purple-500 text-4xl mb-4">
<i class="fas fa-brain"></i>
</div>
<h3 class="tech-font text-xl font-bold mb-3">AI 기반 생성</h3>
<p class="text-gray-300">최신 SDXL 및 ControlNet 모델을 활용하여 간단한 텍스트 프롬프트로 놀라운 시각 자료를 만듭니다.</p>
</div>
<div class="neon-border bg-gray-900 rounded-xl p-6 hover:bg-opacity-100 transition">
<div class="text-green-500 text-4xl mb-4">
<i class="fas fa-sliders-h"></i>
</div>
<h3 class="tech-font text-xl font-bold mb-3">정밀 제어</h3>
<p class="text-gray-300">ControlNet과 참조 이미지를 사용하여 스타일을 변경하면서 구성, 깊이 또는 가장자리를 유지합니다.</p>
</div>
<div class="neon-border bg-gray-900 rounded-xl p-6 hover:bg-opacity-100 transition">
<div class="text-yellow-500 text-4xl mb-4">
<i class="fas fa-templates"></i>
</div>
<h3 class="tech-font text-xl font-bold mb-3">스마트 템플릿</h3>
<p class="text-gray-300">각 소셜 플랫폼에 최적화된 수백 개의 전문적으로 설계된 템플릿 중에서 선택하세요.</p>
</div>
</div>
</div>
<!-- Recent Creations -->
<div class="mt-20">
<div class="flex justify-between items-center mb-8">
<h2 class="tech-font text-3xl font-bold gradient-text">최근 생성물</h2>
<button class="text-purple-400 hover:text-white flex items-center">
모두 보기 <i class="fas fa-arrow-right ml-2"></i>
</button>
</div>
<div class="grid grid-cols-2 md:grid-cols-4 gap-4">
<div class="relative group overflow-hidden rounded-lg h-48">
<img src="https://source.unsplash.com/random/300x300/?cyberpunk" alt="" class="w-full h-full object-cover">
<div class="absolute inset-0 bg-gradient-to-t from-black to-transparent opacity-0 group-hover:opacity-100 transition flex items-end p-4">
<div>
<p class="text-white font-medium">"Cyberpunk cityscape"</p>
<p class="text-purple-300 text-sm">SDXL + ControlNet</p>
</div>
</div>
</div>
<div class="relative group overflow-hidden rounded-lg h-48">
<img src="https://source.unsplash.com/random/300x300/?portrait" alt="" class="w-full h-full object-cover">
<div class="absolute inset-0 bg-gradient-to-t from-black to-transparent opacity-0 group-hover:opacity-100 transition flex items-end p-4">
<div>
<p class="text-white font-medium">"Professional headshot"</p>
<p class="text-purple-300 text-sm">SDXL Photoreal</p>
</div>
</div>
</div>
<div class="relative group overflow-hidden rounded-lg h-48">
<img src="https://source.unsplash.com/random/300x300/?product" alt="" class="w-full h-full object-cover">
<div class="absolute inset-0 bg-gradient-to-t from-black to-transparent opacity-0 group-hover:opacity-100 transition flex items-end p-4">
<div>
<p class="text-white font-medium">"Minimal product display"</p>
<p class="text-purple-300 text-sm">ControlNet Edges</p>
</div>
</div>
</div>
<div class="relative group overflow-hidden rounded-lg h-48">
<img src="https://source.unsplash.com/random/300x300/?abstract" alt="" class="w-full h-full object-cover">
<div class="absolute inset-0 bg-gradient-to-t from-black to-transparent opacity-0 group-hover:opacity-100 transition flex items-end p-4">
<div>
<p class="text-white font-medium">"Colorful abstract art"</p>
<p class="text-purple-300 text-sm">SDXL Digital Art</p>
</div>
</div>
</div>
</div>
</div>
</main>
<!-- Footer -->
<footer class="bg-black bg-opacity-80 border-t border-purple-900 mt-20">
<div class="max-w-7xl mx-auto py-12 px-4 sm:px-6 lg:px-8">
<div class="grid grid-cols-1 md:grid-cols-4 gap-8">
<div>
<h3 class="tech-font text-lg font-bold gradient-text mb-4">soc.io</h3>
<p class="text-gray-400">AI-powered social media content creation for the modern creator.</p>
<div class="flex gap-4 mt-4">
<a href="#" class="text-gray-400 hover:text-purple-400"><i class="fab fa-twitter"></i></a>
<a href="#" class="text-gray-400 hover:text-purple-400"><i class="fab fa-instagram"></i></a>
<a href="#" class="text-gray-400 hover:text-purple-400"><i class="fab fa-discord"></i></a>
</div>
</div>
<div>
<h3 class="tech-font text-sm font-bold text-gray-300 mb-4">PRODUCT</h3>
<ul class="space-y-2">
<li><a href="#" class="text-gray-400 hover:text-white">Features</a></li>
<li><a href="#" class="text-gray-400 hover:text-white">Pricing</a></li>
<li><a href="#" class="text-gray-400 hover:text-white">Templates</a></li>
<li><a href="#" class="text-gray-400 hover:text-white">API</a></li>
</ul>
</div>
<div>
<h3 class="tech-font text-sm font-bold text-gray-300 mb-4">RESOURCES</h3>
<ul class="space-y-2">
<li><a href="#" class="text-gray-400 hover:text-white">Documentation</a></li>
<li><a href="#" class="text-gray-400 hover:text-white">Tutorials</a></li>
<li><a href="#" class="text-gray-400 hover:text-white">Blog</a></li>
<li><a href="#" class="text-gray-400 hover:text-white">Community</a></li>
</ul>
</div>
<div>
<h3 class="tech-font text-sm font-bold text-gray-300 mb-4">NEWSLETTER</h3>
<p class="text-gray-400 mb-4">Subscribe for updates and AI tips.</p>
<div class="flex">
<input type="email" placeholder="Your email" class="bg-gray-800 text-white px-4 py-2 rounded-l-lg focus:outline-none focus:ring-1 focus:ring-purple-500 w-full">
<button class="bg-purple-600 hover:bg-purple-700 text-white px-4 py-2 rounded-r-lg">
<i class="fas fa-paper-plane"></i>
</button>
</div>
</div>
</div>
<div class="border-t border-gray-800 mt-12 pt-8 flex flex-col md:flex-row justify-between items-center">
<p class="text-gray-500 text-sm">© 2023 soc.io. All rights reserved.</p>
<div class="flex gap-6 mt-4 md:mt-0">
<a href="#" class="text-gray-500 hover:text-gray-300 text-sm">Privacy</a>
<a href="#" class="text-gray-500 hover:text-gray-300 text-sm">Terms</a>
<a href="#" class="text-gray-500 hover:text-gray-300 text-sm">Cookies</a>
</div>
</div>
</div>
</footer>
<script>
// Store API key in localStorage
let apiKey = localStorage.getItem('hf_api_key');
let generatedImages = [];
// DOM elements
const apiKeyModal = document.getElementById('api-key-modal');
const apiKeyInput = document.getElementById('api-key-input');
const saveApiKeyBtn = document.getElementById('save-api-key');
const cancelApiKeyBtn = document.getElementById('cancel-api-key');
const closeModalBtn = document.getElementById('close-modal');
const generateNowBtn = document.getElementById('generate-now-btn');
const startCreatingBtn = document.getElementById('start-creating');
const generateBtn = document.getElementById('generate-btn');
const promptInput = document.getElementById('prompt-input');
const styleSelect = document.getElementById('style-select');
const aspectRatioSelect = document.getElementById('aspect-ratio-select');
const outputContainer = document.getElementById('output-container');
const placeholderContent = document.getElementById('placeholder-content');
const loadingIndicator = document.getElementById('loading-indicator');
const generatedImage = document.getElementById('generated-image');
const statusText = document.getElementById('status-text');
const generationStatus = document.getElementById('generation-status');
const uploadArea = document.getElementById('upload-area');
const fileInput = document.getElementById('file-input');
const previewContainer = document.getElementById('preview-container');
const previewImage = document.getElementById('preview-image');
const removeImageBtn = document.getElementById('remove-image');
const advancedToggle = document.getElementById('advanced-toggle');
const advancedControls = document.getElementById('advanced-controls');
const downloadBtn = document.getElementById('download-btn');
const undoBtn = document.getElementById('undo-btn');
const randomizeBtn = document.getElementById('randomize-btn');
const promptSuggestionsBtn = document.getElementById('prompt-suggestions');
const variationsContainer = document.getElementById('variations-container');
const guidanceScale = document.getElementById('guidance-scale');
const stepsInput = document.getElementById('steps-input');
const seedInput = document.getElementById('seed-input');
// Modal control
function showApiKeyModal() {
apiKeyModal.classList.add('active');
if (apiKey) {
apiKeyInput.value = apiKey;
}
}
function hideApiKeyModal() {
apiKeyModal.classList.remove('active');
}
// Event listeners for modal
[saveApiKeyBtn, cancelApiKeyBtn, closeModalBtn].forEach(btn => {
btn.addEventListener('click', (e) => {
e.preventDefault();
if (btn === saveApiKeyBtn && apiKeyInput.value) {
apiKey = apiKeyInput.value;
localStorage.setItem('hf_api_key', apiKey);
updateStatus('API key saved successfully');
}
hideApiKeyModal();
});
});
// Check for API key on actions that require it
function checkApiKey() {
if (!apiKey) {
showApiKeyModal();
return false;
}
return true;
}
// Generate image function
async function generateImage() {
if (!checkApiKey()) return;
const prompt = promptInput.value.trim();
if (!prompt) {
updateStatus('Please enter a prompt', 'error');
return;
}
// Show loading state
placeholderContent.classList.add('hidden');
loadingIndicator.classList.remove('hidden');
generatedImage.classList.add('hidden');
outputContainer.classList.add('neon-border');
// Get generation parameters
const style = styleSelect.value;
const aspectRatio = aspectRatioSelect.value;
const guidance = parseFloat(guidanceScale.value);
const steps = parseInt(stepsInput.value);
const seed = seedInput.value ? parseInt(seedInput.value) : Math.floor(Math.random() * 1000000);
// Update status messages
updateGenerationStatus('Validating API key...');
await delay(1000);
updateGenerationStatus('Initializing SDXL model...');
await delay(1500);
updateGenerationStatus('Processing prompt...');
await delay(2000);
updateGenerationStatus('Generating image...');
await delay(2500);
try {
// In a real app, this would call the Hugging Face API
// For demo purposes, we'll simulate the API call
const imageUrl = await mockHuggingFaceAPICall(prompt, style, aspectRatio, guidance, steps, seed);
// Store the generated image
generatedImages.push(imageUrl);
// Display the image
displayGeneratedImage(imageUrl);
updateStatus('Image generated successfully!');
// Generate variations
generateVariations();
} catch (error) {
console.error('Error generating image:', error);
updateStatus(`Error: ${error.message}`, 'error');
placeholderContent.classList.remove('hidden');
loadingIndicator.classList.add('hidden');
}
}
// Mock Hugging Face API call
async function mockHuggingFaceAPICall(prompt, style, aspectRatio, guidance, steps, seed) {
// This is just a simulation - in a real app, you would call the actual API
console.log('Mock API call with:', {
prompt,
style,
aspectRatio,
guidance,
steps,
seed
});
// Map styles to different Unsplash categories
const styleMap = {
'photorealistic': 'portrait',
'digital_art': 'abstract,art',
'3d_render': '3d,render',
'anime': 'anime',
'watercolor': 'watercolor,painting',
'cyberpunk': 'cyberpunk'
};
// Get dimensions based on aspect ratio
const dimensions = getDimensionsFromAspectRatio(aspectRatio);
// Return a random image from Unsplash based on the style
return `https://source.unsplash.com/random/${dimensions.width}x${dimensions.height}/?${styleMap[style] || 'portrait'}&${seed}`;
}
function getDimensionsFromAspectRatio(ratio) {
switch(ratio) {
case '1:1': return { width: 768, height: 768 };
case '4:5': return { width: 640, height: 800 };
case '16:9': return { width: 1024, height: 576 };
case '9:16': return { width: 432, height: 768 };
default: return { width: 768, height: 768 };
}
}
function displayGeneratedImage(imageUrl) {
generatedImage.src = imageUrl;
loadingIndicator.classList.add('hidden');
generatedImage.classList.remove('hidden');
// Enable download button
downloadBtn.onclick = () => {
downloadImage(imageUrl);
};
}
function downloadImage(url) {
const a = document.createElement('a');
a.href = url;
a.download = `soc.io-${Date.now()}.jpg`;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
updateStatus('Image downloaded');
}
function generateVariations() {
// Clear previous variations
variationsContainer.innerHTML = '';
// Add 3 variations
for (let i = 0; i < 3; i++) {
const variation = document.createElement('div');
variation.className = 'bg-gray-800 rounded-lg h-20 overflow-hidden';
variation.innerHTML = `<img src="https://source.unsplash.com/random/150x150/?${styleSelect.value}&${Date.now() + i}" class="w-full h-full object-cover" alt="Variation ${i+1}">`;
variation.addEventListener('click', () => {
// When a variation is clicked, display it as the main image
const imgUrl = variation.querySelector('img').src;
generatedImages.push(imgUrl);
displayGeneratedImage(imgUrl);
updateStatus('Variation loaded');
});
variationsContainer.appendChild(variation);
}
}
function updateStatus(message, type = 'success') {
statusText.textContent = message;
statusText.className = 'terminal-text text-xs mt-2';
if (type === 'error') {
statusText.classList.add('text-red-400');
} else {
statusText.classList.add('text-green-400');
}
}
function updateGenerationStatus(message) {
generationStatus.textContent = message;
}
function delay(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
// Image upload handling
uploadArea.addEventListener('click', () => fileInput.click());
fileInput.addEventListener('change', (e) => {
const file = e.target.files[0];
if (file) {
const reader = new FileReader();
reader.onload = (event) => {
previewImage.src = event.target.result;
previewContainer.classList.remove('hidden');
uploadArea.classList.add('hidden');
};
reader.readAsDataURL(file);
}
});
removeImageBtn.addEventListener('click', (e) => {
e.stopPropagation();
fileInput.value = '';
previewContainer.classList.add('hidden');
uploadArea.classList.remove('hidden');
});
// Drag and drop for image upload
uploadArea.addEventListener('dragover', (e) => {
e.preventDefault();
uploadArea.classList.add('border-purple-500', 'bg-gray-800');
});
uploadArea.addEventListener('dragleave', () => {
uploadArea.classList.remove('border-purple-500', 'bg-gray-800');
});
uploadArea.addEventListener('drop', (e) => {
e.preventDefault();
uploadArea.classList.remove('border-purple-500', 'bg-gray-800');
const file = e.dataTransfer.files[0];
if (file && (file.type === 'image/jpeg' || file.type === 'image/png')) {
fileInput.files = e.dataTransfer.files;
const event = new Event('change');
fileInput.dispatchEvent(event);
}
});
// Advanced controls toggle
advancedToggle.addEventListener('change', () => {
if (advancedToggle.checked) {
advancedControls.classList.remove('hidden');
} else {
advancedControls.classList.add('hidden');
}
});
// Generate buttons
[generateNowBtn, startCreatingBtn, generateBtn].forEach(btn => {
btn.addEventListener('click', (e) => {
e.preventDefault();
if (btn === generateNowBtn) {
generateImage();
} else {
// Scroll to generator section
document.querySelector('.neon-border.rounded-xl').scrollIntoView({
behavior: 'smooth'
});
}
});
});
// Randomize button - generates random prompt
randomizeBtn.addEventListener('click', () => {
const randomPrompts = [
"A futuristic cityscape at night with neon lights and flying cars",
"A majestic lion in the savanna at golden hour",
"An astronaut floating in space with Earth in the background",
"A cyberpunk hacker in a dark room with multiple monitors",
"A magical forest with glowing plants and fairies",
"A steampunk airship flying over Victorian London",
"A cute anime character with pink hair and big eyes",
"A watercolor painting of a sunset over mountains"
];
promptInput.value = randomPrompts[Math.floor(Math.random() * randomPrompts.length)];
updateStatus('Random prompt generated');
});
// Prompt suggestions button
promptSuggestionsBtn.addEventListener('click', () => {
const suggestions = [
"Try adding more details like lighting, style, or composition",
"Example: 'A photorealistic portrait of a woman with freckles, soft lighting'",
"Example: 'Cyberpunk city at night, neon lights, rain on streets'",
"Example: 'Cute anime girl with blue hair, wearing school uniform'"
];
alert("Prompt Tips:\n\n" + suggestions.join("\n\n"));
});
// Undo button - goes back to previous generated image
undoBtn.addEventListener('click', () => {
if (generatedImages.length > 1) {
generatedImages.pop(); // Remove current image
const prevImage = generatedImages[generatedImages.length - 1];
displayGeneratedImage(prevImage);
updateStatus('Reverted to previous image');
} else if (generatedImages.length === 1) {
generatedImages = [];
generatedImage.classList.add('hidden');
placeholderContent.classList.remove('hidden');
updateStatus('Generation cleared');
} else {
updateStatus('Nothing to undo', 'error');
}
});
// Initialize typing effect for status text
document.addEventListener('DOMContentLoaded', function() {
const elements = document.querySelectorAll('.slide-in');
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
entry.target.style.opacity = 1;
entry.target.style.transform = 'translateY(0)';
}
});
}, { threshold: 0.1 });
elements.forEach(el => {
el.style.opacity = 0;
el.style.transform = 'translateY(20px)';
el.style.transition = 'all 0.5s ease-out';
observer.observe(el);
});
// Terminal-like effect for status text
const originalText = statusText.textContent;
statusText.textContent = '';
let i = 0;
const typingEffect = setInterval(() => {
if (i < originalText.length) {
statusText.textContent += originalText.charAt(i);
i++;
} else {
clearInterval(typingEffect);
}
}, 50);
// Initialize seed value
seedInput.value = Math.floor(Math.random() * 1000000);
});
</script>
<p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=pauloyatowo/soc-io" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p>
<section class="image-generation-section">
<textarea id="promptInput" placeholder="Enter your ad prompt here...">A minimal, stylish Instagram ad for Brilla.ng, a professional cleaning service targeting Lagos homeowners and businesses. The visual features a spotless, modern Lagos living room or office with clean lines and natural light. A bold yet elegant text overlay says: “We don’t cut corners, we clean them.” The Brilla.ng logo is subtly placed in the corner. Use soft whites, warm neutrals, and a hint of the brand’s color for accen...
<button onclick="generateImage()">Generate Ad</button>
<div id="imageContainer"></div>
</section>
<script>
async function generateImage() {
const prompt = document.getElementById('promptInput').value;
const response = await fetch('/generate', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ prompt })
});
if (!response.ok) {
alert("Failed to generate image.");
return;
}
const blob = await response.blob();
const imageUrl = URL.createObjectURL(blob);
document.getElementById('imageContainer').innerHTML = `<img src="${imageUrl}" alt="Generated Ad" style="max-width:100%;margin-top:1em;" />`;
}
</script>
<p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=nkjoy/soc-io" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body>
</html>