Skip to content

Commit 9110b06

Browse files
MaxGhenisclaude
andcommitted
Initial commit: Scrollywood Chrome extension
One-click smooth scroll video recording with Art Deco cinema aesthetic. Features: - Tab capture and MediaRecorder for video recording - Configurable scroll duration and delay - Background service worker + offscreen document architecture - CI with GitHub Actions - Automated Chrome Web Store publishing on tags Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
0 parents  commit 9110b06

19 files changed

Lines changed: 1364 additions & 0 deletions

.github/workflows/ci.yml

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
name: CI
2+
3+
on:
4+
push:
5+
branches: [main]
6+
pull_request:
7+
branches: [main]
8+
9+
jobs:
10+
test:
11+
runs-on: ubuntu-latest
12+
13+
steps:
14+
- uses: actions/checkout@v4
15+
16+
- name: Setup Bun
17+
uses: oven-sh/setup-bun@v2
18+
with:
19+
bun-version: latest
20+
21+
- name: Install dependencies
22+
run: bun install
23+
24+
- name: Run tests
25+
run: bun test
26+
27+
- name: Build extension package
28+
run: bun run build
29+
30+
- name: Upload extension artifact
31+
uses: actions/upload-artifact@v4
32+
with:
33+
name: scrollywood-extension
34+
path: dist/scrollywood-*.zip
35+
36+
# Publish to Chrome Web Store on release
37+
publish:
38+
needs: test
39+
runs-on: ubuntu-latest
40+
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v')
41+
42+
steps:
43+
- uses: actions/checkout@v4
44+
45+
- name: Setup Bun
46+
uses: oven-sh/setup-bun@v2
47+
with:
48+
bun-version: latest
49+
50+
- name: Install dependencies
51+
run: bun install
52+
53+
- name: Build extension package
54+
run: bun run build
55+
56+
- name: Upload to Chrome Web Store
57+
uses: mnao305/chrome-extension-upload@v5.0.0
58+
with:
59+
file-path: dist/scrollywood-*.zip
60+
extension-id: ${{ secrets.CHROME_EXTENSION_ID }}
61+
client-id: ${{ secrets.CHROME_CLIENT_ID }}
62+
client-secret: ${{ secrets.CHROME_CLIENT_SECRET }}
63+
refresh-token: ${{ secrets.CHROME_REFRESH_TOKEN }}

.gitignore

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
# Dependencies
2+
node_modules/
3+
4+
# Build artifacts
5+
dist/
6+
*.zip
7+
8+
# Logs
9+
*.log
10+
11+
# OS files
12+
.DS_Store
13+
Thumbs.db
14+
15+
# IDE
16+
.idea/
17+
.vscode/
18+
*.swp
19+
20+
# Test coverage
21+
coverage/
22+
23+
# Lock files (using bun)
24+
package-lock.json
25+
yarn.lock

