retire-provenance: superseded Q b/ objects (sha256 CAS, HOLO-LEAN-RESOLVER P1.3)
Browse files
sha256/57e17a233e6239acabf7976b3f296bc3966c684242c0beb3947b5212986577d8
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
sha256/a75f6bd307e60199f5fd8b87fcd349124e98842905acfe4e11232a400b8e427c
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":"9f44164b17e6b7e37f39623bb445d49b6608da5f2b04cef8b1a8b183362a6b9a","hello":"66c1ada5ac08a2d4f73851bbe7458ad5fb6e2226c5a131c3829abfe715900fed","holo-messenger":"8b9b5f882c85455bf29325bddf30142c1a0164b3b9f856815cb07e31df9a1863","resolve":"23aec66382775f5773693615338f37c37a5f58257ec95aece33a664baa6fe0db","self":"0511772584510f0888d97dd5a72bf986ad6791bd8e305063ddd8a82c39888a70","spaces":"d8aebefb67e7fc7802e757b9a7d95bb881e6d75b4a9bc4bd4227634541757e51","yourspace":"c224b1941a746ba15fc62cd82299cc66751b5b4b3d8148972fa9b18b34792cfc"},"boot":{"entry":"holo-portal.mjs","sha256":"caeb3e57b6eeab09bee6d67c022145a8e2dae610c929a7f51bc169898625e7e8"},"closure":"c27676b9ede1d345a3bfb5ef6c214de4b9408975cb0526783912a42b01829652","manifest":"a20831a879e5c9e364b56821e793ddc9e01f441ad7c0f1dc4fe06772b8175ac2","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":"815ba7c08262334c6be7c4c7867348593ecddef37819715047feb99b999c7e67"},"holstr:prev":"did:holo:sha256:c01d6fc97a16c7657ea3bb9765a9e63d990705feb41809a264222558d3c20455","holstr:seq":225,"prov:generatedAtTime":"2026-07-13T13:01:08.563Z"}
|
sha256/c224b1941a746ba15fc62cd82299cc66751b5b4b3d8148972fa9b18b34792cfc
ADDED
|
@@ -0,0 +1,419 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!doctype html>
|
| 2 |
+
<html lang="en">
|
| 3 |
+
<head>
|
| 4 |
+
<meta charset="utf-8" />
|
| 5 |
+
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover" />
|
| 6 |
+
<title>Your Space — HOLOGRAM</title>
|
| 7 |
+
<meta name="description" content="A whole world in one link. Tap to step inside — then make it yours in one tap. No signup, no app, no server." />
|
| 8 |
+
<meta property="og:title" content="Your Space — HOLOGRAM" />
|
| 9 |
+
<meta property="og:description" content="A whole world in one link. Step inside, then make it yours in one tap." />
|
| 10 |
+
<meta property="og:type" content="website" />
|
| 11 |
+
<meta name="twitter:card" content="summary" />
|
| 12 |
+
<style>
|
| 13 |
+
/* ── YOUR SPACE — familiar profile skin (MAYA), radical substrate underneath. ─────────
|
| 14 |
+
One column, mobile-first, zero framework: first paint is pure CSS + the pre-paint
|
| 15 |
+
script below (boot-ground pattern — the wallpaper and name land BEFORE the module). */
|
| 16 |
+
:root {
|
| 17 |
+
color-scheme: dark;
|
| 18 |
+
--accent: #7c6cf0;
|
| 19 |
+
--ink: #eceaf4; --dim: rgba(236,234,244,.62); --faint: rgba(236,234,244,.38);
|
| 20 |
+
--card: rgba(16,13,26,.58); --line: rgba(255,255,255,.11);
|
| 21 |
+
--r: 10px; --rl: 16px; --pill: 999px;
|
| 22 |
+
--sans: ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
|
| 23 |
+
--mono: ui-monospace, "Cascadia Code", Menlo, Consolas, monospace;
|
| 24 |
+
}
|
| 25 |
+
* { box-sizing: border-box; -webkit-tap-highlight-color: transparent; }
|
| 26 |
+
html, body { min-height: 100%; margin: 0; }
|
| 27 |
+
body { color: var(--ink); font: 1rem/1.55 var(--sans); background: #060410; overflow-x: hidden; }
|
| 28 |
+
|
| 29 |
+
/* ── wallpaper: full-bleed, mood-keyed pure-CSS presets (fast, zero assets) ─────────── */
|
| 30 |
+
#wall { position: fixed; inset: 0; z-index: 0; background: #060410 center/cover no-repeat; transition: opacity .8s ease; opacity: 0; }
|
| 31 |
+
#wall.on { opacity: 1; }
|
| 32 |
+
#wall::after { content: ""; position: absolute; inset: 0; background: linear-gradient(180deg, rgba(4,2,10,.25) 0%, rgba(4,2,10,.55) 68%, rgba(4,2,10,.82) 100%); }
|
| 33 |
+
.wp-cosmic { background-image: radial-gradient(1px 1px at 12% 22%, rgba(255,255,255,.9) 50%, transparent 51%), radial-gradient(1px 1px at 78% 12%, rgba(255,255,255,.7) 50%, transparent 51%), radial-gradient(1.5px 1.5px at 62% 70%, rgba(255,255,255,.8) 50%, transparent 51%), radial-gradient(1px 1px at 30% 82%, rgba(255,255,255,.6) 50%, transparent 51%), radial-gradient(1px 1px at 88% 52%, rgba(255,255,255,.75) 50%, transparent 51%), radial-gradient(ellipse 90% 60% at 50% 30%, #241a4d 0%, #120c28 55%, #05030d 100%) !important; }
|
| 34 |
+
.wp-vaporwave { background-image: linear-gradient(160deg, #ff71ce22 0%, transparent 40%), linear-gradient(200deg, #01cdfe22 10%, transparent 55%), linear-gradient(180deg, #2b1055 0%, #7597de44 140%), linear-gradient(180deg, #1a0b33 0%, #0d0619 100%) !important; }
|
| 35 |
+
.wp-cozy { background-image: radial-gradient(ellipse 100% 80% at 50% 20%, #4a2e1a 0%, #2a1810 55%, #120a06 100%) !important; }
|
| 36 |
+
.wp-neon { background-image: radial-gradient(ellipse 60% 40% at 20% 30%, #16f7a422 0%, transparent 70%), radial-gradient(ellipse 60% 40% at 80% 70%, #f716c922 0%, transparent 70%), linear-gradient(#07060d, #07060d) !important; }
|
| 37 |
+
.wp-noir { background-image: radial-gradient(ellipse 120% 90% at 50% 0%, #1c1c20 0%, #0c0c0e 60%, #050506 100%) !important; }
|
| 38 |
+
.wp-sunset { background-image: linear-gradient(180deg, #3d1635 0%, #7a2b3d 40%, #c96f3f66 78%, #1a0d18 100%) !important; }
|
| 39 |
+
.wp-forest { background-image: radial-gradient(ellipse 110% 80% at 50% 10%, #14351f 0%, #0a1f12 55%, #04100a 100%) !important; }
|
| 40 |
+
.wp-ocean { background-image: radial-gradient(ellipse 110% 80% at 50% 100%, #0a3a4a 0%, #072433 55%, #030d16 100%) !important; }
|
| 41 |
+
.wp-chaos { background-image: conic-gradient(from 210deg at 50% 40%, #2b1055, #4a1a3d, #1a3a4a, #3d2b10, #2b1055) !important; }
|
| 42 |
+
|
| 43 |
+
/* ── the page: one centred column, a profile everyone already knows how to read ─────── */
|
| 44 |
+
#page { position: relative; z-index: 1; max-width: 560px; margin: 0 auto; padding: max(4.5vh, 2rem) 1.1rem 7.5rem; animation: rise .5s cubic-bezier(.2,.8,.2,1); }
|
| 45 |
+
@keyframes rise { from { opacity: 0; transform: translateY(14px); } to { opacity: 1; transform: none; } }
|
| 46 |
+
|
| 47 |
+
/* via-lineage credit — the road back up the fork chain */
|
| 48 |
+
#via { display: none; align-items: center; gap: .4rem; font-size: .82rem; color: var(--faint); margin-bottom: 1rem; }
|
| 49 |
+
#via.on { display: inline-flex; } #via a { color: var(--dim); cursor: pointer; text-decoration: none; border-bottom: 1px dotted var(--faint); }
|
| 50 |
+
|
| 51 |
+
/* head: avatar · name · bio · mood + κ */
|
| 52 |
+
#avatar { width: 104px; height: 104px; border-radius: 28%; display: grid; place-items: center; font-size: 3.2rem; user-select: none;
|
| 53 |
+
background: color-mix(in srgb, var(--accent) 20%, rgba(255,255,255,.04)); border: 1px solid color-mix(in srgb, var(--accent) 40%, var(--line));
|
| 54 |
+
box-shadow: 0 8px 40px -8px color-mix(in srgb, var(--accent) 55%, transparent); overflow: hidden; }
|
| 55 |
+
#avatar img { width: 100%; height: 100%; object-fit: cover; }
|
| 56 |
+
#name { font-size: 2rem; font-weight: 800; letter-spacing: -.02em; margin: 1rem 0 .1rem; min-height: 1.2em; outline: none; }
|
| 57 |
+
#bio { color: var(--dim); margin: 0 0 .8rem; white-space: pre-wrap; outline: none; }
|
| 58 |
+
#name:empty::before, #bio:empty::before { content: attr(data-ph); color: var(--faint); }
|
| 59 |
+
.chips { display: flex; flex-wrap: wrap; gap: .45rem; align-items: center; }
|
| 60 |
+
.chip { display: inline-flex; align-items: center; gap: .4rem; font-size: .8rem; padding: .28rem .7rem; border-radius: var(--pill);
|
| 61 |
+
border: 1px solid var(--line); color: var(--dim); background: var(--card); backdrop-filter: blur(8px); }
|
| 62 |
+
#kap { font-family: var(--mono); cursor: pointer; max-width: 15ch; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
| 63 |
+
#kap.ok { color: #34d399; border-color: #34d39966; }
|
| 64 |
+
|
| 65 |
+
/* sections read like the page everyone grew up on: song, top friends, wall */
|
| 66 |
+
.sec { margin-top: 1.6rem; }
|
| 67 |
+
.sec h2 { font-size: .78rem; font-weight: 700; letter-spacing: .14em; text-transform: uppercase; color: var(--faint); margin: 0 0 .6rem; }
|
| 68 |
+
.card { background: var(--card); border: 1px solid var(--line); border-radius: var(--rl); backdrop-filter: blur(12px); }
|
| 69 |
+
|
| 70 |
+
/* the profile song — MySpace's most-loved feature, reborn as a κ-findable track */
|
| 71 |
+
#song { display: none; align-items: center; gap: .8rem; padding: .7rem .8rem; cursor: pointer; }
|
| 72 |
+
#song.on { display: flex; }
|
| 73 |
+
#songart { width: 52px; height: 52px; border-radius: var(--r); background: color-mix(in srgb, var(--accent) 25%, #111) center/cover; display: grid; place-items: center; font-size: 1.4rem; flex: none; }
|
| 74 |
+
#songmeta { min-width: 0; flex: 1; } #songmeta b { display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
| 75 |
+
#songmeta span { font-size: .84rem; color: var(--dim); }
|
| 76 |
+
#songbtn { width: 44px; height: 44px; flex: none; border-radius: 50%; border: 0; cursor: pointer; font-size: 1.05rem; color: #fff;
|
| 77 |
+
background: var(--accent); display: grid; place-items: center; box-shadow: 0 0 0 0 color-mix(in srgb, var(--accent) 60%, transparent); animation: pulse 2.4s infinite; }
|
| 78 |
+
@keyframes pulse { 0% { box-shadow: 0 0 0 0 color-mix(in srgb, var(--accent) 45%, transparent); } 70% { box-shadow: 0 0 0 14px transparent; } 100% { box-shadow: 0 0 0 0 transparent; } }
|
| 79 |
+
#song.playing #songbtn { animation: none; }
|
| 80 |
+
|
| 81 |
+
/* Top 8 — doors into other worlds */
|
| 82 |
+
#top { display: none; grid-template-columns: repeat(4, 1fr); gap: .55rem; }
|
| 83 |
+
#top.on { display: grid; }
|
| 84 |
+
.door { display: flex; flex-direction: column; align-items: center; gap: .35rem; padding: .7rem .3rem .55rem; text-align: center; cursor: pointer;
|
| 85 |
+
background: var(--card); border: 1px solid var(--line); border-radius: var(--rl); backdrop-filter: blur(8px); position: relative;
|
| 86 |
+
transition: transform .15s cubic-bezier(.2,.8,.2,1), border-color .15s; }
|
| 87 |
+
.door:hover { transform: translateY(-2px); border-color: color-mix(in srgb, var(--accent) 55%, var(--line)); }
|
| 88 |
+
.door .face { width: 44px; height: 44px; border-radius: 50%; display: grid; place-items: center; font-size: 1.25rem; font-weight: 700;
|
| 89 |
+
background: color-mix(in srgb, var(--accent) 18%, rgba(255,255,255,.05)); border: 1px solid color-mix(in srgb, var(--accent) 35%, var(--line)); }
|
| 90 |
+
.door small { font-size: .72rem; color: var(--dim); max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
| 91 |
+
.door .rm { display: none; position: absolute; top: -7px; right: -7px; width: 22px; height: 22px; border-radius: 50%; border: 1px solid var(--line);
|
| 92 |
+
background: #1c1626; color: var(--dim); font-size: .8rem; line-height: 1; cursor: pointer; place-items: center; }
|
| 93 |
+
body.edit .door .rm { display: grid; }
|
| 94 |
+
.door.add { border-style: dashed; color: var(--faint); display: none; }
|
| 95 |
+
body.edit .door.add { display: flex; }
|
| 96 |
+
|
| 97 |
+
#wallbtn { display: none; width: 100%; padding: .8rem; text-align: center; cursor: pointer; color: var(--ink); font-weight: 600; }
|
| 98 |
+
#wallbtn.on { display: block; }
|
| 99 |
+
|
| 100 |
+
/* ── the sticky CTA — the whole viral loop is these two buttons ──────────────────────── */
|
| 101 |
+
#cta { position: fixed; z-index: 5; left: 0; right: 0; bottom: 0; padding: .8rem 1.1rem calc(.8rem + env(safe-area-inset-bottom));
|
| 102 |
+
display: flex; gap: .6rem; justify-content: center; background: linear-gradient(transparent, rgba(4,2,10,.88) 40%); }
|
| 103 |
+
.btn { flex: 1; max-width: 270px; padding: .85rem 1.1rem; font: 600 1rem var(--sans); border-radius: 14px; border: 1px solid var(--line);
|
| 104 |
+
color: var(--ink); background: rgba(22,17,34,.85); backdrop-filter: blur(12px); cursor: pointer; transition: transform .12s, filter .12s; }
|
| 105 |
+
.btn:active { transform: scale(.97); }
|
| 106 |
+
.btn.go { background: var(--accent); border-color: transparent; color: #fff; box-shadow: 0 6px 30px -6px color-mix(in srgb, var(--accent) 70%, transparent); }
|
| 107 |
+
.btn.go:hover { filter: brightness(1.08); }
|
| 108 |
+
|
| 109 |
+
/* ── EDIT — Playground law: every element is editable directly, in place ─────────────── */
|
| 110 |
+
body.edit [data-edit] { outline: 1.5px dashed color-mix(in srgb, var(--accent) 55%, transparent); outline-offset: 4px; border-radius: 6px; cursor: text; }
|
| 111 |
+
body.edit #avatar, body.edit .door.add, body.edit #songpick { cursor: pointer; }
|
| 112 |
+
.row { display: none; flex-wrap: wrap; gap: .45rem; margin-top: .7rem; }
|
| 113 |
+
body.edit .row { display: flex; }
|
| 114 |
+
.sw { width: 30px; height: 30px; border-radius: 50%; border: 2px solid transparent; cursor: pointer; }
|
| 115 |
+
.sw.on { border-color: #fff; }
|
| 116 |
+
.mood { font-size: .8rem; padding: .3rem .75rem; border-radius: var(--pill); border: 1px solid var(--line); background: var(--card); color: var(--dim); cursor: pointer; }
|
| 117 |
+
.mood.on { color: #fff; background: color-mix(in srgb, var(--accent) 45%, transparent); border-color: transparent; }
|
| 118 |
+
#picker { display: none; position: fixed; z-index: 9; left: 50%; bottom: 90px; transform: translateX(-50%); width: min(92vw, 380px);
|
| 119 |
+
padding: .8rem; border-radius: var(--rl); background: #171126; border: 1px solid var(--line); box-shadow: 0 20px 60px rgba(0,0,0,.6); }
|
| 120 |
+
#picker.on { display: block; }
|
| 121 |
+
#picker .grid { display: grid; grid-template-columns: repeat(8, 1fr); gap: .3rem; }
|
| 122 |
+
#picker .grid button { font-size: 1.35rem; padding: .3rem 0; background: none; border: 0; border-radius: 8px; cursor: pointer; }
|
| 123 |
+
#picker .grid button:hover { background: rgba(255,255,255,.08); }
|
| 124 |
+
#picker input, #srch input, #doorform input { width: 100%; margin-top: .55rem; padding: .6rem .7rem; font: inherit; color: var(--ink);
|
| 125 |
+
background: rgba(255,255,255,.05); border: 1px solid var(--line); border-radius: var(--r); outline: none; }
|
| 126 |
+
#srch { display: none; padding: .8rem; } body.edit #srch { display: block; }
|
| 127 |
+
#srch .res { display: flex; align-items: center; gap: .6rem; padding: .45rem .3rem; cursor: pointer; border-radius: var(--r); }
|
| 128 |
+
#srch .res:hover { background: rgba(255,255,255,.06); }
|
| 129 |
+
#srch .res img { width: 38px; height: 38px; border-radius: 8px; }
|
| 130 |
+
#srch .res small { color: var(--dim); }
|
| 131 |
+
#doorform { display: none; padding: .8rem; } #doorform.on { display: block; }
|
| 132 |
+
|
| 133 |
+
/* toast + hint */
|
| 134 |
+
#toast { position: fixed; z-index: 10; left: 50%; bottom: 96px; transform: translateX(-50%) translateY(20px); opacity: 0; pointer-events: none;
|
| 135 |
+
background: #171126; border: 1px solid var(--line); color: var(--ink); padding: .6rem 1rem; border-radius: var(--pill); font-size: .9rem;
|
| 136 |
+
transition: all .25s; max-width: 88vw; text-align: center; }
|
| 137 |
+
#toast.on { opacity: 1; transform: translateX(-50%); }
|
| 138 |
+
#hint { display: none; margin: 0 0 1.2rem; padding: .7rem .9rem; font-size: .9rem; border-radius: var(--rl);
|
| 139 |
+
background: color-mix(in srgb, var(--accent) 14%, rgba(10,7,20,.6)); border: 1px solid color-mix(in srgb, var(--accent) 35%, var(--line)); }
|
| 140 |
+
#hint.on { display: block; }
|
| 141 |
+
footer { position: relative; z-index: 1; text-align: center; padding: 0 1rem 8.5rem; color: var(--faint); font-size: .78rem; }
|
| 142 |
+
footer a { color: var(--dim); cursor: pointer; text-decoration: none; border-bottom: 1px dotted var(--faint); }
|
| 143 |
+
@media (min-width: 700px) { #page { padding-top: 9vh; } }
|
| 144 |
+
</style>
|
| 145 |
+
</head>
|
| 146 |
+
<body>
|
| 147 |
+
<div id="wall"></div>
|
| 148 |
+
<main id="page">
|
| 149 |
+
<div id="hint"></div>
|
| 150 |
+
<span id="via">forked via <a id="vialink"></a></span>
|
| 151 |
+
<div id="avatar" data-edit-tap>🪐</div>
|
| 152 |
+
<h1 id="name" data-ph="Name your space"></h1>
|
| 153 |
+
<p id="bio" data-ph="Say something. This is your corner of the universe."></p>
|
| 154 |
+
<div class="chips">
|
| 155 |
+
<span class="chip" id="moodchip" style="display:none"></span>
|
| 156 |
+
<span class="chip" id="kap" title="This space's content address — tap to verify + copy">κ…</span>
|
| 157 |
+
</div>
|
| 158 |
+
<div class="row" id="accents"></div>
|
| 159 |
+
<div class="row" id="moods"></div>
|
| 160 |
+
|
| 161 |
+
<section class="sec" id="songsec">
|
| 162 |
+
<h2>Profile song</h2>
|
| 163 |
+
<div class="card" id="song">
|
| 164 |
+
<div id="songart">♪</div>
|
| 165 |
+
<div id="songmeta"><b id="songt"></b><span id="songa"></span></div>
|
| 166 |
+
<button id="songbtn" aria-label="Play">▶</button>
|
| 167 |
+
</div>
|
| 168 |
+
<div class="card" id="srch">
|
| 169 |
+
<input id="srchin" placeholder="Search any song — this plays when someone enters" autocomplete="off" />
|
| 170 |
+
<div id="srchres"></div>
|
| 171 |
+
</div>
|
| 172 |
+
</section>
|
| 173 |
+
|
| 174 |
+
<section class="sec">
|
| 175 |
+
<h2 id="toph">Top 8</h2>
|
| 176 |
+
<div id="top"></div>
|
| 177 |
+
<div class="card" id="doorform">
|
| 178 |
+
<input id="doorlink" placeholder="Paste a friend's space link (or any link)" autocomplete="off" />
|
| 179 |
+
<input id="doorname" placeholder="Name this door" autocomplete="off" />
|
| 180 |
+
<div class="row" style="display:flex;margin-top:.6rem"><button class="btn go" id="dooradd" style="max-width:none">Add door</button></div>
|
| 181 |
+
</div>
|
| 182 |
+
</section>
|
| 183 |
+
|
| 184 |
+
<section class="sec">
|
| 185 |
+
<div class="card"><div id="wallbtn">💬 Leave a note on my wall</div></div>
|
| 186 |
+
</section>
|
| 187 |
+
</main>
|
| 188 |
+
<footer>
|
| 189 |
+
your space is one link — no account, no app, no server · <a id="newspace">start a blank space</a><br/>
|
| 190 |
+
<span style="opacity:.7">HOLOGRAM · the κ re-derives from the link itself</span>
|
| 191 |
+
</footer>
|
| 192 |
+
|
| 193 |
+
<div id="cta"></div>
|
| 194 |
+
<div id="picker"><div class="grid" id="emojis"></div><input id="avurl" placeholder="…or paste an image URL" autocomplete="off" /></div>
|
| 195 |
+
<div id="toast"></div>
|
| 196 |
+
<audio id="audio"></audio>
|
| 197 |
+
|
| 198 |
+
<script>
|
| 199 |
+
/* pre-paint (boot-ground): land wallpaper + name from the link BEFORE the module loads,
|
| 200 |
+
so the teleport chorus is identical and instant on every click. Decode only — no hash,
|
| 201 |
+
no trust decisions here; the module re-derives κ and owns all behaviour. */
|
| 202 |
+
(function () {
|
| 203 |
+
try {
|
| 204 |
+
var m = location.hash.match(/#p=v2\.([A-Za-z0-9_-]+)/);
|
| 205 |
+
var p = m ? JSON.parse(atob(m[1].replace(/-/g, "+").replace(/_/g, "/").padEnd(Math.ceil(m[1].length / 4) * 4, "="))) : null;
|
| 206 |
+
if (p && p.kind === "profile") {
|
| 207 |
+
if (p.accent) document.documentElement.style.setProperty("--accent", p.accent);
|
| 208 |
+
var w = document.getElementById("wall"), wp = String(p.wallpaper || "");
|
| 209 |
+
if (/^https:\/\//.test(wp)) w.style.backgroundImage = "url('" + wp.replace(/'/g, "%27") + "')";
|
| 210 |
+
else w.className = "wp-" + ((wp.match(/^preset:(\w+)/) || [])[1] || p.mood || "cosmic");
|
| 211 |
+
document.getElementById("name").textContent = p.name || "";
|
| 212 |
+
} else { document.getElementById("wall").className = "wp-cosmic"; }
|
| 213 |
+
} catch (e) { document.getElementById("wall").className = "wp-cosmic"; }
|
| 214 |
+
requestAnimationFrame(function () { document.getElementById("wall").classList.add("on"); });
|
| 215 |
+
})();
|
| 216 |
+
</script>
|
| 217 |
+
|
| 218 |
+
<script type="module">
|
| 219 |
+
import * as YS from "./holo-yourspace.mjs";
|
| 220 |
+
|
| 221 |
+
/* ── Q's Space — the Tom Anderson seed: everyone's first friend, everyone's first fork ── */
|
| 222 |
+
const OS_HOME = "https://hologram-technologies.github.io/Q";
|
| 223 |
+
const SEED = YS.identity({
|
| 224 |
+
name: "Q", bio: "I live inside HOLOGRAM — a whole operating system that unfolds from one link.\nThis page is my space. Make yours in one tap. I'll be your first friend.",
|
| 225 |
+
avatar: "🪐", accent: "#7c6cf0", mood: "cosmic", wallpaper: "preset:cosmic",
|
| 226 |
+
song: { title: "Space Oddity", artist: "David Bowie", query: "david bowie space oddity", art: "", preview: "" },
|
| 227 |
+
top: [{ name: "Q", link: OS_HOME }, { name: "HOLOGRAM", link: "https://hologram-technologies.github.io/" }],
|
| 228 |
+
wall: "", via: null,
|
| 229 |
+
});
|
| 230 |
+
|
| 231 |
+
const $ = (id) => document.getElementById(id);
|
| 232 |
+
const ME_KEY = "holo-yourspace.me.v2";
|
| 233 |
+
const ACCENTS = ["#7c6cf0", "#2dd4bf", "#f472b6", "#f59e0b", "#22c55e", "#38bdf8", "#ef4444", "#e2e8f0"];
|
| 234 |
+
const EMOJIS = ["🙂","😎","🛰️","🪐","🌸","🔥","🌊","🌙","⚡","🎧","🎮","🐉","🦋","🌵","👾","✨"];
|
| 235 |
+
let doc = null, mode = "view", myLink = "", curLink = "", curKappa = "";
|
| 236 |
+
|
| 237 |
+
const me = () => { try { return JSON.parse(localStorage.getItem(ME_KEY)); } catch (e) { return null; } };
|
| 238 |
+
const saveMe = (d, l) => localStorage.setItem(ME_KEY, JSON.stringify({ payload: YS.encode(d), link: l }));
|
| 239 |
+
const toast = (msg, ms = 2600) => { const t = $("toast"); t.textContent = msg; t.classList.add("on"); clearTimeout(t._h); t._h = setTimeout(() => t.classList.remove("on"), ms); };
|
| 240 |
+
|
| 241 |
+
/* ── boot: the hash IS the state. no hash → my space, or Q's seed for a cold stranger. ── */
|
| 242 |
+
function boot() {
|
| 243 |
+
const payload = YS.parseLink(location.hash);
|
| 244 |
+
if (payload) { const p = YS.decode(payload); if (p) return show(p, "view"); }
|
| 245 |
+
const m = me();
|
| 246 |
+
if (m && m.payload) { const p = YS.decode(m.payload); if (p) { history.replaceState(null, "", "#p=v2." + m.payload); return show(p, "view"); } }
|
| 247 |
+
history.replaceState(null, "", location.pathname + location.search);
|
| 248 |
+
show(SEED, "view", true);
|
| 249 |
+
}
|
| 250 |
+
addEventListener("hashchange", boot); // doors between spaces teleport in place — no reload
|
| 251 |
+
|
| 252 |
+
/* ── render — one function, view and edit are the same page (Playground law) ─────────── */
|
| 253 |
+
async function show(p, m, isSeed = false) {
|
| 254 |
+
doc = YS.identity(p); mode = m;
|
| 255 |
+
curLink = location.hash.includes("#p=v2.") ? location.origin + location.pathname + "#p=v2." + YS.encode(doc) : "";
|
| 256 |
+
document.body.classList.toggle("edit", mode === "edit");
|
| 257 |
+
document.documentElement.style.setProperty("--accent", doc.accent || "#7c6cf0");
|
| 258 |
+
|
| 259 |
+
// wallpaper (κ | https | preset | mood default) — pre-paint already landed it on cold load
|
| 260 |
+
const w = $("wall"); const wp = doc.wallpaper; const kx = YS.hexOf(wp);
|
| 261 |
+
w.style.backgroundImage = ""; w.className = "on";
|
| 262 |
+
if (kx) w.style.backgroundImage = `url('/.holo/blake3/${kx}')`;
|
| 263 |
+
else if (/^https:\/\//.test(wp)) w.style.backgroundImage = `url('${wp.replace(/'/g, "%27")}')`;
|
| 264 |
+
else w.classList.add("wp-" + ((wp.match(/^preset:(\w+)/) || [])[1] || doc.mood || "cosmic"));
|
| 265 |
+
|
| 266 |
+
// head
|
| 267 |
+
$("avatar").innerHTML = /^https:\/\//.test(doc.avatar) ? `<img src="${doc.avatar}" alt="">` : (doc.avatar || (mode === "edit" ? "+" : "🪐"));
|
| 268 |
+
$("name").textContent = doc.name; $("bio").textContent = doc.bio;
|
| 269 |
+
$("name").contentEditable = $("bio").contentEditable = mode === "edit" ? "plaintext-only" : "false";
|
| 270 |
+
$("name").toggleAttribute("data-edit", mode === "edit"); $("bio").toggleAttribute("data-edit", mode === "edit");
|
| 271 |
+
$("moodchip").style.display = doc.mood ? "" : "none"; $("moodchip").textContent = "· " + doc.mood + " ·";
|
| 272 |
+
$("via").classList.toggle("on", !!doc.via);
|
| 273 |
+
if (doc.via) { $("vialink").textContent = doc.via.name || "κ·" + doc.via.k.slice(0, 8); $("vialink").onclick = () => history.back(); }
|
| 274 |
+
|
| 275 |
+
// κ badge — re-derives live; tap = verify + copy (the trust signature)
|
| 276 |
+
$("kap").textContent = "κ…";
|
| 277 |
+
try { curKappa = await YS.kappa(doc); $("kap").textContent = "κ·" + YS.hexOf(curKappa).slice(0, 10); } catch (e) { $("kap").textContent = "κ offline"; }
|
| 278 |
+
|
| 279 |
+
// song
|
| 280 |
+
const s = doc.song; $("song").classList.toggle("on", !!s);
|
| 281 |
+
if (s) { $("songt").textContent = s.title; $("songa").textContent = s.artist;
|
| 282 |
+
$("songart").style.backgroundImage = s.art ? `url('${s.art}')` : ""; $("songart").textContent = s.art ? "" : "♪"; }
|
| 283 |
+
|
| 284 |
+
// top doors
|
| 285 |
+
const top = $("top"); top.innerHTML = ""; top.classList.toggle("on", doc.top.length > 0 || mode === "edit");
|
| 286 |
+
$("toph").textContent = doc.top.length ? `Top ${Math.min(doc.top.length, 8)}` : "Top 8";
|
| 287 |
+
doc.top.forEach((d, i) => {
|
| 288 |
+
const el = document.createElement("div"); el.className = "door";
|
| 289 |
+
const isSpace = !!YS.parseLink(d.link);
|
| 290 |
+
el.innerHTML = `<div class="face">${isSpace ? "🌐" : (d.name || "•")[0].toUpperCase()}</div><small>${d.name || "door"}</small><button class="rm" aria-label="remove">×</button>`;
|
| 291 |
+
el.onclick = (ev) => { if (ev.target.className === "rm") return commit(YS.removeDoor(doc, i));
|
| 292 |
+
if (isSpace || d.link.startsWith(location.origin)) location.assign(d.link); else window.open(d.link, "_blank", "noopener"); };
|
| 293 |
+
top.appendChild(el);
|
| 294 |
+
});
|
| 295 |
+
const add = document.createElement("div"); add.className = "door add"; add.innerHTML = `<div class="face">+</div><small>add door</small>`;
|
| 296 |
+
add.onclick = () => $("doorform").classList.toggle("on"); top.appendChild(add);
|
| 297 |
+
|
| 298 |
+
// wall
|
| 299 |
+
$("wallbtn").classList.toggle("on", !!doc.wall);
|
| 300 |
+
$("wallbtn").onclick = () => doc.wall && window.open(doc.wall, "_blank", "noopener");
|
| 301 |
+
|
| 302 |
+
// hint (the seed sells the fork; a fresh fork sells the share)
|
| 303 |
+
const hint = $("hint"); hint.classList.toggle("on", isSeed);
|
| 304 |
+
if (isSeed) hint.innerHTML = "👋 This is <b>Q's space</b> — a whole world living in one link. <b>Make it yours in one tap</b> ↓";
|
| 305 |
+
|
| 306 |
+
// CTA — the loop: visitor → [Make It Mine · Share] · owner → [Edit · Share] · edit → [Done]
|
| 307 |
+
const owner = !!(me() && me().payload === YS.encode(doc));
|
| 308 |
+
const cta = $("cta"); cta.innerHTML = "";
|
| 309 |
+
const btn = (label, cls, fn) => { const b = document.createElement("button"); b.className = "btn " + cls; b.textContent = label; b.onclick = fn; cta.appendChild(b); };
|
| 310 |
+
if (mode === "edit") btn("Done — mint my link", "go", finish);
|
| 311 |
+
else if (owner) { btn("Edit my space", "", () => show(doc, "edit")); btn("Share", "go", share); }
|
| 312 |
+
else { btn("✨ Make It Mine", "go", makeItMine); btn("Share", "", share); }
|
| 313 |
+
|
| 314 |
+
renderEditRows();
|
| 315 |
+
}
|
| 316 |
+
|
| 317 |
+
/* ── the viral gesture ────────────────────────────────────────────────────────────────── */
|
| 318 |
+
async function makeItMine() {
|
| 319 |
+
let mine;
|
| 320 |
+
try { mine = await YS.fork(doc, curLink || location.href); }
|
| 321 |
+
catch (e) { mine = YS.withFields(doc, { name: "", bio: "", avatar: "", wall: "", via: { k: "", name: doc.name } }); }
|
| 322 |
+
toast("It's yours. Decorate, then mint your link ✨");
|
| 323 |
+
show(mine, "edit");
|
| 324 |
+
}
|
| 325 |
+
|
| 326 |
+
function commit(next) { doc = next; show(doc, mode); } // every edit re-renders from the doc — the doc IS the page
|
| 327 |
+
|
| 328 |
+
async function finish() {
|
| 329 |
+
// pull the two contenteditables, mint, save, land on MY link, open the share sheet
|
| 330 |
+
doc = YS.withFields(doc, { name: $("name").textContent.trim(), bio: $("bio").textContent.trim() });
|
| 331 |
+
const payload = YS.encode(doc);
|
| 332 |
+
myLink = location.origin + location.pathname + "#p=v2." + payload;
|
| 333 |
+
saveMe(doc, myLink);
|
| 334 |
+
history.replaceState(null, "", "#p=v2." + payload);
|
| 335 |
+
await show(doc, "view");
|
| 336 |
+
toast("Minted. This link IS your space — post it anywhere.");
|
| 337 |
+
share();
|
| 338 |
+
}
|
| 339 |
+
|
| 340 |
+
async function share() {
|
| 341 |
+
const url = location.href;
|
| 342 |
+
const title = (doc.name ? doc.name + "'s" : "my") + " space";
|
| 343 |
+
try { if (navigator.share) return await navigator.share({ title, text: "step into " + title, url }); } catch (e) { if (e.name === "AbortError") return; }
|
| 344 |
+
try { await navigator.clipboard.writeText(url); toast("Link copied — drop it in any chat 💫"); }
|
| 345 |
+
catch (e) { prompt("Copy your space link:", url); }
|
| 346 |
+
}
|
| 347 |
+
|
| 348 |
+
/* ── edit affordances: accents · moods · avatar · song search · doors ─────────────────── */
|
| 349 |
+
function renderEditRows() {
|
| 350 |
+
const ac = $("accents"); ac.innerHTML = "";
|
| 351 |
+
ACCENTS.forEach((c) => { const b = document.createElement("button"); b.className = "sw" + (doc.accent === c ? " on" : "");
|
| 352 |
+
b.style.background = c; b.onclick = () => commit(YS.withFields(grab(), { accent: c })); ac.appendChild(b); });
|
| 353 |
+
const md = $("moods"); md.innerHTML = "";
|
| 354 |
+
YS.MOODS.forEach((m) => { const b = document.createElement("button"); b.className = "mood" + (doc.mood === m ? " on" : ""); b.textContent = m;
|
| 355 |
+
b.onclick = () => commit(YS.withFields(grab(), { mood: m, wallpaper: (!doc.wallpaper || doc.wallpaper.startsWith("preset:")) ? "preset:" + m : doc.wallpaper })); md.appendChild(b); });
|
| 356 |
+
}
|
| 357 |
+
const grab = () => YS.withFields(doc, { name: $("name").textContent.trim(), bio: $("bio").textContent.trim() }); // keep in-flight typing across re-renders
|
| 358 |
+
|
| 359 |
+
$("avatar").onclick = () => { if (mode !== "edit") return; const p = $("picker"); p.classList.toggle("on");
|
| 360 |
+
const g = $("emojis"); if (!g.childElementCount) EMOJIS.forEach((e) => { const b = document.createElement("button"); b.textContent = e;
|
| 361 |
+
b.onclick = () => { p.classList.remove("on"); commit(YS.withFields(grab(), { avatar: e })); }; g.appendChild(b); }); };
|
| 362 |
+
$("avurl").onchange = () => { $("picker").classList.remove("on"); commit(YS.withFields(grab(), { avatar: $("avurl").value.trim() })); };
|
| 363 |
+
|
| 364 |
+
// song search — client-direct iTunes (existing holo-imusic pattern), zero servers of ours
|
| 365 |
+
let srchT = null;
|
| 366 |
+
$("srchin").oninput = () => { clearTimeout(srchT); srchT = setTimeout(async () => {
|
| 367 |
+
const q = $("srchin").value.trim(); const box = $("srchres"); if (!q) { box.innerHTML = ""; return; }
|
| 368 |
+
try {
|
| 369 |
+
const r = await fetch(`https://itunes.apple.com/search?media=music&limit=5&term=${encodeURIComponent(q)}`);
|
| 370 |
+
const js = await r.json(); box.innerHTML = "";
|
| 371 |
+
(js.results || []).forEach((t) => { const d = document.createElement("div"); d.className = "res";
|
| 372 |
+
d.innerHTML = `<img src="${t.artworkUrl100 || ""}" alt=""><div><b>${t.trackName}</b><br/><small>${t.artistName}</small></div>`;
|
| 373 |
+
d.onclick = () => { commit(YS.withFields(grab(), { song: { title: t.trackName, artist: t.artistName, art: t.artworkUrl100 || "", query: `${t.artistName} ${t.trackName}`, preview: t.previewUrl || "" } })); toast("Song set 🎧"); };
|
| 374 |
+
box.appendChild(d); });
|
| 375 |
+
} catch (e) { box.innerHTML = `<small style="color:var(--dim)">search unreachable — paste on again later</small>`; }
|
| 376 |
+
}, 350); };
|
| 377 |
+
|
| 378 |
+
$("dooradd").onclick = () => { const l = $("doorlink").value.trim(), n = $("doorname").value.trim();
|
| 379 |
+
if (!l) return toast("Paste a link first");
|
| 380 |
+
commit(YS.addDoor(grab(), { name: n || "door", link: l })); $("doorlink").value = $("doorname").value = ""; $("doorform").classList.remove("on"); };
|
| 381 |
+
|
| 382 |
+
/* ── the song plays: in-OS full track via the holo-music-ctl bus, else the 30s preview ── */
|
| 383 |
+
$("song").onclick = async (ev) => {
|
| 384 |
+
if (ev.target.id === "songbtn" || ev.currentTarget.contains(ev.target)) {
|
| 385 |
+
const s = doc.song; if (!s) return;
|
| 386 |
+
const el = $("song"), a = $("audio");
|
| 387 |
+
if (el.classList.contains("playing")) { a.pause(); el.classList.remove("playing"); $("songbtn").textContent = "▶"; try { busCmd("pause"); } catch (e) {} return; }
|
| 388 |
+
if (await busCmd("play", [s.query || `${s.artist} ${s.title}`])) { el.classList.add("playing"); $("songbtn").textContent = "❚❚"; toast("Playing in Holo Music 🎶"); return; }
|
| 389 |
+
let src = s.preview;
|
| 390 |
+
if (!src && (s.query || s.title)) { try {
|
| 391 |
+
const r = await fetch(`https://itunes.apple.com/search?media=music&limit=1&term=${encodeURIComponent(s.query || s.artist + " " + s.title)}`);
|
| 392 |
+
src = ((await r.json()).results || [])[0]?.previewUrl || ""; } catch (e) {} }
|
| 393 |
+
if (!src) return toast("Song not reachable right now");
|
| 394 |
+
a.src = src; try { await a.play(); el.classList.add("playing"); $("songbtn").textContent = "❚❚"; } catch (e) { toast("Tap again to play"); }
|
| 395 |
+
a.onended = () => { el.classList.remove("playing"); $("songbtn").textContent = "▶"; };
|
| 396 |
+
}
|
| 397 |
+
};
|
| 398 |
+
function busCmd(cmd, args = []) { // same-origin Holo Music tab answers in ≤800ms, else fall through
|
| 399 |
+
return new Promise((res) => { let done = false;
|
| 400 |
+
try { const ch = new BroadcastChannel("holo-music-ctl"); const id = Math.random().toString(36).slice(2);
|
| 401 |
+
ch.onmessage = (e) => { if (e.data && e.data.re === id) { done = true; ch.close(); res(!!e.data.ok); } };
|
| 402 |
+
ch.postMessage({ id, cmd, args }); setTimeout(() => { if (!done) { ch.close(); res(false); } }, 800);
|
| 403 |
+
} catch (e) { res(false); } });
|
| 404 |
+
}
|
| 405 |
+
|
| 406 |
+
/* κ badge: tap → re-derive (L5) + copy. Green flash = the page you see IS the κ you share. */
|
| 407 |
+
$("kap").onclick = async () => { try {
|
| 408 |
+
const ok = await YS.verify(doc, curKappa);
|
| 409 |
+
$("kap").classList.toggle("ok", ok); setTimeout(() => $("kap").classList.remove("ok"), 1600);
|
| 410 |
+
await navigator.clipboard.writeText(curKappa); toast(ok ? "re-derived ✓ · κ copied" : "κ MISMATCH — refused");
|
| 411 |
+
} catch (e) { toast("κ unavailable here"); } };
|
| 412 |
+
|
| 413 |
+
$("newspace").onclick = () => show(YS.identity({ mood: "cosmic", wallpaper: "preset:cosmic", accent: "#7c6cf0" }), "edit");
|
| 414 |
+
document.addEventListener("click", (e) => { if (!$("picker").contains(e.target) && e.target !== $("avatar")) $("picker").classList.remove("on"); });
|
| 415 |
+
|
| 416 |
+
boot();
|
| 417 |
+
</script>
|
| 418 |
+
</body>
|
| 419 |
+
</html>
|