← Commit history

Title bar: add OS-window app icon (top-left) to fix iconless/fake-looking title bars

John Lauer ·c3154ebc6f ·4mo ago ·parent 730a5cb
3 files changed +841
SKILL.mdadded+610
@@ -0,0 +1,610 @@+---+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)++**Pre-flight (do this before you write a single word into the hero):**+1. **Lock the EXACT name** — read it verbatim from the page's `page.json` `title` / the slug. Do not+   use a conversational nickname. (See Rule #4 — wrong names are pixel-baked and reused everywhere.)+2. **Pick the headline mode** — default **value-prop-led** (value phrase = the big headline, name a+   smaller line) unless the name itself is the pitch. The eye must land on what it DOES.+3. **Draft the "just ask Claude" prompt around THIS app's trigger words** and sanity-check it+   wouldn't route to a sibling tool (see Rule #15).++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.++---++## 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.+   - **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.++   **The eye must land on the VALUE PROP, not the name — so DEFAULT to tagline-led** whenever the+   name is generic/descriptive ("Browser Extension", "Parts Search", "Desktop Bridge"). Make the+   **value-prop phrase the LARGEST element** (the 54–72px headline) and the **app NAME a smaller+   secondary line** (~24–28px) beneath it. The viewer reads *what it does* first, *what it's called*+   second. Reserve name-led (name = the big headline) for when the name itself is the pitch. A hero+   whose biggest text is a name nobody recognizes yet wastes the one-second read.++   **Use the app's EXACT canonical name — never invent or paraphrase one.** Copy it verbatim from the+   page's `page.json` `title` (or the slug). A conversational/descriptive label you've been using+   ("Adom Native Browser") is NOT the name if the title/slug says otherwise ("Adom Browser+   Extension"). The name is **pixel-baked into the image** and reused on the page header, grid card,+   screensaver, installer, and social cards — a wrong name there is far costlier than a wrong README+   line. Cross-check the rendered headline/name against that exact string in the §7.5 pass.+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).++   **The title bar MUST have a small APP ICON at the top-LEFT.** A real OS window always shows the+   app's icon to the left of the title — without it the bar looks fake/unfinished (a repeated,+   rejected failure). Use the **Adom logomark** (the two teal blobs from `assets/adom-white.svg`,+   ~16px, recolored `#00e6dc`) — or the app's own mark — as a `.cic` span before the `.ct` title.+   The bundled `hero.html` / `build-variants.js` already bake this in; if you hand-roll a title bar,+   add the icon. Order, left→right: **[icon] [title] … [min] [max] [close]**.+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.+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. **All text on ONE side; the product shot full-bleeds and DOMINATES the opposite side.** Keep+    every text element (wordmark, chip, headline, value-prop, pills, prompt, URL) in a single+    column on one side — the left by default. The image owns the OTHER side: it **full-bleeds top+    to bottom and off the outer edge**, large and confident, with its **inner edge masked/scrimmed+    so it melts into the dark text side**. NEVER split text across both sides of the image, wrap+    copy around it, or drop any text/URL/CTA onto the image's side. (This is what fixes the endless+    margin-collision churn: separate text and image spatially and there's nothing left to collide.)+    A floating "webview window" shot (for app UIs) is one valid form of this; a full-bleed product+    photo is another — either way, text on one side, image on the other.+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.)++    **The prompt MUST route to THIS app, not a neighbour.** Before you ship it, ask: *if a user+    pasted this exact prompt, would it trigger this page's tool — or a different skill?* A generic+    outcome ("fetch the STM32 CAD into my KiCad library") routes to whatever owns that outcome+    (chip-fetcher → pup), NOT to a native-browser extension. Phrase it around THIS app's distinctive+    action/trigger words ("load my native browser and log into Mouser"). A prompt that demos a+    sibling tool is worse than no prompt — it misattributes the value.++---++## 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 | `object-fit:cover; object-position:top left` (keep title bar + canvas; side panels bleed off) |++**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;object-fit:cover;object-position:top left}+</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">&#10005;</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. 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. Pick the money shot (a 3D board render, a populated PCB). `object-fit:cover`+  top-left keeps the title bar + main canvas; let side panels bleed off.+- **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 **full app name** is the headline; 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 with an+      **app icon top-LEFT** (Adom logomark) and 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.+- [ ] 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. |++### 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)+```
build-variants.jsadded+166
@@ -0,0 +1,166 @@+// build-variants.js — generate adom-wiki-hero-image heroes across two axes: LAYOUT × THEME.+// Usage:+//   node build-variants.js                      -> writes the curated showcase set (see COMBOS)+//   node build-variants.js <layout> <theme>     -> one hero, e.g. "bleedRight pcb"+//   node build-variants.js name <comboName>     -> one named combo from COMBOS+// LAYOUTS: bottom | mirror | bleedRight | contained+// THEMES:  midnight | pcb | onion | pill | gradient+// Brand assets (fonts, wordmark, Kickstand tiles) are bundled under assets/, referenced relatively.+const fs = require('fs');+const path = require('path');+const DIR = __dirname;++const WORDMARK = fs.readFileSync(path.join(DIR, 'assets', 'adom-white.svg'), 'utf8')+  .replace(/<\?xml[^>]*\?>/, '').replace(/<style>[\s\S]*?<\/style>/, '').replace(/class="cls-1"/g, 'fill="#fff"');++// recolor a currentColor tile SVG and return a CSS background data-URI+function tile(name, color) {+  const svg = fs.readFileSync(path.join(DIR, 'assets', 'tiling', name), 'utf8').replace(/currentColor/g, color);+  return `url("data:image/svg+xml;base64,${Buffer.from(svg).toString('base64')}")`;+}++// ---- content (the skill's own hero) ----+const C = {+  headline: 'Every app deserves<br>a <span class="accent">billboard.</span>',+  sub: 'Render a dark-mode, on-brand <b>hero</b> for any Adom wiki page — the <b>one-second billboard</b> the installer, homepage, screensaver and socials all reuse. One name, one screenshot, zero clutter.',+  chip: 'Wiki Skill', prompt: 'make a hero for my app',+  url: 'wiki.adom.inc/adom/adom-wiki-hero-image',+  winlabel: 'adom-parts-search · hero.png · Adom webview',+  shot: 'file:///home/adom/project/adom-parts-search/screenshots/hero.png',+};++// ---- THEMES: background + texture overlay + text colors ----+const THEMES = {+  midnight: {+    bg: `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%),+         linear-gradient(155deg,#0a0e14 0%,#0d1117 45%,#071318 100%)`,+    tex: `background:${tile('dot-grid.svg', '#ffffff')} 0 0/24px repeat;opacity:.05;+          -webkit-mask-image:linear-gradient(115deg,transparent 0%,#000 48%);mask-image:linear-gradient(115deg,transparent 0%,#000 48%)`,+    text: '#e6edf3', sub: '#aeb8c4', chipB: 'rgba(0,184,177,.34)', chipF: 'rgba(0,184,177,.07)', chipT: '#7fe3dd', accent: true,+  },+  // PCB traces — Kickstand "ethereal circuit field" on deep brand teal+  pcb: {+    bg: `radial-gradient(1000px 760px at 86% -5%, rgba(0,184,177,.28), transparent 60%),+         linear-gradient(160deg,#022a2b 0%,#003c3f 46%,#021a1c 100%)`,+    tex: `background:${tile('pcb-trace.svg', '#8FD3D4')} center/170px repeat;opacity:.16;+          -webkit-mask-image:linear-gradient(115deg,transparent 6%,#000 52%);mask-image:linear-gradient(115deg,transparent 6%,#000 52%)`,+    text: '#eafdfb', sub: '#a9d6d2', chipB: 'rgba(143,211,212,.40)', chipF: 'rgba(0,184,177,.10)', chipT: '#8fd3d4', accent: true,+  },+  // Onion corner — signature concentric-arc motif on solid navy (deck "substance" slide)+  onion: {+    bg: `radial-gradient(900px 700px at 80% -10%, rgba(100,171,255,.16), transparent 60%),+         linear-gradient(160deg,#021a36 0%,#00204f 50%,#01122b 100%)`,+    tex: `background:${tile('onion-corner.svg', '#8FD3D4')} right bottom/62% no-repeat;opacity:.5`,+    text: '#eaf2ff', sub: '#a9c0e0', chipB: 'rgba(100,171,255,.40)', chipF: 'rgba(100,171,255,.10)', chipT: '#9fc4ff', accent: true,+  },+  // Pill field on navy+  pill: {+    bg: `radial-gradient(900px 700px at 85% 0%, rgba(100,171,255,.18), transparent 60%),+         linear-gradient(160deg,#001a3f 0%,#00204f 50%,#001129 100%)`,+    tex: `background:${tile('pill-field.svg', '#64ABFF')} center/64px repeat;opacity:.5;+          -webkit-mask-image:linear-gradient(115deg,transparent 4%,#000 50%);mask-image:linear-gradient(115deg,transparent 4%,#000 50%)`,+    text: '#eaf2ff', sub: '#a9c0e0', chipB: 'rgba(100,171,255,.40)', chipF: 'rgba(100,171,255,.10)', chipT: '#9fc4ff', accent: true,+  },+  // Brand gradient — the "wow"/cover-slide look; left scrim keeps copy readable+  gradient: {+    bg: `linear-gradient(90deg, rgba(5,8,12,.82) 0%, rgba(5,8,12,.42) 42%, rgba(5,8,12,.08) 70%),+         radial-gradient(800px 600px at 90% 0%, rgba(255,255,255,.12), transparent 55%),+         linear-gradient(115deg,#00b8b1 0%,#0061ef 52%,#8c6bf7 100%)`,+    tex: ``,+    text: '#ffffff', sub: '#d7e2ee', chipB: 'rgba(255,255,255,.40)', chipF: 'rgba(255,255,255,.10)', chipT: '#ffffff', accent: true,+  },+};++// ---- LAYOUTS: window position/shape + text side ----+const LAYOUTS = {+  bottom:     { side: 'left',  win: 'right:72px;top:280px;width:806px;border-radius:16px 16px 0 0', imgH: 760 },+  mirror:     { side: 'right', win: 'left:72px;top:280px;width:806px;border-radius:16px 16px 0 0',  imgH: 760 },+  bleedRight: { side: 'left',  win: 'right:-74px;top:104px;width:900px;border-radius:16px 0 0 16px', imgH: 812 },+  contained:  { side: 'left',  win: 'right:80px;top:150px;width:752px;border-radius:16px',           imgH: 672 },+};++function html(L, T) {+  const left = L.side === 'left';+  return `<!DOCTYPE html><html><head><meta charset="utf-8">+<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')}+*{margin:0;padding:0;box-sizing:border-box}+html,body{width:1600px;height:1000px;overflow:hidden}+body{font-family:'Satoshi',sans-serif;color:${T.text};position:relative;background:${T.bg};}+.tex{position:absolute;inset:0;z-index:0;${T.tex}}+.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:44px;width:auto;display:block}+.chip{border:1px solid ${T.chipB};background:${T.chipF};border-radius:999px;padding:9px 17px;font-size:14px;+  letter-spacing:2.5px;text-transform:uppercase;color:${T.chipT};display:inline-flex;align-items:center;gap:9px;font-weight:600}+.chip .dot{width:8px;height:8px;border-radius:50%;background:#00e6dc;box-shadow:0 0 10px #00e6dc}+.col{max-width:520px;${left ? '' : 'margin-left:auto;text-align:right;display:flex;flex-direction:column;align-items:flex-end'}}+h1{font-family:'Familjen Grotesk',sans-serif;font-weight:700;font-size:70px;line-height:1.0;letter-spacing:-2px;color:${T.text};margin-top:48px;margin-bottom:24px}+h1 .accent{${T.accent ? 'background:linear-gradient(100deg,#00e6dc,#39b8ff 60%,#8c6bf7);-webkit-background-clip:text;background-clip:text;color:transparent' : 'color:' + T.text}}+.sub{font-size:23px;line-height:1.5;color:${T.sub};max-width:500px;margin-bottom:30px}+.sub b{color:${T.text};font-weight:500}+.prompt{display:inline-flex;align-items:center;gap:12px;background:rgba(255,255,255,.06);border:1px solid rgba(0,230,220,.30);+  border-radius:14px;padding:15px 20px;font-size:19px;color:#e3eaf2}+.prompt .q{color:#00e6dc;font-weight:700}.prompt .ask{color:#9aa6b5;font-size:15px;margin-left:6px}+.url{position:absolute;${left ? 'left:92px' : 'right:92px'};bottom:54px;z-index:3;color:#00e6dc;font-weight:600;font-size:19px;font-family:'Familjen Grotesk',sans-serif}+.window{position:absolute;${L.win};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)}+.chrome{height:42px;background:#1b2128;display:flex;align-items:center;padding-left:13px;border-bottom:1px solid #232a33}.chrome .cic{display:flex;align-items:center;margin-right:9px}.chrome .cic svg{display:block}+.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,sans-serif;font-size:18px;line-height:1}+.window img{width:100%;height:${L.imgH}px;display:block;object-fit:cover;object-position:top left}+</style></head><body>+  <div class="tex"></div>+  <div class="window">+    <div class="chrome"><span class="cic"><svg viewBox="0 0 236 236" width="16" height="16" style="display:block"><path fill="#00e6dc" d="M212.94,23.05c-25.84-25.84-62.87-30.69-82.73-10.84-8.06,8.06-12.04,18.94-12.21,30.76h0s0,.02,0,.02c0,.04,0,.08,0,.12v73.29c0,.89.72,1.61,1.61,1.61h72.5l-.02-.02c12.17.03,23.42-3.94,31.69-12.2,19.85-19.85,15-56.89-10.84-82.73Z"/><path fill="#00e6dc" d="M23.05,212.95c25.84,25.84,62.87,30.69,82.73,10.84,8.06-8.06,12.04-18.94,12.21-30.76h0s0-.02,0-.02c0-.04,0-.08,0-.12v-73.29c0-.89-.72-1.61-1.61-1.61H43.89l.02.02c-12.17-.03-23.42,3.94-31.69,12.2-19.85,19.85-15,56.89,10.84,82.73Z"/></svg></span><span class="ct">${C.winlabel}</span>+      <div class="winbtns"><span class="b min"><i></i></span><span class="b max"><i></i></span><span class="b cls">&#10005;</span></div></div>+    <img src="${C.shot}">+  </div>+  <div class="zone">+    <div class="top"><div class="wordmark">${WORDMARK}</div><div class="chip"><span class="dot"></span>${C.chip}</div></div>+    <div class="col">+      <h1>${C.headline}</h1>+      <div class="sub">${C.sub}</div>+      <div class="prompt"><span class="q">"${C.prompt}"</span><span class="ask">— just ask Claude</span></div>+    </div>+  </div>+  <div class="url">${C.url}</div>+</body></html>`;+}++// curated showcase set: name -> [layout, theme]+const COMBOS = {+  'midnight':           ['bottom', 'midnight'],+  'mirror':             ['mirror', 'midnight'],+  'pcb':                ['bottom', 'pcb'],+  'onion':              ['bottom', 'onion'],+  'pill':               ['bottom', 'pill'],+  'gradient':           ['bottom', 'gradient'],+  'bleed-right':        ['bleedRight', 'midnight'],+  'bleed-right-pcb':    ['bleedRight', 'pcb'],+  'contained':          ['contained', 'midnight'],+  'contained-gradient': ['contained', 'gradient'],+};++function write(name, layoutKey, themeKey) {+  const L = LAYOUTS[layoutKey], T = THEMES[themeKey];+  if (!L || !T) throw new Error('bad layout/theme: ' + layoutKey + '/' + themeKey);+  const out = path.join(DIR, `variant-${name}.html`);+  fs.writeFileSync(out, html(L, T));+  console.log('wrote', out, `(${layoutKey} × ${themeKey})`);+}++const a = process.argv.slice(2);+if (a.length === 0) {+  for (const [name, [l, t]] of Object.entries(COMBOS)) write(name, l, t);+} else if (a[0] === 'name') {+  const [l, t] = COMBOS[a[1]]; write(a[1], l, t);+} else {+  write(`${a[0]}-${a[1]}`, a[0], a[1]);+}
hero.htmladded+65
@@ -0,0 +1,65 @@+<!DOCTYPE html><html><head><meta charset="utf-8">+<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')}+*{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:44px;width:auto;display:block}+.chip{border:1px solid rgba(0,184,177,.34);background:rgba(0,184,177,.07);border-radius:999px;+  padding:9px 17px;font-size:14px;letter-spacing:2.5px;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;box-shadow:0 0 10px #00e6dc}+h1{font-family:'Familjen Grotesk',sans-serif;font-weight:700;font-size:70px;line-height:1.0;+  letter-spacing:-2px;color:#f4f8fb;margin-top:48px;margin-bottom:24px;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.5;color:#aeb8c4;max-width:500px;margin-bottom:30px}+.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:250px;width:806px;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)}+.chrome{height:42px;background:#1b2128;display:flex;align-items:center;padding-left:13px;border-bottom:1px solid #232a33}.chrome .cic{display:flex;align-items:center;margin-right:9px}.chrome .cic svg{display:block}+.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}+.window img{width:100%;height:760px;display:block;object-fit:cover;object-position:top left}+</style></head><body>+  <div class="dots"></div>+  <div class="window">+    <div class="chrome"><span class="cic"><svg viewBox="0 0 236 236" width="16" height="16" style="display:block"><path fill="#00e6dc" d="M212.94,23.05c-25.84-25.84-62.87-30.69-82.73-10.84-8.06,8.06-12.04,18.94-12.21,30.76h0s0,.02,0,.02c0,.04,0,.08,0,.12v73.29c0,.89.72,1.61,1.61,1.61h72.5l-.02-.02c12.17.03,23.42-3.94,31.69-12.2,19.85-19.85,15-56.89-10.84-82.73Z"/><path fill="#00e6dc" d="M23.05,212.95c25.84,25.84,62.87,30.69,82.73,10.84,8.06-8.06,12.04-18.94,12.21-30.76h0s0-.02,0-.02c0-.04,0-.08,0-.12v-73.29c0-.89-.72-1.61-1.61-1.61H43.89l.02.02c-12.17-.03-23.42,3.94-31.69,12.2-19.85,19.85-15,56.89,10.84,82.73Z"/></svg></span><span class="ct">adom-parts-search · hero.png · Adom webview</span>+      <div class="winbtns"><span class="b min"><i></i></span><span class="b max"><i></i></span><span class="b cls">&#10005;</span></div></div>+    <img src="file:///home/adom/project/adom-parts-search/screenshots/hero.png">+  </div>+  <div class="zone">+    <div class="top">+      <div class="wordmark"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1072.9 236"><path fill="#fff" d="M212.94,23.05c-25.84-25.84-62.87-30.69-82.73-10.84-8.06,8.06-12.04,18.94-12.21,30.76h0s0,.02,0,.02c0,.04,0,.08,0,.12v73.29c0,.89.72,1.61,1.61,1.61h72.5l-.02-.02c12.17.03,23.42-3.94,31.69-12.2,19.85-19.85,15-56.89-10.84-82.73Z"/><path fill="#fff" d="M23.05,212.95c25.84,25.84,62.87,30.69,82.73,10.84,8.06-8.06,12.04-18.94,12.21-30.76h0s0-.02,0-.02c0-.04,0-.08,0-.12v-73.29c0-.89-.72-1.61-1.61-1.61H43.89l.02.02c-12.17-.03-23.42,3.94-31.69,12.2-19.85,19.85-15,56.89,10.84,82.73Z"/><path fill="#fff" d="M1065.5,25.22h-58.49c-2.98,0-5.68,1.87-6.73,4.65l-44.4,117.54c-.06.17-.31.17-.37,0l-45.44-117.59c-1.07-2.75-3.76-4.6-6.72-4.6h-58.79c-4.09,0-7.4,3.31-7.4,7.4v35.06l-.03-.12c-1.24,4.99-5.89,6-5.89,6,0,0-4.51,1.02-7.61-2.53v.03c-17.82-29.81-50.31-48.93-89.04-48.93-41.5,0-75.86,21.85-92.76,55.23v-.07c-2.86,5.19-8.74,5.13-8.74,5.13,0,0-5.63-.01-8.41-4.61-16.49-32.68-50.56-52.59-94.79-52.59h-73.46c-4.09,0-7.4,3.31-7.4,7.4v121.27s0,.03,0,.04c-.95,5.15-4.75,5.94-4.75,5.94,0,0-3.88.85-6.77-3.32l.05.23-46.05-126.83c-1.03-2.84-3.75-4.74-6.77-4.74h-57.14c-3.01,0-5.73,1.9-6.76,4.73l-68.67,188.38c-1.05,2.88-.18,6.21,2.4,8.19,1.31,1.01,2.98,1.47,4.64,1.47h34.96s6.43,0,6.43,0c2.5,0,4.83-1.39,5.9-3.65,8.49-17.86,26.5-29.64,48.37-29.64s39.71,11.73,48.19,29.53c1.1,2.32,3.49,3.76,6.06,3.76h120.78c31.22,0,57.85-9.95,77.02-28.77,7.38-7.25,13.43-15.72,18.05-25.1v.09c2.74-5.19,8.37-5.13,8.37-5.13,0,0,5.43,0,8.08,4.64,4.88,9.88,11.26,18.78,18.91,26.42,18.6,18.6,44.61,29.78,74.26,29.78,38.74,0,71.23-19.18,89.05-49.07l-.02.07c3.1-3.55,7.61-2.53,7.61-2.53,0,0,4.65,1,5.89,6l.03-.12v36.32c0,4.09,3.31,7.4,7.4,7.4h45.79c4.09,0,7.4-3.31,7.4-7.4v-86.26c0-.23.32-.28.39-.06l30.37,88.86c1,2.91,3.74,4.87,6.81,4.87h39.69c3.08,0,5.82-1.96,6.81-4.87l30.1-88.02c.07-.21.39-.16.39.06v85.43c0,4.09,3.31,7.4,7.4,7.4h45.79c4.09,0,7.4-3.31,7.4-7.4V32.62c0-4.09-3.31-7.4-7.4-7.4ZM372.31,147.57c-.15.21-.5.57-1.1.57h-30.33c-.61,0-.95-.35-1.1-.56-.15-.21-.38-.65-.19-1.23l15.06-45.79c.27-.81.92-.94,1.29-.94h0c.37,0,1.02.12,1.29.93l15.26,45.8c.19.58-.04,1.01-.19,1.23ZM525.72,173.84h-14.52c-.86,0-1.56-.7-1.56-1.56v-92.16c0-.86.7-1.56,1.56-1.56h14.52c30.81,0,48.48,17.47,48.48,47.92s-17.67,47.36-48.48,47.36ZM734.6,173.26c-26.51,0-42.33-17.71-42.34-47.36,0-14.59,3.88-26.68,11.21-34.98,7.33-8.29,18.09-12.67,31.13-12.67,12.11,0,22.28,3.84,29.58,11.13.5.5.98,1.01,1.45,1.54,7.4,8.33,11.31,20.42,11.31,34.97,0,29.21-16.22,47.36-42.34,47.36Z"/></svg></div>+      <div class="chip"><span class="dot"></span>Wiki Skill</div>+    </div>+    <h1>Every app deserves<br>a <span class="accent">billboard.</span></h1>+    <div class="sub">Render a dark-mode, on-brand <b>hero</b> for any Adom wiki page — the <b>one-second billboard</b> the installer, homepage and screensaver all reuse. One name, one screenshot, zero clutter.</div>+    <div class="prompt"><span class="q">"make a hero for my app"</span><span class="ask">— just ask Claude</span></div>+  </div>+  <div class="url">wiki.adom.inc/adom/adom-wiki-hero-image</div>+</body></html>