mdeck 3.7.1 → 3.8.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.
Files changed (43) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/README.md +3 -3
  3. package/assets/base.css +9 -5
  4. package/assets/extensions/themes/academic/styles.css +22 -22
  5. package/assets/extensions/themes/glass/styles.css +17 -17
  6. package/assets/extensions/themes/minimal/styles.css +14 -14
  7. package/assets/extensions/themes/neue/styles.css +14 -14
  8. package/assets/extensions/themes/plain/styles.css +17 -17
  9. package/assets/extensions/themes/work/styles.css +26 -26
  10. package/docs/reference/authoring.md +12 -0
  11. package/docs/site/content/drawing.md +1 -1
  12. package/docs/site/content/presenting.md +45 -0
  13. package/examples/python/slides.de.md +493 -0
  14. package/examples/python/{slides.md → slides.en.md} +1 -1
  15. package/examples/python/slides.fa.md +492 -0
  16. package/examples/python/slides.hi.md +492 -0
  17. package/examples/showcase/slides.md +33 -0
  18. package/package.json +1 -1
  19. package/skills/write-slides/SKILL.md +1 -0
  20. package/src/build/config.js +1 -1
  21. package/src/build/slidesPlugin.js +34 -4
  22. package/src/components/callout.css +10 -10
  23. package/src/components/citation.css +2 -2
  24. package/src/components/code-block.css +3 -3
  25. package/src/components/poll.css +6 -6
  26. package/src/components/video-player.css +1 -1
  27. package/src/core/editDeck.js +3 -2
  28. package/src/core/icons.js +1 -0
  29. package/src/core/languages.js +51 -0
  30. package/src/core/parseSlides.js +15 -3
  31. package/src/core/validateDeck.js +5 -0
  32. package/src/editor/components/DeckSettings.jsx +4 -0
  33. package/src/editor/components/MetaFields.jsx +7 -0
  34. package/src/layouts/layoutApi.jsx +7 -1
  35. package/src/live/answer/answer.css +2 -2
  36. package/src/runtime/chrome.jsx +6 -7
  37. package/src/runtime/deck-stage.js +249 -11
  38. package/src/runtime/hotspots.js +98 -0
  39. package/src/runtime/ink/bus.js +12 -1
  40. package/src/runtime/ink/room.js +20 -8
  41. package/src/runtime/main.jsx +152 -22
  42. package/src/runtime/zoom/camera.js +77 -0
  43. /package/examples/python/{slides.drawings.json → slides.en.drawings.json} +0 -0
