🎮ArcadeLab

✨ Infinite Craft

by SonicBear35
674 lines24.4 KB
▶ Play
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
    <title>✨ Infinite Craft</title>
    <style>
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
            user-select: none;
            -webkit-tap-highlight-color: transparent;
        }
        body {
            background: radial-gradient(ellipse at center, #1a1a2e, #0a0a0a);
            min-height: 100vh;
            font-family: 'Segoe UI', Arial, sans-serif;
            touch-action: manipulation;
            display: flex;
            justify-content: center;
            align-items: center;
            padding: 12px;
        }
        #app {
            background: rgba(30, 30, 50, 0.9);
            border-radius: 40px;
            padding: 20px 20px 25px;
            max-width: 700px;
            width: 100%;
            box-shadow: 0 20px 60px rgba(0,0,0,0.9);
            border: 2px solid rgba(255, 215, 0, 0.1);
            max-height: 98vh;
            overflow-y: auto;
        }
        .header {
            display: flex;
            justify-content: space-between;
            align-items: center;
            padding-bottom: 14px;
            color: #f0e8d5;
            flex-wrap: wrap;
            gap: 8px;
        }
        .title {
            font-size: 1.8rem;
            font-weight: 900;
            background: linear-gradient(135deg, #ffd700, #ff6b6b);
            -webkit-background-clip: text;
            -webkit-text-fill-color: transparent;
        }
        .stats {
            display: flex;
            gap: 16px;
            background: rgba(0,0,0,0.3);
            padding: 6px 16px;
            border-radius: 30px;
            font-size: 0.8rem;
            color: #aaa;
        }
        .stats span {
            color: #ffd700;
            font-weight: bold;
            font-size: 1rem;
        }
        .craft-area {
            background: rgba(0,0,0,0.3);
            border-radius: 20px;
            padding: 16px;
            margin-bottom: 16px;
            display: flex;
            flex-direction: column;
            gap: 12px;
        }
        .craft-slots {
            display: flex;
            justify-content: center;
            align-items: center;
            gap: 16px;
            flex-wrap: wrap;
        }
        .slot {
            width: 80px;
            height: 80px;
            background: rgba(255,255,255,0.05);
            border-radius: 16px;
            border: 2px dashed rgba(255,255,255,0.15);
            display: flex;
            align-items: center;
            justify-content: center;
            font-size: 1.2rem;
            font-weight: bold;
            color: #f0e8d5;
            transition: all 0.2s;
            cursor: pointer;
            text-align: center;
            padding: 4px;
            word-break: break-word;
        }
        .slot.filled {
            border-color: #ffd700;
            background: rgba(255, 215, 0, 0.1);
        }
        .slot.selected {
            border-color: #64c8ff;
            box-shadow: 0 0 20px rgba(100, 200, 255, 0.3);
        }
        .slot .remove {
            position: absolute;
            top: -6px;
            right: -6px;
            background: rgba(255,0,0,0.6);
            border-radius: 50%;
            width: 20px;
            height: 20px;
            display: flex;
            align-items: center;
            justify-content: center;
            font-size: 0.7rem;
            color: #fff;
            cursor: pointer;
        }
        .slot-wrap {
            position: relative;
        }
        .combine-btn {
            background: rgba(255, 215, 0, 0.15);
            border: 2px solid #ffd700;
            color: #ffd700;
            padding: 10px 28px;
            border-radius: 40px;
            font-size: 1.1rem;
            font-weight: bold;
            cursor: pointer;
            transition: all 0.2s;
            touch-action: manipulation;
        }
        .combine-btn:active {
            transform: scale(0.95);
            background: rgba(255, 215, 0, 0.3);
        }
        .combine-btn:disabled {
            opacity: 0.3;
            pointer-events: none;
        }
        .message {
            text-align: center;
            color: #ffd700;
            font-size: 0.95rem;
            min-height: 1.6rem;
            padding: 4px;
        }
        .discovery-list {
            display: flex;
            flex-wrap: wrap;
            gap: 8px;
            padding: 8px 0;
            justify-content: center;
        }
        .discovery-item {
            background: rgba(255,255,255,0.06);
            border-radius: 30px;
            padding: 6px 16px;
            font-size: 0.9rem;
            color: #f0e8d5;
            border: 1px solid rgba(255,255,255,0.08);
            transition: all 0.3s;
            cursor: default;
            animation: pop 0.3s ease;
        }
        .discovery-item.new {
            border-color: #ffd700;
            background: rgba(255, 215, 0, 0.15);
            animation: pop 0.5s ease;
        }
        @keyframes pop {
            0% { transform: scale(0.5); opacity: 0; }
            100% { transform: scale(1); opacity: 1; }
        }
        .search-box {
            width: 100%;
            padding: 10px 16px;
            border-radius: 30px;
            border: 1px solid rgba(255,255,255,0.1);
            background: rgba(0,0,0,0.3);
            color: #f0e8d5;
            font-size: 0.95rem;
            outline: none;
            margin-bottom: 8px;
        }
        .search-box:focus {
            border-color: #ffd700;
        }
        .category-tabs {
            display: flex;
            gap: 8px;
            flex-wrap: wrap;
            margin: 6px 0 10px;
        }
        .category-tab {
            background: rgba(0,0,0,0.2);
            border: 1px solid rgba(255,255,255,0.05);
            padding: 4px 14px;
            border-radius: 30px;
            color: #8a8a8a;
            font-size: 0.8rem;
            cursor: pointer;
            transition: all 0.2s;
        }
        .category-tab.active {
            border-color: #ffd700;
            color: #ffd700;
            background: rgba(255, 215, 0, 0.1);
        }
        .category-tab:active {
            transform: scale(0.95);
        }
        .reset-btn {
            background: rgba(255,0,0,0.1);
            border: 1px solid rgba(255,0,0,0.2);
            color: #ff6b6b;
            padding: 4px 14px;
            border-radius: 30px;
            font-size: 0.8rem;
            cursor: pointer;
            transition: all 0.2s;
        }
        .reset-btn:active {
            transform: scale(0.95);
        }
        .footer {
            display: flex;
            justify-content: space-between;
            align-items: center;
            margin-top: 10px;
            flex-wrap: wrap;
            gap: 8px;
        }
        @media (max-width: 500px) {
            .slot { width: 64px; height: 64px; font-size: 1rem; }
            .title { font-size: 1.4rem; }
            .stats { font-size: 0.7rem; padding: 4px 12px; }
            .combine-btn { padding: 8px 20px; font-size: 1rem; }
        }
        @media (max-width: 400px) {
            .slot { width: 54px; height: 54px; font-size: 0.8rem; }
            .discovery-item { font-size: 0.75rem; padding: 4px 12px; }
        }
    </style>
</head>
<body>
    <div id="app">
        <div class="header">
            <div class="title">✨ Infinite Craft</div>
            <div class="stats">
                <span id="countDisplay">0</span> elements
            </div>
        </div>

        <!-- Crafting Area -->
        <div class="craft-area">
            <div class="craft-slots">
                <div class="slot-wrap">
                    <div class="slot" id="slot1" data-index="0">Tap to select</div>
                </div>
                <span style="color:#8a8a8a;font-size:1.5rem;">+</span>
                <div class="slot-wrap">
                    <div class="slot" id="slot2" data-index="1">Tap to select</div>
                </div>
                <button class="combine-btn" id="combineBtn">⚡ Combine</button>
            </div>
            <div class="message" id="messageBox">Select two elements to combine</div>
        </div>

        <!-- Discovery List -->
        <input class="search-box" id="searchBox" placeholder="🔍 Search elements..." />
        <div class="category-tabs" id="categoryTabs">
            <button class="category-tab active" data-category="all">All</button>
            <button class="category-tab" data-category="elements">Elements</button>
            <button class="category-tab" data-category="crafted">Crafted</button>
        </div>
        <div class="discovery-list" id="discoveryList"></div>

        <div class="footer">
            <button class="reset-btn" id="resetBtn">🗑️ Reset Progress</button>
            <span style="color:#4a4a6a;font-size:0.7rem;">💾 Auto-saved</span>
        </div>
    </div>

    <script>
        (function() {
            // ---------- Element Database ----------
            // Base elements (always discovered)
            const BASE_ELEMENTS = ['Water', 'Fire', 'Earth', 'Air'];

            // Combination map: key = "element1|element2" (sorted) => result
            // We'll build a rich combination set.
            const COMBINATIONS = {
                // Water + Fire = Steam
                'Water|Fire': 'Steam',
                'Water|Earth': 'Mud',
                'Water|Air': 'Rain',
                'Water|Water': 'Lake',
                'Water|Steam': 'Cloud',
                'Water|Lava': 'Stone',
                'Water|Stone': 'Sand',
                'Water|Sand': 'Beach',
                'Water|Beach': 'Sea',
                'Water|Sea': 'Ocean',
                'Water|Ocean': 'Wave',
                'Water|Wave': 'Tsunami',
                'Water|Cloud': 'Rain',
                'Water|Rain': 'Flood',
                'Water|Mud': 'Swamp',
                'Water|Swamp': 'Marsh',
                'Water|Marsh': 'Bog',
                'Water|Bog': 'Peat',
                'Water|Peat': 'Coal',
                'Water|Coal': 'Diamond',
                // Fire + Earth = Lava
                'Fire|Earth': 'Lava',
                'Fire|Air': 'Smoke',
                'Fire|Fire': 'Inferno',
                'Fire|Lava': 'Magma',
                'Fire|Stone': 'Metal',
                'Fire|Metal': 'Forge',
                'Fire|Forge': 'Smithy',
                'Fire|Wood': 'Charcoal',
                'Fire|Charcoal': 'Ash',
                'Fire|Ash': 'Phoenix',
                'Fire|Phoenix': 'Rebirth',
                'Fire|Smoke': 'Fog',
                'Fire|Fog': 'Mist',
                'Fire|Mist': 'Haze',
                'Fire|Haze': 'Smog',
                // Earth + Air = Dust
                'Earth|Air': 'Dust',
                'Earth|Earth': 'Mountain',
                'Earth|Mountain': 'Hill',
                'Earth|Hill': 'Valley',
                'Earth|Valley': 'Canyon',
                'Earth|Canyon': 'Gorge',
                'Earth|Gorge': 'Ravine',
                'Earth|Dust': 'Sandstorm',
                'Earth|Sandstorm': 'Dune',
                'Earth|Dune': 'Desert',
                'Earth|Desert': 'Oasis',
                'Earth|Oasis': 'Palm',
                'Earth|Palm': 'Coconut',
                // Air + Air = Wind
                'Air|Air': 'Wind',
                'Air|Wind': 'Storm',
                'Air|Storm': 'Hurricane',
                'Air|Hurricane': 'Tornado',
                'Air|Tornado': 'Vortex',
                'Air|Vortex': 'Whirlwind',
                'Air|Dust': 'Sandstorm',
                'Air|Sandstorm': 'Dust Devil',
                'Air|Cloud': 'Sky',
                'Air|Sky': 'Heaven',
                'Air|Heaven': 'Angel',
                'Air|Angel': 'Divine',
                // Advanced
                'Steam|Lava': 'Geyser',
                'Geyser|Water': 'Hot Spring',
                'Hot Spring|Stone': 'Bath',
                'Bath|Bubble': 'Spa',
                'Spa|Relax': 'Peace',
                'Peace|War': 'Truce',
                'Truce|Treaty': 'Alliance',
                'Alliance|Nation': 'Empire',
                // Wood stuff
                'Tree|Fire': 'Charcoal',
                'Tree|Water': 'Bark',
                'Bark|Water': 'Canoe',
                'Canoe|River': 'Raft',
                'Raft|Ocean': 'Ship',
                'Ship|Wind': 'Sailboat',
                'Sailboat|Storm': 'Wreck',
                'Wreck|Treasure': 'Pirate',
                // More
                'Mud|Fire': 'Brick',
                'Brick|Brick': 'Wall',
                'Wall|Wall': 'Fortress',
                'Fortress|Army': 'Kingdom',
                'Kingdom|Crown': 'Monarchy',
                'Monarchy|Revolution': 'Democracy',
                'Democracy|Vote': 'Election',
                'Election|Candidate': 'President',
                // Food
                'Wheat|Fire': 'Bread',
                'Bread|Butter': 'Toast',
                'Toast|Jam': 'Breakfast',
                'Breakfast|Coffee': 'Morning',
                'Morning|Sun': 'Day',
                'Day|Night': 'Twilight',
                'Twilight|Moon': 'Night',
                'Night|Star': 'Constellation',
                'Constellation|Zodiac': 'Horoscope',
                // Animals
                'Egg|Chicken': 'Hen',
                'Hen|Rooster': 'Farm',
                'Farm|Cow': 'Dairy',
                'Dairy|Milk': 'Cheese',
                'Cheese|Wine': 'Fondue',
                'Fondue|Party': 'Celebration',
                'Celebration|Fireworks': 'Festival',
                // Tech
                'Metal|Electricity': 'Wire',
                'Wire|Glass': 'Lightbulb',
                'Lightbulb|Idea': 'Innovation',
                'Innovation|Factory': 'Industry',
                'Industry|Robot': 'Automation',
                'Automation|AI': 'Singularity',
                // just for fun
                'Water|Fire|Earth|Air': 'Primordial',
                'Primordial|Chaos': 'Creation',
                'Creation|Time': 'Eternity',
            };

            // Also support multi-combinations? We'll only support two at a time.

            // State
            let discovered = new Set();
            let allElements = new Set();

            // DOM refs
            const slot1 = document.getElementById('slot1');
            const slot2 = document.getElementById('slot2');
            const combineBtn = document.getElementById('combineBtn');
            const messageBox = document.getElementById('messageBox');
            const discoveryList = document.getElementById('discoveryList');
            const countDisplay = document.getElementById('countDisplay');
            const searchBox = document.getElementById('searchBox');
            const categoryTabs = document.querySelectorAll('.category-tab');
            const resetBtn = document.getElementById('resetBtn');

            // Selected slots: store element names or null
            let selected = [null, null]; // index 0 = slot1, 1 = slot2

            // Load from localStorage
            function loadProgress() {
                try {
                    const saved = localStorage.getItem('infiniteCraft_discovered');
                    if (saved) {
                        const arr = JSON.parse(saved);
                        discovered = new Set(arr);
                    } else {
                        // Base elements
                        discovered = new Set(BASE_ELEMENTS);
                    }
                } catch (e) {
                    discovered = new Set(BASE_ELEMENTS);
                }
                // Ensure base elements are always present
                BASE_ELEMENTS.forEach(el => discovered.add(el));

                // Build allElements from discovered + combinations
                updateAllElements();
            }

            function saveProgress() {
                const arr = Array.from(discovered);
                localStorage.setItem('infiniteCraft_discovered', JSON.stringify(arr));
            }

            function updateAllElements() {
                allElements = new Set(discovered);
                // Add all combination results that are not discovered yet
                for (let key in COMBINATIONS) {
                    const result = COMBINATIONS[key];
                    // If the required elements are discovered, add result to allElements
                    const [a, b] = key.split('|');
                    if (discovered.has(a) && discovered.has(b)) {
                        allElements.add(result);
                    }
                }
            }

            // ---------- Render ----------
            function render() {
                // Update counts
                countDisplay.textContent = discovered.size;

                // Render discovery list based on search and category
                const query = searchBox.value.toLowerCase().trim();
                const category = document.querySelector('.category-tab.active')?.dataset.category || 'all';

                let list = Array.from(discovered).sort();
                if (category === 'elements') {
                    list = list.filter(el => BASE_ELEMENTS.includes(el));
                } else if (category === 'crafted') {
                    list = list.filter(el => !BASE_ELEMENTS.includes(el));
                }

                if (query) {
                    list = list.filter(el => el.toLowerCase().includes(query));
                }

                discoveryList.innerHTML = '';
                for (let el of list) {
                    const div = document.createElement('div');
                    div.className = 'discovery-item';
                    if (el === recentlyDiscovered && !BASE_ELEMENTS.includes(el)) {
                        div.classList.add('new');
                        // Remove new class after animation
                        setTimeout(() => div.classList.remove('new'), 800);
                    }
                    div.textContent = el;
                    // Click to fill slot
                    div.addEventListener('click', (e) => {
                        e.stopPropagation();
                        // Fill the first empty slot, or replace the first slot
                        if (!selected[0] || selected[0] === '') {
                            selectSlot(0, el);
                        } else if (!selected[1] || selected[1] === '') {
                            selectSlot(1, el);
                        } else {
                            // Replace first slot
                            selectSlot(0, el);
                        }
                    });
                    discoveryList.appendChild(div);
                }
                // Reset recentlyDiscovered after render
                if (recentlyDiscovered) {
                    setTimeout(() => { recentlyDiscovered = null; }, 500);
                }

                // Update slot displays
                updateSlots();

                // Enable/disable combine button
                combineBtn.disabled = !(selected[0] && selected[1]);
            }

            let recentlyDiscovered = null;

            function updateSlots() {
                slot1.textContent = selected[0] || 'Tap to select';
                slot1.className = 'slot' + (selected[0] ? ' filled' : '');
                slot2.textContent = selected[1] || 'Tap to select';
                slot2.className = 'slot' + (selected[1] ? ' filled' : '');
                // Remove selection highlight
                slot1.classList.remove('selected');
                slot2.classList.remove('selected');
            }

            function selectSlot(index, element) {
                if (!element) return;
                // If element already in that slot, deselect
                if (selected[index] === element) {
                    selected[index] = null;
                } else {
                    selected[index] = element;
                }
                render();
                // Highlight the slot
                if (index === 0) slot1.classList.add('selected');
                else slot2.classList.add('selected');
            }

            // Click on slot to clear or select
            slot1.addEventListener('click', () => {
                if (selected[0]) {
                    // If clicked again, maybe clear? Or open picker? We'll clear.
                    selected[0] = null;
                    render();
                } else {
                    // prompt to pick from list? or we can just show message
                    messageBox.textContent = 'Click an element from the list below to select it.';
                }
            });
            slot2.addEventListener('click', () => {
                if (selected[1]) {
                    selected[1] = null;
                    render();
                } else {
                    messageBox.textContent = 'Click an element from the list below to select it.';
                }
            });

            // ---------- Combine Logic ----------
            function combine() {
                const a = selected[0];
                const b = selected[1];
                if (!a || !b) {
                    messageBox.textContent = 'Please select two elements.';
                    return;
                }
                // Check if combination exists
                const key1 = a + '|' + b;
                const key2 = b + '|' + a;
                let result = COMBINATIONS[key1] || COMBINATIONS[key2];
                if (result) {
                    // Check if result is already discovered
                    if (discovered.has(result)) {
                        messageBox.textContent = `✅ You already have ${result}!`;
                    } else {
                        discovered.add(result);
                        recentlyDiscovered = result;
                        saveProgress();
                        updateAllElements();
                        messageBox.textContent = `🎉 New discovery: ${result}!`;
                        // Clear slots after successful combine? We'll keep them.
                        // Optionally clear one slot to encourage more combining.
                        // We'll clear both for convenience.
                        selected = [null, null];
                        render();
                        // Auto-save
                        saveProgress();
                    }
                } else {
                    messageBox.textContent = `❌ No combination found for ${a} + ${b}`;
                }
            }

            combineBtn.addEventListener('click', combine);

            // ---------- Reset ----------
            resetBtn.addEventListener('click', () => {
                if (confirm('Reset all progress? This cannot be undone.')) {
                    localStorage.removeItem('infiniteCraft_discovered');
                    discovered = new Set(BASE_ELEMENTS);
                    selected = [null, null];
                    recentlyDiscovered = null;
                    saveProgress();
                    updateAllElements();
                    render();
                    messageBox.textContent = 'Progress reset.';
                }
            });

            // ---------- Search & Category ----------
            searchBox.addEventListener('input', render);

            categoryTabs.forEach(tab => {
                tab.addEventListener('click', () => {
                    categoryTabs.forEach(t => t.classList.remove('active'));
                    tab.classList.add('active');
                    render();
                });
            });

            // ---------- Keyboard shortcuts ----------
            document.addEventListener('keydown', (e) => {
                if (e.key === 'Enter' && !e.shiftKey) {
                    if (combineBtn.disabled) return;
                    combine();
                    e.preventDefault();
                }
                if (e.key === 'r' || e.key === 'R') {
                    // reload? no
                }
                if (e.key === 'Escape') {
                    selected = [null, null];
                    render();
                    messageBox.textContent = 'Cleared selection.';
                }
            });

            // ---------- Init ----------
            loadProgress();
            updateAllElements();
            render();
            messageBox.textContent = 'Select two elements and combine!';

            // Auto-save on page unload
            window.addEventListener('beforeunload', saveProgress);

        })();
    </script>
</body>
</html>

Game Source: ✨ Infinite Craft

Creator: SonicBear35

Libraries: none

Complexity: complex (674 lines, 24.4 KB)

The full source code is displayed above on this page.

Remix Instructions

To remix this game, copy the source code above and modify it. Add a ARCADELAB header at the top with "remix_of: infinite-craft-sonicbear35" to link back to the original. Then publish at arcadelab.ai/publish.