Rename Claude Code combination ids to the -cc suffix; refresh the Opus 4.8 mythos build
claude-fable-5-code -> claude-fable-5-cc, claude-opus-4-8 -> claude-opus-4-8-cc (the entry id doubles as the outputs/ folder name). The Opus mythos-craft artifact is a newer build of the same one-shot provenance. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
b42022e8ce
commit
8acf16262f
7 files changed
+780
-375
No files matched your search
@@ -1,375 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
|
||||
<title>方块秘境 · Voxel Mythos</title>
|
||||
<style>
|
||||
:root { --acid:#c8ff00; --night:#0c0e14; --ink:#e9f0ff; }
|
||||
* { margin:0; padding:0; box-sizing:border-box; }
|
||||
html, body { width:100%; height:100%; overflow:hidden; background:#0c0e14; }
|
||||
body { font-family: ui-monospace, "SFMono-Regular", Menlo, Consolas, monospace; color:var(--ink); -webkit-user-select:none; user-select:none; touch-action:none; }
|
||||
canvas { display:block; }
|
||||
#game { position:fixed; inset:0; }
|
||||
|
||||
/* Crosshair */
|
||||
#cross { position:fixed; left:50%; top:50%; width:22px; height:22px; transform:translate(-50%,-50%); pointer-events:none; z-index:20; mix-blend-mode:difference; opacity:.9; }
|
||||
#cross::before, #cross::after { content:""; position:absolute; background:#fff; }
|
||||
#cross::before { left:50%; top:0; width:2px; height:100%; transform:translateX(-50%); }
|
||||
#cross::after { top:50%; left:0; height:2px; width:100%; transform:translateY(-50%); }
|
||||
|
||||
/* HUD top-left */
|
||||
#hud { position:fixed; left:14px; top:12px; z-index:20; font-size:12px; line-height:1.55; text-shadow:0 1px 2px rgba(0,0,0,.8); pointer-events:none; }
|
||||
#hud b { color:var(--acid); font-weight:700; }
|
||||
#hud .dim { color:#9aa6c2; }
|
||||
|
||||
/* Hotbar */
|
||||
#hotbar { position:fixed; left:50%; bottom:18px; transform:translateX(-50%); z-index:20; display:flex; gap:6px; padding:6px; background:rgba(8,10,18,.55); border:1px solid rgba(255,255,255,.12); border-radius:12px; backdrop-filter:blur(8px); pointer-events:none; }
|
||||
.slot { position:relative; width:48px; height:48px; border-radius:8px; background:rgba(255,255,255,.04); border:2px solid rgba(255,255,255,.10); display:flex; align-items:center; justify-content:center; }
|
||||
.slot.active { border-color:var(--acid); box-shadow:0 0 0 2px rgba(200,255,0,.25), 0 6px 20px rgba(200,255,0,.18); transform:translateY(-3px); }
|
||||
.slot canvas { width:34px; height:34px; image-rendering:pixelated; border-radius:4px; }
|
||||
.slot .key { position:absolute; top:-9px; left:-6px; font-size:9px; color:#0c0e14; background:var(--acid); border-radius:5px; padding:0 4px; font-weight:800; }
|
||||
.slot .nm { position:absolute; bottom:-16px; left:50%; transform:translateX(-50%); font-size:9px; color:#cfd8ef; white-space:nowrap; opacity:0; transition:opacity .15s; }
|
||||
.slot.active .nm { opacity:1; }
|
||||
|
||||
/* Start / pause overlay */
|
||||
#veil { position:fixed; inset:0; z-index:40; display:flex; align-items:center; justify-content:center; flex-direction:column; gap:22px;
|
||||
background:radial-gradient(1200px 800px at 50% 20%, rgba(40,60,120,.35), rgba(8,10,18,.92) 60%), #06070c; text-align:center; transition:opacity .4s; }
|
||||
#veil.hide { opacity:0; pointer-events:none; }
|
||||
#veil .badge { font-size:11px; letter-spacing:.4em; color:var(--acid); text-transform:uppercase; }
|
||||
#veil h1 { font-size:clamp(34px, 7vw, 76px); line-height:.96; font-weight:800; letter-spacing:-.02em;
|
||||
background:linear-gradient(180deg,#fff, #9fb6ff 70%, #6f86ff); -webkit-background-clip:text; background-clip:text; color:transparent; text-shadow:0 0 60px rgba(120,150,255,.25); }
|
||||
#veil h1 small { display:block; font-size:.34em; letter-spacing:.5em; color:#7d8bb8; -webkit-text-fill-color:#7d8bb8; margin-top:10px; font-weight:600; }
|
||||
#veil .play { pointer-events:auto; cursor:pointer; font:inherit; font-weight:800; font-size:15px; letter-spacing:.18em; color:#0c0e14; background:var(--acid); border:none; padding:14px 34px; border-radius:999px; text-transform:uppercase; box-shadow:0 14px 40px rgba(200,255,0,.30); transition:transform .15s, box-shadow .15s; }
|
||||
#veil .play:hover { transform:translateY(-2px); box-shadow:0 18px 50px rgba(200,255,0,.45); }
|
||||
#veil .play:disabled { background:#39414f; color:#7d8bb8; box-shadow:none; cursor:default; }
|
||||
#veil .keys { display:flex; gap:18px; flex-wrap:wrap; justify-content:center; max-width:680px; font-size:12px; color:#aab4d0; }
|
||||
#veil .keys span b { color:#fff; background:rgba(255,255,255,.08); border:1px solid rgba(255,255,255,.16); border-radius:6px; padding:2px 7px; margin-right:5px; font-weight:700; }
|
||||
#veil .tip { font-size:11px; color:#6b769a; max-width:560px; line-height:1.7; }
|
||||
|
||||
#bar { position:fixed; left:50%; bottom:-2px; transform:translateX(-50%); width:min(420px,70vw); height:3px; background:rgba(255,255,255,.08); z-index:41; border-radius:2px; overflow:hidden; }
|
||||
#bar i { display:block; height:100%; width:0%; background:var(--acid); transition:width .2s; }
|
||||
|
||||
#flash { position:fixed; inset:0; z-index:15; pointer-events:none; background:#fff; opacity:0; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<canvas id="game"></canvas>
|
||||
<div id="cross"></div>
|
||||
<div id="flash"></div>
|
||||
|
||||
<div id="hud">
|
||||
<div><b>方块秘境</b> <span class="dim">Voxel Mythos</span></div>
|
||||
<div class="dim" id="stat">pos —</div>
|
||||
<div class="dim" id="biome"></div>
|
||||
<div class="dim" id="fps"></div>
|
||||
</div>
|
||||
|
||||
<div id="hotbar"></div>
|
||||
|
||||
<div id="veil">
|
||||
<div class="badge">A Hand-built Myth · 神话造物</div>
|
||||
<h1>方块秘境<small>VOXEL MYTHOS</small></h1>
|
||||
<button class="play" id="play" disabled>生成世界中…</button>
|
||||
<div class="keys">
|
||||
<span><b>W A S D</b>移动</span>
|
||||
<span><b>SPACE</b>跳跃</span>
|
||||
<span><b>SHIFT</b>疾跑</span>
|
||||
<span><b>鼠标</b>环视</span>
|
||||
<span><b>左键</b>破坏</span>
|
||||
<span><b>右键</b>放置</span>
|
||||
<span><b>1-9 / 滚轮</b>切换方块</span>
|
||||
<span><b>F</b>飞行</span>
|
||||
</div>
|
||||
<div class="tip">柏林噪声生成的无限地形之上,矗立着由开发者亲手设计的「天悬神殿」与「世界之树」——光之回廊、水晶尖塔、悬浮岛屿、巨龙守卫与符文法阵环绕其间。降临神域,开始你的造物。</div>
|
||||
</div>
|
||||
<div id="bar"><i id="barfill"></i></div>
|
||||
|
||||
<script type="importmap">
|
||||
{ "imports": {
|
||||
"three": "https://cdn.jsdelivr.net/npm/three@0.160.0/build/three.module.js",
|
||||
"three/addons/": "https://cdn.jsdelivr.net/npm/three@0.160.0/examples/jsm/"
|
||||
}}
|
||||
</script>
|
||||
|
||||
<script type="module">
|
||||
import * as THREE from 'three';
|
||||
import { PointerLockControls } from 'three/addons/controls/PointerLockControls.js';
|
||||
|
||||
// ======================================================================
|
||||
// 方块秘境 · VOXEL MYTHOS — single-file voxel sandbox
|
||||
// Perlin terrain · first-person · break/place · a hand-designed mythos
|
||||
// ======================================================================
|
||||
|
||||
// ---------- tunables ----------
|
||||
const CHUNK = 16; // chunk width/depth in blocks
|
||||
const WORLD_H = 72; // world height
|
||||
const SEA = 22; // sea level
|
||||
const RENDER_DIST = 6; // chunk radius around player to keep loaded
|
||||
const SEED = 1337;
|
||||
|
||||
// ---------- block ids ----------
|
||||
const B = {
|
||||
AIR:0, GRASS:1, DIRT:2, STONE:3, SAND:4, LOG:5, LEAVES:6, WATER:7,
|
||||
PLANK:8, COBBLE:9, MARBLE:10, GLOW:11, CRYSTAL:12, GOLD:13, OBSID:14,
|
||||
RUNE:15, SNOW:16, MOSS:17, LAPIS:18, BEDROCK:19
|
||||
};
|
||||
// transparency / pass classification
|
||||
const TRANSPARENT = new Set([B.AIR, B.WATER, B.LEAVES, B.CRYSTAL]); // don't fully occlude neighbours
|
||||
const EMISSIVE = new Set([B.GLOW, B.RUNE, B.LAPIS]); // full-bright pass
|
||||
const GLASSY = new Set([B.WATER, B.CRYSTAL]); // translucent pass
|
||||
const isAir = id => id === B.AIR;
|
||||
const isOpaque = id => id !== B.AIR && !TRANSPARENT.has(id);
|
||||
const isSolid = id => id !== B.AIR && id !== B.WATER; // for collision
|
||||
|
||||
// ---------- seeded PRNG ----------
|
||||
function mulberry32(a){ return function(){ a|=0; a=a+0x6D2B79F5|0; let t=Math.imul(a^a>>>15,1|a); t=t+Math.imul(t^t>>>7,61|t)^t; return ((t^t>>>14)>>>0)/4294967296; }; }
|
||||
function hash2(x,y){ let h = x*374761393 + y*668265263; h = (h^(h>>>13))*1274126177; return ((h^(h>>>16))>>>0)/4294967296; }
|
||||
|
||||
// ---------- Perlin noise (improved, seeded permutation) ----------
|
||||
const perm = new Uint8Array(512);
|
||||
(function buildPerm(){
|
||||
const p = new Uint8Array(256); for(let i=0;i<256;i++) p[i]=i;
|
||||
const rng = mulberry32(SEED);
|
||||
for(let i=255;i>0;i--){ const j=(rng()*(i+1))|0; const t=p[i]; p[i]=p[j]; p[j]=t; }
|
||||
for(let i=0;i<512;i++) perm[i]=p[i&255];
|
||||
})();
|
||||
const fade = t => t*t*t*(t*(t*6-15)+10);
|
||||
const lerp = (a,b,t) => a+(b-a)*t;
|
||||
function grad2(h,x,y){ switch(h&7){ case 0:return x+y; case 1:return -x+y; case 2:return x-y; case 3:return -x-y; case 4:return x; case 5:return -x; case 6:return y; default:return -y; } }
|
||||
function perlin2(x,y){
|
||||
const X=Math.floor(x)&255, Y=Math.floor(y)&255; x-=Math.floor(x); y-=Math.floor(y);
|
||||
const u=fade(x), v=fade(y);
|
||||
const aa=perm[perm[X]+Y], ab=perm[perm[X]+Y+1], ba=perm[perm[X+1]+Y], bb=perm[perm[X+1]+Y+1];
|
||||
return lerp( lerp(grad2(aa,x,y), grad2(ba,x-1,y), u), lerp(grad2(ab,x,y-1), grad2(bb,x-1,y-1), u), v );
|
||||
}
|
||||
// fractal brownian motion
|
||||
function fbm(x,y,oct,lac,gain){ let f=0,amp=0.5,freq=1,norm=0; for(let i=0;i<oct;i++){ f+=amp*perlin2(x*freq,y*freq); norm+=amp; amp*=gain; freq*=lac; } return f/norm; }
|
||||
|
||||
// ======================================================================
|
||||
// PROCEDURAL TEXTURE ATLAS — drawn pixel-by-pixel, NearestFilter
|
||||
// ======================================================================
|
||||
const TILE = 16, COLS = 8;
|
||||
// tile indices
|
||||
const T = { GRASS_TOP:0, GRASS_SIDE:1, DIRT:2, STONE:3, SAND:4, LOG_TOP:5, LOG_SIDE:6, LEAVES:7,
|
||||
WATER:8, PLANK:9, COBBLE:10, MARBLE:11, GLOW:12, CRYSTAL:13, GOLD:14, OBSID:15,
|
||||
RUNE:16, SNOW:17, MOSS:18, LAPIS:19, BEDROCK:20, MARBLE_GOLD:21 };
|
||||
const TILE_COUNT = 22, ROWS = Math.ceil(TILE_COUNT/COLS);
|
||||
const atlas = document.createElement('canvas'); atlas.width = COLS*TILE; atlas.height = ROWS*TILE;
|
||||
const actx = atlas.getContext('2d');
|
||||
|
||||
// helpers operating on a TILE×TILE off-screen, then blitted into atlas at index
|
||||
function makeTile(index, draw){
|
||||
const c = document.createElement('canvas'); c.width=TILE; c.height=TILE; const g=c.getContext('2d');
|
||||
const img = g.createImageData(TILE,TILE); const d = img.data;
|
||||
const put=(x,y,r,gr,b,a=255)=>{ if(x<0||y<0||x>=TILE||y>=TILE)return; const i=(y*TILE+x)*4; d[i]=r;d[i+1]=gr;d[i+2]=b;d[i+3]=a; };
|
||||
draw(put, mulberry32(SEED*131 + index*7919));
|
||||
g.putImageData(img,0,0);
|
||||
const cx=(index%COLS)*TILE, cy=((index/COLS)|0)*TILE;
|
||||
actx.drawImage(c, cx, cy);
|
||||
}
|
||||
// colour util
|
||||
function vary(base, rng, amt){ const k=(rng()*2-1)*amt; return [clamp8(base[0]+k), clamp8(base[1]+k), clamp8(base[2]+k)]; }
|
||||
function clamp8(v){ return v<0?0:v>255?255:v|0; }
|
||||
function mix(a,b,t){ return [a[0]+(b[0]-a[0])*t, a[1]+(b[1]-a[1])*t, a[2]+(b[2]-a[2])*t]; }
|
||||
|
||||
// ---- individual tile painters ----
|
||||
function fill(put, base, rng, amt){ for(let y=0;y<TILE;y++)for(let x=0;x<TILE;x++){ const c=vary(base,rng,amt); put(x,y,c[0],c[1],c[2]); } }
|
||||
|
||||
makeTile(T.DIRT, (put,rng)=>{ fill(put,[134,96,67],rng,26);
|
||||
for(let i=0;i<10;i++){ const x=(rng()*TILE)|0,y=(rng()*TILE)|0; const c=vary([96,68,46],rng,18); put(x,y,c[0],c[1],c[2]); } });
|
||||
|
||||
makeTile(T.GRASS_TOP, (put,rng)=>{ fill(put,[86,150,58],rng,24);
|
||||
for(let i=0;i<22;i++){ const x=(rng()*TILE)|0,y=(rng()*TILE)|0; const c=rng()<.5?[64,118,42]:[110,176,70]; put(x,y,c[0],c[1],c[2]); } });
|
||||
|
||||
makeTile(T.GRASS_SIDE, (put,rng)=>{ // dirt body + green crown w/ dribble
|
||||
fill(put,[134,96,67],rng,24);
|
||||
for(let x=0;x<TILE;x++){ const top=2+((rng()*3)|0);
|
||||
for(let y=0;y<top;y++){ const c=vary([78,142,52],rng,20); put(x,y,c[0],c[1],c[2]); }
|
||||
if(rng()<.4){ const c=vary([70,128,46],rng,16); put(x,top,c[0],c[1],c[2]); if(rng()<.4) put(x,top+1,c[0]-8,c[1]-8,c[2]-6); } } });
|
||||
|
||||
makeTile(T.STONE, (put,rng)=>{ fill(put,[128,128,133],rng,16);
|
||||
for(let i=0;i<6;i++){ const x=(rng()*TILE)|0,y=(rng()*TILE)|0; put(x,y,96,96,102); } });
|
||||
|
||||
makeTile(T.COBBLE, (put,rng)=>{ const cell=4; for(let cy=0;cy<TILE;cy+=cell)for(let cx=0;cx<TILE;cx+=cell){ const b=110+((rng()*40)|0);
|
||||
for(let y=0;y<cell;y++)for(let x=0;x<cell;x++){ const edge=(x===0||y===0); const v=edge?70:b+((rng()*16-8)|0); put(cx+x,cy+y,v,v,v+4); } } });
|
||||
|
||||
makeTile(T.SAND, (put,rng)=>{ fill(put,[219,203,140],rng,16);
|
||||
for(let i=0;i<8;i++){ const x=(rng()*TILE)|0,y=(rng()*TILE)|0; put(x,y,198,182,120); } });
|
||||
|
||||
makeTile(T.SNOW, (put,rng)=>{ fill(put,[236,242,250],rng,8);
|
||||
for(let i=0;i<6;i++){ const x=(rng()*TILE)|0,y=(rng()*TILE)|0; put(x,y,255,255,255); } });
|
||||
|
||||
makeTile(T.LOG_SIDE, (put,rng)=>{ for(let x=0;x<TILE;x++){ const streak=(x%4===0)?-18:0;
|
||||
for(let y=0;y<TILE;y++){ const c=vary([101,73,42],rng,12); put(x,y,c[0]+streak,c[1]+streak,c[2]+streak); } }
|
||||
for(let i=0;i<6;i++){ const x=(rng()*TILE)|0,y=(rng()*TILE)|0; put(x,y,70,50,28); } });
|
||||
|
||||
makeTile(T.LOG_TOP, (put,rng)=>{ const c0=TILE/2-0.5; for(let y=0;y<TILE;y++)for(let x=0;x<TILE;x++){ const d=Math.hypot(x-c0,y-c0); const ring=(Math.floor(d)%2===0); const base=ring?[176,140,95]:[150,115,75]; const c=vary(base,rng,10); put(x,y,c[0],c[1],c[2]); }
|
||||
put(c0|0,c0|0,120,92,58); });
|
||||
|
||||
makeTile(T.LEAVES, (put,rng)=>{ for(let y=0;y<TILE;y++)for(let x=0;x<TILE;x++){ if(rng()<.10){ put(x,y,0,0,0,0); continue; } const r=rng(); const base=r<.4?[38,82,28]:r<.8?[54,110,40]:[78,140,52]; const c=vary(base,rng,12); put(x,y,c[0],c[1],c[2]); } });
|
||||
|
||||
makeTile(T.MOSS, (put,rng)=>{ fill(put,[120,120,124],rng,14);
|
||||
for(let i=0;i<60;i++){ const x=(rng()*TILE)|0,y=(rng()*TILE)|0; if(rng()<.5){ const c=vary([62,116,46],rng,18); put(x,y,c[0],c[1],c[2]); } } });
|
||||
|
||||
makeTile(T.WATER, (put,rng)=>{ for(let y=0;y<TILE;y++)for(let x=0;x<TILE;x++){ const w=Math.sin((x+y)*0.9)*0.5+0.5; const base=mix([34,104,196],[60,140,226],w*0.6+rng()*0.2); put(x,y,base[0]|0,base[1]|0,base[2]|0,210); } });
|
||||
|
||||
makeTile(T.PLANK, (put,rng)=>{ fill(put,[176,134,78],rng,14);
|
||||
for(let y=0;y<TILE;y++){ if(y%4===0){ for(let x=0;x<TILE;x++) put(x,y,120,86,46); } }
|
||||
for(let i=0;i<10;i++){ const x=(rng()*TILE)|0,y=(rng()*TILE)|0; put(x,y,150,112,62); } });
|
||||
|
||||
makeTile(T.MARBLE, (put,rng)=>{ fill(put,[232,230,223],rng,7);
|
||||
let vx=(rng()*TILE)|0; for(let y=0;y<TILE;y++){ vx+=(rng()*3-1)|0; const c=[188,190,198]; put((vx+TILE)%TILE,y,c[0],c[1],c[2]); }
|
||||
let vx2=(rng()*TILE)|0; for(let y=0;y<TILE;y++){ vx2+=(rng()*3-1)|0; put((vx2+TILE)%TILE,y,200,202,210); } });
|
||||
|
||||
makeTile(T.MARBLE_GOLD, (put,rng)=>{ fill(put,[232,230,223],rng,7);
|
||||
for(let x=0;x<TILE;x++){ put(x,0,255,210,74); put(x,TILE-1,205,160,40); put(x,1,255,236,150); } });
|
||||
|
||||
makeTile(T.GLOW, (put,rng)=>{ fill(put,[255,206,107],rng,14);
|
||||
for(let i=0;i<26;i++){ const x=(rng()*TILE)|0,y=(rng()*TILE)|0; put(x,y,255,238,176); }
|
||||
for(let i=0;i<8;i++){ const x=(rng()*TILE)|0,y=(rng()*TILE)|0; put(x,y,224,150,60); } });
|
||||
|
||||
makeTile(T.CRYSTAL, (put,rng)=>{ for(let y=0;y<TILE;y++)for(let x=0;x<TILE;x++){ const band=((x+y)>>1)%3; const base=band===0?[210,250,255]:band===1?[150,235,255]:[96,180,228]; const c=vary(base,rng,10); put(x,y,c[0],c[1],c[2],235); }
|
||||
for(let i=0;i<5;i++){ const x=(rng()*TILE)|0; put(x,(rng()*TILE)|0,255,255,255,255); } });
|
||||
|
||||
makeTile(T.LAPIS, (put,rng)=>{ fill(put,[40,66,176],rng,16);
|
||||
for(let i=0;i<26;i++){ const x=(rng()*TILE)|0,y=(rng()*TILE)|0; const c=rng()<.5?[92,124,232]:[24,40,120]; put(x,y,c[0],c[1],c[2]); } });
|
||||
|
||||
makeTile(T.GOLD, (put,rng)=>{ const half=TILE/2; for(let by=0;by<2;by++)for(let bx=0;bx<2;bx++){ for(let y=0;y<half;y++)for(let x=0;x<half;x++){ let c=[255,210,74]; if(x===0||y===0) c=[150,112,30]; else if(x<2&&y<2) c=[255,244,180]; else if(x>half-3&&y>half-3) c=[205,160,40]; const v=vary(c,rng,8); put(bx*half+x,by*half+y,v[0],v[1],v[2]); } } });
|
||||
|
||||
makeTile(T.OBSID, (put,rng)=>{ fill(put,[22,18,34],rng,8);
|
||||
for(let i=0;i<14;i++){ const x=(rng()*TILE)|0,y=(rng()*TILE)|0; const c=vary([74,44,98],rng,18); put(x,y,c[0],c[1],c[2]); }
|
||||
for(let k=0;k<TILE;k++){ if(k%5===0) put(k,k,120,90,150); } });
|
||||
|
||||
makeTile(T.BEDROCK, (put,rng)=>{ for(let y=0;y<TILE;y++)for(let x=0;x<TILE;x++){ const v=30+((rng()*60)|0); put(x,y,v,v,v+3); } });
|
||||
|
||||
makeTile(T.RUNE, (put,rng)=>{ fill(put,[58,58,66],rng,12); // dark stone + glowing glyph
|
||||
const glyph=[ "0001111000","0010000100","0100110010","0101001010","1010000101","1010000101","0101001010","0100110010","0010000100","0001111000" ];
|
||||
const off=3; for(let y=0;y<10;y++)for(let x=0;x<10;x++){ if(glyph[y][x]==='1'){ const c=vary([120,240,255],rng,16); put(x+off,y+off,c[0],c[1],c[2]); } }
|
||||
put(8,8,200,250,255); });
|
||||
|
||||
// ---------- atlas → texture ----------
|
||||
const atlasTex = new THREE.CanvasTexture(atlas);
|
||||
atlasTex.magFilter = THREE.NearestFilter; atlasTex.minFilter = THREE.NearestFilter;
|
||||
atlasTex.generateMipmaps = false; atlasTex.flipY = false; // canvas (0,0)=top-left maps to UV(0,0)
|
||||
atlasTex.colorSpace = THREE.SRGBColorSpace; atlasTex.anisotropy = 1;
|
||||
|
||||
const EPS = 0.0008; // inset to kill texture bleed
|
||||
function tileUV(idx){ const col=idx%COLS, row=(idx/COLS)|0;
|
||||
const u0=(col*TILE)/atlas.width + EPS, v0=(row*TILE)/atlas.height + EPS;
|
||||
const u1=((col+1)*TILE)/atlas.width - EPS, v1=((row+1)*TILE)/atlas.height - EPS;
|
||||
return [u0,v0,u1,v1]; // v0=top edge, v1=bottom edge (flipY=false)
|
||||
}
|
||||
|
||||
// block id → face tiles {top, side, bottom}
|
||||
const FACE = {};
|
||||
FACE[B.GRASS]={top:T.GRASS_TOP, side:T.GRASS_SIDE, bottom:T.DIRT};
|
||||
FACE[B.DIRT]={top:T.DIRT,side:T.DIRT,bottom:T.DIRT};
|
||||
FACE[B.STONE]={top:T.STONE,side:T.STONE,bottom:T.STONE};
|
||||
FACE[B.SAND]={top:T.SAND,side:T.SAND,bottom:T.SAND};
|
||||
FACE[B.LOG]={top:T.LOG_TOP,side:T.LOG_SIDE,bottom:T.LOG_TOP};
|
||||
FACE[B.LEAVES]={top:T.LEAVES,side:T.LEAVES,bottom:T.LEAVES};
|
||||
FACE[B.WATER]={top:T.WATER,side:T.WATER,bottom:T.WATER};
|
||||
FACE[B.PLANK]={top:T.PLANK,side:T.PLANK,bottom:T.PLANK};
|
||||
FACE[B.COBBLE]={top:T.COBBLE,side:T.COBBLE,bottom:T.COBBLE};
|
||||
FACE[B.MARBLE]={top:T.MARBLE,side:T.MARBLE_GOLD,bottom:T.MARBLE};
|
||||
FACE[B.GLOW]={top:T.GLOW,side:T.GLOW,bottom:T.GLOW};
|
||||
FACE[B.CRYSTAL]={top:T.CRYSTAL,side:T.CRYSTAL,bottom:T.CRYSTAL};
|
||||
FACE[B.GOLD]={top:T.GOLD,side:T.GOLD,bottom:T.GOLD};
|
||||
FACE[B.OBSID]={top:T.OBSID,side:T.OBSID,bottom:T.OBSID};
|
||||
FACE[B.RUNE]={top:T.RUNE,side:T.OBSID,bottom:T.OBSID};
|
||||
FACE[B.SNOW]={top:T.SNOW,side:T.SNOW,bottom:T.SNOW};
|
||||
FACE[B.MOSS]={top:T.MOSS,side:T.MOSS,bottom:T.MOSS};
|
||||
FACE[B.LAPIS]={top:T.LAPIS,side:T.LAPIS,bottom:T.LAPIS};
|
||||
FACE[B.BEDROCK]={top:T.BEDROCK,side:T.BEDROCK,bottom:T.BEDROCK};
|
||||
|
||||
// ---------- materials ----------
|
||||
const matOpaque = new THREE.MeshLambertMaterial({ map:atlasTex, vertexColors:true, alphaTest:0.5, side:THREE.FrontSide });
|
||||
const matGlass = new THREE.MeshLambertMaterial({ map:atlasTex, vertexColors:true, transparent:true, opacity:0.82, depthWrite:false, side:THREE.DoubleSide });
|
||||
const matGlow = new THREE.MeshBasicMaterial({ map:atlasTex, vertexColors:true, transparent:true, alphaTest:0.4 });
|
||||
|
||||
// ======================================================================
|
||||
// WORLD MODEL
|
||||
// - chunks store generated voxel data
|
||||
// - overrides[] holds everything non-terrain: trees, the mythos, player edits
|
||||
// - sampleWorld() is a stateless fallback so chunk borders never seam
|
||||
// ======================================================================
|
||||
const CSIZE = CHUNK*CHUNK*WORLD_H;
|
||||
const chunks = new Map(); // "cx,cz" -> { data, group, dirty }
|
||||
const overrides = new Map(); // "x,y,z" -> id (persistent edits/structures/trees)
|
||||
const colCache = new Map(); // "x,z" -> column info
|
||||
const decorated = new Set(); // chunks whose trees were stamped
|
||||
|
||||
const ckey = (cx,cz)=> cx+","+cz;
|
||||
const bkey = (x,y,z)=> x+","+y+","+z;
|
||||
const cdiv = n => Math.floor(n/CHUNK);
|
||||
const localIdx = (lx,y,lz)=> (y*CHUNK + lz)*CHUNK + lx;
|
||||
|
||||
// write an override (force=false → first writer wins, used by trees so the mythos isn't overwritten)
|
||||
function ov(x,y,z,id,force){ if(y<0||y>=WORLD_H) return; const k=bkey(x,y,z);
|
||||
if(!force && overrides.has(k)) return; overrides.set(k,id); }
|
||||
|
||||
function columnInfo(x,z){ const k=x+","+z; let c=colCache.get(k); if(c) return c;
|
||||
const base = fbm(x*0.0065, z*0.0065, 5, 2.0, 0.5); // rolling continents
|
||||
const ridge = Math.pow(1 - Math.abs(fbm(x*0.012, z*0.012, 3, 2.0, 0.5)), 2); // mountain ridges
|
||||
let h = SEA + Math.round(base*22) + Math.round(ridge*16) - 3;
|
||||
h = Math.max(3, Math.min(WORLD_H-12, h));
|
||||
const temp = perlin2(x*0.0032+50.5, z*0.0032-50.5);
|
||||
const moist = perlin2(x*0.0032-80.5, z*0.0032+30.5);
|
||||
const sandy = (h <= SEA+1) || (temp>0.34 && moist<-0.05);
|
||||
const snowy = (h > SEA+25) || (temp < -0.42 && h>SEA+6);
|
||||
let topId = sandy ? B.SAND : snowy ? B.SNOW : (h<SEA? B.DIRT : B.GRASS);
|
||||
c = { h, topId, sandy, snowy }; colCache.set(k,c); return c;
|
||||
}
|
||||
|
||||
function terrainBlock(x,y,z){
|
||||
if(y<=0) return B.BEDROCK;
|
||||
if(y>=WORLD_H) return B.AIR;
|
||||
const info = columnInfo(x,z);
|
||||
if(y > info.h){ return (y<=SEA) ? B.WATER : B.AIR; }
|
||||
if(y === info.h) return info.topId;
|
||||
if(y > info.h-4) return info.sandy ? B.SAND : B.DIRT;
|
||||
// deep stone with rare veins for flavour
|
||||
const r = hash2(x*1.7+y*3.1, z*1.3-y*2.7);
|
||||
if(r < 0.010 && y < SEA-2) return B.LAPIS;
|
||||
if(r > 0.992) return B.COBBLE;
|
||||
return B.STONE;
|
||||
}
|
||||
|
||||
// single authoritative lookup (used at borders & by physics/raycast for any coord)
|
||||
function sampleWorld(x,y,z){ if(y<0) return B.BEDROCK; if(y>=WORLD_H) return B.AIR;
|
||||
const o = overrides.get(bkey(x,y,z)); if(o!==undefined) return o; return terrainBlock(x,y,z); }
|
||||
|
||||
function getBlock(x,y,z){ if(y<0) return B.BEDROCK; if(y>=WORLD_H) return B.AIR;
|
||||
const ch = chunks.get(ckey(cdiv(x),cdiv(z)));
|
||||
if(ch){ const lx=x-cdiv(x)*CHUNK, lz=z-cdiv(z)*CHUNK; return ch.data[localIdx(lx,y,lz)]; }
|
||||
return sampleWorld(x,y,z);
|
||||
}
|
||||
|
||||
// ---------- tree decoration (writes only to overrides; first-writer-wins) ----------
|
||||
function placeTree(x,by,z){ const r=mulberry32((x*73856093)^(z*19349663));
|
||||
const th = 4 + ((r()*3)|0);
|
||||
for(let i=0;i<th;i++) ov(x,by+i,z,B.LOG,false);
|
||||
const top = by+th;
|
||||
for(let dy=-2;dy<=1;dy++){ const ly=top+dy; const rad = dy<=0?2:1;
|
||||
for(let dx=-rad;dx<=rad;dx++)for(let dz=-rad;dz<=rad;dz++){
|
||||
if(Math.abs(dx)===rad && Math.abs(dz)===rad && r()<0.6) continue; // round the corners
|
||||
if(dx===0&&dz===0&&dy<0) continue; // keep trunk clear
|
||||
ov(x+dx, ly, z+dz, B.LEAVES, false);
|
||||
} }
|
||||
ov(x,top+1,z,B.LEAVES,false);
|
||||
}
|
||||
function decorate(cx,cz){ const k=ckey(cx,cz); if(decorated.has(k)) return; decorated.add(k);
|
||||
for(let lx=0;lx<CHUNK;lx++)for(let lz=0;lz<CHUNK;lz++){
|
||||
const x=cx*CHUNK+lx, z=cz*CHUNK+lz;
|
||||
if(x*x+z*z < 52*52) continue; // keep the mythos plaza clear of forest
|
||||
const info = columnInfo(x,z);
|
||||
if(info.topId!==B.GRASS || info.h<=SEA) continue;
|
||||
if(hash2(x*1.13+0.5, z*2.27+0.5) < 0.016) placeTree(x, info.h+1, z);
|
||||
}
|
||||
}
|
||||
|
||||
/* @@APPEND@@ */
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,300 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 900 600" width="900" height="600" role="img" aria-labelledby="t d">
|
||||
<title id="t">A pelican riding a bicycle by the sea</title>
|
||||
<desc id="d">A lively brown pelican with a long pouched beak pedals a red bicycle along a sunlit beach, the turquoise ocean and a sailboat behind it.</desc>
|
||||
|
||||
<defs>
|
||||
<linearGradient id="sky" x1="0" y1="0" x2="0" y2="1">
|
||||
<stop offset="0" stop-color="#2f7fc6"/>
|
||||
<stop offset="0.45" stop-color="#74b6e6"/>
|
||||
<stop offset="0.80" stop-color="#cfe9f6"/>
|
||||
<stop offset="1" stop-color="#f6e7c4"/>
|
||||
</linearGradient>
|
||||
<radialGradient id="sun" cx="0.5" cy="0.5" r="0.5">
|
||||
<stop offset="0" stop-color="#fffdf2"/>
|
||||
<stop offset="0.35" stop-color="#fff0b8"/>
|
||||
<stop offset="0.7" stop-color="#ffe07f" stop-opacity="0.55"/>
|
||||
<stop offset="1" stop-color="#ffe07f" stop-opacity="0"/>
|
||||
</radialGradient>
|
||||
<linearGradient id="sea" x1="0" y1="0" x2="0" y2="1">
|
||||
<stop offset="0" stop-color="#0f7f96"/>
|
||||
<stop offset="0.45" stop-color="#15a3b3"/>
|
||||
<stop offset="1" stop-color="#46c6c4"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="sand" x1="0" y1="0" x2="0" y2="1">
|
||||
<stop offset="0" stop-color="#ecd29a"/>
|
||||
<stop offset="0.5" stop-color="#e7c27c"/>
|
||||
<stop offset="1" stop-color="#d8ad62"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="body" x1="0.2" y1="0" x2="0.7" y2="1">
|
||||
<stop offset="0" stop-color="#ffffff"/>
|
||||
<stop offset="0.55" stop-color="#eef2f5"/>
|
||||
<stop offset="1" stop-color="#cdd7df"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="wing" x1="0" y1="0" x2="0.4" y2="1">
|
||||
<stop offset="0" stop-color="#c6cfd8"/>
|
||||
<stop offset="1" stop-color="#8b97a4"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="beak" x1="0" y1="0" x2="0.4" y2="1">
|
||||
<stop offset="0" stop-color="#ffcf5c"/>
|
||||
<stop offset="1" stop-color="#f1972a"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="pouch" x1="0" y1="0" x2="0" y2="1">
|
||||
<stop offset="0" stop-color="#f6a93c"/>
|
||||
<stop offset="1" stop-color="#e07a22"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="frame" x1="0" y1="0" x2="0" y2="1">
|
||||
<stop offset="0" stop-color="#ff6a45"/>
|
||||
<stop offset="1" stop-color="#d6371d"/>
|
||||
</linearGradient>
|
||||
<radialGradient id="seaGlitter" cx="0.5" cy="0.2" r="0.7">
|
||||
<stop offset="0" stop-color="#ffffff" stop-opacity="0.6"/>
|
||||
<stop offset="1" stop-color="#ffffff" stop-opacity="0"/>
|
||||
</radialGradient>
|
||||
<filter id="soft" x="-30%" y="-30%" width="160%" height="160%">
|
||||
<feGaussianBlur stdDeviation="6"/>
|
||||
</filter>
|
||||
<filter id="softer" x="-50%" y="-50%" width="200%" height="200%">
|
||||
<feGaussianBlur stdDeviation="12"/>
|
||||
</filter>
|
||||
</defs>
|
||||
|
||||
<!-- SKY -->
|
||||
<rect x="0" y="0" width="900" height="600" fill="url(#sky)"/>
|
||||
<circle cx="170" cy="120" r="160" fill="url(#sun)"/>
|
||||
<circle cx="170" cy="120" r="46" fill="#fffdf0"/>
|
||||
|
||||
<!-- clouds -->
|
||||
<g fill="#ffffff" opacity="0.92">
|
||||
<g filter="url(#soft)">
|
||||
<ellipse cx="350" cy="92" rx="70" ry="24"/>
|
||||
<ellipse cx="400" cy="80" rx="46" ry="22"/>
|
||||
<ellipse cx="305" cy="84" rx="40" ry="18"/>
|
||||
</g>
|
||||
<g filter="url(#soft)" opacity="0.85">
|
||||
<ellipse cx="700" cy="64" rx="80" ry="22"/>
|
||||
<ellipse cx="752" cy="54" rx="46" ry="20"/>
|
||||
<ellipse cx="648" cy="58" rx="40" ry="16"/>
|
||||
</g>
|
||||
</g>
|
||||
|
||||
<!-- seagulls -->
|
||||
<g fill="none" stroke="#5b6b7d" stroke-width="2.4" stroke-linecap="round">
|
||||
<path d="M250,150 q11,-11 22,0 q11,-11 22,0"/>
|
||||
<path d="M520,96 q8,-8 16,0 q8,-8 16,0"/>
|
||||
<path d="M560,128 q9,-9 18,0 q9,-9 18,0"/>
|
||||
</g>
|
||||
|
||||
<!-- SEA -->
|
||||
<rect x="0" y="250" width="900" height="158" fill="url(#sea)"/>
|
||||
<rect x="0" y="250" width="900" height="158" fill="url(#seaGlitter)"/>
|
||||
<!-- distant sailboat -->
|
||||
<g transform="translate(150,300)">
|
||||
<path d="M0,0 L0,-42 L34,-6 Z" fill="#fbfbf7"/>
|
||||
<path d="M-3,-2 L-22,-44 L-3,-44 Z" fill="#e7ecef"/>
|
||||
<path d="M-26,2 q26,16 52,0 l-9,12 q-17,8 -34,0 Z" fill="#34424f"/>
|
||||
</g>
|
||||
<!-- wave highlights -->
|
||||
<g stroke="#eafcff" stroke-linecap="round" fill="none" opacity="0.75">
|
||||
<path d="M60,300 q30,-7 60,0" stroke-width="2.5"/>
|
||||
<path d="M520,288 q34,-7 68,0" stroke-width="2.5"/>
|
||||
<path d="M700,320 q40,-8 80,0" stroke-width="3"/>
|
||||
<path d="M300,345 q44,-9 88,0" stroke-width="3"/>
|
||||
<path d="M620,360 q40,-8 80,0" stroke-width="3"/>
|
||||
</g>
|
||||
|
||||
<!-- SAND -->
|
||||
<path d="M0,408 L900,408 L900,600 L0,600 Z" fill="url(#sand)"/>
|
||||
<!-- wet-sand shoreline + foam -->
|
||||
<path d="M0,408 q120,18 240,4 q140,-16 260,6 q150,20 400,-2 L900,420 L900,440 q-220,22 -430,2 q-150,-14 -250,4 q-130,16 -220,0 Z" fill="#cfeef0" opacity="0.65"/>
|
||||
<path d="M0,406 q120,18 240,4 q140,-16 260,6 q150,20 400,-2" fill="none" stroke="#ffffff" stroke-width="3" opacity="0.85"/>
|
||||
<!-- tyre tracks trailing behind -->
|
||||
<g fill="none" stroke="#bc9558" stroke-width="3" stroke-linecap="round" opacity="0.7">
|
||||
<path d="M60,486 q120,-10 200,8"/>
|
||||
<path d="M64,498 q120,-10 200,8"/>
|
||||
</g>
|
||||
<!-- foreground shells & starfish -->
|
||||
<g opacity="0.95">
|
||||
<g transform="translate(120,548) rotate(-12)" fill="#e8743c" stroke="#c25827" stroke-width="2">
|
||||
<path d="M0,-16 L5,-3 L18,-3 L7,5 L11,18 L0,10 L-11,18 L-7,5 L-18,-3 L-5,-3 Z"/>
|
||||
</g>
|
||||
<ellipse cx="800" cy="560" rx="16" ry="11" fill="#f4dfc0" stroke="#d8b27e" stroke-width="2"/>
|
||||
<path d="M790,560 q10,-10 20,0" fill="none" stroke="#d8b27e" stroke-width="1.6"/>
|
||||
<circle cx="760" cy="538" r="4" fill="#c9a26a"/>
|
||||
<circle cx="180" cy="572" r="5" fill="#cba978"/>
|
||||
</g>
|
||||
|
||||
<!-- ground shadow under bike -->
|
||||
<ellipse cx="430" cy="506" rx="220" ry="22" fill="#7a5a28" opacity="0.28" filter="url(#softer)"/>
|
||||
|
||||
<!-- ================= BICYCLE ================= -->
|
||||
<!-- wheels: rear (275,428) & front (585,428), r=72 -->
|
||||
<g stroke-linecap="round">
|
||||
<!-- spokes -->
|
||||
<g stroke="#b9c2cc" stroke-width="1.6">
|
||||
<line x1="275" y1="428" x2="339" y2="428"/><line x1="275" y1="428" x2="330" y2="460"/><line x1="275" y1="428" x2="307" y2="483"/><line x1="275" y1="428" x2="275" y2="492"/><line x1="275" y1="428" x2="243" y2="483"/><line x1="275" y1="428" x2="220" y2="460"/><line x1="275" y1="428" x2="211" y2="428"/><line x1="275" y1="428" x2="220" y2="396"/><line x1="275" y1="428" x2="243" y2="373"/><line x1="275" y1="428" x2="275" y2="364"/><line x1="275" y1="428" x2="307" y2="373"/><line x1="275" y1="428" x2="330" y2="396"/>
|
||||
<line x1="585" y1="428" x2="649" y2="428"/><line x1="585" y1="428" x2="640" y2="460"/><line x1="585" y1="428" x2="617" y2="483"/><line x1="585" y1="428" x2="585" y2="492"/><line x1="585" y1="428" x2="553" y2="483"/><line x1="585" y1="428" x2="530" y2="460"/><line x1="585" y1="428" x2="521" y2="428"/><line x1="585" y1="428" x2="530" y2="396"/><line x1="585" y1="428" x2="553" y2="373"/><line x1="585" y1="428" x2="585" y2="364"/><line x1="585" y1="428" x2="617" y2="373"/><line x1="585" y1="428" x2="640" y2="396"/>
|
||||
</g>
|
||||
<!-- rims -->
|
||||
<circle cx="275" cy="428" r="64" fill="none" stroke="#cfd6dd" stroke-width="4"/>
|
||||
<circle cx="585" cy="428" r="64" fill="none" stroke="#cfd6dd" stroke-width="4"/>
|
||||
<!-- tyres -->
|
||||
<circle cx="275" cy="428" r="72" fill="none" stroke="#23292f" stroke-width="9"/>
|
||||
<circle cx="585" cy="428" r="72" fill="none" stroke="#23292f" stroke-width="9"/>
|
||||
<!-- hubs -->
|
||||
<circle cx="275" cy="428" r="8" fill="#5a626b"/><circle cx="275" cy="428" r="3" fill="#2a2f35"/>
|
||||
<circle cx="585" cy="428" r="8" fill="#5a626b"/><circle cx="585" cy="428" r="3" fill="#2a2f35"/>
|
||||
</g>
|
||||
|
||||
<!-- chain -->
|
||||
<path d="M420,432 L275,428" fill="none" stroke="#2c333b" stroke-width="6" stroke-linecap="round"/>
|
||||
<circle cx="275" cy="428" r="13" fill="none" stroke="#3a4049" stroke-width="4"/>
|
||||
|
||||
<!-- frame (dark underlay then coloured tube) -->
|
||||
<g fill="none" stroke-linecap="round">
|
||||
<g stroke="#9e2a16" stroke-width="13">
|
||||
<path d="M275,428 L345,318 M275,428 L420,432 M420,432 L345,318 M420,432 L560,326 M345,318 L560,326 M560,326 L585,428"/>
|
||||
</g>
|
||||
<g stroke="url(#frame)" stroke-width="9">
|
||||
<path d="M275,428 L345,318 M275,428 L420,432 M420,432 L345,318 M420,432 L560,326 M345,318 L560,326 M560,326 L585,428"/>
|
||||
</g>
|
||||
<!-- steerer + handlebar -->
|
||||
<path d="M560,326 L580,290" stroke="#9e2a16" stroke-width="13"/>
|
||||
<path d="M560,326 L580,290" stroke="url(#frame)" stroke-width="9"/>
|
||||
<path d="M560,290 q24,-6 40,6" stroke="#2c333b" stroke-width="8"/>
|
||||
</g>
|
||||
|
||||
<!-- crank + pedals -->
|
||||
<circle cx="420" cy="432" r="16" fill="#3a4049"/>
|
||||
<circle cx="420" cy="432" r="9" fill="#5a626b"/>
|
||||
<path d="M420,432 L408,378" stroke="#2c333b" stroke-width="6" stroke-linecap="round"/>
|
||||
<path d="M420,432 L432,488" stroke="#2c333b" stroke-width="6" stroke-linecap="round"/>
|
||||
<rect x="396" y="372" width="26" height="8" rx="3" fill="#1f242a"/>
|
||||
<rect x="420" y="484" width="28" height="8" rx="3" fill="#1f242a"/>
|
||||
|
||||
<!-- saddle -->
|
||||
<path d="M316,314 q28,-9 58,0 q-6,12 -30,12 q-22,0 -28,-12 Z" fill="#2c2c2c"/>
|
||||
|
||||
<!-- ================= PELICAN ================= -->
|
||||
<!-- soft contact shadow of bird on frame area -->
|
||||
<ellipse cx="400" cy="408" rx="120" ry="20" fill="#5a6b78" opacity="0.18" filter="url(#soft)"/>
|
||||
|
||||
<!-- far leg (behind) -->
|
||||
<path d="M412,392 L405,378" stroke="#d9731c" stroke-width="9" stroke-linecap="round"/>
|
||||
<path d="M408,374 q-12,2 -16,8 q14,2 22,-2 Z" fill="#e8822a" stroke="#cf6a18" stroke-width="1.5"/>
|
||||
|
||||
<!-- tail feathers -->
|
||||
<g fill="#c3ccd5" stroke="#9aa6b1" stroke-width="1.5">
|
||||
<path d="M306,322 l-44,2 l40,12 Z"/>
|
||||
<path d="M306,332 l-46,14 l42,8 Z"/>
|
||||
<path d="M306,344 l-40,24 l40,2 Z"/>
|
||||
</g>
|
||||
|
||||
<!-- body -->
|
||||
<path d="M300,318
|
||||
C320,250 402,236 472,258
|
||||
C512,272 522,312 506,350
|
||||
C498,380 470,404 430,408
|
||||
C372,414 322,392 306,356
|
||||
C300,344 298,330 300,318 Z" fill="url(#body)"/>
|
||||
<!-- belly warm under-light -->
|
||||
<path d="M330,372 C372,406 460,402 496,366 C484,392 452,408 422,410 C378,414 344,398 330,372 Z" fill="#f3e6cf" opacity="0.55"/>
|
||||
|
||||
<!-- folded wing on the side -->
|
||||
<path d="M470,278
|
||||
C432,300 392,326 356,350
|
||||
C374,356 392,352 408,346
|
||||
C436,334 464,316 484,294
|
||||
C480,288 476,282 470,278 Z" fill="url(#wing)"/>
|
||||
<g fill="none" stroke="#7c8794" stroke-width="2" stroke-linecap="round">
|
||||
<path d="M462,292 C432,312 402,330 372,346"/>
|
||||
<path d="M470,302 C444,320 414,336 386,350"/>
|
||||
<path d="M478,312 C454,328 426,342 400,352"/>
|
||||
</g>
|
||||
<!-- dark primary wingtip -->
|
||||
<path d="M356,350 C366,344 378,344 388,348 C378,356 366,358 356,354 Z" fill="#2f3742"/>
|
||||
|
||||
<!-- neck -->
|
||||
<path d="M470,272
|
||||
C462,232 478,204 514,194
|
||||
C544,186 570,196 578,216
|
||||
C584,232 582,248 570,258
|
||||
C556,250 544,250 530,256
|
||||
C510,268 500,288 500,302
|
||||
C492,290 478,282 470,272 Z" fill="url(#body)"/>
|
||||
<!-- chestnut nape (breeding plumage) -->
|
||||
<path d="M474,266 C468,234 480,210 506,200 C496,222 492,246 498,272 C488,272 480,270 474,266 Z" fill="#9a5230" opacity="0.85"/>
|
||||
|
||||
<!-- head -->
|
||||
<circle cx="556" cy="212" r="30" fill="url(#body)"/>
|
||||
<!-- pale yellow crown wash + crest -->
|
||||
<path d="M534,196 C540,178 566,176 580,190 C566,192 548,196 538,206 Z" fill="#f6e4a6"/>
|
||||
<g stroke="#e9d488" stroke-width="3" stroke-linecap="round">
|
||||
<path d="M540,188 q-6,-12 -2,-22"/>
|
||||
<path d="M552,184 q-2,-13 4,-22"/>
|
||||
</g>
|
||||
|
||||
<!-- upper mandible -->
|
||||
<path d="M578,206
|
||||
C632,206 694,222 748,248
|
||||
C751,253 749,259 742,260
|
||||
C700,250 648,242 598,236
|
||||
C586,234 580,224 578,206 Z" fill="url(#beak)" stroke="#d57e1c" stroke-width="1.4"/>
|
||||
<!-- pouch (lower mandible / gular sac) -->
|
||||
<path d="M566,246
|
||||
C562,300 598,338 654,334
|
||||
C696,331 728,300 742,260
|
||||
C736,256 730,255 724,256
|
||||
C694,272 642,278 600,272
|
||||
C584,269 574,258 566,246 Z" fill="url(#pouch)" stroke="#cf6c1c" stroke-width="1.4"/>
|
||||
<!-- pouch shading + seam -->
|
||||
<path d="M580,262 C586,300 614,326 656,326 C620,322 596,300 588,268 Z" fill="#c8631a" opacity="0.45"/>
|
||||
<path d="M578,236 C636,244 696,254 742,260" fill="none" stroke="#b85e16" stroke-width="2"/>
|
||||
<!-- beak hooked tip + nostril -->
|
||||
<path d="M742,248 q12,2 12,12 q-7,4 -14,0 Z" fill="#e07a1f" stroke="#c0661a" stroke-width="1.2"/>
|
||||
<path d="M600,216 l26,5" stroke="#c0661a" stroke-width="2" stroke-linecap="round"/>
|
||||
<!-- top ridge highlight -->
|
||||
<path d="M584,210 C632,210 692,225 742,250" fill="none" stroke="#fff0c0" stroke-width="2" opacity="0.8"/>
|
||||
|
||||
<!-- eye -->
|
||||
<circle cx="568" cy="206" r="9" fill="#f4e3a0"/>
|
||||
<circle cx="568" cy="206" r="6.2" fill="#23282f"/>
|
||||
<circle cx="570.5" cy="203.5" r="2" fill="#ffffff"/>
|
||||
|
||||
<!-- near wing-arm gripping handlebar -->
|
||||
<path d="M494,300
|
||||
C522,300 552,298 572,298
|
||||
C578,302 578,312 572,318
|
||||
C552,320 522,320 500,318
|
||||
C494,312 492,306 494,300 Z" fill="url(#body)"/>
|
||||
<g fill="none" stroke="#bcc6cf" stroke-width="2" stroke-linecap="round">
|
||||
<path d="M506,306 q34,-2 58,-2"/>
|
||||
<path d="M510,313 q30,0 54,0"/>
|
||||
</g>
|
||||
<!-- little grip hand on bar -->
|
||||
<path d="M566,296 q16,-2 22,8 q-10,8 -24,4 Z" fill="#eef2f5" stroke="#c3ccd5" stroke-width="1.5"/>
|
||||
|
||||
<!-- near leg + webbed foot on pedal -->
|
||||
<path d="M438,400 C446,432 444,456 434,478" fill="none" stroke="#e8822a" stroke-width="10" stroke-linecap="round"/>
|
||||
<path d="M420,484 q-10,4 -8,14 q22,6 40,-2 q4,-8 -2,-14 q-16,8 -30,2 Z" fill="#f0902f" stroke="#cf6a18" stroke-width="1.6"/>
|
||||
<g stroke="#cf6a18" stroke-width="1.4">
|
||||
<path d="M424,492 l-2,8"/><path d="M436,494 l0,8"/><path d="M448,492 l2,7"/>
|
||||
</g>
|
||||
|
||||
<!-- front basket with a fish -->
|
||||
<path d="M596,300 l52,0 l-7,40 l-38,0 Z" fill="#cf9a5a" stroke="#9c6e34" stroke-width="2"/>
|
||||
<g stroke="#9c6e34" stroke-width="1.5">
|
||||
<path d="M600,312 l44,0"/><path d="M601,324 l42,0"/>
|
||||
<path d="M612,302 l-3,38"/><path d="M624,302 l-1,38"/><path d="M636,302 l2,38"/>
|
||||
</g>
|
||||
<!-- fish peeking out -->
|
||||
<g transform="translate(640,300)">
|
||||
<path d="M0,0 C-14,-10 -36,-10 -48,-2 C-36,6 -14,6 0,-2 Z" fill="#5fb4d6" stroke="#357f9e" stroke-width="1.5"/>
|
||||
<path d="M0,-2 l12,-9 l-2,12 l-9,-1 Z" fill="#357f9e"/>
|
||||
<circle cx="-40" cy="-2" r="2.4" fill="#0e2f3d"/>
|
||||
</g>
|
||||
|
||||
<!-- light sand sparkle / motion puffs -->
|
||||
<g fill="#ffffff" opacity="0.5">
|
||||
<circle cx="250" cy="504" r="5"/><circle cx="232" cy="498" r="3"/><circle cx="266" cy="498" r="3"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 14 KiB |
Reference in new issue
Block a user