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.
@@ -0,0 +1,198 @@
1
+ const api = window.pmptrPrompter;
2
+
3
+ const $ = (id) => document.getElementById(id);
4
+ const frame = $("frame");
5
+ const track = $("track");
6
+ const text = $("text");
7
+ const readArea = $("readArea");
8
+ const hud = $("hud");
9
+ const btnPlay = $("btnPlay");
10
+ const btnReset = $("btnReset");
11
+ const btnLock = $("btnLock");
12
+ const btnClose = $("btnClose");
13
+ const iconPlay = $("iconPlay");
14
+ const iconLock = $("iconLock");
15
+
16
+ let settings = null;
17
+ let playing = true;
18
+ let locked = false;
19
+ let trackY = 0;
20
+ let lastTs = 0;
21
+ let rafId = null;
22
+
23
+ function hexToRgb(hex) {
24
+ const h = (hex || "#000000").replace("#", "");
25
+ const full =
26
+ h.length === 3
27
+ ? h
28
+ .split("")
29
+ .map((c) => c + c)
30
+ .join("")
31
+ : h;
32
+ const n = parseInt(full, 16);
33
+ return `${(n >> 16) & 255}, ${(n >> 8) & 255}, ${n & 255}`;
34
+ }
35
+
36
+ function applySettings() {
37
+ if (!settings) return;
38
+ const root = document.documentElement.style;
39
+ root.setProperty("--text", settings.color || "#ffffff");
40
+ root.setProperty("--hl", settings.hl || "#ffd84d");
41
+ root.setProperty("--bg-rgb", hexToRgb("#000000"));
42
+ root.setProperty("--bg-alpha", String((settings.bg ?? 35) / 100));
43
+ root.setProperty("--dim", String(settings.dim ?? 0));
44
+ root.setProperty("--font", `${settings.font || 44}px`);
45
+ root.setProperty("--lh", String(settings.lh || 1.45));
46
+ root.setProperty("--ls", `${settings.ls || 0}px`);
47
+ root.setProperty("--margin", `${settings.margin || 0}px`);
48
+ root.setProperty("--stroke", `${settings.stroke || 0}px`);
49
+ root.setProperty("--mirror", settings.mirror ? "scaleX(-1)" : "none");
50
+ root.setProperty("--tt", settings.uppercase ? "uppercase" : "none");
51
+ root.setProperty("--weight", settings.bold ? "700" : "500");
52
+ root.setProperty("--show-line", settings.showReadingLine ? "block" : "none");
53
+
54
+ frame.classList.toggle("dim", (settings.dim ?? 0) > 0);
55
+
56
+ text.innerHTML = formatScript(settings.script || "");
57
+ resetScroll();
58
+ }
59
+
60
+ function formatScript(raw) {
61
+ const safe = (raw || "")
62
+ .replace(/&/g, "&")
63
+ .replace(/</g, "&lt;")
64
+ .replace(/>/g, "&gt;");
65
+ const paragraphs = safe
66
+ .split(/\n{2,}/)
67
+ .map((p) => p.trim())
68
+ .filter(Boolean)
69
+ .map((p) => `<p>${p.replace(/\n/g, "<br>")}</p>`)
70
+ .join("\n");
71
+ return paragraphs || "<p><i>(empty script)</i></p>";
72
+ }
73
+
74
+ function resetScroll() {
75
+ trackY = readArea.clientHeight / 2;
76
+ render();
77
+ }
78
+
79
+ function play() {
80
+ playing = true;
81
+ iconPlay.textContent = "❚❚";
82
+ lastTs = 0;
83
+ if (!rafId) rafId = requestAnimationFrame(loop);
84
+ api.sendState({ playing: true, locked });
85
+ }
86
+ function pause() {
87
+ playing = false;
88
+ iconPlay.textContent = "▶";
89
+ if (rafId) cancelAnimationFrame(rafId);
90
+ rafId = null;
91
+ api.sendState({ playing: false, locked });
92
+ }
93
+ function togglePlay() {
94
+ if (playing) pause();
95
+ else play();
96
+ }
97
+
98
+ function loop(ts) {
99
+ if (!playing) return;
100
+ if (!lastTs) lastTs = ts;
101
+ const dt = (ts - lastTs) / 1000;
102
+ lastTs = ts;
103
+ const speed = (settings && settings.speed) || 40;
104
+ trackY -= speed * dt;
105
+ render();
106
+ if (-trackY > track.scrollHeight - readArea.clientHeight / 2) {
107
+ pause();
108
+ return;
109
+ }
110
+ rafId = requestAnimationFrame(loop);
111
+ }
112
+
113
+ function render() {
114
+ track.style.transform = `translate3d(0, ${trackY}px, 0)`;
115
+ }
116
+
117
+ async function setLocked(v) {
118
+ locked = !!v;
119
+ hud.dataset.locked = String(locked);
120
+ iconLock.textContent = locked ? "🔒" : "🔓";
121
+ btnLock.setAttribute("aria-pressed", String(locked));
122
+ await api.setClickThrough(locked);
123
+ api.sendState({ locked, playing });
124
+ }
125
+
126
+ function adjustSpeed(delta) {
127
+ if (!settings) return;
128
+ const next = Math.max(5, Math.min(300, (settings.speed || 40) + delta));
129
+ if (next === settings.speed) return;
130
+ settings.speed = next;
131
+ api.sendState({ speed: next, locked, playing });
132
+ }
133
+
134
+ function wireHud() {
135
+ btnPlay.addEventListener("click", togglePlay);
136
+ btnReset.addEventListener("click", () => {
137
+ lastTs = 0;
138
+ resetScroll();
139
+ });
140
+ btnLock.addEventListener("click", () => setLocked(!locked));
141
+ btnClose.addEventListener("click", () => window.close());
142
+
143
+ readArea.addEventListener(
144
+ "wheel",
145
+ (e) => {
146
+ e.preventDefault();
147
+ trackY -= e.deltaY;
148
+ render();
149
+ },
150
+ { passive: false }
151
+ );
152
+ }
153
+
154
+ function wireKeys() {
155
+ window.addEventListener("keydown", (e) => {
156
+ if (e.key === " " || e.code === "Space") {
157
+ e.preventDefault();
158
+ togglePlay();
159
+ } else if (e.key === "r" || e.key === "R") {
160
+ lastTs = 0;
161
+ resetScroll();
162
+ } else if (e.key === "ArrowUp") {
163
+ e.preventDefault();
164
+ adjustSpeed(+5);
165
+ } else if (e.key === "ArrowDown") {
166
+ e.preventDefault();
167
+ adjustSpeed(-5);
168
+ } else if (e.key === "l" || e.key === "L") {
169
+ setLocked(!locked);
170
+ } else if (e.key === "Escape") {
171
+ window.close();
172
+ }
173
+ });
174
+ }
175
+
176
+ function boot() {
177
+ wireHud();
178
+ wireKeys();
179
+ applySettings({});
180
+ api.onSettings((s) => {
181
+ if (!s) return;
182
+ settings = s;
183
+ applySettings();
184
+ if (playing) play();
185
+ if (typeof s.clickThrough === "boolean" && s.clickThrough !== locked) {
186
+ setLocked(s.clickThrough);
187
+ }
188
+ });
189
+ api.onCommand((cmd) => {
190
+ if (!cmd) return;
191
+ if (cmd.type === "reset") resetScroll();
192
+ if (cmd.type === "play") play();
193
+ if (cmd.type === "pause") pause();
194
+ });
195
+ api.sendState({ playing, locked });
196
+ }
197
+
198
+ boot();