feat: add GPT-5.6-Sol benchmark results (#32)

This commit is contained in:
ZidongChen25 authored and GitHub committed 2026-07-09 20:23:36 +01:00
1 parent 2192d05ac8
commit 807bdac433
4 files changed
+2826

No files matched your search

+31
View File
@@ -0,0 +1,31 @@
{
"label": "GPT-5.6-Sol",
"vendor": "OpenAI",
"harness": "Codex CLI",
"effort": "ultra",
"artifactDir": "gpt-5-6-sol/codex-cli-ultra",
"order": 29,
"runs": {
"mythos-craft": {
"note": {
"zh": "在 Codex 应用内置的 Codex CLI 0.144.0-alpha.4 中以 GPT-5.6-Sol(ultra 思考档位),使用仓库标准中文提示词生成。此 case 使用独立、全新的 CODEX_HOME,并从空工作目录开始,未加载 harness 自带以外的任何 skill、插件、MCP、自定义规则或系统提示。原始 Ultra 回合在已完整写出可玩单 HTML 后,因宿主收到状态查询而在最终加固阶段中断;本页是中断前落盘的完整 fallback.html,仅原样重命名,未修改内容,也未重试生成。",
"en": "Generated from the repository's canonical Chinese prompt by GPT-5.6-Sol (ultra effort) in Codex CLI 0.144.0-alpha.4 bundled with the Codex app. This case used a separate clean CODEX_HOME and started in an empty working directory, with no skills, plugins, MCP servers, custom rules, or system prompts beyond those bundled with the harness. The original Ultra turn was host-interrupted during final hardening after it had written a complete playable single-file HTML; this page is that saved fallback.html, renamed verbatim with no content changes and no retry."
},
"contributor": "ZidongChen25"
},
"pelican-cycling": {
"note": {
"zh": "在 Codex 应用内置的 Codex CLI 0.144.0-alpha.4 中以 GPT-5.6-Sol(ultra 思考档位),使用仓库标准中文提示词一次生成。此 case 使用独立、全新的 CODEX_HOME,并从空工作目录开始,未加载 harness 自带以外的任何 skill、插件、MCP、自定义规则或系统提示;本页为未经人工修改的原始 agent 产物。",
"en": "Generated in one shot from the repository's canonical Chinese prompt by GPT-5.6-Sol (ultra effort) in Codex CLI 0.144.0-alpha.4 bundled with the Codex app. This case used a separate clean CODEX_HOME and started in an empty working directory, with no skills, plugins, MCP servers, custom rules, or system prompts beyond those bundled with the harness; this is the original agent artifact with no human edits."
},
"contributor": "ZidongChen25"
},
"skeleton-watch": {
"note": {
"zh": "在 Codex 应用内置的 Codex CLI 0.144.0-alpha.4 中以 GPT-5.6-Sol(ultra 思考档位),使用仓库标准中文提示词一次生成。此 case 使用独立、全新的 CODEX_HOME,并从空工作目录开始,未加载 harness 自带以外的任何 skill、插件、MCP、自定义规则或系统提示。原始 Ultra 回合已生成完整 HTML 写入调用,但宿主状态查询在该调用执行前将回合中断;本页通过原样重放 rollout 中完整记录的 apply_patch 参数恢复,未续写模型,也未修改内容。",
"en": "Generated in one shot from the repository's canonical Chinese prompt by GPT-5.6-Sol (ultra effort) in Codex CLI 0.144.0-alpha.4 bundled with the Codex app. This case used a separate clean CODEX_HOME and started in an empty working directory, with no skills, plugins, MCP servers, custom rules, or system prompts beyond those bundled with the harness. The original Ultra turn had produced the complete HTML write call when a host status query interrupted the turn before that call executed; this page was recovered by replaying the exact apply_patch payload recorded in the rollout, with no model continuation or content changes."
},
"contributor": "ZidongChen25"
}
}
}
@@ -0,0 +1,488 @@
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no">
<title>神域方块 · MYTHOS CRAFT</title>
<style>
:root{--gold:#f6d78b;--gold2:#b98743;--ink:#071317;--cyan:#71f4dc;--paper:#e9e0c5;--danger:#ff796c}
*{box-sizing:border-box}html,body{width:100%;height:100%;margin:0;overflow:hidden;background:#061116;color:var(--paper);font-family:"Noto Sans SC","Microsoft YaHei",system-ui,sans-serif}
body{user-select:none}canvas{display:block}#game{position:fixed;inset:0}
#game:after{content:"";position:absolute;inset:0;pointer-events:none;background:radial-gradient(circle at 50% 44%,transparent 48%,rgba(2,7,10,.2) 72%,rgba(0,0,0,.58) 115%);z-index:2}
.hidden{display:none!important}.glass{background:linear-gradient(145deg,rgba(7,22,25,.78),rgba(9,19,27,.56));border:1px solid rgba(246,215,139,.28);box-shadow:inset 0 0 28px rgba(113,244,220,.035),0 10px 34px rgba(0,0,0,.25);backdrop-filter:blur(10px)}
#loading{position:fixed;inset:0;z-index:100;background:#030b0e;display:flex;align-items:center;justify-content:center;letter-spacing:.18em;color:var(--gold)}
#loading .mark{width:34px;height:34px;margin-right:14px;border:1px solid var(--gold2);transform:rotate(45deg);animation:spin 2.2s linear infinite;box-shadow:0 0 24px rgba(113,244,220,.3)}
@keyframes spin{to{transform:rotate(405deg)}}
#veil{position:fixed;inset:0;z-index:40;display:grid;place-items:center;background:radial-gradient(circle at 50% 34%,rgba(18,56,59,.42),rgba(1,7,10,.88) 65%),linear-gradient(130deg,#06181a,#080b18);transition:opacity .3s}
#veil:before,#veil:after{content:"";position:absolute;inset:22px;border:1px solid rgba(246,215,139,.19);pointer-events:none}#veil:after{inset:30px;border-color:rgba(113,244,220,.08)}
.intro{position:relative;width:min(870px,calc(100vw - 42px));padding:48px 55px 38px;text-align:center;overflow:hidden}
.intro:before{content:"ᚱ ✦ ᛉ ◇ ᛟ";position:absolute;left:0;right:0;top:15px;color:rgba(246,215,139,.3);letter-spacing:1.1em;font-size:11px}
.eyebrow{font-size:11px;letter-spacing:.48em;color:var(--cyan);text-transform:uppercase;margin-bottom:14px}.intro h1{font-family:Georgia,"Noto Serif SC",serif;font-size:clamp(38px,7vw,76px);font-weight:500;line-height:.95;margin:0;background:linear-gradient(#fff8df 10%,#e5bd68 65%,#8e6334);-webkit-background-clip:text;color:transparent;text-shadow:0 9px 38px rgba(246,215,139,.12);letter-spacing:.08em}.en-title{font-family:Georgia,serif;font-size:12px;letter-spacing:.72em;color:rgba(233,224,197,.45);margin:14px 0 28px}.intro .lore{max-width:660px;margin:0 auto 27px;color:rgba(233,224,197,.72);line-height:1.9;font-size:14px}.intro .lore em{font-style:normal;color:var(--gold)}
.start-btn{border:1px solid rgba(246,215,139,.65);background:linear-gradient(90deg,rgba(105,74,36,.48),rgba(25,80,75,.42),rgba(105,74,36,.48));color:#fff3cf;padding:14px 37px;min-width:210px;font:500 13px inherit;letter-spacing:.32em;cursor:pointer;clip-path:polygon(7px 0,calc(100% - 7px) 0,100% 7px,100% calc(100% - 7px),calc(100% - 7px) 100%,7px 100%,0 calc(100% - 7px),0 7px);transition:.2s}.start-btn:hover{filter:brightness(1.28);box-shadow:0 0 30px rgba(113,244,220,.16);transform:translateY(-1px)}
.controls-row{display:flex;justify-content:center;gap:20px;flex-wrap:wrap;margin-top:28px;color:rgba(233,224,197,.48);font-size:11px;letter-spacing:.06em}.controls-row b{display:inline-grid;place-items:center;height:22px;min-width:22px;padding:0 5px;margin-right:5px;border:1px solid rgba(233,224,197,.24);color:rgba(255,255,255,.78);font-weight:500}
#hud{position:fixed;inset:0;z-index:5;pointer-events:none}.top-left{position:absolute;left:22px;top:20px;min-width:258px;padding:13px 15px}.brand{font-family:Georgia,"Noto Serif SC",serif;color:var(--gold);font-size:15px;letter-spacing:.18em}.brand small{display:block;color:rgba(233,224,197,.38);font:8px system-ui;letter-spacing:.42em;margin-top:4px}.readout{display:flex;gap:13px;margin-top:12px;padding-top:10px;border-top:1px solid rgba(246,215,139,.12);font:10px ui-monospace,monospace;color:rgba(233,224,197,.6)}.readout strong{color:#d8f7ec;font-weight:500}
#minimapWrap{position:absolute;right:22px;top:20px;width:170px;padding:10px}.map-title{display:flex;justify-content:space-between;align-items:center;color:rgba(246,215,139,.7);font:9px ui-monospace,monospace;letter-spacing:.18em;margin-bottom:7px}#minimap{width:148px;height:148px;border-radius:50%;border:1px solid rgba(246,215,139,.3);box-shadow:inset 0 0 24px rgba(0,0,0,.9)}
#objective{position:absolute;right:22px;top:226px;width:210px;padding:13px 14px}.obj-kicker{color:var(--cyan);font-size:8px;letter-spacing:.3em}.obj-name{margin:6px 0 5px;font-family:Georgia,"Noto Serif SC",serif;color:#fff0c6;font-size:14px}.obj-copy{font-size:10px;color:rgba(233,224,197,.52);line-height:1.65}.obj-distance{margin-top:8px;color:rgba(113,244,220,.78);font:9px ui-monospace,monospace}
#crosshair{position:absolute;left:50%;top:50%;width:20px;height:20px;transform:translate(-50%,-50%)}#crosshair:before,#crosshair:after{content:"";position:absolute;background:rgba(255,247,218,.9);box-shadow:0 0 4px rgba(0,0,0,.8)}#crosshair:before{left:9px;top:2px;width:1px;height:16px}#crosshair:after{left:2px;top:9px;width:16px;height:1px}#crosshair i{position:absolute;inset:6px;border:1px solid rgba(113,244,220,.5);transform:rotate(45deg)}
#targetText{position:absolute;left:50%;top:calc(50% + 23px);transform:translateX(-50%);white-space:nowrap;color:rgba(255,247,218,.74);font-size:9px;letter-spacing:.12em;text-shadow:0 1px 3px #000}
#toast{position:absolute;left:50%;top:23%;transform:translate(-50%,-8px);padding:9px 17px;border:1px solid rgba(246,215,139,.25);background:rgba(5,15,18,.74);font-size:11px;letter-spacing:.16em;color:#fff0c9;opacity:0;transition:.25s}#toast.show{opacity:1;transform:translate(-50%,0)}
#hotbar{position:absolute;left:50%;bottom:24px;transform:translateX(-50%);display:flex;gap:6px;padding:7px}.slot{position:relative;width:54px;height:54px;border:1px solid rgba(233,224,197,.17);background:rgba(1,8,11,.63);display:grid;place-items:center;transition:.14s}.slot.active{border-color:var(--gold);background:rgba(78,63,34,.72);transform:translateY(-5px);box-shadow:0 0 20px rgba(246,215,139,.16)}.slot .key{position:absolute;left:4px;top:2px;color:rgba(233,224,197,.4);font:8px ui-monospace}.slot .cube{width:25px;height:25px;border:1px solid rgba(255,255,255,.14);box-shadow:inset -6px -6px 0 rgba(0,0,0,.2),inset 3px 3px 0 rgba(255,255,255,.08);image-rendering:pixelated}.slot .name{position:absolute;bottom:2px;left:0;right:0;text-align:center;font-size:7px;color:rgba(255,255,255,.62)}
#meters{position:absolute;left:22px;bottom:26px;width:230px}.meter-label{display:flex;justify-content:space-between;color:rgba(233,224,197,.45);font:8px ui-monospace;margin-bottom:5px}.meter{height:3px;background:rgba(255,255,255,.1);overflow:hidden}.meter>i{display:block;height:100%;width:100%;background:linear-gradient(90deg,var(--gold2),var(--gold));transition:width .2s}.hints{margin-top:10px;color:rgba(233,224,197,.34);font-size:9px;line-height:1.8}
#debug{position:absolute;left:50%;top:16px;transform:translateX(-50%);font:9px ui-monospace;color:rgba(233,224,197,.5);letter-spacing:.06em}
@media(max-width:760px){#objective,#meters{display:none}.top-left{left:10px;top:10px;transform:scale(.86);transform-origin:top left}#minimapWrap{right:10px;top:10px;transform:scale(.78);transform-origin:top right}.slot{width:38px;height:44px}.slot .cube{width:19px;height:19px}.intro{padding:42px 22px 28px}.controls-row{gap:8px}.en-title{letter-spacing:.42em}}
</style>
</head>
<body>
<div id="loading"><div class="mark"></div><span>正在唤醒远古世界</span></div>
<main id="game"></main>
<section id="veil">
<div class="intro glass">
<div class="eyebrow">程序生成 · 无限神话世界</div>
<h1>神域方块</h1><div class="en-title">MYTHOS CRAFT</div>
<p class="lore">诸神离去后,九种原初方块仍在梦中生长。穿过<em>日冕圣门</em>,寻找倒悬天岛、月神仪庭与星骸巨龙;你破坏或放置的每一块石头,都会留在这个种子世界里。</p>
<button class="start-btn" id="startBtn">踏 入 神 域</button>
<div class="controls-row"><span><b>WASD</b>移动</span><span><b>鼠标</b>环视</span><span><b>左键</b>破坏</span><span><b>右键</b>放置</span><span><b>SPACE</b>跳跃</span><span><b>1—9</b>选方块</span></div>
</div>
</section>
<div id="hud" class="hidden">
<div class="top-left glass"><div class="brand">神域方块<small>MYTHOS CRAFT · SEED 9C4F</small></div><div class="readout"><span>坐标 <strong id="coords">0 / 0 / 0</strong></span><span>地域 <strong id="biome">翡翠荒原</strong></span><span>时刻 <strong id="daytime">晨曦</strong></span></div></div>
<div id="minimapWrap" class="glass"><div class="map-title"><span>星盘地志</span><span id="heading">N</span></div><canvas id="minimap" width="296" height="296"></canvas></div>
<div id="objective" class="glass"><div class="obj-kicker">最近的神话遗迹</div><div class="obj-name" id="objName">日冕圣门</div><div class="obj-copy" id="objCopy">旧日太阳神用鎏金符石铸成的天空之钥。</div><div class="obj-distance" id="objDistance">距离 0 格</div></div>
<div id="crosshair"><i></i></div><div id="targetText">凝视方块以采集</div><div id="toast"></div><div id="debug"></div>
<div id="meters"><div class="meter-label"><span>神行者体力</span><span id="staminaText">100</span></div><div class="meter"><i id="stamina"></i></div><div class="hints">SHIFT 疾跑 · SPACE 跳跃 · R 返回星印<br>滚轮切换方块 · ESC 释放鼠标</div></div>
<div id="hotbar" class="glass"></div>
</div>
<script src="https://cdn.jsdelivr.net/npm/three@0.160.0/build/three.min.js"></script>
<script>
'use strict';
(() => {
if (!window.THREE) {
document.getElementById('loading').innerHTML = '无法载入 Three.js,请检查网络后刷新';
return;
}
const $ = (q) => document.querySelector(q);
const game = $('#game'), loading = $('#loading'), veil = $('#veil'), hud = $('#hud');
const coordsEl = $('#coords'), biomeEl = $('#biome'), daytimeEl = $('#daytime');
const targetText = $('#targetText'), hotbar = $('#hotbar'), toastEl = $('#toast');
const mapCanvas = $('#minimap'), mapCtx = mapCanvas.getContext('2d');
const SEED = 0x9c4f21, CHUNK = 16, WORLD_H = 64, VIEW_RADIUS = 3;
const AIR = 0, GRASS = 1, DIRT = 2, STONE = 3, RUNE = 4, OBSIDIAN = 5;
const GOLD = 6, CRYSTAL = 7, WOOD = 8, LEAVES = 9;
const BLOCKS = [
null,
{name:'神域草', short:'草地', side:1, top:0, bottom:2, css:'#4f9b68', tint:[.92,1,.94]},
{name:'沃土', short:'泥土', side:2, top:2, bottom:2, css:'#845a3b', tint:[1,.94,.88]},
{name:'远古岩', short:'石头', side:3, top:3, bottom:3, css:'#7f8784', tint:[.94,.97,.96]},
{name:'符文石', short:'符石', side:4, top:4, bottom:3, css:'#3c776f', tint:[.79,1,.94]},
{name:'夜曜石', short:'夜曜', side:5, top:5, bottom:5, css:'#302845', tint:[.86,.79,1]},
{name:'鎏金砖', short:'鎏金', side:6, top:6, bottom:6, css:'#c28a35', tint:[1,.91,.65]},
{name:'星辉晶', short:'星晶', side:7, top:7, bottom:7, css:'#55cdd1', tint:[.72,1,1]},
{name:'世界木', short:'神木', side:9, top:8, bottom:8, css:'#765137', tint:[1,.91,.79]},
{name:'永青叶', short:'永叶', side:10, top:10, bottom:10, css:'#39725c', tint:[.75,1,.82]}
];
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}}
class Perlin {
constructor(seed){
const rnd=mulberry32(seed), p=Array.from({length:256},(_,i)=>i);
for(let i=255;i>0;i--){const j=Math.floor(rnd()*(i+1));[p[i],p[j]]=[p[j],p[i]]}
this.p=new Uint16Array(512);for(let i=0;i<512;i++)this.p[i]=p[i&255];
}
fade(t){return t*t*t*(t*(t*6-15)+10)} lerp(a,b,t){return a+t*(b-a)}
grad(h,x,y,z){const q=h&15,u=q<8?x:y,v=q<4?y:q===12||q===14?x:z;return ((q&1)?-u:u)+((q&2)?-v:v)}
noise3(x,y,z){
const 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);
const u=this.fade(x),v=this.fade(y),w=this.fade(z),p=this.p;
const A=p[X]+Y,AA=p[A]+Z,AB=p[A+1]+Z,B=p[X+1]+Y,BA=p[B]+Z,BB=p[B+1]+Z;
return this.lerp(this.lerp(this.lerp(this.grad(p[AA],x,y,z),this.grad(p[BA],x-1,y,z),u),this.lerp(this.grad(p[AB],x,y-1,z),this.grad(p[BB],x-1,y-1,z),u),v),this.lerp(this.lerp(this.grad(p[AA+1],x,y,z-1),this.grad(p[BA+1],x-1,y,z-1),u),this.lerp(this.grad(p[AB+1],x,y-1,z-1),this.grad(p[BB+1],x-1,y-1,z-1),u),v),w);
}
noise2(x,z){return this.noise3(x,31.416,z)}
}
const perlin=new Perlin(SEED), heightCache=new Map();
function fbm(x,z,oct=4,persist=.5){let sum=0,amp=1,freq=1,norm=0;for(let i=0;i<oct;i++){sum+=perlin.noise2(x*freq,z*freq)*amp;norm+=amp;amp*=persist;freq*=2.03}return sum/norm}
function hash2(x,z,s=SEED){let h=Math.imul(x|0,374761393)^Math.imul(z|0,668265263)^s;h=Math.imul(h^(h>>>13),1274126177);return ((h^(h>>>16))>>>0)/4294967295}
function hash3(x,y,z){return hash2((x|0)^Math.imul(y|0,1597334677),z|0,SEED^0x51ed270b)}
const clamp=(v,a,b)=>Math.max(a,Math.min(b,v)), floorDiv=(a,b)=>Math.floor(a/b), mod=(a,b)=>((a%b)+b)%b;
function heightAt(x,z){
x=Math.floor(x);z=Math.floor(z);const key=x+','+z;if(heightCache.has(key))return heightCache.get(key);
const continent=.5+.5*fbm(x*.006,z*.006,5,.52), hills=fbm(x*.026,z*.026,4,.52);
const ridge=1-Math.abs(perlin.noise2(x*.012-30,z*.012+18));
const highland=Math.max(0,fbm(x*.003+70,z*.003-40,3,.55)-.05);
let h=Math.floor(6+continent*10+hills*3+ridge*highland*15);
h=clamp(h,4,31);heightCache.set(key,h);if(heightCache.size>240000)heightCache.clear();return h;
}
function biomeAt(x,z){
const h=heightAt(x,z),m=perlin.noise2(x*.009+120,z*.009-90);
if(h>24)return {name:'诸神脊岭',color:'#727c7d'};
if(m>.28)return {name:'永青密林',color:'#326b55'};
if(m<-.3)return {name:'月盐荒漠',color:'#9d8b72'};
if(perlin.noise2(x*.004,z*.004)>.38)return {name:'星落高原',color:'#477c75'};
return {name:'翡翠荒原',color:'#548766'};
}
function terrainBlockAt(x,y,z){
if(y<0||y>=WORLD_H)return AIR;if(y===0)return OBSIDIAN;
const h=heightAt(x,z);if(y>h)return AIR;
if(y>3&&y<h-3){
const cave=perlin.noise3(x*.074,y*.083,z*.074)+.36*perlin.noise3(x*.151+8,y*.143,z*.151-5);
if(cave>.69)return AIR;
}
if(y===h)return h>25?STONE:GRASS;
if(y>=h-3)return DIRT;
const ore=hash3(x,y,z);
if(y<20&&ore>.994)return CRYSTAL;if(y<15&&ore<.004)return GOLD;if(y<24&&ore>.987)return RUNE;
return STONE;
}
function makeAtlas(){
const S=64,C=4,c=document.createElement('canvas');c.width=c.height=S*C;const g=c.getContext('2d');g.imageSmoothingEnabled=false;
const rand=mulberry32(SEED^0xada5), xy=(tile)=>[(tile%C)*S,Math.floor(tile/C)*S];
function noiseTile(tile,base,spots,amount=150){const [ox,oy]=xy(tile);g.fillStyle=base;g.fillRect(ox,oy,S,S);for(let i=0;i<amount;i++){const k=(rand()*spots.length)|0,sz=2+(rand()*5|0);g.globalAlpha=.25+rand()*.5;g.fillStyle=spots[k];g.fillRect(ox+(rand()*S|0),oy+(rand()*S|0),sz,sz)}g.globalAlpha=1}
noiseTile(0,'#4f925d',['#82b865','#315f48','#b1c86d']);
noiseTile(1,'#76533a',['#5a3d2d','#956b45','#3d7049']);{const[o,y]=xy(1);g.fillStyle='#4d8d59';g.fillRect(o,y,S,12);g.fillStyle='#79af63';for(let x=0;x<S;x+=6)g.fillRect(o+x,y+9,4,5+(x%12));}
noiseTile(2,'#79543a',['#563825','#9b6a44','#45382d'],180);
noiseTile(3,'#707876',['#929b96','#555c5b','#3f4d4e'],170);
noiseTile(4,'#315d59',['#427d73','#183e40','#789d86'],100);{const[o,y]=xy(4);g.strokeStyle='#77d8be';g.lineWidth=4;g.shadowColor='#63e9d0';g.shadowBlur=7;g.beginPath();g.moveTo(o+17,y+12);g.lineTo(o+17,y+49);g.lineTo(o+33,y+32);g.lineTo(o+47,y+48);g.moveTo(o+17,y+31);g.lineTo(o+39,y+12);g.stroke();g.shadowBlur=0}
noiseTile(5,'#201a31',['#3d3158','#151220','#695081'],120);{const[o,y]=xy(5);g.strokeStyle='rgba(160,114,204,.5)';g.lineWidth=2;for(let i=0;i<6;i++){g.beginPath();g.moveTo(o+rand()*S,y);g.lineTo(o+rand()*S,y+S);g.stroke()}}
noiseTile(6,'#aa762c',['#e5bd58','#725024','#f2d87e'],100);{const[o,y]=xy(6);g.strokeStyle='#5f4221';g.lineWidth=3;g.strokeRect(o+3,y+3,S-6,S-6);g.beginPath();g.moveTo(o+6,y+32);g.lineTo(o+32,y+6);g.lineTo(o+58,y+32);g.lineTo(o+32,y+58);g.closePath();g.stroke()}
noiseTile(7,'#43aeb2',['#a8ffff','#267c91','#6ee4d4'],90);{const[o,y]=xy(7);g.fillStyle='rgba(221,255,255,.52)';g.beginPath();g.moveTo(o+31,y+3);g.lineTo(o+58,y+28);g.lineTo(o+37,y+61);g.lineTo(o+6,y+38);g.closePath();g.fill();g.strokeStyle='#dbffff';g.lineWidth=2;g.beginPath();g.moveTo(o+31,y+3);g.lineTo(o+37,y+61);g.moveTo(o+6,y+38);g.lineTo(o+58,y+28);g.stroke()}
noiseTile(8,'#6d4b34',['#3b2b24','#956a42'],60);{const[o,y]=xy(8);g.strokeStyle='#3d2a21';g.lineWidth=4;for(let r=8;r<31;r+=8){g.beginPath();g.arc(o+32,y+32,r,0,Math.PI*2);g.stroke()}}
noiseTile(9,'#67472f',['#966744','#3f2d25'],80);{const[o,y]=xy(9);g.strokeStyle='#3a2821';g.lineWidth=4;for(let x=8;x<S;x+=14){g.beginPath();g.moveTo(o+x,y);g.lineTo(o+x-5,y+S);g.stroke()}}
noiseTile(10,'#32664f',['#4f9167','#1f493f','#76a965'],220);{const[o,y]=xy(10);g.fillStyle='rgba(151,220,143,.38)';for(let i=0;i<14;i++)g.fillRect(o+(rand()*S|0),y+(rand()*S|0),4,4)}
noiseTile(11,'#a5a69d',['#c7c7b6','#777f7c'],100);noiseTile(12,'#5f6574',['#9195a4','#363b49'],100);noiseTile(13,'#536d75',['#79a4a1','#263c46'],100);noiseTile(14,'#806951',['#ac9472','#51483d'],100);noiseTile(15,'#172f3c',['#315c66','#0b202c'],100);
const texture=new THREE.CanvasTexture(c);texture.colorSpace=THREE.SRGBColorSpace;texture.magFilter=THREE.NearestFilter;texture.minFilter=THREE.NearestFilter;texture.generateMipmaps=false;return texture;
}
const atlas=makeAtlas();
const scene=new THREE.Scene();scene.background=new THREE.Color(0x183b49);scene.fog=new THREE.FogExp2(0x183b49,.0115);
const camera=new THREE.PerspectiveCamera(72,innerWidth/innerHeight,.05,230);camera.rotation.order='YXZ';
const renderer=new THREE.WebGLRenderer({antialias:false,powerPreference:'high-performance'});renderer.setPixelRatio(Math.min(devicePixelRatio,1.65));renderer.setSize(innerWidth,innerHeight);renderer.outputColorSpace=THREE.SRGBColorSpace;renderer.toneMapping=THREE.ACESFilmicToneMapping;renderer.toneMappingExposure=1.12;game.appendChild(renderer.domElement);
const worldMaterial=new THREE.MeshStandardMaterial({map:atlas,vertexColors:true,roughness:.88,metalness:.05,side:THREE.FrontSide});
const hemi=new THREE.HemisphereLight(0x9de8ef,0x18201b,1.32);scene.add(hemi);
const sun=new THREE.DirectionalLight(0xffe5b1,2.15);sun.position.set(60,90,28);scene.add(sun);
const moonGlow=new THREE.PointLight(0x73e9dc,18,55,1.8);moonGlow.position.set(38,35,24);scene.add(moonGlow);
function makeSkyDetails(){
const rnd=mulberry32(SEED), starGeo=new THREE.BufferGeometry(),arr=[];
for(let i=0;i<520;i++){const a=rnd()*Math.PI*2,b=.12+rnd()*1.18,r=145+20*rnd();arr.push(Math.sin(b)*Math.cos(a)*r,Math.cos(b)*r+18,Math.sin(b)*Math.sin(a)*r)}
starGeo.setAttribute('position',new THREE.Float32BufferAttribute(arr,3));const stars=new THREE.Points(starGeo,new THREE.PointsMaterial({color:0xc9fff3,size:.52,transparent:true,opacity:.55,sizeAttenuation:true,depthWrite:false}));scene.add(stars);
const sunDisc=new THREE.Mesh(new THREE.SphereGeometry(4,18,12),new THREE.MeshBasicMaterial({color:0xffe0a0,fog:false}));sunDisc.name='sunDisc';scene.add(sunDisc);
const moonDisc=new THREE.Mesh(new THREE.SphereGeometry(2.1,16,10),new THREE.MeshBasicMaterial({color:0x9fffee,fog:false}));moonDisc.position.set(88,65,-90);scene.add(moonDisc);
return {stars,sunDisc,moonDisc};
}
const sky=makeSkyDetails();
const chunks=new Map(), editsByChunk=new Map(), loadQueue=[];
const chunkKey=(cx,cz)=>cx+','+cz, voxelIndex=(x,y,z)=>y*CHUNK*CHUNK+z*CHUNK+x;
function inChunk(chunk,x,z){return x>=chunk.cx*CHUNK&&x<(chunk.cx+1)*CHUNK&&z>=chunk.cz*CHUNK&&z<(chunk.cz+1)*CHUNK}
function put(chunk,x,y,z,type,mode='replace'){
x=Math.floor(x);y=Math.floor(y);z=Math.floor(z);if(y<0||y>=WORLD_H||!inChunk(chunk,x,z))return;
const i=voxelIndex(mod(x,CHUNK),y,mod(z,CHUNK)),old=chunk.data[i];
if(mode==='air'&&old!==AIR)return;if(mode==='solid'&&old===AIR)return;chunk.data[i]=type;
}
function box(chunk,x0,y0,z0,x1,y1,z1,type,mode='replace'){
for(let y=y0;y<=y1;y++)for(let z=z0;z<=z1;z++)for(let x=x0;x<=x1;x++)put(chunk,x,y,z,type,mode);
}
function sphere(chunk,cx,cy,cz,r,type,mode='replace'){
const rr=r*r;for(let y=Math.floor(cy-r);y<=Math.ceil(cy+r);y++)for(let z=Math.floor(cz-r);z<=Math.ceil(cz+r);z++)for(let x=Math.floor(cx-r);x<=Math.ceil(cx+r);x++)if((x-cx)**2+(y-cy)**2+(z-cz)**2<=rr)put(chunk,x,y,z,type,mode);
}
function lineBlocks(chunk,a,b,type,r=.6,mode='replace'){
const len=Math.hypot(b[0]-a[0],b[1]-a[1],b[2]-a[2]),n=Math.ceil(len*1.8);
for(let i=0;i<=n;i++){const t=i/n;sphere(chunk,a[0]+(b[0]-a[0])*t,a[1]+(b[1]-a[1])*t,a[2]+(b[2]-a[2])*t,r,type,mode)}
}
function overlaps(chunk,x0,x1,z0,z1){const ax=chunk.cx*CHUNK,az=chunk.cz*CHUNK;return ax<=x1&&ax+CHUNK-1>=x0&&az<=z1&&az+CHUNK-1>=z0}
const LANDMARKS=[
{name:'日冕圣门',x:0,z:-24,copy:'旧日太阳神用鎏金符石铸成的天空之钥。'},
{name:'永恒世界树',x:-36,z:27,copy:'它的根须穿过洞窟,枝叶托举着失落的星辰。'},
{name:'月神仪庭',x:39,z:25,copy:'潮汐祭司在此丈量月亮与梦的距离。'},
{name:'星骸巨龙',x:31,z:-47,copy:'一头从天幕坠落的古龙,只余发光的脊骨。'},
{name:'倒悬天岛',x:5,z:48,copy:'被神链锚定在云上的小型王国遗址。'}
];
const templeBase=heightAt(0,-24)+2, treeBase=heightAt(-36,27)+1, moonBase=heightAt(39,25)+2, dragonBase=heightAt(31,-47)+3;
function decorateNature(chunk){
const x0=chunk.cx*CHUNK,z0=chunk.cz*CHUNK;
// Deterministic trees. Candidate cells are evaluated beyond the chunk edge so crowns join seamlessly.
for(let gz=floorDiv(z0-5,9);gz<=floorDiv(z0+CHUNK+5,9);gz++)for(let gx=floorDiv(x0-5,9);gx<=floorDiv(x0+CHUNK+5,9);gx++){
const chance=hash2(gx,gz,SEED^0x4b1d);if(chance<.57)continue;
const tx=gx*9+2+Math.floor(hash2(gx,gz,71)*6),tz=gz*9+2+Math.floor(hash2(gx,gz,109)*6),h=heightAt(tx,tz);
const bio=biomeAt(tx,tz);if(h>24||bio.name==='月盐荒漠'||Math.abs(heightAt(tx+1,tz)-h)>1)continue;
if(Math.hypot(tx,tz+24)<15||Math.hypot(tx+36,tz-27)<11||Math.hypot(tx-39,tz-25)<12)continue;
const tall=3+Math.floor(hash2(gx,gz,131)*3);for(let y=h+1;y<=h+tall;y++)put(chunk,tx,y,tz,WOOD);
sphere(chunk,tx,h+tall+1,tz,2.45,LEAVES,'air');sphere(chunk,tx+1,h+tall,tz-1,1.8,LEAVES,'air');
}
// Small star-crystal outcrops.
for(let gz=floorDiv(z0-3,13);gz<=floorDiv(z0+CHUNK+3,13);gz++)for(let gx=floorDiv(x0-3,13);gx<=floorDiv(x0+CHUNK+3,13);gx++){
if(hash2(gx,gz,991)<.78)continue;const x=gx*13+3+(hash2(gx,gz,37)*7|0),z=gz*13+3+(hash2(gx,gz,43)*7|0),y=heightAt(x,z)+1;
const n=2+(hash2(gx,gz,59)*3|0);for(let i=0;i<n;i++){const dx=(hash2(gx+i,gz,61)*3|0)-1,dz=(hash2(gx,gz+i,67)*3|0)-1;lineBlocks(chunk,[x+dx,y,z+dz],[x+dx,y+2+(i%3),z+dz],CRYSTAL,.48,'air')}
}
}
function buildWanderingSpires(chunk){
const x0=chunk.cx*CHUNK,z0=chunk.cz*CHUNK;
for(let gz=floorDiv(z0-8,58);gz<=floorDiv(z0+CHUNK+8,58);gz++)for(let gx=floorDiv(x0-8,58);gx<=floorDiv(x0+CHUNK+8,58);gx++){
if(hash2(gx,gz,0x2211)<.34)continue;const x=gx*58+10+(hash2(gx,gz,4)*38|0),z=gz*58+10+(hash2(gx,gz,8)*38|0);
if(Math.min(...LANDMARKS.map(m=>Math.hypot(x-m.x,z-m.z)))<17)continue;
const base=heightAt(x,z)+1,height=7+(hash2(gx,gz,12)*9|0),rad=2+(hash2(gx,gz,16)>.65?1:0);
for(let y=base;y<base+height;y++){const rr=y>base+height-3?1:rad;for(let dz=-rr;dz<=rr;dz++)for(let dx=-rr;dx<=rr;dx++)if(dx*dx+dz*dz<=rr*rr+.2){const edge=Math.abs(dx)===rr||Math.abs(dz)===rr;put(chunk,x+dx,y,z+dz,edge&&y%4===0?RUNE:OBSIDIAN)}}
sphere(chunk,x,base+height,z,1.35,CRYSTAL,'air');
}
}
function buildTemple(chunk){
const X=0,Z=-24,B=templeBase;if(!overlaps(chunk,-15,15,Z-13,Z+23))return;
// Three-tier, rounded ceremonial platform.
for(let y=B-2;y<=B;y++){const shrink=B-y;for(let z=Z-10+shrink;z<=Z+10-shrink;z++)for(let x=-12+shrink;x<=12-shrink;x++)if(Math.abs(x)<=10||Math.abs(z-Z)<=8){const edge=Math.abs(x)>=10-shrink||Math.abs(z-Z)>=8-shrink;put(chunk,x,y,z,edge?RUNE:STONE)}}
// Long processional stair descending toward the player spawn.
for(let s=0;s<13;s++){const z=Z+11+s,y=B-Math.floor(s/4);for(let x=-4;x<=4;x++)put(chunk,x,y,z,(x===-4||x===4||s%4===0)?GOLD:STONE)}
// Sun gate: paired pylons and a stepped lintel.
for(const px of [-5,5]){box(chunk,px-1,B+1,Z+6,px+1,B+9,Z+8,RUNE);box(chunk,px,B+2,Z+7,px,B+8,Z+7,GOLD);sphere(chunk,px,B+11,Z+7,1.55,CRYSTAL,'air')}
box(chunk,-6,B+9,Z+6,6,B+10,Z+8,GOLD);box(chunk,-4,B+11,Z+7,4,B+11,Z+7,RUNE);
// Four sanctum towers, each capped with a luminous crystal brazier.
for(const [px,pz] of [[-10,Z-7],[10,Z-7],[-10,Z+7],[10,Z+7]]){
for(let y=B+1;y<=B+12;y++)for(let dz=-1;dz<=1;dz++)for(let dx=-1;dx<=1;dx++){const shell=Math.abs(dx)===1||Math.abs(dz)===1;put(chunk,px+dx,y,pz+dz,(y%4===0||!shell)?GOLD:RUNE)}
for(let d=-3;d<=3;d++){put(chunk,px+d,B+12,pz,GOLD);put(chunk,px,B+12,pz+d,GOLD)}sphere(chunk,px,B+14,pz,1.45,CRYSTAL,'air');
}
// Altar, constellation mosaic and suspended halo.
for(let r=4;r>=1;r--)for(let z=Z-r;z<=Z+r;z++)for(let x=-r;x<=r;x++)put(chunk,x,B+5-r,z,(Math.abs(x)+Math.abs(z-Z))%3===0?GOLD:RUNE);
lineBlocks(chunk,[0,B+2,Z],[0,B+8,Z],CRYSTAL,.72);
const hy=B+16;for(let i=0;i<64;i++){const a=i/64*Math.PI*2;put(chunk,Math.round(Math.cos(a)*7),hy+Math.round(Math.sin(a)*2),Z+Math.round(Math.sin(a)*7),i%8===0?CRYSTAL:GOLD)}
// Rune colonnade along both sides.
for(const sx of [-1,1])for(let p=-6;p<=6;p+=4){const x=sx*8,z=Z+p;box(chunk,x,B+1,z,x,B+7,z,RUNE);put(chunk,x,B+3,z,GOLD);put(chunk,x,B+7,z,CRYSTAL)}
}
function buildWorldTree(chunk){
const X=-36,Z=27,B=treeBase;if(!overlaps(chunk,X-15,X+15,Z-15,Z+15))return;
// Roots crawl across the terrain before converging into a twisting trunk.
const roots=[[11,1],[9,7],[-10,5],[-12,-2],[-7,-10],[5,-11]];
for(const [dx,dz] of roots){const ey=heightAt(X+dx,Z+dz)+1;lineBlocks(chunk,[X,B,Z],[X+dx,ey,Z+dz],WOOD,1.0)}
for(let y=0;y<=19;y++){const r=y<7?2.25:y<14?1.65:1.15;sphere(chunk,X+Math.sin(y*.52)*.8,B+y,Z+Math.cos(y*.43)*.7,r,WOOD)}
const branches=[[-10,16,-4],[-8,20,7],[9,17,5],[10,21,-5],[-3,23,10],[5,24,8],[1,25,-9]];
for(const [dx,dy,dz] of branches){lineBlocks(chunk,[X,B+12,Z],[X+dx,B+dy,Z+dz],WOOD,.85);sphere(chunk,X+dx,B+dy,Z+dz,4.1,LEAVES,'air');sphere(chunk,X+dx*.72,B+dy+2,Z+dz*.72,3.2,LEAVES,'air')}
sphere(chunk,X,B+24,Z,5.4,LEAVES,'air');
// A golden shrine is hidden inside the roots.
box(chunk,X-2,B,Z-3,X+2,B,Z+1,GOLD);box(chunk,X-2,B+1,Z-2,X-2,B+4,Z,RUNE);box(chunk,X+2,B+1,Z-2,X+2,B+4,Z,RUNE);box(chunk,X-2,B+4,Z-2,X+2,B+4,Z,GOLD);put(chunk,X,B+1,Z-2,CRYSTAL);
for(let y=B+22;y<=B+29;y+=2)sphere(chunk,X+(y%3)-1,y,Z,0.75,CRYSTAL,'air');
}
function buildMoonShrine(chunk){
const X=39,Z=25,B=moonBase;if(!overlaps(chunk,X-13,X+13,Z-13,Z+13))return;
for(let y=B-2;y<=B;y++){const r=11-(B-y);for(let z=Z-r;z<=Z+r;z++)for(let x=X-r;x<=X+r;x++){const d=Math.hypot(x-X,z-Z);if(d<=r)put(chunk,x,y,z,d>r-1.4?CRYSTAL:(x+z)%5===0?GOLD:STONE)}}
// Eight astronomer columns and an overhead orbit.
for(let i=0;i<8;i++){const a=i/8*Math.PI*2,px=Math.round(X+Math.cos(a)*8),pz=Math.round(Z+Math.sin(a)*8);box(chunk,px,B+1,pz,px,B+8,pz,i%2?RUNE:STONE);put(chunk,px,B+4,pz,GOLD);sphere(chunk,px,B+9,pz,1.05,CRYSTAL,'air')}
const oy=B+11;for(let i=0;i<72;i++){const a=i/72*Math.PI*2;put(chunk,Math.round(X+Math.cos(a)*7),oy+Math.round(Math.sin(a*3)),Math.round(Z+Math.sin(a)*7),i%9===0?CRYSTAL:GOLD)}
// Giant crescent standing in the meridian plane.
for(let y=-7;y<=7;y++)for(let x=-7;x<=7;x++){const outer=x*x+y*y<=49,cut=(x-3)*(x-3)+y*y<40;if(outer&&!cut)put(chunk,X+x,B+9+y,Z,GOLD)}
sphere(chunk,X-1,B+9,Z,1.2,CRYSTAL,'air');
// Four reflecting pools rendered as star glass mosaics.
for(const [dx,dz] of [[-5,-5],[5,-5],[-5,5],[5,5]])for(let z=-2;z<=2;z++)for(let x=-2;x<=2;x++)if(x*x+z*z<5)put(chunk,X+dx+x,B+1,Z+dz+z,CRYSTAL);
}
function buildDragon(chunk){
const B=dragonBase;if(!overlaps(chunk,12,55,-58,-34))return;
// Curving vertebrae and ribs form a walkable fossil across the valley.
const spine=[];for(let i=0;i<=33;i++){const x=16+i,z=-47+Math.round(Math.sin(i*.25)*2),y=B+Math.round(Math.sin(i*.18)*2);spine.push([x,y,z]);sphere(chunk,x,y,z,.85,i%5===0?CRYSTAL:RUNE)}
for(let i=0;i<spine.length-1;i++)lineBlocks(chunk,spine[i],spine[i+1],RUNE,.62);
for(let i=3;i<28;i+=4){const [x,y,z]=spine[i];for(const side of [-1,1]){let prev=[x,y,z];for(let k=1;k<=7;k++){const a=k/7*Math.PI*.92,next=[x,y+Math.round(Math.sin(a)*5),z+side*Math.round(k*1.12)];lineBlocks(chunk,prev,next,k===7?GOLD:RUNE,.52);prev=next}}}
// Horned skull at the eastern end.
sphere(chunk,51,B+1,-46,3.3,RUNE);box(chunk,51,B,-48,56,B+2,-44,RUNE);box(chunk,53,B+1,-47,57,B+1,-45,GOLD);put(chunk,53,B+2,-48,CRYSTAL);put(chunk,53,B+2,-44,CRYSTAL);
lineBlocks(chunk,[50,B+3,-48],[47,B+8,-53],OBSIDIAN,.65);lineBlocks(chunk,[50,B+3,-44],[47,B+8,-39],OBSIDIAN,.65);
// Tail tapers into the ground.
lineBlocks(chunk,[16,B,-47],[9,heightAt(9,-49)+1,-49],GOLD,.48);
}
function buildFloatingIsland(chunk){
const X=5,Z=48,Y=40;if(!overlaps(chunk,X-17,X+17,Z-14,Z+14))return;
// Main island: grassy crown and tapered stone underside.
for(let y=Y-11;y<=Y;y++)for(let z=Z-9;z<=Z+9;z++)for(let x=X-12;x<=X+12;x++){
const ny=(Y-y)/11,rx=12*(1-ny*.72),rz=9*(1-ny*.72),d=((x-X)/rx)**2+((z-Z)/rz)**2;
if(d<=1)put(chunk,x,y,z,y===Y?GRASS:y>Y-3?DIRT:(x+z+y)%11===0?RUNE:STONE);
}
// Chained anchors descend toward the world below.
for(const [dx,dz] of [[-7,-5],[7,-5],[-7,5],[7,5]])for(let y=Y-10;y>=9;y--)if(y%3!==1)put(chunk,X+dx+(y%4===0?1:0),y,Z+dz,OBSIDIAN);
// Ruined sky keep, bridge, beacon and satellite isles.
box(chunk,X-6,Y+1,Z-5,X+6,Y+1,Z+5,GOLD);for(const [dx,dz] of [[-6,-5],[6,-5],[-6,5],[6,5]]){box(chunk,X+dx,Y+2,Z+dz,X+dx,Y+10,Z+dz,RUNE);sphere(chunk,X+dx,Y+11,Z+dz,1.2,CRYSTAL,'air')}
box(chunk,X-6,Y+8,Z-5,X+6,Y+8,Z-5,GOLD);box(chunk,X-6,Y+8,Z+5,X+6,Y+8,Z+5,GOLD);lineBlocks(chunk,[X,Y+2,Z],[X,Y+13,Z],CRYSTAL,.62);
for(const [dx,dz,yy,r] of [[14,1,Y-3,4],[-15,-2,Y+4,3],[2,13,Y+1,3]])for(let y=yy-4;y<=yy;y++)for(let z=-r;z<=r;z++)for(let x=-r;x<=r;x++)if(x*x+z*z<=(r-(yy-y)*.45)**2)put(chunk,X+dx+x,y,Z+dz+z,y===yy?GRASS:STONE);
}
function generateChunk(cx,cz){
const chunk={cx,cz,data:new Uint8Array(CHUNK*WORLD_H*CHUNK),mesh:null,dirty:true};
for(let lz=0;lz<CHUNK;lz++)for(let lx=0;lx<CHUNK;lx++){const x=cx*CHUNK+lx,z=cz*CHUNK+lz,h=heightAt(x,z);for(let y=0;y<=h;y++)chunk.data[voxelIndex(lx,y,lz)]=terrainBlockAt(x,y,z)}
decorateNature(chunk);buildWanderingSpires(chunk);buildTemple(chunk);buildWorldTree(chunk);buildMoonShrine(chunk);buildDragon(chunk);buildFloatingIsland(chunk);
const stored=editsByChunk.get(chunkKey(cx,cz));if(stored)for(const [idx,type] of stored)chunk.data[idx]=type;
return chunk;
}
const FACES=[
{n:[1,0,0], side:'side', shade:.87, v:[[1,0,0],[1,1,0],[1,1,1],[1,0,1]]},
{n:[-1,0,0],side:'side', shade:.73, v:[[0,0,1],[0,1,1],[0,1,0],[0,0,0]]},
{n:[0,1,0], side:'top', shade:1, v:[[0,1,1],[1,1,1],[1,1,0],[0,1,0]]},
{n:[0,-1,0],side:'bottom',shade:.54,v:[[0,0,0],[1,0,0],[1,0,1],[0,0,1]]},
{n:[0,0,1], side:'side', shade:.81,v:[[1,0,1],[1,1,1],[0,1,1],[0,0,1]]},
{n:[0,0,-1],side:'side', shade:.68,v:[[0,0,0],[0,1,0],[1,1,0],[1,0,0]]}
];
const meshGroup=new THREE.Group();meshGroup.name='Voxel world';scene.add(meshGroup);
function getVoxel(x,y,z){
x=Math.floor(x);y=Math.floor(y);z=Math.floor(z);if(y<0||y>=WORLD_H)return AIR;
const cx=floorDiv(x,CHUNK),cz=floorDiv(z,CHUNK),chunk=chunks.get(chunkKey(cx,cz));
if(chunk)return chunk.data[voxelIndex(mod(x,CHUNK),y,mod(z,CHUNK))];
return terrainBlockAt(x,y,z);
}
function buildChunkMesh(chunk){
const pos=[],normal=[],uv=[],color=[],indices=[];let vi=0;
for(let y=0;y<WORLD_H;y++)for(let lz=0;lz<CHUNK;lz++)for(let lx=0;lx<CHUNK;lx++){
const type=chunk.data[voxelIndex(lx,y,lz)];if(type===AIR)continue;const def=BLOCKS[type],wx=chunk.cx*CHUNK+lx,wz=chunk.cz*CHUNK+lz;
for(const face of FACES){if(getVoxel(wx+face.n[0],y+face.n[1],wz+face.n[2])!==AIR)continue;
const tile=def[face.side],tx=tile%4,ty=Math.floor(tile/4),pad=.0018,u0=tx/4+pad,u1=(tx+1)/4-pad,v0=1-(ty+1)/4+pad,v1=1-ty/4-pad;
const uvs=[[u0,v0],[u0,v1],[u1,v1],[u1,v0]],tint=def.tint;
for(let k=0;k<4;k++){const p=face.v[k];pos.push(lx+p[0],y+p[1],lz+p[2]);normal.push(...face.n);uv.push(...uvs[k]);const sparkle=type===CRYSTAL?1.12:1;color.push(clamp(tint[0]*face.shade*sparkle,0,1),clamp(tint[1]*face.shade*sparkle,0,1),clamp(tint[2]*face.shade*sparkle,0,1))}
indices.push(vi,vi+1,vi+2,vi,vi+2,vi+3);vi+=4;
}
}
const geo=new THREE.BufferGeometry();geo.setAttribute('position',new THREE.Float32BufferAttribute(pos,3));geo.setAttribute('normal',new THREE.Float32BufferAttribute(normal,3));geo.setAttribute('uv',new THREE.Float32BufferAttribute(uv,2));geo.setAttribute('color',new THREE.Float32BufferAttribute(color,3));geo.setIndex(indices);geo.computeBoundingSphere();
const mesh=new THREE.Mesh(geo,worldMaterial);mesh.position.set(chunk.cx*CHUNK,0,chunk.cz*CHUNK);mesh.userData.chunk=chunk;mesh.matrixAutoUpdate=false;mesh.updateMatrix();
if(chunk.mesh){meshGroup.remove(chunk.mesh);chunk.mesh.geometry.dispose()}chunk.mesh=mesh;meshGroup.add(mesh);chunk.dirty=false;
}
function rebuildChunk(cx,cz){const ch=chunks.get(chunkKey(cx,cz));if(ch)buildChunkMesh(ch)}
const queued=new Set();
function queueChunk(cx,cz,priority=false){const k=chunkKey(cx,cz);if(chunks.has(k)||queued.has(k))return;queued.add(k);priority?loadQueue.unshift([cx,cz]):loadQueue.push([cx,cz])}
function loadChunk(cx,cz){const k=chunkKey(cx,cz);if(chunks.has(k))return;const chunk=generateChunk(cx,cz);chunks.set(k,chunk);buildChunkMesh(chunk);queued.delete(k)}
function queueNeighborhood(x,z){
const cx=floorDiv(x,CHUNK),cz=floorDiv(z,CHUNK),todo=[];
for(let dz=-VIEW_RADIUS;dz<=VIEW_RADIUS;dz++)for(let dx=-VIEW_RADIUS;dx<=VIEW_RADIUS;dx++)if(dx*dx+dz*dz<=(VIEW_RADIUS+.35)**2)todo.push([cx+dx,cz+dz,dx*dx+dz*dz]);
todo.sort((a,b)=>a[2]-b[2]);for(const q of todo)queueChunk(q[0],q[1]);
for(const [k,ch] of chunks){if(Math.max(Math.abs(ch.cx-cx),Math.abs(ch.cz-cz))>VIEW_RADIUS+2){meshGroup.remove(ch.mesh);ch.mesh.geometry.dispose();chunks.delete(k)}}
}
function processLoadQueue(){
const start=performance.now();while(loadQueue.length&&performance.now()-start<9){const q=loadQueue.shift();if(!chunks.has(chunkKey(q[0],q[1])))loadChunk(q[0],q[1]);else queued.delete(chunkKey(q[0],q[1]))}
}
function setVoxel(x,y,z,type){
x=Math.floor(x);y=Math.floor(y);z=Math.floor(z);if(y<=0||y>=WORLD_H)return false;
const cx=floorDiv(x,CHUNK),cz=floorDiv(z,CHUNK),k=chunkKey(cx,cz),idx=voxelIndex(mod(x,CHUNK),y,mod(z,CHUNK));let edit=editsByChunk.get(k);if(!edit){edit=new Map();editsByChunk.set(k,edit)}edit.set(idx,type);
const chunk=chunks.get(k);if(!chunk)return false;chunk.data[idx]=type;rebuildChunk(cx,cz);
if(mod(x,CHUNK)===0)rebuildChunk(cx-1,cz);if(mod(x,CHUNK)===CHUNK-1)rebuildChunk(cx+1,cz);if(mod(z,CHUNK)===0)rebuildChunk(cx,cz-1);if(mod(z,CHUNK)===CHUNK-1)rebuildChunk(cx,cz+1);queueSave();return true;
}
const selection=new THREE.LineSegments(new THREE.EdgesGeometry(new THREE.BoxGeometry(1.012,1.012,1.012)),new THREE.LineBasicMaterial({color:0xffe3a0,transparent:true,opacity:.9,depthTest:false}));selection.visible=false;selection.renderOrder=5;scene.add(selection);
const raycaster=new THREE.Raycaster();raycaster.far=7.5;const centerNdc=new THREE.Vector2(0,0);let currentHit=null;
function traceTarget(){
raycaster.setFromCamera(centerNdc,camera);const hits=raycaster.intersectObjects(meshGroup.children,false);currentHit=null;
for(const hit of hits){if(hit.distance>7.5||!hit.face)continue;const n=hit.face.normal,breakPos=hit.point.clone().addScaledVector(n,-.012),placePos=hit.point.clone().addScaledVector(n,.012);currentHit={hit,normal:n.clone(),break:{x:Math.floor(breakPos.x),y:Math.floor(breakPos.y),z:Math.floor(breakPos.z)},place:{x:Math.floor(placePos.x),y:Math.floor(placePos.y),z:Math.floor(placePos.z)}};break}
if(currentHit){const p=currentHit.break,type=getVoxel(p.x,p.y,p.z);selection.visible=true;selection.position.set(p.x+.5,p.y+.5,p.z+.5);targetText.textContent=`${BLOCKS[type]?.name||'未知方块'} · 左键采集 / 右键放置`;}
else{selection.visible=false;targetText.textContent='凝视方块以采集'}
}
let saveTimer=0;
function restoreEdits(){try{const raw=localStorage.getItem('mythos-craft-edits-v1');if(!raw)return;for(const [k,entries] of JSON.parse(raw))editsByChunk.set(k,new Map(entries))}catch(e){console.warn('存档读取失败',e)}}
function queueSave(){clearTimeout(saveTimer);saveTimer=setTimeout(()=>{try{localStorage.setItem('mythos-craft-edits-v1',JSON.stringify([...editsByChunk].map(([k,v])=>[k,[...v]])))}catch(e){}},600)}
restoreEdits();
let selectedBlock=RUNE, toastTimer=0;
function buildHotbar(){
hotbar.innerHTML='';for(let i=1;i<=9;i++){const d=BLOCKS[i],slot=document.createElement('div');slot.className='slot'+(i===selectedBlock?' active':'');slot.dataset.block=i;slot.innerHTML=`<span class="key">${i}</span><i class="cube" style="background:${d.css}"></i><span class="name">${d.short}</span>`;hotbar.appendChild(slot)}
}
function selectBlock(n,announce=true){selectedBlock=clamp(n,1,9);for(const el of hotbar.children)el.classList.toggle('active',+el.dataset.block===selectedBlock);if(announce)showToast(`已选择 · ${BLOCKS[selectedBlock].name}`)}
function showToast(text){toastEl.textContent=text;toastEl.classList.add('show');clearTimeout(toastTimer);toastTimer=setTimeout(()=>toastEl.classList.remove('show'),1500)}
buildHotbar();
const player={pos:new THREE.Vector3(.5,20,8.5),vel:new THREE.Vector3(),radius:.29,eye:1.62,head:.18,onGround:false,stamina:100};
const spawn={x:.5,z:8.5,y:20};let yaw=0,pitch=-.04;
function highestSolid(x,z){for(let y=WORLD_H-2;y>=1;y--)if(getVoxel(x,y,z)!==AIR)return y;return heightAt(x,z)}
function respawn(silent=false){const y=highestSolid(spawn.x,spawn.z)+1+player.eye+.03;player.pos.set(spawn.x,y,spawn.z);player.vel.set(0,0,0);yaw=0;pitch=-.05;if(!silent)showToast('星印将你送回日冕古道')}
function playerCollides(pos){
const e=.001,minX=Math.floor(pos.x-player.radius+e),maxX=Math.floor(pos.x+player.radius-e),minY=Math.floor(pos.y-player.eye+e),maxY=Math.floor(pos.y+player.head-e),minZ=Math.floor(pos.z-player.radius+e),maxZ=Math.floor(pos.z+player.radius-e);
for(let y=minY;y<=maxY;y++)for(let z=minZ;z<=maxZ;z++)for(let x=minX;x<=maxX;x++)if(getVoxel(x,y,z)!==AIR)return true;return false;
}
function moveAxis(axis,delta){if(!delta)return;const steps=Math.max(1,Math.ceil(Math.abs(delta)/.16)),d=delta/steps;for(let i=0;i<steps;i++){player.pos[axis]+=d;if(playerCollides(player.pos)){player.pos[axis]-=d;player.vel[axis]=0;if(axis==='y'&&d<0)player.onGround=true;break}}}
const keys=new Set();let locked=false,started=false;
function updatePlayer(dt){
if(!locked){camera.position.copy(player.pos);return}
const f=(keys.has('KeyW')?1:0)-(keys.has('KeyS')?1:0),s=(keys.has('KeyD')?1:0)-(keys.has('KeyA')?1:0),mag=Math.hypot(f,s)||1;
const sprint=keys.has('ShiftLeft')&&f>0&&player.stamina>1,speed=sprint?7.5:4.65,tx=(-Math.sin(yaw)*f+Math.cos(yaw)*s)/mag*speed,tz=(-Math.cos(yaw)*f-Math.sin(yaw)*s)/mag*speed;
const response=1-Math.exp(-(player.onGround?13:4.5)*dt);player.vel.x+=(tx-player.vel.x)*response;player.vel.z+=(tz-player.vel.z)*response;
if(f===0&&s===0){const drag=Math.exp(-(player.onGround?15:2)*dt);player.vel.x*=drag;player.vel.z*=drag}
if(sprint&&(f||s))player.stamina=Math.max(0,player.stamina-dt*22);else player.stamina=Math.min(100,player.stamina+dt*14);
player.vel.y-=27*dt;player.onGround=false;moveAxis('x',player.vel.x*dt);moveAxis('z',player.vel.z*dt);moveAxis('y',player.vel.y*dt);
if(player.pos.y<-8)respawn();camera.position.copy(player.pos);camera.rotation.x=pitch;camera.rotation.y=yaw;
$('#stamina').style.width=player.stamina+'%';$('#staminaText').textContent=Math.round(player.stamina);
}
function blockIntersectsPlayer(x,y,z){const minX=player.pos.x-player.radius,maxX=player.pos.x+player.radius,minY=player.pos.y-player.eye,maxY=player.pos.y+player.head,minZ=player.pos.z-player.radius,maxZ=player.pos.z+player.radius;return x<maxX&&x+1>minX&&y<maxY&&y+1>minY&&z<maxZ&&z+1>minZ}
let audioCtx=null,lastAction=0;const particles=[];
function initAudio(){if(!audioCtx)audioCtx=new (window.AudioContext||window.webkitAudioContext)();if(audioCtx.state==='suspended')audioCtx.resume()}
function tone(freq,dur=.08,type='triangle',vol=.025,slide=0){if(!audioCtx)return;const t=audioCtx.currentTime,o=audioCtx.createOscillator(),g=audioCtx.createGain();o.type=type;o.frequency.setValueAtTime(freq,t);o.frequency.exponentialRampToValueAtTime(Math.max(20,freq+slide),t+dur);g.gain.setValueAtTime(vol,t);g.gain.exponentialRampToValueAtTime(.0001,t+dur);o.connect(g).connect(audioCtx.destination);o.start(t);o.stop(t+dur)}
function burst(x,y,z,type){
const color=new THREE.Color(BLOCKS[type]?.css||'#aaa');for(let i=0;i<7;i++){const mesh=new THREE.Mesh(new THREE.BoxGeometry(.11,.11,.11),new THREE.MeshBasicMaterial({color}));mesh.position.set(x+.5+(Math.random()-.5)*.4,y+.5+(Math.random()-.5)*.4,z+.5+(Math.random()-.5)*.4);scene.add(mesh);particles.push({mesh,life:.48,vel:new THREE.Vector3((Math.random()-.5)*2.5,1+Math.random()*2,(Math.random()-.5)*2.5)})}
}
function updateParticles(dt){for(let i=particles.length-1;i>=0;i--){const p=particles[i];p.life-=dt;p.vel.y-=7*dt;p.mesh.position.addScaledVector(p.vel,dt);p.mesh.rotation.x+=dt*5;p.mesh.rotation.y+=dt*4;p.mesh.material.opacity=Math.max(0,p.life*2);p.mesh.material.transparent=true;if(p.life<=0){scene.remove(p.mesh);p.mesh.geometry.dispose();p.mesh.material.dispose();particles.splice(i,1)}}}
function useTool(button){
if(!locked||performance.now()-lastAction<145)return;lastAction=performance.now();traceTarget();if(!currentHit)return;
if(button===0){const p=currentHit.break,type=getVoxel(p.x,p.y,p.z);if(type!==AIR&&p.y>0&&setVoxel(p.x,p.y,p.z,AIR)){burst(p.x,p.y,p.z,type);tone(150,.095,'square',.018,-90);showToast(`采集 · ${BLOCKS[type].name}`)}}
if(button===2){const p=currentHit.place;if(p.y>0&&p.y<WORLD_H&&getVoxel(p.x,p.y,p.z)===AIR&&!blockIntersectsPlayer(p.x,p.y,p.z)&&setVoxel(p.x,p.y,p.z,selectedBlock)){tone(105,.07,'triangle',.025,45);showToast(`放置 · ${BLOCKS[selectedBlock].name}`)}}
}
document.addEventListener('keydown',e=>{
keys.add(e.code);if(locked&&['Space','ArrowUp','ArrowDown','ArrowLeft','ArrowRight'].includes(e.code))e.preventDefault();
if(e.code==='Space'&&locked&&player.onGround){player.vel.y=8.8;player.onGround=false;tone(170,.06,'sine',.014,40)}
if(/^Digit[1-9]$/.test(e.code))selectBlock(+e.code.slice(5));if(e.code==='KeyR')respawn();
});
document.addEventListener('keyup',e=>keys.delete(e.code));
document.addEventListener('mousemove',e=>{if(!locked)return;yaw-=e.movementX*.00215;pitch=clamp(pitch-e.movementY*.00215,-Math.PI/2+.015,Math.PI/2-.015)});
document.addEventListener('mousedown',e=>useTool(e.button));document.addEventListener('contextmenu',e=>e.preventDefault());
document.addEventListener('wheel',e=>{if(!locked)return;selectBlock(mod(selectedBlock-1+(e.deltaY>0?1:-1),9)+1);e.preventDefault()},{passive:false});
$('#startBtn').addEventListener('click',()=>{initAudio();renderer.domElement.requestPointerLock()});
document.addEventListener('pointerlockchange',()=>{locked=document.pointerLockElement===renderer.domElement;keys.clear();if(locked){started=true;veil.classList.add('hidden');hud.classList.remove('hidden');showToast('向北沿古道寻找日冕圣门')}else if(started){veil.classList.remove('hidden');$('#startBtn').textContent='继 续 探 索'}});
function makeAtmosphere(){
const rnd=mulberry32(SEED^0x7799),motePos=[];for(let i=0;i<180;i++)motePos.push((rnd()-.5)*50,(rnd()-.5)*28,(rnd()-.5)*50);
const geo=new THREE.BufferGeometry();geo.setAttribute('position',new THREE.Float32BufferAttribute(motePos,3));const motes=new THREE.Points(geo,new THREE.PointsMaterial({color:0x92f7dc,size:.105,transparent:true,opacity:.62,depthWrite:false,blending:THREE.AdditiveBlending}));scene.add(motes);
const clouds=new THREE.Group(),mat=new THREE.MeshBasicMaterial({color:0xcde4dd,transparent:true,opacity:.07,depthWrite:false});
for(let i=0;i<18;i++){const m=new THREE.Mesh(new THREE.BoxGeometry(10+rnd()*18,.7+rnd()*1.4,4+rnd()*9),mat);m.position.set((rnd()-.5)*170,38+rnd()*20,(rnd()-.5)*170);m.rotation.y=rnd()*.5;clouds.add(m)}scene.add(clouds);return {motes,clouds};
}
const atmosphere=makeAtmosphere();let dayPhase=.18;
const dawnColor=new THREE.Color(0xa66b58),dayColor=new THREE.Color(0x5e9aaa),nightColor=new THREE.Color(0x07121f),skyColor=new THREE.Color();
function updateSky(dt,time){
dayPhase=(dayPhase+dt*.00125)%1;const a=dayPhase*Math.PI*2,sunUp=clamp(Math.sin(a)*1.7+.14,0,1),twilight=clamp(1-Math.abs(Math.sin(a))*3.2,0,1);
skyColor.copy(nightColor).lerp(dayColor,sunUp).lerp(dawnColor,twilight*.48);scene.background.copy(skyColor);scene.fog.color.copy(skyColor);
const sx=Math.cos(a)*115,sy=Math.sin(a)*105,sz=42;sun.position.set(player.pos.x+sx,sy,player.pos.z+sz);sun.target.position.set(player.pos.x,0,player.pos.z);sun.intensity=.12+sunUp*2.25;hemi.intensity=.32+sunUp*1.05;
sky.sunDisc.position.set(player.pos.x+sx,sy,player.pos.z+sz);sky.sunDisc.visible=sy>-12;sky.moonDisc.position.set(player.pos.x-sx*.82,-sy*.75+18,player.pos.z-sz*1.8);sky.moonDisc.visible=sy<30;
sky.stars.position.set(player.pos.x,0,player.pos.z);sky.stars.material.opacity=.12+(1-sunUp)*.75;sky.stars.rotation.y=time*.000008;
atmosphere.motes.position.set(player.pos.x,player.pos.y,player.pos.z);atmosphere.motes.rotation.y=time*.000025;atmosphere.motes.position.y+=Math.sin(time*.0004)*1.5;
atmosphere.clouds.position.x=time*.00032;const wrap=180;if(atmosphere.clouds.position.x-player.pos.x>wrap)atmosphere.clouds.position.x-=wrap*2;if(atmosphere.clouds.position.x-player.pos.x<-wrap)atmosphere.clouds.position.x+=wrap*2;
daytimeEl.textContent=sy>55?'曜日':sy>8?(Math.cos(a)>0?'晨曦':'暮色'):sy>-18?'星昏':'月夜';
}
function updateWorldReadout(){
const x=Math.floor(player.pos.x),y=Math.floor(player.pos.y-player.eye),z=Math.floor(player.pos.z),bio=biomeAt(x,z);coordsEl.textContent=`${x} / ${y} / ${z}`;biomeEl.textContent=bio.name;
const dir=mod(Math.round((-yaw)/(Math.PI/4)),8),names=['N','NE','E','SE','S','SW','W','NW'];$('#heading').textContent=names[dir];
let nearest=LANDMARKS[0],dist=Infinity;for(const m of LANDMARKS){const d=Math.hypot(player.pos.x-m.x,player.pos.z-m.z);if(d<dist){nearest=m;dist=d}}
$('#objName').textContent=nearest.name;$('#objCopy').textContent=nearest.copy;$('#objDistance').textContent=dist<9?'你已抵达此处':`距离 ${Math.round(dist)} 格 · 朝 ${Math.abs(nearest.x-player.pos.x)>Math.abs(nearest.z-player.pos.z)?nearest.x>player.pos.x?'东':'西':nearest.z>player.pos.z?'南':'北'}`;
$('#debug').textContent=`区块 ${floorDiv(x,CHUNK)}, ${floorDiv(z,CHUNK)} · 已载入 ${chunks.size} · 地形队列 ${loadQueue.length}`;
}
function drawMinimap(){
const W=mapCanvas.width,c=W/2,scale=2.18,step=6;mapCtx.clearRect(0,0,W,W);mapCtx.save();mapCtx.beginPath();mapCtx.arc(c,c,c-2,0,Math.PI*2);mapCtx.clip();mapCtx.fillStyle='#071216';mapCtx.fillRect(0,0,W,W);
for(let py=-c;py<c;py+=step)for(let px=-c;px<c;px+=step){if(px*px+py*py>c*c)continue;const wx=Math.floor(player.pos.x+px/scale),wz=Math.floor(player.pos.z+py/scale),h=heightAt(wx,wz),bio=biomeAt(wx,wz);mapCtx.fillStyle=h>24?'#77817e':bio.color;mapCtx.globalAlpha=.55+Math.min(.35,h/70);mapCtx.fillRect(c+px,c+py,step+1,step+1)}mapCtx.globalAlpha=1;
mapCtx.strokeStyle='rgba(244,217,150,.18)';mapCtx.lineWidth=1;for(const r of [46,92,136]){mapCtx.beginPath();mapCtx.arc(c,c,r,0,Math.PI*2);mapCtx.stroke()}mapCtx.beginPath();mapCtx.moveTo(c,3);mapCtx.lineTo(c,W-3);mapCtx.moveTo(3,c);mapCtx.lineTo(W-3,c);mapCtx.stroke();
for(const m of LANDMARKS){let dx=(m.x-player.pos.x)*scale,dz=(m.z-player.pos.z)*scale,rr=Math.hypot(dx,dz);if(rr>c-11){dx*= (c-11)/rr;dz*=(c-11)/rr}mapCtx.save();mapCtx.translate(c+dx,c+dz);mapCtx.rotate(Math.PI/4);mapCtx.fillStyle=m.name==='日冕圣门'?'#f1c66c':'#76ead5';mapCtx.shadowColor=mapCtx.fillStyle;mapCtx.shadowBlur=7;mapCtx.fillRect(-4,-4,8,8);mapCtx.restore()}
mapCtx.save();mapCtx.translate(c,c);mapCtx.rotate(-yaw);mapCtx.fillStyle='#fff5cf';mapCtx.shadowColor='#fff';mapCtx.shadowBlur=6;mapCtx.beginPath();mapCtx.moveTo(0,-10);mapCtx.lineTo(6,7);mapCtx.lineTo(0,4);mapCtx.lineTo(-6,7);mapCtx.closePath();mapCtx.fill();mapCtx.restore();mapCtx.restore();
}
// The spawn ring is loaded synchronously; the wider world streams in while the title screen is visible.
for(let dz=-1;dz<=1;dz++)for(let dx=-1;dx<=1;dx++)loadChunk(dx,dz);
queueNeighborhood(spawn.x,spawn.z);respawn(true);camera.position.copy(player.pos);scene.add(sun.target);
loading.classList.add('hidden');
let last=performance.now(),frame=0,maintenance=0,mapTimer=0;
function animate(now){
requestAnimationFrame(animate);const dt=Math.min(.045,(now-last)/1000||0);last=now;frame++;processLoadQueue();updatePlayer(dt);updateParticles(dt);updateSky(dt,now);
maintenance+=dt;mapTimer+=dt;if(maintenance>.8){maintenance=0;queueNeighborhood(player.pos.x,player.pos.z);updateWorldReadout()}if(mapTimer>.65){mapTimer=0;drawMinimap()}if(locked&&frame%3===0)traceTarget();
renderer.render(scene,camera);
}
updateWorldReadout();drawMinimap();requestAnimationFrame(animate);
addEventListener('resize',()=>{camera.aspect=innerWidth/innerHeight;camera.updateProjectionMatrix();renderer.setPixelRatio(Math.min(devicePixelRatio,1.65));renderer.setSize(innerWidth,innerHeight)});
})();
</script>
</body>
</html>
@@ -0,0 +1,357 @@
<svg xmlns="http://www.w3.org/2000/svg" width="1600" height="900" viewBox="0 0 1600 900" role="img" aria-labelledby="title desc">
<title id="title">海边骑自行车的鹈鹕</title>
<desc id="desc">一只羽毛细腻的鹈鹕迎着海风骑行在金色海岸小路上,青绿色海浪、暖阳、海鸥和摇曳的沙丘草环绕四周。</desc>
<defs>
<linearGradient id="sky" x1="0" y1="0" x2="0" y2="1">
<stop offset="0" stop-color="#51b7d4"/>
<stop offset=".5" stop-color="#9fdfeb"/>
<stop offset="1" stop-color="#ffd7aa"/>
</linearGradient>
<radialGradient id="sun" cx="50%" cy="50%" r="50%">
<stop offset="0" stop-color="#fffde4"/>
<stop offset=".48" stop-color="#ffe989" stop-opacity=".95"/>
<stop offset="1" stop-color="#ffbf61" stop-opacity="0"/>
</radialGradient>
<linearGradient id="ocean" x1="0" y1="0" x2="0" y2="1">
<stop offset="0" stop-color="#267f98"/>
<stop offset=".46" stop-color="#0a8f9c"/>
<stop offset="1" stop-color="#35b9ad"/>
</linearGradient>
<linearGradient id="waterShine" x1="0" y1="0" x2="1" y2="1">
<stop offset="0" stop-color="#fff5c9" stop-opacity=".8"/>
<stop offset=".52" stop-color="#e9ffff" stop-opacity=".24"/>
<stop offset="1" stop-color="#d7ffff" stop-opacity="0"/>
</linearGradient>
<linearGradient id="headland" x1="0" y1="0" x2="0" y2="1">
<stop offset="0" stop-color="#66856f"/>
<stop offset="1" stop-color="#314f4a"/>
</linearGradient>
<linearGradient id="sand" x1="0" y1="0" x2=".9" y2="1">
<stop offset="0" stop-color="#f5ca83"/>
<stop offset=".55" stop-color="#d9a45e"/>
<stop offset="1" stop-color="#b67945"/>
</linearGradient>
<linearGradient id="path" x1="0" y1="0" x2="0" y2="1">
<stop offset="0" stop-color="#dfc49c"/>
<stop offset="1" stop-color="#ac805e"/>
</linearGradient>
<linearGradient id="frame" x1="0" y1="0" x2="1" y2="1">
<stop offset="0" stop-color="#21aeb0"/>
<stop offset=".55" stop-color="#08737b"/>
<stop offset="1" stop-color="#034b55"/>
</linearGradient>
<linearGradient id="metal" x1="0" y1="0" x2="1" y2="1">
<stop offset="0" stop-color="#f8ffff"/>
<stop offset=".32" stop-color="#9ebabe"/>
<stop offset=".68" stop-color="#526f73"/>
<stop offset="1" stop-color="#e2eeee"/>
</linearGradient>
<linearGradient id="bodyFeathers" x1=".2" y1="0" x2=".85" y2="1">
<stop offset="0" stop-color="#fffdf2"/>
<stop offset=".44" stop-color="#f6f1dc"/>
<stop offset=".78" stop-color="#cbd8d6"/>
<stop offset="1" stop-color="#8da6a8"/>
</linearGradient>
<linearGradient id="wingFeathers" x1=".25" y1="0" x2=".8" y2="1">
<stop offset="0" stop-color="#f8f1dc"/>
<stop offset=".48" stop-color="#c6d1cc"/>
<stop offset="1" stop-color="#667f82"/>
</linearGradient>
<linearGradient id="neckFeathers" x1="0" y1="0" x2="1" y2="1">
<stop offset="0" stop-color="#fffdf0"/>
<stop offset=".58" stop-color="#eee7ce"/>
<stop offset="1" stop-color="#aebfc0"/>
</linearGradient>
<linearGradient id="beak" x1="0" y1="0" x2="1" y2=".2">
<stop offset="0" stop-color="#f5c55a"/>
<stop offset=".5" stop-color="#ef9544"/>
<stop offset=".86" stop-color="#d7603e"/>
<stop offset="1" stop-color="#7f3f38"/>
</linearGradient>
<linearGradient id="pouch" x1="0" y1="0" x2="1" y2="1">
<stop offset="0" stop-color="#ffc267" stop-opacity=".93"/>
<stop offset=".62" stop-color="#ec7546" stop-opacity=".9"/>
<stop offset="1" stop-color="#b94d3d" stop-opacity=".86"/>
</linearGradient>
<linearGradient id="leg" x1="0" y1="0" x2="1" y2="0">
<stop offset="0" stop-color="#bf573b"/>
<stop offset=".48" stop-color="#e88a4c"/>
<stop offset="1" stop-color="#9b4337"/>
</linearGradient>
<radialGradient id="eye" cx="35%" cy="30%" r="65%">
<stop offset="0" stop-color="#ffd56b"/>
<stop offset=".46" stop-color="#b47227"/>
<stop offset=".5" stop-color="#161716"/>
<stop offset="1" stop-color="#050707"/>
</radialGradient>
<linearGradient id="tire" x1="0" y1="0" x2="1" y2="1">
<stop offset="0" stop-color="#18282b"/>
<stop offset=".5" stop-color="#071114"/>
<stop offset="1" stop-color="#34484a"/>
</linearGradient>
<filter id="sunGlow" x="-100%" y="-100%" width="300%" height="300%">
<feGaussianBlur stdDeviation="16"/>
</filter>
<filter id="softShadow" x="-30%" y="-50%" width="160%" height="220%">
<feGaussianBlur stdDeviation="16"/>
</filter>
<filter id="objectShadow" x="-25%" y="-25%" width="160%" height="170%">
<feDropShadow dx="0" dy="12" stdDeviation="10" flood-color="#153c40" flood-opacity=".28"/>
</filter>
<filter id="featherShadow" x="-20%" y="-20%" width="150%" height="155%">
<feDropShadow dx="2" dy="7" stdDeviation="5" flood-color="#36585a" flood-opacity=".25"/>
</filter>
<filter id="mist" x="-20%" y="-100%" width="140%" height="300%">
<feGaussianBlur stdDeviation="9"/>
</filter>
<filter id="grain" x="0" y="0" width="100%" height="100%">
<feTurbulence type="fractalNoise" baseFrequency=".65" numOctaves="2" seed="8" result="noise"/>
<feColorMatrix in="noise" values="1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 .035 0"/>
</filter>
<clipPath id="seaClip">
<path d="M0 322H1600V624C1290 575 910 572 566 602 345 621 147 647 0 670Z"/>
</clipPath>
<g id="wheel">
<circle r="155" fill="none" stroke="url(#tire)" stroke-width="18"/>
<circle r="145.5" fill="none" stroke="#7e9799" stroke-width="4"/>
<circle r="140" fill="none" stroke="#d7e4e3" stroke-width="2.5"/>
<g fill="none" stroke="#b8cccd" stroke-width="2" stroke-opacity=".78">
<path d="M0-140V140M-140 0H140"/>
<path d="M0-140V140M-140 0H140" transform="rotate(30)"/>
<path d="M0-140V140M-140 0H140" transform="rotate(60)"/>
<path d="M0-140V140M-140 0H140" transform="rotate(15)"/>
<path d="M0-140V140M-140 0H140" transform="rotate(45)"/>
<path d="M0-140V140M-140 0H140" transform="rotate(75)"/>
</g>
<circle r="16" fill="url(#metal)" stroke="#3b5b60" stroke-width="5"/>
<path d="M-2-147h22l-2 8H1Z" fill="#ffcf49" stroke="#9e6a1d" stroke-width="2"/>
<path d="M-4 139h22l3 8H-2Z" fill="#f35f48" stroke="#88372f" stroke-width="2"/>
<path d="M-112-94a145 145 0 0 1 84-47" fill="none" stroke="#f2ffff" stroke-width="5" stroke-linecap="round" opacity=".6"/>
</g>
<g id="tuft" fill="none" stroke-linecap="round">
<path d="M0 32Q-3 9-20-9M3 32Q12 4 30-18M-1 31Q-20 15-34 7M3 31Q3 6 1-21" stroke="#426b4a" stroke-width="7"/>
<path d="M0 31Q8 7 18-7M-2 30Q-12 12-24 4" stroke="#85a65d" stroke-width="3"/>
</g>
</defs>
<style>
.wheel-spin { transform-origin: 0 0; animation: wheel-turn 2.5s linear infinite; }
.wave-drift { animation: wave-slide 5.5s ease-in-out infinite alternate; }
.grass-sway { transform-box: fill-box; transform-origin: 50% 100%; animation: sway 2.8s ease-in-out infinite alternate; }
.grass-sway.slow { animation-duration: 4.2s; animation-delay: -1.4s; }
.pelican-bob { animation: bob 1.25s ease-in-out infinite alternate; }
.scarf-tail { transform-box: fill-box; transform-origin: 100% 50%; animation: scarf 1.7s ease-in-out infinite alternate; }
@keyframes wheel-turn { to { transform: rotate(360deg); } }
@keyframes wave-slide { to { transform: translateX(20px); } }
@keyframes sway { to { transform: rotate(4deg); } }
@keyframes bob { to { transform: translateY(-3px); } }
@keyframes scarf { to { transform: rotate(-3deg) scaleX(1.035); } }
@media (prefers-reduced-motion: reduce) {
.wheel-spin, .wave-drift, .grass-sway, .pelican-bob, .scarf-tail { animation: none; }
}
</style>
<g id="background" aria-hidden="true">
<rect width="1600" height="900" fill="url(#sky)"/>
<circle cx="230" cy="165" r="150" fill="url(#sun)" filter="url(#sunGlow)" opacity=".9"/>
<circle cx="230" cy="165" r="55" fill="#fff3ac" opacity=".88"/>
<g fill="#fff" opacity=".48" filter="url(#mist)">
<path d="M80 244c27-33 78-35 108-7 41-36 112-18 119 35H58c1-12 9-23 22-28Z"/>
<path d="M1110 177c21-28 67-32 94-6 36-29 94-12 101 34h-216c1-12 9-23 21-28Z"/>
<path d="M1334 276c18-24 54-26 75-5 29-24 77-9 83 29h-176c1-10 7-18 18-24Z"/>
</g>
<path d="M0 322H1600V624C1290 575 910 572 566 602 345 621 147 647 0 670Z" fill="url(#ocean)"/>
<path d="M0 335C275 326 511 340 760 330s534-12 840 5" fill="none" stroke="#efffff" stroke-width="5" opacity=".62"/>
<path d="M0 345C356 330 664 350 951 340c224-8 423-3 649 3" fill="none" stroke="#174f68" stroke-width="3" opacity=".25"/>
<path d="M125 340c56 38 119 116 178 244l-197 46-148 11V368c55-27 106-35 167-28Z" fill="url(#headland)"/>
<path d="M-10 374c58-41 99-41 133-32 42 13 70 40 94 79-75-37-151-35-227-10Z" fill="#739777"/>
<path d="M-5 405c73-26 143-21 214 17-79-14-146-5-214 23Z" fill="#3f6c5c" opacity=".7"/>
<g clip-path="url(#seaClip)">
<path d="M118 350 279 350 529 606 344 625Z" fill="url(#waterShine)" opacity=".75"/>
<g class="wave-drift" fill="none" stroke-linecap="round">
<path d="M282 405c72-22 131 17 202-3 67-18 112-18 176 1" stroke="#b7eff0" stroke-width="8" opacity=".38"/>
<path d="M794 390c95 24 179-17 268 3 78 18 130 17 208-2" stroke="#c8f5f2" stroke-width="7" opacity=".36"/>
<path d="M100 476c91-22 169 21 260-3 81-21 145-14 220 5" stroke="#e4ffff" stroke-width="10" opacity=".48"/>
<path d="M658 496c109-31 189 27 301-3 86-23 165-12 258 8" stroke="#d8ffff" stroke-width="11" opacity=".5"/>
<path d="M1115 444c80-22 152 20 238-2 75-20 140-12 207 5" stroke="#e6ffff" stroke-width="8" opacity=".43"/>
<path d="M-42 562c128-42 225 39 347 2 105-31 200-21 307 6" stroke="#f4ffff" stroke-width="15" opacity=".58"/>
<path d="M817 560c132-41 244 35 376-4 120-35 228-14 355 17" stroke="#efffff" stroke-width="14" opacity=".56"/>
</g>
<g fill="#eaffff" opacity=".64">
<circle cx="696" cy="487" r="5"/><circle cx="718" cy="475" r="3"/><circle cx="746" cy="488" r="4"/>
<circle cx="1219" cy="540" r="6"/><circle cx="1247" cy="527" r="3"/><circle cx="1280" cy="542" r="5"/>
</g>
</g>
<g stroke="#2c5960" stroke-width="5" fill="none" stroke-linecap="round" opacity=".72">
<path d="M436 218q18-18 36 0 18-18 36 0"/>
<path d="M1298 148q15-14 30 0 15-14 30 0"/>
<path d="M1383 244q11-11 22 0 11-11 22 0"/>
</g>
<g transform="translate(483 371)" opacity=".7">
<path d="M0 0h55" stroke="#5d4c3c" stroke-width="3"/>
<path d="M25-2v-54l36 43H25Z" fill="#fff7de" stroke="#b68155" stroke-width="2"/>
<path d="M25-2v-45L-5-11h30Z" fill="#f27654" stroke="#9c523f" stroke-width="2"/>
<path d="M4 2q23 13 50 0" fill="#745347"/>
</g>
<path d="M0 621C286 586 594 567 881 561c264-6 495-27 719-68v407H0Z" fill="url(#sand)"/>
<path d="M0 676c369-55 697-58 1000-64 247-5 422-33 600-76v364H0Z" fill="url(#path)"/>
<path d="M0 659c415-54 756-50 1057-59 223-7 377-35 543-73" fill="none" stroke="#fff0c5" stroke-width="11" opacity=".64"/>
<path d="M0 704c329-48 656-42 949-43 261 0 439-39 651-80" fill="none" stroke="#8e654c" stroke-width="4" stroke-dasharray="25 38" opacity=".3"/>
<path d="M80 775c301-40 577-39 866-35 250 4 421-30 633-75" fill="none" stroke="#f5d6a3" stroke-width="7" stroke-dasharray="12 45" opacity=".45"/>
<g opacity=".58" fill="#7b5a43">
<ellipse cx="266" cy="726" rx="10" ry="4" transform="rotate(-14 266 726)"/>
<ellipse cx="330" cy="810" rx="7" ry="3" transform="rotate(24 330 810)"/>
<ellipse cx="1335" cy="728" rx="11" ry="4" transform="rotate(-6 1335 728)"/>
<ellipse cx="1490" cy="641" rx="8" ry="3" transform="rotate(16 1490 641)"/>
</g>
<rect width="1600" height="900" filter="url(#grain)" opacity=".65"/>
</g>
<g id="ground-details" aria-hidden="true">
<g transform="translate(78 650) scale(1.5)" class="grass-sway slow"><use href="#tuft"/></g>
<g transform="translate(196 637) scale(1.15)" class="grass-sway"><use href="#tuft"/></g>
<g transform="translate(1440 574) scale(.88)" class="grass-sway slow"><use href="#tuft"/></g>
<g transform="translate(1519 552) scale(1.06)" class="grass-sway"><use href="#tuft"/></g>
<path d="M214 664q21-27 37-1M1474 590q16-24 33-1" fill="none" stroke="#e9c660" stroke-width="6" stroke-linecap="round"/>
<ellipse cx="842" cy="838" rx="454" ry="42" fill="#31565a" opacity=".22" filter="url(#softShadow)"/>
<ellipse cx="560" cy="839" rx="96" ry="13" fill="#2e4c4c" opacity=".25" filter="url(#softShadow)"/>
<ellipse cx="1110" cy="839" rx="96" ry="13" fill="#2e4c4c" opacity=".25" filter="url(#softShadow)"/>
<path d="M355 832q-86 8-148 1M358 807q-53-4-96-19M1292 816q71-5 130-25" fill="none" stroke="#87634e" stroke-width="7" stroke-linecap="round" opacity=".48"/>
</g>
<g id="bicycle" filter="url(#objectShadow)">
<g transform="translate(560 680)">
<g class="wheel-spin"><use href="#wheel"/></g>
</g>
<g transform="translate(1110 680)">
<g class="wheel-spin"><use href="#wheel"/></g>
</g>
<g fill="none" stroke-linecap="round" stroke-linejoin="round">
<path d="M560 680 790 681 735 522 560 680Z" stroke="url(#frame)" stroke-width="21"/>
<path d="M790 681 972 548 735 522Z" stroke="url(#frame)" stroke-width="21"/>
<path d="M790 681 1110 680" stroke="url(#frame)" stroke-width="18"/>
<path d="M972 548 1110 680" stroke="url(#metal)" stroke-width="17"/>
<path d="M958 520 1110 680" stroke="#075d66" stroke-width="9"/>
<path d="M735 522 714 476" stroke="#294d51" stroke-width="16"/>
<path d="M958 521 983 472 1045 471" stroke="url(#metal)" stroke-width="13"/>
<path d="M1016 471h48" stroke="#363c3c" stroke-width="18"/>
<path d="M1039 471h29" stroke="#f08b48" stroke-width="12"/>
<path d="M1027 463c36 22 47 62 31 101" stroke="#263e42" stroke-width="4"/>
</g>
<path d="M675 473q48-14 105 1c8 2 10 12 3 18-33 15-76 15-111 4-12-4-10-20 3-23Z" fill="#4d3930" stroke="#251d1b" stroke-width="6"/>
<path d="M688 480q38-8 77 1" fill="none" stroke="#ac7b5c" stroke-width="4" opacity=".7"/>
<g transform="translate(790 681)">
<circle r="38" fill="#214d52" stroke="url(#metal)" stroke-width="7"/>
<circle r="14" fill="#d6e1dd" stroke="#435c5f" stroke-width="5"/>
<g class="wheel-spin">
<path d="M0 0 63-38M0 0l-63 38" stroke="url(#metal)" stroke-width="9" stroke-linecap="round"/>
<path d="M54-44h45" stroke="#273a3c" stroke-width="12" stroke-linecap="round"/>
<path d="M-98 44h45" stroke="#273a3c" stroke-width="12" stroke-linecap="round"/>
</g>
</g>
<path d="M790 681Q672 692 560 680" fill="none" stroke="#3b2e28" stroke-width="7" stroke-dasharray="7 5"/>
<circle cx="1042" cy="489" r="14" fill="#ffd34f" stroke="#8a6122" stroke-width="5"/>
<path d="M1039 487q-15-19-29-2" fill="none" stroke="#8a6122" stroke-width="4"/>
</g>
<g id="pelican" class="pelican-bob">
<g id="legs" fill="url(#leg)" stroke="#8a4438" stroke-width="5" stroke-linejoin="round">
<path d="M737 582c-5 42-2 85 11 126l25-8c-5-37-5-78 3-111Z"/>
<path d="M834 582c2 25 9 48 20 70l24-12c-7-23-9-45-6-67Z"/>
<path d="M741 705c-9 8-21 16-35 23 22 10 45 11 68 0l-2-27Z"/>
<path d="M850 648c11 6 25 9 43 10-12-17-26-27-42-30Z"/>
</g>
<g fill="none" stroke="#713b33" stroke-width="3" stroke-linecap="round" opacity=".72">
<path d="M716 724q19-7 55-2M735 712l-17 14M750 710l-8 19M864 643l17 12M850 645l28 5"/>
</g>
<g id="body" filter="url(#featherShadow)">
<path d="M572 550c11-80 74-139 165-151 95-13 187 28 217 101 25 61-13 125-99 153-82 27-190 11-254-33-28-19-39-43-29-70Z" fill="url(#bodyFeathers)" stroke="#768e90" stroke-width="5"/>
<path d="M585 528c-29 5-57 20-86 50 34 3 60 11 79 30 21-13 36-33 43-60Z" fill="#b7c8c5" stroke="#6f8888" stroke-width="5"/>
<path d="M522 573c-28-1-50 5-72 17 30 6 49 16 66 31 22-8 41-22 54-40Z" fill="#718c8c"/>
<path d="M655 452c58-34 139-23 184 20 33 31 42 74 22 111-22 41-74 62-129 48-64-16-115-68-121-117-3-26 13-47 44-62Z" fill="url(#wingFeathers)" stroke="#718789" stroke-width="5"/>
<g fill="none" stroke-linecap="round">
<path d="M653 486q89 12 175 89" stroke="#7f9697" stroke-width="7" opacity=".66"/>
<path d="M638 511q86 20 166 93" stroke="#799091" stroke-width="7" opacity=".64"/>
<path d="M643 540q65 26 123 79" stroke="#6f8788" stroke-width="6" opacity=".62"/>
<path d="M683 460q80 33 138 91" stroke="#eff3e6" stroke-width="5" opacity=".65"/>
<path d="M706 447q-8 34 12 56M746 451q-4 34 22 62M786 467q1 33 25 54" stroke="#fff9e6" stroke-width="5" opacity=".6"/>
</g>
<g fill="#f7f1dc" opacity=".9">
<path d="M601 555q23-19 48-6-18 5-25 24Z"/>
<path d="M616 584q25-20 50-5-18 5-26 23Z"/>
<path d="M657 611q25-19 49-4-18 5-25 22Z"/>
<path d="M861 581q24-16 45-1-17 4-24 19Z"/>
<path d="M823 613q22-15 44-1-16 5-22 18Z"/>
</g>
</g>
<g id="neck-and-head" filter="url(#featherShadow)">
<path d="M840 466c3-56 8-112 42-160 22-31 47-52 84-70 56-26 120-7 145 42 18 35 9 77-22 104-25 22-59 27-88 42-36 18-49 54-45 101-43 4-84-16-116-59Z" fill="url(#neckFeathers)" stroke="#879b9b" stroke-width="5"/>
<path d="M890 444c0-68 19-124 67-163" fill="none" stroke="#fffdf0" stroke-width="24" stroke-linecap="round" opacity=".64"/>
<path d="M917 281c-13-33 1-68 34-86 49-27 116-13 145 29 19 27 19 62 1 87-21 29-58 40-96 30-38-9-70-31-84-60Z" fill="url(#neckFeathers)" stroke="#829798" stroke-width="5"/>
<path d="M957 202c21-27 62-36 96-18 14 8 26 19 33 32-36-9-68-3-99 18Z" fill="#f3d885" opacity=".9"/>
<path d="M947 207q27-39 61-45l-8 29q28-28 59-25l-16 30q31-19 55-8l-28 30Z" fill="#fff8d8" stroke="#9b9d8f" stroke-width="4" stroke-linejoin="round"/>
<path d="M1056 248c101-9 220 2 337 25 21 4 29 22 15 35-19 18-61 25-113 22l-219-15c-28-2-45-17-43-38 1-14 9-24 23-29Z" fill="url(#beak)" stroke="#8b4e3d" stroke-width="5"/>
<path d="M1050 282c95 11 204 17 333 19-23 34-76 52-151 44l-138-16c-30-4-49-20-52-40Z" fill="url(#pouch)" stroke="#a95342" stroke-width="5"/>
<path d="M1065 279c102 7 207 14 317 21" fill="none" stroke="#fff0b4" stroke-width="5" stroke-linecap="round" opacity=".72"/>
<path d="M1393 273c15 2 25 9 27 19-2 17-19 27-51 31 20-11 29-26 24-50Z" fill="#6f3d38"/>
<path d="M1079 314q86 18 167 22" fill="none" stroke="#9b443b" stroke-width="4" opacity=".58"/>
<path d="M1033 238c7-14 25-20 39-12 12 7 15 23 7 34-9 12-27 13-38 3-7-6-10-16-8-25Z" fill="#e9bf57" stroke="#6f6752" stroke-width="4"/>
<circle cx="1058" cy="244" r="8" fill="url(#eye)"/>
<circle cx="1061" cy="241" r="2.4" fill="#fff"/>
<path d="M1040 225q19-11 35 3" fill="none" stroke="#6d6150" stroke-width="5" stroke-linecap="round"/>
<path d="M1100 264q18-7 34 2-16 3-31 1Z" fill="#7f4d3d" opacity=".78"/>
<g fill="none" stroke="#9cacab" stroke-width="4" stroke-linecap="round" opacity=".67">
<path d="M866 369q31 14 56 3M855 395q35 16 65 6M854 425q31 17 64 10"/>
</g>
</g>
<g id="reaching-wing" filter="url(#featherShadow)">
<path d="M827 466c45-10 88-1 124 26 27 20 53 32 90 35 18 2 28 16 23 30-6 17-30 24-61 17-42-10-78-25-111-47-31-20-53-38-65-61Z" fill="url(#wingFeathers)" stroke="#6f8587" stroke-width="5"/>
<path d="M891 486q60 53 142 61" fill="none" stroke="#ecf0e3" stroke-width="7" stroke-linecap="round" opacity=".68"/>
<path d="M1035 530q22-6 40 11-15 2-22 16 17-2 30 11-25 16-54 1Z" fill="#b5c3bd" stroke="#687e80" stroke-width="4"/>
<path d="M921 509q8 25 36 46M947 522q11 22 36 39" fill="none" stroke="#728789" stroke-width="5" opacity=".65"/>
</g>
<g id="scarf">
<path d="M871 337q55 20 110 4l8 28q-66 25-128-5Z" fill="#e9524b" stroke="#993b3b" stroke-width="5"/>
<g class="scarf-tail">
<path d="M872 349c-73 7-116-3-157-32 19 30 42 47 71 56-32 4-65-1-98-15 47 50 113 60 190 23Z" fill="#ef5d4f" stroke="#9b3f3a" stroke-width="5"/>
<path d="M722 331q56 35 145 31" fill="none" stroke="#ff9a6f" stroke-width="6" opacity=".65"/>
</g>
</g>
</g>
<g id="foreground" aria-hidden="true">
<g transform="translate(25 865) scale(2.25)" class="grass-sway slow"><use href="#tuft"/></g>
<g transform="translate(139 882) scale(1.75)" class="grass-sway"><use href="#tuft"/></g>
<g transform="translate(1450 872) scale(2.1)" class="grass-sway slow"><use href="#tuft"/></g>
<g transform="translate(1555 858) scale(1.6)" class="grass-sway"><use href="#tuft"/></g>
<path d="M0 870q69-27 137-5t138-2q78-28 163 11L438 900H0Z" fill="#815e45" opacity=".46"/>
<path d="M1268 879q68-28 130-10t101-4q54-24 101 3v32h-332Z" fill="#815e45" opacity=".42"/>
<g fill="#f8ffff" opacity=".72">
<circle cx="1510" cy="505" r="5"/><circle cx="1535" cy="488" r="3"/><circle cx="1483" cy="521" r="4"/>
<circle cx="326" cy="583" r="4"/><circle cx="349" cy="569" r="2.5"/><circle cx="370" cy="582" r="3.5"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 21 KiB

File diff suppressed because it is too large. Load diff