<html><head><base href="https://api.websim.io/CrateCraze/">

<meta charset="UTF-8">

<meta name="viewport" content="width=device-width, initial-scale=1.0">

<title>BTC Stack Challenge: Crypto Crate Craze</title>

<style>

  @import url('https://fonts.googleapis.com/css2?family=Roboto:wght@400;700&family=Press+Start+2P&display=swap');


  body {

    margin: 0;

    padding: 0;

    overflow: hidden;

    font-family: 'Roboto', sans-serif;

    background-image: url('https://i.redd.it/o17eipie3ijb1.png');

    background-size: cover;

    background-position: center;

    background-repeat: no-repeat;

    height: 100vh;

  }

  #game-container {

    width: 100vw;

    height: 100vh;

    position: relative;

  }

  #crane {

    position: absolute;

    top: 0;

    left: 50%;

    transform: translateX(-50%);

    width: 100px;

    height: 100px;

    z-index: 10;

  }

  #rope {

    position: absolute;

    top: 0;

    left: 50%;

    width: 2px;

    height: 300px;

    background-color: #FFD700;

    transform-origin: top center;

    box-shadow: 0 0 10px #FFD700;

  }

  .block {

    position: absolute;

    width: 80px;

    height: 80px;

    bottom: -40px;

    left: 50%;

    transform: translateX(-50%);

    background-image: url('https://static.wikia.nocookie.net/portalworldsgame/images/0/00/Wooden_Crate.png');

    background-size: cover;

    display: flex;

    justify-content: center;

    align-items: center;

    font-size: 40px;

    box-shadow: 0 5px 15px rgba(0,0,0,0.3);

    border-radius: 10px;

  }

  #tower {

    position: absolute;

    bottom: 60px;

    left: 50%;

    transform: translateX(-50%);

    width: 100%;

    height: calc(100% - 60px);

  }

  .tower-block {

    width: 80px;

    height: 80px;

    background-image: url('https://static.wikia.nocookie.net/portalworldsgame/images/0/00/Wooden_Crate.png');

    background-size: cover;

    position: absolute;

    transition: opacity 0.3s;

    display: flex;

    justify-content: center;

    align-items: center;

    font-size: 40px;

    box-shadow: 0 5px 15px rgba(0,0,0,0.3);

    border-radius: 10px;

  }

  #score, #timer {

    position: absolute;

    left: 20px;

    font-size: 24px;

    color: #FFD700;

    text-shadow: 2px 2px 4px rgba(0,0,0,0.7);

    font-family: 'Press Start 2P', cursive;

    background-color: rgba(0, 0, 0, 0.6);

    padding: 10px;

    border-radius: 10px;

    box-shadow: 0 0 10px rgba(255, 215, 0, 0.5);

  }

  #score {

    top: 20px;

  }

  #timer {

    top: 80px;

  }

  #game-over {

    position: absolute;

    top: 50%;

    left: 50%;

    transform: translate(-50%, -50%);

    background-color: rgba(0, 0, 0, 0.8);

    padding: 40px;

    border-radius: 20px;

    text-align: center;

    display: none;

    color: #FFD700;

    font-family: 'Press Start 2P', cursive;

    box-shadow: 0 0 30px rgba(255, 215, 0, 0.5);

  }

  #restart-btn {

    margin-top: 20px;

    padding: 15px 30px;

    font-size: 18px;

    background-color: #FFD700;

    color: #000;

    border: none;

    border-radius: 10px;

    cursor: pointer;

    font-family: 'Press Start 2P', cursive;

    transition: all 0.3s ease;

  }

  #restart-btn:hover {

    background-color: #FFA500;

    transform: scale(1.05);

  }

  #ground {

    position: absolute;

    bottom: 0;

    left: 0;

    width: 100%;

    height: 60px;

    display: flex;

    justify-content: flex-start;

  }

  .ground-tile {

    width: 60px;

    height: 60px;

    background-image: url('https://cdna.artstation.com/p/assets/images/images/034/060/716/large/diego-lopez-groundtile1.jpg');

    background-size: cover;

    box-shadow: inset 0 5px 10px rgba(0,0,0,0.3);

  }

  #rules {

    position: absolute;

    top: 20px;

    right: 20px;

    background-color: rgba(0, 0, 0, 0.8);

    padding: 20px;

    border-radius: 15px;

    font-size: 14px;

    max-width: 300px;

    color: #FFD700;

    box-shadow: 0 0 20px rgba(255, 215, 0, 0.3);

  }

  #rules h3 {

    font-family: 'Press Start 2P', cursive;

    margin-bottom: 15px;

  }

  #rules ul {

    padding-left: 20px;

  }

  #rules li {

    margin-bottom: 10px;

  }

  #points-effect, #time-effect {

    position: absolute;

    font-weight: bold;

    text-shadow: 2px 2px 4px rgba(0,0,0,0.7);

    pointer-events: none;

    opacity: 0;

    transition: opacity 0.5s, transform 0.5s;

    font-family: 'Press Start 2P', cursive;

  }

  #points-effect {

    font-size: 28px;

    color: #4CAF50;

  }

  #time-effect {

    font-size: 24px;

    color: #FFA500;

  }

