Spaces:
Sleeping
Sleeping
ui: compass rose star + smooth gradient arc + tick marks on signal wheel
Browse files
app.py
CHANGED
|
@@ -493,35 +493,57 @@ TOTEM_CSS = """
|
|
| 493 |
|
| 494 |
.signal-track {
|
| 495 |
fill: none;
|
| 496 |
-
stroke: rgba(246, 241, 232, 0.
|
| 497 |
-
stroke-width:
|
| 498 |
}
|
| 499 |
|
| 500 |
.signal-progress {
|
| 501 |
fill: none;
|
| 502 |
-
stroke:
|
| 503 |
-
stroke-width: 7;
|
| 504 |
stroke-linecap: round;
|
| 505 |
transform: rotate(-90deg);
|
| 506 |
transform-origin: 80px 80px;
|
| 507 |
-
filter: drop-shadow(0 0
|
| 508 |
}
|
| 509 |
|
| 510 |
.signal-outer-ring {
|
| 511 |
fill: none;
|
| 512 |
-
stroke: rgba(242, 193, 78, 0.
|
| 513 |
stroke-width: 1;
|
| 514 |
}
|
| 515 |
|
| 516 |
-
.signal-
|
| 517 |
fill: none;
|
| 518 |
stroke: rgba(57, 201, 255, 0.18);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 519 |
stroke-width: 1;
|
| 520 |
}
|
| 521 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 522 |
.signal-star {
|
| 523 |
-
fill:
|
| 524 |
-
|
|
|
|
|
|
|
| 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 |
-
|
| 1394 |
-
|
| 1395 |
-
|
| 1396 |
-
|
| 1397 |
-
|
| 1398 |
-
|
| 1399 |
-
|
| 1400 |
-
|
| 1401 |
-
|
| 1402 |
-
|
| 1403 |
-
|
| 1404 |
-
|
| 1405 |
-
|
| 1406 |
-
|
| 1407 |
-
|
| 1408 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
-
{
|
| 1415 |
-
<circle class="signal-
|
| 1416 |
-
<circle class="signal-inner-ring" cx="80" cy="80" r="
|
| 1417 |
-
|
| 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>
|