🎮ArcadeLab

Gear Factory

by SparkDragon18
1925 lines26.5 KB
▶ Play
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no">

<title>Gear Factory</title>

<style>
*{
  box-sizing:border-box;
  -webkit-tap-highlight-color:transparent;
}

html,body{
  margin:0;
  width:100%;
  height:100%;
  overflow:hidden;
  background:#101216;
  color:#eee;
  font-family:Arial,sans-serif;
  touch-action:none;
}

body{
  background:
    radial-gradient(circle at 50% 35%,#30343a 0%,#181b20 45%,#090a0d 100%);
}

/* =========================
   ВЕРХНЯЯ ПАНЕЛЬ
========================= */

#top{
  position:absolute;
  top:0;
  left:0;
  width:100%;
  height:76px;
  z-index:20;

  display:flex;
  align-items:center;
  justify-content:space-between;

  padding:8px 14px;

  background:rgba(9,10,13,.94);
  border-bottom:1px solid #3a3e45;

  box-shadow:0 5px 20px #0008;
}

/* =========================
   НОВЫЙ ЛОГОТИП
========================= */

.logo{
  display:flex;
  align-items:center;
  gap:10px;

  height:58px;

  user-select:none;
}

.logoImage{
  width:54px;
  height:54px;

  object-fit:contain;

  filter:
    drop-shadow(0 3px 4px #000)
    drop-shadow(0 0 7px #8c969f);

  mix-blend-mode:screen;
}

.logoText{
  font-weight:900;
  font-size:20px;
  letter-spacing:1px;

  color:#e8edf2;

  text-shadow:
    0 2px 4px #000,
    0 0 8px #777;
}

.logoSub{
  font-size:8px;
  letter-spacing:3px;
  color:#7f8993;
  margin-top:2px;
}

/* =========================
   СТАТИСТИКА
========================= */

.stats{
  display:flex;
  gap:8px;
}

.stat{
  min-width:82px;
  padding:8px 11px;

  border:1px solid #444951;
  border-radius:10px;

  background:#191c21;

  text-align:center;
  font-weight:bold;
}

.stat small{
  display:block;
  color:#858b94;
  font-size:10px;
}

/* =========================
   ИГРОВАЯ ОБЛАСТЬ
========================= */

#factory{
  position:absolute;
  left:0;
  right:0;
  top:76px;
  bottom:100px;

  overflow:hidden;

  background:
    linear-gradient(#ffffff05 1px,transparent 1px),
    linear-gradient(90deg,#ffffff05 1px,transparent 1px);

  background-size:40px 40px;
}

/* =========================
   МАШИНА
========================= */

#machine{
  position:absolute;

  left:50%;
  top:50%;

  width:900px;
  height:620px;

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

#base{
  position:absolute;

  left:50%;
  top:50%;

  width:720px;
  height:460px;

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

  border-radius:24px;

  border:2px solid #454a52;

  background:
    linear-gradient(145deg,#292d33,#15181c);

  box-shadow:
    inset 0 0 0 8px #0b0d10,
    inset 0 0 35px #000,
    0 20px 50px #000b;
}

#base:before,
#base:after{
  content:"";

  position:absolute;

  width:18px;
  height:18px;

  border-radius:50%;

  background:#08090b;

  border:3px solid #555b63;
}

#base:before{
  left:25px;
  top:25px;
}

#base:after{
  right:25px;
  bottom:25px;
}

/* =========================
   ШЕСТЕРЁНКИ
========================= */

.gear{
  position:absolute;

  width:140px;
  height:140px;

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

  cursor:pointer;

  z-index:5;

  user-select:none;
  touch-action:none;
}

