Pointf5ive commited on
Commit
c0388f3
·
verified ·
1 Parent(s): 4a18e9c

ui: compass rose star + smooth gradient arc + tick marks on signal wheel

Browse files
Files changed (1) hide show
  1. app.py +99 -29
app.py CHANGED
@@ -493,35 +493,57 @@ TOTEM_CSS = """
493
 
494
  .signal-track {
495
  fill: none;
496
- stroke: rgba(246, 241, 232, 0.10);
497
- stroke-width: 7;
498
  }
499
 
500
  .signal-progress {
501
  fill: none;
502
- stroke: url(#gaugeGradient);
503
- stroke-width: 7;
504
  stroke-linecap: round;
505
  transform: rotate(-90deg);
506
  transform-origin: 80px 80px;
507
- filter: drop-shadow(0 0 6px rgba(138, 77, 255, 0.6));
508
  }
509
 
510
  .signal-outer-ring {
511
  fill: none;
512
- stroke: rgba(242, 193, 78, 0.28);
513
  stroke-width: 1;
514
  }
515
 
516
- .signal-inner-ring {
517
  fill: none;
518
  stroke: rgba(57, 201, 255, 0.18);
 
 
 
 
 
 
 
 
 
 
 
519
  stroke-width: 1;
520
  }
521
 
 
 
 
 
 
 
 
 
 
 
522
  .signal-star {
523
- fill: var(--totem-gold);
524
- filter: drop-shadow(0 0 8px rgba(242, 193, 78, 0.7));
 
 
525
  }
526
 
527
  .totem-signal-label {
@@ -1390,31 +1412,79 @@ def render_signal_gauge(value: int) -> str:
1390
  large = 1 if (end_deg - start_deg) > 180 else 0
1391
  return f"M {x1:.2f} {y1:.2f} A {r} {r} 0 {large} 1 {x2:.2f} {y2:.2f}"
1392
 
1393
- seg_colors = ["#8A4DFF", "#39C9FF", "#22B573", "#F2C14E"]
1394
- seg_deg = 90.0
1395
- paths = []
1396
- for idx, color in enumerate(seg_colors):
1397
- seg_start = idx * seg_deg
1398
- if total_deg <= seg_start:
1399
- break
1400
- seg_filled = min(total_deg - seg_start, seg_deg)
1401
- if seg_filled < 0.5:
1402
- break
1403
- svg_start = seg_start - 90 # -90 = top of circle
1404
- svg_end = svg_start + seg_filled
1405
- d = arc_path(80, 80, 68, svg_start, svg_end)
1406
- paths.append(f'<path d="{d}" fill="none" stroke="{color}" stroke-width="7" stroke-linecap="round" />')
1407
-
1408
- paths_html = "\n ".join(paths)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1409
  return f"""
1410
  <div class="totem-signal-wrap">
1411
  <svg class="totem-signal" viewBox="0 0 160 160" role="img" aria-label="TOTEM Signal {clamped} percent">
1412
  <circle class="signal-outer-ring" cx="80" cy="80" r="76"></circle>
 
1413
  <circle class="signal-track" cx="80" cy="80" r="68"></circle>
1414
- {paths_html}
1415
- <circle class="signal-inner-ring" cx="80" cy="80" r="52"></circle>
1416
- <circle class="signal-inner-ring" cx="80" cy="80" r="40"></circle>
1417
- <path class="signal-star" d="M80 58 L84.7 73.5 L100.9 73.5 L87.8 82.9 L92.6 98.4 L80 89.1 L67.4 98.4 L72.2 82.9 L59.1 73.5 L75.3 73.5 Z"></path>
1418
  </svg>
1419
  <div class="totem-signal-label">TOTEM Signal {clamped}%</div>
1420
  </div>
 
493
 
494
  .signal-track {
495
  fill: none;
496
+ stroke: rgba(246, 241, 232, 0.08);
497
+ stroke-width: 8;
498
  }
499
 
500
  .signal-progress {
501
  fill: none;
502
+ stroke-width: 8;
 
503
  stroke-linecap: round;
504
  transform: rotate(-90deg);
505
  transform-origin: 80px 80px;
506
+ filter: drop-shadow(0 0 7px rgba(57, 201, 255, 0.55));
507
  }
508
 
509
  .signal-outer-ring {
510
  fill: none;
511
+ stroke: rgba(242, 193, 78, 0.35);
512
  stroke-width: 1;
513
  }
514
 
515
+ .signal-middle-ring {
516
  fill: none;
517
  stroke: rgba(57, 201, 255, 0.18);
518
+ stroke-width: 0.75;
519
+ }
520
+
521
+ .signal-inner-ring {
522
+ fill: none;
523
+ stroke: rgba(57, 201, 255, 0.12);
524
+ stroke-width: 0.75;
525
+ }
526
+
527
+ .signal-tick {
528
+ stroke: rgba(242, 193, 78, 0.4);
529
  stroke-width: 1;
530
  }
531
 
532
+ .signal-compass-line {
533
+ stroke: rgba(242, 193, 78, 0.9);
534
+ stroke-width: 1;
535
+ stroke-linecap: round;
536
+ }
537
+
538
+ .signal-compass-dot {
539
+ fill: rgba(242, 193, 78, 0.95);
540
+ }
541
+
542
  .signal-star {
543
+ fill: none;
544
+ stroke: var(--totem-gold);
545
+ stroke-width: 0.8;
546
+ filter: drop-shadow(0 0 6px rgba(242, 193, 78, 0.8));
547
  }
