Wildlife Kingdom — original browser zoo-management sim

- Procedural canvas art: 15 species × 4 poses, 15 buildings, modular terrain, UI icons
- Habitat enclosure detection, biome suitability, staff/guest AI pathfinding
- Economy: tickets, shops, wages, star rating, research tiers, 15 missions
- Day/night cycle, particles, WebAudio soundtrack & synth SFX
- Mouse + touch controls, responsive UI, localStorage autosave
- Verified: 31/31 logic tests (test/smoke.js) · 12/12 render audits (test/qa.html)
This commit is contained in:
2026-08-23 07:01:21 +00:00
commit c06786518c
22 changed files with 5130 additions and 0 deletions
+260
View File
@@ -0,0 +1,260 @@
/* ============================================================
Wildlife Kingdom — original UI identity
Cream paper panels · rounded chunks · soft warm shadows
============================================================ */
:root{
--cream:#fdf6e7;
--cream-2:#f6ead1;
--ink:#43362a;
--ink-soft:#7a6a55;
--green:#63b34c;
--green-dark:#3e8a33;
--orange:#ff9d47;
--orange-dark:#e07f26;
--sky:#69c3ef;
--red:#e85d5d;
--gold:#ffc93c;
--line:rgba(122,94,50,.16);
--shadow:0 6px 18px rgba(80,52,10,.22), 0 2px 4px rgba(80,52,10,.14);
--shadow-sm:0 3px 8px rgba(80,52,10,.18);
--r-lg:20px; --r-md:14px; --r-pill:999px;
--font:'Baloo 2', 'Comic Sans MS', 'Segoe UI Rounded', system-ui, sans-serif;
}
*{box-sizing:border-box; -webkit-tap-highlight-color:transparent;}
html,body{margin:0;padding:0;height:100%;overflow:hidden;background:#87ceeb;}
body{font-family:var(--font);color:var(--ink);user-select:none;-webkit-user-select:none;}
#app{position:fixed;inset:0;}
#game{position:absolute;inset:0;width:100%;height:100%;display:block;touch-action:none;cursor:grab;}
#game.tooling{cursor:crosshair;}
.hidden{display:none !important;}
/* ---------- generic panel & buttons ---------- */
.panel{
background:linear-gradient(180deg,#fffdf6, var(--cream));
border:2px solid var(--line);
border-radius:var(--r-lg);
box-shadow:var(--shadow);
}
.btn{
font-family:var(--font);font-weight:700;color:var(--ink);
background:linear-gradient(180deg,#fffdf4,#f3e6c9);
border:2px solid rgba(122,94,50,.22);
border-radius:var(--r-pill);
box-shadow:var(--shadow-sm);
padding:8px 16px;font-size:15px;line-height:1.1;cursor:pointer;
transition:transform .06s ease, filter .12s ease;
}
.btn:hover{filter:brightness(1.04);}
.btn:active{transform:translateY(2px);box-shadow:0 1px 2px rgba(80,52,10,.2);}
.btn.primary{
background:linear-gradient(180deg,#8fd45e,#5cab43);
color:#fff;border-color:rgba(30,80,20,.35);
text-shadow:0 1px 0 rgba(0,0,0,.18);
}
.btn.danger{background:linear-gradient(180deg,#ff9d8a,#e85d5d);color:#fff;border-color:rgba(120,20,20,.3);}
.btn.ghost{background:rgba(255,253,244,.75);}
.btn.big{font-size:19px;padding:13px 34px;letter-spacing:.4px;}
.btn:disabled{opacity:.45;cursor:default;transform:none;}
.icobtn{width:42px;height:42px;padding:0;display:inline-flex;align-items:center;justify-content:center;font-size:19px;}
/* ---------- top HUD ---------- */
#hud-top{
position:absolute;top:10px;left:10px;right:10px;
display:flex;align-items:flex-start;gap:10px;flex-wrap:wrap;
pointer-events:none;z-index:20;
}
#hud-top .hud-group{display:flex;gap:8px;pointer-events:auto;}
#hud-top .hud-group.right{margin-left:auto;}
.stat.pill{
display:flex;align-items:center;gap:7px;
background:linear-gradient(180deg,#fffdf6,var(--cream-2));
border:2px solid var(--line);border-radius:var(--r-pill);
box-shadow:var(--shadow-sm);padding:7px 14px 7px 9px;font-size:15px;
}
.stat.pill b{font-weight:800;font-variant-numeric:tabular-nums;}
.stat.pill.time{gap:9px;}
.stat.pill.time i{font-style:normal;color:var(--ink-soft);font-size:13px;font-weight:600;}
.stat.pill.stars b{color:var(--orange-dark);}
.stat .ic{width:24px;height:24px;display:block;}
.stat.flash{animation:pillFlash .5s ease;}
@keyframes pillFlash{0%{transform:scale(1)}30%{transform:scale(1.12)}100%{transform:scale(1)}}
.speedctl{display:flex;gap:4px;background:rgba(255,253,244,.8);border-radius:var(--r-pill);padding:4px;border:2px solid var(--line);}
.speedctl .spd{width:36px;height:36px;box-shadow:none;background:transparent;border-color:transparent;}
.speedctl .spd.active{background:linear-gradient(180deg,#ffe9b8,#ffd47a);border-color:rgba(122,94,50,.28);}
/* ---------- bottom toolbar ---------- */
#toolbar{
position:absolute;left:50%;bottom:12px;transform:translateX(-50%);
display:flex;flex-direction:column;align-items:center;gap:8px;z-index:20;
max-width:calc(100vw - 16px);
}
#tb-row{
display:flex;align-items:center;gap:8px;
background:linear-gradient(180deg,#fffdf6,#f3e6c9);
border:2px solid var(--line);border-radius:var(--r-pill);
box-shadow:var(--shadow);padding:7px 10px;
}
#tb-categories{display:flex;gap:4px;overflow-x:auto;scrollbar-width:none;max-width:min(62vw,560px);}
#tb-categories::-webkit-scrollbar{display:none;}
.tb-cat{
display:flex;flex-direction:column;align-items:center;gap:1px;
min-width:58px;padding:4px 8px;border-radius:14px;cursor:pointer;
border:2px solid transparent;background:transparent;font-family:var(--font);
color:var(--ink-soft);font-weight:700;font-size:11px;
}
.tb-cat canvas{width:30px;height:30px;}
.tb-cat.active{background:linear-gradient(180deg,#ffe9b8,#ffd47a);border-color:rgba(122,94,50,.3);color:var(--ink);}
.tb-cat:hover:not(.active){background:rgba(255,233,184,.4);}
#tb-palette{
display:flex;gap:7px;align-items:stretch;justify-content:flex-start;
background:linear-gradient(180deg,#fffdf6,#f3e6c9);
border:2px solid var(--line);border-radius:18px;box-shadow:var(--shadow);
padding:8px;max-width:min(92vw,720px);overflow-x:auto;overflow-y:hidden;
scrollbar-width:thin;animation:palIn .18s ease;
}
@keyframes palIn{from{opacity:0;transform:translateY(6px);}to{opacity:1;transform:none;}}
.pal-item{
position:relative;flex:0 0 auto;width:76px;padding:6px 4px 7px;
display:flex;flex-direction:column;align-items:center;gap:2px;
border:2px solid transparent;border-radius:14px;cursor:pointer;background:rgba(255,255,255,.35);
font-family:var(--font);color:var(--ink);
}
.pal-item canvas{width:52px;height:52px;}
.pal-item .nm{font-size:10.5px;font-weight:700;line-height:1.05;text-align:center;}
.pal-item .pr{font-size:10px;font-weight:800;color:var(--green-dark);}
.pal-item:hover{background:#ffefc9;}
.pal-item.active{border-color:var(--orange);background:#fff1d6;}
.pal-item.locked{opacity:.5;filter:grayscale(.8);cursor:default;}
.pal-item.locked::after{
content:'🔒';position:absolute;top:2px;right:4px;font-size:11px;
}
#tool-info{
display:flex;flex-direction:column;padding:2px 10px;min-width:110px;
}
#tool-info b{font-size:12.5px;}
#tool-info span{font-size:11px;color:var(--ink-soft);font-weight:700;}
#btn-bulldoze.active{background:linear-gradient(180deg,#ffb08a,#e86a4a);color:#fff;}
/* ---------- side panel ---------- */
#side-panel{
position:absolute;top:64px;right:10px;width:272px;max-height:calc(100vh - 170px);
z-index:18;display:flex;flex-direction:column;overflow:hidden;
}
.sp-tabs{display:flex;gap:4px;padding:8px 8px 0;}
.sp-tab{
flex:1;font-family:var(--font);font-weight:800;font-size:12.5px;color:var(--ink-soft);
background:transparent;border:none;border-radius:12px 12px 0 0;padding:7px 2px;cursor:pointer;
}
.sp-tab.active{background:var(--cream-2);color:var(--ink);box-shadow:inset 0 -3px 0 var(--orange);}
#sp-content{padding:8px 10px 12px;overflow-y:auto;scrollbar-width:thin;}
.mission{
background:rgba(255,255,255,.5);border:2px solid var(--line);border-radius:14px;
padding:8px 10px;margin-bottom:8px;font-size:12.5px;line-height:1.25;
}
.mission .m-title{font-weight:800;display:flex;justify-content:space-between;gap:6px;align-items:center;}
.mission.done{opacity:.62;border-style:dashed;}
.mission.done .m-title::after{content:'✔';color:var(--green-dark);}
.mission.current{border-color:var(--orange);background:#fff3da;animation:mGlow 2s infinite alternate;}
@keyframes mGlow{from{box-shadow:0 0 0 rgba(255,157,71,0);}to{box-shadow:0 0 12px rgba(255,157,71,.55);}}
.mission .m-hint{color:var(--ink-soft);font-size:11.5px;margin-top:2px;}
.mission .m-reward{color:var(--green-dark);font-weight:800;font-size:11.5px;}
.res-row{
display:flex;align-items:center;justify-content:space-between;gap:8px;
background:rgba(255,255,255,.5);border:2px solid var(--line);border-radius:14px;
padding:7px 10px;margin-bottom:8px;font-size:12.5px;font-weight:700;
}
.res-row small{display:block;font-weight:600;color:var(--ink-soft);font-size:10.5px;}
.res-row .btn{padding:5px 12px;font-size:12px;}
.zoo-stat{display:flex;justify-content:space-between;font-size:12.5px;padding:4px 2px;border-bottom:1px dashed var(--line);}
.zoo-stat b{font-variant-numeric:tabular-nums;}
/* ---------- selection popup ---------- */
#sel-popup{
position:absolute;z-index:25;padding:10px 12px;min-width:210px;max-width:260px;
font-size:12.5px;animation:popIn .14s ease;
}
@keyframes popIn{from{opacity:0;transform:scale(.92);}to{opacity:1;transform:scale(1);}}
#sel-popup h3{margin:0 0 4px;font-size:15px;display:flex;align-items:center;gap:8px;}
#sel-popup h3 canvas,#sel-popup h3 img{width:40px;height:40px;border-radius:10px;}
#sel-popup .bar{height:10px;border-radius:999px;background:rgba(122,94,50,.15);overflow:hidden;margin:3px 0 6px;}
#sel-popup .bar i{display:block;height:100%;border-radius:999px;transition:width .25s ease;}
#sel-popup .row{display:flex;justify-content:space-between;margin:2px 0;}
#sel-popup .btns{display:flex;gap:6px;margin-top:8px;}
#sel-popup .btns .btn{flex:1;padding:6px 8px;font-size:12.5px;text-align:center;}
/* ---------- toasts ---------- */
#toasts{
position:absolute;top:64px;left:50%;transform:translateX(-50%);
display:flex;flex-direction:column;align-items:center;gap:6px;z-index:40;pointer-events:none;
}
.toast{
background:rgba(67,54,42,.92);color:#fff8ea;font-weight:700;font-size:13.5px;
padding:9px 18px;border-radius:999px;box-shadow:var(--shadow);
animation:toastIn .22s ease;max-width:78vw;text-align:center;
}
.toast.good{background:rgba(62,138,51,.95);}
.toast.bad{background:rgba(200,60,50,.95);}
.toast.gold{background:linear-gradient(180deg,#ffb63c,#e07f26);color:#fff;text-shadow:0 1px 0 rgba(0,0,0,.15);}
.toast.fade{opacity:0;transform:translateY(-8px);transition:all .4s ease;}
@keyframes toastIn{from{opacity:0;transform:translateY(-10px) scale(.9);}to{opacity:1;transform:none;}}
/* ---------- main menu / hero ---------- */
#menu{
position:absolute;inset:0;z-index:50;display:flex;align-items:center;justify-content:center;
background:radial-gradient(120% 90% at 50% 0%, rgba(255,244,214,.25), rgba(30,60,20,.32) 78%);
}
.menu-inner{text-align:center;padding:20px;max-width:640px;}
.title{margin:0;font-weight:800;line-height:.92;letter-spacing:2px;
filter:drop-shadow(0 4px 0 rgba(90,50,10,.35)) drop-shadow(0 10px 24px rgba(20,40,10,.45));}
.title span{display:block;font-size:clamp(44px,9vw,84px);color:#fffdf4;
text-shadow:0 5px 0 #e07f26, 0 5px 0 rgba(0,0,0,.12);}
.title em{display:block;font-style:normal;font-size:clamp(56px,11.5vw,108px);
color:#ffe066;text-shadow:0 6px 0 #3e8a33;}
.tagline{color:#fffdf4;font-weight:700;font-size:clamp(14px,2.4vw,19px);
margin:14px 0 26px;text-shadow:0 2px 6px rgba(20,40,10,.65);}
.menu-buttons{display:flex;flex-direction:column;gap:12px;align-items:center;}
.menu-buttons .btn.big{min-width:250px;}
.menu-buttons .btn.primary{animation:breathe 2.4s ease-in-out infinite;}
@keyframes breathe{0%,100%{transform:scale(1);}50%{transform:scale(1.04);}}
.menu-foot{margin-top:26px;color:rgba(255,253,244,.85);font-size:12px;font-weight:600;text-shadow:0 1px 3px rgba(20,40,10,.5);}
/* ---------- modals ---------- */
#modal-root{position:absolute;inset:0;z-index:60;pointer-events:none;}
.modal-backdrop{
position:absolute;inset:0;background:rgba(40,30,15,.45);backdrop-filter:blur(2px);
display:flex;align-items:center;justify-content:center;pointer-events:auto;animation:fadeIn .15s ease;
}
@keyframes fadeIn{from{opacity:0}to{opacity:1}}
.modal{
width:min(480px, calc(100vw - 32px));max-height:82vh;overflow-y:auto;
padding:18px 20px;animation:popIn .16s ease;
}
.modal h2{margin:0 0 10px;font-size:21px;}
.modal p,.modal li{font-size:13.5px;line-height:1.5;color:var(--ink);}
.modal ul{padding-left:18px;margin:8px 0;}
.modal .modal-btns{display:flex;gap:8px;justify-content:flex-end;margin-top:14px;}
.set-row{display:flex;align-items:center;justify-content:space-between;padding:8px 0;border-bottom:1px dashed var(--line);font-weight:700;font-size:14px;}
/* ---------- misc ---------- */
#sp-toggle{position:absolute;right:10px;top:64px;z-index:17;width:42px;height:42px;font-size:18px;}
.badge-dot{position:absolute;top:-3px;right:-3px;width:14px;height:14px;border-radius:50%;
background:var(--red);border:2px solid #fff;animation:breathe 1.2s infinite;}
.kbd{background:#efe3c6;border:1px solid var(--line);border-bottom-width:3px;border-radius:6px;padding:0 6px;font-family:inherit;font-weight:800;font-size:12px;}
@media (max-width:820px){
#st-joy,#st-research{display:none;}
.stat.pill{padding:5px 10px 5px 7px;font-size:13px;}
.stat .ic{width:20px;height:20px;}
.speedctl .spd{width:32px;height:32px;}
#side-panel{width:min(300px, calc(100vw - 20px));top:112px;}
.tb-cat{min-width:46px;padding:3px 6px;}
.tb-cat .lbl{display:none;}
.pal-item{width:66px;}
.menu-buttons .btn.big{min-width:220px;font-size:17px;}
}
@media (max-height:520px){
#toolbar{bottom:6px;}
.stat.pill.time i{display:none;}
}