sugakrit6 commited on
Commit
94cab96
·
verified ·
1 Parent(s): 0bfb93e

Create gallery.html

Browse files
Files changed (1) hide show
  1. gallery.html +385 -0
gallery.html ADDED
@@ -0,0 +1,385 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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>Grain & Gallery Studio</title>
7
+ <style>
8
+ :root {
9
+ --bg: #121212;
10
+ --surface: #1e1e1e;
11
+ --primary: #bb86fc;
12
+ --text: #e0e0e0;
13
+ --danger: #cf6679;
14
+ }
15
+
16
+ body {
17
+ font-family: 'Segoe UI', sans-serif;
18
+ background-color: var(--bg);
19
+ color: var(--text);
20
+ margin: 0;
21
+ display: flex;
22
+ height: 100vh;
23
+ overflow: hidden;
24
+ }
25
+
26
+ /* --- Sidebar --- */
27
+ .sidebar {
28
+ width: 250px;
29
+ background-color: var(--surface);
30
+ padding: 20px;
31
+ display: flex;
32
+ flex-direction: column;
33
+ border-right: 1px solid #333;
34
+ }
35
+
36
+ .sidebar h2 { margin-top: 0; font-size: 1.2rem; color: var(--primary); }
37
+
38
+ .album-list {
39
+ list-style: none;
40
+ padding: 0;
41
+ margin-top: 20px;
42
+ flex-grow: 1;
43
+ overflow-y: auto;
44
+ }
45
+
46
+ .album-item {
47
+ padding: 10px;
48
+ cursor: pointer;
49
+ border-radius: 4px;
50
+ margin-bottom: 5px;
51
+ display: flex;
52
+ justify-content: space-between;
53
+ align-items: center;
54
+ }
55
+
56
+ .album-item:hover { background-color: #333; }
57
+ .album-item.active { background-color: var(--primary); color: #000; }
58
+
59
+ .lock-icon { font-size: 0.8rem; opacity: 0.7; }
60
+
61
+ button {
62
+ background-color: var(--primary);
63
+ border: none;
64
+ color: #000;
65
+ padding: 10px;
66
+ border-radius: 4px;
67
+ cursor: pointer;
68
+ font-weight: bold;
69
+ margin-top: 10px;
70
+ }
71
+
72
+ button.secondary { background-color: #333; color: white; border: 1px solid #555; }
73
+
74
+ /* --- Main Content --- */
75
+ .main {
76
+ flex-grow: 1;
77
+ padding: 20px;
78
+ overflow-y: auto;
79
+ position: relative;
80
+ }
81
+
82
+ .header {
83
+ display: flex;
84
+ justify-content: space-between;
85
+ align-items: center;
86
+ margin-bottom: 20px;
87
+ border-bottom: 1px solid #333;
88
+ padding-bottom: 20px;
89
+ }
90
+
91
+ .gallery-grid {
92
+ display: grid;
93
+ grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
94
+ gap: 20px;
95
+ }
96
+
97
+ .img-card {
98
+ background-color: var(--surface);
99
+ border-radius: 8px;
100
+ overflow: hidden;
101
+ transition: transform 0.2s;
102
+ }
103
+
104
+ .img-card:hover { transform: translateY(-5px); }
105
+
106
+ .img-card img {
107
+ width: 100%;
108
+ height: 200px;
109
+ object-fit: cover;
110
+ display: block;
111
+ }
112
+
113
+ .empty-state {
114
+ text-align: center;
115
+ opacity: 0.5;
116
+ margin-top: 50px;
117
+ }
118
+
119
+ /* --- Editor Modal --- */
120
+ .modal {
121
+ position: fixed;
122
+ top: 0; left: 0; right: 0; bottom: 0;
123
+ background: rgba(0,0,0,0.9);
124
+ display: none;
125
+ flex-direction: column;
126
+ align-items: center;
127
+ justify-content: center;
128
+ z-index: 1000;
129
+ }
130
+
131
+ .modal.open { display: flex; }
132
+
133
+ .canvas-container {
134
+ max-width: 80%;
135
+ max-height: 70%;
136
+ border: 1px solid #555;
137
+ margin-bottom: 20px;
138
+ overflow: hidden;
139
+ }
140
+
141
+ canvas { max-width: 100%; max-height: 100%; display: block; }
142
+
143
+ .controls {
144
+ background: var(--surface);
145
+ padding: 20px;
146
+ border-radius: 8px;
147
+ display: flex;
148
+ gap: 20px;
149
+ align-items: center;
150
+ }
151
+
152
+ input[type="range"] { accent-color: var(--primary); }
153
+
154
+ /* Access Badge */
155
+ .badge {
156
+ padding: 4px 8px;
157
+ border-radius: 4px;
158
+ font-size: 0.8rem;
159
+ font-weight: bold;
160
+ }
161
+ .badge.public { background: #2e7d32; color: white; }
162
+ .badge.private { background: #c62828; color: white; }
163
+
164
+ </style>
165
+ </head>
166
+ <body>
167
+
168
+ <div class="sidebar">
169
+ <h2>My Studio</h2>
170
+ <div class="album-list" id="albumList">
171
+ </div>
172
+
173
+ <hr style="width:100%; border-color:#333;">
174
+
175
+ <h3>New Album</h3>
176
+ <input type="text" id="newAlbumName" placeholder="Album Name" style="width: 90%; padding: 8px; margin-bottom: 10px; background: #333; border: none; color: white;">
177
+ <select id="newAlbumAccess" style="width: 96%; padding: 8px; margin-bottom: 10px; background: #333; border: none; color: white;">
178
+ <option value="public">Allow All (Public)</option>
179
+ <option value="private">Request Access (Private)</option>
180
+ </select>
181
+ <button onclick="createAlbum()">Create Album</button>
182
+ </div>
183
+
184
+ <div class="main">
185
+ <div class="header">
186
+ <div>
187
+ <h1 id="currentAlbumTitle">Select an Album</h1>
188
+ <span id="accessBadge" class="badge public" style="display:none;">Public</span>
189
+ </div>
190
+ <div id="uploadSection" style="display:none;">
191
+ <input type="file" id="fileInput" accept="image/*" style="display:none" onchange="loadImageForEditing(this)">
192
+ <button onclick="document.getElementById('fileInput').click()">+ Upload & Edit</button>
193
+ </div>
194
+ </div>
195
+
196
+ <div class="gallery-grid" id="galleryGrid">
197
+ <div class="empty-state">Select or create an album to begin.</div>
198
+ </div>
199
+ </div>
200
+
201
+ <div class="modal" id="editorModal">
202
+ <div class="canvas-container">
203
+ <canvas id="editorCanvas"></canvas>
204
+ </div>
205
+
206
+ <div class="controls">
207
+ <div>
208
+ <label>Grain Amount: <span id="grainValue">0</span></label><br>
209
+ <input type="range" id="grainRange" min="0" max="100" value="0" oninput="applyGrainEffect()">
210
+ </div>
211
+ <div>
212
+ <button onclick="saveImage()">Save to Album</button>
213
+ <button class="secondary" onclick="closeModal()">Cancel</button>
214
+ </div>
215
+ </div>
216
+ </div>
217
+
218
+ <script>
219
+ // --- State Management ---
220
+ // Using localStorage to persist data in browser
221
+ let albums = JSON.parse(localStorage.getItem('galleryAlbums')) || [];
222
+ let currentAlbumId = null;
223
+ let originalImage = null; // Stores the uploaded image for resetting filters
224
+
225
+ // --- Album Logic ---
226
+ function renderAlbums() {
227
+ const list = document.getElementById('albumList');
228
+ list.innerHTML = '';
229
+
230
+ albums.forEach(album => {
231
+ const li = document.createElement('li');
232
+ li.className = `album-item ${currentAlbumId === album.id ? 'active' : ''}`;
233
+ li.innerHTML = `
234
+ <span>${album.name}</span>
235
+ ${album.access === 'private' ? '<span class="lock-icon">🔒</span>' : ''}
236
+ `;
237
+ li.onclick = () => selectAlbum(album.id);
238
+ list.appendChild(li);
239
+ });
240
+ }
241
+
242
+ function createAlbum() {
243
+ const name = document.getElementById('newAlbumName').value;
244
+ const access = document.getElementById('newAlbumAccess').value;
245
+
246
+ if (!name) return alert("Please enter a name");
247
+
248
+ const newAlbum = {
249
+ id: Date.now(),
250
+ name: name,
251
+ access: access,
252
+ images: []
253
+ };
254
+
255
+ albums.push(newAlbum);
256
+ saveData();
257
+ document.getElementById('newAlbumName').value = '';
258
+ renderAlbums();
259
+ selectAlbum(newAlbum.id);
260
+ }
261
+
262
+ function selectAlbum(id) {
263
+ currentAlbumId = id;
264
+ renderAlbums();
265
+ renderGallery();
266
+ }
267
+
268
+ function renderGallery() {
269
+ const album = albums.find(a => a.id === currentAlbumId);
270
+ const grid = document.getElementById('galleryGrid');
271
+ const title = document.getElementById('currentAlbumTitle');
272
+ const uploadSection = document.getElementById('uploadSection');
273
+ const badge = document.getElementById('accessBadge');
274
+
275
+ if (!album) return;
276
+
277
+ // Update Header
278
+ title.innerText = album.name;
279
+ uploadSection.style.display = 'block';
280
+
281
+ // Update Access Badge
282
+ badge.style.display = 'inline-block';
283
+ badge.className = `badge ${album.access}`;
284
+ badge.innerText = album.access === 'public' ? 'Public (Allow All)' : 'Private (Request Only)';
285
+
286
+ // Render Images
287
+ grid.innerHTML = '';
288
+ if (album.images.length === 0) {
289
+ grid.innerHTML = '<div class="empty-state">No images yet. Upload one!</div>';
290
+ } else {
291
+ album.images.forEach(imgData => {
292
+ const card = document.createElement('div');
293
+ card.className = 'img-card';
294
+ card.innerHTML = `<img src="${imgData}" />`;
295
+ grid.appendChild(card);
296
+ });
297
+ }
298
+ }
299
+
300
+ // --- Editor & Grain Logic ---
301
+ const modal = document.getElementById('editorModal');
302
+ const canvas = document.getElementById('editorCanvas');
303
+ const ctx = canvas.getContext('2d');
304
+
305
+ function loadImageForEditing(input) {
306
+ if (input.files && input.files[0]) {
307
+ const reader = new FileReader();
308
+ reader.onload = function(e) {
309
+ const img = new Image();
310
+ img.onload = function() {
311
+ // Set Canvas size to match image (max 800px width for performance)
312
+ const scale = Math.min(1, 800 / img.width);
313
+ canvas.width = img.width * scale;
314
+ canvas.height = img.height * scale;
315
+
316
+ ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
317
+ originalImage = ctx.getImageData(0, 0, canvas.width, canvas.height);
318
+
319
+ modal.classList.add('open');
320
+ document.getElementById('grainRange').value = 0;
321
+ }
322
+ img.src = e.target.result;
323
+ }
324
+ reader.readAsDataURL(input.files[0]);
325
+ }
326
+ }
327
+
328
+ function applyGrainEffect() {
329
+ if (!originalImage) return;
330
+
331
+ const amount = parseInt(document.getElementById('grainRange').value);
332
+ document.getElementById('grainValue').innerText = amount;
333
+
334
+ // Restore original before applying new noise
335
+ ctx.putImageData(originalImage, 0, 0);
336
+
337
+ if (amount === 0) return;
338
+
339
+ const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
340
+ const data = imageData.data;
341
+
342
+ // Loop through pixels and add noise
343
+ for (let i = 0; i < data.length; i += 4) {
344
+ // Generate random noise value (-amount to +amount)
345
+ const noise = (Math.random() - 0.5) * amount * 2;
346
+
347
+ // Apply to RGB
348
+ data[i] = Math.min(255, Math.max(0, data[i] + noise)); // Red
349
+ data[i+1] = Math.min(255, Math.max(0, data[i+1] + noise)); // Green
350
+ data[i+2] = Math.min(255, Math.max(0, data[i+2] + noise)); // Blue
351
+ }
352
+
353
+ ctx.putImageData(imageData, 0, 0);
354
+ }
355
+
356
+ function saveImage() {
357
+ const album = albums.find(a => a.id === currentAlbumId);
358
+ if (album) {
359
+ // Convert canvas back to image URL
360
+ const newData = canvas.toDataURL('image/jpeg', 0.8);
361
+ album.images.push(newData);
362
+ saveData();
363
+ renderGallery();
364
+ closeModal();
365
+ }
366
+ }
367
+
368
+ function closeModal() {
369
+ modal.classList.remove('open');
370
+ document.getElementById('fileInput').value = ''; // Reset input
371
+ }
372
+
373
+ function saveData() {
374
+ try {
375
+ localStorage.setItem('galleryAlbums', JSON.stringify(albums));
376
+ } catch (e) {
377
+ alert("Storage full! Cannot save more images in this demo.");
378
+ }
379
+ }
380
+
381
+ // Initialize
382
+ renderAlbums();
383
+ </script>
384
+ </body>
385
+ </html>