From da028e4b0b06edc7537fb46fd2d236e85a4f211a Mon Sep 17 00:00:00 2001 From: Pramod-Munoli Date: Sun, 26 Oct 2025 12:27:56 +0530 Subject: [PATCH 1/3] feat: add real-time theme updates, local storage, and palette sharing --- projects/theme-switcher/index.html | 1 - 1 file changed, 1 deletion(-) diff --git a/projects/theme-switcher/index.html b/projects/theme-switcher/index.html index 7ffe68f..39e80be 100644 --- a/projects/theme-switcher/index.html +++ b/projects/theme-switcher/index.html @@ -16,5 +16,4 @@

Theme Switcher

- \ No newline at end of file From 2a59c2798cf5091591126c35699b964a3beea597 Mon Sep 17 00:00:00 2001 From: Pramod-Munoli Date: Sun, 26 Oct 2025 12:32:59 +0530 Subject: [PATCH 2/3] feat: enhance theme switcher with real-time updates and palette sharing --- projects/theme-switcher/index.html | 70 +++- projects/theme-switcher/main.js | 345 ++++++++++++++++- projects/theme-switcher/styles.css | 580 +++++++++++++++++++++++++++-- 3 files changed, 949 insertions(+), 46 deletions(-) diff --git a/projects/theme-switcher/index.html b/projects/theme-switcher/index.html index 39e80be..ff27da7 100644 --- a/projects/theme-switcher/index.html +++ b/projects/theme-switcher/index.html @@ -8,12 +8,70 @@ - -
+ +

Theme Switcher

-
-

Add generated palettes, save themes, and share links.

-
- +

Generate beautiful color palettes with random colors

+ +
+
+
+
#6ee7b7
+
Accent Color
+
+ +
+ +
+
Current Palette
+
+
+ +
+

Color Selection Panel

