coreprinciple Claude Opus 4.8 commited on
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>

Files changed (1) hide show
  1. 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
- .field-wrap > select.dr-field{ padding-left:34px; cursor:pointer; }
 
 
 
 
 
 
 
 
 
 
 
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 budget</label>
396
- <div id="dr-budget" class="dr-slider">
397
- <input type="range" id="dr-budget-i" min="0" max="2" step="0.1" value="0.5"
398
- aria-label="Detour budget">
399
- <output id="dr-budget-o">0.5</output>
400
- </div>
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">Adventurousness</label>
406
- <div id="dr-adv" class="dr-slider">
407
- <input type="range" id="dr-adv-i" min="0" max="1" step="0.05" value="0.3"
408
- aria-label="Adventurousness">
409
- <output id="dr-adv-o">0.3</output>
410
- </div>
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">Prefer green</label>
421
- <div id="dr-green" class="dr-slider green">
422
- <input type="range" id="dr-green-i" min="0" max="1" step="0.05" value="0.5"
423
- aria-label="Prefer green">
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">Prefer quiet</label>
429
- <div id="dr-quiet" class="dr-slider green">
430
- <input type="range" id="dr-quiet-i" min="0" max="1" step="0.05" value="0.5"
431
- aria-label="Prefer quiet">
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
- """Stride mascot (reused) + the 4-step stepper."""
518
- steps = ["Interpreting vibe", "Scoring places", "Solving route", "Writing itinerary"]
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
- {stepper}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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