diff --git a/src/assets/_root/webtoys/bbe/beasties/blinker_shadow_red.png b/src/assets/_root/webtoys/bbe/beasties/blinker_shadow_red.png new file mode 100644 index 0000000..9c4cc66 Binary files /dev/null and b/src/assets/_root/webtoys/bbe/beasties/blinker_shadow_red.png differ diff --git a/src/assets/_root/webtoys/bbe/beasties/bobbler_shadow_yellow.png b/src/assets/_root/webtoys/bbe/beasties/bobbler_shadow_yellow.png new file mode 100644 index 0000000..5ec32a4 Binary files /dev/null and b/src/assets/_root/webtoys/bbe/beasties/bobbler_shadow_yellow.png differ diff --git a/src/assets/_root/webtoys/bbe/beasties/chomper_shadow_blue.png b/src/assets/_root/webtoys/bbe/beasties/chomper_shadow_blue.png new file mode 100644 index 0000000..614ba76 Binary files /dev/null and b/src/assets/_root/webtoys/bbe/beasties/chomper_shadow_blue.png differ diff --git a/src/assets/_root/webtoys/bbe/beasties/creeper_shadow_red.png b/src/assets/_root/webtoys/bbe/beasties/creeper_shadow_red.png new file mode 100644 index 0000000..7171ba9 Binary files /dev/null and b/src/assets/_root/webtoys/bbe/beasties/creeper_shadow_red.png differ diff --git a/src/assets/_root/webtoys/bbe/beasties/diver_shadow_blue.png b/src/assets/_root/webtoys/bbe/beasties/diver_shadow_blue.png new file mode 100644 index 0000000..70029ed Binary files /dev/null and b/src/assets/_root/webtoys/bbe/beasties/diver_shadow_blue.png differ diff --git a/src/assets/_root/webtoys/bbe/beasties/gawker_shadow_yellow.png b/src/assets/_root/webtoys/bbe/beasties/gawker_shadow_yellow.png new file mode 100644 index 0000000..f9cd91e Binary files /dev/null and b/src/assets/_root/webtoys/bbe/beasties/gawker_shadow_yellow.png differ diff --git a/src/assets/_root/webtoys/bbe/beasties/grabber_shadow_red.png b/src/assets/_root/webtoys/bbe/beasties/grabber_shadow_red.png new file mode 100644 index 0000000..d170eca Binary files /dev/null and b/src/assets/_root/webtoys/bbe/beasties/grabber_shadow_red.png differ diff --git a/src/assets/_root/webtoys/bbe/beasties/puffer_shadow_green.png b/src/assets/_root/webtoys/bbe/beasties/puffer_shadow_green.png new file mode 100644 index 0000000..40d02ea Binary files /dev/null and b/src/assets/_root/webtoys/bbe/beasties/puffer_shadow_green.png differ diff --git a/src/assets/_root/webtoys/bbe/beasties/reacher_shadow_yellow.png b/src/assets/_root/webtoys/bbe/beasties/reacher_shadow_yellow.png new file mode 100644 index 0000000..3c12adc Binary files /dev/null and b/src/assets/_root/webtoys/bbe/beasties/reacher_shadow_yellow.png differ diff --git a/src/assets/_root/webtoys/bbe/beasties/scanner_shadow_red.png b/src/assets/_root/webtoys/bbe/beasties/scanner_shadow_red.png new file mode 100644 index 0000000..cf0537f Binary files /dev/null and b/src/assets/_root/webtoys/bbe/beasties/scanner_shadow_red.png differ diff --git a/src/assets/_root/webtoys/bbe/beasties/seer_shadow_green.png b/src/assets/_root/webtoys/bbe/beasties/seer_shadow_green.png new file mode 100644 index 0000000..504cea0 Binary files /dev/null and b/src/assets/_root/webtoys/bbe/beasties/seer_shadow_green.png differ diff --git a/src/assets/_root/webtoys/bbe/beasties/shuffler_shadow_yellow.png b/src/assets/_root/webtoys/bbe/beasties/shuffler_shadow_yellow.png new file mode 100644 index 0000000..731b88f Binary files /dev/null and b/src/assets/_root/webtoys/bbe/beasties/shuffler_shadow_yellow.png differ diff --git a/src/assets/_root/webtoys/bbe/beasties/snapper_shadow_blue.png b/src/assets/_root/webtoys/bbe/beasties/snapper_shadow_blue.png new file mode 100644 index 0000000..5b4a647 Binary files /dev/null and b/src/assets/_root/webtoys/bbe/beasties/snapper_shadow_blue.png differ diff --git a/src/assets/_root/webtoys/bbe/beasties/spooker_shadow_green.png b/src/assets/_root/webtoys/bbe/beasties/spooker_shadow_green.png new file mode 100644 index 0000000..fb39475 Binary files /dev/null and b/src/assets/_root/webtoys/bbe/beasties/spooker_shadow_green.png differ diff --git a/src/assets/_root/webtoys/bbe/beasties/thinker_shadow_green.png b/src/assets/_root/webtoys/bbe/beasties/thinker_shadow_green.png new file mode 100644 index 0000000..0566523 Binary files /dev/null and b/src/assets/_root/webtoys/bbe/beasties/thinker_shadow_green.png differ diff --git a/src/assets/_root/webtoys/bbe/beasties/walker_shadow_blue.png b/src/assets/_root/webtoys/bbe/beasties/walker_shadow_blue.png new file mode 100644 index 0000000..a642b20 Binary files /dev/null and b/src/assets/_root/webtoys/bbe/beasties/walker_shadow_blue.png differ diff --git a/src/assets/_root/webtoys/bbe/index.html b/src/assets/_root/webtoys/bbe/index.html new file mode 100644 index 0000000..b83bd61 --- /dev/null +++ b/src/assets/_root/webtoys/bbe/index.html @@ -0,0 +1,74 @@ + + + + + Byte Beasties: Escape! + + + + +
+

