retire-provenance: superseded Q b/ objects (sha256 CAS, HOLO-LEAN-RESOLVER P1.3)
Browse files- sha256/084ffe7f687c3a3ea081a102e10a82d5496af5ad9da86fbd54b767130a122149 +409 -0
- sha256/17468ff2158a7c4e874e959d72e0bcfd1de69975905c46596afb1ed828a80d21 +0 -0
- sha256/46107459f9b835eeab666a14d2a4efcc6a70bad38e84e899b52ea719ab85ce2c +1 -0
- sha256/8e73919c7f3276c213d41bdd8408c33c034d87ecac05b36a143fc0a6c75659d2 +0 -0
sha256/084ffe7f687c3a3ea081a102e10a82d5496af5ad9da86fbd54b767130a122149
ADDED
|
@@ -0,0 +1,409 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!doctype html>
|
| 2 |
+
<meta charset="utf-8">
|
| 3 |
+
<meta name="viewport" content="width=device-width,initial-scale=1">
|
| 4 |
+
<title>Bonsai</title>
|
| 5 |
+
<style>
|
| 6 |
+
/* Claude-desktop look: warm charcoal stage, 11% hairlines, 12-14px radii, serif display, coral accent.
|
| 7 |
+
Type floor is 15px everywhere. Everything fits ONE viewport at 1280×800 AND 375×812 (dvh + clamp). */
|
| 8 |
+
:root { --bg:#262624; --ink:#eceae4; --muted:#b8b5ad; --faint:rgba(232,230,224,.45);
|
| 9 |
+
--hair:rgba(255,255,255,.11); --coral:#d97757; --coral-hi:#e8987a; --lav:#c4b5fd; --r:13px;
|
| 10 |
+
--serif:"Tiempos Text","Charter","Iowan Old Style",Georgia,serif; }
|
| 11 |
+
* { box-sizing:border-box; margin:0; padding:0; }
|
| 12 |
+
html,body { height:100%; }
|
| 13 |
+
body { background:var(--bg); color:var(--ink); font:15px/1.55 system-ui,-apple-system,"Segoe UI",Inter,sans-serif;
|
| 14 |
+
-webkit-font-smoothing:antialiased; overflow:hidden; }
|
| 15 |
+
.num { font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace; font-size:15px; }
|
| 16 |
+
.stage { position:fixed; inset:0; height:100dvh; display:flex; flex-direction:column; align-items:center;
|
| 17 |
+
justify-content:center; padding:24px; opacity:0; pointer-events:none; transition:opacity .6s ease; }
|
| 18 |
+
.stage.on { opacity:1; pointer-events:auto; }
|
| 19 |
+
|
| 20 |
+
/* ── arrival: full-bleed hologram canvas behind left-aligned copy ── */
|
| 21 |
+
#arrive { flex-direction:row; align-items:center; justify-content:flex-start; padding:0; }
|
| 22 |
+
#holo { position:absolute; inset:0; width:100%; height:100%; display:block; }
|
| 23 |
+
#arrive .copy { position:relative; z-index:1; max-width:620px; padding:0 clamp(24px,6vw,96px); }
|
| 24 |
+
#arrive h1 { font-family:var(--serif); font-weight:500; font-size:clamp(40px,7vw,84px); line-height:1.06;
|
| 25 |
+
letter-spacing:-.015em; text-wrap:balance; }
|
| 26 |
+
#arrive .sub { margin-top:22px; font-size:clamp(18px,1.6vw,20px); line-height:1.5; color:var(--muted); max-width:46ch; }
|
| 27 |
+
#arrive button { margin-top:34px; background:var(--coral); border:none; color:#faf9f5; border-radius:999px;
|
| 28 |
+
padding:16px 34px; font:500 17px/1 system-ui,sans-serif; cursor:pointer;
|
| 29 |
+
transition:transform .2s ease, filter .2s ease; }
|
| 30 |
+
#arrive button:hover { filter:brightness(1.08); transform:translateY(-1px); }
|
| 31 |
+
#arrive button:disabled { filter:saturate(.3) brightness(.8); cursor:default; transform:none; }
|
| 32 |
+
#arrive .fine { margin-top:16px; font-size:15px; color:var(--faint); }
|
| 33 |
+
@media (max-width:760px) { /* stacked: hologram floats in the upper half, copy anchors low */
|
| 34 |
+
#arrive { align-items:flex-end; }
|
| 35 |
+
#arrive .copy { padding-bottom:max(44px, env(safe-area-inset-bottom, 0px) + 36px); }
|
| 36 |
+
}
|
| 37 |
+
|
| 38 |
+
/* ── chat: the Claude-desktop room — serif greeting, ONE centered input card, quiet metrics ── */
|
| 39 |
+
#chat { justify-content:center; padding:0; }
|
| 40 |
+
#chat .col { display:flex; flex-direction:column; width:min(100%,760px); height:100%; padding:0 22px; justify-content:center; }
|
| 41 |
+
#chat .col.started { justify-content:flex-start; }
|
| 42 |
+
#hello { text-align:center; margin-bottom:26px; }
|
| 43 |
+
#hello .mark { display:inline-block; width:30px; height:30px; vertical-align:-4px; margin-right:12px; color:var(--coral); }
|
| 44 |
+
#hello h2 { display:inline; font-family:var(--serif); font-weight:500; font-size:clamp(30px,4.4vw,44px); letter-spacing:-.01em; }
|
| 45 |
+
.col.started #hello { display:none; }
|
| 46 |
+
#log { display:none; }
|
| 47 |
+
.col.started #log { display:block; flex:1; overflow-y:auto; padding:30px 4px 16px; }
|
| 48 |
+
.msg { margin:0 0 20px; }
|
| 49 |
+
.msg .who { font-size:15px; color:var(--faint); margin-bottom:4px; }
|
| 50 |
+
.msg.user .who { color:var(--muted); }
|
| 51 |
+
.msg .body { white-space:pre-wrap; font-size:16px; line-height:1.6; }
|
| 52 |
+
details.think { margin:0 0 8px; border-left:2px solid var(--hair); padding-left:10px; }
|
| 53 |
+
details.think summary { list-style:none; cursor:pointer; font-size:15px; color:var(--faint); }
|
| 54 |
+
details.think summary::-webkit-details-marker { display:none; }
|
| 55 |
+
details.think .t { color:var(--faint); font-size:15px; white-space:pre-wrap; margin-top:4px; }
|
| 56 |
+
.note { text-align:center; font-size:15px; color:var(--faint); margin:6px 0 12px; }
|
| 57 |
+
#card { background:rgba(255,255,255,.03); border:1px solid var(--hair); border-radius:18px; padding:6px 8px 8px; }
|
| 58 |
+
#card:focus-within { border-color:rgba(217,119,87,.5); }
|
| 59 |
+
#inp { width:100%; background:none; border:none; color:var(--ink); padding:14px 12px 10px;
|
| 60 |
+
font:16px/1.45 system-ui,sans-serif; outline:none; }
|
| 61 |
+
#cardrow { display:flex; align-items:center; gap:12px; padding:0 8px 4px 12px; }
|
| 62 |
+
#modelname { font-size:15px; color:var(--muted); }
|
| 63 |
+
#cardrow .grow { flex:1; }
|
| 64 |
+
#send { background:var(--coral); border:none; color:#faf9f5; border-radius:999px; width:38px; height:38px;
|
| 65 |
+
font:500 17px/1 system-ui,sans-serif; cursor:pointer; transition:filter .2s ease; }
|
| 66 |
+
#send:hover { filter:brightness(1.08); }
|
| 67 |
+
#send:disabled { background:rgba(255,255,255,.06); color:var(--faint); cursor:default; }
|
| 68 |
+
#metrics { text-align:center; font-size:15px; color:var(--faint); padding:12px 0 16px; min-height:22px; }
|
| 69 |
+
#metrics b { color:var(--muted); font-weight:500; }
|
| 70 |
+
#err { color:#f87171; font-size:15px; padding:0 0 18px; text-align:center; white-space:pre-wrap; }
|
| 71 |
+
.spn { display:inline-block; width:1.1ch; font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;
|
| 72 |
+
color:var(--coral-hi); margin-right:7px; }
|
| 73 |
+
</style>
|
| 74 |
+
<body>
|
| 75 |
+
|
| 76 |
+
<div class="stage on" id="arrive">
|
| 77 |
+
<canvas id="holo"></canvas>
|
| 78 |
+
<div class="copy">
|
| 79 |
+
<h1>An AI<br>that’s yours.</h1>
|
| 80 |
+
<p class="sub">Bonsai streams into your browser like a film. Every piece is checked before it runs. After one visit it answers on your device, even offline.</p>
|
| 81 |
+
<button id="go">Stream the mind</button>
|
| 82 |
+
<div class="fine" id="fine">Free · open weights by Prism ML · nothing leaves your device</div>
|
| 83 |
+
</div>
|
| 84 |
+
</div>
|
| 85 |
+
|
| 86 |
+
<div class="stage" id="chat">
|
| 87 |
+
<div class="col" id="col">
|
| 88 |
+
<div id="hello">
|
| 89 |
+
<svg class="mark" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.6" stroke-linecap="round" aria-hidden="true"><path d="M12 3v18M3 12h18M5.6 5.6l12.8 12.8M18.4 5.6L5.6 18.4"/></svg><h2>Ready when you are.</h2>
|
| 90 |
+
</div>
|
| 91 |
+
<div id="log"></div>
|
| 92 |
+
<div id="card">
|
| 93 |
+
<input id="inp" placeholder="Ask Bonsai anything" autocomplete="off">
|
| 94 |
+
<div id="cardrow">
|
| 95 |
+
<span id="modelname">Bonsai</span>
|
| 96 |
+
<span class="grow"></span>
|
| 97 |
+
<button id="send" aria-label="Send">↑</button>
|
| 98 |
+
</div>
|
| 99 |
+
</div>
|
| 100 |
+
<div id="metrics">on your device</div>
|
| 101 |
+
<div id="err" hidden></div>
|
| 102 |
+
</div>
|
| 103 |
+
</div>
|
| 104 |
+
|
| 105 |
+
<script type="module">
|
| 106 |
+
// ── the holographic centerpiece ─────────────────────────────────────────────────────────────
|
| 107 |
+
// Fresnel rim + animated scan stripes + occasional slice glitch, additive-blended over the warm
|
| 108 |
+
// charcoal. In the spirit of ektogamat/threejs-vanilla-holographic-material (MIT) — this GLSL is
|
| 109 |
+
// written fresh for this page. three.js is VENDORED (no CDN) and imported AFTER first paint so
|
| 110 |
+
// the hero text never waits on it. On "Stream the mind" the loop stops and the GL context is
|
| 111 |
+
// released (dispose + lose-context) — the model engine gets the whole GPU.
|
| 112 |
+
const cv = document.getElementById("holo");
|
| 113 |
+
const reduced = matchMedia("(prefers-reduced-motion: reduce)").matches;
|
| 114 |
+
window.__holoOff = null;
|
| 115 |
+
try {
|
| 116 |
+
// hero paints first: two rAFs when the tab is visible, 250ms fallback when rAF is starved
|
| 117 |
+
// (hidden/embedded tabs never fire rAF — the hologram must still initialize for their return).
|
| 118 |
+
await new Promise((r) => { requestAnimationFrame(() => requestAnimationFrame(r)); setTimeout(r, 250); });
|
| 119 |
+
if (!document.getElementById("arrive").classList.contains("on")) throw new Error("past landing");
|
| 120 |
+
const THREE = await import("./vendor/three.module.min.js");
|
| 121 |
+
const renderer = new THREE.WebGLRenderer({ canvas: cv, antialias: true, alpha: true, powerPreference: "high-performance" });
|
| 122 |
+
renderer.setPixelRatio(Math.min(devicePixelRatio || 1, 2));
|
| 123 |
+
const scene = new THREE.Scene();
|
| 124 |
+
const cam = new THREE.PerspectiveCamera(45, 1, 0.1, 30); cam.position.z = 5;
|
| 125 |
+
const uni = {
|
| 126 |
+
uTime: { value: 0 },
|
| 127 |
+
uSpeed: { value: 0.35 }, // stripe scroll; boosted on hover
|
| 128 |
+
uGlitch: { value: reduced ? 0 : 1 },
|
| 129 |
+
uBase: { value: new THREE.Color("#d97757") }, // Claude coral core
|
| 130 |
+
uRim: { value: new THREE.Color("#c4b5fd") }, // lavender at the fresnel rim
|
| 131 |
+
};
|
| 132 |
+
const mat = new THREE.ShaderMaterial({
|
| 133 |
+
uniforms: uni, transparent: true, blending: THREE.AdditiveBlending, side: THREE.DoubleSide, depthWrite: false,
|
| 134 |
+
vertexShader: /* glsl */`
|
| 135 |
+
uniform float uTime; uniform float uGlitch;
|
| 136 |
+
varying vec3 vN; varying vec3 vW;
|
| 137 |
+
float rnd(float n){ return fract(sin(n)*43758.5453123); }
|
| 138 |
+
void main(){
|
| 139 |
+
vec3 p = position;
|
| 140 |
+
float gate = step(0.72, rnd(floor(uTime*1.8))) * uGlitch; // a burst every few seconds
|
| 141 |
+
float band = step(0.94, rnd(floor(p.y*9.0) + floor(uTime*7.0))); // thin horizontal slices
|
| 142 |
+
float g = gate * band;
|
| 143 |
+
p.x += (rnd(p.y*13.7 + floor(uTime*7.0)) - 0.5) * 0.22 * g;
|
| 144 |
+
p.z += (rnd(p.y*7.1 - floor(uTime*7.0)) - 0.5) * 0.22 * g;
|
| 145 |
+
vec4 w = modelMatrix * vec4(p, 1.0);
|
| 146 |
+
vW = w.xyz; vN = normalize(mat3(modelMatrix) * normal);
|
| 147 |
+
gl_Position = projectionMatrix * viewMatrix * w;
|
| 148 |
+
}`,
|
| 149 |
+
fragmentShader: /* glsl */`
|
| 150 |
+
uniform float uTime; uniform float uSpeed; uniform vec3 uBase; uniform vec3 uRim;
|
| 151 |
+
varying vec3 vN; varying vec3 vW;
|
| 152 |
+
void main(){
|
| 153 |
+
vec3 V = normalize(cameraPosition - vW);
|
| 154 |
+
vec3 N = normalize(vN); if (!gl_FrontFacing) N = -N;
|
| 155 |
+
float f = pow(1.0 - clamp(dot(V, N), 0.0, 1.0), 2.2); // fresnel rim
|
| 156 |
+
float s = pow(fract(vW.y * 6.0 - uTime * uSpeed), 3.0); // scan stripes
|
| 157 |
+
gl_FragColor = vec4(mix(uBase, uRim, f), f * 0.85 + s * 0.28 + 0.03);
|
| 158 |
+
}`,
|
| 159 |
+
});
|
| 160 |
+
const mesh = new THREE.Mesh(new THREE.IcosahedronGeometry(1.35, 12), mat);
|
| 161 |
+
scene.add(mesh);
|
| 162 |
+
// pointer parallax: mesh tilts toward the cursor (eased); stripes speed up when the cursor is near.
|
| 163 |
+
let tx = 0, ty = 0, rx = 0, ry = 0, boost = 0, tBoost = 0;
|
| 164 |
+
if (!reduced) addEventListener("pointermove", (e) => {
|
| 165 |
+
const nx = e.clientX / innerWidth * 2 - 1, ny = e.clientY / innerHeight * 2 - 1;
|
| 166 |
+
tx = nx * 0.45; ty = ny * 0.35;
|
| 167 |
+
const c = mesh.position.clone().project(cam); // mesh center in NDC
|
| 168 |
+
tBoost = Math.hypot(nx - c.x, ny - c.y) < 0.55 ? 1 : 0;
|
| 169 |
+
}, { passive: true });
|
| 170 |
+
const size = () => { // right-of-center desktop · upper-half mobile
|
| 171 |
+
renderer.setSize(innerWidth, innerHeight, false);
|
| 172 |
+
cam.aspect = innerWidth / innerHeight; cam.updateProjectionMatrix();
|
| 173 |
+
const hw = Math.tan(cam.fov * Math.PI / 360) * cam.position.z; // half-height of the frustum at z=0
|
| 174 |
+
if (cam.aspect > 0.92) { mesh.position.set(hw * cam.aspect * 0.48, 0, 0); mesh.scale.setScalar(1); }
|
| 175 |
+
else { mesh.position.set(0, hw * 0.42, 0); mesh.scale.setScalar(0.78); }
|
| 176 |
+
};
|
| 177 |
+
size(); addEventListener("resize", size);
|
| 178 |
+
let raf = 0, dead = false;
|
| 179 |
+
const loop = () => {
|
| 180 |
+
const t = performance.now() / 1000;
|
| 181 |
+
uni.uTime.value = t;
|
| 182 |
+
boost += (tBoost - boost) * 0.06;
|
| 183 |
+
uni.uSpeed.value = 0.35 + boost * 1.1;
|
| 184 |
+
rx += (ty - rx) * 0.05; ry += (tx - ry) * 0.05; // eased parallax
|
| 185 |
+
mesh.rotation.x = rx * 0.6; mesh.rotation.y = t * 0.12 + ry; // + constant slow spin
|
| 186 |
+
renderer.render(scene, cam);
|
| 187 |
+
raf = requestAnimationFrame(loop);
|
| 188 |
+
};
|
| 189 |
+
document.addEventListener("visibilitychange", () => {
|
| 190 |
+
if (dead) return;
|
| 191 |
+
if (document.hidden) cancelAnimationFrame(raf); else raf = requestAnimationFrame(loop);
|
| 192 |
+
});
|
| 193 |
+
loop();
|
| 194 |
+
// verification hook: force one frame at time t and return it as a PNG (rAF is starved in
|
| 195 |
+
// hidden/embedded panes — this is how an agent proves the hologram actually draws pixels).
|
| 196 |
+
window.__holoFrame = (t) => { uni.uTime.value = t || 0; renderer.render(scene, cam); return cv.toDataURL("image/png"); };
|
| 197 |
+
window.__holoOff = () => { // MANDATORY before the engine loads
|
| 198 |
+
dead = true; cancelAnimationFrame(raf); window.__holoFrame = null;
|
| 199 |
+
mesh.geometry.dispose(); mat.dispose(); renderer.dispose();
|
| 200 |
+
try { renderer.getContext().getExtension("WEBGL_lose_context").loseContext(); } catch {}
|
| 201 |
+
cv.remove();
|
| 202 |
+
};
|
| 203 |
+
} catch {} // no WebGL / past landing → hero still perfect
|
| 204 |
+
</script>
|
| 205 |
+
|
| 206 |
+
<script type="module">
|
| 207 |
+
import init, { qvac_load_gpu, qvac_gpu_free } from "../q/pkg/holospaces_web.js";
|
| 208 |
+
import { loadKappaObject } from "../q/holo-load2bit.mjs";
|
| 209 |
+
import { createQvacGPU } from "../q/qvac-gpu.js?v=68";
|
| 210 |
+
import { createEngine } from "../q/core/engine.js";
|
| 211 |
+
|
| 212 |
+
const $ = (id) => document.getElementById(id);
|
| 213 |
+
// braille pulse: one 80ms ticker animates every [data-spin] slot (loading, waking, thinking) —
|
| 214 |
+
// a single interval, zero layout work, stops mattering the instant the last spinner is removed.
|
| 215 |
+
const SPIN = ["⠋","⠙","⠹","⠸","⠼","⠴","⠦","⠧","⠇","⠏"];
|
| 216 |
+
let _sf = 0;
|
| 217 |
+
setInterval(() => { const els = document.querySelectorAll("[data-spin]"); if (!els.length) return; const f = SPIN[_sf = (_sf + 1) % SPIN.length]; for (const el of els) el.textContent = f; }, 80);
|
| 218 |
+
const spn = () => `<span class="spn" data-spin>${SPIN[0]}</span>`;
|
| 219 |
+
const show = (id) => { for (const s of document.querySelectorAll(".stage")) s.classList.toggle("on", s.id === id); };
|
| 220 |
+
const short = (k) => String(k).replace(/^(did:holo:)?sha256:/, "").slice(0, 12) + "…";
|
| 221 |
+
|
| 222 |
+
// model source: ?src=<κ-object dir> for dev, HF mirror by default; ?m=27b flips to the 27B
|
| 223 |
+
// κ-object (engine support for its hybrid layers lands separately — the guard below is honest).
|
| 224 |
+
// Everything shown to the user (name, size) is READ FROM THE MANIFEST — swapping the κ URL is a
|
| 225 |
+
// zero-code model change. The manifest pin is the Law-L5 anchor.
|
| 226 |
+
const QS = new URLSearchParams(location.search);
|
| 227 |
+
const SRC = QS.get("src");
|
| 228 |
+
const DEFAULT_27B = false; // flips true the moment the hybrid engine passes parity (κ-object already published)
|
| 229 |
+
const M27 = QS.get("m") === "27b" || (DEFAULT_27B && QS.get("m") !== "8b");
|
| 230 |
+
const BASE = SRC || (M27 ? "https://huggingface.co/HOLOGRAMTECH/q-bonsai-27b/resolve/main" : "https://huggingface.co/HOLOGRAMTECH/q-bonsai-8b/resolve/main");
|
| 231 |
+
const PIN = M27 ? "did:holo:sha256:00beea03509a79f6567879f019c04bb15dda8721f6320f8295d9ea1dd9ba54af" : "did:holo:sha256:a0dc81f26ec5ce98b28ee9c1fab620e91c20720f777e1de6bc416317f54d27e2";
|
| 232 |
+
const CTX = 640;
|
| 233 |
+
|
| 234 |
+
let engine = null, gpu = null, rootK = "", warm = false, mind = [], thinkOpen = -1, thinkClose = -1;
|
| 235 |
+
let modelName = "Bonsai"; // set from the manifest once loaded
|
| 236 |
+
|
| 237 |
+
let entered = false, streamStarted = false;
|
| 238 |
+
async function stream() {
|
| 239 |
+
try { if (window.__holoOff) { window.__holoOff(); window.__holoOff = null; } } catch {} // hand the GPU to the engine
|
| 240 |
+
if (entered) { show("chat"); $("inp").focus(); } // welcome stays until the visitor chooses to enter
|
| 241 |
+
const t0 = performance.now();
|
| 242 |
+
const phase = (s) => { $("metrics").innerHTML = s; };
|
| 243 |
+
try {
|
| 244 |
+
if (!navigator.gpu) throw new Error("This browser has no WebGPU. Chrome or Edge 113+ required.");
|
| 245 |
+
// warm-load detect (honest offline copy): the κ-cache already holds the manifest → this visit is disk-speed.
|
| 246 |
+
try { const c = await caches.open("holo-kappa-v1"); warm = !!(await c.match(BASE + "/manifest.json")); } catch {}
|
| 247 |
+
phase(spn() + (warm ? "waking up · from your disk" : "streaming the mind · every piece verified on arrival"));
|
| 248 |
+
await init();
|
| 249 |
+
// PARALLEL κ-BLOCKS FIRST (the measured Netflix path on CDN origins): 399 content-addressed
|
| 250 |
+
// blocks race over 12 connections (HF cdn-lfs caps a single stream ~2 MB/s; the parallel parts
|
| 251 |
+
// path measured 6.8 MB/s on the same line). The ONE-file .holo stays as the fallback for
|
| 252 |
+
// origins that only host the wafer (and its OPFS warm store makes revisits 0-network either way).
|
| 253 |
+
let r, pinned = true, holo = false;
|
| 254 |
+
try {
|
| 255 |
+
r = await loadKappaObject(BASE, { expectKappa: PIN, blake3: false });
|
| 256 |
+
} catch (e1) {
|
| 257 |
+
try {
|
| 258 |
+
const hs = await import("../q/core/holo-stream-load.mjs");
|
| 259 |
+
r = await hs.openHoloKappa(BASE + "/q-bonsai-8b.v1.holo", {});
|
| 260 |
+
holo = true; warm = !!r.warm;
|
| 261 |
+
} catch (e) { if (!SRC) throw e; pinned = false; r = await loadKappaObject(BASE, { allowUnpinned: true, blake3: false }); } // dev fallback only under ?src
|
| 262 |
+
}
|
| 263 |
+
rootK = (r.info && r.info.root) || PIN;
|
| 264 |
+
modelName = String((r.info && r.info.model) || "Bonsai").replace(/-/g, " ");
|
| 265 |
+
$("modelname").textContent = modelName;
|
| 266 |
+
// HONESTY GATE: the hybrid (27B) engine ships separately — never pretend to run what we can't.
|
| 267 |
+
if (r.manifest.hybrid) throw new Error(modelName + " uses a hybrid engine this build does not include yet. It is arriving; the 8B runs today.");
|
| 268 |
+
// progress = ONE quiet line under the input (per-tensor counters; no grid, no takeover)
|
| 269 |
+
const total = (r.manifest.tensors || []).length;
|
| 270 |
+
let done = 0, bytes = 0;
|
| 271 |
+
const tick = () => {
|
| 272 |
+
const dt = (performance.now() - t0) / 1000;
|
| 273 |
+
if (warm) phase(`${spn()}waking up · ${Math.round(done / Math.max(total, 1) * 100)}%`);
|
| 274 |
+
else phase(`${spn()}streaming the mind · <b>${(bytes / 1e6).toFixed(0)} MB</b> · ${(bytes / 1e6 / Math.max(dt, .1)).toFixed(1)} MB/s · every piece verified on arrival`);
|
| 275 |
+
};
|
| 276 |
+
const iv = setInterval(tick, 400);
|
| 277 |
+
const fetchT = async (name) => { // wrap fetchTensor: the progress driver
|
| 278 |
+
const b = await r.fetchTensor(name);
|
| 279 |
+
bytes += b.byteLength; done++;
|
| 280 |
+
return b;
|
| 281 |
+
};
|
| 282 |
+
// tokenizer header (arch + vocab) — embedded in the .holo (0 extra fetches), else same κ-object dir.
|
| 283 |
+
const hdr = r.tokenizerBytes || new Uint8Array(await (await fetch(BASE + "/tokenizer.gguf")).arrayBuffer());
|
| 284 |
+
const lr = JSON.parse(qvac_load_gpu(hdr));
|
| 285 |
+
if (lr.error) throw new Error("tokenizer: " + lr.error);
|
| 286 |
+
try { qvac_gpu_free(); } catch {}
|
| 287 |
+
r.manifest.kv4 = false; // exact f32 KV (the proven first-light config)
|
| 288 |
+
gpu = await createQvacGPU(r.manifest, fetchT, CTX, lr.eos ?? 2, false);
|
| 289 |
+
clearInterval(iv);
|
| 290 |
+
phase(spn() + "waking the mind");
|
| 291 |
+
const m = { fam: "Bonsai", name: modelName, size: "1.16 GB", fmt: "q1 1-bit κ", cap: 200, ctx: CTX, qwen: true, chat: true, rep: 1.05, gpu: true, gpuOnly: true };
|
| 292 |
+
engine = await createEngine(m, { gpu, info: lr, imageKappa: r.info.root });
|
| 293 |
+
// Qwen3 thinking markers are atomic tokens and clean() strips them from text — split by token id instead.
|
| 294 |
+
const o = engine.tokenize("<think>"), c2 = engine.tokenize("</think>");
|
| 295 |
+
thinkOpen = o.length === 1 ? o[0] : -1; thinkClose = c2.length === 1 ? c2[0] : -1;
|
| 296 |
+
enterChat();
|
| 297 |
+
} catch (e) {
|
| 298 |
+
$("metrics").textContent = ""; $("err").hidden = false;
|
| 299 |
+
$("err").textContent = String(e && e.message || e);
|
| 300 |
+
console.error(e);
|
| 301 |
+
}
|
| 302 |
+
}
|
| 303 |
+
|
| 304 |
+
// the ONLY chat metrics, one quiet line: model · time to first word · tok/s (operator spec)
|
| 305 |
+
let lastTTFT = null, lastTokps = null;
|
| 306 |
+
function metrics(state) {
|
| 307 |
+
const bits = [`<b>${modelName}</b>`];
|
| 308 |
+
if (lastTTFT != null) bits.push(`${lastTTFT.toFixed(1)}s to first word`);
|
| 309 |
+
if (lastTokps != null) bits.push(`<b>${lastTokps.toFixed(0)}</b> tokens a second`);
|
| 310 |
+
if (!lastTTFT && !lastTokps) bits.push(state || (warm ? "from your disk · works offline" : "on your device"));
|
| 311 |
+
$("metrics").innerHTML = bits.join(" · ");
|
| 312 |
+
}
|
| 313 |
+
let pendingQ = null;
|
| 314 |
+
function enterChat() {
|
| 315 |
+
ready = true; $("send").disabled = false; metrics();
|
| 316 |
+
if (pendingQ) { $("inp").value = pendingQ; pendingQ = null; send(); }
|
| 317 |
+
else if (entered) $("inp").focus();
|
| 318 |
+
}
|
| 319 |
+
|
| 320 |
+
const log = $("log");
|
| 321 |
+
function msg(who, cls) {
|
| 322 |
+
const d = document.createElement("div"); d.className = "msg " + cls;
|
| 323 |
+
d.innerHTML = `<div class="who">${who}</div>`;
|
| 324 |
+
const think = document.createElement("details"); think.className = "think"; think.hidden = true;
|
| 325 |
+
think.innerHTML = `<summary><span class="sum">thinking…</span></summary><div class="t"></div>`;
|
| 326 |
+
const body = document.createElement("div"); body.className = "body";
|
| 327 |
+
d.appendChild(think); d.appendChild(body); log.appendChild(d);
|
| 328 |
+
log.scrollTop = log.scrollHeight;
|
| 329 |
+
return { body, think, t: think.querySelector(".t") };
|
| 330 |
+
}
|
| 331 |
+
function note(s) { const d = document.createElement("div"); d.className = "note"; d.textContent = s; log.appendChild(d); }
|
| 332 |
+
|
| 333 |
+
// split streamed outIds into thinking + answer segments by the <think> token ids (robust: no
|
| 334 |
+
// tags at position 0 → everything is answer; unclosed → everything so far is thinking).
|
| 335 |
+
function splitThink(outIds) {
|
| 336 |
+
if (thinkOpen < 0 || outIds[0] !== thinkOpen) return { think: [], ans: outIds };
|
| 337 |
+
const c = outIds.indexOf(thinkClose);
|
| 338 |
+
if (c < 0) return { think: outIds.slice(1), ans: [] };
|
| 339 |
+
return { think: outIds.slice(1, c), ans: outIds.slice(c + 1) };
|
| 340 |
+
}
|
| 341 |
+
|
| 342 |
+
let busy = false, ready = false;
|
| 343 |
+
async function send() {
|
| 344 |
+
const q = $("inp").value.trim();
|
| 345 |
+
if (!q || busy) return;
|
| 346 |
+
if (!ready || !engine) { // the mind is still arriving: hold ONE question, it sends itself
|
| 347 |
+
pendingQ = q; $("inp").value = "";
|
| 348 |
+
$("col").classList.add("started");
|
| 349 |
+
msg("You", "user").body.textContent = q;
|
| 350 |
+
note("your question will send itself the moment the mind arrives");
|
| 351 |
+
return;
|
| 352 |
+
}
|
| 353 |
+
busy = true; $("inp").value = ""; $("send").disabled = true;
|
| 354 |
+
$("col").classList.add("started"); // the greeting yields to the conversation (Claude-room transition)
|
| 355 |
+
msg("You", "user").body.textContent = q;
|
| 356 |
+
const turn = engine.tokenize(engine.frameTurn(q, mind.length > 0));
|
| 357 |
+
if (mind.length + turn.length + 256 > CTX) { mind = []; note("fresh context"); } // keep ≥256 tokens of room (thinking model)
|
| 358 |
+
const ids = mind.concat(mind.length ? turn : engine.tokenize(engine.frameTurn(q, false)));
|
| 359 |
+
const ui = msg(modelName, "bot"); ui.body.innerHTML = spn();
|
| 360 |
+
ui.think.querySelector("summary").innerHTML = spn() + `<span class="sum">thinking</span>`;
|
| 361 |
+
const tSend = performance.now(); let ttftSet = false;
|
| 362 |
+
lastTTFT = null; lastTokps = null;
|
| 363 |
+
$("metrics").innerHTML = spn() + `<b>${modelName}</b> is thinking`;
|
| 364 |
+
try {
|
| 365 |
+
const res = await engine.generate(ids, {
|
| 366 |
+
maxNew: CTX - ids.length - 8, // the whole remaining KV window (eos stops early)
|
| 367 |
+
onToken: (t) => {
|
| 368 |
+
if (!ttftSet) { ttftSet = true; lastTTFT = (performance.now() - tSend) / 1000; } // TTFT = send → first visible token
|
| 369 |
+
if (t.stats && t.stats.tokps) lastTokps = t.stats.tokps;
|
| 370 |
+
metrics();
|
| 371 |
+
const s = splitThink(t.outIds);
|
| 372 |
+
if (s.think.length) { ui.think.hidden = false; ui.t.textContent = engine.detokenize(s.think); }
|
| 373 |
+
if (s.ans.length) ui.think.querySelector("summary").innerHTML = `<span class="sum">thinking…</span>`; // answer flowing: the pulse retires
|
| 374 |
+
ui.body.textContent = s.ans.length ? engine.detokenize(s.ans) : (s.think.length ? "" : t.text);
|
| 375 |
+
log.scrollTop = log.scrollHeight;
|
| 376 |
+
},
|
| 377 |
+
});
|
| 378 |
+
const s = splitThink(res.outIds || []);
|
| 379 |
+
ui.think.querySelector("summary").innerHTML = `<span class="sum">thinking…</span>`;
|
| 380 |
+
if (s.think.length) { ui.think.hidden = false; ui.t.textContent = engine.detokenize(s.think); }
|
| 381 |
+
if (s.ans.length) {
|
| 382 |
+
ui.body.textContent = engine.detokenize(s.ans);
|
| 383 |
+
mind = ids.concat(s.ans); // history carries the ANSWER only (Qwen3 convention: prior-turn thinking dropped)
|
| 384 |
+
} else if (s.think.length) {
|
| 385 |
+
ui.body.textContent = "I ran out of room while thinking. Ask again, a bit shorter."; // unclosed <think> at the window edge; turn not kept
|
| 386 |
+
} else {
|
| 387 |
+
ui.body.textContent = res.text;
|
| 388 |
+
mind = ids.concat(res.outIds || []);
|
| 389 |
+
}
|
| 390 |
+
if (res.stats && res.stats.tokps) lastTokps = res.stats.tokps;
|
| 391 |
+
metrics();
|
| 392 |
+
} catch (e) { ui.body.textContent = "Something broke: " + (e && e.message || e); }
|
| 393 |
+
busy = false; $("send").disabled = false; $("inp").focus();
|
| 394 |
+
log.scrollTop = log.scrollHeight;
|
| 395 |
+
}
|
| 396 |
+
|
| 397 |
+
$("go").addEventListener("click", () => {
|
| 398 |
+
entered = true; show("chat"); $("inp").focus();
|
| 399 |
+
if (!streamStarted) { streamStarted = true; stream(); }
|
| 400 |
+
});
|
| 401 |
+
$("send").disabled = true; // until the mind is resident (early questions queue)
|
| 402 |
+
(async () => { // returning visitor: the mind wakes BEHIND the welcome screen
|
| 403 |
+
try { const c = await caches.open("holo-kappa-v1"); if (await c.match(BASE + "/manifest.json")) { streamStarted = true; stream(); } } catch {}
|
| 404 |
+
})();
|
| 405 |
+
$("send").addEventListener("click", send);
|
| 406 |
+
$("inp").addEventListener("keydown", (e) => { if (e.key === "Enter") send(); });
|
| 407 |
+
if (!navigator.gpu) { $("go").disabled = true; $("fine").textContent = "This browser has no WebGPU. Chrome or Edge 113 or newer required."; }
|
| 408 |
+
</script>
|
| 409 |
+
</body>
|
sha256/17468ff2158a7c4e874e959d72e0bcfd1de69975905c46596afb1ed828a80d21
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
sha256/46107459f9b835eeab666a14d2a4efcc6a70bad38e84e899b52ea719ab85ce2c
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
{"@context":["https://www.w3.org/ns/did/v1","https://w3id.org/security/data-integrity/v2",{"dcterms":"http://purl.org/dc/terms/","links":{"@container":"@set","@id":"schema:hasPart"},"prov":"http://www.w3.org/ns/prov#","rel":"schema:additionalType","schema":"https://schema.org/"},{"holstr":"https://hologram.os/ns/strand#"}],"@type":["prov:Entity","holstr:Entry"],"holstr:kind":"release","holstr:op":"did:holo:sha256:87599792c7b13fad9d6c57d3b89d2cef35e4715869f5cde597ab60ca92699f0b","holstr:payload":{"apps":{"bonsai":"084ffe7f687c3a3ea081a102e10a82d5496af5ad9da86fbd54b767130a122149","carry":"9f44164b17e6b7e37f39623bb445d49b6608da5f2b04cef8b1a8b183362a6b9a","hello":"66c1ada5ac08a2d4f73851bbe7458ad5fb6e2226c5a131c3829abfe715900fed","holo-messenger":"01e2db1e847e73881b7ce6f251677a4c0f9629203d0bc913053ffb4047b1df2d","jellyfin":"3fd2a00105ad0e8f01109dce52ff4c7a0cc6c9444532b0453ed9d770cce2d3a4","resolve":"23aec66382775f5773693615338f37c37a5f58257ec95aece33a664baa6fe0db","yourspace":"9ffc94e11c78fbe6b940bc04ac92f0f12238cf7ce82b5092c53d5761146a50df"},"boot":{"entry":"holo-portal.mjs","sha256":"caeb3e57b6eeab09bee6d67c022145a8e2dae610c929a7f51bc169898625e7e8"},"closure":"93b9f7c93b2e9c1c2b88853b38417ca657ec1ce051c1b2860ac59e6fab1bf09e","manifest":"792736ddc9a6fa4d6ad612ac0bf851834e7acc46ba3ea335c6f0e32fdf24bda6","runtime":{"apps/q/pkg/holospaces_web.js":"b9f8a887de588c5193dc65ca9ce206a70c6df53595b848e0ce0299c2d88a5753","apps/q/pkg/holospaces_web_bg.wasm":"9d90b4ea8bccee8c239073c7ec7dd5158c6d11f1f668487570d17475d79e661e","apps/workspace/pkg/holospaces_web.js":"8c30aa5582dffe4548d27fa7863163eba689ed65126be472c003f5884fc8e534","usr/lib/holo/holowhat/holospaces_web.js":"a5b5d9c559a1d2812c33e9d7a4b1c24bb03059482503a23356e4c9c32d4d8b88","usr/lib/holo/holowhat/holospaces_web_bg.wasm":"00cfa4960b319ba1ba7291cddb20dea130a407ce8b42426218854f4f68f4dcd7","usr/lib/pkg/holospaces_web.js":"8c30aa5582dffe4548d27fa7863163eba689ed65126be472c003f5884fc8e534","usr/lib/pkg/holospaces_web_bg.wasm":"3370b22070b56218a57df6406e2e4ba7cc171e942d4d1f2ae4297ea2c4c08d9c"},"shell":"0e8ac9d7dd55694dcc30ce1cdcb688a2b624ba54eb90619bdf7251d123ba17b5","world":"38cc902c76906e93b6e98451fd88e4de17a0bc36df0515ea63c1aa564577b7fe"},"holstr:prev":"did:holo:sha256:b7dab505a509d7161ce816c17977746895289907a53d13027e52ab7e8fc4a59c","holstr:seq":386,"prov:generatedAtTime":"2026-07-15T20:47:45.703Z"}
|
sha256/8e73919c7f3276c213d41bdd8408c33c034d87ecac05b36a143fc0a6c75659d2
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|