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.18.4
5 files changed
+53−6
package.json+1−1@@ -2,7 +2,7 @@ "slug": "hero-studio", "title": "Hero Studio", "type": "app",- "version": "0.18.3",+ "version": "0.18.4", "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.18.3",+ "version": "0.18.4", "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+33−4@@ -27,6 +27,7 @@ function wizSend(patch) { /* ---------- step engine ---------- */ let wizStep = 'package';+let wizPickerOpen = false; let wizExpanded = false; // seasoned checklist expanded back to one-by-one let wizUploads = []; // this session's uploaded images (client-side gallery) let wizFineTune = false;@@ -89,11 +90,39 @@ const WIZ_THEMES = [ const WIZ_STEPS = [ { id: 'package', title: 'Package',- hint: 'Which page this hero is for.',+ hint: 'Which page this hero is for. One studio per page; your other open studios show here.', summary: () => (wp().title || state.page),- options: () => pages.slice(0, 24).map(pg => ({ v: pg.owner + '/' + pg.slug, label: pg.title, sub: pg.owner + '/' + pg.slug })),- get: () => state.page,- set: (v) => { if (!state.verify) wizSend({ page: v }); else handleToast({ text: 'The page is locked during a review gate.', kind: 'warn', seq: Date.now() }); },+ custom: (host) => {+ const wrap = document.createElement('div');+ wrap.className = 'wiz-finish';+ const cur = wizCard({ v: state.page, label: wp().title, sub: state.page + (state.verify ? ' · in review' : ' · this studio') }, true, () => {});+ wrap.appendChild(cur);+ fetch('instances').then(r => r.json()).then((d) => {+ (d.instances || []).filter(i => !i.self && i.task && i.task !== state.page).forEach(i => {+ const c = wizCard({ v: 'sib', label: i.task, sub: 'open in "' + i.thread + '"' }, false, () => {+ // same host, sibling port: swap the port segment of this URL+ location.href = location.href.replace(/\b\d{4,5}\b/, String(i.port));+ });+ wrap.appendChild(c);+ });+ }).catch(() => {});+ if (!wizPickerOpen) {+ const more = wizCard({ v: 'more', label: 'Another page\u2026', sub: state.verify ? 'This studio is mid-review; a second studio opens for other pages' : 'Pick from the wiki' }, false, () => {+ if (state.verify) { handleToast({ text: 'This studio is locked to ' + state.page + ' during review. Ask your AI to pose the other page; it gets its own studio.', kind: 'info', seq: Date.now() }); return; }+ wizPickerOpen = true;+ wizLastSig = '';+ wizardRender();+ });+ wrap.appendChild(more);+ } else {+ pages.slice(0, 40).forEach(pg => {+ const ref = pg.owner + '/' + pg.slug;+ if (ref === state.page) return;+ wrap.appendChild(wizCard({ v: ref, label: pg.title, sub: ref }, false, () => { wizPickerOpen = false; wizSend({ page: ref }); }));+ });+ }+ host.appendChild(wrap);+ }, }, { id: 'showcase', title: 'Showcase image',
public/studio.css+2@@ -386,3 +386,5 @@ body.wizard.show-ctx #contexts { display:flex; } #ab-close { position:absolute; top:8px; right:10px; background:none; border:none; color:var(--text-2); font-size:18px; line-height:1; cursor:pointer; padding:2px 6px; border-radius:6px; } #ab-close:hover { color:var(--text); background:rgba(255,255,255,.06); }+/* finishing + package cards: one size, aligned */+.wiz-finish .card { width:200px; min-height:92px; justify-content:center; }
server.js+16@@ -390,6 +390,22 @@ function route(req, res, url, p) { res.writeHead(200, { 'Content-Type': mime, 'Content-Length': buf.length, 'Cache-Control': 'public, max-age=31536000, immutable' }); return res.end(buf); }+ if (p === '/instances' && req.method === 'GET') {+ // the live-studio roster for the wizard's Package step: one studio per+ // page/thread is the multi-package model, so show the siblings+ const rows = [];+ try {+ for (const f of fs.readdirSync(INSTANCES_DIR)) {+ if (!f.endsWith('.json') || f === 'gates.json') continue;+ let e;+ try { e = JSON.parse(fs.readFileSync(path.join(INSTANCES_DIR, f), 'utf8')); } catch { continue; }+ try { process.kill(e.pid, 0); } catch { continue; }+ rows.push({ port: e.port, thread: e.thread, task: e.task, self: e.port === PORT });+ }+ } catch {}+ res.writeHead(200, { 'Content-Type': 'application/json' });+ return res.end(JSON.stringify({ instances: rows }));+ } if (p === '/version') { res.writeHead(200, { 'Content-Type': 'application/json' }); return res.end(JSON.stringify({ version: PKG_VERSION, port: PORT, thread: AI_THREAD, ttlHours: KEEP_ALIVE ? null : TTL_HOURS, startedAt: STARTED_AT }));