kinaar111 commited on
Commit
02fc690
·
verified ·
1 Parent(s): aa4bd0d

60s petal sequence: 1 tick/s, G2-G7 color cycle every 10s

Browse files
__pycache__/app.cpython-314.pyc CHANGED
Binary files a/__pycache__/app.cpython-314.pyc and b/__pycache__/app.cpython-314.pyc differ
 
app.py CHANGED
@@ -3,13 +3,21 @@
3
 
4
  from __future__ import annotations
5
 
 
 
6
  import matplotlib
7
 
8
  matplotlib.use("Agg")
9
 
10
  from universal_clock import UniversalPiClock, render_clock_array
11
  from universal_clock.clock import EARTH_DAY_SECONDS
12
- from universal_clock.visualize import DEFAULT_SLICE_LINES
 
 
 
 
 
 
13
 
14
  import gradio as gr
15
 
@@ -24,19 +32,46 @@ DEMO_SCENES: dict[str, tuple[int, str]] = {
24
  "Carry · 50k": (50000, "Carry propagates — watch G3+ advance."),
25
  "Deep · 500k": (500000, "Deep hierarchy — stress-test all seven gears."),
26
  }
27
- DEFAULT_DEMO = "PoC · 5k"
 
 
 
 
 
 
28
 
29
  PANEL_HEIGHT = 860
30
  CLOCK_VIEWPORT_SPAN = 2.12
31
  CONTROLS_WIDTH = 220
32
 
33
 
34
- def _format_state(clock: UniversalPiClock) -> str:
35
- lines = [f"Total ticks: {clock.total_ticks:,}"]
 
 
 
 
 
 
 
 
 
 
 
 
 
36
  for i in range(1, 8):
37
  k = clock.gears[i - 1]
38
  fill = 100.0 * k / 350
39
- lines.append(f"Gear {i}: k={k:3d} v≈{clock.value(i):6.2f} ({fill:5.1f}% filled)")
 
 
 
 
 
 
 
 
40
  return "\n".join(lines)
41
 
42
 
@@ -47,6 +82,8 @@ def _render(
47
  show_hands: bool,
48
  show_labels: bool,
49
  show_ticks: bool,
 
 
50
  ) -> tuple:
