다람쥐 모델에 눈/코/주둥이/다리와 곡선형 꼬리를 더해 디테일을 높이고, 바구니가 꽉 찼을 때 quotes.txt에서 랜덤 명언을 골라 축하 메시지와 함께 보여주도록 추가

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-25 22:53:50 +09:00
parent 09d8514b99
commit d6cb8c7a92
3 changed files with 86 additions and 7 deletions

View File

@@ -88,7 +88,20 @@
padding:0.85rem 1.3rem; border-radius:14px;
font-weight:700; font-size:1.05rem;
box-shadow:0 10px 28px rgba(0,0,0,0.22);
max-width: 80vw;
}
.celebrate .quote{
display:none;
margin-top:0.5rem;
font-weight:500; font-size:0.85rem;
color:#4c6155;
}
.celebrate .quote.show{ display:block; }
@media (prefers-color-scheme: dark){
.celebrate .quote{ color:#9db3a6; }
}
:root[data-theme="dark"] .celebrate .quote{ color:#9db3a6; }
:root[data-theme="light"] .celebrate .quote{ color:#4c6155; }
@media (prefers-color-scheme: dark){
.celebrate .msg{ background: rgba(20,28,22,0.92); color:#eaf3ec; }
}
@@ -107,7 +120,12 @@
<div class="stage" id="stage">
<div class="hud">🧺 <span id="basketCount">0 / 5</span></div>
<div class="confetti-layer" id="confettiLayer"></div>
<div class="celebrate" id="celebrate"><div class="msg">🎉 사과 5개를 모았어요!</div></div>
<div class="celebrate" id="celebrate">
<div class="msg">
🎉 사과 5개를 모았어요!
<div class="quote" id="quoteText"></div>
</div>
</div>
</div>
<p class="hint">나무를 드래그해서 흔들면 사과가 떨어져요. 바구니에 5개를 모아보세요 🧺</p>
@@ -117,6 +135,13 @@ const stage = document.getElementById('stage');
const celebrateEl = document.getElementById('celebrate');
const confettiLayer = document.getElementById('confettiLayer');
const basketCountEl = document.getElementById('basketCount');
const quoteEl = document.getElementById('quoteText');
let quotes = [];
fetch('./quotes.txt')
.then(r => (r.ok ? r.text() : ''))
.then(text => { quotes = text.split('\n').map(s => s.trim()).filter(Boolean); })
.catch(() => {});
// ---------- renderer / scene / camera ----------
const renderer = new THREE.WebGLRenderer({ antialias: true, alpha: false, powerPreference: 'low-power' });
@@ -340,6 +365,8 @@ function knockOffLeaf(){
function createSquirrelMesh(){
const bodyMat = new THREE.MeshStandardMaterial({ color: 0x8a6642, roughness: 0.9, flatShading: true });
const bellyMat = new THREE.MeshStandardMaterial({ color: 0xe4c9a0, roughness: 0.9, flatShading: true });
const darkMat = new THREE.MeshStandardMaterial({ color: 0x231a14, roughness: 0.5, flatShading: true });
const noseMat = new THREE.MeshStandardMaterial({ color: 0x3a2a1e, roughness: 0.5, flatShading: true });
const g = new THREE.Group();
@@ -356,6 +383,23 @@ function createSquirrelMesh(){
head.position.set(0, 0.06, 0.22);
g.add(head);
const snout = new THREE.Mesh(new THREE.SphereGeometry(0.055, 7, 6), bellyMat);
snout.scale.set(0.9, 0.75, 1);
snout.position.set(0, 0.02, 0.31);
g.add(snout);
const nose = new THREE.Mesh(new THREE.SphereGeometry(0.017, 5, 4), noseMat);
nose.position.set(0, 0.035, 0.36);
g.add(nose);
const eyeGeo = new THREE.SphereGeometry(0.02, 5, 4);
const eyeL = new THREE.Mesh(eyeGeo, darkMat);
eyeL.position.set(-0.045, 0.11, 0.28);
g.add(eyeL);
const eyeR = eyeL.clone();
eyeR.position.x = 0.045;
g.add(eyeR);
const earGeo = new THREE.ConeGeometry(0.035, 0.07, 6);
const earL = new THREE.Mesh(earGeo, bodyMat);
earL.position.set(-0.06, 0.15, 0.26);
@@ -365,11 +409,35 @@ function createSquirrelMesh(){
earR.position.x = 0.06;
g.add(earR);
const tail = new THREE.Mesh(new THREE.SphereGeometry(0.14, 8, 6), bodyMat);
tail.scale.set(0.7, 1.4, 0.7);
tail.position.set(0, 0.2, -0.24);
tail.rotation.x = 0.7;
g.add(tail);
const legGeo = new THREE.SphereGeometry(0.042, 6, 5);
const legSpots = [
[-0.08, -0.1, 0.16], [0.08, -0.1, 0.16],
[-0.09, -0.1, -0.12], [0.09, -0.1, -0.12],
];
for (const [x, y, z] of legSpots){
const leg = new THREE.Mesh(legGeo, bodyMat);
leg.scale.set(0.75, 0.65, 0.75);
leg.position.set(x, y, z);
g.add(leg);
}
// bushy curved tail made of tapering, gradually offset segments
const tailGroup = new THREE.Group();
tailGroup.position.set(0, 0.1, -0.22);
tailGroup.rotation.x = 0.45;
g.add(tailGroup);
const tailSegs = [
{ r: 0.1, y: 0, z: 0 },
{ r: 0.095, y: 0.13, z: -0.02 },
{ r: 0.085, y: 0.25, z: -0.02 },
{ r: 0.07, y: 0.35, z: 0.02 },
{ r: 0.05, y: 0.43, z: 0.08 },
];
for (const seg of tailSegs){
const s = new THREE.Mesh(new THREE.SphereGeometry(seg.r, 7, 6), bodyMat);
s.position.set(0, seg.y, seg.z);
tailGroup.add(s);
}
return g;
}
@@ -525,6 +593,12 @@ function catchInBasket(apple){
function celebrate(){
celebrateEl.classList.add('show');
if (quotes.length){
quoteEl.textContent = quotes[Math.floor(Math.random() * quotes.length)];
quoteEl.classList.add('show');
} else {
quoteEl.classList.remove('show');
}
spawnConfetti();
setTimeout(clearBasket, 2800);
}