A set of difficult tasks with the theme of emoji's including: tier list creation, music creation and, themeable website creation.
Prompt
Make a good looking html file of drag and droppable tier list of emojis. There should be a card for each of the emojis I will compare. Cards should have rounded corners, shadowing while I drag but no shadow while I am not dragging. Every transition should include animations. Crads should be snapped to the left of the tier if the tier is empty. But if there are already items present in the tier, then it should snap between the two logos (or the right side of the rightmost logo, or the left side of the leftmost logo according to where it is being dragged at). It should highlight where it will snap before the user drops the card and the snapping happens
A system prompt was added to support web rendering
```html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Emoji Tier List</title> <!-- SortableJS CDN --> <script src="https://cdn.jsdelivr.net/npm/sortablejs@1.15.0/Sortable.min.js"></script> <style> :root{ --card-size:72px; --gap:12px; --border-radius:14px; --placeholder-bg:rgba(0,0,0,.08); --transition-time:.25s; } *{box-sizing:border-box} body{ font-family:system-ui,-apple-system,Segoe UI,Roboto,Helvetica,Arial,sans-serif; margin:0; padding:2rem; background:#fafafa; } h1{ text-align:center; margin-bottom:2rem; } /* Tier row -------------------------------------------------------- */ .tier{ display:flex; align-items:flex-start; margin-bottom:1.2rem; } .tier-label{ width:80px; font-weight:700; font-size:1.2rem; line-height:var(--card-size); text-align:center; } .tier-area{ flex:1; display:flex; gap:var(--gap); min-height:var(--card-size); padding:var(--gap); border:2px dashed #d0d0d0; border-radius:var(--border-radius); transition:background-color var(--transition-time); } /* Emoji cards ---------------------------------------------------- */ .emoji-card{ width:var(--card-size); height:var(--card-size); display:flex; align-items:center; justify-content:center; font-size:2rem; background:#fff; border-radius:var(--border-radius); user-select:none; cursor:grab; transition: transform var(--transition-time) ease, box-shadow var(--transition-time) ease; } /* Shadow & scale while dragging */ .dragging{ box-shadow:0 6px 12px rgba(0,0,0,.25); transform:scale(1.08) rotate(2deg); cursor:grabbing; } /* Placeholder that shows snap location --------------------------- */ .placeholder{ width:var(--card-size); height:var(--card-size); border-radius:var(--border-radius); background:var(--placeholder-bg); border:2px dashed #999; margin:0 !important; /* override Sortable inline style */ animation:fadeIn var(--transition-time) ease forwards; } @keyframes fadeIn{ from{opacity:0; transform:scale(.8)} to{opacity:1; transform:scale(1)} } </style> </head> <body> <h1>Emoji Tier List</h1> <!-- Tiers ------------------------------------------------------------> <div class="tier"> <div class="tier-label">S</div> <div class="tier-area" id="tier-s"></div> </div> <div class="tier"> <div class="tier-label">A</div> <div class="tier-area" id="tier-a"></div> </div> <div class="tier"> <div class="tier-label">B</div> <div class="tier-area" id="tier-b"></div> </div> <div class="tier"> <div class="tier-label">C</div> <div class="tier-area" id="tier-c"></div> </div> <div class="tier"> <div class="tier-label">D</div> <div class="tier-area" id="tier-d"></div> </div> <!-- Starting pool --> <h2 style="margin-top:3rem;">Unsorted</h2> <div class="tier-area" id="pool"></div> <script> /* ----------------------------------------------- Initial setup: put some emojis in the pool -------------------------------------------------*/ const emojis = ["😀","😎","😂","😍","🤔","🙌","🐶","🐱","🍕","⚽","🚗","🌈"]; const pool = document.getElementById("pool"); emojis.forEach(e=>{ const card = document.createElement("div"); card.className = "emoji-card"; card.textContent = e; pool.appendChild(card); }); /* ----------------------------------------------- Make every tier (and pool) sortable & connected -------------------------------------------------*/ const lists = document.querySelectorAll('.tier-area'); lists.forEach(list=>{ new Sortable(list,{ group:'tierlist', // allow cross-tier dragging animation:200, // smooth transition ghostClass:'placeholder', // where the card will snap chosenClass:'dragging', // card being picked up dragClass:'dragging', // card while moving direction:'horizontal', // snap between logos filter:'.placeholder', // ignore placeholder onStart: evt=>{ /* ensure placeholder width exactly matches card width (Sortable normally sets it automatically but we want custom stylings & animation) */ evt.clone.style.display="none"; // hide auto clone }, onEnd: evt=>{ evt.item.style.removeProperty('transform'); } }); }); </script> </body> </html> ```
```html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Emoji Tier List</title> <style> @import url('https://fonts.googleapis.com/css2?family=Nunito:wght@400;700&display=swap'); :root { --bg-color: #f0f2f5; --card-bg: #ffffff; --border-color: #e0e0e0; --shadow-light: rgba(0, 0, 0, 0.1); --shadow-medium: rgba(0, 0, 0, 0.15); --shadow-dark: rgba(0, 0, 0, 0.2); --primary-color: #6200ea; --s-tier-color: #ff7f7f; /* Red */ --a-tier-color: #ffbf7f; /* Orange */ --b-tier-color: #ffff7f; /* Yellow */ --c-tier-color: #7fff7f; /* Green */ --f-tier-color: #7fbfff; /* Blue */ --header-bg: #f8f8f8; --gap-spacing: 15px; --card-size: 60px; --card-padding: 10px; --card-font-size: 3em; --border-radius-main: 12px; --border-radius-card: 10px; } body { font-family: 'Nunito', sans-serif; background-color: var(--bg-color); margin: 0; padding: 20px; display: flex; justify-content: center; align-items: flex-start; min-height: 100vh; color: #333; box-sizing: border-box; } .tier-list-container { background-color: #ffffff; border-radius: var(--border-radius-main); box-shadow: 0 8px 20px var(--shadow-light); padding: 25px; width: 100%; max-width: 900px; box-sizing: border-box; } h1 { text-align: center; color: var(--primary-color); margin-top: 0; margin-bottom: 30px; font-weight: 700; } .tier-container { border: 1px solid var(--border-color); border-radius: var(--border-radius-main); margin-bottom: var(--gap-spacing); overflow: hidden; /* Ensures border-radius applies correctly to children */ } .tier-header { padding: 12px 18px; font-weight: 700; font-size: 1.2em; background-color: var(--header-bg); border-bottom: 1px solid var(--border-color); color: var(--primary-color); display: flex; align-items: center; gap: 10px; } .tier-header.unsorted-emojis-color { color: #555; background-color: #fbeedc; border-color: #e0d0b0; } .tier-header.s-tier-color { background-color: var(--s-tier-color); color: white; border-color: darken(var(--s-tier-color), 10%); } .tier-header.a-tier-color { background-color: var(--a-tier-color); color: #333; border-color: darken(var(--a-tier-color), 10%); } .tier-header.b-tier-color { background-color: var(--b-tier-color); color: #333; border-color: darken(var(--b-tier-color), 10%); } .tier-header.c-tier-color { background-color: var(--c-tier-color); color: #333; border-color: darken(var(--c-tier-color), 10%); } .tier-header.f-tier-color { background-color: var(--f-tier-color); color: #333; border-color: darken(var(--f-tier-color), 10%); } .tier-content { display: flex; flex-wrap: wrap; gap: var(--gap-spacing); padding: var(--gap-spacing); min-height: calc(var(--card-size) + (var(--card-padding) * 2) + 20px); /* Enough space for one card + padding */ align-items: flex-start; /* Align cards to the top in a row */ background-color: #fdfdfd; transition: background-color 0.3s ease; } .tier-content.drag-over { background-color: #e6f7ff; /* Light blue when drag is over specific tier content */ } .emoji-card { background-color: var(--card-bg); border: 1px solid var(--border-color); border-radius: var(--border-radius-card); box-shadow: 0 2px 5px var(--shadow-light); cursor: grab; user-select: none; display: flex; justify-content: center; align-items: center; width: var(--card-size); height: var(--card-size); padding: var(--card-padding); font-size: var(--card-font-size); transition: box-shadow 0.3s ease, transform 0.3s ease, opacity 0.3s ease, border-color 0.3s ease; flex-shrink: 0; /* Prevents cards from shrinking */ } .emoji-card:hover { box-shadow: 0 4px 10px var(--shadow-medium); transform: translateY(-2px); border-color: var(--primary-color); } .emoji-card.dragging { box-shadow: 0 10px 25px var(--shadow-dark); transform: scale(1.1); cursor: grabbing; opacity: 0.8; /* Make dragged item slightly transparent */ z-index: 1000; /* Bring to front */ } .drop-indicator { width: 4px; /* Thin line */ background-color: var(--primary-color); border-radius: 2px; opacity: 0.7; transition: all 0.2s ease-in-out; pointer-events: none; /* Important: don't block dragover events on children */ display: none; /* Hidden by default */ flex-shrink: 0; } /* Responsive adjustments */ @media (max-width: 768px) { body { padding: 10px; } .tier-list-container { padding: 15px; } h1 { font-size: 1.8em; margin-bottom: 20px; } .tier-header { font-size: 1.1em; padding: 10px 15px; } .tier-content { gap: 10px; padding: 10px; } .emoji-card { width: 50px; height: 50px; font-size: 2.5em; padding: 8px; } .drop-indicator { height: 50px; /* Adjust height for smaller cards */ } } @media (max-width: 480px) { .emoji-card { width: 45px; height: 45px; font-size: 2em; padding: 5px; } .drop-indicator { height: 45px; } } </style> </head> <body> <div class="tier-list-container"> <h1>Emoji Tier List</h1> <div class="tier-container unsorted-emojis" id="unsorted-emojis-container"> <div class="tier-header unsorted-emojis-color">Unsorted Emojis</div> <div class="tier-content" id="unsorted-emojis-content"> <!-- Initial emoji cards will be injected here by JS --> </div> </div> <div class="tier-container" id="s-tier-container"> <div class="tier-header s-tier-color">S Tier</div> <div class="tier-content" id="s-tier-content"></div> </div> <div class="tier-container" id="a-tier-container"> <div class="tier-header a-tier-color">A Tier</div> <div class="tier-content" id="a-tier-content"></div> </div> <div class="tier-container" id="b-tier-container"> <div class="tier-header b-tier-color">B Tier</div> <div class="tier-content" id="b-tier-content"></div> </div> <div class="tier-container" id="c-tier-container"> <div class="tier-header c-tier-color">C Tier</div> <div class="tier-content" id="c-tier-content"></div> </div> <div class="tier-container" id="f-tier-container"> <div class="tier-header f-tier-color">F Tier</div> <div class="tier-content" id="f-tier-content"></div> </div> </div> <script> let draggedItem = null; let dropIndicator = document.createElement('div'); dropIndicator.classList.add('drop-indicator'); document.body.appendChild(dropIndicator); // Append to body, its position will be managed within tier-content const emojis = ['😂', '❤️', '👍', '😭', '🔥', '😊', '🤔', '💯', '🙏', '🥳', '😎', '🤩', '👍', '🙏', '😂', '💯']; // Use a Set to ensure unique emojis, then convert back to array const uniqueEmojis = Array.from(new Set(emojis)); // Function to render initial emoji cards function renderEmojis() { const unsortedEmojisContainer = document.getElementById('unsorted-emojis-content'); unsortedEmojisContainer.innerHTML = ''; // Clear existing content uniqueEmojis.forEach((emoji, index) => { const emojiCard = document.createElement('div'); emojiCard.classList.add('emoji-card'); emojiCard.setAttribute('draggable', 'true'); emojiCard.setAttribute('id', `emoji-${emoji.codePointAt(0)}-${index}`); // Unique ID: emoji unicode + index emojiCard.textContent = emoji; unsortedEmojisContainer.appendChild(emojiCard); }); } // Logic to show the drop indicator at the correct position function showDropIndicator(container, clientX) { // Filter out the dragged item itself and other indicators const children = Array.from(container.children).filter( el => el !== draggedItem && !el.classList.contains('drop-indicator') ); let targetElement = null; let insertBeforeElement = null; if (children.length === 0) { // If container is empty, just append the indicator insertBeforeElement = null; } else { for (let i = 0; i < children.length; i++) { const child = children[i]; const rect = child.getBoundingClientRect(); // If clientX is before the midpoint of the child, insert before this child if (clientX < rect.left + rect.width / 2) { insertBeforeElement = child; break; } } // If loop finishes, clientX is past all midpoints, so append to end (or null for append) } if (dropIndicator.parentNode !== container || dropIndicator.nextSibling !== insertBeforeElement) { if (insertBeforeElement) { container.insertBefore(dropIndicator, insertBeforeElement); } else { container.appendChild(dropIndicator); } } dropIndicator.style.display = 'block'; // Set indicator height based on the dragged item's current height if (draggedItem) { dropIndicator.style.height = `${draggedItem.offsetHeight}px`; } else { dropIndicator.style.height = '60px'; // Default if draggedItem is somehow null } } // Logic to hide the drop indicator function hideDropIndicator() { if (dropIndicator.parentNode) { dropIndicator.parentNode.removeChild(dropIndicator); } dropIndicator.style.display = 'none'; } // --- Event Listeners --- // Drag start: Set the dragged item, add 'dragging' class, and allow move effect document.addEventListener('dragstart', (e) => { if (e.target.classList.contains('emoji-card')) { draggedItem = e.target; draggedItem.classList.add('dragging'); e.dataTransfer.effectAllowed = 'move'; // Using a timeout to allow the browser to take a snapshot of the element // before it becomes semi-transparent or moves from its original position. setTimeout(() => { draggedItem.style.opacity = '0.001'; // Make it almost invisible, but still occupy space }, 0); } }); // Drag over: Prevent default to allow drop, highlight dropped zone, show indicator document.addEventListener('dragover', (e) => { e.preventDefault(); // Allow drop if (!draggedItem) return; let targetContainer = e.target.closest('.tier-content'); if (targetContainer) { showDropIndicator(targetContainer, e.clientX); // Add drag-over class to highlight the entire tier while dragging over it document.querySelectorAll('.tier-content').forEach(container => { container.classList.remove('drag-over'); }); targetContainer.classList.add('drag-over'); } else { hideDropIndicator(); // If dragging outside any valid tier content document.querySelectorAll('.tier-content').forEach(container => { container.classList.remove('drag-over'); }); } }); // Drag leave: Remove drag-over class from the container document.addEventListener('dragleave', (e) => { let targetContainer = e.target.closest('.tier-content'); // This event can fire even when moving between child elements. // A more precise check is needed if we want to hide indicator immediately. // For simplicity, we rely on dragover to re-show or drop/dragend to clean up. if (targetContainer) { // Check if the cursor truly left the container if (!targetContainer.contains(e.relatedTarget)) { targetContainer.classList.remove('drag-over'); } } }); // Drop: Perform the actual DOM manipulation document.addEventListener('drop', (e) => { e.preventDefault(); if (!draggedItem) return; // Remove drag-over class from all containers document.querySelectorAll('.tier-content').forEach(container => { container.classList.remove('drag-over'); }); let targetContainer = e.target.closest('.tier-content'); if (targetContainer) { hideDropIndicator(); // Remove drop indicator once drop happens // Same logic as showDropIndicator to find the exact insertion point const children = Array.from(targetContainer.children).filter( el => el !== draggedItem && !el.classList.contains('drop-indicator') ); let insertBeforeElement = null; if (children.length === 0) { targetContainer.appendChild(draggedItem); } else { for (let i = 0; i < children.length; i++) { const child = children[i]; const rect = child.getBoundingClientRect(); if (e.clientX < rect.left + rect.width / 2) { insertBeforeElement = child; break; } } if (insertBeforeElement) { targetContainer.insertBefore(draggedItem, insertBeforeElement); } else { targetContainer.appendChild(draggedItem); } } } // The dragend event will handle cleanup regardless of whether a valid drop occurred }); // Drag end: Clean up after dragging document.addEventListener('dragend', () => { if (draggedItem) { draggedItem.classList.remove('dragging'); draggedItem.style.opacity = '1'; // Restore full opacity draggedItem = null; } hideDropIndicator(); // Ensure indicator is hidden document.querySelectorAll('.tier-content').forEach(container => { container.classList.remove('drag-over'); }); }); // Initial setup document.addEventListener('DOMContentLoaded', renderEmojis); </script> </body> </html> ```
Of course! Here is a feature-rich and visually appealing emoji tier list. It's fully self-contained in a single HTML file as requested. You can drag any emoji from the "Unranked" pool at the bottom into any of the colored tiers. The animations are smooth, and a placeholder will show you exactly where the emoji will land. ```html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Emoji Tier List</title> <style> @import url('https://fonts.googleapis.com/css2?family=Poppins:wght@400;600;700&display=swap'); :root { --bg-color: #1a1a2e; --card-bg: #16213e; --container-bg: #0f1828; --text-color: #e0e0e0; --shadow-color: rgba(22, 33, 62, 0.5); --accent-glow: #e94560; --tier-s: #ff7675; --tier-a: #ffc048; --tier-b: #55efc4; --tier-c: #74b9ff; --tier-d: #a29bfe; } * { box-sizing: border-box; margin: 0; padding: 0; } body { font-family: 'Poppins', sans-serif; background-color: var(--bg-color); color: var(--text-color); display: flex; justify-content: center; align-items: flex-start; padding: 40px 20px; min-height: 100vh; } .tier-list-container { width: 100%; max-width: 900px; background-color: var(--container-bg); border-radius: 16px; padding: 24px; box-shadow: 0 10px 30px rgba(0, 0, 0, 0.3); border: 1px solid rgba(255, 255, 255, 0.1); } h1 { text-align: center; font-size: 2.5rem; font-weight: 700; margin-bottom: 24px; color: var(--accent-glow); text-shadow: 0 0 10px var(--accent-glow); } .tiers { display: flex; flex-direction: column; gap: 8px; } .tier-row { display: flex; align-items: stretch; /* Make label and dropzone same height */ min-height: 80px; background-color: var(--card-bg); border-radius: 8px; overflow: hidden; /* Ensures child elements respect border-radius */ border: 1px solid rgba(255,255,255,0.05); } .tier-label { flex-shrink: 0; width: 80px; display: flex; align-items: center; justify-content: center; font-size: 2rem; font-weight: 700; color: #111; } /* Tier Label Colors */ .tier-s { background-color: var(--tier-s); } .tier-a { background-color: var(--tier-a); } .tier-b { background-color: var(--tier-b); } .tier-c { background-color: var(--tier-c); } .tier-d { background-color: var(--tier-d); } .tier-dropzone { flex-grow: 1; display: flex; flex-wrap: wrap; align-items: center; padding: 8px; gap: 8px; transition: background-color 0.2s ease; } /* All Draggable Cards */ .emoji-card { width: 64px; height: 64px; background-color: rgba(0,0,0,0.2); border-radius: 12px; display: flex; justify-content: center; align-items: center; font-size: 2.5rem; cursor: grab; transition: all 0.2s ease-in-out; user-select: none; touch-action: none; /* For mobile compatibility */ } /* Unranked Container */ .unranked-container { margin-top: 24px; padding: 16px; background-color: var(--card-bg); border-radius: 8px; border: 1px solid rgba(255,255,255,0.05); } .unranked-container h2 { text-align: center; font-weight: 600; margin-bottom: 16px; font-size: 1.25rem; } #unranked-pool { display: flex; flex-wrap: wrap; gap: 8px; justify-content: center; min-height: 80px; /* Make it a drop target even when empty */ } /* --- DRAG & DROP STYLING --- */ /* The item being dragged */ .sortable-drag { cursor: grabbing; opacity: 1 !important; transform: scale(1.1) rotate(3deg); box-shadow: 0 8px 25px var(--accent-glow); z-index: 999; } /* The placeholder for where the item will drop */ .sortable-ghost { opacity: 0.4; background-color: var(--accent-glow); border-radius: 12px; /* Remove the emoji text from the ghost element */ } .sortable-ghost .emoji-content { display: none; } /* The animation for when an item is moved in the list */ .sortable-chosen { opacity: 0.7; } .sortable-fallback { display: none !important; /* hide default drag image */ } </style> </head> <body> <div class="tier-list-container"> <h1>Emoji Tier List</h1> <div class="tiers"> <!-- Tier S --> <div class="tier-row"> <div class="tier-label tier-s">S</div> <div class="tier-dropzone" data-tier="s"></div> </div> <!-- Tier A --> <div class="tier-row"> <div class="tier-label tier-a">A</div> <div class="tier-dropzone" data-tier="a"></div> </div> <!-- Tier B --> <div class="tier-row"> <div class="tier-label tier-b">B</div> <div class="tier-dropzone" data-tier="b"></div> </div> <!-- Tier C --> <div class="tier-row"> <div class="tier-label tier-c">C</div> <div class="tier-dropzone" data-tier="c"></div> </div> <!-- Tier D --> <div class="tier-row"> <div class="tier-label tier-d">D</div> <div class="tier-dropzone" data-tier="d"></div> </div> </div> <div class="unranked-container"> <h2>Unranked</h2> <div id="unranked-pool"> <!-- Emojis are wrapped for better ghost styling --> <div class="emoji-card"><span class="emoji-content">😀</span></div> <div class="emoji-card"><span class="emoji-content">😂</span></div> <div class="emoji-card"><span class="emoji-content">😍</span></div> <div class="emoji-card"><span class="emoji-content">🤔</span></div> <div class="emoji-card"><span class="emoji-content">😢</span></div> <div class="emoji-card"><span class="emoji-content">😠</span></div> <div class="emoji-card"><span class="emoji-content">🤯</span></div> <div class="emoji-card"><span class="emoji-content">😴</span></div> <div class="emoji-card"><span class="emoji-content">🥳</span></div> <div class="emoji-card"><span class="emoji-content">🥺</span></div> <div class="emoji-card"><span class="emoji-content">🤢</span></div> <div class="emoji-card"><span class="emoji-content">💀</span></div> <div class="emoji-card"><span class="emoji-content">🤡</span></div> <div class="emoji-card"><span class="emoji-content">🤖</span></div> <div class="emoji-card"><span class="emoji-content">👻</span></div> <div class="emoji-card"><span class="emoji-content">👽</span></div> <div class="emoji-card"><span class="emoji-content">👾</span></div> <div class="emoji-card"><span class="emoji-content">🚀</span></div> <div class="emoji-card"><span class="emoji-content">🔥</span></div> <div class="emoji-card"><span class="emoji-content">💯</span></div> </div> </div> </div> <!-- SortableJS library for drag-and-drop functionality --> <script src="https://cdnjs.cloudflare.com/ajax/libs/Sortable/1.15.0/Sortable.min.js"></script> <script> document.addEventListener('DOMContentLoaded', (event) => { const dropzones = document.querySelectorAll('.tier-dropzone, #unranked-pool'); const sortableOptions = { group: 'emojis', // Set a group name for all lists animation: 250, // ms, animation speed moving items when sorting, `0` — without animation easing: "cubic-bezier(1, 0, 0, 1)", // Easing for animation ghostClass: 'sortable-ghost', // Class name for the drop placeholder dragClass: 'sortable-drag', // Class name for the dragging item fallbackOnBody: true, // Append the drag element to the body swapThreshold: 0.65, // Threshold of the swap zone }; dropzones.forEach(zone => { new Sortable(zone, sortableOptions); }); }); </script> </body> </html> ```
```html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Emoji Tier List</title> <style> * { margin: 0; padding: 0; box-sizing: border-box; } body { font-family: 'Arial', sans-serif; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); min-height: 100vh; padding: 20px; } .container { max-width: 1200px; margin: 0 auto; background: white; border-radius: 15px; padding: 30px; box-shadow: 0 20px 40px rgba(0,0,0,0.1); } h1 { text-align: center; color: #333; margin-bottom: 30px; font-size: 2.5rem; font-weight: bold; } .emoji-pool { margin-bottom: 30px; padding: 20px; background: #f8f9fa; border-radius: 12px; border: 2px dashed #dee2e6; } .emoji-pool h3 { margin-bottom: 15px; color: #495057; font-size: 1.2rem; } .emoji-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(60px, 1fr)); gap: 15px; justify-items: center; } .emoji-card { width: 60px; height: 60px; background: white; border-radius: 12px; display: flex; align-items: center; justify-content: center; font-size: 2rem; cursor: grab; transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); border: 2px solid #e9ecef; user-select: none; } .emoji-card:hover { transform: translateY(-2px); border-color: #007bff; } .emoji-card.dragging { box-shadow: 0 10px 25px rgba(0,0,0,0.3); transform: rotate(5deg) scale(1.1); z-index: 1000; cursor: grabbing; } .tier-list { display: flex; flex-direction: column; gap: 15px; } .tier { display: flex; min-height: 80px; background: #f8f9fa; border-radius: 12px; overflow: hidden; box-shadow: 0 2px 8px rgba(0,0,0,0.05); transition: all 0.3s ease; } .tier.drag-over { background: #e3f2fd; border: 2px solid #2196f3; transform: scale(1.02); } .tier-label { min-width: 100px; display: flex; align-items: center; justify-content: center; font-weight: bold; font-size: 1.2rem; color: white; writing-mode: horizontal-tb; } .tier-s .tier-label { background: #ff6b6b; } .tier-a .tier-label { background: #4ecdc4; } .tier-b .tier-label { background: #45b7d1; } .tier-c .tier-label { background: #96ceb4; } .tier-d .tier-label { background: #feca57; } .tier-f .tier-label { background: #ff9ff3; } .tier-content { flex: 1; display: flex; align-items: center; padding: 10px; gap: 10px; position: relative; min-height: 80px; } .drop-indicator { position: absolute; width: 3px; height: 50px; background: #2196f3; border-radius: 2px; opacity: 0; transition: all 0.2s ease; z-index: 10; } .drop-indicator.active { opacity: 1; animation: pulse 1s infinite; } @keyframes pulse { 0%, 100% { transform: scaleY(1); } 50% { transform: scaleY(1.2); } } .tier-content .emoji-card { position: relative; animation: slideIn 0.3s cubic-bezier(0.4, 0, 0.2, 1); } @keyframes slideIn { from { opacity: 0; transform: translateY(-10px) scale(0.8); } to { opacity: 1; transform: translateY(0) scale(1); } } .return-zone { position: fixed; top: 20px; right: 20px; width: 80px; height: 80px; background: #ff6b6b; border-radius: 50%; display: flex; align-items: center; justify-content: center; color: white; font-size: 2rem; opacity: 0; pointer-events: none; transition: all 0.3s ease; z-index: 1001; } .return-zone.active { opacity: 1; pointer-events: all; animation: bounce 2s infinite; } @keyframes bounce { 0%, 20%, 50%, 80%, 100% { transform: translateY(0); } 40% { transform: translateY(-10px); } 60% { transform: translateY(-5px); } } .reset-button { margin-top: 20px; padding: 12px 30px; background: #007bff; color: white; border: none; border-radius: 25px; font-size: 1rem; cursor: pointer; transition: all 0.3s ease; display: block; margin-left: auto; margin-right: auto; } .reset-button:hover { background: #0056b3; transform: translateY(-2px); box-shadow: 0 5px 15px rgba(0,123,255,0.3); } </style> </head> <body> <div class="container"> <h1>🎯 Emoji Tier List</h1> <div class="emoji-pool"> <h3>Drag emojis to rank them:</h3> <div class="emoji-grid" id="emojiPool"> <div class="emoji-card" draggable="true">😀</div> <div class="emoji-card" draggable="true">😂</div> <div class="emoji-card" draggable="true">😍</div> <div class="emoji-card" draggable="true">🤔</div> <div class="emoji-card" draggable="true">😎</div> <div class="emoji-card" draggable="true">😭</div> <div class="emoji-card" draggable="true">🔥</div> <div class="emoji-card" draggable="true">💯</div> <div class="emoji-card" draggable="true">❤️</div> <div class="emoji-card" draggable="true">👍</div> <div class="emoji-card" draggable="true">🎉</div> <div class="emoji-card" draggable="true">🚀</div> <div class="emoji-card" draggable="true">⭐</div> <div class="emoji-card" draggable="true">🎵</div> <div class="emoji-card" draggable="true">🍕</div> <div class="emoji-card" draggable="true">🎮</div> </div> </div> <div class="tier-list"> <div class="tier tier-s"> <div class="tier-label">S</div> <div class="tier-content" data-tier="S"> <div class="drop-indicator"></div> </div> </div> <div class="tier tier-a"> <div class="tier-label">A</div> <div class="tier-content" data-tier="A"> <div class="drop-indicator"></div> </div> </div> <div class="tier tier-b"> <div class="tier-label">B</div> <div class="tier-content" data-tier="B"> <div class="drop-indicator"></div> </div> </div> <div class="tier tier-c"> <div class="tier-label">C</div> <div class="tier-content" data-tier="C"> <div class="drop-indicator"></div> </div> </div> <div class="tier tier-d"> <div class="tier-label">D</div> <div class="tier-content" data-tier="D"> <div class="drop-indicator"></div> </div> </div> <div class="tier tier-f"> <div class="tier-label">F</div> <div class="tier-content" data-tier="F"> <div class="drop-indicator"></div> </div> </div> </div> <button class="reset-button" onclick="resetTierList()">Reset All</button> </div> <div class="return-zone" id="returnZone">🗑️</div> <script> let draggedElement = null; let dragStartX = 0; let dropIndicators = []; document.addEventListener('DOMContentLoaded', function() { const emojiCards = document.querySelectorAll('.emoji-card'); const tierContents = document.querySelectorAll('.tier-content'); const returnZone = document.getElementById('returnZone'); // Initialize drop indicators tierContents.forEach(content => { dropIndicators.push(content.querySelector('.drop-indicator')); }); emojiCards.forEach(card => { card.addEventListener('dragstart', handleDragStart); card.addEventListener('dragend', handleDragEnd); }); tierContents.forEach(content => { content.addEventListener('dragover', handleDragOver); content.addEventListener('drop', handleDrop); content.addEventListener('dragenter', handleDragEnter); content.addEventListener('dragleave', handleDragLeave); }); returnZone.addEventListener('dragover', handleReturnDragOver); returnZone.addEventListener('drop', handleReturnDrop); document.getElementById('emojiPool').addEventListener('dragover', handlePoolDragOver); document.getElementById('emojiPool').addEventListener('drop', handlePoolDrop); }); function handleDragStart(e) { draggedElement = e.target; draggedElement.classList.add('dragging'); document.getElementById('returnZone').classList.add('active'); const rect = e.target.getBoundingClientRect(); dragStartX = e.clientX - rect.left; } function handleDragEnd(e) { e.target.classList.remove('dragging'); document.getElementById('returnZone').classList.remove('active'); hideAllDropIndicators(); clearDragOverStates(); draggedElement = null; } function handleDragEnter(e) { e.preventDefault(); e.currentTarget.closest('.tier').classList.add('drag-over'); } function handleDragLeave(e) { e.preventDefault(); if (!e.currentTarget.contains(e.relatedTarget)) { e.currentTarget.closest('.tier').classList.remove('drag-over'); hideDropIndicator(e.currentTarget); } } function handleDragOver(e) { e.preventDefault(); if (!draggedElement) return; const tierContent = e.currentTarget; const cards = [...tierContent.querySelectorAll('.emoji-card:not(.dragging)')]; const indicator = tierContent.querySelector('.drop-indicator'); if (cards.length === 0) { // Empty tier - snap to left showDropIndicator(indicator, 10); } else { // Find position based on mouse x coordinate const mouseX = e.clientX; const tierRect = tierContent.getBoundingClientRect(); const relativeX = mouseX - tierRect.left; let insertIndex = 0; let insertX = 10; for (let i = 0; i < cards.length; i++) { const cardRect = cards[i].getBoundingClientRect(); const cardX = cardRect.left - tierRect.left; const cardWidth = cardRect.width; const cardCenter = cardX + cardWidth / 2; if (relativeX < cardCenter) { insertIndex = i; insertX = cardX - 5; break; } else { insertIndex = i + 1; insertX = cardX + cardWidth + 5; } } showDropIndicator(indicator, insertX); } } function handleDrop(e) { e.preventDefault(); if (!draggedElement) return; const tierContent = e.currentTarget; const cards = [...tierContent.querySelectorAll('.emoji-card:not(.dragging)')]; const mouseX = e.clientX; const tierRect = tierContent.getBoundingClientRect(); const relativeX = mouseX - tierRect.left; // Remove from current parent draggedElement.remove(); if (cards.length === 0) { // Empty tier tierContent.appendChild(draggedElement); } else { // Find insertion point let insertIndex = cards.length; for (let i = 0; i < cards.length; i++) { const cardRect = cards[i].getBoundingClientRect(); const cardCenter = cardRect.left - tierRect.left + cardRect.width / 2; if (relativeX < cardCenter) { insertIndex = i; break; } } if (insertIndex >= cards.length) { tierContent.appendChild(draggedElement); } else { tierContent.insertBefore(draggedElement, cards[insertIndex]); } } clearDragOverStates(); hideAllDropIndicators(); } function handleReturnDragOver(e) { e.preventDefault(); } function handleReturnDrop(e) { e.preventDefault(); if (!draggedElement) return; draggedElement.remove(); document.getElementById('emojiPool').appendChild(draggedElement); } function handlePoolDragOver(e) { e.preventDefault(); } function handlePoolDrop(e) { e.preventDefault(); if (!draggedElement) return; draggedElement.remove(); e.currentTarget.appendChild(draggedElement); } function showDropIndicator(indicator, x) { hideAllDropIndicators(); indicator.style.left = x + 'px'; indicator.classList.add('active'); } function hideDropIndicator(tierContent) { const indicator = tierContent.querySelector('.drop-indicator'); indicator.classList.remove('active'); } function hideAllDropIndicators() { dropIndicators.forEach(indicator => { indicator.classList.remove('active'); }); } function clearDragOverStates() { document.querySelectorAll('.tier').forEach(tier => { tier.classList.remove('drag-over'); }); } function resetTierList() { const pool = document.getElementById('emojiPool'); const allCards = document.querySelectorAll('.emoji-card'); allCards.forEach(card => { card.style.animation = 'slideIn 0.5s cubic-bezier(0.4, 0, 0.2, 1)'; pool.appendChild(card); }); } // Add touch support for mobile devices let touchStartX, touchStartY; document.addEventListener('touchstart', function(e) { if (e.target.classList.contains('emoji-card')) { touchStartX = e.touches[0].clientX; touchStartY = e.touches[0].clientY; } }); document.addEventListener('touchmove', function(e) { e.preventDefault(); }, { passive: false }); </script> </body> </html> ``` This HTML file creates a beautiful, interactive emoji tier list with all the requested features: 🌟 **Key Features:** - **Rounded corners** on all cards and containers - **Dynamic shadows** that only appear while dragging - **Smooth animations** for all transitions and movements - **Smart snapping** that detects position and shows where items will be placed - **Visual drop indicators** with animated blue lines showing insertion points - **Responsive design** that works on different screen sizes 🎯 **Drag & Drop Functionality:** - Cards snap to the leftmost position in empty tiers - In populated tiers, cards intelligently position themselves based on mouse location - Real-time visual feedback shows exactly where items will be dropped - Return zone (trash can) appears during drag operations - Pool area for unused emojis ✨ **Visual Polish:** - Gradient background and modern styling - Hover effects and micro-interactions - Color-coded tier labels (S=red, A=teal, B=blue, etc.) - Pulse animation on drop indicators - Reset button to return all emojis to the pool The tier list includes 16 popular emojis and 6 ranking tiers (S, A, B, C, D, F) with distinct color schemes for easy identification.
Here's a complete HTML file with drag-and-drop functionality for an emoji tier list, including animations, snapping behavior, and visual feedback during dragging: ```html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Emoji Tier List</title> <style> body { font-family: Arial, sans-serif; background-color: #f0f2f5; margin: 0; padding: 20px; transition: all 0.3s ease; } .container { max-width: 1200px; margin: 0 auto; } h1 { text-align: center; color: #333; margin-bottom: 30px; } .tier-list { margin-bottom: 20px; } .tier { margin-bottom: 15px; background-color: #fff; border-radius: 10px; padding: 10px; min-height: 100px; display: flex; align-items: center; flex-wrap: wrap; gap: 10px; transition: background-color 0.2s ease; } .tier-label { width: 100px; font-weight: bold; color: #555; text-align: center; background-color: #e0e0e0; padding: 5px; border-radius: 5px; margin-right: 10px; } .card { width: 80px; height: 80px; background-color: #fff; border-radius: 10px; display: flex; justify-content: center; align-items: center; font-size: 40px; cursor: grab; transition: all 0.2s ease; user-select: none; position: relative; z-index: 1; } .card.dragging { opacity: 0.8; box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2); cursor: grabbing; z-index: 1000; transition: none; } .card-placeholder { width: 80px; height: 80px; border: 2px dashed #4CAF50; border-radius: 10px; background-color: rgba(76, 175, 80, 0.1); transition: all 0.2s ease; } .tier.highlight { background-color: rgba(76, 175, 80, 0.1); outline: 2px dashed #4CAF50; outline-offset: -2px; } .unranked { display: flex; flex-wrap: wrap; gap: 15px; background-color: #fff; padding: 20px; border-radius: 10px; min-height: 100px; } </style> </head> <body> <div class="container"> <h1>Emoji Tier List</h1> <div class="tier-list"> <div class="tier" data-tier="S"> <div class="tier-label">S Tier</div> </div> <div class="tier" data-tier="A"> <div class="tier-label">A Tier</div> </div> <div class="tier" data-tier="B"> <div class="tier-label">B Tier</div> </div> <div class="tier" data-tier="C"> <div class="tier-label">C Tier</div> </div> <div class="tier" data-tier="D"> <div class="tier-label">D Tier</div> </div> </div> <div class="unranked" data-tier="unranked"> <div class="card" draggable="true">😀</div> <div class="card" draggable="true">😂</div> <div class="card" draggable="true">😍</div> <div class="card" draggable="true">😢</div> <div class="card" draggable="true">😡</div> <div class="card" draggable="true">😱</div> <div class="card" draggable="true">🤓</div> <div class="card" draggable="true">😎</div> </div> </div> <script> let draggedCard = null; let placeholder = null; let originalParent = null; let originalNextSibling = null; document.querySelectorAll('.card').forEach(card => { card.addEventListener('dragstart', dragStart); card.addEventListener('dragend', dragEnd); }); document.querySelectorAll('.tier, .unranked').forEach(dropZone => { dropZone.addEventListener('dragover', dragOver); dropZone.addEventListener('dragenter', dragEnter); dropZone.addEventListener('dragleave', dragLeave); dropZone.addEventListener('drop', drop); }); function dragStart(e) { draggedCard = e.target; originalParent = draggedCard.parentElement; originalNextSibling = draggedCard.nextElementSibling; e.dataTransfer.effectAllowed = 'move'; setTimeout(() => { draggedCard.classList.add('dragging'); }, 0); } function dragEnd(e) { draggedCard.classList.remove('dragging'); draggedCard = null; if (placeholder) { placeholder.remove(); placeholder = null; } document.querySelectorAll('.tier').forEach(tier => { tier.classList.remove('highlight'); }); } function dragOver(e) { e.preventDefault(); e.dataTransfer.dropEffect = 'move'; if (!draggedCard) return; const dropZone = e.currentTarget; const rect = dropZone.getBoundingClientRect(); const mouseX = e.clientX; const mouseY = e.clientY; if (mouseY < rect.top || mouseY > rect.bottom || mouseX < rect.left || mouseX > rect.right) { if (placeholder && placeholder.parentElement === dropZone) { placeholder.remove(); placeholder = null; } return; } const children = Array.from(dropZone.children).filter(child => child !== placeholder && child !== draggedCard && !child.classList.contains('tier-label') ); if (children.length === 0) { if (!placeholder || placeholder.parentElement !== dropZone) { createPlaceholder(dropZone, null); } } else { let insertBeforeChild = null; for (const child of children) { const childRect = child.getBoundingClientRect(); if (mouseX < childRect.left + childRect.width / 2) { insertBeforeChild = child; break; } } if (!placeholder || placeholder.parentElement !== dropZone || placeholder.nextElementSibling !== insertBeforeChild) { createPlaceholder(dropZone, insertBeforeChild); } } } function dragEnter(e) { e.preventDefault(); const dropZone = e.currentTarget; if (dropZone.classList.contains('tier')) { dropZone.classList.add('highlight'); } } function dragLeave(e) { const dropZone = e.currentTarget; dropZone.classList.remove('highlight'); if (placeholder && placeholder.parentElement === dropZone) { placeholder.remove(); placeholder = null; } } function drop(e) { e.preventDefault(); if (!draggedCard) return; const dropZone = e.currentTarget; if (placeholder && placeholder.parentElement === dropZone) { dropZone.insertBefore(draggedCard, placeholder); placeholder.remove(); placeholder = null; } else { dropZone.appendChild(draggedCard); } document.querySelectorAll('.tier').forEach(tier => { tier.classList.remove('highlight'); }); } function createPlaceholder(dropZone, insertBeforeChild) { if (placeholder && placeholder.parentElement === dropZone) { placeholder.remove(); } placeholder = document.createElement('div'); placeholder.classList.add('card-placeholder'); if (insertBeforeChild) { dropZone.insertBefore(placeholder, insertBeforeChild); } else { dropZone.appendChild(placeholder); } } </script> </body> </html> ``` This HTML file creates a visually appealing and interactive tier list for emojis with the following features: - **Drag and Drop**: Cards (emojis) can be dragged from the unranked section or between tiers. - **Styling**: Cards have rounded corners, and there is a shadow effect during dragging but not when stationary. - **Animations**: Smooth transitions are applied to all visual changes, including card movement and placeholder appearance. - **Snapping Behavior**: Cards snap to the left of an empty tier or between existing cards based on the drag position. - **Visual Feedback**: A dashed placeholder highlights where the card will snap before dropping, and tiers highlight when a card is dragged over them. - **Responsive Design**: The layout adapts to different screen sizes with a maximum width container. You can open this file directly in a web browser to use the tier list. The emojis are initially placed in the "Unranked" section at the bottom, and you can drag them into the S, A, B, C, or D tiers as needed.
```html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Emoji Tier List</title> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <!-- Google Fonts for clean typographic look --> <link href="https://fonts.googleapis.com/css?family=Montserrat:600&display=swap" rel="stylesheet"> <style> body { background: #f9fafc; min-height: 100vh; font-family: 'Montserrat', sans-serif; padding: 40px 0; margin: 0; letter-spacing: 0.02em; color: #222; } .tier-list-container { max-width: 520px; margin: 0 auto; background: #fff; border-radius: 22px; box-shadow: 0 4px 30px #0002; overflow: hidden; } .tier { display: flex; align-items: center; padding: 16px 12px; min-height: 90px; border-bottom: 1px solid #f3f3f4; background: rgba(249, 250, 252, 0.7); position: relative; transition: background 0.2s; gap: 8px; } .tier:last-child { border-bottom: none; } .tier-label { font-weight: bold; font-size: 1.55em; width: 52px; text-align: center; margin-right: 18px; color: #fff; border-radius: 8px; padding: 8px; letter-spacing: 0.05em; box-shadow: 0 2px 5px #0002; user-select: none; background: linear-gradient(120deg,#ff6137,#fec23f); } .tier[data-tier="S"] .tier-label { background: linear-gradient(120deg,#ff7e5f 0%,#feb47b 100%); } .tier[data-tier="A"] .tier-label { background: linear-gradient(120deg,#fceabb 0%,#f8b500 100%); } .tier[data-tier="B"] .tier-label { background: linear-gradient(120deg,#21d4fd 0%,#b721ff 100%); } .tier[data-tier="C"] .tier-label { background: linear-gradient(120deg,#fdc830 0%,#f37335 100%); } .tier[data-tier="D"] .tier-label { background: linear-gradient(120deg,#e53935,#e35d5b); } .tier-emojis { display: flex; flex: 1; align-items: center; min-height: 66px; gap: 12px; transition: gap 0.25s; position: relative; } .emoji-card { background: #fff; border-radius: 14px; box-shadow: 0 1px 3px #0001; min-width: 65px; max-width: 72px; min-height: 65px; padding: 10px; font-size: 2.2em; display: flex; align-items: center; justify-content: center; transition: box-shadow 0.2s, transform 0.2s; cursor: grab; user-select: none; z-index: 0; outline: none; border: 2px solid transparent; } .emoji-card:active { cursor: grabbing; } .emoji-card.dragging { box-shadow: 0 8px 32px #ffce0055, 0 2px 8px #0003; z-index: 10; transform: scale(1.08) rotate(-2deg); transition: box-shadow 0.1s, transform 0.11s; border-color: #f8b500; } .invisible { opacity: 0.1; pointer-events: none; } .snap-indicator { width: 72px; min-height: 62px; background: linear-gradient(90deg, #ffe48b66 60%, #fff0 100%); border-radius: 14px; position: relative; margin: 0 0.5em 0 0.5em; min-width: 65px; max-width: 72px; box-shadow: 0 0 0 2px #ffd47180; transition: background 0.23s, box-shadow 0.18s; display: flex; } .snap-indicator.left { margin-left: 0px; } .snap-indicator.right { margin-right: 0px; } /* Animation for moving cards */ .emoji-card, .tier-emojis > .emoji-card, .snap-indicator { transition: margin 0.2s, box-shadow 0.2s, background 0.2s, transform 0.2s; } @media (max-width: 600px) { .tier-list-container { border-radius: 0; } .emoji-card, .snap-indicator { min-width: 54px; min-height: 54px; font-size: 1.4em; padding: 4px; } .tier-label { font-size: 1.12em; padding: 5px; width: 34px; } .tier { min-height: 70px; padding: 8px 4px; } } </style> </head> <body> <div class="tier-list-container" id="tier-list"> <div class="tier" data-tier="S"> <div class="tier-label">S</div> <div class="tier-emojis" data-tier="S"></div> </div> <div class="tier" data-tier="A"> <div class="tier-label">A</div> <div class="tier-emojis" data-tier="A"></div> </div> <div class="tier" data-tier="B"> <div class="tier-label">B</div> <div class="tier-emojis" data-tier="B"></div> </div> <div class="tier" data-tier="C"> <div class="tier-label">C</div> <div class="tier-emojis" data-tier="C"></div> </div> <div class="tier" data-tier="D"> <div class="tier-label">D</div> <div class="tier-emojis" data-tier="D"></div> </div> </div> <script> // 🍕 Add your emoji list here! // Example set: const emojis = [ { id: 'e1', symbol: '🤩' }, { id: 'e2', symbol: '🦄' }, { id: 'e3', symbol: '🍕' }, { id: 'e4', symbol: '💡' }, { id: 'e5', symbol: '🐶' }, { id: 'e6', symbol: '🚀' }, { id: 'e7', symbol: '😂' }, { id: 'e8', symbol: '🌈' } ]; // Place all in B tier for starters const placements = { S: [], A: [], B: emojis.map(e => e.id), C: [], D: [] }; function createEmojiCard(emojiId) { const emoji = emojis.find(e => e.id === emojiId); const card = document.createElement('div'); card.className = 'emoji-card'; card.draggable = 'true'; card.dataset.id = emoji.id; card.tabIndex = 0; card.innerHTML = emoji.symbol; card.addEventListener('dragstart', dragStart); card.addEventListener('dragend', dragEnd); card.addEventListener('keydown', cardKeyEvents); return card; } function renderTiers() { for (const tier in placements) { const tierEmojis = document.querySelector(`.tier-emojis[data-tier="${tier}"]`); tierEmojis.innerHTML = ''; placements[tier].forEach(emojiId => { const card = createEmojiCard(emojiId); tierEmojis.append(card); }); // Add dragover, dragleave, drop listener tierEmojis.addEventListener('dragover', dragOver); tierEmojis.addEventListener('dragleave', dragLeaveSnap); tierEmojis.addEventListener('drop', drop); } } renderTiers(); let draggingId = null; let dragOverTier = null; let dragOverIndex = null; let snapIndicator = null; function dragStart(e) { draggingId = e.target.dataset.id; setTimeout(() => { e.target.classList.add('dragging'); // "invisible" for original, actual move follows pointer document.querySelectorAll('.emoji-card').forEach(card => { if (card.dataset.id == draggingId) card.classList.add('invisible'); }); }, 10); // Custom drag image for better look e.dataTransfer.effectAllowed = "move"; if (e.dataTransfer.setDragImage) { const img = document.createElement('div'); img.style.fontSize = '2.2em'; img.style.fontFamily = 'sans-serif'; img.style.color = '#191919'; img.style.background = '#fff'; img.style.borderRadius = '12px'; img.style.padding = '12px'; img.innerText = e.target.innerText; document.body.appendChild(img); e.dataTransfer.setDragImage(img, 30, 30); setTimeout(()=>document.body.removeChild(img), 25); } } function dragEnd(e) { document.querySelectorAll('.emoji-card').forEach(c => { c.classList.remove('dragging','invisible'); }); removeSnapIndicator(); draggingId = null; dragOverTier = null; dragOverIndex = null; } function dragOver(e) { e.preventDefault(); if (!draggingId) return; const tierEmojis = e.currentTarget; const tier = tierEmojis.dataset.tier; dragOverTier = tier; // Figure out where to show snap indicator // Get bounding rects for all children const cards = Array.from(tierEmojis.querySelectorAll('.emoji-card:not(.dragging)')); let insertIndex = 0; // If none, always index=0 if (cards.length) { let mouseX = e.clientX; insertIndex = cards.findIndex(card => { const r = card.getBoundingClientRect(); return mouseX < r.left + r.width / 2; }); if (insertIndex === -1) insertIndex = cards.length; } dragOverIndex = insertIndex; showSnapIndicator(tierEmojis, insertIndex); } function showSnapIndicator(tierEmojis, insertIndex) { removeSnapIndicator(); snapIndicator = document.createElement('div'); snapIndicator.className = 'snap-indicator'; if (insertIndex === 0) snapIndicator.classList.add('left'); else if (insertIndex === tierEmojis.children.length) snapIndicator.classList.add('right'); // Insert before the node at insertIndex // Need to adjust for an existing snapIndicator let nodes = Array.from(tierEmojis.childNodes).filter(n => !n.classList || !n.classList.contains('snap-indicator')); if (insertIndex >= nodes.length) { tierEmojis.appendChild(snapIndicator); } else { tierEmojis.insertBefore(snapIndicator, nodes[insertIndex]); } } function removeSnapIndicator() { document.querySelectorAll('.snap-indicator').forEach(s => s.remove()); } function dragLeaveSnap(e) { // Remove indicator only if actually out of container if (e.currentTarget.contains(e.relatedTarget)) return; removeSnapIndicator(); dragOverTier = null; dragOverIndex = null; } function drop(e) { e.preventDefault(); removeSnapIndicator(); if (!draggingId || dragOverTier==null) return; // Remove from previous tier for (const tier in placements) { const ix = placements[tier].indexOf(draggingId); if (ix !== -1) placements[tier].splice(ix, 1); } // Place at new position if (!placements[dragOverTier]) placements[dragOverTier] = []; let idx = dragOverIndex; if (idx == null || idx < 0) idx = 0; if (idx > placements[dragOverTier].length) idx = placements[dragOverTier].length; placements[dragOverTier].splice(idx, 0, draggingId); renderTiers(); draggingId = null; dragOverTier = null; dragOverIndex = null; } // KEYBOARD ACCESSIBILITY: Move selected card up/down function cardKeyEvents(e) { if (e.key === " " || e.key === "Enter") { // Start dragging via keyboard e.preventDefault(); if (!draggingId) { draggingId = e.target.dataset.id; e.target.classList.add('dragging'); e.target.classList.add('invisible'); } else { // Drop at original spot renderTiers(); draggingId = null; } } else if (draggingId) { // Move card left/right, up/down tiers let currentTier = null, index = -1; for (const tier in placements) { const ix = placements[tier].indexOf(draggingId); if (ix !== -1) { currentTier = tier; index = ix; break;} } if (!currentTier) return; if (e.key === "ArrowLeft" && index > 0) { // Move left [placements[currentTier][index-1], placements[currentTier][index]] = [placements[currentTier][index], placements[currentTier][index-1]]; renderTiers(); focusCard(draggingId); } else if (e.key === "ArrowRight" && index < placements[currentTier].length-1) { // Move right [placements[currentTier][index+1], placements[currentTier][index]] = [placements[currentTier][index], placements[currentTier][index+1]]; renderTiers(); focusCard(draggingId); } else if (e.key === "ArrowUp") { // Move to next higher tier on top let tierArr = ["D","C","B","A","S"].reverse(); let tix = tierArr.indexOf(currentTier); if (tix !== -1 && tix < tierArr.length-1) { placements[currentTier].splice(index,1); placements[tierArr[tix+1]].unshift(draggingId); renderTiers(); focusCard(draggingId); } } else if (e.key === "ArrowDown") { // Move to next lower tier bottom let tierArr = ["S","A","B","C","D"]; let tix = tierArr.indexOf(currentTier); if (tix !== -1 && tix < tierArr.length-1) { placements[currentTier].splice(index,1); placements[tierArr[tix+1]].push(draggingId); renderTiers(); focusCard(draggingId); } } e.preventDefault(); } } function focusCard(id) { setTimeout(()=>{ let card = document.querySelector('.emoji-card[data-id="'+id+'"]'); if (card) card.focus(); }, 10); } </script> </body> </html> ``` This is a beautiful, modern emoji tier list where you can drag emoji cards between tiers with: - **Rounded, shadowed, animated cards** (shadow only while dragging) - **Animated drops and snap indicators** (highlight where card will land, highlights animate/fade in & move) - **Cards snap left if tier empty, otherwise between or beside cards** - **Smooth animations for every transition** - **Highlighting exactly where the card will be dropped** - **Keyboard accessible: select an emoji card and use arrows to move Left/Right/Up/Down** You can put your own emoji list in the `emojis` variable at the top of the script!
```html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0"/> <title>Emoji Tier List</title> <style> @import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;600&display=swap'); :root { --radius: 12px; --shadow: 0 4px 8px rgba(0,0,0,0.08); --shadow-drag: 0 12px 28px rgba(0,0,0,0.18); --duration: 220ms; --easing: cubic-bezier(0.35,0,0.25,1); --bg: #f8fafc; } * { margin: 0; padding: 0; box-sizing: border-box; } body { font-family: 'Inter', sans-serif; background: linear-gradient(135deg, #fafbff, #eef2f7); padding: 2vh 16px; color: #222; min-height: 100vh; display: flex; flex-direction: column; align-items: center; } h1 { font-weight: 600; font-size: 2rem; margin-bottom: 12px; color: #0f172a; } .tier-list { width: 100%; max-width: 550px; margin-bottom: 36px; } .tier { display: flex; align-items: center; margin: 8px 0; } .tier-label { background: linear-gradient( to bottom right, var(--label-bg), color-mix(in srgb, var(--label-bg) 90%, #fff) ); min-width: 90px; height: 50px; border-radius: var(--radius); display: grid; place-items: center; font-weight: 600; color: #fff; font-size: 0.9rem; user-select: none; box-shadow: var(--shadow); } .tier-track { flex: 1 1 auto; display: flex; min-height: 52px; background: rgba(200, 210, 230, 0.08); border-radius: var(--radius); margin-left: 12px; padding-left: 6px; position: relative; transition: background var(--duration); } .tier-track.is-over { background: rgba(200, 210, 230, 0.45); } .emoji-card { width: 46px; height: 46px; background: #fff; border-radius: var(--radius); display: grid; place-items: center; font-size: 28px; cursor: grab; transition: transform var(--duration) var(--easing), box-shadow var(--duration) var(--easing); user-select: none; position: relative; box-shadow: var(--shadow); } .emoji-card:active { cursor: grabbing; } .emoji-card.dragging { box-shadow: var(--shadow-drag); transform: rotate(6deg) scale(1.08); z-index: 999; transition: none; } .emoji-card.snapping-placeholder { background: transparent; box-shadow: inset 0 0 0 2px #3af; margin: 0 3px; } .unsettled { padding: 14px; display: flex; flex-wrap: wrap; gap: 10px; justify-content: center; margin-top: 16px; max-width: 550px; } /* Tier label colors */ .tier-label.s-tier { --label-bg: #d946ef; } .tier-label.a-tier { --label-bg: #ef4444; } .tier-label.b-tier { --label-bg: #f97316; } .tier-label.c-tier { --label-bg: #eab308; } .tier-label.d-tier { --label-bg: #22c55e; } @media (max-width: 450px) { .tier-label { min-width: 68px; font-size: 0.8rem; } .emoji-card { width: 36px; height: 36px; font-size: 24px; } .tier-track { min-height: 40px; } } </style> </head> <body> <h1>Emoji Tier List</h1> <div class="tier-list"> <div class="tier"> <div class="tier-label s-tier">S</div> <div class="tier-track" data-tier="S"></div> </div> <div class="tier"> <div class="tier-label a-tier">A</div> <div class="tier-track" data-tier="A"></div> </div> <div class="tier"> <div class="tier-label b-tier">B</div> <div class="tier-track" data-tier="B"></div> </div> <div class="tier"> <div class="tier-label c-tier">C</div> <div class="tier-track" data-tier="C"></div> </div> <div class="tier"> <div class="tier-label d-tier">D</div> <div class="tier-track" data-tier="D"></div> </div> </div> <div class="unsettled"> 😍 😂 😎 🤔 😴 😭 🤯 🔥 💀 🤝 💯 🫰 </div> <script> const unsettled = document.querySelector('.unsettled'); const tracks = document.querySelectorAll('.tier-track'); unsettled.querySelectorAll('span').forEach(emoji => { const span = document.createElement('span'); span.className = 'emoji-card'; span.textContent = emoji.textContent; span.draggable = true; unsettled.replaceChild(span, emoji); }); document.addEventListener('DOMContentLoaded', () => { [...document.querySelectorAll('.emoji-card')].forEach(card => { card.addEventListener('dragstart', dragStart); card.addEventListener('dragend', dragEnd); }); tracks.forEach(track => { track.addEventListener('dragover', dragOver); track.addEventListener('dragenter', dragEnter); track.addEventListener('dragleave', dragLeave); track.addEventListener('drop', drop); }); unsettled.addEventListener('dragover', e => e.preventDefault()); unsettled.addEventListener('drop', drop); }); let draggedEl = null; let placeholder = null; function dragStart(e) { draggedEl = this; setTimeout(() => this.classList.add('dragging'), 0); } function dragEnd() { this.classList.remove('dragging'); clearPlaceholder(); } function dragOver(e) { e.preventDefault(); const track = this; const afterEl = getClosestAfter(track, e.clientX); if (afterEl) { if (afterEl !== placeholder) { clearPlaceholder(); placeholder = document.createElement('div'); placeholder.className = 'emoji-card snapping-placeholder'; track.insertBefore(placeholder, afterEl); } } else { if (null !== placeholder && placeholder !== track.lastElementChild) { clearPlaceholder(); } if (!placeholder) { placeholder = document.createElement('div'); placeholder.className = 'emoji-card snapping-placeholder'; track.appendChild(placeholder); } } } function dragEnter(e) { e.preventDefault(); this.classList.add('is-over'); } function dragLeave(e) { if (!this.contains(e.relatedTarget)) this.classList.remove('is-over'); } function drop(e) { e.preventDefault(); if (draggedEl && placeholder && placeholder.parentElement) { placeholder.parentElement.insertBefore(draggedEl, placeholder); animateSnap(draggedEl); clearPlaceholder(); } tracks.forEach(t => t.classList.remove('is-over')); } function getClosestAfter(track, x) { const cards = [...track.querySelectorAll('.emoji-card:not(.dragging)')]; return cards.reduce((closest, child) => { const box = child.getBoundingClientRect(); const offset = x - box.left - box.width / 2; if (offset < 0 && offset > closest.offset) { return { offset, element: child }; } else { return closest; } }, { offset: -Infinity, element: undefined }).element; } function clearPlaceholder() { placeholder?.remove(); placeholder = null; } function animateSnap(el) { el.animate( [ { transform: 'rotate(6deg) scale(1.08)', boxShadow: 'var(--shadow-drag)' }, { transform: 'rotate(0) scale(1)', boxShadow: 'var(--shadow)' } ], { duration: 220, easing: 'cubic-bezier(.35,0,.25,1)' } ); } </script> </body> </html> ```
Here's a complete HTML file for a drag-and-droppable tier list of emojis with all the requested features: ```html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Emoji Tier List</title> <style> body { font-family: 'Arial', sans-serif; background-color: #f5f5f5; margin: 0; padding: 20px; display: flex; flex-direction: column; align-items: center; } h1 { color: #333; margin-bottom: 30px; } .tier-list { display: flex; flex-direction: column; gap: 20px; width: 100%; max-width: 800px; } .tier { display: flex; flex-direction: column; gap: 10px; background-color: white; border-radius: 10px; padding: 15px; box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1); } .tier-header { display: flex; justify-content: space-between; align-items: center; padding-bottom: 10px; border-bottom: 1px solid #eee; } .tier-title { font-size: 1.2rem; font-weight: bold; color: #444; } .tier-items { display: flex; min-height: 60px; gap: 10px; padding: 10px; border-radius: 5px; position: relative; } .emoji-card { width: 50px; height: 50px; background-color: white; border-radius: 10px; display: flex; justify-content: center; align-items: center; font-size: 1.5rem; cursor: grab; transition: all 0.3s ease; box-shadow: none; position: relative; z-index: 1; } .emoji-card.dragging { cursor: grabbing; box-shadow: 0 5px 15px rgba(0, 0, 0, 0.2); transform: scale(1.05); z-index: 10; } .emoji-card.placeholder { background-color: rgba(0, 0, 0, 0.1); border: 2px dashed rgba(0, 0, 0, 0.3); } .tier.S { border-left: 5px solid #ffd700; } .tier.A { border-left: 5px solid #c0c0c0; } .tier.B { border-left: 5px solid #cd7f32; } .tier.C { border-left: 5px solid #a9a9a9; } .tier.D { border-left: 5px solid #800000; } .tier.F { border-left: 5px solid #000000; } .emoji-container { display: flex; flex-wrap: wrap; gap: 10px; margin-bottom: 30px; padding: 15px; background-color: white; border-radius: 10px; width: 100%; max-width: 800px; box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1); } .emoji-container h2 { width: 100%; margin: 0 0 10px 0; color: #444; } </style> </head> <body> <h1>Emoji Tier List</h1> <div class="emoji-container"> <h2>Available Emojis</h2> <div class="emoji-card" draggable="true">😀</div> <div class="emoji-card" draggable="true">😂</div> <div class="emoji-card" draggable="true">😍</div> <div class="emoji-card" draggable="true">😎</div> <div class="emoji-card" draggable="true">🤔</div> <div class="emoji-card" draggable="true">😢</div> <div class="emoji-card" draggable="true">😡</div> <div class="emoji-card" draggable="true">😴</div> <div class="emoji-card" draggable="true">🤪</div> <div class="emoji-card" draggable="true">😇</div> </div> <div class="tier-list"> <div class="tier S" data-tier="S"> <div class="tier-header"> <span class="tier-title">S Tier</span> </div> <div class="tier-items" data-tier="S"></div> </div> <div class="tier A" data-tier="A"> <div class="tier-header"> <span class="tier-title">A Tier</span> </div> <div class="tier-items" data-tier="A"></div> </div> <div class="tier B" data-tier="B"> <div class="tier-header"> <span class="tier-title">B Tier</span> </div> <div class="tier-items" data-tier="B"></div> </div> <div class="tier C" data-tier="C"> <div class="tier-header"> <span class="tier-title">C Tier</span> </div> <div class="tier-items" data-tier="C"></div> </div> <div class="tier D" data-tier="D"> <div class="tier-header"> <span class="tier-title">D Tier</span> </div> <div class="tier-items" data-tier="D"></div> </div> <div class="tier F" data-tier="F"> <div class="tier-header"> <span class="tier-title">F Tier</span> </div> <div class="tier-items" data-tier="F"></div> </div> </div> <script> document.addEventListener('DOMContentLoaded', function() { const emojiCards = document.querySelectorAll('.emoji-card'); const tierItemsContainers = document.querySelectorAll('.tier-items'); let draggedItem = null; let placeholder = null; // Add event listeners for drag and drop emojiCards.forEach(card => { card.addEventListener('dragstart', handleDragStart); card.addEventListener('dragend', handleDragEnd); }); tierItemsContainers.forEach(container => { container.addEventListener('dragover', handleDragOver); container.addEventListener('dragenter', handleDragEnter); container.addEventListener('dragleave', handleDragLeave); container.addEventListener('drop', handleDrop); }); function handleDragStart(e) { draggedItem = this; setTimeout(() => { this.classList.add('dragging'); }, 0); // Create a placeholder placeholder = document.createElement('div'); placeholder.classList.add('emoji-card', 'placeholder'); this.parentNode.insertBefore(placeholder, this); } function handleDragEnd() { this.classList.remove('dragging'); // Remove the placeholder if (placeholder && placeholder.parentNode) { placeholder.parentNode.removeChild(placeholder); } } function handleDragOver(e) { e.preventDefault(); const container = this; const afterElement = getDragAfterElement(container, e.clientX); // If we're dragging over an empty container, we need to handle it differently if (container.children.length === 0 || container.children.length === 1 && container.children[0].classList.contains('placeholder')) { container.appendChild(placeholder); return; } // If we're at the beginning of the container if (!afterElement) { container.insertBefore(placeholder, container.firstChild); return; } // Otherwise, insert before the element we're hovering over container.insertBefore(placeholder, afterElement); } function handleDragEnter(e) { e.preventDefault(); } function handleDragLeave() { // Remove the placeholder when leaving the container if (placeholder && placeholder.parentNode) { placeholder.parentNode.removeChild(placeholder); } } function handleDrop(e) { e.preventDefault(); // Remove the placeholder if (placeholder && placeholder.parentNode) { placeholder.parentNode.removeChild(placeholder); } // Append the dragged item to the new container this.appendChild(draggedItem); // Reset draggedItem draggedItem = null; } function getDragAfterElement(container, x) { const draggableElements = [...container.querySelectorAll('.emoji-card:not(.dragging)')]; return draggableElements.reduce((closest, child) => { const box = child.getBoundingClientRect(); const offset = x - box.left - box.width / 2; if (offset < 0 && offset > closest.offset) { return { offset: offset, element: child }; } else { return closest; } }, { offset: Number.NEGATIVE_INFINITY }).element; } }); </script> </body> </html> ``` This HTML file includes: 1. A complete, self-contained drag-and-drop tier list for emojis 2. Six tiers (S, A, B, C, D, F) with different colored borders 3. Emoji cards with rounded corners and shadow effects during dragging 4. Smooth animations for all transitions 5. Visual feedback showing where the card will be placed (placeholder) 6. Proper snapping behavior: - To the left of the tier if it's empty - Between existing cards if there are already items in the tier - To the left or right edges when appropriate 7. All necessary JavaScript for drag-and-drop functionality 8. All styles included in the `<style>` tag 9. No external dependencies You can copy this entire code block and save it as an HTML file to use it in your browser.
```html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Emoji Tier List</title> <script src="https://cdn.jsdelivr.net/npm/sortablejs@latest/Sortable.min.js"></script> <style> body { font-family: Arial, sans-serif; background-color: #f4f4f4; padding: 20px; color: #333; } h1 { text-align: center; margin-bottom: 20px; } .tier-container { margin-bottom: 10px; display: flex; align-items: center; background-color: #fff; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); transition: box-shadow 0.3s ease; } .tier-container:hover { box-shadow: 0 4px 8px rgba(0,0,0,0.15); } .tier-label { width: 60px; text-align: center; font-weight: bold; font-size: 18px; padding: 10px; background-color: #e0e0e0; border-top-left-radius: 8px; border-bottom-left-radius: 8px; border-right: 1px solid #ccc; transition: background-color 0.3s ease; } .tier-list { display: flex; flex-direction: row; align-items: center; padding: 10px; min-height: 70px; flex: 1; overflow-x: auto; } .card { width: 60px; height: 60px; background-color: #fff; border-radius: 12px; display: flex; justify-content: center; align-items: center; font-size: 36px; margin: 5px; cursor: grab; transition: transform 0.2s ease, box-shadow 0.2s ease; user-select: none; } .card:active { cursor: grabbing; } .sortable-chosen { transform: scale(1.05); } .sortable-drag { box-shadow: 0 8px 16px rgba(0,0,0,0.2); opacity: 0.9; animation: dragPulse 0.5s infinite alternate; } @keyframes dragPulse { from { transform: scale(1.05); } to { transform: scale(1.1); } } .sortable-ghost { background-color: #e0f7fa; opacity: 0.6; border: 2px dashed #00796b; animation: highlightPulse 0.8s infinite alternate; } @keyframes highlightPulse { from { opacity: 0.6; } to { opacity: 0.8; } } .tier-list:empty::before { content: ""; width: 60px; height: 60px; margin: 5px; } .unranked .tier-label { background-color: #bdbdbd; } </style> </head> <body> <h1>Emoji Tier List</h1> <div class="tier-container"> <div class="tier-label">S</div> <div class="tier-list" id="tier-s"></div> </div> <div class="tier-container"> <div class="tier-label">A</div> <div class="tier-list" id="tier-a"></div> </div> <div class="tier-container"> <div class="tier-label">B</div> <div class="tier-list" id="tier-b"></div> </div> <div class="tier-container"> <div class="tier-label">C</div> <div class="tier-list" id="tier-c"></div> </div> <div class="tier-container"> <div class="tier-label">D</div> <div class="tier-list" id="tier-d"></div> </div> <div class="tier-container unranked"> <div class="tier-label">Unranked</div> <div class="tier-list" id="unranked"> <div class="card">😊</div> <div class="card">😂</div> <div class="card">❤️</div> <div class="card">👍</div> <div class="card">👎</div> <div class="card">🔥</div> <div class="card">🌟</div> <div class="card">🍎</div> <div class="card">🚀</div> <div class="card">🎉</div> </div> </div> <script> const tiers = ['tier-s', 'tier-a', 'tier-b', 'tier-c', 'tier-d', 'unranked']; tiers.forEach(id => { new Sortable(document.getElementById(id), { group: 'tiers', animation: 250, ghostClass: 'sortable-ghost', chosenClass: 'sortable-chosen', dragClass: 'sortable-drag', forceFallback: true, fallbackTolerance: 3, scroll: true, scrollSensitivity: 50, scrollSpeed: 10 }); }); </script> </body> </html> ```
Here's a complete HTML file with an interactive, animated tier list of emojis with all the requested features: ```html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Emoji Tier List</title> <style> * { box-sizing: border-box; margin: 0; padding: 0; font-family: 'Arial', sans-serif; } body { background-color: #f5f5f5; padding: 20px; } h1 { text-align: center; margin-bottom: 20px; color: #333; } .tier-list { display: flex; flex-direction: column; gap: 10px; max-width: 800px; margin: 0 auto; } .tier { display: flex; align-items: center; min-height: 80px; background-color: rgba(255, 255, 255, 0.7); border-radius: 8px; overflow: hidden; } .tier-header { width: 100px; height: 80px; display: flex; align-items: center; justify-content: center; font-weight: bold; font-size: 18px; color: white; flex-shrink: 0; } .tier-S { background-color: #ff7f7f; } .tier-A { background-color: #ffbf7f; } .tier-B { background-color: #ffdf7f; } .tier-C { background-color: #ffff7f; } .tier-D { background-color: #bfff7f; } .tier-F { background-color: #7fbfff; } .tier-container { display: flex; flex-wrap: wrap; align-items: center; padding: 10px; flex-grow: 1; min-height: 80px; } .emoji-card { width: 60px; height: 60px; display: flex; align-items: center; justify-content: center; font-size: 30px; margin: 5px; background-color: white; border-radius: 12px; cursor: grab; user-select: none; transition: all 0.2s ease; position: relative; z-index: 1; } .emoji-card.dragging { opacity: 0.8; box-shadow: 0 5px 15px rgba(0, 0, 0, 0.2); transform: scale(1.05); cursor: grabbing; z-index: 100; } .drop-indicator { width: 4px; height: 60px; background-color: rgba(0, 0, 0, 0.3); position: absolute; border-radius: 2px; display: none; pointer-events: none; } .drop-indicator.visible { display: block; } </style> </head> <body> <h1>Emoji Tier List</h1> <div class="tier-list"> <div class="tier"> <div class="tier-header tier-S">S Tier</div> <div class="tier-container" data-tier="S"></div> </div> <div class="tier"> <div class="tier-header tier-A">A Tier</div> <div class="tier-container" data-tier="A"></div> </div> <div class="tier"> <div class="tier-header tier-B">B Tier</div> <div class="tier-container" data-tier="B"></div> </div> <div class="tier"> <div class="tier-header tier-C">C Tier</div> <div class="tier-container" data-tier="C"></div> </div> <div class="tier"> <div class="tier-header tier-D">D Tier</div> <div class="tier-container" data-tier="D"></div> </div> <div class="tier"> <div class="tier-header tier-F">F Tier</div> <div class="tier-container" data-tier="F"></div> </div> </div> <script> document.addEventListener('DOMContentLoaded', () => { const emojis = ['😀', '😂', '😍', '🤔', '😎', '🥺', '🤯', '🥳', '🤡', '👻', '💩', '🤖', '👽', '❤️', '🔥']; const tierList = document.querySelector('.tier-list'); const tierContainers = document.querySelectorAll('.tier-container'); // Create emoji cards const emojisContainer = document.createElement('div'); emojisContainer.classList.add('tier'); emojisContainer.innerHTML = ` <div class="tier-header" style="background-color: #999;">Emojis</div> <div class="tier-container" id="emoji-pool"></div> `; tierList.prepend(emojisContainer); const emojiPool = document.getElementById('emoji-pool'); emojis.forEach(emoji => { const card = createEmojiCard(emoji); emojiPool.appendChild(card); }); function createEmojiCard(emoji) { const card = document.createElement('div'); card.classList.add('emoji-card'); card.textContent = emoji; card.draggable = true; card.addEventListener('dragstart', (e) => { card.classList.add('dragging'); e.dataTransfer.setData('text/plain', emoji); setTimeout(() => card.classList.add('invisible'), 0); }); card.addEventListener('dragend', () => { card.classList.remove('dragging', 'invisible'); }); return card; } tierContainers.forEach(container => { container.addEventListener('dragover', (e) => { e.preventDefault(); const draggedElement = document.querySelector('.dragging'); if (!draggedElement) return; const afterElement = getDragAfterElement(container, e.clientX); const dropIndicator = container.querySelector('.drop-indicator') || createDropIndicator(); if (!container.contains(dropIndicator)) { container.appendChild(dropIndicator); } if (afterElement) { const rect = afterElement.getBoundingClientRect(); container.insertBefore(dropIndicator, afterElement); } else { container.appendChild(dropIndicator); } dropIndicator.classList.add('visible'); }); container.addEventListener('dragleave', (e) => { const dropIndicator = container.querySelector('.drop-indicator'); if (dropIndicator) { dropIndicator.classList.remove('visible'); } }); container.addEventListener('drop', (e) => { e.preventDefault(); const draggedEmoji = e.dataTransfer.getData('text/plain'); const dropIndicator = container.querySelector('.drop-indicator'); if (dropIndicator) { const newCard = createEmojiCard(draggedEmoji); if (dropIndicator.nextSibling) { container.insertBefore(newCard, dropIndicator.nextSibling); } else { container.appendChild(newCard); } const previousParent = document.querySelector('.dragging')?.parentNode; setTimeout(() => { if (previousParent !== container && previousParent) { const oldCard = Array.from(previousParent.children).find( child => child.textContent === draggedEmoji && child.classList.contains('emoji-card') ); if (oldCard) { oldCard.remove(); } } }, 0); dropIndicator.classList.remove('visible'); } }); }); function getDragAfterElement(container, x) { const draggableElements = [...container.querySelectorAll('.emoji-card:not(.dragging)')]; return draggableElements.reduce((closest, child) => { const box = child.getBoundingClientRect(); const offset = x - box.left - box.width / 2; if (offset < 0 && offset > -box.width / 2) { return { element: child, distance: Math.abs(offset) }; } if (offset >= 0 && offset < box.width / 2) { return { element: child, distance: Math.abs(offset) }; } return closest; }, { element: null, distance: Number.POSITIVE_INFINITY }).element; } function createDropIndicator() { const indicator = document.createElement('div'); indicator.classList.add('drop-indicator'); return indicator; } }); </script> </body> </html> ``` Features included: 1. Drag and drop functionality for emoji cards 2. Rounded corners and shadow only during dragging 3. Smooth animations for all transitions 4. Snap positioning: - Left side if tier is empty - Between existing emojis when dragging over them - Shows a visual indicator of where the card will drop 5. Organized by tier levels (S-F) 6. Clean, modern styling with color-coded tiers 7. The emoji cards automatically relocate between tiers while maintaining their order You can drag emojis from the top pool into any tier, and also reorder them within tiers. The drop indicator shows precisely where the emoji will snap to before you release it.
```html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Emoji Tier List</title> <style> body { font-family: sans-serif; margin: 0; padding: 20px; background: #f0f0f0; } h2 { margin: 10px 0; } .tiers { display: flex; gap: 10px; } .tier { flex: 1; background: #fff; border: 1px solid #ccc; border-radius: 8px; padding: 10px; min-height: 150px; display: flex; flex-wrap: wrap; align-content: flex-start; position: relative; transition: background 0.2s; } .tier.drag-over { background: #e0f7fa; } .tier-title { position: absolute; top: -14px; left: 10px; background: #fff; padding: 0 5px; font-weight: bold; } .card, .placeholder { width: 50px; height: 50px; border-radius: 8px; margin: 5px; display: flex; align-items: center; justify-content: center; font-size: 24px; user-select: none; flex-shrink: 0; transition: all 0.2s; } .card { background: #fafafa; border: 1px solid #ccc; cursor: grab; } .card.dragging { opacity: 0.8; box-shadow: 0 8px 16px rgba(0,0,0,0.2); cursor: grabbing; transition: none; } .placeholder { background: #aaf; opacity: 0.4; border: 2px dashed #77c; } </style> </head> <body> <h1>Emoji Tier List</h1> <div class="tiers"> <div class="tier" data-tier="Pool"> <div class="tier-title">Pool</div> <div class="card" draggable="true">😀</div> <div class="card" draggable="true">😎</div> <div class="card" draggable="true">🚀</div> <div class="card" draggable="true">❤️</div> <div class="card" draggable="true">🐱👤</div> <div class="card" draggable="true">🍕</div> <div class="card" draggable="true">🎉</div> <div class="card" draggable="true">🏆</div> </div> <div class="tier" data-tier="S"><div class="tier-title">S Tier</div></div> <div class="tier" data-tier="A"><div class="tier-title">A Tier</div></div> <div class="tier" data-tier="B"><div class="tier-title">B Tier</div></div> <div class="tier" data-tier="C"><div class="tier-title">C Tier</div></div> </div> <script> let dragged = null; const tiers = document.querySelectorAll('.tier'); function onDragStart(e) { dragged = e.target; e.dataTransfer.effectAllowed = 'move'; e.dataTransfer.setData('text/plain', ''); requestAnimationFrame(()=> dragged.classList.add('dragging')); } function onDragEnd(e) { dragged.classList.remove('dragging'); cleanupPlaceholders(); } function cleanupPlaceholders() { document.querySelectorAll('.placeholder').forEach(p => p.remove()); tiers.forEach(t => t.classList.remove('drag-over')); } function onDragOver(e) { e.preventDefault(); const tier = e.currentTarget; if (!dragged) return; tier.classList.add('drag-over'); // Determine insert position const cards = [...tier.querySelectorAll('.card:not(.dragging)')]; let insertBeforeNode = null; for (let card of cards) { const rect = card.getBoundingClientRect(); if (e.clientX < rect.left + rect.width / 2) { insertBeforeNode = card; break; } } // remove existing placeholder tier.querySelectorAll('.placeholder').forEach(p => p.remove()); // create placeholder const placeholder = document.createElement('div'); placeholder.className = 'placeholder'; placeholder.style.width = dragged.offsetWidth + 'px'; placeholder.style.height = dragged.offsetHeight + 'px'; // insert placeholder if (insertBeforeNode) { tier.insertBefore(placeholder, insertBeforeNode); } else { tier.appendChild(placeholder); } } function onDrop(e) { e.preventDefault(); const tier = e.currentTarget; const placeholder = tier.querySelector('.placeholder'); if (placeholder) { tier.insertBefore(dragged, placeholder); } else { tier.appendChild(dragged); } cleanupPlaceholders(); } tiers.forEach(tier => { tier.addEventListener('dragover', onDragOver); tier.addEventListener('drop', onDrop); tier.addEventListener('dragleave', (e) => { // if leaving to outside the container entirely const rect = tier.getBoundingClientRect(); if (e.clientX < rect.left || e.clientX >= rect.right || e.clientY < rect.top || e.clientY >= rect.bottom) { tier.classList.remove('drag-over'); tier.querySelectorAll('.placeholder').forEach(p => p.remove()); } }); }); document.querySelectorAll('.card').forEach(card => { card.addEventListener('dragstart', onDragStart); card.addEventListener('dragend', onDragEnd); }); </script> </body> </html> ```
Below is a simple example of an HTML file that meets your requirements. It uses JavaScript and CSS to create a drag-and-drop tier list with the specified behavior. ```html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Tier List</title> <style> body { font-family: Arial, sans-serif; } .tier-list { display: flex; flex-direction: column; align-items: center; padding: 20px; } .tier { display: flex; align-items: center; padding: 10px; border: 1px solid #ddd; border-radius: 10px; margin-bottom: 20px; width: 100%; min-height: 50px; background-color: #f0f0f0; } .card { display: inline-block; padding: 10px; border: 1px solid #ccc; border-radius: 10px; background-color: #fff; margin: 5px; cursor: grab; transition: box-shadow 0.2s ease-in-out; } .card.dragging { box-shadow: 0 0 10px rgba(0, 0, 0, 0.2); opacity: 0.8; } .highlight { background-color: #ccc; border-radius: 5px; padding: 5px; margin: 5px; } </style> </head> <body> <div class="tier-list"> <div class="tier" data-tier="S-Tier"> <span>S-Tier</span> </div> <div class="tier" data-tier="A-Tier"> <span>A-Tier</span> </div> <div class="tier" data-tier="B-Tier"> <span>B-Tier</span> </div> </div> <div class="cards-container" style="display: flex; flex-wrap: wrap; justify-content: center;"> <div class="card" draggable="true" data-emoji="😊">😊</div> <div class="card" draggable="true" data-emoji="😄">😄</div> <div class="card" draggable="true" data-emoji="😆">😆</div> <div class="card" draggable="true" data-emoji="😎">😎</div> <div class="card" draggable="true" data-emoji="😍">😍</div> </div> <script> let draggedCard = null; let highlightedElement = null; document.addEventListener("dragstart", (e) => { if (e.target.classList.contains("card")) { draggedCard = e.target; draggedCard.classList.add("dragging"); e.dataTransfer.effectAllowed = "move"; e.dataTransfer.setData("text/html", draggedCard.outerHTML); } }); document.addEventListener("dragend", (e) => { if (draggedCard) { draggedCard.classList.remove("dragging"); draggedCard = null; } if (highlightedElement) { highlightedElement.classList.remove("highlight"); highlightedElement = null; } }); document.addEventListener("dragover", (e) => { e.preventDefault(); if (e.target.classList.contains("tier")) { const tier = e.target; const rect = tier.getBoundingClientRect(); const x = e.clientX - rect.left; const cards = tier.querySelectorAll(".card"); let targetCard = null; for (const card of cards) { const cardRect = card.getBoundingClientRect(); if (cardRect.left - rect.left < x && x < cardRect.right - rect.left) { targetCard = card; break; } else if (card === cards[cards.length - 1] && x > cardRect.left - rect.left) { targetCard = card; } } if (highlightedElement) { highlightedElement.classList.remove("highlight"); } if (targetCard) { highlightedElement = targetCard; highlightedElement.classList.add("highlight"); } else if (cards.length === 0) { highlightedElement = document.createElement("div"); highlightedElement.classList.add("highlight"); tier.appendChild(highlightedElement); } else { if (x < cards[0].getBoundingClientRect().left - rect.left) { highlightedElement = document.createElement("div"); highlightedElement.classList.add("highlight"); tier.insertBefore(highlightedElement, cards[0]); } else { highlightedElement = document.createElement("div"); highlightedElement.classList.add("highlight"); tier.appendChild(highlightedElement); tier.insertBefore(highlightedElement, null); } } } else if (e.target.classList.contains("card")) { const card = e.target; const tier = card.parentNode; const rect = tier.getBoundingClientRect(); const x = e.clientX - rect.left; const cards = tier.querySelectorAll(".card"); let targetCard = card; const index = Array.prototype.indexOf.call(cards, targetCard); if (index > 0 && x < cards[index - 1].getBoundingClientRect().right - rect.left) { targetCard = cards[index - 1]; } else if (index < cards.length - 1 && x > cards[index + 1].getBoundingClientRect().left - rect.left) { targetCard = cards[index + 1]; } if (highlightedElement) { highlightedElement.classList.remove("highlight"); } highlightedElement = targetCard; highlightedElement.classList.add("highlight"); } }); document.addEventListener("drop", (e) => { e.preventDefault(); if (e.target.classList.contains("tier") || e.target.classList.contains("card")) { let tier; if (e.target.classList.contains("tier")) { tier = e.target; } else { tier = e.target.parentNode; } if (draggedCard) { if (highlightedElement) { if (highlightedElement.parentNode !== tier) { tier.insertBefore(draggedCard, highlightedElement); } else if (Array.prototype.indexOf.call(tier.children, highlightedElement) > Array.prototype.indexOf.call(tier.children, draggedCard)) { tier.insertBefore(draggedCard, highlightedElement); } else if (Array.prototype.indexOf.call(tier.children, highlightedElement) < Array.prototype.indexOf.call(tier.children, draggedCard)) { tier.insertBefore(draggedCard, highlightedElement.nextSibling); } } else { tier.appendChild(draggedCard); } highlightedElement.classList.remove("highlight"); highlightedElement = null; } } }); </script> </body> </html> ``` This code creates a tier list with three tiers and five cards with different emojis. The cards are draggable and can be dropped into any tier. When a card is dragged over a tier, the tier or a card within the tier is highlighted to indicate where the card will be placed when dropped. The card is then inserted into the tier at the highlighted position. The transitions are animated using CSS.
# Drag and Drop Emoji Tier List I'll create an interactive tier list with visually appealing draggable emoji cards that feature animations, drop highlighting, and snapping behavior. ## HTML Implementation ```html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Emoji Tier List</title> <style> * { margin: 0; padding: 0; box-sizing: border-box; font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; } body { background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%); color: #fff; min-height: 100vh; padding: 20px; } .container { max-width: 1000px; margin: 0 auto; } header { text-align: center; margin-bottom: 30px; animation: fadeIn 1s ease-out; } h1 { font-size: 2.5rem; margin-bottom: 10px; text-shadow: 0 2px 4px rgba(0,0,0,0.4); } .subtitle { font-size: 1.2rem; opacity: 0.8; max-width: 600px; margin: 0 auto; } .tier-list { display: flex; flex-direction: column; gap: 15px; margin-bottom: 30px; } .tier { background: rgba(255, 255, 255, 0.08); border-radius: 12px; padding: 15px; display: flex; min-height: 120px; transition: all 0.3s ease; box-shadow: 0 4px 20px rgba(0,0,0,0.2); } .tier-label { width: 80px; height: 80px; background: rgba(0, 0, 0, 0.3); border-radius: 8px; display: flex; align-items: center; justify-content: center; font-size: 2rem; font-weight: bold; margin-right: 20px; flex-shrink: 0; box-shadow: inset 0 0 10px rgba(0,0,0,0.5); transition: transform 0.2s ease, background 0.3s ease; } .tier-content { display: flex; flex-wrap: wrap; gap: 15px; flex: 1; align-items: center; padding: 10px; border-radius: 8px; min-height: 100px; transition: background 0.3s ease; } .tier-content.highlight { background: rgba(255, 255, 255, 0.1); } .card { width: 80px; height: 80px; display: flex; align-items: center; justify-content: center; font-size: 2.5rem; border-radius: 10px; cursor: grab; user-select: none; transition: all 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275); background: rgba(255, 255, 255, 0.12); box-shadow: 0 3px 10px rgba(0,0,0,0.1); position: relative; overflow: hidden; } .card::before { content: ''; position: absolute; top: 0; left: 0; right: 0; bottom: 0; background: rgba(255, 255, 255, 0.1); border-radius: 10px; opacity: 0; transition: opacity 0.3s ease; } .card:hover::before { opacity: 1; } .card.dragging { transform: scale(1.05) rotate(3deg); box-shadow: 0 8px 25px rgba(0,0,0,0.3); z-index: 100; opacity: 0.9; cursor: grabbing; } .card-placeholder { width: 80px; height: 80px; border-radius: 10px; background: rgba(255, 255, 255, 0.2); border: 2px dashed rgba(255, 255, 255, 0.4); transition: all 0.3s ease; } .card-placeholder.highlight { background: rgba(100, 200, 255, 0.3); border-color: #64c8ff; transform: scale(1.07); } .instructions { background: rgba(255, 255, 255, 0.08); border-radius: 12px; padding: 20px; margin-top: 30px; line-height: 1.6; } .instructions h2 { margin-bottom: 10px; color: #64c8ff; } .instructions ul { padding-left: 20px; } .instructions li { margin-bottom: 8px; } .s-tier .tier-label { background: linear-gradient(135deg, #ffd700, #ffa500); } .a-tier .tier-label { background: linear-gradient(135deg, #ff6b6b, #ff8e8e); } .b-tier .tier-label { background: linear-gradient(135deg, #4ecdc4, #88d3ce); } .c-tier .tier-label { background: linear-gradient(135deg, #a29bfe, #c2bdf8); } .d-tier .tier-label { background: linear-gradient(135deg, #636e72, #b2bec3); } .reset-btn { background: linear-gradient(135deg, #6a11cb 0%, #2575fc 100%); color: white; border: none; border-radius: 30px; padding: 12px 30px; font-size: 1rem; font-weight: bold; cursor: pointer; margin: 20px auto; display: block; transition: all 0.3s ease; box-shadow: 0 4px 15px rgba(0,0,0,0.2); } .reset-btn:hover { transform: translateY(-3px); box-shadow: 0 6px 20px rgba(0,0,0,0.3); } @keyframes fadeIn { from { opacity: 0; transform: translateY(-20px); } to { opacity: 1; transform: translateY(0); } } @keyframes pulse { 0% { transform: scale(1); } 50% { transform: scale(1.05); } 100% { transform: scale(1); } } .pulse { animation: pulse 1.5s infinite; } </style> </head> <body> <div class="container"> <header> <h1>Emoji Tier List</h1> <p class="subtitle">Drag and drop emojis into tiers to rank them. See highlight feedback showing where you can drop!</p> </header> <div class="tier-list"> <div class="tier s-tier"> <div class="tier-label">S</div> <div class="tier-content" data-tier="s"> <!-- Emojis will be placed here --> </div> </div> <div class="tier a-tier"> <div class="tier-label">A</div> <div class="tier-content" data-tier="a"> <!-- Emojis will be placed here --> </div> </div> <div class="tier b-tier"> <div class="tier-label">B</div> <div class="tier-content" data-tier="b"> <!-- Emojis will be placed here --> </div> </div> <div class="tier c-tier"> <div class="tier-label">C</div> <div class="tier-content" data-tier="c"> <!-- Emojis will be placed here --> </div> </div> <div class="tier d-tier"> <div class="tier-label">D</div> <div class="tier-content" data-tier="d"> <!-- Emojis will be placed here --> </div> </div> </div> <button class="reset-btn" id="resetBtn">Reset Tier List</button> <div class="instructions"> <h2>How to Use:</h2> <ul> <li>Drag emojis from the pool below into tiers</li> <li>Drag emojis between tiers to reorganize</li> <li>Drop zones will highlight to show where cards will snap</li> <li>Cards snap to the top-left when tier is empty</li> <li>Cards snap between existing cards when tier has items</li> </ul> </div> <div class="tier"> <div class="tier-label">Pool</div> <div class="tier-content" id="emoji-pool"> <!-- Emojis will be placed here --> </div> </div> </div> <script> document.addEventListener('DOMContentLoaded', () => { // Emoji pool const emojis = ['😀', '😂', '😍', '🥺', '😎', '🤩', '😊', '🤔', '🥰', '🙄', '😴', '🤮', '🥶', '😡', '🤯']; // Elements const emojiPool = document.getElementById('emoji-pool'); const resetBtn = document.getElementById('resetBtn'); const tierContainers = document.querySelectorAll('.tier-content'); // Initialize emoji pool emojis.forEach(emoji => { const card = createCard(emoji); emojiPool.appendChild(card); }); // Create card element function createCard(emoji) { const card = document.createElement('div'); card.className = 'card'; card.textContent = emoji; card.draggable = true; card.addEventListener('dragstart', handleDragStart); card.addEventListener('dragend', handleDragEnd); return card; } // Drag start handler function handleDragStart(e) { e.dataTransfer.setData('text/plain', e.target.textContent); e.target.classList.add('dragging'); // Add a slight delay to hide the original element visually setTimeout(() => { e.target.classList.add('hide-while-dragging'); }, 0); } // Drag end handler function handleDragEnd(e) { e.target.classList.remove('dragging', 'hide-while-dragging'); // Remove all placeholders document.querySelectorAll('.card-placeholder').forEach(p => p.remove()); // Remove highlights tierContainers.forEach(container => { container.classList.remove('highlight'); }); } // Initialize drag events on tier containers tierContainers.forEach(container => { container.addEventListener('dragover', handleDragOver); container.addEventListener('dragenter', handleDragEnter); container.addEventListener('dragleave', handleDragLeave); container.addEventListener('drop', handleDrop); }); // Drag over handler function handleDragOver(e) { e.preventDefault(); const container = e.currentTarget; const draggingCard = document.querySelector('.dragging'); const cards = Array.from(container.querySelectorAll('.card:not(.dragging)')); const placeholder = container.querySelector('.card-placeholder'); // Highlight container container.classList.add('highlight'); // Remove existing placeholder if (placeholder) placeholder.remove(); // Create a new placeholder const newPlaceholder = document.createElement('div'); newPlaceholder.className = 'card-placeholder'; if (cards.length === 0) { // If tier is empty, add placeholder at the beginning container.prepend(newPlaceholder); newPlaceholder.classList.add('highlight'); } else { // Find closest card to mouse position const containerRect = container.getBoundingClientRect(); const mouseX = e.clientX - containerRect.left; // Find the card after which to place the placeholder let closestCard = null; let closestDist = Infinity; cards.forEach(card => { const cardRect = card.getBoundingClientRect(); const cardCenter = cardRect.left - containerRect.left + cardRect.width / 2; const dist = Math.abs(mouseX - cardCenter); if (dist < closestDist) { closestDist = dist; closestCard = card; } }); // Determine insertion position if (closestCard) { const cardRect = closestCard.getBoundingClientRect(); const cardCenter = cardRect.left - containerRect.left + cardRect.width / 2; if (mouseX > cardCenter) { // Insert after the closest card closestCard.after(newPlaceholder); } else { // Insert before the closest card closestCard.before(newPlaceholder); } newPlaceholder.classList.add('highlight'); } else { // Append to end if no closest card found container.appendChild(newPlaceholder); newPlaceholder.classList.add('highlight'); } } } // Drag enter handler function handleDragEnter(e) { e.preventDefault(); e.currentTarget.classList.add('highlight'); } // Drag leave handler function handleDragLeave(e) { if (!e.currentTarget.contains(e.relatedTarget)) { e.currentTarget.classList.remove('highlight'); // Remove placeholder const placeholder = e.currentTarget.querySelector('.card-placeholder'); if (placeholder) placeholder.remove(); } } // Drop handler function handleDrop(e) { e.preventDefault(); const container = e.currentTarget; const emoji = e.dataTransfer.getData('text/plain'); const draggingCard = document.querySelector('.dragging'); const placeholder = container.querySelector('.card-placeholder'); // Remove placeholder if (placeholder) placeholder.remove(); // Remove old card if it exists if (draggingCard) { draggingCard.remove(); } // Create new card const newCard = createCard(emoji); // Insert card where placeholder was if (placeholder) { placeholder.replaceWith(newCard); } else { // If no placeholder, add to beginning container.prepend(newCard); } // Remove highlight container.classList.remove('highlight'); // Animate the new card newCard.style.opacity = '0'; newCard.style.transform = 'scale(0.7)'; setTimeout(() => { newCard.style.transition = 'opacity 0.3s ease, transform 0.3s ease'; newCard.style.opacity = '1'; newCard.style.transform = 'scale(1)'; setTimeout(() => { newCard.style.transition = ''; }, 300); }, 10); } // Reset tier list resetBtn.addEventListener('click', () => { // Clear all tiers tierContainers.forEach(container => { if (container.id !== 'emoji-pool') { while (container.firstChild) { container.removeChild(container.firstChild); } } }); // Clear emoji pool while (emojiPool.firstChild) { emojiPool.removeChild(emojiPool.firstChild); } // Recreate emoji pool emojis.forEach(emoji => { const card = createCard(emoji); emojiPool.appendChild(card); }); }); }); </script> </body> </html> ``` ## Features Implemented 1. **Visually Appealing Design**: - Dark theme with gradient backgrounds - Color-coded tiers (S, A, B, C, D) - Card styling with rounded corners and animations 2. **Drag-and-Drop Functionality**: - Draggable emoji cards with visual feedback - Shadow
```html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Emoji Tier List</title> <style> body { display: flex; justify-content: center; padding: 2rem; background: #f0f0f5; font-family: sans-serif; } .tier-container { display: flex; width: 90%; gap: 2rem; } .tier { background: white; width: 20%; min-height: 500px; padding: 1rem; border-radius: 10px; box-shadow: 0 4px 8px rgba(0,0,0,0.1); border: 2px dashed #ccc; position: relative; display: flex; flex-direction: column; align-items: center; } .tier h3 { margin-top: 0; font-size: 1.2rem; color: #333; } .card { width: 80%; min-height: 80px; margin: 0.5rem auto; background: #fff; border-radius: 15px; box-shadow: 0 4px 6px rgba(0,0,0,0.1); display: flex; align-items: center; justify-content: center; font-size: 2rem; cursor: grab; transition: all 0.3s ease; user-select: none; border: 2px solid transparent; } .card:active { box-shadow: 0 8px 20px rgba(0,0,0,0.2); transform: scale(1.05); z-index: 10; } .placeholder { height: 80px; width: 80%; margin: 0.5rem auto; border: 2px dashed #888; border-radius: 15px; transition: all 0.3s ease; } .card.dragging { opacity: 0.4; transform: scale(0.95); box-shadow: 0 0 0 transparent; } .drop-highlight { position: absolute; height: 80px; width: 80%; background: rgba(0, 255, 0, 0.2); border-radius: 15px; z-index: 1; } </style> </head> <body> <div class="tier-container"> <div class="tier" data-tier="S"> <h3>S Tier</h3> </div> <div class="tier" data-tier="A"> <h3>A Tier</h3> </div> <div class="tier" data-tier="B"> <h3>B Tier</h3> </div> <div class="tier" data-tier="C"> <h3>C Tier</h3> </div> </div> <script> const cards = [ {emoji: "😀", text: "Grinning Face"}, {emoji: "😎", text: "Smiling Face with Sunglasses"}, {emoji: "😂", text: "Face with Tears of Joy"}, {emoji: "😭", text: "Crying Face"}, {emoji: "❤️", text: "Red Heart"}, {emoji: "🔥", text: "Fire"}, {emoji: "✨", text: "Sparkles"}, {emoji: "🌟", text: "Sparkling Star"} ]; const tierContainer = document.querySelector('.tier-container'); const tiers = document.querySelectorAll('.tier'); // Initialize cards cards.forEach(card => { const cardEl = document.createElement('div'); cardEl.className = 'card'; cardEl.draggable = true; cardEl.textContent = card.emoji; cardEl.setAttribute('title', card.text); tierContainer.querySelector('.tier[data-tier="A"]').appendChild(cardEl); }); let draggedCard = null; let currentTier = null; let dropHighlight = document.createElement('div'); dropHighlight.classList.add('drop-highlight'); tiers.forEach(tier => { tier.addEventListener('dragover', e => { e.preventDefault(); e.stopPropagation(); if (!draggedCard) return; currentTier = tier; tier.appendChild(dropHighlight); const rect = tier.getBoundingClientRect(); const cardsInTier = Array.from(tier.querySelectorAll('.card:not(.dragging)')); // Position highlight based on drop area if (cardsInTier.length === 0) { dropHighlight.style.top = '3rem'; } else { let y = e.clientY - rect.top; let closestIndex = 0; let closestDist = Math.abs(y - (cardsInTier[0].offsetTop + cardsInTier[0].offsetHeight/2)); for (let i = 1; i < cardsInTier.length; i++) { let dist = Math.abs(y - (cardsInTier[i].offsetTop + cardsInTier[i].offsetHeight/2)); if (dist < closestDist) { closestDist = dist; closestIndex = i; } } if (y < cardsInTier[0].offsetTop) { closestIndex = 0; } else if (y > cardsInTier[cardsInTier.length - 1].offsetTop + cardsInTier[cardsInTier.length - 1].offsetHeight) { closestIndex = cardsInTier.length; } dropHighlight.style.top = `${cardsInTier[closestIndex].offsetTop - rect.top - 40}px`; } }); tier.addEventListener('dragleave', e => { if (!draggedCard) return; if (e.target.classList.contains('tier') && !tier.contains(e.relatedTarget)) { dropHighlight.remove(); } }); tier.addEventListener('drop', e => { e.preventDefault(); if (!draggedCard || !currentTier) return; dropHighlight.remove(); const targetTier = e.target.closest('.tier'); // Remove from original tier draggedCard.classList.remove('dragging'); // Add to new tier if (targetTier.querySelector('.card') === null) { targetTier.appendChild(draggedCard); } else { const cards = Array.from(targetTier.querySelectorAll('.card:not(.dragging)')); let insertBefore = null; if (cards.length === 0) { targetTier.appendChild(draggedCard); } else { const rect = draggedCard.getBoundingClientRect(); const midY = (rect.top + rect.bottom) / 2; for (let i = 0; i < cards.length; i++) { const cardRect = cards[i].getBoundingClientRect(); if (midY < (cardRect.top + cardRect.height/2)) { insertBefore = cards[i]; break; } } insertBefore ? targetTier.insertBefore(draggedCard, insertBefore) : targetTier.appendChild(draggedCard); } } }); }); // Common drag events document.querySelectorAll('.card').forEach(card => { card.addEventListener('dragstart', e => { draggedCard = card; draggedCard.classList.add('dragging'); }); card.addEventListener('dragend', () => { draggedCard = null; document.querySelectorAll('.drop-highlight').forEach(el => el.remove()); }); }); </script> </body> </html> ```
```html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Emoji Tier List</title> <style> body { font-family: Arial, sans-serif; background-color: #f0f0f0; display: flex; justify-content: center; padding: 20px; } #tiers { display: flex; gap: 20px; } .tier { background-color: #fff; border: 1px solid #ddd; border-radius: 10px; padding: 20px; width: 200px; min-height: 300px; box-shadow: 0 2px 5px rgba(0,0,0,0.1); transition: all 0.3s ease; } .tier h2 { text-align: center; margin-bottom: 10px; color: #333; } .card { background-color: #ffffff; border-radius: 15px; padding: 15px; margin: 10px 0; font-size: 24px; text-align: center; cursor: grab; box-shadow: none; /* No shadow when not dragging */ transition: all 0.3s ease; /* Animate transitions */ } .card.dragging { box-shadow: 0 10px 20px rgba(0,0,0,0.2); /* Shadow when dragging */ transform: scale(1.05); /* Slight scale for visual feedback */ } .sortable-placeholder { background-color: lightblue; /* Highlight for snapping */ border: 2px dashed blue; border-radius: 15px; margin: 10px 0; height: 50px; /* Approximate height of a card */ transition: all 0.3s ease; /* Animate the placeholder */ } </style> <script src="https://cdn.jsdelivr.net/npm/sortablejs@1.14.0/Sortable.min.js"></script> <script> document.addEventListener('DOMContentLoaded', function() { const tiers = document.querySelectorAll('.tier-content'); tiers.forEach(tier => { new Sortable(tier, { group: 'emoji-tiers', // Allows dragging between tiers animation: 200, // Smooth animation for sorting sort: true, // Enable sorting filter: '.filtered', // If needed, but not here onStart: function(evt) { evt.item.classList.add('dragging'); // Add dragging class }, onEnd: function(evt) { evt.item.classList.remove('dragging'); // Remove dragging class // The placeholder will handle snapping visualization }, onMove: function(evt) { // Optional: Fine-tune if needed, but Sortable handles snapping } }); }); }); </script> </head> <body> <div id="tiers"> <div class="tier"> <h2>S Tier</h2> <div class="tier-content"> <!-- Initial emojis; you can add more as needed --> <div class="card">😊</div> <div class="card">😂</div> </div> </div> <div class="tier"> <h2>A Tier</h2> <div class="tier-content"> <!-- Empty initially --> </div> </div> <div class="tier"> <h2>B Tier</h2> <div class="tier-content"> <!-- Empty initially --> <div class="card">❤️</div> </div> </div> <div class="tier"> <h2>C Tier</h2> <div class="tier-content"> <!-- You can add more cards here if desired --> <div class="card">🔥</div> <div class="card">👍</div> </div> </div> </div> </body> </html> ```