.gear img{
  position:absolute;

  width:100%;
  height:100%;

  object-fit:contain;

  pointer-events:none;
  user-select:none;

  mix-blend-mode:screen;

  filter:
    drop-shadow(0 5px 4px #000)
    drop-shadow(0 0 4px #777);
}

.gear .core{
  position:absolute;

  left:50%;
  top:50%;

  width:37px;
  height:37px;

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

  border-radius:50%;

  border:7px solid #70757c;

  background:#17191c;

  box-shadow:
    inset 0 0 7px #000,
    0 0 4px #000;

  pointer-events:none;
}

.gear .bolt{
  position:absolute;

  left:50%;
  top:50%;

  width:12px;
  height:12px;

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

  border-radius:50%;

  background:#969ba1;

  box-shadow:inset 0 2px 2px #fff5;

  pointer-events:none;
}

/* =========================
   ВРАЩЕНИЕ
========================= */

.gear.cw{
  animation:rotateCW var(--speed,3s) linear infinite;
}

.gear.ccw{
  animation:rotateCCW var(--speed,3s) linear infinite;
}

.gear.dragging{
  animation-play-state:paused !important;
  z-index:50;

  filter:
    drop-shadow(0 0 10px #fff)
    drop-shadow(0 0 20px #8fa8c5);
}

@keyframes rotateCW{
  from{
    transform:
      translate(-50%,-50%)
      rotate(0deg);
  }

  to{
    transform:
      translate(-50%,-50%)
      rotate(360deg);
  }
}

@keyframes rotateCCW{
  from{
    transform:
      translate(-50%,-50%)
      rotate(360deg);
  }

  to{
    transform:
      translate(-50%,-50%)
      rotate(0deg);
  }
}

/* =========================
   ВЫБРАННАЯ ШЕСТЕРЁНКА
========================= */

.gear.selected{
  filter:
    drop-shadow(0 0 10px #9eb5ca)
    drop-shadow(0 0 18px #7891a8);
}

.gear.selected:after{
  content:"";

  position:absolute;

  inset:-7px;

  border:2px solid #aabed3;

  border-radius:50%;

  pointer-events:none;
}

/* =========================
   ПРОЧНОСТЬ
========================= */

.health{
  position:absolute;

  left:50%;
  top:calc(100% + 3px);

  transform:translateX(-50%);

  width:95px;
  height:7px;

  background:#090a0c;

  border:1px solid #555;

  border-radius:8px;

  overflow:hidden;
}

.healthFill{
  width:100%;
  height:100%;

  background:#8fa37b;

  transition:.25s;
}

/* =========================
   ИНФОРМАЦИЯ
========================= */

#info{
  position:absolute;

  left:12px;
  top:88px;

  z-index:25;

  width:205px;

  padding:12px;

  border-radius:15px;

  border:1px solid #454a51;

  background:#101318ef;

  box-shadow:0 10px 30px #000b;

  display:none;
}

#info.show{
  display:block;
}

.infoTitle{
  font-size:18px;
  font-weight:900;
  margin-bottom:7px;
}

.infoRow{
  display:flex;
  justify-content:space-between;

  padding:4px 0;

  color:#b8bec6;
}

.infoRow b{
  color:#fff;
}

/* =========================
   МАГАЗИН
========================= */

#shop{
  position:absolute;

  right:12px;
  top:88px;

  z-index:25;

  width:175px;

  padding:12px;

  border:1px solid #4a5058;

  border-radius:15px;

  background:#111419ee;

  box-shadow:0 10px 30px #000b;

  display:none;
}

#shop.show{
  display:block;
}

.shopTitle{
  font-weight:900;
  margin-bottom:8px;
}

.shopGear{
  width:100%;
  height:100px;

  object-fit:contain;

  mix-blend-mode:screen;

  filter:drop-shadow(0 5px 5px #000);
}

.price{
  color:#c4cbd3;
  margin-bottom:7px;
}

#buy{
  width:100%;
}

/* =========================
   НИЖНЯЯ ПАНЕЛЬ
========================= */

#bottom{
  position:absolute;

  bottom:0;
  left:0;

  width:100%;
  height:100px;

  z-index:30;

  display:flex;

  align-items:center;
  justify-content:center;

  gap:8px;

  padding:8px;

  background:#0b0d10ee;

  border-top:1px solid #40454c;

  box-shadow:0 -5px 25px #000a;
}

button{
  border:1px solid #555c65;

  background:
    linear-gradient(#30353c,#1a1d22);

  color:#fff;

  border-radius:12px;

  min-height:64px;

  padding:7px 12px;

  font-weight:bold;
  font-size:12px;

  box-shadow:
    inset 0 1px 0 #ffffff12,
    0 4px 12px #0008;

  transition:
    transform .08s,
    background .15s,
    box-shadow .15s;
}

button:active{
  transform:scale(.94);

  background:#414750;

  box-shadow:
    inset 0 0 15px #0008,
    0 2px 6px #000;
}

button.active{
  background:
    linear-gradient(#515b66,#252b31);

  border-color:#aebbc7;

  box-shadow:
    0 0 12px #8294a5,
    inset 0 0 10px #fff1;
}

.actionIcon{
  width:30px;
  height:30px;

  object-fit:contain;

  display:block;

  margin:0 auto 3px;

  filter:
    brightness(0)
    invert(1)
    drop-shadow(0 0 3px #fff5);
}

button span{
  display:block;
  margin-top:3px;
}

/* =========================
   СООБЩЕНИЯ
========================= */

#toast{
  position:absolute;

  left:50%;
  top:90px;

  transform:
    translateX(-50%)
    translateY(-20px);

  z-index:100;

  padding:11px 17px;

  border-radius:10px;

  background:#15191e;

  border:1px solid #656c75;

  box-shadow:0 8px 25px #000;

  opacity:0;

  pointer-events:none;

  transition:.25s;
}

#toast.show{
  opacity:1;

  transform:
    translateX(-50%)
    translateY(0);
}

/* =========================
   МОБИЛЬНЫЙ РЕЖИМ
========================= */

@media(max-width:700px){

  #top{
    height:64px;
    padding:6px 8px;
  }

  .logo{
    gap:6px;
    height:52px;
  }

  .logoImage{
    width:43px;
    height:43px;
  }

  .logoText{
    font-size:13px;
    letter-spacing:.5px;
  }

  .logoSub{
    font-size:6px;
    letter-spacing:2px;
  }

  .stats{
    gap:3px;
  }

  .stat{
    min-width:59px;
    padding:6px 3px;
    font-size:10px;
  }

  .stat small{
    font-size:7px;
  }

  #factory{
    top:64px;
    bottom:94px;
  }

  #machine{
    width:700px;
    height:500px;
  }

  #base{
    width:590px;
    height:370px;
  }

  #bottom{
    height:94px;

    gap:4px;
    padding:5px;

    overflow-x:auto;
    justify-content:flex-start;

    scrollbar-width:none;
  }

  #bottom::-webkit-scrollbar{
    display:none;
  }

  button{
    flex:0 0 auto;

    min-width:64px;
    min-height:67px;

    padding:5px 3px;

    font-size:8px;
  }

  .actionIcon{
    width:28px;
    height:28px;
  }

  .gear{
    width:115px;
    height:115px;
  }

  #info{
    transform:scale(.85);
    transform-origin:top left;
  }

  #shop{
    transform:scale(.85);
    transform-origin:top right;
  }
}
</style>
</head>

