skatemakerpro/index.html
type-two d68fa84f92 Kit thumbnails + prop colliders
js/kitthumbs.js renders each kit GLB to a 128x96 sprite once (offscreen
renderer, framed by bounding sphere), POSTs to /api/kit-thumb, and all 52
are committed under assets/kit_thumbs/ so every checkout gets instant thumbs;
the generator only runs for props without one (future kit drops). KIT drawer
shows them with category groups and filter.

js/colliders.js derives world-space colliders from kit manifest hints +
a table for builtin props (bins, planters, trunks, fences...). Test ride
resolves circle-vs-box/circle push-out — you scrub and stop instead of
riding through furniture. Exports gain an additive COLLIDERS array for
bookquoy to adopt when its player is ready; existing contract untouched.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-03 21:13:36 +10:00

106 lines
5.2 KiB
HTML

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>SKATEMAKER PRO — park builder</title>
<style>
:root{ --bg:#14181e; --panel:#1c222b; --panel2:#232b36; --ink:#dbe4ee; --dim:#7d8a99;
--gold:#ffd93b; --gold2:#b8860b; --line:#2e3947; --danger:#ff6b6b; }
*{box-sizing:border-box}
body{margin:0;overflow:hidden;background:var(--bg);color:var(--ink);
font:13px/1.4 "SF Mono",Menlo,monospace}
#app{display:grid;height:100vh;
grid-template-rows:46px 1fr 26px;
grid-template-columns:170px 1fr 230px;
grid-template-areas:"top top top" "pal view insp" "status status status"}
#topbar{grid-area:top;display:flex;align-items:center;gap:8px;padding:0 12px;
background:var(--panel);border-bottom:1px solid var(--line)}
.logo{font-weight:bold;font-size:16px;letter-spacing:1px;margin-right:6px;color:var(--ink)}
.logo b{color:var(--gold)}
#palette{grid-area:pal;overflow-y:auto;background:var(--panel);
border-right:1px solid var(--line);padding:10px}
#viewport{grid-area:view;position:relative;min-width:0;min-height:0}
#viewport canvas{display:block}
#inspector{grid-area:insp;overflow-y:auto;background:var(--panel);
border-left:1px solid var(--line);padding:10px}
#status{grid-area:status;display:flex;align-items:center;padding:0 12px;
background:var(--panel);border-top:1px solid var(--line);color:var(--dim);
white-space:nowrap;overflow:hidden}
#drawer{display:none;position:fixed;left:178px;bottom:34px;width:400px;max-height:56vh;
flex-direction:column;gap:8px;overflow-y:auto;background:var(--panel2);
border:1px solid var(--line);border-radius:8px;padding:12px;z-index:20;
box-shadow:0 12px 40px #000a}
.ptitle{color:var(--dim);font-size:11px;letter-spacing:2px;margin:14px 0 6px}
.ptitle:first-child{margin-top:0}
.ptitle .dim{letter-spacing:0;color:#5a6673}
.pgrid{display:grid;grid-template-columns:1fr 1fr;gap:5px}
.pcol{display:flex;flex-direction:column;gap:5px}
.pbtn{background:var(--panel2);border:1px solid var(--line);color:var(--ink);
font:inherit;font-size:11px;padding:7px 6px;border-radius:5px;cursor:pointer;
letter-spacing:.5px}
.pbtn:hover{border-color:var(--gold);color:var(--gold)}
.pbtn.active{background:var(--gold);color:#1a1a1a;border-color:var(--gold);font-weight:bold}
.pbtn.accent{border-color:var(--gold2);color:var(--gold)}
.pbtn.danger{color:var(--danger)}
.pbtn.wide{width:100%}
.pbtn.test{font-weight:bold}
.snaprow{display:flex;gap:6px;align-items:center;color:var(--dim);font-size:11px;
padding:4px 2px;cursor:pointer}
.ititle{font-weight:bold;color:var(--gold);letter-spacing:1px;margin-bottom:8px}
.ihint{color:var(--dim);font-size:11px;margin:8px 0}
.irow{display:flex;align-items:center;gap:6px;margin:5px 0}
.irow>label{width:66px;flex-shrink:0;color:var(--dim);font-size:11px}
.numwrap{display:flex;align-items:center;gap:5px;flex:1;min-width:0}
input[type=range]{flex:1;min-width:0;accent-color:var(--gold)}
input.num{width:52px;background:var(--panel2);border:1px solid var(--line);
color:var(--ink);font:inherit;font-size:11px;padding:2px 4px;border-radius:4px}
input.text,select{background:var(--panel2);border:1px solid var(--line);color:var(--ink);
font:inherit;font-size:12px;padding:4px 6px;border-radius:4px;min-width:0}
input.parkname{width:150px;color:var(--gold);font-weight:bold}
input[type=color]{background:none;border:1px solid var(--line);border-radius:4px;
width:44px;height:24px;padding:1px}
.ibtns{display:flex;gap:6px;margin-top:12px}
.ibtns .pbtn{flex:1}
.checklist{display:flex;flex-direction:column;gap:5px}
.checkitem{background:var(--panel);border:1px solid var(--line);border-radius:5px;
padding:7px 9px;font-size:12px;color:#f0c674}
.checkitem.link{cursor:pointer}
.checkitem.link:hover{border-color:var(--gold)}
.checkok{color:#7bd88f;padding:8px}
.mbgen{display:flex;flex-wrap:wrap;gap:6px}
.mbgen input{flex:1 1 100%}
.mbstatus{color:var(--dim);font-size:11px;flex:1;align-self:center}
.mblist{display:flex;flex-direction:column;gap:4px;max-height:30vh;overflow-y:auto}
.mbitem{display:flex;align-items:center;gap:6px;background:var(--panel);
border:1px solid var(--line);border-radius:5px;padding:5px 8px}
.mbname{flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:11px}
.kitthumb{width:44px;height:33px;object-fit:contain;border-radius:3px;background:#141a21;flex-shrink:0}
</style>
</head>
<body>
<div id="app">
<div id="topbar"></div>
<div id="palette"></div>
<div id="viewport"></div>
<div id="inspector"></div>
<div id="status">booting…</div>
</div>
<div id="drawer"></div>
<script type="importmap">
{"imports":{"three":"https://unpkg.com/three@0.160.0/build/three.module.js",
"three/addons/":"https://unpkg.com/three@0.160.0/examples/jsm/"}}
</script>
<script type="module">
import { initEditor } from './js/editor.js';
import { initUI } from './js/ui.js';
import { attachTestRide } from './js/testride.js';
const ed = initEditor(document.getElementById('viewport'));
attachTestRide(ed);
ed.boot().then(() => initUI(ed));
window.__ed = ed; // headless testing hook
</script>
</body>
</html>