Byte Beasties: Escape!

+ + +
+
+
+

Story

+ +

+ The Byte Beasties are on the run from the GTP (General Thinking Program), + an evil AI that wants to absorb them into its software. +

+

+ Help the Byte Beasties escape GTP's yellow absorption wave by + hitting the green logic gates and avoiding the red NULL gates. +

+
+ +
+ +
+

Settings

+ +

+ +

+ +

+ Beastie: +

+

+ +

+ + +

+ +
+
+
+ +

Controls

+ + +
+ + + + + \ No newline at end of file diff --git a/src/assets/_root/webtoys/bbe/scripts/bbe.js b/src/assets/_root/webtoys/bbe/scripts/bbe.js new file mode 100644 index 0000000..31867a7 --- /dev/null +++ b/src/assets/_root/webtoys/bbe/scripts/bbe.js @@ -0,0 +1,656 @@ +import { createLightning, getMousePos } from "./lightning.js"; + +const startGame = (canvas) => { + const ctx = canvas.getContext("2d"), + BEASTIE_IMAGES = [ + "blinker_shadow_red.png", + "creeper_shadow_red.png", + "grabber_shadow_red.png", + "scanner_shadow_red.png", + "bobbler_shadow_yellow.png", + "gawker_shadow_yellow.png", + "reacher_shadow_yellow.png", + "shuffler_shadow_yellow.png", + "puffer_shadow_green.png", + "seer_shadow_green.png", + "spooker_shadow_green.png", + "thinker_shadow_green.png", + "chomper_shadow_blue.png", + "diver_shadow_blue.png", + "snapper_shadow_blue.png", + "walker_shadow_blue.png", + ], + GATE = { + HEIGHT: 20, + MARGIN: 25, + MAX_SPACE: 140, + MIN_SPACE: 60, + START: canvas.height - 200, + WIDTH: 65, + }, + game = { + interval: 1000 / 90, // ms/f = 1000 ms/s / 90 f/s + winScore: 15000, + winScores: { + easy: 15000, + medium: 30000, + hard: 50000, + endless: 0, + }, + blockerFrequency: 0.3, + blockerFrequencies: { + easy: 0.3, + medium: 0.4, + hard: 0.5, + endless: 0.3, + }, + hasBlockers: true, + isOver: false, + isPaused: false, + showRestartModal: false, + maxLead: 400, + maxSpeed: -8, + + speed: -1, + speedBoost: { + duration: 1, + multiplier: 2, + }, + }, + drag = 0.3, + beastie = { + width: 48, + height: 48, + x: canvas.width / 2 - 20, + y: GATE.START + 100, + + // velocity + dx: 0, + dy: 0, + + // for tracking speed boosts + boostEnd: 0, + + score: 0, + image: null, + }, + player = { + dx: 0, + dy: 0, + }, + enemy = { + width: canvas.width, + height: canvas.height, + x: 0, + y: canvas.height - 50, + delaySeconds: 5, + speed: -2, + }, + random = (min, max) => Math.random() * (max - min) + min, + images = BEASTIE_IMAGES.reduce((acc, name) => { + const tempImg = new Image(); + tempImg.src = `/webtoys/bbe/beasties/${name}`; + acc[name] = tempImg; + return acc; + }, {}), + img = new Image(), + hasCollision = (gate) => + // Collision check: AAB (Axis-Aligned Bounding Box) + beastie.x < gate.x + GATE.WIDTH && + beastie.x > gate.x - beastie.width && + beastie.y < gate.y + GATE.HEIGHT && + beastie.y > gate.y - beastie.height, + settingsBeastie = document.getElementById("settingsBeastie"); + + let // vertical space between gates + minPlatformSpace = GATE.MIN_SPACE, + maxPlatformSpace = GATE.MAX_SPACE, + gates = [ + { + x: canvas.width / 2 - GATE.WIDTH / 2, + y: GATE.START, + touched: false, + blocker: false, + }, + ], + y = GATE.START, + keydown = false, + runtime = 0, + lastTimestamp; + + img.onload = () => { + ctx.drawImage(img, beastie.x, beastie.y); + }; + // img.src = "/webtoys/bbe/beasties/blinker_shadow_red.png"; + + Object.keys(images).forEach((key, i) => { + const input = document.createElement("input"), + label = document.createElement("label"), + bImage = images[key]; + label.className = "beastieImageInputWrapper"; + input.name = "beastie"; + input.type = "radio"; + // TODO: Add Cookie support + input.checked = i === 0; + input.value = key; + label.appendChild(input); + bImage.className = "beastieImage"; + label.appendChild(bImage); + settingsBeastie.appendChild(label); + }); + + const renderLightning = () => { + var color = "hsla(60, 100%, 50%, .7)"; + + ctx.save(); + ctx.globalCompositeOperation = "lighter"; + + ctx.strokeStyle = color; + ctx.shadowColor = color; + + ctx.fillStyle = color; + ctx.fillRect(enemy.x, enemy.y, enemy.width, canvas.height); + ctx.fillStyle = "hsla(0, 0%, 10%, 0.2)"; + ctx.shadowBlur = 0; + ctx.globalCompositeOperation = "source-over"; + ctx.fillRect(enemy.x, enemy.y, enemy.width, canvas.height); + ctx.globalCompositeOperation = "lighter"; + ctx.shadowBlur = 15; + + const lightning = createLightning(canvas, enemy); + ctx.beginPath(); + for (let i = 0; i < lightning.length; i++) { + ctx.lineTo(lightning[i].x, Math.max(lightning[i].y, enemy.y)); + } + ctx.stroke(); + // requestAnimationFrame(renderLightning); + ctx.restore(); + }; + + const restartGame = () => { + const whichBeastie = document.querySelector( + 'input[name="beastie"]:checked' + ).value, + settingsMode = document.getElementById("settingsMode"); + + if (settingsMode) { + const { value } = settingsMode; + game.winScore = game.winScores[value]; + game.blockerFrequency = game.blockerFrequencies[value]; + } + + img.src = `/webtoys/bbe/beasties/${whichBeastie}`; + + game.isPaused = false; + game.isOver = false; + game.showRestartModal = false; + y = GATE.START; + minPlatformSpace = GATE.MIN_SPACE; + maxPlatformSpace = GATE.MAX_SPACE; + // all the gates - starts in the bottom middle + gates = [ + { + x: canvas.width / 2 - GATE.WIDTH / 2, + y: GATE.START, + touched: false, + blocker: false, + }, + ]; + beastie.dx = 0; + beastie.dy = 0; + beastie.boostEnd = 0; + beastie.score = 0; + beastie.x = canvas.width / 2 - 20; + beastie.y = canvas.height - 50; + + player.dx = 0; + player.dy = 0; + + enemy.y = canvas.height - 50; + + runtime = 0; + + while (y > 0) { + // the next gate can be placed above the previous one with a space + // somewhere between the min and max space + y -= GATE.HEIGHT + random(minPlatformSpace, maxPlatformSpace); + + gates.push({ + x: random( + GATE.MARGIN, + canvas.width - GATE.MARGIN - GATE.WIDTH + ), + y, + touched: false, + }); + } + }, + // game loop + loop = (timestamp) => { + requestAnimationFrame(loop); + + // limit FPS + if (timestamp - lastTimestamp <= game.interval) return; + + if (!(game.isOver || game.isPaused)) + runtime += timestamp - lastTimestamp; + lastTimestamp = timestamp; + + if (game.isOver) { + ctx.save(); + ctx.fillStyle = "yellow"; + ctx.fillRect( + canvas.width / 4, + canvas.height / 4, + enemy.width / 2, + canvas.height / 2 + ); + ctx.fillStyle = "black"; + ctx.font = "32px sans"; + ctx.fillText( + beastie.y <= 0 ? "You Win!" : "Game Over", + canvas.width / 3, + canvas.height / 3, + canvas.width / 3 + ); + ctx.fillText( + `Score: + ${beastie.score}`, + canvas.width / 3, + (canvas.height / 3) * 2, + canvas.width / 3 + ); + ctx.restore(); + return; + } + + if (game.showRestartModal) { + ctx.save(); + ctx.fillStyle = "yellow"; + ctx.fillRect( + canvas.width / 4, + canvas.height / 4, + enemy.width / 2, + canvas.height / 2 + ); + ctx.fillStyle = "black"; + ctx.font = "48px sans"; + ctx.fillText( + `Restart? (Y / N)`, + canvas.width / 3, + canvas.height / 3, + canvas.width / 3 + ); + ctx.restore(); + return; + } + + if (game.isPaused) { + ctx.save(); + ctx.fillStyle = "yellow"; + ctx.fillRect( + canvas.width / 4, + canvas.height / 3, + canvas.width / 2, + canvas.height / 3 + ); + ctx.fillStyle = "black"; + ctx.font = "40px sans"; + ctx.fillText(`Paused`, canvas.width / 3 - 5, canvas.height / 2); + ctx.restore(); + return; + } + ctx.clearRect(0, 0, canvas.width, canvas.height); + + // if beastie reaches the middle of the screen, move the gates down + // instead of beastie up to make it look like beastie is going up + if ( + beastie.y < canvas.height - canvas.height / 3 && + beastie.dy < 0 && + (beastie.score <= game.winScore || !game.winScore) + ) { + gates = gates.map(({ y, ...otherProps }) => ({ + ...otherProps, + y: y - beastie.dy, + })); + + // add more gates to the top of the screen as beastie moves up + while (gates[gates.length - 1].y > 0) { + gates.push({ + x: random(25, canvas.width - 25 - GATE.WIDTH), + y: + gates[gates.length - 1].y - + (GATE.HEIGHT + + random(minPlatformSpace, maxPlatformSpace)), + touched: false, + blocker: + game.hasBlockers && + gates.filter(({ blocker }) => blocker).length / + gates.length < + game.blockerFrequency && + Math.random() <= game.blockerFrequency + ? true + : false, + }); + + // add a bit to the min/max gate space as the player goes up + minPlatformSpace += 0.5; + maxPlatformSpace += 0.5; + + // cap max space + maxPlatformSpace = Math.min( + maxPlatformSpace, + canvas.height / 2 + ); + } + // move enemy backwards + enemy.y -= beastie.dy; + } else { + if (beastie.y <= 0) beastie.dy = -10; + beastie.y += beastie.dy; + } + + // apply drag when key not pressed + if (!keydown) { + if (player.dx < 0) { + beastie.dx += drag; + + // don't let dx go above 0 + if (beastie.dx > 0) { + beastie.dx = 0; + player.dx = 0; + } + } else if (player.dx > 0) { + beastie.dx -= drag; + + if (beastie.dx < 0) { + beastie.dx = 0; + player.dx = 0; + } + } + + if ( + player.dy === 0 || + (player.dy < 0 && Date.now() > beastie.boostEnd) + ) { + beastie.dy += drag; + + // don't let dx go above 0 + if (beastie.dy > game.speed) { + beastie.dy = game.speed; + player.dy = game.speed; + } + } else if (player.dy > 0) { + beastie.dy -= drag; + + if (beastie.dy < 0) { + beastie.dy = 0; + player.dy = 0; + } + } + } + + beastie.x += beastie.dx; + + // make beastie wrap the screen + if (beastie.x + beastie.width < 0) { + beastie.x = canvas.width; + } else if (beastie.x > canvas.width) { + beastie.x = -beastie.width; + } + + ctx.fillStyle = "green"; + gates = gates.map((gate) => { + // draw gates + ctx.save(); + if (gate.blocker) ctx.fillStyle = "red"; + else ctx.fillStyle = gate.touched ? "lime" : "green"; + ctx.fillRect(gate.x, gate.y, GATE.WIDTH, GATE.HEIGHT); + if (gate.blocker) { + ctx.save(); + ctx.lineWidth = 4; + ctx.strokeStyle = "black"; + ctx.beginPath(); + ctx.moveTo(gate.x, gate.y); + ctx.lineTo(gate.x + GATE.WIDTH, gate.y + GATE.HEIGHT); + ctx.moveTo(gate.x + GATE.WIDTH, gate.y); + ctx.lineTo(gate.x, gate.y + GATE.HEIGHT); + ctx.stroke(); + ctx.restore(); + } + + // make beastie stop if it collides with a blocking gate + if (gate.blocker && hasCollision(gate)) { + beastie.dy = game.speed * 0.5; + gate.touched = true; + } + + // make beastie jump if it collides with a gate from above + if ( + // beastie is falling + // beastie.dy > 0 && + // beastie was previous above the gate + // prevDoodleY + doodle.height <= gate.y && + // doodle collides with gate + + !gate.blocker && + // !gate.touched && + hasCollision(gate) + ) { + // reset beastie position so it's on top of the gate + // beastie.y = gate.y - beastie.height; + if (!gate.touched) { + beastie.dy = Math.max( + game.maxSpeed, + game.speedBoost.multiplier * + Math.min(beastie.dy, game.speed) + ); + beastie.score += 100; + } + beastie.boostEnd = + Date.now() + game.speedBoost.duration * 1000; + gate.touched = true; + } + + return gate; + }); + + // check on enemy + if (!runtime) runtime = 0; + + if (runtime > enemy.delaySeconds * 1000) + enemy.y = enemy.y + enemy.speed; + if (enemy.y < 1) { + enemy.y = 0; + game.isOver = true; + } else if (enemy.y > beastie.y + game.maxLead) + enemy.y = beastie.y + game.maxLead; + + // draw beastie + ctx.drawImage( + img, + beastie.x, + beastie.y, + beastie.width, + beastie.height + ); + + // +100 points for each offscreen blocker that wasn't touched + beastie.score += + 100 * + gates.filter( + (gate) => + gate.y > canvas.height && gate.blocker && !gate.touched + ).length; + // remove any offscreen gates + gates = gates.filter((gate) => gate.y <= canvas.height); + + beastie.score += Math.floor(Math.abs(beastie.dy)); + ctx.fillStyle = "white"; + ctx.font = "12px sans"; + ctx.fillText(`Score: ${beastie.score}`, 10, 20); + + renderLightning(); + }; + + // listen to keyboard events to move beastie + document.addEventListener("keydown", (e) => { + if (keydown) return; + + if (e.code === "ArrowLeft") { + keydown = true; + player.dx = -1; + beastie.dx = -3; + } else if (e.code === "ArrowRight") { + keydown = true; + player.dx = 1; + beastie.dx = 3; + } else if (e.code === "KeyP") { + keydown = true; + game.isPaused = !game.isPaused; + } else if (e.code === "KeyR") { + keydown = true; + if (game.isOver) { + restartGame(); + return; + } + game.isPaused = true; + game.showRestartModal = true; + } else if (e.code === "KeyS") { + keydown = true; + game.isPaused = true; + document.getElementById("settings").classList.add("showSettings"); + } else if (e.code === "KeyY") { + keydown = true; + if (game.showRestartModal) restartGame(); + } else if (e.code === "KeyN") { + keydown = true; + if (game.showRestartModal) { + game.showRestartModal = false; + game.isPaused = false; + } + + /* + } else if (e.code === "ArrowDown") { + keydown = true; + player.dy = 1; + beastie.dy = 3; + } else if (e.code === "ArrowUp") { + keydown = true; + player.dy = -1; + beastie.dy = -3; + */ + } + }); + + document.addEventListener("keyup", () => { + keydown = false; + }); + + canvas.addEventListener( + "mousedown", + (e) => { + keydown = true; + const result = getMousePos(canvas, e); + if (result.x < canvas.width / 2) { + player.dx = -1; + beastie.dx = -3; + } else { + player.dx = 1; + beastie.dx = 3; + } + }, + false + ); + + canvas.addEventListener( + "mousemove", + (e) => { + if (!keydown) return; + const result = getMousePos(canvas, e); + if (result.x < beastie.x) { + player.dx = -1; + beastie.dx = -3; + } else { + player.dx = 1; + beastie.dx = 3; + } + }, + false + ); + + canvas.addEventListener( + "mouseup", + () => { + keydown = false; + }, + false + ); + + canvas.addEventListener( + "touchstart", + (e) => { + mousePos = getTouchPos(canvas, e); + const touch = e.touches[0], + mouseEvent = new MouseEvent("mousedown", { + clientX: touch.clientX, + clientY: touch.clientY, + }); + canvas.dispatchEvent(mouseEvent); + }, + false + ); + canvas.addEventListener( + "touchend", + () => { + var mouseEvent = new MouseEvent("mouseup", {}); + canvas.dispatchEvent(mouseEvent); + }, + false + ); + canvas.addEventListener( + "touchmove", + (e) => { + const touch = e.touches[0]; + keydown = true; + if (touch.clientX < beastie.x) { + player.dx = -1; + beastie.dx = -3; + } else { + player.dx = 1; + beastie.dx = 3; + } + canvas.dispatchEvent(mouseEvent); + }, + false + ); + document.getElementById("cancelSettings").addEventListener("click", (e) => { + // close modal + document.getElementById("settings").classList.remove("showSettings"); + // leave game paused + }); + document.getElementById("applySettings").addEventListener("click", (e) => { + // close modal + document.getElementById("settings").classList.remove("showSettings"); + // restart game + restartGame(); + }); + document + .getElementById("toggleSettings") + ?.addEventListener("click", (e) => { + // pause game + game.isPaused = true; + + // show settings + document + .getElementById("settings") + .classList.toggle("showSettings"); + }); + + // start the game + restartGame(); + + requestAnimationFrame(loop); +}; + +export { startGame }; diff --git a/src/assets/_root/webtoys/bbe/scripts/lightning.js b/src/assets/_root/webtoys/bbe/scripts/lightning.js new file mode 100644 index 0000000..60093a0 --- /dev/null +++ b/src/assets/_root/webtoys/bbe/scripts/lightning.js @@ -0,0 +1,45 @@ +export const createLightning = (canvas, enemy) => { + const { y: size } = enemy; + var center = { x: canvas.width / 2, y: canvas.height }; + var minSegmentHeight = 5; + var roughness = 2; + var maxDifference = size / 5; + + let lightning = []; + let segmentHeight = size / 3; + lightning.push({ + x: center.x, + y: center.y + 200, + }); + lightning.push({ + x: Math.random() * (canvas.width - 100) + 50, + y: Math.abs((Math.random() - 0.9) * 100), + }); + let currDiff = maxDifference; + while (segmentHeight > minSegmentHeight) { + const newSegments = []; + for (var i = 0; i < lightning.length - 1; i++) { + const start = lightning[i], + end = lightning[i + 1], + midX = (start.x + end.x) / 2, + newX = midX + (Math.random() * 2 - 1) * currDiff; + newSegments.push(start, { x: newX, y: (start.y + end.y) / 2 }); + } + + newSegments.push(lightning.pop()); + lightning = newSegments; + + currDiff /= roughness; + segmentHeight /= 2; + } + return lightning; +}; + +// Get the position of the mouse relative to the canvas +export const getMousePos = (canvasDom, mouseEvent) => { + var rect = canvasDom.getBoundingClientRect(); + return { + x: mouseEvent.clientX - rect.left, + y: mouseEvent.clientY - rect.top, + }; +}; diff --git a/src/assets/_root/webtoys/bbe/scripts/main.js b/src/assets/_root/webtoys/bbe/scripts/main.js new file mode 100644 index 0000000..b4d5469 --- /dev/null +++ b/src/assets/_root/webtoys/bbe/scripts/main.js @@ -0,0 +1,32 @@ +// @license magnet:?xt=urn:btih:90dc5c0be029de84e523b9b3922520e79e0e6f08&dn=cc0.txt CC0-1.0 +/**************************************************************************** + * Planar Vagabond's Guide to the Multiverse (planarvagabond.com) + * + * Copyright 2023-2024 Eric Woodward + * Source released under CC0 Public Domain License v1.0 + * https://www.planarvagabond.com/licenses/cc0/ + * http://creativecommons.org/publicdomain/zero/1.0/ + ****************************************************************************/ + +import { startGame } from "./bbe.js"; + +export default (() => { + // we load this library as a module to guarantee baseline ES6 functionality + + // Indicate JS is loaded + document.documentElement.className = + document.documentElement.className.replace("no-js", "js"); + + setTimeout(() => { + const canvas = document.createElement("canvas"), + contentDiv = document.getElementById("game"); + + canvas.setAttribute("width", "375"); + canvas.setAttribute("height", "667"); + canvas.setAttribute("id", "game"); + contentDiv.appendChild(canvas); + + startGame(canvas); + }, 1); +})(); +// @license-end diff --git a/src/assets/_root/webtoys/bbe/styles.css b/src/assets/_root/webtoys/bbe/styles.css new file mode 100644 index 0000000..8cd9942 --- /dev/null +++ b/src/assets/_root/webtoys/bbe/styles.css @@ -0,0 +1,82 @@ +html, body { + height: 100%; + margin: 0; +} + +body { + display: flex; + align-items: center; + justify-content: center; + background-color: #333; + color: #ddd; +} + +canvas { + border: 1px solid black; + z-index: 0; +} + +.beastieImage { + max-width: 2rem; +} + +.beastieImageInputWrapper { + align-items: center; + display: flex; + flex-direction: row; + justify-content: center; + padding: .5rem; +} + +.content { + display: flex; + flex-direction: column; + max-width: 375px; +} + +.game { + position: relative; +} + +.settings { + background-color: rgba(33, 33, 33, .95); + bottom: 0; + left: 0; + opacity: 0; + padding: 0 1rem 1rem; + position: absolute; + right: 0; + top: 0; + transition: opacity ease 0.3s; + z-index: 1; +} + +.settings.showSettings { + opacity: 1; +} + +.settingsBeastie { + align-items: center; + display: flex; + flex-wrap: wrap; + justify-content: center; +} + +.settingsBeastie label { + padding: .5rem; +} + +.settingsFooter { + margin: 1rem; + text-align: right; +} + +.toggleSettings { + background-color: rgba(33, 33, 33, .9); + border: 1px solid rgba(33, 33, 33, .9); + cursor: pointer; + position: absolute; + right: 10px; + top: 5px; + z-index: 2; +} \ No newline at end of file