taleem-player 0.6.0-rc.1 β†’ 0.7.0-rc

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
@@ -1,215 +1,263 @@
1
1
 
2
- # πŸ“¦ taleem-player
2
+ # Taleem Player
3
3
 
4
- **taleem-player** is a **time-driven player** for **Taleem slides**.
5
4
 
6
- It plays slide decks rendered by `taleem-slides`, using **time** instead of index.
5
+ <img src="https://raw.githubusercontent.com/bilza2023/taleem-player/main/docs/images/taleem.webp" alt="Taleem Player β€” JSON to Web Presentations" />
7
6
 
8
- It is designed for:
7
+ **Taleem Player** is a JavaScript library that converts **JSON slide data** into **web-based presentations**.
9
8
 
10
- * narrated lessons
11
- * timed presentations
12
- * audio / video–synced slides
13
- * external playback control
14
-
15
- At its core, `taleem-player` does one thing:
16
-
17
- > **Given a player-ready deck and a time value, render the correct Taleem slide.**
18
-
19
- It does **not** author decks.
20
- It does **not** fix data.
21
- It does **not** manage time.
9
+ It provides multiple **modes** to display the same JSON presentation in different ways on the web.
22
10
 
23
11
  ---
12
+ ###### Work in progress. Expect minor bugs, but no API breakages.
24
13
 
25
- ## 🌐 Live Docs, Demo & Reference (START HERE)
14
+ Demo and Documentation
26
15
 
