Änderungen von Dokument Animation Reaktionsgeschwindigkeit

Zuletzt geändert von Holger Engels am 2026/10/26 19:37

Von Version 5.1
bearbeitet von Holger Engels
am 2026/10/10 21:26
Änderungskommentar: Löschung des Anhangs reaktionskinetik.py
Auf Version 3.1
bearbeitet von Holger Engels
am 2026/10/10 21:25
Änderungskommentar: Neuen Anhang reaktionskinetik.json hochladen

Zusammenfassung

Details

ReaktionskinetikAnimation.mp4
Author
... ... @@ -1,1 +1,0 @@
1 -xwiki:XWiki.holgerengels
Größe
... ... @@ -1,1 +1,0 @@
1 -963.8 KB
Inhalt
reaktionskinetik.py
Author
... ... @@ -1,0 +1,1 @@
1 +xwiki:XWiki.holgerengels
Größe
... ... @@ -1,0 +1,1 @@
1 +17.9 KB
Inhalt
... ... @@ -1,0 +1,438 @@
1 +from manim import *
2 +import numpy as np
3 +from styles import *
4 +
5 +# =============================================================================
6 +# 1. MATHEMATISCHES & CHEMISCHES MODELL
7 +# =============================================================================
8 +# Eduktkonzentration: c(t) = c_0 * exp(-k * t)
9 +# c_0 = 1.0 mol/l
10 +# k = 0.00078 s^-1
11 +# t in [0, 4000] Sekunden
12 +
13 +C_0 = 1.0
14 +K_RATE = 0.00078
15 +
16 +def get_conc(t):
17 + """Konzentration des Edukts zum Zeitpunkt t in mol/l."""
18 + return C_0 * np.exp(-K_RATE * t)
19 +
20 +def get_reaction_rate(t):
21 + """Betrag der momentanen Reaktionsgeschwindigkeit v(t) = |dc/dt| in mol/(l*s)."""
22 + return K_RATE * C_0 * np.exp(-K_RATE * t)
23 +
24 +def get_solution_color(c_val):
25 + """
26 + Farbverlauf der Reaktionslösung im Erlenmeyerkolben:
27 + Tiefes Kristallviolett bei c=1.0 mol/l
28 + -> Hellviolett / Rosa bei c=0.5 mol/l
29 + -> Fast farblos / transparent-wässrig bei c->0.
30 + """
31 + alpha = float(np.clip(c_val / 1.0, 0.0, 1.0))
32 + c_start = ManimColor("#6A1B9A") # Tiefes Violett
33 + c_mid = ManimColor("#BA68C8") # Helles Rosa-Violett
34 + c_end = ManimColor("#E1BEE7") # Blasses, wässriges Violett
35 +
36 + if alpha > 0.4:
37 + return interpolate_color(c_mid, c_start, (alpha - 0.4) / 0.6)
38 + else:
39 + return interpolate_color(c_end, c_mid, alpha / 0.4)
40 +
41 +def get_solution_opacity(c_val):
42 + alpha = float(np.clip(c_val / 1.0, 0.0, 1.0))
43 + return 0.20 + 0.70 * alpha
44 +
45 +# Farbcodierung analog zum Arbeitsblatt
46 +COLOR_DURCHSCHNITT = COLOR_INFLOW # #00E5FF (Cyan / Blau für Sekante / mittlere Änderungsrate)
47 +COLOR_MOMENTAN = COLOR_OUTFLOW # #FF5252 (Korallrot für Tangente / momentane Änderungsrate)
48 +COLOR_ANFANG = COLOR_EQUILIBRIUM # #26A69A (Grün für Anfangsgeschwindigkeit)
49 +
50 +
51 +def create_erlenmeyer(width=2.4, height=3.0):
52 + """
53 + Erstellt einen detailreichen Erlenmeyerkolben mit Glaswand,
54 + Skalenstrichen und dynamischer Flüssigkeitsfüllung.
55 + """
56 + neck_w = 0.60
57 + neck_h = 0.90
58 + base_w = width
59 + body_h = height - neck_h
60 + rim_w = 0.76
61 + rim_h = 0.10
62 +
63 + # Eckpunkte des Glaskörpers (konischer Rumpf)
64 + p_bl = np.array([-base_w/2, -body_h/2, 0])
65 + p_br = np.array([ base_w/2, -body_h/2, 0])
66 + p_tr = np.array([ neck_w/2, body_h/2, 0])
67 + p_tl = np.array([-neck_w/2, body_h/2, 0])
68 +
69 + body = Polygon(
70 + p_bl, p_br, p_tr, p_tl,
71 + stroke_color=WHITE, stroke_width=2.4,
72 + fill_color="#101422", fill_opacity=0.4
73 + )
74 +
75 + neck = Rectangle(
76 + width=neck_w, height=neck_h,
77 + stroke_color=WHITE, stroke_width=2.4,
78 + fill_color="#101422", fill_opacity=0.4
79 + ).next_to(body, UP, buff=0)
80 +
81 + rim = RoundedRectangle(
82 + corner_radius=0.04, width=rim_w, height=rim_h,
83 + stroke_color=WHITE, stroke_width=1.8,
84 + fill_color="#1E2438", fill_opacity=0.9
85 + ).next_to(neck, UP, buff=0)
86 +
87 + # Skalenstriche auf dem Glas
88 + marks = VGroup()
89 + for dy in [-0.5, -0.15, 0.2]:
90 + m_line = Line([-neck_w*0.8, dy, 0], [-neck_w*0.4, dy, 0], stroke_color=GREY_A, stroke_width=1.4)
91 + marks.add(m_line)
92 +
93 + glass = VGroup(body, neck, rim, marks)
94 +
95 + # Flüssigkeits-Trapez (Füllstand ca. 72% der Rumpfhöhe)
96 + fill_ratio = 0.72
97 + y_liquid_top = -body_h/2 + body_h * fill_ratio
98 + top_w = base_w - (base_w - neck_w) * fill_ratio
99 +
100 + l_bl = np.array([-base_w/2 + 0.05, -body_h/2 + 0.05, 0])
101 + l_br = np.array([ base_w/2 - 0.05, -body_h/2 + 0.05, 0])
102 + l_tr = np.array([ top_w/2 - 0.03, y_liquid_top, 0])
103 + l_tl = np.array([-top_w/2 + 0.03, y_liquid_top, 0])
104 +
105 + liquid = Polygon(
106 + l_bl, l_br, l_tr, l_tl,
107 + stroke_width=0, fill_color=ManimColor("#6A1B9A"), fill_opacity=0.85
108 + )
109 +
110 + meniscus = Line(l_tl, l_tr, stroke_color="#CE93D8", stroke_width=2.0)
111 +
112 + return VGroup(liquid, meniscus, glass), liquid, meniscus
113 +
114 +
115 +class ReaktionskinetikAnimation(Scene):
116 + def construct(self):
117 + self.camera.background_color = COLOR_BG
118 +
119 + # -------------------------------------------------------------
120 + # 1. Schritt: Initialisierung
121 + # -------------------------------------------------------------
122 + self.next_section("01_Initialisierung")
123 +
124 + formula_header = MathTex(
125 + r"\text{Reaktionsgeschwindigkeit: } v = \frac{|\Delta c|}{\Delta t}",
126 + font_size=FONT_SIZE_FORMULA, color=COLOR_STEP_LINE
127 + ).to_edge(UP, buff=0.15)
128 +
129 + self.play(Write(formula_header), run_time=0.8)
130 +
131 + # Erlenmeyerkolben links
132 + flask_group, liquid_poly, meniscus_line = create_erlenmeyer(width=2.4, height=3.0)
133 + flask_group.move_to([-4.6, -0.3, 0])
134 +
135 + flask_title = Tex(r"\textbf{Reaktionsansatz}", font_size=16, color=WHITE).next_to(flask_group, UP, buff=0.18)
136 + c_label = Tex(r"$c(\text{Edukt}) = $", font_size=15, color=COLOR_TEXT_MUTED)
137 + c_num = DecimalNumber(1.0, num_decimal_places=2, unit=r"\,\text{mol/l}", font_size=15, color=WHITE)
138 + c_readout = VGroup(c_label, c_num).arrange(RIGHT, buff=0.06).next_to(flask_group, DOWN, buff=0.22)
139 +
140 + erlenmeyer_full = VGroup(flask_group, flask_title, c_readout)
141 +
142 + # Koordinatensystem rechts
143 + axes = Axes(
144 + x_range=[0, 4200, 500],
145 + y_range=[0, 1.25, 0.2],
146 + x_length=6.4,
147 + y_length=4.4,
148 + axis_config=AXIS_COMMON_CONFIG,
149 + x_axis_config=get_x_axis_config([500, 1000, 1500, 2000, 2500, 3000, 3500, 4000], font_size=14),
150 + y_axis_config=get_y_axis_config([0.2, 0.4, 0.6, 0.8, 1.0, 1.2], font_size=14)
151 + ).move_to([1.8, -0.35, 0])
152 +
153 + y_label = Tex(r"Konzentration $c$ [$\text{mol}\cdot\text{l}^{-1}$]", font_size=15, color=COLOR_TEXT_PRIMARY).next_to(
154 + axes.y_axis.get_top(), UP, buff=0.10
155 + ).shift(RIGHT * 1.0)
156 + x_label = Tex(r"Zeit $t$ [$\text{s}$]", font_size=16, color=COLOR_TEXT_MUTED).next_to(
157 + axes.x_axis.get_end(), RIGHT, buff=0.15
158 + )
159 +
160 + axes_group = VGroup(axes, x_label, y_label)
161 +
162 + self.play(
163 + FadeIn(erlenmeyer_full),
164 + Create(axes), Write(x_label), Write(y_label),
165 + run_time=1.2
166 + )
167 + self.wait(0.4)
168 +
169 + # -------------------------------------------------------------
170 + # 2. Schritt: Reaktion und Punkte .. am Ende erscheint die Näherungskurve
171 + # -------------------------------------------------------------
172 + self.next_section("02_Reaktion_und_Messpunkte")
173 +
174 + np.random.seed(42)
175 + sample_times = np.arange(0, 4001, 250)
176 + noisy_concs = np.clip(get_conc(sample_times) + np.random.normal(0, 0.018, len(sample_times)), 0.02, 1.05)
177 + noisy_concs[0] = 1.00
178 +
179 + t_tracker = ValueTracker(0.0)
180 +
181 + # Updater für Flüssigkeitsfarbe & Konzentration
182 + liquid_poly.add_updater(lambda m: m.set_color(get_solution_color(get_conc(t_tracker.get_value()))).set_opacity(get_solution_opacity(get_conc(t_tracker.get_value()))))
183 + c_num.add_updater(lambda m: m.set_value(get_conc(t_tracker.get_value())))
184 +
185 + # Punktwolke der Messwerte mit Rauschen
186 + noisy_dots = VGroup()
187 + for t_s, c_s in zip(sample_times, noisy_concs):
188 + dot = Dot(axes.c2p(t_s, c_s), radius=0.065, color="#81D4FA")
189 + dot.set_opacity(0.0)
190 + noisy_dots.add(dot)
191 +
192 + # Updater für synchrone Messpunkterfassung exakt zum jeweiligen Reaktionszeitpunkt t_s
193 + def update_noisy_dots(group):
194 + curr_t = t_tracker.get_value()
195 + for dot, t_s in zip(group, sample_times):
196 + if curr_t < t_s:
197 + dot.set_opacity(0.0)
198 + else:
199 + dt = curr_t - t_s
200 + flash_window = 140.0
201 + if dt < flash_window:
202 + frac = dt / flash_window
203 + dot.set_opacity(1.0)
204 + dot.set_color(interpolate_color(WHITE, ManimColor("#81D4FA"), frac))
205 + else:
206 + dot.set_opacity(1.0)
207 + dot.set_color(ManimColor("#81D4FA"))
208 +
209 + noisy_dots.add_updater(update_noisy_dots)
210 + self.add(noisy_dots)
211 +
212 + # Reaktion läuft ab: Entfärbung der Flüssigkeit und Erscheinen der Punkte erfolgen synchron (erhöhte Geschwindigkeit)
213 + self.play(
214 + t_tracker.animate.set_value(4000.0),
215 + run_time=3.2,
216 + rate_func=linear
217 + )
218 + self.wait(0.3)
219 + noisy_dots.clear_updaters()
220 + for dot in noisy_dots:
221 + dot.set_opacity(1.0)
222 + dot.set_color(ManimColor("#81D4FA"))
223 +
224 + # Am Ende dieses Schritts: Näherungskurve erscheint durch die Punkte!
225 + full_curve = axes.plot(lambda t: get_conc(t), x_range=[0, 4000], color=COLOR_STEP_LINE, stroke_width=3.4)
226 + curve_label = Tex(r"\textbf{Ausgleichskurve} $c(t)$", font_size=14, color=COLOR_STEP_LINE).next_to(
227 + axes.c2p(500, get_conc(500)), UR, buff=0.10
228 + )
229 +
230 + self.play(Create(full_curve), FadeIn(curve_label), run_time=1.5)
231 + self.wait(0.6)
232 +
233 + # -------------------------------------------------------------
234 + # 3. Schritt: Zoom und Punkte verschwinden
235 + # -------------------------------------------------------------
236 + self.next_section("03_Zoom_und_Ausgleichskurve")
237 +
238 + liquid_poly.clear_updaters()
239 + c_num.clear_updaters()
240 +
241 + diagram_all = VGroup(axes, x_label, y_label, full_curve)
242 + target_center = np.array([-2.45, -0.30, 0.0])
243 + zoom_scale = 1.12
244 +
245 + self.play(
246 + FadeOut(erlenmeyer_full, shift=LEFT * 1.5),
247 + FadeOut(noisy_dots, scale=0.8),
248 + FadeOut(curve_label),
249 + diagram_all.animate.move_to(target_center).scale(zoom_scale),
250 + run_time=1.8
251 + )
252 + self.wait(0.6)
253 +
254 + # -------------------------------------------------------------
255 + # 4. Schritt: Mittlere Änderungsrate (Sekante 1000 - 2000 s)
256 + # -------------------------------------------------------------
257 + self.next_section("04_Mittlere_Aenderungsrate")
258 +
259 + t1, t2 = 1000.0, 2000.0
260 + c1, c2 = get_conc(t1), get_conc(t2)
261 + pt1 = axes.c2p(t1, c1)
262 + pt2 = axes.c2p(t2, c2)
263 +
264 + # 1. Zeitpunkte auf der x-Achse & von unten nach oben animierte Projektionslinien
265 + p_axis_1 = axes.c2p(t1, 0)
266 + p_axis_2 = axes.c2p(t2, 0)
267 +
268 + dot_axis1 = Dot(p_axis_1, radius=0.05, color=COLOR_DURCHSCHNITT)
269 + dot_axis2 = Dot(p_axis_2, radius=0.05, color=COLOR_DURCHSCHNITT)
270 + proj_line_1 = DashedLine(p_axis_1, pt1, color=COLOR_DURCHSCHNITT, stroke_width=2.0, dash_length=0.07)
271 + proj_line_2 = DashedLine(p_axis_2, pt2, color=COLOR_DURCHSCHNITT, stroke_width=2.0, dash_length=0.07)
272 +
273 + self.play(FadeIn(dot_axis1), FadeIn(dot_axis2), run_time=0.3)
274 + self.play(
275 + Create(proj_line_1), Create(proj_line_2),
276 + run_time=1.0
277 + )
278 + self.wait(0.2)
279 +
280 + # 2. Punkte auf der Kurve erscheinen
281 + dot1 = create_data_point(pt1, is_milestone=True, color=COLOR_DURCHSCHNITT)
282 + dot2 = create_data_point(pt2, is_milestone=True, color=COLOR_DURCHSCHNITT)
283 +
284 + self.play(
285 + FadeIn(dot1, scale=1.5),
286 + FadeIn(dot2, scale=1.5),
287 + run_time=0.6
288 + )
289 + self.wait(0.2)
290 +
291 + # 3. Delta y (|Delta c|) und Delta x (Delta t) - FONT_SIZE_DELTA aus styles.py für optimale Lesbarkeit
292 + pt_corner = axes.c2p(t2, c1)
293 + tri_sec_dc = Line(pt_corner, pt2, color=COLOR_DURCHSCHNITT, stroke_width=2.2)
294 + dc_sec_lbl = MathTex(r"|\Delta c| \approx 0{,}25\,\frac{\text{mol}}{\text{l}}", font_size=FONT_SIZE_DELTA, color=COLOR_DURCHSCHNITT).next_to(tri_sec_dc, RIGHT, buff=0.10)
295 +
296 + tri_sec_dt = Line(pt1, pt_corner, color=COLOR_DURCHSCHNITT, stroke_width=2.2)
297 + dt_sec_lbl = MathTex(r"\Delta t = 1000\,\text{s}", font_size=FONT_SIZE_DELTA, color=COLOR_DURCHSCHNITT).next_to(tri_sec_dt, UP, buff=0.10)
298 +
299 + self.play(Create(tri_sec_dc), Write(dc_sec_lbl), run_time=0.7)
300 + self.play(Create(tri_sec_dt), Write(dt_sec_lbl), run_time=0.7)
301 + self.wait(0.2)
302 +
303 + # 4. Sekante und Info-Badge (am rechten Rand)
304 + sec_dir = normalize(pt2 - pt1)
305 + sec_start = pt1 - sec_dir * 0.7
306 + sec_end = pt2 + sec_dir * 0.7
307 + secant_line = Line(sec_start, sec_end, color=COLOR_DURCHSCHNITT, stroke_width=3.2)
308 +
309 + badge_sec = create_info_badge(
310 + r"\textbf{Sekante} $\mathbf{\hat{=}}$ \textbf{Mittlere Rate}",
311 + r"$\bar{v} = \frac{|\Delta c|}{\Delta t} \approx 2{,}5 \times 10^{-4}\,\frac{\text{mol}}{\text{l}\cdot\text{s}}$",
312 + border_color=COLOR_DURCHSCHNITT, width=4.7, height=1.18,
313 + font_size_title=22, font_size_body=21
314 + ).move_to([4.45, 1.70, 0])
315 +
316 + self.play(
317 + Create(secant_line),
318 + FadeIn(badge_sec, shift=LEFT * 0.2),
319 + proj_line_1.animate.set_stroke(opacity=0.35),
320 + proj_line_2.animate.set_stroke(opacity=0.35),
321 + run_time=1.0
322 + )
323 + self.wait(1.5)
324 +
325 + # -------------------------------------------------------------
326 + # 5. Schritt: Momentane Änderungsrate (Tangente t = 3000 s, nebeneinander zur Sekante)
327 + # -------------------------------------------------------------
328 + self.next_section("05_Momentane_Aenderungsrate")
329 +
330 + tm = 3000.0
331 + cm = get_conc(tm)
332 + pt_m = axes.c2p(tm, cm)
333 +
334 + # 1. Erst der senkrechte Strich (von der Zeitachse nach oben zur Kurve)
335 + p_axis_m = axes.c2p(tm, 0)
336 + dot_axis_m = Dot(p_axis_m, radius=0.05, color=COLOR_MOMENTAN)
337 + proj_line_m = DashedLine(p_axis_m, pt_m, color=COLOR_MOMENTAN, stroke_width=2.0, dash_length=0.07)
338 +
339 + self.play(FadeIn(dot_axis_m), run_time=0.3)
340 + self.play(Create(proj_line_m), run_time=0.9)
341 + self.wait(0.2)
342 +
343 + # 2. Dann der Punkt auf der Kurve
344 + dot_m = create_data_point(pt_m, is_milestone=True, color=COLOR_MOMENTAN)
345 + self.play(FadeIn(dot_m, scale=1.5), run_time=0.5)
346 + self.wait(0.2)
347 +
348 + # 3. Schließlich die Tangente mit einem Steigungsdreieck (oberhalb, Breite 1000 s)
349 + t_tan_start, t_tan_end = 2400.0, 3600.0
350 + slope_m = -K_RATE * cm
351 + c_tan_start = cm + slope_m * (t_tan_start - tm)
352 + c_tan_end = cm + slope_m * (t_tan_end - tm)
353 +
354 + pt_tan_s = axes.c2p(t_tan_start, c_tan_start)
355 + pt_tan_e = axes.c2p(t_tan_end, c_tan_end)
356 + tan_m_line = Line(pt_tan_s, pt_tan_e, color=COLOR_MOMENTAN, stroke_width=3.2)
357 +
358 + # Steigungsdreieck an der Tangente (oberhalb der Tangente von 2500 bis 3500 s, Breite 1000)
359 + t_tri1 = 2500.0
360 + t_tri2 = 3500.0
361 + c_tri1 = cm + slope_m * (t_tri1 - tm)
362 + c_tri2 = cm + slope_m * (t_tri2 - tm)
363 +
364 + p_tri_start = axes.c2p(t_tri1, c_tri1)
365 + p_tri_corner = axes.c2p(t_tri2, c_tri1)
366 + p_tri_end = axes.c2p(t_tri2, c_tri2)
367 +
368 + tri_tan_dt = Line(p_tri_start, p_tri_corner, color=COLOR_MOMENTAN, stroke_width=2.2)
369 + tri_tan_dc = Line(p_tri_corner, p_tri_end, color=COLOR_MOMENTAN, stroke_width=2.2)
370 +
371 + dt_tan_lbl = MathTex(r"\Delta t = 1000\,\text{s}", font_size=FONT_SIZE_DELTA, color=COLOR_MOMENTAN).next_to(tri_tan_dt, UP, buff=0.08)
372 + dc_tan_lbl = MathTex(r"|\Delta c| \approx 0{,}075\,\frac{\text{mol}}{\text{l}}", font_size=FONT_SIZE_DELTA, color=COLOR_MOMENTAN).next_to(tri_tan_dc, RIGHT, buff=0.10)
373 +
374 + # Info-Badge rechts unter dem 1. Badge (ohne v im Titel, vergrößerte Schrift)
375 + badge_tan = create_info_badge(
376 + r"\textbf{Tangente} $\mathbf{\hat{=}}$ \textbf{Momentane Rate}",
377 + r"$v = \frac{|\Delta c|}{\Delta t} \approx 7{,}5 \times 10^{-5}\,\frac{\text{mol}}{\text{l}\cdot\text{s}}$",
378 + border_color=COLOR_MOMENTAN, width=4.7, height=1.18,
379 + font_size_title=22, font_size_body=21
380 + ).move_to([4.45, 0.35, 0])
381 +
382 + self.play(Create(tan_m_line), run_time=1.0)
383 + self.play(
384 + Create(tri_tan_dt), Write(dt_tan_lbl),
385 + Create(tri_tan_dc), Write(dc_tan_lbl),
386 + run_time=1.0
387 + )
388 + self.play(
389 + FadeIn(badge_tan, shift=LEFT * 0.2),
390 + proj_line_m.animate.set_stroke(opacity=0.35),
391 + run_time=0.8
392 + )
393 + self.wait(1.5)
394 +
395 + # -------------------------------------------------------------
396 + # 6. Schritt: Startgeschwindigkeit (Tangente t = 0 s, oberhalb, Breite 1000 s)
397 + # -------------------------------------------------------------
398 + self.next_section("06_Startgeschwindigkeit")
399 +
400 + p0 = axes.c2p(0, 1.0)
401 + p0_end = axes.c2p(1282, 0.0)
402 +
403 + tan_0 = Line(p0, p0_end, color=COLOR_ANFANG, stroke_width=3.2)
404 + dot_0 = create_data_point(p0, is_milestone=True, color=COLOR_ANFANG)
405 +
406 + # Steigungsdreieck oberhalb mit Breite 1000 s (von t = 0 bis t = 1000 s)
407 + t0_start = 0.0
408 + t0_end = 1000.0
409 + c0_start = 1.0
410 + c0_end = 1.0 - K_RATE * 1000.0 # 0.22 mol/l
411 +
412 + p_0_start = axes.c2p(t0_start, c0_start)
413 + p_0_corner = axes.c2p(t0_end, c0_start)
414 + p_0_tan_pt = axes.c2p(t0_end, c0_end)
415 +
416 + tri_0_dt = Line(p_0_start, p_0_corner, color=COLOR_ANFANG, stroke_width=2.2)
417 + tri_0_dc = Line(p_0_corner, p_0_tan_pt, color=COLOR_ANFANG, stroke_width=2.2)
418 +
419 + dt_0_lbl = MathTex(r"\Delta t = 1000\,\text{s}", font_size=FONT_SIZE_DELTA, color=COLOR_ANFANG).next_to(tri_0_dt, UP, buff=0.08)
420 + dc_0_lbl = MathTex(r"|\Delta c| = 0{,}78\,\frac{\text{mol}}{\text{l}}", font_size=FONT_SIZE_DELTA, color=COLOR_ANFANG).next_to(tri_0_dc, RIGHT, buff=0.10)
421 +
422 + # Info-Badge rechts unter dem 2. Badge
423 + badge_0 = create_info_badge(
424 + r"\textbf{Tangente} $\mathbf{\hat{=}}$ \textbf{Startgeschwindigkeit}",
425 + r"$v_0 = \frac{|\Delta c|}{\Delta t} \approx 7{,}8 \times 10^{-4}\,\frac{\text{mol}}{\text{l}\cdot\text{s}}$",
426 + border_color=COLOR_ANFANG, width=4.7, height=1.18,
427 + font_size_title=22, font_size_body=21
428 + ).move_to([4.45, -1.00, 0])
429 +
430 + self.play(
431 + FadeIn(dot_0), Create(tan_0),
432 + Create(tri_0_dt), Write(dt_0_lbl),
433 + Create(tri_0_dc), Write(dc_0_lbl),
434 + FadeIn(badge_0, shift=LEFT * 0.2),
435 + run_time=1.8
436 + )
437 + self.wait(2.5)
438 +