Basado en el documento de requerimientos, he desarrollado la aplicación completa. Aquí está el código estructurado y listo para implementar:
index.html
css/
styles.css
js/
app.js
colorUtils.js
harmonyGenerator.js
storageManager.js
uiComponents.js
assets/
colorNames.json
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Generador de Paletas Armónicas</title>
<link href="https://cdn.jsdelivr.net/npm/@simonwep/pickr/dist/themes/nano.min.css" rel="stylesheet">
<link href="css/styles.css" rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
</head>
<body>
<div class="app-container">
<header class="app-header">
<h1>Generador de Paletas</h1>
<div class="theme-toggle">
<button id="themeBtn"><i class="fas fa-moon"></i></button>
</div>
</header>
<main class="main-content">
<div class="color-input-section">
<div class="color-display" id="currentColor"></div>
<div class="input-group">
<input type="text" id="colorInput" placeholder="#RRGGBB o nombre de color">
<button id="searchBtn"><i class="fas fa-search"></i></button>
</div>
<div class="action-buttons">
<button id="randomBtn" class="btn"><i class="fas fa-random"></i> Aleatorio</button>
<button id="pickerBtn" class="btn"><i class="fas fa-eye-dropper"></i> Selector</button>
<button id="favoritesBtn" class="btn"><i class="fas fa-star"></i> Favoritos</button>
</div>
<div class="color-formats">
<div class="format-box">
<label>HEX</label>
<span id="hexValue">#FFFFFF</span>
<button class="copy-btn" data-target="hexValue"><i class="far fa-copy"></i></button>
</div>
<div class="format-box">
<label>RGB</label>
<span id="rgbValue">rgb(255, 255, 255)</span>
<button class="copy-btn" data-target="rgbValue"><i class="far fa-copy"></i></button>
</div>
<div class="format-box">
<label>HSL</label>
<span id="hslValue">hsl(0, 0%, 100%)</span>
<button class="copy-btn" data-target="hslValue"><i class="far fa-copy"></i></button>
</div>
</div>
</div>
<div class="harmonies-section">
<h2>Armonías de Color</h2>
<div id="harmoniesContainer" class="harmonies-grid">
<!-- Las paletas se generarán aquí dinámicamente -->
</div>
</div>
<div class="favorites-section hidden" id="favoritesSection">
<div class="favorites-header">
<h2>Mis Colores Favoritos</h2>
<div>
<button id="exportFavoritesBtn" class="btn"><i class="fas fa-download"></i> Exportar</button>
<button id="backBtn" class="btn"><i class="fas fa-arrow-left"></i> Volver</button>
</div>
</div>
<div id="favoritesContainer" class="favorites-grid">
<!-- Los favoritos se mostrarán aquí -->
</div>
</div>
</main>
<div class="color-picker-container hidden" id="pickerContainer"></div>
<div class="notification" id="notification"></div>
</div>
<script src="https://cdn.jsdelivr.net/npm/chroma-js/chroma.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/@simonwep/pickr/dist/pickr.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.11.4/gsap.min.js"></script>
<script src="js/colorUtils.js"></script>
<script src="js/harmonyGenerator.js"></script>
<script src="js/storageManager.js"></script>
<script src="js/uiComponents.js"></script>
<script src="js/app.js"></script>
</body>
</html>
:root {
--primary-bg: #ffffff;
--secondary-bg: #f5f5f5;
--text-color: #333333;
--border-color: #e0e0e0;
--card-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
--hover-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
--transition: all 0.3s ease;
}
[data-theme="dark"] {
--primary-bg: #1e1e1e;
--secondary-bg: #2d2d2d;
--text-color: #f0f0f0;
--border-color: #444444;
--card-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
--hover-shadow: 0 4px 12px rgba(0, 0, 0, 0.4);
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
transition: background-color 0.3s, color 0.3s;
}
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background-color: var(--primary-bg);
color: var(--text-color);
line-height: 1.6;
}
.app-container {
max-width: 1200px;
margin: 0 auto;
padding: 20px;
}
.app-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 30px;
padding-bottom: 15px;
border-bottom: 1px solid var(--border-color);
}
.theme-toggle button {
background: none;
border: none;
color: var(--text-color);
font-size: 1.2rem;
cursor: pointer;
padding: 5px;
}
.color-input-section {
background-color: var(--secondary-bg);
padding: 20px;
border-radius: 10px;
margin-bottom: 30px;
box-shadow: var(--card-shadow);
}
.color-display {
width: 100%;
height: 100px;
border-radius: 8px;
margin-bottom: 20px;
box-shadow: var(--card-shadow);
}
.input-group {
display: flex;
margin-bottom: 15px;
}
.input-group input {
flex: 1;
padding: 10px 15px;
border: 1px solid var(--border-color);
border-radius: 5px 0 0 5px;
font-size: 1rem;
background-color: var(--primary-bg);
color: var(--text-color);
}
.input-group button {
padding: 0 15px;
background-color: #4a6fa5;
color: white;
border: none;
border-radius: 0 5px 5px 0;
cursor: pointer;
font-size: 1rem;
}
.action-buttons {
display: flex;
gap: 10px;
margin-bottom: 20px;
}
.btn {
padding: 10px 15px;
background-color: #4a6fa5;
color: white;
border: none;
border-radius: 5px;
cursor: pointer;
font-size: 0.9rem;
display: flex;
align-items: center;
gap: 5px;
}
.btn:hover {
background-color: #3a5a8f;
}
.color-formats {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 15px;
}
.format-box {
background-color: var(--primary-bg);
padding: 10px;
border-radius: 5px;
box-shadow: var(--card-shadow);
}
.format-box label {
display: block;
font-weight: bold;
margin-bottom: 5px;
font-size: 0.9rem;
}
.format-box span {
display: inline-block;
margin-right: 10px;
font-family: monospace;
}
.copy-btn {
background: none;
border: none;
color: var(--text-color);
cursor: pointer;
}
.harmonies-section h2, .favorites-header h2 {
margin-bottom: 20px;
padding-bottom: 10px;
border-bottom: 1px solid var(--border-color);
}
.harmonies-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 20px;
}
.harmony-card {
background-color: var(--secondary-bg);
border-radius: 8px;
overflow: hidden;
box-shadow: var(--card-shadow);
}
.harmony-title {
padding: 10px 15px;
background-color: var(--primary-bg);
font-weight: bold;
}
.harmony-colors {
display: flex;
height: 80px;
}
.color-item {
flex: 1;
position: relative;
display: flex;
flex-direction: column;
justify-content: flex-end;
align-items: center;
padding: 10px;
cursor: pointer;
}
.color-value {
background-color: rgba(0, 0, 0, 0.7);
color: white;
padding: 2px 5px;
border-radius: 3px;
font-size: 0.8rem;
margin-bottom: 5px;
font-family: monospace;
}
.color-actions {
position: absolute;
top: 5px;
right: 5px;
display: flex;
gap: 5px;
}
.color-actions button {
background-color: rgba(255, 255, 255, 0.8);
border: none;
width: 24px;
height: 24px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
opacity: 0;
transition: var(--transition);
}
.color-item:hover .color-actions button {
opacity: 1;
}
.harmony-actions {
padding: 10px;
display: flex;
justify-content: space-between;
background-color: var(--primary-bg);
}
.favorites-section {
margin-top: 30px;
}
.favorites-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
}
.favorites-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
gap: 20px;
}
.favorite-card {
background-color: var(--secondary-bg);
border-radius: 8px;
overflow: hidden;
box-shadow: var(--card-shadow);
transition: var(--transition);
cursor: pointer;
}
.favorite-card:hover {
transform: translateY(-5px);
box-shadow: var(--hover-shadow);
}
.favorite-color {
height: 100px;
position: relative;
}
.favorite-info {
padding: 10px;
text-align: center;
}
.favorite-hex {
font-family: monospace;
font-weight: bold;
}
.favorite-actions {
position: absolute;
bottom: 10px;
right: 10px;
}
.color-picker-container {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.5);
display: flex;
justify-content: center;
align-items: center;
z-index: 1000;
}
.notification {
position: fixed;
top: 20px;
left: 50%;
transform: translateX(-50%);
background-color: #4CAF50;
color: white;
padding: 10px 20px;
border-radius: 5px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.2);
opacity: 0;
transition: opacity 0.3s;
z-index: 1001;
}
.notification.show {
opacity: 1;
}
.hidden {
display: none;
}
@media (max-width: 768px) {
.harmonies-grid {
grid-template-columns: 1fr;
}
.favorites-grid {
grid-template-columns: repeat(2, 1fr);
}
.action-buttons {
flex-direction: column;
}
.btn {
width: 100%;
justify-content: center;
}
}
// Lista de nombres de colores CSS
let colorNames = {};
// Cargar nombres de colores desde el archivo JSON
fetch('assets/colorNames.json')
.then(response => response.json())
.then(data => {
colorNames = data.reduce((acc, color) => {
acc[color.name.toLowerCase()] = color.hex;
return acc;
}, {});
})
.catch(error => console.error('Error loading color names:', error));
/**
* Valida y normaliza un valor de color
* @param {string} color - Valor de color (hex, nombre, etc.)
* @returns {string|null} - Valor hexadecimal o null si no es válido
*/
export function validateColor(color) {
if (!color) return null;
// Si es un código hexadecimal
if (/^#([0-9A-F]{3}){1,2}$/i.test(color)) {
return color.length === 4 ?
`#${color[1]}${color[1]}${color[2]}${color[2]}${color[3]}${color[3]}` :
color;
}
// Si es un nombre de color
const lowerColor = color.toLowerCase();
if (colorNames[lowerColor]) {
return colorNames[lowerColor];
}
return null;
}
/**
* Convierte un color a diferentes formatos
* @param {string} hex - Color en formato hexadecimal
* @returns {object} - Objeto con los diferentes formatos
*/
export function convertColorFormats(hex) {
if (!chroma.valid(hex)) return null;
const color = chroma(hex);
return {
hex: color.hex(),
rgb: color.css('rgb'),
hsl: color.css('hsl')
};
}
/**
* Ajusta el color de texto para mejor contraste
* @param {string} bgColor - Color de fondo en hexadecimal
* @returns {string} - Color de texto recomendado (negro o blanco)
*/
export function getContrastColor(bgColor) {
return chroma.contrast(bgColor, '#000') > 4.5 ? '#000' : '#fff';
}
import { getContrastColor } from './colorUtils.js';
/**
* Genera diferentes esquemas de armonía de color
* @param {string} baseHex - Color base en hexadecimal
* @returns {object} - Objeto con todos los esquemas de armonía
*/
export function generateHarmonies(baseHex) {
if (!chroma.valid(baseHex)) return null;
const base = chroma(baseHex);
const hsl = base.hsl();
// Esquema monocromático (3 colores)
const monochromatic = [
base,
base.set('hsl.l', Math.min(hsl[2] + 0.2, 0.9)),
base.set('hsl.l', Math.max(hsl[2] - 0.2, 0.1))
];
// Esquema complementario (2 colores)
const complementary = [
base,
base.set('hsl.h', (hsl[0] + 180) % 360)
];
// Esquema de tríada (3 colores)
const triad = [
base,
base.set('hsl.h', (hsl[0] + 120) % 360),
base.set('hsl.h', (hsl[0] + 240) % 360)
];
// Esquema análogo (3 colores)
const analogous = [
base,
base.set('hsl.h', (hsl[0] + 30) % 360),
base.set('hsl.h', (hsl[0] - 30 + 360) % 360)
];
// Esquema complementario dividido (3 colores)
const splitComplementary = [
base,
base.set('hsl.h', (hsl[0] + 150) % 360),
base.set('hsl.h', (hsl[0] + 210) % 360)
];
// Esquema tétrada (4 colores)
const tetradic = [
base,
base.set('hsl.h', (hsl[0] + 90) % 360),
base.set('hsl.h', (hsl[0] + 180) % 360),
base.set('hsl.h', (hsl[0] + 270) % 360)
];
return {
monochromatic: monochromatic.map(c => c.hex()),
complementary: complementary.map(c => c.hex()),
triad: triad.map(c => c.hex()),
analogous: analogous.map(c => c.hex()),
splitComplementary: splitComplementary.map(c => c.hex()),
tetradic: tetradic.map(c => c.hex())
};
}
/**
* Crea el HTML para mostrar una paleta de colores
* @param {string} title - Título de la paleta
* @param {array} colors - Array de colores en hexadecimal
* @param {boolean} isFavorite - Si el color base está en favoritos
* @returns {string} - HTML de la tarjeta de paleta
*/
export function createHarmonyCard(title, colors, isFavorite = false) {
const baseColor = colors[0];
const contrastColor = getContrastColor(baseColor);
let colorsHTML = colors.map((color, idx) => {
const textColor = getContrastColor(color);
return `
<div class="color-item" style="background-color: ${color};" data-color="${color}">
<span class="color-value" style="color: ${textColor}">${color}</span>
<div class="color-actions">
<button class="copy-color" title="Copiar color" data-color="${color}">
<i class="fas fa-copy" style="color: ${color}; font-size: 12px;"></i>
</button>
<button class="favorite-color" title="${isFavorite ? 'Quitar de favoritos' : 'Añadir a favoritos'}" data-color="${color}">
<i class="${isFavorite ? 'fas' : 'far'} fa-star" style="color: ${color}; font-size: 12px;"></i>
</button>
</div>
</div>
`;
}).join('');
return `
<div class="harmony-card">
<div class="harmony-title" style="background-color: ${baseColor}; color: ${contrastColor}">
${title}
</div>
<div class="harmony-colors">
${colorsHTML}
</div>
<div class="harmony-actions">
<button class="export-palette" data-palette='${JSON.stringify(colors)}'>
<i class="fas fa-download"></i> Exportar
</button>
<button class="share-palette" data-palette='${JSON.stringify(colors)}'>
<i class="fas fa-share-alt"></i> Compartir
</button>
</div>
</div>
`;
}
/**
* Guarda un color en favoritos
* @param {string} hex - Color en formato hexadecimal
*/
export function saveFavorite(hex) {
const favorites = getFavorites();
if (!favorites.includes(hex)) {
favorites.push(hex);
localStorage.setItem('colorFavorites', JSON.stringify(favorites));
}
}
/**
* Elimina un color de favoritos
* @param {string} hex - Color en formato hexadecimal
*/
export function removeFavorite(hex) {
const favorites = getFavorites();
const updated = favorites.filter(color => color !== hex);
localStorage.setItem('colorFavorites', JSON.stringify(updated));
}
/**
* Obtiene todos los colores favoritos
* @returns {array} - Array de colores en hexadecimal
*/
export function getFavorites() {
return JSON.parse(localStorage.getItem('colorFavorites')) || [];
}
/**
* Comprueba si un color está en favoritos
* @param {string} hex - Color en formato hexadecimal
* @returns {boolean}
*/
export function isFavorite(hex) {
const favorites = getFavorites();
return favorites.includes(hex);
}
import { convertColorFormats, validateColor } from './colorUtils.js';
import { generateHarmonies, createHarmonyCard } from './harmonyGenerator.js';
import { saveFavorite, removeFavorite, getFavorites, isFavorite } from './storageManager.js';
let currentColor = '#4a6fa5';
let colorPicker = null;
/**
* Inicializa el selector de color Pickr
*/
export function initColorPicker() {
const pickerContainer = document.getElementById('pickerContainer');
colorPicker = Pickr.create({
el: pickerContainer,
theme: 'nano',
default: currentColor,
components: {
preview: true,
opacity: false,
hue: true,
interaction: {
hex: true,
rgba: true,
hsla: true,
input: true,
save: true
}
}
});
colorPicker.on('save', (color) => {
if (color) {
const hex = color.toHEXA().toString();
updateColor(hex);
togglePicker();
}
});
colorPicker.on('cancel', togglePicker);
}
/**
* Muestra/oculta el selector de color
*/
export function togglePicker() {
const pickerContainer = document.getElementById('pickerContainer');
pickerContainer.classList.toggle('hidden');
if (!pickerContainer.classList.contains('hidden')) {
colorPicker.setColor(currentColor);
gsap.from(pickerContainer, { opacity: 0, duration: 0.3 });
}
}
/**
* Actualiza el color principal y regenera las armonías
* @param {string} hex - Nuevo color en hexadecimal
*/
export function updateColor(hex) {
if (!chroma.valid(hex)) return;
currentColor = hex;
const colorDisplay = document.getElementById('currentColor');
colorDisplay.style.backgroundColor = hex;
// Actualizar formatos de color
const formats = convertColorFormats(hex);
document.getElementById('hexValue').textContent = formats.hex;
document.getElementById('rgbValue').textContent = formats.rgb;
document.getElementById('hslValue').textContent = formats.hsl;
// Actualizar armonías
updateHarmonies(hex);
// Actualizar URL
updateURL(hex);
}
/**
* Genera y muestra las armonías de color
* @param {string} hex - Color base en hexadecimal
*/
function updateHarmonies(hex) {
const harmonies = generateHarmonies(hex);
const container = document.getElementById('harmoniesContainer');
if (!harmonies) return;
const isFav = isFavorite(hex);
container.innerHTML = `
${createHarmonyCard('Monocromático', harmonies.monochromatic, isFav)}
${createHarmonyCard('Complementario', harmonies.complementary, isFav)}
${createHarmonyCard('Tríada', harmonies.triad, isFav)}
${createHarmonyCard('Análogos', harmonies.analogous, isFav)}
${createHarmonyCard('Complementario Dividido', harmonies.splitComplementary, isFav)}
${createHarmonyCard('Tétrada', harmonies.tetradic, isFav)}
`;
}
/**
* Actualiza la URL con el color actual
* @param {string} hex - Color en hexadecimal
*/
function updateURL(hex) {
const url = new URL(window.location.href);
url.searchParams.set('color', hex);
window.history.pushState({}, '', url);
}
/**
* Muestra la vista de favoritos
*/
export function showFavorites() {
const mainSection = document.querySelector('.harmonies-section');
const favSection = document.getElementById('favoritesSection');
mainSection.classList.add('hidden');
favSection.classList.remove('hidden');
renderFavorites();
}
/**
* Vuelve a la vista principal
*/
export function showMainView() {
const mainSection = document.querySelector('.harmonies-section');
const favSection = document.getElementById('favoritesSection');
mainSection.classList.remove('hidden');
favSection.classList.add('hidden');
}
/**
* Renderiza los colores favoritos
*/
export function renderFavorites() {
const favorites = getFavorites();
const container = document.getElementById('favoritesContainer');
if (favorites.length === 0) {
container.innerHTML = '<p class="no-favorites">No tienes colores favoritos aún.</p>';
return;
}
container.innerHTML = favorites.map(color => {
const textColor = getContrastColor(color);
return `
<div class="favorite-card" data-color="${color}">
<div class="favorite-color" style="background-color: ${color};">
<div class="favorite-actions">
<button class="copy-color" title="Copiar color" data-color="${color}">
<i class="fas fa-copy" style="color: ${textColor};"></i>
</button>
<button class="remove-favorite" title="Quitar de favoritos" data-color="${color}">
<i class="fas fa-trash-alt" style="color: ${textColor};"></i>
</button>
</div>
</div>
<div class="favorite-info">
<div class="favorite-hex" style="color: ${textColor}">${color}</div>
</div>
</div>
`;
}).join('');
}
/**
* Maneja el evento de búsqueda de color
*/
export function handleSearch() {
const input = document.getElementById('colorInput');
const color = validateColor(input.value.trim());
if (color) {
updateColor(color);
input.value = '';
} else {
showNotification('Color no válido');
}
}
/**
* Genera un color aleatorio
*/
export function generateRandomColor() {
const randomHex = chroma.random().hex();
updateColor(randomHex);
}
/**
* Copia texto al portapapeles
* @param {string} text - Texto a copiar
*/
export function copyToClipboard(text) {
navigator.clipboard.writeText(text)
.then(() => showNotification('Copiado al portapapeles'))
.catch(err => console.error('Error al copiar:', err));
}
/**
* Exporta una paleta de colores
* @param {array} palette - Array de colores en hexadecimal
* @param {string} type - Tipo de exportación (txt, css, json)
*/
export function exportPalette(palette, type = 'txt') {
let content, filename, mimeType;
const timestamp = new Date().getTime();
const baseName = `palette_${palette[0].replace('#', '')}_${timestamp}`;
switch (type) {
case 'css':
content = `:root {\n${palette.map((c, i) => ` --color-${i+1}: ${c};`).join('\n')}\n}`;
filename = `${baseName}.css`;
mimeType = 'text/css';
break;
case 'json':
content = JSON.stringify(palette, null, 2);
filename = `${baseName}.json`;
mimeType = 'application/json';
break;
default: // txt
content = palette.join('\n');
filename = `${baseName}.txt`;
mimeType = 'text/plain';
}
const blob = new Blob([content], { type: mimeType });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = filename;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
}
/**
* Exporta todos los favoritos
* @param {string} type - Tipo de exportación (txt, css, json)
*/
export function exportFavorites(type = 'txt') {
const favorites = getFavorites();
if (favorites.length === 0) {
showNotification('No hay favoritos para exportar');
return;
}
let content, filename, mimeType;
const timestamp = new Date().getTime();
switch (type) {
case 'css':
content = `:root {\n${favorites.map((c, i) => ` --color-${i+1}: ${c};`).join('\n')}\n}`;
filename = `favorites_${timestamp}.css`;
mimeType = 'text/css';
break;
case 'json':
content = JSON.stringify(favorites, null, 2);
filename = `favorites_${timestamp}.json`;
mimeType = 'application/json';
break;
default: // txt
content = favorites.join('\n');
filename = `favorites_${timestamp}.txt`;
mimeType = 'text/plain';
}
const blob = new Blob([content], { type: mimeType });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = filename;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
}
/**
* Comparte una paleta de colores
* @param {array} palette - Array de colores en hexadecimal
*/
export function sharePalette(palette) {
const url = new URL(window.location.href);
url.searchParams.set('color', palette[0]);
if (navigator.share) {
navigator.share({
title: 'Mira esta paleta de colores',
text: `Paleta generada con colores: ${palette.join(', ')}`,
url: url.toString()
}).catch(err => {
console.log('Error al compartir:', err);
fallbackShare(url.toString());
});
} else {
fallbackShare(url.toString());
}
}
function fallbackShare(url) {
// Copiar URL al portapapeles como fallback
copyToClipboard(url);
showNotification('Enlace copiado al portapapeles');
}
/**
* Muestra una notificación temporal
* @param {string} message - Mensaje a mostrar
* @param {number} duration - Duración en milisegundos (opcional)
*/
export function showNotification(message, duration = 3000) {
const notification = document.getElementById('notification');
notification.textContent = message;
notification.classList.add('show');
setTimeout(() => {
notification.classList.remove('show');
}, duration);
}
/**
* Alterna el tema claro/oscuro
*/
export function toggleTheme() {
const body = document.body;
const currentTheme = body.getAttribute('data-theme');
const themeBtn = document.getElementById('themeBtn');
if (currentTheme === 'dark') {
body.removeAttribute('data-theme');
themeBtn.innerHTML = '<i class="fas fa-moon"></i>';
localStorage.setItem('colorTheme', 'light');
} else {
body.setAttribute('data-theme', 'dark');
themeBtn.innerHTML = '<i class="fas fa-sun"></i>';
localStorage.setItem('colorTheme', 'dark');
}
}
/**
* Carga el tema guardado
*/
export function loadTheme() {
const savedTheme = localStorage.getItem('colorTheme');
const themeBtn = document.getElementById('themeBtn');
if (savedTheme === 'dark') {
document.body.setAttribute('data-theme', 'dark');
themeBtn.innerHTML = '<i class="fas fa-sun"></i>';
}
}
/**
* Maneja los eventos de la aplicación
*/
export function setupEventListeners() {
// Búsqueda de color
document.getElementById('searchBtn').addEventListener('click', handleSearch);
document.getElementById('colorInput').addEventListener('keypress', (e) => {
if (e.key === 'Enter') handleSearch();
});
// Botones principales
document.getElementById('randomBtn').addEventListener('click', generateRandomColor);
document.getElementById('pickerBtn').addEventListener('click', togglePicker);
document.getElementById('favoritesBtn').addEventListener('click', showFavorites);
document.getElementById('backBtn').addEventListener('click', showMainView);
// Copiar formatos de color
document.querySelectorAll('.copy-btn').forEach(btn => {
btn.addEventListener('click', () => {
const target = btn.getAttribute('data-target');
const text = document.getElementById(target).textContent;
copyToClipboard(text);
});
});
// Exportar favoritos
document.getElementById('exportFavoritesBtn').addEventListener('click', () => {
exportFavorites('txt');
});
// Tema
document.getElementById('themeBtn').addEventListener('click', toggleTheme);
// Delegación de eventos para elementos dinámicos
document.addEventListener('click', (e) => {
// Copiar color
if (e.target.closest('.copy-color')) {
const color = e.target.closest('.copy-color').getAttribute('data-color');
copyToClipboard(color);
}
// Añadir/quitar favorito
if (e.target.closest('.favorite-color')) {
const color = e.target.closest('.favorite-color').getAttribute('data-color');
if (isFavorite(color)) {
removeFavorite(color);
e.target.classList.replace('fas', 'far');
showNotification('Color eliminado de favoritos');
} else {
saveFavorite(color);
e.target.classList.replace('far', 'fas');
showNotification('Color añadido a favoritos');
}
}
// Eliminar favorito
if (e.target.closest('.remove-favorite')) {
const color = e.target.closest('.remove-favorite').getAttribute('data-color');
removeFavorite(color);
renderFavorites();
showNotification('Color eliminado de favoritos');
}
// Exportar paleta
if (e.target.closest('.export-palette')) {
const palette = JSON.parse(e.target.closest('.export-palette').getAttribute('data-palette'));
exportPalette(palette, 'txt');
}
// Compartir paleta
if (e.target.closest('.share-palette')) {
const palette = JSON.parse(e.target.closest('.share-palette').getAttribute('data-palette'));
sharePalette(palette);
}
// Click en tarjeta de favorito
if (e.target.closest('.favorite-card')) {
const color = e.target.closest('.favorite-card').getAttribute('data-color');
updateColor(color);
showMainView();
}
});
}
/**
* Inicializa la aplicación con el color de la URL o uno por defecto
*/
export function initApp() {
const urlParams = new URLSearchParams(window.location.search);
if (urlParams.has('color')) {
const color = validateColor(urlParams.get('color'));
if (color) {
updateColor(color);
return;
}
}
if (urlParams.has('random')) {
generateRandomColor();
return;
}
if (urlParams.has('favorites')) {
showFavorites();
return;
}
// Color por defecto
updateColor(currentColor);
}
import { initColorPicker, togglePicker, updateColor, handleSearch, generateRandomColor,
showFavorites, showMainView, renderFavorites, setupEventListeners,
toggleTheme, loadTheme, initApp } from './uiComponents.js';
// Inicialización de la aplicación
document.addEventListener('DOMContentLoaded', () => {
initColorPicker();
loadTheme();
setupEventListeners();
initApp();
});
[
{"name": "AliceBlue", "hex": "#F0F8FF"},
{"name": "AntiqueWhite", "hex": "#FAEBD7"},
{"name": "Aqua", "hex": "#00FFFF"},
{"name": "Aquamarine", "hex": "#7FFFD4"},
{"name": "Azure", "hex": "#F0FFFF"},
{"name": "Beige", "hex": "#F5F5DC"},
{"name": "Bisque", "hex": "#FFE4C4"},
{"name": "Black", "hex": "#000000"},
{"name": "BlanchedAlmond", "hex": "#FFEBCD"},
{"name": "Blue", "hex": "#0000FF"},
{"name": "BlueViolet", "hex": "#8A2BE2"},
{"name": "Brown", "hex": "#A52A2A"},
{"name": "BurlyWood", "hex": "#DEB887"},
{"name": "CadetBlue", "hex": "#5F9EA0"},
{"name": "Chartreuse", "hex": "#7FFF00"},
{"name": "Chocolate", "hex": "#D2691E"},
{"name": "Coral", "hex": "#FF7F50"},
{"name": "CornflowerBlue", "hex": "#6495ED"},
{"name": "Cornsilk", "hex": "#FFF8DC"},
{"name": "Crimson", "hex": "#DC143C"},
{"name": "Cyan", "hex": "#00FFFF"},
{"name": "DarkBlue", "hex": "#00008B"},
{"name": "DarkCyan", "hex": "#008B8B"},
{"name": "DarkGoldenRod", "hex": "#B8860B"},
{"name": "DarkGray", "hex": "#A9A9A9"},
{"name": "DarkGreen", "hex": "#006400"},
{"name": "DarkKhaki", "hex": "#BDB76B"},
{"name": "DarkMagenta", "hex": "#8B008B"},
{"name": "DarkOliveGreen", "hex": "#556B2F"},
{"name": "DarkOrange", "hex": "#FF8C00"},
{"name": "DarkOrchid", "hex": "#9932CC"},
{"name": "DarkRed", "hex": "#8B0000"},
{"name": "DarkSalmon", "hex": "#E9967A"},
{"name": "DarkSeaGreen", "hex": "#8FBC8F"},
{"name": "DarkSlateBlue", "hex": "#483D8B"},
{"name": "DarkSlateGray", "hex": "#2F4F4F"},
{"name": "DarkTurquoise", "hex": "#00CED1"},
{"name": "DarkViolet", "hex": "#9400D3"},
{"name": "DeepPink", "hex": "#FF1493"},
{"name": "DeepSkyBlue", "hex": "#00BFFF"},
{"name": "DimGray", "hex": "#696969"},
{"name": "DodgerBlue", "hex": "#1E90FF"},
{"name": "FireBrick", "hex": "#B22222"},
{"name": "FloralWhite", "hex": "#FFFAF0"},
{"name": "ForestGreen", "hex": "#228B22"},
{"name": "Fuchsia", "hex": "#FF00FF"},
{"name": "Gainsboro", "hex": "#DCDCDC"},
{"name": "GhostWhite", "hex": "#F8F8FF"},
{"name": "Gold", "hex": "#FFD700"},
{"name": "GoldenRod", "hex": "#DAA520"},
{"name": "Gray", "hex": "#808080"},
{"name": "Green", "hex": "#008000"},
{"name": "GreenYellow", "hex": "#ADFF2F"},
{"name": "HoneyDew", "hex": "#F0FFF0"},
{"name": "HotPink", "hex": "#FF69B4"},
{"name": "IndianRed", "hex": "#CD5C5C"},
{"name": "Indigo", "hex": "#4B0082"},
{"name": "Ivory", "hex": "#FFFFF0"},
{"name": "Khaki", "hex": "#F0E68C"},
{"name": "Lavender", "hex": "#E6E6FA"},
{"name": "LavenderBlush", "hex": "#FFF0F5"},
{"name": "LawnGreen", "hex": "#7CFC00"},
{"name": "LemonChiffon", "hex": "#FFFACD"},
{"name": "LightBlue", "hex": "#ADD8E6"},
{"name": "LightCoral", "hex": "#F08080"},
{"name": "LightCyan", "hex": "#E0FFFF"},
{"name": "LightGoldenRodYellow", "hex": "#FAFAD2"},
{"name": "LightGray", "hex": "#D3D3D3"},
{"name": "LightGreen", "hex": "#90EE90"},
{"name": "LightPink", "hex": "#FFB6C1"},
{"name": "LightSalmon", "hex": "#FFA07A"},
{"name": "LightSeaGreen", "hex": "#20B2AA"},
{"name": "LightSkyBlue", "hex": "#87CEFA"},
{"name": "LightSlateGray", "hex": "#778899"},
{"name": "LightSteelBlue", "hex": "#B0C4DE"},
{"name": "LightYellow", "hex": "#FFFFE0"},
{"name": "Lime", "hex": "#00FF00"},
{"name": "LimeGreen", "hex": "#32CD32"},
{"name": "Linen", "hex": "#FAF0E6"},
{"name": "Magenta", "hex": "#FF00FF"},
{"name": "Maroon", "hex": "#800000"},
{"name": "MediumAquaMarine", "hex": "#66CDAA"},
{"name": "MediumBlue", "hex": "#0000CD"},
{"name": "MediumOrchid", "hex": "#BA55D3"},
{"name": "MediumPurple", "hex": "#9370DB"},
{"name": "MediumSeaGreen", "hex": "#3CB371"},
{"name": "MediumSlateBlue", "hex": "#7B68EE"},
{"name": "MediumSpringGreen", "hex": "#00FA9A"},
{"name": "MediumTurquoise", "hex": "#48D1CC"},
{"name": "MediumVioletRed", "hex": "#C71585"},
{"name": "MidnightBlue", "hex": "#191970"},
{"name": "MintCream", "hex": "#F5FFFA"},
{"name": "MistyRose", "hex": "#FFE4E1"},
{"name": "Moccasin", "hex": "#FFE4B5"},
{"name": "NavajoWhite", "hex": "#FFDEAD"},
{"name": "Navy", "hex": "#000080"},
{"name": "OldLace", "hex": "#FDF5E6"},
{"name": "Olive", "hex": "#808000"},
{"name": "OliveDrab", "hex": "#6B8E23"},
{"name": "Orange", "hex": "#FFA500"},
{"name": "OrangeRed", "hex": "#FF4500"},
{"name": "Orchid", "hex": "#DA70D6"},
{"name": "PaleGoldenRod", "hex": "#EEE8AA"},
{"name": "PaleGreen", "hex": "#98FB98"},
{"name": "PaleTurquoise", "hex": "#AFEEEE"},
{"name": "PaleVioletRed", "hex": "#DB7093"},
{"name": "PapayaWhip", "hex": "#FFEFD5"},
{"name": "PeachPuff", "hex": "#FFDAB9"},
{"name": "Peru", "hex": "#CD853F"},
{"name": "Pink", "hex": "#FFC0CB"},
{"name": "Plum", "hex": "#DDA0DD"},
{"name": "PowderBlue", "hex": "#B0E0E6"},
{"name": "Purple", "hex": "#800080"},
{"name": "RebeccaPurple", "hex": "#663399"},
{"name": "Red", "hex": "#FF0000"},
{"name": "RosyBrown", "hex": "#BC8F8F"},
{"name": "RoyalBlue", "hex": "#4169E1"},
{"name": "SaddleBrown", "hex": "#8B4513"},
{"name": "Salmon", "hex": "#FA8072"},
{"name": "SandyBrown", "hex": "#F4A460"},
{"name": "SeaGreen", "hex": "#2E8B57"},
{"name": "SeaShell", "hex": "#FFF5EE"},
{"name": "Sienna", "hex": "#A0522D"},
{"name": "Silver", "hex": "#C0C0C0"},
{"name": "SkyBlue", "hex": "#87CEEB"},
{"name": "SlateBlue", "hex": "#6A5ACD"},
{"name": "SlateGray", "hex": "#708090"},
{"name": "Snow", "hex": "#FFFAFA"},
{"name": "SpringGreen", "hex": "#00FF7F"},
{"name": "SteelBlue", "hex": "#4682B4"},
{"name": "Tan", "hex": "#D2B48C"},
{"name": "Teal", "hex": "#008080"},
{"name": "Thistle", "hex": "#D8BFD8"},
{"name": "Tomato", "hex": "#FF6347"},
{"name": "Turquoise", "hex": "#40E0D0"},
{"name": "Violet", "hex": "#EE82EE"},
{"name": "Wheat", "hex": "#F5DEB3"},
{"name": "White", "hex": "#FFFFFF"},
{"name": "WhiteSmoke", "hex": "#F5F5F5"},
{"name": "Yellow", "hex": "#FFFF00"},
{"name": "YellowGreen", "hex": "#9ACD32"}
]
Entrada de color múltiple:
Generación de armonías:
Gestión de favoritos:
Exportación:
Compartición:
Accesibilidad:
Diseño responsive:
Tema oscuro/claro:
Efectos visuales:
index.html
en un navegador modernoLa aplicación funciona completamente del lado del cliente sin necesidad de backend.