pmptr 0.1.0

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Jatin Krmalik
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,189 @@
1
+ # <img src="assets/icon.svg" height="40" align="center" /> pmptr
2
+
3
+ [![Release](https://img.shields.io/github/v/release/jatinkrmalik/pmptr?include_prereleases)](https://github.com/jatinkrmalik/pmptr/releases)
4
+ [![Build](https://github.com/jatinkrmalik/pmptr/actions/workflows/build.yml/badge.svg)](https://github.com/jatinkrmalik/pmptr/actions/workflows/build.yml)
5
+ [![Release Pipeline](https://github.com/jatinkrmalik/pmptr/actions/workflows/release.yml/badge.svg)](https://github.com/jatinkrmalik/pmptr/actions/workflows/release.yml)
6
+ [![Nightly](https://github.com/jatinkrmalik/pmptr/actions/workflows/nightly.yml/badge.svg)](https://github.com/jatinkrmalik/pmptr/actions/workflows/nightly.yml)
7
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
8
+ [![Platform](https://img.shields.io/badge/platform-linux%20%7C%20macos%20%7C%20windows-blue)](https://github.com/jatinkrmalik/pmptr)
9
+ [![Node](https://img.shields.io/badge/node-%3E%3D20-339933)](https://nodejs.org)
10
+ [![Electron](https://img.shields.io/badge/electron-31-47848F)](https://www.electronjs.org)
11
+
12
+ > **Beta** — pmptr is in active beta. Expect occasional bugs and breaking changes.
13
+ > Please [report issues](https://github.com/jatinkrmalik/pmptr/issues) you encounter.
14
+
15
+ A minimal virtual teleprompter that lives as a transparent, always-on-top,
16
+ click-through overlay over whatever you do on your screen.
17
+
18
+ ## Download
19
+
20
+ Grab the latest build from the [Releases page](https://github.com/jatinkrmalik/pmptr/releases).
21
+
22
+ | Platform | Format |
23
+ |----------|--------|
24
+ | macOS | `.dmg` |
25
+ | Windows | `.exe` (NSIS installer) |
26
+ | Linux | `.AppImage` or `.deb` |
27
+
28
+ Not code-signed — your OS may warn on first launch. That's expected during beta.
29
+
30
+ ## Install from npm
31
+
32
+ If you have [Node.js](https://nodejs.org) installed, you can install `pmptr` globally and run it from your terminal:
33
+
34
+ ```bash
35
+ npm install -g pmptr
36
+ pmptr
37
+ ```
38
+
39
+ Requires Node.js **20** or later.
40
+
41
+ ## Run from source
42
+
43
+ ```bash
44
+ git clone https://github.com/jatinkrmalik/pmptr.git
45
+ cd pmptr
46
+ npm install
47
+ npm start
48
+ ```
49
+
50
+ Then click **Open floating prompter** in the control window.
51
+
52
+ ## Features
53
+
54
+ - **Control window** — paste your script, tune speed, size, colors, opacity, mirror,
55
+ window dimensions, and more.
56
+ - **Floating prompter window** — transparent, frameless, always on top, with
57
+ a true OS-level click-through "lock" so you can keep working with your mouse
58
+ on whatever is underneath.
59
+ - Settings persist to disk in your Electron user-data folder.
60
+ - Live updates: edits in the control window apply to the prompter instantly.
61
+
62
+ ## Shortcuts (in the floating window)
63
+
64
+ | Key | Action |
65
+ | --------- | --------------------------------------- |
66
+ | `Space` | Play / pause |
67
+ | `R` | Reset scroll to the top |
68
+ | `↑` / `↓` | Speed ± 5 px/s |
69
+ | `L` | Toggle click-through (lock / unlock) |
70
+ | `Esc` | Close the prompter |
71
+
72
+ You can also use the small HUD in the bottom-right of the floating window
73
+ (mouse over it to reveal it).
74
+
75
+ ## Architecture
76
+
77
+ ```mermaid
78
+ graph TB
79
+ subgraph "Electron Main Process"
80
+ M[main.js]
81
+ SETTINGS[(settings.json)]
82
+ end
83
+
84
+ subgraph "Control Window"
85
+ CH[control.html]
86
+ CJ[control.js]
87
+ end
88
+
89
+ subgraph "Prompter Window"
90
+ PH[prompter.html]
91
+ PJ[prompter.js]
92
+ end
93
+
94
+ M -->|spawns| CH
95
+ M -->|spawns| PH
96
+ M <-->|save/load| SETTINGS
97
+ CJ -->|IPC: settings changed| M
98
+ M -->|IPC: settings| PH
99
+ PH -->|IPC: state| M
100
+ M -->|IPC: state| CJ
101
+ ```
102
+
103
+ The app runs in two Electron `BrowserWindow` instances that communicate
104
+ through IPC handlers in the main process. The control window is where you
105
+ paste your script and adjust settings; the prompter window is the
106
+ transparent, always-on-top overlay that scrolls the text. Settings are
107
+ persisted to `settings.json` in the Electron user-data directory.
108
+
109
+ ## How the click-through works
110
+
111
+ The prompter is a separate `BrowserWindow` with `transparent: true`, `frame: false`,
112
+ and `alwaysOnTop: true`. When you toggle "click-through" (the lock), the main
113
+ process calls `win.setIgnoreMouseEvents(true, { forward: true })` — clicks
114
+ and wheel events fall straight through to whatever app is behind, while the
115
+ window stays visible and keeps scrolling. The HUD itself is hidden while
116
+ locked, so nothing on the prompter intercepts your pointer.
117
+
118
+ ## Tweaking transparency
119
+
120
+ The window background is a CSS `rgba()` color set on `.frame`. Move the
121
+ **Background opacity** slider to 0 for fully see-through, or use
122
+ **Background dim** to keep it readable on bright content underneath. The text
123
+ itself stays opaque.
124
+
125
+ ## Project layout
126
+
127
+ ```
128
+ src/
129
+ ├── main/
130
+ │ ├── main.js Electron main: creates both windows, handles IPC,
131
+ │ │ click-through, always-on-top, position presets.
132
+ │ └── preload.js contextBridge for the control window.
133
+ ├── control/
134
+ │ ├── control.html The control panel UI.
135
+ │ ├── control.js Control panel logic.
136
+ │ └── control.css Control panel styles.
137
+ └── prompter/
138
+ ├── prompter.html The floating teleprompter overlay.
139
+ ├── prompter.js Prompter logic (scroll, shortcuts, HUD).
140
+ ├── prompter.css Prompter styles.
141
+ └── prompter-preload.js contextBridge for the prompter window.
142
+
143
+ assets/
144
+ └── icon.svg Application icon.
145
+
146
+ .github/workflows/
147
+ ── build.yml CI build for Linux, macOS, Windows.
148
+ ├── release.yml Tag-triggered release pipeline.
149
+ ├── nightly.yml Daily scheduled builds.
150
+ └── pr-build.yml Comment-triggered PR artifact builds.
151
+ ```
152
+
153
+ ## Development
154
+
155
+ ```bash
156
+ # Install dependencies
157
+ npm install
158
+
159
+ # Run the app
160
+ npm start
161
+
162
+ # Lint code
163
+ npm run lint
164
+
165
+ # Run tests (currently just lint)
166
+ npm test
167
+
168
+ # Build for distribution
169
+ npm run build
170
+ ```
171
+
172
+ ## Known limitations
173
+
174
+ - Wayland compositors vary in their support for `setIgnoreMouseEvents` and
175
+ `setAlwaysOnTop` (the underlying APIs Electron uses). X11 (Xorg) and recent
176
+ KDE / GNOME Wayland work fine; some lighter Wayland compositors may ignore
177
+ these hints. If click-through or always-on-top does not work, the prompter
178
+ is still useful — just drag it to a corner.
179
+ - On macOS you may need to grant Accessibility / Screen Recording permissions
180
+ to the app for click-through to behave predictably across all apps.
181
+ - The app is not code-signed. Your OS may warn on first launch.
182
+
183
+ ## Contributing
184
+
185
+ Contributions are welcome! See [CONTRIBUTING.md](CONTRIBUTING.md) for details.
186
+
187
+ ## License
188
+
189
+ MIT — see [LICENSE](LICENSE) for details.
Binary file
@@ -0,0 +1,10 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" fill="none">
2
+ <!-- Dark background -->
3
+ <rect width="512" height="512" rx="64" fill="#0f1115"/>
4
+ <!-- Screen -->
5
+ <rect x="96" y="144" width="320" height="224" rx="16" stroke="#ffffff" stroke-width="12"/>
6
+ <!-- Text lines -->
7
+ <path d="M144 208h224M144 256h224M144 304h224" stroke="#ffffff" stroke-width="8" stroke-linecap="round"/>
8
+ <!-- Play arrow -->
9
+ <path d="M336 344l-40 24v-48z" fill="#ffd84d"/>
10
+ </svg>
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
package/bin/pmptr.js ADDED
@@ -0,0 +1,12 @@
1
+ #!/usr/bin/env node
2
+
3
+ const { spawn } = require('child_process');
4
+ const path = require('path');
5
+ const electron = require('electron');
6
+
7
+ const appPath = path.join(__dirname, '..');
8
+ const child = spawn(electron, [appPath], { stdio: 'inherit' });
9
+
10
+ child.on('close', (code) => {
11
+ process.exit(code ?? 0);
12
+ });
package/package.json ADDED
@@ -0,0 +1,72 @@
1
+ {
2
+ "name": "pmptr",
3
+ "version": "0.1.0",
4
+ "description": "A minimal virtual teleprompter that lives as a transparent, always-on-top, click-through overlay.",
5
+ "main": "src/main/main.js",
6
+ "bin": {
7
+ "pmptr": "bin/pmptr.js"
8
+ },
9
+ "files": [
10
+ "bin/**/*",
11
+ "src/**/*",
12
+ "assets/**/*"
13
+ ],
14
+ "author": {
15
+ "name": "Jatin Malik",
16
+ "email": "jatinkrmalik@gmail.com"
17
+ },
18
+ "repository": {
19
+ "type": "git",
20
+ "url": "https://github.com/jatinkrmalik/pmptr.git"
21
+ },
22
+ "bugs": {
23
+ "url": "https://github.com/jatinkrmalik/pmptr/issues"
24
+ },
25
+ "homepage": "https://github.com/jatinkrmalik/pmptr",
26
+ "scripts": {
27
+ "start": "electron .",
28
+ "lint": "eslint src/",
29
+ "test": "npm run lint",
30
+ "build": "electron-builder"
31
+ },
32
+ "build": {
33
+ "appId": "com.pmptr.app",
34
+ "productName": "pmptr",
35
+ "directories": {
36
+ "output": "dist"
37
+ },
38
+ "files": [
39
+ "bin/**/*",
40
+ "src/**/*",
41
+ "assets/**/*",
42
+ "package.json"
43
+ ],
44
+ "mac": {
45
+ "target": "dmg",
46
+ "icon": "assets/icon.png"
47
+ },
48
+ "win": {
49
+ "target": "nsis",
50
+ "icon": "assets/icon.png"
51
+ },
52
+ "linux": {
53
+ "target": [
54
+ "AppImage",
55
+ "deb"
56
+ ],
57
+ "icon": "assets/icon.png",
58
+ "category": "Utility"
59
+ }
60
+ },
61
+ "dependencies": {
62
+ "electron": "^31.0.0"
63
+ },
64
+ "devDependencies": {
65
+ "electron-builder": "^24.13.3",
66
+ "eslint": "^8.57.0"
67
+ },
68
+ "engines": {
69
+ "node": ">=20.0.0"
70
+ },
71
+ "license": "MIT"
72
+ }
@@ -0,0 +1,317 @@
1
+ :root {
2
+ --bg: #0f1115;
3
+ --bg-2: #15181f;
4
+ --bg-3: #1c2029;
5
+ --fg: #e7eaf0;
6
+ --muted: #8a93a6;
7
+ --line: #262b36;
8
+ --accent: #7aa2ff;
9
+ --accent-2: #ffd84d;
10
+ --danger: #ff6b6b;
11
+ --ok: #6bd58a;
12
+ --radius: 10px;
13
+ --radius-sm: 6px;
14
+ --shadow: 0 1px 0 rgba(255, 255, 255, 0.03) inset, 0 6px 20px rgba(0, 0, 0, 0.35);
15
+ color-scheme: dark;
16
+ }
17
+
18
+ * {
19
+ box-sizing: border-box;
20
+ }
21
+
22
+ html,
23
+ body {
24
+ margin: 0;
25
+ padding: 0;
26
+ background: var(--bg);
27
+ color: var(--fg);
28
+ font: 14px/1.45 -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
29
+ "Helvetica Neue", Arial, sans-serif;
30
+ min-height: 100vh;
31
+ -webkit-font-smoothing: antialiased;
32
+ }
33
+
34
+ .topbar {
35
+ display: flex;
36
+ align-items: baseline;
37
+ gap: 12px;
38
+ padding: 14px 20px 6px;
39
+ border-bottom: 1px solid var(--line);
40
+ }
41
+ .brand {
42
+ font-weight: 700;
43
+ letter-spacing: 0.5px;
44
+ }
45
+ .sub {
46
+ color: var(--muted);
47
+ font-size: 12px;
48
+ }
49
+
50
+ .layout {
51
+ padding: 16px 20px 28px;
52
+ display: flex;
53
+ flex-direction: column;
54
+ gap: 14px;
55
+ max-width: 720px;
56
+ margin: 0 auto;
57
+ }
58
+
59
+ .card {
60
+ background: var(--bg-2);
61
+ border: 1px solid var(--line);
62
+ border-radius: var(--radius);
63
+ padding: 14px 16px 16px;
64
+ box-shadow: var(--shadow);
65
+ }
66
+ .card h2 {
67
+ margin: 0 0 10px;
68
+ font-size: 12px;
69
+ text-transform: uppercase;
70
+ letter-spacing: 1.2px;
71
+ color: var(--muted);
72
+ font-weight: 600;
73
+ }
74
+ .card-head {
75
+ display: flex;
76
+ align-items: center;
77
+ justify-content: space-between;
78
+ margin-bottom: 8px;
79
+ }
80
+ .card-head h2 {
81
+ margin: 0;
82
+ }
83
+
84
+ textarea#script {
85
+ width: 100%;
86
+ min-height: 180px;
87
+ max-height: 50vh;
88
+ resize: vertical;
89
+ background: var(--bg-3);
90
+ color: var(--fg);
91
+ border: 1px solid var(--line);
92
+ border-radius: var(--radius-sm);
93
+ padding: 10px 12px;
94
+ font: 14px/1.5 ui-monospace, "SF Mono", Menlo, Consolas, monospace;
95
+ outline: none;
96
+ }
97
+ textarea#script:focus {
98
+ border-color: var(--accent);
99
+ }
100
+
101
+ .hint {
102
+ font-size: 12px;
103
+ color: var(--muted);
104
+ margin-top: 6px;
105
+ }
106
+
107
+ .grid {
108
+ display: grid;
109
+ grid-template-columns: 1fr 1fr;
110
+ gap: 12px 16px;
111
+ }
112
+ @media (max-width: 560px) {
113
+ .grid {
114
+ grid-template-columns: 1fr;
115
+ }
116
+ }
117
+
118
+ .field {
119
+ display: flex;
120
+ flex-direction: column;
121
+ gap: 6px;
122
+ min-width: 0;
123
+ }
124
+ .field.wide {
125
+ grid-column: 1 / -1;
126
+ }
127
+ .field .lbl {
128
+ display: flex;
129
+ align-items: baseline;
130
+ gap: 6px;
131
+ font-size: 12px;
132
+ color: var(--muted);
133
+ }
134
+ .field output {
135
+ color: var(--fg);
136
+ font-variant-numeric: tabular-nums;
137
+ font-weight: 600;
138
+ margin-left: auto;
139
+ }
140
+ .field .unit {
141
+ color: var(--muted);
142
+ font-weight: 400;
143
+ }
144
+
145
+ input[type="range"] {
146
+ -webkit-appearance: none;
147
+ appearance: none;
148
+ width: 100%;
149
+ height: 18px;
150
+ background: transparent;
151
+ margin: 0;
152
+ }
153
+ input[type="range"]::-webkit-slider-runnable-track {
154
+ height: 4px;
155
+ background: var(--bg-3);
156
+ border-radius: 999px;
157
+ }
158
+ input[type="range"]::-moz-range-track {
159
+ height: 4px;
160
+ background: var(--bg-3);
161
+ border-radius: 999px;
162
+ }
163
+ input[type="range"]::-webkit-slider-thumb {
164
+ -webkit-appearance: none;
165
+ appearance: none;
166
+ width: 14px;
167
+ height: 14px;
168
+ margin-top: -5px;
169
+ border-radius: 50%;
170
+ background: var(--accent);
171
+ border: 0;
172
+ cursor: pointer;
173
+ box-shadow: 0 0 0 3px rgba(122, 162, 255, 0.15);
174
+ }
175
+ input[type="range"]::-moz-range-thumb {
176
+ width: 14px;
177
+ height: 14px;
178
+ border-radius: 50%;
179
+ background: var(--accent);
180
+ border: 0;
181
+ cursor: pointer;
182
+ }
183
+
184
+ input[type="color"] {
185
+ width: 100%;
186
+ height: 30px;
187
+ background: var(--bg-3);
188
+ border: 1px solid var(--line);
189
+ border-radius: var(--radius-sm);
190
+ padding: 2px;
191
+ cursor: pointer;
192
+ }
193
+
194
+ select {
195
+ width: 100%;
196
+ height: 30px;
197
+ background: var(--bg-3);
198
+ color: var(--fg);
199
+ border: 1px solid var(--line);
200
+ border-radius: var(--radius-sm);
201
+ padding: 0 8px;
202
+ outline: none;
203
+ }
204
+
205
+ .row {
206
+ display: flex;
207
+ flex-wrap: wrap;
208
+ gap: 14px;
209
+ align-items: center;
210
+ margin-top: 12px;
211
+ }
212
+ .row.tight {
213
+ gap: 8px;
214
+ margin-top: 10px;
215
+ }
216
+
217
+ .check {
218
+ display: inline-flex;
219
+ align-items: center;
220
+ gap: 6px;
221
+ font-size: 13px;
222
+ color: var(--fg);
223
+ user-select: none;
224
+ cursor: pointer;
225
+ }
226
+ .check input {
227
+ margin: 0;
228
+ accent-color: var(--accent);
229
+ }
230
+
231
+ button {
232
+ appearance: none;
233
+ border: 1px solid var(--line);
234
+ background: var(--bg-3);
235
+ color: var(--fg);
236
+ padding: 8px 14px;
237
+ border-radius: var(--radius-sm);
238
+ cursor: pointer;
239
+ font: inherit;
240
+ transition: background 80ms ease, border-color 80ms ease, transform 80ms ease;
241
+ }
242
+ button:hover {
243
+ background: #232834;
244
+ }
245
+ button:active {
246
+ transform: translateY(1px);
247
+ }
248
+ button:disabled {
249
+ opacity: 0.5;
250
+ cursor: not-allowed;
251
+ }
252
+ button.primary {
253
+ background: var(--accent);
254
+ color: #0b0d12;
255
+ border-color: transparent;
256
+ font-weight: 600;
257
+ }
258
+ button.primary:hover {
259
+ background: #95b5ff;
260
+ }
261
+ button.ghost {
262
+ background: transparent;
263
+ }
264
+
265
+ .actions {
266
+ display: flex;
267
+ align-items: center;
268
+ gap: 10px;
269
+ flex-wrap: wrap;
270
+ }
271
+ .actions .state {
272
+ margin-left: auto;
273
+ color: var(--muted);
274
+ font-size: 12px;
275
+ font-variant-numeric: tabular-nums;
276
+ }
277
+ .actions .state.ok {
278
+ color: var(--ok);
279
+ }
280
+ .actions .state.warn {
281
+ color: var(--accent-2);
282
+ }
283
+
284
+ details.hint-card summary {
285
+ cursor: pointer;
286
+ color: var(--muted);
287
+ font-size: 13px;
288
+ list-style: none;
289
+ display: flex;
290
+ align-items: center;
291
+ gap: 6px;
292
+ }
293
+ details.hint-card summary::before {
294
+ content: "▸";
295
+ display: inline-block;
296
+ transition: transform 120ms ease;
297
+ color: var(--muted);
298
+ }
299
+ details.hint-card[open] summary::before {
300
+ transform: rotate(90deg);
301
+ }
302
+ details.hint-card ul {
303
+ margin: 10px 0 0;
304
+ padding-left: 20px;
305
+ color: var(--fg);
306
+ }
307
+ details.hint-card li {
308
+ margin: 4px 0;
309
+ }
310
+ kbd {
311
+ background: var(--bg-3);
312
+ border: 1px solid var(--line);
313
+ border-bottom-width: 2px;
314
+ border-radius: 4px;
315
+ padding: 0 5px;
316
+ font: 11px/1.6 ui-monospace, "SF Mono", Menlo, monospace;
317
+ }