/* 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 (
Πού πας;
Αναζήτηση
Πρόσφατα
{[['Αθήνα → Θεσσαλονίκη', '€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) */}
✓ Verified
08:00 → 12:50
· VW Passat
Κράτηση θέσης
);
}
/* ── 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) => (
))}
{/* 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:41 5G ●●●
{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 */}
{/* hint chip */}
↔ move to explore
);
}
window.PhoneMockup = PhoneMockup;