mdeck 3.7.1 → 3.9.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 (59) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/README.md +3 -3
  3. package/assets/base.css +9 -5
  4. package/assets/extensions/themes/academic/extension.toml +4 -1
  5. package/assets/extensions/themes/academic/styles.css +22 -22
  6. package/assets/extensions/themes/glass/extension.toml +7 -1
  7. package/assets/extensions/themes/glass/styles.css +17 -17
  8. package/assets/extensions/themes/minimal/extension.toml +3 -1
  9. package/assets/extensions/themes/minimal/styles.css +14 -14
  10. package/assets/extensions/themes/neue/extension.toml +3 -1
  11. package/assets/extensions/themes/neue/styles.css +14 -14
  12. package/assets/extensions/themes/plain/extension.toml +7 -1
  13. package/assets/extensions/themes/plain/styles.css +17 -17
  14. package/assets/extensions/themes/work/extension.toml +8 -2
  15. package/assets/extensions/themes/work/styles.css +26 -26
  16. package/docs/reference/authoring.md +21 -4
  17. package/docs/reference/extensions.md +5 -1
  18. package/docs/reference/themes.md +6 -2
  19. package/docs/site/content/drawing.md +1 -1
  20. package/docs/site/content/presenting.md +45 -0
  21. package/docs/site/content/theme-authoring.md +28 -0
  22. package/examples/python/slides.de.md +493 -0
  23. package/examples/python/{slides.md → slides.en.md} +1 -1
  24. package/examples/python/slides.fa.md +492 -0
  25. package/examples/python/slides.hi.md +492 -0
  26. package/examples/showcase/slides.md +33 -0
  27. package/package.json +1 -1
  28. package/skills/write-slides/SKILL.md +1 -0
  29. package/src/build/config.js +1 -1
  30. package/src/build/slidesPlugin.js +34 -4
  31. package/src/cli/index.js +4 -0
  32. package/src/cli/themes.js +2 -0
  33. package/src/components/callout.css +10 -10
  34. package/src/components/citation.css +2 -2
  35. package/src/components/code-block.css +3 -3
  36. package/src/components/poll.css +6 -6
  37. package/src/components/video-player.css +1 -1
  38. package/src/core/editDeck.js +3 -2
  39. package/src/core/icons.js +1 -0
  40. package/src/core/languages.js +51 -0
  41. package/src/core/parseSlides.js +15 -3
  42. package/src/core/validateDeck.js +13 -1
  43. package/src/editor/components/DeckSettings.jsx +37 -3
  44. package/src/editor/components/ExtensionForms.jsx +12 -0
  45. package/src/editor/components/MetaFields.jsx +7 -0
  46. package/src/editor/extensions.js +6 -3
  47. package/src/extensions/appearance.js +6 -2
  48. package/src/extensions/manifest.js +3 -3
  49. package/src/extensions/params.js +113 -0
  50. package/src/layouts/layoutApi.jsx +7 -1
  51. package/src/live/answer/answer.css +2 -2
  52. package/src/runtime/chrome.jsx +6 -7
  53. package/src/runtime/deck-stage.js +249 -11
  54. package/src/runtime/hotspots.js +98 -0
  55. package/src/runtime/ink/bus.js +12 -1
  56. package/src/runtime/ink/room.js +20 -8
  57. package/src/runtime/main.jsx +152 -22
  58. package/src/runtime/zoom/camera.js +77 -0
  59. /package/examples/python/{slides.drawings.json → slides.en.drawings.json} +0 -0