27
- πŸ‘‰ **[https://bilza2023.github.io/taleem](https://bilza2023.github.io/taleem)**
16
+ See Taleem Player in action:
28
17
 
29
- This website is the **official reference** for the Taleem system.
18
+ πŸ‘‰ https://bilza2023.github.io/taleem/
30
19
 
31
- It shows:
20
+ This live demo lets you explore:
32
21
 
33
- * all supported slide types
34
- * real visual behavior
35
- * actual rendered output
36
- * examples and demos
22
+ Browser Mode β€” instant, index-based slide rendering
37
23
 
38
- If anything in this README feels unclear:
24
+ Player Mode β€” time-driven, progressive presentations
39
25
 
40
- > **The website is the final authority.**
26
+ Real Taleem slide JSON used in production
41
27
 
42
- The README explains *rules*.
43
- The website shows *results*.
28
+ Shared CSS system powering all display modes
44
29
 
30
+ No screenshots. No mock data.
31
+ What you see is the real engine running in the browser.
45
32
  ---
46
33
 
47
- ## Core idea
34
+ ## What it does
35
+
36
+ * Takes **Taleem slide JSON**
37
+ * Renders it as a **web presentation**
38
+ * Supports **multiple display modes**
39
+ * Ships **ready-to-use CSS**
40
+ * Includes **utilities** for real-world usage
48
41
 
49
- Slides are not just ordered β€” they are placed on a **timeline**.
42
+ ---
50
43
 
51
- `taleem-player` treats a deck as time intervals:
44
+ ## Installation
52
45
 
53
- ```text
54
- [start ─────────── end)
46
+ ```bash
47
+ npm install taleem-player
55
48
  ```
56
49
 
57
- At any given moment:
50
+ ---
58
51
 
59
- * one slide may be active
60
- * or no slide (before or after the timeline)
52
+ ## Display Modes
61
53
 
62
- The player’s job is simple:
54
+ ### 1. Browser Mode
63
55
 
64
- > **time β†’ slide β†’ render**
56
+ **Index-based slide viewer**.
65
57
 
66
- ---
58
+ Use this when you want:
67
59
 
68
- ## What this library does
60
+ * manual navigation
61
+ * previews
62
+ * galleries
63
+ * syllabus / editor views
69
64
 
70
- `taleem-player`:
65
+ #### API
71
66
 
72
- * Accepts a **player-ready deck-v1**
73
- * Assumes **valid timing**
74
- * Owns a single DOM stage
75
- * Resolves which slide is active at a given time
76
- * Renders slides using **taleem-slides**
77
- * Exposes a **minimal API**
67
+ ```js
68
+ import { createTaleemBrowser } from "taleem-player";
78
69
 
79
- Public API:
70
+ const browser = createTaleemBrowser({
71
+ mount: "#app",
72
+ deck
73
+ });
80
74
 
81
- ```js
82
- player.renderAt(time)
83
- player.destroy()
75
+ browser.render(0); // render slide by index
76
+ browser.getTotal(); // total number of slides
84
77
  ```
85
78
 
86
- Time always comes from **outside**.
79
+ Characteristics:
80
+
81
+ * slide index driven
82
+ * no timing
83
+ * deterministic rendering
84
+ * same slide JSON as other modes
87
85
 
88
86
  ---
89
87
 
90
- ## What this library intentionally does NOT do
88
+ ### 2. Player Mode
91
89
 
92
- `taleem-player` does **not**:
90
+ **Time-based slide player**.
93
91
 
94
- * validate deck schemas
95
- * auto-generate timings
96
- * fix broken decks
97
- * define slide layouts
98
- * ship CSS
99
- * manage audio or narration
100
- * play / pause / autoplay
101
- * own clocks or intervals
92
+ Use this when you want:
102
93
 
103
- If a deck is wrong, the player may break.
104
- This is intentional.
94
+ * narrated lessons
95
+ * video / audio sync
96
+ * timed presentations
105
97
 
106
- ---
98
+ #### API
99
+
100
+ ```js
101
+ import { createTaleemPlayer } from "taleem-player";
102
+
103
+ const player = createTaleemPlayer({
104
+ mount: "#app",
105
+ deckDemo and Documentation
106
+
107
+ Live demo and documentation are available here:
108
+
109
+ πŸ‘‰ https://bilza2023.github.io/taleem/
110
+
111
+ The demo showcases:
107
112
 
108
- ## Strict timing model (important)
113
+ browser mode rendering
109
114
 
110
- > **NOTE: taleem-player assumes a *player-ready deck*.**
115
+ player mode rendering
111
116
 
112
- Every slide **must** define:
117
+ real Taleem slide JSON
113
118
 
114
- ```json
115
- {
116
- "start": number,
117
- "end": number
118
- }
119
+ shared CSS across modes
120
+ });
121
+
122
+ player.renderAt(12.5); // render slide at time (seconds)
123
+ player.destroy();
119
124
  ```
120
125
 
121
- Rules:
126
+ Characteristics:
127
+
128
+ * time driven
129
+ * external clock control
130
+ * no play / pause logic
131
+ * one active slide at a time
122
132
 
123
- * time unit is **seconds**
124
- * fractional seconds are allowed (e.g. `3.1`, `3.75`)
125
- * `end` must be greater than `start`
126
- * timings must be monotonic
127
- * no auto-injection
128
- * no browser forgiveness
133
+ ---
134
+ ## Browser Mode vs Player Mode
135
+
136
+ Both modes render the same JSON presentation, but they serve very different purposes.
129
137
 
130
- Deck preparation happens **before** the player
131
- (e.g. via tooling like `assignMockTimings`).
138
+ | Feature | Browser Mode | Player Mode |
139
+ |-------|-------------|-------------|
140
+ | Rendering model | Index-based | Time-based |
141
+ | Navigation | Manual (by slide index) | Progressive (by time) |
142
+ | Timing required | No | Yes (required) |
143
+ | Rendering behavior | One slide at a time | Slides change over time |
144
+ | Control source | Application-driven | External clock / media |
145
+ | Best suited for | Previews, galleries, editors | Narration, video, audio sync |
132
146
 
133
147
  ---
134
148
 
135
- ## Rendering model
149
+ ### Browser Mode
136
150
 
137
- `taleem-player` renders **Taleem slides**.
151
+ Use Browser Mode when you want direct access to slides.
138
152
 
139
- Internally it:
153
+ **Characteristics**
154
+ - Index-based rendering
155
+ - No timing data required
156
+ - Deterministic output
140
157
 
141
- 1. Finds the active slide for the given time
142
- 2. Computes minimal render state
143
- 3. Uses `taleem-slides` to generate HTML
144
- 4. Injects HTML into a single stage
158
+ **Ideal for**
159
+ - previews
160
+ - galleries
161
+ - editors
162
+ - syllabus pages
145
163
 
146
- Rendering is **deterministic** and **stateless**.
164
+
165
+ ⚠️ Important:
166
+ In Player Mode, the user must provide valid and ordered timings (start, end).
167
+ The library does not auto-fix or guess timings.
147
168
 
148
169
  ---
149
170
 
150
- ## Relationship to other Taleem libraries
171
+ ## Utilities
151
172
 
152
- `taleem-player` is part of a small, focused system.
173
+ Taleem Player includes small helper utilities for preparing decks.
153
174
 
154
- ### **taleem-core**
175
+ ### assignMockTimings
155
176
 
156
- Defines schemas and canonical (golden) decks.
177
+ Convert a non-timed deck into a player-ready deck.
157
178
 
158
- ### **taleem-slides**
179
+ ```js
180
+ import { assignMockTimings } from "taleem-player";
159
181
 
160
- Renders slide JSON into HTML + CSS.
182
+ const timedDeck = assignMockTimings(deck, 5);
183
+ ```
161
184
 
162
- ### **taleem-browser**
185
+ ---
163
186
 
164
- Index-based slide viewer (no time).
187
+ ### resolveAssetPaths
165
188
 
166
- ### **Apps / demos**
189
+ Resolve image paths for deployment.
167
190
 
168
- Own time, UI, controls, audio, narration.
191
+ ```js
192
+ import { resolveAssetPaths } from "taleem-player";
169
193
 
