name: making-the-hero description: "DEV skill: the COMPLETE recipe for the nb-bridge hero image - build_nb_hero.py in this folder renders the whole 2000x1250 from a fresh real-nb-window capture, then hero-studio full-import stamps provenance at the human gate. Read this BEFORE touching the hero. Encodes every rule John set shipping v10 (2026-08-09) and every trap: the studio's .window wrapper, font-metric lies, occlusion-blank captures, the title-vs-headline split. Sibling recipe: pup-bridge dev-skills/pup-hero-image (we share its design constants)."

Parent skill: adom-native-browser (maintainers). Publish flow: publishing-the-bridge.

The nb-bridge hero: build, gate, apply

Shipped as hero-v10 after ~6 hours of iteration with John at the gate. EVERYTHING below was paid for; do not re-derive it.

The pipeline (the only path that works)

# 1. a REAL nb window - never pup (pup is the sibling product; John rejected a pup window hard)
adom-bridge-cli --target <box> --ai-thread "<you>" nbrowser_wake_profile '{"profile":"chrome:[email protected]"}'
adom-bridge-cli ... nbrowser_open_window '{"sessionId":"nbhero","thread":"<you>","purpose":"hero capture",
  "profile":"chrome:[email protected]","url":"https://wiki.adom.inc/blob/app/nb-bridge/demo/nb.html"}'
adom-bridge-cli ... nbrowser_taskbar '{"sessionId":"nbhero","overlay":{"badge":"adom"}}'   # the nb signature
adom-bridge-cli ... nbrowser_window_state '{"sessionId":"nbhero","width":900,"height":772}' # taller aspect -> vitrine fills pup's height
adom-bridge-cli ... desktop_screenshot_window '{"hwnd":<from open>}'                        # RAW - keep the pink debugging banner!

# 2. build the full 2000x1250 (fonts/logo/taskbar-strip ship in assets/ - reproducible from a bare clone)
python3 dev-skills/making-the-hero/build_nb_hero.py --window <that capture> --out /tmp/hero_candidate.png

# 3. human gate: full-import (NEVER the studio's native vitrine slot - see traps)
HERO_STUDIO_PORT=<free> hero-studio pose adom/nb-bridge --type bridge --port <free> &   # --surface pup is broken (#31); open the gate in pup yourself
hero-studio vitrine --file /tmp/hero_candidate.png --full --port <port>
hero-studio set overlay.pill=false overlay.slug=false --port <port>                     # our pill is baked; studio double-stamps otherwise
# open the proxy URL in a pup window for John, maximize ONCE, flash - never foreground uninvited
# poll GET /state .approved; a timed-out pose process does NOT kill the server

# 4. apply (order matters)
cp <generated png> screenshots/hero.png; cp <generated png> screenshots/hero-vN.png
adom-wiki repo push adom/nb-bridge --files screenshots/hero.png screenshots/hero-vN.png -m "..."
adom-wiki page hero adom/nb-bridge --image screenshots/hero.png     # works for us (pup's skill says broken for them - path layout differs)
adom-wiki repo push adom/nb-bridge --files page.json -m "..."       # LAST - pkg publish clobbers title+hero
# verify: hero_provenance=="studio" AND sha256(served blob) == sha256(generated) - then cache-busted refresh in John's pup window

John's rules (each cost a rejected round - all verbatim-enforced in the build script)

  1. Real nb window, never pup. The vitrine window is opened by nbrowser_* in a real signed-in profile. The Work-profile pill + avatar + the pink "Adom Native Browser Extension started debugging this browser" banner ARE the product story - do not splice, retouch, or re-composite the capture. Raw pixels only. (Every "cleanup" I did made it read faker.)
  2. No window-in-a-window. The window floats directly on the hero background with only a shadow. Corollary: never put the capture through the studio's vitrine slot (see traps).
  3. Taskbar = a small band, not the user's real full bar. "A couple chrome icons and a couple edge icons" + the BADGED nb icon (user photo + Adom mark) underlined as active. 68px band, 54px tiles, left-aligned - pup's exact band spec. NOT Start/Search/pinned-app zoo (rejected), NOT a stylized floating pod (rejected), and nothing may be "cut off" below a taskbar.
  4. Family geometry is law - taken from pup's dev-skills/pup-hero-image (they measured Bridge's hero): vitrine y=140 w=1062 h=1050, RIGHT EDGE = PILL RIGHT EDGE = W-52; pill Familjen-700 26px pad 22x12 fill (0,206,194) inset 52; logo 236px at (90,64); bg #0d1117 + teal glow. kicad + fusion match. Do not eyeball; the constants are in the build script.
  5. Title: hero headline is the short form "Native Browser" (page stays "NB - Native Browser Bridge" - the studio has ONE field for both, #33). Two lines, leading 0.92em ("tighter, one message"), as large as fits the column (200px; family 240px is for one-word names). Subtitle 54px below (pup's offset; 36px was "a tinge close"), accent #4aa8ff on "Your real".
  6. Alignment is measured, not trusted. Familjen reports NO left side bearing via PIL.textbbox, so metric compensation silently no-ops and the title sat 13px off the margin - John caught it from a zoomed phone crop after the linter said CLEAN. build_nb_hero.py renders each line to scratch, finds the actual first ink column, and offsets so every line's ink starts at exactly x=90. Verify after any font/text change: scan ink-left per line on the output PNG.

Traps (the pain, so you skip it)

  • The studio's vitrine slot CANNOT show a browser window honestly: every vitrine gets a .window wrapper (border, radius 9, shadow, glow, OPAQUE bg - alpha killed). --chrome none removes only the fake titlebar. Full-import is the only clean path (#34).
  • Occlusion-blank captures: a background Chrome stops painting - desktop_screenshot_window returns the frame with BLACK content. Detect by max-saturation scan; nudge a paint with nbrowser_screenshot (CDP) or recapture while it draws. pup's Chrome ships anti-occlusion flags; the user's real Chrome does NOT.
  • Screenshots cap at 1568px wide -> taskbar icons are ~24px native. Height-normalize each icon crop at TRUE aspect (squares smoosh them - John caught that too) and upscale ~2x with UnsharpMask. Icon crops in assets/taskbar_strip.png: chrome+profile 499-523, chrome 697-728, edge 576-606, badged-nb 899-934 (y 20-63).
  • The studio is a stale snapshot (#33): its header/PAGE FIELDS title is a pose-time local record, NOT the live wiki title. Never click "Apply changes to wiki" - it renames the real page to the hero headline. Refresh a stale brief with POST /api/pages.
  • Full-import leaves studio overlays armed - set overlay.pill=false or you ship two pills.
  • The subtitle renders only the FIRST SENTENCE of brief - one sentence, commas.
  • --type bridge works for the BRIDGE pill even though state.enums.mode only lists app/component (#27/#34).
  • Screen captures for taskbar material: crop the strip immediately and DELETE the full capture unviewed - John's screen carries sensitive threads. The bar's true top is ~20px into a bottom-6.5% crop (row-profile to find it).
  • The full hero-studio grievance ledger from this campaign: #28, #33, #34 (+ #25/#26/#27 and kicad's #29-#32). Read them before proposing studio-side fixes.