need to add some "snapage" functionality to vertices and edges, to be able to attach and detach lines from wireframe model. output full code, without any frameworks or libraries used.: <!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Visual Matrix Constructor</title>
  <style>
    body {
      margin: 0;
      overflow: hidden;
    }
    canvas {
      display: block;
    }
    .vmc-menu {
      display: none;
      position: absolute;
      background-color: rgba(0,0,0,0.1);
      border-radius: 5px;
      padding: 10px;
      box-shadow: 0 2px 5px rgba(0,0,0,0.1);
      z-index:1;
    }
    .vmc-menu label {
      display: block;
      margin-bottom: 10px;
    }
    .red-dot {
      position: absolute;
      width: 5px;
      height: 5px;
      background-color: red;
      border-radius: 50%;
      pointer-events: none;
      color:red;
      z-index:0;

    }
  </style>
</head>
<body>
  <div class="vmc-menu" id="vmc-menu">
    <label>Vertex X: <input type="number" id="vmc-vertex-x" step="0.1"></label>
    <label>Vertex Y: <input type="number" id="vmc-vertex-y" step="0.1"></label>
    <label>Vertex Z: <input type="number" id="vmc-vertex-z" step="0.1"></label>
    <button id="add-edge">Add Edge</button>
    <button id="remove-edge">Remove Edge</button>
  </div>
  <div class="red-dot" id="red-dot"></div>
  <script>
  const canvas = document.createElement('canvas');
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
document.body.appendChild(canvas);

const ctx = canvas.getContext('2d');
const vmcMenu = document.getElementById('vmc-menu');

const vertices = [
  [0, 0, 0],
  [0, 1, 0],
  [1, 1, 0],
  [1, 0, 0],
  [0, 0, 1],
  [0, 1, 1],
  [1, 1, 1],
  [1, 0, 1],
];

const edges = [
  [0, 1],
  [1, 2],
  [2, 3],
  [3, 0],
  [0, 4],
  [1, 5],
  [2, 6],
  [3, 7],
  [4, 5],
  [5, 6],
  [6, 7],
  [7, 4],
];

const scale = 0.025;
const zoom = 1;
const offsetX = 0.5;
const offsetY = 0.5;

let angleX = 0;
let angleY = 0;
let angleZ = 0;
  let bestIndex = -1;
  let bestDistance = Infinity;
let startNewEdgeIndex = -1;
let isMouseDown = false;
let prevMousePos = null;

// Red Dot
const redDot = document.getElementById('red-dot');

// Add Edge
document.getElementById('add-edge').addEventListener('click', () => {
  if (bestIndex === -1) return;
  if (startNewEdgeIndex === -1) {
    startNewEdgeIndex = bestIndex;
  } else {
    edges.push([startNewEdgeIndex, bestIndex]);
    startNewEdgeIndex = -1;
  }
});

// Remove Edge
document.getElementById('remove-edge').addEventListener('click', () => {
  if (bestIndex === -1) return;
  edges.forEach((edge, index) => {
    if (edge.includes(bestIndex)) {
      edges.splice(index, 1);
    }
  });
});

function rotateX(angle) {
  const c = Math.cos(angle);
  const s = Math.sin(angle);
  return [
    [1, 0, 0],
    [0, c, -s],
    [0, s, c],
  ];
}

function rotateY(angle) {
  const c = Math.cos(angle);
  const s = Math.sin(angle);
  return [
    [c, 0, s],
    [0, 1, 0],
    [-s, 0, c],
  ];
}

function rotateZ(angle) {
  const c = Math.cos(angle);
  const s = Math.sin(angle);
  return [
    [c, -s, 0],
    [s, c, 0],
    [0, 0, 1],
  ];
}

function project(vertex, scale, offsetX, offsetY, zoom) {
  const [x, y, z] = vertex;
  const posX = (x - offsetX) * scale;
  const posY = (y - offsetY) * scale;
  const posZ = z * scale;

  return [
    (posX * (zoom + posZ) + canvas.width / 2),
    (posY * (zoom + posZ) + canvas.height / 2),
  ];
}

function transform(vertex, rotationMatrix) {
  const [x, y, z] = vertex;
  const [rowX, rowY, rowZ] = rotationMatrix;
  return [
    x * rowX[0] + y * rowX[1] + z * rowX[2],
    x * rowY[0] + y * rowY[1] + z * rowY[2],
    x * rowZ[0] + y * rowZ[1] + z * rowZ[2],
  ];
}

function extraterrestrialTransformation(vertex, frequency, amplitude) {
  const [x, y, z] = vertex;
  const cosX = (Math.cos(x * frequency) * amplitude);
  const cosY = (Math.cos(y * frequency) * amplitude);
  const cosZ = (Math.cos(z * frequency) * amplitude);

  return [x + cosX, y + cosY, z + cosZ];
}

function getDeviation(maxDeviation) {
const t = Date.now() / 1000;
const frequency = 100 / 50;
const amplitude = maxDeviation / 10;
const deviation = Math.sin(t * frequency) * amplitude;
return deviation.toFixed(3);
}