<body>

<!-- =========================
     ВЕРХ
========================= -->

<div id="top">

  <div class="logo">

    <img
      class="logoImage"
      src="https://img.pikbest.com/element_our/20230502/bg/184c55385d6c5.png!f305cw"
      alt="Gear Factory logo"
    >

    <div>
      <div class="logoText">
        GEAR FACTORY
      </div>

      <div class="logoSub">
        MECHANICAL SYSTEM
      </div>
    </div>

  </div>

  <div class="stats">

    <div class="stat">
      <span id="money">250</span> $
      <small>ДЕНЬГИ</small>
    </div>

    <div class="stat">
      <span id="power">0</span>
      <small>МОЩНОСТЬ</small>
    </div>

    <div class="stat">
      <span id="count">0</span>
      <small>ДЕТАЛЕЙ</small>
    </div>

  </div>

</div>


<!-- =========================
     ИГРА
========================= -->

<div id="factory">

  <div id="machine">

    <div id="base"></div>

  </div>

</div>


<!-- =========================
     ИНФОРМАЦИЯ
========================= -->

<div id="info">

  <div class="infoTitle">
    Шестерёнка
  </div>

  <div class="infoRow">
    <span>Уровень</span>
    <b id="level">1</b>
  </div>

  <div class="infoRow">
    <span>Прочность</span>
    <b id="durability">100</b>
  </div>

  <div class="infoRow">
    <span>Скорость</span>
    <b id="speedText">1.0x</b>
  </div>

  <div class="infoRow">
    <span>Мощность</span>
    <b id="powerText">10</b>
  </div>

