Add Cursor runs for Claude and Gemini models

This commit is contained in:
Jesse Zhang committed 2026-06-13 19:24:46 +01:00
1 parent 9bc21c2ace
commit 3936cd26af
17 files changed
+5169 -4

No files matched your search

@@ -0,0 +1,877 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Minecraft Clone</title>
<style>
*{margin:0;padding:0;box-sizing:border-box}
html,body{width:100%;height:100%;overflow:hidden;background:#000;font-family:'Segoe UI',system-ui,sans-serif}
canvas{display:block;width:100%;height:100%}
#overlay{position:fixed;inset:0;display:flex;flex-direction:column;align-items:center;justify-content:center;background:rgba(0,0,0,0.75);color:#fff;z-index:200;cursor:pointer;user-select:none}
#overlay h1{font-size:48px;font-weight:900;letter-spacing:4px;margin-bottom:24px;text-shadow:0 0 40px rgba(100,200,255,0.4)}
#overlay .controls{font-size:15px;line-height:2;color:#aaa;text-align:center}
#overlay .controls span{color:#fff;font-weight:600;background:rgba(255,255,255,0.1);padding:2px 8px;border-radius:4px;margin:0 2px}
#overlay .start{margin-top:32px;font-size:20px;color:#7cf;animation:pulse 2s ease-in-out infinite}
@keyframes pulse{0%,100%{opacity:1}50%{opacity:0.5}}
#overlay.hidden{display:none}
#crosshair{position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);pointer-events:none;z-index:100;width:1px;height:1px}
#crosshair::before,#crosshair::after{content:'';position:absolute;background:rgba(255,255,255,0.8)}
#crosshair::before{width:24px;height:2px;top:-1px;left:-12px}
#crosshair::after{width:2px;height:24px;top:-12px;left:-1px}
#hotbar{position:fixed;bottom:16px;left:50%;transform:translateX(-50%);display:flex;gap:2px;background:rgba(0,0,0,0.65);padding:4px;border-radius:6px;z-index:100;border:1px solid rgba(255,255,255,0.1)}
.hslot{width:52px;height:52px;border:2px solid rgba(255,255,255,0.15);border-radius:4px;display:flex;align-items:center;justify-content:center;position:relative;cursor:pointer;transition:border-color 0.15s}
.hslot.sel{border-color:rgba(120,210,255,0.9);box-shadow:0 0 12px rgba(120,210,255,0.3)}
.hslot canvas{width:36px;height:36px;image-rendering:pixelated}
.hslot .num{position:absolute;top:2px;left:5px;font-size:10px;color:rgba(255,255,255,0.5);font-weight:600}
#debug{position:fixed;top:12px;left:12px;color:rgba(255,255,255,0.6);font-size:12px;font-family:'Cascadia Code','SF Mono',monospace;z-index:100;pointer-events:none;line-height:1.6;white-space:pre}
</style>
</head>
<body>
<div id="overlay">
<h1>MINECRAFT</h1>
<div class="controls">
<span>W A S D</span> Move / <span>Space</span> Jump / <span>Shift</span> Descend<br>
<span>Mouse</span> Look / <span>Left Click</span> Break / <span>Right Click</span> Place<br>
<span>1-9</span> Select Block / <span>Scroll</span> Cycle Blocks
</div>
<div class="start">Click to Play</div>
</div>
<div id="crosshair"></div>
<div id="hotbar"></div>
<div id="debug"></div>
<script src="https://unpkg.com/three@0.160.0/build/three.min.js"></script>
<script>
(function(){
"use strict";
var CHUNK=16, CHUNK_H=128, RDIST=7, SEA=28;
var GRAV=28, JVEL=9, PH=1.7, PW=0.3, REACH=6;
var B={AIR:0,GRASS:1,DIRT:2,STONE:3,SAND:4,WATER:5,WOOD:6,LEAVES:7,SNOW:8,OBSIDIAN:9,GOLD:10,CRYSTAL:11,BEDROCK:12,BONE:13,BRICK:14,GLASS:15,LAVA:16};
var BNAME=['','Grass','Dirt','Stone','Sand','Water','Wood','Leaves','Snow','Obsidian','Gold','Crystal','Bedrock','Bone','Brick','Glass','Lava'];
var TRANS=new Set([B.AIR,B.WATER,B.GLASS,B.LEAVES]);
function isSolid(t){return t!==B.AIR&&t!==B.WATER}
var HOTBAR=[B.GRASS,B.DIRT,B.STONE,B.SAND,B.WOOD,B.BRICK,B.OBSIDIAN,B.GOLD,B.CRYSTAL];
function PerlinNoise(seed){
this.p=new Uint8Array(512);
var perm=new Uint8Array(256);
for(var i=0;i<256;i++)perm[i]=i;
var s=(seed*2147483647)|0;if(s<=0)s=1;
for(var i=255;i>0;i--){s=(s*16807)%2147483647;var j=s%(i+1);var t=perm[i];perm[i]=perm[j];perm[j]=t;}
for(var i=0;i<512;i++)this.p[i]=perm[i&255];
}
PerlinNoise.prototype.fade=function(t){return t*t*t*(t*(t*6-15)+10)};
PerlinNoise.prototype.lerp=function(a,b,t){return a+t*(b-a)};
PerlinNoise.prototype.grad2=function(h,x,y){var v=h&3;return((v&1)?-x:x)+((v&2)?-y:y)};
PerlinNoise.prototype.grad3=function(h,x,y,z){var b=h&15;var u=b<8?x:y;var v=b<4?y:(b===12||b===14?x:z);return((b&1)?-u:u)+((b&2)?-v:v)};
PerlinNoise.prototype.noise2=function(x,y){
var X=Math.floor(x)&255,Y=Math.floor(y)&255;
x-=Math.floor(x);y-=Math.floor(y);
var u=this.fade(x),v=this.fade(y),p=this.p;
var A=p[X]+Y,C=p[X+1]+Y;
return this.lerp(this.lerp(this.grad2(p[A],x,y),this.grad2(p[C],x-1,y),u),this.lerp(this.grad2(p[A+1],x,y-1),this.grad2(p[C+1],x-1,y-1),u),v);
};
PerlinNoise.prototype.noise3=function(x,y,z){
var X=Math.floor(x)&255,Y=Math.floor(y)&255,Z=Math.floor(z)&255;
x-=Math.floor(x);y-=Math.floor(y);z-=Math.floor(z);
var u=this.fade(x),v=this.fade(y),w=this.fade(z),p=this.p;
var A=p[X]+Y,AA=p[A]+Z,AB=p[A+1]+Z,C=p[X+1]+Y,BA=p[C]+Z,BB=p[C+1]+Z;
return this.lerp(this.lerp(this.lerp(this.grad3(p[AA],x,y,z),this.grad3(p[BA],x-1,y,z),u),this.lerp(this.grad3(p[AB],x,y-1,z),this.grad3(p[BB],x-1,y-1,z),u),v),this.lerp(this.lerp(this.grad3(p[AA+1],x,y,z-1),this.grad3(p[BA+1],x-1,y,z-1),u),this.lerp(this.grad3(p[AB+1],x,y-1,z-1),this.grad3(p[BB+1],x-1,y-1,z-1),u),v),w);
};
PerlinNoise.prototype.fbm2=function(x,y,oct){
var val=0,amp=1,freq=1,max=0;
for(var i=0;i<oct;i++){val+=this.noise2(x*freq,y*freq)*amp;max+=amp;amp*=0.5;freq*=2;}
return val/max;
};
PerlinNoise.prototype.fbm3=function(x,y,z,oct){
var val=0,amp=1,freq=1,max=0;
for(var i=0;i<oct;i++){val+=this.noise3(x*freq,y*freq,z*freq)*amp;max+=amp;amp*=0.5;freq*=2;}
return val/max;
};
var noise=new PerlinNoise(7777);
function srand(x,y){var n=Math.sin(x*127.1+y*311.7)*43758.5453;return n-Math.floor(n);}
function terrainH(wx,wz){
var base=noise.fbm2(wx*0.008,wz*0.008,6)*35+32;
var mt=Math.max(0,noise.fbm2(wx*0.003+100,wz*0.003+100,4))*30;
var dt=noise.fbm2(wx*0.03,wz*0.03,3)*5;
return Math.floor(Math.max(1,base+mt+dt));
}
function getBiome(wx,wz){
var t=noise.noise2(wx*0.004,wz*0.004);
var h=noise.noise2(wx*0.004+500,wz*0.004+500);
if(t>0.25)return'desert';
if(t<-0.3)return'tundra';
if(h>0.15)return'forest';
return'plains';
}
function isCave(wx,wy,wz){
if(wy<2||wy>55)return false;
return noise.fbm3(wx*0.04,wy*0.06,wz*0.04,3)>0.55;
}
function shouldTree(wx,wz){
var biome=getBiome(wx,wz);
var th=biome==='forest'?0.55:biome==='plains'?0.78:999;
if(th>1)return false;
var gx=Math.floor(wx/4)*4,gz=Math.floor(wz/4)*4;
var ox=Math.floor(srand(gx,gz)*3),oz=Math.floor(srand(gz,gx)*3);
if(wx!==gx+ox||wz!==gz+oz)return false;
return srand(wx*13+7,wz*17+3)>(1-th)&&terrainH(wx,wz)>SEA;
}
function treeH(wx,wz){return 4+Math.floor(srand(wx*31+5,wz*37+11)*4);}
var atlasCanvas=null;
function buildAtlas(){
var size=16;
atlasCanvas=document.createElement('canvas');
atlasCanvas.width=atlasCanvas.height=256;
var actx=atlasCanvas.getContext('2d');
function drawTex(idx,fn){
var c=document.createElement('canvas');c.width=c.height=size;
fn(c.getContext('2d'),size);
actx.drawImage(c,(idx%16)*size,Math.floor(idx/16)*size);
}
function fill(ctx,s,cols){
for(var y=0;y<s;y++)for(var x=0;x<s;x++){
ctx.fillStyle=cols[Math.floor(srand(x+0.5,y+0.5)*cols.length)];
ctx.fillRect(x,y,1,1);
}
}
drawTex(0,function(ctx,s){fill(ctx,s,['#4a8a2a','#52952e','#5da33a','#489025','#67b040','#3e7820']);});
drawTex(1,function(ctx,s){
for(var y=0;y<s;y++)for(var x=0;x<s;x++){
if(y<3){var c=['#4a8a2a','#52952e','#5da33a','#3e7820'];ctx.fillStyle=c[Math.floor(srand(x+.1,y+.1)*c.length)];}
else if(y<5){ctx.fillStyle=srand(x+.2,y+.2)>.5?'#6b5435':'#4a8a2a';}
else{var c=['#866043','#7a5539','#92694d','#6b4c30'];ctx.fillStyle=c[Math.floor(srand(x+.3,y+.3)*c.length)];}
ctx.fillRect(x,y,1,1);
}
});
drawTex(2,function(ctx,s){fill(ctx,s,['#866043','#7a5539','#92694d','#6b4c30','#7e5b3f']);});
drawTex(3,function(ctx,s){
for(var y=0;y<s;y++)for(var x=0;x<s;x++){
var c=['#828282','#777','#8e8e8e','#6e6e6e','#999'];
var ci=Math.floor(srand(x+.5,y+.5)*c.length);
if(srand(x*3.1+.7,y*2.3+.9)>.85)ci=3;
ctx.fillStyle=c[ci];ctx.fillRect(x,y,1,1);
}
});
drawTex(4,function(ctx,s){fill(ctx,s,['#dbc896','#d0bd8a','#e5d3a3','#c9b07e','#d6c490']);});
drawTex(5,function(ctx,s){
for(var y=0;y<s;y++)for(var x=0;x<s;x++){
var c=['#6b4423','#7b5433','#5b3413','#7e5a38'];
var ci=Math.floor(srand(x+.4,y+.4)*c.length);
if((x+Math.floor(srand(x*.1,y*.7)*2))%4<2)ci=0;
ctx.fillStyle=c[ci];ctx.fillRect(x,y,1,1);
}
});
drawTex(6,function(ctx,s){
for(var y=0;y<s;y++)for(var x=0;x<s;x++){
var dx=x-7.5,dy=y-7.5,r=Math.sqrt(dx*dx+dy*dy);
if(r<2)ctx.fillStyle='#5a3a15';
else if(r<5)ctx.fillStyle=srand(x+.6,y+.6)>.5?'#b8945f':'#a0804a';
else ctx.fillStyle=srand(x+.7,y+.7)>.5?'#c8a46f':'#b08850';
ctx.fillRect(x,y,1,1);
}
});
drawTex(7,function(ctx,s){fill(ctx,s,['#3a7a1a','#4a8c28','#2e6e14','#55a035','#3d8020','#488a25']);});
drawTex(8,function(ctx,s){fill(ctx,s,['#efeff0','#e4e4e8','#f5f5f8','#dcdce0','#eaeaee']);});
drawTex(9,function(ctx,s){
for(var y=0;y<s;y++)for(var x=0;x<s;x++){
if(y<3)ctx.fillStyle=srand(x+.8,y+.8)>.5?'#efeff0':'#ddd';
else if(y<5)ctx.fillStyle=srand(x+.9,y+.9)>.5?'#a09888':'#e0e0e0';
else{var c=['#866043','#7a5539','#92694d','#6b4c30'];ctx.fillStyle=c[Math.floor(srand(x+1,y+1)*c.length)];}
ctx.fillRect(x,y,1,1);
}
});
drawTex(10,function(ctx,s){
for(var y=0;y<s;y++)for(var x=0;x<s;x++){
var c=['#120820','#1a0c30','#0d0518','#150928'];
ctx.fillStyle=srand(x*2.7+.3,y*3.1+.7)>.9?'#2a1848':c[Math.floor(srand(x+1.1,y+1.1)*c.length)];
ctx.fillRect(x,y,1,1);
}
});
drawTex(11,function(ctx,s){
for(var y=0;y<s;y++)for(var x=0;x<s;x++){
var c=['#f0c800','#e8be10','#f8d520','#ddb408','#fce040'];
var ci=Math.floor(srand(x+1.2,y+1.2)*c.length);
if(x===0||x===15||y===0||y===15)ci=3;
if((x===1||x===14||y===1||y===14)&&srand(x*1.5,y*1.5)>.7)ci=3;
ctx.fillStyle=c[ci];ctx.fillRect(x,y,1,1);
}
});
drawTex(12,function(ctx,s){
for(var y=0;y<s;y++)for(var x=0;x<s;x++){
var n=srand(x+1.3,y+1.3);
ctx.fillStyle=n>.7?'#b0f0ff':n>.4?'#80d8f0':'#60c0e8';
ctx.fillRect(x,y,1,1);
}
});
drawTex(13,function(ctx,s){fill(ctx,s,['#333','#222','#444','#1a1a1a','#3a3a3a','#2a2a2a']);});
drawTex(14,function(ctx,s){fill(ctx,s,['#e8dcc8','#d8ccb0','#f0e4d4','#c8b898','#e0d4c0']);});
drawTex(15,function(ctx,s){
for(var y=0;y<s;y++)for(var x=0;x<s;x++){
if(y%4===0||(y<8?x%8===0:(x+4)%8===0))ctx.fillStyle='#bcac9c';
else{var c=['#8e4438','#994c40','#7a3830','#a05548'];ctx.fillStyle=c[Math.floor(srand(x+1.5,y+1.5)*c.length)];}
ctx.fillRect(x,y,1,1);
}
});
drawTex(16,function(ctx,s){fill(ctx,s,['#c8e8ff','#b0d8f8','#d0eeff','#a8d0f0']);});
drawTex(17,function(ctx,s){fill(ctx,s,['#ff4400','#ff6600','#ee3300','#ff5500','#dd2200']);});
var tex=new THREE.CanvasTexture(atlasCanvas);
tex.magFilter=THREE.NearestFilter;
tex.minFilter=THREE.NearestFilter;
tex.colorSpace=THREE.SRGBColorSpace;
return tex;
}
var FT={};
FT[B.GRASS]={top:0,side:1,bottom:2};FT[B.DIRT]={top:2,side:2,bottom:2};
FT[B.STONE]={top:3,side:3,bottom:3};FT[B.SAND]={top:4,side:4,bottom:4};
FT[B.WATER]={top:16,side:16,bottom:16};FT[B.WOOD]={top:6,side:5,bottom:6};
FT[B.LEAVES]={top:7,side:7,bottom:7};FT[B.SNOW]={top:8,side:9,bottom:2};
FT[B.OBSIDIAN]={top:10,side:10,bottom:10};FT[B.GOLD]={top:11,side:11,bottom:11};
FT[B.CRYSTAL]={top:12,side:12,bottom:12};FT[B.BEDROCK]={top:13,side:13,bottom:13};
FT[B.BONE]={top:14,side:14,bottom:14};FT[B.BRICK]={top:15,side:15,bottom:15};
FT[B.GLASS]={top:16,side:16,bottom:16};FT[B.LAVA]={top:17,side:17,bottom:17};
function texUV(idx){
var u0=(idx%16)/16,v0=1-(Math.floor(idx/16)+1)/16;
return{u0:u0,v0:v0,u1:u0+1/16,v1:v0+1/16};
}
var FACES=[
{dir:[1,0,0],verts:[[1,0,0],[1,1,0],[1,1,1],[1,0,1]],norm:[1,0,0],face:'side'},
{dir:[-1,0,0],verts:[[0,0,1],[0,1,1],[0,1,0],[0,0,0]],norm:[-1,0,0],face:'side'},
{dir:[0,1,0],verts:[[0,1,1],[1,1,1],[1,1,0],[0,1,0]],norm:[0,1,0],face:'top'},
{dir:[0,-1,0],verts:[[0,0,0],[1,0,0],[1,0,1],[0,0,1]],norm:[0,-1,0],face:'bottom'},
{dir:[0,0,1],verts:[[1,0,1],[1,1,1],[0,1,1],[0,0,1]],norm:[0,0,1],face:'side'},
{dir:[0,0,-1],verts:[[0,0,0],[0,1,0],[1,1,0],[1,0,0]],norm:[0,0,-1],face:'side'}
];
function Chunk(cx,cz,world){
this.cx=cx;this.cz=cz;this.world=world;
this.blocks=new Uint8Array(CHUNK*CHUNK_H*CHUNK);
this.mesh=null;this.waterMesh=null;this.dirty=true;this.generated=false;
}
Chunk.prototype.idx=function(x,y,z){return(y*CHUNK+z)*CHUNK+x;};
Chunk.prototype.getBlock=function(x,y,z){
if(x<0||x>=CHUNK||z<0||z>=CHUNK||y<0||y>=CHUNK_H)return B.AIR;
return this.blocks[this.idx(x,y,z)];
};
Chunk.prototype.setBlock=function(x,y,z,t){
if(x<0||x>=CHUNK||z<0||z>=CHUNK||y<0||y>=CHUNK_H)return;
this.blocks[this.idx(x,y,z)]=t;this.dirty=true;
};
Chunk.prototype.generate=function(){
if(this.generated)return;this.generated=true;
var ox=this.cx*CHUNK,oz=this.cz*CHUNK;
for(var lx=0;lx<CHUNK;lx++)for(var lz=0;lz<CHUNK;lz++){
var wx=ox+lx,wz=oz+lz,h=terrainH(wx,wz),biome=getBiome(wx,wz);
this.blocks[this.idx(lx,0,lz)]=B.BEDROCK;
for(var y=1;y<CHUNK_H;y++){
if(y<=h){
if(isCave(wx,y,wz)&&y>1&&y<h-1)this.blocks[this.idx(lx,y,lz)]=B.AIR;
else if(y===h){
if(biome==='desert')this.blocks[this.idx(lx,y,lz)]=B.SAND;
else if(biome==='tundra')this.blocks[this.idx(lx,y,lz)]=B.SNOW;
else this.blocks[this.idx(lx,y,lz)]=B.GRASS;
}else if(y>h-4){
this.blocks[this.idx(lx,y,lz)]=biome==='desert'?B.SAND:B.DIRT;
}else this.blocks[this.idx(lx,y,lz)]=B.STONE;
}else if(y<=SEA)this.blocks[this.idx(lx,y,lz)]=B.WATER;
}
}
for(var lx=0;lx<CHUNK;lx++)for(var lz=0;lz<CHUNK;lz++){
var wx=ox+lx,wz=oz+lz;
if(!shouldTree(wx,wz))continue;
var h=terrainH(wx,wz);if(h<=SEA)continue;
var th=treeH(wx,wz);
for(var y=h+1;y<=h+th&&y<CHUNK_H;y++)this.blocks[this.idx(lx,y,lz)]=B.WOOD;
var cb=h+th-1,ct=h+th+2;
for(var dy=cb;dy<=ct;dy++){
var r=dy<=h+th?2:1;
for(var dx=-r;dx<=r;dx++)for(var dz=-r;dz<=r;dz++){
if(dx===0&&dz===0&&dy<=h+th)continue;
if(Math.abs(dx)===r&&Math.abs(dz)===r&&srand(wx+dx,wz+dz)>.6)continue;
var nx=lx+dx,nz=lz+dz;
if(nx>=0&&nx<CHUNK&&nz>=0&&nz<CHUNK&&dy<CHUNK_H&&this.blocks[this.idx(nx,dy,nz)]===B.AIR)
this.blocks[this.idx(nx,dy,nz)]=B.LEAVES;
}
}
}
this.applyStructures(ox,oz);
};
Chunk.prototype.applyStructures=function(ox,oz){
for(var si=0;si<STRUCTURES.length;si++){
var s=STRUCTURES[si];
if(ox+CHUNK<=s.minX||ox>s.maxX||oz+CHUNK<=s.minZ||oz>s.maxZ)continue;
for(var lx=0;lx<CHUNK;lx++){
var wx=ox+lx;if(wx<s.minX||wx>s.maxX)continue;
for(var lz=0;lz<CHUNK;lz++){
var wz=oz+lz;if(wz<s.minZ||wz>s.maxZ)continue;
for(var wy=s.minY;wy<=s.maxY;wy++){
if(wy<0||wy>=CHUNK_H)continue;
var bt=s.gen(wx,wy,wz);
if(bt>0)this.blocks[this.idx(lx,wy,lz)]=bt;
}
}
}
}
};
Chunk.prototype.getNeighborBlock=function(lx,y,lz){
if(lx>=0&&lx<CHUNK&&lz>=0&&lz<CHUNK){
if(y<0||y>=CHUNK_H)return B.AIR;
return this.blocks[this.idx(lx,y,lz)];
}
return this.world.getBlock(this.cx*CHUNK+lx,y,this.cz*CHUNK+lz);
};
Chunk.prototype.computeAO=function(x,y,z,face){
var ao=[0,0,0,0],d=face.dir,t1,t2;
if(d[0]!==0){t1=[0,1,0];t2=[0,0,1];}
else if(d[1]!==0){t1=[1,0,0];t2=[0,0,1];}
else{t1=[1,0,0];t2=[0,1,0];}
var self=this;
function s(dx,dy,dz){return isSolid(self.getNeighborBlock(x+d[0]+dx,y+d[1]+dy,z+d[2]+dz))?1:0;}
var e0=s(-t1[0],-t1[1],-t1[2]),e1=s(t1[0],t1[1],t1[2]);
var e2=s(-t2[0],-t2[1],-t2[2]),e3=s(t2[0],t2[1],t2[2]);
var s00=s(-t1[0]-t2[0],-t1[1]-t2[1],-t1[2]-t2[2]);
var s10=s(t1[0]-t2[0],t1[1]-t2[1],t1[2]-t2[2]);
var s01=s(-t1[0]+t2[0],-t1[1]+t2[1],-t1[2]+t2[2]);
var s11=s(t1[0]+t2[0],t1[1]+t2[1],t1[2]+t2[2]);
function vao(a,b,c){return(a&&b)?0:(3-(a+b+c))/3;}
ao[0]=vao(e0,e2,s00);ao[1]=vao(e1,e2,s10);ao[2]=vao(e1,e3,s11);ao[3]=vao(e0,e3,s01);
return ao;
};
Chunk.prototype.buildMesh=function(mat,wmat){
if(!this.dirty)return;this.dirty=false;
var pos=[],norms=[],uvs=[],cols=[],idx=[];
var wp=[],wn=[],wu=[],wc=[],wi=[];
for(var y=0;y<CHUNK_H;y++)for(var z=0;z<CHUNK;z++)for(var x=0;x<CHUNK;x++){
var bt=this.blocks[this.idx(x,y,z)];if(bt===B.AIR)continue;
var isW=bt===B.WATER,ft=FT[bt];if(!ft)continue;
for(var fi=0;fi<6;fi++){
var f=FACES[fi];
var nx=x+f.dir[0],ny=y+f.dir[1],nz=z+f.dir[2];
var nb=this.getNeighborBlock(nx,ny,nz);
var show=isW?(nb===B.AIR||(nb!==B.WATER&&TRANS.has(nb))):TRANS.has(nb);
if(!show)continue;
var tp=isW?wp:pos,tn=isW?wn:norms,tu=isW?wu:uvs,tc=isW?wc:cols,ti=isW?wi:idx;
var vc=tp.length/3;
var uv=texUV(ft[f.face]);
var ao=isW?[1,1,1,1]:this.computeAO(x,y,z,f);
for(var vi=0;vi<4;vi++){
var v=f.verts[vi];tp.push(x+v[0],y+v[1],z+v[2]);
tn.push(f.norm[0],f.norm[1],f.norm[2]);
var a=0.4+0.6*ao[vi];tc.push(a,a,a);
}
tu.push(uv.u0,uv.v0,uv.u1,uv.v0,uv.u1,uv.v1,uv.u0,uv.v1);
if(ao[0]+ao[2]>ao[1]+ao[3])ti.push(vc,vc+1,vc+2,vc,vc+2,vc+3);
else ti.push(vc+1,vc+2,vc+3,vc+1,vc+3,vc);
}
}
if(this.mesh){this.world.scene.remove(this.mesh);this.mesh.geometry.dispose();}
if(this.waterMesh){this.world.scene.remove(this.waterMesh);this.waterMesh.geometry.dispose();}
this.mesh=null;this.waterMesh=null;
if(pos.length>0){
var g=new THREE.BufferGeometry();
g.setAttribute('position',new THREE.Float32BufferAttribute(pos,3));
g.setAttribute('normal',new THREE.Float32BufferAttribute(norms,3));
g.setAttribute('uv',new THREE.Float32BufferAttribute(uvs,2));
g.setAttribute('color',new THREE.Float32BufferAttribute(cols,3));
g.setIndex(idx);
this.mesh=new THREE.Mesh(g,mat);
this.mesh.position.set(this.cx*CHUNK,0,this.cz*CHUNK);
this.world.scene.add(this.mesh);
}
if(wp.length>0){
var g=new THREE.BufferGeometry();
g.setAttribute('position',new THREE.Float32BufferAttribute(wp,3));
g.setAttribute('normal',new THREE.Float32BufferAttribute(wn,3));
g.setAttribute('uv',new THREE.Float32BufferAttribute(wu,2));
g.setAttribute('color',new THREE.Float32BufferAttribute(wc,3));
g.setIndex(wi);
this.waterMesh=new THREE.Mesh(g,wmat);
this.waterMesh.position.set(this.cx*CHUNK,0,this.cz*CHUNK);
this.waterMesh.renderOrder=1;
this.world.scene.add(this.waterMesh);
}
};
Chunk.prototype.dispose=function(){
if(this.mesh){this.world.scene.remove(this.mesh);this.mesh.geometry.dispose();this.mesh=null;}
if(this.waterMesh){this.world.scene.remove(this.waterMesh);this.waterMesh.geometry.dispose();this.waterMesh=null;}
};
var STRUCTURES=[];
function buildStructures(){
var cY=80;
STRUCTURES.push({
minX:-25,maxX:25,minY:cY-5,maxY:cY+45,minZ:50,maxZ:100,
gen:function(wx,wy,wz){
var x=wx,y=wy,z=wz-75,ax=Math.abs(x),az=Math.abs(z);
if(y>=cY-5&&y<=cY-1&&ax<=22&&az<=22)return B.OBSIDIAN;
if(y>=cY&&y<=cY+1&&ax<=20&&az<=20)return B.STONE;
if(y>=cY+2&&y<=cY+14&&ax<=16&&az<=16&&(ax>=15||az>=15)){
if(y===cY+14&&(ax+az)%2===0)return B.STONE;
if(y<cY+14){
if(y>=cY+5&&y<=cY+7&&az>=15&&ax<3)return 0;
return B.STONE;
}
}
var towers=[[-14,-14],[14,-14],[-14,14],[14,14]];
for(var ti=0;ti<4;ti++){
var tx=towers[ti][0],tz=towers[ti][1];
var dx=x-tx,dz2=z-tz,r2=dx*dx+dz2*dz2;
if(r2<=20&&y>=cY+2&&y<=cY+22)return B.BRICK;
if(r2<=30&&y>cY+22&&y<=cY+24)return B.STONE;
if(r2<=5&&y>cY+24&&y<=cY+32)return B.CRYSTAL;
if(r2<=1&&y>cY+32&&y<=cY+35)return B.GOLD;
}
if(ax<=7&&az<=7&&y>=cY+2&&y<=cY+18){
if(ax>=6||az>=6)return B.BRICK;
if(y===cY+2)return B.GOLD;
if(y===cY+18&&(ax+az)%2===0)return B.GOLD;
}
if(y>=cY-8&&y<cY-5&&ax<=22&&az<=22){
if((ax+az)%5===0||srand(wx*.1,wz*.1)>.92)return B.CRYSTAL;
}
return 0;
}
});
var tX=70,tZ=-10,tB=30;
STRUCTURES.push({
minX:tX-30,maxX:tX+30,minY:tB-5,maxY:tB+75,minZ:tZ-30,maxZ:tZ+30,
gen:function(wx,wy,wz){
var dx=wx-tX,dz=wz-tZ,d2=dx*dx+dz*dz,y=wy;
if(y>=tB&&y<=tB+55){
var tr=Math.max(1,5-Math.floor((y-tB)/15));
if(d2<=tr*tr)return B.WOOD;
if(y<=tB+5&&d2<=tr*tr+4&&srand(wx,wz)>.3)return B.WOOD;
}
for(var i=0;i<6;i++){
var angle=(i/6)*Math.PI*2+0.5;
var bY=tB+25+i*5,bL=12-i;
for(var d=3;d<bL;d++){
var bx=tX+Math.round(Math.cos(angle)*d);
var bz=tZ+Math.round(Math.sin(angle)*d);
var by=bY+Math.round(d*0.25);
if(Math.abs(wx-bx)<=1&&Math.abs(wz-bz)<=1&&Math.abs(wy-by)<=0)
if(Math.abs(wx-bx)+Math.abs(wz-bz)<=1)return B.WOOD;
}
}
var cc=tB+50,cdy=(y-cc)*1.3,cr2=22*22;
if(d2+cdy*cdy<=cr2&&y>tB+35){
if(noise.noise3(wx*.15,wy*.15,wz*.15)<.3)return B.LEAVES;
}
if(y>=tB&&y<=tB+55&&d2<=36&&srand(wx*7.1+wy*3.3,wz*5.7)>.97)return B.CRYSTAL;
if(y>=tB-5&&y<tB){
for(var i=0;i<8;i++){
var a=(i/8)*Math.PI*2,rl=12+Math.floor(srand(i,0)*6);
for(var d=4;d<rl;d++){
var rx=tX+Math.round(Math.cos(a)*d),rz=tZ+Math.round(Math.sin(a)*d);
var ry=tB-Math.floor((d-4)*.4);
if(Math.abs(wx-rx)<=1&&Math.abs(wz-rz)<=1&&wy===ry)return B.WOOD;
}
}
}
return 0;
}
});
var dX=-55,dZ=55,dY=38;
STRUCTURES.push({
minX:dX-10,maxX:dX+45,minY:dY-3,maxY:dY+18,minZ:dZ-18,maxZ:dZ+18,
gen:function(wx,wy,wz){
var lx=wx-dX,ly=wy-dY,lz=wz-dZ;
if(lx>=0&&lx<=38){
var sy=Math.round(Math.sin(lx*.08)*2),sz=Math.round(Math.sin(lx*.12)*1.5);
if(ly===sy&&Math.abs(lz-sz)<=0)return B.BONE;
if(lx<35&&ly===sy+1&&lz===sz&&lx%3===0)return B.BONE;
}
if(lx>=-5&&lx<=3&&ly>=0&&ly<=5&&Math.abs(lz)<=3){
if(ly===3&&lx===-3&&(lz===1||lz===-1))return B.LAVA;
if(ly>=0&&ly<=4&&lx>=-5&&lx<=1&&(Math.abs(lz)===2||ly===0||ly===4||lx===-5||lx===1))return B.BONE;
if(lx<=-3&&ly>=1&&ly<=3&&Math.abs(lz)<=1)return B.BONE;
}
for(var i=0;i<7;i++){
var rx=6+i*4;
if(lx===rx||lx===rx+1){
var sy2=Math.round(Math.sin(rx*.08)*2),sz2=Math.round(Math.sin(rx*.12)*1.5);
for(var r=0;r<=5;r++){
var ry=sy2+Math.round(Math.sin(r*Math.PI/5)*5);
var rz=sz2+Math.round(Math.cos(r*Math.PI/5)*4);
if(ly===ry&&(lz===rz||lz===sz2*2-rz))return B.BONE;
}
}
}
if(lx>=10&&lx<=12){
for(var d=0;d<14;d++){
var wy2=dY+Math.round(Math.sin(10*.08)*2)+3+Math.round(d*.3);
var wz2=dZ+(lz>0?d+4:-d-4);
if(Math.abs(wy-wy2)<=0&&Math.abs(wz-wz2)<=0&&lx>=10&&lx<=11)return B.BONE;
}
}
if(lx>=32&&lx<=42){
var ti=lx-32;
var ty=Math.round(Math.sin(ti*.3)*2),tz=Math.round(Math.sin(ti*.2)*3);
if(ly===ty&&Math.abs(lz-tz)<=(ti<5?1:0))return B.BONE;
}
return 0;
}
});
var sX=-10,sZ=-65;
STRUCTURES.push({
minX:sX-12,maxX:sX+12,minY:25,maxY:90,minZ:sZ-12,maxZ:sZ+12,
gen:function(wx,wy,wz){
var spires=[[0,0,35],[-6,-4,25],[5,-6,28],[-4,7,22],[7,5,18]];
for(var i=0;i<spires.length;i++){
var sp=spires[i],sdx=wx-(sX+sp[0]),sdz=wz-(sZ+sp[1]),r2=sdx*sdx+sdz*sdz;
var baseY=terrainH(sX+sp[0],sZ+sp[1]),topY=baseY+sp[2];
if(wy>=baseY&&wy<=topY){
var t=(wy-baseY)/sp[2],maxR=(1-t)*3+.5;
if(r2<=maxR*maxR)return B.CRYSTAL;
}
}
var ddx=wx-sX,ddz=wz-sZ;
if(ddx*ddx+ddz*ddz<=64){
var bY=terrainH(wx,wz);
if((wy===bY+1||wy===bY+2)&&srand(wx*3.3,wz*7.7)>.85)return B.CRYSTAL;
}
return 0;
}
});
var rX=-75,rZ=-35,rY=32;
STRUCTURES.push({
minX:rX-18,maxX:rX+18,minY:rY,maxY:rY+14,minZ:rZ-18,maxZ:rZ+18,
gen:function(wx,wy,wz){
var lx=wx-rX,lz=wz-rZ,ly=wy-rY;
if(ly===0&&Math.abs(lx)<=15&&Math.abs(lz)<=15){
if(Math.abs(lx)>=14||Math.abs(lz)>=14)return B.STONE;
if(Math.abs(lx)<=2&&Math.abs(lz)<=2)return B.GOLD;
}
if(Math.abs(lx)<=15&&Math.abs(lz)<=15&&(Math.abs(lx)>=13||Math.abs(lz)>=13)){
var wh=Math.floor(srand(wx*.5,wz*.5)*6)+2;
if(ly>0&&ly<=wh&&srand(wx*1.1+ly*2.2,wz*3.3)>.15)return B.STONE;
}
var pills=[[-10,-10],[10,-10],[-10,10],[10,10],[-10,0],[10,0],[0,-10],[0,10]];
for(var i=0;i<pills.length;i++){
var px=pills[i][0],pz=pills[i][1];
if(Math.abs(lx-px)<=1&&Math.abs(lz-pz)<=1){
var ph=Math.floor(srand(px+100,pz+100)*5)+6;
if(ly>0&&ly<=ph)return B.STONE;
}
}
if(Math.abs(lx)<=2&&Math.abs(lz)<=2&&ly>=1&&ly<=3){
if(ly<=2)return B.OBSIDIAN;return B.GOLD;
}
if(ly>=1&&ly<=3&&Math.abs(lx)<=15&&Math.abs(lz)<=15&&srand(wx*2.2,wz*4.4+ly)>.96)return B.LEAVES;
return 0;
}
});
var pX=30,pZ=-45;
STRUCTURES.push({
minX:pX-6,maxX:pX+6,minY:25,maxY:60,minZ:pZ-2,maxZ:pZ+2,
gen:function(wx,wy,wz){
var lx=wx-pX,lz=wz-pZ,baseY=terrainH(pX,pZ),ly=wy-baseY;
if(Math.abs(lz)<=1){
if(Math.abs(lx)===5&&ly>=0&&ly<=18)return B.OBSIDIAN;
if(ly===0&&Math.abs(lx)<=5)return B.OBSIDIAN;
if(ly===18&&Math.abs(lx)<=5)return B.OBSIDIAN;
if(ly>=1&&ly<=17&&Math.abs(lx)<5&&lz===0)return B.CRYSTAL;
if(ly===19&&Math.abs(lx)<=3&&lz===0)return B.GOLD;
}
if(ly===-1&&Math.abs(lx)<=6&&Math.abs(lz)<=2)return B.STONE;
return 0;
}
});
}
buildStructures();
function World(scene){
this.scene=scene;this.chunks=new Map();
}
World.prototype.key=function(cx,cz){return cx+','+cz;};
World.prototype.getChunk=function(cx,cz){return this.chunks.get(this.key(cx,cz));};
World.prototype.getBlock=function(wx,wy,wz){
if(wy<0||wy>=CHUNK_H)return B.AIR;
var cx=Math.floor(wx/CHUNK),cz=Math.floor(wz/CHUNK);
var c=this.getChunk(cx,cz);if(!c||!c.generated)return B.AIR;
var lx=((wx%CHUNK)+CHUNK)%CHUNK,lz=((wz%CHUNK)+CHUNK)%CHUNK;
return c.getBlock(lx,wy,lz);
};
World.prototype.setBlock=function(wx,wy,wz,type){
if(wy<0||wy>=CHUNK_H)return;
var cx=Math.floor(wx/CHUNK),cz=Math.floor(wz/CHUNK);
var c=this.getChunk(cx,cz);if(!c)return;
var lx=((wx%CHUNK)+CHUNK)%CHUNK,lz=((wz%CHUNK)+CHUNK)%CHUNK;
c.setBlock(lx,wy,lz,type);
if(lx===0){var n=this.getChunk(cx-1,cz);if(n)n.dirty=true;}
if(lx===CHUNK-1){var n=this.getChunk(cx+1,cz);if(n)n.dirty=true;}
if(lz===0){var n=this.getChunk(cx,cz-1);if(n)n.dirty=true;}
if(lz===CHUNK-1){var n=this.getChunk(cx,cz+1);if(n)n.dirty=true;}
};
World.prototype.update=function(px,pz,mat,wmat){
var pcx=Math.floor(px/CHUNK),pcz=Math.floor(pz/CHUNK);
var self=this;
this.chunks.forEach(function(chunk,key){
var dx=chunk.cx-pcx,dz=chunk.cz-pcz;
if(Math.abs(dx)>RDIST+1||Math.abs(dz)>RDIST+1){chunk.dispose();self.chunks.delete(key);}
});
var toGen=[];
for(var dx=-RDIST;dx<=RDIST;dx++)for(var dz=-RDIST;dz<=RDIST;dz++){
if(dx*dx+dz*dz>RDIST*RDIST)continue;
var cx=pcx+dx,cz=pcz+dz,k=this.key(cx,cz);
if(!this.chunks.has(k))toGen.push({cx:cx,cz:cz,d:dx*dx+dz*dz});
}
toGen.sort(function(a,b){return a.d-b.d;});
var gc=0;
for(var i=0;i<toGen.length&&gc<3;i++){
var g=toGen[i],chunk=new Chunk(g.cx,g.cz,this);
chunk.generate();this.chunks.set(this.key(g.cx,g.cz),chunk);
var adj=[[-1,0],[1,0],[0,-1],[0,1]];
for(var j=0;j<4;j++){var a=this.getChunk(g.cx+adj[j][0],g.cz+adj[j][1]);if(a)a.dirty=true;}
gc++;
}
var mc=0;
this.chunks.forEach(function(chunk){
if(mc>=4)return;
if(chunk.dirty&&chunk.generated){chunk.buildMesh(mat,wmat);mc++;}
});
};
function Player(camera){
this.camera=camera;
this.pos=new THREE.Vector3(0,60,0);
this.vel=new THREE.Vector3();
this.yaw=0;this.pitch=0;this.onGround=false;
this.keys={};this.speed=6;this.sens=0.002;this.locked=false;
var self=this;
document.addEventListener('keydown',function(e){self.keys[e.code]=true;if(e.code==='Space')e.preventDefault();});
document.addEventListener('keyup',function(e){self.keys[e.code]=false;});
document.addEventListener('mousemove',function(e){
if(!self.locked)return;
self.yaw-=e.movementX*self.sens;
self.pitch-=e.movementY*self.sens;
self.pitch=Math.max(-Math.PI/2+.01,Math.min(Math.PI/2-.01,self.pitch));
});
}
Player.prototype.update=function(dt,world){
var fw=new THREE.Vector3(-Math.sin(this.yaw),0,-Math.cos(this.yaw));
var rt=new THREE.Vector3(Math.cos(this.yaw),0,-Math.sin(this.yaw));
var mv=new THREE.Vector3();
if(this.keys['KeyW'])mv.add(fw);if(this.keys['KeyS'])mv.sub(fw);
if(this.keys['KeyA'])mv.sub(rt);if(this.keys['KeyD'])mv.add(rt);
if(mv.lengthSq()>0)mv.normalize().multiplyScalar(this.speed);
this.vel.x=mv.x;this.vel.z=mv.z;
this.vel.y-=GRAV*dt;
if(this.keys['Space']&&this.onGround){this.vel.y=JVEL;this.onGround=false;}
var steps=4,sdt=dt/steps;
for(var s=0;s<steps;s++){
this.pos.x+=this.vel.x*sdt;this.collide('x',world);
this.pos.y+=this.vel.y*sdt;this.collide('y',world);
this.pos.z+=this.vel.z*sdt;this.collide('z',world);
}
if(this.pos.y<-10){this.pos.set(0,60,0);this.vel.set(0,0,0);}
this.camera.position.set(this.pos.x,this.pos.y+PH,this.pos.z);
var euler=new THREE.Euler(this.pitch,this.yaw,0,'YXZ');
this.camera.quaternion.setFromEuler(euler);
};
Player.prototype.collide=function(axis,world){
var hw=PW,hh=PH+.1;
var mnx=Math.floor(this.pos.x-hw),mxx=Math.floor(this.pos.x+hw);
var mny=Math.floor(this.pos.y),mxy=Math.floor(this.pos.y+hh);
var mnz=Math.floor(this.pos.z-hw),mxz=Math.floor(this.pos.z+hw);
for(var bx=mnx;bx<=mxx;bx++)for(var by=mny;by<=mxy;by++)for(var bz=mnz;bz<=mxz;bz++){
if(!isSolid(world.getBlock(bx,by,bz)))continue;
var ov=this.overlap(bx,by,bz);if(!ov)continue;
if(axis==='x'){this.pos.x+=ov.x;this.vel.x=0;}
else if(axis==='y'){this.pos.y+=ov.y;if(ov.y>0){this.onGround=true;this.vel.y=0;}else if(ov.y<0)this.vel.y=0;}
else{this.pos.z+=ov.z;this.vel.z=0;}
}
};
Player.prototype.overlap=function(bx,by,bz){
var hw=PW,hh=PH+.1;
var ox=Math.min(this.pos.x+hw-bx,bx+1-(this.pos.x-hw));
var oy=Math.min(this.pos.y+hh-by,by+1-this.pos.y);
var oz=Math.min(this.pos.z+hw-bz,bz+1-(this.pos.z-hw));
if(ox<=0||oy<=0||oz<=0)return null;
var mn=Math.min(ox,oy,oz),r={x:0,y:0,z:0};
if(mn===ox)r.x=(this.pos.x<bx+.5)?-ox:ox;
else if(mn===oy)r.y=(this.pos.y+hh/2<by+.5)?-oy:oy;
else r.z=(this.pos.z<bz+.5)?-oz:oz;
return r;
};
Player.prototype.getForward=function(){
return new THREE.Vector3(-Math.sin(this.yaw)*Math.cos(this.pitch),Math.sin(this.pitch),-Math.cos(this.yaw)*Math.cos(this.pitch)).normalize();
};
function castRay(origin,dir,world,maxD){
var x=Math.floor(origin.x),y=Math.floor(origin.y),z=Math.floor(origin.z);
var sx=dir.x>=0?1:-1,sy=dir.y>=0?1:-1,sz=dir.z>=0?1:-1;
var tmx=dir.x!==0?((dir.x>0?x+1:x)-origin.x)/dir.x:1e9;
var tmy=dir.y!==0?((dir.y>0?y+1:y)-origin.y)/dir.y:1e9;
var tmz=dir.z!==0?((dir.z>0?z+1:z)-origin.z)/dir.z:1e9;
var tdx=dir.x!==0?Math.abs(1/dir.x):1e9;
var tdy=dir.y!==0?Math.abs(1/dir.y):1e9;
var tdz=dir.z!==0?Math.abs(1/dir.z):1e9;
var px=x,py=y,pz=z,dist=0;
while(dist<maxD){
var bt=world.getBlock(x,y,z);
if(isSolid(bt))return{hit:true,x:x,y:y,z:z,px:px,py:py,pz:pz,block:bt};
px=x;py=y;pz=z;
if(tmx<tmy){
if(tmx<tmz){dist=tmx;x+=sx;tmx+=tdx;}
else{dist=tmz;z+=sz;tmz+=tdz;}
}else{
if(tmy<tmz){dist=tmy;y+=sy;tmy+=tdy;}
else{dist=tmz;z+=sz;tmz+=tdz;}
}
}
return{hit:false};
}
var scene=new THREE.Scene();
scene.background=new THREE.Color(0x87CEEB);
scene.fog=new THREE.Fog(0x87CEEB,CHUNK*RDIST*0.6,CHUNK*RDIST*0.95);
var camera=new THREE.PerspectiveCamera(75,window.innerWidth/window.innerHeight,0.1,CHUNK*RDIST*1.2);
var renderer=new THREE.WebGLRenderer({antialias:false});
renderer.setSize(window.innerWidth,window.innerHeight);
renderer.setPixelRatio(Math.min(window.devicePixelRatio,2));
renderer.outputColorSpace=THREE.SRGBColorSpace;
document.body.prepend(renderer.domElement);
scene.add(new THREE.HemisphereLight(0x87CEEB,0x6B4C30,0.6));
var sun=new THREE.DirectionalLight(0xfff5e0,1.0);
sun.position.set(50,100,30);scene.add(sun);
scene.add(new THREE.AmbientLight(0x404060,0.35));
var tex=buildAtlas();
var mat=new THREE.MeshLambertMaterial({map:tex,vertexColors:true,side:THREE.FrontSide});
var wmat=new THREE.MeshLambertMaterial({map:tex,vertexColors:true,transparent:true,opacity:0.6,side:THREE.DoubleSide,depthWrite:false});
var world=new World(scene);
var player=new Player(camera);
var hlBox=new THREE.LineSegments(
new THREE.EdgesGeometry(new THREE.BoxGeometry(1.005,1.005,1.005)),
new THREE.LineBasicMaterial({color:0x000000,linewidth:2,transparent:true,opacity:0.5})
);
hlBox.visible=false;scene.add(hlBox);
var selectedSlot=0;
function setupHotbar(){
var hb=document.getElementById('hotbar');hb.innerHTML='';
for(var i=0;i<HOTBAR.length;i++){
var slot=document.createElement('div');
slot.className='hslot'+(i===0?' sel':'');
var num=document.createElement('span');num.className='num';num.textContent=i+1;
slot.appendChild(num);
var c=document.createElement('canvas');c.width=c.height=16;
var ctx=c.getContext('2d');
var ft=FT[HOTBAR[i]],ti=ft.top;
var sx=(ti%16)*16,sy=Math.floor(ti/16)*16;
ctx.drawImage(atlasCanvas,sx,sy,16,16,0,0,16,16);
slot.appendChild(c);
(function(idx){slot.addEventListener('click',function(e){e.stopPropagation();selectedSlot=idx;updateHotbar();});})(i);
hb.appendChild(slot);
}
}
function updateHotbar(){
var slots=document.querySelectorAll('.hslot');
for(var i=0;i<slots.length;i++)slots[i].className=i===selectedSlot?'hslot sel':'hslot';
}
function spawnPlayer(){
var sx=0,sz=0,h=terrainH(sx,sz);
for(var si=0;si<STRUCTURES.length;si++){
var s=STRUCTURES[si];
if(sx>=s.minX&&sx<=s.maxX&&sz>=s.minZ&&sz<=s.maxZ)
for(var y=s.maxY;y>=s.minY;y--)if(s.gen(sx,y,sz)>0){h=Math.max(h,y+1);break;}
}
player.pos.set(sx,h+2,sz);
}
function rebuildNearby(wx,wy,wz){
var cx=Math.floor(wx/CHUNK),cz=Math.floor(wz/CHUNK);
for(var dx=-1;dx<=1;dx++)for(var dz=-1;dz<=1;dz++){
var c=world.getChunk(cx+dx,cz+dz);if(c)c.dirty=true;
}
}
var overlay=document.getElementById('overlay');
var canvas=renderer.domElement;
overlay.addEventListener('click',function(){canvas.requestPointerLock();});
document.addEventListener('pointerlockchange',function(){
player.locked=document.pointerLockElement===canvas;
overlay.className=player.locked?'hidden':'';
});
canvas.addEventListener('mousedown',function(e){
if(!player.locked)return;
var origin=camera.position.clone(),dir=player.getForward();
var hit=castRay(origin,dir,world,REACH);
if(e.button===0&&hit.hit){
world.setBlock(hit.x,hit.y,hit.z,B.AIR);
rebuildNearby(hit.x,hit.y,hit.z);
}else if(e.button===2&&hit.hit){
var ppx=Math.floor(player.pos.x),ppy=Math.floor(player.pos.y),ppz=Math.floor(player.pos.z);
var ppy2=Math.floor(player.pos.y+PH);
if(hit.px===ppx&&hit.pz===ppz&&(hit.py===ppy||hit.py===ppy2))return;
world.setBlock(hit.px,hit.py,hit.pz,HOTBAR[selectedSlot]);
rebuildNearby(hit.px,hit.py,hit.pz);
}
});
canvas.addEventListener('contextmenu',function(e){e.preventDefault();});
document.addEventListener('keydown',function(e){
var n=parseInt(e.key);if(n>=1&&n<=9){selectedSlot=n-1;updateHotbar();}
});
canvas.addEventListener('wheel',function(e){
if(!player.locked)return;
selectedSlot=(selectedSlot+(e.deltaY>0?1:-1)+HOTBAR.length)%HOTBAR.length;
updateHotbar();
});
window.addEventListener('resize',function(){
camera.aspect=window.innerWidth/window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth,window.innerHeight);
});
setupHotbar();
spawnPlayer();
var clock=new THREE.Clock();
function animate(){
requestAnimationFrame(animate);
var dt=Math.min(clock.getDelta(),0.05);
player.update(dt,world);
world.update(player.pos.x,player.pos.z,mat,wmat);
var origin=camera.position.clone(),dir=player.getForward();
var hit=castRay(origin,dir,world,REACH);
if(hit.hit){hlBox.position.set(hit.x+.5,hit.y+.5,hit.z+.5);hlBox.visible=true;}
else hlBox.visible=false;
var p=player.pos;
var dbg=document.getElementById('debug');
dbg.textContent='XYZ: '+p.x.toFixed(1)+' / '+p.y.toFixed(1)+' / '+p.z.toFixed(1)+
'\nChunk: '+Math.floor(p.x/CHUNK)+', '+Math.floor(p.z/CHUNK)+' Loaded: '+world.chunks.size+
'\nBlock: '+BNAME[HOTBAR[selectedSlot]];
renderer.render(scene,camera);
}
animate();
})();
</script>
</body>
</html>
Whitespace-only changes.
@@ -0,0 +1,11 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">
<title>Mythic Realms — A Voxel Pilgrimage</title>
</head>
<body>
<div id="probe">init</div>
</body>
</html>
@@ -0,0 +1,628 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 800 600" width="100%" height="100%">
<defs>
<!-- ========== ???? ========== -->
<linearGradient id="skyGrad" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#16264d"/>
<stop offset="22%" stop-color="#3f5d8e"/>
<stop offset="48%" stop-color="#c66e58"/>
<stop offset="72%" stop-color="#f4ad7a"/>
<stop offset="100%" stop-color="#fde2b8"/>
</linearGradient>
<radialGradient id="sunGlow" cx="50%" cy="50%" r="50%">
<stop offset="0%" stop-color="#fff7d8" stop-opacity="0.95"/>
<stop offset="30%" stop-color="#ffd56b" stop-opacity="0.55"/>
<stop offset="65%" stop-color="#ff8a3a" stop-opacity="0.18"/>
<stop offset="100%" stop-color="#ff5e2a" stop-opacity="0"/>
</radialGradient>
<radialGradient id="sunCore" cx="40%" cy="40%" r="60%">
<stop offset="0%" stop-color="#fffae6"/>
<stop offset="55%" stop-color="#ffe9a3"/>
<stop offset="100%" stop-color="#ffbe5e"/>
</radialGradient>
<linearGradient id="seaGrad" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#7da7c4"/>
<stop offset="35%" stop-color="#33648c"/>
<stop offset="100%" stop-color="#0d2c4d"/>
</linearGradient>
<linearGradient id="wetSandGrad" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#caa57e"/>
<stop offset="100%" stop-color="#8a6741"/>
</linearGradient>
<linearGradient id="sandGrad" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#f6d8a1"/>
<stop offset="55%" stop-color="#dab177"/>
<stop offset="100%" stop-color="#a47c4e"/>
</linearGradient>
<linearGradient id="bodyGrad" x1="0.2" y1="0" x2="0.7" y2="1">
<stop offset="0%" stop-color="#ffffff"/>
<stop offset="55%" stop-color="#f2ecdc"/>
<stop offset="100%" stop-color="#b3a482"/>
</linearGradient>
<linearGradient id="bellyGrad" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#ffffff"/>
<stop offset="100%" stop-color="#e8e0c8"/>
</linearGradient>
<linearGradient id="wingGrad" x1="0" y1="0" x2="0.4" y2="1">
<stop offset="0%" stop-color="#d9c8a0"/>
<stop offset="55%" stop-color="#8d7549"/>
<stop offset="100%" stop-color="#3f3018"/>
</linearGradient>
<linearGradient id="beakGrad" x1="0" y1="0" x2="0.3" y2="1">
<stop offset="0%" stop-color="#ffe287"/>
<stop offset="50%" stop-color="#f29d28"/>
<stop offset="100%" stop-color="#a05810"/>
</linearGradient>
<linearGradient id="pouchGrad" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#ffd9a0"/>
<stop offset="55%" stop-color="#ed984c"/>
<stop offset="100%" stop-color="#a8521e"/>
</linearGradient>
<linearGradient id="footGrad" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#ffb350"/>
<stop offset="100%" stop-color="#b85e10"/>
</linearGradient>
<linearGradient id="frameGrad" x1="0" y1="0" x2="0.3" y2="1">
<stop offset="0%" stop-color="#5dd9e8"/>
<stop offset="55%" stop-color="#1ba5ba"/>
<stop offset="100%" stop-color="#0e6473"/>
</linearGradient>
<linearGradient id="scarfGrad" x1="0" y1="0" x2="1" y2="0">
<stop offset="0%" stop-color="#ff5142"/>
<stop offset="100%" stop-color="#a51914"/>
</linearGradient>
<linearGradient id="cloudGrad" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#ffd09a" stop-opacity="0.9"/>
<stop offset="100%" stop-color="#c66e58" stop-opacity="0.55"/>
</linearGradient>
<radialGradient id="tireGrad" cx="50%" cy="50%" r="50%">
<stop offset="85%" stop-color="#2a2a2a"/>
<stop offset="100%" stop-color="#000000"/>
</radialGradient>
<radialGradient id="hubGrad" cx="40%" cy="40%" r="60%">
<stop offset="0%" stop-color="#e8e8e8"/>
<stop offset="60%" stop-color="#888"/>
<stop offset="100%" stop-color="#2f2f2f"/>
</radialGradient>
<linearGradient id="basketGrad" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#d9a868"/>
<stop offset="100%" stop-color="#6f4419"/>
</linearGradient>
<linearGradient id="fishGrad" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#88cdee"/>
<stop offset="100%" stop-color="#2a6c92"/>
</linearGradient>
</defs>
<!-- ============ ?? ============ -->
<rect width="800" height="385" fill="url(#skyGrad)"/>
<!-- ??????? -->
<circle cx="560" cy="260" r="200" fill="url(#sunGlow)"/>
<circle cx="560" cy="260" r="62" fill="url(#sunCore)"/>
<circle cx="551" cy="250" r="22" fill="#fffdf0" opacity="0.7"/>
<!-- ?? -->
<g>
<ellipse cx="120" cy="90" rx="85" ry="11" fill="url(#cloudGrad)"/>
<ellipse cx="175" cy="108" rx="55" ry="8" fill="url(#cloudGrad)" opacity="0.75"/>
<ellipse cx="660" cy="80" rx="95" ry="13" fill="url(#cloudGrad)" opacity="0.7"/>
<ellipse cx="730" cy="95" rx="55" ry="8" fill="url(#cloudGrad)" opacity="0.5"/>
<ellipse cx="380" cy="160" rx="120" ry="12" fill="#ffd0a2" opacity="0.35"/>
<ellipse cx="90" cy="200" rx="100" ry="9" fill="#ffb98a" opacity="0.4"/>
<ellipse cx="720" cy="220" rx="80" ry="7" fill="#ffba80" opacity="0.35"/>
<ellipse cx="200" cy="280" rx="90" ry="6" fill="#ffaa70" opacity="0.4"/>
<ellipse cx="620" cy="320" rx="100" ry="6" fill="#ffaa70" opacity="0.4"/>
</g>
<!-- ???? -->
<g stroke="#3b2818" stroke-width="1.6" fill="none" stroke-linecap="round" opacity="0.6">
<path d="M 90 130 q 7 -7 14 0 q 7 -7 14 0"/>
<path d="M 135 148 q 5 -5 10 0 q 5 -5 10 0"/>
<path d="M 220 115 q 9 -9 18 0 q 9 -9 18 0"/>
<path d="M 260 135 q 5 -5 10 0 q 5 -5 10 0"/>
<path d="M 705 175 q 5 -5 10 0 q 5 -5 10 0"/>
<path d="M 735 195 q 4 -4 8 0 q 4 -4 8 0"/>
</g>
<!-- ??????? -->
<g transform="translate(40, 385)" opacity="0.92">
<path d="M -10 0 L 25 -48 L 80 -48 L 100 0 Z" fill="#3d4e66"/>
<path d="M 25 -48 L 80 -48 L 82 -42 L 23 -42 Z" fill="#2c394d"/>
<rect x="45" y="-82" width="14" height="34" fill="#e8e0d0"/>
<rect x="45" y="-82" width="14" height="6" fill="#b8362a"/>
<rect x="45" y="-68" width="14" height="6" fill="#b8362a"/>
<rect x="45" y="-54" width="14" height="6" fill="#b8362a"/>
<polygon points="43,-82 61,-82 52,-92" fill="#5a2a25"/>
<circle cx="52" cy="-60" r="2.5" fill="#fff8a0"/>
<circle cx="52" cy="-60" r="6" fill="#fff8a0" opacity="0.3"/>
</g>
<!-- ???? -->
<g transform="translate(700, 388)" opacity="0.92">
<path d="M 0 8 L 32 8 L 26 14 L 6 14 Z" fill="#2a1e15"/>
<path d="M 16 8 L 16 -30 L 29 6 Z" fill="#fff8ee"/>
<path d="M 14 8 L 14 -14 L 4 6 Z" fill="#ece2c8"/>
<line x1="16" y1="-32" x2="16" y2="8" stroke="#2a1e15" stroke-width="1.2"/>
<path d="M 6 15 L 26 15 L 22 19 L 10 19 Z" fill="#2a1e15" opacity="0.3"/>
</g>
<!-- ????? -->
<g opacity="0.55">
<path d="M 280 385 q 30 -22 70 -18 q 35 5 60 18 Z" fill="#4a5a72"/>
<path d="M 295 380 q 25 -12 55 -8 q 30 4 45 8" stroke="#2c394d" stroke-width="1" fill="none"/>
</g>
<!-- ============ ?? ============ -->
<rect y="385" width="800" height="80" fill="url(#seaGrad)"/>
<!-- ?????????? -->
<g fill="#ffeaa0" opacity="0.85">
<rect x="530" y="387" width="60" height="3" rx="1.5"/>
<rect x="520" y="394" width="80" height="3" rx="1.5"/>
<rect x="535" y="401" width="50" height="3" rx="1.5"/>
<rect x="512" y="408" width="92" height="3" rx="1.5"/>
<rect x="540" y="415" width="40" height="3" rx="1.5"/>
<rect x="525" y="422" width="68" height="3" rx="1.5"/>
<rect x="545" y="429" width="30" height="3" rx="1.5"/>
<rect x="535" y="436" width="48" height="3" rx="1.5"/>
<rect x="550" y="443" width="20" height="3" rx="1.5"/>
<rect x="540" y="450" width="38" height="3" rx="1.5"/>
<rect x="555" y="457" width="18" height="3" rx="1.5"/>
</g>
<!-- ???? -->
<g stroke="#ffffff" stroke-width="1.4" fill="none" stroke-linecap="round" opacity="0.5">
<path d="M 30 400 q 25 -3 50 0 q 25 3 50 0"/>
<path d="M 200 410 q 30 -4 60 0 q 30 4 60 0"/>
<path d="M 370 405 q 20 -3 40 0 q 20 3 40 0"/>
<path d="M 660 400 q 25 -3 50 0 q 25 3 50 0"/>
<path d="M 100 428 q 25 -3 50 0 q 25 3 50 0"/>
<path d="M 290 432 q 30 -4 60 0 q 30 4 60 0"/>
<path d="M 660 432 q 25 -3 50 0 q 25 3 50 0"/>
<path d="M 50 450 q 22 -3 44 0 q 22 3 44 0"/>
<path d="M 250 455 q 22 -3 44 0 q 22 3 44 0"/>
</g>
<!-- ????? -->
<line x1="0" y1="385" x2="800" y2="385" stroke="#ffd6a0" stroke-width="1.2" opacity="0.85"/>
<!-- ============ ?? ============ -->
<!-- ??????????? -->
<path d="M 0 444 q 200 -12 400 -4 t 400 16 L 800 500 L 0 500 Z" fill="url(#wetSandGrad)"/>
<!-- ????? -->
<path d="M 0 444 q 200 -12 400 -4 t 400 16" stroke="#ffffff" stroke-width="5" fill="none" stroke-linecap="round" opacity="0.9"/>
<path d="M 0 452 q 200 -8 400 -1 t 400 14" stroke="#ffffff" stroke-width="2.8" fill="none" stroke-linecap="round" opacity="0.55"/>
<g fill="#ffffff" opacity="0.8">
<circle cx="120" cy="446" r="2"/>
<circle cx="240" cy="444" r="1.5"/>
<circle cx="360" cy="443" r="2"/>
<circle cx="480" cy="448" r="1.5"/>
<circle cx="620" cy="452" r="2"/>
<circle cx="710" cy="458" r="1.5"/>
<circle cx="70" cy="449" r="1.5"/>
<circle cx="180" cy="448" r="1.2"/>
<circle cx="300" cy="445" r="1.2"/>
<circle cx="550" cy="450" r="1.2"/>
</g>
<!-- ???????? -->
<path d="M 0 500 q 400 -10 800 4 L 800 600 L 0 600 Z" fill="url(#sandGrad)"/>
<!-- ???? -->
<g fill="#a8814e" opacity="0.55">
<circle cx="50" cy="528" r="1.2"/><circle cx="120" cy="540" r="1"/>
<circle cx="180" cy="552" r="1.3"/><circle cx="80" cy="548" r="0.8"/>
<circle cx="200" cy="538" r="1"/><circle cx="540" cy="545" r="1.2"/>
<circle cx="600" cy="555" r="1"/><circle cx="660" cy="540" r="1.3"/>
<circle cx="700" cy="555" r="0.9"/><circle cx="750" cy="548" r="1.1"/>
<circle cx="630" cy="528" r="0.9"/><circle cx="40" cy="560" r="1"/>
<circle cx="160" cy="532" r="0.8"/><circle cx="230" cy="552" r="1"/>
<circle cx="280" cy="560" r="0.9"/><circle cx="520" cy="535" r="1"/>
<circle cx="780" cy="556" r="1.1"/>
<circle cx="100" cy="572" r="1"/><circle cx="320" cy="580" r="1.2"/>
<circle cx="480" cy="575" r="1"/><circle cx="690" cy="580" r="1.1"/>
</g>
<g fill="#7a5b2e" opacity="0.4">
<circle cx="90" cy="540" r="0.7"/><circle cx="150" cy="550" r="0.8"/>
<circle cx="210" cy="554" r="0.7"/><circle cx="570" cy="540" r="0.8"/>
<circle cx="640" cy="552" r="0.7"/><circle cx="690" cy="568" r="0.8"/>
<circle cx="60" cy="555" r="0.7"/><circle cx="730" cy="538" r="0.7"/>
<circle cx="380" cy="580" r="0.8"/><circle cx="440" cy="570" r="0.7"/>
</g>
<!-- ???? -->
<g transform="translate(640, 545)">
<path d="M 0 0 q 9 -11 18 0 q -2 5 -9 5 q -7 0 -9 -5 Z" fill="#fceedd" stroke="#cd9466" stroke-width="0.8"/>
<path d="M 5 0 q 4 -6 8 0 M 7 0 q 2 -3 4 0" stroke="#cd9466" stroke-width="0.6" fill="none"/>
</g>
<g transform="translate(720, 535)">
<ellipse rx="7" ry="4.5" fill="#f5d4b8" stroke="#b07a4a" stroke-width="0.7"/>
<path d="M -3 0 q 3 -3 6 0 M -2 1 q 2 -2 4 0" stroke="#b07a4a" stroke-width="0.5" fill="none"/>
</g>
<g transform="translate(70, 555) rotate(18)">
<path d="M 0 0 L 0 -9 L 5 -7 L 5 2 Z" fill="#e8a86c" stroke="#8e5a28" stroke-width="0.6"/>
<path d="M 0 -4 L 5 -3" stroke="#8e5a28" stroke-width="0.4"/>
</g>
<!-- ?? -->
<g transform="translate(195, 562)" fill="#e87a3a" stroke="#a83a1a" stroke-width="0.7">
<path d="M 0 -8 L 2 -2 L 8 -2 L 3 2 L 5 8 L 0 4 L -5 8 L -3 2 L -8 -2 L -2 -2 Z"/>
<circle cx="0" cy="0" r="1.5" fill="#a83a1a"/>
</g>
<!-- ??? -->
<g>
<ellipse cx="450" cy="560" rx="5" ry="2.5" fill="#9b8b6a" opacity="0.8"/>
<ellipse cx="350" cy="555" rx="4" ry="2" fill="#7a5e3e" opacity="0.7"/>
<ellipse cx="555" cy="565" rx="3" ry="1.5" fill="#9b8b6a" opacity="0.6"/>
</g>
<!-- ============ ????? ============ -->
<ellipse cx="380" cy="555" rx="210" ry="10" fill="#5a3a1a" opacity="0.32"/>
<ellipse cx="380" cy="555" rx="180" ry="6" fill="#3a2510" opacity="0.4"/>
<!-- ??????? -->
<g opacity="0.55">
<ellipse cx="170" cy="555" rx="22" ry="6" fill="#d9b078"/>
<ellipse cx="140" cy="548" rx="14" ry="4" fill="#d9b078" opacity="0.85"/>
<ellipse cx="115" cy="538" rx="10" ry="3" fill="#d9b078" opacity="0.7"/>
<circle cx="192" cy="538" r="3" fill="#d9b078"/>
<circle cx="162" cy="528" r="2.2" fill="#d9b078" opacity="0.8"/>
<circle cx="138" cy="525" r="1.8" fill="#d9b078" opacity="0.7"/>
<circle cx="100" cy="522" r="1.5" fill="#d9b078" opacity="0.6"/>
</g>
<!-- ????? -->
<g opacity="0.5">
<path d="M 50 558 q 60 -3 110 0" stroke="#7a5028" stroke-width="3" fill="none"/>
<path d="M 80 565 q 50 -3 90 0" stroke="#7a5028" stroke-width="2" fill="none"/>
</g>
<!-- ============ ?? ============ -->
<g transform="translate(240, 500)">
<circle r="55" fill="url(#tireGrad)"/>
<circle r="48" fill="#101010"/>
<circle r="46" stroke="#5a5a5a" stroke-width="1.2" fill="#1a1a1a"/>
<circle r="42" fill="#c8c8c8"/>
<!-- ?????????? -->
<circle r="38" stroke="#9a9a9a" stroke-dasharray="2 7" stroke-width="36" fill="none" opacity="0.35"/>
<circle r="35" stroke="#fff" stroke-dasharray="1 12" stroke-width="34" fill="none" opacity="0.18"/>
<circle r="9" fill="url(#hubGrad)"/>
<circle r="4" fill="#1a1a1a"/>
<g stroke="#3a3a3a" stroke-width="1" opacity="0.7">
<path d="M -53 -8 l 4 0 M -53 0 l 4 0 M -53 8 l 4 0"/>
<path d="M 49 -8 l 4 0 M 49 0 l 4 0 M 49 8 l 4 0"/>
</g>
</g>
<!-- ============ ?????????? ============ -->
<g>
<path d="M 355 415 q -8 26 -22 38 q -8 7 -18 6" stroke="#b3a482" stroke-width="14" fill="none" stroke-linecap="round" opacity="0.9"/>
<g transform="translate(315, 461) rotate(-18)">
<path d="M 0 0 q -22 -4 -28 4 q 4 8 28 4 Z" fill="url(#footGrad)" stroke="#7a4810" stroke-width="0.8" opacity="0.9"/>
<path d="M -8 1 q -2 5 1 6 M -16 1 q -2 5 1 6 M -22 2 q -2 4 0 5" stroke="#7a4810" stroke-width="0.6" fill="none"/>
</g>
</g>
<!-- ============ ???? ============ -->
<g>
<!-- ??? -->
<line x1="350" y1="500" x2="244" y2="500" stroke="url(#frameGrad)" stroke-width="9" stroke-linecap="round"/>
<!-- ??? -->
<line x1="340" y1="395" x2="244" y2="500" stroke="url(#frameGrad)" stroke-width="9" stroke-linecap="round"/>
<!-- ?? -->
<line x1="340" y1="395" x2="350" y2="500" stroke="url(#frameGrad)" stroke-width="11" stroke-linecap="round"/>
<!-- ?? -->
<line x1="350" y1="500" x2="500" y2="418" stroke="url(#frameGrad)" stroke-width="11" stroke-linecap="round"/>
<!-- ??????????? -->
<path d="M 340 397 Q 410 425, 497 414" stroke="url(#frameGrad)" stroke-width="11" fill="none" stroke-linecap="round"/>
<!-- ?? -->
<line x1="500" y1="418" x2="520" y2="500" stroke="url(#frameGrad)" stroke-width="9" stroke-linecap="round"/>
<!-- ?????? -->
<line x1="349" y1="497" x2="245" y2="497" stroke="#bff2f8" stroke-width="1.5" opacity="0.8"/>
<line x1="350" y1="498" x2="498" y2="416" stroke="#bff2f8" stroke-width="1.5" opacity="0.8"/>
<path d="M 342 399 Q 410 426, 495 412" stroke="#bff2f8" stroke-width="1.2" fill="none" opacity="0.7"/>
</g>
<!-- ???? -->
<path d="M 186 500 a 54 54 0 0 1 108 0" stroke="url(#frameGrad)" stroke-width="6" fill="none" stroke-linecap="round"/>
<!-- ????? -->
<g>
<circle cx="350" cy="500" r="15" fill="#3a3a3a"/>
<circle cx="350" cy="500" r="13" fill="#5a5a5a"/>
<circle cx="350" cy="500" r="4" fill="#1a1a1a"/>
<g stroke="#888" stroke-width="1" fill="none">
<line x1="350" y1="483" x2="350" y2="480"/>
<line x1="363" y1="488" x2="366" y2="485"/>
<line x1="365" y1="500" x2="368" y2="500"/>
<line x1="363" y1="512" x2="366" y2="515"/>
<line x1="350" y1="517" x2="350" y2="520"/>
<line x1="337" y1="512" x2="334" y2="515"/>
<line x1="335" y1="500" x2="332" y2="500"/>
<line x1="337" y1="488" x2="334" y2="485"/>
</g>
<path d="M 350 485 q 60 -32 145 -65" stroke="#444" stroke-width="2" fill="none" opacity="0.85"/>
<path d="M 350 515 q 60 -22 145 -55" stroke="#444" stroke-width="2" fill="none" opacity="0.7"/>
</g>
<!-- ????? -->
<g>
<line x1="350" y1="500" x2="378" y2="520" stroke="#222" stroke-width="5" stroke-linecap="round"/>
<line x1="350" y1="500" x2="322" y2="480" stroke="#222" stroke-width="5" stroke-linecap="round"/>
<rect x="372" y="517" width="16" height="7" rx="1.5" fill="#1a1a1a"/>
<rect x="370" y="517" width="2" height="7" fill="#5a5a5a"/>
<rect x="312" y="475" width="16" height="7" rx="1.5" fill="#1a1a1a"/>
</g>
<!-- ?? -->
<line x1="340" y1="395" x2="335" y2="380" stroke="#3a3a3a" stroke-width="5" stroke-linecap="round"/>
<!-- ????? -->
<g transform="translate(335, 378)">
<path d="M -24 -3 q -3 -9 11 -11 q 16 -2 23 1 q 9 3 -5 13 q -12 6 -29 -3 Z" fill="#4d2914"/>
<path d="M -24 -3 q -3 -9 11 -11 q 16 -2 23 1" stroke="#8a4f25" stroke-width="0.8" fill="none"/>
<ellipse cx="-3" cy="-4" rx="15" ry="3" fill="#7a4220" opacity="0.7"/>
<ellipse cx="-3" cy="-6" rx="8" ry="1.5" fill="#a06030" opacity="0.5"/>
</g>
<!-- ============ ????????? ============ -->
<g>
<!-- ?? -->
<g transform="translate(303, 425)">
<path d="M 0 0 L -54 18 L -28 -22 Z" fill="#b3a482"/>
<path d="M 0 -4 L -62 -6 L -32 -34 Z" fill="#e8dfc8"/>
<path d="M 0 -8 L -58 -28 L -28 -40 Z" fill="#fafaf0"/>
<g stroke="#9b8b6a" stroke-width="0.6" opacity="0.65" fill="none">
<path d="M -8 -4 L -45 -14"/>
<path d="M -8 -10 L -45 -28"/>
<path d="M -5 0 L -40 12"/>
</g>
</g>
<!-- ????????? -->
<path d="M 305 425
C 268 397, 270 337, 322 320
C 388 304, 438 340, 438 396
C 438 428, 388 442, 348 438 Z" fill="url(#bodyGrad)"/>
<!-- ???? -->
<path d="M 308 425 C 300 410, 308 390, 332 383 C 372 376, 408 395, 418 415 C 400 432, 360 438, 320 435 Z" fill="url(#bellyGrad)" opacity="0.85"/>
<!-- ????? -->
<path d="M 380 322 C 420 336, 438 365, 438 396 C 432 422, 410 435, 388 437 C 420 422, 432 388, 422 352 Z" fill="#9b8b6a" opacity="0.28"/>
<!-- ?????? -->
<g stroke="#c5b896" stroke-width="0.5" fill="none" opacity="0.5">
<path d="M 290 378 q 25 -8 50 -5"/>
<path d="M 290 393 q 28 -6 56 -3"/>
<path d="M 295 408 q 30 -4 60 0"/>
</g>
<!-- S ???? -->
<path d="M 362 328
C 410 318, 425 290, 408 258
C 390 233, 395 215, 420 208"
stroke="url(#bodyGrad)" stroke-width="38" fill="none" stroke-linecap="round"/>
<!-- ???? -->
<path d="M 368 330
C 410 318, 422 290, 405 260
C 388 235, 395 220, 415 213"
stroke="#c5b896" stroke-width="7" fill="none" stroke-linecap="round" opacity="0.5"/>
<!-- ???? -->
<path d="M 358 326
C 400 314, 414 288, 400 258"
stroke="#ffffff" stroke-width="4" fill="none" stroke-linecap="round" opacity="0.6"/>
<!-- ?????????? -->
<g>
<path d="M 400 226 q -12 5 -28 -7 q 20 -10 35 -5 Z" fill="url(#scarfGrad)"/>
<path d="M 392 235 q -50 -3 -82 18 q 32 -1 86 -6 Z" fill="url(#scarfGrad)"/>
<path d="M 392 245 q -55 2 -100 32 q 45 -3 105 -12 Z" fill="#a01515" opacity="0.92"/>
<path d="M 388 238 q -42 4 -70 22 q 30 -2 75 -8 Z" fill="#ff6e58" opacity="0.55"/>
<path d="M 350 253 q 8 4 0 10 M 320 266 q 8 4 0 10" stroke="#7a0e0e" stroke-width="0.8" fill="none" opacity="0.7"/>
</g>
<!-- ?? -->
<ellipse cx="423" cy="198" rx="34" ry="29" fill="url(#bodyGrad)"/>
<ellipse cx="431" cy="206" rx="22" ry="20" fill="#b3a482" opacity="0.3"/>
<ellipse cx="411" cy="186" rx="14" ry="10" fill="#ffffff" opacity="0.75"/>
<!-- ???? -->
<path d="M 408 174 q 3 -10 9 -9 q -1 7 -1 13 Z" fill="#e8dfc8"/>
<path d="M 419 170 q 5 -9 11 -5 q -3 7 -4 11 Z" fill="#e8dfc8"/>
<path d="M 429 172 q 6 -7 10 -3 q -3 5 -4 8 Z" fill="#d9c8a0"/>
<!-- ?? -->
<ellipse cx="435" cy="196" rx="6" ry="5.5" fill="#3a2818"/>
<!-- ????? -->
<g>
<path d="M 417 190 q 17 -7 32 -2 L 449 204 q -14 7 -30 2 Z" fill="#0d0d0d"/>
<path d="M 417 190 q 17 -7 32 -2 L 449 204 q -14 7 -30 2 Z" fill="none" stroke="#444" stroke-width="1.2"/>
<path d="M 423 194 q 8 -3 16 -1" stroke="#7ed0f0" stroke-width="1.8" fill="none" opacity="0.9"/>
<path d="M 435 200 q 3 -1 6 -0.5" stroke="#fff" stroke-width="1" fill="none" opacity="0.75"/>
<line x1="449" y1="198" x2="456" y2="196" stroke="#1a1a1a" stroke-width="1.6"/>
<line x1="417" y1="192" x2="409" y2="188" stroke="#1a1a1a" stroke-width="1.5"/>
</g>
<!-- ???? -->
<path d="M 453 194 Q 545 204, 585 235 Q 565 240, 475 233 Q 451 230, 449 213 Z" fill="url(#beakGrad)"/>
<path d="M 455 200 Q 540 208, 575 230 Q 545 225, 475 220 Q 455 215, 455 204 Z" fill="#ffd86b" opacity="0.6"/>
<path d="M 465 211 Q 535 215, 577 235" stroke="#a85a10" stroke-width="0.9" fill="none" opacity="0.75"/>
<ellipse cx="475" cy="211" rx="2.2" ry="1.2" fill="#5a2a08"/>
<path d="M 580 237 q 8 4 0 9 q -10 -1 -12 -7 Z" fill="#8e4810"/>
<!-- ?? -->
<path d="M 449 225
Q 473 256, 528 260
Q 568 256, 580 237
Q 533 248, 481 238
Q 461 233, 449 225 Z" fill="url(#pouchGrad)"/>
<path d="M 458 233 q 40 22 80 21" stroke="#a8521e" stroke-width="0.9" fill="none" opacity="0.7"/>
<path d="M 465 239 q 38 18 72 18" stroke="#a8521e" stroke-width="0.7" fill="none" opacity="0.55"/>
<path d="M 473 245 q 35 14 65 14" stroke="#a8521e" stroke-width="0.6" fill="none" opacity="0.45"/>
<path d="M 473 230 q 30 14 60 14" stroke="#fff" stroke-width="1.3" fill="none" opacity="0.45"/>
<path d="M 449 225 Q 533 248, 580 237" stroke="#7e3c08" stroke-width="1.2" fill="none" opacity="0.6"/>
<!-- ????????????? -->
<g transform="translate(510, 385)">
<path d="M 0 0 q -12 -7 -20 -4 q 0 6 0 12 q 8 0 20 -2 Z" fill="url(#fishGrad)"/>
<path d="M -20 -4 L -28 -10 L -28 14 L -20 8 Z" fill="#2a6c92"/>
<path d="M -28 -10 L -26 0 L -28 14" stroke="#88cdee" stroke-width="0.7" fill="none"/>
<circle cx="-4" cy="2" r="1.2" fill="#1a1a1a"/>
<circle cx="-3.7" cy="1.8" r="0.5" fill="#fff"/>
<path d="M -12 5 q 6 1 10 0" stroke="#1a4a6a" stroke-width="0.5" fill="none"/>
<path d="M -14 -3 q 3 0 3 3 M -8 -3 q 3 0 3 3 M -14 3 q 3 0 3 3 M -8 3 q 3 0 3 3" stroke="#1a4a6a" stroke-width="0.3" fill="none" opacity="0.6"/>
</g>
<!-- ???? -->
<path d="M 352 348
C 392 338, 425 358, 445 396
C 432 425, 388 425, 360 405
C 342 390, 338 368, 352 348 Z" fill="url(#wingGrad)"/>
<!-- ?????? -->
<g fill="#6e5a32" opacity="0.6">
<path d="M 360 365 q 22 6 50 24 q -10 3 -25 0 q -18 -8 -30 -18 Z"/>
<path d="M 364 385 q 22 6 56 22 q -12 4 -26 1 q -22 -8 -32 -19 Z"/>
<path d="M 370 403 q 25 6 60 16 q -12 4 -25 2 q -25 -6 -36 -14 Z"/>
</g>
<!-- ?????? -->
<g fill="none" stroke="#3f3018" stroke-width="0.8" opacity="0.7">
<path d="M 358 358 q 25 4 50 22"/>
<path d="M 362 376 q 25 4 56 22"/>
<path d="M 368 394 q 28 4 60 16"/>
<path d="M 378 410 q 30 3 55 8"/>
</g>
<!-- ??????????? -->
<g fill="#2a1f10">
<path d="M 432 410 q 12 2 14 12 q -6 6 -16 0 Z"/>
<path d="M 422 420 q 12 0 14 12 q -6 6 -16 -2 Z"/>
<path d="M 410 426 q 10 2 12 13 q -8 5 -14 -3 Z"/>
</g>
<!-- ???? -->
<path d="M 358 354 q 16 -8 36 -5 q -5 8 -22 12 q -12 1 -16 -8 Z" fill="#f5ecc8" opacity="0.7"/>
<path d="M 354 351 q 20 -8 42 -3" stroke="#fff" stroke-width="1.2" fill="none" opacity="0.5"/>
<!-- ??"??"????????? -->
<path d="M 440 398 Q 472 402, 494 414" stroke="url(#wingGrad)" stroke-width="14" fill="none" stroke-linecap="round"/>
<path d="M 440 396 Q 472 400, 492 412" stroke="#6e5a32" stroke-width="3" fill="none" stroke-linecap="round" opacity="0.7"/>
<!-- ?????? -->
<ellipse cx="498" cy="416" rx="10" ry="8" fill="#6e5a32"/>
<ellipse cx="498" cy="414" rx="6" ry="3" fill="#9b8b6a" opacity="0.7"/>
<!-- ??? -->
<path d="M 370 428 Q 372 470, 382 516" stroke="url(#bodyGrad)" stroke-width="15" fill="none" stroke-linecap="round"/>
<path d="M 374 430 Q 376 470, 384 512" stroke="#c5b896" stroke-width="3" fill="none" stroke-linecap="round" opacity="0.5"/>
<!-- ???? -->
<g transform="translate(382, 519) rotate(-5)">
<path d="M -6 -3 q 6 -3 12 -2 L 32 0 q 6 4 -2 9 L 0 11 q -6 -3 -2 -11 Z" fill="url(#footGrad)" stroke="#7a4810" stroke-width="0.9"/>
<path d="M 8 0 q 1 6 -1 9 M 18 0 q 1 6 -2 9 M 28 0 q 0 5 -3 7" stroke="#7a4810" stroke-width="0.6" fill="none"/>
<path d="M 0 -2 q 16 -1 28 1" stroke="#ffd070" stroke-width="0.9" fill="none" opacity="0.75"/>
<path d="M 32 -1 l 2 -2 M 22 -2 l 2 -2 M 12 -2 l 1 -2" stroke="#3a1808" stroke-width="0.7"/>
</g>
</g>
<!-- ============ ???? ============ -->
<g>
<line x1="500" y1="418" x2="500" y2="388" stroke="#2a2a2a" stroke-width="7" stroke-linecap="round"/>
<path d="M 500 388 Q 500 374, 512 374 Q 524 374, 524 386 L 522 396"
stroke="#222" stroke-width="6.5" fill="none" stroke-linecap="round"/>
<path d="M 500 388 Q 500 374, 488 374 Q 476 374, 476 386 L 478 396"
stroke="#222" stroke-width="6.5" fill="none" stroke-linecap="round"/>
<rect x="516" y="383" width="12" height="8" rx="2.5" fill="#b8362a"/>
<rect x="472" y="383" width="12" height="8" rx="2.5" fill="#b8362a"/>
<rect x="516" y="384" width="12" height="2" fill="#ff6e58" opacity="0.7"/>
<rect x="472" y="384" width="12" height="2" fill="#ff6e58" opacity="0.7"/>
<!-- ?? -->
<g transform="translate(490, 384)">
<circle r="5" fill="#c5862a"/>
<circle r="3.5" fill="#e8a850"/>
<circle r="1" fill="#5a3a18"/>
<path d="M -3 -2 q 3 -1 4 1" stroke="#fff8a0" stroke-width="0.6" fill="none" opacity="0.8"/>
</g>
<!-- ??? -->
<g transform="translate(480, 358)">
<path d="M 0 0 L 48 0 L 54 26 L -6 26 Z" fill="url(#basketGrad)"/>
<path d="M 0 0 L 48 0 L 49 4 L -1 4 Z" fill="#a87a3a"/>
<g stroke="#4a2f15" stroke-width="0.6" fill="none" opacity="0.75">
<line x1="-3" y1="9" x2="51" y2="9"/>
<line x1="-4" y1="15" x2="52" y2="15"/>
<line x1="-5" y1="21" x2="53" y2="21"/>
<line x1="4" y1="4" x2="4" y2="26"/>
<line x1="14" y1="4" x2="14" y2="26"/>
<line x1="24" y1="4" x2="24" y2="26"/>
<line x1="34" y1="4" x2="34" y2="26"/>
<line x1="44" y1="4" x2="44" y2="26"/>
</g>
<path d="M 0 1 L 48 1" stroke="#ffd9a0" stroke-width="0.8" opacity="0.8"/>
<!-- ???? -->
<path d="M 5 0 q 19 -12 38 0" stroke="#6f4419" stroke-width="1.5" fill="none"/>
</g>
</g>
<!-- ============ ?? ============ -->
<g transform="translate(520, 500)">
<circle r="55" fill="url(#tireGrad)"/>
<circle r="48" fill="#101010"/>
<circle r="46" stroke="#5a5a5a" stroke-width="1.2" fill="#1a1a1a"/>
<circle r="42" fill="#c8c8c8"/>
<circle r="38" stroke="#9a9a9a" stroke-dasharray="2 7" stroke-width="36" fill="none" opacity="0.35"/>
<circle r="35" stroke="#fff" stroke-dasharray="1 12" stroke-width="34" fill="none" opacity="0.18"/>
<circle r="9" fill="url(#hubGrad)"/>
<circle r="4" fill="#1a1a1a"/>
<g stroke="#3a3a3a" stroke-width="1" opacity="0.7">
<path d="M -53 -8 l 4 0 M -53 0 l 4 0 M -53 8 l 4 0"/>
<path d="M 49 -8 l 4 0 M 49 0 l 4 0 M 49 8 l 4 0"/>
</g>
<ellipse cx="20" cy="-22" rx="11" ry="5" fill="#ffd86b" opacity="0.4"/>
</g>
<!-- ???? -->
<path d="M 466 500 a 54 54 0 0 1 108 0" stroke="url(#frameGrad)" stroke-width="6" fill="none" stroke-linecap="round"/>
<ellipse cx="572" cy="494" rx="3" ry="2" fill="#ffd86b"/>
<!-- ============ ????? ============ -->
<g stroke-linecap="round" opacity="0.7">
<line x1="30" y1="470" x2="135" y2="468" stroke="#ffffff" stroke-width="3"/>
<line x1="10" y1="495" x2="85" y2="493" stroke="#ffea99" stroke-width="2"/>
<line x1="50" y1="520" x2="160" y2="518" stroke="#ffffff" stroke-width="3.5"/>
<line x1="15" y1="540" x2="85" y2="540" stroke="#ffffff" stroke-width="2.5"/>
<line x1="70" y1="556" x2="130" y2="556" stroke="#ffd86b" stroke-width="2" opacity="0.6"/>
</g>
<!-- ????? -->
<g fill="#f8f5e8" opacity="0.85">
<ellipse cx="385" cy="265" rx="4" ry="1.5" transform="rotate(-15 385 265)"/>
<ellipse cx="365" cy="285" rx="3" ry="1.2" transform="rotate(-20 365 285)"/>
<ellipse cx="345" cy="305" rx="2.5" ry="1" transform="rotate(-25 345 305)"/>
</g>
<!-- ??????? -->
<circle cx="441" cy="198" r="1.2" fill="#ffffff" opacity="0.9"/>
</svg>

After

Width:  |  Height:  |  Size: 28 KiB

Whitespace-only changes.
Whitespace-only changes.
@@ -0,0 +1,603 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Mythiccraft</title>
<style>
body { margin: 0; overflow: hidden; background: #000; font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; user-select: none; }
#crosshair {
position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%);
color: rgba(255,255,255,0.8); font-size: 24px; font-weight: normal; pointer-events: none; text-shadow: 1px 1px 0 #000;
}
#toolbar {
position: absolute; bottom: 20px; left: 50%; transform: translateX(-50%);
display: flex; gap: 8px; background: rgba(0,0,0,0.6); padding: 10px; border-radius: 12px;
box-shadow: 0 4px 15px rgba(0,0,0,0.5);
}
.slot {
width: 50px; height: 50px; border: 3px solid #444; border-radius: 6px;
display: flex; align-items: flex-end; justify-content: center; color: white;
font-size: 14px; font-weight: bold; text-shadow: 1px 1px 2px black; padding-bottom: 4px;
box-sizing: border-box; cursor: pointer; background-size: cover; background-position: center;
transition: transform 0.1s; image-rendering: pixelated;
}
.slot.selected { border-color: #fff; transform: scale(1.15); box-shadow: 0 0 15px rgba(255,255,255,0.5); z-index: 10;}
#instructions {
position: absolute; top: 0; left: 0; width: 100%; height: 100%;
background: rgba(26, 11, 46, 0.85); display: flex; flex-direction: column;
align-items: center; justify-content: center; color: #fff; z-index: 200;
}
#instructions h1 { font-size: 64px; margin: 0; color: #f5d436; text-shadow: 0 0 20px rgba(245, 212, 54, 0.5); }
#instructions p { font-size: 18px; color: #ccc; }
#start-btn {
padding: 15px 40px; font-size: 24px; cursor: pointer; margin-top: 30px;
background: #f5d436; color: #1a0b2e; border: none; border-radius: 30px; font-weight: bold;
transition: background 0.2s, transform 0.2s;
}
#start-btn:hover { background: #fff; transform: scale(1.05); }
.controls-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 10px 40px; margin-top: 40px; text-align: left; background: rgba(0,0,0,0.4); padding: 20px; border-radius: 10px;}
.controls-grid span { color: #f5d436; font-weight: bold; }
</style>
</head>
<body>
<div id="crosshair">+</div>
<div id="toolbar">
<div class="slot selected" id="slot1">1</div>
<div class="slot" id="slot2">2</div>
<div class="slot" id="slot3">3</div>
<div class="slot" id="slot4">4</div>
<div class="slot" id="slot5">5</div>
<div class="slot" id="slot6">6</div>
</div>
<div id="instructions">
<h1>Mythiccraft</h1>
<p>Explore the roots of the World Tree.</p>
<button id="start-btn">Enter World</button>
<div class="controls-grid">
<div><span>WASD</span> / Arrows to Move</div>
<div><span>Left Click</span> to Break Block</div>
<div><span>SPACE</span> to Jump</div>
<div><span>Right Click</span> to Place Block</div>
<div><span>Mouse</span> to Look Around</div>
<div><span>1 - 6</span> to Select Block Type</div>
</div>
</div>
<!-- Load Three.js and PointerLockControls -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/three@0.128.0/examples/js/controls/PointerLockControls.js"></script>
<script>
// --- Perlin Noise Implementation ---
class Perlin {
constructor() {
this.p = new Uint8Array(512);
for(let i=0; i<256; i++) this.p[i] = Math.floor(Math.random()*256);
for(let i=0; i<256; i++) this.p[256+i] = this.p[i];
}
fade(t) { return t * t * t * (t * (t * 6 - 15) + 10); }
lerp(t, a, b) { return a + t * (b - a); }
grad(hash, x, y, z) {
let h = hash & 15;
let u = h < 8 ? x : y;
let v = h < 4 ? y : h === 12 || h === 14 ? x : z;
return ((h & 1) === 0 ? u : -u) + ((h & 2) === 0 ? v : -v);
}
noise(x, y, z) {
let X = Math.floor(x) & 255, Y = Math.floor(y) & 255, Z = Math.floor(z) & 255;
x -= Math.floor(x); y -= Math.floor(y); z -= Math.floor(z);
let u = this.fade(x), v = this.fade(y), w = this.fade(z);
let A = this.p[X] + Y, AA = this.p[A] + Z, AB = this.p[A + 1] + Z;
let B = this.p[X + 1] + Y, BA = this.p[B] + Z, BB = this.p[B + 1] + Z;
return this.lerp(w, this.lerp(v, this.lerp(u, this.grad(this.p[AA], x, y, z), this.grad(this.p[BA], x - 1, y, z)),
this.lerp(u, this.grad(this.p[AB], x, y - 1, z), this.grad(this.p[BB], x - 1, y - 1, z))),
this.lerp(v, this.lerp(u, this.grad(this.p[AA + 1], x, y, z - 1), this.grad(this.p[BA + 1], x - 1, y, z - 1)),
this.lerp(u, this.grad(this.p[AB + 1], x, y - 1, z - 1), this.grad(this.p[BB + 1], x - 1, y - 1, z - 1))));
}
}
const perlin = new Perlin();
// --- Texture Atlas Generator ---
let atlasCanvas;
function generateAtlas() {
atlasCanvas = document.createElement('canvas');
atlasCanvas.width = 256; atlasCanvas.height = 256;
const ctx = atlasCanvas.getContext('2d');
function drawNoise(x, y, w, h, baseColor, varColor, noiseLevel) {
ctx.fillStyle = baseColor;
ctx.fillRect(x, y, w, h);
for(let i=x; i<x+w; i+=2) {
for(let j=y; j<y+h; j+=2) {
if(Math.random() < noiseLevel) {
ctx.fillStyle = Math.random() > 0.5 ? varColor : baseColor;
ctx.fillRect(i, j, 2, 2);
}
}
}
}
// Row 0
drawNoise(0, 0, 64, 64, '#3e6b2c', '#4a8035', 0.5); // 0: Grass Top
drawNoise(64, 0, 64, 64, '#4a3018', '#382412', 0.5); // 1: Grass Side
ctx.fillStyle = '#3e6b2c'; ctx.fillRect(64, 0, 64, 12);
drawNoise(128, 0, 64, 64, '#4a3018', '#382412', 0.5); // 2: Dirt
drawNoise(192, 0, 64, 64, '#555555', '#666666', 0.5); // 3: Stone
// Row 1
drawNoise(0, 64, 64, 64, '#2c1e10', '#1f150b', 0.5); // 4: Wood Side
drawNoise(64, 64, 64, 64, '#54402c', '#423222', 0.5); // 5: Wood Top
drawNoise(128, 64, 64, 64, '#1c3614', '#12240d', 0.8); // 6: Leaves
drawNoise(192, 64, 64, 64, '#f5d436', '#fff17a', 0.6); // 7: Magic (Gold)
let tex = new THREE.CanvasTexture(atlasCanvas);
tex.magFilter = THREE.NearestFilter;
tex.minFilter = THREE.NearestFilter;
return tex;
}
// --- Toolbar Icons ---
function setToolbarIcons() {
let canvas = document.createElement('canvas');
canvas.width = 64; canvas.height = 64;
let ctx = canvas.getContext('2d');
let drawIcon = (id, tx, ty) => {
ctx.clearRect(0,0,64,64);
ctx.drawImage(atlasCanvas, tx*64, ty*64, 64, 64, 0, 0, 64, 64);
document.getElementById('slot' + id).style.backgroundImage = `url(${canvas.toDataURL()})`;
};
drawIcon(1, 1, 0); // Grass Side
drawIcon(2, 2, 0); // Dirt
drawIcon(3, 3, 0); // Stone
drawIcon(4, 0, 1); // Wood Side
drawIcon(5, 2, 1); // Leaves
drawIcon(6, 3, 1); // Magic
}
// --- Core Variables ---
const chunkWidth = 16, chunkHeight = 160, renderDistance = 3;
const chunks = new Map(), chunkMeshes = new Map();
let lastChunkX = -999, lastChunkZ = -999;
let currentBlockType = 1;
// --- Three.js Setup ---
const scene = new THREE.Scene();
scene.background = new THREE.Color(0x1a0b2e); // Mythical purple night
scene.fog = new THREE.FogExp2(0x1a0b2e, 0.015);
const camera = new THREE.PerspectiveCamera(80, window.innerWidth / window.innerHeight, 0.1, 1000);
const renderer = new THREE.WebGLRenderer({ antialias: false });
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
const controls = new THREE.PointerLockControls(camera, renderer.domElement);
// Lighting
const dirLight = new THREE.DirectionalLight(0xaa88ff, 0.7);
dirLight.position.set(-100, 150, -50);
scene.add(dirLight);
const ambientLight = new THREE.AmbientLight(0x403050, 1.0);
scene.add(ambientLight);
// Stars
const starsGeo = new THREE.BufferGeometry();
const starsPositions = [];
for(let i=0; i<2000; i++) {
starsPositions.push((Math.random() - 0.5) * 1000, Math.random() * 400 + 50, (Math.random() - 0.5) * 1000);
}
starsGeo.setAttribute('position', new THREE.Float32BufferAttribute(starsPositions, 3));
const starsMat = new THREE.PointsMaterial({color: 0xffffff, size: 1.5});
scene.add(new THREE.Points(starsGeo, starsMat));
// Block Material
const material = new THREE.MeshLambertMaterial({ map: generateAtlas(), transparent: true, alphaTest: 0.1 });
setToolbarIcons();
// Active Block Outline
const outlineGeo = new THREE.EdgesGeometry(new THREE.BoxGeometry(1.01, 1.01, 1.01));
const outlineMat = new THREE.LineBasicMaterial({ color: 0xffffff, transparent: true, opacity: 0.5 });
const outlineMesh = new THREE.LineSegments(outlineGeo, outlineMat);
outlineMesh.visible = false;
scene.add(outlineMesh);
// --- Chunk Generation ---
function generateChunk(cx, cz) {
let data = new Uint8Array(16 * chunkHeight * 16);
let treeX = 0, treeZ = 0;
for(let lx = 0; lx < 16; lx++) {
for(let lz = 0; lz < 16; lz++) {
let wx = cx * 16 + lx;
let wz = cz * 16 + lz;
// Base Terrain
let n = perlin.noise(wx * 0.02, 0, wz * 0.02);
let n2 = perlin.noise(wx * 0.005, 0, wz * 0.005);
let h = Math.floor((n * 0.8 + n2 * 1.2) * 15 + 30);
if(h < 2) h = 2;
for(let y = 0; y < chunkHeight; y++) {
let idx = (lx * 16 + lz) * chunkHeight + y;
if(y === h) data[idx] = 1; // Grass
else if(y < h && y > h - 4) data[idx] = 2; // Dirt
else if(y <= h - 4) data[idx] = 3; // Stone
else data[idx] = 0; // Air
}
let distToTree = Math.sqrt((wx - treeX)**2 + (wz - treeZ)**2);
// Yggdrasil Trunk
if(distToTree < 14 - (h * 0.03)) {
for(let y = h; y < 115; y++) {
data[(lx * 16 + lz) * chunkHeight + y] = 4; // Wood
}
}
// Roots
if(distToTree >= 12 && distToTree < 40) {
let angle = Math.atan2(wz - treeZ, wx - treeX);
let rootFactor = Math.sin(angle * 7 + distToTree * 0.15);
if(rootFactor > 0.6) {
let rootHeight = h + (40 - distToTree) * 0.4;
for(let y = h; y < rootHeight; y++) {
data[(lx * 16 + lz) * chunkHeight + y] = 4;
}
}
}
// Canopy
if(distToTree < 55) {
for(let y = 80; y < 150; y++) {
let d3 = Math.sqrt((wx - treeX)**2 + (y - 115)**2 * 2.0 + (wz - treeZ)**2);
if(d3 < 45) {
let idx = (lx * 16 + lz) * chunkHeight + y;
if(data[idx] === 0) {
let leafNoise = perlin.noise(wx * 0.15, y * 0.15, wz * 0.15);
if(leafNoise > -0.2) {
if(Math.random() < 0.02) data[idx] = 6; // Magic fruit
else data[idx] = 5; // Leaves
}
}
}
}
}
// Floating Islands
if(distToTree > 60 && distToTree < 200) {
let islandNoise = perlin.noise(wx * 0.04, 0, wz * 0.04);
if(islandNoise > 0.72) {
let islandY = 60 + Math.sin(wx * 0.03 + wz * 0.03) * 25;
let islandThickness = (islandNoise - 0.72) * 25;
for(let y = Math.floor(islandY - islandThickness); y <= Math.floor(islandY); y++) {
let idx = (lx * 16 + lz) * chunkHeight + y;
if(y === Math.floor(islandY)) data[idx] = 1;
else data[idx] = 3;
}
// Shrine ruins
if(islandNoise > 0.85 && Math.random() < 0.08) {
for(let sy = 1; sy <= 3; sy++) data[(lx * 16 + lz) * chunkHeight + Math.floor(islandY + sy)] = 3;
data[(lx * 16 + lz) * chunkHeight + Math.floor(islandY + 4)] = 6;
}
}
}
}
}
return data;
}
// --- Data Access ---
function getBlock(x, y, z) {
if(y < 0 || y >= chunkHeight) return 0;
let cx = Math.floor(x / 16), cz = Math.floor(z / 16);
let key = cx + ',' + cz;
if(!chunks.has(key)) return 0;
let lx = x - cx * 16, lz = z - cz * 16;
return chunks.get(key)[(lx * 16 + lz) * chunkHeight + y];
}
function setBlock(x, y, z, v) {
if(y < 0 || y >= chunkHeight) return;
let cx = Math.floor(x / 16), cz = Math.floor(z / 16);
let key = cx + ',' + cz;
if(!chunks.has(key)) return;
let lx = x - cx * 16, lz = z - cz * 16;
chunks.get(key)[(lx * 16 + lz) * chunkHeight + y] = v;
buildChunkMesh(cx, cz);
if(lx === 0) buildChunkMesh(cx - 1, cz);
if(lx === 15) buildChunkMesh(cx + 1, cz);
if(lz === 0) buildChunkMesh(cx, cz - 1);
if(lz === 15) buildChunkMesh(cx, cz + 1);
}
// --- Chunk Meshing ---
function getBlockTexture(blockType, faceIndex) {
switch(blockType) {
case 1: return faceIndex === 2 ? 0 : (faceIndex === 3 ? 2 : 1);
case 2: return 2;
case 3: return 3;
case 4: return (faceIndex === 2 || faceIndex === 3) ? 5 : 4;
case 5: return 6;
case 6: return 7;
}
return 0;
}
const faces = [
[ [1,1,1], [1,0,1], [1,0,0], [1,1,0] ], // Right X+
[ [0,1,0], [0,0,0], [0,0,1], [0,1,1] ], // Left X-
[ [0,1,0], [0,1,1], [1,1,1], [1,1,0] ], // Top Y+
[ [0,0,1], [0,0,0], [1,0,0], [1,0,1] ], // Bottom Y-
[ [0,1,1], [0,0,1], [1,0,1], [1,1,1] ], // Front Z+
[ [1,1,0], [1,0,0], [0,0,0], [0,1,0] ] // Back Z-
];
const faceNormals = [ [1,0,0], [-1,0,0], [0,1,0], [0,-1,0], [0,0,1], [0,0,-1] ];
function buildChunkMesh(cx, cz) {
let key = cx + ',' + cz;
let data = chunks.get(key);
if(!data) return;
let positions = [], normals = [], uvs = [], indices = [];
let idxCounter = 0;
for(let lx = 0; lx < 16; lx++) {
for(let lz = 0; lz < 16; lz++) {
for(let y = 0; y < chunkHeight; y++) {
let block = data[(lx * 16 + lz) * chunkHeight + y];
if(block === 0) continue;
let wx = cx * 16 + lx, wz = cz * 16 + lz;
for(let f = 0; f < 6; f++) {
let nx = wx + faceNormals[f][0];
let ny = y + faceNormals[f][1];
let nz = wz + faceNormals[f][2];
let neighborBlock = getBlock(nx, ny, nz);
let drawFace = false;
if (neighborBlock === 0) drawFace = true;
else if (neighborBlock === 5 && block !== 5) drawFace = true;
else if (neighborBlock === 5 && block === 6) drawFace = true;
if(drawFace) {
let texIndex = getBlockTexture(block, f);
let tu = (texIndex % 4) * 0.25;
let ty = Math.floor(texIndex / 4);
let tv = 1.0 - (ty + 1) * 0.25;
let fVerts = faces[f];
for(let i=0; i<4; i++) {
positions.push(wx + fVerts[i][0], y + fVerts[i][1], wz + fVerts[i][2]);
normals.push(...faceNormals[f]);
let uu = tu + (i===2 || i===3 ? 0.25 : 0);
let vv = tv + (i===0 || i===3 ? 0.25 : 0);
uvs.push(uu, vv);
}
indices.push(idxCounter, idxCounter+1, idxCounter+2, idxCounter, idxCounter+2, idxCounter+3);
idxCounter += 4;
}
}
}
}
}
let geometry = new THREE.BufferGeometry();
geometry.setAttribute('position', new THREE.Float32BufferAttribute(positions, 3));
geometry.setAttribute('normal', new THREE.Float32BufferAttribute(normals, 3));
geometry.setAttribute('uv', new THREE.Float32BufferAttribute(uvs, 2));
geometry.setIndex(indices);
if(chunkMeshes.has(key)) {
scene.remove(chunkMeshes.get(key));
chunkMeshes.get(key).geometry.dispose();
}
if(positions.length > 0) {
let mesh = new THREE.Mesh(geometry, material);
scene.add(mesh);
chunkMeshes.set(key, mesh);
} else {
chunkMeshes.set(key, new THREE.Mesh()); // Empty placeholder to prevent rebuilds
}
}
function updateChunks(playerPos) {
let px = Math.floor(playerPos.x / 16), pz = Math.floor(playerPos.z / 16);
if(px !== lastChunkX || pz !== lastChunkZ) {
lastChunkX = px; lastChunkZ = pz;
for(let x = px - renderDistance; x <= px + renderDistance; x++) {
for(let z = pz - renderDistance; z <= pz + renderDistance; z++) {
let key = x + ',' + z;
if(!chunks.has(key)) chunks.set(key, generateChunk(x, z));
}
}
for(let x = px - renderDistance; x <= px + renderDistance; x++) {
for(let z = pz - renderDistance; z <= pz + renderDistance; z++) {
let key = x + ',' + z;
if(!chunkMeshes.has(key)) buildChunkMesh(x, z);
}
}
let toDelete = [];
for(let [key, mesh] of chunkMeshes) {
let [cx, cz] = key.split(',').map(Number);
if(Math.abs(cx - px) > renderDistance + 1 || Math.abs(cz - pz) > renderDistance + 1) {
scene.remove(mesh);
if(mesh.geometry) mesh.geometry.dispose();
toDelete.push(key);
}
}
for(let key of toDelete) chunkMeshes.delete(key);
}
}
// --- Interaction & UI ---
document.getElementById('start-btn').addEventListener('click', () => controls.lock());
controls.addEventListener('lock', () => document.getElementById('instructions').style.display = 'none');
controls.addEventListener('unlock', () => document.getElementById('instructions').style.display = 'flex');
let moveForward = false, moveBackward = false, moveLeft = false, moveRight = false, canJump = false;
document.addEventListener('keydown', (e) => {
switch (e.code) {
case 'ArrowUp': case 'KeyW': moveForward = true; break;
case 'ArrowLeft': case 'KeyA': moveLeft = true; break;
case 'ArrowDown': case 'KeyS': moveBackward = true; break;
case 'ArrowRight': case 'KeyD': moveRight = true; break;
case 'Space': if(canJump) { velocity.y = 12; canJump = false; } break;
case 'Digit1': setBlockType(1); break;
case 'Digit2': setBlockType(2); break;
case 'Digit3': setBlockType(3); break;
case 'Digit4': setBlockType(4); break;
case 'Digit5': setBlockType(5); break;
case 'Digit6': setBlockType(6); break;
}
});
document.addEventListener('keyup', (e) => {
switch (e.code) {
case 'ArrowUp': case 'KeyW': moveForward = false; break;
case 'ArrowLeft': case 'KeyA': moveLeft = false; break;
case 'ArrowDown': case 'KeyS': moveBackward = false; break;
case 'ArrowRight': case 'KeyD': moveRight = false; break;
}
});
function setBlockType(type) {
currentBlockType = type;
document.querySelectorAll('.slot').forEach(el => el.classList.remove('selected'));
document.getElementById('slot' + type).classList.add('selected');
}
const raycaster = new THREE.Raycaster();
const center = new THREE.Vector2(0, 0);
document.addEventListener('mousedown', (e) => {
if(!controls.isLocked) return;
raycaster.setFromCamera(center, camera);
let validMeshes = Array.from(chunkMeshes.values()).filter(m => m.geometry);
let intersects = raycaster.intersectObjects(validMeshes);
if(intersects.length > 0 && intersects[0].distance < 8) {
let intersect = intersects[0];
let normal = intersect.face.normal;
if(e.button === 0) { // Break
let bx = Math.floor(intersect.point.x - normal.x * 0.5);
let by = Math.floor(intersect.point.y - normal.y * 0.5);
let bz = Math.floor(intersect.point.z - normal.z * 0.5);
if(by > 0) setBlock(bx, by, bz, 0);
} else if(e.button === 2) { // Place
let bx = Math.floor(intersect.point.x + normal.x * 0.5);
let by = Math.floor(intersect.point.y + normal.y * 0.5);
let bz = Math.floor(intersect.point.z + normal.z * 0.5);
let cx = Math.floor(camera.position.x), cz = Math.floor(camera.position.z);
let cy = Math.floor(camera.position.y), cyF = Math.floor(camera.position.y - 1.6);
if(bx === cx && bz === cz && (by === cy || by === cyF)) return; // Prevent placing in player
if(by > 0 && by < chunkHeight - 1) setBlock(bx, by, bz, currentBlockType);
}
}
});
document.addEventListener('contextmenu', e => e.preventDefault());
// --- Physics & Loop ---
let velocity = new THREE.Vector3();
let direction = new THREE.Vector3();
const clock = new THREE.Clock();
const playerHeight = 1.6;
function checkCollision(pos) {
let x0 = Math.floor(pos.x - 0.3), x1 = Math.floor(pos.x + 0.3);
let z0 = Math.floor(pos.z - 0.3), z1 = Math.floor(pos.z + 0.3);
let y0 = Math.floor(pos.y - playerHeight), y1 = Math.floor(pos.y + 0.2);
for(let x = x0; x <= x1; x++) {
for(let y = y0; y <= y1; y++) {
for(let z = z0; z <= z1; z++) {
if(getBlock(x, y, z) !== 0) return true;
}
}
}
return false;
}
// Initial Spawn
camera.position.set(0, 100, 70); // Spawn in front of the tree
updateChunks(camera.position);
let startY = 150;
while(getBlock(0, startY, 70) === 0 && startY > 0) startY--;
camera.position.set(0, startY + playerHeight + 1, 70);
function animate() {
requestAnimationFrame(animate);
if (controls.isLocked) {
let delta = Math.min(clock.getDelta(), 0.1);
velocity.x -= velocity.x * 10.0 * delta;
velocity.z -= velocity.z * 10.0 * delta;
velocity.y -= 35.0 * delta; // Gravity
direction.z = Number(moveForward) - Number(moveBackward);
direction.x = Number(moveRight) - Number(moveLeft);
direction.normalize();
let speed = 50.0;
if (moveForward || moveBackward) velocity.z -= direction.z * speed * delta;
if (moveLeft || moveRight) velocity.x -= direction.x * speed * delta;
let cObj = controls.getObject();
let prevPos = cObj.position.clone();
controls.moveRight(-velocity.x * delta);
controls.moveForward(-velocity.z * delta);
if(checkCollision(cObj.position)) {
let tx = cObj.position.x, tz = cObj.position.z;
cObj.position.set(tx, prevPos.y, prevPos.z);
if(checkCollision(cObj.position)) { cObj.position.x = prevPos.x; velocity.x = 0; }
cObj.position.set(cObj.position.x, prevPos.y, tz);
if(checkCollision(cObj.position)) { cObj.position.z = prevPos.z; velocity.z = 0; }
}
cObj.position.y += velocity.y * delta;
if(checkCollision(cObj.position)) {
if(velocity.y < 0) {
canJump = true;
cObj.position.y = Math.floor(cObj.position.y - playerHeight) + 1 + playerHeight + 0.001;
} else {
cObj.position.y = Math.floor(cObj.position.y + 0.2) - 0.201;
}
velocity.y = 0;
} else {
canJump = false;
}
updateChunks(cObj.position);
// Update Outline
raycaster.setFromCamera(center, camera);
let validMeshes = Array.from(chunkMeshes.values()).filter(m => m.geometry);
let intersects = raycaster.intersectObjects(validMeshes);
if(intersects.length > 0 && intersects[0].distance < 8) {
let pt = intersects[0].point, nm = intersects[0].face.normal;
outlineMesh.position.set(
Math.floor(pt.x - nm.x * 0.5) + 0.5,
Math.floor(pt.y - nm.y * 0.5) + 0.5,
Math.floor(pt.z - nm.z * 0.5) + 0.5
);
outlineMesh.visible = true;
} else {
outlineMesh.visible = false;
}
} else {
clock.getDelta();
}
renderer.render(scene, camera);
}
window.addEventListener('resize', () => {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
});
animate();
</script>
</body>
</html>
Whitespace-only changes.
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,670 @@
<svg viewBox="0 0 800 600" xmlns="http://www.w3.org/2000/svg" width="100%" height="100%">
<defs>
<!-- Sky background -->
<linearGradient id="skyGrad" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#0b132b" />
<stop offset="25%" stop-color="#1c2541" />
<stop offset="50%" stop-color="#3a225d" />
<stop offset="70%" stop-color="#8c2f39" />
<stop offset="85%" stop-color="#eb6424" />
<stop offset="100%" stop-color="#fbc02d" />
</linearGradient>
<!-- Sun glow -->
<radialGradient id="sunGrad" cx="0.5" cy="0.5" r="0.5">
<stop offset="0%" stop-color="#ffffff" />
<stop offset="20%" stop-color="#fff59d" />
<stop offset="55%" stop-color="#ff9100" stop-opacity="0.8" />
<stop offset="100%" stop-color="#ff3d00" stop-opacity="0" />
</radialGradient>
<!-- Cloud gradient -->
<linearGradient id="cloudGrad" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#ffb74d" stop-opacity="0.45" />
<stop offset="50%" stop-color="#e040fb" stop-opacity="0.3" />
<stop offset="100%" stop-color="#4a148c" stop-opacity="0.05" />
</linearGradient>
<!-- Ocean gradient -->
<linearGradient id="seaGrad" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#170c2a" />
<stop offset="20%" stop-color="#161f4c" />
<stop offset="55%" stop-color="#02476b" />
<stop offset="85%" stop-color="#006c84" />
<stop offset="100%" stop-color="#058085" />
</linearGradient>
<!-- Sea wave highlight -->
<linearGradient id="seaHighlight" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#ffe082" stop-opacity="0.8" />
<stop offset="50%" stop-color="#ff8f00" stop-opacity="0.4" />
<stop offset="100%" stop-color="#008080" stop-opacity="0" />
</linearGradient>
<!-- Sand gradient -->
<linearGradient id="sandGrad" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#fed766" />
<stop offset="35%" stop-color="#f4b251" />
<stop offset="75%" stop-color="#d07c34" />
<stop offset="100%" stop-color="#803e12" />
</linearGradient>
<!-- Wet sand reflection -->
<linearGradient id="wetSand" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#45112e" stop-opacity="0.75" />
<stop offset="40%" stop-color="#5a2c22" stop-opacity="0.6" />
<stop offset="100%" stop-color="#240c02" stop-opacity="0.9" />
</linearGradient>
<!-- Chrome metallic reflection -->
<linearGradient id="chromeGrad" x1="0" y1="0" x2="1" y2="1">
<stop offset="0%" stop-color="#cbd5e1" />
<stop offset="20%" stop-color="#f8fafc" />
<stop offset="40%" stop-color="#94a3b8" />
<stop offset="45%" stop-color="#475569" />
<stop offset="55%" stop-color="#e2e8f0" />
<stop offset="80%" stop-color="#f8fafc" />
<stop offset="100%" stop-color="#334155" />
</linearGradient>
<!-- Cruiser bike frame teal -->
<linearGradient id="frameGrad" x1="0" y1="0" x2="1" y2="0">
<stop offset="0%" stop-color="#26c6da" />
<stop offset="50%" stop-color="#00acc1" />
<stop offset="100%" stop-color="#006064" />
</linearGradient>
<!-- Tire gradient -->
<radialGradient id="tireGrad" cx="0.5" cy="0.5" r="0.5" fx="0.4" fy="0.4">
<stop offset="0%" stop-color="#ffffff" />
<stop offset="75%" stop-color="#f5f5f4" />
<stop offset="90%" stop-color="#e7e5e4" />
<stop offset="100%" stop-color="#b4b2af" />
</radialGradient>
<!-- Leather brown -->
<linearGradient id="leatherGrad" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#a1887f" />
<stop offset="40%" stop-color="#6d4c41" />
<stop offset="100%" stop-color="#3e2723" />
</linearGradient>
<!-- Pelican feathers -->
<radialGradient id="pelicanFeather" cx="0.3" cy="0.3" r="0.7">
<stop offset="0%" stop-color="#ffffff" />
<stop offset="70%" stop-color="#f1f5f9" />
<stop offset="90%" stop-color="#cbd5e1" />
<stop offset="100%" stop-color="#94a3b8" />
</radialGradient>
<!-- Pelican beak -->
<linearGradient id="beakGrad" x1="0" y1="0" x2="1" y2="0.3">
<stop offset="0%" stop-color="#ffe082" />
<stop offset="40%" stop-color="#ffb300" />
<stop offset="80%" stop-color="#ff7043" />
<stop offset="100%" stop-color="#d84315" />
</linearGradient>
<!-- Pelican pouch (semi-translucent) -->
<linearGradient id="pouchGrad" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#ffe082" stop-opacity="0.9" />
<stop offset="45%" stop-color="#ffb300" stop-opacity="0.85" />
<stop offset="80%" stop-color="#ff7043" stop-opacity="0.75" />
<stop offset="100%" stop-color="#d84315" stop-opacity="0.65" />
</linearGradient>
<!-- Drop shadows -->
<filter id="shadow" x="-10%" y="-10%" width="120%" height="120%">
<feDropShadow dx="3" dy="5" stdDeviation="4" flood-color="#090514" flood-opacity="0.35" />
</filter>
<filter id="bikeShadow" x="-10%" y="-10%" width="120%" height="120%">
<feGaussianBlur stdDeviation="6" result="blur" />
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.5 0"/>
</filter>
<filter id="sunGlow" x="-50%" y="-50%" width="200%" height="200%">
<feGaussianBlur stdDeviation="15" result="blur" />
<feMerge>
<feMergeNode in="blur" />
<feMergeNode in="SourceGraphic" />
</feMerge>
</filter>
<!-- Soft ambient shadow gradient -->
<radialGradient id="groundShadow" cx="50%" cy="50%" r="50%">
<stop offset="0%" stop-color="#140602" stop-opacity="0.7" />
<stop offset="40%" stop-color="#140602" stop-opacity="0.4" />
<stop offset="100%" stop-color="#140602" stop-opacity="0" />
</radialGradient>
</defs>
<!-- ==================== BACKGROUND ==================== -->
<!-- Sky -->
<rect width="800" height="600" fill="url(#skyGrad)" />
<!-- Sun -->
<circle cx="560" cy="270" r="140" fill="url(#sunGrad)" filter="url(#sunGlow)" opacity="0.85" />
<circle cx="560" cy="270" r="50" fill="#ffffff" filter="url(#sunGlow)" opacity="0.9" />
<!-- Sky Clouds -->
<!-- Distant thin cloud -->
<path d="M 100,140 C 180,120 280,130 350,150 C 370,120 440,110 490,130 C 530,115 590,125 610,145 C 670,140 720,150 750,165 L 750,180 L 100,180 Z" fill="url(#cloudGrad)" opacity="0.3" />
<!-- Vibrant midground cloud -->
<path d="M -20,230 C 30,205 90,200 130,220 C 170,195 240,190 290,215 C 330,200 390,210 420,235 C 470,225 520,235 550,255 L 550,280 L -20,280 Z" fill="url(#cloudGrad)" opacity="0.4" />
<!-- Seagulls in the distance -->
<g stroke="#ffb74d" stroke-width="1.5" fill="none" opacity="0.7">
<path d="M 120,95 Q 128,88 135,95 Q 142,88 150,95" />
<path d="M 165,110 Q 171,105 177,110 Q 183,105 189,110" />
<path d="M 220,100 Q 225,96 230,100 Q 235,96 240,100" opacity="0.5" transform="scale(0.8) translate(50, 20)" />
</g>
<!-- ==================== THE OCEAN ==================== -->
<!-- Distant ocean back -->
<rect x="0" y="270" width="800" height="130" fill="url(#seaGrad)" />
<!-- Sun reflection on water -->
<ellipse cx="560" cy="335" rx="160" ry="60" fill="url(#seaHighlight)" style="mix-blend-mode: color-dodge;" opacity="0.7" />
<!-- Horizon glare strip -->
<line x1="0" y1="270" x2="800" y2="270" stroke="#ffeb3b" stroke-width="2" opacity="0.6" />
<!-- Wave horizontal streaks (shimmer) -->
<g stroke="#ffd54f" stroke-width="1" opacity="0.8">
<line x1="420" y1="285" x2="680" y2="285" stroke-width="1.5" />
<line x1="380" y1="295" x2="720" y2="295" stroke-width="2" />
<line x1="480" y1="305" x2="660" y2="305" stroke-width="2.5" />
<line x1="350" y1="315" x2="750" y2="315" stroke-width="3" />
<line x1="450" y1="330" x2="700" y2="330" stroke-width="4" />
<line x1="280" y1="345" x2="780" y2="345" stroke-width="3.5" />
<line x1="400" y1="365" x2="740" y2="365" stroke-width="4" />
</g>
<!-- Wave lines (scrolling look) -->
<path d="M 0,330 C 150,320 250,345 400,335 C 550,325 650,345 800,335 L 800,400 L 0,400 Z" fill="#034f70" opacity="0.3" />
<path d="M 0,360 C 120,355 220,380 380,365 C 540,350 680,385 800,365 L 800,420 L 0,420 Z" fill="#005d7a" opacity="0.4" />
<!-- Foam details near shore -->
<path d="M 0,390 C 180,375 280,410 480,395 C 680,380 720,415 800,405" stroke="#e0f7fa" stroke-width="3" fill="none" opacity="0.6" />
<path d="M -10,405 C 150,395 310,425 490,410 C 670,395 730,430 810,415" stroke="#ffffff" stroke-width="4" stroke-dasharray="10 5 15 8" fill="none" opacity="0.8" />
<!-- ==================== THE BEACH SHORE ==================== -->
<!-- Wet Sand Area -->
<path d="M -10,400 Q 250,380 500,430 T 810,410 L 810,600 L -10,600 Z" fill="url(#wetSand)" />
<!-- Glowing Edge of Tide (Water foam on sand) -->
<path d="M -10,402 Q 250,382 500,432 T 810,412" stroke="#ffffff" stroke-width="6" fill="none" filter="url(#sunGlow)" opacity="0.8" />
<path d="M -10,402 Q 250,382 500,432 T 810,412" stroke="#ffe082" stroke-width="2" fill="none" opacity="0.9" />
<!-- Dry Sand Dune (Foreground) -->
<path d="M -10,455 Q 180,430 380,480 T 810,465 L 810,600 L -10,600 Z" fill="url(#sandGrad)" />
<!-- Ambient Shadows under the bicycle wheels -->
<!-- Rear wheel shadow -->
<ellipse cx="230" cy="525" rx="85" ry="18" fill="url(#groundShadow)" />
<!-- Front wheel shadow -->
<ellipse cx="550" cy="525" rx="85" ry="18" fill="url(#groundShadow)" />
<!-- Center stand/pedal shadow -->
<ellipse cx="380" cy="528" rx="140" ry="22" fill="url(#groundShadow)" opacity="0.8" />
<!-- ==================== BICYCLE REFLECTION (ON WET SAND) ==================== -->
<!-- Highly stylized, low-opacity, slightly blurred vertical reflection -->
<g transform="translate(0, 1050) scale(1, -1)" opacity="0.12" filter="url(#bikeShadow)">
<!-- Back wheel reflection -->
<circle cx="230" cy="525" r="70" fill="none" stroke="#ffe082" stroke-width="12" />
<!-- Front wheel reflection -->
<circle cx="550" cy="525" r="70" fill="none" stroke="#ffe082" stroke-width="12" />
<!-- Frame reflection -->
<path d="M 230,525 L 360,525 L 330,390 L 230,525 L 310,320 M 330,390 L 485,320 L 550,525 M 310,320 L 485,320" stroke="#00acc1" stroke-width="10" fill="none" />
<!-- Pelican body reflection outline -->
<ellipse cx="330" cy="270" rx="65" ry="50" fill="#ffffff" />
<path d="M 330,220 C 350,160 410,140 430,120" stroke="#ffb300" stroke-width="18" stroke-linecap="round" fill="none" />
</g>
<!-- ==================== THE BICYCLE ==================== -->
<g id="bicycle">
<!-- REAR WHEEL -->
<g id="rear-wheel">
<!-- Outer Tire (Cream White) -->
<circle cx="230" cy="450" r="72" fill="none" stroke="#eae6df" stroke-width="15" filter="url(#shadow)" />
<!-- Tire Tread Grooves -->
<circle cx="230" cy="450" r="78" fill="none" stroke="#d6d1ca" stroke-width="1" stroke-dasharray="4 8" />
<circle cx="230" cy="450" r="65" fill="none" stroke="#d2cbc4" stroke-width="1" />
<!-- Rim (Chrome) -->
<circle cx="230" cy="450" r="64" fill="none" stroke="url(#chromeGrad)" stroke-width="5" />
<circle cx="230" cy="450" r="61" fill="none" stroke="#334155" stroke-width="1" />
<!-- Spokes (Thin shiny wires) -->
<g stroke="#eaeaea" stroke-width="0.75" opacity="0.85">
<line x1="230" y1="450" x2="166" y2="450" /> <line x1="230" y1="450" x2="294" y2="450" />
<line x1="230" y1="450" x2="230" y2="386" /> <line x1="230" y1="450" x2="230" y2="514" />
<line x1="230" y1="450" x2="185" y2="405" /> <line x1="230" y1="450" x2="275" y2="495" />
<line x1="230" y1="450" x2="185" y2="495" /> <line x1="230" y1="450" x2="275" y2="405" />
<line x1="230" y1="450" x2="201" y2="392" /> <line x1="230" y1="450" x2="259" y2="508" />
<line x1="230" y1="450" x2="259" y2="392" /> <line x1="230" y1="450" x2="201" y2="508" />
<line x1="230" y1="450" x2="169" y2="423" /> <line x1="230" y1="450" x2="291" y2="477" />
<line x1="230" y1="450" x2="169" y2="477" /> <line x1="230" y1="450" x2="291" y2="423" />
</g>
<!-- Wheel Hub -->
<circle cx="230" cy="450" r="10" fill="url(#chromeGrad)" stroke="#475569" stroke-width="1" />
</g>
<!-- FRONT WHEEL -->
<g id="front-wheel">
<!-- Outer Tire (Cream White) -->
<circle cx="550" cy="450" r="72" fill="none" stroke="#eae6df" stroke-width="15" filter="url(#shadow)" />
<!-- Tire Tread Grooves -->
<circle cx="550" cy="450" r="78" fill="none" stroke="#d6d1ca" stroke-width="1" stroke-dasharray="4 8" />
<circle cx="550" cy="450" r="65" fill="none" stroke="#d2cbc4" stroke-width="1" />
<!-- Rim (Chrome) -->
<circle cx="550" cy="450" r="64" fill="none" stroke="url(#chromeGrad)" stroke-width="5" />
<circle cx="550" cy="450" r="61" fill="none" stroke="#334155" stroke-width="1" />
<!-- Spokes -->
<g stroke="#eaeaea" stroke-width="0.75" opacity="0.85">
<line x1="550" y1="450" x2="486" y2="450" /> <line x1="550" y1="450" x2="614" y2="450" />
<line x1="550" y1="450" x2="550" y2="386" /> <line x1="550" y1="450" x2="550" y2="514" />
<line x1="550" y1="450" x2="505" y2="405" /> <line x1="550" y1="450" x2="595" y2="495" />
<line x1="550" y1="450" x2="505" y2="495" /> <line x1="550" y1="450" x2="595" y2="405" />
<line x1="550" y1="450" x2="521" y2="392" /> <line x1="550" y1="450" x2="579" y2="508" />
<line x1="550" y1="450" x2="579" y2="392" /> <line x1="550" y1="450" x2="521" y2="508" />
<line x1="550" y1="450" x2="489" y2="423" /> <line x1="550" y1="450" x2="611" y2="477" />
<line x1="550" y1="450" x2="489" y2="477" /> <line x1="550" y1="450" x2="611" y2="423" />
</g>
<!-- Wheel Hub -->
<circle cx="550" cy="450" r="10" fill="url(#chromeGrad)" stroke="#475569" stroke-width="1" />
</g>
<!-- CRANKSET, CHAIN & CHAINSTAYS -->
<!-- Rear Fork / Chain Stay (behind chain) -->
<line x1="230" y1="450" x2="360" y2="450" stroke="url(#chromeGrad)" stroke-width="6" stroke-linecap="round" />
<!-- Rear Fork / Seat Stay (behind wheel) -->
<line x1="230" y1="450" x2="315" y2="320" stroke="url(#chromeGrad)" stroke-width="5" stroke-linecap="round" />
<!-- Bicycle Chain Loop -->
<path d="M 230,450 C 270,432 320,432 360,432 C 378,432 382,450 380,458 C 380,466 374,470 360,468 C 320,468 270,468 230,450 Z" fill="none" stroke="#475569" stroke-width="3" stroke-linejoin="round" />
<!-- Chain Details (dots/links) -->
<path d="M 230,450 C 270,432 320,432 360,432 M 380,458 C 380,466 374,470 360,468 C 320,468 270,468 230,450" fill="none" stroke="#94a3b8" stroke-width="2.5" stroke-dasharray="3 3" />
<!-- Large Front Chainring / Sprocket -->
<circle cx="360" cy="450" r="24" fill="url(#chromeGrad)" stroke="#334155" stroke-width="1.5" />
<!-- Chainring Cutouts -->
<g fill="#16151d" opacity="0.6">
<circle cx="360" cy="450" r="17" fill="none" stroke="#16151d" stroke-width="2" stroke-dasharray="6 4" />
</g>
<!-- BIKE FRAME (Cruiser Style Curved Tubes) -->
<!-- Seat Tube (straight) -->
<line x1="315" y1="320" x2="360" y2="450" stroke="url(#frameGrad)" stroke-width="10" stroke-linecap="round" />
<!-- Curved Top Tube (Cruiser signature curve) -->
<path d="M 315,335 C 370,335 440,305 485,305" fill="none" stroke="url(#frameGrad)" stroke-width="10" stroke-linecap="round" filter="url(#shadow)" />
<!-- Second Parallel Thin Curved Tube -->
<path d="M 315,360 C 370,360 430,335 482,330" fill="none" stroke="url(#frameGrad)" stroke-width="6" stroke-linecap="round" />
<!-- Down Tube (Curved down) -->
<path d="M 315,350 C 345,410 350,440 360,450" fill="none" stroke="url(#frameGrad)" stroke-width="11" stroke-linecap="round" />
<path d="M 360,450 C 400,450 450,420 485,310" fill="none" stroke="url(#frameGrad)" stroke-width="11" stroke-linecap="round" />
<!-- Front Fork (Chrome) -->
<g id="front-fork">
<line x1="485" y1="300" x2="550" y2="450" stroke="url(#chromeGrad)" stroke-width="9" stroke-linecap="round" filter="url(#shadow)" />
<line x1="485" y1="275" x2="485" y2="300" stroke="url(#chromeGrad)" stroke-width="12" />
<circle cx="485" cy="300" r="8" fill="#334155" />
</g>
<!-- Fenders (Retro mudguards - Chrome) -->
<!-- Rear Fender -->
<path d="M 152,450 C 152,380 200,345 270,335" fill="none" stroke="url(#chromeGrad)" stroke-width="8" stroke-linecap="round" filter="url(#shadow)" />
<line x1="158" y1="446" x2="230" y2="450" stroke="#475569" stroke-width="2.5" />
<line x1="210" y1="375" x2="230" y2="450" stroke="#475569" stroke-width="2.5" />
<!-- Front Fender -->
<path d="M 488,405 C 505,385 540,373 585,390 C 605,405 615,425 618,450" fill="none" stroke="url(#chromeGrad)" stroke-width="8" stroke-linecap="round" filter="url(#shadow)" />
<line x1="550" y1="450" x2="605" y2="408" stroke="#475569" stroke-width="2.5" />
<!-- HANDLEBARS & STEM -->
<g id="handlebars">
<!-- Handlebar Stem -->
<line x1="485" y1="275" x2="480" y2="225" stroke="url(#chromeGrad)" stroke-width="8" />
<!-- Handlebar curve facing backward -->
<path d="M 480,225 C 475,210 455,210 435,225 C 425,232 420,242 415,255" fill="none" stroke="url(#chromeGrad)" stroke-width="7" stroke-linecap="round" />
<!-- Handlebar Grip (Leather) -->
<path d="M 420,245 L 413,258" stroke="url(#leatherGrad)" stroke-width="10" stroke-linecap="round" />
<circle cx="413" cy="258" r="5" fill="#3e2723" />
<!-- Far handlebar side (slightly visible) -->
<path d="M 480,225 C 488,215 503,215 515,223" fill="none" stroke="#94a3b8" stroke-width="5" stroke-linecap="round" opacity="0.7" />
</g>
<!-- RETRO FRONT HEADLIGHT -->
<g id="headlight">
<path d="M 480,245 L 500,245 C 505,245 512,250 512,258 C 512,266 505,271 500,271 L 480,271 Z" fill="url(#chromeGrad)" stroke="#334155" stroke-width="1" />
<!-- Golden Lens -->
<path d="M 512,250 C 515,252 515,264 512,266 Z" fill="#fff59d" stroke="#fbc02d" stroke-width="1" />
<!-- Light beam glow -->
<polygon points="513,252 640,220 640,320 513,264" fill="url(#sunGrad)" opacity="0.25" style="mix-blend-mode: screen;" />
<!-- Mounting bracket -->
<line x1="485" y1="265" x2="482" y2="255" stroke="#475569" stroke-width="3" />
</g>
<!-- SADDLE / SEAT -->
<g id="saddle" filter="url(#shadow)">
<!-- Seat Post -->
<line x1="315" y1="320" x2="315" y2="295" stroke="url(#chromeGrad)" stroke-width="7" />
<!-- Seat Springs -->
<circle cx="310" cy="295" r="5" fill="none" stroke="#475569" stroke-width="2.5" />
<circle cx="320" cy="295" r="5" fill="none" stroke="#475569" stroke-width="2.5" />
<!-- Cushion (Leather) -->
<path d="M 292,288 C 292,275 310,270 330,270 C 345,270 355,278 350,285 C 345,292 315,294 292,288 Z" fill="url(#leatherGrad)" stroke="#3e2723" stroke-width="1" />
</g>
<!-- FRONT WICKER BASKET & FLOWERS -->
<g id="basket" filter="url(#shadow)">
<!-- Leather mounting straps -->
<rect x="474" y="240" width="4" height="12" rx="1" fill="#5c4033" />
<rect x="492" y="242" width="4" height="12" rx="1" fill="#5c4033" opacity="0.8" />
<!-- Basket body -->
<path d="M 495,250 L 545,255 C 550,255 553,260 550,270 L 538,312 C 536,318 531,320 525,320 L 485,315 C 480,315 478,310 480,302 L 488,255 C 489,250 492,250 495,250 Z" fill="#d7ccc8" stroke="#a1887f" stroke-width="1.5" />
<!-- Woven texture lines -->
<!-- Diagonals -->
<path d="M 488,255 L 525,320 M 498,251 L 532,310 M 510,252 L 538,300 M 525,253 L 542,285 M 484,275 L 510,318 M 481,295 L 495,317" stroke="#b09e99" stroke-width="1" />
<path d="M 545,255 L 485,315 M 535,254 L 481,302 M 520,253 L 480,288 M 505,251 L 480,272 M 548,272 L 505,318 M 550,290 L 522,320" stroke="#b09e99" stroke-width="1" />
<!-- Rim -->
<path d="M 488,252 Q 515,252 546,255" fill="none" stroke="#a1887f" stroke-width="4.5" stroke-linecap="round" />
<!-- Flowers peeking out of the basket -->
<!-- Red Hibiscus -->
<g transform="translate(492, 245) scale(0.6)">
<circle cx="0" cy="0" r="12" fill="#e91e63" />
<path d="M 0,0 C -15,-15 -25,-5 -15,10 C -5,25 15,25 20,5 C 25,-15 15,-25 0,-20 C -10,-15 -15,-5 0,0 Z" fill="#ff1744" opacity="0.9" />
<circle cx="0" cy="0" r="4" fill="#ffeb3b" />
<line x1="0" y1="0" x2="15" y2="-15" stroke="#ffeb3b" stroke-width="2" />
<circle cx="15" cy="-15" r="2" fill="#ffeb3b" />
</g>
<!-- Green Leaves -->
<path d="M 522,250 Q 535,235 530,225 Q 515,235 522,250 Z" fill="#2e7d32" />
<path d="M 484,255 Q 470,245 475,235 Q 490,245 484,255 Z" fill="#1b5e20" />
<!-- White Plumeria -->
<g transform="translate(515, 246) scale(0.5)">
<ellipse cx="0" cy="-10" rx="7" ry="12" fill="#ffffff" transform="rotate(0)" />
<ellipse cx="10" cy="-5" rx="7" ry="12" fill="#ffffff" transform="rotate(72 10 -5)" />
<ellipse cx="8" cy="8" rx="7" ry="12" fill="#ffffff" transform="rotate(144 8 8)" />
<ellipse cx="-5" cy="8" rx="7" ry="12" fill="#ffffff" transform="rotate(216 -5 8)" />
<ellipse cx="-8" cy="-5" rx="7" ry="12" fill="#ffffff" transform="rotate(288 -8 -5)" />
<circle cx="0" cy="0" r="5" fill="#ffeb3b" />
</g>
<!-- A cute little silver fish peeking out -->
<g transform="translate(532, 248) rotate(-20) scale(0.6)">
<!-- Fish Body -->
<path d="M 0,0 C 15,-10 30,-5 40,5 C 30,10 15,10 0,0" fill="#cfd8dc" stroke="#78909c" stroke-width="1" />
<!-- Tail -->
<path d="M 38,3 L 48,-3 L 45,5 L 48,12 L 38,6 Z" fill="#b0bec5" stroke="#78909c" stroke-width="1" />
<!-- Eye -->
<circle cx="8" cy="1" r="2.5" fill="#ffffff" />
<circle cx="9" cy="1" r="1" fill="#000000" />
<!-- Smiling Mouth -->
<path d="M 3,4 Q 6,6 8,4" fill="none" stroke="#78909c" stroke-width="1" />
</g>
</g>
</g>
<!-- ==================== THE PELICAN RIDER ==================== -->
<g id="pelican">
<!-- BACK/FAR LEG & PEDAL (Drawn behind the body) -->
<g id="far-leg">
<!-- Far Hip/Thigh (highly shadowed) -->
<path d="M 320,315 C 310,345 320,380 345,395" fill="none" stroke="#e65100" stroke-width="12" stroke-linecap="round" opacity="0.6" />
<!-- Far Shin -->
<line x1="345" y1="395" x2="395" y2="470" stroke="#f57c00" stroke-width="8" stroke-linecap="round" opacity="0.7" />
<!-- Far Foot (on lower pedal) -->
<path d="M 390,468 C 390,468 410,480 422,480 C 428,480 425,472 415,468 Z" fill="#ef6c00" opacity="0.7" />
</g>
<!-- Far Pedal (Lower, extending forward-down) -->
<g id="far-pedal" opacity="0.8">
<!-- Pedal Crank Arm -->
<line x1="360" y1="450" x2="395" y2="475" stroke="url(#chromeGrad)" stroke-width="5.5" stroke-linecap="round" />
<!-- Pedal Block -->
<polygon points="385,471 410,471 405,481 380,481" fill="#1e293b" stroke="#475569" stroke-width="1" />
</g>
<!-- PELICAN MAIN BODY -->
<!-- Tail Feathers -->
<g id="tail-feathers">
<path d="M 230,270 C 220,285 225,305 242,315 C 255,310 262,295 265,280 Z" fill="#cbd5e1" stroke="#94a3b8" stroke-width="1" filter="url(#shadow)" />
<path d="M 235,285 C 228,295 232,310 248,312 C 252,305 258,292 258,285 Z" fill="#94a3b8" />
<!-- Layered fluff -->
<path d="M 245,260 C 235,270 238,288 252,295 C 260,288 268,272 265,262 Z" fill="#f1f5f9" />
</g>
<!-- Torso / Main Body -->
<path id="torso" d="M 260,275 C 250,230 310,210 355,230 C 385,245 400,285 385,325 C 365,365 285,360 260,275 Z" fill="url(#pelicanFeather)" filter="url(#shadow)" />
<!-- Soft Feather Texture Overlays on Torso -->
<g stroke="#e2e8f0" stroke-width="1.5" fill="none" opacity="0.8">
<path d="M 285,255 C 295,260 305,255 315,260" />
<path d="M 275,280 C 290,288 305,280 320,288" />
<path d="M 290,305 C 305,315 320,305 335,312" />
<path d="M 315,330 C 328,338 340,330 352,335" />
<!-- Dark feather tips near rear -->
<path d="M 258,278 C 265,285 272,280 280,285" stroke="#94a3b8" />
<path d="M 264,298 C 272,305 280,298 288,305" stroke="#94a3b8" />
</g>
<!-- WIND-BLOWN SCARF (Under the neck, blowing backwards) -->
<g id="scarf-back" filter="url(#shadow)">
<!-- Wave 1 (Longer tail) -->
<path d="M 295,232 C 220,225 180,185 130,195 C 120,197 122,212 135,212 C 185,212 215,245 290,252 Z" fill="#d32f2f" />
<!-- Red and White Stripes -->
<path d="M 255,241 L 248,227 M 235,234 L 228,220 M 215,225 L 208,211 M 195,216 L 188,202 M 175,208 L 168,195 M 155,201 L 148,197 M 138,199 L 134,196" stroke="#ffffff" stroke-width="7" />
<!-- Wave 2 (Shorter tail) -->
<path d="M 295,242 C 240,242 200,210 155,225 C 148,227 150,240 160,240 C 205,240 235,262 290,257 Z" fill="#c62828" />
<path d="M 260,251 L 252,242 M 240,249 L 232,240 M 220,246 L 212,237 M 200,243 L 192,234 M 180,239 L 172,231 M 164,236 L 158,230" stroke="#ffebee" stroke-width="5" />
<!-- Fringes at the end -->
<line x1="130" y1="195" x2="120" y2="192" stroke="#ffca28" stroke-width="2" />
<line x1="131" y1="200" x2="121" y2="198" stroke="#ffffff" stroke-width="2" />
<line x1="132" y1="205" x2="122" y2="204" stroke="#ffca28" stroke-width="2" />
<line x1="133" y1="210" x2="123" y2="210" stroke="#ffffff" stroke-width="2" />
</g>
<!-- S-CURVED FEATHERY NECK -->
<g id="neck">
<!-- Thick neck path -->
<path d="M 345,230 C 370,180 340,115 390,105 C 400,102 415,110 422,125" fill="none" stroke="url(#pelicanFeather)" stroke-width="36" stroke-linecap="round" filter="url(#shadow)" />
<!-- Shadow on neck curve -->
<path d="M 345,230 C 370,180 340,115 390,105" fill="none" stroke="#94a3b8" stroke-width="4" stroke-linecap="round" opacity="0.25" />
<!-- Feather fluff along the back of neck -->
<path d="M 353,190 Q 343,180 348,172" stroke="#cbd5e1" stroke-width="2" fill="none" />
<path d="M 358,165 Q 348,155 353,147" stroke="#cbd5e1" stroke-width="2" fill="none" />
<path d="M 368,140 Q 358,130 363,122" stroke="#cbd5e1" stroke-width="2" fill="none" />
</g>
<!-- FOREGROUND/NEAR LEG & PEDAL -->
<!-- Near Pedal (Upper, extending up-back) -->
<g id="near-pedal" filter="url(#shadow)">
<!-- Pedal Crank Arm -->
<line x1="360" y1="450" x2="330" y2="415" stroke="url(#chromeGrad)" stroke-width="7" stroke-linecap="round" />
<!-- Pedal Hub -->
<circle cx="330" cy="415" r="5" fill="#334155" />
<!-- Pedal Block -->
<polygon points="312,410 342,410 348,420 318,420" fill="#334155" stroke="#94a3b8" stroke-width="1" />
</g>
<g id="near-leg" filter="url(#shadow)">
<!-- Hip / Joint -->
<path d="M 325,325 C 300,350 295,370 325,395" fill="none" stroke="url(#beakGrad)" stroke-width="14" stroke-linecap="round" />
<!-- Thigh fluff shading -->
<path d="M 305,335 C 295,350 295,362 312,375" fill="none" stroke="#cbd5e1" stroke-width="10" stroke-linecap="round" />
<!-- Knee to Foot (Ankle) -->
<line x1="325" y1="395" x2="330" y2="415" stroke="#f57c00" stroke-width="9" stroke-linecap="round" />
<!-- Leg scales/wrinkles -->
<line x1="325" y1="400" x2="329" y2="401" stroke="#e65100" stroke-width="1.5" />
<line x1="326" y1="406" x2="330" y2="407" stroke="#e65100" stroke-width="1.5" />
<!-- Big Webbed Foot gripping pedal -->
<!-- Heel -->
<circle cx="330" cy="415" r="6" fill="#ef6c00" />
<!-- Three webbed toes overlapping pedal -->
<path d="M 330,415 C 322,415 310,405 298,408 C 308,416 322,418 330,417" fill="#ff9800" stroke="#e65100" stroke-width="1" />
<path d="M 330,415 C 320,422 305,422 295,422 C 308,425 324,422 330,418" fill="#ff9800" stroke="#e65100" stroke-width="1" />
<path d="M 330,415 C 325,428 312,435 302,435 C 314,432 325,425 330,418" fill="#ff9800" stroke="#e65100" stroke-width="1" />
<!-- Toe Claws -->
<circle cx="297" cy="408" r="1.5" fill="#eceff1" />
<circle cx="294" cy="422" r="1.5" fill="#eceff1" />
<circle cx="301" cy="435" r="1.5" fill="#eceff1" />
</g>
<!-- HEAD, EYE, BEAK, POUCH & SUNGLASSES -->
<g id="head-group" filter="url(#shadow)">
<!-- Head Base -->
<circle cx="415" cy="115" r="22" fill="url(#pelicanFeather)" />
<!-- Feather tuft / crest blowing back -->
<path d="M 398,102 C 385,92 372,98 360,95 C 372,105 385,108 395,108 Z" fill="#cbd5e1" />
<path d="M 394,112 C 380,105 365,112 352,110 C 366,118 380,118 393,116 Z" fill="#94a3b8" />
<path d="M 397,122 C 385,118 372,128 358,128 C 372,132 385,130 395,125 Z" fill="#cbd5e1" />
<!-- Cute Expressive Eye -->
<circle cx="418" cy="112" r="5.5" fill="#ffffff" />
<circle cx="419" cy="112" r="4" fill="#00acc1" /> <!-- Bright blue iris -->
<circle cx="420" cy="112" r="2.5" fill="#090d16" /> <!-- Pupil -->
<circle cx="421" cy="110" r="1" fill="#ffffff" /> <!-- Catchlight -->
<!-- Happy eyebrow -->
<path d="M 411,105 Q 418,101 425,106" fill="none" stroke="#475569" stroke-width="2" stroke-linecap="round" />
<!-- FOREGROUND WING (Holding the Handlebars!) -->
<!-- Wing Shoulder -->
<path d="M 310,240 C 330,240 355,255 360,275 C 362,295 330,325 310,320 Z" fill="url(#pelicanFeather)" />
<!-- Wing Feathers folding forward and down -->
<path d="M 345,265 C 375,275 410,285 432,266 C 438,260 442,252 445,248" fill="none" stroke="url(#pelicanFeather)" stroke-width="16" stroke-linecap="round" />
<!-- Wingtip feathers wrapping around the leather grip -->
<!-- Feather finger 1 -->
<path d="M 432,266 C 430,255 418,252 410,256" fill="none" stroke="#cbd5e1" stroke-width="7" stroke-linecap="round" />
<!-- Feather finger 2 (wrapping handlebar) -->
<path d="M 434,268 C 430,258 414,258 413,264" fill="none" stroke="#f1f5f9" stroke-width="6.5" stroke-linecap="round" />
<!-- Feather finger 3 -->
<path d="M 432,272 C 428,266 416,268 416,274" fill="none" stroke="#cbd5e1" stroke-width="6" stroke-linecap="round" />
<!-- Wing Layer lines for 3D effect -->
<path d="M 330,255 Q 355,270 380,275" fill="none" stroke="#cbd5e1" stroke-width="1.5" />
<path d="M 325,275 Q 350,290 375,292" fill="none" stroke="#cbd5e1" stroke-width="1.5" />
<path d="M 315,295 Q 340,305 365,302" fill="none" stroke="#94a3b8" stroke-width="1.5" opacity="0.6" />
<!-- THE BEAK (Upper bill) -->
<!-- Long, beautiful, curved upper beak -->
<path d="M 432,108 C 455,110 510,120 545,132 C 550,134 554,138 552,142 C 549,145 540,143 535,141 C 495,131 450,123 430,121 Z" fill="url(#beakGrad)" />
<!-- Beak tip hook -->
<path d="M 548,133 Q 555,138 550,143 Q 544,141 546,136 Z" fill="#bf360c" />
<!-- Nostril slit -->
<path d="M 445,115 Q 455,117 460,118" fill="none" stroke="#b71c1c" stroke-width="1" stroke-linecap="round" />
<!-- THE GIGANTIC THROAT POUCH -->
<!-- Drop down elastic pouch -->
<path d="M 430,121 C 430,121 432,185 450,195 C 470,205 515,180 535,141 C 490,131 450,123 430,121 Z" fill="url(#pouchGrad)" />
<!-- Pouch texture wrinkles -->
<path d="M 442,132 Q 448,168 456,182" fill="none" stroke="#ffb300" stroke-width="1" opacity="0.6" />
<path d="M 455,132 Q 465,165 478,178" fill="none" stroke="#ffb300" stroke-width="1" opacity="0.6" />
<path d="M 470,133 Q 482,160 500,165" fill="none" stroke="#ff8f00" stroke-width="1" opacity="0.5" />
<path d="M 490,134 Q 502,152 518,154" fill="none" stroke="#ff8f00" stroke-width="1" opacity="0.5" />
<!-- Lower jaw line supporting the pouch -->
<path d="M 430,121 C 430,121 432,185 450,195 C 470,205 515,180 535,141" fill="none" stroke="#e65100" stroke-width="2.5" />
<!-- SILHOUETTE OF A LITTLE FISH INSIDE THE POUCH (Easter Egg!) -->
<g opacity="0.22" transform="translate(452, 142) rotate(25) scale(0.85)">
<path d="M 0,0 C 10,-8 25,-4 32,4 C 24,8 12,8 0,0" fill="#02476b" />
<path d="M 30,2 L 38,-3 L 36,4 L 38,10 L 30,5 Z" fill="#02476b" />
<circle cx="6" cy="1" r="1.5" fill="#ffe082" />
</g>
<!-- Small bubbles inside pouch around the fish -->
<circle cx="475" cy="155" r="2" fill="#ffffff" opacity="0.3" />
<circle cx="482" cy="150" r="1" fill="#ffffff" opacity="0.3" />
<circle cx="468" cy="162" r="1.5" fill="#ffffff" opacity="0.2" />
<!-- COOL AVIATOR SUNGLASSES -->
<g id="sunglasses">
<!-- Lens facing us -->
<path d="M 412,112 C 412,106 426,106 428,114 C 428,122 414,124 412,112 Z" fill="#263238" stroke="#ffb300" stroke-width="1.5" />
<path d="M 412,112 C 412,106 426,106 428,114 C 428,122 414,124 412,112 Z" fill="url(#beakGrad)" opacity="0.4" style="mix-blend-mode: color-dodge;" />
<!-- Diagonal glass reflection -->
<polygon points="415,109 425,112 422,121 413,115" fill="#ffffff" opacity="0.25" />
<!-- Bridge -->
<rect x="427" y="111" width="5" height="1.5" fill="#ffb300" />
<!-- Second Lens (partially hidden/angled) -->
<path d="M 431,113 C 431,109 441,110 442,116 C 442,122 432,123 431,113 Z" fill="#263238" stroke="#ffb300" stroke-width="1.2" />
<path d="M 431,113 C 431,109 441,110 442,116 C 442,122 432,123 431,113 Z" fill="url(#beakGrad)" opacity="0.4" style="mix-blend-mode: color-dodge;" />
<polygon points="433,111 439,113 437,120 432,116" fill="#ffffff" opacity="0.2" />
<!-- Frame temples / arms going back -->
<path d="M 412,111 Q 403,110 400,114" fill="none" stroke="#ffb300" stroke-width="1.2" />
</g>
</g>
<!-- FOREGROUND STRIPED SCARF KNOT & WIND FLOW (Drawn in front of neck/shoulder) -->
<g id="scarf-front" filter="url(#shadow)">
<!-- Knot around neck -->
<path d="M 378,212 Q 388,210 398,220 Q 388,235 375,225 Z" fill="#d32f2f" />
<!-- Scarf wrap -->
<path d="M 364,195 C 378,195 396,204 398,218 C 392,230 374,224 358,210 Z" fill="#c62828" />
<!-- Stripes on wrap -->
<path d="M 368,195 Q 375,206 376,219 M 382,197 Q 388,208 390,222" stroke="#ffffff" stroke-width="4.5" fill="none" />
<!-- Little hanging knot tie -->
<path d="M 388,218 C 394,224 396,234 392,242 C 386,242 382,232 384,222 Z" fill="#b71c1c" />
<path d="M 391,223 C 396,228 398,235 395,241" stroke="#ffffff" stroke-width="3" fill="none" />
</g>
</g>
<!-- ==================== DYNAMIC FOREGROUND EFFECTS ==================== -->
<!-- Splashing wet sand droplets and dust kicked up by rear tire -->
<g fill="#ffe082" opacity="0.5">
<circle cx="152" cy="515" r="2.5" />
<circle cx="145" cy="505" r="1.5" />
<circle cx="138" cy="510" r="2" />
<circle cx="142" cy="522" r="1" />
<circle cx="160" cy="495" r="2" opacity="0.3" />
<circle cx="172" cy="490" r="1.5" opacity="0.3" />
</g>
<!-- Splashing water droplets and foam near the tires (riding through shallow water) -->
<g fill="#ffffff" opacity="0.75">
<path d="M 230,520 Q 215,505 195,502 Q 218,515 230,520 Z" />
<path d="M 235,520 Q 250,502 270,498 Q 252,514 235,520 Z" />
<path d="M 550,520 Q 532,508 515,504 Q 535,515 550,520 Z" />
<path d="M 555,520 Q 572,504 595,500 Q 575,515 555,520 Z" />
<!-- Droplets -->
<circle cx="198" cy="495" r="2" />
<circle cx="185" cy="501" r="1" />
<circle cx="210" cy="488" r="1.5" />
<circle cx="274" cy="492" r="2" />
<circle cx="285" cy="498" r="1" />
<circle cx="510" cy="498" r="1.5" />
<circle cx="498" cy="502" r="1" />
<circle cx="600" cy="494" r="2" />
<circle cx="612" cy="500" r="1" />
</g>
<!-- ==================== FOREGROUND FRAMING ==================== -->
<!-- Silhouetted beach grass blowing in the wind (Bottom right corner) -->
<g fill="none" stroke="#240c02" stroke-linecap="round" opacity="0.95" filter="url(#shadow)">
<!-- Sand hill bump -->
<path d="M 680,600 Q 740,540 810,530 L 810,600 Z" fill="url(#sandGrad)" stroke="none" />
<!-- Grass blades -->
<path d="M 720,560 Q 690,470 640,440" stroke-width="4.5" />
<path d="M 735,555 Q 710,450 660,420" stroke-width="5" />
<path d="M 750,550 Q 735,430 670,395" stroke-width="5.5" />
<path d="M 755,555 Q 755,460 710,425" stroke-width="4" />
<path d="M 770,540 Q 775,440 720,405" stroke-width="5" />
<path d="M 785,535 Q 805,420 755,385" stroke-width="6" />
<path d="M 790,540 Q 820,445 780,410" stroke-width="4.5" />
<path d="M 800,530 Q 840,450 810,420" stroke-width="3.5" />
<!-- Warm highlights on grass from the sunset -->
<path d="M 720,560 Q 690,470 640,440" stroke="#fbc02d" stroke-width="1.5" opacity="0.7" />
<path d="M 735,555 Q 710,450 660,420" stroke="#ff9100" stroke-width="1.5" opacity="0.7" />
<path d="M 750,550 Q 735,430 670,395" stroke="#fbc02d" stroke-width="2" opacity="0.8" />
<path d="M 770,540 Q 775,440 720,405" stroke="#ff9100" stroke-width="1.5" opacity="0.7" />
<path d="M 785,535 Q 805,420 755,385" stroke="#fbc02d" stroke-width="2.5" opacity="0.8" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 37 KiB