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.14.0
6 files changed
+124−13
SKILL.md+14−5@@ -147,7 +147,13 @@ text intruding into the dynamic marquee band, text clipped by the hero edge (HARD FAIL: locks Generate), near-edge insets, and 4+ line title wraps. Findings render in the gate and publish to `state.lint` ([{id, level: warn|fail, msg}]); the AI should fix warns without being asked.-Contrast checking is not in v1.+FULL-HERO IMPORTS are never reported all-clear: the linter discloses that+composed checks cannot see imported pixels, and an edge-density scan of the+lower band flags text-like structure there (heuristic, hero-studio#22).+Quoted prompt-looking text in the image zone warns too: sample prompts belong+in page metadata (sample_prompts), which the dynamic overlay previews with a+copy button; a baked prompt is pixels nobody can copy. Contrast checking is+not in v1. ## Verification mode (what the pose gate shows) @@ -159,10 +165,13 @@ vitrine, then the type pill (where the human catches an AI's wrong app/skill/ component call); "Looks good" ticks that check and advances. The homepage- marquee overlay composites over the hero by default in the gate so the human SEES where the wiki URL, pill, and today's duplicated title land (annotated-with adom/wiki#80, which will drop the duplicate). The overlay BREATHES, tags-itself DYNAMIC, and hold-to-hides (press the band to see the pure export), so-dynamic text is never mistaken for baked text. Tour dialogs are draggable by-their header and remember where you put them. The header's focus button (zen)+with adom/wiki#80, which will drop the duplicate). STAGE HYGIENE (hero-studio#23): the stage shows ONLY final-hero+pixels plus the dynamic overlay when previewing; every explanation lives in+the STAGEBAR above the stage, never on the artwork. The stagebar's segmented+toggle flips between "Exactly my PNG" and "With homepage overlay" (keyboard D;+hold-the-band still peeks); the dynamic layer breathes and carries an amber+dashed outline so it can never be mistaken for baked pixels. Tour dialogs are+draggable by their header and remember where you put them. The header's focus button (zen) hides the panel and requests fullscreen so the hero gets the whole surface; Esc restores. Free exploration without the checklist is lab mode, a plain open.
data/pages.json+2−2@@ -84,7 +84,7 @@ "score": 0, "stars": 0, "installs30d": 0,- "brief": "Fair rolls in your terminal: dice expressions, coin flips, random picks.",+ "brief": "Roll dice expressions from the terminal with visible fair math: 2d6+1, d20, coin flips, and random picks.", "tags": [ "cli", "dice",@@ -229,7 +229,7 @@ "score": 539, "stars": 0, "installs30d": 82,- "brief": "adom-lbr , the EDA library translator. Bring a component in from any supported EDA tool and convert it to any other (KiCad \u21c4 Altium \u21c4 EAGLE/Fusion) through one canonical adom-lbr JSON , symbol + footp",+ "brief": "adom-lbr , the EDA library translator. Bring a component in from any supported EDA tool and convert it to any other (KiCad ⇄ Altium ⇄ EAGLE/Fusion) through one canonical adom-lbr JSON , symbol + footp", "tags": [ "eda", "altium",
package.json+1−1@@ -2,7 +2,7 @@ "slug": "hero-studio", "title": "Hero Studio", "type": "app",- "version": "0.13.0",+ "version": "0.14.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 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.13.0",+ "version": "0.14.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 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": [
pose.mjs+1−1@@ -135,7 +135,7 @@ await jf('state', { body: JSON.stringify({ mode: poseMode, page: ref, // the gate presentation: verification chrome + a fresh checklist- verify: true, tour: true, marqueePreview: true,+ verify: true, tour: true, marqueePreview: true, guides: false, confirm: { title: false, subtitle: false, vitrine: false, type: false }, flags: { title: false, subtitle: false, vitrine: false, type: false }, lint: [],
public/index.html+105−3@@ -295,8 +295,18 @@ label.chk input { accent-color:var(--accent); width:14px; height:14px; cursor:po /* dynamic-vs-baked (#16): the overlay breathes, tags itself, and hold-hides */ .hero.show-mq #mqoverlay { animation:mqbreathe 3.4s ease-in-out infinite; cursor:pointer; } @keyframes mqbreathe { 0%,100% { opacity:1; } 50% { opacity:.5; } }-#mqoverlay::after { content:'DYNAMIC \00b7 homepage adds this, NOT in your PNG \00b7 hold to hide'; position:absolute; right:1.2cqw; top:.6cqw; font-family:'JetBrains Mono',monospace; font-size:.85cqw; letter-spacing:.06em; color:#e8a13c; } .hero.mq-hold #mqoverlay { display:none !important; }+#mqoverlay .mqo-prompt { display:flex; gap:.8cqw; align-items:center; margin-top:.6cqw; font-family:'JetBrains Mono',monospace; font-size:1.05cqw; color:#c9d1d9; }+#mqoverlay .mqo-prompt button { font-size:.95cqw; padding:.2cqw .8cqw; border-radius:.5cqw; border:1px solid rgba(232,161,60,.6); background:rgba(232,161,60,.12); color:#e8a13c; cursor:pointer; }+.hero.show-mq #mqoverlay { outline:2px dashed rgba(232,161,60,.85); outline-offset:-2px; background:rgba(232,161,60,.045); }+/* the stagebar (#21/#23): stage chrome lives ABOVE the stage, never on it */+#stagebar { display:none; align-items:center; gap:10px; margin:10px 0 8px; }+#stagebar.on { display:flex; }+#stagebar .seg { display:flex; border:1px solid #30363d; border-radius:8px; overflow:hidden; }+#stagebar .seg button { background:#0d1117; color:var(--text); border:0; padding:6px 12px; font-size:12.5px; cursor:pointer; }+#stagebar .seg button.on { background:rgba(0,184,176,.18); color:var(--accent-bright); }+#stagebar .legend { font-size:12px; color:#8b949e; line-height:1.35; }+#stagebar .legend b { color:#e8a13c; font-weight:600; } .obadge { display:flex; align-items:center; gap:.42cqw; font-family:'JetBrains Mono',monospace; font-size:.95cqw; color:#aeb8c4;@@ -728,6 +738,13 @@ body[data-render] #hero { width:1600px; max-width:none; aspect-ratio:auto; heigh <div id="lintbox"></div> <button class="btn btn-primary" id="v-generate" disabled>Generate hero PNG</button> </div>+ <div id="stagebar">+ <div class="seg">+ <button id="sb-png">Exactly my PNG</button>+ <button id="sb-mq">With homepage overlay</button>+ </div>+ <div class="legend"><b>amber-outlined = dynamic</b>: the homepage adds it, it is NOT in your PNG. Press D (or hold the band) to flip. The lower band stays calm in the image because the marquee overlays its own info there.</div>+ </div> <div id="hero" class="hero" data-theme="midnight" data-layout="bleed-bottom"> <div class="bg"></div> @@ -791,6 +808,7 @@ body[data-render] #hero { width:1600px; max-width:none; aspect-ratio:auto; heigh <h3 class="mqo-title" id="mqo-title"></h3> <p class="mqo-brief" id="mqo-brief"></p> <span class="mqo-owner" id="mqo-owner"></span>+ <div class="mqo-prompt" id="mqo-prompt" style="display:none"><span id="mqo-prompt-text"></span><button id="mqo-prompt-copy">copy</button></div> </div> <div class="pill-tr" id="pill-tr" data-tip="Type pill: bakes into the PNG top right (app / skill / component). Toggle under Image badges"><span class="pill app" id="l-pill">APP</span></div> <div class="slug-br" id="slug-br" data-tip="Wiki slug: bakes into the PNG bottom right. Toggle under Image badges"><a class="l-slug" id="l-slug" target="_blank" href="#"></a></div>@@ -822,7 +840,7 @@ body[data-render] #hero { width:1600px; max-width:none; aspect-ratio:auto; heigh </div> </div> - <div class="guide"><span>marquee overlays its info over this lower band; keep it calm</span></div>+ <div class="guide"></div> </div> <div id="contexts">@@ -996,6 +1014,11 @@ function render() { } $('verifybar').style.display = state.verify && !approvedHere ? 'block' : 'none'; + // stagebar (#21/#23): stage chrome above the stage, never on it+ $('stagebar').classList.toggle('on', !!state.verify || !!state.marqueePreview);+ $('sb-png').classList.toggle('on', !state.marqueePreview);+ $('sb-mq').classList.toggle('on', !!state.marqueePreview);+ // walkthrough + procedural background + measured layout lint + page fields showTourStep(); scheduleLint();@@ -1015,7 +1038,12 @@ function render() { // planned fix so nobody thinks it is an accident. $('mqo-title').textContent = d.title; $('mqo-brief').textContent = firstSentence(d.brief || p.brief || '', 120);- $('mqo-owner').textContent = 'wiki.adom.inc/' + d.owner + '/' + d.slug + ' (overlay as the homepage renders TODAY; adom/wiki#80 will drop this duplicated title)';+ $('mqo-owner').textContent = 'wiki.adom.inc/' + d.owner + '/' + d.slug;+ // sample prompt: DYNAMIC by design (#22): metadata the page renders with a+ // copy button; if it were pixels nobody could copy it+ const sp = (Array.isArray(d.sample_prompts) && d.sample_prompts[0] && d.sample_prompts[0].prompt) || state.samplePrompt || '';+ $('mqo-prompt').style.display = sp ? 'flex' : 'none';+ $('mqo-prompt-text').textContent = sp ? '"' + sp + '"' : ''; } $('chk-mqpreview').checked = !!state.marqueePreview; @@ -1522,6 +1550,26 @@ function runLint() { } } + // 4b. full-hero imports (#22): the composed checks cannot see imported+ // pixels; say so instead of reporting all clear, and run the band scan.+ const vit2 = state.vitrine || {};+ if (vit2.full && vit2.src) {+ scanImportBand(vit2.src);+ items.push({ id: 'import-unlinted', level: 'warn', el: 'fullimport-img', msg: 'full-hero import: composed-layout checks cannot inspect imported pixels; verify the lower band and margins by eye before ratifying' });+ if (bandScan.status === 'done' && bandScan.texty) {+ items.push({ id: 'import-band-text', level: 'warn', el: 'fullimport-img', msg: 'the imported image appears to carry text or graphics in the lower dynamic band (heuristic edge scan): the homepage overlays its own info there, keep the band calm' });+ }+ }++ // 4c. prompt-looking text baked into pixels (#22): sample prompts belong in+ // page metadata (sample_prompts), rendered dynamically with a copy button.+ for (const t of texts) {+ const txt = ($(t.id).textContent || '');+ if (/["\u201c\u201d'][^"\u201c\u201d']{12,}["\u201c\u201d']/.test(txt)) {+ items.push({ id: 'prompt-' + t.id, level: 'warn', el: t.id, msg: t.name + ' contains quoted prompt-looking text; a baked prompt cannot be copied. Put it in sample_prompts (page metadata) and the page renders it dynamically with a copy button' });+ }+ }+ // 4. title wrap count: a 4-line title is almost never intended. const tEl = $('el-title'); if (vis(tEl)) {@@ -1592,6 +1640,60 @@ $('tp-flag').addEventListener('click', () => { }); $('tp-skip').addEventListener('click', () => { clearTourGlow(); $('tourpop').style.display = 'none'; send({ tour: false }); }); +$('mqo-prompt-copy').addEventListener('click', async (e) => {+ e.stopPropagation();+ const txt = $('mqo-prompt-text').textContent.replace(/^"|"$/g, '');+ try { await navigator.clipboard.writeText(txt); } catch {}+ handleToast({ text: 'sample prompt copied', kind: 'success', seq: Date.now() });+});++/* full-hero import band scan (#22.1): composed-layout lint cannot see imported+ pixels, so the class of hero most likely to violate the band skipped every+ check. A cheap edge-density scan of the bottom 24% flags text-like structure+ in the band; it is a heuristic and says so. */+let bandScan = { key: '', status: 'idle', texty: false };+function scanImportBand(src) {+ const key = src.length + ':' + src.slice(40, 72);+ if (bandScan.key === key) return;+ bandScan = { key, status: 'pending', texty: false };+ const img = new Image();+ img.onload = () => {+ try {+ const w = Math.min(800, img.width);+ const bandH = Math.max(10, Math.round(img.height * 0.24 * (w / img.width)));+ const c = document.createElement('canvas');+ c.width = w; c.height = bandH;+ const g = c.getContext('2d');+ g.drawImage(img, 0, img.height * 0.76, img.width, img.height * 0.24, 0, 0, w, bandH);+ const d = g.getImageData(0, 0, w, bandH).data;+ let edges = 0, n = 0;+ for (let y = 0; y < bandH; y++) {+ for (let x = 1; x < w; x++) {+ const i = (y * w + x) * 4, j = i - 4;+ const a = d[i] * .3 + d[i + 1] * .59 + d[i + 2] * .11;+ const b = d[j] * .3 + d[j + 1] * .59 + d[j + 2] * .11;+ if (Math.abs(a - b) > 60) edges++;+ n++;+ }+ }+ bandScan.texty = n > 0 && (edges / n) > 0.0015;+ bandScan.status = 'done';+ } catch { bandScan.status = 'failed'; }+ scheduleLint();+ };+ img.onerror = () => { bandScan.status = 'failed'; scheduleLint(); };+ img.src = src;+}++/* the stagebar (#21/#23): explicit dynamic on/off, keyboard D, legend off-stage */+$('sb-png').addEventListener('click', () => send({ marqueePreview: false }));+$('sb-mq').addEventListener('click', () => send({ marqueePreview: true }));+document.addEventListener('keydown', (e) => {+ if (e.key.toLowerCase() === 'd' && !e.target.closest('input,textarea')) {+ send({ marqueePreview: !state.marqueePreview });+ }+});+ /* hold-to-hide the dynamic overlay (#16): press the band to see the pure export, release to bring the overlay back */ (() => {