@@ -0,0 +1,492 @@
1
+ ---
2
+ theme: academic
3
+ meta:
4
+ title: "Python का परिचय"
5
+ author: "Tilman Schieber"
6
+ organization: "FHNW"
7
+ date: "2026-05-19"
8
+ palette: paper
9
+ ---
10
+
11
+ ---
12
+ :::meta
13
+ layout: title
14
+ id: python-for-life-sciences
15
+ :::
16
+ # लाइफ़ साइंसेज़ के लिए Python।
17
+ ## मास्टर्स के छात्रों के लिए क़दम-दर-क़दम परिचय।
18
+
19
+ :::notes
20
+ Python मॉड्यूल में आपका स्वागत है। इस सत्र के लिए प्रोग्रामिंग का कोई पूर्व अनुभव ज़रूरी नहीं है।
21
+
22
+ - हम क़दम-दर-क़दम चलेंगे — सवाल पूछने में बिल्कुल न झिझकें
23
+ - सारा कोड सीधे ब्राउज़र में चलता है — आज कुछ इंस्टॉल करने की ज़रूरत नहीं
24
+ - लक्ष्य: अंत तक आप एक डेटासेट लोड कर पाएँगे, उसे जाँच पाएँगे और उसका प्लॉट बना पाएँगे
25
+ :::
26
+
27
+ ---
28
+ :::meta
29
+ layout: focus
30
+ eyebrow: Python क्यों?
31
+ id: the-tools-your-field-uses-are-written-in
32
+ :::
33
+ # आपके क्षेत्र के टूल *Python में लिखे गए हैं*।
34
+
35
+ :::notes
36
+ डेटा पर आधारित काम के लिए Python शिक्षा जगत और उद्योग, दोनों में सबसे प्रचलित भाषा है।
37
+ लाइफ़ साइंसेज़ भी अपवाद नहीं — बायोइन्फ़ॉर्मेटिक्स, इमेजिंग और क्लिनिकल डेटा विश्लेषण, सब Python पर चलते हैं।
38
+
39
+ इसका इकोसिस्टम (NumPy, Pandas, Matplotlib, scikit-learn, BioPython) बेजोड़ है।
40
+ बताएँ कि सांख्यिकी में R अब भी आम है, पर Python काफ़ी हद तक साझा भाषा बन चुकी है।
41
+ :::
42
+
43
+ ---
44
+ :::meta
45
+ layout: chapter
46
+ number: 1
47
+ part: शुरुआत
48
+ description: वेरिएबल, टाइप और आपके कोड की पहली पंक्तियाँ।
49
+ id: variables-and-data-types
50
+ :::
51
+ # वेरिएबल और डेटा टाइप।
52
+
53
+ :::notes
54
+ यहीं से शुरू करें, भले ही छात्रों ने पहले थोड़ा Python देखा हो। हम बुनियाद से बनाते हैं।
55
+ इस अध्याय का अनुमानित समय: 20 मिनट।
56
+ :::
57
+
58
+ ---
59
+ :::meta
60
+ layout: split
61
+ section: वेरिएबल
62
+ id: variables-store-data
63
+ props:
64
+ ratio: [3, 2]
65
+ :::
66
+ # वेरिएबल किसी मान का नाम है।
67
+ :::slot left
68
+ ```python live editable copy
69
+ name = "E. coli"
70
+ genome_size = 4_641_652 # बेस पेयर
71
+ gc_content = 0.506 # अनुपात
72
+
73
+ print(name, genome_size, gc_content)
74
+ print(f"GC content: {gc_content:.1%}")
75
+ ```
76
+ :::
77
+ :::slot right
78
+ कोई घोषणा नहीं: Python मान से ही टाइप पहचान लेता है।
79
+
80
+ **आज़माएँ:** `gc_content` को `0.65` कर दें और फिर से चलाएँ।
81
+ :::
82
+
83
+ :::notes
84
+ वेरिएबल बस किसी मान से जुड़ा एक नाम है। Python टाइप अपने-आप पहचान लेता है।
85
+
86
+ ध्यान दिलाएँ: न सेमीकोलन, न टाइप घोषणा, न अतिरिक्त ढाँचा। `f"…"` स्ट्रिंग किसी मान को टेक्स्ट के भीतर फ़ॉर्मैट करती है; `:.1%` अनुपात को प्रतिशत में दिखाता है।
87
+ छात्रों को मान बदलकर फिर से चलाने के लिए प्रोत्साहित करें — लाइव ब्लॉक का यही मक़सद है।
88
+ :::
89
+
90
+ ---
91
+ :::meta
92
+ section: डेटा टाइप
93
+ id: four-types-cover-most-biology-data
94
+ :::
95
+ # चार टाइप से जीवविज्ञान का ज़्यादातर डेटा बन जाता है।
96
+
97
+ :::steps
98
+ - **`int`** — पूर्ण संख्याएँ: गिनती, स्थिति, इंडेक्स
99
+ - **`float`** — दशमलव संख्याएँ: सांद्रता, p-मान, फ़ोल्ड चेंज
100
+ - **`str`** — टेक्स्ट: जीन के नाम, सीक्वेंस, सैंपल ID
101
+ - **`bool`** — True या False: सार्थकता फ़्लैग, गुणवत्ता फ़िल्टर
102
+ :::
103
+
104
+ :::notes
105
+ एक बार में एक टाइप दिखाएँ और अगला दिखाने से पहले लैब से एक उदाहरण पूछें।
106
+
107
+ ज़ोर दें कि बूलियन आमतौर पर तुलना से आते हैं, सीधे लिखे True/False से नहीं। अगली स्लाइड पर पूरे कमरे से पूछा जाएगा।
108
+ :::
109
+
110
+ ---
111
+ :::meta
112
+ section: डेटा टाइप
113
+ title: "पोल: p-मान का टाइप"
114
+ id: poll-the-type-of-a-p-value
115
+ :::
116
+ # p-मान किस टाइप का होता है?
117
+
118
+ <poll room="p-value" options="int|float|str|bool" answer="float" />
119
+
120
+ :::notes
121
+ आगे बढ़ने से पहले एक छोटी जाँच। फ़ोन QR कोड से जुड़ते हैं; डेक को `mdeck run slides.hi.md --network` से शुरू करें ताकि वे इस कंप्यूटर तक पहुँच सकें।
122
+
123
+ उत्तर: `float`; बार के नीचे का टिक बटन इसे चिह्नित करता है। अगर कई लोगों ने `bool` चुना, तो वे उससे निकाले गए *सार्थकता फ़्लैग* (`p < 0.05`) के बारे में सोच रहे हैं: यह दिखाने का अच्छा मौक़ा है कि तुलना से बूलियन बनते हैं।
124
+ :::
125
+
126
+ ---
127
+ :::meta
128
+ layout: split
129
+ section: स्ट्रिंग
130
+ props:
131
+ ratio: [3, 2]
132
+ :::
133
+ # स्ट्रिंग सीक्वेंस की तरह व्यवहार करती हैं।
134
+ :::slot left
135
+ ```python live editable copy
136
+ dna = "ATGCGTAAGCTTGAC"
137
+
138
+ print("Length: ", len(dna))
139
+ print("First codon:", dna[:3])
140
+ print("A count: ", dna.count("A"))
141
+ print("Reversed: ", dna[::-1])
142
+ print("As RNA: ", dna.replace("T", "U"))
143
+ ```
144
+ :::
145
+ :::slot right
146
+ स्लाइसिंग, गिनना और बदलना किसी भी टेक्स्ट पर एक जैसा काम करता है।
147
+
148
+ **आज़माएँ:** `dna[-3:]` से आख़िरी कोडॉन प्रिंट करें।
149
+ :::
150
+
151
+ :::notes
152
+ स्ट्रिंग सीक्वेंस होती हैं — आप उन्हें न्यूक्लियोटाइड सीक्वेंस की तरह ही काट सकते हैं।
153
+ `replace("T", "U")` एक पंक्ति में ट्रांसक्रिप्शन है। ये अवधारणाएँ बाद में सीधे BioPython में काम आती हैं।
154
+ :::
155
+
156
+ ---
157
+ :::meta
158
+ layout: chapter
159
+ number: 2
160
+ part: कलेक्शन
161
+ description: एक साथ कई मान रखना और उन तक पहुँचना।
162
+ :::
163
+ # लिस्ट और डिक्शनरी।
164
+
165
+ :::notes
166
+ ज़्यादातर असली डेटा एक अकेला मान नहीं होता। लिस्ट और डिक्शनरी दो सबसे काम के कलेक्शन हैं।
167
+ इस अध्याय का अनुमानित समय: 25 मिनट।
168
+ :::
169
+
170
+ ---
171
+ :::meta
172
+ layout: split
173
+ section: लिस्ट
174
+ props:
175
+ ratio: [3, 2]
176
+ :::
177
+ # लिस्ट मानों को क्रम में रखती है।
178
+ :::slot left
179
+ ```python live editable copy
180
+ conc = [0.12, 0.45, 0.33, 0.78, 0.21]
181
+
182
+ print("Samples:", len(conc))
183
+ print("First: ", conc[0])
184
+ print("Last: ", conc[-1])
185
+ print("Sorted: ", sorted(conc))
186
+ print("Mean: ", sum(conc) / len(conc))
187
+ ```
188
+ :::
189
+ :::slot right
190
+ गिनती शून्य से शुरू होती है: `conc[0]` पहला आइटम है, `conc[-1]` आख़िरी।
191
+
192
+ **आज़माएँ:** छठा मान जोड़ें और देखें कि औसत कैसे बदलता है।
193
+ :::
194
+
195
+ :::notes
196
+ लिस्ट क्रम बनाए रखती है और दोहराए गए मान भी रख सकती है। इंडेक्स 0 से शुरू होता है। ऋणात्मक इंडेक्स अंत से गिनते हैं।
197
+ इनके लिए बढ़िया: टाइम सीरीज़, दोहराए गए माप, क्रम से होने वाले प्रोसेसिंग चरण।
198
+
199
+ आम ग़लती: छात्र पहले आइटम के लिए इंडेक्स 1 लेते हैं। 0 से इंडेक्सिंग शुरू में ही पक्की करवाएँ।
200
+ :::
201
+
202
+ ---
203
+ :::meta
204
+ layout: split
205
+ section: डिक्शनरी
206
+ props:
207
+ ratio: [3, 2]
208
+ :::
209
+ # डिक्शनरी मानों को नाम से ढूँढती है।
210
+ :::slot left
211
+ ```python live editable copy
212
+ sample = {
213
+ "id": "S042",
214
+ "organism": "Mus musculus",
215
+ "tissue": "liver",
216
+ "weight_mg": 312,
217
+ }
218
+
219
+ print(sample["organism"])
220
+ sample["weight_mg"] = 318 # एक फ़ील्ड बदलें
221
+ print(sample)
222
+ ```
223
+ :::
224
+ :::slot right
225
+ एक डिक्शनरी एक सैंपल का वर्णन करती है: कॉलम नंबर याद रखने की ज़रूरत नहीं।
226
+
227
+ **आज़माएँ:** `"treated": True` जोड़ें।
228
+ :::
229
+
230
+ :::notes
231
+ डिक्शनरी कुंजियों (keys) को मानों से जोड़ती है। संरचित रिकॉर्ड के लिए एकदम सही — एक डिक्शनरी = एक सैंपल।
232
+ बाद में डिक्शनरियों की एक लिस्ट Pandas DataFrame बन जाती है। यह बीज अभी बो दें।
233
+ :::
234
+
235
+ ---
236
+ :::meta
237
+ layout: chapter
238
+ number: 3
239
+ part: कंट्रोल फ़्लो
240
+ description: फ़ैसले लेना और पूरे डेटा पर काम दोहराना।
241
+ :::
242
+ # फ़ैसले और लूप।
243
+
244
+ :::notes
245
+ ये दो संरचनाएँ — if और for — डेटा प्रोसेसिंग स्क्रिप्ट के ज़्यादातर तर्क को संभाल लेती हैं।
246
+ इस अध्याय का अनुमानित समय: 25 मिनट।
247
+ :::
248
+
249
+ ---
250
+ :::meta
251
+ layout: split
252
+ section: शर्तें
253
+ props:
254
+ ratio: [3, 2]
255
+ :::
256
+ # `if` रास्ता चुनता है।
257
+ :::slot left
258
+ ```python live editable copy
259
+ p_value = 0.023
260
+ fold_change = 2.4
261
+
262
+ if p_value < 0.05 and fold_change > 2:
263
+ print("Significant upregulation")
264
+ elif p_value < 0.05:
265
+ print("Significant, modest effect")
266
+ else:
267
+ print("Not significant")
268
+ ```
269
+ :::
270
+ :::slot right
271
+ इंडेंटेशन ही ब्लॉक है: Python ब्रेसेज़ नहीं, ख़ाली जगह का इस्तेमाल करता है।
272
+
273
+ **आज़माएँ:** `fold_change = 1.5` कर दें।
274
+ :::
275
+
276
+ :::notes
277
+ if / elif / else से हर तरह की शाखा बनती है। Python में इंडेंटेशन शैली की बात नहीं — यह सिंटैक्स है।
278
+ दूसरी भाषाओं या R से आए छात्र यहाँ अटकते हैं। कोलन और इंडेंट अनिवार्य हैं।
279
+ दिखाएँ कि कोलन भूलने पर क्या होता है: Python SyntaxError देता है।
280
+ :::
281
+
282
+ ---
283
+ :::meta
284
+ layout: split
285
+ section: लूप
286
+ props:
287
+ ratio: [3, 2]
288
+ :::
289
+ # `for` हर आइटम के लिए एक काम दोहराता है।
290
+ :::slot left
291
+ ```python live editable copy
292
+ values = [1.2, 0.8, 2.1, 1.5, 0.9, 2.8]
293
+
294
+ above = []
295
+ for value in values:
296
+ if value > 1.0:
297
+ above.append(value)
298
+
299
+ print(len(above), "of", len(values))
300
+ print(above)
301
+ ```
302
+ :::
303
+ :::slot right
304
+ लूप, जाँच, संग्रह: डेटा फ़िल्टर करने का मूल पैटर्न।
305
+
306
+ **आज़माएँ:** इसके बजाय 1.0 से छोटे मान गिनें।
307
+ :::
308
+
309
+ :::notes
310
+ for लूप किसी भी कलेक्शन पर चलते हैं। यह पैटर्न — लूप, जाँच, संग्रह — लगभग हर उस डेटा प्रोसेसिंग स्क्रिप्ट के केंद्र में है जो छात्र लिखेंगे।
311
+
312
+ **D** दबाएँ और स्लाइड पर एक मान को लूप में ट्रेस करें: `value` में, `if` से होकर, `above` में। Escape से ड्रॉइंग बंद होती है; ड्रॉइंग स्लाइड के साथ बनी रहती है।
313
+ :::
314
+
315
+ ---
316
+ :::meta
317
+ layout: chapter
318
+ number: 4
319
+ part: फ़ंक्शन
320
+ description: तर्क को पैक करना ताकि आप उसे दोबारा इस्तेमाल और टेस्ट कर सकें।
321
+ :::
322
+ # फ़ंक्शन तर्क को दोबारा इस्तेमाल के लिए पैक करते हैं।
323
+
324
+ :::notes
325
+ फ़ंक्शन दोबारा इस्तेमाल होने लायक़ और टेस्ट करने लायक़ कोड की ओर पहला क़दम हैं।
326
+ प्रेरणा के लिए: "आप यही गणना 200 सैंपल पर चलाएँगे — इसे एक बार लिखें, 200 बार बुलाएँ।"
327
+ इस अध्याय का अनुमानित समय: 20 मिनट।
328
+ :::
329
+
330
+ ---
331
+ :::meta
332
+ layout: split
333
+ section: फ़ंक्शन
334
+ props:
335
+ ratio: [3, 2]
336
+ :::
337
+ # फ़ंक्शन इनपुट को आउटपुट में बदलता है।
338
+ :::slot left
339
+ ```python live editable copy
340
+ def gc_content(seq):
341
+ """सीक्वेंस में GC का अनुपात लौटाता है।"""
342
+ gc = seq.count("G") + seq.count("C")
343
+ return gc / len(seq)
344
+
345
+ for seq in ["ATGCGC", "AATTAA", "GCGCGC"]:
346
+ print(seq, f"{gc_content(seq):.0%}")
347
+ ```
348
+ :::
349
+ :::slot right
350
+ इसे एक बार लिखें, फिर हर सैंपल पर बुलाएँ।
351
+
352
+ **आज़माएँ:** लिस्ट में अपना सीक्वेंस जोड़ें।
353
+ :::
354
+
355
+ :::notes
356
+ def, पैरामीटर, return — पूरा सिंटैक्स बस इतना है।
357
+ समझाएँ: एक बार परिभाषित करें, अलग-अलग इनपुट के साथ कई बार बुलाएँ।
358
+ docstring (def के बाद तीन उद्धरण चिह्नों वाली स्ट्रिंग) अच्छी आदत है — इसे जल्दी सिखाएँ।
359
+ :::
360
+
361
+ ---
362
+ :::meta
363
+ layout: chapter
364
+ number: 5
365
+ part: वैज्ञानिक Python
366
+ description: NumPy, Pandas और Matplotlib — तीन स्तंभ।
367
+ :::
368
+ # वैज्ञानिक स्टैक।
369
+
370
+ :::notes
371
+ यही तीन लाइब्रेरी Python को डेटा साइंस की सबसे प्रचलित भाषा बनाती हैं।
372
+ व्यवहार में छात्र ज़्यादा शुद्ध Python नहीं लिखेंगे — वे इन्हीं का इस्तेमाल करेंगे।
373
+ इस अध्याय का अनुमानित समय: 30 मिनट।
374
+ :::
375
+
376
+ ---
377
+ :::meta
378
+ section: वैज्ञानिक Python
379
+ :::
380
+ # तीन लाइब्रेरी से लाइफ़ साइंसेज़ के ज़्यादातर काम हो जाते हैं।
381
+
382
+ :::steps
383
+ - **NumPy** — तेज़ ऐरे: पूरे डेटासेट पर एक साथ गणना
384
+ - **Pandas** — तालिकाएँ: स्प्रेडशीट डेटा लोड करना, फ़िल्टर करना, समूह बनाना और सारांश निकालना
385
+ - **Matplotlib** — प्लॉट, हर तत्व पर नियंत्रण के साथ
386
+ :::
387
+
388
+ :::notes
389
+ गहराई में जाने से पहले एक संक्षिप्त झलक। मुख्य संदेश: ये लाइब्रेरी भारी काम संभालती हैं,
390
+ इसलिए छात्र कम कोड लिखते हैं और शुद्ध Python लूप की तुलना में तेज़ और भरोसेमंद नतीजे पाते हैं।
391
+ :::
392
+
393
+ ---
394
+ :::meta
395
+ layout: split
396
+ section: NumPy
397
+ props:
398
+ ratio: [3, 2]
399
+ :::
400
+ # NumPy पूरे ऐरे पर गणना करता है।
401
+ :::slot left
402
+ ```python live copy
403
+ import numpy as np
404
+
405
+ m = np.array([1.2, 0.8, 2.1, 1.5, 0.9, 2.8])
406
+
407
+ print("Mean: ", m.mean().round(3))
408
+ print("Std: ", m.std().round(3))
409
+ print("Median:", np.median(m))
410
+ print("> 1.5: ", m[m > 1.5])
411
+ ```
412
+ :::
413
+ :::slot right
414
+ `m[m > 1.5]` बिना लूप के फ़िल्टर करता है।
415
+
416
+ इसकी तुलना दो अध्याय पहले वाले `for` लूप से करें।
417
+ :::
418
+
419
+ :::notes
420
+ NumPy के ऑपरेशन पूरे ऐरे पर एक साथ लागू होते हैं — कोई लूप नहीं। यह तेज़ भी है और पढ़ने में आसान भी।
421
+ इस वेक्टराइज़्ड शैली की आदत पड़ने में समय लगता है, पर इसे जल्दी सीखना फ़ायदेमंद है।
422
+ डेमो: चलाने से पहले छात्रों से अनुमान लगवाएँ कि `m > 1.5` क्या लौटाता है।
423
+ :::
424
+
425
+ ---
426
+ :::meta
427
+ layout: focus
428
+ eyebrow: मुख्य सीख
429
+ :::
430
+ # ऑपरेशन अकेले मानों पर नहीं, *ऐरे* पर लिखें।
431
+
432
+ :::notes
433
+ शुद्ध Python से वैज्ञानिक Python की ओर सोच में यह सबसे अहम बदलाव है।
434
+ इसे दोहराएँ, बैठने दें। जो छात्र इसे जल्दी आत्मसात कर लेते हैं, वे बाद में कहीं साफ़ कोड लिखते हैं।
435
+ :::
436
+
437
+ ---
438
+ :::meta
439
+ layout: chapter
440
+ number: 6
441
+ part: आगे के क़दम
442
+ description: यहाँ से आगे कहाँ जाएँ।
443
+ id: keep-going
444
+ :::
445
+ # आगे बढ़ते रहें।
446
+
447
+ :::notes
448
+ एक रोडमैप के साथ समापन करें। परिचय के बाद छात्र अक्सर भटका हुआ महसूस करते हैं — उन्हें ठोस अगले क़दम दें
449
+ और सबसे अच्छे मुफ़्त संसाधनों की ओर इशारा करें।
450
+ :::
451
+
452
+ ---
453
+ :::meta
454
+ section: आगे के क़दम
455
+ :::
456
+ # आगे क्या सीखें।
457
+
458
+ - **Pandas** — CSV लोड करना, पंक्तियाँ फ़िल्टर करना, समूहों के आँकड़े निकालना
459
+ - **Matplotlib और Seaborn** — स्कैटर प्लॉट, हीटमैप, वॉल्केनो प्लॉट
460
+ - **scikit-learn** — वर्गीकरण, क्लस्टरिंग, आयाम घटाना
461
+ - **BioPython** — सीक्वेंस, BLAST, GenBank और PDB फ़ाइलें
462
+
463
+ :::notes
464
+ स्व-अध्ययन के लिए: सिंटैक्स के लिए आधिकारिक Python ट्यूटोरियल, डेटा के काम के लिए Pandas का दस्तावेज़।
465
+ Kaggle के मुफ़्त Python और Pandas कोर्स बेहतरीन हैं और अपनी गति से किए जा सकते हैं।
466
+ ख़ास तौर पर लाइफ़ साइंसेज़ के लिए: BioPython का दस्तावेज़ और Python for Bioinformatics पाठ्यपुस्तक (Bassi)।
467
+ :::
468
+
469
+ ---
470
+ :::meta
471
+ layout: focus
472
+ eyebrow: याद रखें
473
+ :::
474
+ # कोड लिखना सीखने का सबसे अच्छा तरीक़ा है *कोड लिखना*।
475
+
476
+ :::notes
477
+ इसी पर समापन करें। प्रोग्रामिंग एक कौशल है — इसमें अभ्यास लगता है। पहले कुछ हफ़्ते धीमे लगते हैं,
478
+ फिर बात बैठ जाती है। रोज़ अभ्यास के लिए प्रोत्साहित करें, चाहे छोटी स्क्रिप्ट ही क्यों न हों। अभ्यास-प्रश्नों की ओर इशारा करें।
479
+ :::
480
+
481
+ ---
482
+ :::meta
483
+ layout: title
484
+ :::
485
+ # सवाल?
486
+ ## धन्यवाद — अब अपने लैपटॉप खोलिए।
487
+
488
+ :::notes
489
+ सवालों के लिए समय छोड़ें।
490
+ छात्रों को याद दिलाएँ कि कोर्स सामग्री कहाँ मिलेगी और आपसे कैसे संपर्क करें।
491
+ अगले सत्र में Pandas और असली डेटासेट लोड करना शामिल होगा।
492
+ :::
@@ -350,6 +350,39 @@ The underline and the circle on this slide were drawn with the pen and saved; th
350
350
  On an iPad, pair it from the launch page: you draw and steer there while the laptop drives the projector.