+
+
+ + +
+ + + +
+ + +
+ +
+ + +
+ + +
+
+ +
+ + + + +
+ +
+
Saved Palettes
+
+
+ + + \ No newline at end of file diff --git a/projects/theme-switcher/main.js b/projects/theme-switcher/main.js index aa71c55..4eabd40 100644 --- a/projects/theme-switcher/main.js +++ b/projects/theme-switcher/main.js @@ -1,2 +1,343 @@ -const input = document.getElementById('color'); document.getElementById('apply').addEventListener('click', () => { document.documentElement.style.setProperty('--accent', input.value); }); -// TODOs: generate palettes (HSL); save themes (localStorage); share URL hash + + let accentColor = '#6ee7b7'; + let currentPalette = ['#6ee7b7']; + let savedPalettes = []; + + function init() { + loadFromStorage(); + loadFromHash(); + updateUI(); + renderSavedPalettes(); + document.getElementById('colorPicker').addEventListener('change', (e) => { + accentColor = e.target.value; + updateUI(); + }); + } + + function updateUI() { + document.documentElement.style.setProperty('--accent', accentColor); + document.getElementById('colorPreview').style.backgroundColor = accentColor; + document.getElementById('colorValue').textContent = accentColor; + document.getElementById('colorPicker').value = accentColor; + renderPaletteColors(); + updateHash(); + } + + function renderPaletteColors() { + const container = document.getElementById('paletteColors'); + container.innerHTML = ''; + currentPalette.forEach(color => { + const div = document.createElement('div'); + div.className = 'palette-color'; + div.style.backgroundColor = color; + div.title = color; + div.onclick = () => applySpecificColor(color); + container.appendChild(div); + }); + } + + function applyColor() { + updateUI(); + showToast('Color Applied', `${accentColor} is now your accent color`); + } + + function applySpecificColor(color) { + accentColor = color; + updateUI(); + showToast('Color Applied', `${color} is now your accent color`); + } + + function generateRandomPalette() { + const colors = []; + for (let i = 0; i < 5; i++) { + const hue = Math.floor(Math.random() * 360); + const saturation = 60 + Math.floor(Math.random() * 30); + const lightness = 45 + Math.floor(Math.random() * 30); + colors.push(hslToHex(hue, saturation, lightness)); + } + currentPalette = colors; + accentColor = colors[0]; + updateUI(); + showToast('Random Palette', 'New random palette generated!'); + } + + function generateVibrantPalette() { + const vibrantColors = [ + '#FF006E', '#FB5607', '#FFBE0B', '#8338EC', '#3A86FF', + '#FF4081', '#E91E63', '#9C27B0', '#673AB7', '#3F51B5', + '#2196F3', '#03A9F4', '#00BCD4', '#009688', '#4CAF50' + ]; + const shuffled = [...vibrantColors].sort(() => Math.random() - 0.5); + currentPalette = shuffled.slice(0, 5); + accentColor = currentPalette[0]; + updateUI(); + showToast('Vibrant Colors', 'High-energy palette created!'); + } + + function generatePastelPalette() { + const colors = []; + for (let i = 0; i < 5; i++) { + const hue = Math.floor(Math.random() * 360); + const saturation = 25 + Math.floor(Math.random() * 35); + const lightness = 75 + Math.floor(Math.random() * 15); + colors.push(hslToHex(hue, saturation, lightness)); + } + currentPalette = colors; + accentColor = colors[0]; + updateUI(); + showToast('Pastel Colors', 'Soft soothing palette created!'); + } + + function generateScheme(type) { + const baseHue = Math.floor(Math.random() * 360); + let colors = []; + switch (type) { + case 'monochromatic': + colors = Array.from({ length: 5 }, (_, i) => { + const lightness = 20 + (i * 15); + return hslToHex(baseHue, 70, lightness); + }); + break; + case 'analogous': + colors = Array.from({ length: 5 }, (_, i) => { + const hue = (baseHue + (i * 30) - 60) % 360; + return hslToHex(hue, 70, 60); + }); + break; + case 'complementary': + colors = [ + hslToHex(baseHue, 70, 60), + hslToHex(baseHue, 70, 40), + hslToHex((baseHue + 180) % 360, 70, 60), + hslToHex((baseHue + 180) % 360, 70, 40), + hslToHex(baseHue, 50, 80) + ]; + break; + case 'triadic': + colors = [ + hslToHex(baseHue, 70, 60), + hslToHex((baseHue + 120) % 360, 70, 60), + hslToHex((baseHue + 240) % 360, 70, 60), + hslToHex(baseHue, 70, 40), + hslToHex((baseHue + 120) % 360, 70, 40) + ]; + break; + default: + showToast('Error', 'Unknown scheme type', 'error'); + return; + } + currentPalette = colors; + accentColor = colors[0]; + updateUI(); + showToast(`${type.charAt(0).toUpperCase() + type.slice(1)} Scheme`, `${type} palette generated!`); + } + + function savePalette() { + const name = document.getElementById('paletteName').value.trim(); + if (!name) { + showToast('Name Required', 'Please enter a palette name', 'error'); + return; + } + if (savedPalettes.some(p => p.name === name)) { + showToast('Name Exists', 'A palette with this name already exists', 'error'); + return; + } + const palette = { + id: Date.now().toString(), + name: name, + colors: [...currentPalette], + timestamp: Date.now() + }; + savedPalettes.push(palette); + saveToStorage(); + renderSavedPalettes(); + document.getElementById('paletteName').value = ''; + showToast('Palette Saved', `"${name}" saved successfully!`); + } + + function deletePalette(id) { + const palette = savedPalettes.find(p => p.id === id); + savedPalettes = savedPalettes.filter(p => p.id !== id); + saveToStorage(); + renderSavedPalettes(); + showToast('Palette Deleted', `"${palette.name}" has been deleted`); + } + + function loadPalette(id) { + const palette = savedPalettes.find(p => p.id === id); + if (palette) { + currentPalette = [...palette.colors]; + accentColor = palette.colors[0]; + updateUI(); + showToast('Palette Loaded', `"${palette.name}" applied successfully!`); + } + } + + function sharePalette() { + const data = { colors: currentPalette }; + const hash = encodeURIComponent(JSON.stringify(data)); + const url = `${window.location.origin}${window.location.pathname}#${hash}`; + copyToClipboard(url).then(() => { + showToast('Link Copied', 'Share link copied to clipboard!'); + }).catch(() => { + showToast('Error', 'Failed to copy link', 'error'); + }); + } + + function copyColor() { + copyToClipboard(accentColor).then(() => { + showToast('Color Copied', `${accentColor} copied to clipboard!`); + }).catch(() => { + showToast('Error', 'Failed to copy color', 'error'); + }); + } + + function copyToClipboard(text) { + if (navigator.clipboard) { + return navigator.clipboard.writeText(text); + } else { + return new Promise((resolve, reject) => { + const textArea = document.createElement('textarea'); + textArea.value = text; + document.body.appendChild(textArea); + textArea.select(); + try { + const successful = document.execCommand('copy'); + document.body.removeChild(textArea); + if (successful) { + resolve(); + } else { + reject(new Error('Copy command failed')); + } + } catch (err) { + document.body.removeChild(textArea); + reject(err); + } + }); + } + } + + function renderSavedPalettes() { + const container = document.getElementById('savedPalettes'); + container.innerHTML = ''; + if (savedPalettes.length === 0) { + container.innerHTML = '
No saved palettes yet. Create and save your first palette!
'; + return; + } + savedPalettes.forEach(palette => { + const card = createPaletteCard(palette); + container.appendChild(card); + }); + } + + function createPaletteCard(palette) { + const card = document.createElement('div'); + card.className = 'palette-card'; + const header = document.createElement('div'); + header.className = 'palette-header'; + const nameDiv = document.createElement('div'); + nameDiv.innerHTML = ` +
${palette.name}
+
${new Date(palette.timestamp).toLocaleDateString()}
+ `; + const actions = document.createElement('div'); + actions.className = 'palette-actions'; + const applyBtn = document.createElement('button'); + applyBtn.className = 'btn-small'; + applyBtn.textContent = 'Apply'; + applyBtn.onclick = (e) => { + e.stopPropagation(); + loadPalette(palette.id); + }; + const deleteBtn = document.createElement('button'); + deleteBtn.className = 'btn-small btn-delete'; + deleteBtn.textContent = 'Delete'; + deleteBtn.onclick = (e) => { + e.stopPropagation(); + deletePalette(palette.id); + }; + actions.appendChild(applyBtn); + actions.appendChild(deleteBtn); + header.appendChild(nameDiv); + header.appendChild(actions); + const colorsRow = document.createElement('div'); + colorsRow.className = 'palette-colors-row'; + palette.colors.forEach(color => { + const colorDiv = document.createElement('div'); + colorDiv.style.backgroundColor = color; + colorDiv.title = color; + colorsRow.appendChild(colorDiv); + }); + card.appendChild(header); + card.appendChild(colorsRow); + card.onclick = () => loadPalette(palette.id); + return card; + } + + function showToast(title, message, type = 'success') { + const existing = document.querySelector('.toast'); + if (existing) existing.remove(); + + const toast = document.createElement('div'); + toast.className = `toast ${type}`; + toast.innerHTML = ` +
${title}
+
${message}
+ `; + document.body.appendChild(toast); + setTimeout(() => { + toast.style.animation = 'slideOut 0.3s ease-out forwards'; + setTimeout(() => toast.remove(), 300); + }, 3000); + } + + function saveToStorage() { + localStorage.setItem('themeSwitcherPalettes', JSON.stringify(savedPalettes)); + } + + function loadFromStorage() { + const saved = localStorage.getItem('themeSwitcherPalettes'); + if (saved) { + try { + savedPalettes = JSON.parse(saved); + } catch (e) { + console.error('Error loading saved palettes:', e); + savedPalettes = []; + } + } + } + + function loadFromHash() { + if (window.location.hash) { + try { + const data = JSON.parse(decodeURIComponent(window.location.hash.substring(1))); + if (data.colors) { + currentPalette = data.colors; + accentColor = data.colors[0]; + showToast('Palette Loaded', 'Palette loaded from shared link!'); + } + } catch (e) { + console.error('Invalid hash data'); + } + } + } + + function updateHash() { + const data = { colors: currentPalette }; + const hash = encodeURIComponent(JSON.stringify(data)); + window.history.replaceState(null, null, `#${hash}`); + } + + 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)}`; + } + + window.addEventListener('DOMContentLoaded', init); + \ No newline at end of file diff --git a/projects/theme-switcher/styles.css b/projects/theme-switcher/styles.css index d9059cd..01542c5 100644 --- a/projects/theme-switcher/styles.css +++ b/projects/theme-switcher/styles.css @@ -1,38 +1,542 @@ -:root { - --accent: #6ee7b7 -} - -body { - font-family: system-ui; - background: #0f0f12; - color: #eef1f8; - margin: 0; - padding: 2rem; - display: grid; - place-items: center -} - -main { - max-width: 560px; - width: 100% -} - -.controls { - display: flex; - gap: .5rem -} - -button { - background: var(--accent); - color: #0b1020; - border: none; - padding: .5rem .75rem; - border-radius: .5rem; - font-weight: 600; - cursor: pointer -} - -.notes { - color: #a6adbb; - font-size: .9rem -} \ No newline at end of file + * { + margin: 0; + padding: 0; + box-sizing: border-box; + } + + :root { + --bg-primary: #1a1a1a; + --bg-secondary: #2d2d2d; + --bg-card: #242424; + --text-primary: #ffffff; + --text-secondary: #b0b0b0; + --border: #404040; + --shadow: rgba(0, 0, 0, 0.3); + --accent: #6ee7b7; + } + + body { + font-family: system-ui, -apple-system, sans-serif; + background: var(--bg-primary); + color: var(--text-primary); + min-height: 100vh; + display: flex; + align-items: center; + justify-content: center; + padding: 20px; + line-height: 1.6; + transition: all 0.3s ease; + } + + .container { + max-width: 600px; + width: 100%; + background: var(--bg-card); + border-radius: 16px; + padding: 32px; + box-shadow: 0 8px 32px var(--shadow); + border: 1px solid var(--border); + transition: all 0.3s ease; + } + + h1 { + font-size: 2.2rem; + font-weight: 700; + text-align: center; + margin-bottom: 8px; + background: linear-gradient(135deg, var(--accent), var(--accent)); + background-clip: text; + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + color: transparent; + } + + .subtitle { + text-align: center; + color: var(--text-secondary); + margin-bottom: 25px; + font-size: 1rem; + } + + .current-color { + display: flex; + align-items: center; + gap: 16px; + margin-bottom: 24px; + padding: 20px; + background: var(--bg-secondary); + border-radius: 12px; + border: 1px solid var(--border); + cursor: pointer; + transition: all 0.3s ease; + } + + .current-color:hover { + transform: translateY(-2px); + box-shadow: 0 8px 24px var(--shadow); + border-color: var(--accent); + } + + .color-preview { + width: 60px; + height: 60px; + border-radius: 12px; + background: var(--accent); + border: 3px solid var(--text-primary); + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); + } + + .color-info { + flex: 1; + } + + .color-value { + font-family: 'Courier New', monospace; + font-size: 1.1rem; + font-weight: 600; + margin-bottom: 4px; + } + + .color-label { + color: var(--text-secondary); + font-size: 0.875rem; + } + + .copy-btn { + background: var(--accent); + color: var(--bg-primary); + border: none; + padding: 8px 12px; + border-radius: 8px; + cursor: pointer; + font-weight: 600; + transition: transform 0.2s; + } + + .copy-btn:hover { + transform: scale(1.05); + } + + .palette-display { + margin-bottom: 20px; + } + + .palette-label { + font-size: 0.875rem; + font-weight: 600; + margin-bottom: 12px; + text-transform: uppercase; + letter-spacing: 0.05em; + } + + .palette-colors { + display: flex; + gap: 8px; + flex-wrap: wrap; + } + + .palette-color { + width: 48px; + height: 48px; + border-radius: 8px; + border: 2px solid transparent; + cursor: pointer; + transition: all 0.2s; + } + + .palette-color:hover { + transform: scale(1.1); + border-color: var(--text-primary); + } + + .section-subtitle { + font-size: 1rem; + font-weight: 600; + color: white; + margin-bottom: 8px; + } + + .controls { + display: flex; + flex-direction: column; + gap: 16px; + padding-bottom: 10px; + } + + .control-row { + display: flex; + gap: 10px; + align-items: center; + margin-bottom: 0.6rem; + } + + input[type="color"] { + width: 48px; + height: 40px; + border: 1px solid var(--border); + border-radius: 8px; + cursor: pointer; + background: var(--bg-secondary); + } + + input[type="text"] { + flex: 1; + background: var(--bg-secondary); + border: 1px solid var(--border); + color: var(--text-primary); + padding: 10px 16px; + border-radius: 8px; + font-family: inherit; + font-size: 0.875rem; + transition: all 0.2s ease; + } + + input[type="text"]::placeholder { + color: var(--text-secondary); + } + + input[type="text"]:focus { + outline: none; + border-color: var(--accent); + } + + button { + background: var(--accent); + color: var(--bg-primary); + border: none; + padding: 10px 10px; + border-radius: 8px; + font-weight: 600; + cursor: pointer; + transition: all 0.2s; + font-size: 0.875rem; + white-space: nowrap; + text-overflow: ellipsis; + } + + button:hover { + transform: translateY(-2px); + box-shadow: 0 4px 12px rgba(110, 231, 183, 0.3); + } + + button:active { + transform: translateY(0); + } + + .btn-full { + width: 100%; + justify-content: center; + } + + .btn-vibrant { + background: linear-gradient(135deg, #ff006e, #8338ec); + color: white; + } + + .btn-pastel { + background: linear-gradient(135deg, #ffd6ff, #c8b6ff); + color: #333; + } + + .btn-scheme { + background: var(--bg-card); + border: 1px solid var(--accent); + color: var(--accent); + cursor: pointer; + transition: all 0.4s ease; + } + + .btn-scheme:hover { + background: var(--accent); + color: var(--bg-primary); + transform: translateY(-2.5px); + } + + .scheme-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(120px, 1fr)); + gap: 8px; + margin-bottom: 20px; + } + + .saved-section { + margin-top: 32px; + padding-top: 24px; + border-top: 1px solid var(--border); + } + + .section-title { + font-size: 1.125rem; + font-weight: 600; + margin-bottom: 16px; + color: var(--text-primary); + } + + .saved-palettes { + display: grid; + gap: 12px; + } + + .palette-card { + background: var(--bg-secondary); + border-radius: 12px; + padding: 16px; + cursor: pointer; + transition: all 0.3s ease; + border: 1px solid var(--border); + } + + .palette-card:hover { + transform: translateY(-2px); + box-shadow: 0 8px 24px var(--shadow); + border-color: var(--accent); + } + + .palette-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 12px; + } + + .palette-name { + font-weight: 600; + color: var(--text-primary); + } + + .palette-date { + font-size: 0.75rem; + color: var(--text-secondary); + } + + .palette-colors-row { + display: flex; + gap: 8px; + } + + .btn-small { + padding: 4px 8px; + font-size: 0.75rem; + background: var(--bg-card); + color: var(--text-primary); + border: 1px solid var(--border); + } + + .palette-colors-row>div { + flex: 1; + height: 32px; + border-radius: 4px; + border: 1px solid var(--border); + transition: transform 0.2s ease; + } + + .palette-colors-row>div:hover { + transform: scale(1.04); + } + + .empty-state { + text-align: center; + padding: 32px; + color: var(--text-secondary); + font-style: italic; + } + + .palette-actions { + display: flex; + gap: 8px; + } + + .btn-delete { + background: #dc3545; + color: white; + border-color: #dc3545; + } + + .btn-delete:hover { + background: #c82333; + } + + .toast { + position: fixed; + bottom: 20px; + right: 20px; + background: var(--bg-card); + border: 1px solid var(--border); + border-radius: 12px; + padding: 16px; + box-shadow: 0 8px 32px var(--shadow); + z-index: 1000; + animation: slideIn 0.3s ease-out; + max-width: 300px; + color: var(--text-primary); + } + + .toast-title { + font-weight: 600; + margin-bottom: 4px; + } + + .toast-message { + font-size: 0.875rem; + opacity: 0.8; + } + + .toast.error { + background: #dc3545; + color: white; + border-color: #dc3545; + } + + @keyframes slideIn { + from { + transform: translateX(100%); + opacity: 0; + } + + to { + transform: translateX(0); + opacity: 1; + } + } + + @keyframes slideOut { + from { + transform: translateX(0); + opacity: 1; + } + + to { + transform: translateX(100%); + opacity: 0; + } + } + + @media (max-width: 640px) { + .container { + padding: 20px; + } + + h1 { + font-size: 1.8rem; + } + + .current-color { + flex-direction: row; + text-align: center; + gap: 12px; + } + + .color-info { + text-align: center; + } + + .control-row { + flex-wrap: wrap; + width: 100%; + } + + .control-row input[type="text"] { + flex: 1; + } + + .control-row button { + flex-wrap: wrap; + } + + .scheme-grid { + grid-template-columns: 1fr 1fr; + } + + .palette-colors-row>div { + height: 32px; + } + } + + @media (max-width: 355px) { + body { + padding: 10px; + } + + .container { + padding: 15px; + } + + h1 { + font-size: 1.6rem; + } + + .subtitle { + font-size: 0.9rem; + margin-bottom: 20px; + } + + .current-color { + flex-direction: column; + text-align: center; + gap: 12px; + } + + .color-preview { + width: 50px; + height: 50px; + } + + .color-value { + font-size: 1rem; + } + + .control-row { + flex-direction: column; + gap: 10px; + margin-bottom: 10px; + } + + .control-row input[type="text"] { + width: 100%; + } + + .control-row button { + width: 100%; + padding: 8px 12px; + font-size: 0.8rem; + } + + button { + padding: 8px 12px; + font-size: 0.8rem; + } + + .btn-full { + padding: 10px 14px; + } + + .scheme-grid { + grid-template-columns: 1fr; + gap: 10px; + } + + .palette-color { + width: 40px; + height: 40px; + } + + .palette-colors-row>div { + height: 28px; + } + + .palette-actions { + flex-direction: column; + gap: 5px; + } + + .btn-small { + padding: 6px 10px; + font-size: 0.7rem; + } + + .toast { + max-width: 90%; + right: 5%; + left: 5%; + } + } \ No newline at end of file From 1dc1e6c04b943195d9a0e64159fb19e7e708e372 Mon Sep 17 00:00:00 2001 From: Pramod_Munnoli Date: Sun, 26 Oct 2025 13:41:07 +0530 Subject: [PATCH 3/3] Delete projects/theme-switcher/index.html --- projects/theme-switcher/index.html | 77 ------------------------------ 1 file changed, 77 deletions(-) delete mode 100644 projects/theme-switcher/index.html diff --git a/projects/theme-switcher/index.html b/projects/theme-switcher/index.html deleted file mode 100644 index ff27da7..0000000 --- a/projects/theme-switcher/index.html +++ /dev/null @@ -1,77 +0,0 @@ - - - - - - - Theme Switcher - - - - -
-

Theme Switcher

-

Generate beautiful color palettes with random colors

- -
-
-
-
#6ee7b7
-
Accent Color
-
- -
- -
-
Current Palette
-
-
- -
-

Color Selection Panel

-
-
- - -
- - - -
- - -
- -
- - -
- - -
-
- -
- - - - -
- -
-
Saved Palettes
-
-
-
- - - - \ No newline at end of file