File size: 20,789 Bytes
07381d2 b94499a 07381d2 b94499a 07381d2 b94499a 07381d2 b94499a 07381d2 b94499a 07381d2 b94499a 07381d2 b94499a 07381d2 b94499a 07381d2 b94499a 07381d2 b94499a 07381d2 b94499a 07381d2 b94499a 07381d2 b94499a 07381d2 b94499a 07381d2 b94499a 07381d2 b94499a 07381d2 b94499a | 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 | <!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Models — 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); }
.gradient-text { background: linear-gradient(135deg, oklch(0.70 0.28 350), oklch(0.75 0.15 215)); -webkit-background-clip: text; -webkit-text-fill-color: transparent; background-clip: text; }
::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; }
.tabular-nums { font-variant-numeric: tabular-nums; }
.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(20px); box-shadow: 0 25px 50px rgba(0,0,0,0.4), 0 0 0 1px oklch(0.70 0.28 350 / 0.2); }
.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(-6px) scale(1.02); box-shadow: 0 20px 40px rgba(0,0,0,0.3), 0 0 0 1px oklch(0.70 0.28 350 / 0.2); }
.gradient-border-animated { position: relative; isolation: isolate; }
.gradient-border-animated::before { content: ''; position: absolute; inset: 0; border-radius: inherit; padding: 1.5px; background: linear-gradient(270deg, oklch(0.70 0.28 350), oklch(0.75 0.15 215), oklch(0.80 0.18 85), oklch(0.70 0.28 350)); background-size: 300% 100%; -webkit-mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0); -webkit-mask-composite: xor; mask-composite: exclude; opacity: 0; transition: opacity 0.3s ease; }
.gradient-border-animated:hover::before { opacity: 1; }
.glow-border { position: relative; isolation: isolate; }
.glow-border::after { content: ''; position: absolute; inset: -2px; border-radius: inherit; background: linear-gradient(135deg, oklch(0.70 0.28 350), oklch(0.75 0.15 215)); z-index: -1; opacity: 0; transition: opacity 0.3s ease; filter: blur(10px); }
.glow-border:hover::after { opacity: 0.6; }
</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' },
{ name: 'Playground', icon: 'code', href: 'playground.html' },
{ name: 'Models', icon: 'box', href: 'models.html', active: true },
{ 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="#" 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="#" 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 [activeTab, setActiveTab] = useState('my-models');
const myModels = [
{ name: 'EroticDream XL', type: 'NSFW Image Generation', status: 'published', runs: '45.2K', rating: 4.9, revenue: '$2,340', updated: '2 days ago', img: 'http://static.photos/cyberpunk/400x300/20', desc: 'Ultra-realistic erotic art model with advanced anatomy and lighting' },
{ name: 'DarkSeduction v3', type: 'Fantasy Erotic', status: 'published', runs: '28.1K', rating: 4.8, revenue: '$1,890', updated: '1 week ago', img: 'http://static.photos/red/400x300/21', desc: 'Dark fantasy and fetish art with stunning detail and mood' },
{ name: 'MorphX Body v2', type: 'Body Morph', status: 'draft', runs: '0', rating: '-', revenue: '$0', updated: '3 days ago', img: 'http://static.photos/abstract/400x300/22', desc: 'Advanced body transformation and morphing model' },
{ name: 'Pleasure Enhance Pro', type: 'Upscaling', status: 'review', runs: '12.8K', rating: 4.7, revenue: '$890', updated: '5 days ago', img: 'http://static.photos/nature/400x300/23', desc: '4K upscaling optimized for adult content with detail recovery' },
];
const exploreModels = [
{ name: 'SinfulAnime XL', type: 'Hentai / Anime', author: '@velvet_sin', runs: '52K', rating: 4.9, price: '$7.99', img: 'http://static.photos/people/400x300/24' },
{ name: 'PhotoReal Erotica', type: 'Photorealistic', author: '@kink_architect', runs: '67K', rating: 4.9, price: '$14.99', img: 'http://static.photos/travel/400x300/25' },
{ name: 'Fantasy Flesh XL', type: 'Fantasy / Furry', author: '@fantasy_weaver', runs: '19.4K', rating: 4.8, price: '$5.99', img: 'http://static.photos/nature/400x300/26' },
{ name: 'Ink & Skink', type: 'Tattoo / Alt', author: '@dark_desires', runs: '15.3K', rating: 4.7, price: 'Free', img: 'http://static.photos/minimal/400x300/27' },
{ name: 'FutureBods', type: 'Sci-Fi Body', author: '@cosmic_ai', runs: '8.7K', rating: 4.6, price: '$3.99', img: 'http://static.photos/aerial/400x300/28' },
{ name: 'RetroPinup', type: 'Vintage Nude', author: '@retro_vibes', runs: '22.1K', rating: 4.8, price: 'Free', img: 'http://static.photos/vintage/400x300/29' },
];
const statusColors = { published: 'bg-success/10 text-success border-success/20', draft: 'bg-muted/10 text-muted border-border', review: 'bg-accent/10 text-accent border-accent/20' };
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">Models</h1>
</div>
<div className="flex items-center gap-3">
<div className="hidden sm:flex items-center gap-2 px-3 py-2 rounded-xl bg-surface-2 border border-border text-muted text-sm w-56"><Icon name="search" size={16} /><span>Search models...</span></div>
<a href="#" className="px-4 py-2 rounded-lg bg-primary text-primary-foreground text-sm font-semibold hover:brightness-110 transition-all hover:scale-105 shadow-lg shadow-primary/20 flex items-center gap-2"><Icon name="plus" size={16} />Train Model</a>
<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>
<main className="p-4 sm:p-6 lg:p-8">
<div className="flex items-center gap-2 mb-6 border-b border-border">
{[{ id: 'my-models', label: 'My Models' }, { id: 'explore', label: 'Explore' }, { id: 'training', label: 'Training' }].map(t => (
<button key={t.id} onClick={() => setActiveTab(t.id)} className={`px-4 py-3 border-b-2 text-sm font-semibold transition-all duration-300 -mb-px ${activeTab === t.id ? 'border-primary text-primary' : 'border-transparent text-muted hover:text-fg'}`}>{t.label}</button>
))}
</div>
{activeTab === 'my-models' && (
<div className="space-y-4">
{myModels.map((m, i) => (
<div key={i} className="group rounded-2xl bg-surface border border-border hover:border-primary/30 transition-all duration-500 overflow-hidden card-3d gradient-border-animated">
<div className="flex flex-col sm:flex-row">
<div className="sm:w-48 h-32 sm:h-auto shrink-0 overflow-hidden">
<img src={m.img} alt={m.name} className="w-full h-full object-cover group-hover:scale-110 transition-transform duration-700" />
</div>
<div className="flex-1 p-5">
<div className="flex items-start justify-between gap-3 mb-3">
<div>
<div className="flex items-center gap-2 mb-1">
<h3 className="font-bold text-fg group-hover:text-primary transition-colors duration-300">{m.name}</h3>
<span className={`px-2.5 py-0.5 rounded-full text-xs font-bold border ${statusColors[m.status]}`}>{m.status}</span>
</div>
<p className="text-sm text-muted">{m.type}</p>
<p className="text-xs text-muted/70 mt-1">{m.desc}</p>
</div>
<div className="text-right">
<div className="text-lg font-bold text-fg tabular-nums">{m.revenue}</div>
<div className="text-xs text-muted">Revenue</div>
</div>
</div>
<div className="flex items-center gap-6 text-sm text-muted">
<span className="flex items-center gap-1"><Icon name="play" size={14} />{m.runs} runs</span>
<span className="flex items-center gap-1"><Icon name="star" size={14} className="text-accent" />{m.rating}</span>
<span className="flex items-center gap-1"><Icon name="clock" size={14} />{m.updated}</span>
</div>
<div className="flex items-center gap-2 mt-4">
<button className="px-3 py-1.5 rounded-lg bg-primary text-primary-foreground text-sm font-semibold hover:brightness-110 transition-all duration-200 hover:scale-105">Manage</button>
<button className="px-3 py-1.5 rounded-lg border border-border text-sm text-muted hover:text-fg hover:bg-surface-2 transition-all duration-200">Analytics</button>
<button className="px-3 py-1.5 rounded-lg border border-border text-sm text-muted hover:text-fg hover:bg-surface-2 transition-all duration-200">Edit</button>
</div>
</div>
</div>
</div>
))}
</div>
)}
{activeTab === 'explore' && (
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4">
{exploreModels.map((m, i) => (
<div key={i} className="group rounded-2xl bg-surface border border-border hover:border-primary/30 overflow-hidden transition-all duration-500 hover:-translate-y-2 hover:shadow-2xl hover:shadow-primary/10 card-3d shine gradient-border-animated">
<div className="aspect-[4/3] overflow-hidden relative">
<img src={m.img} alt={m.name} className="w-full h-full object-cover group-hover:scale-110 transition-transform duration-700" />
<div className="absolute inset-0 bg-gradient-to-t from-black/70 to-transparent" />
<span className="absolute top-3 right-3 px-2.5 py-1 rounded-lg bg-black/50 backdrop-blur-sm text-xs font-bold border border-white/10">{m.type}</span>
</div>
<div className="p-4">
<div className="flex items-start justify-between mb-2">
<div>
<h3 className="font-bold text-fg group-hover:text-primary transition-colors duration-300">{m.name}</h3>
<p className="text-xs text-muted">{m.author}</p>
</div>
<span className="text-sm font-bold text-primary">{m.price}</span>
</div>
<div className="flex items-center gap-4 text-xs text-muted">
<span className="flex items-center gap-1"><Icon name="star" size={12} className="text-accent" />{m.rating}</span>
<span className="flex items-center gap-1"><Icon name="download" size={12} />{m.runs}</span>
</div>
</div>
</div>
))}
</div>
)}
{activeTab === 'training' && (
<div className="text-center py-16">
<div className="w-20 h-20 rounded-2xl bg-primary/10 flex items-center justify-center mx-auto mb-4 hover-lift"><Icon name="cpu" size={32} className="text-primary" /></div>
<h2 className="text-xl font-bold mb-2">No Active Training</h2>
<p className="text-muted mb-6 max-w-md mx-auto">Start training a new model by uploading your dataset and configuring your training parameters.</p>
<a href="#" className="inline-flex items-center gap-2 px-6 py-3 rounded-xl bg-primary text-primary-foreground font-bold hover:brightness-110 transition-all duration-300 hover:scale-105 shadow-lg shadow-primary/20"><Icon name="plus" size={18} />Start Training</a>
</div>
)}
</main>
</div>
</div>
);
};
ReactDOM.createRoot(document.getElementById('root')).render(<App />);
</script>
</body>
</html> |