✨ Infinite Craft
by SonicBear35674 lines24.4 KB
<!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.