Wiki Hero Image Skill
Public Made by Adomby adom
Design and render a billboard-grade hero image for any Adom wiki page — the 16:10 dark-mode image (set via page.json) that the wiki page, the landing grid, the Hydrogen Desktop installer, the homepage, and the screensaver all reuse. App name + value prop + one gorgeous shot of the real UI in a floating Windows-style webview window. Readable in one second.
name: adom-wiki-hero-image description: >- Design and render a billboard-grade HERO IMAGE for an Adom Wiki app/skill/bridge page (wiki.adom.inc) — the 16:10 image, set via page.json, that the wiki page header, the landing grid card, the Hydrogen Desktop installer, the wiki homepage promos, and the adom-screensaver all show as a promotional billboard. Produces a dark-mode, on-brand hero: the app NAME as the headline, a one-line value prop, an on-brand teal→blue→purple accent, and ONE gorgeous shot of the app's REAL UI shown as a floating drop-shadowed "Adom webview" window bleeding off the bottom. Use whenever the user wants a hero image, hero billboard, wiki hero, app hero, billboard image, wiki-page art, app marketing image, screensaver/homepage art, "the image at the top of the page," hero for , redo my hero, or hero.png — even if they don't say the exact word "hero." Also use it when a hero looks busy/overwhelming and needs to be simplified.
adom-wiki-hero — make a billboard-grade hero image
A hero image is not a screenshot and not a brochure. It is a billboard. Picture it at highway speed: the viewer gets about one second. So it must read instantly — **the app's name
- what it does + one gorgeous glimpse of the app itself.** That's the whole job. Every rule below serves that one-second read.
Why this skill exists
Without guidance, an AI asked for a hero just shoves a boring screenshot into the frame. That fails the job. This skill exists so that any Adom employee — or a third party — building a hero for their wiki app or skill ends up with an image that is both beautiful AND unmistakably theirs. The mental shift it forces: "I have to convey this app's value proposition in one clean image," not *"paste a screenshot." Two outcomes, always together:
- Beautiful & on-brand — the rules and brand tokens below guarantee a polished, consistent family.
- Unique to the app — the variants (layout × theme, §9), the headline, the brand mark/chip, and the real screenshot make each hero distinct so the wiki doesn't look like one repeated template.
Where these heroes run (design for ALL of them)
The hero is reused, unchanged, as a promotional billboard across many surfaces — so it must stand alone and read in one second on each:
- the wiki page header and the landing grid card;
- the wiki homepage daily trending-apps slideshow (a rotating showcase);
- the adom-screensaver — it auto-discovers every public page's
hero.pngand cross-fades them, so your hero literally runs as an ad on idle screens; - the Hydrogen Desktop installer setup steps (rotating billboards of recent apps);
- and increasingly social/video — YouTube podcast cards, TikTok/Instagram/Reels, X.com posts. Treat the hero as ad creative: bold, legible at a glance, self-explanatory, no surrounding context.
This skill is the consolidated recipe behind the adom-parts-search / adom-mouser / adom-digikey / adom-jlcpcb / adom-desktop heroes. Every rule was learned by getting it wrong; follow it closely.
Step 0 — ASK the author for a look & feel FIRST (AskUserQuestion)
Before rendering anything, call AskUserQuestion to let the author choose the look & feel along
two axes — these compose, so the same skill yields a distinct hero per app. Ask both in ONE
AskUserQuestion call (two questions). If the user already named a layout/theme, skip that question.
Tokens + presets live in §9 and build-variants.js.
Q1 header "Layout" question "How should the app window sit?"
• Bleed bottom (Recommended) — window high-right, runs off the BOTTOM. The family default.
• Bleed right — window runs off the RIGHT edge (tall panel).
• Contained — window framed fully inside, margins top/right/bottom.
• Mirror — name/value-prop on the RIGHT, window on the LEFT (bleeds bottom).
Q2 header "Theme" question "Which background + texture?"
• Midnight (Recommended) — near-black gradient + faint dot-grid. The family default.
• PCB Traces — deep brand teal + the Kickstand circuit-field tile (ethereal/electronic).
• Onion / Pill (solid) — navy field + the signature concentric-arc onion, or the pill-field tile.
• Gradient (wow) — bold brand teal→blue→purple cover-slide gradient. For flagship/launch.
Then render: node build-variants.js <layout> <theme> (layouts: bottom|bleedRight|contained|mirror;
themes: midnight|pcb|onion|pill|gradient) → render.js → downscale. Every combination still obeys
the non-negotiable rules below (dark, one screenshot, ≥~90px gutter, real UI, brand type, Windows-style
chrome). A variant changes background/texture/window-placement only — never the rules.
Step 0.5 — 🛑 SHOW THE CONTENT TABLE AND GET SIGN-OFF BEFORE YOU RENDER
Rendering is a heavy, token-expensive operation, and the single most-repeated failure is shipping a render with the wrong headline (using the slug/CLI name instead of the wiki page title, a tagline where the name belongs, a two-sentence subhead, a mis-quoted value prop). Re-rendering to fix copy you could have confirmed in one cheap message wastes a lot of tokens — so confirm FIRST.
After Step 0 (look & feel) and BEFORE you render anything, post a table of every hero element for the author to verify, and WAIT for their confirmation or correction. One row per element, three columns — and the TEXT column is the point (it's what keeps getting wrong):
| Item | Exact text / content | Font size / image size |
|---|---|---|
| ADOM logo | (the white ADOM wordmark) | 46px, top-left |
| Top-right chip | <EXACT CHIP TEXT> |
15px, ≤28 chars |
| Title | <EXACT HEADLINE = the WIKI PAGE TITLE> — accent word: <word> |
Familjen Grotesk 700, 64px |
| Sub-title | <EXACT one-sentence value prop, with the bold words marked> |
Satoshi 400, 23px |
| CTA pill | "<EXACT AI prompt>" |
19px |
| Wiki URL | wiki.adom.inc/adom/<slug> |
19px, lower-left |
| Product window | <which app screen + dataset> shot at the window aspect |
width ~800px (~50%) |
Call it out explicitly: "Title = the wiki PAGE TITLE (e.g. adom-lbr's page is titled Adom
Library) — confirm before I render." Do NOT render-then-ask; that burns tokens on a throwaway
image. Only after the author signs off do you proceed to render (and then the §2.5 pass before push).
0. Output contract (what you produce)
- One image, 16:10, rendered at 1600×1000 @ deviceScaleFactor 2 (= a crisp 3200×2000),
then downscaled to a 2000×1250 PNG (~300–950 KB). Store it at
screenshots/hero.png(or a versioned name on replacement — see §7). - Registered in
page.json(NOT inlined in the README):"hero": { "type": "image", "path": "screenshots/hero.png" } - Pushed to the page's git repo and verified live (see §7).
1. NON-NEGOTIABLE RULES (the hard-won ones — do not violate)
- ONE app, ONE screenshot. No collages. A grid trying to show six features at once reads as noise and fails the one-second test. The single best whole-window shot, presented beautifully, beats any montage. If you catch yourself assembling a grid, stop.
- Dark mode. Minimize bright pixels. Background is the Adom dark gradient. NEVER put a logo or product shot inside a big white box/plate — too many white pixels for a billboard. Logos go on transparent backgrounds, recolored for dark (see §3, §5).
- No background watermark logo. Don't drop a giant faint Adom mark behind the content — the billboard frames crop it and it looks broken. The only background texture allowed is a subtle Kickstand pattern — dot-grid, concentric arcs, or square grid (see §9), kept faint and masked.
- Win the one-second read AND make the app unmistakable. Two proven headline patterns — pick
per app, never leave the viewer guessing what this is:
- Name-led — the full app NAME is the headline ("Adom Desktop KiCad Bridge", "Adom Parts
Search"), accent on the distinctive word. Best when the name itself sells it.
The "name" is the app's WIKI PAGE TITLE (its human display name) — NOT the slug, the CLI
binary, or the README's
# code-styleheading. e.g. slugadom-lbr→ headline "Adom Library" (its page title); slugadom-parts-search→ "Adom Parts Search". Match the page title verbatim so the hero and the page header agree; the distinctive word gets the accent. - Tagline-led — a punchy value phrase is the headline ("One search. Every distributor."), accent on the key word, with the app identified by a big brand mark / category chip and the URL. Best when paired with a recognizable mark (e.g. Mouser). Either way: short, scannable, gradient accent on the key word. A pure tagline with no mark/chip to anchor it is the failure mode — the viewer shouldn't have to work out which app it is.
- Name-led — the full app NAME is the headline ("Adom Desktop KiCad Bridge", "Adom Parts
Search"), accent on the distinctive word. Best when the name itself sells it.
The "name" is the app's WIKI PAGE TITLE (its human display name) — NOT the slug, the CLI
binary, or the README's
- Put the product UI in a floating "webview" window, not full-bleed-width. A pseudo-window (rounded top, drop shadow, Windows-style title bar — min/max/close at the top-RIGHT, never macOS traffic-light dots — faint teal glow), ~50–58% of the width, high-right, bleeding off only the bottom (and slightly off the right for wide landscape shots), reads as "you're using this in an Adom webview." Do NOT stretch the screenshot to the full image width, and do NOT float it in the lower third — place it high (top ≈ 30% down).
- Value-prop line under the name — one sentence on what it does, a few keywords bolded (white) for scanning.
- Margins are sacred. Nothing overlaps anything. Humans need margins. EVERY element — wordmark, top-right chip/mark, headline, value-prop, prompt pill, capability pills, URL, slug, and the floating product shot — must hold a clear ≥80px margin from every canvas edge and ≥24px of air from every neighbouring element. The top-right chip is the most common offender: it must sit ≥80px from the top edge and ≥80px from the right edge, and must NOT collide with the product shot below/beside it. Keep chip/label text SHORT (≤ ~28 chars) so it never runs toward the corner — abbreviate (e.g. "STMICRO", "TI", "NORDIC"), put the detail in the headline, not the chip. This is a HARD, repeatedly-failed rule: after every render you MUST do the margin pass in §7.5 and re-render until it passes. Crowding reads as amateur and gets the hero rejected.
- Wiki URL in the lower-LEFT corner, with margin — never over the window.
- Use REAL app UI with good, specific example data (real MPNs / real-looking domain
content). Render the app's own
ui.htmlpopulated with a realistic dataset — not a fake mockup, not an empty state. Generic "Lorem"/blank states look dead. Capture the laptop-browser view with every toolbar / studio / HUD / layers panel OPEN — the busy, sophisticated state, captured at ~1440px wide (deviceScaleFactor 2), with content loaded and the control panels deployed before the shot. A bare canvas reads as a toy; a full-toolbar shot says "capable." (John, 2026-06-21: he explicitly wanted the full-toolbar laptop view so viewers "understand how sophisticated the apps have gotten" — proven on the EDA apps.)
9b. Shoot the screenshot at the hero slot's TRUE SIZE (or at least its exact
aspect ratio) — NEVER crop-to-fit. Every layout places the shot in a fixed
slot with object-fit: cover (e.g. the mirror layout's window is 806x760);
a screenshot taken at some other aspect gets silently cropped — headers cut
mid-word, panels sliced — and ships looking broken. The right way: read the
slot's width/height out of the variant HTML, resize the browser/viewport to
exactly that CSS size (2x deviceScaleFactor for crispness), and let the
app's fluid layout reflow NATURALLY at that size before capturing. The UI
laying itself out at the display aspect always beats cropping a shot taken
at the wrong one. Then trim any blank/white overflow rows from the capture
before compositing. (User rule, learned twice: adom-tts and hands-free both
shipped cropped heroes before this.)
- On-brand type only. Familjen Grotesk (headlines, 700), Satoshi (body), JetBrains Mono (terminal/code, optional). Never Inter/Arial/system.
- The hero is self-contained (name + mark/chip + value prop + product). The installer, homepage, and screensaver frame it directly, so don't rely on surrounding text.
- Do NOT also embed the hero in the README. The page already shows it from
hero.path; repeating it is redundant. READMEs get 3 other distinct inline screenshots instead. - Every image MUST be a real, relevant, high-quality photo of the actual subject. NEVER ship a placeholder. If the hero features a device/chip/product, the image must clearly BE that device — not a wrong variant, not the underside of a board, not a meme/stock image that merely shares a keyword, not a low-res (<800px) crop, and never a generated placeholder / striped "no-photo" panel. A striped or blank panel in a shipped hero is a FAILURE, not a fallback. If you cannot find a genuinely good, on-subject image, that is a BLOCKER to solve (search harder, try other terms/sources, or ask the user) — do not paper over it. See the image-QA pass in §7.5.
- Keep text and image in SEPARATE zones — but the specific layout is YOUR choice and SHOULD vary per app. The only universal here is the principle: text lives in its own clean column, the image in its own zone, they never overlap, and you never wrap copy around the image or drop a URL/CTA onto its busy area. (That separation is what kills the margin-collision churn.) This principle is satisfied by every layout in §9 — so pick a different layout × theme per app so the wiki never looks templated. That variety is the whole point — do NOT collapse every hero into one layout. Forms that all satisfy the principle, pick what flatters THIS app's shot: a floating "webview window" (app UIs), a framed contained shot, a bottom or right bleed, a mirror (text right), or a full-bleed product photo dominating one side (great for device/ chip/hardware photos). The full-bleed-photo form is one OPTION among these, not the default.
- The call-to-action is an AI prompt — NEVER a command line. Show a paste-into-Claude prompt
(the "just ask Claude" pill), e.g.
"flash my RP2040 as a USB keyboard". Do NOT putadompkg install …,pip install …,curl … | sh, or any shell command on a hero. People copy/paste AI prompts now; a CLI string on a billboard reads as dated. (The wiki page's Install section still shows the install command — the hero sells the outcome via an AI prompt.)
2.5. 🛑 The mandatory PASS — run BEFORE you push (you keep skipping this)
After you render the PNG and BEFORE you push it to the wiki, open the rendered image and do these two passes by eye. They are not optional. The #1 and #2 most-repeated hero failures are crowded margins and bad/placeholder images — both are invisible in code and only caught by looking at the output.
§7.5a — Image-quality pass (look at the actual image):
- Is it a real photo (or real UI), clearly of the EXACT subject the hero is about? (RP2040 board for an RP2040 hero — not a different board, not a chip's underside, not a keyword-collision meme, not a stock cable.)
- Is it sharp and ≥800px, well-exposed, framed top/front (not the solder side)?
- It is NOT a generated placeholder, striped panel, blank box, or "couldn't find one" filler.
- If ANY answer is no → fix the image first. A placeholder hero never ships.
§7.5b — Margin & layout pass (measure the gaps):
- Every element ≥80px from each canvas edge; the top-right chip ≥80px from BOTH the top and right.
- ≥24px of air between neighbouring elements; the text column clears the product shot by ≥80px.
- Nothing overlaps, kisses an edge, or runs off unintentionally (only the product shot may bleed, and only off the designated edge).
- Chip/label text is short enough that it isn't crammed into the corner.
- ALL text is on one side; the image full-bleeds/dominates the other; NO text sits on the image side.
- The CTA is an AI "just ask Claude" prompt — there is NO
adompkg/curl/shell command anywhere. - If ANY gap is tight → adjust the layout and re-render, then re-check. Repeat until clean.
Only after BOTH passes are clean do you push. If you find yourself pushing without having looked at the rendered PNG, stop — that is exactly how the broken heroes shipped.
2. The layout (zones on a 1600×1000 canvas)
┌────────────────────────────────────────────────────────────┐
│ [ADOM wordmark] [CATEGORY chip ● / │ ← top row, space-between
│ big transparent mark] │
│ Adom Parts Search ← FULL ┌───────────────────┐│
│ NAME, accent on the │● ● ● X · Adom ││ ← floating webview window:
│ distinctive word │ webview ││ high-right, ~50–58% wide,
│ ├───────────────────┤│ rounded top, drop shadow,
│ value-prop sentence with a few │ REAL app UI ││ teal glow, bleeds off the
│ bold keywords. (max-width ~520) │ (one shot, ││ BOTTOM (+ a bit of the
│ │ good data) ││ right for landscape)
│ ["just-ask-Claude" prompt pill] │ … ││
│ │ … ││
│ wiki.adom.inc/adom/<slug> ← lower-left └───────────────┄┄┄┘│ (window bottom runs off canvas)
└────────────────────────────────────────────────────────────┘
Reference values:
| Element | Value |
|---|---|
| Canvas | 1600×1000, render deviceScaleFactor:2 → 3200×2000, downscale to 2000×1250 |
| Side margins | 80–92px |
| Text column | left:92px; max-width:~500–520px (right edge ≈ 592–612) |
| Gutter to window | ≥ ~90px (aim ~120) — measure it; the #1 failure is the window/prompt box touching the copy |
| Headline | Familjen Grotesk 700, 54–72px, line-height:.99–1.06, letter-spacing:-1.4 to -2px |
| Gradient accent | linear-gradient(100deg,#00e6dc,#39b8ff 60%,#8c6bf7) + background-clip:text |
| Value-prop | Satoshi 400, 21–24px, line-height:1.45–1.5, max-width:~480–520; bold→white |
| Prompt/quote box | teal border rgba(0,184,177,.28), tint rgba(0,184,177,.05), radius 14 |
| URL | teal #00b8b1, bottom-left, left:92px; bottom:54px |
| Window | right:~72px, width:~50% (≈800px), top ≈ 25–30% down, border-radius:15–16px 16px 0 0 |
| Window shadow | 0 28px 80px rgba(0,0,0,.62), 0 0 0 1px rgba(255,255,255,.07), 0 0 130px rgba(0,184,177,.07) |
| Window image fit | shoot the app AT the window's aspect (§6) → fills with NO crop: width:100% at the shot's native aspect. Never object-fit:cover a mismatched shot — it slices panels off the edges. |
Do the gutter arithmetic, every time. With width:800px; right:72px, the window's left edge =
1600 − 72 − 800 = x728. The text column lives in x:92 → ~592–612 (left 92 + max-width 500–520).
Gutter = 728 − 612 ≈ 116px. ✅ The trap (learned the hard way): right:7%; width:56% puts the
left edge at x592 — exactly the text's right edge → 0px gutter, and the prompt box visibly
kisses the window. ALWAYS keep ≥ ~90px between the text column's right edge (including the prompt
pill) and the window's left edge.
3. Brand tokens (exact values)
Background gradient (body):
radial(1200×800 at 88% -10%, rgba(0,184,177,.22), transparent 60%),
radial(900×700 at -5% 110%, rgba(140,107,247,.18), transparent 55%),
radial(700×600 at 50% 50%, rgba(0,97,239,.10), transparent 60%),
linear(155deg, #0a0e14 0%, #0d1117 45%, #071318 100%)
Text: #e6edf3 Secondary: #aeb8c4 Muted: #8b949e / #9aa6b5
Accent teal: #00b8b1 / bright #00e6dc Purple: #8c6bf7 Blue: #64ABFF
Headline gradient: linear(100deg, #00e6dc, #39b8ff 60%, #8c6bf7) (blue→teal→purple family)
Vendor/brand reversed colors for dark bg (brighten for contrast):
blue→#64ABFF red→#FF5252 green→#34D17E (white #fff is fine for wordmarks)
Fonts: 'Familjen Grotesk' 700 (headlines), 'Satoshi' 400/500 (body), 'JetBrains Mono' (code, opt).
WOFF2 bundled with this skill at: assets/fonts/*.woff2 (see §3.1)
Adom wordmark SVG (white): assets/adom-white.svg (bundled)
3.1 Bundled assets & packaging (so the skill works after adompkg install)
A wiki page is, at the end of the day, a git repo + a pkg release tarball — so whatever you
commit into the skill ships to whoever installs it. Do NOT reference gallia/... absolute paths;
gallia won't exist on an installed copy and you'll silently fall back to Arial. This skill
therefore vendors its own brand assets (≈80 KB, OFL / Fontshare-free — fine to redistribute):
adom-wiki-hero/
SKILL.md
assets/
adom-white.svg # the ADOM wordmark ({{WORDMARK_SVG}} source)
fonts/
familjen-grotesk-700-normal.woff2 # headlines
satoshi-400-normal.woff2 # body
satoshi-500-normal.woff2 # body emphasis
render.js # the §4 renderer
hero.html # the §5 template, ready to fill
When you publish this skill's wiki page, commit assets/ (and render.js / hero.html) into the
page repo so the release tarball carries them. Reference everything by relative path from the
skill dir; never reach into gallia. If you add JetBrains Mono later, drop its woff2 in assets/fonts/
and add an @font-face the same way.
4. Rendering pipeline (in-container, no desktop, no live backends)
Traps, learned the hard way:
- The container's
chromium-browseris a dead snap stub — DO NOT use it. Use the Puppeteer-managed Chrome:CHROME=$(ls -d /home/adom/.cache/puppeteer/chrome/linux-*/chrome-linux64/chrome | head -1). chrome --headless --screenshothangs on non-trivial pages in new headless (Chrome v146+), and--headless=oldwas removed. The reliable path is Puppeteer/CDP + an explicitbrowser.close()— that combo shoots and exits.- Inline everything (fonts, the ADOM SVG, the screenshot) as
base64/file://, and abort external requests in the render — no network at render time = deterministic, and aborted fetches can't hang you. Brand fonts referenced by adom.inc URL won't resolve once requests are aborted, so load them locally (see the@font-faceblock below). - Edge headless on a managed Windows box may be policy-blocked and silently write nothing — don't fight it; render where a real Chromium works (here, in-container).
render.js (renders any HTML file → PNG at a fixed viewport, aborts external requests):
const puppeteer = require('/home/adom/project/node_modules/puppeteer-core');
(async () => {
const [,, html, w, h, out, scale] = process.argv;
const b = await puppeteer.launch({ executablePath: process.env.CHROME, headless: 'new',
args:['--no-sandbox','--disable-gpu','--disable-dev-shm-usage','--hide-scrollbars',
'--font-render-hinting=none','--force-color-profile=srgb'] });
const p = await b.newPage();
await p.setRequestInterception(true);
p.on('request', r => (r.url().startsWith('http')) ? r.abort().catch(()=>{}) : r.continue().catch(()=>{}));
await p.setViewport({ width:+w, height:+h, deviceScaleFactor:+(scale||2) });
await p.goto('file://'+html, { waitUntil:'domcontentloaded' });
await new Promise(r=>setTimeout(r,900)); // let fonts settle
await p.screenshot({ path: out });
await b.close(); console.log('OK '+out); // close yourself or it won't exit
})().catch(e=>{console.error(e);process.exit(1)});
Run:
CHROME=$(ls -d /home/adom/.cache/puppeteer/chrome/linux-*/chrome-linux64/chrome | head -1)
CHROME=$CHROME node render.js /abs/path/hero.html 1600 1000 /tmp/[email protected] 2
convert /tmp/[email protected] -resize 2000x <app>/screenshots/hero.png # ImageMagick is available
Brand fonts MUST be injected into the HTML <head> as local @font-face. This skill ships
its own fonts (see §3.1), so reference the bundled copies — relative paths work because
puppeteer only aborts http(s) requests; file:// refs are left alone and resolve against the
HTML's own location. Keep hero.html in the skill dir (or copy assets/ next to it):
<style>
@font-face{font-family:'Familjen Grotesk';font-weight:700;font-display:block;
src:url('assets/fonts/familjen-grotesk-700-normal.woff2') format('woff2')}
@font-face{font-family:'Satoshi';font-weight:400;font-display:block;
src:url('assets/fonts/satoshi-400-normal.woff2') format('woff2')}
@font-face{font-family:'Satoshi';font-weight:500;font-display:block;
src:url('assets/fonts/satoshi-500-normal.woff2') format('woff2')}
</style>
(If hero.html must live elsewhere, swap to absolute file://<skill-dir>/assets/fonts/… refs.)
5. The hero HTML/CSS template (copy, then fill the placeholders)
Placeholders: {{WORDMARK_SVG}} (contents of adom-white.svg), {{TOPRIGHT}} (a category chip OR
a big transparent brand mark — see below), {{HEADLINE}} (the app name; wrap the distinctive word
in <span class="accent">…</span>), {{SUBHEAD}}, {{PROMPT}}, {{URL}}, {{WINLABEL}},
{{SHOT_PATH}} (absolute path to the real app screenshot PNG).
<!DOCTYPE html><html><head><meta charset="utf-8">
<!-- inject the @font-face block from §4 here -->
<style>
*{margin:0;padding:0;box-sizing:border-box}
html,body{width:1600px;height:1000px;overflow:hidden}
body{font-family:'Satoshi',sans-serif;color:#e6edf3;position:relative;
background:
radial-gradient(1200px 800px at 88% -10%, rgba(0,184,177,.22), transparent 60%),
radial-gradient(900px 700px at -5% 110%, rgba(140,107,247,.18), transparent 55%),
radial-gradient(700px 600px at 50% 50%, rgba(0,97,239,.10), transparent 60%),
linear-gradient(155deg,#0a0e14 0%,#0d1117 45%,#071318 100%);}
.dots{position:absolute;inset:0;background-image:radial-gradient(rgba(255,255,255,.045) 1.4px,transparent 1.4px);
background-size:34px 34px;mask-image:linear-gradient(180deg,rgba(0,0,0,.6),transparent 70%);}
.zone{position:absolute;inset:0;padding:80px 92px 0;z-index:3;display:flex;flex-direction:column}
.top{display:flex;align-items:flex-start;justify-content:space-between}
.wordmark svg{height:46px;width:auto;display:block}
.mark svg{height:150px;width:auto;display:block} /* BIG transparent brand mark (vendor apps) */
.chip{border:1px solid rgba(0,184,177,.34);background:rgba(0,184,177,.07);border-radius:999px;
padding:8px 16px;font-size:15px;letter-spacing:2px;text-transform:uppercase;color:#7fe3dd;
display:inline-flex;align-items:center;gap:9px;font-weight:600}
.chip .dot{width:8px;height:8px;border-radius:50%;background:#00e6dc}
h1{font-family:'Familjen Grotesk',sans-serif;font-weight:700;font-size:64px;line-height:1.02;
letter-spacing:-1.8px;color:#f4f8fb;margin-top:40px;margin-bottom:22px;max-width:520px}
h1 .accent{background:linear-gradient(100deg,#00e6dc,#39b8ff 60%,#8c6bf7);
-webkit-background-clip:text;background-clip:text;color:transparent}
.sub{font-size:23px;line-height:1.45;color:#aeb8c4;max-width:500px;margin-bottom:28px}
.sub b{color:#e6edf3;font-weight:500}
.prompt{display:inline-flex;align-items:center;gap:12px;background:rgba(255,255,255,.045);
border:1px solid rgba(0,230,220,.28);border-radius:14px;padding:15px 20px;font-size:19px;color:#cfd8e2;max-width:500px}
.prompt .q{color:#00e6dc;font-weight:700}.prompt .ask{color:#8b949e;font-size:15px;margin-left:6px}
.url{position:absolute;left:92px;bottom:54px;z-index:3;color:#00b8b1;font-weight:600;font-size:19px;
font-family:'Familjen Grotesk',sans-serif}
.window{position:absolute;right:72px;top:280px;width:800px;border-radius:16px 16px 0 0;overflow:hidden;z-index:1;
box-shadow:0 28px 80px rgba(0,0,0,.62), 0 0 0 1px rgba(255,255,255,.07), 0 0 130px rgba(0,184,177,.07)}
/* Windows-style title bar: title left, min/max/close controls top-RIGHT. (We don't use macOS traffic lights.) */
.chrome{height:42px;background:#1b2128;display:flex;align-items:center;padding-left:18px;border-bottom:1px solid #232a33}
.chrome .ct{color:#8b949e;font-size:15px;margin-right:auto}
.winbtns{display:flex;height:100%}
.winbtns .b{width:48px;height:100%;display:flex;align-items:center;justify-content:center;color:#b9c2cd}
.winbtns .min i{display:block;width:12px;height:1.5px;background:#b9c2cd}
.winbtns .max i{display:block;width:11px;height:11px;border:1.5px solid #b9c2cd;border-radius:1px}
.winbtns .cls{font-family:Arial,Helvetica,sans-serif;font-size:18px;line-height:1}
.winbtns .cls:hover{background:#e81123;color:#fff} /* Windows close-button red on hover */
.window img{width:100%;display:block} /* shot already matches the window aspect (§6) → no crop */
</style></head><body>
<div class="dots"></div>
<div class="window">
<div class="chrome"><span class="ct">{{WINLABEL}} · Adom webview</span>
<div class="winbtns"><span class="b min"><i></i></span><span class="b max"><i></i></span><span class="b cls">✕</span></div></div>
<img src="file://{{SHOT_PATH}}">
</div>
<div class="zone">
<div class="top"><div class="wordmark">{{WORDMARK_SVG}}</div>{{TOPRIGHT}}</div>
<h1>{{HEADLINE}}</h1>
<div class="sub">{{SUBHEAD}}</div>
<div class="prompt"><span class="q">"{{PROMPT}}"</span><span class="ask">— just ask Claude</span></div>
</div>
<div class="url">{{URL}}</div>
</body></html>
The top-right ({{TOPRIGHT}})
Pick ONE based on app type:
- Generic Adom app → a small category chip:
<div class="chip"><span class="dot"></span>PARTS SOURCING</div>(DESKTOP APP,BRIDGE,BRIDGE SDK,WIKI SKILL, …). Gives context without repeating the name. - Third-party brand app (a vendor/service) → recreate that brand's wordmark as a big transparent SVG in reversed/brightened colors (no white plate), ~150px tall, keeping its signature element (checkmark, icon, sub-label) so it's instantly recognizable and each billboard is distinct.
The headline keeps the teal→blue→purple accent regardless — that's the family consistency; the top-right mark/chip provides the per-app uniqueness.
6. Picking & producing THE screenshot (real UI, one window)
You want the single best whole window of the app — shown in full, never cropped.
🔑 MATCH THE SHOT TO THE WINDOW'S ASPECT RATIO — NEVER CROP. The product window in the hero
occupies a specific aspect ratio (its width × its height — for a bleed layout, count the part that
runs off-canvas too). You MUST capture the app at that aspect ratio so the app's own fluid /
responsive layout reflows to fill it: open the app in pup and resize the window to the
target aspect (or set the render viewport to those exact W×H), let the layout settle, then
screenshot. Composite it 1:1, with no cropping. Do NOT shoot the app at some convenient size and
then object-fit:cover it into the window — cover slices columns / panels / side rails off the
edges, and it looks awful. The app reflowing to the aspect (columns restacking, a list getting
taller, a panel collapsing) is exactly what you want; a sliced-off panel is a FAILURE. Because the
shot already is the window's shape, object-fit then never has to crop. Workflow:
- Compute the window's W×H in the chosen layout (e.g. bleed-bottom: width ≈ 800px, and it spans from
topdown past the canvas bottom — so it's a tall/portrait region, not the app's default landscape). Get its aspect ratio. - Open the app in pup (
browser_open_window) — or render itsui.html— and resize the window/viewport to that aspect (browser_set_viewport/ window resize). Let it reflow + settle. - Screenshot the whole reflowed window (its own title bar may stay or be replaced by the hero's
faux-chrome). That PNG is
{{SHOT_PATH}}; it drops into the window with no crop.
By app type:
- A web/HTML app with its own
ui.html(parts-search, mouser, digikey, …): render the app's ownsrc/app/ui.htmlpopulated with realistic data, then screenshot it.- Copy
ui.html, inject the local@font-faceblock, and append a boot<script>that calls the app's render functions with a hardcoded realistic dataset (read the render-fn signatures fromui.html). Set the search box value too. - Neutralize anything that fights
file://: override health/poll functions to no-ops and re-assert your data after ~600ms (async aborted fetches can otherwise overwrite your render). - Render at ~1180–1280 × 800 @2x and use that hi-res PNG as
{{SHOT_PATH}}so it stays crisp inside the window.
- Copy
- A real GUI app (the desktop app, KiCad, Fusion): use the actual app window — it already has
its own chrome. Resize that window to the hero window's aspect (the 🔑 rule above) so the whole
app reflows into frame, pick the money shot (a 3D board render, a populated PCB), and composite
it with no crop — don't
cover-crop a default-size grab. - A browser / web-automation app (puppeteer): wrap the page viewport in the faux-Chrome frame (the template's Windows title bar — or a Chrome-style address bar with min/max/close at the right — showing a recognizable, attractive vendor site). No macOS traffic-light dots.
- A CLI / no-GUI thing (sample bridges, the SDK): the terminal is the app surface. Render a
faux terminal/editor window (dots + title) with real commands and output — an install plus a
verb call returning JSON, or a
bridge.jsonopen in an editor.
The same populated-UI technique also produces the 3 distinct inline README screenshots (different states / example queries — never reuse the hero).
7. Build + publish
CHROME=$(ls -d /home/adom/.cache/puppeteer/chrome/linux-*/chrome-linux64/chrome | head -1)
CHROME=$CHROME node render.js /abs/path/hero.html 1600 1000 /tmp/[email protected] 2
convert /tmp/[email protected] -resize 2000x <app>/screenshots/hero.png
Then publish the image and point page.json at it (see adom-wiki-v2 / adom-wiki-publish):
- Push the PNG to the page's
/files(e.g.POST /api/v1/pages/<slug>/fileswith the PNG as{content:<base64>, encoding:"base64"}, oradom-wiki-publish push <slug> --files <png>from a login shell soADOM_WIKI_TOKENis set).- Gotcha: Python
urllibneeds aUser-Agentheader or Cloudflare 403s. - Gotcha: JSON body cap ~4MB → push images in their own small batch, separate from text.
- Gotcha: Python
- Set
page.json→"hero": {"type":"image","path":"<name>.png"}and re-assert"visibility":"public". - Use a versioned filename when REPLACING a hero (e.g.
hero-v2.png) — the CDN can serve a stale image if you overwrite the same URL. Bump the name to force freshness; the old file becomes a harmless orphan (there's no file-DELETE API). - Verify two things anon (200-on-the-API ≠ rendered):
- the image URL returns
200 image/png, AND itsmd5summatches your local file (curl .../blob/app/<slug>/screenshots/<name>.png | md5sum); - the rendered page HTML (
https://wiki.adom.inc/adom/<slug>) references the new filename.
- the image URL returns
8. QA checklist (look at the rendered PNG before you ship)
- §7.5a image pass: the image is a real, sharp, on-subject photo of the EXACT device — NOT a placeholder/striped panel, wrong variant, board underside, meme, or low-res crop.
- §7.5b margin pass: every element ≥80px from each edge (top-right chip ≥80px from top AND right), ≥24px between neighbours, nothing crowds/overlaps; chip text is short.
- 16:10, dark, no big white boxes, no background watermark.
- Exactly one screenshot — no collage.
- The headline is the app's wiki PAGE TITLE (display name, e.g. "Adom Library" — NOT the
slug
adom-lbr); the distinctive word is in the gradient accent. - Value-prop line present, a few keywords bolded; readable across the room.
- Top-right has a category chip OR a big transparent recolored brand mark (recognizable).
- ADOM wordmark top-left; wiki URL lower-left with margin, not over the window.
- Floating window has a drop shadow + faint teal glow and a Windows-style title bar (min/max/close top-RIGHT, NOT macOS traffic-light dots); matches the chosen layout (bleed bottom / bleed right / contained / mirror); shows REAL app UI with good example data.
- The shot was captured AT the window's aspect ratio (app reflowed to fit) — the WHOLE app shows,
NOTHING cropped (no sliced-off columns/panels). No
object-fit:coveron a mismatched grab. - Before rendering, the Step-0.5 content table was shown and the author confirmed the TEXT (esp. the title = the wiki page title).
- Left text column has a clear gutter from the window — NOTHING overlaps.
- Brand fonts actually rendered (not a fallback sans); on-brand colors + gradient.
- Rendered at 2× (3200×2000), downscaled to 2000×1250, crisp.
-
page.jsonhero.path set (versioned filename if replacing); README does NOT re-embed the hero; 3 other distinct inline shots present. Anon image 200 + rendered page references it.
9. Variants — look & feel along two axes (offered in Step 0)
Same content, same rules — a variant is a skin, never a new set of rules. Pick one LAYOUT and
one THEME; they compose. All are generated by build-variants.js
(node build-variants.js <layout> <theme>), which inlines the bundled wordmark, fonts, and Kickstand
tiles so the render is deterministic. Everything derives from the Kickstand brand guide, bundled at
assets/Adom-Brand-Guidelines.pdf.
Axis 1 — LAYOUT (window treatment + text side)
Layout (key) |
Window | Text | When |
|---|---|---|---|
Bleed bottom (bottom, default) |
high-right, runs off the bottom, rounded top | left | The family default; most apps. |
Bleed right (bleedRight) |
tall panel, runs off the right edge, rounded left | left | Wide/landscape UIs; gives a strong "wall of app." |
Contained (contained) |
framed fully inside, margins top/right/bottom, rounded all | left | When the whole window matters; calmest, most product-shot-like. |
Mirror (mirror) |
bleeds bottom, on the left; text on the right | right | Variety in a rotation; or when the shot enters better from the left. |
Full-bleed photo (photo) |
a real product/device PHOTO full-bleeds & dominates one side; inner edge masked/scrimmed into the bg | opposite side | Device/chip/hardware photos (NOT app UIs); bold and editorial. Pair with persona/feature pills in the text column. |
Mix it up. With 5 layouts × 5 themes = 25 distinct looks, plus your headline, accent, mark/chip and the real shot, every hero should feel its own. Rotating the layout/theme per app is REQUIRED, not optional — a wiki where every card is the same layout is a failure of this skill.
Axis 2 — THEME (background + Kickstand texture)
Theme (key) |
Surface | Texture | Feel / when |
|---|---|---|---|
Midnight (midnight, default) |
near-black #0a0e14→#071318 + teal/purple glows |
faint dot-grid | The family default; neutral, lets the UI pop. |
PCB Traces (pcb) |
deep teal #003C3F |
pcb-trace circuit field (~16%) |
Electronic/"ethereal"; great for EDA/hardware apps. |
Onion (onion) |
navy #00204F |
onion-corner concentric arcs (bottom-right) |
The signature deck "substance" slide; solid + serious. |
Pill Field (pill) |
navy #00204F |
pill-field tile |
Softer, friendlier solid; good for utilities. |
Gradient (gradient) |
brand #00b8b1→#0061ef→#8c6bf7 + left scrim |
none (color is the star) | The "wow"/cover-slide; flagship & launch heroes. |
Deck principle (from the Kickstand brand-deck system): gradient = emotional · solids = substance. Use Gradient for the splashy launch hero; solids (PCB/Onion/Pill) for substance; Midnight as the safe default.
Kickstand tiling textures (bundled in assets/tiling/)
Four repeatable currentColor SVGs that "play off PCB/electronics in an ethereal way." They tint to
any surface — place a lighter tint of the surface color at 6–22% opacity, faded out behind the
text so they never compete with copy (build-variants.js does this via a left-fade mask):
pcb-trace.svg — circuit traces + pads (the headline texture) tile ~150–200px
onion-corner.svg— concentric "onion" arcs, anchor bottom-right place ~60% no-repeat
dot-grid.svg — fine dot grid tile ~20–26px
pill-field.svg — scattered rounded pills tile ~60px
Brand palette (Kickstand guide, p.11) — for inventing your own theme
Gradient: linear-gradient(110deg,#00b8b1 0%,#0061ef 48%,#8c6bf7 100%) (teal→blue→purple)
Core: Teal #00B8B1 Blue #0061EF Purple #8C6BF7
Lights: #64ABFF (blue) #8FD3D4 (teal) #C5B3FF (purple)
Darks: Black #191919 Navy #00204F Teal #003C3F Purple #4323AD
To add a layout or theme: add an entry to LAYOUTS or THEMES in build-variants.js, then list it in
the Step-0 AskUserQuestion. Keep the non-negotiable rules (§1) intact.
Bundled brand assets (see also §3.1)
assets/
Adom-Brand-Guidelines.pdf # the full Kickstand guide (palette, patterns, type, logo rules)
adom-white.svg # the ADOM wordmark
fonts/*.woff2 # Familjen Grotesk 700, Satoshi 400/500
tiling/*.svg # pcb-trace, onion-corner, dot-grid, pill-field (currentColor)
---
name: adom-wiki-hero-image
description: >-
Design and render a billboard-grade HERO IMAGE for an Adom Wiki app/skill/bridge page
(wiki.adom.inc) — the 16:10 image, set via page.json, that the wiki page header, the landing
grid card, the Hydrogen Desktop installer, the wiki homepage promos, and the adom-screensaver
all show as a promotional billboard. Produces a dark-mode, on-brand hero: the app NAME as the
headline, a one-line value prop, an on-brand teal→blue→purple accent, and ONE gorgeous shot of
the app's REAL UI shown as a floating drop-shadowed "Adom webview" window bleeding off the
bottom. Use whenever the user wants a hero image, hero billboard, wiki hero, app hero, billboard
image, wiki-page art, app marketing image, screensaver/homepage art, "the image at the top of
the page," hero for <app>, redo my hero, or hero.png — even if they don't say the exact word
"hero." Also use it when a hero looks busy/overwhelming and needs to be simplified.
---
# adom-wiki-hero — make a billboard-grade hero image
A hero image is **not a screenshot and not a brochure**. It is a **billboard**. Picture it at
highway speed: the viewer gets about **one second**. So it must read instantly — **the app's name
+ what it does + one gorgeous glimpse of the app itself.** That's the whole job. Every rule below
serves that one-second read.
### Why this skill exists
Without guidance, an AI asked for a hero just **shoves a boring screenshot into the frame**. That
fails the job. This skill exists so that **any Adom employee — or a third party — building a hero
for their wiki app or skill ends up with an image that is both beautiful AND unmistakably theirs.**
The mental shift it forces: *"I have to convey this app's **value proposition** in one clean image,"*
not *"paste a screenshot." Two outcomes, always together:
- **Beautiful & on-brand** — the rules and brand tokens below guarantee a polished, consistent family.
- **Unique to the app** — the variants (layout × theme, §9), the headline, the brand mark/chip, and
the real screenshot make each hero distinct so the wiki doesn't look like one repeated template.
### Where these heroes run (design for ALL of them)
The hero is reused, **unchanged**, as a promotional billboard across many surfaces — so it must
stand alone and read in one second on each:
- the wiki **page** header and the landing **grid card**;
- the **wiki homepage** daily **trending-apps slideshow** (a rotating showcase);
- the **adom-screensaver** — it auto-discovers every public page's `hero.png` and cross-fades them,
so your hero literally runs as an **ad** on idle screens;
- the **Hydrogen Desktop installer** setup steps (rotating billboards of recent apps);
- and increasingly **social/video** — YouTube podcast cards, TikTok/Instagram/Reels, X.com posts.
Treat the hero as ad creative: bold, legible at a glance, self-explanatory, no surrounding context.
This skill is the consolidated recipe behind the adom-parts-search / adom-mouser / adom-digikey /
adom-jlcpcb / adom-desktop heroes. Every rule was learned by getting it wrong; follow it closely.
---
## Step 0 — ASK the author for a look & feel FIRST (AskUserQuestion)
Before rendering anything, call **`AskUserQuestion`** to let the author choose the look & feel along
**two axes** — these compose, so the same skill yields a distinct hero per app. Ask both in ONE
`AskUserQuestion` call (two questions). If the user already named a layout/theme, skip that question.
Tokens + presets live in §9 and [`build-variants.js`](build-variants.js).
```
Q1 header "Layout" question "How should the app window sit?"
• Bleed bottom (Recommended) — window high-right, runs off the BOTTOM. The family default.
• Bleed right — window runs off the RIGHT edge (tall panel).
• Contained — window framed fully inside, margins top/right/bottom.
• Mirror — name/value-prop on the RIGHT, window on the LEFT (bleeds bottom).
Q2 header "Theme" question "Which background + texture?"
• Midnight (Recommended) — near-black gradient + faint dot-grid. The family default.
• PCB Traces — deep brand teal + the Kickstand circuit-field tile (ethereal/electronic).
• Onion / Pill (solid) — navy field + the signature concentric-arc onion, or the pill-field tile.
• Gradient (wow) — bold brand teal→blue→purple cover-slide gradient. For flagship/launch.
```
Then render: `node build-variants.js <layout> <theme>` (layouts: `bottom|bleedRight|contained|mirror`;
themes: `midnight|pcb|onion|pill|gradient`) → `render.js` → downscale. Every combination still obeys
the non-negotiable rules below (dark, one screenshot, ≥~90px gutter, real UI, brand type, Windows-style
chrome). A variant changes background/texture/window-placement only — **never** the rules.
---
## Step 0.5 — 🛑 SHOW THE CONTENT TABLE AND GET SIGN-OFF *BEFORE* YOU RENDER
Rendering is a **heavy, token-expensive** operation, and the single most-repeated failure is shipping
a render with the **wrong headline** (using the slug/CLI name instead of the **wiki page title**, a
tagline where the name belongs, a two-sentence subhead, a mis-quoted value prop). Re-rendering to fix
copy you could have confirmed in one cheap message **wastes a lot of tokens** — so confirm FIRST.
**After Step 0 (look & feel) and BEFORE you render anything, post a table of every hero element for
the author to verify, and WAIT for their confirmation or correction.** One row per element, three
columns — and the **TEXT column is the point** (it's what keeps getting wrong):
| Item | Exact text / content | Font size / image size |
|---|---|---|
| ADOM logo | (the white ADOM wordmark) | 46px, top-left |
| Top-right chip | `<EXACT CHIP TEXT>` | 15px, ≤28 chars |
| **Title** | `<EXACT HEADLINE = the WIKI PAGE TITLE>` — accent word: `<word>` | Familjen Grotesk 700, 64px |
| Sub-title | `<EXACT one-sentence value prop, with the bold words marked>` | Satoshi 400, 23px |
| CTA pill | `"<EXACT AI prompt>"` | 19px |
| Wiki URL | `wiki.adom.inc/adom/<slug>` | 19px, lower-left |
| Product window | `<which app screen + dataset>` shot at the window aspect | width ~800px (~50%) |
Call it out explicitly: **"Title = the wiki PAGE TITLE (e.g. `adom-lbr`'s page is titled *Adom
Library*) — confirm before I render."** Do NOT render-then-ask; that burns tokens on a throwaway
image. Only after the author signs off do you proceed to render (and then the §2.5 pass before push).
---
## 0. Output contract (what you produce)
- One image, **16:10**, rendered at **1600×1000 @ deviceScaleFactor 2** (= a crisp 3200×2000),
then downscaled to a **2000×1250** PNG (~300–950 KB). Store it at `screenshots/hero.png`
(or a versioned name on replacement — see §7).
- Registered in `page.json` (NOT inlined in the README):
```json
"hero": { "type": "image", "path": "screenshots/hero.png" }
```
- Pushed to the page's git repo and verified live (see §7).
---
## 1. NON-NEGOTIABLE RULES (the hard-won ones — do not violate)
1. **ONE app, ONE screenshot.** No collages. A grid trying to show six features at once reads as
noise and fails the one-second test. The single best whole-window shot, presented beautifully,
beats any montage. If you catch yourself assembling a grid, **stop**.
2. **Dark mode. Minimize bright pixels.** Background is the Adom dark gradient. NEVER put a logo
or product shot inside a big white box/plate — too many white pixels for a billboard. Logos go
on **transparent** backgrounds, recolored for dark (see §3, §5).
3. **No background watermark logo.** Don't drop a giant faint Adom mark behind the content — the
billboard frames crop it and it looks broken. The only background texture allowed is a subtle
**Kickstand pattern** — dot-grid, concentric arcs, or square grid (see §9), kept faint and masked.
4. **Win the one-second read AND make the app unmistakable.** Two proven headline patterns — pick
per app, never leave the viewer guessing what this is:
- **Name-led** — the full app NAME is the headline ("Adom Desktop KiCad Bridge", "Adom Parts
Search"), accent on the distinctive word. Best when the name itself sells it.
**The "name" is the app's WIKI PAGE TITLE (its human display name) — NOT the slug, the CLI
binary, or the README's `# code-style` heading.** e.g. slug `adom-lbr` → headline **"Adom
Library"** (its page title); slug `adom-parts-search` → "Adom Parts Search". Match the page
title verbatim so the hero and the page header agree; the distinctive word gets the accent.
- **Tagline-led** — a punchy value phrase is the headline ("One search. Every distributor."),
accent on the key word, with the app identified by a big brand mark / category chip **and** the
URL. Best when paired with a recognizable mark (e.g. Mouser).
Either way: short, scannable, gradient accent on the key word. A pure tagline with no mark/chip
to anchor it is the failure mode — the viewer shouldn't have to work out which app it is.
5. **Put the product UI in a floating "webview" window, not full-bleed-width.** A pseudo-window
(rounded top, drop shadow, **Windows-style title bar** — min/max/close at the top-RIGHT, never
macOS traffic-light dots — faint teal glow), ~50–58% of the width,
high-right, **bleeding off only the bottom** (and slightly off the right for wide landscape
shots), reads as "you're using this in an Adom webview." Do NOT stretch the screenshot to the
full image width, and do NOT float it in the lower third — place it **high** (top ≈ 30% down).
6. **Value-prop line under the name** — one sentence on what it does, a few keywords bolded
(white) for scanning.
7. **Margins are sacred. Nothing overlaps anything. Humans need margins.** EVERY element —
wordmark, top-right chip/mark, headline, value-prop, prompt pill, capability pills, URL,
slug, and the floating product shot — must hold a **clear ≥80px margin from every canvas
edge** and **≥24px of air from every neighbouring element**. The top-right chip is the most
common offender: it must sit **≥80px from the top edge and ≥80px from the right edge**, and
must NOT collide with the product shot below/beside it. Keep chip/label text SHORT (≤ ~28
chars) so it never runs toward the corner — abbreviate (e.g. "STMICRO", "TI", "NORDIC"), put
the detail in the headline, not the chip. This is a HARD, repeatedly-failed rule: after every
render you MUST do the margin pass in §7.5 and re-render until it passes. Crowding reads as
amateur and gets the hero rejected.
8. **Wiki URL in the lower-LEFT corner**, with margin — never over the window.
9. **Use REAL app UI** with **good, specific example data** (real MPNs / real-looking domain
content). Render the app's own `ui.html` populated with a realistic dataset — not a fake
mockup, not an empty state. Generic "Lorem"/blank states look dead. **Capture the
laptop-browser view with every toolbar / studio / HUD / layers panel OPEN** — the busy,
sophisticated state, captured at ~1440px wide (deviceScaleFactor 2), with content loaded and
the control panels deployed before the shot. A bare canvas reads as a toy; a full-toolbar
shot says "capable." (John, 2026-06-21: he explicitly wanted the full-toolbar laptop view so
viewers "understand how sophisticated the apps have gotten" — proven on the EDA apps.)
9b. **Shoot the screenshot at the hero slot's TRUE SIZE (or at least its exact
aspect ratio) — NEVER crop-to-fit.** Every layout places the shot in a fixed
slot with `object-fit: cover` (e.g. the mirror layout's window is 806x760);
a screenshot taken at some other aspect gets silently cropped — headers cut
mid-word, panels sliced — and ships looking broken. The right way: read the
slot's width/height out of the variant HTML, resize the browser/viewport to
exactly that CSS size (2x deviceScaleFactor for crispness), and let the
app's fluid layout reflow NATURALLY at that size before capturing. The UI
laying itself out at the display aspect always beats cropping a shot taken
at the wrong one. Then trim any blank/white overflow rows from the capture
before compositing. (User rule, learned twice: adom-tts and hands-free both
shipped cropped heroes before this.)
10. **On-brand type only.** Familjen Grotesk (headlines, 700), Satoshi (body), JetBrains Mono
(terminal/code, optional). Never Inter/Arial/system.
11. **The hero is self-contained** (name + mark/chip + value prop + product). The installer,
homepage, and screensaver frame it directly, so don't rely on surrounding text.
12. **Do NOT also embed the hero in the README.** The page already shows it from `hero.path`;
repeating it is redundant. READMEs get 3 *other* distinct inline screenshots instead.
13. **Every image MUST be a real, relevant, high-quality photo of the actual subject. NEVER ship
a placeholder.** If the hero features a device/chip/product, the image must clearly BE that
device — not a wrong variant, not the *underside* of a board, not a meme/stock image that
merely shares a keyword, not a low-res (<800px) crop, and **never a generated placeholder /
striped "no-photo" panel.** A striped or blank panel in a shipped hero is a FAILURE, not a
fallback. If you cannot find a genuinely good, on-subject image, that is a BLOCKER to solve
(search harder, try other terms/sources, or ask the user) — do not paper over it. See the
image-QA pass in §7.5.
14. **Keep text and image in SEPARATE zones — but the specific layout is YOUR choice and SHOULD
vary per app.** The only universal here is the *principle*: text lives in its own clean column,
the image in its own zone, they never overlap, and you never wrap copy around the image or drop
a URL/CTA onto its busy area. (That separation is what kills the margin-collision churn.) This
principle is satisfied by **every** layout in §9 — so **pick a different layout × theme per app
so the wiki never looks templated. That variety is the whole point — do NOT collapse every hero
into one layout.** Forms that all satisfy the principle, pick what flatters THIS app's shot: a
floating "webview window" (app UIs), a framed *contained* shot, a *bottom* or *right* bleed, a
*mirror* (text right), or a **full-bleed product photo** dominating one side (great for device/
chip/hardware photos). The full-bleed-photo form is one OPTION among these, not the default.
15. **The call-to-action is an AI prompt — NEVER a command line.** Show a paste-into-Claude prompt
(the "just ask Claude" pill), e.g. `"flash my RP2040 as a USB keyboard"`. Do NOT put
`adompkg install …`, `pip install …`, `curl … | sh`, or any shell command on a hero. People
copy/paste AI prompts now; a CLI string on a billboard reads as dated. (The wiki page's Install
section still shows the install command — the *hero* sells the outcome via an AI prompt.)
---
## 2.5. 🛑 The mandatory PASS — run BEFORE you push (you keep skipping this)
After you render the PNG and BEFORE you push it to the wiki, **open the rendered image and do
these two passes by eye.** They are not optional. The #1 and #2 most-repeated hero failures are
crowded margins and bad/placeholder images — both are invisible in code and only caught by
looking at the output.
**§7.5a — Image-quality pass (look at the actual image):**
- Is it a real photo (or real UI), clearly of the EXACT subject the hero is about? (RP2040 board
for an RP2040 hero — not a different board, not a chip's underside, not a keyword-collision
meme, not a stock cable.)
- Is it sharp and ≥800px, well-exposed, framed top/front (not the solder side)?
- It is **NOT** a generated placeholder, striped panel, blank box, or "couldn't find one" filler.
- If ANY answer is no → fix the image first. A placeholder hero never ships.
**§7.5b — Margin & layout pass (measure the gaps):**
- Every element ≥80px from each canvas edge; the top-right chip ≥80px from BOTH the top and right.
- ≥24px of air between neighbouring elements; the text column clears the product shot by ≥80px.
- Nothing overlaps, kisses an edge, or runs off unintentionally (only the product shot may bleed,
and only off the designated edge).
- Chip/label text is short enough that it isn't crammed into the corner.
- ALL text is on one side; the image full-bleeds/dominates the other; NO text sits on the image side.
- The CTA is an AI "just ask Claude" prompt — there is NO `adompkg`/`curl`/shell command anywhere.
- If ANY gap is tight → adjust the layout and **re-render**, then re-check. Repeat until clean.
Only after BOTH passes are clean do you push. If you find yourself pushing without having looked
at the rendered PNG, stop — that is exactly how the broken heroes shipped.
---
## 2. The layout (zones on a 1600×1000 canvas)
```
┌────────────────────────────────────────────────────────────┐
│ [ADOM wordmark] [CATEGORY chip ● / │ ← top row, space-between
│ big transparent mark] │
│ Adom Parts Search ← FULL ┌───────────────────┐│
│ NAME, accent on the │● ● ● X · Adom ││ ← floating webview window:
│ distinctive word │ webview ││ high-right, ~50–58% wide,
│ ├───────────────────┤│ rounded top, drop shadow,
│ value-prop sentence with a few │ REAL app UI ││ teal glow, bleeds off the
│ bold keywords. (max-width ~520) │ (one shot, ││ BOTTOM (+ a bit of the
│ │ good data) ││ right for landscape)
│ ["just-ask-Claude" prompt pill] │ … ││
│ │ … ││
│ wiki.adom.inc/adom/<slug> ← lower-left └───────────────┄┄┄┘│ (window bottom runs off canvas)
└────────────────────────────────────────────────────────────┘
```
**Reference values:**
| Element | Value |
|---|---|
| Canvas | `1600×1000`, render `deviceScaleFactor:2` → `3200×2000`, downscale to `2000×1250` |
| Side margins | `80–92px` |
| Text column | `left:92px; max-width:~500–520px` (right edge ≈ 592–612) |
| Gutter to window | **≥ ~90px (aim ~120)** — measure it; the #1 failure is the window/prompt box touching the copy |
| Headline | Familjen Grotesk 700, `54–72px`, `line-height:.99–1.06`, `letter-spacing:-1.4 to -2px` |
| Gradient accent | `linear-gradient(100deg,#00e6dc,#39b8ff 60%,#8c6bf7)` + `background-clip:text` |
| Value-prop | Satoshi 400, `21–24px`, `line-height:1.45–1.5`, `max-width:~480–520`; bold→white |
| Prompt/quote box | teal border `rgba(0,184,177,.28)`, tint `rgba(0,184,177,.05)`, radius 14 |
| URL | teal `#00b8b1`, bottom-left, `left:92px; bottom:54px` |
| Window | `right:~72px`, `width:~50%` (≈800px), top ≈ 25–30% down, `border-radius:15–16px 16px 0 0` |
| Window shadow | `0 28px 80px rgba(0,0,0,.62), 0 0 0 1px rgba(255,255,255,.07), 0 0 130px rgba(0,184,177,.07)` |
| Window image fit | shoot the app AT the window's aspect (§6) → fills with **NO crop**: `width:100%` at the shot's native aspect. **Never** `object-fit:cover` a mismatched shot — it slices panels off the edges. |
**Do the gutter arithmetic, every time.** With `width:800px; right:72px`, the window's left edge =
`1600 − 72 − 800 = x728`. The text column lives in `x:92 → ~592–612` (left 92 + max-width 500–520).
Gutter = `728 − 612 ≈ 116px`. ✅ The trap (learned the hard way): `right:7%; width:56%` puts the
left edge at `x592` — exactly the text's right edge → **0px gutter**, and the prompt box visibly
kisses the window. ALWAYS keep ≥ ~90px between the text column's right edge (including the prompt
pill) and the window's left edge.
---
## 3. Brand tokens (exact values)
```
Background gradient (body):
radial(1200×800 at 88% -10%, rgba(0,184,177,.22), transparent 60%),
radial(900×700 at -5% 110%, rgba(140,107,247,.18), transparent 55%),
radial(700×600 at 50% 50%, rgba(0,97,239,.10), transparent 60%),
linear(155deg, #0a0e14 0%, #0d1117 45%, #071318 100%)
Text: #e6edf3 Secondary: #aeb8c4 Muted: #8b949e / #9aa6b5
Accent teal: #00b8b1 / bright #00e6dc Purple: #8c6bf7 Blue: #64ABFF
Headline gradient: linear(100deg, #00e6dc, #39b8ff 60%, #8c6bf7) (blue→teal→purple family)
Vendor/brand reversed colors for dark bg (brighten for contrast):
blue→#64ABFF red→#FF5252 green→#34D17E (white #fff is fine for wordmarks)
Fonts: 'Familjen Grotesk' 700 (headlines), 'Satoshi' 400/500 (body), 'JetBrains Mono' (code, opt).
WOFF2 bundled with this skill at: assets/fonts/*.woff2 (see §3.1)
Adom wordmark SVG (white): assets/adom-white.svg (bundled)
```
### 3.1 Bundled assets & packaging (so the skill works after `adompkg install`)
A wiki page is, at the end of the day, **a git repo + a pkg release tarball** — so whatever you
commit into the skill ships to whoever installs it. Do NOT reference `gallia/...` absolute paths;
gallia won't exist on an installed copy and you'll silently fall back to Arial. This skill
therefore **vendors its own brand assets** (≈80 KB, OFL / Fontshare-free — fine to redistribute):
```
adom-wiki-hero/
SKILL.md
assets/
adom-white.svg # the ADOM wordmark ({{WORDMARK_SVG}} source)
fonts/
familjen-grotesk-700-normal.woff2 # headlines
satoshi-400-normal.woff2 # body
satoshi-500-normal.woff2 # body emphasis
render.js # the §4 renderer
hero.html # the §5 template, ready to fill
```
When you publish this skill's wiki page, commit `assets/` (and `render.js` / `hero.html`) into the
page repo so the release tarball carries them. Reference everything by **relative** path from the
skill dir; never reach into gallia. If you add JetBrains Mono later, drop its woff2 in `assets/fonts/`
and add an `@font-face` the same way.
---
## 4. Rendering pipeline (in-container, no desktop, no live backends)
**Traps, learned the hard way:**
- The container's `chromium-browser` is a **dead snap stub** — DO NOT use it. Use the
Puppeteer-managed Chrome: `CHROME=$(ls -d /home/adom/.cache/puppeteer/chrome/linux-*/chrome-linux64/chrome | head -1)`.
- `chrome --headless --screenshot` **hangs** on non-trivial pages in new headless (Chrome v146+),
and `--headless=old` was removed. The reliable path is **Puppeteer/CDP + an explicit
`browser.close()`** — that combo shoots *and* exits.
- **Inline everything** (fonts, the ADOM SVG, the screenshot) as `base64`/`file://`, and abort
external requests in the render — no network at render time = deterministic, and aborted fetches
can't hang you. Brand fonts referenced by adom.inc URL won't resolve once requests are aborted,
so load them locally (see the `@font-face` block below).
- Edge headless on a managed Windows box may be policy-blocked and silently write nothing — don't
fight it; render where a real Chromium works (here, in-container).
`render.js` (renders any HTML file → PNG at a fixed viewport, aborts external requests):
```js
const puppeteer = require('/home/adom/project/node_modules/puppeteer-core');
(async () => {
const [,, html, w, h, out, scale] = process.argv;
const b = await puppeteer.launch({ executablePath: process.env.CHROME, headless: 'new',
args:['--no-sandbox','--disable-gpu','--disable-dev-shm-usage','--hide-scrollbars',
'--font-render-hinting=none','--force-color-profile=srgb'] });
const p = await b.newPage();
await p.setRequestInterception(true);
p.on('request', r => (r.url().startsWith('http')) ? r.abort().catch(()=>{}) : r.continue().catch(()=>{}));
await p.setViewport({ width:+w, height:+h, deviceScaleFactor:+(scale||2) });
await p.goto('file://'+html, { waitUntil:'domcontentloaded' });
await new Promise(r=>setTimeout(r,900)); // let fonts settle
await p.screenshot({ path: out });
await b.close(); console.log('OK '+out); // close yourself or it won't exit
})().catch(e=>{console.error(e);process.exit(1)});
```
Run:
```bash
CHROME=$(ls -d /home/adom/.cache/puppeteer/chrome/linux-*/chrome-linux64/chrome | head -1)
CHROME=$CHROME node render.js /abs/path/hero.html 1600 1000 /tmp/[email protected] 2
convert /tmp/[email protected] -resize 2000x <app>/screenshots/hero.png # ImageMagick is available
```
**Brand fonts MUST be injected** into the HTML `<head>` as local `@font-face`. This skill **ships
its own fonts** (see §3.1), so reference the bundled copies — **relative** paths work because
puppeteer only aborts `http(s)` requests; `file://` refs are left alone and resolve against the
HTML's own location. Keep `hero.html` in the skill dir (or copy `assets/` next to it):
```html
<style>
@font-face{font-family:'Familjen Grotesk';font-weight:700;font-display:block;
src:url('assets/fonts/familjen-grotesk-700-normal.woff2') format('woff2')}
@font-face{font-family:'Satoshi';font-weight:400;font-display:block;
src:url('assets/fonts/satoshi-400-normal.woff2') format('woff2')}
@font-face{font-family:'Satoshi';font-weight:500;font-display:block;
src:url('assets/fonts/satoshi-500-normal.woff2') format('woff2')}
</style>
```
(If `hero.html` must live elsewhere, swap to absolute `file://<skill-dir>/assets/fonts/…` refs.)
---
## 5. The hero HTML/CSS template (copy, then fill the placeholders)
Placeholders: `{{WORDMARK_SVG}}` (contents of adom-white.svg), `{{TOPRIGHT}}` (a category chip OR
a big transparent brand mark — see below), `{{HEADLINE}}` (the app name; wrap the distinctive word
in `<span class="accent">…</span>`), `{{SUBHEAD}}`, `{{PROMPT}}`, `{{URL}}`, `{{WINLABEL}}`,
`{{SHOT_PATH}}` (absolute path to the real app screenshot PNG).
```html
<!DOCTYPE html><html><head><meta charset="utf-8">
<!-- inject the @font-face block from §4 here -->
<style>
*{margin:0;padding:0;box-sizing:border-box}
html,body{width:1600px;height:1000px;overflow:hidden}
body{font-family:'Satoshi',sans-serif;color:#e6edf3;position:relative;
background:
radial-gradient(1200px 800px at 88% -10%, rgba(0,184,177,.22), transparent 60%),
radial-gradient(900px 700px at -5% 110%, rgba(140,107,247,.18), transparent 55%),
radial-gradient(700px 600px at 50% 50%, rgba(0,97,239,.10), transparent 60%),
linear-gradient(155deg,#0a0e14 0%,#0d1117 45%,#071318 100%);}
.dots{position:absolute;inset:0;background-image:radial-gradient(rgba(255,255,255,.045) 1.4px,transparent 1.4px);
background-size:34px 34px;mask-image:linear-gradient(180deg,rgba(0,0,0,.6),transparent 70%);}
.zone{position:absolute;inset:0;padding:80px 92px 0;z-index:3;display:flex;flex-direction:column}
.top{display:flex;align-items:flex-start;justify-content:space-between}
.wordmark svg{height:46px;width:auto;display:block}
.mark svg{height:150px;width:auto;display:block} /* BIG transparent brand mark (vendor apps) */
.chip{border:1px solid rgba(0,184,177,.34);background:rgba(0,184,177,.07);border-radius:999px;
padding:8px 16px;font-size:15px;letter-spacing:2px;text-transform:uppercase;color:#7fe3dd;
display:inline-flex;align-items:center;gap:9px;font-weight:600}
.chip .dot{width:8px;height:8px;border-radius:50%;background:#00e6dc}
h1{font-family:'Familjen Grotesk',sans-serif;font-weight:700;font-size:64px;line-height:1.02;
letter-spacing:-1.8px;color:#f4f8fb;margin-top:40px;margin-bottom:22px;max-width:520px}
h1 .accent{background:linear-gradient(100deg,#00e6dc,#39b8ff 60%,#8c6bf7);
-webkit-background-clip:text;background-clip:text;color:transparent}
.sub{font-size:23px;line-height:1.45;color:#aeb8c4;max-width:500px;margin-bottom:28px}
.sub b{color:#e6edf3;font-weight:500}
.prompt{display:inline-flex;align-items:center;gap:12px;background:rgba(255,255,255,.045);
border:1px solid rgba(0,230,220,.28);border-radius:14px;padding:15px 20px;font-size:19px;color:#cfd8e2;max-width:500px}
.prompt .q{color:#00e6dc;font-weight:700}.prompt .ask{color:#8b949e;font-size:15px;margin-left:6px}
.url{position:absolute;left:92px;bottom:54px;z-index:3;color:#00b8b1;font-weight:600;font-size:19px;
font-family:'Familjen Grotesk',sans-serif}
.window{position:absolute;right:72px;top:280px;width:800px;border-radius:16px 16px 0 0;overflow:hidden;z-index:1;
box-shadow:0 28px 80px rgba(0,0,0,.62), 0 0 0 1px rgba(255,255,255,.07), 0 0 130px rgba(0,184,177,.07)}
/* Windows-style title bar: title left, min/max/close controls top-RIGHT. (We don't use macOS traffic lights.) */
.chrome{height:42px;background:#1b2128;display:flex;align-items:center;padding-left:18px;border-bottom:1px solid #232a33}
.chrome .ct{color:#8b949e;font-size:15px;margin-right:auto}
.winbtns{display:flex;height:100%}
.winbtns .b{width:48px;height:100%;display:flex;align-items:center;justify-content:center;color:#b9c2cd}
.winbtns .min i{display:block;width:12px;height:1.5px;background:#b9c2cd}
.winbtns .max i{display:block;width:11px;height:11px;border:1.5px solid #b9c2cd;border-radius:1px}
.winbtns .cls{font-family:Arial,Helvetica,sans-serif;font-size:18px;line-height:1}
.winbtns .cls:hover{background:#e81123;color:#fff} /* Windows close-button red on hover */
.window img{width:100%;display:block} /* shot already matches the window aspect (§6) → no crop */
</style></head><body>
<div class="dots"></div>
<div class="window">
<div class="chrome"><span class="ct">{{WINLABEL}} · Adom webview</span>
<div class="winbtns"><span class="b min"><i></i></span><span class="b max"><i></i></span><span class="b cls">✕</span></div></div>
<img src="file://{{SHOT_PATH}}">
</div>
<div class="zone">
<div class="top"><div class="wordmark">{{WORDMARK_SVG}}</div>{{TOPRIGHT}}</div>
<h1>{{HEADLINE}}</h1>
<div class="sub">{{SUBHEAD}}</div>
<div class="prompt"><span class="q">"{{PROMPT}}"</span><span class="ask">— just ask Claude</span></div>
</div>
<div class="url">{{URL}}</div>
</body></html>
```
### The top-right (`{{TOPRIGHT}}`)
Pick ONE based on app type:
- **Generic Adom app** → a small **category chip**: `<div class="chip"><span class="dot"></span>PARTS SOURCING</div>`
(`DESKTOP APP`, `BRIDGE`, `BRIDGE SDK`, `WIKI SKILL`, …). Gives context without repeating the name.
- **Third-party brand app** (a vendor/service) → recreate that brand's wordmark as a **big
transparent SVG in reversed/brightened colors** (no white plate), ~150px tall, keeping its
signature element (checkmark, icon, sub-label) so it's instantly recognizable and each billboard
is distinct.
The headline keeps the teal→blue→purple accent regardless — that's the family consistency; the
top-right mark/chip provides the per-app uniqueness.
---
## 6. Picking & producing THE screenshot (real UI, one window)
You want the single best **whole window** of the app — shown **in full, never cropped.**
**🔑 MATCH THE SHOT TO THE WINDOW'S ASPECT RATIO — NEVER CROP.** The product window in the hero
occupies a specific aspect ratio (its width × its height — for a *bleed* layout, count the part that
runs off-canvas too). You MUST capture the app at **that** aspect ratio so the app's own fluid /
responsive layout **reflows to fill it**: open the app **in pup** and **resize the window to the
target aspect** (or set the render viewport to those exact W×H), let the layout settle, *then*
screenshot. Composite it **1:1, with no cropping**. Do NOT shoot the app at some convenient size and
then `object-fit:cover` it into the window — `cover` slices columns / panels / side rails off the
edges, and it looks awful. The app **reflowing** to the aspect (columns restacking, a list getting
taller, a panel collapsing) is exactly what you want; a sliced-off panel is a FAILURE. Because the
shot already *is* the window's shape, `object-fit` then never has to crop. Workflow:
1. Compute the window's W×H in the chosen layout (e.g. bleed-bottom: width ≈ 800px, and it spans from
`top` down past the canvas bottom — so it's a **tall/portrait** region, not the app's default
landscape). Get its aspect ratio.
2. Open the app in pup (`browser_open_window`) — or render its `ui.html` — and **resize the
window/viewport to that aspect** (`browser_set_viewport` / window resize). Let it reflow + settle.
3. Screenshot the **whole** reflowed window (its own title bar may stay or be replaced by the hero's
faux-chrome). That PNG is `{{SHOT_PATH}}`; it drops into the window with **no crop**.
By app type:
- **A web/HTML app with its own `ui.html`** (parts-search, mouser, digikey, …): render the app's
own `src/app/ui.html` populated with realistic data, then screenshot it.
1. Copy `ui.html`, inject the local `@font-face` block, and append a boot `<script>` that calls
the app's render functions with a hardcoded realistic dataset (read the render-fn signatures
from `ui.html`). Set the search box value too.
2. Neutralize anything that fights `file://`: override health/poll functions to no-ops and
re-assert your data after ~600ms (async aborted fetches can otherwise overwrite your render).
3. Render at ~1180–1280 × 800 @2x and use that hi-res PNG as `{{SHOT_PATH}}` so it stays crisp
inside the window.
- **A real GUI app** (the desktop app, KiCad, Fusion): use the actual app window — it already has
its own chrome. **Resize that window to the hero window's aspect (the 🔑 rule above) so the whole
app reflows into frame**, pick the money shot (a 3D board render, a populated PCB), and composite
it with no crop — don't `cover`-crop a default-size grab.
- **A browser / web-automation app** (puppeteer): wrap the page viewport in the faux-Chrome frame
(the template's Windows title bar — or a Chrome-style address bar with min/max/close at the
right — showing a recognizable, attractive vendor site). No macOS traffic-light dots.
- **A CLI / no-GUI thing** (sample bridges, the SDK): the terminal *is* the app surface. Render a
faux terminal/editor window (dots + title) with **real** commands and output — an install plus a
verb call returning JSON, or a `bridge.json` open in an editor.
The same populated-UI technique also produces the **3 distinct inline README screenshots**
(different states / example queries — never reuse the hero).
---
## 7. Build + publish
```bash
CHROME=$(ls -d /home/adom/.cache/puppeteer/chrome/linux-*/chrome-linux64/chrome | head -1)
CHROME=$CHROME node render.js /abs/path/hero.html 1600 1000 /tmp/[email protected] 2
convert /tmp/[email protected] -resize 2000x <app>/screenshots/hero.png
```
Then publish the image and point `page.json` at it (see `adom-wiki-v2` / `adom-wiki-publish`):
1. **Push the PNG** to the page's `/files` (e.g. `POST /api/v1/pages/<slug>/files` with the PNG as
`{content:<base64>, encoding:"base64"}`, or `adom-wiki-publish push <slug> --files <png>` from a
login shell so `ADOM_WIKI_TOKEN` is set).
- **Gotcha:** Python `urllib` needs a `User-Agent` header or Cloudflare 403s.
- **Gotcha:** JSON body cap ~4MB → push images in their own small batch, separate from text.
2. Set `page.json` → `"hero": {"type":"image","path":"<name>.png"}` and re-assert
`"visibility":"public"`.
3. **Use a versioned filename when REPLACING a hero** (e.g. `hero-v2.png`) — the CDN can serve a
stale image if you overwrite the same URL. Bump the name to force freshness; the old file
becomes a harmless orphan (there's no file-DELETE API).
4. **Verify two things anon** (200-on-the-API ≠ rendered):
- the image URL returns `200 image/png`, AND its `md5sum` matches your local file
(`curl .../blob/app/<slug>/screenshots/<name>.png | md5sum`);
- the rendered page HTML (`https://wiki.adom.inc/adom/<slug>`) references the new filename.
---
## 8. QA checklist (look at the rendered PNG before you ship)
- [ ] **§7.5a image pass:** the image is a real, sharp, on-subject photo of the EXACT device —
NOT a placeholder/striped panel, wrong variant, board underside, meme, or low-res crop.
- [ ] **§7.5b margin pass:** every element ≥80px from each edge (top-right chip ≥80px from top
AND right), ≥24px between neighbours, nothing crowds/overlaps; chip text is short.
- [ ] 16:10, dark, no big white boxes, no background watermark.
- [ ] Exactly **one** screenshot — no collage.
- [ ] The headline is the app's **wiki PAGE TITLE** (display name, e.g. "Adom Library" — NOT the
slug `adom-lbr`); the distinctive word is in the gradient accent.
- [ ] Value-prop line present, a few keywords bolded; readable across the room.
- [ ] Top-right has a category chip OR a big transparent recolored brand mark (recognizable).
- [ ] ADOM wordmark top-left; wiki URL lower-left with margin, not over the window.
- [ ] Floating window has a drop shadow + faint teal glow and a Windows-style title bar (min/max/close
top-RIGHT, NOT macOS traffic-light dots); matches the chosen layout (bleed bottom / bleed right /
contained / mirror); shows REAL app UI with good example data.
- [ ] The shot was captured AT the window's aspect ratio (app reflowed to fit) — the WHOLE app shows,
NOTHING cropped (no sliced-off columns/panels). No `object-fit:cover` on a mismatched grab.
- [ ] Before rendering, the Step-0.5 content table was shown and the author confirmed the TEXT
(esp. the title = the wiki page title).
- [ ] Left text column has a clear gutter from the window — NOTHING overlaps.
- [ ] Brand fonts actually rendered (not a fallback sans); on-brand colors + gradient.
- [ ] Rendered at 2× (3200×2000), downscaled to 2000×1250, crisp.
- [ ] `page.json` hero.path set (versioned filename if replacing); README does NOT re-embed the
hero; 3 other distinct inline shots present. Anon image 200 + rendered page references it.
---
## 9. Variants — look & feel along two axes (offered in Step 0)
Same content, same rules — a variant is a **skin**, never a new set of rules. Pick one **LAYOUT** and
one **THEME**; they compose. All are generated by [`build-variants.js`](build-variants.js)
(`node build-variants.js <layout> <theme>`), which inlines the bundled wordmark, fonts, and Kickstand
tiles so the render is deterministic. Everything derives from the **Kickstand brand guide**, bundled at
[`assets/Adom-Brand-Guidelines.pdf`](assets/Adom-Brand-Guidelines.pdf).
### Axis 1 — LAYOUT (window treatment + text side)
| Layout (`key`) | Window | Text | When |
|---|---|---|---|
| **Bleed bottom** (`bottom`, default) | high-right, runs off the **bottom**, rounded top | left | The family default; most apps. |
| **Bleed right** (`bleedRight`) | tall panel, runs off the **right edge**, rounded left | left | Wide/landscape UIs; gives a strong "wall of app." |
| **Contained** (`contained`) | framed **fully inside**, margins top/right/bottom, rounded all | left | When the whole window matters; calmest, most product-shot-like. |
| **Mirror** (`mirror`) | bleeds bottom, on the **left**; text on the **right** | right | Variety in a rotation; or when the shot enters better from the left. |
| **Full-bleed photo** (`photo`) | a real product/device PHOTO full-bleeds & **dominates** one side; inner edge masked/scrimmed into the bg | opposite side | Device/chip/hardware photos (NOT app UIs); bold and editorial. Pair with persona/feature pills in the text column. |
> **Mix it up.** With 5 layouts × 5 themes = **25 distinct looks**, plus your headline, accent,
> mark/chip and the real shot, every hero should feel its own. Rotating the layout/theme per app
> is REQUIRED, not optional — a wiki where every card is the same layout is a failure of this skill.
### Axis 2 — THEME (background + Kickstand texture)
| Theme (`key`) | Surface | Texture | Feel / when |
|---|---|---|---|
| **Midnight** (`midnight`, default) | near-black `#0a0e14→#071318` + teal/purple glows | faint dot-grid | The family default; neutral, lets the UI pop. |
| **PCB Traces** (`pcb`) | deep teal `#003C3F` | `pcb-trace` circuit field (~16%) | Electronic/"ethereal"; great for EDA/hardware apps. |
| **Onion** (`onion`) | navy `#00204F` | `onion-corner` concentric arcs (bottom-right) | The signature deck "substance" slide; solid + serious. |
| **Pill Field** (`pill`) | navy `#00204F` | `pill-field` tile | Softer, friendlier solid; good for utilities. |
| **Gradient** (`gradient`) | brand `#00b8b1→#0061ef→#8c6bf7` + left scrim | none (color is the star) | The "wow"/cover-slide; flagship & launch heroes. |
> Deck principle (from the Kickstand brand-deck system): **gradient = emotional · solids = substance.**
> Use Gradient for the splashy launch hero; solids (PCB/Onion/Pill) for substance; Midnight as the safe default.
### Kickstand tiling textures (bundled in `assets/tiling/`)
Four repeatable `currentColor` SVGs that "play off PCB/electronics in an ethereal way." They tint to
any surface — place a **lighter tint** of the surface color at **6–22% opacity**, faded out behind the
text so they never compete with copy (`build-variants.js` does this via a left-fade mask):
```
pcb-trace.svg — circuit traces + pads (the headline texture) tile ~150–200px
onion-corner.svg— concentric "onion" arcs, anchor bottom-right place ~60% no-repeat
dot-grid.svg — fine dot grid tile ~20–26px
pill-field.svg — scattered rounded pills tile ~60px
```
### Brand palette (Kickstand guide, p.11) — for inventing your own theme
```
Gradient: linear-gradient(110deg,#00b8b1 0%,#0061ef 48%,#8c6bf7 100%) (teal→blue→purple)
Core: Teal #00B8B1 Blue #0061EF Purple #8C6BF7
Lights: #64ABFF (blue) #8FD3D4 (teal) #C5B3FF (purple)
Darks: Black #191919 Navy #00204F Teal #003C3F Purple #4323AD
```
To add a layout or theme: add an entry to `LAYOUTS` or `THEMES` in `build-variants.js`, then list it in
the Step-0 AskUserQuestion. Keep the non-negotiable rules (§1) intact.
### Bundled brand assets (see also §3.1)
```
assets/
Adom-Brand-Guidelines.pdf # the full Kickstand guide (palette, patterns, type, logo rules)
adom-white.svg # the ADOM wordmark
fonts/*.woff2 # Familjen Grotesk 700, Satoshi 400/500
tiling/*.svg # pcb-trace, onion-corner, dot-grid, pill-field (currentColor)
```