00Boobs00 commited on
Commit
394e244
·
verified ·
1 Parent(s): 9cefeeb

Upload folder using huggingface_hub

Browse files
Files changed (1) hide show
  1. index.html +660 -19
index.html CHANGED
@@ -1,19 +1,660 @@
1
- <!doctype html>
2
- <html>
3
- <head>
4
- <meta charset="utf-8" />
5
- <meta name="viewport" content="width=device-width" />
6
- <title>My static Space</title>
7
- <link rel="stylesheet" href="style.css" />
8
- </head>
9
- <body>
10
- <div class="card">
11
- <h1>Welcome to your static Space!</h1>
12
- <p>You can modify this app directly by editing <i>index.html</i> in the Files and versions tab.</p>
13
- <p>
14
- Also don't forget to check the
15
- <a href="https://huggingface.co/docs/hub/spaces" target="_blank">Spaces documentation</a>.
16
- </p>
17
- </div>
18
- </body>
19
- </html>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>LUXE | Professional Studio Suite</title>
7
+ <script src="https://cdn.tailwindcss.com"></script>
8
+ <link href="https://fonts.googleapis.com/css2?family=Playfair+Display:wght@400;700&family=Inter:wght@300;400;600&display=swap" rel="stylesheet">
9
+ <style>
10
+ :root {
11
+ --primary-red: #8B0000;
12
+ --accent-gold: #D4AF37;
13
+ --deep-grey: #1a1a1a;
14
+ --ui-green: #10B981;
15
+ }
16
+
17
+ body {
18
+ font-family: 'Inter', sans-serif;
19
+ background-color: #0f0f0f;
20
+ color: #e5e5e5;
21
+ overflow-x: hidden;
22
+ }
23
+
24
+ h1, h2, h3, .serif {
25
+ font-family: 'Playfair Display', serif;
26
+ }
27
+
28
+ /* Custom Scrollbar */
29
+ ::-webkit-scrollbar {
30
+ width: 8px;
31
+ height: 8px;
32
+ }
33
+ ::-webkit-scrollbar-track {
34
+ background: #1a1a1a;
35
+ }
36
+ ::-webkit-scrollbar-thumb {
37
+ background: #333;
38
+ border-radius: 4px;
39
+ }
40
+ ::-webkit-scrollbar-thumb:hover {
41
+ background: var(--primary-red);
42
+ }
43
+
44
+ /* Glassmorphism */
45
+ .glass-panel {
46
+ background: rgba(30, 30, 30, 0.7);
47
+ backdrop-filter: blur(12px);
48
+ -webkit-backdrop-filter: blur(12px);
49
+ border: 1px solid rgba(255, 255, 255, 0.08);
50
+ box-shadow: 0 8px 32px 0 rgba(0, 0, 0, 0.37);
51
+ }
52
+
53
+ /* Neon Glows */
54
+ .glow-gold {
55
+ box-shadow: 0 0 15px rgba(212, 175, 55, 0.3);
56
+ }
57
+ .glow-red {
58
+ box-shadow: 0 0 15px rgba(139, 0, 0, 0.4);
59
+ }
60
+ .text-glow {
61
+ text-shadow: 0 0 10px rgba(212, 175, 55, 0.5);
62
+ }
63
+
64
+ /* Range Slider Styling */
65
+ input[type=range] {
66
+ -webkit-appearance: none;
67
+ background: transparent;
68
+ }
69
+ input[type=range]::-webkit-slider-thumb {
70
+ -webkit-appearance: none;
71
+ height: 16px;
72
+ width: 16px;
73
+ border-radius: 50%;
74
+ background: var(--accent-gold);
75
+ cursor: pointer;
76
+ margin-top: -6px;
77
+ box-shadow: 0 0 10px rgba(212, 175, 55, 0.8);
78
+ }
79
+ input[type=range]::-webkit-slider-runnable-track {
80
+ width: 100%;
81
+ height: 4px;
82
+ cursor: pointer;
83
+ background: #333;
84
+ border-radius: 2px;
85
+ }
86
+
87
+ /* Loader */
88
+ .loader {
89
+ border: 3px solid rgba(255,255,255,0.1);
90
+ border-left-color: var(--accent-gold);
91
+ border-radius: 50%;
92
+ width: 40px;
93
+ height: 40px;
94
+ animation: spin 1s linear infinite;
95
+ }
96
+ @keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } }
97
+
98
+ /* Canvas Container */
99
+ #canvas-container {
100
+ background-image:
101
+ linear-gradient(45deg, #1a1a1a 25%, transparent 25%),
102
+ linear-gradient(-45deg, #1a1a1a 25%, transparent 25%),
103
+ linear-gradient(45deg, transparent 75%, #1a1a1a 75%),
104
+ linear-gradient(-45deg, transparent 75%, #1a1a1a 75%);
105
+ background-size: 20px 20px;
106
+ background-position: 0 0, 0 10px, 10px -10px, -10px 0px;
107
+ background-color: #0f0f0f;
108
+ }
109
+
110
+ .tool-btn.active {
111
+ background-color: rgba(212, 175, 55, 0.1);
112
+ border-left: 3px solid var(--accent-gold);
113
+ color: var(--accent-gold);
114
+ }
115
+
116
+ /* Custom Checkbox */
117
+ .toggle-checkbox:checked {
118
+ right: 0;
119
+ border-color: var(--ui-green);
120
+ }
121
+ .toggle-checkbox:checked + .toggle-label {
122
+ background-color: var(--ui-green);
123
+ }
124
+
125
+ /* Grain Overlay */
126
+ .noise-overlay {
127
+ position: fixed;
128
+ top: 0;
129
+ left: 0;
130
+ width: 100%;
131
+ height: 100%;
132
+ pointer-events: none;
133
+ z-index: 9999;
134
+ opacity: 0.03;
135
+ background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 200 200' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='noiseFilter'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.65' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23noiseFilter)'/%3E%3C/svg%3E");
136
+ }
137
+ </style>
138
+ </head>
139
+ <body class="flex flex-col h-screen">
140
+
141
+ <!-- Noise Texture -->
142
+ <div class="noise-overlay"></div>
143
+
144
+ <!-- Header -->
145
+ <header class="h-16 border-b border-white/10 flex items-center justify-between px-6 bg-[#0f0f0f] z-50 relative">
146
+ <div class="flex items-center gap-3">
147
+ <div class="w-8 h-8 bg-gradient-to-br from-[#8B0000] to-[#D4AF37] rounded-sm flex items-center justify-center shadow-lg shadow-red-900/20">
148
+ <span class="font-serif font-bold text-white text-lg">L</span>
149
+ </div>
150
+ <div>
151
+ <h1 class="text-xl font-serif tracking-widest text-white">LUXE <span class="text-[#D4AF37] text-xs align-top">STUDIO</span></h1>
152
+ <p class="text-[10px] text-gray-500 tracking-widest uppercase">Professional Post-Production Suite</p>
153
+ </div>
154
+ </div>
155
+
156
+ <div class="flex items-center gap-6">
157
+ <a href="https://huggingface.co/spaces/akhaliq/anycoder" target="_blank" class="text-xs text-gray-400 hover:text-[#D4AF37] transition-colors uppercase tracking-wider border-b border-transparent hover:border-[#D4AF37]">
158
+ Built with anycoder
159
+ </a>
160
+ <div class="h-4 w-[1px] bg-white/10"></div>
161
+ <div class="flex items-center gap-2 text-xs text-gray-400">
162
+ <span class="w-2 h-2 rounded-full bg-[#10B981] animate-pulse"></span>
163
+ System Online
164
+ </div>
165
+ <button onclick="exportImage()" class="bg-[#D4AF37] hover:bg-[#b5952f] text-black px-6 py-2 text-sm font-bold tracking-wide transition-all shadow-[0_0_15px_rgba(212,175,55,0.3)]">
166
+ EXPORT FINAL CUT
167
+ </button>
168
+ </div>
169
+ </header>
170
+
171
+ <!-- Main Workspace -->
172
+ <div class="flex-1 flex overflow-hidden relative">
173
+
174
+ <!-- Left Sidebar: Tools -->
175
+ <aside class="w-80 bg-[#141414] border-r border-white/5 flex flex-col z-40 overflow-y-auto">
176
+
177
+ <!-- File Upload Zone -->
178
+ <div class="p-6 border-b border-white/5">
179
+ <label for="file-upload" class="group cursor-pointer flex flex-col items-center justify-center w-full h-32 border-2 border-dashed border-white/10 rounded-lg hover:border-[#D4AF37]/50 hover:bg-white/5 transition-all">
180
+ <div class="flex flex-col items-center justify-center pt-5 pb-6">
181
+ <svg class="w-8 h-8 mb-3 text-gray-400 group-hover:text-[#D4AF37]" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 16a4 4 0 01-.88-7.903A5 5 0 1115.9 6L16 6a5 5 0 011 9.9M15 13l-3-3m0 0l-3 3m3-3v12"></path></svg>
182
+ <p class="text-xs text-gray-400">Click to upload RAW</p>
183
+ </div>
184
+ <input id="file-upload" type="file" class="hidden" accept="image/*" />
185
+ </label>
186
+ </div>
187
+
188
+ <!-- Tool Categories -->
189
+ <div class="flex-1">
190
+ <!-- Section: Retouch -->
191
+ <div class="p-4 border-b border-white/5">
192
+ <h3 class="text-xs font-bold text-gray-500 uppercase tracking-widest mb-4">Skin & Tone</h3>
193
+
194
+ <div class="space-y-5">
195
+ <div>
196
+ <div class="flex justify-between mb-1">
197
+ <label class="text-xs text-gray-300">Smoothing (Frequency Sep)</label>
198
+ <span id="val-smooth" class="text-xs text-[#D4AF37]">0%</span>
199
+ </div>
200
+ <input type="range" id="smooth" min="0" max="100" value="0" class="w-full">
201
+ </div>
202
+
203
+ <div>
204
+ <div class="flex justify-between mb-1">
205
+ <label class="text-xs text-gray-300">Tan / Bronze</label>
206
+ <span id="val-tan" class="text-xs text-[#D4AF37]">0%</span>
207
+ </div>
208
+ <input type="range" id="tan" min="0" max="100" value="0" class="w-full">
209
+ </div>
210
+
211
+ <div>
212
+ <div class="flex justify-between mb-1">
213
+ <label class="text-xs text-gray-300">Highlights (Oily Skin Fix)</label>
214
+ <span id="val-highlight" class="text-xs text-[#D4AF37]">0%</span>
215
+ </div>
216
+ <input type="range" id="highlight" min="0" max="100" value="0" class="w-full">
217
+ </div>
218
+ </div>
219
+ </div>
220
+
221
+ <!-- Section: Color Grading -->
222
+ <div class="p-4 border-b border-white/5">
223
+ <h3 class="text-xs font-bold text-gray-500 uppercase tracking-widest mb-4">Cinematic Grade</h3>
224
+
225
+ <div class="space-y-5">
226
+ <div>
227
+ <div class="flex justify-between mb-1">
228
+ <label class="text-xs text-gray-300">Teal & Orange Shift</label>
229
+ <span id="val-teal" class="text-xs text-[#D4AF37]">0%</span>
230
+ </div>
231
+ <input type="range" id="teal" min="0" max="100" value="0" class="w-full">
232
+ </div>
233
+
234
+ <div>
235
+ <div class="flex justify-between mb-1">
236
+ <label class="text-xs text-gray-300">Vignette (Spotlight)</label>
237
+ <span id="val-vignette" class="text-xs text-[#D4AF37]">0%</span>
238
+ </div>
239
+ <input type="range" id="vignette" min="0" max="100" value="0" class="w-full">
240
+ </div>
241
+
242
+ <div>
243
+ <div class="flex justify-between mb-1">
244
+ <label class="text-xs text-gray-300">Film Grain</label>
245
+ <span id="val-grain" class="text-xs text-[#D4AF37]">0%</span>
246
+ </div>
247
+ <input type="range" id="grain" min="0" max="100" value="0" class="w-full">
248
+ </div>
249
+ </div>
250
+ </div>
251
+
252
+ <!-- Section: Lighting -->
253
+ <div class="p-4 border-b border-white/5">
254
+ <h3 class="text-xs font-bold text-gray-500 uppercase tracking-widest mb-4">Studio Lighting</h3>
255
+
256
+ <div class="space-y-5">
257
+ <div>
258
+ <div class="flex justify-between mb-1">
259
+ <label class="text-xs text-gray-300">Key Light (Exposure)</label>
260
+ <span id="val-exposure" class="text-xs text-[#D4AF37]">0</span>
261
+ </div>
262
+ <input type="range" id="exposure" min="-50" max="50" value="0" class="w-full">
263
+ </div>
264
+
265
+ <div class="flex items-center justify-between">
266
+ <label class="text-xs text-gray-300">Neon Rim Light (Red)</label>
267
+ <div class="relative inline-block w-10 mr-2 align-middle select-none transition duration-200 ease-in">
268
+ <input type="checkbox" name="toggle" id="neon-toggle" class="toggle-checkbox absolute block w-5 h-5 rounded-full bg-white border-4 appearance-none cursor-pointer border-gray-600"/>
269
+ <label for="neon-toggle" class="toggle-label block overflow-hidden h-5 rounded-full bg-gray-700 cursor-pointer"></label>
270
+ </div>
271
+ </div>
272
+ </div>
273
+ </div>
274
+ </div>
275
+ </aside>
276
+
277
+ <!-- Center: Canvas Area -->
278
+ <main class="flex-1 bg-[#0f0f0f] relative flex flex-col">
279
+ <!-- Toolbar -->
280
+ <div class="h-12 bg-[#1a1a1a] border-b border-white/5 flex items-center justify-center gap-4 px-4">
281
+ <span class="text-xs text-gray-500 uppercase tracking-widest mr-4">View Mode:</span>
282
+ <button onclick="setZoom(0.5)" class="text-gray-400 hover:text-white text-xs px-2 py-1 rounded hover:bg-white/5">50%</button>
283
+ <button onclick="setZoom(1)" class="text-[#D4AF37] text-xs px-2 py-1 rounded bg-white/5 border border-[#D4AF37]/30">100%</button>
284
+ <button onclick="setZoom(2)" class="text-gray-400 hover:text-white text-xs px-2 py-1 rounded hover:bg-white/5">200%</button>
285
+ <div class="w-[1px] h-4 bg-white/10 mx-2"></div>
286
+ <button onclick="resetFilters()" class="text-[#8B0000] hover:text-red-500 text-xs uppercase tracking-wider font-bold flex items-center gap-1">
287
+ <svg class="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"></path></svg>
288
+ Reset All
289
+ </button>
290
+ </div>
291
+
292
+ <!-- Canvas Wrapper -->
293
+ <div id="canvas-container" class="flex-1 overflow-auto flex items-center justify-center p-8 relative">
294
+ <div id="loader" class="hidden absolute inset-0 bg-black/80 z-50 flex flex-col items-center justify-center">
295
+ <div class="loader mb-4"></div>
296
+ <p class="text-[#D4AF37] text-sm tracking-widest animate-pulse">PROCESSING LAYERS...</p>
297
+ </div>
298
+
299
+ <!-- The Canvas -->
300
+ <canvas id="editor" class="shadow-2xl shadow-black max-w-full max-h-full"></canvas>
301
+
302
+ <!-- Empty State -->
303
+ <div id="empty-state" class="absolute inset-0 flex flex-col items-center justify-center pointer-events-none">
304
+ <div class="w-24 h-24 border border-white/10 rounded-full flex items-center justify-center mb-4">
305
+ <svg class="w-10 h-10 text-white/20" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1" d="M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z"></path></svg>
306
+ </div>
307
+ <p class="text-gray-600 font-serif text-lg">No Image Loaded</p>
308
+ <p class="text-gray-700 text-xs mt-2">Upload a RAW file to begin editing</p>
309
+ </div>
310
+ </div>
311
+ </main>
312
+
313
+ <!-- Right Sidebar: Details & History -->
314
+ <aside class="w-72 bg-[#141414] border-l border-white/5 flex flex-col z-40">
315
+ <div class="p-5 border-b border-white/5">
316
+ <h3 class="text-xs font-bold text-gray-500 uppercase tracking-widest mb-4">Asset Info</h3>
317
+ <div class="space-y-3 text-xs text-gray-400 font-mono">
318
+ <div class="flex justify-between">
319
+ <span>Resolution</span>
320
+ <span id="meta-res" class="text-gray-200">---</span>
321
+ </div>
322
+ <div class="flex justify-between">
323
+ <span>Format</span>
324
+ <span class="text-gray-200">RAW / JPG</span>
325
+ </div>
326
+ <div class="flex justify-between">
327
+ <span>Color Space</span>
328
+ <span class="text-gray-200">sRGB IEC</span>
329
+ </div>
330
+ </div>
331
+ </div>
332
+
333
+ <div class="flex-1 p-5 overflow-y-auto">
334
+ <h3 class="text-xs font-bold text-gray-500 uppercase tracking-widest mb-4">Action History</h3>
335
+ <ul id="history-list" class="space-y-2 text-xs">
336
+ <li class="text-gray-600 italic">No actions recorded...</li>
337
+ </ul>
338
+ </div>
339
+
340
+ <div class="p-5 border-t border-white/5 bg-[#0f0f0f]">
341
+ <div class="bg-[#8B0000]/10 border border-[#8B0000]/30 p-3 rounded">
342
+ <h4 class="text-[#8B0000] text-xs font-bold uppercase mb-1">Pro Tip</h4>
343
+ <p class="text-gray-400 text-[10px] leading-relaxed">
344
+ For skin retouching, keep smoothing below 40% to maintain texture detail. Use the "Oily Skin Fix" to matte down highlights.
345
+ </p>
346
+ </div>
347
+ </div>
348
+ </aside>
349
+ </div>
350
+
351
+ <script>
352
+ // --- Configuration & State ---
353
+ const canvas = document.getElementById('editor');
354
+ const ctx = canvas.getContext('2d', { willReadFrequently: true });
355
+ const fileInput = document.getElementById('file-upload');
356
+ const emptyState = document.getElementById('empty-state');
357
+ const loader = document.getElementById('loader');
358
+ const historyList = document.getElementById('history-list');
359
+
360
+ let originalImage = new Image();
361
+ let currentImage = null; // Will hold the processed image data for base
362
+ let fileName = 'luxe-edit.jpg';
363
+ let isProcessing = false;
364
+
365
+ // Filter State
366
+ const filters = {
367
+ smooth: 0,
368
+ tan: 0,
369
+ highlight: 0,
370
+ teal: 0,
371
+ vignette: 0,
372
+ grain: 0,
373
+ exposure: 0,
374
+ neon: false
375
+ };
376
+
377
+ // --- Event Listeners ---
378
+
379
+ // File Upload
380
+ fileInput.addEventListener('change', (e) => {
381
+ const file = e.target.files[0];
382
+ if (!file) return;
383
+
384
+ fileName = file.name;
385
+ const reader = new FileReader();
386
+ reader.onload = (event) => {
387
+ originalImage.src = event.target.result;
388
+ };
389
+ reader.readAsDataURL(file);
390
+ });
391
+
392
+ originalImage.onload = () => {
393
+ // Resize canvas to match image but cap max size for performance
394
+ const maxWidth = 1920;
395
+ let width = originalImage.width;
396
+ let height = originalImage.height;
397
+
398
+ if (width > maxWidth) {
399
+ height *= maxWidth / width;
400
+ width = maxWidth;
401
+ }
402
+
403
+ canvas.width = width;
404
+ canvas.height = height;
405
+
406
+ // Draw original
407
+ ctx.drawImage(originalImage, 0, 0, width, height);
408
+
409
+ // Store original pixel data for non-destructive editing base
410
+ // Note: For a real app we'd store the image object, here we redraw from source
411
+ // But for pixel manipulation we need the buffer.
412
+
413
+ emptyState.style.display = 'none';
414
+ updateMeta();
415
+ addHistory('Image Loaded');
416
+ render();
417
+ };
418
+
419
+ // Sliders
420
+ const sliderIds = ['smooth', 'tan', 'highlight', 'teal', 'vignette', 'grain', 'exposure'];
421
+ sliderIds.forEach(id => {
422
+ const el = document.getElementById(id);
423
+ el.addEventListener('input', (e) => {
424
+ filters[id] = parseInt(e.target.value);
425
+ document.getElementById(`val-${id}`).innerText = filters[id] + (id === 'exposure' ? '' : '%');
426
+ requestAnimationFrame(render);
427
+ });
428
+ // Add history on change (mouse up)
429
+ el.addEventListener('change', () => {
430
+ addHistory(`${id.charAt(0).toUpperCase() + id.slice(1)} adjusted to ${filters[id]}`);
431
+ });
432
+ });
433
+
434
+ // Toggles
435
+ document.getElementById('neon-toggle').addEventListener('change', (e) => {
436
+ filters.neon = e.target.checked;
437
+ addHistory(filters.neon ? 'Neon Rim Light ON' : 'Neon Rim Light OFF');
438
+ render();
439
+ });
440
+
441
+ // --- Core Rendering Logic ---
442
+
443
+ function render() {
444
+ if (!originalImage.src) return;
445
+
446
+ // 1. Draw Base Image
447
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
448
+ ctx.drawImage(originalImage, 0, 0, canvas.width, canvas.height);
449
+
450
+ // Get image data for pixel manipulation
451
+ // Note: getImageData is expensive. In a high-perf app, use WebGL.
452
+ // For this demo, we use it for specific effects.
453
+
454
+ // Helper to apply filters
455
+ ctx.save();
456
+
457
+ // Global Exposure (Brightness/Contrast composite)
458
+ // We simulate exposure by adjusting brightness and contrast slightly
459
+ const exposureVal = filters.exposure;
460
+ // Simple exposure simulation using filter string
461
+ let filterString = `brightness(${100 + exposureVal}%)`;
462
+
463
+ // Teal & Orange
464
+ if (filters.teal > 0) {
465
+ // Sepia adds the orange, Hue-rotate shifts to teal shadows (simplified)
466
+ filterString += ` sepia(${filters.teal * 0.3}%) contrast(${100 + filters.teal * 0.1}%)`;
467
+ }
468
+
469
+ ctx.filter = filterString;
470
+ ctx.drawImage(originalImage, 0, 0, canvas.width, canvas.height);
471
+ ctx.filter = 'none'; // Reset for overlays
472
+
473
+ // 2. Skin Smoothing (Simulated via Blur + Blend)
474
+ if (filters.smooth > 0) {
475
+ ctx.globalAlpha = filters.smooth / 100;
476
+ ctx.filter = `blur(${filters.smooth / 10}px)`;
477
+ ctx.drawImage(originalImage, 0, 0, canvas.width, canvas.height);
478
+ ctx.filter = 'none';
479
+ ctx.globalAlpha = 1.0;
480
+ }
481
+
482
+ // 3. Tan / Bronze (Overlay Color)
483
+ if (filters.tan > 0) {
484
+ ctx.globalCompositeOperation = 'overlay';
485
+ ctx.fillStyle = `rgba(205, 127, 50, ${filters.tan / 150})`; // Bronze color
486
+ ctx.fillRect(0, 0, canvas.width, canvas.height);
487
+ ctx.globalCompositeOperation = 'source-over';
488
+ }
489
+
490
+ // 4. Highlights / Matte (White overlay with mask logic simulated)
491
+ if (filters.highlight > 0) {
492
+ ctx.globalCompositeOperation = 'screen'; // Lighten
493
+ // This is a rough approximation. Real high-pass filter is better.
494
+ // We just reduce opacity of a white layer where it's bright? Hard to do without pixel loop.
495
+ // Let's use a simpler approach: Draw image lighter, mix it.
496
+ ctx.globalAlpha = filters.highlight / 200;
497
+ ctx.filter = 'brightness(150%)';
498
+ ctx.drawImage(originalImage, 0, 0, canvas.width, canvas.height);
499
+ ctx.filter = 'none';
500
+ ctx.globalAlpha = 1.0;
501
+ ctx.globalCompositeOperation = 'source-over';
502
+ }
503
+
504
+ // 5. Vignette
505
+ if (filters.vignette > 0) {
506
+ const gradient = ctx.createRadialGradient(
507
+ canvas.width / 2, canvas.height / 2, canvas.width * 0.3,
508
+ canvas.width / 2, canvas.height / 2, canvas.width * 0.8
509
+ );
510
+ gradient.addColorStop(0, 'rgba(0,0,0,0)');
511
+ gradient.addColorStop(1, `rgba(0,0,0,${filters.vignette / 100})`);
512
+
513
+ ctx.fillStyle = gradient;
514
+ ctx.fillRect(0, 0, canvas.width, canvas.height);
515
+ }
516
+
517
+ // 6. Neon Rim Light (Simulated)
518
+ if (filters.neon) {
519
+ ctx.shadowColor = "#ff0000";
520
+ ctx.shadowBlur = 40;
521
+ ctx.globalCompositeOperation = 'screen';
522
+ ctx.globalAlpha = 0.6;
523
+ ctx.drawImage(originalImage, 0, 0, canvas.width, canvas.height);
524
+ ctx.globalAlpha = 1.0;
525
+ ctx.globalCompositeOperation = 'source-over';
526
+ ctx.shadowBlur = 0;
527
+ }
528
+
529
+ // 7. Film Grain
530
+ if (filters.grain > 0) {
531
+ drawNoise(filters.grain);
532
+ }
533
+
534
+ ctx.restore();
535
+ }
536
+
537
+ // Helper: Procedural Noise
538
+ function drawNoise(amount) {
539
+ const w = canvas.width;
540
+ const h = canvas.height;
541
+ const idata = ctx.getImageData(0, 0, w, h);
542
+ const buffer32 = new Uint32Array(idata.data.buffer);
543
+ const len = buffer32.length;
544
+
545
+ for (let i = 0; i < len; i++) {
546
+ if (Math.random() < 0.5) continue; // Skip some pixels for performance
547
+ // Generate noise
548
+ let noise = (Math.random() - 0.5) * amount;
549
+
550
+ // Apply noise to pixel (simplified)
551
+ // This is a very basic noise implementation
552
+ // Real film grain is more complex
553
+ let val = buffer32[i];
554
+ // Extract alpha to ensure we don't mess with transparent pixels if any
555
+ let a = (val >> 24) & 0xff;
556
+ if (a === 0) continue;
557
+
558
+ // We can't easily edit the Uint32 directly without bit shifting for RGB separately
559
+ // So we fallback to a canvas overlay method for grain to be faster
560
+
561
+ // Better Grain Approach: Create small noise canvas and overlay
562
+ }
563
+
564
+ // Re-doing grain via overlay for performance
565
+ ctx.save();
566
+ ctx.globalAlpha = amount / 300; // subtle
567
+ ctx.globalCompositeOperation = 'overlay';
568
+ // We would need a pre-generated noise image.
569
+ // Since we can't load external assets easily without CORS/Reliability issues in this specific prompt context,
570
+ // We will draw random rects (very fast) to simulate noise.
571
+
572
+ ctx.fillStyle = '#808080'; // Neutral grey for overlay blend
573
+
574
+ // Optimization: Draw a few thousand random dots
575
+ for(let i=0; i<5000; i++) {
576
+ const x = Math.random() * w;
577
+ const y = Math.random() * h;
578
+ const s = Math.random() * 2;
579
+ ctx.fillStyle = Math.random() > 0.5 ? '#ffffff' : '#000000';
580
+ ctx.globalAlpha = Math.random() * (amount / 100);
581
+ ctx.fillRect(x, y, s, s);
582
+ }
583
+ ctx.restore();
584
+ }
585
+
586
+ // --- Utilities ---
587
+
588
+ function setZoom(level) {
589
+ // CSS Transform for zooming the canvas container visually
590
+ // Note: This doesn't change canvas resolution, just view
591
+ const container = document.getElementById('canvas-container');
592
+ // Simple implementation: Just width/height css
593
+ // For a robust app, use transform: scale()
594
+ canvas.style.transform = `scale(${level})`;
595
+ canvas.style.transition = 'transform 0.3s ease';
596
+ }
597
+
598
+ function resetFilters() {
599
+ sliderIds.forEach(id => {
600
+ document.getElementById(id).value = 0;
601
+ document.getElementById(`val-${id}`).innerText = id === 'exposure' ? '0' : '0%';
602
+ filters[id] = 0;
603
+ });
604
+ document.getElementById('neon-toggle').checked = false;
605
+ filters.neon = false;
606
+
607
+ addHistory('Reset all filters');
608
+ render();
609
+ }
610
+
611
+ function updateMeta() {
612
+ document.getElementById('meta-res').innerText = `${canvas.width} x ${canvas.height}`;
613
+ }
614
+
615
+ function addHistory(action) {
616
+ const li = document.createElement('li');
617
+ li.className = "flex justify-between items-center text-gray-300 border-b border-white/5 pb-1 animate-[fadeIn_0.3s_ease-in]";
618
+ const time = new Date().toLocaleTimeString([], { hour: '2-digit', minute: '2-digit', second: '2-digit' });
619
+
620
+ li.innerHTML = `
621
+ <span>${action}</span>
622
+ <span class="text-[10px] text-gray-600 font-mono">${time}</span>
623
+ `;
624
+
625
+ // Remove "No actions" text if present
626
+ if (historyList.children[0] && historyList.children[0].innerText.includes('No actions')) {
627
+ historyList.innerHTML = '';
628
+ }
629
+
630
+ historyList.prepend(li);
631
+ }
632
+
633
+ function exportImage() {
634
+ if (!originalImage.src) return alert("Please load an image first");
635
+
636
+ // Simulate processing delay
637
+ loader.classList.remove('hidden');
638
+
639
+ setTimeout(() => {
640
+ const link = document.createElement('a');
641
+ link.download = 'luxe-final-cut-' + Date.now() + '.jpg';
642
+ link.href = canvas.toDataURL('image/jpeg', 0.9);
643
+ link.click();
644
+ loader.classList.add('hidden');
645
+ addHistory('Exported Final Cut');
646
+ }, 1500);
647
+ }
648
+
649
+ // Initial Animation
650
+ window.addEventListener('DOMContentLoaded', () => {
651
+ document.body.style.opacity = 0;
652
+ setTimeout(() => {
653
+ document.body.style.transition = 'opacity 1s ease';
654
+ document.body.style.opacity = 1;
655
+ }, 100);
656
+ });
657
+
658
+ </script>
659
+ </body>
660
+ </html>