diff --git a/docs/CHROME_WEB_STORE.md b/docs/CHROME_WEB_STORE.md
index 6e5b96b..966d671 100644
--- a/docs/CHROME_WEB_STORE.md
+++ b/docs/CHROME_WEB_STORE.md
@@ -101,21 +101,30 @@ repo must be public for it to resolve).
## Graphic assets
The **Graphic assets** section of the listing form. Ready-made, branded assets
-live in [`packages/extension/store-assets/`](../packages/extension/store-assets)
-(regenerate with `python3 packages/extension/store-assets/generate.py`).
+live in [`packages/extension/store-assets/`](../packages/extension/store-assets).
+All of them are HTML in `store-assets/screens/` (the popup screenshot frames
+the real `popup.css`); re-render with `store-assets/screens/render.sh`.
| Slot | Required | Size | Format | Use |
|---|---|---|---|---|
| **Store icon** | ✅ | 128×128 | PNG | `packages/extension/icons/icon-128.png` |
-| **Screenshots** (≤5) | ✅ (≥1) | 1280×800 or 640×400 | JPEG / 24-bit PNG, no alpha | `store-assets/screenshot-1280x800.png` |
+| **Screenshots** (≤5) | ✅ (≥1) | 1280×800 or 640×400 | JPEG / 24-bit PNG, no alpha | `store-assets/screenshot-{1..5}-1280x800.png`, uploaded in order |
| **Small promo tile** | optional | 440×280 | JPEG / 24-bit PNG, no alpha | `store-assets/small-tile-440x280.png` |
| **Marquee promo tile** | optional | 1400×560 | JPEG / 24-bit PNG, no alpha | `store-assets/marquee-1400x560.png` |
| Global promo video | optional | — | YouTube URL | skip |
-The generated screenshot is a **branded hero** — enough to submit. For a
-stronger listing, add real captures too (up to 5): the toolbar popover in its
-connected state, and an agent driving a page. Capture at exactly 1280×800 (or
-640×400) with no alpha channel.
+The five screenshots, in upload order:
+
+1. **Hero**: an agent driving a signed-in page, refs on the elements, Chrome's
+ debugging banner showing.
+2. **Site permissions**: the popup, connected, with deny / read / full rules.
+3. **`reins do`**: one command for a whole task, with the benchmark medians.
+4. **Debugging**: console errors and a failing request, read from the shell.
+5. **Local-only**: agent → daemon on 127.0.0.1 → extension, and the facts.
+
+Terminal output in them follows the CLI's real formats; if a formatter
+changes, update the matching slide. Slide 3's figures come from
+`packages/web/src/routes/docs/benchmarks.tsx`.
## Store listing description (paste-ready)
diff --git a/packages/extension/store-assets/generate.py b/packages/extension/store-assets/generate.py
deleted file mode 100644
index b2195ed..0000000
--- a/packages/extension/store-assets/generate.py
+++ /dev/null
@@ -1,157 +0,0 @@
-#!/usr/bin/env python3
-"""Generate reins Chrome Web Store graphic assets (exact sizes, 24-bit, no alpha).
-
-Run: python3 generate.py (needs Pillow; macOS system fonts)
-Outputs PNGs next to this file. Regenerate whenever the branding changes.
-"""
-import os
-from PIL import Image, ImageDraw, ImageFont
-
-HERE = os.path.dirname(os.path.abspath(__file__))
-C0 = (124, 131, 255) # #7C83FF
-C1 = (109, 40, 217) # #6D28D9
-WHITE = (255, 255, 255)
-INK = (14, 16, 32)
-
-FONT_CANDIDATES = [
- "/System/Library/Fonts/Helvetica.ttc",
- "/System/Library/Fonts/HelveticaNeue.ttc",
- "/System/Library/Fonts/Supplemental/Arial.ttf",
-]
-MONO_CANDIDATES = [
- "/System/Library/Fonts/Menlo.ttc",
- "/System/Library/Fonts/SFNSMono.ttf",
- "/System/Library/Fonts/Courier.ttc",
-]
-
-
-def font(size, bold=False, mono=False):
- cands = MONO_CANDIDATES if mono else FONT_CANDIDATES
- for path in cands:
- if os.path.exists(path):
- try:
- # .ttc index 1 is often the bold/variant face; fall back to 0.
- return ImageFont.truetype(path, size, index=1 if bold and path.endswith(".ttc") else 0)
- except Exception:
- try:
- return ImageFont.truetype(path, size)
- except Exception:
- continue
- return ImageFont.load_default()
-
-
-def gradient(w, h):
- """Diagonal gradient C0 (top-left) → C1 (bottom-right)."""
- img = Image.new("RGB", (w, h))
- px = img.load()
- denom = float(w + h - 2) or 1.0
- # Precompute per-diagonal color for speed.
- row = [None] * (w + h)
- for s in range(w + h):
- t = s / denom
- row[s] = (
- int(C0[0] + (C1[0] - C0[0]) * t),
- int(C0[1] + (C1[1] - C0[1]) * t),
- int(C0[2] + (C1[2] - C0[2]) * t),
- )
- for y in range(h):
- for x in range(w):
- px[x, y] = row[x + y]
- return img
-
-
-def cubic(p0, p1, p2, p3, n=48):
- pts = []
- for i in range(n + 1):
- t = i / n
- u = 1 - t
- x = u * u * u * p0[0] + 3 * u * u * t * p1[0] + 3 * u * t * t * p2[0] + t * t * t * p3[0]
- y = u * u * u * p0[1] + 3 * u * u * t * p1[1] + 3 * u * t * t * p2[1] + t * t * t * p3[1]
- pts.append((x, y))
- return pts
-
-
-def draw_mark(img, x, y, size):
- """The reins logo mark: white rounded tile + two grips + connecting spline."""
- d = ImageDraw.Draw(img)
- s = size
- d.rounded_rectangle([x, y, x + s, y + s], radius=int(s * 0.24), fill=WHITE)
-
- def P(fx, fy):
- return (x + fx * s, y + fy * s)
-
- curve = cubic(P(0.33, 0.33), P(0.33, 0.62), P(0.67, 0.40), P(0.67, 0.67))
- d.line(curve, fill=C1, width=max(3, int(s * 0.095)), joint="curve")
- r = s * 0.11
- for cx, cy in (P(0.33, 0.33), P(0.67, 0.67)):
- d.ellipse([cx - r, cy - r, cx + r, cy + r], fill=C1)
-
-
-def text(d, xy, s, fnt, fill=WHITE, anchor="la"):
- d.text(xy, s, font=fnt, fill=fill, anchor=anchor)
-
-
-def marquee():
- w, h = 1400, 560
- img = gradient(w, h)
- draw_mark(img, 150, 170, 220)
- d = ImageDraw.Draw(img)
- text(d, (440, 130), "reins", font(150, bold=True))
- text(d, (446, 300), "Drive your real browser from your coding agent.", font(40, bold=True))
- text(d, (446, 362), "npm i -g @karnstack/reins", font(30, mono=True), fill=(235, 236, 247))
- img.save(os.path.join(HERE, "marquee-1400x560.png"))
-
-
-def small_tile():
- w, h = 440, 280
- img = gradient(w, h)
- draw_mark(img, 40, 42, 96)
- d = ImageDraw.Draw(img)
- text(d, (158, 52), "reins", font(58, bold=True))
- text(d, (40, 176), "Drive your real browser", font(27, bold=True))
- text(d, (40, 212), "from your coding agent.", font(27, bold=True))
- img.save(os.path.join(HERE, "small-tile-440x280.png"))
-
-
-def screenshot():
- w, h = 1280, 800
- img = gradient(w, h)
- draw_mark(img, 120, 96, 132)
- d = ImageDraw.Draw(img)
- text(d, (284, 92), "reins", font(96, bold=True))
- text(d, (290, 190), "Drive your real, logged-in browser from your coding agent.", font(33, bold=True))
-
- # terminal card
- d.rounded_rectangle([120, 300, 1160, 704], radius=20, fill=INK)
- for i, cx in enumerate((150, 178, 206)):
- col = [(255, 95, 87), (254, 188, 46), (40, 200, 64)][i]
- d.ellipse([cx - 8, 26 + 300, cx + 8, 42 + 300], fill=col)
- mono = font(25, mono=True)
- green = (155, 231, 166)
- fg = (237, 238, 247)
- muted = (138, 143, 176)
- lines = [
- (green, "$ ", fg, "reins tabs"),
- (muted, " b1 tab 12 * Docs — https://example.com", None, None),
- (green, "$ ", fg, "reins snapshot --tab 12"),
- (muted, ' e5: button "Submit"', None, None),
- (green, "$ ", fg, "reins click --ref e5"),
- (muted, " ok", None, None),
- ]
- y = 380
- for c0, t0, c1, t1 in lines:
- d.text((150, y), t0, font=mono, fill=c0)
- if t1:
- w0 = d.textlength(t0, font=mono)
- d.text((150 + w0, y), t1, font=mono, fill=c1)
- y += 50
- text(d, (120, 748), "Local-only · tabs · click · type · screenshot · console & network · raw CDP",
- font(24, bold=True), fill=(235, 236, 247))
- img.save(os.path.join(HERE, "screenshot-1280x800.png"))
-
-
-if __name__ == "__main__":
- marquee()
- small_tile()
- screenshot()
- print("generated marquee / small-tile / screenshot PNGs")
diff --git a/packages/extension/store-assets/marquee-1400x560.png b/packages/extension/store-assets/marquee-1400x560.png
index 4174dc8..8ab3826 100644
Binary files a/packages/extension/store-assets/marquee-1400x560.png and b/packages/extension/store-assets/marquee-1400x560.png differ
diff --git a/packages/extension/store-assets/screens/1-hero.html b/packages/extension/store-assets/screens/1-hero.html
new file mode 100644
index 0000000..d80c3a2
--- /dev/null
+++ b/packages/extension/store-assets/screens/1-hero.html
@@ -0,0 +1,103 @@
+
+
+
+
+
+
+
+
+
+
+
# Drive the browser you're signed in to
+
Your coding agent gets your real Chrome, logins and all. No debug profile, no launch flags, no signing in again.
+
+
+
+
+
+
Pull requests
+
agent
+
Invoices · Acme
+
Stripe export
+
+
+
"reins" started debugging this browserCancel
+
+
+
Acme
+
Overview
+
Customers
+
Invoices
+
Payouts
+
Settings
+
+
+
Invoices maya@acme.dev
+
+
e9 Search invoices
+
Filter
+
e7 Export CSV
+
+
+
+ Invoice Customer Amount Status
+ e12 INV-2041Northwind $4,200.00 Paid
+ INV-2040 Globex $1,180.00 Due
+ INV-2039 Initech $9,640.00 Paid
+ INV-2038 Umbrella $720.00 Paid
+ INV-2037 Hooli $3,015.00 Due
+
+
+
+
+
+
+
claude — ~/acme
+
$ reins snapshot
+e7: button "Export CSV"
+e9: textbox "Search invoices"
+e12: link "INV-2041"
+$ reins click --ref e7
+ok
+$ reins text
+Export ready: invoices-2026-09.csv
+$
+
+
+
+
+
diff --git a/packages/extension/store-assets/screens/2-popup.html b/packages/extension/store-assets/screens/2-popup.html
new file mode 100644
index 0000000..be73993
--- /dev/null
+++ b/packages/extension/store-assets/screens/2-popup.html
@@ -0,0 +1,56 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
# You decide what it can touch
+
Every site resolves to one tier, checked inside the extension before a command reaches the tab. Granting more takes a click here. An agent in your shell can't make one.
+
+ deny Nothing. The site's tabs are hidden from the agent.
+ read Snapshot, text, screenshot. No clicks, no typing.
+ full Everything, including eval and raw CDP.
+
+
+
+
+
+
+
diff --git a/packages/extension/store-assets/screens/3-do.html b/packages/extension/store-assets/screens/3-do.html
new file mode 100644
index 0000000..e322a32
--- /dev/null
+++ b/packages/extension/store-assets/screens/3-do.html
@@ -0,0 +1,51 @@
+
+
+
+
+
+
+
+
+
+
+
# Hand off the whole taskNEW
+
reins do gives a browsing goal to Jev, TypeSafe's action model. Jev picks each click in about 0.2 s. Your agent supplies the values and checks the result.
+
+
+
+
claude — ~/app
+
$ reins do 'find the package and open it' --fill query=zod
+done in 3.5s · 3 steps · 4 jev calls · 16k tokens · self-check 0.94
+ 1 click "Search packages"
+ 2 type "Search packages" ← query
+ 3 click "zod"
+now: https://www.npmjs.com/package/zod — "zod - npm"
+next: reins snapshot # verify before trusting DONE
+$ reins snapshot
+e2: link "zod"
+e5: button "Copy install command"
+$
+
+
+
+
4.6× faster, median
+
~100× cheaper, median
+
+
+
+
+
+
+
diff --git a/packages/extension/store-assets/screens/4-debug.html b/packages/extension/store-assets/screens/4-debug.html
new file mode 100644
index 0000000..fcc1341
--- /dev/null
+++ b/packages/extension/store-assets/screens/4-debug.html
@@ -0,0 +1,77 @@
+
+
+
+
+
+
+
+
+
+
+
# Console and network, no DevTools
+
Your agent reads the page's errors and failed requests on its own, then goes and fixes the code that caused them.
+
+
+
+
+
+
Checkout · Acme Shop
+
Orders · Admin
+
+
+
"reins" started debugging this browserCancel
+
+
Acme Shop Cart (3)
+
Checkout
+
+
Trail runner ×1 $128.00
+
Wool socks ×2 $36.00
+
Shipping $6.00
+
Total $NaN
+
Pay now
+
+
+
Something went wrong placing your order. Please try again.
+
+
+
+
claude — ~/shop
+
$ reins console --level error
+[error] TypeError: Cannot read properties of undefined (reading 'price')
+[error] checkout failed: 500 Internal Server Error
+$ reins network --url /api
+GET http://localhost:5173/api/cart -> 200
+GET http://localhost:5173/api/shipping -> 200
+POST http://localhost:5173/api/checkout -> 500
+$ reins eval 'window.__cart.items.length'
+3
+$
+
+
+
+
+
diff --git a/packages/extension/store-assets/screens/5-local.html b/packages/extension/store-assets/screens/5-local.html
new file mode 100644
index 0000000..0d386e7
--- /dev/null
+++ b/packages/extension/store-assets/screens/5-local.html
@@ -0,0 +1,52 @@
+
+
+
+
+
+
+
+
+
+
+
# Everything runs on your machine
+
The CLI, a daemon it starts on demand, and this extension talk over 127.0.0.1. Chrome shows its debugging banner the whole time, and Disconnect cuts it off.
+
+
+
your machine
+
+
your agent reins <cmd> Claude Code, Cursor, Codex…
+
+
daemon 127.0.0.1:8765 starts on demand
+
+
this extension your browserchrome.debugger → tab
+
+
+
hosted service None
+
account None
+
telemetry None. No analytics, no remote code
+
license MIT, open source
+
browsers Chrome, Brave, Edge, Arc, Dia
+
install the CLI npm i -g @karnstack/reins
+
+
+
+
+
diff --git a/packages/extension/store-assets/screens/logo.svg b/packages/extension/store-assets/screens/logo.svg
new file mode 100644
index 0000000..5279747
--- /dev/null
+++ b/packages/extension/store-assets/screens/logo.svg
@@ -0,0 +1 @@
+
diff --git a/packages/extension/store-assets/screens/popup-frame.html b/packages/extension/store-assets/screens/popup-frame.html
new file mode 100644
index 0000000..6cfbfe5
--- /dev/null
+++ b/packages/extension/store-assets/screens/popup-frame.html
@@ -0,0 +1,83 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Daemon v0.6.0 · 127.0.0.1:8765
+
This browser b1 (Chrome)
+
+
+ Disconnect
+
+
+ Jev
+
+
+
+ Ready
+ ••••7f3a
+
+
+ Replace
+ Remove
+
+
+
+
+ Site permissions
+
+
+ mybank.com
+
+ Full
+ Read
+ Deny
+
+
+
+
Default for other sites
+
Full ▾
+
+
+
+ mybank.comdeny ×
+ mail.google.comread ×
+ *.github.comfull ×
+ localhostfull ×
+
+
+
+
+ Advanced
+
+
+
diff --git a/packages/extension/store-assets/screens/render.sh b/packages/extension/store-assets/screens/render.sh
new file mode 100755
index 0000000..f18e996
--- /dev/null
+++ b/packages/extension/store-assets/screens/render.sh
@@ -0,0 +1,34 @@
+#!/usr/bin/env bash
+# Render the store graphics to ../*.png at their exact sizes, as 24-bit PNGs
+# with no alpha (the Chrome Web Store rejects alpha): the five 1280×800
+# screenshots ([0-9]-*.html) and the two promo tiles (tile-*.html). Needs
+# chrome-headless-shell (Playwright installs one: npx playwright install
+# chromium-headless-shell) and python3 with Pillow. Full Chrome's --headless
+# can hang on exit on macOS; the shell does not.
+set -euo pipefail
+cd "$(dirname "$0")"
+CHROME="${CHROME:-$(ls -d "$HOME"/Library/Caches/ms-playwright/chromium_headless_shell-*/chrome-headless-shell-*/chrome-headless-shell 2>/dev/null | tail -1)}"
+[ -x "$CHROME" ] || { echo "chrome-headless-shell not found; set CHROME=" >&2; exit 1; }
+TMP="$(mktemp -d)"
+trap 'rm -rf "$TMP"' EXIT
+
+# Pass files to render a subset: ./render.sh 2-popup.html tile-small.html
+if [ $# -eq 0 ]; then set -- [0-9]-*.html tile-*.html; fi
+for f in "$@"; do
+ case "$f" in
+ tile-small.html) out="small-tile-440x280.png" w=440 h=280 ;;
+ tile-marquee.html) out="marquee-1400x560.png" w=1400 h=560 ;;
+ [0-9]-*.html) out="screenshot-${f%%-*}-1280x800.png" w=1280 h=800 ;;
+ *) echo "unknown slide: $f" >&2; exit 1 ;;
+ esac
+ "$CHROME" --hide-scrollbars --force-device-scale-factor=1 --window-size="$w,$h" --virtual-time-budget=4000 \
+ --screenshot="$TMP/out.png" "file://$PWD/$f" >/dev/null 2>&1
+ python3 - "$TMP/out.png" "../$out" "$w" "$h" <<'PY'
+import sys
+from PIL import Image
+im = Image.open(sys.argv[1]).convert("RGB")
+assert im.size == (int(sys.argv[3]), int(sys.argv[4])), im.size
+im.save(sys.argv[2], optimize=True)
+PY
+ echo "$out"
+done
diff --git a/packages/extension/store-assets/screens/shared.css b/packages/extension/store-assets/screens/shared.css
new file mode 100644
index 0000000..2ab807f
--- /dev/null
+++ b/packages/extension/store-assets/screens/shared.css
@@ -0,0 +1,418 @@
+/* Chrome Web Store screenshots, 1280×800. Same language as reins.tech: the
+ words are mono and square, the drawings of real UI (browser, popup) are
+ sans and keep their corners. Render with ./render.sh. */
+@import url("https://fonts.googleapis.com/css2?family=Geist+Mono:wght@400..700&family=Inter:wght@400..700&display=block");
+
+:root {
+ --bg: #ffffff;
+ --ink: #18181b;
+ --ink-2: #3f3f46;
+ --muted: #71717a;
+ --faint: #a1a1aa;
+ --line: rgb(24 24 27 / 0.1);
+ --line-strong: rgb(24 24 27 / 0.18);
+ --fill: rgb(24 24 27 / 0.04);
+ --primary: #7c3aed;
+ --primary-ink: #6d28d9;
+ --primary-tint: rgb(124 58 237 / 0.08);
+ --term-bg: #16161a;
+ --term-ink: #e4e4e7;
+ --term-muted: #8b8b95;
+ --term-accent: #a78bfa;
+ --ok: #16a34a;
+ --err: #dc2626;
+ --mono: "Geist Mono", ui-monospace, "SF Mono", Menlo, monospace;
+ --sans: "Inter", ui-sans-serif, system-ui, -apple-system, sans-serif;
+}
+
+* {
+ box-sizing: border-box;
+}
+
+html,
+body {
+ margin: 0;
+ width: 1280px;
+ height: 800px;
+ overflow: hidden;
+ background: var(--bg);
+ color: var(--ink);
+ font-family: var(--mono);
+ -webkit-font-smoothing: antialiased;
+}
+
+.slide {
+ position: relative;
+ width: 1280px;
+ height: 800px;
+ overflow: hidden;
+ padding: 56px 72px 0;
+ /* the faint grid the eye reads as paper, not decoration */
+ background-image:
+ linear-gradient(var(--line) 1px, transparent 1px),
+ linear-gradient(90deg, var(--line) 1px, transparent 1px);
+ background-size: 32px 32px;
+ background-position: -1px -1px;
+}
+
+.slide::before {
+ /* fade the grid out under the caption so the words sit on white */
+ content: "";
+ position: absolute;
+ inset: 0;
+ background: linear-gradient(
+ 180deg,
+ #fff 0%,
+ #fff 30%,
+ rgb(255 255 255 / 0.55) 60%,
+ rgb(255 255 255 / 0.2) 100%
+ );
+ pointer-events: none;
+}
+
+.slide > * {
+ position: relative;
+}
+
+/* ---- caption --------------------------------------------------------- */
+
+.meta {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ font-size: 15px;
+ color: var(--muted);
+ letter-spacing: 0.02em;
+}
+
+.brand {
+ display: flex;
+ align-items: center;
+ gap: 10px;
+ color: var(--ink);
+ font-weight: 600;
+ font-size: 17px;
+}
+
+.brand svg {
+ width: 26px;
+ height: 26px;
+}
+
+.headline {
+ margin: 34px 0 0;
+ font-size: 40px;
+ line-height: 1.15;
+ font-weight: 650;
+ letter-spacing: 0.01em;
+ text-transform: uppercase;
+ text-wrap: balance;
+}
+
+.headline .hash {
+ color: var(--primary);
+ margin-right: 14px;
+}
+
+.sub {
+ margin: 16px 0 0;
+ max-width: 64ch;
+ font-size: 19px;
+ line-height: 1.5;
+ color: var(--ink-2);
+ border-left: 3px solid var(--primary);
+ padding-left: 16px;
+}
+
+.sub code {
+ color: var(--primary-ink);
+ font-weight: 600;
+}
+
+/* ---- terminal ---------------------------------------------------------- */
+
+.term {
+ background: var(--term-bg);
+ color: var(--term-ink);
+ border-radius: 12px;
+ box-shadow:
+ 0 0 0 1px rgb(0 0 0 / 0.6),
+ 0 30px 60px -20px rgb(24 24 27 / 0.45),
+ 0 12px 24px -12px rgb(24 24 27 / 0.3);
+ font-family: var(--mono);
+ overflow: hidden;
+}
+
+.term__bar {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ padding: 12px 16px;
+ border-bottom: 1px solid rgb(255 255 255 / 0.08);
+ font-size: 12px;
+ color: var(--term-muted);
+}
+
+.term__bar i {
+ width: 11px;
+ height: 11px;
+ border-radius: 50%;
+ background: #3f3f46;
+}
+
+.term__bar span {
+ margin-left: 10px;
+}
+
+.term pre {
+ margin: 0;
+ padding: 18px 22px 22px;
+ font-family: inherit;
+ font-size: 16px;
+ line-height: 1.75;
+ white-space: pre;
+}
+
+.term .p {
+ color: var(--term-accent);
+}
+.term .c {
+ color: #fff;
+ font-weight: 500;
+}
+.term .o {
+ color: var(--term-muted);
+}
+.term .ok {
+ color: #4ade80;
+}
+.term .er {
+ color: #f87171;
+}
+.term .hl {
+ color: #fde68a;
+}
+.term .caret {
+ display: inline-block;
+ width: 9px;
+ height: 18px;
+ vertical-align: -3px;
+ background: var(--term-accent);
+}
+
+/* ---- browser window ------------------------------------------------------ */
+
+.browser {
+ font-family: var(--sans);
+ background: #fff;
+ border-radius: 12px;
+ box-shadow:
+ 0 0 0 1px var(--line-strong),
+ 0 40px 80px -30px rgb(24 24 27 / 0.35),
+ 0 16px 32px -16px rgb(24 24 27 / 0.2);
+ overflow: hidden;
+}
+
+.tabs {
+ display: flex;
+ align-items: flex-end;
+ gap: 2px;
+ height: 42px;
+ padding: 0 12px;
+ background: #dfe1e5;
+}
+
+.tabs .lights {
+ display: flex;
+ gap: 8px;
+ align-self: center;
+ margin: 0 14px 0 4px;
+}
+
+.tabs .lights i {
+ width: 12px;
+ height: 12px;
+ border-radius: 50%;
+}
+.tabs .lights i:nth-child(1) {
+ background: #ff5f57;
+}
+.tabs .lights i:nth-child(2) {
+ background: #febc2e;
+}
+.tabs .lights i:nth-child(3) {
+ background: #28c840;
+}
+
+.tab {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ height: 34px;
+ width: 200px;
+ padding: 0 12px;
+ font-size: 12.5px;
+ color: #3c4043;
+ border-radius: 8px 8px 0 0;
+ white-space: nowrap;
+ overflow: hidden;
+}
+
+.tab--active {
+ background: #fff;
+}
+
+.tab .fav {
+ width: 14px;
+ height: 14px;
+ border-radius: 3px;
+ flex: none;
+}
+
+.group-chip {
+ align-self: center;
+ margin: 0 2px 0 6px;
+ padding: 3px 9px;
+ border-radius: 6px;
+ font-size: 12px;
+ font-weight: 600;
+ color: #fff;
+ background: #8b5cf6;
+}
+
+.group-line {
+ box-shadow: inset 0 -2px 0 #8b5cf6;
+}
+
+.toolbar {
+ display: flex;
+ align-items: center;
+ gap: 12px;
+ height: 44px;
+ padding: 0 14px;
+ border-bottom: 1px solid #e5e7eb;
+}
+
+.toolbar .nav {
+ display: flex;
+ gap: 14px;
+ color: #5f6368;
+ font-size: 15px;
+}
+
+.omnibox {
+ flex: 1;
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ height: 30px;
+ padding: 0 14px;
+ border-radius: 15px;
+ background: #f1f3f4;
+ font-size: 13.5px;
+ color: #202124;
+}
+
+.omnibox .lock {
+ color: #5f6368;
+ font-size: 12px;
+}
+
+.omnibox .host {
+ color: #202124;
+}
+.omnibox .path {
+ color: #5f6368;
+}
+
+.ext {
+ display: grid;
+ place-items: center;
+ width: 28px;
+ height: 28px;
+ border-radius: 50%;
+}
+
+.ext svg {
+ width: 20px;
+ height: 20px;
+}
+
+.ext--on {
+ background: #ede9fe;
+}
+
+.ext .badge {
+ position: absolute;
+}
+
+/* Chrome's own infobar while an extension is attached with chrome.debugger */
+.debugbar {
+ display: flex;
+ align-items: center;
+ gap: 10px;
+ height: 38px;
+ padding: 0 16px;
+ font-size: 13px;
+ color: #202124;
+ background: #fff;
+ border-bottom: 1px solid #e5e7eb;
+}
+
+.debugbar .icon {
+ width: 16px;
+ height: 16px;
+ border-radius: 50%;
+ border: 2px solid #1a73e8;
+ position: relative;
+}
+
+.debugbar .icon::after {
+ content: "";
+ position: absolute;
+ inset: 3px;
+ border-radius: 50%;
+ background: #1a73e8;
+}
+
+.debugbar .cancel {
+ margin-left: auto;
+ padding: 5px 14px;
+ border: 1px solid #dadce0;
+ border-radius: 6px;
+ color: #1a73e8;
+ font-weight: 500;
+}
+
+/* ref chips: what `reins snapshot` calls each element */
+.ref {
+ position: absolute;
+ z-index: 3;
+ padding: 3px 6px;
+ font-family: var(--mono);
+ font-size: 11.5px;
+ font-weight: 600;
+ line-height: 1;
+ color: #fff;
+ background: var(--primary);
+ border-radius: 3px;
+ box-shadow: 0 2px 6px rgb(124 58 237 / 0.35);
+}
+
+.ref--dark {
+ background: var(--ink);
+ box-shadow: 0 2px 6px rgb(0 0 0 / 0.3);
+}
+
+.outlined {
+ outline: 2px solid var(--primary);
+ outline-offset: 2px;
+}
+
+.cursor {
+ position: absolute;
+ z-index: 4;
+ width: 22px;
+ height: 22px;
+ filter: drop-shadow(0 2px 3px rgb(0 0 0 / 0.3));
+}
diff --git a/packages/extension/store-assets/screens/tile-marquee.html b/packages/extension/store-assets/screens/tile-marquee.html
new file mode 100644
index 0000000..d71d36b
--- /dev/null
+++ b/packages/extension/store-assets/screens/tile-marquee.html
@@ -0,0 +1,39 @@
+
+
+
+
+
+
+
+
+
+
+
+
reins
+
# Drive the browser you're signed in to
+
$ npm i -g @karnstack/reins
+
+
+
claude — ~/acme
+
$ reins snapshot
+e7: button "Export CSV"
+e9: textbox "Search invoices"
+$ reins click --ref e7
+ok
+$ reins do 'open the latest invoice'
+done in 2.9s · 2 steps · 3 jev calls · 9k tokens
+$
+
+
+
+
diff --git a/packages/extension/store-assets/screens/tile-small.html b/packages/extension/store-assets/screens/tile-small.html
new file mode 100644
index 0000000..9683a29
--- /dev/null
+++ b/packages/extension/store-assets/screens/tile-small.html
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+
+
+
+
+
reins
+
Drive your real browser from your coding agent .
+
$ reins click --ref e7
+
+
+
diff --git a/packages/extension/store-assets/screens/tile.css b/packages/extension/store-assets/screens/tile.css
new file mode 100644
index 0000000..c7b7cd2
--- /dev/null
+++ b/packages/extension/store-assets/screens/tile.css
@@ -0,0 +1,46 @@
+/* Promo tiles. The store shows them on white, often small, so unlike the
+ screenshots they sit on the terminal's dark ground. */
+html,
+body {
+ background: var(--term-bg);
+}
+
+.tile {
+ position: relative;
+ overflow: hidden;
+ color: #fff;
+ background-color: var(--term-bg);
+ background-image:
+ radial-gradient(120% 90% at 100% 100%, rgb(124 58 237 / 0.45), transparent 60%),
+ linear-gradient(rgb(255 255 255 / 0.05) 1px, transparent 1px),
+ linear-gradient(90deg, rgb(255 255 255 / 0.05) 1px, transparent 1px);
+ background-size:
+ auto,
+ 24px 24px,
+ 24px 24px;
+}
+
+.tile > * {
+ position: relative;
+}
+
+.tile .brand {
+ color: #fff;
+}
+
+:where(.tile p) {
+ margin: 0;
+}
+
+.tile em {
+ font-style: normal;
+ color: var(--term-accent);
+}
+
+.tile .cmd {
+ color: var(--term-muted);
+}
+
+.tile .p {
+ color: var(--term-accent);
+}
diff --git a/packages/extension/store-assets/screenshot-1-1280x800.png b/packages/extension/store-assets/screenshot-1-1280x800.png
new file mode 100644
index 0000000..fc8349e
Binary files /dev/null and b/packages/extension/store-assets/screenshot-1-1280x800.png differ
diff --git a/packages/extension/store-assets/screenshot-1280x800.png b/packages/extension/store-assets/screenshot-1280x800.png
deleted file mode 100644
index 5ade67a..0000000
Binary files a/packages/extension/store-assets/screenshot-1280x800.png and /dev/null differ
diff --git a/packages/extension/store-assets/screenshot-2-1280x800.png b/packages/extension/store-assets/screenshot-2-1280x800.png
new file mode 100644
index 0000000..14f9995
Binary files /dev/null and b/packages/extension/store-assets/screenshot-2-1280x800.png differ
diff --git a/packages/extension/store-assets/screenshot-3-1280x800.png b/packages/extension/store-assets/screenshot-3-1280x800.png
new file mode 100644
index 0000000..8ba4ec4
Binary files /dev/null and b/packages/extension/store-assets/screenshot-3-1280x800.png differ
diff --git a/packages/extension/store-assets/screenshot-4-1280x800.png b/packages/extension/store-assets/screenshot-4-1280x800.png
new file mode 100644
index 0000000..b3e5261
Binary files /dev/null and b/packages/extension/store-assets/screenshot-4-1280x800.png differ
diff --git a/packages/extension/store-assets/screenshot-5-1280x800.png b/packages/extension/store-assets/screenshot-5-1280x800.png
new file mode 100644
index 0000000..5857a26
Binary files /dev/null and b/packages/extension/store-assets/screenshot-5-1280x800.png differ
diff --git a/packages/extension/store-assets/small-tile-440x280.png b/packages/extension/store-assets/small-tile-440x280.png
index ca31d73..21e9b70 100644
Binary files a/packages/extension/store-assets/small-tile-440x280.png and b/packages/extension/store-assets/small-tile-440x280.png differ