51
  image = render_clock_array(
52
  clock,
@@ -59,8 +96,9 @@ def _render(
59
  title="",
60
  viewport_span=CLOCK_VIEWPORT_SPAN,
61
  tight_margins=True,
 
62
  )
63
- return image, _format_state(clock)
64
 
65
 
66
  def _display_kwargs(
@@ -84,14 +122,67 @@ def load_demo_scene(
84
  slice_lines: int,
85
  overlays: list[str],
86
  ) -> tuple:
87
- """Load a named demo from scratch."""
88
  clock = _new_clock()
89
  ticks, _ = DEMO_SCENES.get(scene, (0, ""))
90
- if ticks:
91
  clock.fast_forward(ticks)
92
  return clock, *_render(clock, **_display_kwargs(slice_lines, overlays))
93
 
94
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
95
  def demo_hint(scene: str) -> str:
96
  return DEMO_SCENES.get(scene, (0, ""))[1]
97
 
@@ -168,12 +259,33 @@ def refresh_view(
168
  clock: UniversalPiClock | None,
169
  slice_lines: int,
170
  overlays: list[str],
 
 
171
  ) -> tuple:
172
  if clock is None:
173
  clock = _new_clock()
 
 
 
 
 
 
 
 
 
174
  return clock, *_render(clock, **_display_kwargs(slice_lines, overlays))
175
 
176
 
 
 
 
 
 
 
 
 
 
 
177
  THEME = gr.themes.Base(
178
  primary_hue="orange",
179
  secondary_hue="slate",
@@ -370,13 +482,15 @@ Seven-gear cascading π clock — [GitHub]({GITHUB_URL}) · [Space]({HF_SPACE_UR
370
 
371
  clock_state = gr.State(_new_clock())
372
  realtime_running = gr.State(False)
 
 
373
 
374
  with gr.Row(elem_id="main-row", equal_height=True, height=PANEL_HEIGHT):
375
  with gr.Column(scale=1, min_width=CONTROLS_WIDTH, elem_id="col-controls"):
376
  with gr.Column(elem_id="controls-scroll", variant="compact"):
377
  gr.Markdown("**1 · Demo**", elem_classes=["section-header"])
378
  demo_scene = gr.Dropdown(
379
- choices=list(DEMO_SCENES.keys()),
380
  value=DEFAULT_DEMO,
381
  label="Scene",
382
  show_label=False,
@@ -453,20 +567,30 @@ Seven-gear cascading π clock — [GitHub]({GITHUB_URL}) · [Space]({HF_SPACE_UR
453
  interactive=False,
454
  )
455
 
456
- timer = gr.Timer(0.15, active=False)
 
457
 
458
  display_inputs = [slice_lines, overlays]
459
  outputs = [clock_state, clock_image, state_text]
460
  rt_outputs = [clock_state, realtime_running, speed, clock_image, state_text]
 
461
  view_outputs = [clock_state, clock_image, state_text]
 
 
 
 
 
 
 
462
 
463
  reload_demo_btn.click(
464
- load_demo_scene,
465
  inputs=[demo_scene, *display_inputs],
466
- outputs=outputs,
467
  ).then(lambda: False, outputs=realtime_running).then(
468
- lambda: gr.Timer(active=False),
469
- outputs=timer,
 
470
  )
471
 
472
  demo_scene.change(
@@ -474,22 +598,20 @@ Seven-gear cascading π clock — [GitHub]({GITHUB_URL}) · [Space]({HF_SPACE_UR
474
  inputs=demo_scene,
475
  outputs=demo_hint_box,
476
  ).then(
477
- load_demo_scene,
478
  inputs=[demo_scene, *display_inputs],
479
- outputs=outputs,
480
  ).then(lambda: False, outputs=realtime_running).then(
481
- lambda: gr.Timer(active=False),
482
- outputs=timer,
 
483
  )
484
 
485
  reset_btn.click(
486
  reset_clock,
487
  inputs=display_inputs,
488
  outputs=outputs,
489
- ).then(lambda: False, outputs=realtime_running).then(
490
- lambda: gr.Timer(active=False),
491
- outputs=timer,
492
- )
493
 
494
  step_btn.click(
495
  step_clock,
@@ -501,32 +623,45 @@ Seven-gear cascading π clock — [GitHub]({GITHUB_URL}) · [Space]({HF_SPACE_UR
501
  enable_realtime,
502
  inputs=[clock_state, rev_seconds, speed, *display_inputs],
503
  outputs=rt_outputs,
504
- ).then(lambda: gr.Timer(active=True), outputs=timer)
 
 
505
 
506
  stop_rt.click(
507
  disable_realtime,
508
  inputs=[clock_state, *display_inputs],
509
  outputs=rt_outputs,
510
- ).then(lambda: gr.Timer(active=False), outputs=timer)
511
 
512
  for control in (slice_lines, overlays):
513
  control.change(
514
  refresh_view,
515
- inputs=[clock_state, *display_inputs],
516
  outputs=view_outputs,
517
  )
518
 
519
- timer.tick(
520
  realtime_tick,
521
  inputs=[clock_state, realtime_running, speed, *display_inputs],
522
  outputs=view_outputs,
523
  show_progress="hidden",
524
  )
525
 
 
 
 
 
 
 
 
526
  demo.load(
527
- load_demo_scene,
528
  inputs=[demo_scene, *display_inputs],
529
- outputs=outputs,
 
 
 
 
530
  )
531
 
532
  return demo
 
3
 
4
  from __future__ import annotations
5
 
6
+ import time
7
+
8
  import matplotlib
9
 
10
  matplotlib.use("Agg")
11
 
12
  from universal_clock import UniversalPiClock, render_clock_array
13
  from universal_clock.clock import EARTH_DAY_SECONDS
14
+ from universal_clock.visualize import (
15
+ DEFAULT_SLICE_LINES,
16
+ PETAL_CYCLE_SECONDS,
17
+ PETAL_SEGMENT_SECONDS,
18
+ petal_color_for_elapsed,
19
+ petal_color_gear,
20
+ )
21
 
22
  import gradio as gr
23
 
 
32
  "Carry · 50k": (50000, "Carry propagates — watch G3+ advance."),
33
  "Deep · 500k": (500000, "Deep hierarchy — stress-test all seven gears."),
34
  }
35
+ PETAL_SCENE = "Petal · 60s"
36
+ DEMO_SCENES[PETAL_SCENE] = (
37
+ -1,
38
+ "60s loop: G1 ticks 1/s; petal color cycles G2→G7 every 10s.",
39
+ )
40
+ SCENE_CHOICES = list(DEMO_SCENES.keys())
41
+ DEFAULT_DEMO = PETAL_SCENE
42
 
43
  PANEL_HEIGHT = 860
44
  CLOCK_VIEWPORT_SPAN = 2.12
45
  CONTROLS_WIDTH = 220
46
 
47
 
48
+ def _format_state(
49
+ clock: UniversalPiClock,
50
+ *,
51
+ petal_elapsed: float | None = None,
52
+ ) -> str:
53
+ lines: list[str] = [f"Total ticks: {clock.total_ticks:,}"]
54
+ if petal_elapsed is not None:
55
+ t = petal_elapsed % PETAL_CYCLE_SECONDS
56
+ seg = int(t // PETAL_SEGMENT_SECONDS)
57
+ g = petal_color_gear(t)
58
+ lines.append(
59
+ f"Petal: {t:4.1f}s / {PETAL_CYCLE_SECONDS}s "
60
+ f"· color=G{g} · segment {seg * 10}–{(seg + 1) * 10}s"
61
+ )
62
+ lines.append("")
63
  for i in range(1, 8):
64
  k = clock.gears[i - 1]
65
  fill = 100.0 * k / 350
66
+ if petal_elapsed is not None and i == 1:
67
+ g = petal_color_gear(petal_elapsed)
68
+ overlay = f"petal→G{g}"
69
+ elif petal_elapsed is not None:
70
+ overlay = "hands"
71
+ else:
72
+ overlay = ""
73
+ base = f"Gear {i}: k={k:3d} v≈{clock.value(i):6.2f} ({fill:5.1f}% filled)"
74
+ lines.append(f"{base} {overlay}" if overlay else base)
75
  return "\n".join(lines)
76
 
77
 
 
82
  show_hands: bool,
83
  show_labels: bool,
84
  show_ticks: bool,
85
+ petal_elapsed: float | None = None,
86
+ gear_colors: dict[int, str] | None = None,
87
  ) -> tuple:
88
  image = render_clock_array(
89
  clock,
 
96
  title="",
97
  viewport_span=CLOCK_VIEWPORT_SPAN,
98
  tight_margins=True,
99
+ gear_colors=gear_colors,
100
  )
101
+ return image, _format_state(clock, petal_elapsed=petal_elapsed)
102
 
103
 
104
  def _display_kwargs(
 
122
  slice_lines: int,
123
  overlays: list[str],
124
  ) -> tuple:
125
+ """Load a named demo from scratch (static scenes only)."""
126
  clock = _new_clock()
127
  ticks, _ = DEMO_SCENES.get(scene, (0, ""))
128
+ if ticks > 0:
129
  clock.fast_forward(ticks)
130
  return clock, *_render(clock, **_display_kwargs(slice_lines, overlays))
131
 
132
 
133
+ def start_petal_sequence(
134
+ slice_lines: int,
135
+ overlays: list[str],
136
+ ) -> tuple:
137
+ """Reset and begin the 60s petal loop (1 tick/s, color G2→G7)."""
138
+ clock = _new_clock()
139
+ elapsed = 0.0
140
+ color = petal_color_for_elapsed(elapsed)
141
+ return (
142
+ clock,
143
+ True,
144
+ time.time(),
145
+ *_render(
146
+ clock,
147
+ **_display_kwargs(slice_lines, overlays),
148
+ petal_elapsed=elapsed,
149
+ gear_colors={1: color},
150
+ ),
151
+ )
152
+
153
+
154
+ def petal_sequence_tick(
155
+ clock: UniversalPiClock | None,
156
+ running: bool,
157
+ epoch: float,
158
+ slice_lines: int,
159
+ overlays: list[str],
160
+ ) -> tuple:
161
+ if clock is None:
162
+ clock = _new_clock()
163
+ if not running:
164
+ return (
165
+ clock,
166
+ running,
167
+ epoch,
168
+ *_render(clock, **_display_kwargs(slice_lines, overlays)),
169
+ )
170
+ elapsed = time.time() - epoch
171
+ clock.tick(1)
172
+ color = petal_color_for_elapsed(elapsed)
173
+ return (
174
+ clock,
175
+ running,
176
+ epoch,
177
+ *_render(
178
+ clock,
179
+ **_display_kwargs(slice_lines, overlays),
180
+ petal_elapsed=elapsed,
181
+ gear_colors={1: color},
182
+ ),
183
+ )
184
+
185
+
186
  def demo_hint(scene: str) -> str:
187
  return DEMO_SCENES.get(scene, (0, ""))[1]
188
 
 
259
  clock: UniversalPiClock | None,
260
  slice_lines: int,
261
  overlays: list[str],
262
+ petal_running: bool = False,
263
+ petal_epoch: float = 0.0,
264
  ) -> tuple:
265
  if clock is None:
266
  clock = _new_clock()
267
+ if petal_running:
268
+ elapsed = time.time() - petal_epoch
269
+ color = petal_color_for_elapsed(elapsed)
270
+ return clock, *_render(
271
+ clock,
272
+ **_display_kwargs(slice_lines, overlays),
273
+ petal_elapsed=elapsed,
274
+ gear_colors={1: color},
275
+ )
276
  return clock, *_render(clock, **_display_kwargs(slice_lines, overlays))
277
 
278
 
279
+ def _timers_for_scene(scene: str) -> tuple:
280
+ if scene == PETAL_SCENE:
281
+ return gr.Timer(active=False), gr.Timer(active=True)
282
+ return gr.Timer(active=False), gr.Timer(active=False)
283
+
284
+
285
+ def _stop_all_timers() -> tuple:
286
+ return False, False, gr.Timer(active=False), gr.Timer(active=False)
287
+
288
+
289
  THEME = gr.themes.Base(
290
  primary_hue="orange",
291
  secondary_hue="slate",
 
482
 
483
  clock_state = gr.State(_new_clock())
484
  realtime_running = gr.State(False)
485
+ petal_running = gr.State(False)
486
+ petal_epoch = gr.State(0.0)
487
 
488
  with gr.Row(elem_id="main-row", equal_height=True, height=PANEL_HEIGHT):
489
  with gr.Column(scale=1, min_width=CONTROLS_WIDTH, elem_id="col-controls"):
490
  with gr.Column(elem_id="controls-scroll", variant="compact"):
491
  gr.Markdown("**1 · Demo**", elem_classes=["section-header"])
492
  demo_scene = gr.Dropdown(
493
+ choices=SCENE_CHOICES,
494
  value=DEFAULT_DEMO,
495
  label="Scene",
496
  show_label=False,
 
567
  interactive=False,
568
  )
569
 
570
+ earth_timer = gr.Timer(0.15, active=False)
571
+ petal_timer = gr.Timer(1.0, active=False)
572
 
573
  display_inputs = [slice_lines, overlays]
574
  outputs = [clock_state, clock_image, state_text]
575
  rt_outputs = [clock_state, realtime_running, speed, clock_image, state_text]
576
+ petal_outputs = [clock_state, petal_running, petal_epoch, clock_image, state_text]
577
  view_outputs = [clock_state, clock_image, state_text]
578
+ anim_stop = [realtime_running, petal_running, earth_timer, petal_timer]
579
+
580
+ def _load_scene_or_petal(scene, *display):
581
+ if scene == PETAL_SCENE:
582
+ return start_petal_sequence(*display)
583
+ clock, img, st = load_demo_scene(scene, *display)
584
+ return clock, False, 0.0, img, st
585
 
586
  reload_demo_btn.click(
587
+ _load_scene_or_petal,
588
  inputs=[demo_scene, *display_inputs],
589
+ outputs=petal_outputs,
590
  ).then(lambda: False, outputs=realtime_running).then(
591
+ _timers_for_scene,
592
+ inputs=demo_scene,
593
+ outputs=[earth_timer, petal_timer],
594
  )
595
 
596
  demo_scene.change(
 
598
  inputs=demo_scene,
599
  outputs=demo_hint_box,
600
  ).then(
601
+ _load_scene_or_petal,
602
  inputs=[demo_scene, *display_inputs],
603
+ outputs=petal_outputs,
604
  ).then(lambda: False, outputs=realtime_running).then(
605
+ _timers_for_scene,
606
+ inputs=demo_scene,
607
+ outputs=[earth_timer, petal_timer],
608
  )
609
 
610
  reset_btn.click(
611
  reset_clock,
612
  inputs=display_inputs,
613
  outputs=outputs,
614
+ ).then(_stop_all_timers, outputs=anim_stop)
 
 
 
615
 
616
  step_btn.click(
617
  step_clock,
 
623
  enable_realtime,
624
  inputs=[clock_state, rev_seconds, speed, *display_inputs],
625
  outputs=rt_outputs,
626
+ ).then(lambda: (False, gr.Timer(active=False), gr.Timer(active=True)), outputs=[
627
+ petal_running, petal_timer, earth_timer,
628
+ ])
629
 
630
  stop_rt.click(
631
  disable_realtime,
632
  inputs=[clock_state, *display_inputs],
633
  outputs=rt_outputs,
634
+ ).then(lambda: gr.Timer(active=False), outputs=earth_timer)
635
 
636
  for control in (slice_lines, overlays):
637
  control.change(
638
  refresh_view,
639
+ inputs=[clock_state, *display_inputs, petal_running, petal_epoch],
640
  outputs=view_outputs,
641
  )
642
 
643
+ earth_timer.tick(
644
  realtime_tick,
645
  inputs=[clock_state, realtime_running, speed, *display_inputs],
646
  outputs=view_outputs,
647
  show_progress="hidden",
648
  )
649
 
650
+ petal_timer.tick(
651
+ petal_sequence_tick,
652
+ inputs=[clock_state, petal_running, petal_epoch, *display_inputs],
653
+ outputs=petal_outputs,
654
+ show_progress="hidden",
655
+ )
656
+
657
  demo.load(
658
+ _load_scene_or_petal,
659
  inputs=[demo_scene, *display_inputs],
660
+ outputs=petal_outputs,
661
+ ).then(
662
+ _timers_for_scene,
663
+ inputs=demo_scene,
664
+ outputs=[earth_timer, petal_timer],
665
  )
666
 
667
  return demo
universal_clock/__init__.py CHANGED
@@ -1,15 +1,31 @@
1
  """Universal π Clock — Egg of Life 7-gear sacred geometry timekeeper."""
2
 
3
  from .clock import EARTH_DAY_SECONDS, SLICES_PER_GEAR, UniversalPiClock
4
- from .visualize import DEFAULT_SLICE_LINES, GEAR_COLORS, draw_hand, render_clock, render_clock_array
 
 
 
 
 
 
 
 
 
 
 
5
 
6
  __all__ = [
7
  "DEFAULT_SLICE_LINES",
 
 
8
  "EARTH_DAY_SECONDS",
9
  "SLICES_PER_GEAR",
10
  "UniversalPiClock",
11
  "GEAR_COLORS",
12
  "draw_hand",
 
 
 
13
  "render_clock",
14
  "render_clock_array",
15
  ]
 
1
  """Universal π Clock — Egg of Life 7-gear sacred geometry timekeeper."""
2
 
3
  from .clock import EARTH_DAY_SECONDS, SLICES_PER_GEAR, UniversalPiClock
4
+ from .visualize import (
5
+ DEFAULT_SLICE_LINES,
6
+ GEAR_COLORS,
7
+ PETAL_CYCLE_SECONDS,
8
+ PETAL_SEGMENT_SECONDS,
9
+ draw_hand,
10
+ petal_color_for_elapsed,
11
+ petal_color_gear,
12
+ petal_segment_index,
13
+ render_clock,
14
+ render_clock_array,
15
+ )
16
 
17
  __all__ = [
18
  "DEFAULT_SLICE_LINES",
19
+ "PETAL_CYCLE_SECONDS",
20
+ "PETAL_SEGMENT_SECONDS",
21
  "EARTH_DAY_SECONDS",
22
  "SLICES_PER_GEAR",
23
  "UniversalPiClock",
24
  "GEAR_COLORS",
25
  "draw_hand",
26
+ "petal_color_for_elapsed",
27
+ "petal_color_gear",
28
+ "petal_segment_index",
29
  "render_clock",
30
  "render_clock_array",
31
  ]
universal_clock/__pycache__/__init__.cpython-314.pyc CHANGED
Binary files a/universal_clock/__pycache__/__init__.cpython-314.pyc and b/universal_clock/__pycache__/__init__.cpython-314.pyc differ
 
universal_clock/__pycache__/visualize.cpython-314.pyc CHANGED
Binary files a/universal_clock/__pycache__/visualize.cpython-314.pyc and b/universal_clock/__pycache__/visualize.cpython-314.pyc differ
 
universal_clock/visualize.py CHANGED
@@ -26,6 +26,23 @@ OUTLINE_COLOR = "#3d4f5f"
26
  LABEL_COLOR = "#8b9cb3"
27
  TEXT_COLOR = "#e6edf3"
28
  DEFAULT_SLICE_LINES = 70
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
29
 
30
 
31
  def egg_of_life_positions(
@@ -224,6 +241,7 @@ def draw_clock(
224
  slice_lines: int = DEFAULT_SLICE_LINES,
225
  title: str | None = None,
226
  viewport_span: float | None = None,
 
227
  ) -> None:
228
  """Draw the Egg of Life clock face onto an existing axes."""
229
  centers = egg_of_life_positions(circle_radius)
@@ -236,15 +254,17 @@ def draw_clock(
236
  ax.set_ylim(-span, span)
237
  ax.axis("off")
238
 
 
239
  for gear_num in range(1, 8):
240
  idx = gear_num - 1
 
241
  _draw_gear_circle(
242
  ax,
243
  centers[idx],
244
  circle_radius,
245
  gear_num,
246
  clock.gears[idx],
247
- GEAR_COLORS[idx],
248
  show_ticks=show_ticks,
249
  show_labels=show_labels,
250
  show_hands=show_hands,
@@ -275,6 +295,7 @@ def render_clock(
275
  title: str | None = None,
276
  viewport_span: float | None = None,
277
  tight_margins: bool = False,
 
278
  ) -> plt.Figure:
279
  """Render the Egg of Life clock face for the current clock state."""
280
  fig, ax = plt.subplots(figsize=figsize, facecolor=BG_COLOR)
@@ -288,6 +309,7 @@ def render_clock(
288
  slice_lines=slice_lines,
289
  title=title,
290
  viewport_span=viewport_span,
 
291
  )
292
  if tight_margins:
293
  fig.subplots_adjust(left=0, right=1, bottom=0, top=1, wspace=0, hspace=0)
@@ -311,6 +333,7 @@ def render_clock_array(
311
  title: str = "",
312
  viewport_span: float | None = None,
313
  tight_margins: bool = False,
 
314
  ) -> np.ndarray:
315
  """Render the clock to an RGB numpy array for Gradio / web display."""
316
  fig = render_clock(
@@ -325,6 +348,7 @@ def render_clock_array(
325
  title=title,
326
  viewport_span=viewport_span,
327
  tight_margins=tight_margins,
 
328
  )
329
  fig.canvas.draw()
330
  rgba = np.asarray(fig.canvas.buffer_rgba())
 
26
  LABEL_COLOR = "#8b9cb3"
27
  TEXT_COLOR = "#e6edf3"
28
  DEFAULT_SLICE_LINES = 70
29
+ PETAL_CYCLE_SECONDS = 60
30
+ PETAL_SEGMENT_SECONDS = 10
31
+
32
+
33
+ def petal_segment_index(elapsed_seconds: float) -> int:
34
+ """Segment 0–5 within the 60s petal loop (each 10s → gears 2–7)."""
35
+ return int((elapsed_seconds % PETAL_CYCLE_SECONDS) // PETAL_SEGMENT_SECONDS)
36
+
37
+
38
+ def petal_color_gear(elapsed_seconds: float) -> int:
39
+ """Gear number (2–7) whose hand color the center petal matches."""
40
+ return petal_segment_index(elapsed_seconds) + 2
41
+
42
+
43
+ def petal_color_for_elapsed(elapsed_seconds: float) -> str:
44
+ """Fill/hand color for Gear 1 petal at this point in the 60s sequence."""
45
+ return GEAR_COLORS[petal_color_gear(elapsed_seconds) - 1]
46
 
47
 
48
  def egg_of_life_positions(
 
241
  slice_lines: int = DEFAULT_SLICE_LINES,
242
  title: str | None = None,
243
  viewport_span: float | None = None,
244
+ gear_colors: dict[int, str] | None = None,
245
  ) -> None:
246
  """Draw the Egg of Life clock face onto an existing axes."""
247
  centers = egg_of_life_positions(circle_radius)
 
254
  ax.set_ylim(-span, span)
255
  ax.axis("off")
256
 
257
+ overrides = gear_colors or {}
258
  for gear_num in range(1, 8):
259
  idx = gear_num - 1
260
+ color = overrides.get(gear_num, GEAR_COLORS[idx])
261
  _draw_gear_circle(
262
  ax,
263
  centers[idx],
264
  circle_radius,
265
  gear_num,
266
  clock.gears[idx],
267
+ color,
268
  show_ticks=show_ticks,
269
  show_labels=show_labels,
270
  show_hands=show_hands,
 
295
  title: str | None = None,
296
  viewport_span: float | None = None,
297
  tight_margins: bool = False,
298
+ gear_colors: dict[int, str] | None = None,
299
  ) -> plt.Figure:
300
  """Render the Egg of Life clock face for the current clock state."""
301
  fig, ax = plt.subplots(figsize=figsize, facecolor=BG_COLOR)
 
309
  slice_lines=slice_lines,
310
  title=title,
311
  viewport_span=viewport_span,
312
+ gear_colors=gear_colors,
313
  )
314
  if tight_margins:
315
  fig.subplots_adjust(left=0, right=1, bottom=0, top=1, wspace=0, hspace=0)
 
333
  title: str = "",
334
  viewport_span: float | None = None,
335
  tight_margins: bool = False,
336
+ gear_colors: dict[int, str] | None = None,
337
  ) -> np.ndarray:
338
  """Render the clock to an RGB numpy array for Gradio / web display."""
339
  fig = render_clock(
 
348
  title=title,
349
  viewport_span=viewport_span,
350
  tight_margins=tight_margins,
351
+ gear_colors=gear_colors,
352
  )
353
  fig.canvas.draw()
354
  rgba = np.asarray(fig.canvas.buffer_rgba())