548
 
549
  .totem-signal-label {
 
1412
  large = 1 if (end_deg - start_deg) > 180 else 0
1413
  return f"M {x1:.2f} {y1:.2f} A {r} {r} 0 {large} 1 {x2:.2f} {y2:.2f}"
1414
 
1415
+ # Build smooth gradient arc using many small segments
1416
+ # Gradient: gold(0%) -> teal(33%) -> blue(66%) -> purple(100%)
1417
+ gradient_stops = [
1418
+ (0.0, (242, 193, 78)), # gold
1419
+ (0.33, (34, 181, 115)), # green-teal
1420
+ (0.66, (57, 201, 255)), # cyan-blue
1421
+ (1.0, (138, 77, 255)), # purple
1422
+ ]
1423
+
1424
+ def interp_color(t):
1425
+ for i in range(len(gradient_stops) - 1):
1426
+ t0, c0 = gradient_stops[i]
1427
+ t1, c1 = gradient_stops[i+1]
1428
+ if t <= t1:
1429
+ f = (t - t0) / (t1 - t0) if t1 > t0 else 0
1430
+ r2 = int(c0[0] + f * (c1[0] - c0[0]))
1431
+ g2 = int(c0[1] + f * (c1[1] - c0[1]))
1432
+ b2 = int(c0[2] + f * (c1[2] - c0[2]))
1433
+ return f"#{r2:02X}{g2:02X}{b2:02X}"
1434
+ return f"#{gradient_stops[-1][1][0]:02X}{gradient_stops[-1][1][1]:02X}{gradient_stops[-1][1][2]:02X}"
1435
+
1436
+ n_segs = max(1, int(total_deg / 3)) if total_deg > 0 else 0
1437
+ seg_paths = []
1438
+ for i in range(n_segs):
1439
+ t = i / max(n_segs - 1, 1)
1440
+ color = interp_color(t)
1441
+ seg_start_d = -90 + (i / n_segs) * total_deg
1442
+ seg_end_d = -90 + ((i + 1) / n_segs) * total_deg
1443
+ d = arc_path(80, 80, 68, seg_start_d, seg_end_d)
1444
+ seg_paths.append(f'<path d="{d}" fill="none" stroke="{color}" stroke-width="8" stroke-linecap="butt" />')
1445
+
1446
+ # Round end cap on last segment
1447
+ if seg_paths:
1448
+ last_d = arc_path(80, 80, 68, -90 + total_deg - 0.01, -90 + total_deg)
1449
+ color_end = interp_color(1.0 if total_deg >= 360 else total_deg / 360)
1450
+ seg_paths.append(f'<path d="{last_d}" fill="none" stroke="{color_end}" stroke-width="8" stroke-linecap="round" />')
1451
+
1452
+ seg_html = "\n ".join(seg_paths)
1453
+
1454
+ # Tick marks on outer ring (every 30 degrees)
1455
+ ticks = []
1456
+ for deg in range(0, 360, 30):
1457
+ a = math.radians(deg - 90)
1458
+ r_out, r_in = 75, 71
1459
+ x1t = 80 + r_out * math.cos(a)
1460
+ y1t = 80 + r_out * math.sin(a)
1461
+ x2t = 80 + r_in * math.cos(a)
1462
+ y2t = 80 + r_in * math.sin(a)
1463
+ ticks.append(f'<line class="signal-tick" x1="{x1t:.2f}" y1="{y1t:.2f}" x2="{x2t:.2f}" y2="{y2t:.2f}" />')
1464
+ ticks_html = "\n ".join(ticks)
1465
+
1466
+ # 8-pointed compass rose star (thin lines, not filled)
1467
+ # 4 main axes (N/S/E/W) long, 4 diagonal axes shorter
1468
+ compass_lines = []
1469
+ for i, (angle, length) in enumerate([(0,16),(45,9),(90,16),(135,9),(180,16),(225,9),(270,16),(315,9)]):
1470
+ a = math.radians(angle - 90)
1471
+ x_tip = 80 + length * math.cos(a)
1472
+ y_tip = 80 + length * math.sin(a)
1473
+ compass_lines.append(f'<line class="signal-compass-line" x1="80" y1="80" x2="{x_tip:.2f}" y2="{y_tip:.2f}" />')
1474
+ # Small center dot
1475
+ compass_lines.append('<circle class="signal-compass-dot" cx="80" cy="80" r="2" />')
1476
+ compass_html = "\n ".join(compass_lines)
1477
+
1478
  return f"""
1479
  <div class="totem-signal-wrap">
1480
  <svg class="totem-signal" viewBox="0 0 160 160" role="img" aria-label="TOTEM Signal {clamped} percent">
1481
  <circle class="signal-outer-ring" cx="80" cy="80" r="76"></circle>
1482
+ {ticks_html}
1483
  <circle class="signal-track" cx="80" cy="80" r="68"></circle>
1484
+ {seg_html}
1485
+ <circle class="signal-middle-ring" cx="80" cy="80" r="55"></circle>
1486
+ <circle class="signal-inner-ring" cx="80" cy="80" r="42"></circle>
1487
+ {compass_html}
1488
  </svg>
1489
  <div class="totem-signal-label">TOTEM Signal {clamped}%</div>
1490
  </div>