170
- Each library does **one job**.
171
- They are composed, not mixed.
194
+ resolveAssetPaths(deck, "/images/");
195
+ ```
172
196
 
173
197
  ---
174
198
 
175
- ## Typical usage
199
+ ### resolveBackground
200
+
201
+ Normalize and resolve background configuration.
176
202
 
177
203
  ```js
178
- import { createTaleemPlayer } from "taleem-player";
204
+ import { resolveBackground } from "taleem-player";
179
205
 
180
- const player = createTaleemPlayer({
181
- mount: "#app",
182
- deck // must be player-ready
183
- });
206
+ resolveBackground(deck, "/images/");
207
+ ```
184
208
 
185
- // external clock
186
- player.renderAt(12.5);
209
+ ---
210
+
211
+ ## CSS
212
+
213
+ Taleem Player ships with built-in styles.
214
+
215
+ ### Base styles
216
+
217
+ ```js
218
+ import "taleem-player/css";
219
+ ```
220
+
221
+ ### Themes
222
+
223
+ ```js
224
+ import "taleem-player/css/dark";
225
+ import "taleem-player/css/light";
226
+ import "taleem-player/css/paper";
187
227
  ```
188
228
 
189
- `taleem-player` never controls time.
190
- Time always belongs to the app.
229
+ CSS controls layout, visibility, and visual behavior.
230
+ Modes share the same CSS.
191
231
 
192
232
  ---
193
233
 
194
- ## Design philosophy (locked)
234
+ ## Input format
195
235
 
196
- * Time is external
197
- * Slides are deterministic
198
- * Seconds are enough
199
- * Libraries stay small
200
- * Control lives at the top
236
+ Taleem Player does **not** define slide structure.
201
237
 
202
- > **The player decides *what* to show.**
203
- > **The app decides *when* and *why*.**
238
+ It renders JSON produced for **taleem-slides**.
239
+
240
+ * Player Mode requires slides with `start` / `end`
241
+ * Browser Mode only needs ordered slides
204
242
 
205
243
  ---
206
244
 
207
- ## Project status
245
+ ## What Taleem Player does NOT do
246
+
247
+ * create slides
248
+ * edit JSON
249
+ * validate schemas
250
+ * manage time or playback
251
+ * handle audio or narration
252
+ * provide UI controls
253
+
254
+ Those belong to the **application**, not the library.
255
+
256
+ ---
208
257
 
209
- **Release Candidate (API locked).**
258
+ ## Status
210
259
 
211
- Only bug fixes should follow.
212
- No new concepts belong here.
260
+ **Release Candidate (API stable)**
213
261
 
214
262
  ---
215
263
 
@@ -0,0 +1,347 @@
1
+
2
+ /* =====================================================
3
+ TALEEM β€” Single Public Stylesheet
4
+
5
+ /* -------------------------------
6
+ Global Page Reset
7
+ ------------------------------- */
8
+
9
+ html,body {
10
+ height: 100%;
11
+ overflow: hidden;
12
+ margin: 0;
13
+ font-family: system-ui, -apple-system, Segoe UI, Roboto, sans-serif;
14
+ background: var(--backgroundColor, #0b1220);
15
+ color: var(--primaryColor, #e6e9ff);
16
+ }
17
+
18
+ #app {
19
+ position: relative;
20
+ width: 100vw;
21
+ min-height: 100vh;
22
+ overflow: hidden;
23
+ }
24
+
25
+ /* -------------------------------
26
+ Browser / Player Layers
27
+ ------------------------------- */
28
+ .taleem-browser-bg {
29
+ position: absolute;
30
+ inset: 0;
31
+ z-index: 0;
32
+ background-size: cover;
33
+ background-position: center;
34
+ }
35
+
36
+ .taleem-browser-slide {
37
+ position: relative;
38
+ z-index: 1;
39
+ }
40
+
41
+ /* -------------------------------
42
+ Base Slide Grammar
43
+ ------------------------------- */
44
+ .slide {
45
+ height: 100vh;
46
+ box-sizing: border-box;
47
+
48
+ display: flex;
49
+ flex-direction: column;
50
+ justify-content: center;
51
+ align-items: center;
52
+
53
+ padding: 64px 80px;
54
+ gap: 32px;
55
+
56
+ overflow: hidden;
57
+ background: transparent;
58
+ color: var(--primaryColor, #e6e9ff);
59
+
60
+ font-size: 2.4rem;
61
+ line-height: 1.6;
62
+ letter-spacing: 0.01em;
63
+ text-align: center;
64
+ }
65
+
66
+ /* -------------------------------
67
+ Global Image Safety
68
+ ------------------------------- */
69
+ .slide img {
70
+ max-width: 100%;
71
+ max-height: 100%;
72
+ height: auto;
73
+ width: auto;
74
+ display: block;
75
+ }
76
+
77
+ /* -------------------------------
78
+ Headings
79
+ ------------------------------- */
80
+ .slide h1 {
81
+ margin: 0;
82
+ letter-spacing: -0.015em;
83
+ }
84
+
85
+ /* -------------------------------
86
+ Slide Types
87
+ ------------------------------- */
88
+ .slide.titleSlide h1 {
89
+ font-size: 5.6rem;
90
+ font-weight: 700;
91
+ line-height: 1.2;
92
+ }
93
+
94
+ .slide.titleAndSubtitle h1 {
95
+ font-size: 5.8rem;
96
+ font-weight: 700;
97
+ }
98
+
99
+ .slide.titleAndSubtitle h2 {
100
+ font-size: 3.8rem;
101
+ font-weight: 400;
102
+ opacity: 0.8;
103
+ margin: 0;
104
+ }
105
+
106
+ .slide.titleAndPara h1 {
107
+ font-size: 4.8rem;
108
+ font-weight: 600;
109
+ }
110
+
111
+ .slide.titleAndPara p {
112
+ font-size: 3rem;
113
+ max-width: 70ch;
114
+ opacity: 0.9;
115
+ margin: 0;
116
+ }
117
+
118
+ /* -------------------------------
119
+ Bullet List
120
+ ------------------------------- */
121
+ .slide.bulletList ul {
122
+ list-style: disc;
123
+ padding-left: 2rem;
124
+ margin: 0;
125
+ }
126
+
127
+ .slide.bulletList li {
128
+ font-size: 3.6rem;
129
+ margin-bottom: 1rem;
130
+ text-align: left;
131
+ font-weight: 500;
132
+ }
133
+
134
+ /* -------------------------------
135
+ Image Variants
136
+ ------------------------------- */
137
+ .slide.imageSlide {
138
+ padding: 0;
139
+ }
140
+
141
+ .slide.imageSlide img {
142
+ object-fit: contain;
143
+ }
144
+
145
+ .slide.imageWithTitle {
146
+ position: relative;
147
+ padding: 48px;
148
+ }
149
+
150
+ .slide.imageWithTitle img {
151
+ height: calc(100vh - 96px);
152
+ border-radius: 12px;
153
+ }
154
+
155
+ .slide.imageWithTitle h1 {
156
+ position: absolute;
157
+ bottom: 64px;
158
+ left: 50%;
159
+ transform: translateX(-50%);
160
+ font-size: 4.6rem;
161
+ background: rgba(0, 0, 0, 0.45);
162
+ padding: 0.4em 0.8em;
163
+ border-radius: 6px;
164
+ }
165
+
166
+ .slide.imageWithCaption {
167
+ display: flex;
168
+ flex-direction: column;
169
+ justify-content: center;
170
+ gap: 32px;
171
+ height: 100vh;
172
+ overflow: hidden;
173
+ }
174
+
175
+ .slide.imageWithCaption img {
176
+ max-height: 55vh; /* reduce slightly */
177
+ object-fit: contain;
178
+ flex-shrink: 0;
179
+ }
180
+
181
+ .slide.imageWithCaption p {
182
+ max-height: 20vh; /* hard cap text */
183
+ overflow: hidden;
184
+ text-overflow: ellipsis;
185
+ }
186
+
187
+ /* -------------------------------
188
+ Two Column Text
189
+ ------------------------------- */
190
+
191
+
192
+ .slide.twoColumnText {
193
+ flex-direction: row;
194
+ justify-content: center; /* center the columns block */
195
+ align-items: center;
196
+ gap: 64px;
197
+ }
198
+
199
+ .slide.twoColumnText > div {
200
+ flex: 1;
201
+ max-width: 520px; /* keeps columns visually centered */
202
+ }
203
+
204
+
205
+ /* QUOTE SLIDE */
206
+ .slide.quoteSlide {
207
+ display: flex;
208
+ flex-direction: column;
209
+ justify-content: center;
210
+ gap: 32px;
211
+ height: 100vh;
212
+ overflow: hidden;
213
+ }
214
+
215
+ .slide.quoteSlide blockquote {
216
+ max-height: 60vh;
217
+ overflow: hidden;
218
+ }
219
+
220
+ .slide.quoteSlide cite {
221
+ flex-shrink: 0;
222
+ }
223
+
224
+ /* -------------------------------
225
+ Quote Slide
226
+ ------------------------------- */
227
+ .slide.quoteSlide blockquote {
228
+ font-size: 3.8rem;
229
+ font-style: italic;
230
+ max-width: 60ch;
231
+ }
232
+
233
+ .slide.quoteSlide cite {
234
+ font-size: 2.4rem;
235
+ opacity: 0.75;
236
+ }
237
+
238
+ /* -------------------------------
239
+ Big Number
240
+ ------------------------------- */
241
+ .slide.bigNumber .number {
242
+ font-size: 9rem;
243
+ font-weight: 700;
244
+ }
245
+
246
+ /* -------------------------------
247
+ Fill Image (Full Bleed)
248
+ ------------------------------- */
249
+ .slide.fillImage {
250
+ padding: 0;
251
+ height: 100vh;
252
+ display: block;
253
+ }
254
+
255
+ .slide.fillImage img {
256
+ width: 100%;
257
+ height: 100%;
258
+ object-fit: cover;
259
+ }
260
+
261
+ /* -------------------------------
262
+ Demo / Player UI (Optional)
263
+ ------------------------------- */
264
+ #timebar {
265
+ position: fixed;
266
+ bottom: 0;
267
+ left: 0;
268
+ right: 0;
269
+ height: 32px;
270
+ background: rgba(0, 0, 0, 0.6);
271
+ backdrop-filter: blur(6px);
272
+ z-index: 10;
273
+ }
274
+
275
+
276
+
277
+ /* ///////fixes */
278
+
279
+ /* -------------------------------
280
+ Image + Bullets (Side by Side)
281
+ ------------------------------- */
282
+
283
+ .slide.imageRightBulletsLeft,
284
+ .slide.imageLeftBulletsRight {
285
+ display: flex;
286
+ flex-direction: row;
287
+ align-items: center;
288
+ gap: 64px;
289
+ text-align: left;
290
+ }
291
+
292
+ /* image on right */
293
+ .slide.imageRightBulletsLeft img {
294
+ order: 2;
295
+ }
296
+ /* -------------------------------
297
+ Table Slide
298
+ ------------------------------- */
299
+
300
+ .slide.table {
301
+ display: flex;
302
+ justify-content: center;
303
+ align-items: center;
304
+
305
+ width: 100%;
306
+ height: 100vh;
307
+
308
+ padding: 64px 80px;
309
+ box-sizing: border-box;
310
+ }
311
+
312
+ /* actual table */
313
+ .slide.table table {
314
+ border-collapse: collapse;
315
+ width: 100%;
316
+ max-width: 1200px;
317
+
318
+ font-size: 2.8rem;
319
+ line-height: 1.4;
320
+ text-align: center;
321
+
322
+ color: var(--primaryColor, #e6e9ff);
323
+ }
324
+
325
+ /* header cells (title row) */
326
+ .slide.table thead th {
327
+ font-weight: 700; /* title row bold */
328
+ padding: 20px 24px;
329
+
330
+ border: 2px solid rgba(255, 255, 255, 0.35);
331
+ }
332
+
333
+ /* body cells */
334
+ .slide.table tbody td {
335
+ padding: 20px 24px;
336
+
337
+ border: 2px solid rgba(255, 255, 255, 0.25);
338
+ }
339
+ /* hide empty table header row */
340
+ .slide.table thead th:empty {
341
+ display: none;
342
+ }
343
+
344
+ .slide.table thead tr:has(th:empty) {
345
+ display: none;
346
+ }
347
+
@@ -0,0 +1,6 @@
1
+
2
+ :root {
3
+ --backgroundColor: #081b7a;
4
+ --primaryColor: #ccd0e4;
5
+ }
6
+
@@ -0,0 +1,6 @@
1
+
2
+ :root {
3
+ --backgroundColor: #15df1b;
4
+ --primaryColor: #cbd44b;
5
+ }
6
+
@@ -0,0 +1,6 @@
1
+
2
+ :root {
3
+ --backgroundColor: #f8f9fb;
4
+ --primaryColor: #1a1f36;
5
+ }
6
+
@@ -274,22 +274,19 @@ var ImageRightBulletsLeftSlide = {
274
274
  var TableSlide = {
275
275
  type: "table",
276
276
  fromJSON(raw) {
277
- const headers = raw.data?.find((d) => d.name === "header")?.content;
278
- const rows = raw.data?.find((d) => d.name === "row")?.content;
279
- if (!headers || !rows?.length) {
280
- throw new Error("table: requires headers and at least one row");
277
+ const rows = raw.data;
278
+ if (!Array.isArray(rows) || rows.length === 0) {
279
+ console.warn("Empty table skipped", raw);
280
+ return null;
281
281
  }
282
282
  return Object.freeze({
283
283
  type: "table",
284
284
  render() {
285
285
  return `
