made a page to generate crafting trees (TODO: docs + testing)

This commit is contained in:
2026-07-19 00:47:00 +02:00
parent 0fb8cc4974
commit 1f9e0e2b04
11 changed files with 497 additions and 26 deletions
+3 -1
View File
@@ -5,7 +5,8 @@ const PAGES = {
TOYS: 2,
LICENSE: 3,
LOADING: 4,
LIBS: 5
LIBS: 5,
CRAFTING: 6
}
const CONSOLE_COLORS = {
red: "color: #FF0000;",
@@ -189,6 +190,7 @@ document.addEventListener("DOMContentLoaded", async function () {
case (PAGES.LICENSE): loadingScreenStars(); await loadingScreenLicense(); break
case (PAGES.TOYS): loadingScreenStars(); break
case (PAGES.LIBS): loadingScreenStars(); break
case (PAGES.CRAFTING): loadingScreenCrafting(); break
}
if (navBarPresent) {
+40
View File
@@ -64,6 +64,9 @@ canvas {
margin: calc(3px * var(--pxDensity)) calc(3px * var(--pxDensity)) 0% 0%;
width: round(auto, var(--pxDensity));
/*height: calc(6px * var(--pxDensity));*/
border: none;
color: #ffffff;
font-size: inherit;
}
.switch {
position: relative;
@@ -110,3 +113,40 @@ canvas {
image-rendering: pixelated;
transform: translate(-50%, -50%);
}
button {
display: inline-block;
background-color: var(--navBarBtn_Inner);
box-shadow: calc(1px * var(--pxDensity)) 0px var(--navBarBtn_Border),
calc(-1px * var(--pxDensity)) 0px var(--navBarBtn_Border),
0px calc(1px * var(--pxDensity)) var(--navBarBtn_Border),
0px calc(-1px * var(--pxDensity)) var(--navBarBtn_Border);
padding: calc(1px * var(--pxDensity));
margin: calc(3px * var(--pxDensity)) calc(3px * var(--pxDensity)) 0% 0%;
width: round(auto, var(--pxDensity));
/*height: calc(6px * var(--pxDensity));*/
border: none;
color: #ffffff;
font-size: inherit;
}
button:hover {
filter: brightness(1.35) drop-shadow(0px calc(1px * var(--pxDensity)) rgba(0, 0, 0, 0.5));
cursor: pointer;
}
button:active {
filter: brightness(0.75);
transform: translateY(calc(1px * var(--pxDensity)));
}
button:disabled, button[disabled] {
filter: brightness(0.75) drop-shadow(0px calc(1px * var(--pxDensity)) rgba(0, 0, 0, 0.5));
cursor: not-allowed;
}
button:disabled:hover, button[disabled]:hover {
filter: brightness(0.75) drop-shadow(0px calc(1px * var(--pxDensity)) rgba(0, 0, 0, 0.5));
cursor: not-allowed;
}
button:disabled:active, button[disabled]:active {
filter: brightness(0.75) drop-shadow(0px calc(1px * var(--pxDensity)) rgba(0, 0, 0, 0.5));
cursor: not-allowed;
transform: none;
}
+3 -21
View File
@@ -20,27 +20,9 @@ li {
padding: 0%;
border: none;
filter: drop-shadow(0px calc(1px * var(--pxDensity)) rgba(0, 0, 0, 0.5));
}
button:hover {
filter: brightness(1.35) drop-shadow(0px calc(1px * var(--pxDensity)) rgba(0, 0, 0, 0.5));
cursor: pointer;
}
button:active {
filter: brightness(0.75);
transform: translateY(calc(1px * var(--pxDensity)));
}
.button:disabled, button[disabled] {
filter: brightness(0.75) drop-shadow(0px calc(1px * var(--pxDensity)) rgba(0, 0, 0, 0.5));
cursor: not-allowed;
}
.button:disabled:hover, button[disabled]:hover {
filter: brightness(0.75) drop-shadow(0px calc(1px * var(--pxDensity)) rgba(0, 0, 0, 0.5));
cursor: not-allowed;
}
.button:disabled:active, button[disabled]:active {
filter: brightness(0.75) drop-shadow(0px calc(1px * var(--pxDensity)) rgba(0, 0, 0, 0.5));
cursor: not-allowed;
transform: none;
margin: 0%;
box-shadow: none;
width: auto;
}
.navBtnImg {
width: calc(12px * var(--pxDensity));
+68
View File
@@ -0,0 +1,68 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, height=device-height, initial-scale=1.0, user-scalable=no">
<link rel="stylesheet" href="/global/navBar.css">
<link rel="stylesheet" href="/global/globalStyle.css">
<link rel="stylesheet" href="/global/loadingScreen.css">
<link rel="stylesheet" href="style.css">
<link rel="stylesheet" href="tree.css">
<link rel="icon" type="image/png" href="/assets/favicon_32x32.png">
<title>Byte Dice - Crafting</title>
<meta name="robots" content="index, follow">
<link rel="canonical" href="https://bytedice.net/toys">
<meta name="description" content="A crafting-tree-like generation tool">
<meta name="keywords" content="Byte Dice, pixel art, procedural generation, game art, digital art, ByteDice showcase">
<meta name="author" content="Byte Dice">
<meta property="og:title" content="Byte Dice">
<meta property="og:description" content="A crafting-tree-like generation tool">
<meta property="og:url" content="https://bytedice.net/toys/crafting">
<meta property="og:type" content="website">
</head>
<body>
<div id="mainDiv">
<img src="/assets/backgrounds/starNoise_512x512.png" draggable="false" id="starNoise">
<canvas id="starfallCanvas"></canvas>
<div id="treeStaticContainer">
<div id="treeScaleContainer">
<div id="trees"></div>
</div>
</div>
<div class="tomlInputContainer" id="tomlInputContainer">
<div class="tomlInputGrid">
<label><a href="./docs">Tutorial / Docs</a></label>
<textarea id="tomlInput" name="tomlInput"></textarea>
<button id="generateButton">Generate Tree</button>
</div>
<button id="collapseButton">&lt;</button>
</div>
<div id="navBarContainer" data-alt="byte"></div>
</div>
<div id="loadingScreen"></div>
<!-- global -->
<script src="/global/globalScript.js"></script>
<script src="/global/pxDensity.js"></script>
<script src="/global/loadingScreen.js"></script>
<script src="/global/navBar.js"></script>
<script src="/global/screenTransition.js"></script>
<!-- local -->
<script src="script.js"></script>
<script type="module" src="tree_generator.js"></script>
<script src="/stars.js"></script>
</body>
</html>
+81
View File
@@ -0,0 +1,81 @@
CURRENT_PAGE = PAGES.CRAFTING
let tomlInputContainer = document.getElementById("tomlInputContainer")
let collapseButton = document.getElementById("collapseButton")
let treeStatic = document.getElementById("treeStaticContainer")
let treeScale = document.getElementById("treeScaleContainer")
let mouseX = 0
let mouseY = 0
let dragX = 0
let dragY = 0
let dragOffsetX = 0
let dragOffsetY = 0
let isDragging = false
let scale = 1
let isCollapsed = false
document.addEventListener("mousemove", function(event) {
mouseX = event.clientX;
mouseY = event.clientY;
})
function collapseInput() {
const COLLAPSE = "collapse 0.5s cubic-bezier(0, 0, 0, 1.46) forwards"
tomlInputContainer.style.animation = COLLAPSE
isCollapsed = true
collapseButton.textContent = ">"
}
function unCollapseInput() {
const UN_COLLAPSE = "un-collapse 0.5s cubic-bezier(0, 0, 0, 1.46) forwards"
tomlInputContainer.style.animation = UN_COLLAPSE
isCollapsed = false
collapseButton.textContent = "<"
}
collapseButton.onclick = function() {
if (!isCollapsed) { collapseInput(); }
else { unCollapseInput(); }
}
function loadingScreenCrafting() {
unCollapseInput();
}
function drag_trees(mouse_offset) {
dragX = (mouseX - dragOffsetX) / scale
dragY = (mouseY - dragOffsetY) / scale
trees.style.transform = `translate(${dragX}px, ${dragY}px)`
}
function on_start_drag() {
dragOffsetX = mouseX - dragX * scale
dragOffsetY = mouseY - dragY * scale
isDragging = true
}
function dragLoop() {
if (isDragging) { drag_trees() }
requestAnimationFrame(dragLoop)
}
treeStatic.onmousedown = on_start_drag
document.addEventListener("mouseup", function() { isDragging = false })
treeStatic.addEventListener("wheel", function(event) {
scale += (-event.deltaY / 500)
scale = Math.max(0.2, scale)
treeScale.style.transform = `scale(${scale})`
})
document.addEventListener("DOMContentLoaded", dragLoop)
+84
View File
@@ -0,0 +1,84 @@
.tomlInputContainer textarea {
font-family: "Roboto", sans-serif;
background-color: var(--navBarBtn_Inner);
box-shadow: calc(1px * var(--pxDensity)) 0px var(--navBarBtn_Border),
calc(-1px * var(--pxDensity)) 0px var(--navBarBtn_Border),
0px calc(1px * var(--pxDensity)) var(--navBarBtn_Border),
0px calc(-1px * var(--pxDensity)) var(--navBarBtn_Border);
padding: calc(1px * var(--pxDensity));
border: none;
font-size: min(5vmin, 1.2vmax, 0.9rem);
margin: calc(3px * var(--pxDensity)) 0% 0% 0%;
box-sizing: border-box;
width: calc(60px * var(--pxDensity));
/*height: calc(100px * var(--pxDensity));*/
height: 65vh;
color: inherit;
display: inline-block;
text-align: left;
resize: none
}
.tomlInputContainer textarea:hover {
filter: brightness(1.35);
cursor: pointer;
}
.tomlInputContainer label {
background-color: var(--navBarBtn_Inner);
box-shadow: calc(1px * var(--pxDensity)) 0px var(--navBarBtn_Border),
calc(-1px * var(--pxDensity)) 0px var(--navBarBtn_Border),
0px calc(1px * var(--pxDensity)) var(--navBarBtn_Border),
0px calc(-1px * var(--pxDensity)) var(--navBarBtn_Border);
padding: calc(1px * var(--pxDensity));
box-sizing: border-box;
width: round(auto, var(--pxDensity));
height: calc(6px * var(--pxDensity));
font-weight: 800;
text-align: center;
}
.tomlInputGrid {
display: grid;
}
.tomlInputContainer {
position: relative;
display: inline-flex;
margin: 0% auto 0% auto;
width: max-content;
max-height: 70vh;
padding: calc(2px * var(--pxDensity));
max-width: min(600px, 100vw, 100vw);
font-size: min(1.5rem, calc(4px * var(--pxDensity)));
box-sizing: border-box;
}
#generateButton {
width: 100%;
height: calc(6px * var(--pxDensity));
}
#collapseButton {
margin: 0% calc(3px * var(--pxDensity));
height: calc(6px * var(--pxDensity));
width: calc(6px * var(--pxDensity));
aspect-ratio: 1 / 1;
}
@keyframes collapse {
0% { transform: translateX(0%); }
100% { transform: translateX(calc(-63px * var(--pxDensity))); }
}
@keyframes un-collapse {
0% { transform: translateX(calc(-63px * var(--pxDensity))); }
100% { transform: translateX(0%); }
}
+85
View File
@@ -0,0 +1,85 @@
#trees {
position: absolute;
left: 0%;
top: 0%;
width: max-content;
height: max-content;
padding: calc(16px * var(--pxDensity)) calc(2px * var(--pxDensity));
overflow: visible;
display: flex;
flex-direction: column;
gap: calc(5px * var(--pxDensity));
user-select: none;
}
.tree {
display: inline-flex;
justify-content: center;
width: min-content;
height: min-content;
padding: calc(3px * var(--pxDensity));
background-color: rgba(0.1, 0.1, 0.1, 0.1);
user-select: none;
}
.item {
position: relative;
display: flex;
flex-direction: column;
align-items: center;
overflow: visible;
margin-bottom: calc(3px * var(--pxDensity));
user-select: none;
}
.siblings {
display: flex;
justify-content: center;
gap: calc(3px * var(--pxDensity));
align-items: flex-end;
background-color: rgba(0.1, 0.1, 0.1, 0.1);
padding: calc(2px * var(--pxDensity));
margin-bottom: calc(3px * var(--pxDensity));
user-select: none;
}
.item p { /* from .encased */
display: inline-block;
background-color: var(--navBarBtn_Inner);
box-shadow: calc(1px * var(--pxDensity)) 0px var(--navBarBtn_Border),
calc(-1px * var(--pxDensity)) 0px var(--navBarBtn_Border),
0px calc(1px * var(--pxDensity)) var(--navBarBtn_Border),
0px calc(-1px * var(--pxDensity)) var(--navBarBtn_Border);
padding: calc(1px * var(--pxDensity));
width: round(auto, var(--pxDensity));
height: calc(3px * var(--pxDensity));
border: none;
color: #ffffff;
font-size: min(5vmin, 1.2vmax, 0.9rem);
min-width: calc(12px * var(--pxDensity));
text-align: center;
margin: 0;
user-select: none;
}
.item .blue {
background-color: var(--navBar_Inner);
box-shadow: calc(1px * var(--pxDensity)) 0px var(--navBar_Border),
calc(-1px * var(--pxDensity)) 0px var(--navBar_Border),
0px calc(1px * var(--pxDensity)) var(--navBar_Border),
0px calc(-1px * var(--pxDensity)) var(--navBar_Border);
}
#treeStaticContainer {
position: fixed;
top: 0%;
left: 0%;
width: 100vw;
height: 100vh;
display: inline-block;
cursor: grab;
}
#treeScaleContainer {
width: 100vw;
height: 100vh;
}
+120
View File
@@ -0,0 +1,120 @@
import { parse } from "https://esm.sh/smol-toml"
let tomlInput = document.getElementById("tomlInput")
let genButton = document.getElementById("generateButton")
let trees = document.getElementById("trees")
let recipes = {}
let startItems = []
let products = []
genButton.onclick = getDataFromInput
function getDataFromInput() {
let input = tomlInput.value
try {
let json = parse(input)
startItems = json.io.inputs
products = json.io.products
recipes = json.recipes
} catch (e) { alert(e) }
clearTrees()
for (let product of products) {
console.log("generating: \"" + product + "\"")
genTreeForProduct(product)
}
}
function genTreeForProduct(product) {
let treeData = RecursivelyGetRecipes(product)
let treeNode = document.createElement("div")
treeNode.className = "tree"
makeTreeHTML(treeData, treeNode, true)
trees.appendChild(treeNode)
}
function RecursivelyGetRecipes(product) {
let recipe = recipes[product]
if (recipe == undefined) {
alert("ERROR: Recipe not found for \"" + product + "\"")
return [product]
}
let children = []
for (let item of recipe) {
if (startItems.includes(item)) { children.push(item) }
else { children.push(...RecursivelyGetRecipes(item)) }
}
return [product, children]
}
function prettyTreeString(tree, indent=0) {
let string = ""
for (let item of tree) {
if (typeof item == "string")
{ string += "| ".repeat(indent) + item + "\n" }
else { string += prettyTreeString(item, indent + 1) }
}
return string
}
function clearTrees() {
let children = trees.children
for (let c of children) { c.remove() }
}
function makeTreeHTML(tree, parentNode, blue = false) {
let previouslyAdded = undefined
let stringCount = tree.filter((x) => typeof(x) == "string").length
let arrCount = tree.length - stringCount
// add .siblings container if theres more than 1 .item
if (stringCount > 1) {
let s = document.createElement("div")
s.className = "siblings"
parentNode.prepend(s)
parentNode = s
}
if (arrCount == 0) { blue = true }
for (let i = 0; i < tree.length; i++) {
if (typeof(tree[i]) == "string") {
if (tree.length - 1 == i || tree[i + 1] == "string") { blue = true }
let n = makeItemNode(tree[i], blue)
parentNode.prepend(n)
previouslyAdded = n
}
else { makeTreeHTML(tree[i], previouslyAdded, false) }
}
}
function makeItemNode(name, blue = false) {
let node = document.createElement("div")
node.className = "item"
let p = document.createElement("p")
if (blue) { p.className = "blue" }
p.textContent = name
node.appendChild(p)
return node
}
+9
View File
@@ -50,6 +50,15 @@
</div>
</button>
</div>
<div class="toyLi">
<button onclick="slideTransition('/toys/crafting', event)" class="toyContainer borderedRect">
<img class="toyImg" src="/assets/byte_dice_fancy_64x64.png">
<div class="toyTextContainer">
<h1>Crafting Tree Generator</h1>
<p class="desc">A crafting-tree-like generation tool that generates descending trees from a TOML file</p>
</div>
</button>
</div>
</div>
<div id="navBarContainer" data-alt="byte"></div>
+2 -2
View File
@@ -36,7 +36,7 @@ h1 {
margin: 0%;
}
button:hover {
filter: brightness(1.15) drop-shadow(0px calc(1px * var(--pxDensity)) rgba(0, 0, 0, 0.5));
filter: brightness(1.15) drop-shadow(0px calc(2px * var(--pxDensity)) rgba(0, 0, 0, 0.5));
cursor: pointer;
}
button:active {
@@ -44,7 +44,7 @@ button:active {
transform: translateY(calc(1px * var(--pxDensity)));
}
.toyUl {
padding: calc(3px * var(--pxDensity));
padding: 0% calc(3px * var(--pxDensity));
display: inline-flex;
width: fit-content;
justify-content: center;