File size: 17,187 Bytes
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
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Playground — 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; }
    </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', active: true },
            { 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 [selectedModel, setSelectedModel] = useState('DreamVision XL');
            const [prompt, setPrompt] = useState('');
            const [isRunning, setIsRunning] = useState(false);

            const models = ['DreamVision XL', 'SDXL 1.0', 'CyberPunk v3', 'Anime Diffusion', 'Photo Real v2', 'Fantasy Art XL'];
            const history = [
                { prompt: 'A neon cityscape at night with rain', model: 'DreamVision XL', time: '2 min ago', status: 'success' },
                { prompt: 'Cyberpunk anime girl with glowing eyes', model: 'Anime Diffusion', time: '8 min ago', status: 'success' },
                { prompt: 'Dark forest with mystical creatures', model: 'Fantasy Art XL', time: '15 min ago', status: 'error' },
                { prompt: 'Futuristic soldier in power armor', model: 'SDXL 1.0', time: '22 min ago', status: 'success' },
            ];

            const handleRun = () => { setIsRunning(true); setTimeout(() => setIsRunning(false), 3000); };

            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">API Playground</h1>
                                </div>
                                <div className="flex items-center gap-3">
                                    <select className="px-3 py-2 rounded-lg bg-surface-2 border border-border text-sm text-fg" value={selectedModel} onChange={e => setSelectedModel(e.target.value)}>
                                        {models.map(m => <option key={m} value={m}>{m}</option>)}
                                    </select>
                                    <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 h-[calc(100vh-64px)]">
                            {/* Left Panel - Config */}
                            <div className="w-80 border-r border-border flex flex-col overflow-y-auto no-scrollbar hidden md:flex">
                                <div className="p-4 border-b border-border">
                                    <h3 className="font-semibold text-sm mb-3">Parameters</h3>
                                    <div className="space-y-4">
                                        <div>
                                            <label className="text-xs text-muted mb-1.5 block">Model</label>
                                            <select className="w-full px-3 py-2.5 rounded-xl bg-surface-2 border border-border text-sm text-fg" value={selectedModel} onChange={e => setSelectedModel(e.target.value)}>
                                                {models.map(m => <option key={m} value={m}>{m}</option>)}
                                            </select>
                                        </div>
                                        {[
                                            { label: 'Width', value: '1024', id: 'w' },
                                            { label: 'Height', value: '1024', id: 'h' },
                                            { label: 'Steps', value: '30', id: 's' },
                                        ].map(p => (
                                            <div key={p.id}>
                                                <label className="text-xs text-muted mb-1.5 block">{p.label}</label>
                                                <input type="text" defaultValue={p.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" />
                                            </div>
                                        ))}
                                        <div>
                                            <label className="text-xs text-muted mb-1.5 block">Guidance Scale</label>
                                            <input type="range" min="1" max="20" defaultValue="7.5" className="w-full accent-primary" />
                                            <div className="flex justify-between text-xs text-muted mt-1"><span>1</span><span>7.5</span><span>20</span></div>
                                        </div>
                                        <div>
                                            <label className="text-xs text-muted mb-1.5 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 py-2.5 rounded-xl bg-surface-2 border border-border text-muted hover:text-fg transition-colors"><Icon name="shuffle" size={16} /></button>
                                            </div>
                                        </div>
                                        <div>
                                            <label className="text-xs text-muted mb-1.5 block">Negative Prompt</label>
                                            <textarea 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 h-20 resize-none" placeholder="blurry, low quality, watermark..." />
                                        </div>
                                    </div>
                                </div>
                                <div className="p-4">
                                    <h3 className="font-semibold text-sm mb-3">History</h3>
                                    <div className="space-y-2">
                                        {history.map((h, i) => (
                                            <div key={i} className="p-3 rounded-xl hover:bg-surface-2/50 cursor-pointer transition-colors">
                                                <div className="flex items-center justify-between mb-1">
                                                    <span className={`w-2 h-2 rounded-full ${h.status === 'success' ? 'bg-success' : 'bg-destructive'}`} />
                                                    <span className="text-xs text-muted">{h.time}</span>
                                                </div>
                                                <p className="text-xs text-fg/80 truncate">{h.prompt}</p>
                                                <p className="text-xs text-muted mt-0.5">{h.model}</p>
                                            </div>
                                        ))}
                                    </div>
                                </div>
                            </div>

                            {/* Main Content */}
                            <div className="flex-1 flex flex-col">
                                {/* Code & Result tabs */}
                                <div className="flex-1 flex flex-col overflow-hidden">
                                    <div className="flex items-center gap-1 px-4 pt-4 pb-2">
                                        <button className="px-4 py-2 rounded-lg text-sm font-medium bg-primary text-primary-foreground">Request</button>
                                        <button className="px-4 py-2 rounded-lg text-sm font-medium text-muted hover:text-fg hover:bg-surface-2 transition-colors">Response</button>
                                        <button className="px-4 py-2 rounded-lg text-sm font-medium text-muted hover:text-fg hover:bg-surface-2 transition-colors">cURL</button>
                                        <button className="px-4 py-2 rounded-lg text-sm font-medium text-muted hover:text-fg hover:bg-surface-2 transition-colors">Python</button>
                                    </div>

                                    <div className="flex-1 px-4">
                                        <textarea className="w-full h-full min-h-[200px] px-4 py-3 rounded-xl bg-surface border border-border text-sm text-fg font-mono focus:outline-none focus:ring-2 focus:ring-primary/50 resize-none" value={`{\n  "model": "${selectedModel}",\n  "prompt": "A stunning neon cityscape...",\n  "width": 1024,\n  "height": 1024,\n  "steps": 30,\n  "guidance_scale": 7.5,\n  "seed": null\n}`} readOnly />
                                    </div>
                                </div>

                                {/* Prompt bar */}
                                <div className="p-4 border-t border-border">
                                    <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="terminal" size={18} className="text-muted" />
                                            <input type="text" value={prompt} onChange={e => setPrompt(e.target.value)} placeholder="Enter your prompt..." className="flex-1 bg-transparent text-fg placeholder:text-muted outline-none text-sm" />
                                        </div>
                                        <button onClick={handleRun} disabled={isRunning} className={`px-6 py-3 rounded-xl font-semibold transition-all flex items-center gap-2 ${isRunning ? 'bg-surface-2 border border-border text-muted' : 'bg-primary text-primary-foreground hover:brightness-110 shadow-lg shadow-primary/25'}`}>
                                            {isRunning ? <><span className="w-4 h-4 border-2 border-muted border-t-transparent rounded-full animate-spin" />Running...</> : <><Icon name="play" size={18} />Run</>}
                                        </button>
                                    </div>
                                </div>
                            </div>
                        </div>
                    </div>
                </div>
            );
        };
        ReactDOM.createRoot(document.getElementById('root')).render(<App />);
    </script>
</body>
</html>