286
286
  <table class="slide table">
287
- <thead>
288
- <tr>${headers.map((h) => `<th>${h}</th>`).join("")}</tr>
289
- </thead>
290
287
  <tbody>
291
288
  ${rows.map(
292
- (r) => `<tr>${r.map((c) => `<td>${c}</td>`).join("")}</tr>`
289
+ (row) => `<tr>${row.map((cell) => `<td>${cell}</td>`).join("")}</tr>`
293
290
  ).join("")}
294
291
  </tbody>
295
292
  </table>
@@ -593,7 +590,7 @@ function getSlideTemplate(type) {
593
590
  return template;
594
591
  }
595
592
 
596
- // src/core/stage.js
593
+ // src/engines/player/stage.js
597
594
  function createStage(mount) {
598
595
  if (!mount) throw new Error("taleem-player: mount is required");
599
596
  const root = typeof mount === "string" ? document.querySelector(mount) : mount;
@@ -618,7 +615,7 @@ function createStage(mount) {
618
615
  };
619
616
  }
620
617
 
621
- // src/core/player.js
618
+ // src/engines/player/player.js
622
619
  function createTaleemPlayer({ mount, deck }) {
623
620
  const stage = createStage(mount);
624
621
  let lastSlide = null;
@@ -680,6 +677,118 @@ function createTaleemPlayer({ mount, deck }) {
680
677
  destroy
681
678
  };
682
679
  }