README.md

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
# Scrollywood 🎬
2+
3+
One-click smooth scroll video recording for any webpage. Capture beautiful, cinematic scroll recordings with an Art Deco-inspired interface.
4+
5+
## Features
6+
7+
- **One-click recording**: Just set duration and click "Action"
8+
- **Smooth scrolling**: Configurable scroll duration (5-300 seconds)
9+
- **Delay before roll**: Optional countdown before scrolling starts
10+
- **Auto-save**: Downloads as WebM when complete
11+
- **Visual feedback**: "REC" badge shows recording status
12+
13+
## Installation
14+
15+
### From Chrome Web Store
16+
*(Coming soon)*
17+
18+
### Manual Installation (Developer Mode)
19+
1. Clone this repository
20+
2. Run `bun install`
21+
3. Open `chrome://extensions/`
22+
4. Enable "Developer mode" (top right)
23+
5. Click "Load unpacked"
24+
6. Select the `Scrollywood` folder
25+
26+
## Development
27+
28+
```bash
29+
# Install dependencies
30+
bun install
31+
32+
# Run tests
33+
bun test
34+
35+
# Run tests in watch mode
36+
bun test:watch
37+
38+
# Build extension ZIP for distribution
39+
bun run build
40+
```
41+
42+
## Publishing to Chrome Web Store
43+
44+
### First-time setup
45+
46+
1. **Create a developer account** at [Chrome Web Store Developer Dashboard](https://chrome.google.com/webstore/devconsole/)
47+
- One-time $5 registration fee
48+
49+
2. **Create OAuth credentials** for automated publishing:
50+
- Go to [Google Cloud Console](https://console.cloud.google.com/)
51+
- Create a new project or select existing
52+
- Enable the Chrome Web Store API
53+
- Create OAuth 2.0 credentials (Desktop app type)
54+
- Note the Client ID and Client Secret
55+
56+
3. **Get a refresh token**:
57+
```bash
58+
# Use the Chrome Web Store API OAuth flow
59+
# Visit: https://accounts.google.com/o/oauth2/auth?response_type=code&scope=https://www.googleapis.com/auth/chromewebstore&client_id=YOUR_CLIENT_ID&redirect_uri=urn:ietf:wg:oauth:2.0:oob
60+
```
61+
62+
4. **Add secrets to GitHub**:
63+
- `CHROME_EXTENSION_ID`: Your extension's ID (after first manual upload)
64+
- `CHROME_CLIENT_ID`: OAuth client ID
65+
- `CHROME_CLIENT_SECRET`: OAuth client secret
66+
- `CHROME_REFRESH_TOKEN`: OAuth refresh token
67+
68+
### Manual publishing
69+
70+
1. Build the extension: `bun run build`
71+
2. Go to [Chrome Web Store Developer Dashboard](https://chrome.google.com/webstore/devconsole/)
72+
3. Upload `dist/scrollywood-v*.zip`
73+
4. Fill out store listing details
74+
5. Submit for review (typically 1-3 days)
75+
76+
### Automated publishing (via CI)
77+
78+
Push a version tag to trigger automatic publishing:
79+
80+
```bash
81+
# Update version in manifest.json
82+
git tag v1.0.1
83+
git push origin v1.0.1
84+
```
85+
86+
## Architecture
87+
88+
```
89+
Scrollywood/
90+
├── manifest.json # Extension manifest (MV3)
91+
├── popup.html/js # Extension popup UI
92+
├── background.js # Service worker (orchestration)
93+
├── background-logic.js # Testable business logic
94+
├── offscreen.html/js # MediaRecorder (needs DOM context)
95+
├── scroll-utils.js # Scroll calculation utilities
96+
└── scripts/build.js # Build script for packaging
97+
```
98+
99+
### Why offscreen document?
100+
101+
Chrome Manifest V3 service workers don't have DOM access, but `MediaRecorder` requires it. The offscreen document provides a DOM context for video recording while the service worker handles orchestration.
102+
103+
## License
104+
105+
MIT

background-logic.js

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
// Background logic - testable functions
2+
3+
let recording = false;
4+
5+
function sleep(ms) {
6+
return new Promise(resolve => setTimeout(resolve, ms));
7+
}
8+
9+
export async function startRecording(tabId, duration, delay) {
10+
if (recording) return;
11+
recording = true;
12+
13+
// Show "REC" badge
14+
chrome.action.setBadgeText({ text: 'REC' });
15+
chrome.action.setBadgeBackgroundColor({ color: '#ff6b6b' });
16+
17+
try {
18+
// Scroll to top first
19+
await chrome.scripting.executeScript({
20+
target: { tabId },
21+
func: () => window.scrollTo({ top: 0, behavior: 'instant' }),
22+
});
23+
24+
await sleep(500);
25+
26+
// Get a MediaStream for the tab
27+
const streamId = await new Promise((resolve, reject) => {
28+
chrome.tabCapture.getMediaStreamId({ targetTabId: tabId }, (id) => {
29+
if (chrome.runtime.lastError) {
30+
reject(new Error(chrome.runtime.lastError.message));
31+
} else {
32+
resolve(id);
33+
}
34+
});
35+
});
36+
37+
// Create offscreen document for MediaRecorder
38+
await setupOffscreenDocument();
39+
40+
// Send stream ID to offscreen document to start recording
41+
chrome.runtime.sendMessage({
42+
action: 'startCapture',
43+
streamId,
44+
tabId,
45+
duration,
46+
delay,
47+
});
48+
49+
} catch (error) {
50+
console.error('Recording error:', error);
51+
recording = false;
52+
chrome.action.setBadgeText({ text: '' });
53+
}
54+
}
55+
56+
export async function setupOffscreenDocument() {
57+
const existingContexts = await chrome.runtime.getContexts({
58+
contextTypes: ['OFFSCREEN_DOCUMENT'],
59+
});
60+
61+
if (existingContexts.length > 0) return;
62+
63+
await chrome.offscreen.createDocument({
64+
url: 'offscreen.html',
65+
reasons: ['USER_MEDIA'],
66+
justification: 'Recording tab video with MediaRecorder',
67+
});
68+
}
69+
70+
export function handleRecordingComplete() {
71+
recording = false;
72+
chrome.action.setBadgeText({ text: '' });
73+
}
74+
75+
export function isRecording() {
76+
return recording;
77+
}
78+
79+
export function resetRecordingState() {
80+
recording = false;
81+
}

background.js

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
// Background service worker for Scrollywood
2+
import { startRecording, handleRecordingComplete } from './background-logic.js';
3+
4+
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
5+
if (message.action === 'startRecording') {
6+
startRecording(message.tabId, message.duration, message.delay);
7+
sendResponse({ status: 'started' });
8+
}
9+
if (message.action === 'recordingComplete') {
10+
handleRecordingComplete();
11+
console.log('Recording saved!');
12+
}
13+
return true;
14+
});

0 commit comments

Comments
 (0)