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.2.0
10 files changed
+276−35
README.md+23@@ -46,6 +46,29 @@ homepage marquee already proves the pattern.  +## Component heroes++A separate mode for component pages, switched at the top of the panel ("What are you+making"). A component hero leads with the physical part, not an app window:++- **Image ladder**: a real-world photo where the screenshot area would be; if none exists,+ a raytraced render; last resort, the plain phong 3D viewport shot. The ladder is the+ quality bar: the wiki's own `sm02b-srss-tb` hero is currently the bottom tier.+- **Name where the title goes**, and a plain-language spec line as the subtitle+ ("100k SMD 1% resistor").+- **MPN placement**: in the family chip row, under the name, or in the lower-third+ overlay next to the slug.+- **Family chip** ("JST · SH SERIES · 1.0 MM") in the eyebrow slot, following the+ adom-usb family-marker pattern.++++The bundled component imagery is sample material for the lab: the photo tier is cropped+from the john/adom-usb-sniffer-dap hero (a Wikimedia Commons photograph under its+respective CC license), the phong tier is cropped from the live adom/sm02b-srss-tb hero,+and the raytrace tier is an illustrative render. Swap in your own part's imagery when+designing a real hero.+ ## Run it ```
data/components.jsonadded+46@@ -0,0 +1,46 @@+[+ {+ "id": "sm02b-srss-tb",+ "name": "SM02B-SRSS-TB",+ "family": "JST · SH SERIES · 1.0 MM",+ "spec": "2-pin SH 1.0mm side-entry SMT header",+ "mpn": "SM02B-SRSS-TB(LF)(SN)",+ "mfr": "JST",+ "owner": "adom",+ "slug": "sm02b-srss-tb",+ "type": "component",+ "score": 317,+ "stars": 0,+ "author": "John Lauer"+ },+ {+ "id": "rc0402-100k",+ "name": "100k 0402 Resistor",+ "family": "YAGEO · RC SERIES · 0402",+ "spec": "100k SMD 1% thick film resistor",+ "mpn": "RC0402FR-07100KL",+ "mfr": "Yageo",+ "owner": "adom",+ "slug": "rc0402-100k",+ "type": "component",+ "score": 112,+ "stars": 0,+ "author": "Adom Components",+ "sample": true+ },+ {+ "id": "stm32f103c8t6",+ "name": "STM32F103C8T6",+ "family": "ST · STM32F1 · LQFP-48",+ "spec": "Cortex-M3 MCU, 64KB flash, 72MHz",+ "mpn": "STM32F103C8T6",+ "mfr": "STMicroelectronics",+ "owner": "adom",+ "slug": "stm32f103c8t6",+ "type": "component",+ "score": 204,+ "stars": 1,+ "author": "Adom Components",+ "sample": true+ }+]
docs/component-mode.pngadded⋯ 1 unchanged line ⋯
package.json+12−6@@ -2,8 +2,8 @@ "slug": "hero-studio", "title": "Hero Studio", "type": "app",- "version": "0.1.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.",+ "version": "0.2.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.", "license": "MIT", "tags": [ "hero",@@ -13,7 +13,8 @@ "branding", "marquee", "studio",- "brand"+ "brand",+ "components" ], "dependencies": {}, "discovery_triggers": [@@ -24,7 +25,11 @@ "lower third hero overlay", "hero billboard lab", "try hero layouts and themes",- "what would my hero look like on the wiki"+ "what would my hero look like on the wiki",+ "component hero",+ "hero for a component",+ "part photo hero",+ "mpn on the hero" ], "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": [@@ -65,6 +70,7 @@ "docs/hero.png", "docs/contexts.png", "docs/themes.png",- "docs/index-cards.png"+ "docs/index-cards.png",+ "docs/component-mode.png" ]-}+}
page.json+11−5@@ -2,8 +2,8 @@ "slug": "hero-studio", "title": "Hero Studio", "type": "app",- "version": "0.1.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.",+ "version": "0.2.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.", "license": "MIT", "tags": [ "hero",@@ -13,7 +13,8 @@ "branding", "marquee", "studio",- "brand"+ "brand",+ "components" ], "dependencies": {}, "discovery_triggers": [@@ -24,7 +25,11 @@ "lower third hero overlay", "hero billboard lab", "try hero layouts and themes",- "what would my hero look like on the wiki"+ "what would my hero look like on the wiki",+ "component hero",+ "hero for a component",+ "part photo hero",+ "mpn on the hero" ], "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": [@@ -66,7 +71,8 @@ "docs/hero.png", "docs/contexts.png", "docs/themes.png",- "docs/index-cards.png"+ "docs/index-cards.png",+ "docs/component-mode.png" ], "org": "adom", "author": {
public/comp/phong.pngadded⋯ 1 unchanged line ⋯
public/comp/photo.pngadded⋯ 1 unchanged line ⋯
public/comp/raytrace.pngadded⋯ 1 unchanged line ⋯
public/index.html+177−24@@ -253,9 +253,52 @@ label.chk input { accent-color:var(--accent); width:14px; height:14px; cursor:po .l-author { font-size:1.05cqw; color:#8b949e; } .l-author a { color:#aeb8c4; text-decoration:none; } .l-author a:hover { color:#00e6dc; }-.l-slug { flex:0 0 auto; font-family:'JetBrains Mono',monospace; font-size:.95cqw; color:#00b8b1; text-decoration:none; padding-bottom:.15cqw; }+.l-right { flex:0 0 auto; display:flex; flex-direction:column; align-items:flex-end; gap:.4cqw; padding-bottom:.15cqw; }+.l-mpn { font-family:'JetBrains Mono',monospace; font-size:.95cqw; color:#aeb8c4; display:none; }+.l-slug { font-family:'JetBrains Mono',monospace; font-size:.95cqw; color:#00b8b1; text-decoration:none; } .l-slug:hover { color:#00e6dc; } +/* ---------- component mode ---------- */+.hero[data-mode=component] .textzone, .hero[data-mode=component] .window { display:none; }+.hero[data-mode=app] .comp-side, .hero[data-mode=app] .ctextzone { display:none; }+body[data-mode=app] .mode-component { display:none; }+body[data-mode=component] .mode-app { display:none; }+.comp-side { position:absolute; top:0; bottom:0; right:0; width:56%; z-index:1; }+.comp-img {+ position:absolute; inset:0; background-size:cover; background-position:center;+ -webkit-mask-image:linear-gradient(90deg, transparent 0, #000 26%);+ mask-image:linear-gradient(90deg, transparent 0, #000 26%);+}+.hero[data-imgsrc=photo] .comp-img { background-image:url('comp/photo.png'); }+.hero[data-imgsrc=raytrace] .comp-img { background-image:url('comp/raytrace.png'); }+.hero[data-imgsrc=phong] .comp-img { background-image:url('comp/phong.png'); background-size:contain; background-repeat:no-repeat; background-position:center 40%; }+.hero[data-clayout=photo-left] .comp-side { left:0; right:auto; }+.hero[data-clayout=photo-left] .comp-img {+ -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-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; }+.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;+ color:#00e6dc; background:rgba(5,9,14,.55); border:1px solid rgba(0,230,220,.3);+ 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-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); }+.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; }+ /* guides */ .guide { position:absolute; left:0; right:0; top:66.6%; z-index:6; border-top:1.5px dashed rgba(0,230,220,.55); pointer-events:none; display:none; } .guide span {@@ -366,6 +409,40 @@ label.chk input { accent-color:var(--accent); width:14px; height:14px; cursor:po <main> <div id="panel">+ <h3>What are you making</h3>+ <div class="optgrid" id="grp-mode">+ <button class="opt" data-v="app">App / skill</button>+ <button class="opt" data-v="component">Component</button>+ </div>++ <div class="mode-component">+ <h3>Component</h3>+ <select id="sel-comp" data-tip="Sample parts; sm02b-srss-tb is a real wiki component page"></select>++ <h3>Image source (fallback ladder)</h3>+ <div class="optgrid" id="grp-imgsrc">+ <button class="opt" data-v="photo">Real photo</button>+ <button class="opt" data-v="raytrace">Raytraced render</button>+ <button class="opt" data-v="phong">Phong 3D</button>+ </div>+ <div class="hint">The ladder: real-world photo first, else a raytraced render, else the plain 3D viewport shot (last resort).</div>++ <h3>Photo placement</h3>+ <div class="optgrid" id="grp-clayout">+ <button class="opt" data-v="photo-right">Full-bleed right</button>+ <button class="opt" data-v="photo-left">Full-bleed left</button>+ <button class="opt" data-v="photo-full">Full background</button>+ </div>++ <h3>MPN placement</h3>+ <div class="optgrid" id="grp-mpn">+ <button class="opt" data-v="eyebrow">Family chip row</button>+ <button class="opt" data-v="under-title">Under the name</button>+ <button class="opt" data-v="overlay">In the overlay</button>+ </div>+ </div>++ <div class="mode-app"> <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> @@ -380,6 +457,7 @@ label.chk input { accent-color:var(--accent); width:14px; height:14px; cursor:po <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>+ </div> <h3>Background theme</h3> <div class="swatches" id="grp-theme">@@ -395,6 +473,7 @@ label.chk input { accent-color:var(--accent); width:14px; height:14px; cursor:po <input type="range" id="rng-angle" min="0" max="360" step="5" value="155" data-tip="Rotate the base gradient so each hero feels unique while staying on-brand"> <div class="hint">Angle: <span id="angle-val">155</span> deg. Brand default is 155.</div> + <div class="mode-app"> <h3>Image-zone text</h3> <label class="chk"><input type="checkbox" id="chk-logo"> Adom wordmark</label> <label class="chk"><input type="checkbox" id="chk-title"> Title (big headline)</label>@@ -408,6 +487,7 @@ label.chk input { accent-color:var(--accent); width:14px; height:14px; cursor:po <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> <h3>Overlay content</h3> <label class="chk"><input type="checkbox" id="chk-pill"> Type pill</label>@@ -430,6 +510,18 @@ label.chk input { accent-color:var(--accent); width:14px; height:14px; cursor:po <div class="h-desc" id="el-desc"></div> </div> + <div class="comp-side"><div class="comp-img"></div></div>+ <div class="ctextzone">+ <div class="wordmark"><svg viewBox="0 0 24 24"><path d="M12 3 3 21h18z"/></svg><span>ADOM</span></div>+ <div class="c-chips">+ <span class="c-family" id="c-family"></span>+ <span class="c-family c-mpnchip" id="c-mpn-chip"></span>+ </div>+ <div class="c-name" id="c-name"></div>+ <div class="c-mpn" id="c-mpn-under"></div>+ <div class="c-spec" id="c-spec"></div>+ </div>+ <div class="window"> <div class="titlebar"> <span class="t" id="win-label"></span>@@ -482,7 +574,10 @@ label.chk input { accent-color:var(--accent); width:14px; height:14px; cursor:po <a class="l-title" id="l-title" target="_blank" href="#"></a> <div class="l-author" id="l-author"></div> </div>- <a class="l-slug" id="l-slug" target="_blank" href="#"></a>+ <div class="l-right">+ <span class="l-mpn" id="l-mpn"></span>+ <a class="l-slug" id="l-slug" target="_blank" href="#"></a>+ </div> </div> </div> @@ -523,12 +618,24 @@ label.chk input { accent-color:var(--accent); width:14px; height:14px; cursor:po })(); let pages = [];+let comps = []; let state = null; const $ = (id) => document.getElementById(id); const send = (patch) => fetch('state', { method:'POST', headers:{'Content-Type':'application/json'}, body: JSON.stringify(patch) }).catch(()=>{}); function pageByKey(key) { return pages.find(p => p.owner + '/' + p.slug === key) || pages[0]; }+function compById(id) { return comps.find(c => c.id === id) || comps[0]; }+/* the object the overlay, header, and context cards render: page or component */+function displayEntry() {+ if (state.mode === 'component' && comps.length) {+ const c = compById(state.comp.id);+ return { owner: c.owner, slug: c.slug, type: 'component', title: c.name, brief: c.spec,+ author: c.author, score: c.score, stars: c.stars, installs30d: null,+ tags: [c.mfr.toLowerCase()], updated_at: null, mpn: c.mpn };+ }+ return pageByKey(state.page);+} function firstSentence(s, max) { let t = (s || '').split(/(?<=\.)\s/)[0] || s || '';@@ -545,19 +652,38 @@ function accentize(text) { function render() { if (!state || !pages.length) return; const p = pageByKey(state.page);+ const d = displayEntry();+ const mode = state.mode || 'app'; const hero = $('hero');+ document.body.dataset.mode = mode;+ hero.dataset.mode = mode; hero.dataset.theme = state.theme; hero.dataset.layout = state.layout;+ hero.dataset.clayout = state.comp.layout;+ hero.dataset.imgsrc = state.comp.imgSrc;+ hero.dataset.mpn = state.comp.mpn; hero.classList.toggle('guides', !!state.guides); hero.style.setProperty('--ga', (state.gradAngle || 155) + 'deg'); $('rng-angle').value = state.gradAngle || 155; $('angle-val').textContent = state.gradAngle || 155; // header subject- $('subj-title').textContent = p.title;- $('subj-meta').textContent = p.type + ' · score ' + p.score + ' · ' + p.stars + ' stars · ' + p.installs30d + ' installs/30d';+ $('subj-title').textContent = d.title;+ $('subj-meta').textContent = mode === 'component'+ ? d.type + ' · score ' + d.score + ' · MPN ' + d.mpn+ : d.type + ' · score ' + d.score + ' · ' + d.stars + ' stars · ' + d.installs30d + ' installs/30d'; $('btn-guides').classList.toggle('on', !!state.guides); + // component image zone+ if (mode === 'component' && comps.length) {+ const c = compById(state.comp.id);+ $('c-family').textContent = c.family;+ $('c-mpn-chip').textContent = c.mpn;+ $('c-name').textContent = c.name;+ $('c-mpn-under').textContent = c.mpn;+ $('c-spec').textContent = c.spec;+ }+ // image zone const showTitleInImage = state.show.title && state.titleMode !== 'overlay-only'; $('el-logo').style.display = state.show.logo ? 'flex' : 'none';@@ -570,27 +696,33 @@ function render() { $('win-label').textContent = p.title + ' · Adom webview'; // lower third overlay- const showTitleInOverlay = state.titleMode !== 'image-only';+ const showTitleInOverlay = mode === 'component' || state.titleMode !== 'image-only'; $('l-pill').style.display = state.overlay.pill ? '' : 'none';- $('l-pill').textContent = p.type.toUpperCase();- $('l-pill').className = 'pill ' + p.type;+ $('l-pill').textContent = d.type.toUpperCase();+ $('l-pill').className = 'pill ' + d.type; $('l-trend').style.display = state.overlay.scores ? '' : 'none';- $('l-trend').querySelector('b').textContent = p.score.toLocaleString('en-US');+ $('l-trend').querySelector('b').textContent = d.score.toLocaleString('en-US'); $('l-stars').style.display = state.overlay.scores ? '' : 'none';- $('l-stars').querySelector('b').textContent = p.stars;- $('l-installs').style.display = state.overlay.scores ? '' : 'none';- $('l-installs').querySelector('b').textContent = p.installs30d;- const url = 'https://wiki.adom.inc/' + p.owner + '/' + p.slug;+ $('l-stars').querySelector('b').textContent = d.stars;+ $('l-installs').style.display = (state.overlay.scores && d.installs30d != null) ? '' : 'none';+ $('l-installs').querySelector('b').textContent = d.installs30d;+ const url = 'https://wiki.adom.inc/' + d.owner + '/' + d.slug; $('l-title').style.display = showTitleInOverlay ? 'block' : 'none';- $('l-title').textContent = p.title;+ $('l-title').textContent = d.title; $('l-title').href = url; $('l-author').style.display = state.overlay.author ? '' : 'none';- $('l-author').innerHTML = 'by <a href="' + url + '" target="_blank">' + p.author + '</a>';+ $('l-author').innerHTML = 'by <a href="' + url + '" target="_blank">' + d.author + '</a>';+ $('l-mpn').textContent = d.mpn || ''; $('l-slug').style.display = state.overlay.slug ? '' : 'none';- $('l-slug').textContent = 'wiki.adom.inc/' + p.owner + '/' + p.slug;+ $('l-slug').textContent = 'wiki.adom.inc/' + d.owner + '/' + d.slug; $('l-slug').href = url; // controls reflect state+ document.querySelectorAll('#grp-mode .opt').forEach(b => b.classList.toggle('on', b.dataset.v === mode));+ if (comps.length) $('sel-comp').value = compById(state.comp.id).id;+ document.querySelectorAll('#grp-imgsrc .opt').forEach(b => b.classList.toggle('on', b.dataset.v === state.comp.imgSrc));+ document.querySelectorAll('#grp-clayout .opt').forEach(b => b.classList.toggle('on', b.dataset.v === state.comp.layout));+ document.querySelectorAll('#grp-mpn .opt').forEach(b => b.classList.toggle('on', b.dataset.v === state.comp.mpn)); $('sel-page').value = state.page; document.querySelectorAll('#grp-layout .opt').forEach(b => b.classList.toggle('on', b.dataset.v === state.layout)); document.querySelectorAll('#grp-theme .swatch').forEach(b => b.classList.toggle('on', b.dataset.v === state.theme));@@ -662,8 +794,8 @@ function cloneHero(imageOnly) { } function syncPreviews() {- const p = pageByKey(state.page);- const nb = pages.filter(x => x !== p).slice(0, 3);+ const p = displayEntry();+ const nb = pages.filter(x => x.owner + '/' + x.slug !== p.owner + '/' + p.slug).slice(0, 3); // marquee: live tile among real neighbors, content doubled for the -50% loop const seq = marqueeTile(p, true) + nb.map(x => marqueeTile(x)).join(''); $('mq-track').innerHTML = seq + seq;@@ -689,6 +821,11 @@ function toast(text, kind) { } /* wire controls: server owns the truth, SSE echoes back */+document.querySelectorAll('#grp-mode .opt').forEach(b => b.addEventListener('click', () => send({ mode: b.dataset.v })));+$('sel-comp').addEventListener('change', e => send({ comp: { id: e.target.value } }));+document.querySelectorAll('#grp-imgsrc .opt').forEach(b => b.addEventListener('click', () => send({ comp: { imgSrc: b.dataset.v } })));+document.querySelectorAll('#grp-clayout .opt').forEach(b => b.addEventListener('click', () => send({ comp: { layout: b.dataset.v } })));+document.querySelectorAll('#grp-mpn .opt').forEach(b => b.addEventListener('click', () => send({ comp: { mpn: b.dataset.v } }))); $('sel-page').addEventListener('change', e => { send({ page: e.target.value }); }); document.querySelectorAll('#grp-layout .opt').forEach(b => b.addEventListener('click', () => send({ layout: b.dataset.v }))); document.querySelectorAll('#grp-theme .swatch').forEach(b => b.addEventListener('click', () => send({ theme: b.dataset.v })));@@ -710,16 +847,28 @@ $('rng-angle').addEventListener('input', e => { angleTimer = setTimeout(() => send({ gradAngle: +e.target.value }), 120); }); $('btn-rand').addEventListener('click', () => {- const layouts = ['bleed-bottom','bleed-right','bleed-top','bleed-corner-br','bleed-corner-tr','contained','mirror']; const themes = ['midnight','pcb','onion','pill','grid','gradient']; const angles = [65, 100, 155, 210, 245, 290, 335];- const p = pages[Math.floor(Math.random() * pages.length)];- send({ page: p.owner + '/' + p.slug, layout: layouts[Math.floor(Math.random()*layouts.length)],- theme: themes[Math.floor(Math.random()*themes.length)], gradAngle: angles[Math.floor(Math.random()*angles.length)] });- toast('Randomized: ' + p.title, 'info');+ const theme = themes[Math.floor(Math.random()*themes.length)];+ const gradAngle = angles[Math.floor(Math.random()*angles.length)];+ if (state.mode === 'component') {+ const clayouts = ['photo-right','photo-left','photo-full'];+ const srcs = ['photo','raytrace','phong'];+ const mpns = ['eyebrow','under-title','overlay'];+ const c = comps[Math.floor(Math.random() * comps.length)];+ send({ theme, gradAngle, comp: { id: c.id, layout: clayouts[Math.floor(Math.random()*clayouts.length)],+ imgSrc: srcs[Math.floor(Math.random()*srcs.length)], mpn: mpns[Math.floor(Math.random()*mpns.length)] } });+ toast('Randomized: ' + c.name, 'info');+ } else {+ const layouts = ['bleed-bottom','bleed-right','bleed-top','bleed-corner-br','bleed-corner-tr','contained','mirror'];+ const p = pages[Math.floor(Math.random() * pages.length)];+ send({ page: p.owner + '/' + p.slug, layout: layouts[Math.floor(Math.random()*layouts.length)], theme, gradAngle });+ toast('Randomized: ' + p.title, 'info');+ } }); $('btn-reset').addEventListener('click', () => {- send({ layout:'bleed-bottom', theme:'midnight', titleMode:'overlay-only', gradAngle:155,+ send({ mode:'app', layout:'bleed-bottom', theme:'midnight', titleMode:'overlay-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 }); toast('Reset to the proposed default: overlay title, image keeps value-prop', 'success');@@ -753,14 +902,18 @@ $('btn-reset').addEventListener('click', () => { /* boot: load real pages, subscribe to the live channel */ Promise.all([ fetch('api/pages').then(r => r.json()),+ fetch('api/components').then(r => r.json()), fetch('state').then(r => r.json()),-]).then(([pg, st]) => {+]).then(([pg, cp, st]) => { pages = pg;+ comps = cp; state = st; seenToastSeq = (st._toast && st._toast.seq) || 0; // don't replay a stale toast on load const sel = $('sel-page'); sel.innerHTML = pages.map(p => '<option value="' + p.owner + '/' + p.slug + '">' + p.title + ' (' + p.type + ')</option>').join('');+ $('sel-comp').innerHTML = comps.map(c =>+ '<option value="' + c.id + '">' + c.name + (c.sample ? ' (sample)' : '') + '</option>').join(''); render(); console.log('hero-studio ready: ' + pages.length + ' live pages'); if (location.hash) {
server.js+7@@ -6,9 +6,12 @@ const path = require('path'); const PORT = 8846; const ROOT = path.join(__dirname, 'public'); const PAGES = JSON.parse(fs.readFileSync(path.join(__dirname, 'data', 'pages.json'), 'utf8'));+const COMPONENTS = JSON.parse(fs.readFileSync(path.join(__dirname, 'data', 'components.json'), 'utf8')); let state = {+ mode: 'app', // app | component page: 'adom/adom-tts',+ comp: { id: 'sm02b-srss-tb', imgSrc: 'photo', layout: 'photo-right', mpn: 'under-title' }, 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 },@@ -113,6 +116,10 @@ const server = http.createServer((req, res) => { res.writeHead(200, { 'Content-Type': 'application/json' }); return res.end(JSON.stringify(PAGES)); }+ if (p === '/api/components') {+ res.writeHead(200, { 'Content-Type': 'application/json' });+ return res.end(JSON.stringify(COMPONENTS));+ } // static let file = p === '/' ? '/index.html' : p;