Murlocninja/DictionaryColorCombinations
1
1document.addEventListener('DOMContentLoaded', () => {2 const grid = document.getElementById('bento-grid');3 const root = document.documentElement;4 const toast = document.getElementById('toast');5 const filterBtns = document.querySelectorAll('.filter-btn');6 const randomizeBtn = document.getElementById('randomize-btn');7 let toastTimeout;8 let activeFilter = 'all';9 10 // ─── Fisher-Yates Shuffle ──────────────────────────────────────────────11 function shuffle(arr) {12 const a = [...arr];13 for (let i = a.length - 1; i > 0; i--) {14 const j = Math.floor(Math.random() * (i + 1));15 [a[i], a[j]] = [a[j], a[i]];16 }17 return a;18 }19 20 // ─── Build a single tile DOM node ──────────────────────────────────────21 function buildTile(palette) {22 const tile = document.createElement('div');23 tile.className = `palette-tile ${palette.span} visible`;24 tile.dataset.colorCount = palette.colors.length;25 tile.dataset.id = palette.id;26 27 // Harmony overlay on hover28 const harmonyColor = palette.colors[0];29 tile.addEventListener('mouseenter', () => {30 root.style.setProperty('--harmony-overlay', harmonyColor);31 });32 tile.addEventListener('mouseleave', () => {33 root.style.setProperty('--harmony-overlay', 'transparent');34 });35 36 // Build swatches37 palette.colors.forEach(color => {38 const swatch = document.createElement('div');39 swatch.className = 'color-swatch';40 swatch.style.backgroundColor = color;41 swatch.style.setProperty('--swatch-color', color);42 43 const hexLabel = document.createElement('div');44 hexLabel.className = 'hex-label';45 hexLabel.textContent = color.toUpperCase();46 swatch.appendChild(hexLabel);47 48 swatch.addEventListener('click', (e) => {49 e.stopPropagation();50 navigator.clipboard.writeText(color.toUpperCase()).then(() => {51 showToast(`Copied ${color.toUpperCase()}`);52 }).catch(() => {});53 });54 55 tile.appendChild(swatch);56 });57 58 return tile;59 }60 61 // ─── Render grid from a (possibly shuffled) palette array ──────────────62 let currentOrder = shuffle(palettes); // randomize on first load63 64 function renderGrid(orderedPalettes) {65 grid.innerHTML = '';66 orderedPalettes.forEach((palette, index) => {67 const tile = buildTile(palette);68 69 // Staggered entrance animation70 tile.style.opacity = '0';71 tile.style.transform = 'translateY(24px) scale(0.96)';72 grid.appendChild(tile);73 74 setTimeout(() => {75 tile.style.transition = 'opacity 0.5s cubic-bezier(0.16, 1, 0.3, 1), transform 0.5s cubic-bezier(0.16, 1, 0.3, 1)';76 tile.style.opacity = '1';77 tile.style.transform = 'translateY(0) scale(1)';78 79 // After entrance, hand off to CSS classes80 setTimeout(() => {81 tile.style.transition = '';82 tile.style.opacity = '';83 tile.style.transform = '';84 tile.classList.add('visible');85 }, 520);86 }, 40 * index);87 });88 89 // Apply the current filter after render90 applyFilter(activeFilter, false);91 }92 93 // ─── Filter Logic ──────────────────────────────────────────────────────94 function applyFilter(filter, animate = true) {95 activeFilter = filter;96 const tiles = grid.querySelectorAll('.palette-tile');97 98 tiles.forEach((tile, i) => {99 const count = parseInt(tile.dataset.colorCount, 10);100 const matches = filter === 'all' || count === parseInt(filter, 10);101 102 if (matches) {103 tile.style.display = '';104 // Tiny stagger for "reveal" feel105 const delay = animate ? i * 25 : 0;106 setTimeout(() => {107 tile.classList.remove('hidden');108 tile.classList.add('visible');109 }, delay);110 } else {111 tile.classList.remove('visible');112 tile.classList.add('hidden');113 setTimeout(() => {114 // Only hide from layout after transition115 if (tile.classList.contains('hidden')) {116 tile.style.display = 'none';117 }118 }, 350);119 }120 });121 }122 123 // ─── Wire up filter buttons ────────────────────────────────────────────124 filterBtns.forEach(btn => {125 btn.addEventListener('click', () => {126 filterBtns.forEach(b => b.classList.remove('active'));127 btn.classList.add('active');128 applyFilter(btn.dataset.filter);129 });130 });131 132 // ─── Shuffle Button ────────────────────────────────────────────────────133 randomizeBtn.addEventListener('click', () => {134 // Spin animation135 randomizeBtn.classList.add('spinning');136 setTimeout(() => randomizeBtn.classList.remove('spinning'), 460);137 138 currentOrder = shuffle(palettes);139 renderGrid(currentOrder);140 });141 142 // ─── Toast ─────────────────────────────────────────────────────────────143 function showToast(message = 'Copied to clipboard') {144 toast.textContent = message;145 if (toastTimeout) clearTimeout(toastTimeout);146 toast.classList.add('show');147 toastTimeout = setTimeout(() => toast.classList.remove('show'), 2000);148 }149 150 // ─── Initial Render (already shuffled) ─────────────────────────────────151 renderGrid(currentOrder);152});153 