function render() {
  ctx.fillStyle = '#FFF';
  ctx.fillRect(0, 0, canvas.width, canvas.height);

  const rotX = rotateX(angleX);
  const rotY = rotateY(angleY);
  const rotZ = rotateZ(angleZ);

  // Extraterrestrial transformation parameters
  const frequency = 1;
  const amplitude = 0.8;

  const transformedVertices = vertices.map(vertex => {
    const extraterrestrialVertex = extraterrestrialTransformation(vertex, frequency, amplitude);
    const cx = extraterrestrialVertex[0] - offsetX;
    const cy = extraterrestrialVertex[1] - offsetY;
    const cz = extraterrestrialVertex[2] - offsetY;

    const rotated = transform(transform(transform([cx, cy, cz], rotX), rotY), rotZ);

    return [
      rotated[0] + offsetX,
      rotated[1] + offsetY,
      rotated[2] + offsetY,
    ];
  });

  const projectedVertices = transformedVertices.map(vertex => project(vertex, canvas.height * scale, offsetX, offsetY, zoom));

ctx.lineWidth = 2;
ctx.strokeStyle = 'hsla(' + (angleX + offsetX + angleY + offsetY) * 55 + ', 100%, 30%, 0.8)';
ctx.beginPath();

for (let edge of edges) {
const [a, b] = edge;
const [x1, y1] = projectedVertices[a];
const [x2, y2] = projectedVertices[b];

const dist = Math.sqrt((x2 - x1) ** 2 + (y2 - y1) ** 2 + (y2 - x1) ** 2 + (x2 - y1));
const angle = Math.atan2(y2 - y1, x2 - x1, x2 - y1, y2 - x1);

// Calculate control point for curved edge
const cpDist = 0.005 * dist;
const cpX = (x1 + x2) / 2 + cpDist * Math.cos(angle - Math.PI / 2) * getDeviation(0.2);
const cpY = (y1 + y2) / 2 + cpDist * Math.sin(angle - Math.PI / 2) * getDeviation(0.2);

ctx.moveTo(x1, y1, x2, y2);
ctx.quadraticCurveTo(cpX, cpY, x2, y2, x1, y1);
}

ctx.stroke();

canvas.addEventListener('mousedown', (event) => {
  isMouseDown = true;
  prevMousePos = { x: event.clientX, y: event.clientY };
});

canvas.addEventListener('mouseup', () => {
  isMouseDown = false;
  prevMousePos = null;
});

canvas.addEventListener('mousemove', (event) => {
  const mousePos = {
    x: event.clientX - canvas.getBoundingClientRect().left,
    y: event.clientY - canvas.getBoundingClientRect().top
  };
  
  bestIndex = -1;
  bestDistance = Infinity;

  projectedVertices.forEach((currVertex, index) => {
    const distance = Math.hypot(
      currVertex[0] - mousePos.x,
      currVertex[1] - mousePos.y
    );
    if (distance < bestDistance) {
      bestIndex = index;
      bestDistance = distance;
    }
  });
  
  if (bestDistance < 10 && bestIndex !== -1) {
    vmcMenu.style.display = 'block';
    vmcMenu.style.left = mousePos.x + 'px';
    vmcMenu.style.top = mousePos.y + 'px';

    document.getElementById('vmc-vertex-x').value = vertices[bestIndex][0];
    document.getElementById('vmc-vertex-y').value = vertices[bestIndex][1];
    document.getElementById('vmc-vertex-z').value = vertices[bestIndex][2];

    document.getElementById('vmc-vertex-x').dataset.vertexIndex = bestIndex;
    document.getElementById('vmc-vertex-y').dataset.vertexIndex = bestIndex;
    document.getElementById('vmc-vertex-z').dataset.vertexIndex = bestIndex;

    redDot.style.display = 'block';
    redDot.style.left = projectedVertices[bestIndex][0] - 3 + 'px';
    redDot.style.top = projectedVertices[bestIndex][1] - 3 + 'px';
  } else {
    vmcMenu.style.display = 'none';
    redDot.style.display = 'none';
  }

  if (isMouseDown && prevMousePos) {
    const deltaX = event.clientX - prevMousePos.x;
    const deltaY = event.clientY - prevMousePos.y;

    angleY += deltaX * 0.01;
    angleX += deltaY * 0.01;

    prevMousePos = { x: event.clientX, y: event.clientY };
  }
});

function updateVertexValue(event, indexToUpdate) {
  const newValue = parseFloat(event.target.value);
  const vertexIndex = parseInt(event.target.dataset.vertexIndex);

  if (!isNaN(newValue) && vertexIndex >= 0) {
    vertices[vertexIndex][indexToUpdate] = newValue;
  }
}

document.getElementById('vmc-vertex-x').addEventListener('input', (event) => {
  updateVertexValue(event, 0);
});
document.getElementById('vmc-vertex-y').addEventListener('input', (event) => {
  updateVertexValue(event, 1);
});
document.getElementById('vmc-vertex-z').addEventListener('input', (event) => {
  updateVertexValue(event, 2);
});

angleX += +getDeviation(0.0005);
angleY += +getDeviation(0.0005);
angleZ += +getDeviation(0.0005);

  requestAnimationFrame(render);
}

requestAnimationFrame(render);
window.addEventListener("resize", () => {
  canvas.width = window.innerWidth;
  canvas.height = window.innerHeight;
});
  </script>
</body>
</html>