app
Hero Studio
Public Made by Adomby adom
The hero-image lab with a human gate: `hero-studio pose <owner/slug>` opens the studio in front of the human, posed for their package, and blocks until they click Generate, producing the provenance-stamped 2000x1250 PNG the registry's hero gate requires. The image carries the title and artwork; the exported image also bakes the type pill (top right) and full wiki slug (bottom right); the page shows the hero bare, and only the homepage marquee overlays its own dynamic info. Every hero wears its P
← Commit history
Publish 0.19.8
4 files changed
+15−7
package.json+1−1@@ -2,7 +2,7 @@ "slug": "hero-studio", "title": "Hero Studio", "type": "app",- "version": "0.19.7",+ "version": "0.19.8", "description": "The hero-image lab with a human gate: `hero-studio pose <owner/slug>` opens the studio in front of the human, posed for their package, and blocks until they click Generate, producing the provenance-stamped 2000x1250 PNG the registry's hero gate requires. The image carries the title and artwork; the exported image also bakes the type pill (top right) and full wiki slug (bottom right); the page shows the hero bare, and only the homepage marquee overlays its own dynamic info. Adom-org pages get the house wordmark and palette automatically; every other owner brings their own accent and background colors, with placement fixed for all. The floating app window is the vitrine: it auto-loads a real screenshot from the package's wiki page (screenshots/, docs/), takes a manual import, or a full-hero import (the human-ratified re-stamp path for existing heroes). The pose gate opens in verification mode: per-item checklist (title, subtitle, vitrine, type match) and a Generate that unlocks only fully confirmed. Includes vitrine bleeds, brand tile themes, gradient angles, pixel-faithful wiki marquee and index-card previews, and a component-hero mode with a photo/raytrace/phong fallback ladder.", "license": "MIT", "tags": [
page.json+1−1@@ -2,7 +2,7 @@ "slug": "hero-studio", "title": "Hero Studio", "type": "app",- "version": "0.19.7",+ "version": "0.19.8", "description": "The hero-image lab with a human gate: `hero-studio pose <owner/slug>` opens the studio in front of the human, posed for their package, and blocks until they click Generate, producing the provenance-stamped 2000x1250 PNG the registry's hero gate requires. The image carries the title and artwork; the exported image also bakes the type pill (top right) and full wiki slug (bottom right); the page shows the hero bare, and only the homepage marquee overlays its own dynamic info. Adom-org pages get the house wordmark and palette automatically; every other owner brings their own accent and background colors, with placement fixed for all. The floating app window is the vitrine: it auto-loads a real screenshot from the package's wiki page (screenshots/, docs/), takes a manual import, or a full-hero import (the human-ratified re-stamp path for existing heroes). The pose gate opens in verification mode: per-item checklist (title, subtitle, vitrine, type match) and a Generate that unlocks only fully confirmed. Includes vitrine bleeds, brand tile themes, gradient angles, pixel-faithful wiki marquee and index-card previews, and a component-hero mode with a photo/raytrace/phong fallback ladder.", "license": "MIT", "tags": [
public/js/wizard.js+1−1@@ -397,7 +397,7 @@ function wizRenderReview(host) { const checks = document.createElement('div'); checks.className = 'rv-col';- const seasoned = (state.gatesDone || 0) >= 4;+ const seasoned = state.seasoned !== undefined ? !!state.seasoned : (state.gatesDone || 0) >= 4; if (seasoned && !wizExpanded) { const row = document.createElement('div'); row.className = 'rv-check ok';
server.js+12−4@@ -328,14 +328,14 @@ async function fetchRemoteGates() { const REG = process.env.ADOM_WIKI_REGISTRY || 'https://wiki.adom.inc'; const headers = {}; try { headers.Authorization = 'Bearer ' + fs.readFileSync('/var/run/adom/api-key', 'utf8').trim(); } catch {}- const who = await fetch(REG + '/api/me', { headers }).then((r) => (r.ok ? r.json() : null)).catch(() => null);- const handle = who && (who.name || who.handle || (who.user && (who.user.name || who.user.handle)));+ const who = await fetch(REG + '/auth/me', { headers }).then((r) => (r.ok ? r.json() : null)).catch(() => null);+ const handle = who && (who.username || who.name || who.handle || (who.user && (who.user.username || who.user.name || who.user.handle))); if (!handle) return; const r = await fetch(REG + '/api/users/' + handle + '/hero-gates', { headers }); if (!r.ok) return; const d = await r.json(); if (typeof d.hero_gates === 'number') {- remoteGates = { count: d.hero_gates, seasoned: !!d.seasoned, handle };+ remoteGates = { count: d.hero_gates, seasoned: !!d.seasoned, threshold: d.threshold || 4, handle }; logLine('info', 'account gate count: ' + d.hero_gates + ' (' + handle + ', seasoned=' + d.seasoned + ')'); stateRev += 1; }@@ -345,6 +345,14 @@ fetchRemoteGates(); function readGates() { return Math.max(readLocalGates(), remoteGates ? remoteGates.count : 0); }+// seasoned keys on the SERVER's boolean when the endpoint answered (their+// threshold propagates with no studio release); local >= 4 is the offline+// fallback and covers gates whose heroes have not landed yet+function isSeasoned() {+ const threshold = remoteGates ? remoteGates.threshold : 4;+ if (readLocalGates() >= threshold) return true;+ return remoteGates ? remoteGates.seasoned : false;+} // Live wiki re-fetch for a page row, shared by /refresh-page, the dropdown, // and now raw POST /state page changes (#44: the fixture-row hole).@@ -403,7 +411,7 @@ function route(req, res, url, p) { let body; if (cached && cached.rev === stateRev) body = cached.body; else {- body = JSON.stringify(Object.assign({}, lean ? leanState() : state, { _toast: lastToast, _gen: lastGen, enums: ENUMS, version: PKG_VERSION, aiThread: AI_THREAD, gatesDone: readGates(), resolved: resolvedText() }));+ body = JSON.stringify(Object.assign({}, lean ? leanState() : state, { _toast: lastToast, _gen: lastGen, enums: ENUMS, version: PKG_VERSION, aiThread: AI_THREAD, gatesDone: readGates(), seasoned: isSeasoned(), resolved: resolvedText() })); stateBodyCache[variant] = { rev: stateRev, body }; } res.writeHead(200, { 'Content-Type': 'application/json', ETag: etag, 'Cache-Control': 'no-cache' });