JustPaste
HomeCategoriesAboutDonateContactTerms of UsePrivacy Policy
JustPaste

Free online notepad — write and share instantly

Navigate

  • Home
  • Timeline
  • Categories

Info

  • About
  • Donate
  • Contact

Legal

  • Terms of Use
  • Privacy Policy

© 2026 JustPaste.app. All rights reserved.

Made with ♥ by JustPaste

code | JustPaste.app
13 days ago10 views
💻Technology

code

<!DOCTYPE html>
<html lang="fa">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>Mini Blender</title>

<style>
*{box-sizing:border-box}
html,body{margin:0;width:100%;height:100%;overflow:hidden;background:#171717;color:#ddd;font-family:Arial,sans-serif}
button,select,input{background:#292929;color:#ddd;border:1px solid #444;border-radius:4px}
button{padding:6px 10px;cursor:pointer}
button:hover{background:#3b3b3b}
#top{
 height:42px;background:#222;border-bottom:1px solid #111;
 display:flex;align-items:center;gap:6px;padding:5px
}
#top .logo{font-weight:bold;color:#f0a020;font-size:17px;margin:0 12px}
#top select{height:30px}
#main{height:calc(100% - 42px);display:flex}
#left{
 width:220px;background:#202020;border-right:1px solid #111;
 display:flex;flex-direction:column
}
#center{flex:1;position:relative;background:#111}
#right{
 width:280px;background:#202020;border-left:1px solid #111;
 overflow:auto
}
#timeline{
 position:absolute;left:0;right:0;bottom:0;height:155px;
 background:#191919;border-top:1px solid #050505
}
.panel-title{
 padding:8px;background:#292929;border-bottom:1px solid #111;font-weight:bold
}
.panel{padding:9px;border-bottom:1px solid #111}
.row{display:flex;gap:5px;margin:5px 0;align-items:center}
.row label{width:80px;font-size:12px;color:#aaa}
.row input{width:90px;padding:5px}
#outliner{overflow:auto;flex:1}
.obj{
 padding:6px 10px;cursor:pointer;white-space:nowrap
}
.obj:hover{background:#303030}
.obj.selected{background:#454545;color:white}
#tools{
 position:absolute;left:10px;top:10px;display:flex;flex-direction:column;gap:4px
}
#tools button{width:38px;height:38px;padding:0;font-size:17px}
#mode{
 position:absolute;left:65px;top:10px;
 background:#252525;padding:5px;border-radius:5px
}
#status{
 position:absolute;left:10px;bottom:165px;background:#222c;
 padding:7px;border-radius:4px;font-size:12px
}
#help{
 position:absolute;right:10px;top:10px;background:#222c;
 padding:10px;border-radius:5px;font-size:12px;line-height:1.7
}
#timelineHead{height:35px;display:flex;align-items:center;gap:5px;padding:4px}
#timelineCanvas{position:absolute;left:0;right:0;bottom:0;top:35px}
.section{
 padding:8px;border-bottom:1px solid #111
}
.section h4{margin:0 0 8px;color:#f0a020}
.color{
 width:55px;height:28px;padding:0;border:0
}
</style>
</head>

<body>

<div id="top">
 <div class="logo">MINI BLENDER</div>

 <select id="modeSelect">
  <option>Object Mode</option>
  <option>Edit Mode</option>
  <option>Pose Mode</option>
 </select>

 <button onclick="addCube()">+ Cube</button>
 <button onclick="addSphere()">+ Sphere</button>
 <button onclick="addCylinder()">+ Cylinder</button>
 <button onclick="addCone()">+ Cone</button>

 <button onclick="duplicate()">Duplicate</button>
 <button onclick="deleteSelected()">Delete</button>

 <button onclick="saveProject()">Save</button>
 <button onclick="loadProject()">Load</button>

 <span style="flex:1"></span>
 <button onclick="toggleGrid()">Grid</button>
</div>

<div id="main">

 <aside id="left">
  <div class="panel-title">OUTLINER</div>
  <div id="outliner"></div>

  <div class="panel-title">TOOLS</div>
  <div class="panel">
   <button onclick="setTool('translate')">Move</button>
   <button onclick="setTool('rotate')">Rotate</button>
   <button onclick="setTool('scale')">Scale</button>
   <button onclick="setTool('none')">Select</button>
  </div>
 </aside>

 <main id="center">
  <div id="tools">
   <button onclick="setTool('translate')">✥</button>
   <button onclick="setTool('rotate')">↻</button>
   <button onclick="setTool('scale')">⤢</button>
  </div>

  <div id="mode">
   <select id="selectMode">
    <option value="vertex">Vertex</option>
    <option value="edge">Edge</option>
    <option value="face">Face</option>
   </select>
  </div>

  <div id="help">
   <b>Shortcuts</b><br>
   G = Move<br>
   R = Rotate<br>
   S = Scale<br>
   Tab = Mode<br>
   A = Select All<br>
   X = Delete<br>
   Shift+D = Duplicate<br>
   I = Keyframe
  </div>

  <div id="status">Ready</div>

  <div id="timeline">
   <div id="timelineHead">
    <button onclick="play()">▶</button>
    <button onclick="stop()">■</button>
    <button onclick="prevFrame()">◀</button>
    <button onclick="nextFrame()">▶</button>
    <span>Frame:</span>
    <input id="frameInput" type="number" value="1" min="1" max="1000"
           style="width:70px">
    <button onclick="insertKeyframe()">Insert Keyframe</button>
   </div>
   <canvas id="timelineCanvas"></canvas>
  </div>
 </main>

 <aside id="right">

  <div class="panel-title">PROPERTIES</div>

  <div class="section">
   <h4>Transform</h4>

   <div class="row">
    <label>Location X</label>
    <input id="px" type="number" step=".1">
   </div>

   <div class="row">
    <label>Location Y</label>
    <input id="py" type="number" step=".1">
   </div>

   <div class="row">
    <label>Location Z</label>
    <input id="pz" type="number" step=".1">
   </div>

   <div class="row">
    <label>Rotation X</label>
    <input id="rx" type="number" step=".1">
   </div>

   <div class="row">
    <label>Rotation Y</label>
    <input id="ry" type="number" step=".1">
   </div>

   <div class="row">
    <label>Rotation Z</label>
    <input id="rz" type="number" step=".1">
   </div>

   <div class="row">
    <label>Scale X</label>
    <input id="sx" type="number" step=".1">
   </div>

   <div class="row">
    <label>Scale Y</label>
    <input id="sy" type="number" step=".1">
   </div>

   <div class="row">
    <label>Scale Z</label>
    <input id="sz" type="number" step=".1">
   </div>
  </div>

  <div class="section">
   <h4>Material</h4>

   <div class="row">
    <label>Color</label>
    <input id="color" class="color" type="color" value="#4b82ff">
   </div>

   <div class="row">
    <label>Metallic</label>
    <input id="metallic" type="range" min="0" max="1" step=".01" value="0">
   </div>

   <div class="row">
    <label>Roughness</label>
    <input id="roughness" type="range" min="0" max="1" step=".01" value=".5">
   </div>

   <div class="row">
    <label>Opacity</label>
    <input id="opacity" type="range" min="0" max="1" step=".01" value="1">
   </div>
  </div>

  <div class="section">
   <h4>Mesh</h4>
   <button onclick="enterEdit()">Edit Mode</button>
   <button onclick="extrude()">Extrude</button>
   <button onclick="mergeVertices()">Merge</button>
  </div>

  <div class="section">
   <h4>Animation</h4>
   <button onclick="insertKeyframe()">Insert Location Keyframe</button>
   <button onclick="clearKeyframes()">Clear Keyframes</button>
  </div>

 </aside>

</div>

<script src="https://cdn.jsdelivr.net/npm/[email protected]/build/three.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/examples/js/controls/OrbitControls.js"></script>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/examples/js/controls/TransformControls.js"></script>

<script>

let scene,camera,renderer,orbit,gizmo,raycaster,mouse;
let selected=null;
let tool='none';
let grid=true;
let playing=false;
let timer=null;

const objects=[];
const keyframes={};

init();
animate();

function init(){

 scene=new THREE.Scene();
 scene.background=new THREE.Color(0x111111);

 camera=new THREE.PerspectiveCamera(
  50,
  (window.innerWidth-500)/window.innerHeight,
  .1,
  5000
 );

 camera.position.set(7,5,8);

 renderer=new THREE.WebGLRenderer({
  antialias:true
 });

 renderer.setPixelRatio(window.devicePixelRatio);
 renderer.setSize(
  document.getElementById('center').clientWidth,
  document.getElementById('center').clientHeight
 );

 document.getElementById('center').appendChild(renderer.domElement);

 orbit=new THREE.OrbitControls(camera,renderer.domElement);
 orbit.enableDamping=true;

 gizmo=new THREE.TransformControls(camera,renderer.domElement);

 gizmo.addEventListener('dragging-changed',e=>{
  orbit.enabled=!e.value;
 });

 gizmo.addEventListener('change',()=>{
  updateProperties();
 });

 scene.add(gizmo);

 raycaster=new THREE.Raycaster();
 mouse=new THREE.Vector2();

 createGrid();

 const ambient=new THREE.HemisphereLight(0xffffff,0x333333,1.5);
 scene.add(ambient);

 const light=new THREE.DirectionalLight(0xffffff,2);
 light.position.set(5,10,5);
 scene.add(light);

 addCube();

 window.addEventListener('resize',resize);
 renderer.domElement.addEventListener('pointerdown',selectObject);

 setupProperties();
 drawTimeline();

}

function createGrid(){

 const helper=new THREE.GridHelper(40,40,0x444444,0x292929);
 helper.name='Grid';
 scene.add(helper);

 const axes=new THREE.AxesHelper(4);
 axes.name='Axes';
 scene.add(axes);

}

function toggleGrid(){

 grid=!grid;

 scene.getObjectByName('Grid').visible=grid;
 scene.getObjectByName('Axes').visible=grid;

}

function addCube(){

 const geo=new THREE.BoxGeometry(2,2,2);

 const mat=new THREE.MeshStandardMaterial({
  color:0x4b82ff,
  roughness:.5,
  metalness:0
 });

 const mesh=new THREE.Mesh(geo,mat);

 mesh.name='Cube';
 mesh.position.set(0,1,0);

 addObject(mesh);

}

function addSphere(){

 const geo=new THREE.SphereGeometry(1,32,16);

 const mat=new THREE.MeshStandardMaterial({
  color:0x4b82ff
 });

 const mesh=new THREE.Mesh(geo,mat);

 mesh.name='Sphere';
 mesh.position.set(0,1,0);

 addObject(mesh);

}

function addCylinder(){

 const geo=new THREE.CylinderGeometry(1,1,2,32);

 const mat=new THREE.MeshStandardMaterial({
  color:0x4b82ff
 });

 const mesh=new THREE.Mesh(geo,mat);

 mesh.name='Cylinder';
 mesh.position.set(0,1,0);

 addObject(mesh);

}

function addCone(){

 const geo=new THREE.ConeGeometry(1,2,32);

 const mat=new THREE.MeshStandardMaterial({
  color:0x4b82ff
 });

 const mesh=new THREE.Mesh(geo,mat);

 mesh.name='Cone';
 mesh.position.set(0,1,0);

 addObject(mesh);

}

function addObject(obj){

 let base=obj.name;
 let n=1;

 while(scene.getObjectByName(obj.name)){
  n++;
  obj.name=base+'.'+String(n).padStart(3,'0');
 }

 scene.add(obj);
 objects.push(obj);

 select(obj);
 updateOutliner();

}

function select(obj){

 selected=obj;

 gizmo.detach();

 if(obj){
  gizmo.attach(obj);
 }

 updateOutliner();
 updateProperties();

 document.getElementById('status').textContent=
  obj ? 'Selected: '+obj.name : 'Nothing selected';

}

function selectObject(e){

 if(tool!=='none' && gizmo.dragging)return;

 const rect=renderer.domElement.getBoundingClientRect();

 mouse.x=((e.clientX-rect.left)/rect.width)*2-1;
 mouse.y=-((e.clientY-rect.top)/rect.height)*2+1;

 raycaster.setFromCamera(mouse,camera);

 const hits=raycaster.intersectObjects(objects,true);

 if(hits.length){

  let o=hits[0].object;

  while(o.parent && !objects.includes(o)){
   o=o.parent;
  }

  if(objects.includes(o)){
   select(o);
  }

 }

}

function updateOutliner(){

 const box=document.getElementById('outliner');
 box.innerHTML='';

 objects.forEach(o=>{

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

  div.className='obj'+(o===selected?' selected':'');

  let icon='◼';

  if(o.type==='Mesh')icon='▣';
  if(o.type==='Light')icon='💡';
  if(o.type==='Camera')icon='📷';

  div.textContent=icon+' '+o.name;

  div.onclick=()=>select(o);

  box.appendChild(div);

 });

}

function setTool(t){

 tool=t;

 if(t==='none'){
  gizmo.detach();
 }else{
  gizmo.attach(selected);

  if(t==='translate')gizmo.setMode('translate');
  if(t==='rotate')gizmo.setMode('rotate');
  if(t==='scale')gizmo.setMode('scale');
 }

 document.getElementById('status').textContent=
  'Tool: '+t;

}

function updateProperties(){

 if(!selected)return;

 setValue('px',selected.position.x);
 setValue('py',selected.position.y);
 setValue('pz',selected.position.z);

 setValue('rx',THREE.MathUtils.radToDeg(selected.rotation.x));
 setValue('ry',THREE.MathUtils.radToDeg(selected.rotation.y));
 setValue('rz',THREE.MathUtils.radToDeg(selected.rotation.z));

 setValue('sx',selected.scale.x);
 setValue('sy',selected.scale.y);
 setValue('sz',selected.scale.z);

 if(selected.material){

  document.getElementById('color').value=
   '#'+selected.material.color.getHexString();

  document.getElementById('metallic').value=
   selected.material.metalness ?? 0;

  document.getElementById('roughness').value=
   selected.material.roughness ?? .5;

  document.getElementById('opacity').value=
   selected.material.opacity ?? 1;

 }

}

function setValue(id,v){

 document.getElementById(id).value=
  Number(v).toFixed(3);

}

function setupProperties(){

 const ids=[
  'px','py','pz',
  'rx','ry','rz',
  'sx','sy','sz'
 ];

 ids.forEach(id=>{

  document.getElementById(id).addEventListener('change',()=>{

   if(!selected)return;

   selected.position.x=Number(document.getElementById('px').value);
   selected.position.y=Number(document.getElementById('py').value);
   selected.position.z=Number(document.getElementById('pz').value);

   selected.rotation.x=
    THREE.MathUtils.degToRad(Number(document.getElementById('rx').value));

   selected.rotation.y=
    THREE.MathUtils.degToRad(Number(document.getElementById('ry').value));

   selected.rotation.z=
    THREE.MathUtils.degToRad(Number(document.getElementById('rz').value));

   selected.scale.x=Number(document.getElementById('sx').value);
   selected.scale.y=Number(document.getElementById('sy').value);
   selected.scale.z=Number(document.getElementById('sz').value);

  });

 });

 document.getElementById('color').addEventListener('input',e=>{

  if(selected && selected.material){
   selected.material.color.set(e.target.value);
  }

 });

 document.getElementById('metallic').addEventListener('input',e=>{

  if(selected && selected.material){
   selected.material.metalness=Number(e.target.value);
  }

 });

 document.getElementById('roughness').addEventListener('input',e=>{

  if(selected && selected.material){
   selected.material.roughness=Number(e.target.value);
  }

 });

 document.getElementById('opacity').addEventListener('input',e=>{

  if(selected && selected.material){

   selected.material.opacity=Number(e.target.value);

   selected.material.transparent=
    selected.material.opacity<1;

  }

 });

 document.getElementById('modeSelect').addEventListener('change',e=>{

  if(e.target.value==='Edit Mode'){
   enterEdit();
  }

  if(e.target.value==='Object Mode'){
   exitEdit();
  }

 });

}

function enterEdit(){

 if(!selected || !selected.isMesh)return;

 document.getElementById('modeSelect').value='Edit Mode';

 document.getElementById('status').textContent=
  'Edit Mode: '+selected.name;

}

function exitEdit(){

 document.getElementById('modeSelect').value='Object Mode';

 document.getElementById('status').textContent=
  'Object Mode';

}

function extrude(){

 if(!selected || !selected.isMesh)return;

 const old=selected.geometry;

 const geo=old.clone();

 const pos=geo.attributes.position;

 const arr=[];

 for(let i=0;i<pos.count;i++){

  arr.push(
   pos.getX(i),
   pos.getY(i),
   pos.getZ(i)
  );

 }

 const newGeo=new THREE.BufferGeometry();

 const verts=[];

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

  verts.push(
   arr[i],
   arr[i+1],
   arr[i+2]
  );

 }

 const offset=1;

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

  verts.push(
   arr[i],
   arr[i+1]+offset,
   arr[i+2]
  );

 }

 newGeo.setAttribute(
  'position',
  new THREE.Float32BufferAttribute(verts,3)
 );

 newGeo.computeVertexNormals();

 selected.geometry.dispose();
 selected.geometry=newGeo;

 document.getElementById('status').textContent=
  'Extrude executed';

}