@@ -0,0 +1,493 @@
1
+ ---
2
+ theme: academic
3
+ meta:
4
+ title: "Einführung in 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 für die Life Sciences.
17
+ ## Eine Einführung Schritt für Schritt für Masterstudierende.
18
+
19
+ :::notes
20
+ Willkommen zum Python-Modul. Diese Sitzung setzt keine Programmiererfahrung voraus.
21
+
22
+ - Wir gehen Schritt für Schritt vor – unterbrecht mich ruhig mit Fragen
23
+ - Aller Code läuft live im Browser – heute muss nichts installiert werden
24
+ - Ziel: am Ende könnt ihr einen Datensatz laden, ansehen und plotten
25
+ :::
26
+
27
+ ---
28
+ :::meta
29
+ layout: focus
30
+ eyebrow: Warum Python?
31
+ id: the-tools-your-field-uses-are-written-in
32
+ :::
33
+ # Die Werkzeuge eures Fachs sind *in Python geschrieben*.
34
+
35
+ :::notes
36
+ Python ist die führende Sprache für datenintensive Arbeit, in der Forschung wie in der Industrie.
37
+ Die Life Sciences sind keine Ausnahme – Bioinformatik, Bildanalyse und klinische Daten laufen alle mit Python.
38
+
39
+ Das Ökosystem (NumPy, Pandas, Matplotlib, scikit-learn, BioPython) ist unerreicht.
40
+ Erwähnen, dass R in der Statistik noch verbreitet ist, Python sich aber weitgehend als gemeinsame Sprache durchgesetzt hat.
41
+ :::
42
+
43
+ ---
44
+ :::meta
45
+ layout: chapter
46
+ number: 1
47
+ part: Erste Schritte
48
+ description: Variablen, Typen und die ersten Zeilen Code.
49
+ id: variables-and-data-types
50
+ :::
51
+ # Variablen und Datentypen.
52
+
53
+ :::notes
54
+ Hier anfangen, auch wenn einige schon etwas Python gesehen haben. Wir bauen von Grund auf auf.
55
+ Geschätzte Zeit für dieses Kapitel: 20 Minuten.
56
+ :::
57
+
58
+ ---
59
+ :::meta
60
+ layout: split
61
+ section: Variablen
62
+ id: variables-store-data
63
+ props:
64
+ ratio: [3, 2]
65
+ :::
66
+ # Eine Variable ist ein Name für einen Wert.
67
+ :::slot left
68
+ ```python live editable copy
69
+ name = "E. coli"
70
+ genome_size = 4_641_652 # Basenpaare
71
+ gc_content = 0.506 # Anteil
72
+
73
+ print(name, genome_size, gc_content)
74
+ print(f"GC content: {gc_content:.1%}")
75
+ ```
76
+ :::
77
+ :::slot right
78
+ Keine Deklarationen: Python erkennt den Typ am Wert.
79
+
80
+ **Probiert es aus:** setzt `gc_content` auf `0.65` und führt den Code erneut aus.
81
+ :::
82
+
83
+ :::notes
84
+ Eine Variable ist nur ein Name, der an einem Wert hängt. Den Typ bestimmt Python selbst.
85
+
86
+ Hinweisen: keine Strichpunkte, keine Typdeklarationen, kein Drumherum. Der String `f"…"` setzt einen Wert in einen Text ein; `:.1%` zeigt einen Anteil als Prozentzahl.
87
+ Die Studierenden ermutigen, die Werte zu ändern und neu auszuführen – genau dafür ist der Live-Block da.
88
+ :::
89
+
90
+ ---
91
+ :::meta
92
+ section: Datentypen
93
+ id: four-types-cover-most-biology-data
94
+ :::
95
+ # Vier Typen decken die meisten biologischen Daten ab.
96
+
97
+ :::steps
98
+ - **`int`** – ganze Zahlen: Anzahlen, Positionen, Indizes
99
+ - **`float`** – Dezimalzahlen: Konzentrationen, p-Werte, Fold Changes
100
+ - **`str`** – Text: Gennamen, Sequenzen, Proben-IDs
101
+ - **`bool`** – True oder False: Signifikanz-Flags, Qualitätsfilter
102
+ :::
103
+
104
+ :::notes
105
+ Einen Typ nach dem anderen aufdecken und vor dem nächsten nach einem Beispiel aus dem Labor fragen.
106
+
107
+ Betonen, dass Booleans meist aus Vergleichen entstehen, nicht aus einem geschriebenen True/False. Die nächste Folie fragt den Raum.
108
+ :::
109
+
110
+ ---
111
+ :::meta
112
+ section: Datentypen
113
+ title: "Umfrage: der Typ eines p-Werts"
114
+ id: poll-the-type-of-a-p-value
115
+ :::
116
+ # Welchen Typ hat ein p-Wert?
117
+
118
+ <poll room="p-value" options="int|float|str|bool" answer="float" />
119
+
120
+ :::notes
121
+ Eine kurze Kontrolle, bevor es weitergeht. Die Handys kommen über den QR-Code dazu; startet das Deck mit `mdeck run slides.de.md --network`, damit sie diesen Computer erreichen.
122
+
123
+ Antwort: `float`; der Haken-Knopf unter den Balken markiert sie. Wenn viele `bool` gewählt haben, denken sie an das *Signifikanz-Flag*, das daraus berechnet wird (`p < 0.05`): ein guter Moment, um zu zeigen, dass Vergleiche Booleans ergeben.
124
+ :::
125
+
126
+ ---
127
+ :::meta
128
+ layout: split
129
+ section: Strings
130
+ props:
131
+ ratio: [3, 2]
132
+ :::
133
+ # Strings verhalten sich wie Sequenzen.
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
+ Ausschneiden, Zählen und Ersetzen funktioniert bei jedem Text gleich.
147
+
148
+ **Probiert es aus:** gebt das letzte Codon mit `dna[-3:]` aus.
149
+ :::
150
+
151
+ :::notes
152
+ Strings sind Sequenzen – man kann sie ausschneiden wie eine Nukleotidsequenz.
153
+ `replace("T", "U")` ist Transkription in einer Zeile. Die Begriffe lassen sich später direkt auf BioPython übertragen.
154
+ :::
155
+
156
+ ---
157
+ :::meta
158
+ layout: chapter
159
+ number: 2
160
+ part: Sammlungen
161
+ description: Mehrere Werte auf einmal speichern und abrufen.
162
+ :::
163
+ # Listen und Dictionaries.
164
+
165
+ :::notes
166
+ Die meisten echten Daten sind kein einzelner Wert. Listen und Dictionaries sind die beiden Arbeitspferde unter den Sammlungen.
167
+ Geschätzte Zeit für dieses Kapitel: 25 Minuten.
168
+ :::
169
+
170
+ ---
171
+ :::meta
172
+ layout: split
173
+ section: Listen
174
+ props:
175
+ ratio: [3, 2]
176
+ :::
177
+ # Eine Liste hält Werte in einer Reihenfolge.
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
+ Gezählt wird ab null: `conc[0]` ist das erste Element, `conc[-1]` das letzte.
191
+
192
+ **Probiert es aus:** fügt einen sechsten Wert hinzu und seht, wie sich der Mittelwert ändert.
193
+ :::
194
+
195
+ :::notes
196
+ Listen behalten die Reihenfolge und erlauben Duplikate. Der Index beginnt bei 0. Negative Indizes zählen vom Ende.
197
+ Gut für: Zeitreihen, Replikat-Messungen, geordnete Verarbeitungsschritte.
198
+
199
+ Häufiger Fehler: Studierende nehmen Index 1 für das erste Element. Die Zählung ab 0 früh einüben.
200
+ :::
201
+
202
+ ---
203
+ :::meta
204
+ layout: split
205
+ section: Dictionaries
206
+ props:
207
+ ratio: [3, 2]
208
+ :::
209
+ # Ein Dictionary sucht Werte über einen Namen.
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 # ein Feld ändern
221
+ print(sample)
222
+ ```
223
+ :::
224
+ :::slot right
225
+ Ein Dictionary beschreibt eine Probe: keine Spaltennummern zum Merken.
226
+
227
+ **Probiert es aus:** fügt `"treated": True` hinzu.
228
+ :::
229
+
230
+ :::notes
231
+ Dictionaries ordnen Schlüsseln Werte zu. Ideal für strukturierte Datensätze – ein Dictionary = eine Probe.
232
+ Später wird aus einer Liste von Dictionaries ein Pandas-DataFrame. Diesen Gedanken jetzt schon säen.
233
+ :::
234
+
235
+ ---
236
+ :::meta
237
+ layout: chapter
238
+ number: 3
239
+ part: Kontrollfluss
240
+ description: Entscheidungen treffen und Arbeitsschritte über die Daten wiederholen.
241
+ :::
242
+ # Entscheidungen und Schleifen.
243
+
244
+ :::notes
245
+ Diese beiden Konstrukte – if und for – decken den Grossteil der Logik in Datenverarbeitungs-Skripten ab.
246
+ Geschätzte Zeit für dieses Kapitel: 25 Minuten.
247
+ :::
248
+
249
+ ---
250
+ :::meta
251
+ layout: split
252
+ section: Bedingungen
253
+ props:
254
+ ratio: [3, 2]
255
+ :::
256
+ # `if` wählt einen Weg.
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
+ Die Einrückung ist der Block: Python verwendet Leerraum, keine geschweiften Klammern.
272
+
273
+ **Probiert es aus:** setzt `fold_change = 1.5`.
274
+ :::
275
+
276
+ :::notes
277
+ if / elif / else deckt jede Verzweigung ab. Einrückung ist in Python kein Stil, sondern Syntax.
278
+ Das bringt Leute aus anderen Sprachen oder aus R ins Stolpern. Doppelpunkt und Einrückung sind Pflicht.
279
+ Zeigen, was passiert, wenn der Doppelpunkt fehlt: Python meldet einen SyntaxError.
280
+ :::
281
+
282
+ ---
283
+ :::meta
284
+ layout: split
285
+ section: Schleifen
286
+ props:
287
+ ratio: [3, 2]
288
+ :::
289
+ # `for` wiederholt einen Schritt für jedes Element.
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
+ Durchlaufen, prüfen, sammeln: das Grundmuster beim Filtern von Daten.
305
+
306
+ **Probiert es aus:** zählt stattdessen die Werte unter 1.0.
307
+ :::
308
+
309
+ :::notes
310
+ for-Schleifen laufen über jede Sammlung. Dieses Muster – durchlaufen, prüfen, sammeln – steckt im Kern
311
+ fast jedes Datenverarbeitungs-Skripts, das die Studierenden schreiben werden.
312
+
313
+ **D** drücken und einen Wert auf der Folie durch die Schleife verfolgen: in `value`, durch das `if`, in `above`. Escape beendet das Zeichnen; die Zeichnung bleibt bei der Folie.
314
+ :::
315
+
316
+ ---
317
+ :::meta
318
+ layout: chapter
319
+ number: 4
320
+ part: Funktionen
321
+ description: Logik verpacken, um sie wiederzuverwenden und zu testen.
322
+ :::
323
+ # Funktionen verpacken Logik zur Wiederverwendung.
324
+
325
+ :::notes
326
+ Funktionen sind der erste Schritt zu wiederverwendbarem, testbarem Code.
327
+ Motivieren mit: „Ihr rechnet dasselbe für 200 Proben – einmal schreiben, 200-mal aufrufen.“
328
+ Geschätzte Zeit für dieses Kapitel: 20 Minuten.
329
+ :::
330
+
331
+ ---
332
+ :::meta
333
+ layout: split
334
+ section: Funktionen
335
+ props:
336
+ ratio: [3, 2]
337
+ :::
338
+ # Eine Funktion macht aus Eingaben ein Ergebnis.
339
+ :::slot left
340
+ ```python live editable copy
341
+ def gc_content(seq):
342
+ """Gibt den GC-Anteil einer Sequenz zurück."""
343
+ gc = seq.count("G") + seq.count("C")
344
+ return gc / len(seq)
345
+
346
+ for seq in ["ATGCGC", "AATTAA", "GCGCGC"]:
347
+ print(seq, f"{gc_content(seq):.0%}")
348
+ ```
349
+ :::
350
+ :::slot right
351
+ Einmal schreiben, dann für jede Probe aufrufen.
352
+
353
+ **Probiert es aus:** fügt der Liste eure eigene Sequenz hinzu.
354
+ :::
355
+
356
+ :::notes
357
+ def, Parameter, return – das ist die ganze Syntax.
358
+ Durchgehen: einmal definieren, viele Male mit verschiedenen Eingaben aufrufen.
359
+ Der Docstring (der String in dreifachen Anführungszeichen nach def) ist gute Praxis – früh einführen.
360
+ :::
361
+
362
+ ---
363
+ :::meta
364
+ layout: chapter
365
+ number: 5
366
+ part: Wissenschaftliches Python
367
+ description: NumPy, Pandas und Matplotlib – die drei Säulen.
368
+ :::
369
+ # Der wissenschaftliche Stack.
370
+
371
+ :::notes
372
+ Diese drei Bibliotheken machen Python zur führenden Sprache in der Datenwissenschaft.
373
+ In der Praxis schreiben die Studierenden wenig reines Python – sie verwenden diese Bibliotheken.
374
+ Geschätzte Zeit für dieses Kapitel: 30 Minuten.
375
+ :::
376
+
377
+ ---
378
+ :::meta
379
+ section: Wissenschaftliches Python
380
+ :::
381
+ # Drei Bibliotheken decken die meisten Abläufe in den Life Sciences ab.
382
+
383
+ :::steps
384
+ - **NumPy** – schnelle Arrays: mit einem ganzen Datensatz auf einmal rechnen
385
+ - **Pandas** – Tabellen: Tabellendaten laden, filtern, gruppieren und zusammenfassen
386
+ - **Matplotlib** – Plots, mit Kontrolle über jedes Element
387
+ :::
388
+
389
+ :::notes
390
+ Kurzer Überblick, bevor es in die Tiefe geht. Die Kernbotschaft: Diese Bibliotheken erledigen die Schwerarbeit,
391
+ sodass die Studierenden weniger Code schreiben und schneller zu verlässlicheren Ergebnissen kommen als mit reinen Python-Schleifen.
392
+ :::
393
+
394
+ ---
395
+ :::meta
396
+ layout: split
397
+ section: NumPy
398
+ props:
399
+ ratio: [3, 2]
400
+ :::
401
+ # NumPy rechnet mit ganzen Arrays.
402
+ :::slot left
403
+ ```python live copy
404
+ import numpy as np
405
+
406
+ m = np.array([1.2, 0.8, 2.1, 1.5, 0.9, 2.8])
407
+
408
+ print("Mean: ", m.mean().round(3))
409
+ print("Std: ", m.std().round(3))
410
+ print("Median:", np.median(m))
411
+ print("> 1.5: ", m[m > 1.5])
412
+ ```
413
+ :::
414
+ :::slot right
415
+ `m[m > 1.5]` filtert ohne Schleife.
416
+
417
+ Vergleicht es mit der `for`-Schleife von vor zwei Kapiteln.
418
+ :::
419
+
420
+ :::notes
421
+ NumPy-Operationen wirken auf das ganze Array auf einmal – ohne Schleifen. Das ist schneller und lesbarer.
422
+ Der vektorisierte Stil braucht Gewöhnung, lohnt sich aber, früh gelernt zu werden.
423
+ Demo: Die Studierenden vorhersagen lassen, was `m > 1.5` zurückgibt, bevor der Code läuft.
424
+ :::
425
+
426
+ ---
427
+ :::meta
428
+ layout: focus
429
+ eyebrow: Die wichtigste Einsicht
430
+ :::
431
+ # Rechnet mit *Arrays*, nicht mit einzelnen Werten.
432
+
433
+ :::notes
434
+ Das ist der wichtigste Umbau im Denken von reinem Python zu wissenschaftlichem Python.
435
+ Wiederholen und wirken lassen. Wer das früh verinnerlicht, schreibt später viel saubereren Code.
436
+ :::
437
+
438
+ ---
439
+ :::meta
440
+ layout: chapter
441
+ number: 6
442
+ part: Wie es weitergeht
443
+ description: Wohin es von hier aus geht.
444
+ id: keep-going
445
+ :::
446
+ # Dranbleiben.
447
+
448
+ :::notes
449
+ Mit einem Fahrplan schliessen. Nach einer Einführung fühlen sich Studierende oft verloren – konkrete nächste Schritte geben
450
+ und auf die besten freien Quellen verweisen.
451
+ :::
452
+
453
+ ---
454
+ :::meta
455
+ section: Wie es weitergeht
456
+ :::
457
+ # Was ihr als Nächstes lernen könnt.
458
+
459
+ - **Pandas** – eine CSV-Datei laden, Zeilen filtern, Gruppenstatistiken berechnen
460
+ - **Matplotlib und Seaborn** – Streudiagramme, Heatmaps, Volcano-Plots
461
+ - **scikit-learn** – Klassifikation, Clustering, Dimensionsreduktion
462
+ - **BioPython** – Sequenzen, BLAST, GenBank- und PDB-Dateien
463
+
464
+ :::notes
465
+ Zum Selbststudium: das offizielle Python-Tutorial für die Syntax, die Pandas-Dokumentation für die Arbeit mit Daten.
466
+ Die kostenlosen Python- und Pandas-Kurse von Kaggle sind ausgezeichnet und im eigenen Tempo machbar.
467
+ Speziell für die Life Sciences: die BioPython-Dokumentation und das Lehrbuch Python for Bioinformatics (Bassi).
468
+ :::
469
+
470
+ ---
471
+ :::meta
472
+ layout: focus
473
+ eyebrow: Merkt euch
474
+ :::
475
+ # Programmieren lernt man am besten, indem man *Code schreibt*.
476
+
477
+ :::notes
478
+ Damit schliessen. Programmieren ist eine Fertigkeit – sie braucht Übung. Die ersten Wochen fühlen sich langsam an,
479
+ dann macht es Klick. Zum täglichen Üben ermutigen, auch mit kleinen Skripten. Auf die Übungen verweisen.
480
+ :::
481
+
482
+ ---
483
+ :::meta
484
+ layout: title
485
+ :::
486
+ # Fragen?
487
+ ## Danke – jetzt die Laptops aufklappen.
488
+
489
+ :::notes
490
+ Zeit für Fragen lassen.
491
+ Daran erinnern, wo die Kursunterlagen liegen und wie man euch erreicht.
492
+ Die nächste Sitzung behandelt Pandas und das Laden echter Datensätze.
493
+ :::
@@ -118,7 +118,7 @@ id: poll-the-type-of-a-p-value
118
118
  <poll room="p-value" options="int|float|str|bool" answer="float" />
119
119
 
120
120
  :::notes
121
- A quick check before moving on. Phones join with the QR code; start the deck with `mdeck run slides.md --network` so they can reach this computer.
121
+ A quick check before moving on. Phones join with the QR code; start the deck with `mdeck run slides.en.md --network` so they can reach this computer.
122
122
 
123
123
  Answer: `float`; the tick button under the bars marks it. If many chose `bool`, they are thinking of the *significance flag* computed from it (`p < 0.05`): a good moment to show that comparisons produce booleans.
124
124
  :::