eleven / ocho Public

Clone
git clone https://amadan.net/eleven/ocho

Plain git — no account needed to clone.

Download

Download this file

πŸ€– Marketing screenshots β€” native Mac + iPhone captures for the hero collage

How we capture the native Mac and iPhone screenshots that sit in the marketing site's three-device hero collage (alongside a web frame). This is the committed, hand-operable form of a capture recipe that previously lived only in a throwaway branch and agent memory β€” the "outlive-AI" rule in action: anyone, human or agent, can stand the demo up and reproduce the shots from this doc plus tools/marketing-shots/, with no special knowledge encoded elsewhere.

The whole flow is fictional and local. Nothing here ever points an app at a real or production instance, and the Mac app is built in an isolated identity so it can only ever show the seeded demo space β€” never anyone's real signed-in spaces (see Isolation below β€” it is the load-bearing safety property).

The tool stands up the demo and points you here; the interactive captures themselves stay manual on purpose (a person frames and takes each shot).

Quick start (./tools/dev shots)

./tools/dev shots up          # Terminal 1 β€” build the isolated demo Mac app (if
                              # needed) + run a throwaway local server seeded with
                              # the fictional "The Commons" space. Foreground:
                              # prints the local URL + a localhost sign-in link;
                              # Ctrl-C stops it.
./tools/dev shots tunnel      # Terminal 2 β€” give the local server a real public
                              # https try-*.11relay.com address (via `lchat try
                              # --local`) + single-use Mac & iPhone sign-in links.
./tools/dev shots mac         # Launch the isolated "Eleven Demo" Mac app.
./tools/dev shots web-check shot.png   # Headless 2x screenshot of the seeded
                              # space, to eyeball the look before capturing native.

Implementation lives in tools/marketing-shots/ (serve.sh, tunnel.sh, seed-team.mjs, demo-lib.mjs, device-link.mjs, web-check.mjs) β€” the shots verbs are thin wrappers, mirroring how ./tools/dev up wraps tools/lchat-dev-up.mjs. Runtime artifacts (the demo DB, state, PNGs) are gitignored.

The demo space: "The Commons"

A fictional civic-tech team space that matches the web hero frame (website/src/assets/hero/team-web.png). It's seeded through the real E2EE protocol (demo-lib.mjs provisions each member via an admin key-invite β†’ claim β†’ pubkey, then encrypts every thread/message client-side exactly as the browser does) β€” the server only ever holds ciphertext, so there is no plaintext shortcut. Six threads: The commons 🌱 (the flagship β€” the exact web-frame conversation: Aoife / Marcus / Devi, a reply, and reactions), Standup, Shipping πŸš€, Field notes πŸ““, Roadmap, Watercooler β˜•. You sign in as Cass (your own messages are the accent bubbles on the right, matching the web frame's reply).

Eyeball it any time without a native build:

./tools/dev shots web-check /tmp/commons.png && open /tmp/commons.png

Isolation β€” why this is safe (load-bearing)

A normal Mac build reads the real account. Both LChatMac.entitlements and LChatMac-Dev.entitlements carry keychain-access-groups = $(AppIdentifierPrefix)com.elevenmessenger.messenger.shared, and a dev alt (./tools/dev alt) shares that same group on purpose β€” an alt is the real account, not a sandbox, and has grabbed Paul's private space before. That is exactly what we must NOT do for a marketing screenshot.

So the demo Mac build is signed with ios/LChatMac/LChatMac-Demo.entitlements β€” minimal by design (only app-sandbox, network.client, and files.user-selected.read-write), with the shared keychain group (and the app group, push, and Sparkle exceptions) removed. No shared keychain group β†’ the build has its own empty keychain and can only show the demo space you add. It also gets a distinct bundle id, com.elevenmessenger.messenger.mac.capdemo (its own sandbox container + Launch Services identity, so it runs beside the real app), and the display name "Eleven Demo". The real purple AppIcon is kept on purpose so the screenshot shows the real brand mark.

Crucially, none of this touches project.yml. ./tools/dev shots applies the demo identity purely as xcodebuild command-line build-setting overrides (CODE_SIGN_ENTITLEMENTS=<abs demo path>, PRODUCT_BUNDLE_IDENTIFIER=…mac.capdemo, LCHAT_MAC_DISPLAY_NAME=Eleven Demo), which outrank the target/config values in the generated project. A plain ./tools/dev build mac (or xcodegen generate) therefore still resolves the normal entitlements (Release β†’ LChatMac.entitlements, Debug β†’ LChatMac-Dev.entitlements) and the real com.elevenmessenger.messenger.mac bundle id β€” the released app is never at risk.

Verify the isolation on the built app any time β€” no output means isolated:

codesign -d --entitlements :- \
  "ios/build-demo/Build/Products/Debug/Eleven Demo.app" | grep -i keychain
# β†’ (no output) = no keychain-access-groups = isolated, own empty keychain

