09 · GPT-6.1 Sol / medium · 原场景逻辑源码

返回测评 · 运行原作品 · 下载原始源码

本页只阅读源码,不运行代码;行号对应原文件,完整成品保持原交付字节。

1<!DOCTYPE html>2<html lang="zh-CN"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>海风小骑士 · Coast Cat</title>3<style>4*{box-sizing:border-box}body{margin:0;overflow:hidden;background:#b5e1dd;font-family:Segoe UI,Microsoft YaHei,sans-serif;color:#183f43}canvas{display:block;width:100%;height:100%;touch-action:none}header{position:absolute;left:38px;top:30px;pointer-events:none}.eyebrow{font-size:11px;letter-spacing:4px;font-weight:700}h1{font-size:32px;letter-spacing:3px;margin:10px 0 8px;font-weight:600}header p{font-size:12px;letter-spacing:2px;margin:0;opacity:.7}.badge{position:absolute;right:32px;top:32px;border:1px solid #ffffff90;border-radius:30px;padding:10px 16px;background:#ffffff40;font-size:11px;letter-spacing:2px}.panel{position:absolute;bottom:28px;left:50%;transform:translateX(-50%);display:flex;align-items:center;gap:12px;background:#f9fcf2e8;border:1px solid #fff;border-radius:22px;padding:14px 18px;box-shadow:0 12px 45px #183f4314;backdrop-filter:blur(14px);white-space:nowrap}button{cursor:pointer;border:0;border-radius:12px;background:#e5eeea;color:#183f43;padding:12px 17px;font:600 12px inherit;transition:background .2s}button:hover{background:#c6dfd6}button.active{background:#214e4c;color:#fff}button:focus-visible,input:focus-visible{outline:3px solid #ec9b61;outline-offset:3px}.speed{display:flex;gap:10px;align-items:center;font-size:12px;margin:0 8px}.speed input{width:100px;accent-color:#2a6862}.readout{border-left:1px solid #d6dfda;padding-left:16px;min-width:85px}.readout strong{font-size:22px;font-weight:500}.readout small{font-size:10px;margin-left:5px}.hint{position:absolute;bottom:114px;left:50%;transform:translateX(-50%);font-size:11px;letter-spacing:1px;color:#284d4c;white-space:nowrap}.signature{position:absolute;left:36px;bottom:32px;font-size:10px;letter-spacing:3px;opacity:.65}#error{display:none;position:absolute;inset:25%;background:#fff;padding:30px;border-radius:20px;z-index:10}a{color:inherit}5@media(max-width:680px){header{top:22px;left:22px}h1{font-size:25px}.eyebrow{font-size:8px;letter-spacing:2px;max-width:220px}.badge{right:18px;top:24px;padding:9px;font-size:9px;letter-spacing:1px}.panel{bottom:16px;gap:6px;padding:10px;border-radius:16px;flex-wrap:wrap;justify-content:center;width:calc(100% - 24px);max-width:440px}button{padding:10px 12px}.speed{margin:0 2px;gap:6px}.speed input{width:65px}.readout{min-width:65px;padding-left:8px}.readout strong{font-size:18px}.hint{bottom:115px;font-size:10px}.signature{display:none}}6</style></head><body>7<header><div class="eyebrow">COAST CAT / A LITTLE GETAWAY</div><h1>海风小骑士</h1><p>沿着海岸,把今天骑慢一点。</p></header><div class="badge">☀ 海岸公路 · 26°</div>8<div class="hint">拖动环绕 · 滚轮缩放 · 空格暂停</div><div class="signature">A RIDE BY THE SEA — THREE.JS</div>9<div class="panel"><button id="pause" class="active" aria-label="暂停动画">Ⅱ 暂停</button><button id="view">切换视角</button><button id="reset">复位</button><label class="speed">速度 <input id="speed" type="range" min="0.3" max="2" step="0.1" value="1" aria-label="骑行速度"></label><div class="readout"><strong id="kmh">18</strong><small>km/h</small></div></div>10<div id="error" role="alert"></div>11<script>/* THREE_LIBRARY */</script>12<script>13(()=>{'use strict';14const T=THREE, scene=new T.Scene();scene.fog=new T.Fog('#bddbd5',45,155);15let renderer;try{renderer=new T.WebGLRenderer({antialias:true,powerPreference:'high-performance'});}catch(e){const el=document.getElementById('error');el.style.display='block';el.textContent='浏览器无法启动 3D。请在 Chrome 或 Edge 中开启硬件加速后重新打开。';return;}16renderer.setPixelRatio(Math.min(devicePixelRatio,1.8));renderer.setSize(innerWidth,innerHeight);renderer.shadowMap.enabled=true;renderer.shadowMap.type=T.PCFSoftShadowMap;renderer.outputColorSpace=T.SRGBColorSpace;renderer.toneMapping=T.ACESFilmicToneMapping;renderer.toneMappingExposure=1.25;document.body.prepend(renderer.domElement);17const camera=new T.PerspectiveCamera(43,innerWidth/innerHeight,.1,230);scene.add(new T.HemisphereLight('#f3fff5','#7a8e70',2.4));const sun=new T.DirectionalLight('#ffe2b2',3.2);sun.position.set(-25,35,-25);sun.castShadow=true;sun.shadow.mapSize.set(2048,2048);Object.assign(sun.shadow.camera,{left:-24,right:24,top:24,bottom:-24,near:1,far:100});sun.shadow.bias=-.0003;sun.shadow.normalBias=.03;scene.add(sun);18const sky=new T.Mesh(new T.SphereGeometry(190,24,16),new T.ShaderMaterial({side:T.BackSide,depthWrite:false,uniforms:{},vertexShader:'varying vec3 v;void main(){v=position;gl_Position=projectionMatrix*modelViewMatrix*vec4(position,1.);}',fragmentShader:'varying vec3 v;void main(){float h=normalize(v).y;vec3 c=mix(vec3(.88,.91,.79),vec3(.35,.72,.79),smoothstep(-.05,.8,h));gl_FragColor=vec4(c,1.);}' }));scene.add(sky);19const mats={};function mat(c,rough=.7,metal=0){let key=c+rough+metal;return mats[key]||(mats[key]=new T.MeshStandardMaterial({color:c,roughness:rough,metalness:metal}));}20function mesh(g,c,parent=scene,p=[0,0,0],s=[1,1,1]){let m=new T.Mesh(g,typeof c==='string'?mat(c):c);m.position.set(...p);m.scale.set(...s);m.castShadow=true;m.receiveShadow=true;parent.add(m);return m;}21const sphere=new T.SphereGeometry(1,24,16),box=new T.BoxGeometry(1,1,1);22function ball(c,p,s,parent){return mesh(sphere,c,parent,p,s);}function block(c,p,s,parent){return mesh(box,c,parent,p,s);}function rod(a,b,r,c,parent=scene){const va=new T.Vector3(...a),vb=new T.Vector3(...b);const m=mesh(new T.CylinderGeometry(r,r,va.distanceTo(vb),10),c,parent);m.position.copy(va).add(vb).multiplyScalar(.5);m.quaternion.setFromUnitVectors(new T.Vector3(0,1,0),vb.sub(va).normalize());return m;}23function curve(points,r,c,parent){return mesh(new T.TubeGeometry(new T.CatmullRomCurve3(points.map(p=>new T.Vector3(...p))),28,r,8,false),c,parent);}24// A repeating coast is translated by the same distance that drives the wheels.25block('#8dbd9b',[45,-.5,0],[90,1,360]);block('#efd9ab',[-7.3,-.22,0],[7,.45,360]);block('#696f73',[0,-.08,0],[7.6,.2,360]);26for(const x of [-3.42,3.42])block('#f4ecd3',[x,.025,0],[.08,.02,360]);block('#d4c6a6',[-4,.05,0],[.65,.28,360]);block('#c6c8b2',[4.05,.06,0],[.65,.3,360]);27const seaGeo=new T.PlaneGeometry(155,370,60,120);seaGeo.rotateX(-Math.PI/2);const seaMat=new T.ShaderMaterial({uniforms:{time:{value:0}},vertexShader:'uniform float time;varying vec3 v;void main(){vec3 p=position;p.y+=sin(p.x*.42+time*.9+p.z*.12)*.18+sin(p.z*.32-time*.8)*.13;v=p;gl_Position=projectionMatrix*modelViewMatrix*vec4(p,1.);}',fragmentShader:'varying vec3 v;uniform float time;void main(){float f=sin(v.x*1.6+v.z*.27+time*.7)*sin(v.z*.6-time*.8);vec3 c=mix(vec3(.08,.49,.55),vec3(.31,.73,.73),.55+v.y);c+=pow(max(0.,f),16.)*.2;gl_FragColor=vec4(c,1.);}' });mesh(seaGeo,seaMat,scene,[-88,-.16,0]);28const foam=[];for(let i=0;i<32;i++){let f=mesh(new T.PlaneGeometry(1,1),new T.MeshBasicMaterial({color:'#edf8da',transparent:true,opacity:.6}),scene,[-11,.02,i*11-170],[1.3,7,1]);f.rotation.x=-Math.PI/2;foam.push(f);}29const tiles=[];function palm(parent,x,z,scale=1){const g=new T.Group();parent.add(g);g.position.set(x,0,z);g.scale.setScalar(scale);curve([[0,0,0],[.1,2,0],[.55,4,0]],.15,'#ac8253',g);for(let i=0;i<7;i++){let ang=i/7*Math.PI*2;const pts=[[.55,4,0],[.55+Math.cos(ang)*.9,4.4,Math.sin(ang)*.9],[.55+Math.cos(ang)*2,3.7,Math.sin(ang)*2]];curve(pts,.12,'#468b69',g);for(let j=1;j<5;j++){let u=j/5;let px=.55+Math.cos(ang)*u*2,pz=Math.sin(ang)*u*2,py=4.2-u*u*.6;rod([px,py,pz],[px+Math.cos(ang+1.4)*.6,py-.25,pz+Math.sin(ang+1.4)*.6],.045,'#70a16c',g);rod([px,py,pz],[px+Math.cos(ang-1.4)*.6,py-.25,pz+Math.sin(ang-1.4)*.6],.045,'#70a16c',g);}}ball('#967048',[.55,3.85,0],[.25,.25,.25],g);}30for(let i=0;i<12;i++){const g=new T.Group();scene.add(g);g.userData.base=i*25-150;tiles.push(g);for(let j=0;j<5;j++)block('#f5e9c5',[0,.036,j*5],[.12,.025,2.4],g);31for(let j=0;j<5;j++){block('#eeebe0',[-4.6,.62,j*5],[.13,1.2,.13],g);}rod([-4.6,1,0],[-4.6,1,25],.065,'#f5f0de',g);32palm(g,6.3,8,.9);palm(g,10.5,19,1.2);ball('#78ab79',[7,.32,15],[1.3,.6,1.1],g);ball('#9eb77d',[10,.32,3],[1.5,.7,1],g);33rod([4.4,0,0],[4.4,5,0],.065,'#485e5c',g);curve([[4.4,4.9,0],[4,5.3,0],[3.4,5.3,0]],.06,'#485e5c',g);ball('#fff0cb',[3.4,5.22,0],[.32,.1,.2],g);34const rock=mesh(new T.DodecahedronGeometry(1,0),'#c6b89b',g,[-9.3,.3,15],[.7,.6,1]);rock.rotation.set(.2,.5,.4);35}36// Distant lighthouse, small islands, sailboats and clouds.37const island=new T.Group();scene.add(island);island.position.set(-38,0,25);ball('#7faaa0',[0,-.5,0],[8,2.6,6],island);ball('#98b985',[0,.4,0],[5,1.5,4],island);mesh(new T.CylinderGeometry(.7,1.1,7,16),'#fff2d7',island,[0,5,0]);for(let y of [3,5.5])mesh(new T.CylinderGeometry(.82,.9,.65,16),'#db8067',island,[0,y,0]);mesh(new T.CylinderGeometry(1.15,1.15,.25,16),'#456366',island,[0,8.55,0]);mesh(new T.CylinderGeometry(.65,.65,1,12),'#9cd7d5',island,[0,9,0]);mesh(new T.ConeGeometry(1.1,.8,16),'#d8745c',island,[0,9.85,0]);38for(let i=0;i<3;i++){const b=new T.Group();scene.add(b);b.position.set(-24-i*13,.15,-35+i*37);ball('#fff1dc',[0,0,0],[.45,.22,1.3],b);rod([0,0,0],[0,3,0],.035,'#947b60',b);const geo=new T.BufferGeometry();geo.setAttribute('position',new T.Float32BufferAttribute([0,2.9,0,0,.4,0,0,.4,-1.65],3));geo.computeVertexNormals();mesh(geo,new T.MeshStandardMaterial({color:'#fff1d4',side:T.DoubleSide}),b);}39const sunDisc=mesh(new T.SphereGeometry(4,24,16),new T.MeshBasicMaterial({color:'#fff0b1'}),scene,[-58,10,75]);sunDisc.castShadow=false;const clouds=[];for(let i=0;i<14;i++){let g=new T.Group();scene.add(g);g.position.set(-70+i*12,9+(i%3)*2,35+(i%4)*14);for(let j=0;j<4;j++)ball('#fff7df',[j*1.7,Math.sin(j)*.6,0],[2,1,1.2],g);clouds.push(g);}40const birds=[];for(let i=0;i<6;i++){const g=new T.Group();scene.add(g);g.position.set(-15-i*3,7+i%3,-14-i*5);let wings=[];for(let side of [-1,1]){const w=new T.Group();g.add(w);rod([0,0,0],[side*.5,.1,.07],.025,'#f9f1d8',w);rod([side*.5,.1,.07],[side*.85,0,.15],.018,'#f9f1d8',w);wings.push(w);}birds.push({g,wings});}41// Scooter: longitudinal axis -Z, axles X. No pedalling on an electric scooter.42const ride=new T.Group();scene.add(ride);ride.position.x=-1.65;const wheels=[];43for(let z of [.94,-1.03]){const w=new T.Group();ride.add(w);w.position.set(0,.44,z);const tire=mesh(new T.TorusGeometry(.33,.105,12,32),'#293b3f',w);tire.rotation.y=Math.PI/2;const hub=mesh(new T.CylinderGeometry(.25,.25,.17,24),mat('#dce3d9',.35,.45),w);hub.rotation.z=Math.PI/2;for(let side of [-1,1]){const center=mesh(new T.CylinderGeometry(.105,.105,.025,16),'#467d79',w,[side*.1,0,0]);center.rotation.z=Math.PI/2;for(let k=0;k<6;k++){let a=k*Math.PI/3;rod([side*.11,0,0],[side*.11,Math.cos(a)*.22,Math.sin(a)*.22],.018,'#abbab4',w);}}wheels.push(w);}44ball('#df8167',[0,.77,.87],[.36,.25,.55],ride);block('#e39b7c',[0,.54,-.12],[.64,.17,1.25],ride);block('#59655f',[0,.64,-.2],[.54,.04,.92],ride);ball('#e4876d',[0,1.13,.64],[.43,.46,.6],ride);block('#ede3c5',[0,1.47,.57],[.79,.17,.83],ride);ball('#e28d73',[0,1.1,-.79],[.4,.62,.23],ride);ball('#f4b898',[0,1.61,-.83],[.33,.26,.24],ride);rod([0,.46,-1.03],[0,1.48,-.82],.07,'#b8c8bf',ride);45const light=mesh(new T.CylinderGeometry(.155,.155,.07,24),mat('#fff2cb',.25),ride,[0,1.69,-1.055]);light.rotation.x=Math.PI/2;const trim=mesh(new T.TorusGeometry(.167,.02,8,24),'#eeead4',ride,[0,1.69,-1.097]);46rod([-.46,1.72,-.76],[.46,1.72,-.76],.045,'#698e88',ride);for(let s of [-1,1]){rod([s*.33,1.72,-.76],[s*.52,1.72,-.76],.06,'#354e4c',ride);rod([s*.31,1.72,-.76],[s*.46,2.08,-.85],.021,'#a6b4ac',ride);ball('#e4e7da',[s*.47,2.1,-.86],[.13,.095,.035],ride);ball('#e9976d',[s*.3,1.45,-.98],[.06,.045,.035],ride);}47block('#d75f52',[0,1.18,1.22],[.3,.12,.05],ride);block('#f3e9d2',[0,.92,1.28],[.32,.17,.035],ride);rod([-.28,.45,.94],[-.28,1.14,.65],.04,'#749087',ride);48// Rear wicker basket and a paper bag.49block('#ba9369',[0,1.59,1.18],[.65,.42,.46],ride);for(let i=0;i<5;i++){block('#e6c798',[0,1.41+i*.075,1.419],[.66,.025,.025],ride);for(let s of [-1,1])block('#e6c798',[s*.334,1.41+i*.075,1.18],[.025,.025,.47],ride);}block('#dfc699',[.08,1.92,1.17],[.33,.43,.27],ride);let baguette=ball('#be854e',[-.12,2.05,1.16],[.08,.4,.08],ride);baguette.rotation.z=-.2;50// Orange tabby in a little sea-green helmet.51const cat=new T.Group();ride.add(cat);ball('#e4a05b',[0,1.9,.42],[.36,.51,.3],cat);ball('#f4cf94',[0,1.94,.15],[.23,.35,.11],cat);52for(let s of [-1,1]){curve([[s*.25,1.7,.49],[s*.36,1.18,.05],[s*.32,.76,-.42]],.11,'#e4a05b',cat);ball('#f3d3a1',[s*.32,.77,-.46],[.13,.09,.22],cat);curve([[s*.29,2.1,.29],[s*.4,1.93,-.18],[s*.42,1.74,-.75]],.105,'#e4a05b',cat);ball('#f4d0a2',[s*.42,1.74,-.77],[.12,.11,.12],cat);}53const head=new T.Group();cat.add(head);head.position.set(0,2.56,.23);ball('#e9ac66',[0,0,0],[.48,.43,.4],head);54for(let s of [-1,1]){const ear=mesh(new T.ConeGeometry(.2,.4,3),'#e4a05b',head,[s*.34,.37,0]);ear.rotation.y=s*.18;const inner=mesh(new T.ConeGeometry(.115,.25,3),'#e9b39a',head,[s*.34,.4,-.09]);inner.rotation.y=s*.18;ball('#f7deb4',[s*.16,-.12,-.35],[.21,.16,.105],head);ball('#35534d',[s*.185,.07,-.359],[.067,.1,.028],head);ball('#fff7da',[s*.2,.102,-.383],[.017,.02,.012],head);for(let j=0;j<3;j++)rod([s*.2,-.12+j*.05,-.442],[s*.63,-.12+(j-1)*.09,-.40],.008,'#6e6151',head);}55ball('#c97976',[0,-.08,-.455],[.065,.044,.032],head);curve([[0,-.11,-.45],[0,-.19,-.45],[-.07,-.2,-.43]],.012,'#85664d',head);curve([[0,-.19,-.45],[.07,-.2,-.43]],.012,'#85664d',head);56for(let s of [-1,0,1]){let stripe=ball('#bb7c42',[s*.125,.2,-.32],[.035,.11,.025],head);stripe.rotation.z=s*-.3;}57const helmet=mesh(new T.SphereGeometry(.48,28,16,0,Math.PI*2,0,Math.PI/2),'#4c9790',head,[0,.13,.01],[1,.88,1]);curve([[-.45,.17,0],[-.28,.19,-.38],[0,.19,-.48],[.28,.19,-.38],[.45,.17,0]],.025,'#d2ebce',head);block('#f2e5be',[0,.533,-.01],[.065,.015,.58],head);58curve([[0,1.69,.67],[.25,1.5,1.05],[.55,1.85,1.27],[.65,2.13,1.13]],.075,'#dca05d',cat);59const scarf=curve([[-.3,2.24,.18],[0,2.21,-.08],[.3,2.24,.18]],.085,'#e97254',cat);60const ribbonGeo=new T.PlaneGeometry(.23,.9,1,14);ribbonGeo.rotateX(Math.PI/2);ribbonGeo.translate(.2,2.23,.8);const ribbon=mesh(ribbonGeo,new T.MeshStandardMaterial({color:'#e97254',side:T.DoubleSide,roughness:1}),cat);const ribbonBase=ribbonGeo.attributes.position.array.slice();61let t=0,distance=0,speed=1,paused=matchMedia('(prefers-reduced-motion: reduce)').matches,mode=0,yaw=0,pitch=0,zoom=1,drag=null;62const pause=document.getElementById('pause');function showPause(){pause.textContent=paused?'▶ 继续':'Ⅱ 暂停';pause.classList.toggle('active',!paused);pause.setAttribute('aria-label',paused?'继续动画':'暂停动画');document.getElementById('kmh').textContent=paused?'0':Math.round(speed*18);}63pause.onclick=()=>{paused=!paused;showPause();};showPause();document.getElementById('speed').oninput=e=>{speed=+e.target.value;showPause();};document.getElementById('view').onclick=()=>{mode=(mode+1)%3;yaw=pitch=0;zoom=1;};document.getElementById('reset').onclick=()=>{mode=0;yaw=pitch=0;zoom=1;speed=1;document.getElementById('speed').value=1;distance=t=0;paused=false;showPause();};addEventListener('keydown',e=>{if(e.code==='Space'&&!['INPUT','BUTTON'].includes(document.activeElement.tagName)){e.preventDefault();pause.click();}});64const canvas=renderer.domElement;canvas.addEventListener('pointerdown',e=>{drag={x:e.clientX,y:e.clientY};canvas.setPointerCapture(e.pointerId);});canvas.addEventListener('pointermove',e=>{if(drag){yaw-=(e.clientX-drag.x)*.006;pitch=T.MathUtils.clamp(pitch+(e.clientY-drag.y)*.004,-.3,.65);drag={x:e.clientX,y:e.clientY};}});canvas.addEventListener('pointerup',()=>drag=null);canvas.addEventListener('pointercancel',()=>drag=null);canvas.addEventListener('wheel',e=>{e.preventDefault();zoom=T.MathUtils.clamp(zoom+e.deltaY*.001,.6,2);},{passive:false});65addEventListener('resize',()=>{camera.aspect=innerWidth/innerHeight;camera.updateProjectionMatrix();renderer.setSize(innerWidth,innerHeight);});66let last=performance.now(),frames=0;const offsets=[[7,2.8,-9],[-.1,3.9,9],[-13,10,-2]],target=new T.Vector3();function update(dt){if(!paused){t+=dt;distance+=dt*speed*5;}ride.position.y=Math.sin(t*5)*.012;head.rotation.y=Math.sin(t*.55)*.055;head.rotation.z=Math.sin(t*1.5)*.015;67for(const w of wheels)w.rotation.x=-distance/.435;for(const g of tiles)g.position.z=((g.userData.base+distance+150)%300+300)%300-150;68seaMat.uniforms.time.value=t;for(let i=0;i<foam.length;i++){foam[i].position.x=-10.85+Math.sin(t*.8+i*.6)*.3;foam[i].material.opacity=.25+Math.sin(t+i)*.15;}69const a=ribbonGeo.attributes.position;for(let i=0;i<a.count;i++){const z=ribbonBase[i*3+2];a.setY(i,ribbonBase[i*3+1]+Math.sin(z*10-t*9)*.055*(z-.35));}a.needsUpdate=true;ribbonGeo.computeVertexNormals();birds.forEach((b,i)=>{b.wings[0].rotation.z=Math.sin(t*3+i)*.3;b.wings[1].rotation.z=-Math.sin(t*3+i)*.3;b.g.position.y=7+i%3+Math.sin(t+i)*.2;});70let v=new T.Vector3(...offsets[mode]);v.applyAxisAngle(new T.Vector3(0,1,0),yaw);v.y+=pitch*10;v.multiplyScalar(zoom*(innerWidth<680?1.3:1));target.set(ride.position.x,1.55,0);camera.position.copy(target).add(v);camera.lookAt(target);renderer.render(scene,camera);frames++;71window.__scene={ready:true,frames,distance,time:t,paused,speed,wheelAngle:wheels[0].rotation.x,wheelRadius:.435,mode,drawCalls:renderer.info.render.calls};}72function frame(now){requestAnimationFrame(frame);const dt=Math.min((now-last)/1000,.05);last=now;update(document.hidden?0:dt);}requestAnimationFrame(frame);73renderer.domElement.addEventListener('webglcontextlost',e=>{e.preventDefault();paused=true;showPause();const el=document.getElementById('error');el.style.display='block';el.textContent='3D 显示暂时中断,请刷新页面恢复。';});74})();75</script></body></html>