</div>


<!-- =========================
     МАГАЗИН
========================= -->

<div id="shop">

  <div class="shopTitle">
    МАГАЗИН
  </div>

  <img
    class="shopGear"
    src="https://png.pngtree.com/png-clipart/20240208/original/pngtree-small-gear-on-a-white-background-watch-photo-png-image_14259145.png"
  >

  <div class="price">
    Новая шестерёнка — 75 $
  </div>

  <button id="buy">
    КУПИТЬ
  </button>

</div>


<div id="toast"></div>


<!-- =========================
     КНОПКИ
========================= -->

<div id="bottom">

  <button id="repair">

    <img
      class="actionIcon"
      src="https://png.pngtree.com/png-vector/20260224/ourmid/pngtree-industrial-gear-and-wrench-tool-outline-vector-for-repair-service-icon-png-image_18801855.webp"
    >

    <span>ПОЧИНИТЬ</span>

  </button>


  <button id="upgrade">

    <img
      class="actionIcon"
      src="https://png.pngtree.com/png-vector/20260224/ourmid/pngtree-industrial-gear-and-wrench-tool-outline-vector-for-repair-service-icon-png-image_18801855.webp"
    >

    <span>УЛУЧШИТЬ</span>

  </button>


  <button id="add">

    <img
      class="actionIcon"
      src="https://cdn-icons-png.flaticon.com/512/416/416255.png"
    >

    <span>ПОСТАВИТЬ</span>

  </button>


  <button id="shopBtn">

    <img
      class="actionIcon"
      src="https://cdn-icons-png.flaticon.com/512/126/126122.png"
    >

    <span>МАГАЗИН</span>

  </button>


  <button id="remove">

    <img
      class="actionIcon"
      src="https://cdn-icons-png.flaticon.com/512/64/64022.png"
    >

    <span>УБРАТЬ</span>

  </button>


  <button id="dragButton">

    <img
      class="actionIcon"
      src="https://images.icon-icons.com/4260/PNG/512/gear_cogwheel_arrows_circular_process_icon_265392.png"
    >

    <span>ПЕРЕТАСКИВАТЬ</span>

  </button>

</div>


<script>

/* =========================================
   ОСНОВА
========================================= */

const machine=
  document.getElementById("machine");

const factory=
  document.getElementById("factory");

let money=250;

let selected=null;

let gearID=0;

let dragMode=false;

let draggingGear=null;

let dragPointerId=null;

const gears=[];


/* =========================================
   СТАРТОВЫЕ ШЕСТЕРЁНКИ
========================================= */

addGear(330,250,145);
addGear(465,250,110);
addGear(560,250,155);


/* =========================================
   СОЗДАНИЕ
========================================= */

function addGear(x,y,size=130){

  const data={

    id:gearID++,

    x:x,
    y:y,

    size:size,

    level:1,

    hp:100,
    maxHp:100,

    speed:1,
    power:10,

    direction:
      gears.length%2===0
      ? "cw"
      : "ccw",

    element:null
  };

  gears.push(data);

  createGearElement(data);

  updateStats();

  return data;
}


