
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>
```