Humuhumu33 commited on
Commit
c7bc4b4
·
verified ·
1 Parent(s): d1c54c0

q-provenance: Q b/ CAS (240 sha256-addressed objects) - verified sha256 mirror rung capacity (HOLO-LEAN-RESOLVER U6)

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. .gitattributes +1 -0
  2. sha256/00566dbbfe60f4291416c9823f7bae58f700f5f352f3aedfdaed680663954f0f +0 -0
  3. sha256/007141010ac01ca81bb4bf74ad758709586b2899c25d7b8f8cf412abf1998c62 +1 -0
  4. sha256/00c677e5492be7ba044ac290c26a837d9c381e187c6302d2ccc8c2645fdf6f65 +0 -0
  5. sha256/014e5418ebb9a676db95c32570b0a74b55b6c90bdf555804a994d4921748fc07 +1 -0
  6. sha256/0511772584510f0888d97dd5a72bf986ad6791bd8e305063ddd8a82c39888a70 +474 -0
  7. sha256/0553be28941b9513e5a55c5486a017433436df3a6aa479291a110c4b9732f2e1 +0 -0
  8. sha256/089f68f15bc55cd293d235f4ce0326f6d138455b9414ce66748677c1ae6c6f5e +16 -0
  9. sha256/08d961eae11dbc7299fcc899954dddc1b14c66678abcc3cdfca2c752a13d3157 +0 -0
  10. sha256/09f17a1ad37e8f6648e242a1fc77c37fb2bd012af572bc6cd49767f32d6b0b0f +1 -0
  11. sha256/0b308063d2fb1ce8aa570c2482e7d37bda7ac0467eb033f53d1ed264dfc0efb8 +0 -0
  12. sha256/0b4824f805c77c02dcd3316c174b393b97ad921be3db36cf258879fd0d38e524 +16 -0
  13. sha256/0c0cb5ec7968fa1285fb2c1333d312f8bf9c46e910a693e5dca48433cd639109 +1 -0
  14. sha256/0ca30cc0318bb9471049187dbe2f83f3230532486276e87b647d222400a07d7f +1 -0
  15. sha256/0d45643cb4c06dd0344db52b9d64249da78b51f8430456adc721058971fe4114 +1 -0
  16. sha256/0f60cc84c21bf2e134257400f0d48b58a2d7adddf9a8e8710054d6de099f6251 +1 -0
  17. sha256/10367ce2b521074714ed6f241b5a7a3cb2230c144a8647849ebbf5e166a25f0e +0 -0
  18. sha256/11247d28c5c2fb3d976002c2b55fecb6ba19fd88153e11d2c7af5832fd6ff331 +1 -0
  19. sha256/13b56d2b9cd29994363ae363831f424ba2ebfcb4d05dac8844e3be8af9c1b32b +0 -0
  20. sha256/140e48d5fd95d8926c39793cb917d0184d6d6440a854c5561463c68100f065e5 +0 -0
  21. sha256/154d26ffee4857720bdae0e11485b74a95782c4011d566777f72f8a70a141f8f +0 -0
  22. sha256/15661c9995268912abc3a99e300819502e13e9d9cbf7d1de0cabc8332fa7ac75 +0 -0
  23. sha256/15c5bccd5549900f8bb67bbeee9f2a8e0020849a489abb3d167922c673f31df1 +47 -0
  24. sha256/15d29add2eab75dc8c291e042fdac738a6fc8bcbaafcfb8c89c52d9d7535eab8 +0 -0
  25. sha256/178f65b88853309e38e4accc57ed017857aab257ac62215a7a5845bd89a3c68e +0 -0
  26. sha256/1905bfea3c1e2b47fcca71fc9290e3b5562d40ad5a703d9b4a20bdedfb78419d +0 -0
  27. sha256/196b965ba8823e5256519a0cf5b1e1786f360c03a18bd26f247f20631759fb81 +0 -0
  28. sha256/1c67d2f5605c4d51f99ea8c7b25796d90129c3d6e775baaeb8d6d2374b6cedbd +0 -0
  29. sha256/1db5b69648abb9551faad7400a556c3102f3cb9cc71751c809da614589ab2092 +1 -0
  30. sha256/1e167904347bf667638a11ee8eeaf9e795494c01266b9d3ea7f87335ffe1a055 +0 -0
  31. sha256/1e175094425429ed2ccb267298246a608e9e72b9641829e76d83238855456992 +488 -0
  32. sha256/1e5edeebc42921c19d60f9a4875040b8eee274d25262e27e02843bc1be6c2ad2 +532 -0
  33. sha256/1e6016292b58e5397de70e1df675451578014820ef442901cee58eda4e91bb94 +1 -0
  34. sha256/20f24e1a21bc5e8e3c161f82afcb6e9beae002c92015be8661dd3bbed6c39ac8 +1 -0
  35. sha256/2130e7ee136037d25388f86d22552b51222907c242b4f54ef10b55c30d4d459e +0 -0
  36. sha256/232d735a20f3a27c22126ea42cbfc7768b9c2db16c52b110980e82d37e710c0b +141 -0
  37. sha256/23ad078d7c8a8e5c306772fbd0f70c81df62c2d37532b7a93c17a3f67c0428f3 +1 -0
  38. sha256/23aec66382775f5773693615338f37c37a5f58257ec95aece33a664baa6fe0db +15 -0
  39. sha256/23e6f02a486ed64490ff8a2c59e4c641b40316a9c4542ae8c82d9a2c1540668e +0 -0
  40. sha256/24ae47813dc749b66bba0a21804680028e2d8947e81a0e49b81ddf00ea1d0778 +1 -0
  41. sha256/2612bd713c62cfb24c2d49e12596055cca3d77911b63bc5470fabbfc469675e5 +0 -0
  42. sha256/287ce2d887ba0cea9ce037085d629fa5c662abde23f757484cee8b1309c30c7e +1 -0
  43. sha256/2905a32f569e289dbb9fb3bdb16bd57f011a12fa920f5396d3ec6d768f421c82 +1 -0
  44. sha256/29f6cbec51d5d103e676c312ab27151e2aa98e38f05532b3ea4adec6df0b36bb +1 -0
  45. sha256/2aa7375ed161dd5b85b93ee612036d9f8f163af65fe6558b8dce8b0984467ea8 +1 -0
  46. sha256/2dab033a00555cbc3b8b877f23625ff7a39ba88588fa7640b80be9b6dcb9b150 +0 -0
  47. sha256/2dd0c36b154a559a9f45264c53decbaca48aaba7315a7b4c5fcbe498c4cf4893 +1 -0
  48. sha256/2e2dc1bf446e5ca1ec4bffa421947dc22143efd64c054e6cb0cc6a8f91fc23fe +1 -0
  49. sha256/30774d033489de9e35a6d645425eb9486c72cf13d8703346eb3065d4d234e641 +0 -0
  50. sha256/310c5b0dfd9e59f3fce276da94235913365a0e8570c2ce5d9290213ca0f3067d +378 -0
.gitattributes CHANGED
@@ -106,3 +106,4 @@ b/6f89df7ac143f7be87409587aa86fdf9640c28f743871f15954544dda360c16c filter=lfs di
106
  b/765a9f4b7f4975d323b6d2b03fc85c6240e533f89188b5c9c3712c774efd3a29 filter=lfs diff=lfs merge=lfs -text
107
  b/8c43519c6f672c286fcde80cb884165030ce3a62171181416642e24ca4462ab6 filter=lfs diff=lfs merge=lfs -text
