File size: 19,331 Bytes
07381d2 b94499a 07381d2 b94499a 07381d2 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 | <!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Generator — NSFWStudio</title>
<script src="https://cdn.tailwindcss.com"></script>
<script src="https://unpkg.com/lucide@latest"></script>
<script src="https://unpkg.com/react@18/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
<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:opsz,wght@14..32,300..800&family=JetBrains+Mono:wght@400;500;600&display=swap" rel="stylesheet">
<script>
tailwind.config = { theme: { extend: { fontFamily: { sans: ['Inter','system-ui','sans-serif'], mono: ['JetBrains Mono','monospace'] }, colors: { bg: 'oklch(0.07 0.006 285)', surface: 'oklch(0.12 0.02 285)', 'surface-2': 'oklch(0.16 0.025 285)', 'surface-3': 'oklch(0.20 0.028 285)', fg: 'oklch(0.97 0.005 285)', muted: 'oklch(0.55 0.02 285)', primary: 'oklch(0.70 0.28 350)', 'primary-foreground': 'oklch(0.98 0.005 285)', secondary: 'oklch(0.75 0.15 215)', accent: 'oklch(0.80 0.18 85)', border: 'oklch(0.22 0.02 285)', ring: 'oklch(0.70 0.28 350)', destructive: 'oklch(0.55 0.22 25)', success: 'oklch(0.65 0.2 145)' } } } }
</script>
<style>
body { background-color: oklch(0.07 0.006 285); color: oklch(0.97 0.005 285); font-family: 'Inter', system-ui, sans-serif; -webkit-font-smoothing: antialiased; }
.glass { background: oklch(0.14 0.015 285 / 0.6); backdrop-filter: blur(24px) saturate(1.2); -webkit-backdrop-filter: blur(24px) saturate(1.2); border: 1px solid oklch(0.25 0.02 285 / 0.5); }
::selection { background: oklch(0.70 0.28 350 / 0.3); }
body::after { content:''; position:fixed; inset:0; pointer-events:none; z-index:9999; opacity:0.025; background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 256 256' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E"); background-repeat:repeat; background-size:256px 256px; }
.no-scrollbar::-webkit-scrollbar { display: none; } .no-scrollbar { -ms-overflow-style: none; scrollbar-width: none; }
.card-3d { transition: transform 0.4s cubic-bezier(0.25, 0.46, 0.45, 0.94), box-shadow 0.4s ease; }
.card-3d:hover { transform: perspective(1000px) rotateY(-3deg) rotateX(2deg) translateZ(15px); box-shadow: 0 20px 40px rgba(0,0,0,0.35); }
.hover-lift { transition: transform 0.3s cubic-bezier(0.34, 1.56, 0.64, 1), box-shadow 0.3s ease; }
.hover-lift:hover { transform: translateY(-4px) scale(1.02); box-shadow: 0 12px 24px rgba(0,0,0,0.3); }
@keyframes shimmer { 0% { background-position: -200% 0; } 100% { background-position: 200% 0; } }
@keyframes pulse-glow { 0%, 100% { box-shadow: 0 0 20px oklch(0.70 0.28 350 / 0.2); } 50% { box-shadow: 0 0 40px oklch(0.70 0.28 350 / 0.4); } }
.generating-glow { animation: pulse-glow 2s ease-in-out infinite; }
@keyframes morph-bg { 0%, 100% { border-radius: 60% 40% 30% 70% / 60% 30% 70% 40%; } 50% { border-radius: 30% 60% 70% 40% / 50% 60% 30% 60%; } }
</style>
</head>
<body class="antialiased">
<div id="root"></div>
<script type="text/babel">
const { useState } = React;
const Icon = ({ name, size = 20, className = "", strokeWidth = 2 }) => {
const ref = React.useRef(null);
React.useEffect(() => { if (ref.current) { const existing = ref.current.querySelector('svg'); if (existing) existing.remove(); const svg = lucide.createElement(lucide.icons[name]); if (svg) { svg.setAttribute('width',size); svg.setAttribute('height',size); svg.setAttribute('stroke-width',strokeWidth); ref.current.appendChild(svg); } } }, [name, size, className, strokeWidth]);
return <span ref={ref} className={`inline-flex items-center justify-center ${className}`} style={{ width: size, height: size }} />;
};
const navItems = [
{ name: 'Dashboard', icon: 'layout-dashboard', href: 'dashboard.html' },
{ name: 'Generator', icon: 'sparkles', href: 'generator.html', active: true },
{ name: 'Playground', icon: 'code', href: 'playground.html' },
{ name: 'Models', icon: 'box', href: 'models.html' },
{ name: 'Workflows', icon: 'workflow', href: 'workflows.html' },
{ name: 'Marketplace', icon: 'shopping-bag', href: 'marketplace.html' },
{ name: 'API Keys', icon: 'key', href: 'api-keys.html' },
{ divider: true },
{ name: 'Profile', icon: 'user', href: 'profile.html' },
{ name: 'Settings', icon: 'settings', href: 'settings.html' },
];
const Sidebar = ({ isOpen, setIsOpen }) => (
<>
<div className={`fixed inset-0 z-40 lg:hidden transition-all ${isOpen ? 'opacity-100 pointer-events-auto' : 'opacity-0 pointer-events-none'}`}>
<div className="absolute inset-0 bg-black/60 backdrop-blur-sm" onClick={() => setIsOpen(false)} />
</div>
<aside className={`fixed top-0 left-0 bottom-0 z-50 w-64 bg-surface border-r border-border flex flex-col transition-transform duration-300 lg:translate-x-0 ${isOpen ? 'translate-x-0' : '-translate-x-full'}`}>
<div className="p-4 flex items-center gap-2.5 border-b border-border"><div className="w-8 h-8 rounded-lg bg-primary flex items-center justify-center"><Icon name="sparkles" size={16} className="text-primary-foreground" /></div><span className="text-lg font-bold tracking-tight">NSFW<span className="text-muted">Studio</span></span></div>
<nav className="flex-1 p-3 space-y-1 overflow-y-auto no-scrollbar">{navItems.map((item, i) => item.divider ? <div key={i} className="my-3 border-t border-border" /> : <a key={i} href={item.href} className={`flex items-center gap-3 px-3 py-2.5 rounded-xl text-sm font-medium transition-colors ${item.active ? 'bg-primary/10 text-primary border border-primary/20' : 'text-muted hover:text-fg hover:bg-surface-2 border border-transparent'}`}><Icon name={item.icon} size={18} />{item.name}</a>)}</nav>
<div className="p-4 border-t border-border"><a href="documentation.html" className="flex items-center gap-3 px-3 py-2.5 rounded-xl text-sm text-muted hover:text-fg hover:bg-surface-2 transition-colors"><Icon name="book-open" size={18} />Documentation</a><a href="api-reference.html" className="flex items-center gap-3 px-3 py-2.5 rounded-xl text-sm text-muted hover:text-fg hover:bg-surface-2 transition-colors"><Icon name="file-code" size={18} />API Reference</a></div>
</aside>
</>
);
const App = () => {
const [sidebarOpen, setSidebarOpen] = useState(false);
const [model, setModel] = useState('DreamVision XL');
const [prompt, setPrompt] = useState('');
const [negativePrompt, setNegativePrompt] = useState('');
const [isGenerating, setIsGenerating] = useState(false);
const [generated, setGenerated] = useState(false);
const [aspectRatio, setAspectRatio] = useState('1:1');
const handleGenerate = () => { if (!prompt.trim()) return; setIsGenerating(true); setGenerated(false); setTimeout(() => { setIsGenerating(false); setGenerated(true); }, 2500); };
return (
<div className="min-h-screen bg-bg">
<Sidebar isOpen={sidebarOpen} setIsOpen={setSidebarOpen} />
<div className="lg:ml-64">
<header className="sticky top-0 z-30 glass border-b border-border">
<div className="flex items-center justify-between px-4 sm:px-6 h-16">
<div className="flex items-center gap-4">
<button onClick={() => setSidebarOpen(true)} className="lg:hidden p-2 rounded-lg hover:bg-surface-2 text-muted"><Icon name="menu" size={20} /></button>
<h1 className="text-xl font-bold">Generator</h1>
</div>
<div className="flex items-center gap-3">
<button className="p-2 rounded-lg hover:bg-surface-2 text-muted relative"><Icon name="bell" size={20} /><span className="absolute top-1.5 right-1.5 w-2 h-2 rounded-full bg-primary" /></button>
<a href="profile.html" className="w-9 h-9 rounded-lg overflow-hidden border border-border"><img src="http://static.photos/people/100x100/10" alt="" className="w-full h-full object-cover" /></a>
</div>
</div>
</header>
<div className="flex flex-col lg:flex-row h-[calc(100vh-64px)]">
{/* Settings Panel */}
<div className="w-full lg:w-80 border-b lg:border-b-0 lg:border-r border-border overflow-y-auto no-scrollbar p-4 space-y-5">
<div>
<label className="text-xs font-medium text-muted uppercase tracking-wider mb-2 block">Model</label>
<select value={model} onChange={e => setModel(e.target.value)} className="w-full px-3 py-2.5 rounded-xl bg-surface-2 border border-border text-sm text-fg focus:outline-none focus:ring-2 focus:ring-primary/50">
{['DreamVision XL', 'SDXL 1.0', 'CyberPunk v3', 'Anime Diffusion'].map(m => <option key={m}>{m}</option>)}
</select>
</div>
<div>
<label className="text-xs font-medium text-muted uppercase tracking-wider mb-2 block">Aspect Ratio</label>
<div className="grid grid-cols-4 gap-2">
{['1:1', '4:3', '16:9', '9:16'].map(r => (
<button key={r} onClick={() => setAspectRatio(r)} className={`py-2 rounded-lg text-xs font-medium transition-colors ${aspectRatio === r ? 'bg-primary text-primary-foreground' : 'bg-surface-2 border border-border text-muted hover:text-fg'}`}>{r}</button>
))}
</div>
</div>
{[
{ label: 'Steps', val: '30', desc: 'Number of denoising steps' },
].map(p => (
<div key={p.label}>
<label className="text-xs font-medium text-muted uppercase tracking-wider mb-2 block">{p.label}: {p.val}</label>
<input type="range" min="1" max="50" defaultValue="30" className="w-full accent-primary" />
<p className="text-xs text-muted mt-1">{p.desc}</p>
</div>
))}
<div>
<label className="text-xs font-medium text-muted uppercase tracking-wider mb-2 block">Guidance Scale: 7.5</label>
<input type="range" min="1" max="20" defaultValue="7.5" step="0.5" className="w-full accent-primary" />
</div>
<div>
<label className="text-xs font-medium text-muted uppercase tracking-wider mb-2 block">Seed</label>
<div className="flex gap-2">
<input type="text" defaultValue="Random" className="flex-1 px-3 py-2.5 rounded-xl bg-surface-2 border border-border text-sm text-fg focus:outline-none focus:ring-2 focus:ring-primary/50" />
<button className="px-3 rounded-xl bg-surface-2 border border-border text-muted hover:text-fg"><Icon name="shuffle" size={16} /></button>
</div>
</div>
<div className="pt-2 border-t border-border">
<h3 className="text-xs font-medium text-muted uppercase tracking-wider mb-2">Quick Styles</h3>
<div className="flex flex-wrap gap-2">
{['Photorealistic', 'Anime', 'Fantasy', 'Cinematic', 'Oil Painting', '3D Render'].map(s => (
<button key={s} className="px-3 py-1.5 rounded-lg bg-surface-2 border border-border text-xs text-muted hover:text-fg hover:border-surface-3 transition-colors">{s}</button>
))}
</div>
</div>
</div>
{/* Main Canvas */}
<div className="flex-1 flex flex-col">
<div className="flex-1 flex items-center justify-center p-4 sm:p-8">
{isGenerating ? (
<div className="text-center">
<div className="w-20 h-20 mx-auto mb-6 relative">
<div className="absolute inset-0 border-4 border-primary/30 border-t-primary rounded-full animate-spin" />
<div className="absolute inset-2 border-4 border-secondary/30 border-b-secondary rounded-full animate-spin" style={{animationDirection:'reverse', animationDuration:'1.5s'}} />
<div className="absolute inset-4 border-4 border-accent/30 border-t-accent rounded-full animate-spin" style={{animationDuration:'3s'}} />
<div className="absolute inset-8 bg-primary/20 rounded-full generating-glow" style={{borderRadius:'60% 40% 30% 70%', animation:'morph-bg 3s ease-in-out infinite'}} />
</div>
<p className="text-fg font-bold text-lg">Generating your fantasy...</p>
<p className="text-sm text-muted mt-1">This usually takes 10-30 seconds</p>
</div>
) : generated ? (
<div className="max-w-2xl w-full">
<div className="rounded-2xl overflow-hidden border border-border shadow-2xl shadow-black/40">
<img src={`http://static.photos/cyberpunk/1024x576/1`} alt="Generated" className="w-full" />
</div>
<div className="flex items-center justify-between mt-4">
<div className="flex gap-2">
{['Download', 'Upscale', 'Variations', 'Add to Collection'].map(a => (
<button key={a} className="px-4 py-2 rounded-lg bg-surface border border-border text-sm text-fg hover:bg-surface-2 transition-colors flex items-center gap-1.5"><Icon name={a === 'Download' ? 'download' : a === 'Upscale' ? 'maximize' : a === 'Variations' ? 'copy' : 'bookmark'} size={14} />{a}</button>
))}
</div>
<span className="text-xs text-muted">DreamVision XL · 1024×1024 · 30 steps</span>
</div>
</div>
) : (
<div className="text-center max-w-md">
<div className="w-20 h-20 rounded-2xl bg-surface-2 flex items-center justify-center mx-auto mb-6"><Icon name="sparkles" size={32} className="text-muted" /></div>
<h2 className="text-xl font-semibold mb-2">Ready to Create</h2>
<p className="text-muted text-sm">Enter a prompt below and click Generate to bring your vision to life.</p>
</div>
)}
</div>
{/* Prompt Input */}
<div className="p-4 border-t border-border">
<div className="max-w-4xl mx-auto space-y-3">
<textarea value={negativePrompt} onChange={e => setNegativePrompt(e.target.value)} placeholder="Negative prompt (optional)..." className="w-full px-4 py-2.5 rounded-xl bg-surface border border-border text-sm text-fg placeholder:text-muted focus:outline-none focus:ring-2 focus:ring-primary/50 resize-none h-16" />
<div className="flex gap-3">
<div className="flex-1 flex items-center gap-2 px-4 py-3 rounded-xl bg-surface border border-border focus-within:ring-2 focus-within:ring-primary/50">
<Icon name="sparkles" size={18} className="text-muted shrink-0" />
<input type="text" value={prompt} onChange={e => setPrompt(e.target.value)} onKeyDown={e => e.key === 'Enter' && handleGenerate()} placeholder="Describe what you want to create..." className="flex-1 bg-transparent text-fg placeholder:text-muted outline-none text-sm" />
</div>
<button onClick={handleGenerate} disabled={isGenerating} className="px-6 py-3 rounded-xl bg-primary text-primary-foreground font-semibold hover:brightness-110 transition-all shadow-lg shadow-primary/25 flex items-center gap-2 shrink-0">
<Icon name="zap" size={18} />Generate
</button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
);
};
ReactDOM.createRoot(document.getElementById('root')).render(<App />);
</script>
</body>
</html> |