function mergeVertices(){

 if(!selected || !selected.isMesh)return;

 const geo=selected.geometry;

 const pos=geo.attributes.position;

 const seen=[];
 const out=[];

 for(let i=0;i<pos.count;i++){

  const x=pos.getX(i);
  const y=pos.getY(i);
  const z=pos.getZ(i);

  let found=-1;

  for(let j=0;j<seen.length;j++){

   const p=seen[j];

   if(
    Math.abs(p.x-x)<.001 &&
    Math.abs(p.y-y)<.001 &&
    Math.abs(p.z-z)<.001
   ){
    found=j;
    break;
   }

  }

  if(found===-1){

   seen.push({x,y,z});
   out.push(x,y,z);

  }

 }

 const ng=new THREE.BufferGeometry();

 ng.setAttribute(
  'position',
  new THREE.Float32BufferAttribute(out,3)
 );

 ng.computeVertexNormals();

 selected.geometry.dispose();
 selected.geometry=ng;

}

function duplicate(){

 if(!selected)return;

 const copy=selected.clone();

 copy.name=selected.name+'.copy';

 copy.position.x+=1;

 addObject(copy);

}

function deleteSelected(){

 if(!selected)return;

 scene.remove(selected);

 const i=objects.indexOf(selected);

 if(i!==-1)objects.splice(i,1);

 gizmo.detach();

 selected=null;

 updateOutliner();

}

