public/index.html
分割 1 / 6 · 元ファイル 1〜175行付近
<!doctype html>
<html lang="ja">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1,viewport-fit=cover,user-scalable=no">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black">
<meta name="robots" content="noindex,nofollow,noarchive">
<title>石とゾンビ</title>
<style>
*{margin:0;padding:0;box-sizing:border-box;-webkit-tap-highlight-color:transparent;
-webkit-touch-callout:none;-webkit-user-select:none;user-select:none}
html,body{height:100%}
/* 遊ぶ画面はスクロールしない(五月雨開発の決まり) */
body{background:#05070b;color:#f2ead6;overflow:hidden;overscroll-behavior:none;
font-family:"Hiragino Kaku Gothic ProN","Yu Gothic",sans-serif}
#app{height:100dvh;display:flex;flex-direction:column;
padding-top:env(safe-area-inset-top);padding-bottom:env(safe-area-inset-bottom)}
#stage{flex:1 1 auto;min-height:0;position:relative;overflow:hidden;touch-action:none}
#cv{display:block;width:100%;height:100%}
/* 着せ替えとお店だけDOM。ここは縦に流してよい(遊ぶ画面ではないので) */
.sheet{position:absolute;inset:0;z-index:20;background:#0d131a;display:none;flex-direction:column}
.sheet.on{display:flex}
.sheet .top{flex:0 0 auto;display:flex;align-items:center;gap:8px;padding:10px 12px;
background:#16202b;border-bottom:2px solid #2b3b4a}
.sheet .top b{font-size:15px}
.sheet .top .sc{margin-left:auto;font-size:12px;color:#ffce6a;font-weight:bold}
.sheet .top .x{font-size:12px;font-weight:bold;color:#f2ead6;background:#22303d;
border:1.5px solid #3a4d5e;border-radius:9px;padding:7px 13px}
.sheet .tabs{flex:0 0 auto;display:flex;gap:6px;padding:8px 12px 0}
.sheet .tabs button{flex:1;font-size:12px;font-weight:bold;color:#b9c6d2;background:#16202b;
border:1.5px solid #2b3b4a;border-radius:9px 9px 0 0;padding:8px 0}
.sheet .tabs button.on{color:#0d131a;background:#ffce6a;border-color:#ffce6a}
.sheet .body{flex:1 1 auto;min-height:0;overflow-y:auto;-webkit-overflow-scrolling:touch;padding:10px 12px 24px}
.row{display:flex;align-items:center;gap:10px;background:#16202b;border:1.5px solid #263442;
border-radius:11px;padding:8px 10px;margin-bottom:7px}
.row canvas{flex:0 0 auto;background:#0a1016;border-radius:8px}
.row .nm{font-size:13px;font-weight:bold}
.row .pr{font-size:11px;color:#9fb0c0;margin-top:2px}
.row .sp{flex:1 1 auto}
.row button{flex:0 0 auto;font-size:12px;font-weight:bold;border-radius:9px;
padding:9px 12px 17px;border:1.5px solid #3a4d5e;color:#f2ead6;background:#22303d;min-width:76px}
.row button.buy{background:#ffce6a;border-color:#ffce6a;color:#0d131a}
.row button.on{background:#3f7d52;border-color:#3f7d52;color:#fff}
.row button.ng{background:#1b232c;border-color:#2b3b4a;color:#6d7d8c;position:relative}
.row button.ng i{position:absolute;left:0;right:0;bottom:3px;color:#c8604f;font-style:normal;
font-size:9px;font-weight:bold;text-align:center}
.grid{display:grid;grid-template-columns:repeat(4,1fr);gap:7px}
.grid button{background:#16202b;border:2px solid #263442;border-radius:10px;padding:7px 0;
color:#f2ead6;font-size:11px}
.grid button.on{border-color:#ffce6a}
.lab{font-size:11px;color:#9fb0c0;font-weight:bold;margin:12px 2px 6px}
</style>
</head>
<body>
<div id="app"><div id="stage">
<canvas id="cv"></canvas>
<div class="sheet" id="shAv">
<div class="top"><b>アバター変更</b><span class="sc" id="avSc"></span><button class="x" id="avX">とじる</button></div>
<div class="body" id="avBody"></div>
</div>
<div class="sheet" id="shShop">
<div class="top"><b>ショップ</b><span class="sc" id="spSc"></span><button class="x" id="spX">とじる</button></div>
<div class="tabs" id="spTabs"></div>
<div class="body" id="spBody"></div>
</div>
</div></div>
<script>window.HZG_BUILD='';</script>
<script src="lib/matter.min.js"></script>
<script src="lib/shurechara.js?v=20260913b"></script>
<script src="lib/world.js?v=20260913b"></script>
<script src="lib/wardrobe.js?v=20260913b"></script>
<script>
'use strict';
/* ===== 石とゾンビ =====
正本: docs/K1仕様書_20260913.md(芯) と docs/K1仕様書_磨き込みとマルチ_20260913.md(磨き込みとマルチ)
芯(立ち上げの日から1文字も変えていない): **上から落とす軌道を作れるか**の遊び。
横から当てても押し戻すだけ。頭の上から急な角度で落ちてきた石だけが倒せる。
絵は1枚も使わない。人は共有財産のシュールキャラ(lib/shurechara.js)。
世界の中身(ゾンビ・カラス・HP・スクラップ)は lib/world.js。
**サーバーと端末が同じ1本を回す。**石だけはここ(投げた本人の端末)で飛ばす——
通信を待たせないため(K1「投石は操作レスポンス最優先」)。 */
var VER='v20260913b';
/* ===== 音(合成。音のファイルは持たない) ===== */
var actx=null;
function ac(){
if(!actx){ var C=window.AudioContext||window.webkitAudioContext; if(!C) return null; actx=new C(); }
if(actx.state==='suspended'){ try{ actx.resume(); }catch(e){} }
return actx;
}
function tone(f1,f2,vol,dur,type){
var c=ac(); if(!c) return;
var o=c.createOscillator(), g=c.createGain(); o.type=type||'sine';
o.frequency.setValueAtTime(f1,c.currentTime);
o.frequency.exponentialRampToValueAtTime(Math.max(24,f2),c.currentTime+dur);
g.gain.setValueAtTime(vol,c.currentTime);
g.gain.exponentialRampToValueAtTime(0.0001,c.currentTime+dur);
o.connect(g); g.connect(c.destination); o.start(); o.stop(c.currentTime+dur+0.03);
}
function nz(vol,dur,type,f1,f2,q){
var c=ac(); if(!c) return;
var n=Math.max(1,Math.floor(c.sampleRate*dur)), b=c.createBuffer(1,n,c.sampleRate), d=b.getChannelData(0);
for(var i=0;i<n;i++) d[i]=Math.random()*2-1;
var s=c.createBufferSource(); s.buffer=b;
var f=c.createBiquadFilter(); f.type=type||'bandpass';
f.frequency.setValueAtTime(f1,c.currentTime);
if(f2) f.frequency.exponentialRampToValueAtTime(Math.max(24,f2),c.currentTime+dur);
f.Q.value=(q==null?1:q);
var g=c.createGain(); g.gain.setValueAtTime(vol,c.currentTime);
g.gain.exponentialRampToValueAtTime(0.0001,c.currentTime+dur);
s.connect(f); f.connect(g); g.connect(c.destination); s.start();
}
function seThrow(){ nz(.055,.10,'highpass',1700,3200); }
function seKill(){ tone(190,62,.20,.15,'square'); nz(.16,.20,'bandpass',430,170,.8);
setTimeout(function(){ nz(.11,.34,'lowpass',320,90,.6); },70); }
function seKnock(){ tone(124,84,.09,.09,'triangle'); nz(.06,.10,'bandpass',820,460,1.3); }
function seGround(v){ nz(Math.min(.09,.012+v*0.00010),.07,'bandpass',1500,700,1.4); }
function seLose(){ tone(240,46,.20,.95,'sawtooth'); setTimeout(function(){ nz(.12,.6,'lowpass',260,70,.6); },90); }
function seStart(){ tone(520,790,.10,.12,'triangle'); }
function seScrap(){ tone(880,1320,.045,.07,'triangle'); }
function seRock(){ tone(70,38,.26,.55,'sawtooth'); nz(.20,.45,'lowpass',260,70,.6); }
function seRockRdy(){ tone(420,720,.07,.10,'triangle'); setTimeout(function(){ tone(720,980,.06,.10,'triangle'); },90); }
function seCrow(){ tone(1250,760,.09,.13,'sawtooth'); setTimeout(function(){ tone(980,620,.07,.11,'sawtooth'); },110); }
function seGrab(){ tone(150,96,.12,.30,'sawtooth'); nz(.09,.30,'lowpass',500,180,.7); }
function seSwap(){ tone(560,820,.08,.09,'square'); }
function seJoin(){ tone(640,960,.10,.11,'triangle'); setTimeout(function(){ tone(960,1240,.09,.12,'triangle'); },110); }
/* ===== 端末に覚えておく物 ===== */
var SAVE={ scrap:0, av:{h:0,hc:0,sk:0,w:0}, own:{c:[0],ht:[0],st:[0]}, eq:{c:0,ht:0,st:0}, best:0 };
function loadSave(){
try{
var j=JSON.parse(localStorage.getItem('ishizombie:v1')||'null');
if(j){ for(var k in SAVE) if(j[k]!=null) SAVE[k]=j[k]; }
}catch(e){}
if(!SAVE.own) SAVE.own={c:[0],ht:[0],st:[0]};
if(!SAVE.own.c||!SAVE.own.c.length) SAVE.own.c=[0];
if(!SAVE.own.ht||!SAVE.own.ht.length) SAVE.own.ht=[0];
if(!SAVE.own.st||!SAVE.own.st.length) SAVE.own.st=[0];
}
function putSave(){ try{ localStorage.setItem('ishizombie:v1',JSON.stringify(SAVE)); }catch(e){} }
loadSave();
function devId(){
var k='ishizombie:dev';
try{ var v=localStorage.getItem(k); if(v) return v;
v='d'+Date.now().toString(36)+Math.random().toString(36).slice(2,7);
localStorage.setItem(k,v); return v; }catch(e){ return 'd0'; }
}
/* ===== ゾンビの死人の肌と汚れた服 =====
共有財産のシュールキャラの色表へ**後ろから足すだけ**。
lib/shurechara.js そのものは1文字も直していない(あれは共有財産なので) */
var ZSKIN=['#93a97b','#a6b78e','#82996f'];
var ZSK0=SHURE.SKIN_COL.length;
SHURE.SKIN_COL.push.apply(SHURE.SKIN_COL,ZSKIN);
SHURE.WEAR_COL.push.apply(SHURE.WEAR_COL,['#5a5348','#6b6256','#4a5250','#7a6f5c']);
/* ===== ショップの品(lib/wardrobe.js)。届いていなくても遊べるようにする ===== */
var WR=window.WARDROBE||null;
if(!WR||!WR.STONES||!WR.STONES.length){
WR={ COSTUMES:[{id:'plain',nm:'そのまま',price:0,tone:'neutral',draw:function(){}}],
HATS:[{id:'none',nm:'なし',price:0,draw:function(){}}],
STONES:[{id:'plain',nm:'ふつうの石',price:0,draw:function(g,r){
g.fillStyle='#8d8e86'; g.beginPath();
for(var i=0;i<7;i++){ var a=i/7*Math.PI*2, rr=r*(0.82+((i*5)%3)*0.13);
if(i===0) g.moveTo(Math.cos(a)*rr,Math.sin(a)*rr); else g.lineTo(Math.cos(a)*rr,Math.sin(a)*rr); }
g.closePath(); g.fill();
g.fillStyle='rgba(255,255,240,.30)'; g.beginPath(); g.arc(-r*0.28,-r*0.30,r*0.34,0,7); g.fill();
}}] };
}
function wrGet(list,i){ return list[Math.max(0,Math.min(list.length-1,i|0))]; }
/* ===== 盤面の寸法 =====