makepad-ports/ride.html
andodeki 7ddd75fb70 Makepad 2.0 ports: rider, koboyo, insurance (+ HTML sources, assessments)
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.
2026-08-04 05:07:18 +00:00

841 lines
48 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!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>