Spaces:
Running
Running
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Hero Roster Manager</title> | |
| <!-- Google Fonts --> | |
| <link href="https://fonts.googleapis.com/css2?family=Outfit:wght@300;400;600;800&display=swap" rel="stylesheet"> | |
| <!-- Font Awesome for Icons --> | |
| <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> | |
| <style> | |
| :root { | |
| --primary: #6366f1; | |
| --primary-dark: #4f46e5; | |
| --secondary: #ec4899; | |
| --bg-dark: #0f172a; | |
| --card-bg: #1e293b; | |
| --text-light: #f8fafc; | |
| --text-gray: #94a3b8; | |
| --success: #22c55e; | |
| --danger: #ef4444; | |
| --glass: rgba(30, 41, 59, 0.7); | |
| --border: rgba(255, 255, 255, 0.1); | |
| } | |
| * { | |
| box-sizing: border-box; | |
| margin: 0; | |
| padding: 0; | |
| font-family: 'Outfit', sans-serif; | |
| } | |
| body { | |
| background-color: var(--bg-dark); | |
| color: var(--text-light); | |
| min-height: 100vh; | |
| background-image: | |
| radial-gradient(at 0% 0%, rgba(99, 102, 241, 0.15) 0px, transparent 50%), | |
| radial-gradient(at 100% 0%, rgba(236, 72, 153, 0.15) 0px, transparent 50%); | |
| padding-bottom: 4rem; | |
| } | |
| /* Header */ | |
| header { | |
| display: flex; | |
| justify-content: space-between; | |
| align-items: center; | |
| padding: 1.5rem 2rem; | |
| background: var(--glass); | |
| backdrop-filter: blur(12px); | |
| border-bottom: 1px solid var(--border); | |
| position: sticky; | |
| top: 0; | |
| z-index: 100; | |
| } | |
| .brand { | |
| font-weight: 800; | |
| font-size: 1.5rem; | |
| background: linear-gradient(to right, var(--primary), var(--secondary)); | |
| -webkit-background-clip: text; | |
| background-clip: text; | |
| color: transparent; | |
| display: flex; | |
| align-items: center; | |
| gap: 0.5rem; | |
| } | |
| .anycoder-link { | |
| font-size: 0.85rem; | |
| color: var(--text-gray); | |
| text-decoration: none; | |
| transition: color 0.3s ease; | |
| font-weight: 600; | |
| } | |
| .anycoder-link:hover { | |
| color: var(--primary); | |
| } | |
| /* Main Container */ | |
| .container { | |
| max-width: 1200px; | |
| margin: 2rem auto; | |
| padding: 0 1.5rem; | |
| } | |
| .controls { | |
| display: flex; | |
| justify-content: space-between; | |
| align-items: center; | |
| margin-bottom: 2rem; | |
| flex-wrap: wrap; | |
| gap: 1rem; | |
| } | |
| .stats { | |
| color: var(--text-gray); | |
| font-size: 0.9rem; | |
| } | |
| .btn { | |
| padding: 0.75rem 1.5rem; | |
| border-radius: 0.75rem; | |
| border: none; | |
| cursor: pointer; | |
| font-weight: 600; | |
| transition: all 0.3s ease; | |
| display: inline-flex; | |
| align-items: center; | |
| gap: 0.5rem; | |
| font-size: 0.95rem; | |
| } | |
| .btn-primary { | |
| background: linear-gradient(135deg, var(--primary), var(--primary-dark)); | |
| color: white; | |
| box-shadow: 0 4px 15px rgba(99, 102, 241, 0.3); | |
| } | |
| .btn-primary:hover { | |
| transform: translateY(-2px); | |
| box-shadow: 0 8px 25px rgba(99, 102, 241, 0.4); | |
| } | |
| .btn-icon { | |
| padding: 0.5rem; | |
| border-radius: 50%; | |
| width: 36px; | |
| height: 36px; | |
| display: flex; | |
| justify-content: center; | |
| align-items: center; | |
| } | |
| /* Grid Layout */ | |
| .hero-grid { | |
| display: grid; | |
| grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); | |
| gap: 2rem; | |
| } | |
| /* Hero Card */ | |
| .hero-card { | |
| background: var(--card-bg); | |
| border-radius: 1.5rem; | |
| overflow: hidden; | |
| border: 1px solid var(--border); | |
| transition: all 0.3s ease; | |
| position: relative; | |
| display: flex; | |
| flex-direction: column; | |
| } | |
| .hero-card:hover { | |
| transform: translateY(-5px); | |
| border-color: var(--primary); | |
| box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2); | |
| } | |
| .card-header { | |
| height: 120px; | |
| background-size: cover; | |
| background-position: center; | |
| position: relative; | |
| } | |
| .card-header::after { | |
| content: ''; | |
| position: absolute; | |
| bottom: 0; | |
| left: 0; | |
| width: 100%; | |
| height: 60%; | |
| background: linear-gradient(to top, var(--card-bg), transparent); | |
| } | |
| .hero-avatar { | |
| width: 80px; | |
| height: 80px; | |
| border-radius: 50%; | |
| border: 4px solid var(--card-bg); | |
| position: absolute; | |
| bottom: -40px; | |
| left: 20px; | |
| background-color: #334155; | |
| object-fit: cover; | |
| z-index: 2; | |
| } | |
| .card-body { | |
| padding: 3rem 1.5rem 1.5rem; | |
| flex-grow: 1; | |
| } | |
| .hero-name { | |
| font-size: 1.5rem; | |
| font-weight: 700; | |
| margin-bottom: 0.25rem; | |
| } | |
| .hero-alias { | |
| color: var(--secondary); | |
| font-size: 0.9rem; | |
| font-weight: 600; | |
| text-transform: uppercase; | |
| letter-spacing: 1px; | |
| margin-bottom: 1rem; | |
| display: block; | |
| } | |
| .hero-desc { | |
| color: var(--text-gray); | |
| font-size: 0.95rem; | |
| line-height: 1.6; | |
| margin-bottom: 1.5rem; | |
| display: -webkit-box; | |
| -webkit-line-clamp: 3; | |
| -webkit-box-orient: vertical; | |
| overflow: hidden; | |
| } | |
| /* Stats Bars */ | |
| .stat-row { | |
| display: flex; | |
| align-items: center; | |
| margin-bottom: 0.5rem; | |
| font-size: 0.85rem; | |
| } | |
| .stat-label { | |
| width: 80px; | |
| color: var(--text-gray); | |
| } | |
| .stat-bar-bg { | |
| flex-grow: 1; | |
| height: 6px; | |
| background: rgba(255, 255, 255, 0.1); | |
| border-radius: 3px; | |
| overflow: hidden; | |
| } | |
| .stat-bar-fill { | |
| height: 100%; | |
| border-radius: 3px; | |
| background: var(--primary); | |
| } | |
| .card-actions { | |
| padding: 1rem 1.5rem; | |
| border-top: 1px solid var(--border); | |
| display: flex; | |
| justify-content: flex-end; | |
| gap: 0.5rem; | |
| background: rgba(0, 0, 0, 0.1); | |
| } | |
| .action-btn { | |
| background: transparent; | |
| color: var(--text-gray); | |
| border: 1px solid var(--border); | |
| padding: 0.5rem 1rem; | |
| border-radius: 0.5rem; | |
| cursor: pointer; | |
| transition: all 0.2s; | |
| font-size: 0.85rem; | |
| } | |
| .action-btn:hover { | |
| background: rgba(255, 255, 255, 0.05); | |
| color: white; | |
| } | |
| .action-btn.delete:hover { | |
| background: rgba(239, 68, 68, 0.1); | |
| color: var(--danger); | |
| border-color: var(--danger); | |
| } | |
| /* Modal */ | |
| .modal-overlay { | |
| position: fixed; | |
| top: 0; | |
| left: 0; | |
| width: 100%; | |
| height: 100%; | |
| background: rgba(0, 0, 0, 0.7); | |
| backdrop-filter: blur(5px); | |
| z-index: 1000; | |
| display: flex; | |
| justify-content: center; | |
| align-items: center; | |
| opacity: 0; | |
| pointer-events: none; | |
| transition: opacity 0.3s ease; | |
| } | |
| .modal-overlay.active { | |
| opacity: 1; | |
| pointer-events: all; | |
| } | |
| .modal { | |
| background: var(--card-bg); | |
| width: 90%; | |
| max-width: 500px; | |
| border-radius: 1.5rem; | |
| padding: 2rem; | |
| border: 1px solid var(--border); | |
| box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.5); | |
| transform: translateY(20px); | |
| transition: transform 0.3s ease; | |
| } | |
| .modal-overlay.active .modal { | |
| transform: translateY(0); | |
| } | |
| .modal-header { | |
| display: flex; | |
| justify-content: space-between; | |
| align-items: center; | |
| margin-bottom: 1.5rem; | |
| } | |
| .modal-title { | |
| font-size: 1.5rem; | |
| font-weight: 700; | |
| } | |
| .close-modal { | |
| background: none; | |
| border: none; | |
| color: var(--text-gray); | |
| font-size: 1.5rem; | |
| cursor: pointer; | |
| } | |
| .form-group { | |
| margin-bottom: 1.25rem; | |
| } | |
| .form-label { | |
| display: block; | |
| margin-bottom: 0.5rem; | |
| color: var(--text-gray); | |
| font-size: 0.9rem; | |
| } | |
| .form-input, .form-textarea { | |
| width: 100%; | |
| background: rgba(0, 0, 0, 0.2); | |
| border: 1px solid var(--border); | |
| padding: 0.75rem; | |
| border-radius: 0.75rem; | |
| color: white; | |
| font-size: 1rem; | |
| transition: border-color 0.3s; | |
| } | |
| .form-input:focus, .form-textarea:focus { | |
| outline: none; | |
| border-color: var(--primary); | |
| } | |
| .range-container { | |
| display: flex; | |
| align-items: center; | |
| gap: 1rem; | |
| } | |
| input[type=range] { | |
| flex-grow: 1; | |
| accent-color: var(--primary); | |
| } | |
| .range-value { | |
| width: 30px; | |
| text-align: right; | |
| font-weight: 600; | |
| color: var(--primary); | |
| } | |
| /* Empty State */ | |
| .empty-state { | |
| grid-column: 1 / -1; | |
| text-align: center; | |
| padding: 4rem 2rem; | |
| background: rgba(255, 255, 255, 0.02); | |
| border-radius: 1.5rem; | |
| border: 2px dashed var(--border); | |
| } | |
| .empty-icon { | |
| font-size: 3rem; | |
| color: var(--text-gray); | |
| margin-bottom: 1rem; | |
| opacity: 0.5; | |
| } | |
| /* Notification */ | |
| .notification { | |
| position: fixed; | |
| bottom: 2rem; | |
| right: 2rem; | |
| background: var(--card-bg); | |
| padding: 1rem 1.5rem; | |
| border-radius: 0.75rem; | |
| border-left: 4px solid var(--success); | |
| box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.3); | |
| transform: translateY(100px); | |
| transition: transform 0.3s ease; | |
| z-index: 2000; | |
| display: flex; | |
| align-items: center; | |
| gap: 0.75rem; | |
| } | |
| .notification.show { | |
| transform: translateY(0); | |
| } | |
| @media (max-width: 600px) { | |
| .hero-grid { | |
| grid-template-columns: 1fr; | |
| } | |
| .controls { | |
| flex-direction: column; | |
| align-items: stretch; | |
| } | |
| .btn { | |
| justify-content: center; | |
| } | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <header> | |
| <div class="brand"> | |
| <i class="fa-solid fa-bolt"></i> HeroForce | |
| </div> | |
| <a href="https://huggingface.co/spaces/akhaliq/anycoder" target="_blank" class="anycoder-link">Built with anycoder</a> | |
| </header> | |
| <div class="container"> | |
| <div class="controls"> | |
| <div> | |
| <h1 style="margin-bottom: 0.5rem;">Hero Roster</h1> | |
| <span class="stats" id="heroCount">0 Heroes Active</span> | |
| </div> | |
| <button class="btn btn-primary" onclick="openModal()"> | |
| <i class="fa-solid fa-plus"></i> Add New Hero | |
| </button> | |
| </div> | |
| <div class="hero-grid" id="heroGrid"> | |
| <!-- Heroes will be injected here --> | |
| </div> | |
| </div> | |
| <!-- Create/Edit Modal --> | |
| <div class="modal-overlay" id="heroModal"> | |
| <div class="modal"> | |
| <div class="modal-header"> | |
| <h2 class="modal-title" id="modalTitle">New Hero</h2> | |
| <button class="close-modal" onclick="closeModal()">×</button> | |
| </div> | |
| <form id="heroForm"> | |
| <input type="hidden" id="heroId"> | |
| <div class="form-group"> | |
| <label class="form-label">Hero Name</label> | |
| <input type="text" class="form-input" id="heroName" required placeholder="e.g. Captain Velocity"> | |
| </div> | |
| <div class="form-group"> | |
| <label class="form-label">Alias / Secret Identity</label> | |
| <input type="text" class="form-input" id="heroAlias" required placeholder="e.g. John Doe"> | |
| </div> | |
| <div class="form-group"> | |
| <label class="form-label">Power Description</label> | |
| <textarea class="form-textarea" id="heroDesc" rows="3" placeholder="Describe their abilities..."></textarea> | |
| </div> | |
| <div class="form-group"> | |
| <label class="form-label">Strength Level</label> | |
| <div class="range-container"> | |
| <input type="range" id="heroStrength" min="1" max="100" value="50" oninput="updateRangeValue(this)"> | |
| <span class="range-value">50</span> | |
| </div> | |
| </div> | |
| <div class="form-group"> | |
| <label class="form-label">Speed Level</label> | |
| <div class="range-container"> | |
| <input type="range" id="heroSpeed" min="1" max="100" value="50" oninput="updateRangeValue(this)"> | |
| <span class="range-value">50</span> | |
| </div> | |
| </div> | |
| <div class="form-group"> | |
| <label class="form-label">Intelligence Level</label> | |
| <div class="range-container"> | |
| <input type="range" id="heroIntel" min="1" max="100" value="50" oninput="updateRangeValue(this)"> | |
| <span class="range-value">50</span> | |
| </div> | |
| </div> | |
| <button type="submit" class="btn btn-primary" style="width: 100%">Save Hero</button> | |
| </form> | |
| </div> | |
| </div> | |
| <!-- Notification Toast --> | |
| <div class="notification" id="notification"> | |
| <i class="fa-solid fa-check-circle" style="color: var(--success)"></i> | |
| <span id="notificationMsg">Action successful</span> | |
| </div> | |
| <script> | |
| // Initial Data (The 5 Heroes requested) | |
| const defaultHeroes = [ | |
| { | |
| id: '1', | |
| name: 'Iron Guardian', | |
| alias: 'Tony Stark-ish', | |
| desc: 'A genius billionaire who built a high-tech suit of armor to protect the world from cosmic threats.', | |
| strength: 85, | |
| speed: 70, | |
| intel: 95, | |
| avatarColor: '#ef4444', | |
| headerColor: '#991b1b' | |
| }, | |
| { | |
| id: '2', | |
| name: 'Solar Flare', | |
| alias: 'Elena Rodriguez', | |
| desc: 'Capable of manipulating solar energy to fly and project intense beams of heat.', | |
| strength: 75, | |
| speed: 90, | |
| intel: 60, | |
| avatarColor: '#f59e0b', | |
| headerColor: '#b45309' | |
| }, | |
| { | |
| id: '3', | |
| name: 'Shadow Weaver', | |
| alias: 'Damian Black', | |
| desc: 'Can merge with shadows and teleport instantly between dark places. Master of stealth.', | |
| strength: 50, | |
| speed: 88, | |
| intel: 80, | |
| avatarColor: '#8b5cf6', | |
| headerColor: '#5b21b6' | |
| }, | |
| { | |
| id: '4', | |
| name: 'Titanus', | |
| alias: 'Marcus Steel', | |
| desc: 'Possesses immense physical strength and invulnerability. The tank of the team.', | |
| strength: 98, | |
| speed: 40, | |
| intel: 55, | |
| avatarColor: '#10b981', | |
| headerColor: '#065f46' | |
| }, | |
| { | |
| id: '5', | |
| name: 'Mind Reader', | |
| alias: 'Sarah Jean', | |
| desc: 'A powerful telepath who can read thoughts and influence the actions of others.', | |
| strength: 30, | |
| speed: 45, | |
| intel: 100, | |
| avatarColor: '#ec4899', | |
| headerColor: '#9d174d' | |
| } | |
| ]; | |
| // State Management | |
| let heroes = JSON.parse(localStorage.getItem('heroes')) || defaultHeroes; | |
| // DOM Elements | |
| const grid = document.getElementById('heroGrid'); | |
| const modalOverlay = document.getElementById('heroModal'); | |
| const heroForm = document.getElementById('heroForm'); | |
| const heroCountLabel = document.getElementById('heroCount'); | |
| // Render Function | |
| function renderHeroes() { | |
| grid.innerHTML = ''; | |
| if (heroes.length === 0) { | |
| grid.innerHTML = ` | |
| <div class="empty-state"> | |
| <div class="empty-icon"><i class="fa-solid fa-mask"></i></div> | |
| <h3>No Heroes Found</h3> | |
| <p style="color: var(--text-gray); margin-top: 0.5rem;">The world needs saving! Add a hero to start.</p> | |
| </div> | |
| `; | |
| heroCountLabel.textContent = `0 Heroes Active`; | |
| return; | |
| } | |
| heroCountLabel.textContent = `${heroes.length} Heroes Active`; | |
| heroes.forEach(hero => { | |
| const card = document.createElement('div'); | |
| card.className = 'hero-card'; | |
| // Generate random-ish gradients based on name if no color set (for new adds) | |
| const headerBg = hero.headerColor || `hsl(${Math.random() * 360}, 60%, 30%)`; | |
| const avatarBg = hero.avatarColor || `hsl(${Math.random() * 360}, 70%, 50%)`; | |
| card.innerHTML = ` | |
| <div class="card-header" style="background-color: ${headerBg};"> | |
| <div class="hero-avatar" style="background-color: ${avatarBg}; display: flex; justify-content: center; align-items: center; font-size: 2rem;"> | |
| ${hero.name.charAt(0)} | |
| </div> | |
| </div> | |
| <div class="card-body"> | |
| <h3 class="hero-name">${hero.name}</h3> | |
| <span class="hero-alias">${hero.alias}</span> | |
| <p class="hero-desc">${hero.desc}</p> | |
| <div class="stat-row"> | |
| <span class="stat-label">STR</span> | |
| <div class="stat-bar-bg"> | |
| <div class="stat-bar-fill" style="width: ${hero.strength}%"></div> | |
| </div> | |
| </div> | |
| <div class="stat-row"> | |
| <span class="stat-label">SPD</span> | |
| <div class="stat-bar-bg"> | |
| <div class="stat-bar-fill" style="width: ${hero.speed}%; background-color: var(--secondary)"></div> | |
| </div> | |
| </div> | |
| <div class="stat-row"> | |
| <span class="stat-label">INT</span> | |
| <div class="stat-bar-bg"> | |
| <div class="stat-bar-fill" style="width: ${hero.intel}%; background-color: var(--success)"></div> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="card-actions"> | |
| <button class="action-btn" onclick="editHero('${hero.id}')"> | |
| <i class="fa-solid fa-pen"></i> Edit | |
| </button> | |
| <button class="action-btn delete" onclick="deleteHero('${hero.id}')"> | |
| <i class="fa-solid fa-trash"></i> | |
| </button> | |
| </div> | |
| `; | |
| grid.appendChild(card); | |
| }); | |
| } | |
| // Form Handling | |
| function openModal(isEdit = false) { | |
| document.getElementById('modalTitle').textContent = isEdit ? 'Edit Hero' : 'New Hero'; | |
| modalOverlay.classList.add('active'); | |
| if(!isEdit) { | |
| heroForm.reset(); | |
| document.getElementById('heroId').value = ''; | |
| // Reset ranges visuals | |
| document.querySelectorAll('input[type=range]').forEach(input => { | |
| input.nextElementSibling.textContent = input.value; | |
| }); | |
| } | |
| } | |
| function closeModal() { | |
| modalOverlay.classList.remove('active'); | |
| } | |
| // Update range number display | |
| function updateRangeValue(input) { | |
| input.nextElementSibling.textContent = input.value; | |
| } | |
| // CRUD Operations | |
| function saveHero(e) { | |
| e.preventDefault(); | |
| const id = document.getElementById('heroId').value; | |
| const name = document.getElementById('heroName').value; | |
| const alias = document.getElementById('heroAlias').value; | |
| const desc = document.getElementById('heroDesc').value; | |
| const strength = document.getElementById('heroStrength').value; | |
| const speed = document.getElementById('heroSpeed').value; | |
| const intel = document.getElementById('heroIntel').value; | |
| if (id) { | |
| // Update | |
| const index = heroes.findIndex(h => h.id === id); | |
| if (index !== -1) { | |
| heroes[index] = { | |
| ...heroes[index], | |
| name, alias, desc, strength, speed, intel | |
| }; | |
| showNotification('Hero updated successfully!'); | |
| } | |
| } else { | |
| // Create | |
| const newHero = { | |
| id: Date.now().toString(), | |
| name, | |
| alias, | |
| desc, | |
| strength, | |
| speed, | |
| intel, | |
| headerColor: `hsl(${Math.random() * 360}, 60%, 30%)`, | |
| avatarColor: `hsl(${Math.random() * 360}, 70%, 50%)` | |
| }; | |
| heroes.push(newHero); | |
| showNotification('New hero recruited!'); | |
| } | |
| saveToStorage(); | |
| renderHeroes(); | |
| closeModal(); | |
| } | |
| function editHero(id) { | |
| const hero = heroes.find(h => h.id === id); | |
| if (!hero) return; | |
| document.getElementById('heroId').value = hero.id; | |
| document.getElementById('heroName').value = hero.name; | |
| document.getElementById('heroAlias').value = hero.alias; | |
| document.getElementById('heroDesc').value = hero.desc; | |
| const sInput = document.getElementById('heroStrength'); | |
| sInput.value = hero.strength; | |
| updateRangeValue(sInput); | |
| const spInput = document.getElementById('heroSpeed'); | |
| spInput.value = hero.speed; | |
| updateRangeValue(spInput); | |
| const iInput = document.getElementById('heroIntel'); | |
| iInput.value = hero.intel; | |
| updateRangeValue(iInput); | |
| openModal(true); | |
| } | |
| function deleteHero(id) { | |
| if(confirm('Are you sure you want to remove this hero from the roster?')) { | |
| heroes = heroes.filter(h => h.id !== id); | |
| saveToStorage(); | |
| renderHeroes(); | |
| showNotification('Hero removed from roster.'); | |
| } | |
| } | |
| // Storage | |
| function saveToStorage() { | |
| localStorage.setItem('heroes', JSON.stringify(heroes)); | |
| } | |
| // Notification System | |
| function showNotification(msg) { | |
| const notif = document.getElementById('notification'); | |
| document.getElementById('notificationMsg').textContent = msg; | |
| notif.classList.add('show'); | |
| setTimeout(() => { | |
| notif.classList.remove('show'); | |
| }, 3000); | |
| } | |
| // Event Listeners | |
| heroForm.addEventListener('submit', saveHero); | |
| // Close modal when clicking outside | |
| modalOverlay.addEventListener('click', (e) => { | |
| if (e.target === modalOverlay) closeModal(); | |
| }); | |
| // Initial Load | |
| renderHeroes(); | |
| </script> | |
| </body> | |
| </html> |