351
351
  :::
352
352
 
353
+ ---
354
+ :::meta
355
+ layout: split
356
+ hotspots: auto
357
+ id: zoom-in-on-a-part
358
+ :::
359
+ # Zoom in on a part
360
+ :::slot left
361
+ ```markdown
362
+ :::meta
363
+ hotspots: auto
364
+ :::
365
+ ```
366
+
367
+ In the presenter view, press a **number** to zoom into that part. The same number, **0** or **Escape** goes back.
368
+ :::
369
+ :::slot right
370
+ | Layer | Who owns it |
371
+ |---|---|
372
+ | Words | You |
373
+ | Look | The theme |
374
+
375
+ $$
376
+ A = \pi r^2
377
+ $$
378
+ :::
379
+
380
+ :::notes
381
+ The numbered keys show on this slide only here in the presenter view, never on the projector. Press 2 for the table, 3 for the formula; the audience window flies along. The arrow keys leave the zoom and move on in one press.
382
+
383
+ `hotspots: auto` finds pictures, code, tables, formulas and callouts. Put it in the deck's settings at the top to have keys on every slide, or write `marked` to give keys only to parts marked `data-hotspot`. H switches the keys off and on during the talk.
384
+ :::
385
+
353
386
  ---
354
387
  # Which part will you try first?