/* =========================================
   ЭЛЕМЕНТ
========================================= */

function createGearElement(data){

  const el=
    document.createElement("div");

  el.className=
    "gear "+data.direction;

  el.dataset.id=data.id;

  el.style.left=
    data.x+"px";

  el.style.top=
    data.y+"px";

  el.style.width=
    data.size+"px";

  el.style.height=
    data.size+"px";

  el.style.setProperty(
    "--speed",
    Math.max(
      .45,
      3.2/data.speed
    )+"s"
  );

  el.innerHTML=`

    <img
      src="https://png.pngtree.com/png-clipart/20240208/original/pngtree-small-gear-on-a-white-background-watch-photo-png-image_14259145.png"
    >

    <div class="core"></div>

    <div class="bolt"></div>

    <div class="health">
      <div class="healthFill"></div>
    </div>

  `;

  machine.appendChild(el);

  data.element=el;

  updateHealth(data);

  el.addEventListener(
    "pointerdown",
    gearPointerDown
  );
}


/* =========================================
   НАЖАТИЕ
========================================= */

function gearPointerDown(e){

  e.stopPropagation();

  const id=
    Number(this.dataset.id);

  const data=
    gears.find(
      g=>g.id===id
    );

  if(!data)return;

  selectGear(data);

  if(!dragMode)return;

  startDragging(
    e,
    data
  );
}


/* =========================================
   ВЫБОР
========================================= */

function selectGear(data){

  selected=data;

  document
    .querySelectorAll(".gear")
    .forEach(g=>{
      g.classList.remove("selected");
    });

  data.element
    .classList.add("selected");

  document
    .getElementById("info")
    .classList.add("show");

  updateInfo();
}


/* =========================================
   РЕЖИМ ПЕРЕТАСКИВАНИЯ
========================================= */

document
  .getElementById("dragButton")
  .addEventListener(
    "click",
    ()=>{

      dragMode=!dragMode;

      document
        .getElementById("dragButton")
        .classList.toggle(
          "active",
          dragMode
        );

      toast(
        dragMode
        ? "Режим перетаскивания включён"
        : "Режим перетаскивания выключен"
      );

    }
  );


/* =========================================
   НАЧАЛО ПЕРЕТАСКИВАНИЯ
========================================= */

function startDragging(e,data){

  draggingGear=data;

  dragPointerId=
    e.pointerId;

  data.element
    .classList
    .add("dragging");

  try{

    data.element
      .setPointerCapture(
        e.pointerId
      );

  }catch(err){}

  updateGearPosition(
    e,
    data
  );

  data.element.addEventListener(
    "pointermove",
    draggingMove
  );

  data.element.addEventListener(
    "pointerup",
    draggingEnd
  );

  data.element.addEventListener(
    "pointercancel",
    draggingEnd
  );
}


/* =========================================
   ДВИЖЕНИЕ
========================================= */

function draggingMove(e){

  if(!draggingGear)return;

  if(
    e.pointerId!==dragPointerId
  )return;

  updateGearPosition(
    e,
    draggingGear
  );
}


/* =========================================
   ПОЗИЦИЯ
========================================= */

function updateGearPosition(e,data){

  const rect=
    machine.getBoundingClientRect();

  const scaleX=
    machine.offsetWidth/
    rect.width;

  const scaleY=
    machine.offsetHeight/
    rect.height;

  let x=
    (e.clientX-rect.left)*
    scaleX;

  let y=
    (e.clientY-rect.top)*
    scaleY;

  const margin=
    data.size*.35;

  x=Math.max(
    margin,
    Math.min(
      machine.offsetWidth-margin,
      x
    )
  );

  y=Math.max(
    margin,
    Math.min(
      machine.offsetHeight-margin,
      y
    )
  );

  data.x=x;
  data.y=y;

  data.element.style.left=
    x+"px";

  data.element.style.top=
    y+"px";
}


/* =========================================
   КОНЕЦ ПЕРЕТАСКИВАНИЯ
========================================= */

