/* Phone mockup: a foreground phone whose screen auto-advances through the booking flow (Search → Driver result → Confirmed) like a live screen recording, plus a static trip-list phone behind it for depth. The driver result screen has a real drop-in photo slot. */ function FareRow({ label, value, accent }) { return (
{label} {value}
); } function StatusBar() { return (
9:41 ●●●5G
); } /* ── Screen 1: search ── */ function ScreenSearch() { return (
Πού πας;
Αθήνα
Θεσσαλονίκη
Παρ 18 Ιουλ
1 θέση
Πρόσφατα
{[['Αθήνα → Θεσσαλονίκη', '€22.00'], ['Θεσ/νίκη → Αθήνα', '€22.00']].map(([r, p]) => (
{r}{p}
))}
); } /* ── Screen 2: driver result + Open Fare (with real photo slot) ── */ function ScreenDriver() { const { IconStar, IconGauge, IconClock } = window; return (
Αθήνα Θεσσαλονίκη
{/* real driver photo (baked default, user can drop their own) */}
Γιώργος Κ.
4.9 98%
✓ Verified
08:00 → 12:50 · VW Passat
Open Fare™
); } /* ── Screen 3: confirmed / escrow ── */ function ScreenConfirmed() { return (
Κρατήθηκε!
Η θέση σου με τον Γιώργο επιβεβαιώθηκε.
🔒 Αποδέσμευση μετά την άφιξη
); } function PhoneScreens() { const A = window.__ELLA_APPSHOTS__ || {}; const screens = [A.offer, A.trips, A.passenger].filter(Boolean); const [idx, setIdx] = React.useState(0); const SCREEN_H = 486; React.useEffect(() => { const reduce = window.matchMedia('(prefers-reduced-motion: reduce)').matches; if (reduce || screens.length < 2) return; const id = setInterval(() => setIdx(i => (i + 1) % screens.length), 3000); return () => clearInterval(id); }, [screens.length]); return (
{/* white strip so the app title clears the notch */}
{screens.map((src, i) => (
ellaride app
))}
{/* screen dots */}
{screens.map((_, i) => ( ))}
); } /* Background phone — real app screen */ function TripListPhone({ width }) { const A = window.__ELLA_APPSHOTS__ || {}; return (
); } function TripListPhoneOld({ width }) { const D = window.__ELLA_DRIVERS__ || {}; const trips = [ { from: 'Αθήνα', to: 'Θεσσαλονίκη', date: 'Παρ 18 Ιουλ', seats: 3, price: '€18.50', photo: D.d4, initials: 'ΓΚ', color: '#1486E6' }, { from: 'Αθήνα', to: 'Πάτρα', date: 'Σάβ 19 Ιουλ', seats: 2, price: '€9.50', photo: D.d2, initials: 'ΜΠ', color: '#7A5AF8' }, { from: 'Θεσ', to: 'Ιωάννινα', date: 'Κυρ 20 Ιουλ', seats: 1, price: '€10.50', photo: D.d3, initials: 'ΑΔ', color: '#00A86B' }, ]; return (
9:415G ●●●
Πού θέλεις να πας;
{trips.map((tr, i) => (
{tr.photo ? : tr.initials}
{tr.from} → {tr.to}
{tr.date} · {tr.seats} θέσεις
{tr.price}
))}
); } function PhoneMockup({ width = 300 }) { const bgWidth = Math.round(width * 0.82); const wrapRef = React.useRef(null); const [rot, setRot] = React.useState({ x: 0, y: 0, gx: 50, gy: 0, active: false }); // Interactive 3D — cursor-tracked rotation with smooth spring + idle float React.useEffect(() => { const reduce = window.matchMedia('(prefers-reduced-motion: reduce)').matches; const el = wrapRef.current; if (!el) return; let target = { x: 0, y: 0 }; let cur = { x: 0, y: 0 }; let hovering = false; let raf; const onMove = (e) => { const rect = el.getBoundingClientRect(); const px = (e.clientX - rect.left) / rect.width; // 0..1 const py = (e.clientY - rect.top) / rect.height; // 0..1 target.y = (px - 0.5) * 26; // rotateY target.x = -(py - 0.5) * 20; // rotateX hovering = true; }; const onLeave = () => { hovering = false; target = { x: 0, y: 0 }; }; const tick = () => { // ease toward target cur.x += (target.x - cur.x) * 0.09; cur.y += (target.y - cur.y) * 0.09; // idle float when not hovering let floatX = 0, floatY = 0; if (!hovering && !reduce) { const t = performance.now() / 1000; floatY = Math.sin(t * 0.8) * 3.2; floatX = Math.cos(t * 0.6) * 1.6; } const ry = cur.y + floatX; const rx = cur.x + floatY * 0.2; setRot({ x: rx, y: ry, gx: 50 + cur.y * 1.6, gy: -cur.x * 1.6, active: hovering, floatY, }); raf = requestAnimationFrame(tick); }; el.addEventListener('mousemove', onMove); el.addEventListener('mouseleave', onLeave); raf = requestAnimationFrame(tick); return () => { el.removeEventListener('mousemove', onMove); el.removeEventListener('mouseleave', onLeave); cancelAnimationFrame(raf); }; }, []); const floatY = rot.floatY || 0; return (
{/* background phone */}
{/* foreground phone — interactive 3D */}
{/* notch */}
{/* side button highlights for depth */}
{/* moving glare */}
{/* hint chip */}
↔ move to explore
); } window.PhoneMockup = PhoneMockup;