Spaces:
Sleeping
Sleeping
Commit ·
1e4da49
1
Parent(s): 1add89c
UI: style city dropdown to match, condense sliders to one row, live-map teaser loader
Browse files- City <select> now styled like the text inputs (cream, rounded, custom caret)
- Detour + Adventurousness sliders condensed to single compact rows with inline captions
- Replace static 4-step loader with an animated live-map teaser (route threads between popping pins + a walking dot)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- src/discoverroute/ui/shell.py +92 -43
src/discoverroute/ui/shell.py
CHANGED
|
@@ -82,7 +82,18 @@ body{ font-family:'DM Sans',ui-sans-serif,system-ui,sans-serif; color:var(--dr-i
|
|
| 82 |
/* a leading pin/flag glyph for the start & destination fields */
|
| 83 |
.field-wrap{ position:relative; }
|
| 84 |
.field-wrap > input[type=text]{ padding-left:34px; }
|
| 85 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 86 |
.field-wrap::before{ content:attr(data-glyph); position:absolute; left:12px; top:50%;
|
| 87 |
transform:translateY(-50%); font-size:14px; pointer-events:none; z-index:2; line-height:1; }
|
| 88 |
|
|
@@ -128,6 +139,19 @@ body{ font-family:'DM Sans',ui-sans-serif,system-ui,sans-serif; color:var(--dr-i
|
|
| 128 |
.slider-ends{ display:flex; justify-content:space-between; font-size:10.5px; color:var(--dr-soft);
|
| 129 |
margin-top:3px; letter-spacing:.02em; }
|
| 130 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 131 |
details.dr-collapse{ padding:0; margin-bottom:14px; }
|
| 132 |
details.dr-collapse summary{ list-style:none; cursor:pointer; padding:12px 15px;
|
| 133 |
font-family:'Fredoka',sans-serif; font-weight:600; font-size:13px; color:var(--dr-ink);
|
|
@@ -255,6 +279,28 @@ details.dr-collapse .collapse-body{ padding:13px 15px; }
|
|
| 255 |
animation:drPulse 1.1s ease-in-out infinite; margin:0 auto 10px; }
|
| 256 |
@keyframes drPulse{ 0%,100%{ transform:scale(.7); opacity:.5; } 50%{ transform:scale(1.1); opacity:1; } }
|
| 257 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 258 |
/* no-detour sticker slot */
|
| 259 |
#dr-nodetour:empty{ display:none; }
|
| 260 |
#dr-nodetour{ margin-bottom:14px; }
|
|
@@ -391,24 +437,22 @@ def _left_panel() -> str:
|
|
| 391 |
</div>
|
| 392 |
</div>
|
| 393 |
|
| 394 |
-
<div class="dr-control">
|
| 395 |
-
<label class="dr-label" for="dr-budget-i">Detour
|
| 396 |
-
<
|
| 397 |
-
|
| 398 |
-
|
| 399 |
-
|
| 400 |
-
</
|
| 401 |
-
<div class="slider-ends"><span>straight there</span><span>up to 2× longer</span></div>
|
| 402 |
</div>
|
| 403 |
|
| 404 |
-
<div class="dr-control">
|
| 405 |
-
<label class="dr-label" for="dr-adv-i">
|
| 406 |
-
<
|
| 407 |
-
|
| 408 |
-
|
| 409 |
-
|
| 410 |
-
</
|
| 411 |
-
<div class="slider-ends"><span>well-known</span><span>hidden gems</span></div>
|
| 412 |
</div>
|
| 413 |
|
| 414 |
<details class="dr-collapse">
|
|
@@ -416,21 +460,17 @@ def _left_panel() -> str:
|
|
| 416 |
<div class="collapse-body">
|
| 417 |
<div class="dr-help" style="margin:0 0 10px;">Only used when Vibe and your saved
|
| 418 |
profile are both empty.</div>
|
| 419 |
-
<div class="dr-control">
|
| 420 |
-
<label class="dr-label" for="dr-green-i">
|
| 421 |
-
<
|
| 422 |
-
|
| 423 |
-
|
| 424 |
-
<output id="dr-green-o">0.5</output>
|
| 425 |
-
</div>
|
| 426 |
</div>
|
| 427 |
-
<div class="dr-control" style="margin-bottom:0;">
|
| 428 |
-
<label class="dr-label" for="dr-quiet-i">
|
| 429 |
-
<
|
| 430 |
-
|
| 431 |
-
|
| 432 |
-
<output id="dr-quiet-o">0.5</output>
|
| 433 |
-
</div>
|
| 434 |
</div>
|
| 435 |
</div>
|
| 436 |
</details>
|
|
@@ -513,25 +553,34 @@ def index_html() -> str:
|
|
| 513 |
</html>"""
|
| 514 |
|
| 515 |
|
|
|
|
|
|
|
|
|
|
| 516 |
def _loading_inner() -> str:
|
| 517 |
-
"""
|
| 518 |
-
|
| 519 |
-
pips = []
|
| 520 |
-
for i, label in enumerate(steps):
|
| 521 |
-
if i:
|
| 522 |
-
pips.append('<div class="bar" data-bar="%d"></div>' % i)
|
| 523 |
-
pips.append(
|
| 524 |
-
f'<div class="step" data-step="{i}"><div class="pip"></div>'
|
| 525 |
-
f'<div class="lbl">{label}</div></div>'
|
| 526 |
-
)
|
| 527 |
-
stepper = '<div class="stepper">' + "".join(pips) + "</div>"
|
| 528 |
return f"""
|
| 529 |
<div style="text-align:center; max-width:540px; padding:0 16px;">
|
| 530 |
<div style="font-family:'Fredoka',system-ui,sans-serif;font-weight:600;font-size:21px;
|
| 531 |
color:#2B2620;">Scouting your wander…</div>
|
| 532 |
<div style="font-family:'DM Sans',system-ui,sans-serif;font-size:13px;color:#6B6256;
|
| 533 |
margin-top:6px;">reading your vibe · scoring 30,000 places · threading the detour</div>
|
| 534 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 535 |
</div>
|
| 536 |
"""
|
| 537 |
|
|
|
|
| 82 |
/* a leading pin/flag glyph for the start & destination fields */
|
| 83 |
.field-wrap{ position:relative; }
|
| 84 |
.field-wrap > input[type=text]{ padding-left:34px; }
|
| 85 |
+
/* city picker <select> — styled to match the text inputs (no native chrome) */
|
| 86 |
+
.field-wrap > select.dr-field{
|
| 87 |
+
width:100%; padding:11px 38px 11px 34px; font-size:14px;
|
| 88 |
+
font-family:'DM Sans',ui-sans-serif,system-ui,sans-serif; color:var(--dr-ink);
|
| 89 |
+
border:2px solid var(--dr-line); border-radius:var(--dr-r); background-color:#FFFEFB;
|
| 90 |
+
background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath d='M2.5 4.5l3.5 3.5 3.5-3.5' fill='none' stroke='%236B6256' stroke-width='1.7' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
|
| 91 |
+
background-repeat:no-repeat; background-position:right 14px center; background-size:12px;
|
| 92 |
+
-webkit-appearance:none; -moz-appearance:none; appearance:none; cursor:pointer;
|
| 93 |
+
transition:border-color .18s, box-shadow .18s; }
|
| 94 |
+
.field-wrap > select.dr-field:hover{ border-color:#D8C9A8; }
|
| 95 |
+
.field-wrap > select.dr-field:focus{ outline:none; border-color:var(--dr-cobalt);
|
| 96 |
+
box-shadow:0 0 0 4px rgba(47,93,244,.14); }
|
| 97 |
.field-wrap::before{ content:attr(data-glyph); position:absolute; left:12px; top:50%;
|
| 98 |
transform:translateY(-50%); font-size:14px; pointer-events:none; z-index:2; line-height:1; }
|
| 99 |
|
|
|
|
| 139 |
.slider-ends{ display:flex; justify-content:space-between; font-size:10.5px; color:var(--dr-soft);
|
| 140 |
margin-top:3px; letter-spacing:.02em; }
|
| 141 |
|
| 142 |
+
/* condensed single-row sliders: label · min-cap · slider · max-cap · value */
|
| 143 |
+
.dr-slider-1{ display:flex; align-items:center; gap:8px; }
|
| 144 |
+
.dr-slider-1 > .dr-label{ margin:0; flex:0 0 auto; width:58px; font-size:12.5px; line-height:1.1; }
|
| 145 |
+
.dr-slider-1 .cap{ flex:0 0 auto; font-size:10px; color:var(--dr-soft); white-space:nowrap; }
|
| 146 |
+
.dr-slider-1 .srng{ flex:1 1 auto; min-width:40px; display:flex; }
|
| 147 |
+
.dr-slider-1 .srng input[type=range]{ width:100%; }
|
| 148 |
+
.dr-slider-1 > .val{ flex:0 0 auto; min-width:30px; text-align:right;
|
| 149 |
+
font-family:'JetBrains Mono',monospace; font-size:11.5px; color:var(--dr-soft);
|
| 150 |
+
padding:2px 6px; background:#FFFDF8; border:1px solid var(--dr-line); border-radius:9px; }
|
| 151 |
+
/* keep the grass-accented thumb on the optional green/quiet sliders */
|
| 152 |
+
#dr-green input[type=range]::-webkit-slider-thumb,
|
| 153 |
+
#dr-quiet input[type=range]::-webkit-slider-thumb{ border-color:var(--dr-grass); }
|
| 154 |
+
|
| 155 |
details.dr-collapse{ padding:0; margin-bottom:14px; }
|
| 156 |
details.dr-collapse summary{ list-style:none; cursor:pointer; padding:12px 15px;
|
| 157 |
font-family:'Fredoka',sans-serif; font-weight:600; font-size:13px; color:var(--dr-ink);
|
|
|
|
| 279 |
animation:drPulse 1.1s ease-in-out infinite; margin:0 auto 10px; }
|
| 280 |
@keyframes drPulse{ 0%,100%{ transform:scale(.7); opacity:.5; } 50%{ transform:scale(1.1); opacity:1; } }
|
| 281 |
|
| 282 |
+
/* live-map teaser loader — a route threads itself between popping pins while we plan */
|
| 283 |
+
.teaser-map{ width:288px; max-width:80vw; margin:20px auto 0; }
|
| 284 |
+
.teaser-map svg{ width:100%; height:auto; display:block;
|
| 285 |
+
filter:drop-shadow(0 14px 30px rgba(43,38,32,.16)); }
|
| 286 |
+
.route-line{ stroke-dasharray:360; stroke-dashoffset:360; animation:drDraw 3.4s ease-in-out infinite; }
|
| 287 |
+
@keyframes drDraw{ 0%{ stroke-dashoffset:360; } 55%{ stroke-dashoffset:0; }
|
| 288 |
+
90%{ stroke-dashoffset:0; } 100%{ stroke-dashoffset:360; } }
|
| 289 |
+
.teaser-map .pin{ opacity:0; transform-box:fill-box; transform-origin:center;
|
| 290 |
+
animation:drPinPop 3.4s var(--dr-spring) infinite; }
|
| 291 |
+
.teaser-map .p-start{ animation-delay:.15s; } .teaser-map .p1{ animation-delay:1s; }
|
| 292 |
+
.teaser-map .p2{ animation-delay:1.7s; } .teaser-map .p-end{ animation-delay:2.3s; }
|
| 293 |
+
@keyframes drPinPop{ 0%{ opacity:0; transform:scale(.2); } 9%{ opacity:1; transform:scale(1.25); }
|
| 294 |
+
16%{ transform:scale(1); } 86%{ opacity:1; } 100%{ opacity:0; transform:scale(1); } }
|
| 295 |
+
.teaser-map .walker{ offset-rotate:0deg; animation:drWalk 3.4s ease-in-out infinite;
|
| 296 |
+
offset-path:path("M26,120 C66,112 70,66 116,70 C150,73 168,44 206,58 C236,69 246,40 262,34"); }
|
| 297 |
+
@keyframes drWalk{ 0%{ offset-distance:0%; opacity:0; } 8%{ opacity:1; }
|
| 298 |
+
55%{ offset-distance:100%; opacity:1; } 64%{ opacity:0; } 100%{ offset-distance:100%; opacity:0; } }
|
| 299 |
+
@media (prefers-reduced-motion: reduce){
|
| 300 |
+
.route-line{ animation:none; stroke-dashoffset:0; }
|
| 301 |
+
.teaser-map .pin{ animation:none; opacity:1; }
|
| 302 |
+
.teaser-map .walker{ animation:none; opacity:0; } }
|
| 303 |
+
|
| 304 |
/* no-detour sticker slot */
|
| 305 |
#dr-nodetour:empty{ display:none; }
|
| 306 |
#dr-nodetour{ margin-bottom:14px; }
|
|
|
|
| 437 |
</div>
|
| 438 |
</div>
|
| 439 |
|
| 440 |
+
<div class="dr-control dr-slider-1">
|
| 441 |
+
<label class="dr-label" for="dr-budget-i">Detour</label>
|
| 442 |
+
<span class="cap">direct</span>
|
| 443 |
+
<span id="dr-budget" class="srng"><input type="range" id="dr-budget-i" min="0" max="2"
|
| 444 |
+
step="0.1" value="0.5" aria-label="Detour budget"></span>
|
| 445 |
+
<span class="cap">2× longer</span>
|
| 446 |
+
<output id="dr-budget-o" class="val">0.5</output>
|
|
|
|
| 447 |
</div>
|
| 448 |
|
| 449 |
+
<div class="dr-control dr-slider-1">
|
| 450 |
+
<label class="dr-label" for="dr-adv-i">Adventure</label>
|
| 451 |
+
<span class="cap">classic</span>
|
| 452 |
+
<span id="dr-adv" class="srng"><input type="range" id="dr-adv-i" min="0" max="1"
|
| 453 |
+
step="0.05" value="0.3" aria-label="Adventurousness"></span>
|
| 454 |
+
<span class="cap">hidden gems</span>
|
| 455 |
+
<output id="dr-adv-o" class="val">0.3</output>
|
|
|
|
| 456 |
</div>
|
| 457 |
|
| 458 |
<details class="dr-collapse">
|
|
|
|
| 460 |
<div class="collapse-body">
|
| 461 |
<div class="dr-help" style="margin:0 0 10px;">Only used when Vibe and your saved
|
| 462 |
profile are both empty.</div>
|
| 463 |
+
<div class="dr-control dr-slider-1">
|
| 464 |
+
<label class="dr-label" for="dr-green-i">Green</label>
|
| 465 |
+
<span id="dr-green" class="srng green"><input type="range" id="dr-green-i" min="0"
|
| 466 |
+
max="1" step="0.05" value="0.5" aria-label="Prefer green"></span>
|
| 467 |
+
<output id="dr-green-o" class="val">0.5</output>
|
|
|
|
|
|
|
| 468 |
</div>
|
| 469 |
+
<div class="dr-control dr-slider-1" style="margin-bottom:0;">
|
| 470 |
+
<label class="dr-label" for="dr-quiet-i">Quiet</label>
|
| 471 |
+
<span id="dr-quiet" class="srng green"><input type="range" id="dr-quiet-i" min="0"
|
| 472 |
+
max="1" step="0.05" value="0.5" aria-label="Prefer quiet"></span>
|
| 473 |
+
<output id="dr-quiet-o" class="val">0.5</output>
|
|
|
|
|
|
|
| 474 |
</div>
|
| 475 |
</div>
|
| 476 |
</details>
|
|
|
|
| 553 |
</html>"""
|
| 554 |
|
| 555 |
|
| 556 |
+
_TEASER_PATH = "M26,120 C66,112 70,66 116,70 C150,73 168,44 206,58 C236,69 246,40 262,34"
|
| 557 |
+
|
| 558 |
+
|
| 559 |
def _loading_inner() -> str:
|
| 560 |
+
"""Live-map teaser: a little route threads itself between popping pins while
|
| 561 |
+
we plan — a playful preview of the real map so the wait feels like progress."""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 562 |
return f"""
|
| 563 |
<div style="text-align:center; max-width:540px; padding:0 16px;">
|
| 564 |
<div style="font-family:'Fredoka',system-ui,sans-serif;font-weight:600;font-size:21px;
|
| 565 |
color:#2B2620;">Scouting your wander…</div>
|
| 566 |
<div style="font-family:'DM Sans',system-ui,sans-serif;font-size:13px;color:#6B6256;
|
| 567 |
margin-top:6px;">reading your vibe · scoring 30,000 places · threading the detour</div>
|
| 568 |
+
<div class="teaser-map" aria-hidden="true">
|
| 569 |
+
<svg viewBox="0 0 288 150" role="img">
|
| 570 |
+
<rect x="0" y="0" width="288" height="150" rx="18" fill="#FFFCF5" stroke="#E7DAC0"/>
|
| 571 |
+
<g stroke="#EFE3C8" stroke-width="2" stroke-linecap="round">
|
| 572 |
+
<line x1="20" y1="40" x2="268" y2="34"/><line x1="16" y1="92" x2="272" y2="100"/>
|
| 573 |
+
<line x1="70" y1="16" x2="84" y2="138"/><line x1="178" y1="14" x2="196" y2="140"/>
|
| 574 |
+
</g>
|
| 575 |
+
<path class="route-line" d="{_TEASER_PATH}" fill="none" stroke="var(--dr-cobalt)"
|
| 576 |
+
stroke-width="3.5" stroke-linecap="round" stroke-linejoin="round"/>
|
| 577 |
+
<circle class="pin p-start" cx="26" cy="120" r="6.5" fill="var(--dr-grass)" stroke="#fff" stroke-width="2"/>
|
| 578 |
+
<circle class="pin p1" cx="116" cy="70" r="5.5" fill="var(--dr-coral)" stroke="#fff" stroke-width="2"/>
|
| 579 |
+
<circle class="pin p2" cx="206" cy="58" r="5.5" fill="var(--dr-coral)" stroke="#fff" stroke-width="2"/>
|
| 580 |
+
<circle class="pin p-end" cx="262" cy="34" r="6.5" fill="var(--dr-cobalt)" stroke="#fff" stroke-width="2"/>
|
| 581 |
+
<circle class="walker" r="4.5" fill="#fff" stroke="var(--dr-cobalt)" stroke-width="3"/>
|
| 582 |
+
</svg>
|
| 583 |
+
</div>
|
| 584 |
</div>
|
| 585 |
"""
|
| 586 |
|