Three single-file HTML apps ported to Makepad 2.0 (dev b41e740) script_mod! DSL: - rider (ride.html): 21 unit + 9 integration + 9 headless UI tests - koboyo (koboyo.html): 29 + 14 + 8 - insurance (insurance.html): 28 + 14 + 8 All 140 tests green. Includes headless-Linux platform patch, full README, setup script, and three brutal codebase assessments with execution plans.
841 lines
48 KiB
HTML
841 lines
48 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="en">
|
||
<head>
|
||
<meta charset="utf-8">
|
||
<meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no,viewport-fit=cover">
|
||
<title>Ryde — Rider App</title>
|
||
<style>
|
||
/* ================= RESET / TOKENS ================= */
|
||
*,*::before,*::after{box-sizing:border-box;margin:0;padding:0}
|
||
html,body{height:100%}
|
||
:root{
|
||
--ink:#f4f4f6;
|
||
--mut:#8f929c;
|
||
--bg:#17181b;
|
||
--card:#232428;
|
||
--card2:#2b2c31;
|
||
--line:#34363d;
|
||
--map-sh:0 10px 30px rgba(0,0,0,.4);
|
||
}
|
||
body{
|
||
margin:0;font-family:-apple-system,BlinkMacSystemFont,"SF Pro Text","SF Pro Display",Inter,"Segoe UI",Roboto,Helvetica,Arial,sans-serif;
|
||
background:#0e0e10;color:var(--ink);-webkit-font-smoothing:antialiased;
|
||
}
|
||
button{font:inherit;color:inherit;background:none;border:0;cursor:pointer;-webkit-tap-highlight-color:transparent}
|
||
input{font:inherit;color:inherit;background:none;border:0;outline:0}
|
||
|
||
/* ================= PHONE ================= */
|
||
.stage{min-height:100dvh;display:flex;align-items:center;justify-content:center}
|
||
.phone{width:390px;max-width:100vw;height:100dvh;position:relative;overflow:hidden;background:#000;display:flex;flex-direction:column}
|
||
@media(min-width:440px){
|
||
.stage{padding:26px;background:linear-gradient(180deg,#1c1d21,#0a0a0c)}
|
||
.phone{height:844px;border-radius:46px;box-shadow:0 34px 90px rgba(0,0,0,.6),inset 0 0 0 10px #101114,inset 0 0 0 11px #2a2b31}
|
||
}
|
||
.island{position:absolute;top:12px;left:50%;transform:translateX(-50%);width:104px;height:30px;background:#0a0b0e;border-radius:18px;z-index:120}
|
||
.statusbar{position:absolute;top:0;left:0;right:0;z-index:110;display:flex;align-items:center;justify-content:space-between;padding:15px 26px 4px;font-size:14.5px;font-weight:650;pointer-events:none}
|
||
.sb-icons{display:flex;align-items:center;gap:6px}
|
||
.home-ind{position:absolute;bottom:4px;left:50%;transform:translateX(-50%);width:126px;height:5px;border-radius:999px;background:rgba(255,255,255,.85);z-index:130;pointer-events:none}
|
||
|
||
/* ================= SCREENS ================= */
|
||
.screens{flex:1;position:relative;overflow:hidden}
|
||
.scr{position:absolute;inset:0;display:none;background:var(--bg)}
|
||
.scr.on{display:block;animation:fadeIn .3s ease}
|
||
@keyframes fadeIn{from{opacity:.4}to{opacity:1}}
|
||
.map{position:absolute;inset:0}
|
||
.map svg{width:100%;height:100%}
|
||
.fade-map::after{content:'';position:absolute;inset:0;background:rgba(224,226,232,.55);pointer-events:none}
|
||
|
||
/* floating map controls */
|
||
.fl-ava{
|
||
position:absolute;top:56px;left:18px;width:46px;height:46px;border-radius:50%;z-index:20;
|
||
border:2.5px solid rgba(255,255,255,.9);display:grid;place-items:center;font-size:22px;
|
||
background:radial-gradient(circle at 32% 26%,#ffd9c3,#e8926a);box-shadow:var(--map-sh);
|
||
}
|
||
.fl-cross{
|
||
position:absolute;top:56px;right:18px;width:46px;height:46px;border-radius:50%;z-index:20;
|
||
background:rgba(30,31,35,.7);backdrop-filter:blur(8px);display:grid;place-items:center;box-shadow:var(--map-sh);
|
||
}
|
||
.fl-back{
|
||
position:absolute;top:56px;left:18px;width:42px;height:42px;border-radius:50%;z-index:20;
|
||
background:rgba(30,31,35,.72);backdrop-filter:blur(8px);display:grid;place-items:center;box-shadow:var(--map-sh);
|
||
}
|
||
.eta-pill{
|
||
position:absolute;top:58px;right:18px;z-index:20;display:flex;align-items:center;gap:9px;
|
||
background:rgba(20,21,25,.85);backdrop-filter:blur(8px);border-radius:999px;padding:10px 16px;
|
||
font-size:13.5px;font-weight:700;box-shadow:var(--map-sh);
|
||
}
|
||
.eta-pill .sq{width:11px;height:11px;border-radius:3px;background:#fff}
|
||
|
||
/* ================= HOME ================= */
|
||
.wtc-pill{
|
||
position:absolute;left:16px;right:16px;bottom:96px;z-index:15;display:flex;align-items:center;gap:12px;
|
||
background:rgba(32,33,38,.82);backdrop-filter:blur(14px);-webkit-backdrop-filter:blur(14px);
|
||
border-radius:999px;padding:9px 9px 9px 20px;box-shadow:var(--map-sh);text-align:left;width:calc(100% - 32px);
|
||
}
|
||
.wtc-pill .lbl{flex:1;font-size:16px;font-weight:650;color:#e8e9ec}
|
||
.wtc-now{background:#fff;color:#101114;font-size:12.5px;font-weight:800;padding:8px 15px;border-radius:999px}
|
||
|
||
/* ================= SEARCH ================= */
|
||
.search-card{
|
||
position:absolute;left:0;right:0;bottom:0;z-index:15;
|
||
background:rgba(28,29,34,.78);backdrop-filter:blur(18px);-webkit-backdrop-filter:blur(18px);
|
||
border-radius:26px 26px 0 0;padding:12px 16px 120px;box-shadow:0 -14px 44px rgba(0,0,0,.45);
|
||
}
|
||
.s-top{display:flex;align-items:center;gap:12px;margin-bottom:16px}
|
||
.s-back{width:30px;height:30px;display:grid;place-items:center}
|
||
.s-in{flex:1;font-size:16.5px;font-weight:650;color:#fff}
|
||
.s-in::placeholder{color:#cfd1d8}
|
||
.sug-lbl{font-size:10.5px;letter-spacing:1.6px;color:var(--mut);font-weight:800;margin:2px 0 12px}
|
||
.qpills{display:flex;gap:9px;margin-bottom:16px}
|
||
.qpill{display:flex;align-items:center;gap:7px;background:rgba(255,255,255,.09);border-radius:999px;padding:9px 16px;font-size:13.5px;font-weight:700}
|
||
.sug-row{display:flex;align-items:center;gap:13px;padding:13px 2px;border-top:1px solid rgba(255,255,255,.07);width:100%;text-align:left}
|
||
.sug-row:first-of-type{border-top:0}
|
||
.sug-ic{width:38px;height:38px;border-radius:50%;background:rgba(255,255,255,.08);display:grid;place-items:center;flex:0 0 38px}
|
||
.sug-t{font-size:15.5px;font-weight:700}
|
||
.sug-s{font-size:12px;color:var(--mut);margin-top:2px}
|
||
.sug-ar{margin-left:auto;color:var(--mut)}
|
||
|
||
/* ================= TAB BAR ================= */
|
||
.tabbar{
|
||
position:absolute;left:52px;right:52px;bottom:14px;z-index:100;height:66px;
|
||
background:rgba(16,17,20,.9);backdrop-filter:blur(16px);-webkit-backdrop-filter:blur(16px);
|
||
border-radius:999px;display:flex;box-shadow:0 14px 34px rgba(0,0,0,.5),inset 0 0 0 1px rgba(255,255,255,.05);
|
||
transition:opacity .25s,transform .25s;
|
||
}
|
||
.tabbar.hide{opacity:0;transform:translateY(16px);pointer-events:none}
|
||
.tb{flex:1;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:3px;color:#84868f}
|
||
.tb .tl{font-size:10.5px;font-weight:700}
|
||
.tb.on{color:#fff}
|
||
|
||
/* ================= BOTTOM PANELS ================= */
|
||
.panel{
|
||
position:absolute;left:0;right:0;bottom:0;z-index:30;background:#18191d;border-radius:26px 26px 0 0;
|
||
padding:16px 16px 26px;box-shadow:0 -16px 50px rgba(0,0,0,.5);animation:rise .38s cubic-bezier(.22,1,.36,1);
|
||
}
|
||
@keyframes rise{from{transform:translateY(40px);opacity:0}to{transform:none;opacity:1}}
|
||
.p-head{display:flex;align-items:center;gap:14px;margin:2px 0 16px}
|
||
.p-back{width:34px;height:34px;border-radius:50%;display:grid;place-items:center}
|
||
.p-title{font-size:19px;font-weight:780;letter-spacing:-.3px}
|
||
.cta{
|
||
width:100%;height:52px;border-radius:999px;background:#fff;color:#0f1013;font-size:15.5px;font-weight:780;
|
||
display:block;text-align:center;transition:transform .15s;
|
||
}
|
||
.cta:active{transform:scale(.97)}
|
||
.cta.dark{background:#2a2b30;color:#fff;box-shadow:inset 0 0 0 1px #3c3e45}
|
||
|
||
/* choose */
|
||
.ride-opt{
|
||
width:100%;display:flex;align-items:center;gap:14px;background:#242529;border-radius:18px;padding:14px 16px;
|
||
margin-bottom:11px;border:1.5px solid transparent;transition:all .18s;text-align:left;
|
||
}
|
||
.ride-opt.on{background:#2c2d33;border-color:rgba(255,255,255,.85)}
|
||
.ro-t{font-size:15.5px;font-weight:760;display:flex;align-items:center}
|
||
.ro-badge{margin-left:8px;font-size:9.5px;font-weight:800;color:#cfd1d8;border:1.3px solid rgba(255,255,255,.5);border-radius:999px;padding:3px 8px}
|
||
.ro-s{font-size:12px;color:var(--mut);margin-top:3px}
|
||
.ro-p{margin-left:auto;font-size:16.5px;font-weight:780}
|
||
|
||
/* looking */
|
||
.drv-row{display:flex;gap:12px;justify-content:center;margin:4px 0 18px}
|
||
.drv-card{
|
||
width:106px;background:#242529;border-radius:20px;padding:16px 8px 13px;text-align:center;
|
||
border:1.5px solid transparent;transition:all .18s;
|
||
}
|
||
.drv-card.on{background:#2f3037;border-color:#4d505b}
|
||
.dc-ava{width:58px;height:58px;border-radius:50%;margin:0 auto 9px;display:grid;place-items:center;font-size:27px}
|
||
.dc-n{font-size:13.5px;font-weight:750}
|
||
.dc-r{font-size:11.5px;color:var(--mut);margin-top:3px}
|
||
.searching{display:inline-flex;gap:4px;margin-left:6px}
|
||
.searching i{width:4px;height:4px;border-radius:50%;background:#fff;display:inline-block;animation:blip 1.1s infinite}
|
||
.searching i:nth-child(2){animation-delay:.18s}.searching i:nth-child(3){animation-delay:.36s}
|
||
@keyframes blip{0%,100%{opacity:.25}50%{opacity:1}}
|
||
|
||
/* found */
|
||
.found-hero{position:relative;width:150px;height:82px;margin:6px auto 14px}
|
||
.fh-ava{
|
||
position:absolute;left:0;bottom:6px;width:54px;height:54px;border-radius:50%;z-index:2;
|
||
border:2.5px solid #18191d;display:grid;place-items:center;font-size:25px;
|
||
background:radial-gradient(circle at 32% 26%,#ffd9c3,#e8926a);box-shadow:0 8px 20px rgba(0,0,0,.5);
|
||
}
|
||
.fd-row{display:flex;align-items:flex-end;justify-content:space-between}
|
||
.fd-name{font-size:19px;font-weight:800;letter-spacing:-.3px}
|
||
.fd-plate{font-size:15px;font-weight:800;color:#dfe0e5}
|
||
.fd-rate{display:flex;align-items:center;gap:5px;color:var(--mut);font-size:13px;font-weight:700;margin-top:5px}
|
||
.fd-arr{display:flex;align-items:center;gap:7px;color:#d7d8dd;font-size:13.5px;font-weight:600;margin:14px 0 18px}
|
||
|
||
/* compact driver plate chip */
|
||
.plate-chip{background:#24262b;border:1px solid #3d3f47;color:#e6e7ea;font-size:11.5px;font-weight:800;letter-spacing:1.2px;border-radius:8px;padding:4.5px 9px}
|
||
|
||
/* ================= ARRIVING SHEET ================= */
|
||
.sheet{
|
||
position:absolute;left:12px;right:12px;bottom:12px;z-index:40;background:#1b1c20;border-radius:26px;
|
||
box-shadow:0 -12px 44px rgba(0,0,0,.55),inset 0 1px 0 rgba(255,255,255,.05);
|
||
transform:translateY(0);transition:transform .34s cubic-bezier(.22,1,.36,1);
|
||
display:flex;flex-direction:column;overflow:hidden;touch-action:none;
|
||
}
|
||
.sheet.peek{height:148px}
|
||
.sheet.expanded{height:486px}
|
||
.sheet.drag{transition:none !important}
|
||
.grab{flex:0 0 18px;display:grid;place-items:center;cursor:grab;user-select:none}
|
||
.grab i{width:42px;height:4.5px;border-radius:999px;background:#3f414a}
|
||
.sh-pad{padding:2px 16px 16px}
|
||
.trip-card{background:#242529;border-radius:18px;padding:15px 16px;margin:14px 0 11px}
|
||
.tr-row{display:flex;align-items:center;gap:13px;position:relative}
|
||
.tr-row + .tr-row{margin-top:16px}
|
||
.tr-dot{width:11px;height:11px;border-radius:50%;border:2.5px solid #e8e9ec;background:transparent;flex:0 0 11px}
|
||
.tr-sq{width:11px;height:11px;border-radius:2.5px;background:#e8e9ec;flex:0 0 11px}
|
||
.tr-line{position:absolute;left:4.5px;top:15px;bottom:-19px;width:2px;background:#3b3d44}
|
||
.tr-l{font-size:10.5px;color:var(--mut);font-weight:700;letter-spacing:.4px}
|
||
.tr-a{font-size:14.5px;font-weight:760;margin-top:1px}
|
||
.tr-t{margin-left:auto;font-size:14.5px;font-weight:800}
|
||
.drv-strip{display:flex;align-items:center;gap:12px;background:#242529;border-radius:18px;padding:11px 14px;margin-bottom:11px}
|
||
.ds-ava{width:42px;height:42px;border-radius:50%;display:grid;place-items:center;font-size:20px;background:radial-gradient(circle at 32% 26%,#ffd9c3,#e8926a)}
|
||
.chat-btn{width:42px;height:42px;border-radius:50%;background:#2d2e34;border:1.4px solid #44464e;display:grid;place-items:center;margin-left:auto;transition:transform .15s}
|
||
.chat-btn:active{transform:scale(.9)}
|
||
|
||
/* compact car card (peek) */
|
||
.carline{display:flex;align-items:center;gap:13px}
|
||
.p-badge{width:20px;height:20px;border-radius:6px;background:#2c2d33;border:1px solid #46484f;color:#fff;font-size:11px;font-weight:800;display:grid;place-items:center;flex:0 0 20px}
|
||
.cl-t{font-size:15px;font-weight:780}
|
||
.cl-sub{display:flex;align-items:center;gap:8px;margin-top:5px}
|
||
.cl-rate{font-size:12px;color:var(--mut);font-weight:700}
|
||
.carthumb{position:relative;width:74px;flex:0 0 74px}
|
||
.ct-ava{position:absolute;left:-8px;bottom:-4px;width:30px;height:30px;border-radius:50%;border:2.5px solid #1b1c20;display:grid;place-items:center;font-size:14px;background:radial-gradient(circle at 32% 26%,#ffd9c3,#e8926a)}
|
||
|
||
/* ================= CHAT ================= */
|
||
.chat-head{
|
||
display:flex;align-items:center;gap:12px;padding:58px 16px 12px;background:#101012;
|
||
border-bottom:1px solid #242529;position:relative;z-index:10;
|
||
}
|
||
.ch-back{width:32px;height:32px;display:grid;place-items:center}
|
||
.ch-ava{width:44px;height:44px;border-radius:50%;display:grid;place-items:center;font-size:21px;background:radial-gradient(circle at 32% 26%,#ffd9c3,#e8926a);flex:0 0 44px}
|
||
.ch-name{font-size:16.5px;font-weight:780}
|
||
.ch-sub{font-size:12px;color:var(--mut);margin-top:2px}
|
||
.ch-call{width:38px;height:38px;border-radius:50%;display:grid;place-items:center;margin-left:auto}
|
||
.chat-body{position:absolute;inset:0;padding:124px 16px 96px;overflow-y:auto;scrollbar-width:none;background:#131315}
|
||
.chat-body::-webkit-scrollbar{display:none}
|
||
.msg{display:flex;align-items:flex-end;gap:8px;margin-bottom:6px;animation:pop .25s ease}
|
||
@keyframes pop{from{opacity:0;transform:translateY(8px)}to{opacity:1;transform:none}}
|
||
.msg-ava{width:30px;height:30px;border-radius:50%;display:grid;place-items:center;font-size:15px;background:radial-gradient(circle at 32% 26%,#ffd9c3,#e8926a);flex:0 0 30px}
|
||
.bub{max-width:72%;padding:11px 15px;border-radius:19px;font-size:15px;line-height:1.4}
|
||
.bub.them{background:#2b2c2f;color:#f2f3f5;border-bottom-left-radius:6px}
|
||
.msg.me{justify-content:flex-end}
|
||
.bub.me{background:#f2f2f4;color:#111;border-bottom-right-radius:6px;margin-left:auto}
|
||
.m-time{font-size:11.5px;color:#6d6f78;margin:8px 0 12px 40px}
|
||
.chat-input{
|
||
position:absolute;left:12px;right:12px;bottom:14px;z-index:20;display:flex;gap:9px;align-items:center;
|
||
}
|
||
.ci-pill{
|
||
flex:1;display:flex;align-items:center;background:#2b2c2f;border-radius:999px;padding:0 6px 0 18px;height:50px;
|
||
box-shadow:0 8px 24px rgba(0,0,0,.4);
|
||
}
|
||
.ci-pill input{flex:1;font-size:15px;color:#fff}
|
||
.ci-pill input::placeholder{color:#7c7e88}
|
||
.ci-send{width:38px;height:38px;border-radius:50%;background:#44464d;display:grid;place-items:center;flex:0 0 38px}
|
||
.ci-up{width:50px;height:50px;border-radius:50%;background:#2b2c2f;display:grid;place-items:center;flex:0 0 50px;box-shadow:0 8px 24px rgba(0,0,0,.4);transition:transform .15s}
|
||
.ci-up:active{transform:scale(.9)}
|
||
|
||
/* ================= ACTIVITY / ACCOUNT ================= */
|
||
.subpage{position:absolute;inset:0;background:#131315;padding:64px 18px 110px;overflow-y:auto;scrollbar-width:none}
|
||
.subpage::-webkit-scrollbar{display:none}
|
||
.sp-title{font-size:24px;font-weight:830;letter-spacing:-.4px;margin:8px 0 18px}
|
||
.trip-row{display:flex;align-items:center;gap:13px;background:#1e1f23;border-radius:17px;padding:14px 15px;margin-bottom:11px}
|
||
.tri-ic{width:42px;height:42px;border-radius:14px;background:#2a2b30;display:grid;place-items:center;flex:0 0 42px}
|
||
.tri-t{font-size:14.5px;font-weight:750}
|
||
.tri-s{font-size:11.5px;color:var(--mut);margin-top:2px}
|
||
.tri-p{margin-left:auto;font-size:14px;font-weight:800}
|
||
.acc-card{background:#1e1f23;border-radius:20px;padding:18px;display:flex;align-items:center;gap:14px;margin-bottom:14px}
|
||
.acc-ava{width:62px;height:62px;border-radius:50%;display:grid;place-items:center;font-size:30px;background:radial-gradient(circle at 32% 26%,#ffd9c3,#e8926a)}
|
||
.acc-row{display:flex;align-items:center;gap:13px;background:#1e1f23;border-radius:16px;padding:15px 16px;margin-bottom:10px;font-size:14.5px;font-weight:700;width:100%;text-align:left}
|
||
.acc-ic{width:38px;height:38px;border-radius:13px;background:#2a2b30;display:grid;place-items:center;flex:0 0 38px}
|
||
.acc-row .chev{margin-left:auto;color:#5b5d66}
|
||
|
||
/* toast */
|
||
.toast{
|
||
position:absolute;left:50%;bottom:104px;transform:translateX(-50%) translateY(14px);z-index:140;
|
||
background:#f2f2f4;color:#101114;font-size:13px;font-weight:750;padding:11px 19px;border-radius:999px;
|
||
opacity:0;pointer-events:none;transition:all .3s cubic-bezier(.22,1,.36,1);white-space:nowrap;box-shadow:0 12px 30px rgba(0,0,0,.45);
|
||
}
|
||
.toast.show{opacity:1;transform:translateX(-50%) translateY(0)}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<div class="stage">
|
||
<div class="phone">
|
||
<div class="island"></div>
|
||
<div class="statusbar">
|
||
<span id="sbTime">13:20</span>
|
||
<span class="sb-icons">
|
||
<svg width="17" height="11" viewBox="0 0 17 11" fill="#fff"><rect x="0" y="7" width="3" height="4" rx="1"/><rect x="4.5" y="4.8" width="3" height="6.2" rx="1"/><rect x="9" y="2.5" width="3" height="8.5" rx="1"/><rect x="13.5" y="0" width="3" height="11" rx="1"/></svg>
|
||
<svg width="16" height="11" viewBox="0 0 16 12" fill="none" stroke="#fff" stroke-width="1.7" stroke-linecap="round"><path d="M1.5 4.2a9.5 9.5 0 0 1 13 0"/><path d="M4 7a6 6 0 0 1 8 0"/><circle cx="8" cy="10" r="1.2" fill="#fff" stroke="none"/></svg>
|
||
<svg width="25" height="12" viewBox="0 0 25 12" fill="none"><rect x=".7" y=".7" width="20" height="10.6" rx="3" stroke="#fff" stroke-opacity=".45" stroke-width="1"/><rect x="2.5" y="2.5" width="13.5" height="7" rx="1.6" fill="#fff"/><path d="M22.8 4v4a2.2 2.2 0 0 0 0-4z" fill="#fff" fill-opacity=".45"/></svg>
|
||
</span>
|
||
</div>
|
||
|
||
<div class="screens">
|
||
|
||
<!-- ============ HOME ============ -->
|
||
<section class="scr on" id="scr-home">
|
||
<div class="map" id="map-home"></div>
|
||
<button class="fl-ava" data-go="account">🧔🏻</button>
|
||
<button class="fl-cross" data-toast="Re-centered to your location">
|
||
<svg width="19" height="19" viewBox="0 0 24 24" fill="none" stroke="#fff" stroke-width="1.9"><circle cx="12" cy="12" r="7"/><circle cx="12" cy="12" r="2.2" fill="#fff" stroke="none"/><path d="M12 1.5v3M12 19.5v3M1.5 12h3M19.5 12h3" stroke-linecap="round"/></svg>
|
||
</button>
|
||
<button class="wtc-pill" data-go="search">
|
||
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="#cfd1d8" stroke-width="2.2" stroke-linecap="round"><circle cx="11" cy="11" r="7"/><path d="m16.5 16.5 4 4"/></svg>
|
||
<span class="lbl">Where to?</span>
|
||
<span class="wtc-now">Now</span>
|
||
</button>
|
||
</section>
|
||
|
||
<!-- ============ SEARCH (Where to?) ============ -->
|
||
<section class="scr" id="scr-search">
|
||
<div class="map" id="map-search"></div>
|
||
<button class="fl-ava" data-go="account">🧔🏻</button>
|
||
<div class="search-card">
|
||
<div class="s-top">
|
||
<button class="s-back" data-go="home" aria-label="Back">
|
||
<svg width="19" height="19" viewBox="0 0 24 24" fill="none" stroke="#fff" stroke-width="2.3" stroke-linecap="round" stroke-linejoin="round"><path d="M15 5.5 8.5 12l6.5 6.5"/></svg>
|
||
</button>
|
||
<input class="s-in" id="destInput" placeholder="Where to?" aria-label="Destination">
|
||
<span class="wtc-now" data-toast="Pickup: Now">Now</span>
|
||
</div>
|
||
<div class="sug-lbl">SUGGESTIONS</div>
|
||
<div class="qpills">
|
||
<button class="qpill" data-dest="Home">
|
||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="#fff" stroke-width="2.1" stroke-linecap="round" stroke-linejoin="round"><path d="m4 10.5 8-6.5 8 6.5V19a1.5 1.5 0 0 1-1.5 1.5H14v-6h-4v6H5.5A1.5 1.5 0 0 1 4 19v-8.5Z"/></svg>
|
||
Home</button>
|
||
<button class="qpill" data-dest="Work">
|
||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="#fff" stroke-width="2.1" stroke-linecap="round" stroke-linejoin="round"><rect x="4" y="7.5" width="16" height="12" rx="2.5"/><path d="M9 7.5V6a2 2 0 0 1 2-2h2a2 2 0 0 1 2 2v1.5M4 12.5h16"/></svg>
|
||
Work</button>
|
||
<button class="qpill" data-dest="San Diego International Airport">
|
||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="#fff" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10.5 20.5 12 15l-6.5 1.5L8 13l-3 .5-1-3 4.5-1L12 4l2.5 2 5-1-1 3 .5 3-3.5 2.5L17 20l-3-.5-1.5-4L10.5 20.5Z"/></svg>
|
||
Airport</button>
|
||
</div>
|
||
<div id="sugList">
|
||
<button class="sug-row" data-dest="Coronado Island">
|
||
<span class="sug-ic"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#cfd1d8" stroke-width="2"><circle cx="12" cy="12" r="8.5"/><path d="M12 7.5V12l3 2" stroke-linecap="round"/></svg></span>
|
||
<span><span class="sug-t">Coronado Island</span><div class="sug-s">Via Coronado Bridge · 3.8 mi</div></span>
|
||
<svg class="sug-ar" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.3" stroke-linecap="round"><path d="M6 18 18 6M9 6h9v9"/></svg>
|
||
</button>
|
||
<button class="sug-row" data-dest="San Diego International Airport">
|
||
<span class="sug-ic"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#cfd1d8" stroke-width="2"><circle cx="12" cy="12" r="8.5"/><path d="M12 7.5V12l3 2" stroke-linecap="round"/></svg></span>
|
||
<span><span class="sug-t">San Diego International Airport</span><div class="sug-s">SAN · 3.2 mi</div></span>
|
||
<svg class="sug-ar" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.3" stroke-linecap="round"><path d="M6 18 18 6M9 6h9v9"/></svg>
|
||
</button>
|
||
<button class="sug-row" data-dest="Petco Park">
|
||
<span class="sug-ic"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#cfd1d8" stroke-width="2"><circle cx="12" cy="12" r="8.5"/><path d="M12 7.5V12l3 2" stroke-linecap="round"/></svg></span>
|
||
<span><span class="sug-t">Petco Park</span><div class="sug-s">100 Park Blvd · 0.8 mi</div></span>
|
||
<svg class="sug-ar" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.3" stroke-linecap="round"><path d="M6 18 18 6M9 6h9v9"/></svg>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- ============ CHOOSE A RIDE ============ -->
|
||
<section class="scr" id="scr-choose">
|
||
<div class="map" id="map-choose"></div>
|
||
<div class="panel">
|
||
<div class="p-head">
|
||
<button class="p-back" data-go="search" aria-label="Back">
|
||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="#fff" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"><path d="M19 12H5m6.5-6.5L5 12l6.5 6.5"/></svg>
|
||
</button>
|
||
<div class="p-title">Choose a ride</div>
|
||
</div>
|
||
<div id="rideOpts">
|
||
<button class="ride-opt on" data-name="Standard">
|
||
<span id="car-opt-0"></span>
|
||
<span><span class="ro-t">Standard <em class="ro-badge" style="font-style:normal">Cheapest</em></span><span class="ro-s" style="display:block">2 min away</span></span>
|
||
<span class="ro-p">$10.99</span>
|
||
</button>
|
||
<button class="ride-opt" data-name="Black SUV">
|
||
<span id="car-opt-1"></span>
|
||
<span><span class="ro-t">Black SUV</span><span class="ro-s" style="display:block">4 min away</span></span>
|
||
<span class="ro-p">$32.86</span>
|
||
</button>
|
||
<button class="ride-opt" data-name="Black">
|
||
<span id="car-opt-2"></span>
|
||
<span><span class="ro-t">Black</span><span class="ro-s" style="display:block">3 min away</span></span>
|
||
<span class="ro-p">$24.98</span>
|
||
</button>
|
||
</div>
|
||
<button class="cta" id="chooseBtn">Choose Standard</button>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- ============ LOOKING FOR DRIVERS ============ -->
|
||
<section class="scr" id="scr-looking">
|
||
<div class="map" id="map-looking"></div>
|
||
<button class="fl-ava" data-go="account">🧔🏻</button>
|
||
<button class="fl-cross" data-toast="Re-centered to your location">
|
||
<svg width="19" height="19" viewBox="0 0 24 24" fill="none" stroke="#fff" stroke-width="1.9"><circle cx="12" cy="12" r="7"/><circle cx="12" cy="12" r="2.2" fill="#fff" stroke="none"/><path d="M12 1.5v3M12 19.5v3M1.5 12h3M19.5 12h3" stroke-linecap="round"/></svg>
|
||
</button>
|
||
<div class="panel">
|
||
<div class="p-head">
|
||
<button class="p-back" data-go="choose" aria-label="Back">
|
||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="#fff" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"><path d="M19 12H5m6.5-6.5L5 12l6.5 6.5"/></svg>
|
||
</button>
|
||
<div class="p-title">Looking for drivers<span class="searching"><i></i><i></i><i></i></span></div>
|
||
</div>
|
||
<div class="drv-row">
|
||
<button class="drv-card" data-toast="Requesting Marcus J.…">
|
||
<div class="dc-ava" style="background:radial-gradient(circle at 32% 26%,#e8c9a8,#c9884f)">👨🏼</div>
|
||
<div class="dc-n">Marcus J.</div><div class="dc-r">★ 4.9</div>
|
||
</button>
|
||
<button class="drv-card on" data-toast="Requesting Sarah K.…">
|
||
<div class="dc-ava" style="background:radial-gradient(circle at 32% 26%,#ffd9c3,#e8926a)">🧔🏻</div>
|
||
<div class="dc-n">Sarah K.</div><div class="dc-r">★ 4.8</div>
|
||
</button>
|
||
<button class="drv-card" data-toast="Requesting David R.…">
|
||
<div class="dc-ava" style="background:radial-gradient(circle at 32% 26%,#cdd8ea,#7d93b5)">👨🏽</div>
|
||
<div class="dc-n">David R.</div><div class="dc-r">★ 4.7</div>
|
||
</button>
|
||
</div>
|
||
<button class="cta dark" data-cancel>Cancel</button>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- ============ DRIVER FOUND ============ -->
|
||
<section class="scr" id="scr-found">
|
||
<div class="map fade-map" id="map-found"></div>
|
||
<div class="panel">
|
||
<div class="p-head">
|
||
<button class="p-back" id="foundBack" aria-label="Back">
|
||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="#fff" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"><path d="M19 12H5m6.5-6.5L5 12l6.5 6.5"/></svg>
|
||
</button>
|
||
<div class="p-title">Driver found!</div>
|
||
</div>
|
||
<div class="found-hero">
|
||
<span id="foundCar"></span>
|
||
<span class="fh-ava">🧔🏻</span>
|
||
</div>
|
||
<div class="fd-row">
|
||
<div>
|
||
<div class="fd-name">Marcus Johnson</div>
|
||
<div class="fd-rate">★ 4.9</div>
|
||
</div>
|
||
<div class="fd-plate">8TRD 420</div>
|
||
</div>
|
||
<div class="fd-arr">
|
||
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="#d7d8dd" stroke-width="2"><circle cx="12" cy="12" r="8.5"/><path d="M12 7.5V12l3 2" stroke-linecap="round"/></svg>
|
||
Arriving in 2 min
|
||
</div>
|
||
<button class="cta" id="acceptBtn">Accept Ride</button>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- ============ ARRIVING / TRIP ============ -->
|
||
<section class="scr" id="scr-arriving">
|
||
<div class="map" id="map-arriving"></div>
|
||
<button class="fl-back" data-go="home" aria-label="Back home">
|
||
<svg width="17" height="17" viewBox="0 0 24 24" fill="none" stroke="#fff" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"><path d="M15 5.5 8.5 12l6.5 6.5"/></svg>
|
||
</button>
|
||
<div class="eta-pill"><span class="sq"></span> Arriving in 6 min</div>
|
||
|
||
<div class="sheet peek" id="tripSheet">
|
||
<div class="grab" id="tripGrab"><i></i></div>
|
||
<div class="sh-pad">
|
||
<!-- compact car card -->
|
||
<div class="carline">
|
||
<div class="carthumb"><span id="arrCar"></span><span class="ct-ava">🧔🏻</span></div>
|
||
<div style="flex:1;min-width:0">
|
||
<div class="cl-t">Toyota Camry · Black</div>
|
||
<div class="cl-sub"><span class="plate-chip">8TRD 420</span><span class="cl-rate">★ 4.9</span></div>
|
||
</div>
|
||
<button class="chat-btn" data-go="chat" aria-label="Chat">
|
||
<svg width="17" height="17" viewBox="0 0 24 24" fill="none" stroke="#fff" stroke-width="2"><path d="M12 4c-4.6 0-8.3 3-8.3 6.8 0 2.1 1.2 4 3.1 5.3l-.8 3 3.3-1.6c.9.2 1.8.4 2.7.4 4.6 0 8.3-3 8.3-6.9S16.6 4 12 4Z"/></svg>
|
||
</button>
|
||
</div>
|
||
|
||
<!-- expanded content -->
|
||
<div class="trip-card">
|
||
<div class="tr-row">
|
||
<span class="tr-dot"></span><span class="tr-line"></span>
|
||
<div><div class="tr-l">Pick up</div><div class="tr-a">234 18th St</div></div>
|
||
<div class="tr-t">6 min</div>
|
||
</div>
|
||
<div class="tr-row">
|
||
<span class="tr-sq"></span>
|
||
<div><div class="tr-l">Drop off</div><div class="tr-a" id="dropTxt">1500 Valencia St</div></div>
|
||
<div class="tr-t">9:41 am</div>
|
||
</div>
|
||
</div>
|
||
<div class="drv-strip">
|
||
<span class="ds-ava">🧔🏻</span>
|
||
<div>
|
||
<div style="font-size:15px;font-weight:780">Marcus Johnson</div>
|
||
<div style="font-size:11.5px;color:var(--mut);margin-top:2px">★ 4.9 · Toyota Camry</div>
|
||
</div>
|
||
<button class="chat-btn" data-go="chat" aria-label="Chat">
|
||
<svg width="17" height="17" viewBox="0 0 24 24" fill="none" stroke="#fff" stroke-width="2"><path d="M12 4c-4.6 0-8.3 3-8.3 6.8 0 2.1 1.2 4 3.1 5.3l-.8 3 3.3-1.6c.9.2 1.8.4 2.7.4 4.6 0 8.3-3 8.3-6.9S16.6 4 12 4Z"/></svg>
|
||
</button>
|
||
</div>
|
||
<button class="cta" data-cancel style="background:#f2f2f4">Cancel ride</button>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- ============ CHAT ============ -->
|
||
<section class="scr" id="scr-chat" style="background:#131315">
|
||
<div class="chat-head">
|
||
<button class="ch-back" data-go="arriving" aria-label="Back">
|
||
<svg width="19" height="19" viewBox="0 0 24 24" fill="none" stroke="#fff" stroke-width="2.3" stroke-linecap="round" stroke-linejoin="round"><path d="M15 5.5 8.5 12l6.5 6.5"/></svg>
|
||
</button>
|
||
<span class="ch-ava">🧔🏻</span>
|
||
<div>
|
||
<div class="ch-name">Marcus Johnson</div>
|
||
<div class="ch-sub">In transit · 6 min away</div>
|
||
</div>
|
||
<button class="ch-call" data-toast="Calling Marcus…" aria-label="Call">
|
||
<svg width="19" height="19" viewBox="0 0 24 24" fill="#fff"><path d="M6.6 3.5c.6-.2 1.3 0 1.7.5l2 2.6c.4.6.4 1.4-.1 1.9l-1.2 1.2c.9 1.9 2.4 3.4 4.3 4.3l1.2-1.2c.5-.5 1.3-.5 1.9-.1l2.6 2c.5.4.7 1.1.5 1.7l-.8 2c-.3.8-1.1 1.3-1.9 1.2C9.9 18.8 5.2 14.1 4.4 7.2c-.1-.8.4-1.6 1.2-1.9l1-.3Z" transform="rotate(12 12 12)"/></svg>
|
||
</button>
|
||
</div>
|
||
<div class="chat-body" id="chatBody">
|
||
<div class="msg">
|
||
<span class="msg-ava">🧔🏻</span>
|
||
<div class="bub them">On my way! I'll be there in about 6 minutes.</div>
|
||
</div>
|
||
<div class="m-time">9:35 AM</div>
|
||
<div class="msg">
|
||
<span class="msg-ava">🧔🏻</span>
|
||
<div class="bub them">Look for the black SUV.</div>
|
||
</div>
|
||
</div>
|
||
<div class="chat-input">
|
||
<div class="ci-pill">
|
||
<input id="msgInput" placeholder="Message…" aria-label="Message">
|
||
</div>
|
||
<button class="ci-up" id="sendBtn" aria-label="Send">
|
||
<svg width="19" height="19" viewBox="0 0 24 24" fill="none" stroke="#fff" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="M12 19V5m-6.5 6.5L12 5l6.5 6.5"/></svg>
|
||
</button>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- ============ ACTIVITY ============ -->
|
||
<section class="scr" id="scr-activity" style="background:#131315">
|
||
<div class="subpage">
|
||
<div class="sp-title">Activity</div>
|
||
<div class="trip-row"><span class="tri-ic" id="tri0"></span><div><div class="tri-t">Coronado Island</div><div class="tri-s">Jul 18 · Standard · Completed</div></div><span class="tri-p">$24.98</span></div>
|
||
<div class="trip-row"><span class="tri-ic" id="tri1"></span><div><div class="tri-t">Petco Park</div><div class="tri-s">Jul 12 · Standard · Completed</div></div><span class="tri-p">$12.40</span></div>
|
||
<div class="trip-row"><span class="tri-ic" id="tri2"></span><div><div class="tri-t">SAN Airport</div><div class="tri-s">Jul 6 · Black SUV · Completed</div></div><span class="tri-p">$31.20</span></div>
|
||
<div class="trip-row"><span class="tri-ic" id="tri3"></span><div><div class="tri-t">Gaslamp Quarter</div><div class="tri-s">Jun 28 · Standard · Completed</div></div><span class="tri-p">$9.60</span></div>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- ============ ACCOUNT ============ -->
|
||
<section class="scr" id="scr-account" style="background:#131315">
|
||
<div class="subpage">
|
||
<div class="sp-title">Account</div>
|
||
<div class="acc-card">
|
||
<span class="acc-ava">🧔🏻</span>
|
||
<div>
|
||
<div style="font-size:18px;font-weight:800">Alex Turner</div>
|
||
<div style="font-size:12px;color:var(--mut);margin-top:3px">★ 4.9 rider · San Diego</div>
|
||
</div>
|
||
</div>
|
||
<button class="acc-row" data-toast="Opening wallet…">
|
||
<span class="acc-ic"><svg width="17" height="17" viewBox="0 0 24 24" fill="none" stroke="#fff" stroke-width="2"><rect x="3" y="6.5" width="18" height="13" rx="3"/><path d="M16 13.2h2.4M3 10h18" stroke-linecap="round"/></svg></span>
|
||
Wallet · Visa ••4242 <span class="chev">›</span>
|
||
</button>
|
||
<button class="acc-row" data-go="activity">
|
||
<span class="acc-ic"><svg width="17" height="17" viewBox="0 0 24 24" fill="none" stroke="#fff" stroke-width="2" stroke-linecap="round"><path d="M7 3.5h7l4 4v12a1.5 1.5 0 0 1-1.5 1.5h-9A1.5 1.5 0 0 1 6 19.5v-14A1.5 1.5 0 0 1 7.5 3.5Z"/><path d="M9.5 12h5M9.5 15.5h5"/></svg></span>
|
||
Trip history <span class="chev">›</span>
|
||
</button>
|
||
<button class="acc-row" data-toast="Opening settings…">
|
||
<span class="acc-ic"><svg width="17" height="17" viewBox="0 0 24 24" fill="none" stroke="#fff" stroke-width="2" stroke-linecap="round"><circle cx="12" cy="12" r="3"/><path d="M12 2.8v2.4M12 18.8v2.4M2.8 12h2.4M18.8 12h2.4M5 5l1.7 1.7M17.3 17.3 19 19M19 5l-1.7 1.7M6.7 17.3 5 19"/></svg></span>
|
||
Settings <span class="chev">›</span>
|
||
</button>
|
||
<button class="acc-row" data-toast="Opening help…">
|
||
<span class="acc-ic"><svg width="17" height="17" viewBox="0 0 24 24" fill="none" stroke="#fff" stroke-width="2" stroke-linecap="round"><circle cx="12" cy="12" r="9"/><path d="M9.6 9.4a2.6 2.6 0 0 1 5 .9c0 1.6-2.4 2-2.4 3.2M12.1 17h.01"/></svg></span>
|
||
Help <span class="chev">›</span>
|
||
</button>
|
||
<button class="acc-row" data-toast="Signed out" style="color:#ff8d8d">
|
||
<span class="acc-ic"><svg width="17" height="17" viewBox="0 0 24 24" fill="none" stroke="#ff8d8d" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M14 4h-7a1.5 1.5 0 0 0-1.5 1.5v13A1.5 1.5 0 0 0 7 20h7M10 12h11m-3.5-3.5L21 12l-3.5 3.5"/></svg></span>
|
||
Sign out
|
||
</button>
|
||
</div>
|
||
</section>
|
||
|
||
</div><!-- /screens -->
|
||
|
||
<!-- TAB BAR -->
|
||
<nav class="tabbar" id="tabbar">
|
||
<button class="tb on" data-go="home">
|
||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.1" stroke-linecap="round" stroke-linejoin="round"><path d="m4 10.5 8-6.5 8 6.5V19a1.5 1.5 0 0 1-1.5 1.5H14v-6h-4v6H5.5A1.5 1.5 0 0 1 4 19v-8.5Z"/></svg>
|
||
<span class="tl">Home</span>
|
||
</button>
|
||
<button class="tb" data-go="activity">
|
||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.1" stroke-linecap="round" stroke-linejoin="round"><path d="M7.5 3.5h7l4 4v12a1.5 1.5 0 0 1-1.5 1.5h-9A1.5 1.5 0 0 1 6 19.5v-14A1.5 1.5 0 0 1 7.5 3.5Z"/><path d="M10 11h4.5M10 14.5h4.5" opacity=".9"/></svg>
|
||
<span class="tl">Activity</span>
|
||
</button>
|
||
<button class="tb" data-go="account">
|
||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.1" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="9"/><circle cx="12" cy="9" r="3"/><path d="M6.3 18.2c1.2-2.4 3.2-3.6 5.7-3.6s4.5 1.2 5.7 3.6"/></svg>
|
||
<span class="tl">Account</span>
|
||
</button>
|
||
</nav>
|
||
|
||
<div class="toast" id="toast">Done</div>
|
||
<div class="home-ind"></div>
|
||
</div>
|
||
</div>
|
||
|
||
<script>
|
||
/* ==================================================
|
||
MAP RENDERER — hand-drawn dark city map (SVG)
|
||
================================================== */
|
||
const AVES = [[44,'Third Ave'],[96,'Fourth Ave'],[148,'Fifth Ave'],[200,'Sixth Ave'],[252,'Seventh Ave'],[304,'Eighth Ave'],[356,'Ninth Ave']];
|
||
const STS = [[90,'Elm St'],[150,'Beech St'],[232,'A St'],[300,'B St'],[372,'C St'],[448,'E St'],[528,'F St'],[610,'G St']];
|
||
|
||
function streets(){
|
||
let s = '';
|
||
AVES.forEach(([x]) => { s += `<line x1="${x}" y1="-20" x2="${x}" y2="880" stroke="#43464f" stroke-width="4"/>`; });
|
||
STS.forEach(([y]) => { s += `<line x1="-20" y1="${y}" x2="410" y2="${y}" stroke="#43464f" stroke-width="${y===300||y===150?6:4}"/>`; });
|
||
return s;
|
||
}
|
||
function labels(){
|
||
let s = '';
|
||
AVES.forEach(([x,n],i) => { if(i!==0&&i!==6) s += `<text x="${x-5}" y="${330+(i%3)*170}" fill="#787d89" font-size="10" letter-spacing="1" font-weight="600" transform="rotate(-90 ${x-5} ${330+(i%3)*170})">${n}</text>`; });
|
||
STS.forEach(([y,n],i) => { if(i%2===0) s += `<text x="118" y="${y-7}" fill="#787d89" font-size="10" letter-spacing="1" font-weight="600">${n}</text>`; });
|
||
s += `<text x="96" y="342" fill="#8a8f9b" font-size="11" letter-spacing="2.5" font-weight="800" opacity=".9">LITTLE ITALY</text>`;
|
||
s += `<text x="222" y="648" fill="#8a8f9b" font-size="11" letter-spacing="2.5" font-weight="800" opacity=".9">EAST VILLAGE</text>`;
|
||
s += `<text x="112" y="742" fill="#787d89" font-size="10" letter-spacing="2" font-weight="700" opacity=".8">GASLAMP</text>`;
|
||
s += `<text x="306" y="86" fill="#787d89" font-size="10" letter-spacing="1" font-weight="600">Balboa Park</text>`;
|
||
return s;
|
||
}
|
||
function blocks(){
|
||
let s = '';
|
||
const B = [[16,104,20,34],[60,104,24,30],[112,166,24,52],[164,104,24,34],[216,166,24,52],[268,104,24,30],[320,166,24,34],
|
||
[60,246,24,40],[164,246,24,40],[268,246,24,40],[112,318,24,40],[216,318,24,40],[320,318,24,40],
|
||
[60,388,24,46],[164,388,24,46],[268,388,24,46],[112,464,24,50],[216,464,24,50],[320,464,24,50]];
|
||
B.forEach(([x,y,w,h]) => { s += `<rect x="${x}" y="${y}" width="${w}" height="${h}" rx="5" fill="#3c3f48"/>`; });
|
||
return s;
|
||
}
|
||
function topCar(x,y,r,body,glass){
|
||
return `<g transform="translate(${x} ${y}) rotate(${r})">
|
||
<rect x="-13" y="-24" width="26" height="48" rx="9.5" fill="${body}" stroke="rgba(0,0,0,.4)" stroke-width="1.2"/>
|
||
<rect x="-9.5" y="-15" width="19" height="9" rx="3.5" fill="${glass}"/>
|
||
<rect x="-9.5" y="8" width="19" height="8" rx="3.5" fill="${glass}" opacity=".85"/>
|
||
<rect x="-13" y="-24" width="26" height="5" rx="2.5" fill="rgba(255,255,255,.14)"/>
|
||
<rect x="-11" y="20" width="7" height="2.6" rx="1.3" fill="#ffd66b" opacity=".9"/>
|
||
<rect x="4" y="20" width="7" height="2.6" rx="1.3" fill="#ffd66b" opacity=".9"/>
|
||
</g>`;
|
||
}
|
||
function dot(x,y){
|
||
return `<circle cx="${x}" cy="${y}" r="11" fill="#000" stroke="#fff" stroke-width="3.2"/><circle cx="${x}" cy="${y}" r="20" fill="none" stroke="rgba(255,255,255,.25)" stroke-width="1.4"/>`;
|
||
}
|
||
function route(d){
|
||
return `<path d="${d}" fill="none" stroke="rgba(0,0,0,.45)" stroke-width="8" stroke-linecap="round" stroke-linejoin="round"/>
|
||
<path d="${d}" fill="none" stroke="#fff" stroke-width="4.6" stroke-linecap="round" stroke-linejoin="round"/>`;
|
||
}
|
||
function chip(x,y,text){
|
||
const w = text.length*7.6+26;
|
||
return `<g><rect x="${x}" y="${y}" width="${w}" height="37" rx="10" fill="#101114"/>
|
||
<text x="${x+13}" y="${y+23.5}" fill="#fff" font-size="13.5" font-weight="700">${text}</text></g>`;
|
||
}
|
||
function mapSVG(inner){
|
||
return `<svg viewBox="0 0 390 860" preserveAspectRatio="xMidYMid slice">
|
||
<rect width="390" height="860" fill="#33363d"/>
|
||
<path d="M0 528 Q 66 546 96 596 Q 128 648 112 706 Q 96 768 52 812 Q 28 836 0 850 Z" fill="#2b2e35"/>
|
||
<path d="M0 528 Q 66 546 96 596 Q 128 648 112 706 Q 96 768 52 812 Q 28 836 0 850" fill="none" stroke="#484c56" stroke-width="2.4"/>
|
||
<path d="M390 690 Q 356 720 348 760 Q 342 806 390 830 Z" fill="#2b2e35"/>
|
||
<path d="M0 -20 Q 190 60 390 26 L 390 -20 Z" fill="#2b2e35"/>
|
||
<path d="M0 -14 Q 190 66 390 32" fill="none" stroke="#484c56" stroke-width="5"/>
|
||
${blocks()}${streets()}${labels()}
|
||
${inner}
|
||
<text x="14" y="846" fill="rgba(255,255,255,.85)" font-size="17" font-weight="700">Google</text>
|
||
</svg>`;
|
||
}
|
||
|
||
/* per-screen maps */
|
||
document.getElementById('map-home').innerHTML = mapSVG(
|
||
topCar(150,286,-38,'#dddfe4','#2c2f36') + topCar(256,326,80,'#17181c','#33363d') +
|
||
topCar(66,470,6,'#dddfe4','#2c2f36') + topCar(300,470,-92,'#dddfe4','#2c2f36') +
|
||
topCar(120,676,-70,'#17181c','#33363d') + topCar(330,236,4,'#17181c','#33363d') +
|
||
dot(200,430)
|
||
);
|
||
document.getElementById('map-search').innerHTML = mapSVG(
|
||
topCar(150,286,-38,'#dddfe4','#2c2f36') + topCar(300,470,-92,'#dddfe4','#2c2f36') +
|
||
topCar(330,236,4,'#17181c','#33363d') + dot(200,430)
|
||
);
|
||
const tripRoute = 'M200 380 V 330 H 302 V 560 Q 302 650 238 700 Q 180 748 174 880';
|
||
document.getElementById('map-choose').innerHTML = mapSVG(route(tripRoute) + dot(200,380));
|
||
document.getElementById('map-looking').innerHTML = mapSVG(
|
||
route(tripRoute) + dot(200,380) +
|
||
topCar(200,238,0,'#dddfe4','#2c2f36') + topCar(96,150,90,'#dddfe4','#2c2f36') +
|
||
topCar(330,330,12,'#17181c','#33363d') + topCar(148,70,90,'#dddfe4','#2c2f36')
|
||
);
|
||
document.getElementById('map-found').innerHTML = mapSVG(
|
||
route(tripRoute) + dot(200,380) +
|
||
topCar(200,238,0,'#fff','#3a3d45') + topCar(96,150,90,'#fff','#3a3d45') + topCar(330,330,12,'#44474f','#17181c')
|
||
);
|
||
document.getElementById('map-arriving').innerHTML = mapSVG(
|
||
route('M330 150 H 200 V 420 H 122') + dot(122,420) +
|
||
chip(8,395,'Pickup on 234 18th St') +
|
||
topCar(330,150,90,'#17181c','#3a3d45') + topCar(200,70,0,'#dddfe4','#2c2f36')
|
||
);
|
||
|
||
/* ==================================================
|
||
SIDE-VIEW CARS for panels
|
||
================================================== */
|
||
function carSide(body, glass, rim, suv){
|
||
const roofTop = suv ? 6 : 14;
|
||
const cabin = suv
|
||
? `<path d="M34 42 V 24 Q 34 18 40 17 H 78 Q 86 17 90 24 L 94 42 Z" fill="${body}"/>
|
||
<rect x="39" y="20.5" width="20" height="15" rx="3" fill="${glass}"/>
|
||
<rect x="63" y="20.5" width="18" height="15" rx="3" fill="${glass}"/>`
|
||
: `<path d="M38 42 C 42 26 52 ${roofTop} 64 ${roofTop} C 78 ${roofTop} 86 28 92 42 Z" fill="${body}"/>
|
||
<path d="M46 40 C 49 29 55 22 64 21.5 V 40 Z" fill="${glass}"/>
|
||
<path d="M68 21.5 C 77 22 83 29 86 40 H 68 Z" fill="${glass}"/>`;
|
||
return `<svg viewBox="0 0 128 62" style="width:100%">
|
||
<ellipse cx="64" cy="55" rx="56" ry="4.5" fill="rgba(0,0,0,.4)"/>
|
||
<path d="M10 46 Q 8 36 22 33 L 34 31 Q 40 42 34 42 L 94 42 Q 88 42 94 31 L 106 33 Q 120 36 118 46 Q 117 50 111 50 H 17 Q 11 50 10 46 Z" fill="${body}" stroke="rgba(0,0,0,.25)"/>
|
||
${cabin}
|
||
<rect x="12" y="38" width="9" height="4" rx="2" fill="#ffd66b"/>
|
||
<rect x="107" y="38" width="9" height="4" rx="2" fill="#ff8080"/>
|
||
<circle cx="38" cy="47" r="10.5" fill="#141519"/><circle cx="38" cy="47" r="6" fill="${rim}"/><circle cx="38" cy="47" r="2.4" fill="#c9ccd2"/>
|
||
<circle cx="92" cy="47" r="10.5" fill="#141519"/><circle cx="92" cy="47" r="6" fill="${rim}"/><circle cx="92" cy="47" r="2.4" fill="#c9ccd2"/>
|
||
</svg>`;
|
||
}
|
||
const sedSilver = carSide('#c9ccd2','#31343c','#5a5e68',false);
|
||
const suvBlack = carSide('#202227','#41454f','#33363d',true);
|
||
const suvWhite = carSide('#e8eaee','#2f323a','#9ea3ad',true);
|
||
const sedWhite = carSide('#e8eaee','#2f323a','#9ea3ad',false);
|
||
const camry = carSide('#232529','#41454f','#3c404a',false);
|
||
|
||
document.getElementById('car-opt-0').innerHTML = sedSilver;
|
||
document.getElementById('car-opt-1').innerHTML = suvBlack;
|
||
document.getElementById('car-opt-2').innerHTML = suvWhite;
|
||
document.getElementById('foundCar').innerHTML = `<span style="display:block;width:120px;margin:8px auto 0">${sedWhite}</span>`;
|
||
document.getElementById('arrCar').innerHTML = camry;
|
||
[0,1,2,3].forEach(i => document.getElementById('tri'+i).innerHTML =
|
||
`<span style="display:block;width:38px">${carSide('#4a4d55','#22242a','#33363d',i===2)}</span>`);
|
||
document.querySelectorAll('#rideOpts .ride-opt > span:first-child').forEach(s => s.style.cssText = 'width:84px;flex:0 0 84px');
|
||
|
||
/* ==================================================
|
||
ROUTER + FLOW
|
||
================================================== */
|
||
const TIMES = {home:'13:20',search:'13:20',activity:'13:20',account:'13:20',choose:'13:27',looking:'13:27',found:'13:27',arriving:'13:27',chat:'13:27'};
|
||
const NAVSCREENS = ['home','search','activity','account'];
|
||
const tabbar = document.getElementById('tabbar');
|
||
const sbTime = document.getElementById('sbTime');
|
||
let foundTimer = null;
|
||
|
||
function go(name){
|
||
document.querySelectorAll('.scr').forEach(s => s.classList.toggle('on', s.id === 'scr-'+name));
|
||
document.querySelectorAll('.tabbar .tb').forEach(t => t.classList.toggle('on', t.dataset.go === name));
|
||
tabbar.classList.toggle('hide', !NAVSCREENS.includes(name));
|
||
sbTime.textContent = TIMES[name] || '13:27';
|
||
if(name === 'looking'){ clearTimeout(foundTimer); foundTimer = setTimeout(() => go('found'), 2800); }
|
||
else clearTimeout(foundTimer);
|
||
if(name === 'arriving'){ sheetPeek(); }
|
||
}
|
||
document.querySelectorAll('[data-go]').forEach(el => el.addEventListener('click', e => { e.stopPropagation(); go(el.dataset.go); }));
|
||
|
||
/* toast */
|
||
const toast = document.getElementById('toast');
|
||
let tt;
|
||
function showToast(m){ toast.textContent = m; toast.classList.add('show'); clearTimeout(tt); tt = setTimeout(()=>toast.classList.remove('show'), 2100); }
|
||
document.querySelectorAll('[data-toast]').forEach(el => el.addEventListener('click', () => showToast(el.dataset.toast)));
|
||
|
||
/* destination -> choose */
|
||
const dropTxt = document.getElementById('dropTxt');
|
||
document.querySelectorAll('[data-dest]').forEach(b => b.addEventListener('click', () => {
|
||
const d = b.dataset.dest;
|
||
document.getElementById('destInput').value = d;
|
||
dropTxt.textContent = d;
|
||
go('choose');
|
||
}));
|
||
|
||
/* choose ride selection */
|
||
const chooseBtn = document.getElementById('chooseBtn');
|
||
document.querySelectorAll('#rideOpts .ride-opt').forEach(o => o.addEventListener('click', () => {
|
||
document.querySelectorAll('#rideOpts .ride-opt').forEach(x => x.classList.remove('on'));
|
||
o.classList.add('on');
|
||
chooseBtn.textContent = 'Choose ' + o.dataset.name;
|
||
}));
|
||
chooseBtn.addEventListener('click', () => go('looking'));
|
||
|
||
/* looking -> driver cards selectable */
|
||
document.querySelectorAll('.drv-card').forEach(c => c.addEventListener('click', () => {
|
||
document.querySelectorAll('.drv-card').forEach(x => x.classList.remove('on'));
|
||
c.classList.add('on');
|
||
clearTimeout(foundTimer);
|
||
go('found');
|
||
}));
|
||
|
||
/* found */
|
||
document.getElementById('foundBack').addEventListener('click', () => go('choose'));
|
||
document.getElementById('acceptBtn').addEventListener('click', () => { go('arriving'); showToast('Marcus is on the way 🚗'); });
|
||
|
||
/* cancels */
|
||
document.querySelectorAll('[data-cancel]').forEach(b => b.addEventListener('click', () => {
|
||
clearTimeout(foundTimer);
|
||
go('home');
|
||
showToast('Ride cancelled');
|
||
}));
|
||
|
||
/* ==================================================
|
||
ARRIVING SHEET — peek / expanded drag (Y only)
|
||
================================================== */
|
||
const sheet = document.getElementById('tripSheet');
|
||
const grab = document.getElementById('tripGrab');
|
||
let mode = 'peek';
|
||
function setSheet(m){ mode = m; sheet.classList.toggle('peek', m==='peek'); sheet.classList.toggle('expanded', m==='expanded'); }
|
||
function sheetPeek(){ setSheet('peek'); }
|
||
let drag=false, sy=0, cy=0, ly=0, vy=0, lt=0, moved=false;
|
||
grab.addEventListener('pointerdown', e => {
|
||
drag=true; moved=false; grab.setPointerCapture(e.pointerId);
|
||
sy=cy=ly=e.clientY; vy=0; lt=performance.now(); sheet.classList.add('drag');
|
||
});
|
||
grab.addEventListener('pointermove', e => {
|
||
if(!drag) return;
|
||
const now = performance.now();
|
||
cy = e.clientY;
|
||
vy = (cy-ly)/Math.max(1, now-lt); ly = cy; lt = now;
|
||
const dy = cy - sy;
|
||
if(Math.abs(dy) > 8) moved = true;
|
||
let ty = 0;
|
||
if(mode==='peek') ty = Math.max(dy, -140);
|
||
else ty = Math.max(dy, 0);
|
||
sheet.style.transform = `translateY(${ty}px)`;
|
||
});
|
||
function endDrag(){
|
||
if(!drag) return;
|
||
drag=false; sheet.classList.remove('drag'); sheet.style.transform='';
|
||
const dy = cy - sy;
|
||
const up = dy < -26 || (dy < -12 && vy < -0.2);
|
||
const down = dy > 36 || (dy > 18 && vy > 0.28);
|
||
if(mode==='peek' && up) return setSheet('expanded');
|
||
if(mode==='expanded' && down) return setSheet('peek');
|
||
setSheet(mode);
|
||
}
|
||
grab.addEventListener('pointerup', endDrag);
|
||
grab.addEventListener('pointercancel', endDrag);
|
||
grab.addEventListener('click', () => { if(!moved) setSheet(mode==='peek' ? 'expanded' : 'peek'); });
|
||
|
||
/* ==================================================
|
||
CHAT
|
||
==================================================*/
|
||
const chatBody = document.getElementById('chatBody');
|
||
const msgInput = document.getElementById('msgInput');
|
||
const replies = ["Almost there — one block away 👍","I'm by the corner on 18th St","Can you see the black SUV?","On my way!","Great, see you in a sec 🚗"];
|
||
let ri = 0;
|
||
function addMsg(text, me){
|
||
const row = document.createElement('div');
|
||
row.className = 'msg' + (me ? ' me' : '');
|
||
row.innerHTML = me ? `<div class="bub me"></div>` : `<span class="msg-ava">🧔🏻</span><div class="bub them"></div>`;
|
||
row.querySelector('.bub').textContent = text;
|
||
chatBody.appendChild(row);
|
||
chatBody.scrollTop = chatBody.scrollHeight;
|
||
}
|
||
function send(){
|
||
const v = msgInput.value.trim();
|
||
if(!v) return;
|
||
msgInput.value = '';
|
||
addMsg(v, true);
|
||
setTimeout(() => addMsg(replies[ri++ % replies.length], false), 950);
|
||
}
|
||
document.getElementById('sendBtn').addEventListener('click', send);
|
||
msgInput.addEventListener('keydown', e => { if(e.key === 'Enter') send(); });
|
||
</script>
|
||
</body>
|
||
</html>
|