Capturing the Mac shot

  1. ./tools/dev shots up (Terminal 1), then ./tools/dev shots tunnel (Terminal 2) for a real https origin the native apps can reach.
  2. ./tools/dev shots mac to launch "Eleven Demo". In the app: Spaces β–Έ Add from link… and paste the Mac link shots tunnel printed (signs you in as Cass). If a "Keep your access safe / Sign in" card appears, dismiss it with "Remind me later".
  3. Leave the Mac on its default Classic light theme (light background, blue accent bubbles) β€” that is what hero-mac.png uses and what the collage wants for the natives; do not switch it to Lit Room (see Theme pairing). Open The commons 🌱 to mirror the web frame, or Standup for variety (it has an own-bubble + a reaction).
  4. Capture full-screen, then crop with PIL β€” the robust path. Interactive screencapture -w (click a window) and System Events window-bounds both rely on Accessibility / Quartz (pyobjc), which isn't granted on the build Mac and is unreliable when driven headlessly. Full-screen + crop needs none of that:

``sh screencapture -o ~/Desktop/team-mac-raw.png # -o = no window shadow; full screen, 2x retina ``

Crop the app-window region to the collage aspect (hero-mac.png is 1300Γ—919, aspect β‰ˆ 1.415) and scale to 1300Γ—919:

``python from PIL import Image im = Image.open("team-mac-raw.png") # full-screen 2x capture # Pick a crop box (left, top, right, bottom) around the window, keeping # (right-left)/(bottom-top) β‰ˆ 1.415 so nothing is squashed on the resize. box = (LEFT, TOP, RIGHT, BOTTOM) im.crop(box).resize((1300, 919), Image.LANCZOS).save("team-mac.png") ``

Capturing the iPhone shot β€” REAL DEVICE ONLY

> ⚠️ Do NOT use the iOS Simulator. The iOS 26.3.1 simulator renders emoji as > tofu (β–‘) β€” confirmed 2026-07-28, both in-app and in Safari. Use a real iPhone.

  1. ./tools/dev shots tunnel prints an iPhone link. Open it on the device (paste into Safari β†’ Open in the app / join) β€” it signs the phone in as Cass. The phone is the real app, so it'll show this demo space alongside any real spaces; scroll to / open "The Commons" for the shot.
  2. Land on the space's thread list (the frame in hero-ios.png: title + the six thread rows with previews).
  3. Screenshot on the device (Volume-Up + Side button) and AirDrop it to the Mac. An iPhone 16 Pro shot is 1206Γ—2622 (aspect β‰ˆ 0.460).

Reusing the existing bezel frame

The website's hero-ios.png (760Γ—1554, RGBA, transparent surround) already has the Apple bezel baked in. Rather than re-render a device frame, drop the new screenshot into that same bezel. Its screen rect is (41, 39) with size 678Γ—1476 (aspect β‰ˆ 0.459 β€” the raw 1206Γ—2622 shot's 0.460 is a near-perfect match, so scaling barely distorts), corners rounded at r β‰ˆ 92px. PIL only (numpy is not installed on the build Mac):

from PIL import Image, ImageDraw

BEZEL = "hero-ios.png"        # 760Γ—1554, Apple bezel baked in, transparent surround
RAW   = "team-ios-raw.png"    # on-device screenshot (e.g. 1206Γ—2622, iPhone 16 Pro)
OUT   = "team-ios.png"

X, Y, W, H, R = 41, 39, 678, 1476, 92          # screen rect + corner radius in the bezel

frame = Image.open(BEZEL).convert("RGBA")
shot  = Image.open(RAW).convert("RGBA").resize((W, H), Image.LANCZOS)

# Rounded-rect mask so the shot's corners tuck under the bezel's screen cutout.
mask = Image.new("L", (W, H), 0)
ImageDraw.Draw(mask).rounded_rectangle([0, 0, W, H], radius=R, fill=255)

frame.paste(shot, (X, Y), mask)
frame.save(OUT)

You may read website/src/assets/hero/hero-ios.png as the reference bezel, but the composite output belongs in the website repo β€” do not modify the website repo from a messenger-repo change.

Theme pairing

The collage deliberately pairs a warm web frame (Lit Room cream) with lighter natives (Classic light / blue). So only the web frame is Lit Room; both natives stay on the default Classic light (blue accent) β€” the Mac app's out-of-the-box theme, matching hero-mac.png and the iPhone. Don't switch the Mac to Lit Room, or all three frames read as the same cream wash.

Where the outputs go

The finished frames live in the website repo (not this one):

  • website/src/assets/hero/team-web.png, team-mac.png, team-ios.png β€” wired into the homepage .collage in website/src/index.njk.
  • The original hero-web.png / hero-mac.png / hero-ios.png family (a household space) is used on /families/.

Committing and deploying the website is a separate, website-repo concern (its own deploy path) β€” out of scope for this kit, which only produces the raw + framed images.

Known cosmetic gaps (not seed bugs)

  • "6 members Β· N online": the header's online count reflects who is really connected (you), so it reads "6 members" or "6 members Β· 1 online", not the web frame's mocked "3 online". Faking more would need extra live connections β€” not worth it for a still.
  • Thread-list times all read the same recent minute (the server stamps created_at at seed time). In-thread times match the web ("Today 9:12 AM") because those come from the encrypted payload.

Teardown

  • Ctrl-C the tunnel (Terminal 2) to retire the public address, then Ctrl-C the server (Terminal 1). The demo DB is wiped on the next shots up anyway.
  • Quit "Eleven Demo".
  • Optional: rm -rf ios/build-demo and the gitignored tools/marketing-shots/.demo* runtime files when the collage is done.