function insertKeyframe(){

 if(!selected)return;

 const frame=
  Number(document.getElementById('frameInput').value);

 if(!keyframes[selected.uuid])
  keyframes[selected.uuid]=[];

 keyframes[selected.uuid].push({

  frame:frame,

  x:selected.position.x,
  y:selected.position.y,
  z:selected.position.z,

  rx:selected.rotation.x,
  ry:selected.rotation.y,
  rz:selected.rotation.z,

  sx:selected.scale.x,
  sy:selected.scale.y,
  sz:selected.scale.z

 });

 drawTimeline();

 document.getElementById('status').textContent=
  'Keyframe inserted at '+frame;

}

function clearKeyframes(){

 if(!selected)return;

 delete keyframes[selected.uuid];

 drawTimeline();

}

function play(){

 if(playing)return;

 playing=true;

 timer=setInterval(()=>{

  let f=
   Number(document.getElementById('frameInput').value)+1;

  if(f>250)f=1;

  document.getElementById('frameInput').value=f;

  evaluateFrame(f);

 },40);

}

function stop(){

 playing=false;

 clearInterval(timer);

}

function prevFrame(){

 let f=
  Number(document.getElementById('frameInput').value)-1;

 if(f<1)f=1;

 document.getElementById('frameInput').value=f;

 evaluateFrame(f);

}