function draggingEnd(e){

  if(!draggingGear)return;

  if(
    e.pointerId!==dragPointerId
  )return;

  const gear=
    draggingGear;

  gear.element
    .classList
    .remove("dragging");

  gear.element
    .removeEventListener(
      "pointermove",
      draggingMove
    );

  gear.element
    .removeEventListener(
      "pointerup",
      draggingEnd
    );

  gear.element
    .removeEventListener(
      "pointercancel",
      draggingEnd
    );

  try{

    gear.element
      .releasePointerCapture(
        e.pointerId
      );

  }catch(err){}

  draggingGear=null;

  dragPointerId=null;

  updateConnections();

  toast(
    "Шестерёнка перемещена"
  );
}


/* =========================================
   ИНФОРМАЦИЯ
========================================= */

function updateInfo(){

  if(!selected)return;

  document.getElementById("level")
    .textContent=
    selected.level;

  document.getElementById("durability")
    .textContent=
    Math.round(
      selected.hp
    );

  document.getElementById("speedText")
    .textContent=
    selected.speed.toFixed(1)
    +"x";

  document.getElementById("powerText")
    .textContent=
    selected.power;
}


/* =========================================
   ПРОЧНОСТЬ
========================================= */

function updateHealth(data){

  if(!data.element)return;

  const fill=
    data.element
      .querySelector(
        ".healthFill"
      );

  fill.style.width=
    Math.max(
      0,
      data.hp/data.maxHp*100
    )+"%";
}


/* =========================================
   ДЕНЬГИ
========================================= */

function spend(amount){

  if(money<amount){

    toast(
      "Недостаточно денег"
    );

    return false;
  }

  money-=amount;

  updateStats();

  return true;
}


/* =========================================
   ПОЧИНИТЬ
========================================= */

document
  .getElementById("repair")
  .addEventListener(
    "click",
    ()=>{

      if(!selected){

        toast(
          "Сначала выбери шестерёнку"
        );

        return;
      }

      if(
        selected.hp>=
        selected.maxHp
      ){

        toast(
          "Она уже полностью исправна"
        );

        return;
      }

      const missing=
        selected.maxHp-
        selected.hp;

      const price=
        Math.max(
          5,
          Math.ceil(
            missing*.45
          )
        );

      if(!spend(price))
        return;

      selected.hp=
        selected.maxHp;

      updateHealth(selected);
      updateInfo();

      toast(
        "Шестерёнка починена"
      );
    }
  );


/* =========================================
   УЛУЧШИТЬ
========================================= */

document
  .getElementById("upgrade")
  .addEventListener(
    "click",
    ()=>{

      if(!selected){

        toast(
          "Сначала выбери шестерёнку"
        );

        return;
      }

      const price=
        50*selected.level;

      if(!spend(price))
        return;

      selected.level++;

      selected.maxHp+=35;
      selected.hp=
        selected.maxHp;

      selected.speed+=.35;
      selected.power+=12;

      selected.element
        .style.setProperty(
          "--speed",
          Math.max(
            .35,
            3.2/
            selected.speed
          )+"s"
        );

      updateHealth(selected);
      updateInfo();
      updateStats();

      toast(
        "Уровень повышен до "+
        selected.level
      );
    }
  );


/* =========================================
   ПОКУПКА
========================================= */

document
  .getElementById("buy")
  .addEventListener(
    "click",
    ()=>{

      if(!spend(75))
        return;

      const angle=
        Math.random()*
        Math.PI*2;

      const radius=160;

      const x=
        450+
        Math.cos(angle)*
        radius;

      const y=
        310+
        Math.sin(angle)*
        radius;

      const g=
        addGear(
          x,
          y,
          95+
          Math.random()*65
        );

      selectGear(g);

      toast(
        "Новая шестерёнка установлена"
      );
    }
  );


/* =========================================
   ПОСТАВИТЬ
========================================= */