</style>

</head>

<body>

<div id="game-container">

  <div id="crane">

    <div id="rope">

      <div class="block">₿</div>

    </div>

  </div>

  <div id="tower"></div>

  <div id="score">Score: 0</div>

  <div id="timer">Time: 60s</div>

  <div id="game-over">

    <h2>Game Over!</h2>

    <p>Final Score: <span id="final-score"></span></p>

    <button id="restart-btn">Restart</button>

  </div>

  <div id="ground"></div>

  <div id="rules">

    <h3>Game Rules:</h3>

    <ul>

      <li>Click to drop BTC crates</li>

      <li>Stack crates carefully</li>

      <li>5 vertical crates = 25 points</li>

      <li>5+ ground crates = Game Over</li>

      <li>60 seconds to play</li>

      <li>+5 seconds per 25 points</li>

      <li>Faster swing under 10 seconds</li>

    </ul>

  </div>

  <div id="points-effect">+25 points</div>

  <div id="time-effect">+5 seconds</div>

</div>


<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.9.1/gsap.min.js"></script>

<script src="https://cdnjs.cloudflare.com/ajax/libs/matter-js/0.18.0/matter.min.js"></script>

<script>

  const gameContainer = document.getElementById('game-container');

  const rope = document.getElementById('rope');

  const block = document.querySelector('.block');

  const tower = document.getElementById('tower');

  const scoreElement = document.getElementById('score');

  const timerElement = document.getElementById('timer');

  const gameOverElement = document.getElementById('game-over');

  const finalScoreElement = document.getElementById('final-score');

  const restartBtn = document.getElementById('restart-btn');

  const ground = document.getElementById('ground');

  const pointsEffect = document.getElementById('points-effect');

  const timeEffect = document.getElementById('time-effect');


  let score = 0;

  let swingAngle = 0;

  let swingDirection = 1;

  let gameActive = true;

  let swingSpeed = 0.02;

  let blockFalling = false;

  let timeRemaining = 60;

  let timerInterval;


  const engine = Matter.Engine.create({

    gravity: { x: 0, y: 1 }

  });

  const world = engine.world;

  const render = Matter.Render.create({

    element: document.body,

    engine: engine,

    options: {

      width: window.innerWidth,

      height: window.innerHeight,

      wireframes: false,

      background: 'transparent'

    }

  });


  Matter.Render.run(render);


  const runner = Matter.Runner.create();

  Matter.Runner.run(runner, engine);


  const tileSize = 60;

  const tilesNeeded = Math.ceil(window.innerWidth / tileSize);

  

  for (let i = 0; i < tilesNeeded; i++) {

    const tile = document.createElement('div');

    tile.className = 'ground-tile';

    ground.appendChild(tile);


    const physicalTile = Matter.Bodies.rectangle(

      i * tileSize + tileSize / 2,

      window.innerHeight - tileSize / 2,

      tileSize,

      tileSize,

      { isStatic: true, friction: 0.5, restitution: 0.2 }

    );

    Matter.World.add(world, physicalTile);

  }


  function updateScore(points) {

    score += points;

    scoreElement.textContent = `Score: ${score}`;

    if (points === 25) {

      showPointsEffect();

      addTime(5);

    }

  }


  function showPointsEffect() {

    const x = window.innerWidth / 2;

    const y = window.innerHeight / 2;


    pointsEffect.style.left = `${x}px`;

    pointsEffect.style.top = `${y}px`;

    pointsEffect.style.transform = 'translate(-50%, -50%) scale(1)';

    pointsEffect.style.opacity = '1';


    timeEffect.style.left = `${x}px`;

    timeEffect.style.top = `${y + 40}px`;

    timeEffect.style.transform = 'translate(-50%, -50%) scale(1)';

    timeEffect.style.opacity = '1';


    gsap.to(pointsEffect, {

      opacity: 0,

      y: -50,

      scale: 1.5,

      duration: 1,

      ease: "power2.out"

    });


    gsap.to(timeEffect, {

      opacity: 0,

      y: 50,

      scale: 1.5,

      duration: 1,

      ease: "power2.out"

    });

  }


  function updateTimer() {

    timeRemaining--;

    timerElement.textContent = `Time: ${timeRemaining}s`;

    updateTimerColor();

    if (timeRemaining <= 0) {

      gameOver();

    } else if (timeRemaining <= 10) {

      swingSpeed = 0.04;

    }

  }


  function updateTimerColor() {

    if (timeRemaining > 40) {

      timerElement.style.color = '#4CAF50'; // Green

    } else if (timeRemaining > 10) {

      timerElement.style.color = '#FFA500'; // Orange

    } else {

      timerElement.style.color = '#FF0000'; // Red

    }

  }


  function addTime(seconds) {

    timeRemaining += seconds;

    timerElement.textContent = `Time: ${timeRemaining}s`;

    updateTimerColor();

  }


  function swingRope() {

    if (!gameActive) return;


    swingAngle += swingSpeed * swingDirection;

    if (Math.abs(swingAngle) > 0.8) {

      swingDirection *= -1;

    }


    gsap.to(rope, {

      rotation: swingAngle * 50,

      duration: 0.1

    });


    requestAnimationFrame(swingRope);

  }


  function dropBlock() {

    if (!gameActive || blockFalling) return;


    blockFalling = true;

    block.style.visibility = 'hidden';


    const ropeRect = rope.getBoundingClientRect();

    const ropeEnd = {

      x: ropeRect.left + ropeRect.width / 2,

      y: ropeRect.bottom

    };

    const newBlock = createPhysicsBlock(ropeEnd.x, ropeEnd.y);

  }


  function createPhysicsBlock(x, y) {

    const blockSize = 80;


    const physicsBlock = Matter.Bodies.rectangle(x, y, blockSize, blockSize, {

      friction: 0.5,

      restitution: 0.2,

      render: {

        sprite: {

          texture: 'https://static.wikia.nocookie.net/portalworldsgame/images/0/00/Wooden_Crate.png',

          xScale: 0.5,

          yScale: 0.5

        }

      }

    });

    Matter.World.add(world, physicsBlock);


    const visualBlock = document.createElement('div');

    visualBlock.className = 'tower-block';

    visualBlock.innerHTML = '₿';

    tower.appendChild(visualBlock);


    Matter.Events.on(engine, 'afterUpdate', () => {

      visualBlock.style.transform = `translate(${physicsBlock.position.x - blockSize/2}px, ${physicsBlock.position.y - blockSize/2}px) rotate(${physicsBlock.angle}rad)`;

      checkBlockPosition(physicsBlock, visualBlock);

    });


    return { physicsBlock, visualBlock };

  }


  function checkBlockPosition(physicsBlock, visualBlock) {

    if (Math.abs(physicsBlock.velocity.y) < 0.1 && Math.abs(physicsBlock.velocity.x) < 0.1) {

      if (blockFalling) {

        blockFalling = false;

        block.style.visibility = 'visible';

        checkForCompleteColumns();

        checkForGameOver();

      }

    }

  }


  function checkForCompleteColumns() {

    const blockSize = 80;

    const tolerance = 10;

    const blocks = Array.from(document.querySelectorAll('.tower-block'));

    const columns = {};


    blocks.forEach((block) => {

      const transform = block.style.transform;

      const x = parseInt(transform.split(',')[0].split('(')[1]);

      const y = parseInt(transform.split(',')[1]);

      const roundedX = Math.round(x / blockSize) * blockSize;

      if (!columns[roundedX]) columns[roundedX] = [];

      columns[roundedX].push({ block, y });

    });


    let columnsToRemove = [];


    for (const [x, columnBlocks] of Object.entries(columns)) {

      columnBlocks.sort((a, b) => b.y - a.y);

      let consecutiveCount = 1;

      let startIndex = 0;


      for (let i = 1; i < columnBlocks.length; i++) {

        if (Math.abs(columnBlocks[i].y - columnBlocks[i-1].y) <= blockSize + tolerance) {

          consecutiveCount++;

          if (consecutiveCount === 5) {

            columnsToRemove.push({ columnBlocks, startIndex: startIndex, endIndex: i });

            break;

          }

        } else {

          consecutiveCount = 1;

          startIndex = i;

        }

      }

    }


    if (columnsToRemove.length > 0) {

      columnsToRemove.forEach(({ columnBlocks, startIndex, endIndex }) => {

        removeVerticalBlocks(columnBlocks, startIndex, endIndex);

        updateScore(25);

      });

    }

  }


  function removeVerticalBlocks(columnBlocks, startIndex, endIndex) {

    for (let i = startIndex; i <= endIndex; i++) {

      const block = columnBlocks[i].block;

      const physicsBody = Matter.Composite.allBodies(world).find(

        (body) => Math.abs(body.position.x - (parseFloat(block.style.transform.split(',')[0].split('(')[1]) + 40)) < 10 &&

                  Math.abs(body.position.y - (parseFloat(block.style.transform.split(',')[1]) + 40)) < 10

      );

      if (physicsBody) {

        Matter.World.remove(world, physicsBody);

      }

      gsap.to(block, {

        opacity: 0,

        scale: 1.5,

        duration: 0.3,

        onComplete: () => block.remove()

      });

    }


    // Move blocks above the removed ones down

    for (let i = startIndex - 1; i >= 0; i--) {

      const block = columnBlocks[i].block;

      const physicsBody = Matter.Composite.allBodies(world).find(

        (body) => Math.abs(body.position.x - (parseFloat(block.style.transform.split(',')[0].split('(')[1]) + 40)) < 10 &&

                  Math.abs(body.position.y - (parseFloat(block.style.transform.split(',')[1]) + 40)) < 10

      );

      if (physicsBody) {

        Matter.Body.translate(physicsBody, { x: 0, y: 80 * 5 });

      }

    }

  }


  function checkForGameOver() {

    const groundLevel = window.innerHeight - 60;

    const blockSize = 80;

    const tolerance = 10;

    const blocks = Array.from(document.querySelectorAll('.tower-block'));

    const groundTouchingBlocks = blocks.filter((block) => {

      const y = parseFloat(block.style.transform.split(',')[1]);

      return Math.abs(y + blockSize - groundLevel) < tolerance;

    });


    if (groundTouchingBlocks.length >= 5) {

      gameOver();

    }

  }


  function gameOver() {

    gameActive = false;

    finalScoreElement.textContent = score;

    gsap.to(gameOverElement, {

      display: 'block',

      opacity: 1,

      scale: 1,

      duration: 0.5,

      ease: "back.out(1.7)"

    });

    clearInterval(timerInterval);

  }


  function restartGame() {

    score = 0;

    swingAngle = 0;

    swingDirection = 1;

    gameActive = true;

    swingSpeed = 0.02;

    blockFalling = false;

    timeRemaining = 60;

    

    scoreElement.textContent = 'Score: 0';

    timerElement.textContent = 'Time: 60s';

    updateTimerColor();

    gsap.to(gameOverElement, {

      opacity: 0,

      scale: 0.8,

      duration: 0.3,

      onComplete: () => {

        gameOverElement.style.display = 'none';

      }

    });

    

    block.style.visibility = 'visible';


    Matter.World.clear(world);

    Matter.Engine.clear(engine);


    tower.innerHTML = '';


    for (let i = 0; i < tilesNeeded; i++) {

      const physicalTile = Matter.Bodies.rectangle(

        i * tileSize + tileSize / 2,

        window.innerHeight - tileSize / 2,

        tileSize,

        tileSize,

        { isStatic: true, friction: 0.5, restitution: 0.2 }

      );

      Matter.World.add(world, physicalTile);

    }


    swingRope();

    clearInterval(timerInterval);

    timerInterval = setInterval(updateTimer, 1000);

  }


  gameContainer.addEventListener('click', dropBlock);

  restartBtn.addEventListener('click', restartGame);


  swingRope();

  timerInterval = setInterval(updateTimer, 1000);


  // Continuous checking for complete columns and game over

  Matter.Events.on(engine, 'afterUpdate', () => {

    if (gameActive) {

      checkForCompleteColumns();

      checkForGameOver();

    }

  });


  // Preload images

  const images = [

    'https://static.wikia.nocookie.net/portalworldsgame/images/0/00/Wooden_Crate.png',

    'https://cdna.artstation.com/p/assets/images/images/034/060/716/large/diego-lopez-groundtile1.jpg'

  ];


  images.forEach((src) => {

    const img = new Image();

    img.src = src;

    img.onload = () => console.log(`Image loaded successfully: ${src}`);

    img.onerror = () => console.error(`Failed to load image: ${src}`);

  });

</script>

</body>

</html>