680
+
681
+ // src/engines/browser/browser.js
682
+ function createTaleemBrowser({ mount, deck }) {
683
+ if (!mount) {
684
+ throw new Error("taleem-browser: mount is required");
685
+ }
686
+ if (!deck || !Array.isArray(deck.deck)) {
687
+ throw new Error("taleem-browser: valid deck-v1 required");
688
+ }
689
+ const root = typeof mount === "string" ? document.querySelector(mount) : mount;
690
+ if (!root) {
691
+ throw new Error("taleem-browser: mount not found");
692
+ }
693
+ root.innerHTML = `
694
+ <div class="taleem-browser-bg"></div>
695
+ <div class="taleem-browser-slide"></div>
696
+ `;
697
+ const bgEl = root.querySelector(".taleem-browser-bg");
698
+ const slideEl = root.querySelector(".taleem-browser-slide");
699
+ applyBackground(bgEl, deck.background);
700
+ const slides = deck.deck.map((slideJSON, i) => {
701
+ if (!slideJSON.type) {
702
+ throw new Error(`taleem-browser: slide ${i} missing type`);
703
+ }
704
+ const Template = getSlideTemplate(slideJSON.type);
705
+ if (!Template) {
706
+ throw new Error(`taleem-browser: unknown slide type "${slideJSON.type}"`);
707
+ }
708
+ return Template.fromJSON(slideJSON);
709
+ });
710
+ const total = slides.length;
711
+ function render(index, renderState = {}) {
712
+ if (index < 0 || index >= total) return;
713
+ const slide = slides[index];
714
+ slideEl.innerHTML = slide.render(renderState);
715
+ }
716
+ return {
717
+ render,
718
+ getTotal() {
719
+ return total;
720
+ }
721
+ };
722
+ }
723
+ function applyBackground(el, bg = {}) {
724
+ el.style.position = "absolute";
725
+ el.style.inset = "0";
726
+ el.style.zIndex = "0";
727
+ el.style.backgroundColor = bg.backgroundColor || "#000";
728
+ el.style.backgroundImage = bg.backgroundImage ? `url(${bg.backgroundImage})` : "none";
729
+ el.style.backgroundSize = "cover";
730
+ el.style.backgroundPosition = "center";
731
+ el.style.opacity = bg.backgroundImageOpacity !== void 0 ? bg.backgroundImageOpacity : 1;
732
+ }
733
+
734
+ // src/utils/assignMockTimings.js
735
+ function assignMockTimings(goldenDeck, slideDuration = 5) {
736
+ if (!goldenDeck || !Array.isArray(goldenDeck.deck) || typeof slideDuration !== "number") {
737
+ throw new Error("assignMockTimings: invalid deck or slideDuration");
738
+ }
739
+ let currentTime = 0;
740
+ const deckWithTimings = {
741
+ ...goldenDeck,
742
+ deck: goldenDeck.deck.map((slide) => {
743
+ const start = currentTime;
744
+ const end = start + slideDuration;
745
+ currentTime = end;
746
+ const data = Array.isArray(slide.data) ? slide.data.map((item) => ({
747
+ ...item,
748
+ showAt: start + (typeof item.showAt === "number" ? item.showAt : 0)
749
+ })) : slide.data;
750
+ return {
751
+ ...slide,
752
+ start,
753
+ end,
754
+ data
755
+ };
756
+ })
757
+ };
758
+ return deckWithTimings;
759
+ }
760
+
761
+ // src/utils/resolveAssetPaths.js
762
+ function resolveAssetPaths(deck, IMG_BASE) {
763
+ if (deck.background?.backgroundImage && typeof deck.background.backgroundImage === "string") {
764
+ deck.background.backgroundImage = IMG_BASE + deck.background.backgroundImage.split("/").pop();
765
+ }
766
+ deck.deck.forEach((slide) => {
767
+ slide.data?.forEach((item) => {
768
+ if (item.name === "image" && typeof item.content === "string") {
769
+ item.content = IMG_BASE + item.content.split("/").pop();
770
+ }
771
+ });
772
+ });
773
+ return deck;
774
+ }
775
+
776
+ // src/utils/resolveBackground.js
777
+ function resolveBackground2(deck, ASSET_BASE) {
778
+ if (!deck || !deck.background) return deck;
779
+ const bg = deck.background;
780
+ if (typeof bg.backgroundImage === "string" && bg.backgroundImage.length > 0) {
781
+ bg.backgroundImage = ASSET_BASE + bg.backgroundImage.split("/").pop();
782
+ }
783
+ if (bg.backgroundImageOpacity === void 0) {
784
+ bg.backgroundImageOpacity = 1;
785
+ }
786
+ return deck;
787
+ }
683
788
  export {
684
- createTaleemPlayer
789
+ assignMockTimings,
790
+ createTaleemBrowser,
791
+ createTaleemPlayer,
792
+ resolveAssetPaths,
793
+ resolveBackground2 as resolveBackground
685
794
  };
