diff --git a/system/netlify/functions/index.mjs b/system/netlify/functions/index.mjs index ee11702435..397c5a995c 100644 --- a/system/netlify/functions/index.mjs +++ b/system/netlify/functions/index.mjs @@ -1360,7 +1360,10 @@ async function fun(event, context) { // Also detect based on nolabel/nogap query params which indicate embedded preview mode var isL5=false;try{isL5=location.pathname.indexOf('.lua')>=0||(window.self!==window.top&&(document.referrer.indexOf('l5.aesthetic')>=0||document.referrer.indexOf('l5.prompt')>=0));}catch(e){isL5=document.referrer.indexOf('l5.')>=0;} var isKidlisp=false;if(!isL5){try{isKidlisp=window.self!==window.top&&(window.parent.location.hostname.indexOf('kidlisp')>=0||document.referrer.indexOf('kidlisp')>=0);}catch(e){isKidlisp=document.referrer.indexOf('kidlisp')>=0;}} - if(!isKidlisp&&!isL5&&window.self!==window.top){if(qs.indexOf('nolabel')>=0&&qs.indexOf('nogap')>=0)isKidlisp=true;} + // nogap+nolabel reads as "someone else's preview chrome" — kidlisp.com's + // embed. An HTML shell (prompt.ac) passes the same pair but says so with + // shellhtml, and wants the plain boot log, not the $code preview. + if(!isKidlisp&&!isL5&&window.self!==window.top&&qs.indexOf('shellhtml')<0){if(qs.indexOf('nolabel')>=0&&qs.indexOf('nogap')>=0)isKidlisp=true;} // Detect $code piece name from URL pathname OR document.referrer (kidlisp.com parent) var klPieceName='';(function(){var p=location.pathname;if(p.length>1){var pn=p.slice(1).split('?')[0].split(':')[0];if(pn.startsWith('$'))pn=pn.slice(1);if(pn&&pn!=='prompt'&&pn!=='kidlisp')klPieceName=pn;}if(!klPieceName){try{var ref=document.referrer;if(ref&&(ref.indexOf('kidlisp.com')>=0||ref.indexOf('kidlisp')>=0)){var rp=new URL(ref).pathname.slice(1).split('?')[0].split(':')[0];if(rp.startsWith('$'))rp=rp.slice(1);if(rp&&rp!=='prompt'&&rp!=='kidlisp'&&rp!=='device')klPieceName=rp;}}catch(e){}}})(); var klSourceLines=[],klSourceReady=false; diff --git a/system/public/aesthetic.computer/boot.mjs b/system/public/aesthetic.computer/boot.mjs index 458a9eb2af..a09020a6c6 100644 --- a/system/public/aesthetic.computer/boot.mjs +++ b/system/public/aesthetic.computer/boot.mjs @@ -332,6 +332,11 @@ function hideBootLog() { if (bc?.hide) { bc.hide(); } + // 🐚 The piece is on screen now. `ready` (disk loaded) fires earlier, while + // the overlay still stands, so a hosting shell that dresses the runtime — + // prompt.ac's corner label — needs this later moment to avoid drawing its + // chrome over the boot log. + window.parent?.postMessage({ type: "ac:boot-hidden" }, "*"); // Safety net: if the disk-loaded path somehow never marked success, getting // far enough to hide the overlay still counts as a boot. markBootSuccess(); diff --git a/system/public/html/lib/moat.mjs b/system/public/html/lib/moat.mjs index becfe10e2c..74cdb9ea26 100644 --- a/system/public/html/lib/moat.mjs +++ b/system/public/html/lib/moat.mjs @@ -114,6 +114,10 @@ export function el(tag, props = {}, ...kids) { // frame.load("clock") → in-place piece swap (no reload) via `load-piece` // frame.onPiece(cb) → fires when the runtime reports a new slug // frame.onReady(cb) → fires once the runtime is booted + interactive +// frame.onLive(cb) → fires once the runtime's boot overlay is down +// and the piece itself is on screen (later than +// onReady — that one lands while the boot log +// still covers the frame) // In-place swap depends on boot.mjs's `load-piece` parent handler; if the // runtime is older, call frame.load(slug, { reload: true }) to fall back to a // full iframe.src navigation. @@ -123,9 +127,11 @@ export class ACFrame { this.origin = origin; this.piece = null; this.ready = false; + this.live = false; this.bridgeReady = false; this._onPiece = []; this._onReady = []; + this._onLive = []; this._onBridgeReady = []; window.addEventListener("message", (e) => { if (e.source !== iframe.contentWindow) return; @@ -145,6 +151,11 @@ export class ACFrame { this.ready = true; this._onReady.forEach((cb) => cb()); } + } else if (d.type === "ac:boot-hidden") { + if (!this.live) { + this.live = true; + this._onLive.forEach((cb) => cb()); + } } else if (d.type === "ac:bridge-ready") { if (!this.bridgeReady) { this.bridgeReady = true; @@ -158,6 +169,7 @@ export class ACFrame { // the fresh runtime. boot(slug = "prompt", params = "nogap=true&nolabel=true") { this.ready = false; + this.live = false; this.bridgeReady = false; this.piece = null; this.iframe.src = `${this.origin}/${slug}?${params}`; @@ -174,6 +186,7 @@ export class ACFrame { } onPiece(cb) { this._onPiece.push(cb); return this; } onReady(cb) { this._onReady.push(cb); return this; } + onLive(cb) { this._onLive.push(cb); return this; } onBridgeReady(cb) { this._onBridgeReady.push(cb); return this; } } diff --git a/system/public/prompt.ac/index.html b/system/public/prompt.ac/index.html index a7f667d1f0..0aacc57144 100644 --- a/system/public/prompt.ac/index.html +++ b/system/public/prompt.ac/index.html @@ -66,8 +66,16 @@ * { box-sizing: border-box; -webkit-tap-highlight-color: transparent; } html, body { height: 100%; margin: 0; background: var(--bg); } - body { font-family: var(--font); color: var(--text); overflow: hidden; cursor: var(--cursor-precise), auto; } - #cmd { cursor: var(--cursor-precise), text; } + /* The shell is a screen, not a document: dragging across it paints no blue + wash and a long press raises no callout, the way the raster prompt's + canvas behaves. The typed line is the one place text is really text. */ + body { + font-family: var(--font); color: var(--text); overflow: hidden; + cursor: var(--cursor-precise), auto; + -webkit-user-select: none; user-select: none; + -webkit-touch-callout: none; + } + #cmd { cursor: var(--cursor-precise), text; -webkit-user-select: text; user-select: text; } button, .corner { cursor: var(--cursor-active), pointer; } ::selection { background: var(--highlight); color: var(--bg); } @@ -75,7 +83,12 @@ stage stays inert underneath the opaque #screen — never visibility:hidden, because Chrome throttles rendering for hidden cross-origin iframes and the runtime's boot/load loop would stall (same-origin dev masks this). ── */ + /* The stage's ground is bare for the half-second before the runtime's own + first paint lands, so it wears the same paper the boot log will — black + under the dark log, white under the light one — and the boot starts + without a flash. */ .stage { position: absolute; inset: 0; background: #000; } + @media (prefers-color-scheme: light) { .stage { background: #fff; } } iframe { position: absolute; inset: 0; width: 100%; height: 100%; border: 0; display: block; } body[data-mode="prompt"] .stage { pointer-events: none; } @@ -194,6 +207,7 @@ login curtain (showLoginCurtain in prompt.mjs): it stands only while the line is deactivated, empty, and quiet (no reply). ─────────────── */ .auth { display: flex; gap: 14px; transition: opacity .35s; } + .auth[hidden] { display: none; } /* `display: flex` above outranks the UA rule */ body.active .auth, body.typing .auth, body.replying .auth, body.submitting .auth { opacity: 0; pointer-events: none; } .auth button { @@ -220,23 +234,32 @@ font-size: clamp(20px, 2.6vw, 30px); line-height: 1.3; text-shadow: 1px 1px 0 rgba(0, 0, 0, .75); } - body[data-mode="piece"] .corner { display: block; } + body[data-mode="piece"].live .corner { display: block; } body[data-mode="piece"] #screen.overlay ~ .corner { display: none; } .corner:hover { color: var(--highlight); } - /* ── Boot loader — only when deep-linking straight into a piece. ────── */ - .loading { - position: absolute; inset: 0; z-index: 5; display: none; - align-items: center; justify-content: center; - color: var(--dim); font-size: 13px; letter-spacing: .5px; pointer-events: none; - transition: opacity .4s; background: var(--bg); - } - .loading.on { display: flex; } - .loading.gone { opacity: 0; } .sr-only { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0; } - + +
@@ -263,7 +286,6 @@
-
booting aesthetic computer…
runtime booting