document
  .getElementById("add")
  .addEventListener(
    "click",
    ()=>{

      const g=
        addGear(
          300+
          Math.random()*300,

          170+
          Math.random()*250,

          100+
          Math.random()*60
        );

      selectGear(g);

      toast(
        "Шестерёнка добавлена"
      );
    }
  );


/* =========================================
   УБРАТЬ
========================================= */

document
  .getElementById("remove")
  .addEventListener(
    "click",
    ()=>{

      if(!selected){

        toast(
          "Выбери шестерёнку"
        );

        return;
      }

      selected.element.remove();

      const index=
        gears.indexOf(
          selected
        );

      if(index!==-1){

        gears.splice(
          index,
          1
        );
      }

      selected=null;

      document
        .getElementById("info")
        .classList
        .remove("show");

      updateStats();

      toast(
        "Шестерёнка убрана"
      );
    }
  );


/* =========================================
   МАГАЗИН
========================================= */

document
  .getElementById("shopBtn")
  .addEventListener(
    "click",
    ()=>{

      document
        .getElementById("shop")
        .classList.toggle(
          "show"
        );
    }
  );


/* =========================================
   СВЯЗИ ШЕСТЕРЁНОК
========================================= */

function updateConnections(){

  for(
    let i=0;
    i<gears.length;
    i++
  ){

    const a=gears[i];

    if(!a.element)
      continue;

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

      const b=gears[j];

      const dx=
        a.x-b.x;

      const dy=
        a.y-b.y;

      const distance=
        Math.sqrt(
          dx*dx+
          dy*dy
        );

      const contact=
        (a.size+b.size)*.46;

      if(
        distance<contact
      ){

        if(
          a.direction===
          b.direction
        ){

          b.direction=
            b.direction==="cw"
            ? "ccw"
            : "cw";

          b.element
            .classList
            .remove(
              "cw",
              "ccw"
            );

          b.element
            .classList
            .add(
              b.direction
            );
        }
      }
    }
  }
}


/* =========================================
   СТАТИСТИКА
========================================= */

function updateStats(){

  document
    .getElementById("money")
    .textContent=
    money;

  document
    .getElementById("count")
    .textContent=
    gears.length;

  let total=0;

  gears.forEach(g=>{

    total+=
      g.power*
      (g.hp/g.maxHp);

  });

  document
    .getElementById("power")
    .textContent=
    Math.round(total);
}


/* =========================================
   СООБЩЕНИЯ
========================================= */

let toastTimer;

function toast(text){

  const box=
    document.getElementById(
      "toast"
    );

  box.textContent=text;

  box.classList.add("show");

  clearTimeout(
    toastTimer
  );

  toastTimer=
    setTimeout(
      ()=>{

        box.classList.remove(
          "show"
        );

      },
      1600
    );
}


/* =========================================
   ИЗНОС
========================================= */

setInterval(
  ()=>{

    gears.forEach(g=>{

      g.hp-=
        .8*g.speed;

      if(g.hp<0)
        g.hp=0;

      updateHealth(g);

    });

    updateStats();

    if(selected)
      updateInfo();

  },
  2500
);


/* =========================================
   ПРОВЕРКА СОЕДИНЕНИЙ
========================================= */

setInterval(
  ()=>{
    updateConnections();
  },
  700
);


/* =========================================
   КЛИК ПО ПУСТОМУ МЕСТУ
========================================= */

factory.addEventListener(
  "pointerdown",
  e=>{

    if(
      e.target.closest(".gear")
    ){
      return;
    }

    if(selected){

      selected.element
        .classList
        .remove("selected");

      selected=null;

      document
        .getElementById("info")
        .classList
        .remove("show");
    }
  }
);


/* =========================================
   СТАРТ
========================================= */

updateStats();

</script>

</body>
</html>

Game Source: Gear Factory

Creator: SparkDragon18

Libraries: none

Complexity: complex (1925 lines, 26.5 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: gear-factory-sparkdragon18" to link back to the original. Then publish at arcadelab.ai/publish.