@@ -20,7 +20,11 @@ var TaleemPlayer = (() => {
20
20
  // src/index.js
21
21
  var index_exports = {};
22
22
  __export(index_exports, {
23
- createTaleemPlayer: () => createTaleemPlayer
23
+ assignMockTimings: () => assignMockTimings,
24
+ createTaleemBrowser: () => createTaleemBrowser,
25
+ createTaleemPlayer: () => createTaleemPlayer,
26
+ resolveAssetPaths: () => resolveAssetPaths,
27
+ resolveBackground: () => resolveBackground2
24
28
  });
25
29
 
26
30
  // node_modules/taleem-slides/src/slides/TitleSlide.js
@@ -299,22 +303,19 @@ var TaleemPlayer = (() => {
299
303
  var TableSlide = {
300
304
  type: "table",
301
305
  fromJSON(raw) {
302
- const headers = raw.data?.find((d) => d.name === "header")?.content;
303
- const rows = raw.data?.find((d) => d.name === "row")?.content;
304
- if (!headers || !rows?.length) {
305
- throw new Error("table: requires headers and at least one row");
306
+ const rows = raw.data;
307
+ if (!Array.isArray(rows) || rows.length === 0) {
308
+ console.warn("Empty table skipped", raw);
309
+ return null;
306
310
  }
307
311
  return Object.freeze({
308
312
  type: "table",
309
313
  render() {
310
314
  return `
311
315
  <table class="slide table">
312
- <thead>
313
- <tr>${headers.map((h) => `<th>${h}</th>`).join("")}</tr>
314
- </thead>
315
316
  <tbody>
316
317
  ${rows.map(
317
- (r) => `<tr>${r.map((c) => `<td>${c}</td>`).join("")}</tr>`
318
+ (row) => `<tr>${row.map((cell) => `<td>${cell}</td>`).join("")}</tr>`
318
319
  ).join("")}
319
320
  </tbody>
320
321
  </table>
@@ -618,7 +619,7 @@ var TaleemPlayer = (() => {
618
619
  return template;
619
620
  }
620
621
 
621
- // src/core/stage.js
622
+ // src/engines/player/stage.js
622
623
  function createStage(mount) {
623
624
  if (!mount) throw new Error("taleem-player: mount is required");
624
625
  const root = typeof mount === "string" ? document.querySelector(mount) : mount;
@@ -643,7 +644,7 @@ var TaleemPlayer = (() => {
643
644
  };
644
645
  }
645
646
 
646
- // src/core/player.js
647
+ // src/engines/player/player.js
647
648
  function createTaleemPlayer({ mount, deck }) {
648
649
  const stage = createStage(mount);
649
650
  let lastSlide = null;
@@ -705,5 +706,113 @@ var TaleemPlayer = (() => {
705
706
  destroy
706
707
  };
707
708
  }
709
+
710
+ // src/engines/browser/browser.js
711
+ function createTaleemBrowser({ mount, deck }) {
712
+ if (!mount) {
713
+ throw new Error("taleem-browser: mount is required");
714
+ }
715
+ if (!deck || !Array.isArray(deck.deck)) {
716
+ throw new Error("taleem-browser: valid deck-v1 required");
717
+ }
718
+ const root = typeof mount === "string" ? document.querySelector(mount) : mount;
719
+ if (!root) {
720
+ throw new Error("taleem-browser: mount not found");
721
+ }
722
+ root.innerHTML = `
723
+ <div class="taleem-browser-bg"></div>
724
+ <div class="taleem-browser-slide"></div>
725
+ `;
726
+ const bgEl = root.querySelector(".taleem-browser-bg");
727
+ const slideEl = root.querySelector(".taleem-browser-slide");
728
+ applyBackground(bgEl, deck.background);
729
+ const slides = deck.deck.map((slideJSON, i) => {
730
+ if (!slideJSON.type) {
731
+ throw new Error(`taleem-browser: slide ${i} missing type`);
732
+ }
733
+ const Template = getSlideTemplate(slideJSON.type);
734
+ if (!Template) {
735
+ throw new Error(`taleem-browser: unknown slide type "${slideJSON.type}"`);
736
+ }
737
+ return Template.fromJSON(slideJSON);
738
+ });
739
+ const total = slides.length;
740
+ function render(index, renderState = {}) {
741
+ if (index < 0 || index >= total) return;
742
+ const slide = slides[index];
743
+ slideEl.innerHTML = slide.render(renderState);
744
+ }
745
+ return {
746
+ render,
747
+ getTotal() {
748
+ return total;
749
+ }
750
+ };
751
+ }
752
+ function applyBackground(el, bg = {}) {
753
+ el.style.position = "absolute";
754
+ el.style.inset = "0";
755
+ el.style.zIndex = "0";
756
+ el.style.backgroundColor = bg.backgroundColor || "#000";
757
+ el.style.backgroundImage = bg.backgroundImage ? `url(${bg.backgroundImage})` : "none";
758
+ el.style.backgroundSize = "cover";
759
+ el.style.backgroundPosition = "center";
760
+ el.style.opacity = bg.backgroundImageOpacity !== void 0 ? bg.backgroundImageOpacity : 1;
761
+ }
762
+
763
+ // src/utils/assignMockTimings.js
764
+ function assignMockTimings(goldenDeck, slideDuration = 5) {
765
+ if (!goldenDeck || !Array.isArray(goldenDeck.deck) || typeof slideDuration !== "number") {
766
+ throw new Error("assignMockTimings: invalid deck or slideDuration");
767
+ }
768
+ let currentTime = 0;
769
+ const deckWithTimings = {
770
+ ...goldenDeck,
771
+ deck: goldenDeck.deck.map((slide) => {
772
+ const start = currentTime;
773
+ const end = start + slideDuration;
774
+ currentTime = end;
775
+ const data = Array.isArray(slide.data) ? slide.data.map((item) => ({
776
+ ...item,
777
+ showAt: start + (typeof item.showAt === "number" ? item.showAt : 0)
778
+ })) : slide.data;
779
+ return {
780
+ ...slide,
781
+ start,
782
+ end,
783
+ data
784
+ };
785
+ })
786
+ };
787
+ return deckWithTimings;
788
+ }
789
+
790
+ // src/utils/resolveAssetPaths.js
791
+ function resolveAssetPaths(deck, IMG_BASE) {
792
+ if (deck.background?.backgroundImage && typeof deck.background.backgroundImage === "string") {
793
+ deck.background.backgroundImage = IMG_BASE + deck.background.backgroundImage.split("/").pop();
794
+ }
795
+ deck.deck.forEach((slide) => {
796
+ slide.data?.forEach((item) => {
797
+ if (item.name === "image" && typeof item.content === "string") {
798
+ item.content = IMG_BASE + item.content.split("/").pop();
799
+ }
800
+ });
801
+ });
802
+ return deck;
803
+ }
804
+
805
+ // src/utils/resolveBackground.js
806
+ function resolveBackground2(deck, ASSET_BASE) {
807
+ if (!deck || !deck.background) return deck;
808
+ const bg = deck.background;
809
+ if (typeof bg.backgroundImage === "string" && bg.backgroundImage.length > 0) {
810
+ bg.backgroundImage = ASSET_BASE + bg.backgroundImage.split("/").pop();
811
+ }
812
+ if (bg.backgroundImageOpacity === void 0) {
813
+ bg.backgroundImageOpacity = 1;
814
+ }
815
+ return deck;
816
+ }
708
817
  return __toCommonJS(index_exports);
709
818
  })();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "taleem-player",
3
- "version": "0.6.0-rc.1",
3
+ "version": "0.7.0-rc",
4
4
  "type": "module",
5
5
  "main": "./dist/taleem-player.umd.js",
6
6
  "module": "./dist/taleem-player.esm.js",
@@ -8,22 +8,26 @@
8
8
  ".": {
9
9
  "import": "./dist/taleem-player.esm.js",
10
10
  "require": "./dist/taleem-player.umd.js"
11
- }
11
+ },
12
+ "./css": "./dist/css/taleem.css",
13
+ "./css/dark": "./dist/css/themes/dark.css",
14
+ "./css/light": "./dist/css/themes/light.css",
15
+ "./css/paper": "./dist/css/themes/paper.css"
12
16
  },
13
17
  "files": [
14
18
  "dist"
15
19
  ],
16
20
  "scripts": {
17
21
  "build": "node ./scripts/build.js",
18
- "test": "node --experimental-vm-modules node_modules/jest/bin/jest.js"
22
+ "test": "vitest run"
19
23
  },
20
24
  "dependencies": {
21
- "taleem-core": "^1.3.2",
22
- "taleem-slides": "^0.6.0"
25
+ "taleem-core": "^1.4.3",
26
+ "taleem-slides": "^0.6.2-rc.6"
23
27
  },
24
28
  "devDependencies": {
25
29
  "esbuild": "^0.27.2",
26
- "jest": "^30.2.0",
27
- "jest-environment-jsdom": "^30.2.0"
30
+ "jsdom": "^22.1.0",
31
+ "vitest": "^3.2.4"
28
32
  }
29
33
  }