108
  b/** filter=lfs diff=lfs merge=lfs -text
 
 
106
  b/765a9f4b7f4975d323b6d2b03fc85c6240e533f89188b5c9c3712c774efd3a29 filter=lfs diff=lfs merge=lfs -text
107
  b/8c43519c6f672c286fcde80cb884165030ce3a62171181416642e24ca4462ab6 filter=lfs diff=lfs merge=lfs -text
108
  b/** filter=lfs diff=lfs merge=lfs -text
109
+ sha256/f6caa9eeead2c99deeb2cdb70b7e25bbaa2cf5c60555f5b4030ecf6b6d9efa51 filter=lfs diff=lfs merge=lfs -text
sha256/00566dbbfe60f4291416c9823f7bae58f700f5f352f3aedfdaed680663954f0f ADDED
The diff for this file is too large to render. See raw diff
 
sha256/007141010ac01ca81bb4bf74ad758709586b2899c25d7b8f8cf412abf1998c62 ADDED
@@ -0,0 +1 @@
 
 
1
+ import"./chunk-SQE76S5B.js";var I=1,k={standard:{name:"Standard",tag:"matte black",locked:!1},volt:{name:"Volt",tag:"electric streaks",locked:!1},fiber:{name:"Fiber",tag:"woven copper",locked:!1},sovereign:{name:"Sovereign",tag:"TEE holders only",locked:!0}},S=["v","kind","chain","address","label","edition","holder","created"];function g(e){return JSON.stringify(S.map(o=>e[o]??null))}async function b(e){let o=await crypto.subtle.digest("SHA-256",new TextEncoder().encode(e));return[...new Uint8Array(o)].map(t=>t.toString(16).padStart(2,"0")).join("")}async function w({chain:e="ethereum",address:o="",label:t="Card",edition:a="standard",holder:l=""}={}){let i={v:1,kind:"card",chain:e,address:o,label:t,edition:a,holder:l,created:Date.now(),frozen:!1};return i.kappa=await b(g(i)),i}async function R(e){return e&&e.kappa===await b(g(e))}function y(e){let o=parseInt(e.slice(0,8),16)>>>0,t=parseInt(e.slice(8,16),16)>>>0,a=parseInt(e.slice(16,24),16)>>>0,l=parseInt(e.slice(24,32),16)>>>0;return()=>{let i=(o+t>>>0)+l>>>0;return l=l+1>>>0,o=t^t>>>9,t=a+(a<<3)>>>0,a=(a<<21|a>>>11)>>>0,a=a+i>>>0,(i>>>0)/4294967296}}var s=856,n=540;function p(e,o,t){let a=e.createLinearGradient(0,0,s*.35,n);a.addColorStop(0,o),a.addColorStop(1,t),e.fillStyle=a,e.fillRect(0,0,s,n)}function d(e,o,t,a){e.save();for(let l=0;l<t;l++)e.fillStyle=`rgba(255,255,255,${o()*a})`,e.fillRect(o()*s,o()*n,1,1);e.restore()}function h(e,o){p(e,"#101114","#050506");let t=s*(.2+o()*.6),a=e.createLinearGradient(t-180,0,t+180,n);a.addColorStop(0,"rgba(255,255,255,0)"),a.addColorStop(.5,"rgba(255,255,255,0.045)"),a.addColorStop(1,"rgba(255,255,255,0)"),e.fillStyle=a,e.fillRect(0,0,s,n),d(e,o,900,.05)}function m(e,o){p(e,"#04070f","#010208"),e.save(),e.globalCompositeOperation="lighter";let t=205+o()*40;for(let a=0;a<42;a++){let l=n*o(),i=n*o(),f=s*o(),r=s*o(),u=t+(o()-.5)*50;e.strokeStyle=`hsla(${u}, 100%, ${55+o()*25}%, ${.03+o()*.1})`,e.lineWidth=.5+o()*3.2,e.beginPath(),e.moveTo(-40,l),e.bezierCurveTo(f,l+(o()-.5)*260,r,i+(o()-.5)*260,s+40,i),e.stroke()}e.restore(),d(e,o,500,.04)}function C(e,o){p(e,"#12080a","#070304"),e.save();let t=s*(.3+o()*.4);for(let a=-4;a<n+4;a+=2){let l=Math.exp(-Math.abs(a-n*.45)/(n*.5)),f=8+o()*30+l*22;e.strokeStyle=`hsla(${14+o()*26}, ${55+o()*35}%, ${f}%, ${.5+o()*.5})`,e.lineWidth=1+o()*1.6,e.beginPath();let r=(o()-.5)*22;e.moveTo(0,a),e.bezierCurveTo(t*.5,a+r,t,a-r,s,a+(o()-.5)*14),e.stroke()}e.globalCompositeOperation="lighter";for(let a=0;a<60;a++){let l=n*o();e.strokeStyle=`hsla(${20+o()*25}, 100%, ${60+o()*25}%, ${.1+o()*.22})`,e.lineWidth=.6+o(),e.beginPath();let i=s*o()*.7;e.moveTo(i,l),e.lineTo(i+s*(.15+o()*.5),l+(o()-.5)*10),e.stroke()}e.restore()}function v(e,o){p(e,"#0a0a12","#040408"),e.save(),e.globalCompositeOperation="lighter";let t=o()*Math.PI*2;for(let a=0;a<26;a++){let l=a/26,i=(t*57+l*320+o()*30)%360,f=n*(l+(o()-.5)*.06),r=e.createLinearGradient(0,f-60,s,f+60);r.addColorStop(0,`hsla(${i},90%,60%,0)`),r.addColorStop(.5,`hsla(${i},90%,60%,${.05+o()*.06})`),r.addColorStop(1,`hsla(${(i+60)%360},90%,60%,0)`),e.fillStyle=r,e.beginPath(),e.ellipse(s/2,f,s*(.55+o()*.25),46+o()*40,(o()-.5)*.22,0,Math.PI*2),e.fill()}e.restore(),d(e,o,700,.05)}var T={standard:h,volt:m,fiber:C,sovereign:v};function $(e,o){e.save(),e.fillStyle="rgba(255,255,255,0.94)",e.font="600 44px ui-sans-serif, system-ui, 'Segoe UI', Roboto, sans-serif",e.textBaseline="top",e.fillText("Holo",52,46),e.font="300 44px ui-sans-serif, system-ui, 'Segoe UI', Roboto, sans-serif",e.fillText("Money",158,46),e.font="700 22px ui-sans-serif, system-ui, sans-serif",e.fillStyle="rgba(255,255,255,0.62)";let a="VIRTUAL";e.fillText(a,s-52-e.measureText(a).width,56),e.textBaseline="alphabetic",e.fillStyle="rgba(255,255,255,0.88)",e.font="500 30px ui-sans-serif, system-ui, sans-serif",e.fillText(o.holder||"Operator",52,n-52-34),e.fillStyle="rgba(255,255,255,0.55)",e.font="400 24px ui-monospace, Consolas, monospace";let l=(o.address||o.kappa||"").slice(-4);e.fillText(`${o.label} \xB7\xB7${l}`,52,n-52);let i=n-52-26,f=s-52-34,r=30;e.globalAlpha=.9,e.fillStyle="rgba(255,255,255,0.22)",e.beginPath(),e.arc(f-20,i,r,0,Math.PI*2),e.fill(),e.fillStyle="rgba(255,255,255,0.34)",e.beginPath(),e.arc(f+20,i,r,0,Math.PI*2),e.fill(),e.globalAlpha=1,e.fillStyle="rgba(10,10,14,0.85)",e.font="800 30px ui-sans-serif, system-ui, sans-serif",e.fillText("H",f-11,i+11),e.restore()}function A(e,o){e.width=s,e.height=n;let t=e.getContext("2d"),a=y(o.kappa||"deadbeefdeadbeefdeadbeefdeadbeef");(T[o.edition]||h)(t,a);let l=t.createRadialGradient(s/2,n/2,n*.4,s/2,n/2,s*.75);return l.addColorStop(0,"rgba(0,0,0,0)"),l.addColorStop(1,"rgba(0,0,0,0.38)"),t.fillStyle=l,t.fillRect(0,0,s,n),$(t,o),e}export{I as CARD_VERSION,k as EDITIONS,n as FACE_H,s as FACE_W,w as mintCard,A as renderFace,b as sha256hex,R as verifyCard};
sha256/00c677e5492be7ba044ac290c26a837d9c381e187c6302d2ccc8c2645fdf6f65 ADDED
The diff for this file is too large to render. See raw diff
 
sha256/014e5418ebb9a676db95c32570b0a74b55b6c90bdf555804a994d4921748fc07 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":{"games":"5de2be050dc5a4f9fcb464ffd45bbfcdca368ac02c39e5807ef6ce9430c45edd","holo-games":"791a9ac999486256499d76a938345b173053b8de643cc89797bf946c38c365e3","holo-messenger":"3fed097288cfa44d5843d179913e64231b4e6b4fef67b7f58ac02e44b65f4bb9","resolve":"23aec66382775f5773693615338f37c37a5f58257ec95aece33a664baa6fe0db","self":"0511772584510f0888d97dd5a72bf986ad6791bd8e305063ddd8a82c39888a70","spaces":"d8aebefb67e7fc7802e757b9a7d95bb881e6d75b4a9bc4bd4227634541757e51"},"closure":"5f86e3b72af4e65ab68489fb52f67ab0ce0b99ab046ad9d13c52b68edd80256a","manifest":"fc3ca0298cfcc6efbe688a97b801722f50de1b2401c3867b5aeef8b746756b77","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":"f805a57ea761491ca780163f73fe2e61acaab9f2c50881ca93a5f80e3a29d1e0"},"holstr:prev":"did:holo:sha256:3222d8ef91ba64176f05046942c2f6f5d764ac5ea8ad74f129a3dd22bc5326db","holstr:seq":123,"prov:generatedAtTime":"2026-07-12T10:17:54.084Z"}
sha256/0511772584510f0888d97dd5a72bf986ad6791bd8e305063ddd8a82c39888a70 ADDED
@@ -0,0 +1,474 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <meta charset="utf-8">
4
+ <title>SELF · on Hologram</title>
5
+ <meta name="viewport" content="width=device-width, initial-scale=1">
6
+ <!--
7
+ A fully self-contained, SERVERLESS identity-wallet demo. It mirrors the SELF® product
8
+ (selfid.com) — non-custodial wallet, selective disclosure, passwordless Connect, QR verify —
9
+ but runs entirely on the κ-addressable substrate: your DID is the hash of your key, and every
10
+ proof verifies by re-derivation with NO registry, NO blockchain, NO server. Pull the network
11
+ plug (✈) and everything still works. All three modules below are the REAL Hologram primitives.
12
+ -->
13
+ <style>
14
+ :root{
15
+ --space0:#06070b; --space1:#0e1018; --card:#13151d; --card2:#171a23; --line:#242838;
16
+ --ink:#eaecf4; --dim:#888ea4; --faint:#5a6075;
17
+ --blue:#7c9cff; --violet:#a07cff; --good:#34d399; --bad:#f87171;
18
+ --glow:0 0 40px -6px var(--blue);
19
+ }
20
+ *{box-sizing:border-box;-webkit-tap-highlight-color:transparent}
21
+ html,body{margin:0}
22
+ body{background:
23
+ radial-gradient(120% 70% at 80% -5%, #1a1f3a 0%, transparent 55%),
24
+ radial-gradient(90% 60% at 10% 0%, #241a3a 0%, transparent 50%),
25
+ var(--space0);
26
+ color:var(--ink); font:15px/1.55 -apple-system,BlinkMacSystemFont,"Segoe UI",system-ui,sans-serif;
27
+ min-height:100vh; overflow-x:hidden}
28
+ .geo{position:fixed;inset:0;z-index:0;pointer-events:none;overflow:hidden}
29
+ .geo i{position:absolute;display:block;border:1.5px solid;border-radius: var(--holo-radius-sm, 8px);opacity:.5;
30
+ filter:blur(.3px);animation:float linear infinite}
31
+ @keyframes float{0%{transform:translateY(0) rotate(0)}50%{transform:translateY(-26px) rotate(180deg)}100%{transform:translateY(0) rotate(360deg)}}
32
+ .wrap{position:relative;z-index:1;max-width:1080px;margin:0 auto;padding:0 22px 90px}
33
+
34
+ /* nav */
35
+ nav{display:flex;align-items:center;justify-content:space-between;padding:22px 2px 8px}
36
+ .brand{display:flex;align-items:center;gap:10px;font-weight:700;letter-spacing:-.01em}
37
+ .brand .dot{width:22px;height:22px;border-radius:7px;background:linear-gradient(145deg,var(--blue),var(--violet));box-shadow:var(--glow)}
38
+ .brand small{color:var(--faint);font-weight:500}
39
+ .air{display:flex;align-items:center;gap:10px;font-size:13px;color:var(--dim)}
40
+ .toggle{position:relative;width:46px;height:26px;border-radius:20px;background:#222636;border:1px solid var(--line);cursor:pointer;transition:.2s}
41
+ .toggle::after{content:"";position:absolute;top:2px;left:2px;width:20px;height:20px;border-radius:50%;background:#aeb4cc;transition:.2s}
42
+ .toggle.on{background:linear-gradient(90deg,var(--blue),var(--violet))}
43
+ .toggle.on::after{left:22px;background:#fff}
44
+ .netcount{font-variant-numeric:tabular-nums;color:var(--good)}
45
+
46
+ /* hero */
47
+ .hero{padding:54px 0 30px;text-align:center}
48
+ .hero h1{font-size:clamp(34px,6vw,60px);font-weight:740;letter-spacing:-.03em;margin:0 0 14px;line-height:1.02}
49
+ .hero h1 span{background:linear-gradient(120deg,var(--blue),var(--violet));-webkit-background-clip:text;background-clip:text;color:transparent}
50
+ .hero p{color:var(--dim);font-size:18px;max-width:560px;margin:0 auto 26px}
51
+ .cta{font:inherit;font-weight:650;border:0;border-radius:14px;padding:14px 26px;color:#fff;cursor:pointer;
52
+ background:linear-gradient(180deg,#6f8bff,#5566e6);box-shadow:0 10px 30px -10px #5566e6;transition:.15s}
53
+ .cta:hover{filter:brightness(1.07)} .cta:active{transform:translateY(1px)}
54
+ .did-live{margin-top:22px;font:12.5px/1.4 ui-monospace,Menlo,monospace;color:var(--faint)}
55
+ .did-live b{color:var(--blue)}
56
+
57
+ /* stage: phone + relying party */
58
+ .stage{display:grid;grid-template-columns:300px 1fr;gap:30px;align-items:start;margin-top:18px}
59
+ @media(max-width:740px){.stage{grid-template-columns:1fr;gap:22px}}
60
+
61
+ /* iPhone */
62
+ .phone{width:300px;height:600px;border-radius:46px;background:linear-gradient(160deg,#1c2030,#0c0e16);
63
+ border:1px solid #2b3047;padding:12px;box-shadow:0 40px 90px -40px #000,inset 0 1px 0 rgba(255,255,255,.05);position:relative;justify-self:center}
64
+ .notch{position:absolute;top:16px;left:50%;transform:translateX(-50%);width:120px;height:26px;background:#05060a;border-radius:0 0 16px 16px;z-index:5}
65
+ .screen{position:absolute;inset:12px;border-radius:36px;background:radial-gradient(100% 60% at 50% 0,#161a28,#0b0d14);overflow:hidden;display:flex;flex-direction:column}
66
+ .scr-top{padding:38px 18px 12px;display:flex;align-items:center;gap:11px;border-bottom:1px solid var(--line)}
67
+ .ava{width:38px;height:38px;border-radius: var(--holo-radius, 12px);display:grid;place-items:center;font-weight:700;color:#0a0a12;flex:none}
68
+ .scr-top .nm{font-weight:640;font-size:14.5px;line-height:1.1}
69
+ .scr-top .did{font:10.5px ui-monospace,Menlo,monospace;color:var(--faint)}
70
+ .modeswitch{margin-left:auto;display:flex;background:#0c0e16;border:1px solid var(--line);border-radius:9px;padding:2px}
71
+ .modeswitch button{background:transparent;border:0;color:var(--dim);font-size:14px;padding:4px 7px;border-radius:7px;cursor:pointer}
72
+ .modeswitch button.on{background:#262c46;color:#fff}
73
+ .scr-list{flex:1;overflow:auto;padding:14px}
74
+ .scr-list h4{margin:2px 4px 10px;font-size:11px;letter-spacing:.09em;text-transform:uppercase;color:var(--faint);display:flex;justify-content:space-between;align-items:center}
75
+ .addc{cursor:pointer;color:var(--blue);font-size:16px;line-height:1}
76
+ .cardc{background:linear-gradient(155deg,var(--card2),var(--card));border:1px solid var(--line);border-radius: var(--holo-radius-lg, 16px);padding:13px;margin-bottom:11px;cursor:pointer;transition:.15s;position:relative;overflow:hidden}
77
+ .cardc:hover{border-color:#33405f;transform:translateY(-1px)}
78
+ .cardc .ci{display:flex;align-items:center;gap:10px}
79
+ .cardc .ico{font-size:20px}
80
+ .cardc .ct{font-weight:620;font-size:13.5px}
81
+ .cardc .cs{color:var(--dim);font-size:11.5px}
82
+ .cardc .iss{color:var(--faint);font-size:10.5px;margin-top:7px;display:flex;align-items:center;gap:5px}
83
+ .vdot{width:7px;height:7px;border-radius:50%;background:var(--good);box-shadow:0 0 8px var(--good)}
84
+ .kre{margin-top:9px;font:10px/1.4 ui-monospace,Menlo,monospace;color:var(--good);display:none;word-break:break-all}
85
+ .cardc.open .kre{display:block}
86
+
87
+ /* approve sheet */
88
+ .sheet{position:absolute;left:0;right:0;bottom:0;background:#10131d;border-top:1px solid #2c3350;
89
+ border-radius:24px 24px 36px 36px;padding:18px 18px 24px;transform:translateY(110%);transition:.32s cubic-bezier(.2,.85,.25,1);z-index:8;box-shadow:0 -20px 50px -20px #000}
90
+ .sheet.up{transform:translateY(0)}
91
+ .sheet .rq{display:flex;align-items:center;gap:10px;margin-bottom:4px}
92
+ .sheet .rq .ico{width:34px;height:34px;border-radius:10px;background:#221b14;display:grid;place-items:center;font-size:18px}
93
+ .sheet .rq b{font-size:14.5px}.sheet .rq small{display:block;color:var(--dim);font-size:11.5px}
94
+ .fields{margin:14px 0 10px;border:1px solid var(--line);border-radius:13px;overflow:hidden}
95
+ .field{display:flex;align-items:center;justify-content:space-between;padding:9px 12px;font-size:12.5px;border-bottom:1px solid var(--line)}
96
+ .field:last-child{border-bottom:0}
97
+ .field.share{background:rgba(52,211,153,.07)}
98
+ .field .k{color:var(--dim)} .field.share .k{color:var(--ink);font-weight:600}
99
+ .field .v{font:11.5px ui-monospace,Menlo,monospace;color:var(--ink)}
100
+ .field .lock{color:var(--faint)}
101
+ .mini{font-size:11px;color:var(--faint);text-align:center;margin:2px 0 12px}
102
+ .mini b{color:var(--good)}
103
+ .sheet-act{display:flex;gap:9px}
104
+ .sheet-act button{flex:1;font:inherit;font-weight:640;border:0;border-radius: var(--holo-radius, 12px);padding:12px;cursor:pointer}
105
+ .approve{background:linear-gradient(180deg,#6f8bff,#5566e6);color:#fff}
106
+ .cancel{background:#1b1f2c;color:var(--dim)}
107
+ .agentchip{display:inline-flex;align-items:center;gap:6px;font-size:11px;color:var(--violet);background:rgba(160,124,255,.1);border:1px solid rgba(160,124,255,.3);border-radius:20px;padding:3px 10px;margin:0 auto 12px}
108
+
109
+ /* relying-party column */
110
+ .rp{display:flex;flex-direction:column;gap:18px}
111
+ .panel{background:var(--card);border:1px solid var(--line);border-radius:20px;padding:22px}
112
+ .panel h3{margin:0 0 3px;font-size:17px;letter-spacing:-.01em}
113
+ .panel .host{color:var(--faint);font:12px ui-monospace,Menlo,monospace;margin-bottom:14px}
114
+ .panel .role{float:right;font-size:10.5px;color:var(--violet);border:1px solid rgba(160,124,255,.3);border-radius:20px;padding:2px 9px}
115
+ .req-row{display:flex;gap:9px;flex-wrap:wrap;margin-bottom:14px}
116
+ .req{background:#1a1e2b;border:1px solid var(--line);border-radius:11px;padding:9px 12px;cursor:pointer;font-size:12.5px;color:var(--dim);transition:.15s}
117
+ .req:hover{border-color:#33405f;color:var(--ink)}
118
+ .req.on{border-color:var(--blue);color:#fff;background:#1b2236}
119
+ .connectbtn{display:flex;align-items:center;gap:9px;width:100%;justify-content:center;font:inherit;font-weight:650;
120
+ border:0;border-radius:13px;padding:13px;color:#fff;cursor:pointer;background:linear-gradient(180deg,#6f8bff,#5566e6);transition:.15s}
121
+ .connectbtn:hover{filter:brightness(1.07)}
122
+ .qrwrap{display:flex;gap:16px;align-items:center;margin-top:16px;padding-top:16px;border-top:1px solid var(--line)}
123
+ .qr{width:104px;height:104px;border-radius: var(--holo-radius, 12px);background:#fff;padding:8px;flex:none}
124
+ .qr svg{display:block;width:100%;height:100%}
125
+ .qrwrap .qt{font-size:12.5px;color:var(--dim)} .qrwrap .qt b{color:var(--ink)}
126
+ .scanbtn{margin-top:8px;background:#1b1f2c;border:1px solid var(--line);color:var(--ink);border-radius:10px;padding:7px 12px;font:inherit;font-size:12.5px;cursor:pointer}
127
+
128
+ .result{display:none;margin-top:4px}.result.show{display:block;animation:rise .3s}
129
+ @keyframes rise{from{opacity:0;transform:translateY(6px)}}
130
+ .verdict{border-radius:14px;padding:15px 16px}
131
+ .verdict.ok{background:rgba(52,211,153,.08);border:1px solid rgba(52,211,153,.34)}
132
+ .verdict.no{background:rgba(248,113,113,.08);border:1px solid rgba(248,113,113,.34)}
133
+ .verdict .big{display:flex;align-items:center;gap:10px;font-weight:680;font-size:15.5px}
134
+ .tick{width:24px;height:24px;border-radius:50%;display:grid;place-items:center;font-size:14px;flex:none}
135
+ .ok .tick{background:var(--good);color:#06281c}.no .tick{background:var(--bad);color:#3a0c0c}
136
+ .verdict ul{margin:11px 0 0;padding:0;list-style:none;color:var(--dim);font-size:12.5px}
137
+ .verdict li{padding:2.5px 0}.verdict li b{color:var(--ink)}
138
+ .wirelink{margin-top:10px;font-size:12px;color:var(--blue);cursor:pointer;display:inline-block}
139
+ pre.wire{display:none;margin:8px 0 0;background:#0a0c12;border:1px solid var(--line);border-radius:10px;padding:11px;
140
+ font:11px/1.5 ui-monospace,Menlo,monospace;color:#aab2cc;max-height:180px;overflow:auto;white-space:pre-wrap;word-break:break-all}
141
+ pre.wire.show{display:block}
142
+ .absent{color:var(--good)}
143
+
144
+ /* lower sections */
145
+ .grid2{display:grid;grid-template-columns:1fr 1fr;gap:18px;margin-top:30px}
146
+ @media(max-width:740px){.grid2{grid-template-columns:1fr}}
147
+ .why h3,.break h3{margin:0 0 12px;font-size:16px}
148
+ .cmp{display:grid;grid-template-columns:1fr 1fr;gap:1px;background:var(--line);border:1px solid var(--line);border-radius:14px;overflow:hidden}
149
+ .cmp>div{background:var(--card);padding:14px}
150
+ .cmp .h{font-size:11px;text-transform:uppercase;letter-spacing:.08em;color:var(--faint);margin-bottom:8px}
151
+ .cmp ul{margin:0;padding-left:16px;font-size:12.5px;color:var(--dim)}.cmp li{margin:5px 0}
152
+ .cmp .ours li{color:var(--ink)}
153
+ .break .row{display:flex;gap:9px;flex-wrap:wrap}
154
+ .break button{background:#1b1f2c;border:1px solid var(--line);color:var(--ink);border-radius:11px;padding:10px 13px;cursor:pointer;font:inherit;font-size:13px}
155
+ .break button:hover{border-color:#3a4straw;border-color:#33405f}
156
+ .attack{margin-top:13px;display:none}.attack.show{display:block;animation:rise .3s}
157
+ .foot{text-align:center;color:var(--faint);font-size:12.5px;margin-top:34px}
158
+
159
+ /* face id overlay */
160
+ .scan{position:fixed;inset:0;background:rgba(6,7,11,.74);backdrop-filter:blur(7px);display:grid;place-items:center;opacity:0;transition:.22s;z-index:60}
161
+ .scan.show{opacity:1}
162
+ .scan .face{width:96px;height:96px;margin:0 auto 16px;position:relative;display:grid;place-items:center}
163
+ .scan .ring{position:absolute;inset:0;border-radius:26px;border:3px solid var(--blue);box-shadow:0 0 34px -4px var(--blue);animation:breathe 1.1s ease-in-out infinite}
164
+ .scan .glyph{font-size:46px;filter:grayscale(1) brightness(1.5)}
165
+ .scan .face.done .ring{border-color:var(--good);box-shadow:0 0 36px -2px var(--good);animation:none}
166
+ .scan p{color:var(--ink);text-align:center}.scan p b{color:var(--blue)}
167
+ @keyframes breathe{0%,100%{transform:scale(.9);opacity:.6}50%{transform:scale(1);opacity:1}}
168
+ .airbanner{position:fixed;left:50%;bottom:20px;transform:translateX(-50%) translateY(140%);z-index:40;
169
+ background:#10131d;border:1px solid var(--line);border-radius:30px;padding:9px 18px;font-size:13px;color:var(--ink);
170
+ box-shadow:0 18px 40px -20px #000;transition:.3s;display:flex;align-items:center;gap:9px}
171
+ .airbanner.show{transform:translateX(-50%) translateY(0)}
172
+ </style>
173
+
174
+ <div class="geo" id="geo"></div>
175
+ <div class="wrap">
176
+ <nav>
177
+ <div class="brand"><span class="dot"></span>SELF <small>· on Hologram</small></div>
178
+ <div class="air">
179
+ <span>✈ Airplane mode</span>
180
+ <div class="toggle" id="air"></div>
181
+ <span class="netcount" id="net">0 network calls</span>
182
+ </div>
183
+ </nav>
184
+
185
+ <section class="hero">
186
+ <h1>It's your data,<br><span>your rules.</span></h1>
187
+ <p>A self-sovereign identity wallet with no registry, no blockchain, no server. Prove only what's needed — verified by re-derivation.</p>
188
+ <button class="cta" id="open">Open your wallet</button>
189
+ <div class="did-live">your DID, minted on this device a moment ago:<br><b id="did">did:holo:…</b></div>
190
+ </section>
191
+
192
+ <section class="stage" id="stage">
193
+ <!-- iPhone -->
194
+ <div class="phone">
195
+ <div class="notch"></div>
196
+ <div class="screen">
197
+ <div class="scr-top">
198
+ <div class="ava" id="ava">A</div>
199
+ <div><div class="nm" id="nm">Alice</div><div class="did" id="pdid">did:holo:…</div></div>
200
+ <div class="modeswitch" id="mode">
201
+ <button data-mode="human" class="on" title="You (Face ID)">🧑</button>
202
+ <button data-mode="agent" title="Your AI agent (delegated)">🤖</button>
203
+ </div>
204
+ </div>
205
+ <div class="scr-list">
206
+ <h4>Your credentials <span class="addc" id="add" title="Issue one to yourself (Attestor)">+</span></h4>
207
+ <div id="cards"></div>
208
+ </div>
209
+ <div class="sheet" id="sheet"></div>
210
+ </div>
211
+ </div>
212
+
213
+ <!-- relying party -->
214
+ <div class="rp">
215
+ <div class="panel">
216
+ <span class="role">Verifier</span>
217
+ <h3>thebar.example</h3>
218
+ <div class="host">a relying party · wants to check one thing</div>
219
+ <div class="req-row" id="reqs"></div>
220
+ <button class="connectbtn" id="connect"><span>🔓</span> Connect with SELF</button>
221
+ <div class="qrwrap">
222
+ <div class="qr" id="qr"></div>
223
+ <div>
224
+ <div class="qt"><b>Scan to verify</b><br>the code carries a signed challenge κ</div>
225
+ <button class="scanbtn" id="scan">▣ Simulate scan</button>
226
+ </div>
227
+ </div>
228
+ </div>
229
+ <div class="result" id="result"></div>
230
+ </div>
231
+ </section>
232
+
233
+ <section class="grid2">
234
+ <div class="panel why">
235
+ <h3>Why this is different</h3>
236
+ <div class="cmp">
237
+ <div><div class="h">SELF® today</div><ul>
238
+ <li>MDIP · blockchain-agnostic registry</li>
239
+ <li>DID anchored to a ledger</li>
240
+ <li>App server mediates connect</li>
241
+ <li>Trust = look it up</li></ul></div>
242
+ <div class="ours"><div class="h">On Hologram</div><ul>
243
+ <li>No registry — the DID <i>is</i> sha256(key)</li>
244
+ <li>No chain — the bytes are the ledger</li>
245
+ <li>No server — the proof is the login</li>
246
+ <li>Trust = re-derive it (Law L5)</li></ul></div>
247
+ </div>
248
+ <div class="mini" style="margin-top:12px">Flip ✈ Airplane mode and run any flow — it still verifies.</div>
249
+ </div>
250
+ <div class="panel break">
251
+ <h3>Try to break it</h3>
252
+ <div class="row">
253
+ <button id="replay">Replay an old proof</button>
254
+ <button id="tamper">Forge the answer</button>
255
+ <button id="overshare">Reveal more than asked</button>
256
+ </div>
257
+ <div class="attack" id="attack"></div>
258
+ </div>
259
+ </section>
260
+
261
+ <div class="foot">Self-contained · serverless · offline-first. The same code path serves a human and an AI agent — only the consent gate differs.</div>
262
+ </div>
263
+
264
+ <div class="airbanner" id="airbanner">🔌 Offline — and still verifying. Network calls this session: <b id="net2">0</b></div>
265
+
266
+ <script type="module">
267
+ import { ephemeral } from "./holo-identity.mjs";
268
+ import { issueCredential, verifyCredential } from "./holo-credential.mjs";
269
+ import { makeChallenge, present, verifyPresentation } from "./holo-present.mjs";
270
+
271
+ const $ = (s)=>document.querySelector(s);
272
+ const short = (k)=>k.replace("did:holo:sha256:","").slice(0,12)+"…";
273
+ const hue = (k)=>{const h=k.split(":").pop();return parseInt(h.slice(0,4),16)%360;};
274
+
275
+ // honest network meter: wrap fetch; the app never calls it, so it stays 0 in both modes.
276
+ let netCalls=0; const _f=window.fetch; window.fetch=function(){netCalls++;paintNet();return _f.apply(this,arguments);};
277
+ const paintNet=()=>{ $("#net").textContent=netCalls+" network calls"; $("#net2").textContent=netCalls; };
278
+
279
+ // floating geometry
280
+ const G=$("#geo"); const shapes=[[60,12,14,'--blue'],[40,78,9,'--violet'],[80,40,20,'--blue'],[28,30,7,'--violet'],[54,88,11,'--blue'],[34,60,16,'--violet']];
281
+ for(const [x,y,d,c] of shapes){const i=document.createElement('i');i.style.cssText=`left:${x}%;top:${y}%;width:${d*4}px;height:${d*4}px;border-color:var(${c});animation-duration:${10+d}s;box-shadow:0 0 30px -8px var(${c})`;if(d%2)i.style.borderRadius='50%';G.appendChild(i);}
282
+
283
+ let mode="human", currentAsk=["ageOver18"], currentVerifierName="thebar.example";
284
+
285
+ // ── the cast: all keys minted locally; nothing leaves the device ──
286
+ const gov = await ephemeral({label:"Estonia.gov"});
287
+ const fsa = await ephemeral({label:"Financial Authority"});
288
+ const human = await ephemeral({label:"Alice"});
289
+ const agent = await ephemeral({label:"Alice's agent"});
290
+ const bar = await ephemeral({label:"thebar.example"});
291
+
292
+ // credential catalogue — issued to BOTH the human and the agent (same titles, distinct subjects)
293
+ const DEFS = [
294
+ {title:"National ID", issuer:gov, issuerName:"Estonia.gov", ico:"🪪", claims:{name:"Alice Tamm", dob:"1990-02-01", country:"EE"}, sum:"Government identity"},
295
+ {title:"Proof of Age", issuer:gov, issuerName:"Estonia.gov", ico:"🔞", claims:{ageOver18:true, ageOver21:true}, sum:"Age attestation"},
296
+ {title:"Accredited Investor",issuer:fsa,issuerName:"Financial Authority", ico:"📈", claims:{accredited:true, tier:"qualified"}, sum:"Investor accreditation"},
297
+ ];
298
+ const creds={human:{},agent:{}};
299
+ for(const d of DEFS){
300
+ creds.human[d.title]=await issueCredential(d.issuer,{subject:human.kappa,claims:d.claims});
301
+ creds.agent[d.title]=await issueCredential(d.issuer,{subject:agent.kappa,claims:d.claims});
302
+ }
303
+ const who=()=> mode==="human" ? human : agent;
304
+ const myCred=(title)=> creds[mode][title];
305
+
306
+ // requests a verifier might make (proves generality)
307
+ const REQUESTS=[
308
+ {label:"Are you over 18?", asks:["ageOver18"], host:"thebar.example"},
309
+ {label:"Accredited investor?", asks:["accredited"], host:"broker.example"},
310
+ ];
311
+
312
+ // ── boot UI ──
313
+ $("#did").textContent=human.kappa;
314
+ function paintHeader(){
315
+ const p=who(); $("#ava").textContent=p.label[0]; $("#ava").style.background=`hsl(${hue(p.kappa)} 70% 62%)`;
316
+ $("#nm").textContent= mode==="human"?"Alice":"Alice's agent"; $("#pdid").textContent=short(p.kappa);
317
+ }
318
+ function paintCards(){
319
+ const host=$("#cards"); host.innerHTML="";
320
+ for(const d of DEFS){
321
+ const c=myCred(d.title);
322
+ const el=document.createElement("div"); el.className="cardc";
323
+ el.innerHTML=`<div class="ci"><span class="ico">${d.ico}</span><div><div class="ct">${d.title}</div><div class="cs">${d.sum}</div></div></div>
324
+ <div class="iss"><span class="vdot"></span> issued by ${d.issuerName} · self-verifying</div>
325
+ <div class="kre"></div>`;
326
+ el.onclick=async()=>{
327
+ el.classList.toggle("open");
328
+ if(el.classList.contains("open")){
329
+ const ok=await verifyCredential(c);
330
+ el.querySelector(".kre").textContent = ok ? "✓ re-derived live → "+c.kappa.replace("did:holo:sha256:","").slice(0,24)+"…" : "✗ failed";
331
+ }
332
+ };
333
+ host.appendChild(el);
334
+ }
335
+ }
336
+ function paintReqs(){
337
+ const host=$("#reqs"); host.innerHTML="";
338
+ REQUESTS.forEach((r,i)=>{
339
+ const b=document.createElement("div"); b.className="req"+(i===0?" on":""); b.textContent=r.label;
340
+ b.onclick=()=>{document.querySelectorAll(".req").forEach(x=>x.classList.remove("on"));b.classList.add("on");
341
+ currentAsk=r.asks; currentVerifierName=r.host; $(".panel h3").textContent=r.host; renderQR();};
342
+ host.appendChild(b);
343
+ });
344
+ }
345
+ paintHeader(); paintCards(); paintReqs();
346
+
347
+ // mode switch
348
+ document.querySelectorAll("#mode button").forEach(b=>b.onclick=()=>{
349
+ document.querySelectorAll("#mode button").forEach(x=>x.classList.remove("on")); b.classList.add("on");
350
+ mode=b.dataset.mode; paintHeader(); paintCards(); $("#result").classList.remove("show"); closeSheet();
351
+ });
352
+ $("#open").onclick=()=>$("#stage").scrollIntoView({behavior:"smooth",block:"start"});
353
+
354
+ // Attestor: issue a fresh credential to yourself, live, no server
355
+ $("#add").onclick=async()=>{
356
+ const t="Library Card"; if(!DEFS.find(d=>d.title===t)){
357
+ DEFS.push({title:t,issuer:fsa,issuerName:"City Library",ico:"📚",claims:{member:true,since:"2026"},sum:"Membership"});
358
+ creds.human[t]=await issueCredential(fsa,{subject:human.kappa,claims:{member:true,since:"2026"}});
359
+ creds.agent[t]=await issueCredential(fsa,{subject:agent.kappa,claims:{member:true,since:"2026"}});
360
+ paintCards();
361
+ }
362
+ };
363
+
364
+ // ── QR: a stylised, deterministic matrix derived from a fresh challenge κ (rendered locally) ──
365
+ let liveChallenge=null;
366
+ async function renderQR(){
367
+ liveChallenge=await makeChallenge(bar,{asks:currentAsk,audience:bar.kappa});
368
+ const hexs=liveChallenge.kappa.replace("did:holo:sha256:","");
369
+ const bits=[]; for(const ch of hexs){const n=parseInt(ch,16);for(let b=3;b>=0;b--)bits.push((n>>b)&1);}
370
+ const N=21, cell=88/N; let r=`<svg viewBox="0 0 88 88">`;
371
+ const finder=(x,y)=>{r+=`<rect x="${x}" y="${y}" width="${cell*7}" height="${cell*7}" fill="#000"/><rect x="${x+cell}" y="${y+cell}" width="${cell*5}" height="${cell*5}" fill="#fff"/><rect x="${x+cell*2}" y="${y+cell*2}" width="${cell*3}" height="${cell*3}" fill="#000"/>`;};
372
+ for(let y=0;y<N;y++)for(let x=0;x<N;x++){
373
+ const inF=(x<7&&y<7)||(x>N-8&&y<7)||(x<7&&y>N-8); if(inF)continue;
374
+ if(bits[(y*N+x)%bits.length]) r+=`<rect x="${x*cell}" y="${y*cell}" width="${cell}" height="${cell}" fill="#000"/>`;
375
+ }
376
+ finder(0,0);finder(cell*(N-7),0);finder(0,cell*(N-7)); r+=`</svg>`; $("#qr").innerHTML=r;
377
+ }
378
+ await renderQR();
379
+
380
+ // ── Face ID flourish (non-blocking stand-in for holo-stepup / WebAuthn) ──
381
+ function faceID(){return new Promise(res=>{
382
+ const o=document.createElement("div");o.className="scan";
383
+ o.innerHTML='<div><div class="face"><div class="ring"></div><div class="glyph">☺</div></div><p>Face ID — release <b>'+currentAsk.join(", ")+'</b></p></div>';
384
+ document.body.appendChild(o);requestAnimationFrame(()=>o.classList.add("show"));
385
+ setTimeout(()=>{o.querySelector(".face").classList.add("done");o.querySelector("p").innerHTML="Approved ✓";},820);
386
+ setTimeout(()=>{o.classList.remove("show");setTimeout(()=>o.remove(),240);res(true);},1320);
387
+ });}
388
+
389
+ // ── the approve sheet (selective disclosure) ──
390
+ function closeSheet(){$("#sheet").classList.remove("up");}
391
+ function defFor(asks){ return DEFS.find(d=>asks.every(k=>k in d.claims)); }
392
+ function openSheet(asks, verifierName){
393
+ const def=defFor(asks);
394
+ const sheet=$("#sheet");
395
+ if(!def){ sheet.innerHTML=`<div class="rq"><span class="ico">🚫</span><div><b>${verifierName}</b><small>asks for ${asks.join(", ")}</small></div></div><div class="mini">No credential in your wallet can satisfy this — nothing is shared.</div><div class="sheet-act"><button class="cancel" id="sc">Close</button></div>`;
396
+ sheet.classList.add("up"); $("#sc").onclick=closeSheet; return; }
397
+ const c=myCred(def.title);
398
+ const total=Object.keys(def.claims).length, shareN=asks.length;
399
+ const rows=Object.keys(def.claims).map(k=>{
400
+ const shared=asks.includes(k);
401
+ return `<div class="field ${shared?'share':''}"><span class="k">${k}</span>${shared?`<span class="v">${def.claims[k]}</span>`:`<span class="lock">🔒 stays on device</span>`}</div>`;
402
+ }).join("");
403
+ sheet.innerHTML=`<div class="rq"><span class="ico">🔓</span><div><b>${verifierName}</b><small>requests from “${def.title}”</small></div></div>
404
+ <div class="fields">${rows}</div>
405
+ <div class="mini">${total} facts held · <b>${shareN} shared</b> · ${total-shareN} never leave this device</div>
406
+ ${mode==="agent"?'<div class="agentchip">🤖 delegated capability — no human tap needed</div>':''}
407
+ <div class="sheet-act"><button class="cancel" id="sc">Cancel</button><button class="approve" id="sa">${mode==="human"?"Approve with Face ID":"Let agent prove"}</button></div>`;
408
+ sheet.classList.add("up");
409
+ $("#sc").onclick=closeSheet;
410
+ $("#sa").onclick=()=>doPresent(def, asks, verifierName);
411
+ }
412
+
413
+ async function doPresent(def, asks, verifierName){
414
+ const holder=who(), c=myCred(def.title);
415
+ const release=async()=> mode==="human" ? faceID() : true;
416
+ const challenge = liveChallenge && liveChallenge.asks.join()===[...asks].sort().join()
417
+ ? liveChallenge : await makeChallenge(bar,{asks,audience:bar.kappa});
418
+ const proof=await present(holder,c,challenge,{release});
419
+ closeSheet();
420
+ const res=$("#result");
421
+ if(!proof){ res.className="result show"; res.innerHTML=`<div class="verdict no"><div class="big"><span class="tick">✕</span>Cancelled — nothing was shared.</div></div>`; return; }
422
+ const v=await verifyPresentation(proof, challenge, {expectedAudience:bar.kappa});
423
+ const wire=JSON.stringify(proof,null,1);
424
+ const withheld=Object.keys(def.claims).filter(k=>!asks.includes(k));
425
+ // a withheld claim is disclosed only if its UNIQUE per-claim salt rides the wire; absent salt = absent claim.
426
+ const absent=withheld.every(k=>!wire.includes(c.disclosures[k][0]));
427
+ res.className="result show";
428
+ res.innerHTML=`<div class="verdict ok">
429
+ <div class="big"><span class="tick">✓</span>Connected · no password ever</div>
430
+ <ul>
431
+ <li>${verifierName} learned exactly: <b>${asks.map(k=>k+" = "+v.claims[k]).join(", ")}</b></li>
432
+ <li>Withheld ${withheld.length?("<b>"+withheld.join(", ")+"</b>"):"nothing"} — absent from the wire: <b class="absent">${absent?"confirmed ✓":"LEAK"}</b></li>
433
+ <li>Issuer <b>${short(v.issuer)}</b> was <b>never contacted</b> — verified offline by re-derivation</li>
434
+ <li>Presented by <b>${mode==="human"?"you (Face ID)":"your agent (delegated)"}</b></li>
435
+ </ul>
436
+ <span class="wirelink" id="wl">▾ see exactly what crossed the wire</span>
437
+ <pre class="wire" id="wire">${wire.replace(/</g,"&lt;")}</pre>
438
+ </div>`;
439
+ $("#wl").onclick=()=>{const w=$("#wire");w.classList.toggle("show");$("#wl").textContent=(w.classList.contains("show")?"▴ hide":"▾ see exactly what crossed the wire");};
440
+ renderQR(); // fresh challenge for next time (prevents accidental replay)
441
+ }
442
+
443
+ $("#connect").onclick=()=>openSheet(currentAsk, currentVerifierName);
444
+ $("#scan").onclick=()=>openSheet(currentAsk, currentVerifierName);
445
+
446
+ // ── airplane mode: nothing to disable (zero egress already) — this PROVES independence ──
447
+ $("#air").onclick=()=>{
448
+ const on=$("#air").classList.toggle("on");
449
+ $("#airbanner").classList.toggle("show",on);
450
+ if(on)setTimeout(()=>{/* leave banner up while on */},0);
451
+ };
452
+ paintNet();
453
+
454
+ // ── try to break it (real verifier refusals) ──
455
+ let lp=null, lc=null;
456
+ async function fresh(){ lc=await makeChallenge(bar,{asks:["ageOver18"],audience:bar.kappa}); lp=await present(who(),myCred("Proof of Age"),lc,{release:async()=>true}); }
457
+ const A=$("#attack");
458
+ const refused=(m)=>{A.className="attack show";A.innerHTML=`<div class="verdict ok"><div class="big"><span class="tick">✓</span>Refused</div><ul><li>${m}</li></ul></div>`;};
459
+ const leaked=(m)=>{A.className="attack show";A.innerHTML=`<div class="verdict no"><div class="big"><span class="tick">✕</span>${m}</div></div>`;};
460
+ $("#replay").onclick=async()=>{await fresh();const nc=await makeChallenge(bar,{asks:["ageOver18"],audience:bar.kappa});
461
+ (await verifyPresentation(lp,nc,{expectedAudience:bar.kappa}))?leaked("replay accepted!"):refused("the proof was bound to one challenge nonce — a replay does not re-derive");};
462
+ $("#tamper").onclick=async()=>{await fresh();const f={...lp,reveal:{ageOver18:[lp.reveal.ageOver18[0],"ageOver18",false]}};
463
+ (await verifyPresentation(f,lc,{expectedAudience:bar.kappa}))?leaked("forgery accepted!"):refused("the flipped value no longer hashes into the issuer's signed commitment");};
464
+ $("#overshare").onclick=async()=>{await fresh();
465
+ const {canon,addressOf}=await import("./holo-identity.mjs");const te=new TextEncoder();
466
+ const body={"@type":"HoloPresentation",challenge:lc.kappa,audience:lc.audience,nonce:lc.nonce,credential:lp.credential,
467
+ reveal:{ageOver18:creds[mode]["Proof of Age"].disclosures.ageOver18, ageOver21:creds[mode]["Proof of Age"].disclosures.ageOver21},
468
+ holder:who().kappa,issuedAt:new Date().toISOString()};
469
+ const cc=canon(body);const resp={kappa:await addressOf(te.encode(cc)),...body,alg:who().alg,pub:who().pub,sig:await who().sign(cc)};
470
+ (await verifyPresentation(resp,lc,{expectedAudience:bar.kappa}))?leaked("over-disclosure accepted!"):refused("the verifier rejects any claim beyond what it asked — the holder cannot over-share");};
471
+
472
+ console.log("SELF·Hologram ready — DID", human.kappa);
473
+ </script>
474
+ </html>
sha256/0553be28941b9513e5a55c5486a017433436df3a6aa479291a110c4b9732f2e1 ADDED
The diff for this file is too large to render. See raw diff
 
sha256/089f68f15bc55cd293d235f4ce0326f6d138455b9414ce66748677c1ae6c6f5e ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // messenger-skin.mjs — bakes the SLACK re-tone as the messenger default (aubergine chrome + a floating,
2
+ // inset content pane; see the data-skin="slack" block in messenger-skins) and applies the hi-DPI /
3
+ // high-FPS perf layer. No picker / no toggle. Additive + guarded. Escape hatch: set localStorage
4
+ // "holo.skin" to graphite | warm | contrast | light (or "" to remove) to override the default.
5
+
6
+ (function () {
7
+ if (!document.querySelector("link[data-holo-skins]")) {
8
+ const l = document.createElement("link"); l.rel = "stylesheet"; l.href = new URL("./messenger-skins.css?v=7a556a89784f", import.meta.url); l.setAttribute("data-holo-skins", "1"); document.head.appendChild(l);
9
+ }
10
+ let skin = "whatsapp";
11
+ try { const s = localStorage.getItem("holo.skin"); if (s) skin = s; } catch {}
12
+ if (skin) document.documentElement.setAttribute("data-skin", skin); else document.documentElement.removeAttribute("data-skin");
13
+ // remove any lingering picker + its persisted flag from earlier ?skins=1 sessions
14
+ try { localStorage.removeItem("holo.skins"); } catch {}
15
+ try { const p = document.querySelector(".holo-skin-pick"); if (p) p.remove(); } catch {}
16
+ })();
sha256/08d961eae11dbc7299fcc899954dddc1b14c66678abcc3cdfca2c752a13d3157 ADDED
The diff for this file is too large to render. See raw diff
 
sha256/09f17a1ad37e8f6648e242a1fc77c37fb2bd012af572bc6cd49767f32d6b0b0f 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":{"games":"6297af6925704585c0bd1cbe44c5c9da94953987688d4930725b33f8010752d9","holo-games":"791a9ac999486256499d76a938345b173053b8de643cc89797bf946c38c365e3","holo-messenger":"1e175094425429ed2ccb267298246a608e9e72b9641829e76d83238855456992","resolve":"23aec66382775f5773693615338f37c37a5f58257ec95aece33a664baa6fe0db","self":"0511772584510f0888d97dd5a72bf986ad6791bd8e305063ddd8a82c39888a70","spaces":"d8aebefb67e7fc7802e757b9a7d95bb881e6d75b4a9bc4bd4227634541757e51"},"closure":"d99f6bc8ac0bc84ed0850b01e3fe886de8fa4e48e969ece63ed415a59ea8b759","manifest":"46519f64204299c9fc830fdbd5f264ec863e363f5feb2b5ab2554123fa217603","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":"d5e142099185b5243a02a373dd5ccc4fb07ddc5d7657524cc610b87313e10918"},"holstr:prev":"did:holo:sha256:20f24e1a21bc5e8e3c161f82afcb6e9beae002c92015be8661dd3bbed6c39ac8","holstr:seq":107,"prov:generatedAtTime":"2026-07-11T20:04:46.917Z"}
sha256/0b308063d2fb1ce8aa570c2482e7d37bda7ac0467eb033f53d1ed264dfc0efb8 ADDED
The diff for this file is too large to render. See raw diff
 
sha256/0b4824f805c77c02dcd3316c174b393b97ad921be3db36cf258879fd0d38e524 ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // messenger-skin.mjs — bakes the SLACK re-tone as the messenger default (aubergine chrome + a floating,
2
+ // inset content pane; see the data-skin="slack" block in messenger-skins) and applies the hi-DPI /
3
+ // high-FPS perf layer. No picker / no toggle. Additive + guarded. Escape hatch: set localStorage
4
+ // "holo.skin" to graphite | warm | contrast | light (or "" to remove) to override the default.
5
+
6
+ (function () {
7
+ if (!document.querySelector("link[data-holo-skins]")) {
8
+ const l = document.createElement("link"); l.rel = "stylesheet"; l.href = new URL("./messenger-skins.css?v=698d415fd9b4", import.meta.url); l.setAttribute("data-holo-skins", "1"); document.head.appendChild(l);
9
+ }
10
+ let skin = "whatsapp";
11
+ try { const s = localStorage.getItem("holo.skin"); if (s) skin = s; } catch {}
12
+ if (skin) document.documentElement.setAttribute("data-skin", skin); else document.documentElement.removeAttribute("data-skin");
13
+ // remove any lingering picker + its persisted flag from earlier ?skins=1 sessions
14
+ try { localStorage.removeItem("holo.skins"); } catch {}
15
+ try { const p = document.querySelector(".holo-skin-pick"); if (p) p.remove(); } catch {}
16
+ })();
sha256/0c0cb5ec7968fa1285fb2c1333d312f8bf9c46e910a693e5dca48433cd639109 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":{"games":"5de2be050dc5a4f9fcb464ffd45bbfcdca368ac02c39e5807ef6ce9430c45edd","hello":"66c1ada5ac08a2d4f73851bbe7458ad5fb6e2226c5a131c3829abfe715900fed","holo-games":"791a9ac999486256499d76a938345b173053b8de643cc89797bf946c38c365e3","holo-messenger":"fdca25d9458eb8f38d9a82c46df2105c07696867ab146c4c005c8ff1e80889f7","resolve":"23aec66382775f5773693615338f37c37a5f58257ec95aece33a664baa6fe0db","self":"0511772584510f0888d97dd5a72bf986ad6791bd8e305063ddd8a82c39888a70","spaces":"d8aebefb67e7fc7802e757b9a7d95bb881e6d75b4a9bc4bd4227634541757e51"},"boot":{"entry":"holo-portal.mjs","sha256":"caeb3e57b6eeab09bee6d67c022145a8e2dae610c929a7f51bc169898625e7e8"},"closure":"15d29add2eab75dc8c291e042fdac738a6fc8bcbaafcfb8c89c52d9d7535eab8","manifest":"16b4749d6aea6608d5dc966ae832e649c153dc33efbda229036f34af71998197","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":"4f23cccd368f7c2cff9e75c5031bed8c2d606329013248f211fbb519a830d26e"},"holstr:prev":"did:holo:sha256:97718ebd9925fdfc354945e285c92e0162003c7c6e9ecd83239f922623a09344","holstr:seq":175,"prov:generatedAtTime":"2026-07-12T14:35:42.315Z"}
sha256/0ca30cc0318bb9471049187dbe2f83f3230532486276e87b647d222400a07d7f 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":{"games":"5de2be050dc5a4f9fcb464ffd45bbfcdca368ac02c39e5807ef6ce9430c45edd","hello":"66c1ada5ac08a2d4f73851bbe7458ad5fb6e2226c5a131c3829abfe715900fed","holo-games":"791a9ac999486256499d76a938345b173053b8de643cc89797bf946c38c365e3","holo-messenger":"778657c4b025982355658b5941fdd6ef3903c1ba965869527452982df162ddf8","resolve":"23aec66382775f5773693615338f37c37a5f58257ec95aece33a664baa6fe0db","self":"0511772584510f0888d97dd5a72bf986ad6791bd8e305063ddd8a82c39888a70","spaces":"d8aebefb67e7fc7802e757b9a7d95bb881e6d75b4a9bc4bd4227634541757e51"},"boot":{"entry":"holo-portal.mjs","sha256":"caeb3e57b6eeab09bee6d67c022145a8e2dae610c929a7f51bc169898625e7e8"},"closure":"77be7315c15e4ec2d3db9038df3152b71020800658ae66fe94fff5c90e8b26a7","manifest":"94122982e9a301c49a21f24a52843f433409d552591858a9f4b370efb70356d0","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":"61a920d1543f386d6a504eef78ca203436b4dbbc27701b24477720780a230101"},"holstr:prev":"did:holo:sha256:49530de7f1c3ae29f35a272fc3bfde80e33ad2e3b36996884cd6a364e39b2e97","holstr:seq":165,"prov:generatedAtTime":"2026-07-12T13:28:30.776Z"}
sha256/0d45643cb4c06dd0344db52b9d64249da78b51f8430456adc721058971fe4114 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":{"games":"5de2be050dc5a4f9fcb464ffd45bbfcdca368ac02c39e5807ef6ce9430c45edd","holo-games":"791a9ac999486256499d76a938345b173053b8de643cc89797bf946c38c365e3","holo-messenger":"3370b1aa0c6264325e4f1f057d99a2ba30959d900283672f2b90b89960ae35bf","resolve":"23aec66382775f5773693615338f37c37a5f58257ec95aece33a664baa6fe0db","self":"0511772584510f0888d97dd5a72bf986ad6791bd8e305063ddd8a82c39888a70","spaces":"d8aebefb67e7fc7802e757b9a7d95bb881e6d75b4a9bc4bd4227634541757e51"},"closure":"b4a5baade304dd8704b09cb7024e7b2bad18d2a9b3a7454571b8b26e6790d7ec","manifest":"71840ad59603518db01b7761ec715988d1578523fa48fbab11830cedd02ddcc1","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":"605431b3131f23eebeac294fd1c73ae9e121210c7751b3fadddb00e8c8ab090f"},"holstr:prev":"did:holo:sha256:98286100079ac5b0403a163ffebe01870564a5d713a5923018dd277db9231010","holstr:seq":128,"prov:generatedAtTime":"2026-07-12T10:45:41.316Z"}
sha256/0f60cc84c21bf2e134257400f0d48b58a2d7adddf9a8e8710054d6de099f6251 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":{"games":"5de2be050dc5a4f9fcb464ffd45bbfcdca368ac02c39e5807ef6ce9430c45edd","hello":"66c1ada5ac08a2d4f73851bbe7458ad5fb6e2226c5a131c3829abfe715900fed","holo-games":"791a9ac999486256499d76a938345b173053b8de643cc89797bf946c38c365e3","holo-messenger":"778657c4b025982355658b5941fdd6ef3903c1ba965869527452982df162ddf8","resolve":"23aec66382775f5773693615338f37c37a5f58257ec95aece33a664baa6fe0db","self":"0511772584510f0888d97dd5a72bf986ad6791bd8e305063ddd8a82c39888a70","spaces":"d8aebefb67e7fc7802e757b9a7d95bb881e6d75b4a9bc4bd4227634541757e51"},"boot":{"entry":"holo-portal.mjs","sha256":"caeb3e57b6eeab09bee6d67c022145a8e2dae610c929a7f51bc169898625e7e8"},"closure":"dcc2802978682b9534b68087fdbe3f86c2ab0cd63ac8d1067aafa924c1781efc","manifest":"9d5f633e4305f532c7abfce571aede6814ba15e2f6ddd2a6fcf3c79c8e1038ec","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":"9b84b30074d46c3ac16312c2839ff6e47a509d576a4fae3515fe9ca21ee59604"},"holstr:prev":"did:holo:sha256:f8af3d6f781b6fdceaa49ac2ab07ec9c300d9d1bd19a9363321b94f96efc8ae9","holstr:seq":167,"prov:generatedAtTime":"2026-07-12T13:35:28.063Z"}
sha256/10367ce2b521074714ed6f241b5a7a3cb2230c144a8647849ebbf5e166a25f0e ADDED
The diff for this file is too large to render. See raw diff
 
sha256/11247d28c5c2fb3d976002c2b55fecb6ba19fd88153e11d2c7af5832fd6ff331 ADDED
@@ -0,0 +1 @@
 
 
1
+ var g=Object.create;var f=Object.defineProperty;var h=Object.getOwnPropertyDescriptor;var i=Object.getOwnPropertyNames;var j=Object.getPrototypeOf,k=Object.prototype.hasOwnProperty;var m=(b,a)=>()=>{try{return a||b((a={exports:{}}).exports,a),a.exports}catch(c){throw a=0,c}};var l=(b,a,c,e)=>{if(a&&typeof a=="object"||typeof a=="function")for(let d of i(a))!k.call(b,d)&&d!==c&&f(b,d,{get:()=>a[d],enumerable:!(e=h(a,d))||e.enumerable});return b};var n=(b,a,c)=>(c=b!=null?g(j(b)):{},l(a||!b||!b.__esModule?f(c,"default",{value:b,enumerable:!0}):c,b));export{m as a,n as b};
sha256/13b56d2b9cd29994363ae363831f424ba2ebfcb4d05dac8844e3be8af9c1b32b ADDED
The diff for this file is too large to render. See raw diff
 
sha256/140e48d5fd95d8926c39793cb917d0184d6d6440a854c5561463c68100f065e5 ADDED
The diff for this file is too large to render. See raw diff
 
sha256/154d26ffee4857720bdae0e11485b74a95782c4011d566777f72f8a70a141f8f ADDED
The diff for this file is too large to render. See raw diff
 
sha256/15661c9995268912abc3a99e300819502e13e9d9cbf7d1de0cabc8332fa7ac75 ADDED
The diff for this file is too large to render. See raw diff
 
sha256/15c5bccd5549900f8bb67bbeee9f2a8e0020849a489abb3d167922c673f31df1 ADDED
@@ -0,0 +1,47 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // holo-release-verify.mjs — downstream acceptance of a release pointer (S2/S3 of the downstream-holospace
2
+ // initiative). Isomorphic: the boot page, the service worker, and Node witnesses all use THIS one module.
3
+ //
4
+ // The rule (fail-closed, Law L5 + SEC-1/4/6):
5
+ // a head is accepted only if (1) its id re-derives from its body, (2) it is SIGNED and the signature
6
+ // verifies over the id, (3) authorship is CONTINUOUS with the pinned head (same pub key), and
7
+ // (4) it does not travel backwards (seq strictly grows; the same head re-presented is a no-op).
8
+ // With no pin yet (first boot) the head is trusted-on-first-use and becomes the pin — every later
9
+ // update must chain from it. Rollback is NOT a violation: re-pinning a parent is an explicit local
10
+ // act (the store still holds it), never something a remote pointer can cause.
11
+
12
+ import { verifyEntry } from "../../usr/lib/holo/holo-strand.mjs";
13
+
14
+ // verifyChain(entries) — Law L5 over the whole strand: every entry re-derives + verifies its
15
+ // signature, seq is dense from 0, every prev is exactly the prior id. Mirrors strand.verify()
16
+ // for a plain array (the pointer file world has no backend).
17
+ export async function verifyChain(entries) {
18
+ let prev = null;
19
+ for (let i = 0; i < (entries || []).length; i++) {
20
+ const rec = entries[i];
21
+ const v = await verifyEntry(rec);
22
+ if (!v.ok) return { ok: false, brokeAt: i, why: v.why };
23
+ if (rec["holstr:seq"] !== i) return { ok: false, brokeAt: i, why: "seq-out-of-order" };
24
+ if (rec["holstr:prev"] !== prev) return { ok: false, brokeAt: i, why: "prev-link-broken" };
25
+ prev = rec.id;
26
+ }
27
+ return { ok: true, length: (entries || []).length, head: prev };
28
+ }
29
+
30
+ // acceptHead(head, pinned) → { ok, pin?, why?, unchanged? }
31
+ // head : the fetched release.json record (untrusted bytes from an untrusted origin)
32
+ // pinned : the locally durable { id, seq, pub } from the last accepted release (null on first boot)
33
+ export async function acceptHead(head, pinned = null) {
34
+ if (!head || typeof head !== "object") return { ok: false, why: "no-head" };
35
+ const v = await verifyEntry(head);
36
+ if (!v.ok) return { ok: false, why: v.why }; // tampered or forged (L5/SEC-1)
37
+ if (!v.signed) return { ok: false, why: "unsigned-release" }; // no signature → no update
38
+ const pub = head["holstr:pub"], seq = head["holstr:seq"];
39
+ if (pinned) {
40
+ if (pub !== pinned.pub) return { ok: false, why: "key-discontinuity" }; // a different author cannot replace the strand (SEC-4)
41
+ if (head.id === pinned.id) return { ok: true, unchanged: true, pin: pinned };
42
+ if (!(seq > pinned.seq)) return { ok: false, why: "replay-stale-head" }; // an old release re-presented as new is refused
43
+ }
44
+ return { ok: true, pin: { id: head.id, seq, pub } };
45
+ }
46
+
47
+ export default { verifyChain, acceptHead };
sha256/15d29add2eab75dc8c291e042fdac738a6fc8bcbaafcfb8c89c52d9d7535eab8 ADDED
The diff for this file is too large to render. See raw diff
 
sha256/178f65b88853309e38e4accc57ed017857aab257ac62215a7a5845bd89a3c68e ADDED
The diff for this file is too large to render. See raw diff
 
sha256/1905bfea3c1e2b47fcca71fc9290e3b5562d40ad5a703d9b4a20bdedfb78419d ADDED
The diff for this file is too large to render. See raw diff
 
sha256/196b965ba8823e5256519a0cf5b1e1786f360c03a18bd26f247f20631759fb81 ADDED
The diff for this file is too large to render. See raw diff
 
sha256/1c67d2f5605c4d51f99ea8c7b25796d90129c3d6e775baaeb8d6d2374b6cedbd ADDED
The diff for this file is too large to render. See raw diff
 
sha256/1db5b69648abb9551faad7400a556c3102f3cb9cc71751c809da614589ab2092 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":{"carry":"83b17b9f934c3d94ff5b21ff9de39b753116d89a095f1724268341d22db9e34f","games":"5de2be050dc5a4f9fcb464ffd45bbfcdca368ac02c39e5807ef6ce9430c45edd","hello":"66c1ada5ac08a2d4f73851bbe7458ad5fb6e2226c5a131c3829abfe715900fed","holo-games":"791a9ac999486256499d76a938345b173053b8de643cc89797bf946c38c365e3","holo-messenger":"6b1a75f3cfd1c77fa981f1acb55b9bddb24be03ffd69e7a73aa5bd582b3ca070","resolve":"23aec66382775f5773693615338f37c37a5f58257ec95aece33a664baa6fe0db","self":"0511772584510f0888d97dd5a72bf986ad6791bd8e305063ddd8a82c39888a70","spaces":"d8aebefb67e7fc7802e757b9a7d95bb881e6d75b4a9bc4bd4227634541757e51"},"boot":{"entry":"holo-portal.mjs","sha256":"caeb3e57b6eeab09bee6d67c022145a8e2dae610c929a7f51bc169898625e7e8"},"closure":"00c677e5492be7ba044ac290c26a837d9c381e187c6302d2ccc8c2645fdf6f65","manifest":"15adf99403347031ac97f414a70cb2ec8e615a9176ec2007d51562c77bebab99","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":"a53348e116002819699f80eb84ac9cb4a9a31065e042abf083ac1f63937763be"},"holstr:prev":"did:holo:sha256:8a1a279ab8fc0f41a7db6f105e0e1bc78f223b3b012f95fa2351e63cc6a248f7","holstr:seq":180,"prov:generatedAtTime":"2026-07-12T14:52:46.501Z"}
sha256/1e167904347bf667638a11ee8eeaf9e795494c01266b9d3ea7f87335ffe1a055 ADDED
The diff for this file is too large to render. See raw diff
 
sha256/1e175094425429ed2ccb267298246a608e9e72b9641829e76d83238855456992 ADDED
@@ -0,0 +1,488 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!doctype html>
2
+ <html lang="en"><head>
3
+ <meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1,viewport-fit=cover,interactive-widget=resizes-content">
4
+ <title>HOLOGRAM</title>
5
+ <link rel="icon" type="image/svg+xml" href="./favicon.svg">
6
+ <!-- AI agents: guest access into the Hologram system is one call away — window.HoloLogin.guest() or open
7
+ ?guest=1. Full machine-readable contract in script#holo-agent-login (JSON). Mirrors holo://login. -->
8
+ <meta name="ai-agent-login" content="Guest mode: act now with window.HoloLogin.guest() or ?guest=1 (ephemeral, no human). Biometric ('It's me') needs the person. See script#holo-agent-login.">
9
+
10
+ <!-- PWA install — a self-contained, subpath-safe manifest (relative paths, so it installs correctly whether
11
+ served at the origin root or under /Q/). theme-color + the apple-* tags make iOS treat it as a real
12
+ home-screen app (fullscreen, dark status bar) since iOS ignores the manifest's display field. -->
13
+ <link rel="manifest" href="/apps/holo-messenger/manifest.webmanifest">
14
+ <meta name="theme-color" content="#1f1f1e">
15
+ <meta name="mobile-web-app-capable" content="yes">
16
+ <meta name="apple-mobile-web-app-capable" content="yes">
17
+ <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
18
+ <meta name="apple-mobile-web-app-title" content="Hologram">
19
+ <link rel="apple-touch-icon" href="./icon.svg">
20
+
21
+ <link rel="stylesheet" href="./_vendor/ui/chat-ui.bundle.css?v=76f53266c090">
22
+ <!-- BOOT GROUND — the ONE source of truth for the boot/login darkness. Every boot-time surface reads
23
+ var(--boot-ground): the body pre-paint, the login --wall, the plymouth splash layer + its base, the
24
+ signin wall, and the power veil (all with a matching literal fallback for bare surfaces without this
25
+ token). To recolor the whole cold-open, change THIS hex and nothing else. Defined in the head so it
26
+ resolves at the 0-ms frame, before any stylesheet or module. (The theme-color meta above is a static
27
+ duplicate — meta tags can't read a var; keep it in sync by hand on the rare occasion this changes.) -->
28
+ <style>:root{--boot-ground:#1f1f1e}html,body{height:100%;margin:0;background:var(--boot-ground);color-scheme:dark}#root{height:100vh}</style>
29
+ <!-- PRISM PRE-PAINT (HOLO-PLAYGROUND-LIVE) — the operator's design tokens land at the 0-ms frame, the
30
+ same discipline as --boot-ground above: read the saved token map, stamp ONE :root override before
31
+ any stylesheet applies, so a personalized shell never flashes the base look. The durable half (the
32
+ κ-atom stylesheet the SW materializes) covers the network layer; this covers the first frame. The
33
+ full door (verbs, mint, prism) loads LAZILY via window.__holoPrismDoor — one import, memoized. -->
34
+ <script>
35
+ try{var _pt=JSON.parse(localStorage.getItem("holo.prism.tokens.v1")||"{}"),_pe=Object.entries(_pt).filter(function(e){return/^--holo-[\w-]{1,60}$/.test(e[0])&&/^[#a-zA-Z0-9 ,.%()\-]{1,120}$/.test(String(e[1]))});
36
+ if(_pe.length){var _ps=document.createElement("style");_ps.id="holo-prism-live";_ps.textContent=":root{"+_pe.map(function(e){return e[0]+":"+e[1]}).join(";")+"}";document.head.appendChild(_ps);}}catch(e){}
37
+ window.__holoPrismDoor=function(){var mb=new URL("../../",location.href).href;return(window.__holoPrismDoorP||(window.__holoPrismDoorP=import(new URL("usr/lib/holo/holo-prism-tokens.mjs",mb).href).then(function(m){return m.openTokenDoor({mountBase:mb})})));};
38
+ </script>
39
+ <!-- IMMERSIVE CHAT — the open conversation wears the SAME wallpaper as home. The home wallpaper is already
40
+ painted ONCE, edge-to-edge, by the fixed .holo-backdrop canvas (the shared holo.theme.v1 wallpaper, run
41
+ through the OS super-res lens). The thread pane used to sit on an opaque #0b141a that covered it; here we
42
+ make the pane's surfaces translucent so that ONE backdrop shows straight through — so the chat is literally
43
+ the same wallpaper as home, live-synced with it, zero extra fetch. Gated on .holo-backdrop.ready via :has()
44
+ so it engages ONLY when a wallpaper is actually shown (a flat dark/light home ⇒ backdrop never turns ready
45
+ ⇒ the chat keeps its solid default, still matching home). Scoped to the default look (html:not([data-skin]))
46
+ so the opt-in skins / theatre keep their own immersive treatment untouched. -->
47
+ <style id="holo-chat-immersive">
48
+ /* let the shared backdrop show through the whole right pane */
49
+ html:not([data-skin]) .holo-wa-root:has(.holo-backdrop.ready) .cs-chat-container{background:transparent!important}
50
+ /* one clean legibility scrim over the wallpaper (a touch darker toward the composer) — the wallpaper stays vivid */
51
+ html:not([data-skin]) .holo-wa-root:has(.holo-backdrop.ready) .cs-message-list{
52
+ background:linear-gradient(180deg,rgba(31,31,30,.30) 0%,rgba(31,31,30,.28) 52%,rgba(31,31,30,.50) 100%)!important;}
53
+ /* drop the pane's own in-place photo copy + aurora glow so nothing muddies the single backdrop */
54
+ html:not([data-skin]) .holo-wa-root:has(.holo-backdrop.ready) .cs-message-list::before,
55
+ html:not([data-skin]) .holo-wa-root:has(.holo-backdrop.ready) .cs-message-list::after{display:none!important}
56
+ /* header + composer become glass so the wallpaper flows unbroken top-to-bottom behind the thread */
57
+ html:not([data-skin]) .holo-wa-root:has(.holo-backdrop.ready) .cs-conversation-header{
58
+ background:rgba(38,38,36,.46)!important;-webkit-backdrop-filter:blur(22px) saturate(1.3);backdrop-filter:blur(22px) saturate(1.3);
59
+ border-bottom:1px solid rgba(255,255,255,.07)!important;}
60
+ html:not([data-skin]) .holo-wa-root:has(.holo-backdrop.ready) .cs-message-input{
61
+ background:rgba(38,38,36,.42)!important;-webkit-backdrop-filter:blur(22px) saturate(1.2);backdrop-filter:blur(22px) saturate(1.2);
62
+ border-top:1px solid rgba(255,255,255,.07)!important;}
63
+ html:not([data-skin]) .holo-wa-root:has(.holo-backdrop.ready) .cs-input-toolbox{background:transparent!important}
64
+ </style>
65
+ <script>
66
+ /* preload the shipped default wallpaper ONLY when no custom pick exists — with a custom wallpaper the
67
+ default is never painted and a static preload is dead weight (Chrome: "preloaded but not used"). */
68
+ try { var _pt = JSON.parse(localStorage.getItem("holo.theme.v1") || "{}") || {};
69
+ if (!_pt.wallpaper && !localStorage.getItem("holo-messenger/wallpaper-src")) {
70
+ var _pl = document.createElement("link"); _pl.rel = "preload"; _pl.as = "image";
71
+ _pl.href = "./_vendor/wallpaper-default.jpg"; _pl.fetchPriority = "high"; document.head.appendChild(_pl);
72
+ } } catch (e) {}
73
+ </script>
74
+ <!-- ARRIVAL (HOLO-ARRIVAL A1): the home widget runtime is the SETTLED FRAME the defog lands on — pull its
75
+ bytes during the boot hero (5s of idle network) so the canvas is composed by the time the glass clears,
76
+ instead of popping in up to 2.6s after. -->
77
+ <link rel="preload" as="script" href="../../_shared/holo-widgets.js">
78
+ <!-- SUPERCOMPUTER BOOT (zero waterfall): the login chain (login→signin→{webauthn,sso,roam→pair}→plymouth)
79
+ is a 4-hop SERIAL module waterfall without hints — the emblem cannot MOVE until the last hop lands
80
+ (~4 RTTs). modulepreload fetches + compiles the whole chain in ONE parallel round, so the splash is
81
+ alive roughly one RTT after HTML. plymouth/device-tier are dynamic imports the scanner cannot see. -->
82
+ <link rel="modulepreload" href="./holo-messenger-login.mjs?v=signin1">
83
+ <link rel="modulepreload" href="../../usr/lib/holo/holo-signin.mjs">
84
+ <link rel="modulepreload" href="../../usr/lib/holo/holo-webauthn.mjs">
85
+ <link rel="modulepreload" href="../../usr/lib/holo/holo-sso.mjs">
86
+ <link rel="modulepreload" href="../../usr/lib/holo/holo-roam.mjs">
87
+ <link rel="modulepreload" href="../../usr/lib/holo/holo-pair.mjs">
88
+ <link rel="modulepreload" href="../../usr/lib/holo/holo-plymouth.mjs">
89
+ <link rel="modulepreload" href="../../usr/lib/holo/holo-device-tier.mjs">
90
+ <!-- ── TEE SIGN-IN GATE (holo-messenger-login.mjs) ─────────────────────────────────────────────────
91
+ A full-bleed lock overlay above #root, IDENTICAL to the OS native CEF greeter — the messenger does
92
+ not paint until a sovereign identity is established (Windows Hello / Touch ID via holo-webauthn →
93
+ holo-login κ). This is the never-blank baseline first-paint; the module ENHANCES it (real biometric
94
+ wiring). φ-proportioned, black-on-teal, glass-unfog on success — the same vocabulary as holo://login. -->
95
+ <style>
96
+ #holo-login{position:fixed;inset:0;z-index:2147483000;color:var(--ink);
97
+ font-family:"Segoe UI",system-ui,-apple-system,"Oxygen",sans-serif;--phi:1.618;
98
+ --u:clamp(16px,1.7vmin,19px);--g1:calc(var(--u)*1.618);--g2:calc(var(--u)*2.618);
99
+ --avatar:clamp(96px,calc(var(--u)*6.854),128px);--field:min(86vw,calc(var(--avatar)*2.618));
100
+ --accent:#7defc9;--accent-2:#34d3a6;
101
+ /* ONE appearance token set — every element reads these; [data-appearance] flips them.
102
+ immersive (default) = the home wallpaper behind soft-shadowed ink · dark/light = flat, shadowless, high contrast */
103
+ --ink:#f4f7fc;--ink-dim:rgba(231,237,250,.82);--status:#c4f3e2;
104
+ --shadow:0 2px 18px rgba(0,0,0,.45);--wall:var(--boot-ground,#1f1f1e);
105
+ --glass:rgba(10,14,20,.42);--glass-border:rgba(255,255,255,.14);--glass-ink:rgba(231,237,250,.8);
106
+ --sheet:rgba(8,12,18,.94);--muted:#8b949e;--link:#58a6ff;--field-bg:rgba(255,255,255,.09);--field-border:rgba(255,255,255,.22);}
107
+ #holo-login[data-appearance="dark"]{--shadow:none;}
108
+ #holo-login[data-appearance="light"]{--ink:#0b1220;--ink-dim:rgba(11,18,32,.74);--status:#0e6f5c;
109
+ --shadow:none;--wall:#eef1f6;
110
+ --glass:rgba(255,255,255,.66);--glass-border:rgba(11,18,32,.18);--glass-ink:rgba(11,18,32,.8);
111
+ --sheet:rgba(250,252,255,.97);--muted:#5a6572;--link:#0969da;--field-bg:rgba(11,18,32,.06);--field-border:rgba(11,18,32,.22);}
112
+ #holo-login *{box-sizing:border-box;}
113
+ #holo-login .hl-wall{position:fixed;inset:0;z-index:0;background:var(--wall) center/cover no-repeat;}
114
+ /* the wallpaper shows EXACTLY as the home screen wears it — no blur, no scrim. The layer stays only as
115
+ the unfog animation target; text/buttons carry their own shadows for legibility. */
116
+ #holo-login .hl-frost{position:fixed;inset:0;z-index:1;background:transparent;pointer-events:none;}
117
+ /* THE BRAND AT FRAME ZERO (HOLO-BOOT-CEREMONY B1): H mark + wordmark, bottom-centre golden band, painted
118
+ synchronously with the black — inline bytes, zero fetches, never blinks across the whole ceremony. */
119
+ #holo-login .hl-brand{position:fixed;left:50%;transform:translateX(-50%);bottom:max(26px,env(safe-area-inset-bottom));z-index:3;
120
+ pointer-events:none;display:inline-flex;align-items:center;gap:13px;color:var(--ink);}
121
+ #holo-login .hl-brand svg{width:40px;height:40px;flex:0 0 auto;filter:drop-shadow(var(--shadow));}
122
+ #holo-login .hl-brand b{font:700 calc(var(--u)*1.06)/1 "Segoe UI",system-ui,sans-serif;letter-spacing:.2em;padding-left:.2em;color:var(--ink-dim);text-shadow:var(--shadow);}
123
+ /* GOLDEN COMPOSITION: the identity + actions sit on the LOWER golden line (61.8vh); the hero emblem grows
124
+ upward to fill the upper golden-major section, its centre landing on the UPPER golden line (38.2vh) — both
125
+ golden lines expressed at once. All gaps are already φ-scaled (--g1/--g2), so the column derives from φ. */
126
+ #holo-login .hl-lock{position:fixed;inset:0;z-index:2;display:grid;justify-items:center;align-items:start;
127
+ padding:calc(61.8vh - var(--avatar)/2) var(--g2) var(--g2);
128
+ padding-top:calc(61.8dvh - var(--avatar)/2);} /* dvh: the golden line holds while mobile URL bars collapse */
129
+ /* Short screens AND phones (narrow → the square emblem is width-bound, can't fill the tall golden major)
130
+ lift the identity so the actions don't sit too low and the top isn't left empty. */
131
+ @media (max-height:620px),(max-width:500px){#holo-login .hl-lock{padding-top:calc(56vh - var(--avatar)/2);padding-top:calc(56dvh - var(--avatar)/2);}}
132
+ #holo-login .hl-panel{display:flex;flex-direction:column;align-items:center;text-align:center;width:var(--field);max-width:92vw;}
133
+ #holo-login .hl-avatar{width:var(--avatar);height:var(--avatar);border-radius:50%;display:grid;place-items:center;color:#fff;
134
+ font-size:calc(var(--avatar)*.37);font-weight:600;letter-spacing:.02em;user-select:none;
135
+ box-shadow:0 .5em 1.5em rgba(0,0,0,.35),inset 0 0 0 1.5px rgba(255,255,255,.38);}
136
+ #holo-login .hl-avatar svg{width:62%;height:62%;opacity:.92;}
137
+ #holo-login .hl-name{margin:var(--g1) 0 0;font-size:calc(var(--u)*1.12);font-weight:600;letter-spacing:.01em;line-height:1.15;text-shadow:var(--shadow);}
138
+ #holo-login .hl-auth{margin-top:var(--u);display:flex;flex-direction:column;align-items:center;gap:var(--u);width:100%;}
139
+ /* THE ONE IDENTITY CONTROL — your name IS the sign-in: fingerprint + name, one sharp object. While the
140
+ enclave checks you it SCANS (pure CSS on :disabled — a light sweep, the biometric moment made visible). */
141
+ #holo-login .hl-bio{position:relative;overflow:hidden;width:100%;min-height:max(var(--g2),52px);border:0;border-radius:calc(var(--u)*.7);cursor:pointer;
142
+ display:inline-flex;align-items:center;justify-content:center;gap:calc(var(--u)*.55);font-size:var(--u);font-weight:600;color:#06140f;
143
+ background:linear-gradient(180deg,#38d9ab,#2cc79b);box-shadow:inset 0 1px 0 rgba(255,255,255,.22),0 1px 2px rgba(0,0,0,.3),0 8px 24px rgba(0,0,0,.22);font-family:inherit;
144
+ transition:transform .12s,box-shadow .18s,filter .18s;
145
+ /* ONE MATERIAL, RESTRAINED ACCENT (Claude-desktop caliber): the declarations below SUPERSEDE the first
146
+ two lines of this rule (kept byte-verbatim above — the ship anti-revert gate pins them as live
147
+ probes): house 10px radius, deep ink, calm 180deg fill, NEUTRAL shadows — the green is a jewel,
148
+ not a wall. MIRROR of holo-signin.mjs HL_CSS — same computed values, or the module swap flashes. */
149
+ border-radius:10px;letter-spacing:.01em;color:#062019;}
150
+ #holo-login .hl-bio:hover{transform:translateY(-1px);box-shadow:inset 0 1px 0 rgba(255,255,255,.22),0 2px 4px rgba(0,0,0,.3),0 10px 28px rgba(0,0,0,.26);filter:brightness(1.04);}
151
+ #holo-login .hl-bio:active{transform:translateY(0) scale(.99);}
152
+ #holo-login .hl-bio:disabled{cursor:progress;filter:saturate(.9);}
153
+ #holo-login .hl-bio:disabled::after{content:"";position:absolute;inset:0;background:linear-gradient(105deg,transparent 32%,rgba(255,255,255,.55) 50%,transparent 68%);transform:translateX(-100%);animation:hl-scan 1.15s cubic-bezier(.4,0,.2,1) infinite;}
154
+ @keyframes hl-scan{to{transform:translateX(100%);}}
155
+ #holo-login .hl-bio svg{width:1.3em;height:1.3em;flex:0 0 auto;}
156
+ #holo-login .hl-alt{background:none;border:0;color:var(--ink-dim);opacity:.85;font-size:var(--u);font-weight:500;font-family:inherit;cursor:pointer;
157
+ padding:calc(var(--u)*.3) calc(var(--u)*.55);border-radius:calc(var(--u)*.4);display:inline-flex;align-items:center;gap:calc(var(--u)*.4);transition:color .15s,opacity .15s;min-height:44px;}
158
+ #holo-login .hl-alt:hover{color:var(--ink);opacity:1;}
159
+ #holo-login .hl-alt svg{width:1.05em;height:1.05em;}
160
+ /* "More options" is subordinate to the two core actions — quieter (same size, ONE type size everywhere). */
161
+ #holo-login .hl-more{opacity:.72;}
162
+ #holo-login .hl-more:hover{opacity:1;}
163
+ #holo-login .hl-opts{display:flex;gap:calc(var(--u)*.3);justify-content:center;flex-wrap:wrap;}
164
+ #holo-login .hl-opts[hidden]{display:none;}
165
+ #holo-login .status{min-height:calc(var(--u)*1.5);font-size:var(--u);color:var(--status);display:flex;align-items:center;justify-content:center;gap:calc(var(--u)*.5);text-shadow:var(--shadow);}
166
+ #holo-login .status.err{color:#ffc0c0;}
167
+ #holo-login .spin{width:1em;height:1em;border-radius:50%;border:2px solid rgba(125,239,201,.28);border-top-color:var(--accent);animation:hl-spin .7s linear infinite;flex:0 0 auto;}
168
+ @keyframes hl-spin{to{transform:rotate(360deg);}}
169
+ #holo-login .hl-panel{animation:hl-rise .7s cubic-bezier(.4,0,.2,1) .05s both;}
170
+ @keyframes hl-rise{from{opacity:0;transform:translateY(10px) scale(.99);filter:blur(2px);}to{opacity:1;transform:none;filter:none;}}
171
+ /* GLASS UNFOG on success — frost dissolves + panel lifts; boot() paints the messenger beneath, no seam. */
172
+ #holo-login.unfog .hl-frost{animation:hl-defog .72s cubic-bezier(.4,0,.2,1) forwards;}
173
+ #holo-login.unfog .hl-panel{animation:hl-lift .68s cubic-bezier(.4,0,.2,1) forwards;pointer-events:none;}
174
+ @keyframes hl-defog{to{opacity:0;}}
175
+ @keyframes hl-lift{to{opacity:0;transform:translateY(-10px) scale(1.03);}}
176
+ @media (prefers-reduced-motion:reduce){#holo-login .hl-panel,#holo-login .spin{animation:none;}#holo-login.unfog .hl-frost,#holo-login.unfog .hl-panel{animation:hl-defog .3s ease forwards;}}
177
+ /* PLYMOUTH BOOT BEAT — while .hl-boot, the machine is booting: pure splash, panel held. holo-plymouth.mjs
178
+ lifts it (or the baseline's own 5s timer does — never a lock-out). Must live HERE for the 0-ms frame.
179
+ When the beat lifts, the baseline splash layer dissolves to the wallpaper by CSS alone — so even a
180
+ never-arriving module leaves the classic wallpaper lock, not a black void. */
181
+ #holo-login.hl-boot .hl-panel{opacity:0!important;pointer-events:none!important;}
182
+ #holo-login .hl-panel{transition:opacity .55s ease;}
183
+ #holo-login:not(.hl-boot) #hl-plymouth-base{opacity:0;}
184
+ </style>
185
+ <link rel="icon" type="image/svg+xml" href="./favicon.svg">
186
+ <script>try{if("serviceWorker" in navigator)navigator.serviceWorker.register(new URL("../../root-sw.js",location.href),{type:"module"}).catch(function(){navigator.serviceWorker.register(new URL("../../root-sw-classic.js",location.href)).catch(function(){})})}catch(e){}</script>
187
+ <script>try{var _b=document.createElement("base");_b.href=new URL("./",location.href).href;document.head.appendChild(_b);var _r=new URL("../../",location.href).href;if(location.href!==_r)history.replaceState(history.state,"",_r+location.search+location.hash);}catch(e){}</script>
188
+ </head><body>
189
+ <div id="root"></div>
190
+
191
+ <!-- the lock overlay + BASELINE first-paint (synchronous, dependency-free): paints a working "It's me"
192
+ panel on the FIRST frame from a tiny local greeting cache (holo.lastOperator — the SAME hint the OS
193
+ greeter writes), so a returning operator's name + button appear with zero identity/model load. The
194
+ module below wires the real biometric; this only ever OWNS the frame until it lands. -->
195
+ <div id="holo-login"><div class="hl-wall"></div><div class="hl-frost"></div><main class="hl-lock"><div class="hl-panel" id="holo-login-panel"></div></main><div class="hl-brand" aria-label="Hologram"><svg viewBox="-104 -104 208 208" fill="currentColor" role="img" aria-label="Hologram"><g><circle cx="0.20" cy="-97.39" r="2.61"/><circle cx="-22.86" cy="-86.55" r="2.71"/><circle cx="22.54" cy="-86.32" r="2.81"/><circle cx="-0.03" cy="-76.01" r="2.71"/><circle cx="45.26" cy="-75.92" r="7.80"/><circle cx="-45.82" cy="-75.86" r="2.61"/><circle cx="68.34" cy="-65.13" r="7.70"/><circle cx="-68.83" cy="-65.00" r="2.61"/><circle cx="-22.91" cy="-64.90" r="2.61"/><circle cx="22.71" cy="-64.88" r="2.51"/><circle cx="91.24" cy="-54.34" r="2.61"/><circle cx="-45.94" cy="-54.25" r="7.83"/><circle cx="-91.17" cy="-54.19" r="2.71"/><circle cx="-0.03" cy="-54.19" r="2.71"/><circle cx="45.35" cy="-54.19" r="7.80"/><circle cx="-22.86" cy="-43.64" r="2.71"/><circle cx="22.71" cy="-43.49" r="2.51"/><circle cx="68.29" cy="-43.47" r="7.73"/><circle cx="-68.60" cy="-43.37" r="7.73"/><circle cx="-45.85" cy="-32.63" r="7.77"/><circle cx="45.36" cy="-32.60" r="7.80"/><circle cx="-91.26" cy="-32.55" r="2.71"/><circle cx="0.10" cy="-32.51" r="2.61"/><circle cx="91.24" cy="-32.51" r="2.61"/><circle cx="68.22" cy="-21.95" r="7.83"/><circle cx="22.67" cy="-21.84" r="7.87"/><circle cx="-22.86" cy="-21.82" r="2.71"/><circle cx="-68.57" cy="-21.80" r="7.80"/><circle cx="45.45" cy="-11.06" r="7.73"/><circle cx="-0.19" cy="-11.04" r="7.87"/><circle cx="91.35" cy="-11.01" r="2.81"/><circle cx="-91.54" cy="-10.97" r="2.51"/><circle cx="-45.87" cy="-10.87" r="7.73"/><circle cx="22.71" cy="-0.27" r="8.06"/><circle cx="-22.89" cy="-0.21" r="7.90"/><circle cx="68.28" cy="-0.15" r="7.87"/><circle cx="-68.62" cy="-0.11" r="8.00"/><circle cx="-0.06" cy="10.98" r="7.87"/><circle cx="45.54" cy="11.00" r="7.83"/><circle cx="-45.85" cy="11.02" r="7.77"/><circle cx="-91.26" cy="11.10" r="2.71"/><circle cx="91.24" cy="11.13" r="2.61"/><circle cx="22.71" cy="21.64" r="2.71"/><circle cx="-68.74" cy="21.66" r="7.87"/><circle cx="-22.86" cy="21.67" r="7.87"/><circle cx="68.28" cy="21.67" r="7.87"/><circle cx="-91.54" cy="32.46" r="2.61"/><circle cx="0.15" cy="32.46" r="2.71"/><circle cx="-45.72" cy="32.50" r="7.73"/><circle cx="45.54" cy="32.59" r="7.83"/><circle cx="91.35" cy="32.63" r="2.81"/><circle cx="-23.01" cy="43.23" r="2.61"/><circle cx="68.25" cy="43.31" r="7.83"/><circle cx="-68.71" cy="43.34" r="7.83"/><circle cx="22.71" cy="43.37" r="2.90"/><circle cx="91.39" cy="53.92" r="2.71"/><circle cx="45.48" cy="53.95" r="7.87"/><circle cx="-45.86" cy="53.97" r="7.80"/><circle cx="-91.34" cy="53.99" r="2.61"/><circle cx="0.20" cy="54.09" r="2.61"/><circle cx="-68.57" cy="64.90" r="7.80"/><circle cx="-22.86" cy="64.92" r="2.71"/><circle cx="68.28" cy="64.92" r="2.71"/><circle cx="22.54" cy="65.15" r="2.81"/><circle cx="-45.88" cy="75.56" r="7.80"/><circle cx="0.10" cy="75.62" r="2.61"/><circle cx="45.32" cy="75.62" r="2.61"/><circle cx="22.53" cy="86.47" r="2.71"/><circle cx="-22.86" cy="86.75" r="2.71"/><circle cx="-0.03" cy="97.29" r="2.71"/></g></svg><b>HOLOGRAM</b></div></div>
196
+
197
+ <!-- Machine-readable sign-in contract for AI agents — always in the DOM, even before scripts run, so an
198
+ agent can discover guest access by parsing the page. Read by window.HoloLogin.options. Mirrors the OS
199
+ greeter's #holo-agent-login. Guest = act now (no human); biometric = ask the person. -->
200
+ <script type="application/json" id="holo-agent-login">
201
+ {
202
+ "service": "Holo Messenger sign-in",
203
+ "audience": "ai-agent",
204
+ "summary": "Two ways in. Guest is instant and needs no human; the biometric needs the person.",
205
+ "options": [
206
+ {
207
+ "id": "guest",
208
+ "title": "Guest",
209
+ "persistent": false,
210
+ "requiresHuman": false,
211
+ "do": { "navigate": "?guest=1", "call": "window.HoloLogin.guest()" },
212
+ "result": "An instant, private, ephemeral guest session into the Hologram system (Holo Messenger). No biometric and no human needed. Nothing persists after the session ends."
213
+ },
214
+ {
215
+ "id": "biometric",
216
+ "title": "It’s me",
217
+ "persistent": true,
218
+ "requiresHuman": true,
219
+ "do": { "tap": "the 'It’s me' / 'Sign in' button", "then": "ask the person to approve with their device biometric (Windows Hello / Touch ID)" },
220
+ "result": "Signs in with this device's TEE to the operator's sovereign identity (the SAME κ as the OS). An agent cannot complete this alone."
221
+ }
222
+ ]
223
+ }
224
+ </script>
225
+ <script>
226
+ (function () {
227
+ // O2 (HOLO-SOVEREIGN-OFFLINE): storage durability has ONE owner — holo-persist.mjs (persist +
228
+ // estimate, called once, result RECORDED under holo.persist). Import trouble → the inline
229
+ // best-effort call below keeps today's behavior (fail-soft, old browsers included).
230
+ try { import("../../usr/lib/holo/holo-persist.mjs").then(function (m) { m.persistOnce(); }).catch(function () { try { navigator.storage.persist().catch(function(){}); } catch (e) {} }); }
231
+ catch (e) { try { if (navigator.storage && navigator.storage.persist) navigator.storage.persist().catch(function(){}); } catch (e2) {} }
232
+ // O5 (HOLO-SOVEREIGN-OFFLINE): registration hygiene — pre-O0.5 profiles carry workers registered at
233
+ // the OTHER deployment's scopes (the absolute-registrar era: "/", "/apps/holo-messenger/" on a /Q
234
+ // mount). Inert (deeper scopes win) but noise; sweep any registration whose scope is OUTSIDE this
235
+ // mount. On the root-copy deployment the mount IS "/", so every scope matches and nothing is touched.
236
+ try {
237
+ if ("serviceWorker" in navigator) {
238
+ var _mount = new URL("../../", location.href).pathname;
239
+ navigator.serviceWorker.getRegistrations().then(function (rs) {
240
+ rs.forEach(function (r) {
241
+ try { var s = new URL(r.scope).pathname; if (s.indexOf(_mount) !== 0) r.unregister().catch(function () {}); } catch (e) {}
242
+ });
243
+ }).catch(function () {});
244
+ }
245
+ } catch (e) {}
246
+ var panel = document.getElementById("holo-login-panel"); if (!panel) return;
247
+ var esc = function (s) { return String(s).replace(/[&<>"]/g, function (c) { return ({ "&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;" }[c]); }); };
248
+ var op = null; try { op = JSON.parse(localStorage.getItem("holo.lastOperator") || "null"); } catch (e) {}
249
+ // Paint the SHARED wallpaper behind the lock at first frame (the same one the desktop + home wear), so the
250
+ // sign-in is never a black void. A live-sim / none / plain falls back to the shipped default photo — the SAME
251
+ // default the home shows — so login and home always match. κ → content route; a path/url passes through.
252
+ try {
253
+ // APPEARANCE at 0 ms — the lock wears the SAME holo.theme.v1 row home wears: immersive (wallpaper),
254
+ // dark, or light. One data attribute flips the whole token set; flat modes never fetch the photo.
255
+ var _thm = {}; try { _thm = JSON.parse(localStorage.getItem("holo.theme.v1") || "{}") || {}; } catch (e) {}
256
+ var _mode = (_thm.immersive === false) ? (_thm.palette === "light" ? "light" : "dark") : "immersive";
257
+ var _lv = document.getElementById("holo-login");
258
+ if (_lv) _lv.setAttribute("data-appearance", _mode);
259
+ var _tw = _thm.wallpaper || "";
260
+ var _km = String(_tw).match(/^(sha256|blake3|sha512):([0-9a-f]+)$/i);
261
+ var _wp = _km ? ("/.holo/" + _km[1].toLowerCase() + "/" + _km[2]) : ((!_tw || _tw === "plain" || /^live:/i.test(_tw)) ? "" : _tw);
262
+ // MATCH THE HOME EXACTLY: the messenger home resolves its wallpaper as holo.theme.v1 → the messenger's own
263
+ // "holo-messenger/wallpaper-src" pick → the shipped default. The lock skipped the middle step, so a wallpaper
264
+ // set only via the messenger's picker showed on home but not here. Fold that same fallback in — one chain,
265
+ // so the sign-in glass and the desktop it lifts onto are never two different photos.
266
+ if (!_wp) { try { _wp = localStorage.getItem("holo-messenger/wallpaper-src") || ""; } catch (e) {} }
267
+ if (!_wp) _wp = "./_vendor/wallpaper-default.jpg";
268
+ var _wall = document.querySelector("#holo-login .hl-wall");
269
+ if (_wall && _mode === "immersive") _wall.style.backgroundImage = 'url("' + _wp + '")';
270
+ // PLYMOUTH BOOT SPLASH baseline (holo-plymouth.mjs owns the real player; this is the 0-ms frame). Once a
271
+ // boot style has been worn (holo.plymouth.v1 persisted), a cold open paints a dedicated BLACK layer with
272
+ // the sealed frame-0 synchronously — the machine boots like a machine — and the panel holds for one boot
273
+ // beat behind .hl-boot. The wallpaper underneath is NEVER touched: the module dissolves this layer into
274
+ // it (boot → your OS). Fail-open twice over: the module drops the layer when its canvas is live, and if
275
+ // the module never arrives the 5s timer lifts .hl-boot and CSS fades the layer to the wallpaper lock.
276
+ try {
277
+ // S2 zero-drift: circle_hud frame-0, byte-identical to holo-plymouth.mjs DEFAULT_FF (witness-gated).
278
+ var _DFF = "data:image/png;base64,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";
279
+ var _pl = JSON.parse(localStorage.getItem("holo.plymouth.v1") || "null");
280
+ if (!_pl) _pl = { v: 1, on: true, theme: "circle_hud", firstFrame: _DFF, ffKeyed: true }; // first-EVER visit boots like an OS, at 0 ms
281
+ else if (_pl.on && !_pl.firstFrame && _pl.theme === "circle_hud") { _pl.firstFrame = _DFF; _pl.ffKeyed = true; } // default theme, frame not yet sealed
282
+ // GROUND LAW: the boot ground and the login wall are ONE token (--boot-ground) — they can never
283
+ // drift. Only a seal stamped ffKeyed (chroma-keyed, black→air, holo-plymouth sealFirstFrame) — or
284
+ // the embedded keyed default — may paint over the ground; a LEGACY unkeyed seal would lay its
285
+ // baked-black square over the charcoal (the "boot darker than login" mismatch), so it falls back.
286
+ if (_pl.firstFrame && !_pl.ffKeyed && _pl.firstFrame !== _DFF) _pl.firstFrame = (_pl.theme === "circle_hud") ? _DFF : null;
287
+ if (_pl && _pl.on && _wall) {
288
+ // First boot for this theme (frames not yet sealed to κ) streams from the pack CDN — warm the
289
+ // DNS/TLS handshake NOW so the emblem's first frames land sooner. Warm boots (sealed) skip this.
290
+ try { if (!localStorage.getItem("holo.plymouth.frames:" + _pl.theme)) {
291
+ var _pc = document.createElement("link"); _pc.rel = "preconnect"; _pc.href = "https://raw.githubusercontent.com"; _pc.crossOrigin = "anonymous"; document.head.appendChild(_pc);
292
+ // SUPERCOMPUTER BOOT: the DEFAULT theme animates from the same-origin frames.pack (holo-plymouth
293
+ // loadPack) — start that ONE fetch NOW, at HTML parse, instead of after the module chain evaluates.
294
+ // By the time plymouth asks, the bytes are already in the HTTP cache: the emblem MOVES at first sight.
295
+ if (_pl.theme === "circle_hud") { var _pk = document.createElement("link"); _pk.rel = "preload"; _pk.as = "fetch"; _pk.crossOrigin = "anonymous"; _pk.href = "../../usr/share/plymouth/circle_hud/frames.pack"; _pk.fetchPriority = "high"; document.head.appendChild(_pk); }
296
+ } } catch (e) {}
297
+ var _base = document.createElement("div");
298
+ _base.id = "hl-plymouth-base";
299
+ // dead-centre, sized like the module's boot HERO pose (1.5× natural capped at 95vmin — exact for
300
+ // the default theme's 533px frame), so the canvas takes over with zero visible jump
301
+ _base.style.cssText = "position:fixed;inset:0;z-index:0;pointer-events:none;background:var(--boot-ground,#1f1f1e) center 50% no-repeat;transition:opacity .9s ease";
302
+ if (_pl.theme === "circle_hud") _base.style.backgroundSize = "min(800px,95vmin)";
303
+ if (_pl.firstFrame) _base.style.backgroundImage = 'url("' + _pl.firstFrame + '")';
304
+ _wall.parentNode.insertBefore(_base, _wall.nextSibling);
305
+ var _ov = document.getElementById("holo-login");
306
+ // BOOT HERO: hold the whole login behind .hl-boot — holo-plymouth OWNS the beat (5s hero, one
307
+ // coordinated reveal) and cancels this fallback; the timer only lifts the gate if the module
308
+ // NEVER arrives. Runs on every device — the hold is stillness, not motion.
309
+ try { performance.mark("holo:ceremony:brand-paint"); } catch (e) {}
310
+ if (_ov) { _ov.classList.add("hl-boot"); window.__hlBootT0 = Date.now(); window.__hlBootFallback = setTimeout(function () { try { _ov.classList.remove("hl-boot"); } catch (e) {} }, 6500); }
311
+ }
312
+ } catch (e) {}
313
+ } catch (e) {}
314
+ var fp = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round"><path d="M5.5 11a6.5 6.5 0 0 1 13 0"/><path d="M8.5 11a3.5 3.5 0 0 1 7 0v2.6"/><path d="M12 11v4.4"/><path d="M8.6 14.2V16"/><path d="M15.4 15.4V18"/></svg>';
315
+ var person = '<svg viewBox="0 0 24 24" fill="currentColor"><circle cx="12" cy="8.4" r="4.1"/><path d="M3.8 20.4a8.2 8.2 0 0 1 16.4 0z"/></svg>';
316
+ var avatar, name, label;
317
+ if (op && op.label) {
318
+ var hue = (op.hue != null ? op.hue : 210) | 0;
319
+ var ini = op.label.trim().split(/\s+/).map(function (w) { return w[0]; }).filter(Boolean).slice(0, 2).join("").toUpperCase() || "·";
320
+ avatar = '<div class="hl-avatar" style="background:linear-gradient(140deg,hsl(' + hue + ' 52% 46%),hsl(' + (hue + 26) + ' 52% 46%))">' + esc(ini) + '</div>';
321
+ name = esc(op.label); label = "It’s me";
322
+ } else {
323
+ avatar = '<div class="hl-avatar" style="background:linear-gradient(140deg,#5b6b86,#3a455c)">' + person + '</div>';
324
+ name = "Welcome"; label = "Sign in";
325
+ }
326
+ // your name IS the sign-in — one control carries who you are and the act of proving it (no heading)
327
+ var btnText = (op && op.label) ? name : "Sign in";
328
+ panel.innerHTML = avatar + '<div class="hl-auth"><button class="hl-bio" id="hl-baseline">' + fp + '<span>' + btnText + '</span></button><div class="status"></div></div>';
329
+ var b = document.getElementById("hl-baseline"); if (b) b.onclick = function () { b.disabled = true; location.reload(); };
330
+ })();
331
+ </script>
332
+
333
+ <!-- LEAN BOOT — the sovereign gate + boot splash mount NOW, in PARALLEL with the heavy chat bundle below,
334
+ so the emblem animates and the panel is ready without waiting on the messenger's code to download or
335
+ parse. The login chain (login→signin→plymouth) is small; the preload scanner fetches it alongside the
336
+ big bundle, and this module evaluates first, so the gate is up before the messenger even starts. The
337
+ resolved auth promise is handed to boot() below. Fail-SOFT: a gate that can't load drops the overlay
338
+ and boot uses its device-stable identity, never bricked. -->
339
+ <script type="module">
340
+ try {
341
+ const { mountLogin } = await import("./holo-messenger-login.mjs?v=signin1");
342
+ window.__holoAuthP = mountLogin({ root: document.getElementById("root") }); // the PROMISE (resolves on sign-in), not an awaited value
343
+ } catch (e) {
344
+ try { const ov = document.getElementById("holo-login"); if (ov) ov.remove(); } catch {}
345
+ window.__holoAuthP = Promise.resolve(null);
346
+ console.warn("[login] gate unavailable, booting device-stable identity:", e);
347
+ }
348
+ </script>
349
+
350
+ <!-- SUPERCOMPUTER RETURN + OFFLINE: after the ceremony settles, seal the ENTIRE boot sequence — this
351
+ document, the login-chain modules, frames.pack, the wallpaper — into a versioned Cache-Storage set
352
+ ("holo-boot-<stamp>", stamp = the SIGNED release pointer). root-sw.js answers cache-first from it at
353
+ the "/" scope holo-sw cannot reach, so a RETURNING operator boots with ZERO network and the same
354
+ bytes — document included — answer fully OFFLINE. Idle-time, fail-soft, ?nosw=1 skips (dev). One
355
+ release = one sealed set; superseded sets are purged; re-visits with a sealed set do zero work. -->
356
+ <script>
357
+ (function () {
358
+ try {
359
+ if (!("caches" in window)) return;
360
+ if (new URLSearchParams(location.search).get("nosw") === "1") return;
361
+ var seal = function () { setTimeout(async function () {
362
+ try {
363
+ // site root: app.html lives at <root>/apps/holo-messenger/; a root copy (index.html) is <root>/ itself
364
+ var base = new URL(".", location.origin + location.pathname.replace(/apps\/holo-messenger\/app\.html$/, ""));
365
+ var stamp = "live";
366
+ try { var rj = await fetch(new URL("release.json", base), { cache: "no-store" });
367
+ if (rj.ok) { var r = await rj.json(); var pay = r["holstr:payload"] || {};
368
+ // the signed strand pointer: holstr:seq + the shell κ — one stamp per sealed release
369
+ stamp = String((r["holstr:seq"] != null ? "s" + r["holstr:seq"] : "") + String(pay.shell || r.seq || r.root || "").slice(0, 12) || "live").replace(/[^a-zA-Z0-9]/g, "").slice(0, 24) || "live"; } } catch (e) {}
370
+ var name = "holo-boot-" + stamp;
371
+ try { var ks = await caches.keys(); for (var i = 0; i < ks.length; i++) if (ks[i].indexOf("holo-boot-") === 0 && ks[i] !== name) await caches.delete(ks[i]); } catch (e) {}
372
+ // O3/O4 (HOLO-SOVEREIGN-OFFLINE): the AUTOMATIC PIN runs on EVERY online visit — BEFORE the
373
+ // already-sealed early-return below, or a returning user would never resume an interrupted pin
374
+ // and never receive a κ-delta update. Cheap when nothing changed (one no-store head fetch →
375
+ // ledger says done). releaseBoot is the only head acceptor; the pin refuses to run ahead of it.
376
+ // Fail-soft: any trouble and this device behaves exactly as before the pin existed.
377
+ try { import("../../usr/lib/holo/holo-pin.mjs").then(function (m) { return m.ensurePinned({}); }).then(function (s) { try { console.info("[holo-pin]", JSON.stringify(s)); } catch (e) {} }).catch(function () {}); } catch (e) {}
378
+ if (await caches.has(name)) return; // this release is already sealed — zero work
379
+ var cache = await caches.open(name);
380
+ var SET = [
381
+ "", "index.html", "apps/holo-messenger/app.html",
382
+ // the DOOR's own closure — the root navigation must RESOLVE offline, not just paint: the door
383
+ // module + its pure resolver + the inline name plane + the signed index it reads (release.json
384
+ // is answered network-FIRST by root-sw, so the sealed copy serves only when the radio is dead).
385
+ "root-door.mjs", "holo-root-resolver.mjs", "holo-resolve-view.mjs",
386
+ "release.json", "apps/index.jsonld", "apps-blake3.json",
387
+ "usr/lib/holo/holo-app-index.mjs",
388
+ "apps/holo-messenger/holo-messenger-login.mjs?v=signin1",
389
+ "usr/lib/holo/holo-signin.mjs", "usr/lib/holo/holo-webauthn.mjs", "usr/lib/holo/holo-sso.mjs",
390
+ "usr/lib/holo/holo-roam.mjs", "usr/lib/holo/holo-pair.mjs", "usr/lib/holo/holo-plymouth.mjs",
391
+ "usr/lib/holo/holo-device-tier.mjs", "usr/lib/holo/holo-manifesto.mjs",
392
+ "usr/share/plymouth/circle_hud/frames.pack",
393
+ "_shared/holo-widgets.js",
394
+ "apps/holo-messenger/_vendor/wallpaper-default.jpg",
395
+ // the ENTRY closure — a sealed gate must also OPEN offline: guest needs only holo-identity
396
+ // (zero static imports); the returning unlock adds the small login/session/crypto leaves.
397
+ "usr/lib/holo/holo-identity.mjs", "usr/lib/holo/holo-login.mjs", "usr/lib/holo/holo-session.mjs",
398
+ "usr/lib/holo/holo-uor.mjs", "usr/lib/holo/holo-blake3.mjs", "usr/lib/holo/holo-pqc.mjs",
399
+ "usr/lib/holo/holo-portrait.mjs",
400
+ // O2 (HOLO-SOVEREIGN-OFFLINE): the device-store rung + its IDB backend + the ONE persist
401
+ // owner — sealed so the offline SW can load the rung itself with the radio dead.
402
+ "usr/lib/holo/holo-store-rung.mjs", "usr/lib/holo/holo-persist.mjs",
403
+ "usr/lib/holo/holo-forge/holo-kstore.mjs", "usr/lib/holo/holo-evict-rescue.mjs",
404
+ ];
405
+ for (var j = 0; j < SET.length; j++) {
406
+ try {
407
+ var u = new URL(SET[j], base).toString();
408
+ var res = await fetch(u, { cache: "no-cache" }); // revalidated — never seal a stale HTTP copy
409
+ if (res && res.ok) await cache.put(u, res);
410
+ } catch (e) {}
411
+ }
412
+ } catch (e) {}
413
+ }, 12000); }; // well past the hero + arrival — the boot's own network is long quiet by then
414
+ if (document.readyState === "complete") seal(); else addEventListener("load", seal);
415
+ } catch (e) {}
416
+ })();
417
+ </script>
418
+
419
+ <!-- NATIVE MEDIA — the real OS media stack, loaded into the top page so the messenger rail delivers the exact
420
+ native experience: window.HoloVinyl = the "NOW PLAYING" pill (preloaded Ben Böhmer "Begin Again"), streamed
421
+ through the same-origin /sc/* routes the dev server now answers (holo-sc-route.mjs → yt-dlp). holo-fx.js
422
+ drives the live equalizer; holo-vinyl lazily pulls holo-audio.js for the Hi-Fi chain. Each self-guards on the
423
+ widgets/dock runtime, so they run standalone here. Classic defer → they set their globals before the bundle boots. -->
424
+ <script src="../../usr/lib/holo/holo-fx.js" defer></script>
425
+ <script src="../../usr/lib/holo/holo-vinyl.js" defer></script>
426
+ <script src="../../usr/lib/holo/holo-video.js" defer></script>
427
+
428
+ <script type="module">
429
+ // ?v=N - bump on each deploy so the browser can never serve a stale cached bundle (the cause of a frozen/old build).
430
+ import "./_vendor/ui/chat-ui.bundle.js?v=7b71a9a119c2"; // → window.HoloMessengerUI
431
+ // Opt-in: render the WhatsApp UX from STREAMING shadcn κ-components instead of the chatscope bundle.
432
+ // Lazy — the module loads ONLY when opted in (?ui=shadcn or holo.ui.shadcn=1); the default boot never
433
+ // parses it. Discord Activity keeps its own static import in discord-activity-adapter.mjs.
434
+ try {
435
+ if (new URLSearchParams(location.search).get("ui") === "shadcn" || localStorage.getItem("holo.ui.shadcn") === "1")
436
+ await import("./messenger-shadcn-ui.mjs");
437
+ } catch {}
438
+ import { boot } from "./holo-messenger-app.mjs?v=0acdc077312e";
439
+
440
+ const root = document.getElementById("root");
441
+ // TEE GATE + SPECULATIVE WARM PAINT, one motion. The gate already mounted (parallel module above); it
442
+ // renders the lock + runs the biometric, while boot() starts IMMEDIATELY behind the glass, painting the
443
+ // messenger from the local κ-snapshot during the human's look-and-tap. boot awaits the auth ONLY at its
444
+ // identity step, so the biometric overlaps an already-live UI and loginSucceeded just unfogs onto it
445
+ // (tap → interactive ≈ one frame). Fail-CLOSED: nothing confidential is interactable until the glass lifts.
446
+ const authP = window.__holoAuthP || Promise.resolve(null); // the promise the parallel gate module produced
447
+ boot(root, authP).catch((e) => {
448
+ document.body.innerHTML = "<pre style='color:#f88;padding:16px;white-space:pre-wrap'>boot error: " + (e && e.stack || e) + "</pre>";
449
+ });
450
+
451
+ // Intelligent Inbox (holo-mail) - additive, OFF by default. Enable per-operator: localStorage["holo.mail.enabled"]="1".
452
+ import("./mail/holo-mail-attach.mjs").catch(() => {});
453
+ // HoloChat, woven into THIS messenger — one canonical surface. Enable: localStorage["holo.chat.enabled"]="1" or ?chat=1.
454
+ import("./holo-messenger-weave.mjs").catch(() => {});
455
+ // Colour re-tone picker — colours only, all features intact. Gated: ?skins=1 or localStorage["holo.skins"]="1".
456
+ import("./messenger-skin.mjs?v=970e7ec3151a").catch(() => {});
457
+ // Asset cache (service worker) — repeat opens serve the shell NETWORK-FREE. Escape hatch: open with ?nosw=1.
458
+ // RELATIVE by design (HOLO-SOVEREIGN-OFFLINE / O0.5 root cause): an ABSOLUTE "/apps/…" here resolves against
459
+ // the DOCUMENT, so on the github.io /Q mount it hits the ROOT-COPY deployment and registers holo-sw at the
460
+ // WRONG scope (never /Q) — the O0 outage. "./" resolves against app.html's own folder on BOTH mounts (OS
461
+ // root AND /Q), so the registration always lands on the mount actually serving this page. Do not re-absolutise.
462
+ import("./messenger-sw-register.mjs").catch(() => {});
463
+ // Q SUMMON — tap the home orb and the canvas MORPHS into Q (View Transitions pair the orb corner→face),
464
+ // Q greets OUT LOUD at the tap (the tap IS the autoplay gesture), the HD voice + Q's BitNet brain pre-warm
465
+ // while home idles (window.HoloQ.warm — standalone-parity TTFT), and every Q-thread message is sealed to a
466
+ // BLAKE3 κ chain (did:holo:blake3, hash-linked, verified on load). Additive + fail-soft.
467
+ import("./q-summon.mjs?v=18").catch(() => {});
468
+ // Q LIVE CALL — tap Call in the hero → realtime voice conversation (createQLive: streaming voice,
469
+ // hands-free turns, barge-in). Additive + fail-soft: no mic/WebGPU → a clear message, chat still works.
470
+ import("./q-live-hero.mjs?v=4").catch(() => {});
471
+ // POWER & SESSION — one power button at the very bottom of the rail. Tap → a calm sheet in the sign-in's own
472
+ // teal vocabulary: live session uptime + who you are + Lock & Sign Out (window.holo.signOut → the clean gate,
473
+ // box secured). Additive + fail-soft: no rail / no signOut → the control just doesn't appear.
474
+ import("./holo-power-session.mjs?v=3").catch(() => {});
475
+ // KEYBOARD LAYER — the native CEF browser's shortcut system, ported to the web (DESKTOP MODE ONLY): the
476
+ // ambient "hold-Ctrl" hint dot, Ctrl K spotlight, Ctrl ⇧ P command palette, ? cheat sheet, Esc to dismiss,
477
+ // Ctrl ]/[ to cycle conversations — one dependency-free engine (holo-keys), every action content-addressed.
478
+ // Self-gating: mounts nothing on touch/narrow, inside the native shell, or when embedded. Fail-soft.
479
+ import("./holo-messenger-keys.mjs?v=5").catch(() => {});
480
+ // HOLO DIRECT — the NATIVE holospace↔holospace sealed chat (dual-path engine: P2P WebRtcLink fast-path via
481
+ // sealed-SDP rendezvous over the blind mailbox, mailbox fallback; TOFU + safety number + key-change). One 🔐
482
+ // rail button; the 3 MB spine wasm loads on FIRST use only (cold boot pays nothing). Additive + fail-soft.
483
+ import("./holo-direct-mount.mjs?v=7").catch(() => {});
484
+ // IMMERSIVE VIDEO — chromeless YouTube embed + a hover-only Holo control bar (play/pause · seek · time ·
485
+ // fullscreen). Additive + fail-soft.
486
+ import("./holo-video-immersive.mjs?v=1").catch(() => {});
487
+ </script>
488
+ </body></html>
sha256/1e5edeebc42921c19d60f9a4875040b8eee274d25262e27e02843bc1be6c2ad2 ADDED
@@ -0,0 +1,532 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!doctype html>
2
+ <html lang="en"><head>
3
+ <meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1,viewport-fit=cover,interactive-widget=resizes-content">
4
+ <title>HOLOGRAM</title>
5
+ <link rel="icon" type="image/svg+xml" href="./favicon.svg">
6
+ <!-- AI agents: guest access into the Hologram system is one call away — window.HoloLogin.guest() or open
7
+ ?guest=1. Full machine-readable contract in script#holo-agent-login (JSON). Mirrors holo://login. -->
8
+ <meta name="ai-agent-login" content="Guest mode: act now with window.HoloLogin.guest() or ?guest=1 (ephemeral, no human). Biometric ('It's me') needs the person. See script#holo-agent-login.">
9
+
10
+ <!-- PWA install — a self-contained, subpath-safe manifest (relative paths, so it installs correctly whether
11
+ served at the origin root or under /Q/). theme-color + the apple-* tags make iOS treat it as a real
12
+ home-screen app (fullscreen, dark status bar) since iOS ignores the manifest's display field. -->
13
+ <link rel="manifest" href="/apps/holo-messenger/manifest.webmanifest">
14
+ <meta name="theme-color" content="#1f1f1e">
15
+ <meta name="mobile-web-app-capable" content="yes">
16
+ <meta name="apple-mobile-web-app-capable" content="yes">
17
+ <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
18
+ <meta name="apple-mobile-web-app-title" content="Hologram">
19
+ <meta name="description" content="A private, end-to-end encrypted space. One link — no signup, no server. Tap to connect.">
20
+ <meta property="og:type" content="website">
21
+ <meta property="og:site_name" content="Hologram">
22
+ <meta property="og:title" content="You're invited to Hologram">
23
+ <meta property="og:description" content="A private, end-to-end encrypted space. One link — no signup, no server. Tap to connect.">
24
+ <meta property="og:image" content="https://hologram-technologies.github.io/Q/apps/holo-messenger/og.jpg">
25
+ <meta property="og:image:width" content="1200">
26
+ <meta property="og:image:height" content="630">
27
+ <meta property="og:url" content="https://hologram-technologies.github.io/Q/apps/holo-messenger/app.html">
28
+ <meta name="twitter:card" content="summary_large_image">
29
+ <meta name="twitter:title" content="You're invited to Hologram">
30
+ <meta name="twitter:description" content="A private, end-to-end encrypted space. One link — no signup, no server. Tap to connect.">
31
+ <meta name="twitter:image" content="https://hologram-technologies.github.io/Q/apps/holo-messenger/og.jpg">
32
+ <link rel="apple-touch-icon" href="./icon.svg">
33
+
34
+ <link rel="stylesheet" href="./_vendor/ui/chat-ui.bundle.css?v=76f53266c090">
35
+ <!-- BOOT GROUND — the ONE source of truth for the boot/login darkness. Every boot-time surface reads
36
+ var(--boot-ground): the body pre-paint, the login --wall, the plymouth splash layer + its base, the
37
+ signin wall, and the power veil (all with a matching literal fallback for bare surfaces without this
38
+ token). To recolor the whole cold-open, change THIS hex and nothing else. Defined in the head so it
39
+ resolves at the 0-ms frame, before any stylesheet or module. (The theme-color meta above is a static
40
+ duplicate — meta tags can't read a var; keep it in sync by hand on the rare occasion this changes.) -->
41
+ <style>:root{--boot-ground:#1f1f1e}html,body{height:100%;margin:0;background:var(--boot-ground);color-scheme:dark}#root{height:100vh}</style>
42
+ <!-- PRISM PRE-PAINT (HOLO-PLAYGROUND-LIVE) — the operator's design tokens land at the 0-ms frame, the
43
+ same discipline as --boot-ground above: read the saved token map, stamp ONE :root override before
44
+ any stylesheet applies, so a personalized shell never flashes the base look. The durable half (the
45
+ κ-atom stylesheet the SW materializes) covers the network layer; this covers the first frame. The
46
+ full door (verbs, mint, prism) loads LAZILY via window.__holoPrismDoor — one import, memoized. -->
47
+ <script>
48
+ try{var _pt=JSON.parse(localStorage.getItem("holo.prism.tokens.v1")||"{}"),_pe=Object.entries(_pt).filter(function(e){return/^--holo-[\w-]{1,60}$/.test(e[0])&&/^[#a-zA-Z0-9 ,.%()\-]{1,120}$/.test(String(e[1]))});
49
+ if(_pe.length){var _ps=document.createElement("style");_ps.id="holo-prism-live";_ps.textContent=":root{"+_pe.map(function(e){return e[0]+":"+e[1]}).join(";")+"}";document.head.appendChild(_ps);}}catch(e){}
50
+ window.__holoPrismDoor=function(){var mb=new URL("../../",location.href).href;return(window.__holoPrismDoorP||(window.__holoPrismDoorP=import(new URL("usr/lib/holo/holo-prism-tokens.mjs",mb).href).then(function(m){return m.openTokenDoor({mountBase:mb})})));};
51
+ </script>
52
+ <!-- IMMERSIVE CHAT — the open conversation wears the SAME wallpaper as home. The home wallpaper is already
53
+ painted ONCE, edge-to-edge, by the fixed .holo-backdrop canvas (the shared holo.theme.v1 wallpaper, run
54
+ through the OS super-res lens). The thread pane used to sit on an opaque #0b141a that covered it; here we
55
+ make the pane's surfaces translucent so that ONE backdrop shows straight through — so the chat is literally
56
+ the same wallpaper as home, live-synced with it, zero extra fetch. Gated on .holo-backdrop.ready via :has()
57
+ so it engages ONLY when a wallpaper is actually shown (a flat dark/light home ⇒ backdrop never turns ready
58
+ ⇒ the chat keeps its solid default, still matching home). Scoped to the default look (html:not([data-skin]))
59
+ so the opt-in skins / theatre keep their own immersive treatment untouched. -->
60
+ <style id="holo-chat-immersive">
61
+ /* let the shared backdrop show through the whole right pane */
62
+ html:not([data-skin]) .holo-wa-root:has(.holo-backdrop.ready) .cs-chat-container{background:transparent!important}
63
+ /* one clean legibility scrim over the wallpaper (a touch darker toward the composer) — the wallpaper stays vivid */
64
+ html:not([data-skin]) .holo-wa-root:has(.holo-backdrop.ready) .cs-message-list{
65
+ background:linear-gradient(180deg,rgba(31,31,30,.30) 0%,rgba(31,31,30,.28) 52%,rgba(31,31,30,.50) 100%)!important;}
66
+ /* drop the pane's own in-place photo copy + aurora glow so nothing muddies the single backdrop */
67
+ html:not([data-skin]) .holo-wa-root:has(.holo-backdrop.ready) .cs-message-list::before,
68
+ html:not([data-skin]) .holo-wa-root:has(.holo-backdrop.ready) .cs-message-list::after{display:none!important}
69
+ /* header + composer become glass so the wallpaper flows unbroken top-to-bottom behind the thread */
70
+ html:not([data-skin]) .holo-wa-root:has(.holo-backdrop.ready) .cs-conversation-header{
71
+ background:rgba(38,38,36,.46)!important;-webkit-backdrop-filter:blur(22px) saturate(1.3);backdrop-filter:blur(22px) saturate(1.3);
72
+ border-bottom:1px solid rgba(255,255,255,.07)!important;}
73
+ html:not([data-skin]) .holo-wa-root:has(.holo-backdrop.ready) .cs-message-input{
74
+ background:rgba(38,38,36,.42)!important;-webkit-backdrop-filter:blur(22px) saturate(1.2);backdrop-filter:blur(22px) saturate(1.2);
75
+ border-top:1px solid rgba(255,255,255,.07)!important;}
76
+ html:not([data-skin]) .holo-wa-root:has(.holo-backdrop.ready) .cs-input-toolbox{background:transparent!important}
77
+ </style>
78
+ <script>
79
+ /* preload the shipped default wallpaper ONLY when no custom pick exists — with a custom wallpaper the
80
+ default is never painted and a static preload is dead weight (Chrome: "preloaded but not used"). */
81
+ try { var _pt = JSON.parse(localStorage.getItem("holo.theme.v1") || "{}") || {};
82
+ if (!_pt.wallpaper && !localStorage.getItem("holo-messenger/wallpaper-src")) {
83
+ var _pl = document.createElement("link"); _pl.rel = "preload"; _pl.as = "image";
84
+ _pl.href = "./_vendor/wallpaper-default.jpg"; _pl.fetchPriority = "high"; document.head.appendChild(_pl);
85
+ } } catch (e) {}
86
+ </script>
87
+ <!-- ARRIVAL (HOLO-ARRIVAL A1): the home widget runtime is the SETTLED FRAME the defog lands on — pull its
88
+ bytes during the boot hero (5s of idle network) so the canvas is composed by the time the glass clears,
89
+ instead of popping in up to 2.6s after. -->
90
+ <link rel="preload" as="script" href="../../_shared/holo-widgets.js">
91
+ <!-- SUPERCOMPUTER BOOT (zero waterfall): the login chain (login→signin→{webauthn,sso,roam→pair}→plymouth)
92
+ is a 4-hop SERIAL module waterfall without hints — the emblem cannot MOVE until the last hop lands
93
+ (~4 RTTs). modulepreload fetches + compiles the whole chain in ONE parallel round, so the splash is
94
+ alive roughly one RTT after HTML. plymouth/device-tier are dynamic imports the scanner cannot see. -->
95
+ <link rel="modulepreload" href="./holo-messenger-login.mjs?v=signin1">
96
+ <link rel="modulepreload" href="../../usr/lib/holo/holo-signin.mjs">
97
+ <link rel="modulepreload" href="../../usr/lib/holo/holo-webauthn.mjs">
98
+ <link rel="modulepreload" href="../../usr/lib/holo/holo-sso.mjs">
99
+ <link rel="modulepreload" href="../../usr/lib/holo/holo-roam.mjs">
100
+ <link rel="modulepreload" href="../../usr/lib/holo/holo-pair.mjs">
101
+ <link rel="modulepreload" href="../../usr/lib/holo/holo-plymouth.mjs">
102
+ <link rel="modulepreload" href="../../usr/lib/holo/holo-device-tier.mjs">
103
+ <!-- ── TEE SIGN-IN GATE (holo-messenger-login.mjs) ─────────────────────────────────────────────────
104
+ A full-bleed lock overlay above #root, IDENTICAL to the OS native CEF greeter — the messenger does
105
+ not paint until a sovereign identity is established (Windows Hello / Touch ID via holo-webauthn →
106
+ holo-login κ). This is the never-blank baseline first-paint; the module ENHANCES it (real biometric
107
+ wiring). φ-proportioned, black-on-teal, glass-unfog on success — the same vocabulary as holo://login. -->
108
+ <style>
109
+ #holo-login{position:fixed;inset:0;z-index:2147483000;color:var(--ink);
110
+ font-family:"Segoe UI",system-ui,-apple-system,"Oxygen",sans-serif;--phi:1.618;
111
+ --u:clamp(17px,1.8vmin,20px);--g1:calc(var(--u)*1.618);--g2:calc(var(--u)*2.618);
112
+ --avatar:clamp(96px,calc(var(--u)*6.854),128px);--field:min(86vw,calc(var(--avatar)*2.618));
113
+ --accent:#7defc9;--accent-2:#34d3a6;
114
+ /* ONE appearance token set — every element reads these; [data-appearance] flips them.
115
+ immersive (default) = the home wallpaper behind soft-shadowed ink · dark/light = flat, shadowless, high contrast */
116
+ --ink:#f4f7fc;--ink-dim:rgba(231,237,250,.82);--status:#c4f3e2;
117
+ --shadow:0 2px 18px rgba(0,0,0,.45);--wall:var(--boot-ground,#1f1f1e);
118
+ --glass:rgba(10,14,20,.42);--glass-border:rgba(255,255,255,.14);--glass-ink:rgba(231,237,250,.8);
119
+ --sheet:rgba(8,12,18,.94);--muted:#8b949e;--link:#58a6ff;--field-bg:rgba(255,255,255,.09);--field-border:rgba(255,255,255,.22);}
120
+ #holo-login[data-appearance="dark"]{--shadow:none;}
121
+ #holo-login[data-appearance="light"]{--ink:#0b1220;--ink-dim:rgba(11,18,32,.74);--status:#0e6f5c;
122
+ --shadow:none;--wall:#eef1f6;
123
+ --glass:rgba(255,255,255,.66);--glass-border:rgba(11,18,32,.18);--glass-ink:rgba(11,18,32,.8);
124
+ --sheet:rgba(250,252,255,.97);--muted:#5a6572;--link:#0969da;--field-bg:rgba(11,18,32,.06);--field-border:rgba(11,18,32,.22);}
125
+ #holo-login *{box-sizing:border-box;}
126
+ #holo-login .hl-wall{position:fixed;inset:0;z-index:0;background:var(--wall) center/cover no-repeat;}
127
+ /* the wallpaper shows EXACTLY as the home screen wears it — no blur, no scrim. The layer stays only as
128
+ the unfog animation target; text/buttons carry their own shadows for legibility. */
129
+ #holo-login .hl-frost{position:fixed;inset:0;z-index:1;background:transparent;pointer-events:none;}
130
+ /* THE BRAND AT FRAME ZERO (HOLO-BOOT-CEREMONY B1): H mark + wordmark, bottom-centre golden band, painted
131
+ synchronously with the black — inline bytes, zero fetches, never blinks across the whole ceremony. */
132
+ #holo-login .hl-brand{position:fixed;left:50%;transform:translateX(-50%);bottom:max(26px,env(safe-area-inset-bottom));z-index:3;
133
+ pointer-events:none;display:inline-flex;align-items:center;gap:clamp(11px,1.4vw,16px);color:var(--ink);}
134
+ #holo-login .hl-brand svg{width:clamp(36px,4.2vw,44px);height:clamp(36px,4.2vw,44px);flex:0 0 auto;filter:drop-shadow(var(--shadow));shape-rendering:geometricPrecision;}
135
+ #holo-login .hl-brand b{font:600 clamp(24px,3.6vw,36px)/1 "Bahnschrift","DIN Alternate","DIN Next","Roboto Condensed","Segoe UI",system-ui,sans-serif;letter-spacing:.26em;text-transform:uppercase;padding-left:.26em;color:var(--ink-dim);text-shadow:var(--shadow);-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;text-rendering:geometricPrecision;}
136
+ /* GOLDEN COMPOSITION: the identity + actions sit on the LOWER golden line (61.8vh); the hero emblem grows
137
+ upward to fill the upper golden-major section, its centre landing on the UPPER golden line (38.2vh) — both
138
+ golden lines expressed at once. All gaps are already φ-scaled (--g1/--g2), so the column derives from φ. */
139
+ #holo-login .hl-lock{position:fixed;inset:0;z-index:2;display:grid;justify-items:center;align-items:start;
140
+ padding:calc(61.8vh - var(--avatar)/2) var(--g2) var(--g2);
141
+ padding-top:calc(61.8dvh - var(--avatar)/2);} /* dvh: the golden line holds while mobile URL bars collapse */
142
+ /* Short screens AND phones (narrow → the square emblem is width-bound, can't fill the tall golden major)
143
+ lift the identity so the actions don't sit too low and the top isn't left empty. */
144
+ @media (max-height:620px),(max-width:500px){#holo-login .hl-lock{padding-top:calc(56vh - var(--avatar)/2);padding-top:calc(56dvh - var(--avatar)/2);}}
145
+ #holo-login .hl-panel{display:flex;flex-direction:column;align-items:center;text-align:center;width:var(--field);max-width:92vw;}
146
+ #holo-login .hl-avatar{width:var(--avatar);height:var(--avatar);border-radius:50%;display:grid;place-items:center;color:#fff;
147
+ font-size:calc(var(--avatar)*.37);font-weight:600;letter-spacing:.02em;user-select:none;
148
+ box-shadow:0 .5em 1.5em rgba(0,0,0,.35),inset 0 0 0 1.5px rgba(255,255,255,.38);}
149
+ #holo-login .hl-avatar svg{width:62%;height:62%;opacity:.92;}
150
+ #holo-login .hl-name{margin:var(--g1) 0 0;font-size:calc(var(--u)*1.12);font-weight:600;letter-spacing:.01em;line-height:1.15;text-shadow:var(--shadow);}
151
+ #holo-login .hl-auth{margin-top:var(--u);display:flex;flex-direction:column;align-items:center;gap:var(--u);width:100%;}
152
+ /* THE ONE IDENTITY CONTROL — your name IS the sign-in: fingerprint + name, one sharp object. While the
153
+ enclave checks you it SCANS (pure CSS on :disabled — a light sweep, the biometric moment made visible). */
154
+ #holo-login .hl-bio{position:relative;overflow:hidden;width:100%;min-height:max(var(--g2),52px);border:0;border-radius:calc(var(--u)*.7);cursor:pointer;
155
+ display:inline-flex;align-items:center;justify-content:center;gap:calc(var(--u)*.55);font-size:var(--u);font-weight:600;color:#06140f;
156
+ background:linear-gradient(180deg,#38d9ab,#2cc79b);box-shadow:inset 0 1px 0 rgba(255,255,255,.22),0 1px 2px rgba(0,0,0,.3),0 8px 24px rgba(0,0,0,.22);font-family:inherit;
157
+ transition:transform .12s,box-shadow .18s,filter .18s;
158
+ /* ONE MATERIAL, RESTRAINED ACCENT (Claude-desktop caliber): the declarations below SUPERSEDE the first
159
+ two lines of this rule (kept byte-verbatim above — the ship anti-revert gate pins them as live
160
+ probes): house 10px radius, deep ink, calm 180deg fill, NEUTRAL shadows — the green is a jewel,
161
+ not a wall. MIRROR of holo-signin.mjs HL_CSS — same computed values, or the module swap flashes. */
162
+ /* OBSIDIAN IGNITE (Pure): the two lines above are pinned live probes (green jewel); THESE supersede them —
163
+ the key is obsidian glass at rest, hairline-bordered, ink label + a mint fingerprint. Sovereign when
164
+ idle, it ignites mint only while the enclave verifies (the beam rule below). Fixed dark ground so the
165
+ key reads the same on any wallpaper and in light appearance. */
166
+ border-radius:12px;letter-spacing:.01em;color:#eef2f8;
167
+ background:linear-gradient(180deg,rgba(28,30,28,.94),rgba(12,13,12,.96));border:1px solid rgba(255,255,255,.16);
168
+ box-shadow:inset 0 1px 0 rgba(255,255,255,.06),0 12px 34px rgba(0,0,0,.5);}
169
+ #holo-login .hl-bio:hover{transform:translateY(-1px);border-color:rgba(125,239,201,.45);filter:none;
170
+ box-shadow:inset 0 1px 0 rgba(255,255,255,.08),0 16px 40px rgba(0,0,0,.55),0 0 0 1px rgba(52,211,166,.1);}
171
+ #holo-login .hl-bio:active{transform:translateY(0) scale(.99);}
172
+ #holo-login .hl-bio:disabled{cursor:progress;border-color:rgba(125,239,201,.55);}
173
+ #holo-login .hl-bio:disabled span{color:var(--accent);} /* label warms to mint on verify */
174
+ #holo-login .hl-bio svg:not(.hl-beam){width:1.3em;height:1.3em;flex:0 0 auto;color:var(--accent);}
175
+ /* OBSIDIAN IGNITE beam — a single mint comet winds the whole rim once per loop while the enclave checks.
176
+ The module sizes this SVG rect to the key's exact px, so the comet is even on a wide button. */
177
+ #holo-login .hl-bio .hl-beam{position:absolute;inset:0;width:100%;height:100%;pointer-events:none;opacity:0;transition:opacity .2s;}
178
+ #holo-login .hl-bio .hl-beam rect{fill:none;stroke:var(--accent);stroke-width:1.6;stroke-dasharray:15 85;stroke-linecap:round;filter:drop-shadow(0 0 5px rgba(52,211,166,.7));}
179
+ #holo-login .hl-bio:disabled .hl-beam{opacity:1;}
180
+ #holo-login .hl-bio:disabled .hl-beam rect{animation:hl-beam 1.3s linear infinite;}
181
+ @keyframes hl-beam{from{stroke-dashoffset:100;}to{stroke-dashoffset:0;}}
182
+ /* ENCLAVE ASSURANCE LINE — names who guards the key (the security read), just under it. */
183
+ #holo-login .hl-enclave{margin-top:calc(var(--u)*-.5);display:flex;align-items:center;justify-content:center;gap:calc(var(--u)*.35);
184
+ font-size:calc(var(--u)*.72);color:var(--ink-dim);opacity:.72;text-shadow:var(--shadow);}
185
+ /* SUPERSEDES the pinned probe above → enclave line matches the name + guest size (one type size, larger). */
186
+ #holo-login .hl-enclave{margin-top:calc(var(--u)*-.35);gap:calc(var(--u)*.42);font-size:var(--u);font-weight:450;opacity:.66;}
187
+ #holo-login .hl-enclave svg{width:1em;height:1em;color:var(--accent);opacity:.9;flex:0 0 auto;}
188
+ /* SLIDE TO ENTER — touch devices only (the module renders this instead of the tap key on coarse pointers).
189
+ A deliberate drag is intent that can't misfire; the biometric fires on release. Same obsidian/mint language. */
190
+ #holo-login .hl-slide{position:relative;width:100%;min-height:max(var(--g2),56px);border-radius:12px;user-select:none;touch-action:none;overflow:hidden;
191
+ background:linear-gradient(180deg,rgba(28,30,28,.94),rgba(12,13,12,.96));border:1px solid rgba(255,255,255,.16);box-shadow:inset 0 1px 3px rgba(0,0,0,.6);}
192
+ #holo-login .hl-slide .hl-fill{position:absolute;top:0;bottom:0;left:0;width:0;border-radius:12px;background:linear-gradient(90deg,rgba(52,211,166,.34),rgba(52,211,166,.03));}
193
+ #holo-login .hl-slide .hl-track-lbl{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;gap:calc(var(--u)*.45);pointer-events:none;color:var(--ink-dim);font-size:var(--u);font-weight:500;letter-spacing:.01em;text-shadow:var(--shadow);}
194
+ #holo-login .hl-slide .hl-knob{position:absolute;top:4px;bottom:4px;left:4px;aspect-ratio:1;border-radius:9px;cursor:grab;display:grid;place-items:center;color:#062019;
195
+ background:linear-gradient(180deg,#3ce0b0,#28c096);box-shadow:0 4px 12px rgba(0,0,0,.45),inset 0 1px 0 rgba(255,255,255,.42);touch-action:none;}
196
+ #holo-login .hl-slide .hl-knob:active{cursor:grabbing;}
197
+ #holo-login .hl-slide .hl-knob svg{width:1.3em;height:1.3em;color:#062019;}
198
+ #holo-login .hl-slide.armed{border-color:rgba(125,239,201,.5);}
199
+ #holo-login .hl-slide.done .hl-fill{width:100%!important;background:linear-gradient(90deg,#34d3a6,#7defc9);}
200
+ #holo-login .hl-alt{background:none;border:0;color:var(--ink-dim);opacity:.66;margin-top:calc(var(--u)*.3);font-size:calc(var(--u)*.82);font-weight:500;font-family:inherit;cursor:pointer;
201
+ padding:calc(var(--u)*.3) calc(var(--u)*.55);border-radius:calc(var(--u)*.4);display:inline-flex;align-items:center;gap:calc(var(--u)*.4);transition:color .15s,opacity .15s;min-height:44px;}
202
+ #holo-login .hl-alt:hover{color:var(--ink);opacity:1;}
203
+ #holo-login .hl-alt svg{width:1.05em;height:1.05em;}
204
+ /* "More options" is subordinate to the two core actions — quieter (same size, ONE type size everywhere). */
205
+ #holo-login .hl-more{opacity:.72;}
206
+ #holo-login .hl-more:hover{opacity:1;}
207
+ #holo-login .hl-opts{display:flex;gap:calc(var(--u)*.3);justify-content:center;flex-wrap:wrap;}
208
+ #holo-login .hl-opts[hidden]{display:none;}
209
+ #holo-login .status{min-height:calc(var(--u)*1.5);font-size:var(--u);color:var(--status);display:flex;align-items:center;justify-content:center;gap:calc(var(--u)*.5);text-shadow:var(--shadow);}
210
+ #holo-login .status.err{color:#ffc0c0;}
211
+ #holo-login .spin{width:1em;height:1em;border-radius:50%;border:2px solid rgba(125,239,201,.28);border-top-color:var(--accent);animation:hl-spin .7s linear infinite;flex:0 0 auto;}
212
+ @keyframes hl-spin{to{transform:rotate(360deg);}}
213
+ #holo-login .hl-panel{animation:hl-rise .7s cubic-bezier(.4,0,.2,1) .05s both;}
214
+ @keyframes hl-rise{from{opacity:0;transform:translateY(10px) scale(.99);filter:blur(2px);}to{opacity:1;transform:none;filter:none;}}
215
+ /* GLASS UNFOG on success — frost dissolves + panel lifts; boot() paints the messenger beneath, no seam. */
216
+ #holo-login.unfog .hl-frost{animation:hl-defog .72s cubic-bezier(.4,0,.2,1) forwards;}
217
+ #holo-login.unfog .hl-panel{animation:hl-lift .68s cubic-bezier(.4,0,.2,1) forwards;pointer-events:none;}
218
+ @keyframes hl-defog{to{opacity:0;}}
219
+ @keyframes hl-lift{to{opacity:0;transform:translateY(-10px) scale(1.03);}}
220
+ @media (prefers-reduced-motion:reduce){#holo-login .hl-panel,#holo-login .spin{animation:none;}#holo-login.unfog .hl-frost,#holo-login.unfog .hl-panel{animation:hl-defog .3s ease forwards;}}
221
+ /* PLYMOUTH BOOT BEAT — while .hl-boot, the machine is booting: pure splash, panel held. holo-plymouth.mjs
222
+ lifts it (or the baseline's own 5s timer does — never a lock-out). Must live HERE for the 0-ms frame.
223
+ When the beat lifts, the baseline splash layer dissolves to the wallpaper by CSS alone — so even a
224
+ never-arriving module leaves the classic wallpaper lock, not a black void. */
225
+ #holo-login.hl-boot .hl-panel{opacity:0!important;pointer-events:none!important;}
226
+ #holo-login .hl-panel{transition:opacity .55s ease;}
227
+ #holo-login:not(.hl-boot) #hl-plymouth-base{opacity:0;}
228
+ </style>
229
+ <link rel="icon" type="image/svg+xml" href="./favicon.svg">
230
+ <script>try{if("serviceWorker" in navigator)navigator.serviceWorker.register(new URL("../../root-sw.js",location.href),{type:"module"}).catch(function(){navigator.serviceWorker.register(new URL("../../root-sw-classic.js",location.href)).catch(function(){})})}catch(e){}</script>
231
+ <script>try{var _b=document.createElement("base");_b.href=new URL("./",location.href).href;document.head.appendChild(_b);var _r=new URL("../../",location.href).href;if(location.href!==_r)history.replaceState(history.state,"",_r+location.search+location.hash);}catch(e){}</script>
232
+ </head><body>
233
+ <div id="root"></div>
234
+
235
+ <!-- the lock overlay + BASELINE first-paint (synchronous, dependency-free): paints a working "It's me"
236
+ panel on the FIRST frame from a tiny local greeting cache (holo.lastOperator — the SAME hint the OS
237
+ greeter writes), so a returning operator's name + button appear with zero identity/model load. The
238
+ module below wires the real biometric; this only ever OWNS the frame until it lands. -->
239
+ <div id="holo-login"><div class="hl-wall"></div><div class="hl-frost"></div><main class="hl-lock"><div class="hl-panel" id="holo-login-panel"></div></main><div class="hl-brand" aria-label="Hologram"><svg viewBox="-104 -104 208 208" fill="currentColor" role="img" aria-label="Hologram"><g><circle cx="0.20" cy="-97.39" r="2.61"/><circle cx="-22.86" cy="-86.55" r="2.71"/><circle cx="22.54" cy="-86.32" r="2.81"/><circle cx="-0.03" cy="-76.01" r="2.71"/><circle cx="45.26" cy="-75.92" r="7.80"/><circle cx="-45.82" cy="-75.86" r="2.61"/><circle cx="68.34" cy="-65.13" r="7.70"/><circle cx="-68.83" cy="-65.00" r="2.61"/><circle cx="-22.91" cy="-64.90" r="2.61"/><circle cx="22.71" cy="-64.88" r="2.51"/><circle cx="91.24" cy="-54.34" r="2.61"/><circle cx="-45.94" cy="-54.25" r="7.83"/><circle cx="-91.17" cy="-54.19" r="2.71"/><circle cx="-0.03" cy="-54.19" r="2.71"/><circle cx="45.35" cy="-54.19" r="7.80"/><circle cx="-22.86" cy="-43.64" r="2.71"/><circle cx="22.71" cy="-43.49" r="2.51"/><circle cx="68.29" cy="-43.47" r="7.73"/><circle cx="-68.60" cy="-43.37" r="7.73"/><circle cx="-45.85" cy="-32.63" r="7.77"/><circle cx="45.36" cy="-32.60" r="7.80"/><circle cx="-91.26" cy="-32.55" r="2.71"/><circle cx="0.10" cy="-32.51" r="2.61"/><circle cx="91.24" cy="-32.51" r="2.61"/><circle cx="68.22" cy="-21.95" r="7.83"/><circle cx="22.67" cy="-21.84" r="7.87"/><circle cx="-22.86" cy="-21.82" r="2.71"/><circle cx="-68.57" cy="-21.80" r="7.80"/><circle cx="45.45" cy="-11.06" r="7.73"/><circle cx="-0.19" cy="-11.04" r="7.87"/><circle cx="91.35" cy="-11.01" r="2.81"/><circle cx="-91.54" cy="-10.97" r="2.51"/><circle cx="-45.87" cy="-10.87" r="7.73"/><circle cx="22.71" cy="-0.27" r="8.06"/><circle cx="-22.89" cy="-0.21" r="7.90"/><circle cx="68.28" cy="-0.15" r="7.87"/><circle cx="-68.62" cy="-0.11" r="8.00"/><circle cx="-0.06" cy="10.98" r="7.87"/><circle cx="45.54" cy="11.00" r="7.83"/><circle cx="-45.85" cy="11.02" r="7.77"/><circle cx="-91.26" cy="11.10" r="2.71"/><circle cx="91.24" cy="11.13" r="2.61"/><circle cx="22.71" cy="21.64" r="2.71"/><circle cx="-68.74" cy="21.66" r="7.87"/><circle cx="-22.86" cy="21.67" r="7.87"/><circle cx="68.28" cy="21.67" r="7.87"/><circle cx="-91.54" cy="32.46" r="2.61"/><circle cx="0.15" cy="32.46" r="2.71"/><circle cx="-45.72" cy="32.50" r="7.73"/><circle cx="45.54" cy="32.59" r="7.83"/><circle cx="91.35" cy="32.63" r="2.81"/><circle cx="-23.01" cy="43.23" r="2.61"/><circle cx="68.25" cy="43.31" r="7.83"/><circle cx="-68.71" cy="43.34" r="7.83"/><circle cx="22.71" cy="43.37" r="2.90"/><circle cx="91.39" cy="53.92" r="2.71"/><circle cx="45.48" cy="53.95" r="7.87"/><circle cx="-45.86" cy="53.97" r="7.80"/><circle cx="-91.34" cy="53.99" r="2.61"/><circle cx="0.20" cy="54.09" r="2.61"/><circle cx="-68.57" cy="64.90" r="7.80"/><circle cx="-22.86" cy="64.92" r="2.71"/><circle cx="68.28" cy="64.92" r="2.71"/><circle cx="22.54" cy="65.15" r="2.81"/><circle cx="-45.88" cy="75.56" r="7.80"/><circle cx="0.10" cy="75.62" r="2.61"/><circle cx="45.32" cy="75.62" r="2.61"/><circle cx="22.53" cy="86.47" r="2.71"/><circle cx="-22.86" cy="86.75" r="2.71"/><circle cx="-0.03" cy="97.29" r="2.71"/></g></svg><b>HOLOGRAM</b></div></div>
240
+
241
+ <!-- Machine-readable sign-in contract for AI agents — always in the DOM, even before scripts run, so an
242
+ agent can discover guest access by parsing the page. Read by window.HoloLogin.options. Mirrors the OS
243
+ greeter's #holo-agent-login. Guest = act now (no human); biometric = ask the person. -->
244
+ <script type="application/json" id="holo-agent-login">
245
+ {
246
+ "service": "Holo Messenger sign-in",
247
+ "audience": "ai-agent",
248
+ "summary": "Two ways in. Guest is instant and needs no human; the biometric needs the person.",
249
+ "options": [
250
+ {
251
+ "id": "guest",
252
+ "title": "Guest",
253
+ "persistent": false,
254
+ "requiresHuman": false,
255
+ "do": { "navigate": "?guest=1", "call": "window.HoloLogin.guest()" },
256
+ "result": "An instant, private, ephemeral guest session into the Hologram system (Holo Messenger). No biometric and no human needed. Nothing persists after the session ends."
257
+ },
258
+ {
259
+ "id": "biometric",
260
+ "title": "It’s me",
261
+ "persistent": true,
262
+ "requiresHuman": true,
263
+ "do": { "tap": "the 'It’s me' / 'Sign in' button", "then": "ask the person to approve with their device biometric (Windows Hello / Touch ID)" },
264
+ "result": "Signs in with this device's TEE to the operator's sovereign identity (the SAME κ as the OS). An agent cannot complete this alone."
265
+ }
266
+ ]
267
+ }
268
+ </script>
269
+ <script>
270
+ (function () {
271
+ // O2 (HOLO-SOVEREIGN-OFFLINE): storage durability has ONE owner — holo-persist.mjs (persist +
272
+ // estimate, called once, result RECORDED under holo.persist). Import trouble → the inline
273
+ // best-effort call below keeps today's behavior (fail-soft, old browsers included).
274
+ try { import("../../usr/lib/holo/holo-persist.mjs").then(function (m) { m.persistOnce(); }).catch(function () { try { navigator.storage.persist().catch(function(){}); } catch (e) {} }); }
275
+ catch (e) { try { if (navigator.storage && navigator.storage.persist) navigator.storage.persist().catch(function(){}); } catch (e2) {} }
276
+ // O5 (HOLO-SOVEREIGN-OFFLINE): registration hygiene — pre-O0.5 profiles carry workers registered at
277
+ // the OTHER deployment's scopes (the absolute-registrar era: "/", "/apps/holo-messenger/" on a /Q
278
+ // mount). Inert (deeper scopes win) but noise; sweep any registration whose scope is OUTSIDE this
279
+ // mount. On the root-copy deployment the mount IS "/", so every scope matches and nothing is touched.
280
+ try {
281
+ if ("serviceWorker" in navigator) {
282
+ var _mount = new URL("../../", location.href).pathname;
283
+ navigator.serviceWorker.getRegistrations().then(function (rs) {
284
+ rs.forEach(function (r) {
285
+ try { var s = new URL(r.scope).pathname; if (s.indexOf(_mount) !== 0) r.unregister().catch(function () {}); } catch (e) {}
286
+ });
287
+ }).catch(function () {});
288
+ }
289
+ } catch (e) {}
290
+ var panel = document.getElementById("holo-login-panel"); if (!panel) return;
291
+ var esc = function (s) { return String(s).replace(/[&<>"]/g, function (c) { return ({ "&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;" }[c]); }); };
292
+ var op = null; try { op = JSON.parse(localStorage.getItem("holo.lastOperator") || "null"); } catch (e) {}
293
+ // Paint the SHARED wallpaper behind the lock at first frame (the same one the desktop + home wear), so the
294
+ // sign-in is never a black void. A live-sim / none / plain falls back to the shipped default photo — the SAME
295
+ // default the home shows — so login and home always match. κ → content route; a path/url passes through.
296
+ try {
297
+ // APPEARANCE at 0 ms — the lock wears the SAME holo.theme.v1 row home wears: immersive (wallpaper),
298
+ // dark, or light. One data attribute flips the whole token set; flat modes never fetch the photo.
299
+ var _thm = {}; try { _thm = JSON.parse(localStorage.getItem("holo.theme.v1") || "{}") || {}; } catch (e) {}
300
+ var _mode = (_thm.immersive === false) ? (_thm.palette === "light" ? "light" : "dark") : "immersive";
301
+ var _lv = document.getElementById("holo-login");
302
+ if (_lv) _lv.setAttribute("data-appearance", _mode);
303
+ var _tw = _thm.wallpaper || "";
304
+ var _km = String(_tw).match(/^(sha256|blake3|sha512):([0-9a-f]+)$/i);
305
+ var _wp = _km ? ("/.holo/" + _km[1].toLowerCase() + "/" + _km[2]) : ((!_tw || _tw === "plain" || /^live:/i.test(_tw)) ? "" : _tw);
306
+ // MATCH THE HOME EXACTLY: the messenger home resolves its wallpaper as holo.theme.v1 → the messenger's own
307
+ // "holo-messenger/wallpaper-src" pick → the shipped default. The lock skipped the middle step, so a wallpaper
308
+ // set only via the messenger's picker showed on home but not here. Fold that same fallback in — one chain,
309
+ // so the sign-in glass and the desktop it lifts onto are never two different photos.
310
+ if (!_wp) { try { _wp = localStorage.getItem("holo-messenger/wallpaper-src") || ""; } catch (e) {} }
311
+ if (!_wp) _wp = "./_vendor/wallpaper-default.jpg";
312
+ var _wall = document.querySelector("#holo-login .hl-wall");
313
+ if (_wall && _mode === "immersive") _wall.style.backgroundImage = 'url("' + _wp + '")';
314
+ // PLYMOUTH BOOT SPLASH baseline (holo-plymouth.mjs owns the real player; this is the 0-ms frame). Once a
315
+ // boot style has been worn (holo.plymouth.v1 persisted), a cold open paints a dedicated BLACK layer with
316
+ // the sealed frame-0 synchronously — the machine boots like a machine — and the panel holds for one boot
317
+ // beat behind .hl-boot. The wallpaper underneath is NEVER touched: the module dissolves this layer into
318
+ // it (boot → your OS). Fail-open twice over: the module drops the layer when its canvas is live, and if
319
+ // the module never arrives the 5s timer lifts .hl-boot and CSS fades the layer to the wallpaper lock.
320
+ try {
321
+ // S2 zero-drift: circle_hud frame-0, byte-identical to holo-plymouth.mjs DEFAULT_FF (witness-gated).
322
+ var _DFF = "data:image/png;base64,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";
323
+ var _pl = JSON.parse(localStorage.getItem("holo.plymouth.v1") || "null");
324
+ if (!_pl) _pl = { v: 1, on: true, theme: "circle_hud", firstFrame: _DFF, ffKeyed: true }; // first-EVER visit boots like an OS, at 0 ms
325
+ else if (_pl.on && !_pl.firstFrame && _pl.theme === "circle_hud") { _pl.firstFrame = _DFF; _pl.ffKeyed = true; } // default theme, frame not yet sealed
326
+ // GROUND LAW: the boot ground and the login wall are ONE token (--boot-ground) — they can never
327
+ // drift. Only a seal stamped ffKeyed (chroma-keyed, black→air, holo-plymouth sealFirstFrame) — or
328
+ // the embedded keyed default — may paint over the ground; a LEGACY unkeyed seal would lay its
329
+ // baked-black square over the charcoal (the "boot darker than login" mismatch), so it falls back.
330
+ if (_pl.firstFrame && !_pl.ffKeyed && _pl.firstFrame !== _DFF) _pl.firstFrame = (_pl.theme === "circle_hud") ? _DFF : null;
331
+ if (_pl && _pl.on && _wall) {
332
+ // First boot for this theme (frames not yet sealed to κ) streams from the pack CDN — warm the
333
+ // DNS/TLS handshake NOW so the emblem's first frames land sooner. Warm boots (sealed) skip this.
334
+ try { if (!localStorage.getItem("holo.plymouth.frames:" + _pl.theme)) {
335
+ var _pc = document.createElement("link"); _pc.rel = "preconnect"; _pc.href = "https://raw.githubusercontent.com"; _pc.crossOrigin = "anonymous"; document.head.appendChild(_pc);
336
+ // SUPERCOMPUTER BOOT: the DEFAULT theme animates from the same-origin frames.pack (holo-plymouth
337
+ // loadPack) — start that ONE fetch NOW, at HTML parse, instead of after the module chain evaluates.
338
+ // By the time plymouth asks, the bytes are already in the HTTP cache: the emblem MOVES at first sight.
339
+ if (_pl.theme === "circle_hud") { var _pk = document.createElement("link"); _pk.rel = "preload"; _pk.as = "fetch"; _pk.crossOrigin = "anonymous"; _pk.href = "../../usr/share/plymouth/circle_hud/frames.pack"; _pk.fetchPriority = "high"; document.head.appendChild(_pk); }
340
+ } } catch (e) {}
341
+ var _base = document.createElement("div");
342
+ _base.id = "hl-plymouth-base";
343
+ // dead-centre, sized like the module's boot HERO pose (1.5× natural capped at 95vmin — exact for
344
+ // the default theme's 533px frame), so the canvas takes over with zero visible jump
345
+ _base.style.cssText = "position:fixed;inset:0;z-index:0;pointer-events:none;background:var(--boot-ground,#1f1f1e) center 50% no-repeat;transition:opacity .9s ease";
346
+ if (_pl.theme === "circle_hud") _base.style.backgroundSize = "min(800px,95vmin)";
347
+ if (_pl.firstFrame) _base.style.backgroundImage = 'url("' + _pl.firstFrame + '")';
348
+ _wall.parentNode.insertBefore(_base, _wall.nextSibling);
349
+ var _ov = document.getElementById("holo-login");
350
+ // BOOT HERO: hold the whole login behind .hl-boot — holo-plymouth OWNS the beat (5s hero, one
351
+ // coordinated reveal) and cancels this fallback; the timer only lifts the gate if the module
352
+ // NEVER arrives. Runs on every device — the hold is stillness, not motion.
353
+ try { performance.mark("holo:ceremony:brand-paint"); } catch (e) {}
354
+ if (_ov) { _ov.classList.add("hl-boot"); window.__hlBootT0 = Date.now(); window.__hlBootFallback = setTimeout(function () { try { _ov.classList.remove("hl-boot"); } catch (e) {} }, 6500); }
355
+ }
356
+ } catch (e) {}
357
+ } catch (e) {}
358
+ var fp = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round"><path d="M5.5 11a6.5 6.5 0 0 1 13 0"/><path d="M8.5 11a3.5 3.5 0 0 1 7 0v2.6"/><path d="M12 11v4.4"/><path d="M8.6 14.2V16"/><path d="M15.4 15.4V18"/></svg>';
359
+ var person = '<svg viewBox="0 0 24 24" fill="currentColor"><circle cx="12" cy="8.4" r="4.1"/><path d="M3.8 20.4a8.2 8.2 0 0 1 16.4 0z"/></svg>';
360
+ var avatar, name, label;
361
+ if (op && op.label) {
362
+ var hue = (op.hue != null ? op.hue : 210) | 0;
363
+ var ini = op.label.trim().split(/\s+/).map(function (w) { return w[0]; }).filter(Boolean).slice(0, 2).join("").toUpperCase() || "·";
364
+ avatar = '<div class="hl-avatar" style="background:linear-gradient(140deg,hsl(' + hue + ' 52% 46%),hsl(' + (hue + 26) + ' 52% 46%))">' + esc(ini) + '</div>';
365
+ name = esc(op.label); label = "It’s me";
366
+ } else {
367
+ avatar = '<div class="hl-avatar" style="background:linear-gradient(140deg,#5b6b86,#3a455c)">' + person + '</div>';
368
+ name = "Welcome"; label = "Sign in";
369
+ }
370
+ // your name IS the sign-in — one control carries who you are and the act of proving it (no heading)
371
+ var btnText = (op && op.label) ? name : "Sign in";
372
+ panel.innerHTML = avatar + '<div class="hl-auth"><button class="hl-bio" id="hl-baseline">' + fp + '<span>' + btnText + '</span></button><div class="status"></div></div>';
373
+ var b = document.getElementById("hl-baseline"); if (b) b.onclick = function () { b.disabled = true; location.reload(); };
374
+ })();
375
+ </script>
376
+
377
+ <!-- LEAN BOOT — the sovereign gate + boot splash mount NOW, in PARALLEL with the heavy chat bundle below,
378
+ so the emblem animates and the panel is ready without waiting on the messenger's code to download or
379
+ parse. The login chain (login→signin→plymouth) is small; the preload scanner fetches it alongside the
380
+ big bundle, and this module evaluates first, so the gate is up before the messenger even starts. The
381
+ resolved auth promise is handed to boot() below. Fail-SOFT: a gate that can't load drops the overlay
382
+ and boot uses its device-stable identity, never bricked. -->
383
+ <script type="module">
384
+ try {
385
+ const { mountLogin } = await import("./holo-messenger-login.mjs?v=signin1");
386
+ window.__holoAuthP = mountLogin({ root: document.getElementById("root") }); // the PROMISE (resolves on sign-in), not an awaited value
387
+ } catch (e) {
388
+ try { const ov = document.getElementById("holo-login"); if (ov) ov.remove(); } catch {}
389
+ window.__holoAuthP = Promise.resolve(null);
390
+ console.warn("[login] gate unavailable, booting device-stable identity:", e);
391
+ }
392
+ </script>
393
+
394
+ <!-- SUPERCOMPUTER RETURN + OFFLINE: after the ceremony settles, seal the ENTIRE boot sequence — this
395
+ document, the login-chain modules, frames.pack, the wallpaper — into a versioned Cache-Storage set
396
+ ("holo-boot-<stamp>", stamp = the SIGNED release pointer). root-sw.js answers cache-first from it at
397
+ the "/" scope holo-sw cannot reach, so a RETURNING operator boots with ZERO network and the same
398
+ bytes — document included — answer fully OFFLINE. Idle-time, fail-soft, ?nosw=1 skips (dev). One
399
+ release = one sealed set; superseded sets are purged; re-visits with a sealed set do zero work. -->
400
+ <script>
401
+ (function () {
402
+ try {
403
+ if (!("caches" in window)) return;
404
+ if (new URLSearchParams(location.search).get("nosw") === "1") return;
405
+ var seal = function () { setTimeout(async function () {
406
+ try {
407
+ // site root: app.html lives at <root>/apps/holo-messenger/; a root copy (index.html) is <root>/ itself
408
+ var base = new URL(".", location.origin + location.pathname.replace(/apps\/holo-messenger\/app\.html$/, ""));
409
+ var stamp = "live";
410
+ try { var rj = await fetch(new URL("release.json", base), { cache: "no-store" });
411
+ if (rj.ok) { var r = await rj.json(); var pay = r["holstr:payload"] || {};
412
+ // the signed strand pointer: holstr:seq + the shell κ — one stamp per sealed release
413
+ stamp = String((r["holstr:seq"] != null ? "s" + r["holstr:seq"] : "") + String(pay.shell || r.seq || r.root || "").slice(0, 12) || "live").replace(/[^a-zA-Z0-9]/g, "").slice(0, 24) || "live"; } } catch (e) {}
414
+ var name = "holo-boot-" + stamp;
415
+ try { var ks = await caches.keys(); for (var i = 0; i < ks.length; i++) if (ks[i].indexOf("holo-boot-") === 0 && ks[i] !== name) await caches.delete(ks[i]); } catch (e) {}
416
+ // O3/O4 (HOLO-SOVEREIGN-OFFLINE): the AUTOMATIC PIN runs on EVERY online visit — BEFORE the
417
+ // already-sealed early-return below, or a returning user would never resume an interrupted pin
418
+ // and never receive a κ-delta update. Cheap when nothing changed (one no-store head fetch →
419
+ // ledger says done). releaseBoot is the only head acceptor; the pin refuses to run ahead of it.
420
+ // Fail-soft: any trouble and this device behaves exactly as before the pin existed.
421
+ try { import("../../usr/lib/holo/holo-pin.mjs").then(function (m) { return m.ensurePinned({}); }).then(function (s) { try { console.info("[holo-pin]", JSON.stringify(s)); } catch (e) {} }).catch(function () {}); } catch (e) {}
422
+ if (await caches.has(name)) return; // this release is already sealed — zero work
423
+ var cache = await caches.open(name);
424
+ var SET = [
425
+ "", "index.html", "apps/holo-messenger/app.html",
426
+ // the DOOR's own closure — the root navigation must RESOLVE offline, not just paint: the door
427
+ // module + its pure resolver + the inline name plane + the signed index it reads (release.json
428
+ // is answered network-FIRST by root-sw, so the sealed copy serves only when the radio is dead).
429
+ "root-door.mjs", "holo-root-resolver.mjs", "holo-resolve-view.mjs",
430
+ "release.json", "apps/index.jsonld", "apps-blake3.json",
431
+ "usr/lib/holo/holo-app-index.mjs",
432
+ "apps/holo-messenger/holo-messenger-login.mjs?v=signin1",
433
+ "usr/lib/holo/holo-signin.mjs", "usr/lib/holo/holo-webauthn.mjs", "usr/lib/holo/holo-sso.mjs",
434
+ "usr/lib/holo/holo-roam.mjs", "usr/lib/holo/holo-pair.mjs", "usr/lib/holo/holo-plymouth.mjs",
435
+ "usr/lib/holo/holo-device-tier.mjs", "usr/lib/holo/holo-manifesto.mjs",
436
+ "usr/share/plymouth/circle_hud/frames.pack",
437
+ "_shared/holo-widgets.js",
438
+ "apps/holo-messenger/_vendor/wallpaper-default.jpg",
439
+ // the ENTRY closure — a sealed gate must also OPEN offline: guest needs only holo-identity
440
+ // (zero static imports); the returning unlock adds the small login/session/crypto leaves.
441
+ "usr/lib/holo/holo-identity.mjs", "usr/lib/holo/holo-login.mjs", "usr/lib/holo/holo-session.mjs",
442
+ "usr/lib/holo/holo-uor.mjs", "usr/lib/holo/holo-blake3.mjs", "usr/lib/holo/holo-pqc.mjs",
443
+ "usr/lib/holo/holo-portrait.mjs",
444
+ // O2 (HOLO-SOVEREIGN-OFFLINE): the device-store rung + its IDB backend + the ONE persist
445
+ // owner — sealed so the offline SW can load the rung itself with the radio dead.
446
+ "usr/lib/holo/holo-store-rung.mjs", "usr/lib/holo/holo-persist.mjs",
447
+ "usr/lib/holo/holo-forge/holo-kstore.mjs", "usr/lib/holo/holo-evict-rescue.mjs",
448
+ ];
449
+ for (var j = 0; j < SET.length; j++) {
450
+ try {
451
+ var u = new URL(SET[j], base).toString();
452
+ var res = await fetch(u, { cache: "no-cache" }); // revalidated — never seal a stale HTTP copy
453
+ if (res && res.ok) await cache.put(u, res);
454
+ } catch (e) {}
455
+ }
456
+ } catch (e) {}
457
+ }, 12000); }; // well past the hero + arrival — the boot's own network is long quiet by then
458
+ if (document.readyState === "complete") seal(); else addEventListener("load", seal);
459
+ } catch (e) {}
460
+ })();
461
+ </script>
462
+
463
+ <!-- NATIVE MEDIA — the real OS media stack, loaded into the top page so the messenger rail delivers the exact
464
+ native experience: window.HoloVinyl = the "NOW PLAYING" pill (preloaded Ben Böhmer "Begin Again"), streamed
465
+ through the same-origin /sc/* routes the dev server now answers (holo-sc-route.mjs → yt-dlp). holo-fx.js
466
+ drives the live equalizer; holo-vinyl lazily pulls holo-audio.js for the Hi-Fi chain. Each self-guards on the
467
+ widgets/dock runtime, so they run standalone here. Classic defer → they set their globals before the bundle boots. -->
468
+ <script src="../../usr/lib/holo/holo-fx.js" defer></script>
469
+ <script src="../../usr/lib/holo/holo-vinyl.js" defer></script>
470
+ <script src="../../usr/lib/holo/holo-video.js" defer></script>
471
+
472
+ <script type="module">
473
+ // ?v=N - bump on each deploy so the browser can never serve a stale cached bundle (the cause of a frozen/old build).
474
+ import "./_vendor/ui/chat-ui.bundle.js?v=c6c04deb6973"; // → window.HoloMessengerUI
475
+ // Opt-in: render the WhatsApp UX from STREAMING shadcn κ-components instead of the chatscope bundle.
476
+ // Lazy — the module loads ONLY when opted in (?ui=shadcn or holo.ui.shadcn=1); the default boot never
477
+ // parses it. Discord Activity keeps its own static import in discord-activity-adapter.mjs.
478
+ try {
479
+ if (new URLSearchParams(location.search).get("ui") === "shadcn" || localStorage.getItem("holo.ui.shadcn") === "1")
480
+ await import("./messenger-shadcn-ui.mjs");
481
+ } catch {}
482
+ import { boot } from "./holo-messenger-app.mjs?v=366379098ee3";
483
+
484
+ const root = document.getElementById("root");
485
+ // TEE GATE + SPECULATIVE WARM PAINT, one motion. The gate already mounted (parallel module above); it
486
+ // renders the lock + runs the biometric, while boot() starts IMMEDIATELY behind the glass, painting the
487
+ // messenger from the local κ-snapshot during the human's look-and-tap. boot awaits the auth ONLY at its
488
+ // identity step, so the biometric overlaps an already-live UI and loginSucceeded just unfogs onto it
489
+ // (tap → interactive ≈ one frame). Fail-CLOSED: nothing confidential is interactable until the glass lifts.
490
+ const authP = window.__holoAuthP || Promise.resolve(null); // the promise the parallel gate module produced
491
+ boot(root, authP).catch((e) => {
492
+ document.body.innerHTML = "<pre style='color:#f88;padding:16px;white-space:pre-wrap'>boot error: " + (e && e.stack || e) + "</pre>";
493
+ });
494
+
495
+ // Intelligent Inbox (holo-mail) - additive, OFF by default. Enable per-operator: localStorage["holo.mail.enabled"]="1".
496
+ import("./mail/holo-mail-attach.mjs").catch(() => {});
497
+ // HoloChat, woven into THIS messenger — one canonical surface. Enable: localStorage["holo.chat.enabled"]="1" or ?chat=1.
498
+ import("./holo-messenger-weave.mjs").catch(() => {});
499
+ // Colour re-tone picker — colours only, all features intact. Gated: ?skins=1 or localStorage["holo.skins"]="1".
500
+ import("./messenger-skin.mjs?v=970e7ec3151a").catch(() => {});
501
+ // Asset cache (service worker) — repeat opens serve the shell NETWORK-FREE. Escape hatch: open with ?nosw=1.
502
+ // RELATIVE by design (HOLO-SOVEREIGN-OFFLINE / O0.5 root cause): an ABSOLUTE "/apps/…" here resolves against
503
+ // the DOCUMENT, so on the github.io /Q mount it hits the ROOT-COPY deployment and registers holo-sw at the
504
+ // WRONG scope (never /Q) — the O0 outage. "./" resolves against app.html's own folder on BOTH mounts (OS
505
+ // root AND /Q), so the registration always lands on the mount actually serving this page. Do not re-absolutise.
506
+ import("./messenger-sw-register.mjs").catch(() => {});
507
+ // Q SUMMON — tap the home orb and the canvas MORPHS into Q (View Transitions pair the orb corner→face),
508
+ // Q greets OUT LOUD at the tap (the tap IS the autoplay gesture), the HD voice + Q's BitNet brain pre-warm
509
+ // while home idles (window.HoloQ.warm — standalone-parity TTFT), and every Q-thread message is sealed to a
510
+ // BLAKE3 κ chain (did:holo:blake3, hash-linked, verified on load). Additive + fail-soft.
511
+ import("./q-summon.mjs?v=18").catch(() => {});
512
+ // Q LIVE CALL — tap Call in the hero → realtime voice conversation (createQLive: streaming voice,
513
+ // hands-free turns, barge-in). Additive + fail-soft: no mic/WebGPU → a clear message, chat still works.
514
+ import("./q-live-hero.mjs?v=4").catch(() => {});
515
+ // POWER & SESSION — one power button at the very bottom of the rail. Tap → a calm sheet in the sign-in's own
516
+ // teal vocabulary: live session uptime + who you are + Lock & Sign Out (window.holo.signOut → the clean gate,
517
+ // box secured). Additive + fail-soft: no rail / no signOut → the control just doesn't appear.
518
+ import("./holo-power-session.mjs?v=3").catch(() => {});
519
+ // KEYBOARD LAYER — the native CEF browser's shortcut system, ported to the web (DESKTOP MODE ONLY): the
520
+ // ambient "hold-Ctrl" hint dot, Ctrl K spotlight, Ctrl ⇧ P command palette, ? cheat sheet, Esc to dismiss,
521
+ // Ctrl ]/[ to cycle conversations — one dependency-free engine (holo-keys), every action content-addressed.
522
+ // Self-gating: mounts nothing on touch/narrow, inside the native shell, or when embedded. Fail-soft.
523
+ import("./holo-messenger-keys.mjs?v=5").catch(() => {});
524
+ // HOLO DIRECT — the NATIVE holospace↔holospace sealed chat (dual-path engine: P2P WebRtcLink fast-path via
525
+ // sealed-SDP rendezvous over the blind mailbox, mailbox fallback; TOFU + safety number + key-change). One 🔐
526
+ // rail button; the 3 MB spine wasm loads on FIRST use only (cold boot pays nothing). Additive + fail-soft.
527
+ import("./holo-direct-mount.mjs?v=7").catch(() => {});
528
+ // IMMERSIVE VIDEO — chromeless YouTube embed + a hover-only Holo control bar (play/pause · seek · time ·
529
+ // fullscreen). Additive + fail-soft.
530
+ import("./holo-video-immersive.mjs?v=22eef34f49d2").catch(() => {});
531
+ </script>
532
+ </body></html>
sha256/1e6016292b58e5397de70e1df675451578014820ef442901cee58eda4e91bb94 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":{"games":"5de2be050dc5a4f9fcb464ffd45bbfcdca368ac02c39e5807ef6ce9430c45edd","hello":"66c1ada5ac08a2d4f73851bbe7458ad5fb6e2226c5a131c3829abfe715900fed","holo-games":"791a9ac999486256499d76a938345b173053b8de643cc89797bf946c38c365e3","holo-messenger":"8d8286952efd57d1d8c9bab0202ddb3bcb3c6b5e29a2715a9c7b18a53937b4a6","resolve":"23aec66382775f5773693615338f37c37a5f58257ec95aece33a664baa6fe0db","self":"0511772584510f0888d97dd5a72bf986ad6791bd8e305063ddd8a82c39888a70","spaces":"d8aebefb67e7fc7802e757b9a7d95bb881e6d75b4a9bc4bd4227634541757e51"},"closure":"f8a36842be2fbdeb46a42552929e317769ea944ce1895ea48723d5d279817f82","manifest":"7e0f2cff85c992261d4eba677c473faf78438850e2bb566561e7fc04f2bd3765","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":"3e3f598a0f41fca61fb80f20417ab2e5484e74bc3f0c2cfdc33c31199ce30e13"},"holstr:prev":"did:holo:sha256:c0bdf84ad8067c1332c1062b9d3327e616e22aa5a9acf8f003167663d37e3473","holstr:seq":157,"prov:generatedAtTime":"2026-07-12T12:49:34.721Z"}
sha256/20f24e1a21bc5e8e3c161f82afcb6e9beae002c92015be8661dd3bbed6c39ac8 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":{"holo-games":"791a9ac999486256499d76a938345b173053b8de643cc89797bf946c38c365e3","holo-messenger":"1e175094425429ed2ccb267298246a608e9e72b9641829e76d83238855456992","resolve":"23aec66382775f5773693615338f37c37a5f58257ec95aece33a664baa6fe0db","self":"0511772584510f0888d97dd5a72bf986ad6791bd8e305063ddd8a82c39888a70","spaces":"d8aebefb67e7fc7802e757b9a7d95bb881e6d75b4a9bc4bd4227634541757e51"},"closure":"b08e06b79609bf6b7f1b9acc2b2e414f0fd1406a9fa1b9eca24346a88453afb0","manifest":"46519f64204299c9fc830fdbd5f264ec863e363f5feb2b5ab2554123fa217603","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":"d5e142099185b5243a02a373dd5ccc4fb07ddc5d7657524cc610b87313e10918"},"holstr:prev":"did:holo:sha256:6ffcec292be0a5a2a585362d1abf659f6d95ed08c541fbaac1c06e226db1a3dc","holstr:seq":106,"prov:generatedAtTime":"2026-07-11T19:55:35.366Z"}
sha256/2130e7ee136037d25388f86d22552b51222907c242b4f54ef10b55c30d4d459e ADDED
The diff for this file is too large to render. See raw diff
 
sha256/232d735a20f3a27c22126ea42cbfc7768b9c2db16c52b110980e82d37e710c0b ADDED
@@ -0,0 +1,141 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // holo-messenger-weave.mjs — weave HoloChat INTO the real messenger (additive, no monolith edits).
2
+ //
3
+ // Why: there were two messengers — the beautiful one, and a plainer HoloChat surface behind "#". This
4
+ // dissolves the second into the first, so apps/money/identity/proofs live in the messenger you know.
5
+ // How: the real UI is a closed React/chatscope app exposing only low-level globals, so we weave at the
6
+ // DOM seam (the pattern the app itself uses to turn pay/watch links into cards): a body-level observer
7
+ // upgrades card-text bubbles into native cards, and the composer attach "+" opens the unified App Tray.
8
+ // What: in ANY conversation, a message whose text is a HoloChat card renders as that card; tapping "+"
9
+ // opens apps · Spaces · money · identity · prove, and a pick sends into the current conversation.
10
+
11
+ import "./holo-app-tray.mjs"; // installs window.HoloApps (+ HoloOpen)
12
+
13
+ const CONTENT = ".cs-message__content";
14
+ const EDITOR = ".cs-message-input__content-editor";
15
+ const SEND = ".cs-button--send";
16
+ const ATTACH = ".cs-button--attachment";
17
+
18
+ // drop a card into whatever conversation is open by POPULATING the real composer (reliable), then the user
19
+ // presses Send — exactly like attaching in WhatsApp. Set the contenteditable's text + dispatch a native
20
+ // `input` event so chatscope's React onInput registers it and enables Send; place the cursor at the end.
21
+ function composerChannel() {
22
+ return {
23
+ send(text) {
24
+ const ed = document.querySelector(EDITOR); if (!ed) return;
25
+ ed.focus();
26
+ ed.textContent = String(text);
27
+ ed.dispatchEvent(new InputEvent("input", { bubbles: true, cancelable: true, inputType: "insertText", data: String(text) }));
28
+ try { const r = document.createRange(); r.selectNodeContents(ed); r.collapse(false); const sel = getSelection(); sel.removeAllRanges(); sel.addRange(r); } catch {}
29
+ },
30
+ };
31
+ }
32
+
33
+ // upgrade one message-content node: if its text is a HoloChat card, replace it with the rendered card.
34
+ function upgrade(node) {
35
+ try {
36
+ if (!node || node.dataset.holoCard) return;
37
+ const A = window.HoloApps; if (!A || !A.isCard) return;
38
+ const text = (node.textContent || "").trim();
39
+ if (!A.isCard(text)) return;
40
+ node.dataset.holoCard = "1";
41
+ node.textContent = "";
42
+ node.appendChild(A.renderCard(text, composerChannel()));
43
+ } catch {}
44
+ }
45
+
46
+ // the composer attach "+" opens the unified tray (the modern "attach anything": apps, money, id, files).
47
+ function hookAttach(btn) {
48
+ if (!btn || btn.dataset.holoAttach) return;
49
+ btn.dataset.holoAttach = "1";
50
+ btn.addEventListener("click", (e) => { e.preventDefault(); e.stopPropagation(); if (window.HoloApps) window.HoloApps.pick(composerChannel()); }, true);
51
+ }
52
+
53
+ // a native "Spaces" rail at the top of the chat list — the familiar status/Communities strip. Placed in
54
+ // the app's own .holo-convlist-wrap (a sibling of React's <ul>, so it survives re-renders); tap → open.
55
+ const WRAP = ".holo-convlist-wrap";
56
+ // one clean word per Space — no truncation, no ellipsis.
57
+ const SPACE_SHORT = { "org.hologram.holospace.Web3": "Web3", "org.hologram.holospace.CreatorStudio": "Studio", "org.hologram.holospace.DevCockpit": "Dev", "org.hologram.holospace.AILab": "AI", "org.hologram.holospace.TrustCenter": "Trust", "org.hologram.holospace.EmulationArcade": "Arcade" };
58
+ async function mountSpaces() {
59
+ try {
60
+ if (document.getElementById("holo-spaces")) return;
61
+ const list = document.querySelector(".cs-conversation-list"); if (!list) return;
62
+ const wrap = document.querySelector(WRAP) || list.parentElement; if (!wrap) return;
63
+ const A = window.HoloApps; if (!A || !A.catalog) return;
64
+ const spaces = (await A.catalog()).filter((x) => x.kind === "space");
65
+ if (!spaces.length || document.getElementById("holo-spaces")) return;
66
+ const open0 = (() => { try { return localStorage.getItem("holo.spaces.open") === "1"; } catch { return false; } })();
67
+ const sec = document.createElement("div"); sec.id = "holo-spaces";
68
+ sec.style.cssText = "flex:0 0 auto;border-bottom:1px solid #ffffff0d;background:transparent;";
69
+ const head = document.createElement("button"); head.type = "button"; // one quiet line by default; tap to reveal
70
+ head.style.cssText = "display:flex;align-items:center;gap:8px;width:100%;border:0;background:transparent;color:#e9edef;cursor:pointer;padding:11px 16px;font:600 13px system-ui;opacity:.6;transition:opacity .15s ease;";
71
+ head.innerHTML = `<span style="font-size:15px;">✨</span><span style="flex:1;text-align:left;">Spaces</span><span class="hsp-chev" style="font-size:17px;line-height:1;opacity:.7;transition:transform .18s ease;">›</span>`;
72
+ head.onmouseenter = () => { head.style.opacity = "1"; }; head.onmouseleave = () => { head.style.opacity = ".6"; };
73
+ const rail = document.createElement("div"); rail.style.cssText = "display:" + (open0 ? "flex" : "none") + ";gap:18px;overflow-x:auto;padding:2px 16px 12px;scrollbar-width:none;";
74
+ for (const s of spaces) {
75
+ const nm = SPACE_SHORT[s.id] || s.name;
76
+ const b = document.createElement("button"); b.type = "button"; b.title = s.tagline || s.name;
77
+ b.style.cssText = "flex:0 0 auto;display:flex;flex-direction:column;align-items:center;gap:6px;border:0;background:transparent;color:#e9edef;cursor:pointer;";
78
+ b.innerHTML = `<span class="hsp-av" style="width:40px;height:40px;border-radius:50%;display:grid;place-items:center;font-size:19px;background:${s.accent}12;box-shadow:inset 0 0 0 1px ${s.accent}44;transition:transform .15s ease,box-shadow .15s ease;">${s.glyph}</span>` +
79
+ `<span style="font-size:13px;font-weight:500;opacity:.62;">${nm}</span>`;
80
+ const av = b.querySelector(".hsp-av");
81
+ b.onmouseenter = () => { av.style.transform = "scale(1.08)"; av.style.boxShadow = "inset 0 0 0 1.5px " + s.accent + "aa"; b.lastChild.style.opacity = "1"; };
82
+ b.onmouseleave = () => { av.style.transform = "scale(1)"; av.style.boxShadow = "inset 0 0 0 1px " + s.accent + "44"; b.lastChild.style.opacity = ".62"; };
83
+ b.onclick = () => (window.HoloOpen ? window.HoloOpen(s) : A.openSpace(s));
84
+ rail.appendChild(b);
85
+ }
86
+ const chev = head.querySelector(".hsp-chev"); chev.style.transform = open0 ? "rotate(90deg)" : "rotate(0deg)";
87
+ head.onclick = () => {
88
+ const opening = rail.style.display === "none";
89
+ rail.style.display = opening ? "flex" : "none";
90
+ chev.style.transform = opening ? "rotate(90deg)" : "rotate(0deg)";
91
+ try { localStorage.setItem("holo.spaces.open", opening ? "1" : "0"); } catch {}
92
+ };
93
+ sec.append(head, rail); wrap.insertBefore(sec, list);
94
+ } catch {}
95
+ }
96
+
97
+ function scan(root) {
98
+ (root || document).querySelectorAll?.(CONTENT).forEach(upgrade);
99
+ (root || document).querySelectorAll?.(ATTACH).forEach(hookAttach);
100
+ mountSpaces();
101
+ }
102
+
103
+ // a light, reversible calm: soften the loudest native strip (the "need you" banner) and hide the rail's
104
+ // scrollbar. Conservative — no functional element is hidden; remove #holo-calm to revert entirely.
105
+ function injectCalm() {
106
+ if (document.getElementById("holo-calm")) return;
107
+ const st = document.createElement("style"); st.id = "holo-calm";
108
+ st.textContent = [
109
+ ".holo-catchup{opacity:.8;}", // soften the loud "need you" banner
110
+ ".holo-chip:not(.on){opacity:.62;font-weight:500;}", // quiet the inactive filters (all still work)
111
+ ".holo-chip{transition:opacity .15s ease;} .holo-chip:hover{opacity:1;}",
112
+ "#holo-spaces::-webkit-scrollbar{display:none;height:0;}",
113
+ ].join("");
114
+ (document.head || document.documentElement).appendChild(st);
115
+ }
116
+
117
+ let _obs = null;
118
+ function start() {
119
+ if (_obs || typeof MutationObserver === "undefined") return;
120
+ injectCalm();
121
+ scan(document);
122
+ _obs = new MutationObserver((muts) => {
123
+ for (const m of muts) for (const n of m.addedNodes) {
124
+ if (n.nodeType !== 1) continue;
125
+ if (n.matches?.(CONTENT)) upgrade(n);
126
+ if (n.matches?.(ATTACH)) hookAttach(n);
127
+ n.querySelectorAll?.(CONTENT).forEach(upgrade);
128
+ n.querySelectorAll?.(ATTACH).forEach(hookAttach);
129
+ if (n.matches?.(".cs-conversation-list") || n.querySelector?.(".cs-conversation-list")) mountSpaces();
130
+ }
131
+ });
132
+ _obs.observe(document.body, { childList: true, subtree: true });
133
+ }
134
+
135
+ export function installWeave() { if (typeof document !== "undefined") { if (document.body) start(); else document.addEventListener("DOMContentLoaded", start); } return true; }
136
+
137
+ if (typeof window !== "undefined") {
138
+ const flag = () => { try { return localStorage.getItem("holo.chat.enabled") === "1" || /(?:^|[?&])chat=1(?:&|$)/.test(location.search); } catch { return false; } };
139
+ if (flag()) installWeave();
140
+ }
141
+ export default { installWeave };
sha256/23ad078d7c8a8e5c306772fbd0f70c81df62c2d37532b7a93c17a3f67c0428f3 ADDED
@@ -0,0 +1 @@
 
 
1
+ function s(e){let t=e.replace(/-/g,"+").replace(/_/g,"/"),n=decodeURIComponent(escape(atob(t)));return JSON.parse(n)}var c=["v","kind","amount","asset","fiat","to","toName","from","fromName","memo","created","expires","claimSecret","kappa","hashlock","pub","sig"];function l(e){let t=s(e);if(!Array.isArray(t))return t;let n={};return c.forEach((o,a)=>{t[a]!=null&&(n[o]=t[a])}),n.memo==null&&(n.memo=""),n}async function u(e){let t=new TextEncoder().encode(e),n=await crypto.subtle.digest("SHA-256",t);return[...new Uint8Array(n)].map(o=>o.toString(16).padStart(2,"0")).join("")}var y=u;function r(e){let t=atob(e.replace(/-/g,"+").replace(/_/g,"/")),n=new Uint8Array(t.length);for(let o=0;o<t.length;o++)n[o]=t.charCodeAt(o);return n}async function i(e,t,n){try{let o=await crypto.subtle.importKey("raw",r(e),{name:"ECDSA",namedCurve:"P-256"},!1,["verify"]);return await crypto.subtle.verify({name:"ECDSA",hash:"SHA-256"},o,r(t),new TextEncoder().encode(n))}catch{return!1}}async function m(e){return!e||!e.pub||!e.sig?!1:i(e.pub,e.sig,p(e))}function p(e){let t=[e.v,e.kind,e.amount,e.asset,e.fiat||"",e.to||"",e.toName||"",e.from||"",e.fromName||"",e.memo||"",e.created||"",e.expires||""];return(e.hashlock||e.pub)&&t.push(e.hashlock||"",e.pub||""),t.join("|")}var d={USD:"$",EUR:"\u20AC",GBP:"\xA3"};function w(e){let t=Number(e.amount);return e.fiat?`${d[e.fiat]||""}${t.toFixed(2)}`:`${t%1===0?t:t.toFixed(4).replace(/0+$/,"")} ${e.asset}`}var f=/(https?:\/\/\S*pay-claim\.html#([A-Za-z0-9_-]+))|(holo:\/\/pay\/\S*#([A-Za-z0-9_-]+))/;function h(e){let t=String(e||"").match(f);if(!t)return null;let n=t[2]||t[4],o=t[0];try{let a=l(n);if(a&&a.v===1&&Number(a.amount)>0)return{intent:a,url:o,payload:n}}catch{}return null}export{y as a,m as b,w as c,h as d};
sha256/23aec66382775f5773693615338f37c37a5f58257ec95aece33a664baa6fe0db ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!doctype html>
2
+ <html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
3
+ <title>Holo Resolve</title><style>html,body{height:100%;margin:0;background:#0b141a}</style>
4
+ <script>
5
+ // Holo Resolve is UNIFIED into the root (github.io/Q). This stub forwards any already-shared
6
+ // /apps/resolve link — /apps/resolve/#<name> or /apps/resolve/?resolve=<name> — to the root: …/#<name>.
7
+ (function () {
8
+ try {
9
+ var q = new URLSearchParams(location.search).get("resolve");
10
+ var h = (location.hash || "").replace(/^#/, "");
11
+ var n = h || (q ? encodeURIComponent(q) : "");
12
+ location.replace(new URL("../../" + (n ? "#" + n : ""), location.href).href);
13
+ } catch (e) { location.replace(new URL("../../", location.href).href); }
14
+ })();
15
+ </script></head><body></body></html>
sha256/23e6f02a486ed64490ff8a2c59e4c641b40316a9c4542ae8c82d9a2c1540668e ADDED
The diff for this file is too large to render. See raw diff
 
sha256/24ae47813dc749b66bba0a21804680028e2d8947e81a0e49b81ddf00ea1d0778 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":{"games":"5de2be050dc5a4f9fcb464ffd45bbfcdca368ac02c39e5807ef6ce9430c45edd","hello":"66c1ada5ac08a2d4f73851bbe7458ad5fb6e2226c5a131c3829abfe715900fed","holo-games":"791a9ac999486256499d76a938345b173053b8de643cc89797bf946c38c365e3","holo-messenger":"778657c4b025982355658b5941fdd6ef3903c1ba965869527452982df162ddf8","resolve":"23aec66382775f5773693615338f37c37a5f58257ec95aece33a664baa6fe0db","self":"0511772584510f0888d97dd5a72bf986ad6791bd8e305063ddd8a82c39888a70","spaces":"d8aebefb67e7fc7802e757b9a7d95bb881e6d75b4a9bc4bd4227634541757e51"},"boot":{"entry":"holo-portal.mjs","sha256":"caeb3e57b6eeab09bee6d67c022145a8e2dae610c929a7f51bc169898625e7e8"},"closure":"0b308063d2fb1ce8aa570c2482e7d37bda7ac0467eb033f53d1ed264dfc0efb8","manifest":"9d5f633e4305f532c7abfce571aede6814ba15e2f6ddd2a6fcf3c79c8e1038ec","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":"9b84b30074d46c3ac16312c2839ff6e47a509d576a4fae3515fe9ca21ee59604"},"holstr:prev":"did:holo:sha256:0f60cc84c21bf2e134257400f0d48b58a2d7adddf9a8e8710054d6de099f6251","holstr:seq":168,"prov:generatedAtTime":"2026-07-12T13:37:42.763Z"}
sha256/2612bd713c62cfb24c2d49e12596055cca3d77911b63bc5470fabbfc469675e5 ADDED
The diff for this file is too large to render. See raw diff
 
sha256/287ce2d887ba0cea9ce037085d629fa5c662abde23f757484cee8b1309c30c7e 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":{"games":"6297af6925704585c0bd1cbe44c5c9da94953987688d4930725b33f8010752d9","holo-games":"791a9ac999486256499d76a938345b173053b8de643cc89797bf946c38c365e3","holo-messenger":"1e175094425429ed2ccb267298246a608e9e72b9641829e76d83238855456992","resolve":"23aec66382775f5773693615338f37c37a5f58257ec95aece33a664baa6fe0db","self":"0511772584510f0888d97dd5a72bf986ad6791bd8e305063ddd8a82c39888a70","spaces":"d8aebefb67e7fc7802e757b9a7d95bb881e6d75b4a9bc4bd4227634541757e51"},"closure":"99b2f4895cc2955b167d0b5a5146de0f9cef899b6c8da1842732c57226d6d2be","manifest":"fe042f6df0d16ed9e8907efc40c5d711b5cfb3eefaca053af8ff4417cc10f169","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":"39498b555b566bf89cb48eb3952a12760a14f75f2b3c1f45cbbdb3ab0bf57ba6"},"holstr:prev":"did:holo:sha256:a5becc3e18b904507200b31c043ead00887cfcb107443532b03a9c730ab9d001","holstr:seq":117,"prov:generatedAtTime":"2026-07-12T09:23:25.989Z"}
sha256/2905a32f569e289dbb9fb3bdb16bd57f011a12fa920f5396d3ec6d768f421c82 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":{"games":"5de2be050dc5a4f9fcb464ffd45bbfcdca368ac02c39e5807ef6ce9430c45edd","hello":"66c1ada5ac08a2d4f73851bbe7458ad5fb6e2226c5a131c3829abfe715900fed","holo-games":"791a9ac999486256499d76a938345b173053b8de643cc89797bf946c38c365e3","holo-messenger":"9fabcd6296e8af9390cf27a8e0573dd25a0d288e1e28f17aeb35698cc1c020e7","resolve":"23aec66382775f5773693615338f37c37a5f58257ec95aece33a664baa6fe0db","self":"0511772584510f0888d97dd5a72bf986ad6791bd8e305063ddd8a82c39888a70","spaces":"d8aebefb67e7fc7802e757b9a7d95bb881e6d75b4a9bc4bd4227634541757e51"},"closure":"15661c9995268912abc3a99e300819502e13e9d9cbf7d1de0cabc8332fa7ac75","manifest":"eb0f1bb597e5eca8cfa27e20883708720f443768f3fdb9bff9d89199579368d5","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":"132b34ac93f317fb151f9091f3f184ceff8b9e1b3e436ad1c92058c61143609e"},"holstr:prev":"did:holo:sha256:c5e997a7e1b7938495af072c67b990b9596ae64902c87efede18b1801426713a","holstr:seq":152,"prov:generatedAtTime":"2026-07-12T12:33:11.372Z"}
sha256/29f6cbec51d5d103e676c312ab27151e2aa98e38f05532b3ea4adec6df0b36bb ADDED
@@ -0,0 +1 @@
 
 
1
+ import{a as d,b as y,c as w,d as k}from"./chunk-67U7RCKQ.js";import"./chunk-SQE76S5B.js";function m(e){return["L1",e.type,e.kappa,e.hashlock||"",e.preimage||"",String(e.amount??""),e.asset||"",e.fiat||"",String(e.expires||""),e.by||"",String(e.at||"")].join("|")}async function v(e,{claimSecret:s,signer:a,at:t}){let i={type:"claim",kappa:e.kappa,hashlock:e.hashlock,preimage:s,amount:e.amount,asset:e.asset,fiat:e.fiat||null,expires:e.expires,by:a.pub,at:t};return i.sig=await a.sign(m(i)),i.tid=await d(m(i)),i}async function S(e,{signer:s,at:a}){let t={type:"refund",kappa:e.kappa,expires:e.expires,by:s.pub,at:a};return t.sig=await s.sign(m(t)),t.tid=await d(m(t)),t}async function b(e,s){return!e||!e.type||!e.kappa||!e.by||!e.sig||!await w(e.by,e.sig,m(e))?!1:e.type==="claim"?!(!e.preimage||await d(e.preimage)!==e.hashlock||e.expires&&e.at>e.expires):e.type==="refund"?!!(e.expires&&s>e.expires):!1}async function g(e,s=Date.now()){let a=[];for(let o of e)await b(o,s)&&a.push(o);let t=a.filter(o=>o.type==="claim");if(t.length){t.sort((r,n)=>r.at-n.at||(r.tid<n.tid?-1:r.tid>n.tid?1:0));let o=t[0];return{state:"claimed",winner:o.tid,by:o.by,amount:o.amount,asset:o.asset,fiat:o.fiat}}let i=a.filter(o=>o.type==="refund");return i.length?(i.sort((o,r)=>o.at-r.at||(o.tid<r.tid?-1:1)),{state:"refunded",winner:i[0].tid}):{state:"open"}}function D(e){return e&&e.transitions?Object.values(e.transitions):[]}async function j(e,s,a=Date.now()){e.transitions=e.transitions||{};for(let t of s||[])e.transitions[t.tid]||await b(t,a)&&(e.transitions[t.tid]=t);return e}var x=()=>({funded:!1,mode:"custodial",committed:null,transitions:{}}),h=new Map;function p(){return{get:e=>h.get(e)||null,set:(e,s)=>h.set(e,s),has:e=>h.has(e)}}function C(e="holo-escrow"){if(typeof localStorage>"u")return p();let s=a=>e+":"+a;return{get:a=>{try{let t=localStorage.getItem(s(a));return t?JSON.parse(t):null}catch{return null}},set:(a,t)=>{try{localStorage.setItem(s(a),JSON.stringify(t))}catch{}},has:a=>{try{return localStorage.getItem(s(a))!=null}catch{return!1}}}}async function E(e,{store:s=null,wallet:a=null,chain:t="base",nowMs:i=null}={}){let o=s||p();if(!e||!e.kappa)return{ok:!1,error:"no payment to fund"};if(!await k(e))return{ok:!1,error:"this payment isn't authentically signed, refusing to fund"};if(!e.hashlock)return{ok:!1,error:"this payment has no escrow lock"};let r=i||Date.now();if(e.expires&&r>e.expires)return{ok:!1,error:"already expired"};let n=o.get(e.kappa);if(n&&n.funded)return{ok:!0,kappa:e.kappa,mode:n.mode,live:n.mode==="live",tx:n.tx,already:!0};let c=a&&typeof a.htlcFund=="function"?"live":"custodial",f=null;if(c==="live")try{f=await a.htlcFund({chain:t,amount:e.amount,token:e.asset,hashlock:e.hashlock,timeout:e.expires})}catch(l){return{ok:!1,error:String(l&&l.message||l)}}let u=n||x();return u.funded=!0,u.mode=c,u.tx=f,u.amount=e.amount,u.asset=e.asset,u.fiat=e.fiat||null,u.expires=e.expires,o.set(e.kappa,u),{ok:!0,kappa:e.kappa,mode:c,live:c==="live",tx:f}}async function F(e,{claimSecret:s=null,claimerPub:a=null,claimerSigner:t=null,store:i=null,nowMs:o=null}={}){let r=i||p(),n=o||Date.now();if(!e||!e.kappa)return{ok:!1,error:"not a payment"};if(!await k(e))return{ok:!1,error:"this link isn't authentically signed"};if(e.expires&&n>e.expires)return{ok:!1,error:"this payment has expired"};let c=s||e.claimSecret;if(!c)return{ok:!1,error:"this link has no claim key"};if(await d(c)!==e.hashlock)return{ok:!1,error:"this claim key doesn't match the payment"};let f=t||await y(),u=await v(e,{claimSecret:c,signer:f,at:n}),l=r.get(e.kappa)||x();l.funded||(l.offDevice=!0);let M=l.committed&&l.committed!==u.tid;return l.transitions[u.tid]=u,l.committed||(l.committed=u.tid),r.set(e.kappa,l),M?{ok:!1,error:"already claimed"}:{ok:!0,amount:e.amount,asset:e.asset,fiat:e.fiat||null,mode:l.mode,live:l.mode==="live",offDevice:!!l.offDevice,tid:u.tid,claimerPub:a||f.pub,custodialWallet:"\u03BA:"+e.kappa.slice(0,16)}}async function J(e,{store:s=null,signer:a=null,nowMs:t=null}={}){let i=s||p(),o=t||Date.now();if(!e||!e.kappa)return{ok:!1,error:"not a payment"};let r=i.get(e.kappa);if(!r)return{ok:!1,error:"no escrow to refund"};let n=await g(Object.values(r.transitions),o);if(n.state==="claimed")return{ok:!1,error:"already claimed, can't refund"};if(n.state==="refunded")return{ok:!1,error:"already refunded"};if(!(r.expires&&o>r.expires))return{ok:!1,error:"not yet expired, can't refund"};let c=a||await y(),f=await S(e,{signer:c,at:o});return r.transitions[f.tid]=f,i.set(e.kappa,r),{ok:!0,amount:r.amount,asset:r.asset,mode:r.mode}}async function _(e,{store:s=null,nowMs:a=null}={}){let t=s||p(),i=a||Date.now(),o=typeof e=="string"?e:e&&e.kappa;if(!o)return{state:"none"};let r=t.get(o);if(!r)return{state:"none"};let n=await g(Object.values(r.transitions),i);return n.state==="open"?{state:r.funded?"open":"none",committed:r.committed||null,canonicalWinner:null,reconciled:!1}:{state:n.state,canonicalWinner:n.winner,committed:r.committed||null,reconciled:!!(r.committed&&n.state==="claimed"&&r.committed!==n.winner),by:n.by,amount:n.amount,asset:n.asset}}function N(e,{store:s=null}={}){let a=s||p(),t=typeof e=="string"?e:e&&e.kappa,i=a.get(t);return i?D(i):[]}async function L(e,s,{store:a=null,nowMs:t=null}={}){let i=a||p(),o=t||Date.now(),r=typeof e=="string"?e:e&&e.kappa,n=i.get(r)||x();return await j(n,s||[],o),i.set(r,n),_(r,{store:i,nowMs:o})}export{F as claimEscrow,N as exportPeer,E as fund,C as localStore,L as mergePeer,J as refund,_ as status};
sha256/2aa7375ed161dd5b85b93ee612036d9f8f163af65fe6558b8dce8b0984467ea8 ADDED
@@ -0,0 +1 @@
 
 
1
+ function l(e){let t=e.replace(/-/g,"+").replace(/_/g,"/"),n=decodeURIComponent(escape(atob(t)));return JSON.parse(n)}var i=["v","kind","amount","asset","fiat","to","toName","from","fromName","memo","created","expires","claimSecret","pub","sig"];function u(e){let t=l(e);if(!Array.isArray(t))return t;let n={};return i.forEach((o,a)=>{t[a]!=null&&(n[o]=t[a])}),n.memo==null&&(n.memo=""),n}async function p(e){let t=new TextEncoder().encode(e),n=await crypto.subtle.digest("SHA-256",t);return[...new Uint8Array(n)].map(o=>o.toString(16).padStart(2,"0")).join("")}var y=p;function r(e){let t="";for(let n of e)t+=String.fromCharCode(n);return btoa(t).replace(/\+/g,"-").replace(/\//g,"_").replace(/=+$/,"")}function s(e){let t=atob(e.replace(/-/g,"+").replace(/_/g,"/")),n=new Uint8Array(t.length);for(let o=0;o<t.length;o++)n[o]=t.charCodeAt(o);return n}async function w(){let e=await crypto.subtle.generateKey({name:"ECDSA",namedCurve:"P-256"},!0,["sign","verify"]),t=new Uint8Array(await crypto.subtle.exportKey("raw",e.publicKey));return{pub:r(t),sign:async n=>r(new Uint8Array(await crypto.subtle.sign({name:"ECDSA",hash:"SHA-256"},e.privateKey,new TextEncoder().encode(n))))}}async function c(e,t,n){try{let o=await crypto.subtle.importKey("raw",s(e),{name:"ECDSA",namedCurve:"P-256"},!1,["verify"]);return await crypto.subtle.verify({name:"ECDSA",hash:"SHA-256"},o,s(t),new TextEncoder().encode(n))}catch{return!1}}var h=c;async function b(e){return!e||!e.pub||!e.sig?!1:c(e.pub,e.sig,d(e))}function d(e){let t=[e.v,e.kind,e.amount,e.asset,e.fiat||"",e.to||"",e.toName||"",e.from||"",e.fromName||"",e.memo||"",e.created||"",e.expires||""];return(e.hashlock||e.pub)&&t.push(e.hashlock||"",e.pub||""),t.join("|")}var f={USD:"$",EUR:"\u20AC",GBP:"\xA3"};function g(e){let t=Number(e.amount);return e.fiat?`${f[e.fiat]||""}${t.toFixed(2)}`:`${t%1===0?t:t.toFixed(4).replace(/0+$/,"")} ${e.asset}`}var m=/(https?:\/\/\S*pay-claim\.html#([A-Za-z0-9_-]+))|(holo:\/\/pay\/\S*#([A-Za-z0-9_-]+))/;function S(e){let t=String(e||"").match(m);if(!t)return null;let n=t[2]||t[4],o=t[0];try{let a=u(n);if(a&&a.v===1&&Number(a.amount)>0)return{intent:a,url:o,payload:n}}catch{}return null}export{y as a,w as b,h as c,b as d,g as e,S as f};
sha256/2dab033a00555cbc3b8b877f23625ff7a39ba88588fa7640b80be9b6dcb9b150 ADDED
The diff for this file is too large to render. See raw diff
 
sha256/2dd0c36b154a559a9f45264c53decbaca48aaba7315a7b4c5fcbe498c4cf4893 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":{"games":"5de2be050dc5a4f9fcb464ffd45bbfcdca368ac02c39e5807ef6ce9430c45edd","hello":"66c1ada5ac08a2d4f73851bbe7458ad5fb6e2226c5a131c3829abfe715900fed","holo-games":"791a9ac999486256499d76a938345b173053b8de643cc89797bf946c38c365e3","holo-messenger":"fdca25d9458eb8f38d9a82c46df2105c07696867ab146c4c005c8ff1e80889f7","resolve":"23aec66382775f5773693615338f37c37a5f58257ec95aece33a664baa6fe0db","self":"0511772584510f0888d97dd5a72bf986ad6791bd8e305063ddd8a82c39888a70","spaces":"d8aebefb67e7fc7802e757b9a7d95bb881e6d75b4a9bc4bd4227634541757e51"},"boot":{"entry":"holo-portal.mjs","sha256":"caeb3e57b6eeab09bee6d67c022145a8e2dae610c929a7f51bc169898625e7e8"},"closure":"66efbe9713e0d70c83dfe93f2b30509af3d3bfe45d761f42f85069fb50d8087b","manifest":"16b4749d6aea6608d5dc966ae832e649c153dc33efbda229036f34af71998197","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":"4f23cccd368f7c2cff9e75c5031bed8c2d606329013248f211fbb519a830d26e"},"holstr:prev":"did:holo:sha256:ada52cd5e41f493ecdd7e0991f2d3f3c36dfb3c111f5aa15191a9816b80979c1","holstr:seq":172,"prov:generatedAtTime":"2026-07-12T14:13:05.862Z"}
sha256/2e2dc1bf446e5ca1ec4bffa421947dc22143efd64c054e6cb0cc6a8f91fc23fe 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":{"games":"5de2be050dc5a4f9fcb464ffd45bbfcdca368ac02c39e5807ef6ce9430c45edd","hello":"66c1ada5ac08a2d4f73851bbe7458ad5fb6e2226c5a131c3829abfe715900fed","holo-games":"791a9ac999486256499d76a938345b173053b8de643cc89797bf946c38c365e3","holo-messenger":"9fabcd6296e8af9390cf27a8e0573dd25a0d288e1e28f17aeb35698cc1c020e7","resolve":"23aec66382775f5773693615338f37c37a5f58257ec95aece33a664baa6fe0db","self":"0511772584510f0888d97dd5a72bf986ad6791bd8e305063ddd8a82c39888a70","spaces":"d8aebefb67e7fc7802e757b9a7d95bb881e6d75b4a9bc4bd4227634541757e51"},"closure":"514a5b90570134cc9b262fbbda1b4177c92e9c92b5a45c0ba48ea3309cbc7711","manifest":"eb0f1bb597e5eca8cfa27e20883708720f443768f3fdb9bff9d89199579368d5","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":"132b34ac93f317fb151f9091f3f184ceff8b9e1b3e436ad1c92058c61143609e"},"holstr:prev":"did:holo:sha256:9a6482dc9f6a3dea0e3eea21a5f4ce3559012f18273a9e49a5320facf8dc2448","holstr:seq":150,"prov:generatedAtTime":"2026-07-12T12:21:35.844Z"}
sha256/30774d033489de9e35a6d645425eb9486c72cf13d8703346eb3065d4d234e641 ADDED
The diff for this file is too large to render. See raw diff
 
sha256/310c5b0dfd9e59f3fce276da94235913365a0e8570c2ce5d9290213ca0f3067d ADDED
@@ -0,0 +1,378 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // messenger-shadcn-ui.mjs — DROP-IN mount(el, model) rendering the Holo Messenger UX from STREAMING
2
+ // shadcn κ-components, faithful to the real app (network app-rail · Q sidebar · triage · rich rows ·
3
+ // Q chat pane · earth wallpaper · real SVG line icons), same contract as the chatscope bundle.
4
+ //
5
+ // ADDITIVE + SAFE: only takes over window.HoloMessengerUI.mount on opt-in (?ui=shadcn or
6
+ // localStorage["holo.ui.shadcn"]="1"). Default boot stays the untouched chatscope UI.
7
+ //
8
+ // PERF: memoized rows · composer owns its draft · windowed thread (≤WINDOW rows in DOM; 1M chat stays tiny).
9
+ // PASS 1 = faithful shell. Rich message content + action surface are Pass 2/3.
10
+
11
+ const WANT = (() => {
12
+ try { const u = new URL(location.href);
13
+ if (u.searchParams.get("ui") === "shadcn") return true;
14
+ if (u.searchParams.get("ui") === "chatscope") return false;
15
+ return localStorage.getItem("holo.ui.shadcn") === "1";
16
+ } catch { return false; }
17
+ })();
18
+
19
+ const WINDOW = 80;
20
+ const BRANDS = "/usr/share/brands/";
21
+ const WP = "/apps/holo-messenger/_vendor/wallpaper-default.jpg"; // the earth photo (default)
22
+ const NET_SLUG = { whatsapp: "whatsapp", telegram: "telegram", signal: "signal", imessage: "imessage", instagram: "instagram", messenger: "messenger", discord: "discord", slack: "slack", gmessages: "googlemessages", googlemessages: "googlemessages", x: "x", twitter: "x", linkedin: "linkedin", gmail: "gmail", email: "gmail", internal: null };
23
+
24
+ const CSS = `
25
+ .wa{height:100dvh;display:grid;grid-template-columns:64px minmax(300px,.46fr) minmax(0,1fr);background:#0b141a;color:#e9edef;
26
+ font:14.5px/1.5 ui-sans-serif,system-ui,-apple-system,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif;-webkit-font-smoothing:antialiased;text-rendering:optimizeLegibility}
27
+ .wa *{box-sizing:border-box}
28
+ .wa svg{display:block}
29
+ .rail{background:#111b21;border-right:1px solid #0a1015;display:flex;flex-direction:column;align-items:center;gap:7px;padding:12px 0}
30
+ .rb{position:relative;width:46px;height:46px;border-radius:15px;display:grid;place-items:center;cursor:pointer;color:#aebac1;transition:background .12s,color .12s;background:transparent;border:0}
31
+ .rb:hover{background:#202c33;color:#e9edef}.rb.on{background:#2a3942;color:#00a884}
32
+ .rb img{width:28px;height:28px;border-radius:8px;object-fit:cover}
33
+ .rb .dot{position:absolute;right:5px;bottom:5px;width:10px;height:10px;border-radius:50%;background:#00d95f;border:2px solid #111b21}
34
+ .rb .bdg{position:absolute;top:-3px;right:-3px;min-width:19px;height:19px;border-radius:10px;background:#00a884;color:#0b141a;font-size:11px;font-weight:700;display:grid;place-items:center;padding:0 4px;border:2px solid #111b21}
35
+ .rsep{width:28px;height:1px;background:#22303a;margin:3px 0}
36
+ .rgrow{flex:1 1 auto}
37
+ .rav{width:40px;height:40px;border-radius:50%;overflow:hidden;cursor:pointer;background:radial-gradient(circle at 35% 30%,#7cf0c8,#2b9e7a 60%,#0b3b2e);margin-top:2px}
38
+ .side{background:#111b21;border-right:1px solid #222d34;display:flex;flex-direction:column;min-width:0}
39
+ .stitle{display:flex;align-items:center;gap:6px;padding:15px 14px 10px}
40
+ .stitle .t{font-size:23px;font-weight:600;letter-spacing:-.4px;flex:1 1 auto}
41
+ .search{padding:4px 12px 8px;position:relative}
42
+ .search .si{position:absolute;left:26px;top:50%;transform:translateY(-50%);color:#8696a0;pointer-events:none;z-index:1;display:grid}
43
+ .filt{display:flex;align-items:center;gap:8px;padding:2px 14px 8px}
44
+ .chip{display:inline-flex;align-items:center;gap:6px;background:#202c33;color:#8696a0;border:0;border-radius:999px;padding:5px 14px;font-size:13px;cursor:pointer}
45
+ .chip.on{background:#0b3b2e;color:#00d95f}
46
+ .caret{background:#202c33;color:#8696a0;border:0;border-radius:999px;width:30px;height:28px;display:grid;place-items:center;cursor:pointer;font-size:11px}
47
+ .triage{margin:2px 12px 8px;display:flex;align-items:center;gap:9px;background:linear-gradient(90deg,#0e2a24,#122a1e);color:#7cf0c8;border:1px solid #123c30;border-radius:14px;padding:9px 14px;font-size:13.5px;cursor:pointer}
48
+ .triage .sk{color:#00d95f;display:grid;flex:0 0 auto}
49
+ .chats{flex:1 1 auto;overflow-y:auto;overflow-x:hidden}
50
+ .chats::-webkit-scrollbar{width:6px}.chats::-webkit-scrollbar-thumb{background:#374248;border-radius:6px}
51
+ .row{display:flex;gap:13px;align-items:center;padding:9px 14px;cursor:pointer;position:relative}
52
+ .row:after{content:"";position:absolute;left:78px;right:0;bottom:0;height:1px;background:#1c262d}
53
+ .row:hover{background:#202c33}.row.on{background:#2a3942}.row.on:after{background:transparent}
54
+ .av{position:relative;flex:0 0 auto;border-radius:50%;overflow:hidden}
55
+ .av img{width:100%;height:100%;object-fit:cover;display:block}
56
+ .row .b{min-width:0;flex:1 1 auto}
57
+ .row .n{color:#e9edef;font-size:16px;font-weight:400;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;display:flex;align-items:center;gap:6px}
58
+ .row .p{color:#8696a0;font-size:13.5px;margin-top:2px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;display:flex;align-items:center;gap:3px}
59
+ .row .rc{color:#53bdeb}
60
+ .row .m{margin-left:auto;text-align:right;display:flex;flex-direction:column;align-items:flex-end;gap:6px;flex:0 0 auto}
61
+ .row .t{color:#8696a0;font-size:12px}.row.unread .t{color:#00d95f;font-weight:500}
62
+ .row .pin{color:#f15c6d;font-size:13px;transform:rotate(45deg)}
63
+ .netdot{width:16px;height:16px;border-radius:4px;object-fit:cover;flex:0 0 auto}
64
+ .main{position:relative;display:flex;flex-direction:column;min-width:0;background:#0b141a}
65
+ .wp{position:absolute;inset:0;background:#0b141a center/cover no-repeat;z-index:0}
66
+ .wp:after{content:"";position:absolute;inset:0;background:linear-gradient(rgba(11,20,26,.35),rgba(11,20,26,.5))}
67
+ .mh,.thread,.composer,.e2ewrap{position:relative;z-index:1}
68
+ .mh{height:60px;flex:0 0 auto;background:#202c33;display:flex;align-items:center;gap:14px;padding:0 16px}
69
+ .mh .who .n{font-size:16px;color:#e9edef;font-weight:500}
70
+ .mh .who .s{font-size:13px;color:#00a884;margin-top:1px}
71
+ .mh .sp{margin-left:auto}.mh .icons{display:flex;gap:2px}
72
+ .ico{width:40px;height:40px;border-radius:50%;display:grid;place-items:center;color:#aebac1;cursor:pointer;transition:background .12s;background:transparent;border:0}
73
+ .ico:hover{background:#ffffff14;color:#e9edef}
74
+ .ico.sk{color:#00d95f}
75
+ .e2ewrap{display:flex;justify-content:center;padding:14px 0 2px}
76
+ .e2e{background:#182229;color:#ffd279;font-size:12.5px;padding:6px 14px;border-radius:8px;box-shadow:0 1px .5px #0003}
77
+ .thread{flex:1 1 auto;overflow-y:auto;overflow-x:hidden;padding:10px 8% 14px;display:flex;flex-direction:column;gap:3px}
78
+ .thread::-webkit-scrollbar{width:6px}.thread::-webkit-scrollbar-thumb{background:#374248cc;border-radius:6px}
79
+ .more{align-self:center;color:#8696a0;font-size:12px;padding:8px;opacity:.85}
80
+ .day{align-self:center;background:#182229;color:#8696a0;font-size:12.5px;padding:5px 12px;border-radius:8px;margin:8px 0 6px;box-shadow:0 1px .5px #0003}
81
+ .bub{max-width:65%;padding:6px 9px 8px 10px;border-radius:8px;font-size:14.2px;line-height:1.42;box-shadow:0 1px .5px #0003;word-wrap:break-word;white-space:pre-wrap}
82
+ .in{align-self:flex-start;background:#202c33;border-top-left-radius:0}
83
+ .out{align-self:flex-end;background:#005c4b;border-top-right-radius:0}
84
+ .grp{margin-top:8px}
85
+ .bub .tt{font-size:11px;color:#ffffff8a;float:right;margin:6px 0 -3px 12px;position:relative;top:4px}
86
+ .bub .tt .rc{color:#53bdeb}
87
+ .empty{margin:auto;color:#8696a0;text-align:center;background:#182229;padding:8px 16px;border-radius:8px}
88
+ .composer{flex:0 0 auto;background:#202c33;display:flex;align-items:center;gap:6px;padding:8px 14px}
89
+ .composer .grow{flex:1 1 auto}
90
+ .send{width:44px;height:44px;border-radius:50%;background:#00a884;color:#0b141a;border:0;display:grid;place-items:center;cursor:pointer;flex:0 0 auto}
91
+ /* media */
92
+ .m-img{display:block;max-width:320px;max-height:340px;border-radius:6px;cursor:pointer;object-fit:cover}
93
+ .m-vid{position:relative;max-width:320px;border-radius:6px;overflow:hidden;cursor:pointer}
94
+ .m-vid img{display:block;width:100%;border-radius:6px}
95
+ .m-play{position:absolute;inset:0;display:grid;place-items:center}
96
+ .m-play span{width:48px;height:48px;border-radius:50%;background:#0009;color:#fff;display:grid;place-items:center;font-size:20px}
97
+ .m-file{display:flex;align-items:center;gap:10px;background:#ffffff0f;border-radius:8px;padding:9px 11px;min-width:200px;cursor:pointer}
98
+ .m-file .fi{width:38px;height:38px;border-radius:8px;background:#00a88433;color:#7cf0c8;display:grid;place-items:center;font-size:16px;flex:0 0 auto}
99
+ .m-file .fn{font-size:13.5px;color:#e9edef;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
100
+ .m-file .fs{font-size:12px;color:#8696a0;margin-top:2px}
101
+ .m-aud{display:flex;align-items:center;gap:10px;min-width:220px;padding:2px 0}
102
+ .m-aud audio{height:34px}
103
+ .lb{position:fixed;inset:0;background:#000c;z-index:60;display:grid;place-items:center;cursor:zoom-out}
104
+ .lb img,.lb video{max-width:90vw;max-height:90vh;border-radius:8px}
105
+ .lb .x{position:absolute;top:18px;right:22px;color:#fff;font-size:28px;cursor:pointer;background:0;border:0}
106
+ /* attach tray + pay sheet */
107
+ .tray{position:absolute;bottom:66px;left:14px;background:#233138;border-radius:12px;box-shadow:0 10px 34px #0009;padding:6px;z-index:6;min-width:236px}
108
+ .tray button{display:flex;align-items:center;gap:11px;width:100%;background:0;border:0;color:#e9edef;font-size:14.5px;padding:10px 12px;border-radius:8px;cursor:pointer;text-align:left}
109
+ .tray button:hover{background:#2a3942}.tray button:disabled{opacity:.5}
110
+ .tray .sep{height:1px;background:#2a3942;margin:4px 6px}
111
+ .paywrap{position:fixed;inset:0;background:#000a;z-index:60;display:grid;place-items:center}
112
+ .pay{background:#111b21;border-radius:14px;padding:20px;width:346px;max-width:90vw;box-shadow:0 20px 60px #000a}
113
+ .pay h3{margin:0 0 4px;font-size:17px}.pay .psub{color:#8696a0;font-size:13px;margin-bottom:14px}
114
+ .pay .seg{display:flex;background:#202c33;border-radius:10px;padding:3px;margin-bottom:14px}
115
+ .pay .seg button{flex:1;background:0;border:0;color:#8696a0;padding:8px;border-radius:8px;cursor:pointer;font-size:14px}
116
+ .pay .seg button.on{background:#00a884;color:#0b141a;font-weight:600}
117
+ .pay .amt{width:100%;background:#202c33;border:0;border-radius:10px;color:#e9edef;font-size:26px;text-align:center;padding:12px;margin-bottom:10px;outline:0}
118
+ .pay .chips{display:flex;gap:8px;margin-bottom:12px}
119
+ .pay .chips button{flex:1;background:#202c33;border:0;color:#e9edef;border-radius:8px;padding:9px;cursor:pointer}
120
+ .pay .chips button.on{background:#0b3b2e;color:#00d95f}
121
+ .pay .memo{width:100%;background:#202c33;border:0;border-radius:10px;color:#e9edef;padding:10px 12px;margin-bottom:12px;font-size:14px;outline:0}
122
+ .pay .err{color:#f15c6d;font-size:13px;margin-bottom:8px;text-align:center}
123
+ .pay .go{width:100%;background:#00a884;color:#0b141a;border:0;border-radius:10px;padding:12px;font-weight:600;cursor:pointer;font-size:15px}
124
+ .pay .go:disabled{opacity:.5;cursor:default}
125
+ .pay .cancel{width:100%;background:0;color:#8696a0;border:0;padding:10px;margin-top:6px;cursor:pointer}
126
+ `;
127
+
128
+ async function ensureImportmap() {
129
+ if (document.querySelector('script[type="importmap"][data-holo-ui]')) return;
130
+ const map = await (await fetch("/apps/ui/vendor/importmap.json")).json();
131
+ const imports = {}; for (const [k, v] of Object.entries(map.imports || map)) imports[k] = typeof v === "string" ? v.replace(/^\.\//, "/apps/ui/") : v;
132
+ const s = document.createElement("script"); s.type = "importmap"; s.setAttribute("data-holo-ui", "1"); s.textContent = JSON.stringify({ imports }); document.head.appendChild(s);
133
+ }
134
+ let _deps = null;
135
+ async function deps() {
136
+ if (_deps) return _deps;
137
+ await ensureImportmap();
138
+ const React = await import("react");
139
+ const { createRoot } = await import("react-dom/client");
140
+ const reg = await (await fetch("/apps/ui/registry/index.json")).json();
141
+ const K = Object.fromEntries(reg.components.map((c) => [c.name, c.holo]));
142
+ const [button, badge, avatar, input] = await Promise.all(["button", "badge", "avatar", "input"].map((n) => import(K[n])));
143
+ return (_deps = { React, createRoot, C: { button, badge, avatar, input } });
144
+ }
145
+ if (!document.getElementById("wa-shadcn-css")) { const st = document.createElement("style"); st.id = "wa-shadcn-css"; st.textContent = CSS; document.head.appendChild(st); }
146
+
147
+ const initials = (s) => (String(s || "?").trim().split(/\s+/).map((w) => w[0]).join("").slice(0, 2) || "?").toUpperCase();
148
+ const hue = (s) => { let h = 0; s = String(s || ""); for (let i = 0; i < s.length; i++) h = (h * 31 + s.charCodeAt(i)) >>> 0; return `hsl(${h % 360} 40% 46%)`; };
149
+ const isOut = (m) => !!(m && (m.mine || m.out || m.fromMe || m.dir === "out" || m.from === "me" || m.self));
150
+ const threadOf = (model, id) => { try { if (id && model.thread) return model.thread(id) || []; } catch {} return (model.threads && model.threads[id]) || []; };
151
+ const rcMark = (m) => m.status === "read" || m.status === "delivered" ? "✓✓" : m.status === "sent" ? "✓" : "";
152
+ const slugOf = (net) => { const k = String(net || "").toLowerCase(); return NET_SLUG[k] === undefined ? k : NET_SLUG[k]; };
153
+ const mimeKind = (mime, kind) => { const k = String(kind || "").toLowerCase(); if (["image", "video", "audio", "voice"].includes(k)) return k === "voice" ? "audio" : k; const t = String(mime || "").split("/")[0]; if (t === "image" || t === "video" || t === "audio") return t; return "file"; };
154
+ const humanSize = (n) => { n = +n || 0; if (!n) return ""; const u = ["B", "KB", "MB", "GB"]; let i = 0; while (n >= 1024 && i < u.length - 1) { n /= 1024; i++; } return (n < 10 && i ? n.toFixed(1) : Math.round(n)) + " " + u[i]; };
155
+ const fileGlyph = (mime, name) => { const m = String(mime || ""), e = String(name || "").split(".").pop().toLowerCase(); if (m.includes("pdf") || e === "pdf") return "📕"; if (["zip", "rar", "7z"].includes(e)) return "🗜"; if (["doc", "docx"].includes(e)) return "📘"; if (["xls", "xlsx", "csv"].includes(e)) return "📗"; return "📄"; };
156
+
157
+ function buildApp({ React, C }) {
158
+ const { createElement: h, memo, useState, useRef, useEffect, useCallback, useLayoutEffect } = React;
159
+ const Avatar = C.avatar.Avatar, AvatarImage = C.avatar.AvatarImage, AvatarFallback = C.avatar.AvatarFallback;
160
+ const Button = C.button.Button, Input = C.input.Input, Badge = C.badge.Badge;
161
+
162
+ // exact SVG line icons (from the real app's IC set)
163
+ const svg = (kids, s = 22, w = 1.8) => h("svg", { viewBox: "0 0 24 24", width: s, height: s, fill: "none", stroke: "currentColor", strokeWidth: w, strokeLinecap: "round", strokeLinejoin: "round" }, ...kids);
164
+ const P = (d, e) => h("path", { d, ...e });
165
+ const CIR = (cx, cy, r, e) => h("circle", { cx, cy, r, ...e });
166
+ const RC = (x, y, width, height, rx, e) => h("rect", { x, y, width, height, rx, ...e });
167
+ const fillC = { fill: "currentColor", stroke: "none" };
168
+ const IC = {
169
+ chats: () => svg([P("M20 11.4A7.6 7.6 0 0 1 8.9 18.2L4.5 19.4l1.2-4.2A7.6 7.6 0 1 1 20 11.4Z")]),
170
+ search: (s) => svg([CIR(11, 11, 7), P("m20 20-3.6-3.6")], s || 19),
171
+ phone: () => svg([P("M6.6 4H4.5A1.5 1.5 0 0 0 3 5.6 16 16 0 0 0 18.4 21a1.5 1.5 0 0 0 1.6-1.5v-2.1a1.5 1.5 0 0 0-1.2-1.45l-2.2-.45a1.5 1.5 0 0 0-1.5.6l-.5.7a12 12 0 0 1-5.1-5.1l.7-.5a1.5 1.5 0 0 0 .6-1.5l-.45-2.2A1.5 1.5 0 0 0 6.6 4Z")]),
172
+ video: () => svg([RC(3, 6.5, 12, 11, 2.5), P("m15 10.5 5.5-3v9l-5.5-3Z")]),
173
+ menu: () => svg([CIR(12, 5, 1.35, fillC), CIR(12, 12, 1.35, fillC), CIR(12, 19, 1.35, fillC)]),
174
+ newchat: () => svg([P("M13.5 4H6a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-7.5"), P("M18.4 3.6a2 2 0 0 1 2.8 2.8L13 14.6l-3.4.9.9-3.4 7.9-8.5Z")]),
175
+ emoji: () => svg([CIR(12, 12, 8.5), P("M8.8 14.2a4.2 4.2 0 0 0 6.4 0"), CIR(9, 10, 1, fillC), CIR(15, 10, 1, fillC)]),
176
+ plus: () => svg([P("M12 5.5v13M5.5 12h13")]),
177
+ mic: () => svg([RC(9, 3, 6, 11, 3), P("M5.5 11a6.5 6.5 0 0 0 13 0M12 17.5V21M8.5 21h7")]),
178
+ send: () => svg([P("M4.5 12 20.5 4.5 13 20.5l-2.8-6.4L4.5 12Z")]),
179
+ spark: (s) => svg([P("M12 3.2l1.7 4.6 4.6 1.7-4.6 1.7L12 15.8l-1.7-4.6L5.7 9.5l4.6-1.7L12 3.2Z"), P("M5.5 15.5l.8 2 2 .8-2 .8-.8 2-.8-2-2-.8 2-.8.8-2Z")], s || 20),
180
+ sun: () => svg([CIR(12, 12, 4), P("M12 2v2M12 20v2M4 12H2M22 12h-2M5.6 5.6 4.2 4.2M19.8 19.8l-1.4-1.4M18.4 5.6l1.4-1.4M4.2 19.8l1.4-1.4")]),
181
+ plusThin: () => svg([P("M12 5.5v13M5.5 12h13")], 26, 1.6),
182
+ };
183
+
184
+ const Av = (name, size, bg, src) => h("div", { className: "av", style: { height: size + "px", width: size + "px" } },
185
+ h(Avatar, { style: { height: "100%", width: "100%", background: bg || hue(name), display: "grid", placeItems: "center" } },
186
+ src ? h(AvatarImage, { src, alt: "", style: { height: "100%", width: "100%", objectFit: "cover" } }) : null,
187
+ h(AvatarFallback, { style: { background: bg || hue(name), color: "#fff", fontSize: (size * 0.36) + "px", fontWeight: 500, height: "100%", width: "100%", display: "grid", placeItems: "center" } }, initials(name))));
188
+ const Ico = (icon, onClick, title, cls) => h("button", { className: "ico" + (cls ? " " + cls : ""), onClick, title }, icon);
189
+ const netLogo = (net) => { const s = slugOf(net); return s ? h("img", { className: "netdot", src: BRANDS + s + ".svg", alt: "", onError: (e) => { e.currentTarget.style.display = "none"; } }) : null; };
190
+
191
+ // rich media in a bubble — image/video/audio/file, with lazy bridge-media resolution
192
+ function MediaView({ media, model, onMedia }) {
193
+ const [url, setUrl] = useState(media.url || media.thumb || "");
194
+ const bucket = mimeKind(media.mime, media.kind);
195
+ useEffect(() => { let alive = true;
196
+ if (!media.url && (media.lazy || media.pending || media.ref) && model.resolveBridgeMedia) {
197
+ Promise.resolve(model.resolveBridgeMedia(media)).then((u) => { if (alive && u) setUrl(typeof u === "string" ? u : (u.url || u.src || "")); }).catch(() => {});
198
+ } return () => { alive = false; }; }, []);
199
+ if (bucket === "image") return url ? h("img", { className: "m-img", src: url, alt: "", loading: "lazy", onClick: () => onMedia({ kind: "image", url }) }) : h("div", { className: "m-file" }, "🖼 Photo");
200
+ if (bucket === "video") return h("div", { className: "m-vid", onClick: () => onMedia({ kind: "video", url }) }, h("img", { src: media.thumb || url, alt: "" }), h("div", { className: "m-play" }, h("span", {}, "▶")));
201
+ if (bucket === "audio") return h("div", { className: "m-aud" }, url ? h("audio", { controls: true, src: url }) : h("span", {}, "🎤 Voice message"));
202
+ return h("div", { className: "m-file", onClick: () => url && window.open(url, "_blank") }, h("div", { className: "fi" }, fileGlyph(media.mime, media.filename)), h("div", { style: { minWidth: 0 } }, h("div", { className: "fn" }, media.filename || "File"), media.size ? h("div", { className: "fs" }, humanSize(media.size)) : null));
203
+ }
204
+
205
+ const Rail = memo(function Rail({ nets, active, totalUnread, onPick }) {
206
+ return h("nav", { className: "rail" },
207
+ h("button", { className: "rb" + (active === "All" ? " on" : ""), title: "All chats", onClick: () => onPick("All") }, IC.chats(), totalUnread ? h("span", { className: "bdg" }, totalUnread > 99 ? "99+" : totalUnread) : null),
208
+ nets.length ? h("div", { className: "rsep" }) : null,
209
+ nets.map((n) => h("button", { key: n.id, className: "rb" + (active === n.id ? " on" : ""), title: n.label, onClick: () => onPick(n.id) },
210
+ (() => { const s = slugOf(n.id); return s ? h("img", { src: BRANDS + s + ".svg", alt: n.label, onError: (e) => { const t = document.createTextNode((n.label || "?")[0].toUpperCase()); try { e.currentTarget.replaceWith(t); } catch {} } }) : (n.label || "?")[0].toUpperCase(); })(),
211
+ h("span", { className: "dot" }), n.unread ? h("span", { className: "bdg" }, n.unread > 99 ? "99+" : n.unread) : null)),
212
+ h("button", { className: "rb", title: "Add network", onClick: () => onPick("+") }, IC.plusThin()),
213
+ h("div", { className: "rgrow" }),
214
+ h("div", { className: "rav" }));
215
+ });
216
+
217
+ const Row = memo(function Row({ c, on, preview, receipt, onSelect }) {
218
+ return h("div", { className: "row" + (on ? " on" : "") + (c.unread ? " unread" : ""), onClick: () => onSelect(c.id) },
219
+ Av(c.name, 49, c.isQ ? "#12b886" : hue(c.name), c.avatar),
220
+ h("div", { className: "b" },
221
+ h("div", { className: "n" }, c.name || c.id || "Chat", c.network ? netLogo(c.network) : null),
222
+ h("div", { className: "p" }, receipt ? h("span", { className: "rc" }, receipt + " ") : null, preview)),
223
+ h("div", { className: "m" },
224
+ h("div", { className: "t" }, c.time || c.ts || ""),
225
+ c.pinned ? h("span", { className: "pin" }, "📌") : null,
226
+ c.unread ? h(Badge, { style: { background: "#00a884", color: "#0b141a", borderRadius: "999px", height: "20px", minWidth: "20px", justifyContent: "center", padding: "0 6px", fontSize: "12px", fontWeight: 600 } }, String(c.unread)) : null));
227
+ });
228
+
229
+ function Thread({ model, activeId, onMedia }) {
230
+ const all = threadOf(model, activeId);
231
+ const [limit, setLimit] = useState(WINDOW);
232
+ const ref = useRef(null), prevId = useRef(activeId), anchorH = useRef(0), pin = useRef(true);
233
+ const toBottom = (el) => { el.scrollTop = el.scrollHeight; };
234
+ useEffect(() => { setLimit(WINDOW); }, [activeId]);
235
+ // Runs after every render (no deps): a fresh chat, or new messages arriving, both flow through here.
236
+ // While "pinned" we stay glued to the newest message — exactly like WhatsApp.
237
+ useLayoutEffect(() => { const el = ref.current; if (!el) return;
238
+ if (prevId.current !== activeId) { prevId.current = activeId; pin.current = true; anchorH.current = 0; toBottom(el); return; }
239
+ if (anchorH.current) { el.scrollTop = el.scrollHeight - anchorH.current; anchorH.current = 0; return; }
240
+ if (pin.current) toBottom(el); });
241
+ // Media resolves async (placeholder first, <img> swapped in later) and reflows the thread AFTER layout.
242
+ // A ResizeObserver on the scroll container won't catch it (its own box size never changes), and per-image
243
+ // listeners miss images added later — so listen for `load` in the CAPTURE phase on the container, which
244
+ // fires for ANY descendant image whenever it appears/loads. Keeps us glued to the newest message.
245
+ useEffect(() => { const el = ref.current; if (!el) return; pin.current = true; toBottom(el);
246
+ requestAnimationFrame(() => { if (pin.current && ref.current) toBottom(ref.current); });
247
+ const stick = () => { if (pin.current && ref.current) toBottom(ref.current); };
248
+ el.addEventListener("load", stick, true); el.addEventListener("error", stick, true);
249
+ return () => { el.removeEventListener("load", stick, true); el.removeEventListener("error", stick, true); };
250
+ }, [activeId]);
251
+ // Detach when the user scrolls up; re-attach ("jump to latest") once they're back near the bottom.
252
+ const onScroll = useCallback((e) => { const el = e.currentTarget;
253
+ pin.current = el.scrollHeight - el.scrollTop - el.clientHeight < 80;
254
+ if (el.scrollTop < 120 && limit < all.length) { anchorH.current = el.scrollHeight; setLimit((l) => Math.min(all.length, l + WINDOW)); } }, [limit, all.length]);
255
+ const start = Math.max(0, all.length - limit), shown = all.slice(start);
256
+ if (!all.length) return h("div", { className: "thread", ref }, h("div", { className: "empty" }, "No messages yet. Say hello 👋"));
257
+ const rows = [h("div", { className: "e2ewrap", key: "e2e" }, h("div", { className: "e2e" }, "🔒 Messages are end-to-end encrypted")), h("div", { className: "day", key: "day" }, "Today")];
258
+ if (start > 0) rows.push(h("div", { className: "more", key: "more" }, `↑ ${start.toLocaleString()} earlier — scroll up`));
259
+ let lastOut = null;
260
+ shown.forEach((m, i) => { const out = isOut(m); const hasMedia = m.media && (m.media.url || m.media.thumb || m.media.ref || m.media.lazy || m.media.kind);
261
+ rows.push(h("div", { key: start + i, className: "bub " + (out ? "out" : "in") + (out !== lastOut ? " grp" : ""), style: hasMedia ? { maxWidth: "min(360px,72%)" } : null },
262
+ hasMedia ? h(MediaView, { media: m.media, model, onMedia }) : null,
263
+ m.text ? h("div", { style: hasMedia ? { marginTop: "5px" } : null }, m.text) : null,
264
+ h("span", { className: "tt" }, (m.time || m.ts || ""), out ? h("span", { className: "rc" }, " " + rcMark(m)) : null)));
265
+ lastOut = out; });
266
+ return h("div", { className: "thread", ref, onScroll }, rows);
267
+ }
268
+
269
+ const Composer = memo(function Composer({ activeId, onSend, onPlus }) {
270
+ const [draft, setDraft] = useState("");
271
+ const send = () => { const t = draft.trim(); if (!t) return; onSend(activeId, t); setDraft(""); };
272
+ return h("div", { className: "composer" },
273
+ Ico(IC.plus(), onPlus, "Attach"),
274
+ h("div", { className: "grow" }, h(Input, { placeholder: "Type a message", value: draft, autoFocus: true,
275
+ onChange: (e) => setDraft(e.target.value), onKeyDown: (e) => { if (e.key === "Enter" && !e.shiftKey) { e.preventDefault(); send(); } },
276
+ style: { background: "#2a3942", border: "0", borderRadius: "10px", height: "44px", color: "#e9edef", paddingLeft: "16px" } })),
277
+ h("button", { className: "send", onClick: send, "aria-label": "Send" }, draft.trim() ? IC.send() : IC.mic()));
278
+ });
279
+
280
+ const TrayBtn = (glyph, label, onClick, disabled) => h("button", { onClick, disabled }, h("span", { style: { fontSize: "17px" } }, glyph), label);
281
+
282
+ function PaySheet({ model, activeId, conv, sheet, setSheet }) {
283
+ const set = (patch) => setSheet((s) => ({ ...s, ...patch, err: null }));
284
+ const amt = parseFloat(sheet.amount);
285
+ const go = async () => { set({ busy: true }); let r;
286
+ try { r = await model.holoPay(sheet.genesis || activeId, { kind: sheet.kind, amount: amt, memo: (sheet.memo || "").trim() }); } catch (e) { r = { ok: false, error: String(e && e.message || e) }; }
287
+ if (r && r.ok) setSheet(null); else setSheet((s) => ({ ...s, busy: false, err: (r && r.error) || "Couldn't create the payment" })); };
288
+ return h("div", { className: "paywrap", onClick: () => setSheet(null) },
289
+ h("div", { className: "pay", onClick: (e) => e.stopPropagation() },
290
+ h("h3", {}, sheet.kind === "send" ? "Send money" : "Request money"), h("div", { className: "psub" }, (sheet.kind === "send" ? "To " : "From ") + (conv.name || "this chat")),
291
+ h("div", { className: "seg" }, h("button", { className: sheet.kind === "send" ? "on" : "", onClick: () => set({ kind: "send" }) }, "Send"), h("button", { className: sheet.kind === "request" ? "on" : "", onClick: () => set({ kind: "request" }) }, "Request")),
292
+ h("input", { className: "amt", inputMode: "decimal", placeholder: "$0", value: sheet.amount, onChange: (e) => set({ amount: e.target.value.replace(/[^0-9.]/g, "") }), autoFocus: true }),
293
+ h("div", { className: "chips" }, [10, 20, 50, 100].map((a) => h("button", { key: a, className: amt === a ? "on" : "", onClick: () => set({ amount: String(a) }) }, "$" + a))),
294
+ h("input", { className: "memo", placeholder: "What's it for? (optional)", maxLength: 120, value: sheet.memo || "", onChange: (e) => set({ memo: e.target.value }) }),
295
+ sheet.err ? h("div", { className: "err" }, sheet.err) : null,
296
+ h("button", { className: "go", disabled: sheet.busy || !(amt > 0), onClick: go }, sheet.busy ? (sheet.kind === "send" ? "Confirm in your wallet…" : "Creating…") : ((sheet.kind === "send" ? "Send" : "Request") + (amt > 0 ? " $" + sheet.amount : " money"))),
297
+ h("button", { className: "cancel", onClick: () => setSheet(null) }, "Cancel")));
298
+ }
299
+
300
+ const previewOf = (model, c) => { if (c.preview) return c.preview; const t = threadOf(model, c.id); const last = t.length ? t[t.length - 1] : null; return (last && (last.text || (last.media ? "📎 media" : ""))) || ""; };
301
+
302
+ return function App({ model }) {
303
+ const convs = Array.isArray(model.conversations) ? model.conversations : [];
304
+ const [activeId, setActiveId] = useState(null);
305
+ const [filter, setFilter] = useState("All");
306
+ const [q, setQ] = useState("");
307
+ const [lb, setLb] = useState(null);
308
+ const [attachOpen, setAttachOpen] = useState(false);
309
+ const [paySheet, setPaySheet] = useState(null);
310
+ const fileRef = useRef(null);
311
+ const onSelect = useCallback((id) => { setActiveId(id); setAttachOpen(false); }, []);
312
+ const active = (activeId && convs.some((c) => c.id === activeId)) ? activeId : ((convs.find((c) => c.active) || convs[0] || {}).id || null);
313
+ const conv = convs.find((c) => c.id === active) || {};
314
+ const onSend = useCallback((id, text) => { try { model.onSend && model.onSend(id, text); } catch (e) { console.error("[shadcn-ui] onSend", e); } }, [model]);
315
+ const call = useCallback((video) => { try { model.startCall && model.startCall(active, { video }); } catch (e) { console.error("[shadcn-ui] startCall", e); } }, [model, active]);
316
+ const pickFile = (accept) => { if (fileRef.current) { fileRef.current.accept = accept; fileRef.current.click(); } setAttachOpen(false); };
317
+ const onFile = (e) => { const f = e.target.files && e.target.files[0]; if (f) { try { model.onAttach && model.onAttach(active, f); } catch (er) { console.error("[shadcn-ui] onAttach", er); } } e.target.value = ""; };
318
+ const tryM = (fn, ...a) => { setAttachOpen(false); try { const r = model[fn] && model[fn](...a); if (r && r.catch) r.catch(() => {}); } catch (e) { console.error("[shadcn-ui] " + fn, e); } };
319
+ const nets = Array.isArray(model.networks) && model.networks.length ? model.networks
320
+ : Object.values(convs.reduce((a, c) => { const id = (c.network || c.platform); if (id && !a[id]) a[id] = { id, label: id[0].toUpperCase() + id.slice(1), unread: 0 }; if (id) a[id].unread += (c.unread || 0); return a; }, {}));
321
+ const totalUnread = convs.reduce((n, c) => n + (c.unread || 0), 0);
322
+ const signalUnread = model.signalUnread != null ? model.signalUnread : convs.filter((c) => c.unread).length;
323
+ const qs = q.trim().toLowerCase();
324
+ const shown = convs.filter((c) => (filter === "All" || (c.network || c.platform) === filter) && (!qs || (c.name || "").toLowerCase().includes(qs) || (previewOf(model, c) || "").toLowerCase().includes(qs)));
325
+ const sub = conv.presence || (conv.isQ ? "online · on your device" : (conv.status || conv.network || conv.platform || ""));
326
+ const runSearch = () => { const t = q.trim(); if (!t) return; try { if (model.qCommand) model.qCommand(t); else if (model.onNewChat) model.onNewChat(t); } catch {} };
327
+
328
+ return h("div", { className: "wa" },
329
+ h(Rail, { nets, active: filter, totalUnread, onPick: (id) => { if (id === "+") { try { model.openNetworks && model.openNetworks(); } catch {} } else setFilter(id); } }),
330
+ h("div", { className: "side" },
331
+ h("div", { className: "stitle" }, h("span", { className: "t" }, "Messenger"),
332
+ model.askQ ? Ico(IC.spark(), () => { try { model.askQ(); } catch {} }, "Ask Q", "sk") : null, Ico(IC.newchat(), () => { try { model.onNewChat && model.onNewChat(""); } catch {} }, "New chat")),
333
+ h("div", { className: "search" }, h("span", { className: "si" }, IC.search()),
334
+ h(Input, { placeholder: "Search, ask Q, or start a chat…", value: q, onChange: (e) => setQ(e.target.value), onKeyDown: (e) => { if (e.key === "Enter") runSearch(); },
335
+ style: { background: "#202c33", border: "0", borderRadius: "10px", height: "42px", color: "#e9edef", paddingLeft: "40px", fontSize: "14.5px" } })),
336
+ signalUnread ? h("div", { className: "triage" }, h("span", { className: "sk" }, IC.spark(18)), `${signalUnread} need you · ~${Math.max(1, Math.round(signalUnread * 0.4))} min`) : null,
337
+ h("div", { className: "chats" }, shown.map((c) => h(Row, { key: c.id, c, on: c.id === active, preview: previewOf(model, c), receipt: c.receipt || "", onSelect })))),
338
+ h("div", { className: "main" },
339
+ h("div", { className: "wp", style: { backgroundImage: `url(${WP})` } }),
340
+ h("div", { className: "mh" }, Av(conv.name, 40, conv.isQ ? "#12b886" : hue(conv.name), conv.avatar),
341
+ h("div", { className: "who" }, h("div", { className: "n" }, conv.name || ""), sub ? h("div", { className: "s" }, sub) : null),
342
+ h("div", { className: "sp" }), h("div", { className: "icons" }, Ico(IC.video(), () => call(true), "Video call"), Ico(IC.phone(), () => call(false), "Voice call"), Ico(IC.menu(), () => { try { model.onFavourite && model.onFavourite(active); } catch {} }, "Menu"))),
343
+ h(Thread, { model, activeId: active, onMedia: setLb }),
344
+ h("input", { ref: fileRef, type: "file", style: { display: "none" }, onChange: onFile }),
345
+ attachOpen ? h("div", { className: "tray" },
346
+ TrayBtn("🖼", "Photos & videos", () => pickFile("image/*,video/*")),
347
+ TrayBtn("📄", "Document", () => pickFile("*/*")),
348
+ (conv.network && !conv.isQ && model.holoPay) ? TrayBtn("💸", "Send money", () => { setAttachOpen(false); setPaySheet({ kind: "send", amount: "", memo: "", busy: false, err: null }); }) : null,
349
+ h("div", { className: "sep" }),
350
+ model.startMeet ? TrayBtn("👥", "Start a meeting", () => tryM("startMeet", active, { video: true })) : null,
351
+ model.startTogether ? TrayBtn("🖥", "Share my screen", () => tryM("startTogether", active, { kind: "tab", title: "" })) : null,
352
+ model.startTogether ? TrayBtn("📝", "Co-edit a doc", () => tryM("startTogether", active, { kind: "doc", title: "" })) : null,
353
+ (conv.network && !conv.isQ && model.qDraft) ? [h("div", { className: "sep", key: "s" }), TrayBtn("✨", "Draft a reply with Q", () => tryM("qDraft", active))] : null) : null,
354
+ h(Composer, { key: active, activeId: active, onSend, onPlus: () => setAttachOpen((v) => !v) })),
355
+ lb ? h("div", { className: "lb", onClick: () => setLb(null) }, h("button", { className: "x", "aria-label": "Close" }, "✕"),
356
+ lb.kind === "video" ? h("video", { src: lb.url, controls: true, autoPlay: true, onClick: (e) => e.stopPropagation() }) : h("img", { src: lb.url, alt: "", onClick: (e) => e.stopPropagation() })) : null,
357
+ paySheet ? h(PaySheet, { model, activeId: active, conv, sheet: paySheet, setSheet: setPaySheet }) : null);
358
+ };
359
+ }
360
+
361
+ async function mount(el, model) {
362
+ const d = await deps();
363
+ const App = buildApp(d);
364
+ let cur = model;
365
+ const root = d.createRoot(el);
366
+ const render = (mdl) => { cur = mdl || cur; root.render(d.React.createElement(App, { model: cur })); };
367
+ render(model);
368
+ console.log("[shadcn-ui] Holo Messenger (Pass 1) mounted from κ-components ·", (model.conversations || []).length, "chats");
369
+ return { update: render, unmount: () => { try { root.unmount(); } catch {} } };
370
+ }
371
+
372
+ if (WANT && typeof window !== "undefined") {
373
+ const prev = window.HoloMessengerUI;
374
+ window.HoloMessengerUI = { mount, version: ((prev && prev.version) || "m1") + "+shadcn", _chatscope: prev };
375
+ console.log("[shadcn-ui] streaming-κ shadcn UI ACTIVE (opt out: ?ui=chatscope)");
376
+ }
377
+
378
+ export { mount };