afterbefore 0.2.0 → 0.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -3,88 +3,114 @@
3
3
  [![npm version](https://img.shields.io/npm/v/afterbefore?color=blue)](https://www.npmjs.com/package/afterbefore)
4
4
  [![npm downloads](https://img.shields.io/npm/dm/afterbefore)](https://www.npmjs.com/package/afterbefore)
5
5
 
6
- Automatic before/after screenshot capture for Next.js pull requests.
6
+ Before/after screenshot capture for Next.js development. A floating icon in your dev server — click to capture before, make your changes, click to capture after.
7
7
 
8
- ## The idea
8
+ No CLI. No Playwright. No second server. The browser is the tool.
9
9
 
10
- Other visual regression tools make you list which URLs to capture. afterbefore reads your git diff instead, builds an import graph, and figures out which routes were affected by your changes. You change a button component, it finds every page that uses that button, and captures those. No URL lists, no config.
11
-
12
- ## Quick start
13
-
14
- Run it from a feature branch in any Next.js app router project:
10
+ ## Setup
15
11
 
16
12
  ```bash
17
- npx afterbefore
13
+ npm install afterbefore
18
14
  ```
19
15
 
20
- It spins up two dev servers (your branch and the base branch) and captures screenshots of affected routes.
16
+ ### 1. Add the overlay to your root layout
17
+
18
+ ```tsx
19
+ // app/layout.tsx
20
+ import { AfterBefore } from "afterbefore/overlay"
21
+
22
+ export default function RootLayout({ children }) {
23
+ return (
24
+ <html>
25
+ <body>
26
+ {children}
27
+ {process.env.NODE_ENV === "development" && <AfterBefore />}
28
+ </body>
29
+ </html>
30
+ )
31
+ }
32
+ ```
21
33
 
22
- Output lands in `.afterbefore/`:
34
+ ### 2. Create the API route
23
35
 
36
+ ```ts
37
+ // app/api/afterbefore/[...path]/route.ts
38
+ export { GET, POST } from "afterbefore/server"
24
39
  ```
25
- .afterbefore/
26
- └── feature-branch_2026-02-26/
27
- ├── about-before.png
28
- └── about-after.png
40
+
41
+ ### 3. Wrap your Next.js config
42
+
43
+ ```ts
44
+ // next.config.ts
45
+ import { withAfterBefore } from "afterbefore/next"
46
+
47
+ export default withAfterBefore({
48
+ // your existing config
49
+ })
29
50
  ```
30
51
 
52
+ The config wrapper adds a dev-only rewrite from `/__afterbefore/*` to the API route. It does nothing in production.
53
+
54
+ > **Next.js 16+ / Turbopack:** If `withAfterBefore` fails to import (`ERR_PACKAGE_PATH_NOT_EXPORTED`), you can inline the rewrite as a workaround:
55
+ >
56
+ > ```ts
57
+ > // Workaround if withAfterBefore import fails with Turbopack:
58
+ > rewrites: process.env.NODE_ENV === "development"
59
+ > ? async () => [{ source: "/__afterbefore/:path*", destination: "/api/afterbefore/:path*" }]
60
+ > : undefined,
61
+ > ```
62
+
63
+
31
64
  ## How it works
32
65
 
33
- 1. Reads `git diff` to find changed files
34
- 2. Classifies each file (page, component, layout, style, utility, config)
35
- 3. Builds an import graph by parsing ES module imports across your codebase
36
- 4. Walks the graph backward from each changed file to find which `page.tsx` files are affected
37
- 5. Creates a git worktree for the base branch and runs `npm install`
38
- 6. Starts two Next.js dev servers in parallel (base branch + current branch) and captures full-page screenshots of each affected route using Playwright
39
-
40
- Layouts work the same way. Edit `app/dashboard/layout.tsx` and it captures every page under `app/dashboard/`.
41
-
42
- If only global files changed (like `globals.css` or `tailwind.config.ts`) and no specific routes were found, it captures all pages.
43
-
44
- ## Options
45
-
46
- | Flag | Default | Description |
47
- |------|---------|-------------|
48
- | `--base <ref>` | `main` | Base branch to compare against |
49
- | `--output <dir>` | `.afterbefore` | Output directory |
50
- | `--max-routes <count>` | `6` | Cap the number of routes captured (0 = unlimited) |
51
- | `--width <pixels>` | `1280` | Viewport width |
52
- | `--height <pixels>` | `720` | Viewport height |
53
- | `--delay <ms>` | `0` | Extra wait time after page load |
54
- | `--max-depth <n>` | `10` | Max import graph traversal depth |
55
- | `--dry-run` | `false` | Show affected routes without capturing |
56
- | `--verbose` | `false` | Show detailed import graph traversal |
57
-
58
- ## Configuration file
59
-
60
- For pages that need interaction before capture (opening a modal, clicking a dropdown), create an `afterbefore.config.json`:
61
-
62
- ```json
63
- {
64
- "scenarios": {
65
- "/design-system": [
66
- {
67
- "name": "components-tab",
68
- "actions": [
69
- { "click": ".tab-components" },
70
- { "wait": 300 }
71
- ]
72
- }
73
- ]
74
- }
75
- }
66
+ 1. A floating icon appears in the bottom-left corner of your dev server
67
+ 2. Click it and choose a capture mode the screenshot is saved as `before.png`
68
+ 3. Make your changes
69
+ 4. Click again the screenshot is saved as `after.png`
70
+ 5. Open the output folder, copy markdown for a PR comment, or push directly to your GitHub PR
71
+
72
+ Screenshots are captured using [html-to-image](https://github.com/nicorevin/html-to-image), which renders the DOM to a canvas. This captures exactly what you see — logged-in state, open modals, scroll position, form inputs — without needing a headless browser.
73
+
74
+ Screenshots land on your Desktop, named after your branch:
75
+
76
+ ```
77
+ ~/Desktop/
78
+ my-feature-before.png
79
+ my-feature-after.png
76
80
  ```
77
81
 
78
- Actions run in order before the screenshot is taken. Supported actions: `click` (CSS selector), `scroll` (CSS selector), `wait` (milliseconds).
82
+ ## Capture modes
83
+
84
+ | Mode | Behavior |
85
+ |------|----------|
86
+ | **Viewport** | Captures the visible area |
87
+ | **Full Page** | Captures the entire document height |
88
+ | **Select Area** | Crosshair cursor — drag a rectangle to capture a specific region |
89
+
90
+ ## PR integration
91
+
92
+ When both screenshots are captured, click the icon to:
93
+
94
+ - **Open Folder** — opens `.afterbefore/` in your file manager
95
+ - **Copy Markdown** — copies a before/after comparison table to your clipboard
96
+ - **Push to PR** — commits the screenshots, pushes, and posts a PR comment with the images (requires [GitHub CLI](https://cli.github.com/))
97
+ - **Reset** — start over
98
+
99
+ ## Known limitations
100
+
101
+ - Web fonts may render slightly differently than in the browser
102
+ - Complex CSS (`backdrop-filter`, `mix-blend-mode`) may not reproduce perfectly
103
+ - iframes and tainted canvas/WebGL content are not captured
104
+ - Next.js only (React overlay, API routes)
105
+
106
+ For pixel-perfect CI screenshots, see the roadmap for Phase 1 (Playwright-based CLI).
79
107
 
80
108
  ## Requirements
81
109
 
82
- - Next.js with the app router (`app/` directory)
83
- - Git
110
+ - Next.js >= 14 (app router)
111
+ - React >= 18
84
112
  - Node.js >= 18
85
113
 
86
- Playwright's Chromium browser is installed automatically on first run if it's missing.
87
-
88
114
  ## License
89
115
 
90
116
  Licensed under [PolyForm Shield 1.0.0](https://polyformproject.org/licenses/shield/1.0.0)
@@ -1,7 +1,17 @@
1
1
  // src/server/save.ts
2
2
  import { NextResponse } from "next/server";
3
- import { writeFile, mkdir } from "fs/promises";
3
+ import { writeFile } from "fs/promises";
4
4
  import { join } from "path";
5
+ import { homedir } from "os";
6
+ import { execFile } from "child_process";
7
+ import { promisify } from "util";
8
+ var execFileAsync = promisify(execFile);
9
+ async function getBranch() {
10
+ const { stdout } = await execFileAsync("git", ["rev-parse", "--abbrev-ref", "HEAD"], {
11
+ cwd: process.cwd()
12
+ });
13
+ return stdout.trim();
14
+ }
5
15
  var VALID_TYPES = ["before", "after"];
6
16
  var VALID_MODES = ["fullpage", "viewport", "area"];
7
17
  var DATA_URL_PREFIX = "data:image/png;base64,";
@@ -36,12 +46,10 @@ async function handleSave(req) {
36
46
  }
37
47
  const base64 = image.slice(DATA_URL_PREFIX.length);
38
48
  const buffer = Buffer.from(base64, "base64");
39
- const dir = join(process.cwd(), ".afterbefore");
40
- const filename = `${type}.png`;
41
- const filepath = join(dir, filename);
42
- const relativePath = `.afterbefore/${filename}`;
49
+ const branch = await getBranch();
50
+ const filename = `${branch}-${type}.png`;
51
+ const filepath = join(homedir(), "Desktop", filename);
43
52
  try {
44
- await mkdir(dir, { recursive: true });
45
53
  await writeFile(filepath, buffer);
46
54
  } catch (err) {
47
55
  return NextResponse.json(
@@ -49,19 +57,19 @@ async function handleSave(req) {
49
57
  { status: 500 }
50
58
  );
51
59
  }
52
- return NextResponse.json({ success: true, path: relativePath });
60
+ return NextResponse.json({ success: true, path: `~/Desktop/${filename}` });
53
61
  }
54
62
 
55
63
  // src/server/push.ts
56
64
  import { NextResponse as NextResponse2 } from "next/server";
57
- import { execFile } from "child_process";
58
- import { access } from "fs/promises";
65
+ import { execFile as execFile2 } from "child_process";
66
+ import { access, mkdir, copyFile } from "fs/promises";
59
67
  import { join as join2 } from "path";
60
- import { promisify } from "util";
61
- var execFileAsync = promisify(execFile);
62
- var DIR = ".afterbefore";
68
+ import { homedir as homedir2 } from "os";
69
+ import { promisify as promisify2 } from "util";
70
+ var execFileAsync2 = promisify2(execFile2);
63
71
  async function run(cmd, args) {
64
- return execFileAsync(cmd, args, { cwd: process.cwd() });
72
+ return execFileAsync2(cmd, args, { cwd: process.cwd() });
65
73
  }
66
74
  async function ghAvailable() {
67
75
  try {
@@ -106,25 +114,39 @@ async function handlePush(req) {
106
114
  { status: 404 }
107
115
  );
108
116
  }
109
- const dir = join2(process.cwd(), DIR);
110
- const beforePath = join2(dir, "before.png");
111
- const afterPath = join2(dir, "after.png");
117
+ const desktop = join2(homedir2(), "Desktop");
118
+ const branch = pr.headRefName;
119
+ const beforePath = join2(desktop, `${branch}-before.png`);
120
+ const afterPath = join2(desktop, `${branch}-after.png`);
112
121
  const [hasBefore, hasAfter] = await Promise.all([
113
122
  fileExists(beforePath),
114
123
  fileExists(afterPath)
115
124
  ]);
116
125
  if (!hasBefore || !hasAfter) {
117
126
  const missing = [
118
- !hasBefore && "before.png",
119
- !hasAfter && "after.png"
127
+ !hasBefore && `${branch}-before.png`,
128
+ !hasAfter && `${branch}-after.png`
120
129
  ].filter(Boolean);
121
130
  return NextResponse2.json(
122
131
  { success: false, error: `Missing screenshots: ${missing.join(", ")}` },
123
132
  { status: 400 }
124
133
  );
125
134
  }
135
+ const repoDir = join2(process.cwd(), ".afterbefore");
136
+ const repoBefore = join2(repoDir, `${branch}-before.png`);
137
+ const repoAfter = join2(repoDir, `${branch}-after.png`);
138
+ try {
139
+ await mkdir(repoDir, { recursive: true });
140
+ await copyFile(beforePath, repoBefore);
141
+ await copyFile(afterPath, repoAfter);
142
+ } catch (err) {
143
+ return NextResponse2.json(
144
+ { success: false, error: "Failed to copy screenshots into repo", detail: String(err) },
145
+ { status: 500 }
146
+ );
147
+ }
126
148
  try {
127
- await run("git", ["add", beforePath, afterPath]);
149
+ await run("git", ["add", repoBefore, repoAfter]);
128
150
  await run("git", [
129
151
  "commit",
130
152
  "-m",
@@ -149,10 +171,9 @@ async function handlePush(req) {
149
171
  }
150
172
  const owner = pr.headRepository.owner.login;
151
173
  const repo = pr.headRepository.name;
152
- const branch = pr.headRefName;
153
174
  const rawBase = `https://raw.githubusercontent.com/${owner}/${repo}/${branch}`;
154
- const beforeUrl = `${rawBase}/${DIR}/before.png`;
155
- const afterUrl = `${rawBase}/${DIR}/after.png`;
175
+ const beforeUrl = `${rawBase}/.afterbefore/${branch}-before.png`;
176
+ const afterUrl = `${rawBase}/.afterbefore/${branch}-after.png`;
156
177
  const ts = Date.now();
157
178
  const commentBody = [
158
179
  "## Before / After",
@@ -187,12 +208,13 @@ async function handlePush(req) {
187
208
 
188
209
  // src/server/open.ts
189
210
  import { NextResponse as NextResponse3 } from "next/server";
190
- import { exec } from "child_process";
211
+ import { execFile as execFile3 } from "child_process";
191
212
  import { join as join3 } from "path";
213
+ import { homedir as homedir3 } from "os";
192
214
  async function handleOpen(_req) {
193
- const dir = join3(process.cwd(), ".afterbefore");
215
+ const desktop = join3(homedir3(), "Desktop");
194
216
  const cmd = process.platform === "darwin" ? "open" : process.platform === "win32" ? "explorer" : "xdg-open";
195
- exec(`${cmd} "${dir}"`);
217
+ execFile3(cmd, [desktop]);
196
218
  return NextResponse3.json({ success: true });
197
219
  }
198
220
 
@@ -201,4 +223,4 @@ export {
201
223
  handlePush,
202
224
  handleOpen
203
225
  };
204
- //# sourceMappingURL=chunk-XRQ4MAEV.js.map
226
+ //# sourceMappingURL=chunk-DAWAW3ZL.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/server/save.ts","../src/server/push.ts","../src/server/open.ts"],"sourcesContent":["import { NextRequest, NextResponse } from \"next/server\";\nimport { writeFile } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport { homedir } from \"node:os\";\nimport { execFile } from \"node:child_process\";\nimport { promisify } from \"node:util\";\n\nconst execFileAsync = promisify(execFile);\n\nasync function getBranch(): Promise<string> {\n const { stdout } = await execFileAsync(\"git\", [\"rev-parse\", \"--abbrev-ref\", \"HEAD\"], {\n cwd: process.cwd(),\n });\n return stdout.trim();\n}\n\nconst VALID_TYPES = [\"before\", \"after\"] as const;\ntype ScreenshotType = (typeof VALID_TYPES)[number];\n\nconst VALID_MODES = [\"fullpage\", \"viewport\", \"area\"] as const;\n\nconst DATA_URL_PREFIX = \"data:image/png;base64,\";\n\ninterface SaveRequestBody {\n type: ScreenshotType;\n mode: string;\n image: string;\n}\n\nexport async function handleSave(req: NextRequest): Promise<NextResponse> {\n let body: SaveRequestBody;\n try {\n body = await req.json();\n } catch {\n return NextResponse.json(\n { error: \"Invalid JSON body\" },\n { status: 400 },\n );\n }\n\n const { type, mode, image } = body;\n\n if (!VALID_TYPES.includes(type as ScreenshotType)) {\n return NextResponse.json(\n { error: `Invalid type: must be \"before\" or \"after\"` },\n { status: 400 },\n );\n }\n\n if (!VALID_MODES.includes(mode as (typeof VALID_MODES)[number])) {\n return NextResponse.json(\n { error: `Invalid mode: must be \"fullpage\", \"viewport\", or \"area\"` },\n { status: 400 },\n );\n }\n\n if (typeof image !== \"string\" || !image.startsWith(DATA_URL_PREFIX)) {\n return NextResponse.json(\n { error: \"Invalid image: must be a data:image/png;base64 data URL\" },\n { status: 400 },\n );\n }\n\n const base64 = image.slice(DATA_URL_PREFIX.length);\n const buffer = Buffer.from(base64, \"base64\");\n\n const branch = await getBranch();\n const filename = `${branch}-${type}.png`;\n const filepath = join(homedir(), \"Desktop\", filename);\n\n try {\n await writeFile(filepath, buffer);\n } catch (err) {\n return NextResponse.json(\n { error: \"Failed to write screenshot\", detail: String(err) },\n { status: 500 },\n );\n }\n\n return NextResponse.json({ success: true, path: `~/Desktop/${filename}` });\n}\n","import { NextRequest, NextResponse } from \"next/server\";\nimport { execFile } from \"node:child_process\";\nimport { access, mkdir, copyFile } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport { homedir } from \"node:os\";\nimport { promisify } from \"node:util\";\n\nconst execFileAsync = promisify(execFile);\n\ninterface PrInfo {\n number: number;\n url: string;\n headRepository: { owner: { login: string }; name: string };\n headRefName: string;\n}\n\nasync function run(\n cmd: string,\n args: string[],\n): Promise<{ stdout: string; stderr: string }> {\n return execFileAsync(cmd, args, { cwd: process.cwd() });\n}\n\nasync function ghAvailable(): Promise<boolean> {\n try {\n await run(\"gh\", [\"--version\"]);\n return true;\n } catch {\n return false;\n }\n}\n\nasync function getPrInfo(): Promise<PrInfo | null> {\n try {\n const { stdout } = await run(\"gh\", [\n \"pr\",\n \"view\",\n \"--json\",\n \"number,url,headRepository,headRefName\",\n ]);\n return JSON.parse(stdout) as PrInfo;\n } catch {\n return null;\n }\n}\n\nasync function fileExists(path: string): Promise<boolean> {\n try {\n await access(path);\n return true;\n } catch {\n return false;\n }\n}\n\nexport async function handlePush(req: NextRequest): Promise<NextResponse> {\n // 1. Check gh CLI availability\n if (!(await ghAvailable())) {\n return NextResponse.json(\n { success: false, error: \"GitHub CLI (gh) is not installed or not in PATH\" },\n { status: 500 },\n );\n }\n\n // 2. Check for active PR\n const pr = await getPrInfo();\n if (!pr) {\n return NextResponse.json(\n { success: false, error: \"No PR found for current branch\" },\n { status: 404 },\n );\n }\n\n // 3. Check that screenshot files exist on Desktop\n const desktop = join(homedir(), \"Desktop\");\n const branch = pr.headRefName;\n const beforePath = join(desktop, `${branch}-before.png`);\n const afterPath = join(desktop, `${branch}-after.png`);\n\n const [hasBefore, hasAfter] = await Promise.all([\n fileExists(beforePath),\n fileExists(afterPath),\n ]);\n\n if (!hasBefore || !hasAfter) {\n const missing = [\n !hasBefore && `${branch}-before.png`,\n !hasAfter && `${branch}-after.png`,\n ].filter(Boolean);\n return NextResponse.json(\n { success: false, error: `Missing screenshots: ${missing.join(\", \")}` },\n { status: 400 },\n );\n }\n\n // 4. Copy from Desktop into repo, stage, commit, and push\n const repoDir = join(process.cwd(), \".afterbefore\");\n const repoBefore = join(repoDir, `${branch}-before.png`);\n const repoAfter = join(repoDir, `${branch}-after.png`);\n\n try {\n await mkdir(repoDir, { recursive: true });\n await copyFile(beforePath, repoBefore);\n await copyFile(afterPath, repoAfter);\n } catch (err) {\n return NextResponse.json(\n { success: false, error: \"Failed to copy screenshots into repo\", detail: String(err) },\n { status: 500 },\n );\n }\n\n try {\n await run(\"git\", [\"add\", repoBefore, repoAfter]);\n await run(\"git\", [\n \"commit\",\n \"-m\",\n \"chore: add before/after screenshots\",\n ]);\n } catch (err) {\n // Commit may fail if files are already committed with no changes.\n // That's fine -- we still want to push and comment.\n const msg = String(err);\n if (!msg.includes(\"nothing to commit\")) {\n return NextResponse.json(\n { success: false, error: \"Failed to commit screenshots\", detail: msg },\n { status: 500 },\n );\n }\n }\n\n try {\n await run(\"git\", [\"push\"]);\n } catch (err) {\n return NextResponse.json(\n { success: false, error: \"Failed to push to remote\", detail: String(err) },\n { status: 500 },\n );\n }\n\n // 5. Build raw GitHub URLs for the images\n const owner = pr.headRepository.owner.login;\n const repo = pr.headRepository.name;\n const rawBase = `https://raw.githubusercontent.com/${owner}/${repo}/${branch}`;\n\n const beforeUrl = `${rawBase}/.afterbefore/${branch}-before.png`;\n const afterUrl = `${rawBase}/.afterbefore/${branch}-after.png`;\n\n // Cache-bust with timestamp so GitHub doesn't serve stale images\n const ts = Date.now();\n const commentBody = [\n \"## Before / After\",\n \"\",\n \"| Before | After |\",\n \"|--------|-------|\",\n `| ![before](${beforeUrl}?t=${ts}) | ![after](${afterUrl}?t=${ts}) |`,\n ].join(\"\\n\");\n\n // 6. Post PR comment\n let commentUrl: string | undefined;\n try {\n const { stdout } = await run(\"gh\", [\n \"pr\",\n \"comment\",\n String(pr.number),\n \"--body\",\n commentBody,\n ]);\n // gh pr comment prints the comment URL to stdout\n commentUrl = stdout.trim() || undefined;\n } catch (err) {\n return NextResponse.json(\n { success: false, error: \"Failed to post PR comment\", detail: String(err) },\n { status: 500 },\n );\n }\n\n return NextResponse.json({\n success: true,\n prNumber: pr.number,\n prUrl: pr.url,\n commentUrl,\n });\n}\n","import { NextRequest, NextResponse } from \"next/server\";\nimport { execFile } from \"node:child_process\";\nimport { join } from \"node:path\";\nimport { homedir } from \"node:os\";\n\nexport async function handleOpen(_req: NextRequest): Promise<NextResponse> {\n const desktop = join(homedir(), \"Desktop\");\n\n const cmd =\n process.platform === \"darwin\"\n ? \"open\"\n : process.platform === \"win32\"\n ? \"explorer\"\n : \"xdg-open\";\n\n execFile(cmd, [desktop]);\n\n return NextResponse.json({ success: true });\n}\n"],"mappings":"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","names":["NextResponse","execFile","join","homedir","promisify","execFileAsync","NextResponse","execFile","join","homedir"]}
package/dist/next.js CHANGED
@@ -8,7 +8,7 @@ function withAfterBefore(config = {}) {
8
8
  const rules = [
9
9
  {
10
10
  source: "/__afterbefore/:path*",
11
- destination: "/api/__afterbefore/:path*"
11
+ destination: "/api/afterbefore/:path*"
12
12
  }
13
13
  ];
14
14
  if (Array.isArray(existing)) {
package/dist/next.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/next.ts"],"sourcesContent":["import type { NextConfig } from \"next\";\n\ntype Rewrite = { source: string; destination: string };\n\nexport function withAfterBefore(config: NextConfig = {}): NextConfig {\n if (process.env.NODE_ENV !== \"development\") return config;\n\n return {\n ...config,\n async rewrites() {\n const existing = (await config.rewrites?.()) ?? [];\n const rules: Rewrite[] = [\n {\n source: \"/__afterbefore/:path*\",\n destination: \"/api/__afterbefore/:path*\",\n },\n ];\n\n if (Array.isArray(existing)) {\n return [...rules, ...existing];\n }\n\n return {\n beforeFiles: [...rules, ...(existing.beforeFiles ?? [])],\n afterFiles: existing.afterFiles ?? [],\n fallback: existing.fallback ?? [],\n };\n },\n };\n}\n"],"mappings":";AAIO,SAAS,gBAAgB,SAAqB,CAAC,GAAe;AACnE,MAAI,QAAQ,IAAI,aAAa,cAAe,QAAO;AAEnD,SAAO;AAAA,IACL,GAAG;AAAA,IACH,MAAM,WAAW;AACf,YAAM,WAAY,MAAM,OAAO,WAAW,KAAM,CAAC;AACjD,YAAM,QAAmB;AAAA,QACvB;AAAA,UACE,QAAQ;AAAA,UACR,aAAa;AAAA,QACf;AAAA,MACF;AAEA,UAAI,MAAM,QAAQ,QAAQ,GAAG;AAC3B,eAAO,CAAC,GAAG,OAAO,GAAG,QAAQ;AAAA,MAC/B;AAEA,aAAO;AAAA,QACL,aAAa,CAAC,GAAG,OAAO,GAAI,SAAS,eAAe,CAAC,CAAE;AAAA,QACvD,YAAY,SAAS,cAAc,CAAC;AAAA,QACpC,UAAU,SAAS,YAAY,CAAC;AAAA,MAClC;AAAA,IACF;AAAA,EACF;AACF;","names":[]}
1
+ {"version":3,"sources":["../src/next.ts"],"sourcesContent":["import type { NextConfig } from \"next\";\n\ntype Rewrite = { source: string; destination: string };\n\nexport function withAfterBefore(config: NextConfig = {}): NextConfig {\n if (process.env.NODE_ENV !== \"development\") return config;\n\n return {\n ...config,\n async rewrites() {\n const existing = (await config.rewrites?.()) ?? [];\n const rules: Rewrite[] = [\n {\n source: \"/__afterbefore/:path*\",\n destination: \"/api/afterbefore/:path*\",\n },\n ];\n\n if (Array.isArray(existing)) {\n return [...rules, ...existing];\n }\n\n return {\n beforeFiles: [...rules, ...(existing.beforeFiles ?? [])],\n afterFiles: existing.afterFiles ?? [],\n fallback: existing.fallback ?? [],\n };\n },\n };\n}\n"],"mappings":";AAIO,SAAS,gBAAgB,SAAqB,CAAC,GAAe;AACnE,MAAI,QAAQ,IAAI,aAAa,cAAe,QAAO;AAEnD,SAAO;AAAA,IACL,GAAG;AAAA,IACH,MAAM,WAAW;AACf,YAAM,WAAY,MAAM,OAAO,WAAW,KAAM,CAAC;AACjD,YAAM,QAAmB;AAAA,QACvB;AAAA,UACE,QAAQ;AAAA,UACR,aAAa;AAAA,QACf;AAAA,MACF;AAEA,UAAI,MAAM,QAAQ,QAAQ,GAAG;AAC3B,eAAO,CAAC,GAAG,OAAO,GAAG,QAAQ;AAAA,MAC/B;AAEA,aAAO;AAAA,QACL,aAAa,CAAC,GAAG,OAAO,GAAI,SAAS,eAAe,CAAC,CAAE;AAAA,QACvD,YAAY,SAAS,cAAc,CAAC;AAAA,QACpC,UAAU,SAAS,YAAY,CAAC;AAAA,MAClC;AAAA,IACF;AAAA,EACF;AACF;","names":[]}
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/overlay/index.tsx","../../src/overlay/state.ts","../../src/overlay/capture.ts","../../src/overlay/ui/icon.tsx","../../src/overlay/ui/menu.tsx","../../src/overlay/ui/selector.tsx","../../src/overlay/ui/status.tsx"],"sourcesContent":["\"use client\";\n\nimport React, { useState, useCallback, useRef } from \"react\";\nimport { useOverlayState } from \"./state\";\nimport type { CaptureMode } from \"./state\";\nimport { capture } from \"./capture\";\nimport { Icon } from \"./ui/icon\";\nimport { Menu } from \"./ui/menu\";\nimport { Selector } from \"./ui/selector\";\nimport { Status } from \"./ui/status\";\n\nasync function saveCapture(\n type: \"before\" | \"after\",\n mode: CaptureMode,\n dataUrl: string,\n) {\n try {\n const res = await fetch(\"/__afterbefore/save\", {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ type, mode, image: dataUrl }),\n });\n if (!res.ok) throw new Error(\"Save failed\");\n } catch {\n // Fallback: browser download\n const link = document.createElement(\"a\");\n link.download = `${type}.png`;\n link.href = dataUrl;\n link.click();\n }\n}\n\nexport function AfterBefore() {\n const { state, captureComplete, reset } = useOverlayState();\n const [menuOpen, setMenuOpen] = useState(false);\n const [statusOpen, setStatusOpen] = useState(false);\n const [selectorActive, setSelectorActive] = useState(false);\n const [loading, setLoading] = useState(false);\n const iconPos = useRef<{ x: number; y: number }>({ x: 24, y: 0 });\n\n const handlePositionChange = useCallback(\n (pos: { x: number; y: number }) => {\n iconPos.current = pos;\n },\n [],\n );\n\n const handleIconClick = useCallback(() => {\n if (loading) return;\n\n if (state.phase === \"ready\") {\n setStatusOpen((prev: boolean) => !prev);\n setMenuOpen(false);\n } else {\n setMenuOpen((prev: boolean) => !prev);\n setStatusOpen(false);\n }\n }, [state.phase, loading]);\n\n const performCapture = useCallback(\n async (\n mode: CaptureMode,\n area?: { x: number; y: number; width: number; height: number },\n ) => {\n setLoading(true);\n try {\n const dataUrl = await capture({ mode, area });\n const type = state.phase === \"idle\" ? \"before\" : \"after\";\n await saveCapture(type, mode, dataUrl);\n captureComplete({\n dataUrl,\n mode,\n timestamp: Date.now(),\n });\n } catch (err) {\n console.error(\"[afterbefore] Capture failed:\", err);\n } finally {\n setLoading(false);\n }\n },\n [state.phase, captureComplete],\n );\n\n const handleModeSelect = useCallback(\n (mode: CaptureMode) => {\n setMenuOpen(false);\n if (mode === \"area\") {\n setSelectorActive(true);\n } else {\n performCapture(mode);\n }\n },\n [performCapture],\n );\n\n const handleAreaSelect = useCallback(\n (area: { x: number; y: number; width: number; height: number }) => {\n setSelectorActive(false);\n performCapture(\"area\", area);\n },\n [performCapture],\n );\n\n const handleAreaCancel = useCallback(() => {\n setSelectorActive(false);\n }, []);\n\n const handleReset = useCallback(() => {\n reset();\n setStatusOpen(false);\n setMenuOpen(false);\n }, [reset]);\n\n const handleMenuClose = useCallback(() => {\n setMenuOpen(false);\n }, []);\n\n const handleStatusClose = useCallback(() => {\n setStatusOpen(false);\n }, []);\n\n return (\n <div data-afterbefore=\"true\">\n <Icon\n phase={state.phase}\n onClick={handleIconClick}\n loading={loading}\n onPositionChange={handlePositionChange}\n />\n\n {menuOpen &&\n (state.phase === \"idle\" || state.phase === \"captured-before\") && (\n <Menu\n onSelect={handleModeSelect}\n onClose={handleMenuClose}\n position={iconPos.current}\n />\n )}\n\n {selectorActive && (\n <Selector onSelect={handleAreaSelect} onCancel={handleAreaCancel} />\n )}\n\n {statusOpen && state.phase === \"ready\" && (\n <Status\n onReset={handleReset}\n position={iconPos.current}\n onClose={handleStatusClose}\n />\n )}\n </div>\n );\n}\n","import { useState, useCallback } from \"react\";\n\nexport type CaptureMode = \"viewport\" | \"fullpage\" | \"area\";\n\nexport type OverlayPhase = \"idle\" | \"captured-before\" | \"ready\";\n\nexport interface CaptureResult {\n dataUrl: string;\n mode: CaptureMode;\n timestamp: number;\n}\n\nexport interface OverlayState {\n phase: OverlayPhase;\n before: CaptureResult | null;\n after: CaptureResult | null;\n}\n\nconst initialState: OverlayState = {\n phase: \"idle\",\n before: null,\n after: null,\n};\n\nexport function useOverlayState() {\n const [state, setState] = useState<OverlayState>(initialState);\n\n const captureComplete = useCallback(\n (result: CaptureResult) => {\n setState((prev) => {\n if (prev.phase === \"idle\") {\n return { ...prev, phase: \"captured-before\", before: result };\n }\n if (prev.phase === \"captured-before\") {\n return { ...prev, phase: \"ready\", after: result };\n }\n return prev;\n });\n },\n [],\n );\n\n const reset = useCallback(() => {\n setState(initialState);\n }, []);\n\n return { state, captureComplete, reset };\n}\n","import { toPng } from \"html-to-image\";\nimport type { CaptureMode } from \"./state\";\n\ninterface CaptureOptions {\n mode: CaptureMode;\n area?: { x: number; y: number; width: number; height: number };\n}\n\nexport async function capture(options: CaptureOptions): Promise<string> {\n const { mode, area } = options;\n\n if (mode === \"viewport\") {\n return captureViewport();\n }\n if (mode === \"fullpage\") {\n return captureFullPage();\n }\n if (mode === \"area\" && area) {\n return captureArea(area);\n }\n throw new Error(`Invalid capture mode: ${mode}`);\n}\n\nasync function captureViewport(): Promise<string> {\n const dataUrl = await toPng(document.documentElement, {\n width: window.innerWidth,\n height: window.innerHeight,\n style: {\n overflow: \"hidden\",\n },\n filter: filterOverlay,\n });\n return dataUrl;\n}\n\nasync function captureFullPage(): Promise<string> {\n const scrollY = window.scrollY;\n const body = document.body;\n const html = document.documentElement;\n\n const fullHeight = Math.max(\n body.scrollHeight,\n body.offsetHeight,\n html.clientHeight,\n html.scrollHeight,\n html.offsetHeight,\n );\n\n const dataUrl = await toPng(document.documentElement, {\n width: window.innerWidth,\n height: fullHeight,\n style: {\n overflow: \"visible\",\n height: `${fullHeight}px`,\n },\n filter: filterOverlay,\n });\n\n // Restore scroll position (toPng may have changed it)\n window.scrollTo(0, scrollY);\n return dataUrl;\n}\n\nasync function captureArea(area: {\n x: number;\n y: number;\n width: number;\n height: number;\n}): Promise<string> {\n // Capture the full viewport first\n const fullDataUrl = await captureViewport();\n\n // Crop to selection using an offscreen canvas\n const img = await loadImage(fullDataUrl);\n const dpr = window.devicePixelRatio || 1;\n\n const canvas = document.createElement(\"canvas\");\n canvas.width = area.width * dpr;\n canvas.height = area.height * dpr;\n\n const ctx = canvas.getContext(\"2d\")!;\n ctx.drawImage(\n img,\n area.x * dpr,\n area.y * dpr,\n area.width * dpr,\n area.height * dpr,\n 0,\n 0,\n area.width * dpr,\n area.height * dpr,\n );\n\n return canvas.toDataURL(\"image/png\");\n}\n\nfunction loadImage(src: string): Promise<HTMLImageElement> {\n return new Promise((resolve, reject) => {\n const img = new Image();\n img.onload = () => resolve(img);\n img.onerror = reject;\n img.src = src;\n });\n}\n\n/** Filter out the afterbefore overlay from screenshots */\nfunction filterOverlay(node: HTMLElement): boolean {\n return !node.dataset?.afterbefore;\n}\n","\"use client\";\n\nimport React, { useRef, useCallback, useEffect, useState } from \"react\";\nimport type { OverlayPhase } from \"../state\";\n\ninterface IconProps {\n phase: OverlayPhase;\n onClick: () => void;\n loading?: boolean;\n onPositionChange?: (pos: { x: number; y: number }) => void;\n}\n\nconst ICON_SIZE = 40;\nconst EDGE_MARGIN = 24;\n\nexport function Icon({ phase, onClick, loading, onPositionChange }: IconProps) {\n const ref = useRef<HTMLDivElement>(null);\n const [pos, setPos] = useState({ x: EDGE_MARGIN, y: -1 });\n const dragState = useRef<{\n dragging: boolean;\n startX: number;\n startY: number;\n origX: number;\n origY: number;\n distance: number;\n } | null>(null);\n\n // Initialize y position on mount (need window.innerHeight)\n useEffect(() => {\n setPos((prev) => {\n if (prev.y === -1) {\n const y = window.innerHeight - ICON_SIZE - EDGE_MARGIN;\n return { x: prev.x, y };\n }\n return prev;\n });\n }, []);\n\n // Report position changes upstream\n useEffect(() => {\n if (pos.y !== -1) {\n onPositionChange?.({ x: pos.x, y: pos.y });\n }\n }, [pos, onPositionChange]);\n\n const handleMouseDown = useCallback(\n (e: React.MouseEvent) => {\n e.preventDefault();\n dragState.current = {\n dragging: true,\n startX: e.clientX,\n startY: e.clientY,\n origX: pos.x,\n origY: pos.y,\n distance: 0,\n };\n },\n [pos],\n );\n\n useEffect(() => {\n const handleMouseMove = (e: MouseEvent) => {\n const ds = dragState.current;\n if (!ds || !ds.dragging) return;\n\n const dx = e.clientX - ds.startX;\n const dy = e.clientY - ds.startY;\n ds.distance = Math.sqrt(dx * dx + dy * dy);\n\n const newX = Math.max(\n 0,\n Math.min(window.innerWidth - ICON_SIZE, ds.origX + dx),\n );\n const newY = Math.max(\n 0,\n Math.min(window.innerHeight - ICON_SIZE, ds.origY + dy),\n );\n setPos({ x: newX, y: newY });\n };\n\n const handleMouseUp = () => {\n const ds = dragState.current;\n if (!ds) return;\n\n if (ds.distance < 5) {\n onClick();\n }\n dragState.current = null;\n };\n\n window.addEventListener(\"mousemove\", handleMouseMove);\n window.addEventListener(\"mouseup\", handleMouseUp);\n return () => {\n window.removeEventListener(\"mousemove\", handleMouseMove);\n window.removeEventListener(\"mouseup\", handleMouseUp);\n };\n }, [onClick]);\n\n // Don't render until y is initialized\n if (pos.y === -1) return null;\n\n return (\n <div\n ref={ref}\n data-afterbefore=\"true\"\n onMouseDown={handleMouseDown}\n style={{\n position: \"fixed\",\n left: pos.x,\n top: pos.y,\n width: ICON_SIZE,\n height: ICON_SIZE,\n borderRadius: \"50%\",\n background: \"rgba(30, 30, 30, 0.85)\",\n display: \"flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n cursor: \"grab\",\n zIndex: 2147483647,\n boxShadow: \"0 2px 8px rgba(0,0,0,0.3)\",\n transition: \"background 0.15s\",\n userSelect: \"none\",\n }}\n onMouseEnter={(e) => {\n (e.currentTarget as HTMLDivElement).style.background =\n \"rgba(30, 30, 30, 0.95)\";\n }}\n onMouseLeave={(e) => {\n (e.currentTarget as HTMLDivElement).style.background =\n \"rgba(30, 30, 30, 0.85)\";\n }}\n >\n <style\n dangerouslySetInnerHTML={{\n __html: `\n@keyframes ab-pulse {\n 0%, 100% { transform: scale(1); opacity: 1; }\n 50% { transform: scale(1.08); opacity: 0.85; }\n}\n@keyframes ab-spin {\n 0% { transform: rotate(0deg); }\n 100% { transform: rotate(360deg); }\n}`,\n }}\n />\n\n {loading ? (\n <svg\n width=\"20\"\n height=\"20\"\n viewBox=\"0 0 20 20\"\n style={{ animation: \"ab-spin 0.8s linear infinite\" }}\n >\n <circle\n cx=\"10\"\n cy=\"10\"\n r=\"8\"\n fill=\"none\"\n stroke=\"white\"\n strokeWidth=\"2\"\n strokeDasharray=\"40\"\n strokeDashoffset=\"10\"\n strokeLinecap=\"round\"\n />\n </svg>\n ) : phase === \"ready\" ? (\n <svg width=\"20\" height=\"20\" viewBox=\"0 0 20 20\">\n <path\n d=\"M4 10l4 4 8-8\"\n fill=\"none\"\n stroke=\"#4ade80\"\n strokeWidth=\"2.5\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n />\n </svg>\n ) : (\n <div\n style={{\n position: \"relative\",\n display: \"flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n animation:\n phase === \"captured-before\"\n ? \"ab-pulse 2s ease-in-out infinite\"\n : \"none\",\n }}\n >\n {/* Camera icon */}\n <svg width=\"20\" height=\"20\" viewBox=\"0 0 20 20\">\n <rect\n x=\"2\"\n y=\"5\"\n width=\"16\"\n height=\"12\"\n rx=\"2\"\n fill=\"none\"\n stroke=\"white\"\n strokeWidth=\"1.5\"\n />\n <circle\n cx=\"10\"\n cy=\"11\"\n r=\"3\"\n fill=\"none\"\n stroke=\"white\"\n strokeWidth=\"1.5\"\n />\n <path d=\"M7 5l1-2h4l1 2\" fill=\"none\" stroke=\"white\" strokeWidth=\"1.5\" />\n </svg>\n\n {/* Badge for captured-before */}\n {phase === \"captured-before\" && (\n <div\n style={{\n position: \"absolute\",\n top: -6,\n right: -8,\n width: 14,\n height: 14,\n borderRadius: \"50%\",\n background: \"#3b82f6\",\n color: \"white\",\n fontSize: \"9px\",\n fontWeight: 700,\n display: \"flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n lineHeight: 1,\n fontFamily: \"system-ui, sans-serif\",\n }}\n >\n 1\n </div>\n )}\n </div>\n )}\n </div>\n );\n}\n","\"use client\";\n\nimport React, { useEffect, useRef, useCallback } from \"react\";\nimport type { CaptureMode } from \"../state\";\n\ninterface MenuProps {\n onSelect: (mode: CaptureMode) => void;\n onClose: () => void;\n position: { x: number; y: number };\n}\n\nconst modes: { mode: CaptureMode; label: string; icon: React.ReactNode }[] = [\n {\n mode: \"viewport\",\n label: \"Viewport\",\n icon: (\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 16 16\">\n <rect\n x=\"1\"\n y=\"2\"\n width=\"14\"\n height=\"11\"\n rx=\"1.5\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"1.5\"\n />\n <line\n x1=\"1\"\n y1=\"14\"\n x2=\"15\"\n y2=\"14\"\n stroke=\"currentColor\"\n strokeWidth=\"1.5\"\n strokeLinecap=\"round\"\n />\n </svg>\n ),\n },\n {\n mode: \"fullpage\",\n label: \"Full Page\",\n icon: (\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 16 16\">\n <rect\n x=\"3\"\n y=\"1\"\n width=\"10\"\n height=\"14\"\n rx=\"1.5\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"1.5\"\n />\n <line\n x1=\"5.5\"\n y1=\"4\"\n x2=\"10.5\"\n y2=\"4\"\n stroke=\"currentColor\"\n strokeWidth=\"1\"\n strokeLinecap=\"round\"\n />\n <line\n x1=\"5.5\"\n y1=\"6.5\"\n x2=\"10.5\"\n y2=\"6.5\"\n stroke=\"currentColor\"\n strokeWidth=\"1\"\n strokeLinecap=\"round\"\n />\n <line\n x1=\"5.5\"\n y1=\"9\"\n x2=\"10.5\"\n y2=\"9\"\n stroke=\"currentColor\"\n strokeWidth=\"1\"\n strokeLinecap=\"round\"\n />\n </svg>\n ),\n },\n {\n mode: \"area\",\n label: \"Select Area\",\n icon: (\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 16 16\">\n <path\n d=\"M1 5V2.5A1.5 1.5 0 012.5 1H5\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"1.5\"\n strokeLinecap=\"round\"\n />\n <path\n d=\"M11 1h2.5A1.5 1.5 0 0115 2.5V5\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"1.5\"\n strokeLinecap=\"round\"\n />\n <path\n d=\"M15 11v2.5a1.5 1.5 0 01-1.5 1.5H11\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"1.5\"\n strokeLinecap=\"round\"\n />\n <path\n d=\"M5 15H2.5A1.5 1.5 0 011 13.5V11\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"1.5\"\n strokeLinecap=\"round\"\n />\n </svg>\n ),\n },\n];\n\nconst MENU_WIDTH = 160;\n\nexport function Menu({ onSelect, onClose, position }: MenuProps) {\n const menuRef = useRef<HTMLDivElement>(null);\n\n const handleClickOutside = useCallback(\n (e: MouseEvent) => {\n if (menuRef.current && !menuRef.current.contains(e.target as Node)) {\n onClose();\n }\n },\n [onClose],\n );\n\n const handleKeyDown = useCallback(\n (e: KeyboardEvent) => {\n if (e.key === \"Escape\") {\n onClose();\n }\n },\n [onClose],\n );\n\n useEffect(() => {\n document.addEventListener(\"mousedown\", handleClickOutside);\n document.addEventListener(\"keydown\", handleKeyDown);\n return () => {\n document.removeEventListener(\"mousedown\", handleClickOutside);\n document.removeEventListener(\"keydown\", handleKeyDown);\n };\n }, [handleClickOutside, handleKeyDown]);\n\n // Position the menu above the icon, clamped to viewport\n const menuLeft = Math.max(\n 8,\n Math.min(position.x - MENU_WIDTH / 2 + 20, window.innerWidth - MENU_WIDTH - 8),\n );\n const menuBottom = window.innerHeight - position.y + 8;\n\n return (\n <div\n ref={menuRef}\n data-afterbefore=\"true\"\n style={{\n position: \"fixed\",\n left: menuLeft,\n bottom: menuBottom,\n width: MENU_WIDTH,\n background: \"rgba(24, 24, 27, 0.95)\",\n borderRadius: 10,\n padding: 4,\n boxShadow: \"0 4px 20px rgba(0,0,0,0.4), 0 0 0 1px rgba(255,255,255,0.08)\",\n zIndex: 2147483647,\n fontFamily: \"system-ui, -apple-system, sans-serif\",\n backdropFilter: \"blur(12px)\",\n }}\n >\n {modes.map(({ mode, label, icon }) => (\n <button\n key={mode}\n onClick={() => onSelect(mode)}\n style={{\n display: \"flex\",\n alignItems: \"center\",\n gap: 8,\n width: \"100%\",\n padding: \"8px 10px\",\n border: \"none\",\n background: \"transparent\",\n color: \"rgba(255,255,255,0.9)\",\n fontSize: 13,\n borderRadius: 6,\n cursor: \"pointer\",\n textAlign: \"left\",\n transition: \"background 0.1s\",\n }}\n onMouseEnter={(e) => {\n (e.currentTarget as HTMLButtonElement).style.background =\n \"rgba(255,255,255,0.1)\";\n }}\n onMouseLeave={(e) => {\n (e.currentTarget as HTMLButtonElement).style.background = \"transparent\";\n }}\n >\n <span\n style={{\n display: \"flex\",\n alignItems: \"center\",\n color: \"rgba(255,255,255,0.6)\",\n }}\n >\n {icon}\n </span>\n {label}\n </button>\n ))}\n </div>\n );\n}\n","\"use client\";\n\nimport React, { useState, useRef, useCallback, useEffect } from \"react\";\n\ninterface SelectorProps {\n onSelect: (area: {\n x: number;\n y: number;\n width: number;\n height: number;\n }) => void;\n onCancel: () => void;\n}\n\nconst MIN_SIZE = 10;\n\nexport function Selector({ onSelect, onCancel }: SelectorProps) {\n const [selection, setSelection] = useState<{\n startX: number;\n startY: number;\n endX: number;\n endY: number;\n } | null>(null);\n const dragging = useRef(false);\n\n const handleKeyDown = useCallback(\n (e: KeyboardEvent) => {\n if (e.key === \"Escape\") {\n onCancel();\n }\n },\n [onCancel],\n );\n\n useEffect(() => {\n document.addEventListener(\"keydown\", handleKeyDown);\n return () => document.removeEventListener(\"keydown\", handleKeyDown);\n }, [handleKeyDown]);\n\n const handleMouseDown = useCallback((e: React.MouseEvent) => {\n e.preventDefault();\n dragging.current = true;\n setSelection({\n startX: e.clientX,\n startY: e.clientY,\n endX: e.clientX,\n endY: e.clientY,\n });\n }, []);\n\n const handleMouseMove = useCallback((e: React.MouseEvent) => {\n if (!dragging.current) return;\n setSelection((prev) => {\n if (!prev) return prev;\n return { ...prev, endX: e.clientX, endY: e.clientY };\n });\n }, []);\n\n const handleMouseUp = useCallback(() => {\n if (!dragging.current || !selection) return;\n dragging.current = false;\n\n const x = Math.min(selection.startX, selection.endX);\n const y = Math.min(selection.startY, selection.endY);\n const width = Math.abs(selection.endX - selection.startX);\n const height = Math.abs(selection.endY - selection.startY);\n\n if (width >= MIN_SIZE && height >= MIN_SIZE) {\n onSelect({ x, y, width, height });\n } else {\n setSelection(null);\n }\n }, [selection, onSelect]);\n\n // Compute the normalized rect for display\n const rect = selection\n ? {\n x: Math.min(selection.startX, selection.endX),\n y: Math.min(selection.startY, selection.endY),\n w: Math.abs(selection.endX - selection.startX),\n h: Math.abs(selection.endY - selection.startY),\n }\n : null;\n\n return (\n <div\n data-afterbefore=\"true\"\n onMouseDown={handleMouseDown}\n onMouseMove={handleMouseMove}\n onMouseUp={handleMouseUp}\n style={{\n position: \"fixed\",\n inset: 0,\n zIndex: 2147483647,\n cursor: \"crosshair\",\n }}\n >\n {/* Dimmed overlay using clip-path to cut out the selection */}\n <div\n style={{\n position: \"absolute\",\n inset: 0,\n background: \"rgba(0, 0, 0, 0.4)\",\n pointerEvents: \"none\",\n ...(rect && rect.w > 0 && rect.h > 0\n ? {\n clipPath: `polygon(\n 0% 0%, 0% 100%, 100% 100%, 100% 0%, 0% 0%,\n ${rect.x}px ${rect.y}px,\n ${rect.x}px ${rect.y + rect.h}px,\n ${rect.x + rect.w}px ${rect.y + rect.h}px,\n ${rect.x + rect.w}px ${rect.y}px,\n ${rect.x}px ${rect.y}px\n )`,\n }\n : {}),\n }}\n />\n\n {/* Selection border and label */}\n {rect && rect.w > 0 && rect.h > 0 && (\n <>\n <div\n style={{\n position: \"absolute\",\n left: rect.x,\n top: rect.y,\n width: rect.w,\n height: rect.h,\n border: \"2px solid rgba(59, 130, 246, 0.8)\",\n borderRadius: 2,\n pointerEvents: \"none\",\n boxShadow: \"0 0 0 1px rgba(0,0,0,0.3)\",\n }}\n />\n {/* Dimensions label */}\n <div\n style={{\n position: \"absolute\",\n left: rect.x + rect.w / 2,\n top: rect.y + rect.h + 8,\n transform: \"translateX(-50%)\",\n background: \"rgba(24, 24, 27, 0.9)\",\n color: \"rgba(255,255,255,0.9)\",\n fontSize: 11,\n fontFamily: \"system-ui, -apple-system, monospace\",\n padding: \"2px 8px\",\n borderRadius: 4,\n whiteSpace: \"nowrap\",\n pointerEvents: \"none\",\n }}\n >\n {Math.round(rect.w)} &times; {Math.round(rect.h)}\n </div>\n </>\n )}\n\n {/* Instruction hint when nothing selected yet */}\n {!selection && (\n <div\n style={{\n position: \"absolute\",\n top: \"50%\",\n left: \"50%\",\n transform: \"translate(-50%, -50%)\",\n color: \"rgba(255,255,255,0.7)\",\n fontSize: 14,\n fontFamily: \"system-ui, -apple-system, sans-serif\",\n pointerEvents: \"none\",\n textShadow: \"0 1px 4px rgba(0,0,0,0.5)\",\n }}\n >\n Drag to select an area &middot; Esc to cancel\n </div>\n )}\n </div>\n );\n}\n","\"use client\";\n\nimport React, { useState, useRef, useEffect, useCallback } from \"react\";\n\ninterface StatusProps {\n onReset: () => void;\n position: { x: number; y: number };\n onClose: () => void;\n}\n\ninterface Toast {\n message: string;\n type: \"success\" | \"error\";\n}\n\nconst PANEL_WIDTH = 220;\n\nexport function Status({ onReset, position, onClose }: StatusProps) {\n const panelRef = useRef<HTMLDivElement>(null);\n const [toast, setToast] = useState<Toast | null>(null);\n const [pushing, setPushing] = useState(false);\n\n const showToast = useCallback((message: string, type: Toast[\"type\"]) => {\n setToast({ message, type });\n setTimeout(() => setToast(null), 3000);\n }, []);\n\n // Click outside to close\n useEffect(() => {\n const handler = (e: MouseEvent) => {\n if (panelRef.current && !panelRef.current.contains(e.target as Node)) {\n onClose();\n }\n };\n document.addEventListener(\"mousedown\", handler);\n return () => document.removeEventListener(\"mousedown\", handler);\n }, [onClose]);\n\n // Escape to close\n useEffect(() => {\n const handler = (e: KeyboardEvent) => {\n if (e.key === \"Escape\") onClose();\n };\n document.addEventListener(\"keydown\", handler);\n return () => document.removeEventListener(\"keydown\", handler);\n }, [onClose]);\n\n const handleOpenFolder = async () => {\n try {\n const res = await fetch(\"/__afterbefore/open\", { method: \"POST\" });\n if (!res.ok) throw new Error();\n showToast(\"Opened folder\", \"success\");\n } catch {\n showToast(\"Could not open folder\", \"error\");\n }\n };\n\n const handleCopyMarkdown = async () => {\n try {\n const res = await fetch(\"/__afterbefore/markdown\");\n if (!res.ok) throw new Error();\n const { markdown } = await res.json();\n await navigator.clipboard.writeText(markdown);\n showToast(\"Copied!\", \"success\");\n } catch {\n showToast(\"Copy failed\", \"error\");\n }\n };\n\n const handlePush = async () => {\n setPushing(true);\n try {\n const res = await fetch(\"/__afterbefore/push\", { method: \"POST\" });\n const data = await res.json();\n if (!res.ok) {\n showToast(data.error || \"Push failed\", \"error\");\n } else if (data.pr) {\n showToast(`Posted to PR #${data.pr}`, \"success\");\n } else {\n showToast(\"No PR found\", \"error\");\n }\n } catch {\n showToast(\"Push failed\", \"error\");\n } finally {\n setPushing(false);\n }\n };\n\n const handleReset = () => {\n onReset();\n onClose();\n };\n\n const panelLeft = Math.max(\n 8,\n Math.min(position.x - PANEL_WIDTH / 2 + 20, window.innerWidth - PANEL_WIDTH - 8),\n );\n const panelBottom = window.innerHeight - position.y + 8;\n\n const buttonStyle: React.CSSProperties = {\n display: \"flex\",\n alignItems: \"center\",\n gap: 6,\n width: \"100%\",\n padding: \"7px 10px\",\n border: \"none\",\n background: \"transparent\",\n color: \"rgba(255,255,255,0.9)\",\n fontSize: 13,\n borderRadius: 6,\n cursor: \"pointer\",\n textAlign: \"left\" as const,\n fontFamily: \"system-ui, -apple-system, sans-serif\",\n transition: \"background 0.1s\",\n };\n\n const onEnter = (e: React.MouseEvent) => {\n (e.currentTarget as HTMLButtonElement).style.background =\n \"rgba(255,255,255,0.1)\";\n };\n const onLeave = (e: React.MouseEvent) => {\n (e.currentTarget as HTMLButtonElement).style.background = \"transparent\";\n };\n\n return (\n <div\n ref={panelRef}\n data-afterbefore=\"true\"\n style={{\n position: \"fixed\",\n left: panelLeft,\n bottom: panelBottom,\n width: PANEL_WIDTH,\n background: \"rgba(24, 24, 27, 0.95)\",\n borderRadius: 10,\n boxShadow:\n \"0 4px 20px rgba(0,0,0,0.4), 0 0 0 1px rgba(255,255,255,0.08)\",\n zIndex: 2147483647,\n fontFamily: \"system-ui, -apple-system, sans-serif\",\n backdropFilter: \"blur(12px)\",\n overflow: \"hidden\",\n }}\n >\n {/* Header */}\n <div\n style={{\n padding: \"10px 12px 6px\",\n fontSize: 12,\n fontWeight: 600,\n color: \"rgba(255,255,255,0.5)\",\n letterSpacing: \"0.02em\",\n }}\n >\n Before &amp; After captured\n </div>\n\n {/* Actions */}\n <div style={{ padding: \"0 4px 4px\" }}>\n <button\n style={buttonStyle}\n onClick={handleOpenFolder}\n onMouseEnter={onEnter}\n onMouseLeave={onLeave}\n >\n <FolderIcon />\n Open Folder\n </button>\n <button\n style={buttonStyle}\n onClick={handleCopyMarkdown}\n onMouseEnter={onEnter}\n onMouseLeave={onLeave}\n >\n <CopyIcon />\n Copy Markdown\n </button>\n <button\n style={buttonStyle}\n onClick={handlePush}\n disabled={pushing}\n onMouseEnter={onEnter}\n onMouseLeave={onLeave}\n >\n <PushIcon />\n {pushing ? \"Pushing...\" : \"Push to PR\"}\n </button>\n\n {/* Separator */}\n <div\n style={{\n height: 1,\n background: \"rgba(255,255,255,0.08)\",\n margin: \"4px 6px\",\n }}\n />\n\n <button\n style={{ ...buttonStyle, color: \"rgba(255,255,255,0.5)\" }}\n onClick={handleReset}\n onMouseEnter={onEnter}\n onMouseLeave={onLeave}\n >\n <ResetIcon />\n Reset\n </button>\n </div>\n\n {/* Toast */}\n {toast && (\n <div\n style={{\n position: \"absolute\",\n bottom: \"100%\",\n left: \"50%\",\n transform: \"translateX(-50%)\",\n marginBottom: 8,\n padding: \"6px 12px\",\n borderRadius: 6,\n fontSize: 12,\n fontWeight: 500,\n whiteSpace: \"nowrap\",\n color: \"white\",\n background:\n toast.type === \"success\"\n ? \"rgba(34, 197, 94, 0.9)\"\n : \"rgba(239, 68, 68, 0.9)\",\n boxShadow: \"0 2px 8px rgba(0,0,0,0.3)\",\n }}\n >\n {toast.message}\n </div>\n )}\n </div>\n );\n}\n\nfunction FolderIcon() {\n return (\n <svg\n width=\"14\"\n height=\"14\"\n viewBox=\"0 0 14 14\"\n style={{ color: \"rgba(255,255,255,0.5)\" }}\n >\n <path\n d=\"M1.5 3A1.5 1.5 0 013 1.5h2.38a1 1 0 01.72.3L7 2.72a1 1 0 00.72.3H11A1.5 1.5 0 0112.5 4.5v6A1.5 1.5 0 0111 12H3A1.5 1.5 0 011.5 10.5V3z\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"1.3\"\n />\n </svg>\n );\n}\n\nfunction CopyIcon() {\n return (\n <svg\n width=\"14\"\n height=\"14\"\n viewBox=\"0 0 14 14\"\n style={{ color: \"rgba(255,255,255,0.5)\" }}\n >\n <rect\n x=\"4\"\n y=\"4\"\n width=\"8.5\"\n height=\"8.5\"\n rx=\"1.5\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"1.3\"\n />\n <path\n d=\"M10 4V2.5A1.5 1.5 0 008.5 1h-6A1.5 1.5 0 001 2.5v6A1.5 1.5 0 002.5 10H4\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"1.3\"\n />\n </svg>\n );\n}\n\nfunction PushIcon() {\n return (\n <svg\n width=\"14\"\n height=\"14\"\n viewBox=\"0 0 14 14\"\n style={{ color: \"rgba(255,255,255,0.5)\" }}\n >\n <path\n d=\"M7 11V3m0 0L4 6m3-3l3 3\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"1.3\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n />\n </svg>\n );\n}\n\nfunction ResetIcon() {\n return (\n <svg\n width=\"14\"\n height=\"14\"\n viewBox=\"0 0 14 14\"\n style={{ color: \"rgba(255,255,255,0.4)\" }}\n >\n <path\n d=\"M2.5 7a4.5 4.5 0 118 2.5\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"1.3\"\n strokeLinecap=\"round\"\n />\n <path\n d=\"M2.5 3v4h4\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"1.3\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n />\n </svg>\n );\n}\n"],"mappings":"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","names":["useState","useCallback","useRef","useCallback","useState","useEffect","useRef","useCallback","jsx","jsxs","useState","useRef","useCallback","useEffect","jsx","jsxs","useState","useRef","useEffect","useCallback","jsx","jsxs","jsx","jsxs","useState","useRef","useCallback"]}
1
+ {"version":3,"sources":["../../src/overlay/index.tsx","../../src/overlay/state.ts","../../src/overlay/capture.ts","../../src/overlay/ui/icon.tsx","../../src/overlay/ui/menu.tsx","../../src/overlay/ui/selector.tsx","../../src/overlay/ui/status.tsx"],"sourcesContent":["\"use client\";\n\nimport React, { useState, useCallback, useRef } from \"react\";\nimport { useOverlayState } from \"./state\";\nimport type { CaptureMode } from \"./state\";\nimport { capture } from \"./capture\";\nimport { Icon } from \"./ui/icon\";\nimport { Menu } from \"./ui/menu\";\nimport { Selector } from \"./ui/selector\";\nimport { Status } from \"./ui/status\";\n\nasync function saveCapture(\n type: \"before\" | \"after\",\n mode: CaptureMode,\n dataUrl: string,\n) {\n try {\n const res = await fetch(\"/__afterbefore/save\", {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ type, mode, image: dataUrl }),\n });\n if (!res.ok) throw new Error(\"Save failed\");\n } catch {\n // Fallback: browser download (no branch info available client-side)\n const link = document.createElement(\"a\");\n link.download = `${type}.png`;\n link.href = dataUrl;\n link.click();\n }\n}\n\nexport function AfterBefore() {\n const { state, captureComplete, reset } = useOverlayState();\n const [menuOpen, setMenuOpen] = useState(false);\n const [statusOpen, setStatusOpen] = useState(false);\n const [selectorActive, setSelectorActive] = useState(false);\n const [loading, setLoading] = useState(false);\n const iconPos = useRef<{ x: number; y: number }>({ x: 24, y: 0 });\n\n const handlePositionChange = useCallback(\n (pos: { x: number; y: number }) => {\n iconPos.current = pos;\n },\n [],\n );\n\n const handleIconClick = useCallback(() => {\n if (loading) return;\n\n if (state.phase === \"ready\") {\n setStatusOpen((prev: boolean) => !prev);\n setMenuOpen(false);\n } else {\n setMenuOpen((prev: boolean) => !prev);\n setStatusOpen(false);\n }\n }, [state.phase, loading]);\n\n const performCapture = useCallback(\n async (\n mode: CaptureMode,\n area?: { x: number; y: number; width: number; height: number },\n ) => {\n setLoading(true);\n try {\n const dataUrl = await capture({ mode, area });\n const type = state.phase === \"idle\" ? \"before\" : \"after\";\n await saveCapture(type, mode, dataUrl);\n captureComplete({\n dataUrl,\n mode,\n timestamp: Date.now(),\n });\n } catch (err) {\n console.error(\"[afterbefore] Capture failed:\", err);\n } finally {\n setLoading(false);\n }\n },\n [state.phase, captureComplete],\n );\n\n const handleModeSelect = useCallback(\n (mode: CaptureMode) => {\n setMenuOpen(false);\n if (mode === \"area\") {\n setSelectorActive(true);\n } else {\n performCapture(mode);\n }\n },\n [performCapture],\n );\n\n const handleAreaSelect = useCallback(\n (area: { x: number; y: number; width: number; height: number }) => {\n setSelectorActive(false);\n performCapture(\"area\", area);\n },\n [performCapture],\n );\n\n const handleAreaCancel = useCallback(() => {\n setSelectorActive(false);\n }, []);\n\n const handleReset = useCallback(() => {\n reset();\n setStatusOpen(false);\n setMenuOpen(false);\n }, [reset]);\n\n const handleMenuClose = useCallback(() => {\n setMenuOpen(false);\n }, []);\n\n const handleStatusClose = useCallback(() => {\n setStatusOpen(false);\n }, []);\n\n return (\n <div data-afterbefore=\"true\">\n <Icon\n phase={state.phase}\n onClick={handleIconClick}\n loading={loading}\n onPositionChange={handlePositionChange}\n />\n\n {menuOpen &&\n (state.phase === \"idle\" || state.phase === \"captured-before\") && (\n <Menu\n onSelect={handleModeSelect}\n onClose={handleMenuClose}\n position={iconPos.current}\n />\n )}\n\n {selectorActive && (\n <Selector onSelect={handleAreaSelect} onCancel={handleAreaCancel} />\n )}\n\n {statusOpen && state.phase === \"ready\" && (\n <Status\n onReset={handleReset}\n position={iconPos.current}\n onClose={handleStatusClose}\n />\n )}\n </div>\n );\n}\n","import { useState, useCallback } from \"react\";\n\nexport type CaptureMode = \"viewport\" | \"fullpage\" | \"area\";\n\nexport type OverlayPhase = \"idle\" | \"captured-before\" | \"ready\";\n\nexport interface CaptureResult {\n dataUrl: string;\n mode: CaptureMode;\n timestamp: number;\n}\n\nexport interface OverlayState {\n phase: OverlayPhase;\n before: CaptureResult | null;\n after: CaptureResult | null;\n}\n\nconst initialState: OverlayState = {\n phase: \"idle\",\n before: null,\n after: null,\n};\n\nexport function useOverlayState() {\n const [state, setState] = useState<OverlayState>(initialState);\n\n const captureComplete = useCallback(\n (result: CaptureResult) => {\n setState((prev) => {\n if (prev.phase === \"idle\") {\n return { ...prev, phase: \"captured-before\", before: result };\n }\n if (prev.phase === \"captured-before\") {\n return { ...prev, phase: \"ready\", after: result };\n }\n return prev;\n });\n },\n [],\n );\n\n const reset = useCallback(() => {\n setState(initialState);\n }, []);\n\n return { state, captureComplete, reset };\n}\n","import { toPng } from \"html-to-image\";\nimport type { CaptureMode } from \"./state\";\n\ninterface CaptureOptions {\n mode: CaptureMode;\n area?: { x: number; y: number; width: number; height: number };\n}\n\nexport async function capture(options: CaptureOptions): Promise<string> {\n const { mode, area } = options;\n\n if (mode === \"viewport\") {\n return captureViewport();\n }\n if (mode === \"fullpage\") {\n return captureFullPage();\n }\n if (mode === \"area\" && area) {\n return captureArea(area);\n }\n throw new Error(`Invalid capture mode: ${mode}`);\n}\n\nasync function captureViewport(): Promise<string> {\n const dataUrl = await toPng(document.documentElement, {\n width: window.innerWidth,\n height: window.innerHeight,\n style: {\n overflow: \"hidden\",\n },\n filter: filterOverlay,\n });\n return dataUrl;\n}\n\nasync function captureFullPage(): Promise<string> {\n const scrollY = window.scrollY;\n const body = document.body;\n const html = document.documentElement;\n\n const fullHeight = Math.max(\n body.scrollHeight,\n body.offsetHeight,\n html.clientHeight,\n html.scrollHeight,\n html.offsetHeight,\n );\n\n const dataUrl = await toPng(document.documentElement, {\n width: window.innerWidth,\n height: fullHeight,\n style: {\n overflow: \"visible\",\n height: `${fullHeight}px`,\n },\n filter: filterOverlay,\n });\n\n // Restore scroll position (toPng may have changed it)\n window.scrollTo(0, scrollY);\n return dataUrl;\n}\n\nasync function captureArea(area: {\n x: number;\n y: number;\n width: number;\n height: number;\n}): Promise<string> {\n // Capture the full viewport first\n const fullDataUrl = await captureViewport();\n\n // Crop to selection using an offscreen canvas\n const img = await loadImage(fullDataUrl);\n const dpr = window.devicePixelRatio || 1;\n\n const canvas = document.createElement(\"canvas\");\n canvas.width = area.width * dpr;\n canvas.height = area.height * dpr;\n\n const ctx = canvas.getContext(\"2d\")!;\n ctx.drawImage(\n img,\n area.x * dpr,\n area.y * dpr,\n area.width * dpr,\n area.height * dpr,\n 0,\n 0,\n area.width * dpr,\n area.height * dpr,\n );\n\n return canvas.toDataURL(\"image/png\");\n}\n\nfunction loadImage(src: string): Promise<HTMLImageElement> {\n return new Promise((resolve, reject) => {\n const img = new Image();\n img.onload = () => resolve(img);\n img.onerror = reject;\n img.src = src;\n });\n}\n\n/** Filter out the afterbefore overlay from screenshots */\nfunction filterOverlay(node: HTMLElement): boolean {\n return !node.dataset?.afterbefore;\n}\n","\"use client\";\n\nimport React, { useRef, useCallback, useEffect, useState } from \"react\";\nimport type { OverlayPhase } from \"../state\";\n\ninterface IconProps {\n phase: OverlayPhase;\n onClick: () => void;\n loading?: boolean;\n onPositionChange?: (pos: { x: number; y: number }) => void;\n}\n\nconst ICON_SIZE = 40;\nconst EDGE_MARGIN = 24;\n\nexport function Icon({ phase, onClick, loading, onPositionChange }: IconProps) {\n const ref = useRef<HTMLDivElement>(null);\n const [pos, setPos] = useState({ x: EDGE_MARGIN, y: -1 });\n const dragState = useRef<{\n dragging: boolean;\n startX: number;\n startY: number;\n origX: number;\n origY: number;\n distance: number;\n } | null>(null);\n\n // Initialize y position on mount (need window.innerHeight)\n useEffect(() => {\n setPos((prev) => {\n if (prev.y === -1) {\n const y = window.innerHeight - ICON_SIZE - EDGE_MARGIN;\n return { x: prev.x, y };\n }\n return prev;\n });\n }, []);\n\n // Report position changes upstream\n useEffect(() => {\n if (pos.y !== -1) {\n onPositionChange?.({ x: pos.x, y: pos.y });\n }\n }, [pos, onPositionChange]);\n\n const handleMouseDown = useCallback(\n (e: React.MouseEvent) => {\n e.preventDefault();\n dragState.current = {\n dragging: true,\n startX: e.clientX,\n startY: e.clientY,\n origX: pos.x,\n origY: pos.y,\n distance: 0,\n };\n },\n [pos],\n );\n\n useEffect(() => {\n const handleMouseMove = (e: MouseEvent) => {\n const ds = dragState.current;\n if (!ds || !ds.dragging) return;\n\n const dx = e.clientX - ds.startX;\n const dy = e.clientY - ds.startY;\n ds.distance = Math.sqrt(dx * dx + dy * dy);\n\n const newX = Math.max(\n 0,\n Math.min(window.innerWidth - ICON_SIZE, ds.origX + dx),\n );\n const newY = Math.max(\n 0,\n Math.min(window.innerHeight - ICON_SIZE, ds.origY + dy),\n );\n setPos({ x: newX, y: newY });\n };\n\n const handleMouseUp = () => {\n const ds = dragState.current;\n if (!ds) return;\n\n if (ds.distance < 5) {\n onClick();\n }\n dragState.current = null;\n };\n\n window.addEventListener(\"mousemove\", handleMouseMove);\n window.addEventListener(\"mouseup\", handleMouseUp);\n return () => {\n window.removeEventListener(\"mousemove\", handleMouseMove);\n window.removeEventListener(\"mouseup\", handleMouseUp);\n };\n }, [onClick]);\n\n // Don't render until y is initialized\n if (pos.y === -1) return null;\n\n return (\n <div\n ref={ref}\n data-afterbefore=\"true\"\n onMouseDown={handleMouseDown}\n style={{\n position: \"fixed\",\n left: pos.x,\n top: pos.y,\n width: ICON_SIZE,\n height: ICON_SIZE,\n borderRadius: \"50%\",\n background: \"rgba(30, 30, 30, 0.85)\",\n display: \"flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n cursor: \"grab\",\n zIndex: 2147483647,\n boxShadow: \"0 2px 8px rgba(0,0,0,0.3)\",\n transition: \"background 0.15s\",\n userSelect: \"none\",\n }}\n onMouseEnter={(e) => {\n (e.currentTarget as HTMLDivElement).style.background =\n \"rgba(30, 30, 30, 0.95)\";\n }}\n onMouseLeave={(e) => {\n (e.currentTarget as HTMLDivElement).style.background =\n \"rgba(30, 30, 30, 0.85)\";\n }}\n >\n <style\n dangerouslySetInnerHTML={{\n __html: `\n@keyframes ab-pulse {\n 0%, 100% { transform: scale(1); opacity: 1; }\n 50% { transform: scale(1.08); opacity: 0.85; }\n}\n@keyframes ab-spin {\n 0% { transform: rotate(0deg); }\n 100% { transform: rotate(360deg); }\n}`,\n }}\n />\n\n {loading ? (\n <svg\n width=\"20\"\n height=\"20\"\n viewBox=\"0 0 20 20\"\n style={{ animation: \"ab-spin 0.8s linear infinite\" }}\n >\n <circle\n cx=\"10\"\n cy=\"10\"\n r=\"8\"\n fill=\"none\"\n stroke=\"white\"\n strokeWidth=\"2\"\n strokeDasharray=\"40\"\n strokeDashoffset=\"10\"\n strokeLinecap=\"round\"\n />\n </svg>\n ) : phase === \"ready\" ? (\n <svg width=\"20\" height=\"20\" viewBox=\"0 0 20 20\">\n <path\n d=\"M4 10l4 4 8-8\"\n fill=\"none\"\n stroke=\"#4ade80\"\n strokeWidth=\"2.5\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n />\n </svg>\n ) : (\n <div\n style={{\n position: \"relative\",\n display: \"flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n animation:\n phase === \"captured-before\"\n ? \"ab-pulse 2s ease-in-out infinite\"\n : \"none\",\n }}\n >\n {/* Camera icon */}\n <svg width=\"20\" height=\"20\" viewBox=\"0 0 20 20\">\n <rect\n x=\"2\"\n y=\"5\"\n width=\"16\"\n height=\"12\"\n rx=\"2\"\n fill=\"none\"\n stroke=\"white\"\n strokeWidth=\"1.5\"\n />\n <circle\n cx=\"10\"\n cy=\"11\"\n r=\"3\"\n fill=\"none\"\n stroke=\"white\"\n strokeWidth=\"1.5\"\n />\n <path d=\"M7 5l1-2h4l1 2\" fill=\"none\" stroke=\"white\" strokeWidth=\"1.5\" />\n </svg>\n\n {/* Badge for captured-before */}\n {phase === \"captured-before\" && (\n <div\n style={{\n position: \"absolute\",\n top: -6,\n right: -8,\n width: 14,\n height: 14,\n borderRadius: \"50%\",\n background: \"#3b82f6\",\n color: \"white\",\n fontSize: \"9px\",\n fontWeight: 700,\n display: \"flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n lineHeight: 1,\n fontFamily: \"system-ui, sans-serif\",\n }}\n >\n 1\n </div>\n )}\n </div>\n )}\n </div>\n );\n}\n","\"use client\";\n\nimport React, { useEffect, useRef, useCallback } from \"react\";\nimport type { CaptureMode } from \"../state\";\n\ninterface MenuProps {\n onSelect: (mode: CaptureMode) => void;\n onClose: () => void;\n position: { x: number; y: number };\n}\n\nconst modes: { mode: CaptureMode; label: string; icon: React.ReactNode }[] = [\n {\n mode: \"viewport\",\n label: \"Viewport\",\n icon: (\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 16 16\">\n <rect\n x=\"1\"\n y=\"2\"\n width=\"14\"\n height=\"11\"\n rx=\"1.5\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"1.5\"\n />\n <line\n x1=\"1\"\n y1=\"14\"\n x2=\"15\"\n y2=\"14\"\n stroke=\"currentColor\"\n strokeWidth=\"1.5\"\n strokeLinecap=\"round\"\n />\n </svg>\n ),\n },\n {\n mode: \"fullpage\",\n label: \"Full Page\",\n icon: (\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 16 16\">\n <rect\n x=\"3\"\n y=\"1\"\n width=\"10\"\n height=\"14\"\n rx=\"1.5\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"1.5\"\n />\n <line\n x1=\"5.5\"\n y1=\"4\"\n x2=\"10.5\"\n y2=\"4\"\n stroke=\"currentColor\"\n strokeWidth=\"1\"\n strokeLinecap=\"round\"\n />\n <line\n x1=\"5.5\"\n y1=\"6.5\"\n x2=\"10.5\"\n y2=\"6.5\"\n stroke=\"currentColor\"\n strokeWidth=\"1\"\n strokeLinecap=\"round\"\n />\n <line\n x1=\"5.5\"\n y1=\"9\"\n x2=\"10.5\"\n y2=\"9\"\n stroke=\"currentColor\"\n strokeWidth=\"1\"\n strokeLinecap=\"round\"\n />\n </svg>\n ),\n },\n {\n mode: \"area\",\n label: \"Select Area\",\n icon: (\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 16 16\">\n <path\n d=\"M1 5V2.5A1.5 1.5 0 012.5 1H5\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"1.5\"\n strokeLinecap=\"round\"\n />\n <path\n d=\"M11 1h2.5A1.5 1.5 0 0115 2.5V5\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"1.5\"\n strokeLinecap=\"round\"\n />\n <path\n d=\"M15 11v2.5a1.5 1.5 0 01-1.5 1.5H11\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"1.5\"\n strokeLinecap=\"round\"\n />\n <path\n d=\"M5 15H2.5A1.5 1.5 0 011 13.5V11\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"1.5\"\n strokeLinecap=\"round\"\n />\n </svg>\n ),\n },\n];\n\nconst MENU_WIDTH = 160;\n\nexport function Menu({ onSelect, onClose, position }: MenuProps) {\n const menuRef = useRef<HTMLDivElement>(null);\n\n const handleClickOutside = useCallback(\n (e: MouseEvent) => {\n if (menuRef.current && !menuRef.current.contains(e.target as Node)) {\n onClose();\n }\n },\n [onClose],\n );\n\n const handleKeyDown = useCallback(\n (e: KeyboardEvent) => {\n if (e.key === \"Escape\") {\n onClose();\n }\n },\n [onClose],\n );\n\n useEffect(() => {\n document.addEventListener(\"mousedown\", handleClickOutside);\n document.addEventListener(\"keydown\", handleKeyDown);\n return () => {\n document.removeEventListener(\"mousedown\", handleClickOutside);\n document.removeEventListener(\"keydown\", handleKeyDown);\n };\n }, [handleClickOutside, handleKeyDown]);\n\n // Position the menu above the icon, clamped to viewport\n const menuLeft = Math.max(\n 8,\n Math.min(position.x - MENU_WIDTH / 2 + 20, window.innerWidth - MENU_WIDTH - 8),\n );\n const menuBottom = window.innerHeight - position.y + 8;\n\n return (\n <div\n ref={menuRef}\n data-afterbefore=\"true\"\n style={{\n position: \"fixed\",\n left: menuLeft,\n bottom: menuBottom,\n width: MENU_WIDTH,\n background: \"rgba(24, 24, 27, 0.95)\",\n borderRadius: 10,\n padding: 4,\n boxShadow: \"0 4px 20px rgba(0,0,0,0.4), 0 0 0 1px rgba(255,255,255,0.08)\",\n zIndex: 2147483647,\n fontFamily: \"system-ui, -apple-system, sans-serif\",\n backdropFilter: \"blur(12px)\",\n }}\n >\n {modes.map(({ mode, label, icon }) => (\n <button\n key={mode}\n onClick={() => onSelect(mode)}\n style={{\n display: \"flex\",\n alignItems: \"center\",\n gap: 8,\n width: \"100%\",\n padding: \"8px 10px\",\n border: \"none\",\n background: \"transparent\",\n color: \"rgba(255,255,255,0.9)\",\n fontSize: 13,\n borderRadius: 6,\n cursor: \"pointer\",\n textAlign: \"left\",\n transition: \"background 0.1s\",\n }}\n onMouseEnter={(e) => {\n (e.currentTarget as HTMLButtonElement).style.background =\n \"rgba(255,255,255,0.1)\";\n }}\n onMouseLeave={(e) => {\n (e.currentTarget as HTMLButtonElement).style.background = \"transparent\";\n }}\n >\n <span\n style={{\n display: \"flex\",\n alignItems: \"center\",\n color: \"rgba(255,255,255,0.6)\",\n }}\n >\n {icon}\n </span>\n {label}\n </button>\n ))}\n </div>\n );\n}\n","\"use client\";\n\nimport React, { useState, useRef, useCallback, useEffect } from \"react\";\n\ninterface SelectorProps {\n onSelect: (area: {\n x: number;\n y: number;\n width: number;\n height: number;\n }) => void;\n onCancel: () => void;\n}\n\nconst MIN_SIZE = 10;\n\nexport function Selector({ onSelect, onCancel }: SelectorProps) {\n const [selection, setSelection] = useState<{\n startX: number;\n startY: number;\n endX: number;\n endY: number;\n } | null>(null);\n const dragging = useRef(false);\n\n const handleKeyDown = useCallback(\n (e: KeyboardEvent) => {\n if (e.key === \"Escape\") {\n onCancel();\n }\n },\n [onCancel],\n );\n\n useEffect(() => {\n document.addEventListener(\"keydown\", handleKeyDown);\n return () => document.removeEventListener(\"keydown\", handleKeyDown);\n }, [handleKeyDown]);\n\n const handleMouseDown = useCallback((e: React.MouseEvent) => {\n e.preventDefault();\n dragging.current = true;\n setSelection({\n startX: e.clientX,\n startY: e.clientY,\n endX: e.clientX,\n endY: e.clientY,\n });\n }, []);\n\n const handleMouseMove = useCallback((e: React.MouseEvent) => {\n if (!dragging.current) return;\n setSelection((prev) => {\n if (!prev) return prev;\n return { ...prev, endX: e.clientX, endY: e.clientY };\n });\n }, []);\n\n const handleMouseUp = useCallback(() => {\n if (!dragging.current || !selection) return;\n dragging.current = false;\n\n const x = Math.min(selection.startX, selection.endX);\n const y = Math.min(selection.startY, selection.endY);\n const width = Math.abs(selection.endX - selection.startX);\n const height = Math.abs(selection.endY - selection.startY);\n\n if (width >= MIN_SIZE && height >= MIN_SIZE) {\n onSelect({ x, y, width, height });\n } else {\n setSelection(null);\n }\n }, [selection, onSelect]);\n\n // Compute the normalized rect for display\n const rect = selection\n ? {\n x: Math.min(selection.startX, selection.endX),\n y: Math.min(selection.startY, selection.endY),\n w: Math.abs(selection.endX - selection.startX),\n h: Math.abs(selection.endY - selection.startY),\n }\n : null;\n\n return (\n <div\n data-afterbefore=\"true\"\n onMouseDown={handleMouseDown}\n onMouseMove={handleMouseMove}\n onMouseUp={handleMouseUp}\n style={{\n position: \"fixed\",\n inset: 0,\n zIndex: 2147483647,\n cursor: \"crosshair\",\n }}\n >\n {/* Dimmed overlay using clip-path to cut out the selection */}\n <div\n style={{\n position: \"absolute\",\n inset: 0,\n background: \"rgba(0, 0, 0, 0.4)\",\n pointerEvents: \"none\",\n ...(rect && rect.w > 0 && rect.h > 0\n ? {\n clipPath: `polygon(\n 0% 0%, 0% 100%, 100% 100%, 100% 0%, 0% 0%,\n ${rect.x}px ${rect.y}px,\n ${rect.x}px ${rect.y + rect.h}px,\n ${rect.x + rect.w}px ${rect.y + rect.h}px,\n ${rect.x + rect.w}px ${rect.y}px,\n ${rect.x}px ${rect.y}px\n )`,\n }\n : {}),\n }}\n />\n\n {/* Selection border and label */}\n {rect && rect.w > 0 && rect.h > 0 && (\n <>\n <div\n style={{\n position: \"absolute\",\n left: rect.x,\n top: rect.y,\n width: rect.w,\n height: rect.h,\n border: \"2px solid rgba(59, 130, 246, 0.8)\",\n borderRadius: 2,\n pointerEvents: \"none\",\n boxShadow: \"0 0 0 1px rgba(0,0,0,0.3)\",\n }}\n />\n {/* Dimensions label */}\n <div\n style={{\n position: \"absolute\",\n left: rect.x + rect.w / 2,\n top: rect.y + rect.h + 8,\n transform: \"translateX(-50%)\",\n background: \"rgba(24, 24, 27, 0.9)\",\n color: \"rgba(255,255,255,0.9)\",\n fontSize: 11,\n fontFamily: \"system-ui, -apple-system, monospace\",\n padding: \"2px 8px\",\n borderRadius: 4,\n whiteSpace: \"nowrap\",\n pointerEvents: \"none\",\n }}\n >\n {Math.round(rect.w)} &times; {Math.round(rect.h)}\n </div>\n </>\n )}\n\n {/* Instruction hint when nothing selected yet */}\n {!selection && (\n <div\n style={{\n position: \"absolute\",\n top: \"50%\",\n left: \"50%\",\n transform: \"translate(-50%, -50%)\",\n color: \"rgba(255,255,255,0.7)\",\n fontSize: 14,\n fontFamily: \"system-ui, -apple-system, sans-serif\",\n pointerEvents: \"none\",\n textShadow: \"0 1px 4px rgba(0,0,0,0.5)\",\n }}\n >\n Drag to select an area &middot; Esc to cancel\n </div>\n )}\n </div>\n );\n}\n","\"use client\";\n\nimport React, { useState, useRef, useEffect, useCallback } from \"react\";\n\ninterface StatusProps {\n onReset: () => void;\n position: { x: number; y: number };\n onClose: () => void;\n}\n\ninterface Toast {\n message: string;\n type: \"success\" | \"error\";\n}\n\nconst PANEL_WIDTH = 220;\n\nexport function Status({ onReset, position, onClose }: StatusProps) {\n const panelRef = useRef<HTMLDivElement>(null);\n const [toast, setToast] = useState<Toast | null>(null);\n const [pushing, setPushing] = useState(false);\n\n const showToast = useCallback((message: string, type: Toast[\"type\"]) => {\n setToast({ message, type });\n setTimeout(() => setToast(null), 3000);\n }, []);\n\n // Click outside to close\n useEffect(() => {\n const handler = (e: MouseEvent) => {\n if (panelRef.current && !panelRef.current.contains(e.target as Node)) {\n onClose();\n }\n };\n document.addEventListener(\"mousedown\", handler);\n return () => document.removeEventListener(\"mousedown\", handler);\n }, [onClose]);\n\n // Escape to close\n useEffect(() => {\n const handler = (e: KeyboardEvent) => {\n if (e.key === \"Escape\") onClose();\n };\n document.addEventListener(\"keydown\", handler);\n return () => document.removeEventListener(\"keydown\", handler);\n }, [onClose]);\n\n const handleOpenFolder = async () => {\n try {\n const res = await fetch(\"/__afterbefore/open\", { method: \"POST\" });\n if (!res.ok) throw new Error();\n showToast(\"Opened folder\", \"success\");\n } catch {\n showToast(\"Could not open folder\", \"error\");\n }\n };\n\n const handleCopyMarkdown = async () => {\n try {\n const res = await fetch(\"/__afterbefore/markdown\");\n if (!res.ok) throw new Error();\n const { markdown } = await res.json();\n await navigator.clipboard.writeText(markdown);\n showToast(\"Copied!\", \"success\");\n } catch {\n showToast(\"Copy failed\", \"error\");\n }\n };\n\n const handlePush = async () => {\n setPushing(true);\n try {\n const res = await fetch(\"/__afterbefore/push\", { method: \"POST\" });\n const data = await res.json();\n if (!res.ok) {\n showToast(data.error || \"Push failed\", \"error\");\n } else if (data.pr) {\n showToast(`Posted to PR #${data.pr}`, \"success\");\n } else {\n showToast(\"No PR found\", \"error\");\n }\n } catch {\n showToast(\"Push failed\", \"error\");\n } finally {\n setPushing(false);\n }\n };\n\n const handleReset = () => {\n onReset();\n onClose();\n };\n\n const panelLeft = Math.max(\n 8,\n Math.min(position.x - PANEL_WIDTH / 2 + 20, window.innerWidth - PANEL_WIDTH - 8),\n );\n const panelBottom = window.innerHeight - position.y + 8;\n\n const buttonStyle: React.CSSProperties = {\n display: \"flex\",\n alignItems: \"center\",\n gap: 6,\n width: \"100%\",\n padding: \"7px 10px\",\n border: \"none\",\n background: \"transparent\",\n color: \"rgba(255,255,255,0.9)\",\n fontSize: 13,\n borderRadius: 6,\n cursor: \"pointer\",\n textAlign: \"left\" as const,\n fontFamily: \"system-ui, -apple-system, sans-serif\",\n transition: \"background 0.1s\",\n };\n\n const onEnter = (e: React.MouseEvent) => {\n (e.currentTarget as HTMLButtonElement).style.background =\n \"rgba(255,255,255,0.1)\";\n };\n const onLeave = (e: React.MouseEvent) => {\n (e.currentTarget as HTMLButtonElement).style.background = \"transparent\";\n };\n\n return (\n <div\n ref={panelRef}\n data-afterbefore=\"true\"\n style={{\n position: \"fixed\",\n left: panelLeft,\n bottom: panelBottom,\n width: PANEL_WIDTH,\n background: \"rgba(24, 24, 27, 0.95)\",\n borderRadius: 10,\n boxShadow:\n \"0 4px 20px rgba(0,0,0,0.4), 0 0 0 1px rgba(255,255,255,0.08)\",\n zIndex: 2147483647,\n fontFamily: \"system-ui, -apple-system, sans-serif\",\n backdropFilter: \"blur(12px)\",\n overflow: \"hidden\",\n }}\n >\n {/* Header */}\n <div\n style={{\n padding: \"10px 12px 6px\",\n fontSize: 12,\n fontWeight: 600,\n color: \"rgba(255,255,255,0.5)\",\n letterSpacing: \"0.02em\",\n }}\n >\n Before &amp; After captured\n </div>\n\n {/* Actions */}\n <div style={{ padding: \"0 4px 4px\" }}>\n <button\n style={buttonStyle}\n onClick={handleOpenFolder}\n onMouseEnter={onEnter}\n onMouseLeave={onLeave}\n >\n <FolderIcon />\n Open Folder\n </button>\n <button\n style={buttonStyle}\n onClick={handleCopyMarkdown}\n onMouseEnter={onEnter}\n onMouseLeave={onLeave}\n >\n <CopyIcon />\n Copy Markdown\n </button>\n <button\n style={buttonStyle}\n onClick={handlePush}\n disabled={pushing}\n onMouseEnter={onEnter}\n onMouseLeave={onLeave}\n >\n <PushIcon />\n {pushing ? \"Pushing...\" : \"Push to PR\"}\n </button>\n\n {/* Separator */}\n <div\n style={{\n height: 1,\n background: \"rgba(255,255,255,0.08)\",\n margin: \"4px 6px\",\n }}\n />\n\n <button\n style={{ ...buttonStyle, color: \"rgba(255,255,255,0.5)\" }}\n onClick={handleReset}\n onMouseEnter={onEnter}\n onMouseLeave={onLeave}\n >\n <ResetIcon />\n Reset\n </button>\n </div>\n\n {/* Toast */}\n {toast && (\n <div\n style={{\n position: \"absolute\",\n bottom: \"100%\",\n left: \"50%\",\n transform: \"translateX(-50%)\",\n marginBottom: 8,\n padding: \"6px 12px\",\n borderRadius: 6,\n fontSize: 12,\n fontWeight: 500,\n whiteSpace: \"nowrap\",\n color: \"white\",\n background:\n toast.type === \"success\"\n ? \"rgba(34, 197, 94, 0.9)\"\n : \"rgba(239, 68, 68, 0.9)\",\n boxShadow: \"0 2px 8px rgba(0,0,0,0.3)\",\n }}\n >\n {toast.message}\n </div>\n )}\n </div>\n );\n}\n\nfunction FolderIcon() {\n return (\n <svg\n width=\"14\"\n height=\"14\"\n viewBox=\"0 0 14 14\"\n style={{ color: \"rgba(255,255,255,0.5)\" }}\n >\n <path\n d=\"M1.5 3A1.5 1.5 0 013 1.5h2.38a1 1 0 01.72.3L7 2.72a1 1 0 00.72.3H11A1.5 1.5 0 0112.5 4.5v6A1.5 1.5 0 0111 12H3A1.5 1.5 0 011.5 10.5V3z\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"1.3\"\n />\n </svg>\n );\n}\n\nfunction CopyIcon() {\n return (\n <svg\n width=\"14\"\n height=\"14\"\n viewBox=\"0 0 14 14\"\n style={{ color: \"rgba(255,255,255,0.5)\" }}\n >\n <rect\n x=\"4\"\n y=\"4\"\n width=\"8.5\"\n height=\"8.5\"\n rx=\"1.5\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"1.3\"\n />\n <path\n d=\"M10 4V2.5A1.5 1.5 0 008.5 1h-6A1.5 1.5 0 001 2.5v6A1.5 1.5 0 002.5 10H4\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"1.3\"\n />\n </svg>\n );\n}\n\nfunction PushIcon() {\n return (\n <svg\n width=\"14\"\n height=\"14\"\n viewBox=\"0 0 14 14\"\n style={{ color: \"rgba(255,255,255,0.5)\" }}\n >\n <path\n d=\"M7 11V3m0 0L4 6m3-3l3 3\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"1.3\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n />\n </svg>\n );\n}\n\nfunction ResetIcon() {\n return (\n <svg\n width=\"14\"\n height=\"14\"\n viewBox=\"0 0 14 14\"\n style={{ color: \"rgba(255,255,255,0.4)\" }}\n >\n <path\n d=\"M2.5 7a4.5 4.5 0 118 2.5\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"1.3\"\n strokeLinecap=\"round\"\n />\n <path\n d=\"M2.5 3v4h4\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"1.3\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n />\n </svg>\n );\n}\n"],"mappings":"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","names":["useState","useCallback","useRef","useCallback","useState","useEffect","useRef","useCallback","jsx","jsxs","useState","useRef","useCallback","useEffect","jsx","jsxs","useState","useRef","useEffect","useCallback","jsx","jsxs","jsx","jsxs","useState","useRef","useCallback"]}
@@ -2,7 +2,7 @@ import {
2
2
  handleOpen,
3
3
  handlePush,
4
4
  handleSave
5
- } from "../chunk-XRQ4MAEV.js";
5
+ } from "../chunk-DAWAW3ZL.js";
6
6
 
7
7
  // src/server/middleware.ts
8
8
  import { NextResponse } from "next/server";
@@ -2,7 +2,7 @@ import {
2
2
  handleOpen,
3
3
  handlePush,
4
4
  handleSave
5
- } from "../chunk-XRQ4MAEV.js";
5
+ } from "../chunk-DAWAW3ZL.js";
6
6
 
7
7
  // src/server/route.ts
8
8
  import { NextResponse } from "next/server";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "afterbefore",
3
- "version": "0.2.0",
3
+ "version": "0.2.2",
4
4
  "description": "Before/after screenshot overlay for Next.js development",
5
5
  "type": "module",
6
6
  "main": "./dist/overlay/index.js",
@@ -12,6 +12,8 @@
12
12
  },
13
13
  "./next": {
14
14
  "import": "./dist/next.js",
15
+ "require": "./dist/next.js",
16
+ "default": "./dist/next.js",
15
17
  "types": "./dist/next.d.ts"
16
18
  },
17
19
  "./server": {
@@ -55,9 +57,15 @@
55
57
  "react-dom": ">=18.0.0"
56
58
  },
57
59
  "peerDependenciesMeta": {
58
- "next": { "optional": false },
59
- "react": { "optional": false },
60
- "react-dom": { "optional": false }
60
+ "next": {
61
+ "optional": false
62
+ },
63
+ "react": {
64
+ "optional": false
65
+ },
66
+ "react-dom": {
67
+ "optional": false
68
+ }
61
69
  },
62
70
  "devDependencies": {
63
71
  "@types/node": "^22.0.0",
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/server/save.ts","../src/server/push.ts","../src/server/open.ts"],"sourcesContent":["import { NextRequest, NextResponse } from \"next/server\";\nimport { writeFile, mkdir } from \"node:fs/promises\";\nimport { join } from \"node:path\";\n\nconst VALID_TYPES = [\"before\", \"after\"] as const;\ntype ScreenshotType = (typeof VALID_TYPES)[number];\n\nconst VALID_MODES = [\"fullpage\", \"viewport\", \"area\"] as const;\n\nconst DATA_URL_PREFIX = \"data:image/png;base64,\";\n\ninterface SaveRequestBody {\n type: ScreenshotType;\n mode: string;\n image: string;\n}\n\nexport async function handleSave(req: NextRequest): Promise<NextResponse> {\n let body: SaveRequestBody;\n try {\n body = await req.json();\n } catch {\n return NextResponse.json(\n { error: \"Invalid JSON body\" },\n { status: 400 },\n );\n }\n\n const { type, mode, image } = body;\n\n if (!VALID_TYPES.includes(type as ScreenshotType)) {\n return NextResponse.json(\n { error: `Invalid type: must be \"before\" or \"after\"` },\n { status: 400 },\n );\n }\n\n if (!VALID_MODES.includes(mode as (typeof VALID_MODES)[number])) {\n return NextResponse.json(\n { error: `Invalid mode: must be \"fullpage\", \"viewport\", or \"area\"` },\n { status: 400 },\n );\n }\n\n if (typeof image !== \"string\" || !image.startsWith(DATA_URL_PREFIX)) {\n return NextResponse.json(\n { error: \"Invalid image: must be a data:image/png;base64 data URL\" },\n { status: 400 },\n );\n }\n\n const base64 = image.slice(DATA_URL_PREFIX.length);\n const buffer = Buffer.from(base64, \"base64\");\n\n const dir = join(process.cwd(), \".afterbefore\");\n const filename = `${type}.png`;\n const filepath = join(dir, filename);\n const relativePath = `.afterbefore/${filename}`;\n\n try {\n await mkdir(dir, { recursive: true });\n await writeFile(filepath, buffer);\n } catch (err) {\n return NextResponse.json(\n { error: \"Failed to write screenshot\", detail: String(err) },\n { status: 500 },\n );\n }\n\n return NextResponse.json({ success: true, path: relativePath });\n}\n","import { NextRequest, NextResponse } from \"next/server\";\nimport { execFile } from \"node:child_process\";\nimport { access } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport { promisify } from \"node:util\";\n\nconst execFileAsync = promisify(execFile);\n\nconst DIR = \".afterbefore\";\n\ninterface PrInfo {\n number: number;\n url: string;\n headRepository: { owner: { login: string }; name: string };\n headRefName: string;\n}\n\nasync function run(\n cmd: string,\n args: string[],\n): Promise<{ stdout: string; stderr: string }> {\n return execFileAsync(cmd, args, { cwd: process.cwd() });\n}\n\nasync function ghAvailable(): Promise<boolean> {\n try {\n await run(\"gh\", [\"--version\"]);\n return true;\n } catch {\n return false;\n }\n}\n\nasync function getPrInfo(): Promise<PrInfo | null> {\n try {\n const { stdout } = await run(\"gh\", [\n \"pr\",\n \"view\",\n \"--json\",\n \"number,url,headRepository,headRefName\",\n ]);\n return JSON.parse(stdout) as PrInfo;\n } catch {\n return null;\n }\n}\n\nasync function fileExists(path: string): Promise<boolean> {\n try {\n await access(path);\n return true;\n } catch {\n return false;\n }\n}\n\nexport async function handlePush(req: NextRequest): Promise<NextResponse> {\n // 1. Check gh CLI availability\n if (!(await ghAvailable())) {\n return NextResponse.json(\n { success: false, error: \"GitHub CLI (gh) is not installed or not in PATH\" },\n { status: 500 },\n );\n }\n\n // 2. Check for active PR\n const pr = await getPrInfo();\n if (!pr) {\n return NextResponse.json(\n { success: false, error: \"No PR found for current branch\" },\n { status: 404 },\n );\n }\n\n // 3. Check that screenshot files exist\n const dir = join(process.cwd(), DIR);\n const beforePath = join(dir, \"before.png\");\n const afterPath = join(dir, \"after.png\");\n\n const [hasBefore, hasAfter] = await Promise.all([\n fileExists(beforePath),\n fileExists(afterPath),\n ]);\n\n if (!hasBefore || !hasAfter) {\n const missing = [\n !hasBefore && \"before.png\",\n !hasAfter && \"after.png\",\n ].filter(Boolean);\n return NextResponse.json(\n { success: false, error: `Missing screenshots: ${missing.join(\", \")}` },\n { status: 400 },\n );\n }\n\n // 4. Stage, commit, and push the screenshots\n try {\n await run(\"git\", [\"add\", beforePath, afterPath]);\n await run(\"git\", [\n \"commit\",\n \"-m\",\n \"chore: add before/after screenshots\",\n ]);\n } catch (err) {\n // Commit may fail if files are already committed with no changes.\n // That's fine -- we still want to push and comment.\n const msg = String(err);\n if (!msg.includes(\"nothing to commit\")) {\n return NextResponse.json(\n { success: false, error: \"Failed to commit screenshots\", detail: msg },\n { status: 500 },\n );\n }\n }\n\n try {\n await run(\"git\", [\"push\"]);\n } catch (err) {\n return NextResponse.json(\n { success: false, error: \"Failed to push to remote\", detail: String(err) },\n { status: 500 },\n );\n }\n\n // 5. Build raw GitHub URLs for the images\n const owner = pr.headRepository.owner.login;\n const repo = pr.headRepository.name;\n const branch = pr.headRefName;\n const rawBase = `https://raw.githubusercontent.com/${owner}/${repo}/${branch}`;\n\n const beforeUrl = `${rawBase}/${DIR}/before.png`;\n const afterUrl = `${rawBase}/${DIR}/after.png`;\n\n // Cache-bust with timestamp so GitHub doesn't serve stale images\n const ts = Date.now();\n const commentBody = [\n \"## Before / After\",\n \"\",\n \"| Before | After |\",\n \"|--------|-------|\",\n `| ![before](${beforeUrl}?t=${ts}) | ![after](${afterUrl}?t=${ts}) |`,\n ].join(\"\\n\");\n\n // 6. Post PR comment\n let commentUrl: string | undefined;\n try {\n const { stdout } = await run(\"gh\", [\n \"pr\",\n \"comment\",\n String(pr.number),\n \"--body\",\n commentBody,\n ]);\n // gh pr comment prints the comment URL to stdout\n commentUrl = stdout.trim() || undefined;\n } catch (err) {\n return NextResponse.json(\n { success: false, error: \"Failed to post PR comment\", detail: String(err) },\n { status: 500 },\n );\n }\n\n return NextResponse.json({\n success: true,\n prNumber: pr.number,\n prUrl: pr.url,\n commentUrl,\n });\n}\n","import { NextRequest, NextResponse } from \"next/server\";\nimport { exec } from \"node:child_process\";\nimport { join } from \"node:path\";\n\nexport async function handleOpen(_req: NextRequest): Promise<NextResponse> {\n const dir = join(process.cwd(), \".afterbefore\");\n\n const cmd =\n process.platform === \"darwin\"\n ? \"open\"\n : process.platform === \"win32\"\n ? \"explorer\"\n : \"xdg-open\";\n\n exec(`${cmd} \"${dir}\"`);\n\n return NextResponse.json({ success: true });\n}\n"],"mappings":"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","names":["NextResponse","join","NextResponse","join"]}