TierList - Drag and drop
<!DOCTYPE html><html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>Tier Maker</title> <style> :root { --color-s: #ff7f80; --color-a: #ffc07f; --color-b: #ffdf80; --color-c: #fdff7f; --color-d: #bfff7f; --color-e: #7fff7f; }
*, /* CSS Reset */ *::before, *::after { box-sizing: border-box; }
button { /* CSS Reset */ background: transparent; border: 0; color: #fff; cursor: pointer; }
body { background: #111; color: #fff; font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif; margin: 0 auto; max-width: 500px; padding-inline: 32px; /* Padding left and right */ user-select: none; /* Disable text selection */ }
#top-header { display: flex; justify-content: center; align-items: center; padding: 32px 0 16px;
& img { max-width: 125px; height: auto; } }
.tier { border: 1px solid #444; display: flex; flex-direction: column; background: #1f1f1f; }
.row { display: flex; flex-direction: row; border-bottom: 1px solid #111; transition: all 0.3s ease;
&.drag-over { background: #555; scale: 1.01; } }
.label { cursor: pointer; background: var(--level, #09f); color: #333; font-weight: bold; width: 50px; height: 50px;
display: flex; align-items: center; justify-content: center;
& span:focus { outline: 1px solid #fff; } }
#selector { display: flex; flex-direction: column; align-items: center; gap: 16px; margin-top: 16px; }
#selector-buttons { display: flex; gap: 8px; justify-content: center;
& button, & label { cursor: pointer; transition: all 0.3s ease; background: #222; display: flex; justify-content: center; align-items: center; width: 24px; height: 24px; padding: 4px;
&:hover { background: #444; scale: 1.1; } }
& svg { width: 100%; height: 100%; } }
#selector-items { border: 1px solid #666; width: 100%; height: 100px; margin-bottom: 100px; display: flex; flex-wrap: wrap;
&.drag-files { background: #555; border-style: dashed; } }
.item-image { width: 50px; height: 50px; object-fit: cover; background: #fff; cursor: grab;
&.drag-preview { opacity: 0.5; pointer-events: none; } } </style>
<script type="module"> const $ = (el) => document.querySelector(el) // Helper function to select elements const $$ = (el) => document.querySelectorAll(el) // Helper function to select multiple elements
const imageInput = $('#image-input') const itemsSection = $('#selector-items') const resetButton = $('#reset-tier-button') const saveButton = $('#save-tier-button')
function createItem(src) { const imgElement = document.createElement('img') imgElement.draggable = true imgElement.src = src imgElement.className = 'item-image'
imgElement.addEventListener('dragstart', handleDragStart) imgElement.addEventListener('dragend', handleDragEnd)
itemsSection.appendChild(imgElement) return imgElement }
function useFilesToCreateItems(files) { if (files && files.length > 0) { Array.from(files).forEach((file) => { const reader = new FileReader()
reader.onload = (eventReader) => { createItem(eventReader.target.result) }
reader.readAsDataURL(file) }) } }
imageInput.addEventListener('change', (event) => { const { files } = event.target useFilesToCreateItems(files) })
let draggedElement = null let sourceContainer = null
const rows = $$('.tier .row')
rows.forEach((row) => { row.addEventListener('dragover', handleDragOver) row.addEventListener('drop', handleDrop) row.addEventListener('dragleave', handleDragLeave) })
itemsSection.addEventListener('dragover', handleDragOver) itemsSection.addEventListener('drop', handleDrop) itemsSection.addEventListener('dragleave', handleDragLeave)
itemsSection.addEventListener('drop', handleDropFromDesktop) itemsSection.addEventListener('dragover', handleDragOverFromDesktop)
function handleDragOverFromDesktop(event) { event.preventDefault()
const { currentTarget, dataTransfer } = event
if (dataTransfer.types.includes('Files')) { currentTarget.classList.add('drag-files') } }
function handleDropFromDesktop(event) { event.preventDefault() const { currentTarget, dataTransfer } = event
if (dataTransfer.types.includes('Files')) { currentTarget.classList.remove('drag-files') const { files } = dataTransfer useFilesToCreateItems(files) } }
function handleDrop(event) { event.preventDefault()
const { currentTarget, dataTransfer } = event
if (sourceContainer && draggedElement) { sourceContainer.removeChild(draggedElement) }
if (draggedElement) { const src = dataTransfer.getData('text/plain') const imgElement = createItem(src) currentTarget.appendChild(imgElement) }
currentTarget.classList.remove('drag-over') currentTarget.querySelector('.drag-preview')?.remove() }
function handleDragOver(event) { event.preventDefault()
const { currentTarget, dataTransfer } = event if (sourceContainer === currentTarget) return
currentTarget.classList.add('drag-over')
const dragPreview = document.querySelector('.drag-preview')
if (draggedElement && !dragPreview) { const previewElement = draggedElement.cloneNode(true) previewElement.classList.add('drag-preview') currentTarget.appendChild(previewElement) } }
function handleDragLeave(event) { event.preventDefault()
const { currentTarget } = event currentTarget.classList.remove('drag-over') currentTarget.querySelector('.drag-preview')?.remove() }
function handleDragStart(event) { draggedElement = event.target sourceContainer = draggedElement.parentNode event.dataTransfer.setData('text/plain', draggedElement.src) }
function handleDragEnd(event) { draggedElement = null sourceContainer = null }
resetButton.addEventListener('click', () => { const items = $$('.tier .item-image') items.forEach((item) => { item.remove() itemsSection.appendChild(item) }) })
saveButton.addEventListener('click', () => { const tierContainer = $('.tier') const canvas = document.createElement('canvas') const ctx = canvas.getContext('2d')
import('https://cdn.jsdelivr.net/npm/html2canvas-pro@1.5.8/+esm').then(({ default: html2canvas }) => { html2canvas(tierContainer).then((canvas) => { ctx.drawImage(canvas, 0, 0) const imgURL = canvas.toDataURL('image/png')
const downloadLink = document.createElement('a') downloadLink.download = 'tier.png' downloadLink.href = imgURL downloadLink.click() }) }) }) </script> </head>
<body> <header id="top-header"> <img src="https://tiermaker.com/images/tiermaker-logo.png" /> </header>
<section class="tier"> <div class="row"> <aside class="label" style="--level: var(--color-s)"> <span contenteditable="true">S</span> </aside> </div>
<div class="row"> <aside class="label" style="--level: var(--color-a)"> <span contenteditable="true">A</span> </aside> </div>
<div class="row"> <aside class="label" style="--level: var(--color-b)"> <span contenteditable="true">B</span> </aside> </div>
<div class="row"> <aside class="label" style="--level: var(--color-c)"> <span contenteditable="true">C</span> </aside> </div>
<div class="row"> <aside class="label" style="--level: var(--color-d)"> <span contenteditable="true">D</span> </aside> </div>
<div class="row"> <aside class="label" style="--level: var(--color-e)"> <span contenteditable="true">E</span> </aside> </div> </section>
<footer id="selector"> <section id="selector-buttons"> <label> <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1" stroke-linecap="round" stroke-linejoin="round"> <path stroke="none" d="M0 0h24v24H0z" fill="none" /> <path d="M3 12a9 9 0 1 0 18 0a9 9 0 0 0 -18 0" /> <path d="M9 12h6" /> <path d="M12 9v6" /> </svg> <input multiple accept="image/*" id="image-input" type="file" hidden /> </label>
<button id="reset-tier-button"> <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1" stroke-linecap="round" stroke-linejoin="round"> <path stroke="none" d="M0 0h24v24H0z" fill="none" /> <path d="M4.05 11a8 8 0 1 1 .5 4m-.5 5v-5h5" /> </svg> </button>
<button id="save-tier-button"> <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-device-floppy"> <path stroke="none" d="M0 0h24v24H0z" fill="none" /> <path d="M6 4h10l4 4v10a2 2 0 0 1 -2 2h-12a2 2 0 0 1 -2 -2v-12a2 2 0 0 1 2 -2" /> <path d="M12 14m-2 0a2 2 0 1 0 4 0a2 2 0 1 0 -4 0" /> <path d="M14 4l0 4l-6 0l0 -4" /> </svg> </button> </section>
<section id="selector-items"></section> </footer> </body></html>