Spaces:
Running
Running
Download trips.html from Wayed11/wheelie-app: direct link, hf CLI and curl.
- Browser
- Download file 80.1 kB
-
https://huggingface.co/spaces/Wayed11/wheelie-app/resolve/main/trips.html
- Command line
-
hf download hf://spaces/Wayed11/wheelie-app/trips.html
-
curl -L -o trips.html https://huggingface.co/spaces/Wayed11/wheelie-app/resolve/main/trips.html
80.1 kB
| <html lang="en"> | |
| <head><script>window.huggingface={variables:{"SPACE_CREATOR_USER_ID":"6ac2ff4d9f39a66f2405ee29"}};</script><script>window.huggingface={variables:{"SPACE_CREATOR_USER_ID":"6ac2ff4d9f39a66f2405ee29"}};</script><script>window.huggingface={variables:{"SPACE_CREATOR_USER_ID":"6ac2ff4d9f39a66f2405ee29"}};</script> | |
| <meta charset="utf-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover, maximum-scale=1"> | |
| <title>Trips with Wheelie</title> | |
| <meta name="theme-color" content="#000000"> | |
| <meta name="apple-mobile-web-app-capable" content="yes"> | |
| <link rel="apple-touch-icon" href="wheelie-icon.png?v=2"> | |
| <link rel="icon" type="image/png" href="wheelie-icon.png?v=2"> | |
| <link rel="stylesheet" href="https://unpkg.com/maplibre-gl@4.7.1/dist/maplibre-gl.css"> | |
| <style> | |
| :root{--pink:#FF6FB5;--blue:#5BC0F8;--bg:#000;--card:rgba(18,18,24,.92);--line:rgba(255,255,255,.12);--fg:#fff;--dim:#a1a1aa;--ok:#1fd1a0;--warn:#ff5a5a;color-scheme:dark} | |
| *{box-sizing:border-box;-webkit-tap-highlight-color:transparent} | |
| html,body{margin:0;height:100%;background:var(--bg);color:var(--fg);font:16px/1.4 -apple-system,BlinkMacSystemFont,"SF Pro Text",system-ui,sans-serif;overflow:hidden} | |
| button,input{font:inherit;color:inherit} | |
| button{cursor:pointer} | |
| #map{position:fixed;inset:0;background:#07070b} | |
| .maplibregl-ctrl-attrib{font-size:10px;background:rgba(0,0,0,.55);color:#888} | |
| .maplibregl-ctrl-attrib a{color:#aaa} | |
| .split{background:conic-gradient(var(--pink) 0 50%,var(--blue) 50% 100%)} | |
| /* top bar */ | |
| #top{position:fixed;left:0;right:0;top:0;z-index:20;padding:calc(env(safe-area-inset-top) + 10px) 14px 0;display:flex;align-items:center;gap:10px;pointer-events:none} | |
| #top>*{pointer-events:auto} | |
| .pill{height:44px;border-radius:999px;border:1px solid var(--line);background:var(--card);display:flex;align-items:center;gap:8px;padding:0 16px;font-weight:700;font-size:15px} | |
| #back{width:44px;padding:0;justify-content:center;font-size:22px} | |
| #ttl{flex:1;justify-content:center;pointer-events:none} | |
| #ttl i{width:10px;height:10px;border-radius:50%} | |
| #sosBtn{color:#fff;border-color:rgba(255,90,90,.6);background:rgba(60,10,14,.92)} | |
| /* plan card */ | |
| #plan{position:fixed;left:12px;right:12px;top:calc(env(safe-area-inset-top) + 64px);z-index:15;background:var(--card);border:1px solid var(--line);border-radius:26px;padding:14px;box-shadow:0 14px 40px rgba(0,0,0,.6);transition:transform .35s cubic-bezier(.2,1,.3,1),opacity .3s} | |
| #plan.min{padding:8px}#plan.min .hello,#plan.min #fromRow,#plan.min .chips,#plan.min #when{display:none}#plan.min .fld,#plan.min .mic{height:50px}#plan.min .mic{flex-basis:50px} | |
| .hello{display:flex;align-items:center;gap:12px;margin-bottom:12px} | |
| .av{flex:0 0 48px;height:48px;border-radius:50%;padding:3px} | |
| .av img{width:100%;height:100%;border-radius:50%;object-fit:cover;display:block;border:2px solid #000} | |
| .hello b{display:block;font-size:19px;font-weight:800;letter-spacing:-.2px} | |
| .hello small{display:block;color:var(--dim);font-size:14px} | |
| .av.talk{animation:talk .6s ease-in-out infinite} | |
| @keyframes talk{50%{transform:scale(1.08)}} | |
| .row{display:flex;gap:8px;align-items:center} | |
| .fld{flex:1;min-width:0;height:54px;border-radius:16px;border:1px solid rgba(255,255,255,.16);background:#0b0b10;display:flex;align-items:center;padding:0 6px 0 14px;gap:8px} | |
| .fld span{font-size:13px;font-weight:700;color:var(--dim);flex:0 0 38px} | |
| .fld input{flex:1;min-width:0;border:0;background:none;outline:none;font-size:17px;height:100%} | |
| .fld input::placeholder{color:#6b6b75} | |
| #fromRow{margin:0 62px 8px 0} | |
| #fromRow .fld{height:46px} | |
| #fromRow input{font-size:15px} | |
| .mic{flex:0 0 54px;height:54px;border-radius:50%;border:3px solid #fff;display:flex;align-items:center;justify-content:center;box-shadow:0 0 18px rgba(255,111,181,.45)} | |
| .mic.on{animation:talk .7s ease-in-out infinite} | |
| .mic svg{width:24px;height:24px} | |
| #sugg{margin-top:8px;max-height:40vh;overflow-y:auto;border-radius:16px} | |
| #sugg button{width:100%;text-align:left;border:0;background:none;padding:12px 10px;border-bottom:1px solid var(--line);display:flex;gap:12px;align-items:center;min-height:56px} | |
| #sugg button:last-child{border-bottom:0} | |
| #sugg em{font-style:normal;font-size:22px;flex:0 0 28px;text-align:center} | |
| #sugg b{display:block;font-size:16px} | |
| #sugg small{display:block;color:var(--dim);font-size:13px} | |
| .chips{display:flex;gap:8px;overflow-x:auto;margin-top:10px;scrollbar-width:none} | |
| .chips::-webkit-scrollbar{display:none} | |
| .chip{flex:0 0 auto;height:40px;border-radius:999px;border:1px solid var(--line);background:#121218;padding:0 14px;font-size:14px;font-weight:600;color:#ddd} | |
| .chip.on{border-color:var(--pink);color:#fff;box-shadow:inset 0 0 0 1px var(--pink)} | |
| #when{display:none;margin-top:8px} | |
| #when input{width:100%;height:46px;border-radius:14px;border:1px solid var(--line);background:#0b0b10;padding:0 12px;font-size:16px} | |
| /* sheet */ | |
| #sheet{position:fixed;left:0;right:0;bottom:0;z-index:16;background:var(--card);border-top:1px solid var(--line);border-radius:26px 26px 0 0;box-shadow:0 -14px 40px rgba(0,0,0,.6);max-height:58vh;display:flex;flex-direction:column;transform:translateY(105%);transition:transform .4s cubic-bezier(.2,1,.3,1);padding-bottom:env(safe-area-inset-bottom)} | |
| #sheet.open{transform:none} | |
| #sheet.open{max-height:72vh} | |
| #sheet.small{transform:translateY(calc(100% - 132px - env(safe-area-inset-bottom)))} | |
| .handle{border:0;background:none;padding:10px 0 4px;width:100%} | |
| .handle i{display:block;width:44px;height:5px;border-radius:5px;background:#3a3a44;margin:0 auto} | |
| .sb{overflow-y:auto;padding:0 14px 16px;-webkit-overflow-scrolling:touch} | |
| #say{display:flex;gap:10px;align-items:flex-start;margin:4px 0 12px} | |
| #say .av{flex-basis:40px;height:40px} | |
| #say p{margin:0;background:#16161d;border:1px solid var(--line);border-radius:4px 18px 18px 18px;padding:10px 12px;font-size:15px;line-height:1.35} | |
| .opts{display:flex;gap:10px;overflow-x:auto;margin:0 -14px;padding:2px 14px 6px;scrollbar-width:none} | |
| .opts::-webkit-scrollbar{display:none} | |
| .opt{flex:0 0 230px;text-align:left;border:1px solid var(--line);background:#101016;border-radius:20px;padding:12px;min-height:118px} | |
| .opt.on{border-color:transparent;box-shadow:0 0 0 2px var(--pink),0 0 0 4px var(--blue)} | |
| .opt .big{font-size:22px;font-weight:800;letter-spacing:-.3px} | |
| .opt .tm{color:#ddd;font-size:14px;margin:2px 0 8px;font-variant-numeric:tabular-nums} | |
| .opt .bd{display:flex;flex-wrap:wrap;gap:5px;align-items:center} | |
| .bdg{display:inline-flex;align-items:center;gap:4px;height:26px;border-radius:8px;padding:0 8px;font-size:13px;font-weight:800;color:#000;background:var(--blue)} | |
| .bdg.w{background:var(--pink);color:#000} | |
| .opt .ft{color:var(--dim);font-size:13px;margin-top:8px} | |
| .alt{flex:0 0 200px;background:#101016} | |
| .alt .big{font-size:18px} | |
| .acts{display:grid;grid-template-columns:1fr 1fr;gap:10px;margin:14px 0 6px} | |
| .act{height:56px;border-radius:18px;border:0;font-weight:800;font-size:16px;display:flex;align-items:center;justify-content:center;gap:8px} | |
| .act.p{background:var(--pink);color:#000} | |
| .act.b{background:var(--blue);color:#000} | |
| h3{font-size:13px;letter-spacing:.8px;text-transform:uppercase;color:var(--dim);margin:16px 2px 8px} | |
| .steps{list-style:none;margin:0;padding:0;position:relative} | |
| .st{display:flex;gap:12px;padding:10px 4px;border:0;background:none;width:100%;text-align:left;border-radius:14px} | |
| .st:active{background:#17171f} | |
| .st .ic{flex:0 0 40px;height:40px;border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:19px;border:2px solid var(--pink);background:#000} | |
| .st.r .ic{border-color:var(--blue)} | |
| .st b{display:block;font-size:16px} | |
| .st small{display:block;color:var(--dim);font-size:14px;margin-top:2px} | |
| .st .t{margin-left:auto;font-size:14px;color:#ddd;font-variant-numeric:tabular-nums;white-space:nowrap;padding-left:6px} | |
| .note{font-size:14px;color:#ddd;background:#101016;border:1px solid var(--line);border-radius:16px;padding:12px;margin-top:12px} | |
| .note b{color:#fff} | |
| .err{color:#ffb3b3} | |
| .loading{display:flex;align-items:center;gap:10px;color:#ddd;padding:14px 4px} | |
| .spin{width:22px;height:22px;border-radius:50%;border:3px solid #333;border-top-color:var(--pink);border-right-color:var(--blue);animation:sp 0.9s linear infinite} | |
| @keyframes sp{to{transform:rotate(360deg)}} | |
| /* markers */ | |
| .wm{width:50px;height:50px;border-radius:50%;padding:3px;box-shadow:0 0 22px rgba(255,111,181,.55),0 0 22px rgba(91,192,248,.35)} | |
| .wm img{width:100%;height:100%;border-radius:50%;object-fit:cover;border:2px solid #000;display:block} | |
| .dm{width:34px;height:34px;border-radius:50%;background:var(--pink);border:4px solid #fff;box-shadow:0 0 18px rgba(255,111,181,.8);display:flex;align-items:center;justify-content:center;font-size:15px} | |
| .me{width:20px;height:20px;border-radius:50%;background:var(--blue);border:4px solid #fff;box-shadow:0 0 0 8px rgba(91,192,248,.25)} | |
| /* sos */ | |
| #sos{position:fixed;inset:0;z-index:40;background:rgba(0,0,0,.6);display:none;align-items:flex-end} | |
| #sos.on{display:flex} | |
| #sos .box{width:100%;background:#111116;border-radius:26px 26px 0 0;padding:18px 16px calc(18px + env(safe-area-inset-bottom));border-top:1px solid var(--line)} | |
| #sos h2{margin:0 0 4px;font-size:21px} | |
| #sos p{margin:0 0 14px;color:var(--dim)} | |
| .sb2{display:flex;align-items:center;gap:12px;width:100%;min-height:62px;border-radius:18px;border:1px solid var(--line);background:#18181f;margin-bottom:10px;padding:0 16px;font-weight:700;font-size:17px;text-decoration:none;color:#fff} | |
| .sb2.r{background:#c62828;border-color:#c62828} | |
| .sb2 span{font-size:24px} | |
| @keyframes micP{0%{box-shadow:0 0 0 0 rgba(255,111,181,.9)}70%{box-shadow:0 0 0 20px rgba(255,111,181,0)}100%{box-shadow:0 0 0 0 rgba(255,111,181,0)}} | |
| #mic.hint{animation:micP 1.3s ease-out infinite} | |
| #mic,#micTip,.tips,#readAll{display:none} | |
| #fromRow{margin-right:0} | |
| #micTip{position:absolute;right:-4px;top:calc(100% + 8px);z-index:5;background:#FF6FB5;color:#000;font-weight:900;font-size:15px;padding:8px 12px;border-radius:14px;white-space:nowrap;box-shadow:0 8px 20px rgba(0,0,0,.5);animation:tipB 1.3s ease-in-out infinite;pointer-events:none} | |
| #micTip:before{content:"";position:absolute;right:22px;top:-7px;border:7px solid transparent;border-top:0;border-bottom-color:#FF6FB5} | |
| @keyframes tipB{50%{transform:translateY(-4px)}} | |
| .tips{margin:0 0 12px}.tips b{display:block;color:#fff;font-size:17px}.tips span{display:block;color:#aaa;font-size:14px;margin:2px 0 8px}.tips div{display:flex;gap:8px;overflow-x:auto;-webkit-overflow-scrolling:touch;padding-bottom:4px}.tips button{flex:none;border:1px solid rgba(255,255,255,.18);background:#1c1c22;color:#fff;border-radius:999px;padding:11px 15px;font-size:15px;font-weight:600;white-space:nowrap}.tips button:active{background:#FF6FB5;color:#000}#plan.min .tips{display:none} | |
| #tl{position:fixed;inset:0;z-index:40;background:#000;overflow-y:auto;-webkit-overflow-scrolling:touch;padding:calc(env(safe-area-inset-top) + 16px) 16px calc(env(safe-area-inset-bottom) + 24px);transition:opacity .35s,transform .35s}#tl.off{opacity:0;transform:scale(1.04);pointer-events:none} | |
| .tlh{display:flex;align-items:center;gap:12px;margin:0 0 18px}.tlh img{width:58px;height:58px;border-radius:50%;object-fit:cover;border:3px solid #FF6FB5;box-shadow:0 0 0 3px #5BC0F8}.tlh b{display:block;font-size:30px;font-weight:900;color:#fff;letter-spacing:-.5px}.tlh span{color:#bbb;font-size:17px} | |
| .tlb{width:44px;height:44px;border-radius:50%;background:#1c1c22;color:#fff;display:grid;place-items:center;font-size:30px;text-decoration:none;flex:none} | |
| .tc{position:relative;display:block;width:100%;text-align:left;border:0;border-radius:28px;margin:0 0 14px;padding:26px 22px 22px;min-height:178px;background:#0d0d12;color:#fff;overflow:hidden;font:inherit;box-shadow:0 18px 40px rgba(0,0,0,.7),inset 0 0 0 1px rgba(255,255,255,.08);opacity:0;transform:translateY(26px);animation:tcIn .7s cubic-bezier(.2,.8,.2,1) var(--d) forwards} | |
| .tc:active{transform:scale(.97)} | |
| .tc .sp{position:absolute;left:0;right:0;top:0;height:6px;background:linear-gradient(90deg,#FF6FB5 50%,#5BC0F8 50%)} | |
| .tc .tr{position:absolute;right:-10px;bottom:6px;width:78%;height:90px;opacity:.55}.tc .tr path{fill:none;stroke:#FF6FB5;stroke-width:5;stroke-linecap:round;stroke-dasharray:420;stroke-dashoffset:420;animation:trD 2.4s ease-out calc(var(--d) + .4s) forwards;filter:drop-shadow(0 0 6px #FF6FB5)} | |
| .tc em{font-style:normal;font-size:46px;display:block;margin-bottom:10px;filter:drop-shadow(0 6px 14px rgba(0,0,0,.6))} | |
| .tc b{display:block;font-size:28px;font-weight:900;letter-spacing:-.4px;margin-bottom:6px;position:relative}.tc span{display:block;font-size:17px;line-height:1.35;color:#c9c9d1;max-width:92%;position:relative} | |
| .tc.sos{min-height:150px;background:#1a0710}.tc.sos .sp{background:linear-gradient(90deg,#FF3B5C 50%,#fff 50%)} | |
| #ecBox{margin:14px 0 4px}#ecBox h3{font-size:19px;margin:0 0 4px;color:#fff}#ecBox p.x{margin:0 0 10px;color:#bbb;font-size:15px} | |
| .ec{display:flex;align-items:center;gap:10px;background:#16161c;border-radius:18px;padding:12px 14px;margin-bottom:8px}.ec b{flex:1;font-size:18px;color:#fff}.ec b small{display:block;color:#999;font-size:14px;font-weight:500} | |
| .ec a,.ec button{width:46px;height:46px;border-radius:50%;border:0;display:grid;place-items:center;font-size:21px;text-decoration:none;background:#5BC0F8;color:#000}.ec a.t{background:#FF6FB5}.ec button{background:#2a2a32;color:#fff;font-size:17px} | |
| .ecAdd{width:100%;height:54px;border-radius:18px;border:2px dashed #555;background:none;color:#fff;font-size:17px;font-weight:700;margin-bottom:8px} | |
| .ecF{display:none;background:#16161c;border-radius:18px;padding:12px;margin-bottom:8px}.ecF.on{display:block}.ecF input{width:100%;height:50px;border-radius:14px;border:0;background:#24242c;color:#fff;font-size:18px;padding:0 14px;margin-bottom:8px;box-sizing:border-box} | |
| .bkBtn{width:100%;min-height:62px;border-radius:20px;border:0;background:#FF6FB5;color:#000;font-size:19px;font-weight:900;margin:6px 0 10px} | |
| #bk{display:none;margin-bottom:10px}#bk.on{display:block}#bk a{display:flex;align-items:center;gap:12px;background:#16161c;border-radius:18px;padding:14px;margin-bottom:8px;color:#fff;text-decoration:none;font-size:18px;font-weight:700}#bk a small{display:block;color:#aaa;font-weight:500;font-size:14px}#bk a em{font-style:normal;font-size:26px} | |
| @keyframes tcIn{to{opacity:1;transform:none}}@keyframes trD{to{stroke-dashoffset:0}} | |
| #nav{position:fixed;left:12px;right:12px;bottom:calc(env(safe-area-inset-bottom) + 12px);z-index:18;background:var(--card);border:1px solid var(--line);border-radius:24px;padding:16px;display:none;box-shadow:0 -10px 40px rgba(0,0,0,.6)}#nav.on{display:block}#nav p{margin:0 0 4px;font-size:21px;font-weight:800;line-height:1.25}#nav small{display:block;color:var(--dim);font-size:15px;margin-bottom:12px}.navb{display:flex;gap:10px}.navb .act{flex:1} | |
| @media (prefers-reduced-motion:reduce){*{animation:none;transition:none}} | |
| </style> | |
| <meta property="og:title" content="Wheelie"><meta property="og:image" content="https://cdn-thumbnails.huggingface.co/social-thumbnails/spaces/Wayed11/wheelie-app.png"><meta name="twitter:card" content="summary_large_image"><meta name="twitter:image" content="https://cdn-thumbnails.huggingface.co/social-thumbnails/spaces/Wayed11/wheelie-app.png"></head> | |
| <body> | |
| <div id="map" role="application" aria-label="Trip map"></div> | |
| <div id="top"> | |
| <a class="pill" id="back" href="index.html" aria-label="Back to Wheelie">βΉ</a> | |
| <div class="pill" id="ttl"><i class="split"></i>Trips with Wheelie</div> | |
| <button class="pill" id="sosBtn" aria-label="Help and emergency">SOS</button> | |
| </div> | |
| <section id="plan" aria-label="Plan a trip"> | |
| <div class="hello"> | |
| <div class="av split" id="hAv"><img src="wheelie.jpg" alt=""></div> | |
| <div><b id="hi">Where are we going today?</b><small id="hiSub">Tell me or type it. I'll find the best accessible way.</small></div> | |
| </div> | |
| <div class="tips" id="tips"><b>π¬ Try saying</b><span>Tap the pink mic ποΈ, then talk to Wheelie like a friend. Or tap one of these.</span><div> | |
| <button data-s="Take me home">π βTake me homeβ</button> | |
| <button data-s="Take me to Irvine Spectrum">πΊοΈ βTake me to Irvine Spectrumβ</button> | |
| <button data-s="From Irvine Spectrum to Target">π βFrom the Spectrum to Targetβ</button> | |
| <button data-s="Take me to the nearest hospital">π₯ βTake me to the nearest hospitalβ</button> | |
| <button data-s="Take me to the train station">π βTake me to the train stationβ</button> | |
| </div></div> | |
| <div class="row" id="fromRow"> | |
| <label class="fld"><span>From</span><input id="from" placeholder="My location" autocomplete="off" aria-label="Starting from"></label> | |
| </div> | |
| <div class="row"> | |
| <label class="fld"><span>To</span><input id="to" placeholder="A place, address or stop" autocomplete="off" enterkeyhint="search" aria-label="Where to"></label> | |
| <button class="mic split" id="mic" aria-label="Tell Wheelie where you're going"><svg viewBox="0 0 24 24" fill="none" stroke="#fff" stroke-width="2.3" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="2.5" width="6" height="12" rx="3"/><path d="M5 11a7 7 0 0 0 14 0M12 18v3.5M8.5 21.5h7"/></svg></button> | |
| </div> | |
| <div id="sugg" role="listbox" aria-label="Places"></div> | |
| <div class="chips" id="chips"> | |
| <button class="chip on" data-w="now">Leave now</button> | |
| <button class="chip" data-w="hour">In 1 hour</button> | |
| <button class="chip" data-w="tmrw">Tomorrow 9 AM</button> | |
| <button class="chip" data-w="pick">Pick a time</button> | |
| </div> | |
| <div id="when"><input type="datetime-local" id="whenIn" aria-label="Leave at"></div> | |
| </section> | |
| <section id="sheet" aria-label="Your trip"> | |
| <button class="handle" id="handle" aria-label="Show more or less"><i></i></button> | |
| <div class="sb" id="sb"></div> | |
| </section> | |
| <div id="nav" role="status" aria-live="polite"><p id="navT"></p><small id="navD"></small><div class="navb"><button class="act b" id="navRe" style="display:none">β» New route</button><button class="act" id="navDrv" style="background:#FF6FB5;color:#000" aria-label="Show the driver what I need">🦽 Driver</button><a class="act" id="navWave" href="https://apps.apple.com/app/id6747179377" target="_blank" rel="noopener" style="background:#5BC0F8;color:#000;text-decoration:none" aria-label="Open Wave by OCTA to pay your fare">💳 Wave</a><button class="act" id="navStop" style="background:#fff;color:#000">β End trip</button></div></div> | |
| <div id="tl" aria-label="Trips home"> | |
| <div class="tlh"><a class="tlb" href="index.html" aria-label="Back to Wheelie">βΉ</a><img src="wheelie.jpg" alt=""><div><b>Let's roll.</b><span>Where are we going today?</span></div></div> | |
| <button class="tc" data-k="home" style="--d:0s"><i class="sp"></i><svg class="tr" viewBox="0 0 300 80"><path d="M10 60 C 80 10, 140 75, 200 30 S 280 20, 290 40"/></svg><em>π </em><b>Find your way home</b><span id="tlHomeSub">One tap and Wheelie gets you home, every step of the way.</span></button> | |
| <button class="tc" data-k="plan" style="--d:.12s"><i class="sp"></i><svg class="tr" viewBox="0 0 300 80"><path d="M10 40 C 60 70, 120 10, 180 50 S 260 70, 290 20"/></svg><em>πΊοΈ</em><b>Plan a trip</b><span>Anywhere in America. Bus, train, subway, paratransit or Uber. Wheelie rides with you.</span></button> | |
| <a class="tc" href="flights.html" style="--d:.18s;text-decoration:none;color:inherit;display:block"><i class="sp"></i><svg class="tr" viewBox="0 0 300 80"><path d="M10 70 C 90 60, 160 20, 290 10"/></svg><em>βοΈ</em><b>Fly anywhere</b><span>Find the best flight price, then get wheelchair help lined up with your airline.</span></a> | |
| <button class="tc" data-k="night" style="--d:.24s"><i class="sp"></i><svg class="tr" viewBox="0 0 300 80"><path d="M10 30 C 70 70, 150 20, 210 60 S 270 40, 290 55"/></svg><em>π</em><b>Late-night ride</b><span>Buses done for the night? Wheelie finds what's still running and wheelchair rides.</span></button> | |
| <button class="tc sos" data-k="sos" style="--d:.36s"><i class="sp"></i><em>π</em><b>I need help now</b><span>Call 911, share where you are, or call your ride. Wheelie stays with you.</span></button> | |
| </div> | |
| <div id="sos" role="dialog" aria-label="Help"> | |
| <div class="box"> | |
| <h2>Need help?</h2> | |
| <p>I'm right here with you.</p> | |
| <a class="sb2 r" href="tel:911"><span>π¨</span>Call 911</a> | |
| <button class="sb2" id="shareLoc"><span>π</span>Send my location to someone</button> | |
| <a class="sb2" id="ptS" href="tel:18776282232"><span>βΏ</span><i id="ptSL" style="font-style:normal">Call my wheelchair ride</i></a> | |
| <button class="sb2" id="sosClose"><span>β</span>Close</button> | |
| </div> | |
| </div> | |
| <script src="https://unpkg.com/maplibre-gl@4.7.1/dist/maplibre-gl.js"></script> | |
| <script> | |
| (function(){ | |
| ; | |
| var API='https://api.transitous.org/api/v1/'; | |
| var VOICE='https://yzzxvjrzniuhxebqqrsy.supabase.co/functions/v1/readpage?text='; | |
| var PINK='#FF6FB5',BLUE='#5BC0F8'; | |
| var $=function(id){return document.getElementById(id)}; | |
| var st={from:null,to:null,its:[],sel:0,when:'now',me:null}; | |
| function ls(k,v){try{if(v===undefined)return JSON.parse(localStorage.getItem(k)||'null');localStorage.setItem(k,JSON.stringify(v))}catch(e){return null}} | |
| function esc(s){return String(s==null?'':s).replace(/[&<>"']/g,function(c){return '&#'+c.charCodeAt(0)+';'})} | |
| /* ---------- voice ---------- */ | |
| var aud=new Audio(),voiceOn=false,lastSay=''; | |
| aud.addEventListener('playing',function(){talk(true)}); | |
| ['ended','pause','error'].forEach(function(e){aud.addEventListener(e,function(){talk(false)})}); | |
| function talk(on){['hAv','sayAv'].forEach(function(i){var e=$(i);e&&e.classList.toggle('talk',on)})} | |
| function unlock(){if(voiceOn)return;voiceOn=true;try{aud.src='data:audio/wav;base64,UklGRiYAAABXQVZFZm10IBAAAAABAAEAQB8AAIA+AAACABAAZGF0YQIAAAAAAA==';aud.play().catch(function(){})}catch(e){}} | |
| document.addEventListener('touchstart',unlock,{once:true,passive:true}); | |
| document.addEventListener('click',unlock,{once:true}); | |
| function say(t){ | |
| if(!t)return;lastSay=t; | |
| fetch(VOICE+encodeURIComponent(t)).then(function(r){return r.json()}).then(function(j){ | |
| if(lastSay!==t)return; | |
| if(j&&j.url){aud.src=j.url;aud.play().catch(function(){window.wPend=j.url})} | |
| }).catch(function(){}); | |
| } | |
| ['touchstart','click'].forEach(function(ev){document.addEventListener(ev,function(){if(window.wPend){var u=window.wPend;window.wPend=null;aud.src=u;aud.play().catch(function(){})}},{passive:true,capture:true})}); | |
| function fallback(t){return;try{var u=new SpeechSynthesisUtterance(t.replace(/Whyeed/g,'Why-eed'));u.rate=1.02;u.onstart=function(){talk(true)};u.onend=function(){talk(false)};speechSynthesis.cancel();speechSynthesis.speak(u)}catch(e){}} | |
| /* ---------- text helpers ---------- */ | |
| function title(s){ | |
| s=String(s||''); | |
| if(s===s.toUpperCase())s=s.toLowerCase().replace(/\b([a-z])/g,function(m){return m.toUpperCase()}).replace(/\b(Ii|Iii|Oc|Ucla|Uci|Ups|Usa)\b/g,function(m){return m.toUpperCase()}); | |
| s=s.replace(/\s*-\s*(Dock|Bay|Platform|Gate|Track|Stop)\b/gi,', $1').replace(/\s*-\s*/g,' & '); | |
| s=s.replace(/ (And|Of|The|At|To|On|In|For) /g,function(m){return m.toLowerCase()}); | |
| return s.replace(/\s+/g,' ').trim(); | |
| } | |
| function nm(p,kind){if(!p)return '';if(p.name==='START')return kind==='to'?'':(st.from&&st.from.label||'your starting point');if(p.name==='END')return st.to?st.to.label:'your destination';return title(p.name)} | |
| function hm(iso){var d=new Date(iso);return d.toLocaleTimeString('en-US',{hour:'numeric',minute:'2-digit'})} | |
| function dur(s){var m=Math.max(1,Math.round(s/60));if(m<60)return m+' min';var h=Math.floor(m/60),r=m%60;return h+' hr'+(r?' '+r+' min':'')} | |
| function dist(m){var ft=m*3.28084;if(ft<1000)return Math.round(ft/10)*10+' ft';return (m/1609.34).toFixed(1)+' mi'} | |
| var MW={BUS:'bus',COACH:'coach bus',TRAM:'streetcar',SUBWAY:'subway',METRO:'subway',RAIL:'train',REGIONAL_RAIL:'train',REGIONAL_FAST_RAIL:'train',HIGHSPEED_RAIL:'train',LONG_DISTANCE:'train',NIGHT_RAIL:'train',SUBURBAN:'train',FERRY:'ferry',FUNICULAR:'funicular',AERIAL_LIFT:'gondola',CABLE_CAR:'cable car',ODM:'on-demand ride',RIDE_SHARING:'ride share'}; | |
| var MI={bus:'π','coach bus':'π',streetcar:'π',subway:'π',train:'π',ferry:'β΄οΈ',gondola:'π‘','cable car':'π',funicular:'π','on-demand ride':'π','ride share':'π'}; | |
| function isWalk(l){return l.mode==='WALK'||l.mode==='BIKE'||l.mode==='CAR'||l.mode==='FLEX'} | |
| function agency(l){var a=l.agencyName||'';if(/orange county transportation/i.test(a))return 'OCTA';if(/metrolink/i.test(a))return 'Metrolink';if(/amtrak/i.test(a))return 'Amtrak';if(/los angeles county metro/i.test(a))return 'Metro';return a.length>22?'':a} | |
| function line(l){return l.routeShortName||l.displayName||l.tripShortName||title(l.routeLongName||'')} | |
| /* polyline, any precision */ | |
| function decode(str,prec){var f=Math.pow(10,prec||5),i=0,lat=0,lng=0,out=[];while(i<str.length){var b,s=0,r=0;do{b=str.charCodeAt(i++)-63;r|=(b&31)<<s;s+=5}while(b>=32);lat+=(r&1)?~(r>>1):(r>>1);s=0;r=0;do{b=str.charCodeAt(i++)-63;r|=(b&31)<<s;s+=5}while(b>=32);lng+=(r&1)?~(r>>1):(r>>1);out.push([lng/f,lat/f])}return out} | |
| function legCoords(l){var g=l.legGeometry;if(g&&g.points){try{var c=decode(g.points,g.precision||7);if(c.length>1)return c}catch(e){}}return [[l.from.lon,l.from.lat],[l.to.lon,l.to.lat]]} | |
| /* ---------- map ---------- */ | |
| var map=null,meM=null,destM=null,wM=null; | |
| function mk(cls,html){var d=document.createElement('div');d.className=cls;if(html)d.innerHTML=html;return d} | |
| try{ | |
| map=new maplibregl.Map({container:'map',style:'https://tiles.openfreemap.org/styles/dark',center:[-117.8265,33.6846],zoom:11.2,pitch:45,bearing:-12,attributionControl:{compact:true},maxPitch:70}); | |
| map.on('load',function(){ | |
| try{ | |
| if(map.getSource('openmaptiles')&&!map.getLayer('wb3d')){ | |
| map.addLayer({id:'wb3d',type:'fill-extrusion',source:'openmaptiles','source-layer':'building',minzoom:13,paint:{'fill-extrusion-color':['interpolate',['linear'],['coalesce',['get','render_height'],8],0,'#15151d',60,'#23233a'],'fill-extrusion-height':['coalesce',['get','render_height'],8],'fill-extrusion-base':['coalesce',['get','render_min_height'],0],'fill-extrusion-opacity':.85}}); | |
| } | |
| }catch(e){} | |
| map.addSource('route',{type:'geojson',data:fc([])}); | |
| map.addSource('stops',{type:'geojson',data:fc([])}); | |
| map.addLayer({id:'r-ride-glow',type:'line',source:'route',filter:['==',['get','k'],'ride'],layout:{'line-cap':'round','line-join':'round'},paint:{'line-color':BLUE,'line-width':16,'line-opacity':.22,'line-blur':6}}); | |
| map.addLayer({id:'r-walk-glow',type:'line',source:'route',filter:['==',['get','k'],'walk'],layout:{'line-cap':'round','line-join':'round'},paint:{'line-color':PINK,'line-width':16,'line-opacity':.25,'line-blur':6}}); | |
| map.addLayer({id:'r-ride',type:'line',source:'route',filter:['==',['get','k'],'ride'],layout:{'line-cap':'round','line-join':'round'},paint:{'line-color':BLUE,'line-width':7}}); | |
| map.addLayer({id:'r-walk',type:'line',source:'route',filter:['==',['get','k'],'walk'],layout:{'line-cap':'round','line-join':'round'},paint:{'line-color':PINK,'line-width':6}}); | |
| map.addLayer({id:'s-mid',type:'circle',source:'stops',filter:['==',['get','k'],'mid'],minzoom:12,paint:{'circle-radius':4,'circle-color':'#fff','circle-stroke-color':BLUE,'circle-stroke-width':2}}); | |
| map.addLayer({id:'s-key',type:'circle',source:'stops',filter:['==',['get','k'],'key'],paint:{'circle-radius':9,'circle-color':BLUE,'circle-stroke-color':'#fff','circle-stroke-width':3}}); | |
| try{map.addLayer({id:'s-lbl',type:'symbol',source:'stops',filter:['==',['get','k'],'key'],layout:{'text-field':['get','n'],'text-size':13,'text-offset':[0,1.5],'text-anchor':'top','text-font':['Noto Sans Bold'],'text-max-width':10},paint:{'text-color':'#fff','text-halo-color':'#000','text-halo-width':2}})}catch(e){} | |
| map.on('click','s-key',function(e){var p=e.features[0].properties;say(p.say||p.n)}); | |
| map.on('click','s-mid',function(e){var p=e.features[0].properties;say(p.n+(p.t?', the bus gets there at '+p.t:''))}); | |
| if(st.its.length)draw(); | |
| }); | |
| }catch(e){$('map').innerHTML='<div style="padding:120px 20px;color:#888;text-align:center">Map is loading slowly. Your trip steps still work below.</div>';map=null} | |
| function fc(f){return {type:'FeatureCollection',features:f}} | |
| function ready(){return map&&map.isStyleLoaded&&map.getSource&&map.getSource('route')} | |
| /* ---------- location ---------- */ | |
| function locate(cb){ | |
| if(!navigator.geolocation){cb&&cb(null);return} | |
| navigator.geolocation.getCurrentPosition(function(p){ | |
| st.me={lat:p.coords.latitude,lon:p.coords.longitude}; | |
| if(map){if(!meM)meM=new maplibregl.Marker({element:mk('me')}).setLngLat([st.me.lon,st.me.lat]).addTo(map);else meM.setLngLat([st.me.lon,st.me.lat]); | |
| if(!st.its.length)map.flyTo({center:[st.me.lon,st.me.lat],zoom:14,pitch:55,speed:.8})} | |
| cb&&cb(st.me); | |
| },function(){cb&&cb(null)},{enableHighAccuracy:true,timeout:9000,maximumAge:60000}); | |
| } | |
| /* ---------- search ---------- */ | |
| var tmr=0,active='to'; | |
| function bias(){var c=st.me||(map?{lat:map.getCenter().lat,lon:map.getCenter().lng}:{lat:33.6846,lon:-117.8265});return c.lat.toFixed(4)+','+c.lon.toFixed(4)} | |
| function area(r){var a=(r.areas||[]);var city=a.filter(function(x){return x.adminLevel===8||x.adminLevel===6})[0];var stt=a.filter(function(x){return x.adminLevel===4})[0];var parts=[];if(r.street)parts.push((r.houseNumber?r.houseNumber+' ':'')+r.street);if(city)parts.push(city.name);if(stt)parts.push(stt.name);return parts.join(', ')} | |
| function icon(r){if(r.type==='STOP'){var m=(r.modes||[]).join(' ');return /RAIL|SUBWAY|METRO|TRAM/.test(m)?'π':'π'}if(r.type==='ADDRESS')return 'π ';return 'π'} | |
| function search(q,which){ | |
| active=which;clearTimeout(tmr); | |
| if(!q||q.trim().length<2){renderSugg([]);return} | |
| tmr=setTimeout(function(){ | |
| fetch(API+'geocode?text='+encodeURIComponent(q)+'&place='+bias()).then(function(r){return r.json()}).then(function(L){renderSugg((L||[]).slice(0,6))}).catch(function(){renderSugg([],'I can\'t reach the map search right now. Try again in a moment.')}) | |
| },280); | |
| } | |
| function renderSugg(L,msg){ | |
| var s=$('sugg'); | |
| if(msg){s.innerHTML='<div class="note err">'+esc(msg)+'</div>';return} | |
| var rec=(!L.length&&active==='to'&&document.activeElement===$('to')&&!$('to').value)?(ls('wTripRecent')||[]):[]; | |
| var items=L.length?L.map(function(r){return {label:title(r.name),sub:area(r),lat:r.lat,lon:r.lon,ic:icon(r)}}):rec.map(function(r){return {label:r.label,sub:r.sub||'Recent',lat:r.lat,lon:r.lon,ic:'π'}}); | |
| s.innerHTML=items.map(function(x,i){return '<button role="option" data-i="'+i+'"><em>'+x.ic+'</em><span><b>'+esc(x.label)+'</b><small>'+esc(x.sub)+'</small></span></button>'}).join(''); | |
| [].forEach.call(s.children,function(b){b.onclick=function(){pick(items[+b.dataset.i],active)}}); | |
| } | |
| function pick(p,which){ | |
| if(window.tlPick)try{window.tlPick(p,which)}catch(e){} | |
| $('sugg').innerHTML=''; | |
| if(which==='from'){st.from=p;$('from').value=p.label;$('from').blur();if(st.to)plan();return} | |
| st.to=p;$('to').value=p.label;$('to').blur(); | |
| var rec=(ls('wTripRecent')||[]).filter(function(r){return r.label!==p.label});rec.unshift({label:p.label,sub:p.sub,lat:p.lat,lon:p.lon});ls('wTripRecent',rec.slice(0,6)); | |
| plan(); | |
| } | |
| $('to').addEventListener('input',function(){search(this.value,'to')}); | |
| $('to').addEventListener('focus',function(){active='to';$('plan').classList.remove('min');if(!this.value)renderSugg([])}); | |
| $('from').addEventListener('input',function(){if(!this.value){st.from=null}search(this.value,'from')}); | |
| $('to').addEventListener('keydown',function(e){if(e.key==='Enter'){var f=$('sugg').querySelector('button');f&&f.click()}}); | |
| $('from').addEventListener('keydown',function(e){if(e.key==='Enter'){var f=$('sugg').querySelector('button');f&&f.click()}}); | |
| function heard(raw){ | |
| raw=String(raw||'').replace(/[.?!]$/,'').trim();var f=null,t=raw,m; | |
| if(m=raw.match(/\bfrom\s+(.+?)\s+to\s+(.+)$/i)){f=m[1];t=m[2]}else if(m=raw.match(/\bto\s+(.+?)\s+from\s+(.+)$/i)){t=m[1];f=m[2]} | |
| t=t.replace(/^(take me to|i need to go to|i want to go to|go to|get me to|how do i get to|take me|get me)\s+/i,'').replace(/^the\s+nearest\s+/i,''); | |
| function G(q,cb){q=q.replace(/^the\s+/i,''); | |
| if(/^(home|my house|my home|my place)$/i.test(q)){var H=ls('wTripHome');if(H)return cb(H);if(f&&q===f.replace(/^the\s+/i,'')){say('I\'ll start from where you are right now.');return cb('here')}window.tlHome=1;$('to').value='';$('to').placeholder='Type your home address';setTimeout(function(){$('to').focus()},300);say('Where\'s home? Type your address once and I\'ll remember it.');return cb(null)} | |
| if(/^(here|my location|where i am|right here)$/i.test(q))return cb('here'); | |
| fetch(API+'geocode?text='+encodeURIComponent(q)+'&place='+bias()).then(function(x){return x.json()}).then(function(L){if(L&&L.length){var w=q.toLowerCase().split(/\s+/).filter(function(x){return x.length>2}),stn=/station|stop|train|metrolink|amtrak/i.test(q);var r0=L.map(function(r,i){var n=(r.name||'').toLowerCase(),s=0;w.forEach(function(x){if(n.indexOf(x)>-1)s+=2});if(r.type==='STOP'&&!stn)s-=3;return{r:r,s:s-i*0.1}}).sort(function(a,b){return b.s-a.s})[0].r;cb({label:title(r0.name),sub:area(r0),lat:r0.lat,lon:r0.lon})}else{say('I couldn\'t find '+q+'. Can you say it another way?');cb(null)}}).catch(function(){say('I can\'t reach the map right now.');cb(null)})} | |
| $('to').value=t;active='to';say('Got it. Looking up '+t+'.'); | |
| function goTo(){G(t,function(p){if(p&&p!=='here')pick(p,'to')})} | |
| if(f){st.to=null;$('from').value=f;say('Starting from '+f+'. Going to '+t+'.');G(f,function(p){if(p==='here'){st.from=null;$('from').value='';goTo()}else if(p){pick(p,'from');goTo()}})}else goTo(); | |
| } | |
| window.heard=heard; | |
| /* voice destination */ | |
| $('mic').onclick=function(){ | |
| unlock(); | |
| var SR=window.SpeechRecognition||window.webkitSpeechRecognition; | |
| if(!SR){$('to').focus();say('Type where you want to go, and I\'ll take it from there.');return} | |
| var r=new SR();r.lang='en-US';r.interimResults=false;r.maxAlternatives=1; | |
| $('mic').classList.add('on');$('hiSub').textContent='Listening⦠say where to, or from where to where, like "Irvine Spectrum."'; | |
| r.onresult=function(e){r._got=1;heard(e.results[0][0].transcript);return;var t=e.results[0][0].transcript.replace(/^(take me to|i need to go to|i want to go to|go to|get me to|how do i get to)\s+/i,'').replace(/[.?!]$/,'');$('to').value=t;active='to'; | |
| fetch(API+'geocode?text='+encodeURIComponent(t)+'&place='+bias()).then(function(x){return x.json()}).then(function(L){if(L&&L.length){var r0=L[0];pick({label:title(r0.name),sub:area(r0),lat:r0.lat,lon:r0.lon},'to')}else{say('I couldn\'t find '+t+'. Can you say it another way?')}}).catch(function(){say('I can\'t reach the map right now.')})}; | |
| r.onend=function(){if(!r._got){r._got=1;say('I didn\'t catch that. Tap the mic and say it again.')}$('mic').classList.remove('on');$('hiSub').textContent='Tell me or type it. I\'ll find the best accessible way.'}; | |
| r.onerror=r.onend; | |
| try{r.start()}catch(e){r.onend()} | |
| }; | |
| /* when */ | |
| [].forEach.call(document.querySelectorAll('.chip'),function(c){c.onclick=function(){ | |
| [].forEach.call(document.querySelectorAll('.chip'),function(x){x.classList.remove('on')});c.classList.add('on');st.when=c.dataset.w; | |
| $('when').style.display=st.when==='pick'?'block':'none'; | |
| if(st.when==='pick'){var d=new Date(Date.now()+3600e3);d.setMinutes(d.getMinutes()-d.getTimezoneOffset());$('whenIn').value=d.toISOString().slice(0,16);} | |
| if(st.to)plan(); | |
| }}); | |
| $('whenIn').addEventListener('change',function(){if(st.to)plan()}); | |
| function leaveAt(){var d=new Date();if(st.when==='hour')d=new Date(Date.now()+3600e3);else if(st.when==='tmrw'){d.setDate(d.getDate()+1);d.setHours(9,0,0,0)}else if(st.when==='pick'&&$('whenIn').value)d=new Date($('whenIn').value);return d} | |
| /* ---------- plan ---------- */ | |
| function sheet(mode){var s=$('sheet');s.classList.remove('open','small');if(mode)s.classList.add(mode)} | |
| $('handle').onclick=function(){var s=$('sheet');if(s.classList.contains('small'))sheet('open');else sheet('small')}; | |
| function plan(){ | |
| if(!st.to)return; | |
| var go=function(from){ | |
| if(!from){sheet('open');$('sb').innerHTML='<div class="note">I need to know where you\'re starting. Type it in the <b>From</b> box, or allow location for this page.</div>';$('from').focus();say('Where are you starting from? Type it in the From box.');return} | |
| $('plan').classList.add('min'); | |
| sheet('open');$('sb').innerHTML='<div class="loading"><div class="spin"></div>Finding the best accessible wayβ¦</div>'; | |
| var u=API+'plan?fromPlace='+from.lat+','+from.lon+'&toPlace='+st.to.lat+','+st.to.lon+'&pedestrianProfile=WHEELCHAIR&time='+encodeURIComponent(leaveAt().toISOString())+'&fastestDirectFactor=20&maxPreTransitTime=2400&maxPostTransitTime=2400&numItineraries=6'; | |
| fetch(u).then(function(r){if(!r.ok)throw new Error('server '+r.status);return r.json()}).then(function(d){ | |
| st.its=(d.itineraries||[]).filter(function(it){return it.legs&&it.legs.length}).slice(0,5);if(!st.its.length&&d.direct&&d.direct.length)st.its=d.direct.filter(function(it){return it.legs&&it.legs.length}).slice(0,1); | |
| try{var hasT=window.wHasT=function(it){return it.legs.some(function(l){return l.mode&&l.mode!=='WALK'&&l.mode!=='WHEELCHAIR'})};var T=st.its.filter(hasT),W=st.its.filter(function(it){return !hasT(it)});if(d.direct)W=W.concat(d.direct.filter(function(it){return it.legs&&it.legs.length}).slice(0,1));st.its=T.concat(W.slice(0,1)).slice(0,6)}catch(_){} | |
| st.sel=0;st.fromUsed=from;try{render()}catch(e){try{console.error(e)}catch(_){}var dm=st.its[0]?Math.round((st.its[0].duration||0)/60):0;$('sb').innerHTML='<div class="note">'+(dm?'You can roll there in about '+dm+' minutes. ':'')+'Tap Start trip and I will guide you.</div>'}try{if(st.its.length){var sbx=$('sb'),rb=document.createElement('button');rb.className='act';rb.id='readAll';rb.textContent='π Read every step to me';rb.style.cssText='width:100%;background:#FF6FB5;color:#000;font-weight:900;font-size:18px;margin:0 0 12px;min-height:54px';rb.onclick=function(){unlock();speakPlan(false,true)};sbx.insertBefore(rb,sbx.firstChild)}}catch(_){} | |
| try{draw()}catch(e){try{console.error(e)}catch(_){}} | |
| sheet('open');setTimeout(function(){if(!(nav&&nav.w))sheet('open');var sb=$('sb');if(sb)sb.scrollTop=0},700); | |
| try{if(st.its.length){var lg=st.its[st.sel].legs;if(lg.length===1&&!lg[0].routeShortName)say('Good news. It is close. You can roll there in about '+Math.max(1,Math.round((st.its[st.sel].duration||0)/60))+' minutes.');else speakPlan(true)}else say('I couldn\'t find a bus or train for that trip right now. A '+pt().n+' ride or a wheelchair van can still get you there.')}catch(e){try{console.error(e)}catch(_){}} | |
| }).catch(function(err){try{console.error(err)}catch(e){}$('sb').innerHTML='<div class="note err">I couldn\'t reach the trip planner. Tap Try again.<small style="display:block;opacity:.55;margin-top:6px;font-size:12px">'+esc(String(err&&err.message||err||''))+'</small></div><div class="acts"><button class="act p" id="retry">Try again</button></div>';$('retry').onclick=plan}); | |
| }; | |
| if(st.from)go(st.from);else if(st.me)go(st.me);else locate(go); | |
| } | |
| function badges(it){var h='';it.legs.forEach(function(l){if(isWalk(l))return;var w=MW[l.mode]||'ride';h+='<span class="bdg" style="background:#'+(l.routeColor&&l.routeColor!=='000000'&&l.routeColor!=='ffffff'?l.routeColor:'5BC0F8')+';color:#'+(l.routeTextColor&&l.routeColor&&l.routeColor!=='000000'?l.routeTextColor:'000')+'">'+(MI[w]||'π')+' '+esc(line(l))+'</span>'});return h||'<span class="bdg w">βΏ Roll there</span>'} | |
| function live(l){if(!l||isWalk(l))return '';var m=Math.round((new Date(l.startTime)-Date.now())/60000),lv=m<=0?'leaving now':'leaves in '+m+' min';if(!l.realTime)return lv+' (schedule)';var dl=l.scheduledStartTime?Math.round((new Date(l.startTime)-new Date(l.scheduledStartTime))/60000):0;return 'π’ LIVE Β· '+lv+(dl>1?' Β· '+dl+' min late':dl<-1?' Β· '+(-dl)+' min early':' Β· on time')} | |
| setInterval(function(){try{if(st.its&&st.its.length&&$('sheet').classList.contains('open')&&!document.hidden&&!(window.nav&&nav.w)){var y=$('sb').scrollTop;render();$('sb').scrollTop=y}}catch(e){}},30000); | |
| setInterval(function(){try{if(st.its&&st.its.length&&st.to&&!document.hidden&&!(window.nav&&nav.w)&&$('sheet').classList.contains('open')){var f=st.fromUsed;if(!f)return;fetch(API+'plan?fromPlace='+f.lat+','+f.lon+'&toPlace='+st.to.lat+','+st.to.lon+'&pedestrianProfile=WHEELCHAIR&fastestDirectFactor=20&maxPreTransitTime=2400&maxPostTransitTime=2400&numItineraries=6&time='+encodeURIComponent(leaveAt().toISOString())).then(function(r){return r.json()}).then(function(d){var n=(d.itineraries||[]).filter(function(it){return it.legs&&it.legs.length});var h=window.wHasT||function(){return true};n=n.filter(h).concat(n.filter(function(i){return !h(i)}).slice(0,1)).slice(0,6);if(n.length&&h(n[0])){var y=$('sb').scrollTop;st.its=n;if(st.sel>=n.length)st.sel=0;render();$('sb').scrollTop=y}}).catch(function(){})}}catch(e){}},90000); | |
| function rollTime(it){return it.legs.filter(isWalk).reduce(function(a,l){return a+(l.duration||0)},0)} | |
| function render(){ | |
| var sb=$('sb'); | |
| if(!st.its.length){ | |
| sb.innerHTML=bubble('I couldn\'t find a bus or train for that trip right now. These can still get you there:')+'<div class="opts">'+altCards()+'</div><button class="act" id="newTrip" style="width:100%;background:#fff;color:#000;margin-top:14px">οΌ Plan a new trip</button>';$('newTrip').onclick=resetTrip; | |
| wireAlts();return; | |
| } | |
| var it=st.its[st.sel]; | |
| var h=bubble(summary(it)); | |
| h+='<div class="opts" id="opts">'+st.its.map(function(x,i){ | |
| return '<button class="opt'+(i===st.sel?' on':'')+'" data-i="'+i+'" aria-label="Option '+(i+1)+', '+dur(x.duration)+'"><div class="big">'+dur(x.duration)+'</div><div class="tm">'+hm(x.startTime)+' β '+hm(x.endTime)+'</div><div class="bd">'+badges(x)+'</div>'+(function(){var fl=x.legs.filter(function(l){return !isWalk(l)})[0];return fl?'<div class="ft" style="color:'+(fl.realTime?'#7CFFB2':'#aaa')+';font-weight:700">'+esc(live(fl))+'</div>':''})()+'<div class="ft">βΏ Roll '+dur(rollTime(x))+(x.transfers?' Β· '+x.transfers+' transfer'+(x.transfers>1?'s':''):' Β· No transfers')+'</div></button>'}).join('')+altCards()+'</div>'; | |
| h+='<button class="act p" id="go" style="width:100%;margin-top:14px;height:62px;font-size:18px">π§ Start trip</button><div class="acts"><button class="act" id="ride" style="background:#1c1c22;color:#fff;border:1px solid rgba(255,255,255,.18)">βΆ Ride along</button><button class="act b" id="read">π Read it to me</button></div><button class="act" id="newTrip" style="width:100%;background:#fff;color:#000;margin-top:4px">οΌ Plan a new trip</button>'; | |
| h+='<h3>Step by step</h3><div class="steps">'+steps(it).map(function(s,i){return '<button class="st'+(s.r?' r':'')+'" data-i="'+i+'"><span class="ic">'+s.ic+'</span><span><b>'+esc(s.b)+'</b>'+(s.s?'<small>'+esc(s.s)+'</small>':'')+'</span><span class="t">'+esc(s.t||'')+'</span></button>'}).join('')+'</div>'; | |
| if(it.legs.some(function(l){return /orange county transportation/i.test(l.agencyName||'')}))h+='<div class="note"><b>Good to know:</b> every OCTA bus has a ramp and a wheelchair spot. Tell the driver your stop when you board.</div>'; | |
| h+='<div class="note">π’ LIVE means the bus is tracked right now. Wheelie updates these times while this screen is open.</div>'; | |
| sb.innerHTML=h; | |
| [].forEach.call(sb.querySelectorAll('.opt:not(.alt)'),function(b){b.onclick=function(){st.sel=+b.dataset.i;render();draw();speakPlan(false)}}); | |
| var ss=steps(it); | |
| [].forEach.call(sb.querySelectorAll('.st'),function(b){b.onclick=function(){var s=ss[+b.dataset.i];if(map&&s.at)map.flyTo({center:s.at,zoom:16,pitch:60,speed:1.1});say(s.say||s.b)}}); | |
| $('ride').onclick=rideAlong;$('go').onclick=startTrip;$('newTrip').onclick=resetTrip;$('read').onclick=function(){speakPlan(false,true)}; | |
| wireAlts(); | |
| } | |
| function bubble(t){return '<div id="say"><div class="av split" id="sayAv"><img src="wheelie.jpg" alt=""></div><p>'+esc(t)+'</p></div>'} | |
| if(typeof hav!=='function'){var hav=function(a,b){var R=6371000,r=Math.PI/180,dLat=(b[1]-a[1])*r,dLon=(b[0]-a[0])*r,x=Math.sin(dLat/2)*Math.sin(dLat/2)+Math.cos(a[1]*r)*Math.cos(b[1]*r)*Math.sin(dLon/2)*Math.sin(dLon/2);return 2*R*Math.atan2(Math.sqrt(x),Math.sqrt(1-x))}} | |
| if(typeof dist!=='function'){var dist=function(m){var mi=m/1609.34;return mi<0.1?Math.round(m*3.28)+' ft':mi.toFixed(1)+' mi'}} | |
| function pt(){var c=st.me; | |
| if(!c||hav([c.lon,c.lat],[-117.85,33.70])<70000)return{n:'OC ACCESS',tel:1,href:'tel:18776282232',ft:'Call 1-877-628-2232, press 1. Book by 5 PM the day before.',say:'Calling OC ACCESS. Press 1 to make your reservation. And remember, book it before 5 PM the day before.'}; | |
| return{n:'Local paratransit',tel:0,href:'https://www.google.com/search?q='+encodeURIComponent('ADA paratransit wheelchair ride near me'),ft:'Your city\'s door-to-door wheelchair ride. Tap to find their number and book.',say:'Here\'s your city\'s wheelchair ride service. Book it ahead, most need a day\'s notice.'}} | |
| window.pt=pt; | |
| document.addEventListener('click',function(e){if(e.target.closest&&e.target.closest('#sosBtn,.tc.sos')){var P=pt(),a=$('ptS');if(a){a.href=P.href;if(P.tel)a.removeAttribute('target');else a.target='_blank';$('ptSL').textContent=(P.tel?'Call ':'Find ')+P.n}}},true); | |
| function altCards(){ | |
| var P=pt();return '<a class="opt alt" href="'+P.href+'" id="altAcc"'+(P.tel?'':' target="_blank"')+'><div class="big">βΏ '+P.n+'</div><div class="tm">Door to door</div><div class="ft">'+P.ft+'</div></a>'+ | |
| '<a class="opt alt" id="altUber" href="#" target="_blank" rel="noopener"><div class="big">π Uber WAV</div><div class="tm">Wheelchair van</div><div class="ft">Opens Uber with your trip filled in. Pick WAV.</div></a>'; | |
| } | |
| function wireAlts(){ | |
| var a=$('altAcc');a&&a.addEventListener('click',function(){say(pt().say)}); | |
| var u=$('altUber');if(u&&st.to){var f=st.fromUsed||st.from||st.me;var q='action=setPickup'+(f?'&pickup[latitude]='+f.lat+'&pickup[longitude]='+f.lon:'&pickup=my_location')+'&dropoff[latitude]='+st.to.lat+'&dropoff[longitude]='+st.to.lon+'&dropoff[nickname]='+encodeURIComponent(st.to.label);u.href='https://m.uber.com/ul/?'+q;u.addEventListener('click',function(){say('Opening Uber. Choose WAV for a wheelchair van.')})} | |
| } | |
| function summary(it){ | |
| var first=it.legs.filter(function(l){return !isWalk(l)})[0]; | |
| var s='Leave at '+hm(it.startTime)+' and you\'ll get there at '+hm(it.endTime)+'.'; | |
| if(first)s+=' Your first ride is '+(agency(first)?agency(first)+' ':'')+(MW[first.mode]||'ride')+' '+line(first)+' at '+hm(first.startTime)+'.'; | |
| return s; | |
| } | |
| function steps(it){ | |
| var out=[]; | |
| it.legs.forEach(function(l,i){ | |
| var last=i===it.legs.length-1; | |
| if(isWalk(l)){ | |
| if((l.duration||0)<60&&!last&&!(i===0))return; | |
| var to=last?(st.to?st.to.label:'your destination'):nm(l.to); | |
| out.push({leg:i,ic:'βΏ',b:'Roll to '+to,s:dur(l.duration)+' Β· '+dist(l.distance||0),t:hm(l.startTime),at:[l.from.lon,l.from.lat],say:'Roll about '+dur(l.duration)+' to '+to+'.'}); | |
| }else{ | |
| var w=MW[l.mode]||'ride',ag=agency(l),n=(l.intermediateStops||[]).length+1; | |
| out.push({leg:i,r:1,ic:MI[w]||'π',b:'Take '+(ag?ag+' ':'')+w+' '+line(l)+(l.headsign?' toward '+title(l.headsign):''),s:live(l)+' Β· Board at '+nm(l.from)+' Β· '+n+' stop'+(n>1?'s':'')+' Β· '+dur(l.duration),t:hm(l.startTime),at:[l.from.lon,l.from.lat],say:'At '+hm(l.startTime)+', take '+(ag?ag+' ':'')+w+' '+line(l)+(l.headsign?' toward '+title(l.headsign):'')+' from '+nm(l.from)+'. Ride '+n+' stop'+(n>1?'s':'')+'.'}); | |
| out.push({leg:i,off:1,r:1,ic:'π',b:'Get off at '+nm(l.to),s:'Ring the bell when you pass '+(l.intermediateStops&&l.intermediateStops.length?title(l.intermediateStops[l.intermediateStops.length-1].name):'the stop before'),t:hm(l.endTime),at:[l.to.lon,l.to.lat],say:'Get off at '+nm(l.to)+' around '+hm(l.endTime)+'.'}); | |
| } | |
| }); | |
| out.push({ic:'π',b:'You made it!',s:st.to?st.to.label:'',t:hm(it.endTime),at:st.to?[st.to.lon,st.to.lat]:null,say:'You made it. I\'m proud of you.'}); | |
| return out; | |
| } | |
| function speakPlan(first,full){ | |
| var it=st.its[st.sel];if(!it)return; | |
| if(!full){say((first?'Got it. ':'')+summary(it)+(first?' Your steps are on the screen.':''));return} | |
| var q=steps(it).map(function(s){return s.say}).filter(Boolean),i=0;window.rdQ=q; | |
| function nx(){if(window.rdQ!==q||i>=q.length)return;var t=q[i++];say(t);var w=function(){aud.removeEventListener('ended',w);aud.removeEventListener('error',w);setTimeout(nx,350)};aud.addEventListener('ended',w);aud.addEventListener('error',w);setTimeout(function(){if(aud.paused&&!speechSynthesis.speaking)w()},Math.max(6000,t.length*90))} | |
| nx(); | |
| } | |
| /* ---------- draw ---------- */ | |
| function draw(){ | |
| if(!ready()||!st.its.length){return} | |
| var it=st.its[st.sel],F=[],S=[],all=[]; | |
| it.legs.forEach(function(l){ | |
| var c=legCoords(l);all=all.concat(c); | |
| F.push({type:'Feature',properties:{k:isWalk(l)?'walk':'ride'},geometry:{type:'LineString',coordinates:c}}); | |
| if(!isWalk(l)){ | |
| S.push(gpt(l.from,'key',nm(l.from),'Board '+(MW[l.mode]||'the ride')+' '+line(l)+' at '+nm(l.from)+', at '+hm(l.startTime)+'.')); | |
| S.push(gpt(l.to,'key',nm(l.to),'Get off at '+nm(l.to)+' around '+hm(l.endTime)+'.')); | |
| (l.intermediateStops||[]).forEach(function(s){S.push({type:'Feature',properties:{k:'mid',n:title(s.name),t:s.arrival?hm(s.arrival):''},geometry:{type:'Point',coordinates:[s.lon,s.lat]}})}); | |
| } | |
| }); | |
| map.getSource('route').setData(fc(F));map.getSource('stops').setData(fc(S)); | |
| st.line=all; | |
| var from=st.fromUsed; | |
| if(destM)destM.remove(); | |
| if(st.to)destM=new maplibregl.Marker({element:mk('dm','π')}).setLngLat([st.to.lon,st.to.lat]).addTo(map); | |
| if(!wM)wM=new maplibregl.Marker({element:mk('wm split','<img src="wheelie.jpg" alt="Wheelie">')}); | |
| wM.setLngLat(all[0]).addTo(map); | |
| var b=new maplibregl.LngLatBounds(all[0],all[0]);all.forEach(function(p){b.extend(p)}); | |
| var top=$('plan').getBoundingClientRect().bottom+10,bot=window.innerHeight*0.5; | |
| map.fitBounds(b,{padding:{top:top,bottom:bot,left:40,right:40},pitch:50,duration:1400,maxZoom:15.5}); | |
| } | |
| function gpt(p,k,n,s){return {type:'Feature',properties:{k:k,n:n,say:s},geometry:{type:'Point',coordinates:[p.lon,p.lat]}}} | |
| /* ride along */ | |
| var anim=0; | |
| function rideAlong(){ | |
| if(!map||!st.line||st.line.length<2){speakPlan(false,true);return} | |
| cancelAnimationFrame(anim);sheet('small'); | |
| var it=st.its[st.sel],L=st.line,seg=[0],tot=0; | |
| for(var i=1;i<L.length;i++){tot+=hav(L[i-1],L[i]);seg.push(tot)} | |
| var legEnds=[],acc=0;it.legs.forEach(function(l){acc+=legCoords(l).length;legEnds.push(acc-1)}); | |
| var ss=steps(it),curLeg=0; | |
| var T=Math.min(36000,Math.max(16000,tot/40)),t0=performance.now(); | |
| say('Okay, let\'s ride it together. '+(ss[0]?ss[0].say:'')); | |
| map.easeTo({center:L[0],zoom:16,pitch:62,duration:900}); | |
| function legSay(li){var prev=it.legs[li-1],t='';if(prev&&!isWalk(prev)){var o=ss.filter(function(x){return x.leg===li-1&&x.off})[0];if(o)t+=o.say+' '}var n=ss.filter(function(x){return x.leg===li&&!x.off})[0];if(n)t+=n.say;return t} | |
| function frame(now){ | |
| var p=Math.min(1,(now-t0)/T),d=p*tot,k=1;while(k<seg.length-1&&seg[k]<d)k++; | |
| var a=L[k-1],b=L[k],f=(seg[k]-seg[k-1])?(d-seg[k-1])/(seg[k]-seg[k-1]):0; | |
| var pos=[a[0]+(b[0]-a[0])*f,a[1]+(b[1]-a[1])*f]; | |
| wM.setLngLat(pos); | |
| map.jumpTo({center:pos,bearing:brg(a,b)*0.15+map.getBearing()*0.85}); | |
| var li=legEnds.findIndex(function(e){return k<=e}); | |
| if(li>curLeg){curLeg=li;var t=legSay(li);if(t)say(t)} | |
| if(p<1)anim=requestAnimationFrame(frame);else{say(ss[ss.length-1].say+' Tap Plan a new trip whenever you\'re ready.');anim=0;setTimeout(function(){sheet('open');var n=$('newTrip');n&&n.scrollIntoView({behavior:'smooth',block:'center'})},1800)} | |
| } | |
| setTimeout(function(){t0=performance.now();anim=requestAnimationFrame(frame)},950); | |
| } | |
| function hav(a,b){var R=6371e3,r=Math.PI/180,dl=(b[1]-a[1])*r,dn=(b[0]-a[0])*r,x=Math.sin(dl/2)*Math.sin(dl/2)+Math.cos(a[1]*r)*Math.cos(b[1]*r)*Math.sin(dn/2)*Math.sin(dn/2);return 2*R*Math.atan2(Math.sqrt(x),Math.sqrt(1-x))} | |
| function brg(a,b){var r=Math.PI/180,y=Math.sin((b[0]-a[0])*r)*Math.cos(b[1]*r),x=Math.cos(a[1]*r)*Math.sin(b[1]*r)-Math.sin(a[1]*r)*Math.cos(b[1]*r)*Math.cos((b[0]-a[0])*r);return Math.atan2(y,x)/r} | |
| ['mousedown','touchstart','wheel'].forEach(function(e){$('map').addEventListener(e,function(){if(anim){cancelAnimationFrame(anim);anim=0}},{passive:true})}); | |
| var nav={w:null,i:0,ev:[],off:0,lock:null}; | |
| function navEv(){var it=st.its[st.sel],ev=[];it.legs.forEach(function(l,i){if(isWalk(l))return;var w=MW[l.mode]||'ride',ag=agency(l),ims=l.intermediateStops||[];var nx=it.legs[i+1],after;if(nx&&!isWalk(nx))after=' Then catch '+(MW[nx.mode]||'the next ride')+' '+line(nx)+'.';else if(nx&&i+2<it.legs.length)after=' Then roll to '+nm(nx.to)+'.';else after=' Then roll to '+(st.to?st.to.label:'your destination')+'.'; | |
| ev.push({at:[l.from.lon,l.from.lat],r:60,t:'Wait here for '+(ag?ag+' ':'')+w+' '+line(l),say:'You\'re at your stop, '+nm(l.from)+'. Watch for '+(ag?ag+' ':'')+w+' '+line(l)+(l.headsign?' toward '+title(l.headsign):'')+'. It comes at '+hm(l.startTime)+'. Get your Wave card or your phone ready to tap when you board. Tap the Wave button if you need it.'}); | |
| if(ims.length){var z=ims[ims.length-1];ev.push({at:[z.lon,z.lat],r:120,t:'Your stop is next. Ring the bell.',say:'Your stop is next. Ring the bell now.'})} | |
| ev.push({at:[l.to.lon,l.to.lat],r:70,t:'Get off at '+nm(l.to),say:'Get off here, at '+nm(l.to)+'.'+after})}); | |
| if(st.to)ev.push({at:[st.to.lon,st.to.lat],r:50,end:1,t:'You made it!',say:'You made it! I\'m proud of you.'});return ev} | |
| function navShow(t){var e=$('navT');if(!document.getElementById('navCss')){var c=document.createElement('style');c.id='navCss';c.textContent='#navT{display:flex;flex-direction:column;align-items:center;text-align:center;gap:10px;margin:0}#navT .na{width:120px;height:120px;border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:72px;line-height:1;background:#FF6FB5;color:#000;box-shadow:0 0 0 6px #5BC0F8}#navT .nt{font-size:30px;font-weight:800;line-height:1.2;color:#fff}';document.head.appendChild(c)}var s=String(t||''),l=s.toLowerCase(),a='\u2B06';if(/u-turn/.test(l))a='\u21A9';else if(/roundabout/.test(l))a='\u27F3';else if(/left/.test(l))a='\u2B05';else if(/right/.test(l))a='\u27A1';else if(/bell|stop is next/.test(l))a='\uD83D\uDD14';else if(/get off/.test(l))a='\u2B07';else if(/made it/.test(l))a='\uD83C\uDFC1';else if(/wait|catch|watch for/.test(l))a='\uD83D\uDE8C';e.innerHTML='';var i=document.createElement('span');i.className='na';i.setAttribute('aria-hidden','true');i.textContent=a;var x=document.createElement('span');x.className='nt';x.textContent=s;e.appendChild(i);e.appendChild(x)} | |
| function startTrip(){ | |
| if(!st.its.length)return; | |
| if(!navigator.geolocation){say('I need your location to guide you. Try Ride along instead.');return} | |
| if(anim){cancelAnimationFrame(anim);anim=0} | |
| if(nav.w!=null){try{navigator.geolocation.clearWatch(nav.w)}catch(e){}nav.w=null}nav.rk=null;nav.ev=navEv();nav.i=0;nav.off=0; | |
| nav.tv=[];try{var dec=function(s,pr){var f=Math.pow(10,pr||7),i=0,la=0,lo=0,o=[];while(i<s.length){var b,sh=0,r=0;do{b=s.charCodeAt(i++)-63;r|=(b&31)<<sh;sh+=5}while(b>=32);la+=(r&1)?~(r>>1):(r>>1);sh=0;r=0;do{b=s.charCodeAt(i++)-63;r|=(b&31)<<sh;sh+=5}while(b>=32);lo+=(r&1)?~(r>>1):(r>>1);o.push([la/f,lo/f])}return o}; | |
| var DW={LEFT:'turn left',RIGHT:'turn right',SLIGHTLY_LEFT:'keep left',SLIGHTLY_RIGHT:'keep right',HARD_LEFT:'make a sharp left',HARD_RIGHT:'make a sharp right',UTURN_LEFT:'make a U-turn',UTURN_RIGHT:'make a U-turn',CIRCLE_CLOCKWISE:'go into the roundabout',CIRCLE_COUNTERCLOCKWISE:'go into the roundabout',ELEVATOR:'take the elevator'}; | |
| (st.its[st.sel].legs||[]).forEach(function(l){(l.steps||[]).forEach(function(s,k){var w=DW[s.relativeDirection];if(!w||!s.polyline||!s.polyline.points)return;var p=dec(s.polyline.points,s.polyline.precision);if(!p.length)return;var nm=s.streetName&&!/^(path|sidewalk|footway|service road|parking)/i.test(s.streetName)?' onto '+s.streetName:'';var tx=w.charAt(0).toUpperCase()+w.slice(1)+nm;nav.tv.push({at:p[0],t:tx,warned:false,done:false})})})}catch(_){} | |
| sheet(null);$('plan').style.display='none';$('nav').classList.add('on');$('navD').textContent=''; | |
| var f=steps(st.its[st.sel])[0];navShow(f?f.b:'Let\'s go'); | |
| say('Let\'s go! I\'ll be with you the whole way. '+(f?f.say:'')); | |
| try{navigator.wakeLock&&navigator.wakeLock.request('screen').then(function(l){nav.lock=l}).catch(function(){})}catch(e){} | |
| nav.w=navigator.geolocation.watchPosition(navPos,function(){navShow('I can\'t see your location. Make sure location is on.')},{enableHighAccuracy:true,maximumAge:3000,timeout:20000}); | |
| } | |
| function navPos(p){ | |
| var me=[p.coords.longitude,p.coords.latitude];st.me={lat:me[1],lon:me[0]}; | |
| if(map){if(wM)wM.setLngLat(me).addTo(map);map.easeTo({center:me,zoom:16.5,pitch:60,duration:900})} | |
| for(var j=nav.ev.length-1;j>=nav.i;j--){var e=nav.ev[j];if(hav(me,e.at)<=e.r){nav.i=j+1;navShow(e.t);say(e.say);if(e.end)setTimeout(function(){stopTrip()},5000);break}} | |
| try{for(var q=0;q<nav.tv.length;q++){var tv=nav.tv[q];if(tv.done)continue;var dd=hav(me,tv.at);if(dd<=20){tv.done=true;tv.warned=true;navShow(tv.t);say(tv.t+' now.');break}if(!tv.warned&&dd<=70){tv.warned=true;var m=Math.round(dd/5)*5,lw=tv.t.charAt(0).toLowerCase()+tv.t.slice(1);navShow('In '+m+' meters, '+lw);say('In about '+m+' meters, '+lw+'.');break}if(dd>70)break}}catch(_){} | |
| if(nav.i<nav.ev.length){var nx=nav.ev[nav.i];$('navD').textContent='Next: '+nx.t+' Β· '+dist(hav(me,nx.at))} | |
| if(st.line&&st.line.length){var md=1e9,L=st.line,toXY=function(p){return [p[0]*111320*Math.cos(me[0]*Math.PI/180),p[1]*110540]};var P=toXY([me[1],me[0]]);for(var k=0;k<L.length;k++){var A=toXY([L[k][1],L[k][0]]),B=toXY([(L[k+1]||L[k])[1],(L[k+1]||L[k])[0]]),dx=B[0]-A[0],dy=B[1]-A[1],q=dx*dx+dy*dy,t=q?Math.max(0,Math.min(1,((P[0]-A[0])*dx+(P[1]-A[1])*dy)/q)):0,ex=A[0]+t*dx-P[0],ey=A[1]+t*dy-P[1],d=Math.sqrt(ex*ex+ey*ey);if(d<md)md=d}try{(st.its[st.sel].legs||[]).forEach(function(l){[l.from,l.to].forEach(function(s){if(s&&s.lat!=null){var d=hav(me,[s.lat,s.lon]);if(d<md)md=d}})})}catch(_){}if(md>400){nav.off++;if(nav.off===5){$('navRe').style.display=''}}else nav.off=0} | |
| try{var LG=st.its[st.sel].legs||[];for(var li=0;li<LG.length;li++){var l=LG[li];if(isWalk(l))continue;var ss=(l.intermediateStops||[]).concat([l.to]),bk=-1,bd=150;for(var k=0;k<ss.length;k++){var d=hav(me,[ss[k].lon,ss[k].lat]);if(d<bd){bd=d;bk=k}}if(bk<0)continue;var left=ss.length-1-bk,key=li+':'+bk;if(left>=1)$('navD').textContent='\uD83D\uDE8C '+left+(left===1?' stop':' stops')+' to go';if(nav.rk!==key){nav.rk=key;if(left>=2&&left<=5)say(left+' stops to go.')}break}}catch(_){} | |
| } | |
| function stopTrip(quiet){ | |
| if(nav.w!=null){navigator.geolocation.clearWatch(nav.w);nav.w=null} | |
| try{nav.lock&&nav.lock.release()}catch(e){}nav.lock=null; | |
| var n=$('nav');if(!n.classList.contains('on'))return; | |
| n.classList.remove('on');$('plan').style.display='';$('navRe').style.display='none'; | |
| if(!quiet){sheet('open');var b=$('newTrip');b&&b.scrollIntoView({behavior:'smooth',block:'center'})} | |
| } | |
| /* ===== Rider reports + Family watch ===== */ | |
| var SBU='https://yzzxvjrzniuhxebqqrsy.supabase.co/rest/v1/rpc/',SBK='sb_publishable_DKyw5S1dkCuu_g98yfx1xA_FgeTNtyJ'; | |
| function rpc(fn,b){return fetch(SBU+fn,{method:'POST',headers:{'Content-Type':'application/json',apikey:SBK},body:JSON.stringify(b)}).then(function(r){if(!r.ok)throw new Error('db '+r.status);return r.status===204?null:r.json()})} | |
| var REPK=[['π','Elevator is out'],['π§','Sidewalk is blocked'],['βΏ','Bus ramp is broken'],['β¬οΈ','No curb ramp here'],['ποΈ','Construction in the way'],['π‘','Too dark or unsafe'],['β','Something else']]; | |
| var reps=[],repSaid={}; | |
| function wSegD(p,a,b){var k=Math.cos(p[1]*Math.PI/180)*111320,P=[p[0]*k,p[1]*110540],A=[a[0]*k,a[1]*110540],B=[b[0]*k,b[1]*110540],dx=B[0]-A[0],dy=B[1]-A[1],q=dx*dx+dy*dy,t=q?Math.max(0,Math.min(1,((P[0]-A[0])*dx+(P[1]-A[1])*dy)/q)):0,ex=A[0]+t*dx-P[0],ey=A[1]+t*dy-P[1];return Math.sqrt(ex*ex+ey*ey)} | |
| function wRouteDist(pt){var L=st.line||[];if(!L.length)return 1e9;if(L.length===1)return hav(pt,L[0]);var md=1e9;for(var i=0;i<L.length-1;i++){var d=wSegD(pt,L[i],L[i+1]);if(d<md)md=d}return md} | |
| function wAgo(t){var m=Math.round((Date.now()-new Date(t).getTime())/60000);return m<2?'just now':m<60?m+' min ago':Math.round(m/60)+' hr ago'} | |
| function wEsc(s){return String(s==null?'':s).replace(/[&<>"']/g,function(c){return '&#'+c.charCodeAt(0)+';'})} | |
| function repLayer(){try{if(!map||!map.getSource||!map.isStyleLoaded())return false;if(!map.getSource('wreps')){map.addSource('wreps',{type:'geojson',data:{type:'FeatureCollection',features:[]}});map.addLayer({id:'wreps',type:'circle',source:'wreps',paint:{'circle-radius':12,'circle-color':'#ffb03c','circle-stroke-color':'#000','circle-stroke-width':3}});map.on('click','wreps',function(e){var p=e.features[0].properties;say('A rider reported: '+String(p.k||'a problem').toLowerCase()+', '+(p.a||'')+'.')})}return true}catch(_){return false}} | |
| function repLoad(){try{if(!st.line||!st.line.length)return;var L=st.line,c=L[Math.floor(L.length/2)],far=0;L.forEach(function(p){var d=hav(c,p);if(d>far)far=d}); | |
| rpc('reports_near',{p_lat:c[1],p_lon:c[0],p_km:Math.min(50,far/1000+1)}).then(function(R){reps=(R||[]).filter(function(r){return wRouteDist([r.lon,r.lat])<80});repShow()}).catch(function(){})}catch(_){}} | |
| function repShow(){ | |
| if(repLayer())map.getSource('wreps').setData({type:'FeatureCollection',features:reps.map(function(r){return {type:'Feature',properties:{k:r.kind,a:wAgo(r.created_at)},geometry:{type:'Point',coordinates:[r.lon,r.lat]}}})}); | |
| var old=document.getElementById('repBox');if(old)old.remove(); | |
| var sb=document.getElementById('sb');if(!sb||!reps.length)return; | |
| var box=document.createElement('div');box.id='repBox';box.className='note';box.style.cssText='border-color:#ffb03c;box-shadow:inset 0 0 0 1px #ffb03c'; | |
| box.innerHTML='<b>β οΈ Heads up from other riders on this route</b>'+reps.slice(0,5).map(function(r){return '<div style="margin-top:8px;font-size:15px">'+wEsc(r.kind)+(r.note?' Β· '+wEsc(r.note):'')+' <span style="color:#a1a1aa">('+wAgo(r.created_at)+')</span></div>'}).join('')+'<div style="margin-top:8px;color:#a1a1aa;font-size:13px">Orange dots on the map show where. Reports clear after 2 days.</div>'; | |
| var a=sb.querySelector('.acts');if(a&&a.parentNode===sb)sb.insertBefore(box,a.nextSibling);else sb.insertBefore(box,sb.firstChild); | |
| } | |
| var _drawR=draw;draw=function(){var r=_drawR.apply(this,arguments);setTimeout(repLoad,400);return r}; | |
| var _renR=render;render=function(){var r=_renR.apply(this,arguments);if(reps.length)repShow();return r}; | |
| function wSheet(id,html){var o=document.getElementById(id);if(!o){o=document.createElement('div');o.id=id;o.className='wsh';o.setAttribute('role','dialog');o.addEventListener('click',function(e){if(e.target===o)o.classList.remove('on')});document.body.appendChild(o)}o.innerHTML='<div class="wbox">'+html+'</div>';o.classList.add('on');return o} | |
| (function(){var s=document.createElement('style');s.textContent='.wsh{position:fixed;inset:0;z-index:60;background:rgba(0,0,0,.65);display:none;align-items:flex-end}.wsh.on{display:flex}.wsh .wbox{width:100%;max-height:88vh;overflow-y:auto;background:#111116;border-radius:26px 26px 0 0;border-top:1px solid rgba(255,255,255,.12);padding:20px 16px calc(20px + env(safe-area-inset-bottom))}.wsh h2{margin:0 0 4px;font-size:22px}.wsh p{margin:0 0 14px;color:#c7c7cc;font-size:16px;line-height:1.35}.wgrid{display:grid;grid-template-columns:1fr 1fr;gap:10px}.wbt{min-height:84px;border-radius:18px;border:1px solid rgba(255,255,255,.14);background:#1a1a22;color:#fff;font:700 16px/1.2 -apple-system,system-ui;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:6px;padding:10px;text-align:center}.wbt span{font-size:28px}.wbt:active{transform:scale(.96)}.wfull{width:100%;height:58px;border-radius:18px;border:0;font:800 17px -apple-system,system-ui;margin-top:10px}.winp{width:100%;height:54px;border-radius:16px;border:1px solid rgba(255,255,255,.18);background:#0b0b10;color:#fff;font:500 18px -apple-system,system-ui;padding:0 14px;margin-bottom:6px}.navb{flex-wrap:wrap}.navb .act{flex:1 1 calc(33.3% - 10px)!important;min-width:0;font-size:15px;padding:0 6px}';document.head.appendChild(s)})(); | |
| function repOpen(){ | |
| var o=wSheet('repSh','<h2>β οΈ Report a problem here</h2><p>Tell other riders whatβs in the way. Wheelie warns everyone heading this way for the next 2 days.</p><div class="wgrid">'+REPK.map(function(k,i){return '<button class="wbt" data-i="'+i+'"><span>'+k[0]+'</span>'+k[1]+'</button>'}).join('')+'</div><button class="wfull" id="repX" style="background:#2a2a32;color:#fff">Cancel</button>'); | |
| o.querySelector('#repX').onclick=function(){o.classList.remove('on')}; | |
| [].forEach.call(o.querySelectorAll('.wbt'),function(b){b.onclick=function(){ | |
| var k=REPK[+b.dataset.i][1];b.style.opacity=.5; | |
| var send=function(lat,lon){rpc('report_add',{p_lat:lat,p_lon:lon,p_kind:k,p_note:null}).then(function(id){reps.unshift({id:id,lat:lat,lon:lon,kind:k,note:null,created_at:new Date().toISOString()});repSaid[id]=1;repShow();o.classList.remove('on');say('Thank you. Iβll warn everyone heading this way.')}).catch(function(){b.style.opacity=1;say('I couldnβt send that right now. Try again in a moment.')})}; | |
| if(st.me&&st.me.lat!=null)send(st.me.lat,st.me.lon);else if(navigator.geolocation)navigator.geolocation.getCurrentPosition(function(p){send(p.coords.latitude,p.coords.longitude)},function(){b.style.opacity=1;say('I need your location to mark the spot.')},{enableHighAccuracy:true,timeout:9000}); | |
| }}); | |
| say('Whatβs in the way? Tap it and Iβll warn everyone else.'); | |
| } | |
| var fam={id:null,last:0,anchor:null,moved:null,name:''}; | |
| function famToken(){var a=new Uint8Array(12);crypto.getRandomValues(a);return Array.prototype.map.call(a,function(x){return ('0'+x.toString(16)).slice(-2)}).join('')} | |
| function famPush(status,lat,lon){if(!fam.id)return;var it=st.its&&st.its[st.sel]; | |
| return rpc('share_upsert',{p_id:fam.id,p_name:fam.name||null,p_dest:st.to?st.to.label:null,p_eta:it&&it.endTime?hm(it.endTime):null,p_status:status,p_lat:lat==null?null:lat,p_lon:lon==null?null:lon,p_moved:fam.moved?new Date(fam.moved).toISOString():null}).catch(function(){})} | |
| function famTick(p){if(!fam.id)return;var me=[p.coords.longitude,p.coords.latitude];if(!fam.anchor||hav(me,fam.anchor)>30){fam.anchor=me;fam.moved=Date.now()}if(Date.now()-fam.last>15000){fam.last=Date.now();famPush('moving',me[1],me[0])}} | |
| function famLink(){return location.origin+location.pathname.replace(/[^\/]*$/,'')+'watch.html?t='+fam.id} | |
| function famOpen(){ | |
| var nmv='';try{nmv=ls('wMyName')||''}catch(_){} | |
| var on=!!fam.id; | |
| var o=wSheet('famSh','<h2>πͺ Let someone follow your trip</h2><p>They see where you are live, when youβll get there, and an alert if youβve been stopped for 10 minutes. It stops sharing when your trip ends.</p>'+ | |
| '<label for="famNm" style="display:block;font-weight:700;margin-bottom:6px">Your first name</label><input class="winp" id="famNm" autocomplete="given-name" value="'+wEsc(nmv)+'" placeholder="So they know itβs you">'+ | |
| '<button class="wfull" id="famGo" style="background:#1fd1a0;color:#000">'+(on?'Send the link again':'π€ Send the link')+'</button>'+(on?'<button class="wfull" id="famOff" style="background:#2a2a32;color:#fff">Stop sharing</button>':'')+'<button class="wfull" id="famX" style="background:transparent;color:#a1a1aa">Close</button>'); | |
| o.querySelector('#famX').onclick=function(){o.classList.remove('on')}; | |
| var off=o.querySelector('#famOff');if(off)off.onclick=function(){famPush('ended');fam.id=null;o.classList.remove('on');famBtn();say('Okay, I stopped sharing your trip.')}; | |
| o.querySelector('#famGo').onclick=function(){ | |
| var n=(o.querySelector('#famNm').value||'').trim().slice(0,40);fam.name=n;try{ls('wMyName',n)}catch(_){} | |
| if(!fam.id){fam.id=famToken();fam.moved=Date.now();fam.last=0} | |
| var lat=st.me&&st.me.lat,lon=st.me&&st.me.lon;famPush('moving',lat,lon); | |
| var link=famLink(),txt=(n?n+' is':'Iβm')+' on the way'+(st.to?' to '+st.to.label:'')+'. Follow along live with Wheelie: '; | |
| famBtn();o.classList.remove('on'); | |
| if(navigator.share){navigator.share({title:'Follow my trip',text:txt,url:link}).catch(function(){})}else{location.href='sms:?&body='+encodeURIComponent(txt+link)} | |
| say('Youβre sharing your trip. Pick who to send it to.'); | |
| }; | |
| } | |
| function famBtn(){var b=document.getElementById('navFam');if(b)b.innerHTML=fam.id?'πͺ Sharing β':'πͺ Share'} | |
| (function(){var nb=document.querySelector('#nav .navb'),stp=document.getElementById('navStop');if(!nb||!stp)return; | |
| var r=document.createElement('button');r.className='act';r.id='navRep';r.style.cssText='background:#ffb03c;color:#000';r.setAttribute('aria-label','Report a problem on the route');r.innerHTML='β οΈ Report';r.onclick=repOpen;nb.insertBefore(r,stp); | |
| var f=document.createElement('button');f.className='act';f.id='navFam';f.style.cssText='background:#1fd1a0;color:#000';f.setAttribute('aria-label','Let family follow my trip');f.onclick=famOpen;nb.insertBefore(f,stp);famBtn(); | |
| })(); | |
| var _npR=navPos;navPos=function(p){_npR(p);try{var me=[p.coords.longitude,p.coords.latitude];for(var i=0;i<reps.length;i++){var r=reps[i];if(!repSaid[r.id]&&hav(me,[r.lon,r.lat])<200){repSaid[r.id]=1;(function(k){setTimeout(function(){say('Heads up. A rider reported: '+k.toLowerCase()+', just ahead.')},2500)})(r.kind);break}}}catch(_){}try{famTick(p)}catch(_){}}; | |
| var _stR=stopTrip;stopTrip=function(){try{if(fam.id){var done=nav&&nav.ev&&nav.i>=nav.ev.length;famPush(done?'arrived':'ended',st.me&&st.me.lat,st.me&&st.me.lon);fam.id=null;fam.anchor=null;famBtn()}}catch(_){}return _stR.apply(this,arguments)}; | |
| $('navDrv').onclick=function(){var off='';try{var L=st.its[st.sel].legs||[];for(var i=0;i<L.length;i++){if(!isWalk(L[i])){off=nm(L[i].to);break}}}catch(_){}var o=document.getElementById('drvCard');if(!o){o=document.createElement('div');o.id='drvCard';o.setAttribute('role','dialog');o.setAttribute('aria-label','Message for the driver');o.style.cssText='position:fixed;inset:0;z-index:2147483646;background:#FF6FB5;color:#000;display:flex;flex-direction:column;align-items:center;justify-content:center;text-align:center;padding:28px;font-family:inherit';o.onclick=function(){o.style.display='none'};document.body.appendChild(o)}o.innerHTML='';var h=document.createElement('div');h.style.cssText='font-size:80px;line-height:1';h.textContent='\uD83E\uDDBD';var t=document.createElement('div');t.style.cssText='font-size:38px;font-weight:900;line-height:1.2;margin:18px 0';t.textContent='Hi! I use a wheelchair. Please lower the ramp.';var s=document.createElement('div');s.style.cssText='font-size:30px;font-weight:800;line-height:1.25';s.textContent=off?('I\u2019m getting off at '+off+'.'):'';var th=document.createElement('div');th.style.cssText='font-size:26px;font-weight:700;margin-top:18px';th.textContent='Thank you!';var x=document.createElement('div');x.style.cssText='font-size:18px;margin-top:34px;opacity:.75';x.textContent='Tap anywhere to close';o.appendChild(h);o.appendChild(t);o.appendChild(s);o.appendChild(th);o.appendChild(x);o.style.display='flex'}; | |
| $('navStop').onclick=function(){stopTrip();say('Okay, trip ended. Tap Plan a new trip whenever you\'re ready.')}; | |
| $('navRe').onclick=function(){stopTrip(1);st.from=null;$('from').value='';plan()}; | |
| function resetTrip(){stopTrip(1); | |
| if(anim){cancelAnimationFrame(anim);anim=0} | |
| st.to=null;st.its=[];st.sel=0;st.line=null;st.fromUsed=null; | |
| $('to').value='';$('sugg').innerHTML=''; | |
| $('plan').classList.remove('min');sheet(null); | |
| if(destM){destM.remove();destM=null} | |
| if(wM){wM.remove()} | |
| if(ready()){map.getSource('route').setData(fc([]));map.getSource('stops').setData(fc([]))} | |
| if(map){var c=st.me?[st.me.lon,st.me.lat]:[-117.8265,33.6846];map.flyTo({center:c,zoom:st.me?14:11.2,pitch:45,bearing:-12,speed:1})} | |
| say('Okay, where are we going next?'); | |
| } | |
| /* ---------- SOS ---------- */ | |
| $('sosBtn').onclick=function(){$('sos').classList.add('on');say('I\'m right here. Do you need help?')}; | |
| $('sosClose').onclick=function(){$('sos').classList.remove('on')}; | |
| $('sos').addEventListener('click',function(e){if(e.target===this)this.classList.remove('on')}); | |
| $('shareLoc').onclick=function(){ | |
| locate(function(p){ | |
| var link=p?'https://maps.google.com/?q='+p.lat.toFixed(5)+','+p.lon.toFixed(5):''; | |
| var txt='I could use a hand. Here\'s where I am: '+link; | |
| if(navigator.share){navigator.share({title:'My location',text:txt}).catch(function(){})} | |
| else location.href='sms:?&body='+encodeURIComponent(txt); | |
| }); | |
| }; | |
| /* ---------- start ---------- */ | |
| var hr=new Date().getHours(); | |
| $('hi').textContent=(hr<12?'Good morning':hr<17?'Good afternoon':'Good evening')+'. Where are we going today?'; | |
| locate(null); | |
| var q=new URLSearchParams(location.search).get('to'); | |
| if(q){$('to').value=q;fetch(API+'geocode?text='+encodeURIComponent(q)).then(function(r){return r.json()}).then(function(L){if(L&&L[0])pick({label:title(L[0].name),sub:area(L[0]),lat:L[0].lat,lon:L[0].lon},'to')}).catch(function(){})} | |
| (function(){var m=$('mic');if(!m)return;var n=+(ls('wMicUses')||0);if(n>=3)return;m.classList.add('hint');var p=m.parentNode;if(getComputedStyle(p).position==='static')p.style.position='relative';var t=document.createElement('div');t.id='micTip';t.textContent='ποΈ Tap here to talk';p.appendChild(t);m.addEventListener('click',function(){ls('wMicUses',n+1);m.classList.remove('hint');t.remove()},true)})(); | |
| document.addEventListener('click',function(e){var b=e.target.closest&&e.target.closest('#tips button');if(!b)return;unlock();heard(b.dataset.s)}); | |
| document.addEventListener('click',function(e){var b=e.target.closest&&e.target.closest('#readAll,#read');if(!b)return;e.preventDefault();e.stopPropagation();try{aud.pause()}catch(_){}try{speechSynthesis.cancel()}catch(_){}voiceOn=false;unlock();b.style.opacity='.6';setTimeout(function(){b.style.opacity=''},600);window.rdQ=null;setTimeout(function(){speakPlan(false,true)},150)},true); | |
| window.$=$;window.ls=ls;window.say=say;window.locate=locate;window.pick=pick; | |
| })(); | |
| </script> | |
| <script> | |
| (function(){ | |
| var tl=document.getElementById('tl');window.tlHome=0; | |
| function hide(){tl.classList.add('off')} | |
| function show(){tl.classList.remove('off')} | |
| var H=ls('wTripHome');if(H)document.getElementById('tlHomeSub').textContent='Home: '+H.label+'. One tap and you\'re on your way.'; | |
| window.tlPick=function(p,w){if(w==='to'&&window.tlHome){ls('wTripHome',p);window.tlHome=0;document.getElementById('tlHomeSub').textContent='Home: '+p.label+'. One tap and you\'re on your way.';say('Got it, that\'s home. Now you can get there in one tap.')}}; | |
| tl.addEventListener('click',function(e){var c=e.target.closest('.tc');if(!c)return;var k=c.dataset.k; | |
| if(k==='sos'){$('sosBtn').click();return} | |
| hide(); | |
| if(k==='home'){var h=ls('wTripHome');if(h){say('Taking you home.');pick(h,'to')}else{window.tlHome=1;$('to').value='';$('to').placeholder='Type your home address';setTimeout(function(){$('to').focus()},350);say('Where\'s home? Type your address once and I\'ll remember it.')}return} | |
| $('to').placeholder='A place, address or stop'; | |
| if(k==='night'){say('Tell me where you\'re headed. I\'ll show you what\'s still running tonight, plus wheelchair rides like paratransit and Uber.')} | |
| else say('Where are we going?'); | |
| setTimeout(function(){$('to').focus()},350); | |
| }); | |
| $('ttl').style.cursor='pointer';$('ttl').onclick=show; | |
| })(); | |
| (function(){ | |
| var sos=$('sos'),cl=$('sosClose'),box=document.createElement('div');box.id='ecBox'; | |
| function L(){return ls('wSosContacts')||[]} | |
| function digits(s){return (s||'').replace(/[^\d+]/g,'')} | |
| function E(s){return String(s).replace(/[&<>"]/g,function(c){return{'&':'&','<':'<','>':'>','"':'"'}[c]})} | |
| function draw(){var c=L(); | |
| box.innerHTML='<button class="bkBtn" id="bkGo">π οΈ My chair broke down</button><div id="bk"></div><h3>π₯ My emergency people</h3><p class="x">The people Wheelie reaches out to if you need help. Call or text them with one tap.</p>'+ | |
| c.map(function(x,i){return '<div class="ec"><b>'+E(x.n)+'<small>'+E(x.p)+'</small></b><a href="tel:'+digits(x.p)+'" aria-label="Call '+E(x.n)+'">π</a><a class="t" href="sms:'+digits(x.p)+'" aria-label="Text '+E(x.n)+'">π¬</a><button data-i="'+i+'" aria-label="Remove '+E(x.n)+'">β</button></div>'}).join('')+ | |
| (c.length<5?'<button class="ecAdd" id="ecAdd">οΌ Add someone</button>':'')+ | |
| '<div class="ecF" id="ecF"><input id="ecN" placeholder="Name (like Mom)" autocomplete="name"><input id="ecP" placeholder="Phone number" type="tel" autocomplete="tel"><button class="bkBtn" id="ecSave" style="background:#5BC0F8">Save</button></div>'; | |
| [].forEach.call(box.querySelectorAll('.ec button'),function(b){b.onclick=function(){var c=L();c.splice(+b.dataset.i,1);ls('wSosContacts',c);draw()}}); | |
| var a=$('ecAdd');if(a)a.onclick=function(){$('ecF').classList.add('on');a.style.display='none';$('ecN').focus()}; | |
| $('ecSave').onclick=function(){var n=$('ecN').value.trim(),p=$('ecP').value.trim();if(!n||digits(p).length<7){say('I need a name and a phone number.');return}var c=L();c.push({n:n,p:p});ls('wSosContacts',c);say(n+' is saved. I\'ll reach out to them if you need help.');draw()}; | |
| $('bkGo').onclick=breakdown; | |
| } | |
| function breakdown(){var bk=$('bk');bk.classList.add('on');bk.innerHTML='<p class="x">Finding where you areβ¦</p>';say('Okay, I\'ve got you. Let\'s get you help.'); | |
| locate(function(p){var ll=p?p.lat.toFixed(5)+','+p.lon.toFixed(5):'',c=L(),map='https://maps.google.com/?q='+ll; | |
| var txt='My wheelchair broke down and I need help. I\'m here: '+map; | |
| var h=''; | |
| if(c.length)h+='<a href="sms:/open?addresses='+c.map(function(x){return digits(x.p)}).join(',')+'&body='+encodeURIComponent(txt)+'"><em>π¬</em><span>Text my people<small>Sends '+(c.length>1?'everyone':E(c[0].n))+' where you are</small></span></a><a href="tel:'+digits(c[0].p)+'"><em>π</em><span>Call '+E(c[0].n)+'<small>Your first emergency person</small></span></a>'; | |
| h+='<a href="https://maps.google.com/?q='+encodeURIComponent('wheelchair repair')+(ll?'&near='+ll:'')+'" target="_blank"><em>π§</em><span>Wheelchair repair near me<small>Open shops close by</small></span></a>'; | |
| h+='<a href="https://m.uber.com/ul/?action=setPickup'+(p?'&pickup[latitude]='+p.lat+'&pickup[longitude]='+p.lon:'&pickup=my_location')+'" target="_blank"><em>π</em><span>Wheelchair Uber (WAV)<small>Pick Uber WAV for a ramp van</small></span></a>'; | |
| var P=pt();h+='<a href="'+P.href+'"'+(P.tel?'':' target="_blank"')+'><em>π</em><span>'+(P.tel?'Call ':'Find ')+P.n+'<small>Wheelchair ride service</small></span></a>'; | |
| h+='<a href="tel:911"><em>π¨</em><span>Call 911<small>If you\'re hurt or not safe</small></span></a>'; | |
| bk.innerHTML=h; | |
| say(c.length?'Tap Text my people and I\'ll send them where you are. I also found repair shops and a wheelchair ride.':'I found repair shops and a wheelchair ride. Add your emergency people below so I can reach them next time.'); | |
| }); | |
| } | |
| cl.parentNode.insertBefore(box,cl);draw(); | |
| var sl=$('shareLoc'),old=sl.onclick;sl.onclick=function(){var c=L();if(!c.length)return old&&old.call(sl);locate(function(p){var t='I could use a hand. Here\'s where I am: '+(p?'https://maps.google.com/?q='+p.lat.toFixed(5)+','+p.lon.toFixed(5):'');location.href='sms:/open?addresses='+c.map(function(x){return digits(x.p)}).join(',')+'&body='+encodeURIComponent(t)})}; | |
| })(); | |
| </script> | |
| <script src="wicons.js?v=1"></script> | |
| </body> | |
| </html> | |