function nextFrame(){

 let f=
  Number(document.getElementById('frameInput').value)+1;

 if(f>250)f=1;

 document.getElementById('frameInput').value=f;

 evaluateFrame(f);

}

function evaluateFrame(frame){

 objects.forEach(o=>{

  const keys=keyframes[o.uuid];

  if(!keys || keys.length===0)return;

  let before=null;
  let after=null;

  keys.forEach(k=>{

   if(k.frame<=frame &&
      (!before || k.frame>before.frame))
    before=k;

   if(k.frame>=frame &&
      (!after || k.frame<after.frame))
    after=k;

  });

  if(!before && after)before=after;
  if(!after && before)after=before;

  if(!before || !after)return;

  if(before.frame===after.frame){

   applyKey(o,before);
   return;

  }

  const t=
   (frame-before.frame)/
   (after.frame-before.frame);

  o.position.x=
   THREE.MathUtils.lerp(before.x,after.x,t);

  o.position.y=
   THREE.MathUtils.lerp(before.y,after.y,t);

  o.position.z=
   THREE.MathUtils.lerp(before.z,after.z,t);

  o.rotation.x=
   THREE.MathUtils.lerp(before.rx,after.rx,t);

  o.rotation.y=
   THREE.MathUtils.lerp(before.ry,after.ry,t);

  o.rotation.z=
   THREE.MathUtils.lerp(before.rz,after.rz,t);

 });

}

