← Commit history

Publish 0.6.0

Colby Knox ·e11201dc61 ·2mo ago ·parent f08b6fc
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' +