355
388
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mdeck",
3
- "version": "3.7.1",
3
+ "version": "3.8.0",
4
4
  "type": "module",
5
5
  "description": "Write slides as a plain text file, present them in the browser, share them as one HTML file or PDF.",
6
6
  "license": "MIT",
@@ -44,6 +44,7 @@ meta:
44
44
  date: "2026-09-16"
45
45
  bibliography: refs.bib # optional: .bib, CSL-JSON or Hayagriva; cite [@key, p. 3] or @key, list with <bibliography />
46
46
  csl: apa # optional: apa, vancouver, harvard1 or a .csl file
47
+ hotspots: auto # optional: number keys zoom in on pictures, code and tables while presenting; marked: only data-hotspot parts
47
48
  ---
48
49
 
49
50
  ---
@@ -38,7 +38,7 @@ export function baseConfig(slidesPath, { selfContained = false, defaultView = 'd
38
38
  return {
39
39
  configFile: false,
40
40
  root: runtimeRoot,
41
- plugins: [preact(), slidesPlugin(abs, source === undefined ? {} : { source })],
41
+ plugins: [preact(), slidesPlugin(abs, source === undefined ? { languages: true } : { source })],
42
42
  // Deck-local components live outside the framework root and have no
43
43
  // node_modules of their own, so their preact imports must resolve back to
44
44
  // the framework's copy — and to the *same* instance, or hooks break.
@@ -15,6 +15,7 @@ import { assertDrawings } from './drawings.js'
15
15
  import { marked } from 'marked'
16
16
  import { scanTags, replaceTagsIn, withAttribute } from '../core/tags.js'
17
17
  import { resolveBibliography, sampleBibliography } from './bibliography.js'
18
+ import { languageOfFile, languageFiles } from '../core/languages.js'
18
19
 
19
20
  const VIRTUAL_ID = 'virtual:slides'
20
21
  const RESOLVED_ID = '\0virtual:slides'
@@ -217,12 +218,19 @@ export function deckLookOnly(registry, deckConfig = {}) {
217
218
  return { ...registry, themes: { [theme.id]: theme }, palettes: palette ? { [palette.id]: registry.palettes[palette.id] } : {} }
218
219
  }
219
220
 
221
+ // The same deck in its other languages (talk.en.md beside talk.de.md), for
222
+ // the presenter view's language switch: `[{ lang, file }]`, or none.
223
+ export function deckLanguages(abs) {
224
+ try { return languageFiles(basename(abs), readdirSync(dirname(abs))).map(({ lang, file }) => ({ lang, file: resolve(dirname(abs), file) })) } catch { return [] }
225
+ }
226
+
220
227
  // `editor` keeps the page alive while `mdeck edit` rewrites the deck: deck
221
228
  // changes only invalidate the module, validation errors are warnings, and
222
229
  // extension changes are announced instead of forcing a reload. `source` is
223
230
  // the deck's text where there is no file (`mdeck design`, on the sample deck);
224
- // `slidesPath` then only says where its folder is.
225
- export function slidesPlugin(slidesPath, { inlineImages = false, inlineMedia = false, editor = false, stripNotes = false, stripActivities = false, ink = true, results = true, embedFonts: embedThemeFonts = false, deckLook = false, source: given = null } = {}) {
231
+ // `slidesPath` then only says where its folder is. `languages` also brings
232
+ // the deck's other languages along, for `mdeck run`; a built file has one.
233
+ export function slidesPlugin(slidesPath, { languages = false, inlineImages = false, inlineMedia = false, editor = false, stripNotes = false, stripActivities = false, ink = true, results = true, embedFonts: embedThemeFonts = false, deckLook = false, source: given = null } = {}) {
226
234
  const abs = resolve(slidesPath)
227
235
  const readDeck = () => given ?? readFileSync(abs, 'utf-8')
228
236
  const watchDeck = context => { if (given === null) context.addWatchFile(abs) }
@@ -231,6 +239,10 @@ export function slidesPlugin(slidesPath, { inlineImages = false, inlineMedia = f
231
239
  return stripActivities ? stripActivitiesFrom(shared).source : shared
232
240
  }
233
241
  const inkPath = inkFileFor(abs)
242
+ // The language the file name gives, for a deck that sets no `lang`.
243
+ const fileLanguage = given === null ? languageOfFile(basename(abs))?.lang ?? null : null
244
+ const otherLanguages = () => languages && given === null ? deckLanguages(abs).filter(({ file }) => file !== abs) : []
245
+ let languageWatch = []
234
246
  const resultsPath = resultsFileFor(abs)
235
247
  const extensionDirs = extensionRoots(abs).map(root => root.dir)
236
248
  let componentDirs = componentFolders(abs, readDeck()).map(folder => folder.dir)
@@ -306,7 +318,20 @@ export function slidesPlugin(slidesPath, { inlineImages = false, inlineMedia = f
306
318
  if (errors.length && !editor) throw new Error(formatDiagnostics(errors, abs))
307
319
  if (diagnostics.length) this.warn(formatDiagnostics(diagnostics, abs))
308
320
  const source = maybeInlineAssets(sourceFor(raw), abs, { inlineImages, inlineMedia })
309
- return `export default ${JSON.stringify(source)}`
321
+ // The other languages are read as they are: `mdeck check` checks each file.
322
+ const others = otherLanguages()
323
+ languageWatch = others.map(({ file }) => file)
324
+ for (const file of languageWatch) this.addWatchFile(file)
325
+ for (const { file } of others) {
326
+ const found = validateDeck(parseSlides(readFileSync(file, 'utf-8')), {
327
+ layouts: manifestsOf(registry, 'layout'), themes: manifestsOf(registry, 'theme'), palettes: manifestsOf(registry, 'palette'),
328
+ })
329
+ if (found.length) this.warn(formatDiagnostics(found, file))
330
+ }
331
+ const translations = others.length && fileLanguage
332
+ ? { [fileLanguage]: source, ...Object.fromEntries(others.map(({ lang, file }) => [lang, maybeInlineAssets(sourceFor(readFileSync(file, 'utf-8')), file, { inlineImages, inlineMedia })])) }
333
+ : {}
334
+ return `export default ${JSON.stringify(source)}\nexport const language = ${JSON.stringify(fileLanguage)}\nexport const languages = ${JSON.stringify(translations)}`
310
335
  }
311
336
  // The deck's saved ink (<deck>.drawings.json), scaled to its design size.
312
337
  // Builds bundle it, so the reader view and PDFs show it too.
@@ -385,7 +410,7 @@ export function slidesPlugin(slidesPath, { inlineImages = false, inlineMedia = f
385
410
  if (!/(^|[\/])index\.html$/.test(context.filename ?? context.path ?? '')) return html
386
411
  let config = {}
387
412
  try { config = parseSlides(readDeck()).deckConfig ?? {} } catch {}
388
- const lang = config.lang
413
+ const lang = config.lang ?? fileLanguage
389
414
  if (typeof lang === 'string' && /^[A-Za-z]{2,3}(-[A-Za-z0-9]{2,8})*$/.test(lang)) html = html.replace(/<html lang="[^"]*"/, `<html lang="${lang}"`)
390
415
  if (embedThemeFonts) {
391
416
  const theme = loadRegistry(abs).themes[config.theme ?? 'neue']?.manifest
@@ -414,6 +439,11 @@ export function slidesPlugin(slidesPath, { inlineImages = false, inlineMedia = f
414
439
  reload(server, [RESOLVED_INK_ID], changed)
415
440
  return []
416
441
  }
442
+ if (languageWatch.includes(changed)) {
443
+ invalidate(server, [RESOLVED_ID])
444
+ if (!editor) server.ws.send({ type: 'full-reload' })
445
+ return []
446
+ }
417
447
  if (bibFiles.includes(changed)) {
418
448
  invalidate(server, [RESOLVED_BIB_ID])
419
449
  if (editor) return bibUpdate(server)