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.4.0
4 files changed
+32−18
package.json+1−1@@ -2,7 +2,7 @@ "slug": "hero-studio", "title": "Hero Studio", "type": "app",- "version": "0.3.1",+ "version": "0.4.0", "description": "A live playground for the hero image v2 definition: the lower third of every hero is a true-text overlay (type pill, trending score, stars, title, author, wiki slug) rendered by the surface, never baked into the image. Flip screenshot bleeds, brand tile themes, gradient angles, and title placement on real wiki trending data, and see the result in pixel-faithful wiki contexts: the homepage marquee, the index page card grid, and an adom-desktop style right bar. Includes a component-hero mode: real photo, raytraced render, or phong 3D fallback ladder, name plus spec-line hierarchy, and MPN placement options. Generate renders the posed hero server-side to a provenance-stamped 2000x1250 PNG via POST /generate.", "license": "MIT", "tags": [
page.json+1−1@@ -2,7 +2,7 @@ "slug": "hero-studio", "title": "Hero Studio", "type": "app",- "version": "0.3.1",+ "version": "0.4.0", "description": "A live playground for the hero image v2 definition: the lower third of every hero is a true-text overlay (type pill, trending score, stars, title, author, wiki slug) rendered by the surface, never baked into the image. Flip screenshot bleeds, brand tile themes, gradient angles, and title placement on real wiki trending data, and see the result in pixel-faithful wiki contexts: the homepage marquee, the index page card grid, and an adom-desktop style right bar. Includes a component-hero mode: real photo, raytraced render, or phong 3D fallback ladder, name plus spec-line hierarchy, and MPN placement options. Generate renders the posed hero server-side to a provenance-stamped 2000x1250 PNG via POST /generate.", "license": "MIT", "tags": [
public/index.html+29−15@@ -174,9 +174,9 @@ label.chk input { accent-color:var(--accent); width:14px; height:14px; cursor:po /* ---------- image-zone text column ---------- */ .textzone {- position:absolute; left:5.75cqw; top:8.5cqw; width:33cqw; z-index:3;+ position:absolute; left:6cqw; top:3.75cqw; width:37cqw; z-index:3; }-.hero[data-layout=mirror] .textzone { left:auto; right:5.75cqw; }+.hero[data-layout=mirror] .textzone { left:auto; right:6cqw; } .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 {@@ -185,13 +185,13 @@ label.chk input { accent-color:var(--accent); width:14px; height:14px; cursor:po mask:url('adom-logo.svg') no-repeat left center / contain; } .h-title {- font-family:'Familjen Grotesk'; font-weight:700; font-size:4.1cqw; line-height:1.02;+ font-family:'Familjen Grotesk'; font-weight:700; font-size:5.3cqw; line-height:1.02; letter-spacing:-.09cqw; color:#e6edf3; margin-bottom:1.4cqw; }-.h-sub { font-size:1.42cqw; line-height:1.48; color:#aeb8c4; margin-bottom:1.1cqw; }+.h-sub { font-size:2cqw; line-height:1.48; color:#aeb8c4; margin-bottom:1.1cqw; } .h-sub .accent { background:var(--grad); -webkit-background-clip:text; background-clip:text; color:transparent; font-weight:500; } .h-sub b { color:#e6edf3; font-weight:500; }-.h-desc { font-size:1.05cqw; line-height:1.5; color:#8b949e; max-width:30cqw; }+.h-desc { font-size:1.35cqw; line-height:1.5; color:#8b949e; max-width:30cqw; } /* ---------- the app-window mock (the "screenshot") ---------- */ .window {@@ -200,13 +200,13 @@ label.chk input { accent-color:var(--accent); width:14px; height:14px; cursor:po box-shadow:0 1.7cqw 5cqw rgba(0,0,0,.62), 0 0 8cqw rgba(0,184,177,.07); display:flex; flex-direction:column; }-.hero[data-layout=bleed-bottom] .window { right:4.5cqw; top:10cqw; width:52cqw; height:56cqw; }-.hero[data-layout=bleed-right] .window { right:-8cqw; top:9cqw; width:56cqw; height:44cqw; border-radius:.9cqw 0 0 .9cqw; }+.hero[data-layout=bleed-bottom] .window { right:4.5cqw; top:6cqw; width:56cqw; height:64cqw; }+.hero[data-layout=bleed-right] .window { right:-8cqw; top:6cqw; width:56cqw; height:44cqw; border-radius:.9cqw 0 0 .9cqw; } .hero[data-layout=bleed-top] .window { right:4.5cqw; top:-6cqw; width:52cqw; height:42cqw; border-radius:0 0 .9cqw .9cqw; } .hero[data-layout=bleed-corner-br] .window { right:-7cqw; top:14cqw; width:56cqw; height:52cqw; border-radius:.9cqw 0 0 0; } .hero[data-layout=bleed-corner-tr] .window { right:-7cqw; top:-6cqw; width:56cqw; height:44cqw; border-radius:0 0 0 .9cqw; }-.hero[data-layout=contained] .window { right:4.5cqw; top:8cqw; width:50cqw; height:33cqw; border-radius:.9cqw; }-.hero[data-layout=mirror] .window { left:4.5cqw; top:10cqw; width:52cqw; height:56cqw; }+.hero[data-layout=contained] .window { right:4.5cqw; top:6cqw; width:50cqw; height:33cqw; border-radius:.9cqw; }+.hero[data-layout=mirror] .window { left:4.5cqw; top:6cqw; width:56cqw; height:64cqw; } .titlebar { flex:0 0 2.1cqw; background:#181f28; border-bottom:1px solid rgba(255,255,255,.06); display:flex; align-items:center; padding:0 .9cqw; gap:.6cqw;@@ -283,14 +283,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:5.75cqw; }+.hero[data-clayout=photo-left] .ctextzone { left:auto; right:6cqw; } .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:5.75cqw; top:8.5cqw; width:36cqw; z-index:3; }+.ctextzone { position:absolute; left:6cqw; top:3.75cqw; 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;@@ -298,9 +298,9 @@ body[data-mode=component] .mode-app { display:none; } border-radius:999px; padding:.35cqw 1cqw; text-transform:uppercase; } .c-mpnchip { color:#aeb8c4; border-color:rgba(255,255,255,.16); display:none; }-.c-name { font-family:'Familjen Grotesk'; font-weight:700; font-size:4.2cqw; line-height:1.02; letter-spacing:-.08cqw; color:#e6edf3; margin-bottom:.9cqw; text-shadow:0 2px 12px rgba(0,0,0,.5); }+.c-name { font-family:'Familjen Grotesk'; font-weight:700; font-size:5.5cqw; line-height:1.02; letter-spacing:-.08cqw; color:#e6edf3; margin-bottom:.9cqw; text-shadow:0 2px 12px rgba(0,0,0,.5); } .c-mpn { font-family:'JetBrains Mono',monospace; font-size:1.05cqw; color:#8b949e; margin-bottom:1cqw; display:none; }-.c-spec { font-size:1.5cqw; color:#aeb8c4; line-height:1.45; max-width:30cqw; text-shadow:0 1px 8px rgba(0,0,0,.5); }+.c-spec { font-size:2cqw; color:#aeb8c4; line-height:1.45; max-width:30cqw; text-shadow:0 1px 8px rgba(0,0,0,.5); } .hero[data-mpn=eyebrow] .c-mpnchip { display:inline-block; } .hero[data-mpn=under-title] .c-mpn { display:block; } .hero[data-mpn=overlay] .l-mpn { display:block; }@@ -374,6 +374,17 @@ body[data-mode=component] .mode-app { display:none; } .deskmock { background:var(--surface); border:1px solid var(--border); border-radius:10px; padding:10px; display:flex; flex-direction:column; gap:10px; } .deskhead { font-family:'Familjen Grotesk'; font-weight:500; font-size:11px; letter-spacing:.07em; text-transform:uppercase; color:var(--text-2); } +/* other owners never get the Adom mark: dashed placeholder for their own logo */+.wordmark.noadom .wmark {+ -webkit-mask:none; mask:none; background:transparent;+ border:1.5px dashed rgba(255,255,255,.28); border-radius:.4cqw;+ display:flex; align-items:center; justify-content:center;+}+.wordmark.noadom .wmark::after {+ content:'YOUR LOGO'; font-family:'Familjen Grotesk'; font-weight:700;+ font-size:.8cqw; letter-spacing:.2em; color:rgba(255,255,255,.4);+}+ /* 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; }@@ -511,7 +522,7 @@ body[data-render] #hero { width:1600px; max-width:none; aspect-ratio:auto; heigh <button class="opt" data-v="image-only">Image only</button> <button class="opt" data-v="both">Both</button> </div>- <div class="hint">John's open question: if the big title lives in the image, the overlay title is redundant. Flip these to compare.</div>+ <div class="hint">Resolved per John: the title lives in the IMAGE; the overlay carries only dynamic data (pills, scores, url). Image only is the default; the others remain for comparison.</div> </div> <h3>Overlay content</h3>@@ -720,6 +731,9 @@ function render() { : d.type + ' · score ' + d.score + ' · ' + d.stars + ' stars · ' + d.installs30d + ' installs/30d'; $('btn-guides').classList.toggle('on', !!state.guides); + // Adom branding only on adom-owned pages; everyone else gets the placeholder+ document.querySelectorAll('#hero .wordmark').forEach(w => w.classList.toggle('noadom', d.owner !== 'adom'));+ // component image zone if (mode === 'component' && comps.length) { const c = compById(state.comp.id);@@ -944,7 +958,7 @@ $('btn-generate').addEventListener('click', () => { }); $('btn-reset').addEventListener('click', () => {- send({ mode:'app', layout:'bleed-bottom', theme:'midnight', titleMode:'overlay-only', gradAngle:155,+ send({ mode:'app', layout:'bleed-bottom', theme:'midnight', titleMode:'image-only', gradAngle:155, comp:{ id:'sm02b-srss-tb', imgSrc:'photo', layout:'photo-right', mpn:'under-title' }, show:{ logo:true, title:true, subtitle:true, description:false }, overlay:{ pill:true, scores:true, author:true, slug:true }, guides:true });
server.js+1−1@@ -16,7 +16,7 @@ let state = { layout: 'bleed-bottom', // bleed-bottom | bleed-right | bleed-top | bleed-corner-br | bleed-corner-tr | contained | mirror theme: 'midnight', // midnight | pcb | onion | pill | gradient show: { logo: true, title: true, subtitle: true, description: false },- titleMode: 'overlay-only', // overlay-only | image-only | both+ 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: true, author: true, slug: true }, gradAngle: 155, guides: true,