function applyKey(o,k){

 o.position.set(k.x,k.y,k.z);

 o.rotation.set(k.rx,k.ry,k.rz);

 o.scale.set(k.sx,k.sy,k.sz);

}

function drawTimeline(){

 const c=document.getElementById('timelineCanvas');

 const box=c.getBoundingClientRect();

 c.width=Math.max(400,box.width);
 c.height=Math.max(70,box.height);

 const ctx=c.getContext('2d');

 ctx.clearRect(0,0,c.width,c.height);

 ctx.fillStyle='#191919';
 ctx.fillRect(0,0,c.width,c.height);

 const step=50;

 for(let x=0;x<c.width;x+=step){

  ctx.strokeStyle='#303030';

  ctx.beginPath();
  ctx.moveTo(x,0);
  ctx.lineTo(x,c.height);
  ctx.stroke();

  ctx.fillStyle='#888';

  ctx.fillText(
   Math.round(x/step*10),
   x+2,
   14
  );

 }

 objects.forEach((o,row)=>{

  const keys=keyframes[o.uuid];

  if(!keys)return;

  keys.forEach(k=>{

   const x=k.frame/10*step;

   ctx.fillStyle='#e0a020';

   ctx.beginPath();

   ctx.moveTo(x,25+row*18);
   ctx.lineTo(x+6,31+row*18);
   ctx.lineTo(x,37+row*18);
   ctx.lineTo(x-6,31+row*18);

   ctx.closePath();

   ctx.fill();

  });

 });

}

function saveProject(){

 const data={
  objects:objects.map(o=>({

   name:o.name,

   type:
    o.geometry.type,

   position:o.position.to

⚠️Content was pasted as plain text and auto-formatted as a code block. Use the Code Block button in the editor for proper formatting.

← Back to timeline