Spaces:
Running
Running
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>🌴 Project Configuration Tool</title> | |
| <script src="https://cdn.tailwindcss.com"></script> | |
| <script src="https://unpkg.com/feather-icons"></script> | |
| <script src="https://cdn.jsdelivr.net/npm/feather-icons/dist/feather.min.js"></script> | |
| <style> | |
| .counter-btn { | |
| transition: all 0.2s ease; | |
| } | |
| .counter-btn:hover { | |
| transform: scale(1.1); | |
| } | |
| .tab-active { | |
| border-bottom: 3px solid #22c55e; | |
| color: #22c55e; | |
| font-weight: 600; | |
| } | |
| .copy-btn { | |
| transition: all 0.2s ease; | |
| } | |
| .copy-btn:hover { | |
| background-color: #22c55e; | |
| color: white; | |
| } | |
| body { | |
| background-color: #f0fdf4; | |
| } | |
| .bg-tropical-light { | |
| background-color: #dcfce7; | |
| } | |
| .bg-tropical-medium { | |
| background-color: #bbf7d0; | |
| } | |
| .text-tropical-dark { | |
| color: #166534; | |
| } | |
| </style> | |
| </head> | |
| <body class="bg-gray-50 min-h-screen"> | |
| <div class="container mx-auto px-4 py-8"> | |
| <h1 class="text-3xl font-bold text-center mb-8 text-gray-800">Multiplicative Counter Tool</h1> | |
| <div class="flex flex-col lg:flex-row gap-8"> | |
| <!-- Left Panel --> | |
| <div class="w-full lg:w-1/2 bg-tropical-light p-6 rounded-lg shadow-md border border-green-200"> | |
| <h2 class="text-xl font-semibold mb-4 text-gray-700">Project Configuring</h2> | |
| <!-- Tab Navigation for Left Panel --> | |
| <div class="flex border-b mb-4"> | |
| <button class="py-2 px-4 tab-active-left" data-format-left="construction">Construction</button> | |
| <button class="py-2 px-4 text-gray-500" data-format-left="publishing">Publishing</button> | |
| </div> | |
| <div class="space-y-4" id="counter-container"> | |
| <!-- Elements will be added here --> | |
| </div> | |
| </div> | |
| <!-- Right Panel --> | |
| <div class="w-full lg:w-1/2 bg-tropical-light p-6 rounded-lg shadow-md border border-green-200"> | |
| <h2 class="text-xl font-semibold mb-4 text-gray-700">Output</h2> | |
| <!-- Tab Navigation --> | |
| <div class="flex border-b mb-4"> | |
| <button class="py-2 px-4 tab-active" data-format="format1">Construction</button> | |
| <button class="py-2 px-4 text-gray-500" data-format="format2">Publishing</button> | |
| </div> | |
| <!-- Output Content --> | |
| <div id="output-content" class="bg-gray-100 p-4 rounded-lg min-h-[200px]"> | |
| <div id="format1-output" class="space-y-2"> | |
| <!-- Format 1 output will be here --> | |
| </div> | |
| <div id="format2-output" class="space-y-2 hidden"> | |
| <!-- Format 2 output will be here --> | |
| </div> | |
| </div> | |
| <div class="mt-4 flex justify-end"> | |
| <button id="copy-btn" class="copy-btn border border-blue-500 text-blue-500 px-4 py-2 rounded-lg flex items-center"> | |
| <i data-feather="copy" class="mr-2"></i> Copy to Clipboard | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <script> | |
| document.addEventListener('DOMContentLoaded', function() { | |
| feather.replace(); | |
| // Predefined elements for Construction format | |
| const constructionElements = [ | |
| { name: 'homes', label: 'Homes', value: 1, format1: 'Home', format2: 'Home(s)', isConstruction: true }, | |
| { name: 'rooms', label: 'Rooms Per House', value: 1, format1: 'Rooms per Home', format2: 'Rooms per Home', isConstruction: true }, | |
| { name: 'walls', label: 'Walls Per Room', value: 1, format1: 'Walls per Room', format2: 'Walls per Room', isConstruction: true }, | |
| { name: 'bricks', label: 'Bricks Per Wall', value: 1, format1: 'Bricks per Wall', format2: 'Bricks per Wall', isConstruction: true }, | |
| { name: 'total', label: 'Total Bricks', value: 1, format1: 'Total Bricks', format2: 'Total Bricks', isTotal: true, isConstruction: true } | |
| ]; | |
| // Predefined elements for Publishing format | |
| const publishingElements = [ | |
| { name: 'books', label: 'Books', value: 1, format1: 'Book', format2: 'Book', isPublishing: true }, | |
| { name: 'chapters', label: 'Chapters per Book', value: 1, format1: 'Chapters per Book', format2: 'Chapters per Book', isPublishing: true }, | |
| { name: 'pages', label: 'Pages (scenes) per Chapter', value: 1, format1: 'Pages per Chapter', format2: 'Pages per Chapter', isPublishing: true }, | |
| { name: 'panels', label: 'Panels (moments) per Page', value: 1, format1: 'Panels per Page', format2: 'Panels per Page', isPublishing: true }, | |
| { name: 'total_pub', label: 'Total Panels / Moments', value: 1, format1: 'Total Panels', format2: 'Total Panels / Moments', isTotal: true, isPublishing: true } | |
| ]; | |
| const counterContainer = document.getElementById('counter-container'); | |
| const addElementBtn = document.getElementById('add-element'); | |
| const format1Output = document.getElementById('format1-output'); | |
| const format2Output = document.getElementById('format2-output'); | |
| const copyBtn = document.getElementById('copy-btn'); | |
| const tabButtons = document.querySelectorAll('[data-format]'); | |
| let elements = [...constructionElements]; | |
| let currentLeftFormat = 'construction'; | |
| // Initialize with construction elements | |
| updateLeftPanel(); | |
| // Left panel tab switching functionality | |
| const leftTabButtons = document.querySelectorAll('[data-format-left]'); | |
| leftTabButtons.forEach(button => { | |
| button.addEventListener('click', function() { | |
| const format = this.getAttribute('data-format-left'); | |
| // Update active tab | |
| leftTabButtons.forEach(btn => { | |
| btn.classList.remove('tab-active-left', 'text-green-600'); | |
| btn.classList.add('text-gray-500'); | |
| }); | |
| this.classList.add('tab-active-left', 'text-green-600'); | |
| this.classList.remove('text-gray-500'); | |
| currentLeftFormat = format; | |
| // Update elements based on selected format | |
| if (format === 'construction') { | |
| elements = [...constructionElements]; | |
| } else { | |
| elements = [...publishingElements]; | |
| } | |
| updateLeftPanel(); | |
| updateOutput(); | |
| }); | |
| }); | |
| function updateLeftPanel() { | |
| counterContainer.innerHTML = ''; | |
| elements.forEach((element, index) => { | |
| addCounterElement(element, index); | |
| }); | |
| } | |
| // Tab switching functionality | |
| tabButtons.forEach(button => { | |
| button.addEventListener('click', function() { | |
| const format = this.getAttribute('data-format'); | |
| // Update active tab | |
| tabButtons.forEach(btn => { | |
| btn.classList.remove('tab-active', 'text-green-600'); | |
| btn.classList.add('text-gray-500'); | |
| }); | |
| this.classList.add('tab-active', 'text-green-600'); | |
| this.classList.remove('text-gray-500'); | |
| // Show correct output | |
| if (format === 'format1') { | |
| document.getElementById('format1-output').classList.remove('hidden'); | |
| document.getElementById('format2-output').classList.add('hidden'); | |
| } else { | |
| document.getElementById('format1-output').classList.add('hidden'); | |
| document.getElementById('format2-output').classList.remove('hidden'); | |
| } | |
| }); | |
| }); | |
| // Copy to clipboard functionality | |
| copyBtn.addEventListener('click', function() { | |
| const activeFormat = document.querySelector('[data-format].tab-active').getAttribute('data-format'); | |
| const outputElement = document.getElementById(`${activeFormat}-output`); | |
| const textToCopy = Array.from(outputElement.children) | |
| .map(child => child.textContent) | |
| .join('\n'); | |
| navigator.clipboard.writeText(textToCopy).then(() => { | |
| const originalText = this.innerHTML; | |
| this.innerHTML = '<i data-feather="check" class="mr-2"></i> Copied!'; | |
| feather.replace(); | |
| setTimeout(() => { | |
| this.innerHTML = originalText; | |
| feather.replace(); | |
| }, 2000); | |
| }); | |
| }); | |
| function addCounterElement(element, index) { | |
| const counterDiv = document.createElement('div'); | |
| counterDiv.className = 'counter-item flex items-center justify-between p-3 rounded-lg'; | |
| counterDiv.dataset.index = index; | |
| counterDiv.innerHTML = ` | |
| <div class="flex-1"> | |
| <span class="text-gray-700">${element.label}</span> | |
| </div> | |
| <div class="flex items-center"> | |
| <button class="counter-btn decrease w-8 h-8 rounded-full bg-gray-200 flex items-center justify-center mr-2"> | |
| <i data-feather="minus" class="w-4 h-4"></i> | |
| </button> | |
| <span class="value-display w-10 text-center font-semibold">${element.value}</span> | |
| <button class="counter-btn increase w-8 h-8 rounded-full bg-green-100 text-green-700 flex items-center justify-center ml-2 hover:bg-green-200"> | |
| <i data-feather="plus" class="w-4 h-4"></i> | |
| </button> | |
| </div> | |
| ${element.isCustom ? ` | |
| <button class="delete-btn ml-4 text-red-500 hover:text-red-700"> | |
| <i data-feather="trash-2" class="w-4 h-4"></i> | |
| </button> | |
| ` : ''} | |
| `; | |
| counterContainer.appendChild(counterDiv); | |
| feather.replace(); | |
| // Add event listeners for the buttons | |
| const decreaseBtn = counterDiv.querySelector('.decrease'); | |
| const increaseBtn = counterDiv.querySelector('.increase'); | |
| const valueDisplay = counterDiv.querySelector('.value-display'); | |
| const deleteBtn = counterDiv.querySelector('.delete-btn'); | |
| decreaseBtn.addEventListener('click', () => { | |
| if (elements[index].value > 1) { | |
| elements[index].value--; | |
| valueDisplay.textContent = elements[index].value; | |
| updateOutput(); | |
| } | |
| }); | |
| increaseBtn.addEventListener('click', () => { | |
| elements[index].value++; | |
| valueDisplay.textContent = elements[index].value; | |
| updateOutput(); | |
| }); | |
| if (deleteBtn) { | |
| deleteBtn.addEventListener('click', () => { | |
| elements.splice(index, 1); | |
| counterDiv.remove(); | |
| // Update indices for remaining elements | |
| document.querySelectorAll('.counter-item').forEach((item, i) => { | |
| item.dataset.index = i; | |
| }); | |
| updateOutput(); | |
| }); | |
| } | |
| } | |
| function updateOutput() { | |
| // Calculate total if needed | |
| if (elements.some(el => el.isTotal)) { | |
| const totalIndex = elements.findIndex(el => el.isTotal); | |
| if (totalIndex !== -1) { | |
| let calculatedTotal = 1; | |
| for (let i = 0; i < totalIndex; i++) { | |
| calculatedTotal *= elements[i].value; | |
| } | |
| elements[totalIndex].value = calculatedTotal; | |
| // Update the display for the total element | |
| const totalDisplay = document.querySelector(`.counter-item[data-index="${totalIndex}"] .value-display`); | |
| if (totalDisplay) { | |
| totalDisplay.textContent = calculatedTotal; | |
| } | |
| } | |
| } | |
| // Update format 1 output | |
| format1Output.innerHTML = ''; | |
| elements.forEach(element => { | |
| if (!element.isTotal) { | |
| const outputLine = document.createElement('div'); | |
| outputLine.className = 'text-gray-700'; | |
| outputLine.textContent = `${element.value} ${element.format1}${element.value !== 1 ? 's' : ''}`; | |
| format1Output.appendChild(outputLine); | |
| } | |
| }); | |
| // Add total if it exists | |
| const totalElement = elements.find(el => el.isTotal); | |
| if (totalElement) { | |
| const totalLine = document.createElement('div'); | |
| totalLine.className = 'font-bold text-gray-800 mt-2 pt-2 border-t'; | |
| totalLine.textContent = `${totalElement.value} ${totalElement.format1}${totalElement.value !== 1 ? 's' : ''}`; | |
| format1Output.appendChild(totalLine); | |
| } | |
| // Update format 2 output | |
| format2Output.innerHTML = ''; | |
| elements.forEach(element => { | |
| if (!element.isTotal) { | |
| const outputLine = document.createElement('div'); | |
| outputLine.className = 'text-gray-700'; | |
| outputLine.textContent = `${element.value} ${element.format2}${element.value !== 1 ? 's' : ''}`; | |
| format2Output.appendChild(outputLine); | |
| } | |
| }); | |
| // Add total if it exists | |
| if (totalElement) { | |
| const totalLine = document.createElement('div'); | |
| totalLine.className = 'font-bold text-gray-800 mt-2 pt-2 border-t'; | |
| totalLine.textContent = `${totalElement.value} ${totalElement.format2}${totalElement.value !== 1 ? 's' : ''}`; | |
| format2Output.appendChild(totalLine); | |
| } | |
| } | |
| // Initial output update | |
| updateOutput(); | |
| // Add CSS for left panel tabs | |
| const style = document.createElement('style'); | |
| style.textContent = ` | |
| .tab-active-left { | |
| border-bottom: 3px solid #22c55e; | |
| color: #22c55e; | |
| font-weight: 600; | |
| } | |
| .counter-item { | |
| background-color: #f0fdf4; | |
| border: 1px solid #bbf7d0; | |
| } | |
| `; | |
| document.head.appendChild(style); | |
| }); | |
| </script> | |
| </body> | |
| </html> | |