Server : Apache/2.4.18 (Ubuntu) System : Linux canvaswebdesign 3.13.0-71-generic #114-Ubuntu SMP Tue Dec 1 02:34:22 UTC 2015 x86_64 User : oppastar ( 1041) PHP Version : 7.0.33-0ubuntu0.16.04.15 Disable Function : pcntl_alarm,pcntl_fork,pcntl_waitpid,pcntl_wait,pcntl_wifexited,pcntl_wifstopped,pcntl_wifsignaled,pcntl_wifcontinued,pcntl_wexitstatus,pcntl_wtermsig,pcntl_wstopsig,pcntl_signal,pcntl_signal_dispatch,pcntl_get_last_error,pcntl_strerror,pcntl_sigprocmask,pcntl_sigwaitinfo,pcntl_sigtimedwait,pcntl_exec,pcntl_getpriority,pcntl_setpriority, Directory : /var/www/laciasmara.com/public_html/shop/application/views/admin_new/badges/ |
Upload File : |
<style> .color-picker-overlay { backdrop-filter: blur(4px); } .color-wheel { background: conic-gradient(red, #ff8000, yellow, #80ff00, lime, #00ff80, cyan, #0080ff, blue, #8000ff, magenta, #ff0080, red); } .saturation-lightness { background: linear-gradient(to right, white, transparent), linear-gradient(to top, black, transparent); } </style> <main class="py-4 px-4 flex-1 bg-purple-50 min-h-screen"> <form action="<?= base_url('admin/badge/' . $badge->id . '/update') ?>" method="POST" class="mx-auto space-y-4"> <input type="hidden" name="<?= $this->security->get_csrf_token_name() ?>" value="<?= $this->security->get_csrf_hash() ?>" class="csrf_token"> <!-- Header --> <?php $this->load->view('admin_new/components/header_box', [ 'title' => 'Ubah Badge', 'subtitle' => 'Ubah data badge yang sudah ada', 'status_label' => 'Badge Aktif', 'status_color' => 'green-400', ]); ?> <!-- Badge Information Section --> <div class="bg-white rounded-xl shadow-sm border border-gray-200 overflow-hidden"> <div class="bg-gradient-to-r from-blue-50 to-indigo-50 px-6 py-4 border-b border-gray-200"> <div class="flex items-center space-x-3"> <div class="w-8 h-8 bg-blue-500 rounded-lg flex items-center justify-center"> <i data-feather="tag" class="w-4 h-4 text-white"></i> </div> <h2 class="text-lg font-semibold text-gray-900">Informasi Badge</h2> </div> </div> <div class="p-6 space-y-6"> <!-- Name and Description Row --> <div class="grid md:grid-cols-2 gap-6"> <!-- Badge Name --> <div class="space-y-2"> <label for="badge_name" class="flex items-center text-sm font-medium text-gray-700"> Nama Badge <span class="ml-2 inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium bg-red-100 text-red-800"> Wajib </span> </label> <input id="badge_name" type="text" name="badge_name" placeholder="Masukkan nama badge" autocomplete="off" value="<?= isset($badge) ? $badge->name : '' ?>" required class="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-1 focus:ring-purple-500 focus:border-purple-500 transition-colors text-gray-900 placeholder-gray-500" /> <p class="text-xs text-gray-500">Pastikan nama diisi dengan benar dan lengkap</p> </div> <!-- Badge Description --> <div class="space-y-2"> <label for="badge_description" class="flex items-center text-sm font-medium text-gray-700"> Deskripsi <span class="ml-2 inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium bg-gray-100 text-gray-600"> Opsional </span> </label> <input id="badge_description" type="text" name="badge_description" placeholder="Masukkan deskripsi badge" autocomplete="off" value="<?= isset($badge) ? $badge->description : '' ?>" class="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-1 focus:ring-purple-500 focus:border-purple-500 transition-colors text-gray-900 placeholder-gray-500" /> <p class="text-xs text-gray-500">Deskripsi singkat tentang badge ini</p> </div> </div> <!-- Color Row dengan Color Picker --> <div class="grid md:grid-cols-2 gap-6"> <!-- Badge Background Color --> <div class="space-y-2"> <label for="badge_background" class="flex items-center text-sm font-medium text-gray-700"> Warna Background <span class="ml-2 inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium bg-red-100 text-red-800"> Wajib </span> </label> <div class="relative"> <input id="badge_background" type="text" name="badge_background" placeholder="#3B82F6" autocomplete="off" value="<?= isset($badge) ? $badge->background_color : '#3B82F6' ?>" required class="w-full px-4 py-3 pr-16 border border-gray-300 rounded-lg focus:ring-1 focus:ring-purple-500 focus:border-purple-500 transition-colors text-gray-900 placeholder-gray-500 font-mono" /> <div class="absolute right-2 top-2 flex items-center space-x-2"> <div id="backgroundColorPreview" class="w-8 h-8 rounded-md border-2 border-gray-300 cursor-pointer shadow-sm" style="background-color: <?= isset($badge) ? $badge->background_color : '#3B82F6' ?>;"></div> <button type="button" id="openBackgroundColorPicker" class="p-2 text-gray-500 hover:text-gray-700 hover:bg-gray-100 rounded-md transition-colors"> <i data-feather="edit-3" class="h-4 w-4"></i> </button> </div> </div> <p class="text-xs text-gray-500">Klik pada kotak warna atau tombol untuk membuka color picker</p> </div> <!-- Badge Text Color --> <div class="space-y-2"> <label for="badge_color" class="flex items-center text-sm font-medium text-gray-700"> Warna Teks <span class="ml-2 inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium bg-red-100 text-red-800"> Wajib </span> </label> <div class="relative"> <input id="badge_color" type="text" name="badge_color" placeholder="#FFFFFF" autocomplete="off" value="<?= isset($badge) ? $badge->text_color : '#FFFFFF' ?>" required class="w-full px-4 py-3 pr-16 border border-gray-300 rounded-lg focus:ring-1 focus:ring-purple-500 focus:border-purple-500 transition-colors text-gray-900 placeholder-gray-500 font-mono" /> <div class="absolute right-2 top-2 flex items-center space-x-2"> <div id="textColorPreview" class="w-8 h-8 rounded-md border-2 border-gray-300 cursor-pointer shadow-sm" style="background-color: <?= isset($badge) ? $badge->text_color : '#FFFFFF' ?>;"></div> <button type="button" id="openTextColorPicker" class="p-2 text-gray-500 hover:text-gray-700 hover:bg-gray-100 rounded-md transition-colors"> <i data-feather="edit-3" class="h-4 w-4"></i> </button> </div> </div> <p class="text-xs text-gray-500">Klik pada kotak warna atau tombol untuk membuka color picker</p> </div> </div> <!-- Icon row --> <div class="grid md:grid-cols-2 gap-6"> <!-- Badge Icon --> <div class="space-y-2"> <label for="badge_icon" class="flex items-center text-sm font-medium text-gray-700"> Ikon Badge <span class="ml-2 inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium bg-gray-100 text-gray-600"> Opsional </span> </label> <input id="badge_icon" type="text" name="badge_icon" placeholder="🔥" autocomplete="off" value="<?= isset($badge) ? $badge->icon : '#🔥' ?>" class="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-1 focus:ring-purple-500 focus:border-purple-500 transition-colors text-gray-900 placeholder-gray-500" /> <p class="text-xs text-gray-500">Kamu bisa menggunakan emoji seperti '🔥', '⭐', '🎖️'</p> </div> <!-- Badge Preview --> <div class="space-y-2"> <label class="flex items-center text-sm font-medium text-gray-700"> Preview Badge </label> <div class="flex items-center justify-center h-16 bg-gray-50 rounded-lg border-2 border-dashed border-gray-300"> <div id="badgePreview" class="inline-flex items-center px-3 py-1 rounded-full text-sm font-medium shadow-sm" style="background-color: #3B82F6; color: #FFFFFF;"> <span id="previewIcon">🔥</span> <span id="previewText" class="ml-1">Badge Name</span> </div> </div> <p class="text-xs text-gray-500">Preview badge akan muncul di sini secara real-time</p> </div> </div> </div> </div> <!-- Action Buttons --> <div class="bg-white rounded-xl shadow-sm border border-gray-200 p-6"> <div class="flex flex-col sm:flex-row justify-end gap-4"> <button type="button" class="w-full sm:w-auto px-6 py-3 border border-gray-300 rounded-lg text-gray-700 hover:bg-gray-50 hover:border-gray-400 transition-all duration-200 font-medium" onclick="window.location.href='<?= base_url('admin/badge/manage') ?>'"> <svg class="w-4 h-4 inline mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path> </svg> Batal </button> <button type="submit" class="w-full sm:w-auto px-6 py-3 bg-blue-600 hover:bg-blue-700 text-white rounded-lg transition-all duration-200 font-medium shadow-sm hover:shadow-md"> <svg class="w-4 h-4 inline mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"></path> </svg> Simpan </button> </div> </div> </form> </main> <div id="colorPickerModal" class="fixed inset-0 z-[99] hidden color-picker-overlay bg-black bg-opacity-50 flex items-center justify-center p-4"> <div class="bg-white rounded-2xl shadow-2xl w-full max-w-4xl mx-4 transform transition-all max-h-[90vh] overflow-hidden"> <!-- Modal Header --> <div class="flex items-center justify-between p-4 border-b border-gray-200"> <h3 id="colorPickerTitle" class="text-lg font-semibold text-gray-900">Pilih Warna</h3> <button id="closeColorPicker" class="p-2 hover:bg-gray-100 rounded-lg transition-colors"> <svg class="w-5 h-5 text-gray-500" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path> </svg> </button> </div> <!-- Color Picker Content - Horizontal Layout --> <div class="p-4 flex flex-col lg:flex-row gap-6 overflow-y-auto max-h-[calc(90vh-140px)]"> <!-- Left Section: Color Wheel & Hue --> <div class="flex-1 space-y-4"> <!-- Color Wheel --> <div class="relative"> <div class="w-full h-48 lg:h-56 rounded-lg overflow-hidden relative cursor-crosshair color-wheel" id="colorWheel"> <div class="absolute inset-0 saturation-lightness" id="saturationLightness"></div> <div class="absolute w-4 h-4 border-2 border-white rounded-full shadow-lg transform -translate-x-2 -translate-y-2 pointer-events-none" id="colorCursor"></div> </div> </div> <!-- Hue Slider --> <div class="space-y-2"> <label class="text-sm font-medium text-gray-700">Hue</label> <input type="range" id="hueSlider" min="0" max="360" value="0" class="w-full h-6 rounded-lg appearance-none cursor-pointer" style="background: linear-gradient(to right, hsl(0, 100%, 50%), hsl(60, 100%, 50%), hsl(120, 100%, 50%), hsl(180, 100%, 50%), hsl(240, 100%, 50%), hsl(300, 100%, 50%), hsl(360, 100%, 50%));"> </div> </div> <!-- Right Section: Controls & Preview --> <div class="flex-1 space-y-4"> <!-- Color Preview --> <div class="space-y-2"> <label class="text-sm font-medium text-gray-700">Preview</label> <div class="w-full h-20 rounded-lg border-2 border-gray-200 shadow-inner" id="colorPreview"></div> </div> <!-- Color Input Fields --> <div class="space-y-4"> <div class="grid grid-cols-2 gap-3"> <div> <label class="block text-sm font-medium text-gray-700 mb-1">Hex</label> <input type="text" id="hexInput" placeholder="#000000" class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-1 focus:ring-purple-500 focus:border-purple-500 text-sm font-mono"> </div> <div> <label class="block text-sm font-medium text-gray-700 mb-1">Opacity</label> <input type="range" id="opacitySlider" min="0" max="100" value="100" class="w-full h-2 bg-gray-200 rounded-lg appearance-none cursor-pointer mt-3"> </div> </div> <div class="grid grid-cols-3 gap-3"> <div> <label class="block text-sm font-medium text-gray-700 mb-1">R</label> <input type="number" id="rgbR" min="0" max="255" value="0" class="w-full px-2 py-2 border border-gray-300 rounded-lg focus:ring-1 focus:ring-purple-500 focus:border-purple-500 text-sm"> </div> <div> <label class="block text-sm font-medium text-gray-700 mb-1">G</label> <input type="number" id="rgbG" min="0" max="255" value="0" class="w-full px-2 py-2 border border-gray-300 rounded-lg focus:ring-1 focus:ring-purple-500 focus:border-purple-500 text-sm"> </div> <div> <label class="block text-sm font-medium text-gray-700 mb-1">B</label> <input type="number" id="rgbB" min="0" max="255" value="0" class="w-full px-2 py-2 border border-gray-300 rounded-lg focus:ring-1 focus:ring-purple-500 focus:border-purple-500 text-sm"> </div> </div> </div> <!-- Preset Colors --> <div class="space-y-2"> <label class="text-sm font-medium text-gray-700">Warna Preset</label> <div class="grid grid-cols-8 gap-2"> <div class="w-7 h-7 rounded-lg bg-red-500 cursor-pointer hover:scale-110 transition-transform" data-color="#ef4444"></div> <div class="w-7 h-7 rounded-lg bg-orange-500 cursor-pointer hover:scale-110 transition-transform" data-color="#f97316"></div> <div class="w-7 h-7 rounded-lg bg-yellow-500 cursor-pointer hover:scale-110 transition-transform" data-color="#eab308"></div> <div class="w-7 h-7 rounded-lg bg-green-500 cursor-pointer hover:scale-110 transition-transform" data-color="#22c55e"></div> <div class="w-7 h-7 rounded-lg bg-blue-500 cursor-pointer hover:scale-110 transition-transform" data-color="#3b82f6"></div> <div class="w-7 h-7 rounded-lg bg-indigo-500 cursor-pointer hover:scale-110 transition-transform" data-color="#6366f1"></div> <div class="w-7 h-7 rounded-lg bg-purple-500 cursor-pointer hover:scale-110 transition-transform" data-color="#a855f7"></div> <div class="w-7 h-7 rounded-lg bg-pink-500 cursor-pointer hover:scale-110 transition-transform" data-color="#ec4899"></div> <div class="w-7 h-7 rounded-lg bg-gray-800 cursor-pointer hover:scale-110 transition-transform" data-color="#1f2937"></div> <div class="w-7 h-7 rounded-lg bg-gray-600 cursor-pointer hover:scale-110 transition-transform" data-color="#4b5563"></div> <div class="w-7 h-7 rounded-lg bg-gray-400 cursor-pointer hover:scale-110 transition-transform" data-color="#9ca3af"></div> <div class="w-7 h-7 rounded-lg bg-gray-200 cursor-pointer hover:scale-110 transition-transform" data-color="#e5e7eb"></div> <div class="w-7 h-7 rounded-lg bg-white border-2 border-gray-300 cursor-pointer hover:scale-110 transition-transform" data-color="#ffffff"></div> <div class="w-7 h-7 rounded-lg bg-black cursor-pointer hover:scale-110 transition-transform" data-color="#000000"></div> <div class="w-7 h-7 rounded-lg bg-cyan-500 cursor-pointer hover:scale-110 transition-transform" data-color="#06b6d4"></div> <div class="w-7 h-7 rounded-lg bg-teal-500 cursor-pointer hover:scale-110 transition-transform" data-color="#14b8a6"></div> </div> </div> </div> </div> <!-- Modal Footer --> <div class="flex justify-end space-x-3 p-4 border-t border-gray-200"> <button id="cancelColorPicker" class="px-4 py-2 text-gray-700 hover:bg-gray-100 rounded-lg transition-colors"> Batal </button> <button id="applyColor" class="px-6 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors"> Terapkan </button> </div> </div> </div> <script> // Color Picker State let currentColorInput = null; let currentColorPreview = null; let currentHue = 0; let currentSaturation = 100; let currentLightness = 50; // Elements const colorPickerModal = document.getElementById('colorPickerModal'); const colorPickerTitle = document.getElementById('colorPickerTitle'); const colorWheel = document.getElementById('colorWheel'); const colorCursor = document.getElementById('colorCursor'); const hueSlider = document.getElementById('hueSlider'); const colorPreview = document.getElementById('colorPreview'); const hexInput = document.getElementById('hexInput'); const rgbR = document.getElementById('rgbR'); const rgbG = document.getElementById('rgbG'); const rgbB = document.getElementById('rgbB'); const opacitySlider = document.getElementById('opacitySlider'); // Form elements const badgeNameInput = document.getElementById('badge_name'); const badgeIconInput = document.getElementById('badge_icon'); const badgeBackgroundInput = document.getElementById('badge_background'); const badgeColorInput = document.getElementById('badge_color'); const backgroundColorPreview = document.getElementById('backgroundColorPreview'); const textColorPreview = document.getElementById('textColorPreview'); const badgePreview = document.getElementById('badgePreview'); const previewIcon = document.getElementById('previewIcon'); const previewText = document.getElementById('previewText'); // Open color picker functions document.getElementById('openBackgroundColorPicker').addEventListener('click', () => { openColorPicker('background', 'Pilih Warna Background'); }); document.getElementById('openTextColorPicker').addEventListener('click', () => { openColorPicker('text', 'Pilih Warna Teks'); }); // Click on color preview boxes backgroundColorPreview.addEventListener('click', () => { openColorPicker('background', 'Pilih Warna Background'); }); textColorPreview.addEventListener('click', () => { openColorPicker('text', 'Pilih Warna Teks'); }); function openColorPicker(type, title) { currentColorInput = type === 'background' ? badgeBackgroundInput : badgeColorInput; currentColorPreview = type === 'background' ? backgroundColorPreview : textColorPreview; colorPickerTitle.textContent = title; colorPickerModal.classList.remove('hidden'); // Set initial color from input const currentColor = currentColorInput.value || (type === 'background' ? '#3B82F6' : '#FFFFFF'); setColorFromHex(currentColor); } // Close color picker document.getElementById('closeColorPicker').addEventListener('click', closeColorPicker); document.getElementById('cancelColorPicker').addEventListener('click', closeColorPicker); function closeColorPicker() { colorPickerModal.classList.add('hidden'); currentColorInput = null; currentColorPreview = null; } // Apply color document.getElementById('applyColor').addEventListener('click', () => { if (currentColorInput && currentColorPreview) { const hexColor = hexInput.value; currentColorInput.value = hexColor; currentColorPreview.style.backgroundColor = hexColor; updateBadgePreview(); closeColorPicker(); } }); // Color wheel interaction colorWheel.addEventListener('click', (e) => { const rect = colorWheel.getBoundingClientRect(); const x = e.clientX - rect.left; const y = e.clientY - rect.top; currentSaturation = (x / rect.width) * 100; currentLightness = 100 - ((y / rect.height) * 100); updateColorFromHSL(); updateColorCursor(x, y); }); // Hue slider hueSlider.addEventListener('input', (e) => { currentHue = parseInt(e.target.value); updateColorFromHSL(); }); // Hex input hexInput.addEventListener('input', (e) => { const hex = e.target.value; if (isValidHex(hex)) { setColorFromHex(hex); } }); // RGB inputs rgbR.addEventListener('input', updateColorFromRGB); rgbG.addEventListener('input', updateColorFromRGB); rgbB.addEventListener('input', updateColorFromRGB); // Preset colors document.querySelectorAll('[data-color]').forEach(preset => { preset.addEventListener('click', () => { const color = preset.getAttribute('data-color'); setColorFromHex(color); }); }); // Utility functions function isValidHex(hex) { return /^#[0-9A-F]{6}$/i.test(hex); } function setColorFromHex(hex) { if (!isValidHex(hex)) return; const hsl = hexToHsl(hex); currentHue = hsl.h; currentSaturation = hsl.s; currentLightness = hsl.l; updateColorDisplay(); updateRGBInputs(); updateColorCursorFromHSL(); } function updateColorFromHSL() { updateColorDisplay(); updateRGBInputs(); } function updateColorFromRGB() { const r = parseInt(rgbR.value) || 0; const g = parseInt(rgbG.value) || 0; const b = parseInt(rgbB.value) || 0; const hex = rgbToHex(r, g, b); const hsl = hexToHsl(hex); currentHue = hsl.h; currentSaturation = hsl.s; currentLightness = hsl.l; updateColorDisplay(); updateColorCursorFromHSL(); } function updateColorDisplay() { const hslColor = `hsl(${currentHue}, ${currentSaturation}%, ${currentLightness}%)`; const hexColor = hslToHex(currentHue, currentSaturation, currentLightness); colorPreview.style.backgroundColor = hslColor; hexInput.value = hexColor; hueSlider.value = currentHue; } function updateRGBInputs() { const hex = hslToHex(currentHue, currentSaturation, currentLightness); const rgb = hexToRgb(hex); rgbR.value = rgb.r; rgbG.value = rgb.g; rgbB.value = rgb.b; } function updateColorCursor(x, y) { colorCursor.style.left = x + 'px'; colorCursor.style.top = y + 'px'; } function updateColorCursorFromHSL() { const rect = colorWheel.getBoundingClientRect(); const x = (currentSaturation / 100) * rect.width; const y = ((100 - currentLightness) / 100) * rect.height; updateColorCursor(x, y); } // Color conversion functions function hexToRgb(hex) { const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex); return result ? { r: parseInt(result[1], 16), g: parseInt(result[2], 16), b: parseInt(result[3], 16) } : null; } function rgbToHex(r, g, b) { return "#" + ((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1); } function hexToHsl(hex) { const rgb = hexToRgb(hex); if (!rgb) return { h: 0, s: 0, l: 0 }; const r = rgb.r / 255; const g = rgb.g / 255; const b = rgb.b / 255; const max = Math.max(r, g, b); const min = Math.min(r, g, b); let h, s, l = (max + min) / 2; if (max === min) { h = s = 0; } else { const d = max - min; s = l > 0.5 ? d / (2 - max - min) : d / (max + min); switch (max) { case r: h = (g - b) / d + (g < b ? 6 : 0); break; case g: h = (b - r) / d + 2; break; case b: h = (r - g) / d + 4; break; } h /= 6; } return { h: Math.round(h * 360), s: Math.round(s * 100), l: Math.round(l * 100) }; } function hslToHex(h, s, l) { l /= 100; const a = s * Math.min(l, 1 - l) / 100; const f = n => { const k = (n + h / 30) % 12; const color = l - a * Math.max(Math.min(k - 3, 9 - k, 1), -1); return Math.round(255 * color).toString(16).padStart(2, '0'); }; return `#${f(0)}${f(8)}${f(4)}`; } // Badge preview update functions function updateBadgePreview() { const name = badgeNameInput.value || 'Badge Name'; const icon = badgeIconInput.value || '🔥'; const backgroundColor = badgeBackgroundInput.value || '#3B82F6'; const textColor = badgeColorInput.value || '#FFFFFF'; previewText.textContent = name; previewIcon.textContent = icon; badgePreview.style.backgroundColor = backgroundColor; badgePreview.style.color = textColor; } // Input event listeners for real-time preview badgeNameInput.addEventListener('input', updateBadgePreview); badgeIconInput.addEventListener('input', updateBadgePreview); badgeBackgroundInput.addEventListener('input', (e) => { const color = e.target.value; if (isValidHex(color)) { backgroundColorPreview.style.backgroundColor = color; updateBadgePreview(); } }); badgeColorInput.addEventListener('input', (e) => { const color = e.target.value; if (isValidHex(color)) { textColorPreview.style.backgroundColor = color; updateBadgePreview(); } }); // Close modal when clicking outside colorPickerModal.addEventListener('click', (e) => { if (e.target === colorPickerModal) { closeColorPicker(); } }); // Keyboard shortcuts document.addEventListener('keydown', (e) => { if (e.key === 'Escape' && !colorPickerModal.classList.contains('hidden')) { closeColorPicker(); } }); // Initialize preview updateBadgePreview(); </script>