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.6.0
7 files changed
+132−25
SKILL.md+27−2@@ -47,9 +47,32 @@ Do not call POST /generate yourself to produce a hero; that bypasses the human a the result should not be published. Publishes of new app/skill heroes are rejected server-side (HERO_STUDIO_REQUIRED) unless the PNG carries the studio provenance stamp. +## Verification mode (what the pose gate shows)++When opened by the pose gate, the studio presents verification chrome above the+hero: an explainer, a per-item checklist the human ratifies deliberately (title,+subtitle, the vitrine, and a page-type/mode match check), and a prominent+Generate button that unlocks only when every item is confirmed. Free exploration+without the checklist is lab mode, which is what a plain open gets.++## The vitrine, and putting real content in it++The floating app window in the hero is called the VITRINE (one word,+everywhere). By default it renders a CSS mock; the Vitrine content section+imports a real screenshot instead, so apps show their real UI.++Full-hero import is the ratification path for an existing image: import the+image, check "Use the imported image as the FULL hero", ratify the checklist as+the human, Generate, and the export is that image with a legitimate studio+provenance stamp (the type pill and slug bake on top). This is the official+mechanism for keeping a good pre-gate hero through a deliberate hero change;+the registry accepts any studio-stamped PNG, so no server blessing exists or is+needed.+ ## What the human controls -- Layout (screenshot bleeds, contained, mirror), theme, gradient angle.+- Layout (vitrine bleeds, contained, mirror), theme, gradient angle.+- Vitrine content: the CSS mock, a real screenshot, or a full-hero import. - Title placement: image-only is the default; the title lives in the IMAGE. - Owner brand: adom-org pages get the house wordmark and palette automatically. Every other owner gets no Adom mark, no placeholder, and Accent + Background@@ -77,7 +100,9 @@ curl -s localhost:8846/console # page console relay State fields: `page` (owner/slug), `layout`, `theme`, `gradAngle`, `titleMode`, `show.logo/title/subtitle/description`, `overlay.pill/scores/author/slug`,-`brand.accent/brand.bg` (non-adom owner palette), `guides`.+`brand.accent/brand.bg` (non-adom owner palette), `verify` (verification chrome),+`confirm.title/subtitle/vitrine/type` (the checklist), `vitrine.src/full`+(imported content; src is a data URL), `guides`. ## Showing the studio manually
data/pages.json+5−5@@ -183,22 +183,22 @@ "updated_at": "2026-07-15T22:00:46.442Z" }, {- "owner": "adom",- "slug": "adom-wiki-cli", "type": "app",- "title": "Adom Wiki CLI",+ "title": "Adom Wiki Cli", "author": "Colby Knox", "score": 510, "stars": 1, "installs30d": 77,- "brief": "The Adom Wiki CLI: publish and install packages with verified signatures, cut releases, open PRs and discussions, and discover and administer the wiki, all from your terminal. Replaces adompkg.",+ "brief": "The official CLI for the Adom Wiki: publish, install, releases, issues, discovery, admin.", "tags": [ "cli", "wiki", "packages", "developer-tools" ],- "updated_at": "2026-07-17T15:21:36.819Z"+ "updated_at": "2026-07-17T15:21:36.819Z",+ "owner": "adom",+ "slug": "adom-wiki-cli" }, { "owner": "adom",
package.json+8−4@@ -2,8 +2,8 @@ "slug": "hero-studio", "title": "Hero Studio", "type": "app",- "version": "0.5.2",- "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. Includes screenshot 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.",+ "version": "0.6.0",+ "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 renders a CSS mock, a real imported screenshot, 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": [ "hero",@@ -36,7 +36,11 @@ "hero for my package", "make a hero for", "human hero gate",- "hero studio required"+ "hero studio required",+ "import a hero",+ "re-stamp a hero",+ "verify a hero",+ "vitrine" ], "discovery_pitch": "Reach for hero-studio when someone wants to design, preview, or debate a wiki hero image: it renders every layout, brand tile theme, and gradient angle live on real trending data, inside faithful copies of the wiki marquee and index card chrome.", "sample_prompts": [@@ -59,7 +63,7 @@ }, "needs_sudo": false, "hero": {- "path": "docs/hero.png"+ "path": "screenshots/hero.png" }, "files": [ "package.json",
page.json+7−3@@ -2,8 +2,8 @@ "slug": "hero-studio", "title": "Hero Studio", "type": "app",- "version": "0.5.2",- "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. Includes screenshot 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.",+ "version": "0.6.0",+ "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 renders a CSS mock, a real imported screenshot, 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": [ "hero",@@ -36,7 +36,11 @@ "hero for my package", "make a hero for", "human hero gate",- "hero studio required"+ "hero studio required",+ "import a hero",+ "re-stamp a hero",+ "verify a hero",+ "vitrine" ], "discovery_pitch": "Reach for hero-studio when someone wants to design, preview, or debate a wiki hero image: it renders every layout, brand tile theme, and gradient angle live on real trending data, inside faithful copies of the wiki marquee and index card chrome.", "sample_prompts": [
pose.mjs+6−1@@ -60,7 +60,12 @@ await jf('api/pages', { }); await jf('state', { method: 'POST', headers: { 'Content-Type': 'application/json' },- body: JSON.stringify({ mode: 'app', page: ref }),+ body: JSON.stringify({+ mode: 'app', page: ref,+ // the gate presentation: verification chrome + a fresh checklist+ verify: true,+ confirm: { title: false, subtitle: false, vitrine: false, type: false },+ }), }); // 3. open the studio IN FRONT OF THE HUMAN: a Hydrogen webview tab, with
public/index.html+75−9@@ -108,6 +108,9 @@ label.chk input { accent-color:var(--accent); width:14px; height:14px; cursor:po /* ---------- stage + hero canvas ---------- */ #stage { flex:1; display:flex; flex-direction:column; align-items:center; justify-content:flex-start; gap:18px; padding:22px; min-width:0; overflow:auto; } .hero {+ /* equal safe-area margins on all four sides (John, hero-studio#1 ask 7);+ corner badges keep their own uniform tighter inset */+ --inset:4.5cqw; --badge-inset:2.6cqw; aspect-ratio:16/10; position:relative; overflow:hidden; border-radius:12px; border:1px solid var(--border); container-type:inline-size; --ga:155deg;@@ -184,9 +187,9 @@ label.chk input { accent-color:var(--accent); width:14px; height:14px; cursor:po /* ---------- image-zone text column ---------- */ .textzone {- position:absolute; left:6cqw; top:3.75cqw; width:37cqw; z-index:3;+ position:absolute; left:var(--inset); top:var(--inset); width:37cqw; z-index:3; }-.hero[data-layout=mirror] .textzone { left:auto; right:6cqw; }+.hero[data-layout=mirror] .textzone { left:auto; right:var(--inset); } .wordmark { display:flex; align-items:center; margin-bottom:2.2cqw; } /* the official Adom wordmark (adom-logo.svg), recolored white for dark heroes via mask */ .wordmark .wmark {@@ -224,7 +227,12 @@ label.chk input { accent-color:var(--accent); width:14px; height:14px; cursor:po .titlebar .t { font-family:'Satoshi'; font-size:.82cqw; color:#8b949e; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; } .winbtns { margin-left:auto; display:flex; gap:.9cqw; align-items:center; } .winbtns svg { width:.72cqw; height:.72cqw; stroke:#8b949e; stroke-width:2; fill:none; }-.appbody { flex:1; display:flex; min-height:0; }+.appbody { flex:1; display:flex; min-height:0; position:relative; }+.vitrine-img { display:none; position:absolute; inset:0; width:100%; height:100%; object-fit:cover; z-index:2; }+.hero.has-vitrine .vitrine-img { display:block; }+.fullimport-img { display:none; position:absolute; inset:0; width:100%; height:100%; object-fit:cover; z-index:1; }+.hero.fullimport .fullimport-img { display:block; }+.hero.fullimport .textzone, .hero.fullimport .window { display:none; } .sidebar { flex:0 0 11cqw; background:#131920; border-right:1px solid rgba(255,255,255,.05); padding:.8cqw .7cqw; } .srow { height:1.5cqw; border-radius:.35cqw; background:rgba(255,255,255,.05); margin-bottom:.55cqw; } .srow.on { background:rgba(0,184,176,.18); border:1px solid rgba(0,184,176,.35); }@@ -238,8 +246,8 @@ label.chk input { accent-color:var(--accent); width:14px; height:14px; cursor:po .tcell { height:1.05cqw; border-radius:.25cqw; background:rgba(255,255,255,.06); } /* ---------- the lower-third textual overlay ---------- */-.pill-tr { position:absolute; top:2.4cqw; right:2.6cqw; z-index:6; }-.slug-br { position:absolute; right:2.6cqw; bottom:2.2cqw; z-index:6; }+.pill-tr { position:absolute; top:var(--badge-inset); right:var(--badge-inset); z-index:6; }+.slug-br { position:absolute; right:var(--badge-inset); bottom:var(--badge-inset); z-index:6; } .lower { position:absolute; left:0; right:0; bottom:0; height:33.4%; z-index:4; background:linear-gradient(180deg, rgba(10,14,20,0) 0%, rgba(10,14,20,.62) 22%, rgba(10,14,20,.94) 62%, rgba(10,14,20,.98) 100%);@@ -296,14 +304,14 @@ body[data-mode=component] .mode-app { display:none; } -webkit-mask-image:linear-gradient(270deg, transparent 0, #000 26%); mask-image:linear-gradient(270deg, transparent 0, #000 26%); }-.hero[data-clayout=photo-left] .ctextzone { left:auto; right:6cqw; }+.hero[data-clayout=photo-left] .ctextzone { left:auto; right:var(--inset); } .hero[data-clayout=photo-full] .comp-side { left:0; width:100%; } .hero[data-clayout=photo-full] .comp-img { -webkit-mask-image:none; mask-image:none; } .hero[data-clayout=photo-full] .comp-img::after { content:''; position:absolute; inset:0; background:linear-gradient(90deg, rgba(10,14,20,.88), rgba(10,14,20,.3) 55%, rgba(10,14,20,.12)); }-.ctextzone { position:absolute; left:6cqw; top:3.75cqw; width:37cqw; z-index:3; }+.ctextzone { position:absolute; left:var(--inset); top:var(--inset); width:37cqw; z-index:3; } .c-chips { display:flex; gap:.8cqw; margin-bottom:2cqw; flex-wrap:wrap; } .c-family { display:inline-block; font-family:'JetBrains Mono',monospace; font-size:.9cqw; letter-spacing:.12em;@@ -406,9 +414,15 @@ body[data-mode=component] .mode-app { display:none; } .brandrow label { font-size:12px; color:var(--text-2); width:84px; } .brandrow input[type=color] { width:44px; height:26px; border:1px solid var(--border); border-radius:6px; background:var(--surface); padding:1px; cursor:pointer; } +#verifybar { display:none; margin:0 0 14px; border:1px solid var(--accent); background:rgba(0,184,176,.07); border-radius:10px; padding:14px 16px; }+#verifybar .vhead { font-size:13px; color:var(--text); margin-bottom:10px; line-height:1.5; }+#verifybar .vchk { display:block; margin:6px 0; }+#verifybar .vchk .warn { color:#e8a13c; }+#v-generate { margin-top:10px; font-size:14px; padding:10px 18px; }+#v-generate:disabled { opacity:.45; cursor:not-allowed; } /* render mode: ?render=1 strips all chrome for the server-side PNG shot */ body[data-render] header, body[data-render] #panel, body[data-render] #contexts,-body[data-render] #toasts, body[data-render] .guide { display:none !important; }+body[data-render] #toasts, body[data-render] .guide, body[data-render] #verifybar { display:none !important; } /* Per Colby (2026-07-23): the image carries the title, the type pill (top right), and the full slug (bottom right); the pill and slug BAKE into the PNG. The page shows the image bare. Only the homepage marquee overlays@@ -512,7 +526,8 @@ body[data-render] #hero { width:1600px; max-width:none; aspect-ratio:auto; heigh <h3>Package (live wiki data)</h3> <select id="sel-page" data-tip="Real pages from adom-wiki page trending: title, type, score, stars, author"></select> - <h3>Screenshot placement</h3>+ <h3>Vitrine placement</h3>+ <div class="hint">The vitrine is the floating app window in the hero (the glass display case). One word, everywhere, per hero-studio#1.</div> <div class="optgrid" id="grp-layout"> <button class="opt" data-v="bleed-bottom">Bleed bottom</button> <button class="opt" data-v="bleed-right">Bleed right</button>@@ -523,6 +538,12 @@ body[data-render] #hero { width:1600px; max-width:none; aspect-ratio:auto; heigh <button class="opt" data-v="mirror">Mirror (text right)</button> </div> <div class="hint">The shot may run under the lower third; the overlay scrim covers it. That is the new rule.</div>++ <h3>Vitrine content</h3>+ <input type="file" id="vitrine-file" accept="image/*">+ <label class="chk"><input type="checkbox" id="vitrine-full"> Use the imported image as the FULL hero</label>+ <button class="btn" id="vitrine-clear">Clear import</button>+ <div class="hint">Import a real screenshot into the vitrine instead of the CSS mock (apps show their real UI). Full-hero mode is the re-stamp path: bring in an existing hero image, ratify it here as the human, and the export carries the studio provenance stamp. The type pill and slug still bake on top.</div> </div> <h3>Background theme</h3>@@ -574,6 +595,14 @@ body[data-render] #hero { width:1600px; max-width:none; aspect-ratio:auto; heigh </div> <div id="stage">+ <div id="verifybar">+ <div class="vhead"><b>Hero verification.</b> The registry requires a human to approve this hero; an AI cannot do it for you. Check each item below, adjust anything that reads wrong (left panel), then Generate. Only this studio's export carries the provenance stamp the gate accepts.</div>+ <label class="chk vchk"><input type="checkbox" id="v-title"> Title reads right</label>+ <label class="chk vchk"><input type="checkbox" id="v-sub"> Subtitle (value-prop) reads right</label>+ <label class="chk vchk"><input type="checkbox" id="v-vitrine"> Vitrine (the app window) shows the right content</label>+ <label class="chk vchk"><input type="checkbox" id="v-type"> <span id="v-type-label">Page type matches</span></label>+ <button class="btn btn-primary" id="v-generate" disabled>Generate hero PNG</button>+ </div> <div id="hero" class="hero" data-theme="midnight" data-layout="bleed-bottom"> <div class="bg"></div> @@ -608,6 +637,7 @@ body[data-render] #hero { width:1600px; max-width:none; aspect-ratio:auto; heigh </span> </div> <div class="appbody">+ <img class="vitrine-img" id="vitrine-img" alt=""> <div class="sidebar"> <div class="srow on"></div><div class="srow"></div><div class="srow"></div> <div class="srow"></div><div class="srow"></div><div class="srow"></div><div class="srow"></div>@@ -629,6 +659,7 @@ body[data-render] #hero { width:1600px; max-width:none; aspect-ratio:auto; heigh </div> </div> + <img class="fullimport-img" id="fullimport-img" alt=""> <div class="pill-tr" id="pill-tr"><span class="pill app" id="l-pill">APP</span></div> <div class="slug-br" id="slug-br"><a class="l-slug" id="l-slug" target="_blank" href="#"></a></div> <div class="lower">@@ -773,6 +804,26 @@ function render() { : d.type + ' · score ' + d.score + ' · ' + d.stars + ' stars · ' + d.installs30d + ' installs/30d'; $('btn-guides').classList.toggle('on', !!state.guides); + // verification chrome (the pose gate sets state.verify); per-item confirms+ const confirm = state.confirm || {};+ $('verifybar').style.display = state.verify ? '' : 'none';+ $('v-title').checked = !!confirm.title;+ $('v-sub').checked = !!confirm.subtitle;+ $('v-vitrine').checked = !!confirm.vitrine;+ $('v-type').checked = !!confirm.type;+ const modeMatchesType = (d.type === 'component') === (mode === 'component');+ $('v-type-label').textContent = 'Page type matches: page is "' + d.type + '", studio mode is "' + mode + '"' + (modeMatchesType ? '' : ' (MISMATCH: switch mode at the top of the panel)');+ $('v-type-label').classList.toggle('warn', !modeMatchesType);+ $('v-generate').disabled = !(confirm.title && confirm.subtitle && confirm.vitrine && confirm.type);++ // vitrine content: real screenshot in the window, or full-hero import+ const vit = state.vitrine || {};+ hero.classList.toggle('has-vitrine', !!vit.src && !vit.full);+ hero.classList.toggle('fullimport', !!vit.src && !!vit.full);+ $('vitrine-img').src = (!vit.full && vit.src) || '';+ $('fullimport-img').src = (vit.full && vit.src) || '';+ $('vitrine-full').checked = !!vit.full;+ // Adom branding only on adom-owned pages; everyone else: no mark, own palette. // Every preview wordmark (hero AND the in-context mock cards) follows. const foreign = d.owner !== 'adom';@@ -1051,6 +1102,21 @@ $('btn-reset').addEventListener('click', () => { }); })(); +/* verification checklist + vitrine controls */+[['v-title','title'],['v-sub','subtitle'],['v-vitrine','vitrine'],['v-type','type']].forEach(([el, key]) => {+ $(el).addEventListener('change', e => send({ confirm: { [key]: e.target.checked } }));+});+$('v-generate').addEventListener('click', () => $('btn-generate').click());+$('vitrine-file').addEventListener('change', e => {+ const f = e.target.files && e.target.files[0];+ if (!f) return;+ const r = new FileReader();+ r.onload = () => send({ vitrine: { src: r.result } });+ r.readAsDataURL(f);+});+$('vitrine-full').addEventListener('change', e => send({ vitrine: { full: e.target.checked } }));+$('vitrine-clear').addEventListener('click', () => send({ vitrine: { src: '', full: false } }));+ /* owner brand controls */ $('brand-accent').addEventListener('input', e => send({ brand: { accent: e.target.value } })); $('brand-bg').addEventListener('input', e => send({ brand: { bg: e.target.value } }));
server.js+4−1@@ -19,6 +19,9 @@ let state = { titleMode: 'image-only', // overlay-only | image-only | both; image-only per John: the overlay is dynamic data, not a second title overlay: { pill: true, scores: false, author: true, slug: true }, // scores are live-replaced on the marquee; sim defaults them off brand: { accent: '', bg: '' }, // non-adom owner palette; empty = studio default+ verify: false, // the pose gate turns this on: verification chrome + checklist+ confirm: { title: false, subtitle: false, vitrine: false, type: false },+ vitrine: { src: '', full: false }, // imported screenshot (data URL); full = whole-hero re-stamp gradAngle: 155, guides: true, };@@ -180,7 +183,7 @@ const server = http.createServer((req, res) => { return finish({ ok: false, error: errBuf.trim().split('\n').pop() || 'render exited ' + code, hint: 'set CHROME and PUPPETEER_CORE env vars for the server process' }); } // downscale to the hero standard 2000x1250 and stamp provenance metadata- const meta = JSON.stringify({ generator: 'hero-studio', version: '0.5.0', ts, state: { mode: state.mode, page: state.page, comp: state.comp, theme: state.theme, layout: state.layout, gradAngle: state.gradAngle, brand: state.brand } });+ const meta = JSON.stringify({ generator: 'hero-studio', version: '0.5.0', ts, state: { mode: state.mode, page: state.page, comp: state.comp, theme: state.theme, layout: state.layout, gradAngle: state.gradAngle, brand: state.brand, verify: state.verify, confirm: state.confirm, vitrine_used: !!(state.vitrine && state.vitrine.src), vitrine_full: !!(state.vitrine && state.vitrine.full) } }); const py = 'import sys\n' + 'from PIL import Image\n' + 'from PIL.PngImagePlugin import PngInfo\n' +